C# 用于公开列表成员的IEnumerable vs IReadonlyCollection vs ReadonlyCollection
我花了好几个小时思考揭露名单成员的问题。在一个与我类似的问题中,Jon Skeet给出了一个极好的答案。请随便看看 我通常非常偏执于公开列表,尤其是在开发API时 我一直使用IEnumerable来公开列表,因为它非常安全,并且提供了很大的灵活性。让我举一个例子:C# 用于公开列表成员的IEnumerable vs IReadonlyCollection vs ReadonlyCollection,c#,.net,list,ienumerable,encapsulation,C#,.net,List,Ienumerable,Encapsulation,我花了好几个小时思考揭露名单成员的问题。在一个与我类似的问题中,Jon Skeet给出了一个极好的答案。请随便看看 我通常非常偏执于公开列表,尤其是在开发API时 我一直使用IEnumerable来公开列表,因为它非常安全,并且提供了很大的灵活性。让我举一个例子: public class Activity { private readonly IList<WorkItem> workItems = new List<WorkItem>(); publ
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);
}
}
公共课堂活动
{
私有只读IList workItems=new List();
公共字符串名称{get;set;}
公共IEnumerable工作项
{
得到
{
返回此.workItems;
}
}
公共无效添加工作项(工作项工作项)
{
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);
}
}
公共课堂活动
{
私有只读IList workItems=new List();
公共字符串名称{get;set;}
公共IReadOnlyCollection工作项
{
得到
{
返回新的ReadOnlyCollection(this.workItems);
}
}
公共无效添加工作项(工作项工作项)
{
this.workItems.Add(workItem);
}
}
我觉得它保留了IEnumerable的一些灵活性,并且封装得非常好
我发布这个问题是为了得到一些关于我想法的信息。与IEnumerable相比,您更喜欢此解决方案吗?您认为使用ReadOnlyCollection的具体返回值更好吗?这是一场相当激烈的辩论,我想试着看看我们都能想出哪些优点/缺点
提前感谢您的投入
编辑
首先,感谢大家为本次讨论做出的贡献。我确实从每一个人身上学到了很多,我要真诚地感谢你们
我正在添加一些额外的场景和信息
IReadOnlyCollection和IEnumerable存在一些常见的缺陷
考虑下面的例子:
public IReadOnlyCollection<WorkItem> WorkItems
{
get
{
return this.workItems;
}
}
公共IReadOnlyCollection工作项
{
得到
{
返回此.workItems;
}
}
即使接口是只读的,也可以将上面的示例转换回列表并进行修改。尽管接口同名,但它不能保证不变性。由您提供一个不可变的解决方案,因此您应该返回一个新的ReadOnlyCollection。通过创建新列表(本质上是副本),对象的状态是安全的
Richiban在他的评论中说得最好:接口只保证某些东西能做什么,而不是不能做什么
请参见下面的示例:
public IEnumerable<WorkItem> WorkItems
{
get
{
return new List<WorkItem>(this.workItems);
}
}
公共IEnumerable工作项
{
得到
{
返回新列表(此.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();
}
}
公共类栏:IEnumerable
{
私人名单;
公共酒吧()
{
this.foo=新列表{“123”,“456”};
}
公共IEnumerator GetEnumerator()
{
返回此.foo.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
返回此.GetEnumerator();
}
}
上面的类可以有方法按照您希望的方式改变foo,但是您的对象永远不能被强制转换为任何类型的列表并进行改变
Carsten Führmann对IEnumerables中的收益率-收益率语句提出了一个奇妙的观点
再次感谢大家。您似乎可以返回一个适当的界面:
。。。
私有只读列表工作项=新列表();
//通常,属性不需要是虚拟的
公共虚拟IReadOnlyList工作项{
得到{
返回工作项;
}
}
...
由于
工作项
字段实际上是列表
,因此IMHO的自然想法是公开最广泛的接口,即IReadOnlyList
,在本例中谈论类库时,我认为IReadOnly*非常有用,我认为您做得对:)
这都是关于不可变的集合。。。在不可变之前,扩大数组是一项艰巨的任务,因此.net决定在框架中加入一些不同的、可变的集合,为您实现难看的东西,但他们没有给您提供一个非常有用的不可变的正确方向,特别是在高并发性场景中,共享可变内容总是一个难题
如果你检查一下今天的其他语言,比如objective-c,你会发现实际上规则完全颠倒了!它们总是在不同的类之间交换不可变的集合,换句话说,接口只公开不可变的集合,并且在内部使用可变集合(是的,它们当然有),相反,如果它们想让外部人员更改集合(如果类是有状态的类),它们公开适当的方法
因此,我对其他语言的这一点经验促使我认为.net列表非常强大,但不可变集合的存在是出于某种原因:)
在这种情况下,不需要帮助接口的调用者,以避免他在更改内部实现时更改所有代码,就像IList vs List,b一样
...
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;
}
}
...