Using Interface Based Programming
As shown in several examples already in this chapter, as in VB 6.0, VB.NET also supports the concept of interface inheritance. In this section, we'll first review interface-based programming and then discuss the syntax VB.NET uses to create and implement interfaces.
Put simply, an interface is a semantically related set of members that define the contract between an object and the client application that uses it. When an object implements an interface, it is essentially agreeing to provide a view of itself that looks like the interface. The interesting thing about using interfaces in your classes is that clients can write code that works against any class that implements the interface rather than custom code for each particular class. This promotes polymorphism and, therefore, code reuse.
One of the reasons interface-based programming was not popular in VB 6.0 was because creating interfaces was not intuitive. Because VB 6.0 did not support the explicit creation of interfaces, you had to create a class with methods that had no implementation and compile them into a DLL. The syntax in VB.NET makes creating interfaces an explicit and well-defined activity, which should increase their usage. Actually, VB 6.0 does create an interface automatically for each class in an ActiveX DLL project. However, this interface is hidden and is used behind the scenes by VB.
Although you can first create interfaces for all your classes and therefore take a truly interface-based programming approach, it might not be the most cost-effective use of your time. Typically, interfaces are best leveraged when you want to isolate a set of related methods that will be implemented in different ways by different classes. For example, defining an interface through which to query for data from various classes is a good use of an interface because it can be used in many different classes, and because the implementation in each of those classes is likely to differ. If the implementation were to remain the same, a better technique would be to create a base class and inherit from it so that all the derived classes could reuse the actual code.
In VB.NET, you create an interface with the Interface keyword and include the signatures of the members required in the interface. The following snippet shows a small interface that could be implemented by various classes for handling database queries:
Public Interface IQuilogyQuery Function GetByID(ByVal pid As Long) As DataSet Function GetByName(ByVal pname As String) As DataSet End Interface
Note that the method definitions do not contain any "end" statements and thus have no implementation. The interface itself can also be specified with the Public, Private, Protected, Friend, and Protected Friend keywords in the same way as classes. This allows interfaces to be scoped within classes, modules, and applications. And like classes, interfaces are automatically scoped within a namespace, either the global namespace or a specific namespace declared in your code.
Unlike classes, however, the members defined in the interface cannot be marked with access modifiers such as Public and Private or inheritance hints such as Overridable or MustInherit. This is because, by definition, all the members of an interface must be implemented by the class as a part of its contract to any client application that wants to use it. However, one property of the interface can be marked as the Default property as long as the property requires a parameter, as discussed in the section "Creating Properties" in this chapter.
As in VB 6.0, a class can incorporate an interface using the Implements keyword. Unlike implementation inheritance, you can implement more than one interface on a class. This allows client applications to work with the class in multiple ways depending on which interface they request.
When an interface is implemented, each of the members of the interface must be created in the class. The following code shows a class that implements the IQuilogyQuery interface:
Public Class Offerings Implements IQuilogyQuery Public Function GetById(ByVal pid As Long) As DataSet _ Implements IQuilogyQuery.GetByID ' Provide the implementation End Function Public Function GetByName(ByVal pName As String) As DataSet _ Implements IQuilogyQuery.GetByName ' Provide the implementation End Function End Class
Note that the methods that are used to implement the members of the interface are specified using the Implements keyword as well. Although this might seem unnecessary, requiring this syntax allows your class to implement several interfaces that could include members that have names that collide. As a result, you can use a name of your choosing for the internal name.
Within a client application, writing code against interfaces rather than specific classes allows you to write code that is reusable. For example, assume that your client application contains a procedure that queries a class and uses the resulting data set to populate a grid control on a form. Rather than writing specific procedures for each type that can be used in the application, you could write a single PopulateGrid class that takes as a parameter a reference to an object that supports the IQuilogyQuery interface. The procedure can then call the GetByID method of the underlying class through its interface and retrieve the resulting data set, as shown in the following example:
Public Sub PopulateGrid(ByRef oQuery As IQuilogyQuery, ByVal pId As Integer) Dim ds As DataSet ds = oQuery.GetByID(pid) 'Populate the grid with the data set End Sub
Before calling the PopulateGrid method, the client application can also query the class to make sure that it implements the IQuilogyQuery interface by using the TypeOf statement. If so, and if Option Strict is set to On, the CType function can be used to cast the object reference to IQuilogyQuery during the call.
Dim oIns As New Instructors() If TypeOf oIns Is IQuilogyQuery Then PopulateGrid(CType(oIns, IQuilogyQuery), intInsID) End If
Note that if the object does not support the interface and the TypeOf check was not done first, the CType function will throw an InvalidCastException. The exception would also be thrown if Option Strict were set to Off and the implicit cast failed.
As mentioned previously, developers face the decision of whether to use abstract base classes or interfaces or some combination of both in their designs. In addition to the basic decision of whether you need to reuse the implementation of code located in base classes, the issue of versioning is also important.
Because interfaces form a contract between the client and the implementer of the interface, a change to an interface necessarily breaks the contract. As a result, you cannot add or delete members to an interface, change return types from methods, or change the number or data types of arguments without breaking the contract. When the runtime attempts to load a class that implements an interface, it ensures that the interface implements all of the members and that their signatures are correct. If the interface has changed or is not correctly implemented, the runtime will throw a System.TypeLoadException at load-time.
Although this might at first seem like a restriction, it is a good thing because the strict enforcement of interface versioning is what allows classes that implement the same interface to be used polymorphically by clients.