Подтвердить что ты не робот

Список констант в С#

Я хотел бы создать список на С#, который после его создания не сможет добавлять или удалять элементы из него. Например, я создам список,

List<int> lst = a;

(a - существующий список), но после того, как я не смогу написать код (он будет отмечен как ошибка):

lst.Add(2);
4b9b3361

Ответ 1

.NET поддерживает поистине неизменяемые коллекции, представления только для чтения изменяемых коллекций и интерфейсы только для чтения, реализованные изменяемыми коллекциями.


Одна такая неизменяемая коллекция ImmutableArray<>, которую вы можете создать как a.ToImmutableArray() в вашем примере. Не забудьте взглянуть на другие варианты списков MSDN, потому что вам может быть лучше обслуживаться другая неизменяемая коллекция. Если вы хотите сделать копии исходной последовательности с небольшими изменениями, ImmutableList<> может быть быстрее, например (массив дешевле для создания и доступ, хотя). Обратите внимание, что a.Add(...); действителен, но возвращает новую коллекцию, а не меняет a. Если у вас есть resharper, это предупредит вас, если вы проигнорируете возвращаемое значение чистого метода, такого как Add (и может быть расширение roslyn, чтобы сделать что-то подобное, о котором я не знаю). Если вы идете по этому маршруту, попробуйте полностью пропустить List<> и перейдем к неизменяемым коллекциям.

Представления только для чтения изменяемых коллекций немного менее безопасны, но поддерживаются в более старых версиях .NET. Тип упаковки называется ReadOnlyCollection<>, который в вашем примере можно построить как a.AsReadOnly(). Эта коллекция не гарантирует неизменность; это только защитники, которые вы не можете изменить. Некоторый другой бит кода, который имеет ссылку на базовый List<>, все еще может изменить его. Кроме того, ReadOnlyCollection также накладывает дополнительные накладные расходы; поэтому вы не можете выиграть много, избегая неизменных коллекций по причинам производительности (TODO: сравнивайте эту претензию). Вы можете использовать обертку только для чтения, такую ​​как это даже в публичном API, безопасно - нет (неразрешительного) способа получения основного списка. Однако, поскольку он часто не быстрее, чем неизменяемые коллекции, и он также не совсем безопасен, я рекомендую избегать ReadOnlyCollection<> - я никогда больше не использую это лично.

Интерфейсы только для чтения, реализованные изменчивыми коллекциями, еще ниже по шкале безопасности, но быстро. Вы можете просто нарисовать List<> как IReadOnlyList<>, который вы можете сделать в своем примере как IReadOnlyList<int> lst = a. Это мои предпочтения для внутреннего кода - вы по-прежнему получаете статическую безопасность, вы просто не защищены от вредоносного кода или кода, который использует проверки типов и отличает неразумно (но этого можно избежать с помощью обзоров кода в моем опыте). Я никогда не был укушен этим выбором, но он менее безопасен, чем два вышеупомянутых варианта. С другой стороны, он не берет никаких ассигнований и быстрее. Если вы это обычно делаете, вам может понадобиться определить метод расширения для выполнения upcast для вас (приведение может быть небезопасным в С#, потому что они не только безопасны для повышения, но, возможно, неудачные нисходящие потоки и пользовательские преобразования - так что это хорошо идея избегать явных приемов, где бы вы ни были).

Обратите внимание, что во всех случаях только сама последовательность доступна только для чтения. Не затрагиваются базовые объекты (например, int или string являются неизменяемыми, но более сложные объекты могут быть или не быть).


TL; DR:

  • Для безопасности: используйте a.ToImmutableArray(), чтобы создать неизменяемую копию в ImmutableArray<int>.
  • Для производительности: используйте IReadOnlyList<int>, чтобы предотвратить случайную мутацию во внутреннем коде с минимальными издержками производительности. Имейте в виду, что кто-то может вернуть его обратно в List<> (не делайте этого), делая это менее "безопасным" для публичного api.
  • Избегайте a.AsReadOnly(), который создает ReadOnlyCollection<int>, если вы не работаете с устаревшей базой кода, которая не поддерживает более новые альтернативы, или если вы действительно знаете, что делаете и имеете особые потребности (например, действительно хотите изменить список в другом месте и иметь доступ только для чтения).

Ответ 2

Вы можете использовать ImmutableList<T>/ImmutableArray<T> из System.Collections.Immutable NuGet:

var immutable = ImmutableList<int>.Create(1, 2, 3);

Или используя метод расширения ToImmutableList:

var immutable = mutableList.ToImmutableList();

В случае вызова Add, * возвращается новая копия * и не изменяет исходный список. Это не приведет к ошибке времени компиляции.

Ответ 4

Я рекомендую использовать экземпляр System.Collections.Immutable.ImmutableList<T>, но ссылаюсь на переменную или свойство типа System.Collections.Generic.IReadOnlyList<T>. Если вы просто используете голый неизменный список, вы не получите ошибок для добавления к нему, как вы пожелаете.

System.Collections.Generic.IReadOnlyList<int> list = a.ToImmutableList();

Ответ 5

В качестве альтернативы уже опубликованным ответам вы можете обернуть readonly regular List<T> в объект, который предоставляет его как IReadOnlyList.

class ROList<T>
{
    public ROList(IEnumerable<T> argEnumerable)
    {
        m_list = new List<T>(argEnumerable);
    }

    private readonly List<T> m_list;
    public IReadOnlyList<T> List { get { return m_list; } }
}

void Main()
{
    var list = new  List<int> {1, 2, 3};
    var rolist = new ROList<int>(list);

    foreach(var i in rolist.List)
        Console.WriteLine(i);

    //rolist.List.Add(4); // Uncomment this and it won't compile: Add() is not allowed
}

Ответ 6

Лучше всего использовать IReadOnlyList<int>.

Преимущество использования IReadOnlyList<int> по сравнению с List.AsReadOnly() заключается в том, что a ReadOnlyCollection<T> может быть назначено IList<T>, к которому затем можно получить доступ с помощью записывающего индексатора.

Пример для пояснения:

var original = new List<int> { 1, 2, 3 };
IReadOnlyList<int> readOnlyList = original;

Console.WriteLine(readOnlyList[0]); // Compiles.

readOnlyList[0] = 0; // Does not compile.

var readOnlyCollection = original.AsReadOnly();

readOnlyCollection[0] = 1; // Does not compile.

IList<int> collection = readOnlyCollection; // Compiles.

collection[0] = 1; // Compiles, but throws runtime exception. 

Использование IReadOnlyList<int> исключает возможность случайного пересылки списка только для чтения методу, который принимает IList<> и который затем пытается изменить элемент, что приведет к исключению среды выполнения.

Ответ 7

Это может быть IReadOnlyList<int>, например

  IReadOnlyList<int> lst = a;

Итак, исходный список (a) изменен, а lst - нет. Часто мы используем IReadOnlyList<T> для общедоступных свойств и IList<T> для частных, например.

  public class MyClass {
    // class itself can modify m_MyList 
    private IList<int> m_MyList = new List{1, 2, 3};

    ...
    // ... while code outside can't  
    public IReadOnlyList<int> MyList {
      get {
        return m_MyList; 
      }
    }  
  }

Ответ 8

Почему бы просто не использовать IEnumerable?

IEnumerable<string> myList = new List<string> { "value1", "value2" };