C# Portal | Программирование
15.1K subscribers
929 photos
111 videos
24 files
771 links
Присоединяйтесь к нашему каналу и погрузитесь в мир для C#-разработчика

Связь: @devmangx

РКН: https://clck.ru/3FocB6
Download Telegram
В .NET считается хорошей практикой задавать размер List<T> при создании. И у меня есть бенчмарк, который это подтверждает.

Если добавить в список 10 миллионов элементов, то версия с заранее заданной емкостью работает примерно на 60% быстрее, чем список, созданный через конструктор по умолчанию.

Проблема на поверхности простая:

- List<T> внутри использует обычный массив
- по умолчанию Capacity = 0

То есть новый экземпляр List<T> всегда стартует с нуля.

Что происходит, когда список заполняется? Создается новый массив!
Когда элементы добавляются, а внутренний массив уже заполнен:

a) создается новый массив (всплеск использования памяти)
b) старые данные копируются в новый массив (лишние операции)

Получается довольно неприятный цикл.

Избежать этого можно, если создавать List<T> сразу с емкостью, примерно соответствующей ожидаемому количеству элементов.

Бенчмарк — добавление 10 миллионов элементов в список.

Тестировались три варианта:

- Capacity = 10_000_000 (равен ожидаемому количеству)
- Capacity = 5_000_000 (половина ожидаемого)
- Capacity = 0 (по умолчанию)

Если за базовую точку взять Capacity = 0:

- при Capacity = 50% — прирост скорости 48%
- при Capacity = 100% — прирост скорости 53%

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2363🤔3🤯3
Майкрософт выпустил второй и последний релиз-кандидат .NET 10, который поставляется с лицензией go-live и поэтому уже может использоваться в production. Этот выпуск .NET 10 добавляет главным образом улучшения и исправления уже имеющегося функционала и поддерживается в новом выпуске Visual Studio 2026 Insiders, а также в Visual Studio Code с C# Dev Kit.

Окончательный релиз .NET 10 с C# 14 состоится 11 ноября.

https://devblogs.microsoft.com/dotnet/dotnet-10-rc-2/

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🔥3👏2
Есть вещи в C#, которые настолько базовые для современного .NET, что игнорировать их почти преступление. Это не модные фишки, а практичные инструменты, по которым легко отличить код новичка от кода опытного разработчика.

1. Проверка зависимостей на старте

Одна из типичных проблем .NET-приложений — когда сервис вроде бы создаётся без ошибок, но позже падает из-за недопустимых значений времени жизни или отсутствующих зависимостей. Такое можно ловить заранее.

Как делает новичок:

builder.Services.AddScoped<IMyService, MyService>();
// Без проверки


Ошибка проявится только при первом же запросе.

Как делает опытный:

var builder = Program.CreateHostBuilder(args);
builder.Services.AddScoped<IMyService, MyService>();
builder.Host.UseDefaultServiceProvider((context, options) =>
{
options.ValidateOnBuild = true;
options.ValidateScopes = true;
});


Эти настройки заставляют контейнер проверить конфигурацию при сборке приложения. ValidateScopes выявляет, например, внедрение scoped-сервиса в singleton, а ValidateOnBuild — пробует создать сервисы ещё до запуска. Ошибки проявятся сразу, а не в проде.

2. Осознанное использование областей действия

Многие просто лепят всё в Transient, не задумываясь, что это вообще значит. А время жизни сервисов — это не синтаксис, это логика памяти приложения.

Типичный пример:

builder.Services.AddTransient<UserSession>();


Без понимания, когда объект должен жить и когда исчезать.

Что знает опытный разработчик:

- Transient – новый экземпляр каждый раз,
- Scoped – один экземпляр на запрос/скоуп DI,
- Singleton – один экземпляр на всё время жизни приложения.

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

using var scope = serviceProvider.CreateScope();
var scopedService = scope.ServiceProvider.GetRequiredService<IScopedThing>();


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

3. DateTimeOffset вместо DateTime

Новички часто спотыкаются на часовых поясах и летнем времени. Всё вроде работает... пока не перестаёт.

Плохой вариант:

var orderTime = DateTime.Now; // локальное время


