为什么在C#中不允许使用const参数?


102

尤其对于C ++开发人员而言,这看起来很奇怪。在C ++中,我们通常将参数标记为const,以确保其状态不会在方法中更改。还有其他C ++特定的原因,例如const ref为了通过ref 传递而传递,并确保不会更改状态。但是为什么我们不能在C#中将其标记为方法参数const?

为什么我不能像下面这样声明我的方法?

    ....
    static void TestMethod1(const MyClass val)
    {}
    ....
    static void TestMethod2(const int val)
    {}
    ....

在C ++中,它始终只是安全捕获功能,而从没有像我看到的那样真正集成到该语言中。C#开发人员认为并没有明显增加价值。
Noldorin

3
关于这个问题的更大讨论:“ C#中的常量正确性”:stackoverflow.com/questions/114149/const-correctness-in-c
tenpn 2010年

值类型有[out / ref],但没有引用类型。这是一个有趣的问题。
肯尼2010年

4
这个问题如何同时具有C#和与语言无关的标记?
CJ7 '08年

1
不具副作用的不可变数据和功能更易于推论。您可能会喜欢F#fsharpforfunandprofit.com/posts/is
上校恐慌

Answers:


59

除了其他好的答案之外,我还要添加另一个理由,为什么不将C样式的常量性放入C#中。你说:

我们将参数标记为const,以确保其状态不会在方法中更改。

如果const实际上做到了,那就太好了。const不会那样做。const是骗人的!

const不提供我可以实际使用的任何保证。假设您有一个采用const的方法。有两个代码作者:编写调用方的人和编写被调用的人。被调用者的作者已使该方法采用const。两位作者可以假定对象是不变的吗?

没有。被调用者可以随意抛弃const并对其进行突变,因此调用者无法保证调用采用const的方法实际上不会对其进行突变。同样,被调用方不能假定对象的内容在被调用方的整个操作过程中都不会发生变化。被调用者可以在const对象的非const别名上调用某些更改方法,现在所谓的const对象已更改

C样式const无法保证对象不会更改,因此会损坏。现在,C已经有了一个弱类型系统,如果您确实愿意,可以在其中将双精度型转换为int类型,因此对于const也具有弱类型系统也就不足为奇了。但是C#被设计为具有良好的类型系统,即当您说“此变量包含字符串”时,该类型系统实际上包含对字符串的引用(或null)。我们绝对不想将C样式的“ const”修饰符放入类型系统中,因为我们不希望类型系统成为谎言。我们希望类型系统强大,以便您可以正确地推理代码。

C中的const是一个准则 ; 它的基本意思是“您可以相信我不要试图改变这个东西”。那不应该在类型系统中 ; 在东西类型的系统应该是一个事实有关的对象,您可以推理,而不是一个指导其用途。

现在,不要误会我的意思;仅仅因为C中的const被深深地破坏并不意味着整个概念都没有用。我希望看到的是C#中“ const”注释的一种实际上正确有用的形式,该注释可供人类和编译器使用,以帮助他们理解代码,并且运行时可用于执行自动并行化和其他高级优化。

例如,假设您可以围绕一大堆代码“画一个盒子”,然后说“我保证这一大堆代码不会对该类的任何字段进行任何更改”,这种方式可以由编译器检查。或画一个框,上面写着“此方法会改变对象的内部状态,但不能在框外观察到任何形式”。这样的对象不能安全地自动多线程化,但是可以被自动记录。我们可以在代码上添加各种有趣的注释,以实现丰富的优化和更深入的理解。我们可以比弱C风格的const注释做得更好。

但是,我强调这只是猜测。我们甚至没有确定的计划将这种功能集成到C#的任何假设的未来版本中,即使其中没​​有,我们也没有宣布一种或另一种方式。我很乐意看到这一点,将来可能需要强调多核计算,但这绝不能以任何方式解释为对C#任何特定功能或未来方向的预测或保证。

