O'Reilly logo

C# 3.0 Cookbook, 3rd Edition by Stephen Teilhet, Jay Hilyard

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 4. Generics

4.0. Introduction

Generics are an extremely useful feature that allows you to write type safe and efficient collection-and pattern-based code. This aspect of generics is described in Recipes 4.1 and 4.2. With generics comes quite a bit of programming power, but with that power comes the responsibility to use it correctly. If you are considering converting your ArrayList, Queue, Stack, and Hashtable objects to use their generic counterparts, consider reading Recipes 4.3, 4.4, and 4.9. As you will read, the conversion is not always simple and easy, and there are reasons why you might not want to do this conversion at all.

Some recipes in this chapter, such as Recipe 4.5, deal with other generic classes contained in the .NET Framework. Still, others deal with the operation of any generic type; see Recipes 4.1, 4.7, and 4.11.

4.1. Deciding When and Where to Use Generics

Problem

You want to use generic types in a new project or convert nongeneric types in an existing project to their generic equivalent. However, you do not really know why you would want to do this, and you do not know which nongeneric types should be converted to be generic.

Solution

In deciding when and where to use generic types, you need to consider several things:

  • Will your type contain or be operating on various unspecified data types (e.g., a collection type)? If so, creating a generic type will offer several benefits over creating a nongeneric type. If your type will operate on only a single specific type, then you may not need to create a generic type.

  • If your type will be operating on value types, so that boxing and unboxing operations will occur, you should consider using generics to prevent the boxing and unboxing operations.

  • The stronger type checking associated with generics will aid in finding errors sooner (i.e., during compile time as opposed to runtime), thus shortening your bug-fixing cycle.

  • Is your code suffering from "code bloat," with you writing multiple classes to handle different data types on which they operate (e.g., a specialized ArrayList that stores only StreamReaders and another that stores only StreamWriters)? It is easier to write the code once and have it just work for each of the data types it operates on.

  • Generics allow for greater clarity of code. By eliminating code bloat and forcing stronger type checking on your types, your code will be easier to read and understand.

Discussion

In most cases, your code will benefit from using a generic type. Generics allow for more efficient code reuse, faster performance, stronger type checking, and easier-toread code.

See Also

The "Generics Overview" and "Benefits of Generics" topics in the MSDN documentation.

4.2. Understanding Generic Types

Problem

You need to understand how the .NET types work for generics and how Generic .NET types differ from regular .NET types.

Solution

A couple of quick experiments can show the differences between regular .NET types and generic .NET types. When a regular .NET type is defined, it looks like the FixedSizeCollection type defined in Example 4-1.

Example 4-1. FixedSizeCollection: a regular .NET type

public class FixedSizeCollection
{
    /// <summary>
    /// Constructor that increments static counter
    /// and sets the maximum number of items
    /// and sets the maximum number of items
    /// and sets the maximum number of items
    /// </summary>
    /// <param name="maxItems"></param>
    public FixedSizeCollection(int maxItems)
    {
         FixedSizeCollection.InstanceCount++;
         this.Items = new object[maxItems];
    }
    /// <summary>
    /// Add an item to the class whose type
    /// is unknown as only object can hold any type
    /// </summary>
    /// <param name="item">item to add</param>
    /// <returns>the index of the item added</returns>
    public int AddItem(object item)
    {
        if (this.ItemCount < this.Items.Length)
        {
            this.Items[this.ItemCount] = item;
            return this.ItemCount++;
        }
        else
            throw new Exception("Item queue is full");
    }

    /// <summary>
    /// Get an item from the class
    /// </summary>
    /// <param name="index">the index of the item to get</param>
    /// <returns>an item of type object</returns>
    public object GetItem(int index)
    {
        if (index >= this.Items.Length &&
            index >= 0)
            throw new ArgumentOutOfRangeException("index");
        return this.Items[index];
     }

     #region Properties 
     /// <summary> 
     /// Static instance counter hangs off of the Type for 
     /// StandardClass 
     /// </summary> 
     public static int InstanceCount { get; set; }

     /// <summary>
     /// The count of the items the class holds 
     /// </summary>
     public int ItemCount { get; private set; }

     /// <summary> 
     /// The items in the class 
     /// </summary>
     private object[] Items { get; set; } 
     #endregion // Properties

     /// <summary> 
     /// ToString override to provide class detail 
     /// </summary> 
     /// <returns>formatted string with class details</returns>
     public override string ToString() 
     {

         return "There are " + FixedSizeCollection.InstanceCount.ToString() +
             " instances of " + this.GetType().ToString() + 
             " and this instance contains " + this.ItemCount + " items...";
     }
}

FixedSizeCollection has a static integer property variable, InstanceCount, which is incremented in the instance constructor, and a ToString() override that prints out how many instances of FixedSizeCollection exist in this AppDomain.FixedSizeCollection also contains an array of objects(Items), the size of which is determined by the item count passed in to the constructor. It implements methods to add and retrieve items (AddItem, GetItem) and a read-only property to get the number of items currently in the array (ItemCount).

The FixedSizeCollection<T> type is a generic .NET type with the same static property InstanceCount field, the instance constructor that counts the number of instantiations, and the overridden ToString() method to tell you how many instances there are of this type. FixedSizeCollection<T> also has an Items array property and methods corresponding to those in FixedSizeCollection, as you can see in Example 4-2.

Example 4-2. FixedSizeCollection<T>: a generic .NET type

/// <summary>
/// A generic class to show instance counting
/// </summary>
/// <typeparam name="T">the type parameter used for the array storage</typeparam>
public class FixedSizeCollection<T>
{
    /// <summary> 
    /// Constructor that increments static counter and sets up internal storage 
    /// </summary> 
    /// <param name="items"></param> 
    public FixedSizeCollection(int items) 
    {
        FixedSizeCollection<T>.InstanceCount++; 
        this.Items = new T[items]; 
    }

    /// <summary> 
    /// Add an item to the class whose type 
    /// is determined by the instantiating type 
    /// </summary> 
    /// <param name="item">item to add</param> 
    /// <returns>the zero-based index of the item added</returns>
    public int AddItem(T item) 
    {
        if (this.ItemCount < this.Items.Length)
        {
            this.Items[this.ItemCount] = item; 
            return this.ItemCount++;
        } 
        else
            throw new Exception("Item queue is full"); 
    }

    /// <summary> 
    /// Get an item from the class 
    /// </summary> 
    /// <param name="index">the zero-based index of the item to get</param> 
    /// <returns>an item of the instantiating type</returns> 
    public T GetItem(int index) 
    {
        if (index >= this.Items.Length && 
            index >= 0) 
            throw new ArgumentOutOfRangeException("index");

        return this.Items[index]; 
    }

    #region Properties 
    /// <summary> 
    /// Static instance counter hangs off of the 
    /// instantiated Type for 
    /// GenericClass 
    /// </summary>
    public static int InstanceCount { get; set; }

    /// <summary> 
    /// The count of the items the class holds 
    /// </summary>
    public int ItemCount { get; private set; }

    /// <summary> 
    /// The items in the class 
    /// </summary> 
    private T[] Items { get; set; }
    #endregion // Properties

    /// <summary>
    /// ToString override to provide class detail
    /// </summary>
    /// <returns>formatted string with class details</returns>
    public override string ToString()
    {

        return "There are " + FixedSizeCollection<T>.InstanceCount.ToString() +
            " instances of " + this.GetType().ToString() +
            " and this instance contains " + this.ItemCount + " items...";
    }
}

Things start to get a little different with FixedSizeCollection<T> when you look at the Items array property implementation. The Items array is declared as:

	private T[] Items { get; set; }

instead of:

	private object[] Items { get; set; }

The Items array property uses the type parameter of the generic class (<T>) to determine what type of items are allowed. FixedSizeCollection uses object for the Items array property type, which allows any type to be stored in the array of items (since all types are convertible to object), while FixedSizeCollection<T> provides type safety by allowing the type parameter to dictate what types of objects are permitted. Notice also that the properties have no associated private backing field declared for storing the array. This is an example of using the new Automatically Implemented Properties in C# 3.0. Under the covers, the C# compiler is creating a storage element of the type of the property, but you don't have to write the code for the property storage anymore if you don't have specific code that has to execute when accessing the properties. To make the property read-only, simply mark the set; declaration private.

The next difference is visible in the method declarations of AddItem and GetItem. AddItem now takes a parameter of type T, whereas in FixedSizeCollection, it took a parameter of type object. GetItem now returns a value of type T, whereas in FixedSizeCollection, it returned a value of type object. These changes allow the methods in FixedSizeCollection<T> to use the instantiated type to store and retrieve the items in the array, instead of having to allow any object to be stored as in FixedSizeCollection:

	/// <summary>
	/// Add an item to the class whose type
	/// is determined by the instantiating type
	/// </summary>
	/// <param name="item">item to add</param>
	/// <returns>the zero-based index of the item added</returns>
	public int AddItem(T item)
	{
	    if (this.ItemCount < this.Items.Length) 
	    {
	        this.Items[this.ItemCount] = item;
	        return this.ItemCount++;
	    }
	    else
	        throw new Exception("Item queue is full"); 
	}

	/// <summary> 
	/// Get an item from the class 
	/// </summary> 
	/// <param name="index">the zero-based index of the item to get</param> 
	/// <returns>an item of the instantiating type</returns>
	public T GetItem(int index) 
	{

	    if (index >= this.Items.Length &&
	        index >= 0) 
	        throw new ArgumentOutOfRangeException("index");

	    return this.Items[index]; 
	}

This provides a few advantages. First and foremost is the type safety provided by FixedSizeCollection<T> for items in the array. It was possible to write code like this in FixedSizeCollection:

	// Regular class 
	FixedSizeCollection C = new FixedSizeCollection(5); 
	Console.WriteLine(C);

	string s1 = "s1"; 
	string s2 = "s2"; 
	string s3 = "s3"; 
	int i1 = 1;

	// Add to the fixed size collection (as object). 
	C.AddItem(s1); 
	C.AddItem(s2); 
	C.AddItem(s3); 
	// Add an int to the string array, perfectly OK. 
	C.AddItem(i1);

But FixedSizeCollection<T> will give a compiler error if you try the same thing:

	// Generic class 
	FixedSizeCollection<string> gC = new FixedSizeCollection<string>(5); 
	Console.WriteLine(gC);

	string s1 = "s1"; 
	string s2 = "s2"; 
	string s3 = "s3"; 
	int i1 = 1;
	// Add to the generic class (as string). 
	gC.AddItem(s1); 
	gC.AddItem(s2); 
	gC.AddItem(s3); 
	// Try to add an int to the string instance, denied by compiler. 
	// error CS1503: Argument '1': cannot convert from 'int' to 'string' 
	//gC.AddItem(i1);

Having the compiler prevent this before it can become the source of runtime bugs is a very good thing.

It may not be immediately noticeable, but the integer is actually boxed when it is added to the object array in FixedSizeCollection, as you can see in the IL for the call to GetItem on FixedSizeCollection:

	IL_0170: ldloc.2 
	IL_0171: ldloc.s i1
	IL_0173: box [mscorlib]System.Int32
	IL_0178: callvirt instance int32 
	       CSharpRecipes.Generics/FixedSizeCollection::AddItem(object)

This boxing turns the int, which is a value type, into a reference type (object) for storage in the array. This causes extra work to be done to store value types in theobject array.

There is a problem when you go to get an item back from the class in the FixedSizeCollection implementation. Take a look at how FixedSizeCollection.GetItem retrieves an item:

	// Hold the retrieved string.
	string sHolder;

	// Have to cast or get error CS0266:
	// Cannot implicitly convert type 'object' to 'string'
	sHolder = (string)C.GetItem(1);

Since the item returned by FixedSizeCollection.GetItem is of type object, it needs to be cast to a string in order to get what you hope is a string for index 1. It may not be a string—all you know for sure is that it's an object—but you have to cast it to a more specific type coming out so you can assign it properly.

These are both fixed by the FixedSizeCollection<T> implementation. The unboxing is addressed; no unboxing is required, since the return type of GetItem is the instantiated type, and the compiler enforces this by looking at the value being returned:

	// Hold the retrieved string.
	string sHolder;
	int iHolder;

	// No cast necessary
	sHolder = gC.GetItem(1);

	// Try to get a string into an int.
	// error CS0029: Cannot implicitly convert type 'string' to 'int'
	//iHolder = gC.GetItem(1);

In order to see one other difference between the two types, instantiate a few instances of each of them like so:

	// Regular class 
	FixedSizeCollection A = new FixedSizeCollection(5);
	Console.WriteLine(A); 
	FixedSizeCollection B = new FixedSizeCollection(5);
	Console.WriteLine(B); 
	FixedSizeCollection C = new FixedSizeCollection(5);
	Console.WriteLine(C);	

	// generic class 
	FixedSizeCollection<bool> gA = new FixedSizeCollection<bool>(5);
	Console.WriteLine(gA); 
	FixedSizeCollection<int> gB = new FixedSizeCollection<int>(5);
	Console.WriteLine(gB); 
	FixedSizeCollection<string> gC = new FixedSizeCollection<string>(5);
	Console.WriteLine(gC); 
	FixedSizeCollection<string> gD = new FixedSizeCollection<string>(5);
	Console.WriteLine(gD);

The output from the preceding code shows this:

	There are 1 instances of CSharpRecipes.Generics+FixedSizeCollection and this ins
	tance contains 0 items...
	There are 2 instances of CSharpRecipes.Generics+FixedSizeCollection and this ins
	tance contains 0 items...
	There are 3 instances of CSharpRecipes.Generics+FixedSizeCollection and this ins
	tance contains 0 items...
	There are 1 instances of CSharpRecipes.Generics+FixedSizeCollection'1[System.Boo
	lean] and this instance contains 0 items...
	There are 1 instances of CSharpRecipes.Generics+FixedSizeCollection'1[System.Int
	32] and this instance contains 0 items...
	There are 1 instances of CSharpRecipes.Generics+FixedSizeCollection'1[System.Str
	ing] and this instance contains 0 items...
	There are 2 instances of CSharpRecipes.Generics+FixedSizeCollection'1[System.Str
	ing] and this instance contains 0 items...

Discussion

The type parameters in generics allow you to create type-safe code without knowing the final type you will be working with. In many instances, you want the types to have certain characteristics, in which case you place constraints on the type (see Recipe 4.11). Methods can have generic type parameters whether the class itself does or does not.

Notice that while FixedSizeCollection has three instances, FixedSizeCollection, has one instance in which it was declared with bool as the type, one instance in which int was the type, and two instances in which string was the declaring type. This means that, while there is one .NET Type object created for each nongeneric class, there is one .NET Type object for every constructed type of a generic class.

FixedSizeCollection has three instances in the example code because FixedSizeCollection has only one type that is maintained by the CLR. With generics, one type is maintained for each combination of the class template and the type arguments passed when constructing a type instance. To make it clearer, you get one .NET type for FixedSizeCollection<bool>, one .NET type for FixedSizeCollection<int>, and a third .NET type for FixedSizeCollection<string>.

The static InstanceCount property helps to illustrate this point, as static properties of a class are actually connected to the type that the CLR hangs on to. The CLR creates any given type only once and then maintains it until the AppDomain unloads. This is why the output from the calls to ToString() on these objects shows that the count is three for FixedSizeCollection (as there is truly only one of these) and between one and two for the FixedSizeCollection<T> types.

See Also

The "Generic Type Parameters" and "Generic Classes" topics in the MSDN documentation.

4.3. Replacing the ArrayList 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 ArrayList objects with the generic version. This is imperative when you find that structures or other value types are being stored in these data structures, resulting in boxing/unboxing operations.

Solution

Replace all occurrences of the System.Collection.ArrayList class with the more efficient generic System.Collections.Generic.List class.

Here is a simple example of using a System.Collections.ArrayList object:

	public static void UseNonGenericArrayList()
	{
	    // Create and populate an ArrayList.
	    ArrayList numbers = new ArrayList();
	    numbers.Add(1);// Causes a boxing operation to occur
	    numbers.Add(2);// Causes a boxing operation to occur

	    // Display all integers in the ArrayList.
	    // Causes an unboxing operation to occur on each iteration
	    foreach (int i in numbers)
	    {
	        Console.WriteLine(i);
	    }

	    numbers.Clear(); 
	}

Here is that same code using a System.Collections.Generic.List object:

	public static void UseGenericList()
	{
	    // Create and populate a List.
	    List<int> numbers = new List<int>();
	    numbers.Add(1);
	    numbers.Add(2);

	    // Display all integers in the ArrayList. 
	    foreach (int i in numbers) 
	    {
	        Console.WriteLine(i); 
	    }

	    numbers.Clear(); 
	}

Discussion

Since ArrayLists are used in almost all applications, it is a good place to start to enhance the performance of your application. For simple implementations of the ArrayList in your application, this substitution should be quite easy.

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

Table 4-1. Equivalent members in the ArrayList and the generic List classes

Members in the ArrayList class

Equivalent members in the generic List class

Capacity property

Capacity property

Count property

Count property

IsFixedSize property

((IList)myList).IsFixedSize

IsReadOnly property

((IList)myList).IsReadOnly

IsSynchronized property

((IList)myList).IsSynchronized

Item property

Item property

SyncRoot property

((IList)myList).SyncRoot

Adapter static method

N/A

Add method

Add method

AddRange method

AddRange method

N/A

