Как избежать открытого доступа к закрытым полям?
Например, пусть объявляет:
private readonly List<string> _strings = new List<string>();
public IEnumerable<string> Strings
{
get
{
return _strings;
}
}
И теперь мы можем сделать следующее:
((List<string>) obj.Strings).Add("Hacked");
Таким образом, мы действительно не скрываем List
от использования, а только скрываем его за интерфейсом. Как скрыть список в таком примере без копирования _strings
в новой коллекции, чтобы ограничить модификацию списка?
Ответы
Ответ 1
Множество способов.
Самый наивный подход - всегда копировать личное поле:
return _strings.ToList();
Но это вообще не обязательно. Это будет иметь смысл, если:
- Вы знаете, что код вызова обычно хочет
List<T>
в любом случае, поэтому вы можете также вернуть его (не подвергая оригиналу).
- Для открытого объекта важно сохранить исходные значения, даже если исходная коллекция впоследствии будет изменена.
В качестве альтернативы вы можете просто открыть оболочку только для чтения:
return _strings.AsReadOnly();
Это более эффективно, чем первый вариант, но будет отображаться изменения, внесенные в базовую коллекцию.
Эти стратегии также могут быть объединены. Если вам нужна копия оригинальной коллекции (так что изменения в оригинале не имеют эффекта), но также не хотят, чтобы возвращаемый объект был изменен *, вы могли бы пойти с:
// copy AND wrap
return _strings.ToList().AsReadOnly();
Вы также можете использовать yield
:
foreach (string s in _strings)
{
yield return s;
}
Это похоже на эффективность и компромиссы по первому варианту, но с отсроченным исполнением.
Очевидно, что ключевая вещь, которую следует рассмотреть здесь, - это то, как ваш метод действительно будет использоваться, и какую функциональность, именно, вы хотите обеспечить этим свойством.
* Хотя, честно говоря, я не уверен, почему вам все равно.
Ответ 2
Как насчет использования ReadOnlyCollection
. Это должно исправить эту конкретную проблему.
private ReadOnlyCollection<string> foo;
public IEnumerable<string> Foo { get { return foo; } }
Ответ 3
Я просто отправлю вам мои мысли о мыслях об инкапсуляции, возможно, вы найдете их разумными.
Как известно, инкапсуляция - очень полезная вещь, она помогает бороться с сложностью. Это делает ваш код логически минималистичным и понятным.
Как вы сказали, защищенный доступ к члену может быть нарушен, и пользователь интерфейса может получить полный доступ к базовому объекту. Но в чем проблема? Ваш код не станет более сложным или связанным.
Собственно, насколько я знаю, с использованием некоторого отражения почти нет ограничений, даже с полностью частными членами. Так почему бы вам не попытаться скрыть их?
Я думаю, что нет необходимости делать копию коллекции (или любой другой изменчивой), и вышеприведенные подходы ошибочны. Используйте свой первоначальный вариант:
private readonly List<string> _strings = new List<string>();
public IEnumerable<string> Strings
{
get
{
return _strings;
}
}
Ответ 4
ReadOnlyCollection<T>
: http://msdn.microsoft.com/en-us/library/ms132474.aspx
Инициализирует новый экземпляр класса ReadOnlyCollection, который является оберткой только для чтения вокруг указанного списка.
Ответ 5
вы можете обернуть свой список в getter так:
private readonly List<string> _strings = new List<string>();
public IEnumerable<string> Strings {
get {
return new ReadOnlyCollection<string>(_strings);
}
}
Таким образом, вы можете свободно изменять свой личный список внутри своего класса и не беспокоиться об изменениях вне.
ПРИМЕЧАНИЕ: ReadOnlyCollection
не копирует список, который он содержит (например, _strings)
Ответ 6
Поскольку никто не предлагал это как ответ еще, вот еще одна альтернатива:
private readonly List<string> _strings = new List<string>();
public IEnumerable<string> Strings
{
get { return _strings.Select(x => x); }
}
Он эффективно эквивалентен методу yield return
, упомянутому ранее, но немного более компактному.
Ответ 7
Помимо ReadOnlyCollection
есть действительно Неизменяемые коллекции для .NET.
Использование неизменяемых коллекций гарантирует потребителю, что коллекция не изменяется, а не просто не может их изменить. Поскольку они неизменяемы, вы можете просто их разоблачить:
public ImmutableList<string> Strings { get; private set; }
Подробнее: http://blogs.msdn.com/b/bclteam/archive/2012/12/18/preview-of-immutable-collections-released-on-nuget.aspx