现在,如果您想要的只是局部变量上的注释,该注释是一个参数,指出“此参数的值在整个方法中都不会改变”,那么可以肯定,这很容易做到。我们可以支持将被初始化一次的“只读”局部变量和参数,以及在方法中更改的编译时错误。“ using”语句声明的变量已经是这样的局部变量了。我们可以向所有局部变量和参数添加可选注释,以使它们的行为像“使用”变量一样。它从来不是一个非常高优先级的功能,因此从未实现过。


34
抱歉,但是我发现该论点非常微弱,无法用任何语言阻止开发人员说谎言。修改对象a的void foo(const A&a)与返回梨数的int countApples(Basket b)没什么不同。这只是一个bug,可以用任何语言编译的bug。
亚历山德罗·特鲁齐

55
“被调用者可以随意删除const……” uhhhhh…(°_°)这是您在此处提出的一个很浅的论点。被叫方还可以随意使用随意开始写入随机存储位置0xDEADBEEF。但是两者都将是非常糟糕的编程,并且会被任何现代编译器提早和令人发指地捕获。 将来在编写答案时,请不要将语言的后门与实际实际代码中可行的方法混为一谈。这样的歪斜信息会使经验不足的读者感到困惑,并降低SO上信息的质量。
Slipp D. Thompson

8
“ C语言中的常量是一个准则;” 引用您所说的某些内容的消息来源确实可以帮助您解决这里的问题。在我的教育和行业经验中,引用的语句是hogwash-C中的const与类型系统本身一样是强制性内置功能-两者都有必要时作弊的后门(就像C中的所有内容一样),但是应该期望该书将以99.99%的代码使用率。
Slipp D. Thompson

29
这个答案就是为什么我有时讨厌C#设计的原因。语言设计师绝对确保他们比其他开发人员聪明得多,并尝试过分保护他们免受错误的侵害。显然,const关键字仅在编译时有效,因为在C ++中,我们可以直接访问内存,并且可以做我们想做的任何事情,并且有很多方法可以获取const声明。但是在正常情况下没有人这样做。顺便说一下,C#中的“私有”和“受保护”也不能提供保证,因为我们可以使用反射来访问这些方法或字段。
BlackOverlord

13
@BlackOverlord:(1)设计一种语言,其特性自然会导致开发人员成功而不是失败的愿望,是出于对构建有用工具的渴望,而不是因为您猜想是相信设计师比他们的客户更聪明。(2)反射不是C#语言的一部分;它是运行时的一部分;运行时的安全系统限制了对反射的访问。低信任度代码未获得进行私有反射的能力。访问修饰符提供的保证适用于低信任度代码;高信任度代码可以做任何事情。
埃里克·利珀特

24

C#中没有const正确性的原因之一是因为它在运行时级别不存在。请记住,除非C#1.0是运行时的一部分,否则它没有任何功能。

而CLR没有const正确性概念的几个原因例如:

  1. 它使运行时间复杂化;此外,JVM也没有它,CLR基本上是作为一个项目创建的,以创建类似于JVM的运行时,而不是类似于C ++的运行时。
  2. 如果在运行时级别具有const正确性,则BCL中应具有const正确性,否则就.NET Framework而言,该功能几乎没有意义。
  3. 但是,如果BCL需要const正确性,在CLR之上的每一种语言应该支持const正确性(VB,JavaScript中,Python和Ruby,F#等)的发生的。

常量正确性几乎是仅C ++中提供的一种语言功能。因此,它可以归结为与为什么CLR不需要检查的异常(这是仅Java语言的功能)相同的论点。

另外,我认为您不能在不破坏向后兼容性的情况下在托管环境中引入这样的基本类型系统功能。因此,不要指望曾经进入C#世界的const正确性。


您确定JVM没有它吗?据我所知。您可以在Java中尝试使用公共静态void TestMethod(final int val)。
隐身于2010年

2
最终不是真正的const。您仍然可以更改参考IIRC上的字段,而不能更改值/参考本身。(无论如何都是通过值传递的,因此,这是防止您更改参数值的编译器技巧。)
Ruben 2010年

1
您的第三个项目符号只是部分正确。具有BCL调用的const参数不会造成重大变化,因为它们只是更精确地描述了合同。与“此方法要求您传递一个const值”相反,“此方法将不会更改对象的状态”
Rune FS 2010年

2
它是方法内部强制执行的,因此将其引入BCL并不是一个重大更改。
符文FS 2010年

1
即使运行时无法强制执行,拥有一个指定是否应允许/期望函数写入ref参数的属性也将很有帮助(特别是在struct方法/属性的情况下this)。如果方法特别声明不会写入ref参数,则编译器应允许传递只读值。相反,如果某个方法声明将要写入this,则编译器不应允许对只读值调用此方法。
2013年

12

我相信有两个原因C#不是const正确的。

首先是可理解性。很少有C ++程序员了解const正确性。的简单示例const int arg很可爱,但我也看到了char * const * const arg -指向非常量字符的常量指针的常量指针。函数指针的常量正确性是混淆的全新水平。

第二个原因是因为类参数是按值传递的引用。这意味着已经有两个级别的constness需要处理,而语法没有明显的清晰。一个类似的绊脚石是集合(以及集合的集合等)。

常量正确性是C ++类型系统的重要组成部分。从理论上讲,它可以作为仅在编译时检查的东西添加到C#中(不需要将其添加到CLR中,除非包含const成员方法的概念,否则它不会影响BCL) 。

但是,我认为这不太可能:第二个原因(语法)将很难解决,这将使第一个原因(可理解性)成为更大的问题。


1
没错,CLR不必担心这一点,因为它可以使用modoptmodreq在元数据级别上存储该信息(就像C + / CLI已经做到的那样-请参见System.Runtime.CompilerServices.IsConst)。并且这也可以简单地扩展到const方法。
帕维尔·米纳夫

这是值得的,但需要以类型安全的方式进行。您提到的Deep / Shallow const会打开一整蠕虫。
user1496062 2014年

在实际上操纵引用的c ++中,我可以看到您关于具有两种类型的const的论点。但是,在C#中(必须经过后门才能使用“指针”)对我来说似乎很清楚,引用类型(即类)的含义明确,而值的含义不同类型(即基元,结构)
同化

2
不能理解常数的程序员不应使用C ++进行编程。
史蒂夫·怀特

5

const在C#中表示“编译时常量”,而不是在C ++中表示“只读,但可能被其他代码可变”。const在C#中对C ++的大致模拟是readonly,但该模拟仅适用于字段。除此之外,C#中根本没有类似于C ++的const正确性概念。

很难确定其基本原理,因为有很多潜在的原因,但是我的猜测是希望首先保持语言的简单性,而实现此语言的优势不确定。


因此,您认为MS认为“噪声”在方法中具有const参数。
隐身于2010年

1
我不确定“噪音”是什么-我宁可称其为“高成本效益比”。但是,当然,您需要C#团队的成员来肯定地告诉您。
帕维尔·米纳夫

因此,正如我理解的那样,您的反对意见是,为了简化起见,C#中没有保留constness。考虑一下。
史蒂夫·怀特

2

自C#7.2起(2017年12月提供Visual Studio 2017 15.5),这是可能的。

适用于结构和基本类型,不适用于班级成员。

您必须使用in将该参数作为参考输入发送。看到:

请参阅:https : //docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/in-parameter-modifier

例如:

....
static void TestMethod1(in MyStruct val)
{
    val = new MyStruct;  // Error CS8331 Cannot assign to variable 'in MyStruct' because it is a readonly variable
    val.member = 0;  // Error CS8332 Cannot assign to a member of variable 'MyStruct' because it is a readonly variable
}
....
static void TestMethod2(in int val)
{
    val = 0;  // Error CS8331 Cannot assign to variable 'in int' because it is a readonly variable
}
....
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.