BohdanZPM
BohdanZPM

Reputation: 755

How to create an instance of generic type whose constructor requires a delegate function parameter?

I need to use the following generic class and method ParseFrom() in it:

public sealed class MessageParser<T> : MessageParser where T : IMessage<T>
{
    public MessageParser(Func<T> factory); //constructor
    public T ParseFrom(byte[] data);
}

Now, I do not know the type of the parameter for this class at compile time, so I use type reflection and MakeGenericType() method to do that:

//Assuming itemInstance is given as input parameter 
Type typeArgument = itemInstance.GetType();
Type genericClass = typeof(MessageParser<>);
var genericType = genericClass.MakeGenericType(typeArgument);
var instance = Activator.CreateInstance(genericType);

It gives me a runtime error: MessageParser<> does not have a parameterless constructor. But when I try to pass Func<T> factory as a parameter for CreateInstance():

var instance = Activator.CreateInstance(genericType, () => Activator.CreateInstance(typeArgument));

it gives me a compile error: Cannot convert lambda expression to type 'string' because it is not a delegate type. Am I using the wrong syntax for a delegate function here?

Upvotes: 6

Views: 2166

Answers (3)

Jeremy Lakeman
Jeremy Lakeman

Reputation: 11120

The easy answer is to write your own generic method, then call that via reflection.

public static class Foo
{
    public static MessageParser<T> CreateParser<T>() where T : IMessage<T>, new()
        => new MessageParser<T>(() => new T());

    private static MethodInfo _createMethod = typeof(Foo)
        .GetMethods()
        .Where(m => m.Name == nameof(CreateParser) && m.IsGenericMethod)
        .Single();

    public static MessageParser CreateParser(Type type)
        => (MessageParser)_createMethod.MakeGenericMethod(type)
               .Invoke(null, new object[] { });
}

Upvotes: 0

lidqy
lidqy

Reputation: 2463

To create the Func<T> through reflection, CreateDelegate is the way to go. Therefore a method, that has the expected signature - including the type contraints (T is IMessage<T>)- is needed.

Here's how you can get it work. A downside is, that you will still need to use reflection to invoke the parser's methods, at least those that work with the type parameter:

public class CreateParserLateBound {

    //The method with the matching signature
    public static T MessageParserFactory<T>()
        where T : IMessage<T>
    {
        //your factory code, you pass to MessageParser(Func<T> factory) goes here...
        return default(T); 
    }

        ...
    
        // itemInstance == item that is IMesage<T>, with T unknown at compiletime;
        var itemType          = itemInstance.GetType();

        var boundParserType   = typeof(MessageParser<>).MakeGenericType(itemType);
        
        var boundFuncType     = typeof(Func<>).MakeGenericType(itemType);
        
        var factoryMethodInstance   = typeof(CreateParserLateBound )
                    .GetMethod("MessageParserFactory") 
                    .MakeGenericMethod(itemType)
                    .CreateDelegate(boundFuncType);
        
        var parserInstance    = Activator.CreateInstance(boundParserType, 
                     new object[]{ factoryMethodInstance } );

        //Invoke ParseFrom (also through reflection)   
        byte[] data = {1,2,3,4};                                 
        boundParserType.InvokeMember("ParseFrom", 
                BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, null, 
                parserInstance, new object[] {data});      


    

Full runnable code @ https://dotnetfiddle.net/RIOEXA

Upvotes: 0

Servy
Servy

Reputation: 203835

Constructing a delegate of an unknown type dynamically isn't as easy as using reflection to call a method, so the easiest option is to just write a statically typed method to construct the delegate, and then just call it using reflection.

public class DelegateCreator
{
    public static Func<T> MakeConstructorStatically<T>()
    {
        return Activator.CreateInstance<T>;
    }

    public static object MakeConstructorDynamically(Type type)
    {
        return typeof(DelegateCreator)
            .GetMethod(nameof(MakeConstructorStatically))
            .MakeGenericMethod(type)
            .Invoke(null, Array.Empty<object>());
    }
}

Upvotes: 7

Related Questions