Возврат коллекции только для чтения - PullRequest
18 голосов
/ 11 сентября 2008

У меня есть объект в многопоточной среде, которая поддерживает сбор информации, например ::

public IList<string> Data 
{
    get 
    {
        return data;
    }
}

В настоящее время у меня есть return data;, обернутый ReaderWriterLockSlim для защиты коллекции от нарушений обмена. Однако, чтобы быть вдвойне уверенным, я бы хотел вернуть коллекцию только для чтения, чтобы вызывающий код не мог вносить изменения в коллекцию, а только просматривать то, что уже есть. Это вообще возможно?

Ответы [ 7 ]

28 голосов
/ 11 сентября 2008

Если ваши базовые данные хранятся в виде списка, вы можете использовать метод List (T) .AsReadOnly .
Если ваши данные могут быть перечислены, вы можете использовать метод Enumerable.ToList для приведения вашей коллекции в List и вызова AsReadOnly для нее.

19 голосов
/ 19 декабря 2009

Я проголосовал за ваш принятый ответ и согласен с ним - однако могу ли я дать вам кое-что рассмотреть?

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

Основным преимуществом этого является то, что вы не можете добавлять код в коллекции, поэтому, когда у вас есть собственная «коллекция» в вашей объектной модели, вы ВСЕГДА имеете не-OO-код поддержки, распространяющийся по всему проекту для доступа к нему. .

Например, если бы ваша коллекция была счетами, у вас, вероятно, было бы 3 или 4 места в вашем коде, где вы просматривали неоплаченные счета. Вы можете иметь метод getUnpaidInvoices. Однако настоящая сила приходит, когда вы начинаете думать о таких методах, как «payUnpaidInvoices (payer, account);».

Когда вы передаете коллекции вместо написания объектной модели, вам никогда не придет в голову целый класс рефакторинга.

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

Как вы решаете эту проблему, когда передаете нативную коллекцию?

Кроме того, собственные коллекции не могут быть расширены за счет дополнительных данных. Вы узнаете это в следующий раз, когда обнаружите, что передаете (Collection, Extra) более чем одному или двум методам. Это означает, что «Extra» принадлежит объекту, содержащему вашу коллекцию.

19 голосов
/ 11 сентября 2008

Если ваше единственное намерение состоит в том, чтобы получить вызывающий код, чтобы не допустить ошибки, и изменить коллекцию, когда она должна только читать все, что необходимо, - вернуть интерфейс, который не поддерживает Добавить, Удалить и т. Д. Почему не вернуть IEnumerable<string>? Вызывать код вызова пришлось бы, что они вряд ли будут делать, не зная внутренностей объекта, к которому они обращаются.

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

11 голосов
/ 12 сентября 2008

Я думаю, что вы путаете понятия здесь.

ReadOnlyCollection предоставляет оболочку только для чтения для существующей коллекции, что позволяет вам (класс A) передать ссылку на сейф коллекции, зная, что вызывающая сторона (класс B) не может изменить коллекцию (то есть не может добавить или удалить все элементы из коллекции.)

Абсолютно нет гарантий безопасности ниток.

  • Если вы (класс A) продолжите изменять базовую коллекцию после того, как вы передадите ее как ReadOnlyCollection, тогда класс B увидит эти изменения, аннулирует все итераторы и т. Д. И, как правило, будет открыт для любого обычного параллелизма. проблемы с коллекциями.
  • Кроме того, если элементы в коллекции являются изменяемыми, то вы (класс A) и вызывающий объект (класс B) сможете изменить любое изменяемое состояние объектов в коллекции.

Ваша реализация зависит от ваших потребностей: - Если вы не заботитесь о том, чтобы вызывающий абонент (класс B) увидел какие-либо дальнейшие изменения в коллекции, вы можете просто клонировать коллекцию, передать ее и прекратить заботу. - Если вам определенно нужен вызывающий объект (класс B), чтобы увидеть изменения, внесенные в коллекцию, и вы хотите, чтобы это было поточно-ориентированным, то у вас больше проблем. Одна из возможностей - реализовать свой собственный потокобезопасный вариант ReadOnlyCollection, чтобы разрешить заблокированный доступ, хотя это будет нетривиально и бесполезно, если вы хотите поддерживать IEnumerable, и он все равно не защитит Вы против изменчивых элементов в коллекции.

3 голосов
/ 21 сентября 2008

Вы хотите использовать ключевое слово yield . Вы перебираете список IEnumerable и возвращаете результаты с помощью yeild. Это позволяет потребителю использовать для каждого без изменения коллекции.

Это будет выглядеть примерно так:

List<string> _Data;
public IEnumerable<string> Data
{
  get
  {
    foreach(string item in _Data)
    {
      return yield item;
    }
  }
}
3 голосов
/ 11 сентября 2008

Следует отметить, что ответ aku защитит только список только для чтения. Элементы в списке все еще очень доступны для записи. Я не знаю, существует ли какой-либо способ защиты неатомных элементов без их клонирования, прежде чем помещать их в список только для чтения.

2 голосов
/ 21 сентября 2008

Вместо этого вы можете использовать копию коллекции.

public IList<string> Data {
get {
    return new List<T>(data);
}}

Таким образом, не имеет значения, будет ли оно обновлено.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...