通过Linq表达式树识别事件

9 c# linq expression-trees

当一个事件没有出现在a +=或a 旁边时,编译器通常会窒息-=,所以我不确定这是否可行.

我希望能够通过使用表达式树来识别事件,因此我可以为测试创建事件监视器.语法看起来像这样:

using(var foo = new EventWatcher(target, x => x.MyEventToWatch) {
    // act here
}   // throws on Dispose() if MyEventToWatch hasn't fired
Run Code Online (Sandbox Code Playgroud)

我的问题有两个:

  1. 编译器会窒息吗?如果是这样,有关如何防止这种情况的任何建议?
  2. 如何从构造函数中解析Expression对象以附加到MyEventToWatch事件target

Emp*_*LII 4

编辑:正如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_ );
  }
}
Run Code Online (Sandbox Code Playgroud)

为了利用类型推断,用法与建议的用法略有不同:

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 );
}
Run Code Online (Sandbox Code Playgroud)