È ansible accedere ai campi di supporto dietro le proprietà autoattive?

So che posso usare la syntax dettagliata per le proprietà:

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

O posso usare le proprietà auto-implementate.

 public string PostalCode { get; set; } 

Posso accedere in qualche modo al campo di supporto che si trova dietro la proprietà auto-implementata? (In questo esempio sarebbe _ codice postale ).


Modifica : La mia domanda non riguarda il design, ma piuttosto, diciamo, la capacità teorica di farlo.

Non so voi, ma ho scritto codice in progetti in altre società, e ora voglio sapere come ho fatto qualcosa! Quindi di solito è più veloce fare una ricerca sul web per la risposta, e mi ha portato qui.

Tuttavia, le mie ragioni sono diverse. Sono un test unitario, e non mi interessa cosa hanno da dire i puristi, ma come parte di un setup per un test unitario, sto cercando di invocare un certo stato per un dato object. Ma quello stato dovrebbe essere controllato internamente. Non voglio che qualche altro sviluppatore caschi accidentalmente con lo stato, il che potrebbe avere effetti di vasta portata sul sistema. Quindi deve essere impostato privatamente! Eppure, come puoi testare qualcosa di simile senza invocare comportamenti che (si spera) non accadrà mai? In tali scenari, ritengo sia utile l’uso della riflessione con il test delle unità.

L’alternativa è esporre le cose che non vogliamo esposte, quindi possiamo testarle unitamente! Sì, l’ho visto negli ambienti della vita reale, e il solo pensarci mi fa ancora scuotere la testa.

Quindi, spero che il codice qui sotto possa essere utile.

Ci sono due metodi qui solo per la separazione delle preoccupazioni, in realtà, e anche per facilitare la leggibilità. La riflessione è roba capricciosa per la maggior parte degli sviluppatori, che nella mia esperienza ne evitano, o lo evitano come la peste!

 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); } 

Non so a quali convenzioni del codice si lavori, ma personalmente, mi piacciono i metodi di supporto per essere privati ​​e iniziare con una lettera minuscola. Non lo trovo abbastanza ovvio durante la lettura, quindi mi piace anche il carattere di sottolineatura precedente.

C’è una discussione sui campi di supporto e sulla loro denominazione automatica. Ai fini dei test unitari, lo saprai abbastanza rapidamente se è cambiato o no! Neanche il tuo vero codice sarà catastrofico, solo i test. Quindi possiamo fare delle semplici assunzioni sulla denominazione dei nomi, come ho detto sopra. Potresti non essere d’accordo, e va bene.

L’helper più difficile _getBackingField restituisce uno di quei tipi di riflessione, FieldInfo . Ho anche ipotizzato che il campo di supporto che stai cercando provenga da un object che è un’istanza, invece di essere statico. Puoi suddividerlo in argomenti da inoltrare, se lo desideri, ma le acque saranno sicuramente più fangose ​​per lo sviluppatore medio che potrebbe desiderare la funzionalità ma non la comprensione.

La cosa pratica di FieldInfo è che possono impostare campi su oggetti che corrispondono a FieldInfo . Questo è meglio spiegato con un esempio:

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

In questo caso, il campo è di un tipo di enumerazione chiamato ObjectState . I nomi sono stati cambiati per proteggere gli innocenti! Quindi, nella seconda riga, puoi vedere che accedendo al FieldInfo restituito in precedenza, posso chiamare il metodo SetValue , che potresti pensare dovrebbe essere già correlato al tuo object, ma non lo fa! Questa è la natura della riflessione: FieldInfo separa un campo da dove proviene, quindi devi dirgli con quale istanza lavorare ( myObjectToChange ) e, quindi, il valore che vuoi che abbia, in questo caso, ObjectState.Active .

Quindi, per farla breve, la programmazione orientata agli oggetti ci impedirà di fare cose così brutte come accedere ai campi privati ​​e, peggio, modificarli quando lo sviluppatore del codice non ha intenzione di farlo. Che è buono! Questo è uno dei motivi per cui C # è così prezioso e apprezzato dagli sviluppatori.

Tuttavia, Microsoft ci ha dato la Riflessione, e attraverso di essa, brandiamo un’arma potente. Può essere brutto e molto lento, ma allo stesso tempo espone le profondità più profonde del funzionamento interno di MSIL (MicroSoft Intermediate Language) -IL in breve – e ci consente di infrangere praticamente ogni regola del libro, questo essere un buon esempio

Questo deriva direttamente da MSDN :

In C # 3.0 e versioni successive, le proprietà auto-implementate rendono la dichiarazione di proprietà più concisa quando non è richiesta alcuna logica aggiuntiva negli accessor di proprietà. Inoltre, consentono al codice client di creare oggetti. Quando si dichiara una proprietà come illustrato nell’esempio seguente, il compilatore crea un campo di backup anonimo privato a cui è ansible accedere solo tramite gli attributi get e set della proprietà.

Quindi no, non puoi.

Almeno in Visual Studio 2010, è ansible ottenere l’elenco dei campi privati ​​in una class utilizzando la reflection se si specifica esplicitamente che si desidera che i campi dell’istanza non pubblici:

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

È quindi ansible eseguire il ciclo attraverso l’array FieldInfo. In questo caso vedrai che probabilmente il nome del campo di supporto sarà

k__BackingField

Ho notato che tutte le proprietà automatiche sembrano seguire lo schema del nome della proprietà tra parentesi angolari seguito da “k__BackingField”, ma tieni presente che questo non è ufficiale e può cambiare nelle versioni future di .Net. Non sono del tutto certo che non sia diverso nelle versioni precedenti, del resto.

Una volta che conosci il nome del campo, puoi ottenere il suo valore in questo modo:

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

Vedere il seguente estratto da questo documento :

Le proprietà implementate automaticamente (auto-implementate) automatizzano questo modello. In particolare, le dichiarazioni di proprietà non astratte possono avere corpi di accesso con punto e virgola. Entrambi gli accessor devono essere presenti ed entrambi devono avere un corpo con punto e virgola, ma possono avere modificatori di accessibilità diversi. Quando viene specificata una proprietà come questa, verrà generato automaticamente un campo di supporto per la proprietà e gli accessor verranno implementati per leggere e scrivere su quel campo di supporto. Il nome del campo di supporto è compilato e inaccessibile all’utente.

Quindi, non c’è modo di accedere ai campi. Utilizza il tuo primo approccio. Le proprietà implementate automaticamente sono specifiche per il caso in cui non è necessario accedere al campo di supporto.

AGGIORNAMENTO: https://github.com/jbevain/mono.reflection viene fornito con un metodo resolver backing field che funziona con le proprietà automatiche generate da C #, VB.NET e F #. Il pacchetto NuGet è disponibile su https://www.nuget.org/packages/Mono.Reflection/

ORIGINALE: ho finito con questo metodo abbastanza flessibile solo per le proprietà auto C #. Come chiarito dalle altre risposte, questo non è portatile e non funzionerà se l’implementazione del compilatore utilizza uno schema di denominazione dei campi di supporto diverso da k__BackingField . Per quanto ho visto, tutte le implementazioni dei compilatori C # utilizzano attualmente questo schema di denominazione. I compilatori VB.NET e F # utilizzano un altro schema di denominazione che non funzionerà con questo codice.

 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; } 

No, non c’è. Se si desidera accedere al campo di supporto, non utilizzare le proprietà automatiche e eseguire il rollover.

Dalla documentazione per le proprietà implementate automaticamente :

Quando si dichiara una proprietà come illustrato nell’esempio seguente, il compilatore crea un campo di backup anonimo privato a cui è ansible accedere solo tramite gli attributi get e set della proprietà.

Le proprietà implementate automaticamente sono una versione “più lazza” di una proprietà implementata manualmente con un campo di supporto. Dal momento che non consentono alcuna logica aggiuntiva, l’unica cosa che puoi fare con loro è leggere o scrivere il campo privato “nascosto”. Puoi comunque utilizzare il modificatore private per limitare l’accesso a uno dei metodi di accesso (in genere, il set sarebbe privato in quel caso), se desideri che i tuoi metodi privati ​​abbiano questo privilegio.

Se volessi accedere ad alcuni campi privati ​​di un’altra class (come una class del BCL), potresti usare Reflection per farlo (come spiegato in questi esempi ), ma sarebbe un brutto scherzo dove nessuno potrebbe garantire che uno -Leggere il cambiamento nella fonte del framework non spezzerebbe il tuo codice in futuro.

Ma dal momento che hai già optato per l’auto-implementazione, non vedo alcun motivo per voler accedere al campo di supporto. L’accesso al campo direttamente o tramite gli accessor di proprietà auto-implementati non ha alcuna differenza e nessun beneficio. Si potrebbe, ad esempio, sostenere che è ansible utilizzare Interlocked per modificare atomicamente il campo (che non è ansible eseguire in una proprietà), ma non impone alcun “thread safe” quando tutti gli altri hanno accesso al campo attraverso la proprietà con nessuna atomicità.

Per non parlare del fatto che il compilatore sarà probabilmente in linea su ogni chiamata, quindi non ci sono differenze di prestazioni .