Thread Safety Via Read Only Collections

UPDATE: Made some corrections to the discussion of ReadOnlyCollection’s interface implementations near the bottom. Thanks to Thomas Freudenberg and Damien Guard for pointing out the discrepancy.

In a recent post I warned against needlessly using double check locking for static members such as a Singleton. By using a static initializer, the creation of your Singleton member is thread safe. However the story does not end there.

One common scenario I often run into is having what is effectively a Singleton collection. For example, suppose you want to expose a collection of all fifty states. This should never change, so you might do something like this.

public static class StateHelper
{
  private static readonly IList<State> _states = GetAllStates();

  public static IList<State> States
  {
    get
    {
      return _states;
    }
  }

  private static IList<State> GetAllStates()
  {
    IList<State> states = new List<State>();
    states.Add(new State("Alabama"));
    states.Add(new State("Alaska"));
    //...
    states.Add(new State("Wyoming"));
    return states;
  }
}

While this code works just fine, there is potential for a subtle bug to be introduced in using this class. Do you see it?

The problem with this code is that any thread could potentially alter this collection like so:

StateHelper.States.Add(new State("Confusion"));

This is bad for a couple of reasons. First, we intend that this collection be read-only. Second, since multiple threads can access this collection at the same time, we can run into thread contention issues.

The design of this class does not express the intent that this collection is meant to be read-only. Sure, we used the readonly keyword on the private static member, but that means the variable reference is read only. The actual collection the reference points to can still be modified.

The solution is to use the generic ReadOnlyCollection<T> class. Here is an updated version of the above class.

public static class StateHelper
{
  private static ReadOnlyCollection<State> _states = GetAllStates();

  public static IList<State> States
  {
    get
    {
      return _states;
    }
  }

  private static ReadOnlyCollection<State> GetAllStates()
  {
    IList<State> states = new List<State>();
    states.Add(new State("Alabama"));
    states.Add(new State("Alaska"));
    //...
    states.Add(new State("Wyoming"));
    return new ReadOnlyCollection<State>(states);
  }
}

Now, not only is our intention expressed, but it is enforced.

Notice that In the above example, the static States property still returns a reference of type IList<State> instead of returning a reference of type ReadOnlyCollection<State>.

This is a concrete example of the Decorator Pattern at work. The ReadOnlyCollection<T> is a decorator to the IList<T> class. It implements the IList<T> interface and takes in an existing collection as a parameter in its contstructor.

In this case, if I had any client code already making use of the States property, I would not have to recompile that code.

One drawback to this approach is that interface IList<T> contains an Insert method. Thus the developer using this code can attempt to add a State, which will cause a runtime error.

If this was a brand new class, I would probably make the return type of the States property ReadOnlyCollection<State> which explicitly implements the IList<T> and ICollection<T> interfaces, thus hiding the Add and Insert methods (unless of course you explicitly cast it to one of those interfaces). That way the intent of being a read-only collection is very clear, as there is no way (in general usage) to even attempt to add another state to the collection.

Technorati tags: , ,

What others have said

Requesting Gravatar... Damien Guard Mar 25, 2007 11:50 PM
# re: Thread Safety Via Read Only Collections
"If this was a brand new class, I would probably make the return type of the States property ReadOnlyCollection<State> which does not contain an Add method."

ReadOnlyCollection<T> does contain an Add method - if it didn't then it couldn't be a decorator to IList<T> which defines the Add method.

It is a bad example of the Decorator design pattern in that it does not "Attach additional responsibilities to an object dynamically" but instead removes responsibility by failing to implement the majority of the IList/ICollection interface - something which should be ringing alarm bells.

Personally I'd go with defining a StateList class that just implemented IEnumerable<State> and provided Count, Contains, IndexOf and Item[] members.

[)amien
Requesting Gravatar... Haacked Mar 26, 2007 12:25 AM
# re: Thread Safety Via Read Only Collections
Actually, ReadOnlyCollection<T> does not contain the Add method.

Check out the MSDN documentation. Here's a list of all members of ReadOnlyCollection<T>.

Not to mention that IList<T> does not contain the Add method either.

It does implement the Insert method though, which throws a runtime exception, which is a point I overlooked since I rarely use Insert when populating a collection.

I still think it's a fine example of a decorator in that it added a responsibility that I didn't already have with my IList implementation. It made it read-only.

