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.
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.
Listing 6.10 shows an example of property overriding.
Listing 6.10. Overriding a Property
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.
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
Output 6.1 shows the results of Listing 6.11.
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
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
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.
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.
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.
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.
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
These results occur for the following reasons.
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.
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
In this example, the use of the sealed modifier on class B's Method() declaration prevents C's overriding of Method().
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
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.
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
By identifying the base constructor in the code, you let the runtime know which base constructor to invoke before invoking the derived class constructor.