具有 var/null 奇怪行为的开关

根据以下守则:

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;
}

为什么 switch 语句在 case var o上匹配?

我的理解是,当 s == nullcase string s不匹配,因为(有效地) (null as string) != null计算为 false。VS 代码上的智能感知告诉我,o也是 string。有什么想法吗?


类似: 带空检查的 C # 7交换机案例

4216 次浏览

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.

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.

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.)