ChatGPT解决这个技术问题 Extra ChatGPT

“静态只读”与“常量”

我已经阅读了有关 conststatic readonly 字段的信息。我们有一些只包含常量值的类。它们用于我们系统中的各种事物。所以我想知道我的观察是否正确:

对于所有公开的内容,这些常量值是否应该始终为 static readonly?并且仅将 const 用于内部/受保护/私有值?

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

这是我刚刚发现支持 static readonly 的一个非常有趣的单一案例:try using a const inside an IEnumerator which would trigger an unrecheable yield and you'll get a dreaded "Internal compiler error"。我没有在 Unity3D 之外测试代码,但我相信这是 mono.NET bug。尽管如此,这是一个 c# 问题。
另一个区别是您可以在开关中使用 const 字符串,但不能使用静态只读字符串
static readonly 不能在 switch-case 语句中用作 case 变量,为此需要 const
static readonly 也不能用作属性参数

A
AustinWBryan

public static readonly 字段有点不寻常; public static 属性(只有 get)会更常见(可能由 private static readonly 字段支持)。

const 值直接写入调用站点;这是双刃剑:

如果值是在运行时获取的,可能是从配置中获取的,它是无用的

如果更改 const 的值,则需要重建所有客户端

但它可以更快,因为它避免了方法调用......

...无论如何有时可能已被 JIT 内联

如果值将从不改变,那么 const 就可以了 - Zero 等构成合理的 consts ;p 除此之外,static 属性更常见。


为什么要在字段上设置属性?如果它是一个不可变的类,我看不出有什么区别。
@Michael - 与往常一样的原因;它隐藏了实现。您可能会发现(稍后)您需要延迟加载、基于配置、外观或其他。实际上,两者通常都很好......
@CoffeeAddict 根据定义,常量不会从配置文件中提取值;它在编译时作为文字烧录。在运行时使用常量的唯一方法是通过字段反射。在您尝试使用它的任何其他时间,编译器已经用您的常量用法代替了文字用法;即,如果您的代码中的某个方法使用 6 个常量,并且您将其作为 IL 进行检查,则不会提及任何常量查找;文字值将简单地在原位加载
@MarcGravell - 注意:readonly 字段不能在 switch/case 语句中使用,而您需要它们是 const
@didibus 将字段更改为属性实际上会破坏 API。 C# 中的字段实际上就像一个变量,而 C# 中的属性是用于编写 getter 方法和/或 setter 方法的语法助手。当涉及其他程序集时,这种差异很重要。如果将字段更改为属性,并且其他程序集依赖于该字段,则必须重新编译这些其他程序集。
G
Green Falcon

如果 Consumer 在不同的程序集中,我会使用 static readonly。将 constConsumer 放在两个不同的程序集中是 shoot yourself in the foot 的好方法。


因此,我认为正如一些人提到或暗示的那样,明智的做法是仅将 const 用于实际上是众所周知的常量的值,如果它们是公开的,否则它们应该保留用于内部、受保护或私有访问范围。
@Dio它仍然存在的原因是因为它本身不是问题 - 这是需要注意的事情,但是跨程序集边界内联 consts 的能力对性能来说是一件好事。这实际上只是一个真正理解“恒定”意味着“它永远不会改变”的问题。
@MichaelStum 好的,我不应该将其称为“问题”。在我的工作中,我确实有 const 并在程序集之间共享它,但我为每个部署或代码交付重新编译。尽管如此,这一事实绝对值得关注。
因此,一般而言,internal constpublic static readonly 取决于所需的可见性。
@Iiridayn 是的,这不是一个糟糕的看待它的方式。有一些边缘情况需要考虑(例如,如果使用反射,或者是否需要属性值),并且 public const 有有效的用途(例如,标准的任何部分。任何时候我使用 XML , 有一个包含一堆 public const string 的命名空间文件。)但通常,只有在正确考虑其含义后才能使用 public const
P
Peter Mortensen

还有一些相关的事情需要注意:

const int a

必须初始化。

初始化必须在编译时进行。

只读 int a

可以使用默认值,无需初始化。

初始化可以在运行时完成(编辑:仅在构造函数中)。


仅在 ctor 内。
不仅在构造函数中,而且在声明中 (docs.microsoft.com/en-us/dotnet/csharp/language-reference/…)。
I
Ian Kemp

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

在某些情况下,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 有两个重载,一个接受 short (System.Int16),另一个接受 object (System.Object)。现在的问题是一个或两个是否适用于我的 y 论点。

y 是编译时常量(字面量)时,在 const 的情况下,确实存在隐式转换 from int to { 变得很重要5} 前提是 int 是一个常量,并且 C# 编译器验证其值是否在 short 的范围内(42 是)。请参阅 C# 语言规范中的 Implicit constant expression conversions。因此,必须考虑这两个重载。重载 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 字段(或其他方式,如果可能的话)可以改变程序的行为。


对已接受的答案的一个很好的补充。我想补充一点,数据类型的正确转换和其他类似指南(如尝试捕获等)应该是有经验的程序员的主要内容,而不是留给编译器。尽管如此,我还是从这里学到了一些新东西。谢谢你。
哇,我已经在 C# 中编程很长时间了,我从来没有猜到一个 short 范围内的 const int 可以隐式转换为 short。我必须说这很奇怪。我喜欢 C#,但这些奇怪的不一致似乎并没有增加太多价值,但却增加了很多需要的脑力来不断考虑,这可能很烦人,尤其是对于初学者来说。
@MikeMarynowski 确实如此。但我认为他们制定了该规则(以及其他原因)以使声明 short x = 42; 合法。因为那里有一个 int,即文字 42,它隐式地变成了 short x。但是,他们可能已将其限制为数字文字;但是,他们也选择允许 short x = y; 之类的东西,其中 y 被定义为 const int y = 42;,然后他们就这样结束了。
P
Peter Mortensen

需要注意的一件事是 const 仅限于原始/值类型(字符串除外)。


实际上 const 也可以用于其他类型,只是它必须初始化为 null,这使它无用:)
System.Exception 中的异常? :)
@nawfal 更准确地说,可以使用 const 的唯一值类型sbytebyteshortushortintuintlongulongcharfloatdoubledecimalbool 以及任何 enum 类型。 const 不能用于其他值类型,例如 DateTimeTimeSpanBigInteger。它也不能用于 IntPtr 结构(被某些人认为是“原始”类型;术语原始类型在 C# 中令人困惑)。 ↵↵ const 可用于所有引用类型。如果类型为 string,则可以指定任何字符串值。否则,该值必须为 null
@JeppeStigNielsen - 我recently had an argumentservy 讨论了这个问题 - 他指出您可以使用 default 制作 任何东西(值和引用类型)const。对于 struct 类型,它是一个所有成员都设置为默认值的实例。
P
Peter Mortensen

静态只读:

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

持续的:

默认情况下 static。无法从任何地方(构造函数、函数、运行时等)更改值。

只读:

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

您可以查看我的存储库:C# property types


坏消息...断开的链接!
y
yazanpro

readonly 关键字不同于 const 关键字。 const 字段只能在字段声明时初始化。 readonly 字段可以在声明或构造函数中初始化。因此,readonly 字段可以具有不同的值,具体取决于所使用的构造函数。此外,虽然 const 字段是编译时常量,但 readonly 字段可用于运行时常量

Short and clear MSDN reference here


A
AustinWBryan

constreadonly 相似,但并不完全相同。

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

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

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

因为像 3.14 和 0 这样的值是编译时常量。但是,请考虑您定义类型并希望提供一些预制实例的情况。例如,您可能想要定义一个 Color 类并为黑色、白色等常见颜色提供“常量”。使用 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);
}

但是,没有什么可以阻止 Color 的客户使用它,也许是通过交换 Black 和 White 值。不用说,这会引起 Color 类的其他客户的恐慌。 “只读”功能解决了这种情况。

通过在声明中简单地引入 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 成员始终是静态的,而 readonly 成员可以是静态的,也可以不是静态的,就像常规字段一样。

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

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

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

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

现在,生成的代码可以依赖于 AC 是编译时常量这一事实吗?即,AC 的使用可以简单地用值 0 代替吗?如果您对此说“是”,那么这意味着 A 的开发人员无法更改 AC 的初始化方式——这在未经许可的情况下束缚了 A 的开发人员的手。

如果你对这个问题说“不”,那么就会错过一个重要的优化。也许 A 的作者肯定 AC 将永远为零。 const 和 readonly 的使用允许 A 的开发人员指定意图。这有助于更好的版本控制行为和更好的性能。


P
Peter Mortensen

我的偏好是尽可能使用 const ,如前面的答案所述,它仅限于文字表达式或不需要评估的东西。

如果我遇到了这个限制,那么我会回退到 static readonly,但要注意一点。正如 Marc 提到的 here,我通常会使用带有 getter 和支持 private static readonly 字段的公共静态属性。


P
Peter Mortensen

常量:常量变量值必须与声明一起定义,之后它不会改变。常量是隐式静态的,所以我们可以在不创建类实例的情况下访问它们。这在编译时有一个值。

ReadOnly:我们可以在声明时定义只读变量值,也可以在运行时使用构造函数。没有类实例,只读变量无法访问。

静态只读:我们可以在声明时以及仅通过静态构造函数定义静态只读变量值,但不能使用任何其他构造函数。我们也可以在不创建类实例(作为静态变量)的情况下访问这些变量。

如果我们必须在不同的程序集中使用变量,静态只读将是更好的选择。请查看以下博客文章中的完整详细信息:

Const Strings – a very convenient way to shoot yourself in the foot


你能告诉我你为什么不赞成这个答案,所以我可以在这里更新自己。
不是 DV,但可能这个答案并没有真正为这里已经全面的答案添加任何内容。
确实,请记住在 90 年代后期的 Java 中,我们在一个项目中有多个人使用类文件生成不同的 jar,这些文件可以互操作(相互引用)并且公共 const 字符串存在版本控制问题,因为它们被复制了
Y
Yagnesh Cangi

当向其他程序集公开可能在更高版本中更改的值时,静态只读字段是有利的。

例如,假设程序集 X 公开一个常量,如下所示:

public const decimal ProgramVersion = 2.3;

如果程序集 Y 引用 X 并使用此常量,则值 2.3 将在编译时烘焙到程序集 Y 中。这意味着,如果稍后重新编译 X 并将常量设置为 2.4,则在重新编译 Y 之前,Y 仍将使用旧值 2.3。静态只读字段避免了这个问题。

另一种看待这一点的方式是,任何可能在未来发生变化的值在定义上都不是恒定的,因此不应表示为一个。


C
Community

Const:Const 只不过是“常量”,它是一个变量,其值是常量,但在编译时。并且必须为其分配一个值。默认情况下,const 是静态的,我们不能在整个程序中更改 const 变量的值。静态只读:静态只读类型变量的值可以在运行时赋值或在编译时赋值并在运行时更改。但是这个变量的值只能在静态构造函数中改变。并且无法进一步更改。它只能在运行时更改一次

参考:c-sharpcorner


C
Chirag

C#.Net 中的 const 和 static 只读字段之间存在细微差别

const 必须在编译时用值初始化。

const 默认是静态的,需要用常量值初始化,以后不能修改。它不能用于所有数据类型。对于前日期时间。它不能与 DateTime 数据类型一起使用。

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

readonly 可以声明为静态的,但不是必需的。声明时无需初始化。可以使用构造函数一次分配或更改其值。所以有可能改变一次只读字段的值(没关系,如果它是静态的),这对于 const 是不可能的。


d
dasumohan89

常量:

值应在声明编译时间常数时给出

只读:

可以在声明时或在运行时使用构造函数给出值。值可能因使用的构造函数而异。运行时间常数


回复“应该给予”:你的意思是“必须给予”吗?有办法解决吗?
D
DvS

const(在编译时确定)可以用于只读静态不能使用的情况,例如 switch 语句或属性构造函数。这是因为只读字段仅在运行时解析,并且某些代码构造需要编译时保证。可以在构造函数中计算只读静态,这通常是必不可少且有用的东西。区别是功能性的,我认为它们的用法也应该如此。

在内存分配方面,至少对于字符串(作为引用类型)而言,似乎没有区别,因为两者都是实习生并且将引用一个实习生实例。

就个人而言,我的默认值是只读静态的,因为它对我来说更具语义和逻辑意义,特别是因为在编译时不需要大多数值。而且,顺便说一下,公共只读静态数据并不罕见或不常见,正如标记的答案所述:例如,System.String.Empty 是一。


B
Boris Lipschitz

声明 const 和 static readonly 的另一个区别在于内存分配。

静态字段属于对象的类型,而不属于该类型的实例。结果,一旦第一次引用该类,静态字段将在内存中“存在”剩余时间,并且该静态字段的同一实例将被该类型的所有实例引用。

