Tải bản đầy đủ (.pdf) (67 trang)

microsoft visual c 2008 step by step phần 4 pps

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (663.82 KB, 67 trang )

Chapter 9 Creating Value Types with Enumerations and Structs 175
data values where it’s just as or nearly as effi cient to copy the value as it would be to copy
an address. Use classes for more complex data so that you have the option of copying only
the address of the actual value when you want to improve the effi ciency of your code.
Understanding Structure and Class Differences
A structure and a class are syntactically very similar, but there are a few important differ-
ences. Let’s look at some of these differences:

You can’t declare a default constructor (a constructor with no parameters) for a struc-
ture. The following example would compile if Time were a class, but because Time is a
structure, it does not:
struct Time
{
public Time() { } // compile-time error

}
The reason you can’t declare your own default constructor for a structure is that the
compiler always generates one. In a class, the compiler generates the default con-
structor only if you don’t write a constructor yourself. The compiler-generated default
constructor for a structure always sets the fi elds to 0, false, or null—just as for a class.
Therefore, you should ensure that a structure value created by the default constructor
behaves logically and makes sense with these default values. If you don’t want to use
these default values, you can initialize fi elds to different values by providing a nonde-
fault constructor. However, if you don’t initialize a fi eld in your nondefault structure
constructor, the compiler won’t initialize it for you. This means that you must explic-
itly initialize all the fi elds in all your nondefault structure constructors or you’ll get a
compile-time error. For example, although the following example would compile and
silently initialize seconds to 0 if Time were a class, because Time is a structure, it fails to
compile:
struct Time
{


public Time(int hh, int mm)
{
hours = hh;
minutes = mm;
} // compile-time error: seconds not initialized

private int hours, minutes, seconds;
}
176 Part II Understanding the C# Language

In a class, you can initialize instance fi elds at their point of declaration. In a structure,
you cannot. The following example would compile if Time were a class, but because
Time is a structure, it causes a compile-time error:
struct Time
{

private int hours = 0; // compile-time error
private int minutes;
private int seconds;
}
The following table summarizes the main differences between a structure and a class.
Question Structure Class
Is this a value type or a reference type? A structure is a value
type.
A class is a reference type.
Do instances live on the stack or the heap?
Structure instances
are called values and
live on the stack.
Class instances are called

objects and live on the
heap.
Can you declare a default constructor? No Yes
If you declare your own constructor, will
the compiler still generate the default
constructor?
Yes No
If you don’t initialize a fi eld in your own
constructor, will the compiler automati-
cally initialize it for you?
No Yes
Are you allowed to initialize instance fi elds
at their point of declaration?
No Yes
There are other differences between classes and structures concerning inheritance. These
differences are covered in Chapter 12, “Working with Inheritance.” Now that you know how
to declare structures, the next step is to use them to create values.
Declaring Structure Variables
After you have defi ned a structure type, you can use it in exactly the same way as any other
type. For example, if you have defi ned the Time structure, you can create variables, fi elds,
and parameters of type Time, as shown in this example:
struct Time
{

private int hours, minutes, seconds;
}

Chapter 9 Creating Value Types with Enumerations and Structs 177
class Example
{

public void Method(Time parameter)
{
Time localVariable;

}

private Time currentTime;
}
Note You can create a nullable version of a structure variable by using the ? modifi er. You can
then assign the null value to the variable:
Time? currentTime = null;
Understanding Structure Initialization
Earlier in this chapter, you saw how the fi elds in a structure are initialized by using a
constructor. However, because structures are value types, you can create structure variables
without calling a constructor, as shown in the following example:
Time now;
In this example, the variable is created but its fi elds are left in their uninitialized state. Any
attempt to access the values in these fi elds will result in a compiler error. The following
graphic depicts the state of the fi elds in the now variable:
If you call a constructor, the various rules of structure constructors described earlier
guarantee that all the fi elds in the structure will be initialized:
Time now = new Time();
178 Part II Understanding the C# Language
This time, the default constructor initializes the fi elds in the structure, as shown in the
following graphic:
Note that in both cases, the Time variable is created on the stack.
If you’ve written your own structure constructor, you can also use that to initialize a structure
variable. As explained earlier in this chapter, a structure constructor must always explicitly
initialize all its fi elds. For example:
struct Time

{
public Time(int hh, int mm)
{
hours = hh;
minutes = mm;
seconds = 0;
}

private int hours, minutes, seconds;
}
The following example initializes now by calling a user-defi ned constructor:
Time now = new Time(12, 30);
The following graphic shows the effect of this example:
Chapter 9 Creating Value Types with Enumerations and Structs 179
Copying Structure Variables
You’re allowed to initialize or assign one structure variable to another structure variable, but
only if the structure variable on the right side is completely initialized (that is, if all its fi elds
are initialized). The following example compiles because now is fully initialized. (The graphic
shows the results of performing the assignment.)
Time now = new Time(12, 30);
Time copy = now;
The following example fails to compile because now is not initialized:
Time now;
Time copy = now; // compile-time error: now has not been assigned
When you copy a structure variable, each fi eld on the left side is set directly from the cor-
responding fi eld on the right side. This copying is done as a fast, single operation that copies
the contents of the entire structure and that never throws an exception. Compare this be-
havior with the equivalent action if Time were a class, in which case both variables (now and
copy) would end up referencing the same object on the heap.
Note

C++ programmers should note that this copy behavior cannot be customized.
It’s time to put this knowledge into practice. In the following exercise, you will create and use
a structure to represent a date.
180 Part II Understanding the C# Language
Create and use a structure type
1. In the StructsAndEnums project, display the Date.cs fi le in the Code and Text Editor
window.
2. Add a structure named Date inside the StructsAndEnums namespace.
This structure should contain three private fi elds: one named year of type int, one
named month of type Month (using the enumeration you created in the preceding
exercise), and one named day of type int. The Date structure should look exactly as
follows:
struct Date
{
private int year;
private Month month;
private int day;
}
Consider the default constructor that the compiler will generate for Date. This con-
structor will set the year to 0, the month to 0 (the value of January), and the day to 0.
The year value 0 is not valid (there was no year 0), and the day value 0 is also not valid
(each month starts on day 1). One way to fi x this problem is to translate the year and
day values by implementing the Date structure so that when the year fi eld holds the
value Y, this value represents the year Y + 1900 (you can pick a different century if you
prefer), and when the day fi eld holds the value D, this value represents the day D + 1.
The default constructor will then set the three fi elds to values that represent the date 1
January 1900.
3. Add a public constructor to the Date structure. This constructor should take three
parameters: an int named ccyy for the year, a Month named mm for the month, and an
int named dd for the day. Use these three parameters to initialize the corresponding

fi elds. A year fi eld of Y represents the year Y + 1900, so you need to initialize the year
fi eld to the value ccyy – 1900. A day fi eld of D represents the day D + 1, so you need to
initialize the day
fi eld to the value dd – 1.
The Date structure should now look like this (the constructor is shown in bold):
struct Date
{

public Date(int ccyy, Month mm, int dd)
{
this.year = ccyy - 1900;
this.month = mm;
this.day = dd - 1;
}

private int year;
private Month month;
private int day;
}
C
reate an
d
use a structure type
Chapter 9 Creating Value Types with Enumerations and Structs 181
4. Add a public method named ToString to the Date structure after the constructor. This
method takes no arguments and returns a string representation of the date. Remember,
the value of the year fi eld represents year + 1900, and the value of the day fi eld repre-
sents day + 1.
Note
The ToString method is a little different from the methods you have seen so far.

Every type, including structures and classes that you defi ne, automatically has a ToString
method whether or not you want it. Its default behavior is to convert the data in a variable
to a string representation of that data. Sometimes, the default behavior is meaningful; oth-
er times, it is less so. For example, the default behavior of the ToString method generated
for the Date class simply generates the string “StructsAndEnums.Date”. To quote Zaphod
Beeblebrox in The Restaurant at the End of the Universe (Douglas Adams, Del Rey, 2005),
this is “shrewd, but dull.” You need to defi ne a new version of this method that overrides
the default behavior by using the override keyword. Overriding methods are discussed in
more detail in Chapter 12.
The ToString method should look like this:
public override string ToString()
{
return this.month + “ “ + (this.day + 1) + “ “ + (this.year + 1900);
}
Note The + signs inside the parentheses are the arithmetic addition operator. The others
are the string concatenation operator. Without the parentheses, all occurrences of the +
sign would be treated as the string concatenation operator because the expression being
evaluated is a string. It can be a little confusing when the same symbol in a single expres-
sion denotes different operators!
5. Display the Program.cs fi le in the Code and Text Editor window.
6. Add a statement to the end of the Entrance method to declare a local variable named
defaultDate, and initialize it to a Date value constructed by using the default Date con-
structor. Add another statement to Entrance to write the defaultDate variable to the
console by calling Console.WriteLine.
Note
The Console.WriteLine method automatically calls the ToString method of its
argument to format the argument as a string.
182 Part II Understanding the C# Language
The Entrance method should now look like this:
static void Entrance()

{

Date defaultDate = new Date();
Console.WriteLine(defaultDate);
}
7. On the Debug menu, click Start Without Debugging to build and run the program.
Verify that the date January 1 1900 is written to the console. (The original output of the
Entrance method will be displayed fi rst.)
8. Press the Enter key to return to the Visual Studio 2008 programming environment.
9. In the Code and Text Editor window, return to the Entrance method, and add two
more statements. The fi rst statement should declare a local variable named halloween
and initialize it to October 31 2008. The second statement should write the value of
halloween to the console.
The Entrance method should now look like this:
static void Entrance()
{

Date halloween = new Date(2008, Month.October, 31);
Console.WriteLine(halloween);
}
Note When you type the new keyword, IntelliSense will automatically detect that there
are two constructors available for the Date type.
10. On the Debug menu, click Start Without Debugging. Confi rm that the date October 31
2008 is written to the console below the previous information.
11. Press Enter to close the program.
You have successfully used the enum and struct keywords to declare your own value types
and then used these types in code.

If you want to continue to the next chapter:
Keep Visual Studio 2008 running, and turn to Chapter 10.


