Почему record в C# не делает объект полностью неизменяемым?
record часто воспринимают как “immutable-класс”:
Но неизменяемым становится не всё содержимое объекта.
Свойство Roles нельзя просто заменить, если оно init-only:
Но сам список внутри всё ещё изменяемый:
То есть record защищает ссылку на список, но не защищает сам список от изменений.
Это особенно важно, если объект передаётся между слоями приложения:
Код снаружи может сохранить ссылку и позже поменять содержимое:
Если нужен более безопасный вариант, лучше принимать IReadOnlyList:
А внутри создавать копию:
Для публичных моделей это часто снижает риск случайных мутаций.
record даёт удобное сравнение и init-свойства, но не делает вложенные коллекции immutable. Изменяемые поля всё равно нужно контролировать отдельно.
➡️ C# Ready | #совет
record часто воспринимают как “immutable-класс”:
public record User(string Name, List<string> Roles);
Но неизменяемым становится не всё содержимое объекта.
Свойство Roles нельзя просто заменить, если оно init-only:
var user = new User("Ann", new List<string>());Но сам список внутри всё ещё изменяемый:
user.Roles.Add("admin");То есть record защищает ссылку на список, но не защищает сам список от изменений.
Это особенно важно, если объект передаётся между слоями приложения:
public record Profile(string Name, List<string> Permissions);
Код снаружи может сохранить ссылку и позже поменять содержимое:
var roles = new List<string> { "user" };
var profile = new Profile("Ann", roles);
roles.Add("admin");Если нужен более безопасный вариант, лучше принимать IReadOnlyList:
public record Profile(
string Name,
IReadOnlyList<string> Permissions
);
А внутри создавать копию:
var profile = new Profile(
"Ann",
roles.ToArray()
);
Для публичных моделей это часто снижает риск случайных мутаций.
record даёт удобное сравнение и init-свойства, но не делает вложенные коллекции immutable. Изменяемые поля всё равно нужно контролировать отдельно.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤3🔥2
This media is not supported in your browser
VIEW IN TELEGRAM
Здесь собраны полезные материалы для обучения, практики и более глубокого изучения подходов разработки на .NET. Репозиторий помогает не просто изучать отдельные технологии, а выстраивать целостное понимание платформы. Такой формат удобен и новичкам, и разработчикам с опытом, которые хотят структурировать знания или закрыть пробелы.
Оставляю ссылочку: GitHub📱
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9❤6🔥4
Шпаргалка по DateTime и форматам дат в C#!
Например, yyyy отвечает за год, MM за месяц, dd за день, HH за часы в 24-часовом формате, а zzz показывает смещение часового пояса.
На картинке собраны частые преобразования дат и времени. Есть Parse, ParseExact, ToString с форматами, Unix timestamp, DateTimeOffset и примеры конвертации между строками, DateTime и DateTimeOffset.
Сохрани, чтобы не потерять!
➡️ C# Ready | #совет
Например, yyyy отвечает за год, MM за месяц, dd за день, HH за часы в 24-часовом формате, а zzz показывает смещение часового пояса.
На картинке собраны частые преобразования дат и времени. Есть Parse, ParseExact, ToString с форматами, Unix timestamp, DateTimeOffset и примеры конвертации между строками, DateTime и DateTimeOffset.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤3🔥3👎2
Разбираем File и Path 7 инструментов для работы с файлами!
В C# для повседневной работы с файлами чаще всего хватает стандартных классов File, Directory и Path. Они закрывают проверку существования, чтение, запись, создание папок, обход директорий и безопасную сборку путей.
В этой шпоре собраны File.Exists, ReadAllText, WriteAllText, Directory.CreateDirectory, Directory.EnumerateFiles, Path.Combine и Path.GetExtension.
➡️ C# Ready | #совет
В C# для повседневной работы с файлами чаще всего хватает стандартных классов File, Directory и Path. Они закрывают проверку существования, чтение, запись, создание папок, обход директорий и безопасную сборку путей.
В этой шпоре собраны File.Exists, ReadAllText, WriteAllText, Directory.CreateDirectory, Directory.EnumerateFiles, Path.Combine и Path.GetExtension.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7❤3🤝3👍1
Почему Count() в LINQ может сделать лишнюю работу?
В C# часто нужно проверить, есть ли в последовательности хотя бы один элемент. Иногда для этого пишут так:
Для List<T> это может выглядеть безобидно, но LINQ работает не только со списками. Если items является IEnumerable<T>, Count() может пройти всю последовательность до конца.
На ленивых источниках это особенно неприятно:
Проверка через Count() заставит перебрать все подходящие элементы, хотя для ответа достаточно первого.
Когда нужен только факт наличия элемента, лучше использовать Any():
Any() останавливается сразу после первого найденного элемента. Это проще по смыслу и часто дешевле по выполнению.
Если коллекция уже лежит в памяти как List<T>, массив или HashSet<T>, можно использовать свойство:
А если действительно нужно точное количество, тогда Count() уместен:
Для вопроса есть ли что-то используй Any(), для точного числа используй Count(), а для готовых коллекций смотри на свойство Count или Length.
➡️ C# Ready | #совет
В C# часто нужно проверить, есть ли в последовательности хотя бы один элемент. Иногда для этого пишут так:
if (items.Count() > 0)
{
Console.WriteLine("not empty");
}
Для List<T> это может выглядеть безобидно, но LINQ работает не только со списками. Если items является IEnumerable<T>, Count() может пройти всю последовательность до конца.
На ленивых источниках это особенно неприятно:
var items = GetRows()
.Where(row => row.IsActive);
Проверка через Count() заставит перебрать все подходящие элементы, хотя для ответа достаточно первого.
Когда нужен только факт наличия элемента, лучше использовать Any():
if (items.Any())
{
Console.WriteLine("not empty");
}
Any() останавливается сразу после первого найденного элемента. Это проще по смыслу и часто дешевле по выполнению.
Если коллекция уже лежит в памяти как List<T>, массив или HashSet<T>, можно использовать свойство:
if (users.Count > 0)
{
Send(users);
}
А если действительно нужно точное количество, тогда Count() уместен:
var activeCount = users.Count(u => u.IsActive);
Для вопроса есть ли что-то используй Any(), для точного числа используй Count(), а для готовых коллекций смотри на свойство Count или Length.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9❤5🔥4
This media is not supported in your browser
VIEW IN TELEGRAM
Здесь собраны вопросы с развернутыми ответами и примерами кода по C# и .NET, ООП, алгоритмам и структурам данных, архитектуре, базам данных и многопоточности. Дополнительная тренировка, чтобы системно повторить теорию, проверить пробелы и подготовиться к технической части собесов.
Оставляю ссылочку: GitHub📱
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤4🔥3
Почему ToLower() перед сравнением строк может быть лишним?
Когда нужно сравнить две строки без учёта регистра, часто сначала приводят обе к нижнему регистру. Код работает, но создаёт новые строки и может зависеть от текущей культуры.
Для имён ролей, HTTP-заголовков, ключей конфигурации и технических идентификаторов обычно нужен предсказуемый ordinal-сравнитель.
Этот вариант не создаёт две временные строки и сразу показывает намерение. Он сравнивает символы без правил конкретного языка пользователя.
Особенно важно не использовать ToLower или ToUpper при проверке ключей и значений.
Для отображаемого пользователю текста иногда действительно нужен CurrentCultureIgnoreCase. Но для кода и протоколов чаще всего выбирают OrdinalIgnoreCase.
➡️ C# Ready | #совет
Когда нужно сравнить две строки без учёта регистра, часто сначала приводят обе к нижнему регистру. Код работает, но создаёт новые строки и может зависеть от текущей культуры.
if (input.ToLower() == "admin")
{
GrantAccess();
}
Для имён ролей, HTTP-заголовков, ключей конфигурации и технических идентификаторов обычно нужен предсказуемый ordinal-сравнитель.
if (string.Equals(input, "admin",
StringComparison.OrdinalIgnoreCase))
{
GrantAccess();
}
Этот вариант не создаёт две временные строки и сразу показывает намерение. Он сравнивает символы без правил конкретного языка пользователя.
Особенно важно не использовать ToLower или ToUpper при проверке ключей и значений.
bool json = contentType.StartsWith(
"application/json", StringComparison.OrdinalIgnoreCase);
Для отображаемого пользователю текста иногда действительно нужен CurrentCultureIgnoreCase. Но для кода и протоколов чаще всего выбирают OrdinalIgnoreCase.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9🔥5❤4👎1
Как в C# не забыть про ConfigureAwait(false)?
Когда пишешь библиотечный async-код, часто встречается такой вариант:
Для приложения это обычно нормально. Но для библиотек и низкоуровневого кода часто лучше явно сказать, после await не нужно возвращаться в исходный context.
Для этого используют ConfigureAwait(false):
После await продолжение может выполниться на любом подходящем потоке:
Это особенно полезно в shared-библиотеках, SDK, инфраструктурном коде и клиентах к внешним API.
Например:
Но в UI-коде всё иначе. Если после await нужно обновить интерфейс, context может быть нужен:
В таком случае ConfigureAwait(false) может увести продолжение с UI-потока.
➡️ C# Ready | #совет
Когда пишешь библиотечный async-код, часто встречается такой вариант:
var json = await httpClient.GetStringAsync(url);
return JsonSerializer.Deserialize<User>(json);
Для приложения это обычно нормально. Но для библиотек и низкоуровневого кода часто лучше явно сказать, после await не нужно возвращаться в исходный context.
Для этого используют ConfigureAwait(false):
var json = await httpClient
.GetStringAsync(url)
.ConfigureAwait(false);
После await продолжение может выполниться на любом подходящем потоке:
return JsonSerializer.Deserialize<User>(json);
Это особенно полезно в shared-библиотеках, SDK, инфраструктурном коде и клиентах к внешним API.
Например:
public async Task<User> 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-потока.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6🔥5👍3
Blazor против Vue.js глазами C# разработчика!
Автор сравнивает два frontend-подхода на опыте реальных проектов. Статья показывает, что будет привычным, а что неожиданным для разработчика, который приходит в интерфейсы из C# и .NET.
В статье разбирают:
• как ощущается компонентная разработка в Blazor и Vue
• где C# стек даёт сильную сторону на frontend
• какие различия важнее синтаксиса и шаблонов
➡️ C# Ready | #статья
Автор сравнивает два frontend-подхода на опыте реальных проектов. Статья показывает, что будет привычным, а что неожиданным для разработчика, который приходит в интерфейсы из C# и .NET.
В статье разбирают:
• как ощущается компонентная разработка в Blazor и Vue
• где C# стек даёт сильную сторону на frontend
• какие различия важнее синтаксиса и шаблонов
Продолжай читать на Habr
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤3🔥3👎1
Шпаргалка по поколениям сборщика мусора в .NET!
На картинке показаны поколения управляемой кучи, продвижение выживших объектов и три основные фазы GC. Mark находит достижимые объекты, sweep освобождает мусор, а compact уменьшает фрагментацию памяти.
Например, Gen 0 хранит новые короткоживущие объекты, Gen 1 служит промежуточной зоной, а Gen 2 содержит долгоживущие данные. Объекты крупнее 85 КБ обычно попадают в Large Object Heap.
Сохрани, чтобы не потерять!
➡️ C# Ready | #ресурс
На картинке показаны поколения управляемой кучи, продвижение выживших объектов и три основные фазы GC. Mark находит достижимые объекты, sweep освобождает мусор, а compact уменьшает фрагментацию памяти.
Например, Gen 0 хранит новые короткоживущие объекты, Gen 1 служит промежуточной зоной, а Gen 2 содержит долгоживущие данные. Объекты крупнее 85 КБ обычно попадают в Large Object Heap.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3👍3🔥3