Learning C# and OOP, Polymorphism and Interfaces, Part 2

Baldwin begins a discussion of runtime polymorphism as implemented using method overriding and the C# interface.

Published:  November 10, 2002
By Richard G. Baldwin

C# Programming Notes # 128b


Preface

This lesson is one of a series of lessons designed to teach you about the essence of Object-Oriented Programming (OOP) using C#.

The first lesson in the group was entitled Learning C# and OOP, Getting Started, Objects and Encapsulation.  That lesson, and each of the lessons following that one, has provided explanations of certain aspects of the essence of Object-Oriented Programming using C#.  The previous lesson was entitled Learning C# and OOP, Polymorphism and Interfaces, Part 1.

Necessary and significant aspects

This miniseries will describe and discuss the necessary and significant aspects of OOP using C#.

If you have a general understanding of computer programming, you should be able to read and understand the lessons in this miniseries, even if you don't have a strong background in the C# programming language.

Comparisons with Java

Some of the lessons in this miniseries will also make comparisons between C# and Java with respect to both syntax and OOP concepts.  This is one of those lessons.  I will emphasize the similarities between these two programming languages, which are likely to dominate the programming world in the foreseeable future.  By studying these lessons and learning about OOP using C#, you will also be learning quite a lot about OOP using Java.

Viewing tip

You may find it useful to open another copy of this lesson in a separate browser window.  That will make it easier for you to scroll back and forth among the different listings while you are reading about them.

Supplementary material

I recommend that you also study the other lessons in my extensive collection of online programming tutorials.  You will find a consolidated index at www.DickBaldwin.com. 

Preview

What is polymorphism?

The meaning of the word polymorphism is something like one name, many forms.

How does C# implement polymorphism?

Polymorphism manifests itself in C# in the form of multiple methods having the same name.

In some cases, multiple methods have the same name, but different formal argument lists (overloaded methods, which were discussed in a previous lesson).

In other cases, multiple methods have the same name, same return type, and same formal argument list (overridden methods).

Three distinct forms of polymorphism

From a practical programming viewpoint, polymorphism manifests itself in three distinct forms in C#:

  • Method overloading
  • Method overriding through inheritance
  • Method overriding through the C# interface

I covered method overloading as one form of polymorphism (compile-time polymorphism) in a previous lesson.

I discussed runtime polymorphism implemented through method overriding and class inheritance in a previous lesson.  I also provided an introduction to the C# interface in a previous lesson. 

The C# interface and polymorphism

In this lesson (and the following lesson), I will explain the use of the C# interface for polymorphism.

I will compare the code, which illustrates the use of the C# interface for polymorphism, with similar Java code that illustrates the use of the Java interface for polymorphism. 

The reason for the comparison is to emphasize the strong similarities that exist between the two languages.

Multiple inheritance and the cardinal rule

In the previous lesson, I explained how the implementation of interfaces in C# is similar to multiple inheritance.

I also explained that any class that implements an interface must provide a definition for all the methods declared in the interface, and all the methods inherited into the interface.

A new relationship

I explained that objects instantiated from classes that implement the same interface have a new relationship that goes beyond the relationship imposed by the standard class hierarchy.

I explained that because a class can implement many different interfaces, a single object in C# can be treated as many different types.  However, for any given type, there are restrictions on the methods that can be invoked on the object's reference. 

I also explained that because different classes can implement the same interface, objects instantiated from different classes can be treated as a common interface type.

A skeleton program

In this lesson, I will present a simple skeleton program that illustrates many of the important aspects of polymorphic behavior based on the C# interface.  I will compare that program with a similar Java program.

In the next lesson, I will put some meat on that skeleton and discuss a more substantive program.

Discussion and Sample Code

Listing C6 near the end of the lesson shows a very simple C# program named Poly05(Listing J6 shows a similar Java program.)

The purpose of this program is to illustrate polymorphic behavior using interfaces in addition to class inheritance.

Designed to illustrate structure

This is a skeleton program designed solely to illustrate the inheritance and interface implementation structure in as simple a program as possible (I will put some meat on this skeleton using another program in the next lesson).

Empty methods

Except for the two methods that return type String, all of the methods in the program are empty.

(Methods that return type String cannot be empty.  They must contain a return statement in order to compile successfully.)

Interface definitions

Listing C1 shows the definition of two simple C# interfaces named I1 and I2.
   
interface I1{
  void p();
}//end interface I1
//===================================//

interface I2 : I1{
  void q();
}//end interface I2

Listing C1

The interface named I2 extends the interface named I1.  Assuming that you have studied the previous lesson, there should be nothing in Listing C1 that you haven't already seen before.

Recall from the previous lesson that the declared methods named p and q in the two interfaces are implicitly public, abstract, and virtual.

Note that because I2 extends I1, any class that implements I2, will be required to define the method named p in addition to the method named q.

(A class that implements an interface must define all methods declared in, or inherited into the interface.  In this case, the method named p is inherited into the interface named I2.)

Similar Java interfaces

Listing J1 shows two similar interfaces in Java. Again, the interface named I2 extends I1. Like C#, the methods named p and q are implicitly abstract and virtual. However they are not implicitly public. Therefore, each of the methods is declared public in Listing J1.

interface I1{
  public void p();
}//end interface I1
//===================================//

interface I2 extends I1{
  public void q();
}//end interface I2

Listing J1

Similar but different

As I discussed in the previous lesson, an interface definition is similar to a class definition. However, there are some very important differences.

No single hierarchy

To begin with, unlike the case with classes, there is no single interface hierarchy.  Also, multiple inheritance is allowed when extending interfaces.

A new interface can extend none, one, or more existing interfaces.  In this case, I2 extends I1, but I1 doesn't extend any other interface.

Unlike classes, an interface doesn't automatically extend another interface by default.  However, as explained in the previous lesson, an interface does implicitly extend the class named Object(This is true for both C# and Java.)

A new data type

I told you in the previous lesson that when you define a new interface, you cause a new data type to become available to your program.  Each interface definition constitutes a new type.  (This is true in both C# and Java.)

Thus, new interface types I1 and I2 are available in this program.

The class named A

Listing C2 defines a very simple C# class named A, which in turn defines two methods named ToString and x.
 
class A{
  public override String ToString(){
    return "ToString in A";
  }//end ToString()
  //---------------------------------//
  
  public String x(){
    return "x in A";
  }//end x()
  //---------------------------------//
}//end class A

Listing C2

Overridden ToString method

The method named ToString overrides the ToString method that is defined in the class named Object(The class named A implicitly extends the class named Object, because it doesn't explicitly extend any other class.) 

Recall that in C#, if a method overrides an inherited method, the override must be explicitly declared as shown in Listing C2.

A similar class in Java

Listing J2 shows a similar Java class definition.   

(As in C#, the Java class named A implicitly extends the Object class, because it doesn't explicitly extend any other class.)

class A{
  public String toString(){
    return "toString in A";
  }//end toString()
  //---------------------------------//
  
  public String x(){
    return "x in A";
  }//end x()
  //---------------------------------//
}//end class A

Listing J2

Overridden toString method

As with the C# class, the method named toString overrides the toString method that is defined in the class named Object.

Note, however, that when a method in a Java class overrides an inherited method, no explicit override declaration is required.

New method

In both cases, the method named x is newly defined in the class named A.

(The method named x is not inherited into the class named A, because the class named Object does not define a method named x.)

The class named B

Listing C3 shows a C# class named B, which extends the class named A, and implements the interface named I2.
 
class B : A,I2{
  public void p(){
  }//end p()
  //---------------------------------//
  
  public void q(){
  }//end q();
  //---------------------------------//
}//end class B

Listing C3

The code in Listing C3 defines the two methods named p and q, with empty methods.

(This satisfies the requirement that a class that implements an interface must provide a definition for all methods declared in, or inherited into the interface.)

A similar Java class

Listing J3 shows a Java class named B, which extends the class named A, and implements the interface named I2.
 
class B extends A implements I2{
  public void p(){
  }//end p()
  //---------------------------------//
  
  public void q(){
  }//end q();
  //---------------------------------//
}//end class B

Listing J3

Except for the different syntax used to indicate extending a class and implementing an interface, this code is identical to the C# code in Listing C3.

The class named C

Listing C4 defines a C# class named C, which extends Object, and implements I2.
 
class C : I2{
  public void p(){
  }//end p()
  //---------------------------------//
  
  public void q(){
  }//end q();
  //---------------------------------//
}//end class B

Listing C4

As in the case of the class named B, this class must define the methods named p and q.  As before, those methods are defined as empty methods.

A similar Java class

Listing J4 shows a Java class.  Except for the syntax required to implement an interface, this class definition is identical to the C# class in Listing C4.
 
class C implements I2{
  public void p(){
  }//end p()
  //---------------------------------//
  
  public void q(){
  }//end q();
  //---------------------------------//
}//end class B

Listing J4

A C# driver class

Finally, the C# driver class named Poly05 in Listing C5 defines an empty Main method.
 
public class Poly05{
  public static void Main(){
  }//end main
}//end class Poly05

Listing C5

A Java driver class

Listing J5 shows a Java driver class.  Except for the difference in the signature of the main method, this class definition is identical to the C# class definition in Listing C5.
 
public class Poly05{
  public static void main(
                        String[] args){
  }//end main
}//end class Poly05

Listing J5

Essentially the same program

The two programs, written in C# and Java, are essentially the same program.  The only difference between the two programs is the occasional difference in required syntax.

Doesn't do anything

As mentioned earlier, the purpose of this program is to illustrate an inheritance and interface structure.  This program can be compiled and executed, but it doesn't do anything.  It starts, runs, and terminates immediately.

A new relationship

At this point, it might be useful for you to sketch out the structure in a simple hierarchy diagram.

If you do, you will see that implementation of the interface named I2 by the classes named B and C, has created a new relationship between those two classes that is totally independent of the normal class hierarchical relationship.

What is the new relationship?

By declaring that both of these classes implement the same interface named I2, we are guaranteeing that an object of either class will contain concrete definitions of the two methods declared in the interfaces named I2 and I1.

Furthermore, we are guaranteeing that objects instantiated from the two classes can be treated as the common type I2.

(Important: references to any objects instantiated from classes that implement I2 can be stored in reference variables of type I2, and either of the interface methods declared in I2 and I1 can be invoked on those references.  Those references can also be passed to any method that expects an incoming parameter of type I1.

Similarly, those references can also be stored in reference variables of type I1, and can be passed to any method that expects an incoming parameter of type I1.  However, in this case, only the method declared in I1 can be invoked on the reference.)

We know the user interface

The signatures of the interface methods in the two classes must match the signatures declared in the interfaces.

This means that if we have access to the documentation for the interfaces, we also know the signatures of the interface methods for objects instantiated from any class that implements the interfaces.

Different behavior

However, and this is extremely important, the behavior of the interface methods as defined in the class named B may be (and often will be) entirely different from the behavior of the interface methods having the same signatures as defined in the class named C.

If you don't understand interfaces, ...

If you don't understand interfaces, you don't really understand C# (or Java either for that matter).

It is unlikely that you will ever be successful as a C# programmer without an understanding of interfaces.

There are very few worthwhile programs that can be written in C# without an understanding of interfaces.

The core aspect

So, what is the core aspect of this concept that is so powerful?

I told you earlier that each interface definition constitutes a new type.  As a result, a reference to any object instantiated from any class that implements a given interface can be treated as the type of the interface.

The reference can be stored in any reference variable of the interface type, and can be passed to any method expecting to receive an incoming parameter of the interface type.

So what!

When a reference to an object is treated as an interface type, any method declared in, or inherited into that interface can be invoked on the reference to the object.

However, the behavior of the method when invoked on references to different objects of the same interface type may be very different.  In the current jargon, the behavior is appropriate for the object on which it is invoked.

One object, many types

Furthermore, because a single class can implement any number of different interfaces, a single object instantiated from a given class can be treated as any of the interface types implemented by the class from which it is instantiated.  Therefore, a single object in C# can be treated as many different types.

(However, when an object is treated as an interface type, only those methods declared in that interface can be invoked on the object.  To invoke other methods on the object, it necessary to cast the object's reference to a different type.)

Treating different types of objects as a common type

All of this also makes it possible to treat objects instantiated from widely differing classes as the same type, provided that all of those classes implement the same interface.

Important:  When an interface method is invoked on one of the objects using the reference of the interface type, the behavior of the method will be as defined by the author of the specific class that implemented the interface.  The behavior of the method will often be different for different objects instantiated from different classes that implement the same interface.

Receiving parameters as interface types

Methods can receive parameters that are references of interface types.  In this case, the author of the code that invokes interface methods on the incoming reference doesn't need to know, and often doesn't care, about the name of the class from which the object was instantiated.

(For a discussion of this capability, see my tutorials on Java Data Structures at http://www.DickBaldwin.com )

A common example

A very common example is to store references to objects instantiated from different classes, (which implement the same interface) in some sort of data structure (such as list or a set) and then to invoke the same methods on each of the references in the collection.

Summary

Polymorphic behavior, based on the C# interface, is one of the most important concepts in C# OOP

In this lesson, I began my discussion of runtime polymorphism as implemented using method overriding and the C# interface. (I will continue that discussion in the next lesson as well.)

I presented a simple skeleton program that illustrated many of the important aspects of polymorphic behavior based on the C# interface. 

(I also presented a similar Java program in order to illustrate the strong similarities between C# and Java.)

I explained the cardinal rule, which is:

If a class implements an interface, it must provide a concrete definition for all the methods declared by that interface, and all the methods inherited by that interface.

I explained that objects instantiated from classes that implement the same interface have a new relationship that goes beyond the relationship imposed by the standard class hierarchy.

I explained that because a class can implement many different interfaces, a single object in C# can be treated as many different types.  However, for any given type, there are restrictions on the methods that can be invoked on the object.

I also explained that because different classes can implement the same interface, objects instantiated from different classes can be treated as a common interface type.

Finally, I suggested that there is little if anything useful that can be done in C# without understanding and using interfaces.

What's Next?

In the next lesson, I will discuss a more substantive program as I continue my discussion of polymorphic behavior using the C# interface.

Complete Program Listings

A complete listing of the sample C# program is shown in Listing C6 below.  A complete listing of the sample Java program is shown in Listing J6.

/*File Poly05.cs
Copyright 2002, R.G.Baldwin

This program illustrates polymorphic 
behavior using interfaces in addition
to class inheritance.

This version of the program is 
designed solely to illustrate the
inheritance and implementation 
structure.  Except for the methods
that return String type, all of the 
methods in the program are empty.  
(Methods that return type String
cannot be empty.  They must contain
a return statement in order to
compile successfully.)

The program produces no output.
**************************************/
using System;

interface I1{
  void p();
}//end interface I1
//===================================//

interface I2 : I1{
  void q();
}//end interface I2
//===================================//

class A{
  public override String ToString(){
    return "ToString in A";
  }//end ToString()
  //---------------------------------//
  
  public String x(){
    return "x in A";
  }//end x()
  //---------------------------------//
}//end class A
//===================================//

class B : A,I2{
  public void p(){
  }//end p()
  //---------------------------------//
  
  public void q(){
  }//end q();
  //---------------------------------//
}//end class B
//===================================//

class C : I2{
  public void p(){
  }//end p()
  //---------------------------------//
  
  public void q(){
  }//end q();
  //---------------------------------//
}//end class B
//===================================//

public class Poly05{
  public static void Main(){
  }//end main
}//end class Poly05
//===================================//
    
Listing C6

 

/*File Poly05.java
Copyright 2002, R.G.Baldwin

This program illustrates polymorphic 
behavior using interfaces in addition
to class inheritance.

This version of the program is 
designed solely to illustrate the
inheritance and implementation 
structure.  Except for the methods
that return String type, all of the 
methods in the program are empty.  
(Methods that return type String
cannot be empty.  They must contain
a return statement in order to
compile successfully.)

The program compiles and executes
successfully under JDK 1.3, but
produces no output.
**************************************/

interface I1{
  public void p();
}//end interface I1
//===================================//

interface I2{
  public void q();
}//end interface I2
//===================================//

class A{
  public String toString(){
    return "toString in A";
  }//end toString()
  //---------------------------------//
  
  public String x(){
    return "x in A";
  }//end x()
  //---------------------------------//
}//end class A
//===================================//

class B extends A implements I2{
  public void p(){
  }//end p()
  //---------------------------------//
  
  public void q(){
  }//end q();
  //---------------------------------//
}//end class B
//===================================//

class C implements I2{
  public void p(){
  }//end p()
  //---------------------------------//
  
  public void q(){
  }//end q();
  //---------------------------------//
}//end class B
//===================================//

public class Poly05{
  public static void main(
                        String[] args){
  }//end main
}//end class Poly05
//===================================//

Listing J6

Copyright 2002, Richard G. Baldwin.  Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.

About the author

Richard Baldwin is a college professor (at Austin Community College in Austin, Texas) and private consultant whose primary focus is a combination of Java, C#, and XML. In addition to the many platform and/or language independent benefits of Java and C# applications, he believes that a combination of Java, C#, and XML will become the primary driving force in the delivery of structured information on the Web.

Richard has participated in numerous consulting projects and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas.  He is the author of Baldwin's Programming Tutorials, which has gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro magazine.

Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.

-end-


© 1996, 1997, 1998, 1999, 2000, 2001, 2002 Richard G. Baldwin