Working with Constructors
When you use the new keyword to create an object, the common language runtime has to construct that object by using the definition of the class. The common language runtime has to grab a piece of memory from the operating system, fill it with the fields defined by the class, and then call the constructor to perform any initialization required.
A constructor is a special method; it has the same name as the class, it can take parameters, but it cannot return a value (not even a void). Every class must have a constructor. If you don't write one yourself, the compiler automatically generates a default constructor for you (however, it 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 a value. The following example shows the Circle class with a default constructor that initializes the radius field to 0:
class Circle { public Circle() // default constructor { radius = 0.0; } public double Area() { return 3.141592 * radius * radius; } private double 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 yourself, it is still the default constructor. You can also write non-default constructors, as you will see in the section titled “Overloading Constructors” later in this chapter.
Note that the constructor is marked as public. If this keyword is omitted, the constructor will be private (just like any other methods and fields). If the constructor is private, it cannot be used outside of the class, which will prevent 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. 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 (it's private, and there is no way of changing its value once it has been initialized). One way to solve this problem is to realize that a constructor is just a special kind of method and 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 you can also 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 {
public Circle() // default constructor { radius = 0.0; } public Circle(double initialRadius) // overloaded constructor { radius = initialRadius; } public double Area() { return 3.141593 * radius * radius; } private double radius; }
NOTE
The order of the constructors in a class is immaterial; you can define them in whatever order you feel most comfortable with.
There is a quirk of the C# language that you should be aware of: 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 constructor, you'll have to write the default constructor yourself.
Partial Classes
A class can contain a number of methods, fields, and constructors, as well as other items that we will discuss in later chapters. A highly functional class can become quite large. A new feature of C# 2.0 allows you to split the source code for a class into separate files, so you can organize the definition of a large class into smaller, easier to manage pieces. This feature is used by Visual Studio 2005 for Windows Forms applications, 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 was split between two files called circ1.cs (containing the constructors), and circ2.cs (containing the methods and fields, the contents of circ1.cs would look like this:
partial class Circle { public Circle() // default constructor { this.radius = 0.0; } public Circle(double initialRadius) // overloaded constructor { this.radius = initialRadius; } }
The contents of circ2.cs would look like this:
partial class Circle { public double Area() { return Math.PI * radius * radius; } private double radius; }
When you compile a class that has been split into separate files, you must provide all the files to the compiler.
In the following exercise, you will declare a class with two public constructors and two private fields. You will create instances of the class by using the new keyword and calling the constructors.
Write constructors and create objects
Start Microsoft Visual Studio 2005.
Open the Classes project, located in the \Microsoft Press\Visual CSharp Step by Step\Chapter 7\Classes folder in your My Documents folder.
Display the file Program.cs in the Code and Text Editor window, and locate the Main method of the Program class.
The Main method calls the Entrance method, wrapped in a try block and followed by a catch handler. This try/catch block allows you to write the code that would normally go inside Main in the Entrance method instead, safe in the knowledge that any exceptions will be caught.
Display the file Point.cs in the Code and Text Editor window.
The Point class is currently empty. There is no constructor, so the compiler will write one for you. You will now invoke this compiler-generated constructor.
Return to the Program.cs file, and locate the Entrance method of the Application class. Edit the body of the Entrance method to contain the following statement:
Point origin = new Point();
On the Build menu, click Build Solution.
The code builds without error because the compiler generates the code for a default constructor for the Point class. However, you cannot see the C# code for this constructor as the compiler does not generate any source language statements.
Return to the Point class in Point.cs. Add a public constructor that accepts two int arguments, and calls Console.WriteLine to display the values of the arguments to the console.
The Point class should look like this:
class Point { public Point(int x, int y) { Console.WriteLine("x:{0}, y:{1}", x, y); } }
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.
On the Build menu, click Build Solution.
The compiler now reports an error:
No overload for method 'Point' takes '0' arguments
The call to the default constructor in Entrance no longer works because there is no longer a default constructor. Because you have written your own constructor for the Point class, the compiler has not automatically generated the default constructor. You will now fix this by writing your own default constructor.
Edit the Point class and add a public default constructor that calls Console.WriteLine to write the string “default constructor called” to the console.
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); } }
In the Program.cs file, edit the body of the Entrance method. Declare a variable called bottomRight of type Point and initialize it to a new Point object by using the constructor with two arguments. Supply the values 1024 and 1280.
The Entrance method should now look like this:
static void Entrance() { Point origin = new Point(); Point bottomRight = new Point(1024, 1280); }
On the Debug menu, click Start Without Debugging.
The code now builds without errors, and runs. The following messages are written to the console:
default constructor called x:1024, y:1280
Press the Enter key.
The console window closes, and you return to the Visual Studio 2005 programming environment. You will now add two int fields to the Point class and modify the constructors to initialize these fields.
Edit the Point class and add two private instance fields called x and y of type int.
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); } private int x, y; }
You will now edit the second Point constructor to initialize the x and y fields to the values 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 in! { 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? It doesn't! A parameter to a method with the same name as a field overrides the field in any statements the method. All this constructor actually does is assign the parameters to themselves; it does not modify the fields at all. This is clearly not what we 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.”
Modify the Point constructor as follows:
public Point(int x, int y) { this.x = x; this.y = y; }
Edit the default Point constructor to initialize the x and y fields to –1 (and remove the Console.WriteLine statement). 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; }
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).
Naming and Accessibility
The following .NET Framework recommendations relate to the naming conventions for fields and methods based upon the accessibility of class members:
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 (it was first used in the Pascal language).
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 letter and constructors must match the name of their class exactly; therefore, a private constructor must start with a capital letter.
Methods that belong to a class and that operate on the data belonging to a particular instance of a class are called instance methods. 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
In the Classes project in Visual Studio 2005, add the following public instance method called DistanceTo to the Point class, between the constructors and the private variables. 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 next steps, you will edit 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 will need to calculate the difference between the x coordinates and the y coordinates.
In the DistanceTo method, declare a local int variable called xDiff, and initialize it to the difference between this.x and other.x:
int xDiff = this.x – other.x;
Declare another local int variable called yDiff, and initialize it to the difference between this.y and other.y.
The DistanceTo method should now look like this:
public double DistanceTo(Point other) { int xDiff = x - other.x; int yDiff = y - other.y; }
To calculate the distance, you can use a method based on Pythagoras' theorem: Work out the square root of the sum of the square of xDiff and the square of yDiff. The System.Math class provides the Sqrt method which you can use to calculate square roots.
Add the following return statement to perform the calculation:
return Math.Sqrt(xDiff * xDiff + yDiff * yDiff);
The DistanceTo method should now look like this:
public double DistanceTo(Point other) { int xDiff = x - other.x; int yDiff = y - other.y; return Math.Sqrt(xDiff * xDiff + yDiff * yDiff); }
You will now test the DistanceTo method.
Return to the Entrance 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 and when you pass the bottomRight object to it as an argument.
The Entrance method should now look like this:
static void Entrance() { Point origin = new Point(); Point bottomRight = new Point(1024, 1280); double distance = origin.DistanceTo(bottomRight); }
NOTE
IntelliSense should display the DistanceTo method when you type the period character after origin.
Add another statement to the Entrance method that writes the value of the distance variable to the console by using the Console.WriteLine method.
The Entrance method should now look like this:
static void Entrance() { Point origin = new Point(); Point bottomRight = new Point(1024, 1280); double distance = origin.DistanceTo(bottomRight); Console.WriteLine("Distance is : {0}", distance); }
On the Debug menu, click Start Without Debugging.
The program builds and runs.
Confirm that the value of 1640.605 (approximately) is written to the console window. Press Enter to close the application and return to Visual Studio 2005.