当事件没有出现在+=
或-=
旁边时,编译器通常会窒息,所以我不确定这是否可行。
我希望能够通过使用表达式树来识别事件,因此我可以为测试创建事件监视器。语法看起来像这样:
using(var foo = new EventWatcher(target, x => x.MyEventToWatch) {
// act here
} // throws on Dispose() if MyEventToWatch hasn't fired
我的问题有两个:
MyEventToWatch
的{{1}}事件?答案 0 :(得分:4)
编辑:正如Curt指出的那样,我的实现存在相当大的缺陷,因为它只能在声明事件的类中使用:)而不是“{{1 “返回事件,它返回了支持字段,该字段只能由类访问。
由于表达式不能包含赋值语句,因此不能使用像“x => x.MyEvent
”这样的修饰表达式来检索事件,因此需要使用反射。正确的实现需要使用反射来检索事件的( x, h ) => x.MyEvent += h
(不幸的是,不会强类型化)。
否则,唯一需要进行的更新是存储反映的EventInfo
,并使用EventInfo
/ AddEventHandler
方法注册侦听器(而不是手动{{} 1}} RemoveEventHandler
/ Delegate
调用和字段集)。其余的实现不需要改变。祝你好运:)
注意:这是一个演示质量的代码,可以对访问者的格式做出一些假设。正确的错误检查,静态事件的处理等,留给读者练习;)
Combine
用法与建议略有不同,以便利用类型推断:
Remove
答案 1 :(得分:3)
我也想这样做,而且我想出了一个非常酷的方式,就像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
答案 2 :(得分:2)
.NET事件实际上不是一个对象,它是一个由两个函数表示的端点 - 一个用于添加,另一个用于删除处理程序。这就是为什么编译器不会让你做除+ =(代表添加)或 - =(代表删除)以外的任何事情。
为元编程目的引用事件的唯一方法是作为System.Reflection.EventInfo,并且反射可能是获得一个事件的最佳方式(如果不是唯一的方法)。
编辑:Emperor XLII写了一些漂亮的代码,这些代码应该适用于您自己的事件,前提是您已经将它们从C#声明为public event DelegateType EventName;
那是因为C#从该声明中为您创建了两件事:
方便地,这两者具有相同的名称。这就是示例代码适用于您自己的事件的原因。
但是,在使用其他库实现的事件时,您不能依赖于此。特别是,Windows窗体和WPF中的事件没有自己的后备存储,因此示例代码不适用于它们。
答案 3 :(得分:1)
虽然皇帝XLII已经给出了答案,但我认为值得分享我对此的重写。可悲的是,没有能力通过表达式树获取事件,我正在使用事件的名称。
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();
}