zoukankan      html  css  js  c++  java
  • 理解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 <委托类型> <事件名称>;
    • 事件的调用,由于事件本质上是委托类型,调用事件与调用委托一样,但是事件的调用只能发生在定义事件的类的内部
  • 相关阅读:
    POJ2778 DNA Sequence AC自动机上dp
    codeforces732F Tourist Reform 边双联通分量
    codeforces786B Legacy 线段树优化建图
    洛谷P3588 PUS 线段树优化建图
    codeforces1301D Time to Run 模拟
    codeforces1303B National Project 二分或直接计算
    codeforces1303C Perfect Keyboard 模拟或判断欧拉路
    codeforces1303D Fill The Bag 二进制应用+贪心
    python之路——使用python操作mysql数据库
    python之路——mysql索引原理
  • 原文地址:https://www.cnblogs.com/wilber2013/p/4285033.html
Copyright © 2011-2022 走看看