6. Inheritance

The preceding chapter discussed how one class can reference other classes via fields and properties. This chapter discusses how to use the inheritance relationship between classes to build class hierarchies that form an “is a” relationship.

Image

Derivation

It is common to want to extend a given type to add features, such as behavior and data. The purpose of inheritance is to do exactly that. Given a Person class, you create an Employee class that additionally contains EmployeeId and Department properties. The reverse approach may also occur. Given, for example, a Contact class within a Personal Digital Assistant (PDA), you decide you also can add calendaring support. Toward this effort, you create an Appointment class. However, instead of redefining the methods and properties that are common to both classes, you refactor the Contact class. Specifically, you move the common methods and properties on Contact into a base class called PdaItem from which both Contact and Appointment derive, as shown in Figure 6.1.

Image

Figure 6.1. Refactoring into a Base Class

The common items in this case are Created, LastUpdated, Name, ObjectKey, and the like. Through derivation, the methods defined on the base class, PdaItem, are accessible from all classes derived from PdaItem.

When declaring a derived class, follow the class identifier with a colon and then the base class, as Listing 6.1 demonstrates.

Listing 6.1. Deriving One Class from Another


public class PdaItem
{
  public string Name { get; set; }

  public DateTime LastUpdated { get; set; }
}

// Define the Contact class as inheriting the PdaItem class
public class Contact : PdaItem

{
  public string Address { get; set; }
  public string Phone { get; set; }
}

Listing 6.2 shows how to access the properties defined in Contact.

Listing 6.2. Using Inherited Methods


public class Program
{
  public static void Main()
  {
      Contact contact = new Contact();
      contact.Name = "Inigo Montoya";


      // ...
  }
}

Even though Contact does not directly have a property called Name, all instances of Contact can still access the Name property from PdaItem and use it as though it was part of Contact. Furthermore, any additional classes that derive from Contact will also inherit the members of PdaItem, or any class from which PdaItem was derived. The inheritance chain has no practical limit and each derived class will have all the members of its base class inheritance chain combined (see Listing 6.3).


Note

Via inheritance, each member of a base class will also appear within the chain of derived classes.


Listing 6.3. Classes Deriving from Each Other to Form an Inheritance Chain


public class PdaItem : object
{
  // ...
}

public class Appointment : PdaItem
{
  // ...
}

public class Contact : PdaItem
{
  // ...
}

public class Customer : Contact
{
  // ...
}

In other words, although Customer doesn’t derive from PdaItem directly, it still inherits the members of PdaItem.

In Listing 6.3, PdaItem is shown explicitly to derive from object. Although C# allows such syntax, it is unnecessary because all classes that don’t have some other derivation will derive from object, regardless of whether it is specified.


Note

Unless an alternate base class is specified, all classes will derive from object by default.


Casting between Base and Derived Types

As Listing 6.4 shows, because derivation forms an “is a” relationship, a derived type value can always be directly assigned to a base type variable.

Listing 6.4. Implicit Base Type Casting


public class Program
{
  public static void Main()
  {
      // Derived types can be implicitly converted to
      // base types
      Contact contact = new Contact();
      PdaItem item = contact;

      // ...

      // Base types must be cast explicitly to derived types
      contact = (Contact)item;

      // ...
  }
}

The derived type, Contact, is a PdaItem and can be assigned directly to a variable of type PdaItem. This is known as an implicit conversion because no cast operator is required and the conversion will, on principle, always succeed; it will not throw an exception.

The reverse, however, is not true. A PdaItem is not necessarily a Contact; it could be an Appointment or some other derived type. Therefore, casting from the base type to the derived type requires an explicit cast, which at runtime could fail. To perform an explicit cast, identify the target type within parentheses prior to the original reference, as Listing 6.4 demonstrates.

With the explicit cast, the programmer essentially communicates to the compiler to trust her—she knows what she is doing—and the C# compiler allows the conversion as long as the target type is derived from the originating type. Although the C# compiler allows an explicit conversion at compile time between potentially compatible types, the CLR will still verify the explicit cast at execution time, throwing an exception if in fact the object instance is not of the targeted type.

The C# compiler allows the cast operator even when the type hierarchy allows an implicit cast. For example, the assignment from contact to item could use a cast operator as follows:

item = (PdaItem)contact;

or even when no cast is necessary:

contact = (Contact)contact;


Note

A derived object can be implicitly converted to its base class. In contrast, converting from the base class to the derived class requires an explicit cast operator, as the conversion could fail. Although the compiler will allow an explicit cast if it is potentially valid, the runtime will still prevent an invalid cast at execution time by throwing an exception.


private Access Modifier

All members of a base class, except for constructors and destructors, are inherited by the derived class. However, just because a member is inherited does not mean it is accessible. For example, in Listing 6.6, the private field, _Name, is not available on Contact because private members are only accessible at code locations inside the type that declares them.

Listing 6.6. Private Members Are Inherited But Not Accessible


public class PdaItem
{
  private string _Name;
  // ...
}

public class Contact : PdaItem
{
  // ...
}

public class Program
{
  public static void Main()
  {
      Contact contact = new Contact();

      // ERROR:  'PdaItem. _Name' is inaccessible
      // due to its protection level
      //contact._Name = "Inigo Montoya";

  }
}

As part of keeping with the principle of encapsulation, derived classes cannot access members declared as private.1 This forces the base class developer to make an explicit choice as to whether a derived class gains access to a member. In this case, the base class is defining an API in which _Name can be changed only via the Name property. That way, if validation is added, the derived class will gain the validation benefit automatically because it was unable to access _Name directly from the start.

1. Except for the corner case when the derived class is also a nested class of the base class.


Note

Non-nested derived classes cannot access members declared as private in a base class.


protected Access Modifier

Encapsulation is finer-grained than just public or private, however. It is possible to define members in base classes that only derived classes can access. Consider the ObjectKey property shown in Listing 6.7, for example.

Listing 6.7. protected Members Are Accessible Only from Derived Classes


public class Program
{
  public static void Main()
  {
      Contact contact = new Contact();
      contact.Name = "Inigo Montoya";

      // ERROR:  'PdaItem.ObjectKey' is inaccessible
      // due to its protection level
      // contact.ObjectKey = Guid.NewGuid();

  }
}

public class PdaItem
{
  protected Guid ObjectKey

  {
      get { return _ObjectKey; }
      set { _ObjectKey = value; }
  }
  private Guid _ObjectKey;

  // ...
}

public class Contact : PdaItem
{
  void Save()
  {
      // Instantiate a FileStream using <ObjectKey>.dat
      // for the filename.
      FileStream stream = System.IO.File.OpenWrite(
          ObjectKey + ".dat");
  }

  void Load(PdaItem pdaItem)
  {
      // ERROR:  'pdaItem.ObjectKey' is inaccessible
      // due to its protection level
      // pdaItem.ObjectKey = ...;


      Contact contact = pdaItem as Contact;
      if(contact != null)
      {
          contact.ObjectKey = ...;
      }

      // ...
  }
}

ObjectKey is defined using the protected access modifier. The result is that it is accessible outside of PdaItem only from classes that derive from PdaItem. Contact derives from PdaItem, and, therefore all members of Contact have access to ObjectKey. Since Program does not derive from PdaItem, using the ObjectKey property within Program results in a compile error.


Note

Protected members in the base class are only accessible from the base class and other classes within the derivation chain.


A subtlety shown in the Contact.Load() method is worth noting. Developers are often surprised that from code within Contact it is not possible to access the protected ObjectKey of an explicit PdaItem, even though Contact derives from PdaItem. The reason is that a PdaItem could potentially be an Address, and Contact should not be able to access protected members of Address. Therefore, encapsulation prevents Contact from potentially modifying the ObjectKey of an Address. A successful cast to Contact will bypass the restriction as shown. The governing rule is that accessing a protected member from a derived class requires compile-time determination that the protected member is an instance of the derived class (or a class further derived from it).

Extension Methods

Extension methods are technically not members of a type, and therefore are not inherited. But because every derived class may be used as an instance of any of its base classes, an extension method on one type also extends every derived type. If we extend a base class such as PdaItem, all the extension methods will also be available in the derived classes. However, as with all extension methods, priority is given to instance methods. If a compatible signature appears anywhere within the inheritance chain, this will take precedence over an extension method.

