Compartilhar via


Propriedades 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 aprender mais sobre o processo de incorporação de especificações de funcionalidades ao padrão da linguagem C# no artigo sobre as especificações .

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

Gramática

A gramática de property_declaration(§14.7.1) é atualizada da seguinte maneira:

property_declaration
-    : attributes? property_modifier* type member_name property_body
+    : attributes? property_modifier* 'partial'? type member_name property_body
    ;  

Comentários: isso é um pouco semelhante ao modo como method_header(§15.6.1) e class_declaration(§15.2.1) são especificados. (O problema nº 946 propõe relaxar o requisito de ordenação e provavelmente se aplicaria a todas as declarações que permitem o modificador partial. Pretendemos especificar esse relaxamento de ordenação em um futuro próximo e implementá-lo na mesma versão em que esse recurso é implementado.)

Definindo e implementando declarações

Quando uma declaração de propriedade inclui um modificador parcial , essa propriedade é considerada uma propriedade parcial . As propriedades parciais só podem ser declaradas como membros de tipos parciais.

Uma declaração de propriedade parcial é considerada uma declaração de definição quando todos os seus acessadores têm corpos ponto-e-vírgula e ela não tem o modificador extern. Caso contrário, é uma declaração de implementação.

partial class C
{
    // Defining declaration
    public partial string Prop { get; set; }

    // Implementing declaration
    public partial string Prop { get => field; set => field = value; }
}

Como reservamos a forma sintática com corpos de acessadores separados por ponto e vírgula para a declaração de definição , uma propriedade parcial não pode ser implementada automaticamente . Portanto, ajustamos propriedades implementadas automaticamente (§15.7.4) da seguinte maneira:

Uma propriedade implementada automaticamente (ou propriedade automática, para abreviar) é uma propriedade não abstrata, não externa, não parcial, não de valor de referência com corpos de acessadores somente com ponto-e-vírgula.

Comentários. É útil que o compilador possa examinar uma única declaração isoladamente e saber se ela é uma declaração de definição ou implementação. Portanto, não queremos permitir propriedades automáticas incluindo duas declarações de propriedade partial idênticas, por exemplo. Não achamos que os casos de uso para esse recurso envolvam a implementação da propriedade parcial com uma propriedade automática, mas nos casos em que uma implementação trivial é desejada, achamos que a palavra-chave field torna as coisas simples o suficiente.


Uma propriedade parcial deve ter uma declaração definidora e uma declaração de implementação .

Comentários. Também não achamos útil permitir a divisão da declaração em mais de duas partes, para permitir que diferentes acessadores sejam implementados em locais diferentes, por exemplo. Portanto, simplesmente imitamos o esquema estabelecido por métodos parciais.


Somente a declaração de definição de uma propriedade parcial participa da pesquisa, semelhante a como somente a declaração de definição de um método parcial participa da resolução de sobrecarga.

Comentários. No compilador, esperamos que apenas o símbolo da declaração de definição apareça na lista de membros e que o símbolo da parte de implementação possa ser acessado por meio do símbolo de definição. No entanto, alguns recursos, como a análise anulável, podem levar em conta a declaração de implementação para fornecer um comportamento mais útil.

partial class C
{
    public partial string Prop { get; set; }
    public partial string Prop { get => field; set => field = value; }

    public C() // warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
    {
    }
}

Uma propriedade parcial não tem permissão para ter o modificador de abstract.

Uma propriedade parcial não pode implementar explicitamente as propriedades da interface.

Mesclagem de atributos

Semelhantes aos métodos parciais, os atributos na propriedade resultante são atributos combinados das partes, que são concatenados em uma ordem não especificada, e as duplicatas não são removidas.

Atributos de informações do chamador

Ajustamos a seguinte linguagem do padrão :

É um erro ter o mesmo atributo caller-info em um parâmetro tanto na parte de definição quanto na de implementação de uma declaração de membro demétodo parcial. Somente os atributos de informações de chamador na parte de definição são aplicados, enquanto os atributos de informações de chamador que ocorrem apenas na parte de implementação são ignorados.

  • O erro descrito não se enquadra nas definições desses atributos que não têm AllowMultiple = true. Usá-las várias vezes, inclusive entre declarações parciais, resulta em um erro.
  • Quando atributos de informações de chamador são aplicados a um parâmetro na parte de implementação de um método parcial, o compilador Roslyn relata um aviso. Ele também relatará um aviso para o mesmo cenário em uma propriedade parcial.

