闭包-捕获变量和将它们作为参数读取之间的区别

本文关键字:参数 读取 之间 区别 变量 闭包 | 更新日期: 2023-09-27 18:26:43

假设我们有这样的类:

// Provides deferred behaviour
public class Command<TResult>
{
     private Func<object[], TResult> _executeFunction;
     private object[] _args;
     public Command(Func<object[], TResult> execution, params object[] arguments)
     {
          _executeFunction = execution;
          _args = arguments;
     }
     public TResult Execute()
     {
          return _executeFunction(_args);
     }
}

这两个匿名函数有什么区别?

int a = 1;
int b = 4;
// a and b are passed in as arguments to the function
Command<int> sum = new Command<int>(args => (int)args[0] + (int)args[1], a, b);
// a and b are captured by the function
Command<int> sum2 = new Command<int>(_ => a + b);
Console.WriteLine(sum.Execute()); //Prints out 5
Console.WriteLine(sum2.Execute()); //Prints out 5

我特别关注性能差异。

此外,我们知道,如果某个类持有sum2的引用,那么ab将超出它们定义的范围,如果函数仍在任何地方被引用,则可能永远不会被GC收集。

sum也会发生同样的情况吗?(考虑到参数是引用类型,而不是本例中的值类型)

闭包-捕获变量和将它们作为参数读取之间的区别

当您传入变量ab时,您正在执行此操作。分别传入14的值。但是,当您在lambda表达式的上下文(或范围)中引用ab时,这些值将被"捕获"。lambda表达式范围内的变量ab被视为对范围外原始变量的引用,这意味着如果它们在lambda范围内发生更改,原始变量也会发生更改。当编译到IL中时,它们驻留在共享实例的类中。

static void Main()
{
    int a = 1;
    int b = 4;
    // a and b are passed in as arguments to the function
    var sum = new Command<int>(args => (int)args[0] + (int)args[1], a, b);
    // a and b are captured by the function
    var sum2 = new Command<int>(_ =>
    {
        var c = a + b;
        a++;
        b++;
        return c;
    });
    Console.WriteLine(sum.Execute()); //Prints out 5
    Console.WriteLine(sum2.Execute()); //Prints out 5
    Console.WriteLine("a = " + a); // Prints 2
    Console.WriteLine("b = " + b); // Prints 5
    Console.ReadLine();
}

IL中的差异真的很小,我不认为有任何值得避免的性能影响。为了可读性,我通常更喜欢使用lambda表达式。

看看一些C#lambdas生成的IL。