Using Arrays - Microsoft® Visual C#® 2012 Step by Step (2012)

Microsoft® Visual C#® 2012 Step by Step (2012)

Chapter 10. Using Arrays

After completing this chapter, you will be able to

§ Declare array variables.

§ Populate an array with a set of data items.

§ Access the data items held in an array.

§ Iterate through the data items in an array.

You have already seen how to create and use variables of many different types. However, all the examples of variables you have seen so far have one thing in common—they hold information about a single item (an int, a float, a Circle, a Date, and so on). What happens if you need to manipulate a set of items? One solution is to create a variable for each item in the set, but this leads to a number of further questions: How many variables do you need? How should you name them? If you need to perform the same operation on each item in the set (such as increment each variable in a set of integers), how would you avoid very repetitive code? This solution assumes that you know, when you write the program, how many items you will need, but how often is this the case? For example, if you are writing an application that reads and processes records from a database, how many records are in the database, and how likely is this number to change?

Arrays provide a mechanism that helps to solve the problems posed by these questions.

Declaring and Creating an Array

An array is an unordered sequence of items. All the items in an array have the same type, unlike the fields in a structure or class, which can have different types. The items in an array live in a contiguous block of memory and are accessed by using an index, unlike fields in a structure or class, which are accessed by name.

Declaring Array Variables

You declare an array variable by specifying the name of the element type, followed by a pair of square brackets, followed by the variable name. The square brackets signify that the variable is an array. For example, to declare an array of int variables named pins (for holding a set of personal identification numbers) you can write

int[] pins; // Personal Identification Numbers

NOTE

Microsoft Visual Basic programmers should observe that square brackets, not parentheses, are used in the declaration. C and C++ programmers should note that the size of the array is not part of the declaration. Java programmers should discern that the square brackets must be placed before the variable name.

You are not restricted to primitive types as array elements. You can also create arrays of structures, enumerations, and classes. For example, you can create an array of Date structures like this:

Date[] dates;

TIP

It is often useful to give array variables plural names, such as places (where each element is a Place), people (where each element is a Person), or times (where each element is a Time).

Creating an Array Instance

Arrays are reference types, regardless of the type of their elements. This means that an array variable refers to a contiguous block of memory holding the array elements on the heap, just as a class variable refers to an object on the heap. (To review values and references and the differences between the stack and the heap, see Chapter 8) This rule applies regardless of the type of the data items in the array. Even if the array contains a value type such as int, the memory will still be allocated on the heap; this is the one case where value types are not allocated memory on the stack.

Remember that when you declare a class variable, memory is not allocated for the object until you create the instance by using new. Arrays follow the same pattern: when you declare an array variable, you do not declare its size and no memory is allocated (other than to hold the reference on the stack). The array is given memory only when the instance is created, and this is also the point at which you specify the size of the array.

To create an array instance, you use the new keyword followed by the element type, followed by the size of the array you’re creating between square brackets. Creating an array also initializes its elements by using the now familiar default values (0, null, or false, depending on whether the type is numeric, a reference, or a Boolean, respectively). For example, to create and initialize a new array of four integers for the pins variable declared earlier, you write this:

pins = new int[4];

The following graphic illustrates what happens when you declare an array, and later when you create an instance of the array:

image with no caption

Because the memory for the array instance is allocated dynamically, the size of the array does not have to be a constant; it can be calculated at run time, as shown in this example:

int size = int.Parse(Console.ReadLine());

int[] pins = new int[size];

You’re also allowed to create an array whose size is 0. This might sound bizarre, but it’s useful in situations where the size of the array is determined dynamically and could even be 0. An array of size 0 is not a null array; it is an array containing zero elements.

Populating and Using an Array

When you create an array instance, all the elements of the array are initialized to a default value depending on their type. For example, all numeric values default to 0, objects are initialized to null, DateTime values are set to the date and time “01/01/0001 00:00:00”, and strings are initialized to null. You can modify this behavior and initialize the elements of an array to specific values if you prefer. You achieve this by providing a comma-separated list of values between a pair of braces. For example, to initialize pins to an array of four int variables whose values are 9, 3, 7, and 2, you write this:

int[] pins = new int[4]{ 9, 3, 7, 2 };

The values between the braces do not have to be constants—they can be values calculated at run time, as shown in this example, which populates the pins array with four random numbers:

Random r = new Random();

int[] pins = new int[4]{ r.Next() % 10, r.Next() % 10,

r.Next() % 10, r.Next() % 10 };

NOTE

The System.Random class is a pseudorandom number generator. The Next method returns a nonnegative random integer in the range 0 to Int32.MaxValue by default. The Next method is overloaded, and other versions enable you to specify the minimum value and maximum value of the range. The default constructor for the Random class seeds the random number generator with a time-dependent seed value, which reduces the possibility of the class duplicating a sequence of random numbers. An overloaded version of the constructor enables you to provide your own seed value. That way, you can generate a repeatable sequence of random numbers for testing purposes.

The number of values between the braces must exactly match the size of the array instance being created:

int[] pins = new int[3]{ 9, 3, 7, 2 }; // compile-time error

int[] pins = new int[4]{ 9, 3, 7 }; // compile-time error

int[] pins = new int[4]{ 9, 3, 7, 2 }; // OK

When you’re initializing an array variable in this way, you can actually omit the new expression and the size of the array. In this case, the compiler calculates the size from the number of initializers and generates code to create the array. For example:

int[] pins = { 9, 3, 7, 2 };

If you create an array of structures or objects, you can initialize each structure in the array by calling the structure or class constructor, as shown in this example:

Time[] schedule = { new Time(12,30), new Time(5,30) };

Creating an Implicitly Typed Array

The element type when you declare an array must match the type of elements that you attempt to store in the array. For example, if you declare pins to be an array of int, as shown in the preceding examples, you cannot store a double, string, struct, or anything that is not an int in this array. If you specify a list of initializers when declaring an array, you can let the C# compiler infer the actual type of the elements in the array for you, like this:

var names = new[]{"John", "Diana", "James", "Francesca"};

In this example, the C# compiler determines that the names variable is an array of strings. It is worth pointing out a couple of syntactic quirks in this declaration. First, you omit the square brackets from the type; the names variable in this example is declared simply as var, not var[]. Second, you must specify the new operator and square brackets before the initializer list.

If you use this syntax, you must ensure that all the initializers have the same type. This next example causes the compile-time error “No best type found for implicitly typed array”:

var bad = new[]{"John", "Diana", 99, 100};

However, in some cases, the compiler will convert elements to a different type if doing so makes sense. In the following code, the numbers array is an array of double because the constants 3.5 and 99.999 are both double, and the C# compiler can convert the integer values 1 and 2 to doublevalues:

var numbers = new[]{1, 2, 3.5, 99.999};

Generally, it is best to avoid mixing types and hoping that the compiler will convert them for you.

Implicitly typed arrays are most useful when you are working with anonymous types, as described in Chapter 7 The following code creates an array of anonymous objects, each containing two fields specifying the name and age of the members of my family:

var names = new[] { new { Name = "John", Age = 47 },

new { Name = "Diana", Age = 46 },

new { Name = "James", Age = 20 },

new { Name = "Francesca", Age = 18 } };

The fields in the anonymous types must be the same for each element of the array.

Accessing an Individual Array Element

To access an individual array element, you must provide an index indicating which element you require. Array indexes are zero-based. The initial element of an array lives at index 0 and not index 1. An index value of 1 accesses the second element. For example, you can read the contents of element 2 of the pins array into an int variable by using the following code:

int myPin;

myPin = pins[2];

Similarly, you can change the contents of an array by assigning a value to an indexed element:

myPin = 1645;

pins[2] = myPin;

All array element access is bounds-checked. If you specify an index that is less than 0 or greater than or equal to the length of the array, the compiler throws an IndexOutOfRangeException exception, as in this example:

try

{

int[] pins = { 9, 3, 7, 2 };

Console.WriteLine(pins[4]); // error, the 4th and last element is at index 3

}

catch (IndexOutOfRangeException ex)

{

...

}

Iterating Through an Array

All arrays are actually instances of the System.Array class in the Microsoft .NET Framework, and this class defines a number of useful properties and methods. For example, you can query the Length property to discover how many elements an array contains and iterate through all the elements of an array by using a for statement. The following sample code writes the array element values of the pins array to the console:

int[] pins = { 9, 3, 7, 2 };

for (int index = 0; index < pins.Length; index++)

{

int pin = pins[index];

Console.WriteLine(pin);

}

NOTE

Length is a property and not a method, which is why you don’t use parentheses when you call it. You will learn about properties in Chapter 15

It is common for new programmers to forget that arrays start at element 0 and that the last element is numbered Length – 1. C# provides the foreach statement to enable you to iterate through the elements of an array without worrying about these issues. For example, here’s the preceding forstatement rewritten as an equivalent foreach statement:

int[] pins = { 9, 3, 7, 2 };

foreach (int pin in pins)

{

Console.WriteLine(pin);

}

The foreach statement declares an iteration variable (in this example, int pin) that automatically acquires the value of each element in the array. The type of this variable must match the type of the elements in the array. The foreach statement is the preferred way to iterate through an array; it expresses the intention of the code directly, and all of the for loop scaffolding drops away. However, in a few cases, you’ll find that you have to revert to a for statement:

§ A foreach statement always iterates through the whole array. If you want to iterate through only a known portion of an array (for example, the first half) or bypass certain elements (for example, every third element), it’s easier to use a for statement.

§ A foreach statement always iterates from index 0 through index Length – 1. If you want to iterate backward or in some other sequence, it’s easier to use a for statement.

§ If the body of the loop needs to know the index of the element rather than just the value of the element, you’ll have to use a for statement.

§ If you need to modify the elements of the array, you’ll have to use a for statement. This is because the iteration variable of the foreach statement is a read-only copy of each element of the array.

TIP

It’s perfectly safe to attempt to iterate through a zero-length array by using a foreach statement.

You can declare the iteration variable as a var and let the C# compiler work out the type of the variable from the type of the elements in the array. This is especially useful if you don’t actually know the type of the elements in the array, such as when the array contains anonymous objects. The following example demonstrates how you can iterate through the array of family members shown earlier:

var names = new[] { new { Name = "John", Age = 47 },

new { Name = "Diana", Age = 46 },

new { Name = "James", Age = 20 },

new { Name = "Francesca", Age = 18 } };

foreach (var familyMember in names)

{

Console.WriteLine("Name: {0}, Age: {1}", familyMember.Name, familyMember.Age);

}

Passing Arrays as Parameters and Return Values for a Method

You can define methods that take arrays as parameters or pass them back as return values.

The syntax for passing an array as a parameter is much the same as declaring an array. For example, the following code sample defines a method called ProcessData that takes an array of integers as a parameter. The body of the method iterates through the array and performs some unspecified processing on each element:

public void ProcessData(int[] data)

{

foreach (int i in data)

{

...

}

}

It is important to remember that arrays are reference objects, so if you modify the contents of an array passed as a parameter inside a method such as ProcessData, then the modification is visible through all references to the array, including the original argument passed as the parameter.

To return an array from a method, you specify the type of the array as the return type. In the method, you create and populate the array. The following example prompts the user for the size of an array, followed by the data for each element. The array created by the method is passed back as the return value:

public int[] ReadData()

{

Console.WriteLine("How many elements?");

string reply = Console.ReadLine();

int numElements = int.Parse(reply);

int[] data = new int[numElements];

for (int i = 0; i < numElements; i++)

{

Console.WriteLine("Enter data for element {0}", i);

reply = Console.ReadLine();

int elementData = int.Parse(reply);

data[i] = elementData;

}

return data;

}

You can call the ReadData method like this:

int[] data = ReadData();

ARRAY PARAMETERS AND THE MAIN METHOD

You may have noticed that the Main method for an application takes an array of strings as a parameter:

static void Main(string[] args)

{

...

}

Remember that the Main method is called when your program starts running; it is the entry point of your application. If you start the application from the command line, you can specify additional command-line arguments. The Microsoft Windows operating system passes these arguments to the common language runtime (CLR), which in turn passes them as arguments to the Main method. This mechanism gives you a simple way to enable a user to provide information when an application starts running rather than prompting the user interactively, which is useful if you want to build utilities that can be run from automated scripts.

The following example is taken from a utility application called MyFileUtil that processes files. It expects a set of file names on the command line, and it calls the ProcessFile method (not shown) to handle each file specified:

static void Main(string[] args)

{

foreach (string filename in args)

{

ProcessFile(filename);

}

}

The user can run the MyFileUtil application from the command line like this:

MyFileUtil C:\Temp\TestData.dat C:\Users\John\Documents\MyDoc.txt

Each command line argument is separated by a space. It is up to the MyFileUtil application to verify that these arguments are valid.

Copying Arrays

Arrays are reference types (remember that an array is an instance of the System.Array class). An array variable contains a reference to an array instance. This means that when you copy an array variable, you actually end up with two references to the same array instance, for example:

int[] pins = { 9, 3, 7, 2 };

int[] alias = pins; // alias and pins refer to the same array instance

In this example, if you modify the value at pins[1], the change will also be visible by reading alias[1].

If you want to make a copy of the array instance (the data on the heap) that an array variable refers to, you have to do two things. First, you create a new array instance of the same type and the same length as the array you are copying. Second, you copy the data element by element from the original array to the new array, as in this example:

int[] pins = { 9, 3, 7, 2 };

int[] copy = new int[pins.Length];

for (int i = 0; i < pins.Length; i++)

{

copy[i] = pins[i];

}

Note that this code uses the Length property of the original array to specify the size of the new array.

Copying an array is actually a common requirement of many applications—so much so that the System.Array class provides some useful methods that you can employ to copy an array rather than writing your own code. For example, the CopyTo method copies the contents of one array into another array given a specified starting index. The following example copies all the elements from the pins array to the copy array starting at element zero:

int[] pins = { 9, 3, 7, 2 };

int[] copy = new int[pins.Length];

pins.CopyTo(copy, 0);

Another way to copy the values is to use the System.Array static method named Copy. As with CopyTo, you must initialize the target array before calling Copy:

int[] pins = { 9, 3, 7, 2 };

int[] copy = new int[pins.Length];

Array.Copy(pins, copy, copy.Length);

NOTE

Make sure that you specify a valid value for the length parameter of the Array.Copy method. If you provide a negative value, the method throws an ArgumentOutOfRangeException exception. If you specify a value that is greater than the number of elements in the source array, the method throws an ArgumentException exception.

Yet another alternative is to use the System.Array instance method named Clone. You can call this method to create an entire array and copy it in one action:

int[] pins = { 9, 3, 7, 2 };

int[] copy = (int[])pins.Clone();

NOTE

Clone methods were first described in Chapter 8. The Clone method of the Array class returns an object rather than Array, which is why you must cast it to an array of the appropriate type when you use it. Furthermore, the Clone, CopyTo, and Copy methods all create a shallow copy of an array (shallow and deep copying are described in Chapter 8). If the elements in the array being copied contain references, the Clone method simply copies the references rather than the objects being referred to. After copying, both arrays refer to the same set of objects. If you need to create a deep copy of such an array, you must use appropriate code in a for loop.

Using Multidimensional Arrays

The arrays shown so far have contained only a single dimension, and you can think of them as simple lists of values. You can create arrays with more than one dimension. For example, to create a two-dimensional array, you specify an array that requires two integer indexes. The following code creates a two-dimensional array of 24 integers called items. If it helps, you can think of the array as a table with the first dimension specifying a number of rows and the second specifying a number of columns.

int[,] items = new int[4, 6];

To access an element in the array, you provide two index values to specify the “cell” holding the element. (A cell is the intersection of a row and a column.) The following code shows some examples using the items array:

items[2, 3] = 99; // set the element at cell(2,3) to 99

items[2, 4] = items [2,3]; // copy the element in cell(2, 3) to cell(2, 4)

items[2, 4]++; // increment the integer value at cell(2, 4)

There is no limit on the number of dimensions that you can specify for an array. The next code example creates and uses an array called cube that contains three dimensions. Notice that you must specify three indexes to access each element in the array.

int[, ,] cube = new int[5, 5, 5];

cube[1, 2, 1] = 101;

cube[1, 2, 2] = cube[1, 2, 1] * 3;

At this point, it is worth giving a word of caution about creating arrays with more than three dimensions. Specifically, arrays can be very memory hungry. The cube array contains 125 elements (5 * 5 * 5). A four-dimensional array where each dimension has a size of 5 contains 625 elements. If you start to create arrays with three or more dimensions, you can soon run out of memory. Therefore, you should always be prepared to catch and handle OutOfMemoryException exceptions when you use multidimensional arrays.

Creating Jagged Arrays

In C#, ordinary multidimensional arrays are sometimes referred to as rectangular arrays. Each dimension has a regular shape. For example, in the following tabular two-dimensional items array, every row has a column containing 40 elements, and there are 160 elements in total:

int[,] items = new int[4, 40];

As mentioned in the previous section, multidimensional arrays can consume a lot of memory. If the application uses only some of the data in each column, then allocating memory for unused elements is a waste. In this scenario, you can use a jagged array, where each column has a different length, like this:

int[][] items = new int[4][];

int[] columnForRow0 = new int[3];

int[] columnForRow1 = new int[10];

int[] columnForRow2 = new int[40];

int[] columnForRow3 = new int[25];

items[0] = columnForRow0;

items[1] = columnForRow1;

items[2] = columnForRow2;

items[3] = columnForRow3;

...

In this example, the application requires only 3 elements in the first column, 10 elements in the second column, 40 elements in the third column, and 25 elements in the final column. This code illustrates an array of arrays—rather than items being a two-dimensional array, it has only a single dimension, but the elements in that dimension are themselves arrays. Furthermore, the total size of the items array is 78 elements rather than 160; no space is allocated for elements that the application is not going to use.

It is worth highlighting some of the syntax in this example. The following declaration specifies that items is an array of arrays of int.

int[][] items;

The following statement initializes items to hold four elements, each of which is an array of indeterminate length:

items = new int[4][];

The arrays columnForRow0 to columnForRow3 are all single-dimensional int arrays, initialized to hold the required amount of data for each column. Finally, each column array is assigned to the appropriate elements in the items array, like this:

items[0] = columnForRow0;

Recall that arrays are reference objects, so this statement simply adds a reference to columnForRow0 to the first element in the items array; it does not actually copy any data. You can populate data in this column either by assigning a value to an indexed element in columnForRow0 or by referencing it through the items array. The following statements are equivalent:

columnForRow0[1] = 99;

items[0][1] = 99;

You can extend this idea further if you want to create arrays of arrays of arrays rather than rectangular three-dimensional arrays, and so on.

NOTE

If you have written code using the Java programming language in the past, you should be familiar with this concept. Java does not have multidimensional arrays, but instead you can create arrays of arrays exactly as just described.

In the following exercise, you will use arrays to implement an application that deals playing cards as part of a card game. The application displays a form with four hands of cards dealt at random from a regular (52-card) pack of playing cards. You will complete the code that deals the cards for each hand.

Use arrays to implement a card game

1. Start Microsoft Visual Studio 2012 if it is not already running.

2. Open the Cards project, located in the \Microsoft Press\Visual CSharp Step By Step\Chapter 10\Windows X\Cards Using Arrays folder in your Documents folder.

3. On the DEBUG menu, click Start Debugging to build and run the application.

A form appears with the caption Card Game, four text boxes (labeled North, South, East, and West), and a button with the caption Deal.

If you are using Windows 7, the form looks like this:

image with no caption

If you are using Windows 8, the Deal button is on the app bar rather than on the main form, and the application looks like this:

image with no caption

NOTE

This is the preferred mechanism for locating command buttons in Windows Store apps, and from here on all Windows Store apps presented in this book will follow this style. To display the app bar, right-click the form.

4. Click Deal.

Nothing happens. You have not yet implemented the code that deals the cards; this is what you will do in this exercise.

5. Return to Visual Studio 2012, and on the DEBUG menu click Stop Debugging.

6. In Solution Explorer, locate the Value.cs file. Open this file in the Code and Text Editor window.

This file contains an enumeration called Value, which represents the different values that a card can have, in ascending order:

enum Value { Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King,

Ace }

7. Open the Suit.cs file in the Code and Text Editor window.

This file contains an enumeration called Suit, which represents the suits of cards in a regular pack:

enum Suit { Clubs, Diamonds, Hearts, Spades }

8. Display the PlayingCard.cs file in the Code and Text Editor window.

This file contains the PlayingCard class. This class models a single playing card.

class PlayingCard

{

private readonly Suit suit;

private readonly Value value;

public PlayingCard(Suit s, Value v)

{

this.suit = s;

this.value = v;

}

public override string ToString()

{

string result = string.Format("{0} of {1}", this.value, this.suit);

return result;

}

public Suit CardSuit()

{

return this.suit;

}

public Value CardValue()

{

return this.value;

}

}

This class has two readonly fields that represent the value and suit of the card. The constructor initializes these fields.

