Это потокобезопасная очередь для передачи сообщений между задачами без ручных блокировок и сложной синхронизации.
Что важно знать:
• Работает без ручных блокировок и сложных синхронных вызовов.
• Позволяет автоматически замедлять отправку при перегрузке памяти.
• Обеспечивает плавную передачу данных между параллельными задачами.
Так вы получаете готовый «из коробки» механизм асинхронной очереди с естественным управлением нагрузкой без лишнего кода.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11❤5👍4🤝1
Понятный разбор того, как на самом деле работает async/await в C#!
В этой статье:
• Чем асинхронность отличается от запуска работы в отдельном потоке
• Когда await освобождает поток, а когда выполнение продолжается синхронно
• Как компилятор превращает асинхронный метод в машину состояний
➡️ C# Ready | #статья
В этой статье:
• Чем асинхронность отличается от запуска работы в отдельном потоке
• Когда await освобождает поток, а когда выполнение продолжается синхронно
• Как компилятор превращает асинхронный метод в машину состояний
Продолжай читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤4🔥3
Почему yield return в C# выполняется не сразу?
Метод с yield выглядит как обычный метод:
Но при вызове код внутри ещё не выполняется:
В консоли сначала будет:
А строка start появится только тогда, когда коллекцию начнут реально перебирать:
И вот тогда выполнение пойдёт пошагово:
Это важно помнить, если внутри iterator-метода есть чтение файла, запрос к базе, логирование или любая другая побочная логика.
Например, такой код может выполнить запрос не там, где кажется:
LoadUsers() может начать работу только на foreach, а не на первой строке.
Если нужно зафиксировать результат сразу, материализуй последовательность:
yield return создаёт ленивую последовательность. Код внутри запускается при перечислении, а не при вызове метода.
➡️ C# Ready | #совет
Метод с yield выглядит как обычный метод:
IEnumerable<int> GetNumbers()
{
Console.WriteLine("start");
yield return 1;
yield return 2;
}
Но при вызове код внутри ещё не выполняется:
var numbers = GetNumbers();
Console.WriteLine("created");
В консоли сначала будет:
created
А строка start появится только тогда, когда коллекцию начнут реально перебирать:
foreach (var number in numbers)
{
Console.WriteLine(number);
}
И вот тогда выполнение пойдёт пошагово:
start
1
2
Это важно помнить, если внутри iterator-метода есть чтение файла, запрос к базе, логирование или любая другая побочная логика.
Например, такой код может выполнить запрос не там, где кажется:
var users = LoadUsers();
DoSomething();
foreach (var user in users)
{
Console.WriteLine(user.Name);
}
LoadUsers() может начать работу только на foreach, а не на первой строке.
Если нужно зафиксировать результат сразу, материализуй последовательность:
var users = LoadUsers().ToList();
yield return создаёт ленивую последовательность. Код внутри запускается при перечислении, а не при вызове метода.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍4🔥2
Простая и популярная статья о создании Telegram-бота на C#!
В этой статье:
• Как создать бота через BotFather и подключить его к проекту
• Как принимать сообщения пользователя и отправлять ответы
• Как собрать базу, от которой уже можно развивать своего бота дальше
➡️ C# Ready | #статья
В этой статье:
• Как создать бота через BotFather и подключить его к проекту
• Как принимать сообщения пользователя и отправлять ответы
• Как собрать базу, от которой уже можно развивать своего бота дальше
Продолжай читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍5🔥3
Шпаргалка по недооценённым LINQ-методам в C#!
Например, SequenceEqual() помогает сравнить две последовательности по элементам, ToLookup() создаёт удобную группировку для быстрого доступа по ключу, а GroupJoin() полезен, когда нужно связать две коллекции и получить вложенный результат.
На картинке 5 LINQ-методов с короткими примерами кода: SequenceEqual, Aggregate, GroupJoin, ToLookup и Intersect.
Сохрани, чтобы не потерять!
➡️ C# Ready | #ресурс
Например, SequenceEqual() помогает сравнить две последовательности по элементам, ToLookup() создаёт удобную группировку для быстрого доступа по ключу, а GroupJoin() полезен, когда нужно связать две коллекции и получить вложенный результат.
На картинке 5 LINQ-методов с короткими примерами кода: SequenceEqual, Aggregate, GroupJoin, ToLookup и Intersect.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10👍7🔥6
Собираем простую очередь фоновых задач!
Иногда в приложении нужно отложить работу: отправить email, записать аудит, обработать файл или выполнить тяжёлую операцию после ответа пользователю.
Для этого можно собрать небольшую очередь на Channel<T>.
Сначала заведём интерфейс:
Реализация будет хранить задачи внутри Channel:
Добавление задачи:
Чтение задач:
Теперь можно положить работу в очередь:
А отдельный воркер будет выполнять задачи:
В ASP.NET Core такую логику обычно выносят в BackgroundService:
Такой подход помогает не держать HTTP-запрос открытым, если работу можно выполнить после ответа пользователю.
➡️ C# Ready | #практика
Иногда в приложении нужно отложить работу: отправить email, записать аудит, обработать файл или выполнить тяжёлую операцию после ответа пользователю.
Для этого можно собрать небольшую очередь на Channel<T>.
Сначала заведём интерфейс:
public interface ITaskQueue
{
ValueTask EnqueueAsync(Func<CancellationToken, Task> job);
IAsyncEnumerable<Func<CancellationToken, Task>> ReadAllAsync(
CancellationToken token
);
}
Реализация будет хранить задачи внутри Channel:
using System.Threading.Channels;
public sealed class TaskQueue : ITaskQueue
{
private readonly Channel<Func<CancellationToken, Task>> _channel =
Channel.CreateUnbounded<Func<CancellationToken, Task>>();
Добавление задачи:
public ValueTask EnqueueAsync(Func<CancellationToken, Task> job)
{
return _channel.Writer.WriteAsync(job);
}
Чтение задач:
public IAsyncEnumerable<Func<CancellationToken, Task>> ReadAllAsync(
CancellationToken token
)
{
return _channel.Reader.ReadAllAsync(token);
}
}
Теперь можно положить работу в очередь:
await queue.EnqueueAsync(async token =>
{
await emailSender.SendAsync(user.Email, token);
});
А отдельный воркер будет выполнять задачи:
await foreach (var job in queue.ReadAllAsync(token))
{
await job(token);
}
В ASP.NET Core такую логику обычно выносят в BackgroundService:
protected override async Task ExecuteAsync(CancellationToken token)
{
await foreach (var job in queue.ReadAllAsync(token))
{
await job(token);
}
}
Такой подход помогает не держать HTTP-запрос открытым, если работу можно выполнить после ответа пользователю.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤4🔥3
Шпаргалка по возможностям C# 8!
Например, nullable reference types помогают явно отделять значения, которые могут быть null, а indices/ranges позволяют удобнее брать элементы и срезы через ^ и ...
На картинке default interface methods, nullable reference types, pattern matching, async streams и другие возможности с короткими примерами кода.
Сохрани, чтобы не потерять!
➡️ C# Ready | #ресурс
Например, nullable reference types помогают явно отделять значения, которые могут быть null, а indices/ranges позволяют удобнее брать элементы и срезы через ^ и ...
На картинке default interface methods, nullable reference types, pattern matching, async streams и другие возможности с короткими примерами кода.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍2🔥1
Собираем retry-обёртку для нестабильных операций!
В реальных приложениях операции иногда падают временно. В таких случаях полезно не сразу падать, а повторить попытку несколько раз.
Сделаем универсальный метод RetryAsync:
Внутри сохраним последнюю ошибку:
Теперь запустим несколько попыток:
Между попытками сделаем паузу:
Если все попытки закончились ошибкой, пробросим последнюю:
Теперь можно обернуть нестабильный запрос:
Для логов можно добавить номер попытки:
А для production-кода лучше передавать CancellationToken:
И использовать его в задержке:
Такой retry подходит для временных сетевых ошибок, но не должен скрывать настоящие баги. Например, ошибку валидации или неправильный SQL-запрос повторять бессмысленно.
➡️ C# Ready | #практика
В реальных приложениях операции иногда падают временно. В таких случаях полезно не сразу падать, а повторить попытку несколько раз.
Сделаем универсальный метод RetryAsync:
public static async Task<T> RetryAsync<T>(
Func<Task<T>> action,
int attempts,
TimeSpan delay
)
Внутри сохраним последнюю ошибку:
{
Exception? lastError = null;Теперь запустим несколько попыток:
for (int i = 1; i <= attempts; i++)
{
try {
return await action();
}
catch (Exception ex) {
lastError = ex;
}
Между попытками сделаем паузу:
if (i < attempts) {
await Task.Delay(delay);
}
}Если все попытки закончились ошибкой, пробросим последнюю:
throw lastError!;
}
Теперь можно обернуть нестабильный запрос:
var user = await RetryAsync(
() => api.GetUserAsync(id),
attempts: 3,
delay: TimeSpan.FromMilliseconds(300)
);
Для логов можно добавить номер попытки:
catch (Exception ex) {
lastError = ex;
Console.WriteLine($"Попытка {i} не удалась");
}А для production-кода лучше передавать CancellationToken:
public static async Task<T> RetryAsync<T>(
Func<CancellationToken, Task<T>> action,
int attempts,
TimeSpan delay,
CancellationToken token
)
И использовать его в задержке:
await Task.Delay(delay, token);
Такой retry подходит для временных сетевых ошибок, но не должен скрывать настоящие баги. Например, ошибку валидации или неправильный SQL-запрос повторять бессмысленно.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤4🔥3
Полезная статья о том, как сделать интеграционные тесты ASP.NET Core API чище и удобнее!
В этой статье:
• Как тестировать API вместе с базой, временем и внешними зависимостями
• Почему классический WebApplicationFactory быстро обрастает повторяющимся кодом
• Как фикстуры помогают собирать тестовое окружение декларативнее и проще
➡️ C# Ready | #статья
В этой статье:
• Как тестировать API вместе с базой, временем и внешними зависимостями
• Почему классический WebApplicationFactory быстро обрастает повторяющимся кодом
• Как фикстуры помогают собирать тестовое окружение декларативнее и проще
Продолжай читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍3🔥2
Как не сломать Dictionary в C# из-за регистра строк?
Очень частая ситуация: ключи приходят из API, заголовков, логинов, команд или конфигов. В одном месте строка может быть
Обычный Dictionary сравнивает строки с учётом регистра:
Результат будет:
Если регистр не должен иметь значения, это нужно указать при создании словаря:
Теперь ключи сравниваются без учёта регистра:
Вывод:
Это полезно для HTTP-заголовков:
Для команд:
И для конфигов, где пользователь может написать ключ в разном регистре:
Лучше задавать comparer сразу при создании словаря. Приводить ключи вручную через
➡️ C# Ready | #совет
Очень частая ситуация: ключи приходят из API, заголовков, логинов, команд или конфигов. В одном месте строка может быть
Admin, в другом admin.Обычный Dictionary сравнивает строки с учётом регистра:
var roles = new Dictionary<string, int>();
roles["Admin"] = 10;
Console.WriteLine(roles.ContainsKey("admin"));
Результат будет:
False
Если регистр не должен иметь значения, это нужно указать при создании словаря:
var roles = new Dictionary<string, int>(
StringComparer.OrdinalIgnoreCase
);
Теперь ключи сравниваются без учёта регистра:
roles["Admin"] = 10;
Console.WriteLine(roles["admin"]);
Вывод:
10
Это полезно для HTTP-заголовков:
var headers = new Dictionary<string, string>(
StringComparer.OrdinalIgnoreCase
);
Для команд:
var commands = new Dictionary<string, Action>(
StringComparer.OrdinalIgnoreCase
);
И для конфигов, где пользователь может написать ключ в разном регистре:
settings["LogLevel"] = "Debug";
Console.WriteLine(settings["loglevel"]);
Лучше задавать comparer сразу при создании словаря. Приводить ключи вручную через
ToLower() хуже: можно забыть сделать это в одном месте и получить странный баг.Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤6🔥6
This media is not supported in your browser
VIEW IN TELEGRAM
Этот репозиторий — большая коллекция материалов для работы с .NET. Здесь собраны полезные библиотеки, инструменты, фреймворки и проекты, которые помогают быстрее находить решения и лучше ориентироваться.
Оставляю ссылочку: GitHub📱
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9👍7🔥1
Интересная статья о том, как сделать нормальный онбординг в desktop-приложении на C# и Avalonia!
В этой статье:
• Как строятся интерактивные туры по интерфейсу с подсветкой элементов
• Почему desktop-приложениям не хватает готовых onboarding-инструментов
• Как автор решал позиционирование, скрытые элементы, темы и сохранение прогресса
➡️ C# Ready | #статья
В этой статье:
• Как строятся интерактивные туры по интерфейсу с подсветкой элементов
• Почему desktop-приложениям не хватает готовых onboarding-инструментов
• Как автор решал позиционирование, скрытые элементы, темы и сохранение прогресса
Продолжай читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍4🔥2
Почему в C# стоит аккуратно использовать DateTime.Now?
Когда нужно сохранить время события, часто пишут так:
Для вывода пользователю это может быть нормально. Но для логики, хранения и сравнения времени DateTime.Now часто создаёт проблемы.
Он зависит от локального часового пояса машины:
На сервере, в контейнере, на CI и у разработчика время может отличаться.
Для технических событий обычно лучше брать UTC:
Такое значение проще хранить в базе, сравнивать и передавать между сервисами:
Но есть ещё один нюанс, DateTime сам по себе не хранит смещение часового пояса так явно, как DateTimeOffset.
Для событий, где важно зафиксировать момент времени, часто удобнее DateTimeOffset:
Например, для аудита:
А локальное время лучше получать только на границе приложения, перед показом пользователю, в отчёте или интерфейсе:
Если перемешать локальное время и UTC, баги появляются тихо, токены живут дольше, фильтры по датам съезжают, события в логах идут не в том порядке.
➡️ C# Ready | #совет
Когда нужно сохранить время события, часто пишут так:
var createdAt = DateTime.Now;
Для вывода пользователю это может быть нормально. Но для логики, хранения и сравнения времени DateTime.Now часто создаёт проблемы.
Он зависит от локального часового пояса машины:
Console.WriteLine(DateTime.Now);
На сервере, в контейнере, на CI и у разработчика время может отличаться.
Для технических событий обычно лучше брать UTC:
var createdAt = DateTime.UtcNow;
Такое значение проще хранить в базе, сравнивать и передавать между сервисами:
if (DateTime.UtcNow > token.ExpiresAtUtc) {
return false;
}Но есть ещё один нюанс, DateTime сам по себе не хранит смещение часового пояса так явно, как DateTimeOffset.
Для событий, где важно зафиксировать момент времени, часто удобнее DateTimeOffset:
var createdAt = DateTimeOffset.UtcNow;
Например, для аудита:
var entry = new AuditEntry(
userId,
DateTimeOffset.UtcNow
);
А локальное время лучше получать только на границе приложения, перед показом пользователю, в отчёте или интерфейсе:
var local = createdAt.ToLocalTime();
Если перемешать локальное время и UTC, баги появляются тихо, токены живут дольше, фильтры по датам съезжают, события в логах идут не в том порядке.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3👍2🔥1
This media is not supported in your browser
VIEW IN TELEGRAM
Этот репозиторий поможет разобраться с основными паттернами проектирования и понять, как применять их в реальной разработке на C#. Здесь собраны примеры реализации популярных паттернов с понятным кодом и структурой, которая помогает быстро понять идею каждого решения.
Оставляю ссылочку: GitHub
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11👍7🔥4
Шпаргалка по LINQ в C#!
Например, Where() фильтрует коллекцию, Select() преобразует элементы, а GroupBy() собирает данные по ключу.
На картинке LINQ cheat sheet с примерами query syntax и lambda syntax: filtering, anonymous type, ordering, joining, grouping, paging через Skip/Take и базовые операции над последовательностями.
Сохрани, чтобы не потерять!
➡️ C# Ready | #ресурс
Например, Where() фильтрует коллекцию, Select() преобразует элементы, а GroupBy() собирает данные по ключу.
На картинке LINQ cheat sheet с примерами query syntax и lambda syntax: filtering, anonymous type, ordering, joining, grouping, paging через Skip/Take и базовые операции над последовательностями.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12👍5🔥4
Собираем простой rate limiter для API на C#!
Иногда нужно ограничить частоту действий: попытки входа, запросы к endpoint-у, отправку писем или генерацию кодов. Сделаем маленький in-memory limiter, который разрешает не больше N действий за окно времени.
Создадим модель записи:
Хранить события будем в словаре:
Метод проверки принимает ключ пользователя:
Очистим старые события:
Если лимит уже достигнут, блокируем:
Использование:
Такой вариант подойдёт для одного процесса. Если приложение работает в нескольких инстансах, limiter лучше переносить в Redis или другой общий storage.
➡️ C# Ready | #совет
Иногда нужно ограничить частоту действий: попытки входа, запросы к endpoint-у, отправку писем или генерацию кодов. Сделаем маленький in-memory limiter, который разрешает не больше N действий за окно времени.
Создадим модель записи:
public sealed record Hit(
string Key,
DateTimeOffset CreatedAt
);
Хранить события будем в словаре:
private readonly Dictionary<string, Queue<DateTimeOffset>> _hits = new();
private readonly object _lock = new();
Метод проверки принимает ключ пользователя:
public bool Allow(string key, int limit, TimeSpan window)
{
var now = DateTimeOffset.UtcNow;
var border = now - window;
Очистим старые события:
lock (_lock)
{
if (!_hits.TryGetValue(key, out var queue)) {
queue = new Queue<DateTimeOffset>();
_hits[key] = queue;
}
while (queue.Count > 0 && queue.Peek() < border) {
queue.Dequeue();
}
Если лимит уже достигнут, блокируем:
if (queue.Count >= limit) {
return false;
}
queue.Enqueue(now);
return true;
}
}Использование:
if (!limiter.Allow(userId, 5, TimeSpan.FromMinutes(1))) {
return Results.TooManyRequests();
}Такой вариант подойдёт для одного процесса. Если приложение работает в нескольких инстансах, limiter лучше переносить в Redis или другой общий storage.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍2🔥1
This media is not supported in your browser
VIEW IN TELEGRAM
The C# Academy большой сайт для прокачки C# и .NET через реальные проекты!
На сайте собрана практическая дорожная карта по C# и .NET: консольные приложения, базы данных, ASP.NET Core, Entity Framework, тестирование, API, fullstack-направление и проекты разной сложности. Фишка в том, что это не просто теория, а обучение через задачи и реальные мини-проекты: можно идти по роадмапу, закрывать темы, собирать портфолио и постепенно переходить от базового C# к нормальной backend-разработке на .NET.
➡️ C# Ready | #ресурс
На сайте собрана практическая дорожная карта по C# и .NET: консольные приложения, базы данных, ASP.NET Core, Entity Framework, тестирование, API, fullstack-направление и проекты разной сложности. Фишка в том, что это не просто теория, а обучение через задачи и реальные мини-проекты: можно идти по роадмапу, закрывать темы, собирать портфолио и постепенно переходить от базового C# к нормальной backend-разработке на .NET.
Оставляю ссылочку: The C# Academy
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8🔥6👍4