ReadOnlyCollection или IEnumerable для демонстрации коллекций членов? - PullRequest
113 голосов
/ 29 января 2009

Есть ли причина выставлять внутреннюю коллекцию как коллекцию 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 ]

91 голосов
/ 29 января 2009

Более современное решение

Если вам не требуется, чтобы внутренняя коллекция была изменяемой, вы можете использовать пакет 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 по сравнению с Select / Where - нет делегата для бессмысленного выполнения для каждой итерации.)

Если вы не используете .NET 3.5, вы можете написать очень простую оболочку для того же:

public static IEnumerable<T> Wrapper<T>(IEnumerable<T> source)
{
    foreach (T element in source)
    {
        yield return element;
    }
}
40 голосов
/ 29 января 2009

Если вам нужно только перебрать коллекцию:

foreach (Foo f in bar.Foos)

затем возвращается IEnumerable достаточно.

Если вам нужен произвольный доступ к предметам:

Foo f = bar.Foos[17];

затем оберните его в ReadOnlyCollection .

30 голосов
/ 29 января 2009

Если вы сделаете это, то ничто не помешает вашим вызывающим преобразовать IEnumerable обратно в ICollection и затем изменить его. ReadOnlyCollection устраняет эту возможность, хотя все еще возможно получить доступ к основной коллекции для записи через отражение. Если коллекция небольшая, то безопасный и простой способ обойти эту проблему - вместо этого вернуть копию.

3 голосов
/ 10 июня 2010

Я стараюсь не использовать 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());
0 голосов
/ 11 февраля 2009

Иногда вам может потребоваться использовать интерфейс, возможно, потому что вы хотите смоделировать коллекцию во время модульного тестирования. См. Мою запись в блоге о том, как добавить собственный интерфейс в ReadonlyCollection с помощью адаптера.

...