C# Ready | Unity
10.5K subscribers
1.48K photos
84 videos
739 links
Авторский канал по разработке на C# и Unity.
Ресурсы, гайды, задачи, шпаргалки.
Информация ежедневно пополняется!

Cотрудничество: @energy_c

РКН: https://clck.ru/3SBaT3
Download Telegram
👩‍💻 Сегодня рассмотрим Channel<T> — безопасная асинхронная очередь задач в .NET

Это потокобезопасная очередь для передачи сообщений между задачами без ручных блокировок и сложной синхронизации.

Что важно знать:

• Работает без ручных блокировок и сложных синхронных вызовов.

• Позволяет автоматически замедлять отправку при перегрузке памяти.

• Обеспечивает плавную передачу данных между параллельными задачами.


Так вы получаете готовый «из коробки» механизм асинхронной очереди с естественным управлением нагрузкой без лишнего кода.

➡ C# Ready | #гайд
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11❤5👍4🤝1
🔥3❤2👍1🤝1
Понятный разбор того, как на самом деле работает async/await в C#!

В этой статье:
• Чем асинхронность отличается от запуска работы в отдельном потоке
• Когда await освобождает поток, а когда выполнение продолжается синхронно
• Как компилятор превращает асинхронный метод в машину состояний

Продолжай читать на Habr!


➡️ C# Ready | #статья
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤4🔥3
Почему yield return в C# выполняется не сразу?

Метод с 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 создаёт ленивую последовательность. Код внутри запускается при перечислении, а не при вызове метода.

➡️ C# Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍4🔥2
Простая и популярная статья о создании Telegram-бота на C#!

В этой статье:
• Как создать бота через BotFather и подключить его к проекту
• Как принимать сообщения пользователя и отправлять ответы
• Как собрать базу, от которой уже можно развивать своего бота дальше

Продолжай читать на Habr!


➡️ C# Ready | #статья
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 | #ресурс
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10👍7🔥6
Собираем простую очередь фоновых задач!

Иногда в приложении нужно отложить работу: отправить 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-запрос открытым, если работу можно выполнить после ответа пользователю.

➡️ C# Ready | #практика
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 | #ресурс
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍2🔥1
Собираем retry-обёртку для нестабильных операций!

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

Сделаем универсальный метод 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-запрос повторять бессмысленно.

➡️ C# Ready | #практика
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤4🔥3
Полезная статья о том, как сделать интеграционные тесты ASP.NET Core API чище и удобнее!

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

Продолжай читать на Habr!


➡️ C# Ready | #статья
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍3🔥2
Как не сломать Dictionary в C# из-за регистра строк?

Очень частая ситуация: ключи приходят из 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() хуже: можно забыть сделать это в одном месте и получить странный баг.

➡️ C# Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤6🔥6
❤5👍3🔥2
This media is not supported in your browser
VIEW IN TELEGRAM
🐱 Awesome .NET — подборка полезных ресурсов для .NET-разработчиков!

Этот репозиторий — большая коллекция материалов для работы с .NET. Здесь собраны полезные библиотеки, инструменты, фреймворки и проекты, которые помогают быстрее находить решения и лучше ориентироваться.

Оставляю ссылочку: GitHub 📱


➡️ C# Ready | #репозиторий
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9👍7🔥1
Интересная статья о том, как сделать нормальный онбординг в desktop-приложении на C# и Avalonia!

В этой статье:
• Как строятся интерактивные туры по интерфейсу с подсветкой элементов
• Почему desktop-приложениям не хватает готовых onboarding-инструментов
• Как автор решал позиционирование, скрытые элементы, темы и сохранение прогресса

Продолжай читать на Habr!


➡️ C# Ready | #статья
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍4🔥2
Почему в C# стоит аккуратно использовать DateTime.Now?

Когда нужно сохранить время события, часто пишут так:
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, баги появляются тихо, токены живут дольше, фильтры по датам съезжают, события в логах идут не в том порядке.

➡️ C# Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3👍2🔥1
This media is not supported in your browser
VIEW IN TELEGRAM
🧐 RefactoringGuru Design Patterns C# — паттерны проектирования на C#!

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

Оставляю ссылочку: GitHub 📱

➡️ C# Ready | #репозиторий
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 | #ресурс
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12👍5🔥4
Собираем простой rate limiter для API на C#!

Иногда нужно ограничить частоту действий: попытки входа, запросы к 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.

➡️ C# Ready | #совет
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.

Оставляю ссылочку: The C# Academy


➡️ C# Ready | #ресурс
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8🔥6👍4