C# - Determine if List<T> is dirty?
Asked Answered
M

7

5

I am serializing Lists of classes which are my data entities. I have a DataProvider that contains a List.

I always modify items directly within the collection.

What is the best way of determining if any items in the List have changed? I am using the Compact Framework.

My only current idea is to create a hash of the List (if that's possible) when I load the list. Then when I do a save I re-get the hash of the list and see if they're different values. If they're different I save and then update the stored Hash for comparison later, if they're the same then I don't save.

Any ideas?

Millibar answered 21/5, 2009 at 9:11 Comment(4)
I would use hash thingy idea...Ezana
How would I do my hashing idea?Millibar
You have to keep in mind that the list doesn't change when you change a property of an item in the list. I'd go with lassevk's suggegstion.Urquhart
I've cracked it myself using an Interface that I cast my Type T objects to. It allows me to see all the dirty entities as well as the state of the collection.Millibar
A
9

If the items you add to the list implement the INotifyPropertyChanged interface, you could build your own generic list that hooks the event in that interface for all objects you add to the list, and unhooks the event when the items are removed from the list.

There's a BindingList<T> class in the framework you can use, or you can write your own.

Here's a sample add method, assuming the type has been declared with where T: INotifyPropertyChanged:

public void Add(T item)
{
    // null-check omitted for simplicity
    item.PropertyChanged += ItemPropertyChanged;
    _List.Add(item);
}

and the this[index] indexer property:

public T this[Int32 index]
{
    get { return _List[index]; }
    set {
        T oldItem = _List[index];
        _List[index] = value;
        if (oldItem != value)
        {
            if (oldItem != null)
                oldItem.PropertyChanged -= ItemPropertyChanged;
            if (value != null)
                value.PropertyChanged += ItemPropertyChanged;
        }
    }
}

If your items doesn't support INotifyPropertyChanged, but they're your classes, I would consider adding that support.

Autopilot answered 21/5, 2009 at 10:23 Comment(1)
Only a year later... I decided this is a better answer than what I came up with 12 months ago.Millibar
D
4

You could create your own IList<T> class, say DirtyList<T> that can record when the list has changed.

Dole answered 21/5, 2009 at 9:15 Comment(3)
How would I go about doing that? I'd had this idea and scrapped it due to the fact I don't know how I'd go about determining if an item was changed inside the list, i.e. Item[0].Id = NewId(), how would my list know the item had changed?Millibar
One way - override methods and props which modify the list (i.e. Add) change your boolean flag there and then call the base method.Mitochondrion
If I'm understanding him correctly, he's doing things like this: list[1].Prop = newValue; Unless the item at index 1 in the list informs the list (and thus knows it is in a list) that it has changed, that statement won't set that dirty flag, unless you flag every this[index] get call as dirtyAutopilot
P
4

If you're willing to use reflection, the List<T> class has a private field called _version that is incremented every time the list changes. It won't tell you which items have changed, but you can compare it with the original value of _version to detect an unmodified list.

For reference, this field is used to ensure that enumerators become invalid when the list is modified. So you should be able to use it for your purposes fairly reliably, unless the actual managed code for List<T> changes.

To get the value of _version you can use something like this:

List<T> myList;
var field = myList.GetType().GetField("_version", BindingFlags.Instance | BindingFlags.NonPublic);
int version = field.GetValue(myList);

Generally speaking, though, this isn't the best approach. If you're stuck using a List<T> that someone else created, however, it's probably the best option you have. Please be aware that changes to the .NET framework could change the name of the field (or remove it entirely), and it's not guaranteed to exist in third-party CLR implementations like Mono.

Plasmolysis answered 21/5, 2009 at 10:9 Comment(2)
Any way of making this work in Version 2.0 of CF? This would be perfect.Millibar
Got this working, but it doesn't increment when an item is modified inside the List<T>.Millibar
G
2

How about something like this?

public class ItemChangedArgs<T> : EventArgs
{
    public int Index { get; set; }
    public T Item { get; set; }
}

public class EventList<T> : IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable
{
    private List<T> m_list;
    public event EventHandler<ItemChangedArgs<T>> ItemAdded;
    public event EventHandler<ItemChangedArgs<T>> ItemRemoved;
    public event EventHandler<ItemChangedArgs<T>> ItemChanged;
    public event EventHandler ListCleared;

    public EventList(IEnumerable<T> collection)
    {
        m_list = new List<T>(collection);
    }

    public EventList(int capacity)
    {
        m_list = new List<T>(capacity);
    }

    public EventList()
    {
        m_list = new List<T>();
    }

    public void Add(T item)
    {
        Add(item, true);
    }

    public void Add(T item, Boolean raiseEvent)
    {
        m_list.Add(item);
        if (raiseEvent) RaiseItemAdded(this.Count - 1, item);
    }

    public void AddRange(IEnumerable<T> collection)
    {
        foreach (T t in collection)
        {
            m_list.Add(t);
        }
    }

    private void RaiseItemAdded(int index, T item)
    {
        if (ItemAdded == null) return;

        ItemAdded(this, new ItemChangedArgs<T> { Index = index, Item = item });
    }

    public int IndexOf(T item)
    {
        return m_list.IndexOf(item);
    }

    public void Insert(int index, T item)
    {
        m_list.Insert(index, item);
        RaiseItemAdded(index, item);
    }

    public void RemoveAt(int index)
    {
        T item = m_list[index];
        m_list.RemoveAt(index);
        RaiseItemRemoved(index, item);
    }

    private void RaiseItemRemoved(int index, T item)
    {
        if(ItemRemoved == null) return;
        ItemRemoved(this, new ItemChangedArgs<T> { Index = index, Item = item });
    }

    public T this[int index]
    {
        get { return m_list[index]; }
        set 
        { 
            m_list[index] = value;
            RaiseItemChanged(index, m_list[index]);
        }
    }

    private void RaiseItemChanged(int index, T item)
    {
        if(ItemChanged == null) return;
        ItemChanged(this, new ItemChangedArgs<T> { Index = index, Item = item });
    }

    public void Clear()
    {
        m_list.Clear();
        RaiseListCleared();
    }

    private void RaiseListCleared()
    {
        if(ListCleared == null) return;
        ListCleared(this, null);
    }

    public bool Contains(T item)
    {
        return m_list.Contains(item);
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        m_list.CopyTo(array, arrayIndex);
    }

    public int Count
    {
        get { return m_list.Count; }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }

    public bool Remove(T item)
    {
        for (int i = 0; i < m_list.Count; i++)
        {
            if(item.Equals(m_list[i]))
            {
                T value = m_list[i];
                m_list.RemoveAt(i);
                RaiseItemRemoved(i, value);
                return true;
            }
        }
        return false;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return m_list.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return m_list.GetEnumerator();
    }
}
Grape answered 21/5, 2009 at 12:0 Comment(0)
N
2

Assuming that GetHashCode() for every member contained in the list is implemented properly (and thus changes when an element changes) I'd imagine something along the lines of:

public class DirtyList<T> : List<T> {
    private IList<int> hashCodes = new List<int> hashCodes();
    public DirtyList() : base() { }
    public DirtyList(IEnumerable<T> items) : base() {
        foreach(T item in items){
            this.Add(item); //Add it to the collection
            hashCodes.Add(item.GetHashCode());
        }
    }

    public override void Add(T item){
        base.Add(item);
        hashCodes.Add(item);
    }
    //Add more logic for the setter and also handle the case where items are removed and indexes change and etc, also what happens in case of null values?

    public bool IsDirty {
       get {
           for(int i = 0; i < Count: i++){
               if(hashCodes[i] != this[i].GetHashCode()){ return true; }
           }
           return false;
       }
    }
}

*Please be aware i typed this up on SO and do not have a compiler, so above stated code is in no way guarenteed to work, but hopefully it'll show the idea.

Nymphalid answered 21/5, 2009 at 12:4 Comment(0)
L
1

You could implement you're own list that maintains 2 internal lists... and instantiated version and tracking version... e.g.

//Rough Psuedo Code
public class TrackedList<T> : List<T>
{
    public bool StartTracking {get; set; }
    private List<T> InitialList { get; set; }

    CTOR
    {
        //Instantiate Both Lists...
    }

    ADD(item)
    {
        if(!StartTracking)
        {
            Base.Add(item);
            InitialList.Add(item);
        }
        else
        {
            Base.Add(item);
        }
    }

    public bool IsDirty
    {
       get
       {
           Check if theres any differences between initial list and self.
       }
    }
}
Likely answered 21/5, 2009 at 9:31 Comment(1)
Unfortunately I don't think I can do that considering that my data provider base class uses a List<T> and not List<DataEntity>, so I've no way of comparing both lists.Millibar
N
1

Make sure that T is a descendant of an object that has a dirty flag and have the IList implementation have a check for that which walks the list's dirty flags.

Nubble answered 21/5, 2009 at 11:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.