170 Part II Understanding the C# Language
You can access and modify the value held in the variable i through the pointer variable
pi like this:
*pi = 100;
This code updates the value of the variable i to 100 because pi points to the same
memory location as the variable i.
One of the main problems that developers learning C and C++ have is understanding
the syntax used by pointers. The * operator has at least two meanings (in addition to
being the arithmetic multiplication operator), and there is often great confusion about
when to use & rather than *. The other issue with pointers is that it is easy to point
somewhere invalid, or to forget to point somewhere at all, and then try to reference
the data pointed to. The result will be either garbage or a program that fails with an
error because the operating system detects an attempt to access an illegal address in
memory. There is also a whole range of security flaws in many existing systems result-
ing from the mismanagement of pointers; some environments (not Microsoft Windows)
fail to enforce checks that a pointer does not refer to memory that belongs to another
process, opening up the possibility that confidential data could be compromised.
Reference variables were added to C# to avoid all these problems. If you really want to,
you can continue to use pointers in C#, but you must mark the code as unsafe. The un-
safe keyword can be used to mark a block of code, or an entire method, as shown here:
public static void Main(string [] args)
{
int x = 99, y = 100;
unsafe
{
swap (&x, &y);
}
Console.WriteLine("x is now {0}, y is now {1}", x, y);
}
public static unsafe void swap(int *a, int *b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
When you compile programs containing unsafe code, you must specify the /unsafe
option.
Unsafe code also has a bearing on how memory is managed; objects created in unsafe
code are said to be unmanaged. We discuss this issue in more detail in Chapter 14.
Chapter 8 Understanding Values and References 171
In this chapter, you learned about some important differences between value types that hold
their value directly on the stack and reference types that refer indirectly to their objects on
the heap. You also learned how to use the ref and out keywords on method parameters to
gain access to the arguments. You saw how assigning a value (such as the int 42) to a variable
of the System.Object class creates a boxed copy of the value on the heap and then causes the
System.Object variable to refer to this boxed copy. You also saw how assigning a variable of
a value type (such as an int) to a variable of the System.Object class copies (or unboxes) the
value in the System.Object class to the memory used by the int.
n
If you want to continue to the next chapter
Keep Visual Studio 2010 running, and turn to Chapter 9.
n
If you want to exit Visual Studio 2010 now
On the File menu, click Exit. If you see a Save dialog box, click Yes and save the project.
Chapter 8 Quick Reference
To Do this
Copy a value type variable Simply make the copy. Because the variable is a value type, you will have
two copies of the same value. For example:
int i = 42;
int copyi = i;
Copy a reference type variable Simply make the copy. Because the variable is a reference type, you will
have two references to the same object. For example:
Circle c = new Circle(42);
Circle refc = c;
Declare a variable that can hold
a value type or the null value
Declare the variable using the ? modifier with the type. For example:
int? i = null;
Pass an argument to a ref
parameter
Prefix the argument with the ref keyword. This makes the parameter an
alias for the actual argument rather than a copy of the argument. The
method may change the value of the parameter, and this change is made
to the actual argument rather than a local copy. For example:
static void Main()
{
int arg = 42;
DoWork(ref arg);
Console.WriteLine(arg);
}
172 Part II Understanding the C# Language
To Do this
Pass an argument to an out
parameter
Prefix the argument with the out keyword. This makes the parameter an
alias for the actual argument rather than a copy of the argument. The
method must assign a value to the parameter, and this value is made to
the actual argument. For example:
static void Main()
{
int arg = 42;
DoWork(out arg);
Console.WriteLine(arg);
}
Box a value Initialize or assign a variable of type object to the value. For example:
object o = 42;
Unbox a value Cast the object reference that refers to the boxed value to the type of the
value variable. For example:
int i = (int)o;
Cast an object safely Use the is operator to test whether the cast is valid. For example:
WrappedInt wi = new WrappedInt();
object o = wi;
if (o is WrappedInt)
{
WrappedInt temp = (WrappedInt)o;
}
Alternatively, use the as operator to perform the cast, and test whether
the result is null. For example:
WrappedInt wi = new WrappedInt();
object o = wi;
WrappedInt temp = o as WrappedInt;
if (temp != null)
173
Chapter 9
Creating Value Types with
Enumerations and Structures
After completing this chapter, you will be able to:
n
Declare an enumeration type.
n
Create and use an enumeration type.
n
Declare a structure type.
n
Create and use a structure type.
n
Explain the differences in behavior between a structure and a class.
In Chapter 8, “Understanding Values and References,” you learned about the two
fundamental types that exist in Microsoft Visual C#: value types and reference types. A value
type variable holds its value directly on the stack, whereas a reference type variable holds
a reference to an object on the heap. In Chapter 7, “Creating and Managing Classes and
Objects,” you learned how to create your own reference types by defining classes. In this
chapter, you’ll learn how to create your own value types.
C# supports two kinds of value types: enumerations and structures. We’ll look at each of
them in turn.
Working with Enumerations
Suppose you want to represent the seasons of the year in a program. You could use the
integers 0, 1, 2, and 3 to represent spring, summer, fall, and winter, respectively. This system
would work, but it’s not very intuitive. If you used the integer value 0 in code, it wouldn’t be
obvious that a particular 0 represented spring. It also wouldn’t be a very robust solution. For
example, if you declare an int variable named season, there is nothing to stop you from as-
signing it any legal integer value apart from 0, 1, 2, or 3. C# offers a better solution. You can
create an enumeration (sometimes called an enum type), whose values are limited to a set of
symbolic names.
Declaring an Enumeration
You define an enumeration by using the enum keyword, followed by a set of symbols
identifying the legal values that the type can have, enclosed between braces. Here’s how to
174 Part II Understanding the C# Language
declare an enumeration named Season whose literal values are limited to the symbolic names
Spring, Summer, Fall, and Winter:
enum Season { Spring, Summer, Fall, Winter }
Using an Enumeration
After you have declared an enumeration, you can use it in exactly the same way as any other
type. If the name of your enumeration is Season, you can create variables of type Season,
fields of type Season, and parameters of type Season, as shown in this example:
enum Season { Spring, Summer, Fall, Winter }
class Example
{
public void Method(Season parameter)
{
Season localVariable;
}
private Season currentSeason;
}
Before you can read the value of an enumeration variable, it must be assigned a value.
You can assign a value that is defined by the enumeration only to an enumeration variable.
For example:
Season colorful = Season.Fall;
Console.WriteLine(colorful); // writes out 'Fall'
Note As you can with all value types, you can create a nullable version of an enumeration
variable by using the ? modifier. You can then assign the null value, as well the values defined by
the enumeration, to the variable:
Season? colorful = null;
Notice that you have to write Season.Fall rather than just Fall. All enumeration literal names
are scoped by their enumeration type. This is useful because it allows different enumerations
to coincidentally contain literals with the same name.
Also, notice that when you display an enumeration variable by using Console.WriteLine, the
compiler generates code that writes out the name of the literal whose value matches the
value of the variable. If needed, you can explicitly convert an enumeration variable to a string
Chapter 9 Creating Value Types with Enumerations and Structures 175
that represents its current value by using the built-in ToString method that all enumerations
automatically contain. For example:
string name = colorful.ToString();
Console.WriteLine(name); // also writes out 'Fall'
Many of the standard operators you can use on integer variables can also be used on enu-
meration variables (except the bitwise and shift operators, which are covered in Chapter 16,
“Using Indexers”). For example, you can compare two enumeration variables of the same
type for equality by using the equality operator (==), and you can even perform arithmetic
on an enumeration variable (although the result might not always be meaningful!).
Choosing Enumeration Literal Values
Internally, an enumeration type associates an integer value with each element of the enumer-
ation. By default, the numbering starts at 0 for the first element and goes up in steps of 1. It’s
possible to retrieve the underlying integer value of an enumeration variable. To do this, you
must cast it to its underlying type. Remember from the discussion of unboxing in Chapter 8
that casting a type converts the data from one type to another as long as the conversion is
valid and meaningful. The following code example writes out the value 2 and not the word
Fall (in the Season enumeration Spring is 0, Summer 1, Fall 2, and Winter 3):
enum Season { Spring, Summer, Fall, Winter }
Season colorful = Season.Fall;
Console.WriteLine((int)colorful); // writes out '2'
If you prefer, you can associate a specific integer constant (such as 1) with an enumeration
literal (such as Spring), as in the following example:
enum Season { Spring = 1, Summer, Fall, Winter }
Important The integer value with which you initialize an enumeration literal must be a
compile-time constant value (such as 1).
If you don’t explicitly give an enumeration literal a constant integer value, the compiler gives
it a value that is 1 greater than the value of the previous enumeration literal except for the
very first enumeration literal, to which the compiler gives the default value 0. In the preced-
ing example, the underlying values of Spring, Summer, Fall, and Winter are now 1, 2, 3, and 4.
You are allowed to give more than one enumeration literal the same underlying value. For
example, in the United Kingdom, Fall is referred to as Autumn. You can cater to both cultures
as follows:
enum Season { Spring, Summer, Fall, Autumn = Fall, Winter }
176 Part II Understanding the C# Language
Choosing an Enumeration’s Underlying Type
When you declare an enumeration, the enumeration literals are given values of type int. You
can also choose to base your enumeration on a different underlying integer type. For exam-
ple, to declare that Season’s underlying type is a short rather than an int, you can write this:
enum Season : short { Spring, Summer, Fall, Winter }
The main reason for doing this is to save memory; an int occupies more memory than a
short, and if you do not need the entire range of values available to an int, using a smaller
data type can make sense.
You can base an enumeration on any of the eight integer types: byte, sbyte, short, ushort, int,
uint, long, or ulong. The values of all the enumeration literals must fit inside the range of the
chosen base type. For example, if you base an enumeration on the byte data type, you can
have a maximum of 256 literals (starting at 0).
Now that you know how to declare an enumeration, the next step is to use it. In the follow-
ing exercise, you will work with a Console application to declare and use an enumeration that
represents the months of the year.
Create and use an enumeration
1. Start Microsoft Visual Studio 2010 if it is not already running.
2. Open the StructsAndEnums project, located in the \Microsoft Press\Visual CSharp Step
By Step\Chapter 9\StructsAndEnums folder in your Documents folder.
3. In the Code and Text Editor window, display the Month.cs file.
The source file contains an empty namespace named StructsAndEnums.
4. Add an enumeration named Month for modeling the months of the year inside the
StructsAndEnums namespace, as shown in bold here. The 12 enumeration literals for
Month are January through December.
namespace StructsAndEnums
{
enum Month
{
January, February, March, April,
May, June, July, August,
September, October, November, December
}
}
Chapter 9 Creating Value Types with Enumerations and Structures 177
5. Display the Program.cs file in the Code and Text Editor window.
As in the exercises in previous chapters, the Main method calls the DoWork method
and traps any exceptions that occur.
6. In the Code and Text Editor window, add a statement to the DoWork method to declare
a variable named first of type Month and initialize it to Month.January. Add another
statement to write the value of the first variable to the Console.
The DoWork method should look like this:
static void DoWork()
{
Month first = Month.January;
Console.WriteLine(first);
}
Note When you type the period following Month, Microsoft IntelliSense will
automatically display all the values in the Month enumeration.
7. On the Debug menu, click Start Without Debugging.
Visual Studio 2010 builds and runs the program. Confirm that the word January is
written to the console.
8. Press Enter to close the program and return to the Visual Studio 2010 programming
environment.
9. Add two more statements to the DoWork method to increment the first variable and
display its new value to the console, as shown in bold here:
static void DoWork()
{
Month first = Month.January;
Console.WriteLine(first);
first++;
Console.WriteLine(first);
}
10. On the Debug menu, click Start Without Debugging.
Visual Studio 2010 builds and runs the program. Confirm that the words January and
February are written to the console.
Notice that performing a mathematical operation (such as the increment operation) on
an enumeration variable changes the internal integer value of the variable. When the
variable is written to the console, the corresponding enumeration value is displayed.
178 Part II Understanding the C# Language
11. Press Enter to close the program and return to the Visual Studio 2010 programming
environment.
12. Modify the first statement in the DoWork method to initialize the first variable to
Month.December, as shown in bold here:
static void DoWork()
{
Month first = Month.December;
Console.WriteLine(first);
first++;
Console.WriteLine(first);
}
13. On the Debug menu, click Start Without Debugging.
Visual Studio 2010 builds and runs the program. This time the word December is written
to the console, followed by the number 12. Although you can perform arithmetic on an
enumeration, if the results of the operation are outside the range of values defined for
the enumerator, all the runtime can do is interpret the value of the variable as the cor-
responding integer value.
14. Press Enter to close the program and return to the Visual Studio 2010 programming
environment.
Working with Structures
You saw in Chapter 8 that classes define reference types that are always created on the heap.
In some cases, the class can contain so little data that the overhead of managing the heap
becomes disproportionate. In these cases, it is better to define the type as a structure. A
structure is a value type. Because structures are stored on the stack, as long as the structure
is reasonably small, the memory management overhead is often reduced.
Like a class, a structure can have its own fields, methods, and (with one important exception
discussed later in this chapter) constructors.
Common Structure Types
You might not have realized it, but you have already used structures in previous exer-
cises in this book. In C#, the primitive numeric types int, long, and float are aliases for
the structures System.Int32, System.Int64, and System.Single, respectively. These struc-
tures have fields and methods, and you can actually call methods on variables and liter-
als of these types. For example, all of these structures provide a ToString method that
Chapter 9 Creating Value Types with Enumerations and Structures 179
can convert a numeric value to its string representation. The following statements are
all legal statements in C#:
int i = 99;
Console.WriteLine(i.ToString());
Console.WriteLine(55.ToString());
float f = 98.765F;
Console.WriteLine(f.ToString());
Console.WriteLine(98.765F.ToString());
You don’t see this use of the ToString method often, because the Console.WriteLine
method calls it automatically when it is needed. Use of the static methods exposed by
these structures is much more common. For example, in earlier chapters you used the
static int.Parse method to convert a string to its corresponding integer value. What you
are actually doing is invoking the Parse method of the Int32 structure:
string s = "42";
int i = int.Parse(s); // exactly the same as Int32.Parse
These structures also include some useful static fields. For example, Int32.MaxValue is
the maximum value that an int can hold, and Int32.MinValue is the smallest value you
can store in an int.
The following table shows the primitive types in C# and their equivalent types in the
Microsoft .NET Framework. Notice that the string and object types are classes (refer-
ence types) rather than structures.
Keyword Type equivalent Class or structure
bool System.Boolean Structure
byte System.Byte Structure
decimal System.Decimal Structure
double System.Double Structure
float System.Single Structure
int System.Int32 Structure
long System.Int64 Structure
object System.Object Class
sbyte System.SByte Structure
short System.Int16 Structure
string System.String Class
uint System.UInt32 Structure
ulong System.UInt64 Structure
ushort System.UInt16 Structure
180 Part II Understanding the C# Language
Declaring a Structure
To declare your own structure type, you use the struct keyword followed by the name of the
type, followed by the body of the structure between opening and closing braces. For exam-
ple, here is a structure named Time that contains three public int fields named hours, minutes,
and seconds:
struct Time
{
public int hours, minutes, seconds;
}
As with classes, making the fields of a structure public is not advisable in most cases; there
is no way to control the values held in public fields. For example, anyone could set the value
of minutes or seconds to a value greater than 60. A better idea is to make the fields private
and provide your structure with constructors and methods to initialize and manipulate these
fields, as shown in this example:
struct Time
{
public Time(int hh, int mm, int ss)
{
hours = hh % 24;
minutes = mm % 60;
seconds = ss % 60;
}
public int Hours()
{
return hours;
}
private int hours, minutes, seconds;
}
Note By default, you cannot use many of the common operators on your own structure types.
For example, you cannot use operators such as the equality operator (==) and the inequality op-
erator (!=) on your own structure type variables. However, you can explicitly declare and imple-
ment operators for your own structure types. The syntax for doing this is covered in Chapter 21,
“Operator Overloading.”
Use structures to implement simple concepts whose main feature is their value. When you
copy a value type variable, you get two copies of the value. In contrast, when you copy a ref-
erence type variable, you get two references to the same object. In summary, use structures
for small data values where it’s just as or nearly as efficient to copy the value as it would be
to copy an address. Use classes for more complex data that is too big to copy efficiently.
Chapter 9 Creating Value Types with Enumerations and Structures 181
Understanding Structure and Class Differences
A structure and a class are syntactically similar, but there are a few important differences.
Let’s look at some of these differences:
n
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 fields 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 fields to different values by providing a nonde-
fault constructor. However, if you don’t initialize a field in your nondefault structure
constructor, the compiler won’t initialize it for you. This means that you must explic-
itly initialize all the fields 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
{
private int hours, minutes, seconds;
public Time(int hh, int mm)
{
this.hours = hh;
this.minutes = mm;
} // compile-time error: seconds not initialized
}
n
In a class, you can initialize instance fields 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;
}
182 Part II Understanding the C# Language
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 field in your own
constructor, will the compiler automatically
initialize it for you?
No Yes
Are you allowed to initialize instance fields at
their point of declaration?
No Yes
There are other differences between classes and structures concerning inheritance. These
differences are covered in Chapter 12.
Declaring Structure Variables
After you have defined a structure type, you can use it in exactly the same way as any other
type. For example, if you have defined the Time structure, you can create variables, fields,
and parameters of type Time, as shown in this example:
struct Time
{
private int hours, minutes, seconds;
}
class Example
{
private Time currentTime;
public void Method(Time parameter)
{
Time localVariable;
}
}
Note You can create a nullable version of a structure variable by using the ? modifier. You can
then assign the null value to the variable:
Time? currentTime = null;
Chapter 9 Creating Value Types with Enumerations and Structures 183
Understanding Structure Initialization
Earlier in this chapter, you saw how the fields in a structure can be initialized by using a con-
structor. If you call a constructor, the various rules described earlier guarantee that all the
fields in the structure will be initialized:
Time now = new Time();
The following graphic depicts the state of the fields in this structure:
However, because structures are value types, you can create structure variables without
calling a constructor, as shown in the following example:
Time now;
This time, the variable is created but its fields are left in their uninitialized state. The following
graphic depicts the state of the fields in the now variable. Any attempt to access the values in
these fields will result in a compiler error:
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 fields. For example:
struct Time
{
private int hours, minutes, seconds;
184 Part II Understanding the C# Language
public Time(int hh, int mm)
{
hours = hh;
minutes = mm;
seconds = 0;
}
}
The following example initializes now by calling a user-defined constructor:
Time now = new Time(12, 30);
The following graphic shows the effect of this example:
It’s time to put this knowledge into practice. In the following exercise, you will create and use
a structure to represent a date.
Create and use a structure type
1. In the StructsAndEnums project, display the Date.cs file in the Code and Text Editor
window.
2. Add a structure named Date inside the StructsAndEnums namespace.
This structure should contain three private fields: 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 construc-
tor sets the year to 0, the month to 0 (the value of January), and the day to 0. The year
value 0 is not valid (because there was no year 0), and the day value 0 is also not valid
(because each month starts on day 1). One way to fix this problem is to translate the
year and day values by implementing the Date structure so that when the year field
holds the value Y, this value represents the year Y + 1900 (or you can pick a different
Chapter 9 Creating Value Types with Enumerations and Structures 185
century if you prefer), and when the day field holds the value D, this value represents
the day D + 1. The default constructor will then set the three fields 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
fields. A year field with the value Y represents the year Y + 1900, so you need to initial-
ize the year field to the value ccyy – 1900. A day field with the value D represents the
day D + 1, so you need to initialize the day field to the value dd – 1.
The Date structure should now look like this (with the constructor shown in bold):
struct Date
{
private int year;
private Month month;
private int day;
public Date(int ccyy, Month mm, int dd)
{
this.year = ccyy - 1900;
this.month = mm;
this.day = dd - 1;
}
}
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 field represents year + 1900, and the value of the day
field represents 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 define, 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 by Douglas Adams (Pan Macmillan,
1980), this is “shrewd, but dull.” You need to define a new version of this method that
overrides the default behavior by using the override keyword. Overriding methods are dis-
cussed in more detail in Chapter 12.
The ToString method should look like this:
public override string ToString()
{
string data = String.Format("{0} {1} {2}", this.month, this.day + 1, this.year +
1900);
return data;
}
186 Part II Understanding the C# Language
The Format method of the String class enables you to format data. It operates in a
similar manner to the Console.WriteLine method, except that rather than displaying
data to the console it returns the formatted result as a string. In this example, the posi-
tional parameters are replaced with the text representations of the values of the month
field, the expression this.day + 1, and the expression this.year + 1900. The ToString
method returns the formatted string as its result.
5. Display the Program.cs file in the Code and Text Editor window.
6. In the DoWork method, comment out the existing four statements. Add code to the
DoWork method to declare a local variable named defaultDate, and initialize it to a
Date value constructed by using the default Date constructor. Add another statement
to DoWork 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.
The DoWork method should now look like this:
static void DoWork()
{
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.
8. Press the Enter key to return to the Visual Studio 2010 programming environment.
9. In the Code and Text Editor window, return to the DoWork method, and add two more
statements. In the first statement, declare a local variable named weddingAnniversary
and initialize it to July 4 2010. (In an example of supreme irony, I actually did get mar-
ried on Independence Day and hence lost my independence!) In the second statement,
write the value of weddingAnniversary to the console.
The DoWork method should now look like this:
static void DoWork()
{
Date weddingAnniversary = new Date(2010, Month.July, 4);
Console.WriteLine(weddingAnniversary);
}
Note When you type the new keyword, IntelliSense automatically detects that there are
two constructors available for the Date type.
Chapter 9 Creating Value Types with Enumerations and Structures 187
10. On the Debug menu, click Start Without Debugging. Confirm that the date July 4 2010 is
written to the console below the previous information.
11. Press Enter to close the program.
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 fields
are populated with valid data rather than undefined values). 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 field on the left side is set directly from the
corresponding field 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
behavior 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.
188 Part II Understanding the C# Language
In the final exercise in this chapter, you will contrast the copy behavior of a structure with
that of a class.
Compare the behavior of a structure and a class
1. In the StructsAndEnums project, display the Date.cs file in the Code and Text Editor
window.
2. Add the following method to the Date structure. This method advances the date in the
structure by one month. If, after advancing the month, the value of the month field
has moved beyond December, this code resets the month to January and advances the
value of the year field by 1.
public void AdvanceMonth()
{
this.month++;
if (this.month == Month.December + 1)
{
this.month = Month.January;
this.year++;
}
}
3. Display the Program.cs file in the Code and Text Editor window.
4. In the DoWork method, comment out the first two statements that create and display
the value of the defaultDate variable.
5. Add the following code shown in bold to the end of the DoWork method. This code
creates a copy of the weddingAnniversary variable called weddingAnniversaryCopy and
prints out the value of this new variable.
static void DoWork()
{
Date weddingAnniversaryCopy = weddingAnniversary;
Console.WriteLine(“Value of copy is {0}”, weddingAnniversaryCopy);
}
6. Add the following statements to the end of the DoWork method that call the
AdvanceMonth method of the weddingAnniversary variable, and then display the value
of the weddingAnniversary and weddingAnniversaryCopy variables:
static void DoWork()
{
weddingAnniversaryCopy.AdvanceMonth();
Console.WriteLine("New value of weddingAnniversary is {0}", weddingAnniversary);
Console.WriteLine("Value of copy is {0}", weddingAnniversaryCopy);
}
Chapter 9 Creating Value Types with Enumerations and Structures 189
7. On the Debug menu, click Start Without Debugging to build and run the application.
Verify that the console window displays the following messages:
July 4 2010
Value of copy is July 4 2010
New value of weddingAnniversary is July 4 2010
Value of copy is August 4 2010
The first message displays the initial value of the weddingAnniversary variable (July 4
2010). The second message displays the value of the weddingAnniversaryCopy variable.
You can see that it contains a copy of the date held in the weddingAnniversary variable
(July 4 2010). The third message displays the value of the weddingAnniversary variable
after changing the month of the weddingAnniversaryCopy variable to August 4 2010.
Notice that it has not changed from its original value of July 4 2010. The final message
displays the value of the weddingAnniversaryCopy variable. You can see that this has
changed to August 4 2010.
8. Press Enter and return to Visual Studio 2010.
9. Display the Date.cs file in the Code and Text Editor window.
10. Change the Date structure into a class, as shown in bold in the following code example:
class Date
{
}
11. On the Debug menu, click Start Without Debugging to build and run the application
again. Verify that the console window displays the following messages:
July 4 2010
Value of copy is July 4 2010
New value of weddingAnniversary is August 4 2010
Value of copy is August 4 2010
The first two messages and the fourth message are the same as before. However, the
third message shows that the value of the weddingAnniversary variable has changed
to August 4 2010. Remember that a structure is a value type, and when you copy a
value type variable you make a copy of all the data in the variable. However, a class is
a reference type, and when you copy a reference type variable you copy a reference to
the original variable. If the data in a class variable changes, all references to this variable
see the changes.
12. Press Enter and return to Visual Studio 2010.
In this chapter, you have seen how to create and use enumerations and structures. You have
learned some of the similarities and differences between a structure and a class, and you
have seen how to define constructors to initialize the fields in a structure. You have also
learned how to represent a structure as a string by overriding the ToString method.
190 Part II Understanding the C# Language
n
If you want to continue to the next chapter
Keep Visual Studio 2010 running, and turn to Chapter 10.
n
If you want to exit Visual Studio 2010 now
On the File menu, click Exit. If you see a Save dialog box, click Yes and save the project.
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 fields). For ex-
ample:
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 structure con-
structor. For example:
Time lunch = new Time(12, 30, 0);
191
Chapter 10
Using Arrays and Collections
After completing this chapter, you will be able to:
n
Declare, initialize, and use array variables.
n
Declare, initialize, 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 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 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 fields 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 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 vari-
able is an array. For example, to declare an array of int variables named pins, you 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
192 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 a contiguous block of memory holding the array elements on the
heap, just as a class variable refers to an object on the heap, and this contiguous block of
memory does not hold its array elements directly on the stack as a structure does. (To review
values and references and the differences 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 element type, followed
by the size of the array you’re creating between square brackets. Creating an array also ini-
tializes 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 the effects of this statement:
Chapter 10 Using Arrays and Collections 193
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 even be 0. An
array of size 0 is not a null array.
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 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:
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 maxi-
mum 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 dupli-
cating 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
194 Part II Understanding the C# Language
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 generates 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 at-
tempt 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 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 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};
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