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

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

РКН: https://clck.ru/3SBaT3
Download Telegram
Разбираем 7 инструментов для работы со временем!

В .NET важно различать длительность, измерение прошедшего времени и ожидание. TimeSpan хранит интервал, Stopwatch измеряет выполнение, а PeriodicTimer и Task.Delay помогают управлять асинхронной работой.

➡️ C# Ready | #шпора
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥20👍12❤6
Схема прохождения запроса через middleware в ASP.NET Core!

На картинке показан путь HTTP-запроса через обработчик исключений, HTTPS, статические файлы, маршрутизацию, CORS, аутентификацию и авторизацию.

Например, проверка прав должна выполняться после определения пользователя. Если поставить middleware не в том порядке, приложение может неожиданно возвращать 401, пропускать нужную обработку или выполнять лишнюю работу.

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

➡️ C# Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍4🔥3
Почему фабрика в ConcurrentDictionary.GetOrAdd может сработать несколько раз?

ConcurrentDictionary удобно использовать, когда несколько потоков обращаются к одному словарю. Кажется, что GetOrAdd одновременно проверяет ключ и ровно один раз создаёт значение:
var profile = cache.GetOrAdd(id, key => LoadProfile(key));


В словаре действительно останется одно значение для ключа. Но это не означает, что LoadProfile обязательно вызовется только один раз.

Фабрика выполняется вне внутренней блокировки словаря. Если два потока одновременно не нашли ключ, оба могут начать загрузку:
var first = cache.GetOrAdd(id, LoadProfile);
var second = cache.GetOrAdd(id, LoadProfile);


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

Если повторное вычисление недорого и безопасно, обычный GetOrAdd подходит. Для дорогой инициализации можно хранить в словаре Lazy<T>:
var pending = cache.GetOrAdd(id, key =>
new Lazy<Profile>(() => LoadProfile(key)));
var profile = pending.Value;


Здесь у разных потоков тоже могут создаться несколько объектов Lazy, но в словаре останется один. У него загрузка при обращении к Value выполнится один раз при стандартном режиме Lazy.

Не забывайте и про ошибки. По умолчанию Lazy запоминает исключение фабрики, а значит последующие обращения к тому же Value снова получат эту ошибку:
cache.TryRemove(id, out _);


Удалять запись для повтора нужно с учётом конкуренции с другими запросами.

➡️ C# Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍3🔥2
Проверяем целостность скачанного файла в C#!

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

Пусть ожидаемый SHA-256 приходит из источника, отдельно от самого файла. Сначала проверим его формат и превратим шестнадцатеричную строку в байты:
if (expectedHex.Length != 64)
throw new ArgumentException("Неверный SHA-256");
byte[] expected = Convert.FromHexString(expectedHex);


Теперь expected содержит ровно 32 байта. Если строка нужной длины, но содержит не шестнадцатеричные символы, Convert.FromHexString выбросит FormatException. Так произвольный текст не пройдёт за корректный хеш.

Откроем файл потоком, чтобы не загружать весь архив в память:
await using var stream = File.OpenRead(path);
byte[] actual = await SHA256.HashDataAsync(stream);


HashDataAsync прочитает файл до конца и вернёт 32 байта. Для больших файлов потребление памяти остаётся небольшим, поскольку поток обрабатывается постепенно.

Сравним результат с ожидаемым значением:
bool valid = CryptographicOperations.FixedTimeEquals(actual, expected);
if (!valid) throw new InvalidDataException("SHA-256 не совпал");


➡️ C# Ready | #практика
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9🔥6❤5
❤5👍3🔥3
Разбираем 7 инструментов HttpClient!

HttpClient пригодится не только для простого GET. С ним можно проверять HTTP-статус, отправлять JSON, читать ответ как текст или поток и собирать запросы с нужным методом и заголовками.

В этой шпоре собраны GetAsync, EnsureSuccessStatusCode, ReadAsStringAsync, GetFromJsonAsync, PostAsJsonAsync, SendAsync и GetStreamAsync.

➡️ C# Ready | #шпора
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8🤝4❤3👍2
Почему копия record через with может изменить исходные данные?

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

Представим профиль со списком тегов:
record Profile(string Name, List<string> Tags);
var original = new Profile("Ana", new() { "dev" });


Создадим копию с другим именем:
var copy = original with { Name = "Bob" };


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

