【问题标题】:How to compare values of generic types?如何比较泛型类型的值?
【发布时间】:2020-09-28 17:46:19
【问题描述】:

如何比较泛型类型的值?

我已将其缩减为最小样本:

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),我们仍然无法应用任何运算符&lt;&gt;&lt;=&gt;===!=。 (我知道==!= 存在涉及Equals() 的解决方法,但它对关系运算符没有帮助)。

那么,两个问题:

  1. 为什么我们会观察到这种奇怪的行为?是什么让我们无法比较已知IComparable 的泛型类型的值?它不是以某种方式破坏了通用约束的全部目的吗?
  2. 我该如何解决这个问题,或者至少可以解决这个问题?

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

【问题讨论】:

  • 作为记录,这在 C# 10 中现在是可能的,因为接口现在可以包含静态方法(从而允许运算符重载)

标签: c# .net generics icomparable


【解决方案1】:

IComparable 不会重载 &gt;= 运算符。你应该使用

value.CompareTo(_minimumValue) >= 0

【讨论】:

  • 太好了,这很有效(当然也解释了)- 非常感谢!但这有点令人不满意,并留下了一个问题:为什么 IComparable 不重载比较运算符?这是一个有意识和深思熟虑的设计决定,有充分的理由 - 还是在框架设计中被忽视的东西?毕竟,'x.CompareTo(y) >= 0' 的可读性不如 'x >= y',不是吗?
  • 我绝对明白你的意思。我想问题是操作符是静态的,这意味着它们不能适应界面。我不会判断这是否是一个好的选择,但我倾向于认为当类型不是原始类型时,具有专有名称的方法比运算符更容易阅读;不过,这是一个品味问题。
  • @gstercken:IComparable 重载比较运算符的一个问题是,在某些情况下,X.Equals(Y) 应该返回 false,但 X.CompareTo(Y) 应该返回零(暗示两个元素都不大于另一个元素) ) [例如ExpenseItem 相对于 TotalCost 可能具有自然排序,并且成本相同的费用项目可能没有自然排序,但这并不意味着每个花费 3,141.59 美元的费用项目都应该被视为等同于每个其他价格相同的物品。
  • @gstercken:从根本上说,== 在逻辑上可能意味着很多事情。在某些情况下,X==Y 可能为真而X.Equals(Y) 为假,而在其他情况下X==Y 可能为假而X.Equals(Y) 为真。即使可以为接口重载运算符,重载&lt;&lt;=&gt;&gt;=IComparable&lt;T&gt; 可能会给人一种印象,即==!= 也会在这样的条件下重载.如果 C# 像 vb 一样,不允许在未重载的类类型上使用 ==,这可能不会太糟糕,但是...
  • ...唉,C# 决定使用标记 == 来表示可重载的相等运算符和不可重载的引用相等测试。
【解决方案2】:

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

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

【讨论】:

  • 感谢您的提示。对于我正在研究的一些扩展方法,我需要这个。见下文。
  • 好的,知道了。我没有将T 限制为IComparable。但是你的小费让我克服了困难。
【解决方案3】:

运算符重载问题

不幸的是,接口不能包含重载的运算符。尝试在你的编译器中输入这个:

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 上进行&gt;= 比较。甚至是class Matrix&lt;T&gt;。结果对这两个值意味着什么?尤其是当可能存在歧义时?

官方解决方法

由于上述接口不合法,我们有IComparable&lt;T&gt; 接口来解决这个问题。它不实现任何运算符,只公开一种方法,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 &gt;= _minimumValue 相同的功能,您必须改为:

value.CompareTo(_minimumValue) >= 0

【讨论】:

  • 啊,对 - 有道理。我忘记了 C# 中的接口不能重载运算符。
【解决方案4】:
public bool IsInRange(T value) 
{
    return (value.CompareTo(_minimumValue) >= 0);
}

使用 IComparable 泛型时,所有小于/大于运算符都需要转换为对 CompareTo 的调用。无论您使用什么运算符,都要保持以相同顺序比较的值,并与零进行比较。 (x &lt;op&gt; y 变为x.CompareTo(y) &lt;op&gt; 0,其中&lt;op&gt;&gt;&gt;= 等)

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

【讨论】:

    【解决方案5】:

    而不是value &gt;= _minimValue 使用Comparer 类:

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

    【讨论】:

    • 当已经存在T 必须实现IComparable 的通用约束时,是否要引入Comparer 的使用?
    • @Fredrik 通用约束倾向于建立。我同意在这里省略它们。
    【解决方案6】:

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

    类可以派生多个接口这一事实使得接口非常强大。不幸的是,它经常迫使人们更明确地了解自己真正想要做什么。

    【讨论】:

    • 我认为 MI 不再是重载运算符的问题,因为它是常规方法。它们只是方法的有趣语法。因此,您可以通过使用相同的规则来解决该问题,这些规则将用于将它们作为接口上的常规方法来解决。 C# 的设计目标之一是让 C++ 程序员对它有点熟悉,并且重载的运算符被滥用到地狱并回到该语言中。我猜设计师更喜欢命名方法,这会迫使你为这些方法的意图提供某种文档。
    【解决方案7】:

    IComparable 只强制一个名为CompareTo() 的函数。所以你不能应用你提到的任何运算符

    【讨论】:

      【解决方案8】:

      我能够使用 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
      

      --编辑--

      如 OP 所示,我可以通过在所有方法上将 T 限制为 IComparable(Of T) 来清理这个问题。请注意,此约束还需要类型 T 来实现 IComparable(Of &lt;type&gt;)

      <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
      

      【讨论】:

        猜你喜欢
        • 2010-12-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-27
        相关资源
        最近更新 更多