it-swarm.com.de

Was ist der beste Weg, um Updates nur dann zu verarbeiten, wenn sich ein Wert geändert hat?

Ich habe eine Anwendung, die eine Reihe von Werten erhält, die auf verschiedene Eigenschaften eines Objekts angewendet werden müssen (versiegelte Klasse). Ursprünglich habe ich den Wert nur festgelegt, ohne etwas zu überprüfen, und das Objekt aktualisiert, aber manchmal waren die neuen Werte natürlich nicht gültig, und manchmal waren sie mit vorhandenen Werten identisch, sodass das Ausführen eines Updates eine Verschwendung von Systemressourcen war Ehrlich gesagt ein ziemlich langsamer Prozess.

Um dies zu umgehen, habe ich einen privaten Bool erstellt, der auf true gesetzt wird, wenn ein neuer Wert angewendet wird. Dieser wird mit einer Methode ermittelt, die prüft, ob der Wert unterschiedlich (und gültig) ist:

private bool updated = false;
private bool updateValue(string property, string value, bool allowNulls = false)
{
    if ((allowNulls || !string.IsNullOrEmpty(value)) && !property.Equals(value)) { updated = true;  return true; }
    return false;
}

Wenn ich dann den Wert einer Eigenschaft auf etwas Neues setzen muss, habe ich eine einzelne If-Anweisung:

if (updateValue(obj.property, newValue))
{ obj.property = newValue; }

Und dann natürlich, wenn allen Eigenschaften ihre neuen Werte zugewiesen wurden:

if (updated)
{ obj.commitChanges(); }

Also, 1) Gibt es eine bessere Möglichkeit, dies zu tun, und 2) gibt es eine präzisere Möglichkeit, meine If-Anweisung auszuführen? Einzeilige If-Klauseln wie diese nerven mich; anscheinend sollte ich sagen können "Setze A auf B, wenn C".

7
Michael Bailey

Nein.

Nein.

Alle nein!

Lassen Sie Ihre Anrufer nicht die ganze Arbeit machen! Wenn ich einen Wert festlegen möchte, möchte ich ihn nur festlegen! Und Sie arbeiten in einer Sprache, die es einfach macht, Setter und Getter zu schreiben. Benutze sie.

Folgendes sollten Ihre Anrufer schreiben:

obj.propA = valueA;
obj.propB = valueB;

Und es wird Zeit, obj beizubehalten. Wenn Sie einen aktiven Aufnahmestil verwenden, ist dies nur Folgendes:

obj.Save();

Ihre Setter müssen Ausnahmen auslösen, wenn Sie einen schlechten Wert angeben.

Zweitens ist es Ihren Anrufern egal, was Save() oder commit() tut oder nicht tut. Die Persistenzmethode selbst muss fehlerhafte Flags überprüfen oder Hashes oder Serialisierungen vergleichen, um festzustellen, ob sie tatsächlich funktioniert.

Jede wirklich, egal welche Strategie Sie wählen, ist wahrscheinlich völlig in Ordnung. Ja wirklich! Vergleichen Sie die Codezeilen, die Auswirkungen auf die Leistung und das semantische "Gefühl" der einzelnen Zeilen und wählen Sie diejenige aus, die sich richtig anfühlt.

Was "richtig" ist, kann sogar zwischen Klassen oder Projekten variieren.

Lassen Sie Ihre Anrufer auf keinen Fall darauf achten.

10
svidgen

Es gibt viele Ansätze für dieses Problem:

Bibliotheken von Drittanbietern Verwenden Sie eine Lösung von Drittanbietern, wie in den Kommentaren verlinkt. Dies ist zwangsläufig robuster (und testbarer) als das Umleiten von Ihrer Hauptaufgabe, um diese Funktionalität auszubauen.

Property Get/Set-Methoden Behandeln Sie die Änderungen innerhalb der normalen Eigenschaftsmethoden:

private bool hasUpdates = false;

...

private string name;
public string Name
{
  get { return name; }
  set
  {
     if (!value.Equals(name) && !string.IsNullOrEmpty(value))
     {
       name = value;
       hasUpdates = true;
     }
  }
}

Dies erfordert etwas mehr Code-Bulk. Daher ist es möglicherweise nicht vorzuziehen, wenn viele solcher Eigenschaften vorhanden sind oder Sie wirklich gerne tippen oder dieser Code nicht aus DSL generiert wird.

Single Smart-Update-Methode

private bool hasUpdates = false;

...

private string name;

public bool TryUpdate(string propertyName, T value, out T newValue) {
   var classProperty = this.GetType().GetProperty(propertyName);

   if (classProperty == null) { return false; }

   if (!classProperty.GetValue(this).Equals(value)) {
     classProperty.SetValue(value);
     hasUpdates = true;
     newValue = value;
   }
}

Hier benötigen Sie nur eine sperrige Methode und nicht eine für jede Eigenschaft.

Hash-Vergleich

Eine andere Lösung wäre, einen Cache mit Objekten und deren Hash-Wert beim Abrufen aus der Datenbank zu verwalten und den Hash dann erneut zu vergleichen, wenn commitChanges() aufgerufen wird. Verarbeiten Sie nur die Objekte, deren Hashes sich geändert haben.

3
Dan1701

