01Стандартная библиотека C# и .NET (BCL)

Уровень 1: Foundation

Коллекции и структуры данных C#/.NET

1. System.Collections.Generic — Полный обзор

Основные коллекции и их назначение

КоллекцияНазначениеОперации
List<T>Динамический массив, доступ по индексуДобавление в конец, поиск по индексу
Dictionary<TKey, TValue>Хеш-таблица, ключ-значениеБыстрый поиск/вставка/удаление по ключу
HashSet<T>Уникальные элементы, операции над множествамиПроверка Contains, объединение, пересечение
SortedSet<T>Отсортированные уникальные элементыЗапросы по диапазону, порядковые операции
LinkedList<T>Двусвязный списокВставка/удаление в середине
Queue<T>Очередь FIFOEnqueue/Dequeue
Stack<T>Стек LIFOPush/Pop

Производительность обобщённых и необобщённых коллекций

// Обобщённая — типобезопасная, без боксинга
List<int> genericList = new() { 5, 9, 1, 4 };

// Необобщённая — требует боксинга/анбоксинга, ~75x медленнее
ArrayList nonGenericList = new() { 5, 9, 1, 4 };

Ключевые преимущества: Типобезопасность на этапе компиляции; отсутствие боксинга/анбоксинга для значимых типов (снижает нагрузку на GC); производительность: сортировка List<int> ~75x быстрее, чем ArrayList.

2. Временная сложность операций

List<T>

ОперацияСложностьПримечания
Индексатор []O(1)Прямой доступ к массиву
Add (в конец)O(1)*Амортизированная, O(n) при изменении размера
Insert (по индексу)O(n)Сдвиг элементов
RemoveAtO(n)Сдвиг элементов
Remove (по значению)O(n)Линейный поиск + сдвиг
ContainsO(n)Линейный поиск
SortO(n log n)IntroSort
BinarySearchO(log n)Требует отсортированного списка

Dictionary<TKey, TValue>

ОперацияСложностьПримечания
Индексатор [] (get/set)O(1)*Хеш-таблица с цепочками
TryGetValueO(1)*Амортизированная
AddO(1)*O(n) при изменении размера
RemoveO(1)*
ContainsKeyO(1)*

*При равномерном распределении хеш-кодов. Худший случай (все ключи в одной корзине): O(n).

HashSet<T>

ОперацияСложностьПримечания
AddO(1)*O(n) при изменении размера
ContainsO(1)*
RemoveO(1)*
SetEquals, IsSubsetOf и др.O(n)Перебор элементов

SortedSet<T>

ОперацияСложностьПримечания
AddO(log n)Балансировка красно-чёрного дерева
ContainsO(log n)
RemoveO(log n)
GetViewBetweenO(log n)Создаёт представление без копирования
Min / MaxO(1)Левый/правый край дерева

LinkedList<T>

ОперацияСложностьПримечания
AddFirst/AddLastO(1)
InsertAfter/BeforeO(1)При наличии ссылки на узел
Find (по значению)O(n)Линейный обход
Remove (по узлу)O(1)
Remove (по значению)O(n)Поиск + удаление

Queue<T> и Stack<T>

Обе используют кольцевой массив:

ОперацияQueue<T>Stack<T>
Enqueue / PushO(1)*O(1)*
Dequeue / PopO(1)*O(1)
PeekO(1)O(1)

3. Внутреннее устройство коллекций

List<T> — динамический массив со стратегией роста x2

public class List<T>
{
    private const int DefaultCapacity = 4;
    private T[] _items;      // Внутренний массив
    private int _size;        // Фактическое количество элементов
    private int _version;     // Версия для обнаружения изменений при итерации

    public void Add(T item)
    {
        if (_size == _items.Length)
            EnsureCapacity(_size + 1);  // Изменение размера: удвоение ёмкости
        _items[_size++] = item;
        _version++;
    }
}

Ключевые моменты: Начальная ёмкость: 0 (пустой _emptyArray); первый Add → ёмкость становится 4; каждое последующее изменение размера → ёмкость удваивается (4→8→16→32...); для 10 млн элементов требуется всего ~24 операции изменения размера.

Оптимизация: если размер известен — установите ёмкость заранее:

var list = new List<int>(expectedCount); // Избегает всех операций изменения размера

Dictionary<TKey, TValue> — хеш-таблица с цепочками (цепочки коллизий)

