动态调用方法上的通用目标(Dynamically call a method on a generi

2019-08-21 10:21发布

我有一个通用的接口ICommandHandler<>将有一个号码的每个实施方式的用于处理特定实施ICommand ,例如:

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

当我给一个ICommand对象我想要动态地分派到正确的ICommandHandler 。 目前我使用有一个非常简单的反射方法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");

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

有2个问题,这种方法。 首先,它采用慢反射。 其次,如果该方法抛出任何类型的异常然后它会被包裹在一个TargetInvocationException ,我会失去,如果我堆栈跟踪再次抛出它。

我摸索出了一种通过创建委托并使用拨打电话DynamicInvoke但这并不解决异常问题(我不知道DynamicInvoke真的比什么更好的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);
}

我的问题是,是否有更好的方式来实现我想要做什么? 最好我可以做一个强类型的呼叫,而不是得到一个object和仰视MethodInfo 。 我认为这是不可能的,但因为该类型是不知道在编译时。

如果这是不可能的,那么这将引发异常更多的“原生”的有效解决方案将是一个最好的选择。

编辑 :更新的代码示例,以澄清,我使用的IoC(Ninject)创建ICommandHandler在运行时,不Activator.CreateInstance()是我第一次把。 包括如何按要求,这将使用一个例子:

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'

编辑2:如下建议,我不能投的结果IoC.Get(handlerType)ICommandHandler<T>因为我得到一个InvalidCastException在运行时。 这是因为在运行时T实际上是ICommand ,我想是因为命令类到达了WCF并以某种方式设法失去了强类型。 调用调度程序的代码看起来是这样的:

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

Answer 1:

大多数DI容器(包括Ninject)让你做这样的事情:

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

如果你不知道命令的类型(换句话说,如果typeof(T) != command.GetType()采用双调度是最简单的方法:

class SomeCommand : ICommand
{
    // ...

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

但你可以去思考,如果你发现将此代码添加到所有命令反感。

编辑这里是一个基于反射的版本。 你可以(也应该)缓存编译的委托。

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();
}


Answer 2:

试试这个

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

   catch
   {
   // do whatever you want 
   }


文章来源: Dynamically call a method on a generic target