What Are Collection Classes?


What Are Collection Classes?

Arrays are useful, but they have their limitations. However, arrays are only one way to collect elements of the same type. The.NET Framework provides several classes that also collect elements together in other specialized ways. These are the Collection classes, and they live in the System.Collections namespace and sub-namespaces.

The basic collection classes accept, hold, and return their elements as objects. That is, the element type of a collection class is an object. To understand the implications of this, it is helpful to contrast an array of int variables (int is a value type) with an array of objects (object is a reference type). Because int is a value type, an array of int variables holds its int values directly, as shown in the following graphic:

graphic

Now consider the effect when the array is an array of objects. You can still add integer values to this array (in fact, you can add values of any type to it). When you add an integer value, it is automatically boxed and the array element (an object reference) refers to the boxed copy of the integer value. (For a refresher on boxing, refer to Chapter 8.) This is illustrated in the following graphic:

graphic

Remember that the element type of a collection class is an object. This means that when you insert a value into a collection, it is always boxed, and when you remove a value from a collection, you must unbox it by using a cast. The following sections provide a very quick overview of four of the most useful Collection classes. Refer to the .NET Framework documentation for more details on each class.

NOTE
In fact, there are Collection classes that don't always use object as their element type and that can hold value types as well as references, but you need to know a bit more about C# before we can talk about them. You will meet these Collection classes in Chapter 17, “Introducing Generics.”

The ArrayList Class

ArrayList is a useful class for shuffling elements around in an array. There are certain occasions when an ordinary array can be too restrictive:

  • If you want to resize an array, you have to create a new array, copy the elements (leaving out some if the new array is smaller), and then update the array references.

  • If you want to remove an element from an array, you have to make a copy of the element and then move all the trailing elements up by one place. Even this doesn't quite work because you end up with two copies of the last element.

  • If you want to insert an element into an array, you have to move elements down by one place to make a free slot. However, you lose the last element of the array!

    Here's how you can overcome these restrictions by using the ArrayList class:

  • You can remove an element from an ArrayList by using its Remove method. The ArrayList automatically reorders its elements.

    NOTE
    You cannot use the Remove method in a foreach loop that iterates through an ArrayList.

  • You can add an element to the end of an ArrayList by using its Add method. You supply the element to be added. The ArrayList resizes itself if necessary.

  • You can insert an element into the middle of an ArrayList by using its Insert method. Again, the ArrayList resizes itself if necessary.

Here's an example that shows how you can create, manipulate, and iterate through the contents of an ArrayList:

using System;  using System.Collections;  ...  ArrayList numbers = new ArrayList();  ...  // fill the ArrayList  foreach (int number in new int[12]{10,9,8,7,7,6,5,10,4,3,2,1})  {      numbers.Add(number);  }  ...  // remove first element whose value is 7 (the 4th element, index 3)  numbers.Remove(7);  // remove the element that's now the 7th element, index 6 (10)  numbers.RemoveAt(6);  ...  // iterate remaining 10 elements using a for statement  for (int i = 0; i != numbers.Count; i++)  {      int number = (int)numbers[i]; // Notice the cast      Console.WriteLine(number);  }  ...  // iterate remaining 10 using a foreach statement  foreach (int number in numbers) // No cast needed  {      Console.WriteLine(number);  }

The output of this code is shown below:

10  9  8  7  6  5  4  3  2  1  10  9  8  7  6  5  4  3  2  1

NOTE
You use the Count property to find out how many elements are inside a collection. This is different from arrays, which use the Length property.

The Queue Class

The Queue class implements a first-in first-out (FIFO) mechanism. An element is inserted into the queue at the back (the enqueue operation) and is removed from the queue at the front (the dequeue operation).

Here's an example of a queue and its operations:

using System;  using System.Collections;  ...  Queue numbers = new Queue();  ...  // fill the queue  foreach (int number in new int[4]{9, 3, 7, 2})  {      numbers.Enqueue(number);      Console.WriteLine(number + " has joined the queue");  }  ...  // iterate through the queue  foreach (int number in numbers)  {      Console.WriteLine(number);  }  ...  // empty the queue  while (numbers.Count != 0)  {      int number = (int)numbers.Dequeue();      Console.WriteLine(number + " has left the queue");  }

