An implementing
class is free to mark any or all of the methods that implement the
interface as virtual. Derived classes can override
or provide new
implementations. 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 perhaps 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 implemented by
Document
. Read( )
is then
overridden in a Note
type that derives from
Document
.
Example 8-4. Overriding an interface implementation
using System; 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 virtualpublic 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 methodpublic override void Read( )
{ Console.WriteLine( "Overriding the Read method for Note!"); } // implement my own Write method public void Write( ) { Console.WriteLine( "Implementing the Write method for Note!"); } } public class Tester { static void Main( ) { // create a document 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 (isNote != 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, 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( )
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 we 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
was called, but the Write( )
method of Document
was called.
To prove to yourself that this is a result of the overriding method,
you 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. When, however, 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!
3.142.199.184