在静态类中使用常量

33

上周末,我在为一个开源项目努力工作时,遇到了一些让我感到困惑的代码,需要在C#规范中查找用法。

有问题的代码如下:

internal static class SomeStaticClass
{
    private const int CommonlyUsedValue = 42;

    internal static string UseCommonlyUsedValue(...)
    {
        // some code
        value = CommonlyUsedValue + ...;

        return value.ToString();
    }
}
我被搞糊涂了,因为这似乎是在静态类中编译完全没有问题地使用非静态字段!规范说明了(§10.4):
常量声明可以包括一组属性(§17),一个new修饰符(§10.3.4)和四个访问修饰符(§10.3.5)的有效组合。这些属性和修饰符适用于常量声明的所有成员。尽管常量被视为静态成员,但常量声明既不需要也不允许使用static修饰符。在常量声明中出现相同的修饰符多次是错误的。
现在有点明白了,因为常量被视为静态成员,但剩下的句子仍然有些令人惊讶。为什么常量声明不需要也不允许使用static修饰符呢?坦率地说,我对规范不够了解,所以一开始并不立即理解这个问题,但为什么要决定在常量被视为静态时不强制使用static修饰符呢?
看一下该段落的最后一句话,我无法确定它是否直接涉及前面的陈述,并且在常量上有某种隐含的static修饰符,还是它本身就是常量的另一条规则。有人能帮我澄清一下吗?

5
可能他们选择不允许使用"static"是因为这会引起混淆:有人看到"static const foo..."可能会错误地认为"static"实际上会产生差异,暗示着你可以将其去除,从而得到一个非静态的const。 - Igby Largeman
当您尝试同时使用staticconst时,编译器会给出非常不错的错误提示信息。我认为这已经足够好了。 - Hamish Grubijan
4个回答

61

更新: 2010年6月10日,我的博客讨论了这个问题。 感谢你提出这个好问题!

如果常量被视为静态的,为什么决定不强制常量使用static修饰符?

假设常量被视为静态的,有三种可能的选择:

  1. 使静态变量可选:“const int x...” 或 “static const int x...” 都是合法的。

  2. 使静态变量必需:“const int x...” 不合法,“static const int x...” 合法。

  3. 使静态变量非法:“const int x...” 合法,“static const int x...” 非法。

你的问题是,为什么我们选择了(3)?

1999年的设计说明没有提到;我刚刚查过。但是我们可以推断出语言设计者的想法。

问题在于,如果选择(1),你可能会阅读使用 “const int x...” 和 “static const int y...” 的代码,然后自然而然地问自己 “有什么区别?” 由于非常量字段和方法的默认值是 “instance”,除非是 “static”,自然的结论是某些常量是每个实例的,而某些常量是每个类型的,这个结论是错误的。这很糟糕,因为它是误导性的。

(2)的问题在于,首先它是多余的。这只是更多的打字,而没有为语言添加清晰度或表达能力。其次,我不知道你怎么看,但我个人讨厌编译器给我报错说“你忘记在这里说魔法词了。我知道你忘记说魔法词了,我完全能理解魔法词需要放在那里,但在你说出魔法词之前,我不会让你做任何事情”。
(3)的问题在于,开发人员需要知道const在逻辑上意味着static。然而,一旦开发人员学习了这个事实,他们就已经学会了。这不像是一个难以理解的复杂想法。
对终端用户来说,呈现最少问题和成本的解决方案是(3)。
将此与语言中其他地方进行比较和对比是有趣的。
例如,重载的运算符必须是公共且静态的。在这种情况下,我们再次面临三个选项:
1. 可选的公共静态 2. 要求公共静态 3. 不合法
对于重载的运算符,我们选择了(2)。由于方法的自然状态是private/instance,因此使看起来像是方法的public/static在看不见的情况下,就像(1)和(3)都需要的那样,似乎是奇怪和误导人的。
另一个例子,与基类中虚方法签名相同的虚方法应该有“new”或“override”。同样,有三种选择。
1. 可选的:你可以说new、override或什么都不说,在这种情况下,我们默认为new。 2. 要求必须有new或override 3. 不合法:你不能说new,因此如果你不说override,那么它就自动变成了new。在这种情况下,我们选择(1),因为它对于某人向基类添加虚方法并且您没有意识到您现在正在覆盖该方法的脆弱基类情况效果最好。这会产生一个警告,而不是错误。 我的观点是,每种情况都必须根据具体情况进行考虑。这里没有太多通用的指导方针。

我喜欢你提到的静态在这种情况下是一个“魔法词”。尽管如此,我认为这还不足以让我倾向于选项1,然而,选项1仍然更加混乱,在这个线程中所有的评论之后,我可以理解选择3的原因。如果编译器确实抛出错误,如果我试图使一个常量静态,那么这种情况的相反情况将使我面临类似的问题。 - Nick Larsen
6
+1表示支持权威和使用"魔法词语"的做法,顺序不限。;) - Adam Robinson
1
@RCIX:我经常享受尤达式的语言。 - Adam Robinson
嗨Eric,你的博客在Stack Exchange上经常被链接 - 你希望我的脚本对于那些不再工作的链接做什么?据我所知,你的是MSDN上唯一一个需要登录的页面;不知道为什么。我可以轻松让脚本忽略它,并自动替换成Wayback Machine的副本,就像你做的那样。 - Glorfindel
@Glorfindel:从长远来看,我正在逐步将我的MSDN博客上的所有内容移动到ericlippert.com,但这是一个缓慢的过程,而且我现在有许多其他紧迫的问题占用了我的空闲时间。稍后我会更新这里的链接,指向ericlippert.com的副本。 - Eric Lippert
显示剩余3条评论

45

基本上,const 已经意味着static,因为这个值在运行时不能被更改。没有必要声明 static const,因为它已经被暗含了,并且编程语言的设计者决定让编程语法反映出这一点。

规范语言基本上是说“Const总是静态的,所以你不能显式地说静态和const因为这是多余的。”


1
如果真是这样,那为什么语言中还允许你在几乎所有地方明确使用 private 关键字呢? - Nick Larsen
1
添加private并不意味着有不同的含义。在这里,如果允许“const变量”和“static const变量”,那么它会暗示存在非静态const选项。Private并不定义变量的存储方式,而更多地是描述其可访问性的修饰符 - 添加或删除它并不真正改变含义,但为类型添加了一个描述性元素。 - Reed Copsey
通过允许添加或省略“private”,表明存在非私有成员选项,而确实存在。 :) - Reed Copsey
10
私有访问修饰符只是类成员的默认修饰符。对于常量而言,static 不是默认选项,它是唯一的选择。 - Adam Robinson
1
@Nick,访问修饰符的缺失在不同的上下文中意义不同。默认值并不总是private。它可能是internal。const从未与实例相关联。那怎么办呢?我想如果您可以使用static const,这将非常令人困惑,因为这将意味着您也可以拥有一个实例常量,但是我猜如果您具有C++背景,则可以将const读作readonly(又称可写一次),然后突然间实例化就有了意义。但是,在C#中,const是常量,而readonly几乎是只读的:p - Rune FS
显示剩余2条评论

10

这不是必须的也不被允许,因为它是多余的。如果所有的 const 成员都是静态的,那么允许一些成员被指定为 static 而另一些成员不被指定,只会引起混淆。


1
@Adam:但是你可以要求使用静态关键字,而不是要求其不存在。 - Kent Boogaart
2
因为它们没有不是静态的方式。为什么需要一个多余的术语呢? - Adam Robinson
2
这是一个很好的观点,但是省略高度描述性的术语是我认为会引起困惑的事情(就像我自己一样 :) )。 - Nick Larsen
readonly和const是不同的,但它不让我发布我的其他评论...不,我不会指定必须按值传递参数,但按值或引用传递参数似乎并没有直接违反任何其他语言规则。 - Nick Larsen
1
@Nick:它并没有违反任何语言规则;你说你的反对意见在于它省略了“描述性”信息,但是按值传递是描述性的,标记常量为只读也是描述性的;它们与将常量标记为 static 一样冗余。如果你知道在 C# 中 const 的含义,那么它怎么可能看起来违反任何语言规则呢? - Adam Robinson
显示剩余3条评论

0

不允许将常量声明为静态的另一个原因是,从CLR的角度来看,常量与类型的其他静态字段一样不存储在内存中。

常量没有内存地址,您无法获取对常量值的引用(唯一的例外是字符串常量)。在运行时,如果未引用其他静态/非静态成员,则不会加载持有常量定义的类型。如果它是程序集中唯一的类型,甚至可以在编译后安全地从磁盘中删除其DLL。

因此,常量仅在“可能从静态方法引用”方面具有“静态”属性。常量没有任何其他静态类型成员所具有的“静态”属性。


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