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



    作者:天堂迈舞
    链接:https://www.jianshu.com/p/082accc59812/
    来源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

  • 相关阅读:
    从JAVA看C#中volatile和synchronized关键字的作用
    SSAS动态添加分区 (转载)
    IIS7的集成模式下如何让自定义的HttpModule不处理静态文件(.html .css .js .jpeg等)请求
    SqlServer Analysis Service的事实维度关系
    SqlServer分区表概述(转载)
    SQL2005中的事务与锁定(九)-(1)- 转载
    SQL2005中的事务与锁定(八)- 转载
    SQL2005中的事务与锁定(七)
    LINUX下编译ffmpeg-x264出现的错误及解决转
    从ffmpeg源代码分析如何解决ffmpeg编码的延迟问题 (转)
  • 原文地址:https://www.cnblogs.com/anyihen/p/14184657.html
Copyright © 2020-2023  润新知