EKsumic的个人博客

本博客主要用于整理知识点并分享,转载请注明出处

移动适配已完成,点击左侧按钮可使用目录

原创

C#Lambda表达式

 

Lambda表达式

▢ Lambda表达式其实就是一个用来代替委托实例的未命名的方法;

换句话说,它是个方法,它没有名字。

▢ 编译器会把Lambda表达式转化为以下二者之一:
   ▢ 一个委托实例
   ▢ 一个表达式树(expression tree),类型是Expression<TDelegate>,它表示了可遍历的对象模型中Lambda表达式里面的代码。它允许Lambda表达式延迟到运行时再被解释。

实例:

delegate int Transformer(int i);

Transformer sqr=x=>x*x;
Console.WriteLine(sqr(3));  // 9

不懂基础的委托怎么写的看这里→https://www.v2know.com/MainPage/PreView/178

以上代码,实际上,编译器会通过编写一个私有方法来解析这个lambda表达式,
然后把表达式的代码→到这个方法里。

x=>x*x; 可以写成 x=>{ return x*x;};

 



Func和Action

为什么会出现Func和Action?

先说原因,因为要简化代码。

https://www.v2know.com/MainPage/PreView/179

中,已经给出了Func和Action的源码定义,

我们发现,它们都有无参、有参、到16参的我委托类型方法,

是的,它们就是为了简化委托反复定义的麻烦,而存在的。


功能

▢ Func用来返回,Action用来执行。

用法

▢ Func<参数,参数,. . . ,返回结果>

▢ Lambda表达式通常与Func和Action委托一起使用

Func<int,int>sqr=x=>x*x;

Func<string,string,int> totalLength=(s1,s2)=>s1.Length+s2.Length;
int total=totalLength("hello","world");    //  total is 10

 

Lambda表达式与泛型

void Foo<T>(T x) { }
        void Bar<T>(Action<T> a) { }

        void MyMethod()
        {
            Bar((int x)=>Foo(x));
            Bar<int>(x => Foo(x));
            Bar<int>(Foo);
        }

 


 

捕获外部变量

▢ lambda表达式可以引用本地的变量和所在方法的参数

static void Main()
{
    int factor=2;
    Func<int,int> multiplier=n=>n*factor;
    Console.WriteLine(multiplier(3));   // 6
}

factor是被捕获的外部变量。

▢ 被Lambda表达式引用的外部变量叫做被捕获的变量(capture variables)。

▢ 捕获了外部变量的lambda表达式叫做闭包。(坑)

▢ 被捕获的变量是在委托被实际调用的时候才被计算,而不是在捕获的时候。

实例:

int factor=2;
Func<int,int> multiplier=n=>n*factor;
factor=10;
Console.WriteLine(multiplier(3));      // 30

▢ Lambda表达式本身也可以更新被捕获的变量。

实例:

int seed=0;
Func<int> natural=()=>seed++;
Console.WriteLine(natural());    // 0
Console.WriteLine(natural());    // 1
Console.WriteLine(seed());        // 2

▢ 被捕获的变量的生命周期会被延长到和委托一样。

实例:

static Func<int> Natural()
{
   int seed=0;
   return ()=>seed++;      // Returns as a closure
}

static void Main()
{
   Func<int> natural=Natural();
   Console.WriteLine(natural());      // 0
   Console.WriteLine(natural());      // 1
}

▢ 在Lambda表达式内实例化的本地变量,对于委托实例的每次调用来说,都是唯一的。

static Func<int> Natural()
{
    return ()=>{ int seed=0;  return seed++; }
}

static void Main()
{
    Func<int> natural=Natural();
    Console.WriteLine(natural());    // 0
    Console.WriteLine(natural());    // 0
}

▢ 当捕获for循环的迭代变量时,C#会把这个变量当作是在循环外部定义的变量,这就意味着每次迭代捕获的都是同一个变量。

Action[] actions=new Actions[3];
for(int i=0;i<3;i++)
   actions[i]=()=>Console.Write(i);

foreach(Action a in actions) a();   // 333

因为Console.Write(i)里面的i一直都是同一个i,
且实际的Write动作没有执行,直到循环结束,
才开始按照预定义的方法开始输出。

解决方案:

Action[] actions=new Actions[3];
for(int i=0;i<3;i++)
{
   int loopScopedi=i;
   actions[i]=()=>Console.Write(loopScopedi);
}
foreach(Action a in actions) a();   // 012

loopScopedi相当于有3个同时存在,且不变,
并且延长到生命周期结束,
和之前单独的一个i不一样。

 


 

Lambda表达式vs本地方法
▢ 本地方法是C#7的一个新特性。它和Lambda表达式在功能上有很多重复之处,但它有3个优点:
  ▢ 可以简单明了地进行递归
  ▢ 无需指定委托类型(那一堆代码)
  ▢ 性能开销略低一点
▢ 本地方法效率更高是因为它避免了委托的间接调用(需要CPU周期,内存分配)。
▢ 本地方法也可以访问所在方法的本地变量,而且无需编译器把捕获的变量hoist到隐藏的类。

 


 

匿名方法vsLambda表达式
▢ 匿名方法和Lambda表达式很像,但是缺少以下三个特性:
  ▢ 隐式类型参数
  ▢ 表达式语法(只能是语句块)
  ▢ 编译表达式树的能力,通过赋值给Expression<T>

举例:

delegate int Transformer(int i);
Transformer sqr=delegate (int x){return x*x;};
Console.WriteLine(sqr(3));     //9

可简化:

Transformer sqr=(int x)=>{return x*x;};

甚至:

Transformer sqr=x=>x*x;

 


 

匿名方法-其它
▢ 匿名方法捕获外部变量的规则和Lambda表达式是一样的。
▢ 但匿名方法可以完全省略参数声明,尽管委托需要参数:

public event EventHandler Clicked=delegate{};

▢ 这就避免了触发事件前的null检查

// Notice that we omit the parameters
Clicked+=delegate{Console.WriteLine("clicked");};

文章最后发布于 2020-03-18 21:57:17