AsReadOnly method

BinarySearch method

BinarySearch method

Clear method

Clear method

Clone method

GetRange(0, numbers.Count)

Contains method

Contains method

N/A

ConvertAll method

CopyTo method

CopyTo method

N/A

Exists method

N/A

Find method

N/A

FindAll method

N/A

FindIndex method

N/A

FindLast method

N/A

FindLastIndex method

N/A

ForEach method

FixedSize static method

N/A

GetRange method

GetRange method

IndexOf method

IndexOf method

Insert method

Insert method

InsertRange method

InsertRange method

LastIndexOf method

LastIndexOf method

ReadOnly static method

AsReadOnly method

Remove method

Remove method

N/A

RemoveAll method

RemoveAt method

RemoveAt method

RemoveRange method

RemoveRange method

Repeat static method

Use a for loop and the Add method

Reverse method

Reverse method

SetRange method

InsertRange method

Sort method

Sort method

Synchronized static method

lock(myList.SyncRoot) {…}

ToArray method

ToArray method

N/A

TrimExcess method

TrimToSize method

TrimToSize method

N/A

TrueForAll method

In several cases within Table 4-1, there is not a one-to-one correlation between the members of an ArrayList and the members of the generic List class. Starting with the properties, notice that only the Capacity, Count, and Item properties are present in both classes. To make up for the missing properties in the List class, you can perform a cast to an IList. The following code shows how to use these casts to get at the missing properties:

	List<int> numbers = new List<int>();

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

Note that due to the absence of code that returns a synchronized version of a generic List and the absence of code that returns a fixed-size generic List, the IsFixedSize and IsSynchronized properties will always return false. The SyncRoot property will always return the same object on which it is called. Essentially, this property returns the this pointer. Microsoft has decided to remove the ability to create a synchronous wrapper from any of the generic collection classes. Instead, they recommend using the lock keyword to lock the entire collection or another type of synchronization object that suits your needs.

The ArrayList has several static methods to which there is no direct equivalent method in the generic List class. To fix this, you have to do a little work. The closest match for the static ArrayList.ReadOnly method is the AsReadOnly instance method of the generic List class. This makes for a fairly simple substitution.

The static ArrayList.Repeat method has no direct equivalent in the generic List class. So instead, you can use the following generic extension method:

	public static void Repeat<T>(this List<T> list, T obj, int count)
	{
	    if (count < 0) 
	    {
	        throw (new ArgumentException(
	                "The count parameter must be greater or equal to zero.")); 
	    }

	    for (int index = 0; index < count; index++) 
	    {
	        list.Add(obj); 
	    }
	}

This generic extension method has three parameters:

list

Marks this method as an extension method for List<T>.

obj

The object that will be added to the generic List object a specified number of times.

count

The number of times to add the object contained in obj to the generic List object.

Since the Clone method is also missing from the generic List class (due to the fact that this class does not implement the ICloneable interface), you can instead use the GetRange method of the generic List class:

	List<int> oldList = new List<int>();
	// Populate oldList...

	List<int> newList = oldList.GetRange(0, oldList.Count);

The GetRange method performs a shallow copy (similar to the Clone method of the ArrayList) of a range of elements in the List object. In this case, the range of elements includes all elements.

See Also

The "System.Collections.ArrayList Class" and "System.Collections.Generic.List Class" topics in the MSDN documentation.

4.4. Replacing the Stack and Queue with Their Generic Counterparts

Problem

You want to enhance the performance of your application as well as make the code easier to work with by replacing all Stack and Queue objects with their generic versions. This is imperative when you find that structures or other value types are being stored in these data structures, resulting in boxing/unboxing operations.

Solution

Replace all occurrences of the System.Collections.Stack and System.Collection.Queue objects with the System.Collections.Generic.Stack and System.Collection.Generic.Queue objects.

Here is a simple example of using a System.Collections.Queue object:

	public static void UseNonGenericQueue()
	{

	    // Create a non-generic Queue object.
	    Queue numericQueue = new Queue();

	    // Populate Queue (causing a boxing operation to occur). 
	    numericQueue.Enqueue(1); 
	    numericQueue.Enqueue(2); 
	    numericQueue.Enqueue(3);

	    // De-populate Queue and display items (causing an unboxing operation to
occur)

	    Console.WriteLine(numericQueue.Dequeue());
	    Console.WriteLine(numericQueue.Dequeue());        
	    Console.WriteLine(numericQueue.Dequeue().ToString()); 
	}

Here is that same code using a System.Collections.Generic.Queue object:

	public static void UseGenericQueue()
	{ 
	    // Create a generic Queue object.        
	    Queue<int> numericQueue = new Queue<int>();

	    // Populate Queue. 
	    numericQueue.Enqueue(1);
	    numericQueue.Enqueue(2);
	    numericQueue.Enqueue(3);

	    // De-populate Queue and display items.
	    Console.WriteLine(numericQueue.Dequeue());
	    Console.WriteLine(numericQueue.Dequeue());
	    Console.WriteLine(numericQueue.Dequeue());
	}

Here is a simple example of using a System.Collections.Stack object:

	public static void UseNonGenericStack()
	{ 
	    // Create a non-generic Stack object.
	    Stack numericStack = new Stack();

	    // Populate Stack (causing a boxing operation to occur). 
	    numericStack.Push(1);
	    numericStack.Push(2);
	    numericStack.Push(3);

	    // De-populate Stack and display items (causing an unboxing operation to
occur).
	    Console.WriteLine(numericStack.Pop().ToString());
	    Console.WriteLine(numericStack.Pop().ToString());
	    Console.WriteLine(numericStack.Pop().ToString());
	}

Here is that same code using a System.Collections.Generic.Stack object:

	public static void UseGenericStack()
	{ 

	    // Create a generic Stack object.        
	    Stack<int> numericStack = new Stack<int>();

	    // Populate Stack.
	    numericStack.Push(1);
	    numericStack.Push(2);
	    numericStack.Push(3);

	    // De-populate Stack and display items.
	    Console.WriteLine(numericStack.Pop().ToString());
	    Console.WriteLine(numericStack.Pop().ToString());
	    Console.WriteLine(numericStack.Pop().ToString());
	}

