Benutzer mit den meisten Antworten
Wie einer Nullable-Struktur Werte einer Eigenschaft zuweisen?

Frage
-
Hallo,
ich habe folgende Struktur:
public struct TestStructur
{
public float Xstart;
public float Xend;
}Damit ich diese in einer Klasse auf null setzen und prüfen kann, ist die Struktur wie folgt deklariert:
Nullable<TestStructur> container = null;
Im folgenden Code werden mir die Eigenschaft Xstart und Xend aber nicht mehr angezeigt:
if (container == null)
{
container = new TestStructur();
container. ????
}Mit der folgenden Variante wird die Fehlermeldung ausgegeben, dass der Rückgabewert ".Value" nicht geändert werden kann, da er keine Variable ist.
if (!container.HasValue)
{
container = new TestStructur();
container.Value.Xend = 12f;
}Da ich gerne weiterhin eine Struktur verwenden will, meine Frage: Gibt es eine Möglichkeit, einer Eigenschaft einen Wert zuzuweisen, von einer Nullable-Struktur?
Alexander
Antworten
-
Hallo Alexander,
das geht zum Beispiel so:
container = new TestStructur{ Xend = 12f };
container ist ja der Nullable Typ und Du brauchst eben einen Typ "TestStructure" - dessen (öffentliche) Felder kannst Du dann auch setzen und dann später dem Nullable-Typ container zuweisen. Also mal explizit zum Nachvollziehen implementiert:
var objekt = new TestStructur(); objekt.Xend = 42f; objekt.Xstart = 1f; container = objekt;
ciao Frank- Als Antwort markiert AlexanderRi Donnerstag, 24. Februar 2011 08:39
-
Hallo,
Nullable<TestStructur> container = null;
Mit der folgenden Variante wird die Fehlermeldung ausgegeben, dass der Rückgabewert ".Value" nicht geändert werden kann, da er keine Variable ist.
if (!container.HasValue)
{
container = new TestStructur();
container.Value.Xend = 12f;
}Da ich gerne weiterhin eine Struktur verwenden will, meine Frage: Gibt es eine Möglichkeit, einer Eigenschaft einen Wert zuzuweisen, von einer Nullable-Struktur?
Da gerätst Du in ein subtiles Problem hinein, das
überall da auftritt, wo eine Property oder eine Methode
ein struct zurück liefert. NIcht nur bei Nullables und deren
Value-Property
Z.B auch in WPF
//so geht's nicht: myStackPanel.BorderThickness.Right = 2, //so geht's: Thickness tn = myStackPanel.BorderThickness; tn.Right = 2; myStackPanel.BorderThickness= tn;
Der Aufruf des Property Getters ist ein Methodenaufruf -
bei der der Rückgabe wird der Callstack aufgeräumt, d.h. der
zurückgegeben Wertetyp muss erst beim Aufrufer
in einer Variablen "aufgefangen" werden.
Dadurch wird ihm Speicher zugewiesen.
Erst dann kann er als "L-Value" durchgehen.
Daher ist es bei Properties, die structs zurückgeben, unerlässlich,
das struct zunächst in einer Variablen zu speichern,
um auf Properties des structs schreibend zu zu greifen.
Bei Properties die Reftypes zurückgeben, entfällt das
Problem, weil der Reftype nicht bei bei Abräumen des Callstacks
zerstört wird, sondern vorher und nachher auf dem managed Heap "lebt".
Wenn Du die structs über Felder zugänglich machst, entfällt das
Problem übrigens auch, auch die sind im managed Heap
abgelegt.
Noch ein Beispiel mit demselben Problem:
struct Point { public int X { get; set; } public int Y { get; set; } } struct Line { public Point Start { get; set; } public Point End { get; set; } } public static void Main() { Point p = new Point {X = 1, Y = 2}; Point p2 = new Point { X = 10, Y = 20 }; Line line = new Line {Start = p, End = p2}; //Fehler Can't modify expression because it's not a variable line.Start.Y = 30; }
Wenn Du die beiden Point structs aber als Felder anlegst,
funktioniert es:
Ebenso funktioniert es, wenn Du eine Variable einfügst:struct Point2 { public int X { get; set; } public int Y { get; set; } } struct Line2 { public Point2 Start; public Point2 End; } public static void Main() { Point2 p = new Point2 {X = 1, Y = 2}; Point2 p2 = new Point2 { X = 10, Y = 20 }; Line2 line = new Line2 {Start = p, End = p2}; line.Start.Y = 30; //OK }
struct Point { public int X { get; set; } public int Y { get; set; } } struct Line { public Point Start { get; set; } public Point End { get; set; } } public static void Main() { Point p = new Point {X = 1, Y = 2}; Point p2 = new Point { X = 10, Y = 20 }; Line line = new Line {Start = p, End = p2}; ////Fehler Can't modify expression because it's not a variable //line.Start.Y = 30; //so kann an die Property verändern: Point start = line.Start; start.Y = 30; line.Start = start; }
Christoph
- Als Antwort markiert AlexanderRi Donnerstag, 24. Februar 2011 08:39
Alle Antworten
-
Hallo Alexander,
das geht zum Beispiel so:
container = new TestStructur{ Xend = 12f };
container ist ja der Nullable Typ und Du brauchst eben einen Typ "TestStructure" - dessen (öffentliche) Felder kannst Du dann auch setzen und dann später dem Nullable-Typ container zuweisen. Also mal explizit zum Nachvollziehen implementiert:
var objekt = new TestStructur(); objekt.Xend = 42f; objekt.Xstart = 1f; container = objekt;
ciao Frank- Als Antwort markiert AlexanderRi Donnerstag, 24. Februar 2011 08:39
-
Hallo,
Nullable<TestStructur> container = null;
Mit der folgenden Variante wird die Fehlermeldung ausgegeben, dass der Rückgabewert ".Value" nicht geändert werden kann, da er keine Variable ist.
if (!container.HasValue)
{
container = new TestStructur();
container.Value.Xend = 12f;
}Da ich gerne weiterhin eine Struktur verwenden will, meine Frage: Gibt es eine Möglichkeit, einer Eigenschaft einen Wert zuzuweisen, von einer Nullable-Struktur?
Da gerätst Du in ein subtiles Problem hinein, das
überall da auftritt, wo eine Property oder eine Methode
ein struct zurück liefert. NIcht nur bei Nullables und deren
Value-Property
Z.B auch in WPF
//so geht's nicht: myStackPanel.BorderThickness.Right = 2, //so geht's: Thickness tn = myStackPanel.BorderThickness; tn.Right = 2; myStackPanel.BorderThickness= tn;
Der Aufruf des Property Getters ist ein Methodenaufruf -
bei der der Rückgabe wird der Callstack aufgeräumt, d.h. der
zurückgegeben Wertetyp muss erst beim Aufrufer
in einer Variablen "aufgefangen" werden.
Dadurch wird ihm Speicher zugewiesen.
Erst dann kann er als "L-Value" durchgehen.
Daher ist es bei Properties, die structs zurückgeben, unerlässlich,
das struct zunächst in einer Variablen zu speichern,
um auf Properties des structs schreibend zu zu greifen.
Bei Properties die Reftypes zurückgeben, entfällt das
Problem, weil der Reftype nicht bei bei Abräumen des Callstacks
zerstört wird, sondern vorher und nachher auf dem managed Heap "lebt".
Wenn Du die structs über Felder zugänglich machst, entfällt das
Problem übrigens auch, auch die sind im managed Heap
abgelegt.
Noch ein Beispiel mit demselben Problem:
struct Point { public int X { get; set; } public int Y { get; set; } } struct Line { public Point Start { get; set; } public Point End { get; set; } } public static void Main() { Point p = new Point {X = 1, Y = 2}; Point p2 = new Point { X = 10, Y = 20 }; Line line = new Line {Start = p, End = p2}; //Fehler Can't modify expression because it's not a variable line.Start.Y = 30; }
Wenn Du die beiden Point structs aber als Felder anlegst,
funktioniert es:
Ebenso funktioniert es, wenn Du eine Variable einfügst:struct Point2 { public int X { get; set; } public int Y { get; set; } } struct Line2 { public Point2 Start; public Point2 End; } public static void Main() { Point2 p = new Point2 {X = 1, Y = 2}; Point2 p2 = new Point2 { X = 10, Y = 20 }; Line2 line = new Line2 {Start = p, End = p2}; line.Start.Y = 30; //OK }
struct Point { public int X { get; set; } public int Y { get; set; } } struct Line { public Point Start { get; set; } public Point End { get; set; } } public static void Main() { Point p = new Point {X = 1, Y = 2}; Point p2 = new Point { X = 10, Y = 20 }; Line line = new Line {Start = p, End = p2}; ////Fehler Can't modify expression because it's not a variable //line.Start.Y = 30; //so kann an die Property verändern: Point start = line.Start; start.Y = 30; line.Start = start; }
Christoph
- Als Antwort markiert AlexanderRi Donnerstag, 24. Februar 2011 08:39
-
Hallo Alexander,
Du hast die Frage bereits als beantwortet markiert, ich möchte aber trotzdem noch einige Bemerkungen hinzufügen. Vielleicht helfen sie ja Dir oder anderen Lesern dieses Threads.
Ich muss etwas weiter ausholen: Ein Nullable<T>-Typ ist ein Wertettyp, eine Struktur die einen Wert vom Typ T mit einem boolschen Wert kombiniert. Ein Wertetyp kann niemals null sein. Er enthält immer einen Wert, daher auch die Bezeichnung "Wertetyp". Wenn wir jedoch
Nullable<Point> nullablePoint = null;
schreiben, versuchen wir dem Wertetyp nullablePoint null zuzuweisen. Das ist vergleichbar mit:
int i = null; //Fehler
Während der letzte Aufruf nicht kompiliert wird, gelingt aber die Zuweisung von Null an nullablePoint. Warum denn? Schließlich kann null niemals in den Wertetyp Point konvertiert werden. Nun, die CLR behandelt Nullable<T> in spezieller Weise. In diesem Fall wird die Zuweisung von Null an den Wertetyp nullablePoint einfach ignoriert. Es wird damit die Illusion aufrechterhalten, dass nullablePoint sowohl ein Referenztyp als auch ein Wertetyp ist. Was so natürlich nicht stimmt. Deshalb wird die an Nullable<T> zugewiesene null oft auch als "logische Null" bezeichnet. Denn auch nach der Zuweisung von null kann man durchaus noch:
Console.WriteLine(nullablePoint.HasValue);
schreiben, was bei auf null gesetzten Referenztypen undenkbar wäre.
HasValue ist eine der beiden Eigenschaften, die für eine Nullable<T>-Struktur charakteristisch sind. Die andere ist Value. Die Eigenschaft Value gibt ein Objekt von Typ T zurück, in Deinem Fall eine Struktur. Value hat keinen Setter, wir können also nicht
nullablePoint.Value = new Point();
schreiben, denn Value ist schreibgeschützt. Es gibt zwei Möglichkeiten, einem Nullable<T> einen Wert zuzuweisen. Erstens über den Konstruktor:
Nullable<Point> nullablePoint = new Nullable<Point>(new Point());
Zweitens über eine implizite Konversion:
Nullable<Point> nullablePoint = new Point();
Das gelingt nur weil Nullable<T> einen impliziten Operator definiert:
public static implicit operator Nullable<T>(T value) { return new Nullable<T>(value); }
der seinerseits den Konstruktor aufruft.
Bleibt noch die Frage, warum man zwar lesend auf nullablePoint.Value.X zugreifen kann, aber nicht schreibend.
Wir habe gesehen, dass nullablePoint.HasValue auch nach der Zuweisung von Null einen Wert zurückgibt. Im Fall von nullablePoint.Value ist das nicht der Fall. Der Getter überprüft den Wert von HasValue und wirft eine InvalidOperationException wenn die Eigenschaft auf false steht. Ein Point kann (als Wertetyp) eben nicht null sein und Nullable<T> offenbart hiermit seine bisher gut getarnte Wertetyp-Natur.Sieh Dir mal bitte die folgenden Strukturen an:
struct Kontakt { public string Nachname { get; set; } public string Vorname { get; set; } public Adresse Adresse1 { get; set; } public Adresse Adresse2; } struct Adresse { public string Strasse {get; set;} public string PLZ {get; set;} public string Ort { get; set; } }
Kontakt.Adresse2.Strasse kann problemlos gesetzt werden, während Kontakt.Adresse1.Strasse nicht gesetzt werden kann.
Ändern wir nun testhalber die Definition von struct auf class, funktionieren plötzlich beide Zuweisungen wieder:class Kontakt { public string Nachname { get; set; } public string Vorname { get; set; } public Adresse Adresse1 { get; set; } public Adresse Adresse2; } class Adresse { public string Strasse {get; set;} public string PLZ {get; set;} public string Ort { get; set; } }
und ich kann z.B. schreiben:
Kontakt k = new Kontakt(); k.Adresse1.Strasse = "Strasse 1";
Was also ist der Unterschied? Laß uns mal ein Auge auf das im letzen Fall erzeugte IL werfen:
IL_0009: callvirt instance class TestRunner.Adresse TestRunner.Kontakt::get_Adresse1() IL_000e: ldstr "Strasse 1" IL_0013: callvirt instance void TestRunner.Adresse::set_Strasse(string)
Der Getter TestRunner.Kontakt::get_Adresse1 kann direkt aufgerufen werden. Ebenso der Setter. Ganz anders bei der Verwendung von Wertetypen (Strukturen):
IL_0002: ldloca.s nullablePoint IL_0004: ldloca.s CS$0$0000 IL_0006: initobj [System.Drawing]System.Drawing.Point IL_000c: ldloc.2 IL_000d: call instance void valuetype [mscorlib]System.Nullable`1<valuetype [System.Drawing]System.Drawing.Point>::.ctor(!0) IL_0012: nop IL_0013: ldloca.s nullablePoint IL_0015: call instance !0 valuetype [mscorlib]System.Nullable`1<valuetype [System.Drawing]System.Drawing.Point>::get_Value() IL_001a: stloc.2 IL_001b: ldloca.s CS$0$0000 IL_001d: call instance int32 [System.Drawing]System.Drawing.Point::get_X() IL_0022: call void [mscorlib]System.Console::WriteLine(int32)
Hier gibt der Getter weder ein Referenztyp noch den gekapselten Wertetyp (Point) zurück, sondern unbox-ed einfach den X-Wert. Wenn wir diesen Wert ändern, ändert sich freilich Point.X im Nullable<Point>-Typ nicht mit, ist ja keine Referenz.
Das Problem besteht also darin, dass es bei Wertetypen keine Möglichkeit gibt, eine Referenz auf einen Wertetyp zurückzugeben. Selbst wenn ich also:
Nullable<Point> nullablePoint = new Point(); Point p = nullablePoint.Value; p.X = 1;
schreibe, wird durch die Zuweisung p.X = 1 der Wert der intern im Nullable<Point> geboxten Variablen nicht mitgeändert. Es ist also verständlich, dass
Nullable<Point> nullablePoint = new Point(); nullablePoint.Value.X = 1; // Fehler
ebenfalls nicht klappt: Value ist eben keine Referenz auf eine Stelle im Speicher.
Value Types (C# Reference):
http://msdn.microsoft.com/de-de/library/s1ax56ch.aspxGruss
Marcel