关于C:const和readonly有什么区别? public, private, protected

What is the difference between const and readonly?

constreadonly有什么区别,你用一个比另一个好吗?


除了

  • 在定义constreadonly值时,必须声明该值。该值可以动态计算,但需要在构造函数退出之前进行赋值。在那之后它就冻结了。
  • ‘const’暗含在cx1〔4〕中。您可以使用ClassName.ConstantName符号来访问它们。

有细微的差别。考虑在AssemblyA中定义的类。

1
2
3
4
5
6
7
8
9
public class Const_V_Readonly
{
  public const int I_CONST_VALUE = 2;
  public readonly int I_RO_VALUE;
  public Const_V_Readonly()
  {
     I_RO_VALUE = 3;
  }
}

AssemblyB引用AssemblyA并在代码中使用这些值。编译时,

  • const值的情况下,就像查找替换一样,值2"烘焙"到AssemblyB的il中。这意味着如果明天我会把I_CONST_VALUE更新到20。在我重新编译之前,AssemblyB仍然有2个。
  • readonly值的情况下,对内存位置来说就像是ref。该值不烘焙到EDOCX1的il中。这意味着,如果更新内存位置,AssemblyB将在不重新编译的情况下获取新值。因此,如果I_RO_VALUE更新为30,您只需要构建AssemblyA。不需要重新编译所有客户端。

因此,如果您确信常量的值不会改变,请使用const

1
public const int CM_IN_A_METER = 100;

但是如果你有一个常数,它可能会改变(例如w.r.t.精度)。或者在有疑问时,使用readonly

1
public readonly float PI = 3.14;

更新:阿库需要得到一个提及,因为他首先指出了这一点。我还需要把我学到的东西插上。有效C-比尔·瓦格纳


有个警察来了!如果引用另一个程序集中的常量,则其值将直接编译到调用程序集中。这样,当您更新引用程序集中的常量时,调用程序集中的常量就不会改变!


常量

  • 默认情况下常量是静态的
  • 它们在编译时必须有一个值(例如,可以有3.14*2,但不能调用方法)
  • 无法在函数中声明
  • 复制到使用它们的每个程序集中(每个程序集都获取值的本地副本)
  • 可用于属性

只读实例字段

  • 在构造函数退出时必须具有设置值
  • 在创建实例时计算

静态只读字段

  • 当代码执行命中类引用(创建新实例或执行静态方法时)时计算
  • 必须在完成静态构造函数时具有计算值
  • 不建议在这些线程上放置threadstaticattribute(静态构造函数将只在一个线程中执行,并将为其线程设置值;所有其他线程都将具有未初始化的该值)

只需添加,引用类型的只读只会使引用只读,而不是值。例如:

1
2
3
4
5
6
7
8
9
10
11
public class Const_V_Readonly
{
  public const int I_CONST_VALUE = 2;
  public readonly char[] I_RO_VALUE = new Char[]{'a', 'b', 'c'};

  public UpdateReadonly()
  {
     I_RO_VALUE[0] = 'V'; //perfectly legal and will update the value
     I_RO_VALUE = new char[]{'V'}; //will cause compiler error
  }
}


这就解释了。摘要:const必须在声明时初始化,readonly可以在构造函数上初始化(因此,根据所使用的构造函数,有不同的值)。

编辑:关于细微差别,请参见上面的Gishu's Gotcha。


const:不能在任何地方更改。

readonly:此值只能在构造函数中更改。无法在正常功能中更改。


有一个小的只读的gotcha。可在构造函数内多次设置只读字段。即使在两个不同的链接构造函数中设置了该值,仍然允许这样做。

1
2
3
4
5
6
7
8
9
10
11
public class Sample {
    private readonly string ro;

    public Sample() {
        ro ="set";
    }

    public Sample(string value) : this() {
        ro = value; // this works even though it was set in the no-arg ctor
    }
}


常量成员在编译时定义,不能在运行时更改。常量使用const关键字声明为字段,必须按照声明的方式初始化。

1
2
3
4
public class MyClass
{
    public const double PI1 = 3.14159;
}

