Share via


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.

Comments

  • Anonymous
    June 25, 2007
    "Whereas the non-generic collections offered a synchronized wrappers through their APIs — e.g. Hashtable.Synchronized—the non-generics don’t." Shouldn't it be generics instead of non-generics in the end of the sentence above?

  • Anonymous
    June 25, 2007
    Hi, I couldn't agree more about the reasons to switch to generic collections. However, it seems like you have forgotten to convert the collections in System.Collections.Specialized. I for one like to use HybridDictionary since it will give me OK performance for both small and large collections and I would love a generic alternative to use. Do you plan to create a System.Collections.Generic.Specialized or System.Collections.Specialized.Generic? Take care, Finnsson

  • Anonymous
    June 25, 2007
    >Additional Considerations ... >3. Hashtable.Add() returns the index No, that seems wrong. Probably a "copy'n'paste" bug from "4. ArrayList.Add()", isn't it? Although, that would be one nice feature to have ... Reminds me of ListDictionary and it's friends in System.Collections.Specialized. Any hints on how to migrate those? Any news on Set<T>?

  • Anonymous
    June 26, 2007
    i think there is no need for a Specialized generic namespace as all of the non-generic classes have their generic equivalent implementations. For the HybridDictionaty it seems straightforward to use a SortedList/SortedDictionary with an initial capacity depending on scenario.

  • Anonymous
    June 26, 2007
    Trackback from http://dotmad.blogspot.com/2007/06/non-generic-to-generic-types-conversion.html

  • Anonymous
    June 26, 2007
    If you're using a VB.NET project then be careful - ordinary collections are indexed from 1 whereas System.Collections.Generic.List is indexed from 0.

  • Anonymous
    June 26, 2007
    Those of you who are already using Silverlight 1.1 Alpha may have seen that the familiar non-generic

  • Anonymous
    June 26, 2007
    Henry, You are correct and the blog was fixed. Also, for the Specialized Collections there are no direct non-generic equivilants and so I ommited them from this post. As for Set<T>, we have HashSet<T> introduced in Orcas see http://blogs.msdn.com/bclteam/archive/2006/11/09/introducing-hashset-t-kim-hamilton.aspx for details.

  • Anonymous
    June 26, 2007
    What about other kinds of set types? There does not seem to be an ISet<T> interface or even an abstract Set<T> to extend.

  • Anonymous
    July 03, 2007
    The paragraph before the last one has the below sentence as "However, the Hashtable.Item property is single reader/multiple writer threadsafe."  Shouldn't the above sentence be rephrase as "However, the Hashtable.Item property is multiple reader/single writer threadsafe."

  • Anonymous
    July 19, 2007
    Silverlight 1.1の基本クラス ライブラリにおける非ジェネリック系のCollectionについて Alpha版で調査をなされていらっしゃるお客さまも多いのではと思いますが、Silverlightのランタイムにおいて非ジェネリック系のコレクション

  • Anonymous
    July 19, 2007
    Alpha版で調査をなされていらっしゃるお客さまも多いのではと思いますが、Silverlightのランタイムにおいて非ジェネリック系のコレクション クラスを取り除く予定でございます。また、ジェネリックのコレクション

  • Anonymous
    July 24, 2007
    The comment has been removed

  • Anonymous
    August 21, 2008
    Bind a Collection to a GridView