QCon 全球软件开发大会(北京站)门票 9 折倒计时 4 天,点击立减 ¥880 了解详情
写点什么

C#的未来:不可变变量

2015 年 5 月 06 日

在 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

2015 年 5 月 06 日 09:201256
用户头像

发布了 428 篇内容, 共 151.1 次阅读, 收获喜欢 23 次。

关注

评论

发布
暂无评论
发现更多内容

Android工程架构演进及康威定律

石头

GitHub上10个不可错过的另类有趣项目

码农神说

GitHub 程序员人生 开源项目

【译文】为什么说Rust是机器人技术的未来

袁承兴

rust 机器人 嵌入式

愚蠢写作术(2):怎么让你的文章变得冷冰冰

史方远

学习 个人成长 写作

路径依赖 - 偶然决策导致的依赖。

石云升

思维模型 路径依赖 网络效应 沉没成本 价值网依赖

白天写代码,晚上摆地摊!9年前摆地摊学会了这些道理...

王磊

MyBatis启动之XMLConfigBuilder解析配置文件(二)

ytao

后端 mybatis

【大厂面试03期】MySQL是怎么解决幻读问题的?

NotFound9

MySQL 数据库 编程 架构

除了公关,我还能为公司做点什么?(系列1)

邓瑞恒Ryan

学习 个人成长 自我管理 成长 职业成长

从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(五)

图雀社区

Vue Node

重磅推出:第14份年度敏捷状态报告(最新2020)

Bob Jiang

敏捷 调查报告 state of agile

如何让解决无法访问 GitHub 的问题?

JackTian

GitHub

从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(四)

图雀社区

Vue Node

过滤器 和 拦截器 6个区别,别再傻傻分不清了

程序员内点事

Java

React之Context源码分析与实践

费马

源码分析 React useContext Context React-Router

从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(三)

图雀社区

vue.js Vue Node

大厂经验(1):一套Web自动曝光埋点技术方案

DeeperMan

大数据 数据采集

从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(六)

图雀社区

node.js Vue

深入理解JVM垃圾回收机制 - 何为垃圾?

WANDEFOUR

深入理解JVM 垃圾回收机制

自定义 SpringBootStarter

lee

为什么你成不了「超级个体」?

非著名程序员

程序员 互联网 提升认知 认知提升

csapp-chapter1

卓丁

深入理解计算机系统 csapp

csapp-chapter2

卓丁

深入理解计算机系统 csapp

架构师必备的ToB产品交付之双轮驱动思维模型

常平

产品 极客大学架构师训练营

N皇后问题之位运算解法

孙苏勇

算法 DFS 位运算

ARTS-WEEK02

lee

分布式事务 - 三种常见的解决方案

Java收录阁

分布式事务

中台上线半年,我总结出了“七宗罪”

夜来妖

中台 企业中台 后台开发 业务中台 后台

做一个纸上谈兵的项目经理

escray

多来点胜利,对冲颓丧

zhoo299

成长 备忘

源码分析 | 像盗墓一样分析Spring是怎么初始化xml并注册bean的

小傅哥

源码分析 小傅哥 spring源码 bean注入过程

边缘计算隔离技术的挑战与实践

边缘计算隔离技术的挑战与实践

C#的未来:不可变变量-InfoQ