面试常考:C# 委托(delegate、Action、Func、predicate)和事件 ...

打印 上一主题 下一主题

主题 675|帖子 675|积分 2025

面试常考:C# 委托(delegate、Action、Func、predicate)和事件

刚开始工作的时候,觉得委托和事件有些神秘,而当你理解他们之后,也觉得好像没有想象中的那么难,这篇博文算是自己对委托和事件的一次梳理和总结。
二、委托

C#中的委托,相当于C++中的指针函数,但委托是面向对象的,是安全的,是一个特殊的类,当然他也是引用类型,委托传递的是对方法的引用。
2.1、delegate

声明委托就必须使用关键字“delegate”,委托是先声明,后实例化。至少0个参数,至多32个参数
格式如下所示:
  1. private delegate string GetAsString();
复制代码
委托是一个类,所以他的实例化跟类的实例化一样,只是他总是接受一个将委托方法作为参数的构造函数。调用委托方法就有两种方式,如下所示:
  1. int i = 10;  
  2. var method = new GetAsString(i.ToString);  
  3. //调用方法一  
  4. Console.WriteLine($"method方法{method()}");  
  5. //调用方法二  
  6. Console.WriteLine($"method.Invoke方法{method.Invoke()}");  
复制代码
运行结果:

2.2、Action

Action是无返回值的泛型委托,可以接受0个至16个传入参数
Action 表示无参,无返回值的委托
Action 表示有传入参数int,string无返回值的委托
前面我们【Log4Net 日志记录的实现】中,就使用了Action。如:
  1. public static void Debug(string message, Action RegistedProperties)  
  2. {  
  3.         RegistedProperties();  
  4.         log.Debug(message);  
  5. }
复制代码
调用方式为:
  1. PFTLog.Debug("测试扩展字段", () => {  
  2.     LogicalThreadContext.Properties["LogType"] = "扩展字段内容";  
  3. });  
复制代码
在运行中,直接运行Action中的内容即可。
2.3、Func

Func是有返回值的泛型委托,可以接受0个至16个传入参数
Func 表示无参,返回值为int的委托
Func 表示传入参数为object, string 返回值为int的委托
  1. public static decimal GetTotal(Func<int, int, decimal> func, int a, int b)  
  2. {  
  3.     return func(a, b);  
  4. }  
复制代码
调用方式
  1. var total = GetTotal((a, b) => { return (decimal)a + b; }, 1, 2);  
  2. Console.WriteLine($"结果为{total}");  
复制代码
运行结果

2.4、predicate

predicate 是返回bool型的泛型委托,只能接受一个传入参数
predicate 表示传入参数为int 返回bool的委托
定义一个方法:
  1. public static bool FindPoints(int a)  
  2. {  
  3.     return a >= 60;  
  4. }
复制代码
定义Predicate委托
  1. Predicate<int> predicate = FindPoints;
复制代码
调用
  1. var points = new int[] {  
  2.     10,  
  3.     50,  
  4.     60,  
  5.     80,  
  6.     100 };  
  7. var result = Array.FindAll(points, predicate);  
  8. Console.WriteLine($"结果为{string.Join(";", result)}");
复制代码
运行结果

2.5、多播委托

前面的只包含了一个方法的调用,委托可以包含多个方法,这种委托就叫做多播委托。多播委托利用“+=”和“-+”两种运算符进行添加和删除委托。
先定义两个方法
  1. public static void MultiplyByTwo(double v)  
  2. {  
  3.     double result = v * 2;  
  4.     Console.WriteLine($"传值:{v};MultiplyByTwo结果为{result}");  
  5. }  
  6. public static void Square(double v)  
  7. {  
  8.     double result = v * v;  
  9.     Console.WriteLine($"传值:{v};Square结果为{result}");  
  10. }
复制代码
然后调用
  1. Action<double> operations = MultiplyByTwo;  
  2. operations(1);  
  3. operations += Square;  
  4. operations(2);  
复制代码
运行结果:

三、事件

事件是基于委托,为委托提供一种发布/订阅机制,声明事件需要使用event关键字。
发布者(Publisher):一个事件的发行者,也称作是发送者(sender),其实就是个对象,这个对象会自行维护本身的状态信息,当本身状态信息变动时,便触发一个事件,并通知说有的事件订阅者;
订阅者(Subscriber):对事件感兴趣的对象,也称为Receiver,可以注册感兴趣的事件,在事件发行者触发一个事件后,会自动执行这段代码
是不是看到sender,就有种很熟悉的感觉!!!先不忙着急,我们先看下事件的声明和使用
有这样一个应用场景,如果系统有异常,需要及时的通知管理员。那么需要在我们的日志记录里面添加通知管理员的功能,但是问题来了,该怎么通知管理员呢?至少现在无法知道。所以我们就需要在使用到事件。
添加代码如下,如果不知道日志功能的可以参考【Log4Net 日志记录的实现】
  1. //声明一个通知的委托  
  2. public delegate void NoticeEventHander(string message);  
  3. //在委托的机制下我们建立以个通知事件  
  4. public static event NoticeEventHander OnNotice;
