为什么C#不支持在类/方法级别上使用const?

28

我一直在想为什么C#不支持在类或方法级别上使用const。我知道Jon Skeet长期以来一直希望支持不可变性,并且我认为使用C++函数const语法可能会有所帮助。通过在类级别添加const关键字,我们将拥有完全的支持。

现在,我的问题是,C#团队没有开发此类支持的原因是什么?

我想通过编译时检查或使用属性创建所有内容,而无需更改CLR即可完成。我不介意代码能够通过反射覆盖const行为。

想象一下:

const class NumberContainer
{
    public int Number { get; }
}

这样的类只能在构造时进行填充,因此我们需要一个构造函数来接收一个整数参数。

另一个例子是方法级别的 const:

public int AddNumbers(NumberContainer n1, NumberContainer n2) const
{
   return n1.Number + n2.Number;
}

在其自身类或传递给它们的引用类型实例中,常量级别方法不应该能够更改状态。此外,在其作用域内,常量级别函数只能调用其他常量级别函数。

我不确定Lambda表达式和委托是否会使一切变得太难(或不可能)实现,但我相信有更多语言和编译器设计经验的人可以告诉我。

正如Steve B在评论中指出的那样,readonly的存在使事情变得更加复杂,因为在runtime期间,const和readonly非常接近,但是readonly值无法在编译时确定。我想我们可以有constreadonly级别,但这可能会太令人困惑了吧?

那么,为什么不实现这个功能呢?是可用性问题(对于新用户来说,理解C++中的常量性通常很困难),语言设计问题(无法完成)还是简单地优先考虑问题(不可变性热潮的日子已经过去了)..?


8
你的问题基本上是在问“为什么这种语言没有 X 特性” - 简单来说:要么该特性还未被提出、考虑、批准、范围确定、设计、实现、测试、文档化、部署(并且需要权衡该资源的其他可能用途和语言膨胀的影响),要么有些已经被考虑过而被认为不可取或推迟。并不是每一种编程语言都会自动拥有所有可能的特性。 - Marc Gravell
2
@Adam确实,“因为这不是一个好的时间利用方式”是一个合理的第一假设。Eric Lippert在这里添加了注释 - Marc Gravell
@MarcGravell 我今天早上还没有喝足够的茶来理解Eric Lippert的博客文章...哈哈 - Adam Houldsworth
1
我知道实现这样的功能是很昂贵的,我也知道其他的特性可能优先级更高。我只是想了解实现这样一个功能的后果,以及为什么它会如此昂贵,而且(也许)不够重要。 - cwap
我明显不理解C++中的const - Jodrell
显示剩余4条评论
6个回答

29

冒着有些循环的解释风险,C#不支持const是因为CLR根本不支持。 CLR不支持它是因为它与CLS标准相差太大。

只有很少数语言有这个概念。C语言支持const,在C#中可以用readonly关键字很好地支持。但是当然最重要的是C++,它对const有更广泛的适用性,毫无疑问这也是你寻找的那个。我不会明确指出const应该意味着什么,那本身就是一个棘手的问题,只谈“const性”,即应用const的属性。

const-ness的麻烦在于需要执行它。在C#中,当任意其他语言可以使用C#类而完全忽略const-ness时,这是一个问题,因为该语言不支持它。因此,因为C#支持它,将其添加到每种其他CLS语言中显然是非常不切实际的。

在C++中,执行性也是一个问题。因为语言还支持const_cast<>。任何客户端代码都可以迅速无法诊断地删除const性。您不应该这样做,但是有时您必须这样做。因为有两种const-ness,严格的和可观察的。与私有const-ness和公共const-ness大致类似。随着时间的推移,语言中添加了mutable关键字,以尝试处理对可观察const-ness的需求,因此至少可以避免不可避免地使用const_cast<>。有些人说C++是一种困难的语言。C#很少听到那种声音。


最佳答案!非常感谢,讲得很清楚明白! - cwap

3

您说CLR不需要更改,但请考虑,在编译的程序集中没有标准的表达“const”性质的方法,并且这些程序集可能根本不会被C#代码使用。这不是您可以仅为C#做的事情,您必须为所有.NET语言都做出相应的更改。


2

我认为情况是这样的,在C#中,const与C++中不同。

在C#中,您可以使用readonly关键字来获得与const相同的功能。


1

从你的问题中我无法确定,如何使用const关键字的重载会特别有益。

你的第一个例子可以合法地重写为

public class NumberContainer
{
    private readonly int number;

    public NumberContainer(int number)
    {
        this.number = number;
    }

    public int Number
    {
        get { return number; }
    }
}

也许,如果编译器无法确定这个类的不可变性(我不知道),那么一些属性可能会有用?

在您的第二个示例中,我不明白您的意图。如果一个函数返回一个常量值,那么它可以被替换为一个常量字段。


1
在您提出const-class的建议中,您说:
“这样的类只能在构造时填充,因此我们需要一个构造函数来接收一个int参数。”
但是,通过使所有属性都为只读,您已经实现了您所说的内容。
我不能代表C#语言设计者发言,但也许不将const应用于许多其他结构的原因是因为添加它根本不值得花费,而您可以通过其他方式解决该问题(如上面和其他答案/注释中所描述)。

0

我曾经对以下情况感到惊讶:

class Vector
{
    private double[] m_data;
    public int Dimension {get;set;}

    public double this[int i]
    {
        get {return m_data[i];}
        set {m_data[i] = value;}
    }

    public Vector(int n)
    {
        this.Dimension = n;
        this.m_data = new double(n);
    }

    public static Vector Zero(int n)
    {
        Vector v = new Vector(n);
        for (int i = 0; i < n; i++)
        {
            v[i] = 0.0;
        }

        return v;
     }

    public static readonly Vector Zero3 = Zero(3);
}

尽管Vector.Zero3是只读的,你不能对其进行赋值,但仍然可以访问它的组件,接着会发生以下愚蠢的事情:

Vector a = Vector.Zero3;
a[0]     = 2.87;

现在,由于a只是对Vector.Vector3的引用,因此后者也具有Vector.Vector3 [0] == 2.87!

我曾经掉进这个陷阱,后来我发明了一个非常简单的技巧,虽然不够优雅,但它可以实现其功能。

也就是说,在我假设生成静态只读“常量”的类中,我引入了一个布尔标志:

class Vector
{
    private double[] m_data;
    public int Dimension {get;set;}
    private bool m_bIsConstant = false;
    ...

    public double this[int i]
    {
        get {return m_data[i];}
        set 
        {
           if (!m_bIsConstant)
           {
                m_data[i] = value;
           }
        }
    }
    ...
    public static Vector Zero(int n)
    {
        Vector v = new Vector(n);
        for (int i = 0; i < n; i++)
        {
            v[i] = 0.0;
        }

        v.m_bIsConstant = true;
        return v;
     }
     ...
}

这个技巧可以确保你的静态只读变量永远不会被修改。

糟糕的建议。你不应该在索引器中使用setter,而是应该提取一个IReadOnlyVector接口。 - undefined

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