Type identity for data is important. How do we refactor collections in the face of Whidbey?
The reality of component design is in the contractual agreements between different components. .NET has certainly stepped in and created an excellent cross component scheme to allow just about any code to interoperate with any other piece of code. In the face of this scheme developers have found that doing things at the truly abstract or generic level is often error prone or doesn't provide the appropriate level of performance. In doing so we often find that components are designed with the most specific types in mind in place of the less specific and more general interfaces that could have been in place.
Type Identity
When a method wants a piece of data there are many options for the exact signature or type the method might accept. A method might take a strongly typed array, it might take the more general Array. Then we have the more useful System.Collection classes, where you can take an IList, ICollection, ArrayList, CollectionBase, or a strongly typed collection based on CollectionBase. Beyond that we have System.Collections.Generic where new generic interfaces/classes are added as well. This is where type identity comes into play:
public void DoSomethingWithData(ArrayList foo);
The above method greatly reduces it's ability to consume data. If you want to consume an Array, you have to create an adapter over top of the array. If you want to consume a CollectionBase or generic collection, you also have to create an adapter. Thankfully ArrayList provided a method for consuming the IList interface, else things would get nasty pretty darn fast.
-
Ideally - All components use the interface methods for maximum compatibility. Where the components use specific types, those types should support an adapter that converts from the interface methods. All collections in turn implement the interfaces to allow easy use or conversion where necessary.
-
Reality - Components like to use very specific methods that often aren't available on the interfaces such as BinarySearch and Sort and so they use specific types for maximum functionality (foregoing interoperability). Adapters based on interfaces often have to throw on type specific methods that can't pass through the interface.
Leveraging Type Identity
Initially it becomes evident that you need to leverage type identity to make your new collections work well with other components. The natural progression is to being deriving types from ArrayList, CollectionBase, and List<T>... No matter how hard you try during this process, there are still three separate type identities involved and your ArrayList derived class will never operate in the place of a CollectionBase class or List<T> class.
Leveraging type identity instantly suffers from every design flaw (note: design flaw here is meant to define any design element that prohibits proper inheritance and that some of the design flaws were in fact design features for very specific CLR performance and security purposes) in the underlying type. Both ArrayList and CollectionBase hide their internal collection in a naive manner assuming inherited classes will provide their own storage. In turn inherited classes are forced to override every single method, since all of the base class methods operate on a private, and thus inaccessible storage element. The newer classes suffer an even more unfortunate fate in that all methods are non-virtual. Again, this is a design flaw/feature in that non-virtual methods are faster and at the same time not extensible.
Whidbey Options
The Whidbey options are to advance up to the System.Collections.Generic namespace and start using the interfaces and classes provided there. Currently there isn't a generic collection base of any sort and no design pattern for implementing your own custom collections. If ArrayList is to List<T> then XXX is to CollectionBase. You'll be hard pressed to fill in the blanks.
List<T> is an awesome class with a bunch of options. Definitely a stud if you ask me. The only problem is the lack of virtual methods so you can't change behavior. I've identified a number of methods that I'd like to see in previous posts, including a list mutator (Generic predicates are pretty powerful, but the FindAll implementation doesn't show it.) and a conditional ForEach method (Stacking the Predicate/Converter functions on top of the generic ForEach method...).
Apparently the current design goals for component developers on Whidbey will be to switch over to generic collections. That I would think is a given with the increased performance and attention to detail placed on the new classes. From a generic perspective we can continue to use the interfaces when designing our components, IList<T> and ICollection<T>, and leverage type identity on the List<T> class assuming we don't have a need to override ANY of the existing functionality. If you want custom algorithms then you can use the generic methods that take new delegates such as Predicate, Action, and Converter, as your first option. You can also continue stringing static methods off of helper classes. That is certainly an option. You can even override the List<T> and string methods off as long as you plan on routing through the List<T>'s public methods to make any collection changes and you are only looking for a new behavior, rather than change existing behavior.
At the end of the day, I wouldn't feel too bad about picking whatever collection option was easiest to implement in my configuration. Can't say that everyone feels the way I do though. If you are thinking about upgrading to strongly typed collections and like the features provided in CollectionBase then you might want to check out a first iteration of a generic version I've blown out. I didn't write this to be functionally equivalent to the non-generic CollectionBase, but instead to provide a starting point from which I'm going to produce a series of hinted collections (DWC.Collections.Generic.CollectionBase).