none
Wie einer Nullable-Struktur Werte einer Eigenschaft zuweisen? RRS feed

  • 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 

    Mittwoch, 23. Februar 2011 17:09

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
    Mittwoch, 23. Februar 2011 18:42
  • 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:

        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
         }
    
    Ebenso funktioniert es, wenn Du eine Variable einfügst:

        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
    Mittwoch, 23. Februar 2011 21:07

Alle Antworten

  • Hallo Alexander,

    schon "container.Xend = 12f;" probiert?

     

    Viele Grüße
    Holger M. Rößler

    Mittwoch, 23. Februar 2011 17:44
  • 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
    Mittwoch, 23. Februar 2011 18:42
  • 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:

        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
         }
    
    Ebenso funktioniert es, wenn Du eine Variable einfügst:

        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
    Mittwoch, 23. Februar 2011 21:07
  • 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.aspx

    Gruss
    Marcel

    Donnerstag, 24. Februar 2011 10:11
    Moderator