【问题标题】:Case Statement Block Level Declaration Space in C#C# 中的 Case 语句块级声明空间
【发布时间】:2009-07-02 14:10:28
【问题描述】:

我是否有遗漏 case 语句中的块不被视为块级声明空间的原因?

我在尝试时不断收到错误(变量已被声明)

case x:
  var someVariable = 42;
break;
case y: 
   var someVariable = 40;
break;

但我可以做到

case x:
   try{var someVariable = 42;}catch{} 
break;
case y: 
    try{var someVariable = 40;}catch{}
break;

如果 C# 允许通过语句,那将是有意义的,但事实并非如此,而且我想不出您可以在 case 语句中声明一个变量并在该块之外使用它的场景。

【问题讨论】:

标签: c# switch-statement language-design


【解决方案1】:

更新:这个问题被用作这篇博文的灵感;更多详情请查看。

http://ericlippert.com/2009/08/13/four-switch-oddities/

感谢您提出有趣的问题。


在其他各种答案中存在许多混淆和错误陈述,没有一个能够真正解释为什么这是非法的。我会尽量确定。

首先,严格来说,“范围”是用来描述问题的错误词。巧合的是,我上周写了一篇关于“范围”的确切误用的博客文章;这将在我关于迭代器块的系列之后发布,该系列将在整个 7 月运行。

正确使用的术语是“声明空间”。声明空间是一个代码区域,其中不能声明两个不同的事物具有相同的名称。此处描述的场景表明 switch section 没有定义声明空间,但 switch block 可以。 因为 OP 的两个声明在同一个声明空间并且具有相同的名称,它们是非法的。

(是的,开关块定义了一个范围,但该事实与问题无关,因为问题是关于声明的合法性,而不是标识符查找的语义。)

一个合理的问题是“为什么这不合法?”一个合理的答案是“好吧,为什么应该这样”?您可以通过以下两种方式之一获得它。这是合法的:

switch(y)
{
case 1:  int x = 123; ... break;
case 2:  int x = 456; ... break;
}

或者这是合法的:

switch(y)
{
case 1:  int x = 123; ... break;
case 2:  x = 456; ... break;
}

但你不能两种方式都。 C# 的设计者选择了第二种方式,因为这似乎是更自然的方式。

这个决定是在 1999 年 7 月 7 日做出的,距今不到十年。那天的笔记中的 cmets 非常简短,只是简单地说明“一个 switch-case 不会创建自己的声明空间”,然后给出一些示例代码来说明哪些有效,哪些无效。

为了更多地了解设计师在这一天的想法,我不得不让很多人对他们十年前的想法感到困惑——并让他们抱怨最终是一个微不足道的问题;我不会那样做的。

简而言之,没有特别令人信服的理由来选择一种方式或另一种方式;两者都有优点。语言设计团队选择了一种方式,因为他们必须选择一种方式;他们选择的那个在我看来是合理的。

【讨论】:

  • 感谢您的回复。真的,我的问题源于这样一个事实,即我很好奇我是否错过了更深层次的东西,比如“因为 x,你不能这样做。” “我们必须做出决定,就是这样”的想法似乎是一个完全有效的答案,并且可以接受它归结为必须做出决定的事实。
  • 语言设计者选择定义它的方式,你总是可以通过在case语句中大括号来创建一个新的声明空间以获得行为1。如果你最初选择行为1,没有办法进入行为 2。
  • 然后var 出现了。将Tuple 添加到组合中,您就会遇到问题。很久以前做出的决定的不幸结果,但他们无法预见。
  • 20 年后,仍然希望在未来的某个时候,原团队的某个人能够为这个决定提供一些启示。
【解决方案2】:

啊 - 你没有失败,但你可以使用 goto 跳转到另一个标记的案例块。因此,这些块必须在同一范围内。

【讨论】:

  • +1 用于指出这种表示法不可能的实际原因。
  • +1 因为我怀疑,但从未测试过,实际上并不能 100% 确定地知道 goto 可以与案例标签一起使用。 (在我自己的回答中巩固了我空洞的断言,即案例标签确实是标签。:))
  • 我不确定我是否遵循您的逻辑。您是说 case 块不引入范围的原因是 switch(x){case 1: int y = 123;转到案例 2;案例2:y = 456;休息; } 无需重新声明 y 即可工作?
  • 我问的原因是因为如果这是你的论点那么你的逻辑是错误的。明确的分配规则将保证您无法通过“goto”成功地将信息从一个“范围”传递到下一个“范围”。
  • 或者您可能在争论标签必须在整个块的范围内,因此这些部分不能定义自己的范围?这个论点也是错误的;范围嵌套。块中范围内的标签也将在案例范围内。 (就像今天一样;如果你明确定义自己的块,你会发现你可以从它们中“goto case”。)
【解决方案3】:

你也可以这样做:

case x:
  {var someVariable = 42;}
break;
case y: 
   {var someVariable = 40;}
break;

从本质上讲,大括号创建了词法范围,因此没有大括号, someVariable 被加载到符号表中两次。我相信做出这个选择可能只是为了避免混淆,也可能是为了避免增加符号表构建的复杂性。

【讨论】:

  • 这是错误的,会导致docs.microsoft.com/en-us/dotnet/csharp/misc/…,我投反对票
  • @RaidenCore 我认为这个错误根本不相关。这就像您尝试在 if() 的主体内声明变量并且该变量已经存在一样。喜欢int a; if(true) { char a; }
【解决方案4】:

因为 case 不是块,所以没有大括号表示范围。由于没有更好的词,案例就像标签一样。

最好在switch() 语句之外声明变量,然后再使用它。当然,在那种情况下,您将无法使用 var 关键字,因为编译器不知道要初始化什么类型。

【讨论】:

  • 我的问题是为什么它不被视为一个块。为什么非要强行合一呢?
  • 作用域块在 C# 中永远不会是自动的。默认情况下它们不存在。范围块总是用花括号定义。您可能会注意到范围在 for、foreach、if 等语句中生效——当您使用花括号表示范围块时。使用上述语句,如果您省略花括号,它们只会在下一行操作——因为没有通过花括号定义开始和结束的范围。由于 case 不需要花括号,除非您决定插入它们,否则 case 语句使用最近的范围,即 switch 语句。
  • 是的,现在说得通了。我不认为它们是标签,并且很好奇为什么你需要有一个 {} 来强制作用域,而 break 语句可用于确定 case 语句的结尾。总是必须强制作用域是有道理的,因为这也会破坏语言的整体结构。
  • John Rudy,您的分析虽然看似合理,但实际上根本不正确。在 C# 中,作用域并非总是由花括号限定的。 foreach 循环确实引入了一个新范围,即使主体没有大括号。范围在 C# 规范中被非常仔细地定义;如果您想更正您对在 C# 中引入新作用域的确切规则的理解,我鼓励您通读它。
【解决方案5】:

从 C# 8.0 开始,您现在可以使用 switch expressions 执行此操作

您使用 switch 表达式根据与输入表达式的模式匹配来评估候选表达式列表中的单个表达式

var someVariable = caseSwitch switch {
    case x => 42,
    case y => 40,
    _ => throw new Exception("This is how to declare default")
};

【讨论】:

    【解决方案6】:

    你可以在 switch 语句的范围之外声明变量。

    var someVariable;
    
    switch();
    case x:   
    someVariable = 42;
    break; 
    case y:     
    someVariable = 40;
    break;
    

    【讨论】:

    • 你不能这样做,因为编译器不知道someVariable 是什么类型。您必须至少做到 var someVariable = default(int); 。另外,您的代码中有错字,switch() 后面有 ;
    猜你喜欢
    • 1970-01-01
    • 2020-12-27
    • 2011-07-06
    • 1970-01-01
    • 2016-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-10
    相关资源
    最近更新 更多