【问题标题】:'Static readonly' vs. 'const'“静态只读”与“常量”
【发布时间】:2010-10-19 20:00:01
【问题描述】:

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

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

你有什么推荐的?我是否应该甚至不使用 static readonly 字段,而是使用属性?

【问题讨论】:

标签: c# constants


【解决方案1】:

常量

  1. 只能应用于字段。值应该在代码编译时。
  2. 适用于在编译代码之前在代码中移除魔法“字符串”、“int/double”、(原始类型)等。
  3. 编译后,该值将被放置在所有使用常量的编译代码中。因此,如果您有一个巨大的字符串在很多地方使用,那么在将其设为 const 之前要小心。考虑使用静态只读。

静态只读

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

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

【讨论】:

    【解决方案2】:

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

    private const int Total = 5;
    

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

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

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

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

    【讨论】:

      【解决方案3】:

      有一个重要的问题,上面的答案中没有提到,应该驱使你更喜欢“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!
      

      【讨论】:

      • 我不确定我是否会将此称为特别重要。对于给定的示例,我只写"GET",因为它要短得多,而且无论如何都不会改变。 ?‍♂️
      【解决方案4】:

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

      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 是不可能的。

      【讨论】:

        【解决方案5】:

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

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

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

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

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

        【讨论】:

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

        常量顾名思义,字段不会改变,通常在编译时在代码中静态定义。

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

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

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

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

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

        【讨论】:

          【解决方案7】:

          静态只读

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

          常数

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

          只读

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

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

          【讨论】:

          【解决方案8】:

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

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

          【讨论】:

            【解决方案9】:

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

            const int a

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

            只读 int a

            • 可以使用默认值,无需初始化。
            • 可以在运行时进行初始化(编辑:仅在构造函数中)。

            【讨论】:

            【解决方案10】:

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

            【讨论】:

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

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

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

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

            如果释放内存对你来说更重要,最好使用 const。如果速度快,则使用static readonly

            【讨论】:

              【解决方案12】:

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

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

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

              【讨论】:

                【解决方案13】:

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

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

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

                【讨论】:

                  【解决方案14】:

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

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

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

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

                  【讨论】:

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

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

                  在某些情况下,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 的情况下,确实存在隐式转换 int 到 em> short 前提是 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;,然后他们就这样结束了。
                  【解决方案16】:

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

                  【讨论】:

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

                  Const: Const 只不过是“常量”,它的值是常量但在编译时是一个变量。并且必须为其分配一个值。默认情况下,const 是静态的,我们不能在整个程序中更改 const 变量的值。

                  静态只读:静态只读类型变量的值可以在运行时赋值或在编译时赋值并在运行时更改。但是这个变量的值只能在静态构造函数中改变。并且无法进一步更改。它在运行时只能更改一次

                  参考:c-sharpcorner

                  【讨论】:

                    【解决方案18】:

                    静态只读字段在暴露于 其他程序集的值可能会在以后的版本中更改。

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

                    public const decimal ProgramVersion = 2.3;
                    

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

                    另一种看待这个问题的方式是,任何可能的值 根据定义,未来的变化不是恒定的,因此应该 不能表示为一个。

                    【讨论】:

                      【解决方案19】:

                      常量:

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

                      只读:

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

                      【讨论】:

                      • Re“应该给予”:你的意思是“必须给予”吗?有办法解决吗?
                      【解决方案20】:

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

                      Short and clear MSDN reference here

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 2014-07-02
                        • 1970-01-01
                        • 2011-03-24
                        • 2013-04-16
                        • 2010-12-13
                        相关资源
                        最近更新 更多