代码改变世界

基于DelegateEvent创建第一个IEvent对象

2009-09-11 13:47 Jeffrey Zhao 阅读(...) 评论(...) 编辑 收藏

继续和“事件即对象”打交道。我们之前提到过两个“趣味编程”:DelegateEventFunctional 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中用于事件的委托,是需要满足一定要求的:

  1. 没有返回值(返回void)。
  2. 拥有两个参数。
  3. 第一个参数为object类型。
  4. 第二个参数为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。于是我们开始全方位的检查,如果遇到了以下任意一种情况,则抛出异常:

  1. 返回类型不为void。
  2. 参数数量不为2。
  3. 第一个参数不为object类型。
  4. 第二个参数是否与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();
}

构造表达式的过程分以下几步走:

  1. 构造一个object类型的参数sender。
  2. 构造一个委托第二个参数(m_eventArgsType)类型的参数args。
  3. (如果TEventArgs与委托第二个参数类型不同,则)构造一个转化操作(TEventArgs)args。
  4. 构造一个常量this。
  5. 构造一个方法调用this.FireEvent((TEventArgs)args)。
  6. 将上述方法调用封装为一个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了。