readonly成员就像一个常量,因为它代表一个不变的值。不同之处在于,readonly成员可以在运行时在构造函数中初始化,也可以在声明时进行初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyClass1
{
     public readonly double PI2 = 3.14159;

     //or

     public readonly double PI3;

     public MyClass2()
     {
         PI3 = 3.14159;
     }
}

康斯特

  • 它们不能声明为static(它们是隐式静态的)
  • 常量的值在编译时计算
  • 常量仅在声明中初始化

只读

  • 它们可以是实例级的,也可以是静态的
  • 在运行时评估该值
  • 只读可以在声明中初始化,也可以通过构造函数中的代码初始化


常量是编译时常量,而readonly允许在运行时计算值并在构造函数或字段初始值设定项中设置值。因此,"const"始终是常量,但"read only"一旦被分配就是只读的。

C团队的Eric Lippert有更多关于不同类型不变性的信息


下面是另一个链接,演示const是如何不安全的版本,或者与引用类型相关。

总结:

  • const属性的值在编译时设置,不能在运行时更改。
  • 常量不能标记为静态-关键字表示它们是静态的,而只读字段则可以。
  • 常量不能是除值(基元)类型之外的任何类型
  • readonly关键字将字段标记为不可更改。但是,可以在类的构造函数内更改属性
  • readonly关键字还可以与static组合使用,使其与const(至少在表面上)的作用方式相同。当你看这两者之间的IL时有明显的区别
  • 常量字段在IL中标记为"literal",而readonly为"initonly"

只读:值可以在运行时通过ctor更改。但不是通过成员函数

常数:以防静电。值不能从任何位置更改(ctor、函数、运行时等,不可从何处更改)


另一个问题是:只读值可以通过反射被"不正确"的代码更改。

1
2
3
4
5
var fi = this.GetType()
             .BaseType
             .GetField("_someField",
                       BindingFlags.Instance | BindingFlags.NonPublic);
fi.SetValue(this, 1);

我可以使用反射更改C中的私有只读继承字段吗?


我相信所有对象的const值都是相同的(必须用文本表达式初始化),而readonly对于每个实例化都是不同的…


它们都是常量,但在编译时也可以使用常量。这意味着区别的一个方面是可以使用常量变量作为属性构造函数的输入,但不能使用只读变量。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static class Text {
  public const string ConstDescription ="This can be used.";
  public readonly static string ReadonlyDescription ="Cannot be used.";
}

public class Foo
{
  [Description(Text.ConstDescription)]
  public int BarThatBuilds {
    { get; set; }
  }

  [Description(Text.ReadOnlyDescription)]
  public int BarThatDoesNotBuild {
    { get; set; }
  }
}


我们办公室的一名团队成员就何时使用const、static和readonly提供了以下指导:

  • 如果在运行时可以知道某个类型的变量(string literal、int、double、enums,…)希望类的所有实例或使用者都可以访问不应更改值的位置,请使用const。
  • 当您拥有希望类的所有实例或使用者都可以访问值可以更改的位置的数据时,请使用static。
  • 当有一个类型的变量在运行时(对象)无法知道您希望类的所有实例或使用者都可以访问不应更改值的位置时,请使用static readonly。
  • 当您有一个实例级变量时,请使用readonly,该变量在创建对象时是不会更改的。

最后一个注意事项:常量字段是静态的,但反转不是真的。


标记为const的变量只不过是强类型定义宏,在编译时const变量引用将替换为内联文本值。因此,只能以这种方式使用某些内置基元值类型。标记为read only的变量可以在运行时在构造函数中设置,并且在运行时也强制执行它们的只读性。与此相关联的性能开销很小,但这意味着您可以对任何类型(甚至引用类型)使用readonly。

此外,const变量本质上是静态的,而readonly变量可以根据需要特定于实例。


在C.NET中,const和readonly字段之间存在显著差异。

常量在默认情况下是静态的,需要用常量值初始化,以后不能修改。构造函数中也不允许更改值。它不能与所有数据类型一起使用。对于ex-datetime。它不能与datetime数据类型一起使用。

1
2
3
public const DateTime dt = DateTime.Today;  //throws compilation error
public const string Name = string.Empty;    //throws compilation error
public readonly string Name = string.Empty; //No error, legal

只读可以声明为静态,但不是必需的。声明时不需要初始化。它的值可以使用构造函数进行分配或更改。因此,它在用作实例类成员时具有优势。两个不同的实例化可能具有不同的只读字段值。对于前

1
2
3
4
5
6
7
8
9
class A
{
    public readonly int Id;

    public A(int i)
    {
        Id = i;
    }
}

然后,只读字段可以用即时特定的值初始化,如下所示:

1
2
A objOne = new A(5);
A objTwo = new A(10);

这里,实例objone的readonly字段值为5,objtwo的readonly字段值为10。使用const是不可能的。


另一种方法。

由于const实际上只处理基本数据类型,如果您想处理类,可能会感到"被迫"使用readonly。但是,小心陷阱!readonly表示不能用另一个对象替换该对象(不能使它引用另一个对象)。但是任何引用对象的进程都可以自由地修改对象内部的值!

所以不要困惑地认为readonly意味着用户不能改变事情。在C中没有简单的语法来防止类的实例化改变其内部值(据我所知)。


常量将作为文本值编译到使用者中,而静态字符串将作为对所定义值的引用。

作为练习,尝试创建一个外部库并在控制台应用程序中使用它,然后更改库中的值并重新编译它(不重新编译使用者程序),将dll放到目录中并手动运行exe,您会发现常量字符串没有更改。


常数

我们需要在定义const字段时为其提供值。然后,编译器将常量的值保存在程序集的元数据中。这意味着一个常量只能为诸如boolean、char、byte等基元类型定义。常量始终被视为静态成员,而不是实例成员。

只读

只读字段只能在运行时解析。这意味着我们可以使用字段声明类型的构造函数为值定义一个值。编译器会验证只读字段不是由构造函数以外的任何方法写入的。

有关这两个方面的详细信息,请参阅本文


康斯特

  • const关键字可以应用于字段或局部变量
  • 我们必须在声明时指定const字段
  • 没有分配内存,因为编译后常量值嵌入到IL代码本身中。就像查找所有出现的常量变量并用其值替换。因此编译后的IL代码将用硬编码的值代替常量变量
  • C中的常量默认为静态。
  • 所有对象的值都是常量
  • 存在DLL版本控制问题-这意味着每当我们更改公共常量变量或属性时(实际上,理论上不应该更改),任何使用此变量的其他DLL或程序集都必须重新构建。
  • 只有C内置类型可以声明为常量
  • 常量字段不能作为ref或out参数传递
  • 只读

  • 只读关键字只适用于字段,而不适用于局部变量
  • 我们可以在声明时或在构造函数中分配只读字段,而不是在任何其他方法中。
  • 动态内存分配给只读字段,我们可以在运行时获取该值。
  • 只读属于仅通过类实例访问创建的对象。为了使它成为类成员,我们需要在readonly之前添加静态关键字。
  • 根据所使用的构造函数,值可能不同(因为它属于类的对象)
  • 如果将非基元类型(引用类型)声明为只读,则引用是不可变的,而不是其包含的对象。
  • 由于该值是在运行时获得的,因此只读字段/属性没有DLL版本控制问题。
  • 我们可以在构造函数上下文中将只读字段作为ref或out参数传递。

  • 区别在于静态只读字段的值是在运行时设置的,因此对于程序的不同执行,它可以有不同的值。但是,const字段的值设置为编译时常量。

    记得:对于引用类型,在这两种情况下(静态和实例),只读修饰符只会阻止您为字段分配新的引用。它不会使引用指向的对象不可变。

    有关详细信息,请参阅本主题的C常见问题:http://blogs.msdn.com/csharpfaq/archive/2004/12/03/274791.aspx


    const和readonly是相似的,但它们并不完全相同。常量字段是编译时常量,这意味着该值可以在编译时计算。只读字段允许在构造类型期间运行某些代码的其他方案。构造后,无法更改只读字段。

    例如,const成员可用于定义如下成员:

    1
    2
    3
    4
    5
    struct Test
    {
        public const double Pi = 3.14;
        public const int Zero = 0;
    }

    因为像3.14和0这样的值是编译时常量。但是,考虑一下您定义类型并希望提供它的一些fab前实例的情况。例如,您可能希望定义一个颜色类,并为常见颜色(如黑色、白色等)提供"常量"。使用const成员是不可能的,因为右手边不是编译时常量。我们可以对常规静态成员执行此操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class Color
    {
        public static Color Black = new Color(0, 0, 0);
        public static Color White = new Color(255, 255, 255);
        public static Color Red = new Color(255, 0, 0);
        public static Color Green = new Color(0, 255, 0);
        public static Color Blue = new Color(0, 0, 255);
        private byte red, green, blue;

        public Color(byte r, byte g, byte b) {
            red = r;
            green = g;
            blue = b;
        }
    }

    但是没有什么可以阻止一个有颜色的客户去弄脏它,也许是通过交换黑白值。不用说,这会给颜色类的其他客户机带来恐慌。"只读"功能解决了这种情况。通过在声明中简单地引入readonly关键字,我们保留了灵活的初始化,同时防止客户机代码乱放。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class Color
    {
        public static readonly Color Black = new Color(0, 0, 0);
        public static readonly Color White = new Color(255, 255, 255);
        public static readonly Color Red = new Color(255, 0, 0);
        public static readonly Color Green = new Color(0, 255, 0);
        public static readonly Color Blue = new Color(0, 0, 255);
        private byte red, green, blue;

        public Color(byte r, byte g, byte b) {
            red = r;
            green = g;
            blue = b;
        }
    }

    有趣的是,const成员总是静态的,而readonly成员可以是静态的,也可以不是静态的,就像正则字段一样。

    可以将单个关键字用于这两个目的,但这会导致版本控制问题或性能问题。假设我们对此(const)使用了一个关键字,开发人员写道:

    1
    2
    3
    4
    public class A
    {
        public static const C = 0;
    }

    另一个开发人员编写的代码依赖于:

    1
    2
    3
    4
    5
    6
    public class B
    {
        static void Main() {
            Console.WriteLine(A.C);
        }
    }

    现在,生成的代码是否依赖于a.c是编译时常量这一事实?也就是说,交流电的使用可以简单地用0代替吗?如果您对此说"是",那么这意味着a的开发人员无法更改a.c的初始化方式——这将在未经许可的情况下将a的开发人员的双手绑在一起。如果你对这个问题说"不",那么就会错过一个重要的优化。也许A的作者肯定A.C永远是零。使用const和readonly允许的开发人员指定意图。这有助于更好的版本控制行为和更好的性能。


    只读:该值将仅从类的构造函数初始化一次。const:只能在任何函数中初始化一次


    主要地,您可以在运行时将一个值赋给静态只读字段非常量值,而常量值则必须赋给常量值。


    常量变量在编译时声明和初始化。病房后不能更改该值。只读变量将仅从类的静态构造函数初始化。只读仅在希望在运行时分配值时使用。


    const必须硬编码,其中readonly可以在类的构造函数中设置。


    还有一件事要补充人们上面所说的。如果程序集包含只读值(例如readonly maxfoocount=4;),则可以通过使用不同的值(例如readonly maxfoocount=5;)发送该程序集的新版本来更改调用程序集所看到的值。

    但使用const,在编译调用者时,它将被折叠到调用者的代码中。

    如果你已经达到了这一水平,你就已经准备好接受比尔·瓦格纳的书,有效的C:50种提高你的C的具体方法。#它详细地回答了这个问题(以及其他49件事)。


    简而言之,const是编译时,readonly是运行时。


    关键区别在于const是定义的c等价物。这个数字被替换为A-LA预编译程序。readonly实际上被视为一个变量。

    当项目A依赖于项目B中的公共常数时,这种区别尤其相关。假设公共常数发生了变化。现在,您选择const/readonly将影响项目A的行为:

    const:项目A不捕获新值(当然,除非用新const重新编译),因为它是用子脚本中的常量编译的。

    只读:项目A总是要求项目B提供它的变量值,因此它将在B中获取公共常量的新值。

    老实说,我建议您对几乎所有的东西都使用readonly,除了真正的通用常量(例如pi,inches_to_cm)。对于任何可能改变的东西,我说用readonly。

    希望这有帮助,艾伦。