我正在尝试做类似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&lt;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&lt;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/

10-12 02:27