admin 管理员组

文章数量: 1184232

文章目录

  • 声明自定义事件
    • 事件的声明
      • 完整声明
      • 简略声明(字段式声明,field-like)
    • 有了委托字段/属性,为什么还需要事件?
    • 所以事件的本质是委托字段的一个包装器
    • 用于声明事件的委托类型的命名约定
  • 事件的命名约定
  • 事件与委托的关系
    • 事件真的是“以特殊方式声明的委托字段/实例”吗?
    • 为什么要使用委托类型来声明事件?
    • 对比事件与属性

声明自定义事件

事件的声明

完整声明

 class Program
    {
        static void Main(string[] args)
        {
            Customer customer = new Customer();  //1.事件的拥有者

            Waiter waiter = new Waiter();   //3.事件的响应者
            customer.Order +=  waiter.Action;  //使用Action方法作为Order事件的事件处理器
          //2.事件     4. 订阅    5.事件处理器  


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


    //传递事件信息(EventArgs)的类,命名为事件名字+EventArgs
    public class OrderEventArgs:EventArgs   //如果某个类它的用途是作为EventArgs来使用的话,应该让它派生自EventArgs类,EventArgs类是微软为我们准备好的基类
    {
        public string DishName { get; set; }
        public string Size { get; set; }

    }

    //声明点菜事件 
    //事件是基于委托的有两层意识:①事件需要使用委托类型来做约束,约束即规定事件可以发送什么样的消息给事件的响应者,也规定事件的响应者能收到什么样的消息,这就决定了事件响应者的事件处理器必须能够跟这个约束匹配上,它才能订阅这个事件,
    //②当事件的响应者向事件的拥有者提供了能够匹配这个事件的事件处理器之后,你总得找个地方把这个事件处理器保存或者记录下来,能够记录或者引入方法的任务也只有委托类型的实例才能做到,
    //总结:事件这种成员无论从表层约束来讲,还是底层实现来讲,它都是依赖于委托类型的。
    
    //声明事件前先声明一个委托类型
    public delegate void OrderEventHandler(Customer customer,OrderEventArgs e); //参数列表第一个参数表明谁点的菜,第二个参数存储有关你点的菜的信息
    //      委托    void是可以引用的方法的返回值类型  委托的名字:如果委托是为了声明某个事件准备的,那么委托的名字应该使用EventHandler作为后缀  委托能够引用的方法的参数列表

    //委托名使用EventHandler后缀:它的用意有三个
    //①别人一看到这个委托有EventHandler后缀之后,就立刻知道这个委托是专门用来声明事件的,这时候人家就不会拿这个委托去干别的事情
    //②一使用EventHandler后缀,它就表明这个委托是用来约束事件处理器的
    //③表明这个委托未来创建出来的实例是专门用于存储事件处理器的




    //事件拥有者
    public class Customer
    {
        private OrderEventHandler orderEventHandler;  //使用委托类型声明一个委托类型字段,这个委托字段是用来存储或者说引用这些事件处理器

        //声明事件格式
    //希望外界能访问到用public 声明事件需要加一个关键字event来告诉编译器我现在声明的是事件 拿哪个委托类型来约束事件 事件名Order
        public event OrderEventHandler Order
        {

            //事件处理器的添加器
            add
            {
                this.orderEventHandler += value;  //+=外界传进来的EventHandler
            }

            //事件处理器的移除器
            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 restaurant");
        }
        public void SitDown()
        {
            Console.WriteLine("Sit down");
        }
        public void Think()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("Let me think...");
                Thread.Sleep(1000);
            }

            //触发事件
            if (this.orderEventHandler !=null)  //事件Order只能放在+=或者-=左边,因此!=左边放事件Order会报错
            {
                OrderEventArgs e=new OrderEventArgs();
                e.DishName="Kongpao Chicken";
                e.Size="large";
                this.orderEventHandler.Invoke(this, e);//触发事件使用的是委托点Invoke
            }

        }


        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 serve you the dish -{0}",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.Bill += price;  //累加
        }
    }

简略声明(字段式声明,field-like)

