Как заставить уникальность в коллекции ObservableCollection - PullRequest
1 голос
/ 19 марта 2012

Простой класс

public class Group
{
    public Int16 ID { get; private set; }
    public string Name { get; set; }
    public Group ( Int16 id, string name ) 
    { ID = id; Name = name; }
}

То, что я хотел бы, это ObservableCollection, где коллекция форсирует уникальность идентификатора и уникальность регистра CaseInsensitive для имени.

То, что я пробовал, это:

public class Group
{
    public Int16 ID { get; private set; }
    public string Name { get; set; }

    public override bool Equals(System.Object obj)
    {
        // If parameter is null return false.
        if (obj == null)
        {
            return false;
        }

        // If parameter cannot be cast to Point return false.
        Group g = obj as Group;
        if ((System.Object)g == null)
        {
            return false;
        }

        // Return true if either fields match:
        return ( ID == g.ID || string.Compare(Name, g.Name, true) == 0 ) ;
    }

    public bool Equals(Group g)
    {
        // If parameter is null return false:
        if ((object)g == null)
        {
            return false;
        }

        // Return true if either fields match:
        return ( ID == g.ID || string.Compare(Name, g.Name, true) == 0 ) ;
    }

    public override int GetHashCode()
    {
        return ID; // ^ (Int32)Name.ToLower();
    }

    public Group ( Int16 id, string name ) 
    { ID = id; Name = name; }
}

HashSet &lt Group &gt

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

Ответы [ 2 ]

2 голосов
/ 20 марта 2012

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

Сначала добавьте этот интерфейс и обработчик событий.

public delegate void TestForColisions(object sender, TestForColisionsArgs e);

public class TestForColisionsArgs : CancelEventArgs
{
    public TestForColisionsArgs(object newValue)
    {
        NewValue = newValue;
    }

    public object NewValue { get; private set; }
}


public interface ITestForColisions
{
    /// <summary>
    /// Set the event to Canceled if there will be a collision.
    /// </summary>
    event TestForColisions TestForCollision;
}

Тогда пусть ваш класс реализует интерфейс

public class Group : ITestForColisions, IEquateable<Group>
{
    public Int16 ID { get; private set; }

    private string _Name;
    public string Name 
    {
        get { return _Name; }
        set
        {
            //If RaiseNameChanging returns true there was a collision.
            if (RaiseNameChanging(value))
            {
                throw new ArgumentException(String.Format("The name {0} is in use in the collection", value));
            }
            else
            {
                _Name = value;
            }
        }
    }

    protected virtual bool RaiseNameChanging(string name)
    {
        //Make a copy with the new name.
        var newGroup = (Group)this.MemberwiseClone();
        newGroup.Name = name;

        var cancelEventArgs = new TestForColisionsArgs(newGroup);
        if (TestForCollision != null)
        {
            TestForCollision(this, cancelEventArgs);
        }
        return cancelEventArgs.Cancel;
    }

   //(...)
}

Тогда вам понадобится пользовательская коллекция, которая прослушивает TestForCollision события и обрабатывает соответственно. Для большинства методов вы можете просто вызвать версию родительского _BaseSet, однако для любого из методов, которые изменяют набор, вам нужно будет либо подписаться, либо отказаться от подписки на событие. Я сделал Очистить, Добавить и Удалить для вас.

public class ColisionTestedCollection<T> : ISet<T>
    where T : ITestForColisions
{
    public ColisionTestedCollection(ISet<T> baseSet)
    {
        _BaseSet = baseSet;
        _EqualityComparer = EqualityComparer<T>.Default;
    }

    public ColisionTestedCollection(ISet<T> baseSet, IEqualityComparer<T> equalityComparer)
    {
        _BaseSet = baseSet;
        _EqualityComparer = equalityComparer;
    }

    private ISet<T> _BaseSet;
    private IEqualityComparer<T> _EqualityComparer;


    void TestItemsForCollision(object sender, TestForColisionsArgs e)
    {
        if (_BaseSet.Contains((T)e.NewValue, _EqualityComparer))
        {
            e.Cancel = true;
        }
    }

    public bool Add(T item)
    {
        bool added = _BaseSet.Add(item);
        if(added)
            item.TestForCollision += TestItemsForCollision;
        return added;
    }

    void ICollection<T>.Add(T item)
    {
        ((ICollection<T>)_BaseSet).Add(item);
        item.TestForCollision += TestItemsForCollision;
    }

    public bool Remove(T item)
    {
        bool removed = _BaseSet.Remove(item);
        if (removed)
            item.TestForCollision -= TestItemsForCollision;
        return removed;
    }

    public void Clear()
    {
        foreach (var item in _BaseSet)
            item.TestForCollision -= TestItemsForCollision;
        _BaseSet.Clear();
    }

    public void ExceptWith(IEnumerable<T> other)
    {
        throw new NotImplementedException();
    }

    public void IntersectWith(IEnumerable<T> other)
    {
        throw new NotImplementedException();
    }

    public void SymmetricExceptWith(IEnumerable<T> other)
    {
        throw new NotImplementedException();
    }

    public void UnionWith(IEnumerable<T> other)
    {
        throw new NotImplementedException();
    }

    //The rest of the functions will just be simply calling _BaseSet's version of the method. 
    public bool IsProperSubsetOf(IEnumerable<T> other)
    {
        return _BaseSet.IsProperSubsetOf(other);
    }
    //(snip)
}
0 голосов
/ 19 марта 2012

Я думаю, что ObservableCollection (http://msdn.microsoft.com/en-us/library/ms668604.aspx) и KeyedCollection (http://msdn.microsoft.com/en-us/library/ms132440.aspx)) было бы хорошим началом.

Вы можете создать пользовательский тип, который наследуется от KeyedCollection и реализует интерфейс INotifyCollectionChanged.

Используя KeyedCollection, вы можете «ввести ключ» в столбец идентификатора, и это обеспечит уникальность этого. Однако, если вы хотите более сложную проверку, вы можете просто реализовать IList, INotifyCollectionChanged и выдать некоторые пользовательские исключения, если критерии не соответствуют при добавлении новых элементов.

...