У меня есть объект в многопоточной среде, который поддерживает набор информации, например:
public IList<string> Data
{
get
{
return data;
}
}
В настоящее время у меня есть return data;, завернутый в ReaderWriterLockSlim, чтобы защитить коллекцию от нарушений совместного доступа. Однако, чтобы быть вдвойне уверенным, я хотел бы вернуть коллекцию как доступную только для чтения, чтобы вызывающий код не мог вносить изменения в коллекцию, а только просматривать то, что уже есть. Это вообще возможно?





Если ваши базовые данные хранятся в виде списка, вы можете использовать метод Список (T) .AsReadOnly. Если ваши данные можно перечислить, вы можете использовать метод Enumerable.ToList, чтобы привести вашу коллекцию в List и вызвать для нее AsReadOnly.
Следует отметить, что ответ аку будет защищать список только как доступный только для чтения. Элементы в списке по-прежнему доступны для записи. Я не знаю, есть ли способ защитить неатомарные элементы без их клонирования перед помещением в список только для чтения.
Если ваше единственное намерение - получить вызывающий код, чтобы не допустить ошибки, и изменить коллекцию, когда она должна только читать, все, что необходимо, - это вернуть интерфейс, который не поддерживает добавление, удаление и т. д. Почему бы не вернуть IEnumerable<string> ? Вызывающий код должен будет выполнить приведение, что они вряд ли сделают, не зная внутренней структуры свойства, к которому они обращаются.
Однако если вы намерены предотвратить отслеживание вызывающим кодом обновлений из других потоков, вам придется вернуться к уже упомянутым решениям, чтобы выполнить глубокую или неглубокую копию в зависимости от ваших потребностей.
IEnumerable на самом деле звучит как хороший путь вперед. Как вы предполагаете, я намерен только защитить коллекцию от прямого изменения; члены коллекции не будут изменены (или должны быть изменены).
Я думаю, вы здесь путаете понятия.
ReadOnlyCollection предоставляет оболочку только для чтения для существующей коллекции, позволяя вам (класс A) передавать ссылку на коллекцию, зная, что вызывающий (класс B) не может изменить коллекцию (т.е. не может Добавить или удалять какие-либо элементы из коллекции.)
Нет абсолютно никаких гарантий безопасности потоков.
ReadOnlyCollection, тогда класс 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 тривиальны. Размещение логики в коллекциях обычно приводит к размытым обязанностям
И где вы предлагаете разместить логику, которая управляет несколькими экземплярами объекта из разных мест без копирования и вставки? Такие системы, как Linq, могут пугать, потому что они позволяют просто скопировать и вставить небольшой фрагмент бизнес-логики, а не положить его куда-нибудь, чтобы его можно было повторно использовать. Теперь у вас есть 10 копий крошечной ошибки вместо простого написания простого метода. Когда вы исправляете ошибку, как вы находите остальные? Может текстовый поиск сработает, а может и нет. НИКОГДА не копируйте бизнес-логику, определение СУХОЙ. Краткость - это не одно и то же.
Я ничего не говорил о копировании бизнес-логики. Ваше предложение, с другой стороны, пахнет нарушением SRP - «логика, которая управляет несколькими объектами из разных мест», должна быть в классе собственный отдельный
Да, и место для размещения логики, которая управляет коллекцией, находится в «Держателе коллекции», например, если у вас есть коллекция счетов-фактур и вы хотите получить «закрытые» счета-фактуры, вы не просто перебираете коллекцию и выбираете " Закрыто », потому что это бизнес-логика и может измениться на« Закрыто »|| «Платный», и вы не помещаете его в какую-то общую утилиту - вы помещаете его в коллекцию - если ваша коллекция передается голой без класса, куда вы ее помещаете? После того, как вы создали класс «Invoice Holder», тогда иметь внутри выражения linq для выбора из содержащейся коллекции - замечательно.
Я должен квалифицировать это с концепцией разработки скриптов и крошечных приложений по сравнению с более крупными приложениями. Если вы являетесь единственным потребителем своего собственного кода, то распространение бизнес-логики по всему коду в форме выражений linq вполне нормально, это становится проблемой, когда вам нужно поделиться своим кодом с другими - например, когда вы изменяете свой Выражение linq должно быть закрыто || оплачивается, потому что оно приходит к вам в отчете об ошибке, но вы пропускаете остальные 4 копии, распределенные по вашей кодовой базе, потому что его реализация в linq была «тривиальной» и не было очевидного класса для размещения логики.
Я разрабатываю большие финансовые приложения, поэтому я не «единственный потребитель кода». И ответ прост - коллекции не должны существовать в коде без какого-либо «владельца». «InvoiceCollection» бессмысленна. Должны быть некоторые InvoiceRepository и InvoicePayOperation - первый из которых владеет коллекцией, второй содержит бизнес-логику (и становится владельцем переданной ему временной коллекции). Конечно, если вы привыкли к дублированию кода и «голым коллекциям», как вы говорите, вы не сможете увидеть, как этого можно избежать.
Кроме того, в предоставленном случае лучший центр знаний о том, можно ли оплатить счет-фактуру сам счет-фактура, поэтому у него должно быть свойство CanBePaid - так, немного сообразительности избавляет от необходимости менять «в 4-х местах»
Здесь нужно согласиться с DarkWanderer. Коллекции должны быть просто коллекциями. Есть много других способов централизовать бизнес-логику без нарушения принципа единой ответственности. К тому же, если вы сделаете вашу коллекцию более «умной», это может скорее запутать разработчика, чем помочь ему. Наличие такого метода, как PayUnpaidInvoices, может привести к второму предположению о том, что могут делать «обычные» методы сбора, такие как Remove и Clear. Будет ли он просто удалять счета из коллекции в памяти или удаляет их из базы данных?
@DarkWanderer, вы предполагаете, что инкапсуляция делает SRP недействительным? Коллекция (отвечающая за хранение списка вещей - не бизнес-функция, но все еще действующая в качестве полезности) содержится внутри чего-то, отвечающего за управление счетами, которое может содержаться в модуле, управляющем функциями учета. Это вовсе не нарушение SRP, а принцип работы инкапсуляции. Передача коллекции другому классу для работы с ней нарушает инкапсуляцию и правило, согласно которому вы «просите объект сделать что-то с его данными, а не запрашивать его данные», которое сопровождается инкапсуляцией.
Вы также можете увидеть readonlycollection-or-ienumerable-for-exposing-member-collection /