It is reasonably common to want to extend a given type to add functionality, 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.

Figure 6.1. Refactoring into a Base Class

The common items in this case are DateTimeCreated, DateTimeLastUpdated, Name, ObjectKey, and the like. Through derivation, the methods defined on the base class, PdaItem, are accessible from all subclasses of PdaItem.

When defining 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 { return _Name; }       set { _Name = value; }  }  private string _Name;  public string DateTimeLastUpdate  {       get { return _DateTimeLastUpdate; }       set { _DateTimeLastUpdate = value;}  }  private string _DateTimeLastUpdate; } ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ // Define the Contact class as inheriting the PdaItem class     public class Contact : PdaItem                                   {  public string Address  {       get { return _Address; }       set { _Address = value; }  }  private string _Address;  public string Phone  {  get { return _Phone; }  set { _Phone = value; }  }  private string _Phone; } 

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 exposed members of its base class inheritance chain combined (see Listing 6.3).

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 objects that don't have some other derivation will derive from object, regardless of whether it is specified.

Casting between Base and Derived Types

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

Listing 6.4. Implicit Base Type Casting

 public class Program {    pulic static void Main()    {          // Derived types can be cast implicitly 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 PdaItem. This is known as an implicit cast because no specific operator is required and the conversion will, on principal, always succeed; it will not throw an exception.

Beginner Topic: Casting within the Inheritance Chain

The cast to a base class does not instantiate a new instance. Instead, the same instance is simply referred to as the base type and the capabilities (the accessible members) are those of the base type. It is just like referring to a CD as a storage device. Since not all storage devices support an eject operation, a CD that is cast to a storage device cannot be ejected either, and a call to storageDevice.Eject() would not compile even though the instantiated object may have been a CD object that supported the Eject() method.

Similarly, casting down from the base class to the derived cast simply begins referring to the type more specifically, expanding the available operations. The restriction is that the actual instantiated type must be an instance of the targeted type (or something derived from it.)

The reverse, however, is not true. A PdaItem is not necessarily a Contact; it could be an Appointment or some other undefined, classderived 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 runtime, throwing an exception if in fact the base type 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 = (Contact)contact;

or even when no cast is necessary:

contact = (Contact)contact;

Although casting between Contact and PdaItem is fully supported, casting in either direction between arrays of these types is not supported. In other words, the following, known as covariance, will not compile:

PdaItem[] pdaItems = (PdaItem[])new Contact[42];

The reverse cast, contravariance, is not supported either.

Support for Parameter Covariance and Contravariance

C#'s inheritance implementation doesn't support either covariance or contravariance. Covariance allows overriding a member by supplying a method in the derived class to be a more specialized type. For example, covariance would allow the method on a derived class, void Draw(Widget part), to override the base class, perhaps an abstract member, void Draw(Part part), where Widget derives from Part.

The problem with that covariance is that the base class allowed for any Part to be drawn, but the derived type allows for only the drawing of type Widget. Therefore, does the derived type handle the drawing of Part's? Presumably it should, but not via the implementation of void Draw(Widget part), because it handles only Widget. Effectively, therefore, void Draw(Widget part) does not override void Draw(Part part), but instead, it overloads it, providing a second method with the same name. Covariance is especially questionable when overriding abstract members. An abstract member requires that the derived class implements a specific signature, and if the derived class specializes the signature, narrowing what is actually supported, then the full functionality required by the abstract method will be lacking.

Contravariance occurs when the derived type's members have broader signatures than the base type's signature. For example, the base class could have a method, void Add(Widget widget), and the derived class would override the method with void Add(Part part). This would allow addition of a Part to the class via the derived method, but since the base class always expects a Widget, it may call into the added item as though it was a Widget and fail.

Keeping with the strong type semantics of C#, neither covariance nor contravariance is supported.

Advanced Topic: Defining Custom Conversions

Casting between types is not limited to types within a single inheritance chain. It is possible to cast entirely unrelated types as well. The key is the provision of a conversion operator between the two types. C# allows types to include either explicit or implicit cast operators. Any time the operation could possibly fail, such as in a cast from long to int, developers should choose to define an explicit cast operator. This warns developers performing the cast to do so only when they are certain the cast will succeed, or else to be prepared to catch the exception if it doesn't. They should also use explicit casts over an implicit cast when the conversion is lossy. Converting from a float to an int, for example, truncates the decimal, which a return cast (from int back to float) would not recover.

Listing 6.5 shows implicit and explicit cast operators for Address to string and vice versa.

Listing 6.5. Defining Cast Operators

 class Address {   // ...   public static implicit operator string(       Address address)   {       // ...   }   public static explicit operator Address(       string addressText)   {       // ...   } } 

In this case, you have an implicit cast from Address to string because all Address objects can be converted successfully to a string. However, you have an explicit cast from string to Address because strings will not necessarily be valid addresses.

private Access Modifier

All public members of a base class are available to the derived class. However, private members are not. For example, in Listing 6.6, the private field, _Name, is not available on Contact.

Listing 6.6. Private Members Are Not Inherited

 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 principal of encapsulation, derived classes cannot access members declared as private. 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.

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 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");       // ...  } } ___________________________________________________________________________ ___________________________________________________________________________ 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();                 } } 

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.

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 singleinheritance 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 significant 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 multipleinheritance class structure, the general solution is to use aggregation. 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).

Figure 6.2. Working around 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 resulting code.

Listing 6.8. Working around Single Inheritance Using Aggregation

 public class PdaItem {   protected Guid ObjectKey   {       get { return _ObjectKey; }       set { _ObjectKey = value; }   }   private Guid _ObjectKey;   public virtual string Name   {       get { return _Name; }       set { _Name = value; }   }   private string _Name;   public string DateTimeLastUpdate   {       get { return _DateTimeLastUpdate; }       set { _DateTimeLastUpdate = value; }   }   private string _DateTimeLastUpdate; } public class Person {   public string FirstName   {       get { return _FirstName; }       set { _FirstName = value; }   }   private string _FirstName;   public string LastName   {       get { return _LastName; }       set { _LastName = value; }   }   private string _LastName; } public class Contact : PdaItem {   private Person InternalPerson   {        get { return  _Person; }                                                 set { _Person = value; }                                             }   private Person _Person;   public string FirstName   {        get { return InternalPerson.FirstName; }                                   set { InternalPerson.FirstName = value; }                             }   public string LastName   {       get { return InternalPerson.LastName; }       set { InternalPerson.LastName = value; }   }     public string Address   {       get { return _Address; }       set { _Address = value; }   }   private string _Address;   public string Phone   {       get { return _Phone; }       set { _Phone = value; }   }   private string _Phone; } 

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. Furthermore, a slight performance overhead is associated with derived classes. To avoid some of these concerns, classes can be marked as sealed (see Listing 6.9).

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. The string type is another example of a type that uses the sealed modifier to prevent derivation.

Essential C# 2.0
Essential C# 2.0
ISBN: 0321150775
EAN: 2147483647
Year: 2007
Pages: 185 © 2008-2017.
If you may any questions please contact us: