通过Linq表达式树识别事件
当事件没有出现在" + ="或者"-="旁边时,编译器通常会阻塞,因此我不确定这是否可能。
我希望能够通过使用表达式树来识别事件,因此可以为测试创建事件观察器。语法如下所示:
using(var foo = new EventWatcher(target, x => x.MyEventToWatch) { // act here } // throws on Dispose() if MyEventToWatch hasn't fired
我的问题是双重的:
- 编译器会阻塞吗?如果是这样,关于如何防止这种情况的任何建议?
- 我如何从构造函数中解析Expression对象,以添加到target的MyEventToWatch事件?
解决方案
回答
.NET事件实际上不是对象,它是由两个函数表示的端点-一个用于添加,一个用于删除处理程序。这就是为什么编译器不允许我们执行++(代表添加)或者-=(代表删除)以外的任何操作的原因。
出于元编程目的,引用事件的唯一方法是使用System.Reflection.EventInfo,而反射可能是获得事件的最佳方法(如果不是唯一的方法)。
编辑:皇帝XLII编写了一些漂亮的代码,这些代码应该适用于我们自己的事件,前提是我们已从Csimply声明为
public event DelegateType EventName;
这是因为C从该声明为我们创建了两件事:
- 私人代表字段,用作事件的后备存储
- 实际事件以及使用委托的实现代码。
方便地,这两个名称都相同。这就是为什么示例代码适用于我们自己的事件的原因。
但是,在使用其他库实现的事件时,我们不能依靠这种情况。特别是Windows窗体和WPF中的事件没有自己的后备存储,因此示例代码将不适用于它们。
回答
编辑:正如Curt指出的那样,我的实现存在相当多的缺陷,因为它只能在声明事件的类中使用:)返回事件的不是"x => x.MyEvent
",而是返回支持字段,该字段只能由类访问。
由于表达式不能包含赋值语句,因此不能使用诸如"(x,h)=> x.MyEvent + = h
"之类的修改表达式来检索事件,因此需要使用反射。正确的实现需要使用反射来获取事件的EventInfo(不幸的是,不会强类型输入)。
否则,唯一需要进行的更新是存储反映的EventInfo
,并使用AddEventHandler
/RemoveEventHandler
方法注册侦听器(而不是手动Delegate``Combine
/Remove
调用和字段集)。实施的其余部分无需更改。祝你好运 :)
注意:这是演示质量的代码,它对访问器的格式进行了一些假设。适当的错误检查,静态事件的处理等,留给读者练习;)
public sealed class EventWatcher : IDisposable { private readonly object target_; private readonly string eventName_; private readonly FieldInfo eventField_; private readonly Delegate listener_; private bool eventWasRaised_; public static EventWatcher Create<T>( T target, Expression<Func<T,Delegate>> accessor ) { return new EventWatcher( target, accessor ); } private EventWatcher( object target, LambdaExpression accessor ) { this.target_ = target; // Retrieve event definition from expression. var eventAccessor = accessor.Body as MemberExpression; this.eventField_ = eventAccessor.Member as FieldInfo; this.eventName_ = this.eventField_.Name; // Create our event listener and add it to the declaring object's event field. this.listener_ = CreateEventListenerDelegate( this.eventField_.FieldType ); var currentEventList = this.eventField_.GetValue( this.target_ ) as Delegate; var newEventList = Delegate.Combine( currentEventList, this.listener_ ); this.eventField_.SetValue( this.target_, newEventList ); } public void SetEventWasRaised( ) { this.eventWasRaised_ = true; } private Delegate CreateEventListenerDelegate( Type eventType ) { // Create the event listener's body, setting the 'eventWasRaised_' field. var setMethod = typeof( EventWatcher ).GetMethod( "SetEventWasRaised" ); var body = Expression.Call( Expression.Constant( this ), setMethod ); // Get the event delegate's parameters from its 'Invoke' method. var invokeMethod = eventType.GetMethod( "Invoke" ); var parameters = invokeMethod.GetParameters( ) .Select( ( p ) => Expression.Parameter( p.ParameterType, p.Name ) ); // Create the listener. var listener = Expression.Lambda( eventType, body, parameters ); return listener.Compile( ); } void IDisposable.Dispose( ) { // Remove the event listener. var currentEventList = this.eventField_.GetValue( this.target_ ) as Delegate; var newEventList = Delegate.Remove( currentEventList, this.listener_ ); this.eventField_.SetValue( this.target_, newEventList ); // Ensure event was raised. if( !this.eventWasRaised_ ) throw new InvalidOperationException( "Event was not raised: " + this.eventName_ ); } }
用法与建议的用法略有不同,以便利用类型推断:
try { using( EventWatcher.Create( o, x => x.MyEvent ) ) { //o.RaiseEvent( ); // Uncomment for test to succeed. } Console.WriteLine( "Event raised successfully" ); } catch( InvalidOperationException ex ) { Console.WriteLine( ex.Message ); }