當事件沒有出現在+=
或a -=
旁邊時,編譯器通常會窒息,所以我不確定這是否可行。
我希望能夠通過使用表達式樹來識別事件,因此我可以為測試創建事件監視器。語法看起來像這樣:
using(var foo = new EventWatcher(target, x => x.MyEventToWatch) {
// act here
} // throws on Dispose() if MyEventToWatch hasn't fired
我的問題有兩個:
target
的MyEventToWatch
事件? 編輯:正如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 );
}
我也想這樣做,而且我想出了一個非常酷的方式,就像Emperor XLII的想法一樣。它不使用表達式樹,如上所述,這不能完成,因為表達式樹不允許使用+=
或-=
。
然而,我們可以使用一個巧妙的技巧,我們使用.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