8.2 Accessing Interface Methods

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

IStorable isDoc = (IStorable) 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.

As stated earlier, you cannot instantiate an
interface directly. That is, you
cannot 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 = (IStorable) doc;

You can combine these steps by writing:

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

In general, it is a better design decision to access the interface
methods through an interface reference. Thus, it is
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 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:

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

public class Document : IStorable

the cast to ICompressible would still compile
because ICompressible is a valid interface.
However, because of the illegal cast, when the program is run, an
exception will be 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, in order 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.
Example 8-3 illustrates the use of the
is operator to test whether a
Document implements the
IStorable and ICompressible
interfaces.

Javaprogrammerstakenote: The C# is operator is
the equivalent of Java's
instanceof.

Example 8-3 differs from Example 8-2 in that Document no longer
implements the ICompressible interface.
Main( ) now determines whether the 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.

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):

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 does not 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:

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 you will
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:

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# does not 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: