动态地调用方法上通用的目标 [英] Dynamically call a method on a generic target

查看:82
本文介绍了动态地调用方法上通用的目标的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个通用的接口 ICommandHandler<> ,将有一批各实现的用于处理具体的实施 ICommand的<中/ code>,例如:

 公共类CreateUserCommand:ICommand的{...} 
公共类CreateUserCommandHandler:ICommandHandler< CreateUserCommand> {...}

当我给予的ICommand 对象我试图动态调度到正确的 ICommandHandler 。目前我使用与调用一个非常简单的方式反映在我的分发器类:

 公共无效调度< T>(T指令),其中T:ICommand的
{
键入命令类型= command.GetType();
型handlerType = typeof运算(ICommandHandler<>)MakeGenericType(命令类型)。
对象处理程序= IoC.Get(handlerType);
MethodInfo的方法= handlerType.GetMethod(处理);

method.Invoke(处理器,新的对象[] {命令});
}

有2个问题这种方法。首先,它使用慢速反思。其次,如果该方法抛出任何异常,那么它会被包裹在一个 TargetInvocationException ,我会失去堆栈跟踪,如果我再扔掉它。



我摸索出了一种通过创建委托并使用​​拨打电话 DynamicInvoke 但这并不解决异常问题(我不知道 DynamicInvoke 真的比调用更好):

 公共无效调度< T>(T指令),其中T:ICommand的
{
键入命令类型= command.GetType();
型handlerType = typeof运算(ICommandHandler<>)MakeGenericType(命令类型)。
对象处理程序= IoC.Get(handlerType);
MethodInfo的方法= handlerType.GetMethod(处理);

类型操作类型= ty​​peof运算(动作<>)MakeGenericType(命令类型)。
代表行动= Delegate.CreateDelegate(操作类型,处理程序,方法);
action.DynamicInvoke(命令);
}



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



如果这是不可能的,那么这将引发异常有效的解决方案更多的'本地'将是下一个最好的选择。



修改:更新代码示例,以澄清,我使用的IoC(Ninject)创建 ICommandHandler 在运行时,不 Activator.CreateInstance()我先放。包括了如何将作为请求了一个例子:=新CreateUserCommand()

  VAR命令{名称=亚当·罗杰 }; 
无功调度=新CommandDispatcher();
dispatcher.Dispatch(命令);
//这将消息发送到CreateUserCommandHandler.Handle(命令)
//动态和任何异常会回来的'本地'

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

  [的ServiceContract] 
公共类CommandService
{
〔OperationContract的]
公共无效执行(ICommand的命令)//没有类型信息
{
无功调度=新CommandDispatcher(); //由国际奥委会真实版
dispatcher.Dispatch(命令)注射;
}
}


解决方案

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

 公共无效调度< T>(T指令),其中T :ICommand的
{
ICommandHandler< T>处理器= IoC.Get< ICommandHandler< T>>();
handler.Handle(命令);
}

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

 类SomeCommand:ICommand的
{
// ...

公共无效讯(国际奥委会IOC)
{
VAR处理器= ioc.Get< IHandle< SomeCommand>>();
handler.Handle(本);
}
}



但你可以与反思去,如果你觉得增加这个代码的所有命令反感。



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

 接口的ICommand {} 
接口IHandle< TCommand>其中,TCommand:ICommand的
{
无效手柄(TCommand命令);
}

类CreateUserCommand:ICommand的{}
类CreateUserHandler:IHandle< CreateUserCommand>
{
公共无效手柄(CreateUserCommand命令)
{
Console.Write(你好);
}
}

[TestMethod的]
公共无效build_expression()
{
对象命令=新CreateUserCommand();
对象处理程序=新CreateUserHandler();

动作<对象,对象>调度= BuildDispatcher(command.GetType());
调度(处理程序,命令);
}

私有静态动作<对象,对象> BuildDispatcher(类型命令类型)
{
VAR handlerType = typeof运算(IHandle<>)。MakeGenericType(命令类型);
VAR handleMethod = handlerType.GetMethod(处理);

VAR参数1 = Expression.Parameter(typeof运算(对象));
VAR参数2 = Expression.Parameter(typeof运算(对象));

VAR处理器= Expression.ConvertChecked(参数1,handlerType);
VAR命令= Expression.ConvertChecked(参数2,命令类型);
VAR调用= Expression.Call(处理器,handleMethod,命令);

VAR波长= Expression.Lambda<作用<对象,对象>>(调用,参数1,参数2);
返回lambda.Compile();
}


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

解决方案

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

这篇关于动态地调用方法上通用的目标的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