В другом часовом поясе это может поехать.

Хороший вариант:

var orderTime = DateTimeOffset.Now;


DateTimeOffset хранит не только момент времени, но и смещение. Это идеально для логов, транзакций и расписаний. Храните в UTC, показывайте пользователю в его часовом поясе.

4. Алиасы для пространств имён и типов

Длинные неймспейсы и вложенные типы быстро превращают код в кашу. Но немногие используют алиасы.

До:

System.Collections.Generic.Dictionary<System.Tuple<string, int>, List<MyNamespace.Models.ComplexThing>> myMap;


После:

using ComplexMap = System.Collections.Generic.Dictionary<(string, int), List<ComplexThing>>;


Алиасы — это не просто сокращение, это способ упростить чтение и сделать код гибче. При смене библиотеки или рефакторинге правите одну строчку и всё работает.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥21👍65
Новичок в C#: «Когда стоит использовать static-класс?»

NPC-ответ: «Когда тебе не нужен экземпляр».

Формально верно, но бесполезно. Такой ответ помогает только самооценке того, кто его дал.

На практике всё проще и понятнее:

Используй обычный класс, когда хочешь смоделировать систему, чтобы её было легче понять и использовать.
Система это набор связанных данных и операций над ними.
Например, табло очков (scoreboard): у него есть данные (очки, имена, время), и есть поведение (обновить счёт, сбросить, вывести). Ценность в целостности системы, а не в отдельном поле.

Если ты не моделируешь систему, то обычный класс может быть полезен ещё и ради dependency injection, когда нужно, чтобы внешний код управлял зависимостями, временем жизни и контекстом. Это упрощает тестирование и структуру проекта.

Но если ты не моделируешь и не внедряешь зависимости, тогда используй static-класс.
Всё, что ты делаешь это, скорее всего, утилитарная операция, которая не зависит от состояния и может применяться к любым данным.

Честно говоря, можно было бы даже сказать, что static-классы должны быть дефолтом, а instance использовать только тогда, когда действительно есть повод включать ООП.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍17👎2😁1
Встраивать бизнес-логику прямо в SQL-запросы?

Это верный способ превратить код в нечто нетестируемое и хрупкое.

Представь, ты делаешь поиск по инвойсам. Нужно фильтровать по:

- номеру инвойса
- имени клиента
- email
- статусу заказа
- названию продукта

И всё это в любых комбинациях.

Самое быстрое, но грязное решение — напихать всю эту логику прямо в SQL-запрос.
И вот теперь все твои бизнес-правила оказались заперты внутри базы.
Тестировать их без реальной базы уже не выйдет.

Альтернатива — Specification Pattern.

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

Как это работает:

Создаёшь класс спецификации, который описывает критерии поиска.

Он принимает фильтры и строит выражение (например, LINQ).

Потом передаёшь эту спецификацию в репозиторий или обработчик запросов.

Этот подход отлично подходит для фильтрации и сложных запросов.

Результат:

- тесты запускаются быстрее
- бизнес-логика понятнее
- код запросов проще поддерживать

Specification Pattern это про то, чтобы держать логику в месте, где её можно понять, протестировать и не проклинать через полгода. 🔫

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
8🔥3👍2
Please open Telegram to view this post
VIEW IN TELEGRAM
31😁20👍6😈1
На Хабре вышла статья о том, как с помощью EF Core и PostgreSQL избежать deadlock и oversell при работе со стоками. Автор разбирает подход с сортировкой обновлений по ключам, использованием ExecuteUpdateAsync, повторными попытками (retry), триггерами, очередями и батчами.

В статье есть бенчмарки, примеры кода и ссылка на репозиторий на GitHub.

Читать подробнее: habr.com/ru/articles/955714/

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍72
В превью C# 14 появились partial-конструкторы и partial-события.

Каждый из них должен состоять ровно из одного объявления-определения и одного объявления-реализации.

Реализация partial-события обязана содержать add и remove аксессоры.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👎8👍4🤔2
На GitHub появился классный репозиторий с чётко структурированным материалом по system design. В нём собраны:

- основы проектирования крупных систем,
- типичные вопросы и решения для собеседований,
- карточки Anki для запоминания ключевых концепций.

