Co to jest przypisanie => w C # w sygnaturze właściwości

229

Natknąłem się na kod, który powiedział

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

Teraz jestem nieco zaznajomiony z wyrażeniami Lambda. Po prostu nie widziałem, żeby używało go w ten sposób.

Jaka byłaby różnica między powyższym stwierdzeniem a

public int MaxHealth  = x ? y:z;
Mikrofon
źródło
4
pierwszy blok jest własnością drugi jest zmienną
M.kazem Akhgary
14
@ M.kazemAkhgary * pole, a nie zmienna.
Mafii,

Odpowiedzi:

376

To, na co patrzysz, to wyrazisty członek, a nie wyrażenie lambda.

Gdy kompilator napotka element właściwości wyrażony w wyrażeniu , zasadniczo konwertuje go do elementu pobierającego w następujący sposób:

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

(Możesz to sprawdzić samodzielnie, pompując kod do narzędzia o nazwie TryRoslyn ).

Członkowie o ekspresji - podobnie jak większość funkcji C # 6 - są po prostu cukrem syntaktycznym . Oznacza to, że nie zapewniają one funkcjonalności, której inaczej nie można byłoby uzyskać za pomocą istniejących funkcji. Zamiast tego te nowe funkcje umożliwiają stosowanie bardziej wyrazistej i zwięzłej składni

Jak widać, członkowie o wyrazistej budowie mają garść skrótów, dzięki którym członkowie właściwości są bardziej zwięzli:

  • Nie ma potrzeby używania returninstrukcji, ponieważ kompilator może wywnioskować, że chcesz zwrócić wynik wyrażenia
  • Nie ma potrzeby tworzenia bloku instrukcji, ponieważ ciało jest tylko jednym wyrażeniem
  • Nie ma potrzeby używania getsłowa kluczowego, ponieważ wynika to z użycia składni elementu wyrażenia.

Pogrubiłem ostatni punkt, ponieważ dotyczy on twojego rzeczywistego pytania, na które odpowiem teraz.

Różnica pomiędzy...

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

I...

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

Jest taka sama jak różnica między ...

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

I...

public int MaxHealth = x ? y:z;

Co - jeśli rozumiesz właściwości - powinno być oczywiste.

Jednak dla jasności: pierwszy wpis to właściwość z geterem pod maską, który będzie wywoływany za każdym razem, gdy do niego wejdziesz. Druga lista jest polem z inicjatorem pola, którego wyrażenie jest oceniane tylko raz, gdy tworzony jest typ.

Ta różnica w składni jest w rzeczywistości dość subtelna i może prowadzić do „gotcha”, którą opisuje Bill Wagner w poście zatytułowanym „AC # 6 gotcha: Initialization vs. Expression Bodied Members” .

Podczas gdy członkowie są treściwe wyrażenie lambda expression- jak są one nie wyrażenia lambda. Podstawowa różnica polega na tym, że wyrażenie lambda powoduje wystąpienie delegacji lub drzewa wyrażeń. Pełnoprawne wyrażenia są tylko kompilatorem kompilatora, który generuje właściwość za kulisami. Podobieństwo (mniej więcej) zaczyna się i kończy strzałką ( =>).

Dodam też, że członkowie o wyrazistym wyrazie nie są ograniczeni do członków własności. Pracują na wszystkich tych członkach:

  • Nieruchomości
  • Indeksujący
  • Metody
  • Operatorzy

Dodano w C # 7.0

Nie działają one jednak na tych członków:

  • Typy zagnieżdżone
  • Wydarzenia
  • Pola
Alex Booker
źródło
6
Począwszy od C # 7, konstruktory i finalizatory są również obsługiwane. docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
bzier
8
@bzier To spisek, aby uczynić nas funkcjonalnymi programistami. JEŚLI JESZCZE WIĘCEJ na zawsze !!
Sentinel,
Super niesamowita odpowiedź!
Jaime Arroyo Garcia
2
Link do postu Billa Wagnera jest obecnie zepsuty. Myślę, że znalazłem nowy adres URL: codeproject.com/Articles/1064964/…
Fry Simpson
36

Ok ... Skomentowałem, że były różne, ale nie potrafiłem dokładnie wyjaśnić, ale teraz wiem.

String Property { get; } = "value";

to nie to samo co

String Property => "value";

Oto różnica ...

Podczas korzystania z automatycznego inicjowania właściwość tworzy wystąpienie wartości i używa tej wartości w sposób trwały. W powyższym poście znajduje się uszkodzony link do Billa Wagnera, który dobrze to wyjaśnia, i sam szukałem odpowiedniego linku, aby go zrozumieć.

W mojej sytuacji kazałem mojej właściwości automatycznie zainicjować polecenie w ViewModel dla widoku. Zmieniłem właściwość, aby użyć inicjatora wyrażenia, a polecenie CanExecute przestało działać.

Oto jak to wyglądało i oto co się działo.

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

oto co zmieniłem na.

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

Różnica polega na tym, że kiedy używam { get; } =, tworzę i odwołuję się do polecenia SAME w tej właściwości. Kiedy używam =>, tworzę nowe polecenie i zwracam je przy każdym wywołaniu właściwości. Dlatego nigdy nie mogłem zaktualizować CanExecutepolecenia, ponieważ zawsze mówiłem, aby zaktualizowało nowe odwołanie do tego polecenia.

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

Wszystko to powiedziało, jeśli tylko wskazujesz na pole zaplecza, to działa dobrze. Dzieje się tak tylko wtedy, gdy ciało auto lub wyrażenie tworzy wartość zwracaną.

Michael Puckett II
źródło
8
Składnia => jest równa get {return new Command (); } składnia.
Mafii,
35

Jest to nowa funkcja języka C # 6 o nazwie element wyrażający ciało, która pozwala zdefiniować właściwość tylko gettera za pomocą funkcji podobnej do lambda.

Chociaż uważa się go za cukier składniowy w następujących przypadkach, mogą one nie wytwarzać identycznej IL:

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

Okazuje się, że jeśli skompilujesz obie wersje powyższego i porównasz wygenerowaną IL dla każdej z nich, zobaczysz, że są one prawie takie same.

Oto IL dla klasycznej wersji w tej odpowiedzi, gdy zdefiniowano ją w klasie o nazwie 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

A oto IL dla wyrażonej w treści wersji członka, gdy jest zdefiniowany w klasie o nazwie 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

Zobacz https://msdn.microsoft.com/en-us/magazine/dn802602.aspx, aby uzyskać więcej informacji na temat tej i innych nowych funkcji w C # 6.

Zobacz ten post Różnica między właściwością a polem w C # 3.0+ na temat różnicy między polem a geterem właściwości w C #.

Aktualizacja:

Zauważ, że elementy o wyrazistej budowie zostały rozszerzone o właściwości, konstruktory, finalizatory i indeksatory w C # 7.0.

Tyree Jackson
źródło
16

Nazywa się to Expression Bodied Member i został wprowadzony w C # 6. Jest to jedynie cukier składniowy nad getjedyną właściwością.

Jest to równoważne z:

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

Dostępny jest odpowiednik deklaracji metody:

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

Głównie umożliwiając skrócenie płyty grzewczej.

Yuval Itzchakov
źródło
7

Jeszcze jeden istotny punkt, jeśli używasz C # 6:

„=>” może być użyte zamiast „get” i dotyczy tylko metod „get only” - nie można go używać z „set”.

W przypadku C # 7 zobacz komentarz z @avenmore poniżej - można go teraz używać w większej liczbie miejsc. Oto dobre odniesienie - https://csharp.christiannagel.com/2017/01/25/expressionbodiedmembers/

Chris Halcrow
źródło
8
Nie jest już prawdą, jeśli używasz C # 7. "C # 7.0 kontynuuje ulepszenia produktywności. Elementy o ekspresji były dostępne w C # 6 dla metod i właściwości, teraz można ich używać z konstruktorami, destruktorami, akcesoriami własności i akcesoriami zdarzeń także." ( Źródło )
jeszcze
1

Na poniższą wypowiedź udostępnioną przez Alexa Bookera w odpowiedzi

Gdy kompilator napotka element właściwości wyrażony w wyrażeniu, zasadniczo konwertuje go do elementu pobierającego w następujący sposób:

Zobacz poniższy zrzut ekranu , który pokazuje, w jaki sposób to oświadczenie (przy użyciu łącza SharpLab )

public string APIBasePath => Configuration.ToolsAPIBasePath;

konwertuje na

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

Zrzut ekranu: wprowadź opis zdjęcia tutaj

Shakeel
źródło