C # Custom Observable Collection - Должен ли я использовать Composition или Inheritance? - PullRequest
1 голос
/ 05 октября 2011

Я хочу создать настраиваемую наблюдаемую коллекцию (может быть связана с XAML), но я хочу отслеживать дополнительную информацию, которая требует переопределения наблюдаемых методов сбора. Методы ObservableCollection не являются виртуальными , но это означает, что единственный способ «переопределить» их - это просто скрыть их с помощью ключевого слова «new».Вот простой пример того, что я имею в виду:

//Tracks how many objects of each type are in this collection
class CustomCollectionInherited:ObservableCollection<Object>
{
    private Dictionary<Type, UInt32> _count = new Dictionary<Type,uint>();

    public UInt32 getTypeCount(Type T)
    {
        return _count[T];
    }

    #region Base Method 'Overrides'

    new public void Add(Object item)
    {
        base.Add(item);
        if (!_count.ContainsKey(item.GetType())) {
            _count[item.GetType()] = 1;
        } else {
            _count[item.GetType()] += 1;
        }
    }

    new public bool Remove(Object item)
    {
        if (base.Remove(item))
        {
            _count[item.GetType()] -= 1;
            return true;
        }
        return false;
    }

    #endregion
}

У меня есть две проблемы с этим.Во-первых, хотя существует много методов, которые я хочу наследовать от ObservableCollection, таких как перечислители, интерфейс INotifyCollectionChanged и т. Д., есть много методов, которые я не хочу наследовать .А именно, методы, которые изменяют коллекцию, такие как Clear (), ClearItems (), Insert (), InsertItem () и еще 13, которые приводят к тому, что счетчик типов моей коллекции не синхронизируется.Похоже, это аргумент для композиции.

Вторая проблема - upcasting - программист может случайно обойти мои пользовательские реализации, используя мою коллекцию таким образом, что она будет передана наунаследованный тип.Например:

    myCustomObj.AddToCollection( myCustomCollectionInherited );
...
void CustomObj.AddToCollection( Collection c )
{   
    c.Add(this);
}

Это довольно надуманный пример, но в таком случае будет использоваться унаследованный метод «Добавить», и счетчик типов моей коллекции снова выйдет из синхронизации.Кажется, что нет никакого способа обойти это, если моя коллекция не отслеживает событие base.CollectionChanged и каждый раз перестраивает счет с нуля, что полностью отрицает цель поддержания счета за O (1).


Исходя из этих проблем, я начал думать, что подходящим решением является создание класса, который содержит ObservableCollection.Но помните, мне нужно это для привязки к XAML как наблюдаемой коллекции, поэтому я должен реализовать все соответствующие интерфейсы, которые реализует ObservableCollection, чтобы он мог быть привязан к пользовательскому интерфейсу таким же образом.Ниже приведен пример:

//Tracks how many objects of each type are in this collection
class CustomCollectionEncapsulated : IList<object>, INotifyCollectionChanged
{
    private ObservableCollection<Object> _base = new ObservableCollection<object>();
    private Dictionary<Type, UInt32> _count = new Dictionary<Type, uint>();

    public UInt32 getTypeCount(Type T)
    {
        return _count[T];
    }

    public void Add(object item)
    {
        _base.Add(item);
        if (!_count.ContainsKey(item.GetType())) {
            _count[item.GetType()] = 1;
        } else {
            _count[item.GetType()] += 1;
        }
    }

    public bool Remove(object item)
    {
        if (_base.Remove(item))
        {
            _count[item.GetType()] -= 1;
            return true;
        }
        return false;
    }
}

Конечно, вышеупомянутое само по себе не компилируется, потому что IList реализует ICollection, IEnumerable, IEnumerable, каждый из которых имеет методы, которые мне нужно реализовать, и так далее, пока яв итоге получается около 20 дополнительных методов и сотни строк кода, каждый из которых говорит:

Type methodINeedToImplement(Params)
{
     return _base.methodINeedToImplement(Params);
}

или

Type methodINeedToImplement(Params)
{
     throw new NotImplementedException();
}

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

Так что мне делать?Я абсолютно не могу убедить своего босса, что лучший способ защитить эту пользовательскую коллекцию - использовать инкапсуляцию и явно реализовать 20 новых методов.В то же время мы уже сталкиваемся с ошибками, когда другие люди, использующие эту пользовательскую коллекцию, облажают ее, используя базовые методы ObservableCollection, которые мы не поддерживаем, но не можем скрыть с помощью наследования.

Ответы [ 5 ]

4 голосов
/ 05 октября 2011

ObservableCollection предназначен для использования в качестве базового класса, вы просто смотрите на неправильные методы, а не на общедоступные, такие как Add, Remove, Clear и т. Д., Вы должны переопределить защищенные виртуальные, такие как InsertItem, MoveItem и т. Д. Проверка документация для получения полного списка перезаписываемых материалов.

1 голос
/ 05 октября 2011

Я думаю, что можно придерживаться наследования (хотя вы могли бы использовать адаптер), но вместо «новых» функций я просто переопределил (или обработал бы, если использовал адаптер) OnCollectionChanged:

public class CustomCollectionInherited : ObservableCollection<object>
{
    private Dictionary<Type, UInt32> _count = new Dictionary<Type, uint>();

    public UInt32 GetTypeCount(Type T)
    {
        return _count[T];
    }

    protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                e.NewItems.Cast<Object>().ToList().ForEach(OnAdded);
                break;
            case NotifyCollectionChangedAction.Remove:
                e.OldItems.Cast<Object>().ToList().ForEach(OnRemoved);
                break;
            case NotifyCollectionChangedAction.Replace:
                // TODO: Handle this case
                break;
            case NotifyCollectionChangedAction.Reset:
                _count.Clear();
                this.ToList().ForEach(OnAdded);
                break;
        }
        base.OnCollectionChanged(e);
    }

    private void OnAdded(Object o)
    {
        _count[o.GetType()] += 1;
    }

    private void OnRemoved(Object o)
    {
        _count[o.GetType()] -= 1;
    }
}
1 голос
/ 05 октября 2011

Вы можете скрыть методы из базового класса, вот пример из MSDN:

class Base
{
   public void F() {}
}
class Derived: Base
{
   public void F() {}      // Warning, hiding an inherited name
}

Поскольку вы не предоставляете «затененному» методу какое-либо указание, он просто удаляет его.Тем не менее, удручает ... да, ты облажался.Вы наследуете из наблюдаемой коллекции, что она может быть разлита как одна ...

Джейсон

1 голос
/ 05 октября 2011

Предполагая, что единственная причина, по которой вы расширяете ObservableCollection, состоит в том, чтобы вести подсчет содержащихся типов, вы можете легко сделать это, унаследовав непосредственно от ObservableCollection и переопределив OnCollectionChanged , который является виртуальным :

protected virtual void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)

Таким образом, вам не нужно беспокоиться о том, как он добавляется / удаляется в / из коллекции, вы просто увеличиваете / уменьшаете счет до того, как вызовете метод base.OnCollectionChanged (e). .

В качестве отступления, вместо того чтобы проходить через все эти проблемы, чтобы получить количество типов, почему бы не использовать ссылку на объекты: myCollection.Count (p => p - MyType); ???

1 голос
/ 05 октября 2011

Если вы не можете переопределить методы в ObservableCollection, вам следует использовать CollectionBase и реализовать вместо него измененную INotifyCollection.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...