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

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相关问答推荐

从窗体中移除另一个控件中引用的控件时获取设计时通知

如何在 .Net Core EF 中组合多个条件表达式来过滤数据?

如何在 ASP.NET Core MVC 中读取操作方法的属性?

如何将 Javascript 日期时间转换为 C# 日期时间?

移位比Java中的乘法和除法更快吗? .网?

如何获得友好的操作系统版本名称?

我应该从 .NET 中的 Exception 或 ApplicationException 派生自定义异常吗?

使用只读属性或方法?

Java 和 .NET 技术/框架的类似物

何时在 C#/.NET 中使用指针?

双倍的? = 双倍? + 双倍?

C# 测试字符串是否为整数?

mscorlib 代表什么?

Moq - 不可覆盖的成员不能用于设置/验证表达式

从 C# 中的字符串中删除最后一个字符.优雅的方式?

如何比较泛型类型的值?

带有嵌套控件的设计模式

如何重新启动 WPF 应用程序?

浮动与双重性能

Uri.Host 和 Uri.Authority 有什么区别