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


124

호출 코드가 컬렉션을 반복하는 경우에만 내부 컬렉션을 IEnumerable이 아닌 ReadOnlyCollection으로 노출하는 이유가 있습니까?

class Bar
{
    private ICollection<Foo> foos;

    // Which one is to be preferred?
    public IEnumerable<Foo> Foos { ... }
    public ReadOnlyCollection<Foo> Foos { ... }
}


// Calling code:

foreach (var f in bar.Foos)
    DoSomething(f);

내가보기에 IEnumerable은 ReadOnlyCollection 인터페이스의 하위 집합이며 사용자가 컬렉션을 수정할 수 없도록합니다. 따라서 IEnumberable 인터페이스가 충분하면 사용할 인터페이스입니다. 그게 적절한 추론 방법입니까, 아니면 뭔가 놓치고 있습니까?

감사합니다 / Erik


6
.NET 4.5를 사용하는 경우 새로운 읽기 전용 컬렉션 인터페이스 를 사용해 볼 수 있습니다 . ReadOnlyCollection편집증이 있는 경우 반환 된 컬렉션을 계속 래핑하고 싶지만 이제는 특정 구현에 묶여 있지 않습니다.
StriplingWarrior

답변:


96

더 현대적인 솔루션

내부 컬렉션을 변경할 수 있어야하는 경우가 아니라면 System.Collections.Immutable패키지를 사용하고 필드 유형을 변경 불가능한 컬렉션으로 변경 한 다음 직접 노출 할 수 있습니다 Foo. 물론 자체가 변경 불가능 하다고 가정 합니다.

질문을보다 직접적으로 해결하기 위해 업데이트 된 답변

호출 코드가 컬렉션을 반복하는 경우에만 내부 컬렉션을 IEnumerable이 아닌 ReadOnlyCollection으로 노출하는 이유가 있습니까?

호출 코드를 얼마나 신뢰하는지에 따라 다릅니다. 이 멤버를 호출하는 모든 것을 완벽하게 제어하고 코드가 다음을 사용하지 않는다는 것을 보장 하는 경우 :

ICollection<Foo> evil = (ICollection<Foo>) bar.Foos;
evil.Add(...);

그런 다음 컬렉션을 직접 반환하면 아무런 해가되지 않습니다. 나는 일반적으로 그것보다 좀 더 편집증 적이 되려고 노력합니다.

마찬가지로, 당신이 말했듯이 : 당신이 단지 필요한 경우 IEnumerable<T>왜 자신을 더 강한 것에 묶어 두십니까?

원래 답변

.NET 3.5 를 사용하는 경우 Skip에 대한 간단한 호출을 사용하여 복사 피하고 단순 캐스트를 피할 수 있습니다 .

public IEnumerable<Foo> Foos {
    get { return foos.Skip(0); }
}

(사소하게 래핑 할 수있는 다른 많은 옵션이 있습니다. SkipSelect / where 에 대한 좋은 점은 각 반복에 대해 무의미하게 실행할 델리게이트가 없다는 것입니다.)

.NET 3.5를 사용하지 않는 경우 매우 간단한 래퍼를 작성하여 동일한 작업을 수행 할 수 있습니다.

public static IEnumerable<T> Wrapper<T>(IEnumerable<T> source)
{
    foreach (T element in source)
    {
        yield return element;
    }
}

15
이에 대한 성능 패널티가 있습니다. AFAIK Enumerable.Count 메서드는 IEnumerables로 캐스팅 된 컬렉션에 대해 최적화되었지만 Skip (0)에 의해 생성 된 범위에는 최적화되지 않았습니다
shojtsy

6
-1 그것은 나만입니까, 아니면 다른 질문에 대한 대답입니까? 이 "솔루션"을 아는 것이 유용하지만 오퍼레이션은 ReadOnlyCollection과 IEnumerable을 반환하는 것 사이의 비교를 요청했습니다. 이 답변은 이미 해당 결정을 지원할 이유없이 IEnumerable을 반환하고 싶다고 가정합니다.
Zaid Masud

1
대답은 a ReadOnlyCollection를로 캐스팅 한 ICollection다음 Add성공적으로 실행 하는 것을 보여줍니다 . 내가 아는 한 그것은 불가능합니다. 는 evil.Add(...)오류가 발생합니다. dotnetfiddle.net/GII2jW
Shaun Luttin 2015-10-26

1
@ShaunLuttin : 네, 정확합니다. 하지만 내 대답에, 나는 캐스팅하지 않습니다 ReadOnlyCollection- 나는 캐스트 IEnumerable<T>실제로 읽기 전용되지 않는 ... 그것의 대신 노출의ReadOnlyCollection
존 소총

1
아하. 귀하의 편집증은 사용하는 당신을 이끌 것 ReadOnlyCollection대신의를 IEnumerable악한 캐스팅을 방지하기 위해.
Shaun Luttin 2015 년

43

컬렉션을 반복해야하는 경우 :

foreach (Foo f in bar.Foos)

그런 다음 IEnumerable 을 반환 하면 충분합니다.

항목에 대한 임의 액세스가 필요한 경우 :

Foo f = bar.Foos[17];

그런 다음 ReadOnlyCollection으로 래핑하십시오 .


@Vojislav Stojkovic, +1. 이 조언이 오늘날에도 적용됩니까?
w0051977

1
@ w0051977 그것은 당신의 의도에 달려 있습니다. System.Collections.ImmutableJon Skeet이 권장하는대로 사용 하는 것은 참조를 얻은 후 절대 변경되지 않을 것을 노출 할 때 완벽하게 유효합니다. 반면에 변경 가능한 컬렉션의 읽기 전용 뷰를 노출하는 경우이 조언은 여전히 ​​유효합니다 IReadOnlyCollection.
Vojislav Stojkovic

@VojislavStojkovic bar.Foos[17]와 함께 사용할 수 없습니다 IReadOnlyCollection. 유일한 차이점은 추가 Count속성입니다. public interface IReadOnlyCollection<out T> : IEnumerable<T>, IEnumerable
Konrad는

당신이 필요로하는 경우 @Konrad 오른쪽, bar.Foos[17]당신은으로 노출해야 ReadOnlyCollection<Foo>. 크기를 알고 반복하려면 IReadOnlyCollection<Foo>. 크기를 알 필요가 없으면 IEnumerable<Foo>. 다른 솔루션과 기타 세부 사항이 있지만이 특정 답변에 대한 논의 범위를 벗어납니다.
Vojislav Stojkovic 19

31

이렇게하면 호출자가 IEnumerable을 ICollection으로 다시 캐스팅 한 다음 수정하는 것을 막을 수 없습니다. ReadOnlyCollection은 리플렉션을 통해 기본 쓰기 가능한 컬렉션에 액세스 할 수 있지만 이러한 가능성을 제거합니다. 컬렉션이 작은 경우이 문제를 해결하는 안전하고 쉬운 방법은 대신 복사본을 반환하는 것입니다.


14
그것은 내가 진심으로 동의하지 않는 일종의 "편집증적인 디자인"이다. 정책을 시행하기 위해 부적절한 의미를 선택하는 것은 나쁜 습관이라고 생각합니다.
Vojislav Stojkovic

24
동의하지 않는다고해서 틀린 것은 아닙니다. 외부 배포를위한 라이브러리를 설계하는 경우 API를 오용하려는 사용자가 제기 한 버그를 처리하는 것보다 편집증적인 인터페이스를 선호합니다. msdn.microsoft.com/en-us/library/k2604h5s(VS.71).aspx
Stu Mackellar

5
예, 외부 배포를 위해 라이브러리를 디자인하는 특정 경우에 노출되는 모든 것에 대한 편집증적인 인터페이스를 갖는 것이 합리적입니다. 그렇더라도 Foos 속성의 의미가 순차 액세스 인 경우 ReadOnlyCollection을 사용한 다음 IEnumerable을 반환합니다. 잘못된 의미를 사용할 필요가 없습니다;)
Vojislav Stojkovic

9
당신도 할 필요가 없습니다. 당신은 ... 복사하지 않고-만 읽기 반복자를을 반환 할 수 있습니다
존 소총을

1
@shojtsy 코드 줄이 작동하지 않는 것 같습니다. asnull 생성합니다 . 캐스트에서 예외가 발생합니다.
Nick Alexeev

3

가능한 한 ReadOnlyCollection을 사용하지 않습니다. 실제로 일반 목록을 사용하는 것보다 상당히 느립니다. 이 예를 참조하십시오.

List<int> intList = new List<int>();
        //Use a ReadOnlyCollection around the List
        System.Collections.ObjectModel.ReadOnlyCollection<int> mValue = new System.Collections.ObjectModel.ReadOnlyCollection<int>(intList);

        for (int i = 0; i < 100000000; i++)
        {
            intList.Add(i);
        }
        long result = 0;

        //Use normal foreach on the ReadOnlyCollection
        TimeSpan lStart = new TimeSpan(System.DateTime.Now.Ticks);
        foreach (int i in mValue)
            result += i;
        TimeSpan lEnd = new TimeSpan(System.DateTime.Now.Ticks);
        MessageBox.Show("Speed(ms): " + (lEnd.TotalMilliseconds - lStart.TotalMilliseconds).ToString());
        MessageBox.Show("Result: " + result.ToString());

        //use <list>.ForEach
        lStart = new TimeSpan(System.DateTime.Now.Ticks);
        result = 0;
        intList.ForEach(delegate(int i) { result += i; });
        lEnd = new TimeSpan(System.DateTime.Now.Ticks);
        MessageBox.Show("Speed(ms): " + (lEnd.TotalMilliseconds - lStart.TotalMilliseconds).ToString());
        MessageBox.Show("Result: " + result.ToString());

15
조기 최적화. 내 측정에 따르면 ReadOnlyCollection을 반복하는 것은 for 루프 내에서 아무것도 하지 않는다고 가정하면 일반 목록보다 약 36 % 더 오래 걸립니다 . 이 차이를 결코 눈치 채지 못할 것입니다.하지만 이것이 코드의 핫스팟이고 마지막 모든 성능을 필요로한다면, 대신 배열을 사용하는 것이 어떻습니까? 그것은 여전히 ​​더 빠를 것입니다.
StriplingWarrior 2011

벤치 마크에 결함이 있으며 분기 예측을 완전히 무시하고 있습니다.
Trojaner

0

때로는 단위 테스트 중에 컬렉션을 모의하고 싶기 때문에 인터페이스를 사용하고 싶을 수 있습니다. 어댑터를 사용하여 ReadonlyCollection에 고유 한 인터페이스를 추가 하려면 내 블로그 항목 을 참조하십시오 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.