Ich bin auf einen Code gestoßen, der besagte
public int MaxHealth =>
Memory[Address].IsValid ?
Memory[Address].Read<int>(Offs.Life.MaxHp) :
0;
Jetzt bin ich einigermaßen mit Lambda-Ausdrücken vertraut. Ich habe es einfach noch nicht so gesehen.
Was wäre der Unterschied zwischen der obigen Aussage und
public int MaxHealth = x ? y:z;
Was Sie sehen, ist ein ausdrucksstarkes Mitglied kein Lambda-Ausdruck.
Wenn der Compiler auf ein Element mit ausdrucksstarker Eigenschaft trifft, konvertiert er es im Wesentlichen in einen Getter wie diesen:
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.)
Ausdrucksstarke Mitglieder sind - wie die meisten C # 6-Features - nur syntaktischer Zucker . Dies bedeutet, dass sie keine Funktionen bieten, die mit vorhandenen Funktionen sonst nicht möglich wären. Stattdessen ermöglichen diese neuen Funktionen die Verwendung einer aussagekräftigeren und prägnanteren Syntax
Wie Sie sehen, verfügen ausdrucksstarke Member über eine Reihe von Verknüpfungen, die Eigenschaftsmember kompakter machen:
return
-Anweisung zu verwenden, da der Compiler darauf schließen kann, dass Sie das Ergebnis des Ausdrucks zurückgeben möchtenget
muss nicht verwendet werden, da es durch die Verwendung der Ausdruckssyntax impliziert wird. Ich habe den letzten Punkt fett gedruckt, 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;
Welches - wenn Sie Eigenschaften verstehen - sollte offensichtlich sein.
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: Initialization vs. Expression Bodied Members" beschrieben wird. .
Mitglieder mit Ausdruck sind Lambda-Ausdrücke - wie , aber keine Lambda-Ausdrücke. Der grundlegende Unterschied besteht darin, dass ein Lambda-Ausdruck entweder zu einer Delegateninstanz oder zu einem Ausdrucksbaum führt. Mitglieder mit Ausdruck 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:
Hinzugefügt in C # 7.
Sie arbeiten jedoch nicht mit diesen Mitgliedern:
Dies ist eine neue Funktion in C # 6, die als Element mit Ausdruckskörper bezeichnet wird und mit der Sie eine Nur-Getter-Eigenschaft mithilfe einer Lambda-ähnlichen Funktion definieren können.
Während es für das Folgende als syntaktischer Zucker betrachtet wird, erzeugen sie möglicherweise nicht identische IL:
public int MaxHealth
{
get
{
return Memory[Address].IsValid
? Memory[Address].Read<int>(Offs.Life.MaxHp)
: 0;
}
}
Wenn Sie beide oben genannten Versionen kompilieren und die jeweils generierte IL vergleichen, werden Sie feststellen, dass sie [~ # ~] fast [~ # ~] sind gleich.
Hier ist die IL für die klassische Version in dieser Antwort, wenn sie in einer Klasse mit dem Namen TestClass
definiert ist:
.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 Memberversion mit dem Ausdruck "body", wenn sie in einer Klasse mit dem Namen TestClass
definiert ist:
.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 .
Siehe diesen Beitrag nterschied zwischen Property und Field in C # 3.0 + zum Unterschied zwischen einem Field und einem Property Getter in C #.
Aktualisieren:
Beachten Sie, dass ausdrucksstarke Elemente um Eigenschaften, Konstruktoren, Finalizer und Indexer in C # 7.0 erweitert wurden.
Ok ... Ich habe einen Kommentar abgegeben, dass sie unterschiedlich waren, aber ich konnte 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 fehlerhaften Link zu Bill Wagner, der dies gut erklärt, und ich habe den richtigen Link gesucht, um ihn selbst zu verstehen.
In meiner Situation hat meine Eigenschaft einen Befehl in einem ViewModel für eine View automatisch initialisiert. Ich habe die Eigenschaft geändert, um Initialisierer mit Ausdruck 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 es geändert habe.
Command MyCommand => new Command(); //doesn't work properly
Der Unterschied besteht darin, dass ich mit { get; } =
Den Befehl SAME in dieser Eigenschaft erstelle und darauf verweise. Wenn ich =>
Verwende, erstelle ich tatsächlich einen neuen Befehl und gebe ihn jedes Mal zurück, wenn die Eigenschaft aufgerufen wird. Daher konnte ich das CanExecute
in meinem Befehl nie aktualisieren, da ich ihm immer sagte, dass er eine neue Referenz dieses Befehls aktualisieren soll.
{ get; } = // same reference
=> // new reference
Alles in allem funktioniert es gut, wenn Sie nur auf ein Hintergrundfeld zeigen. Dies geschieht nur, wenn der Auto- oder Ausdruckskörper den Rückgabewert erstellt.
Es heißt Expression Bodied Member und wurde in C # 6 eingeführt. Es ist lediglich syntaktischer Zucker über einer get
only-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 ermöglicht es Ihnen, das Boilerplate zu verkürzen.
Ein weiterer wichtiger Punkt, wenn Sie C # 6 verwenden:
'=>' kann anstelle von 'get' verwendet werden und ist only für 'get only' Methoden - it Kann nicht mit einem 'Set' verwendet werden.
Informationen zu C # 7 finden Sie im Kommentar von @avenmore weiter unten. Es kann jetzt an mehreren Stellen verwendet werden. Hier ist eine gute Referenz - https://csharp.christiannagel.com/2017/01/25/expressionbodiedmembers/