基于DelegateEvent创建第一个IEvent对象
2009-09-11 13:47 by 老赵, 12611 visits继续和“事件即对象”打交道。我们之前提到过两个“趣味编程”:DelegateEvent与Functional Reactive Programming,现在我们在它们两者之间架起一座桥梁。也就是说,我们要从一个DelegateEvent<TDelegate>对象创建一个IEvent<TEventArgs>对象出来,其实也就是实现这样一个接口:
public interface IEvent<TEventArgs> { void Add(Action<TEventArgs> callback); } public class DelegateEvent<TDelegate> { public IEvent<TEventArgs> Wrap<TEventArgs>() where TEventArgs : EventArgs { ... } // other members... }
比较可惜的是,即使得到了一个DelegateEvent<TDelegate>对象,Wrap方法还是需要显式提供TEventArgs参数。因为C#没有一个机制可以静态推断出一个委托的第二个参数是什么类型。而且,事实上TDelegate并不是一个适合事件的委托类型。这是因为,作为.NET中用于事件的委托,是需要满足一定要求的:
- 没有返回值(返回void)。
- 拥有两个参数。
- 第一个参数为object类型。
- 第二个参数为System.EventArgs的子类。
于是,我们现在的任务,便是基于目前的DelegateEvent,创建一个IEvent类型出来。由于它是独立为DelegateEvent服务的,我将其定义为DelegateEvent的内部类:
public class DelegateEvent<TDelegate> { public IEvent<TEventArgs> Wrap<TEventArgs>() where TEventArgs : EventArgs { return new NativeEvent<TEventArgs>(this); } // other members... private class NativeEvent<TEventArgs> : IEvent<TEventArgs> where TEventArgs : EventArgs { public NativeEvent(DelegateEvent<TDelegate> delegateEvent) { ... } // other members... } }
既然C#无法帮助我们获取TDelegate的第二个参数的类型,那么我们的第一步便是“提取”出这个Type对象:
private class NativeEvent<TEventArgs> : IEvent<TEventArgs> where TEventArgs : EventArgs { private DelegateEvent<TDelegate> m_delegateEvent; private Type m_eventArgsType; public NativeEvent(DelegateEvent<TDelegate> delegateEvent) { this.m_delegateEvent = delegateEvent; this.m_eventArgsType = this.GetEventArgsType(); } private Type GetEventArgsType() { var invokeMethod = typeof(TDelegate).GetMethod("Invoke"); if (invokeMethod.ReturnType != typeof(void)) { throw new InvalidOperationException("Invalid delegate type of event."); } var parameters = invokeMethod.GetParameters(); if (parameters.Length != 2 || parameters[0].ParameterType != typeof(object) || !typeof(TEventArgs).IsAssignableFrom(parameters[1].ParameterType)) { throw new InvalidOperationException("Invalid delegate type of event."); } return parameters[1].ParameterType; } // other members... }
在GetEventArgsType方法中,我们首先通过TDelegate的类型获取它Invoke方法——这便是委托的签名信息。由于DelegateEvent已经帮我们确认TDelegate一定是一个委托,因此invokeMethod对象一定不为null。于是我们开始全方位的检查,如果遇到了以下任意一种情况,则抛出异常:
- 返回类型不为void。
- 参数数量不为2。
- 第一个参数不为object类型。
- 第二个参数是否与TEventArgs兼容。
除了最后一点要求,其他都与之前的“标准”一一对应。“标准”中的第4点已经由泛型参数TEventArgs的约束保证了,而我们检查的其实是TEventArgs与第二个参数类型的“兼容性”。换句话说,我们的实现可以允许各种转换方式,如父子继承,隐式转换等等。
NativeEvent<TEventArgs>类型基于DelegateEvent<TDelegate>,自然需要为DelegateEvent添加一个事件处理函数,然后再事件触发时,利用第二个参数再去触发自身的每个回调方法。因此,我们还要为NativeEvent添加这些代码:
private class NativeEvent<TEventArgs> : IEvent<TEventArgs> where TEventArgs : EventArgs { private List<Action<TEventArgs>> m_callbacks = new List<Action<TEventArgs>>(); public void Add(Action<TEventArgs> callback) { if (this.m_callbacks.Count == 0) { this.RegisterEventHandler(); } this.m_callbacks.Add(callback); } private void RegisterEventHandler() { ... } private void FireEvent(TEventArgs args) { foreach (var callback in this.m_callbacks) callback(args); } // other members... }
当外界使用Add方法向NativeEvent添加回调方法时,会将回调方法保存起来。需要注意的是,只有当外界“第一次”调用Add方法时,NatvieEvent才会去“监听”DelegateEvent对象,而监听这个行为是由RegisterEventHandler负责的。它的实现是NativeEvent的又一关键。
既然是要监听DelegateEvent,自然是要准备一个委托对象并使用AddHandler方法交给DelegateEvent。但是,我们现在只知道TDelegate类型,但是无法真正确定它的签名,因此,我们无法在编译期准备好一个方法来创建TDelegate对象,我们能做的就是在运行时动态生成一个委托。那么这个委托的形式应该是什么样的呢?这倒容易:
(sender, args) => this.FireEvent((TEventArgs)args)
这是一个使用Lambda表达式生成的匿名方法,我们需要动态生成的委托便是这个模样。于是剩下来的便交给表达式树吧:
private static MethodInfo s_fireEventMethod = typeof(NativeEvent<TEventArgs>).GetMethod("FireEvent", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod); private void RegisterEventHandler() { // sender var senderExpr = Expression.Parameter(typeof(object), "sender"); // eventArgs var eventArgsExpr = Expression.Parameter(this.m_eventArgsType, "args"); // (TEventArgs)eventArgs var castExpr = typeof(TEventArgs) == this.m_eventArgsType ? eventArgsExpr : (Expression)Expression.Convert(eventArgsExpr, typeof(TEventArgs)); // this var thisExpr = Expression.Constant(this); // this.FireEvent((TEventArgs)args) var bodyExpr = Expression.Call(thisExpr, s_fireEventMethod, castExpr); // (sender, args) => this.FireEvent((TEventArgs)args) var lambdaExpr = Expression.Lambda<TDelegate>(bodyExpr, senderExpr, eventArgsExpr); this.m_delegateEvent += lambdaExpr.Compile(); }
构造表达式的过程分以下几步走:
- 构造一个object类型的参数sender。
- 构造一个委托第二个参数(m_eventArgsType)类型的参数args。
- (如果TEventArgs与委托第二个参数类型不同,则)构造一个转化操作(TEventArgs)args。
- 构造一个常量this。
- 构造一个方法调用this.FireEvent((TEventArgs)args)。
- 将上述方法调用封装为一个Lambda表达式:(sender, args) => this.FireEvent((TEventArgs)args)。
Complie之后即大功告成。可见,生成一个表达式树也是非常直观的,您在构造一个表达式时候,也可以使用这种方法,一步一步地进行下去。
在使用时,我们可以通过这样的代码:
var de = new DelegateEvent<EventHandler>(...); var e = de.Wrap<EventArgs>(); e.Add((eventArgs) => Console.WriteLine(eventArgs));
现在,您可以把它作为第一个IEvent对象,继续尝试Functional Reactive Programming了。
沙发??老赵的沙发??不容易。。。
ps:最近老赵的博客"新陈代谢“旺盛啊