C#中匿名未连接块的价值是什么?

11

在C#中,您可以在方法内创建一个不与任何其他语句相关联的代码块。

    public void TestMethod()
    {
        {
            string x = "test";
            string y = x;

            {
                int z = 42;
                int zz = z;
            }
        }
    }

这段代码编译和运行的结果就好像main方法内部的大括号不存在一样。同时注意到有一个块在另一个块内部。

是否存在某种情况下这样做会很有价值?我还没有发现,但很想听听其他人的想法。

10个回答

10

作用域和垃圾回收: 当您离开未连接的块时,其中声明的任何变量的作用域将结束。这使得垃圾收集器可以清理这些对象。

Ray Hayes 指出,.NET垃圾收集器不会立即收集超出范围的对象,因此作用域是最主要的好处。


1
你是否在虚拟机层面上进行了验证?大多数合理的编译器和垃圾回收器在编译过程中会倾向于将这两个块合并为一个,并且可以在最后引用它的变量的最后使用后对值进行垃圾回收。 - emk
我尚未验证垃圾回收,但我已验证了作用域。我非常确定.NET垃圾回收器将收集已离开作用域/未被引用的内容。 - Ed Schwehm
2
垃圾回收可能会启动,但可能需要一段时间!作用域的丢失并不意味着自动进行垃圾回收。此外,在这种情况下,对于值类型,没有任何需要进行垃圾回收的内容。作用域是主要目的,而不是垃圾回收。 - Ray Hayes

4

举个例子,如果你想要重复使用一个变量名,通常情况下是不允许的。 这是无效的。

        int a = 10;
        Console.WriteLine(a);

        int a = 20;
        Console.WriteLine(a);

但这是:

    {
        int a = 10;
        Console.WriteLine(a);
    }
    {
        int a = 20;
        Console.WriteLine(a);
    }

我现在唯一能想到的例子是,如果你正在处理一些大型对象,并从中提取了一些信息,然后要执行一系列操作,你可以将大型对象处理放在一个块内,这样它就会超出范围,然后继续进行其他操作。

    {
        //Process a large object and extract some data
    }
    //large object is out of scope here and will be garbage collected, 
    //you can now perform other operations with the extracted data that can take a long time, 
    //without holding the large object in memory

    //do processing with extracted data

3

这是语法解析规则的副产品,即语句可以是简单语句或块。也就是说,块可以在任何单个语句可以使用的地方使用。

例如:

if (someCondition)
 SimpleStatement();

if (SomeCondition)
{
   BlockOfStatements();
}

其他人指出变量声明在包含块的末尾仍然在作用域内。对于临时变量拥有短暂的作用域是很好的,但我从未必须使用单独的块来限制变量的作用域。有时您会在“using”语句下面使用块来实现这一点。

因此,通常情况下并没有太大价值。


2

它存在的一个实际原因是,当没有必要引入任何其他块的原因时,如果您想限制某些变量的范围,则可以使用它。实际上,在实践中,这几乎从来没有用过。

就我个人而言,我猜测从语言/编译器的角度来看,很容易说你可以在期望语句的任何位置放置一个块,他们只是没有刻意阻止您在没有if/for/method声明等情况下使用它。

考虑Eric Lippert最近的博客文章this recent blog post的开头。if语句后面不是跟随单个语句或由花括号括起来的多个语句,而是跟随单个语句。每当您在花括号中包含0到N个语句时,您使该代码段等效(从语言解析器的角度来看)为一个语句。同样的做法也适用于所有循环结构,尽管正如博客文章的主要观点所解释的那样,它不适用于try/catch/finally块。

从这个角度来看,处理块的问题就变成了:“是否有充分的理由防止块被用在任何可以使用单个语句的地方?”答案是:“没有”。


1

这使您可以在任何地方创建作用域块。它本身并不是很有用,但可以使逻辑更简单:

switch( value )
{
    case const1: 
        int i = GetValueSomeHow();
        //do something
        return i.ToString();

    case const2:
        int i = GetADifferentValue();
        //this will throw an exception - i is already declared
 ...

在C#中,我们可以使用作用域块,以便在每个case下声明的项仅在该case中有效:
switch( value )
{
    case const1: 
    {
        int i = GetValueSomeHow();
        //do something
        return i.ToString();
    }

    case const2:
    {
        int i = GetADifferentValue();
        //no exception now
        return SomeFunctionOfInt( i );
    }
 ...

这也适用于goto和label,尽管在C#中你很少使用它们。


1
据我所见,这只对组织架构有用。我真的无法想象做那件事情的任何逻辑价值。也许有人会有一个合适的例子。

0

这个例子中除了语义和作用域以及垃圾回收之外,没有任何实际价值。如果您认为这样可以使代码更清晰易懂,那么您当然可以使用它。然而,在代码中进行语义澄清的更常见做法通常是仅使用换行符和内联注释:

public void TestMethod()
{
    //do something with some strings
    string x = "test";
    string y = x;

    //do something else with some ints
    int z = 42;
    int zz = z;
}

0
这样做的一个原因是变量 'z' 和 'zz' 将不会在内部块结束后的代码中可用。在Java中这样做时,JVM为内部代码推送栈帧,这些值可以存在于堆栈上。当代码退出块时,堆栈帧被弹出,这些值消失。根据所涉及的类型,这可以让你避免使用堆和/或垃圾回收。

0
在C#中,就像在c/c++/java中一样,大括号表示范围。这决定了变量的生命周期。当到达关闭大括号时,该变量立即变为可供垃圾回收使用。在c++中,如果var表示一个实例,它会导致调用类的析构函数。
至于用法,唯一可能的用途是释放大对象,但说实话,将其设置为null将具有相同的效果。我怀疑前一种用法可能只是为了使c++程序员能够转移到管理代码时感到熟悉和舒适。如果真的想在C#中调用“析构函数”,通常要实现IDisposable接口并使用“using (var) {...}”模式。
Oisin

0
即使它确实有用(例如变量作用域控制),从良好的代码可读性的角度来看,我也不建议您使用这样的结构。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接