В чём разница между блоком @code и code-behind файлами в Blazor?
@code { } размещает C#-логику прямо в .razor-файле; code-behind (.razor.cs с partial class) выносит её в отдельный файл. Оба подхода компилируются в один partial class — выбор определяется объёмом логики и требованиями к тестируемости.
Два способа писать логику компонента
Blazor компилирует каждый .razor-файл в C#-класс. Логику можно разместить двумя способами: прямо в файле через блок @code { }, или в отдельном .razor.cs-файле через partial class.
Вариант 1: @code блок (всё в одном файле)
@* Counter.razor *@
@page "/counter"
<h1>Count: @count</h1>
<button @onclick="Increment">+1</button>
@code {
private int count = 0;
[Parameter]
public int Step { get; set; } = 1;
private void Increment() => count += Step;
}
Вариант 2: code-behind через partial class
@* Counter.razor — только разметка *@
@page "/counter"
<h1>Count: @count</h1>
<button @onclick="Increment">+1</button>
// Counter.razor.cs — логика
namespace MyApp.Pages;
public partial class Counter
{
private int count = 0;
[Parameter]
public int Step { get; set; } = 1;
private void Increment() => count += Step;
}
Как это работает компилятором
Razor-компилятор генерирует C#-класс с именем файла без расширения. Если файл называется Counter.razor, генерируется partial class Counter. Блок @code { } становится частью этого же partial class. Поэтому Counter.razor.cs с объявлением partial class Counter и @code внутри Counter.razor — это буквально один и тот же класс, просто разбитый по файлам.
Когда что использовать
- @code: для простых компонентов с небольшим количеством логики (≤50 строк). Всё видно сразу, нет необходимости прыгать между файлами.
- Code-behind: когда логика объёмная, содержит DI-зависимости, lifecycle-методы и нужно unit-тестирование без рендеринга. Также улучшает поддержку IDE (Resharper, Rider лучше анализируют .cs-файлы, чем .razor).
- Наследование от ComponentBase: третий вариант — унаследовать базовый класс:
@inherits CounterBase. Используется в библиотеках компонентов для разделения API и реализации.
Доступ к protected членам ComponentBase
В code-behind через partial class автоматически доступны все protected члены ComponentBase: StateHasChanged(), InvokeAsync(), OnInitializedAsync() и т.д. — без дополнительных объявлений.
public partial class DataPage
{
[Inject] private IDataService DataService { get; set; } = default!;
private List<Item> items = new();
protected override async Task OnInitializedAsync()
{
items = await DataService.GetItemsAsync();
// StateHasChanged() вызывается автоматически после OnInitializedAsync
}
}
Подводные камни
- Имя partial class в .razor.cs должно точно совпадать с именем .razor-файла (без расширения). Ошибка в регистре приводит к созданию двух разных классов вместо одного.
- Namespace в .razor.cs должен совпадать с неймспейсом, который Razor генерирует по умолчанию (обычно
RootNamespace.Папка.Подпапка). При несоответствии компилятор выдаст ошибку о дублировании определений. - Нельзя одновременно иметь
@code { }в .razor-файле и объявлять одноимённые члены в .razor.cs — будет ошибка дублирования. - При использовании
@inheritsс базовым классом, code-behind не работает — наследование и partial class несовместимы в одном компоненте. - IDE-рефакторинги (переименование файла) не всегда обновляют связанный .razor.cs — нужно переименовывать оба файла и класс вручную.
- В .razor.cs нельзя использовать Razor-синтаксис (
@inject,@page) — только атрибуты C# ([Inject],[Parameter]).
Common mistakes
- Путать @code и partial class code-behind с похожим механизмом из другой версии или платформы.
- Игнорировать runtime-границы Blazor: lifecycle, DI scope, SQL translation, UI thread или platform API.
- Не обсуждать null/empty/error cases и поведение под нагрузкой.
What the interviewer is testing
- Кандидат объясняет @code и partial class code-behind на конкретном примере, а не только определением.
- Указывает последствия для производительности, тестируемости и поддержки.
- Различает документированное поведение текущего стека и устаревшие практики.