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));同样如此,可以看到,我们给的是方法作为参数,编译器编译成为委托,并且为这个委托指定了我们给的方法名。一切仿佛都变清楚了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • C++ lambda表达式与函数对象 lambda表达式是C++11中引入的一项新技术,利用lambda表达式可以...
    小白将阅读 85,130评论 15 118
  • 前言 人生苦多,快来 Kotlin ,快速学习Kotlin! 什么是Kotlin? Kotlin 是种静态类型编程...
    任半生嚣狂阅读 26,139评论 9 118
  • 若要创建 Lambda 表达式,需要在 Lambda 运算符 =>左侧指定输入参数(如果有),然后在另一侧输入表达...
    func_老衲姓罗阅读 316评论 0 2
  • 应用程序还需要操作存储在其他数据源(如SQL数据库或XML文件)中的数据,甚至通过Web服务访问它们。传统上,查询...
    CarlDonitz阅读 585评论 0 0
  • 我是我眼睑的囚徒 忧伤的木匠来自多雨的北方 在夜里见过候鸟一样的姑娘 竭力挽留她在渴望的火中 让她在梦里、漆黑的雨...
    一位手艺人阅读 404评论 5 15