C# 8.0's language proposal for the new using
statement gives this explanation:
A using
declaration is illegal directly inside a case
label due to complications around its actual lifetime. One potential solution is to simply give it the same lifetime as an out var
in the same location. It was deemed the extra complexity to the feature implementation and the ease of the work around (just add a block to the case
label) didn't justify taking this route.
So as an example, consider that this...
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;
}
...is equivalent to this pseudocode (ignoring the sequential if
logic):
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:
...which the spec says "has the same effect as declaring the variable in a using
statement at the same location.", so if I understand the spec correctly, the above code would be the same as this:
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:
I think the problem is:
- While the jump from
case_4
to case_1
is well-defined to cause fs4
's disposal...
- ...it's unclear...
- Whether
fs1
should be disposed immediately when control encounters goto case_4
(after case_1:
)
- Whether
fs3
should be initialized at all when jumping from case_1:
to case_4:
as it would be in scope (disregarding the fact it isn't used).
- Whether
fs1
should be initialized when in case 3
and case 4
, even though it would strictly-speaking be in scope.
Because the linked specification proposal only shows a backwards goto
to a point before a using
block (thus that using
statement's subject would be out-of-scope), whereas in this case it is not well-defined if fs1
and fs3
is still in-scope (or not) when jumping forward.
Remember that using;
says the object should be disposed when it falls-out-of-scope, not that it should be disposed when it's last used in the scope it's declared (which would prohibit passing it on to another method that's still using it).
There's at least two arguments for what could/should happen:
Dispose fs1
as soon as it jumps to case_3
, even though fs1
is still technically in-scope (if you subscribe to the "all cases share the same scope" school of thought).
- This also ignores the main point of a
using;
statement which strictly binds the lifetime of its subject to the enclosing scope.
Dispose fs1
only when control leaves the entire switch
block (even though arguably fs1
is out-of-scope prior to that.
As the proposal mentions, it's something that could have been hammered out but probably not something that people would have agreed upon given the time constraints the language design team is under.