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

时间:2008-11-10 03:37:51

标签: c# readonly

将成员变量声明为只读有什么好处?它只是防止在类的生命周期中更改某些人,或者是否由于此关键字而导致编译器速度提高

14 个答案:

答案 0 :(得分:169)

我不相信使用只读字段会有任何性能提升。它只是一个检查,以确保一旦完全构造对象,该字段不能指向新值。

然而,“readonly”与其他类型的只读语义非常不同,因为它在运行时由CLR强制执行。 readonly关键字编译为.initonly,可由CLR验证。

此关键字的真正优势在于生成不可变数据结构。根据定义,不可变数据结构一旦构造就不能改变。这使得在运行时很容易推断出结构的行为。例如,没有将不可变结构传递给另一个随机代码部分的危险。它们无法改变它,因此您可以针对该结构进行可靠的编程。

以下是关于不变性的一个好处的一个很好的条目:Threading

答案 1 :(得分:140)

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

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

答案 2 :(得分:59)

使用readonly没有明显的性能优势,至少在任何地方我都没见过。这只是为了完全按照你的建议做的,一旦它被初始化就防止修改。

因此它有助于您编写更健壮,更易读的代码。当你在团队中工作或进行维护时,这种事情的真正好处就来了。将某些内容声明为readonly类似于在代码中为该变量的用法设置合同。您可以将其视为添加文档的方式与其他关键字(例如internalprivate一样),您说“在初始化后不应修改此变量”,而且您强制执行它。

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

答案 3 :(得分:43)

用非常实际的方式来说:

如果在dll中使用const,而dll B引用该const,则该const的值将被编译为dll B.如果使用该const的新值重新部署dll A,则dll B仍将使用原始价值。

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

答案 4 :(得分:12)

有一种可能的情况,编译器可以根据readonly关键字的存在进行性能优化。

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

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

我还没有检查当前版本的JIT编译器是否实际实现了这种优化,所以这只是推测。

答案 5 :(得分:6)

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

答案 6 :(得分:5)

不要忘记有一种解决方法可以使用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

答案 7 :(得分:2)

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

答案 8 :(得分:2)

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

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

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

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

public readonly int Foo; // a readonly field

因此,将字段标记为readonly的一个好处是可以实现与没有set运算符的属性类似的写保护级别 - 无需将字段更改为属性,如果有的话理由,这是理想的。

答案 9 :(得分:1)

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

答案 10 :(得分:1)

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 ;
        }
    }
}

答案 11 :(得分:1)

令人惊讶的是,readonly实际上可能导致代码变慢,正如Jon Skeet在测试他的Noda Time库时发现的那样。在这种情况下,在删除只读后,在20秒内运行的测试只需4秒钟。

https://codeblog.jonskeet.uk/2014/07/16/micro-optimization-the-surprising-inefficiency-of-readonly-fields/

答案 12 :(得分:0)

WPF可以带来性能优势,因为它不再需要昂贵的DependencyProperties。这对于集合

尤其有用

答案 13 :(得分:0)

使用readonly标记的另一个有趣的部分可能是保护字段不受单例初始化的影响。

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