IEnumerable vs IReadonlyCollection vs ReadonlyCollection用于公开列表成员

时间:2014-07-22 06:23:31

标签: c# .net list ienumerable encapsulation

我花了好几个小时思考揭露名单成员的主题。在与我的类似问题中,John Skeet给出了一个很好的答案。请随便看看。

ReadOnlyCollection or IEnumerable for exposing member collections?

我通常对暴露列表非常偏执,特别是如果你正在开发API。

我一直使用IEnumerable来公开列表,因为它非常安全,而且它提供了很大的灵活性。让我在这里使用一个例子

public class Activity
{
    private readonly IList<WorkItem> workItems = new List<WorkItem>();

    public string Name { get; set; }

    public IEnumerable<WorkItem> WorkItems
    {
        get
        {
            return this.workItems;
        }
    }

    public void AddWorkItem(WorkItem workItem)
    {
        this.workItems.Add(workItem);
    }
}

任何对IEnumerable进行编码的人都非常安全。如果我后来决定使用有序列表或其他东西,它们的代码都没有中断,它仍然很好。这样做的缺点是IEnumerable可以被强制转换回这个类之外的列表。

出于这个原因,许多开发人员使用ReadOnlyCollection来公开成员。这是非常安全的,因为它永远不会被强制转换为列表。对我来说,我更喜欢IEnumerable,因为它提供了更大的灵活性,我是否想要实现与列表不同的东西。

我想出了一个我更喜欢的新想法。使用IReadOnlyCollection

public class Activity
{
    private readonly IList<WorkItem> workItems = new List<WorkItem>();

    public string Name { get; set; }

    public IReadOnlyCollection<WorkItem> WorkItems
    {
        get
        {
            return new ReadOnlyCollection<WorkItem>(this.workItems);
        }
    }

    public void AddWorkItem(WorkItem workItem)
    {
        this.workItems.Add(workItem);
    }
}

我觉得这保留了IEnumerable的一些灵活性,并且封装得非常好。

我发布了这个问题,以便对我的想法有所了解。你更喜欢IEnumerable这个解决方案吗?你认为使用ReadOnlyCollection的具体返回值更好吗?这是一个很有争议的问题,我想试着看看我们都可以提出哪些优点/缺点。

提前感谢您的意见。

修改

首先,感谢大家为此次讨论做出的贡献。我确实从每一个人那里学到了很多东西,并真诚地感谢你。

我正在添加一些额外的场景和信息。

IReadOnlyCollection和IEnumerable存在一些常见的陷阱。

考虑以下示例:

public IReadOnlyCollection<WorkItem> WorkItems
{
    get
    {
        return this.workItems;
    }
}

上面的示例可以返回到列表并进行变异,即使接口是只读的。界面,尽管它是同名的,但并没有增加不变性。它由你来提供一个不可变的解决方案,因此你应该返回一个新的ReadOnlyCollection。通过创建一个新列表(本质上是一个副本),对象的状态是安全的。

Richiban在他的评论中说得最好:界面只保证可以做什么,而不是它不能做什么

请参阅下面的示例。

public IEnumerable<WorkItem> WorkItems
{
    get
    {
        return new List<WorkItem>(this.workItems);
    }
}

上面的内容可以被渲染和变异,但你的对象仍然是不可变的。

另一个框外声明将是集合类。请考虑以下事项:

public class Bar : IEnumerable<string>
{
    private List<string> foo;

    public Bar()
    {
        this.foo = new List<string> { "123", "456" };
    }

    public IEnumerator<string> GetEnumerator()
    {
        return this.foo.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

上面的类可以有方法以你想要的方式改变foo,但是你的对象永远不能被转换为任何排序和变异的列表。

CarstenFührmann对IEnumerables中的收益率报表提出了一个很好的观点。

再次感谢大家。

5 个答案:

答案 0 :(得分:57)

到目前为止,答案中似乎缺少一个重要方面:

IEnumerable<T>返回给调用者时,他们必须考虑返回的对象是&#34;惰性流&#34;的可能性,例如,一个用&#34生成的集合;收益率返回&#34;。也就是说,对于IEnumerable的每次使用,可能必须由调用者支付产生IEnumerable<T>的元素的性能损失。 (生产力工具&#34; Resharper&#34;实际上将其指出为代码气味。)

相比之下,IReadOnlyCollection<T>向呼叫者发出信号,表示不会进行延迟评估。 (Count属性,而不是IEnumerable<T>的{​​{3}}(由IReadOnlyCollection<T>继承,因此它也有方法),表示非惰性。所以事实上似乎没有懒惰的IReadOnlyCollection实现。)

这对输入参数也有效,因为请求IReadOnlyCollection<T>而不是IEnumerable<T>信号,该方法需要在集合上多次迭代。当然,该方法可以从IEnumerable<T>创建自己的列表并对其进行迭代,但由于调用者可能已经有一个已加载的集合,因此尽可能利用它是有意义的。如果来电者只有IEnumerable<T>,他只需要在参数中添加.ToArray().ToList()

IReadOnlyCollection做做的是阻止调用者转换为其他某种集合类型。要获得此类保护,必须使用 ReadOnlyCollection<T>

总而言之,事物IReadOnlyCollection<T>相对于IEnumerable<T>确实添加了Count属性,因此表示不涉及任何延迟。

答案 1 :(得分:14)

谈论类库,我认为IReadOnly *非常有用,我认为你做的正确:)

所有关于不可变的集合...之前只有不可变的和放大数组是一项艰巨的任务,所以.net决定在框架中包含一些不同的,可变的集合,为你实现丑陋的东西,但恕我直言他们没有给你一个非常有用的不可变的正确方向,特别是在高并发场景中,共享可变东西总是PITA。

如果您今天检查其他语言,例如objective-c,您会看到实际上规则完全颠倒了!他们总是在不同的类之间交换不可变的集合,换句话说,接口公开只是不可变的,并且在内部他们使用可变集合(是的,他们当然有它),而是他们暴露正确的方法,如果他们想让外人改变集合(如果班级是有状态的班级)。

所以我用其他语言获得的这种小经验促使我认为.net列表是如此强大,但不可变集合出于某种原因:)

在这种情况下,不是帮助调用者接口,避免他改变所有代码,如果你正在改变内部实现,就像IList vs List一样,但是使用IReadOnly *你正在保护你自己,你的班级,以不正确的方式使用,以避免无用的保护代码,有时你不能写的代码(在过去的某些代码中我必须返回完整列表的克隆以避免这个问题)。

答案 2 :(得分:4)

您似乎可以返回适当的界面

...
    private readonly List<WorkItem> workItems = new List<WorkItem>();

    // Usually, there's no need the property to be virtual 
    public virtual IReadOnlyList<WorkItem> WorkItems {
      get {
        return workItems;
      }
    }
...

由于workItems字段实际上是List<T>所以IMHO的自然想法是公开最广泛的接口IReadOnlyList<T>

答案 3 :(得分:0)

! IEnumerable与IReadOnlyList !!

IEnumerable从一开始就与我们同在。多年来,这是代表只读集合的​​事实上的标准方法。但是,从.NET 4.5开始,还有另一种方法:IReadOnlyList。

两个收集接口都很有用。

<>

答案 4 :(得分:0)

我对强制转换和 IReadOnly* 合同的担忧,以及它们的“正确”用法。

如果某些代码“聪明”到足以执行显式转换并破坏接口契约,那么它也“聪明”到可以使用反射或以其他方式做诸如访问 ReadOnlyCollection 的底层 List 之类的邪恶事情wrapper 对象。 我不会针对这些“聪明”的程序员编程。

我唯一保证的是,说IReadOnly*-interface对象被暴露之后,我的代码违反那个契约并且不会修改返回的集合对象。

这意味着我编写的代码返回 List-as-IReadOnly*,例如,很少选择实际的只读具体类型或包装器。使用 IEnumerable.ToList 足以返回 IReadOnly[List|Collection] - 调用 List.AsReadOnly 对仍然可以访问 ReadOnlyCollection 包装的底层列表的“聪明”程序员几乎没有什么价值/em>.

在所有情况下,我保证 IReadOnly* 返回值的具体类型是急切的。如果我曾经写过一个返回 IEnumerable 的方法,那特别是因为该方法的契约是“支持流”fsvo 的那个。

就 IReadOnlyList 和 IReadOnlyCollection 而言,当建立了“一个”隐含的稳定排序对索引有意义时,我使用前者,而不管有目的的排序。例如,数组和列表可以作为 IReadOnlyList 返回,而 HashSet 最好作为 IReadOnlyCollection 返回。调用者总是可以根据需要将 I[ReadOnly]List 分配给 I[ReadOnly]Collection:这个选择是关于公开的合同,而不是程序员,“聪明的”或其他人会做什么。