动态调用泛型目标上的方法

4

我有一个通用接口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。不过我认为这不可能,因为在编译时无法确定类型。
如果这不可能,那么一个有效的解决方案是以更本地的方式抛出异常。 编辑:更新了代码示例,以澄清我正在使用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);
    }
}

我对预期目标感到困惑。你能否添加一个代码示例,说明如何使用这个模式? - Bobson
你声明了一个 CreateUserCommandHandler,但我不知道它是如何被实例化的。此外,你有 Type handlerType = typeof(ICommandHandler<>).MakeGenericType(commandType); 但这只会将其定义为 ICommandHandler<CreateUserCommand>,然后你对其运行 Activator.CreateInstance?你如何创建接口的实例? - Chris Sinclair
实际上,这是您正在使用的_实际_代码吗?如果T是您的ICommand类型,为什么不将您的object handler类型定义为ICommandHandler<T>,它自然会有一个T Handle(T command)方法? - Chris Sinclair
ж №жҚ®жӮЁзҡ„зј–иҫ‘пјҢжңүд»Җд№Ҳйҳ»жӯўжӮЁе°ҶIoC.Get(handlerType)зҡ„з»“жһңејәеҲ¶иҪ¬жҚў/зұ»еһӢеҢ–дёәICommandHandler<T>пјҹиҝҷж ·еҒҡдҪҝе…¶д»–жүҖжңүдәӢжғ…йғҪеҸҳеҫ—еҫ®дёҚи¶ійҒ“пјҢжҳҜеҗ—пјҹзј–иҫ‘пјҡд№ҹи®ёжӮЁеҸҜд»ҘеҸ‘еёғICommandHandler<T>.Handleж–№жі•зҡ„зӯҫеҗҚпјҹ - Chris Sinclair
这里的真正问题是 T 是一些基类或低级接口,来自调用代码,而 command.GetType() 的实际结果是 T 的高级子类吗?因为如果 T command.GetType() 相同类型,那么这应该是微不足道的。 - Chris Sinclair
1
很遗憾,@ChrisSinclair,Tcommand.GetType()不同,否则它肯定是微不足道的。在运行时,TICommand,而command.GetType()CreateUserCommand - Adam Rodger
2个回答

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

我已经使用了编辑后的版本,其中包括表达式构建,并且它完美地工作。速度似乎与正常的方法调用几乎相同(特别是在缓存结果方面),并且所有异常都被正确抛出。 - Adam Rodger

1
尝试这个。
dynamic handler=Activator.CreateInstance(handlerType);
try
  {
         handler.Handle((dynamic)command);
   }

   catch
   {
   // do whatever you want 
   }

我尝试使用 dynamic,但是在运行时我得到了一个异常,说我向 handler.Handle() 提供了一个无效的参数。我认为这是因为实现采用了 ICommand 实现,而调度程序采用了接口类型。 - Adam Rodger
我编辑了我的回答。实际上,我已经构建了你正在尝试做的事情,这就是我所做的(当然,严格执行命令处理程序)。 - MikeSW

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接