C Sharp/匿名方法
匿名方法(Anonymous method)或更常见的兰姆达表达式(lambda expressions)是允许在代码中写内联闭包的函数。
lambda表达式的本质是一个匿名方法。Func<TSource, bool>类型则是一个泛型委托。委托就是对象化的方法,有了委托就可以像操作一个对象一样操作一个方法,比如作为参数进行传递。表达式树(System.Linq.Expressions.Expression)是方法表达式的对象化。Expression通过Expression Tree数据结构来存储和描述代码,LINQ to SQL就可以分析表达式并生成对应的Sql语句,所以Expression是LINQ能够实现的基础。
匿名委托
[编辑]C# 2.0引入的特性。
Func<int, int> f = delegate(int x) { return x * 2; };
Lambda表达式
[编辑]C# 3.0的新特性。
使用lambda声明运算符 =>
分开lambda的参数表和体部(body)。
可分为表达式lambda和语句lambda两种。
表达式lambda以一个表达式作为其体部:
// [arguments] => [method-body]
// With parameters
n => n == 2 //只有一个输入参数可以不写圆括号
(a, b) => a + b
(a, b) => { a++; return a + b; }
// With explicitly typed parameters 参数类型必须要不都是显式要不都是隐式
(int a, int b) => a + b
// No parameters
() => return 0
// Assigning lambda to delegate
Func<int, int, int> f = (a, b) => a + b;
//从C# 9.0开始,可以使用弃元:
Func<int, int, int> constant = (_, _) => 42;
语句lambda的体部是以大括号包括起来的多行语句:
(a, b) => { a++; return a + b; }
lambda表达式可以直接作为方法的参数传递:
var list = stringList.Where(n => n.Length > 2);
lambda表达式可转化为委托或表达式树:
//Func是一个泛型委托,参数为int,返回也为int。
Func<int, int> square = x => x * x;
//lambda表达式对应的expression对象。
System.Linq.Expressions.Expression<Func<int, int>> exp_square = x => x * x;
//一个expression对象可以被“编译”为委托对象。
Func<int, int> square2 = exp_square.Compile();
Console.WriteLine(square(5));
Console.WriteLine(square2(6));
当使用基于方法的语法调用System.Linq.Enumerable类中的Enumerable.Select扩展方法时, 例如LINQ to Objects 或 LINQ to XML, 扩展方法的参数为委托类型System.Func<T,TResult>. 当调用System.Linq.Queryable类中的Queryable.Select扩展方法时,例如LINQ to SQL, 扩展方法的参数为表达式树Expression<Func<TSource,TResult>>。不能用语句lambda创建表达式树。
异步lambda函数
[编辑]public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
button1.Click += async (sender, e) => //注意async关键字
{
await ExampleMethodAsync();
textBox1.Text += "\r\nControl returned to Click event handler.\n";
};
}
private async Task ExampleMethodAsync()
{
// The following line simulates a task-returning asynchronous process.
await Task.Delay(1000);
}
}
lambda表达式和元组
[编辑]C# 7.0增加了内建的元组(tuple)。lambda函数的参数和返回值都可以用元组。
Func<(int, int, int), (int, int, int)> doubleThem = ns => (2 * ns.Item1, 2 * ns.Item2, 2 * ns.Item3);
Func<(int n1, int n2, int n3), (int, int, int)> doubleThem1 = ns => (2 * ns.n1, 2 * ns.n2, 2 * ns.n3); //define a tuple with named components
var numbers = (2, 3, 4);
var doubledNumbers = doubleThem(numbers);
Console.WriteLine($"The set {numbers} doubled: {doubledNumbers}");
带有标准查询运算符的lambda
[编辑]int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index); //lambda表达式编译为表达式树
Console.WriteLine(string.Join(" ", firstSmallNumbers));
lambda表达式的自然类型
[编辑]一些lambda表达式可以推断出其参数类型和返回值类型,从C# 10开始,这种lambda具有自然类型(natural type),编译器可以推断其委托类型或者更不明晰的类型如System.Object或System.Delegate或 System.Linq.Expressions.LambdaExpression 或 System.Linq.Expressions.Expression
var parse = (string s) => int.Parse(s);
object parse = (string s) => int.Parse(s); // Func<string, int>
Delegate parse = (string s) => int.Parse(s); // Func<string, int>
var read = Console.Read; // Just one overload; Func<int> inferred
var write = Console.Write; // ERROR: Multiple overloads, can't choose
LambdaExpression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>
Expression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>
var parse = s => int.Parse(s); // ERROR: Not enough type info in the lambda。必须明确给出委托类型:Func<string, int> parse = s => int.Parse(s);
明确lambda返回的类型
[编辑]C# 10开始支持在参数表之前明确给出lambda返回的类型。要求参数表必须用圆括号包起来:
var choose = object (bool b) => b ? 1 : "two"; // Func<bool, object>
lambda增加特性
[编辑]C# 10开始支持给出lambda表达式、或其参数、或其返回类型的特性。要求参数表必须用圆括号包起来:
Func<string, int> parse = [Example(1)] (s) => int.Parse(s);
var choose = [Example(2)][Example(3)] object (bool b) => b ? 1 : "two";
var sum = ([Example(1)] int a, [Example(2), Example(3)] int b) => a + b;
var inc = [return: Example(1)] (int s) => s++;
捕捉外部变量和变量作用域
[编辑]lambda表达式可以捕获外部变量,并存储外部变量的值,即使外部变量超出其作用域被垃圾回收。能被捕获的外部变量应该在定义了lambda表达式的方法的作用域内,或者包含了lambda表达式的类型的作用域内。
public static class VariableScopeWithLambdas
{
public class VariableCaptureGame
{
internal Action<int>? updateCapturedLocalVariable;
internal Func<int, bool>? isEqualToCapturedLocalVariable;
public void Run(int input)
{
int j = 0;
updateCapturedLocalVariable = x =>
{
j = x;
bool result = j > input;
Console.WriteLine($"{j} is greater than {input}: {result}");
};
isEqualToCapturedLocalVariable = x => x == j;
Console.WriteLine($"Local variable before lambda invocation: {j}");
updateCapturedLocalVariable(10);
Console.WriteLine($"Local variable after lambda invocation: {j}");
}
}
public static void Main()
{
var game = new VariableCaptureGame();
int gameInput = 5;
game.Run(gameInput);
int jTry = 10;
bool result = game.isEqualToCapturedLocalVariable!(jTry);
Console.WriteLine($"Captured local variable is equal to {jTry}: {result}");
int anotherJ = 3;
game.updateCapturedLocalVariable!(anotherJ);
bool equalToAnother = game.isEqualToCapturedLocalVariable(anotherJ);
Console.WriteLine($"Another lambda observes a new value of captured variable: {equalToAnother}");
}
// Output:
// Local variable before lambda invocation: 0
// 10 is greater than 5: True
// Local variable after lambda invocation: 10
// Captured local variable is equal to 10: True
// 3 is greater than 5: False
// Another lambda observes a new value of captured variable: True
}
注意:
- lambda表达式不能直接捕获包含该lambda表达式的方法的 in, ref, out参数。
- lambda表达式引用的变量不能垃圾回收,直至引用lambda表达式的委托被垃圾回收。
静态lambda表达式
[编辑]从C# 9.0开始,lambda表达式可以加上static关键字以避免无意间捕获局部变量或实例状态:
Func<double, double> square = static x => x * x;