Implementation Inheritance


If you want to declare that a class derives from another class, use the following syntax:

  class MyDerivedClass : MyBaseClass {    // functions and data members here } 

Tip 

This syntax is very similar to C++ and Java syntax. However, C++ programmers, who will be used to the concepts of public and private inheritance, should note that C# does not support private inheritance, hence the absence of a public or private qualifier on the base class name. Supporting private inheritance would have complicated the language for very little gain. In practice, private inheritance is used extremely rarely in C++ anyway.

If a class (or a struct) also derives from interfaces, the list of base class and interfaces is separated by commas:

  public class MyDerivedClass : MyBaseClass, IInterface1, IInterface2 {          // etc.  } 

For a struct, the syntax is as follows:

  public struct MyDerivedStruct : IInterface1, IInterface2 {          // etc.  } 

If you do not specify a base class in a class definition, the C# compiler will assume that System.Object is the base class. Hence, the following two pieces of code yield the same result:

  class MyClass : Object  // derives from System.Object {    // etc. } 

and

  class MyClass   // derives from System.Object {    // etc. } 

For the sake of simplicity, the second form is more common.

Because C# supports the object keyword, which serves as a pseudonym for the System.Object class, you can also write:

  class MyClass : object   // derives from System.Object {    // etc. } 

If you want to reference the Object class, use the object keyword, which is recognized by intelligent editors such as Visual Studio .NET and thus facilitates editing your code.

Virtual Methods

By declaring a base class function as virtual, you allow the function to be overridden in any derived classes:

  class MyBaseClass {    public virtual string VirtualMethod()    {       return "This method is virtual and defined in MyBaseClass";    } } 

It is also permitted to declare a property as virtual. For a virtual or overridden property, the syntax is the same as for a nonvirtual property, with the exception of the keyword virtual, which is added to the definition. The syntax looks like this:

 public virtual string ForeName {    get { return fName;}    set { fName = value;} } private string foreName;

For simplicity, the following discussion focuses mainly on methods, but it applies equally well to properties.

The concepts behind virtual functions in C# are identical to standard OOP concepts. You can override a virtual function in a derived class, and when the method is called, the appropriate method for the type of object is invoked. In C#, functions are not virtual by default but (aside from constructors) can be explicitly declared as virtual. This follows the C++ methodology: for performance reasons, functions are not virtual unless indicated. In Java, by contrast, all functions are virtual. C# differs from C++ syntax, however, because it requires you to declare when a derived class’s function overrides another function, using the override keyword:

  class MyDerivedClass : MyBaseClass {    public override string VirtualMethod()    {       return "This method is an override defined in MyDerivedClass.";    } } 

This syntax for method overriding removes potential runtime bugs that can easily occur in C++, when a method signature in a derived class unintentionally differs slightly from the base version, resulting in the method failing to override the base version. In C# this is picked up as a compile-time error, because the compiler would see a function marked as override but no base method for it to override.

Neither member fields nor static functions can be declared as virtual. The concept simply wouldn’t make sense for any class member other than an instance function member.

Hiding Methods

If a method with the same signature is declared in both base and derived classes, but the methods are not declared as virtual and override, respectively, then the derived class version is said to hide the base class version.

In most cases, you would want to override methods rather than hide them; by hiding them you risk calling the “wrong” method for a given class instance. However, as shown in the following example, C# syntax is designed to ensure that the developer is warned at compile time about this potential problem, thus making it safer to hide methods if that is your intention. This also has versioning benefits for developers of class libraries.

Suppose that you have a class called HisBaseClass:

  class HisBaseClass {    // various members } 

At some point in the future you write a derived class that adds some functionality to HisBaseClass. In particular, you add a method called MyGroovyMethod(), which is not present in the base class:

  class MyDerivedClass: HisBaseClass {    public int MyGroovyMethod()    {       // some groovy implementation       return 0;    } } 

One year later, you decide to extend the functionality of the base class. By coincidence, you add a method that is also called MyGroovyMethod() and has the same name and signature as yours, but probably doesn’t do the same thing. When you compile your code using the new version of the base class, you have a potential clash because your program won’t know which method to call. It’s all perfectly legal C#, but because your MyGroovyMethod() is not intended to be related in any way to the base class MyGroovyMethod(), the result is that running this code does not yield the result you want. Fortunately, C# has been designed in such a way that it copes very well when conflicts of this type arise.

In these situations, C# generates a compilation warning. That reminds you to use the new keyword to declare that you intend to hide a method, like this:

  class MyDerivedClass : HisBaseClass {    public new int MyGroovyMethod()    {       // some groovy implementation       return 0;    } } 

However, because your version of MyGroovyMethod() is not declared as new, the compiler will pick up on the fact that it’s hiding a base class method without being instructed to do so and generate a warning (this applies whether or not you declared MyGroovyMethod() as virtual). If you want, you can rename your version of the method. This is the recommended course of action, because it will eliminate future confusion. However, if you decide not to rename your method for whatever reason (for example, if you’ve published your software as a library for other companies, so you can’t change the names of methods), all your existing client code will still run correctly, picking up your version of MyGroovyMethod(). That’s because any existing code that accesses this method must be doing so through a reference to MyDerivedClass (or a further derived class).

Your existing code cannot access this method through a reference to HisBaseClass; it would generate a compilation error when compiled against the earlier version of HisBaseClass. The problem can only happen in client code you have yet to write. C# arranges things so that you get a warning that a potential problem might occur in future code - and you will need to pay attention to this warning, and take care not to attempt to call your version of MyGroovyMethod() through any reference to HisBaseClass in any future code you add. However, all your existing code will still work fine. It may be a subtle point, but it’s quite an impressive example of how C# is able to cope with different versions of classes.

Calling Base Versions of Functions

C# has a special syntax for calling base versions of a method from a derived class: base.<MethodName>(). For example, if you want a method in a derived class to return 90 percent of the value returned by the base class method, you can use the following syntax:

  class CustomerAccount {    public virtual decimal CalculatePrice()    {       // implementation       return 0.0M;    } } class GoldAccount : CustomerAccount {    public override decimal CalculatePrice()    {       return base.CalculatePrice() * 0.9M;    } } 

Java uses a similar syntax, with the exception that Java uses the keyword super rather than base. C++ has no similar keyword but instead requires specification of the class name (CustomerAccount:: CalculatePrice()). Any equivalent to base in C++ would have been ambiguous because C++ supports multiple inheritance.

Note that you can use the base.<MethodName>() syntax to call any method in the base class - you don’t have to call it from inside an override of the same method.

Abstract Classes and Functions

C# allows both classes and functions to be declared as abstract. An abstract class cannot be instantiated, whereas an abstract function does not have an implementation, and must be overridden in any non-abstract derived class. Obviously, an abstract function is automatically virtual (although you don’t need to supply the virtual keyword; doing so results in a syntax error). If any class contains any abstract functions, that class is also abstract and must be declared as such:

  abstract class Building {    public abstract decimal CalculateHeatingCost();   // abstract method } 

C++ developers will notice some syntactical differences in C# here. C# does not support the =0 syntax to declare abstract functions. In C#, this syntax would be misleading because =<value> is allowed in member fields in class declarations to supply initial values:

  abstract class Building {    private bool damaged = false;   // field    public abstract decimal CalculateHeatingCost();   // abstract method } 

Tip 

C++ developers should also note the slightly different terminology: In C++, abstract functions are often described as pure virtual; in the C# world, the only correct term to use is abstract.

Sealed Classes and Methods

C# allows classes and methods to be declared as sealed. In the case of a class, this means that you can’t inherit from that class. In the case of a method, this means that you can’t override that method.

  sealed class FinalClass {    // etc } class DerivedClass : FinalClass       // wrong. Will give compilation error {    // etc } 

Tip 

Java developers will recognize sealed as the C# equivalent of Java’s final.

The most likely situation in which you’ll mark a class or method as sealed will be if the class or method is internal to the operation of the library, class, or other classes that you are writing, so you are sure that any attempt to override some of its functionality will cause problems. You might also mark a class or method as sealed for commercial reasons, in order to prevent a third party from extending your classes in a manner that is contrary to the licensing agreements. In general, however, you should be careful about marking a class or member as sealed, because by doing so you are severely restricting how it can be used. Even if you don’t think it would be useful to inherit from a class or override a particular member of it, it’s still possible that at some point in the future someone will encounter a situation you hadn’t anticipated in which it is useful to do so. The .NET base class library frequently uses sealed classes in order to make these classes inaccessible to third-party developers who might want to derive their own classes from them. For example, string is a sealed class.

Declaring a method as sealed serves a similar purpose as for a class, although you rarely will want to declare a method as sealed.

  class MyClass {    public sealed override void FinalMethod()    {       // etc.    } } class DerivedClass : MyClass {    public override void FinalMethod()      // wrong. Will give compilation error    {    } } 

It does not make sense to use the sealed keyword on a method unless that method is itself an override of another method in some base class. If you are defining a new method and you don’t want anyone else to override it, you would not declare it as virtual in the first place. If, however, you have overridden a base class method, the sealed keyword provides a way of ensuring that the override you supply to a method is a “final” override in the sense that no one else can override it again.

Constructors of Derived Classes

Chapter 3 discusses how constructors can be applied to individual classes. An interesting question arises as to what happens when you start defining your own constructors for classes that are part of a hierarchy, inherited from other classes that may also have custom constructors.

Assume you have not defined any explicit constructors for any of your classes. This means that the compiler supplies default zeroing-out constructors for all your classes. There is actually quite a lot going on under the hood when that happens, but the compiler is able to arrange it so that things work out nicely throughout the class hierarchy and every field in every class gets initialized to whatever its default value is. When you add a constructor of your own, however, you are effectively taking control of construction. This has implications right down through the hierarchy of derived classes, and you have to make sure that you don’t inadvertently do anything to prevent construction through the hierarchy from taking place smoothly.

You might be wondering why there is any special problem with derived classes. The reason is that when you create an instance of a derived class, there is actually more than one constructor at work. The constructor of the class you instantiate isn’t by itself sufficient to initialize the class - the constructors of the base classes must also be called. That’s why we’ve been talking about construction through the hierarchy.

To see why base class constructors must be called, you’re going to develop an example based on a cell phone company called MortimerPhones. The example contains an abstract base class, GenericCustomer, which represents any customer. There is also a (non-abstract) class, Nevermore60Customer, that represents any customer on a particular rate called the Nevermore60 rate. All customers have a name, represented by a private field. Under the Nevermore60 rate, the first few minutes of the customer’s call time are charged at a higher rate, necessitating the need for the field highCostMinutesUsed, which details how many of these higher-cost minutes each customer has used up. The class definitions look like this:

  abstract class GenericCustomer {    private string name;    // lots of other methods etc. } class Nevermore60Customer : GenericCustomer {    private uint highCostMinutesUsed;    // other methods etc. } 

We won’t worry about what other methods might be implemented in these classes, because we are concentrating solely on the construction process here. And if you download the sample code for this chapter, you’ll find that the class definitions include only the constructors.

Take a look at what happens when you use the new operator to instantiate a Nevermore60Customer:

  GenericCustomer customer = new Nevermore60Customer(); 

Clearly, both of the member fields name and highCostMinutesUsed must be initialized when customer is instantiated. If you don’t supply constructors of your own, but rely simply on the default constructors, then you’d expect name to be initialized to the null reference, and highCostMinutesUsed to zero. Let’s look in a bit more detail at how this actually happens.

The highCostMinutesUsed field presents no problem: the default Nevermore60Customer constructor supplied by the compiler will initialize this field to zero.

What about name? Looking at the class definitions, it’s clear that the Nevermore60Customer constructor can’t initialize this value. This field is declared as private, which means that derived classes don’t have access to it. So, the default Nevermore60Customer constructor simply won’t know that this field exists. The only code items that have that knowledge are other members of GenericCustomer. This means that if name is going to be initialized, that’ll have to be done by some constructor in GenericCustomer. No matter how big your class hierarchy is, this same reasoning applies right down to the ultimate base class, System.Object.

Now that you have an understanding of the issues involved, you can look at what actually happens whenever a derived class is instantiated. Assuming that default constructors are used throughout, the compiler first grabs the constructor of the class it is trying to instantiate, in this case Nevermore60Customer. The first thing that the default Nevermore60Customer constructor does is attempt to run the default constructor for the immediate base class, GenericCustomer. Then the GenericCustomer constructor attempts to run the constructor for its immediate base class; System.Object. System.Object doesn’t have any base classes, so its constructor just executes and returns control to the GenericCustomer constructor. That constructor now executes, initializing name to null, before returning control to the Nevermore60Customer constructor. That constructor in turn executes, initializing highCostMinutesUsed to zero, and exits. At this point, the Nevermore60Customer instance has been successfully constructed and initialized.

The net result of all this is that the constructors are called in order of System.Object first, then progressing down the hierarchy until the compiler reaches the class being instantiated. Notice also that in this process, each constructor handles initialization of the fields in its own class. That’s how it should normally work, and when you start adding your own constructors you should try to stick to that principle.

Notice the order in which this happens. It’s always the base class constructors that get called first. This means that there are no problems with a constructor for a derived class invoking any base class methods, properties, and any other members that it has access to, because it can be confident that the base class has already been constructed and its fields initialized. It also means that if the derived class doesn’t like the way that the base class has been initialized, it can change the initial values of the data, provided that it has access to do so. However, good programming practice almost invariably means you’ll try to prevent that situation from occurring if you can, and you will trust the base class constructor to deal with its own fields.

Now that you know how the process of construction works, you can start fiddling with it by adding your own constructors.

Adding a Constructor in a Hierarchy

We’ll take the simplest case first and see what happens if you simply replace the default constructor somewhere in the hierarchy with another constructor that takes no parameters. Suppose that you decide that you want everyone’s name to be initially set to the string “<no name>” instead of to the null reference. You’d modify the code in GenericCustomer like this:

  public abstract class GenericCustomer {    private string name;    public GenericCustomer()       : base()  // We could omit this line without affecting the compiled code.    {       name = "<no name>";    } 

Adding this code will work fine. Nevermore60Customer still has its default constructor, so the sequence of events described earlier will proceed as before, except that the compiler will use the custom GenericCustomer constructor instead of generating a default one, so the name field will always be initialized to “<no name>” as required.

Notice that in your constructor that you’ve added a call to the base class constructor before the GenericCustomer constructor is executed, using a syntax similar to that used earlier when we discussed how to get different overloads of constructors to call each other. The only difference is that this time you use the base keyword instead of this, to indicate that it’s a constructor to the base class rather than a constructor to the current class you want to call. There are no parameters in the brackets after the base keyword - that’s important because it means you are not passing any parameters to the base constructor, so the compiler will have to look for a parameterless constructor to call. The result of all this is that the compiler will inject code to call the System.Object constructor, just as would happen by default anyway.

In fact, you can omit that line of code and write the following (as was done for most of the constructors so far in the chapter):

  public GenericCustomer() {    name = "<no name>"; } 

If the compiler doesn’t see any reference to another constructor before the opening curly brace, it assumes that you intended to call the base class constructor; this fits in with the way that default constructors work.

The base and this keywords are the only keywords allowed in the line that calls another constructor. Anything else causes a compilation error. Also note that only one other constructor can be specified.

So far, this code works fine. One good way to mess up the progression through the hierarchy of constructors, however, is to declare a constructor as private:

  private GenericCustomer() {    name = "<no name>"; } 

If you try this, you’ll find you get an interesting compilation error, which could really throw you if you don’t understand how construction down a hierarchy works:

 'Wrox.ProCSharp.GenericCustomer()' is inaccessible due to its protection level

The interesting thing is that the error occurs not in the GenericCustomer class, but in the derived class, Nevermore60Customer. What’s happened is that the compiler has tried to generate a default constructor for Nevermore60Customer but has not been able to because the default constructor is supposed to invoke the no-parameter GenericCustomer constructor. By declaring that constructor as private, you’ve made it inaccessible to the derived class. A similar error occurs if you supply a constructor to GenericCustomer, which takes parameters, but at the same time you fail to supply a no-parameter constructor. In this case, the compiler will not generate a default constructor for GenericCustomer, so when it tries to generate the default constructors for any derived class, it’ll again find that it can’t because a no-parameter base class constructor is not available. A workaround would be to add your own constructors to the derived classes, even if you don’t actually need to do anything in these constructors, so that the compiler doesn’t try to generate any default constructor for them.

Now that you have all the theoretical background you need, you’re ready to move on to an example of how you can neatly add constructors to a hierarchy of classes. In the next section, you start adding constructors that take parameters to the MortimerPhones example.

Adding constructors with parameters to a hierarchy

You’re going to start with a one-parameter constructor for GenericCustomer, which controls that customers can be instantiated only when they supply their names:

  abstract class GenericCustomer {    private string name;    public GenericCustomer(string name)    {       this.name = name;    } 

So far, so good. However, as mentioned previously, this will cause a compilation error when the compiler tries to create a default constructor for any derived classes, because the default compiler-generated constructors for Nevermore60Customer will try to call a no-parameter GenericCustomer constructor and GenericCustomer does not possess such a constructor. Therefore, you’ll need to supply your own constructors to the derived classes to avoid a compilation error:

  class Nevermore60Customer : GenericCustomer {    private uint highCostMinutesUsed;    public Nevermore60Customer(string name)       :   base(name)    {    } 

Now instantiation of Nevermore60Customer objects can only take place when a string containing the customer’s name is supplied, which is what you want anyway. The interesting thing is what the Nevermore60Customer constructor does with this string. Remember that it can’t initialize the name field itself, because it has no access to private fields in its base class. Instead, it passes the name through to the base class for the GenericCustomer constructor to handle. It does this by specifying that the base class constructor to be executed first is the one that takes the name as a parameter. Other than that, it doesn’t take any action of its own.

Next, you’re going to investigate what happens if you have different overloads of the constructor as well as a class hierarchy to deal with. To this end, assume that Nevermore60 customers may have been referred to MortimerPhones by a friend as part of one of those sign-up-a-friend-and-get-a-discount offers. This means that when you construct a Nevermore60Customer, you may need to pass in the referrer’s name as well. In real life, the constructor would have to do something complicated with the name, such as process the discount, but here you’ll just store the referrer’s name in another field.

The Nevermore60Customer definition will now look like this:

  class Nevermore60Customer : GenericCustomer {    public Nevermore60Customer(string name, string referrerName)       : base(name)    {       this.referrerName = referrerName;    }    private string referrerName;    private uint highCostMinutesUsed; 

The constructor takes the name and passes it to the GenericCustomer constructor for processing. referrerName is the variable that is your responsibility here, so the constructor deals with that parameter in its main body.

However, not all Nevermore60Customers will have a referrer, so you still need a constructor that doesn’t require this parameter (or a constructor that gives you a default value for it). In fact, you will specify that if there is no referrer, then the referrerName field should be set to “<None>”, using the following one-parameter constructor:

  public Nevermore60Customer(string name)    : this(name, "<None>") { } 

You’ve now got all your constructors set up correctly. It’s instructive to examine the chain of events that now occurs when you execute a line like this:

  GenericCustomer customer = new Nevermore60Customer("Arabel Jones"); 

The compiler sees that it needs a one-parameter constructor that takes one string, so the constructor it will identify is the last one that you’ve defined:

  public Nevermore60Customer(string Name)    : this(Name, "<None>") 

When you instantiate customer, this constructor will be called. It immediately transfers control to the corresponding Nevermore60Customer two-parameter constructor, passing it the values “Arabel Jones”, and “<None>”. Looking at the code for this constructor, you see that it in turn immediately passes control to the one-parameter GenericCustomer constructor, giving it the string “Arabel Jones”, and in turn that constructor passes control to the System.Object default constructor. Only now do the constructors execute. First, the System.Object constructor executes. Next comes the GenericCustomer constructor, which initializes the name field. Then the Nevermore60Customer two-parameter constructor gets control back, and sorts out initializing the referrerName to “<None>”. Finally, the Nevermore60Customer one-parameter constructor gets to execute; this constructor doesn’t do anything else.

As you can see, this is a very neat and well-designed process. Each constructor handles initialization of the variables that are obviously its responsibility, and in the process your class has been correctly instantiated and prepared for use. If you follow the same principles when you write your own constructors for your classes, you should find that even the most complex classes get initialized smoothly and without any problems.




Professional C# 2005 with .NET 3.0
Professional C# 2005 with .NET 3.0
ISBN: 470124725
EAN: N/A
Year: 2007
Pages: 427

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