我花了好几个小时思考揭露名单成员的主题。在与我的类似问题中,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中的收益率报表提出了一个很好的观点。
再次感谢大家。
答案 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:这个选择是关于公开的合同,而不是程序员,“聪明的”或其他人会做什么。