Compartilhar via


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:

  1. Dever ter um tipo de retorno void.
  2. Não pode ter parâmetros out.
  3. 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étodo partial 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étodos partial).

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.