Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
A partir de C# 15, podes aplicar o closed modificador a uma classe para declarar uma hierarquia fechada. Só se pode derivar um subtipo direto a partir de uma classe fechada dentro do seu conjunto declarante. Como o conjunto dos descendentes diretos é fixo, uma switch expressão que gere cada descendente direto esgota o tipo de base fechada e não precisa de um braço padrão.
// 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
A restrição da mesma montagem aplica-se apenas a descendentes diretos da classe fechada. Uma classe que deriva de uma classe fechada não é ela própria fechada a menos que também a closedassinalem . Como Failed no exemplo anterior é um registo simples, outra montagem pode derivar dele:
// Assembly 2
public record class RetryableFailed(string Error, int Attempts) : Failed(Error); // OK: 'Failed' isn't sealed or closed
Se quiseres evitar a derivação de Failed também, declara-o como sealed ou closed.
A referência da linguagem C# documenta a versão mais recentemente lançada da linguagem C#. Contém também documentação inicial para funcionalidades em versões preliminares públicas para a próxima versão da linguagem.
A documentação identifica qualquer funcionalidade introduzida pela primeira vez nas últimas três versões da língua ou em pré-visualizações públicas atuais.
Sugestão
Para saber quando uma funcionalidade foi introduzida pela primeira vez em C#, consulte o artigo sobre o histórico de versões da linguagem C#.
Note
closed é uma palavra-chave contextual. Tem um significado especial apenas quando aparece como modificador numa declaração de classe. Pode continuar a usar closed como identificador noutros contextos. Se precisares de usar closed como identificador numa posição onde o modificador também seria válido, prefixa-o com @ (por exemplo, @closed) para dizer ao compilador que o trate como identificador em vez do modificador.
Regras de Declaração
O closed modificador é um modificador de classe:
- Uma
closedclasse é implicitamenteabstract. Não podes combinarclosedcomsealed,static, ou um modificador explícitoabstract. - Deve declarar um subtipo direto de uma classe fechada no mesmo conjunto e módulo que a classe base fechada.
- Uma classe que deriva de uma classe fechada não é ela própria fechada. Aplica novamente o
closedmodificador se quiseres que uma classe derivada também esteja fechada.
Se uma classe genérica deriva diretamente de uma closed classe, cada parâmetro de tipo na classe derivada deve ser usado na especificação da classe base. Esta regra não diz respeito ao closed modificador em si: um tipo construído fechado é um tipo genérico cujos argumentos de tipo são totalmente especificados (como Tree<int>), ao contrário de um tipo aberto como Tree<T>. A regra assegura que cada tipo fechado construído da classe base tem exatamente um tipo fechado correspondente entre os seus descendentes diretos, para que o compilador possa raciocinar sobre a exaustividade.
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
Expressões exaustivas de comutação
Quando uma switch expressão trata todos os descendentes diretos de uma classe fechada, o compilador considera o switch exaustivo e não gera um aviso de não exaustividade:
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.
};
Quando o comutador que rege a expressão é anulável, null torna-se outro valor possível que o comutador deve gerir. Uma transição JobStatus? é exaustiva apenas quando cobre nulltambém :
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.
};
Se omitires o null arm, o compilador avisa que o padrão null não é tratado. A mesma regra aplica-se quer o tipo fechado seja uma classe ou uma estrutura elevada para um tipo anulável.
Para mais informações sobre como o compilador determina a exaustividade, incluindo como as hierarquias fechadas interagem com restrições genéricas e acessibilidade, veja Padrões de hierarquia fechada.
Parâmetros de tipo restritos a um tipo fechado
Um parâmetro de tipo restrito a uma classe fechada é tratado como essa classe fechada para verificações de exaustividade. Uma expressão cujo switch valor governante tem tal parâmetro de tipo é exaustiva quando trata todos os descendentes diretos da restrição fechada:
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.
};
Esta regra aplica-se quer o parâmetro tipo apareça num método ou no tipo que contém.
Especificação da linguagem C#
Para mais informações, consulte a especificação de funcionalidades Hierarquias Fechadas .