Возврат коллекции только для чтения
У меня есть объект в многопоточной среде, которая поддерживает набор информации, например:
public IList<string> Data
{
get
{
return data;
}
}
В настоящее время я return data;
обернут ReaderWriterLockSlim
, чтобы защитить коллекцию от совместного использования нарушений. Однако, чтобы быть уверенным, я хотел бы вернуть коллекцию как доступную только для чтения, чтобы вызывающий код не мог вносить изменения в коллекцию, но просматривал только то, что уже есть. Это вообще возможно?
Ответы
Ответ 1
Если ваше единственное намерение состоит в том, чтобы получить код вызова, чтобы не совершить ошибку, и изменить коллекцию, когда она должна читать только все, что необходимо, это вернуть интерфейс, который не поддерживает Add, Remove и т.д. Почему не вернуть IEnumerable<string>
? Вызов кода должен быть выполнен, что вряд ли удастся обойти без знания внутренних свойств, к которым они обращаются.
Если ваше намерение состоит в том, чтобы запретить вызывающему коду наблюдать за обновлениями из других потоков, вам придется вернуться к уже упомянутым решениям, чтобы выполнить глубокую или мелкую копию в зависимости от ваших потребностей.
Ответ 2
Если ваши базовые данные хранятся в виде списка, вы можете использовать метод List (T).AsReadOnly.
Если ваши данные могут быть перечислены, вы можете использовать метод Enumerable.ToList, чтобы передать свою коллекцию в список и вызвать AsReadOnly на нем.
Ответ 3
Я проголосовал за ваш принятый ответ и согласен с ним - однако могу ли я дать вам кое-что, чтобы рассмотреть?
Не возвращайте коллекцию напрямую. Создайте точно названный бизнес-логический класс, который отражает цель коллекции.
Основное преимущество этого заключается в том, что вы не можете добавлять код в коллекцию, поэтому всякий раз, когда у вас есть собственная "коллекция" в вашей объектной модели, вы ВСЕГДА имеете код поддержки, отличный от OO, в вашем проекте, чтобы получить доступ к нему.
Например, если в вашей коллекции были счета-фактуры, у вас, вероятно, было бы 3 или 4 места в вашем коде, где вы повторили бы неоплаченные счета-фактуры. У вас может быть метод getUnpaidInvoices. Однако реальная сила приходит, когда вы начинаете думать о таких методах, как "payUnpaidInvoices (плательщик, учетная запись)".
Когда вы отправляете коллекцию вместо написания объектной модели, целые классы рефакторинга никогда не будут встречаться с вами.
Обратите внимание, что это делает вашу проблему особенно приятной. Если вы не хотите, чтобы люди меняли коллекции, в вашем контейнере не должно быть мутаторов. Если позже вы решите, что в одном случае вы действительно должны его изменить, вы можете создать безопасный механизм для этого.
Как вы решаете эту проблему при прохождении собственной коллекции?
Кроме того, собственные коллекции не могут быть дополнены дополнительными данными. Вы узнаете это в следующий раз, когда обнаружите, что вы проходите (Collection, Extra) более чем на один или два метода. Он указывает, что "Экстра" принадлежит объекту, содержащему вашу коллекцию.
Ответ 4
Я думаю, вы вводите в заблуждение понятия здесь.
ReadOnlyCollection
предоставляет оболочку только для чтения для существующей коллекции, позволяя вам (класс A) передавать ссылку на сейф коллекции, зная, что вызывающий объект (класс B) не может изменять коллекцию (т.е. не может добавить или удалить любые элементы из коллекции.)
Нет никаких гарантий безопасности нитей.
- Если вы (класс A) продолжаете изменять базовую коллекцию после того, как вы передадите ее как
ReadOnlyCollection
, тогда класс B увидит эти изменения, у вас будут именованные итераторы недействительными и т.д. и, как правило, будет открыт для любого из обычных concurrency проблемы с коллекциями.
- Кроме того, если элементы в коллекции изменяемы, то вы (класс A) и вызывающий объект (класс B) смогут изменять любое изменяемое состояние объектов в коллекции.
Ваша реализация зависит от ваших потребностей:
- Если вы не заботитесь о том, чтобы вызывающий (класс B) не видел каких-либо дальнейших изменений в коллекции, вы можете просто клонировать коллекцию, раздавать ее и прекращать заботу.
- Если вам определенно нужен вызывающий (класс B), чтобы увидеть изменения, внесенные в коллекцию, и вы хотите, чтобы это было потокобезопасным, у вас больше проблем с вашими руками. Одна из возможностей - реализовать собственный потокобезопасный вариант ReadOnlyCollection, чтобы разрешить заблокированный доступ, хотя это будет нетривиально и неэффективно, если вы хотите поддерживать IEnumerable, и он по-прежнему не защитит вас от изменяемых элементов в коллекция.
Ответ 5
Следует отметить, что aku ответ будет защищать только список только для чтения. Элементы в списке все еще доступны для записи. Я не знаю, есть ли способ защитить неатомные элементы, не клонируя их, прежде чем помещать их в список только для чтения.
Ответ 6
Вместо этого вы можете использовать копию коллекции.
public IList<string> Data {
get {
return new List<T>(data);
}}
Таким образом, не имеет значения, будет ли он обновляться.
Ответ 7
Вы хотите использовать yield. Вы просматриваете список IEnumerable и возвращаете результаты с помощью yeild. Это позволяет потребителю использовать для каждого без изменения коллекции.
Он будет выглядеть примерно так:
List<string> _Data;
public IEnumerable<string> Data
{
get
{
foreach(string item in _Data)
{
return yield item;
}
}
}