Compartilhar via


Extensão de métodos partial

Observação

Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ela 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 divergê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 .

Problema do especialista: https://github.com/dotnet/csharplang/issues/3301

Resumo

Esta proposta tem como objetivo 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 do C#.

Consulte também a especificação original dos métodos partial (§15.6.9).

Motivação

O C# tem suporte limitado para desenvolvedores que dividem 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 está ausente, a linguagem simplesmente apaga todas as chamadas para o método partial. Basicamente, 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 ficou propensa a erros porque qualquer ação que fazia o designer gerar novamente o código fazia 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 hooks 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 partial 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 a linguagem 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 em que métodos partial 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 deve existir uma definição. Isso significa que a linguagem 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, uma expressão regular pode ser definida usando o seguinte padrão:

[RegexGenerated("(dog|cat|fish)")]
partial bool IsPetMatch(string input);

Para o desenvolvedor, essa é 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 trecho 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.

Projeto 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 tem um modificador de acessibilidade explícito, o idioma exige que a declaração tenha uma definição correspondente mesmo quando a acessibilidade é 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, a linguagem 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 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 métodos partial 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, por exemplo:

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:

  • declarações partial 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âmetros e listas de atributos (não é novidade, 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. Provavelmente vamos considerar 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 é basicamente garantir que uma declaração tenha uma definição/implementação correspondente. Pensando nisso, devemos usar abstract por já ser 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 muito diferentes. Poderia facilmente levar o desenvolvedor a acreditar que estivesse criando slots virtuais quando, na verdade, não estavam fazendo isso.