how create delegeate of a type which is not created

Viewed 104

I'm using the system. reflection.Emit to implement google protocol message

enter image description here

I want to init a static field

class MessageA
{
   static MessageParser<MessageA> Parser = new MessageParser<MessageA>(()=>new MessageA())
}

now MessageA is not created then i tried to get the constructor of Func it throws System.NotSupportedException with Message “Specified method is not supported.” here my example

using System.Reflection;
using System.Reflection.Emit;

Console.WriteLine("Hello, World!");
var assembleBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Dynamic"), AssemblyBuilderAccess.RunAndCollect);
var moduleBuilder = assembleBuilder.DefineDynamicModule("Dynamic.dll");
var typeA = moduleBuilder.DefineType("classA");
typeA.DefineDefaultConstructor(MethodAttributes.Public);

typeof(Func<>).MakeGenericType(typeA).GetConstructors();

How does the compiler do it?

2 Answers

To mix existing und to be created types, TypeBuilder provides a few static helper methods. In this case you could use TypeBuilder.GetConstructor:

Console.WriteLine("Hello, World!");
var assembleBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Dynamic"), AssemblyBuilderAccess.RunAndCollect);
var moduleBuilder = assembleBuilder.DefineDynamicModule("Dynamic.dll");
var typeA = moduleBuilder.DefineType("classA");
typeA.DefineDefaultConstructor(MethodAttributes.Public);

var desiredCtorDefintion = typeof(Func<>).GetConstructors().First();
var constructedType = typeof(Func<>).MakeGenericType(typeA);
var ctor = TypeBuilder.GetConstructor(constructedType, desiredCtorDefintion);

ctor is of type internal sealed class ConstructorOnTypeBuilderInstantiation : ConstructorInfo source.dot.net

typeA is not a created type so it is not allowed to do that (typeA.GetConstructors() will also fail). Build type first:

var typeA = moduleBuilder.DefineType("classA");
typeA.DefineDefaultConstructor(MethodAttributes.Public);

// typeA.GetConstructors() will fail
var type = typeA.CreateType();

type.GetConstructors(); // typeA.GetConstructors() works too
typeof(Func<>).MakeGenericType(type).GetConstructors();

But to create a Func<T> from a constructor you actually don't need the func's constructor, you should be able to use any of methods described here or here.

P.S.

Useful tool to see how compiler handle thigs is sharplab.io. You can see both decompilation to C# and IL.

This decompilation should represent your scenario.

UPD

I would suggest just to create a helper class (in code) which will do what is needed (i.e. return MessageParser with func returning invocation of default ctor) and then use it to call via emit:

public class Helper
{
    public static MessageParser<T> GetDefaultMessageParser<T>() where T : new() => new MessageParser<T>(() => new T());
}

And generation:

var assembleBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Dynamic"), AssemblyBuilderAccess.RunAndCollect);
var moduleBuilder = assembleBuilder.DefineDynamicModule("Dynamic.dll");
var typeA = moduleBuilder.DefineType("classA");
var defaultConstructorBuilder = typeA.DefineTypeInitializer(); // define static ctor
var mpType = typeof(MessageParser<>).MakeGenericType(typeA);

var parserFieldBuilder = typeA.DefineField("Parser", mpType, FieldAttributes.Static | FieldAttributes.Private);
var methodInfo = typeof(Helper)
    .GetMethod(nameof(Helper.GetDefaultMessageParser))
    .MakeGenericMethod(typeA);
var ctorGenerator = defaultConstructorBuilder.GetILGenerator();
ctorGenerator.Emit(OpCodes.Call, methodInfo);
ctorGenerator.Emit(OpCodes.Stsfld, parserFieldBuilder);
ctorGenerator.Emit(OpCodes.Ret);

var type = typeA.CreateType();

var instance = Activator.CreateInstance(type);
var value = type.GetField("Parser",  BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(new object[] { });
Console.WriteLine(value?.ToString()); // prints MessageParser`1[classA]

But in general I would recommend switching to source generators (here my sandbox one, for example) which will allow you to generate your class during compilation with, I would argue, much less effort and less error prone.

Related