您的位置:首页 > 编程语言 > C#

(转)C#中的委托,匿名方法和Lambda表达式

2010-10-08 18:06 513 查看
原文地址:http://www.cnblogs.com/niyw/archive/2010/10/07/1845232.html

简介

  在.NET中,委托,匿名方法和Lambda表达式很容易发生混淆。我想下面的代码能证实这点。下面哪一个First会被编译?哪一个会返回我们需要的结果?即Customer.ID=5.答案是6个First不仅被编译,并都获得正确答案,且他们的结果一样。如果你对此感到困惑,那么请继续看这篇文章。

view sourceprint?
01
class
Customer
02
{
03
public
int
ID {
get
;
set
;}
04
public
static
bool
Test(Customer x)
05
{
06
return
x.ID == 5;
07
}
08
}
09
...
10
List<Customer>custs =
new
List<Customer>();
11
custs.Add(
new
Customer() { ID = 1}); 
12
custs.Add(
new
Customer() { ID = 5}); 
13
 
14
custs.First(
new
Func<Customer,
bool
>(
delegate
(Customer x) {
return
x.ID == 5;})); 
15
custs.First(
new
Func<Customer,
bool
>((Customer x) =>x.ID == 5));
16
custs.First(
delegate
(Customer x) {
return
x.ID == 5;}); 
17
custs.First((Customer x) =>x.ID== 5); 
18
custs.First(x =>x.ID == 5);
19
custs.First(Customer.Test);
什么是委托?

  现在你定义一个处理用户订单的购物车ShoppingCart类。管理层决定根据数量,价格等给客人折扣。做为其中的一部分,他们已经实现了处理订单时你要考虑一方面。不用考虑过多,你简单声明一个变量来保存有“吸引力的折扣”(magicDisCount),然后实现逻辑。

view sourceprint?
01
class
Program {
02
static
void
Main(
string
[] args){
03
new
ShoppingCart().Process();
04
}
05
}
06
class
ShoppingCart {
07
public
void
Process() {
08
int
magicDiscount = 5;
09
// ...
10
}
11
}
  第二天,异想天开的管理层决定根据购买时间调整折扣。这个很简单,但需要你改动一点代码。

view sourceprint?
1
class
ShoppingCart {
2
public
void
Process() {
3
int
magicDiscount = 5;
4
if
(DateTime.Now.Hour<12) {
5
magicDiscount = 10;
6
}
7
}
8
}
  接下来一段时间里,管理层又反复添加更多的折扣逻辑。这时你就会在心理抱怨“受够了”。那么我该怎么做才能把这些无聊的逻辑从我的代码中剥离出去,让该处理的人去处理呢?这时你要做的是移交或者委派给相应职能的别人。幸运的是,.NET为此提供了一种叫做“委托”的机制。

委托

  如果你有C/C++编程背景,描述委托最好的方法是“函数指针”。对所有人来说,可以认为把委托传递给方法与把值或对象传递给方法一样。比如下面三行代码就表现出一样的基本原则:你在传递数据给Process处理而不是你自己使用。

view sourceprint?
1
// 给方法Process传递一个整形值
2
Process(5 );
3
// 给方法Process传递一个ArrayList的引用
4
Process(
new
ArrayList() );
5
// 给方法Process传递一个方法的引用
6
Process(discountDelegate);
  DiscountDelegate是什么?我如何创建?Process方法如何使用?首先如同声明一个类一样,声明一个委托类型。

view sourceprint?
1
delegate
int
DiscountDelegate();
  这句话的意思是我们有一个叫DiscountDelegate的委托类型,我们可以像使用类,结构体等一样使用它。它不需要数据参数,但返回一个整数值。像类一样,我们必须创建一个它的实例它才有意义。记住,创建一个委托实例实质上是创建一个方法的引用。创建实例时关键是要明白DiscountDelegate没有任何构造器,它有一个隐式的构造函数来构造一个与它相同签名的方法(没有传入参数,返回一个整数)。那你怎么给这个构造函数一个方法呢?.NET向你提供了一个向它名字一样简单的方法,你所做的只是忽略圆括号。
view sourceprint?
[code]1
DiscountDelegate discount =
new
DiscountDelegate(
class
.method);
[/code]
  在深入之前,先回到开始的例子,整理一个代码。我们会添加一个Calculator类来帮助我们处理折扣逻辑,并给我们的委托提供一些方法。view sourceprint?
[code]01
delegate
int
DiscountDelegate(); 
02
 
03
class
Program {
04
static
void
Main(
string
[] args) {
05
Calculator calc =
new
Calculator(); 
06
DiscountDelegate discount =
null
; 
07
if
(DateTime.Now.Hour <12) {
08
discount =
new
DiscountDelegate(calc.Morning); 
09
}
10
else
if
(DateTime.Now.Hour <20) {
11
discount =
new
DiscountDelegate(calc.Afternoon); 
12
}
13
else
{
14
discount =
new
DiscountDelegate(calc.Night); 
15
}
16
new
ShoppingCart().Process(discount); 
17
}
18
}
19
class
Calculator {
20
public
int
Morning() {
21
return
5; 
22
}
23
public
int
Afternoon() {
24
return
10; 
25
}
26
public
int
Night() {
27
return
15; 
28
}
29
}
30
class
ShoppingCart {
31
public
void
Process(DiscountDelegate discount) {
32
int
magicDiscount = discount(); 
33
// ...
34
}
35
}
[/code]
  正如你所见,在Calculator类中,我们为每个逻辑分支创建了一个方法。在Main方法中,我们创建一个Calculator实例和一个DiscountDelegate实例,并按照我们所期望的把它们整合在一起。

  太棒了,我们不用担心Process方法中的逻辑了,我们只需要简单得回调我们定义的委托。记住!我们不关心委托是如何创建的(或什么时间),我们就像调用其他方法一样调用它。如你所见,另一种理解委托的方法是,它延迟执行一个方法。Calculator方法在过去某个时间本选择,但不会执行,直到我们调用discount()的时候。现在看看我们的解决方案,这里仍然存在一些丑陋的代码。在Calculator类中,我们可以用一个不同的方法来返回替代每个有返回值得方法吗?答案是肯定的,让我们把这些乱糟糟的代码合并起来。

view sourceprint?
[code]01
delegate
int
DiscountDelegate(); 
02
 
03
class
Program {
04
static
void
Main(
string
[] args) {
05
new
ShoppingCart().Process(
new
DiscountDelegate(Calculator.Calculate)); 
06
}
07
}
08
class
Calculator {
09
public
static
int
Calculate() {
10
int
discount =0; 
11
if
(DateTime.Now.Hour <12) {
12
discount =5; 
13
}
14
else
if
(DateTime.Now.Hour <20) {
15
discount =10; 
16
}
17
else
{
18
discount =15; 
19
}
20
return
discount; 
21
}
22
}
23
class
ShoppingCart {
24
public
void
Process(DiscountDelegate discount) {
25
int
magicDiscount = discount(); 
26
 
// ...
27
}
28
}
  这样子看起来更好点。你会注意到我们用一个静态的Calculate方法替换了所有原来的方法,在Main方法中也不用费心维护一个指向DiscountDelegate的引用。现在你明白了所有关于委托的东西了吗?在2004年.NET1.1中可以这么说,但是很不幸的是,这种框架自那以后更加成熟了。

灯光,镜头,开始 或者我们需要Func!

  微软在.NET 2.0中引入了泛型,并提供了一个泛型委托:Action<T>。老实说,我认为它远不够用。后来在.NET 3.5中,它为我们提供了一些我们不想定义的通用委托。他们扩展了Action,并添加了Func,二者唯一区别在于Func型方法有一个返回值而Action型方法没有。

  这意味着我们不需要声明自己的DiscountDelegate,可以用Func<int>替代。为说明这些观点是如何工作的,我们来假设管理层又一次改变了我们的逻辑,我们需要提供一些特殊的折扣。很简单,我们将给Calculate方法传入一个bool型值。

  现在我们的委托签名变成Func<bool,int>。注意Calculate方法现在包含一个bool型参数,我们用一个bool值调用discount()。

