0

I have the below extension method which I am trying to invoke with various instantiated classes in an array. As extension methods cannot be invoked with dynamic I have tried using the array type as object or IMessage. However, as this calls MassTransit and Azure Service Bus, they need the exact type for it to work, even though it will compile. I have thought that perhaps reflection would work however the below results in System.InvalidOperationException: System.Threading.Tasks.Task``1[Helix.Sdk.Contracts.CommandResponse] ExecuteCommand[Object](MassTransit.IBus, System.Object, System.Threading.CancellationToken) is not a GenericMethodDefinition. MakeGenericMethod may only be called on a method for which MethodBase.IsGenericMethodDefinition is true.

Is there anyway for this to work so it knows the correct type at runtime?

foreach (var command in commands)
{
   //Bus.ExecuteCommand(command); //won't work with dynamic or object at runtime

   var func = new Func<IBus,object, CancellationToken, Task<CommandResponse>>(BusCommandExtensions.ExecuteCommand);
   func.Method.MakeGenericMethod(command.GetType());
   await func.Invoke(Bus,command, CancellationToken.None);
}

public static class BusCommandExtensions {

    //Removed other methods for brevity     

    public static async Task<CommandResponse> ExecuteCommand<T>(this IBus bus, T command, CancellationToken cancellationToken = default) where T : class =>
        await ExecuteCommand(
            bus, command, _ => { },
            cancellationToken
        );

}
Chris Patterson
  • 28,659
  • 3
  • 47
  • 59
Jon
  • 38,814
  • 81
  • 233
  • 382
  • first off `MakeGenericMethod` doesn't *modify* the `MethodInfo`, but just returns a *new one*. Anyway you cannot modify the `MethodInfo` of a delegate, which makes me ask: why do you even use a delegate in the first place? – MakePeaceGreatAgain Mar 13 '23 at 13:51
  • Check my [previous answer](https://stackoverflow.com/questions/1606966/generic-method-executed-with-a-runtime-type/1606988#1606988) from years ago... – Chris Patterson Mar 13 '23 at 13:53
  • @MakePeaceGreatAgain because using pure reflection also caused issues, using the delegate I thought it might give me a typed way as I have multiple methods with different args called the same name but unfortunately I still get errors. Removing the `MakeGenericMethod` call then results in MassTransit errors `Messages types must not be System types: System.Object` – Jon Mar 13 '23 at 14:24

2 Answers2

0

At runtime, a generic method instance is not callable. func is a delegate instance, so it is callable and could not be generic.

Another issue is your call to Invoke method. The first parameter is the instance of the method. Your method is static, so there is no instance and the first parameter should be null. The second paramater should be an array of objects, with all parameters of the target method.

There is a working version of your code :

var genericMethod = typeof(BusCommandExtensions).GetMethod(nameof(BusCommandExtensions.ExecuteCommand));
var method = genericMethod.MakeGenericMethod(command.GetType());
method.Invoke(null, new object[] { null, command, CancellationToken.None });

EDIT: In case of AmbiguousMatchException, we can get the target method with more precision:

var T = Type.MakeGenericMethodParameter(0);
var genericMethod = typeof(BusCommandExtensions).GetMethod(nameof(BusCommandExtensions.ExecuteCommand), new[] {typeof(IBus), T, typeof(CancellationToken)} );
var method = genericMethod.MakeGenericMethod(command.GetType());

The usefull part here is Type.MakeGenericMethodParameter. Your method is generic, so you can't simply find the method with list of argument types if you don't generate a marker type for the generic argument.

t.ouvre
  • 2,856
  • 1
  • 11
  • 17
  • "The first parameter is the instance of the method." When `func` is a delegate - which it is apparently - there's no instance to even think about. It's just - well - a delegate. There's no notion of an instance, so the first parameter of a delegate is not the instance on which to call it. You mix `Func` with `MethodInfo`. – MakePeaceGreatAgain Mar 13 '23 at 14:04
  • The issue with this is I get `System.Reflection.AmbiguousMatchException:` as I have multiple extension methods with the same name – Jon Mar 13 '23 at 14:22
  • @MakePeaceGreatAgain if you prefer you can replace 'instance' by 'object'. Again, the `MethodInfo.Invoke' method should be called with two arguments : obj and parameters. First argument, named obj, is 'the object on wich to invoke the method' (from documentation). In the actual case, the target method is static, so `obj` must be null. – t.ouvre Mar 14 '23 at 16:04
  • @Jon please look at my last edit. – t.ouvre Mar 14 '23 at 16:10
  • but `func` in OPs code is not a `MethodInfo`, but a delegate. A delegate has no object on which it is called. That doesn't make your solution whrong, but your argumentation about OPs problem is whrong. – MakePeaceGreatAgain Mar 14 '23 at 16:22
0

I went a different route with:

var method = typeof(BusCommandExtensions)
    .GetMethods(BindingFlags.Public | BindingFlags.Static)
    .First(x => 
        x.GetParameters().Length == 3 && 
        x.GetParameters()[0].ParameterType == typeof(IBus) && 
     /*(x.GetParameters()[1].ParameterType== typeof(Type).GetType() &&*/ 
        x.GetParameters()[2].ParameterType == typeof(CancellationToken)
     );
    
//Pass in the message type to the generic method
var genericMethod = method.MakeGenericMethod(command.GetType());

//Invoke the method
var methodResult = genericMethod.Invoke(null, new [] { Bus, command, CancellationToken.None })!;

//Check the CommandResponse result
var response = await (Task<CommandResponse>)methodResult;
Roe
  • 633
  • 2
  • 14
Jon
  • 38,814
  • 81
  • 233
  • 382