|  14.4. Framework Generic Collections  The .NET Framework provides four very useful generic collections , as enumerated earlier (  List  ,  Stack  ,  Queue  , and  Dictionary  ). The most common case is that rather than writing your own collection, you'll use one of the collection classes provided for you. Each is described in turn in the next few sections.   14.4.1. Generic Lists: List<T>  The classic problem with the  Array  type is its fixed size . If you do not know in advance how many objects an array will hold, you run the risk of declaring either too small an array (and running out of room) or too large an array (and wasting memory).   The generic  List  class (which replaces the old non-generic  ArrayList  ) is, essentially , an array whose size is dynamically increased as required.  Lists  provide a number of useful methods and properties for their manipulation.. Some of the most important are shown in Table 14-2.   Table 14-2. List properties and methods        |  Method or property  |  Purpose  |   |   Capacity   |  Property to get or set the number of elements the  List  can contain. This value is increased automatically if count exceeds capacity. You might set this value to reduce the number of reallocations, and you may call  trim( )  to reduce this value to the actual  Count  .  |   |   Count   |  Property to get the number of elements currently in the list.  |   |   Item( )   |  Gets or sets the element at the specified index. This is the indexer for the  List  class.  [a]   |   |   Add( )   |  Public method to add an object to the  List  .  |   |   AddRange( )   |  Public method that adds the elements of an  ICollection  to the end of the  List  .  |   |   BinarySearch( )   |  Overloaded public method that uses a binary search to locate a specific element in a sorted  List  .  |   |   Clear( )   |  Removes all elements from the  List  .  |   |   Contains( )   |  Determines if an element is in the  List  .  |   |   CopyTo( )   |  Overloaded public method that copies a  List  to a one-dimensional array.  |   | Exists( ) |  Determines if an element is in the  List  .  |   | Find( ) |  Returns the first occurrence of the element in the  List  .  |   | FindAll( ) |  Returns all the specified elements in the  List  .  |   |   GetEnumerator( )   |  Overloaded public method that returns an enumerator to iterate through a  List  .  |   |   GeTRange( )   |  Copies a range of elements to a new  List  .  |   |   IndexOf( )   |  Overloaded public method that returns the index of the first occurrence of a value.  |   |   Insert( )   |  Inserts an element into  List  .  |   |   InsertRange( )   |  Inserts the elements of a collection into the  List  .  |   |   LastIndexOf( )   |  Overloaded public method that returns the index of the last occurrence of a value in the  List  .  |   |   Remove( )   |  Removes the first occurrence of a specific object.  |   |   RemoveAt( )   |  Removes the element at the specified index.  |   |   RemoveRange( )   |  Removes a range of elements.  |   |   Reverse( )   |  Reverses the order of elements in the  List  .  |   |   Sort ( )   |  Sorts the  List  .  |   |   ToArray( )   |  Copies the elements of the  List  to a new array.  |   |   trimToSize( )   |  Sets the capacity to the actual number of elements in the  List  .  |  
    [a]  The idiom in the Framework Class Library is to provide an Item element for collection classes, which is implemented as an indexer in C#.   When you create a  List  , you do not define how many objects it will contain. You add to the  List  using the  Add( )  method, and the  List  takes care of its own internal bookkeeping, as illustrated in Example 14-4.   Example 14-4. Working with a List    |  using System; using System.Collections.Generic; namespace ListCollection {    // a simple class to store in the List    public class Employee    {       private int empID;       public Employee( int empID )       {          this.empID = empID;       }       public override string ToString(  )       {          return empID.ToString(  );       }       public int EmpID       {          get          {             return empID;          }          set          {             empID = value;          }       }    }    public class Tester    {       static void Main(  )       {          List<Employee> empList = new List<Employee>(  );          List<int> intList = new List<int>(  );          // populate the List          for ( int i = 0; i < 5; i++ )          {             empList.Add( new Employee( i + 100 ) );             intList.Add( i * 5 );          }          // print all the contents          for ( int i = 0; i < intList.Count; i++ )          {             Console.Write( "{0} ", intList[i].ToString(  ) );          }          Console.WriteLine( "\n" );          // print all the contents of the Employee List          for ( int i = 0; i < empList.Count; i++ )          {             Console.Write( "{0} ", empList[i].ToString(  ) );          }          Console.WriteLine( "\n" );          Console.WriteLine( "empList.Capacity: {0}", empList.Capacity );       }    } } 
 |   The output looks like this:   0 5 10 15 20     100 101 102 103 104     empArray.Capacity: 8  
  The  List  class has a property,  Capacity  , which is the number of elements the  List  is capable of storing; however, this capacity is automatically doubled each time you reach the limit.   14.4.1.1. Creating objects that can be sorted by the generic list  The  List  implements the  Sort( )  method. You can sort any  List  that contains objects that implement  IComparable  . All the built-in types do implement this interface, so you can sort a  List<integer>  or a  List<string>  .   On the other hand, if you want to sort a  List<Employee>  , you must change the  Employee  class to implement  IComparable  :   public class Employee : IComparable<Employee>  
  As part of the  IComparable  interface contract, the  Employee  object must provide a  CompareTo( )  method:   public int CompareTo(Employee rhs)     {        return this.empID.CompareTo(rhs.empID);     } 
  The  CompareTo( )  method takes an  Employee  as a parameter (we know this, because the interface is now generic and we can assume type safety). The  Employee  object must compare itself to this second  Employee  object and return -  1  if it is smaller than the second  Employee  ,  1  if it is greater, and   if the two  Employee  objects are equal to each other.   It is up to the designer of the  Employee  class to determine what  smaller than  ,  greater than  , and  equal to  mean. In this example, you will delegate the comparison to the  empId  member. The  empId  member is an  int  and uses the default  CompareTo( )  method for integer types, which will do an integer comparison of the two values.   To see if the sort is working, you'll add integers and  Employee  instances to their respective lists with random values. To create the random values, you'll instantiate an object of class  Random  . To cause your  Random  instance to generate the random values, you'll call its  Next( )  method. One version of the  Next( )  method allows you to specify the largest random number you want. In this case, you'll pass in the value  10  to generate a random number between   and  10  :   Random r = new Random(  );     r.Next(10);  
  Example 14-5 creates an integer array and an  Employee  array, populates them both with random numbers , and prints their values. It then sorts both arrays and prints the new values.   Example 14-5. Sorting an integer and an Employee array    |  using System; using System.Collections.Generic; namespace IComparable {    // a simple class to store in the array    public class  Employee : IComparable<Employee>  {       private int empID;       public  Employee  ( int empID )       {          this.empID = empID;       }       public override string ToString(  )       {          return empID.ToString(  );       }       public bool Equals(  Employee  other )       {          if ( this.empID == other.empID )          {             return true;          }          else          {             return false;          }       }       // Comparer delegates back to Employee       // Employee uses the integer's default       // CompareTo method  public int CompareTo( Employee rhs )       {          return this.empID.CompareTo( rhs.empID );       }  }    public class  Tester  {       static void Main(  )       {  List<Employee> empList = new List<Employee>(  );   List  <  Int32  > intList = new  List  <  Int32  >(  );          // generate random numbers for          // both the integers and the          // employee id's  Random  r = new  Random  (  );          // populate the array          for ( int i = 0; i < 5; i++ )          {             // add a random employee id             empList.Add( new  Employee  ( r.Next( 10 ) + 100 ) );             // add a random integer             intList.Add( r.Next( 10 ) );          }          // display all the contents of the int array          Console.WriteLine("List<int> before sorting:");          for ( int i = 0; i < intList.Count; i++ )          {  Console  .Write( "{0} ", intList[i].ToString(  ) );          }  Console  .WriteLine( "\n" );          // display all the contents of the Employee array          Console.WriteLine("List<Employee> before sorting:");          for ( int i = 0; i < empList.Count; i++ )          {  Console  .Write( "{0} ", empList[i].ToString(  ) );          }  Console  .WriteLine( "\n" );          // sort and display the int array          Console.WriteLine("List<int>after sorting:");          intList.Sort(  );          for ( int i = 0; i < intList.Count; i++ )          {  Console  .Write( "{0} ", intList[i].ToString(  ) );          }  Console  .WriteLine( "\n" );          // sort and display the Employee array          Console.WriteLine("List<Employee>after sorting:");          //Employee.EmployeeComparer c = Employee.GetComparer(  );          //empList.Sort(c);          empList.Sort(  );          // display all the contents of the Employee array          for ( int i = 0; i < empList.Count; i++ )          {  Console  .Write( "{0} ", empList[i].ToString(  ) );          }  Console  .WriteLine( "\n" );       }    } } 
 |   The output looks like this:   List<int> before sorting:     6 9 8 3 6     List<Employee> before sorting:     108 103 107 102 109     List<int>after sorting:     3 6 6 8 9     List<Employee>after sorting:     102 103 107 108 109  
  The output shows that the lists of integers and  Employees  were generated with random numbers (and thus the numbers may be different each time you run the program). When sorted, the display shows the values have been ordered properly.     |    |   |  Random number generators do not, technically, create true random numbers; they create what computer scientists call pseudo-random numbers. Microsoft notes: "Pseudo-random numbers are chosen with equal probability from a finite set of numbers. The chosen numbers are not completely random because a definite mathematical algorithm is used to select them, but they are sufficiently random for practical purposes." This is certainly sufficient for our example.  |  |  
  14.4.1.2. Controlling how elements in a generic collection are sorted by implementing IComparer<T>  When you call  Sort( )  on the  List  in Example 14-5, the default implementation of I  Comparer  is called, which uses  QuickSort  to call the  IComparable  implementation of  CompareTo( )  on each element in the  List  .   You are free, however, to create your own implementation of  IComparer  , which you might want to do if you need control over how the sort ordering is defined. In the next example, you will add a second field to  Employee  :  yearsOfSvc  . You want to be able to sort the  Employee  objects in the  List  either by ID or by years of service, and you want to make that decision at run time.   To accomplish this, you will create a custom implementation of  IComparer  , which you will pass to the  Sort( )  method of  List  . In this  IComparer  class,  EmployeeComparer  will know how to sort  Employees  .   To simplify the programmer's ability to choose how a given set of  Employees  are sorted, we'll add a property  WhichComparison  , of type  Employee.EmployeeComparer.ComparisonType  (an enumeration):   public     Employee.EmployeeComparer.ComparisonType  WhichComparison     {        get { return whichComparison; }        set { whichComparison = value; }     } 
   ComparisonType  is an enumeration with two values,  empID  or  yearsOfSvc  (indicating that you want to sort by employee ID or years of service, respectively):   public enum ComparisonType     {        EmpID,        YearsOfService     }; 
  Before invoking  Sort( )  , you will create an instance of  EmployeeComparer  and set its  ComparisonType  property:   Employee.EmployeeComparer c = Employee.GetComparer(  );     c.WhichComparison=Employee.EmployeeComparer.ComparisonType.EmpID;     empArray.Sort(c);  
  When you invoke  Sort( )  , the  List  will call the  Compare( )  method on the  Employee-Comparer  , which in turn will delegate the comparison to the  Employee.CompareTo( )  method, passing in its  WhichComparison  property:   public int Compare(  Employee  lhs,  Employee  rhs )     {         return lhs.CompareTo( rhs, WhichComparison );     } 
  The  Employee  object must implement a custom version of  CompareTo( )  , which takes the comparison and compares the objects accordingly :   public int CompareTo         (         Employee rhs,         Employee.EmployeeComparer.ComparisonType whichComparison         )     {        switch (whichComparison)        {           case Employee.EmployeeComparer.ComparisonType.EmpID:              return this.empID.CompareTo(rhs.empID);           case Employee.EmployeeComparer.ComparisonType.Yrs:              return this.yearsOfSvc.CompareTo(rhs.yearsOfSvc);        }        return 0;     } 
  The complete source for this example is shown in Example 14-6. The integer array has been removed to simplify the example, and the output of the employee's  ToString( )  method has been enhanced to enable you to see the effects of the sort.   Example 14-6. Sorting an array by employees' IDs and years of service    |  using System; using System.Collections.Generic; namespace IComparer {    public class  Employee  :  IComparable  <  Employee  >    {       private int empID;       private int yearsOfSvc = 1;       public  Employee  ( int empID )       {          this.empID = empID;       }       public  Employee  ( int empID, int yearsOfSvc )       {          this.empID = empID;          this.yearsOfSvc = yearsOfSvc;       }  public override string ToString(  )       {          return "ID: " + empID.ToString(  ) +          ". Years of Svc: " + yearsOfSvc.ToString(  );       }  public bool Equals(  Employee  other )       {          if ( this.empID == other.empID )          {             return true;          }          else          {             return false;          }       }       // static method to get a Comparer object       public static  EmployeeComparer  GetComparer(  )       {          return new  Employee  .  EmployeeComparer  (  );       }       // Comparer delegates back to Employee       // Employee uses the integer's default       // CompareTo method       public int CompareTo(  Employee  rhs )       {          return this.empID.CompareTo( rhs.empID );       }  // Special implementation to be called by custom comparer       public int CompareTo(          Employee rhs,          Employee.EmployeeComparer.ComparisonType which )       {          switch ( which )          {             case Employee.EmployeeComparer.ComparisonType.EmpID:                return this.empID.CompareTo( rhs.empID );             case Employee.EmployeeComparer.ComparisonType.Yrs:                return this.yearsOfSvc.CompareTo( rhs.yearsOfSvc );          }          return 0;       }  // nested class which implements IComparer       public class  EmployeeComparer  :  IComparer  <  Employee  >       {  // private state variable          private Employee.EmployeeComparer.ComparisonType             whichComparison;          // enumeration of comparsion types          public enum ComparisonType          {             EmpID,             Yrs          };  public  bool Equals(  Employee  lhs,  Employee  rhs )          {             return this.Compare( lhs, rhs ) == 0;          }          public  int GetHashCode(  Employee  e)          {             return e.GetHashCode(  );          }          // Tell the Employee objects to compare themselves          public int Compare(  Employee  lhs,  Employee  rhs )          {              return lhs.CompareTo( rhs, WhichComparison );          }          public  Employee  .  EmployeeComparer  .  ComparisonType  WhichComparison          {             get{return whichComparison;}             set{whichComparison = value;}          }       }    }    public class  Tester  {       static void Main(  )       {  List  <  Employee  > empArray = new  List  <  Employee  >(  );          // generate random numbers for          // both the integers and the          // employee id's  Random  r = new  Random  (  );          // populate the array          for ( int i = 0; i < 5; i++ )          {             // add a random employee id             empArray.Add(                new  Employee  (                   r.Next( 10 ) + 100, r.Next( 20 )                )             );          }          // display all the contents of the Employee array          for ( int i = 0; i < empArray.Count; i++ )          {  Console  .Write( "\n{0} ", empArray[i].ToString(  ) );          }  Console  .WriteLine( "\n" );  // sort and display the employee array          Employee.EmployeeComparer c = Employee.GetComparer(  );          c.WhichComparison =          Employee.EmployeeComparer.ComparisonType.EmpID;          empArray.Sort( c );  // display all the contents of the Employee array          for ( int i = 0; i < empArray.Count; i++ )          {  Console  .Write( "\n{0} ", empArray[i].ToString(  ) );          }  Console  .WriteLine( "\n" );  c.WhichComparison = Employee.EmployeeComparer.ComparisonType.Yrs;          empArray.Sort( c );  for ( int i = 0; i < empArray.Count; i++ )          {  Console  .Write( "\n{0} ", empArray[i].ToString(  ) );          }  Console  .WriteLine( "\n" );       }    } } 
 |   The output looks like this for one run of the program:   ID: 103. Years of Svc: 11     ID: 108. Years of Svc: 15     ID: 107. Years of Svc: 14     ID: 108. Years of Svc: 5     ID: 102. Years of Svc: 0     ID: 102. Years of Svc: 0     ID: 103. Years of Svc: 11     ID: 107. Years of Svc: 14     ID: 108. Years of Svc: 15     ID: 108. Years of Svc: 5     ID: 102. Years of Svc: 0     ID: 108. Years of Svc: 5     ID: 103. Years of Svc: 11     ID: 107. Years of Svc: 14     ID: 108. Years of Svc: 15  
  The first block of output shows the  Employee  objects as they are added to the  List  . The employee ID values and the years of service are in random order. The second block shows the results of sorting by the employee ID, and the third block shows the results of sorting by years of service.   14.4.2. Generic Queues  A  queue  represents a first-in, first-out (FIFO ) collection. The classic analogy is to a line (or queue, if you are British) at a ticket window. The first person in line ought to be the first person to come off the line to buy a ticket.   A queue is a good collection to use when you are managing a limited resource. For example, you might want to send messages to a resource that can only handle one message at a time. You would then create a message queue so that you can say to your clients : "Your message is important to us. Messages are handled in the order in which they are received."   The  Queue  class has a number of member methods and properties, the most important of which are shown in Table 14-3.   Table 14-3. Queue methods and properties        |  Method or property  |  Purpose  |   |   Count   |  Public property that gets the number of elements in the  Queue   |   |   Clear( )   |  Removes all objects from the  Queue   |   |   Contains( )   |  Determines if an element is in the  Queue   |   |   CopyTo( )   |  Copies the  Queue  elements to an existing one-dimensional array  |   |   Dequeue( )   |  Removes and returns the object at the beginning of the  Queue   |   |   Enqueue( )   |  Adds an object to the end of the  Queue   |   |   GetEnumerator( )   |  Returns an enumerator for the  Queue   |   |   Peek( )   |  Returns the object at the beginning of the  Queue  without removing it  |   |   ToArray( )   |  Copies the elements to a new array  |  
  Add elements to your queue with the  Enqueue  command and take them off the queue with  Dequeue  or by using an enumerator, as shown in Example 14-7.   Example 14-7. Working with a queue    |  using System; using System.Collections.Generic; namespace Queue {    public class  Tester  {       static void Main(  )       {  Queue  <  Int32  > intQueue = new  Queue  <  Int32  >(  );          // populate the array          for ( int i = 0; i < 5; i++ )          {             intQueue.Enqueue( i * 5 );          }          // Display the Queue.  Console  .Write( "intQueue values:\t" );          PrintValues( intQueue );          // Remove an element from the Queue.  Console  .WriteLine(             "\n(Dequeue)\t{0}", intQueue.Dequeue(  ) );          // Display the Queue.  Console  .Write( "intQueue values:\t" );          PrintValues( intQueue );          // Remove another element from the Queue.  Console  .WriteLine(             "\n(Dequeue)\t{0}", intQueue.Dequeue(  ) );          // Display the Queue.  Console  .Write( "intQueue values:\t" );          PrintValues( intQueue );          // View the first element in the          // Queue but do not remove.  Console  .WriteLine(             "\n(Peek)   \t{0}", intQueue.Peek(  ) );          // Display the Queue.  Console  .Write( "intQueue values:\t" );          PrintValues( intQueue );       }       public static void PrintValues(  IEnumerable  <  Int32  > myCollection)       {  IEnumerator  <  Int32  > myEnumerator =             myCollection.GetEnumerator(  );          while ( myEnumerator.MoveNext(  ) )  Console  .Write( "{0} ", myEnumerator.Current );  Console  .WriteLine(  );       }    } } 
 |   The output looks like this:   intQueue values:       0 5 10 15 20     (Dequeue)       0     intQueue values:       5 10 15 20     (Dequeue)       5     intQueue values:       10 15 20     (Peek)          10     intQueue values:       10 15 20  
  I've dispensed with the  Employee  class to save room, but of course you can enqueue user -defined objects as well. The output shows that queuing objects adds them to the  Queue  , while  Dequeue( )  returns the object and also removes them from the  Queue  . The  Queue  class also provides a  Peek( )  method that allows you to see, but not remove, the next element.   Because the  Queue  class is enumerable, you can pass it to the  PrintValues( )  method, which is provided as an  IEnumerable  interface. The conversion is implicit. In the  PrintValues  method, you call  GetEnumerator  , which you will remember is the single method of all  IEnumerable  classes. This returns an  IEnumerator  , which you then use to enumerate all the objects in the collection.   14.4.3. Generic Stacks  A  Stack  is a last-in, first-out (LIFO ) collection, like a stack of dishes at a buffet table, or a stack of coins on your desk. A dish added on top, is the first dish you take off the stack.   The principal methods for adding to and removing from a stack are  Push( )  and  Pop( )  ;  Stack  also offers a  Peek( )  method, very much like  Queue  . The significant methods and properties for S  tack  are shown in Table 14-4.   Table 14-4. Stack methods and properties        |  Method or property  |  Purpose  |   |   Count   |  Public property that gets the number of elements in the  Stack   |   |   Clear( )   |  Removes all objects from the  Stack   |   |   Contains( )   |  Determines if an element is in the  Stack   |   |   CopyTo( )   |  Copies the  Stack  elements to an existing one-dimensional array  |   |   GetEnumerator( )   |  Returns an enumerator for the  Stack   |   |   Peek( )   |  Returns the object at the top of the  Stack  without removing it  |   |   Pop( )   |  Removes and returns the object at the top of the  Stack   |   |   Push( )   |  Inserts an object at the top of the  Stack   |   |   ToArray( )   |  Copies the elements to a new array  |  
  The  List  ,  Queue  , and  Stack  types contain multiple versions of the  CopyTo( )  and  ToArray( )  methods for copying their elements to an array. In the case of a  Stack  , the  CopyTo( )  method will copy its elements to an existing one-dimensional array, overwriting the contents of the array beginning at the index you specify. The  ToArray( )  method returns a new array with the contents of the  Stack  's elements. Example 14-8 illustrates several of the  Stack  methods.   Example 14-8. Working with a Stack    |  using System; using System.Collections.Generic; namespace Stack {    public class  Tester  {       static void Main(  )       {  Stack  <  Int32  > intStack = new  Stack  <  Int32  >(  );          // populate the array          for ( int i = 0; i < 8; i++ )          {             intStack.Push( i * 5 );          }          // Display the Stack.  Console  .Write( "intStack values:\t" );          PrintValues( intStack );          // Remove an element from the Stack.  Console  .WriteLine( "\n(Pop)\t{0}",          intStack.Pop(  ) );          // Display the Stack.  Console  .Write( "intStack values:\t" );          PrintValues( intStack );          // Remove another element from the Stack.  Console  .WriteLine( "\n(Pop)\t{0}",             intStack.Pop(  ) );          // Display the Stack.  Console  .Write( "intStack values:\t" );          PrintValues( intStack );          // View the first element in the          // Stack but do not remove.  Console  .WriteLine( "\n(Peek)   \t{0}",             intStack.Peek(  ) );          // Display the Stack.  Console  .Write( "intStack values:\t" );          PrintValues( intStack );          // Declare an array object which will          // hold 12 integers          int[] targetArray = new int[12];          for (int i = 0; i < targetArray.Length; i++)          {              targetArray[i] = i * 100 + 100;          }          // Display the values of the target Array instance.  Console  .WriteLine( "\nTarget array:  " );          PrintValues( targetArray );          // Copy the entire source Stack to the          // target Array instance, starting at index 6.          intStack.CopyTo( targetArray, 6 );          // Display the values of the target Array instance.  Console  .WriteLine( "\nTarget array after copy:  " );          PrintValues( targetArray );       }       public static void PrintValues(  IEnumerable  <  Int32  > myCollection )       {  IEnumerator  <  Int32  > enumerator =             myCollection.GetEnumerator(  );          while ( enumerator.MoveNext(  ) )  Console  .Write( "{0}  ", enumerator.Current );  Console  .WriteLine(  );       }    } } 
 |   The output looks like this:   intStack values:        35  30  25  20  15  10  5  0     (Pop)   35     intStack values:        30  25  20  15  10  5  0     (Pop)   30     intStack values:        25  20  15  10  5  0     (Peek)          25     intStack values:        25  20  15  10  5  0     Target array:     100  200  300  400  500  600  700  800  900  1000  1100  1200     Target array after copy:     100  200  300  400  500  600  25  20  15  10  5  0     The new  array:     25  20  15  10  5  0  
  The output reflects that the items pushed onto the  Stack  were popped in reverse order.   The effect of  CopyTo( )  can be seen by examining the target array before and after calling  CopyTo( )  . The array elements are overwritten beginning with the index specified (  6  ).   14.4.4. Dictionaries  A  dictionary  is a collection that associates a  key  to a  value  . A language dictionary, such as Webster's, associates a word (the key) with its definition (the value).   To see the value of dictionaries , start by imagining that you want to keep a list of the state capitals. One approach might be to put them in an array:   string[] stateCapitals = new string[50];  
  The  stateCapitals  array will hold 50 state capitals. Each capital is accessed as an offset into the array. For example, to access the capital for Arkansas, you need to know that Arkansas is the fourth state in alphabetical order:   string capitalOfArkansas = stateCapitals[3];  
  It is inconvenient, however, to access state capitals using array notation. After all, if I need the capital for Massachusetts, there is no easy way for me to determine that Massachusetts is the 21st state alphabetically .   It would be far more convenient to store the capital with the state name. A dictionary allows you to store a value (in this case, the capital) with a key (in this case, the name of the state).   A .NET Framework dictionary can associate any kind of key (string, integer, object) with any kind of value (string, integer, object). Typically, the key is fairly short and the value fairly complex, though in this case, we'll use short strings for both.   The most important attributes of a good dictionary are that it is easy to add values and it is quick to retrieve values. Table 14-5 lists some of the more important methods and properties of the dictionary.   Table 14-5. Dictionary methods and properties        |  Method or property  |  Purpose  |   |   Count   |  Public property that gets the number of elements in the  Dictionary  .  |   |   Item( )   |  The indexer for the  Dictionary  .  |   |   Keys   |  Public property that gets a collection containing the keys in the  Dictionary  . (See also  Values  , later in this table.)  |   |   Values   |  Public property that gets a collection containing the values in the  Dictionary  . (See also  Keys  , earlier in this table.)  |   |   Add( )   |  Adds an entry with a specified  Key  and  Value  .  |   |   Clear( )   |  Removes all objects from the  Dictionary  .  |   |   ContainsKey( )   |  Determines whether the  Dictionary  has a specified key.  |   |   ContainsValue( )   |  Determines whether the  Dictionary  has a specified value.  |   |   GetEnumerator( )   |  Returns an enumerator for the  Dictionary  .  |   |   Remove( )   |  Removes the entry with the specified  Key  .  |  
  The key in a  Dictionary  can be a primitive type, or it can be an instance of a user-defined type (an object).     |    |   |  Objects used as keys for a  Dictionary  must implement  GetHashCode( )  as well as  Equals  . In most cases, you can simply use the inherited implementation from  Object  .  |  |  
  Dictionaries implement the  IDictionary<TKey,TValue>  interface (where  TKey  is the key type and  TValue>  is the value type).  IDictionary  provides a public property  Item  . The  Item  property retrieves a value with the specified key.   The  Item  property is implemented with the index operator (  []  ). Thus, you access items in any  Dictionary  object using the offset syntax, as you would with an array.   Example 14-9 demonstrates adding items to a  Dictionary  and then retrieving them with the indexer (which implicitly calls the  Dictionary  's  Item  property).   Example 14-9. The Item property as offset operators    |  using System; using System.Collections.Generic; namespace Dictionary {    public class Tester    {       static void Main(  )       {          // Create and initialize a new Dictionary.          Dictionary<string, string>  dict  =            new Dictionary<string, string>(  );       dict.Add("Alabama", "Montgomery");       dict.Add("Alaska", "Juneau");       dict.Add("Arizona", "Phoenix");       dict.Add("Arkansas", "Little Rock");       dict.Add("California", "Sacramento");       dict.Add("Colorado", "Denver");       dict.Add("Connecticut", "Hartford");       dict.Add("Delaware", "Dover");       dict.Add("Florida", "Tallahassee");       dict.Add("Georgia", "Atlanta");       dict.Add("Hawaii", "Honolulu");       dict.Add("Idaho", "Boise");       dict.Add("Illinois", "Springfield");       dict.Add("Indiana", "Indianapolis");       dict.Add("Iowa", "Des Moines");       dict.Add("Kansas", "Topeka");       dict.Add("Kentucky", "Frankfort");       dict.Add("Louisiana", "Baton Rouge");       dict.Add("Maine", "Augusta");       dict.Add("Maryland", "Annapolis");       dict.Add("Massachusetts", "Boston");       dict.Add("Michigan", "Lansing");       dict.Add("Minnesota", "St. Paul");       dict.Add("Mississippi", "Jackson");       dict.Add("Missouri", "Jefferson City");       dict.Add("Montana", "Helena");       dict.Add("Nebraska", "Lincoln");       dict.Add("Nevada", "Carson City");       dict.Add("New Hampshire", "Concord");       dict.Add("New Jersey", "Trenton");       dict.Add("New Mexico", "Santa Fe");       dict.Add("New York", "Albany");       dict.Add("North Carolina", "Raleigh");       dict.Add("North Dakota", "Bismarck");       dict.Add("Ohio", "Columbus");       dict.Add("Oklahoma", "Oklahoma City");       dict.Add("Oregon", "Portland");       dict.Add("Pennsylvania", "Harrisburg");       dict.Add("Rhode Island", "Providence");       dict.Add("South Carolina", "Columbia");       dict.Add("South Dakota", "Pierre");       dict.Add("Tennessee", "Nashville");       dict.Add("Texas", "Austin");       dict.Add("Utah", "Salt Lake City");       dict.Add("Vermont", "Montpelier");       dict.Add("Virginia", "Richmond");       dict.Add("Washington", "Olympia");       dict.Add("West Virginia", "Charleston");       dict.Add("Wisconsin", "Madison");       dict.Add("Wyoming", "Cheyenne");       // access a state  Console.WriteLine( "The capital of Massachusetts is {0}",         dict["Massachusetts"] );  }      // end main    }         // end class }            // end namespace 
 |   The output looks like this:   The capital of Massachusetts is Boston  
  Example 14-9 begins by instantiating a new  Dictionary  object with the type of the key and of the value declared to be string.   We add 50 key/value pairs. In this example, the state name is the key and the capital is the value (though, in a typical dictionary, the value is almost always larger than the key).     |    |   |  You must not change the value of the key object once you use it in a dictionary.  |  |  
 |