Search code examples
c#cil

Using IL Emit to generate a virtual function which returns an existed object


I want to generate a derived class of BasicModel dynamically in my C# code. And I want to override the virtual property in the derived class to return an existed object.

public class BasicModel
{
    [IgnoreProperty]
    public virtual CloudStorageAccount StorageAccount { get; }
}

And here is the IL part. But I constantly get null from calling that method.

var IlGen2 = newMethod2.GetILGenerator();
Func<CloudStorageAccount> getStoredObj = () => parentModel.StorageAccount;
IlGen2.Emit(OpCodes.Call, getStoredObj.GetMethodInfo());
IlGen2.Emit(OpCodes.Ldobj);
IlGen2.Emit(OpCodes.Ret);

What's the problem? Or is there a better way?

Thanks a lot.


Solution

  • Since the delegate is the same for all instances of the type, I would define a static field that will contain the delegate and use that:

    static void Main(string[] args)
    {
    
        var parentModel = new ContainerCloudStorageAccount { StorageAccount = new CloudStorageAccount() } ;
        var type = CreateType("MyOverride", () => parentModel.StorageAccount);
        var value = (BasicModel)type.GetConstructors().First().Invoke(new object[0]);
        Console.WriteLine(value.StorageAccount);
    }
    
    public static Type CreateType(string name, Func<CloudStorageAccount> getter)
    { 
        AppDomain myDomain = Thread.GetDomain();
        AssemblyBuilder myAsmBuilder = myDomain.DefineDynamicAssembly(new AssemblyName("dynamicTypes"), AssemblyBuilderAccess.Run);
        ModuleBuilder interfaceImplementationModule = myAsmBuilder.DefineDynamicModule("overrrides");
    
        TypeBuilder typeBuilder = interfaceImplementationModule.DefineType(name,
            TypeAttributes.Public | TypeAttributes.Class,
            typeof(BasicModel));
    
        var newMethod2 = typeBuilder.DefineMethod("get_StorageAccount", MethodAttributes.Virtual | MethodAttributes.Public,
                typeof(CloudStorageAccount), Type.EmptyTypes
        );
        typeBuilder.DefineMethodOverride(newMethod2, typeof(BasicModel).GetProperty("StorageAccount").GetGetMethod());
    
        var fieldInfo = typeBuilder.DefineField("getter", typeof(Func<CloudStorageAccount>), FieldAttributes.Static | FieldAttributes.Public);
    
        var IlGen2 = newMethod2.GetILGenerator();
        IlGen2.Emit(OpCodes.Ldsfld, fieldInfo);
        IlGen2.Emit(OpCodes.Call, typeof(Func<CloudStorageAccount>).GetMethod("Invoke"));
        IlGen2.Emit(OpCodes.Ret);
    
        typeBuilder.DefineDefaultConstructor(MethodAttributes.Public);
        var type = typeBuilder.CreateType();
        type.GetField("getter").SetValue(null, getter);
    
        return type;
    }
    

    You original solution does not work because the delegate is more then a method, it is also a target object for that method. So if you just call the method associated with the delegate it will not have the data that you captured in the delegate.