BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

C#的未来:不可变变量

| 作者 Jonathan Allen 关注 594 他的粉丝 ,译者 邵思华 关注 3 他的粉丝 发布于 2015年5月7日. 估计阅读时间: 6 分钟 | QCon上海2018 关注大数据平台技术选型、搭建、系统迁移和优化的经验。

在C#中,readonly关键字只能作用于字段级别。而在第115条提议 “只读本地变量与参数”中,将对readonly关键字进行扩展,以涵盖更广泛的场景。

提议中首先提出了创建只读本地变量的功能。这种功能的第一个使用场景只是用于文档,通过将某个变量标记为只读,就意味着在函数中的其它地方不能够、也不应该改动这个本地变量。对于代码很长、非常复杂,无法一眼看清所有情况的函数来说,这一点显得尤为实用。

第二个使用场景是在进行多线程运算和使用闭包时保证安全性。如果你通过执行Parallel.ForEach产生了一个闭包,则很容易会导致竞态的产生。而如果你默认将所有本地变量标为只读,那么那些可变的本地变量将显得很突出,因此在审查中就会注意到它。

语法上的困扰

如果能够默认使用只读本地变量,那么它确实能够带来许多价值,但这也要求语法不能太过繁重。考虑一下以下代码:

var gravity = 9.780327;
double gravity = 9.780327;
const double gravity = 9.780327;

虽然gravity(重力)理应作为一个常量,但多数开发者都倾向于使用第一种代码版本。他们之所以没有选择“正确的做法”,只是为了简化输入,并且在一个独立的场景中,选用哪种方式其实无关紧要。

这种简便性胜于正确性的倾向也出现在类型转换中,下面的代码有一个常见的错误,许多有经验的程序员也难以避免。

var button = sender as Button;
button.Enabled = false;

正确的方法应该是“button = (Button)sender”,但这种正确的代码在输入时的复杂性稍高。

为了应对这些困扰,该提议提出了一种隐式类型化本地变量的简写方式,目前所考虑的有以下两种关键字:

val gravity = 9.780327;
let gravity = 9.780327;

在这两者之间,目前更倾向于“let”这一写法,因为在LINQ表达式中已经用到过它,并且从视觉角度来看更容易与“var”区别开(对于那些母语中不区分r与l的非英语使用者来说,后者也会更好)。

只读参数

接下来就是将参数标注为只读的功能。使用Visual Studio代码分析工具的开发者可能认为这一功能有些多余,因为该工具会自动阻止对常规的参数进行改变。但有些用例是它无法涵盖的。

在开发注重高性能的代码时,使用结构体替代类的做法并不罕见,即使结构体有时显得更大。为了避免结构体复制带来的消耗,因而会使用ref参数的方式将这些结构体传递给函数。

从文档的角度来看,这种函数签名无法清晰地向调用者表现出该参数不应被修改的意图。而将参数标注为“readonly ref”这种方式就能够填补这一漏洞。

并非每个人都喜欢这一语法,因为它要求使用“ref”装饰调用端,而这会产生某些误导的倾向。因此Porges建议使用一种“in”关键字予以代替。

void DoSomething(readonly ref LargeStruct value)
DoSomething(ref myLocal);
void DoSomething(in LargeStruct value)
DoSomething(myLocal);

Readonly与Const

虽然readonly能够彻底地避免某个值被替换,但它通常无法避免对某个对象的成员的改动。因此这条提议另外附加了一点,即对readonly所提供的保护能力进行扩展。

像C++这样的语言已经能够支持这一概念了。虽然它确实能够像所说的方式一样工作,但要正确地使用它并不是一件容易的事,因为开发者们经常对于const这个关键字是对应变量本身,还是对应其中的内容感到困扰。因此在此语法中避免这种含糊性也是同样重要的。一种建议认为,可以使用“readonly”表示对变量本身的保护,而使用“const”表示对其内容的保护。

C++中还提供了const函数的概念,这种函数只能由定义为readonly或const的变量进行调用,因为这些变量已经证实了不会改变对象的状态。在.NET中也能够通过Pure属性实现这一概念,但当前的C#编译器都不支持这一属性。

脚注:Readonly、结构体与字段

虽然并非提议中的一部分,但也应当考虑readonly、结构体与字段的交互。考虑以下代码:

private readonly Foo _foo = new Foo(1, 2, 3);

如果Foo类型是完全不可变的,那么readonly字段就能够像预期一样工作。但如果Foo中提供了任何属性的setter,那么每次你读取这个字段时,编译器会生成一个拷贝,所以你不会在无意中修改了它的内容。因此与readonly引用字段不同,readonly结构体真正做到了只读。但由于这种方式带来了隐藏的性能损耗以及不一致性,因此如果能够以一种更清晰的方式表达出这一概念,将具有很大的益处。

要了解更多信息,请阅读Eric Libbert的帖子“Mutating Readonly Structs”。

查看英文原文:C# Futures: Immutable Variables

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我
社区评论

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT