Wrapping about Collections

Monday, February 1, 2010

Mark Needham’s “Thoughts On Software Development” is a great collection of blog posts. Last year Mark wrote “Coding: The primitive obsession”, and challenged the idea that the primitive obsession anti-pattern is just about overusing low level data types like string and int.

The most frequent offender seems to be the creation of collections of things which we then query to find specific items elsewhere in the code. More often that not we'll also perform some custom logic on that particular item.

At its most extreme we might extract each of the items in a collection and make use of them separately. This somewhat defeats the purpose of the collection.

When this happens it often seems to me that what we actually have is a series of attributes of an object rather than a collection.

Mark goes on to describe how wrapping a collection can often create a more useful abstraction, then points to his debate in Wrapping collections: Inheritance vs Composition. It’s a good read.

I’ve been wrapping collections a bit myself lately, and it’s not too hard to create a custom collection that is both feature rich and descriptive of the problem it solves using composition. For example, let’s say you need to keep a collection of Stamp objects and support INotifyCollectionChanged.

public class StampCollection : IEnumerable<Stamp>, 
                               INotifyCollectionChanged
{
    public StampCollection()
    {
        _items = new ObservableCollection<Stamp>();
        _items.CollectionChanged += ItemsCollectionChanged;
    }

    public void AddStamp(Stamp newStamp)
    {
        /* logic */

        _items.Add(newStamp);
    }

    public void RemoveStamp(Stamp stamp)
    {
        /* logic */

        _items.Remove(stamp);
    }

    public IEnumerator<Stamp> GetEnumerator()
    {
        return _items.OrderBy(stamp => stamp.Something)
                     .GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public event NotifyCollectionChangedEventHandler 
        CollectionChanged = delegate { };

    void ItemsCollectionChanged(object sender, 
                                NotifyCollectionChangedEventArgs e)
    {
        CollectionChanged(this, e);
    }

    readonly ObservableCollection<Stamp> _items;
}

Yes, it’s a bit of code, but it:

  • Gives you complete control over the surface area of the API compared to inheriting from ObservableCollection<T> or List<T>
  • Allows precise control over adding and removing objects (validation, perhaps updating fields).
  • Becomes a more descriptive part of the domain
  • Is not to be confused with a rap collection.

Comments
gravatar Ilia Jerebtsov Monday, February 1, 2010
A collection object by itself should not be more than a glorified array. It shouldn't have any logic that isn't related to the process of maintaining and manipulating a set of objects. Any more specific behavior should be passed along to the collection's owner (which is the object that exposes the collection instance, usually as a readonly property).

The "collection" is really just an API pattern designed to provide read/write access to a set of items. Using component.AddItem and component.RemoveItem methods is equivalent to using component.Items.Add() and component.Items.Remove(); the only thing that changes is the API style.

The reason why the latter is better is because it standardizes list access, and lets the framework do neat things like collection initializers or LINQ. When you hide away that interface, you're taking away a lot of useful behavior that users of your "collection" would expect. Calling something a collection when it isn't one is just going to be confusing for everyone.
gravatar scott Monday, February 1, 2010
@Ilia I disagree that a collection has to provide read and write access. IEnumerable is enough for LINQ operators to work, and it's still a collection you can walk through an item at a time.
gravatar Ilia Jerebtsov Monday, February 1, 2010
.NET has established the semantics on what a collection is through the interfaces they defined, and the classes they've implemented. You can call it a collection, but it would go against .NET's existing conventions.

IEnumerable - a sequentially readable set of items.
ICollection - a writable set of items with some sort of positional accessor, normally used for APIs.
IList - a writable set of items with manipulation methods (sort, etc.)

All of the classes in the framework that call themselves a "collection" implement ICollection.

A "collection" that only implements IEnumerable isn't really a collection but just an enumerable. You can't, for example, access an enumerable by position.

Sometimes it makes sense to restrict that, but if you're going to restrict your class to enumeration access only, then it doesn't make sense to have add/remove methods either. If you are going to have add/remove methods, it might as well be an ICollection.
gravatar Scott Allen Tuesday, February 2, 2010
@Ilia: Ah, I see your point. That's valid. In this scenario the class could just be "Stamps".
Slava Tuesday, February 2, 2010
It seems there should be ItemsCollectionChanged method call in both AddStamp and RemoveStump methods, right?
gravatar Scott Allen Tuesday, February 2, 2010
@Slava: That get's forwarded from the ObservableCollection (see CollectionChanged += ItemsCollectionChanged in the constructor).
Slava Tuesday, February 2, 2010
Yes, correct, sorry. I just was confused with own StampCollection event with same name.
gravatar Ido Ran Friday, February 5, 2010
Hi,
I agree with Ilia but pay attention to the fact that Microsoft has change the ICollection interface in .NET 2 with ICollection<T> which not (than) has Add and Remove methods.

Also remember that ObservableCollection<T> (which is great class) is in a WPF assembly. So we can use this as base class or internal member but it's should be in System.Core or something like that.
Not just UI application need to know when collection has changed.

Ido.
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!