通过Linq表达式树识别事件

时间:2020-03-05 18:45:12  来源:igfitidea点击:

当事件没有出现在" + ="或者"-="旁边时,编译器通常会阻塞,因此我不确定这是否可能。

我希望能够通过使用表达式树来识别事件,因此可以为测试创建事件观察器。语法如下所示:

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 );
}