The output from this code is:

9 has joined the queue  3 has joined the queue  7 has joined the queue  2 has joined the queue  9  3  7  2  9 has left the queue  3 has left the queue  7 has left the queue  2 has left the queue

The Stack Class

The Stack class implements a last-in first-out (LIFO) mechanism. An element joins the stack at the top (the push operation) and leaves the stack at the top (the pop operation). To visualize this, think of a stack of dishes: new dishes are added to the top and dishes are removed from the top, making the last dish to be placed onto the stack the first one to be removed (the dish at the bottom is rarely used, and will inevitably require washing before you can put any food on it as it will be covered in grime!). Here's an example:

using System;  using System.Collections;  ...  Stack numbers = new Stack();  ...  // fill the stack  foreach (int number in new int[4]{9, 3, 7, 2})  {      numbers.Push(number);      Console.WriteLine(number + " has been pushed on the stack");  }  ...  // iterate through the stack  foreach (int number in numbers)  {      Console.WriteLine(number);  }  ...  // empty the stack  while (numbers.Count != 0)  {      int number = (int)numbers.Pop();      Console.WriteLine(number + "has been popped off the stack");  }

The output from this program is:

9 has been pushed on the stack  3 has been pushed on the stack  7 has been pushed on the stack  2 has been pushed on the stack  2  7  3  9  2 has been popped off the stack  7 has been popped off the stack  3 has been popped off the stack  9 has been popped off the stack

The Hashtable Class

The array and ArrayList types provide a way to map an integer index to an element. You provide an integer index inside square brackets (for example, [4]), and you get back the element at index 4 (which is actually the fifth element). However, sometimes you might want to provide a mapping where the type you map from is not an int but rather some other type, such as string, double, or Time. In other languages, this is often called an associative array. The Hashtable class provides this functionality by internally maintaining two object arrays, one for the keys you're mapping from and one for the values you're mapping to. When you insert a key/value pair into a Hashtable, it automatically tracks which key belongs to which value, and enables you to retrieve the value that is associated with a specified key. There are some important consequences of the design of the Hashtable class:

  • A Hashtable cannot contain duplicate keys. If you call the Add method to add a key that is already present in the keys array, you'll get an exception, unless you use the square bracket notation to add a key/value pair (see the following example). You can test whether a Hashtable already contains a particular key by using the ContainsKey method.

  • When you use a foreach statement to iterate through a Hashtable, you get back a DictionaryEntry. The DictionaryEntry class provides access to the key and value elements in both arrays through the Key property and the Value properties.

Here is an example that associates the ages of members of my family with their names, and then prints them out (yes, I really am younger than my wife):

using System;  using System.Collections;   ...  Hashtable ages = new Hashtable();  ...  // fill the SortedList  ages["John"] = 41;  ages["Diana"] = 42;  ages["James"] = 13;  ages["Francesca"] = 11;  ...  // iterate using a foreach statement  // the iterator generates a DictionaryEntry object containing a key/value pair  foreach (DictionaryEntry element in ages)  {      string name = (string)element.Key;      int age = (int)element.Value;      Console.WriteLine("Name: {0}, Age: {1}", name, age);  }

The output from this program is:

Name: James, Age: 13  Name: John, Age: 41   Name: Francesca, Age: 11   Name: Diana, Age: 42

The SortedList Class

The SortedList class is very similar to the Hashtable class in that it allows you to associate keys with values. The main difference is that the keys array is always sorted (it is called a SortedList, after all).

When you insert a key/value pair into a SortedList, the key is inserted into the keys array at the correct index to keep the keys array sorted. The value is then inserted into the values array at the same index. The SortedList class automatically ensures that keys and values are aligned, even when you add and remove elements. This means you can insert key/value pairs into a SortedList in any order you like; they are always sorted based on the value of the keys.

Like the Hashtable class, a SortedList cannot contain duplicate keys. When you use a foreach statement to iterate through a SortedList, you get back a DictionaryEntry. However, the DictionaryEntry objects will be returned sorted by the Key property.

Here is the same example that associates the ages of members of my family with their names, and then prints them out, adjusted to use a SortedList rather than a Hashtable:

