当目标方法参数是从委托签名中指定的参数类型派生的类型时,我无法弄清楚如何创建委托。这甚至可能吗?我的项目涉及为许多不同的命令创建命令处理程序。这些命令按组排列,每个组都有自己的属性和依赖关系(为简洁起见,我在此省略了这些命令。)我试图从它们中提取那些处理程序方法(标有' Handler'属性) group,从生成的MethodInfo创建委托,并将它们存储在Dictionary中,键入命令类型。粗略的想法如下:
public delegate void HandlerDelegate(ICommand command);
public class Handler: Attribute
{
}
public interface ICommand
{
}
public class CommandA: ICommand
{
public string CmdASpecific = "Command A";
}
public class CommandB: ICommand
{
public string CmdBSpecific = "Command B";
}
public class HandlerGroup
{
[Handler]
public void HandleA(CommandA command)
{
Console.WriteLine($"{command.CmdASpecific} Handled");
}
[Handler]
public void HandleB(CommandB command)
{
Console.WriteLine($"{command.CmdBSpecific} Handled");
}
}
我使用反射扫描HandlerGroup实例,使用' Handler'提取方法。属性,从它们创建一个委托,并将它们添加到一个字典(由处理程序期望的参数类型键入)以便稍后调用:
public void Main(){
var handlerGroup = new HandlerGroup();
Dictionary<Type, HandlerDelegate> cache = new Dictionary<Type, HandlerDelegate>();
foreach (var handlerInfo in handlerGroup.GetType().GetMethods())
{
if ((Handler)handlerInfo.GetCustomAttribute(typeof(Handler), false) is Handler handlerAttribute)
{
var parameters = handlerInfo.GetParameters();
HandlerDelegate handler = (HandlerDelegate)Delegate.CreateDelegate(typeof(HandlerDelegate), handlerGroup, handlerInfo.Name);
cache.Add(parameters.Single().ParameterType, handler);
}
}
var cmdA = new CommandA();
var cmdB = new CommandB();
cache[cmdA.GetType()](cmdA); //Should write 'Command A Handled'
cache[cmdB.GetType()](cmdB); //Should write 'Command B Handled'
}
CreateDelegate方法因System.ArgumentException而失败:&#39;无法绑定到目标方法,因为其签名或安全透明度与委托类型的签名或安全透明度不兼容。&#39;异常。
我可以通过向Handler属性添加属性来解决这个问题:
public class Handler: Attribute
{
public Type CommandType;
public Handler(Type commandType)
{
CommandType = commandType;
}
}
使用此HandlerGroup代替:
public class HandlerGroup
{
[Handler(typeof(CommandA))]
public void HandleA(ICommand command)
{
var tmp = (CommandA)command;
Console.WriteLine($"{tmp.CmdASpecific} Handled");
}
[Handler(typeof(CommandB))]
public void HandleB(ICommand command)
{
var tmp = (CommandB)command;
Console.WriteLine($"{tmp.CmdBSpecific} Handled");
}
}
然后使用Handler属性的CommandType属性将其添加到缓存中,而不是使用处理程序方法&#39;参数类型:
if (handlerInfo.GetCustomAttribute(typeof(Handler), false) is Handler handlerAttribute)
{
HandlerDelegate handler = (HandlerDelegate)Delegate.CreateDelegate(typeof(HandlerDelegate), handlerGroup, handlerInfo.Name);
cache.Add(handlerAttribute.CommandType, handler);
}
还有其他选择吗?虽然这有效,但我真的不想依赖假设处理程序以某种方式实现。
答案 0 :(得分:1)
从Action<CommandA>
投射到Action<ICommand>
不是类型安全的,因为前者只接受CommandA
,而后者只接受任何ICommand
,例如CommandB
。出于这个原因
Delegate.CreateDelegate(typeof(HandlerDelegate), ...)
同样失败,因为HandleA
(和HandleB
)的签名与HandlerDelegate
不兼容。
解决它的一种方法是构造具有正确类型的委托,并使用DynamicInvoke
动态调用它,例如:
var parameters = handlerInfo.GetParameters();
// constructing correct delegate here
var dynamicHandler = Delegate.CreateDelegate(typeof(Action<>).MakeGenericType(parameters.Single().ParameterType), handlerGroup, handlerInfo);
HandlerDelegate handler = (p) =>
{
// invoking
dynamicHandler.DynamicInvoke(p);
};
cache.Add(parameters.Single().ParameterType, handler);
但更好的方法是创建表达式树并将其编译为委托:
var parameters = handlerInfo.GetParameters();
// expression of type ICommand
var expressionArg = Expression.Parameter(typeof(ICommand), "x");
// this is handlerInfo.HandleA((CommandA) x)
var callExp = Expression.Call(Expression.Constant(handlerGroup), handlerInfo, Expression.Convert(expressionArg, parameters.Single().ParameterType));
// this is delegate x => handlerInfo.HandleA((CommandA) x)
var handler = Expression.Lambda<HandlerDelegate>(callExp, new[] { expressionArg }).Compile();
cache.Add(parameters.Single().ParameterType, handler);
答案 1 :(得分:0)
我会单独封装你的缓存;您可以通过方法组添加特定的委托。例如:
public class HandlerCache
{
private readonly Dictionary<Type, HandlerDelegate> handlers = new
Dictionary<Type, HandlerDelegate>();
public void AddHandler<T>(Action<T> action) where T : ICommand =>
handlers.Add(typeof(T), command => action((T) command);
public void HandleCommand(ICommand command)
{
var commandType = command.GetType();
if (!handlers.TryGetValue(commandType, out var handler))
{
throw new ArgumentException($"Unable to handle commands of type {commandType}");
}
handler(command);
}
}
然后您可以像这样使用它:
var cache = new HandlerCache();
// This bit could be done by reflection if you want
var handlerGroup = new HandlerGroup();
cache.AddHandler(handlerGroup.HandleA);
cache.AddHandler(handlerGroup.HandleB);
HandlerDelegate handler = cache.HandleCommand;
// Use handler however you want
显然,总会有错误的余地 - 总会有可能会传递你无法处理的命令。请注意,这当前不处理继承,因为如果有一个派生自CommandA
的类,则HandleCommand
中不会匹配;如果你想找到最具体的可用处理程序,你可能会通过基类型等。
如果通过反射将处理程序添加到缓存中,这种方法意味着您可以在不添加任何属性的情况下确定类型 - 您只需要确定适当的参数类型并将方法添加到缓存中。和往常一样,反射代码写起来有点烦人,但至少你可以用最少的努力添加新的处理程序。