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

C# Bible - Jeff Ferguson, Brian Patterson, Jason Beres

.pdf
Скачиваний:
64
Добавлен:
24.05.2014
Размер:
4.21 Mб
Скачать

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

careful using this). In fact, the C# compiler translates your destructor code into an overridden Finalize() method.

protected object MemberwiseClone(): Creates a clone of the object, populates the clone with the same state as the current object, and returns the cloned object. This method is not marked as virtual, which means that you cannot 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.

Structures in C# cannot have explicitly defined base classes, but they do implicitly inherit from the object base class. All the behavior of the object class is available to structures in C# as well as classes.

Using Boxing and Unboxing to Convert to and from the Object Type

Because all classes and structures ultimately derive from the .NET object type, it is used quite often in parameter lists when the method needs to be flexible regarding the data it receives.

For example, consider the System.Consle.WriteLine() method used throughout this book. This same method has been used to write strings, integers, and doubles to the console without using any casting operators. In Listing 11-4, it prints a string with placeholders, and the placeholders are replaced with the values of the parameters supplied to it. How does this actually work? How does System.Console .WriteLine() know what types you'll be passing into it?

The answer is that it can't know. Microsoft built the System.Console.WriteLine() method long before you worked on Listing 11-4, so they couldn't possibly know what types of data you would pass into it. Microsoft implemented a System.Console.WriteLine() method with the following signature:

public static void WriteLine(string format, params object[] arg);

The first parameter is the string to be output, and the second parameter is a parameter array holding a number of items calculated when the code is compiled. But what is the type of the parameter array? The parameter array is of type object. Consider this call to WriteLine():

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

The C# compiler turns the X and Y parameters into a parameter array for us and calls WriteLine(). The X and Y parameters are of type integer, which, as you've already seen, is an alias for a structure called System.Int32. Because C# structures inherit from the .NET object type, these variables inherit from the object type and can be used in the parameter array.

Literals, which are discussed in Chapter 4, are a trickier issue. Instead of using objects, you can just as easily write the following code:

System.Console.WriteLine("({0}, {1})", 100, 200);

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