BCL Refresher: Converting the Non-Generic Collections [Inbar Gazit]

As you know generics were only introduced to the .NET Framework in version 2.0. Originally we had a lot of collections in the System.Collections namespace that can store objects. In version 2.0 we added the System.Collections.Generic namespace to support generic collections that can store any specific type.

If you have legacy code or even if you kept using the old non-generic collections for other reasons you may want to consider converting your code to use the generic collections instead. Here are a few reasons why:

  1. Readability and simplicity of your code. Let’s take the case of getting the first string in a list of strings. With generics you would declare List<T> myList = new List<T>(); and then say string firstString = myList[0] just like working with arrays. This syntax is much more simple and readable than what you used to have to write which was ArrayList myList = new ArrayList(); and then string firstString = myList[0] as string;
  2. Performance. Every time you add a value type to a non-generic collection you have to box it to make it an object and every time you retrieve a value type from a non-generic collection you have to unbox it back from object. Even with reference type you still need to cast back to the correct type when retrieving items.
  3. Working against new smaller Framework SKUs. In Silverlight we decided to remove all the concrete non-generic collections completely from the codebase. This is mostly because the Silverlight core managed libraries is set to be the smallest useful set of classes. It’s also possible that other future small frameworks will not have the non-generic collections in store. If you ever plan to write code for those frameworks — you should convert it to use the generic collections.
  4. Better type-safe libraries. If you are developing libraries to be consumed by 3rd parties you should most definitely use generic collections when possible. This would allow consumer of your libraries to quickly figure out what’s expected to be stored in the collections instead of having to guess or figure out from documentation.

OK, so now I convinced you that you should go back and revise your existing code (not to mention any new code you’re writing) to use generic collection but how exactly should you go about it?

Luckily, most of the non-generic collections (with the exception of BitArray) have very good generic counterparts. The following table illustrates what type to replace for what:

Non-generic Generic replacement
ArrayList List<T>
BitArray List<Boolean> [note that this isn’t stored as compactly as BitArray but represents the same information]
CaseInsensitiveComparer Comparer<T>
CaseInsensitiveHashCodeProvider Comparer<T>
CollectionBase Collection<T>
Comparer Comparer<T>
CompatibleComparer Comparer<T>
DictionaryBase KeyedCollection<K,V>
DictionaryEntry KeyValuePair<K,V>
Hashtable Dictionary<K,V>
KeyValuePairs KeyValuePair<K,V>
Queue Queue<T>
ReadOnlyCollectionBase ReadOnlyCollection<T>
SortedList List<T>
Stack Stack<T>

Notice that in most of these cases you would have to figure out what the T is in the generic class. This assumes you used to store just one type of elements in your collection. If you are storing more than one type you should find the common base-class. In the worse case you can use Object but that would lose most of the benefits of generics.

Let’s look at an example:

This is the old code using non-generic Stack:

using System;

using System.Collections;

 

Stack MRUFiles = new Stack();

MRUFiles.Push("first");

MRUFiles.Push("second");

MRUFiles.Push("third");

Console.WriteLine(MRUFiles.Pop() as string);

Console.WriteLine(MRUFiles.Peek() as string);

Console.WriteLine(MRUFiles.Pop() as string);

Console.WriteLine(MRUFiles.Count.ToString());

Same code would look like this using generic Stack<String>:

using System;

using System.Collections.Generic;

 

Stack<string> MRUFiles = new Stack<string>();

MRUFiles.Push("first");

MRUFiles.Push("second");

MRUFiles.Push("third");

Console.WriteLine(MRUFiles.Pop());

Console.WriteLine(MRUFiles.Peek());

Console.WriteLine(MRUFiles.Pop());

Console.WriteLine(MRUFiles.Count.ToString());

Additional considerations when making the change:

  1. List<Boolean> is not a good enough replacement for BitArray when it comes to the use of memory. If you have a list of millions of bits, using a BitArray would have a much smaller memory footprint than List<Boolean> since it’s actually manipulating bits instead of storing each bit as a Boolean that takes 8 bits each. In addition methods such as And, Xor, Not, Or and SetAll will not exist and you’d have to implement them yourself

  2. Dictionary<K,V> indexer behaves differently than Hashtable did when the key is not found. With Hashtable you would get a null whereas with Dictionary you would get a KeyNotFoundException. So, you would probably have to alter your code a bit to catch this exception and add code to handle this case. Note that you can add a null element to the dictionary and still get null but in the case of Hashtable you would not know when you get a null if it’s a case of a missing key or a case of a null element. You can also call the TryGetValue method instead of using a try/catch block and check its return value.

  3. ArrayList.Add() returns the index whereas List.Add returns void. If your legacy code used to rely on the return value you would have to slightly change it to get the index from Count — 1 since you know it’s always inserted last.

  4. ArrayList.ToArray() calls required not only providing the type but also casting the result to the appropriate array. For example: (String[]) m_list.ToArray(typeof(String)); with List<T> you don’t need to worry about this as it’s already in the correct type so you would have to change this to simply be m_list.ToArray();

  5. If you are enumerating over a Hashtable you’ll have to change your code, here is an example:

    BEFORE

    IDictionaryEnumerator de = m_Datastore.GetEnumerator();

    while (de.MoveNext()) {

        ilcc.Datastore[(String)de.Key] = de.Value;

    }

    AFTER

    IEnumerator<KeyValuePair<String, Object>> de = m_Datastore.GetEnumerator();

    while (de.MoveNext()) {

        ilcc.Datastore[de.Current.Key] = de.Current.Value;

    }

  6. Threading/Synchronization issues:

    1. Hashtable.Item property is multiple-reader/single-writer thread safe, but Dictionary’s getters are not. If your application has a dependency on the thread-safety guarantee of this Hashtable property, but you want to convert Hashtable to Dictionary, then you’ll need to handle this case explicitly
    2. Whereas the non-generic collections offered a synchronized wrappers through their APIs — e.g. Hashtable.Synchronized—the generic collections don’t.

    If you want the quick “golden hammer” workaround to the thread safety issues, you can just lock on the SyncRoot of the Dictionary (after casting to ICollection), but this is most likely unnecessarily expensive for your app. But first we’ll first show the golden hammer technique. Note that wrapping every calls to Dictionary this way will perform the equivalent of Hashtable’s #7b behavior but is stronger than Hashtable’s #7a.

    BEFORE

    String myKey = "key";

    String myValue = "value";

    Hashtable myHashtable = new Hashtable();

    Hashtable mySynchronizedHashtable = Hashtable.Synchronized(myHashtable);

    myHashtable.Add(myKey, myValue);

    AFTER

    String myKey = "key";

    String myValue = "value";

    Dictionary<String, String> myDictionary = new Dictionary<String, String>();

    lock (((ICollection)myDictionary).SyncRoot) {

        myDictionary.Add(myKey, myValue);

    }

    The decision to remove the synchronized wrapped from generics was based on the observation that you often want to perform synchronization at a higher level in your application, and that our synchronized wrappers led to a false sense of security (and flawed code). This puts developers in the position of thinking more heavily about synchronization, but we decided this was the right call. This decision is discussed more thoroughly here: https://blogs.msdn.com/bclteam/archive/2005/03/15/396399.aspx.

    As mentioned above, the golden hammer workaround (used with all calls to Dictionary, including reads) is stronger than #7a. If you lock before every call, then you can have at most one reader or writer at a time. However, the Hashtable.Item property is single reader/multiple writer threadsafe. If you want this same behavior, you can use a ReaderWriterLock or, if you have Orcas installed, ReaderWriterLockSlim.

    In general, the best decision for the #7 conversion will depend on the needs of your app: do you want multiple reader/single writer thread safety? Do you want to lock on the Dictionary SyncRoot or a different object, to coordinate accesses among the Dictionary and another collection (discussed in above-mentioned blog)? You can use our suggestions as starting points and customize depending on the needs of your app.