Настраиваем JSON-сериализацию через System.Text.Json!
Во многих API нужно отдавать JSON в понятном формате. Часто это camelCase-поля, аккуратные отступы в debug-режиме и отсутствие лишних null-значений.
Для начала подключим пространство имён:
Создадим простую модель:
Теперь заведём настройки сериализации:
Включим camelCase для имён свойств:
Если JSON нужно читать глазами, включим форматирование:
Теперь сериализуем объект:
Обратно объект можно получить так:
Важно не создавать настройки в каждом горячем вызове. Лучше вынести их в статическое поле или общий сервис, чтобы код был единообразным и без лишних аллокаций.
➡️ C# Ready | #практика
Во многих API нужно отдавать JSON в понятном формате. Часто это camelCase-поля, аккуратные отступы в debug-режиме и отсутствие лишних null-значений.
Для начала подключим пространство имён:
using System.Text.Json;
Создадим простую модель:
record User(int Id, string Name, string? Email);
Теперь заведём настройки сериализации:
var options = new JsonSerializerOptions();
Включим camelCase для имён свойств:
options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
Если JSON нужно читать глазами, включим форматирование:
options.WriteIndented = true;
Теперь сериализуем объект:
var json = JsonSerializer.Serialize(user, options);
Обратно объект можно получить так:
var copy = JsonSerializer.Deserialize<User>(json, options);
Важно не создавать настройки в каждом горячем вызове. Лучше вынести их в статическое поле или общий сервис, чтобы код был единообразным и без лишних аллокаций.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍4🔥2
Собираем автообновление конфига в C#!
Нужно следить за config.json и автоматически перечитывать настройки после изменения файла. Это удобно для сервисов, внутренних тулов и приложений, где часть параметров можно менять без перезапуска.
В этой задаче:
Debounce нужен потому, что одно сохранение файла часто порождает несколько событий подряд. Без небольшой задержки обработчик может попытаться прочитать файл прямо во время записи.
➡️ C# Ready | #задача
Нужно следить за config.json и автоматически перечитывать настройки после изменения файла. Это удобно для сервисов, внутренних тулов и приложений, где часть параметров можно менять без перезапуска.
В этой задаче:
• создаём FileSystemWatcher
• фильтруем события по имени файла
• добавляем короткий debounce
Debounce нужен потому, что одно сохранение файла часто порождает несколько событий подряд. Без небольшой задержки обработчик может попытаться прочитать файл прямо во время записи.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤7🔥5
Знали, как ограничить количество параллельных async-операций через SemaphoreSlim?
Иногда нужно обработать много элементов, отправить HTTP-запросы, проверить файлы, обновить записи или вызвать внешний API. Кажется логичным запустить всё через Task.WhenAll.
Пример выглядит компактно:
Но если items тысяча, код может создать тысячу одновременных операций. Для внешнего сервиса это может закончиться rate limit, таймаутами, большим расходом памяти и странными ошибками под нагрузкой.
Для ограничения параллельности удобно использовать SemaphoreSlim:
Число 5 означает, что одновременно внутрь защищённого участка попадут только пять операций. Остальные будут ждать свою очередь асинхронно, без блокировки потока.
В обработке это выглядит так:
finally здесь важен. Если SendAsync завершится с ошибкой, Release всё равно выполнится, и очередь не зависнет навсегда из-за занятого слота.
Полный вариант часто заворачивают в Select:
Такой подход хорошо подходит для интеграций, batch-обработки, фоновых задач и любых мест, где нужно контролировать давление на внешнюю систему.
➡️ C# Ready | #совет
Иногда нужно обработать много элементов, отправить HTTP-запросы, проверить файлы, обновить записи или вызвать внешний API. Кажется логичным запустить всё через Task.WhenAll.
Пример выглядит компактно:
await Task.WhenAll(items.Select(SendAsync));
Но если items тысяча, код может создать тысячу одновременных операций. Для внешнего сервиса это может закончиться rate limit, таймаутами, большим расходом памяти и странными ошибками под нагрузкой.
Для ограничения параллельности удобно использовать SemaphoreSlim:
using var gate = new SemaphoreSlim(5);
Число 5 означает, что одновременно внутрь защищённого участка попадут только пять операций. Остальные будут ждать свою очередь асинхронно, без блокировки потока.
В обработке это выглядит так:
await gate.WaitAsync();
try
{
await SendAsync(item);
}
finally
{
gate.Release();
}
finally здесь важен. Если SendAsync завершится с ошибкой, Release всё равно выполнится, и очередь не зависнет навсегда из-за занятого слота.
Полный вариант часто заворачивают в Select:
var tasks = items.Select(async item =>
{
await gate.WaitAsync();
try { await SendAsync(item); }
finally { gate.Release(); }
});
await Task.WhenAll(tasks);
Такой подход хорошо подходит для интеграций, batch-обработки, фоновых задач и любых мест, где нужно контролировать давление на внешнюю систему.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤5🔥3
Шпаргалка по регулярным выражениям в .NET!
Например, \d помогает искать цифры, \w - символы слова, ^ и $ привязывают шаблон к началу и концу строки, а группы позволяют вытаскивать нужные части совпадения.
На картинке собраны основные элементы Regex в .NET. Есть классы символов, квантификаторы, anchors, группы, inline options, backreferences, замены и методы из System.Text.RegularExpressions.Regex.
Сохрани, чтобы не потерять!
➡️ C# Ready | #ресурс
Например, \d помогает искать цифры, \w - символы слова, ^ и $ привязывают шаблон к началу и концу строки, а группы позволяют вытаскивать нужные части совпадения.
На картинке собраны основные элементы Regex в .NET. Есть классы символов, квантификаторы, anchors, группы, inline options, backreferences, замены и методы из System.Text.RegularExpressions.Regex.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤3🔥2
Полезная статья про Avalonia-приложение для Raspberry Pi!
Автор разбирает создание кроссплатформенного приложения на Avalonia и показывает, как связать UI, сборку и доставку проекта через GitHub Actions.
В статье автор показывает:
• как устроить проект на Avalonia под разные платформы
• как подготовить сборку и публикацию через GitHub Actions
• какие нюансы появляются при запуске приложения на Raspberry Pi
➡️ C# Ready | #статья
Автор разбирает создание кроссплатформенного приложения на Avalonia и показывает, как связать UI, сборку и доставку проекта через GitHub Actions.
В статье автор показывает:
• как устроить проект на Avalonia под разные платформы
• как подготовить сборку и публикацию через GitHub Actions
• какие нюансы появляются при запуске приложения на Raspberry Pi
Продолжай читать на Habr
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍6🔥4
Собираем простой in-memory event bus в C#!
Иногда внутри приложения нужно связать части системы без прямых вызовов. Например, заказ создан, а несколько обработчиков должны обновить статистику, отправить уведомление и записать аудит.
Для такой задачи можно собрать маленький event bus на делегатах.
Сначала опишем событие:
Теперь заведём хранилище обработчиков. Ключом будет тип события, а значением список функций:
Метод подписки принимает обработчик конкретного типа:
Здесь внутри мы приводим object обратно к TEvent. Снаружи код остаётся типобезопасным, потому что Subscribe принимает Func<TEvent, Task>.
Теперь добавим публикацию события:
Использование получается коротким:
Теперь можно отправить событие:
Такой event bus не заменяет полноценную очередь сообщений, но хорошо подходит для модульной логики внутри одного процесса.
➡️ C# Ready | #практика
Иногда внутри приложения нужно связать части системы без прямых вызовов. Например, заказ создан, а несколько обработчиков должны обновить статистику, отправить уведомление и записать аудит.
Для такой задачи можно собрать маленький event bus на делегатах.
Сначала опишем событие:
public sealed record OrderCreated(
int OrderId,
decimal Amount
);
Теперь заведём хранилище обработчиков. Ключом будет тип события, а значением список функций:
private readonly Dictionary<Type, List<Func<object, Task>>> _handlers = new();
Метод подписки принимает обработчик конкретного типа:
public void Subscribe<TEvent>(Func<TEvent, Task> handler)
{
var type = typeof(TEvent);
if (!_handlers.ContainsKey(type))
{
_handlers[type] = new();
}
_handlers[type].Add(e => handler((TEvent)e));
}
Здесь внутри мы приводим object обратно к TEvent. Снаружи код остаётся типобезопасным, потому что Subscribe принимает Func<TEvent, Task>.
Теперь добавим публикацию события:
public async Task Publish<TEvent>(TEvent evt)
{
var type = typeof(TEvent);
if (!_handlers.TryGetValue(type, out var handlers))
{
return;
}
foreach (var handler in handlers)
{
await handler(evt!);
}
}
Использование получается коротким:
var bus = new EventBus();
bus.Subscribe<OrderCreated>(e =>
{
Console.WriteLine($"Order {e.OrderId}");
return Task.CompletedTask;
});
Теперь можно отправить событие:
await bus.Publish(new OrderCreated(42, 1990m));
Такой event bus не заменяет полноценную очередь сообщений, но хорошо подходит для модульной логики внутри одного процесса.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍5🔥3
Шпаргалка по throw и throw ex в C#!
Например, обычный throw внутри catch пробрасывает исходное исключение и сохраняет stack trace. А throw ex создаёт повторный выброс так, что место реальной ошибки становится сложнее найти.
На картинке показана разница между двумя вариантами. Есть сохранение stack trace, потеря исходного контекста, влияние на отладку и короткая таблица с правильным вариантом для catch-блоков.
Сохрани, чтобы не потерять!
➡️ C# Ready | #ресурс
Например, обычный throw внутри catch пробрасывает исходное исключение и сохраняет stack trace. А throw ex создаёт повторный выброс так, что место реальной ошибки становится сложнее найти.
На картинке показана разница между двумя вариантами. Есть сохранение stack trace, потеря исходного контекста, влияние на отладку и короткая таблица с правильным вариантом для catch-блоков.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍4🔥2
Делаем потокобезопасный счётчик через Interlocked!
Иногда нескольким потокам нужно увеличивать одно число. Например, считать обработанные события, успешные запросы или количество ошибок в фоне.
Обычный инкремент выглядит невинно:
Но это не одна атомарная операция. Внутри значение читается, увеличивается и записывается обратно. Если два потока сделают это одновременно, одно обновление может потеряться.
Для таких счётчиков в .NET есть Interlocked:
Теперь увеличение выполняется атомарно. Несколько потоков могут вызывать этот код одновременно, а итоговое значение не потеряет часть инкрементов.
Если нужно прибавить произвольное число, есть Add:
А для чтения текущего значения можно использовать CompareExchange без изменения:
Для long есть отдельные перегрузки, и это часто удобно для метрик, которые могут быстро расти:
Interlocked подходит для простых числовых операций. Если нужно менять сразу несколько полей как одно состояние, лучше использовать lock или отдельную потокобезопасную структуру.
➡️ C# Ready | #практика
Иногда нескольким потокам нужно увеличивать одно число. Например, считать обработанные события, успешные запросы или количество ошибок в фоне.
Обычный инкремент выглядит невинно:
_count++;
Но это не одна атомарная операция. Внутри значение читается, увеличивается и записывается обратно. Если два потока сделают это одновременно, одно обновление может потеряться.
Для таких счётчиков в .NET есть Interlocked:
private int _count;
Interlocked.Increment(ref _count);
Теперь увеличение выполняется атомарно. Несколько потоков могут вызывать этот код одновременно, а итоговое значение не потеряет часть инкрементов.
Если нужно прибавить произвольное число, есть Add:
Interlocked.Add(ref _count, batchSize);
А для чтения текущего значения можно использовать CompareExchange без изменения:
int current = Interlocked.CompareExchange(ref _count, 0, 0);
Для long есть отдельные перегрузки, и это часто удобно для метрик, которые могут быстро расти:
private long _bytes;
Interlocked.Add(ref _bytes, size);
Interlocked подходит для простых числовых операций. Если нужно менять сразу несколько полей как одно состояние, лучше использовать lock или отдельную потокобезопасную структуру.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤3🔥2
Напоминалка по async/await в C#!
Например, await приостанавливает выполнение метода до завершения Task, а Task.WhenAll() помогает дождаться нескольких независимых операций.
На картинке основные идеи async/await: зачем нужна асинхронность, что делают async и await, какие бывают return types, как запускать задачи параллельно и каких ошибок стоит избегать.
Сохрани, чтобы не потерять!
➡️ C# Ready | #ресурс
Например, await приостанавливает выполнение метода до завершения Task, а Task.WhenAll() помогает дождаться нескольких независимых операций.
На картинке основные идеи async/await: зачем нужна асинхронность, что делают async и await, какие бывают return types, как запускать задачи параллельно и каких ошибок стоит избегать.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍2🔥2
Парсим простой .env-файл на C#!
В небольших утилитах часто нужно прочитать настройки из файла вида KEY=value: токены, режим запуска, адрес API или путь к директории.
Пример строки:
Разделим её по первому знаку
Сохраним результат в словарь:
Теперь обработаем несколько строк:
Пример
Использование:
Ожидаемый вывод:
Такой парсер не заменяет полноценную библиотеку для сложных
➡️ C# Ready | #практика
В небольших утилитах часто нужно прочитать настройки из файла вида KEY=value: токены, режим запуска, адрес API или путь к директории.
Пример строки:
var line = "APP_ENV=prod";
Разделим её по первому знаку
=:var index = line.IndexOf('=');
var key = line[..index];
var value = line[(index + 1)..];Сохраним результат в словарь:
var config = new Dictionary<string, string>();
config[key] = value;
Теперь обработаем несколько строк:
foreach (var raw in File.ReadLines(".env"))
{
var line = raw.Trim();
if (line.Length == 0 || line.StartsWith("#"))
continue;
var index = line.IndexOf('=');
if (index <= 0)
continue;
config[line[..index]] = line[(index + 1)..];
}Пример
.env:APP_ENV=prod
PORT=8080
DEBUG=false
Использование:
Console.WriteLine(config["APP_ENV"]);
Console.WriteLine(config["PORT"]);
Ожидаемый вывод:
prod
8080
Такой парсер не заменяет полноценную библиотеку для сложных
.env, но отлично подходит для маленьких внутренних утилит.Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍3🔥1
Разбираем immutable collections в C#!
Immutable collections помогают передавать данные без риска, что другой участок кода внезапно изменит коллекцию. Это удобно для конфигов, состояния UI, кешей и событий.
В этой шпоре собраны ImmutableArray, ImmutableList, Add, Remove, SetItem, ToImmutableArray и Builder.
➡️ C# Ready | #шпора
Immutable collections помогают передавать данные без риска, что другой участок кода внезапно изменит коллекцию. Это удобно для конфигов, состояния UI, кешей и событий.
В этой шпоре собраны ImmutableArray, ImmutableList, Add, Remove, SetItem, ToImmutableArray и Builder.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9❤5👍3