简体   繁体   English

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

[英]IEnumerable vs IReadonlyCollection vs ReadonlyCollection for exposing a list member

I have spent quite a few hours pondering the subject of exposing list members.我花了好几个小时思考揭露名单成员的问题。 In a similar question to mine, Jon Skeet gave an excellent answer.在与我类似的问题中,Jon Skeet 给出了一个很好的答案。 Please feel free to have a look.请随意看一看。

ReadOnlyCollection or IEnumerable for exposing member collections? ReadOnlyCollection 或 IEnumerable 用于公开成员集合?

I am usually quite paranoid to exposing lists, especially if you are developing an API.我通常对公开列表非常偏执,尤其是在开发 API 时。

I have always used IEnumerable for exposing lists, as it is quite safe, and it gives that much flexibility.我一直使用 IEnumerable 来公开列表,因为它非常安全,并且提供了很大的灵活性。 Let me use an example here:让我在这里举个例子:

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);
    }
}

Anyone who codes against an IEnumerable is quite safe here.任何针对 IEnumerable 进行编码的人在这里都非常安全。 If I later decide to use an ordered list or something, none of their code breaks and it is still nice.如果我后来决定使用有序列表或其他东西,他们的代码都不会中断,它仍然很好。 The downside of this is IEnumerable can be cast back to a list outside of this class.这样做的缺点是 IEnumerable 可以转换回此类之外的列表。

For this reason, a lot of developers use ReadOnlyCollection for exposing a member.出于这个原因,许多开发人员使用 ReadOnlyCollection 来公开成员。 This is quite safe since it can never be cast back to a list.这是非常安全的,因为它永远不会被转换回列表。 For me I prefer IEnumerable since it provides more flexibility, should I ever want to implement something different than a list.对我来说,我更喜欢 IEnumerable,因为它提供了更大的灵活性,如果我想要实现与列表不同的东西。

I have come up with a new idea I like better.我想出了一个我更喜欢的新想法。 Using IReadOnlyCollection:使用 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);
    }
}

I feel this retains some of the flexibility of IEnumerable and is encapsulated quite nicely.我觉得这保留了 IEnumerable 的一些灵活性,并且封装得很好。

I posted this question to get some input on my idea.我发布了这个问题来获得一些关于我的想法的意见。 Do you prefer this solution to IEnumerable?与 IEnumerable 相比,您更喜欢这个解决方案吗? Do you think it is better to use a concrete return value of ReadOnlyCollection?您认为使用 ReadOnlyCollection 的具体返回值更好吗? This is quite a debate and I want to try and see what are the advantages/disadvantages that we all can come up with.这是一场相当大的辩论,我想尝试看看我们都可以提出哪些优点/缺点。

Thanks in advance for your input.预先感谢您的意见。

EDIT编辑

First of all thank you all for contributing so much to the discussion here.首先感谢大家对这里的讨论做出了如此多的贡献。 I have certainly learned a ton from each and every one and would like to thank you sincerely.我当然从每个人那里学到了很多东西,并真诚地感谢你。

I am adding some extra scenarios and info.我正在添加一些额外的场景和信息。

There are some common pitfalls with IReadOnlyCollection and IEnumerable. IReadOnlyCollection 和 IEnumerable 有一些常见的陷阱。

Consider the example below:考虑下面的例子:

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

The above example can be casted back to a list and mutated, even though the interface is readonly.即使接口是只读的,上面的示例也可以转换回列表并进行变异。 The interface, despite it's namesake does not guarantee immutability.接口尽管同名,但并不能保证不变性。 It is up to you to provide an immutable solution, therefore you should return a new ReadOnlyCollection.由您提供不可变的解决方案,因此您应该返回一个新的 ReadOnlyCollection。 By creating a new list (a copy essentially), the state of your object is safe and sound.通过创建一个新列表(本质上是一个副本),您的对象的状态是安全的。

Richiban says it best in his comment: a interface only guarantees what something can do, not what it cannot do. Richiban 在他的评论中说得最好:接口只保证什么可以做什么,而不是它不能做什么。

See below for an example:请参阅下面的示例:

public IEnumerable<WorkItem> WorkItems
{
    get
    {
        return new List<WorkItem>(this.workItems);
    }
}

The above can be casted and mutated, but your object is still immutable.上面的可以被转换和改变,但你的对象仍然是不可变的。

Another outside the box statement would be collection classes.另一个框外语句是集合类。 Consider the following:考虑以下:

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();
    }
}

The class above can have methods for mutating foo the way you want it to be, but your object can never be casted to a list of any sort and mutated.上面的类可以有方法以你想要的方式改变 foo ,但是你的对象永远不能被转换为任何类型的列表并改变。

