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

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

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

我的问题有两个:

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

解决方案

编辑: 作为 柯特 已经指出,我的实现相当有缺陷,因为它只能在声明事件的类中使用:)而不是“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 );
}

其他提示

我也想这样做,并且我想出了一个非常酷的方法,可以实现类似皇帝四十二的想法。但它不使用表达式树,正如前面提到的,这是无法完成的,因为表达式树不允许使用 += 或者 -=.

然而,我们可以使用一个巧妙的技巧,即使用 .NET Remoting Proxy(或任何其他代理,例如 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<T> 将传递某个实例的委托 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<T> 代表提供。

我们有以下内容 CustomProxy<T> 代码,谁拦截对 +=-= 在代理对象上

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

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

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

编辑:皇帝四十二编写了一些漂亮的代码,这些代码应该适用于您自己的事件,只要您从 C# 中将它们简单地声明为

public event DelegateType EventName;

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

  1. 一个私人代表领域,作为活动的后备存储
  2. 实际事件以及使用代表的实施代码。

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

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

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

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();
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top