c# - 通过 Linq 表达式树识别事件

标签 c# linq expression-trees

当事件没有出现在 +=-= 旁边时,编译器通常会卡住,所以我不确定这是否可能。

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

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

我的问题有两个:

  1. 编译器会卡住吗?如果是这样,关于如何防止这种情况有什么建议吗?
  2. 如何从构造函数中解析 Expression 对象,以便附加到 targetMyEventToWatch 事件?

最佳答案

编辑:作为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/

相关文章:

c# - 某种流程顺序

c# - 在方法中使用 lambda 并匹配列表

c# - 使用表达式组合具有不同签名的函数

c# - Skip and Take 在 Linq to Objects 中的表现

c# - Azure:启动远程调试器失败

c# - 在新的应用程序域中运行时,如何将 stdout 转换为 mstest 输出?

c# - 将笨拙的 xml 文档结构转换为友好的文档结构

c# - 如何在 linq 连接 (lambda) 上添加 where 子句?

linq - 构建动态表达式来比较两个表

c# - 使用带有 jquery ui 对话框的 telerik 网格!