【问题标题】:Where are the readonly/const in .NET?.NET 中的只读/常量在哪里?
【发布时间】:2011-02-18 08:18:54
【问题描述】:

在 C++ 中,void func(const T& t) 无处不在。但是,我在 .NET 中没有看到任何类似的东西。为什么?

我注意到使用 struct 的参数数量很多。但我看不到带有只读/常量的函数。事实上,现在我试过了,我不能使用这些关键字来创建一个承诺不修改传入列表的函数。有没有办法向调用者承诺这个函数永远不会修改列表的内容?有没有办法说调用代码并说这个列表永远不应该被修改? (我知道我可以克隆列表或查看文档,但有时我喜欢编译错误)

【问题讨论】:

    标签: .net language-design library-design


    【解决方案1】:

    不变性仍然是 C# 正在成熟的一个领域。 到目前为止,C# 还没有采用 C++ 的 const 语义……我实际上认为这是一件好事。 C++ 中const 的行为通常使设计以您想要的方式工作的类层次结构具有挑战性。看到代码中充斥着 const_cast<> 以绕过不希望出现的 constness 的情况并不少见。希望 C# 的设计者能够发明一种更简单但仍然具有表现力的替代方案。

    目前,没有将传递给方法的参数或对象标记为不可变的语言功能。 您可以做的最好的事情是使用接口(或者更好的是包装器)传递一个对象,该接口只允许读操作。

    对于 .NET 中的某些标准集合,您可以使用 ReadOnlyCollection 包装器,它将任何可变的 ICollection 类型封装在一个只读容器中。

    创建不可变类型需要规划和了解语言特性。例如,readonly 关键字是您的朋友。它允许您将类或结构的成员声明为不可变的。不幸的是,这种不变性只适用于引用,而不适用于被引用对象的成员。这意味着您可以声明:

    private readonly int[] m_Values = new int[100];
    
    public void SomeMethod()
    {
        m_Values = new int[50]; // illegal, won't compile!
        m_Values[10] = 42;      // perfectly legal, yet undesirable
    }
    

    在上面的示例中,对数组的引用是不可变的,但数组的各个元素不是。当然,这种行为超越了数组。

    我发现在设计不可变类型时很有用的一种做法是将不可变行为分离到自己的接口中,然后由管理数据的类实现。该接口仅公开保证不会改变对象状态的 get 属性和方法。然后可以将您的类型的实例作为该接口类型的参数传递给方法。 这是一种弱形式的不变性支持——因为被调用的方法通常可以将引用转换为可变类型。一个更好但更麻烦的替代方法是创建一个实现相同接口并维护对实际实例的引用的包装器实现(很像ReadOnlyCollection 所做的)。 这是更多的工作,但为不变性提供了更强的保证。

    您选择使用的方法取决于保证不变性的重要性,以及您愿意花费多少时间和精力来实现这一目标。

    如果您有兴趣阅读有关此主题的更多信息,Eric Lippert 有一个出色的 series of articles on immutability in C#

    【讨论】:

    • 我不同意“使用 const_cast 绕过 constness 的代码并不罕见”。充斥着 const_cast 的 C++ 代码表明代码设计器存在错误,而不是语言。在设计良好的代码中,const_cast 很少使用,作为处理不接受 const 参数的遗留代码的最后手段。 (对于用于缓存的数据成员,可以使用 mutable 关键字)。无论如何为帖子的其余部分 +1。
    • @Andrew Shepherd:我同意你的观点,设计良好的代码不应该经常需要const_cast,但在实践中,使用 constness 进行编码或设计是许多初级开发人员的事情很难把握。这不是您在 CS/CE 课程中经常看到的内容。并且当迫在眉睫时(翻译:完成工作的时间已经不多了),开发人员将依赖任何使他们的代码工作的方法。这很不幸,但并不少见。我更重要的一点是,如果 C# 团队能够提出一个更简单的方案以便更多的开发人员可以接受它,那就太好了。
    • @AndrewShepherd:如果我错了,请纠正我,但我认为在代码可能需要接收和存储供应商保证的 (actor, target) 对的情况下,也可能最终需要 const-casting任何修改其目标的actor 只会与非常量target 实例配对。如果没有演员修改他们的目标,或者如果没有目标是 const,那么就不需要 const-casting,但我认为没有办法创建修改他们的目标的演员只会与非配对的概念-const 对象。现在有没有办法做到这一点?
    【解决方案2】:

    C# 中的方法参数没有const 修饰符。如果您想拥有类似于 const 的保证,您可以在接口中使用不可变类型。例如,方法可以接受 IEnumerable<T> 而不是可变集合类型。也可以看ReadOnlyCollection

    【讨论】:

    • 值得注意的是(以防问题 op 不知道),您可以将列表直接传递给需要 IEnumerable 的方法
    • 谁能告诉我 IEnumerable 和 ReadOnlyCollection 之间的区别是什么?我有一种感觉,唯一的区别是 IEnumerable 可以被类型转换为容器然后编辑(或者可能通过反射?也许不是?)而 ReadOnlyCollection 不能?
    • @acidzombie24:IEnumerable 是集合类实现的接口,允许它们参与可以枚举(迭代)其内容的类型的生态系统。 ReadOnlyCollection 是一个特殊的包装类,它组合(聚合)一个 ICollection 实例并只公开只读操作。不幸的是,ROC 仍然提供像 Add 和 Remove 这样的方法,但是在调用它们时会抛出运行时异常——这使得编译时的不变性实施比它可能的弱。顺便说一句,大部分 LINQ 都是基于 IEnumerable<T> 接口构建的。
    【解决方案3】:

    我注意到使用 struct 的参数数量很多。

    这里值得一提的是,与 C++ 相比,C# 通过夸大值类型和引用类型之间的差异来夸大结构和类之间的差异。在 C# 中,所有类都是引用类型,所有结构都是值类型。在 .Net 中,everything 默认是按值传递的。

    这种区分值类型的结果是所有值类型在传递给函数时都复制。如果将结构传递给函数,则可以保证该函数不会更改原始结构,因为该函数仅使用副本。将 const 添加到这样的参数是愚蠢的。

    引用类型也是按值传递的。或者,更具体地说,引用本身是按值传递的。所以你有一个引用的副本,但它指向(引用)同一个对象。因此,函数对对象所做的更改将在函数退出后持续存在。

    从表面上看,添加const 选项(至少对于引用类型)似乎是个好主意。它变得有点模糊的地方是副作用。或者,更确切地说,这是如何执行的?显然,很容易说您的代码不能使用属性设置器。但是其他方法呢?任何方法调用都可能需要更改对象。甚至属性 getters 也可能具有改变某些内容的副作用(例如,您实现了一个安全功能来记录上次访问某些内容的时间)。您是否会因为副作用而拒绝 read 访问某个属性?

    【讨论】:

    • 嗯,C++ 有一个可变关键字stackoverflow.com/questions/105014/c-mutable-keyword。我同意如果添加 const 会变得复杂,但我个人不需要使用可变的。所以复杂性应该很少见。
    • 如果您可以将方法和属性 getter 声明为 const,则参数上的 const 选项将起作用。如果将对象作为 const 参数传递,则只能调用 const 方法。
    【解决方案4】:

    问题在于 C/C++ 中使用的 const 正确性仅由编译器强制执行。顺便说一句,很容易绕过。 CLR 实际上在托管代码中强制执行类型安全,而不是在编译器中。之所以如此,是因为 .NET 框架支持多种语言。类型系统和交互规则在 CLI 中进行了布局,这是一种需要服务于许多目标和大师的通用语言基础架构。

    在当前语言中,常量正确性很少见。就我个人而言,我只知道 C++,一种没有移植到 CLI 的语言。对语法相去甚远的语言强制执行规则是困难的。像没有在 CLS 中的无符号整数类型这样简单的东西在基础程序集的设计上留下了显着的印记。

    不变性(真实的,不是用编译器伪造的)让 MSFT 团队不断思考。它很流行,我知道 C# 团队一直在考虑它。适合并发等。 Eric Lippert 在上面做了一个11 part blog post series,不过有点逐渐消失。对于一个人和他的观众来说太大了。如果有真正的行动来实现它,我相信他们会仔细考虑并使其发挥作用。某天。一些语言。

    【讨论】:

      【解决方案5】:

      据我所知,这是不可能的。或许这篇文章能给你一些想法:

      http://www.c-sharpcorner.com/UploadFile/bulentozkir/PassingConstInCS11082005014622AM/PassingConstInCS.aspx

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-12
      • 2011-02-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多