NOTE

A readonly field is useful for modeling data that should not change after it has been initialized. You can assign a value to a readonly field by using an initializer when you declare it, or in a constructor, but thereafter you cannot change it.

The class contains a pair of methods called CardValue and CardSuit that return this information, and it overrides the ToString method to return a string representation of the card.

NOTE

The CardValue and CardSuit methods are actually better implemented as properties. You will learn how to do this in Chapter 15.

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

This file contains the Pack class, which models a pack of playing cards. At the top of the Pack class are two public const int fields called NumSuits and CardsPerSuit. These two fields specify the number of suits in a pack of cards and the number of cards in each suit. The privatecardPack variable is a two-dimensional array of PlayingCard objects. You will use the first dimension to specify the suit and the second dimension to specify the value of the card in the suit. The randomCardSelector variable is a random number generated based on the Random class. You will use the randomCardSelector variable to help shuffle the cards before they are dealt to each hand.

class Pack

{

public const int NumSuits = 4;

public const int CardsPerSuit = 13;

private PlayingCard[,] cardPack;

private Random randomCardSelector = new Random();

...

}

10.Locate the default constructor for the Pack class. Currently, this constructor is empty apart from a // TODO: comment. Delete the comment, and add the statement shown in bold to instantiate the cardPack array with the appropriate values for each dimension:

11.public Pack()

12.{

13. this.cardPack = new PlayingCard[NumSuits, CardsPerSuit];

}

14.Add the following code shown in bold to the Pack constructor. These statements populate the cardPack array with a full, sorted deck of cards.

15.public Pack()

16.{

17. this.cardPack = new PlayingCard[NumSuits, CardsPerSuit];

18. for (Suit suit = Suit.Clubs; suit <= Suit.Spades; suit++)

19. {

20. for (Value value = Value.Two; value <= Value.Ace; value++)

21. {

22. this.cardPack[(int)suit, (int)value] = new PlayingCard(suit, value);

23. }

24. }

}

The outer for loop iterates through the list of values in the Suit enumeration, and the inner loop iterates through the values each card can have in each suit. The inner loop creates a new PlayingCard object of the specified suit and value, and adds it to the appropriate element in thecardPack array.

NOTE

You must use one of the integer types as indexes into an array. The suit and value variables are enumeration variables. However, enumerations are based on the integer types, so it is safe to cast them to int as shown in the code.

25.Find the DealCardFromPack method in the Pack class. The purpose of this method is to pick a random card from the pack, remove the card from the pack to prevent it from being selected again, and then pass it back as the return value from the method.

The first task in this method is to pick a suit at random. Delete the comment and the statement that throws the NotImplementedException exception from this method and replace them with the following statement shown in bold:

public PlayingCard DealCardFromPack()

{

Suit suit = (Suit)randomCardSelector.Next(NumSuits);

}

This statement uses the Next method of the randomCardSelector random number generator object to return a random number corresponding to a suit. The parameter to the Next method specifies the exclusive upper bound of the range to use; the value selected is between 0 and this value minus 1. Note that the value returned is an int, so it has to be cast before you can assign it a Suit variable.

There is always the possibility that there are no more cards left of the selected suit. You need to handle this situation and pick another suit if necessary.

26.After the code that selects a suit at random, add the following while loop shown in bold. This loop calls the IsSuitEmpty method to determine whether there are any cards of the specified suit left in the pack (you will implement the logic for this method shortly). If not, it picks another suit at random (it might actually pick the same suit again) and checks again. The loop repeats the process until it finds a suit with at least one card left.

27.public PlayingCard DealCardFromPack()

28.{

29. Suit suit = (Suit)randomCardSelector.Next(NumSuits);

30. while (this.IsSuitEmpty(suit))

31. {

32. suit = (Suit)randomCardSelector.Next(NumSuits);

33. }

}

34.You have now selected a suit at random with at least one card left. The next task is to pick a card at random in this suit. You can use the random number generator to select a card value, but as before, there is no guarantee that the card with the chosen value has not already been dealt. However, you can use the same idiom as before: call the IsCardAlreadyDealt method (which you will examine and complete later) to determine whether the card has been dealt before, and if so, pick another card at random and try again, repeating the process until a card is found. Add the following statements shown in bold to the DealCardFromPack method, after the existing code, to do this:

35.public PlayingCard DealCardFromPack()

36.{

37. ...

38. Value value = (Value)randomCardSelector.Next(CardsPerSuit);

39. while (this.IsCardAlreadyDealt(suit, value))

40. {

41. value = (Value)randomCardSelector.Next(CardsPerSuit);

42. }

}

43.You have now selected a random playing card that has not been dealt previously. Add the following code to the end of the DealCardFromPack method to return this card and set the corresponding element in the cardPack array to null:

44.public PlayingCard DealCardFromPack()

45.{

46. ...

47. PlayingCard card = this.cardPack[(int)suit, (int)value];

48. this.cardPack[(int)suit, (int)value] = null;

49. return card;

}

50.Locate the IsSuitEmpty method. Remember that the purpose of this method is to take a Suit parameter and return a Boolean value indicating whether there are any more cards of this suit left in the pack. Delete the comment and the statement that throws the NotImplementedExceptionexception from this method, and add the following code shown in bold:

51.private bool IsSuitEmpty(Suit suit)

52.{

53. bool result = true;

54. for (Value value = Value.Two; value <= Value.Ace; value++)

55. {

56. if (!IsCardAlreadyDealt(suit, value))

57. {

58. result = false;

59. break;

60. }

61. }

62.

63. return result;

}

This code iterates through the possible card values and determines whether there is a card left in the cardPack array that has the specified suit and value by using the IsCardAlreadyDealt method, which you will complete in the next step. If the loop finds a card, the value in the resultvariable is set to false and the break statement causes the loop to terminate. If the loop completes without finding a card, the result variable remains set to its initial value of true. The value of the result variable is passed back as the return value of the method.

64.Find the IsCardAlreadyDealt method. The purpose of this method is to determine whether the card with the specified suit and value has already been dealt and removed from the pack. You will see later that when the DealFromPack method deals a card, it removes the card from thecardPack array and sets the corresponding element to null. Replace the comment and the statement that throws the NotImplementedException exception in this method with the code shown in bold:

65.private bool IsCardAlreadyDealt(Suit suit, Value value)

66.{

67. return (this.cardPack[(int)suit, (int)value] == null);

}

This statement returns true if the element in the cardPack array corresponding to the suit and value is null, and it returns false otherwise.

68.The next step is to add the selected playing card to a hand. Open the Hand.cs file, and display it in the Code and Text Editor window. This file contains the Hand class, which implements a hand of cards (that is, all cards dealt to one player).

This file contains a public const int field called HandSize, which is set to the size of a hand of cards (13). It also contains an array of PlayingCard objects, which is initialized by using the HandSize constant. The playingCardCount field will be used by your code to keep track of how many cards the hand currently contains as it is being populated.

class Hand

{

public const int HandSize = 13;

private PlayingCard[] cards = new PlayingCard[HandSize];

private int playingCardCount = 0;

...

}

The ToString method generates a string representation of the cards in the hand. It uses a foreach loop to iterate through the items in the cards array and calls the ToString method on each PlayingCard object it finds. These strings are concatenated together with a newline character in between (the \n character) for formatting purposes.

public override string ToString()

{

string result = "";

foreach (PlayingCard card in this.cards)

{

result += card.ToString() + "\n";

}

return result;

}

69.Locate the AddCardToHand method in the Hand class. The purpose of this method is to add the playing card specified as the parameter to the hand. Add the statements shown in bold to this method:

70.public void AddCardToHand(PlayingCard cardDealt)

71.{

72. if (this.playingCardCount >= HandSize)

73. {

74. throw new ArgumentException("Too many cards");

75. }

76. this.cards[this.playingCardCount] = cardDealt;

77. this.playingCardCount++;

}

This code first checks to make sure that the hand is not already full and throws an Argument Exception exception if it is (this should never occur, but it is good practice to be safe). Otherwise, the card is added to the cards array at the index specified by the playingCardCount variable, and this variable is then incremented.

78.In Solution Explorer, expand the MainWindow.xaml node and then open the MainWindow.xaml.cs file in the Code and Text Editor window. This is the code for the Card Game window. Locate the dealClick method. This method runs when the user clicks the Deal button. Currently, it contains an empty try block and an exception handler that displays a message if an exception occurs.

79.Add the following statement shown in bold to the try block:

80.private void dealClick(object sender, RoutedEventArgs e)

81.{

82. try

83. {

84. pack = new Pack();

85. }

86. catch (Exception ex)

87. {

88. ...

89. }

}

This statement simply creates a new pack of cards. You earlier saw that this class contains a two-dimensional array holding the cards in the pack, and the constructor populates this array with the details of each card. You now need to create four hands of cards from this pack.

90.Add the following statements shown in bold to the try block:

91.try

92.{

93. pack = new Pack();

94.

95. for (int handNum = 0; handNum < NumHands; handNum++)

96. {

97. hands[handNum] = new Hand();

98. }

99.}

100. catch (Exception ex)

101. {

102. ...

}

This for loop creates four hands from the pack of cards and stores them in an array called hands. Each hand is initially empty, so you need to deal the cards from the pack to each hand.

103. Add the following code shown in bold to the for loop:

104. try

105. {

106. ...

107. for (int handNum = 0; handNum < NumHands; handNum++)

108. {

109. hands[handNum] = new Hand();

110. for (int numCards = 0; numCards < Hand.HandSize; numCards++)

111. {

112. PlayingCard cardDealt = pack.DealCardFromPack();

113. hands[handNum].AddCardToHand(cardDealt);

114. }

115. }

116. }

117. catch (Exception ex)

118. {

119. ...

}

The inner for loop populates each hand by using the DealCardFromPack method to retrieve a card at random from the pack and the AddCardToHand method to add this card to a hand.

120. Add the following code shown in bold after the outer for loop:

121. try

122. {

123. ...

124. for (int handNum = 0; handNum < NumHands; handNum++)

125. {

126. ...

127. }

128.

129. north.Text = hands[0].ToString();

130. south.Text = hands[1].ToString();

131. east.Text = hands[2].ToString();

132. west.Text = hands[3].ToString();

133. }

134. catch (Exception ex)

135. {

136. ...

}

When all the cards have been dealt, this code displays each hand in the text boxes on the form. These text boxes are called north, south, east, and west. The code uses the ToString method of each hand to format the output.

If an exception occurs at any point, the catch handler displays a message box with the error message for the exception.

137. On the DEBUG menu, click Start Debugging. When the Card Game window appears, click Deal. The cards in the pack should be dealt at random to each hand, and the cards in each hand should be displayed on the form as shown in the following image:

image with no caption

138. Click Deal again. Verify that a new set of hands is dealt and the cards in each hand change.

139. Return to Visual Studio and stop debugging.

Summary

In this chapter, you learned how to create and use arrays to manipulate sets of data. You saw how to declare and initialize arrays, access data held in arrays, pass arrays as parameters to methods, and return arrays from methods. You also learned how to create multidimensional arrays and how to use arrays of arrays.

§ If you want to continue to the next chapter

Keep Visual Studio 2012 running, and turn to Chapter 11.

§ If you want to exit Visual Studio 2012 now

On the FILE menu, click Exit. If you see a Save dialog box, click Yes and save the project.

Chapter 10 Quick Reference

To

Do this

Declare an array variable

Write the name of the element type, followed by square brackets, followed by the name of the variable, followed by a semicolon. For example:

bool[] flags;

Create an instance of an array

Write the keyword new, followed by the name of the element type, followed by the size of the array enclosed in square brackets. For example:

bool[] flags = new bool[10];

Initialize the elements of an array to specific values

For an array, write the specific values in a comma-separated list enclosed in braces. For example:

bool[] flags = { true, false, true, false };

Find the number of elements in an array

Use the Length property. For example:

int [] flags = ...;

...

int noOfElements = flags.Length;

Access a single array element

Write the name of the array variable, followed by the integer index of the element enclosed in square brackets. Remember, array indexing starts at 0, not 1. For example:

bool initialElement = flags[0];

Iterate through the elements of an array

Use a for statement or a foreach statement. For example:

bool[] flags = { true, false, true, false };

for (int i = 0; i < flags.Length; i++)

{

Console.WriteLine(flags[i]);

}

foreach (bool flag in flags)

{

Console.WriteLine(flag);

}

Declare an multidimensional array variable

Write the name of the element type, followed by a set of square brackets with a comma separator indicating the number of dimensions, followed by the name of the variable, followed by a semicolon. For example, use the following to create a two-dimensional array called table:

int[,] table;