Membro parcial (Referência do C#)

Um membro parcial tem uma declaração declarante e, com frequência, uma declaração de implementação. A declaração declarante não inclui um corpo. A declaração de implementação fornece o corpo do membro. Os membros parciais permitem que os designers de classe forneçam ganchos de membros que podem ser implementados por ferramentas como geradores de origem. Tipos e membros parciais fornecem uma maneira para desenvolvedores humanos escreverem parte de um tipo enquanto as ferramentas escrevem as outras partes. Se o desenvolvedor não fornecer uma declaração de implementação opcional, o compilador poderá remover a declaração declarante na hora da compilação. As seguintes condições são aplicáveis a membros parciais:

  • As declarações devem começar com a palavra-chave contextual partial.
  • As assinaturas em ambas as partes do tipo parcial devem ser correspondentes.

A palavra-chave partial não é permitida em construtores, finalizadores, operadores sobrecarregados ou declarações de eventos. Antes do C# 13, partial não era permitido em propriedades ou indexadores.

Um método parcial não precisa ter uma declaração de implementação nos seguintes casos:

Qualquer membro que não esteja em conformidade com todas essas restrições (por exemplo, método public virtual partial void) deve fornecer uma implementação. Propriedades parciais e indexadores devem ter uma implementação.

O exemplo a seguir mostra um método parcial que está em conformidade com as restrições anteriores:

partial class MyPartialClass
{
    // Declaring definition
    partial void OnSomethingHappened(string s);
}

// This part can be in a separate file.
partial class MyPartialClass
{
    // Comment out this method and the program
    // will still compile.
    partial void OnSomethingHappened(string s) =>
        Console.WriteLine($"Something happened: {s}");
}

Membros parciais também podem ser úteis em combinação com geradores de origem. Por exemplo, uma expressão regular pode ser definida usando o seguinte padrão:

public partial class RegExSourceGenerator
{
    [GeneratedRegex("cat|dog", RegexOptions.IgnoreCase, "en-US")]
    private static partial Regex CatOrDogGeneratedRegex();

    private static void EvaluateText(string text)
    {
        if (CatOrDogGeneratedRegex().IsMatch(text))
        {
            // Take action with matching text
        }
    }
}

O exemplo anterior mostra um método parcial que deve ter uma declaração de implementação. Como parte de uma compilação, o gerador de origem de expressões regulares cria a declaração de implementação.

O exemplo a seguir mostra uma declaração declarante e uma declaração de implementação para uma classe. Como o tipo de retorno do método não é voidstring) e seu acesso é public, o método deve ter uma declaração de implementação:

// Declaring declaration
public partial class PartialExamples
{
    /// <summary>
    /// Gets or sets the number of elements that the List can contain.
    /// </summary>
    public partial int Capacity { get; set; }

    /// <summary>
    /// Gets or sets the element at the specified index.
    /// </summary>
    /// <param name="index">The index</param>
    /// <returns>The string stored at that index</returns>
    public partial string this[int index] { get; set; }

    public partial string? TryGetAt(int index);
}

public partial class PartialExamples
{
    private List<string> _items = [
        "one",
        "two",
        "three",
        "four",
        "five"
        ];

    // Implementing declaration

    /// <summary>
    /// Gets or sets the number of elements that the List can contain.
    /// </summary>
    /// <remarks>
    /// If the value is less than the current capacity, the list will shrink to the
    /// new value. If the value is negative, the list isn't modified.
    /// </remarks>
    public partial int Capacity
    {
        get => _items.Count;
        set
        {
            if ((value != _items.Count) && (value >= 0))
            {
                _items.Capacity = value;
            }
        }
    }

    public partial string this[int index]
    {
        get => _items[index];
        set => _items[index] = value;
    }

    /// <summary>
    /// Gets the element at the specified index.
    /// </summary>
    /// <param name="index">The index</param>
    /// <returns>The string stored at that index, or null if out of bounds</returns>
    public partial string? TryGetAt(int index)
    {
        if (index < _items.Count)
        {
            return _items[index];
        }
        return null;
    }
}

O exemplo anterior mostra regras de combinação das duas declarações:

  • Correspondências de assinatura: em geral, as assinaturas para as declarações declarantes e de implementação devem coincidir. Isso inclui o modificador de acessibilidade em métodos, propriedades, indexadores e acessadores individuais. Inclui os modificadores de tipo de parâmetro e ref-kind em todos os parâmetros. O tipo de retorno e qualquer modificador ref-kind devem corresponder. Os nomes dos membros da tupla devem corresponder. No entanto, algumas regras são flexíveis:
    • As declarações declarantes e implementação podem ter configurações de anotações anuláveis. O que significa que um pode ser sem reconhecimento de anulável e o outro com anulável habilitado.
    • As diferenças de nulidade que não envolvem sem reconhecimento de anulável geram um aviso.
    • Os valores de parâmetro padrão não precisam corresponder. O compilador emitirá um aviso se a declaração de implementação de um método ou indexador declarar um valor de parâmetro padrão.
    • O compilador emite um aviso quando os nomes de parâmetro não correspondem. O IL emitido contém os nomes de parâmetro da declaração declarante.
  • Comentários da documentação: os comentários da documentação podem ser incluídos a partir de qualquer declaração. Se as declarações declarantes e de execução incluírem comentários de documentação, os comentários da declaração de implementação serão incluídos. No exemplo anterior, os comentários da documentação incluem:
    • Para a propriedade Capacity, os comentários são retirados da declaração de implementação. Os comentários da declaração de implementação são utilizados quando ambas as declarações têm comentários ///.
    • Para o indexador, os comentários são obtidos da declaração declarante. A declaração de implementação não inclui comentários ///.
    • No que diz respeito a TryGetAt, as observações são retiradas da declaração de implementação. A declaração declarante não inclui nenhum comentário ///.
    • O XML gerado tem comentários de documentação para todos os membros public.
  • A maioria das declarações de atributos é combinada. No entanto, todos os atributos de informações do chamador são definidos com AllowMultiple=false. O compilador reconhece qualquer atributo de informações do chamador na declaração de implementação. Todos os atributos de informações do chamador na declaração de implementação são ignorados. O compilador emitirá um aviso se você adicionar atributos de informações do chamador na declaração de implementação.

Confira também