Team LiB   Previous Section   Next Section

14.4 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. One such method might be called LogSavedBytes(). The following code creates a new interface named ILoggedCompressible that is identical to ICompressible except that it adds the method LogSavedBytes.

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.

Example 14-5 extends ICompressible to create ILoggedCompressible, and then casts the Document first to be of type IStorable, then to be of type ILoggedCompressible. Finally, the example casts the Document object to ICompressible. This last cast is safe because any object that implements ILoggedCompressible must also have implemented ICompressible (the former is a superset of the latter). This is the same logic that says you can cast any object of a derived type to an object of a base type (that is, if Student derives from Human, then all Students are Human, even though not all Humans are Students).

Example 14-5. Extending interfaces
using System;

namespace InterfaceDemo
{
    interface IStorable
    {
        void Read();
        void Write(object obj);
        int Status { get; set; }

    }

    // the Compressible interface is now the
    // base for ILoggedCompressible
    interface ICompressible
    {
        void Compress();
        void Decompress();
    }

    // extend ICompressible to log the bytes saved
    interface ILoggedCompressible : ICompressible
    {
        void LogSavedBytes();
    }

 
    // Document implements both interfaces
    public class Document : IStorable, ILoggedCompressible
    {
        // 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");
        }   

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

   class Tester
   {
      public void Run()
      {
          Document doc = new Document("Test Document");

          // cast using as, then test for null
          IStorable isDoc = doc as IStorable;
          if (isDoc != null)
          {
              isDoc.Read();
          }
          else
          {
              Console.WriteLine("Could not cast to IStorable");
          }



          ILoggedCompressible ilDoc = doc as ILoggedCompressible;
          if (ilDoc != null)
          {
              Console.Write("\nCalling both ICompressible and ");
              Console.WriteLine("ILoggedCompressible methods...");
              ilDoc.Compress();
              ilDoc.LogSavedBytes();
          }
          else
          {
              Console.WriteLine("Could not cast to ILoggedCompressible");
          }

          // cast using as, then test for null
          ICompressible icDoc = doc as ICompressible;
          if (icDoc != null)
          {
              Console.WriteLine(
                  "\nTreating the object as Compressible... ");
              icDoc.Compress();
          }
          else
          {
              Console.WriteLine("Could not cast to ICompressible");
          }
      }

      [STAThread]
      static void Main()
      {
         Tester t = new Tester();
         t.Run();
      }
   }
}
Output:
Creating document with: Test Document
Implementing the Read Method for IStorable

Calling both ICompressible and ILoggedCompressible methods...
Implementing Compress
Implementing LogSavedBytes

Treating the object as Compressible...
Implementing Compress

Example 14-5 starts by creating the ILoggedCompressible interface:

// extend ICompressible to log the bytes saved
interface ILoggedCompressible : ICompressible
{
void LogSavedBytes();
}

Notice that the syntax for extending an interface is the same as that for deriving from a class. This extended interface explicitly defines only one method (LogSavedBytes()), but of course any class implementing this interface must also implement the base interface (ICompressible) and all its members.

Define the Document class to implement both IStorable and ILoggedCompressible:

public class Document : IStorable, ILoggedCompressible

You are now free to cast the Document object to IStorable, ILoggedCompressible, or to ICompressible:

IStorable isDoc = doc as IStorable;
ILoggedCompressible ilDoc = doc as ILoggedCompressible;
ICompressible icDoc = doc as ICompressible;

If you look back at the output, you'll see that all three of these casts succeed.

    Team LiB   Previous Section   Next Section