8.1 Implementing 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 next.

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, use the same syntax as if the new Document class were inheriting from IStorablea 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 interface

interface IStorable
{
   // no access modifiers, methods are public
   // no implementation
   void Read( );
   void Write(object obj);
   int Status { get; set; }

}


// create a class which implements the IStorable interface
public class Document : IStorable
{

   // store the value for the property
   private int status = 0;

   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;
      }
   }
}

// 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); 
   }
}

Output:
Creating document with: Test Document
Implementing the Read Method for IStorable
Document Status: -1

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.

8.1.1 Implementing More Than One Interface

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, 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 (which is declared in Example 8-2):

public void Compress( )
{
   Console.WriteLine("Implementing the Compress Method");
}

public void Decompress( )
{
   Console.WriteLine("Implementing the Decompress Method");
}

8.1.2 Extending Interfaces

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 ICompressible with a new interface, ILoggedCompressible, 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 ICompressible. Objects of that type can be cast either to ILoggedCompressible or to ICompressible.

8.1.3 Combining Interfaces

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 precompressed 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
{

   // hold the data for IStorable's Status property
   private int status = 0;

   // 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");
        
   }
}

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
}  


    Part I: The C# Language