C# Ready | Unity


Гео и язык канала: Россия, Русский
Категория: Технологии


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

Зарегистрирован в РКН
Связанные каналы

Гео и язык канала
Россия, Русский
Категория
Технологии
Статистика
Фильтр публикаций



838 0 2 15 11

Читаем большой 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(
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 | #практика


Альфа-Банк ищет скиллового коллегу QA Fullstack на C#

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

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

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

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

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

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

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

Оставляйте отклик по ссылке.


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


🎓 Как гарантированно войти в сферу кибербезопасности с официальным дипломом?

Самостоятельное обучение в ИБ - это месяцы хаоса и разрозненных роликов с 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


Шпаргалка по источникам памяти для Span в C#!

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

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

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


Почему цепочка LINQ иногда читает всю коллекцию до первого результата?

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

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

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

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


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


Знали, когда в 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 | #совет


Почему копия record через with может изменить исходные данные?

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

Представим профиль со списком тегов:
record Profile(string Name, List 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(original.Tags)
};

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

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


Разбираем 7 инструментов HttpClient!

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

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

➡️ C# Ready | #шпора




Проверяем целостность скачанного файла в 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 | #практика


Почему фабрика в 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:
var pending = cache.GetOrAdd(id, key =>
new Lazy(() => LoadProfile(key)));
var profile = pending.Value;

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

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

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

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


Схема прохождения запроса через middleware в ASP.NET Core!

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

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

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

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


Разбираем 7 инструментов для работы со временем!

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

➡️ C# Ready | #шпора




Читаем все страницы API через IAsyncEnumerable в C#!

Многие API отдают данные порциями. Если собрать все страницы в List сразу, код быстро разрастается, а юзер не увидит ни одной записи до конца загрузки.

IAsyncEnumerable позволяет отдавать элементы по мере чтения следующей страницы. Вызывающий код использует await foreach и не знает, сколько запросов понадобилось внутри.
record Page(IReadOnlyList Items, string? NextCursor);

Метод клиента пусть возвращает страницу и курсор для следующего запроса. Null означает, что сервер больше ничего не отдаёт.
Task GetPageAsync(
string? cursor, CancellationToken token);

В итераторе сохраняем текущий курсор. После каждого ответа возвращаем заказы по одному и только потом запрашиваем следующую страницу.
string? cursor = null;
do {
var page = await GetPageAsync(cursor, token);
foreach (var order in page.Items) yield return order;
cursor = page.NextCursor;
} while (cursor is not null);

Юзер может остановиться в любой момент. CancellationToken важно передавать в сетевой вызов, чтобы отмена не ждала лишнюю страницу.
await foreach (var order in ReadOrders(token))
{
await SaveAsync(order, token);
}

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

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


Шпаргалка по поколениям сборщика мусора в .NET!

На картинке показаны поколения управляемой кучи, продвижение выживших объектов и три основные фазы GC. Mark находит достижимые объекты, sweep освобождает мусор, а compact уменьшает фрагментацию памяти.

Например, Gen 0 хранит новые короткоживущие объекты, Gen 1 служит промежуточной зоной, а Gen 2 содержит долгоживущие данные. Объекты крупнее 85 КБ обычно попадают в Large Object Heap.

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

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


Blazor против Vue.js глазами C# разработчика!

Автор сравнивает два frontend-подхода на опыте реальных проектов. Статья показывает, что будет привычным, а что неожиданным для разработчика, который приходит в интерфейсы из C# и .NET.

В статье разбирают:
• как ощущается компонентная разработка в Blazor и Vue
• где C# стек даёт сильную сторону на frontend
• какие различия важнее синтаксиса и шаблонов

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


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


Как в C# не забыть про ConfigureAwait(false)?

Когда пишешь библиотечный async-код, часто встречается такой вариант:
var json = await httpClient.GetStringAsync(url);
return JsonSerializer.Deserialize(json);

Для приложения это обычно нормально. Но для библиотек и низкоуровневого кода часто лучше явно сказать, после await не нужно возвращаться в исходный context.

Для этого используют ConfigureAwait(false):
var json = await httpClient
.GetStringAsync(url)
.ConfigureAwait(false);

После await продолжение может выполниться на любом подходящем потоке:
return JsonSerializer.Deserialize(json);

Это особенно полезно в shared-библиотеках, SDK, инфраструктурном коде и клиентах к внешним API.

Например:
public async Task LoadUserAsync(string id)
{
var response = await client.GetAsync($"/users/{id}")
.ConfigureAwait(false);

return await ParseAsync(response)
.ConfigureAwait(false);
}

Но в UI-коде всё иначе. Если после await нужно обновить интерфейс, context может быть нужен:
label.Text = "Loaded";

В таком случае ConfigureAwait(false) может увести продолжение с UI-потока.

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

Показано 20 последних публикаций.