Лучше ли использовать Enumerable.Empty‹T›() вместо нового List‹T›() для инициализации IEnumerable‹T›?

Предположим, у вас есть класс Person :

public class Person
{
   public string Name { get; set;}
   public IEnumerable<Role> Roles {get; set;}
}

Я, очевидно, должен создать экземпляр Ролей в конструкторе. Теперь я делал это со списком следующим образом:

public Person()
{
   Roles = new List<Role>();
}

Но я обнаружил этот статический метод в пространстве имен System.Linq

IEnumerable<T> Enumerable.Empty<T>();

Из MSDN:

Метод Empty(TResult)() кэширует пустую последовательность типа TResult. Когда объект, который он возвращает, перечисляется, он не дает никаких элементов.

В некоторых случаях этот метод полезен для передачи пустой последовательности определяемому пользователем методу, который принимает IEnumerable(T). Его также можно использовать для создания нейтрального элемента для таких методов, как Union. См. раздел «Пример» для примера такого использования

Так что лучше написать конструктор так? Вы используете его? Почему? или если нет, то почему?

public Person()
{
   Roles = Enumerable.Empty<Role>();
}

person Stéphane    schedule 12.12.2009    source источник
comment
Это класс данных. Я намерен использовать этот класс в качестве класса модели при реализации шаблона репозитория с Entity Framework 4.0 (поиграться...). поэтому я думаю, что нормально иметь здесь публичный сеттер, не так ли?   -  person Stéphane    schedule 12.12.2009
comment
serbech, как вы добавите роль (внутри Person), когда другой код может установить любой производный класс IEnumerable для списка? На что ты его бросишь?   -  person Henk Holterman    schedule 12.12.2009
comment
Я понимаю вашу точку зрения, мне, вероятно, следует иметь внутренний IList или даже список здесь? И это просто устранит мою первоначальную проблему...   -  person Stéphane    schedule 12.12.2009
comment
Я использую Enumerable.Empty‹T› в модульном тестировании, чтобы указать неудовлетворительные тесты пути, чтобы помочь сообщить о намерении. Как многие уже указывали, бонусом является отсутствие распределения на GC, что помогает, когда число ваших модульных тестов исчисляется сотнями или более.   -  person jjhayter    schedule 09.06.2015
comment
Я согласен, что вы хотели бы удалить set и просто использовать интерфейс IList напрямую. Однако, если вы хотите сохранить его как IEnumerable с set, действительно кажется, что вам следует инициализировать его как null. Точно так же вам не нужно инициализировать Name пустой строкой — поскольку у нее есть set, вы просто инициализируете null и получаете код клиента set.   -  person Dax Fohl    schedule 07.11.2016
comment
Теперь можно также использовать Array.Empty<Role>(), который явно является массивом и, следовательно, IList<T>. В Core это фактически один и тот же массив, отличающийся только типом.   -  person Jon Hanna    schedule 28.01.2017
comment
Это, вероятно, давно назрело, но если вам не нужен индексатор [], возможно, вам лучше использовать ICollection<T> вместо IList<T>. См. также stackoverflow.com/ вопросы/9855693/   -  person Didii    schedule 31.07.2018


Ответы (6)


Я думаю, что большинство сообщений упустили главное. Даже если вы используете пустой массив или пустой список, это объекты, и они хранятся в памяти. Сборщик мусора должен позаботиться о них. Если вы имеете дело с приложением с высокой пропускной способностью, это может оказать заметное влияние.

Enumerable.Empty не создает объект для каждого вызова, что снижает нагрузку на сборщик мусора.

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

person Vadym Chekan    schedule 04.04.2011
comment
И это также лучше отражает намерение. - person Olivier Jacot-Descombes; 21.08.2016
comment
Как работает Empty(), если он не создает объект? - person Mohammed Noureldin; 24.05.2018
comment
@MohammedNoureldin возвращает синглтон - person Vadym Chekan; 24.05.2018
comment
Мало того, что Enumerable.Empty<> возвращает одноэлементный перечисляемый, этот перечисляемый также возвращает одноэлементный перечислитель. - person Drew Noakes; 22.02.2021

Я думаю, что Enumerable.Empty<T> лучше, потому что он более явный: ваш код четко указывает на ваши намерения. Это также может быть немного более эффективным, но это лишь второстепенное преимущество.

person Tommy Carlier    schedule 12.12.2009
comment
Да, давая понять, что вы имеете в виду, что это пусто, а не что-то, что может быть добавлено позже, полезно для вашего кода. - person Jay Bazuzi; 12.12.2009
comment
да. Enumerable‹›.Empty позволяет избежать выделения нового объекта, что является небольшим бонусом. - person Neil; 04.01.2010
comment
примечание @NeilWhitaker: это Enumerable.Empty‹T›, а не Enumerable‹T›.Empty (это однажды свело меня с ума...) - person santa; 11.04.2014
comment
Согласованный. Добавить; если вам нужно инициализировать IEnumerable<T>... зачем инициализировать его с большей огневой мощью, чем вам нужно. List<T> — это более сложный объект, который реализует IEnumerable<T>, среди прочего. Я говорю инициализировать свойства самым простым способом и использовать Enumerable.Empty<T>() в этом случае. - person Craig; 18.09.2015