Теперь изменим теги только у копии:
copy.Tags.Add("admin");
Console.WriteLine(string.Join(", ", original.Tags));


Результат у исходного профиля тоже станет dev, admin. С новым именем всё в порядке, проблема только во вложенном изменяемом объекте.

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


Теперь добавление в independent.Tags не изменит original.Tags. Но это копия только самого списка. Если элементы списка тоже изменяемые объекты, их придётся копировать отдельно.

➡️ C# Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍4🔥3
Знали, когда в C# удобно использовать Random.Shared?

Для простого случайного числа нередко создают отдельный генератор прямо в месте вызова:
int dice = new Random().Next(1, 7);


Такой код работает, но при частых вызовах приходится снова и снова создавать объект. Если генератор нужен во многих местах приложения, проще взять общий экземпляр:
int dice = Random.Shared.Next(1, 7);


Random.Shared доступен начиная с .NET 6 и безопасен для одновременного использования из разных потоков. Для обычных игровых событий, тестовых данных и случайного выбора этого достаточно.

Обратите внимание на границы Next. Нижняя включается, верхняя нет:
int index = Random.Shared.Next(0, items.Count);


Так можно выбрать индекс, не выйдя за пределы списка. Для шестигранного кубика верхняя граница должна быть 7, а не 6:
int dice = Random.Shared.Next(1, 7);


А вот пароли, токены и коды подтверждения этим способом генерировать нельзя. Для значений, которые должны быть непредсказуемы для атакующего, нужен криптографический генератор:
int code = RandomNumberGenerator.GetInt32(100000, 1000000);


Random.Shared хорош для обычной случайности, а RandomNumberGenerator нужен там, где от случайности зависит безопасность.

➡️ C# Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9❤4🔥4
Почему цепочка LINQ иногда читает всю коллекцию до первого результата?

Where и Select могут отдавать элементы по одному. А вот OrderBy, GroupBy и Join устроены сложнее, им нужно подготовить данные, прежде чем продолжить обработку. Автор разбирает эту границу между ленивым вычислением и буферизацией.

В статье показывают:
• зачем OrderBy хранит элементы и сортирует индексы
• как GroupBy собирает группы и сохраняет порядок их появления
• почему Join сначала строит структуру для внутренней коллекции

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

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


➡️ C# Ready | #статья
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6🔥6👍3
Шпаргалка по источникам памяти для Span<T> в C#!

На картинке рядом показаны три способа создать Span<T>. Он может ссылаться на обычный массив, область stackalloc или неуправляемый участок памяти. Отдельные примеры помогают увидеть, что Span<T> описывает диапазон, а не владеет памятью.

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

➡️ C# Ready | #ресурс
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3❤1👍1
🎓 Как гарантированно войти в сферу кибербезопасности с официальным дипломом?

Самостоятельное обучение в ИБ - это месяцы хаоса и разрозненных роликов с YouTube. Покупка курсов по отдельности на популярных платформах легко переваливает за 300 000 ₽.

🔥CyberYozh Academy собрали цельный карьерный трек в один пак: 5 фундаментальных программ + официальный Диплом системного администратора за 50 000 ₽ (вместо ~250 000 ₽)

Что входит в программу:
▪️ Linux CyberPunk - системное администрирование с выдачей диплома, открывающего двери в корпоративный сектор.
▪️ SQL для хакера - глубокое понимание архитектуры баз данных и векторов атак.
▪️ HackerPoint - продвинутый уровень пентеста и анализа уязвимостей.
▪️ HackerPoint (Blue vs Red Team) - база сетевой безопасности, защиты и атак.
▪️ AI-помощники на Python - автоматизация рутины безопасника при помощи нейросетей.

Вы получаете законченную квалификацию: от администрирования защищенных облачных решений до проведения тестов на проникновение.

⚠️ Обратите внимание: Из-за персональной проверки заданий и официального оформления дипломов на каждый поток действует жесткий лимит - не более 15 мест.

🦔Отправь промокод C#Ready в чат с менеджером, чтобы закрепить за собой скидку и узнать подробности:
👉@cyacademy_support
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1
Почему await Task.WhenAll может показать не все ошибки сразу?

Допустим несколько независимых операций выполняются параллельно. Мы объединяем их в одну задачу:
var all = Task.WhenAll(first, second);
await all;


