Anfängerfrage: Rückgabe von Referenzen auf Member?



  • Hallo,

    wenn ich eine Referenz auf einen Member meiner Klasse zurückgebe, unterlaufe ich damit ja eigentlich die Datenkapselung. Ich könnte diesen Member auch direkt public machen.

    Habe ich z.B. folgende Klasse "Book":

    class Book
    {
      bool changed;
      List<Author> authors;
    
      public Authors { get { return authors; }}
    }
    

    Dann kann der Aufrufer z.B. über book.Authors.Clear(); meine Autoren löschen, ohne dass im Buch-Objekt das Flag "changed" auf true gesetzt werden kann.

    Aus diesem Grund sollte man eigentlich soviel ich aus meiner C++-Zeit noch weiss, keine Referenzen auf Member zurückgeben.

    Es gibt jetzt natürlich verschiedene Möglichkeiten, so eine Klasse entsprechend anders zu entwerfen, dass kein direkte Zugriff auf den Member authors möglich ist.

    In der .Net-Bibliothek stosse ich jetzt aber ständig auf sowas, z.B. die Klasse SqlCommand hat eine Property Parameters, über die ich offenbar die Liste direkt manipulieren kann. Oder die Klasse ListView hat eine Property Items, auf die ich direkt zugreifen kann.

    Gibt die .Net-Bibliothek hier einfach Referenzen auf Member-Objekte zurück oder steckt eine andere Technik dahinter, mit der man sowas elegant lösen kann?

    update: Steckt vielleicht das (http://msdn.microsoft.com/en-us/library/aa645739(VS.71).aspx) dahinter?

    Tschüss,
    Riky



  • Manchmal wird die Liste mit einer Read Only Collection gewrappt.
    Oder es wird eine Kopie zurückgegeben.

    Simon



  • Eine Kopie zurückzugeben (und dann Member-Methoden AddBook() und RemoveBook() zu implemetnieren) wäre auch meine erste Idee gewesen.

    Das scheint aber nicht die Vorgehensweise bei den .Net-Klassen zu sein, denn da kann man ja die zurückgegebenen Member manipulieren.

    Ich habs mir jetzt aber mal etwas genauer angeguckt. Die Member z.B. bei SqlCommand oder ListView sind nicht einfach Collections, sondern offenbar speziell abgeleitete Klassen wie z.B. SelectedListViewItemCollection.

    Das deutet darauf hin, dass diese abgeleiteten Collections ihre Parents eventuell wirklich über Events darüber informieren, dass sie geändert wurden, wie in dem Link beschrieben, den ich noch in mein Posting editiert hatte. Könnte zumindest sein...



  • class Book
        {
            bool changed;
            List<int> authors;
    
            public Book()
            {
                this.authors = new List<int>();
            }
            public IList<int> Authors
            {
                get
                {
                    return this.authors.AsReadOnly();
                }
            }
        }
    

    Und so?Kriegst sogar ne Exception wenn du probierst ein authors.Clear im Main Programm zu starten.



  • Ja, aber bei der Lösung wird ja auch verhindert, dass ich z.B. ein authors.Clear() machen kann.

    Bei den Klassen aus der .Net-Library geht aber genau das.



  • Ich dachte genau das wolltest du verhindern? In deinem Ausgangsposting hatte ich das gefühl das du diese Verhalten verhindern willst wenn man eine Referenz auf einen Member über eine public Property zurück gibt. Oder hab ich da was vollkommen falsch verstanden? Dann klär mich mal genauer auf.



  • Ich habe gelernt, dass man keine Referenzen auf Member zurückgeben soll, da dies die Datenkapselung unterwandert. Es ist eben letztlich dasselbe, als würde man den Member einfach public machen.

    In meinem praktischen Beispiel, in dem ich eine Referenz auf einen List-Member zurückgebe, habe ich nun den Effekt, dass bei einem schreibenden Zugriff auf diesen zurückgegebenen List-Member natürlich das saved-Flag nicht korrekt gesetzt wird.

    Ich stellte dann fest, dass ich eine Referenz auf einen Member zurückgegeben habe und dies die Ursache für den Effekt ist. Meine gelernten (aber kurz vergessene) Regel fiel mir wieder ein und sie schien sich zu bestätigen.

    Aber ich hatte das so implementiert, weil ich es bei den .Net-Klassen wie ListView, SqlCommand usw... eben so gesehen und mehr oder weniger unbewusst einfach übernommen hatte.

    Und da kam bei mir die Frage auf, ob diese .Net-Klassen eben entgegen der Regel die ich mal gelernt habe (und die mir auch sinnvoll erscheint) doch einfach Refernezen auf Member zurückgeben (denn sowas wie SqlCommand.Parameters.Clear() geht ja!) oder ob da etwas anders hintersteckt.



  • Hallo,

    rikyho schrieb:

    Ich habe gelernt, dass man keine Referenzen auf Member zurückgeben soll, da dies die Datenkapselung unterwandert. Es ist eben letztlich dasselbe, als würde man den Member einfach public machen.

    Mh? Warum? Die Zugriffsbeschränkungen in dem zurückgegebenen Objekt bleiben ja erhalten.

    rikyho schrieb:

    In meinem praktischen Beispiel, in dem ich eine Referenz auf einen List-Member zurückgebe, habe ich nun den Effekt, dass bei einem schreibenden Zugriff auf diesen zurückgegebenen List-Member natürlich das saved-Flag nicht korrekt gesetzt wird.

    Dann darf die Refenrenz halt nicht public gemacht werden und du musst die entsprechenden Methoden selber schreiben und das Flag darin setzen.
    In deinen .Net Beispielen sind die Collections ja auch nur public-Eigenschaften,
    da der 'Zeitpunkt' der Änderung egal ist. Irgendwann kommt halt ein Execute und dann werden Parameter ausgewertet. Wer die wie geändert hat, interessiert ja keinen.

    rikyho schrieb:

    Ich stellte dann fest, dass ich eine Referenz auf einen Member zurückgegeben habe und dies die Ursache für den Effekt ist. Meine gelernten (aber kurz vergessene) Regel fiel mir wieder ein und sie schien sich zu bestätigen.

    Ich kenn die Regel nicht. Insbesondere nicht als allgemein gültig (wie fast alle Regeln).
    Änder die Regl doch um:
    Hab ich ein Problem, wenn jemand mein Objekt (unbemerkt) ändert?
    Ja -> Dann private und Methoden nachbauen, sonst public-Eigenschaft.
    Irgendwelche Events würde ich dafür nicht durch die Gegend schicken.

    Gruß,
    Jockel



  • rikyho schrieb:

    Ich habe gelernt, dass man keine Referenzen auf Member zurückgeben soll, da dies die Datenkapselung unterwandert. Es ist eben letztlich dasselbe, als würde man den Member einfach public machen.

    Referenz C# != Referenzen in C++

    Du vergleichst gerade Äpfel mit Birnen.

    So kannst Du z.B. an der String-Klasse sehen das einmal erzeugt String-Objekte nachträglich nicht mehr geändert werden können _obwohl_ Du ja eine Referenz auf das Objekt hast.



  • Danke erstmal für die Antworten.

    Mit dem Unterschied C++/C# hat das aber nichts zu tun.

    Ich schreib jetzt mal einfach runter, wie ich glaube, wie es ist. Möglicherweise schreib ich falsches, dann könnt ihr mich ja drauf hinweisen.

    class Test1
    {
      int a;
    
      public int A { get { return a; }}
    }
    

    Das ist gut. Denn der Aufrufer kann über die Property das Feld a nicht ändern, da nur ein get-Accessor implemetiert ist und hier keine Referenz zurückgegeben wird.

    class Test2
    {
      List<int> l;
    
      public List<int> L { get { return l; }}
    }
    

    Das ist schlecht. Der Aufrufer bekommt eine Referenz auf das Feld l und kann dieses darüber manipulieren, obwohl es eigentlich private ist.

    Obwohl beide Klassen also von der Struktur her fast gleich aussehen, hat die eine eine Problem, die andere nicht.

    Um diesen Fall geht es bei "Man soll keine Referenzen auf Member" zurückgeben.

    Selbstverständlich gibt es Situationen, in denen das trotzdem in Ordnung ist. Genauso wie es Situationen gibt, in denen man einen Member public macht. Sonst gäbe es das Schlüsselwort nicht. Aber bei zurückgegebenen Referenzen besteht die Gefahr, dass man ein Feld/Member ungewollt public macht, also die Kapselung aushebelt.

    Das ist, worum es in diesem Thread geht.

    Aus euren Antworten habe ich nun gelernt, dass ich tatsächlich, damit mein "save-Flag" korrekt gesetzt werden kann, am besten KEINE Referenz zurückgebe und deshalb dann eben gezwungen bin, die Methoden, die ich für die Manipulation meines Listen-Members benötige, selbst implementieren muss.

    Es hätte aber ja sein können, dass C# hier ein spezielles Sprachkonstrukt anbietet, dass ich noch nicht kenne und dass hinter den Klassen der .Net-Library steckt. Als C++-Programmierer kannte ich ja z.B. auch sowas wie Properties nicht.

    Um halt direkt alles "richtig" zu machen, und nicht aus Gewohnheit meine C++-Denkweise auf meine C#-Programme zu zwingen, hab ich dann lieber mal nachgefragt.



  • Mir fällt gerade auf...

    rikyho schrieb:

    class Test2
    {
      List<int> list;
    
      public List<int> List { get { return list; }}
    }
    

    Das ist schlecht. Der Aufrufer bekommt eine Referenz auf das Feld l und kann dieses darüber manipulieren, obwohl es eigentlich private ist.[/quote]

    Ich denke hier liegt der Denkfehler. Der Aufrufer kann das Feld list _nicht_ ändern, sondenr nur dessen Inhalt. Privat bedeutet hier, das man von ausserhalb der Klasse das eigendliche Listenobjekt nicht austauschen kann. Also sowas wie List = new List<int>() geht nicht.

    Wohl aber kann man den Inhalt der Liste verändern, was man wiederum dadurch unterbinden kann indem man eine ReadOnly-Liste nach aussen verwendet.

    Anders war das bei den C++ Referenzen. Beispiel:

    class Test
    {
        char* data;
    
        char*& GetData() { return data; }
    };
    

    in dem Beispiel wäre es jetzt folgendes möglich:

    t.GetData() = new char[128];

    was zu beliebigen Seiteneffekten führen kann je nachdem was die Klasse intern macht.


Anmelden zum Antworten