An interface
is a contract that guarantees to a client how a class or struct will
behave. When a class implements an interface, it tells any potential
client “I guarantee I’ll support the methods, properties,
events, and indexers of the named interface.” (See Chapter 4 for information about methods and properties,
see Chapter 12 for info about events, and see Chapter 9 for coverage of indexers.)
An interface offers an alternative to an abstract class for creating
contracts among classes and their clients. These contracts are made
manifest using the
interface
keyword, which declares a reference
type that encapsulates the contract.
Syntactically, an interface is like a class that has only abstract methods. An abstract class serves as the base class for a family of derived classes, while interfaces are meant to be mixed in with other inheritance trees.
When a class implements an interface, it must implement all the methods of that interface; in effect the class says “I agree to fulfill the contract defined by this interface.”
Inheriting from an abstract class implements the
is-a
relationship, introduced in Chapter 5. Implementing an interface defines a different
relationship, one we’ve not seen until now: the
implements
relationship. These two
relationships are subtly different. A car is a
vehicle, but it might implement the
CanBeBoughtWithABigLoan
capability (as can a
house, for example).
In this chapter, you will learn how to create, implement, and use interfaces. You’ll learn how to implement multiple interfaces and how to combine and extend interfaces, as well as how to test whether a class has implemented an interface.
The syntax for defining an interface is as follows:
[attributes] [access-modifier]
interface interface-name [:base-list] {interface-body
}
Don’t worry about attributes for now; they’re covered in Chapter 18.
Access modifiers, including
public
, private
,
protected
, internal
, and
protected
internal
, are
discussed in Chapter 4.
The interface
keyword is followed by the name of
the interface. It is common (but not required) to begin the name of
your interface with a capital I
. Thus,
IStorable
, ICloneable
,
IClaudius
, etc.
The
base-list
lists the interfaces that this interface extends (as described in
Section 8.1.1 later in
this chapter).
The interface-body
is the implementation
of the interface, as described below.
Suppose you wish to create an interface that describes the methods
and properties a class needs to be stored to and
retrieved from a database or other storage such as a file. You decide
to call this interface IStorable
.
In this interface you might specify two methods: Read( )
and Write( )
, which appear in the
interface-body
:
interface IStorable { void Read( ); void Write(object); }
The purpose of an interface is to define the capabilities that you want to have available in a class.
For example, you might create a class, Document
.
It turns out that Document
types can be stored in
a database, so you decide to have Document
implement the IStorable
interface.
To do so, you use the same syntax as if the new
Document
class were inheriting from
IStorable
—a colon (:
),
followed by the interface name:
public class Document : IStorable { public void Read( ) {...} public void Write(object obj) {...} // ... }
It is now your responsibility, as the author of the
Document
class, to provide a meaningful
implementation of the IStorable
methods. Having
designated Document
as implementing
IStorable
, you must implement all the
IStorable
methods, or you will generate an error
when you compile. This is illustrated in Example 8-1, in which the Document
class implements the Istorable
interface.
Example 8-1. Using a simple interface
using System; // declare the interfaceinterface IStorable
{
// no access modifiers, methods are public
// no implmentation
void Read( );
void Write(object obj);
int Status { get; set; }
}
// create a class which implements the IStorable interface public class Document : IStorable { public Document(string s) { Console.WriteLine("Creating document with: {0}", s); }// implement the Read method
public void Read( )
{
Console.WriteLine(
"Implementing the Read Method for IStorable");
}
// implement the Write method
public void Write(object o)
{
Console.WriteLine(
"Implementing the Write Method for IStorable");
}
// implement the property public int Status { get { return status; } set { status = value; } } // store the value for the property private int status = 0; } // Take our interface out for a spin public class Tester { static void Main( ) { // access the methods in the Document object Document doc = new Document("Test Document"); doc.Status = -1; doc.Read( ); Console.WriteLine("Document Status: {0}", doc.Status); // cast to an interface and use the interfaceIStorable isDoc = (IStorable) doc;
isDoc.Status = 0;
isDoc.Read( );
Console.WriteLine("IStorable Status: {0}", isDoc.Status); } } Output: Creating document with: Test Document Implementing the Read Method for IStorable Document Status: -1 Implementing the Read Method for IStorable IStorable Status: 0
Example 8-1 defines a simple interface,
IStorable
, with two methods, Read( )
and Write( )
, and a property,
Status
, of type integer
. Notice
that the property declaration does not provide an implementation for
get( )
and set( )
, but simply
designates that there is a get( )
and a set( )
:
int Status { get; set; }
Notice also that the IStorable
method declarations
do not include access modifiers (e.g., public
,
protected
, internal
,
private
). In fact, providing an access modifier
generates a compile error. Interface methods are implicitly
public
because an interface is a contract meant to
be used by other classes. You cannot create an instance of an
interface; instead you instantiate a class that implements the
interface.
The class implementing the interface must fulfill the contract
exactly and completely. Document
must provide both
a Read( )
and a Write( )
method
and the Status
property. How
it fulfills these requirements, however, is entirely up to the
Document
class. Although
IStorable
dictates that
Document
must have a Status
property, it does not know or care whether
Document
stores the actual status as a member
variable, or looks it up in a database. The details are up to the
implementing class.
Classes
can implement more than one interface.
For example, if your Document
class can be stored
and it also can be compressed, you might choose to implement both the
IStorable
and ICompressible
interfaces. To do so, you change the declaration (in the base-list)
to indicate that both interfaces are implemented, separating the two
interfaces with commas:
public class Document : IStorable, ICompressible
Having done this, the Document
class must also
implement the methods specified by the
ICompressible
interface:
public void Compress( ) { Console.WriteLine("Implementing the Compress Method"); } public void Decompress( ) { Console.WriteLine("Implementing the Decompress Method"); }
Running this modified example reveals that the
Document
object can in fact access these interface
methods.
Creating document with: Test Document Implementing the Read Method for IStorable Implementing Compress
It is possible to extend an existing interface to add new methods or
members, or to modify how existing members work. For example, you
might extend ICompressable
with a new interface,
ILoggedCompressable
, which extends the original
interface with methods to keep track of the bytes saved:
interface ILoggedCompressible : ICompressible { void LogSavedBytes( ); }
Classes are now free to implement either
ICompressible
or
ILoggedCompressible
, depending on whether they
need the additional functionality. If a class does implement
ILoggedCompressible
, it must implement all the
methods of both ILoggedCompressible
and also
ICompressible
. Objects of that type can be cast
either to ILoggedCompressible
or to
ICompressible
.
Similarly, you
can create new interfaces by combining existing interfaces, and,
optionally, adding new methods or properties. For example, you might
decide to create IStorableCompressible
. This
interface would combine the methods of each of the other two
interfaces, but would also add a new method to store the original
size of the pre-compressed item:
interface IStorableCompressible : IStoreable, ILoggedCompressible { void LogOriginalSize( ); }
Example 8-2 illustrates extending and combining interfaces.
Example 8-2. Extending and combining interfaces
using System;
interface IStorable
{
void Read( );
void Write(object obj);
int Status { get; set; }
}
// here's the new interface
interface ICompressible
{
void Compress( );
void Decompress( );
}
// Extend the interface
interface ILoggedCompressible : ICompressible
{
void LogSavedBytes( );
}
// Combine Interfaces
interface IStorableCompressible : IStorable, ILoggedCompressible
{
void LogOriginalSize( );
}
// yet another interface
interface IEncryptable
{
void Encrypt( );
void Decrypt( );
}
public class Document : IStorableCompressible, IEncryptable
{
// the document constructor
public Document(string s)
{
Console.WriteLine("Creating document with: {0}", s);
}
// implement IStorable
public void Read( )
{
Console.WriteLine(
"Implementing the Read Method for IStorable");
}
public void Write(object o)
{
Console.WriteLine(
"Implementing the Write Method for IStorable");
}
public int Status
{
get
{
return status;
}
set
{
status = value;
}
}
// implement ICompressible
public void Compress( )
{
Console.WriteLine("Implementing Compress");
}
public void Decompress( )
{
Console.WriteLine("Implementing Decompress");
}
// implement ILoggedCompressible
public void LogSavedBytes( )
{
Console.WriteLine("Implementing LogSavedBytes");
}
// implement IStorableCompressible
public void LogOriginalSize( )
{
Console.WriteLine("Implementing LogOriginalSize");
}
// implement IEncryptable
public void Encrypt( )
{
Console.WriteLine("Implementing Encrypt");
}
public void Decrypt( )
{
Console.WriteLine("Implementing Decrypt");
}
// hold the data for IStorable's Status property
private int status = 0;
}
public class Tester
{
static void Main( )
{
// create a document object
Document doc = new Document("Test Document");
// cast the document to the various interfaces
IStorable isDoc = doc as IStorable;
if (isDoc != null)
{
isDoc.Read( );
}
else
Console.WriteLine("IStorable not supported");
ICompressible icDoc = doc as ICompressible;
if (icDoc != null)
{
icDoc.Compress( );
}
else
Console.WriteLine("Compressible not supported");
ILoggedCompressible ilcDoc = doc as ILoggedCompressible;
if (ilcDoc != null)
{
ilcDoc.LogSavedBytes( );
ilcDoc.Compress( );
// ilcDoc.Read( );
}
else
Console.WriteLine("LoggedCompressible not supported");
IStorableCompressible isc = doc as IStorableCompressible;
if (isc != null)
{
isc.LogOriginalSize( ); // IStorableCompressible
isc.LogSavedBytes( ); // ILoggedCompressible
isc.Compress( ); // ICompressible
isc.Read( ); // IStorable
}
else
{
Console.WriteLine("StorableCompressible not supported");
}
IEncryptable ie = doc as IEncryptable;
if (ie != null)
{
ie.Encrypt( );
}
else
Console.WriteLine("Encryptable not supported");
}
}
using System;
interface IStorable
{
void Read( );
void Write(object obj);
int status { get; set; }
}
// here's the new interface
interface ICompressible
{
void Compress( );
void Decompress( );
}
// Extend the interface
interface ILoggedCompressible : ICompressible
{
void LogSavedBytes( );
}
// Combine Interfaces
interface IStorableCompressible : IStorable, ILoggedCompressible
{
void LogOriginalSize( );
}
// yet another interface
interface IEncryptable
{
void Encrypt( );
void Decrypt( );
}
public class Document : IStorableCompressible, IEncryptable
{
// the document constructor
public Document(string s)
{
Console.WriteLine("Creating document with: {0}", s);
}
// implement IStorable
public void Read( )
{
Console.WriteLine(
"Implementing the Read Method for IStorable");
}
public void Write(object o)
{
Console.WriteLine(
"Implementing the Write Method for IStorable");
}
public int status
{
get
{
return dbStatus;
}
set
{
dbStatus = value;
}
}
// implement ICompressible
public void Compress( )
{
Console.WriteLine("Implementing Compress");
}
public void Decompress( )
{
Console.WriteLine("Implementing Decompress");
}
// implement ILoggedCompressible
public void LogSavedBytes( )
{
Console.WriteLine("Implementing LogSavedBytes");
}
// implement IStorableCompressible
public void LogOriginalSize( )
{
Console.WriteLine("Implementing LogOriginalSize");
}
// implement IEncryptable
public void Encrypt( )
{
Console.WriteLine("Implementing Encrypt");
}
public void Decrypt( )
{
Console.WriteLine("Implementing Decrypt");
}
// hold the data for IStorable's status property
private int dbStatus = 0;
}
public class Tester
{
static void Main( )
{
// create a document object
Document doc = new Document("Test Document");
// cast the document to the various interfaces
IStorable isDoc = doc as IStorable;
if (isDoc != null)
{
isDoc.Read( );
}
else
Console.WriteLine("IStorable not supported");
ICompressible icDoc = doc as ICompressible;
if (icDoc != null)
{
icDoc.Compress( );
}
else
Console.WriteLine("Compressible not supported");
ILoggedCompressible ilcDoc = doc as ILoggedCompressible;
if (ilcDoc != null)
{
ilcDoc.LogSavedBytes( );
ilcDoc.Compress( );
// ilcDoc.Read( );
}
else
Console.WriteLine("LoggedCompressible not supported");
IStorableCompressible isc = doc as IStorableCompressible;
if (isc != null)
{
isc.LogOriginalSize( ); // IStorableCompressible
isc.LogSavedBytes( ); // ILoggedCompressible
isc.Compress( ); // ICompressible
isc.Read( ); // IStorable
}
else
{
Console.WriteLine("StorableCompressible not supported");
}
IEncryptable ie = doc as IEncryptable;
if (ie != null)
{
ie.Encrypt( );
}
else
Console.WriteLine("Encryptable not supported");
}
}
Output:
Creating document with: Test Document
Implementing the Read Method for IStorable
Implementing Compress
Implementing LogSavedBytes
Implementing Compress
Implementing LogOriginalSize
Implementing LogSavedBytes
Implementing Compress
Implementing the Read Method for IStorable
Implementing Encrypt
Example 8-2 starts by implementing the
IStorable
interface and the
ICompressible
interface. The latter is extended to
ILoggedCompressible
and then the two are combined
into IStorableCompressible
. Finally, the example
adds a new interface, IEncryptable
.
The Tester
program creates a new
Document
object and then casts it to the various
interfaces. When the object is cast to
ILoggedCompressible
, you can use the interface to
call methods on Icompressible
because
ILoggedCompressible
extends (and thus subsumes)
the methods from the base interface:
ILoggedCompressible ilcDoc = doc as ILoggedCompressible; if (ilcDoc != null) { ilcDoc.LogSavedBytes( ); ilcDoc.Compress( ); // ilcDoc.Read( ); }
You cannot call Read( )
, however, because that is
a method of IStorable
, an unrelated interface. And
if you uncomment out the call to Read( )
, you will
receive a compiler error.
If you cast to IStorableCompressible
(which
combines the extended interface with the Storable
interface), you can then call methods of
IStorableCompressible
,
Icompressible
,
and
IStorable
:
IStorableCompressible isc = doc as IStorableCompressible if (isc != null) { isc.LogOriginalSize( ); // IStorableCompressible isc.LogSavedBytes( ); // ILoggedCompressible isc.Compress( ); // ICompressible isc.Read( ); // IStorable } isc.LogOriginalSize( ); // IStorableCompressible isc.LogSavedBytes( ); // ILoggedCompressible isc.Compress( ); // ICompressible isc.Read( ); // IStorable
3.145.96.86