C# Lambda 表达式

从** C#3.0开始,可以使用一种新的方法把实现代码赋予委托: Lambda表达式**。只要有委托参数类型的地方,就可以使用Lambda表达式。

这是前面用到的一个Lambda表达式

myTimer.Elapsed += (sender, eventArgs) =>
   {
          Console.Write(displayString[counter++ % displayString.Length]);
   };

lambda运算符=>的左边列出了需要的参数,右边定义了赋予lambda变量的方法的实现代码。

参数

lambda表达式有几种定义参数的方式。如果只有一个参数,只需写出参数名。
下面的例子使用参数s,因为委托定义了一个string参数,那么s的类型也就是string。实现代码调用String.Format()方法来返回一个字符串,在调用这个委托时,字符串就打印出来。

Func<string,string> oneparam = s => String.Format("变为大写  {0}", s.ToUpper());
Console.WriteLine(oneparam("text"));

如果委托有多个参数,就把参数名放在()里面。例如:

Func<double,double,double> twoparams = (x, y) => x * y;
Console.WriteLine(twoparams(3, 2));

这里x,y的类型是double,由Func<double,double,double>委托定义.

上面的例子里都没有给出参数的类型,你可以给变量名添加参数类型。如果编译器不能匹配重载后的版本,那么使用参数类型可以帮助找到匹配的委托:

Func<double,double,double> twoparams = (double x, double y) => x * y;
Console.WriteLine(twoparams(3, 2));

多行代码

如果lambda表达式只有一条语句,在方法块内就不需要{}return语句,编译器会隐式添加一条return
Func<double, double> squre = x => x * x;
添加{}return可以让代码更加易读。

Func<double, double> squre = x => {
    return x * x;
    }

但是如果要在lambda表达式中添加更多语句,就必须使用{}return

Func<string, string> lambda = param =>{
    param += mid;
    param += "and this was added to the string.";
    return param;
    };

闭包

通过lambda表达式可以访问表达式块外部的变量。这称为闭包。但使用时需要注意。

int val = 5;
Func<int, int> f = x => x + val;

Func<int, int>类型的lambda表达式需要一个int参数,返回一个int,代码访问了外部的val变量。调用的结果应该是x+5,但是实际上会更复杂一些。
要是在以后会修改val的值,再次调用这个lambda表达式时,会使用val的新值。
如果有一个线程调用这个lambda表达式,我们可能就会不知道结果到底是多少。
对于表达式x => x + val编译器会创建一个匿名类,有一个构造方法来接收参数,另一个方法实现并返回结果。

foreach的闭包

针对闭包,C#5.0中的foreach语句有了很大改变。

var values = new List<int>(){ 10, 20, 30};
var funcs = new List<Func<int>>();
foreach (var val in values){
    funcs.Add(() => val);
    }
foreach (var f in funcs){
    Console.WriteLine((f()));
}

这段代码funcs泛型列表中添加lambda表达式,第二条foreach语句迭代输出列表中引用的每个函数。其实每个函数都返回一个List<int>列表中的数字。

C#4.0或更早的版本中,会输出30三次,而不是迭代时获得的val变量。这个foreach的内部实现有关。编译器会从foreach语句创建一个while循环。在C#4.0中,编译器在while循环外部定义循环变量,每次迭代中重用这个变量。因此,在循环结束时,该变量的值是最后一次迭代的值。要在C#4.0中得到我们希望的结果需要在第一个foreach做如下操作:

var v = val;
funcs.Add(() => v);

C#5.0中不需要再这样,代码会修改为局部变量。

Lambda表达式用于匿名方法

Lambda表达式是简化匿名方法的一种方式。本文就是以这个lambda表达式开始的。

编译器会提取这个lambda表达式,创建一个匿名方法,工作方式匿名方法相同。其实它会被编译成相同或相似的CIL代码。

下面举一个书上的栗子,我有扩展。
这是一个委托定义,表示一个方法,有两个int参数,返回一个int结果。
private delegate int twoparams(int p1, int p2);
这是一个以上面委托为参数的方法。

         static void Perform(twoparams tdel)
        {
            for (int i = 0; i < 5; i++)
            {
                for (int j = 0; j < 5; j++)
                {
                    int result = tdel(i, j);
                    Console.Write("f({0},{1})={2}", i, j, result);
                    if (j != 5)
                        Console.Write(" ,");
                }
                Console.WriteLine();
            }

        }

可以给这个方法传一个委托实例,也可以是匿名方法lambda表达式
为什么可以是匿名方法lambda表达式?这是因为这些结构都会被编译为委托实例
这个方法会用一组值调用委托实例所表示的方法,并把参数输出。

下面我创建一个方法来调用作为示例。

        static void Show()
        {
            twoparams test;
            test = Tdel;
            Console.WriteLine("a+b");
            Perform(((p1, p2) => p1 + p2));
            Console.WriteLine("a*b");
            Perform((
                delegate (int p1, int p2){ return p1 * p2; }
                ));
            Console.WriteLine("2*a*b");
            Perform(Tdel);
            Console.WriteLine("2*a*b-22222");//22222纯属为了方便区分,在IL中查看
            Perform(test);
        }

        private static int Tdel(int p1, int p2)
        {
            return p1*p2*2;
        }

这里用了4种方式来调用:

  1. Perform(((p1, p2) => p1 + p2));使用lambda表达式;
  2. Perform((delegate (int p1, int p2){ return p1 * p2; }));使用匿名函数;
  3. Perform(Tdel);给方法传递一个匹配委托的方法,似乎一个方法不是一个委托,但因为其满足委托的签名,是可行的,编译器同样可以将其编译成一个委托实例
  4. Perform(test);这是这个实例中唯一一个满足方法参数的,twoparams test;创建一个委托实例,并给它提供一个方法test = Tdel;

主函数中运行一下,得到如下结果:


运行结果

用4种方法调用均可行,得到预期结果。为了验证它会被编译成相同或相似的CIL代码,我们来看看Show这个方法的中间代码。

  private static void Show()
{
    Program.twoparams tdel = new Program.twoparams(Program.Tdel);
    Console.WriteLine("a+b");
    Program.twoparams arg_38_0;
    if ((arg_38_0 = Program.<>c.<>9__4_0) == null)
    {
        arg_38_0 = (Program.<>c.<>9__4_0 = new Program.twoparams(Program.<>c.<>9.<Show>b__4_0));
    }
    Program.Perform(arg_38_0);
    Console.WriteLine("a*b");
    Program.twoparams arg_68_0;
    if ((arg_68_0 = Program.<>c.<>9__4_1) == null)
    {
        arg_68_0 = (Program.<>c.<>9__4_1 = new Program.twoparams(Program.<>c.<>9.<Show>b__4_1));
    }
    Program.Perform(arg_68_0);
    Console.WriteLine("2*a*b");
    Program.Perform(new Program.twoparams(Program.Tdel));
    Console.WriteLine("2*a*b-22222");
    Program.Perform(tdel);
}

可以看到,使用lambda表达式和使用匿名方法得到的中间代码非常像。最终还是给方法传递了一个twoparams的委托参数。而给一个符合委托参数的方法作为参数得到的中间代码Program.Perform(new Program.twoparams(Program.Tdel));同样如此,可以看到,我们给的是方法作为参数,编译器编译成为委托,并且为这个委托指定了我们给的方法名。一切仿佛都变清楚了。

推荐阅读更多精彩内容

  • C++ lambda表达式与函数对象 lambda表达式是C++11中引入的一项新技术,利用lambda表达式可以...
    小白将阅读 76,120评论 13 112
  • 前言 人生苦多,快来 Kotlin ,快速学习Kotlin! 什么是Kotlin? Kotlin 是种静态类型编程...
    任半生嚣狂阅读 25,292评论 9 119
  • 若要创建 Lambda 表达式,需要在 Lambda 运算符 =>左侧指定输入参数(如果有),然后在另一侧输入表达...
    func_老衲姓罗阅读 246评论 0 2
  • 应用程序还需要操作存储在其他数据源(如SQL数据库或XML文件)中的数据,甚至通过Web服务访问它们。传统上,查询...
    CarlDonitz阅读 449评论 0 0
  • 我是我眼睑的囚徒 忧伤的木匠来自多雨的北方 在夜里见过候鸟一样的姑娘 竭力挽留她在渴望的火中 让她在梦里、漆黑的雨...
    一位手艺人阅读 326评论 5 15
  • 饥饿的本质——林海峰作品 2017-08-25 林海峰 幸福岛 饥饿其实并不是肚子的问题,尽管,你有肚子,而且你的...
    清远_a429阅读 130评论 0 0
  • 小花和小木两个人正式熟识的时候是在小花和初恋分手之后。 小花的初恋快速而短暂。她还没来得及习惯,崩的一声就炸了。但...
    小花儿爱吃菜阅读 175评论 0 1
  • 情商高的人,总是很会说话,很会做事,然而简单粗暴的人却只会陷入污泥。生活从不宠幸谁,也并不曾眷顾谁,世界从来公道,...
    流烟远逝阅读 2,478评论 0 3
  • 1.“今天凌晨刚刚参与了一个1000亿的项目!” “这么牛掰?” “沙沙水啦,我经常和建行、工行、中石油这些上10...
    钧吾戏研阅读 146评论 0 1