c # marcando la proprietà della class come sporca

Quello che segue è un semplice esempio di enum che definisce lo stato di un object e una class che mostra l’implementazione di questo enum.

public enum StatusEnum { Clean = 0, Dirty = 1, New = 2, Deleted = 3, Purged = 4 } public class Example_Class { private StatusEnum _Status = StatusEnum.New; private long _ID; private string _Name; public StatusEnum Status { get { return _Status; } set { _Status = value; } } public long ID { get { return _ID; } set { _ID = value; } } public string Name { get { return _Name; } set { _Name = value; } } } 

quando si compila l’object class con i dati dal database, si imposta il valore enum su “clean”. con l’objective di mantenere la maggior parte della logica fuori dal livello di presentazione, come possiamo impostare il valore enum su “dirty” quando viene modificata una proprietà.

stavo pensando qualcosa sulla falsariga di;

 public string Name { get { return _Name; } set { if (value != _Name) { _Name = value; _Status = StatusEnum.Dirty; } } } 

nel setter di ogni proprietà della class.

sembra una buona idea, qualcuno ha idee migliori su come assegnare la bandiera sporca senza farlo nel livello di presentazione.

Quando vuoi davvero una bandiera sporca a livello di class (o, per quel che riguarda, notifiche) – puoi usare trucchi come sotto per minimizzare il disordine nelle tue proprietà (qui mostra sia IsDirty che PropertyChanged , solo per divertimento).

Ovviamente è un aspetto banale utilizzare l’approccio enum (l’unica ragione per cui non l’ho fatto è stato mantenere semplice l’esempio):

 class SomeType : INotifyPropertyChanged { private int foo; public int Foo { get { return foo; } set { SetField(ref foo, value, "Foo"); } } private string bar; public string Bar { get { return bar; } set { SetField(ref bar, value, "Bar"); } } public bool IsDirty { get; private set; } public event PropertyChangedEventHandler PropertyChanged; protected void SetField(ref T field, T value, string propertyName) { if (!EqualityComparer.Default.Equals(field, value)) { field = value; IsDirty = true; OnPropertyChanged(propertyName); } } protected virtual void OnPropertyChanged(string propertyName) { var handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } } 

Potresti anche scegliere di spingerne un po ‘in una class base astratta, ma questa è una discussione separata

Un’opzione è cambiarla su scrittura; un altro è mantenere una copia di tutti i valori originali e calcolare la sporcizia quando qualcuno la chiede. Questo ha l’ulteriore vantaggio di poter indicare esattamente quali campi sono stati modificati (e in che modo), il che significa che è ansible emettere dichiarazioni di aggiornamento minime e semplificare leggermente la risoluzione dei conflitti di fusione.

Puoi anche mettere tutto il controllo della sporcizia in un posto, in modo da non inquinare il resto del codice.

Non sto dicendo che è perfetto, ma è un’opzione che vale la pena considerare.

Se vuoi implementarlo in questo modo e vuoi ridurre la quantità di codice, potresti prendere in considerazione l’applicazione di Aspect Oriented Programming.

Ad esempio, puoi utilizzare un weaver in fase di compilazione come PostSharp e creare un ‘aspetto’ che può essere applicato alle proprietà. Questo aspetto quindi si assicura che il tuo flag sporco sia impostato quando appropriato.

L’aspetto può assomigliare a questo:

 [Serializable] [AttributeUsage(AttributeTargets.Property)] public class ChangeTrackingAttribute : OnMethodInvocationAspect { public override void OnInvocation( MethodInvocationEventArgs e ) { if( e.Delegate.Method.ReturnParameter.ParameterType == typeof(void) ) { // we're in the setter IChangeTrackable target = e.Delegate.Target as IChangeTrackable; // Implement some logic to retrieve the current value of // the property if( currentValue != e.GetArgumentArray()[0] ) { target.Status = Status.Dirty; } base.OnInvocation (e); } } } 

Offcourse, ciò significa che le classi per cui si desidera implementare ChangeTracking devono implementare l’interfaccia IChangeTrackable (interfaccia personalizzata), che ha almeno la proprietà ‘Status’.

Puoi anche creare un attributo personalizzato ChangeTrackingProperty e assicurarti che l’aspetto che è stato creato in precedenza venga applicato solo alle proprietà decorate con questo attributo ChangeTrackingProperty .

Per esempio:

 public class Customer : IChangeTrackable { public DirtyState Status { get; set; } [ChangeTrackingProperty] public string Name { get; set; } } 

Questo è un po ‘come lo vedo io. Puoi anche assicurarti che PostSharp controlli in fase di compilazione se le classi che hanno proprietà decorate con l’attributo ChangeTrackingProperty, implementano l’interfaccia IChangeTrackable.

Dai un’occhiata a PostSharp ( http://www.postsharp.org/ ). Puoi facilmente creare un Attributo che lo segna come sporco, puoi aggiungere l’attrubute a ogni proprietà che ne ha bisogno e mantiene tutto il tuo codice in un unico posto.

In parole povere Crea un’interfaccia con lo stato in cui la class lo implementa. Crea un attributo che può essere applicato alle proprietà e cast alla tua interfaccia per impostare il valore quando qualcosa cambia una delle proprietà contrassegnate.

Questo metodo si basa su un insieme di diversi concetti forniti in questo thread. Pensavo di metterlo fuori là per chiunque stia cercando un modo per farlo in modo pulito ed efficiente, come lo ero io.

La chiave di questo concetto ibrido è che:

  1. Non si desidera duplicare i dati per evitare il gonfiore e il consumo delle risorse;
  2. Vuoi sapere quando le proprietà dell’object sono cambiate da un dato stato originale / pulito;
  3. Si desidera che il flag IsDirty sia accurato e che richieda poco tempo / potenza di elaborazione per restituire il valore; e
  4. Vuoi essere in grado di dire all’object quando considerarsi pulito di nuovo. Ciò è particolarmente utile quando si costruisce / si lavora all’interno dell’interfaccia utente.

Dati questi requisiti, questo è ciò che mi è venuto in mente e sembra funzionare perfettamente per me, ed è diventato molto utile quando si lavora contro le interfacce utente e si acquisiscono accuratamente le modifiche degli utenti. Ho anche pubblicato un “Come usare” di seguito per mostrarti come lo utilizzo nell’interfaccia utente.

L’object

 public class MySmartObject { public string Name { get; set; } public int Number { get; set; } private int clean_hashcode { get; set; } public bool IsDirty { get { return !(this.clean_hashcode == this.GetHashCode()); } } public MySmartObject() { this.Name = ""; this.Number = -1; MakeMeClean(); } public MySmartObject(string name, int number) { this.Name = name; this.Number = number; MakeMeClean(); } public void MakeMeClean() { this.clean_hashcode = this.Name.GetHashCode() ^ this.Number.GetHashCode(); } public override int GetHashCode() { return this.Name.GetHashCode() ^ this.Number.GetHashCode(); } } 

È abbastanza semplice e risponde a tutti i nostri requisiti:

  1. I dati NON sono duplicati per il controllo sporco …
  2. Questo tiene conto di tutti gli scenari di modifica delle proprietà (vedi scenari sotto) …
  3. Quando si chiama la proprietà IsDirty, viene eseguita un’operazione Equals molto semplice e piccola ed è completamente personalizzabile tramite l’override GetHashCode …
  4. Chiamando il metodo MakeMeClean, ora hai di nuovo un object pulito!

Ovviamente puoi adattarlo a un gruppo di stati diversi … dipende solo da te. Questo esempio mostra solo come avere una corretta operazione di flag IsDirty.

scenari
Andiamo oltre alcuni scenari per questo e vediamo cosa ritorna:

  • scenario 1
    Il nuovo object viene creato usando il costruttore vuoto,
    Il nome della proprietà cambia da “” a “James”,
    chiama a IsDirty restituisce True! Accurate.

  • Scenario 2
    Il nuovo object viene creato usando i parametri di “John” e 12345,
    Il nome della proprietà cambia da “John” a “James”,
    Il nome della proprietà cambia da “James” a “John”,
    Chiama IsDirty restituisce False. Accurato e non abbiamo dovuto duplicare i dati per farlo!

Come usare, un esempio di interfaccia utente di WinForms
Questo è solo un esempio, puoi usarlo in molti modi diversi da un’interfaccia utente.

Diciamo che hai due forms ([A] e [B]).

Il primo ([A]) è il tuo modulo principale e il secondo ([B]) è un modulo che consente all’utente di modificare i valori all’interno di MySmartObject.

Sia il modulo [A] che il modulo [B] hanno la seguente proprietà dichiarata:

 public MySmartObject UserKey { get; set; } 

Quando l’utente fa clic su un pulsante nel modulo [A], viene creata un’istanza del modulo [B], la sua proprietà viene impostata e viene visualizzata come una finestra di dialogo.

Dopo la restituzione del modulo [B], il modulo [A] aggiorna la sua proprietà in base al controllo IsDirty del modulo [B]. Come questo:

 private void btn_Expand_Click(object sender, EventArgs e) { SmartForm form = new SmartForm(); form.UserKey = this.UserKey; if(form.ShowDialog() == DialogResult.OK && form.UserKey.IsDirty) { this.UserKey = form.UserKey; //now that we have saved the "new" version, mark it as clean! this.UserKey.MakeMeClean(); } } 

Inoltre, in [B], quando sta chiudendo, puoi controllare e richiedere all’utente se sta chiudendo il modulo con modifiche non salvate, in questo modo:

  private void BForm_FormClosing(object sender, FormClosingEventArgs e) { //If the user is closing the form via another means than the OK button, or the Cancel button (eg: Top-Right-X, Alt+F4, etc). if (this.DialogResult != DialogResult.OK && this.DialogResult != DialogResult.Ignore) { //check if dirty first... if (this.UserKey.IsDirty) { if (MessageBox.Show("You have unsaved changes. Close and lose changes?", "Unsaved Changes", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.No) e.Cancel = true; } } } 

Come puoi vedere dagli esempi sopra, questa può essere una cosa molto utile da quando ottimizza l’interfaccia utente.

Avvertenze

  • Ogni volta che si implementa questo, è necessario personalizzarlo sull’object che si sta utilizzando. Ad esempio: non esiste un modo “facile” generico di fare ciò senza usare il reflection … e se si usa il reflection, si perde efficienza, specialmente in oggetti grandi e complessi.

Spero che questo aiuti qualcuno.

Il tuo approccio è fondamentalmente come lo farei. Vorrei semplicemente rimuovere il setter per la proprietà Status:

 public StatusEnum Status { get { return _Status; } // set { _Status = value; } } 

e invece aggiungi una funzione

 public SetStatusClean() { _Status = StatusEnum.Clean; } 

Così come SetStatusDeleted() e SetStatusPurged() , perché trovo che sia meglio indicare l’intenzione.

modificare

Avendo letto la risposta di Jon Skeet , ho bisogno di riconsiderare il mio approccio 😉 Per gli oggetti semplici vorrei attenermi al mio modo, ma se diventasse più complesso, la sua proposta porterebbe a un codice molto meglio organizzato.

Se il tuo Sample_Class è leggero, considera di memorizzare lo stato originale e quindi di confrontare lo stato corrente con quello originale per determinare le modifiche. In caso contrario, il tuo approccio è il migliore perché accarezzare lo stato originale consuma molte risorse di sistema in questo caso.

A parte il consiglio di ‘considerare la possibilità di rendere il tuo tipo immutabile’, ecco qualcosa che ho scritto (e ho fatto in modo che Jon e Marc mi insegnassero qualcosa lungo la strada)

 public class Example_Class { // snip // all properties are public get and private set private Dictionary m_PropertySetterMap; public Example_Class() { m_PropertySetterMap = new Dictionary(); InitializeSettableProperties(); } public Example_Class(long id, string name):this() { this.ID = id; this.Name = name; } private void InitializeSettableProperties() { AddToPropertyMap("ID", value => { this.ID = value; }); AddToPropertyMap("Name", value => { this.Name = value; }); } // jump thru a hoop because it won't let me cast an anonymous method to an Action/Delegate private void AddToPropertyMap(string sPropertyName, Action setterAction) { m_PropertySetterMap.Add(sPropertyName, setterAction); } public void SetProperty(string propertyName, T value) { (m_PropertySetterMap[propertyName] as Action).Invoke(value); this.Status = StatusEnum.Dirty; } } 

Hai un’idea … possibili miglioramenti: usa le costanti per i PropertyName e verifica se la proprietà è veramente cambiata. Uno svantaggio qui è quello

 obj.SetProperty("ID", 700); // will blow up int instead of long obj.SetProperty("ID", 700); // be explicit or use 700L 

Ecco come lo faccio.

Nei casi in cui non è necessario testare che i campi specifici siano sporchi, ho una class astratta:

 public abstract class SmartWrap : ISmartWrap { private int orig_hashcode { get; set; } private bool _isInterimDirty; public bool IsDirty { get { return !(this.orig_hashcode == this.GetClassHashCode()); } set { if (value) this.orig_hashcode = this.orig_hashcode ^ 108.GetHashCode(); else MakeClean(); } } public void MakeClean() { this.orig_hashcode = GetClassHashCode(); this._isInterimDirty = false; } // must be overridden to return combined hashcodes of fields testing for // example Field1.GetHashCode() ^ Field2.GetHashCode() protected abstract int GetClassHashCode(); public bool IsInterimDirty { get { return _isInterimDirty; } } public void SetIterimDirtyState() { _isInterimDirty = this.IsDirty; } public void MakeCleanIfInterimClean() { if (!IsInterimDirty) MakeClean(); } ///  /// Must be overridden with whatever valid tests are needed to make sure required field values are present. ///  public abstract bool IsValid { get; } } 

}

Così come un’interfaccia

 public interface ISmartWrap { bool IsDirty { get; set; } void MakeClean(); bool IsInterimDirty { get; } void SetIterimDirtyState(); void MakeCleanIfInterimClean(); } 

Questo mi consente di fare dei salvataggi parziali e di preservare lo stato IsDirty se ci sono altri dettagli da salvare. Non perfetto, ma copre molto terreno.

Esempio di utilizzo con stato IsDirty intermedio (annullamento degli errori e convalida rimossi per maggiore chiarezza):

  area.SetIterimDirtyState(); if (!UpdateClaimAndStatus(area)) return false; area.MakeCleanIfInterimClean(); return true; 

Questo è utile per la maggior parte degli scenari, tuttavia per alcune classi voglio testare per ogni campo un campo di supporto di dati originali e restituire un elenco di modifiche o almeno un enum di campi modificati. Con un enum di campi modificati posso poi spingerlo in su attraverso una catena di messaggi per l’aggiornamento selettivo dei campi nelle cache remote.

Un altro metodo è quello di sovrascrivere il metodo GetHashCode () in qualcosa del genere:

 public override int GetHashCode() // or call it GetChangeHash or somthing if you dont want to override the GetHashCode function... { var sb = new System.Text.StringBuilder(); sb.Append(_dateOfBirth); sb.Append(_marital); sb.Append(_gender); sb.Append(_notes); sb.Append(_firstName); sb.Append(_lastName); return sb.ToString.GetHashCode(); } 

Una volta caricato dal database, ottieni il codice hash dell’object. Quindi, prima di salvare, controlla se il codice hash corrente è uguale al codice hash precedente. se sono uguali, non salvare.

Modificare:

Come le persone hanno sottolineato questo fa sì che il codice hash cambi – mentre io uso Guids per identificare i miei oggetti, non mi importa se l’hashcode cambia.

Edit2:

Poiché le persone sono contrarie a modificare il codice hash, invece di sovrascrivere il metodo GetHashCode, basta chiamare il metodo qualcos’altro. Il punto sta rilevando un cambiamento, non so se io uso guids o hashcode per l’identificazione dell’object.