Estendendo métodos parciais
Nota
Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ele inclui alterações de especificação propostas, juntamente com as informações necessárias durante o design e o desenvolvimento do recurso. Esses artigos são publicados até que as alterações de especificação propostas sejam finalizadas e incorporadas na especificação ECMA atual.
Pode haver algumas discrepâncias entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas pertinentes da reunião de design de idioma (LDM).
Você pode saber mais sobre o processo de adoção de speclets de recursos no padrão de linguagem C# no artigo sobre as especificações de .
Resumo
Esta proposta visa remover todas as restrições em torno das assinaturas de métodos partial
em C#. A meta é expandir o conjunto de cenários em que esses métodos podem trabalhar com geradores de origem, além de ser um formulário de declaração mais geral para métodos C#.
Consulte também a especificação original dos métodos parciais (§15.6.9).
Motivação
O C# tem suporte limitado para desenvolvedores dividindo métodos em declarações e definições/implementações.
partial class C
{
// The declaration of C.M
partial void M(string message);
}
partial class C
{
// The definition of C.M
partial void M(string message) => Console.WriteLine(message);
}
Um comportamento dos métodos partial
é que, quando a definição estiver ausente, o idioma simplesmente apagará todas as chamadas para o método partial
. Essencialmente, ele se comporta como uma chamada para um método [Conditional]
em que a condição foi avaliada como falsa.
partial class D
{
partial void M(string message);
void Example()
{
M(GetIt()); // Call to M and GetIt erased at compile time
}
string GetIt() => "Hello World";
}
A motivação original para esse recurso foi a geração de origem na forma de código gerado pelo designer. Os usuários estavam constantemente editando o código gerado porque queriam personalizar algum aspecto dele. Mais notadamente, partes do processo de inicialização do Windows Forms, depois que os componentes tiverem sido inicializados.
A edição do código gerado foi propensa a erros porque qualquer ação que fez o designer regenerar o código faria com que a edição do usuário fosse apagada. O recurso do método partial
aliviou essa tensão porque permitiu que os designers emitissem hooks na forma de métodos partial
.
Os designers podem emitir ganchos como partial void OnComponentInit()
e os desenvolvedores podem definir declarações para eles ou não defini-los. Em ambos os casos, o código gerado seria compilado e os desenvolvedores interessados no processo poderiam se integrar conforme necessário.
Isso significa que os métodos parciais têm várias restrições:
- Dever ter um tipo de retorno
void
. - Não pode ter parâmetros
out
. - Não pode ter nenhuma acessibilidade (implicitamente,
private
).
Essas restrições existem porque o idioma deve ser capaz de emitir código quando o site de chamada é apagado. Uma vez que podem ser apagados, private
é a única acessibilidade possível porque o membro não pode ser exposto em metadados de montagem. Essas restrições também servem para limitar o conjunto de cenários nos quais partial
métodos podem ser aplicados.
A proposta aqui é remover todas as restrições existentes em torno de métodos partial
. Essencialmente, permita que eles tenham parâmetros out
, tipos de retornos não nulos ou qualquer tipo de acessibilidade. Essas declarações partial
teriam, então, o requisito adicional de que uma definição deve existir. Isso significa que o idioma não precisa considerar o impacto de apagar os sites de chamadas.
Isso expandiria o conjunto de cenários de geradores nos quais os métodos partial
poderiam participar e, portanto, se integraria bem ao nosso recurso de geradores de código-fonte. Por exemplo, um regex pode ser definido usando o seguinte padrão:
[RegexGenerated("(dog|cat|fish)")]
partial bool IsPetMatch(string input);
Isso fornece ao desenvolvedor uma maneira declarativa simples de optar por geradores, bem como dar aos geradores um conjunto muito fácil de declarações para analisar no código-fonte para conduzir sua saída gerada.
Compare isso com a dificuldade que um gerador teria ao conectar o snippet de código a seguir.
var regex = new RegularExpression("(dog|cat|fish)");
if (regex.IsMatch(someInput))
{
}
Uma vez que o compilador não permite que os geradores modifiquem o código, a conexão desse padrão seria praticamente impossível para os geradores. Eles precisariam recorrer à reflexão na implementação de IsMatch
ou solicitar que os usuários alterassem seus sites de chamadas para um novo método + refatorar o regex para passar o literal da cadeia de caracteres como um argumento. É muito confuso.
Design detalhado
A linguagem será alterada para permitir que os métodos partial
sejam anotados com um modificador de acessibilidade explícito. Isso significa que eles podem ser rotulados como private
, public
, etc...
Quando um método partial
tiver um modificador de acessibilidade explícito, o idioma exigirá que a declaração tenha uma definição correspondente mesmo quando a acessibilidade for private
:
partial class C
{
// Okay because no definition is required here
partial void M1();
// Okay because M2 has a definition
private partial void M2();
// Error: partial method M3 must have a definition
private partial void M3();
}
partial class C
{
private partial void M2() { }
}
Além disso, o idioma removerá todas as restrições sobre o que pode aparecer em um método partial
que tenha uma acessibilidade explícita. Essas declarações podem conter tipos de retorno não nulos, parâmetros out
, modificador de extern
etc... Essas assinaturas terão toda a expressividade da linguagem C#.
partial class D
{
// Okay
internal partial bool TryParse(string s, out int i);
}
partial class D
{
internal partial bool TryParse(string s, out int i) { ... }
}
Isso permite explicitamente que partial
métodos participem de implementações de overrides
e interface
:
interface IStudent
{
string GetName();
}
partial class C : IStudent
{
public virtual partial string GetName();
}
partial class C
{
public virtual partial string GetName() => "Jarde";
}
O compilador alterará o erro emitido quando um método partial
contiver um elemento ilegal para, essencialmente, dizer:
Não é possível usar
ref
em um métodopartial
que não tem acessibilidade explícita
Isso ajudará a apontar os desenvolvedores na direção certa ao usar esse recurso.
Restrições:
partial
declarações com acessibilidade explícita devem ter uma definição- As declarações e assinaturas de definição
partial
devem corresponder a todos os modificadores de método e parâmetro. Os únicos aspectos que podem ser diferentes são nomes de parâmetro e listas de atributos (isso não é novo, mas sim um requisito existente de métodospartial
).
Perguntas
parcial em todos os membros
Considerando que estamos expandindo partial
para ser mais amigável aos geradores de código-fonte, devemos também expandi-lo para funcionar em todos os membros da classe? Por exemplo, devemos ser capazes de declarar construtores partial
, operadores etc...
Resolução A ideia é sólida, mas neste momento no agendamento do C# 9 estamos tentando evitar o acréscimo excessivo de funcionalidades desnecessárias. Deseja resolver o problema imediato de adaptar o recurso para funcionar com geradores de código-fonte modernos.
Estender partial
para oferecer suporte a outros membros será considerado para a versão C# 10. Parece provável que consideraremos essa extensão. Esta continua sendo uma proposta ativa, mas ainda não foi implementada.
Usar abstrato em vez de parcial
O ponto crucial desta proposta é essencialmente garantir que uma declaração tenha uma definição/implementação correspondente. Considerando que devemos usar abstract
já que já é uma palavra-chave de linguagem que força o desenvolvedor a pensar em ter uma implementação?
Resolução Houve uma discussão saudável sobre isso, mas decidiu-se contra. Sim, os requisitos são familiares, mas os conceitos são significativamente diferentes. Poderia facilmente levar o desenvolvedor a acreditar que estava criando slots virtuais quando eles não estavam fazendo isso.
C# feature specifications