Это часть серии статей:

Enumerable.Empty ‹T› ()

Enumerable.Empty<T>() - статический метод в пространстве имен System.Linq и возвращает простейшую реализацию IEnumerable<T>. Я думаю, что интересно посмотреть, как это действительно работает, чтобы лучше понять перечисление в .NET.

В предыдущей статье я писал, что возвращается

реализация IEnumerable, которая генерирует реализации IEnumerator, где метод MoveNext () всегда возвращает false .

Вот как это выглядит в коде:

ПРИМЕЧАНИЕ. Это не реализация, найденная в System.Linq. Реализация до версии Core 2.1 очень сложна и неэффективна. Текущая реализация, найденная в репозитории corefx, намного ближе к той, что описана в этой статье: Я написал статью, сравнивающую эти две и многие другие формы реализации Empty<T>().

Enumerable.Empty<T>() - это просто статический метод в статическом классе. Он возвращает новый экземпляр внутренней структуры EmptyEnumerable<T> , которая реализует IEnumerable<T>.

EmptyEnumerable<T> является структурой для повышения производительности. Используя синтаксис C # 7.2, он помечен как только для чтения, поскольку IEnumerables должен быть неизменяемым и Ключевое слово может повысить производительность. Оно реализует как универсальную, так и неуниверсальную версии GetEnumerator(). Неуниверсальный реализован явно. Оба возвращают новый экземпляр внутренней структуры Enumerator.

Для случаев, отличных от Empty, перечислимый экземпляр может быть передан как аргумент in конструктора перечислителя, чтобы перечислитель мог получить доступ к перечисляемым частным полям.

Enumerator также является структурой и реализует IEnumerator<T>. Он наследует T от своей внешней структуры. Он реализует MoveNext(), всегда возвращая false. Общее и неуниверсальное свойство Current возвращает значение по умолчанию T. Reset() может быть пустым методом в этом случае, но я создаю исключение, чтобы показать вам, что делают другие IEnumerators , когда они не поддерживают Reset(). Dispose() пуст, так как ему не нужно освобождать ресурсы.

Он помечен как только для чтения для этого конкретного случая, но другие IEnumerators не только для чтения, поскольку они меняют внутреннее состояние при вызове MoveNext().

Это хороший шаблон для реализации других IEnumerable. Для более сложной реализации посмотрите мою другую статью Как использовать Span<T> и Memory<T>.

В качестве любопытства вы также можете реализовать Enumerable.Empty<T>() в паре строк. Компилятор делает всю работу за вас ...

Проверьте сгенерированный код на SharpLab.io.

Производительность (добавлено 09.09.2018)

Показанный выше исходный код является более простой для понимания и более производительной версией по сравнению с той, которая в настоящее время находится в LINQ. Недавний запрос на слияние в репозитории dotnet / corefx изменил его на нечто очень близкое к этому. Разница в том, что они используют синглтон класса, который реализует как IEnumerable, так и IEnumerator (возможно только потому, что он неизменяемый).

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