C#委托,匿名方法和Lambda表达式(译)
2010-02-09 13:05
267 查看
在.net中,委托,匿名方法和Lambda表达式是三个很容易让人混淆的概念.以下代码或许可见一斑:对First的调用中,哪些(个)会被编译?哪些(个)将会返回我们所期待的答案?(ID号为5的Customer).事实上,答案就是:所有的6种方法不令都将编译,而且它们都能够返回正常的customer,它们在功能上是相同的.如果你还在问自己:为什么是这样呢?那么,这篇文章将为你解答.
class Customer
{
public int ID { get; set; }
public static bool Test(Customer x)
{
return x.ID == 5;
}
}
...
List custs = new List();
custs.Add(new Customer() { ID = 1 });
custs.Add(new Customer() { ID = 5 });
custs.First(new Funcbool>(delegate(Customer x) { return x.ID == 5; }));
custs.First(new Funcbool>((Customer x) => x.ID == 5));
custs.First(delegate(Customer x) { return x.ID == 5; });
custs.First((Customer x) => x.ID == 5);
custs.First(x => x.ID == 5);
custs.First(Customer.Test);
然而,第二天,精明的经理又决定要根据一天当中的时间来打折.呵呵,这也不难,你只需要简单的改变一下代码就可以了:
接下来几天里,经理一再增加或修改折扣的计算方法.晕倒!我怎么才能处理和维护这近似荒谬的逻辑呀?其实,你所需要做的只是"移交",或者称之为"委托"责任给别人就可以了.在.net中,就有这么一种机制,我不说你也一定猜到了,那就是"委托".
那么,上面的
这名代码让我们拥有了一个
在进一步探讨之前,让我们回过头来看看之前的例子.我们将增加一个
正如我们所看到的,我们在
到目前为止,我们不再为
这下好啦,我们做到啦.静态的
也就是说,我们不再需要去声明
这样,我们的原本无参委托就变成了
这主意不错,我们又节省了几行代码,不是吗?当然不是,如果利用类型接口,我们还可以节省更多的代码和时间..net允许我们完全省略
基于这一点,我们通过省略自定义委托并在随后略去Func委托的直接创建,又省下了不少代码.还有什么方法可以减少代码量的吗?当然有,因为我们才说了本文的一半.
从上面我们可以看到:我们完全不用
好,我相信到目前为止,我们可以省略更多的代码啦.自然的,我们可以忽略对Func委托的显示声明,当我们使用delegate关键字的时候,.net会替我们处理好一切.
在.net中,当方法期望一个委托作为参数并响应事件的时候,才能看见匿名方法的真正威力.以前,我们必须为每一个可能的动作创建方法.而现在,我们只需要一行语句就搞定了,而且不会"污染"命名空间.
你相信我们还能让它变得更简短一些吗?Lambda 表达式使用"=>"符号来代表"参数被传递到表达式中",编译器遇到这个符号的时候会允许我们忽略参数的类型,并且为我们推断出类型来.如果有两个或两个以上的参数,可以使用括号:
对,就是这样的.x被推断成boolean,返回值类型也一样.因为
第二次计算bar值的时候发生了编辑时错误.这是因为
写到这里,其实还有很多未言的东西,但是我想我必须要结束本文了.lambda 基本上被分为两类:一类是创建匿名方法和委托,另一类是创建表达式.
<完>
class Customer
{
public int ID { get; set; }
public static bool Test(Customer x)
{
return x.ID == 5;
}
}
...
List custs = new List();
custs.Add(new Customer() { ID = 1 });
custs.Add(new Customer() { ID = 5 });
custs.First(new Funcbool>(delegate(Customer x) { return x.ID == 5; }));
custs.First(new Funcbool>((Customer x) => x.ID == 5));
custs.First(delegate(Customer x) { return x.ID == 5; });
custs.First((Customer x) => x.ID == 5);
custs.First(x => x.ID == 5);
custs.First(Customer.Test);
一,什么是委托?
举例来说吧,假如有一个购物篮类(Shoppingcart)用于处理顾客(Customer)的订单(Order).经理决定对超过一定金额或购买量等的顾客打折.于是,你必须使用他们制订的策略来计算订单.这并不是什么难事:你简单的声明了一个变量来保存折扣将在计算订单金额时调用它.class Program { static void Main(string[] args) { new ShoppingCart().Process(); } } class ShoppingCart { public void Process() { int magicDiscount = 5; // ... } }
然而,第二天,精明的经理又决定要根据一天当中的时间来打折.呵呵,这也不难,你只需要简单的改变一下代码就可以了:
class ShoppingCart { public void Process() { int magicDiscount = 5; if (DateTime.Now.Hour < 12) { magicDiscount = 10; } } }
接下来几天里,经理一再增加或修改折扣的计算方法.晕倒!我怎么才能处理和维护这近似荒谬的逻辑呀?其实,你所需要做的只是"移交",或者称之为"委托"责任给别人就可以了.在.net中,就有这么一种机制,我不说你也一定猜到了,那就是"委托".
二,委托
如果大家有C/C++背景的话,那么最佳描述委托的就是函数指针.而对于一般人来说,可以认为它是一种把方法像普通参数一样传递的方式.例如,以下三行代码体现了同样的基本原则:向Process方法传递一条数据,而不使用它.
// passing an integer value for the Process method to use Process( 5 ); // passing a reference to an ArrayList object for the Process method to use Process( new ArrayList() ); // passing a method reference for the Process method to call Process( discountDelegate );
那么,上面的
discountDelegate是什么呢?我们该怎么去创建它呢?Process方法又是怎样使用它的呢?首先,我们需要做的是声明一个delegate类型就像我们声明一个类一样.
delegate int DiscountDelegate();
这名代码让我们拥有了一个
DiscountDelegate委托,我们可以像类和结构一样使用它.这个委托没有参数,只有一个int型的返回值.和类一样,在创建它的任何实例之前,我们不能使用它.在实例化委托的时候,需要特别注意:委托只是某方法的一个引用而已,即使DiscountDelegate没有任何构造函数,当实例化的时候,会有一个隐藏的构造函数(无参数但返回int值).怎样给这个构造函数一个方法呢?OK,在.net中,我们只需要简单的输入想要调用的方法的名称就可以了,省略了方法名后面的括号.
[code]DiscountDelegate discount = new DiscountDelegate(class.method);
在进一步探讨之前,让我们回过头来看看之前的例子.我们将增加一个
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(); // ... } }
正如我们所看到的,我们在
Calculator类中为每一个折扣的计算逻辑创建了一个方法.在Main方法中,我们为
Calculator类和DiscountDelegate委拖分别创建了一个实例,它们将共同为我们即将调用的目标方法服务.
到目前为止,我们不再为
Process方法里的计算逻辑而担忧啦,我们可以简单的调用委托来完成.不过要记住:我们并不关心这个委托是如何,何时创建的.我们只是在需要它的时候像调用其它方法一样调用它.可见,委托也可以理解为延缓方法执行.真正的计算方法是在我们调用
discount()方法的时候才得以执行.再看看上面的代码,还有一些重复的地方.在Calculator类中,我们是不是可以用一个方法来提供所有返回值?当然可以,那就让我们一起来改进吧.
delegate int DiscountDelegate(); class Program { static void Main(string[] args) { new ShoppingCart().Process(new DiscountDelegate(Calculator.Calculate)); } } class Calculator { public static int Calculate() { int discount = 0; if (DateTime.Now.Hour < 12) { discount = 5; } else if (DateTime.Now.Hour < 20) { discount = 10; } else { discount = 15; } return discount; } } class ShoppingCart { public void Process(DiscountDelegate discount) { int magicDiscount = discount(); // ... } }
这下好啦,我们做到啦.静态的
Calculate方法让类变得清爽了很多.Main方法也不再有那么多的对
DiscountDelegate的引用啦.好,现在,我们已经对委托有所了解了.
三,我们需要这样的功能!
在.NET 2.0中,引入了泛型的概念.MS小心翼翼的通过提供Action类来迈向泛型委拖.然后,我认为,一段时间之后,它被我们大多数人所遗忘了.后来,到了3.5,MS很友好的为我们预定义了一些常见的委托来使用,所以我们不再需要不断的定义是我们自己的委托啦.他们还扩展了Action并增加了Func.Action和Func唯一的不同就是前者没返回值而后者有.
也就是说,我们不再需要去声明
DiscountDelegate,我们可以使用
Func来代替它.为们演示参数的工作方法,让我们假设经理再一次改变了折扣的计算逻辑:现在,我们需要记录一个特别的折扣.这也是小菜一碟,我们只需要在
Calculate方法中调用一个boolean类型的值就可以啦.
这样,我们的原本无参委托就变成了
Func的形式了.注意,现在的的时候要带上它.[/code]Calculate方法有一个boolean类型的参数,我们在调用[code]discount
class Program { static void Main(string[] args) { new ShoppingCart().Process(new Func(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 discount) { int magicDiscount = discount(false); int magicDiscount2 = discount(true); } }
这主意不错,我们又节省了几行代码,不是吗?当然不是,如果利用类型接口,我们还可以节省更多的代码和时间..net允许我们完全省略
Func的创建过程,只要这个方法具有和我们期望的委托同样的参数和返回值.
// works because Process expects a method that takes a bool and returns int new ShoppingCart().Process(Calculator.Calculate);
基于这一点,我们通过省略自定义委托并在随后略去Func委托的直接创建,又省下了不少代码.还有什么方法可以减少代码量的吗?当然有,因为我们才说了本文的一半.
四,匿名方法
匿名方法允许我们声明一个没有名字的方法.在后台,有一个名叫'normal'的方法.然而,在代码中我们不能显式的调用这个方法,匿名方法只能在使用委托,并且使用Delegate关键字创建时创建和使用.如:class Program { static void Main(string[] args) { new ShoppingCart().Process( new Func(delegate(bool x) { return x ? 10 : 5; } )); } }
从上面我们可以看到:我们完全不用
Calculator类.你可以输入更多的小逻辑到花括号的分枝中,就像在其它方法中使用的一样.如果你觉得难以理解这段代码的工作过程,就请假设声明
delegate(bool x)是一个方法的签名而不是一个委托的关键字.把这段代码放到一个类中,
delegate(bool x) { return 5; }是一个特别的逻辑计算的方法的声明(本应有返回值类型的).这方面来理解,就好办啦.
delegate只是使原本方法的名称隐藏起来了而已.
好,我相信到目前为止,我们可以省略更多的代码啦.自然的,我们可以忽略对Func委托的显示声明,当我们使用delegate关键字的时候,.net会替我们处理好一切.
class Program { static void Main(string[] args) { new ShoppingCart().Process( delegate(bool x) { return x ? 10 : 5; } ); } }
在.net中,当方法期望一个委托作为参数并响应事件的时候,才能看见匿名方法的真正威力.以前,我们必须为每一个可能的动作创建方法.而现在,我们只需要一行语句就搞定了,而且不会"污染"命名空间.
// creates an anonymous comparer custs.Sort(delegate(Customer c1, Customer c2) { return Comparer.Default.Compare(c1.ID, c2.ID); }); // creates an anonymous event handler button1.Click += delegate(object o, EventArgs e) { MessageBox.Show("Click!"); };
五,Lambda表达式
(引自MSDN)Lambda表达式是一个能够包含表达式和语句,并且能够用来创建委托或表达式树类型的匿名函数.我们需要理解"用来创建委托"部分,Lambda表达式到底扮演着怎么的着色呢?好,其实上,表达式和表达式树已超出了本方的阐述范围.我们只需要知道:表达式就是.net程序中所谓的数据或对象本身的代码而已.表达式树是一种其它代码可以"询问"的表达逻辑的方式.当lambda 表达式被转换成表达式树时,编辑器并没生成新的IL代码,表达式树和lambda 表达式代表了相同的逻辑.
我们所需要关注的是:用lambda 表达式代替匿名方法并增加新的特性.回顾我们最后的那个例子,我们已经在最初创建的基础上去掉了很多代码,将把折扣计算逻辑写到了一行上.class Program { static void Main(string[] args) { new ShoppingCart().Process( delegate(bool x) { return x ? 10 : 5; } ); } }
你相信我们还能让它变得更简短一些吗?Lambda 表达式使用"=>"符号来代表"参数被传递到表达式中",编译器遇到这个符号的时候会允许我们忽略参数的类型,并且为我们推断出类型来.如果有两个或两个以上的参数,可以使用括号:
(x,y) =>. 如果只有一个,那就这样:
x =>.
static void Main(string[] args) { Funcdel = x => x ? 10 : 5; new ShoppingCart().Process(del); } // even shorter... static void Main(string[] args) { new ShoppingCart().Process(x => x ? 10 : 5); }
对,就是这样的.x被推断成boolean,返回值类型也一样.因为
Process有一个
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",前者可以根据分枝需要执行多行语句,但不能创建表达式树.当我们在使用IQueryable接口的时候,有可能会陷入这个问题中, 下面的例子展示了这一问题"
Listlist = new List (); IQueryable query = list.AsQueryable(); list.Add("one"); list.Add("two"); list.Add("three"); string foo = list.First(x => x.EndsWith("o")); string bar = query.First(x => x.EndsWith("o")); // foo and bar are now both 'two' as expected foo = list.First(x => { return x.EndsWith("e"); }); //no error bar = query.First(x => { return x.EndsWith("e"); }); //error bar = query.First((Func )(x => { return x.EndsWith("e"); })); //no error
第二次计算bar值的时候发生了编辑时错误.这是因为
IQueryable.First期望一个表达式做为参数,然而,表达式方法
List.First则期望一个委托.你可以强制将lambda 表达式的值转换为delegate,像上面第三次一样.
写到这里,其实还有很多未言的东西,但是我想我必须要结束本文了.lambda 基本上被分为两类:一类是创建匿名方法和委托,另一类是创建表达式.
七,总结
希望这编文章可以达到解答最前面的六个易混淆的有关委托,匿名方法和lambda表达式调用问题的目标.<完>
相关文章推荐
- C#委托,匿名方法和Lambda表达式(译)
- C#委托,匿名方法和Lambda表达式(译)
- C# 匿名方法和Lambda表达式
- C#中新特性的学习:Delegate、匿名方法、lambda表达式
- 委托中的匿名方法和lambda表达式
- C#委托基础7——匿名方法
- (转)C#中的委托,匿名方法和Lambda表达式
- C#学习笔记18-匿名方法和Lambda表达式
- C#匿名函数之匿名方法与lambda表达式
- C#语法小知识(十二)匿名方法与Lambda表达式
- 委托和匿名方法、lambda表达式
- C# 匿名方法和Lambda表达式
- Lambda表达式的演化,委托-匿名方法-Func-Lambda
- C#委托------匿名方法
- C#特性之匿名方法和Lambda表达式
- 匿名方法的终结者——Lambda表达式
- 匿名方法和Lambda表达式
- C# λ运算符=>匿名方法 lambda表达式
- 再谈C#中的委托,匿名方法和Lambda表达式
- 匿名方法和lambda表达式