9818
Авторский канал по разработке на C# и Unity. Ресурсы, гайды, задачи, шпаргалки. Информация ежедневно пополняется! Автор: @energy_it РКН: https://clck.ru/3SBaT3 Реклама на бирже: https://telega.in/c/csharp_ready
Разбираем System.Threading.Channels в C#!
Channels помогают строить асинхронные очереди между producer и consumer без ручного lock вокруг коллекции. Один код пишет элементы, другой читает их по мере появления.
В этой шпоре собраны CreateUnbounded, CreateBounded, Writer, Reader, WriteAsync, ReadAllAsync и Complete.
➡️ C# Ready | #шпора
Читаем большой JSON-массив по одному объекту в C#!
Пусть orders.json содержит тысячи заказов одним массивом. Если загрузить файл целиком через ReadAllText, память займут и исходный текст, и созданные объекты. Для последовательной обработки удобнее читать поток и получать элементы по мере разбора.
Сначала опишем модель входных данных. В примере у каждого заказа есть номер и сумма:
public sealed record Order(int Id, decimal Total);
await using var stream = File.OpenRead("orders.json");
var orders = JsonSerializer.DeserializeAsyncEnumerable<Order>(
stream, cancellationToken: cancellationToken);decimal total = 0;
await foreach (Order? order in orders) {
if (order is null) continue;
if (order.Total < 0) throw new InvalidDataException("Сумма меньше нуля");
total += order.Total;
}
Шпаргалка по источникам памяти для Span<T> в C#!
На картинке рядом показаны три способа создать Span<T>. Он может ссылаться на обычный массив, область stackalloc или неуправляемый участок памяти. Отдельные примеры помогают увидеть, что Span<T> описывает диапазон, а не владеет памятью.
Сохрани, чтобы не потерять!
➡️ C# Ready | #ресурс
Знали, когда в C# удобно использовать Random.Shared?
Для простого случайного числа нередко создают отдельный генератор прямо в месте вызова:
int dice = new Random().Next(1, 7);
int dice = Random.Shared.Next(1, 7);
int index = Random.Shared.Next(0, items.Count);
int dice = Random.Shared.Next(1, 7);
int code = RandomNumberGenerator.GetInt32(100000, 1000000);
Разбираем 7 инструментов HttpClient!
HttpClient пригодится не только для простого GET. С ним можно проверять HTTP-статус, отправлять JSON, читать ответ как текст или поток и собирать запросы с нужным методом и заголовками.
В этой шпоре собраны GetAsync, EnsureSuccessStatusCode, ReadAsStringAsync, GetFromJsonAsync, PostAsJsonAsync, SendAsync и GetStreamAsync.
➡️ C# Ready | #шпора
Почему фабрика в ConcurrentDictionary.GetOrAdd может сработать несколько раз?
ConcurrentDictionary удобно использовать, когда несколько потоков обращаются к одному словарю. Кажется, что GetOrAdd одновременно проверяет ключ и ровно один раз создаёт значение:
var profile = cache.GetOrAdd(id, key => LoadProfile(key));
var first = cache.GetOrAdd(id, LoadProfile);
var second = cache.GetOrAdd(id, LoadProfile);
var pending = cache.GetOrAdd(id, key =>
new Lazy<Profile>(() => LoadProfile(key)));
var profile = pending.Value;
cache.TryRemove(id, out _);
Разбираем 7 инструментов для работы со временем!
В .NET важно различать длительность, измерение прошедшего времени и ожидание. TimeSpan хранит интервал, Stopwatch измеряет выполнение, а PeriodicTimer и Task.Delay помогают управлять асинхронной работой.
➡️ C# Ready | #шпора
Шпаргалка по поколениям сборщика мусора в .NET!
На картинке показаны поколения управляемой кучи, продвижение выживших объектов и три основные фазы GC. Mark находит достижимые объекты, sweep освобождает мусор, а compact уменьшает фрагментацию памяти.
Например, Gen 0 хранит новые короткоживущие объекты, Gen 1 служит промежуточной зоной, а Gen 2 содержит долгоживущие данные. Объекты крупнее 85 КБ обычно попадают в Large Object Heap.
Сохрани, чтобы не потерять!
➡️ C# Ready | #ресурс
Как в C# не забыть про ConfigureAwait(false)?
Когда пишешь библиотечный async-код, часто встречается такой вариант:
var json = await httpClient.GetStringAsync(url);
return JsonSerializer.Deserialize<User>(json);
var json = await httpClient
.GetStringAsync(url)
.ConfigureAwait(false);
return JsonSerializer.Deserialize<User>(json);
public async Task<User> LoadUserAsync(string id)
{
var response = await client.GetAsync($"/users/{id}")
.ConfigureAwait(false);
return await ParseAsync(response)
.ConfigureAwait(false);
}
label.Text = "Loaded";
🐱 C# Job Questions — большая база для подготовки к собеседованиям!
Здесь собраны вопросы с развернутыми ответами и примерами кода по C# и .NET, ООП, алгоритмам и структурам данных, архитектуре, базам данных и многопоточности. Дополнительная тренировка, чтобы системно повторить теорию, проверить пробелы и подготовиться к технической части собесов.
Оставляю ссылочку: GitHub 📱
Разбираем File и Path 7 инструментов для работы с файлами!
В C# для повседневной работы с файлами чаще всего хватает стандартных классов File, Directory и Path. Они закрывают проверку существования, чтение, запись, создание папок, обход директорий и безопасную сборку путей.
В этой шпоре собраны File.Exists, ReadAllText, WriteAllText, Directory.CreateDirectory, Directory.EnumerateFiles, Path.Combine и Path.GetExtension.
➡️ C# Ready | #совет
❤️ dotnet-learning — большая база ресурсов для изучения .NET!
Здесь собраны полезные материалы для обучения, практики и более глубокого изучения подходов разработки на .NET. Репозиторий помогает не просто изучать отдельные технологии, а выстраивать целостное понимание платформы. Такой формат удобен и новичкам, и разработчикам с опытом, которые хотят структурировать знания или закрыть пробелы.
Оставляю ссылочку: GitHub 📱
Почему нельзя удалять элементы из List прямо внутри foreach?
foreach в C# удобен для чтения коллекции, но в нём лучше не менять коллекцию во время обхода.
На первый взгляд такой код выглядит нормально:
foreach (var user in users)
{
if (!user.Active) users.Remove(user);
}
Collection was modified;
enumeration operation may not execute.
users.RemoveAll(user => !user.Active);
for (int i = users.Count - 1; i >= 0; i--)
{
if (!users[i].Active) users.RemoveAt(i);
}
users = users
.Where(user => user.Active)
.ToList();
Разбираем CancellationToken 7 приёмов для отмены операций!
CancellationToken помогает останавливать долгие операции без грубого убийства потока и без ручных глобальных флагов.
В этой шпоре: CancellationToken, CancellationTokenSource, CancelAfter, ThrowIfCancellationRequested, IsCancellationRequested, CreateLinkedTokenSource и Register.
➡️ C# Ready | #шпора
Полезная статья про тестирование ASP.NET Core API!
Автор показывает, как уменьшить повторение в интеграционных тестах и собрать удобное окружение с БД, фейковым временем и настройкой под конкретный тест.
В статье автор показывает:
• как устроить тестовое окружение для ASP.NET Core API
• почему setup-код быстро начинает повторяться
• как фикстуры помогают переиспользовать инфраструктуру
Продолжай читать на Habr
Делаем простой feature flags на C#!
Feature flags позволяют включать и выключать поведение без переписывания кода. Это удобно для новых экранов и постепенного выката функций.
Для примера будем хранить флаги в словаре:
var flags = new Dictionary<string, bool>
{
["new-checkout"] = true,
["debug-panel"] = false
};
static bool IsEnabled(
Dictionary<string, bool> flags,
string name)
return flags.TryGetValue(name, out var enabled)
&& enabled;
if (IsEnabled(flags, "new-checkout"))
{
ShowNewCheckout();
}
Console.WriteLine(
IsEnabled(flags, "unknown"));
Почему await Task.WhenAll может показать не все ошибки сразу?
Допустим несколько независимых операций выполняются параллельно. Мы объединяем их в одну задачу:
var all = Task.WhenAll(first, second);
await all;
var all = Task.WhenAll(first, second);
try
{
await all;
}
catch
{
Console.Error.WriteLine(all.Exception);
throw;
}
foreach (var error in all.Exception!.InnerExceptions)
Console.WriteLine(error.Message);
var all = Task.WhenAll(first, second);
await all;
var all = Task.WhenAll(first, second);
try
{
await all;
}
catch
{
Console.Error.WriteLine(all.Exception);
throw;
}
foreach (var error in all.Exception!.InnerExceptions)
Console.WriteLine(error.Message);
Почему цепочка LINQ иногда читает всю коллекцию до первого результата?
Where и Select могут отдавать элементы по одному. А вот OrderBy, GroupBy и Join устроены сложнее, им нужно подготовить данные, прежде чем продолжить обработку. Автор разбирает эту границу между ленивым вычислением и буферизацией.
В статье показывают:
• зачем OrderBy хранит элементы и сортирует индексы
• как GroupBy собирает группы и сохраняет порядок их появления
• почему Join сначала строит структуру для внутренней коллекции
Можно понять куда уходит память в длинных цепочках и почему перестановка операций иногда меняет стоимость запроса.
Продолжай читать на Habr!
Почему копия record через with может изменить исходные данные?
Выражение with удобно для создания нового record с одним изменённым полем. Кажется, что после копирования два объекта полностью независимы.
Представим профиль со списком тегов:
record Profile(string Name, List<string> Tags);
var original = new Profile("Ana", new() { "dev" });
var copy = original with { Name = "Bob" };copy.Tags.Add("admin");
Console.WriteLine(string.Join(", ", original.Tags));var independent = original with
{
Tags = new List<string>(original.Tags)
};
Проверяем целостность скачанного файла в C#!
После загрузки архива полезно убедиться, что полученные байты совпадают с опубликованной контрольной суммой.
Пусть ожидаемый SHA-256 приходит из источника, отдельно от самого файла. Сначала проверим его формат и превратим шестнадцатеричную строку в байты:
if (expectedHex.Length != 64)
throw new ArgumentException("Неверный SHA-256");
byte[] expected = Convert.FromHexString(expectedHex);
await using var stream = File.OpenRead(path);
byte[] actual = await SHA256.HashDataAsync(stream);
bool valid = CryptographicOperations.FixedTimeEquals(actual, expected);
if (!valid) throw new InvalidDataException("SHA-256 не совпал");
Схема прохождения запроса через middleware в ASP.NET Core!
На картинке показан путь HTTP-запроса через обработчик исключений, HTTPS, статические файлы, маршрутизацию, CORS, аутентификацию и авторизацию.
Например, проверка прав должна выполняться после определения пользователя. Если поставить middleware не в том порядке, приложение может неожиданно возвращать 401, пропускать нужную обработку или выполнять лишнюю работу.
Сохрани, чтобы не потерять!
➡️ C# Ready | #совет
Читаем все страницы API через IAsyncEnumerable в C#!
Многие API отдают данные порциями. Если собрать все страницы в List сразу, код быстро разрастается, а юзер не увидит ни одной записи до конца загрузки.
IAsyncEnumerable позволяет отдавать элементы по мере чтения следующей страницы. Вызывающий код использует await foreach и не знает, сколько запросов понадобилось внутри.
record Page<T>(IReadOnlyList<T> Items, string? NextCursor);
Task<Page<Order>> 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);
await foreach (var order in ReadOrders(token))
{
await SaveAsync(order, token);
}
Blazor против Vue.js глазами C# разработчика!
Автор сравнивает два frontend-подхода на опыте реальных проектов. Статья показывает, что будет привычным, а что неожиданным для разработчика, который приходит в интерфейсы из C# и .NET.
В статье разбирают:
• как ощущается компонентная разработка в Blazor и Vue
• где C# стек даёт сильную сторону на frontend
• какие различия важнее синтаксиса и шаблонов
Продолжай читать на Habr
Почему ToLower() перед сравнением строк может быть лишним?
Когда нужно сравнить две строки без учёта регистра, часто сначала приводят обе к нижнему регистру. Код работает, но создаёт новые строки и может зависеть от текущей культуры.
if (input.ToLower() == "admin")
{
GrantAccess();
}
if (string.Equals(input, "admin",
StringComparison.OrdinalIgnoreCase))
{
GrantAccess();
}
bool json = contentType.StartsWith(
"application/json", StringComparison.OrdinalIgnoreCase);
Почему Count() в LINQ может сделать лишнюю работу?
В C# часто нужно проверить, есть ли в последовательности хотя бы один элемент. Иногда для этого пишут так:
if (items.Count() > 0)
{
Console.WriteLine("not empty");
}
var items = GetRows()
.Where(row => row.IsActive);
if (items.Any())
{
Console.WriteLine("not empty");
}
if (users.Count > 0)
{
Send(users);
}
var activeCount = users.Count(u => u.IsActive);
Шпаргалка по DateTime и форматам дат в C#!
Например, yyyy отвечает за год, MM за месяц, dd за день, HH за часы в 24-часовом формате, а zzz показывает смещение часового пояса.
На картинке собраны частые преобразования дат и времени. Есть Parse, ParseExact, ToString с форматами, Unix timestamp, DateTimeOffset и примеры конвертации между строками, DateTime и DateTimeOffset.
Сохрани, чтобы не потерять!
➡️ C# Ready | #совет
Почему record в C# не делает объект полностью неизменяемым?
record часто воспринимают как “immutable-класс”:
public record User(string Name, List<string> Roles);
var user = new User("Ann", new List<string>());user.Roles.Add("admin");public record Profile(string Name, List<string> Permissions);
var roles = new List<string> { "user" };
var profile = new Profile("Ann", roles);
roles.Add("admin");public record Profile(
string Name,
IReadOnlyList<string> Permissions
);
var profile = new Profile(
"Ann",
roles.ToArray()
);
Полезная статья про бенчмарки в одном C# файле!
Автор показывает, как запускать BenchmarkDotNet без полноценного проекта. Идея особенно удобна для маленьких проектов, когда нужно быстро проверить информацию по производительности.
В статье автор показывает:
• как работают однофайловые приложения в .NET 10
• почему BenchmarkDotNet не стартует из коробки
• как использовать in-process режим и какие у него ограничения
Продолжай читать на Habr
Собираем простой таймер в C#!
Иногда нужно запустить действие через задержку: показать уведомление, выполнить повторную проверку или отложить сохранение. Но важно уметь отменить задачу, если она уже не нужна.
Создадим CancellationTokenSource:
using var cts = new CancellationTokenSource();
static async Task RunLater(
TimeSpan delay,
Func<Task> action,
CancellationToken token)
{
await Task.Delay(delay, token);
await action();
}
var task = RunLater(
TimeSpan.FromSeconds(3),
() => SaveAsync(),
cts.Token
);
cts.Cancel();
try
{
await task;
}
catch (OperationCanceledException)
{
Console.WriteLine("Задача отменена");
}
cts.Cancel();
using var nextCts = new CancellationTokenSource();
await RunLater(
TimeSpan.FromSeconds(1),
() => SaveAsync(),
nextCts.Token
);
Ограничиваем параллельные запросы через SemaphoreSlim!
Иногда нужно обработать много элементов асинхронно, но не запускать всё сразу. Например, нельзя одновременно ударить сотнями запросов по API или базе.
Наивный вариант запускает все задачи сразу:
var tasks = urls.Select(DownloadAsync);
await Task.WhenAll(tasks);
var gate = new SemaphoreSlim(4);
await gate.WaitAsync();
try
{
await DownloadAsync(url);
}
finally
{
gate.Release();
}
var tasks = urls.Select(ProcessAsync);
await Task.WhenAll(tasks);