Consider creating a custom collection

It’s happened to me a few times when I create a property of a type List<T> or Collection<T>, where T is something simple such as a string or an int, and then later I regret I’ve done so. For instance, the other day I had the following scenario (I’ve changed things around in order to make it a little simpler for this post):

I have some class that had a Parameter property of type Collection<string>:

public class SomeClass
{
    private Collection<string> m_Parameters = new Collection<string>();
    public Collection<string> Parameters
    {
        get { return m_Parameters; }
        set { m_Parameters = value; }
    }

    public SomeClass()
    {
        this.Parameters("A");
        this.Parameters("B");
        this.Parameters("C");
    }
}

It used to work great when all I needed was a list of parameters there:

SomeClass foo = new SomeClass();
foreach (string parameter in foo.Parameters)
{
    Console.WriteLine(parameter);
}

However, in order to support something new feature, I also needed to store the old and new values for the parameter. Hmpf. Now, instead of a Collection of strings, I need a collection of something else. I then created a Parameter class, with the specific properties I needed:

public class Parameter
{
    public string ParameterName { get; private set; }
    public object OldValue { get; private set; }
    public object NewValue { get; private set; }

    public Parameter(string parameterName, object oldValue, object newValue)
    {
        ParameterName = parameterName;
        OldValue = oldValue;
        NewValue = newValue;
    }
}

Nothing too fancy. At this point I can change my Collection<string> to Collection<Parameter>. Obviously, I’ll also have to change any code that works with that collection, since the generic type for the collection has changed:

SomeClass foo = new SomeClass();
 foreach (Parameter parameter in foo.Parameters)
 {
     Console.WriteLine(parameter.ParameterName);
     Console.WriteLine(parameter.OldValue);
     Console.WriteLine(parameter.NewValue);
 }

If I had gone with a richer type to begin with, I wouldn’t have to change any caller of that collection; if I needed more information, all I’d have to do was to add more properties to the Parameter class. In this one case, making the change wasn’t such a big deal because my property was exposed only within the assembly; if it was exposed as a public property, than that’d be a major breaking change because I’d have no clue as to who else would be using it in other assemblies.

That makes me think whenever I’m declaring a Collection or List of something, I should put a little more thought into whether it’s likely that I’ll need some extra information associated with it, and if so, I should create a special type for it.

Also, it turns out that what I really needed was some sort of dictionary, because I wanted to look up items by a given key (in this case, the parameter’s name). The Collection<T> doesn’t give me that behavior, but the KeyedCollection<TKey, TItem>  does, so I created a ParameterCollection like so:

public class ParameterCollection : KeyedCollection<string, Parameter>
{
    protected override string GetKeyForItem(Parameter item)
    {
        return item.ParameterName;
    }
}

The class uses a string as the key (which is the ParameterName), and the Parameter as the value. So my SomeClass class ended up like this:

public class SomeClass
{
    private ParamaterCollection m_Parameters = new ParamaterCollection();
    public ParamaterCollection Parameters
    {
        get { return m_Parameters; }
        set { m_Parameters = value; }
    }

    public SomeClass()
    {
        this.Parameters.Add(new Parameter("A", "A old value", "A new value"));
        this.Parameters.Add(new Parameter("B", "B old value", "B new value"));
        this.Parameters.Add(new Parameter("C", "C old value", "C new value"));
    }
}

And besides using a regular foreach, I can also access the elements like this:

Console.WriteLine(foo.Parameters["A"].OldValue);
Console.WriteLine(foo.Parameters["B"].OldValue);
Advertisements
  1. #1 by Chris on April 6, 2008 - 1:11 pm

    You might want to evaluate not exposing the collection publicly at all and instead have methods on your object like AddParameter, RemoveParameter and GetMessages()I think this is law of demeter. I do this a lot with my nhibernate object and it really helps with enforcing bi-directional relationships between objects.

  2. #2 by Chris on April 6, 2008 - 1:11 pm

    You might want to evaluate not exposing the collection publicly at all and instead have methods on your object like AddParameter, RemoveParameter and GetMessages()I think this is law of demeter. I do this a lot with my nhibernate object and it really helps with enforcing bi-directional relationships between objects.

  3. #3 by William on April 7, 2008 - 10:23 am

    Claudio,
     
    Good blog. It is recommended to use such an approach when you are exposing a public API. One of the reasons is the reason you stated above. A few others are: List<T> exposes many members which are not likely to be used for your collection. Also you do not have the ability to be notified when your collection is modified by the client.

  4. #4 by Claudio on April 9, 2008 - 8:14 pm

    Hi Chris,
     
    yeah, I hear you, and I\’ve actually done a lot of that myself. I agree that not exposing the collection at all is an even better design because then I could completely change the internal storage without worrying about breaking existing code. In this case, though, I didn\’t care that much because the collection was only exposed and used by a few class within the same assembly, so no biggie there.

  5. #5 by Claudio on April 9, 2008 - 8:15 pm

    Hi Bill,
     
    yes, I\’m aware of those guidelines: FxCop has told me that once, about two years ago, and I think I\’ll never forget.  🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: