Section 8.2. Accessing Interface Methods


8.2. Accessing Interface Methods

You can access the members of the IStorable interface as if they were members of the Document class:

Document doc = new Document("Test Document"); doc.status = -1; doc.Read();

You can also create an instance of the interface[1] by casting the document to the interface type, and then use that interface to access the methods:

[1] Or more accurately, a properly cast reference to the object that implements the interface.

IStorable isDoc = doc; isDoc.status = 0; isDoc.Read( );

In this case, in Main( ) you know that Document is in fact an IStorable, so you can take advantage of that knowledge and not explicitly cast or test the cast.

As stated earlier, you can't instantiate an interface directly. That is, you can't say:

IStorable isDoc = new IStorable();

You can, however, create an instance of the implementing class, as in the following:

Document doc = new Document("Test Document");

You can then create an instance of the interface by casting the implementing object to the interface type, which in this case is IStorable:

IStorable isDoc = doc;

You can combine these steps by writing:

IStorable isDoc = new Document("Test Document");

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 interchangeably. See Chapter 5 for more information about polymorphism.

8.2.1. Casting to an Interface

In many cases, you don't know in advance that an object supports a particular interface. Given a collection of objects, you might not know whether a particular object supports IStorable or ICompressible or both. You can just cast to the interfaces:

Document doc = myCollection[0]; IStorable isDoc = (IStorable) doc; isDoc.Read( ); ICompressible icDoc = (ICompressible) doc; icDoc.Compress( );

If it turns out that Document implements only the IStorable interface:

public class Document : IStorable

the cast to ICompressible still compiles because ICompressible is a valid interface. However, because of the illegal cast, when the program is run, an exception is thrown:

An exception of type System.InvalidCastException was thrown.

Exceptions are covered in detail in Chapter 11.

8.2.2. The is Operator

You would like to be able to ask the object if it supports the interface, to then invoke the appropriate methods. In C# there are two ways to accomplish this. The first method is to use the is operator. The form of the is operator is:

expression is type

The is operator evaluates true if the expression (which must be a reference type) can be safely cast to type without throwing an exception.[2] Example 8-3 illustrates the use of the is operator to test whether a Document implements the IStorable and ICompressible interfaces.

[2] Both the is and the as operator (described next) can be used to evaluate types through inheritance, in addition to evaluating implementation of interfaces. Thus, you can use is to check whether a dog is a mammal.

Java programmers take note: the C# is operator is the equivalent of Java's instanceof.


Example 8-3. Using the is operator
#region Using directives using System; using System.Collections.Generic; using System.Text; #endregion namespace IsOperator {    interface IStorable    {       void Read( );       void Write( object obj );       int Status { get; set; }    } // here's the new interface    interface ICompressible    {       void Compress( );       void Decompress( );    } // Document implements IStorable    public class Document : IStorable    {       private int status = 0;       public Document( string s )       {          Console.WriteLine(             "Creating document with: {0}", s );       }       // IStorable.Read       public void Read( )       {          Console.WriteLine( "Reading...");       }       // IStorable.Write       public void Write( object o )       {          Console.WriteLine( "Writing...");       }       // IStorable.Status       public int Status       {          get          {             return status;          }          set          {             status = value;          }       }    }     // derives from Document and implements ICompressible     public class CompressibleDocument : Document, ICompressible     {         public CompressibleDocument(String s) :             base(s)         { }         public void Compress( )         {             Console.WriteLine("Compressing...");         }         public void Decompress( )         {             Console.WriteLine("Decompressing...");         }     }    public class Tester    {       static void Main( )       {           // A collection of Documents           Document[] docArray = new Document[2];           // First entry is a Document           docArray[0] = new Document( "Test Document" );           // Second entry is a CompressibleDocument (ok because            // CompressibleDocument is a Document)           docArray[1] =              new CompressibleDocument("Test compressibleDocument");           // don't know what we'll pull out of this hat           foreach (Document doc in docArray)           {               // report your name               Console.WriteLine("Got: {0}", doc);               // Both pass this test               if (doc is IStorable)               {                   IStorable isDoc = (IStorable)doc;                   isDoc.Read( );               }               // fails for Document               // passes for CompressibleDocument               if (doc is ICompressible)               {                   ICompressible icDoc = (ICompressible)doc;                   icDoc.Compress( );               }           }       }    } } Output: Creating document with: Test Document Creating document with: Test compressibleDocument Got: IsOperator.Document Reading... Got: IsOperator.CompressibleDocument Reading... Compressing...

Example 8-3 differs from Example 8-2 in that Document no longer implements the ICompressible interface, but a class derived from Document named CompressibleDocument does.

Main() checks whether each cast is legal (sometimes referred to as safe) by evaluating the following if clause:

if (doc is IStorable)

This is clean and nearly self-documenting. The if statement tells you that the cast will happen only if the object is of the right interface type.

The Document class passes this test, but fails the next:

if (doc is ICompressible)

but the CompressibleDocument passes both tests.

We put both types of documents into an array (you can imagine such an array being handed to a method which can't know its contents). Before you try to call the ICompressible methods, you must be sure that the type of Document you have does implement ICompressible. The is operator makes that test for you.

Unfortunately, this use of the is operator turns out to be inefficient. To understand why, you need to dip into the MSIL code that this generates. Here is a small excerpt (note that the line numbers are in hexadecimal notation):

IL_0023:  isinst     ICompressible IL_0028:  brfalse.s  IL_0039 IL_002a:  ldloc.0 IL_002b:  castclass  ICompressible IL_0030:  stloc.2 IL_0031:  ldloc.2 IL_0032:  callvirt   instance void ICompressible::Compress( )

What is most important here is the test for ICompressible on line 23. The keyword isinst is the MSIL code for the is operator. It tests to see if the object (doc) is in fact of the right type. Having passed this test we continue on to line 2b, in which castclass is called. Unfortunately, castclass also tests the type of the object. In effect, the test is done twice. A more efficient solution is to use the as operator.

8.2.3. The as Operator

The as operator combines the is and cast operations by testing first to see whether a cast is valid (i.e., whether an is test would return true) and then completing the cast when it is. If the cast is not valid (i.e., if an is test would return false), the as operator returns null.

The keyword null represents a null referenceone that doesn't refer to any object.


Using the as operator eliminates the need to handle cast exceptions. At the same time you avoid the overhead of checking the cast twice. For these reasons, it is optimal to cast interfaces using as.

The form of the as operator is:

expression as type

The following code adapts the test code from Example 8-3, using the as operator and testing for null:

static void Main() {       // A collection of Documents       Document[] docArray = new Document[2];       // First entry is a Document       docArray[0] = new Document( "Test Document" );       // Second entry is a CompressibleDocument (ok because        // CompressibleDocument is a Document)       docArray[1] = new CompressibleDocument("Test compressibleDocument");       // don't know what we'll pull out of this hat       foreach (Document doc in docArray)       {           // report your name           Console.WriteLine("Got: {0}", doc);           // Both pass this test           IStorable isDoc = doc as IStorable;           if (isDoc != null)           {               isDoc.Read( );           }           // fails for Document           // passes for CompressibleDocument           ICompressible icDoc = doc as ICompressible;           if (icDoc != null)           {               icDoc.Compress( );           }       } }

A quick look at the comparable MSIL code shows that the following version is in fact more efficient:

IL_0023:  isinst     ICompressible IL_0028:  stloc.2 IL_0029:  ldloc.2 IL_002a:  brfalse.s  IL_0034 IL_002c:  ldloc.2 IL_002d:  callvirt   instance void ICompressible::Compress( )

8.2.4. The is Operator Versus the as Operator

If your design pattern is to test the object to see if it is of the type you need, and if so to immediately cast it, the as operator is more efficient. At times, however, you might want to test the type of an operator but not cast it immediately. Perhaps you want to test it but not cast it at all; you simply want to add it to a list if it fulfills the right interface. In that case, the is operator will be a better choice.

8.2.5. Interface Versus Abstract Class

Interfaces are very similar to abstract classes. In fact, you could change the declaration of IStorable to be an abstract class:

abstract class Storable {   abstract public void Read();   abstract public void Write( ); }

Document could now inherit from Storable, and there would not be much difference from using the interface.

Suppose, however, that you purchase a List class from a third-party vendor whose capabilities you wish to combine with those specified by Storable. In C++, you could create a StorableList class and inherit from both List and Storable. But in C#, you're stuck; you can't inherit from both the Storable abstract class and also the List class because C# doesn't allow multiple inheritance with classes.

However, C# does allow you to implement any number of interfaces and derive from one base class. Thus, by making Storable an interface, you can inherit from the List class and also from IStorable, as StorableList does in the following example:

public class StorableList : List, IStorable {    // List methods here ...    public void Read( ) {...}    public void Write(object obj) {...}    // ... }

Some designers at Microsoft discourage the use of interfaces and prefer abstract base classes because the latter do better with versioning.


For example, suppose you design an interface and programmers in your shop start using it. You now want to add a new member to that interface. You have two bad choices: you can either change the interface and break existing code, or you can treat the interface as immutable and create, for example, IStore2 or IStorageExtended. If you do that often enough, however, you will soon have dozens of closely related interfaces and a mess on your hands.

With an abstract base class, you can just add a new virtual method with a default implementation. Hey! Presto! Existing code continues to work, but no new class is introduced into the namespace.

The best practice seems to be that if you are creating a class library that will be reused by many people (especially outside your company), you might want to favor abstract base classes. If you are creating classes for a single project, however, interfaces may make for easier-to-understand and more flexible code.



Programming C#(c) Building. NET Applications with C#
Programming C#: Building .NET Applications with C#
ISBN: 0596006993
EAN: 2147483647
Year: 2003
Pages: 180
Authors: Jesse Liberty

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net