Previous section   Next section

8.4 Casting to an Interface

You can access the members (i.e., methods and properties) of an interface through the object of any class that implements the interface. Thus, you can access the methods and properties of IStorable through the Document object, as if they were members of the Document class:

Dim doc As New Document("Test Document")
doc.Status = -1
doc.Read( )

Alternatively, you can create an instance of the interface, and then use that interface to access the methods of that interface:

Dim isDoc As IStorable = doc
isDoc.status = 0
isDoc.Read( )

In Chapter 9, you'll see that at times you may create collections of objects that implement a given interface (e.g., a collection of storable objects). You can manipulate them without knowing their real type�so long as they implement IStorable. For instance, you won't know that you have a Document object; rather, you'll know only that the object in question implements IStorable. You can create a variable of type IStorable and cast your Document to that type. You can then access the IStorable methods through the IStorable variable.

When you cast you say to the compiler, "Trust me, I know this object is really of this type." In this case you are saying, "Trust me, I know this document really implements IStorable, so you can treat it as an IStorable."

As stated earlier, you cannot instantiate an interface directly�that is, you cannot write:

IStorable isDoc As New IStorable( )

You can, however, create an instance of the implementing class, and then create an instance of the interface:

Dim isDoc As IStorable = doc

(isDoc is a reference to an IStorable object.) This is considered a widening conversion (from Document to the IStorable interface), and so there is no need for an explicit cast.

In general, it is a better design decision to access the interface methods through an interface reference. Thus, it was better to use isDoc.Read( ), than doc.Read( ), in the previous example. Access through an interface allows you to treat the interface polymorphically. In other words, you can have two or more classes implement the interface, and then by accessing these classes only through the interface, you can ignore their real runtime type and treat them simply as instances of the interface. You'll see the power of this technique in Chapter 3.

8.4.1 Testing for Interface Implementation

There may be instances in which you do not know in advance (at compile time) that an object supports a particular interface. For instance, given a collection of objects, you might not know whether each object in the collection implements IStorable, ICompressible, or both.

You can find out what interfaces are implemented by a particular object by casting blindly and then catching the exceptions that arise when you've tried to cast the object to an interface it hasn't implemented. The code to cast Document to ICompressible might be:

Dim icDoc As ICompressible = doc
icDoc.Compress( )

If it turns out that if Document implements only the IStorable interface but not the ICompressible interface:

Public Class Document
    Implements IStorable

the cast to ICompressible will fail to compile (assuming Option Strict is On as it should be). If you turn Option Strict off, the code will compile, but at runtime, because of the illegal cast, the program will throw an exception:

System.InvalidCastException: Specified cast is not valid.

Exceptions are used to report errors and are covered in detail in Chapter 11.

You could then catch the exception and take corrective action, but this approach is ugly and evil and you should not do things this way. This is like testing whether a gun is loaded by firing it; it's dangerous and it annoys the neighbors.

Rather than firing blindly, you would like to be able to ask the object if it implements an interface, in order to then invoke the appropriate methods; to do so you will use the Is operator.

8.4.2 TypeOf...Is

The TypeOf...Is expression lets you query whether an object implements an interface. The syntax of the expression is:

TypeOf expression Is type

This expression evaluates true if the tested expression (which must be a reference type, such as an instance of a class) can be safely cast to type (e.g., an interface) without throwing an exception.

Example 8-3 illustrates the use of the TypeOf and Is keywords to test whether a Document object implements the IStorable and ICompressible interfaces.

Example 8-3. Using the TypeOf and Is keywords
Option Strict On
Imports System
Namespace InterfaceDemo

    Interface IStorable
        Sub Read( )
        Sub Write(ByVal obj As Object)
        Property Status( ) As Integer
    End Interface 'IStorable

    ' here's the new interface
    Interface ICompressible
        Sub Compress( )
        Sub Decompress( )
    End Interface 'ICompressible

    ' document implements only IStorable
Public Class Document

        Implements IStorable

        ' the document constructor
        Public Sub New(ByVal s As String)
            Console.WriteLine("Creating document with: {0}", s)
        End Sub 'New

        ' implement IStorable
        Public Sub Read( ) Implements IStorable.Read
            Console.WriteLine("Implementing the Read Method for IStorable")
        End Sub 'Read

        Public Sub Write(ByVal o As Object) Implements IStorable.Write
            Console.WriteLine( _
              "Implementing the Write Method for IStorable")
        End Sub 'Write

        Public Property Status( ) As Integer Implements IStorable.Status
            Get
                Return Status
            End Get
            Set(ByVal Value As Integer)
                Status = Value
            End Set
        End Property

        ' hold the data for IStorable's Status property
        Private myStatus As Integer = 0

    End Class 'Document

    Class Tester

        Public Sub Run( )
            Dim doc As New Document("Test Document")

            ' only cast if it is safe
            If TypeOf doc Is IStorable Then
                Dim isDoc As IStorable = doc
                isDoc.Read( )
            Else
                Console.WriteLine("Could not cast to IStorable")
            End If

            ' this test will fail
            If TypeOf doc Is ICompressible Then
                Dim icDoc As ICompressible = doc
                icDoc.Compress( )
            Else
                Console.WriteLine("Could not cast to ICompressible")
            End If
        End Sub 'Run

        Shared Sub Main( )
            Dim t As New Tester( )
            t.Run( )
        End Sub 'Main

    End Class 'Tester

End Namespace 'InterfaceDemo

Output:
Creating document with: Test Document
Implementing the Read Method for IStorable
Could not cast to ICompressible

In Example 8-3, the Document class implements only IStorable:

Public Class Document
    Implements IStorable

In the Run( ) method of the Tester class, you create an instance of Document:

Dim doc As New Document("Test Document")

and you test whether that instance is an IStorable (that is, does it implement the IStorable interface?):

If TypeOf doc Is IStorable Then

If so, you create an instance of the IStorable interface and call an interface method (isDoc.Read):

Dim isDoc As IStorable = doc
isDoc.Read( )

You then repeat the test with ICompressible, and if the test fails, you print an error message:

If TypeOf doc Is ICompressible Then
    Dim icDoc As ICompressible = CType(doc, ICompressible)
    icDoc.Compress( )
Else
    Console.WriteLine("Could not cast to ICompressible")
End If

The output shows that the first test (IStorable) succeeds (as expected) and the second test (of ICompressible) fails, also as expected:

Implementing the Read Method for IStorable
Could not cast to ICompressible

  Previous section   Next section
Top