就在最近我发现你可以在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) );
}
Run Code Online (Sandbox Code Playgroud)
而不是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) );
Run Code Online (Sandbox Code Playgroud)
这可能是一个很好的例子,可以通过许多其他方式解决.
是否有任何模式,"范围无声明"功能是一个好习惯?
Jep*_*sen 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);
}
}
Run Code Online (Sandbox Code Playgroud)
这是在发布模式下编译的(已启用优化).根据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
Run Code Online (Sandbox Code Playgroud)
.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
Run Code Online (Sandbox Code Playgroud)
.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
Run Code Online (Sandbox Code Playgroud)
结论:
o
的C
方法).我也尝试在调试模式下编译它(没有优化).本地范围的开始和结束似乎仅显示为nop
指令("无操作").在某些情况下,来自不同本地范围的两个同名的局部变量被映射到IL中的相同局部变量,就像C
上面提到的C#方法一样.这两个变量的"统一"只有在它们的类型兼容时才有可能.
Ert*_*maa 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;
}
}
Run Code Online (Sandbox Code Playgroud)
Stu*_*tLC 11
您可以使用{}
来重新定位变量名称(即使是不同的类型):
{
var foo = new SomeClass();
}
{
Bar foo = new Bar(); // impairs readability
}
Run Code Online (Sandbox Code Playgroud)
但是,以这种方式重新调整变量会混淆可读性.
因此,在没有前面的语句的情况下,不使用"未经请求的"范围块,在大多数情况下,代码应该相应地重构为单独的函数.
编辑
IMO,任何时候都需要强制重置本地可变变量值或将其重新用于其他或替代问题,这是一种气味的迹象.例如,原始代码可以重构为:
menu.Add( value > 5
? new MenuItem("http://google.com")
: new MenuItem("#"));
menu.Add( value > 45
? new MenuItem("http://cheese.com")
: new MenuItem("#"));
Run Code Online (Sandbox Code Playgroud)
我认为这#
意味着传达意图,没有应用后备的风险,并且不需要显式的本地可变变量来保持状态.
(或者new MenuItem(value > 45 ? "http://cheese.com" : "#")
,或者MenuItem
使用默认值创建重载#
,或者将创建移动MenuItem
到工厂方法中等)
编辑
Re:范围对寿命没有影响
可以在方法中使用范围来限制昂贵对象的寿命
我的帖子错误地说明了本地范围可能会影响对象的生命周期.这对于两者DEBUG
和RELEASE
构建都是不正确的,无论变量名是否被重新分配,如Jeppe的IL反汇编所示,以及这些单元测试.感谢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"
}
Run Code Online (Sandbox Code Playgroud)
在X点:
DEBUG
构建中,foo
尽管hacky试图诱使GC这样做,但两个变量都不会被收集.RELEASE
构建,既FOOS本来有资格就不需要他们收集,不论范围.收集的时间当然应该留给自己的设备.