Собираем защиту 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
Claude снова банит пользователей из РФ
Многим начали приходить письма счастья от Anthropic: аккаунт заблокирован, доступ закрыт. Банят за вход из неподдерживаемого региона и оплату через серые схемы.
А в CatCode Claude работает без ограничений.
CatСode - это десктопный агент с топовыми нейросетями на борту всего за 1550 рублей в месяц:
😒 Claude Fable
🤖 GPT-6 Astra
😐 Grok 4.6
и многие другие.
Твой аккаунт точно не забанят: подписка оформлена у международной компании. Работает без VPN, оплата российской картой или СБП, никаких перекупов. Первые задачи бесплатно, просто зарегистрируйся и пробуй.
Оформи пакет нейросетей разом и не бойся банов: catcode.dev
Многим начали приходить письма счастья от Anthropic: аккаунт заблокирован, доступ закрыт. Банят за вход из неподдерживаемого региона и оплату через серые схемы.
А в CatCode Claude работает без ограничений.
CatСode - это десктопный агент с топовыми нейросетями на борту всего за 1550 рублей в месяц:
и многие другие.
Твой аккаунт точно не забанят: подписка оформлена у международной компании. Работает без VPN, оплата российской картой или СБП, никаких перекупов. Первые задачи бесплатно, просто зарегистрируйся и пробуй.
Оформи пакет нейросетей разом и не бойся банов: catcode.dev
Please open Telegram to view this post
VIEW IN TELEGRAM
👎2😁2❤1
Убираем лишние запросы при вводе текста в C#!
При поиске по мере набора символов приложение может отправить запрос на каждый ввод. Добавим небольшую задержку и отмену предыдущей операции, чтобы искать только по последнему значению.
Храним источник отмены текущего поиска и блокировку для его безопасной замены:
Когда приходит новый текст, заменяем источник под блокировкой и отменяем предыдущий поиск:
Блокировка не даёт старой задаче одновременно освободить источник, который новый вызов пытается отменить. Каждый вызов освобождает свой объект сам после завершения.
Ждём короткую паузу, затем выполняем поиск с тем же токеном отмены:
Если за 300 миллисекунд пользователь введёт следующий символ, ожидание или запрос завершится отменой. Проверка перед показом результата не даст старому поиску перезаписать более свежие данные.
В блоке finally очищаем поле только тогда, когда там всё ещё наш источник, и освобождаем его:
Такой debounce сокращает сетевую нагрузку.
➡️ C# Ready | #практика
При поиске по мере набора символов приложение может отправить запрос на каждый ввод. Добавим небольшую задержку и отмену предыдущей операции, чтобы искать только по последнему значению.
Храним источник отмены текущего поиска и блокировку для его безопасной замены:
private readonly object _searchLock = new();
private CancellationTokenSource? _searchCts;
Когда приходит новый текст, заменяем источник под блокировкой и отменяем предыдущий поиск:
var current = new CancellationTokenSource();
lock (_searchLock)
{
var previous = _searchCts;
_searchCts = current;
previous?.Cancel();
}
Блокировка не даёт старой задаче одновременно освободить источник, который новый вызов пытается отменить. Каждый вызов освобождает свой объект сам после завершения.
Ждём короткую паузу, затем выполняем поиск с тем же токеном отмены:
try
{
await Task.Delay(300, current.Token);
var results = await _searchService.FindAsync(query, current.Token);
current.Token.ThrowIfCancellationRequested();
_view.Show(results);
}
catch (OperationCanceledException) when (current.IsCancellationRequested)
{
}
Если за 300 миллисекунд пользователь введёт следующий символ, ожидание или запрос завершится отменой. Проверка перед показом результата не даст старому поиску перезаписать более свежие данные.
В блоке finally очищаем поле только тогда, когда там всё ещё наш источник, и освобождаем его:
finally
{
lock (_searchLock)
{
if (ReferenceEquals(_searchCts, current))
{
_searchCts = null;
}
}
current.Dispose();
}
Такой debounce сокращает сетевую нагрузку.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5👍4❤3
🔥 Middle C# на собеседовании у Senior из европейского highload | 20 октября, 19:00 МСК
Можно знать технологию и всё равно завалить техническое интервью. Часто дело не в знаниях, а в том, как ты отвечаешь и какие детали упускаешь ☝🏻
Александр Моргунов, Senior C# разработчик с 7+ годами опыта в европейском highload, проведёт мок-собеседование в прямом эфире!
Отвечает разработчик-доброволец, вопросов он заранее не знает. После каждого ответа Александр разберёт, что было хорошо, где кандидат потерял баллы и как стоило ответить.
Проверяй себя вместе с кандидатом и узнай, к чему подготовиться до своего собеса.
🚀 Это бесплатно
Эфир проходит в рамках менторской программы ШОРТКАТ для C# разработчиков
Ссылка на эфир в боте 👉 @shortcut_csharp_bot
Реклама.
О рекламодателе.
Можно знать технологию и всё равно завалить техническое интервью. Часто дело не в знаниях, а в том, как ты отвечаешь и какие детали упускаешь ☝🏻
Александр Моргунов, Senior C# разработчик с 7+ годами опыта в европейском highload, проведёт мок-собеседование в прямом эфире!
Отвечает разработчик-доброволец, вопросов он заранее не знает. После каждого ответа Александр разберёт, что было хорошо, где кандидат потерял баллы и как стоило ответить.
Проверяй себя вместе с кандидатом и узнай, к чему подготовиться до своего собеса.
🚀 Это бесплатно
Эфир проходит в рамках менторской программы ШОРТКАТ для C# разработчиков
Ссылка на эфир в боте 👉 @shortcut_csharp_bot
Реклама.
О рекламодателе.
Добавляем Request-Id middleware в ASP.NET Core!
Нужно связывать логи, ошибки и ответы API одним идентификатором запроса. Реализуем middleware, который берёт Request-Id из заголовка или создаёт новый.
В этой задаче:
Такой подход помогает быстрее искать конкретный запрос в логах, трассировке, gateway и клиентских жалобах.
➡️ C# Ready | #задача
Нужно связывать логи, ошибки и ответы API одним идентификатором запроса. Реализуем middleware, который берёт Request-Id из заголовка или создаёт новый.
В этой задаче:
• читаем входящий заголовок
• создаём fallback через Guid
• прокидываем значение в response
• подключаем middleware в pipeline
Такой подход помогает быстрее искать конкретный запрос в логах, трассировке, gateway и клиентских жалобах.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤4🔥3