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;