<%@ Page Language="VB" MasterPageFile="~/MasterPage.master" AutoEventWireup="false" Inherits="TSPage" title="Doing Objects in VB.NET and C# - INHERITANCE" MetaDescription="An online e-book and fast-track tutorial for experienced developers. Written by Little Rock, Arkansas-based software consultant, Terry Smith." LocalStylesheet="stylesheet.css" %> Previous ] [ Index ] [ Next ]   

6

Inheritance

Microsoft has introduced some freaky "features" that directly violate traditional object-oriented principles. But before we get into that, let's cover the .NET syntax for the basic inheritance principles that you already understand from prior languages. The differences between VB.NET and C# for these basic inheritance constructs are purely syntactical.

Basic Inheritance

First, the syntax for using inheritance is nearly identical to that of using interfaces. VB 'netters should use Inherits instead of Implements. Second, there "is no" multiple inheritance in .NET. Third, if a class inherits from another and also implements interfaces, the base class is listed first followed by one or more interfaces. Here's an example in VB.NET:

Public Class TrustFundSnob
   Inherits RichBigDaddy
   Implements IAmRich

End Class

And here's the same example in C#:

public class TrustFundSnob : RichBigDaddy, IAmRich
{

}

Instead of using the final keyword to make classes non-inheritable, .NET introduces the new and very annoying keywords NotInheritable (Visual Basic .NET) and sealed (C#). The following is a simple example that makes the classes introduced above non-inheritable:

Public NotInheritable Class TrustFundSnob
   Inherits RichBigDaddy
   Implements IAmRich

End Class

and in C#:

public sealed class TrustFundSnob : RichBigDaddy, IAmRich
{

}

Abstract Classes

The next inheritance tool is that of abstract classes. An abstract class is like abstract art. It serves no purpose by itself, but if you inherit from it (or inherit "it", in the case of art), then it can be quite beneficial. In more concrete terms, an abstract class must be inherited from and can not be instantiated on its own. It can also specify members that must be implemented by the inheriting class. An abstract class differs from an interface in that it can provide some of the implementation of a class.

As an example, let's make the RichBigDaddy class abstract. (He lives in a big abstract house anyway.) The magic keywords are MustInherit in Visual Basic .NET and abstract in C#. In the examples below, a YellObscenities method has been added to the RichBigDaddy class and a property called NetWorth added which all inheriting classes must implement. Here's the code in VB.NET:

Public MustInherit Class RichBigDaddy
   Public Sub YellObscenities()
      Trace.WriteLine("Money is bad.")
   End Sub

   Public MustOverride ReadOnly Property NetWorth() As Decimal
End Class

Public Class TrustFundSnob
   Inherits RichBigDaddy

   Public Overrides ReadOnly Property NetWorth() As Decimal
      Get
         Return Decimal.MaxValue
      End Get
   End Property
End Class

Here's the same implementation in C#:

public abstract class RichBigDaddy
{
   public void YellObscenities()
   {
      // Assumes System.Diagnostics is imported.
      Trace.WriteLine("Money is bad.");
   }

   public abstract Decimal NetWorth
   {
      get;
   }
}

public class TrustFundSnob : RichBigDaddy
{
   public override Decimal NetWorth
   {
      get
      {
         return Decimal.MaxValue;
      }
   }
}

Inherited Members

Inheritance of member routines in .NET is more complicated than it ever needed to be. The best way to understand the options (that you didn't ask for in the first place) is to take things one-step at a time. Let's start by reworking our RichBigDaddy class so that is has only one member, PlayPolo:

Public Class RichBigDaddy
   Public Sub PlayPolo()
      Trace.WriteLine("RichBigDaddy::PlayPolo")
   End Sub
End Class

Now, let's try to make the TrustFundSnob override this method:

Public Class TrustFundSnob
   Inherits RichBigDaddy

   Public Sub PlayPolo()
      Trace.WriteLine("TrustFundSnob::PlayPolo")
   End Sub
End Class

If this were C++ or Java, then it would be that easy. The code directly above does compile, but it generates a warning. Get ready. (You may want to remove any children from the room.) Here is what the code actually looks like in the .NET IDE:

Oh no! The big blue squiggly! What are we to do? The next two sections will cover our options. First though, note that the equivalent code in C# gives an equally ominous warning: "The keyword new is required on 'CSharpInheritanceExample.TrustFundSnob.PlayPolo()' because it hides inherited member 'CSharpInheritanceExample.RichBigDaddy.PlayPolo()'". For completeness, here is the code:

public abstract class RichBigDaddy
{
   public void PlayPolo()
   {
      Trace.WriteLine("RichBigDaddy::PlayPolo");
   }
}

public class TrustFundSnob : RichBigDaddy
{
   public void PlayPolo()
   {
      Trace.WriteLine("TrustFundSnob::PlayPolo");
   }
}

A quick side note, after the C# project is built for a second time the warning is not displayed in the Output window and the squiggly mark is removed. You can get the warning and squiggly mark to redisplay by doing a rebuild instead of a normal build. The warning remains after the second build in the VB.NET project.

The Evil Shadow

There are two methods of what we will term modification inheritance in .NET, shadowing and overriding. The default is shadowing. Since it is the default, the code above compiles (and will run) but chunks warnings. To get rid of the warnings, one solution is to add the Shadows and new keywords to the VB.NET and C# examples, respectively. Think of shadowing as evil. It can be. Shadowing allows you to hide a base class member and redefine, or shadow, it with a new method that has a different signature.

Here is another difference between what is allowed in VB.NET versus C#. The Shadows keyword in VB.NET allows you hide a base class member by defining a new member that can optionally have a different access level, return type, and parameter signature. In other words, only the name remains the same. Members hidden in C# using the new keyword can only redefine their access level and return type. The parameter signature must remain the same, or the method is treated as an overload.

Let's examine this in VB.NET first. Simply adding the Shadows keyword between Public and Sub would make our warning go away; however, our TrustFundSnob is a rebel, so he wants to redefine his PlayPolo method instead.

The following code hides, shadows, or redefines (pick your favorite term) the PlayPolo method that TrustFundSnob inherits from RichBigDaddy with a Friend instead of Public access level, a parameter where there was none before, and a Boolean return value:

Public Class TrustFundSnob
   Inherits RichBigDaddy

   Friend Shadows Function PlayPolo(ByVal hours As Integer) As Boolean
      Trace.WriteLine("TrustFundSnob::PlayPolo - " + hours.ToString())
      Return True
   End Function
End Class

Only one method, the one belonging to our child class, is accessible through an instance of the class:

What if we add a second PlayPolo method to TrustFundSnob as an overloaded method? Does it have to declared Shadows too? Yes. All other methods with the same name must be marked with the Shadows keyword.

Now, let's take a look at the Friend access level given to our new PlayPolo method. Our new member hides its parent's member only within the scope of the new member, or in this case, only within the assembly since it is declared as Friend.

To demonstrate, a VB.NET project containing the above code can be rebuilt as a DLL and referenced from another project. Then add the DLL as a project reference from another .NET project. It's then easy to test the access level rule regarding shadowed members:

Only the RichBigDaddy's member is accessible from the other assembly. Now let's make TrustFundSnob's PlayPolo method Public. Another assembly will now see both methods when using Intellisense:

However, the compiler will not allow you to access the RichBigDaddy's PlayPolo method using a TrustFundSnob pointer. To call the parent object's method you must first obtain a pointer to the parent class like so:

Who Killed Liskov?

If any of this is starting to send chills down your spine, then good. It should. VB.NET and C# have both managed to add built-in support for violating one of the most sacred rules of object-oriented programming, that being the Liskov Substitution Principle. You may be unfamiliar with the name, but I bet you will immediately recognize the concept.

The Liskov Substitution Principle (spoken with an echo, or simply LSP as the "in crowd" calls it) was first stated by Barbara Liskov in 1988 ("Data Abstraction and Hierarchy," SIGPLAN Notices, 23,5 to be exact) and states that functions using references to base classes must be able to use objects of derived classes without knowing it. As a simple example, a method that acts upon a Shape object should be able to be passed a Circle object instead (assuming it inherits from Shape) and never know the difference. This just makes sense.

However, if using VB.NET you decided to shadow Shape's Location member within your Circle class and modify its access level, return type, or parameter signature, then you've effectively descended from the ivory tower of nerd prestige and professionalism into the shadowy, murky depths of murder! You're just as guilty if you used new in C# to make Location's access level private or to modify its return type. Code creep!

Rise of the Overridden

One day the King will return and the angel of the lake will arise again and return Excalibur to its rightful owner and… OK, I'm getting a bit carried away I suppose. Hopefully, you now understand that you should rarely use shadowing in .NET, and if you do, know that your analysis and/or problem solution is mostly likely wrong. In other words, if the IS-A relationship doesn't apply then you should be using another technique, perhaps interfaces.

We started this discussion with the TrustFundSnob class trying to override the implementation logic (or "implementation leisure" in this case) of the PlayPolo method from its RichBigDaddy parent. In such a case where you want to override a parent method you should not take the .NET IDE's advice of using the Shadows or new keywords. Instead, you want to use Overrides and override in VB.NET and C#, respectively and respectfully. By using these keywords you're declaring that you are overriding the internal implementation logic of the method, not hiding it or redefining it. Note though that you should only change the internal behavior of a method. If you change its public, external behavior that clients depend upon then you are still violating the Liskov Substitution Principle.

In order to use Overrides and override the method that you are overriding must be declared virtual using the Overridable keyword in VB.NET or virtual in C#. By being declared virtual, the most-derived method is called even if an object is cast to a base type. Take a syntactical look at this VB.NET example:

Public Class RichBigDaddy
   Public Overridable Sub PlayPolo()
      Trace.WriteLine("RichBigDaddy::PlayPolo")
   End Sub
End Class

Public Class TrustFundSnob
   Inherits RichBigDaddy

   Public Overrides Sub PlayPolo()
      Trace.WriteLine("TrustFundSnob::PlayPolo")
   End Sub
End Class

And here is the equivalent in C#:

public class RichBigDaddy
{
   public virtual void PlayPolo()
   {
      Trace.WriteLine("RichBigDaddy::PlayPolo");
   }
}

public class TrustFundSnob : RichBigDaddy
{
   public override void PlayPolo()
   {
      Trace.WriteLine("TrustFundSnob::PlayPolo");
   }
}

One important lesson to take away from this is that if you are developing base classes for an application, and don't wish to revisit them for modification in the future, then you should take a hard look at which instance methods may need to be declared virtual so that you have the flexibility of overriding them in derived classes later on.

Previous ] [ Index ] [ Next ]   

Amazon Honor System Click Here to Pay Learn More