[ Previous ]
[ Index ]
[ Next ]
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.
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
{
}
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;
}
}
}
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.
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:
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!
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 ]