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

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

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

我通常非常偏执于公开列表,尤其是在开发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);
    }
}
公共课堂活动
{
私有只读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;
      }
    }
...