c# – 为什么lambda表达式在方法终止后保留封闭范围变量值?

c# – 为什么lambda表达式在方法终止后保留封闭范围变量值?,第1张

概述我的印象是C#中的lambda表达式上下文包含对其中使用的父函数作用域的变量的引用.考虑: public class Test{ private static System.Action<int> del; public static void test(){ int i = 100500; del = a => System.Console.W 我的印象是C#中的lambda表达式上下文包含对其中使用的父函数作用域的变量的引用.考虑:
public class Test{    private static System.Action<int> del;    public static voID test(){        int i = 100500;        del = a => System.Console.Writeline("param = {0},i = {1}",a,i);        del(1);        i = 10;        del(1);    }    public static voID Main()    {        test();    }}

输出

param = 1,i = 100500param = 1,i = 10

但是,如果这是真的,则以下内容将是非法的,因为lambda上下文将引用超出范围的局部变量:

public class Test{    private static System.Action<int> del;    public static voID test(){        int i = 100500;        del = a => System.Console.Writeline("param = {0},i);    }    public static voID Main()    {        test();        del(1);    }}

但是,这会编译,运行和输出

param = 1,i = 100500

这意味着要么发生奇怪的事情,要么上下文保留局部变量的值,而不是对它们的引用.但是如果这是真的,它将不得不在每个lambda invokation上更新它们,并且我不知道当原始变量超出范围时它将如何工作.而且,在处理大值类型时,这似乎会产生开销.

我知道,例如,在C中,这是UB(在回答@L_403_0@时确认).

问题是,这是C#中明确定义的行为吗? (我认为C#确实有一些UB,或者至少有一些IB,对吧?)

如果定义明确,这实际上如何以及为什么有效? (实现逻辑会很有趣)

解决方法 与C#中的lambda语法相关的闭包概念是一个非常大的主题,对我来说太大了,只能在这个答案中涵盖所有内容,但让我们至少尝试回答这个问题.实际答案位于底部,其余部分是理解答案所需的背景.

当编译器尝试使用匿名方法编译方法时会发生什么,它会在某种程度上重写该方法.

基本上,会生成一个新类,并将匿名方法提升到此类中.它给出了一个名称,虽然是一个内部名称,因此对于编译器来说,它有点从匿名方法转换为命名方法.但是,您不必知道或处理该名称.

此方法所需的任何变量,除了匿名方法之外声明的变量,但在使用/声明匿名方法的同一方法中,也将被取消,然后重写这些变量的所有用法.

这里涉及到几种方法,所以很难阅读上面的文字,所以让我们做一个例子:

public Func<int,int> Test1(){    int a = 42;    return value => a + value;}

此方法被重写为以下内容:

public Func<int,int> Test1(){    var dummy = new <>c__displayClass1();    dummy.a = 42;    return dummy.<Test1>b__0;}internal class <>c__displayClass1{    public int a;    public int <Test1>b__0(int value)    {        return a + value;    }}

编译器可以处理所有这些时髦的名称(是的,它们确实以所有括号命名),因为它引用具有ID和对象引用的东西,名称不再是编译器的问题.但是,您永远不能声明具有这些名称的类或方法,因此编译器不会生成恰好已经存在的类的风险.

这是一个LINQPad示例,它显示了我声明的类,虽然名称中的括号较少,但看起来与编译器生成的类相同:

voID Main(){    var f1 = Test1();    f1(10).Dump();    f1.Dump();    var f2 = Test2();    f2(10).Dump();    f2.Dump();}public Func<int,int> Test1(){    int a = 42;    return value => a + value;}public Func<int,int> Test2(){    var dummy = new __c__displayClass1();    dummy.a = 42;    return dummy._Test2_b__0;}public class __c__displayClass1{    public int a;    public int _Test2_b__0(int value)    {        return a + value;    }}

输出:

如果你看一下上面的截图,你会发现每个委托变量,一个Method属性和一个Target属性有两件事.

在调用方法时,使用引用Target对象的this引用调用它.因此,委托捕获两件事:调用哪种方法,以及调用它的对象.

基本上,该生成类的对象作为委托的一部分存活,因为它是该方法的目标.

考虑到所有这些,让我们看看你的问题:

为什么lambda表达式在方法终止后保留封闭范围变量值?

答:如果lambda存活下来,所有捕获的变量都会存活,因为它们不再是声明它们的方法的局部变量.相反,它们被提升到一个也具有lambda方法的新对象,因此“跟随” lambda到处都是.

总结

以上是内存溢出为你收集整理的c# – 为什么lambda表达式在方法终止后保留封闭范围变量值?全部内容,希望文章能够帮你解决c# – 为什么lambda表达式在方法终止后保留封闭范围变量值?所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1262199.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-08
下一篇 2022-06-08

发表评论

登录后才能评论

评论列表(0条)

保存