.NET использует раздельные цепочки, а не открытую адресацию. В .NET Core / .NET 5+ структура была оптимизирована для значительного сокращения потребления памяти и ускорения вычислений.

public class Dictionary<TKey, TValue> where TKey : notnull
{
    private struct Entry
    {
        public uint hashCode; // Полный 32-битный хеш-код ключа (uint). В .NET Core/5+ знак не маскируется
        public int next;      // Индекс следующей записи в цепочке, -1 если последняя
        public TKey key;
        public TValue value;
    }

    // Оптимизация компактных (byte-sized) корзин (начиная с .NET Core 2.1):
    // Вместо фиксированного int[] (4 байта на корзину) тип массива корзин выбирается динамически:
    // - byte[] для емкостей < 256 (1 байт на корзину)
    // - ushort[] для емкостей < 65536 (2 байта на корзину)
    // - int[] для больших емкостей (4 байта на корзину)
    private object? _buckets;   // Хранит массив корзин (byte[], ushort[] или int[])
    private Entry[] _entries;   // Все записи (включая удалённые)
    private int _count;         // Количество активных записей
    private int _freeList;      // Индекс первого свободного слота в entries
    private int _freeCount;     // Количество свободных слотов
}

Алгоритм поиска:

private ref TValue FindValue(TKey key)
{
    if (_buckets != null)
    {
        uint hashCode = (uint)_comparer.GetHashCode(key);
        
        // В .NET Core / 5+ деление по модулю оптимизировано через умножение (FastMod) с помощью специального множителя.
        // Ниже показана логическая цепочка обхода коллизий:
        int i = GetBucket(hashCode);
        Entry[] entries = _entries;
        while ((uint)i < (uint)entries.Length)
        {
            if (entries[i].hashCode == hashCode && _comparer.Equals(entries[i].key, key))
                return ref entries[i].value;  // Найдено!
            i = entries[i].next;
        }
    }
    throw new KeyNotFoundException();
}

Почему цепочки вместо открытой адресации?

  • Цепочки лучше справляются с высокими коэффициентами загрузки (>70%).
  • Открытая адресация (используется в старом Hashtable) страдает от кластеризации при плохих хешах.
  • Цепочки дают более стабильную и предсказуемую производительность при росте коллекции.

SortedSet<T> — красно-чёрное дерево

public class SortedSet<T>
{
    private struct Node
    {
        public T Item;           // Значение
        public Node Left;        // Левый потомок (меньше)
        public Node Right;       // Правый потомок (больше)
        public Node Parent;      // Родитель
        internal bool Color;     // true = Красный, false = Чёрный
    }
}

Свойства красно-чёрного дерева: Каждый узел красный или чёрный; корень всегда чёрный; красный узел не может иметь красный потомок; все пути от узла до листьев содержат одинаковое количество чёрных узлов. Гарантия: высота дерева ≤ 2*log2(n+1), обеспечивая O(log n) для всех операций.

LinkedList<T>, Queue<T>, Stack<T>

LinkedList<T> — двусвязный список; LinkedListNode<T> — публичная обёртка; вставка/удаление по ссылке на узел: O(1); поиск по значению: O(n).

Queue<T> и Stack<T> — кольцевой массив:

private T[] _array;
private int _head;      // Индекс первого элемента
private int _tail;      // Индекс следующего свободного слота
private int _size;

4. Параллельные коллекции (System.Collections.Concurrent)

ConcurrentDictionary<TKey, TValue>

var dict = new ConcurrentDictionary<string, int>();

// Атомарная вставка или обновление
dict.AddOrUpdate("key", 
    newValue: 1,
    updateValueFactory: (_, oldValue) => oldValue + 1);

// Атомарное получение или добавление
var value = dict.GetOrAdd("key", k => ComputeExpensiveValue(k));

// Безопасное удаление
dict.TryRemove("key", out int removedValue);

Внутреннее устройство: сегментная блокировка (lock striping) — несколько сегментов блокируются независимо, позволяя параллельные операции с разными ключами.

5. Неизменяемые коллекции (System.Collections.Immutable)

ИзменяемаяНеизменяемый эквивалентОсобенности
List<T>ImmutableList<T>Персистентная структура данных
Dictionary<K,V>ImmutableDictionary<K,V>Hash array mapped trie (HAMT)
HashSet<T>ImmutableHashSet<T>HAMT
SortedSet<T>ImmutableSortedSet<T>Персистентное красно-чёрное дерево

Паттерн Builder для эффективности

var builder = ImmutableHashSet.CreateBuilder<string>();
foreach (var item in largeCollection)
    builder.Add(item);  // O(1) — изменяемый builder

var frozen = builder.ToImmutable();  // O(1) — заморозка без копирования

6. Span<T> vs List<T> — Когда что использовать

КритерийList<T>Span<T>
Выделение памятиКуча (GC)Стек / inline (без GC)
РазмерДинамический (изменение размера)Фиксированный при создании
Совместимость с asyncДаНет (ref struct)
Хранение в поле классаДаНет
СрезGetRange() → новый объектSlice() → представление без выделения памяти

7. IEnumerable<T> vs IReadOnlyCollection<T> vs IReadOnlyList<T>

ИнтерфейсИндексаторCountПереборИзменяемый?
IEnumerable<T>НетНетДаНеизвестно
IReadOnlyCollection<T>НетДаДаНет
IReadOnlyList<T>Да O(1)*ДаДаНет

8. Практические упражнения

Бенчмарк: List<T> vs ArrayPool<T>

using System.Buffers;
using System.Diagnostics;
using BenchmarkDotNet.Attributes;

public class ListVsArrayPoolBenchmark
{
    [Benchmark]
    public int ListApproach()
    {
        var list = new List<int>(1000);
        for (int i = 0; i < 1000; i++) list.Add(i);
        int sum = 0;
        foreach (var x in list) sum += x;
        return sum;
    }

    [Benchmark]
    public int ArrayPoolApproach()
    {
        var array = ArrayPool<int>.Shared.Rent(1000);
        try
        {
            for (int i = 0; i < 1000; i++) array[i] = i;
            int sum = 0;
            for (int i = 0; i < 1000; i++) sum += array[i];
            return sum;
        }
        finally
        {
            ArrayPool<int>.Shared.Return(array);
        }
    }
}

Результаты: ArrayPool выигрывает при повторном использовании (меньше нагрузка на GC), List быстрее для одноразовых операций.

9. Контрольные вопросы

  1. Как работает GetHashCode() и почему важна правильная реализация?
  2. В чём разница между IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T>?
  3. Когда следует использовать неизменяемые коллекции?

LINQ и функциональное программирование в C#/.NET

1. Ленивое выполнение vs немедленное выполнение

var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// Определение запроса — выполнения ЕЩЁ НЕТ
var query = numbers
    .Where(n => n % 2 == 0)
    .Select(n => n * n);

// Выполнение происходит здесь (во время перечисления)
foreach (var item in query)
    Console.WriteLine(item);  // 4, 16, 36, 64, 100

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

2. yield return — внутреннее устройство state machine

yield return создаёт блок итератора, который компилятор преобразует в класс state machine, реализующий IEnumerator<T>:

public static IEnumerable<int> GetNumbers()
{
    yield return 1;   // Состояние 0
    yield return 2;   // Состояние 1
    yield return 3;   // Состояние 2
                      // Состояние -1 (завершено)
}

Промежуточная коллекция не создаётся — значения генерируются по требованию; состояние сохраняется между вызовами MoveNext().

3. IEnumerable<T> vs IQueryable<T>

АспектIEnumerable<T>IQueryable<T>
Место выполненияВ памяти (на клиенте)Удалённо (база данных, сервис)
Представление запросаДелегат Func<>Дерево выражений Expression<Func<>>
ФильтрацияЗагружает все данные, фильтрует в памятиПереводит в SQL/нативный запрос
Методы расширенияSystem.Linq.EnumerableSystem.Linq.Queryable

4. Стандартные операторы запросов

Проекция: Select — преобразование; SelectMany — сглаживание вложенных коллекций; OfType<T> — фильтрация по типу.

Фильтрация: Where, Distinct, Except, Intersect.

Группировка: GroupBy, ToLookup, GroupJoin.

Соединение:

var joined = customers
    .Join(orders,
        c => c.Id,
        o => o.CustomerId,
        (c, o) => new { Customer = c, Order = o });

Агрегация: Sum, Average, Min, Max, Count, Aggregate.

5. ValueTuple vs Tuple

