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。

 

posted on 2015-12-02 09:44  肚肚1103  阅读(227)  评论(0编辑  收藏  举报