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;