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.. If the name of your enumerat
Trang 1170 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 safe keyword can be used to mark a block of code, or an entire method, as shown here:
un-public static void Main(string [] args)
Trang 2In 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
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);
}
Trang 3172 Part II Understanding the C# Language
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() {
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;
Trang 4173
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
Trang 5174 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 }
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
Trang 6that 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 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
enumer-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 ing example, the underlying values of Spring, Summer, Fall, and Winter are now 1, 2, 3, and 4
preced-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 }
Trang 7176 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 ing exercise, you will work with a Console application to declare and use an enumeration that represents the months of the year
follow-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
}
}
Trang 85 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()
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()
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
Trang 9178 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()
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
Trang 10can convert a numeric value to its string representation The following statements are all legal statements in C#:
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
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
Trang 11180 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:
“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
Trang 12Understanding 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:
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 concon-structor 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
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;
}
Trang 13182 Part II Understanding the C# Language
The following table summarizes the main differences between a structure and a 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?
If you don’t initialize a field in your own
constructor, will the compiler automatically
initialize it for you?
Are you allowed to initialize instance fields at
their point of declaration?
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:
private Time currentTime;
public void Method(Time parameter)
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;
Trang 14Understanding Structure Initialization
Earlier in this chapter, you saw how the fields in a structure can be initialized by using a structor If you call a constructor, the various rules described earlier guarantee that all the fields in the structure will be initialized:
con-Time now = new con-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;
Trang 15
184 Part II Understanding the C# Language
public Time(int hh, int mm)
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 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
construc-(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
Trang 16century 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)
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 genoth-erated 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()
Trang 17186 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()
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 ried on Independence Day and hence lost my independence!) In the second statement,
mar-write the value of weddingAnniversary to the console
The DoWork method should now look like this:
static void DoWork()
Trang 1810 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
Trang 19188 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()
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()
Trang 207 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
Trang 21190 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
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 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 ample:
ex-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:
Trang 22191
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 varivari-ables 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
Trang 23192 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:
Trang 24The 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 cating a sequence of random numbers An overloaded version of the constructor enables you
dupli-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
Trang 25194 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 stants 3.5 and 99.999 are both double, and the C# compiler can convert the integer values 1 and 2 to double values:
con-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