Comparing Instances of a Type Parameter


Sometimes you will want to compare two instances of a type parameter. For example, you might want to write a generic method called isIn( ) that returns true if some value is contained within an array. To accomplish this, you might first try something like this:

 // This won't work! public static bool isIn<T>(T what, T[] obs) {   foreach(T v in obs)     if(v == what) // Error!       return true;   return false; }

Unfortunately, this attempt won’t work. Because T is a generic type, the compiler has no way to know precisely how two objects should be compared for equality. Should a bitwise comparison be done? Should only certain fields be compared? Should reference equality be used? The compiler has no way to answer these questions.

To enable two objects of a generic type parameter to be compared, you use the CompareTo( ) method defined by one of C#’s standard interfaces: IComparable. This interface comes in both a generic and non-generic form. It is implemented by all of C#’s built-in types, including int, string, and double. It is also easy to implement for classes that you create. We will begin with its non-generic version.

The non-generic IComparable interface defines only the CompareTo( ) method shown here:

 int CompareTo(object obj)

CompareTo( ) compares the invoking object to obj. It returns zero if the two objects are equal, a positive value if the invoking object is greater than obj, and a negative value if the invoking object is less than obj.

To use CompareTo( ), you must specify a constraint that requires every type argument to implement the IComparable interface. Then, when you need to compare two objects of the type parameter, simply call CompareTo( ). For example, here is a corrected version of isIn( ):

 // Require IComparable interface. public static bool isIn<T>(T what, T[] obs) where T : IComparable {   foreach(T v in obs)     if(v.CompareTo(what) == 0) // now OK, uses CompareTo()       return true;   return false; }

Notice the use of the constraint

 where T : IComparable

This constraint ensures that only types that implement IComparable are valid type arguments for isIn( ).

The following program demonstrates isIn( ). It also shows how IComparable can be easily implemented by a class.

 // Demonstrate IComparable. using System; class MyClass : IComparable {   public int val;   public MyClass(int x) { val = x; }   // Implement IComparable.   public int CompareTo(object obj) {     return val - ((MyClass) obj).val;   } } class CompareDemo {   // Require IComparable interface.   public static bool isIn<T>(T what, T[] obs) where T : IComparable {     foreach(T v in obs)       if(v.CompareTo(what) == 0) // now OK, uses CompareTo()         return true;     return false;   }   // Demonstrate comparisons.   public static void Main() {     // Use isIn() with int.     int[] nums = { 1, 2, 3, 4, 5 };     if(isIn(2, nums))       Console.WriteLine("2 is found.");     if(isIn(99, nums))       Console.WriteLine("This won't display.");     // Use isIn() with string.     string[] strs = { "one", "two", "three"};     if(isIn("two", strs))       Console.WriteLine("two is found.");     if(isIn("five", strs))       Console.WriteLine("This won't display.");     // Use isIn with MyClass.     MyClass[] mcs = { new MyClass(1), new MyClass(2),                       new MyClass(3), new MyClass(4) };     if(isIn(new MyClass(3), mcs))       Console.WriteLine("MyClass(3) is found.");     if(isIn(new MyClass(99), mcs))       Console.WriteLine("This won't display.");   } }

The output is shown here:

 2 is found. two is found. MyClass(3) is found.

Although the preceding program is correct, there is still one potential trouble spot. Notice how CompareTo( ) is implemented by MyClass:

 public int CompareTo(object obj) {   return val - ((MyClass) obj).val; }

Because the parameter to CompareTo( ) must be of type object, obj must be explicitly cast to MyClass in order for val to be accessed. However, it’s precisely this type of thing that generics were designed to eliminate!

To solve this problem, C# 2.0 adds a second, generic version of IComparable, which is declared like this:

 public interface IComparable<T>

In this version, the type of data being compared is passed as a type argument to T. This causes the declaration of CompareTo( ) to be changed, as shown next:

 int CompareTo(T obj)

Now, the parameter to CompareTo( ) can be specified as the proper type, and no cast from object is needed. IComparable<T> is also implemented by all built-in types.

Here is an improved version of MyClass that implements IComparable<T>:

 // This version of MyClass implements IComparable<T> class MyClass : IComparable<MyClass> {   public int val;   public MyClass(int x) { val = x; }   public int CompareTo(MyClass obj) {     return val - obj.val; // Now, no cast is needed.   } }

Notice that a cast is no longer required by this line in CompareTo( ):

 return val - obj.val; // Now, no cast is needed.

Because the type parameter to IComparable<T> is MyClass, the type of obj is now known to be MyClass.

Here is an updated version of isIn( ) that requires IComparable<T>:

 // Require IComparable<T> interface. public static bool isIn<T>(T what, T[] obs) where T : IComparable<T> {   foreach(T v in obs)     if(v.CompareTo(what) == 0) // now OK, uses CompareTo()       return true;   return false; }




C# 2.0(c) The Complete Reference
C# 2.0: The Complete Reference (Complete Reference Series)
ISBN: 0072262095
EAN: 2147483647
Year: 2006
Pages: 300

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net