C# Portal | Программирование
13.3K subscribers
1.28K photos
131 videos
31 files
996 links
Присоединяйтесь к нашему каналу и погрузитесь в мир для C#-разработчика

Сотрудничество, реклама: @devmangx

Менеджер: @Spiral_Yuri

РКН: https://clck.ru/3FocB6
Download Telegram
Ты не делаешь integration tests, если тестируешь через in-memory database.

В лучшем случае это раздутый unit test.

Я видел кучу примеров с EF Core in-memory provider.

Но это не integration test, потому что настоящей базы данных там нет.

Хуже того, такие тесты не поймают баги в LINQ или SQL.

Лучший подход:

- использовать настоящую базу данных или Docker-контейнер
- подключаться к этой базе из тестов
- писать нормальные integration tests, от которых есть польза

Если хочешь использовать Docker, посмотри в сторону Testcontainers.

Он позволяет поднимать одноразовые контейнеры прямо из тестов.

А какие инструменты или подходы вы используете для integration testing?

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
4🔥1🍾1
Как устроены логирование и мониторинг в Docker

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1🍾1
Ваш lock в C# работает идеально.

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

В итоге два воркера могут одновременно:
→ запустить одну и ту же scheduled task
→ обновить один и тот же cache entry
→ обработать один shared resource
→ сгенерировать один и тот же отчёт
→ одновременно записать данные

А дальше начинается веселье: дубли, лишняя нагрузка, гонки и иногда сломанные данные.
Для этого и нужны distributed locks.

Они дают нескольким инстансам одно общее правило:
только один из вас делает эту работу прямо сейчас.
Не надо тащить это везде. Но если job должна выполниться один раз, или shared work не должен пересекаться, distributed lock может сэкономить много боли.

Если у вас уже есть PostgreSQL, можно начать с advisory locks.
А если нужен более чистый вариант поверх Postgres, Redis или SQL Server, можно взять готовую distributed locking library.
На одном сервере приложение может выглядеть нормально.
Настоящая проверка начинается со второго инстанса.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11🍾2😐1
Если вы программируете на Windows, стоит обратить внимание на Windows Developer Config от Microsoft.

Инструмент позволяет подготовить машину для разработки одной командой.

Что умеет:

✓ Настраивает WSL и Ubuntu
✓ Устанавливает Windows Terminal
✓ Ставит Node.js, Python, Rust, Go, Java, .NET, PHP и другие инструменты
✓ Автоматизирует настройку рабочего окружения

Проект полностью открытый и доступен на GitHub.

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

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🤣52🍾1
Фича дня в EF Core: алгоритм Hi/Lo.
С его помощью можно генерировать идентификаторы на стороне приложения и реже обращаться к базе данных.

Как это работает:
Вместо запроса нового ID для каждой записи приложение получает сразу диапазон идентификаторов.
База хранит значение hi_value, которое указывает на верхнюю границу текущего диапазона.
При запросе нового диапазона используется следующий блок ID, начиная с hi_value + 1.

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

В EF Core достаточно вызвать метод UseHiLo(). После этого EF Core создаст в базе последовательность и будет получать диапазоны идентификаторов автоматически. Дальше первичные ключи могут назначаться прямо на стороне клиента.
Особенно удобно при сохранении связанных сущностей, например родительских и дочерних записей.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🍾4
Большинство разработчиков неправильно понимают DRY.

Они думают, что DRY про устранение дублирующегося кода.

На самом деле DRY про дублирование знаний.

Повторяющийся код часто оказывается лишь симптомом. Настоящая проблема начинается тогда, когда одно и то же бизнес-правило разбросано по всей системе.

Если одно правило живёт в пяти местах, любое изменение становится рискованным. Исправили одно место, забыли про остальные четыре.

Где команды обычно ошибаются:

Слишком рано выносят код в общие хелперы

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

Связывают между собой несвязанные части системы ради борьбы с дублированием

В итоге код становится сложнее.

Иногда дублирование вполне нормально.

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

DRY нарушается тогда, когда одна и та же причина для изменения присутствует в нескольких местах.

Примеры реальных нарушений DRY:

→ Правила валидации, скопированные между контроллерами

→ Логика ценообразования, продублированная в сервисах и фоновых задачах

→ Проверки авторизации, разбросанные по разным слоям приложения

А вот такие случаи обычно не проблема:

→ Похожие циклы, выполняющие разные задачи

→ Повторяющиеся маппинги рядом с местом использования

→ Небольшие дублирующиеся SQL или EF Core запросы

Неправильное понимание DRY часто приводит к «энтерпрайзному» коду:

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

Если один и тот же EF Core запрос из пары строк встречается в двух классах, это ещё не повод тащить в проект Repository.

Лично я придерживаюсь простого правила:

