Частичные классы
Классы могут быть частичными. То есть мы можем иметь несколько файлов с определением одного и того же класса, и при компиляции все эти определения будут скомпилированы в одно.
Например, определим в проекте два файла с кодом. Не столь важно как эти файлы будут называться, например, "PersonMove.cs" и "PersonEat.cs". Например, в PersonMove.cs определим следующий класс:
public partial class Person
{
public void Move() => Console.WriteLine("Человек идет");
}
А в другом файле - в PersonEat.cs определим следующий класс:
public partial class Person
{
public void Eat() => Console.WriteLine("Человек ест");
}
Таким образом, два файла в проекте содержит определение одного и того же класса Person, которые содержат два разных метода. И оба определенных здесь класса являются частичными. Для этого они определяются с ключевым словом partial.
Затем мы можем использовать все методы класса Person:
var tom = new Person();
tom.Move();
tom.Eat();
Консольный вывод:
Человек идет
Человек ест
Таким образом, несколько определений частичного класса объединяются в одно. Стоит отметить, что частичные классы необязательно надо размещать в разных файлах. Они могут помещаться и в один и тот же файл и даже быть размещены рядом:
var tom = new Person();
tom.Move();
tom.Eat();
// Console.WriteLine(tom.Name);
public partial class Person
{
public void Eat() => Console.WriteLine("Человек ест");
}
public partial class Person
{
public void Move() => Console.WriteLine("Человек идет");
}
Тем не менее смысл частичных классов состоит в том, что определяя одну версию частичного класса, мы можем не знать, что будет делать другая версия частичного класса. Нередко частичные классы применяются при автогенерации кода, например, по определению пользовательского интерфейса, когда программист пишет основную логику взаимодействия программы с пользователем, а компилятор создает другой частичный класс с логикой отрисовки и позиционирования элементов управления в приложении.
Частичные методы
Частичные классы могут содержать частичные методы (partial methods). Такие методы также опреляются с ключевым словом partial. Причем определение частичного метода без тела метода находится в одном частичном классе, а реализация этого же метода - в другом частичном классе. Концепция частичных методов в некотором роде похожа на разделение функций на прототипы и реализации в таких языках как C и C++.
Например, пусть у нас будет следующий класс Person:
// объявление частичного класса
public partial class Person
{
public partial void Say(string message); // объявление частичного метода
}
Частичный метод Say не имеет тела и представляет лишь объявление. То есть компилятор, встретив подобный метод, будет знать, что метод принимает строку и ничего не возвращает. Причем на момент определения этого класса неизвестно, что представляет собой этот метод, какие действия он будет выполнять. Тем не менее мы знаем список его параметров и можем вызвать его в программе.
Также определим второй класс Person:
// реализация частичного класса
public partial class Person
{
// реализация частичного метода
public partial void Say(string message)
{
Console.WriteLine(message);
}
}
Здесь уже определена реализация метода Say(). И когда мы будем вызывать метод Say, будет выполняться эта реализация.
Стоит отметить, что по умолчанию к частичным методам применяется ряд ограничений:
Они не могут иметь модификаторы доступа
Они имеют тип void
Они не могут иметь out-параметры
Они не могут иметь модификаторы
virtual, override, sealed, newилиextern
Если же они не соответствуют какому-то из этих ограничений, то для них должна быть предоставлена реализация. Как, например,
в предыдущем примере частичные методы применяют модификатор public, поэтому предоставляется реализация.
Частичные свойства и индексаторы
Язык C# уже давно позволял определять частичные методы (partial methods). Начиная с версии C# 13 также можно определять частичные свойства и индексаторы. То есть мы можем разделить определение свойства/индексатора на объявление и реализацию и разместить их в разных файлах. При этом сигнатуры объявления и реализации должны совпадать. Единственное ограничение - нельзя определять частичные автосвойства. Если свойство, объявленное со словом partial, не имеет реализации, то оно рассматривается как объявление свойства.
Пример частичного свойства:
var tom = new Person("Tom");
Console.WriteLine(tom.Name);
// объявление класса
partial class Person{
public partial string Name { get; set; } // объявление частичного свойства
}
// реализация класса
partial class Person{
private string name; // поле для хранения значения свойства
public Person(string name) => this.name = name;
public partial string Name // реализация частичного свойства
{
get => name;
set => name = value;
}
}
Здесь определен частичный класс Person. Причем сначала идет только объявление класса Person:
partial class Person{
public partial string Name { get; set; } // объявление частичного свойства
}
В этом объявлении таже только объявление частичного свойства - Name. И хотя это объявление похоже на автоматическое свойство, это не автосвойство, а именно объявление свойства, так как к нему применяется модификатор partial
Далее идет собственно реализация класса и в том числе реализация свойства:
partial class Person{
private string name; // поле для хранения значения свойства
public Person(string name) => this.name = name;
public partial string Name // реализация частичного свойства
{
get => name;
set => name = value;
}
}
Хотя в данном случае для простоты объявление и реализация класса размещены рядом друг с другом, в реальности их можно разместить в разных файлах.
Пример с частичными индексаторами:
var buffer = new Buffer();
// некоторые действия с индексатором
for(var i=0; i < Buffer.COUNT; ++i){
buffer[i] = i * i;
}
for(var i=0; i < Buffer.COUNT; ++i){
Console.WriteLine(buffer[i]);
}
// объявление класса
partial class Buffer{
public partial int this[int index] { get; set; } // объявление свойства-индексатора
}
// реализация класса
partial class Buffer{
public const int COUNT = 6;
private int[] data = new int[COUNT];
public partial int this[int index] // реализация свойства-индексатора
{
get => data[index];
set => data[index] = value;
}
}
В данном случае частичный индексатор определен в частичном классе Buffer. Фактически индексатор является надстройкой над массивом data.