field
palavra-chave em propriedades
Questão campeã: https://github.com/dotnet/csharplang/issues/8635
Resumo
Estenda todas as propriedades para permitir que elas façam referência a um campo de suporte gerado automaticamente usando a nova palavra-chave contextual field
. As propriedades podem agora também conter um acessor sem corpo e com um acessor que tenha corpo.
Motivação
As propriedades automáticas só permitem definir ou obter diretamente o campo subjacente, oferecendo algum nível de controlo apenas ao colocar modificadores de acesso nos acessadores. Às vezes, há a necessidade de ter controle adicional sobre o que acontece em um ou ambos os acessadores, mas isso confronta os usuários com a sobrecarga de declarar um campo de suporte. O nome do campo de suporte deve ser mantido em sincronia com a propriedade, e o campo de suporte tem escopo para toda a classe, o que pode resultar em desvio acidental dos acessadores de dentro da classe.
Existem vários cenários comuns. Dentro do getter, há inicialização lenta, ou valores padrão quando a propriedade nunca foi dada. Dentro do setter, há a aplicação de uma restrição para garantir a validade de um valor ou a deteção e propagação de atualizações, como o aumento do evento INotifyPropertyChanged.PropertyChanged
.
Nesses casos, até agora, você sempre tem que criar um campo de instância e escrever toda a propriedade por conta própria. Isso não só adiciona uma boa quantidade de código, mas também vaza o campo de suporte para o resto do escopo do tipo, quando muitas vezes é desejável que ele esteja disponível apenas para os corpos dos acessadores.
Glossário
Auto property: Abreviação de "propriedade implementada automaticamente" (§15.7.4). Os acessórios em uma propriedade auto não têm corpo. A implementação e o armazenamento de suporte são fornecidos pelo compilador. As propriedades automáticas têm
{ get; }
,{ get; set; }
ou{ get; init; }
.Acesso automático: Abreviação de "acessor implementado automaticamente". Este é um acessor que não tem corpo. A implementação e o armazenamento de suporte são fornecidos pelo compilador.
get;
,set;
einit;
são acessadores automáticos.Acessor completo: Este é um acessor que tem um corpo. A implementação não é fornecida pelo compilador, embora o armazenamento de apoio ainda possa estar disponível (como no exemplo
set => field = value;
).Propriedade apoiada por campo: Esta é uma propriedade que usa a palavra-chave
field
num acessador ou uma propriedade automática.Campo de apoio: Esta é a variável indicada pela palavra-chave
field
nos acessadores de uma propriedade, que também é implicitamente lida ou escrita em acessadores implementados automaticamente (get;
,set;
ouinit;
).
Design detalhado
Para propriedades com um acessador init
, tudo o que se aplica abaixo a set
seria aplicável também ao acessador init
.
Há duas alterações de sintaxe:
Há uma nova palavra-chave contextual,
field
, que pode ser usada dentro de órgãos de acesso à propriedade para acessar um campo de apoio para a declaração de propriedade (decisão LDM).As propriedades agora podem misturar e combinar acessadores automáticos com acessadores completos (decisão LDM). "Auto propriedade" continuará a significar uma propriedade cujos acessadores não têm corpos. Nenhum dos exemplos abaixo será considerado uma propriedade automática.
Exemplos:
{ get; set => Set(ref field, value); }
{ get => field ?? parent.AmbientValue; set; }
Ambos os acessadores podem ser acessadores completos com um ou ambos fazendo uso de field
:
{ get => field; set => field = value; }
{ get => field; set => throw new InvalidOperationException(); }
{ get => overriddenValue; set => field = value; }
{
get;
set
{
if (field == value) return;
field = value;
OnXyzChanged();
}
}
As propriedades que têm corpo de expressão e as propriedades que têm apenas um acessador de get
também podem usar field
:
public string LazilyComputed => field ??= Compute();
public string LazilyComputed { get => field ??= Compute(); }
As propriedades apenas de configuração também podem usar field
:
{
set
{
if (field == value) return;
field = value;
OnXyzChanged(new XyzEventArgs(value));
}
}
Mudanças significativas
A existência da palavra-chave contextual field
dentro dos corpos de acessadores de propriedade é uma mudança potencialmente disruptiva.
Como field
é uma palavra-chave e não um identificador, ele só pode ser "sombreado" por um identificador usando a rota normal de fuga de palavras-chave: @field
. Todos os identificadores nomeados field
declarados nos corpos de acessador de propriedade podem proteger-se contra interrupções ao atualizar de versões do C# anteriores à 14 adicionando o @
inicial.
Se uma variável chamada field
for declarada em um acessador de propriedade, um erro será relatado.
Na versão de idioma 14 ou posterior, um aviso é emitido se uma expressão primária field
se refere ao campo de base, mas teria se referido a um símbolo diferente em uma versão de idioma anterior.
Atributos direcionados ao campo
Assim como acontece com as propriedades automáticas, qualquer propriedade que use um campo de suporte em um de seus acessadores poderá usar atributos direcionados a campo:
[field: Xyz]
public string Name => field ??= Compute();
[field: Xyz]
public string Name { get => field; set => field = value; }
Um atributo de destino de campo permanecerá inválido, a menos que um acessador use um campo de suporte:
// ❌ Error, will not compile
[field: Xyz]
public string Name => Compute();
Inicializadores de propriedade
Propriedades com inicializadores podem usar field
. O campo de suporte é inicializado diretamente em vez de o setter ser chamado (decisão LDM).
Chamar um método setter para um inicializador não é uma opção; os inicializadores são processados antes de chamar os construtores base, e é ilegal chamar qualquer método de instância antes que o construtor base seja chamado. Isso também é importante para a inicialização padrão/atribuição definitiva de structs.
Isso gera um controle flexível sobre a inicialização. Se quiser inicializar sem chamar o setter, use um inicializador de propriedade. Se você quiser inicializar chamando o setter, deve atribuir à propriedade um valor inicial no construtor.
Aqui está um exemplo de onde isso é útil. Acreditamos que a palavra-chave field
será amplamente utilizada em modelos de visualização devido à solução elegante que oferece para o padrão INotifyPropertyChanged
. É provável que os definidores de propriedades do modelo de exibição sejam vinculados a dados à interface do usuário e provavelmente causem controle de alterações ou acionem outros comportamentos. O código a seguir precisa inicializar o valor padrão de IsActive
sem definir HasPendingChanges
para true
:
class SomeViewModel
{
public bool HasPendingChanges { get; private set; }
public bool IsActive { get; set => Set(ref field, value); } = true;
private bool Set<T>(ref T location, T value)
{
if (RuntimeHelpers.Equals(location, value))
return false;
location = value;
HasPendingChanges = true;
return true;
}
}
Essa diferença no comportamento entre um inicializador de propriedade e a atribuição do construtor também pode ser vista com propriedades automáticas virtuais em versões anteriores da linguagem:
using System;
// Nothing is printed; the property initializer is not
// equivalent to `this.IsActive = true`.
_ = new Derived();
class Base
{
public virtual bool IsActive { get; set; } = true;
}
class Derived : Base
{
public override bool IsActive
{
get => base.IsActive;
set
{
base.IsActive = value;
Console.WriteLine("This will not be reached");
}
}
}
Atribuição do construtor
Tal como acontece com as propriedades automáticas, a atribuição no construtor chama o setter (potencialmente virtual) se ele existir, e se não houver setter ele volta a atribuir diretamente ao campo de apoio.
class C
{
public C()
{
P1 = 1; // Assigns P1's backing field directly
P2 = 2; // Assigns P2's backing field directly
P3 = 3; // Calls P3's setter
P4 = 4; // Calls P4's setter
}
public int P1 => field;
public int P2 { get => field; }
public int P4 { get => field; set => field = value; }
public int P3 { get => field; set; }
}
Atribuição definitiva em structs
Mesmo que eles não possam ser referenciados no construtor, os campos de suporte indicados pela palavra-chave field
estão sujeitos à inicialização padrão e a avisos de desabilitação por padrão nas mesmas condições que quaisquer outros campos struct (LDM decision 1, LDM decision 2).
Por exemplo (esses diagnósticos são silenciosos por padrão):
public struct S
{
public S()
{
// CS9020 The 'this' object is read before all of its fields have been assigned, causing preceding implicit
// assignments of 'default' to non-explicitly assigned fields.
_ = P1;
}
public int P1 { get => field; }
}
public struct S
{
public S()
{
// CS9020 The 'this' object is read before all of its fields have been assigned, causing preceding implicit
// assignments of 'default' to non-explicitly assigned fields.
P2 = 5;
}
public int P2 { get => field; set => field = value; }
}
Propriedades de retorno de referência
Tal como acontece com as propriedades automáticas, a palavra-chave field
não estará disponível para utilização em propriedades de retorno de referência. As propriedades de retorno de referência não podem ter acessores definidos e, sem um acessador definido, o acessador get e o inicializador de propriedade seriam as únicas coisas capazes de acessar o campo de suporte. Na ausência de casos de uso para isso, agora não é o momento para que as propriedades que retornam referência comecem a ser escritas como propriedades automáticas.
Anulabilidade
Um princípio do recurso Nullable Reference Types era entender os padrões de codificação idiomática existentes em C# e exigir o mínimo de cerimônia possível em torno desses padrões. A proposta de palavra-chave field
permite que padrões simples e idiomáticos abordem cenários amplamente solicitados, como propriedades inicializadas preguiçosamente. É importante que os Tipos de Referência Nula combinem bem com esses novos padrões de codificação.
Objetivos:
Um nível razoável de segurança nula deve ser garantido para vários padrões de uso do recurso de palavra-chave
field
.Os padrões que usam a palavra-chave
field
devem parecer como se sempre fizessem parte da linguagem. Evite dificultar a vida do utilizador para ativar Nullable Reference Types em código que é perfeitamente idiomático para o recurso de palavra-chavefield
.
Um dos principais cenários são as propriedades inicializadas preguiçosamente:
public class C
{
public C() { } // It would be undesirable to warn about 'Prop' being uninitialized here
string Prop => field ??= GetPropValue();
}
As seguintes regras de anulabilidade serão aplicadas não apenas às propriedades que usam a palavra-chave field
, mas também às propriedades automáticas existentes.
Anulabilidade do campo de suporte
Consulte do Glossário para obter definições de novos termos.
O campo de apoio tem o mesmo tipo que a propriedade. No entanto, a sua anotação anulável pode diferir da propriedade. Para determinar essa anotação anulável, introduzimos o conceito de resiliência nula .
Null-resilience intuitivamente significa que o acessador get
da propriedade preserva a segurança de nulidade, mesmo quando o atributo contém o valor default
para seu tipo.
Um de propriedade
- Para efeitos desta análise, presume-se temporariamente que
field
tem a anotada com a anulabilidade, por exemplo,string?
. Isso faz com quefield
tenha talvez-nulo ou estado inicial padrão no acessadorget
, dependendo do seu tipo. - Em seguida, se a análise anulável do getter não produzir avisos anuláveis, a propriedade será resiliente nula. Caso contrário, não é resiliente nulo.
- Se a propriedade não tiver um acessor get, ela é, por definição, nula resiliente.
- Se o acessor 'get' for implementado automaticamente, a propriedade não é resiliente a nulos.
A anulabilidade do campo de apoio é determinada da seguinte forma:
- Se o campo tiver atributos de anulabilidade como
[field: MaybeNull]
,AllowNull
,NotNull
ouDisallowNull
, a anotação anulável do campo será a mesma que a anotação anulável da propriedade.- Isso ocorre porque quando o usuário começa a aplicar atributos de anulabilidade ao campo, não queremos mais inferir nada, queremos apenas que a anulabilidade seja o que o usuário disse.
- Se a propriedade que contém alheia ou anotada anulabilidade, então o campo de apoio tem a mesma anulabilidade que a propriedade.
- Se a propriedade que contém tiver anulabilidade de não anotada (por exemplo,
string
ouT
) ou tiver o atributo[NotNull]
, e a propriedade for resiliente nula, o campo de suporte terá anotado anulabilidade. - Se a propriedade que contém tiver anulabilidade de não anotada (por exemplo,
string
ouT
) ou tiver o atributo[NotNull]
, e a propriedade não for resiliente a nulas, o campo de suporte terá anulabilidade de não anotada.
Análise do construtor
Atualmente, uma propriedade auto é tratada de forma muito semelhante a um campo comum em análise de construtor anulável. Estendemos este tratamento às propriedades apoiadas por campo , tratando cada propriedade apoiada por campo como um proxy para o seu campo de apoio.
Atualizamos a seguinte linguagem de especificação da abordagem anterior proposta para conseguir isso:
A cada 'retorno' explícito ou implícito em um construtor, damos um aviso para cada membro cujo estado de fluxo é incompatível com suas anotações e atributos de anulabilidade. Se o membro for uma propriedade com suporte de campo, a anotação anulável do campo de suporte será usada para essa verificação. Caso contrário, a anotação anulável do próprio membro é usada. Um proxy razoável para isso é: se atribuir o membro a si mesmo no ponto de retorno produziria um aviso de anulabilidade, então um aviso de anulabilidade será produzido no ponto de retorno.
Note-se que esta é essencialmente uma análise interprocessual restrita. Antecipamos que, para analisar um construtor, será necessário fazer a análise de vinculação e "resiliência nula" em todos os acessadores get aplicáveis no mesmo tipo, que usam a palavra-chave contextual field
e têm anulabilidade de não anotada. Especulamos que isso não é proibitivamente caro porque os corpos getter geralmente não são muito complexos, e que a análise de "resiliência nula" só precisa ser realizada uma vez, independentemente de quantos construtores estão no tipo.
Análise de setter
Para simplificar, usamos os termos "setter" e "set accessor" para nos referirmos a um acessador set
ou init
.
Há uma necessidade de verificar se os setters das propriedades de baseadas em campo do realmente inicializam o campo de suporte.
class C
{
string Prop
{
get => field;
// getter is not null-resilient, so `field` is not-annotated.
// We should warn here that `field` may be null when exiting.
set { }
}
public C()
{
Prop = "a"; // ok
}
public static void Main()
{
new C().Prop.ToString(); // NRE at runtime
}
}
O estado de fluxo inicial do campo de suporte no setter de uma propriedade apoiada por campo é determinado da seguinte maneira:
- Se a propriedade tiver um inicializador, o estado de fluxo inicial será o mesmo que o estado de fluxo da propriedade depois de visitar o inicializador.
- Caso contrário, o estado de fluxo inicial é o mesmo que o estado de fluxo dado por
field = default;
.
Em cada 'retorno' explícito ou implícito no setter, um aviso é relatado se o estado de fluxo do campo de suporte for incompatível com suas anotações e atributos de anulabilidade.
Comentários
Esta formulação é intencionalmente muito semelhante aos campos comuns em construtores. Essencialmente, como apenas os acessadores de propriedade podem realmente se referir ao campo de apoio, o setter é tratado como um "mini-construtor" para o campo de suporte.
Assim como nos campos comuns, geralmente sabemos que a propriedade foi inicializada no construtor porque foi definida, mas não necessariamente. Simplesmente retornar dentro de um ramo onde Prop != null
era verdade também é bom o suficiente para nossa análise do construtor, uma vez que entendemos que mecanismos não rastreados podem ter sido usados para definir a propriedade.
Foram consideradas alternativas; consulte a seção Alternativas de anulabilidade.
nameof
Em lugares onde field
é uma palavra-chave, nameof(field)
falhará ao compilar (decisão do LDM), como nameof(nint)
. Não é como nameof(value)
, que deve ser usado quando os definidores de propriedade geram ArgumentException, como ocorre em algumas bibliotecas do .NET Core. Em contraste, nameof(field)
não tem casos de uso esperados.
Substituições
As propriedades de substituição podem usar field
. Tais usos de field
referem-se ao campo de suporte para a propriedade sobreposta, distinto do campo de suporte da propriedade base, se esta o tiver. Não há ABI para expor o campo de suporte de uma propriedade base a classes de substituição, uma vez que isso quebraria o encapsulamento.
Como acontece com as propriedades automáticas, as propriedades que usam a palavra-chave field
e substituem uma propriedade base devem substituir todos os acessores (decisão LDM).
Capturas
field
deveria poder ser capturado em funções locais e lambdas, e referências a field
dentro de funções locais e lambdas são permitidas mesmo que não haja outras referências (decisão LDM 1, decisão LDM 2):
public class C
{
public static int P
{
get
{
Func<int> f = static () => field;
return f();
}
}
}
Avisos de uso de campo
Quando a palavra-chave field
é usada em um acessador, a análise existente do compilador de campos não atribuídos ou não lidos incluirá esse campo.
- CS0414: O campo de suporte para a propriedade 'Xyz' é atribuído, mas seu valor nunca é usado
- CS0649: O campo de suporte para a propriedade 'Xyz' nunca é atribuído e sempre terá seu valor padrão
Alterações às especificações
Sintaxe
Ao compilar com a versão de idioma 14 ou superior, field
é considerada uma palavra-chave quando usada como uma expressão primária (LDM decision) nos seguintes locais (LDM decision):
- Em corpos de método de
get
,set
e accessores deinit
em propriedades , mas não em indexadores - Nos atributos aplicados a esses acessadores
- Em expressões lambda aninhadas e funções locais, e em expressões LINQ nesses acessadores
Em todos os outros casos, inclusive ao compilar com a versão de idioma 12 ou inferior, field
é considerado um identificador.
primary_no_array_creation_expression
: literal
+ | 'field'
| interpolated_string_expression
| ...
;
Propriedades
§15.7.1Propriedades Gerais -
Um property_initializer só pode ser fornecido para
uma propriedade implementada automaticamente euma propriedade que tenha um campo de suporte que será emitido. O property_initializer provoca a inicialização do campo subjacente de tais propriedades com o valor fornecido pela expressão .
§15.7.4Propriedades implementadas automaticamente
Uma propriedade implementada automaticamente (ou auto-propriedade, para abreviar), é uma propriedade não-abstrata, não-externa, não-ref-value, com
corpos de acessor somente ponto-e-vírgula. As propriedades automáticas devem ter um acessor get e podem, opcionalmente, ter um acessador definido.uma ou ambas as opções:
- um acessor com um corpo composto apenas por um ponto e vírgula
- uso da palavra-chave contextual
field
dentro dos acessadores oucorpo de expressão da propriedadeQuando uma propriedade é especificada como uma propriedade implementada automaticamente, um campo de suporte oculto
sem nome fica automaticamente disponível para a propriedade , e os acessadores são implementados para ler e gravar nesse campo de suporte . Para propriedades automáticas, qualquer acessador deget
somente ponto-e-vírgula é implementado para leitura e qualquer acessador deset
somente ponto-e-vírgula para gravar em seu campo de suporte.
O campo de suporte oculto é inacessível, ele pode ser lido e escrito apenas através dos acessadores de propriedade implementados automaticamente, mesmo dentro do tipo que contém.O campo de suporte pode ser referenciado diretamente usando a palavra-chavefield
em todos os acessadores e no corpo da expressão de propriedade. Como o campo não tem nome, ele não pode ser usado em uma expressãonameof
.Se a propriedade automática não tiver
acessador definidoapenas um acessor de obtenção somente ponto-e-vírgula, o campo de suporte será consideradoreadonly
(§15.5.3). Assim como um camporeadonly
, uma propriedade automática de leitura só (sem um acessador 'set' ou um acessador 'init') também pode ser atribuída no corpo de um construtor da classe incluída. Tal atribuição atribui diretamente aocampo de suportesomente leitura da propriedade.Uma propriedade automática não pode ter apenas um único acessador de
set
somente ponto-e-vírgula sem um acessadorget
.Uma propriedade automática pode, opcionalmente, ter um property_initializer, que é aplicado diretamente ao campo de apoio como um variable_initializer (§17.7).
O exemplo a seguir:
// No 'field' symbol in scope.
public class Point
{
public int X { get; set; }
public int Y { get; set; }
}
é equivalente à seguinte declaração:
// No 'field' symbol in scope.
public class Point
{
public int X { get { return field; } set { field = value; } }
public int Y { get { return field; } set { field = value; } }
}
que é equivalente a:
// No 'field' symbol in scope.
public class Point
{
private int __x;
private int __y;
public int X { get { return __x; } set { __x = value; } }
public int Y { get { return __y; } set { __y = value; } }
}
O exemplo a seguir:
// No 'field' symbol in scope.
public class LazyInit
{
public string Value => field ??= ComputeValue();
private static string ComputeValue() { /*...*/ }
}
é equivalente à seguinte declaração:
// No 'field' symbol in scope.
public class Point
{
private string __value;
public string Value { get { return __value ??= ComputeValue(); } }
private static string ComputeValue() { /*...*/ }
}
Alternativas
Alternativas de anulabilidade
Além da
Não fazer nada
Não poderíamos introduzir nenhum comportamento especial aqui. Com efeito:
- Trate uma propriedade com campo de apoio da mesma forma que as propriedades automáticas são tratadas hoje em dia — devem ser inicializadas no construtor, exceto quando marcadas como obrigatórias, etc.
- Nenhum tratamento especial da variável de campo ao analisar acessores de propriedade. É simplesmente uma variável com o mesmo tipo e anulabilidade que a propriedade.
Observe que isso resultaria em avisos incômodos para cenários de "propriedade preguiçosa", caso em que os utilizadores provavelmente precisariam atribuir null!
ou semelhante para silenciar os avisos do construtor.
Uma "opção secundária" que podemos considerar é ignorar completamente também as propriedades ao usar a palavra-chave field
para a análise de construtores anuláveis. Nesse caso, não haveria avisos em nenhum lugar sobre o usuário precisar inicializar qualquer coisa, mas também nenhum incômodo para o usuário, independentemente do padrão de inicialização que ele possa estar usando.
Como estamos planejando enviar apenas o recurso de palavra-chave field
no Preview LangVersion no .NET 9, esperamos ter alguma capacidade de alterar o comportamento anulável para o recurso no .NET 10. Portanto, poderíamos considerar a adoção de uma solução de "baixo custo" como esta no curto prazo, e crescer até uma das soluções mais complexas a longo prazo.
field
-atributos de anulabilidade direcionados
Poderíamos introduzir os seguintes incumprimentos, alcançando um nível razoável de segurança nula, sem envolver qualquer análise interprocessual:
- A variável
field
sempre tem a mesma anotação anulável que a propriedade. - Os atributos de anulabilidade
[field: MaybeNull, AllowNull]
etc. podem ser usados para personalizar a anulabilidade do campo de apoio. - As propriedades com suporte de campo são verificadas para inicialização em construtores com base na anotação anulável e nos atributos do campo.
- Os setters em propriedades com suporte de campo verificam a inicialização de
field
de forma semelhante aos construtores.
Isso significaria que o "cenário moderadamente preguiçoso" ficaria assim:
class C
{
public C() { } // no need to warn about initializing C.Prop, as the backing field is marked nullable using attributes.
[field: AllowNull, MaybeNull]
public string Prop => field ??= GetPropValue();
}
Uma razão pela qual evitamos usar atributos de anulabilidade aqui é que os que temos são realmente orientados em torno da descrição de entradas e saídas de assinaturas. Eles são complicados de usar para descrever a anulabilidade de variáveis de longa duração.
- Na prática,
[field: MaybeNull, AllowNull]
é necessário para fazer com que o campo se comporte "razoavelmente" como uma variável anulável, o que dá um estado de fluxo inicial talvez-nulo e permite que possíveis valores nulos sejam gravados nele. Parece complicado pedir aos utilizadores que façam isso para cenários relativamente comuns de "minimamente preguiçoso". - Se adotássemos essa abordagem, consideraríamos adicionar um aviso quando
[field: AllowNull]
é usado, sugerindo também adicionarMaybeNull
. Isto deve-se ao fato de que AllowNull por si só não faz o que os utilizadores precisam de uma variável nula: ele assume que o campo é inicialmente não nulo, quando ainda não vimos nada ser gravado nele. - Também poderíamos considerar ajustar o comportamento de
[field: MaybeNull]
na palavra-chavefield
, ou mesmo campos em geral, para permitir que nulos também sejam gravados na variável, como seAllowNull
também estivessem implicitamente presentes.
Perguntas respondidas sobre LDM
Locais de sintaxe para palavras-chave
Em acessadores onde field
e value
podem se ligar a um campo de suporte sintetizado ou a um parâmetro setter implícito, em quais locais de sintaxe os identificadores devem ser considerados palavras-chave?
- sempre
- apenas expressões primárias
- nunca
Os dois primeiros casos são alterações significativas.
Se os identificadores forem sempre considerados palavras-chave, isso constitui uma mudança significativa para o seguinte, por exemplo:
class MyClass
{
private int field;
public int P => this.field; // error: expected identifier
private int value;
public int Q
{
set { this.value = value; } // error: expected identifier
}
}
Se os identificadores forem palavras-chave quando usados apenas como expressões primárias, a alteração de quebra será menor. A interrupção mais comum pode ser o uso não qualificado de um membro existente chamado field
.
class MyClass
{
private int field;
public int P => field; // binds to synthesized backing field rather than 'this.field'
}
Há também uma interrupção quando field
ou value
é redeclarado numa função aninhada. Esta pode ser a única pausa para value
para expressões primárias.
class MyClass
{
private IEnumerable<string> _fields;
public bool HasNotNullField
{
get => _fields.Any(field => field is { }); // 'field' binds to synthesized backing field
}
public IEnumerable<string> Fields
{
get { return _fields; }
set { _fields = value.Where(value => Filter(value)); } // 'value' binds to setter parameter
}
}
Se os identificadores nunca considerados palavras-chave, os identificadores só serão vinculados a um campo de suporte sintetizado ou ao parâmetro implícito quando os identificadores não se ligarem a outros membros. Não há nenhuma alteração crítica para este caso.
Resposta
field
é uma palavra-chave em acessadores apropriados quando usada como uma expressão primária apenas; value
nunca é considerada uma palavra-chave.
Cenários semelhantes a { set; }
{ set; }
é atualmente proibido e isso faz sentido: o campo que isso cria nunca pode ser lido. Existem agora novas maneiras de acabar numa situação em que o setter introduz um campo de suporte que nunca é lido, como a expansão de { set; }
para { set => field = value; }
.
Qual destes cenários deve ser autorizado a compilar? Suponha que o aviso "campo nunca é lido" se aplicaria da mesma forma que um campo declarado manualmente.
-
{ set; }
- Proibido hoje, continuar a proibir { set => field = value; }
{ get => unrelated; set => field = value; }
{ get => unrelated; set; }
-
{ set { if (field == value) return; field = value; SendEvent(nameof(Prop), value); } }
-
{ get => unrelated; set { if (field == value) return; field = value; SendEvent(nameof(Prop), value); } }
Resposta
Só proibir o que hoje já é proibido em propriedades automáticas, o sem corpo set;
.
field
no evento de acesso
Deve field
ser uma palavra-chave em um acessador de eventos e o compilador deve gerar um campo de suporte?
class MyClass
{
public event EventHandler E
{
add { field += value; }
remove { field -= value; }
}
}
Recomendação: field
não é uma palavra-chave dentro de um acessador de eventos, e não é gerado nenhum campo de suporte.
Resposta
Recomendação tomada.
field
não é uma palavra-chave dentro de um acessador de evento e nenhum campo de suporte é gerado.
Nulidade de field
A proposta de anulabilidade de field
deve ser aceite? Consulte a seção Nullability e a pergunta aberta no seu interior.
Resposta
Adoção de uma proposta de carácter geral. O comportamento específico ainda precisa de mais revisão.
field
no inicializador de propriedade
Deve field
ser uma palavra-chave num inicializador de propriedade e ligar ao campo de apoio?
class A
{
const int field = -1;
object P1 { get; } = field; // bind to const (ok) or backing field (error)?
}
Existem cenários úteis para referenciar o campo de suporte no inicializador?
class B
{
object P2 { get; } = (field = 2); // error: initializer cannot reference instance member
static object P3 { get; } = (field = 3); // ok, but useful?
}
No exemplo acima, a vinculação ao campo de suporte deve resultar em um erro: "o inicializador não pode fazer referência a um campo não estático".
Resposta
Vincularemos o inicializador como nas versões anteriores do C#. Não colocaremos o campo de suporte no escopo, nem impediremos a referência a outros membros nomeados field
.
Interação com propriedades parciais
Inicializadores
Quando uma propriedade parcial usa field
, que partes podem ter um inicializador?
partial class C
{
public partial int Prop { get; set; } = 1;
public partial int Prop { get => field; set => field = value; } = 2;
}
- Parece claro que um erro deve ocorrer quando ambas as partes têm um inicializador.
- Podemos pensar em casos de uso em que a parte de definição ou implementação pode querer definir o valor inicial do
field
. - Parece que, se permitirmos o inicializador na parte de definição, isso efetivamente está a forçar o desenvolvedor a usar
field
para que o programa seja válido. Tudo bem? - Pensamos que será comum os geradores utilizarem
field
sempre que for necessário um campo de apoio do mesmo tipo na implementação. Isso ocorre em parte porque os geradores geralmente querem permitir para que seus usuários usem atributos direcionados[field: ...]
na parte da definição de propriedade. Usar a palavra-chavefield
poupa o implementador do gerador do trabalho de "encaminhar" tais atributos para algum campo gerado e eliminar os avisos sobre a propriedade. É provável que esses mesmos geradores também queiram permitir que o usuário especifique um valor inicial para o campo.
Recomendação: Permitir um inicializador numa das partes de uma propriedade parcial quando a parte de implementação usar field
. Comunique um erro se ambas as partes tiverem um inicializador.
Resposta
Recomendação aceite. Declarar ou implementar locais de propriedade pode usar um inicializador, mas não ambos ao mesmo tempo.
Acessores automáticos
Conforme originalmente projetado, a implementação parcial da propriedade deve ter corpos para todos os métodos de acesso. No entanto, as iterações recentes do recurso de palavra-chave field
incorporaram a noção de "auto-accessors". As implementações de propriedade parcial devem ser capazes de usar esses acessadores? Se forem utilizados exclusivamente, será indistinguível de uma declaração definidora.
partial class C
{
public partial int Prop0 { get; set; }
public partial int Prop0 { get => field; set => field = value; } // this is equivalent to the two "semi-auto" forms below.
public partial int Prop1 { get; set; }
public partial int Prop1 { get => field; set; } // is this a valid implementation part?
public partial int Prop2 { get; set; }
public partial int Prop2 { get; set => field = value; } // what about this? will there be disagreement about which is the "best" style?
public partial int Prop3 { get; }
public partial int Prop3 { get => field; } // it will only be valid to use at most 1 auto-accessor, when a second accessor is manually implemented.
Recomendação: Não permita acessores automáticos em implementações de propriedade parcial, porque as limitações em torno de quando eles seriam utilizáveis são mais confusas de seguir do que o benefício de permiti-los.
Resposta
Pelo menos um acessador de implementação deve ser implementado manualmente, mas o outro acessador pode ser implementado automaticamente.
Campo somente leitura
Quando o campo de suporte sintetizado deve ser considerado somente leitura?
struct S
{
readonly object P0 { get => field; } = ""; // ok
object P1 { get => field ??= ""; } // ok
readonly object P2 { get => field ??= ""; } // error: 'field' is readonly
readonly object P3 { get; set { _ = field; } } // ok
readonly object P4 { get; set { field = value; } } // error: 'field' is readonly
}
Quando o campo auxiliar é considerado somente leitura, o campo emitido nos metadados é marcado initonly
, e um erro é relatado se field
for modificado fora de um inicializador ou construtor.
Recomendação: O campo de suporte sintetizado é somente leitura quando o tipo que contém é um struct
e a propriedade ou o tipo que contém é declarado readonly
.
Resposta
A recomendação é aceite.
Contexto apenas para leitura e set
Deve um acessador de set
ser permitido num contexto readonly
para uma propriedade que usa field
?
readonly struct S1
{
readonly object _p1;
object P1 { get => _p1; set { } } // ok
object P2 { get; set; } // error: auto-prop in readonly struct must be readonly
object P3 { get => field; set { } } // ok?
}
struct S2
{
readonly object _p1;
readonly object P1 { get => _p1; set { } } // ok
readonly object P2 { get; set; } // error: auto-prop with set marked readonly
readonly object P3 { get => field; set { } } // ok?
}
Resposta
Pode haver cenários para isso em que você está implementando um acessador de set
em uma estrutura de readonly
e passando por ele ou lançando. Vamos permitir isso.
Código [Conditional]
Deve o campo sintetizado ser gerado apenas quando field
é usado em chamadas omitidas para métodos condicionais ?
Por exemplo, deverá ser gerado um campo de apoio para o seguinte numa compilação não-DEBUG?
class C
{
object P
{
get
{
Debug.Assert(field is null);
return null;
}
}
}
Como referência, campos relativos aos parâmetros primários do construtor de
Recomendação: O campo de suporte é gerado apenas quando field
é usado em chamadas omitidas para métodos condicionais .
Resposta
O código Conditional
pode ter efeitos no código não condicional, como Debug.Assert
alterando a anulabilidade. Seria estranho se field
não tivesse impactos semelhantes. Também é improvável que apareça na maioria dos códigos, então faremos a coisa simples e aceitaremos a recomendação.
Propriedades de interface e acessores automáticos
Uma combinação de acessadores implementados manualmente e automaticamente é reconhecida para uma propriedade interface
em que o acessador implementado automaticamente se refere a um campo de suporte sintetizado?
Para uma propriedade de instância, será indicado um erro informando que os campos de instância não são suportados.
interface I
{
object P1 { get; set; } // ok: not an implementation
object P2 { get => field; set { field = value; }} // error: instance field
object P3 { get; set { } } // error: instance field
static object P4 { get; set { } } // ok: equivalent to { get => field; set { } }
}
Recomendação: Os acessores automáticos são reconhecidos em propriedades interface
e os acessadores automáticos referem-se a um campo de suporte sintetizado. Para uma propriedade de instância, é relatado um erro de que os campos de instância não são suportados.
Resposta
A padronização em torno do próprio campo de instância ser a causa do erro é consistente com propriedades parciais em classes, e nós gostamos desse resultado. A recomendação é aceite.
C# feature specifications