Partial-классы в C#: как разделить класс на несколько файлов

Partial-классы в C#: как разделить класс на несколько файлов Полезное

Partial-класс (разделяемый, частичный класс) в C# — это класс, определение которого разбито на несколько объявлений с ключевым словом partial, обычно в разных файлах. При компиляции компилятор собирает все части в один тип: для программы это обычный класс, «частей» в собранной сборке нет.

Важно различать два уровня. Partial-тип — класс, структура, интерфейс или record, разнесенные по частям. Partial-член — метод, свойство, конструктор или событие, у которого объявление лежит в одной части, а реализация в другой. Ниже — минимальный пример, правила объединения, частичные члены и типичные ошибки. Все примеры проверены 23.09.2026 на .NET SDK 10.0.401 (C# 14).

Минимальный пример: один класс в двух файлах

Проект из трех файлов. Первая часть класса хранит данные, вторая — поведение.

// Record.Data.cs
namespace HeightWidthInfo;

public partial class Record
{
    private readonly int _height;
    private readonly int _width;

    public Record(int height, int width)
    {
        _height = height;
        _width = width;
    }
}
// Record.Print.cs
namespace HeightWidthInfo;

public partial class Record
{
    public void Print()
    {
        Console.WriteLine($"Высота: {_height}");
        Console.WriteLine($"Ширина: {_width}");
    }
}
// Program.cs
using HeightWidthInfo;

var rec = new Record(10, 15);
rec.Print();

dotnet run печатает:

Высота: 10
Ширина: 15

Метод Print из второго файла свободно читает приватные поля из первого: после сборки это один класс, а private ограничивает доступ пределами типа, а не файла. Мелкая ловушка из прогона: переменную в top-level коде не стоит называть record — строка record.Print(); разбирается как объявление record-типа, и сборка падает с CS1001: Identifier expected.

Правила объединения частей

Компилятор склеивает части по полному имени типа (пространство имен + имя) в пределах одной сборки. Части в разных проектах или в разных пространствах имен — это уже разные классы, и ошибка проявится как «имя не найдено» (CS0103) при обращении к члену другой части. Остальные правила — в таблице, ошибки взяты из реального прогона.

Ситуация Что делает компилятор
У одной из частей нет partial CS0260: Missing partial modifier on declaration of type 'Order'
Части объявлены public и internal CS0262: ... have conflicting accessibility modifiers
У части модификатор доступа не указан Разрешено: берется из других частей (public у одной части делает публичным весь класс)
В частях разные базовые классы CS0263: ... must not specify different base classes
Базовый класс указан только в одной части Весь тип наследует его
Интерфейсы и атрибуты в разных частях Объединяются
abstract или sealed хотя бы в одной части Действует на весь тип: new Order() дает CS0144
partial enum или partial delegate CS0267: partial допустим только перед class, record, struct, interface и частичными членами

Упрощенная мнемоника «части — это просто куски одного файла» верна для членов и модификаторов, но не для директив using: они действуют только в своем файле, поэтому каждая часть подключает нужные пространства имен сама (если не используются глобальные using).

Частичные методы: классические и расширенные

Классический частичный метод — «крючок»: одна часть объявляет сигнатуру, другая может дать реализацию, а может и не дать. Если реализации нет, компилятор удаляет и объявление, и все вызовы — вместе с вычислением аргументов.

var o = new Order(42);
Console.WriteLine($"Заказ {o.Id} создан");

partial class Order
{
    public int Id { get; }

    public Order(int id)
    {
        Id = id;
        OnCreated(Describe());   // вызов частичного метода
    }

    // Объявление без реализации
    partial void OnCreated(string info);

    private string Describe()
    {
        Console.WriteLine("Describe() вызван");
        return $"id={Id}";
    }
}

Вывод — одна строка Заказ 42 создан: Describe() не вызвался, потому что вызов OnCreated удален целиком. Если добавить вторую часть с реализацией, поведение меняется:

partial class Order
{
    partial void OnCreated(string info) => Console.WriteLine($"OnCreated: {info}");
}
Describe() вызван
OnCreated: id=42
Заказ 42 создан

Такие методы неявно private, возвращают void и не имеют out-параметров. С C# 9 есть расширенные частичные методы: с модификатором доступа, возвращаемым значением или out-параметрами. Для них реализация обязательна, иначе ошибка:

error CS8795: Partial method 'Order.Validate()' must have an implementation part because it has accessibility modifiers.

Расширенная форма нужна не для «необязательных крючков», а для связки «человек объявляет — генератор реализует».

Partial-свойства, конструкторы и события (C# 13 и 14)

Частичные свойства и индексаторы появились в C# 13, частичные конструкторы экземпляра и события — в C# 14. Схема та же: одна часть объявляет член, другая обязательно реализует.

using System.Text.RegularExpressions;

var c = new Customer("  Anna ");
Console.WriteLine($"Name = '{c.Name}'");
Console.WriteLine(Validators.Email.IsMatch("user@example.com"));

public partial class Customer
{
    public partial string Name { get; set; }   // C# 13
    public partial Customer(string name);       // C# 14
}

public partial class Customer
{
    private string _name = "";
    public partial string Name
    {
        get => _name;
        set => _name = value.Trim();
    }
    public partial Customer(string name) => Name = name;
}

public static partial class Validators
{
    // Реализацию свойства пишет генератор исходного кода
    [GeneratedRegex(@"^[^@\s]+@[^@\s]+\.[^@\s]+$")]
    public static partial Regex Email { get; }
}
Name = 'Anna'
True

[GeneratedRegex] — типичный сценарий: разработчик пишет только объявление, а генератор исходного кода добавляет вторую часть класса с реализацией на этапе компиляции. На C# 13 (<LangVersion>13</LangVersion>) этот же файл не собирается, среди ошибок — CS9260: Feature 'partial events and constructors' is not available in C# 13.0. Регулярное выражение здесь учебное, для полноценной проверки email его недостаточно.

Где partial встречается на практике

Главный сценарий — отделить код, который пишет инструмент, от кода, который пишет человек. Тогда повторная генерация не затирает ручные правки.

Задача Как используется partial Когда иначе
Формы WinForms Дизайнер пишет Form1.Designer.cs с InitializeComponent(), обработчики — в Form1.cs Designer-файл руками не правят
WPF Из XAML при сборке генерируется часть MainWindow.g.cs —
Blazor Разметка в .razor, логика в .razor.cs (partial-класс) Можно держать код в блоке @code
Генераторы исходного кода [GeneratedRegex], [LibraryImport], генераторы MVVM —
Большой класс «на двоих» Можно разнести по файлам Часто лучше разбить на несколько классов

Про последнюю строку: partial не уменьшает ответственность класса, а только прячет ее по файлам. Если класс разросся до тысяч строк, обычно правильнее выделить отдельные классы. Partial также не заменяет наследование и методы расширения: дописать чужой класс из другой сборки через partial нельзя.

Если не компилируется

  • CS0260 — у одной из частей пропущен partial: добавьте его во все объявления.
  • CS0103: The name '_id' does not exist in the current context при обращении к полю из другой части — проверьте, что namespace и имя класса совпадают буквально и файлы входят в один проект.
  • CS8795 — у расширенного partial-метода нет реализации: допишите ее или, если реализацию дает генератор, проверьте, что он подключен.
  • CS8703 или CS9260 про версию языка — проект собирается старой версией C#: проверьте TargetFramework и LangVersion в .csproj.

Выводы

  • Partial-класс — один тип, объявленный частями; компилятор объединяет их в пределах одной сборки и одного полного имени.
  • Модификаторы доступа частей не должны конфликтовать, базовый класс — один; abstract, sealed, интерфейсы и атрибуты распространяются на весь тип.
  • Классический partial-метод без реализации удаляется вместе с вызовами и вычислением аргументов; расширенный требует реализацию.
  • Partial-свойства и индексаторы доступны с C# 13, конструкторы и события — с C# 14.
  • Основная ценность partial — разделение сгенерированного и ручного кода, а не способ спрятать слишком большой класс.

Где применяется / связь с практикой

Освойте тему на практике

С partial-классами начинающий C#-разработчик сталкивается в первом же проекте на WinForms, WPF или Blazor, а в современных библиотеках — через генераторы исходного кода. Чтобы уверенно читать такой код и понимать, что делает компилятор, нужна база по типам, ООП и сборке проектов. Системно ее дает курс C# Developer. Basic, а отдельные темы по .NET удобно посмотреть на открытых уроках Otus.

FAQ

Можно ли сделать partial статический или generic-класс?
Да. У generic-класса все части должны объявлять одинаковые параметры типа с одинаковыми именами, а ограничения where, если указаны в нескольких частях, должны совпадать.

Влияет ли partial на производительность?
Нет. Разделение существует только в исходном коде; после компиляции получается обычный тип.

Можно ли объявить вложенный partial-класс?
Да, вложенный тип тоже может быть partial. Если части вложенного класса разнесены по файлам, внешний класс в каждом из них тоже объявляется как partial.

OTUS Журнал
Скидка 5% 14-20 сентября на курсы (popup)