Microsoft & .NET.NETUsing Lists and Collections in .NET

Using Lists and Collections in .NET

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Welcome to the next installment of the .NET Nuts & Bolts column. This article outlines the concept of lists and collections in .NET and the current native support that the .NET Framework provides. It touches on a couple of the more common classes in the System.Collections namespace and finishes by exploring why this topic is especially important with the planned inclusion of generics in version 2.0 of the .NET Framework.

System.Collections Namespace

The System.Collections namespace contains a number of different interfaces and classes that define various types of objects, such as lists, hashtables, and dictionaries. Each class has the following attributes:

  • It is a container of sorts that implements different forms of lists or arrays.
  • It has a different implementation that stores or relates the items contained within differently.
  • It has its own scenario where it is applied.

This article focuses on the ArrayList, Hashtable, and CollectionBase, which are some of the more commonly utilized classes.

ArrayList

The array list, as its name implies, is a type very similar to a traditional array. It is simply an improvement in the usability of traditional arrays. Most languages require you to size arrays when you create them. That size is fixed for the lifetime of the array. An ArrayList is an array wrapper that allows the size to dynamically increase as needed. You do not need to know the array’s size at the time when you create the ArrayList, and the array is not limited in size.

ArrayLists are designed to hold objects, which means the contents of the ArrayList can be anything you want because everything derives from System.Object. It has member methods to control adding, clearing, removing, searching, sorting, and trimming to a specific size. The ArrayList supports adding new items into the list through either an Add method that simply adds to the end of the list or an Insert method that adds an item at a specific location.

As you add objects to the ArrayList, it compares the number of elements to the ArrayList’s current capacity (default of 16). If the addition of the new item will exceed the current capacity, the ArrayList’s internal array automatically doubles in size and the current contents are copied into the newly sized internal array. This is important because it has performance implications for your applications. The time it takes to create a new array of doubled size and then traverse the internal array to copy all of the contents into the new internal array is performance overhead. Just because the ArrayList is designed to allow for a dynamic size, you shouldn’t ignore sizing it if you know a relative size you will need. It performs best if you specify a size at the time you create the ArrayList.

ArrayList Sample Code

The following sample code is from a console application (In the interest of brevity, only the relevant code is listed because the majority of the code in the application was automatically generated when the project was created.):

/* * Sample class to add to lists. */private class TestItem{   private int _ItemValue = 0;   public int ItemValue   {      get { return this._ItemValue; }      set { this._ItemValue = value; }   }   public TestItem(int itemValue)   {      this.ItemValue = itemValue;   }}/* * Code to demonstrate using the ArrayList */ArrayList aList = new ArrayList();// Show the default capacityConsole.WriteLine("Default ArrayList capacity: "                  +aList.Capacity.ToString());// Add some numbers to the listfor( int i = 0; i < 20; i++ ){   aList.Add(new TestItem(i));}// Retrieve the enumerator and use it to loop the listIEnumerator listEnumerator = aList.GetEnumerator();while( listEnumerator.MoveNext() ){   Console.WriteLine("{0}", ((TestItem)                    (listEnumerator.Current)).ItemValue);}// Wait so you can read the outputSystem.Console.ReadLine();

Notice how the value in the ArrayList must be cast at the time it is pulled out of the ArrayList. This generally isn’t necessary for basic types (such as int, string, and so forth), but it is for all others.

Hashtable

A Hashtable represents a collection of key-and-value pairs. Whereas arrays are organized according to a sequential numbered index, Hashtables use a different internal storage algorithm based on a hash of the keys provided. Each storage location is often referred to as a bucket. Each key hashes to a unique bucket. Similar to the ArrayList, objects of any desired type can be stored in the Hashtable. It has member methods to control adding, clearing, removing, searching, and safely checking whether a key or value is already contained within the Hashtable.

Hashtable Sample Code

One of the common uses for Hashtables is enabling a specified identifier to quickly retrieve items. Examples could be phone numbers, Social Security numbers, database identity columns, and so on. The following sample code demonstrates the use of a Hashtable. It simply uses a random number generator to create identities, checks to see whether they exist, and, if not, adds them to the Hashtable:

/* * Code to demonstrate using the Hashtable */Hashtable hash = new Hashtable();int key = 0;Random randomKey = new Random(1);// Add some numbers to the listfor( int i = 0; i < 20; i++ ){   key = randomKey.Next();   if( !hash.ContainsKey( key ) )   {      hash.Add(key, new TestItem(i));   }   else   {      Console.WriteLine("Key {0} already exists.", key);   }}// Display the contents of the HashtableSystem.Console.WriteLine("Number of Items: " + hash.Count);IDictionaryEnumerator listEnumerator = hash.GetEnumerator();while ( listEnumerator.MoveNext() ){   Console.WriteLine("t{0}:t{1}",       listEnumerator.Key,      ((TestItem)(listEnumerator.Value)).ItemValue);}// Wait so you can read the outputSystem.Console.ReadLine();

The random number generator likely won’t produce any clashes with such a short loop, but it demonstrates the concept. The sample code assumes reuse of the TestItem class and as with the previous example, the code is limited to what is not generated by default in a console application.

CollectionBase

A CollectionBase is similar to ArrayLists and Hashtables in that it is a list. However, a CollectionBase is an abstract class that allows you to define type-specific lists. For example, if you wanted a list of just TestItem from the previous sample code, you would use the CollectionBase as the base class for your object and then create the appropriate methods or override the desired functionality.

CollectionBase Sample Code

The following sample defines a list that has TestItem as the specific type. It includes sample methods for add/remove functionality:

/* * Sample collection that is specific to TestItem type. */private class TestItemCollection : CollectionBase{   public TestItem this[ int index ]   {      get { return( (TestItem) List[index] ); }      set { List[index] = value; }   }   public int Add( TestItem itemValue )   {      return( List.Add( itemValue ) );   }   public void Remove( TestItem itemValue )   {      List.Remove( itemValue );   }}/* * Code to demonstrate using our TestItemCollection */TestItemCollection testCollection = new TestItemCollection();// Add some numbers to the listfor( int i = 0; i < 20; i++ ){   testCollection.Add(new TestItem(i));}IEnumerator listEnumerator = testCollection.GetEnumerator();while( listEnumerator.MoveNext() ){   Console.WriteLine("{0}", ((TestItem)                    (listEnumerator.Current)).ItemValue);}// Wait so you can read the outputSystem.Console.ReadLine();

Generics

If you recall, the ArrayList, Hashtable, and other list types support use of any type of object. This is great in terms of flexibility, but can easily lead to logic mistakes and performance issues related to the overhead associated with boxing and unboxing types. Because any type can be used, there is no type checking at compile time to enforce consistent storage of the same type within the list. Thus, along with your simple TestItem class, you could also mistakenly add a DataSet or some other type of object to the same ArrayList or Hashtable and it would not cause a compile error. In most scenarios, the same type of object is used within a list and not different types. Meaning, rarely would you want to put a TestItem and a DataSet into the same list. This can lead to logic errors that take time to track down and debug.

In the 1.0 and 1.1 versions of the Microsoft .NET Framework, the workaround for the above problems was to create your own wrappers around ArrayList, Hashtable, and the like, and limit the specific types that can be assigned. Another solution would be to create your own type-specific class with CollectionBase as the base class. For example, you’d have to create your own TestItemArrayList that behaves like an ArrayList but allows only the methods to accept TestItem types. This is not ideal because you would have to create a class for each specific type you need, which could lead to a lot of tedious code. Even if you use a code generator to do it for you, this still generates a lot of code that you ultimately need to maintain.

To help solve these problems, the Microsoft .NET Framework version 2.0 will include generics. Many developers with C++ experience recognize generics as what is known in C++ as templates. A generic is a type-safe class that is declared without a specific type applied to it in the definition. Rather, the type is specified at the time the object is used. In the code examples in this article, that would be equivalent to specifying TestItem as the type at the time you declare your ArrayList or Hashtable variables. The effect is that it limits that particular item from holding any type other than a TestItem. The code will not compile if you attempt to assign something such as a DataSet. This helps to easily avoid those costly logic errors and supports a high degree of code reuse.

Generics have other performance advantages as well, but I’ll save those for another day. Find an introduction to generics with sample code on MSDN.

Possible Enhancements

In the past, creating type-safe wrappers for objects was handy. With the impending inclusion of generics, I recommend evaluating your timeline against the release of version 2.0 of the .NET Framework to determine whether you need them now or whether you can just wait. For those who already have their own type-safe wrappers, you probably should start planning to replace them with generics for the performance and maintenance benefits.

Future Columns

I’ve yet to determine the topic of the next column. If you have something in particular that you would like to see explained, e-mail me at mstrawmyer@crowechizek.com.

About the Author

Mark Strawmyer, MCSD, MCSE, MCDBA is a Senior Architect of .NET applications for large and mid-size organizations. Mark is a technology leader with Crowe Chizek in Indianapolis, Indiana. He specializes in architecture, design, and development of Microsoft-based solutions. Mark was honored to be named a Microsoft MVP for application development with C#. You can reach Mark at mstrawmyer@crowechizek.com.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories