C# Coding Solutions—Using Inheritance Effectively
Microsoft .NET Framework, ASP.NET, Visual C# (CSharp, C Sharp, C-Sharp) Developer Training, Visual Studio
Using Inheritance Effectively
Now we’ll focus on how to use inheritance in .NET. Many people consider inheritance an idea past its prime. .NET, though, has improved inheritance and solved many of its associated problems by using an explicit inheritance model.
One of the problems usually associated with inheritance is the fragile base class: Due to inheritance, changes in a base class may affect derived classes. This behavior should not happen, and indicates that inheritance implicitly creates a tightly coupled situation. The following Java-language example illustrates the fragile-base-class problem. (I am not picking on Java—other languages also have this problem. I am using Java because Java and C# syntax is almost identical.)
class BaseClass { public void display(String buffer) { System.out.println("My string (" + buffer + ")"); } public void callMultipleTimes(String[] buffers) { for (int c1 = 0; c1 < buffers.length; c1++) { display(buffers[c1]); } } }
BaseClass has two methods: display and callMultipleTimes. The method display is used to generate some text to the console. The method callMultipleTimes accepts an array of string buffers. In the implementation of callMultipleTimes the array is iterated, and every foreach iteration of an element of the array is displayed.
Functionally, BaseClass provides a method to generate output (display) and a helper method (callMultipleTimes) to generate output for an array of strings. In abstract terms, BaseClass defines a method display that is called multiple times by callMultipleTimes.
The following code illustrates how to use BaseClass:
public void doCall(BaseClass cls) { cls.callMultipleTimes(new String[] { "buffer1", "buffer2", "buffer3" }); } public void initial() { doCall( new BaseClass()); }
The method initial instantiates the type BaseClass and then calls the method doCall. In the implementation of doCall the method callMultipleTimes is called with an array of three buffers. Calling callMultipleTimes will end up calling display three times.
Suppose the code is called version 1.0 and is released. Some time later the developer would like to use the same code elsewhere, and realizes that the functionality of display needs to be modified. Instead of changing BaseClass, the developer creates a new class and overrides the base functionality. The new class, Derived, is illustrated here:
class Derived extends BaseClass { public void display(String buffer) { super.display("{" + buffer + "}"); } }
The new class Derived subclasses BaseClass and overrides the method display. The new implementation of display calls the base version of display while modifying the parameter buffer. To have the client call the new code, the method initial is changed as follows:
public void initial() { doCall( new DerivedClass()); }
The doCall method is kept as is, and when the client code is executed the following output is generated:
My string ({buffer1})
My string ({buffer2})
My string ({buffer3})
Calling the method callMultipleTimes calls display, and because of the way inheritance works, the new display method is called. For sake of argument, this behavior is desired and fulfills our needs.
However, problems arise if the developer decides to change the behavior of the method BaseClass.callMultipleMethods to the following:
class BaseClass { public void display(String buffer) { System.out.println("My string (" + buffer + ")"); } public void callMultipleTimes(String[] buffers) { for (int c1 = 0; c1 < buffers.length; c1++) { System.out.println("My string (" + buffers[ c1] + ")"); } } }
In the modified version of callMultipleTimes, the method display is not called. Instead, the code from display has been copied and pasted into callMultipleTimes. Let’s not argue about the intelligence of the code change. The reality is that the code has been changed and the change results in a major change of functionality. The result is disastrous because if the client code is executed, where Derived is instantiated, a call to Derived.display is expected by the client code, but BaseClass.display is called and not Derived.display as was expected. That’s because the base class changed its implementation, causing a problem in the subclass. This is the fragile-base-class problem.
A programmer will look at the code and quickly point to the fact that callMultipleTimes has broken a contract in that it does not call display. But this is not correct, as there is no contract that says callMultipleTimes must call display. The problem is in the Java language, because it is not possible to know what defines a contract when inheritance is involved. In contrast, if you were to use interfaces, the contract is the interface, and if you were not to implement the complete interface, a compiler error would result indicating a breaking of a contract. Again, I am not picking on Java, as other programming languages have the same problem.
What makes .NET powerful is its ability to enforce a contract at the interface level and in an inheritance hierarchy. In .NET, the fragile-base-class problem does still exist, but it is brought to the developer’s attention in the form of compiler warnings.
Following is a simple port of the original working application before the modification of callMultipleTimes that changed the behavior of the base class:
class BaseClass { public void Display(String buffer) { Console.WriteLine( "My string (" + buffer + ")"); } public void CallMultipleTimes(String[] buffers) { for (int c1 = 0; c1 < buffers.Length; c1++) { Display(buffers[c1]); } } } class Derived : BaseClass { public new void Display(String buffer) { base.Method("{" + buffer + "}"); } } [TestFixture] public class Tests { void DoCall(BaseClass cls) { cls.CallMultipleTimes(new String[] { "buffer1", "buffer2", "buffer3" }); } void Initial() { DoCall(new BaseClass()); } void Second() { DoCall(new Derived()); } [Test] public void RunTests() { Initial(); Second(); } }
The ported code has one technical modification: the new modifier on the method Derived.Display. The one little change has a very big ramification—the .NET-generated output is very different from the output generated by Java:
My string (buffer1) My string (buffer2) My string (buffer3) My string (buffer1) My string (buffer2) My string (buffer3)
The difference is because the new keyword has changed how the classes Derived and BaseClass behave. The new keyword in the example says that when calling the method Display, use the version from Derived if the type being called is Derived. If, however, the type doing the calling is BaseClass, then use the functionality from BaseClass. This means that when the method DoCall is executed, the type is BaseClass, and that results in the method BaseClass.Display being called.
Using the new keyword does not cause a fragile-base-class problem because the inheritance chain does not come into play. The idea of the new keyword is that whatever functionality was defined at a base class level is explicitly overwritten. The new keyword is a contract that forces a separation of base class and derived class functionality. By having to use the new keyword, the developer is explicitly making up his mind as to how the inheritance hierarchy will work. This is good for the fragile-base-class problem, but bad for inheritance in general. The reason why this is bad for inheritance is because the developer of Derived wanted to override the functionality in BaseClass, but is not allowed to do so by the original developer of BaseClass.
The original developer of BaseClass explicitly said none of his methods could be overwritten because he might change the functionality of BaseClass. The other option that the original developer could use is to declare the methods to be overwritten, thus generating the same output as in the Java example. Following is that source code:
class BaseClass { public virtual void Display(String buffer) { Console.WriteLine("My string (" + buffer + ")"); } public void CallMultipleTimes(String[] buffers) { for (int c1 = 0; c1 < buffers.Length; c1++) { Display(buffers[c1]); } } } class Derived : BaseClass { public override void Display(String buffer) { base.Display("{" + buffer + "}"); } }
The modified source code has two new keywords: virtual and override. The virtual keyword indicates that the class is exposing a method that can be overridden. The override keyword implements a method that overrides a base method. In this modified example, the original developer of BaseClass is telling that whoever subclasses BaseClass can override the functionality of Display. The original developer who implemented CallMultipleTimes to call Display will knowingly create a contract where CallMultipleTimes calls Display, which may be overwritten. Thus, the original developer will not come to the crazy idea of breaking the implied contract of CallMultipleTimes.
Of course, it doesn’t mean that the original developer cannot change and break the contract, thus creating a fragile-base-class problem. What you need to remember is that .NET does not stop you from shooting yourself in the foot. .NET has the facilities to indicate to you and say, "Dude, you are about to shoot yourself in the foot."
When using inheritance in .NET, remember the following:
- The inheritance model in .NET is contract-driven and geared toward making inheritance predictable and robust. The advantage of the .NET inheritance model is its ability to indicate what to do when two methods in a hierarchy conflict.
- The
newkeyword can be used in an inheritance chain to indicate new functionality. It is possible to use thenewkeyword on a base-class method that was declared to bevirtual.
- The
- Use the
virtualandoverridekeywords when the implementation of a derived class method overrides the implementation of the base class method.
- Use the
|

