我有一个通用接口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
,而不是{I}首先放置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);
}
}
答案 0 :(得分:6)
大多数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();
}
答案 1 :(得分:1)
试试这个
dynamic handler=Activator.CreateInstance(handlerType);
try
{
handler.Handle((dynamic)command);
}
catch
{
// do whatever you want
}