Правильно ли выставить список ?

Я знаю, что не должен показывать объект 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>, мне всегда нужно будет создавать новый список? Например, это похоже на работу (test будет нулевым после приведения):

    public static IEnumerable<string> List
    {
        get
        {
            return new ReadOnlyCollection<string>(_list);
        }
    }

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

25.08.2009 07:28:17
что не так с выставлением List (of T) как свойства?
Jason 25.08.2009 07:37:40
Вы не можете продлить его: blogs.msdn.com/fxcop/archive/2006/04/27/…
Michael Stum♦ 25.08.2009 07:38:42
Я не понимаю, в чем смысл этого? Я имею в виду, я бы просто выставил свойство как List <T> ... ??? это потокобезопасная проблема?
Pure.Krome 25.08.2009 07:44:09
для чего нужно его расширять?
Jason 25.08.2009 07:45:48
В статье приведен пример: если вам когда-нибудь понадобятся уведомления о добавлении / удалении элементов, вы проиграли, потому что вы не можете сделать это с помощью List, поэтому вы должны внести кардинальные изменения в ваш публичный интерфейс. Также в моем случае, это было больше о получении списка только для чтения без особых накладных расходов.
Michael Stum♦ 25.08.2009 07:58:19
8 ОТВЕТОВ
РЕШЕНИЕ
2
25.08.2009 07:34:50
AsReadOnlyпросто вызывает ReadOnlyCollectionконструктор, точно так же, как пример кода в вопросе.
LukeH 25.08.2009 07:37:49
Я все еще использовал бы это все же. Суть в том, что он существует по какой-то причине и что документ для него подробно описывает, что он делает. Сказав это, нет, мой ответ не добавляет ничего, чего не может сказать Марк!
Ruben Bartelink 25.08.2009 07:42:51
Разрывался между этим, ответом Хенрика и Марка. Принимая это, потому что я не знал об AsReadOnly (), так как он очень лаконичен, так как решает проблему, поскольку я могу ее кэшировать (создавая частную статическую ReadOnlyCollection <string> _listWrapper; только для чтения, настраивая ее в конструкторе, а затем вернуть его), и, как мне кажется, это правильный способ (я видел, как Эрик Липперт упоминал это как хороший способ обернуть его). Безопасность потоков может быть проблемой, но в любом случае это общая проблема.
Michael Stum♦ 25.08.2009 08:22:43
Я удостоен чести Честная игра на сумму вознаграждения, которое вы вложили в выбор. Я уверен, что я не единственный здесь с +1 за проницательный ответ Марка, хотя - надеюсь, что очки ослабят боль от того, что он не получил руб! Спасибо!
Ruben Bartelink 25.08.2009 09:39:19

Да и нет. Да, производительность снижается, поскольку создается новый объект. Нет, ваш список не клонирован, он упакован в коллекцию ReadOnlyCollection.

4
25.08.2009 07:34:38

Выявление 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является не достаточно , чтобы сделать ваш объект потокобезопасной; он просто защищает от добавления данных потребителем (но они могут все еще перечислять его, пока ваш другой поток добавляет данные, если вы не синхронизируете или не делаете копии).

8
25.08.2009 07:42:55
s / выход / существующий /: P (но +1!)
Ruben Bartelink 25.08.2009 07:41:32
Я всегда забываю, что существует выход ... Безопасность потоков в ASP.net не так уж и важна, так как я стараюсь не использовать изменяемые статические классы в ASP.net, потому что они совместно используются запросами. Я предполагаю, что если мне нужен «снимок», то в любом случае нет способа обойти копию, поэтому return new List<string>(_list);он «решит» это, и если вызывающий преобразует его обратно в List <string>, он изменяет только свою собственную копию, но не мою. Суть в том, чтобы сохранить Class1._list «неизменяемым» для внешних абонентов и предоставить функции добавления / удаления в Class1.
Michael Stum♦ 25.08.2009 07:48:23
Да, полная копия защищает от всех зол, кроме тех, кто изменяет свойства объектов в списке.
Marc Gravell♦ 25.08.2009 08:03:49
Svish 25.08.2009 08:15:57

Вам не нужно беспокоиться о накладных расходах на клонирование: упаковка коллекции с помощью ReadOnlyCollection не клонирует ее. Это просто создает обертку; если базовая коллекция изменяется, изменяется и версия только для чтения.

Если вы беспокоитесь о создании новых оболочек снова и снова, вы можете кэшировать их в отдельной переменной экземпляра.

2
25.08.2009 07:36:38

Если у класса нет других целей, вы можете наследовать из списка, переопределить метод add и вызвать его исключение.

3
25.08.2009 07:36:51
Вы должны унаследовать от, Collection<T>чтобы сделать это ... List<T>не так много virtualчленов.
Marc Gravell♦ 25.08.2009 07:40:41
Но тогда это было бы нарушением LSP, но, тем не менее, интересной «идеей взлома».
Ruben Bartelink 25.08.2009 07:44:07
Это может быть вариант, он просто «чувствует себя неправильно», так как я считаю, что Framework уже должен предоставить подходящий механизм из коробки.
Michael Stum♦ 25.08.2009 07:50:26

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){
  ...
}
0
25.08.2009 07:44:01
Массивы в публичных классовых контрактах считаются плохой практикой
Przemaas 25.08.2009 07:56:52
Не всегда, но у Эрика Липперта были некоторые мысли по этому поводу: blogs.msdn.com/ericlippert/archive/2008/09/22/…
Michael Stum♦ 25.08.2009 08:06:00
@Przemaas, нет общего правила, как массивы - плохая практика, это всегда зависит от ситуации, если вы разбираете и просматриваете источник List, у него также есть массив для хранения своих собственных элементов. Когда вы не хотите изменять содержимое и вам нужна коллекция только для чтения, массив - это самый быстрый способ сделать это.
Akash Kava 25.08.2009 08:07:00

Если вы выставите свой список как IEnumerable, я не буду беспокоиться о том, что вызывающие абоненты возвращаются в List. В контракте вашего класса вы явно указали, что в этом списке разрешены только операции, определенные в IEnumerable. Итак, вы неявно заявили, что реализация этого списка может измениться почти на все, что реализует IEnumerable.

1
25.08.2009 07:52:10
Это правда, и, как отметил Свиш, в любом случае (почти?) Это невозможно сделать без глубокого копирования данных, но я хотел иметь небольшой барьер, чтобы люди не могли слишком легко выстрелить себе в ногу.
Michael Stum♦ 25.08.2009 14:20:49

Я задавал похожий вопрос ранее:

Исходя из этого, я бы порекомендовал вам использовать List<T>внутренне, и вернуть его как Collection<T>или IList<T>. Или , если это необходимо только перечислить и не добавлять или antyhing подобное, IEnumerable<T>.

На вопрос о том, чтобы иметь возможность использовать то, что вы возвращаете, я бы просто сказал: не беспокойтесь. Если люди захотят использовать ваш код так, как он не предназначен, они смогут так или иначе. Ранее я также задавал вопрос по этому поводу, и я бы сказал, что единственное, что нужно сделать, это раскрыть то, что вы намерены, и если люди используют это по-другому, ну, в этом их проблема: p Некоторые связанные вопросы:

2
23.05.2017 11:52:59