在編寫 lambda 表達式時,通常無需指定輸入參數的類型,因為編譯器可以根據 lambda 表達式主體、參數類型以及 C# 語言規範中所述的其他因素推斷出類型。對於大多數標準查詢運算符,第一個輸入是來源序列中元素的類型。如果您查詢的是 IEnumerable<Customer>
類型,則輸入變數會被推斷為 Customer 對象,這表示您可以存取其方法和屬性:
customers.Where(c => c.City == "London");
Lambda 類型推斷的一般規則如下:
Lambda 表達式本身沒有類型,因為通用類型系統沒有「Lambda 表達式」的固有概念。然而,有時非正式地描述 Lambda 表達式的「類型」會更方便。這種非正式的「類型」指的是 Lambda 表達式轉換後的委託類型或表達式類型。 Lambda 表達式可以具有自然類型。編譯器無需強制您聲明委託類型(例如,為 Lambda 表達式聲明 Func<...> 或 Action<...>),而是可以根據 Lambda 表達式推斷委託類型。例如,請考慮以下聲明:
var parse = (string s) => int.Parse(s);
編譯器可以推斷 parse 為 Func<string, int>
。如果存在適當的 Func 或 Action 委託,編譯器會選擇一個。否則,它會合成一個委託類型。例如,如果 Lambda 表達式包含 ref 參數,則會合成委託類型。當 Lambda 表達式具有自然類型時,可以將其賦值給不太明確的類型,例如 System.Object 或 System.Delegate:
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
如果將 lambda 表達式指派給 System.Linq.Expressions.LambdaExpression 或 System.Linq.Expressions.Expression,並且 lambda 具有自然委託類型,則表達式的自然類型為 System.Linq.Expressions.Expression<TDelegate>
,其中委託自然類型用作參數的參數:
LambdaExpression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>
Expression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>
並非所有 lambda 表達式都具有自然類型。請考慮以下聲明:
var parse = s => int.Parse(s); // ERROR: Not enough type info in the lambda
編譯器無法推斷 s 的參數型別。當編譯器無法推斷自然類型時,必須聲明類型:
Func<string, int> parse = s => int.Parse(s);
通常,lambda 表達式的回傳類型是顯而易見且可推斷的。但有些表達式卻無法正常運作:
var choose = (bool b) => b ? 1 : "two"; // ERROR: Can't infer return type
可以在輸入參數前指定 Lambda 運算式的回傳類型。指定明確傳回類型時,必須將輸入參數括起來:
var choose = object (bool b) => b ? 1 : "two"; // Func<bool, object>
可以為 Lambda 表達式及其參數新增屬性。以下範例示範如何為 Lambda 運算式新增屬性:
Func<string?, int?> parse = [ProvidesNullCheck] (s) => (s is not null) ? int.Parse(s) : null;
也可以為輸入參數或傳回值新增屬性,如下例所示:
var concat = ([DisallowNull] string a, [DisallowNull] string b) => a + b;
var inc = [return: NotNullIfNotNull(nameof(s))] (int? s) => s.HasValue ? s++ : null;
如前面的範例所示,在 lambda 表達式或其參數中新增屬性時,必須將輸入參數括起來。
Lambda 可以引用外部變數。這些外部變數是位於定義 Lambda 表達式的方法範圍內的變量,或位於包含 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 表達式中的變數作用域:
Func<double, double> square = static x => x * x;
靜態 lambda 無法從封閉範圍擷取局部變數或實例狀態,但可以引用靜態成員和常數定義。