如何比较泛型类型的值?

如何比较泛型类型的值?

我已经把它缩小到最小样本了:

public class Foo<T> where T : IComparable
{
private T _minimumValue = default(T);


public bool IsInRange(T value)
{
return (value >= _minimumValue); // <-- Error here
}
}

错误是:

运算符“ > =”不能应用于类型为“ T”和“ T”的操作数。

怎么回事?T已经被限制为 IComparable,即使将其限制为值类型(where T: struct) ,我们仍然不能应用任何操作符 <><=>===!=。(我知道对于 ==!=存在包含 Equals()的变通方法,但是对于关系操作符没有帮助)。

有两个问题:

  1. 为什么我们观察到这种奇怪的行为?是什么阻止我们比较泛型类型(已知IComparable)的值?它难道不是在某种程度上违背了通用约束的整个目的吗?
  2. 我该如何解决这个问题,或者至少绕过它?

(我意识到已经有一些问题与这个看似简单的问题有关——但没有一个线程给出详尽或可行的答案,所以在这里。)

91914 次浏览
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.

IComparable doesn't overload the >= operator. You should use

value.CompareTo(_minimumValue) >= 0

IComparable only forces a function called CompareTo(). So you cannot apply any of the operators that you have mentioned

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; }
}

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

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.

If value can be null the current answer could fail. Use something like this instead:

Comparer<T>.Default.Compare(value, _minimumValue) >= 0

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.

I would like to have posted in C#, but Telerik's converter fails on this code. I'm not familiar enough with C# to reliably convert it manually. If someone would like to do the honors, I'd be pleased to see this edited accordingly.

<Extension>
<DebuggerStepThrough>
Public Sub RemoveDuplicates(Of T)(Instance As List(Of T))
Instance.RemoveDuplicates(Function(X, Y) Comparer(Of T).Default.Compare(X, Y))
End Sub






<Extension>
<DebuggerStepThrough>
Public Sub RemoveDuplicates(Of T)(Instance As List(Of T), Comparison As Comparison(Of T))
Instance.RemoveDuplicates(New List(Of Comparison(Of T)) From {Comparison})
End Sub






<Extension>
<DebuggerStepThrough>
Public Sub RemoveDuplicates(Of T)(Instance As List(Of T), Comparisons As List(Of Comparison(Of T)))
Dim oResults As New List(Of Boolean)


For i As Integer = 0 To Instance.Count - 1
For j As Integer = Instance.Count - 1 To i + 1 Step -1
oResults.Clear()


For Each oComparison As Comparison(Of T) In Comparisons
oResults.Add(oComparison(Instance(i), Instance(j)) = 0)
Next oComparison


If oResults.Any(Function(R) R) Then
Instance.RemoveAt(j)
End If
Next j
Next i
End Sub

--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.

<Extension>
<DebuggerStepThrough>
Public Sub RemoveDuplicates(Of T As IComparable(Of T))(Instance As List(Of T))
Instance.RemoveDuplicates(Function(X, Y) X.CompareTo(Y))
End Sub