13.4 Casting to an InterfaceYou 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: Dim isDoc As IStorable = doc isDoc.status = 0 isDoc.Read( ) In Chapter 15, you'll learn 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 the compiler makes it work with 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 15. 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 Document implements only the IStorable interface but not the ICompressible interface: Public Class Document Implements IStorable the cast to ICompressible will fail if Option Strict is On. 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.
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. VB.NET provides the is operator to help you ask the object if it implements an interface. 13.4.1 The Is OperatorThe Is operator lets you query whether an object implements an interface. You use the Is operator with the TypeOf keyword, as follows : TypeOf expression Is type The Is operator evaluates true if the expression (which must be a reference type, e.g., an instance of a class) can be safely cast to type (e.g., an interface) without throwing an exception. Example 13-3 illustrates the use of the Is operator to test whether a Document object implements the IStorable and ICompressible interfaces. Example 13-3. The Is operatorOption 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 both interfaces 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 13-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 |