8.C#知识点:委托和事件
知识点目录==========>传送门
首先推荐两篇大牛写的委托和事件的博客,写的超级好!看了就包你看会,想学习的朋友直接看这两篇就足以,我自己写的是算是自己学习的纪录。
传送门==========》C# 中的委托和事件
C# 中的委托和事件续。
委托是什么?
委托是一个类,它定义了一种的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性。
--摘自百度百科。
说白了委托和我们平常常见的类是差不多的东西。它也是一个类型,一个对象。委托定义类似定义一种方法模板。满足于这个模板的任何方法都可以赋值于委托。并且将这个委托当参数进行传递,进而把方法当参数传递。
public delegate void deTest(string name);
这就定义个没有返回值的委托。定义委托需要关键字 delegate,这个关键字和我们定义的class关键字是一样的,记住是这样定义的就好了,下面部分就和定义方法的声明是一样的。去掉delegate关键,就是和定义方法一模一样。
只要满足没有返回值,而且参数是string类型的参数的方法都满足于这个委托。都可以赋值绑定给这个委托。还是上面那句话.定义委托就等于声明了一个方法的模板。
接下来就演示下委托如何使用
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace 委托和事件 { public delegate void deTest(string name); public class Test { public void SayHello(string name, deTest test) { test(name); } } }
首先看委托是定义在类外面的,说明定义类的地方都可以定义委托,委托和类是平级关系的。
Test类里面有个SayHello方法。这个方法有两个参数。一个就是string类型的参数,一个是委托类型的参数。委托类型的参数是什么意思呢?意思就是将满足委托模板的方法,将方法当作参数传递。因为没有委托方法是无非当参数传递的,最后SayHello方法里面调用这个委托。因为这个委托参数本身也是方法。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace 委托和事件 { class Program { static void Main(string[] args) { Test t1 = new Test(); deTest detest = SayHelloByEngilsh; t1.SayHello("小明",detest); } public static void SayHelloByEngilsh(string name) { Console.WriteLine(string.Format("Hello{0}", name)); } } }
首先我们有一个SayHelloByEnglish的方法。这个方法正好满足我们这个委托模板,无返回值,有一个string类型的参数。Main函数里将这个方法赋值于我们定义的委托。然后将这个委托传递到我们test的SayHello方法里面。
这个例子并不适合使用委托,不是一个好的使用委托的场景,在这里面写主要是为了大家了解委托如何创建使用。现在我们在重新梳理一下。首先我们定义了一个Test类,在类里面有个SayHello方法。这个方法有两个参数对吧。
一个是string类型,一个是委托类型。方法面执行这个委托。因为委托本身就是绑定的方法,值就是方法,所以可以让方法直接调用。接下来是main函数里面真正的调用。将一个满足委托的方法赋值于委托,然后传递给了这个方法。最后方法内部执行的委托的时候,其实是执行了我们绑定的方法。也就是SayHelloTest。一个委托创建使用的demo就结束了。
总结下委托
委托赋值语发是+
绑定语法是+=
解绑语法是-=
下面来演示下委托的应用场景,现在我们有个需求,有一个热水器,热水器连接着显示器,和报警器。热水器温度达到80°的时候,显示器就会显示提示,报警器就会报警。先用正常方法实现下。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace 委托和事件 { public class WaterHeater { int temperature = 0; public void BoilWater() { for (int i = 0; i < 80; i++) { temperature++; } //报警器报警 //显示器报警 } } }
这里就定义个热水器类,这个类就是一个烧水的类。类里面温度一直再加,到了80就开始报警了。报警的操作占时没写。下面就开定义报警类。和显示器类。
public class Monitor { public void ShowMessage(int temperature) { Console.WriteLine(string.Format("热水器在的温度已经{0}°啦,赶紧去洗澡吧", temperature)); } } public class Alarm { public void Police(int temperature) { Console.WriteLine(string.Format("热水器在的温度已经{0}°啦,赶紧去洗澡吧", temperature)); } }
这个就是显示器类和烧水类。两个类的东西一样,只有一个简单的报警方法。接下来就是将热水器到了80°之后的报警操作补全。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace 委托和事件 { public class WaterHeater { int temperature = 0; public void BoilWater() { for (int i = 0; i < 80; i++) { temperature++; } //报警器报警 Monitor monitor = new Monitor(); Alarm alarm = new Alarm(); monitor.ShowMessage(temperature); alarm.Police(temperature);//显示器报警
} }
}
这地方就是new了报警器和显示器的对象。然后调用两个对象的报警方法。功能已经实现了。但是这个地方有问题。写的很low,看到两个new我们就应该知道这个地方又是强耦合。不利于需求变动。万一哪天我们新加了一个别的报警装置。我还们还要修改这个热水器烧水的方法。设计的很不好。大家想想这个地方无非是要在温度到了80°之后执行显示器和报警器里面的报警方法。我们刚刚讲过的委托就是可以和方法绑定。那这个地方我们可不可以让热水器暴露一个委托变量,热水器温度达到80°之后。调用这个委托变量。委托的绑定放在外面。这样的话。就将高耦合变成了没有耦合,或者说是低耦合。如果没有明白的话。看一手代码。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace 委托和事件 {
//定义委托 public delegate void Prompt(int temperature); public class WaterHeater {
//声明委托变量 public Prompt promt; public int temperature = 0; public void BoilWater() { for (int i = 0; i < 80; i++) { temperature++; }
promt(temperature); } }
}
这个地方我们定义了一个委托,修改WaterHeater类声明了一个这个委托的变量。这个地方我改为调用这个委托变量用来取代原来的与报警器和显示器的关联,将之前的高耦合进行解耦。现在这个类已经和报警器没有一点关联。但是不能没有一点关联的对吧。不然怎么调用是吧。接下演示绑定的地方。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace 委托和事件 { class Program { static void Main(string[] args) { Monitor monitor = new Monitor(); Alarm alarm = new Alarm(); WaterHeater waterHeater = new WaterHeater(); waterHeater.promt = monitor.ShowMessage;//委托绑定 waterHeater.promt += alarm.Police;//委托绑定 waterHeater.BoilWater(); } } }
我们在main方法里面进行对委托的绑定。我们将之前在类的内部的强耦合提到了外面。如果不需要某个报警装置了,或者我们要添加某个装置。只要在main方法对委托进行绑定,或者解绑就好了。不需要再去修改热水器烧水的代码了。到这里委托的基本使用就介绍完了,下面介绍下事件。
事件是什么? 事件就是对委托的封装。举个例子,属性封装了字段。事件就是等于对委托的封装。有了事件我赋值可以直接使用+=或者-=而不需要用+。
下面演示一波事件代码。将之前的代码进行修改。
namespace 委托和事件 { public delegate void Prompt(int temperature); public class WaterHeater { private Prompt promt; public event Prompt OnPromptEvent;//定义Prompt委托类型的事件。 public int temperature = 0; public void BoilWater() { for (int i = 0; i < 80; i++) { temperature++; }
//调用事件 OnPromptEvent(temperature); } }
这个地方我们又声明了一个事件。并且将调用委托的地方改为了事件,还将委托的访问类型改为私有。main方法我们也进行了修改。
事件小总结
1.声明关键字 event
2.关键字后面紧跟委托类型
修改main方法
static void Main(string[] args) { Monitor monitor = new Monitor(); Alarm alarm = new Alarm(); WaterHeater waterHeater = new WaterHeater(); waterHeater.OnPromptEvent += monitor.ShowMessage; waterHeater.OnPromptEvent += alarm.Police; waterHeater.BoilWater(); }
将委托的绑定改为了事件绑定。到了这里有没有同学发现和我们winform 按钮事件是极为相似的,其实按钮那个点击事件也是这个事件。
this.button1.Click += new System.EventHandler(this.button1_Click); private void button1_Click(object sender, EventArgs e) { }
这是Vs帮我们生成的按钮绑定事件,是不是和我们写的一样。但是细心的小朋友可能会说:参数定义不一样,Vs里面的事件都会有(object sender, EventArgs e)这两个参数。
其实这是微软定义的一种委托事件声明的规范。sender 传递触发者,EventArgs参数传递而外信息。比如我们写的那个demo sender就是热水器本身,参数我们就要传递的温度。下面我来修改一下。
首先添加一个参数类继承于EventArgs我们自定义的要传递的参数都继承于EventArgs。篇幅有限制,不详细介绍。可以自己查阅资料或者看我文章看透推荐的博客。
public class BoiledEventArgs : EventArgs { public readonly int temperature; public BoiledEventArgs(int temperature) { this.temperature = temperature; } }
修改了显示器类和报警器类的方法签名
public class Monitor { public void ShowMessage(object sender, BoiledEventArgs e) { Console.WriteLine(string.Format("热水器在的温度已经{0}°啦,赶紧去洗澡吧", e.temperature)); } } public class Alarm { public void Police(object sender, BoiledEventArgs e) { Console.WriteLine(string.Format("热水器在的温度已经{0}°啦,赶紧去洗澡吧", e.temperature)); } }
修改热水器类
public delegate void Prompt(object sender, BoiledEventArgs e); public class WaterHeater { private Prompt promt; //定义Prompt类型事件 public event Prompt OnPromptEvent; public int temperature = 0; public void BoilWater() { for (int i = 0; i < 80; i++) { temperature++; } BoiledEventArgs e = new BoiledEventArgs(temperature); OnPromptEvent(this, e); } }
main方法是不用动的。
完整代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace 委托和事件 { public delegate void Prompt(object sender, BoiledEventArgs e); public class WaterHeater { private Prompt promt; //定义Prompt类型事件 public event Prompt OnPromptEvent; public int temperature = 0; public void BoilWater() { for (int i = 0; i < 80; i++) { temperature++; } BoiledEventArgs e = new BoiledEventArgs(temperature); OnPromptEvent(this, e); } } // 定义BoiledEventArgs类,传递需要的的信息 public class BoiledEventArgs : EventArgs { public readonly int temperature; public BoiledEventArgs(int temperature) { this.temperature = temperature; } } public class Monitor { public void ShowMessage(object sender, BoiledEventArgs e) { Console.WriteLine(string.Format("热水器在的温度已经{0}°啦,赶紧去洗澡吧", e.temperature)); } } public class Alarm { public void Police(object sender, BoiledEventArgs e) { Console.WriteLine(string.Format("热水器在的温度已经{0}°啦,赶紧去洗澡吧", e.temperature)); } } }