Сохрани, чтобы не потерять: https://github.com/donnemartin/system-design-primer

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍83
Ты правильно называешь свои DTO?

Какую схему именования выбрать

Когда ты делаешь Web API, твои эндпоинты принимают и отдают данные.

Частая практика — добавлять к таким моделям суффикс Dto

Но вот в чём проблема:

DTO часто смешивают входные и выходные данные в одном классе.
Со временем такие классы разрастаются и становятся непонятными.

Мой вариант: использовать суффиксы Request и Response вместо Dto:

• CreateUserRequest —> для входных данных
• UserResponse —> для выходных

Почему так лучше:

Чёткое назначение —> сразу видно, модель для входа или для выхода.
Масштабируемость —> изменения в Response не ломают Request.
Поддерживаемость —> не надо гадать, что вообще делает UserDto.

Совет: выбери один подход и используй его последовательно во всём проекте.

А ты как называешь свои модели Request/Response или Dto? Делись опытом

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
10👍8🤣3🔥2
Деревья выражений в C# помогают создавать быстрые и эффективные методы преобразования данных, ускоряя работу с объектами и запросами в Entity Framework Core. В статье раскрываются механизмы мэппинга и построения сложных фильтров через IQueryable.

Читать подробнее: https://habr.com/ru/articles/932110/

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍43
Экономим память как профи: 5 продвинутых техник

Если вы работаете с .NET, то знаете, под капотом куча мощных инструментов, про которые редко говорят. Вот 5 API, которые реально экономят память и ускоряют код.

1. CollectionsMarshal.AsSpan() — прямой доступ к списку

Обычно List<T> это удобная, но безопасная обёртка над массивом.
Если нужен Span без копирования:

using System.Runtime.InteropServices;

var numbers = new List<int> { 1, 2, 3, 4, 5 };
Span<int> span = CollectionsMarshal.AsSpan(numbers);


Работает напрямую с внутренним массивом списка, без ToArray().
Важно: при изменении размера списка Span станет недействительным.
Когда использовать: большие буферы, короткие циклы.
Выигрыш: до 2 раз меньше памяти.

2. CollectionsMarshal.GetValueRefOrNullRef() — доступ к словарю без двойного поиска

Обычный способ вызывает поиск ключа дважды:

if (dict.TryGetValue("foo", out var value)) {
value++;
dict["foo"] = value;
}


Лучше так:

using System.Runtime.InteropServices;

ref int valueRef = ref CollectionsMarshal.GetValueRefOrNullRef(dict, "foo");
if (!Unsafe.IsNullRef(ref valueRef))
valueRef++;


Получаем ссылку прямо на значение без повторного поиска.
Когда использовать: большие словари, горячие циклы.
Выигрыш: примерно в 2 раза быстрее.

3. GC.AllocateUninitializedArray<T>() — массив без обнуления

.NET по умолчанию обнуляет массив:

int[] arr = new int[1000];


Если вы всё равно перезаписываете элементы - то это лишняя работа.
Быстрее:

int[] arr = GC.AllocateUninitializedArray<int>(1000);
for (int i = 0; i < arr.Length; i++) arr[i] = i;


Когда использовать: если массив заполняется сразу.
Выигрыш: ~15% быстрее на больших массивах.

4. ArrayPool<T>.Shared и IMemoryOwner — аренда массивов

Постоянные аллокации убивают GC:

for (int i = 0; i < 1000; i++) {
var buffer = new byte[1024];
DoSomething(buffer);
}


Используем пул:

using System.Buffers;

for (int i = 0; i < 1000; i++) {
using IMemoryOwner<byte> owner = MemoryPool<byte>.Shared.Rent(1024);
DoSomething(owner.Memory.Span);
}


Когда использовать: сетевые серверы, конвейеры, потоковые операции.
Выигрыш: до 1000 раз меньше аллокаций.

5. ObjectPool<T> — переиспользование дорогих объектов

Создание тяжёлых объектов вроде StringBuilder дорого:

for (int i = 0; i < 100; i++) {
var sb = new StringBuilder(1024);
sb.Append("Hello ").Append(i);
Console.WriteLine(sb.ToString());
}


