本文是C#的未来系列文章的最后一篇了,这次我们将讨论 第159号提案 ,它建议在编译器中加入对不可变类的支持。虽说在C#中创建不可变类型一直以来都是可以做到的,并且C# 6还将进一步简化这一过程,但目前还没有一种方式能够“将类声明为不可变”,并让编译器对这一声明进行校验。
这一提议看起来似乎并非十分重要,因为对类进行手动检查也不是非常困难的事。但如果缺少了对于不可变性的某种声明,就难以了解开发者的意图。应用程序的开发者可能会做出某些假设,例如可以在多线程环境中安全地使用某个类,却发现在下一个版本中,类库的开发者为其加入了某个属性的setter方法,或是其它任何一种可变的、非线程安全的特性。
第159号提议推荐通过使用一个“immutable”关键字或者属性,显式地表明某个类型在任何情况下都不能更改。此外,一个不可变对象只能够引用其它不可变对象。
不可变对象的构造函数也具有一些限制,尤其是它们不能够通过“this”变量调用方法,因为这有可能将该对象在构造过程完成之前“泄漏”出去,从而破坏了对不可变性的承诺。至于这种泄漏应当引起一个错误还是一次警告,这一点可以再议。Sam Harwell写道:
我比较倾向于发出警告的做法,虽然这种做法并不常见,并且也不是推荐的编码方式,但也很难肯定地说绝对没有人需要编写这样的代码。
第一眼看上去,不可变性与Pure(纯对象或方法)约束的作用似乎是相同的,但它们之间确实存在着一些重要的不同之处。
考虑到这些不同之处的存在,相信你会看到许多同时具有不可变性与Pure特性的对象。
泛型类型也将支持不可变性。要实现这一点,通常需要为每个类型参数都设定一个不可变性的限制。但这一条规则还有待商榷,有人认为可以直接从类型的参数中推断出不可变性,而无需显式地将其列为不可变。
在某些情况下,你必须对类型系统进行欺骗,这一点已经得到了认可。打个比方,ImmutableArray是对一个普通数组的封装。而如果按照这条提议的基本原则来说,这种行为是不允许出现的。为了处理这种场景,你可以在这个类的标注中进行声明,表示你是有意地违背了这条原则,并且已经仔细地考虑过这样做的后果。这种做法与“unsafe”关键字的行为很相似,因为后者也是为了这种场合才出现的。
由于“unsafe”关键字的意思已经固定了,因此该提议考虑使用其它的关键字。目前来说,得到最多认可的关键字是“mutable”,不过看起来大家对此都不是十分满意。
在一个不可变对象中,每个字段在语义上都是只读的。某些开发者认为不必显式地进行声明,这可以减少代码的冗长度。另一些人则认为,正如在静态类中声明静态字段一样,每个字段都应该显式地标注为只读。
具体在何处强制不可变性,这一点需要认真考虑。某些人认为,正如代码契约与Pure属性一样,不可变性也应当由某个外部分析器进行处理。这样一来,开发团队就能够自行决定是否需要以编程方式强制不可变性。
另一部分开发者则认为,正因为如此,不可变性更不应当由外部工具进行处理。他们希望编译器能够进行不可变性的检查,这样就不会在无意中破坏了它的不可变性。
来自于微软的Jared Parsons写道:
我认为在这种场合使用分析器是错误的方案,在一个单一的C#项目中,要强制一系列规则的应用,甚至是对某种C#的变体进行分析,分析器都是一种优秀的选择。因为我对编译过程具有掌控权,因此可以随意地选择并使用分析器。
而如果需要在多个项目中强制某些规则,尤其是在这些项目属于不同开发者的情况下,分析器的作用就降低了。没有什么机制能够强制对某个项目引用通过某种分析器进行扫描,这种情况下唯一有效的强制措施就是双方的携手合作了。
这种情况与Pure属性的承诺不同,后者只是表示在某个对象中的任何方法或是属性都不会“产生任何可见的状态更改”。
查看英文原文: C# Futures: Immutable Classes
感谢张龙对本文的审校。
给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ,@丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入InfoQ读者交流群 )。