Bezdepbonusu7Программирование: статьи и ответы

Вопрос

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

Автор вопроса: StéphaneОтветов: 6

Предположим, у вас есть класс 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>();
}
  • Это класс данных. Я намерен использовать этот класс в качестве класса модели при реализации шаблона репозитория с Entity Framework 4.0 (поиграться...). поэтому я думаю, что нормально иметь здесь публичный сеттер, не так ли? Stéphane 12.12.2009
  • serbech, как вы добавите роль (внутри Person), когда другой код может установить любой производный класс IEnumerable для списка? На что ты его бросишь? Henk Holterman 12.12.2009
  • Я понимаю вашу точку зрения, мне, вероятно, следует иметь внутренний IList или даже список здесь? И это просто устранит мою первоначальную проблему... Stéphane 12.12.2009
  • Я использую Enumerable.Empty‹T› в модульном тестировании, чтобы указать неудовлетворительные тесты пути, чтобы помочь сообщить о намерении. Как многие уже указывали, бонусом является отсутствие распределения на GC, что помогает, когда число ваших модульных тестов исчисляется сотнями или более. jjhayter 09.06.2015
  • Я согласен, что вы хотели бы удалить set и просто использовать интерфейс IList напрямую. Однако, если вы хотите сохранить его как IEnumerable с set, действительно кажется, что вам следует инициализировать его как null. Точно так же вам не нужно инициализировать Name пустой строкой - поскольку у нее есть set, вы просто инициализируете null и получаете код клиента set. Dax Fohl 07.11.2016
  • Теперь можно также использовать Array.Empty<Role>(), который явно является массивом и, следовательно, IList<T>. В Core это фактически один и тот же массив, отличающийся только типом. Jon Hanna 28.01.2017

constructorlinq

Ответы

Ответ 1 · рейтинг 205 · Vadym Chekan

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

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

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

Ответ 2 · рейтинг 106 · Tommy Carlier

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

Ответ 3 · рейтинг 30 · Drew Noakes

Что касается производительности, давайте посмотрим, как реализован 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>()));

Ответ 4 · рейтинг 13 · Neil Barnwell

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

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

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

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

Ответ 5 · рейтинг 7 · Lee

Проблема с вашим подходом заключается в том, что вы не можете добавлять какие-либо элементы в коллекцию - у меня была бы частная структура, такая как список, а затем выставлять элементы как 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.

Ответ 6 · рейтинг 7 · Hans Passant

Типичная проблема с представлением частного списка как 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;
  }
}

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы