Was ist die => Zuweisung in C # in einer Eigenschaftssignatur?


229

Ich bin auf einen Code gestoßen, der besagt

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

Jetzt bin ich mit Lambda-Ausdrücken einigermaßen vertraut. Ich habe es einfach nicht so gesehen.

Was wäre der Unterschied zwischen der obigen Aussage und

public int MaxHealth  = x ? y:z;

4
Der erste Block ist Eigentum, der zweite ist variabel
M.kazem Akhgary

14
@ M.kazemAkhgary * ein Feld, keine Variable.
Mafii

Antworten:


376

Was Sie sehen, ist ein ausdrucksstarkes Mitglied, kein Lambda-Ausdruck.

Wenn der Compiler auf ein Eigenschaftselement mit Ausdruckskörper stößt , konvertiert er es im Wesentlichen in einen Getter wie den folgenden:

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

(Sie können dies selbst überprüfen, indem Sie den Code in ein Tool namens TryRoslyn pumpen .)

Mitglieder mit Ausdruckskörper sind - wie die meisten C # 6-Funktionen - nur syntaktischer Zucker . Dies bedeutet, dass sie keine Funktionen bereitstellen, die sonst mit vorhandenen Funktionen nicht erreicht werden könnten. Stattdessen ermöglichen diese neuen Funktionen die Verwendung einer ausdrucksstärkeren und prägnanteren Syntax

Wie Sie sehen können, haben Mitglieder mit Ausdruckskörper eine Handvoll Verknüpfungen, die die Mitglieder von Eigenschaften kompakter machen:

  • Es ist nicht erforderlich, a zu verwenden return Anweisung zu verwenden, da der Compiler daraus schließen kann, dass Sie das Ergebnis des Ausdrucks zurückgeben möchten
  • Es ist nicht erforderlich, einen Anweisungsblock zu erstellen, da der Textkörper nur ein Ausdruck ist
  • Das getSchlüsselwort muss nicht verwendet werden, da es durch die Verwendung der Ausdruckssyntax für Mitglieder impliziert wird.

Ich habe den letzten Punkt fett gemacht, weil er für Ihre eigentliche Frage relevant ist, die ich jetzt beantworten werde.

Der Unterschied zwischen...

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

Und...

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

Ist das gleiche wie der Unterschied zwischen ...

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

Und...

public int MaxHealth = x ? y:z;

Was - wenn Sie Eigenschaften verstehen - offensichtlich sein sollte.

Um es klar zu machen: Die erste Auflistung ist eine Eigenschaft mit einem Getter unter der Haube, die jedes Mal aufgerufen wird, wenn Sie darauf zugreifen. Die zweite Auflistung ist ein Feld mit einem Feldinitialisierer, dessen Ausdruck nur einmal ausgewertet wird, wenn der Typ instanziiert wird.

Dieser Unterschied in der Syntax ist eigentlich ziemlich subtil und kann zu einem "Gotcha" führen, das von Bill Wagner in einem Beitrag mit dem Titel "AC # 6 Gotcha: Initialisierung vs. Ausdruck körperlicher Mitglieder" beschrieben wird. .

Während die Expression-bodied Mitglieder Lambda sind expression- wie , sie sind nicht Ausdrücke Lambda. Der grundlegende Unterschied besteht darin, dass ein Lambda-Ausdruck entweder zu einer Delegateninstanz oder zu einem Ausdrucksbaum führt. Mitglieder mit Ausdruckskörper sind nur eine Anweisung an den Compiler, eine Eigenschaft hinter den Kulissen zu generieren. Die Ähnlichkeit (mehr oder weniger) beginnt und endet mit dem Pfeil ( =>).

Ich werde auch hinzufügen, dass ausdrucksstarke Mitglieder nicht auf Eigenschaftsmitglieder beschränkt sind. Sie arbeiten an all diesen Mitgliedern:

  • Eigenschaften
  • Indexer
  • Methoden
  • Betreiber

Hinzugefügt in C # 7.0

Sie arbeiten jedoch nicht mit diesen Mitgliedern:

  • Verschachtelte Typen
  • Veranstaltungen
  • Felder

6
Ab C # 7 werden auch Konstruktoren und Finalisierer unterstützt. docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
bzier

8
@bzier Es ist eine Verschwörung, uns zu funktionierenden Programmierern zu machen. WENN DANN ANDER für immer !!
Sentinel

Super tolle Antwort!
Jaime Arroyo Garcia

2
Der Link zu Bill Wagners Beitrag ist derzeit unterbrochen. Ich glaube, ich habe die neue URL gefunden: codeproject.com/Articles/1064964/…
Fry Simpson

36

Ok ... Ich habe einen Kommentar abgegeben, dass sie unterschiedlich waren, konnte aber nicht genau erklären, wie, aber jetzt weiß ich es.

String Property { get; } = "value";

ist nicht dasselbe wie

String Property => "value";

Hier ist der Unterschied ...

Wenn Sie den automatischen Initialisierer verwenden, erstellt die Eigenschaft die Wertinstanz und verwendet diesen Wert dauerhaft. Im obigen Beitrag gibt es einen defekten Link zu Bill Wagner, der dies gut erklärt, und ich habe den richtigen Link gesucht, um ihn selbst zu verstehen.

In meiner Situation ließ ich meine Eigenschaft einen Befehl in einem ViewModel für eine Ansicht automatisch initialisieren. Ich habe die Eigenschaft geändert, um den Initialisierer mit Ausdruckskörper zu verwenden, und der Befehl CanExecute funktioniert nicht mehr.

So sah es aus und so geschah es.

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

Hier ist, was ich geändert habe.

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

Der Unterschied besteht darin, dass ich { get; } =den Befehl SAME in dieser Eigenschaft erstelle und referenziere. Wenn ich benutze, =>erstelle ich tatsächlich einen neuen Befehl und gebe ihn jedes Mal zurück, wenn die Eigenschaft aufgerufen wird. Daher konnte ich CanExecutemeinen Befehl niemals aktualisieren, da ich ihm immer sagte, er solle eine neue Referenz dieses Befehls aktualisieren.

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

Alles in allem funktioniert es einwandfrei, wenn Sie nur auf ein Hintergrundfeld zeigen. Dies geschieht nur, wenn der Auto- oder Ausdruckskörper den Rückgabewert erstellt.


8
Die => Syntax entspricht dem get {return new Command (); } Syntax.
Mafii

35

Dies ist eine neue Funktion von C # 6, die als Ausdruckskörper bezeichnet wird und es Ihnen ermöglicht, eine Nur-Getter-Eigenschaft mithilfe einer Lambda-ähnlichen Funktion zu definieren.

Während es für Folgendes als syntaktischer Zucker angesehen wird , produzieren sie möglicherweise keine identische IL:

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

Es stellt sich heraus, dass, wenn Sie beide Versionen der oben genannten Versionen kompilieren und die für jede generierte IL vergleichen, Sie feststellen, dass sie fast gleich sind.

Hier ist die IL für die klassische Version in dieser Antwort, wenn sie in einer Klasse mit dem Namen definiert ist 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

Und hier ist die IL für die Ausdrucksversion eines Mitglieds, wenn sie in einer Klasse mit dem Namen definiert ist 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

Weitere Informationen zu dieser und anderen neuen Funktionen in C # 6 finden Sie unter https://msdn.microsoft.com/en-us/magazine/dn802602.aspx .

In diesem Beitrag Unterschied zwischen Eigenschaft und Feld in C # 3.0+ wird der Unterschied zwischen einem Feld und einem Eigenschafts-Getter in C # beschrieben.

Aktualisieren:

Beachten Sie, dass Mitglieder mit Ausdruckskörper um Eigenschaften, Konstruktoren, Finalisierer und Indexer in C # 7.0 erweitert wurden.


16

Es heißt Expression Bodied Member und wurde in C # 6 eingeführt. Es ist lediglich syntaktischer Zucker über eine geteinzige Eigenschaft.

Es ist äquivalent zu:

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

Ein Äquivalent einer Methodendeklaration ist verfügbar:

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

Hauptsächlich können Sie die Kesselplatte verkürzen.


7

Ein weiterer wichtiger Punkt, wenn Sie C # 6 verwenden:

'=>' kann anstelle von 'get' verwendet werden und gilt nur für 'get only'-Methoden - es kann nicht mit einem' set 'verwendet werden.

Für C # 7 siehe den Kommentar von @avenmore unten - es kann jetzt an mehreren Stellen verwendet werden. Hier ist eine gute Referenz - https://csharp.christiannagel.com/2017/01/25/expressionbodiedmembers/


8
Wenn Sie C # 7 verwenden, ist dies nicht mehr der Fall. "C # 7.0 fährt mit Produktivitätsverbesserungen fort. Mitglieder mit Ausdruckskörper waren mit C # 6 für Methoden und Eigenschaften verfügbar. Jetzt können sie mit Konstruktoren, Destruktoren, Eigenschaftszugriffsberechtigten und Ereigniszugriffsberechtigten verwendet werden auch." ( Quelle )
Avenmore

1

Für die folgende Aussage von Alex Booker in ihrer Antwort geteilt

Wenn der Compiler auf ein Eigenschaftselement mit Ausdruckskörper trifft, konvertiert er es im Wesentlichen in einen Getter wie den folgenden:

Bitte sehen Sie den folgenden Screenshot , es zeigt, wie diese Aussage (über SharpLab Link )

public string APIBasePath => Configuration.ToolsAPIBasePath;

konvertiert zu

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

Bildschirmfoto: Geben Sie hier die Bildbeschreibung ein

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.