Introduction to Object Oriented Programming
>> Aug 19, 2009
At this point in the book, you ’ ve covered all the basics of C# syntax and programming, and have learned how to debug your applications. Already, you can assemble usable console applications. However, to access the real power of the C# language and the .NET Framework, you need to make use of object-oriented programming (OOP) techniques. In fact, as you will soon see, you ’ ve been using these techniques already, although to keep things simple we haven ’ t focused on this.
This chapter steers away from code temporarily and focuses instead on the principles behind OOP. This leads you back into the C# language, because it has a symbiotic relationship with OOP. All of the concepts introduced in this chapter are revisited in later chapters, with illustrative code — so don ’ t panic if you don ’ t grasp everything in the first read - through of this material.
To start with, you ’ ll look at the basics of OOP, which include answering that most fundamental of questions, “ What is an object? ” You will quickly find that there is a lot of terminology related to OOP that can be quite confusing at first, but plenty of explanations are provided. You will also see that using OOP requires you to look at programming in a different way.
As well as discussing the general principles of OOP, this chapter also looks at an area requiring a thorough understanding of OOP: Windows Forms applications. This type of application (which makes use of the Windows environment, with features such as menus, buttons, and so on) provides plenty of scope for description, and you will be able to illustrate OOP points effectively in
the Windows Forms environment.
In this chapter you learn the following:
. What object - oriented programming is .
. OOP techniques .
. How Windows Forms applications rely on OOP.
OOP as presented in this chapter is really .NET OOP, and some of the techniques presented here don ’ t apply to other OOP environments. When programming in C#, you use .NET- specific OOP, so it makes sense to concentrate on these aspects.
What Is Object - Oriented Programming?
Object - oriented programming is a relatively new approach to creating computer applications that seeks to address many of the problems with traditional programming techniques. The type of programming you have seen so far is known as functional (or procedural) programming, often resulting in so - called monolithic applications, meaning all functionality is contained in a few modules of code (often just one). With OOP techniques, you often use many more modules of code, each offering specific functionality, and each module may be isolated or even completely independent of others. This modular method of programming gives you much more versatility and provides more opportunity for code reuse.
To illustrate this further, imagine that a high - performance application on your computer is a top - of - the range racecar. Written with traditional programming techniques, this sports car is basically a single unit. If you want to improve this car, then you have to replace the whole unit by sending it back to the manufacturer and getting their expert mechanics to upgrade it, or by buying a new one. If OOP techniques are used, then you can simply buy a new engine from the manufacturer and follow their instructions to replace it yourself, rather than take a hacksaw to the bodywork.
In a more traditional application, the flow of execution is often simple and linear. Applications are loaded into memory, begin executing at point A, end at point B, and are then unloaded from memory. Along the way various other entities might be used, such as files on storage media, or the capabilities of a video card, but the main body of the processing occurs in one place. The code along the way is generally concerned with manipulating data through various mathematical and logical means. The methods of manipulation are usually quite simple, using basic types such as integers and Boolean values to build more complex representations of data.
With OOP, things are rarely so linear. Although the same results are achieved, the way of getting there is often very different. OOP techniques are firmly rooted in the structure and meaning of data, and the interaction between that data and other data. This usually means putting more effort into the design stages of a project, but it has the benefit of extensibility. After an agreement is made as to the representation of a specific type of data, that agreement can be worked into later versions of an application, and even entirely new applications. The fact that such an agreement exists can reduce development time dramatically. This explains how the racecar example works. The agreement here is how the code for the “ engine ” is structured, such that new code (for a new engine) can be substituted with ease, rather than requiring a trip back to the manufacturer. Conversely, it also means that the engine, once created, can be used for other purposes. You could put it in a different car, or use it to power a submarine, for example.
As well as providing an agreed - on approach to data representation, OOP programming often simplifies things by providing an agreement on the structure and usage of more abstract entities. For example, an agreement can be made not just on the format of data that should be used to send output to a device such as a printer, but also on the methods of data exchange with that device, including what instructions it understands, and so on. Going back to the racecar analogy, the agreement would include how the engine connects to the fuel tank, how it passes drive power to the wheels, and so on.
As the name of the technology suggests, this is achieved using objects.
What Is an Object?
An object is a building block of an OOP application. This building block encapsulates part of the application, which may be a process, a chunk of data, or a more abstract entity.
In the simplest sense, an object may be very similar to a struct type such as those shown earlier in the book, containing members of variable and function types. The variables contained make up the data stored in the object, and the functions contained allow access to the functionality of the object. Slightly more complex objects might not maintain any data; instead, they can represent a process by containing only functions. For example, an object representing a printer might be used, which would have functions enabling control over a printer (so you can print a document, a test page, and so on).
Objects in C# are created from types, just like the variables you ’ ve seen already. The type of an object is known by a special name in OOP, its class. You can use class definitions to instantiate objects, which means to create a real, named instance of a class. The phrases instance of a class and object mean the same thing here; but note at this point that class and object mean fundamentally different things.
The terms class and object are often confused, and it is important to understand the distinction. It may help you to visualize these terms using the earlier racecar analogy. Think of a class as the template for the car, or perhaps the plans used to build the car. The car itself is an instance of those plans, so it could be referred to as an object.
In this chapter, you work with classes and objects using Universal Modeling Language (UML ) syntax. UML is a language designed for modeling applications, from the objects that build them, to the operations they perform, and to the use cases that are expected. Here, you use only the basics of this language, which are explained as you go along. Not covered are more complex aspects because UML is a specialized subject to which entire books are devoted.
VS has a class viewer that is a powerful tool in its own right.
Figure 8 - 1 shows a UML representation of your printer class, called Printer.
Figure 8-1
The class name is shown in the top section of this box (you learn about the bottom two sections a little later). Figure 8 - 2 shows a UML representation of an instance of this Printerclass called myPrinter.
Figure 8-2
Here, the instance name is shown first in the top section, followed by the name of its class. These two names are separated by a colon.
Properties and Fields
Properties and fields provide access to the data contained in an object. This object data is what differentiates separate objects, because it is possible for different objects of the same class to have different values stored in properties and fields.
The various pieces of data contained in an object together make up the state of that object. Imagine an object class that represents a cup of coffee, called CupOfCoffee. When you instantiate this class (that is, create an object of this class), you must provide it with a state for it to be meaningful. In this case, you might use properties and fields to enable the code that uses this object to set the type of coffee used, whether the coffee contains milk and/or sugar, whether the coffee is instant, and so on. A given coffee cup object would then have a given state, such as “Columbian filter coffee with milk and two sugars. ”
Both fields and properties are typed, so you can store information in them as stringvalues, as int values, and so on. However, properties differ from fields in that they don ’ t provide direct access to data. Objects can shield users from the nitty - gritty details of their data, which needn ’ t be represented on a one to - one basis in the properties that exist. If you used a field for the number of sugars in a CupOfCoffee instance, then users could place whatever values they liked in the field, limited only by the limits of the type used to store this information. If, for example, you used an int to store this data, then users could use any value between – 2147483648 and 2147483647, as shown in Chapter 3 . Obviously, not all values make sense, particularly the negative ones — some of the large positive amounts might require an inordinately large cup. Still, if you used a property for this information, then you could limit this value to, say, a number between 0 and 2.
In general, it is better to provide properties, rather than fields, for state access, because you have more control over various behaviors. This choice doesn ’ t affect code that uses object instances, because the syntax for using properties and fields is the same.
Read/write access to properties may also be clearly defined by an object. Certain properties may be read - only, allowing you to see what they are but not change them (at least not directly). This is often a useful technique for reading several pieces of state simultaneously. You might have a read - only property of the CupOfCoffeeclass called Description, returning a string representing the state of an instance of this class (such as the string given earlier) when requested. You might be able to assemble the same data by interrogating several properties, but a property such as this one may save you time and effort. You might also have write - only properties operating in a similar way.
As well as this read/write access for properties, you can also specify a different sort of access permission for both fields and properties, known as accessibility. Accessibility determines which code can access these members — that is, whether they are available to all code (public), only to code within the class (private), or use a more complex scheme (covered in more detail later in the chapter, when it becomes pertinent). One common practice is to make fields private and provide access to them via public properties. This means that code within the class has direct access to data stored in the field, while the public property shields external users from this data and prevents them from placing invalid content there. Public members are said to be exposed by the class.
One way to visualize this is to equate it with variable scope. Private fields and properties, for example, can be thought of as local to the object that possesses them, whereas the scope of public fields and
properties also encompasses code external to the object.
In the UML representation of a class, you use the second section to display properties and fields, as shown in Figure 8 - 3.
Figure 8-3
This is a representation of the CupOfCoffee class, with five members (properties or fields, because no distinction is made in UML) defined as discussed earlier. Each of the entries contains the following information:
. Accessibility: A + symbol is used for a public member, a - symbol is used for a private member. In general, though, private members are not shown in the diagrams in this chapter, because this information is internal to the class. No information is provided as to read/write access.
. The member name .
. The type of the member.
A colon is used to separate the member names and types.
Methods
Method is the term used to refer to functions exposed by objects. These may be called in the same way as any other function and may use return values and parameters in the same way — you looked at functions in detail in Chapter 6 .
Methods are used to provide access to the object ’ s functionality. Like fields and properties, they can be public or private, restricting access to external code as necessary. They often make use of an object ’ s state to affect their operations, and have access to private members such as private fields if required. For example, the CupOfCoffee class might define a method called AddSugar(), which would provide a more readable syntax for incrementing the amount of sugar than setting the corresponding Sugarproperty.
In UML, class boxes show methods in the third section, as shown in Figure 8 - 4.
Figure 8-4
The syntax here is similar to that for fields and properties, except that the type shown at the end is the return type and method parameters are shown. Each parameter is displayed in UML with one of the following identifiers: in, out, or inout. These are used to signify the direction of data flow, where out and inout roughly correspond to the use of the C# keywords outand refdescribed in Chapter 6 . in roughly corresponds to the default C# behavior, where neither the outnor ref keyword is used.
Everything ’ s an Object
At this point, it ’ s time to come clean: You have been using objects, properties, and methods throughout this book. In fact, everything in C# and the .NET Framework is an object! The Main()function in a console application is a method of a class. Every variable type you ’ ve looked at is a class. Every command you have used has been a property or a method, such as < String > .Length, < String > .ToUpper(), and so on. (The period character here separates the object instance ’ s name from the property or method name.)
Objects really are everywhere, and the syntax to use them is often very simple. It has certainly been simple enough for you to concentrate on some of the more fundamental aspects of C# up until now. From this point on, you ’ ll begin to look at objects in detail. Bear in mind that the concepts introduced here have far- reaching consequences — applying even to that simple little int variable you ’ ve been happily playing around with.
The Life Cycle of an Object
Every object has a clearly defined life cycle. Apart from the normal state of “ being in use, ” this life cycle includes two important stages:
. Construction: When an object is first instantiated it needs to be initialized. This initialization is known as construction and is carried out by a constructor function.
. Destruction: When an object is destroyed, there are often some clean - up tasks to perform, such as freeing memory. This is the job of a destructor function.
Constructors
Basic initialization of an object is automatic. For example, you don ’ t have to worry about finding the memory to fit a new object into. However, at times you will want to perform additional tasks during an object ’ s initialization stage, such as initializing the data stored by an object. A constructor function is what you use to do this.
All objects have a default constructor, which is a parameterless method with the same name as the class itself. A class definition might include several constructor methods with parameters, known as nondefault constructors. These enable code that instantiates an object to do so in many ways, perhaps providing initial values for data stored in the object.
In C#, constructors are called using the new keyword. For example, you could instantiate a CupOfCoffee object using its default constructor in the following way:
CupOfCoffee myCup = new CupOfCoffee();
Objects may also be instantiated using nondefault constructors. For example, the CupOfCoffeeclass might have a nondefault constructor that uses a parameter to set the bean type at instantiation:
CupOfCoffee myCup = new CupOfCoffee(“Blue Mountain”);
Constructors, like fields, properties, and methods, may be public or private. Code external to a class can ’ t instantiate an object using a private constructor; it must use a public constructor. In this way, you can, for example, force users of your classes to use a nondefault constructor (by making the default constructor private).
Some classes have no public constructors, meaning that it is impossible for external code to instantiate them (they are said to be noncreatable). However, this doesn ’ t make them completely useless, as you will see shortly.
Destructors
Destructors are used by the .NET Framework to clean up after objects. In general, you don ’ t have to provide code for a destructor method; instead, the default operation does the work for you. However, you can provide specific instructions if anything important needs to be done before the object instance is deleted.
When a variable goes out of scope, for example, it may not be accessible from your code, but it may still exist somewhere in your computer’ s memory. Only when the .NET runtime performs its garbage collection cleanup is the instance completely destroyed.
Don ’t rely on the destructor to free up resources used by an object instance, as this may occur long after the object is of no further use to you. If the resources in use are critical, then this can cause problems. However, there is a solution to this — described in “ Disposable Objects ” later in this chapter.
Static and Instance Class Members
As well as having members such as properties, methods, and fields that are specific to object instances, it is also possible to have static (also known as shared, particularly to our Visual Basic brethren) members, which may be methods, properties, or fields. Static members are shared between instances of a class, so they can be thought of as global for objects of a given class. Static properties and fields enable you to access data that is independent of any object instances, and static methods enable you to execute commands related to the class type but not specific to object instances. When using static members, in fact, you don ’ t even need to instantiate an object.
For example, the Console.WriteLine()and Convert.ToString() methods you have been using are static. At no point do you need to instantiate the Consoleor Convert classes (indeed, if you try, you ’ ll find that you can ’ t, as the constructors of these classes aren ’ t publicly accessible, as discussed earlier).
There are many situations such as these where static properties and methods can be used to good effect. For example, you might use a static property to keep track of how many instances of a class have been created. In UML syntax, static members of classes appear with underlining, as shown in Figure 8 - 5.
Figure 8-5
Static Constructors
When using static members in a class, you may want to initialize these members beforehand. You can supply a static member with an initial value as part of its declaration, but sometimes you may want to perform a more complex initialization, or perhaps perform some operations before assigning values or allowing static methods to execute.
You can use a static constructor to perform initialization tasks of this type. A class can have a single static constructor, which must have no access modifiers and cannot have any parameters. A static constructor can never be called directly; instead, it is executed when one of the following occurs:
. When an instance of the class containing the static constructor is created .
. When a static member of the class containing the static constructor is accessed .
In both cases, the static constructor is called first, before the class is instantiated or static members accessed. No matter how many instances of a class are created, its static constructor will only be called once. To differentiate between static constructors and the constructors described earlier in this chapter, all non - static constructors are also known as instance constructors.
Static Classes
Often, you will want to use classes that contain only static members and cannot be used to instantiate objects (such as Console). A shorthand way to do this, rather than make the constructors of the class private, is to use a static class. A static class can contain only static members and can ’ t have instance constructors, since by implication it can never be instantiated. Static classes can, however, have a static constructor, as described in the preceding section.
If you are completely new to OOP, then you might like to take a break before embarking on the remainder of this chapter. It is important to fully grasp the fundamentals before learning about the more complicated aspects of this methodology.
OOP Techniques
Now that you ’ ve covered the basics and know what objects are and how they work, you should spend some time looking at some of the other features of objects. This section covers all of the following:
. Interfaces
. Inheritance
. Polymorphism
. Relationships between objects
. Operator overloading
. Events
. Reference versus value types
Interfaces
An interface is a collection of public methods and properties that are grouped together to encapsulate specific functionality. After an interface has been defined, you can implement it in a class. This means that the class will then support all of the properties and members specified by the interface.
Note that interfaces cannot exist on their own. You can ’ t “ instantiate an interface ” as you can a class. In addition, interfaces cannot contain any code that implements its members; it just defines the members themselves. The implementation must come from classes that implement the interface.
In the earlier coffee example, you might group together many of the more general - purpose properties and methods into an interface, such as AddSugar(), Milk, Sugar, and Instant. You could call this
interface something like IHotDrink (interface names are normally prefixed with a capital I ). You could use this interface on other objects, perhaps those of a CupOfTea class. You could therefore treat these objects in a similar way, and they may still have their own individual properties ( BeanTypefor CupOfCoffeeand LeafTypefor CupOfTea, for example).
Interfaces implemented on objects in UML are shown using a lollipop syntax. In Figure 8 - 6 , members of IHotDrink are split into a separate box using class - like syntax.
Figure 8-6
A class can support multiple interfaces, and multiple classes can support the same interface. The concept of an interface, therefore, makes life easier for users and other developers. For example, you might have some code that uses an object with a certain interface. Provided that you don ’ t use other properties and methods of this object, it is possible to replace one object with another (code using the IHotDrink interface shown earlier could work with both CupOfCoffeeand CupOfTeainstances, for example). In addition, the developer of the object itself could supply you with an updated version of an object, and as long as it supports an interface already in use it would be easy to use this new version in your code.
Once an interface is published — that is, it has been made available to other developers or end users — it is good practice not to change it. One way of thinking about this is to imagine the interface as a contract between class creators and class consumers. You are effectively saying, “E very class that supports interface X will support these methods and properties. ” If the interface changes later, perhaps due to an upgrade of the underlying code, this could cause consumers of that interface to run it incorrectly, or even fail. Instead, you should create a new interface that extends the old one, perhaps including a version number, such as X2. This has become the standard way of doing things, and you are likely to come across numbered interfaces frequently.
Disposable Objects
One interface of particular interest is IDisposable. An object that supports the IDisposableinterface must implement the Dispose()method — that is, it must provide code for this method. This method can be called when an object is no longer needed (just before it goes out of scope, for example) and should be used to free up any critical resources that might otherwise linger until the destructor method is called on garbage collection. This gives you more control over the resources used by your objects.
C# enables you to use a structure that makes excellent use of this method. The using keyword enables you to initialize an object that uses critical resources in a code block, where Dispose() is automatically called at the end of the code block:
...
Using ( < VariableName> )
{
...
}
Alternatively, you can instantiate the object
using ( < ClassName> < VariableName> = new
{
...
}
In both cases, the variable
Inheritance
Inheritance is one of the most important features of OOP. Any class may inherit from another, which means that it will have all the members that the class it inherits from has. In OOP terminology, the class being inherited from ( derived from) is the parent class (also known as the base class). Note that classes in C# may derive only from a single base class directly, although of course that base class may have a base class of its own, and so on.
Inheritance enables you to extend or create more specific classes from a single, more generic base class. For example, consider a class that represents a farm animal (as used by ace octogenarian developer Old MacDonald in his livestock application). This class might be called Animaland possess methods such as EatFood()or Breed(). You could create a derived class called Cow, which would support all of these methods, but might also supply its own, such as Moo()and SupplyMilk(). You could also create another derived class, Chicken, with Cluck()and LayEgg()methods.
In UML, you indicate inheritance using arrows, as shown in Figure 8 - 7 .
Figure 8-7
In Figure 8 - 7 , the member return types are omitted for clarity.
When using inheritance from a base class, the question of member accessibility becomes an important one. Private members of the base class are not accessible from a derived class, but public members are. However, public members are accessible to both the derived class and external code. Therefore, if you could use only these two levels of accessibility, you couldn ’ t have a member that was accessible both by the base class and the derived class but not external code.
To get around this, there is a third type of accessibility, protected, in which only derived classes have access to a member. As far as external code is aware, this is identical to a private member — it doesn ’ t have access in either case.
As well as defining the protection level of a member, you can also define an inheritance behavior for it. Members of a base class may be virtual, which means that the member can be overridden by the class that inherits it. Therefore, the derived class may provide an alternative implementation for the member. This alternative implementation doesn ’ t delete the original code, which is still accessible from within the class, but it does shield it from external code. If no alternative is supplied, then any external code that uses the member through the derived class automatically uses the base class implementation of the member.
Virtual members cannot be private because that would cause a paradox — it is impossible to say that a member can be overridden by a derived class at the same time you say that it is inaccessible from the derived class.
In the animals example, you could make EatFood()virtual and provide a new implementation for it on any derived class — for example, just on the Cow class, as shown in Figure 8 - 8 .
This displays the EatFood()method on the Animaland Cowclasses to signify that they have their own implementations.
Base classes may also be defined as abstract classes. An abstract class can ’ t be instantiated directly; to use it you need to inherit from it. Abstract classes may have abstract members, which have no implementation in the base class, so an implementation must be supplied in the derived class. If Animal were an abstract class, then the UML would look as shown in Figure 8 - 9.
Figure 8-9
Abstract class names are shown in italics (or with a dashed line for their boxes).
In Figure 8 - 9 , both EatFood()and Breed()are shown in the derived classes Chickenand Cow , implying that these methods are either abstract (and, therefore, must be overridden in derived classes) or virtual (and, in this case, have been overridden in Chickenand Cow). Of course, abstract base classes can provide implementation of members, which is very common. The fact that you can ’ t instantiate an abstract class doesn ’ t mean you can ’ t encapsulate functionality in it.
Finally, a class may be sealed. A sealed class may not be used as a base class, so no derived classes are possible. C# provides a common base class for all objects called object(which is an alias for the System.Object class in the .NET Framework). You take a closer look at this class in Chapter 9 .
Interfaces, described earlier in this chapter, may also inherit from other interfaces. Unlike classes, interfaces may inherit from multiple base interfaces (in the same way that classes can support multiple interfaces).
Polymorphism
One consequence of inheritance is that classes deriving from a base class have an overlap in the methods and properties that they expose. Because of this, it is often possible to treat objects instantiated from classes with a base type in common using identical syntax. For example, if a base class called Animalhas a method called EatFood(), then the syntax for calling this method from the derived classes Cow and Chicken will be similar:
Cow myCow = new Cow();
Chicken myChicken = new Chicken();
myCow.EatFood();
myChicken.EatFood();
Polymorphism takes this a step further. You can assign a variable that is of the base type to a variable of one of the derived types, as shown here:
Animal myAnimal = myCow;
No casting is required for this. You can then call methods of the base class through this variable:
myAnimal.EatFood();
This results in the implementation of EatFood()in the derived class being called. Note that you can ’ t call methods defined on the derived class in the same way. The following code won ’ t work:
myAnimal.Moo();
However, you can cast a base type variable into a derived class variable and call the method of the derived class that way:
Cow myNewCow = (Cow)myAnimal;
myNewCow.Moo();
This casting causes an exception to be raised if the type of the original variable was anything other than Cow or a class derived from Cow. There are ways to determine what type an object is, which you ’ ll learn in the next chapter.
Polymorphism is an extremely useful technique for performing tasks with a minimum of code on different objects descending from a single class. Note that it isn ’ t just classes sharing the same parent class that can make use of polymorphism. It is also possible to treat, say, a child and a grandchild class in the same way, as long as there is a common class in their inheritance hierarchy.
As a further note here, remember that in C# all classes derive from the base class object at the root of their inheritance hierarchies. It is therefore possible to treat all objects as instances of the class object. This is how Console.WriteLine()is able to process an almost infinite number of parameter combinations when building strings. Every parameter after the first is treated as an objectinstance, allowing output from any object to be written to the screen. To do this, the method ToString() (a member of object) is called. You can override this method to provide an implementation suitable for your class, or simply use the default, which returns the class name (qualified according to any namespaces it is in).
Interface Polymorphism
Earlier, you were introduced to the concept of interfaces for grouping together related methods and properties. Although you can ’ t instantiate interfaces in the same way as objects, you can have a variable of an interface type. You can then use this variable to access methods and properties exposed by this interface on objects that support it.
For example, suppose that instead of an Animalbase class being used to supply the EatFood()method, you place this EatFood()method on an interface called IConsume. The Cowand Chickenclasses could both support this interface, the only difference being that they are forced to provide an implementation for EatFood()(as interfaces contain no implementation). You can then access this method using code such as the following:
Cow myCow = new Cow();
Chicken myChicken = new Chicken();
IConsume consumeInterface;
consumeInterface = myCow;
consumeInterface.EatFood();
consumeInterface = myChicken;
consumeInterface.EatFood();
This provides a simple way for multiple objects to be called in the same manner, and it doesn ’ t rely on a common base class. In this code, calling consumeInterface.EatFood()results in the EatFood() method of the Cowor Chickenclass being called, depending on which instance has been assigned to the interface type variable.
Note here that derived classes inherit the interfaces supported by their base classes. In the preceding example, it may be that either Animalsupports IConsumeor that both Cowand Chickensupport IConsume. Remember that classes with a base class in common do not necessarily have interfaces in common, and vice versa.
Relationships Between Objects
Inheritance is a simple relationship between objects that results in a base class being completely exposed by a derived class, where the derived class may also have some access to the inner workings of its base class (through protected members). There are other situations in which relationships between objects become important.
This section takes a brief look at the following:
. Containment: One class contains another. This is similar to inheritance but allows the containing class to control access to the members of the contained class and even perform additional processing before using members of a contained class.
. Collections: One class acts as a container for multiple instances of another class. This is similar to having arrays of objects, but collections have additional functionality, including indexing, sorting, resizing, and more.
Containment
Containment is simple to achieve by using a member field to hold an object instance. This member field might be public, in which case users of the container object have access to its exposed methods and properties, much like with inheritance. However, you won ’ t have access to the internals of the class via the derived class, as you would with inheritance.
Alternatively, you can make the contained member object a private member. If you do this, then none of its members will be accessible directly by users, even if they are public. Instead, you can provide access to these members using members of the containing class. This means that you have complete control over which members of the contained class to expose, if any, and you can perform additional processing in the containing class members before accessing the contained class members.
For example, a Cowclass might contain an Udderclass with the public method Milk(). The Cowobject could call this method as required, perhaps as part of its SupplyMilk()method, but these details will not be apparent (or important) to users of the Cowobject.
Contained classes may be visualized in UML using an association line. For simple containment, you label the ends of the lines with 1s, showing a one - to - one relationship (one Cowinstance will contain one Udder instance). You can also show the contained Udderclass instance as a private field of the Cowclass for clarity (see Figure 8 - 10 ).
Figure 8-10
Collections
Chapter 5 described how you can use arrays to store multiple variables of the same type. This also works for objects (remember, the variable types you have been using are really objects, so this is no real surprise). Here ’ s an example:
Animal[] animals = new Animal[5];
A collection is basically an array with bells and whistles. Collections are implemented as classes in much the same way as other objects. They are often named in the plural form of the objects they store — for example, a class called Animalsmight contain a collection of Animalobjects.
The main difference from arrays is that collections usually implement additional functionality, such as Add()and Remove()methods to add and remove items to and from the collection. There is also usually an Item property that returns an object based on its index. More often than not this property is implemented in such a way as to allow more sophisticated access. For example, it would be possible to design Animalsso that a given Animalobject could be accessed by its name.
In UML you can visualize this as shown in Figure 8 - 11.
Figure 8-11
Members are not included in Figure 8 - 11 because it ’ s the relationship that is being illustrated. The numbers on the ends of the connecting lines show that one Animals object will contain zero or more Animal objects. You ’ ll take a more detailed look at collections in Chapter 11 .
Operator Overloading
Earlier in the book, you saw how operators can be used to manipulate simple variable types. There are times when it is logical to use operators with objects instantiated from your own classes. This is possible because classes can contain instructions regarding how operators should be treated.
For example, you might add a new property to the Animalclass called Weight. You could then compare animal weights using the following:
if (cowA.Weight > cowB.Weight)
{
...
}
Using operator overloading, you can provide logic that used the Weight property implicitly in your code, so that you can write code such as:
if (cowA > cowB)
{
...
}
Here, the greater- than operator ( >) has been overloaded. An overloaded operator is one for which you have written the code to perform the operation involved — this code is added to the class definition of one of the classes that it operates on. In the preceding example, you are using two Cowobjects, so the operator overload definition is contained in the Cow class. You can also overload operators to work with different classes in the same way, where one (or both) of the class definitions contains the code to achieve this.
Note that you can only overload existing C# operators in this way; you can ’ t create new ones. However, you can provide implementations for both unary and binary usages of operators such as + . You ’ ll see how to do this in C# in Chapter 13 .
Events
Objects may raise (and consume) events as part of their processing. Events are important occurrences that you can act on in other parts of code, similar to (but more powerful than) exceptions. You might, for example, want some specific code to execute when an Animalobject is added to an Animalscollection, where that code isn ’ t part of either the Animalsclass or the code that calls the Add()method. To do this, you need to add an event handler to your code, which is a special kind of function that is called when the event occurs. You also need to configure this handler to listen for the event you are interested in.
Using events, you can create event - driven applications, which are far more prolific than you might think. For example, bear in mind that Windows - based applications are entirely dependent on events. Every button click or scrollbar drag you perform is achieved through event handling, as the events are triggered by the mouse or keyboard.
Later in this chapter you will see how this works in Windows applications, and there is a more in - depth discussion of events in Chapter 13 .
Reference Versus Value Types
Data in C# is stored in a variable in one of two ways, depending on the type of the variable. This type will fall into one of two categories: reference or value. The difference is as follows:
. Value types store themselves and their content in one place in memory.
. Reference types hold a reference to somewhere else in memory (called the heap) where content is stored.
In fact, you don ’ t have to worry about this too much when using C#. So far, you ’ ve used string variables (which are reference types) and other simple variables (most of which are value types, such as int) in pretty much the same way.
One key difference between value types and reference types is that value types always contain a value, whereas reference types can be null, reflecting the fact that they contain no value. It is, however, possible to create a value type that behaves like a reference type in this respect (that is, it can be null ) by using nullable types, which are a form of generic. Generics are an advanced technique described in Chapter 12 .
The only simple types that are reference types are stringand object, although arrays are implicitly reference types as well. Every class you create will be a reference type, which is why this is stressed here.
Structs
The key difference between struct types and classes is that struct types are value types. The fact that struct types and classes are similar may have occurred to you, particularly when you saw in Chapter 6 how you can use functions in struct types. You ’ ll learn more about this in Chapter 9 .
OOP in Windows Applications
In Chapter 2 , you created a simple Windows application in C#. Windows applications are heavily dependent on OOP techniques, and this sections takes a look at this to illustrate some of the points made in this chapter. The following Try It Out enables you to work through a simple example.
Try It Out : Objects in Action
1. Create a new Windows application called Ch08Ex01 and save it in the directory C:\BegVCSharp\Chapter08.
2. Add a new Button control using the Toolbox, and position it in the center of Form1, as shown in Figure 8 - 12.
Figure 8-12
3. Double - click on the button to add code for a mouse click. Modify the code that appears as follows:
private void button1_Click(object sender, System.EventArgs e)
{
((Button)sender).Text = “Clicked!”;
Button newButton = new Button();
newButton.Text = “New Button!”;
newButton.Click += new EventHandler(newButton_Click);
Controls.Add(newButton);
}
private void newButton_Click(object sender, System.EventArgs e)
{
((Button)sender).Text = “Clicked!!”;
}
}
4. Run the application. The form is shown in Figure 8 - 13.
Figure 8-13
5. Click the button marked button1. The display changes (see Figure 8 - 14 ).
Figure 8-14
6. Click the button marked New Button! The display changes (see Figure 8 - 15 ).
Figure 8-15
How It Works
By adding just a few lines of code you ’ ve created a Windows application that does something, while at the same time illustrating some OOP techniques in C#. The phrase “ everything ’ s an object ” is even more true when it comes to Windows applications. From the form that runs to the controls on the form, you need to make use of OOP techniques all the time. This example has highlighted some of the concepts you looked at earlier in this chapter to show how everything fits together.
The first thing you did in your application was add a new button to the Form1 form. This button is an object, called Button. Next, by double - clicking the button, you added an event handler to listen for the Clickevent that the Buttonobject generates. This event handler is added into the code for the Formobject that encapsulates your application, as a private method:
private void button1_Click(object sender, System.EventArgs e)
{
}
This code uses the C# keyword private as a qualifier. Don ’ t worry too much about this for now; the next chapter explains the C# code required for the OOP techniques you ’ ve seen in this chapter.
The first line of code you added changes the text on the button that is clicked. This makes use of polymorphism, described earlier in this chapter. The Button object representing the button that you click is sent to the event handler as an object parameter, which you cast into a Buttontype (this is possible because the Button object inherits from System.Object, which is the .NET class that object is an alias for). You then change the Text property of the object to change the text displayed:
((Button)sender).Text = “Clicked!”;
Next, you create a new Buttonobject with the new keyword (note that namespaces are set up in this project to enable this simple syntax; otherwise, you ’ d need to use the fully qualified name of this object, System.Windows.Forms.Button):
Button newButton = new Button();
newButton.Text = “New Button!”;
Elsewhere in the code a new event handler is added, which you use to respond to the Clickevent generated by the new button:
private void newButton_Click(object sender, System.EventArgs e)
{
((Button)sender).Text = “Clicked!!”;
}
You then register this event handler as a listener for the Clickevent, using overloaded operator syntax. Along the way, you create a new EventHandler object using a nondefault constructor, with the name of the new event handler function:
newButton.Click += new EventHandler(newButton_Click);
Finally, you make use of the Controls property. This property is an object representing a collection of all the controls on your form, and you use its Add()method to add your new button to the form:
Controls.Add(newButton);
The Controls property illustrates that properties don ’ t have to be simple types such as strings or integers but can be any kind of object. This short example used almost all the techniques introduced in this chapter. As you can see, OOP programming needn ’ t be complicated — it just requires a different point of view to get right.
Summary
This chapter has presented a full description of object - oriented techniques. You have worked through this in the context of C# programming, but this has mainly been illustrative. The vast majority of this chapter is relevant to OOP in any language.
You first covered the basics, such as what is meant by the term object and how an object is an instance of a class. Next, you learned how objects can have various members, such as fields, properties, and methods. These members can have restricted accessibility, and you learned what is meant by public and private members. Later, you saw that members can also be protected, as well as being virtual and abstract (where abstract methods are only permissible for abstract classes). You also learned the difference between static (shared) and instance members, and why you might want to use static classes.
Next, you took a quick look at the life cycle of an object, including how constructors are used in object creation and destructors are used in object deletion. Later, after examining groups of members in interfaces, you looked at more advanced object destruction with disposable objects supporting the IDisposableinterface.
Most of the remainder of the chapter covered the features of OOP, many of which you ’ ll explore in more depth in the chapters that follow. You looked at inheritance, whereby classes inherit from base classes; two versions of polymorphism, through base classes and shared interfaces; and how objects can be used to contain one or more other objects (through containment and collections). Finally, you saw how operator overloading can be used to simplify the syntax of object usage and how objects often raise events.
The last part of this chapter demonstrated much of the theory in this chapter, using a Windows application example. The next chapter looks at defining classes using C#.
Exercises
1. Which of the following are real levels of accessibility in OOP?
a. Friend
b. Public
c. Secure
d. Private
e. Protected
f. Loose
g. Wildcard
2. “ You must call the destructor of an object manually or it will waste memory. ” True or false?
3. Do you need to create an object to call a static method of its class?
4. Draw a UML diagram similar to the ones shown in this chapter for the following classes and interface:
. An abstract class called HotDrinkthat has the methods Drink, AddMilk, and AddSugar, and the properties Milkand Sugar.
. An interface called ICupthat has the methods Refilland Wash, and the properties Color and Volume.
. A class called CupOfCoffee that derives from HotDrink, supports the ICupinterface, and has the additional property BeanType.
. A class called CupOfTea that derives from HotDrink, supports the ICupinterface, and has the additional property LeafType.
5. Write some code for a function that will accept either of the two cup objects in the preceding example as a parameter. The function should call the AddMilk, Drink, and Washmethods for any cup object it is passed.