zoukankan      html  css  js  c++  java
  • c#委托、事件是怎么回事

    一直弄不明白这些东西。直到看到这个博客:http://www.cnblogs.com/guodapeng/archive/2010/07/08/1773307.html

    1.什么时候用委托

    Task类里面,Search和ComplexSearch下都有“根据不同平台来进行不同动作”的行为。现在的代码是这样:

    TaskComplexSearch(string taskPlatform)
    {
    if taskPlatform =="aaa"
    {...}
    if taskPlatform=="bob"
    {...}
    }

    这样就需要用到很多if else语句,或者switch case语句。

    用委托就可以避免这种情况。委托就是把方法当做一种变量,定义之后可以当做参数传递。之后凡是和这个委托有相同类型和参数的方法都可以进行传递。

    就像Do(string AAA)之后所有满足string类型的都可以传递到这个函数里。委托只是又多了一个条件:还有一个参数的类型也需要相同。

    声明一个委托,delegate void SearchDifferentPlatform(string keyword)

    同时有针对不同平台写的方法:

    public void SearchAAA(string aaa)

    public void SearchBBB(string bbb)

    ...

    【注意上面这两个方法,和委托一样都是void类型,和委托一样都有一个string的参数。因此是可以当做参数传递的。】

    之前的代码写成委托的形式是这样:

    TaskComplexSearch(string keyword, SearchDifferentPlatform searchDiffPlatform)
    {
    searchDiffPlatform(keyword);
    }

     对这个函数的调用就是这样:

    TaskComplexSearch("找一下aaa",SearchAAA);
    TaskComplexSearch("找一下bbb", SearchBBB);

    委托看上去和变量是一样的,但是有一些不同点:除了赋值外,委托还可以用绑定的方式来调用。就是这两个东西:

    +=

    -=

    如果有多次的绑定,在调用的时候将依次进行:

    Delegate 1 +=xxxx;

    Delegate 2+=yyyy;

    //调用一次

    //输出:依次调用两次的结果

    2.什么时候用方法

    专业一点的代码结构是,委托的定义和调用应该分开在两个类里面。类似MFC的构架。因此工程中代码应该是这样:

    //委托所在的类
    public class myDelegate()
    {
    TaskComplexSearch(string keyword, SearchDifferentPlatform searchDiffPlatform)
    {
    searchDiffPlatform(keyword);
    }
    }
    
    //调用函数所在的类
    public class DoSomething()
    {
    public void SearchAAA(string xxx)...
    public void SearchBBB(string yyy)...
    }

    调用的时候就是在主函数里实例化一个委托类:

    myDelegate D1=new myDelegate();

    D1.TaskComplexSearch("aaa",SearchAAA);

    上面的例子,如果想看上去专业一点,不应该有第二个参数(就是SearchAAA)。该怎么做到呢?这里通过一系列的演变来说明:

    这是委托最初的样子:(委托是委托,类是类)

     //定义委托,它定义了可以代表的方法的类型
        public delegate void GreetingDelegate(string name);
        
        //新建的GreetingManager类
        public class GreetingManager{
           public void GreetPeople(string name, GreetingDelegate MakeGreeting) {
               MakeGreeting(name);
           }
        }

    然后委托进入类里进行了封装:

    public class GreetingManager{
        //在GreetingManager类的内部声明delegate1变量
        public GreetingDelegate delegate1;  
    
        public void GreetPeople(string name, GreetingDelegate MakeGreeting) {
           MakeGreeting(name);
        }
    }

    我们不想在调用时还包含第二个参数,因此把GreetPeople方法写成:

    public class GreetingManager{
        //在GreetingManager类的内部声明delegate1变量
        public GreetingDelegate delegate1;  
    
        public void GreetPeople(string name) {
            if(delegate1!=null){     //如果有方法注册委托变量
              delegate1(name);      //通过委托调用方法
           }
        }
    }

    这样就将一个委托封装进了方法里面,而且调用时只需要一个参数即可。

    将这个概念用到工程里,就是

    public class TaskComplexSearch{
    public TaskComplexSearchDelegate delegate;
    public void GoTaskSearch(string keyword)  {
    if(delegate !=null) {
    delegate (keyword);
    }
    }
    }

    调用的时候:实例化一个TaskComplexSearch类A,然后

    A.GoTaskSearch(keyword);

    就行了。

    可是问题是,这个delegate不能用public来修饰。否则会让整个类的封装性能大打折扣。外部风暴可以随意入侵,nonono。

    但是delegate用private修饰的话,外部又没法调用了。那咋办嘞?

    原博是这句话:“现在我们想想,如果delegate1不是一个委托类型,而是一个string类型,你会怎么做?答案是使用属性对字段进行封装

    其实就算换成值类型我也不知道该怎么办。而且我也不懂“使用属性对字段进行封装”是个啥。

    后来想想,应该就是get set之类,通过属性来调用一个private的类的内部东东。

    换成委托,我们改用event这个东西。概念和上面那个get set是一样的。

    public class GreetingManager{
        //这一次我们在这里声明一个事件
        public event GreetingDelegate MakeGreet;

        public void GreetPeople(string name) {
            MakeGreet(name);
        }
    }

    这样修改之后,event虽然定义为一个public,但编译仍然为private。在调用的时候有可能会出现各种情况,这里先留下,在后面修改工程代码之后再总结。

    3.Observer设计模式

    这是个新的名词,至少对我来说是的。Observer设计模式大概是这样:

    (1)有一个监视对象

    (2)有一个监视器

    Observer是一种松耦合的模式,在监视对象状态更新的时候,监视器可以发现,并进行相应的动作。

    “Observer设计模式是为了定义对象间的一种一对多的依赖关系,以便于当一个对象的状态改变时,其他依赖于它的对象会被自动告知并更新。Observer模式是一种松耦合的设计模式。”

    比如热水器这个例子:烧开,报警,显示温度:

    namespace Delegate {
        class Heater {
        private int temperature; // 水温
        // 烧水
        public void BoilWater() {
            for (int i = 0; i <= 100; i++) {
               temperature = i;
    
               if (temperature > 95) {
                   MakeAlert(temperature);
                   ShowMsg(temperature);
                }
            }
        }
    
        // 发出语音警报
        private void MakeAlert(int param) {
           Console.WriteLine("Alarm:嘀嘀嘀,水已经 {0} 度了:" , param);
        }
        
        // 显示水温
        private void ShowMsg(int param) {
           Console.WriteLine("Display:水快开了,当前温度:{0}度。" , param);
        }
    }
    
    class Program {
        static void Main() {
           Heater ht = new Heater();
           ht.BoilWater();
        }
    }
    }

    这种代码就跟我的似的,乱糟糟。根据Observer设计模式,把警报和显示器分离出来,对热水器的温度进行监控:

    using System;
    using System.Collections.Generic;
    using System.Text;
    
    namespace Delegate {
        // 热水器
        public class Heater {
           private int temperature;
           public delegate void BoilHandler(int param);   //声明委托
           public event BoilHandler BoilEvent;        //声明事件
    
           // 烧水
           public void BoilWater() {
               for (int i = 0; i <= 100; i++) {
                  temperature = i;
    
                  if (temperature > 95) {
                      if (BoilEvent != null) { //如果有对象注册
                          BoilEvent(temperature);  //调用所有注册对象的方法
                      }
                  }
               }
           }
        }
    
        // 警报器
        public class Alarm {
           public void MakeAlert(int param) {
               Console.WriteLine("Alarm:嘀嘀嘀,水已经 {0} 度了:", param);
           }
        }
    
        // 显示器
        public class Display {
           public static void ShowMsg(int param) { //静态方法
               Console.WriteLine("Display:水快烧开了,当前温度:{0}度。", param);
           }
        }
        
        class Program {
           static void Main() {
               Heater heater = new Heater();
               Alarm alarm = new Alarm();
    
               heater.BoilEvent += alarm.MakeAlert;    //注册方法
               heater.BoilEvent += (new Alarm()).MakeAlert;   //给匿名对象注册方法
               heater.BoilEvent += Display.ShowMsg;       //注册静态方法
    
               heater.BoilWater();   //烧水,会自动调用注册过对象的方法
           }
        }
    }
    输出为:
    Alarm:嘀嘀嘀,水已经 96 度了:
    Alarm:嘀嘀嘀,水已经 96 度了:
    Display:水快烧开了,当前温度:96度。
    // 省略...

    上面这种不符合.NET的委托和事件命名规范。

     .Net Framework的编码规范: 

    • 委托类型的名称都应该以EventHandler结束。
    • 委托的原型定义:有一个void返回值,并接受两个输入参数:一个Object 类型,一个 EventArgs类型(或继承自EventArgs)。
    • 事件的命名为 委托去掉 EventHandler之后剩余的部分。
    • 继承自EventArgs的类型应该以EventArgs结尾。

    再做一下说明: 

    1. 委托声明原型中的Object类型的参数代表了Subject,也就是监视对象,在本例中是 Heater(热水器)。回调函数(比如Alarm的MakeAlert)可以通过它访问触发事件的对象(Heater)。
    2. EventArgs 对象包含了Observer所感兴趣的数据,在本例中是temperature。 

    上面这些其实不仅仅是为了编码规范而已,这样也使得程序有更大的灵活性。比如说,如果我们不光想获得热水器的温度,还想在Observer端(警报器或者显示器)方法中获得它的生产日期、型号、价格,那么委托和方法的声明都会变得很麻烦,而如果我们将热水器的引用传给警报器的方法,就可以在方法中直接访问热水器了。

     上面的代码按照.NET的规范,可以写为:

    using System;
    using System.Collections.Generic;
    using System.Text;
    
    namespace Delegate {
        // 热水器
        public class Heater {
           private int temperature;
           public string type = "RealFire 001";       // 添加型号作为演示
           public string area = "China Xian";         // 添加产地作为演示
           //声明委托
           public delegate void BoiledEventHandler(Object sender, BoiledEventArgs e);
           public event BoiledEventHandler Boiled; //声明事件
    
           // 定义BoiledEventArgs类,传递给Observer所感兴趣的信息
           public class BoiledEventArgs : EventArgs {
               public readonly int temperature;
               public BoiledEventArgs(int temperature) {
                  this.temperature = temperature;
               }
           }
    
           // 可以供继承自 Heater 的类重写,以便继承类拒绝其他对象对它的监视
           protected virtual void OnBoiled(BoiledEventArgs e) {
               if (Boiled != null) { // 如果有对象注册
                  Boiled(this, e);  // 调用所有注册对象的方法
               }
           }
           
           // 烧水。
           public void BoilWater() {
               for (int i = 0; i <= 100; i++) {
                  temperature = i;
                  if (temperature > 95) {
                      //建立BoiledEventArgs 对象。
                      BoiledEventArgs e = new BoiledEventArgs(temperature);
                      OnBoiled(e);  // 调用 OnBolied方法
                  }
               }
           }
        }
    
        // 警报器
        public class Alarm {
           public void MakeAlert(Object sender, Heater.BoiledEventArgs e) {
               Heater heater = (Heater)sender;     //这里是不是很熟悉呢?
               //访问 sender 中的公共字段
               Console.WriteLine("Alarm:{0} - {1}: ", heater.area, heater.type);
               Console.WriteLine("Alarm: 嘀嘀嘀,水已经 {0} 度了:", e.temperature);
               Console.WriteLine();
           }
        }
    
        // 显示器
        public class Display {
           public static void ShowMsg(Object sender, Heater.BoiledEventArgs e) {   //静态方法
               Heater heater = (Heater)sender;
               Console.WriteLine("Display:{0} - {1}: ", heater.area, heater.type);
               Console.WriteLine("Display:水快烧开了,当前温度:{0}度。", e.temperature);
               Console.WriteLine();
           }
        }
    
        class Program {
           static void Main() {
               Heater heater = new Heater();
               Alarm alarm = new Alarm();
    
               heater.Boiled += alarm.MakeAlert;   //注册方法
               heater.Boiled += (new Alarm()).MakeAlert;      //给匿名对象注册方法
               heater.Boiled += new Heater.BoiledEventHandler(alarm.MakeAlert);    //也可以这么注册
               heater.Boiled += Display.ShowMsg;       //注册静态方法
    
               heater.BoilWater();   //烧水,会自动调用注册过对象的方法
           }
        }
    }
    
    输出为:
    Alarm:China Xian - RealFire 001:
    Alarm: 嘀嘀嘀,水已经 96 度了:
    Alarm:China Xian - RealFire 001:
    Alarm: 嘀嘀嘀,水已经 96 度了:
    Alarm:China Xian - RealFire 001:
    Alarm: 嘀嘀嘀,水已经 96 度了:
    Display:China Xian - RealFire 001:
    Display:水快烧开了,当前温度:96度。
    // 省略 ...

    最后的这段代码我没有进行迁移,明天就考试了,等考完试迁移到工程里试一试。代码什么的,还是亲自敲一敲比较靠谱。

    最后就是希望明天考试顺利。还有如果敲代码的话不要再出现莫名其妙的错误了。f*ck。

  • 相关阅读:
    C++文件流操作与流缓冲重定向
    转减小编译时间的两种做法
    AFX_MANAGE_STATE(AfxGetStaticModuleState())
    一个游戏程序员的资料一(转)
    ACE的Doublecheckedlocking的Singleton
    Hibernate 过滤器
    悲观锁 HibernateTest.java
    HQL 语句
    HQL 查询语句
    Hibernate 中继承映射之三 每一个类一个表
  • 原文地址:https://www.cnblogs.com/dudu1103/p/5011961.html
Copyright © 2011-2022 走看看