switch with var/null strange behavior
Given the following code:
string someString = null;
switch (someString)
{
case string s:
Console.WriteLine("string s");
break;
case var o:
Console.WriteLine("var o");
break;
default:
Console.WriteLine("default");
break;
}
Why is the switch statement matching on case var o
?
It is my understanding that case string s
does not match when s == null
because (effectively) (null as string) != null
evaluates to false. IntelliSense on VS Code tells me that o
is a string
as well. Any thoughts?
Similiar to: C# 7 switch case with null checks
Solution 1:
Inside a pattern matching switch
statement using a case
for an explicit type is asking if the value in question is of that specific type, or a derived type. It's the exact equivalent of is
switch (someString) {
case string s:
}
if (someString is string)
The value null
does not have a type and hence does not satisfy either of the above conditions. The static type of someString
doesn't come into play in either example.
The var
type though in pattern matching acts as a wild card and will match any value including null
.
The default
case here is dead code. The case var o
will match any value, null or non-null. A non-default case always wins over a default one hence default
will never be hit. If you look at the IL you'll see it's not even emitted.
At a glance it may seem odd that this compiles without any warning (definitely threw me off). But this is matching with C# behavior that goes back to 1.0. The compiler allows default
cases even when it can trivially prove that it will never be hit. Consider as an example the following:
bool b = ...;
switch (b) {
case true: ...
case false: ...
default: ...
}
Here default
will never be hit (even for bool
that have a value that isn't 1 or 0). Yet C# has allowed this since 1.0 without warning. Pattern matching is just falling in line with this behavior here.
Solution 2:
I'm putting together multiple twitter comments here - this is actually new to me, and I'm hoping that jaredpar will jump in with a more comprehensive answer, but; short version as I understand it:
case string s:
is interpreted as if(someString is string) { s = (string)someString; ...
or if((s = (someString as string)) != null) { ... }
- either of which involves a null
test - which is failed in your case; conversely:
case var o:
where the compiler resolves o
as string
is simply o = (string)someString; ...
- no null
test, despite the fact that it looks similar on the surface, just with the compiler providing the type.
finally:
default:
here cannot be reached, because the case above catches everything. This may be a compiler bug in that it didn't emit an unreachable code warning.
I agree that this is very subtle and nuanced, and confusing. But apparently the case var o
scenario has uses with null propagation (o?.Length ?? 0
etc). I agree that it is odd that this works so very differently between var o
and string s
, but it is what the compiler currently does.
Solution 3:
It's because case <Type>
matches on the dynamic (run-time) type, not the static (compile-time) type. null
doesn't have a dynamic type, so it can't match against string
. var
is just the fallback.
(Posting because I like short answers.)