Если обе операции завершатся с ошибками, WhenAll дождётся обеих. Итоговая задача будет содержать исключения из всех упавших операций.

Для диагностики сохраните общую задачу перед await:
var all = Task.WhenAll(first, second);


Теперь можно дождаться её и обработать неудачу:
try
{
await all;
}
catch
{
Console.Error.WriteLine(all.Exception);
throw;
}


Свойство all.Exception содержит AggregateException. Его InnerExceptions позволяют увидеть ошибки нескольких операций, а не только ту, которую выбросил await. Например, внутри обработки именно ошибочной задачи можно пройтись по ним:
foreach (var error in all.Exception!.InnerExceptions)
Console.WriteLine(error.Message);


Знак ! здесь уместен только внутри ветки, где общая задача действительно завершилась с ошибкой. Если она была отменена без ошибок, Exception будет null, и обработку отмены нужно делать отдельно.


Почему await Task.WhenAll может показать не все ошибки сразу?

Допустим, несколько независимых операций выполняются параллельно. Мы объединяем их в одну задачу:
var all = Task.WhenAll(first, second);
await all;


Если обе операции завершатся с ошибками, WhenAll дождётся обеих. Итоговая задача будет содержать исключения из всех упавших операций. Но обычный await выбрасывает одно исключение, поэтому в catch легко увидеть только часть картины.

Для диагностики сохраните общую задачу перед await:
var all = Task.WhenAll(first, second);


Теперь можно дождаться её и обработать неудачу:
try
{
await all;
}
catch
{
Console.Error.WriteLine(all.Exception);
throw;
}


Свойство all.Exception содержит AggregateException. Его InnerExceptions позволяют увидеть ошибки нескольких операций, а не только ту, которую выбросил await. Например, внутри обработки именно ошибочной задачи можно пройтись по ним:
foreach (var error in all.Exception!.InnerExceptions)
Console.WriteLine(error.Message);


Знак ! здесь уместен только внутри ветки, где общая задача действительно завершилась с ошибкой. Если она была отменена без ошибок, Exception будет null, и обработку отмены нужно делать отдельно.

➡️ C# Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5❤2👍2
Альфа-Банк ищет скиллового коллегу QA Fullstack на C#

Предлагаем полную удалёнку или гибридный график – у нас классные ИТ-офисы в Москве, Питере и Екатеринбурге.

Что нужно делать

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

Какие навыки важны

– Опыт автоматизации на C# и ручного тестирования от года.
– Знание API (SOAP, REST), SQL, основ клиент-серверной архитектуры и тест-дизайна.

Условия и развитие

– Сильное сообщество QA и C#-разработчиков: можно получить совет и поделиться опытом на митапе и в блоге на Хабре.
– Реальная перспектива роста: технический трек развития, в том числе до C#-лида

Оставляйте отклик по ссылке.
❤3🔥1
Читаем большой JSON-массив по одному объекту в C#!

Пусть orders.json содержит тысячи заказов одним массивом. Если загрузить файл целиком через ReadAllText, память займут и исходный текст, и созданные объекты. Для последовательной обработки удобнее читать поток и получать элементы по мере разбора.

Сначала опишем модель входных данных. В примере у каждого заказа есть номер и сумма:
public sealed record Order(int Id, decimal Total);


Откроем файл как поток. JsonSerializer.DeserializeAsyncEnumerable ожидает JSON-массив и возвращает асинхронную последовательность его элементов:
await using var stream = File.OpenRead("orders.json");
var orders = JsonSerializer.DeserializeAsyncEnumerable<Order>(
stream, cancellationToken: cancellationToken);


Здесь cancellationToken позволяет прервать длительную обработку, например при остановке запроса или фоновой задачи. Не надо самостоятельно резать JSON на строки, переносы строк не определяют границы объектов.

Обработаем элементы по одному и накопим итог. Нулевые элементы пропустим, а отрицательную сумму считаем ошибкой данных:
decimal total = 0;
await foreach (Order? order in orders) {
if (order is null) continue;
if (order.Total < 0) throw new InvalidDataException("Сумма меньше нуля");
total += order.Total;
}


После завершения цикла в total будет сумма всех валидных заказов. В нужном месте вместо накопления можно отправлять запись в базу. Если важна устойчивость к повторной обработке, используй Id как ключ идемпотентности.

➡️ C# Ready | #практика
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥4❤2
❤6👍2🔥2