用var / null奇怪的行为切换


91

给出以下代码:

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

我的理解是case string s不匹配的,s == null因为(有效)(null as string) != null评估为假。VS Code上的IntelliSense告诉我这o也是一个string。有什么想法吗?


类似于:使用空检查的C#7切换大小写


9
已确认。我喜欢这个问题,尤其是在观察到的o情况下string(已通过泛型确认-即Foo(o)where Foo<T>(T template) => typeof(T).Name)-这是一个非常有趣的情况,即使与(由编译器键入)键入时,string x其行为也不同,例如var xxstring
Marc Gravell

7
默认情况是死代码。相信我们应该在那里发出警告。检查。
JaredPar

13
我感到奇怪的是,C#设计人员决定完全允许var这种情况。这种确定性似乎是我在C ++中会发现的那种东西,而不是用一种旨在使程序员“进入成功之门”的语言。在这里,var模棱两可和无用的是C#设计通常努力避免的事情。
Peter Duniho

1
@PeterDuniho我不会说没用;的入站表达式switch可能无法发音-匿名类型等;并没有歧义 -编译器清楚地知道类型;(至少对我而言)这些null规则是如此的令人困惑!
Marc Gravell

1
@PeterDuniho有趣的事实-我们曾经从C#1.2规范中查找了明确的赋值形式规则,并且说明性的扩展代码在块中包含了变量声明(现在位于此处);它仅在2.0中移至外部,然后在捕获问题明显时再次移回内部。
Marc Gravell

Answers:


69

在模式匹配switch语句中case,对显式类型使用的是询问所涉及的值是该特定类型还是派生类型。完全等同于is

switch (someString) {
  case string s:
}
if (someString is string) 

该值null没有类型,因此不满足以上任何一个条件。someString在两个示例中,静态类型都不起作用。

var类型尽管在模式匹配用作通配符,并且将匹配包括任何值null

default这里的情况是死代码。在case var o将匹配任何值,空或非空。非默认情况总是胜过默认情况,因此default永远不会被击中。如果您查看IL,您会发现它甚至没有发出。

乍一看,它在没有任何警告的情况下进行编译似乎很奇怪(肯定让我失望了)。但这与回溯到1.0的C#行为相匹配。default即使编译器可以琐碎地证明它永远不会被击中,它也允许出现情况。考虑以下示例:

bool b = ...;
switch (b) {
  case true: ...
  case false: ...
  default: ...
}

这里default永远不会被命中(即使bool其值不为1或0)。但是C#从1.0开始就允许这样做而没有警告。模式匹配只是与此行为相符。


4
但是,真正的问题是,编译器“显示” var出的类型string实际上不是真正的类型(诚​​实地不确定应承认哪种类型)
shmuelie

@shmuelie var在示例中的类型被计算为string
JaredPar

5
@JaredPar感谢您的见解;就个人而言,即使以前没有这样做,我也会支持发出更多警告,但是我理解语言团队的限制。您是否曾经考虑过“关于一切模式的渴望”(可能默认情况下处于启用状态)与“传统stoic模式”(选修课程)?也许csc /stiffUpperLip
Marc Gravell

3
@MarcGravell我们有一个称为警告波的功能,旨在使引入新的警告更加容易,不易损坏。本质上,每个编译器版本都是一个新潮,您可以通过/ wave:1,/ wave:2,/ wave:all选择警告。
JaredPar

4
@JonathanDickinson我不认为这表明了您的想法。只是表明a null是有效的string引用,任何string引用(包括null)都可以隐式转换为object引用(保留引用),并且任何可以成功转换(显式)为其他类型的object引用null(仍为)null。就编译器类型系统而言,并非完全相同。
Marc Gravell

22

我在这里汇集了多个Twitter评论-这实际上对我来说是新的,我希望jaredpar能够提供更全面的答案,但是;据我了解,它是短版:

case string s:

被解释为if(someString is string) { s = (string)someString; ...if((s = (someString as string)) != null) { ... }-两者都涉及null测试-在您的情况下失败;反过来:

case var o:

编译器o按原样解析的string位置o = (string)someString; ...-无需null测试,尽管表面上看起来相似,只是由编译器提供类型。

最后:

default:

此处无法达到,因为上述情况涵盖了所有内容。这可能是编译器错误,因为它没有发出无法访问的代码警告。

我同意这是非常微妙的,细微的和令人困惑的。但是很明显,该case var o方案已用于零传播(o?.Length ?? 0等)。我同意这是奇怪的是,这个作品如此非常不同之间var ostring s,但它确实是编译器目前确实。


14

这是因为case <Type>匹配的是动态(运行时)类型,而不是静态(编译时)类型。null没有动态类型,因此无法与匹配stringvar只是后备。

(发帖是因为我喜欢简短的答案。)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.