ReadOnlyCollection или IEnumerable для демонстрации коллекций членов?
Есть ли причина выставлять внутреннюю коллекцию как коллекцию ReadOnlyCollection, а не как IEnumerable, если вызывающий код выполняет итерацию только по коллекции?
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 достаточно, то это тот, который нужно использовать. Это правильный способ рассуждать об этом или я что-то упустил?
Спасибо / Эрик
5 ответов
Более современное решение
Если вам не нужна внутренняя коллекция для изменчивости, вы можете использовать System.Collections.Immutable
пакет, измените свой тип поля, чтобы быть неизменной коллекцией, а затем выставьте это непосредственно - при условии Foo
Сам по себе, конечно, неизменен.
Обновленный ответ для более прямого решения вопроса
Есть ли причина выставлять внутреннюю коллекцию как коллекцию ReadOnlyCollection, а не как IEnumerable, если вызывающий код выполняет итерацию только по коллекции?
Это зависит от того, насколько вы доверяете вызывающему коду. Если вы полностью контролируете все, что когда-либо вызовет этот участник, и вы гарантируете, что ни один код никогда не будет использовать:
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
over Select/ Где то, что для каждой итерации бессмысленно выполнять бессмысленно.)
Если вы не используете.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. Смотрите этот пример:
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 с помощью адаптера.