如何在C#中实现公开访问的常量最佳?

9

在C#中,似乎有三种实现公共常量的选择。我很好奇是否有任何好的理由选择其中一种,还是只是个人喜好的问题。

选择1 - 私有字段加属性获取器

private const string _someConstant = "string that will never change";

public string SomeConstant
{
    get { return _someConstant; }
}

选项2 - 只有属性getter

public string SomeConstant
{
    get { return "string that will never change"; }
}

选择 3 - 仅公共字段
public const string SomeConstant = "string that will never change";

你推荐哪个选项?为什么?


更新

显然,这已经变成了一场讨论,是使用const还是static readonly。虽然这不是我想要的,但它确实教会了我Choice 3绝对是个坏主意,因为如果在将来版本中更改了const的值,则需要重新编译所有引用程序集。

然而,我认为没有人真正讨论过Choice 2。我仍然很好奇只有返回一个值而没有其他内容的getter是否有任何缺点。


如果您正在使用Visual Studio,它有一个现有的机制可以自动生成属性:资源文件。它基本上会自动执行选项1。另外请注意,如果您想支持多种语言,也有特殊的机制。其中第一种是在卫星程序集中定义常量。 - Merlyn Morgan-Graham
6个回答

11

选项1和2是等价的。

在我看来,实际上有三种不同的情况:

  • 你确定这个字符串永远不会改变。在这种情况下,将其设置为const是合理的。(例如,Math.PI是const。那不会很快改变。)使用它比static readonly具有一些微妙的内存影响,但这很少会影响到您。如果该值可能会更改,并且您不想在该情况下重新编译所有调用者,请勿这样做,原因详见其他地方所述。请注意,在许多项目(特别是企业内部项目)中,重新编译所有调用者确实不是问题。

  • 你认为该字符串可能会在未来更改,但你知道它在任何一个版本中始终是一个常量。在这种情况下,public static readonly字段是可以的。请记住,对于字符串是不可变的,所以使用这个方法没有问题,但是不能对任何可变类型如数组这样做。(要么暴露不可变集合,要么使用属性并每次返回一个新副本。)

  • 你认为该字符串可能会更改,甚至可能会在程序的生命周期内更改...例如,“当前日期,格式化”。在这种情况下,请使用一个具有只读 get 属性的 public static 修饰符。请注意,从只读字段更改为只读属性是一种源代码兼容的更改,但不是二进制兼容的更改-因此,如果您选择了第二个选项,但后来需要更改为第三个选项,则需要重新编译所有内容。


嗯,我刚试了你的第三个选项,但是我得到了“修饰符readonly对此项无效。有问题的代码是:public static readonly string SomeConstant { get { return "SomeValue" } }。你是不是想说public static就可以了? - devuxer
回想起来,我认为你只是指“readonly”在“仅具有getter而没有setter”的意义上,而不是字面上尝试标记属性为“readonly” :) - devuxer

5

请考虑

public static readonly string myVar = "something";

原因:当您暴露(然后在其他地方使用)一个const时,该const被嵌入到消费类型的元数据中。 public static readonly不是这样的,因为它是static readonly,所以只需要实例化一次,并且像const一样是不可变的。

常量未嵌入元数据。问题在于常量的字面值嵌入了 IL 中。 - Hans Passant
这是一个分离编译的问题。如果您更改常量的定义,则需要重新编译任何依赖程序集(而不仅仅是重新加载/重新JIT)。 - Doug McClean

2
正确的选择是第四个:
 public static readonly string SomeConstant = "string that might change in a new version";

使用只读字段而不是公共常量非常重要。常量的字面值编译到IL中。如果您更改const值并重新编译使用它的程序集之一,那么您现在将具有与使用const的其他程序集不匹配的情况。那些其他程序集仍将以旧的const值运行。这很难诊断。
使用只读字段,这种情况不会发生,其他程序集始终会读取更新后的值。如果您使用const,请确保始终将它们设置为私有。

有没有什么理由我不能/不应该在新版本中更改const的值?我以前肯定做过这件事,而且没有注意到任何负面后果。 - devuxer
1
是的,有。常量的字面值嵌入在IL中。当您仅重新编译一个库(例如进行错误修复)时,您将遇到大麻烦。使用常量的其他程序集仍将以旧值运行。为确保永远不会发生这种情况,应始终声明常量为私有。 - Hans Passant
那么,汉斯,为什么不选择第一种方案呢?const 被声明为私有的。而为什么 static 必要呢?也许我想通过类的实例访问 const 值以允许多态性。 - devuxer
Hans,如果输入五行代码是个问题,那第二个选择有什么问题吗? - devuxer
我认为没有人会将虚拟属性视为“多态常量”。称其为属性即可。这种模式很常见。 - Hans Passant
显示剩余3条评论

2

const成员是类成员,而不是实例成员(换句话说,const意味着static)。


1

如果我可以投票支持,我会支持Jon Skeet的答案。另外,就像在IL中展示的那样,您的#1和#2完全相同:

.method public hidebysig specialname instance string 
    get_SomeConstant() cil managed
{
  // Code size       11 (0xb)
  .maxstack  1
  .locals init ([0] string CS$1$0000)
  IL_0000:  nop
  IL_0001:  ldstr      "string that will never change"
  IL_0006:  stloc.0
  IL_0007:  br.s       IL_0009
  IL_0009:  ldloc.0
  IL_000a:  ret
} // end of method Class1::get_SomeConstant

选项 #2:

.method public hidebysig specialname instance string 
    get_SomeConstant() cil managed
{
  // Code size       11 (0xb)
  .maxstack  1
  .locals init ([0] string CS$1$0000)
  IL_0000:  nop
  IL_0001:  ldstr      "string that will never change"
  IL_0006:  stloc.0
  IL_0007:  br.s       IL_0009
  IL_0009:  ldloc.0
  IL_000a:  ret
} // end of method Class2::get_SomeConstant

现在看看选项#3。 3号与#1和#2非常不同。 这是因为,如先前所述,#3是静态的,因为const是静态的。 现在真正的问题是要将苹果与苹果进行比较,即如果#1和#2是静态访问器怎么办? 那么它们将更可比于#3。 目前,您必须初始化选项1和2的类,但不需要初始化#3。 因此,在这种情况下存在不必要的对象初始化,并且您始终希望尽可能使用静态,以避免出现这种情况。

现在让我们看看IL中的第3个数字:

.field public static literal string SomeConstant = "string that will never change"

因此,为了效率,我会使用#3。这也是多年来许多才华横溢的同行教给我的。

现在,解决一个显而易见的问题。Readonly和const不同之处在于const在编译时出现,而readonly在运行时出现。静态readonly只初始化一次,而非静态readonly每个实例初始化一次。例如,如果您要创建一个包含永远不会更改的const字符串的类用于错误消息,则应使用选项#3,而不是readonly static或其他选项。想象一下在运行时初始化数百条错误消息,而不是在编译时,您将看到明显的性能差异。此外,由于您明确指出它是“永远不会更改”的字符串,在这种情况下甚至不应考虑readonly,因为“……它永远不会更改”。Const和ReadOnly有其位置,但对于从不更改且在编译时已知的项目,不应使用readonly。


1
一个属性似乎是更好的选择,因为返回的字符串不会嵌入元数据中。const 真正意义上是用于内部使用(例如,版本号可以被制作成 const,并且可能会更改)或对于绝对不会更改的值,就像引用它的代码一样。

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