将成员变量声明为只读有哪些好处?它仅保护类生命周期内防止其他人更改其值,还是使用此关键字会导致任何速度或效率的提高?
将成员变量声明为只读有哪些好处?它仅保护类生命周期内防止其他人更改其值,还是使用此关键字会导致任何速度或效率的提高?
我认为readonly字段并不能提高性能,它只是一个检查,确保对象构造完成后该字段不能指向新值。
但是,“readonly”和其他类型的只读语义非常不同,因为它在CLR中通过运行时强制执行。readonly关键字编译成.initonly,这可以被CLR验证。
这个关键字的真正优势是生成不可变数据结构。根据定义,不可变的数据结构在构建完成后就无法更改。这使得在运行时推理数据结构的行为非常容易。例如,传递不可变的结构到另一个随机的代码部分也没有危险,因为它们永远无法更改,所以你可以对该结构进行可靠地编程。
Robert Pickering写了一篇有关不可变性好处的好博客文章。该文章可以在这里或archive.org备份找到。
readonly
关键字用于声明常量成员变量,但允许在运行时计算其值。这与使用const
修饰符声明的常量不同,后者必须在编译时设置其值。使用readonly
,您可以在声明中或包含该字段的对象的构造函数中设置字段的值。readonly
分配给某些东西(比如类),例如private readonly TaskCompletionSource<bool> _ready = new TaskCompletionSource<bool>();
,那么您仍然可以使用_ready.SetResult(true)
,所以readonly
仅适用于字段,而不一定适用于对象的属性或状态。const
也不仅仅是“编译时”这么简单——它不能用于所有与readonly
相同的东西…… const
只能保存字符串、int、bool或null。例如,您无法使用const HttpClient hello5 = new HttpClient();
,但可以使用readonly
。 - NotoriousPyroconst HttpClient hello5 = new HttpClient()
的原因正是因为在运行时分配了一个新的HttpClient。这真的就像“编译时”一样简单。即使结构体也是在运行时分配的,也不能是const。 - Leonardo Greadonly
没有明显的性能优势,至少我从未在任何地方看到过这样的提及。它只是用于像您所建议的一样防止初始化后进行修改。
因此,它有助于您编写更健壮、更易读的代码。这种方法的真正好处出现在团队合作或维护时。将某些东西声明为readonly
类似于在代码中添加变量使用协定。可以将其视为添加文档,就像其他关键字如internal
或private
一样,您正在表达“此变量在初始化后不应被修改”,而且您正在强制执行它。
因此,如果您创建一个类并将一些成员变量标记为readonly
,则可以防止自己或另一个团队成员以后在扩展或修改类时犯错误。在我看来,这是一个值得拥有的好处(虽然可能会稍稍增加语言复杂度,正如doofledorfer在评论中提到的那样)。
说得具体点:
如果你在dll A中使用const,并且dll B引用了该const,那么该const的值将编译到dll B中。 如果您重新部署dll A,并为该const设置新值,则dll B仍将使用原始值。
如果您在dll A中使用readonly,并且dll B引用了该readonly,那么该readonly将始终在运行时查找。 这意味着如果您重新部署dll A并为该readonly设置新值,则dll B将使用该新值。
可能存在一种情况,即编译器可以基于readonly关键字的存在进行性能优化。
只有在只读字段还被标记为静态时才适用。在这种情况下,JIT编译器可以假设这个静态字段永远不会发生改变。 JIT编译器可以在编译类的方法时考虑到这一点。
一个典型的例子:你的类可能有一个静态只读的IsDebugLoggingEnabled字段,在构造函数中初始化(例如,基于配置文件)。一旦实际的方法被JIT编译,当调试日志未启用时,编译器可以省略整个代码部分。
我没有检查当前版本的JIT编译器是否实际实现了这种优化,所以这只是猜测。
请记住,readonly仅适用于值本身,因此如果您正在使用引用类型,则readonly仅保护引用不被更改。实例的状态不受readonly保护。
为了回答这个问题,我们需要添加一个基本方面:
通过省略set
运算符,可以将属性表示为只读。因此,在大多数情况下,您不需要向属性添加readonly
关键字:
public int Foo { get; } // a readonly property
readonly
关键字来实现类似的效果:public readonly int Foo; // a readonly field
readonly
的一个好处是可以实现与没有set
操作符的属性相似的写保护级别 - 而无需将字段更改为属性(如果出于任何原因需要),从而使内容更加易于理解。请注意,保留html标签。不要忘记,有一种解决方法可以使用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
的引用调用赋值,都不重要。 - user2864740readonly 标记的另一个有趣用法是保护单例中的字段不被初始化。
例如,在 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 强制在编译时创建,但是 singleton 在运行时创建。
readonly
字段相对于未被改变的可变字段来说,会增加一些性能开销。因为调用任何一个readonly
值类型字段的成员都会导致编译器复制该字段并在其上调用该成员。 - supercat