刘鑫-15电子 发表于 2017-9-28 23:10:47

c#委托与事件

本帖最后由 刘鑫-15电子 于 2017-9-29 18:16 编辑

这两天入手C#,发现面向对象编程真的跟C语言有很大的差别,不仅仅是语法上面的,更多的体现在思想上面。世间万物皆对象的概念我有点迷糊,于是,我拉闸了。。。。。
委托和事件是我没能在第一时间搞懂的,看了好多遍,于是我决定开一个帖子,边写边学,加深理解,这个帖子的例子非本人原创,但是我加入了自己的理解。。。。。。。
要了解事件,必须要把委托的概念弄懂。
首先委托,关键字deltgate,
委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性。
这句话怎么理解呢,我将抽象一个我们生活常见例子,烧开水这个例子来进行说明委托,并且解释这句话。
我们定义一个类Heater 代表了烧水壶,当水烧开之后呢,我们可能会有声音提示和显示提示。
    class Heater
    {
      public int temperature;
      //烧水
      public void BoilWater()
      {
            for (int i = 0; i <= 100; i++)
                temperature++;
            if (temperature > 95)
            {
                //这里添加我们水烧开的一些操作
            }
      }
      //警报
      public void Alarm(int param)
      {
            WriteLine("滴滴滴。。水已经{0}度",param);
      }
      //显示
      public void Display(int param)
      {
            WriteLine("水烧开了。。温度是{0}", param);
      }
    }
在C语言里面,我们一般会怎么做呢?我们会在if(temperature>95)代码里去调用警报函数和显示函数。
但是假如我们要选择只需要警报的时候,又不得不去修改代码。这不符合我们封装的思想。
所以我们需要一种方式可以让我们动态的去调用函数(方法),这个时候提出了我们委托的概念。
委托就是可以以参数的形式,让第三方或者说外部可以决定我们在开水烧开的时候进行选择性操作
接下来我来演示一下怎么做这个事情。
namespace Event_Delegate
{
    //定义一个委托,这个委托的参数和我们要调用的方法的参数类型要保持一致
    public delegate void OperateDelegate(int param);

    class Heater
    {

      //烧水
      public void BoilWater(OperateDelegate Operation)
      {
            int temperature =0;
            for (int i = 0; i <= 100; i++)
                temperature=i;
            if (temperature > 95)
            {
                if(Operation!=null)
                Operation(temperature); //执行烧开后的操作
            }
      }
      //警报
      public void Alarm(int param)
      {
            WriteLine("滴滴滴。。水已经{0}度",param);
      }
      //显示
      public void Display(int param)
      {
            WriteLine("水烧开了。。温度是{0}", param);
      }
      


    }
    class Program
    {

      static void Main(string[] args)
      {
            Heater heater = new Heater();
            OperateDelegate operadelegate;
            operadelegate = heater.Display;//将方法绑定到委托
            operadelegate += heater.Alarm;
            
            heater.BoilWater(operadelegate);
            ReadKey();
      }
    }
}
我们来看执行结果:水烧开了。。温度是100
滴滴滴。。水已经100度


我这里的代码利用了委托,干了一件什么事呢?实际上我首先定义了一个委托
我们来看一下委托的定义
public delegate void OperateDelegate(int param);
委托的定义类似于一个方法的定义,这个委托的参数类型和Alarm方法与Display方法参数类型一致,只是在前面加入了delegate。但是实际上,我觉得他其实是定义了一个方法的类型,就像是string类型。
我们来看这句代码public void BoilWater(OperateDelegate Operation)这里我们发现,我们在这里定义了一个以OperateDelegate 为类型的一个变量,是不是就其实是把OperateDelegate当做了一个类型。
然后Operation就是此类型的变量。
接下来我觉得就可以解释我在帖子最开头说的那句红色字体的话了。
但还不够,于是我们来继续看我们的调用,
      static void Main(string[] args)
      {
            Heater heater = new Heater();
            OperateDelegate operadelegate;//定义一个委托类型的变量
            operadelegate = heater.Display;//将方法绑定到委托
            operadelegate += heater.Alarm;
            
            heater.BoilWater(operadelegate);
            ReadKey();
      }

在这里我们先进行一个Heater类的实例化。
然后又定义了一个我们委托类型的变量
重点来了,重点来了,我们将我们的方法赋值给了我们这个变量,看到没,这就是所说的动态的将方法看做一个参数,进行动态的调用,避免了大量if else的使用,在这里,我们想调用哪个方法就调用哪个方法。
并且,我们还可以一直添加我们的参数列表。将多个方法赋值给我们的参数。一起调用。但是注意,在进行方法注册的时候,要去掉我们的括号。

那么,什么又叫做事件呢?要讲解事件我觉得可以重新回头看我们刚刚的代码。我们在命名空间里定义了我们的委托,并在主函数时定义了一个以委托为类型的变量operadelegate用于传递我们对方法的调用,大家想这样是不是很麻烦呢?
既然C#是一门面向对象的语言,注重封装性,那我们能不能将委托作为Heater class 的一部分呢?可不可以将委托与operadelegate都封装进我们的类里面呢?
答案是肯定的,这个方法就是事件的应用。
我对事件的理解就是,以委托为类型定义的一个变量,或者这么说吧,委托是一个类,那么事件就是这个类实例化的一个对象,所以在这里提一点,事件是建立在委托的基础上的。
我们可以这么做。
namespace Event_Delegate
{


    class Heater
    {

      //定义一个委托,这个委托的参数和我们要调用的方法的参数类型要保持一致
      public delegate void OperateDelegate(int param);
      //
      public event OperateDelegate Operation;   //定义了一个事件
      //烧水
      public void BoilWater()
      {
            int temperature =0;
            for (int i = 0; i <= 100; i++)
                temperature=i;
            if (temperature > 95)
            {
                if(Operation!=null)
                Operation(temperature); //执行烧开后的操作
            }
      }
      //警报
      public void Alarm(int param)
      {
            WriteLine("滴滴滴。。水已经{0}度",param);
      }
      //显示
      public void Display(int param)
      {
            WriteLine("水烧开了。。温度是{0}", param);
      }
      


    }
    class Program
    {

      static void Main(string[] args)
      {
            Heater heater = new Heater();
            heater.Operation += heater.Alarm;//注册方法
            heater.Operation += heater.Display;
            heater.BoilWater();
            ReadKey();
      }
    }
}


我们只需要在主函数中对Operation参数进行一个初始化就好了。
对于事件的赋值,我们必须采用 += 这样的方式。
否者会报错。
那么这就是最基本的委托与事件的概念了,但是实际上,这样的使用是不怎么严谨,也不太符合C#语言对事件应用的规范的。


这段话是我在网上看到的,我觉得总结的很好:
.Net Framework的编码规范:
[*]委托类型的名称都应该以EventHandler结束。
[*]委托的原型定义:有一个void返回值,并接受两个输入参数:一个Object 类型,一个 EventArgs类型(或继承自EventArgs)。
[*]事件的命名为 委托去掉 EventHandler之后剩余的部分。
[*]继承自EventArgs的类型应该以EventArgs结尾。
再做一下说明:
[*]委托声明原型中的Object类型的参数代表了Subject,也就是监视对象,在本例中是 Heater(热水器)。回调函数(比如Alarm的MakeAlert)可以通过它访问触发事件的对象(Heater)。
[*]EventArgs 对象包含了Observer所感兴趣的数据,在本例中是temperature。








按照这种思想,我们来将我们的代码修改一下。


namespace Event_Delegate
{


    class Heater
    {
      private int temperature;
      public string producers = "红日集团";

      //定义一个委托,这个委托的参数和我们要调用的方法的参数类型要保持一致
      public delegate void BoiledEventHandler(Object sender, BoiledEventArgs e); //
      //定义了一个事件
      public event BoiledEventHandler Boiled;

      //定义一个BoiledEventArgs继承自EventArgs
      public class BoiledEventArgs : EventArgs
      {
            public readonly int temperature;         //定义为只读变量
            public BoiledEventArgs(int temperature) //结构函数
            {
                this.temperature = temperature;//
            }
      }
      //烧水
      public void BoilWater()
      {
            for (int i = 0; i <= 100; i++)
                temperature = i;
            if (temperature > 95)
            {
                BoiledEventArgs e = new BoiledEventArgs(temperature);//实例化BoiledEventArgs,将所感兴趣的信息传给方法
                OnBiled(e);
            }
      }
      //调用注册方法
      public void OnBiled(BoiledEventArgs e)
      {
            //这里带入了两个参数,一个是我们对象本身,第二是我们的一个对象,这个对象包含了我们需要被观察的信息,这里就是Temperature
            if (Boiled != null)
            {
                Boiled(this, e);
            }
      }
    }


    class Alarm
    {
      //警报
      public void MakeAlarm(Object sender, Heater.BoiledEventArgs e)
      {
            Heater heater = (Heater)sender;   //转换成Heater类
            WriteLine("滴滴滴。。水已经{0}度", e.temperature);
            WriteLine(heater.producers);   //
      }
    }


    class Display
    {
      //显示
      public void MakeDisplay(Object sender, Heater.BoiledEventArgs e)
      {
            WriteLine("水烧开了。。温度是{0}", e.temperature);
      }
    }



    class Program
    {

      static void Main(string[] args)
      {
            Alarm alarm = new Alarm();
            Display display = new Display();
            Heater heater = new Heater();
            heater.Boiled += alarm.MakeAlarm;
            heater.Boiled += display.MakeDisplay;
            heater.BoilWater();
            ReadKey();
      }
    }

}

我们严格按照C#的编程规范来,至于具体这么做有什么好处,我还没研究透   之后再续贴。。。。。   

李维强-15级 发表于 2017-10-5 21:21:28

本帖最后由 李维强-15级 于 2017-10-5 21:23 编辑

就是函数指针 一个意思(片面理解),c#里面,除了unsafe的模式,里面都是不能用指针的,但是有些操作需要指针来完成,所以微软把这些操作封装成这些形式,供用户调用
delegate 我一般用在list.find里面

李维强-15级 发表于 2018-8-28 03:52:06

这里也讲了 委托与事件
https://blog.csdn.net/chopper7278/article/details/3145000
页: [1]
查看完整版本: c#委托与事件