Carsten Führmann makes a fantastic point about yield return statements in IEnumerables. Carsten Führmann 对 IEnumerables 中的 yield return 语句提出了一个绝妙的观点。

Thank you all once again.再次感谢大家。

One important aspect seems to be missing from the answers so far:到目前为止,答案中似乎缺少一个重要方面:

When an IEnumerable<T> is returned to the caller, they must consider the possibility that the returned object is a "lazy stream", eg a collection built with "yield return".IEnumerable<T>返回给调用者时,他们必须考虑返回对象是“惰性流”的可能性,例如使用“yield return”构建的集合。 That is, the performance penalty for producing the elements of the IEnumerable<T> may have to be paid by the caller, for each use of the IEnumerable.也就是说,每次使用 IEnumerable 时,调用者可能必须支付生成IEnumerable<T>元素的性能损失。 (The productivity tool "Resharper" actually points this out as a code smell.) (生产力工具“Resharper”实际上指出这是一种代码异味。)

By contrast, an IReadOnlyCollection<T> signals to the caller that there will be no lazy evaluation.相比之下, IReadOnlyCollection<T>会向调用者发出信号,表明不会进行惰性求值。 (The Count property, as opposed to the Count extension method of IEnumerable<T> (which is inherited by IReadOnlyCollection<T> so it has the method as well), signals non-lazyness. And so does the fact that there seem to be no lazy implementations of IReadOnlyCollection.) Count属性,而不是IEnumerable<T>Count扩展方法(它由IReadOnlyCollection<T>继承,因此它也具有该方法),表示非懒惰。事实上似乎有没有 IReadOnlyCollection 的惰性实现。)

This is also valid for input parameters, as requesting an IReadOnlyCollection<T> instead of IEnumerable<T> signals that the method needs to iterate several times over the collection.这也适用于输入参数,因为请求IReadOnlyCollection<T>而不是IEnumerable<T>表示该方法需要在集合上迭代多次。 Sure the method could create its own list from the IEnumerable<T> and iterate over that, but as the caller may already have a loaded collection at hand it would make sense to take advantage of it whenever possible.当然,该方法可以从IEnumerable<T>创建自己的列表并对其进行迭代,但由于调用者可能已经有一个已加载的集合,因此尽可能利用它是有意义的。 If the caller only has an IEnumerable<T> at hand, he only needs to add .ToArray() or .ToList() to the parameter.如果调用者手头只有一个IEnumerable<T> ,他只需要将.ToArray().ToList()到参数中。

What IReadOnlyCollection does not do is prevent the caller to cast to some other collection type.什么IReadOnlyCollection就是防止来电投其他一些集合类型。 For such protection, one would have to use the class ReadOnlyCollection<T> .对于这种保护,必须使用ReadOnlyCollection<T>

In summary, the only thing IReadOnlyCollection<T> does relative to IEnumerable<T> is add a Count property and thus signal that no lazyness is involved.总之, IReadOnlyCollection<T>相对于IEnumerable<T> IReadOnlyCollection<T>所做的唯一一件事就是添加一个Count属性,从而表明不涉及惰性。

Talking about class libraries, I think IReadOnly* is really useful, and I think you're doing it right :)谈到类库,我认为 IReadOnly* 真的很有用,我认为你做对了 :)

It's all about immutable collection... Before there were just immutables and to enlarge arrays was a huge task, so .net decided to include in the framework something different, mutable collection, that implement the ugly stuff for you, but IMHO they didn't give you a proper direction for immutable that are extremely useful, especially in a high concurrency scenario where sharing mutable stuff is always a PITA.这完全是关于不可变集合......在只有不可变对象之前,扩大数组是一项艰巨的任务,所以 .net 决定在框架中包含一些不同的可变集合,为您实现丑陋的东西,但恕我直言,他们没有” t 给你一个非常有用的不可变的正确方向,特别是在高并发场景中,共享可变的东西总是一个 PITA。

If you check other today languages, such as objective-c, you will see that in fact the rules are completely inverted!如果您查看其他当今的语言,例如objective-c,您会发现实际上规则完全颠倒了! They quite always exchange immutable collection between different classes, in other words the interface expose just immutable, and internally they use mutable collection (yes, they have it of course), instead they expose proper methods if they want let the outsiders change the collection (if the class is a stateful class).他们总是在不同的类之间交换不可变集合,换句话说,接口只公开不可变的,并且在内部他们使用可变集合(是的,他们当然有),如果他们想让外部人员更改集合,他们会公开适当的方法(如果该类是有状态类)。

So this little experience that I've got with other languages pushes me to think that .net list are so powerful, but the immutable collection were there for some reason :)因此,我在其他语言方面的这一小经验促使我认为 .net 列表如此强大,但由于某种原因,不可变集合在那里:)

