Chapter 8. Interfaces

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.

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 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 interface
interface 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 interface
      IStorable 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.

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, 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

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 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.

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 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
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.135.249