csharp_ready | Unsorted

Telegram-канал csharp_ready - C# Ready | Unity

9818

Авторский канал по разработке на C# и Unity. Ресурсы, гайды, задачи, шпаргалки. Информация ежедневно пополняется! Автор: @energy_it РКН: https://clck.ru/3SBaT3 Реклама на бирже: https://telega.in/c/csharp_ready

Subscribe to a channel

C# Ready | Unity

👍 TheAlgorithms C-Sharp — алгоритмы и структуры данных на C#!

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

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

➡️ C# Ready | #репозиторий

Читать полностью…

C# Ready | Unity

Делаем мини-router команд на C#!

Нужно обработать набор текстовых команд без длинного switch? В этой задаче собираем простой router на Dictionary, который находит нужный обработчик и не зависит от регистра введённой команды.

В этой задаче:

• Храним команды в Dictionary;
• Ищем обработчик через TryGetValue;
• Запускаем нужное действие.


Такой подход удобен для CLI-утилит, ботов, админских команд и внутренних инструментов, где набор действий постепенно растёт.

➡️ C# Ready | #задача

Читать полностью…

C# Ready | Unity

The C# Academy большой сайт для прокачки C# и .NET через реальные проекты!

На сайте собрана практическая дорожная карта по C# и .NET: консольные приложения, базы данных, ASP.NET Core, Entity Framework, тестирование, API, fullstack-направление и проекты разной сложности. Фишка в том, что это не просто теория, а обучение через задачи и реальные мини-проекты: можно идти по роадмапу, закрывать темы, собирать портфолио и постепенно переходить от базового C# к нормальной backend-разработке на .NET.

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


➡️ C# Ready | #ресурс

Читать полностью…

C# Ready | Unity

Шпаргалка по LINQ в C#!

Например, Where() фильтрует коллекцию, Select() преобразует элементы, а GroupBy() собирает данные по ключу.

На картинке LINQ cheat sheet с примерами query syntax и lambda syntax: filtering, anonymous type, ordering, joining, grouping, paging через Skip/Take и базовые операции над последовательностями.

Сохрани, чтобы не потерять!

➡️ C# Ready | #ресурс

Читать полностью…

C# Ready | Unity

Почему в 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 | #совет

Читать полностью…

C# Ready | Unity

🐱 Awesome .NET — подборка полезных ресурсов для .NET-разработчиков!

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

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


➡️ C# Ready | #репозиторий

Читать полностью…

C# Ready | Unity

Полезная статья о том, как сделать интеграционные тесты ASP.NET Core API чище и удобнее!

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

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


➡️ C# Ready | #статья

Читать полностью…

C# Ready | Unity

Шпаргалка по возможностям C# 8!

Например, nullable reference types помогают явно отделять значения, которые могут быть null, а indices/ranges позволяют удобнее брать элементы и срезы через ^ и ...

На картинке default interface methods, nullable reference types, pattern matching, async streams и другие возможности с короткими примерами кода.

Сохрани, чтобы не потерять!

➡️ C# Ready | #ресурс

Читать полностью…

C# Ready | Unity

Шпаргалка по недооценённым LINQ-методам в C#!

Например, SequenceEqual() помогает сравнить две последовательности по элементам, ToLookup() создаёт удобную группировку для быстрого доступа по ключу, а GroupJoin() полезен, когда нужно связать две коллекции и получить вложенный результат.

На картинке 5 LINQ-методов с короткими примерами кода: SequenceEqual, Aggregate, GroupJoin, ToLookup и Intersect.

Сохрани, чтобы не потерять!

➡️ C# Ready | #ресурс

Читать полностью…

C# Ready | Unity

Почему 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 | #совет

Читать полностью…

C# Ready | Unity

Понятный разбор того, как на самом деле работает async/await в C#!

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

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


➡️ C# Ready | #статья

Читать полностью…

C# Ready | Unity

👩‍💻 Сегодня рассмотрим Channel<T> — безопасная асинхронная очередь задач в .NET

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

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

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

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

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


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

➡ C# Ready | #гайд

Читать полностью…

C# Ready | Unity

CancellationToken должен проходить через всю цепочку вызовов!

Само наличие CancellationToken в параметрах ещё не делает операцию отменяемой:

async Task SyncAsync(CancellationToken token)
{
var data = await LoadAsync();
await SaveAsync(data);
}


Если вложенные методы не получили токен, они продолжат работать даже после отмены запроса.

Токен нужно передавать во все операции, которые поддерживают отмену:
var data = await LoadAsync(token);
await SaveAsync(data, token);
await Task.Delay(500, token);


Для вычислений без await запрос отмены необходимо проверять вручную:
foreach (var item in items)
{
token.ThrowIfCancellationRequested();
Process(item);
}


Отмена в .NET кооперативная: Cancel() не останавливает код принудительно. Он только сообщает операциям, что работу следует завершить.

