Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

CSharp Bible (2002) [eng]

.pdf
Скачиваний:
46
Добавлен:
16.08.2013
Размер:
4.29 Mб
Скачать

is that the base class does not have enough information to calculate an area. Each shape calculates its area using a different formula.

What you can do is define an abstract method in the Shape base class. Abstract methods do not provide an implementation of their own, but provide a method signature that derived classes must implement. Abstract methods say, "I don't know how to implement this method, but my derived classes will, so make sure that they implement it with the parameters and the return code that I specify." The following snippet shows how you can declare an abstract method in the Shape class.

abstract class Shape

{

public abstract double GetArea();

}

Note Abstract classes use the abstract keyword. They do not have a method body; a semicolon follows the parameter list instead.

Abstract classes are also, by definition, virtual methods, and must be overridden by derived classes using the override keyword:

class Square : Shape

{

public override double GetArea()

{

// implement area calculation

}

}

Classes containing at least one abstract method are called abstract classes, and must include the abstract keyword before the class keyword. If you forget the abstract keyword when defining the class, you get an error from the C# compiler:

error CS0513: 'Shape.GetArea ()' is abstract but it is contained in nonabstract class 'Shape'

The C# compiler doesn't allow you to create objects from abstract classes. If you try to create an object from an abstract class, the C# compiler issues an error:

error CS0144: Cannot create an instance of the abstract class or interface 'Shape'

Abstract classes are used most often to create a common base class to a set of classes. This enables you to use polymorphism when storing derived classes in a collection of some sort. You saw this in action in Chapter 8, " Writing Object-Oriented Code," with the Zoo example.

Base Classes: Working with Inherited Properties and Indexers

With C#, you can mark properties and indexers in base and derived classes as virtual, override, and abstract, just like methods.

Note Indexers are roughly equivalent to the overloaded [] operator and are declared using the

this[] syntax. You use them where you want to access a class property in an array-like manner.

Virtual and override properties and indexers work just like virtual and override properties. Properties and indexers may be marked as virtual in a base class and overridden in a derived class.

Base classes may define abstract properties and indexers, which do not have an implementation of their own. Base classes containing at least one abstract property or indexer must be marked as an abstract class. Abstract properties and indexers must be overridden in a base class.

Using the base keyword

The C# language provides the base keyword so that derived classes can access functionality in their base class. You can use the keyword base to call a base class constructor when an object of a derived class is created. To call a base class constructor, follow the derived class constructor with a colon, the base keyword, and then the parameters to be passed to the base class.

Listing 11-6 shows how this works. It adds constructors for the Point2D and Point3D classes, and the Point3D constructor calls the constructor of its base class.

Listing 11-6: Calling Base Class Constructors

class Point2D

{

public int X; public int Y;

public Point2D(int X, int Y)

{

this.X = X; this.Y = Y;

}

public virtual void PrintToConsole()

{

System.Console.WriteLine("({0}, {1})", X, Y);

}

}

class Point3D : Point2D

{

public int Z;

public Point3D(int X, int Y, int Z) : base(X, Y)

{

this.Z = Z;

}

public override void PrintToConsole()

{

System.Console.WriteLine("({0}, {1}, {2})", X, Y, Z);

}

}

class MyMainClass

{

public static void Main()

{

Point2D My2DPoint = new Point2D(100, 200); Point3D My3DPoint = new Point3D(150, 250, 350);

My2DPoint.PrintToConsole();

My3DPoint.PrintToConsole();

}

}

The constructor for the Point2D class sets the class's X and Y fields using the two integers passed to the constructor. The constructor for the Point3D class accepts three parameters. The first two parameters are passed to the base class's constructor using the base keyword, and the third is used to set the derived class's Z field.

Accessing base class fields with the base keyword

You can also use the base keyword to access members in the base class. In your derived class, you can work with a base class member by prefixing the member's name with the keyword base and a period. You can access base class fields using the following syntax:

base.X = 100;

You can also invoke base class methods using this syntax:

base.PrintToConsole();

Sealed Classes

If you do not want code to derive from your class, you can mark your class with the sealed keyword. You cannot derive a class from a sealed class.

You can specify a sealed class by placing the keyword sealed before the class keyword as follows:

sealed class MySealedClass

If you try to derive a class from a sealed class, the C# compiler issues an error:

error CS0509: 'Point3D' : cannot inherit from sealed class 'Point2D'

Containment and Delegation

Whereas inheritance is an IS-A relationship, containment is a HAS-A relationship. A Burmese IS A cat (so you might want to inherit your Burmese class from your generic Cat class); whereas a Car HAS 4 tires (so your Car class may contain 4 Tire objects). The

interesting aspect of containment is that you can use it as a surrogate for inheritance. The main drawback to using containment instead of inheritance is that you lose the benefits of polymorphism. However, you get all the advantages of code re-use.