编译器为我们简化的事件声明方式准备的委托类型字段

   class Program
    {
        static void Main(string[] args)
        {
            Customer customer = new Customer();  //1.事件的拥有者

            Waiter waiter = new Waiter();   //3.事件的响应者
            customer.Order += waiter.Action;  //使用Action方法作为Order事件的事件处理器
            //2.事件     4. 订阅    5.事件处理器  


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


    //传递事件信息(EventArgs)的类,命名为事件名字+EventArgs
    public class OrderEventArgs : EventArgs   //如果某个类它的用途是作为EventArgs来使用的话,应该让它派生自EventArgs类,EventArgs类是微软为我们准备好的基类
    {
        public string DishName { get; set; }
        public string Size { get; set; }

    }

   

    //声明事件前先声明一个委托类型
    public delegate void OrderEventHandler(Customer customer, OrderEventArgs e); //参数列表第一个参数表明谁点的菜,第二个参数存储有关你点的菜的信息


    //事件拥有者
    public class Customer
    {
        //事件的简略声明
        public event OrderEventHandler Order;  //拿委托类型作为事件的约束以及存储对事件处理器的引用+事件的名字

        public double Bill { get; set; }  //记录用户在点菜的时候花了多少钱

        public void PayTheBill()    //支付账单
        {
            Console.WriteLine("I will pay ${0}", this.Bill);
        }

        //触发这个事件:一定是事件拥有者的内部逻辑触发了这个事件
        public void Walkin()
        {
            Console.WriteLine("Walk into the restaurant");
        }
        public void SitDown()
        {
            Console.WriteLine("Sit down");
        }
        public void Think()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("Let me think...");
                Thread.Sleep(1000);
            }

            //触发事件
            if (this.Order != null)    //触发事件之前,一定要先判断一下事件所封装的委托是否为空
            {
                OrderEventArgs e = new OrderEventArgs();
                e.DishName = "Kongpao Chicken";
                e.Size = "large";
                this.Order.Invoke(this,e); //使用事件的名字来触发
            }
       //提醒:在Customer类内部能够使用Order事件去做非空比较以及调用Order.Invoke方法纯属不得已而为之,因为使用事件的简化声明时,我们没有手动声明一个委托类型的字段,编译器为我们简化的事件声明方式准备了的委托类型字段。这是微软编译器语法糖所造成的语法冲突和前后不一致。

        }


        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 serve you the dish -{0}", 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.Bill += price;  //累加


        }
    }

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

事件成员它能够让程序的逻辑以及对象之间的关系更加“有道理”,更加安全,而且能够防止“借刀杀人”这种情况的发生

为了解决public委托字段在类的外部被滥用,被误用的问题,微软推出了事件这种成员

例子:使用委托类型字段出现借刀杀人情况

class Program
    {
        static void Main(string[] args)
        {
            Console.ReadLine();
            Customer customer = new Customer();  //1.事件的拥有者

            Waiter waiter = new Waiter();   //3.事件的响应者
            customer.Order += waiter.Action;  //使用Action方法作为Order事件的事件处理器
            //2.事件     4. 订阅    5.事件处理器  


        //    customer.Action();
         

            OrderEventArgs e1 = new OrderEventArgs();
            e1.DishName = "Manhanquanxi";
            e1.Size = "large";

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


            //如果没有事件成员的话,会出现“借刀杀人”的情况
            Customer badGuy = new Customer();
            badGuy.Order += waiter.Action;

            badGuy.Order.Invoke(customer, e1);
            badGuy.Order.Invoke(customer, e2);

            customer.PayTheBill();

        }
    }


    //传递事件信息(EventArgs)的类,命名为事件名字+EventArgs
    public class OrderEventArgs : EventArgs   //如果某个类它的用途是作为EventArgs来使用的话,应该让它派生自EventArgs类,EventArgs类是微软为我们准备好的基类
    {
        public string DishName { get; set; }
        public string Size { get; set; }

    }

   

    //声明事件前先声明一个委托类型
    public delegate void OrderEventHandler(Customer customer, OrderEventArgs e); //参数列表

    //事件拥有者
    public class Customer
    {
       //为了解决public委托字段在类的外部被滥用,被误用的问题,微软推出了事件这种成员
        public OrderEventHandler Order;  

        public double Bill { get; set; }  //记录用户在点菜的时候花了多少钱

        public void PayTheBill()    //支付账单
        {
            Console.WriteLine("I will pay ${0}", this.Bill);
        }

        //触发这个事件:一定是事件拥有者的内部逻辑触发了这个事件
        public void Walkin()
        {
            Console.WriteLine("Walk into the restaurant");
        }
        public void SitDown()
        {
            Console.WriteLine("Sit down");
        }
        public void Think()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("Let me think...");
                Thread.Sleep(1000);
            }

            //触发事件
            if (this.Order != null)      //触发事件之前,一定要先判断一下事件所封装的委托是否为空
            {
                OrderEventArgs e = new OrderEventArgs();
                e.DishName = "Kongpao Chicken";
                e.Size = "large";
                this.Order.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 serve you the dish -{0}", 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.Bill += price;  //累加
        }
    }

使用事件优化代码:事件只能够在+=或-=左边,你不能在事件拥有者的外部随随便便的触发事件

  class Program
    {
        static void Main(string[] args)
        {
            Console.ReadLine();
            Customer customer = new Customer();  //1.事件的拥有者

            Waiter waiter = new Waiter();   //3.事件的响应者
            customer.Order += waiter.Action; 


        //    customer.Action();
         
            OrderEventArgs e1 = new OrderEventArgs();
            e1.DishName = "Manhanquanxi";
            e1.Size = "large";

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


            //如果没有事件成员的话,会出现“借刀杀人”的情况
            Customer badGuy = new Customer();
            badGuy.Order += waiter.Action;

            badGuy.Order.Invoke(customer, e1);  //报错
            badGuy.Order.Invoke(customer, e2);  //报错

            customer.PayTheBill();

        }
    }


    //传递事件信息(EventArgs)的类,命名为事件名字+EventArgs
    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
    {
       //为了解决public委托字段在类的外部被滥用,被误用的问题,微软推出了事件这种成员
        public event OrderEventHandler Order;  

        public double Bill { get; set; }  //记录用户在点菜的时候花了多少钱

        public void PayTheBill()    //支付账单
        {
            Console.WriteLine("I will pay ${0}", this.Bill);
        }

        //触发这个事件:一定是事件拥有者的内部逻辑触发了这个事件
        public void Walkin()
        {
            Console.WriteLine("Walk into the restaurant");
        }
        public void SitDown()
        {
            Console.WriteLine("Sit down");
        }
        public void Think()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("Let me think...");
                Thread.Sleep(1000);
            }

            //触发事件
            if (this.Order != null)      //触发事件之前,一定要先判断一下事件所封装的委托是否为空
            {
                OrderEventArgs e = new OrderEventArgs();
                e.DishName = "Kongpao Chicken";
                e.Size = "large";
                this.Order.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 serve you the dish -{0}", 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.Bill += price;  //累加

        }
    }

提醒:在Customer类内部能够使用Order事件去做非空比较以及调用Order.Invoke方法纯属不得已而为之,因为使用事件的简化声明时,我们没有手动声明一个委托类型的字段。这是微软编译器语法糖所造成的语法冲突和前后不一致。

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

这个包装器对委托字段的访问起限制作用,相当于一个“蒙板”,我把大部分内容给你盖住,只让你看到很少部分你有必要看到的内容。

事件只让你访问+=,-=两个操作就可以了,让你足够在里面去添加事件处理器,或者移除不必要的事件处理器就够了,你呢想调用我别的方法不行,限制了外界对我里边委托字段的访问,让它更加安全,让整个程序更加好维护。限制作用让我体会到了面向对象编程中封装的概念

封装(encapsulation)的一个重要功能就是隐藏,我把一些东西封起来,从外界你看不到,同时我还可以向外暴露一些想让你看到的东西,这就是封装的重要作用。

事件对外界隐藏了委托实例的大部分功能,仅暴露添加/移除事件处理器的功能

添加/移除事件处理器的时候可以直接使用方法名,这是委托实例所不具备的功能

用于声明事件的委托类型的命名约定

1.用于声明Foo事件的委托,一般命名为FooEventHandler(除非是一个非常通用的事件约束)

例子:相比最开始的代码,省去委托类型的声明,使用微软已经为我们准备好的一个通用的,专门去用于声明事件的委托类型叫做EventHandler委托

  class Program
    {
        static void Main(string[] args)
        {
            Customer customer = new Customer();  //1.事件的拥有者
            Waiter waiter = new Waiter();   //3.事件的响应者
            customer.Order += waiter.Action;
            customer.Action();
            customer.PayTheBill();

        }
    }


    //传递事件信息(EventArgs)的类,命名为事件名字+EventArgs
    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
    {
        //为了解决public委托字段在类的外部被滥用,被误用的问题,微软推出了事件这种成员
        public event EventHandler  Order;

        public double Bill { get; set; }  //记录用户在点菜的时候花了多少钱

        public void PayTheBill()    //支付账单
        {
            Console.WriteLine("I will pay ${0}", this.Bill);
        }

        //触发这个事件:一定是事件拥有者的内部逻辑触发了这个事件
        public void Walkin()
        {
            Console.WriteLine("Walk into the restaurant");
        }
        public void SitDown()
        {
            Console.WriteLine("Sit down");
        }
        public void Think()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("Let me think...");
                Thread.Sleep(1000);
            }

            //触发事件
            if (this.Order != null)     //触发事件之前,一定要先判断一下事件所封装的委托是否为空
            {
                OrderEventArgs e = new OrderEventArgs();
                e.DishName = "Kongpao Chicken";
                e.Size = "large";
                this.Order.Invoke(this, e); //使用事件的名字来触发
            }


        }


        public void Action()
        {
            Console.ReadLine();  //敲一个回车后会执行后面的操作
            this.Walkin();
            this.SitDown();
            this.Think();
        }
    }

    //事件的响应者
    public class Waiter
    {
        //事件处理器
        public void Action(object sender, EventArgs e)
        {
            Customer customer = sender as Customer;
            OrderEventArgs orderInfo = e as OrderEventArgs;

            Console.WriteLine("I will serve you the dish -{0}", orderInfo.DishName);
            double price = 10;
            switch (orderInfo.Size)
            {
                case "small":
                    price = price * 0.5;
                    break;

                case "large":
                    price = price * 1.5;
                    break;

                default:
                    break;
            }

            customer.Bill += price;  //累加

        }
    }