Requiring extension methods on base types is rare. As with extension methods in general, if the base type’s code is available, it is preferable to modify the base type directly. Even in cases where the base type’s code is unavailable, programmers should consider whether to add extension methods to an interface that the base type or individual derived types implement. We cover interfaces and how to use them with extension methods in the next chapter.

Single Inheritance

In theory, you can place an unlimited number of classes in an inheritance tree. For example, Customer derives from Contact, which derives from PdaItem, which derives from object. However, C# is a single-inheritance programming language (as is the CIL language to which C# compiles). This means that a class cannot derive from two classes directly. It is not possible, for example, to have Contact derive from both PdaItem and Person.


Language Contrast: C++—Multiple Inheritance

C#’s single inheritance is one of its major differences from C++. It makes for a significant migration path from programming libraries such as Active Template Library (ATL), whose entire approach relies on multiple inheritance.


For the rare cases that require a multiple-inheritance class structure, one solution is to use aggregation; instead of one class inheriting from another, one class contains an instance of the other. Figure 6.2 shows an example of this class structure. Aggregation occurs when the association relationship defines a core part of the containing object. For multiple inheritance, this involves picking one class as the primary base class (PdaItem) and deriving a new class (Contact) from that. The second desired base class (Person) is added as a field in the derived class (Contact). Next, all the nonprivate members on the field (Person) are redefined on the derived class (Contact) which then delegates the calls out to the field (Person). Some code duplication occurs because methods are redeclared; however, this is minimal, since the real method body is implemented only within the aggregated class (Person).

Image

Figure 6.2. Simulating Multiple Inheritance Using Aggregation

In Figure 6.2, Contact contains a private property called InternalPerson that is drawn as an association to the Person class. Contact also contains the FirstName and LastName properties but with no corresponding fields. Instead, the FirstName and LastName properties simply delegate their calls out to InternalPerson.FirstName and InternalPerson.LastName, respectively. Listing 6.8 shows the resultant code.

Listing 6.8. Working around Single Inheritance Using Aggregation


public class PdaItem
{
  // ...
}

public class Person
{
  // ...
}

public class Contact : PdaItem
{
  private Person InternalPerson { get; set; }

  public string FirstName
  {
      get { return InternalPerson.FirstName; }
      set { InternalPerson.FirstName = value; }
  }


  public string LastName
  {
      get { return InternalPerson.LastName; }
      set { InternalPerson.LastName = value; }
  }

  // ...
}

Besides the added complexity of delegation, another drawback is that any methods added to the field class (Person) will require manual addition to the derived class (Contact); otherwise, Contact will not expose the added functionality.

Sealed Classes

To design a class correctly that others can extend via derivation can be a tricky task that requires testing with examples to verify the derivation will work successfully. Listing 6.9 shows how to avoid unexpected derivation scenarios and problems by marking classes as sealed.

Listing 6.9. Preventing Derivation with Sealed Classes


public sealed class CommandLineParser
{
  // ...
}

// ERROR:  Sealed classes cannot be derived from
public sealed class DerivedCommandLineParser :
  CommandLineParser
{
  // ...
}

Sealed classes include the sealed modifier, and the result is that they cannot be derived from. The string type is an example of a type that uses the sealed modifier to prevent derivation.

Overriding the Base Class

All members of a base class are inherited in the derived class, except for constructors and destructors. However, sometimes the base class does not have the optimal implementation of a particular member. Consider the Name property on PdaItem, for example. The implementation is probably acceptable when inherited by the Appointment class. For the Contact class, however, the Name property should return the FirstName and LastName properties combined. Similarly, when Name is assigned, it should be split across FirstName and LastName. In other words, the base class property declaration is appropriate for the derived class, but the implementation is not always valid. There needs to be a mechanism for overriding the base class implementation with a custom implementation in the derived class.

virtual Modifier

C# supports overriding on instance methods and properties, but not on fields or on any static members. It requires an explicit action within both the base class and the derived class. The base class must mark each member for which it allows overriding as virtual. If public or protected members do not include the virtual modifier, subclasses will not be able to override those members.


Language Contrast: Java—Virtual Methods by Default

By default, methods in Java are virtual, and they must be explicitly sealed if nonvirtual behavior is preferred. In contrast, C# defaults to nonvirtual.


