C#的十大遗憾

时间:2017-07-30 13:11

-------- 分割线 -----------


我以前在 C# 设计组的时候,每年都有几场见面会活动,回答 C# 爱好者的问题。最常见的问题可能是这个:有没有什么设计决定是让你们现在后悔的?我的答案是,那必须有啊!


本文中列出了我个人心目中的“C#的最差10大特性”,以及从这些决定中我们可以学到的语言设计经验。


正式开始之前,必须声明一下,第一,我的这些意见只代表我自己,不代表 C# 设计组。第二,所有这些设计决定都是由非常聪明的人做的,他们一直都在试图在各种设计目标之间寻找平衡。每一种情况,在当时都有强力的论点支撑,事后诸葛亮来看的话我们当然可以轻易提出批评。所有这些特性其实都只是一门成功的语言中的非常小的瑕疵。


现在开始吐槽了!


10:空语句毫无意义


跟许多其它的C系列的语言一样,C# 也要求一条语句要么用 } 结尾,要么用分号 ; 结尾。一个容易被忽视的特性是,这些语言中,一个单独的分号也是一条合法的语句:

void M(){    ; // 完全合法
}

你为什么需要一条什么都不做的空语句?下面是几个合理的场景:

你可以为空语句设置断点。在 Visual Studio 里,有些时候一条断点是在语句的开始还是中间是让人迷惑的,如果在空语句中设置断点就没有歧义了;

有些场景下,你需要一条语句,但是又不需要做什么事情:

while(whatever) {  while(whatever)  {    while(whatever)    {
     if (whatever)
       goto outerLoop;//跳出两层循环      [...]    }  }  outerLoop: ; }

C# 里跳转标签后面必须有一条语句;这里空语句就是跳转目标。当然,如果有人请我来检查这段代码,我马上会建议把深度嵌套的循环加 goto 跳转重构为其它的更易读更容易维护的代码。这样的分支结构在现代代码中是非常少见的。


我已经解释了这个功能的好处 —— 当然跟其它语言保持一致性也很不错 —— 但是这些优点其实都没什么吸引力。下面这个示例展现了这个功能的缺点:

while(whatever); // 靠!
{  [...] }

第一行最后的那个出其不意的分号几乎很难被看到,但是它对程序的含义有巨大的影响。这个循环体是一个空语句,跟着一个语句块,这个语句块很可能才是期望的循环体。如果循环条件是 true 的话,这段代码将是死循环;如果条件是 false 的话,它只会把循环体执行一次。


C# 编译器对这种情况给了一个“可能是意料之外的空语句”警告。警告表明这段代码几乎肯定是错的;理想情况,语言应该避免那些很可能是错误的用法,而不是仅仅警告它们!如果编译器团队必须为一个功能设计、实现、测试一条警告,往往说明这个功能很可能一开始就是有问题的,当然对于相对轻量级的空语句功能,这个设计的开销不是很大。幸运的是,这个缺陷在产品中是非常少见的;编译器警告了它,在测试的时候这个死循环是很容易发现的。


最后,在 C# 中有不止一种方法来构造空语句;比如空语句块:

{}

意外地写出一个空语句块是困难的,在源代码中很难忽视,所以这才是需要空语句场景中,我首选的语法。


分号空语句是一个冗余的、很少用的、容易出错的功能。而且它给编译器组带来了额外的工作量,去实现一条警告来告诉你不要使用它。这个功能应该从 C# 1.0 版本就砍掉。


我这篇文章中提到的所有功能的问题是,一但我们有了这个功能,设计者就必须永远保留它。后向兼容性对于 C# 设计组来说是宗教。


教训:当你设计一门语言的第一版的时候,仔细思考每个功能的价值。许多其它的语言可能有这个不重要的小功能,但是这并不是把它加入新语言中的充分理由。


9:太多的判等方式


假如你想实现一个支持各种算术运算的值类型,比如,有理数类型。用户可能希望能比较两个有理数是否相等。但是怎么搞?很简单,只要实现下面的这些:

用户自定义运算符, >, =, = y 是 false 的话,就很扯蛋了。这看起来像个 bug。


开发者必须实现9种方法,保持一致;然而只要其中一个方法的输出(泛型的 CompareTo)就足够让其它8种方法推理出结果了。开发者的负担超过了实际需要的N倍。


另外,对于引用类型来说,当你想做“值相等”的时候,很容易意外使用了“引用判等”的操作,然后就错了。


整个事情是毫无必要的搞复杂了。语言应该设计为,如果你实现了 CompareTo 方法,其它的方法都自动实现了。


寓意:太多的灵活性会让代码冗长,而且创造了产生bug的机会。利用这个机会去消灭、清除、避开设计中不必要的重复的冗余吧。


8:左移右移运算符


跟许多 C 系列的其它语言一样,C#有左移  运算符。它们有许多的设计问题。