Emit a factory method

112 Views Asked by At

Given the following code:

    public interface IBar1 { }
    public class Bar1 : IBar1 { }
    public interface IBar1Factory { IBar1 Factory(); }

I want to dynamically emit a type that looks like this.

    public class TestBar1Factory : IBar1Factory
    {
        public IBar1 Factory() { return new Bar1(); }
    }

Problem

I was following the tutorial here and I also created the type, manually built it and looked in ILDASM to emit the code exactly as it is inside the compiled DLL. The ILDASM generated code I'm trying to mimic is at the end of this post.

I cannot even create the type, I'm getting this error:

System.TypeLoadException : Signature of the body and declaration in a method implementation do not match.

I assume there is some problem with the enums inside the class definition / ctor / factory method? But I'm trying all sorts of combinations and the problem won't go away :(

My code

    AssemblyName asmName = new AssemblyName { Name = "ToFactoryDynamicAssembly" };
    AssemblyBuilder asmBuilder = AssemblyBuilder.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run);
    ModuleBuilder moduleBuilder = asmBuilder.DefineDynamicModule("ToFactoryModule");
    ConstructorInfo systemObjectCtor = Type.GetType("System.Object").GetConstructor(new Type[0]);

    TypeBuilder facBuilder = moduleBuilder.DefineType($"DynamicFactoryFor{typeof(IBar1Factory).Name}", 
        TypeAttributes.Public 
        | TypeAttributes.AutoClass
        | TypeAttributes.AnsiClass
        | TypeAttributes.BeforeFieldInit);

    facBuilder.AddInterfaceImplementation(typeof(IBar1Factory));
            
    ConstructorBuilder facCtorBuilder = facBuilder.DefineConstructor(MethodAttributes.Public
        | MethodAttributes.HideBySig
        | MethodAttributes.SpecialName
        | MethodAttributes.RTSpecialName, CallingConventions.Standard, null);

    var ctorIL = facCtorBuilder.GetILGenerator();
    ctorIL.Emit(OpCodes.Ldarg_0);
    ctorIL.Emit(OpCodes.Call, systemObjectCtor);
    ctorIL.Emit(OpCodes.Nop);
    ctorIL.Emit(OpCodes.Ret);

    var methodBuilder = facBuilder.DefineMethod("Factory", MethodAttributes.Public
        | MethodAttributes.HideBySig
        | MethodAttributes.NewSlot
        | MethodAttributes.Virtual
        | MethodAttributes.Final, typeof(IBar), null);
            
    var bar1Ctor = typeof(Bar1).GetConstructors()[0];
    var methodIL = methodBuilder.GetILGenerator();
    methodIL.Emit(OpCodes.Nop);
    methodIL.Emit(OpCodes.Newobj, bar1Ctor);
    methodIL.Emit(OpCodes.Stloc_0);
    methodIL.Emit(OpCodes.Ldloc_0);
    methodIL.Emit(OpCodes.Ret);

    facBuilder.DefineMethodOverride(methodBuilder, typeof(IBar1Factory).GetMethod("Factory"));
    var type = facBuilder.CreateType(); // error
            
    var ctor = type.GetConstructors()[0];
    IBar1Factory factory = ctor.Invoke(null) as IBar1Factory;
    IBar1 bar1 = factory.Factory();

Working IL code from ILDASM (from DLL compiled from Visual Studio)

For TestBar1Factory the ILDASM generates this.

CLASS DEFINITION

.class public auto ansi beforefieldinit AddFactoryExtension.Tests.TestBar1Factory
       extends [System.Runtime]System.Object
       implements AddFactoryExtension.Tests.IBar1Factory
{
} // end of class AddFactoryExtension.Tests.TestBar1Factory

CTOR

.method public hidebysig specialname rtspecialname 
        instance void  .ctor() cil managed
{
  // Code size       8 (0x8)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  call       instance void [System.Runtime]System.Object::.ctor()
  IL_0006:  nop
  IL_0007:  ret
} // end of method TestBar1Factory::.ctor

FACTORY METHOD

.method public hidebysig newslot virtual final 
        instance class AddFactoryExtension.Tests.IBar1 
        Factory() cil managed
{
  // Code size       11 (0xb)
  .maxstack  1
  .locals init (class AddFactoryExtension.Tests.IBar1 V_0)
  IL_0000:  nop
  IL_0001:  newobj     instance void AddFactoryExtension.Tests.Bar1::.ctor()
  IL_0006:  stloc.0
  IL_0007:  br.s       IL_0009
  IL_0009:  ldloc.0
  IL_000a:  ret
} // end of method TestBar1Factory::Factory
1

There are 1 best solutions below

0
Hasan Emrah Süngü On BEST ANSWER

I see the following errors in your code. After fixing those I was able to run your without any problems.

var methodBuilder = facBuilder.DefineMethod("Factory", ..., typeof(IBar), null);

The return type must be of typeof(IBar1). It is also a good practice to pass Type.EmptyTypes instead of null.

And in the following code, you have Stloc but it requires a target local variable.

var methodIL = methodBuilder.GetILGenerator();
methodIL.Emit(OpCodes.Nop);
methodIL.Emit(OpCodes.Newobj, bar1Ctor);
methodIL.Emit(OpCodes.Stloc_0);
methodIL.Emit(OpCodes.Ldloc_0);

Here is a quick fix

var bar1Ctor = typeof(Bar1).GetConstructors()[0];
var methodIL = methodBuilder.GetILGenerator();
methodIL.Emit(OpCodes.Newobj, bar1Ctor);
methodIL.Emit(OpCodes.Ret);