Comparison of instances of generic types. IComparable<T> and IEquatable<T> interfaces
In this topic, you will learn how to properly compare instances of generic types with each other.
Contents
- 1. How to implement comparison of instances of generic type T?
- 2. Implementation of the CompareTo() method of the IComparable interface in classes. The need for application. Explanation
- 3. Implementation of the Equals() method of the IEquatable<T> interface
- 4. Comparison of instances of the Number class in a generic class. An example implementation of the IComparable<T> interface
- 5. Comparison of instances of the Number class in a generic structure. An example of implementing an interface IEquatable<T>
- 6. Comparing objects of generic type T in generic methods
- 7. An example of sorting instances of type Point<T>. Implementing the IComparable<T> and IEquatable<T> Interfaces in a shared static method
- Related topics
Search other resources:
1. How to implement comparison of instances of generic type T?
In a generic class that receives a type T as a parameter, you can implement a comparison of instances of this parameter T. A normal comparison using the == (!=) comparison operator will throw a compilation error. This is because the T parameter is of a generic type, and therefore the compiler will not know how to compare the two objects. Because two objects can be compared in different ways. For example, you might consider comparing based on the values of multiple object fields, or comparing on a single field.
Conclusion: the way to compare two objects of the generic type T must be laid down in some program code, more precisely in a special class method.
To solve this problem, there are corresponding tools in the C# language. To ensure correct comparison of objects of type parameter T in a generic class, the class must implement one of the following interfaces:
- System.IComparable or System.IComparable<T>;
- System.IEquatable<T>.
The choice of this or that interface is determined by the condition of the problem. If you need to compare objects of type T by more or less, then the IComparable<T> interface will do. Such a comparison is necessary, for example, when sorting.
If you need to compare objects of type T for equality/inequality, you can use the IEquatable<T> interface. Comparison tasks include searching for an element in a data array.
According to the syntax of the C# language, the implementation of interfaces implies the implementation of the methods of those interfaces. The class inherits (implements) the corresponding interface and implements (overrides) the methods of this interface.
Basic types int, double, char, etc. the structures Int32, Double, Char and others correspond. In these structures, the above interfaces are implemented, so comparing the data of the base types does not cause problems.
⇑
2. Implementation of the CompareTo() method of the IComparable interface in classes. The need for application. Explanation
The IComparable or IComparable<T> interfaces declare a single CompareTo() method. It is this method that must be implemented in the class in which you want to compare instances of the generic type. According to the documentation, the syntax for declaring a method is as follows
int CompareTo(T other);
here
- T – generic type, which is a class parameter;
- other – a reference to an instance of type T, which is compared to the current instance of the class.
The CompareTo() method must return the following values:
- <0 – if the current instance of the class precedes the instance of other in the case of sorting. In other words, the current instance is smaller than the other;
- =0 – if the current instance of the class is equal (at the same position) to the instance of other;
- >0 – if the current instance of the class follows after the instance of other when sorting. In other words, the current instance is larger than the other instance.
Any comparison logic can be put into the CompareTo() method.
In view of the above, the declaration of the generic class MyGenClass<T>, in which you need to compare objects of type T, can be, for example, like this:
class MyGenClass<T> where T : IComparable<T> { T obj; // current object of type T // ... // A method that compares objects of type T. // return_type is the value returned by the method. return_type CompareMethod(T other) { // The obj.CompareTo(other) method is called if (obj.CompareTo(other)>0) { // Actions, if obj>other // ... } else if (obj.CompareTo(other)<0) { // Actions, if obj<other // ... } else { // Actions, if obj==other // ... } } }
In the above code, when declaring the class MyGenClass<T>, a constraint on the type T is specified
where T : IComparable<T>
this means that the type T must implement the IComparable<T> interface. That is, if the type T is a class (structure), then this class must contain the implementation of the CompareTo() method as agreed.
The type T can be any class or structure that implements the IComparable<T> interface.
The abbreviated code of the class MyClass that implements the IComparable<T> interface can be as follows
class MyClass : IComparable<MyClass> { // ... public int CompareTo(MyClass other) { // Implementing the logic for comparing the current instance with other // ... } }
After the above implementation, the MyClass class can act as a parameter of type T, which is suitable for comparing instances in generic classes.
⇑
3. Implementation of the Equals() method of the IEquatable<T> interface
To compare two objects of generic type T for equality in a generic class, this type T (class, structure) must implement the System.IEquatable<T> interface. This interface declares one single method
bool Equals<T>(T other)
which returns true if the current object is equal to the other object. Otherwise, the method returns false.
In order to ensure compatibility with a concrete implementation of the Equals() method, in addition, the T type must override the Equals() and GetHashCode() methods from the Object class (see the example below).
If the type T is some class named MyClass, then the approximate declaration of this class is as follows
class MyClass : IEquatable<MyClass> { // ... // implementation of the method Equals() of the interface IEquatable public bool Equals(MyClass other) { // Implementation of the logic for comparing the current data (object) with other // ... } // Overriding the Equals() method from the Object class public override bool Equals(object obj) { // Implementation of comparing current data with obj // ... } // Overriding the GetHashCode() method from the Object class public override int GetHashCode() { // ... } }
After the above declaration, the MyClass class can be used as a type argument, which is passed to another generic class that compares objects.
In turn, another generic class that takes a type T as a parameter must specify a constraint on this type in the form of the IEquatable<T> interface.
For example, if in a generic class (structure) named MyGenClass<T> you need to compare two objects of type T for equality, then the shorthand code for declaring such a class with the CompareMethod() comparison method can be as follows
class MyGenClass<T> where T : IEquatable<T> { T obj; // the current object of type T // ... // A method that compares the current object to another object. return_type CompareMethod(T other) { // Calling Equals() method for comparison if (obj.Equals(other)) { // Actions to be performed if obj is equal to other // ... } else { // Actions to be performed if obj is not equal to other // ... } } }
⇑
4. Comparison of instances of the Number class in a generic class. An example implementation of the IComparable<T> interface
The example declares two classes:
- generic class MyGenClass<T>. In this class, the type parameter T is constrained by the IComparable<T> interface;
- the non-generic Number class — serving as a placeholder type in the generic class MyGenClass<T>.
using System; namespace ConsoleApp19 { // In a class, a parameter of type T is limited to the IComparable<T> interface. // This means that the T type must implement the CompareTo() method of the IComparable<T> interface. class MyGenClass<T> where T : IComparable<T> { T obj; // internal field - the instance of type T // Конструктор public MyGenClass(T _obj) { obj = _obj; } // Properties to access to the internal field public T Obj { get { return obj; } set { obj = value; } } // Demo method that outputs the result of comparing // the current value of obj with other.obj public void GreateThen(T other) { if (obj.CompareTo(other) > 0) Console.WriteLine("obj > other.obj"); else if (obj.CompareTo(other) < 0) Console.WriteLine("obj < other.obj"); else Console.WriteLine("obj == other.obj"); } } // A class suitable for comparing instances in generic classes. // The class defines a certain number. The class implements the IComparable<T> interface class Number : IComparable<Number> { // Internal field double num = 0; // Constructor public Number(double num) { this.num = num; } // Inner field access property public double Num { get { return num; } set { num = value; } } // Implementation of the CompareTo() method from the IComparable<Number> interface public int CompareTo(Number other) { if (this.num > other.num) return 1; if (this.num < other.num) return -1; return 0; } } class Program { static void Main(string[] args) { // 1. Class Number. Create and compare 2 instances of type Number Number num1 = new Number(33); Number num2 = new Number(40); Console.WriteLine("num1.CompareTo(num2) == {0}", num1.CompareTo(num2)); // 2. Instantiating a generic class with a placeholder int MyGenClass<int> obj1 = new MyGenClass<int>(45); MyGenClass<int> obj2 = new MyGenClass<int>(37); // Call a comparison method for the int placeholder obj1.GreateThen(39); obj2.GreateThen(100); // 3. Instantiating a generic class with the Number placeholder type Number num3 = new Number(23.8); Number num4 = new Number(23.8); MyGenClass<Number> obj3 = new MyGenClass<Number>(num3); // Call the comparison method obj3.GreateThen(num4); // obj == other.obj Console.WriteLine("Ok!"); Console.ReadKey(); } } }
⇑
5. Comparison of instances of the Number class in a generic structure. An example of implementing an interface IEquatable<T>
This example demonstrates a comparison of objects of type T in generic structures. The following elements are declared:
- the generic structure MyGenStruct<T>, in which the parameter of type T is bounded to the IEquatable<T> interface;
- a Number class that implements the IEquatable<T> interface. In the main() function, the class is passed to the MyGenStruct<T> structure as a type argument.
using System; namespace ConsoleApp19 { // A structure in which a parameter of type T implements the IEquatable<T> interface struct MyGenStruct<T> where T : IEquatable<T> { T obj; // internal object // Constructor public MyGenStruct(T _obj) { obj = _obj; } // The property to access to obj field public T Obj { get { return obj; } set { obj = value; } } // Demo method that compares objects of type T public void CompareMethod(T other) { // Calling the Equals() method implemented in type T if (obj.Equals(other)) { Console.WriteLine("obj == other.obj"); } else { Console.WriteLine("obj != other.obj"); } } } // A class serving as a placeholder type class Number : IEquatable<Number> { // Internal field double num; // Constructor public Number(double _num) { num = _num; } // The property to access to the num field public double Num { get { return num; } set { num = value; } } // Implementation of Equals() method from IEquatable<T> interface public bool Equals(Number other) { return num == other.num; } // Overriding the Equals(Object) method from the Object class public override bool Equals(object obj) { // Comparison of current data with the obj if (obj is Number) return Equals((Number)obj); return false; } // Overriding the GetHashCode() method from the Object class public override int GetHashCode() { return num.GetHashCode(); } } class Program { static void Main(string[] args) { // Declare two numbers Number num1 = new Number(2.8); Number num2 = new Number(3.5); // Declare an instance of the class MyGenClass<T> MyGenStruct<Number> obj1 = new MyGenStruct<Number>(num1); // Compare instance data with num2 obj1.CompareMethod(num2); Console.WriteLine("Ok!"); Console.ReadKey(); } } }
Program execution result
obj != other.obj Ok!
⇑
6. Comparing objects of generic type T in generic methods
If in some non-generalized class (structure) a generalized method is implemented that receives the type T as a parameter, then in this method it is possible to compare objects of type T. In this case, the requirements for a method are the same as for a class or structure. A generic method must specify constraints on a parameter of type T. Constraints must be one of the IComparable<T> or IEquatable<T> interfaces.
In addition, the type-parameter T itself must implement one of the interfaces IComparable<T> or IEquatable<T>.
The syntax for declaring a method in which you want to compare objects of type T can be something like the following
return_type MethodName<T>(parameters) where T : IComparable<T> where T : IEquatable<T> { // Actions to be taken // here you can call the CompareTo() or Equals() methods // in an object of type T // ... }
here
- MethodName – the name of method;
- T – a type parameter that implements the IComparable<T> or IEquatable<T> interfaces. That is, this type implements the CompareTo() method;
- return_type – the type returned by the method. Type T may also appear here;
- parameters – the parameters that the method receives.
In the above code, one of the two bounds on the type T
... where T : IComparable<T> where T : IEquatable<T> ...
may be absent. It all depends on the task solved.
A generic method can be either an instance method or a static method.
⇑
7. An example of sorting instances of type Point<T>. Implementing the IComparable<T> and IEquatable<T> Interfaces in a shared static method
The example declares two classes:
- class Point – describes a point on the coordinate plane. The class implements the IComparable<Point> and IEquatable<Point> interfaces so that objects of this class can be compared;
- the SortMethods class, which implements the generic static SortInsertion<T>() method. The method implements the insertion sort algorithm for sorting instances of the Point class.
using System; namespace ConsoleApp19 { // A class describing a point on a coordinate plane. // To enable comparison, the class implements // the IComparable<T> and IEquatable<T> interfaces. class Point : IComparable<Point>, IEquatable<Point> { // Internal fields - coordinates of a point private double x, y; // Constructor public Point(double x, double y) { this.x = x; this.y = y; } // Property LengthOrigin() - returns the distance from the current point to the origin public double LengthOrigin { get { return Math.Sqrt(x * x + y * y); } } // Implementation of the CompareTo() method of the IComparable<Point> interface. // The distance from the point to the origin is compared: // - if the distance for the current instance is greater than the distance pt, 1 is returned, // - if the distance for the current instance is less than the distance pt, -1 is returned; // - if the distances are equal, 0 is returned. public int CompareTo(Point pt) { if (LengthOrigin > pt.LengthOrigin) return 1; if (LengthOrigin < pt.LengthOrigin) return -1; return 0; } // Implementing the Equals() Method of the IComparable<T> Interface public bool Equals(Point pt) { return LengthOrigin == pt.LengthOrigin; } // Overriding the Equals () method of the Object class, // needed for compatibility with the new Equals(Point) method public override bool Equals(object obj) { if (obj is Point) return Equals((Point)obj); return false; } // Override the GetHashCode () method, // required for compatibility with the new Equals(Point) method public override int GetHashCode() { return base.GetHashCode(); } // Override ToString() method of Object class public override string ToString() { return base.ToString(); } // A method that displays information about the current point. // The method is used for testing. public void ShowPoint() { // Display the coordinates of the current point and the distance to the origin Console.WriteLine("({0}, {1}) => {2:f2}", x, y, LengthOrigin); } } // A class containing a generic static insertion sort method // for objects of generic type T class SortMethods { // The static method of insertion sorting. // Parameters: // - array - the array to be sorted in ascending order. // The method returns a sorted array T[]. public static T[] SortInsertion<T>(T[] array) where T : IComparable<T> { for (int i = 0; i < array.Length - 1; i++) for (int j = i; j >= 0; j--) if (array[j].CompareTo(array[j + 1]) > 0) { T item = array[j]; array[j] = array[j + 1]; array[j + 1] = item; } return array; } } class Program { static void Main(string[] args) { Point pt1 = new Point(2, 4); // Create an array of points Point[] AP = { new Point(3,5), new Point(2,7), new Point(4,6), new Point(-3,1) }; // Print the list of points before sorting foreach (Point p in AP) p.ShowPoint(); // Sort the array, call the static method SortInsertion() Point[] AP2 = SortMethods.SortInsertion<Point>(AP); // Print the list of points after sorting Console.WriteLine("--------------"); Console.WriteLine("After sorting:"); foreach (Point p in AP2) p.ShowPoint(); Console.WriteLine("Ok!"); Console.ReadKey(); } } }
Program execution result
(3, 5) => 5.83 (2, 7) => 7.28 (4, 6) => 7.21 (-3, 1) => 3.16 -------------- After sorting: (-3, 1) => 3.16 (3, 5) => 5.83 (4, 6) => 7.21 (2, 7) => 7.28 Ok!
⇑
Related topics
- Generics. Basic concepts. Generic classes and structures
- Generic interfaces. Declaration syntax. Implementing generic interfaces in classes. Examples
- Generic methods in classes. Declaration syntax. Methods call
- Generic delegates
- Boxing and unboxing. The need of using generics
- Comparison of instances of generic types. IComparable<T> and IEquatable<T> interfaces
⇑