另一方面,一个 const 字段“属于该类型的一个实例。

如果释放内存对您来说更重要,则更喜欢使用 const。如果速度快,则使用静态只读。


G
Guney Ozsan

如果您可以提供编译时常量,请使用 const

private const int Total = 5;

如果您需要在运行时评估您的值,请使用 static readonly

private static readonly int GripKey = Animator.StringToHash("Grip");

这将产生编译错误,因为在编译时无法获取该值。

private const int GripKey = Animator.StringToHash("Grip");

P
Peter Mortensen

常量就像名字所暗示的那样,不会改变的字段,通常在编译时在代码中静态定义。

只读变量是可以在特定条件下更改的字段。

它们可以在你第一次像常量一样声明它们时被初始化,但通常它们在构造函数内的对象构造期间被初始化。

在上述条件下,它们在初始化发生后无法更改。

对我来说,静态只读听起来像是一个糟糕的选择,因为如果它是静态的并且永远不会改变,那么只需使用它 public const。如果它可以更改,那么它不是一个常量,然后,根据您的需要,您可以使用只读变量或仅使用常规变量。

另外,另一个重要的区别是常量属于类,而只读变量属于实例!


U
UberFace

有一个重要的问题,在上述答案的任何地方都没有提到,应该驱使你更喜欢“const”,特别是对于“int”、“string”等基本类型。

常量可以作为属性参数,静态只读字段不行!

Azure functions HttpTrigger, not using HttpMethods class in attribute

如果只有微软为 Http 的 GET、POST、DELETE 等使用常量。

可以写

[HttpTrigger(AuthorizationLeve.Anonymous,  HttpMethods.Get)] // COMPILE ERROR: static readonly, 

但相反,我不得不求助于

[HttpTrigger(AuthorizationLeve.Anonymous,  "GET")] // STRING

或者使用我自己的常量:

public class HttpConstants
{
    public const string Get = "GET";
}

[HttpTrigger(AuthorizationLeve.Anonymous,  HttpConstants.Get)] // Compile FINE!

不过,我不确定我是否会称其为特别 important。对于给定的示例,我只写 "GET",因为它要短得多,而且无论如何都不会改变。 🤷‍♂️
S
Santosh Karanam

常量

只能应用于字段。值应该在代码编译时。适合在编译代码之前在代码中删除魔术“字符串”、“int/double”、(原始类型)等。编译后,该值将放置在使用常量的所有编译代码中。所以如果你有一个巨大的字符串在很多地方使用,那么在使它成为 const 之前要小心。考虑使用静态只读。

静态只读

静态只读适用于字段/道具,静态可用于方法。 (附注)当静态应用于方法时,编译后的代码不会将“this”参数传递给方法,因此您无法访问对象的实例数据。适用于编译代码后可能更改的值。就像从配置中初始化的值,在应用程序启动期间等。编译代码后,对值的引用在 IL 代码中使用,与使用 const 相比可能会更慢,但编译后的代码很小

在重构期间,所有 const 都可以安全地转换为静态只读,但反之亦然,正如我们在上面看到的,当转换的代码可能会中断,因为可以在构造函数中初始化一些静态只读变量。


M
Mmm

上面提到了我不相信的另一个区别:

conststatic readonly 值不会在 Visual Studio IDE 中应用 CodeLens

static 仅获取属性确实会将 CodeLens 应用于它们。

https://i.stack.imgur.com/9fVpP.png

我认为添加 CodeLens 非常有价值。

注意:当前使用 Visual Studio 2022。


P
Pavlo Somko

const、readonly、static readonly - 执行类似操作但有重要区别的关键字:

• Const - 是一个变量,其值为常量,在编译时分配。您必须为其分配一个值。默认常量是静态的,我们不能在整个程序中改变 const 变量的值。

• Readonly - 表示我们可以在运行时更改的值,或者我们可以在运行时分配它,但只能通过非静态构造函数。

• 静态只读- 值可以在运行时分配或在编译时分配并在运行时更改。但是这个变量的值只能在静态构造函数中改变。并且无法进一步更改。在执行过程中只能更改一次。

您可以在此处找到示例 - https://www.c-sharpcorner.com/UploadFile/c210df/difference-between-const-readonly-and-static-readonly-in-C-Sharp/