Assinaturas correspondentes

A reunião do LDM em 14 de setembro de 2020 definiu um conjunto de requisitos "estritos" para correspondência de assinaturas de métodos parciais, que foram introduzidos em um ciclo de aviso. As propriedades parciais têm requisitos análogos aos métodos parciais de correspondência de assinatura tanto quanto possível, exceto pelo fato de que todos os diagnósticos de incompatibilidade são relatados por padrão e não são mantidos atrás de um ciclo de aviso.

Os requisitos de correspondência de assinatura incluem:

  1. Diferenças de tipo e referência entre declarações de propriedade parciais que são significativas para o tempo de execução resultam em um erro em tempo de compilação.
  2. A diferença nos nomes de elementos de tupla em declarações de propriedades parciais resulta em um erro em tempo de compilação, assim como acontece com métodos parciais.
  3. As declarações de propriedade e suas declarações de acessador devem ter os mesmos modificadores, embora os modificadores possam aparecer em uma ordem diferente.
    • Exceção: isso não se aplica ao modificador de extern, que só pode aparecer em um implementando a declaração.
  4. Todas as outras diferenças sintacticas nas assinaturas de declarações de propriedade parcial resultam em um aviso de tempo de compilação, com as seguintes exceções:
    • Listas de atributos em ou dentro de declarações de propriedades parciais não precisam corresponder. Em vez disso, a mesclagem de atributos em posições correspondentes é executada conforme a Mesclagem de atributos.
    • Diferenças de contexto anuláveis não causam avisos. Em outras palavras, uma diferença em que um dos tipos é nullable-oblivious e o outro tipo é nullable-annotated ou not-nullable-annotated não resulta em avisos.
    • Os valores de parâmetro padrão não precisam corresponder. Um aviso é relatado quando a parte de implementação de um indexador parcial tem valores de parâmetro padrão. Isso é semelhante a um aviso existente que ocorre quando a parte de implementação de um método parcial tem valores de parâmetro padrão.
  5. Um aviso ocorre quando os nomes dos parâmetros diferem entre definir e implementar declarações. Os nomes de parâmetro da parte de definição são utilizados em sites de uso e em emissão.
  6. Diferenças de nulidade que não envolvem sem reconhecimento de anulável resultam em avisos. Ao analisar o corpo de um acessador, utiliza-se a assinatura da parte de implementação. A assinatura da parte de definição é utilizada ao analisar sites de uso e em emissão. Isso é consistente com métodos parciais.
partial class C1
{
    public partial string Prop { get; private set; }

    // Error: accessor modifier mismatch in 'set' accessor of 'Prop'
    public partial string Prop { get => field; set => field = value; }
}

partial class C2
{
    public partial string Prop { get; init; }

    // Error: implementation of 'Prop' must have an 'init' accessor to match definition
    public partial string Prop { get => field; set => field = value; }
}

partial class C3
{
    public partial string Prop { get; }

    // Error: implementation of 'Prop' cannot have a 'set' accessor because the definition does not have a 'set' accessor.
    public partial string Prop { get => field; set => field = value; }
}

partial class C4
{
    public partial string this[string s = "a"] { get; set; }
    public partial string this[string s] { get => s; set { } } // ok

    public partial string this[int i, string s = "a"] { get; set; }
    public partial string this[int i, string s = "a"] { get => s; set { } } // CS1066: The default value specified for parameter 's' will have no effect because it applies to a member that is used in contexts that do not allow optional arguments
}

Comentários da documentação

Queremos que o comportamento dos comentários de documentação em propriedades parciais seja consistente com o que enviamos para métodos parciais. Esse comportamento é detalhado em https://github.com/dotnet/csharplang/issues/5193.

É permitido incluir comentários de documento na parte de definição ou implementação de uma propriedade parcial. (Não há suporte para comentários de documentos em acessadores de propriedade.)

Quando os comentários de documentação estão presentes apenas em uma parte da propriedade, esses comentários são usados normalmente (exibidos por meio de ISymbol.GetDocumentationCommentXml(), gravados no arquivo XML da documentação etc.).

Quando os comentários do doc estão presentes em ambas as partes, todos os comentários do documento sobre a parte de definição são descartados e somente os comentários do documento sobre a parte de implementação são usados.

Por exemplo, o seguinte programa:

