In This Chapter
• Deep Diving System.Object
• NotOverridable
Keyword
• Accessing Base Classes Members
• Practical Inheritance: Building Custom Exceptions
Inheritance is the feature that enables you to design classes that derive from simpler classes, known as base classes. Derived classes implement members defined within the base class and have the possibility of defining new members or of redefining inherited members. Members that a derived class inherits from the base one can be methods, properties, and fields, but such members must have Public
, Protected
, Friend
, or Protected Friend
scope. (See Chapter 7, “Class Fundamentals,” for details about scopes.) In .NET development, inheritance represents a typical “is-a” relationship. For a better understanding, let’s consider real life. When you say “person,” you identify a general individual. Every one of us is a person, with a first name and a last name. But a person also has a gender, either man or woman. In such a situation, a single person is the base class and a woman is a derived class because it inherits the name and last name attributes from the person but also offers a gender attribute. But this is only the first layer. Each man and each woman can have a job, and jobs are made of roles. So a woman can be employed by a company; therefore, as an employee she will have an identification number, a phone number, and an office room number. In this representation there is a deeper inheritance; because a person can be compared to a base class, a woman can be compared to an intermediate base class (also deriving from the person), and the employee is the highest level in the inheritance hierarchy. If we want to go on, we could still define other roles, such as lawyer, program manager, law clerk, pharmacist, and so on. Each of these roles could be represented by a class that derives from the employee role. As you can see, this articulate representation is something that in a development environment such as the .NET Framework enables defining a complex but powerful framework of objects. In this chapter you get a complete overview of the inheritance features in .NET Framework with Visual Basic 2012, and you will learn how you can take advantage of inheritance for both building hierarchical frameworks of custom objects and more easily reusing your code.
Before explaining how inheritance is applied in code, a graphical representation can be useful. Figure 12.1 shows how you can create robust hierarchies of custom objects with inheritance.
You derive a class from a base class using the Inherits
keyword. For example, consider the following implementation of the Person
class that exposes some basic properties:
Public Class Person
Public Property FirstName As String
Public Property LastName As String
'A simplified implementation
Public Function FullName() As String
If FirstName = "" And LastName = "" Then
Throw New _
InvalidOperationException("Both FirstName and LastName are empty")
Else
Return String.Concat(FirstName, " ", LastName)
End If
End Function
End Class
Inherits System.Object
In the .NET Framework development, every class inherits from System.Object
. Due to this, there is no need to add an inherits directive each time you implement a custom type because the Visual Basic compiler will do this for you behind the scenes.
The class also offers a FullName
method that returns the concatenation of the two FirstName
and LastName
properties, providing a simplified and basic validation that is here for demonstration purposes. Now we can design a new class that inherits from Person
, and in this scenario Person
is the base class. The new class is named Contact
and represents a personal contact in our everyday life:
Public Class Contact
Inherits Person
Public Property Email As String
Public Property Phone As String
Public Property BirthDate As Date
Public Property Address As String
End Class
Contact is the derived class. It receives all public members from Person
(in this example both the FirstName
and LastName
properties and the FullName
method) and provides implementation of custom members. This is a typical application of .NET inheritance, in which one derived class inherits from the base class. The .NET Framework does not enable inheriting from multiple classes. You can create a derived class from only a base class. But in some situations multiple levels of inheritance would be required. Continuing the example of the Person
class, you will meet several kinds of people in your life, such as customers, employees of your company, and personal contacts. All these people will have common properties, such as the first name and the last name; therefore, the Person
class can be the base class for each of them, providing a common infrastructure that can then be inherited and customized. But if you consider a customer and an employee, both people will have other common properties, such as a title, a business phone number, and an email address. They will differ in the end because of the proper characteristics of their roles. For this purpose, you can implement intermediate classes that are derived classes from a first base class and base classes for other and more specific ones. For example, you could implement an intermediate infrastructure for both customers and employees. The following code snippet provides a class named BusinessPerson
that inherits from Person
:
Public Class BusinessPerson
Inherits Person
Public Property Email As String
Public Property Title As String
Public Property BusinessPhone As String
End Class
This class inherits the FirstName
and LastName
properties from Person
(other than methods such as ToString
and other public methods exposed by System.Object
) and exposes other common properties for classes with a different scope. For example, both a customer and an employee would need the preceding properties, but each of them needs its own properties. Because of this, the BusinessPerson
class is the intermediate derived class in the hierarchic framework of inheritance. Now consider the following classes, Customer
and Employee
:
Public Class Customer
Inherits BusinessPerson
Public Property CustomerID As Integer
Public Property CompanyName As String
Public Property Address As String
Public Property ContactPerson As String
End Class
Public Class Employee
Inherits BusinessPerson
Public Property EmployeeID As Integer
Public Property HomePhone As String
Public Property MobilePhone As String
Public Property HireDate As Date
End Class
Both classes receive the public properties from BusinessPerson
, and both implement their custom properties according to the particular person they intend to represent. We can summarize the situation as follows:
Customer
exposes the following properties:
• FirstName
and LastName
, provided at a higher level by Person
• Email
, Title
, and BusinessPhone
provided by BusinessPerson
• CustomerID
, CompanyName
, Address
, and ContactPerson
provided by its implementation
Employee
exposes the following properties:
• FirstName
and LastName
, provided at a higher level by Person
• Email
, Title
, and BusinessPhone
provided by BusinessPerson
• EmployeeID
, HomePhone
, MobilePhone
, and HireDate
provided by its implementation
At a higher level, we also exposed a method named FullName
, which has public visibility, so this method is also visible from derived classes.
Members’ Scope and Inheritance
Remember that only the Public
, Protected
, Friend
, and Protected Friend
members can be inherited within derived classes.
When available, you can use derived classes the same way as you would do with any other class, even if you do not know at all that a class derives from another one. The following, simple code demonstrates this:
'Employee inherits from BusinessPerson
'which inherits from Person
Dim emp As New Employee With {.EmployeeID = 1,
.Title = "Dr.",
.LastName = "Del Sole",
.FirstName = "Alessandro",
.Email = "[email protected]",
.BusinessPhone = "000-000-000000",
.HomePhone = "000-000-000000",
.MobilePhone = "000-000-000000",
.HireDate = New Date(5 / 30 / 2012)}
Until now, you’ve seen only properties in an inheritance demonstration. Methods are also influenced by inheritance and by interesting features that make them powerful.
Inheritance and Common Language Specification
The Common Language Specification (CLS) establishes that a CLS-compliant class must inherit only from another CLS-compliant class; otherwise, it will not be CLS-compliant.
As you should remember from Chapter 4, “Data Types and Expressions,” in.NET development all types implicitly derive from System.Object
, considering both reference and value types. Because of the inheritance relationship, custom types also inherit some methods, so you have to know them. Table 12.1 summarizes inherited methods.
You need to understand which members are exposed by System.Object
because they will all be inherited by your custom classes and by all built-in classes in the .NET Framework. I already discussed GetType
and Finalize
methods in Chapter 4 and Chapter 8, respectively. Such methods are inherited by all .NET types. The GetHashCode
method returns the hash that is assigned at runtime by the CLR to a class instance. The following code provides an example:
Dim p As New Object
Dim hashCode As Integer = p.GetHashCode
Console.WriteLine(hashCode.ToString)
On my machine the code produces the following result: 33156464
. This is useful to uniquely identify a class’s instance. New
is the constructor, as described in Chapter 7. When creating custom classes, a constructor is inherited and implicitly defined within classes and constitutes the default constructor. Object
also exposes two shared members—Equals
and ReferenceEquals
—which return a Boolean value. It’s worth mentioning that shared methods are also inherited by derived classes, but they cannot be overridden (as better described in next section). For example, the following code establishes whether both specified objects are considered the same instance:
'Two different instances
Dim firstObject As New Object
Dim secondObject As New Object
'Returns False
Dim test As Boolean = Object.ReferenceEquals(firstObject, secondObject)
Next, the code instead checks whether two instances are considered equal by the compiler:
'Returns False
Dim test As Boolean = Object.Equals(firstObject, secondObject)
There is also an overload of the Equals
method that is instead an instance method. The following code shows an example of instance comparisons using Equals
:
'Returns False
Console.WriteLine(firstObject.Equals(secondObject))
'Copies the reference to the instance
Dim testObject As Object = firstObject
'Returns True
Console.WriteLine(testObject.Equals(firstObject))
For assignments, you can always assign any type to an Object
instance, as demonstrated here:
Dim aPerson As New Person
Dim anObject As Object = aPerson
Because Object
is the mother of all classes, it can receive any assignment. The last method in System.Object
(that you will often use) is ToString
. This method provides a string representation of the object. Because System.Object
is the root in the class hierarchy, this method just returns the pure name of the class. Therefore, the following line of code returns System.Object
:
Console.WriteLine(firstObject.ToString)
But this is not appropriate for value types, in which you need a string representation of a number, or for custom classes, in which you need a custom representation. Taking the example of the famous Person
class, it would be more useful to get a string composed by the last name and the first name instead of the name of the class. Fortunately, the .NET Framework inheritance mechanism provides the capability to change the behavior of inherited members as it is exposed by base classes; this is known as overriding.
Polymorphism is another key concept in object-oriented programming (OOP). As its name implies, polymorphism enables an object to assume different forms. In .NET development, it means you can treat an object as another one, due to the implementation of common members. A first form of polymorphism is when you assign base classes with derived classes. For example, both Contact
and Customer
classes are derived of the Person
class. Now consider the following code:
Dim c As New Contact
Dim cs As New Customer
'C is of type Contact
Dim p As Person = c
The new instance of the Person
class receives an assignment from an instance of the Contact
class. This is always possible because Person
is the parent of Contact
(in which base is the parent of derived). Therefore, you might also have the following assignment:
'Cs is of type Customer
Dim p As Person = cs
In this scenario, Person
is polymorphic in that it can “impersonate” multiple classes that derive from itself.
Retrieving the Actual Type
Use the TypeOf
operator, discussed in Chapter 4, to check whether the polymorphic base class is representing a derived one.
Polymorphism is useful when you need to work with different kinds of objects using one common infrastructure that works the same way with all of them. Now let’s continue with the preceding example. The Person
class exposes the usual FirstName
and LastName
properties also common to Contact
and Customer
. At this point, you can remember how our previous implementations of the Person
class offered a method named FullName
that returns the concatenation of both the LastName
and FirstName
properties. For the current discussion, consider the following simplified version of the FullName
method, as part of the Person
class:
Public Function FullName() As String
Return String.Concat(FirstName, " ", LastName)
End Function
All classes deriving from Person
inherit this method. All deriving classes do need a method of this kind for representing the full name of a person, but they would need different implementations. For example, the full name for a customer should include the company name, whereas the full name for a personal contact should include the title. This means that all classes deriving from Person
will still need the FullName
method (which is part of the commonalities mentioned at the beginning of this section) but with a custom implementation fitting the particular need. For this, the .NET Framework enables realizing polymorphism by overriding members, as the next section describes.
Overriding is the most important part of polymorphism in .NET development, but interfaces also play a role. Chapter 13, “Interfaces,” explains how interfaces complete polymorphism.
When a class derives from another one, it inherits members and the members behave as they are defined in the base class. (For this purpose, remember the scope.) As for other .NET languages, Visual Basic enables redefining inherited methods and properties so that you can change their behavior. This technique is known as overriding and requires a little work on both the base class and the derived class. If you want to provide the ability of overriding a member, in the base class you have to mark the member as Overridable
. Let’s continue the example of the Person
class, defined as follows:
Public Class Person
Public Property FirstName As String
Public Property LastName As String
'Simplified version, with no validation
Public Function FullName() As String
Return String.Concat(LastName, " ",
FirstName)
End Function
End Class
The goal is providing derived classes the capability of overriding the FullName
method so that they can provide a custom and more appropriate version. The method definition must be rewritten as follows:
Public Overridable Function FullName() As String
At this point, we could provide a simplified version of the Contact
class, inheriting from Person
. Such implementation will override the FullName
method to provide a custom result. Let’s begin with the following code:
Public Class Contact
Inherits Person
Public Property Email As String
Public Overrides Function FullName() As String
'By default returns the base class'
'implementation
Return MyBase.FullName()
End Function
End Class
Two things are important here. First, the Overrides
keyword enables you to redefine the behavior of a member that has been marked as Overridable in the base class. Second, Visual Studio automatically provides an implementation that is the behavior established in the base class, due to the MyBase
keyword, which is discussed later. IntelliSense is powerful in this situation, too, because when you type the Overrides
keyword, it shows all overridable members, making it easier to choose what you have to override (see Figure 12.2).
Figure 12.2 can also help you understand which members from System.Object
are overridable. The instance overload of Equals
, GetHashCode
, and ToString
are methods you can redefine. You cannot instead override (neither mark as Overridable
) shared members, and this is demonstrated by the fact that the shared overload of Equals
and ReferenceEquals
are not available in the IntelliSense pop-up window. At this point, we could provide a new implementation of the FullName
method specific for the Contact
class:
Public Overrides Function FullName() As String
'A simplified implementation
'with no validation
Dim result As New Text.StringBuilder
result.Append(Me.FirstName)
result.Append(" ")
result.Append(Me.LastName)
result.Append(", Email:")
result.Append(Me.Email)
Return result.ToString
End Function
Now you can create a new instance of the Contact
class and invoke the FullName
method to understand how overriding changed its behavior:
Dim testContact As New Contact With _
{.FirstName = "Alessandro",
.LastName = "Del Sole",
.Email = "[email protected]"}
Console.WriteLine(testContact.FullName)
The preceding code produces the following result:
Alessandro Del Sole, Email:[email protected]
Such a result is, of course, more meaningful if related to the specific kind of class. Another common situation is redefining the behavior of the ToString
method that is inherited from Object
and that is marked as Overridable
. For example, in the Contact
class we could override ToString
as follows:
Public Overrides Function ToString() As String
Dim result As New Text.StringBuilder
result.Append(Me.FirstName)
result.Append(" ")
result.Append(Me.LastName)
result.Append(", Email:")
result.Append(Me.Email)
Return result.ToString
End Function
We can then invoke this method as follows:
Console.WriteLine(testContact.ToString)
Typically, overriding ToString
is more appropriate if you need to return a string representation of a class, as in the preceding example. The FullName
method is just an example of how you can override a custom method that is defined in a base class and that is not inherited from System.Object
.
Overridden is Overridable
When a member is overridden using the Overrides
keyword, the member is also implicitly Overridable
. Because of this, you cannot use the Overridable
keyword on a member marked with Overrides
; the compiler would throw an error message, requiring you to remove the Overridable
keyword.
You can mark an overridden method or property as NotOverridable
so that derived classes cannot override them again. The NotOverridable
keyword cannot be used versus methods or properties that do not override a base member. Continuing the example of the Contact
class previously defined, the NotOverridable
keyword can be used as follows:
Public NotOverridable Overrides Function FullName() As String
Return String.Concat(MyBase.FullName(), ": ", Email)
End Function
In this way the FullName
method within the Contact
class overrides the base class, but derived classes cannot override it again. NotOverridable
is used only within derived classes that override the base class’s members because in a base class the default behavior for members is that they cannot be overridden unless you explicitly mark them as Overridable
.
You can use the overloading technique described in Chapter 7 within derived classes, with a few differences. You saw in Chapter 7 how overloaded members must not be marked with the Overloads
keyword within a class. Instead, a derived class using the Overloads
keyword is mandatory if you implement a new overload of a member with a different signature. The following code provides an example of overloading the FullName
method within the Contact
class you previously saw:
Public Overloads Function FullName(ByVal Age As Integer)
Return MyBase.FullName & " of age: " & Age.ToString
End Function
If another signature of the member is available within the derived class, the Overloads
keyword is required; otherwise, the compiler throws a warning message saying that another signature is declared as Overrides
or Overloads
. If, instead, no other signatures are available within the derived class, the Overloads
keyword is required to prevent from shadowing the base class’s member. Shadowing is discussed at the end of this chapter, in the section “Shadowing.”
Inheritance is an important feature in OOP with .NET. You might have situations in which inheritance is not a good option—for example, when you want to prevent others from accessing members in the base class. Or there could be custom frameworks implementations in which a high-level class should not be used directly, and therefore it should be always inherited. The Visual Basic language enables accomplishing both scenarios via special keywords, as discussed in the next section.
There are situations in which you might want to prevent inheritance from your classes. This can be useful if you do not want a client to modify in any way the base object’s behavior and its members. To accomplish this, you simply need to mark a class with the NotInheritable
keyword. The following code shows an example of a class that cannot be derived:
Public NotInheritable Class BusinessPerson
Inherits Person
Public Property Email As String
Public Property Title As String
Public Property BusinessPhone As String
End Class
As you can see, the BusinessPerson
class is marked as NotInheritable
and cannot be derived by other classes. It can still inherit from other classes but, obviously, members cannot be marked as Overridable
because they are not inheritable. Another typical example of classes that cannot be inheritable is when you have a class exposing only shared members, as shown in the following code:
<CLSCompliant(True)>
Public NotInheritable Class CompressionHelper
Private Sub New()
End Sub
Public Shared Sub CompressFile(ByVal source As String,
ByVal target As String)
'Your code goes here
End Sub
Public Shared Sub DecompressFile(ByVal compressed As String,
ByVal original As String)
'Your code goes here
End Sub
End Class
The class is also decorated with the CLSCompliant
attribute because such a situation is explicitly established by the Common Language Specification. NotInheritable
is the Visual Basic counterpart of the sealed
keyword in Visual C#. It’s important to know the C# representation because in .NET terminology not-inheritable classes are defined as sealed and many analysis tools use this last word. NotInheritable
classes provide better performance; the compiler can optimize the usage of this type of classes, but you cannot blindly use classes that cannot be inherited only to avoid a small overhead. You should always design classes that fit your needs.
Note
As a better programming practice, developers should always mark classes with NotInheritable
unless they explicitly plan for the class to be inheritable by a consuming class.
Inheritance is powerful because it enables building custom objects’ frameworks. In this context, an object can represent the base infrastructure for different kinds of classes. We saw how the Person
class is the base infrastructure for the Customer
, Employee
, and Contact
derived classes. Because of its implementation, the Person
class does nothing special. It has a generic behavior, and you will probably never create instances of that class; it is more likely that you will create instances of its derived classes. In this scenario, therefore, when you have a general-purpose base class that acts just as a basic infrastructure for derived classes, you can force a class to be inherited so it cannot be used directly. To accomplish this, Visual Basic provides the MustInherit
keyword that states that a class will work only as a base class and cannot be used directly unless you create a derived class.
Abstract Classes
In .NET terminology, classes marked as MustInherit
are also known as abstract classes. This is important to remember because you will often encounter this term within the documentation and in several analysis tools.
The following code shows a new implementation of the Person
class:
Public MustInherit Class Person
Public Property FirstName As String
Public Property LastName As String
End Class
Now you can derive classes only from Person
. Another interesting feature is the capability to force members to be overridden. This can be accomplished using the MustOverride
keyword on methods and properties. Continuing with the example of the Person
class, we can rewrite the FullName
method definition as follows:
Public MustOverride Function FullName() As String
When you mark a method with MustOverride
, the method has no body. This makes sense because, if it must be redefined within a derived class, it would not be very helpful providing a base implementation. The same thing happens with properties, meaning that you will have only a declaration.
When you create a class that inherits from an abstract class (that is, marked as MustInherit
), the only thing you need to pay particular attention to is overriding members. To help developers in such a scenario, the Visual Studio IDE automatically generates members’ stubs for methods and properties marked as MustOverride
in the base abstract class. So, if you create a new implementation of the Contact
class, when you press Enter after typing the Inherits
line of code, Visual Studio generates an empty stub for the FullName
method as follows:
Public Class Contacts
Inherits Person
Public Overrides Function FullName() As String
End Function
End Class
Now you can be sure that all MustOverride
members have an implementation. In our example you might want to complete the code by adding the implementation shown in the “Overriding Members” section in this chapter.
The Common Language Specification contains a small rule regarding abstract classes. This rule establishes that to be CLS-compliant, members in abstract classes must explicitly be marked as CLSCompliant
. The following code provides an example:
<CLSCompliant(True)>
Public MustInherit Class Person
<CLSCompliant(True)> Public Property FirstName As String
<CLSCompliant(True)> Public Property LastName As String
<CLSCompliant(True)> Public MustOverride Function FullName() As String
End Class
Sometimes you need to access the base classes’ members from derived classes. There are several reasons for doing this, so you need to know how. Visual Basic provides two special keywords for invoking base members, MyBase
and MyClass
. Both are discussed in this section.
When you need to get a reference to the base class of the derived class you are working on, you can invoke the MyBase
keyword. This keyword represents an instance of the base class and enables you to work on members as they are exposed by the base class, instead of the ones exposed by the derived class. Consider the following implementation of the Person
class, in which a FullInformation
method provides a representation of all the information supplied to the class:
Public Class Person
Public Property FirstName As String
Public Property LastName As String
Public Property Age As Integer
Public Overridable Function FullInformation() As String
Dim info As New Text.StringBuilder
info.Append("Name: ")
info.Append(Me.FirstName)
info.Append(" Last name: ")
info.Append(Me.LastName)
info.Append(" Age: ")
info.Append(Me.Age.ToString)
Return info.ToString
End Function
End Class
Now we can create a new implementation of the Contact
class, inheriting from Person
. A new class needs to override the FullInformation
method from the base class. When you type the Overrides
keyword, Visual Studio generates a default implementation that looks like the following:
Public Overrides Function FullInformation() As String
Return MyBase.FullInformation
End Function
The code returns the result offered by the FullInformation
method as it is implemented in the base class, which is accomplished via the MyBase
keyword. We can now rewrite the complete code for the Contact
class as follows:
Public Class Contact
Inherits Person
Public Property Title As String
Public Overrides Function FullInformation() As String
Dim firstInfo As String = MyBase.FullInformation
Dim newInfo As New Text.StringBuilder
newInfo.Append(firstInfo)
newInfo.Append(" Title: ")
newInfo.Append(Me.Title)
Return newInfo.ToString
End Function
End Class
Notice that the overridden method does not perform a complete string concatenation while it invokes first the MyBase.FullInformation
method. This is a best practice because one of inheritance’s purposes is favoring code reusability; therefore, this invocation is better than rewriting the code from scratch. The following code snippet shows how you can interact with both base class and derived class properties, assuming that FirstName
and LastName
have been declared as Overridable
in the base class and overridden within the derived class:
Public Sub New(ByVal name As String,
ByVal surName As String,
ByVal age As Integer,
ByVal title As String)
'Goes to the base class properties
MyBase.FirstName = name
MyBase.LastName = surName
'Current instance properties
Me.Age = age
Me.Title = title
End Sub
Me and MyBase
The Me
keyword refers to the instance of the current class, whereas MyBase
refers to the base class from which the current class derives. This difference is evident when a member is overridden, but if members are not redefined, both keywords refer to the same code.
The next section details a few things you need to know about constructors within derived classes.
Another way of accessing the base classes’ members is the MyClass
keyword. Imagine you have a base class exposing some overridable members, such as properties or methods; then you have a derived class that overrides those members. The MyClass
keyword avoids the application of overriding and invokes members on the derived class as if they were NotOverridable
on the base class. In other words, MyClass
enables executing members of a base class in the context of a derived class, ensuring that the member version is the one in the base class. Listing 12.1 shows an example.
Public Class BaseClassDemo
Public Overridable ReadOnly Property Test As String
Get
Return "This is a test in the base class"
End Get
End Property
Public Function DoSomething() As String
Return MyClass.Test
End Function
End Class
Public Class DerivedClassDemo
Inherits BaseClassDemo
Public Overrides ReadOnly Property Test As String
Get
Return "This is a test in the derived class"
End Get
End Property
End Class
Module Module1
Sub Main()
Dim derived As New DerivedClassDemo
'Invokes the member within the derived
'class but as if it was not overridden
Dim result As String = derived.DoSomething
End Sub
End Module
The BaseClassDemo
base class exposes an overridable property that returns a text message, for demo purposes. It also exposes a public method that just shows the text stored within the Test
property. Within the derived DerivedClassDemo
, the Test
property is overridden but the DoSomething
method is not. This method is still available when you create an instance of the DerivedClassDemo
class. Because the method is defined within the base class and then is executed within the derived class’s context, if you implemented the method as follows:
Public Function DoSomething() As String
Return Me.Test
End Function
it would return the content of the derived Test
property. In some situations, though, you might want to ensure that only base class members are used within other members that are not overridden; this can be accomplished using the MyClass
keyword. If you run the code shown in Listing 12.1, the result variable contains the string "This is a test in the base class"
, although the DoSomething
method has been invoked on an instance of the derived class. You can still use the overridden Test
property for other purposes in your derived class. MyClass
is similar to Me
in that both get a reference to the instance of the current class, but MyClass
behaves as if members in the base class were marked as NotOverridable
and therefore as if they were not overridden in the derived class.
The previous section discussed the MyBase
keyword and how it can be used to access members from a base class. The keyword also has another important purpose when it comes to constructors. Consider the following constructor, which is implemented within the Person
class (that is, the base class) shown in the previous section:
Public Sub New(ByVal firstName As String,
ByVal lastName As String,
ByVal age As Integer)
Me.FirstName = firstName
Me.LastName = lastName
Me.Age = age
End Sub
The problem now is in derived classes. The rule is that if you have a constructor receiving arguments in the base class, you do need to provide a constructor receiving arguments also within a derived class, and the constructor needs to invoke the base class. The following code shows how a constructor needs to be implemented within the Contact
class:
Public Sub New(ByVal name As String,
ByVal surName As String,
ByVal age As Integer,
ByVal title As String)
MyBase.New(name, surName, age)
Me.Title = title
End Sub
As you can see from the preceding code snippet, the first line of code is an invocation to the constructor of the base class—and this is a rule that you must follow. After that line of code, you can provide any other initialization code. This particular requirement is necessary if you plan to provide a constructor that receives arguments within the base class, although it’s not necessary if you implement a constructor that does not receive arguments or if you do not provide any constructor (which is implicitly provided by the Visual Basic compiler).
The beginning of this chapter explained that classes can inherit from base classes exposed by class libraries such as .dll assemblies and that you do not necessarily need the source code. You could create a class deriving from another class exposed by a compiled assembly and implement a new member. In addition, the publisher of the compiled base class could release a new version of the class, providing a member with the same name of your custom member. In this case, you would not be able to edit the base class because you wouldn’t have the source code. Visual Basic 2012 provides an interesting way of dealing with such a situation known as shadowing. Although the Visual Basic compiler still enables compiling (it throws warning messages), your class needs to “shadow” the member with the same name of your custom one. This is accomplished using the Shadows
keyword. Consider this particular implementation of the Person
class, exposing a Title
property, of type String
:
Public Class Person
Public Property FirstName As String
Public Property LastName As String
Public Property Title As String
End Class
Now consider the following implementation of the Contact
class, which requires a value defined within the Titles
enumeration:
Public Class Contact
Inherits Person
Public Property Title As Titles
End Class
Public Enum Titles
Dr
Mr
Mrs
End Enum
The Contact
class exposes a Title
property, but its base class already has a Title
property; therefore, the Visual Basic compiler shows a warning message related to this situation. If you want your code to use the derived Title
property, you need to mark your member with Shadows
as follows:
Public Class Contact
Inherits Person
Public Shadows Property Title As Titles
End Class
You can accomplish the same result by marking the property within the derived class as Overloads
:
Public Overloads Property Title As Titles
In this particular example we can use auto-implemented properties. If the property within the derived class returned the same type of the one within the base class, it would make more sense using old-fashioned properties so that you have the ability to customize the behavior.
Be Very Careful when Shadowing
If you have a derived class that shadows some property or method (like Contact
in the example above), and you pass an instance to a method that accepts an argument of the base class type (like Person
), if the method makes calls to something that was shadowed, it will take the base implementation, even though you passed in the derived type. This can lead to unexpected results, so be very careful when shadowing.
Shared members cannot be overridden. This means that you can only use them as they have been inherited from the base class or provide a shadowing implementation for creating a new definition from scratch. For example, consider this simplified implementation of the Person
class, which exposes a shared Counter
property:
Public Class Person
Public Shared Property Counter As Integer
End Class
If you now create a Contact
class that inherits from Person
, you can use the Counter
property as previously implemented, or you can shadow the base definition as follows:
Public Class Contact
Inherits Person
Public Shared Shadows Property Counter As Integer
End Class
If you intend to provide an overloaded member with a different signature, you can use overloading as follows:
Public Shared Shadows Property Counter As Integer
Public Shared Shadows Property Counter(ByVal maximum As Integer) As Integer
Get
End Get
Set(ByVal value As Integer)
End Set
End Property
Another limitation of shared members is that you cannot invoke the MyBase
and MyClass
keywords within them. Moreover, you cannot invoke shared members using the MyBase
keyword. So, if you were to assign the Counter
shared property defined in the person class, you would have to write Person.Counter = 0
instead of MyBase.Counter = 0
.
In Chapter 6, “Handling Errors and Exceptions,” you learned about exceptions in .NET development; you saw what exceptions are and how you can intercept them at runtime to create well-formed applications that can handle errors. The .NET Framework ships with hundreds of exceptions related to many aspects of .NET development. You might encounter a situation where you need to implement custom exceptions. You can build custom exceptions due to inheritance. A custom exception can inherit from the root System.Exception
class or from another exception (such as System.IO.IOException
) that necessarily inherits from System.Exception
. Custom exceptions should always be CLS-compliant. Let’s look at the Person
class implementation again, adding a method that returns the full name of the person and that requires at least the last name:
Public Class Person
Public Property FirstName As String
Public Property LastName As String
Public Function FullName() As String
If String.IsNullOrEmpty(Me.LastName) Then
Throw New MissingLastNameException("Last name not specified")
Else
Return String.Concat(LastName, " ", FirstName)
End If
End Function
End Class
As you can see, if the LastName
property contains an empty or null string, the code throws a MissingLastNameException
. This exception is custom and must be implemented.
Naming Conventions
Remember that every exception class’s identifier must terminate with the Exception
word. Microsoft naming convention rules require this.
The MissingLastNameException
is implemented as follows:
<Serializable()>
Public Class MissingLastNameException
Inherits Exception
Public Sub New()
MyBase.New()
End Sub
Public Sub New(ByVal message As String)
MyBase.New(message)
End Sub
Public Sub New(ByVal message As String, ByVal inner As Exception)
MyBase.New(message, inner)
End Sub
Protected Sub New(ByVal info As Runtime.Serialization.SerializationInfo,
ByVal context As _
Runtime.Serialization.StreamingContext)
MyBase.New(info, context)
End Sub
End Class
There is a series of considerations:
• As a custom exception, it inherits from System.Exception
.
• The class is decorated with the Serializable
attribute; this is one of the CLS establishments, and it enables developers to persist the state of the exception to disk (see Chapter 41, “Serialization”).
• Custom exceptions expose three overloads of the base constructors plus one overload marked as Protected
that is therefore available to eventually derive classes and that receives information on serialization.
• Custom exceptions are caught exactly as any other built-in exception.
When you have your custom exception, you can treat it as other ones:
Try
Dim p As New Person
'Will cause an error because
'the LastName was not specified
Console.WriteLine(p.FullName)
Catch ex As MissingLastNameException
Console.WriteLine("ERROR: please specify at least the last name")
Catch ex As Exception
Console.WriteLine("Generic error")
Console.WriteLine(ex.ToString)
Finally
Console.ReadLine()
End Try
The preceding code intentionally causes an exception to demonstrate how the MissingLastNameException
works, by not assigning the LastName
property in the Person
class instance.
Avoid Complex Inheritance Chains
Building a complex inheritance chain is something that should be carefully considered because as the chain grows, the child classes will be tied and rely on the base classes not changing, at risk of disrupting the inheritance chain.
Inheritance is a key topic in the object-oriented programming with Visual Basic and the .NET Framework. In this chapter, you learned lots of important concepts. First, you learned what inheritance is and how you can take advantage of it for creating frameworks of custom objects. Then you saw how to derive classes from base classes using the Inherits
keyword and how your derived classes automatically inherit some members from System.Object
, which is the root in the class hierarchy. When deriving classes, you need to consider how constructors and shared members behave; the chapter also provided an overview. But inheritance would be of less use without polymorphism. You learned how polymorphism requires implementing one common infrastructure for multiple objects, taking advantage of the capability to redefine inherited members’ behavior. For this purpose, the .NET Framework enables the overriding technique that you can use to modify the behavior of derived members. Additionally, with shadowing you can override a member from a class of which you do not have the source code. You often want to condition inheritance, establishing when classes should be sealed (NotInheritable
) or abstract (MustInherit
). In inheritance scenarios, you also often need to have access to base class members; therefore, you need the MyBase
and MyClass
keywords. But you seldom work with theories. Because of this, the chapter closed with a practical demonstration of inheritance, showing how you can build custom exceptions to use in your applications.