什么时候在C#中使用没有语句的范围?

时间:2014-12-13 11:08:01

标签: c# syntax block

就在最近,我发现你可以在C#中执行此操作:

{
    // google
    string url = "#";

    if ( value > 5 )
        url = "http://google.com";

    menu.Add( new MenuItem(url) );
}
{
    // cheese
    string url = "#"; // url has to be redefined again, 
                      // so it can't accidently leak into the new menu item

    if ( value > 45 )
        url = "http://cheese.com";

    menu.Add( new MenuItem(url) );
}

而不是..:

    string url = "#";

    // google
    if ( value > 5 )
        url = "http://google.com";

    menu.Add( new MenuItem(url) );


    // cheese
    url = "#"; // now I need to remember to reset the url

    if ( value > 45 )
        url = "http://cheese.com";

    menu.Add( new MenuItem(url) );

这可能是一个很好的例子,可以通过许多其他方式解决。

是否有任何模式的范围没有声明'功能一个好习惯吗?

5 个答案:

答案 0 :(得分:23)

在许多情况下我认为可接受的一种用法是将switch语句的每个开关部分包含在本地范围内。


延迟补充:

C#源中存在的本地范围块{ ... }似乎与生成的IL字节码无关。我尝试了这个简单的例子:

static void A()
{
    {
        var o = new object();
        Console.WriteLine(o);
    }

    var p = new object();
    Console.WriteLine(p);
}

static void B()
{
    var o = new object();
    Console.WriteLine(o);

    var p = new object();
    Console.WriteLine(p);
}


static void C()
{
    {
        var o = new object();
        Console.WriteLine(o);
    }

    {
        var o = new object();
        Console.WriteLine(o);
    }
}

这是在 Release 模式下编译的(启用了优化)。根据IL DASM得到的IL是:

.method private hidebysig static void  A() cil managed
{
  // Code size       25 (0x19)
  .maxstack  1
  .locals init ([0] object o,
           [1] object p)
  IL_0000:  newobj     instance void [mscorlib]System.Object::.ctor()
  IL_0005:  stloc.0
  IL_0006:  ldloc.0
  IL_0007:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_000c:  newobj     instance void [mscorlib]System.Object::.ctor()
  IL_0011:  stloc.1
  IL_0012:  ldloc.1
  IL_0013:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0018:  ret
} // end of method LocalScopeExamples::A

.method private hidebysig static void  B() cil managed
{
  // Code size       25 (0x19)
  .maxstack  1
  .locals init ([0] object o,
           [1] object p)
  IL_0000:  newobj     instance void [mscorlib]System.Object::.ctor()
  IL_0005:  stloc.0
  IL_0006:  ldloc.0
  IL_0007:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_000c:  newobj     instance void [mscorlib]System.Object::.ctor()
  IL_0011:  stloc.1
  IL_0012:  ldloc.1
  IL_0013:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0018:  ret
} // end of method LocalScopeExamples::B

.method private hidebysig static void  C() cil managed
{
  // Code size       25 (0x19)
  .maxstack  1
  .locals init ([0] object o,
           [1] object V_1)
  IL_0000:  newobj     instance void [mscorlib]System.Object::.ctor()
  IL_0005:  stloc.0
  IL_0006:  ldloc.0
  IL_0007:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_000c:  newobj     instance void [mscorlib]System.Object::.ctor()
  IL_0011:  stloc.1
  IL_0012:  ldloc.1
  IL_0013:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0018:  ret
} // end of method LocalScopeExamples::C

结论:

  • IL字节码中不存在任何形式的本地范围声明。
  • C#编译器将为本来存在冲突的局部变量选择新名称(o方法中的第二个变量C。)
  • 当他们在C#源中引入本地作用域时,感觉(请参阅其他答案和评论有疑问)垃圾收集器可能能够更早地完成其工作的人是错误的。

我也尝试在 Debug 模式下编译它(没有优化)。本地范围的开始和结束似乎仅显示为nop instruction(“无操作”)。在某些情况下,来自不同本地范围的两个同名的局部变量被映射到IL中的相同的局部变量,就像上面名为C的C#方法一样。这两个变量的“统一”只有在它们的类型兼容时才有可能。

答案 1 :(得分:12)

如果你问他们为什么要实现这个功能?

我已经开发了自己的语言,我发现的是“没有语句的范围”语法很容易融入语法。 如果你愿意的话,这会产生一种副作用,当你编写一个效果很好的语法时。很容易阅读

在我的语言中,我具有相同的功能,而且我从未“明确地”设计“ - 我免费获得。我从来没有坐在我的桌子上,并且想“哦,拥有这样的'功能'不是很酷吗?”。哎呀 - 起初,我甚至不知道我的语法允许这样做。

  