Listing 6.10 shows an example of property overriding.

Listing 6.10. Overriding a Property


public class PdaItem
{
    public virtual string Name { get; set; }

    // ...
}

public class Contact : PdaItem
{
    public override string Name

    {
        get
        {
            return FirstName + " " + LastName;
        }

        set
        {
            string[] names = value.Split(' ');
            // Error handling not shown.
            FirstName = names[0];
            LastName = names[1];
        }
    }

    public string FirstName { get; set; }
    public string LastName { get; set; }

    // ...
}

Not only does PdaItem include the virtual modifier on the Name property, but also, Contact’s Name property is decorated with the keyword override. Eliminating virtual would result in an error and omitting override would cause a warning, as you will see shortly. C# requires the overriding methods to use the override keyword explicitly.

In other words, virtual identifies a method or property as available for replacement (overriding) in the derived type.


Language Contrast: Java and C++—Implicit Overriding

Unlike with Java and C++, the override keyword is required on the derived class. C# does not allow implicit overriding. In order to override a method, both the base class and the derived class members must match and have corresponding virtual and override keywords. Furthermore, if specifying the override keyword, the derived implementation is assumed to replace the base class implementation.


Overloading a member causes the runtime to call the most derived implementation (see Listing 6.11).

Listing 6.11. Runtime Calling the Most Derived Implementation of a Virtual Method


public class Program
{
  public static void Main()
  {
        Contact contact;
        PdaItem item;

        contact = new Contact();
        item = contact;

        // Set the name via PdaItem variable
        item.Name = "Inigo Montoya";

        // Display that FirstName & LastName
        // properties were set.
        Console.WriteLine("{0} {1}",
            contact.FirstName, contact.LastName);
}

Output 6.1 shows the results of Listing 6.11.

Output 6.1.

Inigo Montoya

In Listing 6.11, item.Name is called, where item is declared as a PdaItem. However, the contact’s FirstName and LastName are still set. The rule is that whenever the runtime encounters a virtual method, it calls the most derived and overriding implementation of the virtual member. In this case, the code instantiates a Contact and calls Contact.Name because Contact contains the most derived implementation of Name.

In creating a class, programmers should be careful when choosing to allow overriding a method, since they cannot control the derived implementation. Virtual methods should not include critical code because such methods may never be called if the derived class overrides them. Furthermore, converting a method from a virtual method to a nonvirtual method could break derived classes that override the method. This is a code-breaking change and you should avoid it, especially for assemblies intended for use by third parties.

Listing 6.12 includes a virtual Run() method. If the Controller programmer calls Run() with the expectation that the critical Start() and Stop() methods will be called, he will run into a problem.

Listing 6.12. Carelessly Relying on a Virtual Method Implementation


public class Controller
{
  public void Start()
  {
      // Critical code
  }
  public virtual void Run()
  {
      Start();
      Stop();
  }
  public void Stop()
  {
      // Critical code
  }
}

In overriding Run(), a developer could perhaps not call the critical Start() and Stop() methods. To force the Start()/Stop() expectation, the Controller programmer should define the class, as shown in Listing 6.13.

Listing 6.13. Forcing the Desirable Run() Semantics


public class Controller
{
    public void Start()
    {
        // Critical code
    }

    private void Run()
    {
        Start();
        InternalRun();
        Stop();
    }

    protected virtual void InternalRun()
    {
        // Default implementation
    }

    public void Stop()
    {
        // Critical code
    }
}

With this new listing, the Controller programmer prevents users from mistakenly calling InternalRun(), because it is protected. On the other hand, declaring Run() as public ensures that Start() and Stop() are invoked appropriately. It is still possible for users to modify the default implementation of how the Controller executes by overriding the protected InternalRun() member from within the derived class.

Virtual methods provide default implementations only, implementations that derived classes could override entirely. However, because of the complexities of inheritance design, it is important to consider (and preferably to implement) a specific scenario that requires the virtual method definition rather than declaring members as virtual by default.


Language Contrast: C++—Dispatch Method Calls during Construction

In C++, methods called during construction will not dispatch the virtual method. Instead, during construction, the type is associated with the base type rather than the derived type, and virtual methods call the base implementation. In contrast, C# dispatches virtual method calls to the most derived type. This is consistent with the principle of calling the most derived virtual member, even if the derived constructor has not completely executed. Regardless, in C# the situation should be avoided.


Finally, only instance members can be virtual. The CLR uses the concrete type, specified at instantiation time, to determine where to dispatch a virtual method call, so static virtual methods are meaningless and the compiler prohibits them.

new Modifier

When an overriding method does not use override, the compiler issues a warning similar to that shown in Output 6.2 or Output 6.3.

Output 6.2.

warning CS0114: '<derived method name>' hides inherited member
'<base method name>'. To make the current member override that
implementation, add the override keyword. Otherwise add the new
keyword.

Output 6.3.

warning CS0108: The keyword new is required on '<derived property
name>' because it hides inherited member '<base property name>'

The obvious solution is to add the override modifier (assuming the base member is virtual). However, as the warnings point out, the new modifier is also an option. Consider the scenario shown in Table 6.1—a specific example of the more general problem known as the brittle base class or fragile base class problem.

Table 6.1. Why the New Modifier?

Image
Image

Because Person.Name is not virtual, Programmer A will expect Display() to use the Person implementation, even if a Person-derived data type, Contact, is passed in. However, Programmer B would expect Contact.Name to be used in all cases where the variable data type is a Contact. (Programmer B would have no code where Person.Name was used, since no Person.Name property existed initially.) To allow the addition of Person.Name without breaking either programmer’s expected behavior, you cannot assume virtual was intended. Furthermore, since C# requires an override member to explicitly use the override modifier, some other semantic must be assumed, instead of allowing the addition of a member in the base class to cause the derived class to no longer compile.

The semantic is the new modifier, and it hides a redeclared member of the derived class from the base class. Instead of calling the most derived member, a member of the base class calls the most derived member in the inheritance chain prior to the member with the new modifier. If the inheritance chain contains only two classes, a member in the base class will behave as though no method was declared on the derived class (if the derived implementation overrides the base class member). Although the compiler will report the warning shown in either Output 6.2 or Output 6.3, if neither override nor new is specified, new will be assumed, thereby maintaining the desired version safety.

Consider Listing 6.14, for example. Its output appears in Output 6.4.

Listing 6.14. override versus new Modifier


public class Program
{
  public class BaseClass
  {
      public void DisplayName()
      {
          Console.WriteLine("BaseClass");
      }
  }

    public class DerivedClass : BaseClass
    {
        // Compiler WARNING: DisplayName() hides inherited
        // member. Use the new keyword if hiding was intended.
        public virtual void DisplayName()
        {
            Console.WriteLine("DerivedClass");
        }
    }

  public class SubDerivedClass : DerivedClass
  {
      public override void DisplayName()
      {
          Console.WriteLine("SubDerivedClass");
      }
  }

  public class SuperSubDerivedClass : SubDerivedClass
  {
      public new void DisplayName()
      {
          Console.WriteLine("SuperSubDerivedClass");
      }
  }