If you want to exit Visual Studio 2008 now:
On the File menu, click Exit. If you see a Save dialog box, click Yes (if you are using
Visual Studio 2008) or Save (if you are using Visual C# 2008 Express Edition) and save
the project.
Chapter 9 Creating Value Types with Enumerations and Structs 183
Chapter 9 Quick Reference
To Do this
Declare an enumeration
Write the keyword enum, followed by the name of the type, followed
by a pair of braces containing a comma-separated list of the
enumeration literal names. For example:
enum Season { Spring, Summer, Fall, Winter }
Declare an enumeration variable Write the name of the enumeration on the left followed by the name
of the variable, followed by a semicolon. For example:
Season currentSeason;
Assign an enumeration variable
to a value
Write the name of the enumeration literal in combination with the
name of the enumeration to which it belongs. For example:
currentSeason = Spring; // error
currentSeason = Season.Spring; // correct
Declare a structure type
Write the keyword struct, followed by the name of the structure type,
followed by the body of the structure (the constructors, methods, and
fi elds). For example:
struct Time
{
public Time(int hh, int mm, int ss)
{ }


private int hours, minutes, seconds;
}
Declare a structure variable Write the name of the structure type, followed by the name of the
variable, followed by a semicolon. For example:
Time now;
Initialize a structure variable
to a value
Initialize the variable to a structure value created by calling the struc-
ture constructor. For example:
Time lunch = new Time(12, 30, 0);

185
Chapter 10
Using Arrays and Collections
After completing this chapter, you will be able to:

Declare, initialize, copy, and use array variables.

Declare, initialize, copy, and use variables of various collection types.
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 infor-
mation about a single item (an int, a fl oat, a Circle, a Time, and so on). What happens if you
need to manipulate sets of items? One solution would be 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 and collections provide mechanisms that solve the problems posed by these
questions.
What Is an Array?
An array is an unordered sequence of elements. All the elements in an array have the same
type (unlike the fi elds in a structure or class, which can have different types). The elements
of an array live in a contiguous block of memory and are accessed by using an integer index
(unlike fi elds 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 vari-
able is an array. For example, to declare an array of int variables named pins, you would write:
int[] pins; // Personal Identification Numbers
Microsoft Visual Basic programmers should note that you use square brackets and not
parentheses. C and C++ programmers should note that the size of the array is not part of the
186 Part II Understanding the C# Language
declaration. Java programmers should note that you must place the square brackets before
the variable name.
Note
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 Time structures
like this:
Time[] times;
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 the contiguous block of memory holding the array elements on the heap
(just as a class variable refers to an object on the heap) and does not hold its array elements
directly on the stack (as a structure does). (To review values and references and the differenc-

es between the stack and the heap, see Chapter 8, “Understanding Values and References.”)
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 rules—when you declare
an array variable, you do not declare its size. You specify the size of an array only when you
actually create an array instance.
To create an array instance, you use the new keyword followed by the name of 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, de-
pending on whether the type is numeric, a reference, or a Boolean, respectively). For exam-
ple, 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 the effects of this statement:
Chapter 10 Using Arrays and Collections 187
The size of an array instance 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 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 be 0. An array of
size 0 is not a null array.
It’s also possible to create multidimensional arrays. For example, to create a two-dimensional
array, you create an array that requires two integer indexes. Detailed discussion of multidi-
mensional arrays is beyond the scope of this book, but here’s an example:
int[,] table = new int[4,6];
Initializing Array Variables
When you create an array instance, all the elements of the array instance are initialized to a
default value depending on their type. You can modify this behavior and initialize the ele-
ments of an array to specifi c 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 would 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:
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 genera-
tor 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.
188 Part II Understanding the C# Language
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 }; // okay
When you’re initializing an array variable, you can actually omit the new expression and the
size of the array. The compiler calculates the size from the number of initializers and gener-
ates code to create the array. For example:
int[] pins = { 9, 3, 7, 2 };
If you create an array of structures, you can initialize each structure in the array by calling the
structure 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, and 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 will cause 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 con-
stants 3.5 and 99.999 are both double, and the C# compiler can convert the integer values 1
and 2 to double values:
var numbers = new[]{1, 2, 3.5, 99.999};
Chapter 10 Using Arrays and Collections 189
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,
described in Chapter 7, “Creating and Managing Classes and Objects.” The following code
creates an array of anonymous objects, each containing two fi elds specifying the name and
age of the members of my family (yes, I am younger than my wife):
var names = new[] { new { Name = “John”, Age = 42 },
new { Name = “Diana”, Age = 43 },
new { Name = “James”, Age = 15 },
new { Name = “Francesca”, Age = 13 } };
The fi elds 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. 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;
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.
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, 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)
{

}
190 Part II Understanding the C# Language
Iterating Through an Array
Arrays have a number of useful built-in properties and methods. (All arrays inherit meth-
ods and properties from the System.Array class in the Microsoft .NET Framework.) You can
examine 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 there are no parentheses when you
call it. You will learn about properties in Chapter 15, “Implementing Properties to Access Fields.”
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 for statement 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 automati-
cally 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 iter-
ate 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 fi nd 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 fi rst half) or to bypass cer-
tain 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, 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.
Chapter 10 Using Arrays and Collections 191

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.
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 anony-
mous objects. The following example demonstrates how you can iterate through the array of
family members shown earlier:
var names = new[] { new { Name = “John”, Age = 42 },
new { Name = “Diana”, Age = 43 },
new { Name = “James”, Age = 15 },
new { Name = “Francesca”, Age = 13 } };
foreach (var familyMember in names)
{
Console.WriteLine(“Name: {0}, Age: {1}”, familyMember.Name, familyMember.Age);
}
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 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 vari-
able refers to, you have to do two things. First you need to create a new array instance of the
same type and the same length as the array you are copying, as in this example:
int[] pins = { 9, 3, 7, 2 };
int[] copy = new int[4];
This works, but if you later modify the code to change the length of the original array, you
must remember to also change the size of the copy. It’s better to determine the length of an
array by using its Length property, as shown in this example:
int[] pins = { 9, 3, 7, 2 };
int[] copy = new int[pins.Length];
The values inside copy are now all initialized to their default value, 0.
192 Part II Understanding the C# Language
The second thing you need to do is set the values inside the new array to the same values as
the original array. You could do this by using a for statement, as shown in this example:
int[] pins = { 9, 3, 7, 2 };
int[] copy = new int[pins.Length];
for (int i = 0; i < copy.Length; i++)
{
copy[i] = pins[i];
}
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 specifi ed starting index:
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);
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 The Clone method actually returns an object, which is why you must cast it to an array of
the appropriate type when you use it. Furthermore, all four ways of copying shown earlier create
a shallow copy of an array—if the elements in the array being copied contain references, the for
loop as coded and the three preceding methods simply copy the references rather than the ob-
jects 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.
What Are Collection Classes?
Arrays are useful, but they have their limitations. Fortunately, arrays are only one way to
collect elements of the same type. The Microsoft .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.
Chapter 10 Using Arrays and Collections 193
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:
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:
The element type of all the collection classes shown in this chapter 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 Microsoft .NET
Framework Class Library documentation for more details on each class.
Note
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 18, “Introducing Generics.”
194 Part II Understanding the C# Language
The ArrayList Collection Class
ArrayList is a useful class for shuffl ing 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 any references to the
original array so that they refer to the new array.