АспектTuple<T1,T2,...>(T1, T2, ...) / ValueTuple
ТипСсылочный тип (class)Значимый тип (struct)
Имена полейВсегда Item1, Item2...Настраиваемые имена
ПроизводительностьВыделение в куче (GC)Стек/inline (без GC)
ДеконструкцияНетДа
Доступно с.NET 4.0.NET Core 2.0 / .NET Standard 2.1

6. Expression<T> — Как работает и где применяется

// Лямбда как КОД (скомпилирована в делегат)
Func<int, bool> code = x => x > 5;
code(10);  // True

// Лямбда как ДАННЫЕ (дерево выражений)
Expression<Func<int, bool>> data = x => x > 5;
// Сначала нужно скомпилировать:
Func<int, bool> compiled = data.Compile();

7. Бенчмарк: LINQ vs foreach для 1 млн элементов

МетодСреднее времяВыделено памяти
ForeachSum~1мс0 Б
LinqSum~3-5мс~20 КБ
ParallelLinqSum~2-3мс~100 КБ

Ключевой вывод: foreach быстрее для простых операций. LINQ добавляет накладные расходы на выделение делегатов.

Практика

Контрольные вопросы

  1. Что такое deferred execution и как это влияет на производительность?
  2. В чём разница между First(), FirstOrDefault(), Single(), SingleOrDefault()?
  3. Как работает Expression<T> и где применяется?

Делегаты, события и выражения в C#/.NET

1. Встроенные типы делегатов

ДелегатВозвращаемый типЛучше всего для
Func<T, TResult>TResultПреобразования, вычисления, LINQ Select
Action<T>voidПобочные эффекты, обратные вызовы
Predicate<T>boolУсловия, фильтрация (List.Find, List.RemoveAll)
Comparison<T>intСортировка пользовательских коллекций

2. Мультикаст-делегаты — внутреннее устройство

Мультикаст-делегат — это связанный список делегатов:

// [Delegate3] → [Delegate2] → [Delegate1] → null
//      ^первый              ^добавлен вторым     ^добавлен первым (голова)

Action<string> handlers = Message1;
handlers += Message2;
handlers += Message3;
handlers -= Message1;
handlers("Hello");  // Вызывает: Message2, Message3

Ключевые моменты: += добавляет в начало списка (O(1)); порядок вызова: в порядке добавления (FIFO); с Func — возвращается только возвращаемое значение последнего делегата!

3. События — паттерн издатель/подписчик

public class Publisher
{
    public event Action<string> MessageReceived;

    public void SendMessage(string message)
    {
        // Потокобезопасный паттерн вызова
        MessageReceived?.Invoke(message);
    }
}

4. Expression<T> — построение и компиляция деревьев выражений

// Программное построение: x => x > 5
var parameter = Expression.Parameter(typeof(int), "x");
var constant = Expression.Constant(5);
var body = Expression.GreaterThan(parameter, constant);
var lambda = Expression.Lambda<Func<int, bool>>(body, parameter);

5. Динамическая компиляция через Expression.Compile()

ПодходПервый вызовПоследующие вызовы
Рефлексия~100нс~100нс
Expression.Compile() + кэш~5мкс~5нс
Прямой вызов~1нс~1нс

Практика


Сопоставление с образцом и современные возможности C#

1. Эволюция сопоставления с образцом

// Паттерны типов (C# 7)
if (value is int i)
    Console.WriteLine($"Целое число: {i}");

// Реляционные паттерны (C# 9)
string Classify(double value) => value switch
{
    < -100.0 => "Очень низко",
    < 0.0    => "Низко",
    0.0      => "Ноль",
    > 0.0 and <= 100.0 => "Нормально",
    > 100.0  => "Высоко",
    _        => "Неизвестно",
};

// Паттерны свойств (C# 8)
if (person is { Age: >= 18, Email: not null })
    Console.WriteLine("Взрослый с email");

// Рекурсивные паттерны (C# 9)
bool IsAnyEndOnXAxis(Segment seg) =>
    seg is { Start: { Y: 0 } } or { End: { Y: 0 } };

// Паттерны списков (C# 8)
int[] CheckArray(int[] arr) => arr switch
{
    [0, 1, 2, ..] => new[] { 1 },
    [.. , 9, 8, 7] => new[] { 2 },
    [1, .., 9] => new[] { 3 },
    [var first, ..] => new[] { first },
    [] => Array.Empty<int>(),
};

2. switch Expression vs switch Statement

Аспектswitch Statementswitch Expression
Возвращает значениеНетДа
Требует break/gotoДаНет
Проверка полнотыНетПредупреждение компилятора
Безопасность nullРучная проверкаПаттерн null работает естественно

3. Типы record — равенство по значению и неразрушающее изменение

public record Person(string Name, int Age);

var p1 = new Person("Alice", 30);
var p2 = new Person("Alice", 30);

Console.WriteLine(p1 == p2);       // True! (равенство по значению)
Console.WriteLine(p1.Equals(p2));  // True!

Неразрушающее изменение с выражениями with:

var updated = original with { Age = 31 };
Console.WriteLine(original.Age);    // 30 (не изменился!)

4. ref struct, required, file-scoped namespaces

ref struct — структуры, которые должны находиться на стеке — не могут быть боксированы. required modifier (C# 11+) — принудительная инициализация свойств на этапе компиляции.

5. Source Generators

Генерация кода во время компиляции: Исходный код → [Source Generator] → Дополнительный код → Компилятор → IL.

АспектISourceGeneratorIIncrementalSourceGenerator
Выполняется приПолная компиляцияТолько изменённые файлы
ПроизводительностьМедленнееБыстрее (инкрементально)
РекомендацияНаследиеИспользуйте это

Практика

Уровень 2: Advanced

Span<T>, Memory<T> и stackalloc

1. Span<T> — срез без выделения памяти

Span<T> — это ref struct, представляющий непрерывную область памяти любого размера без выделения в куче:

// Из массива — Span просматривает существующий массив (без копирования!)
int[] array = { 1, 2, 3, 4, 5 };
Span<int> spanFromArr = array;

// Из сегмента массива — представление части без выделения памяти
Span<int> slice = array.AsSpan(1, 3);  // [2, 3, 4] — без копирования!

// Из памяти стека — действительно нулевое выделение
Span<int> stackSpan = stackalloc int[100];

// Из строки — доступ к символам без выделения памяти
string text = "Hello, World!";
ReadOnlySpan<char> spanFromStr = text;

2. Memory<T> — альтернатива Span<T> на основе кучи

ВозможностьSpan<T>Memory<T>
Типref structstruct (указатель + длина)
Совместимость с asyncНетДа
Может быть полем классаНетДа
Получить SpanН/ДСвойство .Span

3. ArrayPool<T>.Shared — аренда массивов

int[] rented = ArrayPool<int>.Shared.Rent(100);
try
{
    for (int i = 0; i < 100; i++) rented[i] = i * 2;
    Process(rented.AsSpan(0, 100));
}
finally
{
    ArrayPool<int>.Shared.Return(rented);
}

4. stackalloc — выделение на стеке

Span<byte> buffer = stackalloc byte[1024];  // 1KB на стеке
Span<int> initialized = stackalloc int[5] { 1, 2, 3, 4, 5 };

// Очистка — содержимое стека не определено!
Span<byte> dirty = stackalloc byte[100];
dirty.Clear();

5. ReadOnlySequence<T> — discontinuous буферы

ReadOnlySequence<T> представляет последовательность блоков памяти (возможно, не непрерывных). Идеально для сетевых и файловых потоков.

foreach (var segment in sequence)
{
    Span<byte> span = segment.Span;
    ProcessChunk(span);
}

Практика


Reflection и Emit в C#/.NET

1. System.Reflection — основные типы

// Получение объектов Type
Type t1 = typeof(int);
Type t2 = typeof(List<string>);
Type t3 = someObject.GetType();

// Информация о типе
Console.WriteLine(t1.Name);          // "Int32"
Console.WriteLine(t1.IsValueType);   // true

// Получение свойств и методов
PropertyInfo[] props = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
MethodInfo method = type.GetMethod("PublicMethod");

2. Стоимость рефлексии — кэширование и CreateDelegate

МетодВремя на вызовВыделено памяти
Прямой доступ~1нс0 Б
PropertyInfo.GetValue (кэшированный)~20-50нс0 Б
Delegate.CreateDelegate~5-10нс0 Б
Expression.Compile getter~5-10нс0 Б
Рефлексия без кэша~100-500нсварируется
// Создание быстрого делегата-геттера
var getter = DelegateFactory.CreateGetter<Person, string>("Name");
string name = getter(person);  // ~5нс — скорость, близкая к прямому доступу!

3. System.Reflection.Emit — динамическая генерация кода

// Создание динамической сборки
AssemblyName asmName = new("DynamicAssembly");
AssemblyBuilder asmBuilder = AssemblyBuilder.DefineDynamicAssembly(
    asmName, AssemblyBuilderAccess.Run);

// Определение типа
TypeBuilder typeBuilder = module.DefineType("DynamicGreeter", TypeAttributes.Public | TypeAttributes.Class);

// Генерация IL
ILGenerator il = methodBuilder.GetILGenerator();
il.Emit(OpCodes.Ldstr, "Hello, ");
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, typeof(string).GetMethod("Concat")!);
il.Emit(OpCodes.Ret);

