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/
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

posted @ 2020-12-24 16:11  安以痕_陈  阅读(110)  评论(0编辑  收藏  举报