明确的本地范围 - 任何真正的好处?

时间:2013-06-01 09:50:54

标签: c# scope

我正在清理一些代码并删除了不再需要的if语句。但是,我意识到我忘了删除括号。这当然是有效的,只是创建了一个新的本地范围。现在这让我思考。在我多年的C#开发中,我从未遇到过使用它们的理由。事实上,我有点忘了我能做到。

定义本地范围有什么实际好处吗?我理解我可以在一个范围内定义变量,然后在不相关的范围(for,foreach等)中再次定义相同的变量,如下所示:

void SomeMethod()
{    
    {
        int i = 20;
    }

    int i = 50; //Invalid due to i already being used above.
}

void SomeMethod2()
{    
    {
        int i = 20;
    }
    {
        int i = 50; //Valid due to scopes being unrelated.
    }
    {
        string i = "ABCDEF";
    }
}

定义本地范围的真正意义何在?实际上是否可以获得任何类型的性能提升(或潜在的损失)?我知道你可以用C ++做这件事,并且是帮助你管理内存的一部分,但是因为这是.NET,真的会有好处吗?这只是语言的双重产品,让我们定义随机范围即使没有真正的好处吗?

5 个答案:

答案 0 :(得分:8)

在C#中,将一组语句转换为单个语句纯粹是语法。对于需要遵循单个语句的任何关键字都是必需的,例如if,for,using等。一些极端情况:

  • 交换机内的 case 关键字是特殊的,因为它不要求它是单个语句。 中断 goto 关键字结束它。这解释了为什么你可以使用大括号来阻塞变量声明。
  • 尝试 catch 关键字是特殊的,即使后面只有一个语句,它们需要括号。非常不寻常,但可能是因为强迫程序员考虑块内部的声明范围,因为异常处理的工作方式,catch块不能引用try块内的变量。

用它来限制局部变量的范围是一个失败的原因。它在C ++中是一个大问题,因为结束括号是编译器将在范围块内为变量注入析构函数调用的地方。对于RAII模式来说,这是ab /一直使用,没有什么比计划中的标点符号具有如此剧烈的副作用还要非常漂亮。

C#团队对此没有太多选择,局部变量的生命周期严格受抖动控制。这对于方法中的任何分组构造都是遗忘的,它只知道IL。除了try / except / finally之外没有任何分组结构。任何局部变量的范围,无论它在何处编写,都是方法的主体。在编译的C#代码上运行ildasm.exe时可以看到的东西,你会看到局部变量被提升到方法体的顶部。这部分也解释了为什么C#编译器不允许您在另一个具有相同名称的范围块中声明另一个局部变量。

抖动有关于局部变量生命周期的有趣规则,它们完全由垃圾收集器的工作方式决定。当它运行方法时,它不仅生成方法的机器代码,而且还创建一个表,该表描述每个局部变量的实际范围,初始化的代码地址以及不再使用它的代码地址。垃圾收集器根据活动执行地址使用该表来确定对对象的引用是否有效。

这使得非常有效地收集对象。当您与本机代码互操作时,有时太有效且麻烦,您可能需要神奇的GC.KeepAlive()方法来延长生命周期。这是一种非常了不起的方法,它根本不会产生任何代码。它唯一的用途是让抖动改变表并为变量的生命周期插入一个更大的地址。

答案 1 :(得分:4)

就像函数一样,这些“块”几乎只是为了隔离(大部分)无关代码的区域及其函数内的局部变量。

如果只需要在两个函数调用之间传递一些临时变量,可以使用它,例如

int Foo(int a) {

    // ...

    {
        int temp;
        SomeFuncWithOutParam(a, out temp);

        NowUseThatTempJustOnce(temp);            
    }

    MistakenlyTryToUse(temp);    // Doesn't compile!

    // ...

}
然而,有人可能会说,如果你需要这种词法范围,内部块应该是他们自己的功能。

至于表现等,我非常怀疑它是否重要。编译器将整个函数视为一个整体,并在确定堆栈帧大小时收集所有局部变量(甚至是在线声明的变量)。所以基本上所有局部变量都集中在一起。对你的变量的使用给你一点约束是一个纯粹的词汇。

答案 2 :(得分:3)

有一个地方范围可能有用的地方:case语句的switch个语句。默认情况下,所有案例共享与switch语句相同的范围。 不允许在多个case语句中声明具有相同名称的本地临时变量,并且最终只能在第一个case语句中或甚至在switch - 语句之外声明变量。 您可以通过为每个case语句提供一个局部作用域并在作用域内声明临时变量来解决此问题。 但是,不要使您的案例过于复杂,这个问题可能表明最好调用一个单独的方法来处理case - 语句。

答案 3 :(得分:2)

优势主要在于它使语言的定义更简单。

现在,definiton可以简单地说明if,while,for等......应该后跟一个语句。括号内的一组语句只是一种可能的语句。

您的示例中使用的禁止语句块没有真正的好处。有时它们对避免名字冲突很有用,但你可以在没有的情况下解决问题。与C,C ++和Java等语言相比,它也会不必要地引入语法规则的差异。

如果您想知道它也不会改变引用对象的对象生命周期。

答案 4 :(得分:0)

至少在发布模式下不会有性能提升:GC可以收集对象,如果它知道 - 或者至少认为 - 它将不再使用,无论范围如何。这可能会使您无法管理互操作:http://blogs.msdn.com/b/oldnewthing/archive/2010/08/13/10049634.aspx

那就是说,我不时使用这个“功能”来确保代码后面的代码不能使用一些临时对象。在大多数情况下,它可能通过分成其他方法来完成,但偶尔会有不必要的笨拙,或者由于某种原因不可能(例如在你正在设置只读成员的构造函数中)。有一段时间我用它来重用变量名,但这通常与“临时对象”的原因相结合。