Discussion

On the surface, the generic and nongeneric Queue and Stack classes seem similar enough. However, it is a very different story underneath the surface. The basic use of the generic Queue and Stack objects are the same as with their nongeneric counter-parts, except for the syntax used to instantiate the objects. The generic form requires a type argument in order to create the type. The type argument in this example is an int. This type argument indicates that this Queue or Stack object will be able to contain only integer types, as well as any type that implicitly converts to an integer, such as a short:

	short s = 300; 
	numericQueue.Enqueue(s);     // OK, because of the implicit conversion

However, a type that cannot be implicitly converted to an integer, such as a double, will cause a compile-time error:

	double d = 300; 
	numericQueue.Enqueue(d);         // Error, no implicit conversion available 
	numericQueue.Enqueue((int)d);    // OK, because of the explicit cast

The nongeneric form does not require this type argument, because the nongeneric Queue and Stack objects are allowed to contain any item as an element because all items are convertible to type Object.

When choosing between a generic and nongeneric Queue or Stack, you need to decide whether you wish to use a generic Queue or Stack object or a nongeneric Queue or Stack object. Choosing the generic Queue or Stack class over its nongeneric form gives you many benefits, including:

Type-safety

Each element contained in the data structure is typed to one specific type. This means no more casting of objects when they are added to or removed from the data structure. You cannot store multiple disparate types within a single data structure; you always know what type is stored within the data structure. Type checking is done at compile time rather than runtime. This boils down to writing less code, achieving better performance, and making fewer errors.

Shortened development time

To make a type-safe data structure without using generics means having to sub-class the System.Collections.Queue or System.Collections.Stack class in order to create your own. This is time-consuming and error-prone.

Performance

The generic Queue or Stack does not require a cast that could fail to occur when adding and removing elements from it. In addition, no boxing operation occurs when adding a value type to the Queue or Stack. Likewise, in almost all cases, no unboxing operation occurs when removing a value type from the Queue or Stack.

Easier-to-read code

Your code base will be much smaller because you will not have to subclass the nongeneric Queue or Stack class to create your own strongly typed class. In addition, the type-safety features of generic code will allow you to better understand what the purpose of the Queue or Stack class is in your code.

The following class members are implemented in the nongeneric Queue and Stack classes but not in their generic counterparts:

	Clone method
	IsSynchronized property
	SyncRoot property
	Synchronized method

The addition of the Clone method on the nongeneric Queue and Stack classes is due to the ICloneable interface being implemented only on the nongeneric Queue and Stack classes. However, all other interfaces implemented by the generic and nongeneric Queue and Stack classes are identical.

One way around the missing Clone method in the generic Queue and Stack classes is to use the constructor that accepts an IEnumerable<T> type. Since this is one of the interfaces that the Queue and Stack classes implement, it is easy to write. For the Queue object, the code is as follows:

	public static void CloneQueue()
	{
	    // Create a generic Queue object.
	    Queue<int> numericQueue = new Queue<int>();

	    // Populate Queue. 
	    numericQueue.Enqueue(1); 
	    numericQueue.Enqueue(2); 
	    numericQueue.Enqueue(3);

	    // Create a clone of the numericQueue. 
	    Queue<int> clonedNumericQueue = new Queue<int>(numericQueue);

	    // This does a simple peek at the values, not a dequeue. 
	    foreach (int i in clonedNumericQueue) 
	    {
	        Console.WriteLine("foreach: " + i.ToString());
	    }

	    // De-populate Queue and display items.       
	    Console.WriteLine(clonedNumericQueue.Dequeue().ToString());
	    Console.WriteLine(clonedNumericQueue.Dequeue().ToString());
	    Console.WriteLine(clonedNumericQueue.Dequeue().ToString());
	}

The output for this method is shown here:

	foreach: 1 
	foreach: 2 
	foreach: 3 
	1
	2
	3

For the Stack object, the code is as follows:

	public static void CloneStack()
	{

	    // Create a generic Stack object.
	    Stack<int> numericStack = new Stack<int>();

	    // Populate Stack. 
	    numericStack.Push(1); 
	    numericStack.Push(2); 
	    numericStack.Push(3);

	    // Clone the numericStack object. 
	    Stack<int> clonedNumericStack = new Stack<int>(numericStack);

	    // This does a simple peek at the values, not a pop. 
	    foreach (int i in clonedNumericStack) 
	    {
	        Console.WriteLine("foreach: " + i.ToString()); 
	    }

	    // De-populate Stack and display items.        
	    Console.WriteLine(clonedNumericStack.Pop().ToString());
	    Console.WriteLine(clonedNumericStack.Pop().ToString());
	    Console.WriteLine(clonedNumericStack.Pop().ToString());
	}

The output for this method is shown here:

	foreach: 1 
	foreach: 2 
	foreach: 3 
	1 
	2 
	3

This constructor creates a new instance of the Queue or Stack class containing the elements copied from the IEnumerable<T> type.

See Also

The "System.Collections.Stack Class," "System.Collections.Generic.Stack Class," "System.Collections.Queue Class," and "System.Collections.Generic.Queue Class" topics in the MSDN documentation.

4.5. Using a Linked List

Problem

You need a linked data structure that allows you to easily add and remove elements.

Solution

