C# 是否在本地函数堆栈中声明了值类型变量?

C# 是否在本地函数堆栈中声明了值类型变量?,c#,C#,我读到了最近介绍的本地函数,开始对这个问题感到疑惑。lambda中值类型的Afaik局部变量在堆中分配。此外,在捕获值类型时,本地函数比lambdas有一些优势,在本例中不需要额外的堆分配。我仍然不清楚以下几点: 局部值类型变量是否在堆栈上分配的局部函数中声明 那么在“父”函数中声明并在局部函数中捕获的值类型变量呢 (前提是父母本身不是匿名的) 编辑: int ParentFunction() { int parentVarLambda=0; int parentVarLocal=0; Func

我读到了最近介绍的本地函数,开始对这个问题感到疑惑。lambda中值类型的Afaik局部变量在堆中分配。此外,在捕获值类型时,本地函数比lambdas有一些优势,在本例中不需要额外的堆分配。我仍然不清楚以下几点:

  • 局部值类型变量是否在堆栈上分配的局部函数中声明
  • 那么在“父”函数中声明并在局部函数中捕获的值类型变量呢 (前提是父母本身不是匿名的)

    编辑:

    int ParentFunction()
    {
    int parentVarLambda=0;
    int parentVarLocal=0;
    Func lamdafanson=()=>parentVarLambda+1;
    int a=lamdafanson();
    int b=LocalFunction();
    返回a+b;
    int LocalFunction()
    {
    int localFuncVar=1;
    返回parentVarLocal+=localFuncVar;
    }
    }
    

    parentVarLambda、parentVarLocal和localFuncVar将分配到哪里?

    除非发生其他事情,否则没有一个是堆分配的(特别是当编译器不能保证本地函数捕获的变量的生存期不超过父方法的生存期时,例如,如果委托引用本地函数,或者本地函数包含
    yield return
    wait
    语句)

    假设你有:

    public void M(int i) {
        Inner(i + 1);
    
        void Inner(int x)
        {
            int j = x + i;
            Console.WriteLine(j);   
        }
    }
    
    使用Wonder,我们可以看到它被编译为:

    [StructLayout(LayoutKind.Auto)]
    [CompilerGenerated]
    private struct <>c__DisplayClass0_0
    {
        public int i;
    }
    
    public void M(int i)
    {
        <>c__DisplayClass0_0 <>c__DisplayClass0_ = default(<>c__DisplayClass0_0);
        <>c__DisplayClass0_.i = i;
        <M>g__Inner|0_0(<>c__DisplayClass0_.i + 1, ref <>c__DisplayClass0_);
    }
    
    [CompilerGenerated]
    internal static void <M>g__Inner|0_0(int x, ref <>c__DisplayClass0_0 P_1)
    {
        Console.WriteLine(x + P_1.i);
    }
    
    我们可以再次:

    [编译生成]
    专用密封类c\uuu显示器Class0\u0
    {
    公共国际学校;
    公共和本地;
    内部整数b_u 0()
    {
    返回parentVarLambda+1;
    }
    内部int g_ulocalfunction | 1()
    {
    int num=1;
    返回parentVarLocal+=num;
    }
    }
    私有int ParentFunction()
    {
    c_uuuDisplayClass0_0C_uuuuDisplayClass0_0=新的c_uuuDisplayClass0_0();
    c_uuudisplayClass0_uuu.parentVarLambda=0;
    c_uuudisplayClass0_uuu.parentVarLocal=0;
    int num=新函数(c_uudisplayClass0_u.b_u0)();
    int num2=c_uudisplayClass0_u.g_uulocalfunction|1();
    返回num+num2;
    }
    
    请注意,编译器意识到无论如何都必须为委托创建一个生成类的新实例,因此它只是选择以相同的方式处理局部函数,而不需要额外的成本。在这种情况下,这没有多大区别,但在委托和局部函数捕获相同变量的情况下需要这种技术-它们需要被提升到同一个生成的类中


    正因为如此,
    parentVarLambda
    parentVarLocal
    都被分配到了同一个编译器生成的类上,而
    localFuncVar
    刚刚得到了优化(但会在
    g_ulocalfunction | 1()
    中的堆栈上进行分配)。

    @jdweng:这根本不是真的。“lambda中值类型的Afaik局部变量在堆中分配“-不,不是。lambda表达式捕获的局部变量必须在多个委托调用中持久化时进行堆分配,但这与lambda表达式中声明的局部变量不同。如果您能给出更具体的示例,说明您感兴趣的内容(作为代码)这会更容易帮助您。@jdweng:“所有运行时对象都必须分配到堆栈中”不,它们确实没有。@jdweng:那么您应该知道这是a)不准确的;b) 不是其他人使用这个术语的方式,因此对堆栈溢出没有帮助。@jdweng:它不是堆栈-堆上没有“推送”和“弹出”操作。这不是微软的术语,而是简单的计算机科学。(我同意这也不是一个普通的计算机科学堆,但堆的使用也不是MS特有的。)即使这在技术上是正确的,但显然不是OP所说的:他们谈论的是堆分配与堆栈分配。为什么不试着用每个人实际使用的术语与人交流呢?除非情况并非总是如此。尝试添加
    Action=internal作为你方法的另一行…@JonSkeet很好,谢谢!我本来打算将局部函数与为委托生成的代码进行对比,因此在最后将二者结合在一起是有意义的。在编译器无法控制生存期的其他情况下也会发生这种情况,例如,如果是迭代器或异步方法。是的,在第3节的末尾会有一条注释(当然,隐藏在噪音中)。我会在开始时重复。
    
    [StructLayout(LayoutKind.Auto)]
    [CompilerGenerated]
    private struct <>c__DisplayClass0_0
    {
        public int i;
    }
    
    public void M(int i)
    {
        <>c__DisplayClass0_0 <>c__DisplayClass0_ = default(<>c__DisplayClass0_0);
        <>c__DisplayClass0_.i = i;
        <M>g__Inner|0_0(<>c__DisplayClass0_.i + 1, ref <>c__DisplayClass0_);
    }
    
    [CompilerGenerated]
    internal static void <M>g__Inner|0_0(int x, ref <>c__DisplayClass0_0 P_1)
    {
        Console.WriteLine(x + P_1.i);
    }
    
    public void M(int i) {
        Action<int> inner = x =>
        {
            int j = x + i;
            Console.WriteLine(j);   
        };
    
        inner(i + 1);
    }
    
    [CompilerGenerated]
    private sealed class <>c__DisplayClass0_0
    {
        public int i;
    
        internal void <M>b__0(int x)
        {
            Console.WriteLine(x + i);
        }
    }
    
    public void M(int i)
    {
        <>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
        <>c__DisplayClass0_.i = i;
        new Action<int>(<>c__DisplayClass0_.<M>b__0)(<>c__DisplayClass0_.i + 1);
    }
    
    public void M(int i) {
        void Inner(int x)
        {
            int j = x + i;
            Console.WriteLine(j);   
        }
    
        Action<int> inner = Inner;
        inner(i + 1);
    }
    
    [CompilerGenerated]
    private sealed class <>c__DisplayClass0_0
    {
        public int i;
    
        internal void <M>g__Inner|0(int x)
        {
            Console.WriteLine(x + i);
        }
    }
    
    public void M(int i)
    {
        <>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
        <>c__DisplayClass0_.i = i;
        new Action<int>(<>c__DisplayClass0_.<M>g__Inner|0)(<>c__DisplayClass0_.i + 1);
    }
    
    int ParentFunction ()
    {
        int parentVarLambda = 0;
        int parentVarLocal = 0;
    
        Func<int> lamdaFuncion = () => parentVarLambda + 1;
    
        int a = lamdaFuncion();
        int b = LocalFunction();
    
        return a + b;
    
        int LocalFunction()
        {
            int localVar = 1;
            return parentVarLocal += localVar;
        }
    }
    
    [CompilerGenerated]
    private sealed class <>c__DisplayClass0_0
    {
        public int parentVarLambda;
    
        public int parentVarLocal;
    
        internal int <ParentFunction>b__0()
        {
            return parentVarLambda + 1;
        }
    
        internal int <ParentFunction>g__LocalFunction|1()
        {
            int num = 1;
            return parentVarLocal += num;
        }
    }
    
    private int ParentFunction()
    {
        <>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
        <>c__DisplayClass0_.parentVarLambda = 0;
        <>c__DisplayClass0_.parentVarLocal = 0;
        int num = new Func<int>(<>c__DisplayClass0_.<ParentFunction>b__0)();
        int num2 = <>c__DisplayClass0_.<ParentFunction>g__LocalFunction|1();
        return num + num2;
    }