Problem mit Eigenschaften



  • class Foo{
    	public void Change(){
    		Console.WriteLine("Foo changed");
    	}
    }
    
    class Bar{
    	Foo my_foo = new Foo();
    	public Foo Foo{
    		get{
    			Console.WriteLine("Someone's reading Bar's Foo");
    			return my_foo;
    		}
    		set{
    			my_foo = value;
    			// Dies wird nie ausgegeben
    			Console.WriteLine("Bar noticed that Foo changed");
    
    		}
    	}
    }
    
    class MainClass
    {
    	public static void Main(string[] args)
    	{
    		Bar b = new Bar();
    		b.Foo.Change();
    	}
    }
    

    Wie kann ich sicher stellen, dass Bar merkt wenn Foo auf diese Weise verändert wurde?

    Edit:
    Titel angepasst, sorry für den Typo das war nicht beabsichtigt.



  • Titel: Ich hier

    Wie wärs mit was aussagekräftigem ?



  • Hi Ben,

    so geht das gar nicht, weil in Deinem Code einfach nie der Setter aufgerufe wird. Der Setter wird nur dann aufgerufen, wenn 'b.Foo' alleine auf der linken Seite einer Zuweisung steht, sonst nie.

    Um trotzdem eine Benachrichtigung zu erreichen, musst Du wohl oder übel mit Events arbeiten. Das Standard-Muster hierzu sähe folgendermaßen aus:

    class Foo {
        public event EventHandler FooChanged;
    
        public void Change() {
            Console.WriteLine("Foo changed");
            OnFooChanged();
        }
    
        protected virtual void OnFooChanged() {
            RaiseFooChanged();
        }
    
        protected void RaiseFooChanged() {
            if (FooChanged != null)
                FooChanged(this, EventArgs.Empty);
        }
    }
    
    class Bar {
        Foo my_foo = new Foo();
    
        public Bar() {
            my_foo.FooChanged += my_foo_Changed;
        }
    
        public Foo Foo {
            get { return my_foo; }
            set { my_foo = value; }
        }
    
        private void my_foo_Changed(object sender, EventArgs e) {
            Console.WriteLine("Someone changed foo");
        }
    }
    

    Der Code in 'Foo' mag ein wenig aufwendig aussehen und ist so ausführlich natürlich nicht unbedingt immer nötig. Allerdings handelt es sich hierbei um ein nützliches Idiom. Eigene EventHandler sollten stehts die obige Form besitzen, d.h. sie sollten vom Typ des Delegats 'EventHandler' oder 'EventHandler<T>' sein.

    Die Methode 'RaiseFooChanged' ist lediglich „convenience“, d.h. sie erleichtert das Auslösen des Events. In VB wäre sowas unnötig, da es dort bereits etwas entsprechendes in Form des 'RaiseEvents'-Schlüsselworts gibt.
    Die Methode 'OnFooChanged' ist dafür da, dass man auch in von 'Foo' abgeleiteten Klassen komfortabel auf die Änderungen reagieren kann, ohne einen EventHandler abonnieren zu müssen.

    Dieses Muster benutzen z.B. sämliche WinForms-Controls des .NET-Frameworks für ihre Properties.



  • Danke für die Antwort, auch wenn sie mir nicht gefällt. Mir war auch noch eine weitere unschöne Lösung eingefallen. Falls sie jemand interessiert:

    Mach Foo immutable, d.h. sorg dafür, dass Foo nach der Konstruktion nicht mehr verändert wird. Alle Methoden die etwas ändern geben Kopien zurück. Dadurch wird der Benutzer gezwungen eine Zuweisung zu schreiben.

    b.Foo = b.Foo.ChangeX().ChangeY();
    

    Edit: Würde es Probleme mit folgendem Code geben oder ist der einfach nur unschön?

    public static void Main(string[] args)
    {
        Bar b = new Bar();
        b.Foo.Change();
        b.Foo = b.Foo;
    }
    


  • Ben04 schrieb:

    Danke für die Antwort, auch wenn sie mir nicht gefällt.

    Nur: wieso gefällt sie Dir nicht? Die Lösung ist für Dein Problem maßgeschneidert und ist die .NET-Standardantwort auf Deine Frage.

    Mir war auch noch eine weitere unschöne Lösung eingefallen. […]

    Und was ist hieran unschön? Das ist sogar eine sehr gute Lösung. Immutabilität ist sowieso eine der am meisten unterschätzen Programmiertechniken.

    Edit: Würde es Probleme mit folgendem Code geben oder ist der einfach nur unschön?

    Das kann (und wird) Probleme geben, da der Compiler solche Zuweisungen wegoptimieren kann. Properties sind für den Compiler Dinge, die ihren Wert zwischen zwei Aufrufen garantiert nicht ändern, solange nicht zwischendurch die Klasse modifiziert wurde. Dadurch „weiß“ der Compiler, dass Deine Zuweisung keinen Effekt hat.

    Im Klartext: Nebeneffekte von Properties darf der Compiler ignorieren.



  • Konrad Rudolph schrieb:

    Im Klartext: Nebeneffekte von Properties darf der Compiler ignorieren.

    Wieso darf er das denn? Letztlich sind das doch zwei Methodenaufrufe, wo wer weiß was passieren kann. 😕



  • Konrad Rudolph schrieb:

    Ben04 schrieb:

    Danke für die Antwort, auch wenn sie mir nicht gefällt.

    Nur: wieso gefällt sie Dir nicht?

    Mir gefällt die Idee nicht an jede Klasse die eventuell mal als Eigenschaft verwendet werden könnte ein OnChangeEvent dran zu schrauben. Desweiteren ist die Lösung wahrscheinlich auch aus Sicht der Effizienz suboptimal. Ein einfacher nicht virtueller Funktionsaufruf (der set-Aufruf) ist halt sehr billig. Wobei ich das erste Argument schwerer gewichten würde.

    Konrad Rudolph schrieb:

    Und was ist hieran unschön?

    Man benutzt die Klasse wie ein Value. Semantisch übergibt man Instanzen nicht per Referenz sonder per Wert und das erwarte ich nicht von einem Klassen-Typ. Da rechne ich damit, dass meine Klasse selbst verändert wird. In Java ist das in Ordnung, jedoch in C# dachte ich, dass man in den Fällen structs einsetzen würde. Nun ist Foo bei mir jedoch schon ein wenig zu fettleibig um direkt auf dem Argumenten-Stapel herum geschoben zu werden.

    Ich werde in meinem konkreten Fall wahrscheinlich Immutabilität einsetzen.

    Das kann (und wird) Probleme geben, da der Compiler solche Zuweisungen wegoptimieren kann. Properties sind für den Compiler Dinge, die ihren Wert zwischen zwei Aufrufen garantiert nicht ändern, solange nicht zwischendurch die Klasse modifiziert wurde. Dadurch „weiß“ der Compiler, dass Deine Zuweisung keinen Effekt hat.

    Im Klartext: Nebeneffekte von Properties darf der Compiler ignorieren.

    Folgendes wäre aber in Ordnung, oder?

    public static void Main(string[] args)
    {
        Bar b = new Bar();
        Foo f = b.Foo;
        f.Change();
        b.Foo = f;
    }
    


  • nachgefragt schrieb:

    Konrad Rudolph schrieb:

    Im Klartext: Nebeneffekte von Properties darf der Compiler ignorieren.

    Wieso darf er das denn? Letztlich sind das doch zwei Methodenaufrufe, wo wer weiß was passieren kann. 😕

    Ja, das ist eben eine Abmachung. Die Sprachbeschreibung sichert dem Compiler zu, dass diese speziellen Methodenaufrufe speziell sind und keine Seiteneffekte beim wiederholten Aufrufen erzeugen.

    … Ehrlich gesagt finde ich die entsprechende Definition im CIL-Standard monetan nicht, aber zumindest beim Debuggen *wird* sowas Probleme geben und ich bin mir ziemlich sicher, dass sowas auch tatsächlich vom JITter wegoptimiert wird.



  • Ben04 schrieb:

    Konrad Rudolph schrieb:

    Ben04 schrieb:

    Danke für die Antwort, auch wenn sie mir nicht gefällt.

    Nur: wieso gefällt sie Dir nicht?

    Mir gefällt die Idee nicht an jede Klasse die eventuell mal als Eigenschaft verwendet werden könnte ein OnChangeEvent dran zu schrauben.

    Das ist auch nicht Sinn der Sache. Solche Events sollten nur dort existieren, wo es auch sinnvoll sein kann. Üblicherweise ist eine solche Benachrichtigung gar nicht nötig. UI-Komponenten sind eine wesentliche Ausnahme.

    Die logische Alternative ist übrigens wirklich Immutabilität, das macht das Design auch often leichter: Wenn eine Klasse nicht geändert werden kann, ohne dass das jemand anders mitbekommen muss, dann ist es wahrscheinlich eine gute Idee, Modifikationen grundsätzlich zu verbieten.

    Konrad Rudolph schrieb:

    Und was ist hieran unschön?

    Man benutzt die Klasse wie ein Value. Semantisch übergibt man Instanzen nicht per Referenz sonder per Wert und das erwarte ich nicht von einem Klassen-Typ. Da rechne ich damit, dass meine Klasse selbst verändert wird. In Java ist das in Ordnung, jedoch in C# dachte ich, dass man in den Fällen structs einsetzen würde.

    Nein, an sich nicht. Wie gesagt, Immutabilität ist generell unterschätzt aber um nur mal ein prominentes Gegenbeispiel zu nennen: 'System.String' ist eine immutable Klasse.

    Ich möchte an dieser Stelle auch auf Eric Lipperts Blog verweisen. Lippert ist Core-C#-Entwickler und hat starke Meinungen zu Immutabilität:

    http://blogs.msdn.com/ericlippert/archive/tags/Immutability/default.aspx?p=1

    Folgendes wäre aber in Ordnung, oder?

    public static void Main(string[] args)
    {
        Bar b = new Bar();
        Foo f = b.Foo;
        f.Change();
        b.Foo = f;
    }
    

    Hmm. Ich denke, nicht. Letztendlich ist das ja für den Compiler dasselbe wie vorher. Der Compiler darf die Reihenfolge von Anweisungen ändern, um Optimierungen vorzunehmen, solange die Semantik erhalten bleibt. Ich würde mich nicht darauf verlassen, dass sowas funktioniert. Mal davon abgesehen, dass das von Interface-Gesichtspunkten natürlich eine Katastrophe ist.



  • Die Lösung die Klasse immutable zu machen ist technisch sicherlich möglich, aber das wäre doch aus völlig falschem Grund eingesetzt und der so erzeugte Code ist viel umständlicher als er sein müsste. Abgesehn von evtl. möglichen Performanceeinbußen da ja laufend neue Objekte der Klasse erzeugt werden, obwohl überhaupt nicht nötig.

    Der Eventansatz von Konrad Rudolph ist der technisch viel bessere, auch wenn er bissle Arbeit bedeutet, es kommt halt net alles umsonst. Nur brauchst du nicht unbedingt was selber definieren, meist reicht auch INotifyPropertyChanged, je nachdem wie du drauf reagieren willst auf eine Zustandsänderung des Objektes.


Anmelden zum Antworten