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

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

2010-10-07 22:06 323 查看

简介

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

什么是委托?

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

class Program {
static void Main(string[] args)  {
new ShoppingCart().Process();
}
}
class ShoppingCart {
public void Process() {
int magicDiscount = 5;
// ...
}
}


  第二天,异想天开的管理层决定根据购买时间调整折扣。这个很简单,但需要你改动一点代码。

// 给方法Process传递一个整形值
Process( 5 );
// 给方法Process传递一个ArrayList的引用
Process( new ArrayList() );
// 给方法Process传递一个方法的引用
Process( discountDelegate );


  DiscountDelegate是什么?我如何创建?Process方法如何使用?首先如同声明一个类一样,声明一个委托类型。

  这句话的意思是我们有一个叫DiscountDelegate的委托类型,我们可以像使用类,结构体等一样使用它。它不需要数据参数,但返回一个整数值。像类一样,我们必须创建一个它的实例它才有意义。记住,创建一个委托实例实质上是创建一个方法的引用。创建实例时关键是要明白DiscountDelegate没有任何构造器,它有一个隐式的构造函数来构造一个与它相同签名的方法(没有传入参数,返回一个整数)。那你怎么给这个构造函数一个方法呢?.NET向你提供了一个向它名字一样简单的方法,你所做的只是忽略圆括号。

  在深入之前,先回到开始的例子,整理一个代码。我们会添加一个Calculator类来帮助我们处理折扣逻辑,并给我们的委托提供一些方法。delegate int DiscountDelegate();

class Program {
static void Main(string[] args) {
Calculator calc = new Calculator();
DiscountDelegate discount = null;
if (DateTime.Now.Hour < 12) {
discount = new DiscountDelegate(calc.Morning);
}
else if (DateTime.Now.Hour < 20) {
discount = new DiscountDelegate(calc.Afternoon);
}
else {
discount = new DiscountDelegate(calc.Night);
}
new ShoppingCart().Process(discount);
}
}
class Calculator {
public int Morning() {
return 5;
}
public int Afternoon() {
return 10;
}
public int Night() {
return 15;
}
}
class ShoppingCart {
public void Process(DiscountDelegate discount) {
int magicDiscount = discount();
// ...
}
}


[/code]
  正如你所见,在Calculator类中,我们为每个逻辑分支创建了一个方法。在Main方法中,我们创建一个Calculator实例和一个DiscountDelegate实例,并按照我们所期望的把它们整合在一起。

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

class Program {
static void Main(string[] args) {
new ShoppingCart().Process(new Func<bool, int>(Calculator.Calculate));
}
}

class Calculator {
public static int Calculate(bool special) {
int discount = 0;
if (DateTime.Now.Hour < 12) {
discount = 5;
}
else if (DateTime.Now.Hour < 20) {
discount = 10;
}
else if (special) {
discount = 20;
}
else {
discount = 15;
}
return discount;
}
}

class ShoppingCart {
public void Process(Func<bool,int> discount) {
int magicDiscount = discount(false);
int magicDiscount2 = discount(true);
}
}


  好像还算不错,我们又省了一行代码,这样算结束了吗?当然没有,我们甚至能省掉类型判断。只要我们传递的方法有严格签名的委托,.NET允许我们完全忽略掉显式创建Func<bool,int>。

 匿名方法

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

 args)     {
new ShoppingCart().Process(
new Func<]
class Program {
static void Main(string[] args) {
new ShoppingCart().Process(
delegate(bool x) { return x ? 10 : 5; }
);
}
}


  当把.NET方法做为委托参数时或处理时间时,就能看到匿名方法的真正用处。之前,你会为你所关注的所有可能行为创建了一个方法,现在你仅需以内联的方式创建它们,并可以避免污染你的命名空间。


Lambda 表达式

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

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

class Program {
static void Main(string[] args) {
new ShoppingCart().Process(
delegate(bool x) { return x ? 10 : 5; }
);
}
}


  你相信我们能让这个更短吗?Lambda表达式用'=>'运算符表明什么参数传递给表达式。编译器进一步处理,允许我们忽略类型并自动替我们推断这些类型。如果你有2个或更多个参数,你需要用圆括号:(x,y)=>。如果只有一个,你设置不需要这样:x=>。

 args) {
Func<]
static void Main(string[] args) {
new ShoppingCart().Process(  x => {
int discount = 0;
if (DateTime.Now.Hour < 12) {
discount = 5;
}
else if (DateTime.Now.Hour < 20) {
discount = 10;
}
else if(x)  {
discount = 20;
}
else {
discount = 15;
}
return discount;
});
}


写在最后

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


  倒数第二行在编译时失败。这是因为IQueryable.First期望得到一个表达式作为参数,然而List<T>.First期望得到一个委托。你可以按照最后一行强制转换Lambda到一个委托(使用First的方法重载)。

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

备注:

  此文为CodeProject上的同名文章<C# Delegates,Anonymous Methods, and Lambda Expressions>,个人觉得非常好,所以就翻一下给不想看英文的同学。由于能力有限,翻译的不好,望大家多多包涵。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: