C# Portal | Программирование – Telegram
C# Portal | Программирование
14.9K subscribers
964 photos
117 videos
24 files
806 links
Присоединяйтесь к нашему каналу и погрузитесь в мир для C#-разработчика

Связь: @devmangx

РКН: https://clck.ru/3FocB6
Download Telegram
Библиотеки, которые реально используют в Microsoft.

Надоело, что на каждом углу советуют AutoMapper и Polly? Вот инструменты, которые инженеры Microsoft юзают в продакшене. Они прошли боевые условия, экономят память и закрывают задачи, которые масштабируются до уровня Bing и Azure.

1. Microsoft.IO.RecyclableMemoryStream

Проблема:

Когда в API гоняется большой трафик или крутятся экспортные задания, обычный new MemoryStream() вызывает фрагментацию LOH (Large Object Heap) и частые остановки GC. В проде типа Bing это легко выливается в
OutOfMemoryException.

Решение:

RecyclableMemoryStream:

- переиспользует буферы через пул,
- снижает аллокации и не лезет в LOH,
- даёт нормальную диагностику по памяти.

Где уместно:

ASP.NET API, которые возвращают PDF, изображения, Excel,

- фоновые задачи, работающие с файлами,
- бэкенды под gRPC и SignalR.

До:

using var ms = new MemoryStream();


После:

var mgr = new RecyclableMemoryStreamManager();
using var ms = mgr.GetStream();


Бенч:

Method           | Memory | Gen 0 | Gen 2
---------------- | -------| ----- | -----
MemoryStream | 85 KB | 1 | 1
RecyclableMemory | 2 KB | 0 | 0


2. LoggerMessage (генератор кода)

Проблема:
В сервисах с высоким трафиком даже такие безобидные логи:

_logger.LogInformation($"Обработка заказа {orderId}");


создают проблемы:

- лишние аллокации из-за интерполяции строк,
- боксинг значимых типов,
- нагруженный GC и просадка пропускной способности.

На масштабе Azure это превращается в задержки и прожорливость по памяти.

Решение:
LoggerMessage из .NET 8 генерирует методы на этапе компиляции:

- без интерполяции и упаковки типов,
- со структурированным форматированием через Span<T>,
- с IL без лишних аллокаций,

в 5–10 раз быстрее обычного ILogger.LogInformation(...).

Где уместно:

- нагруженные веб-API,
- воркфлоу/ивент-хендлеры, где логов много,
- телеметрия и критичные hot-path’ы.

До (аллокации):

_logger.LogInformation($"Обработка запроса {orderId}");


После (генерация):

[LoggerMessage(EventId = 100, Level = LogLevel.Information, Message = "Обработка запроса {OrderId}")]
partial void LogProcessingOrder(int orderId);

// вызов
LogProcessingOrder(orderId);


Можно вынести такие методы в статические хелперы и шарить между сервисами.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥226👍3👎1🤣1
C# Portal | Программирование
Библиотеки, которые реально используют в Microsoft. Надоело, что на каждом углу советуют AutoMapper и Polly? Вот инструменты, которые инженеры Microsoft юзают в продакшене. Они прошли боевые условия, экономят память и закрывают задачи, которые масштабируются…
Ещё стоит упомянуть:

3. Microsoft.SemanticKernel

Проблема: Обычных запросов и ответов уже не хватает для продакшн-приложений на ИИ. Нужны память между диалогами, разбиение целей на шаги и доступ к API вроде Microsoft 365 Copilot. Реализовать всё это вручную в .NET — тяжело и долго.

Решение:

SemanticKernel — официальный SDK от Microsoft для сборки похожих на агентов ИИ-процессов. Он даёт:

* Вызов инструментов и плагинов (C# или OpenAPI);
* Оркестрацию цепочек запросов;
* Долговременную память между вызовами;
* Планировщик для разбиения задач на подзадачи.

Когда стоит брать:

* Корпоративные ИИ-ассистенты (CRM, генерация отчётов и т.п.);
* ИИ-процессы, которые ходят во внутренние API/функции;
* Многошаговые чат-боты с планированием и памятью.

До (одиночный запрос):

var result = await openAiClient
.GetCompletionAsync("Какая погода в Москве?");


После (оркестрация через SemanticKernel):

var kernel = Kernel.CreateBuilder().Build();
var prompt = kernel
.CreateFunctionFromPrompt("Какая погода в Москве?");
var result = await prompt.InvokeAsync();


Можно регистрировать плагины, которые вызывают API, делают авторизацию, исполняют SQL или дергают другие сервисы — и всё из .NET. Документация

4. System.Threading.Channels + IAsyncEnumerable

Проблема: BlockingCollection, ConcurrentQueue и кастомные очереди в паттерне «производитель/потребитель» часто упираются в блокировки, нестабильность и проблемы под нагрузкой. Особенно в телеметрии и потоковых пайплайнах.

Решение:

System.Threading.Channels даёт:

* Быстрый обмен сообщениями внутри процесса;
* Контроль пропускной способности через ограниченные каналы;
* Простую связку с IAsyncEnumerable<T>;
* Асинхронный стриминг без лишних выделений памяти.

Изначально жило внутри SignalR и gRPC, сейчас это нормальный способ собирать отказоустойчивые асинхронные конвейеры в .NET.

Когда использовать:

* Сбор телеметрии и обработка метрик;
* Асинхронные пайплайны логов/событий;
* Очередеподобное поведение без внешней брокерской инфраструктуры;
* Изоляция перегородками (Bulkhead) в фоновых сервисах.

До (BlockingCollection или Queue<T>):

var queue = new Queue<int>();
lock (queue) { queue.Enqueue(42); }
int item;
lock (queue) { item = queue.Dequeue(); }


После (каналы и async-потоки):

var ch = Channel.CreateUnbounded<int>();
await ch.Writer.WriteAsync(42);

await foreach (var item in ch.Reader.ReadAllAsync())
Console.WriteLine(item);


Отлично работает из коробки с await foreach и подходит для фоновых задач с I/O, стриминговых API и повторяющихся пайплайнов.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
7👍3
Может кому понадобится: проект генерирует архитектурные диаграммы из кода.

https://github.com/likec4/likec4/ 🤨

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10🤣21
Как компилятор выводит тип из default?

default в C# штука мощная. Для ссылочных типов он даёт null, для значимых — нулевое значение. Забавно, что default(T) и new T() для структур могут вести себя по-разному. Раньше в C# нужно было писать default(T), а сейчас, если тип очевиден для компилятора, можно оставить просто default. Но как именно компилятор понимает, что за тип там должен быть? И всегда ли можно доверять этому выводу?

Разберём простые варианты:

// Тип тянется из левой части
int foo = default;

// Тип тянется из параметра
Foo(default);
void Foo(int bar) => throw null;


Тут всё понятно. Но что происходит в switch-выражении?

var sample = new byte[0];
ReadOnlyMemory<byte> foo = sample switch
{
byte[] value => value,
_ => default
};


Кажется логичным ожидать, что default станет default(ReadOnlyMemory<byte>), ведь слева стоит такой тип. Но нет — фактически это будет default(byte[]). Компилятор берёт тип не из левой части, а из веток switch и пытается вывести общий тип. В данном случае общий тип — byte[], значит и default станет byte[].

Для этого конкретного примера разницы нет — и default(byte[]), и default(ReadOnlyMemory<byte>) в итоге дадут совместимый результат. Но последствия могут всплыть позже. Например, если заменить тип на nullable:

var sample = new object();
ReadOnlyMemory<byte>? foo = sample switch
{
byte[] value => value,
_ => default
};


Тип default снова выводится как default(byte[]), то есть null. А дальше начинает работать неявное преобразование:

// из исходников .NET 9
public static implicit operator ReadOnlyMemory<T>(T[]? array)
=> new ReadOnlyMemory<T>(array);


Конструктор ReadOnlyMemory<T>, который принимает T[]?, при null выдаёт пустое значение:

// из исходников .NET 9
public ReadOnlyMemory(T[]? array)
{
if (array == null)
{
this = default;
return;
}

}


В итоге foo окажется пустым ReadOnlyMemory<byte> — не null. То есть foo.HasValue будет true.

Если же явно указать тип в default:

object sample = new();

ReadOnlyMemory<byte>? foo = sample switch
{
byte[] value => value,
_ => default(ReadOnlyMemory<byte>?)
};


То результатом switch будет именно null, и foo.HasValue станет false.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
62
Самые частые способы уменьшить размер Docker-образа

Сохраните себе — это реально частый вопрос на собесах.

• Использовать минимальную базу
Брать alpine, distroless или slim, а не полноценные образы с целой ОС.

• Multi-stage сборка
Собрать всё тяжёлое и зависимости в одном этапе, а в финальный образ скопировать только готовые артефакты.

• Меньше слоёв
Группировать команды в один RUN, когда это уместно, и не плодить лишние слои.

• Убирать зависимости для сборки
Компиляторы, менеджеры пакетов и прочий мусор — только на build-стейдже, не в рантайме.

• Чистить кеш пакетного менеджера
Чистить apt, yum, apk и прочее в том же слое, где ставятся пакеты.

• .dockerignore
Не тащить лишнее в образ: .git, тесты, доки, локальные конфиги и т.д.

• Не копировать всё подряд
Вместо COPY . . копировать только то, что реально нужно для приложения.

• Меньше и легче рантайм
Статические бинарники, минимальные рантаймы, без лишних зависимостей.

• Чистить бинарники и ресурсы
Убирать отладочную информацию, неиспользуемые ассеты и т.п.

• Настраивать права сразу
Ставить chmod/chown рано, чтобы не плодить лишние слои с изменёнными разрешениями.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥63👍1
8 Распространённых Проблем Проектирования Систем и их Решения

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍94🔥2
Уходим от анемичных моделей. Пример DDD-рефакторинга.

Если работали с древней кодовой базой на C#, то наверняка видели анемичную доменную модель. Открываешь какой-нибудь OrderService и ловишь фейспалм: в одном классе и ценообразование, и скидки, и проверка склада, и работа с БД. Оно вроде крутится, пока продукт маленький, но дальше каждая новая фича превращается в рулетку с регрессиями. Тесты тоже летят к черту, потому что доменная логика спрятана под инфраструктурой.

Классика анемичного домена: сущности как DTO с геттерами и сеттерами, а вся логика в сервисах. Понимать систему тяжело, любое изменение наугад, а поведение расползлось по слоям.

В этой серии мы:

* посмотрим на типичную анемичную реализацию
* найдем скрытые бизнес-правила, которые делают её хрупкой
* шаг за шагом переложим ответственность в агрегат и дадим ему нормальное поведение
* разберем выгоды, чтобы можно было аргументированно защитить изменения в команде

Старт. Божественный OrderService

Ниже довольно жизненный пример. Помимо подсчета итоговой суммы он:

* навешивает VIP-скидку 5 процентов
* падает, если товара нет на складе
* отклоняет заказ, если клиент вышел за кредитный лимит

// OrderService.cs
public void PlaceOrder(
Guid customerId,
IEnumerable<OrderItemDto> items)
{
var customer = _db.Customers.Find(customerId);
if (customer is null)
throw new ArgumentException("Клиент не найден");

var order = new Order { CustomerId = customerId };

foreach (var dto in items)
{
var inventory = _invService.GetStock(dto.ProductId);
if (inventory < dto.Quantity)
throw new InvalidOperationException("Товара недостаточно");

var price = _pricingService.GetPrice(dto.ProductId);
var lineTotal = price * dto.Quantity;

if (customer.IsVip)
lineTotal *= 0.95m;

order.Items.Add(new OrderItem
{
ProductId = dto.ProductId,
Quantity = dto.Quantity,
UnitPrice = price,
LineTotal = lineTotal
});
}

order.Total = order.Items.Sum(i => i.LineTotal);

if (customer.CreditUsed + order.Total > customer.CreditLimit)
throw new InvalidOperationException("Кредитный лимит превышен");

_db.Orders.Add(order);
_db.SaveChanges();
}


Что здесь рыхлое

1. Правила размазаны по сервису. Скидки, склад, кредит — всё в кучу. Чтобы понять бизнес-логику, нужно разбирать инфраструктурный код.
2. Жесткая связка. OrderService тащит в себя знание о ценах, остатках и EF Core, хотя цель — просто разместить заказ.
3. Больно тестировать. Для юнит-теста нужно мокать БД, ценообразование, склад и сценарии для VIP и не VIP. Тесты получаются хрупкие и громоздкие.

Наша цель — втащить правила в домен, чтобы приложение занималось оркестрацией, а не бизнес-логикой.

Принципы рефакторинга

1. Инварианты держим рядом с данными. Проверки склада, скидки, кредит — в агрегате Order, а не в сервисе сверху.
2. Слой приложения читался как сценарий. Хочется "разместить заказ", а не "подсчитать сумму, проверить лимит, сохранить в БД".
3. Рефакторим кусками. Маленькие шаги, компилируемые на каждом этапе. Никаких переписываний с нуля.
4. Баланс чистоты и практичности. Меняем только там, где выигрыш в читабельности, безопасности и тестируемости действительно окупает вложения.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
12
C# Portal | Программирование
Уходим от анемичных моделей. Пример DDD-рефакторинга. Если работали с древней кодовой базой на C#, то наверняка видели анемичную доменную модель. Открываешь какой-нибудь OrderService и ловишь фейспалм: в одном классе и ценообразование, и скидки, и проверка…
Пример DDD-рефакторинга.

Пошаговый рефакторинг


Цель здесь не в догонялках за академической чистотой или «идеальным» DDD. Идея в том, чтобы постепенно усилить связность и дать домену пространство для нормального выражения своей логики. На каждом шаге задаём простой вопрос: это поведение должно жить в домене? Если да — переносим его туда.

Внедрение логики создания и проверки

Первый шаг — заставить агрегат сам собирать себя. Статический метод Create становится единой точкой входа, где инварианты сразу проверяются по принципу fail fast.

Проверка остатков прямо в Order делает код тестируемее, но жёстко привязывает поток создания заказа к доступности товара. Не всегда это хорошо: в некоторых доменах правильнее оформлять это через доменное событие и проверять асинхронно.

// Order.cs (фабричный метод)

public static Order Create(
Customer customer,
IEnumerable<(Guid productId, int quantity)> lines,
IPricingService pricingService,
IInventoryService invService)
{
var order = new Order(customer.Id);

foreach (var (id, quantity) in lines)
{
if (invService.GetStock(id) < quantity)
throw new InvalidOperationException("Товара недостаточно");

var price = pricingService.GetPrice(id);
order.AddItem(id, quantity, price, customer.IsVip);
}

order.EnsureCreditWithinLimit(customer);

return order;
}


Зачем это всё?

Теперь заказ сразу падает с ошибкой, если нарушен какой-то инвариант. Сервис больше не занимается микроменеджментом скидок или склада — всё это живёт внутри домена.

Обратите внимание, как мы теперь следуем принципу «Скажи, не спрашивай». Вместо того, чтобы сервис проверял условия и затем манипулировал заказом, мы говорим заказу создать себя с необходимыми встроенными проверками. Это фундаментальный сдвиг в сторону инкапсуляции.

Замечание о передаче сервисов в методы домена

Передача зависимостей вроде IPricingService или IInventoryService в доменный метод Order.Create может выглядеть нестандартно, но это осмысленное решение. Так мы оставляем оркестрацию в домене, а не превращаем app-сервис в procedural god-object.

Зависимости передаются явно, без скрытых контейнеров внутри сущности. Подход пушка, но применять его стоит там, где операция реально принадлежит домену и выигрывает от прямого доступа к сервисам.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
8👍3🤔1
Современные Сетевые Сервисы

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
8❤‍🔥2
Принёс вам классный ресурс для изучения паттернов проектирования

На сайте Refactoring Guru собрали примеры для каждого паттерна на таких языках, как C#, Java, Python, PHP, Rust и ещё куча других.

Примеры суперпонятные, с кодом и пояснениями.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11
Тестирование текущего времени с помощью TimeProvider и FakeTimeProvider

В тестах тяжело работать с вещами, которые зависят от конкретных данных. Представьте файловую систему: чтобы тесты корректно отрабатывали, нужно гарантировать, что структура файлов именно такая, как ожидается. С датами и временем та же проблема. Если вы пишете тесты, завязанные на текущую дату, при следующем запуске они просто упадут. Поэтому такие зависимости нужно абстрагировать, чтобы их можно было нормально тестировать.

В этом посте разберем класс TimeProvider, как его использовать и как подменять в тестах.

Раньше: ручной интерфейс

Раньше самый простой способ абстрагироваться от работы с датой и временем был в том, чтобы вручную завести интерфейс или абстрактный класс для доступа к текущему времени:

public interface IDateTimeWrapper
{
DateTime GetCurrentDate();
}


И его стандартную реализацию, которая возвращает время в UTC:

public class DateTimeWrapper : IDateTimeWrapper
{
public DateTime GetCurrentDate()
=> DateTime.UtcNow;
}


Либо похожий вариант через абстрактный класс:

public abstract class DateTimeWrapper
{
public virtual DateTime GetCurrentDate() => DateTime.UctNow;
}


Дальше этот класс просто регистрировался в DI-контейнере, и можно было использовать. Минус очевидный: в каждом проекте приходилось писать одно и то же.

Сейчас: класс TimeProvider

Начиная с .NET 8, команда .NET добавила абстрактный класс TimeProvider. Он не только даёт абстракцию над локальным временем, но и предоставляет методы для работы с высокоточными временными метками и часовыми поясами.

Важно: даты возвращаются в виде DateTimeOffset, а не DateTime.

TimeProvider доступен из коробки в консольных приложениях .NET:

DateTimeOffset utc = TimeProvider.System.GetUtcNow();
Console.WriteLine(utc);

DateTimeOffset local = TimeProvider.System.GetLocalNow();
Console.WriteLine(local);


Если вы используете dependency injection, его достаточно зарегистрировать как синглтон:

builder.Services.AddSingleton(TimeProvider.System);


Пример использования:

public class Vacation(TimeProvider _time)
{
public bool IsVacation
=> _time.GetLocalNow().Month == 8;
}


Тестирование TimeProvider

Для тестов можно использовать NuGet-пакет Microsoft.Extensions.TimeProvider.Testing, в котором есть класс FakeTimeProvider. Это заглушка для абстрактного TimeProvider.

С помощью FakeTimeProvider можно вручную задавать текущее UTC-время, локальное время и другие параметры, которые предоставляет TimeProvider:

[Fact]
public void WhenItsAugust_ShouldReturnTrue()
{
// Arrange
var fakeTime = new FakeTimeProvider();
fakeTime.SetUtcNow(
new DateTimeOffset(2025, 8, 14,
22, 24, 12, TimeSpan.Zero));
var sut = new Vacation(fakeTime);

Assert.True(sut.IsVacation);
}


На самом деле TimeProvider умеет намного больше, чем просто возвращать UTC и локальное время. Подробности можно посмотреть в официальной документации

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍93
Настоятельно рекомендую фри PDF-книгу:

Algorithms от Jeff Erickson — с кучей наглядных иллюстраций и упражнений.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8😁1
Создание параметризованных тестов в xUnit

xUnit использует атрибуты для объявления тестовых методов.
Атрибут Fact описывает обычный тест, а Theory — параметризованный.

Допустим, у нас есть тест, который проверяет, что email-парсер корректно извлекает домен:

public void Should_Return_Domain(
string email, string expectedDomain)
{
var parser = new EmailParser();
var domain = parser.GetDomain(email);
Assert.Equal(domain, expectedDomain);
}


Рассмотрим четыре способа написания параметризованных тестов.

1. InlineData

Самый простой вариант — передать тестовые данные напрямую через атрибут:

[Theory]
[InlineData("test@test.com", "test.com")]
[InlineData("user@github.io", "github.io")]
public void Should_Return_Domain(
string email, string expectedDomain)
{ … }


В InlineData мы передаём значения аргументов тестируемого метода и ожидаемый результат. Атрибут можно указывать сколько угодно раз — по одному на каждый тестовый кейс.

Минус в том, что при большом количестве кейсов код быстро разрастается. Плюс можно использовать только константные значения параметров.

2. MemberData

MemberData позволяет программно задавать тестовые данные через статическое свойство или метод:

[Theory]
[MemberData(nameof(EmailTestData))]
public void Should_Return_Domain(
string email, string expectedDomain)
{ … }

public static IEnumerable<object[]>
EmailTestData => new List<object>
{
new object[] { "test@test.com", "test.com" },
new object[] { "user@github.io", "github.io" }
};


В атрибуте MemberData указывается имя члена. Ограничение здесь одно — свойство или метод должны возвращать IEnumerable<object[]>, из-за чего строгая типизация отсутствует.

3. ClassData

ClassData выносит тестовые данные в отдельный класс. Это удобно для структурирования данных и повторного использования.

Тестовые данные загружаются из класса, который реализует IEnumerable<object[]>:

[Theory]
[ClassData(typeof(EmailTestData))]
public void Should_Return_Domain(
string email, string expectedDomain)
{ … }

public class EmailTestData : IEnumerable<object[]>
{
public IEnumerable<object[]> GetEnumerator()
{
yield return new object[] {
"test@test.com", "test.com" };
yield return new object[] {
"user@github.io", "github.io" };
}

IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
}


Подход более громоздкий — нужно вручную реализовывать IEnumerable. При этом проблема с типобезопасностью всё ещё остаётся.

4. TheoryData

TheoryData позволяет хранить тестовые данные с сохранением строгой типизации.

Пример с использованием ClassData (аналогично можно применять и с MemberData, возвращая TheoryData из свойства или метода):

[Theory]
[ClassData(typeof(EmailTestData))]
public void Should_Return_Domain(
string email, string expectedDomain)
{ … }

public class EmailTestData :
TheoryData<string, string>
{
public EmailTestData()
{
Add("test@test.com", "test.com");
Add("user@github.io", "github.io");
}
}


TheoryData — это обобщённый класс, в котором явно задаются типы параметров теста.
Для добавления одного тестового кейса вызывается Add, а новые сценарии просто добавляются дополнительными вызовами этого метода.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥31👍1
This media is not supported in your browser
VIEW IN TELEGRAM
Это аккуратный и чистый способ работать с Git из терминала

Froggit — минималистичный терминальный UI для Git. Он помогает стейджить файлы, делать коммиты, управлять ветками и смотреть логи без необходимости заучивать сложные команды.

Всё управляется с клавиатуры, работает быстро и без лишних отвлекающих элементов, поэтому проще оставаться в потоке при работе с Git.

Тестим и оцениваем 😁

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4👏2
Разбираем генераторы исходного кода.

Генераторы исходного кода, появившиеся в C# 9, стали мощным инструментом метапрограммирования в .NET. Они позволяют разработчикам автоматически генерировать дополнительный C#-код. Генераторы выполняются на этапе компиляции: анализируют существующий код и создают новые файлы исходников. Эти файлы затем компилируются вместе с остальной кодовой базой, что даёт возможность динамически расширять проект на основе уже написанного кода.

Генераторы исходного кода можно использовать для разных задач: скафолдинга, валидации, повышения читаемости и упрощения поддержки кода.

В C# 12 генераторы получили дальнейшее развитие. Появилась поддержка более сложных сценариев и выросла производительность за счёт уменьшения шаблонного кода и более эффективных проверок на этапе компиляции.

1. Инкрементные генераторы

Инкрементные генераторы регенерируют код только тогда, когда это действительно нужно. Они кэшируют результаты предыдущих запусков и пересобирают сгенерированный код только при изменении входных данных, что заметно ускоряет сборку.

2. Анализ зависимостей исходного кода

Компилятор точнее определяет, какие части проекта зависят от сгенерированного кода. В результате сборки становятся эффективнее, а количество лишних перекомпиляций сокращается.

3. Улучшенная диагностика

Ошибки и предупреждения можно более точно сообщать прямо через сгенерированный код. Разработчик получает понятную обратную связь во время разработки, что уп shows упрощает отладку и сопровождение генераторов.

4. Улучшения API Roslyn

API Roslyn получил дополнительные возможности для анализа и модификации синтаксического дерева, что позволяет реализовывать более сложную логику генерации кода.

Простой пример генератора кода

1. Настройка проекта

Создадим проект библиотеки и установим необходимые NuGet-пакеты:

dotnet new classlib -n MySourceGenerator
cd MySourceGenerator
dotnet add package Microsoft.CodeAnalysis.CSharp


2. Генератор кода

Нам понадобится класс, реализующий интерфейс IIncrementalGenerator:

using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;

[Generator]
public class HelloWorldGenerator : IIncrementalGenerator
{
public void Initialize(
IncrementalGeneratorInitializationContext context)
{
var src =
"""
using System;
namespace HelloGenerated;
public static class HelloWorld
{
public static void SayHello()
=> Console.WriteLine("Hello from the generated code!");
}
""";

context.RegisterPostInitializationOutput(
ctx => ctx.AddSource(
"HelloWorldGenerated",
SourceText.From(src, Encoding.UTF8)));
}
}


Этот генератор добавляет в проект класс HelloWorld с методом SayHello. Сгенерированный код компилируется вместе с основным проектом, поэтому метод HelloGenerated.HelloWorld.SayHello() можно вызывать напрямую из вашего кода.

3. Интеграция с проектом

Создадим консольное приложение:

dotnet new console -n UseSourceGenerator


Добавим ссылку на проект генератора:

<ProjectReference
Include="..\MySourceGenerator\MySourceGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />


Обратите внимание на два нестандартных атрибута:

OutputItemType="Analyzer" — указывает, что проект должен использоваться как анализатор;

ReferenceOutputAssembly="false" — гарантирует, что целевой проект не будет ссылаться на DLL генератора во время компиляции.

После этого сгенерированный класс HelloWorld станет доступен в консольном проекте.

4. Сборка и запуск

Соберём проект и запустим его. В консоли появится вывод из сгенерированного метода:

// Program.cs
HelloGenerated.HelloWorld.SayHello();


В след. посте про чтение кода из файла 😌

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
7👍1🤔1
This media is not supported in your browser
VIEW IN TELEGRAM
Это рай для программистов: репозиторий с сотнями бесплатных API.

Отлично подходит для практики и прокачки навыков программирования

Забираем здесь 🥶

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Знаешь, что в .NET 10 сильно снизили накладные расходы при работе с любыми встроенными коллекциями через интерфейсы (да, не только с массивами)?

Итерация по списку через IList<T> или по словарю через IDictionary<K, V> и т.п. теперь почти не даёт абстрактного оверхеда.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🤯11👍10🔥42
Держи инструмент для запросов к данным, если уже надоели классические клиенты для БД: agx

Внутри есть умный SQL-редактор с подсветкой синтаксиса и помощью LLM при написании запросов, браузер схем для быстрого просмотра структуры данных, а также импорт данных из файлов через drag-and-drop.

Работает как нативное десктопное приложение и при этом поддерживает подключение к удалённым серверам через веб-интерфейс, что удобно под разные сценарии использования.

🛌

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Разбираем Server-Sent Events в ASP.NET Core и .NET 10.

Обновления UI в реальном времени больше не считаются просто желательной фичей. Большинство современных приложений ожидают поток данных в реальном времени от сервера. Долгие годы основным решением в экосистеме .NET был SignalR. Он действительно очень мощный, но для более простых сценариев приятно иметь альтернативы.

В ASP.NET Core 10 появился собственный высокоуровневый API для Server-Sent Events (SSE). Он закрывает разрыв между примитивным HTTP-поллингом и полнодуплексными WebSockets через SignalR.

Зачем?

SignalR — инструмент, который автоматически работает с WebSockets, Long Polling и SSE, предоставляя полнодуплексный (двусторонний) канал связи. Но за это приходится платить: свой протокол, обязательная клиентская библиотека и необходимость в «липких сессиях» или отдельном бэкенде (например, Redis) для масштабирования.

В отличие от SignalR, SSE:

- Однонаправленные - разработаны специально для потоковой передачи данных с сервера на клиент.
- Нативны для HTTP - это стандартный HTTP-запрос с типом содержимого text/event-stream. Никаких пользовательских протоколов.
- Поддерживают автоматическое переподключение - браузеры обрабатывают переподключения напрямую через API EventSource.
- Легковесны - нет тяжёлых клиентских библиотек или сложной логики рукопожатия.

Простейшая SSE-конечная точка

Мы можем использовать новый объект Results.ServerSentEvents, чтобы вернуть поток событий из любого IAsyncEnumerable<T>. Поскольку IAsyncEnumerable представляет поток данных, который может приходить со временем, сервер понимает, что HTTP-соединение нужно держать открытым, а не закрывать его после первого куска данных.

Минимальный пример SSE-эндпоинта, который в реальном времени передаёт события о размещении заказов:

app.MapGet("orders/realtime", (
ChannelReader<OrderPlacement> reader,
CancellationToken ct) =>
{
// ReadAllAsync возвращает IAsyncEnumerable
// Results.ServerSentEvents заставляет браузер держать соединение открытым
// Новые данные отправляются клиенту по мере поступления из канала
return Results.ServerSentEvents(
reader.ReadAllAsync(ct),
eventType: "orders");
});


Когда клиент обращается к этой конечной точке:

Сервер отправляет заголовок Content-Type: text/event-stream.

Соединение остаётся открытым и ждёт данные.

Как только приложение пишет новый заказ в канал, IAsyncEnumerable возвращает элемент, и .NET сразу отправляет его в браузер по открытому HTTP-соединению.

Это очень эффективный способ реализовать push-уведомления без накладных расходов, характерных для stateful-протоколов.

В примере используется Channel. В реальном приложении это может быть фоновый сервис, который слушает очередь сообщений (например, RabbitMQ или Azure Service Bus) или поток изменений БД и прокидывает новые события в канал для доставки подключённым клиентам.

В след. посте поговорим про обработку пропущенных событий 👍

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥14👍6👏21
📱 Держите 6 хороших каналов по искусственному интеллекту и программированию для любого уровня!

Выбирай направление:

📱 Нейросети@neuro_prompt

🤖 AI-инструменты @ai_prompt

📱 Python@python_prompt

🤔 InfoSec & Хакинг @infosec_prompt

👩‍💻 IT Новости @it_news

😄 IT Мемы@it_memes

Промпты, обучение, шпаргалки и полезные ресурсы на каждую тему!
Please open Telegram to view this post
VIEW IN TELEGRAM
2👎1🥴1