当事件没有出现在 +=
或 -=
旁边时,编译器通常会卡住,所以我不确定这是否可能。
我希望能够通过使用表达式树来识别事件,因此我可以为测试创建一个事件观察器。语法看起来像这样:
using(var foo = new EventWatcher(target, x => x.MyEventToWatch) {
// act here
} // throws on Dispose() if MyEventToWatch hasn't fired
我的问题有两个:
- 编译器会卡住吗?如果是这样,关于如何防止这种情况有什么建议吗?
- 如何从构造函数中解析 Expression 对象,以便附加到
target
的MyEventToWatch
事件?
最佳答案
编辑:作为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 );
}
关于c# - 通过 Linq 表达式树识别事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35211/