Qual è l'assegnazione => in C # in una firma di proprietà


229

Mi sono imbattuto in un codice che diceva

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

Ora ho una certa familiarità con le espressioni Lambda. Non l'ho visto usato così.

Quale sarebbe la differenza tra l'affermazione di cui sopra e

public int MaxHealth  = x ? y:z;

4
il primo blocco è di proprietà il secondo è variabile
M.kazem Akhgary

14
@ M.kazemAkhgary * un campo, non una variabile.
Mafii,

Risposte:


376

Quello che stai guardando è un membro dal corpo di espressione non un'espressione lambda.

Quando il compilatore incontra un membro della proprietà con corpo di espressione , lo converte essenzialmente in un getter come questo:

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

(Puoi verificarlo tu stesso pompando il codice in uno strumento chiamato TryRoslyn .)

I membri con espressione corporea - come la maggior parte delle funzionalità di C # 6 - sono solo zucchero sintattico . Ciò significa che non forniscono funzionalità che altrimenti non potrebbero essere ottenute tramite le funzionalità esistenti. Al contrario, queste nuove funzionalità consentono di utilizzare una sintassi più espressiva e concisa

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

  • Non è necessario utilizzare returnun'istruzione poiché il compilatore può dedurre che si desidera restituire il risultato dell'espressione
  • Non è necessario creare un blocco di istruzioni poiché il corpo è solo un'espressione
  • Non è necessario utilizzare la getparola chiave perché è implicita nell'uso della sintassi del membro con espressione.

Ho sottolineato il punto finale perché è pertinente alla tua vera domanda, a cui 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.

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

Questa differenza di sintassi è in realtà abbastanza sottile e può portare a un "gotcha" che è descritto da Bill Wagner in un post intitolato "AC # 6 gotcha: Initialization vs. Expression Bodied Members" .

Mentre i membri espressione di corpo sono lambda da un'espressione come , essi sono non espressioni lambda. La differenza fondamentale è che un'espressione lambda risulta in un'istanza delegata o in un albero di espressioni. I membri con corpo di espressione sono solo una direttiva per il 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 corpo di espressione non si limitano ai membri di proprietà. Lavorano su tutti questi membri:

  • Proprietà
  • indicizzatori
  • metodi
  • operatori

Aggiunto in C # 7.0

Tuttavia, non funzionano su questi membri:

  • Tipi nidificati
  • eventi
  • campi

6
A partire da C # 7, sono supportati anche costruttori e finalizzatori. docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
bzier

8
@bzier È una cospirazione per renderci programmatori funzionali. SE ALLORA ANCORA !!
Sentinella

Risposta super fantastica!
Jaime Arroyo Garcia,

2
Il link al post di Bill Wagner è attualmente interrotto. Penso di aver trovato il nuovo URL: codeproject.com/Articles/1064964/…
Fry Simpson,

36

Ok ... ho fatto un commento che erano diversi ma non potevo spiegare esattamente come, ma ora lo so.

String Property { get; } = "value";

non è lo stesso di

String Property => "value";

Ecco la differenza ...

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

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

Ecco come appariva ed ecco cosa stava succedendo.

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

ecco a cosa l'ho cambiato.

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

La differenza qui è quando uso { get; } =creo e faccio riferimento al comando SAME in quella proprietà. Quando uso =>effettivamente creo un nuovo comando e lo restituisco ogni volta che viene chiamata la proprietà. Pertanto, non ho mai potuto aggiornare il CanExecutemio comando perché gli dicevo sempre di aggiornare un nuovo riferimento di quel comando.

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

Detto questo, se stai solo indicando un campo di supporto, allora funziona bene. Ciò accade solo quando il corpo auto o expression crea il valore restituito.


8
La sintassi => è uguale a get {return new Command (); } sintassi.
Mafii,

35

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

Sebbene sia considerato zucchero sintattico per quanto segue, potrebbero non produrre IL identico:

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

Si scopre che se compili entrambe le versioni di cui sopra e confronti l'IL generato per ognuna vedrai che sono quasi uguali.

Ecco l'IL per la versione classica in questa risposta quando definita in una classe 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<int64, class MemoryAddress> TestClass::Memory
    IL_0007: ldarg.0
    IL_0008: ldfld int64 TestClass::Address
    IL_000d: callvirt instance !1 class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress>::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<int64, class MemoryAddress> TestClass::Memory
    IL_0022: ldarg.0
    IL_0023: ldfld int64 TestClass::Address
    IL_0028: callvirt instance !1 class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress>::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<int32>(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 bodied di espressione quando definita in una classe 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<int64, class MemoryAddress> TestClass::Memory
    IL_0006: ldarg.0
    IL_0007: ldfld int64 TestClass::Address
    IL_000c: callvirt instance !1 class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress>::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<int64, class MemoryAddress> TestClass::Memory
    IL_0021: ldarg.0
    IL_0022: ldfld int64 TestClass::Address
    IL_0027: callvirt instance !1 class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress>::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<int32>(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.

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

Aggiornare:

Si noti che i membri con corpo di espressione sono stati espansi per includere proprietà, costruttori, finalizzatori e indicizzatori in C # 7.0.


16

Si chiama Expression Bodied Member ed è stato introdotto in C # 6. È semplicemente zucchero sintattico su getun'unica proprietà.

È equivalente a:

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

È disponibile un equivalente di una dichiarazione di metodo:

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

Principalmente permettendoti di accorciare la piastra della caldaia.


7

Un altro punto significativo se si utilizza C # 6:

'=>' può essere usato al posto di 'get' ed è solo per i metodi 'get only' - non può essere usato con un 'set'.

Per C # 7, vedi il commento da @avenmore di seguito: ora può essere utilizzato in più punti. Ecco un buon riferimento - https://csharp.christiannagel.com/2017/01/25/expressionbodiedmembers/


8
Non è più vero se si utilizza C # 7. "C # 7.0 continua con miglioramenti della produttività. I ​​membri con corpo di espressione sono stati disponibili con C # 6 per metodi e proprietà, ora possono essere utilizzati con costruttori, distruttori, accessori di proprietà e accessori per eventi anche." ( Fonte )
avenmore,

1

Per la seguente dichiarazione condivisa da Alex Booker nella loro risposta

Quando il compilatore incontra un membro della proprietà con corpo di espressione, lo converte essenzialmente in un getter come questo:

Si prega di vedere lo screenshot seguente , mostra come questa affermazione (usando il collegamento SharpLab )

public string APIBasePath => Configuration.ToolsAPIBasePath;

converte in

public string APIBasePath
{
    get
    {
        return Configuration.ToolsAPIBasePath;
    }
}

Immagine dello schermo: inserisci qui la descrizione dell'immagine

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.