Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically call a method on a generic target

I have a generic interface ICommandHandler<> that will have a number of implementations each for processing a specific implementation of ICommand, e.g.:

public class CreateUserCommand : ICommand { ... }
public class CreateUserCommandHandler : ICommandHandler<CreateUserCommand> { ... }

When I'm given an ICommand object I'm trying to dispatch it dynamically to the correct ICommandHandler. At the moment I've used a pretty straightforward reflection approach with an Invoke in my dispatcher class:

public void Dispatch<T>(T command) where T : ICommand
{
    Type commandType = command.GetType();
    Type handlerType = typeof(ICommandHandler<>).MakeGenericType(commandType);
    object handler = IoC.Get(handlerType);
    MethodInfo method = handlerType.GetMethod("Handle");

    method.Invoke(handler, new object[] { command });
}

There are 2 problems with this approach. Firstly it uses slow reflection. Secondly if the method throws any kind of exception then it'll be wrapped in a TargetInvocationException and I'll lose the stack trace if I re-throw it.

I worked out a way to make the call by creating a delegate and using DynamicInvoke but this doesn't solve the problem with exceptions (and I'm not sure DynamicInvoke is really any better than Invoke):

public void Dispatch<T>(T command) where T : ICommand
{
    Type commandType = command.GetType();
    Type handlerType = typeof(ICommandHandler<>).MakeGenericType(commandType);
    object handler = IoC.Get(handlerType);
    MethodInfo method = handlerType.GetMethod("Handle");

    Type actionType = typeof(Action<>).MakeGenericType(commandType);
    Delegate action = Delegate.CreateDelegate(actionType, handler, method);
    action.DynamicInvoke(command);
}

My question is, is there a better way to achieve what I'm trying to do? Preferably I could make a strongly-typed call instead of getting an object and looking up the MethodInfo. I assume that's not possible though because the type isn't know at compile time.

If that's not possible then an efficient solution that would throw the exception more 'natively' would be the next best option.

Edit: Updated code samples to clarify that I'm using IoC (Ninject) to create the ICommandHandler at runtime, not Activator.CreateInstance() as I first put. Included an example of how this would be used as requested:

var command = new CreateUserCommand() { Name = "Adam Rodger" };
var dispatcher = new CommandDispatcher();
dispatcher.Dispatch(command);
// this would send the message to CreateUserCommandHandler.Handle(command) 
// dynamically and any exceptions would come back 'natively'

Edit 2: As suggested below, I can't cast the result of IoC.Get(handlerType) to ICommandHandler<T> because I get an InvalidCastException at runtime. This is because at runtime T is actually ICommand, I assume because the command classes are arriving over WCF and somehow manage to lose their strong typing. The code that calls the dispatcher looks something like:

[ServiceContract]
public class CommandService
{
    [OperationContract]
    public void Execute(ICommand command) // no type information
    {
        var dispatcher = new CommandDispatcher(); // injected by IoC in real version
        dispatcher.Dispatch(command);
    }
}
like image 401
Adam Rodger Avatar asked Feb 17 '23 20:02

Adam Rodger


2 Answers

Most DI containers (including Ninject) allow you to do something like this:

public void Dispatch<T>(T command) where T : ICommand
{
    ICommandHandler<T> handler = IoC.Get<ICommandHandler<T>>();
    handler.Handle(command);
}

If you don't know the type of command (in other words, if typeof(T) != command.GetType()), using double-dispatch is the easiest way:

class SomeCommand : ICommand
{
    // ...

    public void Dispatch(IoC ioc)
    {
        var handler = ioc.Get<IHandle<SomeCommand>>();
        handler.Handle(this);
    }
}

but you could go with reflection if you find adding this code to all Commands distasteful.

Edit Here is a reflection-based version. You can (and should) cache the compiled delegate.

interface ICommand { }
interface IHandle<TCommand> where TCommand : ICommand
{
    void Handle(TCommand command);
}

class CreateUserCommand : ICommand { }
class CreateUserHandler : IHandle<CreateUserCommand>
{
    public void Handle(CreateUserCommand command)
    {
        Console.Write("hello");
    }
}

[TestMethod]
public void build_expression()
{
    object command = new CreateUserCommand();
    object handler = new CreateUserHandler();

    Action<object, object> dispatcher = BuildDispatcher(command.GetType());
    dispatcher(handler, command);
}

private static Action<object, object> BuildDispatcher(Type commandType)
{
    var handlerType = typeof(IHandle<>).MakeGenericType(commandType);
    var handleMethod = handlerType.GetMethod("Handle");

    var param1 = Expression.Parameter(typeof(object));
    var param2 = Expression.Parameter(typeof(object));

    var handler = Expression.ConvertChecked(param1, handlerType);
    var command = Expression.ConvertChecked(param2, commandType);
    var call = Expression.Call(handler, handleMethod, command);

    var lambda = Expression.Lambda<Action<object, object>>(call, param1, param2);
    return lambda.Compile();
}
like image 168
default.kramer Avatar answered Feb 19 '23 10:02

default.kramer


Try this

dynamic handler=Activator.CreateInstance(handlerType);
try
  {
         handler.Handle((dynamic)command);
   }

   catch
   {
   // do whatever you want 
   }
like image 28
MikeSW Avatar answered Feb 19 '23 09:02

MikeSW