Saturday, February 04, 2006

Generic Sets and Collection Wrapper


This simple library is developed for working with NHibernate 1.1 or lower. If there is no benefit of having a generic Set<T> that also implements Iesi.Collections.ISet from NHibnernate, you might be more interested in the C5 collection library or the PowerCollection.

This is a simple extension to JasonSmith's Iesi.Collections to offer a generic version of the ISet and its implementations. Also included are five wrappers which help in using old non-generic collections as generic collections. This could be helpful if you want to wrap a non-generic collection from an old application as generic ones in your application.

Like in .NET 1.1, there is need for a generic ISet in the .NET 2.0 environment. There are already some implementations based on JasonSmith's famous Iesi.Collections.ISet. One of them was found on NHibernate' JIRA which is, according to G77 (thanks!), authored by David Marquam. The implementation of this article is basically a modified version of that implementation. I would like to make it clear that most of the credit of this extension goes to him. I posted this version here so that more people can take advantage of this nice work.

However, David's implementation could not perfectly meet our needs because of several facts:

  1. The ISet<T> in this implementation also inherits the ISet. This inheritance caused the lost of the type safe characteristic that a generic collection normally has.
  2. The ISet<T> does not have the bool Add(T o) method like the ISet has. It only has the void ICollection<T>.Add(T o); the ability to tell whether the item has been actually added might be missed.
  3. The generic extension was included in the assembly of JasonSmith’s Iesi.Collection, which makes some difficulty in working with the original Iesi.Collection.

Using the code

Thus I decided to modify this implementation so that the ISet<T> looks like the following:

public interface ISet<T> : ICollection<T>, ICloneable {…}

In the meantime, I think it could be very helpful if the base Set class still implements the ISet interface since it has been widely applied, including by NHibernate. In this way, you can still have the collection mapped by NHibernate.

public abstract class Set<T> : ISet<T>, ISet {…}

As a reminder, here is the declaration of ISet from Iesi.Collections:

public abstract class ISet : ICollection, ICloneable {…}

For the usage of this ISet<T>, please refer to JasonSmith's article about his Iesi.Collections.

Since Set<T> brought the scenario where a generic collection needs to work with old non-generic collections, I added five simple wrappers:

public struct EnumeratorWrapper<T> : IEnumerator<T>{…}
public class EnumerableWrapper <T> : IEnumerable<T>
public sealed class SetWrapper<T> : ISet<T>
public class CollectionWrapper<T> : EnumerableWrapper<T>, ICollection<T>
public class ListWrapper<T> : EnumerableWrapper<T>, IList<T>

These wrappers support using regular collections under a generic collection interface by wrapping the regular collection as an inner collection and delegate all the functions to it. Also, the Equals() of the wrapper are also overridden to delegate to the wrapped, so that wrapperA.Equals(wrapperB) will return true when and only when wrappedA.Equals(wrappedB) is true. The usage of these wrappers are very simple, here is a sample code:

IList = new ArrayList(3);

ICollection<string> cln = new CollectionWrapper<string>(list);
IEnumerable<string> enl = new EnumerableWrapper<string>(list);
IList<string> lst = new ListWrapper<string>(list);

The Iesi.Collections.Generic is in an independent assembly so that use can have more flexibility with using this together with the Iesi.Collection.Generic.

I will keep working on the implementations since my development greatly relies on them.

Important Notes

  1. This implementation is based on the source code from NHibernate which does not override the Equals method, so the a.Equals(b) in this implementation will only return true if a==b;.
  2. The generic SynchornoizedSet has not been implemented yet.

Latest Update:

Feb 6, 06

I added the Iesi.Collections.Test from Nhibernate1.0.2.0. 67 out of the 87 tests were passed using the generic implementation.

The four ExclusiveOR tests could not be passed before I did a very minor modification to the original Iesi.Collections: in the original Set, the method ExclusiveOr was written as follows:

public static ISet ExclusiveOr(ISet a, ISet b)
if(a == null && b == null)
return null;
else if(a == null)
return (Set)b.Clone();
else if(b == null)
return (Set)a.Clone();
return a.ExclusiveOr(b);

Note that the clones of a and b are unnecessarily down cast to Set. While, in the Union method of this class, these two clones are down cast to ISet. I modify the original code as follows:

return (ISet)b.Clone();
return (ISet)a.Clone();

After this modification, the Iesi.Collection still passes all the Iesi.Collection.Test, and the generic implementation also passes the four ExclusiveOr tests.

Latest update: This small bug of Iesi.Collection.Set has been fixed in the NHibernate version 1.1-alpha1 [ 10081 ], so you don't need to worry about this problem if you are using Iesi.Collection from the later versions of NHibernate.

There are still 16 tests that cannot be passed. They are all operator tests. Since operators can only be used between classes not interfaces, the operator tests down cast the ISet to Set to do the tests, and our generic implementation cannot be downcast to the non-generic Set which causes the failure of the 16 tests.