Dr. GUI .NET 1.1 #2

 

Revised for Version 1.1 of the Microsoft .NET Framework

May 30, 2003

Summary: Provides a brief review of object-oriented programming, including inheritance, virtual (overridable) methods and properties, interfaces, and how to use these features to achieve polymorphism. (22 printed pages)

Contents

Introduction
Where We've Been; Where We're Going
Inheritance
Polymorphism, Virtual, and Overrides
Interfaces
Give It a Shot!
What We've Done; What's Next

See the source code for this article in a new window.

Introduction

Welcome back to Dr. GUI's third.NET article (the first two articles, named in honor of Microsoft® .NET Framework array element numbering, start at zero rather than one, and are called Dr. GUI .NET #0 and Dr. GUI .NET #1). If you're looking for an overview of the earlier articles, check out the Dr. GUI .NET home page.

You should also check out the Dr. GUI .NET message board and the Dr. GUI .NET samples home pagehttp://www.coldrooster.com/DrGUIdotNet/2/. Or if for some strange reason you're interested in what Dr. GUI is thinking, check out his blog, Dr. GUI's Bits and Bytes.

Dr. GUI hopes to see you participate in the message board. There are already some good discussions there, but they'll be better once you join in! Anyone can read the messages, but you'll need a Microsoft® .NET Passport account to authenticate you to post. But don't worry—it's easy to set up a .NET Passport and you can associate it with any of your e-mail accounts. (You don't need to have an MSN or Hotmail account in order to use Passport.)

The Microsoft® ASP.NET applications are actually running on a server: Check out the Cold Rooster Consulting Web site to see all the ASP.NET applications from this series.

Where We've Been; Where We're Going

Last time we talked about the basics of types, mainly classes—but also structures and enums—as well as a few others in the .NET Framework.

This time, we're going to do a brief review of object-oriented programming, including inheritance, virtual (overridable) methods and properties, interfaces, and how to use these features to achieve polymorphism.

But first, let's get started talking about inheritance.

Inheritance

Have you ever wanted an object that's just like some existing object with a few minor specializations? If you have, then inheritance is for you.

When one type inherits from another, it gets all of the members. You can hide members in your inherited type, but you can't get rid of them. Of course, you can add members. And, as we'll see, you can override some members. (What's that mean? It's a mystery, at least for now. But all will be revealed later.)

To create an inherited class, we derive from some base class. All classes are implicitly derived from Object, so the following two declarations in C# and Microsoft® Visual Basic® .NET are identical in meaning:

C#

   class Foo { /* ... */ }
   class Goo : Object { /* ... */ }

Visual Basic .NET

   Class Foo
      ' ...
   End Class
   Class Goo
Inherits Object
' ...
   End Class

In both cases, Object is the base class. In Goo's case, this is made explicit.

You can, of course, derive from a type besides Object. For instance:

C# (See the code.)

// compile with: csc InheritanceCS.cs
using System;
class Base {
   protected int i = 5;
   public void Print() {
      Console.WriteLine("i is {0}", i);
   }
}
class Derived : Base {
   double d = 7.3;
   public void PrintBoth() {
      Console.WriteLine("i is {0}, d is {1}", i, d);
   }
}
class Test {
   static void Main() {
        Base b = new Base();
        Console.WriteLine("b:");
        b.Print();

        Derived d = new Derived();
        Console.WriteLine("d:");
        d.Print();
        d.PrintBoth();
        Console.ReadLine();
  }
}

Visual Basic .NET (See the code.)

' compile with: vbc InheritanceVB.vb
Imports System
Class Base
    Protected i As Integer = 5
    Public Sub Print()
        Console.WriteLine("i is {0}", i)
    End Sub
End Class
Class Derived
    Inherits Base
    Dim d As Double = 7.3
    Public Sub PrintBoth()
        Console.WriteLine("i is {0}, d is {1}", i, d)
    End Sub
End Class
Class Tester
    Shared Sub Main()
        Dim b As New Base()
        Console.WriteLine("b:")
        b.Print()

        Dim d As New Derived()
        Console.WriteLine("d:")
        d.Print()
        d.PrintBoth()
        Console.ReadLine()
    End Sub
End Class

Note that class Derived has four members: i, d, Print(), and PrintBoth(). If we declared a Print() in Derived, it would hide, but not eliminate, the Print method in the base class. (We'd still be able to call it from a derived class member by calling base.Print() in C#, or MyBase.Print() in Visual Basic .NET.)

In ASP.NET, the code for the two classes is nearly identical: since we can't use Console.WriteLine, we change the methods to return the string formatted by String.Format (which has parameters identical to Console.WriteLine) and rename the methods:

ASP.NET with Visual Basic .NET (You can see the code and run this application.)

Class Base
    Protected i As Integer = 5
    Public Function GetValue() As String
        Return String.Format("i is {0}", i)
    End Function
End Class
Class Derived
    Inherits Base
    Dim d As Double = 7.3
    Public Function GetBoth() As String
        Return String.Format("i is {0}, d is {1}", i, d)
    End Function
End Class

The other change is that the method calls that were in Main in the console application are moved to the event handlers for the buttons:

Private Sub Button1_Click(ByVal sender As System.Object, _
       ByVal e As System.EventArgs) Handles Button1.Click
    Dim b As New Base()
    Label1.Text = "b's GetValue: " + b.GetValue()
End Sub

Private Sub Button2_Click(ByVal sender As System.Object, _
       ByVal e As System.EventArgs) Handles Button2.Click
    Dim d As New Derived()
    Label2.Text = "d's GetValue: " + d.GetValue()
    Label3.Text = "d's GetBoth:  " + d.GetBoth()
End Sub

Private Sub Button3_Click(ByVal sender As System.Object, _
       ByVal e As System.EventArgs) Handles Button3.Click
    Dim a = New ArrayList()
    a.Add(5)  ' converted automatically to Object (using boxing)
    Dim five As Integer = CType(a(0), Integer)
    ' object unboxed, put in five
    Label4.Text = String.Format( _
        "The integer {0} has been converted to Object and back " + _
        "(boxed and unboxed).", five)
End Sub

The form is as you'd expect: a set of ASP.NET buttons and labels, such as:

ASP.NET (You can see the complete code.)

      <form id="Form1" method="post" runat="server">
         <asp:Button id="Button1" runat="server" 
            Text="Show base class's value"></asp:Button>&nbsp;
         <asp:Label id="Label1" runat="server">
            What will Label1 show?</asp:Label>

... and so on.

Single inheritance only of implementation

The .NET Framework supports only single inheritance: In any given class, you can only derive from one base class. However, that base class might have been derived from another, and so on—all the way back to Object. (Although you can directly inherit from only one class, your class can implement any number of interfaces. We'll talk about this later.)

Every object has exactly one type. The type of the object represents exactly what kind of thing the object is. Recall that types can be derived from other types. For instance, the type CaseInsensitiveSortedList is derived from SortedList, which is derived from Object.

Although the complete type identity of a CaseInsensitiveSortedList object is CaseInsensitiveSortedList, it is also a SortedList and an Object and can be treated as if it were either of those classes by code that wants to be more general.

For instance, you might write some methods that know how to deal with SortedList objects, or with objects in general (all objects are derived from Object). That code could also be used with CaseInsensitiveSortedList objects or any other class derived from the SortedList—in other words, from whatever type the method uses.

The conversion of a derived type to any of its base types is automatic. For instance, if we had a method defined to take a base class object:

C#

   void Foo(Base b) {
      // ...
   }

Visual Basic .NET

   Sub Foo(ByVal b as Base)
      ' ...
   End Sub

We could pass a derived object:

C#

   Derived d = new Derived();
   Foo(d);

Visual Basic .NET

   Dim d as New Derived()
   Foo(d)

... without explicit conversion. This is possible because it's always safe to treat a derived object as though it were a base-class object.

The opposite is not true—you'll fail if you try to call a derived method on a base-class object. But you can explicitly cast the object to the derived type. If the object really is that type (or a type derived from it), the cast will succeed. If not, an exception will be thrown.

You can also check to see if an object can be cast to another type with the is operator in C# or the TypeOf... Is operator in Visual Basic .NET. Both of these return true if the object is of the type specified or a type derived from that type. For example:

C#

   Derived d = new Derived();
   Console.WriteLine(d is Derived);   // true
   Console.WriteLine(d is Base);      // true
   Console.WriteLine(d is object);   // true
   Console.WriteLine(d is int);      // false

Visual Basic .NET

   Dim d as New Derived()
   Console.WriteLine(TypeOf d is Derived)   ' true
   Console.WriteLine(TypeOf d is Base)      ' true
   Console.WriteLine(TypeOf d is Object)    ' true
   Console.WriteLine(TypeOf d is Integer)   ' false

We Are All Objects

You'll notice above that all objects are ultimately derived from object/Object—as you'll also notice, the is/TypeOf...Is operator returns true when checking to see if any object is of/derived from type Object.

Because Object is the ultimate base class of all classes, methods are often declared to take Object objects when they're generic to any type. Because all types, even built-in types such as int, can be converted to Object implicitly (more on this later), such methods can take any type of parameter. (Note that the ability to convert built-in types to objects uses boxing, as discussed last time. This capability is handy, but rare, and is supported in the .NET Framework, but not in "Brand J.")

Treating different types as Object is very commonly used in collections of objects. You can put an object into a collection without casting it, since the add function typically takes an Object as a parameter, and there's an implicit conversion from any type to Object. However, the get method will also return Object, so when you get your object back out of the collection, you'll have to cast the Object reference back to the type it really is, so you can store and use an appropriately typed reference.

In the example below, ArrayList is a standard .NET Framework class that contains a list of Objects. We're putting one object, a boxed int/Integer, into the ArrayList and retrieving it. The reference we retrieve is to an object/Object, so we need to cast it to int/Integer in order to be able to assign it to a properly typed variable. Note that if the object cannot be cast to int/Integer, the cast will throw an exception.

Also, we had to use using/Imports System.Collections to access ArrayList.

C# (See the code.)

   ArrayList a = new ArrayList();
   a.Add(5);   // converted automatically to Object (using boxing)
   int five = (int)a[0];   // object unboxed, put in five

Visual Basic .NET (You can view the complete code for console and for ASP.NET.)

   Dim a = New ArrayList()
   a.Add(5)      ' converted automatically to Object (using boxing)
   Dim  five as Integer = CType(a[0], Integer)
   ' object unboxed, put in five

Polymorphism, Virtual, and Overrides

One of the more powerful concepts in object-oriented programming is polymorphism. The roots of this word, "poly-" and "-morph" mean, when put together, "many forms." Specifically, the behavior of a polymorphic method call can take many forms even though called with the exact same code—depending on the type of the object being called.

For an example, consider the ToString method. Int32.ToString returns a string containing the string representation of the integer; Double.ToString returns a string containing the string representation of a floating-point number. The default behavior of ToString is to return the name of the type. Clearly, different classes implement this behavior differently—with many, many forms. (We'll talk about how your classes can implement their own ToString method shortly.)

Calling Directly

So, the code to follow is pretty simple. We create a set of objects and call ToString on each:

C# (See the code.)

   object o = new object();
   int i = 5;
   double d = 3.7;
   YourType y = new YourType();  // has its own ToString
   Console.WriteLine("o: {0}, i: {1}, d: {2}, y: {3}", 
                     o.ToString(), i.ToString(),
                     d.ToString(), y.ToString()
   );

Visual Basic .NET (You can view the complete code for console and for ASP.NET and run this application.)

    Dim o As New Object()
    Dim i As Integer = 5
    Dim d As Double = 3.7
    Dim y As New YourType()   ' has its own ToString
    Console.WriteLine("o: {0}, i: {1}, d: {2}, y: {3}", _
          o.ToString(), i.ToString(), _
          d.ToString(), y.ToString())

The output in either case is:

   o: System.Object, i: 5, d: 3.7, y: YourType's ToString!

Note that the ASP.NET application differs in setting a label using String.Format (instead of calling Console.WriteLine) and handling button clicks, just as in the inheritance application.

Each call to ToString will call a different method—the appropriate one for that class. And it's easy to tell what method will be called: just look at the type of the object that's having ToString called on it.

Calling Through a Reference to a Base Class

But let's say we had a handy utility function to write out a variable. We'll call it WriteNameValue and implement it as follows. Because the second parameter is an Object, we can pass any type to it:

C# (See the code.)

   static public void WriteNameValue(String name, Object value) {
      Console.WriteLine("{0}: {1}", name, value.ToString());
   }

Visual Basic .NET (See the code.)

Visual Basic .NET
    Public Sub WriteNameValue(ByVal name As String, ByVal value As Object)
        Console.WriteLine("{0}: {1}", name, value.ToString())
    End Sub

ASP.NET with Visual Basic .NET (You can see the complete code.)

Public Function WriteNameValue(ByVal name As String, _
       ByVal value As Object) As String
    Return String.Format("{0}: {1}", name, value.ToString())
End Function

And let's say we declared four objects (as above) and called WriteNameValue four times:

(You can view this code for Visual Basic .NET and for C#; for C#, add a semicolon to the end of each line.)

   WriteNameValue("o", o)
   WriteNameValue("i", i)
   WriteNameValue("d", d)
   WriteNameValue("y", y)

In ASP.NET, we use the return value from the WriteNameValue method, concatenating it into a string with line breaks before putting the string into a text box.

ASP.NET (You can see the complete code.)

   TextBox1.Text = _
        WriteNameValue("o", o) + Chr(13) + _
        WriteNameValue("i", i) + Chr(13) + _
        WriteNameValue("d", d) + Chr(13) + _
        WriteNameValue("y", y)

The code in WriteNameValue stays the same for all four calls. It might even be in another assembly, compiled separately. So, will we always call the ToString in Object? After all, the type of reference is Object, regardless of the type of object the reference actually refers to. So, shouldn't the four calls print "Object" four times? It might not be what we want, but it makes sense, at least to pick which ToString to call based on the type of the reference—in this case, Object. Indeed, that is the default behavior for the .NET Framework (just as in C++).

But if you run this program, you'll find that it behaves differently—that, in fact, the four different calls to the exact same code in WriteNameValue give four different method calls, each depending on the actual type of the object passed. In other words, many forms of the method call are executed. This is exactly what we want. But why does it work?

Well, if you take a look at the documentation (or the metadata) for ToString (go on, do it!), you'll see that it's declared virtual (Overridable in Visual Basic .NET). This means that when you call this method, regardless of the type of reference you use to do the call (even Object), you always call the most-derived method available—in other words, the right one for the actual type of the object.

Methods or properties can be virtual/Overridable. In a given class hierarchy, the virtual/Overridable keyword appears the first time the method is declared (in the most base class). It must never appear on a method of the same signature in a derived class. Rather, you MUST use the override/Overrides keyword to indicate to the compiler that you're explicitly overriding the base class's implementation.

If you leave out the override/Overrides keyword, you hide (or shadow) the base class implementation rather than overriding it, so you'll not get the results you expect from polymorphic calls. If you did this in the program above, calling ToString with the object itself works correctly, but calling it via a reference of type Object calls the base class's method, not the most derived method (specifically, it calls Object.ToString, which prints out the object's type name, "YourType"). Load up the code and try it! Also note that if you do forget override/Overrides, you'll get a warning, not an error, when you compile.

Let's revise our earlier Base/Derived example to take advantage of virtual (Overridable) methods:

C# (See the code.)

// compile with: csc PolyExampleCS.cs
   class BaseClass {
      int i = 5;
      public virtual void Print() {
         Console.WriteLine("i is {0}", i);
      }
      public override String ToString() {
         return i.ToString();
      }
   }
   class DerivedClass : BaseClass {
      double d = 2.1;
      public override void Print() {
         base.Print();
         Console.WriteLine("d is {0}", d);
      }
      public override String ToString() {
         return base.ToString() + " " + d.ToString();
      }
   }
   class TestBaseDerived {
      public static void Main() {
         BaseClass b = new BaseClass();
         DerivedClass d = new DerivedClass();
         BaseClass bd = new DerivedClass();   // note this!
         b.Print();                   // BaseClass.Print()
         d.Print(); bd.Print();       // BOTH Derived.PrintClass()
         Console.WriteLine("b: {0}, d: {1}, bd: {2}", b, d, bd);
      }
   }

Visual Basic .NET (See the code.)

' compile with: vbc PolyExampleVB.vb
Class BaseClass
    Dim i As Integer = 5
    Public Overridable Sub Print()
        Console.WriteLine("i is {0}", i)
    End Sub
    Public Overrides Function ToString() As String
        Return i.ToString()
    End Function
End Class
Class DerivedClass
    Inherits BaseClass
    Dim d As Double = 2.1
    Public Overrides Sub Print()
        MyBase.Print()
        Console.WriteLine("d is {0}", d)
    End Sub
    Public Overrides Function ToString() As String
        Return MyBase.ToString() + " " + d.ToString()
    End Function
End Class
Class TestBaseDerived
    Public Shared Sub Main()
        Dim b As New BaseClass()
        Dim d As New DerivedClass()
        Dim bd As BaseClass = New DerivedClass() ' note this!
        b.Print()    ' BaseClass.Print()
        d.Print()
        bd.Print()   ' BOTH Derived.PrintClass()
        Console.WriteLine("b: {0}, d: {1}, bd: {2}", b, d, bd)
        Console.ReadLine()
    End Sub
End Class

The ASP.NET version is the same as the Visual Basic .NET version except the methods that call Console.WriteLine are, as in the inheritance example, modified to format and return the string instead. For example:

ASP.NET with Visual Basic .NET (You can see the complete code and run this application.)

' in class BaseClass
    Public Overridable Function GetValue() As String
        Return String.Format("i is {0}", i)
    End Function

And, of course, the statements in Main in the console application are moved to various button-click handlers. For example:

ASP.NET with Visual Basic .NET (You can see the complete code.)

Private Sub Button1_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles Button1.Click
    Dim b As New BaseClass()
    Label1.Text = "b: " + b.GetValue()    ' BaseClass.GetValue()
End Sub

You can also see the .aspx file.

Note how the virtual/Overridable, override/Overrides, and base/MyBase keywords are used. The pattern of a derived class method calling a base class method is extremely common—sometimes at the beginning, sometimes at the end, sometimes in the middle.

In Main, note how we have a BaseClass reference, bd, referring to a DerivedClass object. When we call the Print method, the derived method is called because Print is virtual/overridable.

Polymorphism is most powerful when you can extend your classes to fit into an existing framework. In this example, note that we've provided our own overrides of ToString. As a result, we can use our objects in the standard Console.WriteLine call—and it works!

Another example is being able to override behavior of a control (either Microsoft Windows® Forms or Web Form) by deriving from the .NET Framework-supplied base class, and then overriding the methods you need to change, such as OnInit or OnCreateControl.

And the last example we'll discuss is being able to override Dispose to have the runtime call your Dispose method when the object needs to be cleaned up—perhaps even from a using statement in C#!

In our example with ToString, what's happening is that we're calling the version of Console.WriteLine that takes a String and a set of Object objects. Each of b, d, and bd are converted to Object using the implicit conversion, and passed.

Console.WriteLine calls ToString on each of the objects it has passed to get a string representation of the object's value. (There are other options. Check the documentation for details.) Because ToString is a virtual method in Object, our ToString overrides are called, and life is wonderful.

By the way, if you ever want to hide, rather than override, a virtual method in your base class, you can leave off the override/Overrides keyword. But if you do, you'll get a warning—to avoid the warning and to make your intent clear, use the new/Shadows keyword in place of the override keyword.

Classes Can Be Abstract (MustInherit) or Sealed (NotInheritable)

An abstract/MustInherit class is one that can't be instantiated, meaning no instances can be created of that class. Why would you want such a thing?

You mark a class abstract (MustInherit in Visual Basic .NET) when you intend it to only be a base class for some other class. The usual reason for doing this is that you have some common functionality that you can write generic code for but you can't write a concrete implementation of one or more of the methods and/or properties.

The canonical example is a set of objects you can draw: circles, rectangles, lines, points, etc. It would be handy to have a generic drawable object class to represent any drawable object. But the Draw method in the generic class will have to be abstract, because there is no generic implementation possible.

When you derive a specific drawable object from the generic drawable object class, you'll implement the correct Draw method. The advantage of this is that you can treat each drawable object as a generic drawable object, so you can have, for instance, collections of them and just iterate through the collection, drawing each object without regard to its actual type.

We'll have a sample program that does just this in the next article.

These methods and properties that you can't implement are called abstract (or MustOverride in Visual Basic .NET) methods and/or properties—you can write the interface in the abstract/MustInherit base class, but not the implementation. An abstract method or property is declared but not implemented in that class, and is automatically virtual/Overridable. In the inherited class, you must implement the abtract/MustOverride methods and properties, unless the inherited class is also abstract. In any case, before you can create an actual object, you have to have a concrete (non-abstract) class, meaning that there are no remaining abstract/MustOverride methods or properties.

The sealed/NotInheritable keyword has a near-opposite meaning: it says that you cannot inherit from this class. Sealing a class tells your users that you don't expect them to inherit from you. Proper use of sealed/NotInheritable can also make your code run more efficiently, because calls to virtual/Overridable functions in your class can be converted to more efficient direct calls.

You can also make individual methods and properties sealed (NotOverridable in Visual Basic .NET). This allows you to end overriding of a particular method in derived classes while still allowing derived classes.

Obviously, a class cannot be both abstract/MustInherit and sealed/NotInheritable; nor can a method or property be both abstract/MustOverride and sealed/NotOverridable. (The Visual Basic .NET keywords make this really obvious, don't they?)

Interfaces

Sometimes you don't care what an object is—you merely care about what it does. And it's possible for classes that aren't related in the inheritance tree (except that they're both derived from Object) to implement some common functionality. This common functionality can be grouped together into an interface containing zero or more abstract methods. Visual Basic and COM programmers will find interfaces familiar.

You'll almost always have at least one method in an interface in .NET Framework programs. If you just want to use an empty interface to mark a class as having some attribute, you should use an attribute instead (for example, the Serializable attribute we need to be able to share objects as session or view state). We'll talk more about attributes another time.

Creating and Implementing an Interface

As an example, let's create an interface called ICopyable, which describes the method necessary to copy an object (note that there's a standard interface called ICloneable that does the same thing):

C# (See the code.)

   interface ICopyable {
      object Copy(); // returns a reference to a copy of the object
   }

(You can view this code for Visual Basic .NET and for ASP.NET. The code is exactly the same for both.)

Interface ICopyable
    Function Copy() As Object   ' returns ref to a copy of the object
End Interface

The Copy method, as all methods in an interface, is automatically public and abstract.

Classes that wanted to provide the ability to be copied could implement ICopyable:

C# (See the code.)

   class Hoo : ICopyable {
      public int i; // using public ONLY for demo since shorter!
      public Hoo(int i) {
         this.i = i;
      }
      public object Copy() {
         return new Hoo(i);
      }
   }
   class TestHoo {
      public static void Main() {
         Hoo h1 = new Hoo(5);
         Hoo h2 = (Hoo)h1.Copy(); // note cast necessary
         Console.WriteLine("Original: {0}; Copy: {1}", h1.i, h2.i);
         Console.ReadLine();
      }
   }

Visual Basic .NET (You can see the code; ASP.NET code is the same for class Hoo.)

Class Hoo
    Implements ICopyable
    Public i As Integer ' using public ONLY for demo since shorter!
    Public Sub New(ByVal i As Integer)
        Me.i = i
    End Sub
    Public Function Copy() As Object Implements ICopyable.Copy
        Return New Hoo(i)
    End Function
End Class
Class TestHoo
    Public Shared Sub Main()
        Dim h1 As New Hoo(5)
        Dim h2 As Hoo = CType(h1.Copy(), Hoo) ' note cast necessary
        Console.WriteLine("Original: {0}; Copy: {1}", h1.i, h2.i)
        Console.ReadLine()
    End Sub
End Class

This class's Copy method is simple, but more complicated classes with references to other objects, such as data collections, would be much more complicated.

Since we're not using Console.WriteLine, the ASP.NET code is identical except for class TestHoo, which is done as button click-event handlers as we did it before. (You can see the Visual Basic .NET and ASPX code, and run this application.)

Implementation details

The code is extremely similar in both languages, but the way in which you tell the compiler which method in your class you're using to implement which method in which interface is quite different in the two languages.

In Visual Basic .NET, you must explicitly state which method in which interface your method (in this case, Copy) implements. Because you explicitly state this, you can name the method anything you like, although it's most common to name it the same as the method name it's implementing. You can also have one method in your class be the implementation for as many methods as you like.

C# uses the name of the method you write to match your implementation to the method (and interface) you're implementing. You can specify the name of the interface you're implementing—this is called explicit implementation. When you do this, the method needs to be private, so it can only be called through an interface reference, not with an object directly. Explicit implementation also allows you to implement more than one interface containing methods with the same signature.

A note about our ICopyable interface: because there's a standard interface called ICloneable, which defines a method called Clone that does the same job we're doing with ICopyable.Copy, you should use ICloneable instead of our custom interface. Using the standard interface is far superior because it means your class can be used by any code that knows how to use the standard interface. We wrote our own ICopyable interface just for demonstration purposes.

Interface Polymorphism

Just as there's an implicit conversion from a derived class to any of its base classes, there's also an implicit conversion from a class to any of the interfaces it or its base classes implement. So, we could write a method that takes any ICopyable object:

C# (See the code.)

   static Object CopyMe(ICopyable ic) {
      return ic.Copy();
   }

   // in some other method...
   Hoo h3 = new Hoo(7);
   Hoo h4 = (Hoo)CopyMe(h3);
   // some other class that implements ICopyable
   Woo w3 = new Woo();
   Woo w4 = (Woo)CopyMe(w3); // EXACT SAME METHOD as above!

Visual Basic .NET (You can see the code; the ASP.NET code is identical.)

    Public Shared Function CopyMe(ByVal ic As ICopyable) As Object
        Return ic.Copy()
    End Function

    ' in some other method...
    Dim h3 As New Hoo(7)
    Dim h4 As Hoo = CType(TestCopy.CopyMe(h3), Hoo) ' method call
    Dim w5 As New Woo(3.8) ' some other class that implements ICopyable
    Dim w6 As Woo = CType(TestCopy.CopyMe(w5), Woo) 
    ' EXACT SAME METHOD as above!

(For ASP.NET, you can click here for Visual Basic .NET and click here for ASPX to view the complete code.)

Again, you'd use the standard interface ICloneable instead of writing your own for this case.

Here's another example: there's an IComparable interface that defines a CompareTo method that compares two objects of the same type. Any class that supports less-than/equal/greater-than comparisons can implement the IComparable interface (and its CompareTo method). Objects whose classes implement this interface can be used in sorted data structures just by adding them in: the data structure object takes care of calling the CompareTo method by casting each object to its IComparable interface.

By the same token, classes that implement IFormattable will have their Format method called by the overload of Console.WriteLine that uses a formatting string (rather than ToString, which is only called if the object doesn't implement IFormattable).

As you can see, when you write code to work with interfaces, you don't care what the object is—you just care about what it can do. The object's inheritance list says what the object is; its interface implementation list says what it can do. So, an object that implements IFormattable can be formatted; one that implements IConvertable can be converted; IClonable can be cloned (copied), and so on.

In C#, you can check to see if an object can be cast to a particular class or interface by using the is operator, as in "if (o is Hoo)" or "if (o is IClonable)". In Visual Basic .NET, you use the TypeOf... Is operator in a similar fashion, as in "If TypeOf o is Hoo Then ...". It's considered bad object-oriented programming style to have to check whether an object is a specific type, but sometimes a hack like this can save a lot of grief, so it's the lesser of two evils. Dr. GUI tries to avoid it, though.

Interfaces vs. Abstract Base Classes

One of the interesting arguments is whether you should use an interface or an abstract class to describe a set of methods and properties inherited classes must implement.

Dr. GUI solves this using the "IS A" rule: If the essential type of the derived classes "IS A" specialization of the base type, then inherit from an abstract class. Use an interface if the interface is not related to the essential type of the actual objects, because interfaces express what an object "CAN DO," not what it essentially is.

So, because a circle "IS A" generic drawable object, we derive the circle class from the generic drawable object abstract base class to express that relationship. But the circle also "CAN DO" cloning, so we implement the IClonable interface.

There are other ways of looking at this as well—for instance, you might note that you can inherit partial implementation of an abstract class but that you can't inherit implementation of an interface, so you might prefer an interface if there's no common implementation to inherit, especially if the interface can be implemented by a wide variety of classes from many inheritance hierarchies (for example, ICloneable). On the other hand, you might prefer an abstract class for closely related classes that share their implementations and that you want to version at the same time.

For more on this, check out the recommendations in the Visual Basic and Microsoft Visual C#™ product documentation. You might also want to read the entire section on programming with components.

An important note: Be careful when you design your interfaces—once you've published them, you shouldn't change them, even to add a method. (If you add a method, every class that implements your interface will also have to implement that method—and they'll be broken until they do.) You can add methods to classes without breaking derived classes, however.

Give It a Shot!

If you've got .NET, the way to learn it is to try it out ... and if not, please consider getting it. If you spend an hour or so a week with Dr. GUI .NET, you'll be an expert in the .NET Framework before you know it.

Be the First on Your Block—and Invite Some Friends!

It's always good to be the first to learn a new technology, but even more fun to do it with some friends. For more fun, organize a group of friends to learn .NET together!

Some Things to Try...

First, try out the code shown here. Some of it is excerpted from larger programs; you'll have to build up the program around those snippets. That's good practice. (Or use the code the good doctor provides, if you must.) Either way, play with the code some.

Write some inherited classes, say three levels deep, and try the various protection modifiers. Use both virtual and non-virtual methods and properties.

Write an interface and a small framework that uses that interface, and then write some otherwise-unrelated types that implement your interface and show that they work great in your framework.

What We've Done; What's Next

This time, we reviewed inheritance and polymorphism and showed how the .NET Framework implements them.

Next time, we'll see a more complete example of inheritance, abstract base classes, and interfaces in a cute drawing application. This won't be a console application; because it's graphical, it will be a Windows Forms application instead. The ASP.NET version will demonstrate using custom-drawn bitmaps on Web pages—something that's hard to do in most Web programming systems, but easy with ASP.NET.

Show: