我花了好几个小时思考揭露名单成员的问题.在一个与我类似的问题中,乔恩·斯基特给出了一个极好的答案.请随便看看.

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的一些灵活性,并且封装得相当好.

我发布这个问题是为了得到一些关于我 idea 的信息.比起IEnumerable,你更喜欢这个解决方案吗?您认为使用ReadOnlyCollection的具体返回值更好吗?这是一场相当激烈的辩论,我想试着看看我们都能想出哪些优点/缺点.

提前感谢您的意见.

EDIT

首先,感谢大家为本次讨论做出的贡献.我确实从每一个人身上学到了很多,我想真诚地感谢你们.

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

IReadOnlyCollection和IEnumerable有一些常见的缺陷.

请考虑以下示例:

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

即使接口是只读的,上面的示例也可以转换回列表并进行变异.尽管该接口与其同名,但并不能保证其不变性.提供一个不可变的解决方案取决于您,因此您应该返回一个新的ReadOnlyCollection.通过创建新列表(实质上是副本),您的对象的状态是安全的.

Richiban在他的 comments 中说得最好:一个接口只保证某些东西能做什么,而不是不能做什么.

请参见下面的示例:

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中提出了一个关于yield 率-yield 率声明的奇妙观点.

再次感谢大家.

推荐答案

说到类库,我认为IReadOnly*真的很有用,而且我认为你做得对:)

这都是关于不可变的集合...在没有不可变的数组之前,扩大数组是一项艰巨的任务,所以.net决定在框架中包含一些不同的、可变的集合,为您实现丑陋的东西,但IMHO他们没有为您提供一个非常有用的不可变的正确方向,尤其是在高并发性场景中,共享可变的东西总是一个难题.

如果你判断一下今天的其他语言,比如objective-c,你会发现实际上规则完全颠倒了!他们总是在不同的类之间交换不可变的集合,换句话说,接口只公开不可变的集合,在内部他们使用可变集合(是的,他们当然有),相反,如果他们想让外部人员更改集合(如果类是有状态的类),他们公开适当的方法.

所以我在其他语言方面的一点经验促使我这么想.net list功能强大,但不可变集合的存在是出于某种原因:)

在这种情况下,不是要帮助接口的调用者,避免他在更改内部实现时更改所有代码,就像IList vs List,而是IReadOnly*你在保护你自己,你的类,以不正确的方式使用,避免无用的保护代码,有时您也无法编写的代码(在过go 的某段代码中,为了避免这个问题,我必须返回完整列表的克隆).

.net相关问答推荐

无法在Designer、VS2022、. NET 8中打开WinForms表单'

DI通过对象的接口而不是实际类型来解析服务

当数据大量分布在微服务中时,我应该如何设计后端?

Azure SignalR 和微服务

无法在 Blazor Server 应用程序中触发 InputRadio 的 onchange 事件

为什么具有可为空值的 struct 的 HashSet 非常慢?

将字符串与容差进行比较

使用 Thread.Abort() 有什么问题

如何让 DateTimePicker 显示一个空字符串?

比较 C# 中的字符串和对象

.NET 的 `Array.Sort()` 方法使用的排序算法是稳定的算法吗?

一种消耗(所有字节)BinaryReader 的优雅方式?

.NET - 实现捕获所有异常处理程序的最佳方法是什么

支持 HTTPS 的 Httplistener

自定义属性的构造函数何时运行?

C# - 你如何停止计时器?

如何以编程方式删除 WebClient 中的 2 个连接限制

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

MVVM 没有意义吗?

多行 C# 插值字符串文字