Comparing Objects
We have quite exhaustively studied issues involved in copying objects. We will now examine the issues involved in comparing objects. In order to compare objects, the .NET Framework uses the interface IComparable. In this section, we will examine the use of the interface IComparable through an example of sorting an array.
Sorting an Array
The System.Array class provides a static method, Sort, which can be used for sorting an array. The program ArrayName\Step0 illustrates an attempt to apply this Sort method to an array of Name objects, where the Name class simply encapsulates a string through a read-only property Text.
// ArrayName.cs - Step 0 using System; public class Name { private string text; public Name(string text) { this.text = text; } public string Text { get { return text; } } } public class ArrayName { public static int Main(string[] args) { Name[] array = new Name[10]; array[0] = new Name("Michael"); array[1] = new Name("Charlie"); array[2] = new Name("Peter"); array[3] = new Name("Dana"); array[4] = new Name("Bob"); Array.Sort(array); return 0; } }
Anatomy of Array.Sort
What do you suppose will happen when you run this program? Here is the result:
Exception occurred: System.ArgumentException: [icc] At least one object must implement IComparable.
The static method Sort of the Array class relies on some functionality of the objects in the array. The array objects must implement IComparable.
Suppose we don't know whether the objects in our array support IComparable. Is there a way we can find out programmatically at runtime?
Using the is Operator
There are in fact three ways to dynamically check to see whether an interface is supported:
- Use exceptions.
- Use the as operator.
- Use the is operator.
In this case, the most direct solution is to use the is operator (which is applied to an object, not to a class). See ArrayName\Step1.
// ArrayName.cs - Step 1 ... public class ArrayName { public static int Main(string[] args) { Name[] array = new Name[10]; array[0] = new Name("Michael"); array[1] = new Name("Charlie"); array[2] = new Name("Peter"); array[3] = new Name("Dana"); array[4] = new Name("Bob"); if (array[0] is IComparable) Array.Sort(array); else Console.WriteLine( "Name does not implement IComparable"); return 0; } }
Here is the output from running the program. We're still not sorting the array, but at least we fail more gracefully.
Name does not implement IComparable
Use of Dynamic Type Checking
We can use dynamic-type checking of object references to make our programs more robust. We can degrade gracefully rather than fail completely.
For example, in our array program, the desired outcome is to print the array elements in sorted order. We could check whether the objects in the array support IComparable, and if not, we could go ahead and print out the array elements in unsorted order, obtaining at least some functionality.
Implementing IComparable
Consulting the documentation for System, we find the following specification for IComparable:
public interface IComparable { int CompareTo(object object); }
We will implement IComparable in the class Name. See ArrayName\Step2. We also add a simple loop in Main to display the array elements after sorting.
// ArrayName.cs - Step 2 using System; public class Name : IComparable { private string text; public Name(string text) { this.text = text; } public string Text { get { return text; } } public int CompareTo(object obj) { string s1 = this.Text; string s2 = ((Name) obj).Text; return String.Compare(s1, s2); } } public class ArrayName { public static int Main(string[] args) { ... foreach (Name name in array) Console.WriteLine(name); return 0; } }
Incomplete Solution
If we run the above program, we do not exactly get the desired output:
Name Name Name Name Name
The first five lines of output are blank, and in place of the string in Name, we get the class name Name displayed. The unassigned elements of the array are null, and they compare successfully with real elements, always being less than a real element.
Complete Solution
We should test for null before displaying. The most straightforward way to correct the issue of the strings in Name not displaying is to use the Text property. A more interesting solution is to override the ToString method in our Name class. Here is the complete solution, in the directory ArrayName\Step3.
// ArrayName.cs - Step 3 using System; public class Name : IComparable { private string text; public Name(string text) { this.text = text; } public string Text { get { return text; } } public int CompareTo(object obj) { string s1 = this.Text; string s2 = ((Name) obj).Text; return String.Compare(s1, s2); } override public string ToString() { return text; } } public class ArrayName { public static int Main(string[] args) { Name[] array = new Name[10]; array[0] = new Name("Michael"); array[1] = new Name("Charlie"); array[2] = new Name("Peter"); array[3] = new Name("Dana"); array[4] = new Name("Bob"); if (array[0] is IComparable) Array.Sort(array); else Console.WriteLine( "Name does not implement IComparable"); foreach (Name name in array) { if (name != null) Console.WriteLine(name); } return 0; } }
Here is the output:
Bob Charlie Dana Michael Peter
Compiler Warning
You may notice a compiler warning when you build this program. The compiler can tell that the IComparable interface is implemented by the class Name, so the runtime check is superfluous. The Step 4 solution shows a more general situation in which Name, in fact, does not implement the interface. Rather, the interface is implemented in a derived class SortableName, and there will be no compiler warning. The runtime check makes sense. You can examine this version of the program by downloading the sample chapter from the book.