C# 1001 notes

@csharp_1001_notesVerifiedLive API

CategoryOtherLanguageRUFirst seenJul 06, 2026QualityVerified
Open in Telegram
Subscribers1K
Avg views1.9K
ERR181.2%
Price-- RUB
Price / subscriber--
Price / view--
Metrics updated: Jul 07, 2026
Recent posts
Record-типы в C#: когда модель данных не должна быть обычным class record в C# удобен там, где объект описывает данные, а не поведение и идентичность. Типичный пример: public record User(string Name, int Age); Компилятор сам сгенерирует конструктор, Equals , GetHashCode , ToString и деконструкцию. Но главное отличие не в сокращении кода, а в семантике. Обычный class сравнивается по ссылке: var a = new UserClass("Alice", 25); var b = new UserClass("Alice", 25); Console.WriteLine(a == b); // false record сравнивается по значению: var a = new User("Alice", 25); var b = new User("Alice", 25); Console.WriteLine(a == b); // true Это делает record хорошим выбором для DTO, read models, value objects, событий, результатов запросов и моделей, где важны значения полей. Ещё одна удобная вещь - with . Можно создать копию объекта, изменив только нужные поля: var user = new User("Alice", 25); var updated = user with { Age = 26 }; Console.WriteLine(updated); // User { Name = Alice, Age = 26 } Но есть важный нюанс: with делает поверхностную копию. Если внутри есть изменяемая коллекция, она не станет автоматически immutable. public record Team(string Name, List<string> Members); Такой record всё ещё может меняться через Members.Add(...) . Поэтому для реально неизменяемых моделей лучше использовать immutable-коллекции или аккуратно закрывать доступ к изменяемому состоянию. record не заменяет class везде. Если у объекта есть жизненный цикл, identity, состояние и бизнес-поведение, обычный класс часто будет честнее. Но когда тип нужен как чистая модель данных, record убирает шум и делает намерение в коде очевидным.
828Jun 30
Методы, их перегрузка и расширения. Бесплатный урок специализации «C#-разработчик» Методы — одна из базовых вещей в C#, без которой невозможно нормально писать, читать и поддерживать код. Но у начинающих разработчиков часто всё смешивается: где обычный метод, где перегрузка, как работает сигнатура, зачем нужны параметры по умолчанию и в каких случаях использовать params. На открытом уроке 2 июля в 20:00 разберём, что такое метод в C#, как писать собственные методы и как использовать перегрузку без хаоса в коде. Поговорим о сигнатуре метода, параметрах по умолчанию, ключевом слове params и методах-расширениях. На примерах покажем, как эти механики помогают делать код понятнее, гибче и удобнее для повторного использования. Урок не для тех, кто хочет просто «выучить синтаксис» без понимания, как методы влияют на структуру программы. 👉 Записаться: https://otus.pw/wkKs/?erid=2W5zFG7dRZ3 Реклама. ООО "ОТУС ОНЛАЙН-ОБРАЗОВАНИЕ". ИНН 9705100963.
810Jun 30
Продвинутый C#-трюк: обновляй `Dictionary` без двойного поиска Многие пишут так: if (dict.TryGetValue(key, out var value)) { dict[key] = value + 1; } else { dict[key] = 1; } Проблема: ты сначала ищешь ключ через TryGetValue , а потом снова лезешь в словарь через dict[key] . В hot path это лишняя работа. Есть более взрослый вариант: using System.Runtime.InteropServices; ref var count = ref CollectionsMarshal.GetValueRefOrAddDefault( dict, key, out var exists ); if (!exists) { count = 0; } count++; Что происходит: 1. C# получает ссылку прямо на значение внутри Dictionary 2. ключ ищется один раз 3. значение можно менять без повторного обращения 4. меньше лишних операций в tight loop Где это полезно: 1. счётчики событий 2. парсеры 3. агрегации 4. обработка логов 5. high-performance backend code Но есть важный нюанс: не меняй структуру словаря, пока держишь ref . То есть не делай Add , Remove , Clear рядом с этой ссылкой. Это не трюк для каждого CRUD-сервиса. Это инструмент для мест, где C# уже упёрся в производительность, и ты начинаешь выжимать лишние аллокации и лишние lookup’и.
1.4KJun 24
Что выведет код? using System; using System.Collections.Generic; using System.Linq; static IEnumerable<int> GetNumbers() { Console.Write("A"); yield return 1; Console.Write("B"); yield return 2; } var query = GetNumbers().Where(x => x > 0); Console.Write(query.First()); Console.Write(query.Count()); А — A12 Б — A1B2 В — A1AB2 Г — Error Правильный ответ: В — A1AB2. Почему: IEnumerable и LINQ выполняются лениво. First() запускает перебор один раз и доходит только до первого элемента: печатает A, потом 1. Count() запускает перебор заново: снова A, потом B, и в конце печатает 2.
1.2KJun 23
Аллокации, которых нет в коде: охота на скрытый боксинг в .NET 10 Самая дорогая аллокация в вашем сервисе та, которой нет в исходниках. Вы написали struct ради zero-allocation, прошли code review, а в проде Gen0-коллекции все равно идут косяком. Потому что между вашим кодом и машинным кодом стоит компилятор, и он молча упаковывает ваш value-тип в кучу там, где вы этого не просили — а на код-ревью этого не видно. TL;DR. Боксинг (boxing) в .NET - это не только object o = 42. Он прячется в вызовах интерфейсных методов на struct, в дефолтном ValueType.Equals, в params object[]-аргументах, в foreach по интерфейсу и в замыканиях. При этом часть “классических” примеров боксинга из старых гайдов на современном рантайме уже не аллоцирует — JIT научился их вырезать, и слепо копировать советы десятилетней давности вредно. Ниже — карта мест, где боксинг живёт и сейчас, отдельный разбор того, что рантайм уже оптимизировал, реальный мини-кейс, воспроизводимый бенчмарк на BenchmarkDotNet с MemoryDiagnoser, способ ловить упаковку через DOTNET_JitDisasm и dotnet-gcdump, и паттерны лечения без потери читаемости. О версиях и числах. Всё прверялось на .NET 10 (текущий LTS) и C# 13/14-уровне компилятора, Release, без отладчика, BenchmarkDotNet с MemoryDiagnoser. На .NET 8/9 поведение в основном такое же, но отдельные оптимизации JIT отличаются между мажорными версиями — поэтому главный принцип статьи: не верьте на слово (в том числе мне), гоняйте MemoryDiagnoser на своей версии рантайма. Числа в таблицах ниже - иллюстративные, порядок величины, а не точные замеры с вашего железа. Пролог: “у нас же всё на struct, откуда Gen0?” Сервис на горячем пути считает метрики: миллионы маленьких readonly struct-значений в секунду, никакого new, никаких классов в hot path. По задумке — ноль аллокаций. На дашборде — стабильный поток Gen0-коллекций раз в несколько секунд под нагрузкой. Профайлер показывает аллокации, но стек ведёт в метод, где в коде нет ни одного new. Там цикл по интерфейсу, пара вызовов .Equals(), передача значения в params-метод лога. Глазами — чисто. В машинном коде — box-инструкции на каждой итерации. Это и есть скрытый боксинг: компилятор C# и JIT упаковывают ваш struct в объект на куче, потому что в конкретной точке кода value-тип нужно представить как ссылочный. Симптом — Gen0-коллекции “из ниоткуда”, и его не видно ни в code review, ни в дампе, пока не посмотришь на IL или дизасм. Если тема близка - я регулярно разбираю такие штуки по C# и .NET (внутренности рантайма, перформанс, неочевидные грабли с замерами и дизасмом) в своём Telegram-канале: t.me/csharp_ci . Заходите, если интересно копаться глубже. Что такое боксинг и почему он стоит дорого Боксинг — это упаковка value-типа (struct, enum, примитив) в объект на управляемой куче. Рантайму нужно выделить заголовок объекта, скопировать туда значение и вернуть ссылку. Анбоксинг - обратная операция с проверкой типа. Цена не в самой инструкции, а в последствиях: каждая упаковка - это аллокация в Gen0. Много мелких аллокаций на горячем пути означают частые Gen0-коллекции, паузы (пусть и короткие), вытеснение полезных данных из кэша и общий рост CPU на ровном месте. На сервисе с SLA по p99 это бьёт по хвосту латентности так же, как и любая другая лишняя аллокация. В IL боксинг виден явно - инструкция box. Именно её мы и будем искать. Читать дальше: https://habr.com/ru/articles/1049236/
1.5KJun 19