Пулим:

using Microsoft.Extensions.ObjectPool;

var provider = new DefaultObjectPoolProvider();
var pool = provider.CreateStringBuilderPool();

for (int i = 0; i < 100; i++) {
var sb = pool.Get();
sb.Clear();
sb.Append("Hello ").Append(i);
Console.WriteLine(sb.ToString());
pool.Return(sb);
}


Когда использовать: логирование, сериализация, временные буферы.
Выигрыш: до 4 раз быстрее, в 50 раз меньше памяти.

Если вы когда-нибудь заглядывали в свой профилировщик и задавались вопросом, почему так много времени тратится на сборку мусора или копирование памяти, эти приёмы помогут вам это исправить. Они требуют аккуратности, но дают настоящую производительность.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
12👍9
Rider 2025.3 EAP 6 уже доступен!

В этом билде появилась поддержка ASP. NET и обнаружения проблем с базой данных прямо в окне Monitoring 🔥

Теперь, когда ты запускаешь или отлаживаешь приложение, мониторинг автоматически определяет:

Медленные или чрезмерно частые запросы к БД
Слишком большие результаты выборок
Медленные действия MVC или обработчики Razor-страниц
…и многое другое

Подробнее — здесь

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
14
This media is not supported in your browser
VIEW IN TELEGRAM
Задумывался, почему приложение ощущается «тормозным», хотя с пропускной способностью вроде всё ок? Дело в том, что latency и throughput описывают два совершенно разных аспекта производительности.

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

Throughput — это объём данных в секунду. Не скорость отдельного пакета, а то, сколько пакетов проходит через «трубу» за определённый промежуток времени. Throughput — это ёмкость системы. Высокий throughput означает, что система справляется с нагрузкой, не захлёбываясь.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥63👏2
C# и LINQ = функциональный коктейль для кодинга

Если объяснить максимально просто — функциональный стиль позволяет писать код так, как ты о нём говоришь.

«Отфильтровать пользователей старше 30»
«Сгруппировать пользователей по школе»
«Показать все имена пользователей»

В отличие от процедурного подхода, где нужно думать и о том, что делать, и как именно это сделать — управлять переменными, добавлять и удалять элементы из списков, сортировать результаты и так далее.

В LINQ больше двух десятков операторов, но чтобы начать, достаточно пары базовых:

Select — выбрать нужные данные
Where — отфильтровать записи
Any — проверить, существует ли хотя бы один элемент
GroupBy — сгруппировать совпадающие элементы
ToList — собрать результат в List<T>
ToDictionary — собрать результат в Dictionary<K,V>

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥222
Одно небольшое изменение = запрос в 400 раз быстрее

Вот что было сделано, чтобы добиться такого прироста.

Реализовывалась пагинация с курсором через EF и Postgres.

Но нужно было сделать запрос быстрее.

Как ускорить SQL-запрос?

Добавить индекс, и всё должно полететь.

Так ведь?

Не совсем…

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

НО ЗАПРОС СТАЛ МЕДЛЕННЕЕ!!!

Пришлось разбираться, почему индекс не используется.

Оказалось, что дело в сравнении кортежей — это и решило проблему.

Но непонятно было, как перенести это в запрос EF Core.

К счастью, провайдер Postgres поддерживает это через кастомную функцию.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7👍3
Настройка batch size в Entity Framework

По умолчанию для провайдера SQL Server batch size равен 42.
Это значит, что при вставке 100 строк произойдёт 5 отдельных запросов к базе.

Я недавно гонял простой бенчмарк в .NET, и вот что получилось:
даже если уменьшить количество раунд-трипов, batch size 1000 оказался самым медленным, а вот 200 показал себя лучше всего — некий "sweet spot".

Вывод простой: тестируйте на своих сценариях.
Оптимальный размер batch может отличаться в зависимости от типа нагрузки и объёмов данных.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
1
В свежей статье «Обзор нововведений в C# 14» собрали ключевые фичи новых версий C#:
появилось ключевое слово field, добавили модификаторы в лямбдах, перегрузки присваиваний, extension-свойства, неявные преобразования к Span и даже partial-конструкторы.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10👍4