Как разрешить итерации по частной коллекции, но не модифицировать? - PullRequest
6 голосов
/ 19 февраля 2010

Если у меня есть следующий ученик:

private List<object> obs;

и я хочу разрешить обход этого списка как часть интерфейса класса, как бы я это сделал?

Публикация не будет работать, потому что я не хочу, чтобы список был изменен напрямую.

Ответы [ 8 ]

13 голосов
/ 19 февраля 2010

Вы бы выставили его как IEnumerable<T>, а не просто возвращали его напрямую:

public IEnumerable<object> Objects { get { return obs.Select(o => o); } }

Поскольку вы указали, что вам нужен только обход списка, это все, что вам нужно.

Может возникнуть соблазн вернуть List<object> непосредственно как IEnumerable<T>, но это было бы неправильно, поскольку можно легко проверить IEnumerable<T> во время выполнения, определить, что это List<T>, и привести его к и мутировать содержимое.

Однако, используя return obs.Select(o => o);, вы в конечном итоге возвращаете итератор для List<object>, а не прямую ссылку на сам List<object>.

Некоторые могут подумать, что это квалифицируется как «вырожденное выражение» в соответствии с разделом 7.15.2.5 Спецификации языка C #. Тем не менее, Эрик Липперт подробно описывает, почему эта проекция не оптимизирована .

Кроме того, люди предлагают использовать метод расширения AsEnumerable . Это неверно, так как ссылочная идентичность исходного списка сохраняется. Из раздела «Замечания» документации:

Метод AsEnumerable<TSource>(IEnumerable<TSource>) не имеет никакого другого эффекта, кроме как изменить тип источника времени компиляции с типа, который реализует IEnumerable<T> на IEnumerable<T>.

Другими словами, все, что он делает, это приводит параметр источника к IEnumerable<T>, что не помогает защитить ссылочную целостность, возвращается исходная ссылка и может быть возвращена к List<T> и использоваться для изменения списка .

11 голосов
/ 19 февраля 2010

Вы можете использовать ReadOnlyCollection или сделать копию List и вернуть ее взамен (учитывая снижение производительности операции копирования).Вы также можете использовать List<T>.AsReadOnly.

3 голосов
/ 19 февраля 2010

Это уже было сказано, но я не вижу ни одного из ответов как неясных.

Самый простой способ - просто вернуть ReadOnlyCollection

private List<object> objs;

public ReadOnlyCollection<object> Objs {
    get {
        return objs.AsReadOnly();
    }
}

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

public IEnumerable<object> Objs { 
    get {
        return objs.AsReadOnly();
    }
}

. Обратите внимание, что вам не нужно вызывать AsReadOnly () для компиляции этого кода.Но если вы этого не сделаете, вызывающая сторона может просто вернуть возвращаемое значение обратно в список и изменить ваш список.

// Bad caller code
var objs = YourClass.Objs;
var list = objs as List<object>;
list.Add(new object); // They have just modified your list.

С этим решением также существует потенциальная проблема

public IEnumerable<object> Objs { 
    get { 
        return objs.AsEnumerable(); 
    } 
}

Поэтому я определенно рекомендую вам вызвать AsReadOnly () в вашем списке и вернуть это значение.

2 голосов
/ 19 февраля 2010

В вашем интерфейсе добавьте следующую сигнатуру метода: public IEnumerable TraverseTheList ()

Предполагается, что так:

public IEnumerable<object> TraverseTheList()
{

    foreach( object item in obj)
    {
      yield return item;
    }


}

, что позволит вам сделать следующее:

foreach(object item in Something.TraverseTheList())
{
// do something to the item
}

Возвращаемое значение указывает компилятору создать для вас перечислитель.

2 голосов
/ 19 февраля 2010

Вы можете сделать это двумя способами:

  1. Либо путем преобразования списка в коллекцию только для чтения:

    new System.Collections.ObjectModel.ReadOnlyCollection<object>(this.obs)
    
  2. Иливозвращая IEnumerable предметов:

    this.obs.AsEnumerable()
    
1 голос
/ 19 февраля 2010

Интересный пост и диалог по этому вопросу: http://davybrion.com/blog/2009/10/stop-exposing-collections-already/.

1 голос
/ 19 февраля 2010

Expose a ReadOnlyCollection<T>

0 голосов
/ 19 февраля 2010

Рассматривали ли вы получение класса из System.Collections.ReadOnlyCollectionBase?

...