using System;  using System.Collections;   ...  SortedList ages = new SortedList();  ...  // fill the SortedList  ages["John"] = 39;  ages["Diana"] = 40;  ages["James"] = 12;  ages["Francesca"] = 10;  ...  // iterate using a foreach statement  // the iterator generates a DictionaryEntry object containing a key/value pair  foreach (DictionaryEntry element in ages)  {      string name = (string)element.Key;      int age = (int)element.Value;      Console.WriteLine("Name: {0}, Age: {1}", name, age);  }

The output from this program is sorted by the names of my family members:

Name: Diana, Age: 40  Name: Francesca, Age: 10  Name: James, Age: 12  Name: John, Age: 39

Comparing Arrays and Collections

Here's a summary of the important differences between arrays and collections:

  • An array declares the type of the element it holds, whereas a collection doesn't. This is because the collections store their elements as objects.

  • An array instance has a fixed size and cannot grow or shrink. A collection can dynamically resize itself as required.

  • An array is a read/write data structure—there is no way to create a read-only array. However, it is possible to use the collections in a read-only fashion by using the ReadOnly method provided by these classes. This method returns a read-only version of a collection.

Using Collection Classes to Play Cards

The following exercise presents a Microsoft Windows application that simulates dealing a pack of cards to four players. Cards will either be in the pack, or will be in one of four hands dealt to the players. The pack and hands of cards are implemented as ArrayList objects. You might think that these should be implemented as an array—after all, there are always 52 cards in a pack, and 13 cards in a hand. This is true, but it overlooks the fact that when you deal the cards to players' hands, they are no longer in the pack. If you use an array to implement a pack, you'll have to record how many slots in the array actually hold a PlayingCard. Similarly, when you return cards from a player's hand to the pack, you'll have to record which slots in the hand no longer contain a PlayingCard.

You will study the code and then write two methods: one to shuffle a pack of cards and one to return the cards in a hand to the pack.

Deal the cards

  1. Start Microsoft Visual Studio 2005.

  2. Open the Cards project, located in the \Microsoft Press\Visual CSharp Step by Step\Chapter 10\Cards folder in your My Documents folder.

  3. On the Debug menu, click Start Without Debugging.

    Visual Studio 2005 builds and runs the program. The Windows form displays the cards in the hands of the four players (North, South, East, and West). There are also two buttons: one to deal the cards and one to return the cards to the pack.

  4. On the Windows form, click Deal.

    The 52 cards in the pack are dealt to the four hands, 13 cards per hand.

    graphic

    As you can see, the cards have not yet been shuffled. You will implement the Shuffle method in the next exercise.

  5. Click Return To Pack.

    Nothing happens because the method to return the cards to the pack has also not yet been written.

  6. Click Deal.

    This time the cards in each of the hands disappear, because before dealing the cards, each hand is reset. Because there are no cards in the pack (the method has not been written yet either), there is nothing to deal.

  7. Close the form to return to the Visual Studio 2005 programming environment.

Now that you know what parts are missing from this application, you will add them.

