This post is again going deep down to the bits (writing on high-level topics takes so much more time…).
Suppose (again) you were writing some kind of generic serializer or databinding code. Sooner or later you would have to deal with lists. Collections. Arrays. In other words, you would have to deal with a situation like this:
public class MyObject
{
// …
}public class MyCollection : CollectionBase
{
// …
}public class Data
{
public MyObject[] MyObjectArray { /* … */ }
public MyCollection CollectionOfMyObject { /* … */ }
public IList<MyObject> GenericListOfMyObject { /* … */ }public ArrayList ListOfMyObjects { /* … */ }
public object ThisCouldBeAListOfMyObjects { /* … */ }
}
In order to analyze some arbitrary object (say an instance of Data), you would use either type.GetProperties() (more suited for serializers) or TypeDescriptor.GetProperties(type) (the better choice for databinding and design time related stuff). You would then look at each property’s type, recognize it is a collection type, and somehow deduce the type of the collection elements (to create them dynamically or to read their properties to create list columns during databinding).
Let’s have a look on what our code could be presented with:
- Arrays. They are the most simple collection type, embedded in the language, and are often used by code generation tools. Supporting them is a must.
- Collection classes derived from CollectionBase. MSDN states that
“This base class [CollectionBase] is provided to make it easier for implementers to create a strongly typed custom collection. Implementers should extend this base class instead of creating their own.”
Therefore CollectionBase was the means of choice before we had generics. Please note that this class comes with a pattern that implies type safe methods in the derived class. - Collections implementing ICollection or IList. This is a more generic approach than using CollectionBase. We will have to look closer at this, but if it worked, it would automatically cover the ColectionBase approach.
- Generic collections, implementing ICollection<T> or IList<T>. This is propably the way new code will present collections to our code. Please note that a bunch of methods (like Add, Remove, etc.) that are in the non-generic version part of IList have been pushed down to ICollection<T> in the generic version.
- The predefined collection classes in the System.Collections namespace, notably ArrayList, will also have been used quite often.
- There is a special interface ITypedList, meant to support databinding. This may help (or it may not.)
- Finally we may have to deal with collections that may be present in some untyped property.
Now let’s see which of these cases we can support to what degree:
Arrays: You can check if it’s an array using Type.IsArray and use Type.GetElementType() to get the type of the elements.
➡ Supporting arrays is mandatory and no sweat at all. 100% done.
CollectionBase, ICollection/IList: Neither CollectionBase nor one of the interfaces (also implemented by CollectionBase) tell you something about the element type. The usage of CollectionBase however implies a pattern that will have the implementor support type safe overloads of the usuall collection methods. What we can do is get hold of one of those members (e.g. the Add method or the indexer) and analyze its type.
➡ Supporting arbitrary ICollection classes can be done if they adhere to some pattern (implied by but not restricted to CollectionBase). Let’s call that 90% covered.
ICollection<T>/IList<T>: This case is as easy as arrays are. Well, appart from figuring out the interface. But let’s ignore this exotic cases and settle with, say 99% coverage? Once you got hold of the interface it’s just a matter of calling type.GetGenericArguments().
➡ Supporting generic collections is mandatory and no sweat at all. 99% done.
ArrayList: Here we will raise the white flag. The type of ArrayList does not tell us anything about the element type and no way to get it working. Can we live with that? ArrayList is “not the best choice” as property type, so this restriction might be the encouragement the developer needed to improve his data structures… (allways point out the positive aspects 😉 )
➡ Supporting ArrayList stays at 0%.
ITypedList: ITypedList will give you direct access to the element’s properties (similar to TypeDescriptor.GetProperties(type)). This may be usefull for databinding and design time features — in fact I would regard that as a must, since it is part of the databinding infrastructure of .NET.
For serializers might be used to get the properties and guess the component type (“von hinten durch die Brust ins Auge” — german proverb, literally “from the back through the cest into the eye”, used for arkward indirect ways to achive something). I would consider that only if I absolutely had to.
➡ Supporting ITypedList depends on the purpose of our code. For databinding it should be considered (100% coverage), for serializers it may be a fallback chance, though unreliable. No more than 50% coverage.
Untyped property: No type, no chance to even know it’s a collection.
➡ 0%.
Further complications…
So far we’ve looked at collection types, not at elements. If the collection type does not tell us enough, we may look at the first element in the collection. Asuming that there is one. If not, a serializer might have no problem, yet a databinding scenario might — which is the very reason Microsoft came up with ITypedList.
Another aspect has so far been ignored: We … (OK, I) asumed homogenous collections, i.e. collections of elements of the same type. Collections containing elements of different types (they may have a common base class, or be completely arbitrary) will pose a whole new bunch of problems. This is probably beyond what databinding can support, serializers would have to make sure that each list entry is stored along with type information.
Where are we?
If you take a look at what can be supported and what can’t, you’ll notice that it is simply not possible to cover 100% of the theoretically possible cases. Even some feasible cases will only be covered by 80%. However, if you look closer, those 80% may very well be all you’ll ever need. And if you really stumble over one of the 20% cases (ArrayList might be one of those), don’t try to guess out of the blue; think of some way to feed additional meta information into your serializer.
Leave a Reply