为什么在 C# 中不能在没有大括号的开关部分中使用 using 变量?



考虑以下代码:

switch ("")
{
case "":
using var s = new MemoryStream();
break;
}

上面的代码编译时不会出现以下错误:;using变量不能直接在开关部分内使用(考虑使用大括号(";。

修复方法已经在建议中了,但我的问题是,为什么下面的代码是合法的,而上面的代码不是?为什么C#不能只允许前面的代码?

switch ("")
{
case "":
{
using var s = new MemoryStream();
}

// break can be inside or outside the braces
break;
}

C#8.0针对新using语句的语言建议给出了以下解释:

using声明直接在case标签内是非法的,因为它的实际使用寿命很复杂。一个潜在的解决方案是简单地给它与相同位置的out var相同的寿命。人们认为,功能实现的额外复杂性和解决问题的容易性(只需在case标签上添加一个块(并不能证明采取这种方法是合理的。


因此,作为一个例子,考虑这个。。。

switch( foo )
{
case 1: // Yeah, I'm in the tiny minority who believe `case` statements belong in the same column as the `switch` keyword.
case 2:
using FileStream fs1 = new FileStream( "foo.dat" );
goto case 4;
case 3:
using FileStream fs3 = new FileStream( "bar.dat" );
goto case 1;
case 4:
using FileStream fs4 = new FileStream( "baz.dat" );
if( GetRandomNumber() < 0.5 ) goto case 1;
else break;
}

相当于这个伪代码(忽略顺序if逻辑(:

if( foo == 1 || foo == 2 ) goto case_1;
else if( foo == 3 ) goto case_3;
else if( foo == 4 ) goto case_4;
else goto after;
{
case_1:
using FileStream fs1 = new FileStream( "foo.dat" );
goto case_4;
case_3:
using FileStream fs3 = new FileStream( "bar.dat" );
goto case_1;
case_4:
using FileStream fs4 = new FileStream( "baz.dat" );
if( GetRandomNumber() < 0.5 ) goto case_1;
else goto after;
}
after:

说明书上写着";具有与在using语句中在相同位置声明变量相同的效果&";,因此,如果我正确理解规范,上面的代码将与以下代码相同:

if( foo == 1 || foo == 2 ) goto case_1;
else if( foo == 3 ) goto case_3;
else if( foo == 4 ) goto case_4;
else goto after;
{
case_1:
using( FileStream fs1 = new FileStream( "foo.dat" ) )
{
goto case_4;
case_3:
using( FileStream fs3 = new FileStream( "bar.dat" ) )
{
goto case_1;
}

case_4:
using( FileStream fs4 = new FileStream( "baz.dat" ) )
{
if( GetRandomNumber() < 0.5 ) goto case_1;
else goto after;
}
}
}
after:

认为问题是:

  • 虽然从case_4case_1的跳跃被定义为导致fs4的处置
  • 。。。还不清楚。。。
    • 当控件遇到goto case_4时(在case_1:之后(是否应立即处置fs1
    • 当从case_1:跳到case_4:时,是否应该初始化fs3,因为它在范围内(忽略它没有使用的事实(
    • fs1case 3case 4中是否应该初始化,尽管严格来说它在范围内

因为链接的规范建议只显示一个向后goto到一个using块之前的点(因此using语句的主题将不在范围内(,而在这种情况下,当向前跳转时,fs1fs3是否仍在范围内(或不在(还没有明确定义。

请记住,using;表示,当对象超出作用域时,应该对其进行处理,而不是说,当对象最后一次在其声明的作用域中使用时,应该将其进行处理(这将禁止将其传递给仍在使用它的另一个方法(。

对于可能/应该发生的事情,至少有两个论点:

  1. 一旦fs1跳到case_3,就立即处理它,即使fs1在技术上仍在作用域中(如果您订阅了"所有案例共享相同的作用域"学派(。

    • 这也忽略了using;语句的要点,该语句将其主体的生存期严格绑定到封闭范围
  2. 当控制离开整个switch块时,仅处理fs1(即使可以说fs1在此之前超出了范围(。

正如提案所提到的,考虑到语言设计团队所面临的时间限制,这是一件本可以敲定的事情,但可能不会得到人们的同意。

您需要限制变量的范围

switch ("")
{
case "":
using(var s = new MemoryStream())
{
// SCOPE of var S
}

// break can be inside or outside the braces
break;
}

最新更新