Shuffle the pack

  1. Open the Pack.cs source file in the Code and Text Editor window.

  2. Scroll through the code and examine it.

    The Pack class represents a pack of cards. It contains a private ArrayList field called cards. Notice also that the Pack class has a constructor that creates and adds the 52 playing cards to the ArrayList by using the Accept method defined by this class. The remaining methods in this class constitute the typical operations that you would perform on a pack of cards (Shuffle, Deal, etc).

    Playing cards themselves are represented by the PlayingCard class (in PlayingCard.cs). A playing card exposes two fields of note: suit (which is an enumerated type and is one of Clubs, Diamonds, Hearts, or Spades), and pips (which indicates the numeric value of the card).

  3. Locate the Shuffle method in the Pack class.

    The method is not currently implemented.

    There are a number of ways to simulate shuffling a pack of cards. Perhaps the simplest is to choose each card in sequence and swap it with another card selected at random. The .NET Framework contains a class called Random that you will now use to generate random integer numbers.

  4. Declare a local variable of type Random called random and initialize it to a newly created Random object by using the default Random constructor.

    The Shuffle method should look like this:

    public void Shuffle()  {      Random random = new Random();  }

  5. Add a for statement with an empty body that iterates an int i from 0 up to the number of elements inside the cards ArrayList.

    The Shuffle method should now look like this:

    public void Shuffle()  {      Random random = new Random();      for (int i = 0; i != cards.Count; i++)      {      }  }

    The next step is to choose a random index between 0 and cards.Count. You will then swap the card at index i with the card at this random index. You can generate a positive random integer by calling the Random.Next instance method. The value of this positive random integer will almost certainly be greater than cards.Count. You must then convert this value into the range 0 to cards.Count – 1 inclusive. The easiest way to do this is to use the % operator to find the remainder when dividing by cards.Count.

  6. Inside the for statement, declare a local variable called cardToSwap and initialize it to a random number between 0 and cards.Count.

    The Shuffle method should now look exactly like this:

    public void Shuffle()  {      Random random = new Random();      for (int i = 0; i != cards.Count; i++)      {          int cardToSwap = random.Next() % cards.Count;      }  }

    The final step is to swap the card at index i with the card at index cardToSwap. To do this, you must use a temporary local variable. Remember that the elements inside a collection class (such as ArrayList) are of type System.Object (object).

  7. Add three statements to swap the card at index i with the card at index cardToSwap.

    The Shuffle method should now look exactly like this:

    public void Shuffle()  {      Random random = new Random();      for (int i = 0; i != cards.Count; i++)      {          int cardToSwap = random.Next() % cards.Count;          object temp = cards[i];          cards[i] = cards[cardToSwap];          cards[cardToSwap] = temp;      }  }

    Notice that you have to use a for statement here. A foreach statement would not work because you need to modify each element in the ArrayList and a foreach loop provides read-only access.

  8. On the Debug menu, click Start Without Debugging.

    Visual Studio 2005 builds and runs the program.

  9. Click Deal.

    This time the pack is shuffled before dealing. (Your screen will differ slightly each time, because the card order is now random).

    graphic

  10. Close the form.

The final step is to add the code to return cards to the pack.

Return the cards to the pack

  1. Display the Hand.cs source file in the Code and Text Editor window.

    The Hand class, which also contains an ArrayList called cards, represents the cards held by a player. The idea is that at any one time, each card is either in the pack or in a hand.

  2. Locate the ReturnCardsTo method in the Hand class.

    The Pack class has a method called Accept that takes a single parameter of type PlayingCard. You need to create a loop that goes through the cards in the hand and passes them back to the pack.

  3. Complete the ReturnCardsTo method as follows:

    public void ReturnCardsTo(Pack pack)  {      foreach (PlayingCard card in cards)      {          pack.Accept(card);      }      cards.Clear();  }

    A foreach statement is convenient here because you do not need write access to the element and you do not need to know the index of the element. The Clear method removes all elements from a collection. It is important to call cards.Clear after returning the cards to the pack so that the cards aren't in both the pack and the hand.

  4. On the Debug menu, click Start Without Debugging.

  5. Click Deal.

    The shuffled cards are dealt to the four hands as before.

  6. Click Return To Pack.

    The hands are cleared. The cards are now back in the pack.

  7. Click Deal.

    The shuffled cards are once again dealt to the four hands.

  8. Close the form.

    NOTE
    If you click the Deal button twice without clicking Return to Pack, you lose all the cards. In the real world, you would disable the Deal button until the Return to Pack button was pressed. We will look more at using C# to write user-interface code like this in Part IV of this book.

In this chapter, you have learned how to create and use arrays to manipulate sets of data. You have also seen how to use some of the common collection classes to store and access data in memory in different ways.

  • If you want to continue to the next chapter

    Keep Visual Studio 2005 running and turn to Chapter 11.

  • If you want to exit Visual Studio 2005 now

    On the File menu, click Exit. If you see a Save dialog box, click Yes.




Microsoft Visual C# 2005 Step by Step
Microsoft® Visual C#® 2005 Step by Step (Step By Step (Microsoft))
ISBN: B002CKYPPM
EAN: N/A
Year: 2005
Pages: 183
Authors: John Sharp

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net