在 C# 中将字段标记为“只读”有啥好处?
Posted
技术标签:
【中文标题】在 C# 中将字段标记为“只读”有啥好处?【英文标题】:What are the benefits to marking a field as `readonly` in C#?在 C# 中将字段标记为“只读”有什么好处? 【发布时间】:2010-09-21 13:52:39 【问题描述】:将成员变量声明为只读有什么好处?它只是防止有人在类的生命周期内更改其值,还是使用此关键字会导致任何速度或效率的提高?
【问题讨论】:
良好的外部答案:dotnetperls.com/readonly 有趣。这本质上是这个 Java 问题***.com/questions/137868/… 的 C# 等价物,尽管这里的讨论不那么激烈......嗯...... 可能值得注意的是,结构类型的readonly
字段与根本未发生突变的可变字段相比会带来性能损失,因为调用 readonly
值类型字段的任何成员将导致编译器复制该字段并在其上调用成员。
更多关于性能损失的信息:codeblog.jonskeet.uk/2014/07/16/…
【参考方案1】:
readonly
关键字用于将成员变量声明为常量,但允许在运行时计算值。这与使用 const
修饰符声明的常量不同,后者必须在编译时设置其值。使用readonly
,您可以在声明中或在该字段所属对象的构造函数中设置该字段的值。
如果您不想重新编译引用该常量的外部 DLL(因为它在编译时被替换),也可以使用它。
【讨论】:
但是速度和效率的好处呢?有吗? 请记住,如果您将只读分配给诸如private readonly TaskCompletionSource<bool> _ready = new TaskCompletionSource<bool>();
之类的类,那么您仍然可以使用_ready.SetResult(true)
,因此只读仅适用于该字段而不一定适用于对象的属性或状态。 const 也不像“编译时间”那么简单——它不能用于所有相同的东西,比如 readonly 可以...... const 只能保存字符串、int、bools 或 null。例如你不能做const HttpClient hello5 = new HttpClient();
但你可以做readonly
@NotoriousPyro 你不能做const HttpClient hello5 = new HttpClient()
的原因正是因为在运行时分配了一个新的HttpClient。它真的就像“编译时”一样简单。甚至结构都是在运行时分配的,不能是 const。【参考方案2】:
使用readonly
并没有明显的性能优势,至少我从未见过任何地方提到过。只是为了完全按照您的建议进行操作,以防止在初始化后进行修改。
所以它的好处在于它可以帮助您编写更健壮、更易读的代码。当您在团队中工作或进行维护时,此类事情的真正好处就来了。将某事声明为readonly
类似于在代码中为该变量的使用订立合同。将其视为以与internal
或private
等其他关键字相同的方式添加文档,您是在说“初始化后不应修改此变量”,而且您是在强制执行它。
因此,如果您通过设计创建一个类并标记一些成员变量readonly
,那么您可以防止您自己或其他团队成员稍后在扩展或修改您的类时犯错误。在我看来,这是一个值得拥有的好处(以 doofledorfer 在 cmets 中提到的额外语言复杂性为代价)。
【讨论】:
而 otoh 简化了语言。但是,不要否认您的福利声明。 我同意,但我认为真正的好处来自于不止一个人在编写代码。这就像在代码中有一个小的设计声明,它的使用合同。我可能应该把它放在答案中,呵呵。 这个答案和讨论其实是我认为最好的答案+1 @Xiaofu:你让我一直认为只读哈哈哈美丽的解释这个世界上没有人能解释最愚蠢的头脑一个理解 即你在你的代码中保持这个值不应该在任何时候改变的意图。【参考方案3】:我认为使用只读字段不会带来任何性能提升。这只是一个检查,以确保一旦对象完全构建,该字段就不能指向新值。
但是,“只读”与其他类型的只读语义非常不同,因为它是由 CLR 在运行时强制执行的。 readonly 关键字编译为 .initonly ,可由 CLR 验证。
这个关键字的真正优势是生成不可变的数据结构。根据定义,不可变数据结构一旦构造就不能更改。这使得推理结构在运行时的行为变得非常容易。例如,将不可变结构传递给代码的另一个随机部分是没有危险的。他们永远无法更改它,因此您可以可靠地针对该结构进行编程。
这是关于不变性的好处之一的一个很好的条目:Threading
【讨论】:
如果您阅读本文,***.com/questions/9860595/… 只读成员可以被修改,并且看起来像 .net 的不一致行为 你能更新上面关于线程的帖子的链接吗?坏了。【参考方案4】:用非常实用的术语来说:
如果您在 dll A 中使用 const 并且 dll B 引用该 const,则该 const 的值将被编译到 dll B。如果您使用该 const 的新值重新部署 dll A,则 dll B 仍将使用原值。
如果您在 dll A 和 dll B 中使用 readonly 引用该 readonly,则将始终在运行时查找该 readonly。这意味着如果您使用该只读的新值重新部署 dll A,则 dll B 将使用该新值。
【讨论】:
这是一个很好的实际例子,可以理解差异。谢谢。 另一方面,const
可能比readonly
有性能提升。下面是更深入的代码解释:dotnetperls.com/readonly
我认为这个答案缺少最大的实用术语:将运行时计算的值存储到 readonly
字段中的能力。您不能将 new object();
存储在 const
中,这是有道理的,因为您无法在不更改标识的情况下在编译时将非值的东西(例如对其他程序集的引用)烘焙。【参考方案5】:
请记住,只读仅适用于值本身,因此如果您使用引用类型,只读只会保护引用不被更改。实例的状态不受只读保护。
【讨论】:
【参考方案6】:小心私有只读数组。如果这些将客户端作为对象公开(您可以像我一样为 COM 互操作执行此操作),则客户端可以操作数组值。将数组作为对象返回时使用 Clone() 方法。
【讨论】:
否;公开ReadOnlyCollection<T>
而不是数组。
这应该是评论而不是答案,因为它没有提供问题的答案...
有趣的是,当我上周在另一篇文章中这样做时,有人告诉我要把这类事情作为答案,而不是评论。
自 2013 年起,您可以使用 ImmutableArray<T>
,这可以避免装箱到接口 (IReadOnlyList<T>
) 或包装在类中 (ReadOnlyCollection
)。它的性能可与原生数组相媲美:blogs.msdn.microsoft.com/dotnet/2013/06/24/…【参考方案7】:
编译器可能会根据 readonly 关键字的存在进行性能优化。
这仅适用于只读字段也标记为静态的情况。在这种情况下,JIT 编译器可以假设这个静态字段永远不会改变。 JIT 编译器在编译类的方法时可以考虑到这一点。
一个典型的例子:你的类可能有一个在构造函数中初始化的静态只读 IsDebugLoggingEnabled 字段(例如,基于配置文件)。一旦实际的方法被 JIT 编译,当调试日志没有启用时,编译器可能会忽略整个代码部分。
我没有检查这个优化是否真的在当前版本的JIT编译器中实现了,所以这只是猜测。
【讨论】:
这个有来源吗? 当前的 JIT 编译器实际上确实实现了这一点,并且从 CLR 3.5 开始就有。 github.com/dotnet/coreclr/issues/1079 无法对只读字段进行优化,原因很简单,只读字段不是只读的而是读写的。它们只是大多数编译器尊重的编译器提示,并且只读字段的值可以通过反射轻松覆盖(尽管不是在部分受信任的代码中)。【参考方案8】:不要忘记有一种解决方法可以使用out
参数在任何构造函数之外设置readonly
字段。
有点乱,但是:
private readonly int _someNumber;
private readonly string _someText;
public MyClass(int someNumber) : this(data, null)
public MyClass(int someNumber, string someText)
Initialise(out _someNumber, someNumber, out _someText, someText);
private void Initialise(out int _someNumber, int someNumber, out string _someText, string someText)
//some logic
在这里进一步讨论:http://www.adamjamesnaylor.com/2013/01/23/Setting-Readonly-Fields-From-Chained-Constructors.aspx
【讨论】:
这些字段仍然在构造函数中分配..没有“绕过”。值是来自单个表达式、来自分解的复杂类型还是通过out
的引用语义调用分配并不重要..
这甚至没有试图回答这个问题。【参考方案9】:
WPF 可以带来性能优势,因为它消除了对昂贵的 DependencyProperties 的需求。这对于集合特别有用
【讨论】:
DependecyProperty
用于与字段完全不同的目的
同意,他们一起处于监测突变的同一个生态系统中。如果您可以确保某个字段不会发生变化,那么您就不需要监控它的突变,从而提高性能【参考方案10】:
使用只读标记的另一个有趣的部分是保护字段不被单例初始化。
例如来自csharpindepth的代码:
public sealed class Singleton
private static readonly Lazy<Singleton> lazy =
new Lazy<Singleton>(() => new Singleton());
public static Singleton Instance get return lazy.Value;
private Singleton()
readonly 在保护字段 Singleton 不被初始化两次方面起着很小的作用。另一个细节是,对于上述场景,您不能使用 const,因为 const 会在编译时强制创建,但单例会在运行时创建。
【讨论】:
【参考方案11】:如果您有一个预定义或预先计算的值需要在整个程序中保持不变,那么您应该使用常量,但如果您有一个需要在运行时提供但一旦分配的值应该在整个程序中保持不变你应该使用只读。例如,如果您必须分配程序启动时间,或者您必须在对象初始化时存储用户提供的值,并且您必须限制它进行进一步的更改,您应该使用只读。
【讨论】:
【参考方案12】:readonly
可以在声明时初始化或仅从构造函数获取其值。与const
不同,它必须同时初始化和声明。
readonly
拥有一切 const
拥有,加上构造函数初始化
代码 https://repl.it/HvRU/1
using System;
class MainClass
public static void Main (string[] args)
Console.WriteLine(new Test().c);
Console.WriteLine(new Test("Constructor").c);
Console.WriteLine(new Test().ChangeC()); //Error A readonly field
// `MainClass.Test.c' cannot be assigned to (except in a constructor or a
// variable initializer)
public class Test
public readonly string c = "Hello World";
public Test()
public Test(string val)
c = val;
public string ChangeC()
c = "Method";
return c ;
【讨论】:
【参考方案13】:令人惊讶的是,readonly 实际上会导致代码变慢,正如 Jon Skeet 在测试他的 Noda Time 库时发现的那样。在这种情况下,在 20 秒内运行的测试在删除 readonly 后只用了 4 秒。
https://codeblog.jonskeet.uk/2014/07/16/micro-optimization-the-surprising-inefficiency-of-readonly-fields/
【讨论】:
请注意,如果该字段的类型是 C# 7.2 中的readonly struct
,则将该字段设为非只读的好处就消失了。【参考方案14】:
添加一个基本方面来回答这个问题:
通过省略set
运算符,可以将属性表示为只读。所以在大多数情况下,您不需要将readonly
关键字添加到属性中:
public int Foo get; // a readonly property
与此相反:字段需要readonly
关键字才能达到类似的效果:
public readonly int Foo; // a readonly field
因此,将字段标记为 readonly
的一个好处是可以实现与没有 set
运算符的属性类似的写保护级别 - 如果出于任何原因,无需将字段更改为属性,即想要的。
【讨论】:
两者在行为上有区别吗?以上是关于在 C# 中将字段标记为“只读”有啥好处?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 C# 标记中将 Frame 的高度设置为等于其宽度?