Ответ 1
Используйте AsReadOnly() - подробнее см. в MSDN
Я знаю, что я не должен раскрывать List<T>
в свойстве, но мне интересно, как правильно это сделать? Например, сделав это:
public static class Class1
{
private readonly static List<string> _list;
public static IEnumerable<string> List
{
get
{
return _list;
//return _list.AsEnumerable<string>(); behaves the same
}
}
static Class1()
{
_list = new List<string>();
_list.Add("One");
_list.Add("Two");
_list.Add("Three");
}
}
позволит моему абоненту просто вернуть обратно к List<T>
:
private void button1_Click(object sender, EventArgs e)
{
var test = Class1.List as List<string>;
test.Add("Four"); // This really modifies Class1._list, which is bad™
}
Итак, если мне нужен действительно неизменный List<T>
, мне всегда нужно будет создать новый список? Например, кажется, что это работает (тест имеет значение null после броска):
public static IEnumerable<string> List
{
get
{
return new ReadOnlyCollection<string>(_list);
}
}
Но я беспокоюсь, если есть накладные расходы на производительность, так как мой список клонируется каждый раз, когда кто-то пытается его получить?
Используйте AsReadOnly() - подробнее см. в MSDN
Предоставление List<T>
как свойства на самом деле не является корнем всего зла; особенно если это позволяет использовать ожидаемое использование, например foo.Items.Add(...)
.
Вы можете написать альтернативу AsEnumerable()
:
public static IEnumerable<T> AsSafeEnumerable<T>(this IEnumerable<T> data) {
foreach(T item in data) yield return item;
}
Но ваша самая большая проблема на данный момент - безопасность потоков. Как статический член, у вас могут возникнуть большие проблемы, особенно если это что-то вроде ASP.NET. Даже ReadOnlyCollection
по существующему списку пострадает от этого:
List<int> ints = new List<int> { 1, 2, 3 };
var ro = ints.AsReadOnly();
Console.WriteLine(ro.Count); // 3
ints.Add(4);
Console.WriteLine(ro.Count); // 4
Так что просто обертка с AsReadOnly
достаточно не, чтобы сделать ваш объект потокобезопасным; он просто защищает пользователя от добавления данных (но они все равно могут перечислять его, пока ваш другой поток добавляет данные, если вы не синхронизируете или не делаете копии).
Да и Нет. Да, накладные расходы на производительность, потому что создается новый объект. Нет, ваш список не клонирован, он обернут ReadOnlyCollection.
Если класс не имеет другой цели, вы можете наследовать из списка и переопределить метод add и вызвать его исключение.
Вам не нужно беспокоиться о накладных расходах на клонирование: обертка коллекции с помощью ReadOnlyCollection не клонирует ее. Он просто создает обертку; если базовая коллекция изменяется, изменяется версия readonly.
Если вы беспокоитесь о создании новых оберток снова и снова, вы можете кэшировать его в отдельной переменной экземпляра.
Я задал аналогичный вопрос раньше:
Исходя из этого, я бы рекомендовал вам использовать List<T>
внутренне и вернуть его как Collection<T>
или IList<T>
. Или, если это необходимо только для перечисления, а не для добавления или антиирования, IEnumerable<T>
.
По поводу того, что вы можете бросить то, что вы вернетесь к другим вещам, я бы просто сказал, не беспокойтесь. Если люди хотят использовать ваш код таким образом, чтобы он не был предназначен, они смогут так или иначе. Я также задал вопрос об этом, и я бы сказал, что единственное, что нужно сделать, это разоблачить то, что вы намереваетесь, и если люди используют его по-другому, это их проблема: p Некоторые связанные вопросы:
Если вы публикуете свой список как IEnumerable, я бы не стал беспокоиться о том, что вызывающие абоненты возвращаются к списку. Вы явно указали в контракте своего класса, что в этом списке разрешены только операции, определенные в IEnumerable. Таким образом, вы неявно заявили, что реализация этого списка может измениться практически во всем, что реализует IEnumerable.
AsEnumerable и ReadOnlyCollection имеют проблемы, когда ваше перечисление находится на полпути, и коллекция изменяется. Эти вещи не являются потокобезопасными. Возврат их в виде массива и кэширование их во время вызова может быть намного лучше.
Например,
public static String[] List{
get{
return _List.ToArray();
}
}
//While using ...
String[] values = Class1.List;
foreach(string v in values){
...
}
// instead of calling foreach(string v in Class1.List)
// again and again, values in this context will not be
// duplicated, however values are cached instance so
// immediate changes will not be available, but its
// thread safe
foreach(string v in values){
...
}