静态只读 vs. 常量

1583

我了解了 const static readonly 字段。我们有一些仅包含常量值的类。它们在系统中用于各种目的。所以我想知道我的观察是否正确:

对于所有公共内容,这种类型的常量值是否应始终是 static readonly ?而仅对 internal / protected / private 值使用 const

您有什么建议?甚至我也不应该使用 static readonly 字段,而应该使用属性吗?


7
我找到了一个非常有趣的例子,支持使用 static readonly尝试在 IEnumerator 中使用 const 会触发无法到达的 yield,导致编译器出现可怕的“内部编译器错误”。虽然我没有在 Unity3D 之外测试过这段代码,但我相信这是 mono.NET问题。然而,这仍然是一个关于 c# 的问题。 - cregox
4
可能是 const 和 readonly 有什么区别? 的重复问题。 - nawfal
10
另一个不同之处在于,您可以在 switch 语句中使用 const 字符串,但不能使用 static readonly 字符串。 - flagg19
9
switch-case语句中,不能使用static readonly作为case变量,需要使用const来替代。翻译后的内容保持原意,通俗易懂,无解释,无额外内容返回。 - Mostafiz Rahman
4
static readonly 不能用作属性参数。 - Dread Boy
有关详细信息,请访问 https://enlear.academy/const-vs-readonly-vs-static-readonly-in-c-755c20aa0b57 - Trident
22个回答

1064

public static readonly字段有些不同寻常;public static属性(只有一个get)会更为普遍(可能由private static readonly字段支持)。

const值被直接烧入调用站点;这有利有弊:

  • 如果值在运行时从配置中获取,则无用
  • 如果更改常量的值,则需要重新构建所有客户端
  • 但它可能会更快,因为它避免了方法调用...
  • ... JIT有时可能已经内联该调用

如果值永远不会更改,则使用const就可以了 - Zero等比较合适作为常量;P除此之外,static属性更为普遍。


18
为什么要在域上定义属性?如果它是不可变类,我看不出有什么区别。 - Michael Hedgpeth
85
@Michael - 和往常一样的原因;它隐藏了实现细节。你可能会发现(以后)你需要进行延迟加载,基于配置的设计,创建外观模式等等。其实,这些方法都可以达到同样的效果... - Marc Gravell
53
根据定义,常量并不会从配置文件中获取值;它是在编译时作为文本字符串直接嵌入的。你只能通过反射来访问这些常量,并在运行时使用它们。在其他任何时间使用常量,编译器都已经将你的常量用法替换为文本字符串用法。例如,如果你的代码中有一个方法使用了6个常量,并且你以IL(Intermediate Language)形式检查它,那么就没有提到任何常量查找;这些文本字符串值将被直接加载。 - Marc Gravell
45
警告:readonly 字段不能在 switch/case 语句中使用,相反你需要将它们声明为 const - Luciano
10
将一个字段改为属性会破坏API。在C#中,字段实际上就像变量,而属性是用于编写getter方法和/或setter方法的语法辅助工具。当涉及到其他程序集时,这种区别非常重要。如果将一个字段改为属性,并且其他程序集依赖于该字段,则必须重新编译那些其他程序集。 - Stephen Booher
显示剩余8条评论

281

如果消费者在不同的程序集中,我会使用static readonly。 将const消费者放在两个不同的程序集中是一种让自己自寻烦恼的好方法。


8
我认为,正如一些人已经提到或暗示的那样,如果将某些值公开,只有确实是已知常量的值才使用const是明智的,否则应该保留为内部、受保护或私有访问范围。 - jpierson
2
@Dio 它仍然存在的原因是因为它本身并不是一个问题 - 它只是需要注意的事项,但是在汇编边界内联常量的能力对于性能来说是一件好事。这只是真正理解“常量”意味着“它永远不会改变”的问题。 - Michael Stum
1
@MichaelStum 好的,我不应该称其为“问题”。在我的工作中,我确实有常量并跨程序集共享它,但我会为每个部署或代码发货重新编译。尽管如此,这个事实绝对值得注意。 - Dio Phung
3
因此,一般情况下,根据所需的可见性选择 internal constpublic static readonly - Iiridayn
2
@Iiridayn 是的,这不是一个坏的看法。需要考虑一些边缘情况(例如,如果使用反射,或者如果需要在属性上使用值),并且public const有其有效用途(例如,任何标准的一部分。每当我使用XML时,都会有一个包含许多public const string的命名空间文件)。但通常情况下,在充分考虑后才应使用public const - Michael Stum
显示剩余3条评论

