Chapter 4

Inheritance

WHAT’S IN THIS CHAPTER?

  • Types of inheritance
  • Implementing inheritance
  • Access modifiers
  • Interfaces

WROX.COM CODE DOWNLOADS FOR THIS CHAPTER

The wrox.com code downloads for this chapter are found at http://www.wrox.com/remtitle.cgi?isbn=1118314425 on the Download Code tab. The code for this chapter is divided into the following major examples:

  • BankAccounts.cs
  • CurrentAccounts.cs
  • MortimerPhones.cs

INHERITANCE

Chapter 3, “Objects and Types,” examined how to use individual classes in C#. The focus in that chapter was how to define methods, properties, constructors, and other members of a single class (or a single struct). Although you learned that all classes are ultimately derived from the class System.Object, you have not yet learned how to create a hierarchy of inherited classes. Inheritance is the subject of this chapter, which explains how C# and the .NET Framework handle inheritance.

TYPES OF INHERITANCE

Let’s start by reviewing exactly what C# does and does not support as far as inheritance is concerned.

Implementation Versus Interface Inheritance

In object-oriented programming, there are two distinct types of inheritance — implementation inheritance and interface inheritance:

  • Implementation inheritance means that a type derives from a base type, taking all the base type’s member fields and functions. With implementation inheritance, a derived type adopts the base type’s implementation of each function, unless the definition of the derived type indicates that a function implementation is to be overridden. This type of inheritance is most useful when you need to add functionality to an existing type, or when a number of related types share a significant amount of common functionality.
  • Interface inheritance means that a type inherits only the signatures of the functions, not any implementations. This type of inheritance is most useful when you want to specify that a type makes certain features available.

C# supports both implementation inheritance and interface inheritance. Both are incorporated into the framework and the language from the ground up, thereby enabling you to decide which to use based on the application’s architecture.

Multiple Inheritance

Some languages such as C++ support what is known as multiple inheritance, in which a class derives from more than one other class. The benefits of using multiple inheritance are debatable: On the one hand, you can certainly use multiple inheritance to write extremely sophisticated, yet compact code, as demonstrated by the C++ ATL library. On the other hand, code that uses multiple implementation inheritance is often difficult to understand and debug (a point that is equally well demonstrated by the C++ ATL library). As mentioned, making it easy to write robust code was one of the crucial design goals behind the development of C#. Accordingly, C# does not support multiple implementation inheritance. It does, however, allow types to be derived from multiple interfaces — multiple interface inheritance. This means that a C# class can be derived from one other class, and any number of interfaces. Indeed, we can be more precise: Thanks to the presence of System.Object as a common base type, every C# class (except for Object) has exactly one base class, and may additionally have any number of base interfaces.

Structs and Classes

Chapter 3 distinguishes between structs (value types) and classes (reference types). One restriction of using structs is that they do not support inheritance, beyond the fact that every struct is automatically derived from System.ValueType. Although it’s true that you cannot code a type hierarchy of structs, it is possible for structs to implement interfaces. In other words, structs don’t really support implementation inheritance, but they do support interface inheritance. The following summarizes the situation for any types that you define:

  • Structs are always derived from System.ValueType. They can also be derived from any number of interfaces.
  • Classes are always derived from either System.Object or one that you choose. They can also be derived from any number of interfaces.

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
}

NOTE 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 very 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 this:

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 foreName;}
   set { foreName = 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# does differ from C++ syntax, though, 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 that 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 in 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 to cope very well with these types of conflicts.

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 picks up on the fact that it’s hiding a base class method without being instructed to do so and generates 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 eliminates 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(). This is because any existing code that accesses this method must be done 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 occur in client code you have yet to write. C# is designed to issue a warning that a potential problem might occur in future code — you 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 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;
   }
}

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; and 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
}

NOTE C++ developers should 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 you can’t inherit from that class. In the case of a method, this means you can’t override that method.

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

The most likely situation in which you’ll mark a class or method as sealed is if the class or method is internal to the operation of the library, class, or other classes that you are writing, to ensure that any attempt to override some of its functionality will lead to instability in the code. 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, 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 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 purpose similar to that for a class:

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

In order to use the sealed keyword on a method or property, it must have first been overridden from a base class. If you do not want a method or property in a base class overridden, then don’t mark it as virtual.

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 that 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 is 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, so you have to ensure 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, more than one constructor is 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 understand 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, which 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. 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.
}

Don’t worry about what other methods might be implemented in these classes because we are concentrating solely on the construction process here. 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 initialized 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 initializes 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. Therefore, the default Nevermore60Customer constructor won’t know that this field exists. The only code items that have that knowledge are other members of GenericCustomer. Therefore, if name is going to be initialized, that must be done by a 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. The GenericCustomer constructor attempts to run the constructor for its immediate base class, System.Object; but 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, and then progress down the hierarchy until the compiler reaches the class being instantiated. Notice 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.

Note the order in which this happens. It’s always the base class constructors that are called first. This means there are no problems with a constructor for a derived class invoking any base class methods, properties, and any other members to which it has access, 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 possible, 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

This section takes the easiest case first and demonstrates 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 uses the custom GenericCustomer constructor instead of generating a default one, so the name field is always initialized to "<no name>" as required.

Notice that in your constructor 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 you saw 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 has to look for a parameterless constructor to call. The result of all this is that the compiler injects code to call the System.Object constructor, which is what happens 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 this 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 wanted to call the base class constructor; this is consistent with how 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 way to collapse 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 get an interesting compilation error, which could really throw you if you don’t understand how construction down a hierarchy works:

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

What’s interesting here is that the error occurs not in the GenericCustomer class but in the derived class, Nevermore60Customer. That’s because the compiler tried to generate a default constructor for Nevermore60Customer but was not able to, as 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 won’t generate a default constructor for GenericCustomer, so when it tries to generate the default constructors for any derived class, it again finds that it can’t because a no-parameter base class constructor is not available. A workaround is 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 constructors.

Now that you have all the theoretical background you need, you’re ready to move on to an example demonstrating 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 specifies 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 causes 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 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 occur only when a string containing the customer’s name is supplied, which is what you want anyway. The interesting thing here 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.

Now examine 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 might 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 now have all your constructors set up correctly. It’s instructive to examine the chain of events that 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 identifies is the last one that you defined:

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

When you instantiate customer, this constructor is called. It immediately transfers control to the corresponding Nevermore60Customer two-parameter constructor, passing it the values "ArabelJones", 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 "ArabelJones", 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 is 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 executes; 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 is correctly instantiated and prepared for use. If you follow the same principles when you write your own constructors for your classes, even the most complex classes should be initialized smoothly and without any problems.

MODIFIERS

You have already encountered quite a number of so-called modifiers — keywords that can be applied to a type or a member. Modifiers can indicate the visibility of a method, such as public or private, or the nature of an item, such as whether a method is virtual or abstract. C# has a number of modifiers, and at this point it’s worth taking a minute to provide the complete list.

Visibility Modifiers

Visibility modifiers indicate which other code items can view an item.

MODIFIER APPLIES TO DESCRIPTION
public Any types or members The item is visible to any other code.
protected Any member of a type, and any nested type The item is visible only to any derived type.
internal Any types or members The item is visible only within its containing assembly.
private Any member of a type, and any nested type The item is visible only inside the type to which it belongs.
protected internal Any member of a type, and any nested type The item is visible to any code within its containing assembly and to any code inside a derived type.

Note that type definitions can be internal or public, depending on whether you want the type to be visible outside its containing assembly:

