c# - 不变性/只读语义(特别是 C# IReadOnlyCollection<T>)

标签 c# immutability semantics readonly readonly-collection

我怀疑我对 System.Collection.Generic.IReadOnlyCollection<T> 的理解语义并怀疑如何使用只读和不可变等概念进行设计。让我用 documentation 来描述我怀疑的两种性质。 ,其中指出

Represents a strongly-typed, read-only collection of elements.



取决于我是强调“代表”还是“只读”(在我的脑海中发音,或者如果这是你的风格),我觉得这句话的意思会发生变化:
  • 当我强调“只读”时,文档在我看来定义了观察不变性(在 Eric Lippert's article 中使用的术语),这意味着只要没有公开可见的突变,接口(interface)的实现就可以随心所欲地执行†。
  • 当我强调“代表”时,文档定义了(在我看来,再次)一个不可变的外观(在 Eric Lippert 的文章中再次描述),这是一种较弱的形式,其中可能发生突变,但用户无法进行。例如,类型为 IReadOnlyCollection<T> 的属性向用户(即针对声明类型进行编码的人)明确表示他不能修改此集合。但是,声明类型本身是否可以修改集合是不明确的。
  • 为了完整起见:接口(interface)不携带任何语义,除了由其成员的签名携带的 。在这种情况下,观察或外观不变性是依赖于实现的(不仅依赖于接口(interface)的实现,还依赖于实例)。

  • 第一个选项实际上是我的首选解释,尽管这个契约(Contract)很容易被打破,例如通过构建 ReadOnlyCollection<T>来自 T 的数组's 然后在包装器数组中设置一个值。

    BCL 具有出色的外观不变性接口(interface),例如 IReadOnlyCollection<T> , IReadOnlyList<T>甚至 IEnumerable<T>等。但是,我发现观察不变性也很有用,据我所知,BCL 中没有任何接口(interface)具有这种含义(如果我错了,请指出它们)。这些不存在是有道理的,因为这种形式的不变性不能由接口(interface)声明强制执行,只能由实现者强制执行(尽管接口(interface)可以携带语义,我将在下面展示)。旁白:我很想在 future 的 C# 版本中拥有这种能力!

    示例: (可能会被跳过)我经常必须实现一个方法,该方法将另一个线程也使用的集合作为参数,但是该方法要求在执行过程中不要修改集合,因此我将参数声明为类型IReadOnlyCollection<T>并轻拍自己的后背,认为我已满足要求。错误......对于调用者来说,签名看起来好像该方法 promise 不会更改集合,没有别的,如果调用者对文档(外观)进行第二次解释,他可能只是认为允许突变并且方法在问题对此有抵抗力。尽管此示例还有其他更传统的解决方案,但我希望您看到这个问题可能是一个实际问题,尤其是当其他人使用您的代码时(或 future 的您)。

    所以现在我的实际问题(这引发了对现有接口(interface)语义的怀疑):

    我想使用观察不变性和外观不变性并区分它们。我想到的两个选项是:
  • 每次使用 BCL 接口(interface)和文档,无论是观察性还是外观不变性。缺点:使用此类代码的用户只会在为时已晚,即发现错误时查阅文档。我要带领他们走向成功的深渊;文档无法做到这一点)。此外,我发现这种语义非常重要,可以在类型系统中看到,而不仅仅是在文档中。
  • 定义明确携带观察不变性语义的接口(interface),如 IImmutableCollection<T> : IReadOnlyCollection<T> { }IImmutableList<T> : IReadOnlyList<T> { } .请注意,接口(interface)除了继承的成员之外没有任何成员。这些接口(interface)的目的只是说“即使是声明类型也不会改变我!”‡ 我在这里特别说“不会”而不是“不能”。这里有一个缺点:一个邪恶的(或错误的,为了保持礼貌)实现者不能被编译器或任何真正的东西阻止破坏这个契约。然而,优点是选择实现这个接口(interface)而不是它直接继承的接口(interface)的程序员最有可能知道这个接口(interface)发送的额外消息,因为程序员知道这个接口(interface)的存在,因此可能会相应地实现它。

  • 我正在考虑采用第二个选项,但恐怕它的设计问题与委托(delegate)类型的设计问题相当(它们被发明以在无语义对应物上携带语义信息 FuncAction )并且不知何故失败了,参见例如here .

    我想知道您是否也遇到过/讨论过这个问题,或者我是否只是在争论语义太多而应该接受现有的接口(interface),以及我是否只是不知道 BCL 中的现有解决方案。任何像上面提到的设计问题都会有所帮助。但是我对您可能(已经)为我的问题提出的其他解决方案特别感兴趣(简而言之,在声明和使用中区分观察性和外观不变性)。
    先感谢您。

    † 我忽略了集合元素上的字段等的突变。
    ‡ 这适用于我之前给出的示例,但该陈述实际上更广泛。例如,任何声明的方法都不会改变它,或者这种类型的参数传达了该方法可以期望集合在其执行过程中不会改变(这不同于说该方法不能改变集合,这是唯一的可以使用现有接口(interface)做出的声明),可能还有许多其他接口(interface)。

    最佳答案

    接口(interface)不能确保不变性。名称中的一个词不会阻止可变性,这只是另一个提示,如文档。

    如果你想要一个不可变的对象,需要一个不可变的具体类型。在 c# 中,不变性依赖于实现并且在接口(interface)中不可见。

    正如克里斯所说,你可以找到 existing implementations of immutable collections .

    关于c# - 不变性/只读语义(特别是 C# IReadOnlyCollection<T>),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17843915/

    相关文章:

    java - 是创建一个类的一个实例更好,还是为每次出现创建一个单独的实例更好?

    java - 为什么java允许这样做?

    variable-assignment - Nim 中 `let` 赋值的语义

    c# - 从任何文件夹中删除所有文件

    c# - 即使正确发送了 SOAP 请求,Axis2 也总是收到空参数?

    c# - MS visual Studio Forms 应用程序 : Can't get form to have less width then 100 Pixels

    javascript - 深度卡住和真正的不变性有什么区别

    java - 可变类还是不可变类?

    java - 使用反射修改字符串的目的是什么?

    c# - 使用 mvc 身份注册电话号码而不是电子邮件