227

需要注意的几点:

const int a

  • 必须初始化。
  • 初始化必须在编译时完成。

readonly int a

  • 可以使用默认值,不需要初始化。
  • 初始化可以在运行时进行(编辑:只能在构造函数内执行)。

46
只能在构造函数内部。 - Amit Kumar Ghosh
4
不仅在构造函数内部,在声明时也可以使用 "readonly" 关键字。(https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/readonly) - deChristo

220

这只是对其他答案的补充,我不会重复它们(现在四年过去了)。

有些情况下,const 和非 const 具有不同的语义。例如:

const int y = 42;

static void Main()
{
  short x = 42;
  Console.WriteLine(x.Equals(y));
}

打印出True,而:

static readonly int y = 42;

static void Main()
{
  short x = 42;
  Console.WriteLine(x.Equals(y));
}

写入False

原因是方法x.Equals有两个重载,一个接受shortSystem.Int16),另一个接受objectSystem.Object)。现在的问题是我的y参数是否应用一个或两个重载。

y是编译时常量(字面量)时,即const情况,很重要的一点是存在从intshort的隐式转换,假设int是一个常量,并且C#编译器验证它的值在short范围内(而42是),请参阅C#语言规范中的隐式常量表达式转换。所以必须考虑两个重载。首选覆盖Equals(short)(任何short都是object,但不是所有object都是short)。所以y被转换为short,并使用该重载。然后,Equals比较两个具有相同值的short,这会返回true

y不是常量时,不存在从intshort隐式转换。这是因为一般来说int可能太大而无法适应short。(显式转换确实存在,但我没有写Equals((short)y),所以与此无关。)我们看到只有一个重载适用,即Equals(object)。所以y被装箱为object。然后,Equals将比较System.Int16System.Int32,由于运行时类型甚至不一致,这将产生false

我们得出结论,在某些(罕见)情况下,更改const类型成员为static readonly字段(或反过来,如果可能的话)可以更改程序的行为。


22
对被接受答案的很好的补充。我想要补充的是,数据类型的正确转换和其他类似的指导原则(例如try-catch等)应该是经验丰富的程序员必备的技能,而不是留给编译器处理。尽管如此,我还是从这里学到了新东西。谢谢。 - Uknight
3
哇,我已经用C#编程很长时间了,但我从来没有想到一个在short范围内的const int可以被隐式转换为short。我必须说这相当奇怪。我喜欢C#,但这些奇怪的不一致性似乎并没有增加太多价值,却需要我们不断考虑,对于初学者来说可能会很烦人。 - Mike Marynowski
4
@MikeMarynowski 没错。但我认为他们制定这条规则(除了其他原因之外)是为了使语句 short x = 42; 合法化。因为在这里,你有一个 int,即字面值 42,它被隐式转换为 short x。但随后,他们可能将其限制为仅限于数字字面值;然而,他们选择允许像 short x = y; 这样的东西,其中 y 被定义为 const int y = 42;,于是就出现了这种情况。 - Jeppe Stig Nielsen

101

需要注意的一点是,const 仅限于原始值类型(字符串是一个例外)。


