Вернуть коллекцию как доступную только для чтения

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

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

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


person alastairs    schedule 10.09.2008    source источник
comment
Вы также можете увидеть readonlycollection-or-ienumerable-for- разоблачение-член-коллекции /   -  person nawfal    schedule 11.11.2013


Ответы (7)


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

person aku    schedule 10.09.2008

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

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

person nedruod    schedule 11.09.2008
comment
IEnumerable на самом деле звучит как хороший путь вперед. Как вы предполагаете, я намерен только защитить коллекцию от прямого изменения; члены коллекции не будут изменены (или должны быть изменены). - person alastairs; 11.09.2008

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

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

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

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

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

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

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

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

person Bill K    schedule 18.12.2009
comment
Не согласен. Пока модель предметной области хороша, оплата всех неоплаченных счетов или аналогичные операции в linq тривиальны. Размещение логики в коллекциях обычно приводит к размытым обязанностям - person DarkWanderer; 03.10.2015
comment
И где вы предлагаете разместить логику, которая управляет несколькими экземплярами объекта из разных мест без копирования и вставки? Такие системы, как Linq, могут быть пугающими, потому что они позволяют просто скопировать и вставить небольшой фрагмент бизнес-логики, а не положить его куда-нибудь, чтобы его можно было повторно использовать. Теперь у вас есть 10 копий крошечной ошибки, а не просто написание простого метода. Когда вы исправляете ошибку, как вы находите остальные? Может текстовый поиск сработает, а может и нет. НИКОГДА не копируйте бизнес-логику, определение СУХОГО. Краткость - это не одно и то же. - person Bill K; 03.10.2015
comment
Я ничего не говорил о копировании бизнес-логики. Ваше предложение, с другой стороны, пахнет нарушением SRP - логика, которая управляет несколькими объектами из разных мест, должна быть в собственном отдельном классе - person DarkWanderer; 06.10.2015
comment
Да, и место для размещения логики, которая управляет коллекцией, находится в держателе коллекции, например, если у вас есть коллекция счетов-фактур и вы хотите получить закрытые счета-фактуры, вы не просто перебираете коллекцию и выбираете Closed, потому что это бизнес-логика и может измениться на "Закрыто" || Платный, и вы не помещаете его в какую-то общую утилиту - вы помещаете его в коллекцию - если ваша коллекция передается голой без класса, куда вы ее помещаете? После того, как вы создали класс Invoice Holder, тогда иметь внутри выражения linq для выбора из содержащейся коллекции - это прекрасно. - person Bill K; 06.10.2015
comment
Я должен квалифицировать это с концепцией разработки скриптов и крошечных приложений по сравнению с более крупными приложениями. Если вы являетесь единственным потребителем своего собственного кода, то распространение бизнес-логики по всему коду в виде выражений linq вполне нормально, когда вам нужно поделиться своим кодом с другими, это становится проблемой - например, когда вы изменяете свой Выражение linq должно быть закрыто || оплачивается, потому что оно приходит к вам в отчете об ошибке, но вы пропускаете остальные 4 копии, распределенные по вашей кодовой базе, потому что реализация его в linq была тривиальной, и не было очевидного класса для размещения логики. - person Bill K; 06.10.2015
comment
Я разрабатываю большие финансовые приложения, поэтому я не единственный пользователь кода. И ответ прост - коллекции не должны существовать в коде без какого-либо владельца. InvoiceCollection бессмысленна. Должны быть некоторые InvoiceRepository и InvoicePayOperation - первый из которых владеет коллекцией, второй содержит бизнес-логику (и становится владельцем переданной ему временной коллекции). Конечно, если вы привыкли к дублированию кода и голым коллекциям, как вы говорите, вы не сможете увидеть, как этого можно избежать. - person DarkWanderer; 10.10.2015
comment
Кроме того, в представленном случае лучший центр знаний о том, может ли счет быть оплачен - это сам счет, поэтому у него должно быть свойство CanBePaid - поэтому немного сообразительности устраняет необходимость изменения в 4 местах - person DarkWanderer; 10.10.2015
comment
Здесь нужно согласиться с DarkWanderer. Коллекции должны быть просто коллекциями. Есть много других способов централизовать бизнес-логику без нарушения принципа единой ответственности. К тому же, если вы сделаете свою коллекцию более умной, она может больше сбить с толку разработчика, чем помочь ему. Наличие такого метода, как PayUnpaidInvoices, может привести к второму предположению о том, что могут делать обычные методы сбора, такие как Remove и Clear. Будет ли он просто удалять счета из коллекции в памяти или удаляет их из базы данных? - person Crono; 31.08.2016
comment
@DarkWanderer, вы предполагаете, что инкапсуляция делает SRP недействительным? Коллекция (отвечающая за хранение списка вещей - не бизнес-функция, но все еще действующая в качестве полезности) содержится внутри чего-то, отвечающего за управление счетами, которое может содержаться в модуле, управляющем функциями бухгалтерского учета. Это вовсе не нарушение SRP, а принцип работы инкапсуляции. Передача коллекции другому классу для работы с ней нарушает инкапсуляцию, и правило, согласно которому вы просите объект сделать что-то с его данными, не запрашивайте данные, которые идут вместе с инкапсуляцией. - person Bill K; 22.09.2016

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

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

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

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

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

person Daniel Fortunov    schedule 12.09.2008

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

person Kris Erickson    schedule 11.09.2008

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

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

List<string> _Data;
public IEnumerable<string> Data
{
  get
  {
    foreach(string item in _Data)
    {
      return yield item;
    }
  }
}
person Charles Graham    schedule 21.09.2008

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

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

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

person Dror Helper    schedule 21.09.2008