动态调用通用目标上的方法

本文关键字:方法 目标 调用 动态 | 更新日期: 2023-09-27 18:08:42

我有一个通用接口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 });
}

这种方法有两个问题。首先,它使用慢反射。其次,如果方法抛出任何类型的异常,那么它将被包装在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。我认为这是不可能的,因为在编译时不知道类型。

如果这是不可能的,那么一个更"本地"抛出异常的有效解决方案将是下一个最佳选择。

Edit:更新的代码示例,以澄清我使用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'

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

动态调用通用目标上的方法

大多数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);
    }
}

但是如果你觉得将这些代码添加到所有命令中令人讨厌,你可以使用反射。

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

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

试试这个

dynamic handler=Activator.CreateInstance(handlerType);
try
  {
         handler.Handle((dynamic)command);
   }
   catch
   {
   // do whatever you want 
   }