Chapter 6. Inheritance and Polymorphism Gotchas

Though you can gain a superficial knowledge of C++ in a couple of weeks, and become adept at it in a few years, an experienced C++ programmer would probably never claim to be a master of the language. Its complexities are mind-boggling. (I know people who have made career changes because of it.) More recent languages have eliminated a number of its tricky features and made it much easier to write good code. However, they have their own set of challenges. While they have taken a few steps forward on a number of issues, they have gone a few steps backward on some others. This can impact the extensibility of your code, break polymorphism, and make it harder for you to derive from your classes. Learning what these challenges are, and avoiding them or handling them judiciously, is imperative to effective development. In this chapter I will discuss the things you need to be aware of in the areas of inheritance and polymorphism so you can make best use of these important concepts.

GOTCHA #42 Runtime Type Identification can hurt extensibility

In working with an inheritance hierarchy, how do you know which type an instance belongs to? Given a reference, you can use casting to convert it to the type you desire. However, the problem with casting is that if the conversion is not valid, it results in a runtime InvalidCastException. This is ugly and you must avoid it at all costs. What alternatives do you have?

.NET languages allow you to determine the type of an object at runtime. This feature is called Runtime Type Identification (RTTI). In C# you use the keyword is; in VB.NET you use TypeOf...Is. I will refer to these as RTTI operators.

What are the consequences of using RTTI operators extensively or arbitrarily? I will discuss the dark side of RTTI in a very simple example, shown in Example 6-1.

Example 6-1. Working with object hierarchy

C# (RTTI)

//Animal.cs
public class Animal
{
    public void Eat() { Console.WriteLine("Animal eating"); }
}

//Dog.cs
public class Dog : Animal
{
    public void Bark() { Console.WriteLine("Dog barking"); }
}

//Cat.cs
public class Cat : Animal
{
    public void Meow() { Console.WriteLine("Cat Meowing"); }
}

//Trainer.cs
public class Trainer
{
    public void Train(Animal anAnimal)
    {
        anAnimal.Eat();
        //Using casting

        Dog doggie = (Dog) anAnimal;
        doggie.Bark();

        Cat aCat = (Cat) anAnimal;
        aCat.Meow();
        }
}

//Test.cs
class Test
{
    [STAThread]
    static void Main(string[] args)
    {
        Dog spencer = new Dog();
        Cat snow = new Cat();
        Trainer jimmy = new Trainer();

        jimmy.Train(spencer);
        jimmy.Train(snow);
    }
}

VB.NET (RTTI)

'Animal.vb
Public Class Animal
    Public Sub Eat()
        Console.WriteLine("Animal eating")
    End Sub
End Class

'Dog.vb
Public Class Dog
    Inherits Animal

    Public Sub Bark()
        Console.WriteLine("Dog barking")
    End Sub
End Class


'Cat.vb
Public Class Cat
    Inherits Animal

    Public Sub Meow()
        Console.WriteLine("Cat Meowing")
    End Sub
End Class


'Trainer.vb
Public Class Trainer
    Public Sub Train(ByVal anAnimal As Animal)
        anAnimal.Eat()

        'Using Casting

        Dim doggie As Dog = CType(anAnimal, Dog)
        doggie.Bark()

        Dim aCat As Cat = CType(anAnimal, Cat)
        aCat.Meow()
    End Sub
End Class


'Test.vb
Module Module1

    Sub Main()
        Dim spencer As New Dog
        Dim snow As New Cat
        Dim jimmy As New Trainer

        jimmy.Train(spencer)
        jimmy.Train(snow)
    End Sub

End Module

In this example, the Trainer wants to train an Animal. In the process of training, she first feeds the animal by calling the Eat() method. In the next activity, she wants the animal to express itself. In this example, the animal may be either a Dog or a Cat, so you cast it to these types and call the Bark() and the Meow() methods. But this code, while flawless in compilation, throws an InvalidCastException at runtime, as shown in Figure 6-1.

Output from Example 6-1

Figure 6-1. Output from Example 6-1

The CLR does not like your casting a Dog object to a Cat. (It is only natural that dogs do not like to be treated as cats—do not try that at home.) The worst you can do at this point is to surround the casting code with try/catch statements, suppress the exception in the catch and claim that you have taken care of the situation. This is undesirable for a couple of reasons. For one thing, using exceptions in situations like this is expensive. For another, you have not properly handled the condition where the given Animal is not a type you expect. Casting is ugly.

