Разбираем System.Threading.Channels в C#!
Channels помогают строить асинхронные очереди между producer и consumer без ручного lock вокруг коллекции. Один код пишет элементы, другой читает их по мере появления.
В этой шпоре собраны CreateUnbounded, CreateBounded, Writer, Reader, WriteAsync, ReadAllAsync и Complete.
➡️ C# Ready | #шпора
Channels помогают строить асинхронные очереди между producer и consumer без ручного lock вокруг коллекции. Один код пишет элементы, другой читает их по мере появления.
В этой шпоре собраны CreateUnbounded, CreateBounded, Writer, Reader, WriteAsync, ReadAllAsync и Complete.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7❤3👍3
Знали, что GetAsync может загрузить всё тело ответа в память ещё до начала обработки?
Для короткого JSON это обычно незаметно. С архивом или большим экспортом ситуация другая, поскольку тело ответа может занять значительную часть памяти процесса.
Обычный вызов ждёт, пока HttpClient буферизует всё содержимое:
В этом режиме обработка начинается после загрузки ответа. Для небольших данных это удобно, но для крупных файлов может создать лишний пик памяти.
Чтобы получить управление сразу после заголовков, укажи ResponseHeadersRead:
Теперь тело ещё не прочитано целиком. Сначала проверь HTTP-статус, чтобы не начать сохранять страницу с ошибкой вместо файла:
После проверки открой поток содержимого ответа:
Чтение происходит по мере обработки данных. Поток нужно использовать, пока открыт response, потому что он принадлежит этому ответу.
Например, содержимое можно копировать прямо в файловый поток:
CopyToAsync переносит данные частями, не создавая ещё один массив размером со весь файл. CancellationToken позволяет отменить и сетевой запрос, и копирование.
Если сервер сообщил размер через Content-Length, его можно проверить до сохранения:
Заголовок может отсутствовать или быть недостоверным. Для строгого ограничения размера проверяй и фактически прочитанные байты.
➡️ C# Ready | #совет
Для короткого JSON это обычно незаметно. С архивом или большим экспортом ситуация другая, поскольку тело ответа может занять значительную часть памяти процесса.
Обычный вызов ждёт, пока HttpClient буферизует всё содержимое:
using var response = await client.GetAsync(url, cancellationToken);
В этом режиме обработка начинается после загрузки ответа. Для небольших данных это удобно, но для крупных файлов может создать лишний пик памяти.
Чтобы получить управление сразу после заголовков, укажи ResponseHeadersRead:
using var response = await client.GetAsync(
url, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
Теперь тело ещё не прочитано целиком. Сначала проверь HTTP-статус, чтобы не начать сохранять страницу с ошибкой вместо файла:
response.EnsureSuccessStatusCode();
После проверки открой поток содержимого ответа:
await using var source = await response.Content.ReadAsStreamAsync(cancellationToken);
Чтение происходит по мере обработки данных. Поток нужно использовать, пока открыт response, потому что он принадлежит этому ответу.
Например, содержимое можно копировать прямо в файловый поток:
await source.CopyToAsync(destination, cancellationToken);
CopyToAsync переносит данные частями, не создавая ещё один массив размером со весь файл. CancellationToken позволяет отменить и сетевой запрос, и копирование.
Если сервер сообщил размер через Content-Length, его можно проверить до сохранения:
if (response.Content.Headers.ContentLength is long size && size > maxBytes)
throw new InvalidOperationException("File is too large");
Заголовок может отсутствовать или быть недостоверным. Для строгого ограничения размера проверяй и фактически прочитанные байты.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7👍6❤5
Собираем защиту webhook от повторной обработки!
Платёжные системы и внешние сервисы могут прислать одно и то же событие несколько раз. Наша задача обработать событие один раз и пропустить повтор.
В этой задаче:
Такой подход полезен для уведомлений, импорта заказов и любых webhook, где повторная доставка является нормальным поведением.
➡️ C# Ready | #задача
Платёжные системы и внешние сервисы могут прислать одно и то же событие несколько раз. Наша задача обработать событие один раз и пропустить повтор.
В этой задаче:
• берём idempotency key из события
• атомарно отмечаем ключ как обработанный
• пропускаем дубликаты
• отделяем проверку от бизнес-логики
Такой подход полезен для уведомлений, импорта заказов и любых webhook, где повторная доставка является нормальным поведением.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🔥5❤4
Почему для денег в C# обычно нужен decimal, а не double?
double удобен для инженерных расчётов, координат, графики и статистики. Но для денег он часто создаёт ошибки.
Причина в том, что double хранит число в двоичном формате с плавающей точкой. Не все десятичные дроби можно представить точно.
Простой пример выглядит так:
Результат может оказаться не ровно 0.3, а числом с маленьким хвостом. Для UI это иногда скрывается форматированием, но в сравнении и расчётах ошибка остаётся.
Особенно плохо сравнивать такие значения напрямую:
В финансовых сценариях обычно используют decimal:
Суффикс m важен. Без него литерал сначала будет double, и смысл decimal частично потеряется.
Для округления лучше явно указать правило:
Так код показывает, как именно округляются копейки, налоги, скидки или комиссии.
decimal медленнее и занимает больше памяти, чем double. Но для денег обычно важнее предсказуемость, чем скорость.
➡️ C# Ready | #совет
double удобен для инженерных расчётов, координат, графики и статистики. Но для денег он часто создаёт ошибки.
Причина в том, что double хранит число в двоичном формате с плавающей точкой. Не все десятичные дроби можно представить точно.
Простой пример выглядит так:
double price = 0.1 + 0.2;
Console.WriteLine(price);
Результат может оказаться не ровно 0.3, а числом с маленьким хвостом. Для UI это иногда скрывается форматированием, но в сравнении и расчётах ошибка остаётся.
Особенно плохо сравнивать такие значения напрямую:
if (price == 0.3) {
Console.WriteLine("ok");
}В финансовых сценариях обычно используют decimal:
decimal price = 0.1m + 0.2m;
Console.WriteLine(price);
Суффикс m важен. Без него литерал сначала будет double, и смысл decimal частично потеряется.
Для округления лучше явно указать правило:
decimal total = Math.Round(price, 2, MidpointRounding.AwayFromZero);
Так код показывает, как именно округляются копейки, налоги, скидки или комиссии.
decimal медленнее и занимает больше памяти, чем double. Но для денег обычно важнее предсказуемость, чем скорость.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7❤4👍3
Планируем ближайшие задачи через PriorityQueue в C#!
Нужно хранить отложенные задания и каждый раз быстро выбирать то, чей срок наступит раньше. Обычный список придётся сортировать или просматривать целиком, а PriorityQueue хранит элемент с приоритетом и отдаёт минимальный приоритет первым.
Создадим очередь, где приоритетом будет время запуска:
Внутри может быть много заданий, но для демонстрации добавлены два. cleanup получит меньший приоритет, потому что его время ближе.
Перед обработкой проверим, наступил ли срок ближайшего задания:
TryPeek смотрит на самый ранний срок, не удаляя запись. Если он ещё впереди, очередь можно проверить позже, не перебирая остальные элементы.
Когда очередь проверяется регулярно, извлекай задания циклом, чтобы обработать все уже просроченные:
Для параллельной обработки можно передавать извлечённые элементы в ограниченную очередь работников.
➡️ C# Ready | #практика
Нужно хранить отложенные задания и каждый раз быстро выбирать то, чей срок наступит раньше. Обычный список придётся сортировать или просматривать целиком, а PriorityQueue хранит элемент с приоритетом и отдаёт минимальный приоритет первым.
Создадим очередь, где приоритетом будет время запуска:
var jobs = new PriorityQueue<string, DateTimeOffset>();
var now = DateTimeOffset.UtcNow;
jobs.Enqueue("backup", now.AddMinutes(20));
jobs.Enqueue("cleanup", now.AddMinutes(5));
Внутри может быть много заданий, но для демонстрации добавлены два. cleanup получит меньший приоритет, потому что его время ближе.
Перед обработкой проверим, наступил ли срок ближайшего задания:
if (jobs.TryPeek(out _, out var due) && due <= now)
jobs.TryDequeue(out var job, out _);
TryPeek смотрит на самый ранний срок, не удаляя запись. Если он ещё впереди, очередь можно проверить позже, не перебирая остальные элементы.
Когда очередь проверяется регулярно, извлекай задания циклом, чтобы обработать все уже просроченные:
while (jobs.TryPeek(out _, out var due) && due <= DateTimeOffset.UtcNow) {
var job = jobs.Dequeue();
Console.WriteLine(job);
}Для параллельной обработки можно передавать извлечённые элементы в ограниченную очередь работников.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7❤4👍4
Разбираем 7 приёмов для регулярных выражений в C#!
Regex удобен для поиска и проверки текста, но сложный шаблон может неожиданно долго обрабатывать почти совпавшую строку. В этой шпоре собраны настройки, которые делают регулярные выражения понятнее и помогают контролировать время выполнения.
➡️ C# Ready | #шпора
Regex удобен для поиска и проверки текста, но сложный шаблон может неожиданно долго обрабатывать почти совпавшую строку. В этой шпоре собраны настройки, которые делают регулярные выражения понятнее и помогают контролировать время выполнения.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9🔥6👍3
Почему MemoryStream.GetBuffer может вернуть лишние байты?
Представим, что API должен отправить короткий двоичный ответ. Мы записали его в MemoryStream, а затем получили внутренний массив:
Кажется, что data должен содержать ровно три байта. Но MemoryStream часто выделяет буфер с запасом. Длина массива отражает вместимость буфера, а не количество записанных данных.
Эти числа можно сравнить напрямую:
Length показывает полезную длину, а Capacity и data.Length могут быть больше. Если отправить весь data в сеть или записать его в файл, к нужным байтам добавится незаполненная часть буфера. В некоторых сценариях там могут оставаться данные после предыдущих операций с этим же массивом.
Когда нужен самостоятельный массив точной длины, используй ToArray:
ToArray копирует только записанные данные. Копия требует дополнительной памяти, зато её длина соответствует содержимому и она не изменится при последующей записи в stream.
Если копирование дорого, можно получить сегмент внутреннего массива вместе с правильными Offset и Count:
Не каждый MemoryStream разрешает доступ к своему буферу, GetBuffer может выбросить исключение, а TryGetBuffer в этом случае вернёт false.
➡️ C# Ready | #совет
Представим, что API должен отправить короткий двоичный ответ. Мы записали его в MemoryStream, а затем получили внутренний массив:
using var stream = new MemoryStream();
stream.Write(new byte[] { 1, 2, 3 });
byte[] data = stream.GetBuffer();
Кажется, что data должен содержать ровно три байта. Но MemoryStream часто выделяет буфер с запасом. Длина массива отражает вместимость буфера, а не количество записанных данных.
Эти числа можно сравнить напрямую:
Console.WriteLine(stream.Length);
Console.WriteLine(stream.Capacity);
Console.WriteLine(data.Length);
Length показывает полезную длину, а Capacity и data.Length могут быть больше. Если отправить весь data в сеть или записать его в файл, к нужным байтам добавится незаполненная часть буфера. В некоторых сценариях там могут оставаться данные после предыдущих операций с этим же массивом.
Когда нужен самостоятельный массив точной длины, используй ToArray:
byte[] payload = stream.ToArray();
await destination.WriteAsync(payload);
ToArray копирует только записанные данные. Копия требует дополнительной памяти, зато её длина соответствует содержимому и она не изменится при последующей записи в stream.
Если копирование дорого, можно получить сегмент внутреннего массива вместе с правильными Offset и Count:
if (stream.TryGetBuffer(out ArraySegment<byte> segment)) {
await destination.WriteAsync(segment.AsMemory());
}Не каждый MemoryStream разрешает доступ к своему буферу, GetBuffer может выбросить исключение, а TryGetBuffer в этом случае вернёт false.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍3🔥3
Карта основных возможностей C#!
На картинке язык разложен на крупные ветки: типы, операторы, инструкции, классы, коллекции и LINQ. Цвета помогают быстро увидеть, какие темы относятся к одной группе.
Например, ветка типов ведёт от числовых и ссылочных типов к пользовательским классам, а ветка LINQ объединяет фильтрацию, сортировку, группировку и преобразование данных.
Сохрани, чтобы не потерять!
➡️ C# Ready | #ресурс
На картинке язык разложен на крупные ветки: типы, операторы, инструкции, классы, коллекции и LINQ. Цвета помогают быстро увидеть, какие темы относятся к одной группе.
Например, ветка типов ведёт от числовых и ссылочных типов к пользовательским классам, а ветка LINQ объединяет фильтрацию, сортировку, группировку и преобразование данных.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10👍3🔥2
Знали, как читать обновляемые настройки из singleton-сервиса в ASP.NET Core?
Фоновый сервис обычно живёт всё время работы приложения. Если внедрить в него IOptionsSnapshot<T>, контейнер зависимостей получит scoped-сервис внутри singleton, и такая комбинация нарушает границы времени жизни.
Для долгоживущих сервисов предназначен IOptionsMonitor<T>. Он зарегистрирован как singleton, подходит для внедрения в сервисы любого времени жизни и позволяет получить актуальные настройки через CurrentValue.
Сначала связываем класс настроек с секцией конфигурации:
В singleton сохраняем сам монитор, а не снимок настроек, прочитанный при создании сервиса:
Перед каждой операцией получаем актуальное значение и используем его как единый набор параметров для этой операции:
Если нужно реагировать на изменение сразу, монитор поддерживает уведомления OnChange. Подписку создают один раз на время жизни сервиса и освобождают при остановке, а не создают заново при каждом запросе.
➡️ C# Ready | #совет
Фоновый сервис обычно живёт всё время работы приложения. Если внедрить в него IOptionsSnapshot<T>, контейнер зависимостей получит scoped-сервис внутри singleton, и такая комбинация нарушает границы времени жизни.
Для долгоживущих сервисов предназначен IOptionsMonitor<T>. Он зарегистрирован как singleton, подходит для внедрения в сервисы любого времени жизни и позволяет получить актуальные настройки через CurrentValue.
Сначала связываем класс настроек с секцией конфигурации:
builder.Services.Configure<WorkerOptions>(
builder.Configuration.GetSection("Worker"));
В singleton сохраняем сам монитор, а не снимок настроек, прочитанный при создании сервиса:
private readonly IOptionsMonitor<WorkerOptions> _options;
Перед каждой операцией получаем актуальное значение и используем его как единый набор параметров для этой операции:
WorkerOptions current = _options.CurrentValue;
await RunJobAsync(current.BatchSize, current.Endpoint);
Если нужно реагировать на изменение сразу, монитор поддерживает уведомления OnChange. Подписку создают один раз на время жизни сервиса и освобождают при остановке, а не создают заново при каждом запросе.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4🔥4👍3
Разбираем 7 инструментов System.Text.Json!
С JSON можно работать как с типизированными объектами, так и без заранее описанной модели. Для потоков есть асинхронные методы, а для выборочного чтения и изменения структуры подходят разные представления документа.
В этой шпоре собраны Serialize, Deserialize, SerializeAsync, DeserializeAsync, JsonDocument, TryGetProperty и JsonNode.
➡️ C# Ready | #шпора
С JSON можно работать как с типизированными объектами, так и без заранее описанной модели. Для потоков есть асинхронные методы, а для выборочного чтения и изменения структуры подходят разные представления документа.
В этой шпоре собраны Serialize, Deserialize, SerializeAsync, DeserializeAsync, JsonDocument, TryGetProperty и JsonNode.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12❤5👍4