Membro parcial (Referência C#)

Um membro parcial tem uma declaração declarativa e, muitas vezes, uma declaração de execução. A declaração declarante não inclui um corpo. A declaração de execução indica 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 código-fonte. Tipos e membros parciais fornecem uma maneira para desenvolvedores humanos escreverem parte de um tipo, enquanto as ferramentas escrevem outras partes do tipo. Se o desenvolvedor não fornecer uma declaração de implementação opcional, o compilador poderá remover a declaração declarando em tempo de compilação. Aos membros parciais aplicam-se as seguintes condições:

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

A partial palavra-chave 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 é necessário para 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, public virtual partial void método), 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, um regex pode ser definido 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ão regular 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 void é (é string) 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 ilustra as regras sobre como as duas declarações são combinadas:

  • Correspondências de assinaturas: Em geral, as assinaturas para as declarações declarativas e de implementação devem corresponder. Isso inclui modificador de acessibilidade em métodos, propriedades, indexadores e acessadores individuais. Inclui o tipo de parâmetro e modificadores ref-kind em todos os parâmetros. O tipo de retorno e qualquer modificador ref-kind devem corresponder. Os nomes dos membros da Tuple devem corresponder. No entanto, algumas regras são flexíveis:
    • As declarações de declaração e implementação podem ter diferentes configurações de anotações anuláveis . Ou seja, um pode ser anulável alheio e o outro anulável habilitado.
    • As diferenças de anulabilidade que não envolvem anulabilidade alheia geram um aviso.
    • Os valores dos parâmetros padrão não precisam corresponder. O compilador emite um aviso se a declaração de implementação de um método ou indexador declara um valor de parâmetro padrão.
    • O compilador emite um aviso quando os nomes dos parâmetros não correspondem. O IL emitido contém os nomes dos parâmetros 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 tanto a declaração como a declaração de execução incluírem observações documentais, as observações da declaração de execução são incluídas. No exemplo anterior, os comentários da documentação incluem:
    • Para a Capacity propriedade, os comentários são retirados da declaração de execução. As observações da declaração de execução são utilizadas quando ambas as declarações contêm /// observações.
    • Para o indexador, os comentários são retirados da declaração declarante. A declaração de execução não inclui quaisquer /// comentários.
    • Para TryGetAt, as observações são retiradas da declaração de execução. A declaração declarativa não inclui comentários /// .
    • O XML gerado tem comentários de documentação para todos os public membros.
  • A maioria das declarações de atributo são combinadas. No entanto, todos os atributos de informações do chamador são definidos com AllowMultiple=false. O compilador reconhece qualquer atributo de informação do chamador na declaração de declaração. Todos os atributos de informações do chamador na declaração de implementação são ignorados. O compilador emite um aviso se você adicionar atributos de informações do chamador na declaração de implementação.

Consulte também