In C#, there are two common instances in which you have little choice but to use containment instead of inheritance: when dealing with multiple inheritance and when dealing with sealed classes. An example follows illustrating how this technique works. In addition, you will see polymorphism in action.

Suppose you have an AlarmClock class and a Radio class as shown in the following snippet, and you want to create a ClockRadio class combining these two classes. If C# supported multiple inheritance, you could have ClockRadio inherit from both the AlarmClock class and the Radio class. You could then add a radioAlarm Boolean variable to determine whether the buzzer or the radio goes off and override SoundAlarm() to use this variable. Alas, C# does not support multiple inheritance. Not to worry; you can use containment instead of inheritance and still get all the benefits of code re-use. Note how this works, step by step:

class Radio

{

protected bool on_off;

public void On()

{

if (!on_off) Console.WriteLine("Radio is now on!"); on_off = true;

}

public void Off()

{

if (on_off) Console.WriteLine("Radio is now off!"); on_off = false;

}

}

class AlarmClock

{

private int currentTime; private int alarmTime;

private void SoundAlarm()

{

Console.WriteLine("Buzz!");

}

public void Run()

{

for (int currTime = 0; currTime < 43200; currTime++)

{

SetCurrentTime(currTime);

if (GetCurrentTime() == GetAlarmTime())

{

Console.WriteLine("Current Time = {0}!", currentTime);

SoundAlarm();

break;

}

}

}

public int GetCurrentTime()

{

return currentTime;

}

public void SetCurrentTime(int aTime)

{

currentTime =

aTime;

}

public int GetAlarmTime()

{

return alarmTime;

}

public void SetAlarmTime(int aTime)

{

alarmTime =

aTime;

}

 

}

 

Because you want to override the SoundAlarm() method in AlarmClock, it is best to select to inherit ClockRadio from AlarmClock. As you will see later, this requires a minor change in the AlarmClock implementation. However, as a reward, you will nicely get the benefit of polymorphism. Now that you have selected a base class, you cannot inherit from Radio. Instead of inheriting, you will create a private Radio member variable inside the ClockRadio class. You create the private member in the ClockRadio constructor and delegate the work for the RadioOn() and RadioOff() methods to this private member. You have some extra work to do, but you get all the benefits of code re-use. Whenever the implementation of the Radio class changes (for example, because of bug fixes), your AlarmClock class will automatically incorporate these changes. One inconvenience of the containment/ delegation approach is that new functionality in the contained class (e.g., adding new methods to set the volume) requires changes to the containing class in order to delegate this new functionality to the private member.

class ClockRadio : AlarmClock

{

private Radio radio;

// Declare other member variables...

public ClockRadio()

{

radio = new Radio();

// Set other member variables...

}

//---------- Delegate to Radio ----------

public void RadioOn()

{

radio.On();

}

public void RadioOff()

{

radio.Off();

}

}

You have now implemented full radio functionality using the containment/delegation pattern. It's time to add the AlarmClock functionality. First, quickly add a radioAlarm private variable to determine whether the radio should start blaring or the buzzer should sound when the alarm goes off:

class ClockRadio : AlarmClock

{

private bool radioAlarm;

// Declare other member variables...

public ClockRadio()

{

radioAlarm = false;

// Set other member variables...

}

//---------- New ClockRadio functionality ----------

public void SetRadioAlarm(bool useRadio)

{

radioAlarm = useRadio;

}

}

Because you want to override the SoundAlarm() function in AlarmClock, you need to change the SoundAlarm() method declaration to be protected. Furthermore, because you will want polymorphic behavior in the Run() function, you need to make this method virtual:

class AlarmClock

{

private int currentTime; private int alarmTime;

protected virtual void SoundAlarm()

{

Console.WriteLine("Buzz!");

}

// Other Methods...

}

Overriding SoundAlarm() in AlarmClock is straightforward. Depending on the radioAlarm setting, you either turn the radio on or call the SoundAlarm() method in the base class to sound the buzzer, as follows:

class ClockRadio : AlarmClock

{

private Radio radio; private bool radioAlarm;

//---------- Overridde AlarmClock ----------

protected override void SoundAlarm()

{

if (radioAlarm)

{

RadioOn();

}

else

{

base.SoundAlarm();

}

}

// Other Methods...

}

That's basically it! Something very interesting is happening inside the Run() method of the AlarmClock class (shown in the following code snippet): the polymorphic behavior alluded to previously. The ClockRadio class inherits this method from its base class and does not override it. This Run() method can therefore be executed from either an AlarmClock object or a RadioClock object. Because we declared the SoundAlarm() to be virtual, C# is smart enough to call the appropriate SoundAlarm() depending on which class is invoking the Run() method.

class AlarmClock

{

private int currentTime; private int alarmTime;

public void Run()

{

for (int currTime = 0; currTime < 43200; currTime++)

{

SetCurrentTime(currTime);

if (GetCurrentTime() == GetAlarmTime())

{

Console.WriteLine("Current Time = {0}!", currentTime);

SoundAlarm();

break;

}

}

}

// Other Methods...

}

This example clearly highlights one of the major strengths of inheritance: polymorphism. In addition, when you add new public (or protected) methods to the base class, they become automatically available in the derived class. Listing 11-7 is the full listing with a sample main() method so you can experiment with this sample.

Listing 11-7: Multiple Inheritance Can Be Simulated Using Containment

using System;

namespace Containment

{

class Radio

{

protected bool on_off;

public void On()

{

if (!on_off) Console.WriteLine("Radio is now on!"); on_off = true;

}

public void Off()

{

if (on_off) Console.WriteLine("Radio is now off!"); on_off = false;

}

}

class AlarmClock

{

private int currentTime; private int alarmTime;

protected virtual void SoundAlarm()

{

Console.WriteLine("Buzz!");

}

public void Run()

{

for (int currTime = 0; currTime < 43200; currTime++)

{

SetCurrentTime(currTime);

if (GetCurrentTime() == GetAlarmTime())

{

Console.WriteLine("Current Time = {0}!",

currentTime);

SoundAlarm();

break;

}

}

}

public int GetCurrentTime()

{

return currentTime;

}

public void SetCurrentTime(int aTime)

{

currentTime = aTime;

}

public int GetAlarmTime()

{

return alarmTime;

}

public void SetAlarmTime(int aTime)

{

alarmTime =

aTime;

}

}

class ClockRadio : AlarmClock

{

private Radio radio; private bool radioAlarm;

public ClockRadio()

{

radio = new Radio(); radioAlarm = false;

}

//---------- Delegate to Radio ----------

public void RadioOn()

{

radio.On();

}

public void RadioOff()

{

radio.Off();

}

//---------- Overridde AlarmClock ----------

protected override void SoundAlarm()

{

if (radioAlarm)

{

RadioOn();

}

else

{

base.SoundAlarm();

}

}

//---------- New ClockRadio functionality ----------

public void SetRadioAlarm(bool useRadio)

{

radioAlarm = useRadio;

}

}

class ContInh

{

static int Main(string[] args)

{

ClockRadio clockRadio; clockRadio = new ClockRadio();

clockRadio.SetRadioAlarm(true);

clockRadio.SetAlarmTime(100);

clockRadio.Run();

// wait for user to acknowledge the results Console.WriteLine("Hit Enter to terminate..."); Console.Read();

return 0;

}

}

}

The .NET Object Class

All the classes in C# end up deriving from a class built into the .NET Framework called object. If you write a class in C# and do not define a base class for it, the C# compiler silently

derives it from object. Suppose that you write a C# class declaration without a class declaration, as follows:

class Point2D

This is equivalent to deriving your class from the .NET base class System.Object:

class Point2D : System.Object

The C# keyword object can be used as an alias for the System.Object identifier:

class Point2D : object

If you do derive from a base class, just remember that your base class either inherits from object or inherits from another base class that inherits from object. Eventually, your class inheritance hierarchy will include the .NET object class.

Thanks to the rules of inheritance in C#, the functionality of the .NET object class is available to all classes in C#. The .NET object class carries the following methods:

public virtual bool Equals(object obj): Compares one object to another object and returns true if the objects are equal and false otherwise. This method is marked as virtual, which means that you can override it in your C# classes. You may want to override this method to compare the state of two objects of your class. If the objects have the same values for the fields, you can return true; you can return false if the values differ.

public virtual int GetHashCode(): Calculates a hash code for the object. This method is marked as virtual, which means that you can override it in your C# classes. Collection classes in .NET may call this method to generate a hash code to aid in searching and sorting, and your classes can override this method to generate a hash code that makes sense for the class.

Note Hash code is a unique key for the specified object.

public Type GetType(): Returns an object of a .NET class called Type that provides information about the current class. This method is not marked as virtual, which means that you cannot override it in your C# classes.

public virtual string ToString(): Returns a string representation of your object. This method is marked as virtual, which means that you can override it in your C# classes. An object's ToString() method is called when .NET methods such as System.Console.WriteLine() need to convert a variable into a string. You can override this method to return a string more appropriate for representing your class's state. You may for example want to add the proper currency sign in front of the string representation of your Money class.

protected virtual void Finalize(): May (or may not) becalled when the Common Language Runtime's garbage collector destroys the object. This method is marked as virtual, which means that you can override it in your C# classes. This method is also marked as protected, which means that it can only be called from within the class or a derived class, and cannot be called from outside the class hierarchy. The .NET object implementation of Finalize() does nothing, but you can implement it if you wish. You can also write a destructor for your class, which achieves the same effect (but be

Соседние файлы в предмете Программирование на C++