In this case is not a matter of helping the caller of an interface, to avoid him to change all the code if you're changing internal implementation, like it is with IList vs List, but with IReadOnly* you're protecting yourself, your class, to being used in not a proper way, to avoid useless protection code, code that sometimes you couldn't also write (in the past in some piece of code I had to return a clone of the complete list to avoid this problem).在这种情况下,这不是帮助接口调用者的问题,如果您正在更改内部实现,则避免他更改所有代码,就像使用 IList 与 List,但使用 IReadOnly* 您是在保护自己,您的类,以不正确的方式使用,以避免无用的保护代码,有时您也无法编写的代码(过去在某些代码中,我不得不返回完整列表的克隆以避免此问题) .

It seems that you can just return an appropriate interface :看来你可以只返回一个合适的接口

...
    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;
      }
    }
...

Since workItems field is in fact List<T> so the natural idea IMHO is to expose the most wide interface which is IReadOnlyList<T> in the case由于workItems字段实际上是List<T>所以自然的想法恕我直言是公开最宽的接口,即IReadOnlyList<T>在这种情况下

My take on concerns of casting and IReadOnly* contracts, and 'proper' usages of such.我对强制转换和 IReadOnly* 合同的关注,以及此类的“正确”用法。

If some code is being “clever” enough to perform an explicit cast and break the interface contract, then it is also “clever” enough to use reflection or otherwise do nefarious things such as access the underlying List of a ReadOnlyCollection wrapper object.如果某些代码足够“聪明”以执行显式转换并破坏接口契约,那么它也足够“聪明”以使用反射或以其他方式执行诸如访问 ReadOnlyCollection包装器对象的底层 List 之类的邪恶事情。 I don't program against such “clever” programmers.我不会针对这些“聪明”的程序员进行编程。

The only thing that I guarantee is that after said IReadOnly*-interface objects are exposed, then my code will not violate that contract and will not modified the returned collection object.我唯一可以保证的是,IReadOnly* 接口对象被公开之后,我的代码不会违反该约定,也不会修改返回的集合对象。

This means that I write code that returns List-as-IReadOnly*, eg., and rarely opt for an actual read-only concrete type or wrapper.这意味着我编写的代码返回 List-as-IReadOnly*,例如,很少选择实际的只读具体类型或包装器。 Using IEnumerable.ToList is sufficient to return an IReadOnly[List|Collection] - calling List.AsReadOnly adds little value against “clever” programmers who can still access the underlying list that the ReadOnlyCollection wraps .使用 IEnumerable.ToList足以返回 IReadOnly[List|Collection] - 调用 List.AsReadOnly 对仍然可以访问 ReadOnlyCollection包装的底层列表的“聪明”程序员来说几乎没有什么价值。

In all cases, I guarantee that the concrete types of IReadOnly* return values are eager.在所有情况下,我保证 IReadOnly* 返回值的具体类型是急切的。 If I ever write a method that returns an IEnumerable, it is specifically because the contract of the method is that which “supports streaming” fsvo.如果我曾经编写过返回 IEnumerable 的方法,那特别是因为该方法的契约是“支持流”fsvo 的契约。

As far as IReadOnlyList and IReadOnlyCollection, I use the former when there is 'an' implied stable ordering established that is meaningful to index , regardless of purposeful sorting.就 IReadOnlyList 和 IReadOnlyCollection 而言,当建立了对 index 有意义的“一个”隐含稳定排序时,我使用前者,而不管有目的的排序。 For example, arrays and Lists can be returned as an IReadOnlyList while a HashSet would better be returned as an IReadOnlyCollection.例如,数组和列表可以作为 IReadOnlyList 返回,而 HashSet 最好作为 IReadOnlyCollection 返回。 The caller can always assign the I[ReadOnly]List to an I[ReadOnly]Collection as desired: this choice is about the contract exposed and not what a programmer, “clever” or otherwise, will do.调用者总是可以根据需要将 I[ReadOnly]List 分配给 I[ReadOnly]Collection:这个选择是关于公开的契约,而不是程序员,“聪明的”或其他人会做什么。

!! !! IEnumerable vs IReadOnlyList !! IEnumerable vs IReadOnlyList !!

IEnumerable has been with us from the beginning of time. IEnumerable 从一开始就与我们同在。 For many years, it was a de facto standard way to represent a read-only collection.多年来,它是表示只读集合的​​事实上的标准方式。 Since .NET 4.5, however, there is another way to do that: IReadOnlyList.但是,从 .NET 4.5 开始,还有另一种方法可以做到这一点:IReadOnlyList。

Both collection interfaces are useful.两个集合接口都很有用。

<> <>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM