Определить, если список <T>грязный? - PullRequest
4 голосов
/ 21 мая 2009

Я сериализирую списки классов, которые являются моими объектами данных. У меня есть DataProvider, который содержит список.

Я всегда изменяю предметы непосредственно в коллекции.

Как лучше всего определить, изменились ли какие-либо элементы в списке? Я использую Compact Framework.

Моя единственная текущая идея - создать хэш списка (если это возможно) при загрузке списка. Затем, когда я делаю сохранение, я снова получаю хэш списка и проверяю, являются ли они разными значениями. Если они отличаются, я сохраняю их, а затем обновляю сохраненный хэш для сравнения позже, если они совпадают, я не сохраняю.

Есть идеи?

Ответы [ 7 ]

9 голосов
/ 21 мая 2009

Если элементы, которые вы добавляете в список, реализуют интерфейс INotifyPropertyChanged, вы можете создать свой собственный общий список, который перехватывает событие в этом интерфейсе для всех объектов, добавляемых в список, и отсоединяет событие, когда элементы удаляются из списка.

Существует класс BindingList<T>, который вы можете использовать или можете написать свой собственный.

Вот пример метода добавления, при условии, что тип был объявлен с where T: INotifyPropertyChanged:

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

и свойство индексатора this[index]:

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;
        }
    }
}

Если ваши предметы не поддерживают INotifyPropertyChanged, но это ваши классы, я бы подумал добавить эту поддержку.

4 голосов
/ 21 мая 2009

Если вы хотите использовать отражение, у класса List<T> есть приватное поле с именем _version, которое увеличивается каждый раз при изменении списка. Он не скажет вам, какие элементы были изменены, но вы можете сравнить его с исходным значением _version, чтобы обнаружить неизмененный список.

Для справки, это поле используется, чтобы гарантировать, что перечислители станут недействительными при изменении списка. Таким образом, вы должны иметь возможность использовать его в своих целях достаточно надежно, если только фактический управляемый код для List<T> не изменится.

Чтобы получить значение _version, вы можете использовать что-то вроде этого:

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

В целом, однако, это не лучший подход. Однако, если вы застряли, используя List<T>, созданный кем-то другим, это, вероятно, лучший вариант, который у вас есть. Имейте в виду, что изменения в .NET Framework могут изменить имя поля (или полностью его удалить), и он не гарантированно существует в сторонних реализациях CLR, таких как Mono.

4 голосов
/ 21 мая 2009

Вы можете создать свой собственный класс IList<T>, скажем DirtyList<T>, который может записывать, когда список изменился.

2 голосов
/ 21 мая 2009

Предполагая, что GetHashCode () для каждого члена, содержащегося в списке, реализован должным образом (и, следовательно, изменяется при изменении элемента), я представляю что-то вроде:

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;
       }
    }
}

* Пожалуйста, имейте в виду, что я напечатал это на SO и у меня нет компилятора, так что вышеуказанный код ни в коем случае не гарантирует работу, но, надеюсь, он покажет идею.

2 голосов
/ 21 мая 2009

Как насчет этого?

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();
    }
}
1 голос
/ 21 мая 2009

Убедитесь, что T является потомком объекта с грязным флагом, и у реализации IList есть проверка для того, что обходит грязные флаги списка.

1 голос
/ 21 мая 2009

Вы можете реализовать свой собственный список, который содержит 2 внутренних списка ... и созданную версию и версию отслеживания ... например,

//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.
       }
    }
}
...