委托
编程中,可能会遇到以下逻辑
需求:
写一个方法,输出数组中满足条件(5的倍数,除7余1……)的数字。
我们可以这样写:
方法1(数字x):如果x是5的倍数,返回真。
方法2(数字x):如果x除以7余1,返回真。
打印1(数组a){if(方法1(数组a中所有的元素))打印该元素}
打印2(数组a){if(方法2(数组a中所有的元素))打印该元素}
主方法酌情调用。代码如下:
1 static bool ff1(int x)
2 {
3 return x % 5 == 0;
4 }
5 static bool ff2(int x)
6 {
7 return x % 7 == 1;
8 }
9 static void dy1(int[] a)
10 {
11 foreach(var t in a)
12 {
13 if(ff1(t))
14 {
15 Console.WriteLine(t);
16 }
17 }
18 }
19 static void dy2(int[] a)
20 {
21 foreach (var t in a)
22 {
23 if (ff2(t))
24 {
25 Console.WriteLine(t);
26 }
27 }
28 }
29 static void Main(string[] args)
30 {
31 int[] a = new int[25];
32 for (int i = 0; i < a.Length; i++)
33 {
34 a[i] = i + 1;
35 }
36 dy1(a);
37 dy2(a);
38 Console.ReadKey();
39 }
运行结果如图:

打印1和打印2看起来高度相似,让我们有一种简写愿望。如果能写成下面这样就好了:
打印(数组 a,方法 b){if(方法b(数组a中所有的元素))打印该元素}
即把方法也作为参数传递过去,然后综合处理。委托恰好可以实现这个需求。
c#关于委托,不同时期有不同的主流表达方法。这里只介绍现阶段,用起来方便快捷的用法。
委托的实质:定义一个指向(表示)方法的变量,就像int a定义了一个表示整数的变量一样。
知识点:1、Func表示有一个返回值的方法;2、Action表示一个无返回值的方法(即void);3、其他,此处不介绍。
上例中,想完成数据的综合处理,可以写成:
static bool ff1(int x)
{
return x % 5 == 0;
}
static bool ff2(int x)
{
return x % 7 == 1;
}
static void dy(int[] a,Func<int,bool> b)
{
foreach (var t in a)
{
if (b(t))
{
Console.WriteLine(t);
}
}
}
static void Main(string[] args)
{
int[] a = new int[25];
for (int i = 0; i < a.Length; i++)
{
a[i] = i + 1;
}
dy(a,ff1);
dy(a,ff2);
Console.ReadKey();
}
运行结果不变。
其中,“Func<int,bool> b”表示b是一个返回值为bool,参数为int的任意方法。即“<>”中最后一个数据类型表示返回值,前面的都是参数类型。
(ps:所有参数加起来不能超过16个,一般够用。Action一样,略。)
日常开发中,合理地使用委托能够更好地让程序员专注于类的内部开发,而把程序的耦合部分作为接口保留到使用的时候,由组合程序员进行拼装,使程序更加条理清晰。
委托的综合例(这是我在网上见到的一个非常好的能说明委托优势,并且包含了多播知识的例子):
平时家用的热水器,很多时候是由不同的热水器主体(能烧水、监测温度)、不同的温度显示器(指针、数字、液晶面板等)、不同的报警器(声、光等)构成。
我们在这里编写一个热水器类,一个温度显示器类,一个报警器类。开发的时候它们专注于自己的功能,互相都可以不知道对方的情况;主程序中按照通用的方式进行组装。
三者的功能划分如下:
热水器类应当有一个温度属性和一个烧水动作。作为一个可以附加温度显示器和报警器的好产品,它还应该在水温改变时,向外界送出当前的温度。
显示器类只负责显示温度,不管接在热水器还是烤箱、空调等任何家电上,只要有温度送进来,它就可以显示。
报警器类只负责在温度超过警戒线时进行报警,接在什么设备上应该都可以运行。
代码如下(为了让程序好看,大量采用中文类名、变量名。实际大项目协同开发中应尽量避免):
热水器类:
1 class 热水器
2 {
3 //默认冷水水温为20度
4 int 温度 =20;
5
6 //定义一个委托,在适当的时候向外送出温度
7 //至于送出温度以后对方怎么处理,热水器类不关心。
8 //由于接收温度的设备可能有多个(例如本例中的显示器和报警器),所以这个委托不方便有返回值(大家都有返回值,你接谁的呢?)
9 public Action<int> 送出温度;
10
11 //模拟热水器的加热动作,每加热一次,水温升高一度
12 public void 加热()
13 {
14 if(温度<100)
15 {
16 温度++;
17 }
18 //本热水器设计为,每执行一次加热动作,向外送出一次温度
19 送出温度(温度);
20 }
21 }
显示器类:
class 显示器
{
//很简单,仅显示接收到的温度,不管谁送来的。
public void 显示温度(int 要显示的温度)
{
Console.WriteLine("我看到了,当前温度是:"+要显示的温度.ToString());
}
}
报警器类:
class 报警器
{
int 临界温度;
public 报警器(int 设定临界温度)
{
临界温度 = 设定临界温度;
}
public void 报警器工作(int 传入温度)
{
if(传入温度>=临界温度)
{
Console.WriteLine("滴滴滴~,不得了了,温度太高,已经"+传入温度.ToString()+"度了");
}
}
}
主程序:
static void Main(string[] args)
{
//热水器及配件
热水器 我家的热水器 = new 热水器();
显示器 我家的显示器 = new 显示器();
报警器 我家的报警器 = new 报警器(80);
//组装
//+=运算符实现一个消息传递给多个方法,叫做多播
//因为之前提到的返回值问题,也只有Action委托才可以多播
我家的热水器.送出温度 += 我家的显示器.显示温度;
我家的热水器.送出温度 += 我家的报警器.报警器工作;
//运行,烧水120次
for (int i = 1; i <=120; i++)
{
我家的热水器.加热();
}
Console.ReadKey();
}
运行效果自行查看。
“Lambda 表达式”是采用以下任意一种形式的表达式:
表达式 lambda,表达式为其主体:
(input-parameters) => expression
语句 lambda,语句块作为其主体:
(input-parameters) => { <sequence-of-statements> }
使用 lambda 声明运算符=> 从其主体中分离 lambda 参数列表。 若要创建 Lambda 表达式,需要在 Lambda 运算符左侧指定输入参数(如果有),然后在另一侧输入表达式或语句块。
任何 Lambda 表达式都可以转换为委托类型。 Lambda 表达式可以转换的委托类型由其参数和返回值的类型定义。 如果 lambda 表达式不返回值,则可以将其转换为 Action 委托类型之一;否则,可将其转换为 Func 委托类型之一。 例如,有 2 个参数且不返回值的 Lambda 表达式可转换为 Action<T1,T2> 委托。 有 1 个参数且返回值的 Lambda 表达式可转换为 Func<T,TResult> 委托。
即:“Lambda 表达式”可以看做简写的函数。
所以,如果我们想打印所有3的倍数(没有现成的方法,想用最快的速度临时写一个),上例可以用它改写为:
static void dy(int[] a,Func<int,bool> b)
{
foreach (var t in a)
{
if (b(t))
{
Console.WriteLine(t);
}
}
}
static void Main(string[] args)
{
int[] a = new int[25];
for (int i = 0; i < a.Length; i++)
{
a[i] = i + 1;
}
dy(a,x=>x%3==0);
Console.ReadKey();
}
运行效果为:
