Overriding the Base Class


All public and protected members of a base class are inherited in the derived class. 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 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, then subclasses will not be able to override those members.

Language Contrast: JavaVirtual 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 { return _Name; }        set { _Name = value; }   }   private string _Name;   // ... } 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 { return _FirstName; }       set { _FirstName = value; }  }  private string _FirstName;  public string LastName  {       get { return _LastName; }       set { _LastName = value; }  }  private string _LastName;  // ... } 

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 hide 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 codebreaking 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 InternalRun()     {         Start();         Run();         Stop();     }     public virtual void Run()     {         // Default implementation     }       public void Stop()     {         // Critical code     } } 

Furthermore, the Controller programmer should call Run() via InternalRun(), thereby forcing calls to Start() and Stop() while still allowing a custom implementation of Run() within a derived class.

Another drawback of virtual methods is that only at execution time is it possible to evaluate an inheritance chain to determine the most derived implementation. This results in a slight performance reduction. Rarely is this enough to avoid a virtual method when the design warrants it; however, this is a second factor indicating that virtual methods should be declared intentionally. In summary, virtual methods provide default implementations only, implementations that derived classes could override entirely. To make the best choice about virtual methods consider (and preferably implement) a specific scenario of why to define the virtual method.

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 principal of calling the most derived virtual member, even if the derived constructor has not completely executed.


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 are not allowed.

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.

[View full width]

 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.

Table 6.1. Why the New Modifier?

Activity

Code

Programmer A defines class Person that includes properties FirstName and LastName.

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


Programmer B derives from Person and defines Contact with the additional property, Name. In addition, she defines the Program class whose Main() method instantiates Contact, assigns Name, and then prints out the name.

public class Contact : Person   {    public string Name    {        get        {            return FirstName + " " + LastName;        }        set        {            string names =value.Split(' ');            // Error handling not shown.            FirstName = names[0];            LastName = names[1];        }    } } 


Later, Programmer A adds the Name property but instead of implementing the getter as FirstName + " " + LastName, she implements it as LastName + ", " + FirstName. Furthermore, she doesn't define the property as virtual, and she uses the property in a DisplayName() method.

public class Person  {    public string Name    {        get        {            return LastName + ", " + FirstName;        }        set        {            string names =value.Split(', ');            // Error handling not shown.                 LastName = names[0];                            FirstName = names[1];        }   }   public static void Display(Person person)   {       // Display <LastName>, <FirstName>       Console.WriteLine( person.Name );   }      } 



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, then a member in the base class will behave as though no method was declared on the derived class (if the derived implementation redeclared 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, then 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... new modifier assumed.        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 code the compiler generates. 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().

base Member

In choosing to redeclare 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 base 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 as well, 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.

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, then 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.




Essential C# 2.0
Essential C# 2.0
ISBN: 0321150775
EAN: 2147483647
Year: 2007
Pages: 185

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