21

I have an object, which contains other objects, which contain other objects, including lists, etcetera. This object is databound to a form, exposing numerous fields to the user in different tabs. I also use master-child datagridviews.

Any idea how to check if anything has changed in this object with respect to an earlier moment? Without (manually) adding a changed variable, which is set to true in all (>100) set methods.

willem
  • 2,617
  • 5
  • 26
  • 38
  • Just curious but why do you want this. If your object is databound correctly, you form should update accordingly. – Bas Slagter Oct 31 '11 at 10:46
  • What are referring by `Objects` here. Do you mean different components or objects declared by you from different types? It's bit confusing. – Rahul Oct 31 '11 at 10:47
  • What do you mean 'Without (manually) adding a changed variable' You don't want to write code to solve this? – Ritch Melton Oct 31 '11 at 10:49
  • Objects can be simple ones like doubles or strings, but also lists of custom objects, that may contain lists of other objects. – willem Oct 31 '11 at 10:49
  • There are many ways to implement a dirty flag. http://stackoverflow.com/questions/553882/different-ways-to-implement-dirty-flag-functionality – Ritch Melton Oct 31 '11 at 10:53
  • Some of you suggest to use reflection/serialization. Would serializing the data as XML, and storing the string obtained in that way work to compare with a string obtained using a later serialization work? I see some remarks that this is not guaranteed to give the same result, even though the data might be the same, due to serialization optimization. Is this also true for XML serialization? – willem Oct 31 '11 at 12:31
  • 1
    Write and Fire you're own customized event into accessors of you're Obejct's ,like set { name_ = value; //Fire event} ; – Rosmarine Popcorn Oct 31 '11 at 14:16

7 Answers7

9

As Sll stated, an dirty interface is definitely a good way to go. Taking it further, we want collections to be dirty, but we don't want to necessarily set ALL child objects as dirty. What we can do, however is combine the results of their dirty state, with our own dirty state. Because we're using interfaces, we're leaving it up to the objects to determine whether they are dirty or not.

My solution won't tell you what is dirty, just that the state of any object at any time is dirty or not.

public interface IDirty
{
    bool IsDirty { get; }
}   // eo interface IDirty


public class SomeObject : IDirty
{
    private string name_;
    private bool dirty_;

    public string Name
    {
        get { return name_; }
        set { name_ = value; dirty_ = true; }
    }
    public bool IsDirty { get { return dirty_; } }
}   // eo class SomeObject


public class SomeObjectWithChildren : IDirty
{
    private int averageGrades_;
    private bool dirty_;
    private List<IDirty> children_ = new List<IDirty>();

    public bool IsDirty
    {
        get
        {
            bool ret = dirty_;
            foreach (IDirty child in children_)
                dirty_ |= child.IsDirty;
            return ret;
        }
    }

}   // eo class SomeObjectWithChildren
Moo-Juice
  • 38,257
  • 10
  • 78
  • 128
4

You can implement INotifyPropertyChanged interface and if you user VS2010 there is addin that automatic alter all properties in IL (so you don't have to implement it manualy).

I belive there is also some other methods that use Weaving technique.

I found addin in vs2010 gallery:

http://visualstudiogallery.msdn.microsoft.com/bd351303-db8c-4771-9b22-5e51524fccd3

There is nice example - your code:

public class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public string GivenNames { get; set; }
}

What get compiled:

public class Person : INotifyPropertyChanged
{

    public event PropertyChangedEventHandler PropertyChanged;

    private string givenNames;
    public string GivenNames
    {
        get { return givenNames; }
        set
        {
            if (value != givenNames)
            {
                givenNames = value;
                OnPropertyChanged("GivenNames");
                OnPropertyChanged("FullName");
            }
        }
    }
}

This is from first resoult from unce G (might be usefull):

http://justinangel.net/AutomagicallyImplementingINotifyPropertyChanged

http://www.codeproject.com/KB/WPF/AutonotifyPropertyChange.aspx

Kamil Lach
  • 4,519
  • 2
  • 19
  • 20
  • I personally wouldn't use INotifyPropertyChanged (pub/sub) over walking the parent graph as walking the parent is on-demand, and can be pushed to a parallel or background task. Event models tend to lead to event-storms in large applications with many hands developing them. – Ritch Melton Oct 31 '11 at 11:27
3

How do you define "equality" (between old and new state)?

  • Are you comparing properties only or fields as well?
  • Are you only comparing public properties/fields?
  • Do you ever ignore any properties/fields (i.e. their modifications do not matter)?
  • How do you compare "atomic" types (e.g. are all string comparisons case-insensitive, or you need case-sensitive in some places as well).

If answers to these questions are general enough (i.e. you can devise a set of rules that apply to all of your objects), then you could theoretically accomplish what you want through reflection: The basic idea is to read all properties/fields of the "root" object, then store the "atomic" ones and recursively descend into the "non-atomic" ones (and repeat the whole process). Later, when you want to check if anything changed, you would repeat the recursive descent and compare the results with the stored values.

I'm not arguing this solution is particularly performant or even easy (you'd need to devise a robust naming convention for storing old values and be very careful about multi-threading), but it could potentially be general.

Branko Dimitrijevic
  • 50,809
  • 10
  • 93
  • 167
  • I like the idea of reflection, but I would rather not implement it myself. Could I coax the XML serializer to check for equality? Or does it rearrange things? – willem Oct 31 '11 at 13:04
1

If you want the lazy and easy way of doing this, then @Burimi is correct in that you need to manage this within your class setters and getters.

You will have to, excuse the expression but, "old school" your class/object properties back to having a public getter and setter that accesses a private variable. Within the public setter, is where you would set your object's dirty flag, as well as your private variable... but you have to do it for every property that matters, one by one... I personally think this warrants one of these in the future:

public string FirstName { get; set => { this.isDirty=true; } }

Which would basically equate to the same as the current { get; set; } , but also, if either of these have a => next to it, it would equate to and run this code at after //getting or setting it but I don't even know if this is something that people would like to have or not nowadays.

public class myObject
{
    public bool isDirty { get; set; }
    private string FirstName_ = "";
    public string FirstName { get { return FirstName_; } set { FirstName_ = value; isDirty = true; } }
}

Now, any time FirstName_ is changed, the entire object becomes dirty... rinse repeat.

Of course if you also wanted to report up, as it happens, all you have to do is fire a notify event that you created yourself and has the data you need... here's an example of what I mean:

public class myObject
{
    public bool isDirty { get; set; }
    private string FirstName_ = "";
    public string FirstName { get { return FirstName_; } set { FirstName_ = value; isDirty = true; RaisePropertyChanged("FirstName"); } }

    #region use YOUR_OWN_NOTIFY_EventHandler

    public event YOUR_OWN_NOTIFY_EventHandler PropertyChanged;

    public void RaisePropertyChanged(string propertyName)
    {
        YOUR_OWN_NOTIFY_EventTHandler handler = PropertyChanged;
        if (handler != null) handler(new myPropertyChangedEventArgs(propertyName));
    }

    #endregion
}

Hope this helps someone...

MaxOvrdrv
  • 1,780
  • 17
  • 32
1

You may override the GetHashCode and then create a hash code which is a mixture of the properties of the object. So your program will get the hashcode of the object, store it and then on next check, compare it with current hashcode.

A very simplistic approach:

internal class Foo
{
    public string Bar { get; set; }
    public int Baaz { get; set; }

    public override int GetHashCode()
    {
        return Bar.GetHashCode() - Baaz.GetHashCode();
    }
}

Be careful since you have to look for Bar not being null and also cater for integer overflows.


EDIT

After looking at Eirc Lippert's blog,

I do agree GetHashCode must not be used.

I keep the answer to keep the wealth of discussions.

