为啥 Castle Windsor 拦截器会破坏 C# 动态对象上方法的运行时绑定?
Posted
技术标签:
【中文标题】为啥 Castle Windsor 拦截器会破坏 C# 动态对象上方法的运行时绑定?【英文标题】:Why Castle Windsor interceptor breaks the runtime binding of a method on a C# dynamic object?为什么 Castle Windsor 拦截器会破坏 C# 动态对象上方法的运行时绑定? 【发布时间】:2019-09-10 07:03:01 【问题描述】:对于这个问题涉及的术语过多,我提前道歉:下面的词 dynamic 将用于 C# 动态类型功能和 Castle Windsor 动态代理功能。
我们基本上有一个场景,在运行时,我们必须为事件对象选择正确的事件处理程序。事件处理程序由工厂对象提供,工厂对象在内部使用城堡温莎依赖注入容器来提供对象实例,提供给工厂的输入是标记接口IEvent
的实例。
只是为了解决这个想法,这些是所涉及的类(这是一个简化的场景,但问题的本质得到了保留):
public interface IEvent
public class CustomerCreated : IEvent
public string CustomerName get; set;
public interface IEventHandler
public interface IEventHandler<T> : IEventHandler where T: IEvent
void Handle(T event);
public class CustomerService : IEventHandler<CustomerCreated>
public void Handle(CutomerCreated @event)
// handle the event in some way...
public interface IEventHandlerFactory
IEventHandler[] GetHandlers(IEvent event);
这是获取事件的消费代码,要求工厂提供处理程序并执行所有提供的处理程序(这也是一个简化版本,但本质仍然存在):
public class EventDispatcher
private readonly IEventHandlerFactory factory;
public EventDispatcher(IEventHandlerFactory factory)
this.factory = factory ?? throw new ArgumentNullException(nameof(factory));
public void Dispatch(IEvent @event)
foreach(var handler in this.factory.GetHandlers(@event))
((dynamic)handler).Handle((dynamic)@event);
自从我们决定创建一个 Castle Windsor 拦截器以拦截对 CustomerService
类的方法 Handle
的所有调用以来,这段代码多年来一直运行良好,这样我们就可以在每次该方法运行时写入一些日志叫。
现在整个故事的糟糕部分......
新添加的 Castle Windsor 拦截器破坏了 Handle
方法对在类 EventDispatcher
中显示的动态对象 ((dynamic)handler)
的运行时绑定。
报告的错误是RuntimeBinderException,它表明对CastleDynamicProxy_14.Handle
方法的最佳重载的调用包含一些无效参数(异常消息中实际选择和报告的方法是错误的,因为它接受不同的事件类型作为参数)。
我们仔细调查了这个异常,它基本上意味着运行时绑定选择了错误的Handle
类的Handle
方法来绑定调用(CustomerService
在我们的真实代码中处理了几个事件,所以它有很多称为Handle
的方法,根据IEventHandler<T>
接口定义,每个方法都将不同类型的事件作为其唯一参数。
奇怪的是引入了castle windsor动态代理对象,它包裹了真实的对象(CustomerService
被Castle拦截),打破了C#运行时绑定只针对一些事件,而对于其他人来说,上面显示的类EventDispatcher
和以前一样工作得很好。中断事件和工作事件之间没有相关区别:它们都是实现标记接口IEvent
的POCO类。
有人对动态代码有过类似的问题吗?当 CLR 在动态对象上执行运行时绑定过程时,有没有办法获取有关 CLR 完成的过程的某种详细日志记录?
我们无法在应用程序之外的最小示例中重现相同的问题,因此我将排除 Castle Dynamic Proxy 级别或 C# 级别的错误。
我觉得问题只取决于我们在 Castle Windsor 中注册服务和拦截器的方式。
关键是我们的代码中有几十个拦截器,它们都可以正常工作。此外,为什么只有一两个事件被破坏,而其他事件在相同的事件调度程序代码和相同的注册拦截器下工作正常?
我目前正忙于调查,我不知道出了什么问题。
【问题讨论】:
为什么不使用泛型作为调度程序?完全避免使用动态? @MartinErnst 因为在编译时您对事件的具体类型一无所知,您唯一知道的是它是实现接口 IEvent 的对象的实例。这意味着你不知道如何调用泛型方法(你使用什么类型作为方法调用的类型参数?) 【参考方案1】:这并不能解决 Castle 拦截器/动态的问题,但我已经通过使用通用版本的 Dispatch 多次编写了类似的代码,根本不需要动态。
例如:
public interface IEventHandlerFactory
IEventHandler<TEvent>[] GetHandlers<TEvent>(TEvent event) where TEvent : IEvent;
public class EventDispatcher
private readonly IEventHandlerFactory factory;
public EventDispatcher(IEventHandlerFactory factory)
this.factory = factory ?? throw new ArgumentNullException(nameof(factory));
public void Dispatch<TEvent>(TEvent @event) where TEvent : IEvent
foreach(var handler in this.factory.GetHandlers(@event))
handler.Handle(@event);
调用它:
eventDispatcher.Dispatch(new MyCustomEvent());
如果您确实需要动态调用它,您仍然可以使用反射来调用 eventDispatcher.Dispatch,但您不会遇到强制转换为动态和 Castle Interceptors 的问题 - 对于看似潜在的问题,这是一种可能的解决方法对我来说是个错误
【讨论】:
这仅适用于客户端代码使用显式类型绑定(.Dispatch( new Foo() )
。另一方面,如果它们具有通用外观(例如,参数的唯一类型约束是 IEvent
),具体的类型信息仍然不可用。
@WiktorZychla 你能给我举个例子吗?
@MartinErnst 通用解决方案的问题是我们在eventDispatcher
对象上调用方法Dispatch
的方式。调用是在一段代码中完成的,其中我们有一个编译时间类型为IEvent
的对象。这样你就不能调用泛型方法,因为你不知道调用本身的类型参数。此外,您不能利用类型推断:使用 IEvent
作为类型参数调用泛型方法是不正确的,因为事件处理程序类处理特定的事件类型
@MartinErnst 当然有可能的解决方法。例如,一种可能的解决方案是使用事件对象的运行时类型通过反射调用泛型方法。这样你就不需要在编译时知道类型参数。当然也有一些问题:这不是类型安全的(实际上你对动态方法也有同样的问题),它不是很快(但你可以通过缓存反射结果来改进它)并且它可能比使用解决方案的可读性差动态。
@MartinErnst 除了基于反射的方法之外,您知道在编译时不知道类型参数的情况下调用泛型方法的方法吗? (我确实没有)以上是关于为啥 Castle Windsor 拦截器会破坏 C# 动态对象上方法的运行时绑定?的主要内容,如果未能解决你的问题,请参考以下文章
castle windsor学习-----Inline dependencies 依赖
Castle Windsor 3 + Fluent NHibernate + Castle.NHibernate.Integration