c# 深入理解事件2.0

Posted lxq6701

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c# 深入理解事件2.0相关的知识,希望对你有一定的参考价值。

首先先来理解下:“事件是基于委托的” 含义

 

1.事件需要委托类型来做约束,事件处理器必需与约束匹配上,才能订阅事件

2.当事件的响应者向事件的拥有者提供了能够响应这个事件的事件处理器后,需找个地方把这个事件处理器保存记录下来,这里也需用到委托.

 

温习下事件模型的五个组成部分

                   --- 事件的拥有者(event  souse,对象)

                   --- 事件成员(event,成员)

                   ---事件的响应者(event  subscriber, 对象)

                   --- 事件处理器

                   --- 事件订阅——把事件处理器与事件关联在一起,本质上是一种以委托类型为基础的“约定”

 

注意: 1.事件处理器是方法成员

           2. 挂接事件处理器的时候,可以使用委托实例,也可以直接使用方法名,这是个“语法糖”

          3. 事件处理器对事件的订阅不是随意的,匹配与否声明事件时所使用的委托类型来检测

          4.事件可以同步调用也可以异步调用

 

 

----------------------------------------------------分割线--------------------------------------------------------------

 

 

事件的声明 

 

在深入理解事件1.0中认识了事件,及其用法,接下来是如何自己声明一个事件。

 两种语法:

  • 完整声明
  • 简略声明(字段式声明,field-like

完整声明Demo

(餐馆吃饭,服务员过来让你点菜,你是事件拥有者,点菜是个事件,服务员是事件响应者,完成点菜任务是事件处理器,最后要订阅事件)

 

按五个基本成员来写,就不会逻辑混乱啦~

第一步:事件拥有者

 

   public class Customer
    

        public double Bill  get; set; 
        public void PayTheBill()
        
            Console.WriteLine("I will pay $0", this.Bill);
        
   

 

第二步:声明事件

首先需声明一个委托,其中第一个参数是事件拥有者,第二个参数是事件参数

所以在此之前应该先声明一个OrderEventArgs类(一般这种传递事件参数的都需要继承EventArgs

 

public  class OrderEventArgs:EventArgs
    
        public string DishName  get; set; 
        public string  Size  get; set; 

    

 

public  delegate void OrderEventHandler(Customer customer, OrderEventArgs e);

 

在事件拥有者类中声明一个委托字段用来引用或者存储事件处理器;声明事件方法;

 

    //用委托类型声明一个委托字段,用来引用或者存储事件处理器
        private OrderEventHandler orderEventHandler;
     
     // 声明事件 

        public event OrderEventHandler Order
        
            add
            
                this.orderEventHandler += value;
            

            remove
            
                this.orderEventHandler -= value;
            
        

 

第三步:事件的响应者

 public class Waiter


然后先回到主窗体(实例化customer、waiter的实例)

static void Main()
Customer customer
= new Customer(); Waiter waiter = new Waiter(); customer.Order += waiter.Action;//订阅 //按ctrl+.

可以看到Waiter类中为我们声明好了事件处理器

 public class Waiter
  
        public void Action(Customer customer, OrderEventArgs e)
        
        
   

 

五个成员都有了,还需要事件触发(即你需要进行点菜了)

假象你进来后如果没有服务员过来,那你就没法点菜所以要先判断一下

 if (this.orderEventHandler!=null)

 

          OrderEventArgs e = new OrderEventArgs();

          e.DishName = "Kongpao Chicken";

          e.Size = "Large";

          this.orderEventHandler.Invoke(this,e);

 

 

 

完整代码:

 

技术图片
class Program
    

        static void Main()
        
            Customer customer = new Customer();
            Waiter waiter = new Waiter();
            customer.Order += waiter.Action;

            customer.Action();
            customer.PayTheBill();

        
    
       
    public  class OrderEventArgs:EventArgs
    
        public string DishName  get; set; 
        public string  Size  get; set; 

    

    public  delegate void OrderEventHandler(Customer customer, OrderEventArgs e);

    //第一步 事件拥有者
    public class Customer
    



        //用委托类型声明一个委托字段,用来引用或者存储事件处理器
        private OrderEventHandler orderEventHandler;
        //第二步 声明事件 

        public event OrderEventHandler Order
        
            add
            
                this.orderEventHandler += value;
            

            remove
            
                this.orderEventHandler -= value;
            
        

        public double Bill  get; set; 
        public void PayTheBill()
        
            Console.WriteLine("I will pay $0", this.Bill);
        

        //触发事件 顾客进来 坐下 思考

        public void WalkIn()
        
            Console.WriteLine("Walk into the restuarant");
        

        public void SitDown()
        
            Console.WriteLine("Sit down");
        

       
        public void Think()
        
            for (int i = 0; i < 5; i++)
            
                Console.WriteLine("Let me thingking");
                Thread.Sleep(1000);
            

            //触发事件 假象你进来后如果没有服务员过来 那你就没法点菜 所以要先判断一下
            if (this.orderEventHandler!=null)
            
                OrderEventArgs e = new OrderEventArgs();
                e.DishName = "Kongpao Chicken";
                e.Size = "Large";
                this.orderEventHandler.Invoke(this,e);
            
        


        //声明一个方法加载上述行为
        public void Action()
        
            Console.ReadLine();
            this.WalkIn();
            this.SitDown();
            this.Think();
        
    

    //第三步  事件的响应者
    public class Waiter
    
        public void Action(Customer customer, OrderEventArgs e)
        
            Console.WriteLine("I will server you the dish0",e.DishName);
            double price = 10;
            switch (e.Size)
            
                case "Small":

                    price = price * 0.5;
                    break;
                case "Large":
                    price = price * 1.5;
                    break;
                default:
                    break;
            
            //传进来的参数还有个customer 谁点的菜
            customer.Bill += price;
        
    
View Code

 

 

简略声明(字段式声明,field-like----现在普遍书的教法都直接教简略声明方法

将完整声明中 (去掉声明事件的代码)

        private OrderEventHandler orderEventHandler;
       
        public event OrderEventHandler Order
        
            add
            
                this.orderEventHandler += value;
            

            remove
            
                this.orderEventHandler -= value;
            
        

 

用以下代替

public event  OrderEventHandle  Order;

换了后发现

 if (this.orderEventHandler!=null)
            
                OrderEventArgs e = new OrderEventArgs();
                e.DishName = "Kongpao Chicken";
                e.Size = "Large";
                this.orderEventHandler.Invoke(this,e);
            

会报错

改为以下即可

if (this.Order!=null)

        OrderEventArgs e = new OrderEventArgs();

         e.DishName = "Kongpao Chicken";

         e.Size = "Large";

         this.Order.Invoke(this,e);

   

 

可以看出,,简略声明没有手动声明委托字段,那么是如何来处理对事件处理器的引用的呢?

反编译看下隐藏在简化声明背后的秘密

红色框起的即为后台编码为我们准备的委托字段

技术图片

 

 

 ----------------------------------------------------分割线--------------------------------------------------------------

 

思考:有了委托字段/属性,为什么还需要事件?

  ----为了程序的逻辑更加“有道理”、更加安全,谨防“借刀杀人”

知识点:

若为委托事件

   ---事件只能出现在+=的左边或右边  即 customer.Order += waiter.Action;

以下报错:

技术图片

 

 

若为委托字段技术图片

      ---Order还能通过"."来访问

技术图片

 一个demo来说明容易出现的问题:假设来了一个顾客(badGuy)进行点菜,却把菜价格记到了其他人(customer)的账头上

 

技术图片
   static void Main()
        
            Customer customer = new Customer();
            Waiter waiter = new Waiter();
            customer.Order += waiter.Action;

            // customer.Action();


            OrderEventArgs e = new OrderEventArgs();
            e.DishName = "ManHanquanxi";
            e.Size = "large";

            OrderEventArgs e2 = new OrderEventArgs();
            e2.DishName = "ManHanquanxi";
            e2.Size = "large";

            Customer badGuy = new Customer();
            badGuy.Order += waiter.Action;
            badGuy.Order.Invoke(customer, e);
            badGuy.Order.Invoke(customer, e2);


            customer.PayTheBill();

        
    
View Code

 

联系c++函数指针问题 --经常出现指向其他函数

 

注意当为事件委托时,在customer类内部能够使用Order事件去做非空比较以及调用Order.Invoke方法纯属不得已而为之,

    因为使用事件的简化声明时,我们没有手动声明一个委托类型的字段。这是微软编译器语法糖所造成的语法冲突和前后不一致。

   

 

  ----------------------------------------------------分割线--------------------------------------------------------------

 

 总结:

    事件的本质是委托字段的一个包装器

  •           ---这个包装器对委托字段的访问起限制作用,相当于一个“蒙版”
  •           ---封装(encapsulation)的一个重要功能就是隐藏
  •           ---事件对外界隐藏了委托实例的大部分功能,仅暴露添加/移除事件处理器的功能

 

    用于声明事件的委托

           ---用于声明Foo 事件的委托,一般命名为FooEventHandler

           --- FooEventHandler委托的参数一般有两个

                     ---第一个是object类型,名字为sender,实际上就是事件的拥有者、事件的source

                     ---第二个是EventArgs类的派生类,类名一般为FooEventArgs,参数名为e,也就是前面讲过的事件参数

                      ---没有官方的说法,但我们可以把委托的参数列表看做是事件发生后发送给事件响应者的“事件消息”。

          ---  触发Foo事件的方法一般命名为OnFoo,即“因何引发”、“事出有因”

                 (事件的触发必须由事件的拥有者来做)

          ---访问级别为protected 不能为public,不然又成了可以“借刀杀人”了

 

          --- 事件的命名约定

                 ---带有时态的动词或者动词短语

                 ---事件拥有者“正在做”什么事情,用进行时;事件拥有者“做完了”什么事情,用完成时

 

最开始的源代码中customer的think方法做了两件事,违反了面向对象的单一职责原则

(Single Responsibility Principle)所以应改为:

public void Think()
        
            for (int i = 0; i < 5; i++)
            
                Console.WriteLine("Let me thingking");
                Thread.Sleep(1000);
            

            this.Onorder("Kongpao Chicken", "large");
        
        protected void Onorder(string dishName,string size)
        
            if (this.Order != null)
            
                OrderEventArgs e = new OrderEventArgs();
                e.DishName = dishName;
                e.Size = size;
                this.Order.Invoke(this, e);
            
        

 结合简略声明 完整的代码如下

技术图片
 class Program
    
        static void Main(string[] args)
        
            Customer customer = new Customer();
            Waiter waiter = new Waiter();
            customer.order += waiter.Action;

            customer.action();
            customer.Paythebill();
        
    

     public class OrderEventArgs : EventArgs
    
        public string DishName get; set; 
        public string Size  get; set; 
    


     public  delegate void OrderEventHandle(Customer customer, OrderEventArgs e);


    public class Customer
    
        public double  Bill get; set; 
        public void Paythebill()
        
            Console.WriteLine("I will pay $0",this.Bill);
        

        public event OrderEventHandle order;

        public void Walkin()
        
            Console.WriteLine("I am walking in");
        

        public void Think()
        
            for (int i = 0; i < 5; i++)
            
                Console.WriteLine("Let me thinking");
                Thread.Sleep(1000);
            

            this.Onorder("Hongshaoyu", "Small");
        

       protected  void Onorder(string dishName,string size)
        
            if (this.order != null)
            
                OrderEventArgs e = new OrderEventArgs();
                e.DishName = dishName;
                e.Size = size;
                this.order.Invoke(this, e);
            
        

        public void action()
        
            this.Walkin();
            this.Think();
        
        

    


    public class Waiter
    
        public  void Action(Customer customer, OrderEventArgs e)
        
            Console.WriteLine("I will server you the dish 0",e.DishName);
            double price = 10;
            switch (e.Size)
            

                case "Small":
                    price = price  * 0.5;
                    break;

                case "Large":
                    price = price * 1;
                    break;

                default:
                    break;
            
            customer.Bill = price;

        
        

        
    
View Code

 

  ----------------------------------------------------分割线--------------------------------------------------------------

 

 技术图片

 

 # 自律即是自由 #

以上是关于c# 深入理解事件2.0的主要内容,如果未能解决你的问题,请参考以下文章

深入理解C# 静态类与非静态类静态成员的区别

C# 深入理解类

C#编程之委托与事件四

C# 二维数组深入理解

深入理解-事件委托

#星光计划2.0# 关于final的一些细节,我有话要说——深入理解final