在C#中将字段标记为“只读”有什么好处?


295

将成员变量声明为只读有什么好处?它只是防止有人在类的生命周期内更改其值,还是使用此关键字会提高速度或效率?


8
好的外部答案:dotnetperls.com/readonly
OneWorld

3
有趣。从本质上讲,这是此Java问题的C#等价物stackoverflow.com/questions/137868/… 尽管这里的讨论要少得多……嗯……
RAY 2012年

7
值得注意的是readonly,结构类型的字段与不可变的可变字段相比,会带来性能损失,因为调用readonly值类型字段的任何成员都会导致编译器复制该字段并调用该字段。成员。
2012年

2
有关性能损失的更多信息:codeblog.jonskeet.uk/2014/07/16/…–
CAD

Answers:


171

所述readonly关键字被用于声明一个成员变量恒定的,但允许在运行时计算的值。这不同于用const修饰符声明的常量,该常量必须在编译时设置其值。使用,readonly您可以在声明中或字段所属对象的构造函数中设置字段的值。

如果您不想重新编译引用该常量的外部DLL(因为在编译时将其替换),请也使用它。


193

我认为使用只读字段不会带来任何性能提升。这只是一项检查,以确保一旦完全构造了对象,该字段就无法指向新值。

但是,“ readonly”与其他类型的只读语义有很大不同,因为它是在运行时由CLR强制执行的。readonly关键字编译为.initonly,可通过CLR进行验证。

此关键字的真正优点是生成不可变的数据结构。一旦定义,不变的数据结构就无法更改。这使得在运行时对结构的行为进行推理非常容易。例如,不存在将不变结构传递给代码的另一个随机部分的危险。他们无法更改它,因此您可以针对该结构进行可靠编程。

这是关于不变性的好处之一的好文章:线程化


6
如果您阅读此文章,则stackoverflow.com/questions/9860595/…只读成员可以修改,并且.net看起来像是行为不一致的行为
Akash Kava

69

使用并没有明显的性能优势readonly,至少我从未见过任何地方提到过。它仅用于完全按照您的建议进行,防止在初始化后进行修改。

因此,这样做的好处是可以帮助您编写更健壮,更易读的代码。当您在团队中工作或进行维护时,此类事情的真正好处就体现了。声明某些readonly内容类似于在代码中为该变量的使用订立合同。您可以将其视为与其他关键字(如internal或)添加文档的方式相同private,即表示“初始化后不应修改此变量”,而且执行它。

因此,如果您创建一个类并readonly通过设计标记一些成员变量,则可以防止自己或其他团队成员稍后在扩展或修改您的类时犯错。在我看来,这是值得拥有的好处(以doofledorfer在评论中提到的额外的语言复杂性为代价)。


otoh简化了语言。但是,不要否认您的利益声明。
dkretz

3
我同意,但是我想真正的好处是,当一个以上的人在编写代码时,才能真正受益。这就像在代码中包含一个小的设计声明,以及使用合同。我可能应该把它放在答案中,呵呵。
小夫

7
在我看来,这个答案和讨论实际上是最好的答案+1
杰夫·马丁

@Xiaofu:您使我对唯读哈哈哈的美好想法保持不变,这个世界上没有人可以向最愚蠢的人解释这道理
学习者

也就是说,您一直打算在代码中随时更改此值。
Andez

51

用非常实际的话来说:

如果您在dll A中使用const,并且dll B引用了该const,则该const的值将被编译到dll B中。如果使用该const的新值重新部署dll A,则dll B仍将使用原始值。

如果在只读的dll A和dll B引用中使用只读,则始终在运行时查找该只读。这意味着,如果用只读的新值重新部署dll A,则dll B将使用该新值。


4
这是理解差异的一个很好的实际例子。谢谢。
Shyju'5

另一方面,const可能会提高性能readonly。这是代码的更深层解释:dotnetperls.com/readonly
Dio Phung

2
我认为此答案缺少最大的实用术语:将运行时计算的值存储到readonly字段中的能力。您不能将a存储在new object();const,这是有道理的,因为您不能在不更改标识的情况下将非值对象(例如,对其他程序集中的引用)进行烘烤。
宾基

14

潜在的情况是编译器可以根据readonly关键字的存在进行性能优化。

仅当readonly字段也标记为static时,这才适用。在这种情况下,JIT编译器可以假定此静态字段永远不会更改。JIT编译器在编译类的方法时可以考虑到这一点。

典型示例:您的类可能具有在构造函数中初始化的静态只读IsDebugLoggingEnabled字段(例如,基于配置文件)。一旦实际方法被JIT编译,则在未启用调试日志记录时,编译器可能会省略代码的整个部分。

我还没有检查这种优化是否实际上在当前版本的JIT编译器中实现,所以这仅仅是猜测。


有这个来源吗?
塞达特·卡帕诺格鲁

4
实际上,当前的JIT编译器确实实现了此功能,并且自CLR 3.5起就具有此功能。github.com/dotnet/coreclr/issues/1079
mirhagk,2016年

由于只读字段不是只读而是可读写的简单原因,因此无法对只读字段进行优化。它们只是编译器的一种暗示,即大多数编译器都尊重,并且只读字段的值可以通过反射轻松覆盖(尽管不是在部分受信任的代码中)。
克里斯多夫

9

请记住,只读仅适用于值本身,因此,如果您使用引用类型,则只读仅可保护引用不被更改。实例的状态不受只读保护。


4

不要忘记有一种变通方法,可以readonly使用以下任何构造函数获取字段集out params。

有点混乱,但:

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


4
这些字段仍在构造函数中分配。.没有“解决”的问题。不要紧如果值是从单一表达式,从分解的复杂类型,或通过引用语义经由呼叫分配out..
user2864740

这甚至都没有试图回答这个问题。
谢里登


1

如果您有一个预定义或预先计算的值,需要在整个程序中保持不变,则应使用常量,但如果您需要在运行时提供一个值,但一旦分配该值,则在整个程序中应保持不变只读。例如,如果您必须分配程序开始时间,或者必须在对象初始化时存储用户提供的值,并且必须限制它的进一步更改,则应使用只读。


1

添加一个基本方面来回答这个问题:

通过省略set运算符,可以将属性表示为只读。因此,在大多数情况下,您无需将readonly关键字添加到属性中:

public int Foo { get; }  // a readonly property

与此相反:字段需要readonly关键字才能达到类似的效果:

public readonly int Foo; // a readonly field

因此,将字段标记为的一个好处是readonly可以在没有set运算符的情况下获得与属性相似的写保护级别-无需出于任何原因而将字段更改为属性。


两者之间在行为上有区别吗?
petrosmm

0

注意私有只读数组。如果将这些作为客户端公开(作为对象,您可以像我一样对COM互操作执行此操作),则客户端可以操纵数组值。返回数组作为对象时,请使用Clone()方法。


20
没有; 公开一个ReadOnlyCollection<T>而不是一个数组。
SLaks 2011年

这应该是评论而不是答案,因为它无法回答问题...
Peter

有趣的是,当我上周在另一篇文章中发表评论时,我被告知要把这种事情作为答案,而不是发表评论。
凯尔·巴兰

自2013年起,您可以使用 ImmutableArray<T>,避免将其装箱到界面(IReadOnlyList<T>)或包装在类(ReadOnlyCollection)中。它的性能与天然阵列:blogs.msdn.microsoft.com/dotnet/2013/06/24/...
查尔斯·泰勒

0

WPF可能会带来性能优势,因为它消除了对昂贵的DependencyProperties的需求。这对于集合尤其有用


0

使用只读标记的另一个有趣的部分可以是保护字段免受单例初始化。

例如来自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在运行时进行创建。


0

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 ;
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.