view sourceprint?
01
class
Program {
02
static
void
Main(
string
[] args) {
03
new
ShoppingCart().Process(
new
Func<
bool
,
int
>(Calculator.Calculate)); 
04
}
05
}
06
 
07
class
Calculator {
08
public
static
int
Calculate(
bool
special) {
09
int
discount =0; 
10
if
(DateTime.Now.Hour <12) {
11
discount =5; 
12
}
13
else
if
(DateTime.Now.Hour <20) {
14
discount =10; 
15
}
16
else
if
(special) {
17
discount =20; 
18
}
19
else
{
20
discount =15; 
21
}
22
return
discount; 
23
}
24
}
25
 
26
class
ShoppingCart {
27
public
void
Process(Func<
bool
,
int
>discount) {
28
int
magicDiscount = discount(
false
); 
29
int
magicDiscount2 = discount(
true
); 
30
}
31
}
  好像还算不错,我们又省了一行代码,这样算结束了吗?当然没有,我们甚至能省掉类型判断。只要我们传递的方法有严格签名的委托,.NET允许我们完全忽略掉显式创建Func<bool,int>。

view sourceprint?
1
//因为Process期望的方法有一个bool型输入参数和返回一个int值,所以下面这句话是正确的
2
new
ShoppingCart().Process(Calculator.Calculate);
  至此,首先通过忽略自定义委托,我们省略了代码;然后排出了明确的创建Func委托。我们能继续压缩代码行吗?到此我们才完成此文的一半,答案显然是“能”。

[/code]
匿名方法
  匿名方法能够让你声明一个方法体而不需要给它指定一个名字。在接下来的场景里,它们以一个“普通的”方法存在;但是在你的代码中没有任何方法显式调用它。匿名方法只能在使用委托的时候创建,事实上,它们通过delegate关键字创建。

view sourceprint?
[code]1
class
Program {
2
static
void
Main(
string
[] args) {
3
new
ShoppingCart().Process(
4
new
Func<
bool
,
int
>(
delegate
(
bool
x) {
return
x ? 10 : 5;}
5
)); 
6
}
7
}
  正如你所见,我们完全删除了Calculator类的需求。你可以在打括号中添加任何其他方法中的逻辑。如果你在看它如何执行时有困难,那就把delegate(bool x)做为一个方法签名,而不是一个关键字。设想这段代码在一个类里,delegate(bool x){return 5;}是一个完整的合法方法声明(我们确实有一个返回值),恰好delegate是一个保留字,在这里,它让这个方法匿名。

  至此,我确信现在你知道这里我们甚至能压缩更多的代码。顺利成章的,我们能忽略显式声明Func委托的需要;.NET让我们使用delegate关键字更方便。

view sourceprint?
1
class
Program {
2
static
void
Main(
string
[] args) {
3
new
ShoppingCart().Process(
4
delegate
(
bool
x) {
return
x ? 10 : 5;}
5
); 
6
}
7
}
  当把.NET方法做为委托参数时或处理时间时,就能看到匿名方法的真正用处。之前,你会为你所关注的所有可能行为创建了一个方法,现在你仅需以内联的方式创建它们,并可以避免污染你的命名空间。

view sourceprint?
1
// 创建一个匿名比对方法
2
custs.Sort(
delegate
(Customer c1,Customer c2) {
3
return
Comparer<
int
>.Default.Compare(c1.ID,c2.ID); 
4
}); 
5
 
