the berserker
the berserker

Reputation: 1604

RuntimeBinderException invoking a method on a generic type as dynamic

I'm trying to invoke a method on a generic type (generic is also an argument to invoking method) and are hitting RuntimeBinderException that I don't really see how to work around.

As you can see in below example the instance returns the MyHandler instance (as a dynamic) which I expect to get invoked, but without explicit conversion to the generic type of the DTO (as MyDto) it will crash. And my requirement is to determine the type in runtime (as I see the JsonConvert.DeserializeObject(data, type) does, until the point of invocation).

static async Task Main(string[] args)
{
    string dataType = typeof(MyDto).AssemblyQualifiedName;
    string data = JsonConvert.SerializeObject(new MyDto());

    var type = Type.GetType(dataType);
    var dto = JsonConvert.DeserializeObject(data, type);
    var handler = GetHandler();

    await handler.HandleMessageAsync(dto, CancellationToken.None); // throws the RuntimeBinderException   
    await handler.HandleMessageAsync(dto as MyDto, CancellationToken.None); // is able to invoke MyHandler.HandleMessageAsync

}

public static dynamic GetHandler()
{
    // this is simplified logic for locating a handler instance.
    return new MyHandler();
}

The exception thrown in the 1st invocation from example is

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'The best overloaded method match for 'ConsoleApp.MessageHandlerBase<ConsoleApp.MyDto>.HandleMessageAsync(ConsoleApp.MyDto, System.Threading.CancellationToken)' has some invalid arguments'

I don't see how the conversion can make a change to the DTO as they are the same type. So, my question is how I can succeed invoking the method completely dynamically?

This is the simplified DTO and handler service class hierarchy of the real implementation:

public class MyDto 
{
    public int Prop1 { get; set; } = 1;
}

public interface IMessageHandler<in T> where T : class
{
    Task HandleMessageAsync(T dto, CancellationToken cancellationToken);
}

public class MyHandler : IMessageHandler<MyDto>
{
    public Task HandleMessageAsync(MyDto dto, CancellationToken cancellationToken)
    {
        Console.WriteLine("OnError called from " + this);
        return Task.CompletedTask;
    }
}

Upvotes: 1

Views: 200

Answers (1)

Nikhil Patil
Nikhil Patil

Reputation: 2540

Can you please try changing type of dto variable from object to dynamic as below -

dynamic dto = JsonConvert.DeserializeObject(data, type);

The final code should something like this -

static async Task Main(string[] args)
{
    string dataType = typeof(MyDto).AssemblyQualifiedName;
    string data = JsonConvert.SerializeObject(new MyDto());

    var type = Type.GetType(dataType);
    dynamic dto = JsonConvert.DeserializeObject(data, type); // declare as dynamic instead of var (declaring as var would default to object type)
    var handler = GetHandler();

    await handler.HandleMessageAsync(dto, CancellationToken.None); // throws the RuntimeBinderException   
    await handler.HandleMessageAsync(dto as MyDto, CancellationToken.None); // is able to invoke MyHandler.HandleMessageAsync

}

Upvotes: 1

Related Questions