复制代码
调用方式
  1. public static void Debug(string message, Action RegistedProperties)  
  2. {  
  3.     RegistedProperties();  
  4.     log.Debug(message);  
  5.     //执行通知  
  6.     OnNotice?.Invoke($"系统异常,请及时处理,异常信息:{message}");  
  7. }  
复制代码
在引用场景的代码,先定义一个通知管理员的方法(这里我们直接Console.WriteLine出来)
  1. public static void Notice(string message)  
  2. {  
  3.     Console.WriteLine($"通知内容为{message}");  
  4. }
复制代码
先注册,然后触发异常消息
  1. //注册方式一  PFTLog.OnNotice += Notice;  //注册方式二  //PFTLog.OnNotice += new PFTLog.NoticeEventHander(Notice);    PFTLog.Debug("测试扩展字段", () => {  
  2.     LogicalThreadContext.Properties["LogType"] = "扩展字段内容";  
  3. });  
复制代码
运行结果

这里面我只需要定义好发布者,你可以以任何方式订阅,是不是很非常简单。
弄明白了上面的事件,我们在来说说.Net经常出现的object sender和EventArgs e
.Net Framework的编码规范:
一、委托类型的名称都应该以EventHandler结束
二、委托的原型定义:有一个void返回值,并接受两个输入参数:一个Object 类型,一个 EventArgs类型(或继承自EventArgs)
三、事件的命名为 委托去掉 EventHandler之后剩余的部分
四、继承自EventArgs的类型应该以EventArgs结尾
现在我们以一个新书发布的自定义事件为例
创建对应的类文件:
事件者发布代码:
  1. public class BookInfoEventArgs : EventArgs  
  2. {  
  3.     public BookInfoEventArgs(string bookName)  
  4.     {  
  5.         BookName = bookName;  
  6.     }  
  7.   
  8.     public string BookName { get; set; }  
  9.   
  10. }   
复制代码
  1. public class BookDealer  
  2. {  
  3.     //泛型委托,定义了两个参数,一个是object sender,第二个是泛型 TEventArgs 的e  
  4.     //简化了如下的定义  
  5.     //public delegate void NewBookInfoEventHandler(object sender, BookInfoEventArgs e);  
  6.     //public event NewBookInfoEventHandler NewBookInfo;  
  7.     public event EventHandler<BookInfoEventArgs> NewBookInfo;  
  8.     public void NewBook(string bookName)  
  9.     {  
  10.         RaiseNewBookInfo(bookName);  
  11.     }  
  12.   
  13.     public void RaiseNewBookInfo(string bookName)  
  14.     {  
  15.         NewBookInfo?.Invoke(this, new BookInfoEventArgs(bookName));  
  16.     }  
  17. }  
复制代码
事件订阅者
  1. public class Consumer  
  2. {  
  3.     public Consumer(string name)  
  4.     {  
  5.         Name = name;  
  6.     }  
  7.   
  8.     public string Name { get; set; }  
  9.   
  10.     public void NewBookHere(object sender, BookInfoEventArgs e)  
  11.     {  
  12.         Console.WriteLine($"用户:{Name},收到书名为:{ e.BookName}");  
  13.     }  
  14. }  
复制代码
事件订阅和取消订阅
  1. var dealer = new BookDealer();  
  2. var consumer1 = new Consumer("用户A");  
  3. dealer.NewBookInfo += consumer1.NewBookHere;  
  4. dealer.NewBook("book112");  
复制代码
  1. var consumer2 = new Consumer("用户B");  
  2. dealer.NewBookInfo += consumer2.NewBookHere;  
复制代码
  1. dealer.NewBook("book_abc");  
  2.   
  3. dealer.NewBookInfo -= consumer1.NewBookHere;  
复制代码
  1. dealer.NewBook("book_all");  
复制代码
运行结果

经过这个例子,我们可以知道Object sender参数代表的是事件发布者本身,而EventArgs e
也就是监视对象了。深入理解之后,是不是觉得也没有想象中的那么难了。
四、总结

这里我们讲了委托和事件,在.Net开发中使用委托和事件,可以减少依赖性和层的耦合,开发出具有更高的重用性的组件。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

老婆出轨

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表