/// <summary>
/// My type
/// </summary>
partial class C
{
    /// <summary>Definition part comment</summary>
    /// <returns>Return value comment</returns>
    public partial int Prop { get; set; }
    
    /// <summary>Implementation part comment</summary>
    public partial int Prop { get => 1; set { } }
}

Resulta no seguinte arquivo de documentação XML:

<?xml version="1.0"?>
<doc>
    <assembly>
        <name>ConsoleApp1</name>
    </assembly>
    <members>
        <member name="T:C">
            <summary>
            My type
            </summary>
        </member>
        <member name="P:C.Prop">
            <summary>
            Implementation part comment
            </summary>
        </member>
    </members>
</doc>

Quando os nomes de parâmetro diferem entre declarações parciais, os elementos <paramref> usam os nomes de parâmetro da declaração associada ao comentário da documentação no código-fonte. Por exemplo, um paramref em um comentário de documento colocado em uma declaração de implementação refere-se aos símbolos de parâmetro na declaração de implementação usando seus nomes de parâmetro. Isso é consistente com métodos parciais.

/// <summary>
/// My type
/// </summary>
partial class C
{
    public partial int this[int x] { get; set; }

    /// <summary>
    /// <paramref name="x"/> // warning CS1734: XML comment on 'C.this[int]' has a paramref tag for 'x', but there is no parameter by that name
    /// <paramref name="y"/> // ok. 'Go To Definition' will go to 'int y'.
    /// </summary>
    public partial int this[int y] { get => 1; set { } } // warning CS9256: Partial property declarations 'int C.this[int x]' and 'int C.this[int y]' have signature differences.
}

Resulta no seguinte arquivo de documentação XML:

<?xml version="1.0"?>
<doc>
    <assembly>
        <name>ConsoleApp1</name>
    </assembly>
    <members>
        <member name="T:C">
            <summary>
            My type
            </summary>
        </member>
        <member name="P:C.Item(System.Int32)">
            <summary>
            <paramref name="x"/> // warning CS1734: XML comment on 'C.this[int]' has a paramref tag for 'x', but there is no parameter by that name
            <paramref name="y"/> // ok. 'Go To Definition' will go to 'int y'.
            </summary>
        </member>
    </members>
</doc>

Isso pode ser confuso, pois a assinatura de metadados usará nomes de parâmetro da parte de definição. É recomendável garantir que os nomes dos parâmetros correspondam entre partes para evitar essa confusão.

Indexadores

De acordo com a reunião do LDM em 2 de novembro de 2022, os indexadores terão suporte com esse recurso.

A gramática dos indexadores é modificada da seguinte maneira:

indexer_declaration
-    : attributes? indexer_modifier* indexer_declarator indexer_body
+    : attributes? indexer_modifier* 'partial'? indexer_declarator indexer_body
-    | attributes? indexer_modifier* ref_kind indexer_declarator ref_indexer_body
+    | attributes? indexer_modifier* 'partial'? ref_kind indexer_declarator ref_indexer_body
    ;

Os parâmetros parciais do indexador devem ser correspondentes entre as declarações de acordo com as mesmas regras das assinaturas correspondentes. A mesclagem de atributos é realizada nos parâmetros parciais do indexador.

partial class C
{
    public partial int this[int x] { get; set; }
    public partial int this[int x]
    {
        get => this._store[x];
        set => this._store[x] = value;
    }
}

// attribute merging
partial class C
{
    public partial int this[[Attr1] int x]
    {
        [Attr2] get;
        set;
    }

    public partial int this[[Attr3] int x]
    {
        get => this._store[x];
        [Attr4] set => this._store[x] = value;
    }

    // results in a merged member emitted to metadata:
    public int this[[Attr1, Attr3] int x]
    {
        [Attr2] get => this._store[x];
        [Attr4] set => this._store[x] = value;
    }
}

Problemas abertos

Outros tipos de membro

Um membro da comunidade abriu uma discussão para solicitar suporte para eventos parciais. Na reunião do LDM em 2 de novembro de 2022, decidimos adiar o suporte a eventos, em parte porque ninguém na época o havia solicitado. Talvez queiramos rever essa questão, já que este pedido chegou, e já faz mais de um ano desde a última vez que discutimos isso.

Também poderíamos ir ainda mais longe na permissão de declarações parciais de construtores, operadores, campos etc., mas não está claro se a carga de design associada a elas é justificada só porque já estamos criando propriedades parciais.