【问题标题】:Identify an event via a Linq Expression tree通过 Linq 表达式树识别事件
【发布时间】:2010-09-07 07:19:44
【问题描述】:

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

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

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

我的问题有两个:

  1. 编译器会窒息吗?如果是这样,有什么建议可以防止这种情况发生吗?
  2. 如何从构造函数解析 Expression 对象以附加到 targetMyEventToWatch 事件?

【问题讨论】:

    标签: c# linq expression-trees


    【解决方案1】:

    编辑:正如Curt 所指出的,我的实现存在相当大的缺陷,因为它只能在声明事件的类中使用:) 而不是“x => x.MyEvent”返回事件,它正在返回支持字段,该字段只能由类访问。

    由于表达式不能包含赋值语句,修改后的表达式如“( x, h ) => x.MyEvent += h”不能用于检索事件,因此需要使用反射。正确的实现需要使用反射来检索事件的EventInfo(不幸的是,它不会被强类型化)。

    否则,唯一需要做的更新就是存储反射的EventInfo,并使用AddEventHandler/RemoveEventHandler方法注册监听器(而不是手动DelegateCombine/@ 987654330@ 电话和字段集)。实施的其余部分不需要更改。祝你好运:)


    注意:这是演示质量的代码,它对访问器的格式做了几个假设。正确的错误检查、静态事件的处理等,留给读者作为练习;)

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

    【讨论】:

      【解决方案2】:

      我也想这样做,我想出了一个很酷的方法,可以实现类似于皇帝 XLII 的想法。但它不使用表达式树,正如前面提到的那样,这是无法做到的,因为表达式树不允许使用+=-=

      然而,我们可以使用一个巧妙的技巧,使用 .NET 远程代理(或任何其他代理,例如 LinFu 或 Castle DP)来拦截对生命周期很短的代理对象上的添加/删除处理程序的调用。这个代理对象的作用就是简单的调用它的某个方法,并允许它的方法调用被拦截,这时候我们就可以找出事件的名字了。

      这听起来很奇怪,但这里是代码(顺便说一下,只有在您有 MarshalByRefObject 或代理对象的接口时才有效)

      假设我们有以下接口和类

      public interface ISomeClassWithEvent {
          event EventHandler<EventArgs> Changed;
      }
      
      
      public class SomeClassWithEvent : ISomeClassWithEvent {
          public event EventHandler<EventArgs> Changed;
      
          protected virtual void OnChanged(EventArgs e) {
              if (Changed != null)
                  Changed(this, e);
          }
      }
      

      然后我们可以有一个非常简单的类,它需要一个Action&lt;T&gt; 委托,该委托将传递T 的一些实例。

      这里是代码

      public class EventWatcher<T> {
          public void WatchEvent(Action<T> eventToWatch) {
              CustomProxy<T> proxy = new CustomProxy<T>(InvocationType.Event);
              T tester = (T) proxy.GetTransparentProxy();
              eventToWatch(tester);
      
              Console.WriteLine(string.Format("Event to watch = {0}", proxy.Invocations.First()));
          }
      }
      

      诀窍是将代理对象传递给提供的Action&lt;T&gt; 委托。

      我们有以下CustomProxy&lt;T&gt; 代码,它拦截了对代理对象上+=-= 的调用

      public enum InvocationType { Event }
      
      public class CustomProxy<T> : RealProxy {
          private List<string> invocations = new List<string>();
          private InvocationType invocationType;
      
          public CustomProxy(InvocationType invocationType) : base(typeof(T)) {
              this.invocations = new List<string>();
              this.invocationType = invocationType;
          }
      
          public List<string> Invocations {
              get { 
                  return invocations; 
              }
          }
      
          [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.Infrastructure)]
          [DebuggerStepThrough]
          public override IMessage Invoke(IMessage msg) {
              String methodName = (String) msg.Properties["__MethodName"];
              Type[] parameterTypes = (Type[]) msg.Properties["__MethodSignature"];
              MethodBase method = typeof(T).GetMethod(methodName, parameterTypes);
      
              switch (invocationType) {
                  case InvocationType.Event:
                      invocations.Add(ReplaceAddRemovePrefixes(method.Name));
                      break;
                  // You could deal with other cases here if needed
              }
      
              IMethodCallMessage message = msg as IMethodCallMessage;
              Object response = null;
              ReturnMessage responseMessage = new ReturnMessage(response, null, 0, null, message);
              return responseMessage;
          }
      
          private string ReplaceAddRemovePrefixes(string method) {
              if (method.Contains("add_"))
                  return method.Replace("add_","");
              if (method.Contains("remove_"))
                  return method.Replace("remove_","");
              return method;
          }
      }
      

      然后我们剩下的就是如下使用它

      class Program {
          static void Main(string[] args) {
              EventWatcher<ISomeClassWithEvent> eventWatcher = new EventWatcher<ISomeClassWithEvent>();
              eventWatcher.WatchEvent(x => x.Changed += null);
              eventWatcher.WatchEvent(x => x.Changed -= null);
              Console.ReadLine();
          }
      }
      

      这样做我会看到这个输出:

      Event to watch = Changed
      Event to watch = Changed
      

      【讨论】:

        【解决方案3】:

        .NET 事件实际上并不是一个对象,它是一个由两个函数表示的端点——一个用于添加处理程序,一个用于删除处理程序。这就是为什么编译器不会让您执行除 +=(表示添加)或 -=(表示删除)之外的任何操作。

        为元编程目的引用事件的唯一方法是作为 System.Reflection.EventInfo,而反射可能是获取事件的最佳方法(如果不是唯一方法)。

        编辑:Emperor XLII 编写了一些漂亮的代码,这些代码应该适用于您自己的事件,前提是您已将它们从 C# 中简单地声明为

        public event DelegateType EventName;
        

        这是因为 C# 从该声明中为您创建了两件事:

        1. 用作支持的私有委托字段 活动存储
        2. 实际事件连同 使用的实现代码 代表。

        方便的是,这两者具有相同的名称。这就是示例代码适用于您自己的事件的原因。

        但是,当使用由其他库实现的事件时,您不能依赖这种情况。特别是,Windows 窗体和 WPF 中的事件没有自己的后备存储,因此示例代码不适用于它们。

        【讨论】:

          【解决方案4】:

          虽然四十二皇帝已经给出了答案,但我认为值得分享我对此的重写。遗憾的是,无法通过表达式树获取事件,我使用的是事件的名称。

          public sealed class EventWatcher : IDisposable {
               private readonly object _target;
               private readonly EventInfo _eventInfo;
               private readonly Delegate _listener;
               private bool _eventWasRaised;
          
               public static EventWatcher Create<T>(T target, string eventName) {
                   EventInfo eventInfo = typeof(T).GetEvent(eventName);
                   if (eventInfo == null)
                      throw new ArgumentException("Event was not found.", eventName);
                   return new EventWatcher(target, eventInfo);
               }
          
               private EventWatcher(object target, EventInfo eventInfo) {
                   _target = target;
                   _eventInfo = event;
                   _listener = CreateEventDelegateForType(_eventInfo.EventHandlerType);
                   _eventInfo.AddEventHandler(_target, _listener);
               }
          
               // SetEventWasRaised()
               // CreateEventDelegateForType
          
               void IDisposable.Dispose() {
                   _eventInfo.RemoveEventHandler(_target, _listener);
                   if (!_eventWasRaised)
                      throw new InvalidOperationException("event was not raised.");
               }
          }
          

          而用法是:

          using(EventWatcher.Create(o, "MyEvent")) {
              o.RaiseEvent();
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-08-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多