匿名方法和Lambda表达式

匿名方法本质上是一传递给委托的代码块,是使用委托的另一种方法。

规则:

1、匿名方法中不能使用跳转语句跳至次匿名方法的外部,反之亦然;匿名方法外部的跳转语句也不能跳转到匿名方法的内部;

2、在匿名方法的内部不能访问不安全的代码。另外,也不能访问在匿名方法外部定义的ref和out参数。

3、可以使用在匿名方法外部定义的其他变量。

Lambda达式:就是匿名方法,只是语法不同。

(param)=>expr

param是输入参数列表,expr是一个表达式或者一系列语句。

规则:

1、在一个具有唯一的显示类型参数的Lambda表达式中,圆括号可以从参数列表中删除。

2、当输入参数不唯一时,括号不能省略。

3、输入参数列表中的各参数可以显式指定类型,也可以省略参数类型,具体类型通过类型判断机制判断。

 

4、expr可以只包含一个计算表达式,也可以包含一系列语句,只是语句需要包含在大括号中。

--------------------------------------------------------华丽的分割线-----------------------------------------------------------

在我们程序中,经常有这样一些需求:
1.       需要一个临时方法,这个方法只会使用一次,或者使用的很少。
2.       这个方法的方法体很短,以至于比方法声明都短,写起来实在没劲(我将其称之为“一句话方法”)
没办法,这样的方法写起来真是吃力不讨好,比如一些按钮事件处理中,有些按钮点击就是弹出一个对话框,或者调用一下别的什么方法。比如下面的代码:

this.btnRefresh.Click += new System.EventHandler(this.btnRefresh_Click);
private void btnRefresh_Click(object sender, EventArgs e)
{
    BindData();
}
这个”Refresh”按钮就是做一下调用一下BindData()数据绑定的方法,为此我们不得不写一个新方法。好了,C# 2.0为我们提供了匿名方法:
this.btnRefresh.Click += delegate(object sender, EventArgs e) { BindData(); };
没劲的代码没了。想知道这种写法的幕后黑手么?
其实编译器还是在我们的后面干了一件龌龊的事情:它为我们产生了一个新的方法,它只是表面上为我们节省了代码。
privatevoid<Test>b__0(object sender, EventArgs e)
{
    this.BindData();
}
看看这个编译器产生的方法的名称:
<Test>b_0Test是这个匿名方法所放置的地方(因为这个按钮的时间我是放在一个Test方法里的)
还有一点需要注意的是,如果这个匿名方法是在实例方法里使用,那么编译器为我们生成的幕后方法也是实例方法,否则就是静态方法了.
是不是觉得匿名方法这东西很不错,减少了很多代码阿,但是匿名方法的使用还并不人性化,什么是人性化呢?比如你可以用自然的语言将程序代码读出来,这样才算人性化了..net 2.0System.Collections.Generic命名空间下List<T>里有一些新增的方法。比如Find,如果使用匿名方法我们如何调用呢:
books.Find(delegate(Book book){return book.Price < 50;});
代码是很简单,但是却无法朗读出来,来看看Lambda表达式的写法:
books.Find(book=>book.Price<50);这个Lambda表达式就可以这样阅读出来了:给你一本书,如果它的价格小于50则返回true
好了,那我们就走进Lambda表达式吧:

 

    将使用了Lambda表达式的程序集反编译后,我们发现,它实际上和匿名方法没有什么不同。Lambda的输入参数就对应着delegate括号里面的参数,由于Lambda表达式可以推断参数的类型,所以这里的参数无需声明。Lambda操作符读作”Goes to”,它后面紧跟着表达式或者是语句块(这点和匿名方法也不同,匿名方法只能使用语句块而不能使用表达式),下面我就用实例来说明一下有那些类型的Lambda表达式:

//x的类型省略了,编译器可以根据上下文推断出来,后面跟着的是表达式
x => x+1 
deleage(int x){return x+1;}

//后面跟着的是语句块
x=>{return x+1;} 
delegate(int x){return x+1;}

//输入参数也可以带类型,带类型后别忘记小括号哦
(int x) => x+1 
delegate(int x){return x+1;}

//也可以多个输入参数,逗号分隔,别忘记小括号
(x,y) => x+y 
delegate(int x,int y){return x+y;}

//无参的也行
() => 1
delegate(){return 1;}

    对于Lambda表达式来说她的用法就是如此,但是在Lambda背后却有很多的故事和玄机。用Lambda表达式可以构建表达式树,而表达式树对于Linq来说就像树根对于树一样重要。在这里就不讨论表达式树的问题了,这个东西也不是三言两语能够说清楚的,等待时机成熟的时候我们再来进一步讨论。

    Lambda实际上源远流长,我们现在使用的机器都是冯-诺依曼体系的,属于图灵机,在那之前还有一种称作λ演算的理论,但是图灵机由于先被实现出来,所以大行其道,λ演算后来成就了函数式编程语言特别是Lisp,在函数式编程语言里函数是第一等元素,函数的参数,函数的返回值都是函数,程序没有变量,函数嵌套函数。而且函数式编程语言一直存在于象牙塔中,所以在工业界并没有得到通用,不过近年来工业界比较喜欢“复古”风格,所以函数式编程语言也慢慢的走上了历史的舞台。函数式编程能解决一些命令式编程难以解决的问题(或者解决起来非常麻烦)C#要做到函数风格编程怎么办?靠原来的方法定义的方式肯定是不可行的,2.0的匿名方法从某种程序上来说解决了这个问题,但还是不够,3.0里的Lambda终于很好的解决了,一个Lambda就是一个delegate,一个delegate指向一个方法,现在我们使用Lambda也能简单的将方法作为参数传递了,还可以层层嵌套,都是很简单的事情了。

    另外,匿名方法可以省略参数列表,编译器可以自动推断,lambda表达式做不到这一点。

--------------------------------------------------------华丽的分割线-----------------------------------------------------------

    匿名方法在C#中应用十分广泛,因为委托作为函数参数是件非常平常的事情。在定义简单的事件处理过程时,我们同样可以使用匿名方法。比如WCF中服务寄宿成功后绑定事件:

ServiceHost host = new ServiceHost(typeof(FileTransferImpl));
host.Opened += delegate(object sender, EventArgs e)
{
    Console.WriteLine("Service Opened.");
};

    匿名方法可以很方便地使用本地变量,这与单独定义的命名方法相比,能够简化编程。比如上文的例子中,假如Main函数里面定义了一个整型本地变量(局部变量)number,那么可以在delegate (string name)这一匿名方法定义中使用number变量。

    上文提到,在定义匿名方法的时候,连参数列表都可以省略。因为编译器可以根据委托的签名来确定函数的签名,然后只要再给函数起个名字就可以了。下面的代码演示了这种使用方式:

delegate void IntDelegate(int x);

// 带参数的定义方式   
IntDelegate d2 = delegate(int p) { Console.WriteLine(p); };
// 不带参数的定义方式(当然也没带返回值)   
IntDelegate d3 = delegate { Console.WriteLine("Hello."); };

在使用不带参数和返回值的匿名方法定义时,需要注意以下两点:

  1. 如果在你的匿名方法中需要对参数进行处理,那么你不能使用不定义参数列表的声明方式。也就是在定义匿名方法的时候,需要给出参数列表
  2. 不带参数和返回值的匿名方法,可以被具有任何形式签名的委托所指代

上述第一点显而易见,因为你没有定义参数列表,也就没有办法使用参数;要说明第二点,我们可以看下面的代码:

 1     class Program
 2     {
 3         delegate void IntDelegate(int x);
 4         delegate void StringDelegate(string y);
 5 
 6         static void Output(IntDelegate id)
 7         {
 8             //Do output
 9         }
10         static void Output(StringDelegate sd)
11         {
12             //Do output
13         }
14         static void Main(string[] args)
15         {
16             /*  
17              * ERROR: The call is ambiguous between  
18              *        Output(IntDelegate)  
19              *              and  
20              *        Output(StringDelegate)  
21              */
22             Output(delegate { });
23         }
24     }

    上面的代码没法编译通过,因为编译器不知道应该将delegate { }这一匿名方法还原为由IntDelegate指代的函数,还是还原为由StringDelegate指代的函数。此时只能显式给定参数列表,以便让编译器知道,我们究竟是想调用哪个Output函数。

 

参考文献:
http://blog.csdn.net/jfkidear/article/details/19421175
http://kb.cnblogs.com/page/42579/
http://www.cnblogs.com/daxnet/archive/2008/11/12/1687011.html

posted on 2014-12-28 12:22  熊小熊-chris  阅读(1575)  评论(0编辑  收藏

导航