Collective confusion: Using collections in .NET

If you're expecting to find a .NET equivalent of the VB6 Collection, you may be disappointed in .NET's generic collections. Try these alternatives instead.

Q: I’m an old VB6 programmer, and I’m grappling with .NET. I've been particularly confused by the collection types: I can’t seem to locate an equivalent of the VB6 collection. Is there one?

A: The Collection type is a VB6 programmer’s best friend: It’s really the only way to organize sets of objects into a single group, it provides hassle-free enumeration, and it makes it easy to retrieve arbitrary objects from that group. I might as well tell you up front that there is no real equivalent to the VB6 Collection in .NET. What you have instead are quite a few alternatives to a one-size-fits-all solution like VB6’s Collection. In fact, when looking for a .NET collection, the first question you need to ask yourself is, “Do I really need one?”

Try using arrays instead
Remember that all .NET languages are capable of creating arrays of object types. Arrays will generally be more efficient in terms of access speed and memory usage than any collection and in many cases will be all you need. In fact, quite a few of the .NET framework’s generic collection classes are actually backed by Object arrays!

Assuming you have a class Person, in VB.NET, you’d create an array to hold 10 instances of Person with the following code:
Dim Persons(10) As Person

In C#, it would look like this:
Person[] persons = new Person[10];

As I said, in most cases, arrays are superior to collections. The only exceptions to this rule is when:
  • ·        The number of objects can shrink or grow unpredictably.
  • ·        The objects you’ll be storing aren’t of all the same type.
  • ·        You need to perform some processing on the object when it is added to or retrieved from the array.

So you really do need a collection?
If you’ve decided that you definitely need a collection, you need to determine what sort of collection best fits the situation: a collection, a formal list, or a dictionary. Briefly, these collection types break down like this:
  • ·        A collection is simply an ordered group of objects that are retrievable only by enumerating through the sort order. Check out the System.Collections.ICollection interface for a formal definition of a collection.
  • ·        A formal list, on the other hand, allows indexing directly into the underlying collection and also supports enumeration. Formal lists are defined by the System.Collections.IList interface.
  • ·        The dictionary, like a formal list, allows both enumeration and indexing directly into the collection but stores each object along with an associated key value and sorts the objects by those key values. See System.Collections.IDictionary for more on this.

The next best thing
The closest thing you’ll find to a VB6 Collection in .NET is the dictionary. The only difference is that you won’t be able to index into the collection by numeric index as easily as you could with VB’s version. To get identical functionality, you’ll need to create a class that implements both IList and IDictionary.

The .NET Framework provides quite a few generic implementations of each type of collection, and you’ll probably be able to find one that will work in any situation. For a little help in telling them all apart and choosing the correct one, check out “Getting to know the .NET collections.” If you can't find a collection that fits your needs, you always have the option of creating your own by implementing the appropriate interface. Check out the VB.NET source for WidgetList, a formal list collection I created, in Listing A to get a handle on how that’s done. Notice that by implementing IList, WidgetList also implicitly implements ICollection.

One cool thing about WidgetList is that, by virtue of implementing IList, it is for all intents and purposes a collection as far as the .NET runtime is concerned. It can be used interchangeably any place a generic formal list collection could be used, unlike a custom collection in VB6, which is not type-compatible with a VB6 Collection.

A quick word about implementation
An interface is best viewed as a contract between your object and other objects that may use it, meaning that if you are going to implement one method of an interface, you have to implement all of them. At the very least, you should have your implementation throw a NotSupportedException from any methods you do not implement. Also, the Microsoft documentation spells out that certain exceptions should be thrown from certain interface methods under certain circumstances. You should adhere to these guidelines too, as other programmers may depend on catching a particular exception from your class.

Faking it with IEnumerable
Admittedly, creating a collection by implementing one of the collection interfaces is a lot of work. Fortunately, there is a shortcut. You’ll notice from the code in Listing A that my WidgetList class implements not only IList but a second interface, IEnumerator, as well. IEnumerator defines the methods that .NET uses to iterate over a collection. It’s used specifically when you iterate a collection with the for… each construct.

IEnumerator exposes only three methods:
  • ·        Current returns the current item in the collection.
  • ·        MoveNext moves the “current” pointer to the next item in the collection, returning true if that pointer is a valid location in the collection or false if the pointer has moved to the end of the collection.
  • ·        Reset moves the “current” pointer to just before the first item in the collection, so that MoveNext will make the first item current.

The trick here is that any class that implements IEnumerable can be iterated over like a collection, even if it doesn’t implement any of the collection interfaces we talked about before. So any implementing class can provide you with collection-like enumeration (which is one of the big advantages of using a collection), without actually being a collection.

If the .NET collections interfaces have one disadvantage, it’s that anything added to them is cast to type Object. This has some implications for you as a programmer: Object types must be cast back to their original type when you retrieve them, and value types (Integers for example) must be boxed (turned into a reference type). Both of these constraints have minor performance issues and are yet another reason why you should take a long look at using arrays, especially in performance-critical circumstances.


Editor's Picks