委托是个说烂了的话题,但是依旧有好多人不知道为什么要在 C# 中使用委托,最近有朋友也问到我这个问题,所以举例些场景,以供那些知道怎么声明委托、怎么调用却不知道为什么要用的朋友一些参考,当然也是希望验证下自己的理解是否正确。
委托使用关键字 delegate,从外形上看和一个没有方法体的方法一样,只不过是多了个关键字。
- public delegate void MyDelegateHandler(); //无返回值,无参数
- public delegate int MyDelegateHandler1(); //有返回值,无参数
- public delegate object MyDelegateHandler2(string name); //有返回值,有参数
- public delegate object MyDelegateHandler3(object first, ref int second, out float third, params object[] args); //有返回值,多个返回值
委托的声明可以放在类的外面,也可以在类的内部
- public delegate object MyDelegateHandler2(string name);//有返回值,有参数
- static class Program
- {
- public delegate void MyDelegateHandler();//无返回值,无参数
- }
在. NET Framework 中定义了大量的委托类型,像什么 WaitCallback、ParameterizedThreadStart、EventHandler、...,为了兼容旧版本所以一直没去掉,从. NET Framework 3.5 后你可以使用 Action、和 Func(带返回值)来更简单的使用委托,他们都定义了大量的重载版本
下面是流年写的一个计算器类(比较简陋),里面有各种运算方法,每个方法只干一件事,恩,满足了单一原则,棒棒的!
- public class Calculator
- {
- public static int Add(int first, int second)
- {
- return first + second;
- }
- public static int Sub(int first, int second)
- {
- return first - second;
- }
- }
流年很简单的就使用了这个类里的方法,哇,毫无压力
- var result = Calculator.Add(1, 6);
现在问题来了 "在做运算前,需要去验证每个参数(假设这里希望使用的参数都是正整数)",╮(╯▽╰)╭每个方法都需要去加一段代码
- public static int Add(int first, int second)
- {
- if (first <= 0 || second <= 0)
- {
- throw new ArgumentException("参数错误");
- }
- return first + second;
- }
一个方法一个方法的去改,很是麻烦,干脆我把计算直接写一个方法里,三下五除二,流年开始啪,啪,啪...
- public static int Calc(int first, int second, string operater)
- {
- if (first <= 0 || second <= 0)
- {
- throw new ArgumentException("参数错误");
- }
- int result = 0;
- if (operater.Equals("+"))
- {
- result = first + second;
- }
- if (operater.Equals("-"))
- {
- result = first - second;
- }
- return result;
- }
OK, 搞定,很简单嘛,但仔细一看,我靠 Calc 计算方法中干了那么多事情,又是加又是减的,如果计算器类还要添加对乘法的支持,还需要再修改这个方法,代码耦合度太高了,这不就违背了对扩展开放,对修改关闭的原则了嘛。就在这时,天空乌云密布,一道闪电击中了流年的脑袋...
首先我们来看,这段代码的变化点是什么?运算方法嘛
有什么办法可以隔离这种变化呢? 委托嘛
- public static int Calc(int first, int second, Func<int, int, int> handler)
- {
- if (first <= 0 || second <= 0)
- {
- throw new ArgumentException("参数错误");
- }
- return handler.Invoke(first, second);//更简单的写法handler(first, second)
- }
- Func < int,
- int,
- int > calcHandler = new Func < int,
- int,
- int > (Calculator.Add);
- var result = Calculator.Calc(1, 6, calcHandler);
这样不用去改变原来的方法,Add 还是 Add,减法运算还是减法运算,就算再添加乘 / 除算法,直接添加加乘 / 除算法相关的方法就行,也不用去改动原来的代码了。而且算法选择逻辑也是交给的客户端,而不是方法内部。将变化隔离了出去。
上面的委托实例声明好麻烦,那我们再改进改进,可以将方法直接赋值给委托实例
- Func < int,
- int,
- int > calcHandler = Calculator.Add;
- var result = Calculator.Calc(1, 6, calcHandler);
再改进改进, 直接将方法当做实参传递
- var result = Calculator.Calc(1, 6, Calculator.Add);
此刻,有没有一种想把委托按在床上的冲动。不要着急,这还只是前戏...
在上面我们提到可以将一个方法直接赋值给实例,那么是不是直接就可以将匿名方法直接赋值给委托实例呢?废话不说,直接试试就知道了
- Func < int,
- int,
- int > calcHandler = delegate(int first, int second) {
- return first - second;
- };
- var result = Calculator.Calc(1, 6, calcHandler);
这就完了?当然没有,让我们继续挑逗匿名方法
既然说,calcHandler 实例的引用是指向匿名方法的,那么是不是可以直接将匿名方法直接渗入 Calculator.Calc 方法的参数中
- var result = Calculator.Calc(1, 6, delegate(int x, int y) {
- return x - y;
- });
基于匿名函数,从 Visual Studio 2010 开始,微软将匿名函数又升级成了 Lambda 表达式,代码是越来越简洁,连 TMD 方法都不用创建了,直接将算法写在调用上。
- var result = Calculator.Calc(1, 6, (a, b) => a * b);
而且为了方便对集合类型的操作,微软还封装了大量的 Linq 扩展方法,这些都是基于委托实现的
- int[] numbers = {
- 11,
- 4,
- 3,
- 89,
- 5,
- 10
- };
- //获取集合中大于10的数字
- var query = numbers.Where(w = >w > 10);
还是回到原来的代码,Calculator 类中委托调用的地方 handler.Invoke(first, second),如果说委托实例对应的方法是个耗时的操作,我想我们谁也不想直接同步调用,让程序傻傻的死在那里, 至少给用户一些提示。为了处理这种问题,我们可以直接使用委托的异步调用
- public class Calculator
- {
- public static int Add(int first, int second)
- {
- Console.WriteLine($"Add Thread Id {Thread.CurrentThread.ManagedThreadId}");
- Thread.Sleep(500);//模拟耗时的操作
- return first + second;
- }
- public static int Sub(int first, int second)
- {
- return first - second;
- }
- public static int Calc(int first, int second, Func<int, int, int> handler)
- {
- Console.WriteLine($"Calc Thread Id {Thread.CurrentThread.ManagedThreadId}");
- if (first <= 0 || second <= 0)
- {
- throw new ArgumentException("参数错误");
- }
- var ir = handler.BeginInvoke(first, second, null, null);
- Console.WriteLine("还在计算当中...");
- //等待计算结果
- return handler.EndInvoke(ir);
- }
- }
最后一行代码
,作用是等待异步调用返回结果。他会一直阻塞线程直到异步调用完成,然后返回计算的结果值。 委托异步调用方法的返回值为一个 IAsyncResult 接口,我们可以通过该接口的实例属性 IsCompleted 轮询判断异步是否调用完成。
- handler.EndInvoke(ir)
- //定义一个计算完成事件
- public static event Action<int> OnCalcCompelted;
- var ir = handler.BeginInvoke(first, second,
- (o) =>
- {
- var result = handler.EndInvoke(o);
- //通过事件通知注册用户计算已经完成,并将结果传递出去
- if (OnCalcCompelted!=null)
- {
- OnCalcCompelted(result);
- }
- },
- null);
- Console.WriteLine("还在计算当中...");
结语:
方法与委托就好比普通类与接口(抽象类)的关系。
编码过程中委托并不一定是强制使用,他只不过是一种实现方式,在某些场景下比较合适,所以不要纠结于是要调用方法还是要通过委托调用,就像不懂设计模式也可以写代码完成功能,但是懂得这些套路之后你的代码会更加有条理,更具有扩展性,当然逼格也越高。但是,我觉得不用模式套路的代码逼格更高,谁都看不懂 O(∩_∩)O 哈哈~
回到我们的 Calculator 类,如果需求是 Calculator 中只要实现加法运算,那 TMD 的谁还用委托,直接实现一个加法方法就行了,就这么简单。
涉及到委托的使用还不仅仅是这些,像什么事件、表达式树... 每个都可以单独作为主题来讲,而且园子里也有很多讲解的文章。流年水平有限,就简单写到这里。
来源: