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");};