C#详细讲解委托_事件

来源:互联网 发布:小白自学编程 编辑:程序博客网 时间:2024/06/04 19:27
1、委托》c/c++中的函数指针:函数指针》函数入口地址》函数操作
委托在c#中的不同就是面向对象,是引用类型(应用指定方法的地址),必须是先定义后实例化
1.1定义委托
delegate int someDelegate (int i,string str);//类似于抽象方法,之后方法头,没有方法体,等级与方法一级(定义时和方法同等定义)
1.2、实列化
someDelegate   sd=new someDelegate(某个方法名称(参数、返回值要与委托定义时一致));
1.3、调用
sd(5,"hongying");//调用时向里面传入参数
*、函数的签名一致,委托就是应用存储为函数的类型(就是方法作为另一个方法的参数
委托定义:实际上委托就是一个,它定义方法的类型、返回值,使得可以将方法作为另一个方法的参数来进行传值,这种将方法动态的赋给参数的做法。
委托优点:1、可以避免在程序中大量使用if——else(switch)语句,来调用一个方法
                  2、同时使得程序具有更好地可扩展性
                  3、一个委托可同时绑定多个方法,当调用这个委托的时候,依次执行响应的方法
2、为什么使用委托:
2.1、我们来做一个例子,我用一个方法调用另一个方法,向某人问好,并且传入姓名参数。
        static void GreatPeoson(string name)//向某人问好,传递某人的名字
        {
            EnglishSay(name);//方法中调用另一方法,并且传入一个参数
        }
        static void EnglishSay(string name)//用英语向某人问好,传递某人的名字
        {
            Console.WriteLine("Good Morning  "+name);
        }
        static void Main(string[] args)
        {
            GreatPeoson("张勇宽");
            Console.ReadLine();
        }
2.1、现在我又想用中文向某人问好,当然前提我要知道使用哪一个语言去问好
首先定义一个枚举类型的语言变量:
    enum Language
    { English, Chinese }

static void GreatPeoson(string name,Language lan)//向某人问好,传递某人的名字,并传入用哪一个语言问好
        {
            switch (lan)
            {
                case Language.English:
                    EnglishSay(name);
                    break;
                case Language.Chinese:
                    ChineseSay(name);
                    break;
            }
        }
        static void EnglishSay(string name)//用英语向某人问好,传递某人的名字
        {
            Console.WriteLine("Good Morning  "+name);
        }
        static void ChineseSay(string name)//使用中文向某人问好
        {
            Console.WriteLine("你好,有什么帮助吗?"+name);
        }
        static void Main(string[] args)
        {
            GreatPeoson("Marry", Language.English);
            GreatPeoson("张勇宽",Language.Chinese);
            Console.ReadLine();
        }
说到这里大家应该明白点了,如果我现在想用韩语、日语、法语、阿根廷语言对不同的人问好,那我就不停的要改枚举类型、switch(if-else)语句,这样做实在是太繁琐了,很难实现系统的扩展性
2.2、我用委托(委托一个方法调用我指定的方法)来实现
       static void EnglishSay(string name)//用英语向某人问好,传递某人的名字
        {
            Console.WriteLine("Good Morning  "+name);
        }
        static void ChineseSay(string name)//使用中文向某人问好
        {
            Console.WriteLine("你好,有什么帮助吗?"+name);
        }
        delegate void EnglishSayDelegate(string name); //定义委托
        delegate void ChineseSayDelegate(string name);//定义用中文来说委托
        static void Main(string[] args)
        {
            EnglishSayDelegate en = new EnglishSayDelegate(EnglishSay);//初始化委托
            ChineseSayDelegate chinese = new ChineseSayDelegate(ChineseSay);
            en("Marry");//调用委托指定的方法
            chinese("红鹰");
            Console.ReadLine();
        }
这样就减少了许多if——else(switch)语句,并且使程序有了一个很好地扩展性。

2.2.1、委托的另一种初始化:EnglishSayDelegate en= EnglishSay;//直接把方法给委托对象
static void EnglishSay(string name)//用英语向某人问好,传递某人的名字
        {
            Console.WriteLine("Good Morning  " + name);
        }
        static void ChineseSay(string name)//使用中文向某人问好
        {
            Console.WriteLine("你好,有什么帮助吗?"+name);
        }
        delegate void EnglishSayDelegate(string name); //定义委托
        delegate void ChineseSayDelegate(string name);//定义用中文来说委托
        static void Main(string[] args)
        {
            string name1, name2;
            name1 = "Marry";
            name2 = "红鹰";
            EnglishSayDelegate en= EnglishSay;//直接把方法给委托对象
            ChineseSayDelegate chi = ChineseSay;
            en(name1);//调用委托
            chi(name2);
            Console.ReadLine();
        }
2.3、同一个委托中也同时可以绑定多个方法(多个方法同时赋给同一个委托),调用这个委托的时候依次执行响应的方法。
       static void EnglishSay(string name)//用英语向某人问好,传递某人的名字
        {
            Console.WriteLine("Good Morning  " + name);
        }
        static void ChineseSay(string name)//使用中文向某人问好
        {
            Console.WriteLine("你好,有什么帮助吗?"+name);
        }
        static void hao(string name)
        {
            Console.WriteLine("你好<{0}",name);
        }
        delegate void EnglishSayDelegate(string name); //定义委托
        delegate void ChineseSayDelegate(string name);//定义用中文来说委托
        static void Main(string[] args)
        {
            string name1, name2;
            name1 = "Marry";
            name2 = "红鹰";
            EnglishSayDelegate en= EnglishSay;//直接把方法给委托对象
            en += ChineseSay;
            en += hao;
            ChineseSayDelegate chi = ChineseSay;
            chi += EnglishSay;
            en(name1);//调用委托
            chi(name2);
            Console.ReadLine();
        }
注意:第一个“=”是赋值语句,第二个“+=”是绑定方法,如果直接写“+=”,将出现委托对象没有赋值的错误提示信息。
2.4、既然委托可以绑定多个方法,是否也可以取消对方法的绑定。答案是一定的。使用“-=”
 static void EnglishSay(string name)//用英语向某人问好,传递某人的名字
        {
            Console.WriteLine("Good Morning  " + name);
        }
        static void ChineseSay(string name)//使用中文向某人问好
        {
            Console.WriteLine("你好,有什么帮助吗?"+name);
        }
        static void hao(string name)
        {
            Console.WriteLine("你好<{0}",name);
        }
        delegate void EnglishSayDelegate(string name); //定义委托
        delegate void ChineseSayDelegate(string name);//定义用中文来说委托
        static void Main(string[] args)
        {
            string name1, name2;
            name1 = "Marry";
            name2 = "红鹰";
            EnglishSayDelegate en= EnglishSay;//直接把方法给委托对象
            en += ChineseSay;
            en += hao;
            en -= ChineseSay;
            en -= hao;
            //en -= EnglishSay;//不可以取消所有的方法绑定,这样会导致没有实现对委托en的实列而出现编译错误。
            ChineseSayDelegate chi = ChineseSay;
            chi += EnglishSay;
            en(name1);//调用委托
            chi(name2);
            Console.ReadLine();
        }
3、事件的由来
3.1、对于上面的介绍我们对委托有了详细的理解,但是一般EnglishSay、ChineseSay方法一般都不在Program类中,而是在另一个类中,这样我们做一下简单的修改:
class SayManager 
    {
        public void EnglishSay(string name)//用英语向某人问好,传递某人的名字
        {
            Console.WriteLine("Good Morning  " + name);
        }
        public void ChineseSay(string name)//使用中文向某人问好
        {
            Console.WriteLine("你好,有什么帮助吗?" + name);
        }
        public void hao(string name)
        {
            Console.WriteLine("你好<{0}", name);
        }
    }
    class Program
    {
        delegate void EnglishSayDelegate(string name); //定义委托
        delegate void ChineseSayDelegate(string name);//定义用中文来说委托
        static void Main(string[] args)
        {
            string name1, name2;
            name1 = "Marry";
            name2 = "红鹰";
            SayManager sm = new SayManager();//初始化类对象
            EnglishSayDelegate en= sm.EnglishSay;//直接把方法给委托对象
            en +=sm.ChineseSay;
            en += sm.hao;
            en -= sm.ChineseSay;
            ChineseSayDelegate chi = sm.ChineseSay;
            chi += sm.EnglishSay;
            en(name1);//调用委托
            chi(name2);
            Console.ReadLine();
        }
做到这里我们还是感觉不怎么好,因为面向对象编程,就是对类进行封装,那我们是不是可以把委托封装到类里面去呢?答案是肯定的。看下一个例子:
    //定义全局委托
    delegate void EnglishSayDelegate(string name); //定义委托
    delegate void ChineseSayDelegate(string name);//定义用中文来说委托

    class SayManager 
    {
        public  EnglishSayDelegate en;//在类内部声明委托
        public void PersonToSay(string name)
        {
            if (en != null)
                en(name);//一般调用委托的方法样式
        }
        public void EnglishSay(string name)//用英语向某人问好,传递某人的名字
        {
            Console.WriteLine("Good Morning  " + name);
        }
        public void ChineseSay(string name)//使用中文向某人问好
        {
            Console.WriteLine("你好,有什么帮助吗?" + name);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            string name1;
            name1 = "Marry";
            SayManager sm = new SayManager();
            sm.en= sm.EnglishSay;//直接把方法给委托对象,在这里对委托赋值方法
            sm.en +=sm.ChineseSay;
            sm.PersonToSay(name1);//调用委托
            Console.ReadLine();
        }
我们看到这里,感觉委托和string类型变量没有什么区别了,就是一个变量呗,你就可以对其进行一些设置、赋值。
  1. 我们看看上面类中委托,访问修饰符public,这说明我们在外面可以对他随意的赋值等操作,严重破坏了对象的封装性,如果设置成了private,我们在外面怎么访问啊,更别说绑定事件了。
  2. 我们在对委托“=”和“+=”不是赋值就是绑定,都是对方法的调用,没有什么分别,这样感觉让人很别扭。
  3. 我们想到了string类型的private字段,我们可以用属性进行赋值、访问,对于去别不大委托类型我们是不是也可以。这就到了Event出场了。event作用:封装了委托类型的变量,事件只能通过“+=”或“-=”对事件绑定方法。
delegate void EnglishSayDelegate(string name); //定义委托
    class SayManager 
    {
        public event EnglishSayDelegate toSay;//在这里声明一个事件,与声明委托基本相同,只是增加了一个event,
        //事件的定义 访问修饰符   event  委托名   事件标示符
        public void PersonToSay(string name)
        {
            toSay(name);//调用事件
        }
        public void EnglishSay(string name)//用英语向某人问好,传递某人的名字
        {
            Console.WriteLine("Good Morning  " + name);
        }
        public void ChineseSay(string name)//使用中文向某人问好
        {
            Console.WriteLine("你好,有什么帮助吗?" + name);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            string name1;
            name1 = "Marry";
            SayManager sm = new SayManager();
            //sm.toSay = sm.EnglishSay;//编译时错误:事件“LianxiDelegate.SayManager.toSay”只能出现在 += 或 -= 的左边
            sm.toSay += sm.EnglishSay;
            sm.toSay += sm.ChineseSay;
            //sm.toSay -= sm.ChineseSay;
            sm.PersonToSay(name1);//调用委托
            Console.ReadLine();
        }
    }
4、委托、事件与Observer设计模式

范例说明

上面的例子已不足以再进行下面的讲解了,我们来看一个新的范例,因为之前已经介绍了很多的内容,所以本节的进度会稍微快一些:

假设我们有个高档的热水器,我们给它通上电,当水温超过95度的时候:1、扬声器会开始发出语音,告诉你水的温度;2、液晶屏也会改变水温的显示,来提示水已经快烧开了。

现在我们需要写个程序来模拟这个烧水的过程,我们将定义一个类来代表热水器,我们管它叫:Heater,它有代表水温的字段,叫做 temperature;当然,还有必不可少的给水加热方法BoilWater(),一个发出语音警报的方法MakeAlert(),一个显示水温的方 法,ShowMsg()。

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设计模式简介

上面的例子显然能完成我们之前描述的工作,但是却并不够好。现在假设热水器由三部分组成:热水器、警报器、显示器,它们来自于不同厂商并进行了组装。那么,应该是热水器仅仅负责烧水,它不能发出警报也不能显示水温;在水烧开时由警报器发出警报、显示器显示提示和水温。

这时候,上面的例子就应该变成这个样子:   

// 热水器
public class Heater { 
    private int temperature;
        
    // 烧水
    private void BoilWater() {
       for (int i = 0; i <= 100; i++) {
           temperature = i;
        }
    }
}

// 警报器
public class Alarm{
    private void MakeAlert(int param) {
       Console.WriteLine("Alarm:嘀嘀嘀,水已经 {0} 度了:" , param);
    }
}

// 显示器
public class Display{
    private void ShowMsg(int param) {
       Console.WriteLine("Display:水已烧开,当前温度:{0}度。" , param);
    }
}

这里就出现了一个问题:如何在水烧开的时候通知报警器和显示器?在继续进行之前,我们先了解一下Observer设计模式,Observer设计模式中主要包括如下两类对象:

  1. Subject:监视对象,它往往包含着其他对象所感兴趣的内容。在本范例中,热水器就是一个监视对象,它包含的其他对象所感兴趣的内容,就是temprature字段,当这个字段的值快到100时,会不断把数据发给监视它的对象。
  2. Observer:监视者,它监视Subject,当Subject中的某件事发生的时候,会告知Observer,而Observer则会采取相应的行动。在本范例中,Observer有警报器和显示器,它们采取的行动分别是发出警报和显示水温。

在本例中,事情发生的顺序应该是这样的:

  1. 警报器和显示器告诉热水器,它对它的温度比较感兴趣(注册)。
  2. 热水器知道后保留对警报器和显示器的引用。
  3. 热水器进行烧水这一动作,当水温超过95度时,通过对警报器和显示器的引用,自动调用警报器的MakeAlert()方法、显示器的ShowMsg()方法。

类似这样的例子是很多的,GOF对它进行了抽象,称为Observer设计模式:Observer设计模式是为了定义对象间的一种一对多的依赖关系,以便于当一个对象的状态改变时,其他依赖于它的对象会被自动告知并更新。Observer模式是一种松耦合的设计模式。

实现范例的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度。
// 省略...

5、.Net Framework中的委托与事件

尽管上面的范例很好地完成了我们想要完成的工作,但是我们不仅疑惑:为什么.Net Framework 中的事件模型和上面的不同?为什么有很多的EventArgs参数?

在回答上面的问题之前,我们先搞懂 .Net Framework的编码规范:

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

再做一下说明:

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

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

现在我们改写之前的范例,让它符合 .Net Framework 的规范:

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度。
// 省略 ...


0 0
原创粉丝点击