为什么C#禁止只读局部变量?

Bri*_*sio 107 c# language-design readonly immutability

与同事就此进行了友好的辩论.我们对此有一些想法,但想知道SO人群对此有何看法?

and*_*iej 61

我认为这对C#架构师来说是一个糟糕的判断.局部变量的readonly修饰符有助于维护程序的正确性(就像断言一样)并且可以帮助编译器优化代码(至少在其他语言的情况下).现在它在C#中被禁止的事实是另一个论点,即C#的一些"特征"仅仅是对其创作者的个人编码风格的执行.

  • 我同意"自己保存程序员"部分,但至于帮助编译器优化代码,我认为编译器可以很好地发现变量是否在方法过程中发生变化并相应地优化无论哪种方式.在优化程序为此目的进行识别之前放置"只读"标志并不会真正受益,但可能会产生误导. (9认同)
  • @Cornelius 我同意有人认为在某些情况下编译器使用数据流图来找出优化机会,而不管任何关键字/修饰符。但是**让程序员免于自己**编写不正确的或不必要的未优化代码可能会为编译器打开优化机会。 (2认同)

Jon*_*eet 33

解决Jared的答案,它可能只是一个编译时功能 - 编译器会禁止你在初始声明后写入变量(必须包含一个赋值).

我能看到这个价值吗?可能 - 但不是很多,说实话.如果您无法轻易判断变量是否将在方法中的其他位置分配,那么您的方法太长.

对于它的价值,Java有此功能(使用final修改器),我已经非常难得一见它使用比在其情况下,其他被用来使变量通过一个匿名内部类被捕获-并且它使用它,它给我一个杂乱而不是有用信息的印象.

  • 通过*sight*和*编译器*查看变量是否在方法中被修改是有区别的.我没有反对写一个方法,说明我不修改变量的意图,让编译器在我不小心做的时候通知我(可能在一个月后输入错误)! (72认同)
  • 另一方面,在F#中,默认情况下所有变量都是只读的,如果您希望能够更改它们,则必须使用'mutable'关键字.由于F#是.NET语言,我想它会执行您描述的编译时检查. (49认同)
  • 对于未在闭包中使用的局部变量,`readonly`不会过于重要.另一方面,对于在闭包中使用的局部变量,`readonly`在许多情况下会让编译器生成更有效的代码.目前,当执行进入包含闭包的块时,编译器必须为闭合变量创建一个新的堆对象,*即使没有使用闭包的代码也会被执行*.如果变量是只读的,则闭包外的代码可以使用正常变量; 只有为闭包创建委托时... (4认同)
  • @ A.Rex:问题在于,在阅读代码而不是真正关心代码时,让编译器进行检查的好处是否值得额外的"绒毛". (2认同)
  • 考虑下面的定义 `int arr[] = new int[numItems];`,它放在将 `arr` 传递给不熟悉的方法的循环之前。即使很明显 `arr` 总是指向同一个数组,也可能不清楚这是因为从来没有必要让它指向别处,还是因为被调用的方法依赖于它始终是同一个实例。如果将来的代码要求代码处理事先未知的不同大小的数组,那么这种区别可能会变得很重要。 (2认同)
  • 嗯,我一直在 Java 中使用 Final 来表示“幻数”,它的作用域可以是方法或类。我只是想在 C# 中做同样的事情,但显然这是不可能的,我不明白为什么不给出这个常见的用例。我真的对不可变的魔法数字感到厌恶。是的,我知道我不会修改它们,只是感觉不对。 (2认同)
  • FWIW,Scala通过其`val`和`var`关键字将本地`readonly` /`final`值与变量区分开。在Scala代码中,非常频繁地使用本地`val`(实际上比本地`var`更受欢迎)。我怀疑Java中不更经常使用`final`修饰符的主要原因是a)混乱和b)懒惰。 (2认同)
  • ...是否有必要将局部变量复制到新的堆对象中。此外,如果一个方法包含一个使用两个变量的 lambda,其中一个标识一个小/便宜的对象,另一个标识一个大/昂贵的对象,第二个 lambda 只使用小/便宜的对象,任何代码让后一个委托保持活动也会使昂贵的对象保持活动状态。声明 * 任何一个 * 引用 `readonly` 将允许编译器中断连接。 (2认同)
  • 抱歉,但是“编译器将禁止您在初始声明后写入变量”,这还远远不够。考虑C ++ const正确性会导致一些复杂性的例子:您可以通过引用传递它吗?方法极有可能需要引用而不是副本,因为它需要查看回调所做的更改。然而参数跨语言边界是可见的,并且CLR没有用于将“ ref”参数标记为只读的语法,类似于C ++“ const T&”。您可以使用哪些成员函数?您可以调用属性获取器吗? (2认同)
  • 如果您将只读设置为浅层,那么最后两个问题很简单(是的。)但令人惊讶。而且似乎很可能不允许通过引用传递,因为它是“initonly”字段,尽管这在形式上并不正确。但任何此类功能都不仅仅涉及分配。 (2认同)
  • 如果我们可以使用单子查询理解中的`let`关键字来声明本地人,我个人会喜欢它。和var一样,只是只读。我不知道这是否会使编译器更加复杂,或者这可能是一个重大更改。 (2认同)
  • 实施起来应该不会很难。执行此操作的方法如下: - 修改 Roslyn 编译器,使其在接受“var”的任何地方都接受“let”关键字。以下是类似编译器修改的示例(接受不同的字符串开始/终止字符):https://blogs.msdn.microsoft.com/csharpfaq/2014/04/03/take-a-tour-of-roslyn/ - Write当修改用“let”关键字声明的变量时给出错误的分析器(谷歌“Roslyn分析器”)-将两个组件发送给Microsoft;) (2认同)
  • 使用 const 编写 javascript 的经验让我在 c# 中寻找这个。在编写 javascript 时,我一直缺少 c# 功能。这是我第一次在 c# 中缺少 javascript 功能。 (2认同)

Col*_*nic 26

C#7设计团队简要讨论了一个只读本地和参数的建议.来自2015年1月21日的C#设计会议记录:

lambdas可以捕获参数和本地,从而同时访问它们,但是没有办法保护它们免受共享 - 共同状态问题的影响:它们不能只读.

通常,大多数参数和许多局部变量在获得初始值后从不打算分配.只读它们就可以清楚地表达这一意图.

一个问题是这个特征可能是"有吸引力的麻烦".虽然"正确的事情"几乎总是要使参数和本地人只读,但这样做会使代码混乱.

部分缓解这一点的想法是允许将局部变量上的readonly var组合缩小为val或类似的短路.更一般地,我们可以尝试简单地考虑比建立的只读更短的关键字来表达读取性.

C#语言设计回购继续讨论.投票表示您的支持.https://github.com/dotnet/csharplang/issues/188

  • 也可以使用 `let` 或 `const`。 (2认同)

Jar*_*Par 14

一个原因是对于只读本地没有CLR支持.Readonly被翻译成CLR/CLI initonly操作码.此标志只能应用于字段,对本地没有意义.实际上,将其应用于本地可能会产生无法验证的代码.

这并不意味着C#无法做到这一点.但它会给同一语言结构赋予两种不同的含义.本地版本没有CLR等效映射.

  • 它实际上与CLI对该功能的支持无关,因为局部变量绝不会暴露给其他程序集.CLI需要支持字段的`readonly`关键字,因为其**对其他程序集可见*.它只意味着变量在编译时只在方法中有一个赋值. (53认同)
  • 我认为你刚刚将问题转移到为什么CLR不支持这一点而不是提供它背后的理性.它确实允许const本地人,所以期望只读本地人是合理的. (15认同)
  • 这方面的一个例子是在using语句中定义的变量.它们是本地的...和readonly(尝试分配它们,C#将添加错误). (9认同)
  • -1在C++中,没有机器代码支持`const`(在C++中更像是C#`readonly`而不是像C#`const`,尽管它可以扮演两个角色).然而,C++支持本地自动变量的`const`.因此,对于局部变量的C#`readonly`缺乏CLR支持是无关紧要的. (6认同)
  • 这很容易成为编译器功能,就像在C++中一样.CLR支持完全无关紧要.机器装配也不支持它,那么什么?2.*(它会)可能产生无法验证的代码* - 我不知道如何,但也许我错了.3.*它会给同一语言结构赋予两种不同的含义* - 我怀疑有人会认为这是一个问题,因为`using`和`out`正是这样做的,而且世界并没有崩溃. (5认同)
  • 只读 **var**iable 不是一个甜蜜的矛盾吗?我投票支持“不可变的堆栈元素”。=) (3认同)

小智 7

我是那个同事而且不友善!(开玩笑)

我不会消除这个功能,因为编写简短的方法会更好.这有点像说你不应该使用线程,因为它们很难.给我刀,让我负责不切割自己.

就个人而言,我想要另一个"var"类型的关键字,如"inv"(invarient)或"rvar",以避免混乱.我一直在研究F#,发现不可改变的东西很有吸引力.

从来不知道Java有这个.


Der*_*ang 7

这是对c#语言设计师的疏忽.F#有val关键字,它基于CLR.没有理由C#不能具有相同的语言功能.


brg*_*ner 5

我希望本地只读变量的方式与我喜欢本地const变量的方式相同.但它的优先级低于其他主题.
也许它的优先级与C#设计者没有(还是!)实现此功能的原因相同.但是在未来版本中支持本地只读变量应该很容易(并且向后兼容).