Для собственного таймаута можно создать источник отмены:
using var timeout = new CancellationTokenSource(
TimeSpan.FromSeconds(10)
);

await SyncAsync(timeout.Token);


Если одновременно нужен токен вызывающего кода и внутренний таймаут, их можно объединить:
using var linked = CancellationTokenSource
.CreateLinkedTokenSource(token, timeout.Token);

await SyncAsync(linked.Token);


Передавайте CancellationToken до самого нижнего отменяемого вызова, иначе API обещает отмену, но фактическая работа продолжается.

➡️ C# Ready | #совет

Читать полностью…

C# Ready | Unity

Интересный разбор того, как сегодня создавать игры на C# без готового движка!

В этой статье:
• Почему C# подходит для небольших кроссплатформенных игр
• Как SDL3 закрывает окна, ввод, контроллеры и рендеринг
• Как организовать загрузку ассетов и собрать собственный редактор на Dear ImGui

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


➡️ C# Ready | #статья

Читать полностью…

C# Ready | Unity

Шпаргалка по форматированию даты и времени в C#!

Например, dd.MM.yyyy выведет дату с числом, месяцем и годом, HH:mm покажет время в 24-часовом формате, а MMM и MMMM позволяют получить сокращённое или полное название месяца.

На картинке спецификаторы для дня, месяца, года, часов, минут, секунд, долей секунды, часового пояса и AM/PM. Для каждого варианта приведены готовый формат и пример результата.

Сохрани, чтобы не потерять!

➡️ C# Ready | #ресурс

Читать полностью…

C# Ready | Unity

Почему LINQ-запрос может выполниться несколько раз?

LINQ часто выглядит как готовая коллекция:

var active = users.Where(u => u.IsActive);


Но на самом деле Where() возвращает отложенный запрос. Он не выполняется в момент создания, а запускается каждый раз, когда результат начинают перебирать.

Например:
var active = users.Where(u => {
Console.WriteLine(u.Name);
return u.IsActive;
});


Если пройтись по active два раза, фильтрация тоже выполнится два раза:
foreach (var user in active) { }

foreach (var user in active) { }


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

Когда результат нужен именно как снимок данных, лучше материализовать его явно:
var active = users
.Where(u => u.IsActive)
.ToList();


Теперь фильтрация выполнится один раз, а дальше код будет работать уже с готовым списком:
foreach (var user in active) { }
foreach (var user in active) { }


Если результат используется несколько раз, подумай, нужен ли тебе отложенный запрос или готовая коллекция через ToList().

➡️ C# Ready | #совет

Читать полностью…

C# Ready | Unity

Как в C# аккуратно ограничить область using?

Иногда объект нужно освободить не в конце всего метода, а сразу после маленького участка кода.

Часто пишут так:

using var stream = File.OpenRead(path);

var hash = SHA256.HashData(stream);
DoSomethingElse();


using var освободит stream только в конце текущей области видимости. Если метод большой, файл может оставаться открытым дольше, чем нужно.

Для короткого ресурса лучше явно ограничить блок:
byte[] hash;

using (var stream = File.OpenRead(path))
{
hash = SHA256.HashData(stream);
}


После блока stream уже закрыт:
DoSomethingElse();


Это особенно важно для файлов:
using (var file = File.OpenWrite("report.txt"))
{
await file.WriteAsync(data);
}


И для сетевых или database-ресурсов, где лишнее удержание соединения может мешать другим операциям:
using (var connection = CreateConnection())
{
await connection.OpenAsync();
await SaveAsync(connection);
}


using var удобен, когда ресурс действительно нужен до конца метода:
using var activity = tracer.StartActivity("Import");


А обычный using (...) { } лучше, когда ресурс нужен только в маленьком блоке.

➡️ C# Ready | #совет

Читать полностью…

C# Ready | Unity

Собираем простой 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 | #совет

Читать полностью…

C# Ready | Unity

🧐 RefactoringGuru Design Patterns C# — паттерны проектирования на C#!

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

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

➡️ C# Ready | #репозиторий

Читать полностью…

C# Ready | Unity

Интересная статья о том, как сделать нормальный онбординг в desktop-приложении на C# и Avalonia!

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

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


➡️ C# Ready | #статья

Читать полностью…

C# Ready | Unity

Как не сломать 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 | #совет

Читать полностью…

C# Ready | Unity

Собираем 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 | #практика

Читать полностью…

C# Ready | Unity

Собираем простую очередь фоновых задач!

Иногда в приложении нужно отложить работу: отправить 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 | #практика

Читать полностью…

C# Ready | Unity

Простая и популярная статья о создании Telegram-бота на C#!

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

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


➡️ C# Ready | #статья

Читать полностью…

C# Ready | Unity

🇷🇺 Разбираешься в радиочипах, оптике и связи?
Забери до 1 000 000 рублей за свои инженерные навыки на турнире «Дронкон» 🇷🇺

«Сталинские Соколы» открывают регистрацию на 4-й Всероссийский турнир «Дронкон», который пройдет с 22 по 26 августа.

Турнир пройдет по направлению:
- Инженерное дело: навыки программирования, сборка электронного оборудования, беспроводная связь, оптические системы + стратегия «Битва Дронов»;

Призовой фонд для победителей:

🥇место – 1 000 000 рублей
🥈место – 700 000 рублей
🥉место – 500 000 рублей
Награда за 4-8 места - 100 000 рублей

Пройди заочный онлайн-этап и получи путевку на очный этап турнира в Республику Татарстан!
Перелет, питание, проживание - за счет организаторов.

🇷🇺 Подать заявку и узнать подробности 🇷🇺

Читать полностью…

C# Ready | Unity

Пачку офферов заказывали?

О всех трудоустройствах через ИИ-ассистента Софи ребята узнают от пользователей.

И какой же кайф, когда они делятся своими историями.

Вот так в чате узнали сразу о трех новых офферах:

• Кейс Сергея - 2 недели в Софи и 2 оффера.
• Кейс Елены - 1 неделя в Софи и оффер спустя 10 лет перерыва в работе. Елене 52 года.
• Кейс Юлии - 1 месяц в Софи, свитчер из финансового аналитика в разработчика SQL. И еще один оффер🎉

Искренние поздравления ребятам и удачи им на новой работе!

В следующий раз доступ в Софи откроется уже 21 июля.

Если хочешь получить 3 дня бесплатного доступа - подписывайся на этот канал, все анонсы будут там.

Читать полностью…

C# Ready | Unity

«Снова в школу!» – звучит тревожно.

Но в магистратуре МИФИ и Школы 21 «Управление внедрением искусственного интеллекта в бизнес» совсем другая атмосфера: тут нет страха ошибки и ощущения, что зря тратишь время.

Зато есть рабочая среда, поддержка единомышленников и уверенность, что растёшь.

Что внутри программы:

— не просто алгоритмы, а живые сценарии – где применить ИИ, чтобы было лучше, быстрее, дешевле;
— учим внедрять ИИ для управления продуктом от технического задания до запуска;
— делаешь реальные проекты прямо во время учёбы, а не ближе к диплому;
— диплом государственного образца МИФИ – одного из ведущих технических университетов страны.

Подробности и заявка здесь

Читать полностью…

C# Ready | Unity

IT мероприятия 🤘

Всё самое интересное для карьеры в IT: митапы, хакатоны, конференции, стажировки и обучение.

Подписывайся 🤘

1. Москва
2. Санкт-Петербург
3. Для стажеров и студентов

Читать полностью…

C# Ready | Unity

Почему with у record не создаёт глубокую копию?

Выражение with удобно использовать для создания изменённой копии record:

var updated = user with
{
Name = "Alex"
};


Но копирование будет поверхностным. Сам record создаётся заново, а вложенные ссылочные объекты продолжают использоваться совместно.

Рассмотрим record со списком:
record User(
string Name,
List<string> Roles
);


Создадим его копию и изменим список:
var original = new User(
"Alex",
new() { "read" }
);

var copy = original with { };

copy.Roles.Add("write");


Изменение появится и в исходном объекте:
Console.WriteLine(
original.Roles.Count
);
// 2


Причина в том, что original.Roles и copy.Roles ссылаются на один экземпляр List<string>.

Если списки должны быть независимыми, скопируйте коллекцию явно:
var copy = original with
{
Roles = new List<string>(
original.Roles
)
};


Другой вариант использовать неизменяемые коллекции:
record User(
ImmutableArray<string> Roles
);

var copy = original with
{
Roles = original.Roles.Add("write")
};


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

➡️ C# Ready | #совет

Читать полностью…

C# Ready | Unity

Отправляем несколько HTTP-запросов параллельно на C#!

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

Создадим HttpClient:

using var client = new HttpClient
{
Timeout = TimeSpan.FromSeconds(10)
};


Запустим три запроса без немедленного ожидания результата:
Task<string> usersTask =
client.GetStringAsync(usersUrl);

Task<string> ordersTask =
client.GetStringAsync(ordersUrl);

Task<string> ratesTask =
client.GetStringAsync(ratesUrl);


Теперь все операции уже выполняются параллельно. Дождёмся завершения всей группы:
await Task.WhenAll(
usersTask,
ordersTask,
ratesTask
);


Получим готовые результаты:
string users = await usersTask;
string orders = await ordersTask;
string rates = await ratesTask;


Если каждый запрос занимает около секунды, последовательное выполнение продлится примерно три секунды, а параллельное около одной.

Добавим ограничение общего времени ожидания:
using var cts =
new CancellationTokenSource(
TimeSpan.FromSeconds(5)
);


Передадим токен отмены запросу:
string data = await client.GetStringAsync(
usersUrl,
cts.Token
);


Так приложение сможет прекратить операцию, если внешний сервис отвечает слишком долго.

➡️ C# Ready | #практика

Читать полностью…
Subscribe to a channel