Qual è il => incarico in C # in una firma di proprietà

Mi sono imbattuto in un codice che diceva

public int MaxHealth => Memory[Address].IsValid ? Memory[Address].Read(Offs.Life.MaxHp) : 0; 

Ora ho una certa familiarità con le espressioni Lambda. Solo che non l’ho visto usarlo in questo modo.

Quale sarebbe la differenza tra la dichiarazione di cui sopra e

 public int MaxHealth = x ? y:z; 

Quello che stai guardando è un membro con un’espressione corporea , non un’espressione lambda.

Quando il compilatore incontra un membro di proprietà con espressione, lo convertirà essenzialmente in un get ter, come questo:

 public int MaxHealth { get { return Memory[Address].IsValid ? Memory[Address].Read(Offs.Life.MaxHp) : 0; } } 

(Puoi verificarlo da solo pompando il codice in uno strumento chiamato TryRoslyn .)

I membri del corpo con espressione – come la maggior parte delle caratteristiche del C # 6 – sono solo zucchero sintattico . Ciò significa che non forniscono funzionalità che altrimenti non potrebbero essere raggiunte attraverso funzionalità esistenti. Invece, queste nuove funzionalità consentono di utilizzare una syntax più espressiva e sintetica

Come puoi vedere, i membri con corpo espressivo hanno una manciata di scorciatoie che rendono i membri della proprietà più compatti:

  • Non è necessario utilizzare un’istruzione return perché il compilatore può dedurre che si desidera restituire il risultato dell’espressione
  • Non è necessario creare un blocco di istruzioni perché il corpo è solo un’espressione
  • Non è necessario utilizzare la parola chiave get perché è implicita dall’uso della syntax del membro con espressione.

Ho messo l’ultimo punto in grassetto perché è pertinente alla tua domanda reale, che risponderò ora.

La differenza tra…

 // expression-bodied member property public int MaxHealth => x ? y:z; 

E…

 // field with field initializer public int MaxHealth = x ? y:z; 

È uguale alla differenza tra …

 public int MaxHealth { get { return x ? y:z; } } 

E…

 public int MaxHealth = x ? y:z; 

Che – se capisci le proprietà – dovrebbe essere ovvio.

Giusto per essere chiari, però: il primo elenco è una proprietà con un getter sotto il cofano che verrà chiamato ogni volta che accederai. Il secondo elenco è un campo con un inizializzatore di campo, la cui espressione viene valutata solo una volta, quando il tipo viene istanziato.

Questa differenza di syntax è in realtà piuttosto sottile e può portare a un “gotcha” che è descritto da Bill Wagner in un post intitolato “ACchacha # 6: Inizializzazione contro i membri con espressione” .

Mentre i membri con un’espressione corporea sono simili a un’espressione lambda, non sono espressioni lambda. La differenza fondamentale è che un’espressione lambda risulta in un’istanza delegata o in un albero di espressioni. I membri del corpo con espressione sono solo una direttiva al compilatore per generare una proprietà dietro le quinte. La somiglianza (più o meno) inizia e termina con la freccia ( => ).

Aggiungerò anche che i membri con un’espressione corporea non sono limitati ai membri della proprietà. Lavorano su tutti questi membri:

  • Proprietà
  • indicizzatori
  • metodi
  • operatori

Tuttavia, non funzionano su questi membri:

  • Costruttori
  • Deconstructors
  • Tipi annidati
  • eventi
  • campi

Questa è una nuova funzionalità di C # 6 chiamata un membro con espressione che consente di definire una proprietà solo getter usando una funzione simile a lambda.

Mentre è considerato zucchero sintattico per quanto segue, possono non produrre IL identico:

 public int MaxHealth { get { return Memory[Address].IsValid ? Memory[Address].Read(Offs.Life.MaxHp) : 0; } } 

Si scopre che se compilate entrambe le versioni di sopra e confrontate l’IL generato per ognuno vedrete che sono quasi le stesse.

