3

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.

Alain
  • 26,663
  • 20
  • 114
  • 184
  • Have you already checked to see that the binding doesn't just require `INotifyCollectionChanged` and `INotifyPropertyChanged`? – Marc Oct 05 '11 at 18:39
  • @Marc - Needs IList methods if you're going to actually present the content of the collection. – Alain Oct 05 '11 at 18:47
  • 1
    Right, which continues my bad way of coming to the point of, this is an obvious choice. Shadowing methods is _almost_ always a bad idea. Especially in the manner you're describing. Implementing your interface is nothing more than proxying to your underlying collection. Your TypeCount is also confusing. I can't imagine this collection is so large that `.OfType` isn't sufficient. – Marc Oct 05 '11 at 19:04
  • It was a sample class to get my problem across without putting up proprietary code. The actual problem does call for monitoring properties and registering for events of the objects added and removed. – Alain Oct 06 '11 at 13:26
  • I'm upvoting all the contributions, but I vote to close the question as not constructive on the basis that it is mostly soliciting opinion and doesn't have an objective solution. – Alain Oct 06 '11 at 13:30

5 Answers5

6

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.

Konstantin Oznobihin
  • 5,234
  • 24
  • 31
  • This isn't right, InsertItem and RemoveItem are special case insert/remove methods that happen at a specific index. There's absolutely no reason why the inheriter of this base class should be forced to insert by index only. The reason why these are virtual and the others are not is a mystery, but the point remains that if I use it as a base class, people using my collection will be using unsupported insert and remove methods and messing up my custom content statistics tracking. – Alain Oct 06 '11 at 13:03
  • 7
    @Alain: Did you read the documentation for these methods? They are not special case, but the core the Collection is based upon. All public methods like Add, Remove, Insert and so on end up calling these protected virtual methods. After all, this is how ObservableCollection class is implemented, it has no Add method, the public non-virtual Add method is defined by the Collection class, ObservableCollection just inherits it and overrides InsertItem. Why don't you look at the documentation? – Konstantin Oznobihin Oct 06 '11 at 13:38
  • I'm mistaken, I think you've found the crux of the situation, and I was just too deep into the class to see the bigger picture. If these virtual methods are called by every other modifying method, then overriding them should suffice, and will ensure my code is evaluated even if the collection is upcast. – Alain Oct 06 '11 at 15:55
  • Does anyone have a reference to this documentation? The MSDN doc does not appear to state that the public methods are just wrappers around the protected virtual methods. https://msdn.microsoft.com/en-us/library/ms668604(v=vs.110).aspx – denver Sep 14 '16 at 16:22
2

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;
    }
}
Reddog
  • 15,219
  • 3
  • 51
  • 63
1

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.

Ritch Melton
  • 11,498
  • 4
  • 41
  • 54
  • Well, It's not read-only - as you can see there's an Add method, there's just a lot of methods of modifying the collection, such as merging another collection, which aren't supported. – Alain Oct 05 '11 at 18:45
  • Alain - Ok, I'm confused then. I'll have to try and re-read your post, but its fairly verbose and I get the sense that you're complicating something simple. – Ritch Melton Oct 05 '11 at 19:18
  • @Alain - "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." - So you just want a simpler collection? Then my advice still holds. – Ritch Melton Oct 05 '11 at 19:19
1

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

Jason
  • 11
  • 1
  • Yeah, and then again you get the problem of 100 lines of code in the Inheriting class spent doing nothing more than trying to list every inherited method that you want to hide, a task which again is prone to error because you might miss one. – Alain Oct 05 '11 at 18:46
1

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); ???

Bahri Gungor
  • 2,289
  • 15
  • 16