closed (справочник по C#)

Начиная с C# 15, модификатор можно применить closed к классу для объявления закрытой иерархии. Прямой подтип можно наследить только из закрытого класса в своей декларируемой сборке. Так как набор прямых потомков исправлен, выражение, обрабатывающее каждый прямой потомок, switch исчерпает закрытый базовый тип и не нуждается в руке по умолчанию.

// Assembly 1
public closed record class JobStatus;
public record class Queued : JobStatus;
public record class Running(int PercentComplete) : JobStatus;
public record class Completed(TimeSpan Elapsed) : JobStatus;
public record class Failed(string Error) : JobStatus;

// Assembly 2
public record class Paused : JobStatus; // Error: 'JobStatus' is a closed class

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

// Assembly 2
public record class RetryableFailed(string Error, int Attempts) : Failed(Error); // OK: 'Failed' isn't sealed or closed

Если вы хотите предотвратить производные от Failed него, объявите его как sealed или closed.

Справочные документы по языку C# описывают последнюю выпущенную версию языка C#. Она также содержит начальную документацию по функциям в общедоступных предварительных версиях для предстоящего языкового выпуска.

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

Tip

Чтобы узнать, когда функция впервые появилась в C#, ознакомьтесь со статьей об истории версий языка C#.

Note

closed — это контекстное ключевое слово. Он имеет особое значение, только если он отображается как модификатор в объявлении класса. Вы можете продолжать использовать closed в качестве идентификатора в других контекстах. Если необходимо использовать closed в качестве идентификатора в позиции, где модификатор также будет допустимым, префикс его с @ помощью (например, @closed), чтобы сообщить компилятору рассматривать его как идентификатор, а не модификатор.

Правила объявления

Модификатор closed является модификатором класса:

  • Класс closed неявно abstract. Нельзя сочетать closed с sealedмодификатором или staticявным abstract модификатором.
  • Необходимо объявить прямой подтип закрытого класса в той же сборке и модуле, что и закрытый базовый класс.
  • Класс, производный от закрытого класса, не закрыт. Примените модификатор еще раз, если требуется, чтобы производный closed класс также был закрыт.

Если универсальный класс непосредственно является производным от closed класса, каждый параметр типа в производном классе должен использоваться в спецификации базового класса. Это правило не относится к closed модификатору: закрытый созданный тип является универсальным типом, аргументы типа которого полностью указаны (например), в отличие от открытого типа, напримерTree<T>Tree<int>. Правило гарантирует, что каждый закрытый созданный тип базового класса имеет ровно один соответствующий закрытый построенный тип среди прямых потомков, поэтому компилятор может подумать о исчерпывающей причине.

public closed record class Tree<T>;

public record class Leaf<T>(T Value) : Tree<T>;                       // OK: 'T' appears in the base class
public record class Branch<T>(Tree<T> Left, Tree<T> Right) : Tree<T>; // OK: 'T' appears in the base class
public record class Constant<U>(U Value) : Tree<int> { } // Error: 'U' isn't used in the base class

Исчерпывающие выражения коммутатора

switch Когда выражение обрабатывает каждый прямой потомок закрытого класса, компилятор считает коммутатор исчерпывающим и не создает предупреждение о неисчерпаемой готовности:

public static string Describe(JobStatus status) => status switch
{
    Queued => "waiting to start",
    Running(var percent) => $"{percent}% complete",
    Completed(var elapsed) => $"finished in {elapsed.TotalSeconds:F1}s",
    Failed(var error) => $"failed: {error}",
    // No warning: every direct descendant of 'JobStatus' is handled.
};

Если параметр управления выражением имеет значение NULL, null становится другим возможным значением, которое должен обрабатывать коммутатор. Переключение JobStatus? является исчерпывающим только в том случае, если он также охватывает null:

public static string DescribeOrUnknown(JobStatus? status) => status switch
{
    null => "unknown",
    Queued => "waiting to start",
    Running(var percent) => $"{percent}% complete",
    Completed(var elapsed) => $"finished in {elapsed.TotalSeconds:F1}s",
    Failed(var error) => $"failed: {error}",
    // No warning: every direct descendant of 'JobStatus' is handled, and null is handled.
};

Если опустить null руку, компилятор предупреждает, что шаблон null не обрабатывается. То же правило применяется, является ли закрытый тип классом или структурой, поднятой на тип, допускающий значение NULL.

Дополнительные сведения о том, как компилятор определяет исчерпывающую эффективность, включая взаимодействие закрытых иерархий с универсальными ограничениями и специальными возможностями, см. в разделе "Шаблоны закрытой иерархии".

Параметры типа, ограниченные закрытым типом

Параметр типа, ограниченный закрытым классом, рассматривается как закрытый класс для проверки исчерпывающей готовности. switch Выражение, которое имеет такой параметр типа, является исчерпывающим при обработке каждого прямого потомка закрытого ограничения:

public static string DescribeJob<X>(X status) where X : JobStatus => status switch
{
    Queued => "waiting to start",
    Running(var percent) => $"{percent}% complete",
    Completed(var elapsed) => $"finished in {elapsed.TotalSeconds:F1}s",
    Failed(var error) => $"failed: {error}",
    // No warning: 'X' is constrained to a closed type, so its direct descendants exhaust the switch.
};

Это правило применяется, отображается ли параметр типа в методе или в содержаемом типе.

Спецификация языка C#

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

См. также