Ecco l’IL per la versione classica in questa risposta quando definita in una class denominata TestClass :

 .property instance int32 MaxHealth() { .get instance int32 TestClass::get_MaxHealth() } .method public hidebysig specialname instance int32 get_MaxHealth () cil managed { // Method begins at RVA 0x2458 // Code size 71 (0x47) .maxstack 2 .locals init ( [0] int32 ) IL_0000: nop IL_0001: ldarg.0 IL_0002: ldfld class [mscorlib]System.Collections.Generic.Dictionary`2 TestClass::Memory IL_0007: ldarg.0 IL_0008: ldfld int64 TestClass::Address IL_000d: callvirt instance !1 class [mscorlib]System.Collections.Generic.Dictionary`2::get_Item(!0) IL_0012: ldfld bool MemoryAddress::IsValid IL_0017: brtrue.s IL_001c IL_0019: ldc.i4.0 IL_001a: br.s IL_0042 IL_001c: ldarg.0 IL_001d: ldfld class [mscorlib]System.Collections.Generic.Dictionary`2 TestClass::Memory IL_0022: ldarg.0 IL_0023: ldfld int64 TestClass::Address IL_0028: callvirt instance !1 class [mscorlib]System.Collections.Generic.Dictionary`2::get_Item(!0) IL_002d: ldarg.0 IL_002e: ldfld class Offs TestClass::Offs IL_0033: ldfld class Life Offs::Life IL_0038: ldfld int64 Life::MaxHp IL_003d: callvirt instance !!0 MemoryAddress::Read(int64) IL_0042: stloc.0 IL_0043: br.s IL_0045 IL_0045: ldloc.0 IL_0046: ret } // end of method TestClass::get_MaxHealth 

Ed ecco l’IL per la versione del membro con espressione quando è definita in una class chiamata TestClass :

 .property instance int32 MaxHealth() { .get instance int32 TestClass::get_MaxHealth() } .method public hidebysig specialname instance int32 get_MaxHealth () cil managed { // Method begins at RVA 0x2458 // Code size 66 (0x42) .maxstack 2 IL_0000: ldarg.0 IL_0001: ldfld class [mscorlib]System.Collections.Generic.Dictionary`2 TestClass::Memory IL_0006: ldarg.0 IL_0007: ldfld int64 TestClass::Address IL_000c: callvirt instance !1 class [mscorlib]System.Collections.Generic.Dictionary`2::get_Item(!0) IL_0011: ldfld bool MemoryAddress::IsValid IL_0016: brtrue.s IL_001b IL_0018: ldc.i4.0 IL_0019: br.s IL_0041 IL_001b: ldarg.0 IL_001c: ldfld class [mscorlib]System.Collections.Generic.Dictionary`2 TestClass::Memory IL_0021: ldarg.0 IL_0022: ldfld int64 TestClass::Address IL_0027: callvirt instance !1 class [mscorlib]System.Collections.Generic.Dictionary`2::get_Item(!0) IL_002c: ldarg.0 IL_002d: ldfld class Offs TestClass::Offs IL_0032: ldfld class Life Offs::Life IL_0037: ldfld int64 Life::MaxHp IL_003c: callvirt instance !!0 MemoryAddress::Read(int64) IL_0041: ret } // end of method TestClass::get_MaxHealth 

Vedere https://msdn.microsoft.com/en-us/magazine/dn802602.aspx per ulteriori informazioni su questa e altre nuove funzionalità in C # 6.

Guarda questo post Differenza tra proprietà e campo in C # 3.0+ sulla differenza tra un campo e un getter di proprietà in C #.

Ok … ho fatto un commento sul fatto che erano diversi ma non potevano spiegare esattamente come ma ora lo so.

 String Property { get; } = "value"; 

non è lo stesso di

 Property => value 

Ecco la differenza …

Quando si utilizza l’inizializzatore automatico, la proprietà crea l’istanza di valore e utilizza tale valore in modo permanente. Nel post sopra c’è un link interrotto a Bill Wagner, che spiega bene, e ho cercato il link corretto per capirlo da solo.

Nella mia situazione ho avuto la mia proprietà auto inizializzare un comando in un ViewModel per una vista. Ho modificato la proprietà per utilizzare l’inizializzatore di espressione bodied e il comando CanExecute ha smesso di funzionare.

Ecco come appariva ed ecco cosa stava succedendo.

 Command MyCommand { get; } = new Command(); //works 

ecco cosa ho cambiato.

 Command MyCommand => new Command(); //doesn't work properly 

La differenza qui è quando uso { get; } = { get; } = Creo e faccio riferimento al comando SAME in quella proprietà. Quando uso => realtà creo un nuovo comando e lo restituisco ogni volta che viene richiamata la proprietà. Pertanto, non potrei mai aggiornare CanExecute sul mio comando perché gli stavo sempre dicendo di aggiornare un nuovo riferimento a quel comando.

 { get; } = // same reference => // new reference 

Detto questo, se stai solo indicando un campo di supporto, allora funziona bene. Ciò accade solo quando l’auto o il corpo dell’espressione crea il valore di ritorno.

Si chiama Expression Bodied Member ed è stato introdotto nel C # 6. È semplicemente zucchero sintattico su una proprietà get only.

È equivalente a:

 public int MaxHealth { get { return Memory[Address].IsValid ? Memory[Address].Read(Offs.Life.MaxHp) : 0; } 

Un equivalente di una dichiarazione di metodo è disponibile:

 public string HelloWorld() => "Hello World"; 

Consentendo principalmente di accorciare il boilerplate.

Su altro punto significativo è che ‘=>’ può essere usato al posto di ‘get’ ed è solo per i metodi ‘get only’ – non può essere usato con un ‘set’.