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 с помощью адаптера.

Другие вопросы по тегам