Let’s consider the use of RTTI . In the code in Example 6-2, I show only the changes to the Trainer class (the only class I have changed).

Example 6-2. Using RTTI

C# (RTTI)

//Trainer.cs
public class Trainer
{
    public void Train(Animal anAnimal)
    {
        anAnimal.Eat();

        //Using RTTI

               if (anAnimal is Dog)
{
            Dog doggie = (Dog) anAnimal;
            doggie.Bark();
}
        else if (anAnimal is Cat)
{
            Cat aCat = (Cat) anAnimal;
            aCat.Meow();
        }
    }
}

VB.NET (RTTI)

'Trainer.vb
Public Class Trainer
    Public Sub Train(ByVal anAnimal As Animal)
        anAnimal.Eat()

        'Using RTTI

        If TypeOf anAnimal Is Dog Then
            Dim doggie As Dog = CType(anAnimal, Dog)
            doggie.Bark()
        ElseIf TypeOf anAnimal Is Cat Then
            Dim aCat As Cat = CType(anAnimal, Cat)
            aCat.Meow()
        End If
    End Sub
End Class

In the Train() method you check to see if, at run time, the given reference points to an instance of Dog. If so, then you perform the cast. Similarly, you check to see if the reference points to an object of Cat and make the cast only if it is. You will not trigger an exception in this case. Figure 6-2 shows the output from the modified program.

Output after the code change in Example 6-2

Figure 6-2. Output after the code change in Example 6-2

Tip

 In C#,
    Dog doggie = anAnimal as Dog;
    if (doggie != null)
    {
        doggie.Bark();
    }
 is equivalent to
    if (anAnimal is Dog)
    {
        Dog doggie = (Dog) anAnimal;
        doggie.Bark();
    }

Both the as and is operators represent the use of RTTI. However, the as operator can only be used with reference types.

Is this better than using casting? Well, at least the exceptions go away. But what happens if you add another type of Animal to your system in the future, say a Horse? When an instance of Horse is sent to the Train() method, it invokes the Eat() method, but not any of the other methods on Horse, for example Neighs(). If you ask the horse if it had a good time at the trainer, it will probably say, “The trainer fed me, then he asked if I was a dog and I said no. He then asked if I was a cat and I said no. Then he just walked away. He is not an equal opportunity trainer.”

The code that uses RTTI in this manner is not extensible. It fails the Open-Closed Principle (OCP), which states that a software module must be open for extension but closed for modification. That is, you should be able to accommodate changes in the requirements by adding small new modules of code, not by changing existing code (see Gotcha #23, "Copy Constructor hampers exensibility“).

While RTTI is better than casting, it is still bad. It is better to rely on polymorphism. You should abstract the methods of the derived class into the base class. In this example, the Bark() of Dog and the Meow() of Cat can be abstracted as, say, MakeNoise() in Animal. However, Animal doesn’t know how to implement that method, so it’s marked as abstract/MustOverride. This alerts derived classes that they are responsible for implementing it. The code in Example 6-3 shows these changes.

Example 6-3. Relying on abstraction and polymorphism

C# (RTTI)

//Animal.cs
public abstract class Animal
{
    public void Eat()
    {
        Console.WriteLine("Animal eating");
    }

    public abstract void MakeNoise();
}

//Dog.cs
public class Dog : Animal
{
    public void Bark()
    {
        Console.WriteLine("Dog barking");
    }

    public override void MakeNoise()
               {
        Bark();
    }

}

//Cat.cs
public class Cat : Animal
{
    public void Meow()
    {
        Console.WriteLine("Cat Meowing");
    }

    public override void MakeNoise()
               {
               Meow();
    }

}

//Trainer.cs
public class Trainer
{
    public void Train(Animal anAnimal)
    {
        anAnimal.Eat();

        //Using Abstraction and Polymorphism

               anAnimal.MakeNoise();
    }
}

VB.NET (RTTI)

'Animal.vb
Public MustInherit Class Animal
    Public Sub Eat()
        Console.WriteLine("Animal eating")
    End Sub

    Public MustOverride Sub MakeNoise()
End Class


'Dog.vb

Public Class Dog
    Inherits Animal

    Public Sub Bark()
        Console.WriteLine("Dog barking")
    End Sub

    Public Overrides Sub MakeNoise()
               Bark()
    End Sub
End Class


'Cat.vb
Public Class Cat
    Inherits Animal

    Public Sub Meow()
        Console.WriteLine("Cat Meowing")
    End Sub

    Public Overrides Sub MakeNoise()
               Meow()
    End Sub
End Class


'Trainer.vb
Public Class Trainer
    Public Sub Train(ByVal anAnimal As Animal)
        anAnimal.Eat()

        'Using Abstraction and Polymorphism

               anAnimal.MakeNoise()
    End Sub
End Class

Now the Trainer class relies on the polymorphic behavior of the MakeNoise() method. This code is far superior to using RTTI, as it’s more extensible. It makes it easier to add different domestic animals who might seek education from this Trainer.

Not all uses of RTTI are bad, though. For example, consider the case where an Animal has a Play() method and a Dog decides that it only likes playing with other Dogs. This code looks like Example 6-4.

Example 6-4. OK use of RTTI

C# (RTTI)

//Animal.cs
public abstract class Animal
{
        public void Eat()
        {
            Console.WriteLine("Animal eating");
        }

        public abstract void MakeNoise();

        public abstract bool Play(Animal other);
}

//Dog.cs
public class Dog : Animal
{
        public void Bark()
        {
            Console.WriteLine("Dog barking");
        }

        public override void MakeNoise()
        {
            Bark();
        }

        public override bool Play(Animal other)
{
    if (other is Dog)
        return true;
    else
        return false;
}

}

VB.NET (RTTI)

'Animal.vb
Public MustInherit Class Animal
    Public Sub Eat()
        Console.WriteLine("Animal eating")
    End Sub

    Public MustOverride Sub MakeNoise()

    Public MustOverride Function Play(ByVal other As Animal) As Boolean
End Class


'Dog.vb

Public Class Dog
    Inherits Animal

    Public Sub Bark()
        Console.WriteLine("Dog barking")
    End Sub


    Public Overrides Sub MakeNoise()
        Bark()
    End Sub

    Public Overrides Function Play(ByVal other As Animal) As Boolean
               If TypeOf other Is Dog Then
    Return True
        Else
    Return False
        End If
    End Function
End Class

The use of RTTI in the Play() method is benign. It checks to see if Other refers to an instance of its own type. At this point, there is no extensibility issue if a Dog never wants to play with any animals other than Dogs. Of course, if the Dog changes its mind, you’ll have to externalize the rule (maybe in a configuration file), or apply a solution like the Visitor pattern. (Refer to [Freeman04, Gamma95] for more details on patterns.) Those would eliminate the use of RTTI here as well.

IN A NUTSHELL

Use RTTI sparingly. Do not use it if you are checking against multiple types. Use it only if you do not violate the Open-Closed Principle.

GOTCHA #43 Using new/shadows causes “hideous hiding”

If a method is marked virtual/overridable, the base class tells the compiler not to bind to it statically at compile time, since a derived class may override that method. The method to be called is resolved at runtime. However, if a method is not marked virtual/overridable, then the compiler binds to it statically at compile time.

In C++, hiding occurs when a programmer writes a method in the derived class with the same name and signature as a method not marked virtual in the base class. Hiding can also result from virtual methods that differ in signature between base and derived classes (see Gotcha #47, "Signature mismatches can lead to method hiding“). Hiding of non-virtual methods in C++ is generally an accident and (hopefully) not the intent. Unfortunately, even though the managed compilers warn you against such mistakes, .NET gives you a legal way to violate the principles of good object-oriented programming—the new/shadows keyword. (The .NET approach to method hiding is like the laws in Las Vegas: “Oh yeah, that’s illegal in most parts of the world, but here that’s just fine and you are most welcome!”)

Warning

You may argue that hiding facilitates versioning—it allows a base class to introduce, in a later version, a method that is already present in the derived class. However, this facility leads to more trouble than it’s worth, as discussed below. I prefer my code to behave consistently in an object-oriented manner (or fail compilation) than to quietly misbehave.

You should expect the same method to be executed on an object no matter how you invoke it—whether through a direct reference to its type, or through a reference to its base type. This is the essence of polymorphism, which says that the actual method invoked is based on the type of the object and not the type of the reference. Hiding works against this. It makes the method that is invoked dependent on the type of the reference and not on the real type of the object. Consider Example 6-5.

Example 6-5. Hiding methods

C# (HidingMess)

//Base.cs
namespace Hiding
{
    public class Base
    {
        public virtual void Method1()
        {
            Console.WriteLine("Base.Method1 called");
        }
        public virtual void Method2()
        {
            Console.WriteLine("Base.Method2 called");
        }
    }
}

//Derived.cs
namespace Hiding
{
    public class Derived : Base
    {
        public override void Method1()
        {
            Console.WriteLine("Derived.Method1 called");
        }

        public new void Method2()
        {
            Console.WriteLine("Derived.Method2 called");
        }
    }
}

//Test.cs
namespace Hiding
{
    class Test
    {
        static void Main(string[] args)
        {
    Derived d = new Derived();
    Base b = d;

            d.Method1();
    d.Method2();

            b.Method1();
    b.Method2();
        }
    }
}

VB.NET (HidingMess)

'Base.vb

Public Class Base
    Public Overridable Sub Method1()
        Console.WriteLine("Base.Method1 called")
    End Sub

    Public Overridable Sub Method2()
        Console.WriteLine("Base.Method2 called")
    End Sub
End Class


'Derived.vb

Public Class Derived
    Inherits Base

    Public Overrides Sub method1()
        Console.WriteLine("Derived.Method1 called")
    End Sub
    Public Shadows Sub method2()
        Console.WriteLine("Derived.Method2 called")
    End Sub
End Class


'Test.vb
 Module Test

    Sub Main()
        Dim d As New Derived
        Dim b As Base = d

        d.Method1()
        d.method2()

        b.Method1()
        b.Method2()
    End Sub

End Module

In this example, Method2() in Derived hides Method2() of Base, while Method1() in Derived overrides Method1() in Base. In Test, you have only one instance of Derived, but two references to it. One reference named d is of type Derived, and the other reference named b is of type Base. Regardless of how you invoke Method1(), the same method Method1() in Derived is called, as shown in the output in Figure 6-3. However, the call to Method2() goes to Base.Method2() if called using b, and to Derived.Method2() if called using d. The method that is actually executed, in the latter case, depends on the type of the reference instead of the type of the object that it refers to; this is an example of hiding.

Output from Example 6-5

Figure 6-3. Output from Example 6-5

Hiding is very anti-object-oriented. After all, the reason you mark a method virtual/overridable is to allow derived classes to provide alternate implementations. If the base class uses virtual/overridable correctly, and the derived class uses override/overrides, a consistent method is invoked on that object without regard to how and where it is accessed. Hiding fundamentally breaks that tenet.

IN A NUTSHELL

Do not mark a method as new/shadows. Avoid this insidious feature.

GOTCHA #44 Compilers are lenient toward forgotten override/overrides

Say a method in the base class is declared virtual/overridable, and you implement a method with the same name and signature in the derived class. What happens if you do not mark the method as override/overrides? When you compile the code and look at the output window in Visual Studio, you will not see any compilation error (see Gotcha #12, "Compiler warnings may not be benign“). However, a warning is generated. This warning tells you, very quietly, that the derived method has been assumed to hide the base class method (see Gotcha #43, "Using new/shadows causes “hideous hiding”“). This is a serious warning that should not have been hidden in the output window. In fact, this should not be considered a warning at all, in my opinion. The compiler should jump out of the computer, grab the programmer by the collar, give him a smack, and demand that he fix the code. (Maybe I’m going a bit too far, but you get the point). Consider Example 6-6.

Example 6-6. Accidental hiding

C# (RememberMarkOverride)

//Base.cs
using System;

namespace MarkOverride
{
    public class Base
    {
        public virtual void Method1()
        {
            Console.WriteLine("Base.Method1 called");
        }
    }
}

//Derived.cs
using System;

namespace MarkOverride
{
    public class Derived : Base
    {
        public void Method1()
        {
            Console.WriteLine("Derived.Method1 called");
        }
    }
}

//Test.cs
using System;

namespace MarkOverride
{
    class Test
    {
        [STAThread]
        static void Main(string[] args)
        {
            Derived d = new Derived();
    Base b = d;

               d.Method1();
    b.Method1();
        }
    }
}

VB.NET (RememberMarkOverride)

'Base.vb
Public Class Base
    Public Overridable Sub Method1()
        Console.WriteLine("Base.Method1 called")
    End Sub
End Class


'Derived.vb
Public Class Derived
    Inherits Base

               Public Sub Method1()
        Console.WriteLine("Derived.Method1 called")
    End Sub
End Class


'Test.vb
Module Test

    Sub Main()
        Dim d As New Derived
        Dim b As Base = d

               d.Method1()
        b.Method1()
    End Sub

End Module

In this example, Method1() is declared virtual/overridable in the Base class. However, in the Derived class Method1() has not been marked as override/overrides. This compiles OK, as you can see in Figure 6-4.

Visual Studio output window for Example 6-6

Figure 6-4. Visual Studio output window for Example 6-6

But this is misleading; a warning actually is printed, but it scrolls out of the output window. The message is:

    warning CS0114: 'MarkOverride.Derived.Method1()' hides inherited member 
'MarkOverride.Base.Method1()'. To make the current member override that implementation, 
add the override keyword. Otherwise add the new keyword.

The output produced by the code is shown in Figure 6-5.

Output from Example 6-6

Figure 6-5. Output from Example 6-6

It would have been nice if the .NET compilers had erred on the side of caution. If a method is not marked override/overrides, why not assume that it overrides its base-class method, rather than assume that it hides it? Better still, shouldn’t this have appeared as a fatal compilation error? Unfortunately it doesn’t, so you need to pay attention to these warnings and treat them seriously. You can (and should) configure the compiler to treat warnings as errors. (See Gotcha #12, "Compiler warnings may not be benign.”)

IN A NUTSHELL

If you are overriding a method, always remember to mark the method as override/overrides. Otherwise, the compiler just gives you a polite warning and assumes the method is intended to be new/shadows.

GOTCHA #45 Compilers lean toward hiding virtual methods

Programmers coming from C++ are used to marking methods as virtual in a derived class when overriding virtual methods of the base class. However, this habit should not be carried over to .NET. Let’s see what happens if you mark a method in the derived class virtual/overridable when a virtual/overridable method with the same name and signature exists in the base class. Consider Example 6-7.

Example 6-7. Another accidental hiding

C# (VirtualInDerived)

//Base.cs
using System;

namespace DerivedMethod
{
    public class Base
    {
        public virtual void Method1()
        {
            Console.WriteLine("Base.Method1 called");
        }
    }
}

//Derived.cs
using System;

namespace DerivedMethod
{
    public class Derived : Base
    {
        public virtual void Method1()
        {
            Console.WriteLine("Derived.Method1 called");
        }
    }
}

//Test.cs
using System;

namespace DerivedMethod
{
    public class Test
    {
        [STAThread]
        static void Main(string[] args)
        {
            Derived d = new Derived();
    Base b = d;

    d.Method1();
    b.Method1();
        }
    }
}

VB.NET (VirtualInDerived)

'Base.vb

Public Class Base
    Public Overridable Sub Method1()
        Console.WriteLine("Base.Method1 called")
    End Sub
End Class


'Derived.vb

Public Class Derived
    Inherits Base

    Public Overridable Sub Method1()
        Console.WriteLine("Derived.Method1 called")
    End Sub
End Class


'Test.vb

Public Class Test
    Shared Sub Main()
        Dim d As New Derived
        Dim b As Base = d

        d.Method1()
        b.Method1()
    End Sub
End Class

Once again, as in Gotcha #44, "Compilers are lenient toward forgotten override/overrides ,” there is no compilation error. You only get a hidden warning message from the C# compiler:

    warning CS0114: 'DerivedMethod.Derived.Method1()' hides inherited member 
'DerivedMethod.Base.Method1()'. To make the current member override that implementation, 
add the override keyword. Otherwise add the new keyword.

A similar message appears in the VB.NET version as well. The output from the program is shown in Figure 6-6.

Output from Example 6-7

Figure 6-6. Output from Example 6-7

IN A NUTSHELL

Do not mark a method as virtual/overridable if your intent is to override a base method. Mark it as override/overrides.

GOTCHA #46 Exception handling can break polymorphism

Liskov’s Substitution Principle (LSP) [Martin03] is one of the cardinal tenets of inheritance and polymorphism. To quote Barbara Liskov:

Any derived class object must be substitutable wherever a base class object is used, without the need for the user to know the difference.

Overridden methods in derived classes must appear to behave the same as their base-class version. After all, methods express a contract that users of a class must be able to rely on. While the number of parameters, their types, and the method return type can be verified syntactically, there are more subtle details not expressed through syntax. One is the exceptions that a method throws. Unlike Java, the .NET languages have no syntax to declare this, so it is often expressed using documentation. Consider Example 6-8.

Example 6-8. Expressing method exceptions

C# (ExceptionInDeriving )

//Base.cs
using System;

namespace ExceptionExample
{
    /// <summary>
    /// A class to illustrate exception when overriding
    /// </summary>
    public class Base
    {
        /// <summary>
        /// Method1 does some thing on input given.
        /// </summary>
        /// <param name="val">Input to work on</param>
        /// <exception
        /// cref="ExceptionExample.InvalidInputException">
        /// Thrown if parameter is less than 0.
        /// </exception>
        public virtual void Method1(int val)
        {
            if (val < 0)
                throw new InvalidInputException();
            //... rest of the code goes here
        }
    }
}

VB.NET (ExceptionInDeriving)

'Base.vb
''' <summary>
''' A class to illustrate exception when overriding
''' </summary>
Public Class Base

    ''' <summary>
    ''' Method1 does some thing on input given.
    ''' </summary>
    ''' <param name="val">Input to work on</param>
    ''' <exception
    ''' cref="ExceptionExample.InvalidInputException">
    ''' Thrown if parameter is less than 0.
    ''' </exception>
    Public Overridable Sub Method1(ByVal val As Integer)
        If val < 0 Then
            Throw New InvalidInputException
        End If

        ' ... rest of the code goes here
    End Sub
End Class

The method Method1() of the Base class throws an exception if the parameter’s value is less than zero. Looking at the NDoc-generated documentation, you see the details of Method1() as shown in Figure 6-7. (Third-party tools can be used to generate XML documentation for VB.NET code. VB.NET in VS 2005 will support XML comments directly.)

Documentation showing details of method exception

Figure 6-7. Documentation showing details of method exception

The documentation specifies that Method1() throws an InvalidInputException if the value of its val parameter is less than zero. If you are using an object of Base, then you can consult the documentation for the behavior of the methods. Based on that information, you write code that uses a Base object, as shown in Example 6-9. Notice that the Use() method, relying on the documentation of the Base class, handles the InvalidInputException.

Example 6-9. An example using a method that throws an exception

C# (ExceptionInDeriving )

//Test.cs
using System;

namespace ExceptionExample
{
    class Test
    {
        private static void Use(Base baseObject, int theValue)
        {
            Console.WriteLine("Executing Use with {0}, {1}",
                baseObject.GetType().Name, theValue);

            try
            {
                baseObject.Method1(theValue);
            }
    catch(InvalidInputException e)
            {
                Console.WriteLine(
                    "{0} was thrown", e.GetType().FullName);
                // Handle the exception here
            }
        }

        //...
    }
}

VB.NET (ExceptionInDeriving)

'Test.vb

Class Test
    Private Shared Sub Use(ByVal baseObject As Base, ByVal theValue As Integer)
        Console.WriteLine("Executing Use with {0}, {1}", _
         baseObject.GetType().Name, theValue)

        Try
            baseObject.Method1(theValue)
        Catch e As InvalidInputException
            Console.WriteLine( _
             "{0} was thrown", e.GetType().FullName)
            ' Handle the exception here
        End Try

    End Sub

    '...


End Class

One implication of Liskov’s Substitution Principle is that a derived class should not throw any exceptions that are not thrown by its base class in methods that it overrides. If it does so, then the base and derived classes have different behavior, and the user needs to know the difference. But suppose a programmer who has never heard of Liskov’s Substitution Principle derives a class from Base, and in the course of his development decides he needs to throw a new type of exception. Example 6-10 shows the consequences.

Example 6-10. Improper overriding of a method that throws an exception

C# (ExceptionInDeriving)

using System;

namespace ExceptionExample
{
    /// <summary>
    /// A Derived class that violates LSP.
    /// </summary>
    public class Derived : Base
    {
        /// <summary>
        /// Method1 does something with input
        /// </summary>
        /// <param name="val">val to work with</param>
        /// <exception cref="InvalidInputException">
        /// thrown if parameter is 0
        /// </exception>
        /// <exception cref="InputMustBeEvenException">
        /// thrown if parameter is not even
        /// </exception>
        public override void Method1(int val)
        {
            if ((val % 2) != 0)
            {
                // Not an even number
                throw new InputMustBeEvenException();
            }

            base.Method1(val);
            //Continue with rest of the code
        }

    }
}

VB.NET (ExceptionInDeriving )

''' <summary>
''' A Derived class that violates LSP.
''' </summary>
Public Class Derived
    Inherits Base

    ''' <summary>
    ''' Method1 does something with input
    ''' </summary>
    ''' <param name="val">val to work with</param>
    ''' <exception cref="InvalidInputException">
    ''' thrown if parameter is 0
    ''' </exception>
    ''' <exception cref="InputMustBeEvenException">
    ''' thrown if parameter is not even
    ''' </exception>
    Public Overrides Sub Method1(ByVal val As Integer)
        If Not val Mod 2 = 0 Then
            'Not an even number
            Throw New InputMustBeEvenException
        End If


        MyBase.Method1(val)
        'Continue with rest of the code
    End Sub

End Class

In this example, Method1() of the Derived class violates LSP, because it throws an exception (InputMustBeEvenException) that differs from Method1()’s behavior in Base. A method call through a reference to the base class must be able to target an object of any derived class without determining its type. Let’s consider the code (as part of the Test class) in Example 6-11.

Example 6-11. Code that fails due to violation of LSP

C# (ExceptionInDeriving)

        //...

        [STAThread]
        static void Main(string[] args)
        {
            Base object1 = new Base();
            Use(object1, -1);
            Use(object1, 3);

            Derived object2 = new Derived();
            Use(object2, -1);
            //Use does not handle InputMustBeEvenException
            Use(object2, 3);
        }

VB.NET (ExceptionInDeriving)

    '...

    Shared Sub Main()
        Dim object1 As New Base
        Use(object1, -1)
        Use(object1, 3)

        Dim object2 As New Derived
        Use(object2, -1)
        'Use does not handle InputMustBeEvenException
        Use(object2, 3)
    End Sub

In the Main() method you create an instance of Base and call the Use() method with that object, first with a value of -1 and then with a value of 3. You then create an object of Derived and call the Use() method with this new instance and the same values as before. The output is shown in Figure 6-8.

Output from Example 6-11

Figure 6-8. Output from Example 6-11

The program generates an unhandled exception when the Use() method is called with the Derived object as its first argument and -1 as its second, because Derived throws an InputMustBeEvenException. This is undesirable behavior and should be avoided.

If you do want to throw a new type of exception, how do you handle that? One possibility is for the InputMustBeEvenException to inherit from InvalidInputException . The output after this change is shown in Figure 6-9.

Output from Example 6-11 if InputMustBeEvenException derives from InvalidInputException

Figure 6-9. Output from Example 6-11 if InputMustBeEvenException derives from InvalidInputException

Note that in this case, the Use() method was able to catch the exception. Since the InputMustBeEvenException inherits from InvalidInputException, it is substitutable. This is still dangerous to a great extent. Why? You are still breaking the contract. Method1() in Base has promised to throw the InvalidInputException only when the parameter is less than zero. The derived class throws the exception (though of Derived type) even when its parameter is greater than zero. This again violates LSP.

IN A NUTSHELL

The overriding method in a derived class should not throw an exception in a way that violates Liskov’s Substitution Principle (LSP). An instance of the derived class must be substitutable wherever an instance of the base class is used.

GOTCHA #47 Signature mismatches can lead to method hiding

The signature of a method is its parameter list: the number, order, and type of parameters it takes. When deriving from a class, you need to pay special attention to the signatures of its methods. If you want to introduce a method with a different signature in the derived class, it is not as simple as just writing the newer method. Going this route, you may end up hiding the base-class methods [Cline99]. This is demonstrated in Example 6-12.

Example 6-12. Hiding methods due to signature mismatch

C# (MethodSignature)

//Base.cs
using System;

namespace MethodSignature
{
    public class Base
    {
        public virtual void Method1(double val)
        {
            Console.WriteLine("Base.Method1(double val) called");
        }
    }
}

//Derived.cs
using System;

namespace MethodSignature
{
    public class Derived : Base
    {
        public virtual void Method1(int val)
        {
            Console.WriteLine("Derived.Method1(int val) called");
        }
    }
}

//Test.cs
using System;

namespace MethodSignature
{
    class Test
    {
        [STAThread]
        static void Main(string[] args)
        {
            Derived d = new Derived();
            Base b = d;

    // b and d refer to the same object now.
    b.Method1(3);
    d.Method1(3);
        }
    }
}

VB.NET (MethodSignature)

'Base.cs

Public Class Base
    Public Overridable Sub Method1(ByVal val As Double)
        Console.WriteLine("Base.Method1(double val) called")
    End Sub
End Class


'Derived.cs

Public Class Derived
    Inherits Base

    Public Overridable Sub Method1(ByVal val As Integer)
        Console.WriteLine("Derived.Method1(int val) called")
    End Sub
End Class


'Test.vb
Module Test

    Sub Main()
        Dim d As New Derived
        Dim b As Base = d

        ' b and d refer to the same object now.
        b.Method1(3)
        d.Method1(3)
    End Sub

End Module

The VB.NET code generates a warning (not an error). The message is:

    warning BC40003: sub 'Method1' shadows an overloadable member declared in the base 
class 'Base'.

If you want to overload the base method, the derived method must be declared Overloads. However, adding the Overloads keyword to Derived.Method1() does not change the behavior; the output is still that shown in Figure 6-10.

The C# version does not even generate a warning.

In this example, a class Base has a virtual/overridable method Method1() that has one parameter of type double. In the class Derived, which inherits from Base, there is also a Method1(), but its one parameter is of type int/Integer. Test.Main() creates an object of Derived and calls its Method1() using a reference of type Derived and then a reference of type Base. The output produced by this program is shown in Figure 6-10.

Output from Example 6-12

Figure 6-10. Output from Example 6-12

Even though you are dealing with one instance of Derived, you end up calling two different methods. What went wrong? When you call Method1(3) on the base reference, the compiler uses the base class’s signature of the method to convert 3 to 3.0 at compile time. (You can see this clearly if you look at the MSIL for Test.Main() in ildasm.exe). However, when Method1(3) is called on the Derived reference, the value of 3 is sent as is. Both the calls are bound dynamically at runtime. Since the derived class has no overriding method that takes a double as a parameter, the call using the Base reference ends up in the Base method itself.

If you really want the method to have a different method signature in the derived class, it is better to overload and override at the same time, as shown in Example 6-13.

Example 6-13. Overriding and overloading to change signature

C# (MethodSignature)

//Derived.cs
using System;

namespace MethodSignature
{
    public class Derived : Base
    {
        public override void Method1(double val)
        {
            Console.WriteLine(
                "Derived.Method1(double val) called");
        }

        public virtual void Method1(int val)
        {
            Console.WriteLine("Derived.Method1(int val) called");
            // You may call Method1((double) val) from here if
            // you want consistent behavior.
        }
    }
}

VB.NET (MethodSignature)

'Derived.cs

Public Class Derived
    Inherits Base

    Public Overloads Overrides Sub Method1(ByVal val As Double)
        Console.WriteLine( _
                "Derived.Method1(double val) called")
    End Sub

    Public Overridable Overloads Sub Method1(ByVal val As Integer)
        Console.WriteLine("Derived.Method1(int val) called")
        ' You may call Method1((double) val) from here if
        ' you want consistent behavior.
    End Sub
End Class

As you can see, in this modified version of the Derived class, you override and overload Method1(). The output is shown in Figure 6-11.

Output after modifications in Example 6-13

Figure 6-11. Output after modifications in Example 6-13

Now both the calls end up in Derived, though in two different methods. In the Derived class, you can take care of providing consistent and appropriate behavior for these two calls.

IN A NUTSHELL

If you want to change the signature of a method in a derived class, then override and overload at the same time. This assures that all calls to a virtual/overridable method whose signature the derived class is changing consistently execute in that class, regardless of the type of reference used. Otherwise, you may end up hiding the method.

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

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