我花了不少时间思考揭露名单成员的问题。在一个与我类似的问题中,乔恩·斯基特给出了一个出色的答案。请随意看一看。
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,但是你的对象永远不能被强制转换成任何排序的列表并被改变。
Carsten Führmann对IEnumerables中的收益率回报陈述提出了一个奇妙的观点。
5条答案
按热度按时间jk9hmnmh1#
到目前为止,这些答案似乎缺少一个重要方面:
当
IEnumerable<T>
被返回给调用者时,他们必须考虑返回的对象是“惰性流”的可能性,例如用“yield return”构建的集合。也就是说,产生IEnumerable<T>
的元素的性能损失可能必须由调用者来支付,(生产力工具“Resharper”实际上指出这是一种代码气味。)相比之下,
IReadOnlyCollection<T>
向调用者发出信号,表示将不进行延迟求值(Count
属性与IEnumerable<T>
的Count
extension method相反(IReadOnlyCollection<T>
继承了Count
extension method,因此它也具有该方法),它发出非延迟信号,而且似乎不存在IReadOnlyCollection的延迟实现这一事实也是如此)。这对于输入参数也是有效的,因为请求
IReadOnlyCollection<T>
而不是IEnumerable<T>
表示该方法需要在集合上迭代多次。当然,该方法可以从IEnumerable<T>
创建自己的列表并在该列表上迭代,但是由于调用者可能已经具有加载的集合在手边,因此只要可能就利用它是有意义的。如果调用者手头只有一个IEnumerable<T>
,他只需要将.ToArray()
或.ToList()
添加到参数中。IReadOnlyCollection没有做的是防止调用方强制转换为其他集合类型。要实现这样的保护,必须使用类
ReadOnlyCollection<T>
。总而言之,
IReadOnlyCollection<T>
相对于IEnumerable<T>
所做的 * 唯一 * 事情是添加了一个Count
属性,从而表明不涉及懒惰。jv2fixgn2#
谈到类库,我认为IReadOnly* 非常有用,而且我认为您做得很对:)
这都是关于不可变集合的...以前只有不可变集合,扩大数组是一项艰巨的任务,所以.net决定在框架中包含一些不同的东西,可变集合,为你实现丑陋的东西,但恕我直言,他们没有给予你一个正确的方向不可变,这是非常有用的,特别是在一个高并发的情况下,共享可变的东西总是一个PITA。
如果你检查一下今天的其他语言,比如objective-c,你会发现实际上规则完全颠倒了!它们总是在不同的类之间交换不可变集合,换句话说,接口只暴露不可变,而在内部使用可变集合(是的,他们当然有),相反,如果他们想让外部人员更改集合(如果类是有状态类),他们会公开适当的方法。
因此,我在其他语言中获得的这一点经验促使我认为.net列表是如此强大,但不可变集合的存在是出于某种原因:)
在这种情况下,并不是帮助接口的调用者,避免他在更改内部实现时更改所有代码,就像IList vs List一样,而是使用IReadOnly*,您可以保护自己和类,避免以不正确的方式使用,避免无用的保护代码,有时候你也写不出来的代码(在过去的一些代码中,我不得不返回一个完整列表的克隆来避免这个问题)。
hrirmatl3#
我对强制转换和IReadOnly* 合同的关注,以及这些合同的“正确”用法的看法。
如果某些代码足够“聪明”,能够执行显式强制转换并打破接口契约,那么它也足够“聪明”,能够使用反射或以其他方式做一些邪恶的事情,比如访问ReadOnlyCollection wrapper 对象的底层List。我不会与这样“聪明”的程序员竞争。
我唯一能保证的是,在公开了IReadOnly接口对象之后,我的代码不会违反该契约,也不会修改返回的集合对象。
这意味着我编写的代码返回List-as-IReadOnly*,例如,很少选择实际的只读具体类型或 Package 器。使用IEnumerable.ToList * 足以 * 返回IReadOnly[List| Collection] -调用List.AsReadOnly对“聪明的”程序员来说没有什么价值,因为他们仍然可以访问ReadOnlyCollection * Package * 的底层列表。
在任何情况下,我都保证IReadOnly* 返回值的具体类型是渴望的。如果我写了一个返回IEnumerable的方法,那是因为该方法的契约是“支持流”的fsvo。
就IReadOnlyList和IReadOnlyCollection而言,当存在“一个”隐含的稳定排序时,我使用前者,该排序 * 对索引有意义 *,而不考虑有目的的排序。例如,数组和列表可以作为IReadOnlyList返回,而HashSet最好作为IReadOnlyCollection返回。调用者总是可以根据需要将I[ReadOnly]List分配给I[ReadOnly]Collection:这个选择是关于暴露的“契约”,而不是一个程序员,“聪明”或其他人,会做什么。
ccrfmcuu4#
看起来您可以返回一个 * 适当的接口 *:
由于
workItems
字段实际上是List<T>
,因此IMHO的自然想法是公开最宽的接口,在本例中是IReadOnlyList<T>
slsn1g295#
!! IEnumerable与IReadOnlyList的比较!!
IEnumerable从一开始就伴随着我们。多年来,它一直是表示只读集合的事实上的标准方法。然而,从. NET 4.5开始,有了另一种方法:I只读列表。
这两个集合接口都很有用。
〈〉