6

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?

7
  • Expression<Func<object>> create = () => Activator.CreateInstance(typeArgument); var instance = Activator.CreateInstance(genericType, Expression.Lambda(Expression.Convert(create, typeArgument)).Compile()); Commented Aug 19, 2021 at 15:04
  • @Selvin That won't necessarily provide a delegate of the right type,. It can be any delegate of the right signature. Commented Aug 19, 2021 at 15:16
  • yes it will ... it just the same as Func<T> func = () => (T)Activator.CreateInstance(typeof(T)); where T is defined by typeArgument .... it's working fine Commented Aug 19, 2021 at 15:19
  • 1
    @Selvin No, in the second snippet you provided you're explicitly declaring the delegate type. When you explicitly declare the delegate type it will of course use that delegate type. Expression.Lambda however is documented as returning any delegate type it wants. There are overloads that do provide a delegate type, but you are not using them. Commented Aug 19, 2021 at 15:26
  • Yes, delegate is Func<Message> ... there is second expresion there Expression.Convert ... which makes right delegate Commented Aug 19, 2021 at 15:29

3 Answers 3

7

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>());
    }
}
Sign up to request clarification or add additional context in comments.

Comments

0

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

7 Comments

Thanks for the example. However, it was not useful to me, because MessageParser<> is a built-in class, along with its method ParseFrom(), which is placed in Google.Protobuf assembly. I am not able to move its parsing code into another place, like you did in your example, I just need to use it somehow.
Hi thx for the feedback. I just had put these stubs into my project to let it compile, you won't need them. It should none the less work with the true Protobuf API. What I don't understand is how you can parse a message (that byte array) and create a new instance of that unkown type T in a real-life-environment. Even if you can create the instance technicaly, how are you supposed to fill it with reasonable data. And then work with it in Protobuf?
I'm not sure. The original task was to make this code: byte[] content; Epic payload; payload = Epic.Parser.ParseFrom(content); a generic one, that is to be able to work with any type of object that implements Google.Protobuf.IMessage interface, not only the Epic object. The code worked just fine for one particular type of object. It became pain in the ass when I started to convert it to be generic.
I tried your proposed approach, it seems to be working overall, except for one little error inside InvokeMember() method: ArgumentNullException: Value cannot be null. (Parameter 'message') Any ideas what could be causing this? Should I provide anything instead of the third null parameter? i tried to evaluate the value of every other parameter during runtime and they were not null except for this one.
The method I've used to bind the delegate (that becomes the "factory" parameter of in the MessageParser ctor) returns default(T) which equals null. You must return sth reasonable there, which is not null. It's used by the ParseFrom method, I'd bet.
|
0

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[] { });
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.