none
Event bei Änderung in einer Property RRS feed

  • Frage

  • Hallo zusammen,

    gibt es eine Möglichkeit sich von .net, optimalerweise per Event, Benachrichtigen zu lassen, wenn sich der Wert einer Property (keine DP) ändert?

    Vielen Dank und viele Grüße
    Holger M. Rößler


    Kaum macht man es richtig, schon funktioniert es
    Samstag, 9. Juli 2011 09:26

Antworten

Alle Antworten

  • Hallo Holger,

    ohne zusätzliche (eigenhändige) Implementierung nicht.

    Eine Eigenschaft definiert nichts weiter als die Methoden, die durch ihre Get und Set Methoden implementiert werden.
    Und dafür gibt es keine inhärente Festlegung, wie sie implementiert werden (müssen).
    Automatische Eigenschaften implementieren dies zwar über eine Variable; letztendlich ist aber alles möglich,
    weswegen Property.SetValue keine Annahmen macht und auch keine Benachrichtigung kennt.

    Deswegen muss man entweder auf bekannte Muster wie INotifyPropertyChanged zurückgreifen,
    oder auf eine DependencyProperty in WPF/Silverlight.

    Windows Forms verwendet für die Datenbindung der TypeDescriptor Klasse, deren PropertyDescriptor
    Benachrichtungen unterstützt, solange man dies über dessen SetValue Methode macht.
    Und insofern ein Vorläufer der DependencyProperties.

    Gruß Elmar

    Samstag, 9. Juli 2011 10:49
    Beantworter
  • Hallo Elmar,

    vielen Dank für deine (mal wieder) excellenten Ausführungen ;)

    Mein Problem ist folgendes: Ich habe eine Basisklasse, die über eine Property 'public bool Changed {get; protected set;}' verfügt. Jede Änderung an einer Property in einer Subklasse sollte Changed auf true setzten, damit die Entität weiss, dass sie geändert wurde und die Entität sich dann bei einem InsertOrtUpdate() zurück auf die Datenbank schreibt wird.

    Natürlich ist es auch seither das setzen von Hand möglich. Da ich aber ein kleiner Schussel bin, vergesse ich das hin und wieder. Daher suche ich nach einer Möglichkeit, dies zu automatisieren. Ich hoffte, dass da was per Reflektion geht...aber leider wohl nicht!

    Wie würdest du das lösen?

    Vielen Dank und viele Grüße
    Holger M. Rößler


    Kaum macht man es richtig, schon funktioniert es
    Samstag, 9. Juli 2011 13:44
  • Hallo Holger,

    Lösungansätze gibt es mehrere, die mehr oder minder aufwändig sind:

    1. Verwenden von Proxy-Klassen, siehe z. B.:
      Automatic Implementation of INotifyPropertyChanged on POCO Objects
      Using DynamicObject to Implement General Proxy Classes

    2. ähnlich wie 1. jedoch mittels AOP,
      z. B. mit PostSharp Easier PropertyChanged notification with PostSharp

    3. Verwendenvon eigenen Strukturen/Klassen für die Dateneigenschaften, die sich darum (auch) kümmern.
      U. a. hat Sacha Barber hat einen Ansatz in seiner ersten Cinch-Version.
      Was im Groben eine Variation einer DependyProperty (light) ist.

    4. Eine separate Pufferung wie ihn u. a. das Entity Framework mit dem ObjectStateManager verwendet.
      Dabei sind die Eigenschaften (bzw. ihre Werte) gepuffert, so dass ein Abgleich möglich ist.
      Ähnliche Ansätze findet man auch bei NHibernate.

    5. ... Verzehr von Ginseng-Kapseln - soll die Vergesslichkeit mindern ;-))

    Alle Varianten (außer 5) erfordern jedoch eine Anpassung der Klassen, wie z. B. virtuelle Eigenschaften für Proxies.

    Gruß Elmar

    Samstag, 9. Juli 2011 20:03
    Beantworter
  • Hallo Elmar,

    ich glaube der 5. Punkt löst mein Problem am ehesten. ;-))

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


    Kaum macht man es richtig, schon funktioniert es
    Samstag, 9. Juli 2011 23:39
  • Hallo zusammen,
    ich habe mir jetzt ein Interface ("IReflectionPropertyAccess") geschrieben, dass das Problem lösen kann. Hier die Implementierung in der Basisklasse "DbEntity":

    public object this[string propertyName] {
      get {
    #if DEBUG
       if(this.GetType().GetProperty(memberName) == null) {
         throw new ArgumentException("No member '" + memberName + "' found");
       }
    #endif
       return this.GetType().GetProperty(memberName).GetValue(this, null);
      }
      
      set {
    #if DEBUG
       if(this.GetType().GetProperty(memberName) == null) {
         throw new ArgumentException("No member '" + memberName + "' found");
       }
    #endif
       this.GetType().GetProperty(memberName).SetValue(this, value, null);
       this.Changed = true; //<-- ganz wichtige Zeile!!
      }
    }
    
    public bool SupportsPropertyValueSetting {
      get { return true; }
    }
    



    Ich glaube die Variante dürfte die sein, die am wenigstens Kopfzerbrechen bereitet und ich spare mir das Geld für Ginseng-Kapseln :D
    Viele Grüße
    Holger M. Rößler

    Kaum macht man es richtig, schon funktioniert es
    Sonntag, 10. Juli 2011 08:16
  • Hallo Holger,

    da ich schön allgemein formuliert hatte, wäre das in etwa 3.)

    Wenn Du das Interface massiv nutzen willst, solltest Du überlegen,
    einige Optimierungen vorzunehmen.

    Anstatt jedes mal this.GetType().GetProperty zu bemühen, könnte ein Dictionary<string, PropertyInfo>
    je Type als Cache  einiges beschleunigen.  Dazu siehe C# MVP Jon Skeet in
    http://stackoverflow.com/questions/1204748/cache-reflection-results-class-properties

    Eine andere Variante wäre, die Werte gleich in einem Dictionary verschwinden zu lassen.

    Im Sinne eines sauberen Designs sollte Changed Bestandteil der Schnittstelle sein.

    Weiter zu überlegen wäre, sich den Indexer nicht dauerhaft zu verbauen -
    denn es könnte mal sein, dass es "wichtigeres" dafür gibt.

    Und entweder zwei Methoden SetValue, GetValue zu verwenden - wie es auch DependencyProperty tut,
    (bzw. die Implementation explizit zu machen - was wieder mehr Ginseng braucht ;-)

    Um das in Code zu konkretisieren:

     

      public interface IReflectionPropertyAccess
      {
        bool Changed { get; set; }
    
        void SetValue(string memberName, object value);
        object GetValue(string memberName);
    
        // ggf. explizit oder drauf verzichten
        object this[string memberName] { get; set; }
      }
    
      public class BaseClass : IReflectionPropertyAccess
      {
        public bool Changed { get; set; }
    
        public void SetValue(string memberName, object value)
        {
          GetProperty(memberName).SetValue(this, value, null);
          this.Changed = true;
        }
    
        public object GetValue(string memberName)
        {
          return GetProperty(memberName).GetValue(this, null);
        }
    
        // hier als expliziter Indexer
        object IReflectionPropertyAccess.this[string memberName]
        {
          get { return GetValue(memberName); }
          set { SetValue(memberName, value); }
        }
    
        // Optional (nur wenn mans brauchen kann)
        public void GetValue<T>(string memberName, out T value)
        {
          value = (T)GetProperty(memberName).GetValue(this, null);
        }
    
        protected virtual PropertyInfo GetProperty(string memberName)
        {
          var propertyInfo = this.GetType().GetProperty(memberName);
          if (propertyInfo == null)
            throw new ArgumentException("No member '" + memberName + "' found", "memberName");
          return propertyInfo;
        }
      }
    
    

    Ohne den angesprochenen Cache, dafür mit einer virutellen GetProperty (als potentielle Ausbaustufe).
    Und ganz sicher kein Meisterwerk ...

    Gruß Elmar

    Montag, 11. Juli 2011 19:17
    Beantworter
  • Hallo Elmar,

     

    Wenn Du das Interface massiv nutzen willst, solltest Du überlegen, einige Optimierungen vorzunehmen. Anstatt jedes mal this.GetType().GetProperty zu bemühen, könnte ein Dictionary je Type als Cache einiges beschleunigen. Dazu siehe C# MVP Jon Skeet in http://stackoverflow.com/questions/1204748/cache-reflection-results-class-properties

    Gute Idee. Das setzen von Properties (sofern nicht vom Benutzer überschrieben), läuft nur noch über das Interface.

    Eine andere Variante wäre, die Werte gleich in einem Dictionary verschwinden zu lassen.

    Wird nicht funktionieren. Die DbEntity-Klasse ist speziell darauf ausgelegt, seine Queries, bzw. die seiner Unterklassen, selbst aus Metadaten (Properties) zu generieren. Von daher sind Properties sowieso notwendig...

    Im Sinne eines sauberen Designs sollte Changed Bestandteil der Schnittstelle sein.

    Hmm... IReflectionPropertyAccess soll ja nur den Zugriff auf die Properties einer Entity erlauben (bzw. mir das Geld für die Ginseng-Kapseln sparen). Die Änderung betrifft ja nur die Entität. Oder habe ich jetzt das was falsch verstanden?

    Weiter zu überlegen wäre, sich den Indexer nicht dauerhaft zu verbauen - denn es könnte mal sein, dass es "wichtigeres" dafür gibt.

    Natürlich könnte das mal sein...Aber im Prinzip sind die Entities nur "dumme" Datenträger, die nur für die Querygenerierung über eine erweiterte "Intelligenz" verfügen. Und mehr wird es bei den Entities auch nicht werden. Geschäftslogik wird es in Entities sowieso nie geben. Dafür gibt es DataModels...

    Und entweder zwei Methoden SetValue, GetValue zu verwenden - wie es auch DependencyProperty tut, (bzw. die Implementation explizit zu machen - was wieder mehr Ginseng braucht ;-)

    NEIN!! Bitte keine Ginseng mehr ;-)

     

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


    Kaum macht man es richtig, schon funktioniert es
    Montag, 11. Juli 2011 20:39