在不可更改之前, IEnumerable 是许多API的首选接口(interface),因为它具有API对传入对象的实际类型不敏感的优点。

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

当然,这至少有两个缺点:
  • API背后的代码不能依赖 collectionOfThings 的不变性,并且可能会遇到“集合修改”异常或遇到其他细微问题。
  • 我们不知道 collectionOfThings 是真实集合还是仅仅是延迟查询。如果我们假设这是一个真实的集合,而事实并非如此,那么我们就有可能通过运行多个枚举来降低性能。如果我们假设这是一个延迟查询,并且实际上是一个真实的集合,那么将其转换为本地列表或其他冻结的集合会产生不必要的成本,尽管它确实可以帮助我们避免第一个问题(执行“ToList”操作时仍然存在竞争条件)。显然,我们可以编写少量代码进行检查,然后尝试做“正确的事情”,但这令人讨厌。

  • 我必须承认,除了使用命名约定外,我从未找到令人满意的模式来解决此问题。尽管存在不利方面,但务实的方法似乎是 IEnumerable 是传递收藏集的最低摩擦方法。

    现在,有了不可变的集合,情况有了很大的改善。
    public void DoSomeEnumerationsWithACollection(ImmutableList<Thing> collectionOfThings)
    {
    

    不再存在集合修改的风险,并且对多个枚举的性能影响也没有歧义。

    但是,由于我们现在必须传递 ImmutableList ,因此显然已经失去了API的灵活性。如果我们的客户有其他种类的可枚举不可变集合,则必须将其复制到 ImmutableList 中才能使用,即使我们要做的只是枚举它。

    理想情况下,我们可以使用类似
    public void DoSomeEnumerationsWithACollection(IImmutableEnumerable<Thing> collectionOfThings)
    

    但是,当然,除非约定,否则接口(interface)不能强制执行不变性之类的语义。

    使用基类可能有效
    public void DoSomeEnumerationsWithACollection(ImmutableEnumerableBase<Thing> collectionOfThings)
    

    除了创建未密封的不可变类被认为是不好的形式,以免子类引入可变性。而且无论如何,BCL都没有做到这一点。

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

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

    更新资料

    IReadOnlyCollection (由下面的Yuval Itzchakov建议)是对 IEnumerable 的明显改进,但仍不能完全保护消费者免受集合中不受控制的更改。值得注意的是,Roslyn代码库大量使用了不可变性(主要通过 ImmutableArray ),并且在将它们传递给其他方法时似乎使用了显式键入,尽管在几个位置中, ImmutableList 传递到了接受 IEnumerable的方法中

    最佳答案

    经过几年大量使用不可变集合的经验之后,我决定在几乎所有地方都使用ImmutableArray的惯例。这并不能满足最初希望在API中提供灵活性的要求,但实际上,很少使用ImmutableList或其他类似列表的结构,并且当我这样做时,通常不需要太多开销来调用ToImmutableArray来通过接口(interface)获取数据。

    关于c# - 跨API传递不可变集合的最佳模式是什么,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/30274472/

    10-16 09:02