首页 文章

跨API传递不可变集合的最佳模式是什么

提问于
浏览
11

在不可变性之前, IEnumerable 是许多API中的首选接口,因为它具有API对传递对象的实际类型不敏感的优点 .

public void DoSomeEnumerationsWithACollection(IEnumerable<Thing> collectionOfThings)
{ 
   foreach (var thing in collectionOfThings) doSomethingWith(thing);
   foreach (var thing in collectionOfThings) doSomethingElseWith(thing);
}

当然,至少有两个缺点:

  • API背后的代码不能依赖 collectionOfThings 的不变性,可能会遇到"collection modified"异常或遇到其他其他细微问题 .

  • 我们不知道 collectionOfThings 是真实集合还是仅仅是延迟查询 . 如果我们假设's a real collection and it isn't,我们就会冒着运行多个枚举而降低性能的风险 . 如果我们假设's a deferred query and it's实际上是一个真正的集合,那么将其转换为本地列表或其他冻结集合会产生不必要的成本,尽管它确实有助于保护我们免受第一个问题的影响(在执行"ToList"操作时仍存在竞争条件) . 显然,我们可以编写少量代码来检查这一点并尝试执行"right thing",但这是令人烦恼的额外混乱 .

我必须承认,除了使用命名约定之外,我从未找到过令人满意的模式来解决这个问题 . 尽管存在缺点,但务实的方法似乎是尽可能地绕过收藏品的摩擦方法.1111907_ .

现在,随着不可变的收藏,情况得到了很大改善......

public void DoSomeEnumerationsWithACollection(ImmutableList<Thing> collectionOfThings)
{

不再存在收集修改的风险,并且对多个枚举的性能影响没有任何歧义 .

但是,我们显然失去了API的灵活性,因为我们现在必须传入 ImmutableList . 如果我们的客户端有其他类型的可枚举不可变集合,则必须将其复制到 ImmutableList 才能被使用,即使我们想要做的只是枚举它 .

理想情况下,我们可以使用类似的界面

public void DoSomeEnumerationsWithACollection(IImmutableEnumerable<Thing> collectionOfThings)

但是,当然,除了约定之外,接口不能强制执行不可变性等语义 .

使用基类可能会起作用

public void DoSomeEnumerationsWithACollection(ImmutableEnumerableBase<Thing> collectionOfThings)

除了它被认为是创建未密封的不可变类的不良形式,以免子类引入可变性 . 无论如何,这还没有在BCL中完成 .

或者我们可以继续在API中使用IEnumerable并使用命名约定来明确我们的代码依赖于传递的不可变集合 .

所以...我的问题是在传递不可变集合时哪些模式被认为是最好的?一旦我们开始使用immutablity或者有更好的方法, ImmutableList 是“新 IEnumerable ”吗?

更新

IReadOnlyCollection (由Yuval Itzchakov建议如下)是对 IEnumerable 的明显改进,但仍未完全保护消费者免受集合中不受控制的变化的影响 . 值得注意的是,Roslyn代码库大量使用不变性(主要通过 ImmutableArray )并且在将这些传递给其他方法时似乎使用显式类型,尽管有几个位置 ImmutableList 被传递到接受 IEnumerable 的方法中 .

2 回答

  • 1

    传递不可变集合时哪些模式最佳?

    我认为你的问题的答案是 IReadOnlyCollection<T> ,它将在.NET 4.6中传播 . 通过传递只读集合,您既可以保持不变性,又可以使用实现该接口的常规集合 .

  • 0

    在大量使用不可变集合几年后,我已经确定了几乎在任何地方使用ImmutableArray的约定 . 这并没有解决API允许灵活性的原始愿望,但实际上我很少使用ImmutableList或其他类似列表的结构,当我这样做时,调用ToImmutableArray通过接口获取数据通常没有多少开销 .

相关问题