2013-07-24 37 views
12

我在怀疑我对System.Collection.Generic.IReadOnlyCollection<T>语义的理解,怀疑如何使用诸如只读和不可变等概念进行设计。让我使用documentation描述两个性质,我怀疑之间,其中规定不变性/只读语义(特别是C#IReadOnlyCollection <T>)

表示一个强类型,只读元素的集合。

取决于我是否强调的话“代表”或“只读”(在我的脑海宣判的时候,或者大声,如果这是你的风格),我觉得这句话的变化意味着:

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

第一个选项实际上是我的首选解释,虽然这个合同很容易被打破,通过从T的数组中构建一个ReadOnlyCollection<T>,然后在包装器数组中设置一个值。

的BCL拥有门面不变性出色的接口,如IReadOnlyCollection<T>IReadOnlyList<T>,甚至IEnumerable<T>等。但是,我觉得观察不变性也是有用的,据我所知,有没有在BCL carring任何接口这个意思(如果我错了,请指出他们)。这是不存在的,因为这种不变性的形式不能通过接口声明来实现,只能由实现者来实现(尽管接口可以承载语义,正如我将在下面展示的那样)。除此之外:我很想在未来的C#版本中拥有这种能力!


实施例:(可被跳过)我不得不频繁地执行该获取作为参数,它是由另一个线程以及一个集合的方法,但该方法需要收集期间不被修改其执行,因此我声明该参数的类型为IReadOnlyCollection<T>,并给我自己一个认为我已满足要求的背后拍拍。错误...对于一个调用者来说,签名看起来好像该方法承诺不会改变集合,没有别的,如果调用者对文档(外观)进行第二种解释,他可能会认为允许变异并且方法在问题是抵抗的。虽然这个例子还有其他更传统的解决方案,但我希望你能看到这个问题可能是一个实际问题,特别是当其他人使用你的代码(或者未来 - 你就是这个问题)时。


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

我想用观测不变性和门面不变性和区分它们。我想到的两个选项是:

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

我想与第二个选项去,但恐怕它有设计问题媲美的委托类型(其被发明结转他们semanticless同行FuncAction语义信息),并以某种方式失败,见例如here

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


†我忽略了字段等对集合元素的变化。
‡这对我之前给出的例子有效,但声明实际上更广泛。例如任何声明的方法不会改变它,或这种类型的参数传递,可以在方法需要收集未在其执行过程中改变(这是不同说该方法不能改变集合,这是唯一的可以使用现有的接口声明)以及可能还有其他接口。

+1

这或许是相关的:HTTP://blogs.msdn。com/b/bclteam/archive/2012/12/18/preview-of-immutable-collections-released-on-nuget.aspx –

+6

我赞成使用术语“不可变”作为观察上不可变集合的类名的一部分,和“Readonly”作为可变集合的不可变外观的类名的一部分。我还喜欢名称中带有“不可变”一词的非集合类,它们是“非常不可变的”,即字段都是私有只读值类型,或者是本身是不可变的私有只读引用类型。 ('readonly'就是C#关键字)(这似乎也是微软采用的惯例,非常快乐的日子。) –

+0

我并不是真正希望你在问什么,但是IImmutablexxx 意味着它不会改变(它可以改变,但是更改将生成一个新的集合)IReadOnlyxxx仅防止收件人更改提供者可以更改的集合。 –

回答

1

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

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

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

+0

除了一些“标记”接口外,任何*接口唯一可以保证的是,任何实现它的类都将遵守接口的合约或被视为“破坏”的实现。如果'ImmutableFloatMatrix'的合约指定每个对'GetValue(X,Y)'的调用必须总是返回给定的(X,Y)对的相同结果,那么消费者将同样有权假定'ImmutableFloatMatrix '是不变的,而不是假设通过“Fred”列表.Add'不会追加“Barney”。 – supercat