É possível acessar os campos de apoio por trás das propriedades auto-implementadas?

Eu sei que posso usar a syntax detalhada das propriedades:

private string _postalCode; public string PostalCode { get { return _postalCode; } set { _postalCode = value; } } 

Ou posso usar propriedades implementadas automaticamente.

 public string PostalCode { get; set; } 

Posso de alguma forma acessar o campo de apoio que está por trás da propriedade auto-implementada? (Neste exemplo, seria _ postalCode ).


Edit : Minha pergunta não é sobre design, mas sim sobre, digamos, capacidade teórica para fazê-lo.

Eu não sei sobre você, mas eu escrevi código em projetos em outras empresas, e agora eu quero saber como eu fiz alguma coisa! Por isso, geralmente é mais rápido fazer uma busca na web pela resposta, e isso me trouxe até aqui.

No entanto, minhas razões são diferentes. Eu estou testando a unidade, e não me importo com o que os puristas têm a dizer, mas como parte de uma configuração para um teste de unidade, estou tentando invocar um certo estado para um determinado object. Mas esse estado deve ser controlado internamente. Eu não quero algum outro desenvolvedor acidentalmente mexendo com o estado, o que poderia ter efeitos de longo alcance sobre o sistema. Por isso, deve ser definido em particular! No entanto, como você faz a unidade testar algo assim sem invocar comportamentos que (esperançosamente) nunca acontecerão? Em tais cenários, acredito que usar a reflection com o teste de unidade é útil.

A alternativa é expor coisas que não queremos expor, para que possamos testá-las em unidade! Sim, eu vi isso em ambientes da vida real, e só de pensar nisso ainda me faz balançar a cabeça.

Então, espero que o código abaixo seja útil.

Existem dois methods aqui apenas para separação de interesses, e também para ajudar na legibilidade. O reflexo é uma coisa que gira a cabeça para a maioria dos desenvolvedores, que na minha experiência ou evitam isso, ou evitam isso como a praga!

 private string _getBackingFieldName(string propertyName) { return string.Format("< {0}>k__BackingField", propertyName); } private FieldInfo _getBackingField(object obj, string propertyName) { return obj.GetType().GetField(_getBackingFieldName(propertyName), BindingFlags.Instance | BindingFlags.NonPublic); } 

Eu não sei em que convenções de código você trabalha, mas pessoalmente, eu gosto de methods auxiliares para serem privados e começar com uma letra minúscula. Eu não acho isso óbvio o suficiente ao ler, então eu também gosto do sublinhado anterior.

Há discussão sobre campos de apoio e sua nomeação automática. Para efeitos de testes unitários, você saberá muito rapidamente se mudou ou não! Não será catastrófico para o seu código real, apenas os testes. Assim, podemos fazer suposições simples sobre a nomeação de nomes – como eu fiz acima. Você pode discordar, e tudo bem.

O auxiliar mais difícil _getBackingField retorna um desses tipos de reflection, FieldInfo . Fiz uma suposição aqui também, de que o campo de apoio que você procura é de um object que é uma instância, em vez de ser estático. Você pode dividir isso em argumentos para serem passados, se desejar, mas as águas certamente serão mais confusas para o desenvolvedor médio que pode querer a funcionalidade, mas não o entendimento.

A coisa útil sobre FieldInfo é que eles podem definir campos em objects que correspondem ao FieldInfo . Isso é melhor explicado com um exemplo:

 var field = _getBackingField(myObjectToChange, "State"); field.SetValue(myObjectToChange, ObjectState.Active); 

Nesse caso, o campo é de um tipo de enumeração chamado ObjectState . Os nomes foram alterados para proteger os inocentes! Então, na segunda linha, você pode ver que, acessando o FieldInfo retornado anteriormente, eu posso chamar o método SetValue , que você pode achar que já deveria estar relacionado ao seu object, mas isso não acontece! Essa é a natureza da reflection – FieldInfo separa um campo de onde veio, portanto, você deve informar com qual instância trabalhar com ( myObjectToChange ) e, portanto, o valor que deseja ter, neste caso, ObjectState.Active .

Então, para encurtar a história, a programação orientada a objects nos impedirá de fazer coisas tão desagradáveis ​​quanto acessar campos privados e, pior, alterá-los quando o desenvolvedor do código não pretendesse. Qual é bom! Essa é uma das razões pelas quais o C # é tão valioso e apreciado pelos desenvolvedores.

No entanto, a Microsoft nos deu Reflexão e, através dela, manejamos uma poderosa arma. Pode ser feio e muito lento, mas, ao mesmo tempo, expõe as profundezas mais profundas do funcionamento interno do MSIL (MicroSoft Intermediate Language) – abreviado para o inglês – e nos permite quebrar praticamente todas as regras do livro. sendo um bom exemplo.

Isso vem direto do MSDN :