6
//创建一个匿名事件 
7
button1.Click +=
delegate
(
object
o,EventArgs e) { MessageBox.Show(
"Click!"
);};
Lambda 表达式

  MSDN中写道:“Lambda 表达式”是一个匿名函数,它可以包含表达式和语句,并且可用于创建委托或表达式树类型。你应当明白“用户创建委托”部分,但什么是“表达式”呢?老实说,表达式和表达式树不在此为讨论范围内。现在我们唯一需要明白的是,表达式是.NET程序运行时表示数据或对象的代码(C#代码)。引用Jon Skeet的话:“表达式树是一种表达逻辑,这样其他的代码可以查询的方法。当一个lambda表达式转换成一个表达式树,编译器不会发出了lambda表达式的白细胞介素,它会发出白细胞介素这将会建立一个表达式树表示相同的逻辑。”

  我们需要关注的是Lambda表达式替换匿名方法,和其他的特性。回顾我们最后例子,我们已经在一行代码里压缩了处理整个折扣算法的逻辑。

view sourceprint?
1
class
Program {
2
static
void
Main(
string
[] args) {
3
new
ShoppingCart().Process(
4
delegate
(
bool
x) {
return
x ? 10 : 5;}
5
); 
6
}
7
}
  你相信我们能让这个更短吗?Lambda表达式用'=>'运算符表明什么参数传递给表达式。编译器进一步处理,允许我们忽略类型并自动替我们推断这些类型。如果你有2个或更多个参数,你需要用圆括号:(x,y)=>。如果只有一个,你设置不需要这样:x=>。

view sourceprint?
1
static
void
Main(
string
[] args) {
2
Func<
bool
,
int
>del = x =>x ? 10 : 5; 
3
new
ShoppingCart().Process(del); 
4
}
5
// 更短啦...
6
static
void
Main(
string
[] args) {
7
new
ShoppingCart().Process(x =>x ? 10 : 5); 
8
}
  就是这样子。x被推断为bool型,并且有返回值,因为Process接收一个Func<bool,int>。如果我们想实现像之前那样的完整代码块,我们只需要加上大括号。

view sourceprint?
01
static
void
Main(
string
[] args) {
02
new
ShoppingCart().Process(x =>{
03
int
discount =0; 
04
if
(DateTime.Now.Hour <12) {
05
discount =5; 
06
}
07
else
if
(DateTime.Now.Hour <20) {
08
discount =10; 
09
}
10
else
if
(x){
11
discount =20; 
12
}
13
else
{
14
 
discount =15; 
15
}
16
return
discount; 
17
}); 
18
}
写在最后

  使用与不使用大括号有一个重要的不同。当你用时,你创建一个“语句Lambda”,反之,它是"表达Lambda"。语句Lambda能执行多条语句(因此需要大括号),但不能创建表达树。你可能只在使用IQueryable接口是遇到这个问题。下面的例子说明这个问题。

view sourceprint?
01
List<
string
>list = 
new
List<
string
>(); 
02
IQueryable<
string
>query = list.AsQueryable(); 
03
list.Add(
"one"
); 
04
list.Add(
"two"
); 
05
list.Add(
"three"
); 
06
 
07
string
foo = list.First(x =>x.EndsWith(
"o"
)); 
08
string
bar = query.First(x =>x.EndsWith(
"o"
)); 
09
// foo and bar are now both 'two' as expected
10
foo = list.First(x =>{ 
return
x.EndsWith(
"e"
);});
//no error
11
bar = query.First(x =>{ 
return
x.EndsWith(
"e"
);});
//error
12
bar = query.First((Func<
string
,
bool
>)(x =>{ 
return
x.EndsWith(
"e"
);}));
//no error
  倒数第二行在编译时失败。这是因为IQueryable.First期望得到一个表达式作为参数,然而List<T>.First期望得到一个委托。你可以按照最后一行强制转换Lambda到一个委托(使用First的方法重载)。

  这里很难结束讨论,但是我觉得必须停止。Lambda大体上分为两类:一类创建匿名方法和委托;另一类创建表达式。表达式自成一体,并不是.NET开发者的必备知识(无疑在LINQ中已有实现)。

备注:

  此文为CodeProject上的同名文章<C# Delegates,Anonymous Methods,and Lambda Expressions>,个人觉得非常好,所以就翻一下给不想看英文的同学。由于能力有限,翻译的不好,望大家多多包涵。

[/code]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: