Dynamically create subclass which overrides virtua

2019-08-02 08:19发布

问题:

I have class and interface:

public class TestClass : ITestInterface
{
    public int GetStatus()
    {
        return -1;
    }
}

public interface ITestInterface
{
    int GetStatus();
}

and I would like to dynamically create subclass of TestClass which will look like:

public class TestClass2 : TestClass
{
    public new int GetStatus()
    {
        return base.GetStatus();
    }
}

I have some code which can create subclasses and overrides all virtual methods but when method is virtual final (GetStatus) I'm getting:

"Declaration referenced in a method implementation cannot be a final method."

Any ideas how it can be done?

PS: I can post code mentioned if you'd like.

EDIT 1:

'Some code':

    public static T GetSubClass<T>() where T : class
    {
        var builder = DefineType<T>();
        DefineOverrideMethods(builder, typeof(T));
        var type = CreateType(builder);            
        return (T)Activator.CreateInstance(type);            
    }

    private static TypeBuilder DefineType<T>() where T : class
    {
        return _moduleBuilder.DefineType("Proxy_" + typeof (T).Name,
            TypeAttributes.Sealed | TypeAttributes.Class | TypeAttributes.Public, typeof (T));
    }

    private static void DefineOverrideMethods(TypeBuilder builder, Type type)
    {
        foreach (var virtualMethodInfo in GetVirtualMethods(type))
        {
            var parameters = GetMethodParametersTypes(virtualMethodInfo);
            var newMethodInfo = DefineNewVirtualMethod(builder, virtualMethodInfo, parameters);

            var il = newMethodInfo.GetILGenerator();
            var local = EmitCreateLocal(il, newMethodInfo);

            EmitCallBaseMethod(il, virtualMethodInfo);
            EmitSaveReturnToLocal(il, local);
            EmitReturnMethod(il, virtualMethodInfo, local);

            builder.DefineMethodOverride(newMethodInfo, virtualMethodInfo);
        }
    }

    private static IEnumerable<MethodInfo> GetVirtualMethods(Type type)
    {
        return type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(q => q.IsVirtual);
    }

    private static Type[] GetMethodParametersTypes(MethodInfo virtualMethodInfo)
    {
        return virtualMethodInfo.GetParameters().Select(q => q.ParameterType).ToArray();
    }   

    private static MethodBuilder DefineNewVirtualMethod(TypeBuilder builder, MethodInfo virtualMethodInfo, Type[] parameters)
    {
        return builder.DefineMethod(virtualMethodInfo.Name,
            MethodAttributes.Public | MethodAttributes.Virtual,
            virtualMethodInfo.ReturnType, parameters);
    }

    private static void EmitSaveReturnToLocal(ILGenerator il, LocalBuilder local)
    {
        il.Emit(OpCodes.Stloc_S, local);
        il.Emit(OpCodes.Ldloc_S, local);
    }

    private static LocalBuilder EmitCreateLocal(ILGenerator il, MethodBuilder newMethodInfo)
    {
        return il.DeclareLocal(newMethodInfo.ReturnType);
    }

    private static Type CreateType(TypeBuilder builder)
    {
        builder.DefineDefaultConstructor(MethodAttributes.Public);
        var type = builder.CreateType();
        return type;
    }

    private static void EmitReturnMethod(ILGenerator il, MethodInfo methodInfo, LocalBuilder local)
    {
        il.Emit(OpCodes.Ldloc_S, local);
        il.Emit(OpCodes.Ret);
    }

    private static void EmitCallBaseMethod(ILGenerator il, MethodInfo virtualMethodInfo)
    {
        ushort index = 0;
        while (index < virtualMethodInfo.GetParameters().Length + 1)
            il.Emit(OpCodes.Ldarg, index++);

        il.Emit(OpCodes.Call, virtualMethodInfo);
    }           

Exception is throwed at var type = builder.CreateType();

EDIT 2: @Rahul: Language is C# and as you can see in method 'GetVirtualMethods' there is property 'IsVirtual'. Property 'IsFinal' exists there too and returns true for 'GetStatus' method.

EDIT 3: @Wim.van.Gool: Yes you are right - I can not override non virtual methods. What I am trying to do here is to hide base implementation of 'GetStatus' with dummy implementation which calls base method. Why it would be useful? Imagine that method 'GetSubClass' returns you a class that behaves like base but for example have added log methods before and after base implementation calls.

@Michał Komorowski: Thank you for answer. It works, but only partially. Program do not throws error anymore but in this example:

ITestInterface obj = StaticClass.GetSubClass<TestClass>();
obj.GetStatus();  

'GetStatus' method is called directly from base (TestClass) not from dynamically created subclass. I tried to add: builder.AddInterfaceImplementation(typeof(ITestInterface)); but it did not make any difference.

EDIT 4: @danish: Thank you for answer. It is now working. NewSlot saves problem. Actually whole attribute data: MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual.

Thank you guys for helping. Unfortunately I can not mark both answers as a correct ones but both were significant for resolving problem.

回答1:

Whenever a class implements an interface method, CLR needs that method to be marked as virtual and also final is set to true. However in reality this method is not really virtual. To get actual virtual methods, you need to check IsVirtual for true and IsFinal for false.

In your case, you are actually looking for hiding the intended behaviour. Hence a new method needs to be created and emitted for sub class. From what I understand, NewSlot method attribute will shadow the base class method(can someone confirm? I am not too sure about this one).



回答2:

The problem is that you are actually trying to override a method that is not virtual. You have 2 options. The first one is to make TestClass.GetStatus method virtual. When you do it you will be able to override it. The second solution is to hide this method. It seems to me that your code will work if you:

  1. Remove the following line builder.DefineMethodOverride(newMethodInfo, virtualMethodInfo);
  2. In DefineNewVirtualMethod method use MethodAttributes.HideBySig instead of MethodAttributes.Virtual.