Reputation: 3271
How do I compare values of generic types?
I have reduced it to a minimal sample:
public class Foo<T> where T : IComparable
{
private T _minimumValue = default(T);
public bool IsInRange(T value)
{
return (value >= _minimumValue); // <-- Error here
}
}
The error is:
Operator '>=' cannot be applied to operands of type 'T' and 'T'.
What on earth!? T
is already constrained to IComparable
, and even when constraining it to value types (where T: struct
), we still can't apply any of the operators <
, >
, <=
, >=
, ==
or !=
. (I know that workarounds involving Equals()
exist for ==
and !=
, but it doesn't help for the relational operators).
So, two questions:
IComparable
? Doesn't it somehow defeat the entire purpose of generic constraints?(I realize there are already a handful of questions related to this seemingly simple problem - but none of the threads gives an exhaustive or workable answer, so here.)
Upvotes: 104
Views: 107185
Reputation: 6367
I was able to use Peter Hedberg's answer to create some overloaded extension methods for generics. Note that the CompareTo
method doesn't work here, as type T
is unknown and doesn't present that interface. That said, I'm interested in seeing any alternatives.
[DebuggerStepThrough]
public void RemoveDuplicates<T>(this List<T> Instance)
{
Instance.RemoveDuplicates((X, Y) => Comparer<T>.Default.Compare(X, Y));
}
[DebuggerStepThrough]
public void RemoveDuplicates<T>(this List<T> Instance, Comparison<T> Comparison)
{
Instance.RemoveDuplicates(new List<Comparison<T>>() { Comparison });
}
[DebuggerStepThrough]
public void RemoveDuplicates<T>(this List<T> Instance, List<Comparison<T>> Comparisons)
{
List<bool> oResults = new List<bool>();
for (int i = 0; i <= Instance.Count - 1; i++)
{
for (int j = Instance.Count - 1; j >= i + 1; j += -1)
{
oResults.Clear();
foreach (Comparison<T> oComparison in Comparisons)
oResults.Add(oComparison(Instance[i], Instance[j]) == 0);
if (oResults.Any(R => R))
Instance.RemoveAt(j);
}
}
}
--EDIT--
I was able to clean this up by constraining T
to IComparable(Of T)
on all methods, as indicated by OP. Note that this constraint requires type T
to implement IComparable(Of <type>)
as well.
[DebuggerStepThrough]
public void RemoveDuplicates<T>(this List<T> Instance) where T : IComparable<T>
{
Instance.RemoveDuplicates((X, Y) => X.CompareTo(Y));
}
Upvotes: -1
Reputation: 24403
IComparable
only forces a function called CompareTo()
. So you cannot apply any of the operators that you have mentioned
Upvotes: 1
Reputation: 3649
If value
can be null the current answer could fail. Use something like this instead:
Comparer<T>.Default.Compare(value, _minimumValue) >= 0
Upvotes: 43
Reputation: 81159
As others have stated, one needs to explicitly use the CompareTo method. The reason that one cannot use interfaces with operators is that it is possible for a class to implement an arbitrary number of interfaces, with no clear ranking among them. Suppose one tried to compute the expression "a = foo + 5;" when foo implemented six interfaces all of which define an operator "+" with an integer second argument; which interface should be used for the operator?
The fact that classes can derive multiple interfaces makes interfaces very powerful. Unfortunately, it often forces one to be more explicit about what one actually wants to do.
Upvotes: 2
Reputation: 59111
Problem with operator overloading
Unfortunately, interfaces cannot contain overloaded operators. Try typing this in your compiler:
public interface IInequalityComaparable<T>
{
bool operator >(T lhs, T rhs);
bool operator >=(T lhs, T rhs);
bool operator <(T lhs, T rhs);
bool operator <=(T lhs, T rhs);
}
I don't know why they didn't allow this, but I'm guessing it complicated the language definition, and would be hard for users to implement correctly.
Either that, or the designers didn't like the potential for abuse. For example, imagine doing a >=
compare on a class MagicMrMeow
. Or even on a class Matrix<T>
. What does the result mean about the two values?; Especially when there could be an ambiguity?
The official work-around
Since the above interface isn't legal, we have the IComparable<T>
interface to work around the problem. It implements no operators, and exposes only one method, int CompareTo(T other);
See http://msdn.microsoft.com/en-us/library/4d7sx9hd.aspx
The int
result is actually a tri-bit, or a tri-nary (similar to a Boolean
, but with three states). This table explains the meaning of the results:
Value Meaning
Less than zero This object is less than
the object specified by the CompareTo method.
Zero This object is equal to the method parameter.
Greater than zero This object is greater than the method parameter.
Using the work-around
In order to do the equivalent of value >= _minimumValue
, you must instead write:
value.CompareTo(_minimumValue) >= 0
Upvotes: 35
Reputation: 15076
IComparable
doesn't overload the >=
operator. You should use
value.CompareTo(_minimumValue) >= 0
Upvotes: 114
Reputation: 26632
Instead of value >= _minimValue
use Comparer
class:
public bool IsInRange(T value ) {
var result = Comparer<T>.Default.Compare(value, _minimumValue);
if ( result >= 0 ) { return true; }
else { return false; }
}
Upvotes: 4
Reputation: 27864
public bool IsInRange(T value)
{
return (value.CompareTo(_minimumValue) >= 0);
}
When working with IComparable generics, all less than/greater than operators need to be converted to calls to CompareTo. Whatever operator you would use, keep the values being compared in the same order, and compare against zero. ( x <op> y
becomes x.CompareTo(y) <op> 0
, where <op>
is >
, >=
, etc.)
Also, I'd recommend that the generic constraint you use be where T : IComparable<T>
. IComparable by itself means that the object can be compared against anything, comparing an object against others of the same type is probably more appropriate.
Upvotes: 7