【问题标题】:Why declare a local function static in C# 8.0为什么在 C# 8.0 中声明一个局部函数 static
【发布时间】:2020-03-03 20:23:59
【问题描述】:

在 C# 8.0 中,Static Local Functions are announced

谁能帮我解释一下为什么要将本地函数声明为静态函数?

文中给出的原因:

确保本地函数不会从封闭范围内捕获(引用)任何变量

但是:

  1. 我不明白为什么您要确保这一点?
  2. 是否有任何其他理由或好处将其声明为静态? (可能是性能?)

文中给出的示例代码为:

int M()
{
    int y = 5;
    int x = 7;
    return Add(x, y);

    static int Add(int left, int right) => left + right;
}

【问题讨论】:

  • 在你的 Add 方法中,因为它是静态的,你不能直接引用 xy,这就是你的引用的意思。
  • @DavidG 但问题是:有什么好处?
  • 好问题。我也想不出我会使用它的场景,所以我很好奇。
  • @RenéVogt 在类中使用静态方法有什么好处?防止访问班级成员。
  • @RenéVogt 将int M() 更改为class Mreturn Add(x, y); 更改为public int Foo() { return Add(x, y); },你就会明白我的意思了。方法的局部变量在概念上(从作用域的角度来看)类似于类的私有成员。

标签: c# .net static c#-8.0 local-functions


【解决方案1】:

我认为这只是为了确保正确使用本地函数中使用的变量,如文档所述。在大而复杂的方法中,它可以防止意外使用封闭范围变量如果本地函数中有同名的变量

【讨论】:

    【解决方案2】:

    我不明白你为什么要确保这一点?

    因为它可以防止你射中自己的脚。它强制本地函数为不修改调用者状态的pure function

    这将返回false,因为该函数修改了其调用者的局部变量:

    public bool Is42()
    {
        int i = 42;     
        Foo();      
        return i == 42;
    
        void Foo()
        {
            i = 21;
        }   
    }
    

    这并没有,因为它甚至不编译:

    public bool Is42()
    {
        int i = 42;     
        Foo();      
        return i == 42;
    
        static void Foo()
        {
            i = 21;
        }   
    }
    

    它可以防止意外。当然,在这些简单的例子中,好处并不是很明显,因为“很明显Foo() 修改了i,但是在由多人维护的更大的代码库中并且没有被单元正确覆盖测试,这个简单的修饰符可以防止悲伤。

    【讨论】:

    • 谢谢。您的回答以及@György Kőszeg 的回答全面了解了我在问题 1 和 2 中在修改呼叫者状态和成本方面提出的问题:stackoverflow.com/a/58746550
    【解决方案3】:

    捕获变量会产生少量额外成本,因为它会生成一个内部使用的类型,其中您捕获的变量是公共字段。考虑一个稍微修改的例子:

    int M()
    {
        int y = 5;
        int x = 7;
        return Add();
    
        int Add() => x + y;
    }
    

    它实际上会翻译成这样的:

    int M()
    {
        int y = 5;
        int x = 7;
        var capturedVars = new <>c__DisplayClass0_0 { x = x, y = y };
        return <M>g__Add|0_0(ref capturedVars);
    }
    
    [CompilerGenerated]
    private struct <>c__DisplayClass0_0
    {
        public int x;
        public int y;
    }
    
    [CompilerGenerated]
    internal static int <M>g__Add|0_0(ref <>c__DisplayClass0_0 class_Ref1) => 
        (class_Ref1.x + class_Ref1.y);
    

    【讨论】:

    • 在局部函数中使用局部变量时没有捕获。它们只是通过ref 传递的。仅对匿名方法(lambda、委托)进行捕获。
    • 上面生成的代码来自Reflector编译修改后的例子。如您所见,它捕获了xy 变量。但正如您所看到的,编译器确实尽力做到最好并生成struct 并在可能的情况下传递ref 变量。
    • 哦,对了,对不起。我忘记了将本地人放在一个结构中,然后由 ref 传递。无论如何,我的观点是,这并不像“真实”捕获那样昂贵,因此静态局部函数的好处更多是在“避免更改变量”方面,而不是性能提升。
    • 谢谢。您的回答以及@CodeCaster 的回答全面了解了我在问题 1 和 2 中在修改呼叫者状态和成本方面提出的问题:stackoverflow.com/a/58746428
    • 请注意,如果函数曾经作为 ActionFunc 传递,“真实”捕获(即使用类而不是结构)仍然可能发生。在 OP 的情况下,如果要接受Func&lt;int, int, int&gt;,我们可以简单地传递Add。如果Add 是非静态的并且它碰巧捕获了任何东西,则将使用编译器生成的class
    【解决方案4】:

    This answer from CodeCasterthis separate answer from György Kőszeg 分别回答我的问题的不同部分,因此我将它们放在一起形成接受答案的全貌:

    对于我的问题的第 1 部分),@CodeCaster 说:

    因为它可以防止你射中自己的脚。它强制本地函数为纯函数,不修改调用者的状态

    在由多人维护且未被单元测试正确覆盖的大型代码库中,这个简单的修饰符可以防止悲伤

    所以答案 1 是:静态局部函数确保可靠的调用方方法状态。

    对于我的问题的第 2 部分),@György Kőszeg 说:

    捕获变量会产生少量额外成本,因为它会生成一个内部使用的类型,其中您捕获的变量是公共字段

    他接着给出了一个通过反射器生成的编译器代码的例子。

    所以答案 2 是:静态局部函数防止变量捕获。变量捕获的成本很低。因此,通过声明本地函数为静态的,性能会略有提升

    【讨论】:

    • “因此,通过声明本地函数为静态,性能会有所提升” 不,没有。没有捕获的本地函数无论是否标记为static,其行为都是相同的。 static 只是将您的意图传达给编译器以避免捕获,因此它可以诊断您是否未能兑现承诺。
    猜你喜欢
    • 1970-01-01
    • 2015-06-27
    • 2012-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-13
    • 2011-10-02
    • 2019-01-20
    相关资源
    最近更新 更多