public class MyClass
{
  // etc.

You cannot define types as protected, private, or protected internal because these visibility levels would be meaningless for a type contained in a namespace. Hence, these visibilities can be applied only to members. However, you can define nested types (that is, types contained within other types) with these visibilities because in this case the type also has the status of a member. Hence, the following code is correct:

public class OuterClass
{
  protected class InnerClass
  {
     // etc.
  }
  // etc.
}

If you have a nested type, the inner type is always able to see all members of the outer type. Therefore, with the preceding code, any code inside InnerClass always has access to all members of OuterClass, even where those members are private.

Other Modifiers

The modifiers in the following table can be applied to members of types and have various uses. A few of these modifiers also make sense when applied to types.

MODIFIER APPLIES TO DESCRIPTION
new Function members The member hides an inherited member with the same signature.
static All members The member does not operate on a specific instance of the class.
virtual Function members only The member can be overridden by a derived class.
abstract Function members only A virtual member that defines the signature of the member but doesn’t provide an implementation.
override Function members only The member overrides an inherited virtual or abstract member.
sealed Classes, methods, and properties For classes, the class cannot be inherited from. For properties and methods, the member overrides an inherited virtual member but cannot be overridden by any members in any derived classes. Must be used in conjunction with override.
extern Static [DllImport] methods only The member is implemented externally, in a different language.

INTERFACES

As mentioned earlier, by deriving from an interface, a class is declaring that it implements certain functions. Because not all object-oriented languages support interfaces, this section examines C#’s implementation of interfaces in detail. It illustrates interfaces by presenting the complete definition of one of the interfaces that has been predefined by Microsoft — System.IDisposable. IDisposable contains one method, Dispose(), which is intended to be implemented by classes to clean up code:

public interface IDisposable
{
   void Dispose();
}

This code shows that declaring an interface works syntactically in much the same way as declaring an abstract class. Be aware, however, that it is not permitted to supply implementations of any of the members of an interface. In general, an interface can contain only declarations of methods, properties, indexers, and events.

You can never instantiate an interface; it contains only the signatures of its members. An interface has neither constructors (how can you construct something that you can’t instantiate?) nor fields (because that would imply some internal implementation). Nor is an interface definition allowed to contain operator overloads, although that’s not because there is any problem with declaring them; there isn’t, but because interfaces are usually intended to be public contracts, having operator overloads would cause some incompatibility problems with other .NET languages, such as Visual Basic .NET, which do not support operator overloading.

Nor is it permitted to declare modifiers on the members in an interface definition. Interface members are always implicitly public, and they cannot be declared as virtual or static. That’s up to implementing classes to decide. Therefore, it is fine for implementing classes to declare access modifiers, as demonstrated in the example in this section.

For example, consider IDisposable. If a class wants to declare publicly that it implements the Dispose() method, it must implement IDisposable, which in C# terms means that the class derives from IDisposable:

class SomeClass: IDisposable
{
   // This class MUST contain an implementation of the
   // IDisposable.Dispose() method, otherwise
   // you get a compilation error.
   public void Dispose()
   {
      // implementation of Dispose() method
   }
   // rest of class
}

In this example, if SomeClass derives from IDisposable but doesn’t contain a Dispose() implementation with the exact same signature as defined in IDisposable, you get a compilation error because the class is breaking its agreed-on contract to implement IDisposable. Of course, it’s no problem for the compiler if a class has a Dispose() method but doesn’t derive from IDisposable. The problem is that other code would have no way of recognizing that SomeClass has agreed to support the IDisposable features.


NOTE IDisposable is a relatively simple interface because it defines only one method. Most interfaces contain more members.

Defining and Implementing Interfaces

This section illustrates how to define and use interfaces by developing a short program that follows the interface inheritance paradigm. The example is based on bank accounts. Assume that you are writing code that will ultimately allow computerized transfers between bank accounts. Assume also for this example that there are many companies that implement bank accounts but they have all mutually agreed that any classes representing bank accounts will implement an interface, IBankAccount, which exposes methods to deposit or withdraw money, and a property to return the balance. It is this interface that enables outside code to recognize the various bank account classes implemented by different bank accounts. Although the aim is to enable the bank accounts to communicate with each other to allow transfers of funds between accounts, that feature isn’t introduced just yet.

To keep things simple, you will keep all the code for the example in the same source file. Of course, if something like the example were used in real life, you could surmise that the different bank account classes would not only be compiled to different assemblies, but also be hosted on different machines owned by the different banks. That’s all much too complicated for our purposes here. However, to maintain some realism, you will define different namespaces for the different companies.

To begin, you need to define the IBankAccount interface:

namespace Wrox.ProCSharp
{
   public interface IBankAccount
   {
      void PayIn(decimal amount);
      bool Withdraw(decimal amount);
      decimal Balance { get; }
   }
}

Notice the name of the interface, IBankAccount. It’s a best-practice convention to begin an interface name with the letter I, to indicate it’s an interface.


NOTE Chapter 2, “Core C#,” points out that in most cases, .NET usage guidelines discourage the so-called Hungarian notation in which names are preceded by a letter that indicates the type of object being defined. Interfaces are one of the few exceptions for which Hungarian notation is recommended.

The idea is that you can now write classes that represent bank accounts. These classes don’t have to be related to each other in any way; they can be completely different classes. They will all, however, declare that they represent bank accounts by the mere fact that they implement the IBankAccount interface.

Let’s start off with the first class, a saver account run by the Royal Bank of Venus:

namespace Wrox.ProCSharp.VenusBank
{
   public class SaverAccount: IBankAccount
   {
      private decimal balance;
      public void PayIn(decimal amount)
      {
         balance += amount;
      }
      public bool Withdraw(decimal amount)
      {
         if (balance >= amount)
         {
            balance -= amount;
            return true;
         }
         Console.WriteLine("Withdrawal attempt failed.");
         return false;
      }
      public decimal Balance
      {
         get
         {
            return balance;
         }
      }
      public override string ToString()
      {
         return String.Format("Venus Bank Saver: Balance = {0,6:C}", balance);
      }
   }
}

It should be obvious what the implementation of this class does. You maintain a private field, balance, and adjust this amount when money is deposited or withdrawn. You display an error message if an attempt to withdraw money fails because of insufficient funds. Notice also that because we are keeping the code as simple as possible, we are not implementing extra properties, such as the account holder’s name! In real life that would be essential information, of course, but for this example it’s unnecessarily complicated.

The only really interesting line in this code is the class declaration:

public class SaverAccount: IBankAccount

You’ve declared that SaverAccount is derived from one interface, IBankAccount, and you have not explicitly indicated any other base classes (which of course means that SaverAccount is derived directly from System.Object). By the way, derivation from interfaces acts completely independently from derivation from classes.

Being derived from IBankAccount means that SaverAccount gets all the members of IBankAccount; but because an interface doesn’t actually implement any of its methods, SaverAccount must provide its own implementations of all of them. If any implementations are missing, you can rest assured that the compiler will complain. Recall also that the interface just indicates the presence of its members. It’s up to the class to determine whether it wants any of them to be virtual or abstract (though abstract functions are of course only allowed if the class itself is abstract). For this particular example, you don’t have any reason to make any of the interface functions virtual.

To illustrate how different classes can implement the same interface, assume that the Planetary Bank of Jupiter also implements a class to represent one of its bank accounts — a Gold Account:

namespace Wrox.ProCSharp.JupiterBank
{
   public class GoldAccount: IBankAccount
   {
      // etc
   }
}

We won’t present details of the GoldAccount class here; in the sample code, it’s basically identical to the implementation of SaverAccount. We stress that GoldAccount has no connection with SaverAccount, other than they both happen to implement the same interface.

Now that you have your classes, you can test them. You first need a few using statements:

using System;
using Wrox.ProCSharp;
using Wrox.ProCSharp.VenusBank;
using Wrox.ProCSharp.JupiterBank;

Now you need a Main() method:

namespace Wrox.ProCSharp
{
   class MainEntryPoint
   {
      static void Main()
      {
         IBankAccount venusAccount = new SaverAccount();
         IBankAccount jupiterAccount = new GoldAccount();
         venusAccount.PayIn(200);
         venusAccount.Withdraw(100);
         Console.WriteLine(venusAccount.ToString());
         jupiterAccount.PayIn(500);
         jupiterAccount.Withdraw(600);
         jupiterAccount.Withdraw(100);
         Console.WriteLine(jupiterAccount.ToString());
      }
   }
}

This code (which if you download the sample, you can find in the file BankAccounts.cs) produces the following output:

C:> BankAccounts
Venus Bank Saver: Balance = £100.00
Withdrawal attempt failed.
Jupiter Bank Saver: Balance = £400.00

The main point to notice about this code is the way that you have declared both your reference variables as IBankAccount references. This means that they can point to any instance of any class that implements this interface. However, it also means that you can call only methods that are part of this interface through these references — if you want to call any methods implemented by a class that are not part of the interface, you need to cast the reference to the appropriate type. In the example code, you were able to call ToString() (not implemented by IBankAccount) without any explicit cast, purely because ToString() is a System.Object method, so the C# compiler knows that it will be supported by any class (put differently, the cast from any interface to System.Object is implicit). Chapter 7, “Operators and Casts,” covers the syntax for performing casts.

Interface references can in all respects be treated as class references — but the power of an interface reference is that it can refer to any class that implements that interface. For example, this allows you to form arrays of interfaces, whereby each element of the array is a different class:

IBankAccount[] accounts = new IBankAccount[2];
accounts[0] = new SaverAccount();
accounts[1] = new GoldAccount();

Note, however, that you would get a compiler error if you tried something like this:

accounts[1] = new SomeOtherClass();    // SomeOtherClass does NOT implement
                                              // IBankAccount: WRONG!!

The preceding causes a compilation error similar to this:

Cannot implicitly convert type 'Wrox.ProCSharp. SomeOtherClass' to
 'Wrox.ProCSharp.IBankAccount'

Derived Interfaces

It’s possible for interfaces to inherit from each other in the same way that classes do. This concept is illustrated by defining a new interface, ITransferBankAccount, which has the same features as IBankAccount but also defines a method to transfer money directly to a different account:

namespace Wrox.ProCSharp
{
   public interface ITransferBankAccount: IBankAccount
   {
      bool TransferTo(IBankAccount destination, decimal amount);
   }
}

Because ITransferBankAccount is derived from IBankAccount, it gets all the members of IBankAccount as well as its own. That means that any class that implements (derives from) ITransferBankAccount must implement all the methods of IBankAccount, as well as the new TransferTo() method defined in ITransferBankAccount. Failure to implement all these methods will result in a compilation error.

Note that the TransferTo() method uses an IBankAccount interface reference for the destination account. This illustrates the usefulness of interfaces: When implementing and then invoking this method, you don’t need to know anything about what type of object you are transferring money to — all you need to know is that this object implements IBankAccount.

To illustrate ITransferBankAccount, assume that the Planetary Bank of Jupiter also offers a current account. Most of the implementation of the CurrentAccount class is identical to implementations of SaverAccount and GoldAccount (again, this is just to keep this example simple — that won’t normally be the case), so in the following code only the differences are highlighted:

public class CurrentAccount: ITransferBankAccount
{
   private decimal balance;
   public void PayIn(decimal amount)
   {
      balance += amount;
   }
   public bool Withdraw(decimal amount)
   {
      if (balance >= amount)
      {
         balance -= amount;
         return true;
      }
      Console.WriteLine("Withdrawal attempt failed.");
      return false;
   }
   public decimal Balance
   {
      get
      {
         return balance;
      }
   }
   public bool TransferTo(IBankAccount destination, decimal amount)
   {
      bool result;
      result = Withdraw(amount);
      if (result)
      {
         destination.PayIn(amount);
      }
      return result;
   }
   public override string ToString()
   {
       return String.Format("Jupiter Bank Current Account: Balance = {0,6:C}",balance);
   }
}

The class can be demonstrated with this code:

static void Main()
{
   IBankAccount venusAccount = new SaverAccount();
   ITransferBankAccount jupiterAccount = new CurrentAccount();
   venusAccount.PayIn(200);
   jupiterAccount.PayIn(500);
   jupiterAccount.TransferTo(venusAccount, 100);
   Console.WriteLine(venusAccount.ToString());
   Console.WriteLine(jupiterAccount.ToString());
}

The preceding code (CurrentAccounts.cs) produces the following output, which, as you can verify, shows that the correct amounts have been transferred:

C:> CurrentAccount
Venus Bank Saver: Balance = £300.00
Jupiter Bank Current Account: Balance = £400.00

SUMMARY

This chapter described how to code inheritance in C#. You have seen that C# offers rich support for both multiple interface and single implementation inheritance. You have also learned that C# provides a number of useful syntactical constructs designed to assist in making code more robust. These include the override keyword, which indicates when a function should override a base function; the new keyword, which indicates when a function hides a base function; and rigid rules for constructor initializers that are designed to ensure that constructors are designed to interoperate in a robust manner.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset