호출 코드가 컬렉션을 반복하는 경우에만 내부 컬렉션을 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
답변
더 현대적인 솔루션
내부 컬렉션을 변경할 수 있어야하는 경우가 아니라면 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); }
}
(사소하게 래핑 할 수있는 다른 많은 옵션이 있습니다. Skip
Select / where 에 대한 좋은 점은 각 반복에 대해 무의미하게 실행할 델리게이트가 없다는 것입니다.)
.NET 3.5를 사용하지 않는 경우 매우 간단한 래퍼를 작성하여 동일한 작업을 수행 할 수 있습니다.
public static IEnumerable<T> Wrapper<T>(IEnumerable<T> source)
{
foreach (T element in source)
{
yield return element;
}
}
답변
컬렉션을 반복해야하는 경우 :
foreach (Foo f in bar.Foos)
그런 다음 IEnumerable 을 반환 하면 충분합니다.
항목에 대한 임의 액세스가 필요한 경우 :
Foo f = bar.Foos[17];
그런 다음 ReadOnlyCollection으로 래핑하십시오 .
답변
이렇게하면 호출자가 IEnumerable을 ICollection으로 다시 캐스팅 한 다음 수정하는 것을 막을 수 없습니다. ReadOnlyCollection은 리플렉션을 통해 기본 쓰기 가능한 컬렉션에 액세스 할 수 있지만 이러한 가능성을 제거합니다. 컬렉션이 작은 경우이 문제를 해결하는 안전하고 쉬운 방법은 대신 복사본을 반환하는 것입니다.
답변
가능한 한 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());
답변
때로는 단위 테스트 중에 컬렉션을 모의하고 싶기 때문에 인터페이스를 사용하고 싶을 수 있습니다. 어댑터를 사용하여 ReadonlyCollection에 고유 한 인터페이스를 추가 하려면 내 블로그 항목 을 참조하십시오 .
답변
