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

JBS*_*rro 12 c# readonly immutability readonly-collection semantics

我怀疑我对System.Collection.Generic.IReadOnlyCollection<T>语义的理解,并怀疑如何使用只读和不可变等概念进行设计.让我通过描述两个性质,我怀疑之间的文件,其中规定

表示强类型的只读元素集合.

取决于我是否强调"代表"或"只读"(当我在脑海中发音,或者如果这是你的风格时大声说话),我觉得这句话改变了意思:

  1. 当我强调'只读'时,文档在我看来定义了观察不变性(Eric Lippert的文章中使用的术语),这意味着只要没有公开可见的突变,界面的实现就可以随心所欲地进行.
  2. 当我强调'代表'时,文档定义(在我看来,再次)一个不可变的外观(再次在Eric Lippert的文章中描述),这是一种较弱的形式,其中突变可能是可能的,但用户不能做出.例如,类型的属性IReadOnlyCollection<T>使用户(即对代表声明类型进行编码的人)明确表示他可能不会修改此集合.但是,声明类型本身是否可以修改集合是不明确的.
  3. 为了完整起见:除了由其成员的签名携带的语义之外,接口不携带任何语义.在这种情况下,观察或外观不变性是依赖于实现的(不仅仅是依赖于接口的实现,而是依赖于实例).

第一个选项实际上是我的首选解释,尽管这个契约很容易被破解,例如通过ReadOnlyCollection<T>从一个数组中构造一个T然后将值设置到包装器数组中.

首创置业拥有门面不变性出色的接口,如IReadOnlyCollection<T>,IReadOnlyList<T>甚至可能IEnumerable<T>等.但是,我觉得观察不变性也是有用的,据我所知,有没有在BCL carring这个意义上任何接口(请指出来如果我错了,请告诉我.有意义的是,这些不存在,因为这种形式的不变性不能通过接口声明强制执行,只能由实现者强制执行(接口可以承载语义,如下所示).旁白:我希望在未来的C#版本中拥有这种能力!


示例:(可以跳过)我经常需要实现一个方法,该方法将另一个线程使用的集合作为参数获取,但该方法要求在执行期间不修改集合,因此我将参数声明为类型,IReadOnlyCollection<T>并给自己一个轻拍,认为我已满足要求.错误......对于一个调用者,签名看起来好像该方法承诺不更改集合,没有别的,如果调用者采用文档(外观)的第二种解释,他可能只是认为变异是允许的,并且方法在问题是对此的抵制.虽然此示例还有其他更常规的解决方案,但我希望您看到此问题可能是一个实际问题,特别是当其他人使用您的代码时(或未来 - 您就此而言).


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

我想使用观察不变性和外观不变性并区分它们.我想到的两个选择是:

  1. 每次使用BCL接口和文档,无论是观察还是外观不变.缺点:使用此类代码的用户只会在已经太晚的情况下查看文档,即发现错误时.我想引导他们进入成功的陷阱; 文档不能这样做).此外,我发现这种语义非常重要,可以在类型系统中看到,而不仅仅是在文档中.
  2. 定义明确地带有观察不变性语义的接口,比如IImmutableCollection<T> : IReadOnlyCollection<T> { }IImmutableList<T> : IReadOnlyList<T> { }.请注意,除了继承的接口之外,接口没有任何成员.这些接口的目的是单独说"即使声明类型也不会改变我!"‡我在这里特别说"不会"而不是"不能".这里有一个缺点:一个邪恶的(或者是错误的,保持礼貌的)实现者不会被编译器或其他任何东西阻止破坏这个合同.然而,优点是选择实现此接口而不是直接从其继承的接口的程序员很可能知道该接口发送的额外消息,因为程序员知道该接口的存在,因此可能会相应地实施它.

我正在考虑使用第二种选择,但我担心它的设计问题与委托类型(它们被发明为在无语义的对应物上传递语义信息)相比Func,Action并且以某种方式失败,请参见此处.

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


†我忽略了集合元素的字段等变异.
‡这对我之前给出的例子有效,但声明实际上更广泛.例如,任何声明方法都不会改变它,或者这种类型的参数表示该方法可以期望集合在其执行期间不会改变(这与说该方法不能改变集合不同,这是唯一的语句可以用现有的接口),也可能是许多其他语句.

Gui*_*ume 1

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

如果您想要一个不可变的对象,则需要一个不可变的具体类型。在 C# 中,不变性取决于实现,并且在接口中不可见。

正如 Chris 所说,您可以找到不可变集合的现有实现