In this context, since that's a feature I want, and I already committed to the IList<T> interface, I think it's still fine to call it a decorator.
Requesting Gravatar... Thomas Freudenberg Mar 26, 2007 3:03 AM
# re: Thread Safety Via Read Only Collections
ReadOnlyCollection<T> inherits ICollection<T>, so it has to implement ICollection<T>.Add. However, ReadOnlyCollection<T> hides the method by implementing it explicitly and throwing a NotSupportedException.
Requesting Gravatar... CodeObsessed Mar 26, 2007 5:07 AM
# Enabling or Directing revisited
(this is currently a placeholder article which will be updated later) Looking at Phil Haack's article
Requesting Gravatar... AlSki Mar 26, 2007 5:07 AM
# re: Thread Safety Via Read Only Collections
However a bigger question is should we be restricting the users of our class in this way. Do we wish to empower the users of our class with the ability to change things, (e.g. "Alaska splits into 50th and 51st State in war over the list of correct names for snow") or simply lock them in.

I think the fact that we want to do this, is actually more revealing of our attitude to our fellow developers and their abilities, and its something I constantly battle with. See
http://www.martinfowler.com/bliki/SoftwareDevelopmentAttitude.html
Requesting Gravatar... Bill Pierce Mar 26, 2007 7:31 AM
# re: Thread Safety Via Read Only Collections
If you want to get really hard core read only uber-cool, just expose an IEnumerable. You can still use it for databinding and enumeration, don't have to worry about people even trying to modify the contents. If the user wants to, they can use it to instantiate their own collection and have their way with it.
Requesting Gravatar... Haacked Mar 26, 2007 9:12 AM
# re: Thread Safety Via Read Only Collections
@Alski - That's a good question. I actually have a follow-on blog post to address that topic.

Adding a US state happens so rarely, that if it did happen, I think it's fine to restart the application in order to reload that collection. In fact, since most of my focus is on web apps, that's not such a big deal.

As for creating "restrictions", it's a matter of perspective. I don't see this as restricting other developers. I see it as signalling my intent.

The fact that there's no Add method lets other devs know that I didn't intend for this collection to be added to and doing so could cause unforeseen problems.

Another example is using the ref keyword in a method when you mean out. In the IL, they're the same, but in C# they signal slightly different intents, which is useful to know when reading the code.
Requesting Gravatar... Haacked Mar 26, 2007 9:15 AM
# re: Thread Safety Via Read Only Collections
@Bill - True. For my second scenario where I get free reign to rewrite the class.

However, in the first scenario, that wouldn't work. If I've already exposed the class to client code as an IList<T>, I can't just change the interface.

Also, users of the property lose the ability to reference a state by index! Quick, what's the 49th state alphabetically?

Console.WriteLine(StateHelper.States[48]);

;)

Requesting Gravatar... Haacked Mar 26, 2007 9:16 AM
# re: Thread Safety Via Read Only Collections
@Thomas, thanks for the clarification! I will note it in the post.
Requesting Gravatar... Bill Pierce Mar 26, 2007 9:46 AM
# re: Thread Safety Via Read Only Collections
Phil,
You've been quite the minutiae maniac lately. So I thought I'd strike back :)

"Quick, what's the 49th state alphabetically?"

You make no guarantee to me via your API that the States have been loaded into the collection alphabetically. So I would have to sort your list myself so I'd have to create my own list/array because I can't sort your list in place because it's read-only!

Your move my friend.
Requesting Gravatar... Haacked Mar 26, 2007 9:56 AM
# re: Thread Safety Via Read Only Collections
Heh heh. Point well taken Bill! You've exposed my hand.

In the implementation of GetAllStates above, I was intending to list the states in alphabetical order. For brevity, I only listed three. The first two, ellipses, the last one.

However all that was in my head. I didn't make that clear. Perhaps I should create a ReadOnlyAlphabeticalCollection. ;)

Even if it wasn't a read only collection, you would want to copy it to a local collection/array and sort it. Otherwise you'd have to lock it before sorting since another thread might be sorting it at the same time in a different manner.
Requesting Gravatar... Haacked Mar 26, 2007 9:57 AM
# re: Thread Safety Via Read Only Collections
@Bill, I forgot to add. "Check!"
Requesting Gravatar... Damien Guard Mar 27, 2007 5:51 AM
# re: Thread Safety Via Read Only Collections
Yes, it's always worth remembering that an interface can inherit from another interface.

IList inherits from ICollection and so the IList interface does contain an Add method even thought it is inherited and not explicitly defined there.

[)amien
Requesting Gravatar... Haacked Mar 27, 2007 10:20 AM
# re: Thread Safety Via Read Only Collections
@Damien: True. I've fired my fact checkers and hired new ones.
;)

What do you have to say?

(will show your gravatar)
Please add 2 and 7 and type the answer here: