跳至內容

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;