Что касается производительности, давайте посмотрим, как реализован Enumerable.Empty<T>.

Он возвращает EmptyEnumerable<T>.Instance, который определяется как:

internal class EmptyEnumerable<T>
{
    public static readonly T[] Instance = new T[0];
}

Статические поля в универсальных типах выделяются для каждого параметра универсального типа. Это означает, что среда выполнения может лениво создавать эти пустые массивы только для тех типов, которые нужны пользовательскому коду, и повторно использовать экземпляры столько раз, сколько необходимо, без какого-либо давления на сборщик мусора.

А именно:

Debug.Assert(ReferenceEquals(Enumerable.Empty<int>(), Enumerable.Empty<int>()));
person Drew Noakes    schedule 14.01.2016
comment
Все это время я думал, что реализация была похожа на public static IEnumerable<T> Empty<T>() { yield break; }, MS знает это лучше всего, вероятно, кэшированный new T[0] более эффективен. - person nawfal; 15.09.2018
comment
Проверено, реализация такая же в .NET Core 2.2 (только имена классов другие). - person nawfal; 15.09.2018

Предполагая, что вы действительно хотите каким-то образом заполнить свойство Roles, затем инкапсулируйте это, сделав его сеттер закрытым и инициализировав его новым списком в конструкторе:

public class Person
{
    public string Name { get; set; }
    public IList<Role> Roles { get; private set; }

    public Person()
    {
        Roles = new List<Role>();
    }
}

Если вы действительно хотите иметь общедоступный сеттер, оставьте Roles со значением null и избегайте выделения объекта.

person Neil Barnwell    schedule 12.12.2009
comment
Я бы рекомендовал ICollection вместо IList - person CervEd; 12.03.2021

Проблема с вашим подходом заключается в том, что вы не можете добавлять какие-либо элементы в коллекцию - у меня была бы частная структура, такая как список, а затем выставлять элементы как Enumerable:

public class Person
{
    private IList<Role> _roles;

    public Person()
    {
        this._roles = new List<Role>();
    }

    public string Name { get; set; }

    public void AddRole(Role role)
    {
        //implementation
    }

    public IEnumerable<Role> Roles
    {
        get { return this._roles.AsEnumerable(); }
    }
}

Если вы хотите, чтобы какой-то другой класс создал список ролей (что я бы не рекомендовал), то я бы вообще не инициализировал перечисляемое в Person.

person Lee    schedule 12.12.2009
comment
неплохо подмечено. На самом деле я бы быстро обнаружил это в своей реализации :) - person Stéphane; 12.12.2009
comment
Я бы сделал _roles только для чтения и не вижу необходимости в вызове AsEnumerable(). В противном случае +1 - person Kent Boogaart; 12.12.2009
comment
@Kent - я согласен только для чтения. Что касается AsEnumerable, то он на самом деле не нужен, но предотвращает приведение ролей к IList и их изменение. - person Lee; 12.12.2009

Типичная проблема с представлением частного списка как IEnumerable заключается в том, что клиент вашего класса может возиться с ним путем приведения. Этот код будет работать:

  var p = new Person();
  List<Role> roles = p.Roles as List<Role>;
  roles.Add(Role.Admin);

Вы можете избежать этого, реализуя итератор:

public IEnumerable<Role> Roles {
  get {
    foreach (var role in mRoles)
      yield return role;
  }
}
person Hans Passant    schedule 12.12.2009
comment
Если разработчик переводит ваш IEnumerable‹T› в список‹T›, то это его проблема, а не ваша. Если вы измените внутреннюю реализацию в будущем (так что это уже не List‹T›), это не ваша вина, что код разработчика ломается. Вы не можете помешать людям писать дерьмовый код и злоупотреблять вашим API, поэтому я не думаю, что на это стоит тратить много усилий. - person Tommy Carlier; 12.12.2009
comment
Речь идет не о взломе кода, а об использовании кода. - person Hans Passant; 12.12.2009
comment
Я согласен с @Hans, если это проблема безопасности, и с @Tommy во всех остальных случаях. Непослушные разработчики всегда совершают одну фатальную ошибку. - person TrueWill; 15.04.2011
comment
И часть перечисления может быть написана с помощью метода расширения AsEnumerable(). (похоже на ответ Ли) - person Stéphane; 12.05.2011
comment
@ Стефан, нет, потому что AsEnumerable() просто снова даст вам List<T>, поэтому, если вы надеетесь гарантировать, что его нельзя будет вернуть, вы ничего не получили. - person Jon Hanna; 04.09.2015