  public static void Main()
  {
      SuperSubDerivedClass superSubDerivedClass
          = new SuperSubDerivedClass();

      SubDerivedClass subDerivedClass = superSubDerivedClass;
      DerivedClass derivedClass = superSubDerivedClass;
      BaseClass baseClass = superSubDerivedClass;

      superSubDerivedClass.DisplayName();
      subDerivedClass.DisplayName();
      derivedClass.DisplayName();
      baseClass.DisplayName();
  }
}

Output 6.4.

SuperSubDerivedClass
SubDerivedClass
SubDerivedClass
BaseClass

These results occur for the following reasons.

SuperSubDerivedClass: SuperSubDerivedClass.DisplayName() displays SuperSubDerivedClass because there is no derived class and hence, no overload.

SubDerivedClass: SubDerivedClass.DisplayName() is the most derived member to override a base class’s virtual member. SuperSubDerivedClass.DisplayName() is hidden because of its new modifier.

SubDerivedClass: DerivedClass.DisplayName() is virtual and SubDerivedClass.DisplayName() is the most derived member to override it. As before, SuperSubDerivedClass.DisplayName() is hidden because of the new modifier.

BaseClass: BaseClass.DisplayName() does not redeclare any base class member and it is not virtual; therefore, it is called directly.

When it comes to the CIL, the new modifier has no effect on what statements the compiler generates. However, a “new” method results in the generation of the newslot metadata attribute on the method. From the C# perspective, its only effect is to remove the compiler warning that would appear otherwise.

sealed Modifier

Just as you can prevent inheritance using the sealed modifier on a class, virtual members may be sealed, too (see Listing 6.15). This prevents a subclass from overriding a base class member that was originally declared as virtual higher in the inheritance chain. The situation arises when a subclass B overrides a base class A’s member and then needs to prevent any further overriding below subclass B.

Listing 6.15. Sealing Members


class A
{
  public virtual void Method()
  {
  }
}
class B : A
{
  public override sealed void Method()
  {
  }
}

class C : B
{
  // ERROR:  Cannot override sealed members
  // public override void Method()
  // {
  // }
}

In this example, the use of the sealed modifier on class B’s Method() declaration prevents C’s overriding of Method().

In general, marking a class as sealed is rare and should be reserved only if there are strong reasons in favor of such a restriction. In fact, leaving types unsealed is increasingly desirable, as unit testing has become prominent because of the need to support mock (test double) object creation in place of real implementations. One possible scenario is when the cost of sealing individual virtual members outweighs the benefits of leaving the class unsealed. However, a more targeted sealing of individual members—perhaps because there are dependencies in the base implementation for correct behavior—is likely to be preferable.

base Member

In choosing to override a member, developers often want to invoke the member on the base class (see Listing 6.16).

Listing 6.16. Accessing a Base Member


public class Address
{
    public string StreetAddress;
    public string City;
    public string State;
    public string Zip;

    public override string ToString()
    {
        return string.Format("{0}" + Environment.NewLine +
            "{1}, {2}  {3}",
            StreetAddress, City, State, Zip);
    }
}

public class InternationalAddress : Address
{
    public string Country;

    public override string ToString()
    {
        return base.ToString() + Environment.NewLine +
            Country;
    }
}

In Listing 6.16, InternationalAddress inherits from Address and implements ToString(). To call the parent class’s implementation you use the base keyword. The syntax is virtually identical to this, including support for using base as part of the constructor (discussed shortly).

Parenthetically, in the Address.ToString() implementation, you are required to override because ToString() is also a member of object. Any members that are decorated with override are automatically designated as virtual, so additional child classes may further specialize the implementation.


Note

Any methods decorated with override are automatically virtual. A base class method can only be overridden if it is virtual, and the overriding method is therefore virtual as well.


Constructors

When instantiating a derived class, the runtime first invokes the base class’s constructor so that the base class initialization is not circumvented. However, if there is no accessible (nonprivate) default constructor on the base class, it is not clear how to construct the base class and the C# compiler reports an error.

To avoid the error caused by no accessible default constructor, programmers need to designate explicitly, in the derived class constructor header, which base constructor to run (see Listing 6.17).

Listing 6.17. Specifying Which Base Constructor to Invoke


public class PdaItem
{
  public PdaItem(string name)
  {
      Name = name;
  }

  // ...
}

public class Contact : PdaItem
{
  public Contact(string name) :
      base(name)

  {
      Name = name;
  }

  public string Name { get; set; }
  // ...
}

By identifying the base constructor in the code, you let the runtime know which base constructor to invoke before invoking the derived class constructor.

Abstract Classes

Many of the inheritance examples so far have defined a class called PdaItem that defines the methods and properties common to Contact, Appointment, and so on, which are type objects that derive from PdaItem. PdaItem is not intended to be instantiated itself, however. A PdaItem instance has no meaning by itself; it has meaning only when it is used as a base class—to share default method implementations across the set of data types that derive from it. These characteristics are indicative of the need for PdaItem to be an abstract class rather than a concrete class. Abstract classes are designed for derivation only. It is not possible to instantiate an abstract class, except in the context of instantiating a class that derives from it. Classes that are not abstract and can instead be instantiated directly are concrete classes.

To define an abstract class, C# requires the abstract modifier to the class definition, as shown in Listing 6.18.

Listing 6.18. Defining an Abstract Class


// Define an abstract class
public abstract class PdaItem

{
  public PdaItem(string name)
  {
      Name = name;
  }

