Nice programing

목록 멤버 노출을위한 IEnumerable vs IReadonlyCollection vs ReadonlyCollection

nicepro 2021. 1. 7. 21:18
반응형

목록 멤버 노출을위한 IEnumerable vs IReadonlyCollection vs ReadonlyCollection


나는 명부 회원을 노출하는 주제를 숙고하는데 꽤 많은 시간을 보냈다. 저와 비슷한 질문에서 John Skeet은 훌륭한 대답을했습니다. 부담없이 봐주세요.

구성원 컬렉션을 노출하기위한 ReadOnlyCollection 또는 IEnumerable?

나는 일반적으로 목록을 노출하는 데 꽤 편집증 적이며, 특히 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의 유연성을 유지하고 아주 멋지게 캡슐화되었다고 생각합니다.

내 아이디어에 대한 의견을 얻기 위해이 질문을 게시했습니다. IEnumerable보다이 솔루션을 선호합니까? ReadOnlyCollection의 구체적인 반환 값을 사용하는 것이 더 낫다고 생각하십니까? 이것은 상당한 논쟁이며 우리 모두가 생각 해낼 수있는 장단점이 무엇인지 알아보고 싶습니다.

귀하의 의견에 미리 감사드립니다.

편집하다

우선 여기 토론에 많은 기여를 해주셔서 감사합니다. 저는 한 사람 한 사람에게서 확실히 많은 것을 배웠으며 진심으로 감사드립니다.

몇 가지 추가 시나리오와 정보를 추가하고 있습니다.

IReadOnlyCollection 및 IEnumerable에는 몇 가지 일반적인 함정이 있습니다.

아래 예를 고려하십시오.

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

위의 예는 인터페이스가 읽기 전용 임에도 불구하고 목록으로 다시 캐스팅되고 변형 될 수 있습니다. 인터페이스는 이름이 같지만 불변성을 보장하지 않습니다. 변경 불가능한 솔루션을 제공하는 것은 사용자의 몫이므로 새 ReadOnlyCollection을 반환해야합니다. 새 목록 (본질적으로 복사본)을 만들면 개체의 상태가 안전하고 건전합니다.

Richiban은 자신의 의견에서이를 가장 잘 설명합니다. 인터페이스는 할 수없는 것이 아니라 할 수있는 것을 보장합니다.

예는 아래를 참조하십시오.

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.

Carsten Führmann makes a fantastic point about yield return statements in IEnumerables.

Thank you all once again.


Talking about class libraries, I think IReadOnly* is really useful, and I think you're doing it right :)

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.

If you check other today languages, such as objective-c, you will see that in fact the rules are completely inverted! 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 :)

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).


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", e.g. a collection built with "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. (The productivity tool "Resharper" actually points this out as a code smell.)

By contrast, an IReadOnlyCollection<T> signals to the caller that there will be no lazy evaluation. (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.)

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. 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. If the caller only has an IEnumerable<T> at hand, he only needs to add .ToArray() or .ToList() to the parameter.

What IReadOnlyCollection does not do is prevent the caller to cast to some other collection type. For such protection, one would have to use the class 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.


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

ReferenceURL : https://stackoverflow.com/questions/24880268/ienumerable-vs-ireadonlycollection-vs-readonlycollection-for-exposing-a-list-mem

반응형