CS4L15——Lambda表达式
CS4L15——Lambda表达式
lambda表达式
可以将 lambda 表达式 理解为 匿名函数 的缩写
它除了写法不同外,使用上和匿名函数一模一样
都是和委托或者事件 配合使用的
匿名函数存在闭包这个概念,闭包确保了内层的匿名函数可以引用包含在它外层的函数的变量,即使外层函数的已经执行完毕
注意:通过调用 使用闭包变量的匿名函数 时,闭包变量的值不是变量创建时的值,而是在父函数(严格来说是作用域)范围内的最终值!!!
lambda表达式语法
1 | //匿名函数 |
使用
-
无参无返回值
1
2
3
4
5Action a = () =>
{
Console.WriteLine("无参无返回值的lambda表达式");
};
a();输出:
1
无参无返回值的lambda表达式
-
有参
1
2
3
4
5Action<int> a2 = (int value) =>
{
Console.WriteLine("有参数的lambda表达式:{0}", value);
};
a2(100);输出:
1
有参数的lambda表达式:100
甚至参数类型都可以省略 因为参数类型和委托或事件容器一致
1
2
3
4
5Action<int, string> a3 = (value, value2) =>
{
Console.WriteLine("省略参数类型的写法{0},{1}", value, value2);
};
a3(200, "123");输出:
1
省略参数类型的写法200,123
-
有返回值
1
2
3
4
5
6Func<string, int> a4 = (value) =>
{
Console.WriteLine("有返回值有参的lambda表达式{0}", value);
return 1;
};
Console.WriteLine(a4("123123"));输出:
1
2
3
4
5
6Func<string, int> a4 = (value) =>
{
Console.WriteLine("有返回值有参的lambda表达式{0}", value);
return 1;
};
Console.WriteLine(a4("123123"));
如果函数逻辑非常简单,则可以进一步缩写,下面是缩写示例:
-
函数体只有一句表达式,可以省略大括号,如果有返回值,只要表达式返回值的类型是正确的,就可以直接省略
return
1
2Action<int> printNumber = (int i) => Console.WriteLine(i);
Func<int, string, bool> isTooLong = (int x, string s) => s.Length > x; -
如果lambda表达式的参数列表的类型可以被编译器推断出来(例如可以由接收函数的委托决定各个参数类型)
则参数列表内的参数可以省略类型(注意,lambda表达式的参数列表要么全部省略类型,要么全部指定类型,不能只省略部分)1
Func<int, int, bool> testForEquality = (x, y) => x == y; //这里x, y的类型都可以由前面的委托传入的泛型类型推断出来
-
如果函数只有一个参数,则参数列表的小括号也可以省略 (没有参数时小括号不可省略!)
1
Func<int[], int> getFirstNumber = a => a[0];
这种缩写常用于参数传递,有些方法的参数需要执行对应格式的方法(例如:LINQ),而如果我们传入的逻辑非常简单时,这种写法就很方便了
其他传参使用等和匿名函数一样,缺点也是和匿名函数一样的
闭包(非常重要!!!)
闭包确保了内层的匿名函数可以引用包含在它外层的函数的变量,即使外层函数的执行已经终止
注意:通过调用 使用闭包变量的匿名函数 时,闭包变量的值不是变量创建时的值,而是在父函数(严格来说是作用域)范围内的最终值!!!
函数内部声明的局部变量,在函数执行完毕后就会被释放(以值类型变量为例),而无法再被使用
1 | public void TestFunc() |
内层的匿名函数可以调用外层函数的变量,因为匿名函数很有可能会写在另一个函数内部,而匿名函数内经常会使用到外部函数的局部变量
1 | public event Action action; |
当外层函数的某个局部变量,被内层的匿名函数使用,且这个内层的匿名函数,被传递到函数外部或者由函数外部的成员变量装载时
我们就会说这个 被匿名函数使用的局部变量 形成了闭包,因为这个局部变量的生命周期被改变了,它不会在外层函数执行完毕时就被释放
如上面的代码,TestFunc()
函数执行完毕时,value
不会因为 TestFunc()
函数执行完毕而被释放
value
会一直存在于外部的 action
内,确保调用 action
的逻辑正常执行,除非手动将 action
置空
闭包这个特性确保了匿名函数执行时可以使用其作用域以外的局部变量,并且可以不需要担心这个局部变量因为外层函数执行完毕而被释放的问题
闭包带来的影响
一个形成闭包的变量在被多个匿名函数使用时,这个变量值是相同的,唯一的,变量的值取决于 声明变量的作用域执行完毕时 的最终值
以下面的代码为例:
1 | class Test |
输出:
1 | 10 |
和直觉不同,这里打印了 10 个 10,而不是 0 到 9
这是因为,每次循环向 action
添加的 所有的匿名函数,都共用该循环的同一个临时变量 i
又因为 i
在这个循环里 是唯一的,因此每次循环改变 i
时都会改变 action
内所有匿名函数里的 i
在我们调用 action
输出值时,循环已经执行完毕,循环完毕时 i
是 10,因此 action
内部所有函数的 i
都是10,因此会打印 10 个 10
如果我们要打印出 0 到 9,就需要让委托内部的函数引用不同的变量,且变量只存储当时循环时 i
的值,后续循环不能影响到
1 | class Test |
输出:
1 | 10 |
可见,匿名函数使用在循环内部声明的变量,而非声明循环时声明的变量 i
时,就不会打印出 10 个 10,而是 0 到 9,
因为每次循环,都会重新声明一个 index
,且为其赋值当前循环时的 i
的值
而每次循环并赋值的 index
,都是重新创建的 index
,不是上次循环的 index
,这个循环里的 index
不是唯一的
因此 action
内的各个函数的 index
都是不同的,一个函数对应一个 index
,而这些 index
的值各不相同,因此,会打印0-9