委托和Lambda表达式

思考并回答以下问题:

  • 不需要每次传递一个方法时都定义新接口怎么理解?
  • 静态类有什么特性?为什么要让一个类为静态?
  • switch怎么改成委托?
  • 为什么要使用Lambda表达式?
  • Action\怎么理解?Func代表什么委托?

本章涵盖:

可以创建类来封装数据以及对数据操作。随着创建的类越来越多,你会发现类和类的关系存在着一些常见的模式。一种常见的模式是向方法传递对象,该方法再调用对象的一个方法。例如,向方法传递一个IComparer\引用,被调用的方法本身可以在提供的对象上调用Compare()方法。在这种情况下,接口的作用只是向最终被调用的方法传递一个引用。所以,似乎不需要每次传递一个方法时都定义新接口。在本章中,我们讲述如何创建和使用被称为委托(delegate)的特殊类,它允许像处理其他任何数据那样处理对方法的引用。然后,我们展示如何使用Lambda表达式快速和简单地创建自定义委托。

Lambda表达式是从C#3.0开始加入的。C#2.0支持用匿名方法(anonymous method)这样一种不太优雅的语法来创建自定义委托。C#2.0之后的每个C#版本都支持匿名方法以保持向后兼容,但新写的代码应该弃用它,代之以Lambda表达式。本章将通过“高级主题”来描述如何使用匿名方法。只有要使用遗留的C#2.0代码时才需要了解这些主题,否则完全可以忽略这些补充内容。

委托概述

长期以来,经验丰富的C和C++程序员利用“函数指针”将对方法的引用作为实参传给另一个方法。C#使用委托提供相同的功能。委托允许捕捉对方法的引用,并像传递其他对象那样传递这个引用,像调用其他方法那样调用这个被捕捉的方法。来看看下面的例子。

背景

虽然效率不高,但冒泡排序或许是最简单的排序例程了。代码清单1展示了Bubblesort()方法。

代码清单1 Bubblesort()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace sort
{
class Program
{
static void Main(string[] args)
{
int[] items = { 67, 34, 6, 134, 349, 230, 56 };
SimpleSort1.BubbleSort(items);
}
}

//静态类的主要特性:
//1、仅包含静态成员
//2、无法实例化
//3、是密封的
//4、不能包含实例构造函数。
//5、工具类适合作为静态类,像Math类
static class SimpleSort1
{
public static void BubbleSort(int[] items)
{
int i;
int j;
int temp;

if(items == null)
{
return;
}

for (i=items.Length-1; i>= 0; i--)
{
for (j=1; j<=i; j++)
{
if (items[j-1] > items[j])
{
temp = items[j-1];
items[j-1] = items[j];
items[j] = temp;
}
}
}
for (int x = 0; x < items.Length; x++)
{
Console.WriteLine(items[x]);
}

Console.ReadKey();
}
}
}

该方法对整数数组执行升序排序。

为了能选择升级或降序来排序整数,有两个方案可选:一是复制上述代码,然后将大于操作符替换成小于操作符。但是复制这么多代码只是改变了一个操作符,这似乎不是一个好主意;二是传递一个附加参数,指出如何排序,如代码清单2所示。

代码清单2 Bubblesort()方法,升序或降序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace sort
{
class Program
{
static void Main(string[] args)
{
int[] items = { 67, 34, 6, 134, 349, 230, 56 };
Simplesort2.BubbleSort(items, Simplesort2.SortType.Descending);
}
}

class Simplesort2
{
public enum SortType
{
Ascending,
Descending
}

public static void BubbleSort(int[] items, SortType sortOrder)
{
int i;
int j;
int temp;

if (items == null)
{
return;
}

for (i = items.Length - 1; i >= 0; i--)
{
for (j = 1; j <= i; j++)
{
bool swap = false;

switch (sortOrder)
{
case SortType.Ascending:
swap = items[j - 1] > items[j];
break;

case SortType.Descending:
swap = items[j - 1] < items[j];
break;
}

if (swap)
{
temp = items[j - 1];
items[j - 1] = items[j];
items[j] = temp;
}
}
}
for (int x = 0; x < items.Length; x++)
{
Console.WriteLine(items[x]);
}

Console.ReadKey();
}
}
}

然而,上述代码只是照顾到了两种可能的排序方式。假如想按字典顺序排序(即1, 10, 11, 12, 2, 20, …),或者按其他方式排序,sortType值以及对应的swich分支的数量很快就会变得非常“恐怖”。

委托数据类型

为了增强灵活性和减少重复代码,可以将比较方法作为参数传给Bubblesort()方法。为了能将方法作为参数传递,必须要有一个能够表示方法的数据类型。这个数据类型就是委托,因为它“委托”调用对象所引用的方法。代码清单3对Bubblesort()方法进行了修改,它现在能获取一个委托参数。在本例中,委托数据类型是ComparisonHandler。

代码清单3 带有委托参数的Bubblesort()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class DelegateSample
{
public static void BubbleSort(int[] items, ComparisonHandler comparisonMethod)
{

int i;
int j;
int temp;

if(comparisonMethod == null)
{
throw new ArgumentNullException("comparisonMethod");
}

if(items == null)
{
return;
}

for(i = items.Length-1; i>=0; i--)
{
for (j=1; j <=i; j++)
{
if (comparisonMethod(items[j-1], items[j]))
{
temp = items[j-1];
items[j-1] = items[j];
items[j] = temp;
}
}
}
}
}

ComparisonHandler是委托类型,代表对两个整数进行比较的方法。在Bubblesort()方法中,可以使用comparisonMethod参数所引用的ComparisonHandler的实例来判断哪个整数更大。由于comparisonMethod代表一个方法,所以调用它的语法与直接调用方法的语法完全一致。在这个例子中,comparisonMethod委托获取两个整数参数,返回一个布尔值来指出第一个整数是否大于第二个。传方法进来要注意的是,ComparisonHandler委托是强类型的,它代表返回一个bool值,而且正好接受两个整数参数的方法。和其他方法调用一样,对委托的调用是强类型的。假如数据类型不匹配,C#编译器会报错。

委托类型的声明

前面描述了如何定义使用委托的方法,并介绍了如何将委托变量当作方法,从而简单地发出对委托的调用。然而,还必须学习如何声明委托类型。为了声明委托类型,要使用delegate关键字,后面跟着像是方法声明的东西。这个方法的签名是委托所引用的方法的签名。正常方法声明中方法名称的位置要替换成委托类型的名称。代码清单4展示了如何声明ComparisonHandler委托类型来要求两个整数并返回一个布尔值。

代码清单4 声明一个委托类型

1
public delegate bool ComparisonHandler(int first, int second);

就像类能嵌套在其他类中一样,委托也能嵌套在类中。假如委托声明出现在另一个类的内部,委托类型就会成为嵌套类型,如代码清单5所示。

代码清单5 声明嵌套的委托数据类型

1
2
3
4
class DelegateSample
{
public delegate bool ComparisonHandler(int first, int second);
}

在这个例子中,声明的委托数据类型是DelegateSample.ComparisonHandler,因为它被定义成DelegateSample中的嵌套类型。

委托的实例化

在使用委托来实现Bubblesort()方法的最后一步中,你将学习如何调用方法并传递委托实例-具体地说,传递ComparisonHandler类型的实例。为了实例化委托,需要一个和委托类型自身的签名匹配的方法。对于ComparisonHandler,这个方法应获取两个整数,并返回boo1值。方法名称无关紧要,但是方法的签名的剩余部分(参数和返回值)必须兼容委托的签名。代码清单12-6展示了与委托类型兼容的GreaterThan()方法。

代码清单12-6声明与ComparisonHandler兼容的方法

1
2
3
4
5
6
public delegate bool ComparisonHandler (int first, int second);
class DelegateSample
t
public static void Bubblesort(
int[] items, ComparisonHandler comparisonMethoa)委托作为参数
/..

原来用的是一个方法,现在是多个方法

1
2
public static bool GreaterThan(int first, int second)।
return first > second;

定义好方法之后,就可以调用Bubblesort(),并提供由委托捕捉到的方法的名称作为实参,如代码清单12-7所示。

代码清单12-7 使用方法名作为实参

1
2
public delegate bool ComparisonHandler (int first, int second).
class Delegatesampletblic static void Bubblesort(

注意, ComparisonHandler委托是引用类型,但不必用new实例化它。从C# 2.0开始,从方法组(为方法命名的表达式)向委托类型的转换会自动创建一个新的委托对象。

高级主题: C#1.0中的委托实例化

在代码清单12-7中,调用Bubblesort()时传递所需方法的名称(GreaterThan )作为一个实参就可以实例化委托。C#的第一个版本要求使用如代码清单12-8所示的较复杂的语法来实例化委托。

代码清单8 C#1.0中将委托作为参数传递

1
2
Bubblesort(items
new ComparisonHandler (GreaterThan)).

以后的版本支持上述两种语法。本书剩余的部分只使用更现代的、简洁的语法。高级主题:委托的内部机制

1委托实际是特殊的类]虽然C#标准没有确切规定类的层次结构应该是怎样的,但委托必须直接或间接地派生自System. Delegate。事实上, .NET中的委托类型总是 生自System.MulticastDelegate,后者又从System.Delegate派生,如图12-1所示。

反射图12-1 委托类型的对象模型

第一个属性属于(System.Reflection. MethodInfo)类型,它是第17章要讨论的主题。Methodinfo描述了特定方法的签名,包括方法名称、参数和返回类型除了Methodinfo,委托还需要一个对象实例,其中包含了要调用的方法。这正是第二个属性Target的作用。|在静态方法的情况下, Target对应于类型自身1至于MulticastDelegate类的作用,将在下一章详细描述。 target字段

注意,所有委托都是不可变的。委托一旦创建好就无法更改。如果变量包含了委托的引用,还想引用其他不同的方法,那就必须创建一个新委托,并把它指派给这个变量。虽然所有委托数据类型都是间接地从System. Delegate派生的,但C#编译器不允许声明直接或间接从System.Delegate或者System.MulticastDelegate派生的类。代码清单12-9的代码是无效的。

代码清单12-9 System.Delegate不能显式地作为基类

1
2
3
V ERROR: 'ComparisonHandLer. cannot
// innerit from special class 'System. Delegate"// public class ComparisonHandLer: System. DeLegate
//..

通过传递委托来指定排序方式显然要比本章开头的方式灵活得多。例如,为了改为按字母排序,只需添加一个附加的委托,在比较过程中将整数转换为字符串。代码清单12-10提供了实现按字母排序的完整代码,输出12-1展示了结果。

代码清单12-10使用其他与ComparisonHandler兼容的方法

1
2
3
4
5
6
7
8
9
10
11
using System;
class DelegateSample
public delegate bool comparisonHandler(int first, int second);public static void Bubblesorto
int[] items, ComparisonHandler comparisonMethod)
int i;int j;int temp;
for (1 = items.Length -1; 1 >= 0; 1--).
for (j=1;j<=i; j++)t
if (comparisonMethod(items [j-1], items [j)
temp = items[i-11;items[j-1] - items [3];items[i] = temp;
public static bool GreaterThan(int first, int second)
return first second;

有新的排序方式时,就再添加方法

1
2
3
4
5
6
7
8
9
10
11
12
public static bool AlphabeticalGreaterThan(int first, int second)
int comparison;
comparison(first.Tostring().CompareTor
second. Tostring());
return comparison >;
static void Main(string[] args)
int i;
int[] items = new int[5].
for (i-e; icitems.Length; it+)
Console.write("Enter an integer: ");
items[i] - int.Parse(Console, ReadLine());
Bubblesort(items, AlphabeticalGreaterThan); |这儿其实就是赋值了for (i = 8: i items. Length; i++) comparisonMethod =tconsole.WriteLine(items[41); AlphabeticalGreaterThan;

按字母排序与按数值排序的结果不同。可以看到,和本章开头描述的方式相比,现在添加一个附加的排序机制是多么简单!

要想按字母排序,唯一要做的就是添加A1phabeticalGreaterThan方法,然后在调用Bubblesort()的时候传递该方法。

通用的委托:System.Func和System.Action

为了减少自定义委托类型的必要,.NET3.5“运行时”库(对应C#3.0)包含了一组通用的委托,其中大多数都是泛型。System.Func系列委托代表有返回值的方法,而System.Action系列委托代表返回void的方法。下面展示了这些委托的签名。

Func和Action委托声明

1
2
3
4
5
6
7
8
9
10
11
public delegate void Action();
public delegate void Action<in T>(T arg);
public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);
...
// 一直到16个参数

public delegate TResult Func<out TResult>();
public delegate TResult Func<in T, out TResult>(T arg);
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
...
// 一直到16个参数

0%