【问题标题】:Dynamically call a method on a generic target动态调用泛型目标上的方法
【发布时间】:2013-04-10 15:53:14
【问题描述】:

我有一个通用接口 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&lt;T&gt;,因为我在运行时得到了 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);
    }
}

【问题讨论】:

  • 我对预期的目标感到困惑。您能否添加一个代码示例来说明如何使用此模式?
  • 您声明了一个CreateUserCommandHandler,但我不知道它是如何被实例化的。此外,您有Type handlerType = typeof(ICommandHandler&lt;&gt;).MakeGenericType(commandType);,但不只是将其定义为ICommandHandler&lt;CreateUserCommand&gt;,您运行Activator.CreateInstance 对它?如何创建接口的实例?
  • 事实上,这是您使用的实际代码吗?如果T是你的ICommand类型,那为什么不把你的object handler写成ICommandHandler&lt;T&gt;,其中自然会有T Handle(T command)方法呢?
  • 根据您的编辑,是否有什么阻止您将IoC.Get(handlerType) 的结果简单地转换/键入为ICommandHandler&lt;T&gt;?这样做会使其他一切变得微不足道,不是吗?编辑:也许您可以发布您的 ICommandHandler&lt;T&gt;.Handle 方法的签名?
  • @ChrisSinclair 不幸的是Tcommand.GetType() 不同,否则它肯定是微不足道的。 T 在运行时是 ICommandcommand.GetType()CreateUserCommand

标签: c# generics reflection


【解决方案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();
}

【讨论】:

  • 我已经使用了带有表达式构建的编辑版本,并且效果很好。速度似乎与正确的方法调用几乎相同(尤其是缓存结果)并且所有异常都被正确抛出。
【解决方案2】:

试试这个

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

   catch
   {
   // do whatever you want 
   }

【讨论】:

  • 我尝试使用dynamic,但我在运行时遇到了一个异常,说我向handler.Handle() 提供了一个无效参数。我认为这是因为实现采用ICommand 实现,而调度程序采用接口类型。
  • 我编辑了我的 anwser。我实际上构建了您想要做的事情,我就是这样做的(当然,严格执行命令处理程序)
猜你喜欢
  • 2011-07-14
  • 1970-01-01
  • 1970-01-01
  • 2011-05-18
  • 2010-09-16
  • 2015-03-09
  • 1970-01-01
  • 1970-01-01
  • 2013-08-29
相关资源
最近更新 更多