如何比较泛型类型的值?


79

如何比较泛型类型的值?

我将其简化为最小样本:

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. 我该如何解决这个问题,或者至少要解决它?

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

Answers:


96

IComparable不会使>=运算符超载。你应该用

value.CompareTo(_minimumValue) >= 0

7
太好了,这行得通(当然可以解释它)-非常感谢!但这有点令人不满意,并提出了一个问题:为什么IComparable不使比较运算符超载?这是一个有意识的,有意的设计决定,有充分的理由吗?还是在框架设计中被忽略了?毕竟,“ x.CompareTo(y)> = 0”比“ x> = y”可读性差,不是吗?
gstercken

我一定会明白你的意思的。我想问题是操作符是静态的,这意味着它们不能适合接口。我不会判断这是否是一个好选择,但是我倾向于认为,当类型不是基本类型时,具有专有名称的方法比运算符更易于阅读;不过这是一个品味问题。
faester

5
@gstercken:IComparable比较运算符重载的一个问题是,在某些情况下X.Equals(Y)应返回false,但X.CompareTo(Y)应返回零(建议两个元素中的任何一个都不比另一个大)(例如ExpenseItem,相对于TotalCost,可能具有自然顺序)对于费用相同的费用项目没有自然排序,但这并不意味着每个花费3,141.59美元的费用项目都应被视为等同于其他所有费用相同的项目。
2013年

1
@gstercken:从根本上讲,==从逻辑上讲有很多事情。在某些情况下,有可能为X==Ytrue而X.Equals(Y)false,而在其他情况下则有X==Y可能为false而X.Equals(Y)true。即使运营商可以重载接口,超载<<=>>=在以下方面IComparable<T>可能会给一个印象,==并且!=也将以这样的术语被重载。如果C#像vb一样,禁止使用==未进行重载的类类型,这可能还不错,但是……
supercat

2
... alas C#决定使用令牌==来表示可重载的相等运算符和不可重载的参考相等测试。
2013年

35

操作员重载的问题

不幸的是,接口不能包含重载的运算符。尝试在编译器中输入以下内容:

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

我不知道为什么他们不允许这样做,但是我猜测它使语言定义变得复杂,并且用户很难正确实现。

要么,要么设计师不喜欢滥用的可能性。例如,假设对>=进行比较class MagicMrMeow。甚至上一个class Matrix<T>。结果对这两个值意味着什么?特别是在可能出现歧义的时候?

官方解决方法

由于上述界面不合法,因此我们有IComparable<T>解决该问题的界面。它不实现任何运算符,并且仅公开一种方法,int CompareTo(T other);

请参阅http://msdn.microsoft.com/en-us/library/4d7sx9hd.aspx

int结果实际上是一个三比特,或三进制(类似于Boolean,但具有三个状态)。下表说明了结果的含义:

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.

使用解决方法

为了执行value >= _minimumValue,您必须改为编写:

value.CompareTo(_minimumValue) >= 0

2
嗯,对-有道理。我忘记了C#中的接口不能重载运算符。
gstercken

29

如果value可以为null,则当前答案可能会失败。使用类似这样的东西:

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

1
谢谢你的提示。我需要一些正在使用的扩展方法。见下文。
InteXX '17

1
好的,我知道了。我没有制约TIComparable。但是,您的提示仍然使我不为所动。
InteXX '17

6
public bool IsInRange(T value) 
{
    return (value.CompareTo(_minimumValue) >= 0);
}

使用IComparable泛型时,所有小于/大于运算符的子集都需要转换为对CompareTo的调用。无论您使用哪种运算符,都将比较值的顺序保持不变,然后与零进行比较。(x <op> yx.CompareTo(y) <op> 0,其中<op>>>=等)

另外,我建议您使用的通用约束为where T : IComparable<T>。IComparable本身意味着可以将对象与任何对象进行比较,将对象与相同类型的其他对象进行比较可能更合适。


3

代替value >= _minimValue使用Comparer类:

public bool IsInRange(T value ) {
    var result = Comparer<T>.Default.Compare(value, _minimumValue);
    if ( result >= 0 ) { return true; }
    else { return false; }
}

Comparer当已经存在T必须实现的通用约束时,会介绍使用aIComparable吗?
FredrikMörk2011年

@Fredrik通用约束趋于建立。我同意在这里省略它们。
马克·格雷韦尔

2

正如其他人所述,需要显式使用CompareTo方法。之所以不能与运算符一起使用接口,是因为一类可以实现任意数量的接口,而在它们之间却没有明确的排名。假设有人试图计算表达式“ a = foo + 5;”。当foo实现了六个接口时,所有这些接口都定义了带有第二个整数的运算符“ +”;应该为操作员使用哪个接口?

类可以派生多个接口的事实使接口非常强大。不幸的是,它常常迫使人们对一个人实际想要做的事情更加明确。


1
我认为MI与重载运算符不再像常规方法那样成为问题。它们只是方法的有趣语法。因此,您可以通过使用相同的规则(作为接口上的常规方法来解决它们)来解决该问题。C#的设计目标之一是使C ++程序员对它有些熟悉,并且重载运算符被滥用并以这种语言被滥用。我猜想设计师会首选命名方法,这些方法会强迫您提供一些有关这些方法目的的文档。
Merlyn Morgan-Graham '04年


0

我能够使用Peter Hedburg的答案为泛型创建一些重载的扩展方法。请注意,该CompareTo方法在这里不起作用,因为类型T未知,并且不显示该接口。就是说,我有兴趣看到其他选择。

我想用C#发布,但是Telerik的转换器在此代码上失败。我对C#不够熟悉,无法可靠地对其进行手动转换。如果有人想获得荣誉,我很高兴看到对此进行了相应的编辑。

<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

- 编辑 -

我能够通过限制清理它TIComparable(Of T)所有的方法,由OP所示。请注意,此约束也需要类型T来实现IComparable(Of <type>)

<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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.