2.FooEventHandler委托的参数一般有两个(由Win32API演化而来,历史悠久)

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

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

③虽然没有官方说法,但我们可以把委托的参数列表看作是事件发生后发送给事件响应者的"事件消息"

3.触发Foo事件的方法一般命名为OnFoo,即“因何而发”,“事出有因”

​ 访问级别为protected,不能为public ,如果这个方法为public,在外界别人就又可以调用这个方法了,一调用这个方法,里边的事件又会被触发,又成了“借刀杀人”了

注意:事件的触发必须由事件的拥有者自己来做,这就是微软为什么准备事件这种包装器

   class Program
    {
        static void Main(string[] args)
        {
            Customer customer = new Customer();  //1.事件的拥有者
            Waiter waiter = new Waiter();   //3.事件的响应者
            customer.Order += waiter.Action;
            customer.Action();
            customer.PayTheBill();

        }
    }


    //传递事件信息(EventArgs)的类,命名为事件名字+EventArgs
    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
    {
        //为了解决public委托字段在类的外部被滥用,被误用的问题,微软推出了事件这种成员
        public event EventHandler  Order;

        public double Bill { get; set; }  //记录用户在点菜的时候花了多少钱

        public void PayTheBill()    //支付账单
        {
            Console.WriteLine("I will pay ${0}", this.Bill);
        }

        //触发这个事件:一定是事件拥有者的内部逻辑触发了这个事件
        public void Walkin()
        {
            Console.WriteLine("Walk into the restaurant");
        }
        public void SitDown()
        {
            Console.WriteLine("Sit down");
        }
        public void Think()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("Let me think...");
                Thread.Sleep(1000);
            }

            this.OnOrder("Kongpao Chicken","large");
        }

        protected void OnOrder(string disName,string size)
        {
            //触发事件
            if (this.Order != null)   //触发事件之前,一定要先判断一下事件所封装的委托是否为空
            {
                OrderEventArgs e = new OrderEventArgs();
                e.DishName = disName;
                e.Size = size;
                this.Order.Invoke(this, e); //使用事件的名字来触发
            }
        }


        public void Action()
        {
            Console.ReadLine();  //敲一个回车后会执行后面的操作
            this.Walkin();
            this.SitDown();
            this.Think();
        }
    }

    //事件的响应者
    public class Waiter
    {
        //事件处理器
        public void Action(object sender, EventArgs e)
        {
            Customer customer = sender as Customer;
            OrderEventArgs orderInfo = e as OrderEventArgs;

            Console.WriteLine("I will serve you the dish -{0}", orderInfo.DishName);
            double price = 10;
            switch (orderInfo.Size)
            {
                case "small":
                    price = price * 0.5;
                    break;

                case "large":
                    price = price * 1.5;
                    break;

                default:
                    break;
            }

            customer.Bill += price;  //累加

        }
    }

事件的命名约定

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

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

事件与委托的关系

事件真的是“以特殊方式声明的委托字段/实例”吗?

不是!只是声明的时候“看起来像”(对比委托字段与事件的简化声明,field-like)

事件声明的时候使用了委托类型,简化声明造成事件看上去像一个委托的字段(实例),而event关键字则更像是一个修饰符——这就是错觉的来源之一

订阅事件的时候+=操作符后面可以是一个委托实例,这与委托实例的赋值方法语法相同,这也让事件看起来像是一个委托字段——这是错觉的又一来源

重申:事件的本质是加装在委托字段上的一个“蒙板”(mask),是个起掩蔽作用的包装器。这个用于阻挡非法操作的“蒙板”绝不是委托字段本身。

为什么要使用委托类型来声明事件?

面试:为什么事件是基于委托的?

站在source的角度看,是为了表明source能对外传递哪些消息

站在subscriber的角度来看,它是一种约定,是为了约束能够使用什么样签名的方法来处理(响应)事件

委托类型的实例将用于存储(引用)事件处理器

对比事件与属性

属性不是字段——很多时候属性是字段的包装器,这个包装器用来保护字段不被滥用

事件不是委托字段——它是委托字段的包装器,这个包装器用来保护委托字段不被外界随便调用

包装器永远不可能是被包装的那个东西本身

本文标签: 详解 事件