No C # 3.0 e posterior, as propriedades implementadas automaticamente tornam a declaração de propriedade mais concisa quando nenhuma lógica adicional é necessária nos accessres da propriedade. Eles também permitem que o código do cliente crie objects. Quando você declara uma propriedade conforme mostrado no exemplo a seguir, o compilador cria um campo de apoio anônimo privado que só pode ser acessado por meio dos accessres get e set da propriedade.

Então você não pode.

No Visual Studio 2010, pelo menos, você pode obter a lista de campos privados em uma class usando reflection se você declarar explicitamente que deseja os campos de instância não públicos:

 FieldInfo[] myInfo = ClassWithPostalCode.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic); 

Você pode, então, percorrer o array FieldInfo. Nesse caso, você verá que o nome do campo de apoio provavelmente será

k__BackingField

Percebi que todas as propriedades automáticas parecem seguir o padrão de nome da propriedade entre colchetes angulares seguido por “k__BackingField”, mas lembre-se de que isso não é oficial e pode ser alterado em futuras versões do .Net. Não estou totalmente certo de que não é diferente em versões anteriores, para esse assunto.

Depois de saber o nome do campo, você pode obter seu valor desta maneira:

 object oValue = obj.GetType().InvokeMember(fi.Name , BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance , null, obj, null); 

Veja o seguinte trecho deste documento :

Propriedades implementadas automaticamente (auto-implementadas) automatizam esse padrão. Mais especificamente, declarações de propriedades não abstratas podem ter corpos de acessadores de ponto-e-vírgula. Ambos os accessres devem estar presentes e ambos devem ter corpos de ponto e vírgula, mas podem ter modificadores de acessibilidade diferentes. Quando uma propriedade é especificada dessa forma, um campo de apoio será gerado automaticamente para a propriedade e os acessadores serão implementados para ler e gravar nesse campo de apoio. O nome do campo de apoio é compilador gerado e inacessível ao usuário.

Assim, não há como acessar campos. Utilize sua primeira abordagem. As propriedades implementadas automaticamente são específicas para o caso em que você não precisa acessar o campo de apoio.

ATUALIZAÇÃO: https://github.com/jbevain/mono.reflection vem com um método de resolução de campo de apoio que funciona com auto-propriedades geradas por C #, VB.NET e F #. O pacote NuGet está em https://www.nuget.org/packages/Mono.Reflection/

ORIGINAL: Acabei com este método bastante flexível apenas para propriedades automáticas C #. Como as outras respostas deixam claro, isso não é portátil e não funcionará se a implementação do compilador usar um esquema de nomenclatura de campo de apoio diferente de k__BackingField . Até onde eu vi, todas as implementações de compiladores C # atualmente usam esse esquema de nomenclatura. Os compiladores VB.NET e F # usam outro esquema de nomenclatura que não funciona com este código.

 private static FieldInfo GetBackingField(PropertyInfo pi) { if (!pi.CanRead || !pi.GetGetMethod(nonPublic:true).IsDefined(typeof(CompilerGeneratedAttribute), inherit:true)) return null; var backingField = pi.DeclaringType.GetField($"< {pi.Name}>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic); if (backingField == null) return null; if (!backingField.IsDefined(typeof(CompilerGeneratedAttribute), inherit:true)) return null; return backingField; } 

Não há. Se você quiser acessar o campo de apoio, não use propriedades automáticas e faça o seu próprio.

Na documentação de propriedades implementadas automaticamente :

Quando você declara uma propriedade conforme mostrado no exemplo a seguir, o compilador cria um campo de apoio anônimo privado que só pode ser acessado por meio dos accessres get e set da propriedade.

As propriedades implementadas automaticamente são uma versão “mais preguiçosa” de uma propriedade implementada manualmente com um campo de apoio. Como eles não permitem nenhuma lógica adicional, a única coisa que você pode fazer com eles é ler ou escrever no campo privado “oculto”. Você ainda pode usar o modificador private para limitar o access a um dos acessadores (geralmente, o set seria o privado nesse caso), se você quiser que seus methods privados tenham esse privilégio.

Se você quisesse acessar os campos privados de outra class (como uma class da BCL), você poderia usar o Reflection para fazê-lo (como explicado nestes exemplos ), mas seria um hack desagradável onde ninguém garantiria A mudança de boletim na origem do framework não quebraria seu código no futuro.

Mas como você já optou pela implementação automática, não vejo motivo para querer acessar o campo de apoio. Acessar o campo diretamente ou através dos accessres de propriedade auto-implementados não tem diferença nem benefício. Você poderia, por exemplo, argumentar que é possível usar o Interlocked para modificar o campo atomicamente (o que não pode ser feito em uma propriedade), mas não impõe nenhum “thread com segurança” quando todos os outros têm access ao campo através da propriedade. sem atomicidade.

Sem mencionar que o compilador provavelmente irá inline cada chamada, então não há diferença de desempenho também.