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

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

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

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

Вы также можете увидеть readonlycollection-or-ienumerable-for-exposing-member-collection /

nawfal 11.11.2013 15:02
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
21
1
21 797
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Ответ принят как подходящий

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

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

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

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

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

alastairs 11.09.2008 13:17

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

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

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

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

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

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

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

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

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

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

List<string> _Data;
public IEnumerable<string> Data
{
  get
  {
    foreach(string item in _Data)
    {
      return yield item;
    }
  }
}

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

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

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

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

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

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

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

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

Не согласен. Пока модель предметной области хороша, «оплатить все неоплаченные счета» или аналогичные операции в linq тривиальны. Размещение логики в коллекциях обычно приводит к размытым обязанностям

DarkWanderer 03.10.2015 00:41

И где вы предлагаете разместить логику, которая управляет несколькими экземплярами объекта из разных мест без копирования и вставки? Такие системы, как Linq, могут пугать, потому что они позволяют просто скопировать и вставить небольшой фрагмент бизнес-логики, а не положить его куда-нибудь, чтобы его можно было повторно использовать. Теперь у вас есть 10 копий крошечной ошибки вместо простого написания простого метода. Когда вы исправляете ошибку, как вы находите остальные? Может текстовый поиск сработает, а может и нет. НИКОГДА не копируйте бизнес-логику, определение СУХОЙ. Краткость - это не одно и то же.

Bill K 03.10.2015 00:46

Я ничего не говорил о копировании бизнес-логики. Ваше предложение, с другой стороны, пахнет нарушением SRP - «логика, которая управляет несколькими объектами из разных мест», должна быть в классе собственный отдельный

DarkWanderer 06.10.2015 18:30

Да, и место для размещения логики, которая управляет коллекцией, находится в «Держателе коллекции», например, если у вас есть коллекция счетов-фактур и вы хотите получить «закрытые» счета-фактуры, вы не просто перебираете коллекцию и выбираете " Закрыто », потому что это бизнес-логика и может измениться на« Закрыто »|| «Платный», и вы не помещаете его в какую-то общую утилиту - вы помещаете его в коллекцию - если ваша коллекция передается голой без класса, куда вы ее помещаете? После того, как вы создали класс «Invoice Holder», тогда иметь внутри выражения linq для выбора из содержащейся коллекции - замечательно.

Bill K 06.10.2015 19:15

Я должен квалифицировать это с концепцией разработки скриптов и крошечных приложений по сравнению с более крупными приложениями. Если вы являетесь единственным потребителем своего собственного кода, то распространение бизнес-логики по всему коду в форме выражений linq вполне нормально, это становится проблемой, когда вам нужно поделиться своим кодом с другими - например, когда вы изменяете свой Выражение linq должно быть закрыто || оплачивается, потому что оно приходит к вам в отчете об ошибке, но вы пропускаете остальные 4 копии, распределенные по вашей кодовой базе, потому что его реализация в linq была «тривиальной» и не было очевидного класса для размещения логики.

Bill K 06.10.2015 19:40

Я разрабатываю большие финансовые приложения, поэтому я не «единственный потребитель кода». И ответ прост - коллекции не должны существовать в коде без какого-либо «владельца». «InvoiceCollection» бессмысленна. Должны быть некоторые InvoiceRepository и InvoicePayOperation - первый из которых владеет коллекцией, второй содержит бизнес-логику (и становится владельцем переданной ему временной коллекции). Конечно, если вы привыкли к дублированию кода и «голым коллекциям», как вы говорите, вы не сможете увидеть, как этого можно избежать.

DarkWanderer 10.10.2015 20:00

Кроме того, в предоставленном случае лучший центр знаний о том, можно ли оплатить счет-фактуру сам счет-фактура, поэтому у него должно быть свойство CanBePaid - так, немного сообразительности избавляет от необходимости менять «в 4-х местах»

DarkWanderer 10.10.2015 20:04

Здесь нужно согласиться с DarkWanderer. Коллекции должны быть просто коллекциями. Есть много других способов централизовать бизнес-логику без нарушения принципа единой ответственности. К тому же, если вы сделаете вашу коллекцию более «умной», это может скорее запутать разработчика, чем помочь ему. Наличие такого метода, как PayUnpaidInvoices, может привести к второму предположению о том, что могут делать «обычные» методы сбора, такие как Remove и Clear. Будет ли он просто удалять счета из коллекции в памяти или удаляет их из базы данных?

Crono 31.08.2016 16:48

@DarkWanderer, вы предполагаете, что инкапсуляция делает SRP недействительным? Коллекция (отвечающая за хранение списка вещей - не бизнес-функция, но все еще действующая в качестве полезности) содержится внутри чего-то, отвечающего за управление счетами, которое может содержаться в модуле, управляющем функциями учета. Это вовсе не нарушение SRP, а принцип работы инкапсуляции. Передача коллекции другому классу для работы с ней нарушает инкапсуляцию и правило, согласно которому вы «просите объект сделать что-то с его данными, а не запрашивать его данные», которое сопровождается инкапсуляцией.

Bill K 22.09.2016 22:45

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