我已经了解了常量和静态只读字段。我们有一些类只包含常量值。它们用于我们系统中的各种事情。所以我想知道我的观察是否正确:

对于所有公开的内容,这些常量值是否总是静态只读的?并且只对内部/受保护/私有值使用const?

你有什么建议?我甚至应该不使用静态只读字段,而应该使用财产吗?


当前回答

声明常量和静态只读之间的另一个区别在于内存分配。

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

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

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

其他回答

如果Consumer位于不同的程序集中,我将使用静态只读。将const和Consumer放在两个不同的集合中是一个很好的方法来击中自己的脚。

const和readonly相似,但并不完全相同。

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

例如,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的客户破坏它,可能是通过交换黑白值。不用说,这会给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成员总是静态的,而只读成员可以是静态的,也可以不是静态的,就像常规字段一样。

可以将一个关键字用于这两个目的,但这会导致版本控制问题或性能问题。假设我们对这个(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的开发人员指定意图。这有助于更好的版本控制行为和更好的性能。

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

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

public const decimal ProgramVersion = 2.3;

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

另一种看待这一点的方式是根据定义,未来的变化不是一成不变的,因此应该不能表示为一个。

施工

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

静态只读

静态只读应用于字段/道具,静态可用于方法。(附带说明)当静态应用于方法时,编译的代码不会将“this”参数传递给方法,因此您无法访问对象的实例数据。适用于编译代码后可能更改的值。类似于在应用程序启动等过程中从配置初始化的值。编译代码后,在IL代码中使用ref to值,与使用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有两个重载,一个接受短(System.Int16),另一个接受对象(System.object)。现在的问题是,是一个还是两个都适用于y参数。

当y是编译时常数(文字)(常量)时,如果int是常量,并且C#编译器验证其值是否在short(42是)的范围内,那么从int到short的隐式转换就变得很重要了。请参阅C#语言规范中的隐式常量表达式转换。因此,必须考虑这两种过载。首选重载Equals(short)(任何short都是一个对象,但不是所有对象都是短的)。所以y被转换为short,并使用过载。然后Equals比较两个短的相同值,结果为真。

当y不是常数时,不存在从int到short的隐式转换。这是因为一般来说,int可能太大,无法放入short。(确实存在显式转换,但我没有说Equals((短)y),所以这不相关。)我们看到只有一个重载适用,Equals(对象)重载。所以y被装箱为对象。然后Equals将比较System.Int16和System.Int32,因为运行时类型甚至不一致,这将产生false。

我们得出的结论是,在某些(罕见的)情况下,将常量类型成员更改为静态只读字段(或在可能的情况下,以其他方式)可以更改程序的行为。