• 理解C#事件


    前面文章中介绍了委托相关的概念,委托实例保存这一个或一组操作,程序中将在某个特定的时刻通过委托实例使用这些操作。

    如果做过GUI程序开发,可能对上面的描述会比较熟悉。在GUI程序中,单击一个button会触发一个click事件,然后会执行一系列的操作,这一系列的操作就被存放在一个委托实例中。

    接下来我们就看看事件。

    使用委托中的问题

    回到前面文章中苹果和富士康的例子,苹果将iphone的组装、包装和运输的工作全部委托给了富士康。

    根据上面的描述,我们修改了一下代码,在Apple这个类中加入一个订单属性,苹果只要接到新的订单,就发送一个通知给富士康,然后富士康就会执行一系列的操作了(组装、包装和运输)。

    在主程序中,苹果将iphone的组装、包装和运输工作委托给了富士康,然后苹果每次收到订单,就会通过委托实例"VerdorToAssembleIphone"让富士康去执行一系列操作。

    class Apple
    {
        //声明委托类型
        public delegate void AssembleIphoneHandler(int num);
        public AssembleIphoneHandler VerdorToAssembleIphone;
    
        public void DesignIphone()
        {
            Console.WriteLine("Design Iphone By Apple");
        }
    
        private int orderNum;
        public int OrderNum
        {
            get { return this.orderNum; }
            set
            {
                this.orderNum = value;
                if (VerdorToAssembleIphone != null)
                {
                    VerdorToAssembleIphone(this.orderNum);
                }
            }
        }
    }
    
    class Foxconn
    {
        //与委托类型签名相同的方法
        public void AssembleIphone(int num)
        {
            Console.WriteLine("Assemble {0} Iphone By Foxconn", num);
        }
    
        public void PackIphone(int num)
        {
            Console.WriteLine("Pack {0} Ipnone By Foxconn", num);
        }
    
        public void ShipIphone(int num)
        {
            Console.WriteLine("Ship {0} Iphone By Foxconn", num);
        }
    }
    
    
    class Program
    {
        static void Main(string[] args)
        {
            Apple apple = new Apple();
            Foxconn foxconn = new Foxconn();
            apple.VerdorToAssembleIphone = new Apple.AssembleIphoneHandler(foxconn.AssembleIphone);
            apple.VerdorToAssembleIphone += new Apple.AssembleIphoneHandler(foxconn.PackIphone);
            apple.VerdorToAssembleIphone += new Apple.AssembleIphoneHandler(foxconn.ShipIphone);
    
            apple.OrderNum = 100;
         
            Console.Read();
        }
    }

    下面我们看下这个例子实现中的问题:

    1. 如果用户在建立委托链的时候错误的使用了"="而不是"+=",那么委托链就断了
    2. 在主程序中,我们可以绕过设置订单这一步,直接调用委托实例"apple.VerdorToAssembleIphone(99);"来让富士康执行操作,这点是不合理的
    class Program
    {
        static void Main(string[] args)
        {
            Apple apple = new Apple();
            Foxconn foxconn = new Foxconn();
    
            //创建委托实例
            apple.AssembleIphone = new Apple.AssembleIphoneHandler(foxconn.AssembleIphone);
            apple.AssembleIphone += new Apple.AssembleIphoneHandler(foxconn.PackIphone);
            apple.AssembleIphone = new Apple.AssembleIphoneHandler(foxconn.ShipIphone);
            apple.DesignIphone();
            
            //委托实例的调用
            apple.VerdorToAssembleIphone(99);
         
            Console.Read();
        }
    }

    事件的出现

    为了解决上面两个问题,出现了事件这个概念,我们要做的改变只是在声明委托实例的时候加上event关键字。

    public event AssembleIphoneHandler VerdorToAssembleIphone;

    这时,上面两处有问题的代码就会在编译的时候报错了。    

    上面的问题是解决了,但是event关键字作用是什么,事件跟委托有什么关系,"VerdorToAssembleIphone"怎么理解?

    深入理解事件

    其实,下面这个语句还是比较难理解的,看到的第一反应就是,委托跟事件到底是什么关系,event关键字跟委托类型"AssembleIphoneHandler"是什么关系。

    public event AssembleIphoneHandler VerdorToAssembleIphone;

    其实,事件可以理解成一个委托的属性,通过对委托实例的封装来对委托实例的访问进行一些限制。下面我们通过IL来查看一下这个个程序,得到下图。

    下面我们就结合IL的查看结果来分析事件到底是什么。在开始之前,相信大家一定都熟悉属性(property)这个概念吧,那就让我们从熟悉的属性开始分析。

    C#属性的概念

    首先来看看我们熟悉的东西,"OrderNum"是我们定义的一个订单数量的属性(property),它有一组get/set方法。通过这个属性的get/set方法,我们可以访问"orderNum"字段(field)。

    通过IL查看"OrderNum"这个属性,我们可以看到这个属性对应的get/set方法。

    public int32 get_OrderNum() { }
    public void set_OrderNum(int32 'value') { }

    事件

    接下来再看"VerdorToAssembleIphone"事件,我们按照属性的方式去理解事件,从IL的截图中,可以看到事件中有一对addon/removeon方法来操作我们的委托实例(想想属性的get/set方法)。

    同时,我们看到编译器给我们生成了一个private的field(如下),从这里我们可以看到事件"VerdorToAssembleIphone"本质上就是一个委托类型的变量。由于这个变量是private的,也就解释了为什么我们在定义事件的类外部不能直接访问这个变量。

    .field private class _1_1_Delegate.Apple/AssembleIphoneHandler VerdorToAssembleIphone

    事件代码的转换

    根据上面的分析,我们可以看到,编译器帮我们进行了下面的代码转换。这样一来,就相当于通过event关键字对委托实例访问增加了一些限制。

    原始的声明事件的C#代码:

    public event AssembleIphoneHandler VerdorToAssembleIphone;

    编译器转换后的代码:

    private AssembleIphoneHandler VerdorToAssembleIphone;
    
    public void add_VerdorToAssembleIphone(AssembleIphoneHandler 'value') { }
    
    public void remove_VerdorToAssembleIphone(AssembleIphoneHandler 'value') { }

    通过上面的分析,可以了解到事件封装了委托类型的实例,使得:

    • 在定义事件的类内部,不管你声明它是public还是protected,它总是private的;也就是说在在定义事件的类外部不能对事件进行调用
    • 在定义事件的类外部,添加"+="和移除"-="委托实例的访问限定符与声明事件时使用的访问符相同
      • 也就是说,如果事件声明为private或这protect, 那么定义事件的类外部也不能对事件进行"+="和"-="操作

    事件编程

    其实,上面的例子只是一个简单的演示。在很多情况下,事件使用过程中都会结合两个参数:事件源和事件参数。

    所以,在事件编程中,可以参考下面一些规范:

    • 统一以EventNameEventHandler方式命名委托变量:EventName是事件的名称
    • delegate接受两个参数,参数名统一命名为sender和e:第一个参数类型是object,第二个参数是事件参数类型,以EventNameEventArgs命名,并且需继承于System.EventArgs类
    • 如果在事件中不需要传递任何数据,也需要声明两个参数:第一个参数就是默认的object sender,第二个参数可以使用系统默认的System.EventArgs类

    总结

    通过本文介绍了事件的概念以及原理,解释了通过事件如何封装委托实例,并解决委托例子中遇到的两个问题。

    同时了解了事件的使用:

    • 通过event关键字声明事件,
      • <事件修饰> event <委托类型> <事件名称>;
    • 事件的调用,由于事件本质上是委托类型,调用事件与调用委托一样,但是事件的调用只能发生在定义事件的类的内部
  • 相关阅读:
    eclipse配置spring4.0环境详细教程
    PostgreSQL精简命令:
    Unity3D项目之 Survival Shooter 记录
    Unity UGUI 实现简单拖拽功能
    Unity UGUI实现Button按钮长按状态的判断
    Unity获取文件夹下指定类型的文件数量
    Unity5 AssetBundle打包加载及服务器加载
    Unity5.X 新版AssetBundle打包控制
    Unity基于DFGUI的TreeView设计
    Unity5.X 新版AssetBundle使用方案及策略
  • 原文地址:https://www.cnblogs.com/wilber2013/p/4285033.html
Copyright © 2020-2023  润新知