Chapter 8. Interfaces

An interface is a contract that guarantees to a client how a class or struct will behave (I’ll just use the term class for the rest of this chapter, though everything I say will apply to structs as well).

When a class implements an interface, it tells any potential client “I guarantee I’ll support all the methods, properties, events, and indexers of the named interface.” (See Chapter 4 for information about methods and properties, Chapter 12 for information about events, and Chapter 9 for coverage of indexers.) See also the sidebar "Abstract Class Versus Interface Versus Mix-Ins.”

These contracts are made manifest using the interface keyword, which declares a reference type that encapsulates the contract.

When you define an interface, you may define methods, properties, indexers, and events that will (and must!) be implemented by any class that implements the interface.

Tip

Java programmers take note: C# doesn’t support the use of constant fields (member constants) in interfaces. The closest analog is the use of enumerated constants (enums).

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.

Defining and 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; I cover them in Chapter 20.

I discussed access modifiers, including public, private, protected, internal, and protected internal, 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 the next section, “Implementing More Than One Interface”).

The interface-body describes the methods, properties, and so forth that must be implemented by the implementing class.

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 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 though 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. I illustrate this in Example 8-1, in which the Document class implements the IStorable interface.

Example 8-1. Using a simple interface
using System;



namespace SimpleInterface
{
    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 interfacepublic 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");
        }

        public int Status  { get; set; }


    }

    // 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 doesn’t 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 don’t 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 can’t 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 doesn’t 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, shown here:

interface ICompressible
{
    void Compress(  );
    void Decompress(  );
}

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:

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

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

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

Tip

Effectively, by extending ICompressible in this way, you are saying that anything that implements ILoggedCompressible must also implement ICompressible.

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 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 precompressed item:

interface IStorableCompressible : IStorable, ILoggedCompressible
{
    void LogOriginalSize(  );
}

Example 8-2 illustrates extending and combining interfaces.

Example 8-2. Extending and combining interfaces
using System;

namespace ExtendAndCombineInterface
{
    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; set; }

        // 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");
            doc.Read(  );
            doc.Compress(  );
            doc.LogSavedBytes(  );
            doc.Compress(  );
            doc.LogOriginalSize(  );
            doc.LogSavedBytes(  );
            doc.Compress(  );
            doc.Read(  );
            doc.Encrypt(  );
        }
    }
}

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

Polymorphism with Interfaces

The problem with the approach we’ve taken so far is that you could well have a collection of Document objects, some implementing IStorable, some implementing ICompressible, some implementing ILoggedCompressible, some implementing IStorableCompressible, and some implementing IEncryptable. If you just call methods from each interface, sooner or later you’re going to throw an exception.

Let’s build such an example slowly, because this problem is very real, very confusing, and very likely to cause a nasty bug in your program if it isn’t fully understood.

Start by declaring the interfaces just as you did in the previous example (I won’t repeat them here). Next, rather than declaring a simple Document class, let’s declare an abstract Document class, and two derived Document classes:

public abstract class Document { }

public class BigDocument : Document, IStorableCompressible, IEncryptable
{
   //....
}

The implementation of BigDocument is identical to the implementation of Document in the previous example. There’s no change whatsoever, except that the constructor must be named BigDocument, and note that it now inherits from our abstract class.

Finally, let’s add a smaller type of Document:

class LittleDocument : Document, IEncryptable
{
    public LittleDocument(string s)
    {
        Console.WriteLine("Creating document with: {0}", s);

    }
    void IEncryptable.Encrypt(  )
    {
        Console.WriteLine("Implementing Encrypt");
    }

    void IEncryptable.Decrypt(  )
    {
        Console.WriteLine("Implementing Decrypt");
    }

}

Notice that LittleDocument also inherits from Document, but it implements only one interface: IEncryptable.

Let’s change Main, now to create a collection of Documents:

for (int i = 0; i < 5; i++)
{
    if (i % 2 == 0)
    {
        folder[i] = new BigDocument("Big Document # " + i);
    }
    else
    {
        folder[i] = new LittleDocument("Little Document # " + i);
    }
}

We create five documents, with the even-numbered ones being “big” and the odd-numbered ones being “little.” If you now iterate through the “folder” (the array of Document objects) and try to call various methods of the interface, you have a problem:

foreach (Document doc in folder)
{
    doc.Read(  );
    doc.Compress(  );
    doc.LogSavedBytes(  );
    doc.Compress(  );
    doc.LogOriginalSize(  );
    doc.LogSavedBytes(  );
    doc.Compress(  );
    doc.Read(  );
    doc.Encrypt(  );
}

This won’t compile—nor should it. The compiler cannot know which kind of Document it has: a BigDocument (which can Read and Compress), or a LittleDocument (which can’t).

To solve this problem, we need to see whether the Document in question implements the interface we want to use, as shown in Example 8-3.

Example 8-3. Collections of Documents
using System;

namespace ExtendAndCombineInterface
{
    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 abstract class Document { }

    public class BigDocument : Document, IStorableCompressible, IEncryptable
    {

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

        // the document constructor
        public BigDocument(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; set; }

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

        }
    }

    class LittleDocument : Document, IEncryptable
    {
        public LittleDocument(string s)
        {
            Console.WriteLine("Creating document with: {0}", s);

        }
        void IEncryptable.Encrypt(  )
        {
            Console.WriteLine("Implementing Encrypt");
        }

        void IEncryptable.Decrypt(  )
        {
            Console.WriteLine("Implementing Decrypt");
        }

    }


    public class Tester
    {

        static void Main(  )
        {
            Document[] folder = new Document[5];
            for (int i = 0; i < 5; i++)
            {
                if (i % 2 == 0)
                {
                    folder[i] = new BigDocument("Big Document # " + i);
                }
                else
                {
                    folder[i] = new LittleDocument("Little Document # " + i);
                }
            }

            foreach (Document doc in folder)
            {
                // cast the document to the various interfaces
                IStorable isStorableDoc = doc as IStorable;
                if (isStorableDoc != null)
                {
                    isStorableDoc.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");

            }   // end for
        }       // end main
    }           // end class
}               // end namespace

Output:

Creating document with: Big Document # 0
Creating document with: Little Document # 1
Creating document with: Big Document # 2
Creating document with: Little Document # 3
Creating document with: Big Document # 4
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
IStorable not supported
Compressible not supported
LoggedCompressible not supported
StorableCompressible not supported
Implementing Encrypt
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
IStorable not supported
Compressible not supported
LoggedCompressible not supported
StorableCompressible not supported
Implementing Encrypt
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

A quick examination of the output shows that we created three big documents and two little ones; that in fact, three of the documents are able to implement the interfaces and two are not; and that with the exception of Encrypt, all are able to implement, just as we have every right to expect.

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:

abstract class Storable
{
    abstract public void Read(  );
    abstract public void Write(  );
}

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 List and Storable. But in C#, you’re stuck; you can’t inherit from the Storable abstract class and the List class because C# doesn’t 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 from IStorable, as StorableList does in the following example:

public class StorableList : List, IStorable
{
    // List methods here ...
    public void Read(  ) {...}
    public void Write(object obj) {...}
    // ...
}

Overriding Interface Implementations

An implementing class is free to mark any or all of the methods that implement the interface as virtual. Derived classes can override these implementations to achieve polymorphism. For example, a Document class might implement the IStorable interface and mark the Read( ) and Write( ) methods as virtual. The Document might Read( ) and Write( ) its contents to a File type. The developer might later derive new types from Document, such as a Note or EmailMessage type, and he might decide that Note will read and write to a database rather than to a file.

Example 8-4 strips down the complexity of Example 8-3 and illustrates overriding an interface implementation. The Read( ) method is marked as virtual and is implemented by Document. Read( ) is then overridden in a Note type that derives from Document.

Example 8-4. Overriding an interface implementation
using System;

namespace overridingInterface
{
    interface IStorable
    {
        void Read(  );
        void Write(  );
    }

    // Simplify Document to implement only IStorable
    public class Document : IStorable
    {
        // the document constructor
        public Document(string s)
        {
            Console.WriteLine(
            "Creating document with: {0}", s);
        }

        // Make read virtual
        public virtual void Read(  )
        {
            Console.WriteLine(
            "Document Read Method for IStorable");
        }

        // NB: Not virtual!
        public void Write(  )
        {
            Console.WriteLine(
            "Document Write Method for IStorable");
        }
    }

    // Derive from Document
    public class Note : Document
    {
        public Note(string s) :
            base(s)
        {
            Console.WriteLine(
            "Creating note with: {0}", s);
        }

        // override the Read method

        public override void Read(  )
        {
            Console.WriteLine(
            "Overriding the Read method for Note!");
        }

        // implement my own Write method
        public new void Write(  )
        {
            Console.WriteLine(
            "Implementing the Write method for Note!");
        }
    }
    public class Tester
    {

        static void Main(  )
        {
            // create a document reference to a Note object
            Document theNote = new Note("Test Note");
            IStorable isNote = theNote as IStorable;
            if (isNote != null)
            {
                isNote.Read(  );
                isNote.Write(  );
            }

            Console.WriteLine("
");

            // direct call to the methods
            theNote.Read(  );
            theNote.Write(  );

            Console.WriteLine("
");

            // create a note object
            Note note2 = new Note("Second Test");
            IStorable isNote2 = note2 as IStorable;
            if (isNote2 != null)
            {
                isNote2.Read(  );
                isNote2.Write(  );
            }

            Console.WriteLine("
");

            // directly call the methods
            note2.Read(  );
            note2.Write(  );
        }
    }
}

Output:
Creating document with: Test Note
Creating note with: Test Note
Overriding the Read method for Note!
Document Write Method for IStorable

Overriding the Read method for Note!
Document Write Method for IStorable

Creating document with: Second Test
Creating note with: Second Test
Overriding the Read method for Note!
Document Write Method for IStorable

Overriding the Read method for Note!
Implementing the Write method for Note!

In this example, Document implements a simplified IStorable interface (simplified to make the example clearer):

interface IStorable
{
    void Read(  );
    void Write(  );
}

The designer of Document has opted to make the Read( ) method virtual, but not to make the Write( ) method virtual:

public virtual void Read(  )

In a real-world application, if you were to mark one as virtual, you would almost certainly mark both as virtual, but I’ve differentiated them to demonstrate that the developer is free to pick and choose which methods are made virtual.

The Note class derives from Document:

public class Note : Document

It’s not necessary for Note to override Read( ), but it is free to do so, and has in fact done so here:

public override void Read(  )

In Tester, the Read and Write methods are called in four ways:

  • Through the base class reference to a derived object

  • Through an interface created from the base class reference to the derived object

  • Through a derived object

  • Through an interface created from the derived object

To accomplish the first two calls, a Document (base class) reference is created, and the address of a new Note (derived) object created on the heap is assigned to the Document reference:

Document theNote = new Note("Test Note");

An interface reference is created, and the as operator is used to cast the Document to the IStorable reference:

IStorable isNote = theNote as IStorable;

You then invoke the Read( ) and Write( ) methods through that interface. The output reveals that the Read( ) method is responded to polymorphically and the Write( ) method is not, just as you would expect:

Overriding the Read method for Note!
Document Write Method for IStorable

The Read( ) and Write( ) methods are then called directly on the object itself:

theNote.Read(  );
theNote.Write(  );

and once again you see the polymorphic implementation has worked:

Overriding the Read method for Note!
Document Write Method for IStorable

In both cases, the Read( ) method of Note is called and the Write( ) method of Document is called.

To prove to yourself that this is a result of the overriding method, next create a second Note object, this time assigning its address to a reference to a Note. This will be used to illustrate the final cases (i.e., a call through a derived object, and a call through an interface created from the derived object):

Note note2 = new Note("Second Test");

Once again, when you cast to a reference, the overridden Read( ) method is called. However, when methods are called directly on the Note object:

note2.Read(  );
note2.Write(  );

the output reflects that you’ve called a Note and not an overridden Document:

Overriding the Read method for Note!
Implementing the Write method for Note!

Explicit Interface Implementation

In the implementation shown so far, the implementing class (in this case, Document) creates a member method with the same signature and return type as the method detailed in the interface. It is not necessary to explicitly state that this is an implementation of an interface; the compiler understands this implicitly.

What happens, however, if the class implements two interfaces, each of which has a method with the same signature? Example 8-5 creates two interfaces: IStorable and ITalk. The latter implements a Read( ) method that reads a book aloud. Unfortunately, this conflicts with the Read( ) method in IStorable.

Because both IStorable and ITalk have a Read( ) method, the implementing Document class must use explicit implementation for at least one of the methods. With explicit implementation, the implementing class (Document) explicitly identifies the interface for the method:

void ITalk.Read(  )

This resolves the conflict, but it creates a series of interesting side effects.

First, there is no need to use explicit implementation with the other method of Talk( ):

public void Talk(  )

Because there is no conflict, this can be declared as usual.

More important, the explicit implementation method can’t have an access modifier:

void ITalk.Read(  )

This method is implicitly public.

In fact, a method declared through explicit implementation can’t be declared with the abstract, virtual, override, or new modifier.

Most important, you can’t access the explicitly implemented method through the object itself. When you write:

theDoc.Read(  );

the compiler assumes you mean the implicitly implemented interface for IStorable. The only way to access an explicitly implemented interface is through a cast to an interface:

ITalk itDoc = theDoc;
itDoc.Read(  );

Example 8-5 demonstrates explicit implementation.

Example 8-5. Explicit implementation
using System;

namespace ExplicitImplementation
{
    interface IStorable
    {
        void Read(  );
        void Write(  );
    }

    interface ITalk
    {
        void Talk(  );
        void Read(  );
    }

    // Modify Document to implement IStorable and ITalk
    public class Document : IStorable, ITalk
    {
        // the document constructor
        public Document(string s)
        {
            Console.WriteLine("Creating document with: {0}", s);

        }

        // Make read virtual
        public virtual void Read(  )
        {
            Console.WriteLine("Implementing IStorable.Read");
        }

        public void Write(  )
        {
            Console.WriteLine("Implementing IStorable.Write");

        }

        void ITalk.Read(  )
        {
            Console.WriteLine("Implementing ITalk.Read");
        }

        public void Talk(  )
        {
            Console.WriteLine("Implementing ITalk.Talk");
        }
    }

    public class Tester
    {

        static void Main(  )
        {
            // create a document object
            Document theDoc = new Document("Test Document");
            IStorable isDoc = theDoc;
            isDoc.Read(  );

            ITalk itDoc = theDoc;
            itDoc.Read(  );

            theDoc.Read(  );
            theDoc.Talk(  );
        }
    }
}

Output:
Creating document with: Test Document
Implementing IStorable.Read
Implementing ITalk.Read
Implementing IStorable.Read
Implementing ITalk.Talk

Selectively Exposing Interface Methods

A class designer can take advantage of the fact that when an interface is implemented through explicit implementation, the interface is not visible to clients of the implementing class except through casting.

Suppose the semantics of your Document object dictate that it implement the IStorable interface, but you don’t want the Read( ) and Write( ) methods to be part of the public interface of your Document. You can use explicit implementation to ensure that they aren’t available except through casting. This allows you to preserve the public API of your Document class while still having it implement IStorable. If your client wants an object that implements the IStorable interface, it can make a cast, but when using your document as a Document, the API will not include Read( ) and Write( ).

In fact, you can select which methods to make visible through explicit implementation so that you can expose some implementing methods as part of Document but not others. In Example 8-5, the Document object exposes the Talk( ) method as a method of Document, but the ITalk.Read( ) method can be obtained only through a cast. Even if IStorable didn’t have a Read( ) method, you might choose to make Read( ) explicitly implemented so that you don’t expose Read( ) as a method of Document.

Note that because explicit interface implementation prevents the use of the virtual keyword, a derived class would be forced to reimplement the method. Thus, if Note derived from Document, it would be forced to reimplement ITalk.Read( ) because the Document implementation of ITalk.Read( ) couldn’t be virtual.

Member Hiding

It is possible for an interface member to become hidden. For example, suppose you have an interface IBase that has a property P:

interface IBase
{
    int P { get; set; }
}

Suppose you derive from that interface a new interface, IDerived, which hides the property P with a new method P( ):

interface IDerived : IBase
{
    new int P(  );
}

Setting aside whether this is a good idea, you have now hidden the property P in the base interface. An implementation of this derived interface will require at least one explicit interface member. You can use explicit implementation for either the base property or the derived method, or you can use explicit implementation for both. Thus, any of the following three versions would be legal:

class myClass : IDerived
{
    // explicit implementation for the base property
    int IBase.P { get {...} }

    // implicit implementation of the derived method
    public int P(  ) {...}
}

class myClass : IDerived
{
    // implicit implementation for the base property
    public int P { get {...} }

    // explicit implementation of the derived method
    int IDerived.P(  ) {...}
}

class myClass : IDerived
{
    // explicit implementation for the base property
    int IBase.P { get {...} }

    // explicit implementation of the derived method
    int IDerived.P(  ) {...}
}
..................Content has been hidden....................

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