• 艾伟_转载:把事件当作对象进行传递 狼人:


      最近在琢磨一些事情,和API设计有关。API设计在很多时候是和语言特性有关的,因此如Java这样的语言,在API设计时会处处受到压抑。而C#就能够出现如MoqFluent NHIbernate这样的项目。同样,F#能够开发出FsTest,Scala号称Scalable Language,都是依靠着丰富的语言特性。不过,最近在使用C#的时候鼻子上也碰了一点灰,这是因为我发现“事件”这个东西没法作为对象进行传递。

    public class Program
    {
        public event EventHandler Submit;
    }
    

      我们如果要为这个事件添加处理函数自然只要:

    var myClass = new MyClass();
    myClass.Submit += (sender, eventArgs) => Console.WriteLine(sender);
    

      但是,如果我想写一个“统一添加”的辅助函数,例如可以这样调用:

    RegisterHandlers(myClass.Submit);

      就会发现——做不到。虽然,如果我们提供这样的RegisterHandlers方法的实现:

    class Program
    {
        public event EventHandler Submit;
        static void RegisterHandlers(EventHandler ev)
        {
            ev += (sender, eventArgs) => Console.WriteLine("sender");
        }
        static void Main(string[] args)
        {
            Program p = new Program();
            RegisterHandlers(p.Submit);
            p.Submit("Hello World", EventArgs.Empty);
        }
    }
    

      这是可以编译通过的,似乎……应该也过得去。但是实际执行的时候就会发现,p.Submit事件在触发的时候依然会抛出NullReferenceException异常(为什么?)。因此,我们必须选择另外一种方式。

      我们知道,虽说是一个事件,但是在注册和移除处理函数的时候,实际上都是在调用add方法和remove方法。例如这句代码:

    myClass.Submit += (sender, eventArgs) => Console.WriteLine(sender);

      和下面的代码其实是“等价”的:

    myClass.add_Submit((sender, eventArgs) => Console.WriteLine(sender));

      “等价”打上引号是因为add_Submit这行代码其实无法编译通过,我只是用来表示一个含义。但是这意味着,我们可以通过反射来调用add方法和remove方法。因此,我编写了这样的一个类:

    public class Event
    {
        public Event(Expression<Func> eventExpr)
        {
            ...
        }
        private object m_instance;
        private MethodInfo m_addMethod;
        private MethodInfo m_removeMethod;
        public Event AddHandler(T handler)
        {
            this.m_addMethod.Invoke(this.m_instance, new object[] { handler });
            return this;
        }
        public Event RemoveHandler(T handler)
        {
            this.m_removeMethod.Invoke(this.m_instance, new object[] { handler });
            return this;
        }
    }
    

      于是,我可以设法把一个事件封装为一个对象:

    class Program
    {
        public event EventHandler Submit;
        static void Main(string[] args)
        {
            Program p = new Program();
            var ev = new Event<EventHandler>(() => p.Submit);
            ev.AddHandler((sender, eventArgs) => Console.WriteLine(sender));
            p.Submit("Hello World", EventArgs.Empty);
        }
    }
    

      那么Event类的构造函数该怎么写呢?不过是解析表达式树而已:

    public Event(Expression<Func> eventExpr)
    {
        var memberExpr = eventExpr.Body as MemberExpression;
        this.m_instance = memberExpr.Expression == null ? null :
            Expression.Lambda<Func<object>>(memberExpr.Expression).Compile()();
        var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.InvokeMethod |
            (this.m_instance == null ? BindingFlags.Static : BindingFlags.Instance);
        var member = memberExpr.Member;
        this.m_addMethod = member.DeclaringType.GetMethod("add_" + member.Name, bindingFlags);
        this.m_removeMethod = member.DeclaringType.GetMethod("remove_" + member.Name, bindingFlags);
    }
    

      对于() => p.Submit这样的代码来说,它是一个MemberExpression,我们可以通过MemberExpression的属性来说的p的实例。然后,根据Submit属性的Member的Name便可以得出它的add或remove方法。其中需要再判断这是一个实例事件还是一个静态事件就可以了。总体来说,代码比较简单。当然,在实际运用中会要求在不合法的情况下抛出合适的异常。此外,如果您对性能有要求,也可以使用FastLambdaFastReflectionLib来提高性能。

      为了方便使用,我还为Event类重载了+和-两个操作符,以及一个EventFactory类:

    public static class EventFactory
    {
        public static Event Create(Expression<Func> eventExpr)
        {
            return new Event(eventExpr);
        }
    }
    public class Event
    {
        ...
        public static Event operator +(Event ev, T handler)
        {
            return ev.AddHandler(handler);
        }
        public static Event operator -(Event ev, T handler)
        {
            return ev.RemoveHandler(handler);
        }
    }
    

      EventFactory类的Create方法可以避免显式地提供T类型,而+和-操作符的目的便是在添加和删除事件处理函数的时候“更像那么一回事”。于是现在我们便可以写这样的代码:

    class Program
    {
        public event EventHandler Submit;
        static void Main(string[] args)
        {
            Program p = new Program();
            var ev = EventFactory.Create(() => p.Submit);
            ev += (sender, eventArgs) => Console.WriteLine(sender);
            p.Submit("Hello World", EventArgs.Empty);
            Console.WriteLine("Press any key to exit...");
            Console.ReadLine();
        }
    }
    

      既然有了Event对象,我们便可以把它作为参数传递给其他方法,然后在其他的方法中添加或删除事件处理函数。

      是不是挺美妙的?您也来下载完整代码试试看吧,而且说不定……您还能发现这个方法里的一个陷阱。我承认,其实这个解决方案会遇见C#的一个问题,它糊弄了我,也糊弄了大家……

  • 相关阅读:
    php中json_encode中文编码问题
    PLSQL Developer建表时注释(COMMENT)中文乱码的解决方案(Windows)
    JQuery实现 checkbox 全选、反选,子checkbox有没选去掉全选
    oracle group by 使用
    oracle distinct 去除重复,同时按某字段排序
    phpstorm 设置多项目并存
    putty修改编码
    Java基本开发环境搭建
    smarty 判断变量是否为空
    vim
  • 原文地址:https://www.cnblogs.com/waw/p/2157130.html
Copyright © 2020-2023  润新知