我正在尝试做类似CommandBus的事情,其中在处理命令时应调用一种方法。我将它们存储在字典中
private readonly ConcurrentDictionary<Type, Action<BaseCommand>> _commandHandlers = new ConcurrentDictionary<Type, Action<BaseCommand>>();
因此,当处理了StartCommand类型的命令时,我发现要在此字典中执行的动作。
StartCommand应该调用的方法如下所示。
public void StartCommand(StartCommand command)
StartCommand继承了BaseCommand
我正在尝试用此代码填充字典。
var commands = new List<Type>();
//-- Get all commands that is defined in assembly
var tmpAssembly = typeof(CommandBus).Assembly;
commands.AddRange(tmpAssembly.GetTypes().Where(t => t.BaseType == typeof(BaseCommand)));
commands.ForEach(c =>
{
var methodInfo = instance.GetType().GetMethods().SingleOrDefault(m => m.GetParameters().Count() == 1
&& m.GetParameters().Any(p => p.ParameterType == c));
if (methodInfo != null)
{
var action = (Action<BaseCommand>)Delegate.CreateDelegate(typeof(Action<BaseCommand>), instance, methodInfo);
if (!_commandHandlers.TryAdd(c, action))
throw new ArgumentException(string.Format("An CommandHandler is already registered for the command: '{0}'. Only one CommandHandler can be registered for a command", c.Name));
}
});
运行此代码时,出现以下异常:无法绑定到目标方法,因为其签名或安全性与委托类型的签名或安全性不兼容。
这当然是正确的,因为我的方法不是将BaseCommand作为参数而是StartCommand
但是,有什么方法可以创建动作?我已经看过带有Expression的som示例,但是我没有弄清楚。
提前致谢
最佳答案
我发现这个问题有些令人困惑,原因有两个:
我不清楚为什么类型为例如的实例方法StartCommand
本身应该需要将StartCommand
的其他实例传递给它。
对我来说尚不清楚,为什么这种实际接口方法的参数类型必须与声明类型相同。
我认为处理这种问题的更常用方法是让类型实现接口,或者至少使用相同的方法签名,即以基类类型作为参数类型,而不是实现类的类型。
就是说,如果您真的想按照描述的方式进行操作,则使用Expression
类是正确的。您可以创建一个表达式,该表达式将显式转换为调用成员所需的类型,以便委托实例本身可以接收基本类型。
例如:
/// <summary>
/// Create an Action<T> delegate instance which will call the
/// given method, using the given instance, casting the argument
/// of type T to the actual argument type of the method.
/// </summary>
/// <typeparam name="T">The type for the delegate's parameter</typeparam>
/// <param name="b">The instance of the object for the method call</param>
/// <param name="miCommand">The method to call</param>
/// <returns>A new Action<T></returns>
private static Action<T> CreateAction<T>(B b, MethodInfo miCommand)
{
// Create the parameter object for the expression, and get
// the type needed for it
ParameterExpression tParam = Expression.Parameter(typeof(T));
Type parameterType = miCommand.GetParameters()[0].ParameterType;
// Create an expression to cast the parameter to the correct type
// for the call
Expression castToType = Expression.Convert(tParam, parameterType, null);
// Create the delegate itself: compile a lambda expression where
// the lambda calls the method miCommand using the instance b and
// passing the result of the cast expression as the argument.
return (Action<T>)Expression.Lambda(
Expression.Call(
Expression.Constant(b, b.GetType()),
miCommand, castToType),
tbParam).Compile();
}
您可以这样使用:
var action = CreateAction<BaseCommand>(instance, methodInfo);
if (!_commandHandlers.TryAdd(c, action))
throw new ArgumentException(string.Format("An CommandHandler is already registered for the command: '{0}'. Only one CommandHandler can be registered for a command", c.Name));
将类型名称
B
替换为您实际的控制器类型。不幸的是,您的代码示例未显示instance
的声明,因此在这里我不能使用实型名称。自然,您必须要小心确保在实际调用委托时传递正确类型的实例!如果类型实际上不能转换为方法的参数类型,则上述操作就像进行类型转换一样,并且会像类型转换一样引发异常。
关于c# - 创建Action <T>,其中T是Method参数的基类,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/27382642/