Начиная с .NET 11 Preview 4, все приложения .NET MAUI на Android, iOS и Mac Catalyst по умолчанию работают на CoreCLR. Mono больше не является рантаймом по умолчанию для мобильных платформ.
Это значит, что мобильные приложения теперь крутятся на том же рантайме, что и ASP.NET Core, Azure-сервисы и десктопные приложения.
Что было раньше
До .NET 11 мобильная часть .NET MAUI работала на Mono. Серверные, облачные и десктопные приложения уже давно использовали CoreCLR.
Два разных рантайма означали разное поведение JIT, разные характеристики сборщика мусора, разный инструментарий диагностики и разные баги. Теперь эта разница уходит.
Mono обеспечивал работу .NET на мобильных платформах больше 15 лет. MonoTouch принёс C# на iPhone в 2009 году, MonoDroid появился для Android. На Mono построены Unity, Avalonia UI, Uno Platform, MonoGame и Godot.
Переход на CoreCLR не означает конец Mono. Blazor WebAssembly по-прежнему использует Mono, и в .NET 11 это не меняется.
Зачем переходить на CoreCLR
Унификация рантайма. Один рантайм для всех платформ означает единый набор инструментов и предсказуемое поведение. Не нужно держать в голове различия между Mono и CoreCLR.
Производительность. CoreCLR приносит на мобильные платформы tiered JIT compilation, ReadyToRun (R2R) прекомпиляцию и Profile-Guided Optimization (PGO). В Preview 4 уже включены partial R2R по умолчанию и встроенные PGO-профили. Стартап базового
dotnet new maui приложения стал заметно быстрее.NativeAOT. CoreCLR является основой для NativeAOT-компиляции. С переходом на CoreCLR открывается путь к NativeAOT на Android. На iOS и Mac Catalyst NativeAOT развивает уже существующую AOT-компиляцию, но с более унифицированным тулчейном.
Что с производительностью на практике
Для простых приложений стартап стал быстрее. Но сообщество уже сообщает о регрессиях в сложных приложениях на Android. Время запуска или размер приложения могут увеличиться. На iOS команда успешно прошла ревью App Store.
Из поддерживаемых архитектур убраны Android x86, Android API 23 и ниже, а также embedding APIs. Поддержка Android arm32 пока под вопросом.
Как откатиться на Mono
Если что-то пошло не так, можно временно вернуть Mono. Добавьте в файл проекта:
<PropertyGroup>
<UseMonoRuntime>true</UseMonoRuntime>
</PropertyGroup>
Работает для Android, iOS и Mac Catalyst. Опция доступна на протяжении всего сервисинга .NET 11.
Диагностика
Теперь на мобильных платформах работают стандартные инструменты .NET-диагностики:
dotnet-trace и dotnet-counters. Те же инструменты, что вы используете на сервере и десктопе, теперь доступны для Android и iOS/Mac Catalyst.Как попробовать
Установите .NET 11 Preview 4 SDK и workload:
dotnet workload install maui
Соберите приложение с таргетом
net11.0-android, net11.0-ios или net11.0-maccatalyst и проверьте старт, размер пакета и общую работоспособность. Если нашли проблему, заводите issue в dotnet/android или dotnet/macios. GA-релиз запланирован на ноябрь 2026, и фидбек из превью напрямую влияет на то, что будет исправлено.
📍 Навигация: Вакансии • Задачи • Собесы
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
❤13👍2🤩1
🔄 Process API в .NET 11: большое обновление
Чтение вывода без дедлоков
При перенаправлении stdout и stderr пайпы имеют ограниченный буфер. Последовательное чтение приводит к взаимной блокировке. Раньше нужно было вручную организовывать параллельное чтение. Теперь одна строка:
Также есть
Контроль хэндлов
Жизненный цикл
Производительность
Запуск на Apple Silicon ускорен до 98x (
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#async_news
System.Diagnostics.Process получил крупнейшее обновление за годы в .NET 11 Preview 4.Чтение вывода без дедлоков
При перенаправлении stdout и stderr пайпы имеют ограниченный буфер. Последовательное чтение приводит к взаимной блокировке. Раньше нужно было вручную организовывать параллельное чтение. Теперь одна строка:
var output = Process.RunAndCaptureText("dotnet", ["--help"]);Также есть
ReadAllLinesAsync для построчного чтения и Process.Run когда вывод не нужен.Контроль хэндлов
ProcessStartInfo.InheritedHandles указывает, какие хэндлы наследует дочерний процесс. Предотвращает утечку и удваивает пропускную способность параллельного запуска на Windows. Стандартные хэндлы можно перенаправить на файл, пайп или null.Жизненный цикл
KillOnParentExit убивает дочерний процесс при завершении родителя, включая краши. StartDetached запускает процесс, переживающий родителя. StartAndForget возвращает PID и освобождает ресурсы.Производительность
Запуск на Apple Silicon ускорен до 98x (
posix_spawn). Параллельный запуск на Windows быстрее в 1.8x. Аллокации на Unix сокращены на 30–50%. NativeAOT с SafeProcessHandle до 32% меньше.📍 Навигация: Вакансии • Задачи • Собесы
#async_news
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3👏1
🤔 Вопрос с собеседования по C#
Если вы думаете про
Попробуйте сформулировать ответ сами, а потом сверьтесь 👇
➡️ Ответ
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#dotnet_challenge
Как работают асинхронные делегаты и зачем они нужны?
Если вы думаете про
async/await — это совсем другая история. Здесь механизм старше и ближе к ручному управлению потоками.Попробуйте сформулировать ответ сами, а потом сверьтесь 👇
📍 Навигация: Вакансии • Задачи • Собесы
#dotnet_challenge
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4❤1
🛠 Не баг, а сигнал от пайплайна
Backpressure — механизм, через который консьюмер говорит продюсеру «притормози». TCP делает это с 70-х. Вопрос не нужен ли он, а какой именно.
System.Threading.Channels
В .NET есть
Drop
Данные живут секунды (телеметрия, тикеры). Важно последнее, а не каждое.
Продюсер не блокируется, консьюмер не голодает. Цена — потеря данных. Для аудита неприемлемо, для дашборда — осознанный выбор.
Buffer
Каждый элемент важен, консьюмер не успевает.
Unbounded-канал растёт, пока процесс не упрётся в память. Честная версия — «буфер с бюджетом». Внешний брокер (Kafka, RabbitMQ) или мониторинг глубины очереди. Если
Throttle
Продюсер может замедлиться. ETL-джоб не пострадает от лишних 200 мс.
Когда канал полон,
Как выбирать
Drop — стабильность ценой полноты данных.
Buffer — полнота ценой памяти. Без мониторинга это бомба.
Throttle — полнота и стабильность ценой latency.
Большинство систем комбинируют. Решите, что произойдёт при заполнении очереди, до того как она заполнится.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#il_люминатор
Backpressure — механизм, через который консьюмер говорит продюсеру «притормози». TCP делает это с 70-х. Вопрос не нужен ли он, а какой именно.
System.Threading.Channels
В .NET есть
System.Threading.Channels — асинхронная труба между продюсером и консьюмером. Канал бывает bounded или unbounded. Этот выбор определяет стратегию backpressure.Drop
Данные живут секунды (телеметрия, тикеры). Важно последнее, а не каждое.
Channel.CreateBounded<SensorReading>(
new BoundedChannelOptions(500)
{ FullMode = BoundedChannelFullMode.DropOldest });
Продюсер не блокируется, консьюмер не голодает. Цена — потеря данных. Для аудита неприемлемо, для дашборда — осознанный выбор.
Buffer
Каждый элемент важен, консьюмер не успевает.
Channel.CreateUnbounded<OrderEvent>(
new UnboundedChannelOptions { SingleReader = true });
Unbounded-канал растёт, пока процесс не упрётся в память. Честная версия — «буфер с бюджетом». Внешний брокер (Kafka, RabbitMQ) или мониторинг глубины очереди. Если
reader.Count растёт и не выравнивается — память не поможет.Throttle
Продюсер может замедлиться. ETL-джоб не пострадает от лишних 200 мс.
Channel.CreateBounded<DataRecord>(
new BoundedChannelOptions(1000)
{ FullMode = BoundedChannelFullMode.Wait });
Когда канал полон,
WriteAsync ждёт освобождения слота. Пайплайн работает на скорости самого медленного участника. Цена — latency.Как выбирать
Drop — стабильность ценой полноты данных.
Buffer — полнота ценой памяти. Без мониторинга это бомба.
Throttle — полнота и стабильность ценой latency.
Большинство систем комбинируют. Решите, что произойдёт при заполнении очереди, до того как она заполнится.
📍 Навигация: Вакансии • Задачи • Собесы
#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤3💯2
🆚 EF Core vs Dapper — новые фичи EF закрывают разрыв
EF Core и Dapper решают пересекающиеся, но не одинаковые задачи. С каждым релизом EF Core сокращает отставание по производительности, но у Dapper по-прежнему есть своя ниша.
Что нового в EF Core
Compiled queries убирают overhead на трансляцию повторяющихся запросов.
Улучшенная поддержка raw SQL с проекцией в DTO, JSON-колонки, батчинг. В итоге для типичных API-ридов с небольшими DTO разница с Dapper составляет около 10–30%, а при доминировании latency на стороне БД она вообще не заметна.
Где Dapper всё ещё выигрывает
Кастомные джоины, CTE, оконные функции, db-specific хинты. Read-mostly микросервисы без change tracking. Хранимые процедуры. Минимальный jitter на p95–p99.
Прагматичный подход
Большинство команд используют гибрид. EF Core по умолчанию для всего с
Типичные ошибки
Возврат сущностей вместо DTO из API. Lazy loading без контроля. N+1 из-за навигационных свойств. Отсутствие
Compiled EF vs non-compiled экономит 0.5–2.5 мс на вызов. Если compiled query укладывается в SLO в пределах 15% от Dapper и проще в поддержке, оставляйте EF.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_view
EF Core и Dapper решают пересекающиеся, но не одинаковые задачи. С каждым релизом EF Core сокращает отставание по производительности, но у Dapper по-прежнему есть своя ниша.
Что нового в EF Core
Compiled queries убирают overhead на трансляцию повторяющихся запросов.
private static readonly Func<AppDb, Guid, Task<UserDto?>> GetById =
EF.CompileAsyncQuery((AppDb db, Guid id) =>
db.Users.AsNoTracking()
.Where(u => u.Id == id)
.Select(u => new UserDto(u.Id, u.Email))
.FirstOrDefault());
ExecuteUpdateAsync / ExecuteDeleteAsync позволяют делать set-based операции без материализации сущностей.await db.Orders
.Where(o => o.Status == "Pending" && o.CreatedAtUtc < cutoff)
.ExecuteUpdateAsync(s =>
s.SetProperty(o => o.Status, _ => "Expired"));
Улучшенная поддержка raw SQL с проекцией в DTO, JSON-колонки, батчинг. В итоге для типичных API-ридов с небольшими DTO разница с Dapper составляет около 10–30%, а при доминировании latency на стороне БД она вообще не заметна.
Где Dapper всё ещё выигрывает
Кастомные джоины, CTE, оконные функции, db-specific хинты. Read-mostly микросервисы без change tracking. Хранимые процедуры. Минимальный jitter на p95–p99.
Прагматичный подход
Большинство команд используют гибрид. EF Core по умолчанию для всего с
Select + AsNoTracking. Для топ 2–5 горячих запросов пробуют compiled queries. Если мало, реализуют только их через Dapper за интерфейсом (IOrderQueries), а остальное на EF.Типичные ошибки
Возврат сущностей вместо DTO из API. Lazy loading без контроля. N+1 из-за навигационных свойств. Отсутствие
AsNoTracking на ридах. Generic repository, который прячет фичи EF.Compiled EF vs non-compiled экономит 0.5–2.5 мс на вызов. Если compiled query укладывается в SLO в пределах 15% от Dapper и проще в поддержке, оставляйте EF.
📍 Навигация: Вакансии • Задачи • Собесы
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7
📍 Навигация: Вакансии • Задачи • Собесы
#garbage_collector
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔13
Открываете C# файл, а там десяток красных подчёркиваний. Нажимаете
Ctrl+., выбираете нужный namespace, переходите к следующей ошибке, снова Ctrl+.. И так по кругу. Visual Studio умеет добавлять using только по одному. В Rider есть Import Missing References, но это другая IDE.OhUsings решает эту проблему внутри Visual Studio. Расширение сканирует файл, находит все неразрешённые типы через Roslyn и добавляет нужные
using директивы разом. Без ручного перебора.Как это работает
Под капотом OhUsings использует семантическую модель Roslyn, а не regex. Расширение читает диагностики компилятора (
CS0246, CS0103, CS0234 и ещё около десятка), извлекает имена типов и находит подходящие namespace через SymbolFinder. Если тип однозначный, using добавляется автоматически. Если тип встречается в нескольких namespace (например, Timer живёт и в System.Timers, и в System.Threading), расширение покажет диалог выбора.Добавленные директивы сортируются по алфавиту,
System.* ставятся первыми. Всё форматируется через Roslyn.Три области применения
Можно запустить на текущем файле, на активном проекте или на всём solution целиком.
Установка
Самый простой способ: в Visual Studio 2022 откройте Extensions → Manage Extensions, найдите OhUsings и нажмите Download. После перезапуска расширение готово к работе.
Или соберите из исходников:
git clone https://github.com/MabroukMahdhi/OhUsings.git
Откройте
OhUsings.sln в Visual Studio 2022, соберите проект и установите полученный OhUsings.vsix.Как использовать
Три варианта на выбор. Через меню: Tools → OhUsings: Import All Missing Usings. Через контекстное меню: правый клик в редакторе → OhUsings: Import All Missing Usings. Или через light bulb: ставите курсор на неразрешённый тип, нажимаете
Ctrl+. и выбираете действие из предложенных OhUsings.Результат отображается в статусной строке и Output Window.
Настройки
Расширение поддерживает несколько опций.
SortUsings (по умолчанию true) отвечает за сортировку директив. PlaceSystemFirst (по умолчанию true) ставит System.* выше остальных. MaxDiagnosticsPerDocument (по умолчанию 200) ограничивает число обрабатываемых диагностик на файл для защиты производительности.Ограничения
OhUsings работает только с типами из уже подключённых сборок. Добавлять NuGet пакеты или ссылки на проекты расширение пока не умеет, но это есть в планах. Если тип неоднозначный, его придётся выбрать вручную через диалог.
Итого
OhUsings экономит время на рутине. Вместо того чтобы обходить каждую ошибку по отдельности, вы запускаете одну команду и получаете все нужные
using сразу. Расширение бесплатное, с открытым исходным кодом под MIT лицензией, без телеметрии и сетевых запросов.📍 Навигация: Вакансии • Задачи • Собесы
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11🥱3
— Шаблоны проектов теперь работают из командной строки
— .NET 11 Preview 4
— Майское обновление безопасности .NET
— .NET MAUI переезжает на CoreCLR
— Process API в .NET 11
📍 Навигация: Вакансии • Задачи • Собесы
#async_news
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
Слово «монада» звучит так, будто пришло из учебника по высшей математике. Если вы писали на C#, вы уже пользовались монадами.
Task<T>, Nullable<T>, IEnumerable<T> и весь LINQ построены на этом паттерне.Монада в двух словах
Монада это обёртка над значением, которая умеет делать две вещи.
Первая. Оборачивать значение в контейнер. В C# это конструктор или фабричный метод. Например,
Task.FromResult(42) оборачивает число 42 в Task<int>.Вторая. Прокидывать значение через цепочку операций, где каждая операция тоже возвращает обёртку. В функциональном программировании эту операцию называют
Bind. В C# она реализована как SelectMany.Формально монада должна соблюдать три закона: левая единица, правая единица, ассоциативность; но для повседневной работы достаточно понимать саму идею цепочки.
Nullable как монада
Nullable<T> это контейнер, который либо содержит значение, либо пуст. До C# 8 приходилось писать вложенные проверки на null:int? a = GetValue();
int? b = null;
if (a.HasValue)
{
b = Transform(a.Value);
}
Монадный подход позволяет выстроить цепочку без ручных проверок. Напишем свой
Bind для Nullable<T>:public static T? Bind<T>(this T? source, Func<T, T?> func)
where T : struct
{
return source.HasValue ? func(source.Value) : null;
}
Теперь вместо вложенных if мы получаем цепочку:
int? result = GetValue()
.Bind(x => Multiply(x, 2))
.Bind(x => Add(x, 10));
Если на любом шаге значение окажется null, вся цепочка вернёт null. Никаких проверок вручную.
Task как монада
Task<T> работает по тому же принципу. Метод ContinueWith это грубый аналог Bind, но гораздо удобнее пользоваться async/await. Компилятор сам выстраивает цепочку продолжений:
var user = await GetUserAsync(id);
var orders = await GetOrdersAsync(user);
var total = await CalculateTotalAsync(orders);
Каждый
await разворачивает Task<T>, передаёт значение в следующую операцию и снова заворачивает результат в Task. Это и есть монадная цепочка, просто записанная через синтаксический сахар.LINQ и SelectMany
LINQ query syntax это буквально монадный синтаксис.
Ключевое слово
from вызывает SelectMany под капотом:var pairs =
from x in new[] { 1, 2, 3 }
from y in new[] { "a", "b" }
select (x, y);
Компилятор превращает это в вызов
SelectMany:var pairs = new[] { 1, 2, 3 }
.SelectMany(
x => new[] { "a", "b" },
(x, y) => (x, y));SelectMany берёт каждый элемент, применяет функцию, которая возвращает новую коллекцию, и склеивает всё в один плоский результат. Это и есть Bind для IEnumerable<T>.Свой тип Result как монада
В реальных проектах монады полезны для обработки ошибок без исключений.
Напишем простой
Result<T>:public class Result<T>
{
public T Value { get; }
public string Error { get; }
public bool IsSuccess { get; }
private Result(T value)
{
Value = value;
IsSuccess = true;
}
private Result(string error)
{
Error = error;
IsSuccess = false;
}
public static Result<T> Ok(T value) => new(value);
public static Result<T> Fail(string error) => new(error);
public Result<TOut> Bind<TOut>(Func<T, Result<TOut>> func)
{
return IsSuccess ? func(Value) : Result<TOut>.Fail(Error);
}
}
Теперь цепочка бизнес-логики выглядит так:
var result = Validate(input)
.Bind(SaveToDatabase)
.Bind(SendNotification);
Если валидация упала,
SaveToDatabase и SendNotification не выполнятся. Ошибка пробросится по цепочке без try/catch и без вложенных if.Когда вы видите повторяющийся паттерн «проверь, разверни, передай, заверни обратно», скорее всего перед вами монада. И вместо ручного кода для каждого случая можно написать один
Bind и выстроить цепочку.📍 Навигация: Вакансии • Задачи • Собесы
#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥15❤5👍3
😎 Знакомьтесь с экспертом Proglib.academy: AI-архитектор Андрей Носов
Андрей — один из ключевых спикеров нашего курса AgentOps. Он выстраивает архитектуру, которая выживает в суровом проде и активно делится своим опытом.
За что его ценит IT-комьюнити:
🟣 Топ-спикер AI Conf 2026
🟣 Эксперт по GraphRAG и Knowledge Graphs
🟣 Автор «14 кругов ада для RAG»
🟣 Спикер Saint HighLoad
Андрей упаковал свои наработки в Google Colab, где можно пощупать 14 сценариев ошибок RAG и их решения:
🔗 Забрать Colab-ноутбук
На курсе Андрей отвечает за самые «мясные» блоки: RAG, оркестрацию агентов и их промышленную эксплуатацию.
Узнать больше о программе и обучении у Андрея:
👉 Курс о том, как внедрять AI-логику в бэкенд и сохранять стабильность сервиса
Так, продолжаем знакомить вас с командой?
👍 — Да, ждем новых лиц
🔥 — Пойду тестить Colab Носова
Андрей — один из ключевых спикеров нашего курса AgentOps. Он выстраивает архитектуру, которая выживает в суровом проде и активно делится своим опытом.
За что его ценит IT-комьюнити:
Его доклад про мифы семантического поиска и провалы Naive RAG стал одним из самых рейтинговых на конференции.
Андрей внедряет инженерный подход в сложные системы, заменяя «слепую веру» в эмбеддинги строгой логикой графов.
Разработал уникальный набор из 14 unit-тестов, на которых ломается стандартный векторный поиск (от слепоты к отрицаниям до конфликта версий).
Регулярно выступает на крупнейших хайлоад-площадках, разбирая архитектуру отказоустойчивых ИИ-сервисов.
Андрей упаковал свои наработки в Google Colab, где можно пощупать 14 сценариев ошибок RAG и их решения:
🔗 Забрать Colab-ноутбук
На курсе Андрей отвечает за самые «мясные» блоки: RAG, оркестрацию агентов и их промышленную эксплуатацию.
Узнать больше о программе и обучении у Андрея:
👉 Курс о том, как внедрять AI-логику в бэкенд и сохранять стабильность сервиса
Так, продолжаем знакомить вас с командой?
👍 — Да, ждем новых лиц
🔥 — Пойду тестить Colab Носова
Please open Telegram to view this post
VIEW IN TELEGRAM
😁1
Одни не могут написать строчку кода без наушников. Другие считают, что любой звук убивает концентрацию. Третьи спокойно работают под разговоры коллег в опенспейсе и не понимают, в чём проблема.
Музыка помогает войти в поток
Самый популярный аргумент. Фоновая музыка создаёт ритуал. Надел наушники, включил плейлист, мозг понял, что пора работать. Многие выбирают lo-fi, эмбиент или саундтреки из игр. Главное условие: без слов, или на языке, который не понимаешь.
Текст на знакомом языке подгружает языковые центры мозга, и на код остаётся меньше ресурсов.
Тишина как необходимость
Противоположная позиция. Любой звук, даже фоновый, отъедает часть внимания. Когда задача сложная: архитектурное решение, отладка неочевидного бага, работа с незнакомой кодовой базой; мозгу нужны все ресурсы.
Музыка в сложные моменты не помогает, а мешает. Просто мы этого не замечаем, потому что привыкли.
Офисный шум. Кому-то нормально
Есть люди, которые продуктивнее работают в окружении других людей. Фоновые разговоры, стук клавиатур, шаги создают ощущение общего процесса. Это не всем подходит, но для части разработчиков опенспейс работает лучше, чем домашний кабинет.
📍 Навигация: Вакансии • Задачи • Собесы
#entry_point
Please open Telegram to view this post
VIEW IN TELEGRAM
Абстрактная фабрика даёт интерфейс для создания семейств связанных объектов без привязки к конкретным классам. В книге GoF это один из ключевых порождающих паттернов. В современном .NET он почти не нужен в классическом виде.
Допустим, приложение работает с разными базами данных. Для каждой нужны свои реализации подключения и команды:
public interface IDbFactory
{
IDbConnection CreateConnection();
IDbCommand CreateCommand();
}
public class PostgresFactory : IDbFactory
{
public IDbConnection CreateConnection() => new NpgsqlConnection();
public IDbCommand CreateCommand() => new NpgsqlCommand();
}
public class SqlServerFactory : IDbFactory
{
public IDbConnection CreateConnection() => new SqlConnection();
public IDbCommand CreateCommand() => new SqlCommand();
}
Клиентский код получает
IDbFactory и не знает, с какой базой работает. Переключение между PostgreSQL и SQL Server сводится к замене фабрики:public class OrderRepository
{
private readonly IDbFactory _factory;
public OrderRepository(IDbFactory factory)
{
_factory = factory;
}
public void Save(Order order)
{
using var connection = _factory.CreateConnection();
using var command = _factory.CreateCommand();
// ...
}
}
Выглядит аккуратно. Но посмотрите, сколько кода нужно написать ещё до первой строки бизнес-логики. Интерфейс фабрики, две реализации, и это только для двух продуктов. Добавьте третий, например,
IDbParameter и количество классов растёт линейно.Почему DI-контейнер делает то же самое
DI-контейнер по своей сути и есть абстрактная фабрика. Вы регистрируете конкретные реализации, а контейнер разрешает их по интерфейсу.
Тот же пример без паттерна:
// Program.cs
if (builder.Environment.IsDevelopment())
{
builder.Services.AddScoped<IDbConnection, NpgsqlConnection>();
}
else
{
builder.Services.AddScoped<IDbConnection, SqlConnection>();
}
Никаких фабричных иерархий. Класс
OrderRepository получает IDbConnection через конструктор и не знает, что за ним стоит. Переключение между окружениями (Development, Staging, Production) происходит в одном месте.Когда нужен выбор в рантайме
Иногда реализацию нельзя определить при старте приложения. Например, пользователь выбирает способ оплаты, и от этого зависит, какой платёжный провайдер использовать. Для этого не нужна фабричная иерархия.
Достаточно фабричного делегата:
builder.Services.AddScoped<Func<string, IPaymentProvider>>(sp => key => key switch
{
"stripe" => sp.GetRequiredService<StripeProvider>(),
"paypal" => sp.GetRequiredService<PayPalProvider>(),
_ => throw new ArgumentException($"Unknown provider: {key}")
});
В .NET 8 появились keyed services, и стало ещё проще:
builder.Services.AddKeyedScoped<IPaymentProvider, StripeProvider>("stripe");
builder.Services.AddKeyedScoped<IPaymentProvider, PayPalProvider>("paypal");Получение в конструкторе:
public class CheckoutService
{
private readonly IPaymentProvider _provider;
public CheckoutService(
[FromKeyedServices("stripe")] IPaymentProvider provider)
{
_provider = provider;
}
}
Или через
IServiceProvider, если ключ известен только в рантайме:var provider = serviceProvider
.GetRequiredKeyedService<IPaymentProvider>(userChoice);
Где абстрактная фабрика всё ещё уместна
Паттерн имеет смысл, когда нужно гарантировать совместимость объектов внутри семейства. Например, UI-фреймворк с темами, где кнопка, поле ввода и диалог должны принадлежать одному стилю. Фабрика не даст смешать Material-кнопку с Fluent-диалогом на уровне типов.
Ещё один случай. Код работает вне DI-контейнера. Библиотека, консольная утилита, unit-тесты с ручной подстановкой зависимостей. Там фабрика по-прежнему решает задачу.
Писать отдельную иерархию фабрик стоит только тогда, когда контейнер недоступен или когда важна строгая совместимость объектов внутри семейства.
📍 Навигация: Вакансии • Задачи • Собесы
#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10
Thread.Start существует с первой версии .NET, и большинство разработчиков знают, что он «запускает поток». Но что именно происходит в момент вызова, как передать данные в поток и почему Thread почти не используют в новом коде — стоит разобрать подробно.Как это устроено
Thread — это обёртка над потоком операционной системы. Каждый созданный объект System.Threading.Thread соответствует одному OS-потоку со своим стеком (по умолчанию 1 МБ на x64).Создать поток можно двумя способами — через
ThreadStart или ParameterizedThreadStart:// Без параметра
var t1 = new Thread(new ThreadStart(DoWork));
// С параметром типа object
var t2 = new Thread(new ParameterizedThreadStart(DoWorkWithParam));
После
new Thread(...) поток ещё не запущен. Запуск происходит при вызове Start:t1.Start(); // ThreadStart — без аргумента
t2.Start("hello"); // ParameterizedThreadStart — передаём object
Внутри метода поток получает управление не сразу: ОС ставит его в очередь планировщика. Поэтому код после
Start() в вызывающем потоке продолжает выполняться параллельно.Передача данных в поток
ParameterizedThreadStart принимает object — это неудобно, потому что нужно приводить тип внутри метода:void DoWorkWithParam(object? state)
{
var message = (string)state!;
Console.WriteLine(message);
}
Более чистый способ — захват переменной через лямбду:
string data = "hello from closure";
var t = new Thread(() => Console.WriteLine(data));
t.Start();
Тут важно понимать: лямбда захватывает ссылку на переменную, а не значение. Если
data изменится до того, как поток до неё доберётся, поток увидит новое значение.Фоновые и foreground-потоки
По умолчанию
Thread создаётся как foreground (IsBackground = false). Это значит, что процесс не завершится, пока поток не закончит работу. Если нужно обратное поведение:var t = new Thread(DoWork);
t.IsBackground = true;
t.Start();
Фоновый поток будет убит принудительно при выходе из основного потока.
Подводные камни
Thread.Abort() удалён начиная с .NET 5 — он бросал ThreadAbortException в произвольном месте и приводил к непредсказуемому состоянию программы. Для отмены нужно использовать CancellationToken.Совместный доступ к данным между потоками требует синхронизации. Без неё возникают гонки, которые воспроизводятся нестабильно и тяжело отлаживаются:
int counter = 0;
var t1 = new Thread(() => { for (int i = 0; i < 10000; i++) counter++; });
var t2 = new Thread(() => { for (int i = 0; i < 10000; i++) counter++; });
t1.Start(); t2.Start();
t1.Join(); t2.Join();
// counter будет меньше 20000 — это гонка
Console.WriteLine(counter);
Чтобы дождаться завершения потока, используют
Join. Без него нельзя гарантировать, что поток завершился до следующей строки кода.Пример кода
Корректный вариант с отменой через
CancellationToken:var cts = new CancellationTokenSource();
var t = new Thread(() =>
{
while (!cts.Token.IsCancellationRequested)
{
Console.WriteLine("Working...");
Thread.Sleep(500);
}
Console.WriteLine("Stopped.");
});
t.IsBackground = true;
t.Start();
Thread.Sleep(2000);
cts.Cancel();
t.Join();
Thread.Start запускает OS-поток, но не гарантирует немедленное выполнение. Данные лучше передавать через замыкания, а не через ParameterizedThreadStart. Для большинства задач сегодня используют Task, async/await или ThreadPool — они не блокируют поток в ожидании и экономят ресурсы. 📍 Навигация: Вакансии • Задачи • Собесы
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9❤7❤🔥2👏1
В нашей рассылке нет копилота!
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#garbage_collector
📍 Навигация: Вакансии • Задачи • Собесы
#garbage_collector
Please open Telegram to view this post
VIEW IN TELEGRAM
😁6🔥2
Паттерн Iterator позволяет обходить коллекцию, не раскрывая её внутреннюю структуру. Классическая реализация на C# требовала ручного написания
IEnumerator<T> с методами MoveNext(), Reset() и свойством Current.Это выглядело громоздко. Даже для простого обхода приходилось писать отдельный класс, хранить состояние, следить за позицией. Десятки строк кода ради того, чтобы пройтись по элементам.
Как это выглядело раньше:
public class NumberCollection : IEnumerable<int>
{
private readonly int[] _items = { 1, 2, 3, 4, 5 };
public IEnumerator<int> GetEnumerator()
{
return new NumberEnumerator(_items);
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public class NumberEnumerator : IEnumerator<int>
{
private readonly int[] _items;
private int _position = -1;
public NumberEnumerator(int[] items)
{
_items = items;
}
public int Current => _items[_position];
object IEnumerator.Current => Current;
public bool MoveNext()
{
_position++;
return _position < _items.Length;
}
public void Reset() => _position = -1;
public void Dispose() { }
}
Отдельный класс, ручное управление индексом, реализация
IDisposable. И всё это ради последовательного перебора массива.Что изменил `yield return`
Теперь компилятор умеет генерировать всю эту машину состояний самостоятельно. Вы пишете
yield return, а компилятор создаёт класс с MoveNext(), Current и корректным управлением состоянием за вас.Тот же результат в несколько строк:
public static IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
yield return 4;
yield return 5;
}
Обход ленивый. Каждый элемент вычисляется только при запросе. Это важно, когда вы работаете с большими или бесконечными последовательностями:
public static IEnumerable<int> EvenNumbers()
{
int n = 0;
while (true)
{
yield return n;
n += 2;
}
}
// Берём первые 10 чётных чисел
var result = EvenNumbers().Take(10).ToList();
Попробуйте реализовать бесконечную последовательность через ручной
IEnumerator. Код будет работать, но читать его будет заметно сложнее.Связка с LINQ
yield return хорошо сочетается с LINQ. Можно строить цепочки обработки, где каждый шаг выполняется лениво:public static IEnumerable<string> ProcessUsers(IEnumerable<User> users)
{
foreach (var user in users)
{
if (user.IsActive)
yield return user.Name.ToUpper();
}
}
// Или через LINQ
var names = users
.Where(u => u.IsActive)
.Select(u => u.Name.ToUpper());
Оба варианта ленивые. Ни один не создаёт промежуточных коллекций, пока вы не вызовете
ToList(), foreach или другой терминальный оператор.Когда ручной итератор всё ещё нужен
Есть случаи, где
yield return не подойдёт. Например, если нужен асинхронный обход с отменой, сложная логика очистки ресурсов при досрочном прерывании, или обход нетривиальных структур вроде графов с контролем состояния обхода. В таких ситуациях ручная реализация IEnumerator<T> или IAsyncEnumerator<T> оправдана.Но для большинства задач
yield return закрывает вопрос полностью. Компилятор C# решил эту задачу ещё в 2005 году. Если вы пишете ручной итератор для простого обхода коллекции, стоит проверить, не делаете ли вы работу, которую компилятор готов взять на себя.📍 Навигация: Вакансии • Задачи • Собесы
#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍8
C# Разработчик — удалёнка.
.NET-разработчик (Senior) — удалёнка.
Разработчик C# (.NET) — тоже удалёнка.
Please open Telegram to view this post
VIEW IN TELEGRAM
Стратегия инкапсулирует семейство алгоритмов и позволяет подменять их на лету. Звучит полезно. На практике это часто превращается в десятки файлов ради замены одной функции. В современном C# для этого есть средства попроще.
Классическая реализация
Стандартный подход выглядит так. Интерфейс
ISortStrategy, пять конкретных реализаций, класс-контекст, который принимает стратегию и вызывает её метод.public interface ISortStrategy
{
void Sort(List<int> list);
}
public class BubbleSortStrategy : ISortStrategy
{
public void Sort(List<int> list)
{
// реализация пузырьковой сортировки
}
}
public class QuickSortStrategy : ISortStrategy
{
public void Sort(List<int> list)
{
// реализация быстрой сортировки
}
}
public class SortContext
{
private ISortStrategy _strategy;
public SortContext(ISortStrategy strategy)
{
_strategy = strategy;
}
public void SetStrategy(ISortStrategy strategy)
{
_strategy = strategy;
}
public void ExecuteSort(List<int> list)
{
_strategy.Sort(list);
}
}
Для каждого нового алгоритма мы создаём отдельный класс. Контекст ничего не знает о конкретной реализации и работает через интерфейс. Всё по канону GoF.
Проблема в том, что для простых случаев это избыточно.
Что предлагает C# вместо этого
Начиная с .NET 3.5 в языке есть
Func<T>, Action<T> и лямбда-выражения. Strategy по сути означает «передай поведение как параметр». А в C# функции и так являются объектами первого класса.Тот же пример без интерфейса и дополнительных классов:
public class SortContext
{
public void ExecuteSort(List<int> list, Action<List<int>> sortAlgorithm)
{
sortAlgorithm(list);
}
}
// использование
var context = new SortContext();
context.ExecuteSort(numbers, list => list.Sort());
context.ExecuteSort(numbers, list =>
{
// своя логика сортировки
});
Один метод принимает делегат
Action<List<int>>. Никаких интерфейсов, никаких отдельных файлов. Поведение передаётся напрямую.Ещё пример. Допустим, есть расчёт скидки:
public class PriceCalculator
{
public decimal Calculate(decimal price, Func<decimal, decimal> discountStrategy)
{
return discountStrategy(price);
}
}
var calculator = new PriceCalculator();
decimal result = calculator.Calculate(100m, price => price * 0.9m);
decimal vipResult = calculator.Calculate(100m, price => price * 0.8m);
Func<decimal, decimal> принимает цену, возвращает цену со скидкой. Стратегия задаётся в одну строку прямо в месте вызова.Когда интерфейсы всё-таки нужны
Делегаты хороши для простых случаев. Но если стратегия содержит несколько методов, внутреннее состояние или сложную логику, интерфейс остаётся правильным выбором.
public interface IPaymentStrategy
{
bool Validate(PaymentDetails details);
PaymentResult Process(PaymentDetails details);
void Rollback(string transactionId);
}
Здесь три связанных метода. Засунуть их в три отдельных
Func можно, но читаемость пострадает. Интерфейс даёт единую точку контракта и нормально тестируется через моки.Ещё один аргумент за интерфейс: когда реализации регистрируются в DI-контейнере и выбираются в рантайме. Например, разные провайдеры оплаты в зависимости от региона пользователя. Тут интерфейс упрощает конфигурацию и тестирование.
Если стратегия укладывается в одну функцию, используйте
Func<T> или Action<T>. Это проще, короче и не требует дополнительных абстракций. Если стратегия включает несколько связанных операций или вам нужна подмена реализаций через DI, интерфейс по-прежнему оправдан. Паттерн не устарел, но применять его стоит осознанно, а не по привычке.📍 Навигация: Вакансии • Задачи • Собесы
#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14❤4
🔥 База по экономике токенов и кэшированию от AI Platform Lead из Bitrix24
Знакомьтесь, Сергей Нотевский. AI Platform Lead в Bitrix24.
Он один из ключевых экспертов нашего курса AgentOps. На своих лекциях он детально разбирает экономику AI-агентов, кэширование токенов, LLM-инфраструктуру и вывод генеративных систем в стабильный прод.
Мы попросили Сергея поделиться материалами для тех, кто хочет оптимизировать косты на LLM в проде. Сохраняйте методичку по prefix cache метрике, которая напрямую влияет на ваши деньги.
Как говорят создатели Manus:
🛠 Что внутри методички (комбо из 3 статей + код):
🍒 Вишенка на торте: готовый SKILL для агента, который делает ревью вашего проекта, находит анти-паттерны и предотвращает низкое попадание в кэш.
— Забрать комбо-материалы на GitHub
P.S. Если хотите послушать Сергея вживую — ловите его на конференциях Kode Waves (май), Conversations AI и Highload Spb (июнь).
🎁 Акция в честь старта продаж!
Прямо сейчас при покупке Инженерного трека вы получаете полный доступ к материалам курса «Разработка ИИ-агентов» в подарок.
👉 Забрать 2 курса по цене 1 и начать обучение
Знакомьтесь, Сергей Нотевский. AI Platform Lead в Bitrix24.
Он один из ключевых экспертов нашего курса AgentOps. На своих лекциях он детально разбирает экономику AI-агентов, кэширование токенов, LLM-инфраструктуру и вывод генеративных систем в стабильный прод.
Мы попросили Сергея поделиться материалами для тех, кто хочет оптимизировать косты на LLM в проде. Сохраняйте методичку по prefix cache метрике, которая напрямую влияет на ваши деньги.
Как говорят создатели Manus:
“KV-cache hit rate is the single most important metric for a production-stage AI agent.”
🛠 Что внутри методички (комбо из 3 статей + код):
Экономика кэширования — особенности провайдеров и как правильно считать затраты.
Частые анти-паттерны — почему ваш кэш постоянно сбрасывается и вы платите больше.
Кэш в AI-агентах — специфика работы с памятью в автономных системах.
🍒 Вишенка на торте: готовый SKILL для агента, который делает ревью вашего проекта, находит анти-паттерны и предотвращает низкое попадание в кэш.
— Забрать комбо-материалы на GitHub
P.S. Если хотите послушать Сергея вживую — ловите его на конференциях Kode Waves (май), Conversations AI и Highload Spb (июнь).
🎁 Акция в честь старта продаж!
👉 Забрать 2 курса по цене 1 и начать обучение
❤1
Найм по интуиции и «культурному фиту» вместо реальных навыков — не редкость, а норма для большой части IT-рынка. И это подтверждается исследованиями, а не только ощущениями кандидатов.
📍 Навигация: Вакансии • Задачи • Собесы
Please open Telegram to view this post
VIEW IN TELEGRAM
Visitor позволяет добавлять операции к иерархии объектов без изменения их классов. Классический пример — обход AST, дерева документа или иерархии фигур. Вы создаёте отдельный объект-посетитель, который «обходит» структуру и выполняет нужное действие для каждого типа.
Долгое время это был единственный нормальный способ реализовать double dispatch в C#. Но начиная с C# 8 появился
switch по типам, а в C# 12 эта возможность стала ещё мощнее. И теперь Visitor выглядит как лишняя обвязка.Как выглядел Visitor
Допустим, есть иерархия фигур и нужно посчитать площадь.
public interface IShape
{
void Accept(IShapeVisitor visitor);
}
public class Circle : IShape
{
public double Radius { get; init; }
public void Accept(IShapeVisitor visitor) => visitor.Visit(this);
}
public class Rectangle : IShape
{
public double Width { get; init; }
public double Height { get; init; }
public void Accept(IShapeVisitor visitor) => visitor.Visit(this);
}
public interface IShapeVisitor
{
void Visit(Circle circle);
void Visit(Rectangle rectangle);
}
public class AreaCalculator : IShapeVisitor
{
public double Result { get; private set; }
public void Visit(Circle circle) =>
Result = Math.PI * circle.Radius * circle.Radius;
public void Visit(Rectangle rectangle) =>
Result = rectangle.Width * rectangle.Height;
}
Два интерфейса, метод
Accept в каждом классе, отдельный класс на каждую операцию. Для двух фигур это терпимо. Для десяти — уже много шаблонного кода.Как это делается через pattern matching
Тот же расчёт площади в одном
switch выражении.public static double CalculateArea(IShape shape) => shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r => r.Width * r.Height,
_ => throw new ArgumentException($"Unknown shape: {shape.GetType().Name}")
};
Нет интерфейса
IShapeVisitor. Нет метода Accept. Нет отдельного класса калькулятора. Логика читается сверху вниз, в одном месте.Что происходит, когда добавляется новый тип
Это ключевое отличие. Допустим, вы добавили
Triangle.В Visitor нужно добавить метод
Visit(Triangle t) в интерфейс IShapeVisitor, потом реализовать его в каждом посетителе. Если забыли — получите ошибку в рантайме. Или хуже — молчаливое некорректное поведение, если базовый класс имеет реализацию по умолчанию.В
switch выражении компилятор выдаст предупреждение, что Triangle не обработан. Вы узнаете об этом до запуска, на этапе сборки.// CS8509: The switch expression does not handle all possible values
public static double CalculateArea(IShape shape) => shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r => r.Width * r.Height
// Triangle не обработан — компилятор предупредит
};
Когда Visitor всё ещё уместен
Если иерархия типов находится в чужой библиотеке и вы не можете на неё влиять, а
switch по типам становится слишком длинным и разбросанным по кодовой базе — Visitor помогает собрать логику в одном месте. Также он полезен, когда операция требует сложного состояния между вызовами, которое неудобно протаскивать через отдельные методы.Но для большинства задач, где вы контролируете код и типы,
switch выражения проще, короче и безопаснее. Компилятор работает на вашей стороне, а не против вас.📍 Навигация: Вакансии • Задачи • Собесы
#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8🤔3
🆚 Minimal APIs vs контроллер
ASP.NET Core предлагает два способа строить HTTP API. Minimal APIs и контроллеры. Microsoft в документации рекомендует Minimal APIs для новых проектов, потому что это меньше кода, меньше конфигурации и выше производительность. Но это не значит, что контроллеры устарели. Это значит, что нужен осознанный выбор.
Когда Minimal APIs подходят лучше
Minimal APIs хорошо работают, когда API небольшой и сфокусированный. Микросервисы, внутренние API, легковесные эндпоинты, vertical slice архитектура. Каждый эндпоинт явно определяет маршрут, нет лишней церемонии с наследованием и атрибутами.
Вот как выглядит чистый эндпоинт на Minimal API:
Читаемо, прямолинейно, бизнес-логика вынесена в
Когда контроллеры всё ещё уместны
Контроллеры полезны там, где уже есть устоявшаяся MVC-инфраструктура, стандартизированные фильтры, сложное версионирование или большая команда, привыкшая к этому паттерну. Если в проекте выстроены конвенции вокруг контроллеров и это работает, переписывать на Minimal APIs ради моды нет смысла.
Что изменилось в .NET 10
В .NET 10 Microsoft добавила поддержку валидации для Minimal APIs, включая кастомизацию ответов через
Главная ловушка обоих подходов
Плохо организованный Minimal API проект превращается в огромный
Что выбрать
Для новых микросервисов и сфокусированных API автор статьи рекомендует Minimal APIs с vertical slices. Но не сваливать всё в
Для систем с уже выстроенной контроллерной архитектурой или жёсткими корпоративными стандартами контроллеры остаются рабочим выбором.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_view
ASP.NET Core предлагает два способа строить HTTP API. Minimal APIs и контроллеры. Microsoft в документации рекомендует Minimal APIs для новых проектов, потому что это меньше кода, меньше конфигурации и выше производительность. Но это не значит, что контроллеры устарели. Это значит, что нужен осознанный выбор.
Когда Minimal APIs подходят лучше
Minimal APIs хорошо работают, когда API небольшой и сфокусированный. Микросервисы, внутренние API, легковесные эндпоинты, vertical slice архитектура. Каждый эндпоинт явно определяет маршрут, нет лишней церемонии с наследованием и атрибутами.
Вот как выглядит чистый эндпоинт на Minimal API:
public static class CreateOrderEndpoint
{
public static IEndpointRouteBuilder MapCreateOrder(this IEndpointRouteBuilder app)
{
app.MapPost("/orders", async (
CreateOrderRequest request,
ICreateOrderUseCase useCase,
CancellationToken cancellationToken) =>
{
var result = await useCase.ExecuteAsync(request, cancellationToken);
return Results.Created($"/orders/{result.OrderId}", result);
});
return app;
}
}
Читаемо, прямолинейно, бизнес-логика вынесена в
ICreateOrderUseCase.Когда контроллеры всё ещё уместны
Контроллеры полезны там, где уже есть устоявшаяся MVC-инфраструктура, стандартизированные фильтры, сложное версионирование или большая команда, привыкшая к этому паттерну. Если в проекте выстроены конвенции вокруг контроллеров и это работает, переписывать на Minimal APIs ради моды нет смысла.
Что изменилось в .NET 10
В .NET 10 Microsoft добавила поддержку валидации для Minimal APIs, включая кастомизацию ответов через
IProblemDetailsService. Раньше отсутствие встроенной валидации было одним из главных аргументов против Minimal APIs. Теперь этот аргумент слабее.Главная ловушка обоих подходов
Плохо организованный Minimal API проект превращается в огромный
Program.cs. Плохо организованный проект на контроллерах превращается в папку с пустыми pass-through экшенами. Ни один из подходов не спасёт от слабой архитектуры.Что выбрать
Для новых микросервисов и сфокусированных API автор статьи рекомендует Minimal APIs с vertical slices. Но не сваливать всё в
Program.cs, а организовывать эндпоинты по фичам, выносить бизнес-логику из обработчиков маршрутов, использовать явные контракты запросов и ответов.Для систем с уже выстроенной контроллерной архитектурой или жёсткими корпоративными стандартами контроллеры остаются рабочим выбором.
📍 Навигация: Вакансии • Задачи • Собесы
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6🔥2