Aliostad
  • 80,612
  • 21
  • 160
  • 208
  • ^ this should be caveated with the warning's that go with implemented a hashcode that changes. It would be just as easy to add as IsDirty flag. – Ritch Melton Oct 31 '11 at 10:51
  • 1
    No it's not. IsDirty will have to be set on all setters of the properties of the class and all classes used in that class. Urrrgh,.. And then when would you reset it back? Suppose it is changed and then you wanna find out if it has changed again? – Aliostad Oct 31 '11 at 10:53
  • Right, it requires a certain amount of effort, and may require a bit of infrastructure (if your requirements dictate that), but it works without using GetHashCode in a way that overloads (and possibly breaks) its intended usage. – Ritch Melton Oct 31 '11 at 11:00
  • 3
    Using the hash code to do this is not a good idea. First of all, you'd have to override the `Equals()` method too. Secondly, the hash of an object should never change in the lifetime of the object (also implies it should be immutable where it matters). You're using an existing method for the wrong reasons just to save one writing a separate one. – Jeff Mercado Oct 31 '11 at 11:01
  • `hash of an object should never change in the lifetime of the object`. Can you please give me a reference? – Aliostad Oct 31 '11 at 11:06
  • 1
    @Aliostad - http://stackoverflow.com/questions/462451/gethashcode-guidelines-in-c-sharp – Ritch Melton Oct 31 '11 at 11:17
  • @Aliostad Hashing is vulnerable to false positives (i.e. two objects having the same hash but not being equal). – Branko Dimitrijevic Oct 31 '11 at 11:21
  • @Aliostad Also, I wouldn't use `GetHashCode()` for that since it has special meaning for some containers (as @RitchMelton already alluded to). – Branko Dimitrijevic Oct 31 '11 at 11:23
  • Guys you are right. `GetHashCode` must not be used. Thanks for correcting me. See updates. – Aliostad Oct 31 '11 at 11:26
1

Comparing Hash Codes over time might be an option. If you don't want to add that logic you could serialize the object twice and compare the hash codes of the two resulting strings.

EDIT to include some comments Have a look at this question/answer: Can serializing the same object produce different streams?

So be aware of a serializer that does **not ** guarantee the same output for the same object twice.

Community
  • 1
  • 1
Emond
  • 50,210
  • 11
  • 84
  • 115
  • 2
    Serialised stream of two objects with exactly the same values is not guaranteed to be the same. **I have seen it with my own eyes.** – Aliostad Oct 31 '11 at 11:07
  • @Ritch never mind the hashcode, streams are not guaranteed to be the same. – Aliostad Oct 31 '11 at 11:11
  • 1
    I guess it depends on the kind of serialization. It is not about streams being equal it is about the formatter creating the same 'thing'/structure/string – Emond Oct 31 '11 at 11:15
  • 1
    @Aliostad - I don't buy that. You're saying that if I put the same object through the serializer, it doesn't produce the same results? – Ritch Melton Oct 31 '11 at 11:16
  • @RitchMelton - Of course the strings can be compared too but that takes even more time. – Emond Oct 31 '11 at 11:20
  • @Ritch `I don't buy that`. It is up to you to buy or not - **it is guaranteed to deserialise to the same object but not to generate the same stream.** The reason is the way serialiser could optimise strings. So if two properties are strings and have the same value, sometimes literal string is set to all of them and sometimes one is tored and the rest are referenced. As I said, I have seen it with my own eyes mate. – Aliostad Oct 31 '11 at 11:30
  • @Aliostad - it is guaranteed to deserialise to the same object but not to generate the same stream - Linky please. – Ritch Melton Oct 31 '11 at 11:32
  • No links mate. My own experience. Take it or leave it... Don't have time to reproduce it. – Aliostad Oct 31 '11 at 11:36
  • @Aliodstad - 'roll eyes' - ok. – Ritch Melton Oct 31 '11 at 13:05
  • 1
    @Ritch there you go with the prooooof http://stackoverflow.com/questions/7954275/can-serializing-the-same-object-produce-different-streams/7954530#7954530 – Aliostad Oct 31 '11 at 13:46
  • Would XML serializer guarantee to generate the same stream? – willem Oct 31 '11 at 16:00
0

i can sugest something like this:

public class ObjectBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            OnPropertyChanged(propertyName, true);
        }

        protected virtual void OnPropertyChanged(string propertyName, bool makeDirty)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

            if (makeDirty)
                _IsDirty = true;
        }


        bool _IsDirty;

        public bool IsDirty
        {
            get { return _IsDirty; }
        }

    }

then in the property accesor you can

public class Vehicle : ObjectBase
{


  int _CarId;

 public int CarId
        {
            get { return _CarId; }
            set
            {
                if (_CarId != value)
                {
                    _CarId = value;
                    OnPropertyChanged(nameof(CarId));
                }

            }
        }
  }

hope it helps

  • Not sure how this would work. How do I tell if a property is actually dirty (changed) apart from a property that has merely been set when instantiated? – prison-mike Sep 13 '17 at 16:30