Wenn Ihr Objekt passiv wäre und öffentliche Felder anstelle von Eigenschaften hätte, könnten Sie eine elegantere Update() -Prozedur schreiben und das Feld als ref -Parameter akzeptieren:

static void Update( ref string curVal, string newVal, ref bool isDirty )
{   if( curVal != newVal )
    {   isDirty = true;
        curVal  = newVal;
    }
}
//...
updated = false;
Update( ref obj.Prop1, "NewValue1", ref updated );
Update( ref obj.Prop2, "NewValue2", ref updated );
//... updates of many other properties...
if( updated )
{   CommitChanges( obj );  }

Mit dem Overhead von OOP haben Sie jedoch mehr Arbeit zu erledigen. Die folgende Konsolenanwendung zeigt eine mögliche Lösung für Eigenschaften mit trivialen Setzern (die Klasse Trivial) und nicht trivialen Setzern (die Klasse NonTrivial), die willkürliche Berechnungen durchführen:

// TODO: Maybe store property names as constants?
using System;
using System.Collections.Generic;

class Program {

class Trivial
{   Dictionary<string, string> Props;

    public bool IsDirty // TODO: Make it writable for resetting?
    {   get; private set;  }

    public void PropSet( string prop, string value )
    {   string currentVal;

        currentVal = Props[ prop ];
        if( currentVal != value )
        {   IsDirty = true;
            Props[ prop ] = value;
        }
    }

    public string Prop1
    {   set
        {   Props["Prop1"] = value;  }      
        get
        {   return Props["Prop1"];  }
    }

    public string Prop2
    {   set
        {   Props["Prop2"] = value;  }      
        get
        {   return Props["Prop2"];  }
    }

    public Trivial()
    {   Props = new Dictionary<string, string>();
        Props.Add("Prop1", "");
        Props.Add("Prop2", "");     
    }
}

class NonTrivial
{   delegate void FPropSet( string value );
    // TODO: Modify PropInfo according to your needs, e.g. add an
    //       allowNull flag...
    class PropInfo
    {   public FPropSet Set;
        public string   Value;
    }

    Dictionary< string, PropInfo > Props;

    public bool IsDirty // TODO: Make it writable for resetting?
    {   get; private set;  }

    public string Prop1
    {   set
        {   PropSet( "Prop1", value );  }       
        get
        {   return PropGet( "Prop1" );  }
    }

    void SetProp1( string value )
    {   // Very heavy calculations!
    }

    public string Prop2
    {   set
        {   PropSet( "Prop2", value );  }       
        get
        {   return PropGet( "Prop2" );  }
    }

    void SetProp2( string value )
    {   // Super heavy calculations!
    }   

    void RegisterProp( string prop, FPropSet setter, string defValue )
    {   PropInfo info = new PropInfo();
        info.Set   = setter;
        info.Value = defValue;
        setter( defValue );
        Props.Add( prop, info );
    }

    string PropGet( string prop )
    {   return Props[ prop ].Value;  }

    public void PropSet( string prop, string value )
    {   PropInfo info;
        info = Props[ prop ];
        // TODO: Modify the comparison according to your needs:
        if( info.Value != value )
        {   info.Set( value );
            info.Value = value;
            IsDirty = true;
        }
    }

    public NonTrivial()
    {   Props = new Dictionary< string, PropInfo >();
        RegisterProp( "Prop1", SetProp1, "A" );
        RegisterProp( "Prop2", SetProp2, "1" );
    }
}

public static void Main(string[] args)
{   Trivial    triv    = new Trivial   ();
    NonTrivial nonTriv = new NonTrivial();

    triv.Prop1 = "A"; // The setter will not change .IsDirty
    triv.Prop2 = "1"; // Should it?
    triv.PropSet("Prop1", "B"); // Update property only if changed
    if( triv.IsDirty )
    {   // This work will not be wasted
    }

    nonTriv.Prop1 = "A"; // The setter will change .IsDirty
    nonTriv.Prop2 = "1"; // Should it?
    nonTriv.PropSet("Prop1", "B");  // Update property only if changed
    if( nonTriv.IsDirty )
    {   // It will not be labor lost
    }   

    Console.ReadKey(true);
}

}

Wenn der Aufrufer nicht daran interessiert ist, ob sich die tatsächlichen Daten geändert haben, können Sie die Veröffentlichung der Eigenschaft IsDirty aufheben und sie intern in Ihrer Methode CommitChanges() verwenden, die ich in CommitChangesIfAny() umbenennen sollte den bedingten Charakter der Festschreibungsoperation widerspiegeln.

Eine weitere Option ist Reflection, wobei Sie dasselbe dynamisch tun können (Edit : siehe Single Smart-Update-Methode in = Dan1701 's Antwort).

1
Ant_222

Wie wäre es, wenn Sie die updateValue-Methode so ändern, dass sie null statt false zurückgibt (und sie möglicherweise in getNewValue umbenennt), und dann einfach meine Eigenschaften mit einem bedingten Operator festlegen:

obj.property = getNewValue(obj.property, newValue) | obj.property;

Die Methode getNewValue ist weiterhin für das Setzen des Bits 'IsDirty' verantwortlich, mit dem bestätigt werden kann, dass die neuen Werte festgeschrieben werden müssen.

0
Michael Bailey