'{}'是一个“复合语句”,因为它简化了语法   所有你想要使用的地方(条件,循环体,   等等......因为这可以让你在单身时省去牙箍   正在控制语句('if(a< 10)++ a;'等)。

     

它可以在语句出现的任何地方使用这一事实   直接出于那个;它是无害的,偶尔也会像其他一样有用   答案说。 “如果没有破产,请不要修理它。 - keshlam。

所以,问题不在于“为什么他们实施它”,而是“为什么他们不禁止它/他们为什么允许这样做?”

我可以用我的语言禁用此特定功能吗?当然,但我没有理由 - 这对公司来说是额外的成本。

现在,上述故事可能或者可能不适用于C#,但我没有设计语言(我也不是真正的语言设计师),所以很难说出原因,但我想我会提到它无论如何。

C ++中的相同功能实际上有一些用例 - 如果对象超出范围,它允许确定性地运行对象的终结器,尽管C#不是这种情况。


那就是说,我在4年的C#(嵌入式语句 - >块,在谈到具体的终端时)中没有使用那种特定的语法,我也没看到它被使用过任何地方。你的例子是要求对方法进行重构。

看一下C#语法:http://msdn.microsoft.com/en-us/library/aa664812%28v=vs.71%29.aspx

另外,正如Jeppe所说,我使用了'{}'语法,以确保'switch'构造中的每个case-block都有独立的局部范围:

int a = 10;
switch(a)
{
    case 1:
    {
        int b = 10;
        break;
    }

    case 2:
    {
        int b = 10;
        break;
    }
}

答案 2 :(得分:11)

您可以使用{}重新定位变量名称(即使是不同类型):

{
   var foo = new SomeClass();
}
{
   Bar foo = new Bar(); // impairs readability
}

但是,以这种方式重新调整变量会使可读性变得混乱。

因此,在没有前面的语句的情况下,不使用“未经请求的”范围块,在大多数情况下,代码应该相应地重构为单独的函数。

修改

IMO,任何时候都需要强制重置本地可变变量值或将其重新用于其他或替代问题,这是一种嗅觉的迹象。例如原始代码可以重构为:

menu.Add( value > 5 
            ? new MenuItem("http://google.com")
            : new MenuItem("#"));

menu.Add( value > 45 
            ? new MenuItem("http://cheese.com")
            : new MenuItem("#"));

我认为这样可以传达意图,而不会有#后备不被应用的风险,也不需要显式的本地可变变量来保持状态。

(或new MenuItem(value > 45 ? "http://cheese.com" : "#"),或创建MenuItem的重载,默认值为#,或将MenuItem的创建移动到工厂方法等中。)< / p>

修改
Re:范围对生命周期没有影响

范围可以在方法中用于限制昂贵对象的生命周期

我的帖子错误地说明了本地范围可能会影响对象的生命周期。这对于DEBUGRELEASE版本都是不正确的,无论是否重新分配变量名称,如Jeppe的IL反汇编和Unit tests here所示。感谢Jeppe指出这一点。此外,Lasse指出,即使没有明确地超出范围,不再使用的变量也有资格在发布版本中进行垃圾收集。

TL; DR 虽然使用未经请求的范围可能有助于将变量范围的逻辑用途传达给人类,但这样做会对该对象是否符合条件影响 no 用于收集,在同一方法中。

,即在下面的代码中,范围确定,甚至重新设定下面的变量foo对生命周期没有任何影响。

void MyMethod()
{
  // Gratuituous braces:
  {
      var foo = new ExpensiveClass();
  }
  {
      Bar foo = new Bar(); // Don't repurpose, it impairs readability!
  }
  Thread.Sleep(TimeSpan.FromSeconds(10));
  GC.Collect();
  GC.WaitForPendingFinalizers();
  <-- Point "X"
}

在第X点:

  • DEBUG版本中,尽管有诱骗GC这样做,但两个foo变量都不会被收集。
  • RELEASE版本中,无论范围如何,只要不需要,两个foos都将符合条件进行收集。收集的时间应留给自己的设备。

答案 3 :(得分:3)

这种模式对C#的运行时几乎没有影响,所以它纯粹是一种美学的东西(与C ++相比,我们经常将这种模式与RAII一起使用来锁定诸如锁之类的东西。)

如果我有两个完全不相关的代码块,我有时会以这种方式对它们进行范围化,以使其100%清楚程序员必须保留的变量,并且可以在前一个块中进行修改。它填补了大代码块和隔离函数之间的空白;我可以分享一些变量而不是其他变量。

我也会在自动生成的代码周围使用它。使用这种可插拔块通常更容易,而不必担心交互。

当我使用它时,我在风格上喜欢在每个块之前添加注释,大致是if语句将要去的地方,解释该块将执行的操作。我发现它有助于避免其他开发人员思考&#34;这看起来像曾经是控制流程,但有人惹恼了它。&#34;在这种情况下,它可能有点矫枉过正,但你会得到这个想法:

// Add a menu item for Google, if value is high enough.
{
    string url = "#";

    if ( value > 5 )
        url = "http://google.com";

    menu.Add( new MenuItem(url) );
}

// Add a menu item for Cheese, if the value is high enough
{
    // cheese
    string url = "#";

    if ( value > 45 )
        url = "http://cheese.com";

    menu.Add( new MenuItem(url) );
}

如前所述,这在C#中纯粹是风格。在有意义的地方随意使用自己的风格。

答案 4 :(得分:0)

{
    string f = "hello";
}

看起来很奇怪。

显然,方法需要它们:

private void hmm() {}

切换声明:

switch(msg)
{
    case "hi":
    // do something
    break;

    // ...
}

即使,为了,foreach,while声明......

if(a == 1)
{
    bool varIsEqual = true;
    isBusy = true;
    // do more work
}

但是如果你在循环或if语句中只有1个语句,那么你不需要大括号:

if("Samantha" != "Man")
    msg = "Phew!";