If you want to remove an element from an array, you have to 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 using the ArrayList class:

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

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.

You can reference an existing element in an ArrayList object by using ordinary array
notation, with square brackets and the index of the element.
Note
As with arrays, if you use foreach to iterate through an ArrayList, you cannot use the
iteration variable to modify the contents of the ArrayList. Additionally, you cannot call the
Remove, Add, or Insert method in a foreach loop that iterates through an ArrayList.
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);
}

// insert an element in the penultimate position in the list, and move the last item up
Chapter 10 Using Arrays and Collections 195
// (the first parameter is the position;
// the second parameter is the value being inserted)
numbers.Insert(numbers.Count-1, 99);

// 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, which unboxes the value
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 here:
10
9
8
7
6
5
4
3
2
99
1
10
9
8

7
6
5
4
3
2
99
1
Note The way you determine the number of elements for an ArrayList is different from query-
ing the number of items in an array. When using an ArrayList, you examine the Count property,
and when using an array, you examine the Length property.
196 Part II Understanding the C# Language
The Queue Collection Class
The Queue class implements a fi rst-in, fi rst-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(); // cast required to unbox the value
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
Chapter 10 Using Arrays and Collections 197
The Stack Collection Class
The Stack class implements a last-in, fi rst-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 on the stack the fi rst 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
198 Part II Understanding the C# Language
The Hashtable Collection 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 ele-
ment at index 4 (which is actually the fi fth 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 specifi ed key quickly and easily.
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. You can, however, use
the square brackets notation to add a key/value pair (as shown in the following ex-
ample), without danger of an exception, even if the key has already been added. You
can test whether a Hashtable already contains a particular key by using the ContainsKey
method.

Internally, a Hashtable is a sparse data structure that operates best when it has plenty

of memory to work in. The size of a Hashtable in memory can grow quite quickly as
you insert more elements.

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 the information:
using System;
using System.Collections;

Hashtable ages = new Hashtable();

// fill the Hashtable
ages[“John”] = 42;
ages[“Diana”] = 43;
ages[“James”] = 15;
ages[“Francesca”] = 13;

// 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);
}
Chapter 10 Using Arrays and Collections 199
The output from this program is:
Name: James, Age: 15

Name: John, Age: 42
Name: Francesca, Age: 13
Name: Diana, Age: 43
The SortedList Collection Class
The SortedList class is very similar to the Hashtable class in that it enables 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
kept synchronized, even when you add and remove elements. This means that you can insert
key/value pairs into a SortedList in any sequence; 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 the information, but this version has been adjusted to use a SortedList rather
than a Hashtable:
using System;
using System.Collections;

SortedList ages = new SortedList();

// fill the SortedList
ages[“John”] = 42;
ages[“Diana”] = 43;
ages[“James”] = 15;
ages[“Francesca”] = 13;


// 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);
}

×