// Создание типа
Type createdType = typeBuilder.CreateType();

Практика


Атрибуты и метаданные в C#/.NET

1. Пользовательские атрибуты

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyAttribute : Attribute
{
    public string Description { get; }
    public MyAttribute(string description) => Description = description;
}

[My("Это сущность Person")]
public class Person { }

2. Чтение метаданных: во время выполнения vs на этапе компиляции

АспектРефлексия (runtime)Roslyn (compile-time)
Когда выполняетсяВо время выполненияВо время компиляции
ПроизводительностьСтоимость при каждом вызовеНулевая стоимость runtime
Варианты использованияВалидация, DI, сериализацияГенерация кода, анализаторы

3. API Roslyn — SyntaxTree и SemanticModel

SyntaxTree tree = CSharpSyntaxTree.ParseText(source);
Compilation compilation = CSharpCompilation.Create("MyAssembly", syntaxTrees: new[] { tree });
SemanticModel semanticModel = compilation.GetSemanticModel(tree);

// Разрешение типов
INamedTypeSymbol symbol = semanticModel.GetDeclaredSymbol(classNode)!;
Console.WriteLine(symbol.ToDisplayString());  // global::Person

Практика

Уровень 3: Expert

Внутренности BCL и среда выполнения

1. Понимание исходного кода BCL

РесурсURLНазначение
.NET Runtime (GitHub)github.com/dotnet/runtimeОсновные библиотеки, среда выполнения, JIT
Reference Sourcereferencesource.microsoft.comИсходный код .NET Framework 4.8
SharpLabsharplab.ioПросмотр IL/state machine

2. ObjectPool<T> — внутреннее устройство

var pool = new ObjectPool<MyResource>(() => new MyResource(), maxRetained: 100);

MyResource resource = pool.Rent();
try { resource.DoWork(); }
finally { pool.Return(resource, shouldReset: true); }

3. Lazy<T> — режимы потокобезопасности

РежимПотокобезопасностьПроизводительность
NoneНе потокобезопасноБыстрее всего
PublicationOnlyПервый побеждаетБыстро
ExecutionAndPublicationПодобно мьютексуМедленнее

4. ConditionalWeakTable<TKey, TValue> — слабые ссылки

ConditionalWeakTable хранит слабые ссылки на ключи и сильные ссылки на значения. Когда ключ собирается GC, связанное значение также удаляется.

5. AsyncLocal<T> vs [ThreadStatic]

Аспект[ThreadStatic]AsyncLocal<T>
Область храненияНа поток ОСНа контекст выполнения
Работает с async/await❌ Значение теряется✅ Передаётся через продолжения
Безопасность ThreadPool⚠️ Старые значения✅ Чистый на async-контекст
Лучше всего дляНаследие синхронного кодаСовременные async-приложения

6. Внутреннее устройство параллельных коллекций

ConcurrentDictionary — сегментная блокировка (~16 параллельных записей); ConcurrentQueue — очередь Майкла-Скотта без блокировок (CAS/CompareExchange).

Практика


Контрольная точка модуля 1

Проект: Высокопроизводительная библиотека коллекций

Создать высокопроизводительную библиотеку коллекций с:

  • Zero-allocation API через Span<T>
  • Кастомными comparer'ами и hash code providers
  • Полным LINQ-compatible интерфейсом
  • Benchmark сравнением с BCL аналогами
Критерии прохождения:
  • Все benchmark'и показывают конкурентную производительность
  • Code coverage > 90%
  • Документация XML comments для всех public API