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. In an
earlier example, we created a base class Note
, and a derived class Document
. While the Note
class implements Read( )
and Write( )
to save to a file, the Document
class might implement Read( )
and Write( )
to read from and write to a database.
Example 13-5
strips down the complexity of the previous examples and illustrates
overriding an interface implementation. Note
implements the IStorable
-required Read( )
method as a virtual method, and
Document
overrides that
implementation.
Notice that Note
does not
mark Write( )
as virtual. You’ll
see the implications of this decision in the analysis that follows
Example 13-5.
The complete listing is shown in Example 13-5.
Example 13-5. Overriding an interface implementation
using System; namespace OverridingAnInterfaceImplementation { interface IStorable { void Read( ); void Write( ); } public class Note : IStorable { public Note( string s ) { Console.WriteLine( "Creating Note with: {0}", s ); } // NB: virtual public virtual void Read( ) { Console.WriteLine( "Note Read Method for IStorable" ); } // NB: Not virtual! public void Write( ) { Console.WriteLine( "Note Write Method for IStorable" ); } } public class Document : Note { public Document( string s ): base( s ) { Console.WriteLine( "Creating Document with: {0}", s ); } // override the Read method public override void Read( ) { Console.WriteLine( "Overriding the Read method for Document!" ); } // implement my own Write method public new void Write( ) { Console.WriteLine( "Implementing the Write method for Document!" ); } } class Tester { public void Run( ) { Note theNote = new Document( "Test Document" ); theNote.Read( ); theNote.Write( ); Console.WriteLine( " " ); IStorable isStorable = theNote as IStorable; if ( isStorable != null ) { isStorable.Read( ); isStorable.Write( ); } Console.WriteLine( " " ); // This time create a reference to the derived type Document theDoc = new Document( "Second Test" ); theDoc.Read( ); theDoc.Write( ); Console.WriteLine( " " ); IStorable isStorable2 = theDoc as IStorable; if ( isStorable != null ) { isStorable2.Read( ); isStorable2.Write( ); } } static void Main( ) { Tester t = new Tester( ); t.Run( ); } } }
The output looks like this:
Creating Note with: Test Document Creating Document with: Test Document Overriding the Read method for Document! Note Write Method for IStorable Overriding the Read method for Document! Note Write Method for IStorable Creating Note with: Second Test Creating Document with: Second Test Overriding the Read method for Document! Implementing the Write method for Document! Overriding the Read method for Document! Note Write Method for IStorable
In Example 13-5,
the IStorable
interface is simplified
for clarity’s sake:
interface IStorable { void Read( ); void Write( ); }
The Note
class implements the
IStorable
interface:
public class Note : IStorable
The designer of Note
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 methods 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, Document
,
derives from Note
:
public class Document : Note
It is not necessary for Document
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 Note
class
reference to a Document
object
Through an interface created from the Note
class reference to the Document
object
Through a Document
object
Through an interface created from the Document
object
Virtual implementations of interface methods are polymorphic, just like the virtual methods of classes.
When you call the non-polymorphic Write( )
method on the IStorable
interface cast from the derived Document
, you actually get the Note
’s Write
method, because Write( )
is implemented in the base class and
is non-virtual.
To see polymorphism at work with interfaces, you’ll create a
reference to the Note
class and
initialize it with a new instance of the derived Document
class:
Note theDocument = new Document("Test Document");
Invoke the Read
and Write
methods:
theDocument.Read( ); theDocument.Write( );
The output reveals that the (virtual) Read( )
method is called polymorphically—that
is, the Document
class overrides the
Note
class’s Read( )
, while the non-virtual Write( )
method of the Note
class is invoked because it was not made
virtual.
Overriding the Read method for Document! Note Write Method for IStorable
The overridden method of Read( )
is called because you’ve created a new Document
object:
Note theDocument =new Document
("Test Document");
The non-virtual Write
method of
Note
is called because you’ve
assigned theDocument
to a reference
to a Note
:
Note theDocument
= new Document("Test Document");
To illustrate calling the methods through an interface that is
created from the Note
class reference
to the Document
object, create an
interface reference named isDocument
.
Use the as
operator to cast the
Note
(theDocument
) to the IStorable
reference:
IStorable isDocument = theDocument as IStorable;
Then invoke the Read( )
and
Write( )
methods for theDocument
through that interface:
if (isDocument != null) { isDocument.Read( ); isDocument.Write( ); }
The output is the same: once again, the virtual Read( )
method is polymorphic, and the
non-virtual Write( )
method is
not:
Overriding the Read method for Document Note Write Method for IStorable
Next, create a second Document
object, this time assigning its address to a reference to a Document
, rather than a reference to a
Note
. This will be used to illustrate
the final cases (a call through a Document
object and a call through an
interface created from the Document
object):
Document Document2 = new Document("Second Test");
Call the methods on the Document
object:
Document2.Read( ); Document2.Write( );
Again, the virtual Read( )
method is polymorphic, and the non-virtual Write( )
method is not, but this time you get
the Write( )
method for Document
because you are calling the method on
a Document
object:
Overriding the Read method for Document! Implementing the Write method for Document!
Finally, cast the Document
object to an IStorable
reference and
call Read( )
and Write( )
:
IStorable isDocument2 = Document2 as IStorable; if (isDocument != null) { isDocument2.Read( ); isDocument2.Write( ); }
The Read( )
method is called
polymorphically, but the Write( )
method for Note
is called because
Note
implements IStorable
, and Write( )
is not polymorphic:
Overriding the Read method for Document! Note Write Method for IStorable