Если мне приходится копировать одну и ту же логику в третий раз, тогда стоит задуматься о выносе в отдельное место. До этого момента никакого преждевременного рефакторинга.

Когда в следующий раз увидите дублирование, задайте себе вопрос:

«Это действительно одно и то же знание или просто похожий код?»

От ответа зависит архитектура всей системы.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🍾2
Завязал с Clean Architecture.

Перешёл на Vertical Slices. И назад пока не тянет

Чтобы добавить один endpoint, приходится лазить по 4 проектам и 5 слоям
Controllers, Services, Repositories, DTOs — папок больше, чем логики
Любая мелкая правка расползается по всему решению
AI-агенты сжигают токены, пока добираются до нужной фичи

Vertical Slice Architecture.

Весь код фичи лежит в одной папке.

Что это даёт:

1. Фича = папка

Для Create Shipment:

→ CreateShipment.Endpoint.cs
→ CreateShipment.Handler.cs
→ CreateShipment.Mapping.cs
→ CreateShipment.Validators.cs

Нужно изменить фичу — открываешь одну папку и работаешь.

2. В 2026 маленькие классы реально решают

Про это почему-то редко говорят в архитектурных статьях.

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

3. Clean Architecture никто не отменял

Я не говорю, что она плохая.

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

4. Между слайсами всё равно нужны границы

VSA — не про «пусть все вызывают всех».

В модульном монолите я обычно выделяю отдельный PublicApi-проект для каждого модуля:

→ Снаружи видны только интерфейсы и DTO
→ Реализация остаётся скрытой через internal sealed
→ Если модуль однажды переедет в отдельный сервис, контракт менять не придётся

5. Побочные эффекты через события

Если Create Shipment должен обновить остатки и уведомить перевозчика, handler не дёргает их напрямую.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍83🍾2
Виды Классов в C#

Абстрактный
Базовый класс, экземпляр которого нельзя создать. Он может содержать абстрактные и неабстрактные члены и предназначен для наследования от него.
public abstract class Vehicle
{
public abstract int Wheels { get; }
public abstract void TurnOn();
public bool Started { get; protected set; }
}

Создать экземпляр класса Vehicle нельзя, поэтому унаследуем от него. При этом класс-наследник обязан переопределить абстрактные члены:
public class Car : Vehicle
{
public override int Wheels => 4;
public override void TurnOn()
=> Started = true;
}


Запечатанный (sealed)
Специальный тип класса, который ограничивает иерархию наследования. Это предотвращает создание производных типов, что повышает безопасность кода и позволяет компилятору применять оптимизации производительности.
public sealed class Vehicle
{
}


Статический
Экземпляр статического класса нельзя создать, и от статического класса нельзя унаследовать. Все члены должны быть помечены статическими.
public static class SpeedConverter
{
public static decimal ToMph(decimal kph)
=> return kph / 1.6093m;
}


Частичный
В одном и том же пространстве имён нельзя создавать несколько классов с одним именем. Частичный класс позволяет разделить объявление класса на несколько файлов. При компиляции объявления объединяются в один класс. Нельзя дублировать члены с одной сигнатурой в разных объявлениях частичного класса:
public partial class Team
{
public Team() { }
public string Name { get; set; }
}

public partial class Team
{
public int Players { get; set; }
}


Небезопасный
Небезопасный класс позволяет использовать код с указателями:
public unsafe class MemoryReader
{
public void Read(int* value)
{
Console.WriteLine(*value);
}
}

Если вам необходимо работать с указателями, нужно включить небезопасный код в файле .csproj, установив параметр AllowUnsafeBlocks в значение true.

Запись
Запись — ссылочный тип, предназначенный для данных, а не для поведения, и по умолчанию является неизменяемой:
public record class Team(string Name, int Players);


Модификаторы доступа
- public – доступен отовсюду;
- internal – доступен внутри сборки;
- private – доступен только внутри включающего его типа;
- protected – доступен внутри включающего его типа и всех его наследников;
- file – доступен только внутри содержащего его файла исходного кода.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥94🍾2😁1🤯1
This media is not supported in your browser
VIEW IN TELEGRAM
Чилловый разработчик сделал “чёрную дыру” прямо в терминале, чтобы заставить себя делать перерывы.

Чем дольше работаешь без остановки, тем больше она растёт и начинает искажать код, как гравитационная линза.
Отдохнул — она уменьшается.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥17👍81👎1🍾1
microservices-2025-roadmap.pdf
1.8 MB
roadmap по .NET-микросервисам с подборкой полезных материалов

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🍾6
Что Происходит, Когда вы Вводите google.com в Браузере?

1. Вы вводите адрес веб-сайта в адресную строку браузера.

2. Браузер сначала проверяет свой кэш. Если адрес не найден в кэше, он должен найти IP-адрес.

3. Начинается поиск DNS (представьте, что вы ищете номер телефона). Запрос проходит через различные DNS-серверы (корневой, TLD - сервер имен доменов верхнего уровня - и авторитативный). Наконец, извлекается IP-адрес.

4. Браузер инициирует TCP-соединение, начиная с рукопожатия. Например, в случае HTTP 1.1 клиент и сервер выполняют трёхстороннее TCP-рукопожатие с сообщениями SYN, SYN-ACK и ACK.

5. Браузер отправляет HTTP-запрос на сервер, и сервер отвечает файлами HTML, CSS и JS.

6. Браузер обрабатывает ответ. Он анализирует HTML-документ и создаёт деревья DOM и CSSOM.

7. Браузер выполняет код JavaScript и отображает страницу, проходя через различные этапы (токенизатор, парсер, дерево рендеринга, компоновка и отрисовка).

8. Веб-страница появляется на экране.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🔥43🍾1
Вышел Rider 2026.2 EAP 6.

В этом превью JetBrains улучшили отображение асинхронных стеков вызовов во время отладки.
Теперь отладчик скрывает часть шума от сгенерированных кадров, связанных с async/await и инфраструктурой Task, поэтому проще отслеживать реальный поток выполнения приложения, не продираясь через детали реализации, добавленные компилятором.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥13👍5🤔3🍾2
Небольшой совет по написанию чистого кода:

Заменяйте сложные if-условия методами с понятными названиями.
Сложные if-ы тяжело читать.

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

→ Вынесите условие в метод или переменную с говорящим именем.

Теперь само название объясняет, что именно проверяется.
Людям гораздо проще читать понятные названия, чем разбирать сложные условия в коде.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👏9👍32🔥2🍾1
Виртуальная машина vs Контейнер vs Serverless

Виртуальные машины запускают полноценную операционную систему поверх виртуализированного оборудования. Они обеспечивают высокий уровень изоляции и дают больше контроля над окружением, но требуют больше ресурсов, поскольку каждая ВМ содержит собственную ОС.

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

Serverless-функции позволяют развёртывать небольшие фрагменты кода без управления базовой серверной инфраструктурой. Облачный провайдер берёт на себя выделение ресурсов, масштабирование и выполнение кода, поэтому такой подход хорошо подходит для событийно-ориентированных задач: API, задач по расписанию и обработки очередей.

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

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🍾31
Перед тем как читать новый проект, я первым делом иду не в код, а в Git.

Пара команд за 2 минуты часто рассказывает о кодовой базе больше, чем час листания файлов.

Что меняют чаще всего
git log --format=format: --name-only --since="1 year ago" | sort | uniq -c | sort -nr | head -20


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

Кто реально поддерживает проект
git shortlog -sn --no-merges


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

Где чаще всего чинят баги
git log -i -E --grep="fix|bug|broken" --name-only --format='' | sort | uniq -c | sort -nr | head -20


Если файл часто появляется и здесь, и в списке самых изменяемых файлов — это один из главных источников технического долга.

Проект набирает темп или затухает
git log --format='%ad' --date=format:'%Y-%m' | sort | uniq -c


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

Как часто приходится тушить пожары
git log --oneline --since="1 year ago" | grep -iE 'revert|hotfix|emergency|rollback'


Откаты, хотфиксы и экстренные исправления многое говорят о качестве релизного процесса.

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

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥174💯4🍾1
Вышел roadmap для SqlClient

https://github.com/dotnet/SqlClient/blob/main/roadmap.md

Стоит посмотреть, если тебе интересны:

- особенности работы пула соединений (connection pooling)
- сценарии аутентификации (Entra ID, MSI и другие)
- производительность и надёжность под высокой нагрузкой

На этом этапе обратная связь от сообщества ещё может повлиять на архитектурные и технические решения.

#dotnet #sqlserver

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🍾1
Создаём Базовый Компонент для Всех Компонентов в Blazor

При разработке Blazor-приложения может понадобиться пользовательский базовый компонент для всех остальных компонентов. Это полезно для совместного использования общих функций, таких как токены отмены, логирование или управление состоянием, во всех компонентах. Вместо добавления @inherits YourBaseComponent в каждый файл Razor, вы можете использовать файл _Imports.razor для глобальной установки базового компонента.

Создадим файл _Imports.razor в папке, к компонентам которой нужно применить базовый компонент. Все файлы Razor в этой папке и её подпапках будут наследовать указанный базовый компонент.

@inherits YourNamespace.CustomComponentBase


Файл _Imports.razor обрабатывается перед любым компонентом Razor в том же каталоге или его подкаталогах. Все компоненты затем автоматически наследуют от CustomComponentBase без необходимости объявлять @inherits в каждом файле.

Пример: CustomComponentBase с CancellationToken
Вот пример базового компонента, который предоставляет CancellationToken всем производным компонентам. Это полезно для отмены асинхронных операций при удалении компонента:

@* CustomComponentBase.razor (Razor) *@
@implements IDisposable

@code {
private readonly CancellationTokenSource
_cts = new CancellationTokenSource();

public CancellationToken
CancellationToken => _cts.Token;

public void Dispose()
{
_cts.Cancel();
_cts.Dispose();
}
}


Теперь все наши компоненты могут получить доступ к свойству CancellationToken без какой-либо дополнительной настройки:

@* MyComponent.razor (Razor) *@
@* Не нужно использовать @inherits, т.к. _Imports.razor импортируется автоматически *@

<h3>Мой компонент</h3>

@code {
protected override async Task OnInitializedAsync()
{
// Используем CancellationToken из базового компонента
await LoadDataAsync(CancellationToken);
}

private async Task LoadDataAsync(CancellationToken ct)
{
// … асинхронный код …
await Task.Delay(1000, ct);
}
}


Переопределение базового компонента для конкретных компонентов
Если конкретному компоненту требуется другой базовый компонент или вообще никакой, вы можете объявить @inherits непосредственно в файле этого компонента. Явное объявление имеет приоритет над _Imports.razor:

@* MyComponent.razor (Razor) *@
@inherits ComponentBase

@* Этот компонент будет использовать ComponentBase вместо CustomComponentBase *@


Организация файлов _Imports.razor

Можно иметь несколько файлов _Imports.razor в разных папках, чтобы применять разные базовые компоненты к различным разделам приложения. Приоритет имеет ближайший файл _Imports.razor в иерархии каталогов. Например, /Components/_Imports.razor применяется ко всем компонентам в этой папке /Components/Admin/_Imports.razor применяется специально к компонентам папки Admin. Такой иерархический подход обеспечивает точный контроль над тем, какие компоненты наследуют от каких базовых классов.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👏61🍾1
Вопрос с C#-собеседования уровня Middle/Senior.
Многие разработчики на нём ошибаются

Посмотрите на код на первом слайде.

Что там видно:
ProductStock не равен null;
мы проходимся по элементам в цикле;
внутри цикла используется yield return.

Так почему всё равно появляется warning или error?
Подумайте немного... А потом откройте второй слайд и проверьте свой ответ.

Ответ 👇

yield return не останавливает выполнение цикла.
В отличие от обычного return, который сразу завершает метод, yield return лишь приостанавливает выполнение и возвращает очередной элемент последовательности.

После этого выполнение продолжается с того места, где оно было остановлено.
Поэтому, если нужно пропустить текущую итерацию, следует использовать continue.
Иначе код после yield return всё равно выполнится, что может привести к неожиданному поведению или исключениям.
Это одна из тех особенностей C#, о которых часто забывают даже опытные разработчики.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥145🍾2
Практическое применение архитектуры Vertical Slice в ASP.NET Core

https://www.telerik.com/blogs/practicing-vertical-slice-architecture-aspnet-core

Автор: Assis Zang

#aspnetcore

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5🍾1
Перестаньте использовать исключения для управления логикой приложения

Вот почему 👇

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

Но есть проблема: исключения подходят далеко не для всех сценариев.

Работая архитектором ПО и .NET-разработчиком, я пришёл к простому выводу:

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

👉 Исключения нужны для обработки действительно нештатных ситуаций.

Но ими не стоит заменять обычную бизнес-логику и ожидаемые условия.

Основные недостатки такого подхода:

• Непредсказуемость — по сигнатуре метода не всегда понятно, какие исключения он может выбросить

• Снижение читаемости — try/catch ломает линейный поток чтения кода

• Вложенность — отладка и навигация по коду становятся менее удобными

• Потери производительности — обработка исключений обходится дороже обычных проверок (даже с улучшениями в .NET 9)

Что использовать вместо этого?

Result Pattern

Вместо выбрасывания исключений метод возвращает объект Result.

Так успех и ошибка становятся явной частью контракта метода.

📌 Обычно объект Result содержит:

1️⃣ IsSuccess / IsError — успешно ли выполнена операция

2️⃣ Value — результат выполнения при успехе

3️⃣ Error — информация об ошибке при неудаче

Плюсы такого подхода:

↳ Более предсказуемые API

↳ Более чистый поток выполнения

↳ Проще писать unit-тесты

↳ Лучше производительность

Для этого паттерна уже есть готовые библиотеки:

• FluentResults

• CSharpFunctionalExtensions

• Ardalis.Result

• ErrorOr

Но на практике сторонние пакеты вовсе не обязательны.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🥴5🔥1🍾1