Object-Oriented Programming


As stated in the preceding section, it is beyond the scope of this chapter to teach you all there is to know about good class design and OOD. The goal of this chapter is to show you how to implement your class hierarchies in C#. This section shows you how to create simple classes, how to implement the appropriate member security, how to create an inheritance tree, how to use polymorphism, and finally how to implement interfaces.

Creating Simple Classes

All classes begin with the basic class declaration, which defines the name of the class, as shown in the following snippet:

public class Vehicle { } 


This is the most basic class definition. It will create a new class named Vehicle with no members and no methods. The class will be visible and available to be instantiated by any other .NET code because of the use of the public keyword.

Within the class definition itself, you can declare members that belong to the class itself, such as private members for storing internal data, properties, or methods. If you are following along inside Visual Studio, add a few member declarations for storing private data, as shown in the following example:

public class Vehicle {     private int numWheels;     private int mpg; } 


Now you've got two members: one for storing the number of wheels on the vehicle, and one for storing the miles-per-gallon (mpg) rating of the vehicle. You'll see how to handle member visibility (private, public, and so on) in the next section.

Next, let's add an operation, or method, to the class:

public void PrintWheels() {   Console.WriteLine(     string.Format("This vehicle has {0} wheels.", numWheels)); } 


This method will display the number of wheels on the vehicle when the method is invoked. You invoke this method on an object instance as shown in the following example:

myVehicle.PrintWheels(); 


There are also special types of methods called Constructors, which are automatically invoked when the class is instantiated. For example, you can create a class that will automatically set the number of wheels to four whenever an instance of that class is created by using the following constructor:

public Vehicle() {     numWheels = 4; } 


This will set the private member numWheels to 4 whenever an instance of the Vehicle class is instantiated.

Finally, you can add some properties that allow code to access the private members numWheels and mpg, as shown in the following example:

public int Wheels {   get { return numWheels; }   set { numWheels = value; } } public int MPG {   get { return mpg; }   set { mpg = value; } } 


This is nowhere near a complete coverage of class building in C#. You can learn quite a bit about classes and object-oriented programming within C# simply by reading through this book.

Handling Member Visibility

C# enables you to control the visibility and access of classes and their members. You can prevent code from another assembly from seeing your code, or you can even limit the visibility so that only descendants (you will learn more about inheritance in the next subsection, "Using Object Inheritance") can access specific members. Also, new to C#, you can individually control the accessibility level of the get and set accessors of individual class properties.

You control the accessibility of members through accessibility keywords, such as public, private, and so on. Table 5.1 gives you a complete listing of accessibility keywords and their meanings, as well as the types of code entities to which the keywords apply.

Table 5.1. Member Visibility Levels

Visibility Keyword

Description

public

This member is accessible from both inside the assembly and outside the assembly, as well as by members inside and outside the class.

private

This member is accessible only from code within the class definition itself.

protected

This member is accessible only to derived types, both inside and outside the assembly.

internal

This member is accessible by any code within the assembly.

public protected

This member is accessible by all code within the assembly, but only by derived types outside the assembly.

private protected

This member is protected within the assembly (accessible only to derived types), but is inaccessible outside the assembly.


The following lines of code are examples of the various ways in which accessibility keywords can be applied to classes and individual members:

using System; using System.Collections.Generic; using System.Text; namespace Classes {     // this class is only accessible to code within the assembly     internal class Accessibility     {         // accessible to derivative classes, inside and outside         // this assembly         protected int someNumber;         // accessible only to this class         private int anotherNumber;         // accessible to all code, inside and outside         // this assembly         public string SomeString; // will appear as a public field on the class         public int SomeNumber         {             get { return someNumber; }             protected set { someNumber = value; }         }     } } 


With the table of accessibility levels in hand, the preceding code should make perfect sense. One thing to note, however, is the use of the keyword protected on the public property SomeNumber. This is a new feature in C# 2.0, and it allows you to differentiate the accessibility of one property accessor from another. For instance, in the preceding example, all code is allowed to retrieve the value of SomeNumber, but only derivative classes (inside or outside the assembly) are allowed to use the property to modify the value. This allows finer-grained control over what other code (possibly even code you or your team didn't write) can do with your classes.

Using Object Inheritance

Object inheritance is the concept that allows one class to inherit attributes and operations from a parent class. In the case of the .NET Framework, a class can only have one parent. This is referred to as single inheritance. Languages such as C++ allow for multiple inheritance, which allows a single child class to inherit from multiple parents at the same time.

Inheritance is an extremely useful and powerful concept within the .NET Framework and within C# itself. You see inheritance used over and over in the base classes provided with the .NET Framework. For example, in Windows Forms, a ListBox control inherits from the ListControl class, which in turn inherits from the Control class, which in turn inherits from the Component class, which in turn inherits from the MarshalByRefObject class, which in turn inherits from the class from which all classes in the .NET Framework are derived, System.Object.

By allowing for rich object hierarchies, developers can realistically model business, data, and user interface scenarios in a truly object-oriented fashion, giving them rapid development ability and high code reuse.

The code in Listing 5.1 illustrates the use of inheritance and how the different members of classes in an object hierarchy are handled. There are two keywords that you need to familiarize yourself with when dealing with inheritance:

  • new This keyword indicates to the compiler that the member you are defining on a derived class provides a new, alternate implementation than the member defined on the base class.

  • override This keyword also indicates to the compiler that the member being defined provides a new implementation. The difference between override and new is that override no longer allows access to the inherited implementation. This difference will become clearer in the discussion on polymorphism.

Listing 5.1. The Animal, Cat, and Lion Classes Illustrating Object Inheritance

using System; using System.Collections.Generic; using System.Text; namespace Inheritance {     public abstract class Animal     {         private string color;         public Animal()         {             color = "Blue";         }         public virtual void MakeNoise()         {             Console.WriteLine("Like, Roar, or something!");         }         public string Color         {             get { return color; }             set { color = value; }         }     }     public class Cat : Animal     {         public Cat()             : base()         {             Color = "Black";         }         public new void MakeNoise()         {             Console.WriteLine("Meow, already.");         }     }     public class Lion : Cat     {         public Lion()             : base()         {             Color = "Yellow";         }         public new void MakeNoise()         {             Console.WriteLine("ROOooWWRrrrRR!!");         }     } } 

The first thing you see is an abstract class. An abstract class is essentially the concept of a class. It provides a standard set of code from which child classes can inherit. The difference between an abstract class and a regular parent class is that an abstract class cannot be instantiated. It is there purely to provide inheritable code. The Animal class sets up a default color for all child classes, as well as providing a default noise that child classes will inherit if they don't modify it. The virtual keyword allows that method to be superseded by child classes if the creator of the child class so decides.

The Cat class inherits from the Animal class as indicated by the following line:

public class Cat : Animal 


The Cat class sets its own color in the constructor, and provides its own implementation of MakeNoise without completely hiding the original parent's implementation.

The Lion class inherits from the Cat class, providing its own color and its own implementation of MakeNoise. If we execute the following lines of code:

Cat c = new Cat(); c.MakeNoise(); Lion l = new Lion(); l.MakeNoise(); 


The output will be as follows:

Meow, already. ROOooWWRrrrRR!! 


The other thing to note is that all of the derivative classes have access to the members of the parent, so long as those members are protected or even more accessible.

Introduction to Polymorphism

Polymorphism is the ability to treat one object instance as if it were an instance of a different member of the inheritance tree. For example, using polymorphism and explicit typecasting, you can treat a Lion class as though it were a Cat class (because Lion inherits from Cat), and you can treat a Lion class as though it were an Animal class (because Lion inherits from Cat, which in turn inherits from Animal).

The real power of polymorphism comes from the fact that, through various keywords, you can control what members are used by which classes in the object hierarchy, even if the object is typecast to an ancestor. For example, we can create a class called ReallyBigLion that uses the override keyword that will provide its own implementation of MakeNoise(), even if the object is typecast to Lion instead of ReallyBigLion:

public class ReallyBigLion : Lion {     public ReallyBigLion()         : base()     {         Color = "BrightRed";     }     public override void MakeNoise()     {         Console.WriteLine("REALLY BIG ROAR.");     } } 


Now, when we run the following code to demonstrate polymorphism:

Cat polyCat = (Cat)l; polyCat.MakeNoise(); ReallyBigLion rbl = new ReallyBigLion(); rbl.MakeNoise(); Lion polyLion = (Lion)rbl; polyLion.MakeNoise(); polyCat = (Cat)rbl; polyCat.MakeNoise(); 


We will get the following output:

Meow, already. REALLY BIG ROAR. REALLY BIG ROAR. Meow, already. 


What's interesting to note here is that when you typecast ReallyBigLion to Lion, the MakeNoise() method that is invoked is the one from ReallyBigLion because Lion used the virtual keyword and ReallyBigLion used the override keyword. However, when you typecast ReallyBigLion to Cat, the MakeNoise() implementation that is invoked belongs to the Cat class because the Cat class did not allow its method definition to be superseded with a keyword. This also illustrates how you can create a class and guarantee that no one will tamper with your implementation of a method if you don't want them to.

Implementing Interfaces

As you read earlier in this chapter, interfaces are essentially contracts that define required attributes and operations for classes that implement those interfaces. Rather than being a hierarchical inheritance model, interfaces function more as a list of requirements to be enforced on a class. However, one interface can inherit from another interface, the end result being that the class that implements the child interface must adhere to all requirements as defined by both the child interface and the parent interface.

The following interface defines a list of requirements to which all implementing classes must conform:

interface ICreature {   int NumLegs { get; set; }   string Color { get; set; }   void MakeNoise(); } 


Note that there are no accessibility keywords. Interfaces do not define the accessibility of a member, only the member's presence. The interface defined in the preceding example requires that implementing classes define both the get and set accessors for the NumLegs and Color properties, as well as a void method called MakeNoise().

Thankfully, Visual Studio makes it extremely easy to implement interfaces. To see this in action, create a new class file called Creature, and then type the following:

public class Creature : ICreature 


After you type ICreature, you will be able to use the related smart tag to automatically implement empty stubs that implement the interface as defined. For big interfaces, this can save you a lot of typing and reduce a lot of typo-related errors. The resulting class is shown in Listing 5.2.

Listing 5.2. The Creature Class, Autofilled with Empty ICreature Implementation Stubs

using System; using System.Collections.Generic; using System.Text; namespace Inheritance { class Creature : ICreature { #region ICreature Members public int NumLegs {     get     {         throw new Exception("The method or operation is not implemented.");     }     set     {         throw new Exception("The method or operation is not implemented.");     } } public string Color {     get     {         throw new Exception("The method or operation is not implemented.");     }     set     {         throw new Exception("The method or operation is not implemented.");     } } public void MakeNoise() {     throw new Exception("The method or operation is not implemented."); } #endregion } } 

With the empty stubs in place, all you have to do is remove the exceptions created by Visual Studio and replace them with real code, and your class will have satisfied the requirements of the interface.

Note that one of the basic reasons for an interface is that it provides one common type to which you can typecast a variety of object instances. For example, you might have 10 different object hierarchies that consist of Predators, Omnivores, Herbivores, and so on. However, all of these object hierarchies all implement the ICreature interface, so you can create methods that look like this:

public void DisplayCreature(ICreature creature) {     Console.WriteLine("The creature has " + creature.NumLegs.ToString() + "legs."); } 


You can also typecast an object instance to any interface that the object implements with the C# typecast operator, as shown in the following example :

SnowOwl owl = new SnowOwl(); ICreature creature = (ICreature)owl; DisplayCreature(creature); 


You can also pass the object instance to a method that is expecting just the interface, and C# will do the type conversion for you:

SnowOwl owl = new SnowOwl(); DisplayCreature(owl); 


The only time this will cause a problem is if you pass the method an object that doesn't implement the required interface. In this case you will get a type conversion exception.



Microsoft Visual C# 2005 Unleashed
Microsoft Visual C# 2005 Unleashed
ISBN: 0672327767
EAN: 2147483647
Year: 2004
Pages: 298

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net