using System.Coffee;

Wednesday, June 21, 2006

Encapsulating Generic Collections with NHibernate

I've been playing a bit with Visual Studio 2005 and the .NET 2.0 Framework lately to get more of a feel for using it. We likely won't be upgrading from .NET 1.1 for our primary application anytime soon, so any learning I'm going to do is going to have to be done on my own as I have time. So far, especially compared with VS2003 and .NET 1.1, this new stuff is definitely the bees knees.

One other nugget of joy that I've been playing with is the new alpha for NHibernate (announced here). So far, I've had no problems other than a few obscure exception messages that took me a while to track down. For an alpha release, it's surprisingly stable. Working with generics, especially within the context of a business layer, is very, very rewarding.

One issue that I've struggled with in the past with business layers is encapsulation of collections. Basically, this means that any collection object that you expose as a public property of some other object should be returned in a read-only form. This prevents clients of the library from modifying the collection's structure without the parent object's knowledge and consent. Check out the section on "Encapsulate Collection" in Martin Fowler's book Refactoring if you're still wondering why this is an admirable quality to shoot for.

Anyway, my first attempt went something like this:

List<Item> items = new List<Item>;
public virtual List<Item> Items {
get { return items.AsReadOnly(); }
}

This ended up not working so well, because NHibernate maps to IList<T> and not to it's List<T> implementation. On to try number 2:

IList<Item> items = new List<Item>;
public virtual IList<Item> Items {
get { return ((List<Item>)items).AsReadOnly(); }
}

This seemed to work a little better. It got me past one of my unit tests anyway. I could insert data all day long like this with no problems. However, when I tried to pull data out, I ended up with an invalid type conversion. The reason for this is that NHibernate swaps out your collection implementation for it's own. This basically means that whatever you do, your collection manipulation methods had better conform to IList<T> and not rely on converting to another type in order to perform some logic, because you'll never know what the actual type of your collection will be until runtime. You only know that it's guaranteed to support the IList<T> interface.

It took me a little while to figure out the ideal solution to this, and I stumbled on it a bit by accident. The thing to do here is to use the System.Collections.ObjectModel.ReadOnlyCollection<T> class. One of its constructors will take an IList<T>, so my code eventually became:

IList<Item> items = new List<Item>;
public virtual IList<Item> Items {
get { return new ReadOnlyCollection<Item>(items); }
}

Now, I can finally save and retrieve objects in NHibernate, and everything is working like a dream. I did want to find out if this was an optimal solution to the problem however, so I fired up my trusty copy of Reflector to see how the .NET Framework guys had implemented List<T>.AsReadOnly(). Turns out, it's exactly what I expected:
public ReadOnlyCollection<T> AsReadOnly() {
return new ReadOnlyCollection<Item>(this); }
}

0 Comments:

Post a Comment

<< Home