Partilhar via


Estendendo métodos parciais

Observação

Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ele inclui mudanças de especificação propostas, juntamente com as informações necessárias durante o design e desenvolvimento do recurso. Estes artigos são publicados até que as alterações de especificações 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 Language Design Meeting (LDM).

Você pode saber mais sobre o processo de adoção de especificações de recursos no padrão de linguagem C# no artigo sobre as especificações .

Questão campeã: https://github.com/dotnet/csharplang/issues/3301

Resumo

Esta proposta visa remover todas as restrições em torno das assinaturas de métodos partial em C#. O objetivo é expandir o conjunto de cenários em que esses métodos podem trabalhar com geradores de código-fonte, bem como ser um formulário de declaração mais geral para métodos C#.

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

Motivaçã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 de partial métodos é que, quando a definição está ausente, a linguagem simplesmente apagará todas as chamadas para o método partial. Essencialmente, ele se comporta como uma chamada para um método [Conditional] onde 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 este recurso foi a geração de código-fonte na forma de código gerado pelo designer. Os usuários estavam constantemente editando o código gerado porque queriam conectar algum aspeto do código gerado. Destacadamente, partes do processo de arranque do Windows Forms, depois de os componentes terem sido inicializados.

Editar o código gerado era propenso a erros porque qualquer ação que fizesse com que o designer regenerasse o código faria com que a edição do usuário fosse apagada. O recurso de método partial aliviou essa tensão porque permitiu que os designers emitissem ganchos na forma de métodos partial.

Os designers poderiam emitir ganchos como partial void OnComponentInit() e os desenvolvedores poderiam definir declarações para eles ou não defini-los. Em ambos os casos, porém, o código gerado seria compilado e os desenvolvedores que estavam interessados no processo poderiam se conectar conforme necessário.

Isto significa que os métodos parciais têm várias restrições:

  1. Deve ter um tipo de retorno void.
  2. Não pode haver parâmetros out.
  3. Não pode ter qualquer acessibilidade (implicitamente private).

Essas restrições existem porque o idioma deve ser capaz de emitir código quando o site de chamada é apagado. Dado que podem ser apagados, private é a única opção de acessibilidade possível porque o membro não pode ser exposto nos 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 é eliminar todas as restrições existentes relativamente aos métodos partial. Essencialmente, permita-lhes ter out parâmetros, tipos de retorno não nulos ou qualquer tipo de acessibilidade. Tais 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 chamada.

Isso expandiria o conjunto de cenários de geradores nos quais partial métodos poderiam participar e assim se ligar de forma harmoniosa com a nossa funcionalidade de geradores de origem. Por exemplo, um regex pode ser definido usando o seguinte padrão:

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

Isso dá ao desenvolvedor uma maneira declarativa simples de optar por geradores, bem como dar aos geradores um conjunto muito fácil de declarações para examinar no código-fonte para direcionar sua saída gerada.

Compare isso com a dificuldade que um gerador teria de conectar o seguinte trecho de código.

var regex = new RegularExpression("(dog|cat|fish)");
if (regex.IsMatch(someInput))
{

}

Dado que o compilador não permite que os geradores modifiquem o código, conectar esse padrão seria praticamente impossível para os geradores. Eles precisariam recorrer à reflexão na implementação do IsMatch, ou pedir aos usuários para alterar seus sites de chamada para um novo método + refatorar o regex para passar a string literal como um argumento. É muito bagunçado.

Projeto Detalhado

O idioma será alterado para permitir a anotação de métodos partial 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 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, a linguagem removerá todas as restrições sobre o que pode aparecer em um método partial que tenha uma acessibilidade explícita. Tais declarações podem conter tipos de retorno não nulos, parâmetros de 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 partial métodos participem de overrides e interface implementações:

interface IStudent
{
    string GetName();
}

partial class C : IStudent
{
    public virtual partial string GetName(); 
}

partial class C
{
    public virtual partial string GetName() => "Jarde";
}

O compilador irá alterar o erro que emite quando um método partial contém 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
  • partial declarações e assinaturas de definição devem coincidir em todos os modificadores de método e parâmetro. Os únicos aspetos que podem diferir são nomes de parâmetros e listas de atributos (isso não é novo, mas sim um requisito existente de partial métodos).

Perguntas

parcial em todos os membros

Dado que estamos expandindo o partial para ser mais compatível com geradores de código-fonte, devemos também expandi-lo para funcionar com todos os membros da classe? Por exemplo, devemos ser capazes de declarar partial construtores, operadores, etc ...

Resolução A ideia é boa, mas neste ponto do cronograma do C# 9 estamos a tentar evitar a expansão desnecessária de funcionalidades. Quer resolver o problema imediato de expandir o recurso para trabalhar com geradores de fonte modernos.

Estender partial para dar suporte a outros membros será considerado para a versão C# 10. Parece provável que vamos considerar esta extensão. Esta continua a ser uma proposta ativa, mas ainda não foi implementada.

Use abstrato em vez de parcial

O cerne da presente proposta consiste, essencialmente, em assegurar que uma declaração tenha uma definição/aplicação correspondente. Dado que devemos usar abstract uma vez 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 este assunto, mas acabou por se decidir contra. Sim, os requisitos são familiares, mas os conceitos são significativamente diferentes. Poderia facilmente levar o desenvolvedor a acreditar que eles estavam criando slots virtuais quando eles não estavam fazendo isso.