Blog Archive

Friday, July 7

Collection<T> and explicit interface implementations

Say you're looking at Collection<T> and ReadOnlyCollection<T> in the System.Collections.ObjectModel namespace. You think, “I could use those! In fact, there's a collection I've been meaning to write for awhile:”

class StringCollection : Collection<string>
{
    public StringCollection(params string[] strings) : base(strings) { }

    public string Join(string separator)
    {
        // clearly this is not an efficient solution!
        string[] strings = new string[Items.Count];
        Items.CopyTo(strings, 0);

        return String.Join(separator, strings);
    }
}

class Program
{
    static void Main(string[] args)
    {
        StringCollection strings = new StringCollection("one", "two", "three");
        Console.WriteLine(strings.Join(", "));
    }
}

// Output:
// one, two, three

Perfect! That's exactly what you expected.

Next, you decide to modify your program. You want to add another string:

static void Main(string[] args)
{
    StringCollection strings = new StringCollection("one", "two", "three");
    strings.Add("four");
    Console.WriteLine(strings.Join(", "));
}

Builds fine and… NotSupportedException Collection is read-only!

But wait, we inherited from Collection<T>, not ReadOnlyCollection<T>. What gives?

Well, when we called the base constructor for Collection<T>, we gave it an IList<string>. Okay, so really we gave it an Array of strings, but it was implicitly converted to an IList<string>. That IList was stored as a field within the Collection wrapper (accessible via the protected Items property).

Calling Add calls the explicitly implemented interface member IList.Add on the string[] which (from the docs:) Throws a not supported exception in all cases.

So, how can we modify the StringCollection so that it continues to call the appropriate base constructor, but coerces the string[] to something a bit more useful?

One possibility is to check the IsReadOnly property of the IList before passing it along, like so:

class StringCollection : Collection<string>
{
    public StringCollection() : base() { }
    public StringCollection(params string[] strings) : this((IList<string>) strings) { }
    public StringCollection(IList<string> strings) : base(strings.IsReadOnly ? new List<string>(strings) : strings) { }

    // ...
}

I'm sure there are better ways, though. Any ideas?

No comments: