4.1. Implementing a System Interface: IComparable

Let's start by implementing our own class instance of a predefined interface within the System namespace. The motivational context is the following: We need to sort an ArrayList of strings in ascending order by the length of the string. OK. That sounds simple enough. So how are we going to do that? If we look at the public methods of ArrayList, we see that it provides an overloaded pair of Sort() member functions:

System.Collections.ArrayList

Sort: Overloaded.
Sorts the elements in the ArrayList, or a portion of it.

Let's look at the documentation for the two different methods and see which one, if either, fits the bill. Here's the one with an empty parameter list:

public virtual void Sort()

Sorts the elements in the entire ArrayList using
the IComparable implementation of each element.

Just by its name we know that IComparable is an interface. The element held by our ArrayList is a string. The String class implements the IComparable interface. The no-parameter Sort() method of the ArrayList uses the IComparable implementation of the String class to determine the ordering of two string objects. This imposes a dictionary order on the words—not an ordering by length. This instance, then, is not of use to us.

Okay. Maybe the next overloaded instance is useful:

public virtual void Sort( IComparer ic )

Sorts the elements in the entire ArrayList using the
special comparer ic.

This instance provides us with a way to override the default ordering algorithm associated with the String class. We'll create a special implementation of the IComparer interface that sorts the strings by length. (If we were defining a class, we would include an implementation of the IComparable interface if we wanted to support sorting within the ArrayList container.)

How do we do that? What are the IComparer methods, and what do they do? Not only do we have to provide a definition of every interface member, but we must implement the documented behavior of each member if we want our implementation to transparently meld with the other implementations of that interface.

In addition, we need to throw the same exceptions under the same abnormal conditions. The compiler cannot enforce this. Recognizing and throwing the exceptions are responsibilities of the interface implementation.

It turns out there is only one method to implement. Phew! Not only that, but the implementation seems doable:

int Compare( object x, object y );

Compares two objects and returns a value indicating whether
one is less than (negative number), equal to (zero), or greater
than (positive number) the other.

What about any exceptions the method can throw? Again we have to look at the documentation. As it turns out, there is just one, and it also seems doable:

ArgumentException

Neither x nor y implements the IComparable interface.
-or- x and y are of different types and neither one
can handle comparisons with the other.

This means that our implementation of the IComparer interface requires only one method: Compare(). It should throw an ArgumentException if one or both of the two arguments are invalid; in our case, that simply means that the two arguments are not both strings. Let's call the class StringLengthComparer:

public sealed class StringLengthComparer : IComparer
{
      // first obligation: the Compare() method
      public int Compare( object x, object y ){...}
}

Our Compare() instance must define two parameters of type object, even though our implementation is interested in only two parameters of the string type. This is necessary because Compare() is implicitly a virtual function (as are all interface member functions), and therefore the signature of the overriding instance must exactly match the signature of the inherited method.

This is something of an inconvenience. It means we have to check within Compare() that the arguments are both of type string. If we were able to explicitly declare them as type string, the compiler would enforce that type requirement automatically. We'll see how to get around that—at least partially—in Section 4.4.

The only thing left now is the implementation of Compare(). We can break that down into two primary steps. First we must confirm the validity of the two arguments. That done, we must compare the lengths.

We have two parameters of type object, and we must confirm that they are really instances of type string. One strategy is to use the is operator:

if (( ! ( x is string )) || ( ! ( y is string )))
   throw new ArgumentException( "some dire message" );

// OK: arguments are valid;
//     let's cast them to string objects

string xs = (string) x; string ys = (string) y;

Alternatively, we can allow the as operator to do the downcasting. We throw an exception if either xs or ys is set to null:

string xs = x as string;
string ys = y as string;

if ( xs == null || ys == null )
  throw new ArgumentException( "some dire message" );

That's really the only tricky part. Here is the length comparison:

int ret_val = 1;

if ( xs.Length < ys.Length )
     ret_val = -1;
else
if ( xs.Length == ys.Length )
     ret_val = 0;

return ret_val;

So we've implemented our first concrete instance of an interface class. Here is how we might invoke Sort():

ArrayList stringList = new ArrayList();
// fill it up
stringList.Sort( new StringLengthComparer() );

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.97.40