TGViewer
yet another dev yet another dev @yet_another_dev · 382 subscribers
Post #149 376
👩‍💻 Пишем производительный C# код при работе с коллекциями

Публикую следующую часть статьи про производительность коллекций. Сегодня про Enumerator.

---

Предположим, у нас есть массив _transactionsArray и список _transactionsList. Сама транзакция выглядит следующим образом:

public record class Transaction(
Guid Id,
int Amount,
string Description);


Существует множество способов пройтись по двум вышеупомянутым коллекциям:

// Индексатор массива
var sum = 0;
for (var i = 0; i < _transactionsArray.List; i++) {
sum += _transactionsArray[i].Amount;
}

// Индексатор списка
var sum = 0;
for (var i = 0; i < _transactionsList.Count; i++) {
sum += _transactionsList[i].Amount;
}

// Энумератор IEnumerable<T>
var sum = 0;
var collection = (IEnumerable<T>)_transactionsArray;
foreach (var item in collection) {
sum += item.Amount;
}

var sum = 0;
var collection = (IEnumerable<T>)_transactionsList;
foreach (var item in collection) {
sum += item.Amount;
}

// Энумератор List<T>
var sum = 0;
foreach (var item in _transactionsList) {
sum += item.Amount;
}


Один из этих способов аллоцирует больше памяти, чем остальные – это приведение _transactionsList к IEnumerable<T>. Причина кроется в реализации энумератора для списков. У типа List<T> есть собственный энумератор-структура.

Если использовать foreach с типом List<T>, то проблем не возникает. Компилятор C# сгенерирует код, который будет использовать эту структуру напрямую.

// Исходный код
var nums= new List<int>();
var sum = 0;
foreach (n in nums) sum +=n;

// Код после компиляции
var nums = new List<int>();
var num = 0;
List<int>.Enumerator enumerator =
nums.GetEnumerator();
while (enumerator.MoveNext()) {
num += enumerator.Current;
}


Но если привести List<T>, например, к IList<T> или IReadOnlyList<T>, т.е. к любому интерфейсу, реализующему IEnumerable<T>, то произойдёт неявная упаковка List<T>.Enumerator. Это происходит из-за того, что IEnumerable<T>.GetEnumerator() возвращает интерфейс IEnumerator<T>.

// Исходный код
var nums = new List<int>();
var collection = (IEnumerable<int>) nums;
var sum = 0;
foreach (n in collection) sum +=n;

// Код после компиляции
var nums = new List<int>();
var collection = ((IEnumerable<int>)nums);
var num = 0;
IEnumerator<int>.Enumerator enumerator =
// упаковка
collection.GetEnumerator();
while (enumerator.MoveNext()) {
num += enumerator.Current;
}


Аналогичный подход с энумераторами также встречается и других коллекциях: LinkedList<T>, Stack<T>, Queue<T> и т.д. Исключением являются, например, массивы.

Насколько упаковка влияет на производительность можно понять из графиков. Бенчмарк 100 раз прошёлся по каждой из коллекции. На первом графике ось Y отображает проценты от бенчмарка с массивом, а на втором — миллисекунды. Шкала оси X в обоих случаях логарифмическая.

Если говорить об абсолютных значениях, разница не так велика — десятки миллисекунд для коллекций размером 100 000 элементов и более. Наибольшее коварство такое поведение представляет, когда у вас много небольших коллекций. Я в своей практике встречал проблему, когда упаковка энумератора приводила к аллокациям десятков и сотен мегабайт. Это было большое дерево директорий, а доступ к дочерним директориям был только через IReadOnlyList<T>.
  • 👍 9
More from @yet_another_dev
  1. Sep 21, 2026Опубликовал вчера ролик в одной запрещённой в России соцсети про то, как сходил на выборы.…
  2. Sep 20, 2026Мы пришли в 7:50 и очередь уже была 🥲 Пообщались с другими людьми. Многие приехали из дру…
  3. Sep 19, 2026Post #383
  4. Sep 18, 2026Последние пару недель на чат нападают боты со спамом (прикрыл стикером). Поэтому чат тепер…
  5. Sep 17, 2026Что интересного в этой статье: 1. Потрачено $120К, а агенты суммарно отработали около 3-х…
  6. Sep 17, 2026В Microsoft переписали рантайм GitHub Copilot с TypeScript на Rust при помощи агентов. Под…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →