Generics, Dictionaries, and More

In this conclusion to a three-part series on generics in C#, you'll learn how to replace hash table objects with their generic counterparts, how to handle constraining type arguments, and more. This article is excerpted from chapter four of the C# 3.0 Cookbook, Third Edition, written by Jay Hilyard and Stephen Teilhet (O'Reilly, 2008; ISBN: 059651610X). Copyright © 2008 O'Reilly Media, Inc. All rights reserved. Used with permission from the publisher. Available from booksellers or direct from O'Reilly Media.

Contributed by
Rating: 3 stars3 stars3 stars3 stars3 stars / 4
December 24, 2008
Rate this Article:
MEH MEH++


SEARCH ASP FREE
TOOLS YOU CAN USE

advertisement

4.9 Replacing the Hashtable with Its Generic Counterpart

Problem

You want to enhance the performance of your application as well as make the code easier to work with by replacing all Hashtable objects with the generic version.

Solution

Replace all occurrences of the System.Collections.Hashtable class with the type-safe generic System.Collections.Generic.Dictionary class.

Here is a simple example of using aSystem.Collections.Hashtableobject:

  public static void UseNonGenericHashtable()
  {
     Console.WriteLine("\r\nUseNonGenericHashtable");

     // Create and populate a Hashtable
     Hashtable numbers = new Hashtable()
         { {1, "one"},"one"}, // Causes a boxing operation to occur for the key
           {2, "two"} }; // Causes a boxing operation to occur for the key

     // Display all key/value pairs in the Hashtable
     // Causes an unboxing operation to occur on each iteration for the key
     foreach (DictionaryEntry de in numbers)
     {
       
Console.WriteLine("Key: " + de.Key + "\tValue: " + de.Value);
     }

     Console.WriteLine(numbers.IsReadOnly);
     Console.WriteLine(numbers.IsFixedSize);
     Console.WriteLine(numbers.IsSynchronized);
     Console.WriteLine(numbers.SyncRoot);

     numbers.Clear();
  }

Here is that same code using aSystem.Collections.Generic.Dictionary<T,U>object:

  public static void UseGenericDictionary()
  {
     Console.WriteLine("\r\nUseGenericDictionary");

     // Create and populate a Dictionary
     Dictionary<int, string> numbers = new Dictionary<int, string>()
         { { 1, "one" }, { 2, "two" } };

     // Display all key/value pairs in the Dictionary
     foreach (KeyValuePair<int, string> kvp in numbers)
     {
       
Console.WriteLine("Key: " + kvp.Key + "\tValue: " + kvp.Value);
     }

     Console.WriteLine(((IDictionary)numbers).IsReadOnly);
     Console.WriteLine(((IDictionary)numbers).IsFixedSize);
     Console.WriteLine(((IDictionary)numbers).IsSynchronized);
     Console.WriteLine(((IDictionary)numbers).SyncRoot);

     numbers.Clear();
  }

Discussion

For simple implementations of the Hashtable in your application, this substitution should be quite easy. However, there are some things to watch out for. For example, the generic Dictionary class does not implement the ICloneable interface, while the Hashtable class does.

Table 4-3 shows the equivalent members that are implemented in both classes.

Table 4-3. Equivalent members in the Hashtable and the generic Dictionary classes

Members in the Hashtable class Equivalent members in the generic Dictionary class
N/A Comparerproperty
Countproperty Countproperty
IsFixedSizeproperty ((IDictionary)myDict).IsFixedSize
IsReadOnlyproperty ((IDictionary)myDict).IsReadOnly
IsSynchronizedproperty ((IDictionary)myDict).IsSynchronized
Itemproperty Itemproperty
Keysproperty Keysproperty
SyncRootproperty ((IDictionary)myDict).SyncRoot
Valuesproperty Valuesproperty
Addmethod Addmethod
Clearmethod Clearmethod
Clonemethod Use overloaded constructor, which accepts anIDictionarytype
Containsmethod ContainsKeymethod
ContainsKeymethod ContainsKeymethod
ContainsValuemethod ContainsValuemethod
CopyTomethod ((ICollection)myDict).CopyTo(arr,0)
Removemethod Removemethod
Synchronizedstatic method lock(myDictionary.SyncRoot) {...}
N/A TryGetValuemethod

In several cases within Table 4-3, there is not a one-to-one correlation between the members of aHashtableand the members of the genericDictionaryclass. Starting with the properties, notice that only theCount,Keys,Values, andItemproperties are present in both classes. To make up for the missing properties in theDictionaryclass, you can perform a cast to anIDictionary. The following code shows how to use these casts to get at the missing properties:

  Dictionary<int, string> numbers = new Dictionary<int, string>();

  Console.WriteLine(((IDictionary)numbers).IsReadOnly);
  Console.WriteLine(((IDictionary)numbers).IsFixedSize);
  Console.WriteLine(((IDictionary)numbers).IsSynchronized);
  Console.WriteLine(((IDictionary)numbers).SyncRoot);

Note that due to the absence of code to be able to return a synchronized version of a genericDictionary, theIsSynchronizedproperty will always returnfalse. TheSyncRootproperty will always return the same object on which it is called. Essentially, this property returns thethispointer. Microsoft has decided to remove the ability to create a synchronous wrapper from any of the generic collection classes.

Instead, they recommend using thelockkeyword to lock the entire collection or another type of synchronization object that suits your needs.

Since theClonemethod is also missing from the genericDictionaryclass (due to the fact that this class does not implement theICloneableinterface), you can instead use the overloaded constructor, which accepts anIDictionary<T,U>type:

  // Create and populate a Dictionary
  Dictionary<int, string> numbers = new
  Dictionary<int, string>()
      { { 1, "one" }, { 2, "two" } };

  // Display all key/value pairs in the original Dictionary.
  foreach (KeyValuePair<int, string> kvp in numbers)
  {
    
Console.WriteLine("Original Key: " + kvp.Key + "\tValue: " + kvp.Value);
  }

  // Clone the Dictionary object.
  Dictionary<int, string> clonedNumbers = new Dictionary<int, string>(numbers);

  // Display all key/value pairs in the cloned Dictionary.
  foreach (KeyValuePair<int, string> kvp in numbers)
  {
     Console.WriteLine("Cloned Key: " + kvp.Key + "\tValue: " + kvp.Value);
  }

There are two more methods that are missing from theDictionaryclass, theContainsandCopyTo methods. TheContainsmethod is easy to reproduce in theDictionaryclass. In theHashtableclass, theContainsmethod and theContainsKey method both exhibit the same behavior; therefore, you can simply use theContainsKeymethod of theDictionaryclass to simulate theContains method of theHashtable class:

  // Create and populate a Dictionary
 
Dictionary<int, string> numbers =
      new Dictionary<int, string>()
      { { 1, "one" }, { 2, "two" } };

  Console.WriteLine("numbers.ContainsKey(1) == " + numbers.ContainsKey(1));
  Console.WriteLine("numbers.ContainsKey(3) == " + numbers.ContainsKey(3));

TheCopyTomethod is also easy to simulate in theDictionaryclass, but it involves a little more work:

  // Create and populate a Dictionary
 
Dictionary<int, string> numbers =
      new Dictionary<int, string>()
      { { 1, "one" }, { 2, "two" } };

  // Display all key/value pairs in the Dictionary.
  foreach (KeyValuePair<int, string> kvp in numbers)
  {
    
Console.WriteLine("Key: " + kvp.Key + "\tValue: " + kvp.Value);
  }
  // Create object array to hold copied information from Dictionary object.
  KeyValuePair<int, string>[] objs = new KeyValuePair<int, string>[numbers.Count];

  // Calling CopyTo on a Dictionary
  // Copies all KeyValuePair objects in Dictionary object to objs[]
  ((IDictionary)numbers).CopyTo(objs, 0);

  // Display all key/value pairs in the objs[].
  foreach (KeyValuePair<int, string> kvp in objs)
  {
    
Console.WriteLine("Key: " + kvp.Key + "\tValue: " + kvp.Value);
  }

CallingCopyToon theDictionaryobject involves setting up an array ofKeyValuePair<T,U>objects, which will end up holding all theKeyValuePair<T,U>objects within theDictionaryobject after theCopyTo method is called. Next, thenumbers Dictionaryobject is cast to anIDictionarytype so that theCopyTomethod may be called. Once theCopyTomethod is called, theobjsarray will contain copies of all theKeyValuePair<T,U>objects that are in the originalnumbersobject. Note that iteration of theobjsarray, using aforeachloop, is done in the same fashion as with thenumbersobject.

See Also

The “System.Collections.Hashtable Class” and “System.Collections.Generic.Dictionary Class” topics in the MSDN documentation.

4.10 Using foreach with Generic Dictionary Types

Problem

You need to enumerate the elements within a type that implements System.Collections.Generic.IDictionary, such as System.Collections.Generic.Dictionary or System.Collections.Generic.SortedList.

Solution

The simplest way is to use the KeyValuePair structure in a foreach loop, as shown here:

  // Create a Dictionary object and populate it
  Dictionary<int, string> myStringDict = new Dictionary<int, string>()
     
{ { 1, "Foo" }, { 2, "Bar" }, { 3, "Baz" } };

  // Enumerate and display all key and value pairs.
  foreach (KeyValuePair<int, string> kvp in myStringDict)
  {

     Console.WriteLine("key   " + kvp.Key);
    
Console.WriteLine("Value " + kvp.Value);
  }

Discussion

The nongeneric System.Collections.Hashtable (the counterpart to the
System.Collections.Generic.Dictionary class), System.Collections.CollectionBase, and System.Collections.SortedList classes supportforeachusing theDictionaryEntrytype, as shown here:

  Hashtable myHashtable = new Hashtable()
     { { 1, "Foo" }, { 2, "Bar" }, { 3, "Baz" } };
  foreach (DictionaryEntry de in myHashtable)
  {
    
Console.WriteLine("key   " + de.Key);
    
Console.WriteLine("Value " + de.Value);
    
Console.WriteLine("kvp " + de.ToString
());
 
}

However, theDictionaryobject supports theKeyValuePair<T,U>type when using aforeachloop. This is due to the fact that theGetEnumeratormethod returns anIEnumerator, which in turn returnsKeyValuePair<T,U>types, notDictionaryEntrytypes.

TheKeyValuePair<T,U>type is well suited to be used when enumerating the genericDictionaryclass with aforeachloop. TheDictionaryEntryobject contains key and value pairs as objects, whereas theKeyValuePair<T,U>type contains key and value pairs as their original types, defined when creating theDictionaryobject. This boosts performance and can reduce the amount of code you have to write, as you do not have to cast the key and value pairs to their original types.

See Also

The “System.Collections.Generic.Dictionary Class,” “System.Collections.Generic. SortedList Class,” and “System.Collections.Generic.KeyValuePair Structure” topics in the MSDN documentation.

4.11 Constraining Type Arguments

Problem

Your generic type needs to be created with a type argument that must support the members of a particular interface such as the IDisposable interface.

Solution

Use constraints to force the type arguments of a generic type to be of a type that implements one or more particular interfaces:

  public class DisposableList<T>: IList<T>
     where T : class, IDisposable
  {
     private List<T>_items = new List<T>();

     // Private method that will dispose of items in the list
     private void Delete(T item)
     {
       
item.Dispose();
     }

     // IList<T>Members
    
public int IndexOf(T item)
    
{
       
return (_items.IndexOf(item));
     }

     public void Insert(int index, T item)
     {
        _items.Insert(index, item);
     }

     public T this[int index]
    
{
        get    {return (_items[index]);}
        set    {_items[index] = value;}
    
}

     public void RemoveAt(int index)
     {
       
Delete(this[index]);
        _items.RemoveAt(index);
     }

     // ICollection<T>Members
     public void Add(T item)
     {
       
_items.Add(item);
     }

     public bool Contains(T item)
     {
        return (_items.Contains(item));
     }

     public void CopyTo(T[] array, int arrayIndex)
     {
        _items.CopyTo(array, arrayIndex);
     }

     public int Count
     {
        get     {return (_items.Count);}
     }

     public bool IsReadOnly
     {
        get     {return (false);}
     }

     // IEnumerable<T>Members
     public IEnumerator<T>GetEnumerator()
     {
       
return (_items.GetEnumerator());
     }

     // IEnumerable Members
     IEnumerator IEnumerable.GetEnumerator()
     {
       
return (_items.GetEnumerator());
     }

     // Other members
     public void Clear()
     {
       
for (int index = 0; index < _items.Count; index++)
        {
           Delete(_items[index]);
        }

        _items.Clear();
     }

     public bool Remove(T item)

     {
        int index = _items.IndexOf(item);

        if (index >= 0)
       
{
           Delete(_items[index]);
           _items.RemoveAt(index);

           return (true);
        }
        else
        {
           return (false);
        }
     }
  }

ThisDisposableListclass allows only an object that implementsIDisposableto be passed in as a type argument to this class. The reason for this is that whenever an object is removed from aDisposableList object, theDisposemethod is always called on that object. This allows you to transparently handle the management of any object stored within thisDisposableListobject.

The following code exercises aDisposableList object:

  public static void TestDisposableListCls()
  {
     DisposableList<StreamReader>dl = new DisposableList<StreamReader>();

     // Create a few test objects.
     StreamReader tr1 = new StreamReader("c:\\boot.ini");
     StreamReader tr2 = new StreamReader("c:\\autoexec.bat");
     StreamReader tr3 = new StreamReader("c:\\config.sys");

     // Add the test object to the DisposableList.
     dl.Add(tr1);
     dl.Insert(0, tr2);
     dl.Add(tr3);

     foreach(StreamReader sr in dl)
     {
        Console.WriteLine("sr.ReadLine() == " + sr.ReadLine());
     }

     // Call Dispose before any of the disposable objects are
     // removed from the DisposableList.
     dl.RemoveAt(0);
     dl.Remove(tr1);
     dl.Clear();
 
}

Discussion

The where keyword is used to constrain a type parameter to accept only arguments that satisfy the given constraint. For example, the DisposableList has the constraint that any type argument T must implement the IDisposable interface:

  public class DisposableList<T>: IList<T>
         where T : IDisposable

This means that the following code will compile successfully:

  DisposableList<StreamReader>dl = new DisposableList<StreamReader>();

but the following code will not:

  DisposableList<string>dl = new DisposableList<string>();

This is because thestring type does not implement theIDisposableinterface, and theStreamReadertype does.

Other constraints on the type argument are allowed, in addition to requiring one or more specific interfaces to be implemented. You can force a type argument to be inherited from a specific base class, such as theTextReaderclass:

  public class DisposableList<T>: IList<T>
        where T : System.IO.TextReader, IDisposable

You can also determine if the type argument is narrowed down to only value types or only reference types. The following class declaration is constrained to using only value types:

  public class DisposableList<T>: IList<T>
         where T : struct

This class declaration is constrained to only reference types:

  public class DisposableList<T>: IList<T>
         where T : class

In addition, you can also require any type argument to implement a public default constructor:

  public class DisposableList<T>: IList<T>
        where T
: IDisposable,
new()

Using constraints allows you to write generic types that accept a narrower set of available type arguments. If the IDisposable constraint is omitted in the Solution for this recipe, a compile-time error will occur. This is because not all of the types that can be used as the type argument for the DisposableList class will implement the IDisposable interface. If you skip this compile-time check, a DisposableList object may contain objects that do not have a public no-argument Dispose method. In this case, a runtime exception will occur. Generics and constraints in particular force strict type checking of the class-type arguments and allow you to catch these problems at compile time rather than at runtime.

See Also

The “where Keyword” topic in the MSDN documentation.

4.12 Initializing Generic Variables to Their Default Values

Problem

You have a generic class that contains a variable of the same type as the type parameter defined by the class itself. Upon construction of your generic object, you want that variable to be initialized to its default value.

Solution

Simply use the default keyword to initialize that variable to its default value:

  public class DefaultValueExample<T>
  {
    
T data = default(T);

     public bool IsDefaultData()
     {
       
T temp = default(T);

        if (temp.Equals(data))
        {
          
return (true);
        }
        else
        {
          
return (false);
        }
     }

     public void SetData(T val)
     {
        data = val;
     }
  }

The code to use this class is shown here:

  public static void ShowSettingFieldsToDefaults()
  {
     DefaultValueExample<int> dv = new DefaultValueExample<int>();

     // Check if the data is set to its default value; true is returned.
     bool isDefault = dv.IsDefaultData();
     Console.WriteLine("Initial data: " + isDefault);

     // Set data.
    
dv.SetData(100);
    
// Check again, this time a false is returned.
    
isDefault = dv.IsDefaultData();
    
Console.WriteLine("Set data: " + isDefault);
  }

The first call toIsDefaultDatareturnstrue, while the second returnsfalse. The output is shown here:

  Initial data: True
  Set data: False

Discussion

When initializing a variable of the same type parameter as the generic class, you cannot just set that variable to null. What if the type parameter is a value type such as an int or char? This will not work because value types cannot be null. You may be thinking that anullabletype such aslong?orNullable<long>can be set tonull(see Recipe 4.6 for more on nullable types). However, the compiler has no way of knowing what type argument the user will use to construct the type.

Thedefaultkeyword allows you to tell the compiler that at compile time the default value of this variable should be used. If the type argument supplied is a numeric value (e.g.,int,long,decimal), then the default value is zero. If the type argument supplied is a reference type, then the default value isnull. If the type argument supplied is astruct, then the default value of thestruct is determined by initializing each member field to its default value.

See Also

Recipe 4.6, and the “default Keyword in Generic Code” topic in the MSDN documentation.

blog comments powered by Disqus
C# ARTICLES

- Beginning C#
- ASP.NET RedirectPermanent Method using C# an...
- C Programming Language and UNIX Pioneer Pass...
- Using Facebook JavaScript SDK in ASP.NET wit...
- ASP.NET Export to Excel and Word using VB.NE...
- WAV and MP3 Streaming with ASP.Net and C#
- Game Programming using SDL: the File I/O API
- C# and Java Developer Jobs on the Rise
- The Future Evolution of C# and VB.NET
- C# If and Else-if Statements
- How To Use the C# String Replace Method
- 5 Ways to Parse XML in C#
- C# Meets Design Patterns
- Coding a CRC-Generating Algorithm in C
- Cyclic Redundancy Check

ASP Web Hosting ASP.Net Web Hosting Windows Web Hosting
ASP Free Forums 
 RSS  Tutorials RSS
 RSS  Forums RSS
 RSS  All Feeds
Site Map 
Request Media Kit
Write For Us Get Paid 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Privacy Policy 
Support 


© 2003-2012 by Developer Shed. All rights reserved. DS Cluster 6 - Follow our Sitemap
Most Popular Topics
All ASP.Net Tutorials