120 Part I Introducing Microsoft Visual C# and Microsoft Visual Studio 2010
3. Type 9876543 in the left operand text box, type 9876543 in the right operand text box,
click the Multiplication button, and then click Calculate.
The value –1195595903 appears in the Result text box on the form. This is a nega-
tive value, which cannot possibly be correct. This value is the result of a multiplication
operation that silently overflowed the 32-bit limit of the int type.
4. Click Quit, and return to the Visual Studio 2010 programming environment.
5. In the Code and Text Editor window displaying Window1.xaml.cs, locate the
multiplyValues method. It looks like this:
private int multiplyValues(int leftHandSide, int rightHandSide)
{
expression.Text = leftHandSide.ToString() + " * " + rightHandSide.ToString();
return leftHandSide * rightHandSide;
}
The return statement contains the multiplication operation that is silently overflowing.
6. Edit the return statement so that the return value is checked, like this:
return checked(leftHandSide * rightHandSide);
The multiplication is now checked and will throw an OverflowException rather than
silently returning the wrong answer.
7. Locate the calculateClick method.
8. Add the following catch handler immediately after the existing FormatException catch
handler in the calculateClick method:
catch (OverflowException oEx)
{
result.Text = oEx.Message;
}
Tip The logic of this catch handler is the same as that for the FormatException catch
handler. However, it is still worth keeping these handlers separate rather than simply
writing a generic Exception catch handler because you might decide to handle these
exceptions differently in the future.
9. On the Debug menu, click Start Without Debugging to build and run the application.
10. Type 9876543 in the left operand text box, type 9876543 in the right operand text box,
click the Multiplication button, and then click Calculate.
The second catch handler successfully catches the OverflowException and displays the
message “Arithmetic operation resulted in an overflow” in the Result text box.
11. Click Quit to return to the Visual Studio 2010 programming environment.
Chapter 6 Managing Errors and Exceptions 121
Throwing Exceptions
Suppose you are implementing a method called monthName that accepts a single int
argument and returns the name of the corresponding month. For example, monthName(1)
returns “January”, monthName(2) returns “February”, and so on. The question is: What should
the method return if the integer argument is less than 1 or greater than 12? The best answer
is that the method shouldn’t return anything at all; it should throw an exception. The .NET
Framework class libraries contain lots of exception classes specifically designed for situa-
tions such as this. Most of the time, you will find that one of these classes describes your
exceptional condition. (If not, you can easily create your own exception class, but you need
to know a bit more about the C# language before you can do that.) In this case, the existing
.NET Framework ArgumentOutOfRangeException class is just right. You can throw an excep-
tion by using the throw statement, as shown in the following example:
public static string monthName(int month)
{
switch (month)
{
case 1 :
return "January";
case 2 :
return "February";
case 12 :
return "December";
default :
throw new ArgumentOutOfRangeException("Bad month");
}
}
The throw statement needs an exception object to throw. This object contains the details
of the exception, including any error messages. This example uses an expression that cre-
ates a new ArgumentOutOfRangeException object. The object is initialized with a string that
populates its Message property by using a constructor. Constructors are covered in detail in
Chapter 7, “Creating and Managing Classes and Objects.”
In the following exercises, you will modify the MathsOperators project to throw an exception
if the user attempts to perform a calculation without specifying an operation to perform.
Throw an exception
1. Return to Visual Studio 2010.
2. On the Debug menu, click Start Without Debugging.
3. Type 24 in the left operand text box, type 36 in the right operand text box, and then
click Calculate.
The value 0 appears in the Result text box. The fact that you have not selected an
operator option is not immediately obvious. It would be useful to write a diagnostic
message in the Result text box.
122 Part I Introducing Microsoft Visual C# and Microsoft Visual Studio 2010
4. Click Quit to return to the Visual Studio 2010 programming environment.
5. In the Code and Text Editor window displaying Window1.xaml.cs, locate and examine
the doCalculation method. It looks like this:
private int doCalculation(int leftHandSide, int rightHandSide) {
int result = 0;
if (addition.IsChecked.HasValue && addition.IsChecked.Value)
result = addValues(leftHandSide, rightHandSide);
else if (subtraction.IsChecked.HasValue && subtraction.IsChecked.Value)
result = subtractValues(leftHandSide, rightHandSide);
else if (multiplication.IsChecked.HasValue && multiplication.IsChecked.Value)
result = multiplyValues(leftHandSide, rightHandSide);
else if (division.IsChecked.HasValue && division.IsChecked.Value)
result = divideValues(leftHandSide, rightHandSide);
else if (remainder.IsChecked.HasValue && remainder.IsChecked.Value)
result = remainderValues(leftHandSide, rightHandSide);
return result;
}
The addition, subtraction, multiplication, division, and remainder fields are the buttons
that appear on the form. Each button has a property called IsChecked that indicates
whether the user has selected it. The IsChecked property is an example of a nullable val-
ue, which means it can either contain a specific value or be in an undefined state. (You
learn more about nullable values in Chapter 8, “Understanding Values and References.”)
The IsChecked.HasValue property indicates whether the button is in a defined state,
and if it is, the IsChecked.Value property indicates what this state is. The IsChecked.Value
property is a Boolean that has the value true if the button is selected or false otherwise.
The cascading if statement examines each button in turn to find which one is selected.
(The radio buttons are mutually exclusive, so the user can select only one radio button
at most.) If none of the buttons are selected, none of the if statements will be true and
the result variable will remain at its initial value (0). This variable holds the value that is
returned by the method.
You could try to solve the problem by adding one more else statement to the if-else
cascade to write a message to the result text box on the form. However, this solution
is not a good idea because it is not really the purpose of this method to output mes-
sages. It is better to separate the detection and signaling of an error from the catching
and handling of that error.
6. Add another else statement to the list of if-else statements (immediately before the
return statement), and throw an InvalidOperationException exactly as follows:
else
throw new InvalidOperationException("No operator selected");
7. On the Debug menu, click Start Without Debugging to build and run the application.
8. Type 24 in the left operand text box, type 36 in the right operand text box, and then
click Calculate.
Chapter 6 Managing Errors and Exceptions 123
Windows detects that your application has thrown an exception, and an exception
dialog box appears (eventually). The application has thrown an exception, but your
code does not catch it yet.
9. Click Close program.
The application terminates, and you return to Visual Studio 2010.
Now that you have written a throw statement and verified that it throws an exception, you
will write a catch handler to handle this exception.
Catch the exception
1. In the Code and Text Editor window displaying Window1.xaml.cs, locate the
calculateClick method.
2. Add the following catch handler immediately below the existing two catch handlers in
the calculateClick method:
catch (InvalidOperationException ioEx)
{
result.Text = ioEx.Message;
}
This code catches the InvalidOperationException that is thrown when no operator
button is selected.
3. On the Debug menu, click Start Without Debugging.
4. Type 24 in the left operand text box, type 36 in the right operand text box, and then
click Calculate.
The message “No operator selected” appears in the Result text box.
5. Click Quit.
The application is now a lot more robust than it was. However, several exceptions could
still arise that are not caught and that will cause the application to fail. For example, if you
attempt to divide by 0, an unhandled DivideByZeroException will be thrown. (Integer division
by 0 does throw an exception, unlike floating-point division by 0.) One way to solve this is to
write an ever larger number of catch handlers inside the calculateClick method. However, a
better solution is to add a general catch handler that catches Exception at the end of the list
of catch handlers. This will trap all unhandled exceptions.
Tip The decision of whether to catch all unhandled exceptions explicitly in a method depends
on the nature of the application you are building. In some cases, it makes sense to catch excep-
tions as close as possible to the point at which they occur. In other situations, it is more use-
ful to let an exception propagate back to the method that invoked the routine that threw the
exception and handle the error there.
124 Part I Introducing Microsoft Visual C# and Microsoft Visual Studio 2010
Catch unhandled exceptions
1. In the Code and Text Editor window displaying Window1.xaml.cs, locate the
calculateClick method.
2. Add the following catch handler to the end of the list of existing catch handlers:
catch (Exception ex)
{
result.Text = ex.Message;
}
This catch handler will catch all hitherto unhandled exceptions, whatever their specific
type.
3. On the Debug menu, click Start Without Debugging.
You will now attempt to perform some calculations known to cause exceptions and
confirm that they are all handled correctly.
4. Type 24 in the left operand text box, type 36 in the right operand text box, and then
click Calculate.
Confirm that the diagnostic message “No operator selected” still appears in the Result
text box. This message was generated by the InvalidOperationException handler.
5. Type John in the left operand text box, and then click Calculate.
Confirm that the diagnostic message “Input string was not in a correct format” appears
in the Result text box. This message was generated by the FormatException handler.
6. Type 24 in the left operand text box, type 0 in the right operand text box, click the
Division button, and then click Calculate.
Confirm that the diagnostic message “Attempted to divide by zero” appears in the
Result text box. This message was generated by the general Exception handler.
7. Click Quit.
Using a finally Block
It is important to remember that when an exception is thrown, it changes the flow of
execution through the program. This means you can’t guarantee that a statement will always
run when the previous statement finishes because the previous statement might throw an
exception. Look at the following example. It’s very easy to assume that the call to reader.Close
will always occur when the while loop completes. After all, it’s right there in the code:
TextReader reader = src.OpenText();
string line;
while ((line = reader.ReadLine()) != null)
{
source.Text += line + "\n";
}
reader.Close();
Chapter 6 Managing Errors and Exceptions 125
Sometimes it’s not an issue if one particular statement does not run, but on many occasions
it can be a big problem. If the statement releases a resource that was acquired in a previous
statement, failing to execute this statement results in the resource being retained. This exam-
ple is just such a case: If the call to src.OpenText succeeds, it acquires a resource (a file handle)
and you must ensure that you call reader.Close to release the resource. If you don’t, sooner or
later you’ll run out of file handles and be unable to open more files. (If you find file handles
too trivial, think of database connections instead.)
The way to ensure that a statement is always run, whether or not an exception has been
thrown, is to write that statement inside a finally block. A finally block occurs immediately
after a try block or immediately after the last catch handler after a try block. As long as the
program enters the try block associated with a finally block, the finally block will always be
run, even if an exception occurs. If an exception is thrown and caught locally, the exception
handler executes first, followed by the finally block. If the exception is not caught locally (that
is, the runtime has to search through the list of calling methods to find a handler), the finally
block runs first. In any case, the finally block always executes.
The solution to the reader.Close problem is as follows:
TextReader reader = null;
try
{
reader = src.OpenText();
string line;
while ((line = reader.ReadLine()) != null)
{
source.Text += line + "\n";
}
}
finally
{
if (reader != null)
{
reader.Close();
}
}
Even if an exception is thrown, the finally block ensures that the reader.Close statement
always executes. You’ll see another way to solve this problem in Chapter 14, “Using Garbage
Collection and Resource Management.”
In this chapter, you learned how to catch and handle exceptions by using the try and catch
constructs. You saw how to enable and disable integer overflow checking by using the
checked and unchecked keywords. You learned how to throw an exception if your code de-
tects an exceptional situation, and you saw how to use a finally block to ensure that critical
code always runs, even if an exception occurs.
126 Part I Introducing Microsoft Visual C# and Microsoft Visual Studio 2010
n
If you want to continue to the next chapter
Keep Visual Studio 2010 running, and turn to Chapter 7.
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 6 Quick Reference
To Do this
Catch a specific exception Write a catch handler that catches the specific exception class.
For example:
try
{
}
catch (FormatException fEx)
{
}
Ensure that integer arithmetic is
always checked for overflow
Use the checked keyword. For example:
int number = Int32.MaxValue;
checked
{
number++;
}
Throw an exception Use a throw statement. For example:
throw new FormatException(source);
Catch all exceptions in a single catch
handler
Write a catch handler that catches Exception. For example:
try
{
}
catch (Exception ex)
{
}
Ensure that some code will always be
run, even if an exception is thrown
Write the code inside a finally block. For example:
try
{
}
finally
{
// always run
}
Microsoft Visual C# 2010 Step by Step
127
Part II
Understanding the C# Language
In this part:
Creating and Managing Classes and Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
Understanding Values and References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
Creating Value Types with Enumerations and Structures. . . . . . . . . . . . . . . . . . . 173
Using Arrays and Collections. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
Understanding Parameter Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
Working with Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231
Creating Interfaces and Defining Abstract Classes . . . . . . . . . . . . . . . . . . . . . . . . 253
Using Garbage Collection and Resource Management . . . . . . . . . . . . . . . . . . . . 279
129
Chapter 7
Creating and Managing Classes
and Objects
After completing this chapter, you will be able to:
n
Define a class containing a related set of methods and data items.
n
Control the accessibility of members by using the public and private keywords.
n
Create objects by using the new keyword to invoke a constructor.
n
Write and call your own constructors.
n
Create methods and data that can be shared by all instances of the same class by using
the static keyword.
n
Explain how to create anonymous classes.
In Part I, “Introducing Microsoft Visual C# and Microsoft Visual Studio 2010,” you learned
how to declare variables, use operators to create values, call methods, and write many of the
statements you need when implementing a method. You now know enough to progress to
the next stage—combining methods and data into your own classes.
The Microsoft .NET Framework contains thousands of classes, and you have used a number
of them already, including Console and Exception. Classes provide a convenient mechanism
for modeling the entities manipulated by applications. An entity can represent a specific item,
such as a customer, or something more abstract, such as a transaction. Part of the design
process of any system is concerned with determining the entities that are important to the
processes that the system implements, and then performing an analysis to see what infor-
mation these entities need to hold and what operations they should perform. You store the
information that a class holds as fields and use methods to implement the operations that a
class can perform.
The chapters in Part II, “Understanding the C# Language,” provide you with all you need to
know to be able to create your own classes.
Understanding Classification
Class is the root word of the term classification. When you design a class, you systemati-
cally arrange information and behavior into a meaningful entity. This arranging is an act of
classification and is something that everyone does—not just programmers. For example, all
cars share common behaviors (they can be steered, stopped, accelerated, and so on) and
130 Part II Understanding the C# Language
common attributes (they have a steering wheel, an engine, and so on). People use the word
car to mean objects that share these common behaviors and attributes. As long as every-
one agrees on what a word means, this system works well and you can express complex but
precise ideas in a concise form. Without classification, it’s hard to imagine how people could
think or communicate at all.
Given that classification is so deeply ingrained in the way we think and communicate, it
makes sense to try to write programs by classifying the different concepts inherent in a
problem and its solution and then modeling these classes in a programming language. This
is exactly what you can do with modern object-oriented programming languages, such as
Microsoft Visual C#.
The Purpose of Encapsulation
Encapsulation is an important principle when defining classes. The idea is that a program that
uses a class should not have to worry how that class actually works internally; the program
simply creates an instance of a class and calls the methods of that class. As long as those
methods do what they say they will do, the program does not care how they are implement-
ed. For example, when you call the Console.WriteLine method, you don’t want to be bothered
with all the intricate details of how the Console class physically arranges for data to be writ-
ten to the screen. A class might need to maintain all sorts of internal state information to
perform its various methods. This additional state information and activity is hidden from the
program that is using the class. Therefore, encapsulation is sometimes referred to as informa-
tion hiding. Encapsulation actually has two purposes:
n
To combine methods and data inside a class; in other words, to support classification
n
To control the accessibility of the methods and data; in other words, to control the use
of the class
Defining and Using a Class
In C#, you use the class keyword to define a new class. The data and methods of the class oc-
cur in the body of the class between a pair of braces. Here is a C# class called Circle that con-
tains one method (to calculate the circle’s area) and one piece of data (the circle’s radius):
class Circle
{
int radius;
double Area()
{
return Math.PI * radius * radius;
}
}
Chapter 7 Creating and Managing Classes and Objects 131
Note The Math class contains methods for performing mathematical calculations
and fields containing mathematical constants. The Math.PI field contains the value
3.14159265358979323846, which is an approximation of the value of pi.
The body of a class contains ordinary methods (such as Area) and fields (such as radius)—
remember that variables in a class are called fields. You’ve already seen how to declare vari-
ables in Chapter 2, “Working with Variables, Operators, and Expressions,” and how to write
methods in Chapter 3, “Writing Methods and Applying Scope,” so there’s almost no new
syntax here.
You can use the Circle class in a similar manner to using the other types that you have already
met; you create a variable specifying Circle as its type, and then you initialize the variable
with some valid data. Here is an example:
Circle c; // Create a Circle variable
c = new Circle(); // Initialize it
A point worth highlighting in this code is the use of the new keyword. Previously, when you
initialized a variable such as an int or a float, you simply assigned it a value:
int i;
i = 42;
You cannot do the same with variables of class types. One reason for this is that C# just
doesn’t provide the syntax for assigning literal class values to variables. You cannot write a
statement such as this:
Circle c;
c = 42;
After all, what is the Circle equivalent of 42? Another reason concerns the way in which
memory for variables of class types is allocated and managed by the runtime—this is dis-
cussed further in Chapter 8, “Understanding Values and References.” For now, just accept
that the new keyword creates a new instance of a class, more commonly called an object.
You can, however, directly assign an instance of a class to another variable of the same type,
like this:
Circle c;
c = new Circle();
Circle d;
d = c;
However, this is not as straightforward as it first appears, for reasons that that are described
in Chapter 8.
132 Part II Understanding the C# Language
Important Don’t get confused between the terms class and object. A class is the definition of a
type. An object is an instance of that type, created when the program runs.
Controlling Accessibility
Surprisingly, the Circle class is currently of no practical use. When you encapsulate your
methods and data inside a class, the class forms a boundary to the outside world. Fields
(such as radius) and methods (such as Area) defined in the class can be seen by other meth-
ods inside the class but not by the outside world—they are private to the class. So, although
you can create a Circle object in a program, you cannot access its radius field or call its
Area method, which is why the class is not of much use—yet! However, you can modify the
definition of a field or method with the public and private keywords to control whether it is
accessible from the outside:
n
A method or field is private if it is accessible only from the inside of the class. To declare
that a method or field is private, you write the keyword private before its declaration.
This is actually the default, but it is good practice to state explicitly that fields and
methods are private to avoid any confusion.
n
A method or field is public if it is accessible from both the inside and outside of the
class. To declare that a method or field is public, you write the keyword public before its
declaration.
Here is the Circle class again. This time Area is declared as a public method and radius is
declared as a private field:
class Circle
{
private int radius;
public double Area()
{
return Math.PI * radius * radius;
}
}
Note C++ programmers should note that there is no colon after the public and private
keywords. You must repeat the keyword for every field and method declaration.
Although radius is declared as a private field and is not accessible from outside the class,
radius is accessible from inside the Circle class. The Area method is inside the Circle class, so
the body of Area has access to radius. However, the class is still of limited value because there
is no way of initializing the radius field. To fix this, you can use a constructor.
Chapter 7 Creating and Managing Classes and Objects 133
Tip Unlike variables declared in a method, which are not initialized by default, the fields in a
class are automatically initialized to 0, false, or null depending on their type. However, it is still
good practice to provide an explicit means of initializing fields.
Naming and Accessibility
The following recommendations relate to the naming conventions for fields and
methods based on the accessibility of class members:
n
Identifiers that are public should start with a capital letter. For example,
Area starts with “A” (not “a”) because it’s public. This system is known as the
PascalCase naming scheme (because it was first used in the Pascal language).
n
Identifiers that are not public (which include local variables) should start with
a lowercase letter. For example, radius starts with “r” (not “R”) because it’s
private. This system is known as the camelCase naming scheme.
There’s only one exception to this rule: class names should start with a capital let-
ter, and constructors must match the name of their class exactly; therefore, a private
constructor must start with a capital letter.
Important Don’t declare two public class members whose names differ only in case. If you do,
developers using other languages that are not case sensitive, such as Microsoft Visual Basic, will
not be able to use your class.
Working with Constructors
When you use the new keyword to create an object, the runtime has to construct that object
by using the definition of the class. The runtime has to grab a piece of memory from the
operating system, fill it with the fields defined by the class, and then invoke a constructor to
perform any initialization required.
A constructor is a special method that runs automatically when you create an instance of a
class. It has the same name as the class, and it can take parameters, but it cannot return a
value (not even void). Every class must have a constructor. If you don’t write one, the compil-
er automatically generates a default constructor for you. (However, the compiler-generated
default constructor doesn’t actually do anything.) You can write your own default constructor
quite easily—just add a public method with the same name as the class that does not return
134 Part II Understanding the C# Language
a value. The following example shows the Circle class with a default constructor that initializes
the radius field to 0:
class Circle
{
private int radius;
public Circle() // default constructor
{
radius = 0;
}
public double Area()
{
return Math.PI * radius * radius;
}
}
Note In C# parlance, the default constructor is a constructor that does not take any parameters.
It does not matter whether the compiler generates it or you write it; it is still the default con-
structor. You can also write non–default constructors (constructors that do take parameters), as
you will see in the upcoming section titled “Overloading Constructors.”
In this example, the constructor is marked as public. If this keyword is omitted, the construc-
tor will be private (just like any other methods and fields). If the constructor is private, it can-
not be used outside the class, which prevents you from being able to create Circle objects
from methods that are not part of the Circle class. You might therefore think that private
constructors are not that valuable. However, they do have their uses, but they are beyond the
scope of the current discussion.
You can now use the Circle class and exercise its Area method. Notice how you use dot
notation to invoke the Area method on a Circle object:
Circle c;
c = new Circle();
double areaOfCircle = c.Area();
Overloading Constructors
You’re almost finished, but not quite. You can now declare a Circle variable, point it to a
newly created Circle object, and then call its Area method. However, there is still one last
problem. The area of all Circle objects will always be 0 because the default constructor sets
the radius to 0 and it stays at 0; the radius field is private, and there is no easy way of chang-
ing its value after it has been initialized. However, you should realize that a constructor is just
Chapter 7 Creating and Managing Classes and Objects 135
a special kind of method and that it—like all methods—can be overloaded. Just as there are
several versions of the Console.WriteLine method, each of which takes different parameters,
so too you can write different versions of a constructor. You can add a constructor to the
Circle class, with the radius as its parameter, like this:
class Circle
{
private int radius;
public Circle() // default constructor
{
radius = 0;
}
public Circle(int initialRadius) // overloaded constructor
{
radius = initialRadius;
}
public double Area()
{
return Math.PI * radius * radius;
}
}
Note The order of the constructors in a class is immaterial; you can define constructors in
whatever order you feel most comfortable with.
You can then use this constructor when creating a new Circle object, like this:
Circle c;
c = new Circle(45);
When you build the application, the compiler works out which constructor it should call
based on the parameters that you specify to the new operator. In this example, you passed
an int, so the compiler generates code that invokes the constructor that takes an int
parameter.
You should be aware of a quirk of the C# language: if you write your own constructor for a
class, the compiler does not generate a default constructor. Therefore, if you’ve written your
own constructor that accepts one or more parameters and you also want a default construc-
tor, you’ll have to write the default constructor yourself.
136 Part II Understanding the C# Language
Partial Classes
A class can contain a number of methods, fields, and constructors, as well as other
items discussed in later chapters. A highly functional class can become quite large. With
C#, you can split the source code for a class into separate files so that you can organize
the definition of a large class into smaller, easier to manage pieces. This feature is used
by Microsoft Visual Studio 2010 for Windows Presentation Foundation (WPF) applica-
tions, where the source code that the developer can edit is maintained in a separate
file from the code that is generated by Visual Studio whenever the layout of a form
changes.
When you split a class across multiple files, you define the parts of the class by using
the partial keyword in each file. For example, if the Circle class is split between two files
called circ1.cs (containing the constructors) and circ2.cs (containing the methods and
fields), the contents of circ1.cs look like this:
partial class Circle
{
public Circle() // default constructor
{
this.radius = 0;
}
public Circle(int initialRadius) // overloaded constructor
{
this.radius = initialRadius;
}
}
The contents of circ2.cs look like this:
partial class Circle
{
private int radius;
public double Area()
{
return Math.PI * this.radius * this.radius;
}
}
When you compile a class that has been split into separate files, you must provide all
the files to the compiler.
Note You can define partial interfaces and structs in the same way.
Chapter 7 Creating and Managing Classes and Objects 137
In the following exercise, you will declare a class that models a point in two-dimensional
space. The class will contain two private fields for holding the x and y coordinates of a point
and will provide constructors for initializing these fields. You will create instances of the class
by using the new keyword and calling the constructors.
Write constructors and create objects
1. Start Visual Studio 2010 if it is not already running.
2. Open the Classes project located in the \Microsoft Press\Visual CSharp Step By Step\
Chapter 7\Classes folder in your Documents folder.
3. In Solution Explorer, double-click the file Program.cs to display it in the Code and Text
Editor window.
4. Locate the Main method in the Program class.
The Main method calls the DoWork method, wrapped in a try block and followed by a
catch handler. With this try/catch block, you can write the code that would typically go
inside Main in the DoWork method instead, safe in the knowledge that it will catch and
handle any exceptions.
5. Display the file Point.cs in the Code and Text Editor window.
This file defines a class called Point, which you will use to represent the location of a
point defined by a pair of x and y coordinates. The Point class is currently empty.
6. Return to the Program.cs file, and locate the DoWork method of the Program class. Edit
the body of the DoWork method, and replace the // to do comment with the follow-
ing statement:
Point origin = new Point();
7. On the Build menu, click Build Solution.
The code builds without error because the compiler automatically generates the code
for a default constructor for the Point class. However, you cannot see the C# code
for this constructor because the compiler does not generate any source language
statements.
8. Return to the Point class in the file Point.cs. Replace the // to do comment with a
public constructor that accepts two int arguments called x and y and that calls the
Console.WriteLine method to display the values of these arguments to the console, as
shown in bold type in the following code example. The Point class should look like this:
class Point
{
public Point(int x, int y)
{
Console.WriteLine("x:{0}, y:{1}", x, y);
}
}
138 Part II Understanding the C# Language
Note Remember that the Console.WriteLine method uses {0} and {1} as placeholders. In
the statement shown, {0} will be replaced with the value of x, and {1} will be replaced with
the value of y when the program runs.
9. On the Build menu, click Build Solution.
The compiler now reports an error:
'Classes.Point' does not contain a constructor that takes '0 ' arguments
The call to the default constructor in DoWork no longer works because there is no lon-
ger a default constructor. You have written your own constructor for the Point class, so
the compiler no longer generates the default constructor. You will now fix this by writ-
ing your own default constructor.
10. Edit the Point class, and add a public default constructor that calls Console.WriteLine to
write the string “default constructor called” to the console, as shown in bold type in the
following code example. The Point class should now look like this:
class Point
{
public Point()
{
Console.WriteLine("Default constructor called");
}
public Point(int x, int y)
{
Console.WriteLine("x:{0}, y:{1}", x, y);
}
}
11. On the Build menu, click Build Solution.
The program should now build successfully.
12. In the Program.cs file, edit the body of the DoWork method. Declare a variable called
bottomRight of type Point, and initialize it to a new Point object by using the construc-
tor with two arguments, as shown in bold type in the following code. Supply the values
1024 and 1280, representing the coordinates at the lower-right corner of the screen
based on the resolution 1024 × 1280. The DoWork method should now look like this:
static void DoWork()
{
Point origin = new Point();
Point bottomRight = new Point(1024, 1280);
}
Chapter 7 Creating and Managing Classes and Objects 139
13. On the Debug menu, click Start Without Debugging.
The program builds and runs, displaying the following messages to the console:
Default constructor called
x:1024, y:1280
14. Press the Enter key to end the program and return to Visual Studio 2010.
You will now add two int fields to the Point class to represent the x and y coordinates of
a point, and you will modify the constructors to initialize these fields.
15. Edit the Point class in the Point.cs file, and add two private instance fields called x and
y of type int, as shown in bold type in the following code. The Point class should now
look like this:
class Point
{
private int x, y;
public Point()
{
Console.WriteLine("default constructor called");
}
public Point(int x, int y)
{
Console.WriteLine("x:{0}, y:{1}", x, y);
}
}
You will now edit the second Point constructor to initialize the x and y fields to the val-
ues of the x and y parameters. There is a potential trap when you do this. If you are not
careful, the constructor will look like this:
public Point(int x, int y) // Don't type this!
{
x = x;
y = y;
}
Although this code will compile, these statements appear to be ambiguous. How does
the compiler know in the statement x = x; that the first x is the field and the second
x is the parameter? The answer is that it doesn’t! A method parameter with the same
name as a field hides the field for all statements in the method. All this code actually
does is assign the parameters to themselves; it does not modify the fields at all. This is
clearly not what you want.
The solution is to use the this keyword to qualify which variables are parameters and
which are fields. Prefixing a variable with this means “the field in this object.”
140 Part II Understanding the C# Language
16. Modify the Point constructor that takes two parameters, and replace the
Console.WriteLine statement with the following code shown in bold type:
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
17. Edit the default Point constructor to initialize the x and y fields to –1, as follows in bold
type. Note that although there are no parameters to cause confusion, it is still good
practice to qualify the field references with this:
public Point()
{
this.x = -1;
this.y = -1;
}
18. On the Build menu, click Build Solution. Confirm that the code compiles without errors
or warnings. (You can run it, but it does not produce any output yet.)
Methods that belong to a class and that operate on the data belonging to a particular in-
stance of a class are called instance methods. (There are other types of methods that you will
meet later in this chapter.) In the following exercise, you will write an instance method for the
Point class, called DistanceTo, that calculates the distance between two points.
Write and call instance methods
1. In the Classes project in Visual Studio 2010, add the following public instance method
called DistanceTo to the Point class after the constructors. The method accepts a single
Point argument called other and returns a double.
The DistanceTo method should look like this:
class Point
{
public double DistanceTo(Point other)
{
}
}
In the following steps, you will add code to the body of the DistanceTo instance method
to calculate and return the distance between the Point object being used to make
the call and the Point object passed as a parameter. To do this, you must calculate the
difference between the x coordinates and the y coordinates.
Chapter 7 Creating and Managing Classes and Objects 141
2. In the DistanceTo method, declare a local int variable called xDiff, and initialize it to the
difference between this.x and other.x, as shown here in bold type:
public double DistanceTo(Point other)
{
int xDiff = this.x - other.x;
}
3. Declare another local int variable called yDiff, and initialize it to the difference between
this.y and other.y, as shown here in bold type:
public double DistanceTo(Point other)
{
int xDiff = this.x - other.x;
int yDiff = this.y - other.y;
}
To calculate the distance, you can use the Pythagorean theorem and calculate the
square root of the sum of the square of xDiff and the square of yDiff. The System.Math
class provides the Sqrt method that you can use to calculate square roots.
4. Add the return statement shown in bold type in the following code to the end of the
DistanceTo method to perform the calculation:
public double DistanceTo(Point other)
{
int xDiff = this.x - other.x;
int yDiff = this.y - other.y;
return Math.Sqrt((xDiff * xDiff) + (yDiff * yDiff));
}
You will now test the DistanceTo method.
5. Return to the DoWork method in the Program class. After the statements that
declare and initialize the origin and bottomRight Point variables, declare a variable
called distance of type double. Initialize this double variable to the result obtained when
you call the DistanceTo method on the origin object, passing the bottomRight object to
it as an argument.
The DoWork method should now look like this:
static void DoWork()
{
Point origin = new Point();
Point bottomRight = new Point(1024, 1280);
double distance = origin.DistanceTo(bottomRight);
}
Note Microsoft IntelliSense should display the DistanceTo method when you type the
period character after origin.
142 Part II Understanding the C# Language
6. Add to the DoWork method another statement that writes the value of the distance
variable to the console by using the Console.WriteLine method.
The completed DoWork method should look like this:
static void DoWork()
{
Point origin = new Point();
Point bottomRight = new Point(1024, 1280);
double distance = origin.DistanceTo(bottomRight);
Console.WriteLine("Distance is: {0}", distance);
}
7. On the Debug menu, click Start Without Debugging.
8. Confirm that the value 1640.60537607311 is written to the console window.
9. Press Enter to close the application and return to Visual Studio 2010.
Understanding static Methods and Data
In the preceding exercise, you used the Sqrt method of the Math class; similarly, when look-
ing at the Circle class, you read the PI field of the Math class. If you think about it, the way
in which you called the Sqrt method or read the PI field was slightly odd. You invoked the
method or read the field on the class itself, not on an object of type Math. It is like trying to
write Point.DistanceTo rather than origin.DistanceTo in the code you added in the preceding
exercise. So what’s happening, and how does this work?
You will often find that not all methods naturally belong to an instance of a class; they are
utility methods inasmuch as they provide a useful function that is independent of any specific
class instance. The Sqrt method is just such an example. If Sqrt were an instance method of
Math, you’d have to create a Math object to call Sqrt on:
Math m = new Math();
double d = m.Sqrt(42.24);
This would be cumbersome. The Math object would play no part in the calculation of the
square root. All the input data that Sqrt needs is provided in the parameter list, and the result
is passed back to the caller by using the method’s return value. Objects are not really needed
here, so forcing Sqrt into an instance straitjacket is just not a good idea. As well as contain-
ing the Sqrt method and the PI field, the Math class contains many other mathematical utility
methods, such as Sin, Cos, Tan, and Log.
Chapter 7 Creating and Managing Classes and Objects 143
In C#, all methods must be declared inside a class. However, if you declare a method or a
field as static, you can call the method or access the field by using the name of the class. No
instance is required. This is how the Sqrt method of the real Math class is declared:
class Math
{
public static double Sqrt(double d)
{
}
}
When you define a static method, it does not have access to any instance fields defined for
the class; it can use only fields that are marked as static. Furthermore, it can directly invoke
only other methods in the class that are marked as static; nonstatic (instance) methods re-
quire you first to create an object on which to call them.
Creating a Shared Field
As mentioned in the preceding section, you can also use the static keyword when defining a
field. With this feature, you can create a single field that is shared among all objects created
from a single class. (Nonstatic fields are local to each instance of an object.) In the following
example, the static field NumCircles in the Circle class is incremented by the Circle construc-
tor every time a new Circle object is created:
class Circle
{
private int radius;
public static int NumCircles = 0;
public Circle() // default constructor
{
radius = 0;
NumCircles++;
}
public Circle(int initialRadius) // overloaded constructor
{
radius = initialRadius;
NumCircles++;
}
}
144 Part II Understanding the C# Language
All Circle objects share the same NumCircles field, so the statement NumCircles++; incre-
ments the same data every time a new instance is created. You access the NumCircles field by
specifying the Circle class rather than a Circle object. For example:
Console.WriteLine("Number of Circle objects: {0}", Circle.NumCircles);
Tip Keep in mind that static methods are also called class methods. However, static fields aren’t
usually called class fields; they’re just called static fields (or sometimes static variables).
Creating a static Field by Using the const Keyword
By prefixing the field with the const keyword, you can declare that a field is static but that
its value can never change. const is short for “constant.” A const field does not use the static
keyword in its declaration but is nevertheless static. However, for reasons that are beyond the
scope of this book, you can declare a field as const only when the field is an enumeration, a
numeric type such as int or double, or a string. (You learn about enumerations in Chapter 9,
“Creating Value Types with Enumerations and Structures.”) For example, here’s how the Math
class declares PI as a const field:
class Math
{
public const double PI = 3.14159265358979323846;
}
Static Classes
Another feature of the C# language is the ability to declare a class as static. A static class can
contain only static members. (All objects that you create using the class share a single copy
of these members.) The purpose of a static class is purely to act as a holder of utility meth-
ods and fields. A static class cannot contain any instance data or methods, and it does not
make sense to try to create an object from a static class by using the new operator. In fact,
you can’t actually create an instance of an object using a static class by using new even if you
want to. (The compiler will report an error if you try.) If you need to perform any initializa-
tion, a static class can have a default constructor as long as it is also declared as static. Any
other types of constructor are illegal and will be reported as such by the compiler.