什么时候在C#中使用没有语句的范围?
就在最近,我发现你可以在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) ); }
而不是ie:
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) );
这可能是一个很好的例子,可以通过许多其他方式解决。
是否有任何模式,“范围无声明”function是一个好习惯?
如果你问为什么他们实现了这个function?
我已经开发了自己的语言,我发现的是“没有语句的范围”语法很容易融入语法。 如果你愿意的话,这是一个副作用,当你编写一个效果很好并且易于阅读的语法时 。
在我的语言中,我具有相同的function,而且我从未“明确地”为它“设计” – 我是免费获得的 。 我从来没有坐在我的桌子上,并且想“哦,拥有这样的’function’不是很酷吗?”。 哎呀 – 起初,我甚至都不知道我的语法允许这样做。
‘{}’是一个“复合语句”,因为它简化了你想要使用它的所有地方的语法(条件,循环体等)……因为这可以让你在单个语句时省略括号被控制(’if(a <10)++ a;'等)。
它可以在声明可以出现的任何地方使用的事实直接落在其中; 它是无害的,偶尔也会像其他答案所说的那样有用。 “如果没有破产,请不要修理它。 – keshlam。
因此,问题不在于“他们为什么要实施它”,而是“为什么他们不禁止它/他们为什么允许这样做?”
我可以用我的语言禁用这个特定的function吗? 当然,但我没有理由 – 这对公司来说是额外的成本。
现在,上面的故事可能或者可能不适用于C#,但我没有设计语言(我也不是真正的语言设计师),所以很难说出原因,但我想我还是会提到它。
C ++中的function相同,实际上有一些用例 – 如果对象超出范围,它允许确定性地运行对象的终结器,尽管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; } }
在许多情况下我认为可接受的一种用法是将switch
语句的每个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); } }
这是在发布模式下编译的(启用了优化)。 根据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字节码中不存在任何forms的局部范围声明。
- C#编译器将为本来存在冲突的局部变量选择新名称(
C
方法中的第二个变量o
)。 - 感觉(看到其他答案和评论有疑问)垃圾收集器在C#源中引入本地范围时可能能够更早地完成工作的人是错误的。
我也尝试在调试模式下编译它(没有优化)。 本地范围的开始和结束似乎仅显示为nop
指令 (“无操作”)。 在某些情况下,来自不同本地范围的两个同名的局部变量被映射到IL中的相同局部变量,就像上面名为C
C#方法一样。 两个变量的这种“统一”只有在它们的类型兼容时才有可能。
您可以使用{}
来重新定位变量名称(即使是不同的类型):
{ 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("#"));
我相信这意味着,没有应用#fallback的风险,并且不需要显式本地可变变量来保持状态。
(或new MenuItem(value > 45 ? "http://cheese.com" : "#")
,或者使用默认值#
创建MenuItem
的重载,或者将MenuItem
的创建移动到工厂方法等等。)
编辑
回复:范围对寿命没有影响
可以在方法中使用范围来限制昂贵对象的寿命
我的post错误地说明了本地范围可能会影响对象的生命周期。 这对于DEBUG
和RELEASE
构建都是不正确的,无论是否重新分配变量名称,如Jeppe的IL反汇编所示,以及这些unit testing 。 感谢Jeppe指出这一点。 此外,Lasse指出,即使没有明确地超出范围,不再使用的变量也有资格在发布版本中进行垃圾收集。
TL; DR虽然使用未经请求的范围可能有助于将变量范围的逻辑用途传达给人类,但这样做对于对象是否有资格在同一方法中进行收集没有影响。
即在下面的代码中,范围,甚至重新设置下面的变量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
构建中,尽管hacky试图诱使GC这样做,但都不会收集任何foo
变量。 - 在
RELEASE
版本中,无论范围如何,两个foos都可以在不需要时立即进行收集。 收集的时间应该留给自己的设备。
这种模式对C#的运行时几乎没有影响,所以它纯粹是一种美学的东西(与C ++相比,我们经常将这种模式与RAII一起使用来锁定诸如锁之类的东西)。
如果我有两个完全不相关的代码块,我有时会以这种方式对它们进行范围化,以使其100%清楚程序员必须保留在头部的变量“可能在前一个块中被修改。它填补了之间的差距 – 大代码块和隔离函数;我可以分享一些变量而不是其他变量。
我也将在自动生成的代码周围使用它。 使用这种可插拔块通常更容易,而不必担心交互。
当我使用它时,我在风格上喜欢在每个块之前添加注释,大致在if
语句的位置,解释块将执行的操作。 我发现它有助于避免其他开发人员认为“这看起来像曾经是控制流程,但有人惹恼了它。” 在这种情况下,它可能有点矫枉过正,但你会明白:
// 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#中纯粹是风格。 在有意义的地方随意使用自己的风格。
{ 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!";