我们来看下面的代码:
class Foo
{
string bar;
public void Method()
{
if (!String.IsNullOrEmpty(this.bar))
{
string bar = "Hello";
Console.Write(bar);
}
}
}
这将编译,一切都很好。但是,我们现在删除this.
前缀:
class Foo
{
string bar;
public void Method()
{
if (!String.IsNullOrEmpty(bar)) // <-- Removed "this."
{
string bar = "Hello";
Console.Write(bar);
}
}
}
在这种情况下,我收到编译错误。我同意这是一个错误,但是错误的位置让我感到困惑。错误发生在该行:
string bar = "Hello";
显示消息:
在此范围内无法声明名为“bar”的局部变量,因为 它会给'bar'赋予不同的含义,'bar'已经在a中使用了 '父母或当前'范围表示其他内容
根据我对编译器的理解,bar
的声明将悬挂添加到Method()
方法的顶部。但是,如果是这种情况,则行:
if (!String.IsNullOrEmpty(bar))
应该被认为是不明确的,因为bar
可以是对实例字段或尚未声明的局部变量的引用。
对我而言,删除this.
会导致另一行编译错误似乎很奇怪。换句话说,声明本地bar
变量是完全有效的,只要之前没有对bar
的潜在模糊引用范围(注意,如果我注释掉if (!String.IsNullOrEmpty(bar))
,那么错误就会消失。)
这一切似乎都很迂腐,所以你的问题是什么?:
我的问题是为什么编译器在变量声明之前允许对变量进行模糊引用,然后将声明本身标记为冗余。 bar
中对String.IsNullOrEmpty()
的含糊不清的引用不应该是错误的更精确位置吗?在我的例子中,它当然很容易被发现,但是当我在野外遇到这个问题时,引用就是页面并且更难以追踪。
答案 0 :(得分:11)
根据我对编译器的理解,bar的声明被提升到Method()方法的顶部。
不,事实并非如此。
错误信息非常精确:
名为'bar'的局部变量不能在此范围内声明,因为它会给'bar'赋予不同的含义,'bar'已在'父或当前'范围中用于表示其他内容
违反C#规范的部分是第7.6.2.1节(C#4和5规范):
块中的不变含义
对于给定标识符的每次出现,作为表达式或声明符中的完整简单名称(没有类型参数列表),在局部变量声明空间(第3.3节)内立即包围该出现,每隔一次出现相同的标识符作为表达式或声明符中的完整简单名称必须引用同一实体。此规则确保在给定块,switch块,for-,foreach-或using-statement或匿名函数中名称的含义始终相同。
在带注释的C#规范中,这有一个来自Eric Lippert的有用注释:
这条规则的一个更微妙的理想结果是,进行涉及绕地方变量声明的重构变得更加安全。任何会导致简单名称改变其语义的重构都将被编译器捕获。
除了其他任何事情之外,在我看来,这只是为了清晰。即使第二个版本被允许,第一个版本也是更清晰的IMO。编译器确保你不会编写病态不清楚的代码,当你可以很容易地修复它时,你的意思很明显。
换句话说:真的是否希望能够编写第二个版本?
特别是:
在我的例子中,它当然很容易被发现,但是当我在野外遇到这个问题时,引用就是页面并且更难以追踪。
...这使得更多合理地允许它?恰恰相反,我会说 - 你也应该把它视为重构你的代码的强烈鼓励,这样单个方法就不会“长篇”了。
答案 1 :(得分:1)
bar的第二个定义未被拉到方法级别,其范围是if块。例如,这是完全有效的。
class Foo
{
private string bar;
public void Method()
{
if (!String.IsNullOrEmpty(bar)) // <-- No more this.
{
string bar1 = "Hello";
Console.Write(bar);
}
if (!String.IsNullOrEmpty(bar)) // <-- No more this.
{
string bar1 = "Hello";
Console.Write(bar);
}
}
}
原因是bar
不再含糊不清,bar
/ bar1
的外部和内部范围之间有明确的名称区别 - 编译器不允许覆盖外部作用域{ {1}}通过本地定义。