gpt4 book ai didi

c# - 创建 Action,其中 T 是方法参数的基类

转载 作者:太空宇宙 更新时间:2023-11-03 13:13:48 24 4
gpt4 key购买 nike

我正在尝试做一些类似 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));
}
});

当我运行这段代码时,出现以下异常:无法绑定(bind)到目标方法,因为它的签名或安全透明度与委托(delegate)类型不兼容。

这当然是正确的,因为我的方法不采用 BaseCommand 作为参数,而是采用 StartCommand

但是有什么方法可以创建 Action 吗?我已经用 Expression 查看了一些示例,但我没能弄明白。

提前致谢

最佳答案

我发现这个问题有点令人困惑,原因如下:

  1. 我不清楚为什么 instance 类型的方法,例如StartCommand 本身应该需要将一个不同的 StartCommand 实例传递给它。
  2. 我不清楚为什么这个事实上的接口(interface)方法的参数类型必须与声明类型相同。

我认为处理这类事情的更常用方法是让类型实现一个接口(interface),或者至少使用相同的方法签名,即使用基类类型作为参数类型而不是实现类的类型。

就是说,如果您真的想按照您描述的方式进行操作,那么您使用 Expression 类就走在了正确的轨道上。您可以创建一个表达式,该表达式将显式转换为调用成员所需的类型,以便委托(delegate)实例本身可以接收基类型。

例如:

/// <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 替换为您实际的 Controller 类型。不幸的是,您的代码示例没有显示 instance 的声明,所以我不能在这里使用真实的类型名称。

当然,您必须小心确保在实际调用委托(delegate)时传递正确类型的实例!上面的代码就像进行强制转换,如果类型实际上不能强制转换为方法的参数类型,就会像强制转换一样抛出异常。

关于c# - 创建 Action<T>,其中 T 是方法参数的基类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27382642/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com