  public virtual string Name { get; set; }
}

public class Program
{
  public static void Main()
  {
      PdaItem item;
      // ERROR:  Cannot create an instance of the abstract class
      // item = new PdaItem("Inigo Montoya");
  }
}

Although abstract classes cannot be instantiated, this restriction is a minor characteristic of an abstract class. Their primary significance is achieved when abstract classes include abstract members. An abstract member is a method or property that has no implementation. Its purpose is to force all derived classes to provide the implementation.

Consider Listing 6.19.

Listing 6.19. Defining Abstract Members


// Define an abstract class
public abstract class PdaItem
{
  public PdaItem(string name)
  {
      Name = name;
  }

  public virtual string Name { get; set; }
  public abstract string GetSummary();

}

public class Contact : PdaItem
{
    public override string Name
    {
        get
        {
            return FirstName + " " + LastName;
        }

        set
        {
            string[] names = value.Split(' ');
            // Error handling not shown.
            FirstName = names[0];
            LastName = names[1];
        }
    }

    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Address { get; set; }

  public override string GetSummary()
  {
      return string.Format(
          "FirstName: {0} "
          + "LastName: {1} "
          + "Address: {2}", FirstName, LastName, Address);
  }


  // ...
}

public class Appointment : PdaItem
{
  public Appointment(string name) :
    base(name)
  {
    Name = name;
  }

  public DateTime StartDateTime  { get; set; }
  public DateTime EndDateTime  { get; set; }
  public string Location  { get; set; }

  // ...

  public override string GetSummary()
  {
        return string.Format(
            "Subject: {0}" + Environment.NewLine
            + "Start: {1}"  + Environment.NewLine
            + "End: {2}" + Environment.NewLine
            + "Location: {3}",
            Name, StartDateTime, EndDateTime, Location);
  }
}

Listing 6.19 defines the GetSummary() member as abstract, and therefore, it doesn’t include any implementation. Then, the code overrides it within Contact and provides the implementation. Because abstract members are supposed to be overridden, such members are automatically virtual and cannot be declared so explicitly. In addition, abstract members cannot be private because derived classes would not be able to see them.

It is surprisingly difficult to develop a well-designed object hierarchy. For this reason, when programming abstract types, be sure to implement at least one (preferably more) concrete type that derives from the abstract type in order to validate the design.


Note

Abstract members must be overridden, and therefore are automatically virtual and cannot be declared so explicitly.



Language Contrast: C++—Pure Virtual Functions

C++ allows for the definition of abstract functions using the cryptic notation =0. These functions are called pure virtual functions in C++. In contrast with C#, however, C++ does not require the class itself to have any special declaration. Unlike C#’s abstract class modifier, C++ has no class declaration change when the class includes pure virtual functions.


If you provide no GetSummary() implementation in Contact, the compiler will report an error.


Note

By declaring an abstract member, the abstract class programmer states that in order to form an “is a” relationship between a concrete class and an abstract base class (that is, a PdaItem), it is necessary to implement the abstract members, the members for which the abstract class could not provide an appropriate default implementation.


Abstract members are intended to be a way to enable polymorphism. The base class specifies the signature of the method and the derived class provides implementation (see Listing 6.20).

Listing 6.20. Using Polymorphism to List the PdaItems


public class Program
{
  public static void Main()
  {
      PdaItem[] pda = new PdaItem[3];

      Contact contact = new Contact("Sherlock Holmes");
      contact.Address = "221B Baker Street, London, England";
      pda[0] = contact;

      Appointment appointment =
         new Appointment("Soccer tournament");
      appointment.StartDateTime = new DateTime(2008, 7, 18);
      appointment.EndDateTime = new DateTime(2008, 7, 19);
      appointment.Location = "Estádio da Machava";
      pda[1] = appointment;

      contact = new Contact("Hercule Poirot");
      contact.Address =
          "Apt 56B, Whitehaven Mansions, Sandhurst Sq, London";
      pda[2] = contact;

      List(pda);
  }

  public static void List(PdaItem[] items)
  {
      // Implemented using polymorphism. The derived
      // type knows the specifics of implementing
      // GetSummary().
      foreach (PdaItem item in items)
      {
          Console.WriteLine("________");
          Console.WriteLine(item.GetSummary());
      }
  }
}

The results of Listing 6.20 appear in Output 6.5.

Output 6.5.

________
FirstName: Sherlock
LastName: Holmes
Address: 221B Baker Street, London, England
________
Subject: Soccer tournament
Start: 7/18/2008 12:00:00 AM
End: 7/19/2008 12:00:00 AM
Location: Estádio da Machava
________
FirstName: Hercule
LastName: Poirot
Address: Apt 56B, Whitehaven Mansions, Sandhurst Sq, London

In this way, you can call the method on the base class but the implementation is specific to the derived class.

All Classes Derive from System.Object

Given any class, whether a custom class or one built into the system, the methods shown in Table 6.2 will be defined.

Table 6.2. Members of System.Object

Image

All of these methods appear on all objects through inheritance; all classes derive (either directly or via an inheritance chain) from object. Even literals include these methods, enabling somewhat peculiar-looking code such as this:

Console.WriteLine( 42.ToString() );

Even class definitions that don’t have any explicit derivation from object derive from object anyway. The two declarations for PdaItem in Listing 6.21, therefore, result in identical CIL.

Listing 6.21. System.Object Derivation Implied When No Derivation Is Specified Explicitly


public class PdaItem
{
  // ...
}

public class PdaItem : object
{
  // ...
}

When the object’s default implementation isn’t sufficient, programmers can override one or more of the three virtual methods. Chapter 9 describes the details for doing this.

Verifying the Underlying Type with the is Operator

Because C# allows casting down the inheritance chain, it is sometimes desirable to determine what the underlying type is before attempting a conversion. Also, checking the type may be necessary for type-specific actions where polymorphism was not implemented. To determine the underlying type, C# provides the is operator (see Listing 6.22).

Listing 6.22. is Operator Determining the Underlying Type


public static void Save(object data)
{
    if (data is string)
    {
        data = Encrypt((string) data);
    }

    // ...
}

Listing 6.22 encrypts the data if the underlying type is a string. This is significantly different from encrypting, simply because it successfully casts to a string since many types support casting to a string, and yet their underlying type is not a string.

Although this capability is important, you should consider polymorphism prior to using the is operator. Polymorphism enables support for expanding a behavior to other data types without modifying the implementation that defines the behavior. For example, deriving from a common base type and then using that type as the parameter to the Save() method avoids having to check for string explicitly and enables other data types to support encryption during the save by deriving from the same base type.

Conversion Using the as Operator

The advantage of the is operator is that it enables verification that a data item is of a particular type. The as operator goes one step further: It attempts a conversion to a particular data type and assigns null if the source type is not inherently (within the inheritance chain) of the target type. This is significant because it avoids the exception that could result from casting. Listing 6.23 demonstrates using the as operator.

Listing 6.23. Data Conversion Using the as Operator


object Print(IDocument document)
{
  if(thing != null)
  {
       // Print document...
  }
  else
  {
  }
}

static void Main()
{
  object data;

  // ...

  Print(data as Document);
}

By using the as operator, you are able to avoid additional try/catch handling code if the conversion is invalid, because the as operator provides a way to attempt a cast without throwing an exception if the cast fails.

One advantage of the is operator over the as operator is that the latter cannot successfully determine the underlying type. The latter potentially casts up or down an inheritance chain, as well as across to types supporting the cast operator. Therefore, unlike the as operator, the is operator can determine the underlying type.

Summary

This chapter discussed how to specialize a class by deriving from it and adding additional methods and properties. This included a discussion of the private and protected access modifiers that control the level of encapsulation.

This chapter also investigated the details of overriding the base class implementation, and alternatively hiding it using the new modifier. To control overriding, C# provides the virtual modifier, which identifies to the deriving class developer which members she intends for derivation. For preventing any derivation altogether you learned about the sealed modifier on the class. Similarly, the sealed modifier on a member prevents further overriding from subclasses.

This chapter ended with a brief discussion of how all types derive from object. Chapter 9 discusses this derivation further, with a look at how object includes three virtual methods with specific rules and guidelines that govern overloading. Before you get there, however, you need to consider another programming paradigm that builds on object-oriented programming: interfaces. This is the subject of Chapter 7.

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

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