.NET面试题系列(10)委托与事件
序言
委托
有了委托的存在,使得方法可以作为参数传递给另一个方法。
int Max(int x,int y) { return x>y?x:y; } int Min(int x,int y) { return x<y?x:y; }
上面两个函数的共同特点:具有相同的返回值和参数列表。在C++里,我们使用函数指针来指代(被授权,代表)这两个函数。
实际上,我们可以用函数指针指向任意一个具有相同返回值和参数列表的函数(静态方法或实例的方法成员)。
在C#里没有提供函数指针,取而代之的是委托(delegate);利用委托,我们可以像使用函数指针一样在程序运行时动态指向具备相同签名(具有相同参数类型、参数个数以及相同类型返回值)的方法。
委托的本质:函数指针。说的通俗一些,委托就是能够让方法作为变量来传递。
委托是一种类型安全的函数回调机制, 它不仅能够调用实例方法,也能调用静态方法,并且具备按顺序执行多个方法的能力。
委托的声明
Demo1
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DelegateSamples { //声明一个委托,参数为string,无返回值 delegate void DelSamples(string msg); class Program { static void Main(string[] args) { //使用new关键字 DelSamples delSample = new DelSamples(new Program().SpeakChinese); delSample("Koala工作室"); //不使用new,自动推断委托类型 DelSamples delSample2 = SpeakEnglish; delSample2("KoalaStudio"); //利用Lambda表达式 DelSamples delSample3 = (string msg) => SpeakEnglish(msg); delSample3("KoalaStudio"); Console.ReadKey(); } private void SpeakChinese(string msg) { Console.WriteLine("你好,我是{0}",msg); } private static void SpeakEnglish(string msg) { Console.WriteLine("Hello,I'm {0}",msg); } } }
Demo2
private static void EnglishGreeting(string name) { Console.WriteLine("Good Morning, " + name); } private static void ChineseGreeting(string name) { Console.WriteLine("早上好, " + name); } public delegate void GreetingDelegate(string name);//委托 private static void GreetPeople(string name, GreetingDelegate MakeGreeting) { MakeGreeting(name); } private void button45_Click(object sender, EventArgs e) { GreetPeople("Liker", EnglishGreeting); GreetPeople("沐风", ChineseGreeting); Console.ReadLine(); }
我们现在对委托做一个总结:委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If … Else(Switch)语句,同时使得程序具有更好的可扩展性。
匿名委托
在上一分钟已经知道了,完成一个委托应用分三步走,缺一步都不行,如果要跨大步,当心步子大了扯着蛋。但是微软不怕扯着蛋,非要把三步做成两步来走啊!所以微软就用匿名方法来简化上边的三个步骤。匿名方法这个玩意儿怎么说呢,在C#中完全是可有可无的东西,只是为C#锦上添花,有人别出心裁给它取个名字叫语法糖。
1 public partial class WebForm3 : System.Web.UI.Page 2 { 3 //step01:首先用delegate定义一个委托 4 public delegate int CalculatorAdd(int x, int y); 5 6 protected void Page_Load(object sender, EventArgs e) 7 { 8 //step02:用这样的写法 delegate(int x, int y) { return x + y; },把一个方法赋值给委托 9 CalculatorAdd cAdd = delegate(int x, int y) { return x + y; }; 10 int result = cAdd.Invoke(5, 6); 11 } 12 }
step01:首先用delegate定义一个委托 。
step02:用这样的写法 delegate(int x, int y) { return x + y; },把一个方法赋值给委托,其实这种写法就是匿名方法。
这时会惊奇的发现,这不是三步当着两步走了哇?
匿名委托的写法更加优雅,但是需要注意两点:
1、在函数内部不能使用跳转语句跳出函数外部;
2、不能使用ref和out等关键字
Lambda表达式实现匿名委托
原本很简单的程序,加上几个delegate关键字,这代码一下就变得深奥了,深奥的东西懂的人就变少了,所以这个还可以作为加薪的筹码。但是微软对C#的设计理念是简单易用。微软就想方设法的来简化delegate(int x, int y) { return x + y; }这个匿名方法,Lambda就出现了。下边我来看几种lambda表达式的写法:
1 public partial class WebForm3 : System.Web.UI.Page 2 { 3 public delegate int CalculatorAdd(int x, int y); 4 5 protected void Page_Load(object sender, EventArgs e) 6 { 7 //方法一: 8 CalculatorAdd cAdd1 = (int x, int y) => { return x + y; }; 9 int result1 = cAdd1(5, 6); 10 11 //方法二: 12 CalculatorAdd cAdd2 = (x, y) => { return x + y; }; 13 int result2 = cAdd2(5, 6); 14 15 //方法三: 16 CalculatorAdd cAdd3 = (x, y) => x + y; 17 int result3 = cAdd2(5, 6); 18 } 19 }
方法一:简单的把delegate去掉,在()与{}之间加上 "=>"。
方法二:在方法一的基础上把参数类型都干掉了。
方法三:要干就干彻底些,把{},以及return关键字都去掉了。
这几种方法随便怎么写都行,不过就是害苦了初学者,一会儿看到这种写法,一会儿看到那种写法,把人搞的神魂颠倒人,如果没人指点,确实会迷糊,难就难在这儿。
多播委托
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DelegateSamples { //声明一个委托,参数为string,无返回值 delegate void DelSamples(string msg); class Program { static void Main(string[] args) { //多播委托可以带返回值,但是只有最后一个方法的返回值会被返回。 DelSamples delSample6 = new Program().SpeakChinese; delSample6 += SpeakEnglish; delSample6("KoalaStudio"); Console.ReadKey(); } private void SpeakChinese(string msg) { Console.WriteLine("你好,我是{0}",msg); } private static void SpeakEnglish(string msg) { Console.WriteLine("Hello,I'm {0}",msg); } } }
多播委托可以连续执行函数,但是如果函数有返回值,那只有最后一个函数的返回值会被正确返回.
泛型委托
泛型委托包括Action、Func和Predicate三种委托。
1.Action-无返回值
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DelegateSamples { class Program { static void Main(string[] args) { /* Action<T>:封装只有一个参数(类型为T),不包括返回值的签名函数,它包括以下几种情况: * Action<T>、Action<T1,T2>、Action<T1,T2,T3>、Action<T1,T2,T3,T4> * 声明: * delegate void Action(); * delegate void Action<T1>(T1 arg1); * delegate void Action<T1,T2>(T1 arg1,T2 arg2); * delegate void Action<T1,T2,T3>(T1 arg1,T2 arg2,T3 arg3); * delegate void Action<T1,T2,T3,T4>(T1 arg1,T2 arg2,T3 arg3,T4 arg4); */ Action<string> action = SpeakEnglish; action("KoalaStudio"); Action<string, string> action2 = SpeakTwoLanguage; action2("KoalaStudio","Koala工作室"); Console.ReadKey(); } private void SpeakChinese(string msg) { Console.WriteLine("你好,我是{0}",msg); } private static void SpeakEnglish(string msg) { Console.WriteLine("Hello,I'm {0}",msg); } private static void SpeakTwoLanguage(string msg1, string msg2) { Console.WriteLine("你好,我是{0}",msg1); Console.WriteLine("Hello,I'm {0}",msg2); } } }
2.Func-有返回值
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DelegateSamples { class Program { static void Main(string[] args) { /* Func<T,TResult>:封装一个具有参数(类型为T),返回TResult类型值的签名函数,它包括以下几种情况: * Func<T,TResult>、Func<T1,T2,TResult>、Func<T1,T2,T3,TResult>、Func<T1,T2,T3,T4,TResult> * 声明: * ……略去 */ Func<string,string/*这是返回值类型*/> func = SpeakEnglish; func("KoalaStudio"); Func<string, string, string/*这是返回值类型*/> func2 = SpeakTwoLanguage; func2("KoalaStudio","Koala工作室"); Console.ReadKey(); } private static string SpeakEnglish(string msg) { return string.Format("Hello,I'm {0}", msg); } private static string SpeakTwoLanguage(string msg1, string msg2) { Console.WriteLine("你好,我是{0}",msg1); Console.WriteLine("Hello,I'm {0}",msg2); return string.Format("你好,我是{0};Hello,I'm {1}", msg1,msg2); } } }
3.Predicate
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DelegateSamples { class Program { static void Main(string[] args) { /* bool Predicate<T>:表示定义一组条件并确定指定对象是否符合这些条件的方法。 * 通常,此类委托由Array和List类的几种方法使用,用于在集合中搜索元素。 * delegate bool Predicate<T>(T obj),如果obj符合此委托表示的方法中定义的条件,则返回true,否则返回false */ List<string> listString = new List<string>() { "a","abc","koala","xyz","take" }; //List对象的FindAll定义:public List<T> FindAll(Predicate<T> match); //match 类型:System.Predicate<T> 委托,用于定义要搜索的元素应满足的条件。 //返回值 //类型:System.Collections.Generic.List<T> //如果找到,则为一个 List<T>,其中包含与指定谓词所定义的条件相匹配的所有元素;否则为一个空 List<T>。 Predicate<String> match = delegate(string word) { if (word.Length > 4) { return true; } return false; }; List<string> result = listString.FindAll(match); } } }
协变与逆变
指的是传参和返回值
//斜变:子类转成父类(string=>object)
delegate object XieBianDelegate();
private void button2_Click(object sender, EventArgs e)
{
var xieBianDelegate = new XieBianDelegate(Fun1);
}
private string Fun1()
{
return "hello";
}
//逆变:父类转成子类(object=>string)
delegate void NiBianDelegate(string param);
private void button3_Click(object sender, EventArgs e)
{
var niBianDelegate = new NiBianDelegate(Fun2);
}
private void Fun2(object param)
{
Console.WriteLine($"{param}");
}
异步委托
表达式树
事件
1.什么是事件
谈到委托,必提事件,事件本质是对委托的封装,对外提供add_EventName(对应+=)和remove_EventName(对应-=)访问,从而实现类的封装性。
2.事件能解决什么问题
将公有的委托变量定义为私有变量,从而满足类的封装性原则;
具有委托具有的作用;
事件常用场景:类里面发生了变化,你希望外界能够观测到这种变化
https://www.bilibili.com/video/BV163411S7QG/?spm_id_from=333.999.0.0&vd_source=a56db24cb8cab4dd8153f9a519787c89
private void button1_Click(object sender, EventArgs e) { var collection = new ObservableCollection<int>(); collection.CollectionChanged += Collection_CollectionChanged; collection.Add(1); collection.Add(2); } private void Collection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { Console.WriteLine("观测到值发生了改变"); }
3.怎么使用事件
声明委托
声明事件
事件注册方法
4.事件机制
事件的本质就是委托,向外提供两个访问方法add_EventName(对应+=)和remove-EventName(对应-=),我们通过.NET Reflector反汇编工具来查看,到底是不是这样的。
委托、事件与Observer设计模式
声明委托事件
public delegate void delegatename(int param); //声明委托 public event delegatename eventname; //声明事件
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(); //烧水,会自动调用注册过对象的方法 } } }
输出结果
小结
https://www.bilibili.com/video/BV1AT411U7H2/?spm_id_from=333.788&vd_source=a56db24cb8cab4dd8153f9a519787c89
资料
https://www.bilibili.com/video/BV163411S7QG/?spm_id_from=333.999.0.0&vd_source=a56db24cb8cab4dd8153f9a519787c89
https://roslynpad.net/
https://github.com/roslynpad/roslynpad/releases/tag/19.1
http://www.tracefact.net/tech/009.html
https://www.cnblogs.com/wangjiming/p/8300103.html
http://www.cnblogs.com/laoyu/archive/2013/01/13/2859000.html