将成员变量声明为只读有什么好处?它只是防止有人在类的生命周期内改变它的值,还是使用这个关键字会带来任何速度或效率的提高?
14 回答
我不相信使用只读字段会带来任何性能提升。这只是一个检查,以确保一旦对象完全构建,该字段就不能指向新值。
然而,“只读”与其他类型的只读语义非常不同,因为它是由 CLR 在运行时强制执行的。readonly 关键字编译为 .initonly ,可由 CLR 验证。
这个关键字的真正优势是生成不可变的数据结构。根据定义,不可变数据结构一旦构造就不能更改。这使得推理结构在运行时的行为变得非常容易。例如,将不可变结构传递给代码的另一个随机部分是没有危险的。他们永远无法更改它,因此您可以针对该结构进行可靠的编程。
Robert Pickering 写了一篇很好的博客文章,介绍了不变性的好处。可以在此处或在archive.org 备份中找到该帖子。
该readonly
关键字用于将成员变量声明为常量,但允许在运行时计算该值。这与使用const
修饰符声明的常量不同,后者必须在编译时设置其值。使用readonly
您可以在声明中或在该字段所属的对象的构造函数中设置字段的值。
如果您不想重新编译引用该常量的外部 DLL(因为它在编译时被替换),也可以使用它。
使用 没有明显的性能优势readonly
,至少我从未见过任何地方提到过。这只是为了完全按照您的建议进行操作,以防止在初始化后进行修改。
所以它的好处在于它可以帮助您编写更健壮、更易读的代码。当您在团队中工作或进行维护时,此类事情的真正好处就来了。声明某事readonly
类似于在代码中为该变量的使用订立合同。internal
将其视为以与or之类的其他关键字相同的方式添加文档private
,您是在说“初始化后不应修改此变量”,而且您正在强制执行它。
因此,如果您readonly
通过设计创建一个类并标记一些成员变量,那么您可以防止您自己或其他团队成员稍后在扩展或修改您的类时犯错误。在我看来,这是一个值得拥有的好处(以额外的语言复杂性为代价,正如 doofledorfer 在评论中提到的那样)。
用非常实用的术语来说:
如果您在 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 将使用该新值。
编译器可能会根据 readonly 关键字的存在进行性能优化。
这仅适用于只读字段也标记为static的情况。在这种情况下,JIT 编译器可以假设这个静态字段永远不会改变。JIT 编译器在编译类的方法时可以考虑到这一点。
一个典型的例子:你的类可能有一个在构造函数中初始化的静态只读IsDebugLoggingEnabled字段(例如基于配置文件)。一旦实际方法被 JIT 编译,编译器可能会在未启用调试日志记录时省略整个代码部分。
我没有检查这个优化是否真的在当前版本的 JIT 编译器中实现,所以这只是猜测。
请记住,只读仅适用于值本身,因此如果您使用引用类型,只读只会保护引用不被更改。实例的状态不受只读保护。
令人惊讶的是,readonly 实际上会导致代码变慢,正如 Jon Skeet 在测试他的 Noda Time 库时发现的那样。在这种情况下,在 20 秒内运行的测试在删除 readonly 后只用了 4 秒。
不要忘记有一种解决方法可以使用参数 readonly
在任何构造函数之外设置字段。out
有点乱,但是:
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
添加一个基本方面来回答这个问题:
通过省略运算符,可以将属性表示为只读set
。所以在大多数情况下,您不需要将readonly
关键字添加到属性中:
public int Foo { get; } // a readonly property
与此相反:字段需要readonly
关键字才能达到类似的效果:
public readonly int Foo; // a readonly field
因此,将字段标记为readonly
可以实现与没有set
运算符的属性类似的写保护级别的一个好处 - 如果出于任何原因,无需将字段更改为属性。
小心私有只读数组。如果这些将客户端作为对象公开(您可以像我一样为 COM 互操作执行此操作),则客户端可以操作数组值。将数组作为对象返回时使用 Clone() 方法。
使用只读标记的另一个有趣部分是保护字段不被单例初始化。
例如在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 在编译时强制创建,但单例在运行时创建。
如果您有一个预定义或预先计算的值需要在整个程序中保持不变,那么您应该使用常量,但是如果您有一个需要在运行时提供但一旦分配的值应该在整个程序中保持不变,您应该使用只读。例如,如果您必须分配程序启动时间,或者您必须在对象初始化时存储用户提供的值,并且您必须限制它进行进一步的更改,您应该使用只读。
readonly
可以在声明时初始化或仅从构造函数中获取其值。不像const
它必须同时初始化和声明。
readonly
拥有一切 const
,加上构造函数初始化
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 ;
}
}
}
WPF 可以带来性能优势,因为它消除了对昂贵的 DependencyProperties 的需求。这对于集合特别有用