Как сравнить значения универсальных типов?

81

Как сравнить значения универсальных типов?

Я сократил его до минимального образца:

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. Как мне решить эту проблему или, по крайней мере, обойти это?

(Я понимаю, что уже есть несколько вопросов, связанных с этой, казалось бы, простой проблемой, но ни один из потоков не дает исчерпывающий или работоспособный ответ, поэтому вот.)

Gstercken
источник

Ответы:

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, и может нет естественного упорядочивания для статей расходов, стоимость которых одинакова, но это не означает, что каждая статья расходов, которая стоит 3141,59 доллара, должна считаться эквивалентной любой другой статье, которая стоит столько же.
supercat
1
@gstercken: По сути, есть несколько вещей, которые ==могут иметь логическое значение. В некоторых контекстах может X==Yбыть истинным, пока X.Equals(Y)является ложным, а в других контекстах X==Yможет быть ложным, пока X.Equals(Y)истинно. Даже если операторы могут быть перегружены для интерфейсов, перегрузок <, <=, >и >=с точки зрения IComparable<T>может дать такое впечатление , что ==и !=также будет перегружены в таких условиях. Если бы C #, как и vb, запретил использование ==типов классов, для которых он не был перегружен, это могло бы быть не так уж плохо, но ...
supercat
2
... увы, C # решил использовать токен ==для представления как перегружаемого оператора равенства, так и неперегружаемого теста на равенство ссылок.
supercat
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результате получается трехбитовый или трехзначный (аналогично a 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
32

Если valueможет быть нулевым, текущий ответ может быть неудачным. Вместо этого используйте что-то вроде этого:

Comparer<T>.Default.Compare(value, _minimumValue) >= 0
Питер Хедберг
источник
1
Спасибо за совет. Мне это нужно для некоторых методов расширения, над которыми я работал. Смотри ниже.
InteXX
1
Хорошо понял. Я не сдерживая Tк IComparable. Но твоя подсказка тем не менее меня сбила с толку.
InteXX
6
public bool IsInRange(T value) 
{
    return (value.CompareTo(_minimumValue) >= 0);
}

При работе с универсальными шаблонами IComparable все операторы «меньше / больше» необходимо преобразовать в вызовы метода CompareTo. Какой бы оператор вы ни использовали, сохраняйте сравниваемые значения в том же порядке и сравнивайте с нулем. ( x <op> yстановится x.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; }
}
TcKs
источник
Можете ли вы ввести использование a, Comparerкогда уже существует общее ограничение, которое Tнеобходимо реализовать IComparable?
Fredrik Mörk
Общие ограничения @Fredrik имеют тенденцию нарастать. Я согласен опустить их здесь.
Marc Gravell
2

Как заявляли другие, необходимо явно использовать метод CompareTo. Причина, по которой нельзя использовать интерфейсы с операторами, заключается в том, что класс может реализовать произвольное количество интерфейсов без четкого ранжирования между ними. Предположим, что кто-то пытался вычислить выражение «a = foo + 5;» когда foo реализовал шесть интерфейсов, каждый из которых определяет оператор "+" с целым вторым аргументом; какой интерфейс использовать для оператора?

Тот факт, что классы могут выводить несколько интерфейсов, делает интерфейсы очень мощными. К сожалению, это часто заставляет более четко указывать, что на самом деле нужно делать.

суперкар
источник
1
Я не думаю, что MI больше не проблема с перегруженными операторами, как с обычными методами. Это просто забавный синтаксис для методов. Таким образом, вы можете решить эту проблему, используя те же правила, что и обычные методы для интерфейсов. Одна из целей разработки C # заключалась в том, чтобы он был в некоторой степени знаком программистам на C ++, и перегруженные операторы в этом языке использовались к черту и обратно. Я предполагаю, что дизайнеры предпочли именованные методы, которые вынуждают вас предоставить какую-то документацию для целей этих методов.
Мерлин Морган-Грэм
1

IComparableтолько принудительно вызывает функцию CompareTo(). Таким образом, вы не можете применять ни один из упомянутых вами операторов.

Parapura Rajkumar
источник
0

Я смог использовать ответ Питера Хедбурга для создания некоторых перегруженных методов расширения для дженериков. Обратите внимание, что 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

--РЕДАКТИРОВАТЬ--

Я был в состоянии очистить это путем ограничения Tдля IComparable(Of T)всех методов, как указано ОП. Обратите внимание, что это ограничение также требует 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
InteXX
источник