Use the generic LinkedList<T> class. The following method creates a LinkedList<T> class, adds nodes to this linked list object, and then uses several methods to obtain information from nodes within the linked list:

	public static void UseLinkedList()
	{
	    Console.WriteLine("\r\n\r\n");

	    // Create TodoItem objects to add to the linked list
	    TodoItem i1 =
	        new TodoItem() { Name = "paint door", Comment = "Should be done third" }; 
	    TodoItem i2 =        
	        new TodoItem() { Name = "buy door", Comment = "Should be done first" }; 
	    TodoItem i3 =        
	        new TodoItem() { Name = "assemble door", Comment = "Should be done second" };
	    TodoItem i4 =        
	        new TodoItem() { Name = "hang door", Comment = "Should be done last" };

	    // Create a new LinkedList object
	    LinkedList<TodoItem> todoList = new LinkedList<TodoItem>();

	    // Add the items 
	    todoList.AddFirst(i1); 
	    todoList.AddFirst(i2); 
	    todoList.AddBefore(todoList.Find(i1), i3); 
	    todoList.AddAfter(todoList.Find(i1), i4);

	    // Display all items 
	    foreach (TodoItem tdi in todoList) 
	    {
	        Console.WriteLine(tdi.Name + " : " + tdi.Comment); 
	    }

	    // Display information from the first node in the linked list
	    Console.WriteLine("todoList.First.Value.Name == " + 
	        todoList.First.Value.Name);

	    // Display information from the second node in the linked list
	    Console.WriteLine("todoList.First.Next.Value.Name == " + 
	        todoList.First.Next.Value.Name);

	    // Display information from the next to last node in the linked list
	    Console.WriteLine("todoList.Last.Previous.Value.Name == " +
	        todoList.Last.Previous.Value.Name);
	}

The output for this method is shown here:

	buy door : Should be done first 
	assemble door : Should be done second 
	paint door : Should be done third 
	hang door : Should be done last 
	todoList.First.Value.Name == buy door 
	todoList.First.Next.Value.Name == assemble door 
	todoList.Last.Previous.Value.Name == paint door

This is the TodoItem class, which is a simple container of two string properties Name and Comment. The properties use the new Automatically Implemented Properties feature in C# 3.0 that allows you to declare properties, and the definition of the backing fields is generated automatically:

	/// <summary>
	/// Todo list item
	/// </summary>
	public class TodoItem
	{
	    /// <summary>
	    /// Name of the item
	    /// </summary>
	    public string Name { get; set; }

	    /// <summary>
	    /// Comment for the item
	    /// </summary>
	    public string Comment { get; set; }
	}

Discussion

The LinkedList<T> class in the .NET Framework is a doubly linked list. This is because each node in the linked list contains a pointer to both the previous node and the next node in the linked list. Figure 4-1 shows what a doubly linked list looks like diagrammed on paper. Each node in this diagram represents a single LinkedListNode<T> object.

Graphical representation of a doubly linked list with three nodes

Figure 4-1. Graphical representation of a doubly linked list with three nodes

Notice that each node (i.e., the square boxes) contains a reference to the next node (i.e., the arrows pointing to the right) and a pointer to the previous node (i.e., the arrows pointing to the left) in the linked list. In contrast, a singly linked list contains only pointers to the next node in the list. There is no pointer to the previous node.

In the LinkedList<T> class, the previous node is always accessed through the Previous property, and the next node is always accessed through the Next property. The first node's Previous property in the linked list always returns a null value. Likewise, the last node's Next property in the linked list always returns a null value.

Each node (represented by the boxes in Figure 4-1) in the linked list is actually a generic LinkedListNode <T> object. So a LinkedList<T> object is actually a collection of LinkedListNode <T> objects. Each of these LinkedListNode <T> objects contains properties to access the next and previous LinkedListNode <T> objects, as well as the object contained within it. The object contained in the LinkedListNode <T> object is accessed through the Value property. In addition to these properties, a LinkedListNode <T> object also contains a property called List, which allows access to the containing LinkedList<T> object.

Items to be aware of with List<T> and LinkedList<T>:

  • Adding and removing nodes within a List<T> is, in general, faster than the same operation using a LinkedList<T> class.

  • A List<T> stores its data essentially in one big array on the managed heap, whereas the LinkedList<T> can potentially store its nodes all over the managed heap. This forces the garbage collector to work that much harder to manage LinkedList<T> node objects on the managed heap.

  • Note that the List<T>.Insert* methods can be slower than adding a node anywhere within a LinkedList<T> using one of its Add* methods. However, this is dependent on where the object is inserted into the List<T>. An Insert method must shift all the elements within the List<T> object at the point where the new element is inserted up by one position. If the new element is inserted at or near the end of the List<T>, the overhead of shifting the existing elements is negligible compared to the garbage collector overhead of managing the LinkedList<T> nodes objects. Another area where the List<T> can outperform the LinkedList<T> is when you're doing an indexed access. With the List<T>, you can use the indexer to do an indexed lookup of the element at the specified position. However, with a LinkedList<T> class, you do not have that luxury. With a LinkedList<T> class, you must navigate the LinkedListNode <T> objects using the Previous and Next properties on each LinkedListNode <T>, running through the list until you find the one at the specified position.

  • A List<T> class also has performance benefits over a LinkedList<T> class when searching for an element or node. The List<T>. BinarySearch method is faster at finding elements within a List<T> object than its comparable methods within the LinkedList<T> class, namely the Contains, Find, and FindLast methods.

Table 4-2 shows the comparison between List<T> and LinkedList<T>.

Table 4-2. Performance comparison between List<T> and LinkedList<T>

Action

Who Wins

Adding/Removing Nodes

List<T>

Inserting nodes

LinkedList<T>

Indexed access

List<T>

Node searching

List<T>

See Also

The LinkedList<T> Class topic in the MSDN documentation.

4.6. Creating a Value Type That Can Be Initialized to Null

Problem

You have a variable that is a numeric type, which will hold a numeric value obtained from a database. The database may return this value as a null. You need a simple, clean way to store this numeric value, even if it is returned as a null.

Solution

Use a nullable value type. There are two ways of creating a nullable value type. The first way is to use the ? type modifier:

	int? myDBInt = null;

The second way is to use the Nullable<T> generic type:

	Nullable<int> myDBInt = new Nullable<int>();

Discussion

Both of the following statements are equivalent:

	int? myDBInt = null;
	Nullable<int> myDBInt = new Nullable<int>();

In both cases, myDBInt is a nullable type and is initialized to null.

A nullable type implements the INullableValue interface, which has two read-only property members, HasValue and Value. The HasValue property returns false if the nullable value is set to null; otherwise, it returns true. If HasValue returns true, you can access the Value property, which contains the currently stored value. If HasValue returns false and you attempt to read the Value property, you will get an InvalidOperationException thrown. This is because the Value property is undefined at this point. Below is an example of a test of nullable value using the HasValue property value:

	if (myDBInt.HasValue)
	    Console.WriteLine("Has a value: " + myDBInt.Value);
	else
	    Console.WriteLine("Does not have a value (NULL)");

In addition, one can simply compare the value to null, as shown below:

	if (myDBInt != null)
	    Console.WriteLine("Has a value: " + myDBInt.Value);
	else
	    Console.WriteLine("Does not have a value (NULL)");

Either method is acceptable.

When casting a nullable value to a non-nullable value, the cast operates as it would normally, except when the nullable type is set to null. In this case, an InvalidOperationException is thrown. When casting a non-nullable value to a nullable value, the cast operates as it would normally. No InvalidOperationException will be thrown, as the non-nullable value can never be null.

The tricky thing to watch out for with nullable types is when comparisons are performed. For example, if the following code is executed:

	if (myTempDBInt < 100)
	    Console.WriteLine("myTempDBInt < 100");
	else
	    Console.WriteLine("myTempDBInt >= 100");

The text "myTempDBInt>=100" is displayed, which is obviously incorrect if the value of myTempDBInt is null. To fix this code, you have to check if myTempDBInt is null.Ifit is not, you can execute the if statement in the previous code block:

	if (myTempDBInt != null)
	{
	    if (myTempDBInt < 100)
	        Console.WriteLine("myTempDBInt < 100");
	    else
	        Console.WriteLine("myTempDBInt >= 100");
	}
	else
	{
	    // Handle the null here.
	}

Another interesting thing about nullable types is that you can use them in expressions similar to normal numeric types, for example:

	int? DBInt = 10; 
	int Value = 2; 
	int? Result = DBInt + Value; // Result == 12

The result of using a nullable value in most operators is a null if any nullable value is null.

Tip

Neither the comparison operators nor the null coalescing operator lift to nullable.

However, if none of the nullable values is null, the operation is evaluated as it normally would be. If DBInt, for example, were set to null, the value placed in Result would also be null.

See Also

The "Nullable<T> Generic Class" and "Using Nullable Types" topics in the MSDN documentation.

4.7. Reversing the Contents of a Sorted List

Problem

You want to be able to reverse the contents of a sorted list of items while also maintaining the ability to access them in both array and list styles like SortedList and the generic SortedList<T> classes provide. Neither SortedList nor SortedList<T> provides a direct way to accomplish this without reloading the list.

Solution

Use LINQ to Objects to query the SortedList<T> and apply a descending order to the information in the list. After instantiating a SortedList<TKey, TValue>, the key of which is an int and the value of which is a string, a series of unordered numbers and their text representations are inserted into the list. Those items are then displayed:

	SortedList<int, string> data = new SortedList<int, string>(); 
	data.Add(2, "two"); 
	data.Add(5, "five"); 
	data.Add(3, "three"); 
	data.Add(1, "one");

	foreach (KeyValuePair<int, string> kvp in data) 
	{ 

	    Debug.WriteLine("\t" + kvp.Key + "\t" + kvp.Value); 
	}

The output for the list is shown sorted in ascending order (the default):

	1    one 
	2    two 
	3    three 
	5    five

Now the sort order is reversed by creating a query using LINQ to Objects and setting the orderby clause to descending. The results are then displayed from the query result set:

	// query ordering by descending
	var query = from d in data 
	            orderby d.Key descending 
	            select d;

	foreach (KeyValuePair<int, string> kvp in query) 
	{ 
	    Debug.WriteLine("\t" + kvp.Key + "\t" + kvp.Value); 
	}

This time the output is in descending order:

	5    five
	3    three
	2    two
	1    one

When a new item is added to the list, it is added in the ascending sort order, but by querying again after adding all of the items, you keep the ordering of the list intact:

	data.Add(4, "four");

	// requery ordering by descending
	query = from d in data
	        orderby d.Key descending
	        select d;

	foreach (KeyValuePair<int, string> kvp in query)
	{
	    Debug.WriteLine("\t" + kvp.Key + "\t" + kvp.Value);
	}

	// Just go against the original list for ascending
	foreach (KeyValuePair<int, string> kvp in data)
	{
	    Debug.WriteLine("\t" + kvp.Key + "\t" + kvp.Value);
	}

It can be seen that the output has both descending and ascending orders with the new item:

	5    five 
	4    four 
	3    three 
	2    two 
	1    one 
	1    one 
	2    two 
	3    three 
	4    four 
	5    five

Discussion

A SortedList blends array and list syntax to allow for accessing the data in either format, which can be a handy thing to do. The data is accessible as key/value pairs or directly by index and will not allow duplicate keys to be added. In addition, values that are reference or nullable types can be null, but keys cannot. The items can be iterated using a foreach loop, with KeyValuePair being the type returned. While accessing elements of the SortedList<T>, they may only be read from. The usual iterator syntax prohibits updating or deleting elements of the list while reading, as it will invalidate the iterator.

The orderby clause in the query causes the result set of the query to be ordered either in ascending (the default) or descending order. This sorting is accomplished through use of the default comparer for the element type, so it can be affected by overriding the Equals method for elements that are custom classes. Multiple keys can be specified for the orderby clause, which has the effect of nesting the sort order such as sorting by "last name" and then "first name."

See Also

The "SortedList," "Generic KeyValuePair Structure," and "Generic SortedList" topics in the MSDN documentation.

4.8. Making Read-Only Collections the Generic Way

Problem

You have a collection of information that you want to expose from your class, but you don't want any users modifying the collection.

Solution

Use the ReadOnlyCollection<T> wrapper to easily support collection classes that cannot be modified. For example, a Lottery class that contained the winning lottery numbers should make the winning numbers accessible but not allow them to be changed:

	public class Lottery
	{
	    // make a list
	    List<int> _numbers = null;

	    public Lottery()
	    {

	        // pick the winning numbers
	        _numbers = new List<int>(5) { 17, 21, 32, 44, 58 };
	    } 
	    public ReadOnlyCollection<int> Results
	    { 

	        // return a wrapped copy of the results 
	        get { return new ReadOnlyCollection<int>(_numbers);} 
	    } 
	}

Lottery has a List<int> of winning numbers that it fills in the constructor. The interesting part is that it also exposes a property called Results, which returns a ReadOnlyCollectiontyped as <int> for seeing the winning numbers. Internally, a new ReadOnlyCollection wrapper is created to hold the List<int> that has the numbers in it, and then this instance is returned for use by the user.

If users then attempt to set a value on the collection, they get a compile error:

	Lottery tryYourLuck = new Lottery(); 
	// Print out the results. 
	for (int i = 0; i < tryYourLuck.Results.Count; i++) 
	{

	    Console.WriteLine("Lottery Number " + i + " is " + tryYourLuck.Results[i]); 
	}
	// Change it so we win! 
	tryYourLuck.Results[0]=29;

	//The above line gives // Error 26 // Property or indexer
	// 'System.Collections.ObjectModel.ReadOnlyCollection<int>.this[int]' 
	// cannot be assigned to -- it is read only

Discussion

The main advantage ReadOnlyCollection provides is the flexibility to use it with any collection that supports IList or IList<T> as an interface. ReadOnlyCollection can be used to wrap a regular array like this:

	int[] items = {0, 1, 2 }; 
	ReadOnlyCollection<int> readOnlyItems = 
	    new ReadOnlyCollection<int>(items);

This provides a way to standardize the read-only properties on classes to make it easier for consumers of the class to recognize which properties are read-only simply by the return type.

See Also

The "ReadOnlyCollection" topic in the MSDN documentation.

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 a System.Collections.Hashtable object:

	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 a System.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

Comparer property

Count property

Count property

IsFixedSize property

((IDictionary)myDict).IsFixedSize

IsReadOnlyproperty

((IDictionary)myDict).IsReadOnly

IsSynchronized property

((IDictionary)myDict).IsSynchronized

Item property

Item property

Keys property

Keys property

SyncRoot property

((IDictionary)myDict).SyncRoot

Values property

Values property

Add method

Add method

Clear method

Clear method

Clone method

Use overloaded constructor, which accepts an IDictionary<T, U> type

Contains method

ContainsKey method

ContainsKey method

ContainsKey method

ContainsValue method

ContainsValue method

CopyTo method

((ICollection)myDict).CopyTo(arr,0)

Remove method

Remove method

Synchronized static method

lock(myDictionary.SyncRoot) {…}

N/A

TryGetValue method

In several cases within Table 4-3, there is not a one-to-one correlation between the members of a Hashtable and the members of the generic Dictionary class. Starting with the properties, notice that only the Count, Keys, Values, and Item properties are present in both classes. To make up for the missing properties in the Dictionary class, you can perform a cast to an IDictionary. 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 generic Dictionary, the IsSynchronized property will always return false. The SyncRoot property will always return the same object on which it is called. Essentially, this property returns the this pointer. Microsoft has decided to remove the ability to create a synchronous wrapper from any of the generic collection classes.

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

Since the Clone method is also missing from the generic Dictionary class (due to the fact that this class does not implement the ICloneable interface), you can instead use the overloaded constructor, which accepts an IDictionary<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 the Dictionary class, the Contains and CopyTo methods. The Contains method is easy to reproduce in the Dictionary class. In the Hashtable class, the Contains method and the ContainsKey method both exhibit the same behavior; therefore, you can simply use the ContainsKey method of the Dictionary class to simulate the Contains method of the Hashtable 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));

The CopyTo method is also easy to simulate in the Dictionary class, 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);
	}

Calling CopyTo on the Dictionary object involves setting up an array of KeyValuePair<T, U> objects, which will end up holding all the KeyValuePair<T, U> objects within the Dictionary object after the CopyTo method is called. Next, the numbers Dictionary object is cast to an IDictionary type so that the CopyTo method may be called. Once the CopyTo method is called, the objs array will contain copies of all the KeyValuePair<T, U> objects that are in the original numbers object. Note that iteration of the objs array, using a foreach loop, is done in the same fashion as with the numbers object.

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 support foreach using the DictionaryEntry type, 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, the Dictionary object supports the KeyValuePair<T, U> type when using a foreach loop. This is due to the fact that the GetEnumerator method returns an IEnumerator, which in turn returns KeyValuePair<T, U> types, not DictionaryEntry types.

The KeyValuePair<T, U> type is well suited to be used when enumerating the generic Dictionary class with a foreach loop. The DictionaryEntry object contains key and value pairs as objects, whereas the KeyValuePair<T, U> type contains key and value pairs as their original types, defined when creating the Dictionary object. 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); 
	        }
	    }
	}

This DisposableList class allows only an object that implements IDisposable to be passed in as a type argument to this class. The reason for this is that whenever an object is removed from a DisposableList object, the Dispose method is always called on that object. This allows you to transparently handle the management of any object stored within this DisposableList object.

The following code exercises a DisposableList 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 the string type does not implement the IDisposable interface, and the StreamReader type 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 the TextReader class:

	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 to IsDefaultData returns true, while the second returns false. 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 a nullable type such as long? or Nullable<long> can be set to null (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.

The default keyword 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 is null. If the type argument supplied is a struct, then the default value of the struct 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.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required