tags:

views:

371

answers:

7

I have a class which contains 5 properties.

If any value is assingned to any of these fields, an another value (for example IsDIrty) would change tu true.

public class Class1
{
    bool IsDIrty {get;set;}

    string Prop1 {get;set;}
    string Prop2 {get;set;}
    string Prop3 {get;set;}
    string Prop4 {get;set;}
    string Prop5 {get;set;}
}
+4  A: 

Set IsDirty to true in all of your setters.

You might also consider making the setter for IsDirty private (or protected, if you may have child classes with additional properties). Otherwise you could have code outside of the class negating its internal mechanism for determining dirtiness.

Dan Tao
+1  A: 

Carefully consider the underlying purpose the object tracking is required? Suppose if it is something like other objects have to do something based on another object's state, then consider implementing the observer design pattern.

If its something small consider implementing the INotifyPropertyChanged interface.

deostroll
+13  A: 

To do this you can't really use automatic getter & setters, and you need to set IsDirty in each setter.

I generally have a "setProperty" generic method that takes a ref parameter, the property name and the new value. I call this in the setter, allows a single point where I can set isDirty and raise Change notification events e.g.

protected bool SetProperty<T>(string name, ref T oldValue, T newValue) where T : System.IComparable<T>
    {
        if (oldValue == null || oldValue.CompareTo(newValue) != 0)
        {
            oldValue = newValue;
            if (PropertyChanged != null)
                PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(name));
            isDirty = true;
            return true;
        }
        return false;
    }
// For nullable types
protected void SetProperty<T>(string name, ref Nullable<T> oldValue, Nullable<T> newValue) where T : struct, System.IComparable<T>
{
    if (oldValue.HasValue != newValue.HasValue || (newValue.HasValue && oldValue.Value.CompareTo(newValue.Value) != 0))
    {
        oldValue = newValue;
        if (PropertyChanged != null)
            PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(name));
    }
}
Binary Worrier
Good answer (+1). My only suggestion would be to implement IChangeTracking (http://msdn.microsoft.com/en-us/library/system.componentmodel.ichangetracking.aspx) rather than creating your own IsDirty property.
John Myczek
John Myczek: I was completely unaware of that interface, I shall switch my pet projects to use this immediately (or as soon as that pesky `life` thing allows). Thanks :)
Binary Worrier
Didn't know about that either John
Chris Marisic
You should use `IEquatable` instead of `IComparable`, and you don't need to test if `oldValue` is null. Just use `if (oldValue.Equals(newValue)) return;`.
280Z28
@280Z28: I'll look into that, thanks mate :)
Binary Worrier
+1 To Mr Myczek, I didn't know about that interface, either.
Barry Carr
Event invocation here is not thread-safe. You need to write `var handler = PropertyChanged; if (handler != null) handler(this, e);`
Aaronaught
+3  A: 

Dan's solution is perfect.

Another option to consider if you're going to have to do this on multiple classes (or maybe you want an external class to "listen" for changes to the properties):

  • Implement the INotifyPropertyChanged interface in an abstract class
  • Move the IsDirty property to your abstract class
  • Have Class1 and all other classes that require this functionality to extend your abstract class
  • Have all your setters fire the PropertyChanged event implemented by your abstract class, passing in their name to the event
  • In your base class, listen for the PropertyChanged event and set IsDirty to true when it fires

It's a bit of work initially to create the abstract class, but it's a better model for watching for data changes as any other class can see when IsDirty (or any other property) changes.

My base class for this looks like the following:

public abstract class BaseModel : INotifyPropertyChanged
{
    /// <summary>
    /// Initializes a new instance of the BaseModel class.
    /// </summary>
    protected BaseModel()
    {
    }

    /// <summary>
    /// Fired when a property in this class changes.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Triggers the property changed event for a specific property.
    /// </summary>
    /// <param name="propertyName">The name of the property that has changed.</param>
    public void NotifyPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Any other model then just extends BaseModel, and calls NotifyPropertyChanged in each setter.

Andy Shellam
I've seen solutions like this that are step cooler where it removes the string entirely and uses Lambda's, don't have a link on hand though :(
Chris Marisic
+1 INotifyPropertyChanged interface is the tool one needs to be aware of changes occuring to a property. =)
Will Marcouiller
+1  A: 

If there are a very large number of such classes, all having that same pattern, and you frequently have to update their definitions, consider using code generation to automatically spit out the C# source files for all the classes, so that you don't have to manually maintain them. The input to the code generator would just be a simple text file format that you can easily parse, stating the names and types of the properties needed in each class.

If there are just a small number of them, or the definitions change very infrequently during your development process, then it's unlikely to be worth the effort, in which case you may as well maintain them by hand.

Update:

This is probably way over the top for a simple example, but it was fun to figure out!

In Visual Studio 2008, if you add a file called CodeGen.tt to your project and then paste this stuff into it, you'll have the makings of a code generation system:

<#@ template debug="false" hostspecific="true" language="C#v3.5" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>

<# 

// You "declare" your classes here, as in these examples:

var src = @"

Foo:     string Prop1, 
         int Prop2;

Bar:     string FirstName,
         string LastName,
         int Age;
";

// Parse the source text into a model of anonymous types

Func<string, bool> notBlank = str => str.Trim() != string.Empty;

var classes = src.Split(';').Where(notBlank).Select(c => c.Split(':'))
    .Select(c => new 
    {
        Name = c.First().Trim(),
        Properties = c.Skip(1).First().Split(',').Select(p => p.Split(' ').Where(notBlank))
                      .Select(p => new { Type = p.First(), Name = p.Skip(1).First() })
    });
#>

// Do not edit this file by hand! It is auto-generated.

namespace Generated 
{
<# foreach (var cls in classes) {#>    class <#= cls.Name #> 
    {
        public bool IsDirty { get; private set; }
        <# foreach (var prop in cls.Properties) { #>

        private <#= prop.Type #> _storage<#= prop.Name #>; 

        public <#= prop.Type #> <#= prop.Name #> 
        {
            get { return _storage<#= prop.Name #>; }
            set 
            {
                IsDirty = true;
                _storage<#= prop.Name #> = value;
            }
        } <# } #>

    }

<# } #>
}

There's a simple string literal called src in which you declare the classes you need, in a simple format:

Foo:     string Prop1,
         int Prop2;

Bar:     string FirstName,
         string LastName,
         int Age;

So you can easily add hundreds of similar declarations. Whenever you save your changes, Visual Studio will execute the template and produce CodeGen.cs as output, which contains the C# source for the classes, complete with the IsDirty logic.

You can change the template of what is produced by altering the last section, where it loops through the model and produces the code. If you've used ASP.NET, it's similar to that, except generating C# source instead of HTML.

Daniel Earwicker
+1  A: 

Both Dan's and Andy Shellam's answers are my favorites.

In anyway, if you wanted to keep TRACK of you changes, like in a log or so, you might consider the use of a Dictionary that would add all of your property changes when they are notified to have changed. So, you could add the change into your Dictionary with a unique key, and keep track of your changes. Then, if you wish to Roolback in-memory the state of your object, you could this way.

EDIT Here's what Bart de Smet uses to keep track on property changes throughout LINQ to AD. Once the changes have been committed to AD, he clears the Dictionary. So, when a property changes, because he implemented the INotifyPropertyChanged interface, when a property actually changed, he uses a Dictionary> as follows:

    /// <summary>
    /// Update catalog; keeps track of update entity instances.
    /// </summary>
    private Dictionary<object, HashSet<string>> updates = new Dictionary<object, HashSet<string>>();

    public void UpdateNotification(object sender, PropertyChangedEventArgs e)
    {
        T source = (T)sender;

        if (!updates.ContainsKey(source))
            updates.Add(source, new HashSet<string>());

        updates[source].Add(e.PropertyName);
    }

So, I guess that if Bart de Smet did that, this is somehow a practice to consider.

Will Marcouiller
I got berated by a colleague recently for doing something similar - apparently this will box value types such as int and doubles into .NET types, resulting in a performance penalty. It wasn't so much of a worry on the small(ish) app I'm working on, but could be something to watch out for on larger apps.
Andy Shellam
Interesting! Thanks for this comment, I perhaps would not have gotten aware of it before I make it myself. I shall be warned then. =)
Will Marcouiller
Thus, when I come to think of it, Bart de Smet, from Microsoft, the one who designed LINQ to AD on CodePlex, uses a Dictionary<object, HashSet<string>> to keep track of property changes. I'll edit my answer to indlude the sample code.
Will Marcouiller
A: 

This is something that is built into the BusinessBase class in Rocky Lhokta's CLSA framework, so you could always go and look at how it's done...

rohancragg