C# Custom Observable Collection - Should I use Com

2019-03-02 10:39发布

问题:

I want to create a custom observable collection (can be bound to in XAML), but I want to track additional information which requires overriding observable collection methods. ObservableCollection methods are not virtual though, which means the only way to 'override' them is actually just to hide them using the 'new' keyword. Here is a simple example of what I mean:

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

I have two problems with this. The first is that while there are many methods I want to inherit from an ObservableCollection, such as the enumerators, the INotifyCollectionChanged interface, etc., there are many methods which I do not want to inherit. Namely, methods that modify the collection, such as Clear(), ClearItems(), Insert(), InsertItem(), and 13 others which would cause my collection's type count to get out of sync. This seems to be an argument for composition.

The second problem is upcasting - a programmer might accidentally get around my custom implementations by using my collection in such a way that it gets upcast to the inherited type. For example:

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

It's a pretty contrived example, but in such a case, the inherited 'Add' method would be used, and my collection's type count would get out of sync again. There doesn't seem to be any way around this unless my collection monitors the base.CollectionChanged event and rebuilds the count from scratch every time, which completely defeats the purpose of maintaining a count in O(1) time.


Based on those issues, I've started to think that the appropriate solution is to create a class which contains an ObservableCollection. But remember, I need this to bind to XAML like an observable collection, so I must implement all the relevant interfaces an ObservableCollection implements so that it can be bound to the UI in the same way. An example is below:

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

Of course, the above on its own doesn't compile because IList implements ICollection, IEnumerable, IEnumerable, all of which have methods I need to implement, and so on until I end up having 20 or so extra methods and hundreds of lines of code all of which say

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

or

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

The main reason for inheritance is so that a programmer does not need to do all this work for the 95% of methods and events they aren't changing.

So what do I do? I absolutely cannot convince my boss that the best way to secure this custom collection is to use encapsulation and explicitly implement 20 new methods. At the same time, we're already running into bugs where other people using this custom collection are screwing it up by using base ObservableCollection methods that we don't support, but can't hide via inheritance.

回答1:

ObservableCollection is designed to be a base class, you are just looking at wrong methods, instead of public ones like Add, Remove, Clear, etc. you should override protected virtual ones like InsertItem, MoveItem, etc. Check documentation for a full list of overridable stuff.



回答2:

You can hide methods from a base class, here is an example from MSDN:

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

Since you are not giving the "Shadowed" method any implimentation it simply removes it. However, upcasting... yeah, you are screwed. You inherit from observable collection it can be cast as one...

Jason



回答3:

Assuming that the only reason you are extending ObservableCollection is to keep a count of the contained types, you can accomplish this easily by inheriting directly from ObservableCollection and overriding OnCollectionChanged which is virtual:

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

This way, you don't have to worry about how it is added/removed to/from the collection, you just increment/decrement the count before you call the base.OnCollectionChanged(e) method.

As an aside, rather than going through all this trouble to get a count of the types, why not use link to objects: myCollection.Count(p => p is MyType); ???



回答4:

I think it's OK to stick with the inheritance (though you could use an adapter) but instead of "new"-ing functions I would simply override (or handle if using an adapter) the 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;
    }
}


回答5:

If you can't override the methods in ObservableCollection, then you should use CollectionBase and implement INotifyCollection changed instead.

Although I think your approach to your problem is probably off-base as you want to provide a simpler view of the collection and I think your efforts should be focused on doing that instead. Create a ISimpleCollectionView that provides only the interfaces you are looking for and write an adapter that takes a ICollection in and implements the ISimpleCollectionView interface.