Framework Generic Collections

We spent time explaining what generic collections are and how they work so that you’d have an appreciation for how they’re created and what they can do. Most of the time, you won’t need to create your own collection, because the .NET Framework provides four very useful generic collections, as we discussed earlier (List, Stack, Queue, and Dictionary). We describe each in turn in the next few sections.

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 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

Property that .NET requires for the List class as an indexer; you’ll never see this in C#; you can use the standard [] syntax instead

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 whether an element is in the List

CopyTo( )

Overloaded public method that copies a List to a one-dimensional array; commonly used to convert a List to an array for methods that accept only arrays, not collections

Exists( )

Determines whether the List contains elements that meet the specified criteria

Find( )

Returns the first List element that meets specified criteria

FindAll( )

Returns all List elements that meet specified criteria

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 a 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 List element that meets specified criteria

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; commonly used to convert a List to an array for methods that accept only arrays, not collections

TrimExcess( )

Sets the capacity to the actual number of elements in the List

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. A List offers all the functionality of an array, but without the need to know how many elements it will hold when it’s created

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Example_14_4_  _  _  _List
{
    // a simple class to store in the List
    public class Employee
    {
        private int empID;
        public Employee(int empID) //constructor
        {
            this.empID = empID;
        }
        public override string ToString( )
        {
            return empID.ToString( );
        }
    }

    public class Tester
    {
        static void Main( )
        {

            List<Employee> empList = new List<Employee>( );
            List<int> intList = new List<int>( );

            // populate the Lists
            for (int i = 0; i < 5; i++)
            {
                intList.Add(i * 5);
                empList.Add(new Employee(i + 100));
            }

            // print the contents of the int List
            for (int i = 0; i < intList.Count; i++)
            {
                Console.Write("{0} ", intList[i].ToString( ));
            }

            Console.WriteLine("
");

            // print the contents of the Employee List
            for (int i = 0; i < empList.Count; i++)
            {
                Console.Write("{0} ", empList[i].ToString( ));
            }

            Console.WriteLine("
");
            Console.WriteLine("empList.Capacity: {0}", empList.Capacity);
        }
    }
}

The output looks like this:

0 5 10 15 20
100 101 102 103 104
empList.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 increased each time you reach the limit.

The Add( ) method takes care of a lot of things behind the scenes here—it increases the capacity of the List (if necessary), inserts the new item at the end of the list, and provides it with an appropriate index. You can’t do that with an array.

Sorting objects with 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. You know this is correct because the interface is generic, which means that the List was specified with the Employee class when you created it, so you can assume type safety. The Employee object must compare itself to the second Employee object that was passed in (called rhs) and return -1 if it is smaller than the second Employee, 1 if it is greater, and 0 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 for an employee. In this example, you’ll compare the Employee objects based on the value of their empId members. The empId member is an int, and since int is a built-in type, it already has its own default CompareTo( ) method, which will do an integer comparison of the two values. So, the CompareTo( ) method for Employee just calls the CompareTo( ) method of EmpID, which returns an int property. You let the int CompareTo( ) do the work of the comparison, and then return the result.

To see whether the sort is working, you’ll add integers and Employee instances to their respective lists with random values. (See the sidebar about the Random class.)

Example 14-5 creates an integer list and an Employee list, populates them both with random numbers, and prints their values. It then sorts both lists and prints the new values.

Example 14-5. The List class includes methods that make sorting easy

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Example_14_5_  _  _  _Sorting_a_List
{
    // a simple class to store in the list
    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 IDs
            Random r = new Random( );

            // populate the list
            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 list
            Console.WriteLine("List<int> before sorting:");
            for (int i = 0; i < intList.Count; i++)
            {
                Console.Write("{0} ", intList[i].ToString( ));
            }
            Console.WriteLine("
");

            // display all the contents of the Employee list
            Console.WriteLine("List<Employee> before sorting:");
            for (int i = 0; i < empList.Count; i++)
            {
                Console.Write("{0} ", empList[i].ToString( ));
            }
            Console.WriteLine("
");

            // sort and display the int list
            Console.WriteLine("List<int>after sorting:");
            intList.Sort( );
            for (int i = 0; i < intList.Count; i++)
            {
                Console.Write("{0} ", intList[i].ToString( ));
            }
            Console.WriteLine("
");

            // sort and display the Employee list
            Console.WriteLine("List<Employee>after sorting:");

            //Employee.EmployeeComparer c = Employee.GetComparer( );
            //empList.Sort(c);

            empList.Sort( );

            // display all the contents of the Employee list
            for (int i = 0; i < empList.Count; i++)
            {
                Console.Write("{0} ", empList[i].ToString( ));
            }
            Console.WriteLine("
");

        }
    }
}

The output looks something 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—which means the numbers will be different each time you run the program. When sorted, the display shows that the values have been ordered properly.

Controlling sorting by implementing IComparer<T>

When you call Sort( ) on the List in Example 14-5, the default implementation of IComparer is called behind the scenes, which uses an algorithm called “Quick Sort” 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 runtime.

To accomplish this, you will create a custom implementation of IComparer, which you will pass to the Sort( ) method of List. You’ll implement a new class, EmployeeComparer, which will implement IComparer and will know how to sort Employees.

To simplify the programmer’s ability to choose how a given set of Employees are sorted, you’ll add a property, WhichComparison, of type Employee.EmployeeComparer.ComparisonType (an enumeration):

public
Employee.EmployeeComparer.ComparisonType  WhichComparison
{
   get { return whichComparison; }
   set { whichComparison = value; }
}

The point to this is that when you create an EmployeeComparer, you can pass it WhichComparison, which will be of type ComparisonType. ComparisonType is an enumeration with one of two values, empID or yearsOfSvc (indicating that you want to sort by employee ID or years of service, respectively):

public enum ComparisonType
{
   EmpID,
   YearsOfService
};

It may seem convoluted, but later on, if you decide to add another property to the Employee class—lastName, for example—and you want to sort by the new lastName property, you can very easily add LastName to the enumeration (note the capitalization), making the comparison much easier.

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;
empList.Sort(c);

The EmployeeComparer class must provide a Compare( ) method. When you invoke Sort( ), the List will call that Compare( ) method on the EmployeeComparer, 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 );
}

Your Employee object must implement a custom version of CompareTo( ). This custom method needs to accept the Employee object to compare to (which we’ve been calling rhs), and a member of the ComparisonType enum you defined earlier. Depending on the value of ComparisonType, you’ll need code to compare the value of either empID or yearsOfSvc. Both empID and yearsOfSvc are ints, so once again you can just delegate to the CompareTo( ) method of int in both cases. If you added a LastName member to the enum, you’d need to add another case statement that would call the string class’s CompareTo( ) method:

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. We’ve removed the integer list to simplify the example, and we’ve enhanced the output of the employee’s ToString( ) method so that you can see the effects of the sort.

Example 14-6. You can sort a List by differing properties of your class, if you implement your own IComparer

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Example_14_6_  _  _  _Custom_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.
                              YearsOfService:
                    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 comparison types
            public enum ComparisonType
            {
                EmpID,
                YearsOfService
            };

            public bool Equals(Employee lhs, Employee rhs)
            {
                return this.Compare(lhs, rhs) == 0;
            }

            // 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> empList = new List<Employee>( );

            // generate random numbers for
            // both the integers and the
            // employee IDs
            Random r = new Random( );

            // populate the list
            for (int i = 0; i < 5; i++)
            {
                // add a random employee ID
                empList.Add(new Employee(r.Next(10) + 100,
                                          r.Next(20)));
            }

            // display all the contents of the Employee list
            for (int i = 0; i < empList.Count; i++)
            {
                Console.Write("
{0} ", empList[i].ToString( ));
            }
            Console.WriteLine("
");

            // sort and display the employee list
            Employee.EmployeeComparer c = Employee.GetComparer( );
            c.WhichComparison =
                   Employee.EmployeeComparer.ComparisonType.EmpID;
            empList.Sort(c);

            // display all the contents of the Employee list
            for (int i = 0; i < empList.Count; i++)
            {
                Console.Write("
{0} ", empList[i].ToString( ));
            }
            Console.WriteLine("
");

            c.WhichComparison =
               Employee.EmployeeComparer.ComparisonType.YearsOfService;
            empList.Sort(c);

            for (int i = 0; i < empList.Count; i++)
            {
                Console.Write("
{0} ", empList[i].ToString( ));
            }
            Console.WriteLine("
");
        }
    }
}

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.

As you can see, the List is a lot like an array, but more programmable and versatile. There are plenty of situations where a simple array is all you need, and they’re easiest to learn, so you shouldn’t abandon them altogether. But when you need to go beyond the basics, a List can be useful. The other collection classes are more specific in their uses, as you’ll see.

Generic Queues

A queue is what you’ll hear referred to as a first-in, first-out (FIFO) collection. This is just a fancy way of saying that you add items to the queue one at a time, and you remove items one at a time, such that items are removed in the same order in which you added them. 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 your clients to send messages to a resource that can handle only 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 returns the number of elements in the Queue

Clear( )

Removes all objects from the Queue

Contains( )

Determines whether 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 a reference to 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( ) method and take them off the queue with Dequeue( ), or by using an enumerator, as shown in Example 14-7.

Example 14-7. A queue always returns items in the same order in which they were added

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Example_14_7_  _  _  _Queues
{
    public class Tester
    {

        static void Main( )
        {
            Queue<Int32> intQueue = new Queue<Int32>( );

            // populate the Queue.
            for (int i = 0; i < 5; i++)
            {
                intQueue.Enqueue(i * 5);
            }

            // Display the Queue.
            Console.Write("intQueue values:	");
            PrintValues(intQueue);

            // Remove an element from the Queue.
            Console.WriteLine("
(Dequeue)	{0}", intQueue.Dequeue( ));

            // Display the Queue.
            Console.Write("intQueue values:	");
            PrintValues(intQueue);

            // Remove another element from the Queue.
            Console.WriteLine("
(Dequeue)	{0}", intQueue.Dequeue( ));

            // Display the Queue.
            Console.Write("intQueue values:	");
            PrintValues(intQueue);

            // View the first element in the
            // Queue but do not remove.
            Console.WriteLine("
(Peek)  	{0}", intQueue.Peek( ));

            // Display the Queue.
            Console.Write("intQueue values:	");
            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

We’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 an object adds it to the Queue, and Dequeue( ) returns the object as well as removes it from the Queue. The Queue class also provides a Peek( ) method that allows you to see, but not remove, the next element.

Take a closer look at the PrintValues( ) method:

public static void PrintValues(IEnumerable<Int32> myCollection)
{
    IEnumerator<Int32> myEnumerator = myCollection.GetEnumerator( );
    while (myEnumerator.MoveNext( ))
    {
        Console.Write("{0} ", myEnumerator.Current);
    }

Because the Queue class is enumerable, you can pass it to the PrintValues( ) method, which takes an IEnumerable interface. The conversion is implicit. In the PrintValues method, you call GetEnumerator, which is the single method required by all IEnumerable classes, as we mentioned earlier in the chapter. GetEnumerator( ) returns an IEnumerator, which you then use to enumerate all the objects in the collection. MoveNext( ) is a method of IEnumerator, so no matter what collection type you’re using, you can always call MoveNext( ) to retrieve the next value. Current is the property of IEnumerator that represents the current value, so you can output the current value in the queue by outputting myEnumerator.Current. When there are no more values in the collection, MoveNext( ) returns false, which ends the while loop.

Note that we’re using a while loop here to demonstrate how IEnumerator works; in practice, you’d probably use a foreach loop instead.

Generic Stacks

The Stack is the natural partner of the Queue. A stack is a last-in, first-out (LIFO) collection, so the items are removed in the opposite of the order in which they were added. Think of a stack as a stack of dishes at a buffet table, or a stack of coins on your desk. You add a dish on top, and that’s the first dish you take off the stack. You’ve already seen a form of stack in Chapter 9, when we described the call stack. Each time you call a method, it’s added to the top of the call stack. When a method returns, it’s removed from the top of the stack.

The principal methods for adding to and removing from a stack are Push( ) and Pop( ); these method names are nonintuitive, but they’re traditional with stacks. Push( ) adds an item to the stack, and Pop( ) removes it. Stack also offers a Peek( ) method, very much like Queue. The significant methods and properties for Stack 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 whether 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. Stacks are similar to Queues, but items are removed in the reverse of the order in which they were added

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Example_14_8_  _  _  _Stacks
{
    public class Tester
    {
        static void Main( )
        {
            Stack<Int32> intStack = new Stack<Int32>( );

            // populate the Stack

            for (int i = 0; i < 8; i++)
            {
                intStack.Push(i * 5);
            }

            // Display the Stack.
            Console.Write("intStack values:	");
            PrintValues(intStack);

            // Remove an element from the Stack.
            Console.WriteLine("
(Pop)	{0}", intStack.Pop( ));

            // Display the Stack.
            Console.Write("intStack values:	");
            PrintValues(intStack);

            // Remove another element from the Stack.
            Console.WriteLine("
(Pop)	{0}", intStack.Pop( ));

            // Display the Stack.
            Console.Write("intStack values:	");
            PrintValues(intStack);

            // View the first element in the
            // Stack but do not remove.
            Console.WriteLine("
(Peek)   	{0}", intStack.Peek( ));

            // Display the Stack.
            Console.Write("intStack values:	");
            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("
Target 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("
Target 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 output reflects that the items pushed onto the Stack were popped in reverse order.

We can see the effect of CopyTo( ) by examining the target array before and after calling CopyTo( ). The array elements are overwritten beginning with the index specified (6).

Dictionaries

A dictionary is a collection that associates a key with a value. That is, it uses a non-numeric index. 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 by an index 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 you need the capital for Massachusetts, there is no easy way 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, or object) with any kind of value (string, integer, or 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 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

Values

Public property that gets a collection containing the values in the Dictionary

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 the method GetHashCode( ) as well as Equals. This is how the Dictionary works behind the scenes—there is actually a numeric index assigned to the value, but that index is associated with the key, so you never need to know what it is. GetHashCode( ) is so fundamental that it’s actually implemented in Object, the root base class. In most cases, you don’t need to worry about writing the GetHashCode( ) method; 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 same syntax as you would with an array. If you had a dictionary called addresses, which holds the addresses of various businesses, with the company name as the key, you’d access the address for O’Reilly like this:

addresses["O'Reilly"]

Note the quotation marks around "O'Reilly"—you need them because you’re using a string as your indexer.

Example 14-9 demonstrates adding items to a Dictionary and then retrieving them with the indexer (which implicitly uses the Dictionary’s Item property).

Example 14-9. The Dictionary collection uses nonnumeric indexers

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Example_14_9_  _  _  _Dictionaries
{
    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", "Salem");
            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"]);
        }
    }
}

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

We then added 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).

Warning

You must not change the value of the key object once you use it in a dictionary.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset