Team LiB   Previous Section   Next Section

14.6 Overriding Interface Implementations

An implementing class is free to mark any or all of the methods from the interface as virtual. Derived classes can then override or provide new implementations, just as they might with any other virtual instance method.

For example, a Document class might implement the IStorable interface and mark its Read() and Write() methods as virtual. The developer might later derive new types from Document, such as a Note type. While the Document class implements Read() and Write to save to a File, the Note class might implement Read() and Write() to read from and write to a database.

Example 14-6 strips down the complexity of the previous examples and illustrates overriding an interface implementation. In this example, you'll derive a new class named Note from the Document class.

Document implements the IStorable-required Read() method as a virtual method, and Note overrides that implementation.

Notice that Document does not mark Write() as virtual. You'll see the implications of this decision in the analysis section that follows Example 14-6.

The complete listing is shown in Example 14-6.

Example 14-6. Overriding an interface implementation
using System;

namespace OverridingInterfaces
{
    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!");        
        }
    } 

    class Tester
    {
        public void Run()
        {
            // Create a Document object
            Document theNote = new Note("Test Note");

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

            Console.WriteLine("\n");

            // cast the Document to IStorable
            IStorable isNote = theNote as IStorable;
            if (isNote != null)
            {
                isNote.Read();
                isNote.Write();
            }
            Console.WriteLine("\n");

            // create a note object
            Note note2 = new Note("Second Test");

            // directly call the methods
            note2.Read();
            note2.Write();
            Console.WriteLine("\n");

            // Cast the note to IStorable
            IStorable isNote2 = note2 as IStorable;
            if (isNote != null)
            {
                isNote2.Read();
                isNote2.Write();
            }


        }

        static void Main()
        {
            Tester t = new Tester();
            t.Run();
        }
    }
}

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!
Implementing the Write method for Note!

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

In Example 14-6, the IStorable interface is simplified for clarity's sake:

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

The Document class implements the IStorable interface:

public class Document : IStorable

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

public virtual void Read()
public         void Write()

In a real-world application, 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 new class, Note, derives from Document:

public class Note : Document

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

public override void Read()

To illustrate the implications of marking an implementing method as virtual, the Run() method calls the Read() and Write() methods 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

As you have seen previously, virtual methods are implemented polymorphically, and nonvirtual methods are not. It turns out that the interfaces created from these references work just like the references themselves. That is, virtual implementations of the interface methods are polymorphic, while nonvirtual implementations are not.

The one surprising aspect is this: when you call the nonpolymorphic Write() method on the IStorable interface cast from the derived Note, you actually get the Document's Write method because Write() is implemented in the base class and is nonvirtual.

To illustrate calling the methods through a base class reference to a derived object, instantiate a Document (base class) reference and assign to it the address of a new derived object (of type Note) that is created on the heap:

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

Then invoke the Read and Write methods through that reference:

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

The output reveals that the Read() method is responded to polymorphically and the Write() method is not, just as we would expect:

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

The overridden method of Read() is called because you've created a new Note object.

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

The nonvirtual Write method of Document is called because you've assigned theNote to a reference to a Document.

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

To illustrate calling the methods through an interface that is created from the base class reference to the derived object, create an interface reference named isNote. Use the as operator to cast the Document (theNote) to the IStorable reference:

IStorable isNote = theNote as IStorable;

Then invoke the Read() and Write() methods for theNote through that interface.

if (isNote != null)
{
    isNote.Read();
    isNote.Write();
}

The output is the same: once again the virtual Read method is polymorphic, and the nonvirtual Write() method is not:

Overriding the Read method for Note
Document Write Method for IStorable

Next create a second Note object, this time assigning its address to a reference to a Note, rather than a reference to a Document. 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");

Call the methods on the derived object:

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

Again, the virtual Read() method is polymorphic, and the nonvirtual Write() method is not, but this time you get the Write() method for Note because you are calling the method on a Note object.

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

Finally, cast the Note object to an IStorable reference and call Read() and Write():

IStorable isNote2 = note2 as IStorable;
if (isNote != null)
{
    isNote2.Read();
    isNote2.Write();
}

The Read() method is called polymorphically, but the Write() method for Document is called because Document implements IStorable, and Write() is not polymorphic.

Overriding the Read method for Note!
Document Write Method for IStorable
    Team LiB   Previous Section   Next Section