en
Feedback
C# 1001 notes

C# 1001 notes

Open in Telegram

Регулярные короткие заметки по C# и .NET. Просто о сложном для каждого. admin - @haarrp

Show more
6 539
Subscribers
+324 hours
-67 days
No data30 days
Attracting Subscribers
June '26
June '26
+59
in 0 channels
May '26
+67
in 0 channels
Get PRO
April '26
+66
in 0 channels
Get PRO
March '26
+57
in 0 channels
Get PRO
February '26
+138
in 0 channels
Get PRO
January '26
+91
in 1 channels
Get PRO
December '25
+85
in 0 channels
Get PRO
November '25
+132
in 3 channels
Get PRO
October '25
+87
in 1 channels
Get PRO
September '25
+101
in 4 channels
Get PRO
August '25
+81
in 0 channels
Get PRO
July '25
+80
in 0 channels
Get PRO
June '25
+83
in 0 channels
Get PRO
May '25
+60
in 0 channels
Get PRO
April '25
+80
in 0 channels
Get PRO
March '25
+107
in 0 channels
Get PRO
February '25
+105
in 0 channels
Get PRO
January '25
+169
in 0 channels
Get PRO
December '24
+202
in 1 channels
Get PRO
November '24
+203
in 2 channels
Get PRO
October '24
+364
in 1 channels
Get PRO
September '24
+241
in 0 channels
Get PRO
August '24
+262
in 1 channels
Get PRO
July '24
+168
in 0 channels
Get PRO
June '24
+398
in 2 channels
Get PRO
May '24
+379
in 35 channels
Get PRO
April '24
+304
in 43 channels
Get PRO
March '24
+314
in 20 channels
Get PRO
February '24
+281
in 1 channels
Get PRO
January '24
+572
in 45 channels
Get PRO
December '23
+941
in 48 channels
Get PRO
November '23
+655
in 2 channels
Get PRO
October '23
+61
in 0 channels
Get PRO
September '23
+80
in 0 channels
Get PRO
August '23
+507
in 0 channels
Get PRO
July '23
+487
in 0 channels
Get PRO
June '23
+88
in 0 channels
Get PRO
May '23
+569
in 0 channels
Get PRO
April '23
+21
in 0 channels
Get PRO
March '23
+28
in 0 channels
Get PRO
February '23
+23
in 0 channels
Get PRO
January '23
+28
in 0 channels
Get PRO
December '22
+32
in 0 channels
Get PRO
November '22
+31
in 0 channels
Get PRO
October '22
+40
in 0 channels
Get PRO
September '22
+28
in 0 channels
Get PRO
August '22
+43
in 0 channels
Get PRO
July '22
+321
in 0 channels
Get PRO
June '22
+41
in 0 channels
Get PRO
May '22
+37
in 0 channels
Get PRO
April '22
+180
in 0 channels
Get PRO
March '220
in 0 channels
Get PRO
February '220
in 0 channels
Get PRO
January '220
in 0 channels
Get PRO
December '210
in 0 channels
Get PRO
November '210
in 0 channels
Get PRO
October '210
in 0 channels
Get PRO
September '210
in 0 channels
Get PRO
August '210
in 0 channels
Get PRO
July '210
in 0 channels
Get PRO
June '210
in 0 channels
Get PRO
May '210
in 0 channels
Get PRO
April '21
+1
in 0 channels
Get PRO
March '21
+45
in 0 channels
Get PRO
February '21
+54
in 0 channels
Get PRO
January '21
+42
in 0 channels
Get PRO
December '20
+1 304
in 0 channels
Date
Subscriber Growth
Mentions
Channels
24 June+6
23 June+1
22 June+6
21 June+1
20 June0
19 June+2
18 June+1
17 June+3
16 June+2
15 June+2
14 June+4
13 June+2
12 June+1
11 June0
10 June+1
09 June+3
08 June+1
07 June+3
06 June+1
05 June+3
04 June+5
03 June+4
02 June+5
01 June+2
Channel Posts
Продвинутый 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’и.

2
🚀 DDD на C#: от теории к микросервису за 6 недель Если ты пишешь на C# и в какой-то момент начал чувствовать, что вроде всё
🚀 DDD на C#: от теории к микросервису за 6 недель Если ты пишешь на C# и в какой-то момент начал чувствовать, что вроде всё работает, но как-то костыльно — это тревожный сигнал. Новая фича затрагивает десятки файлов. Тесты становятся сложнее самого кода. Любое изменение заставляет переживать, что сломается что-то ещё. Обычно проблема не в разработчиках. Проблема в том, что проект растёт без понятной архитектурной модели. На курсе по Domain-Driven Design и Clean Architecture на C# ты научишься: — Отделять бизнес-логику от инфраструктуры — Организовывать код так, чтобы новые требования не приводили к переписыванию половины сервиса — Писать тесты, которые проверяют поведение системы, а не набор моков — Подключать HTTP, gRPC и Kafka без изменений в доменной логике — Строить сервисы, которые проще поддерживать и развивать За 6 недель ты соберёшь полноценный микросервис на C# с DDD, Kafka, gRPC и Clean Architecture на реальном кейсе диспетчеризации заказов. 👨‍🏫 Автор курса — Кирилл Ветчинкин, архитектор Авито, ex Staff Engineer Купер, ex Head of Backend BCS Broker. 🎁 Первый модуль доступен бесплатно. В нём разберём, почему кодовые базы со временем становятся хрупкими, откуда появляются сложные тесты и постоянный страх изменений, и как DDD и Clean Architecture помогают решить эти проблемы на практике. Посмотри демо-модуль и оцени, насколько этот подход подходит для твоих проектов: https://microarch.ru/courses/ddd/languages/csharp?utm_source=posev&utm_medium=erid:2VtzqwrtmqB&utm_campaign=4 Реклама. ИП Ветчинкин К.Е. ИНН: 773376451099 Erid: 2VtzqvzVXjP
632
3
Что выведет код? 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.
833
4
Кеширование в ASP.NET Core: от IMemoryCache до Redis Приложение работает быстро — пока растёт нагрузка на базу, увеличивается
Кеширование в ASP.NET Core: от IMemoryCache до Redis Приложение работает быстро — пока растёт нагрузка на базу, увеличивается время ответа API, а масштабирование инфраструктуры не начинает обходиться слишком дорого. Кеширование помогает снизить количество запросов к хранилищам, ускорить работу сервисов и эффективнее использовать ресурсы. Но результат зависит от того, какие данные попадают в кеш, где он хранится и как устроена инвалидация. На открытом вебинаре разберём: — какие данные стоит кешировать, а какие — нет; — как выбрать стратегию инвалидации; — как работают HTTP Cache, UseResponseCaching и IMemoryCache в ASP.NET Core; — когда нужен распределённый кеш через IDistributedCache; — как использовать Redis и чем он отличается от Memcached. Открытый урок пройдёт 25 июня в 20:00 МСК в преддверии старта курса «C# ASP.NET Core разработчик». Подробности и регистрация: https://otus.pw/Suy4/?erid=2W5zFJVZxuX Реклама. ООО "ОТУС ОНЛАЙН-ОБРАЗОВАНИЕ". ИНН 9705100963.
703
5
Аллокации, которых нет в коде: охота на скрытый боксинг в .NET 10 Самая дорогая аллокация в вашем сервисе та, которой нет в и
Аллокации, которых нет в коде: охота на скрытый боксинг в .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 219
6
Аллокации, которых нет в коде: охота на скрытый боксинг в .NET 10 Самая дорогая аллокация в вашем сервисе та, которой нет в и
Аллокации, которых нет в коде: охота на скрытый боксинг в .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 или дизасм. Что такое боксинг и почему он стоит дорого Боксинг — это упаковка value-типа (struct, enum, примитив) в объект на управляемой куче. Рантайму нужно выделить заголовок объекта, скопировать туда значение и вернуть ссылку. Анбоксинг - обратная операция с проверкой типа. Цена не в самой инструкции, а в последствиях: каждая упаковка - это аллокация в Gen0. Много мелких аллокаций на горячем пути означают частые Gen0-коллекции, паузы (пусть и короткие), вытеснение полезных данных из кэша и общий рост CPU на ровном месте. На сервисе с SLA по p99 это бьёт по хвосту латентности так же, как и любая другая лишняя аллокация. В IL боксинг виден явно - инструкция box. Именно её мы и будем искать. Читать дальше: https://habr.com/ru/articles/1049236/
444
7
Задача по .NET: зачем нужен IHttpClientFactory? Есть сервис, который ходит во внешний API: public sealed class PaymentApiClient { private readonly HttpClient _http; public PaymentApiClient(HttpClient http) { _http = http; } public async Task<string> GetStatusAsync(string id, CancellationToken ct) { using var response = await _http.GetAsync($"/payments/{id}", ct); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsStringAsync(ct); } } Регистрация: builder.Services.AddHttpClient<PaymentApiClient>(client => { client.BaseAddress = new Uri("https://payments.example.com"); client.Timeout = TimeSpan.FromSeconds(10); }) .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler { PooledConnectionLifetime = TimeSpan.FromMinutes(5), MaxConnectionsPerServer = 50 }); Вопрос: почему это лучше, чем создавать new HttpClient() на каждый запрос? Потому что IHttpClientFactory переиспользует HttpMessageHandler и соединения под капотом. Это снижает риск socket exhaustion, даёт централизованную настройку таймаутов, ретраев, логирования и авторизации. А PooledConnectionLifetime нужен, чтобы соединения не жили вечно и приложение могло корректно подхватывать изменения DNS.
1 329
8
Program.cs — это не просто точка входа. За несколькими строками кода в ASP.NET Core скрывается полноценная инфраструктура зап
Program.cs — это не просто точка входа. За несколькими строками кода в ASP.NET Core скрывается полноценная инфраструктура запуска приложений, управления жизненным циклом и фоновых процессов. На открытом уроке разберём, как на самом деле устроен ASP.NET Core и почему понимание Generic Host меняет подход к разработке .NET-приложений. Поговорим о жизненном цикле приложения, фоновых задачах через IHostedService и различиях между веб-приложениями и консольными сервисами. Это особенно полезно разработчикам, которые уже работают с ASP.NET Core, но хотят глубже понимать архитектуру платформы, увереннее проектировать сервисы и принимать технические решения осознанно, а не по шаблону. Открытый урок пройдёт 18 июня в 20:00 МСК в преддверии старта курса «C# ASP.NET Core разработчик». Подробности и регистрация: https://otus.pw/0cm8/?erid=2W5zFGkfRf7 Реклама. ООО "ОТУС ОНЛАЙН-ОБРАЗОВАНИЕ". ИНН 9705100963.
747
9
⚡️Про Каналы в .NET Если в приложении нужно обрабатывать поток данных от продюсеров к потребителям, первым делом вспоминают про очереди или ConcurrentQueue. Но в асинхронном мире этого мало. Поэтому в .NET есть каналы. Канал устроен как пара из продюсера и потребителя. Когда продюсер пишет элемент в канал, он либо сразу уходит к потребителю, либо ждёт, если очередь переполнена. Потребитель, в свою очередь, может ждать новые элементы без блокировки потоков — всё работает на async/await. Главная сила каналов — балансировка нагрузки. Ограниченные каналы позволяют держать под контролем количество элементов. Это защищает приложение от перегрузки: если продюсер работает быстрее, чем потребитель, система сама притормозит поток данных. Неограниченные каналы — вариант попроще, они всегда принимают новые элементы, но это может обернуться непредсказуемым ростом памяти. Мини-пример: var channel = Channel.CreateBounded<int>(5); // producer _ = Task.Run(async () => { for (int i = 0; i < 10; i++) { await channel.Writer.WriteAsync(i); Console.WriteLine($"Produced {i}"); } channel.Writer.Complete(); }); // consumer await foreach (var item in channel.Reader.ReadAllAsync()) { Console.WriteLine($"Consumed {item}"); } Почему это лучше, чем ConcurrentQueue? Потому что Channel создан сразу с учётом асинхронности. Вам не нужно городить блокировки и таймеры ожидания — достаточно написать await reader.ReadAsync(), и код будет сам по себе масштабироваться без блокировки потоков. #sharp_view
1 828
10
🔥 Приглашаем на бесплатный открытый вебинар курса «Высоконагруженные системы: архитектура и масштабирование»: «Асинхронная о
🔥 Приглашаем на бесплатный открытый вебинар курса «Высоконагруженные системы: архитектура и масштабирование»: «Асинхронная обработка данных в высоконагруженных системах» 🗓 Когда: 16 июня, 20:00 (мск) На вебинаре разберём, как грамотно внедрять асинхронность и строить по-настоящему производительные системы. Что будет на вебинаре: — Зачем и когда переходить на асинхронную обработку данных в высоконагруженных проектах — Очереди сообщений, веб-сокеты и другие инструменты асинхронного взаимодействия — Реальный архитектурный кейс: от веб-сервера до брокера сообщений и базы данных 👉 Зарегистрироваться: https://otus.pw/msWr/?erid=2W5zFFzVbsw Бесплатное занятие приурочено к курсу «Высоконагруженные системы: архитектура и масштабирование» 🎁При покупке курса вы получите в подарок мини-курс по Kafka, который поможет подготовиться к собеседованию в бигтех Реклама. ООО "ОТУС ОНЛАЙН-ОБРАЗОВАНИЕ". ИНН 9705100963.
649
11
🐳 «Используй Testcontainers вместо in-memory» - это только половина правды Все уже выучили: EF Core InMemory provider - не интеграционный тест. Он не ловит: - баги в LINQ-трансляции - ограничения БД - коллации - реальные типы колонок - поведение конкретного SQL-провайдера Окей, заменили на реальный PostgreSQL через Testcontainers. Победа? Не совсем. Вот что начинается дальше. 1. Вы получили «медленное враньё» вместо «быстрого» Поднимать контейнер на каждый тест-класс - быстрый способ превратить CI из 30 секунд в 8 минут. Нормальный вариант: - один контейнер на всю тестовую сессию - изоляция данных между тестами через Respawn - без пересоздания базы и контейнера каждый раз Respawn чистит таблицы с учётом графа foreign keys за миллисекунды. 2. Транзакционный откат ≠ реальный сценарий Трюк «обернули тест в транзакцию и откатили» красиво выглядит, но ломается, когда в коде есть: - свои транзакции - несколько SaveChanges - фоновые операции - поведение, завязанное на commit В итоге тестируется сценарий, которого в проде нет. 3. Самая коварная ловушка - общий DbContext Если тест и код используют один экземпляр DbContext, EF может вернуть данные из change tracker, а не из базы. Тест зелёный, но он врёт: реальный SQL-запрос мог вообще не выполниться. Между Act и Assert стоит чистить трекер: Db.ChangeTracker.Clear(); 4. Бонус, который теряют 90% команд - тест миграций Реальная БД позволяет прогнать EF-миграции на чистой схеме. Если миграция падает или схема разъехалась с моделью, вы узнаёте об этом в CI, а не в проде в пятницу вечером. Пример базового подхода: public class IntegrationTestBase : IAsyncLifetime { private static readonly PostgreSqlContainer _db = new PostgreSqlBuilder() .WithImage("postgres:16-alpine") .Build(); private Respawner _respawner = null!; protected AppDbContext Db = null!; public async Task InitializeAsync() { await _db.StartAsync(); var options = new DbContextOptionsBuilder<AppDbContext>() .UseNpgsql(_db.GetConnectionString()) .Options; Db = new AppDbContext(options); // Реальные миграции - заодно проверяем, что они накатываются await Db.Database.MigrateAsync(); await using var conn = new NpgsqlConnection(_db.GetConnectionString()); await conn.OpenAsync(); _respawner = await Respawner.CreateAsync(conn, new RespawnerOptions { DbAdapter = DbAdapter.Postgres, SchemasToInclude = ["public"] }); } // Сброс данных перед каждым тестом - без пересоздания контейнера protected async Task ResetAsync() { await using var conn = new NpgsqlConnection(_db.GetConnectionString()); await conn.OpenAsync(); await _respawner.ResetAsync(conn); // Иначе тест может читать из кеша, а не из БД Db.ChangeTracker.Clear(); } public Task DisposeAsync() => Task.CompletedTask; } Testcontainers - это не галочка «best practice», а смена философии. Без нормальной изоляции данных вы просто пересели с быстрого вранья на медленное. А как вы изолируете состояние БД между интеграционными тестами - Respawn, транзакции или пересоздание контейнера? #dotnet #csharp #testing #efcore
1 565
12
✔️ Одна строчка .Result роняет ваш ASP.NET Core при CPU 8 %: разбор hill-climbing в .NET 9 TL;DR. Один foo.GetAsync().Result
✔️ Одна строчка .Result роняет ваш ASP.NET Core при CPU 8 %: разбор hill-climbing в .NET 9 TL;DR. Один foo.GetAsync().Result внутри middleware превращает ASP.NET Core, державший 50k RPS на p99 = 40 мс, в сервис на 12k RPS с p99 = 4 с при CPU 8 %. Виноват не блокирующий вызов сам по себе. Виноват hill-climbing: фидбэк-луп в ThreadPool, внутри которого живёт дискретное преобразование Фурье. Разбираемся по исходникам CoreCLR, как это работает, воспроизводим эффект на ~80 строках кода и показываем, почему SetMinThreads это не лечение, а анестезия. https://habr.com/ru/articles/1040804/
1 783
13
🖥 Сервисы крутятся. Прод вроде живой. Но когда тимлид спрашивает: «почему здесь лучше ValueTask, а не Task?» или «как GC пов
🖥 Сервисы крутятся. Прод вроде живой. Но когда тимлид спрашивает: «почему здесь лучше ValueTask, а не Task?» или «как GC поведёт себя под нагрузкой?» - ты начинаешь плыть. И дело не в том, что ты плохо пишешь код. Просто большинство курсов заканчиваются ровно там, где начинается настоящий .NET. Этот курс про то, что обычно остаётся под капотом: - CLR - JIT - GC - Span - async state machine - Source Generators - lock-free подходы - OpenTelemetry - дампы в проде На практике разбираем, как .NET реально работает внутри: что происходит с кодом после компиляции, как память живёт под нагрузкой, почему async иногда помогает, а иногда ломает производительность, как читать проблемы по дампам и метрикам, а не гадать по логам. Если хочешь дойти до уровня, где система для тебя не чёрный ящик, а инструмент, который ты понимаешь до IL, - велкам. Сейчас на stepik доступна скидка 55%: https://stepik.org/a/288694
1 734
14
⚡️ Machine Learning Roadmap 2025: большая карта входа в ML без сказок про “нейросети за месяц Большой русскоязычный roadmap п
⚡️ Machine Learning Roadmap 2025: большая карта входа в ML без сказок про “нейросети за месяц Большой русскоязычный roadmap по машинному обучению: от первого import numpy до LLM, RAG, fine-tuning, AI-агентов и MLOps и даже вабкодинга. Внутри нормальная структура: что учить, в каком порядке, зачем это нужно и что должно получиться на практике после каждого этапа. Roadmap разбит на 7 треков: 1. Фундамент: Python, математика, статистика, инструменты 2. Классический ML: scikit-learn, табличные данные, метрики, валидация 3. Deep Learning: PyTorch, CNN, RNN, training loop 4. LLM и трансформеры: attention, KV-cache, RAG, LoRA, агенты 5. Generative AI: изображения, видео, аудио, мультимодальность 6. MLOps и прод: Docker, Kubernetes, CI/CD, monitoring, serving 7. Специализация: CV, NLP, RecSys, RL, Safety Roadmap не продаёт иллюзию “обучил модель - стал ML-инженером”. В реальной работе много времени уходит на данные, метрики, деплой, мониторинг, воспроизводимость и разбор ошибок. Модель - только часть системы. Хорошая мысль из roadmap: LLM не делает джуна сеньором. Она ускоряет того, кто уже понимает базу. Без базы человек просто становится оператором Copilot, который не может объяснить, почему всё сломалось. По времени тоже без сказок: 1. 0-3 месяца: математика, классический ML 2. 3-6 месяцев: Deep Learning и PyTorch 3. 6-12 месяцев: LLM, RAG, fine-tuning, AI-агенты 4. 12+ месяцев: MLOps, прод, масштабирование, специализация Тут же собрано 7 болших бесплатных курсов по машинному обучению, математике и вайбкодингу! Если давно хотели зайти в ML системно, а не прыгать между роликами про ChatGPT, Stable Diffusion и “топ-10 библиотек”, это хороший ориентир. https://github.com/justxor/MachineLearningRoadmap
2 015
15
Форма логина и JWT-токен — ещё не безопасность приложения. На практике ошибки в аутентификации и авторизации становятся причи
Форма логина и JWT-токен — ещё не безопасность приложения. На практике ошибки в аутентификации и авторизации становятся причиной утечек данных, проблем с доступом и уязвимостей, которые сложно обнаружить до выхода системы в production. 26 мая в 20:00 МСК приглашаем вас на открытый урок курса «C# ASP.NET Core-разработчик». На занятии разберём, как в ASP.NET Core устроены pipeline, middleware и схемы аутентификации. Покажем, как правильно использовать JWT, cookies, claims, роли и policy-based авторизацию для гибкого и безопасного контроля доступа. Отдельно обсудим типичные ошибки, которые встречаются в production: небезопасное хранение токенов, ошибки настройки схем и проблемы в логике авторизации. Урок будет полезен .NET-разработчикам, которые хотят систематизировать знания по безопасности веб-приложений и увереннее работать с ASP.NET Core в реальных проектах. Регистрация уже открыта: https://otus.pw/tDF66/?erid=2W5zFGbdS73 Реклама. ООО "ОТУС ОНЛАЙН-ОБРАЗОВАНИЕ". ИНН 9705100963.
1 683
16
🖥 C# Roadmap: с нуля до профи Практическое руководство по росту в C#-разработке. Материал собран для тех, кто хочет получить
🖥 C# Roadmap: с нуля до профи Практическое руководство по росту в C#-разработке. Материал собран для тех, кто хочет получить инженерную глубину, а не просто накликать CRUD по туториалам. Здесь последовательность изучения, лучшие практики, ресурсы и трезвый разбор того, как работать с ИИ-инструментами и оставаться востребованным. https://github.com/Develp10/Csharp_Roadmap/
2 286
17
⏱️ Ускоряем async-код в C#: не просто await, а нормальная параллельность Одна из частых ошибок в C# - писать независимые асинхронные операции так, будто они зависят друг от друга: await GetUserAsync(); await GetOrdersAsync(); await GetRecommendationsAsync(); Выглядит нормально, но по факту это последовательное выполнение. Если каждый запрос занимает примерно 1 секунду, весь блок будет выполняться около 3 секунд. Не потому что async медленный, а потому что мы сами заставили код ждать каждый шаг по очереди. Правильнее запускать независимые операции сразу: var userTask = GetUserAsync(); var ordersTask = GetOrdersAsync(); var recsTask = GetRecommendationsAsync(); await Task.WhenAll(userTask, ordersTask, recsTask); Теперь все запросы стартуют одновременно, а общее время будет примерно равно самому долгому из них. Но есть важный нюанс. Task.WhenAll не делает CPU-код магически параллельным. Он особенно полезен для I/O-bound задач: - запросы в базу - HTTP-вызовы - чтение файлов - обращения к внешним API - независимые операции с сетью И ещё один момент: после WhenAll лучше доставать результаты уже из завершённых задач: var user = await userTask; var orders = await ordersTask; var recs = await recsTask; Так код остаётся читаемым, ошибки нормально пробрасываются, а логика не превращается в кашу. Async/await сам по себе не ускоряет код. Он просто даёт возможность не блокировать поток. Ускорение появляется тогда, когда вы правильно запускаете независимые операции вместе, а не ждёте их по одной.
2 122
18
🖥 На Stepik обновили курс «C# с нуля до профи» Представьте: через четыре месяца вы открываете чужой .NET-проект и читаете ег
🖥 На Stepik обновили курс «C# с нуля до профи» Представьте: через четыре месяца вы открываете чужой .NET-проект и читаете его как книгу. IServiceCollection не вызывает ступора. async Task<IActionResult> пишется на автомате. Вы точно знаете, почему EF Core сгенерировал именно такой SQL - и как переписать запрос, чтобы он летал. Это не фантазия. Это результат после 16 модулей, в которых каждая концепция объясняется через код и закрепляется практикой. ООП, SOLID, LINQ, async/await, DI, EF Core, ASP.NET Core, Docker, Kubernetes - всё, что казалось магией, станет рабочим инструментом. А бонусом - портфолио проектов: от CLI-утилит и REST API до собственного SaaS с multi-tenancy, JWT и деплоем в Kubernetes под TLS. Скидка - 58% доступна 48 часов: https://stepik.org/a/282984/
1 578
19
📌 UUID v7 - это почти ULID, только нативно в .NET Раньше в .NET для идентификаторов чаще всего использовали обычный Guid.New
📌 UUID v7 - это почти ULID, только нативно в .NET Раньше в .NET для идентификаторов чаще всего использовали обычный Guid.NewGuid(). Проблема в том, что классический UUID случайный. Для уникальности это удобно, но для базы данных - не всегда. Когда значения генерируются хаотично, новые записи вставляются в разные части индекса. Отсюда: - больше фрагментации - дороже вставки - чаще перестраиваются страницы индекса - хуже поведение на больших таблицах Поэтому многие разработчики начали использовать ULID. ULID состоит из двух частей: - timestamp - random За счет timestamp такие ID сортируются по времени, а значит база может вставлять новые записи более последовательно. Но начиная с .NET 9 появился встроенный вариант: Guid.CreateVersion7() UUID v7 тоже содержит временную часть, поэтому лучше подходит для индексируемых ключей, чем полностью случайный UUID. Главное отличие: ULID - отдельный формат и часто сторонняя библиотека. UUID v7 - обычный Guid, который уже поддерживается в .NET. Для новых проектов это выглядит как более разумный дефолт: - не Guid.NewGuid() - не отдельный ULID-пакет - а Guid.CreateVersion7() Особенно если Guid используется как primary key в базе.
1 499
20
Что выведет код? using System; class Program { static void Main() { int x = 10; Console.WriteLine(x++ + ++x); } } Правильный
Что выведет код? using System; class Program { static void Main() { int x = 10; Console.WriteLine(x++ + ++x); } } Правильный ответ: 22 Почему: выражение считается слева направо. Сначала x++ возвращает 10, но потом увеличивает x до 11. Затем ++x сначала увеличивает x до 12, потом возвращает 12. В итоге 10 + 12 = 22.
0