33
实际上,“const”也可以用于其他类型,只不过它必须初始化为null,这使它变得无用 :) - nawfal
6
System.Exception 中,"exception" 的意思是异常吗? :) - Memet Olsen
6
更精确地说,仅可以对以下“值类型”使用const关键字:sbytebyteshortushortintuintlongulongcharfloatdoubledecimalbool以及任何 enum 类型。const 不能用于其他值类型,例如 DateTimeTimeSpanBigInteger。它也不能用于 IntPtr 结构体(在某些情况下被认为是“原始”类型;在 C# 中,“原始”类型这个术语有些令人困惑)。 const 可以用于所有引用类型。如果类型是 string,可以指定任何字符串值。否则,该值必须为 null - Jeppe Stig Nielsen
2
@JeppeStigNielsen - 我最近与servy就此事争论过 - 他指出,你可以使用default使任何(值类型和引用类型)都变成const。对于struct类型,它是一个所有成员都设置为默认值的实例。 - Wai Ha Lee

40

静态只读(Static Read Only):

该值可以在运行时通过 static 构造函数更改,但不能通过成员函数更改。

常量(Constant):

默认为 static。该值无论是在构造函数、函数、运行时等任何地方都无法更改。

只读(Read Only):

该值可以在运行时通过构造函数更改,但不能通过成员函数更改。

您可以查看我的存储库:C# 属性类型


1
坏消息...链接已损坏! - Fer R
2
@FerR 给你链接:https://github.com/yeasin90/advanced-csharp/blob/master/CSharpAdvanced/VariableTypes.cs - Yeasin Abedin

32

readonly 关键字与 const 关键字不同。一个 const 字段只能在字段的声明时初始化。一个 readonly 字段可以在声明或构造函数中初始化。因此,readonly 字段根据使用的构造函数可以具有不同的值。而且,虽然 const 字段是编译时常量,但 readonly 字段可用于运行时常量。

来自这个简短明了的 MSDN 参考资料


23

constreadonly很相似,但它们并不完全相同。

const字段是编译时常量,这意味着该值可以在编译时计算。readonly字段使得某些代码必须在类型构造期间运行的其他场景成为可能。构造完成后,readonly字段不能被更改。

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

struct Test
{
    public const double Pi = 3.14;
    public const int Zero = 0;
}

因为像3.14和0这样的值是编译时常量。但是考虑到您定义一种类型并想要提供其某些预制实例的情况。例如,您可能希望定义一个颜色类并为常见颜色(如黑色、白色等)提供“常量”。使用const成员无法实现此目的,因为右侧不是编译时常量。可以使用常规静态成员来实现:

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, green, blue) = (r, g, b);
}

但是,如果没有任何方法防止颜色类的客户端对其进行操作,例如交换黑白值,这无疑会给其他颜色类客户端带来困扰。"只读"功能解决了这种情况。

仅通过在声明中引入readonly关键字,我们既保留了灵活的初始化方式,同时又防止了客户端代码的操作。

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, green, blue) = (r, g, b);
}

有趣的是,const成员始终是静态的,而只读成员可以是静态的或非静态的,就像普通字段一样。

可以使用一个关键字来实现这两个目的,但这会导致版本问题或性能问题。暂时假设我们为此使用了一个关键字(const),并且开发人员编写了以下代码:

public class A
{
    public static const C = 0;
}

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

public class B
{
    static void Main() => Console.WriteLine(A.C);
}

现在,生成的代码能否依赖于 A.C 是一个编译时常量这一事实呢?也就是说,可以将对 A.C 的使用简单替换为值 0 吗?如果你回答“是”,那么就意味着 A 的开发者不能改变初始化 A.C 的方式——这样就未经许可限制了 A 的开发者。

如果你回答“否”,那么就会错过一个重要的优化机会。或许 A 的作者确信 A.C 总是为零。使用 const 和 readonly 都允许 A 的开发者指定意图,这会带来更好的版本控制行为和更好的性能。


16

我的偏好是尽可能使用const,正如先前的答案所提到的那样,这仅限于文字表达式或不需要求值的内容。

如果我遇到了这个限制,那么我会退而使用static readonly,但有一个例外。通常我会使用一个公共静态属性,它带有一个getter和一个支持的private static readonly字段,就像Marc在这里所提到的那样。


13

Const: 常量变量的值必须在声明时定义,之后就不会改变。const隐式地是静态的,因此我们可以在没有创建类实例的情况下访问它们。这个值在编译时确定。

ReadOnly: 我们可以在声明时以及在运行时使用构造函数定义只读变量的值。只读变量不能在没有类实例的情况下访问。

Static readonly: 我们可以在声明时以及仅通过静态构造函数而不是任何其他构造函数定义静态只读变量的值。我们还可以在没有创建类实例的情况下访问这些变量(作为静态变量)。如果我们必须在不同的程序集中使用变量,静态只读将是更好的选择。请在以下博客文章中查看完整细节:

Const Strings - 一种非常方便的自我毁灭方式


1
请问您为什么要给答案点踩呢?能否告诉我原因,这样我就可以更新自己的知识并且改进回答了。 - user1756922
1
不是DV,但这个答案可能并没有为此处已经详尽的回答增添什么内容。 - Marc L.
确实,在 Java 的 90 年代末,我们在一个项目中有多个人生成不同的 JAR 文件,这些文件互操作(相互引用)并且公共常量字符串存在版本问题,因为它们被复制到各处。 - George Birbilis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接