Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented...

38
Principles of Object- Oriented Programming In general, when learning a new programming language, you will spend a fair part of your effort learning the syntax of that language: how to declare variables, how to control the flow of execu- tion, and so on. However, to write quality code, you also need to understand the principles and methodologies behind the language. C# is a fully object-oriented language, so in order to create well-designed C# code you need to come to grips with its object-oriented features, and that means learning about object-oriented programming (OOP). In OOP, you aim to write easily maintainable and reusable pieces of code that can perform collec- tively very complex tasks. However, the whole structure of an object-oriented program is very different from the structure of an equivalent program written in a procedural language. This appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented in C#) throughout this appendix, the emphasis is on learning those principles that apply to OOP in general, no matter which language you are using. OOP is an extremely powerful methodology. Once you’ve familiarized yourself with writing your code using OOP, you will probably wonder how you ever got by without it. You’ll find that, unlike procedural languages, OOP gives your code an intuitive, “natural” structure. Even Visual Basic 6, which implements a few object-oriented features, cannot keep up with true OOP. The appendix starts by discussing the nature of an object before moving on to examine the concept of inheritance. Inheritance, which is at the heart of OOP, enables you to conveniently reuse the code for classes. You’ll learn how to use inheritance in your programs both from a conceptual and C# point of view.

Transcript of Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented...

Page 1: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

Principles of Object-Oriented Programming

In general, when learning a new programming language, you will spend a fair part of your effortlearning the syntax of that language: how to declare variables, how to control the flow of execu-tion, and so on. However, to write quality code, you also need to understand the principles andmethodologies behind the language. C# is a fully object-oriented language, so in order to createwell-designed C# code you need to come to grips with its object-oriented features, and that meanslearning about object-oriented programming (OOP).

In OOP, you aim to write easily maintainable and reusable pieces of code that can perform collec-tively very complex tasks. However, the whole structure of an object-oriented program is very different from the structure of an equivalent program written in a procedural language. Thisappendix introduces the principles of object-oriented programming. Although you’ll see some C#syntax (because the examples are presented in C#) throughout this appendix, the emphasis is onlearning those principles that apply to OOP in general, no matter which language you are using.

OOP is an extremely powerful methodology. Once you’ve familiarized yourself with writing yourcode using OOP, you will probably wonder how you ever got by without it. You’ll find that, unlikeprocedural languages, OOP gives your code an intuitive, “natural” structure. Even Visual Basic 6,which implements a few object-oriented features, cannot keep up with true OOP.

The appendix starts by discussing the nature of an object before moving on to examine the conceptof inheritance. Inheritance, which is at the heart of OOP, enables you to conveniently reuse the codefor classes. You’ll learn how to use inheritance in your programs both from a conceptual and C#point of view.

575341 bc01.qxd 10/3/05 9:07 AM Page 1319

Page 2: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

A Note for Visual Basic 6 ProgrammersIf you are a skilled Visual Basic 6 developer but do not have C++ or Java experience, many of the con-cepts in this chapter will strike you as “foreign.” Visual Basic does allow you to code something that isoften referred to as an object: the Visual Basic class module. Some texts even refer to this as involvingOOP, although this bears little resemblance to the original concepts of OOP. It is more accurate to saythat Visual Basic implements a few of the more basic features of OOP. A VB class module is essentially aCOM component but wrapped in a way that hides much of what it does. In particular, it does not sup-port inheritance of its methods in the same way that inheritance is used in C# and conventional OOP.

Because of its support for a different kind of inheritance (implementation inheritance), C# classes aremuch more powerful than VB class modules and are often used very differently. If you want to writegood C# .NET applications and assemblies, you must read this appendix. Objects and inheritance are notjust new language features. In a well-designed object-oriented program, the whole architecture of theprogram is often arranged around inheritance. Once you’re comfortable with the concept of OOP, you’llbe structuring your programs in a completely different way from what you have done in Visual Basic —and your programs will be easier for others to maintain as a result. However, if you already feel comfort-able with manipulating objects in Visual Basic but have not yet used inheritance, you might want to skipahead to the section on inheritance.

Note whenever Visual Basic is referred to in this appendix, it is more specifically referring to VisualBasic 6.

What Is an Object?In everyday life, an object is anything that is identifiably a single material item. An object can be a car, ahouse, a book, a document, or a paycheck. For the purposes here, you can extend that concept a bit andthink of an object as anything that is a single item that you might want to represent in a program. Thisdiscussion therefore also includes living “objects,” such as a person, an employee, or a customer, as wellas more abstract “objects,” such as a company, a database, or a country.

Thinking about objects in this way not only enables you to write code that models the real world; it alsoenables you to break up a large program into smaller, more manageable units. The idea really comesfrom the concept of a black box that you might have encountered in school science.

The idea of a black box is that there are a lot of objects in life that you are able to use but of which youdon’t understand the mechanism. Take, for example, a car radio. Most people don’t know exactly howa car radio works; however, they do know what it does and how to operate it. Furthermore, they cantake out the radio, plug in a different one, and it’ll do basically the same thing, even though the internalworkings of it might be completely different. Black boxes formalize the idea that there’s a differencebetween what something does and how it works, and that two objects can do the same thing but workdifferently on the inside.

Replacing one object with another does have some subtle effects. Car radios might have different knobsand switches, and they might project different sound qualities, but the basic function is unchanged.Another important point is that the basic user interface is unchanged — you plug one car stereo into theslot in much the same way as you would another.

1320

Appendix A

575341 bc01.qxd 10/3/05 9:07 AM Page 1320

Page 3: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

If you understand all that, you basically understand OOP, because OOP is about applying these same con-cepts to computer programming. If, in other areas of your life, you use objects that have a well-designedinterface that you are familiar with, and you know how to use them, but don’t care how they work, whynot do the same thing in your programs? In other words, break each program into lots of units and designeach unit to perform a clearly specified role within the program. That’s what an object is.

If you start thinking about your programs this way, you gain quite a few advantages. You’ll find itbecomes easier to design the programs. The architecture of the programs becomes more intuitive andeasier to understand because it more closely reflects whatever it is that the program is abstracting fromreal life. It becomes easier for multiple developers to work together, because they can work on differentobjects in the code; all they need to know is what an object can do and how to interface with it. Theydon’t have to worry about the details of how the underlying code works.

Objects in ProgrammingNow that you understand what an object is conceptually (and in everyday life), you can learn morespecifically how to apply these concepts to programming.

If you’ve programmed on Windows before, chances are you’re already familiar with objects. For exam-ple, think about the various controls that you can place in Windows, including text boxes, list boxes, buttons, and so on. Microsoft has written these controls for you so that you don’t need to know, forexample, how a text box works internally. You just know that it does certain things. For example, youcan set its Text property to display text onscreen, or you can set its Width property to have the text boxresize itself.

In programming, you need to distinguish between a class and an object. A class is the generic definitionof what an object is — a template. For example, a class could be “car radio” — the abstract idea of a carradio. The class specifies what properties an object must have to qualify as a car radio.

Class MembersSo far, you’ve seen that there are two sides to an object: what it does, which is usually publicly known,and how it works, which is usually hidden. In programming, the “what it does” is normally representedin the first instance by methods, which are blocks of functionality that you can use. A method is just C#parlance for a function. The “how it works” is represented both by methods and by any data (variables)that the object stores. In Java and C++, this data is described as member variables, whereas in VisualBasic this data would be represented by any module-level variables in the class module. In C# the termi-nology is fields. In general, a class is defined by its fields and methods.

The term member is used by itself to denote anything that is part of a class, be it a field, method, or any ofthe other items just mentioned that can be defined within a class.

Defining a ClassThe easiest way to understand how to code a class is by looking at an example. In the following sections,you develop a simple class called Authenticator. Assume you’re in the process of writing a largeapplication, which at some point requires users to log in and supply a password. Authenticator is the

1321

Principles of Object-Oriented Programming

575341 bc01.qxd 10/3/05 9:07 AM Page 1321

Page 4: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

name of the class that will handle this aspect of the program. You won’t worry about the rest of theapplication — you’ll just concentrate on writing this class. However, you will also write a small piece oftest harness code to verify that Authenticator works as intended.

Authenticator allows you to do two things: set a new password and check whether a password isvalid. The C# code you need to define the class looks like this:

public class Authenticator{

private string password = “”;

public bool IsPasswordCorrect(string tryPassword){

return (tryPassword == password) ? true : false;}

public bool ChangePassword(string oldPassword, string newPassword){

if (oldPassword == password){

password = newPassword;return true;

}else

return false;}

}

The keyword class in C# indicates that you are going to define a new class (type of object). The wordimmediately following class is the name you’re going to use for this class. Then the actual definition ofthe object — consisting of variables (fields) and methods — follows in braces. In this example, the defini-tion consists of one field, password, and two methods, IsPasswordCorrect() and ChangePassword().

Access ModifiersThe only field in Authenticator, password, stores the current password (initially an empty stringwhen an Authenticator object is created) and is marked by the keyword private. This means that itis not visible outside the class, only to code that is part of the Authenticator class itself. Marking afield or method as private effectively ensures that that field or method will be part of the internalworking of the class, as opposed to the external interface. The advantage of this is that if you decide tochange the internal working (perhaps you later decide not to store password as a string but to use someother more specialized data type), you can just make the change without breaking the code outside theAuthenticator class definition — nothing from outside of this class can access this field.

Any code that uses the Authenticator class can only access the methods that have been marked withthe keyword public— in this case the IsPasswordCorrect() and ChangePassword() methods. Bothof these methods have been implemented in such a way that nothing will be done (other than returningtrue or false) unless the calling code supplies the current correct password, as you’d expect for soft-ware that implements security. The implementations of these functions access the password field, butthat’s fine because this code forms part of the Authenticator class itself. Notice that these public func-tions simultaneously give you the interface to the external world (in other words, any other code that

1322

Appendix A

575341 bc01.qxd 10/3/05 9:07 AM Page 1322

Page 5: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

uses the Authenticator class) and define what the Authenticator class does, as viewed by the rest ofthe world.

private and public are not the only access modifiers available to define what code is allowed toknow about the existence of a member. Later, this appendix discusses protected, which makes themember available to this class and certain related classes. C# also allows members to be declared asinternal and protected internal, which restrict access to other code within the same assembly.

Instantiating and Using ObjectsThe easiest way to understand how to use a class in your code is to think of the class as a new type ofvariable. You’re used to the predefined variable types — such as int, float, double, and so on. Bydefining the Authenticator class, you’ve effectively told the compiler that there’s a new type of vari-able called an Authenticator. The class definition contains everything the compiler needs to know tobe able to process this variable type. Therefore, just as the compiler knows that a double contains afloating-point number stored in a certain format (which enables you to add doubles, for example),you’ve told the compiler that a variable of type Authenticator contains a string and allows you tocall the IsPasswordCorrect() and ChangePassword() methods.

Although a class is described here as a new type of variable, the more common terminology is data type,or simply type.

Creating a user-defined variable (an object) is known as instantiation, because you create an instance ofthe object. An instance is simply any particular occurrence of the object. So, if your Authenticatorobject is another kind of variable, you should be able to use it just like any other variable — and youcan, as demonstrated in the following example.

Create the MainEntryPoint class, as shown in the following code sample, and place it in theWrox.ProCSharp.OOProg namespace along with the Authenticator class you created earlier:

using System;

namespace Wrox.ProCSharp.OOProg{

class MainEntryPoint{

static void Main(){

Authenticator myAccess = new Authenticator();bool done;done = myAccess.ChangePassword(“”, “MyNewPassword”);if (done == true)

Console.WriteLine(“Password for myAccess changed”);else

Console.WriteLine(“Failed to change password for myAccess”);

done = myAccess.ChangePassword(“”, “AnotherPassword”);if (done == true)

Console.WriteLine(“Password for myAccess changed”);else

Console.WriteLine(“Failed to change password for myAccess”);

1323

Principles of Object-Oriented Programming

575341 bc01.qxd 10/3/05 9:07 AM Page 1323

Page 6: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

if (myAccess.IsPasswordCorrect(“WhatPassword”))Console.WriteLine(“Verified myAccess\’ password”);

elseConsole.WriteLine(“Failed to verify myAccess\’ password”);

}}

public class Authenticator{

// implementation as shown earlier}

}

The MainEntryPoint class is like Authenticator— it can have its own members (that is, its ownfields, methods, and so on). However, you’ve chosen to use this class solely as a container for the pro-gram entry point, the Main() method. Doing it this way means that the Authenticator class can sit asa class in its own right that can be used in other programs (either by cutting and pasting the code or bycompiling it separately into an assembly). MainEntryPoint only really exists as a class because of thesyntactical requirement of C# that even the program’s main entry point has to be defined within a class,rather than being defined as an independent function.

Because all the action is happening in the Main() method, let’s take a closer look at it. The first line ofinterest is:

Authenticator myAccess = new Authenticator();

Here you are declaring and instantiating a new Authenticator object instance. Don’t worry about =new Authenticator() for now — it’s part of C# syntax and is there because in C#, classes are alwaysaccessed by reference. You could actually use the following line if you just wanted to declare a newAuthenticator object called myAccess:

Authenticator myAccess;

This declaration can hold a reference to an Authenticator object, without actually creating any object(in much the same way that the line Dim obj As Object in Visual Basic doesn’t actually create anyobject). The new operator in C# is what actually instantiates an Authenticator object.

Calling class methods is done using the period symbol (.) appended to the name of the variable:

done = myAccess.ChangePassword(“”, “MyNewPassword”);

Here you have called the ChangePassword() method on the myAccess instance and fed the returnvalue into the done Boolean variable. You can retrieve class fields in a similar way. Note, however, thatyou cannot do this:

string myAccessPassword = myAccess.password;

This code will actually cause a compilation error, because the password field was marked as private,so other code outside the Authenticator class cannot access it. If you changed the password field to bepublic, the previous line would compile and feed the value of password into the string variable.

1324

Appendix A

575341 bc01.qxd 10/3/05 9:07 AM Page 1324

Page 7: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

You should note that if you are accessing member methods or fields from inside the same class, you cansimply give the name of the member directly.

Now that you understand how to instantiate objects, call class methods, and retrieve public fields, thelogic in the Main() method should be pretty clear. If you save this code as Authenticator.cs and thencompile and run it, you will get this:

AuthenticatorPassword for myAccess changedFailed to change password for myAccessFailed to verify myAccess’ password

There are a couple of points to note from the code. First, you’ll notice that so far you’re not doing any-thing new compared to what you would do when coding a Visual Basic class module, nor do you doanything that differs from the basic C# syntax covered in the first part of this book. The purpose here isto make sure that you are clear about the concepts behind classes.

Second, the previous example uses the Authenticator class directly in other code within the samesource file. You’ll often want to write classes that are used by other projects that you or others work on.To do this, you write the class in exactly the same way, but compile the code for the class into a library, asexplained in Chapter 15, “Assemblies.”

Using Static MembersYou may have noticed in the example that the Main() method was declared as static. This section dis-cusses what effect this static keyword has.

Creating static fieldsIt’s important to understand that by default each instance of a class (each object) has its own set of all thefields you’ve defined in the class. For example, in the following snippet the instances karli and julianeach contain their own string called password:

Authenticator julian = new Authenticator();Authenticator karli = new Authenticator();karli.ChangePassword(“OldKarliPassword”, “NewKarliPassword”);julian.ChangePassword(“OldJulianPassword”, “NewJulianPassword”);

Changing the password in karli has no effect on the password in julian, and vice versa (unless thetwo references happen to be pointing to the same address in memory, which is discussed later). This sit-uation resembles Figure A-1.

Figure A-1

karli

password

julian

password

1325

Principles of Object-Oriented Programming

575341 bc01.qxd 10/3/05 9:07 AM Page 1325

Page 8: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

In some cases this might not be the behavior you want. For example, suppose you want to define a mini-mum length for all passwords (and therefore for all of the password fields in all instances) in yourAuthenticator class. You do not want each password to have its own minimum length. Therefore, youreally want the minimum length to be stored only once in memory, no matter how many instances ofAuthenticator you create.

To indicate that a field should only be stored once, no matter how many instances of the class you create,you place the keyword static in front of the field declaration in your code:

public class Authenticator{

private static uint minPasswordLength = 6;private string password = “”;

Storing a copy of minPasswordLength with each Authenticator instance not only wastes memory butalso causes problems if you want to be able to change its value! By declaring the field as static, youensure that it is only stored once, and this field is shared among all instances of the class. Note that inthis code snippet you also set an initial value. You also use an unsigned integer (uint) as opposed toa standard integer (int) because you don’t want to ever use a negative value of this variable. Fieldsdeclared with the static keyword are referred to as static fields or static data, whereas fields that are notdeclared as static are referred to as instance fields or instance data. Another way of looking at this is that aninstance field belongs to an object, whereas a static field belongs to the class.

If a field has been declared as static, it exists when your program is running from the moment that theparticular module or assembly containing the definition of the class is loaded — that is, as soon as yourcode tries to use something from that assembly, so you can always guarantee a static variable is therewhen you want to refer to it. This is independent of whether you actually create any instances of thatclass. By contrast, instance fields exist only when variables of that class are currently in scope — one setof instance fields for each variable.

In some ways, static fields perform the same functions as global variables performed for older languagessuch as C and FORTRAN.

You should note that the static keyword is independent of the accessibility of the member to which itapplies. A class member can be public static or private static.

Creating static methodsAs explained in the Authenticator example, by default a method such as ChangePassword() is calledagainst a particular instance, as indicated by the name of the variable in front of the period (.) operator.That method then implicitly has access to all the members (fields, methods, and so on) of that particularinstance.

However, just as with fields, it is possible to declare methods as static, provided that they do notattempt to access any instance data or other instance methods. For example, you might want to providea method to allow users to view the minimum password length:

VB developers shouldn’t confuse static fields with static variables in Visual Basic,which are variables whose values remain between invocations of a method.

1326

Appendix A

575341 bc01.qxd 10/3/05 9:07 AM Page 1326

Page 9: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

public class Authenticator{

private static uint minPasswordLength = 6;public static uint GetMinPasswordLength(){

return minPasswordLength;}

...

You can download the code for Authenticator with this modification from the Wrox Press Web site(www.wrox.com) as the Authenticator2 sample.

In the earlier Authenticator example, the Main() method of the MainEntryPoint class is declared asstatic. This allows it to be invoked as the entry point to the program, despite the fact that no instanceof the MainEntryPoint class was ever created.

Accessing static membersThe fact that static methods and fields are associated with a class rather than an object is reflected in howyou access them. Instead of specifying the name of a variable before the . operator, you specify the nameof the class, like this:

Console.WriteLine(Authenticator.GetMinPasswordLength());

Also notice that in this code you access the Console.WriteLine() method by specifying the name ofthe class, Console. That is because WriteLine() is a static method too — you don’t need to instantiate aConsole object to use WriteLine().

How instance and static methods are implemented in memoryAs mentioned earlier, each object stores its own copy of a class’s instance fields. This is, however, not thecase for methods. If each object had its own copy of the code for a method, it would waste a lot of mem-ory, because the code for the methods remains the same across all object instances. Therefore, instancemethods, just like static methods, are stored only once, and associated with the class as a whole. Lateron, you’ll see other types of class members (constructors, properties, and so on) that contain code ratherthan data and follow the same logic.

Figure A-2 shows how instance and static methods are implemented in memory.

Figure A-2

Authenticator Class

Static FieldsminPasswordLength

All MethodsChangePassword()IsPasswordCorrect()

karli instance

Instance Fieldspassword

julian instance

Instance Fieldspassword

1327

Principles of Object-Oriented Programming

575341 bc01.qxd 10/3/05 9:07 AM Page 1327

Page 10: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

If instance methods are stored only once, how is a method able to access the correct copy of each field?In other words, how can the compiler generate code that accesses Karli’s password with the first methodcall and Julian’s with the second in the following example?

karli.ChangePassword(“OldKarliPassword”, “NewKarliPassword”);julian.ChangePassword(“OldJulianPassword”, “NewJulianPassword”);

The answer is that instance methods actually take an extra implicit parameter, which is a reference towhere in memory the relevant class instance is stored. You can almost think of this code example as theuser-friendly version that you have to write, because that’s how C# syntax works. However, what’s actu-ally happening in your compiled code is this:

ChangePassword(karli, “OldKarliPassword”, “NewKarliPassword”);ChangePassword(julian, “OldJulianPassword”, “NewJulianPassword”);

Declaring a method as static makes calling it slightly more efficient, because it will not be passedthis extra parameter. On the other hand, if a method is declared as static but attempts to access anyinstance data, the compiler will raise an error for the obvious reason that you can’t access instance dataunless you have the address of a class instance!

This means that in the Authenticator example you could not declare ChangePassword() orIsPasswordCorrect() as static because both of these methods access the password field, which isnot static.

Interestingly, although the hidden parameter that comes with instance methods is never declared explic-itly, you do actually have access to it in your code. You can get to it using the keyword this. You canrewrite the code for the ChangePassword() method as follows:

public bool ChangePassword(string oldPassword, string newPassword){

if (oldPassword == this.password){

this.password = newPassword;return true;

}else

return false;}

Generally, you wouldn’t write your code like this unless you have to distinguish between variablenames. All you’ve achieved here is to make the method longer and slightly harder to read.

A Note About Reference TypesBefore leaving the discussion of classes, you should be aware of one potential gotcha that can occur inC# because C# regards all classes as reference types. This can have some unexpected effects when itcomes to comparing instances of classes for equality and setting instances of classes equal to each other.For example, look at this code:

1328

Appendix A

575341 bc01.qxd 10/3/05 9:07 AM Page 1328

Page 11: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

Authenticator User1; Authenticator User2 = new Authenticator();Authenticator User3 = new Authenticator();User1 = User2;User2.ChangePassword (“”, “Tardis”);; // This sets password for User1 as well!User3.ChangePassword (“”, “Tardis”);;if (User2 == User3){

// contents of this if block will NOT be executed even though// objects referred to by User2 and User3 are contain identical values,// because the variables refer to different objects

}if (User2 == User1){

// any code here will be executed because User1 and User2 refer// to the same memory

}

In this code, you declare three variables of type Authenticator: User1, User2, and User3. However,you instantiate only two objects of the Authenticator class, because you use only the new operatortwice. Then you set the variable User1 equal to User2. Unlike with a value type, this does not copyany of the contents of User2. Rather, it means that User1 is set to refer to the same memory as User2 isreferring to. What that means is that any changes you make to User2 also affect User1, because they arenot separate objects; both variables refer to the same data. You can also say that they point to the samedata, and the actual data referred to is sometimes described as the referent. So when you set the pass-word of User2 to Tardis, you are implicitly also setting the password of User1 to Tardis. This is verydifferent from how value types behave.

The situation gets even less intuitive when you try to compare User2 and User3 in the next statement:

if (User2 == User3)

You might expect that this condition returns true because User2 and User3 have both been set to thesame password, so both instances contain identical data. The comparison operator for reference types,however, doesn’t compare the contents of the data by default — it simply tests to see whether the tworeferences are referring to the same address in memory. Because they are not, this test returns false,which means anything inside this if block will not be executed. By contrast, comparing User2 withUser1 returns true because these variables do point to the same address in memory.

Note that this behavior does not apply to strings, because the == operator has been overloaded forstrings. Comparing two strings with == always compares string content. (Any other behavior forstrings would be extremely confusing!)

Overloading MethodsTo overload a method is to create several methods each with the same name, but each with a different sig-nature. The reason you might want to use overloading is best explained with an example. Consider howin C# you write data to the command line, using the Console.WriteLine() method. For example, ifyou want to display the value of an integer, you can write this:

1329

Principles of Object-Oriented Programming

575341 bc01.qxd 10/3/05 9:07 AM Page 1329

Page 12: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

int x = 10;Console.WriteLine(x);

To display a string you can write:

string message = “Hello”;Console.WriteLine(message);

Even though you are passing different data types to the same method, both of these examples compile.This is because there are actually lots of Console.WriteLine() methods, but each has a different signature — one of them takes int as a parameter, while another one takes string, and so on. There iseven a two-parameter overload of the method that allows for formatted output and lets you write codelike this:

string Message = “Hello”;Console.WriteLine(“The message is {0}”, Message);

Obviously, Microsoft provides all of these Console.WriteLine() methods because it realizes that thereare many different data types of which you might want to display the value.

Method overloading is very useful, but there are some pitfalls to be aware of when using it. Supposeyou write:

short y = 10;Console.WriteLine(y);

A quick look at the documentation reveals that no overload of WriteLine() takes short. So what willthe compiler do? In principle, it could generate code that converts short to int and call the int versionof Console.WriteLine(). Or it could convert short to long and call Console.WriteLine(long). Itcould even convert short to string.

In this situation, each language will have a set of rules for what conversion will be the one that is actu-ally performed (for C#, the conversion to int is the preferred one). However, you can see the potentialfor confusion. For this reason, if you define method overloads, you need to take care to do so in a waythat won’t cause any unpredictable results.

When to Use OverloadingGenerally, you should consider overloading a method when you need a number of methods that takedifferent parameters, but conceptually do the same thing, as with Console.WriteLine() in the preced-ing section. The situations in which you will normally use overloading are explained in the followingsubsections.

Optional parametersOne common use of method overloads is to allow certain parameters to a method to be optional andto have default values if the client code does not specify their values explicitly. For example, considerthis code:

1330

Appendix A

575341 bc01.qxd 10/3/05 9:07 AM Page 1330

Page 13: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

public void DoSomething(int x, int y){

// do whatever}

public void DoSomething(int x){

DoSomething(x, 10);}

These overloads allow client code to call DoSomething(), supplying one required parameter and oneoptional parameter. If the optional parameter isn’t supplied, you effectively assume the second int is10. Most modern compilers will also inline method calls in this situation so there is no performance loss.This is certainly true of the .NET JIT compiler.

Some languages, including Visual Basic and C++, allow default parameters to be specified explicitly infunction declarations, with a syntax that looks like public void DoSomething(int X, int Y=10). C# doesnot allow this; in C# you have to simulate default parameters by providing multiple overloads of meth-ods as shown in the previous example.

Different input typesYou have already seen this very common reason for defining overloads in the Console.WriteLine()example.

Different output typesThis situation is far less common; however, occasionally you might have a method that calculates orobtains some quantity, and depending on the circumstances, you might want this to be returned in morethan one way. For example, in an airline company, you might have a class that represents aircraft timeta-bles, and you might want to define a method that tells you where an aircraft should be at a particulartime. Depending on the situation, you might want the method to return either a string description of theposition (“over Atlantic Ocean en route to London”) or the latitude and longitude of the position.

You cannot distinguish overloads using the return type of a method. However, you can do so using outparameters. So you could define these:

void GetAircraftLocation(DateTime Time, out string Location){...}

void GetAircraftLocation(DateTime Time, out float Latitude, out float Longitude) {...}

Note, however, that in most cases using overloads to obtain different out parameters does not lead to anarchitecturally neat design. In the preceding example, a better design would perhaps involve defining aLocation struct that contains the location string as well as the latitude and longitude and returning thisfrom the method call, hence avoiding the need for overloads.

1331

Principles of Object-Oriented Programming

575341 bc01.qxd 10/3/05 9:07 AM Page 1331

Page 14: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

PropertiesAs mentioned earlier, a class is defined by its fields and methods. However, classes can also containother types of class members, including constructors, indexers, properties, delegates, and events. For themost part these other items are used only in more advanced situations and are not essential to under-standing the principles of object-oriented design. For that reason, this appendix discusses only proper-ties, which are extremely common and can significantly simplify the external user interface exposed byclasses. The other class members are introduced in Part I. Properties are in extremely common use, how-ever, and can significantly simplify the external user interface exposed by classes. For this reason, theyare discussed here.

Visual Basic programmers will find that C# properties correspond almost exactly to properties in VBclass modules and are used in just the same way.

Properties exist for the situation in which you want to make a method call look like a field. You can seewhat a property is by looking at the minPasswordLength field in the Authenticator class. In this section, you extend the class so that users can read and modify this field without having to use aGetMinPasswordLength() method like the one introduced earlier.

A property is a method or pair of methods exposed to the outside world as if they are fields. To create aproperty for the minimum password length, modify the code for the Authenticator class as follows:

public static uint MinPasswordLength {

get{

return minPasswordLength;}set{

minPasswordLength = value;}

}

As you can see from this, you define a property in much the same way as a field, except that after thename of the property, you have a code block enclosed by curly braces. In the code block there may betwo methods called get and set. These are known as the get accessor and the set accessor. Note thatalthough no parameter is explicitly mentioned in the definition of the set accessor, there is an implicitparameter passed in, and referred to by the name value. Also, the get accessor always returns the samedata type as the property was declared as (in this case uint).

Now, to retrieve the value of minPasswordLength, you use this syntax:

uint i = Authenticator.MinPasswordLength;

What will actually happen here is that MinPasswordLength property’s get accessor is called. In thiscase, this method is implemented to simply return the value of the minPasswordLength field.

To set the MinPasswordLength field using the property, use the following code:

Authenticator.MinPasswordLength = 7;

1332

Appendix A

575341 bc01.qxd 10/3/05 9:07 AM Page 1332

Page 15: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

This code causes the MinPasswordLength’s set accessor to be called, which is implemented to assignthe required value (7) to the minPasswordLength field. As mentioned earlier, the set accessor has animplicit parameter, called value.

Note that in this particular example, the property in question happens to be static. In general that is notnecessary. Just as for methods, you will normally declare properties as static only if they refer to static data.

Data EncapsulationYou may wonder what the point of all the preceding code is. Wouldn’t it have been easier to make theminPasswordLength field public, so that you could access it directly and not have to bother about anyproperties? The answer is that fields represent the internal data of an object, so they are an integral partof the functionality of an object. Now, in OOP, you aim to make it so that users of objects only need toknow what an object does, not how it does it. So making fields directly accessible to users defeats theideology behind OOP.

Ideology is all very well, but there must be practical reasons behind it. One reason is this: If you makefields directly visible to external users, you lose control over what they do to the fields. They might mod-ify the fields in such a way as to break the intended functionality of the object (give the fields inappro-priate values, for example). However, if you use properties to control access to a field, this is not aproblem; you can add functionality to the property that checks for inappropriate values. Related to this,you can also provide read-only properties by omitting the set accessor completely. The principle of hid-ing fields from client code in this way is known as data encapsulation.

You should only use properties to do something that appears only to set or retrieve a value; in all otherinstances use methods. That means that the set accessor must only take one parameter and return avoid, whereas the get accessor cannot take any parameters. For example, it would not be possible torewrite the IsPasswordValid() method in the Authenticator class as a property. The parametertypes and return value for this method are not of the correct type.

Introducing InheritanceOne characteristic of objects in everyday life is that they tend to come in families of related things thatshare aspects of their design. A sofa is just like an armchair, except that it can seat more than one person.A CD-ROM does the same sort of thing as a cassette tape, but with extra direct-access facilities. Likewise,many cars differ in body style and size, but internally their engines and other components are built inmuch the same way, often using the same components.

This is an example of implementation inheritance, and the equivalent in OOP would be some classes(EscortCar, OrionCar, and FiestaCar, perhaps?), which not only expose methods with the samenames, but actually the same methods, in the sense that when you call the methods you are running thesame code.

Now extend this example. Say that I swapped my Escort for another Escort that has a diesel engine. Bothcars have exactly the same shell (the user interface is the same), but under the hood, the engines are dif-ferent. That’s an example of interface inheritance, and the equivalent in computer programming would betwo classes (EscortCar and EscortDieselCar) that happen to expose methods that have the samenames, purposes, and signatures, but different implementations.

1333

Principles of Object-Oriented Programming

575341 bc01.qxd 10/3/05 9:07 AM Page 1333

Page 16: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

Java or C++ developers who are familiar with COM will recognize that implementation inheritance isthe kind of inheritance that is supported by Java/C++ and other traditional object-oriented languages,while the more restricted interface inheritance was the only form of inheritance that was supported byCOM and COM objects. Visual Basic supports only interface inheritance through the Implementskeyword. The great thing about C# is it supports both types of inheritance.

As far as C# programming is concerned, you’re looking at the issue of how to define a new class, whilereusing features from an existing class. The benefits are twofold: First, inheritance provides a convenientway to reuse existing, fully tested code in different contexts, thereby saving a lot of coding time; second,inheritance can provide even more structure to your programs by giving a finer degree of granularity toyour classes.

At this point you’re going to move on to a new coding example based on a cell phone company that willdemonstrate how implementation inheritance works in a C# program. Inheritance of classes in C# isalways implementation inheritance.

Using Inheritance in C#The example used to demonstrate inheritance is going to be of a fictitious cell phone company, which iscalled Mortimer Phones. You’re going to develop a class that represents a customer account and is respon-sible for calculating that customer’s phone bill. This is a much longer, more complex example than theAuthenticator class, and as it develops you’ll quickly find that one simple class is not adequate;instead, you will need a number of related classes, and inheritance is the solution to your challenge.

You’re going to write a class that works out the monthly bill for each customer of Mortimer Phones. Theclass is called Customer, and each instance of this class represents one customer’s account. In terms ofpublic interface, the class contains two properties:

❑ Name represents the customer’s name (read-write).

❑ Balance represents the amount owed (read-only).

The class also has two methods:

❑ RecordPayment(), which is called to indicate that the customer has paid a certain amount oftheir bill.

❑ RecordCall(), which is called when the customer has made a phone call. It works out the costof the call and adds it to that customer’s balance.

The RecordCall() method is potentially quite a complex function when applied to the real world,because it would involve figuring out the type of call from the number called, then applying the appro-priate rate, and keeping a history of the calls. To keep things simple, assume there are just two types ofcalls: calls to landlines and calls to other cell phones, and that each of these are charged at a flat rate of 2cents a minute for landlines and 30 cents a minute for other cell phones. The RecordCall method willsimply be passed the type of call as a parameter, and you won’t worry about keeping a call history.

With this simplification, you can look at the code for the project. The project is a console application, andthe first thing in it is an enumeration for the types of call:

1334

Appendix A

575341 bc01.qxd 10/3/05 9:07 AM Page 1334

Page 17: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

namespace Wrox.ProCSharp.OOProg{

using System;

public enum TypeOfCall {

CallToCellPhone, CallToLandline

}

Now, look at the definition of the Customer class:

public class Customer{

private string name;private decimal balance;

public string Name{

get{

return name;}set{

name = value;}

}

public decimal Balance{

get{

return balance;}

}

public void RecordPayment(decimal amountPaid){

balance -= amountPaid;}

public void RecordCall(TypeOfCall callType, uint nMinutes){

switch (callType){

case TypeOfCall.CallToLandline:balance += (0.02M * nMinutes);break;

case TypeOfCall.CallToCellPhone:balance += (0.30M * nMinutes);break;

default:

1335

Principles of Object-Oriented Programming

575341 bc01.qxd 10/3/05 9:07 AM Page 1335

Page 18: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

break;}

}}

This code should be reasonably self-explanatory. Note that you hardcode the call charges of 2 cents perminute (landline) and 30 cents per minute (pay-as-you-go charges for a cell phone) into the program. Inreal life, they’d more likely to be read in from a relational database or some file that allows the values tobe changed easily.

Now add some code in the program’s Main() method that displays the amounts of bills currently owed:

public class MainEntryPoint{

public static void Main(){

Customer arabel = new Customer();arabel.Name = “Arabel Jones”;Customer mrJones = new Customer();mrJones.Name = “Ben Jones”;arabel.RecordCall(TypeOfCall.CallToLandline, 20);arabel.RecordCall(TypeOfCall.CallToCellPhone, 5);mrJones.RecordCall(TypeOfCall.CallToLandline, 10);Console.WriteLine(“{0,-20} owes ${1:F2}”, arabel.Name, arabel.Balance);Console.WriteLine(“{0,-20} owes ${1:F2}”, mrJones.Name, mrJones.Balance);

}}

}

Running this code gives the following results:

MortimerPhonesArabel Jones owes $1.90Ben Jones owes $0.20

Adding InheritanceCurrently, the Mortimer Phones example is heavily simplified. In particular, it has only one call plan forall customers, which is not even remotely realistic. Many people are registered under a call plan forwhich they pay a fixed rate each month, but many other plans exist.

The way you’re working at the moment, if you try to take all of the different call plans into account, yourRecordCall() method is going to end up containing various nested switch statements and look some-thing like this (assuming the CallPlan field is an enumeration):

public void RecordCall(TypeOfCall callType, uint nMinutes){

switch (callplan) {case CallPlan.CallPlan1:

switch (callType){

1336

Appendix A

575341 bc01.qxd 10/3/05 9:07 AM Page 1336

Page 19: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

case TypeOfCall.CallToLandline:// work out amount

case TypeOfCall.CallToCellPhone:// work out amount// other cases// etc.

}break;

case CallPlan.CallPlan2: switch (callType){

// etc.

}break;

}

}

That is not a satisfactory solution. Small switch statements are nice, but huge switch statements withlarge numbers of options — and in particular embedded switch statements — make for code that is dif-ficult to follow. It also means that whenever a new call plan is introduced the code for the method willhave to be changed. This could accidentally introduce new bugs into the parts of the code responsiblefor processing existing call plans.

The problem has really to do with the way the code for the different call plans is mixed up in a switchstatement. If you could cleanly separate the code for the different call plans, the problem would besolved. This is one of the issues that inheritance addresses.

You want to separate the code for different types of customers. You’ll start by defining a new class thatrepresents customers on a new call plan. You’ll name this call plan Nevermore60. Nevermore60 isdesigned for customers who use their cell phones a lot. Customers on this call plan pay a higher rate of50 cents a minute for the first 60 minutes of calls to other cell phones, then a reduced rate of 20 cents aminute for all additional calls, so if they make a large enough number of calls they save money com-pared to the previous call plan.

You’ll save actually implementing the new payment calculations for a little while longer, and you’ll ini-tially define Nevermore60Customer like this:

public class Nevermore60Customer : Customer{

}

In other words, the class has no methods, no properties, nothing of its own. On the other hand, it’sdefined in a slightly different way from how you’ve defined any classes before. After the class name is acolon, followed by the name of your earlier class, Customer. This tells the compiler thatNevermore60Customer is derived from Customer. That means that every member in Customer also

1337

Principles of Object-Oriented Programming

575341 bc01.qxd 10/3/05 9:07 AM Page 1337

Page 20: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

exists in Nevermore60Customer. Alternatively, to use the correct terminology, each member ofCustomer is inherited in Nevermore60Customer. Also, Nevermore60Customer is said to be a derivedclass, whereas Customer is said to be the base class. You’ll also sometimes encounter derived classesreferred to as subclasses and base classes as super-classes or parent classes.

Because you’ve not yet put anything else in the Nevermore60Customer class, it is effectively an exactcopy of the definition of the Customer class. You can create instances of and call methods against theNevermore60Customer class, just as you could with Customer. To see this, modify one of the cus-tomers, Arabel, to be a Nevermore60Customer:

public static void Main(){

Nevermore60Customer arabel = new Nevermore60Customer();...

}

In this code, you’ve changed just one line, the declaration of Arabel, to make this customer aNevermore60Customer instance. All the method calls remain the same, and this code producesexactly the same results as your earlier code. If you want to try this out, it’s the MortimerPhones2code sample (which is part of the sample download file, available at www.wrox.com).

By itself, having a copy of the definition of the Customer class might not look very useful. The power ofthis comes from the fact you can now make some modifications or additions to Nevermore60Customer.You can instruct the compiler, “Nevermore60Customer is almost the same as Customer, but with thesedifferences.” In particular, you’re going to modify the way that Nevermore60Customer works out thecharge for each phone call according to the new price plan.

The differences you can specify in principle are:

❑ You can add new members (of any type: fields, methods, properties, and so on) to the derivedclass, where these members are not defined in the base class.

❑ You can replace the implementation of existing members, such as methods or properties that arealready present in the base class.

For this example, you will replace, or override, the RecordCall() method in Customer with a newimplementation of the RecordCall() method in Nevermore60Customer. Not only that, but wheneveryou need to add a new call plan, you can simply create another new class derived from Customer, witha new override of RecordCall(). In this way, you can add code to cope with many different call plans,while keeping the new code separate from all the existing code that is responsible for calculations usingexisting call plans.

Don’t confuse method overriding with method overloading. The similarity in these names is unfortu-nate because they are completely different, unrelated, concepts. Method overloading has nothing to dowith inheritance or virtual methods.

So modify the code for the Nevermore60Customer class, so that it implements the new call plan. To dothis you need not only to override the RecordCall() method, but also to add a new field that indicatesthe number of high-cost minutes that have been used:

1338

Appendix A

575341 bc01.qxd 10/3/05 9:07 AM Page 1338

Page 21: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

public class Nevermore60Customer : Customer{

private uint highCostMinutesUsed;public override void RecordCall(TypeOfCall callType, uint nMinutes){

switch (callType){

case TypeOfCall.CallToLandline:balance += (0.02M * nMinutes);break;

case TypeOfCall.CallToCellPhone:uint highCostMinutes, lowCostMinutes;uint highCostMinutesToGo = (highCostMinutesUsed < 60) ? 60 - highCostMinutesUsed : 0;

if (nMinutes > highCostMinutesToGo){

highCostMinutes = highCostMinutesToGo;lowCostMinutes = nMinutes - highCostMinutes;

}else{

highCostMinutes = nMinutes;lowCostMinutes = 0;

}

highCostMinutesUsed += highCostMinutes;balance += (0.50M * highCostMinutes + 0.20M * lowCostMinutes);break;

default:break;

}}

}

You should note that the new field you’ve added, highCostMinutesUsed, is only stored in instances ofNevermore60Customer. It is not stored in instances of the base class, Customer. The base class itself isnever implicitly modified in any way by the existence of the derived class. This must always be the case;because when you code the base class, you don’t necessarily know what other derived classes might beadded in the future — and you wouldn’t want your code to be broken when someone adds a derived class!

As you can see, the algorithm to compute the call cost in this case is more complex, though if you followthrough the logic you will see it does meet the definition for the Nevermore60 call plan. Notice that theextra keyword override has been added to the definition of the RecordCall() method. This informsthe compiler that this method is actually an override of a method that is already present in the base class,and you must include this keyword.

Before this code will compile, you need to make a couple of modifications to the base class, Customer, too:

public class Customer{

private string name;

1339

Principles of Object-Oriented Programming

575341 bc01.qxd 10/3/05 9:07 AM Page 1339

Page 22: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

protected decimal balance;

// etc.

public virtual void RecordCall(TypeOfCall callType, uint nMinutes){

switch (callType)

The first change you’ve made is to the balance field. Previously it was defined with the private key-word, meaning that no code outside the Customer class could access it directly. Unfortunately thismeans that, even though Nevermore60Customer is derived from Customer, the code in theNevermore60Customer class cannot directly access this field (even though a balance field is still pre-sent inside every Nevermore60Customer object). That would prevent Nevermore60Customer frombeing able to modify the balance when it records calls made, and so prevent the code presented for theNevermore60Customer.RecordCall() method from compiling.

The access modifier keyword protected solves this problem. It indicates that any class derived fromCustomer, as well as Customer itself, should be allowed access to this member. The member is stillinvisible, however, to code in any other class that is not derived from Customer. Essentially, you’reassuming that, because of the close relationship between a class and its derived class, it’s fine for thederived class to know a bit about the internal workings of the base class, at least as far as protectedmembers are concerned.

There is actually a controversial point here about good programming style. Many developers wouldregard it as better practice to keep all fields private, and write a protected accessor method to allowderived classes to modify the balance. In this case, allowing the balance field to be protected rather thanprivate prevents the example from becoming more complex than it already is.

The second change you’ve made is to the declaration of the RecordCall() method in the base class.You’ve added the keyword virtual. This changes the manner in which the method is called when theprogram is run, in a way that facilitates overriding it. C# will not allow derived classes to override amethod unless that method has been declared as virtual in the base class. You will be looking at vir-tual methods and overriding later in this appendix.

Class Hierarchies and Class DesignIn a procedural language, and even to some extent in a language like Visual Basic, the emphasis is verymuch on breaking the program down into functions. Object orientation shifts the emphasis of programdesign away from thinking about what functionality the program has to considering instead whatobjects the program consists of.

Inheritance is also an extremely important feature of object-oriented programming, and a crucial stage inthe design of your program is deciding on class hierarchies — the relationship between your classes. Ingeneral, as with the Mortimer Phones example, you will find that you have a number of specializedobjects that are particular types of more generic objects.

When you’re designing classes it’s normally easiest to use a diagram known as a class hierarchy diagram,which illustrates the relationships between the various base and derived classes in your program.Traditionally, class hierarchy diagrams are drawn with the base class at the top and arrows pointing

1340

Appendix A

575341 bc01.qxd 10/3/05 9:07 AM Page 1340

Page 23: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

from derived classes to their immediate base classes. For example, the hierarchy of the Mortimer Phonesexample from MortimerPhones3 onward looks like what is shown in Figure A-3.

Figure A-3

This class hierarchy diagram emphasizes that inheritance can be direct or indirect. In the example,Nevermore60Customer is directly derived from Customer, but indirectly derived from Object.Although the examples in this discussion are tending to focus on direct derivation, all the principlesapply equally when a class indirectly derives from another class.

Another example is one of the hierarchies from the .NET base classes. In Chapter 23, “Windows Forms,”you see how to use the base classes that encapsulate windows (or to give them their more modern .NETterminology, forms). You may not have realized just how rich a hierarchy could be behind some of thecontrols that you can place on windows (see Figure A-4).

The Form class represents the generic window, and ScrollBar, StatusBar, Button, TextBox, andRichTextBox represent the familiar corresponding controls. The rich hierarchy behind these classesallows a fine-tuning of what implementations of which methods can be made common to a number ofdifferent classes. Many of these classes will also implement certain interfaces, by which they can maketheir nature as windows known to client code.

It’s also important to realize that class hierarchies are, like any other aspect of programming, an area inwhich there may be many possible solutions, each having its advantages and disadvantages. For theMortimer Phones example, there may be other ways to design classes. One argument against the chosenhierarchy is that customers often change their call plans — and do you really want to have to destroy acustomer object and instantiate a new one of a different class whenever that happens? Perhaps it wouldbe better to have just one customer class, which contains a reference to a call plan object, and have a classhierarchy of call plans?

A large application will not have just one hierarchy but will typically implement a large number of hier-archies that possibly stretches into hundreds of classes. That may sound daunting, but the alternative,before object-oriented programming came into being, was to have literally thousands of functions mak-ing up your program, with no way to group them into manageable units. Classes provide a very effec-tive way of breaking your program into smaller sections. This not only facilitates maintenance but alsomakes your program easier to understand because the classes represent the actual objects that your pro-gram is representing in a very intuitive way.

System.Object

Customer

NeverMore60Customer Classes representing customerson other call plans

1341

Principles of Object-Oriented Programming

575341 bc01.qxd 10/3/05 9:07 AM Page 1341

Page 24: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

Figure A-4

It’s also important with your classes to think carefully about the separation between the public interfacepresented to client code and the private internal implementation. In general, the more of a class you areable to keep private, the more modular your program will become, in the sense that you can make modi-fications or improvements to the internal implementation of one class and be certain that it will notbreak or even have any effect on any other part of the program. That’s the reason it’s emphasized thatmember fields in particular will almost invariably be private, unless they are either constant or theyform part of a struct whose main purpose is to group together a small number of fields. This appendixhasn’t always kept to that rule rigidly, but that’s largely to keep the samples as simple as possible.

The object classOne point that you might not realize from the code is that in the Mortimer Phones examples, Customeris itself derived from another class, System.Object. This is a rule that is enforced by .NET and C#: All.NET classes must ultimately derive from a base class called Object. In C# code, if you write a class anddo not specify a base class, the compiler will supply System.Object as the base class by default. Thismeans that all objects in the .NET Framework have certain methods inherited from the Object class,including the ToString() and GetType() methods that are discussed in the beginning of the book. Youlook at the Object class in more detail in Chapter 11, “Reflections.”

MarshallByRelObject

Control

RichControl

ScrollableControl

ContainedControl

Form TextBox RichTextBox

ScrollBar StatusBar ButtonBase

Button TextBoxBase

FormatControl

MarshallByRelComponent

1342

Appendix A

575341 bc01.qxd 10/3/05 9:07 AM Page 1342

Page 25: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

Single and multiple inheritanceIn C#, each derived class can only inherit from one base class (although you can create as many differentclasses that are derived from the same base class as you want). The terminology to describe this is singleinheritance. Some other languages, including C++, allow you to write classes that have more than onebase class, which is known as multiple inheritance.

Polymorphism and Virtual MembersLet’s go back to the Mortimer Phones example. Earlier, you encountered this line of code:

Nevermore60Customer arabel = new Nevermore60Customer();

In fact, you could have instantiated the Nevermore60Customer object like this as well:

Customer arabel = new Nevermore60Customer();

Because Nevermore60Customer is derived from Customer, it’s actually perfectly legitimate for a refer-ence to a Customer to be set up to point to either a Customer or a Nevermore60Customer, or to aninstance of any other class that is derived directly or indirectly from Customer. Notice that all you’vechanged here is the declaration of the reference variable. The actual object that gets instantiated with newis still a Nevermore60Customer object. If, for example, you try to call GetType() against it, it’ll tell youit’s a Nevermore60Customer.

Being able to point to derived classes with a base reference may look like just a syntactical convenience,but it’s actually essential if you want to be able to use derived classes easily — and it’s an essential fea-ture of any language that wants to support OOP. You can understand why if you think about how a realcell phone company will want to store the various Customer-derived classes. In the example, you haveonly two customers, so it is easy to define separate variables. In the real world, however, you have hun-dreds of thousands of customers, and you might want to do something like read them from a databaseinto an array, then process them using the array, using code that looks like this:

Customer[] customers = new Customer[NCustomers];

// do something to initialize customers

foreach (Customer nextCustomer in customers){

Console.WriteLine(“{0,-20} owes ${1:F2}”, nextCustomer.Name, nextCustomer.Balance);

}

If you use an array of Customer references, each element can point to any type of customer, no matterwhat Customer-derived class is used to represent that customer. However, if variables could not storereferences to derived types you’d have to have lots of arrays — an array of Customers, an array ofNevermore60Customers, and another array for each type of class.

You’ve now ensured that you can mix different types of classes in one array, but this will now give thecompiler a new problem. Suppose you have a snippet of code like this:

1343

Principles of Object-Oriented Programming

575341 bc01.qxd 10/3/05 9:07 AM Page 1343

Page 26: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

Customer aCustomer;

// Initialize aCustomer to a particular tariff

aCustomer.RecordCall(TypeOfCall.CallToLandline, 20);

What the compiler can see is a Customer reference, and you are to call the RecordCall() methodon it. The trouble is that aCustomer might refer to a Customer instance, or it might refer to aNevermore60Customer instance, or it might refer to an instance of some other class derived fromCustomer. Each of these classes might have its own implementation of RecordCall(). How will thecompiler determine which method should be called? There are two answers to this, depending onwhether the method in the base class is declared as virtual and the derived class method as an override:

❑ If the methods are not declared as virtual and override, respectively, the compiler will sim-ply use the type that the reference was declared to be. In this case, because aCustomer is of typeCustomer, it will arrange for the Customer.RecordCall() method to be called, no matterwhat aCustomer is actually referring to.

❑ If the methods are declared as virtual and override, respectively, the compiler will generate codethat checks what the aCustomer reference is actually pointing to at runtime. It then identifieswhich class this instance belongs to and calls the appropriate RecordCall() override. Thisdetermination of which overload should be called will need to be made separately each time thestatement is executed. For example, if the virtual method call occurs inside a foreach loopthat executes 100 times, on each iteration through the loop the reference might be pointing to adifferent instance and therefore to a different class of object.

In most cases, the second behavior is the one you want. If you have a reference, for example, to aNevermore60Customer, it’s highly unlikely that you’d want to call any override of any method otherthan the one that applies to Nevermore60Customer instances. In fact, you might wonder why you’dever want the compiler to use the first, non-virtual, approach, because it looks like that means in manycases the “wrong” override will be called up. Why don’t you just make virtual methods the normalbehavior, and say that every method is automatically virtual? This is, incidentally, the approach takenby Java, which automatically makes all methods virtual. There are three good reasons, however, fornot doing this in C#:

❑ Performance. When a virtual method is called, a runtime determination has to be made to iden-tify which override has to be called. For a non-virtual function, this information is available atcompile time. (The compiler can identify the relevant override from the type that the reference isdeclared as!) This means that for a non-virtual function, the compiler can perform optimizationssuch as inlining code to improve performance. Inlining virtual methods is not possible, whichwill hurt performance. Another (minor) factor is that the determination of the method itselfgives a very small performance penalty. This penalty amounts to no more than an extra addresslookup in a table of virtual function addresses (called a vtable) and so is insignificant in mostcases but may be important in very tight and frequently executed loops.

❑ Design. It may be the case that when you design a class, there are some methods that shouldnot be overridden. This actually happens a lot, especially with methods that should only beused internally within the class by other methods or whose implementations reflect the internalclass design. When you design a class, you choose which features of its implementation you

1344

Appendix A

575341 bc01.qxd 10/3/05 9:07 AM Page 1344

Page 27: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

make public, protected, or private. It’s unlikely that you’ll want methods that are primarily con-cerned with the internal operation of the class to be overrideable, so you typically won’t declarethese methods as virtual.

❑ Versioning. Virtual methods can cause a particular problem connected with releasing new ver-sions of base classes.

The ability of a variable to be used to reference objects of different types, and to automatically call theappropriate version of the method of the object it references, is more formally known as polymorphism.However, you should note that in order to make use of polymorphism, the method you are calling mustexist on the base class as well as the derived class. For example, suppose you add some other method,such as a property called HighCostMinutesLeft, to Nevermore60Customer in order to allow users tofind out this piece of information. Then the following would be legal code:

Nevermore60Customer mrLeggit = new Nevermore60Customer();

// processing

int minutesLeft = mrLeggit.HighCostMinutesLeft;

The following, however, would not be legal code, because the HighCostMinutesLeft property doesn’texist in the Customer base class:

Customer mrLeggit = new Nevermore60Customer();

// processing

int minutesLeft = mrLeggit.HighCostMinutesLeft;

Here are some other points about virtual members you should be aware of:

❑ Not only methods can be overridden or hidden. You can do the same thing with any other classmember that has an implementation, including properties.

❑ Fields cannot be declared as virtual or overridden. However, it is possible to hide a base ver-sion of a field by declaring another field of the same name in a derived class. In that case, ifyou wanted to access the base version from the derived class, you’d need to use the syntaxbase.<field_name>. Actually, you probably wouldn’t do that anyway, because you’d haveall your fields declared as private.

❑ Static methods and so on cannot be declared as virtual, but they can be hidden in the sameway that instance methods and other methods can be. It wouldn’t make sense to declare astatic member as virtual; virtual means that the compiler looks up the instance of a classwhen it calls that member, but static members are not associated with any class instance.

❑ Just because a method has been declared as virtual doesn’t mean it has to be overridden. Ingeneral, if the compiler encounters a call to a virtual method, it will look for the definition of themethod first in the class concerned. If the method isn’t defined or overridden in that class, it willcall the base class version of the method. If the method isn’t derived there, it’ll look in the nextbase class, and so on, so that the method executed will be the one closest in the class hierarchyto the class concerned. (Note that this process occurs at compile time, when the compiler is con-structing the vtable for each class. There is no impact at runtime.)

1345

Principles of Object-Oriented Programming

575341 bc01.qxd 10/3/05 9:07 AM Page 1345

Page 28: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

Method HidingEven if a method has not been declared as virtual in a base class, it is still possible to provide anothermethod with the same signature in a derived class. The signature of a method is the set of all informationneeded to describe how to call that method: its name, number of parameters, and parameter types.However, the new method will not override the method in the base class. Rather, it is said to hide thebase class method. As implied earlier, what this means is that the compiler will always examine the datatype of the variable used to reference the instance when deciding which method to call. If a methodhides a method in a base class, you should normally add the keyword new to its definition. Not doing sodoes not constitute an error, but it will cause the compiler to give you a warning.

Realistically, method hiding is not something you’ll often want to do deliberately, but you can see how itworks by adding a new method called GetFunnyString() to your Customer class and hiding it inNevermore60Customer(). GetFunnyString() just displays some information about the class and isdefined like this:

public class Customer{

public string GetFunnyString(){

return “Plain ordinary customer. Kaark!”;}

...

public class Nevermore60Customer : Customer{

public new string GetFunnyString(){

return “Nevermore60. Nevermore!”;}

...

Nevermore60Customer’s version of this function will be the one called up, but only if called using avariable that is declared as a reference to Nevermore60Customer (or some other class derived fromNevermore60Customer). This client code demonstrates this:

public static void Main(){

Customer cust1;Nevermore60Customer cust2;cust1 = new Customer();Console.WriteLine(“Customer referencing Customer: “

+ cust1.GetFunnyString());cust1 = new Nevermore60Customer();Console.WriteLine(“Customer referencing Nevermore60Customer: “

+ cust1.GetFunnyString());cust2 = new Nevermore60Customer();Console.WriteLine(“Nevermore60Customer referencing: “

+ cust2.GetFunnyString()); }

1346

Appendix A

575341 bc01.qxd 10/3/05 9:07 AM Page 1346

Page 29: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

This code is downloadable as the MortimerPhones3Funny sample. Running the sample gives this result:

MortimerPhones3FunnyCustomer referencing Customer: Plain ordinary customer. Kaark!Customer referencing Nevermore60Customer: Plain ordinary customer. Kaark!Nevermore60Customer referencing: Nevermore60. Nevermore!

Abstract Functions and Base ClassesSo far, every time you’ve defined a class you’ve actually created instances of that class, but that’s notalways the case. In many situations, you’ll define a very generic class from which you intend to deriveother, more specialized classes but don’t ever intend to actually use. C# provides the keyword abstractfor this purpose. If a class is declared as abstract it is not possible to instantiate it.

For example, suppose you have an abstract class MyBaseClass, declared like this:

abstract class MyBaseClass{...

In this case, the following statement will not compile:

MyBaseClass MyBaseRef = new MyBaseClass();

However, it’s perfectly legitimate to have MyBaseClass references, so long as they only point to derivedclasses. For example, you can derive a new class from MyBaseClass:

class MyDerivedClass : MyBaseClass{...

In this case, the following is perfectly valid code:

MyBaseClass myBaseRef;myBaseRef = new MyDerivedClass();

It’s also possible to define a method as abstract. This means that the method is treated as a virtualmethod and that you are not actually implementing the method in that class, on the assumption that itwill be overridden in all derived classes. If you declare a method as abstract, you do not need to sup-ply a method body:

abstract class MyBaseClass{

public abstract int MyAbstractMethod(); // look no body!...

If any method in a class is abstract, that implies the class itself should be abstract, and the compilerwill raise an error if the class is not so declared. Also, any non-abstract class that is derived from thisclass must override the abstract method. These rules prevent you from ever actually instantiating aclass that doesn’t have implementations of all its methods.

1347

Principles of Object-Oriented Programming

575341 bc01.qxd 10/3/05 9:07 AM Page 1347

Page 30: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

At this stage, you’re probably wondering what the use of abstract methods and classes are for. They areextremely useful for two reasons. One is that they often allow a better design of class hierarchy, in whichthe hierarchy more closely reflects the situation you are trying to model. The other is that the use ofabstract classes can shift certain potential bugs from hard-to-locate runtime errors into easy-to-locatecompile-time errors. It’s a bit hard to see how that works in practice without looking at an example, soin the next section, you improve the program architecture of MortimerPhones by rearranging the classhierarchy.

Defining an Abstract ClassYou’re not redesigning the Mortimer Phones sample just for the fun of it. There’s actually a bit of adesign flaw in the current class hierarchy. The class Customer represents pay-as-you-go customers asthe base class for all the other customer types. You’re treating that kind of call plan as if it’s a special callplan from which all the others are derived. That’s not really an accurate representation of the situation.In reality, the pay-as-you-go call plan is just one of a range of call plans — there’s nothing special aboutit — and a more carefully designed class hierarchy would reflect that. Therefore, in this section, you’regoing to rework the MortimerPhones sample to give it the class hierarchy shown in Figure A-5.

Figure A-5

Your old Customer class is gone. Your new abstract base class is GenericCustomer. GenericCustomerimplements all the stuff that is common to all types of customers, such as methods and properties thathave the same implementation for all customers and therefore are not virtual. This includes retrievingthe balance or the customer’s name, or recording a payment.

However, GenericCustomer does not provide any implementation of the RecordCall() method, whichworks out the cost of a given call and adds it to the customer’s account. The implementation of thismethod is different for each call plan, so you require that every derived class supplies its own version ofthis method. Therefore, GenericCustomer’s RecordCall() method will be declared as abstract.

Having done that, you need to add a class that represents the pay-as-you-go customers. ThePayAsYouGoCustomer class does this job, supplying the override to RecordCall() that with the previous hierarchy was defined in the base Customer class.

System.Object

Classes representingcustomers on other

call plans

PayAsYouGoCustomer NeverMore60Customer

GenericCustomer(Abstract Class)

1348

Appendix A

575341 bc01.qxd 10/3/05 9:07 AM Page 1348

Page 31: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

You may wonder whether it is really worth the effort in redesigning the sample class hierarchy in thisway. After all, the old hierarchy worked perfectly well, didn’t it? The reason for regarding the new hier-archy as a better designed architecture is simple: it removes a possible subtle source of bugs.

In a real application, RecordCall() probably wouldn’t be the only virtual method that needed to beimplemented separately for each call plan. What happens if later on someone adds a new derived class,representing a new call plan, but forgets to add the overrides of some of these methods? Well, with theold class hierarchy, the compiler would have automatically substituted the corresponding method in thebase class. With that hierarchy, the base class represented pay-as-you-go customers, so you would haveended up with subtle runtime bugs involving the wrong versions of methods being called. With yournew hierarchy, however, that won’t happen. Instead, you’ll get a compile-time error, with the compilercomplaining that the relevant abstract methods haven’t been overridden in the new class.

Anyway, on to the new code, and as you might have guessed by now, this is the MortimerPhones4 sam-ple. With the new hierarchy, the code for GenericCustomer looks like this. Most of the code is the sameas for your old Customer class; in the following code the few lines that are different are highlighted.Note the abstract declaration for the RecordCall() method:

public abstract class GenericCustomer{

...public void RecordPayment(decimal amountPaid){

balance -= amountPaid;}public abstract void RecordCall(TypeOfCall callType, uint nMinutes);

}

Now for the implementation of the pay-as-you-go customers. Again, notice that most of the code istaken directly from the former, obsolete Customer class. The only real difference is that RecordCall()is now an override rather than a virtual method:

public class PayAsYouGoCustomer : GenericCustomer{

public override void RecordCall(TypeOfCall callType, uint nMinutes){

// same implementation as for Customer}

}

You won’t see the full code for Nevermore60Customer here because the RecordCall() override in thisclass is long and completely identical to the earlier version of the example. The only change you need tomake to this class is to derive it from GenericCustomer instead of from the Customer class, which nolonger exists:

public class Nevermore60Customer : GenericCustomer{

private uint highCostMinutesUsed;public override void RecordCall(TypeOfCall callType, uint nMinutes){

// same implementation as for old Nevermore60Customer}...

1349

Principles of Object-Oriented Programming

575341 bc01.qxd 10/3/05 9:07 AM Page 1349

Page 32: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

To finish, you’ll add some new client code to demonstrate the operation of the new class hierarchy. Thistime you’ve actually used an array to store the various customers, so this code shows how an array ofreferences to the abstract base class can be used to reference instances of the various derived classes,with the appropriate overrides of the methods being called:

public static void Main(){

GenericCustomer arabel = new Nevermore60Customer();arabel.Name = “Arabel Jones”;GenericCustomer mrJones = new PayAsYouGoCustomer();mrJones.Name = “Ben Jones”; GenericCustomer [] customers = new GenericCustomer[2];customers[0] = arabel;customers[0].RecordCall(TypeOfCall.CallToLandline, 20);customers[0].RecordCall(TypeOfCall.CallToCellPhone, 5);customers[1] = mrJones;customers[1].RecordCall(TypeOfCall.CallToLandline, 10);foreach (GenericCustomer nextCustomer in customers){

Console.WriteLine(“{0,-20} owes ${1:F2}”, nextCustomer.Name, nextCustomer.Balance);

}}

Running this code produces the correct results for the amounts owed:

MortimerPhones4Arabel Jones owes $2.90Ben Jones owes $0.20

Sealed Classes and MethodsIn many ways, you can think of a sealed class or method as the opposite of an abstract class or method.Whereas declaring something as abstract means that it must be overridden or inherited from, declaringit as sealed means that it cannot be. Not all object-oriented languages support this concept, but it can beuseful. In C# the syntax looks like this:

sealed class FinalClass{...

C# also supports declaring an individual override method as sealed, preventing any further overridesof it.

The most likely situation when you’ll mark a class or method as sealed will be if it is very much inter-nal to the operation of the library, class, or other classes that you are writing, so you are fairly sure thatany attempt to override some of its functionality causes problems. You might also mark a class ormethod as sealed for commercial reasons, in order to prevent a third party from extending your classesin a manner that is contrary to the licensing agreements. In general, however, you should be carefulabout marking a class or member as sealed, because by doing so you are severely restricting how it can

1350

Appendix A

575341 bc01.qxd 10/3/05 9:07 AM Page 1350

Page 33: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

be used. Even if you don’t think it would be useful to inherit from a class or override a particular mem-ber of it, it’s still possible that at some point in the future someone will encounter a situation you hadn’tanticipated in which it is useful to do so.

InterfacesEarlier in this appendix, you learned that there are two types of inheritance: implementation inheritanceand interface inheritance. So far you’ve seen implementation inheritance; this section looks more closely atinterface inheritance.

In general, an interface is a contract that says that a class must implement certain features (usually meth-ods and properties), but which doesn’t specify any implementations of those methods and properties.Therefore you don’t instantiate an interface; instead a class can declare that it implements one or moreinterfaces. In C#, as in most languages that support interfaces, this essentially means that the class inher-its from the interface.

To get an idea of how an interface looks in programming terms, the following shows the syntax for thedefinition of an interface that is defined in the .NET base classes, IEnumerator, from theSystem.Collections namespace. IEnumerator looks like this:

interface IEnumerator {

// Propertiesobject Current {get; }

// Methodsbool MoveNext();void Reset();

}

As you can see, the IEnumerator interface has two methods and one property. This interface is impor-tant in implementing collections and is designed to encapsulate the functionality of moving through theitems in a collection. MoveNext() moves to the next item, Reset() returns to the first item, and Currentretrieves a reference to the current item.

Beyond the lack of method implementations, the main point to note is the lack of any modifiers on themembers. Interface members are always public and cannot be declared as virtual or static.

So why have interfaces? Up to now you’ve treated classes as having certain members and not concernedyourself about grouping any members together — our classes have simply contained a list of variousmiscellaneous methods, fields, properties, and so on. There are often situations in which you need toknow that the class implements certain features in order to be able to use a class in a certain way. Anexample is provided by the foreach loop in C#. In principle, it is possible to use foreach to iteratethrough a class instance, provided that that class is able to act as if it is a collection. How can the .NETruntime tell whether a class instance represents a collection? It queries the instance to find out whether itimplements the System.Collections.IEnumerable interface. If it does, the runtime uses the methodson this interface to iterate through the members of the collection. If it doesn’t, foreach will raise anexception.

1351

Principles of Object-Oriented Programming

575341 bc01.qxd 10/3/05 9:07 AM Page 1351

Page 34: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

You might wonder why in this case you don’t just see if the class implements the required methods andproperties. The answer is that that wouldn’t be a very reliable way of checking. For example, you canprobably think of all sorts of different reasons why a class might happen to implement a method calledMoveNext() or Reset(), which don’t have anything to do with collections. If the class declares that itimplements the interfaces needed for collections, then you know that it really is a collection.

A second reason for using interfaces is for interoperability with COM. Before the advent of .NET, COMand its later versions DCOM and COM+, provided the main way that applications could communicatewith each other on the Windows platform, and the particular object model that COM used was heavilydependent on interfaces. Indeed, it was through COM that the concept of an interface first became com-monly known. It should be stressed, however, that C# interfaces are not the same as COM interfaces.COM interfaces have very strict requirements, such as that they must use GUIDs as identifiers, whichare not necessarily present in C# interfaces. However, using attributes (a C# feature covered in the begin-ning of the book), it is possible to dress up a C# interface so it acts like a COM interface, and hence pro-vide compatibility with COM. COM interoperability is discussed in Chapter 33, “COM Interoperability.”

For more details on interfaces, see Chapter 4, “Inheritance.”

Construction and DisposalThis final section of the appendix leaves inheritance behind and looks at another topic that is importantin OOP programming: creation and disposal of objects — or to use the usual terminology, constructionand destruction of objects. Say you have this code:

{int x;// more code

}

You will be aware that when x is created (comes into scope), memory gets allocated for it, and that whenit goes out of scope, that memory is reclaimed by the system. If you are familiar with C#, you’ll also beaware that x is initialized with the value zero when the variable comes into scope. For integers, the lan-guage defines what initializations happen automatically when an int gets created. But wouldn’t it benice if you could do the same for your own classes? Well, most modern OOP languages support the abil-ity to do this — and C# is no exception. This support happens through something called a constructor. Aconstructor is a special method called automatically whenever an object of a given class is created. Youdon’t have to write a constructor for a class, but if you want some custom initialization to take placeautomatically, you should place the relevant code in the constructor.

Similarly, OOP languages, including C#, support something called a destructor. A destructor is a methodcalled automatically whenever an object is destroyed (the variable goes out of scope). Reclaiming mem-ory aside, destructors are particularly useful for classes that represent a connection to a database, or anopen file, or those that have methods to read from and write to the database/file. In that case, thedestructor can be used to make sure that you don’t leave any database connections or file handles hang-ing open when the object goes out of scope.

That said, the facilities offered by the .NET Framework and the garbage collector mean that destructorsare not only used a lot less often in C# than they are in pre-.NET languages, but also that the syntax for

1352

Appendix A

575341 bc01.qxd 10/3/05 9:07 AM Page 1352

Page 35: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

defining them is more complex (indeed, destructors are almost the only thing that is more complex tocode in C# than in C++!). For that reason this appendix won’t look any more closely at destructors. Howto write destructors in C# is covered in Chapter 5, “Operators and Casts.” This appendix concentrates onconstructors, to give you an idea of how the concept works.

Visual Basic developers will note that there are some similarities between constructors and theInitialize() and Form_Load() methods of VB class modules. Constructors, however, are far moreflexible and powerful.

Creating ConstructorsWhen you see a constructor definition in C#, it looks much like a method definition, but the difference isthat you don’t usually call a constructor explicitly. It’s like a method that is always called on your behalfwhenever an instance of a class is created. In addition, because you never call the method explicitly,there is no way you can get access to any return value, which means that constructors never return any-thing. You can identify a constructor in a class definition because it always has the same name as theclass itself. For example, if you have a class named MyClass, a skeleton constructor will be defined asfollows:

public class MyClass{

public MyClass(){}...

This constructor so far does nothing, because you haven’t added any code to it. Add an integer fieldMyField to the class and initialize it to 10:

public class MyClass{

public MyClass(){

myField = 10;}private int myField;...

Notice that no return type is specified, not even void. The compiler recognizes the constructor from thefact that it has the same name as the containing class. You should note that one implication of this is thatit is not possible to write a method that has the same name as the class it belongs to, because if you dothe compiler will interpret it as a constructor.

From the previous example, you might wonder if you’ve actually achieved anything new. After all, interms of C# syntax, you could have written:

public class MyClassprivate int myField = 10;

1353

Principles of Object-Oriented Programming

575341 bc01.qxd 10/3/05 9:07 AM Page 1353

Page 36: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

This achieves the same effect — specifying how to initialize each object without explicitly indicating aconstructor. Indeed, you have already done something like this in all the Authenticator samples, inwhich you specified that the password field should automatically be initialized to an empty string. Theanswer is that here we are trying to introduce the concept of a constructor. The preceding code is reallyjust C# shorthand for specifying construction code implicitly — a shorthand that is specific to C#. Behindthis shorthand there is still a constructor at work. Besides, by writing a constructor explicitly, it meansyou can write code to compute initial values at runtime — the shorthand requires values to be known atcompile time, as constants.

It’s not necessary to provide a constructor for your class — you haven’t supplied one for any of theexamples so far. In general, if you don’t explicitly supply any constructor, the compiler will just make upa default one for you behind the scenes. It’ll be a very basic constructor that just initializes all the mem-ber fields to their normal default values (empty string for strings, zero for numeric data types, andfalse for bools).

Initializing to default values is something that happens in C# because C# initializes all members of aclass. If you are coding in a different language, this behavior might differ. For example, by default C++never initializes anything unless you explicitly indicate that’s what you want. So in C++, if you don’tsupply a constructor to a class, its members won’t get initialized to anything (unless they have con-structors instead).

Passing parameters to constructorsLet’s go back to the Authenticator class. Say you wanted to modify the class so that you can specifythe initial password when you first instantiate the class. It is possible to do this by supplying a construc-tor that takes parameters. In this regard, a constructor behaves like a method in that you can definewhatever parameters you want for it, and this is where constructors really score over Visual Basic’sInitialize or Form_Load.

For the Authenticator, you’d probably add a constructor that takes an initial password as a parameter:

public class Authenticator{

public Authenticator(string initialPassword){

password = initialPassword;}private string password = “”;private static uint minPasswordLength = 6;...

The advantage of using such a constructor is that it means an Authenticator object is guaranteed tobe initialized the instant it is created. It is, therefore, not possible for other code to access the objectbefore it has been initialized, as would be possible if you initialized it by calling a method after instanti-ating an object.

Now, to instantiate the object, you would use a line of code similar to the following:

Authenticator NewUser = new Authenticator(“MyPassword45”);

1354

Appendix A

575341 bc01.qxd 10/3/05 9:07 AM Page 1354

Page 37: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

Here you have created an instance with the password MyPassword45. You should note that the follow-ing line will not compile anymore:

Authenticator NewUser2 = new Authenticator();

This is because you do not supply any parameters to the constructor, and the constructor requires oneparameter. However, if you wanted to, you could simply create an overload for the constructor that didn’t take any parameter arguments and simply set a default password in this constructor overload(this would not be a very secure approach though!).

More uses of constructorsAlthough the only thing you’ve done with constructors is to initialize the values of fields, a constructordoes act as a normal method so you can place any instructions you want in it; for example, you mightperform some calculations to work out the initial values of the fields. If your class encapsulates access toa file or database, the constructor might attempt to open the file. The only thing that you cannot do in aconstructor is return any value (such as indicating status) to the calling code.

Another novel use is to use a constructor to count how many instances of a class have been created whilethe program is running. If you wanted to do that for the Authenticator class, you could create a staticfield, nInstancesCreated, and amend the code for the constructor as follows:

public class Authenticator{

private static uint nInstancesCreated = 0;

public Authenticator(string initialPassword){

++nInstancesCreated;Password = initialPassword;

}

private string password = 10;private static uint minPasswordLength = 6;...

This example might not have many practical applications, but it demonstrates the kind of flexibility youhave by being able to specify your own constructors. Counting instances is something you’re unlikely towant to do in release builds of code, but it’s something that you might want to do for debugging purposes.

SummaryThe aim of this appendix has been to introduce you to the basic concepts of object-oriented design in C#,including:

❑ Classes, objects, and instances

❑ Fields, methods, and properties

1355

Principles of Object-Oriented Programming

575341 bc01.qxd 10/3/05 9:07 AM Page 1355

Page 38: Principles of Object- Oriented Programming · appendix introduces the principles of object-oriented programming. Although you’ll see some C# syntax (because the examples are presented

❑ Overloading

❑ Inheritance and class hierarchies

❑ Polymorphism

❑ Interfaces

Object-oriented programming methodology is strongly reflected in the design of the C# language, and ofIntermediate Language too, which becomes apparent when you start using the .NET base classes.Microsoft has done this because with our current understanding of programming techniques, it simplyis the most appropriate way of coding any large library or application.

1356

Appendix A

575341 bc01.qxd 10/3/05 9:07 AM Page 1356