Как сравнить значения универсальных типов? - PullRequest
69 голосов
/ 26 июня 2011

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

Я уменьшил его до минимальной выборки:

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

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

Ответы [ 8 ]

84 голосов
/ 26 июня 2011

IComparable не перегружает оператор >=.Вы должны использовать

value.CompareTo(_minimumValue) >= 0
30 голосов
/ 26 июня 2011

Проблема с перегрузкой операторов

К сожалению, интерфейсы не могут содержать перегруженные операторы.Попробуйте набрать это в вашем компиляторе:

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
20 голосов
/ 07 ноября 2014

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

Comparer<T>.Default.Compare(value, _minimumValue) >= 0
6 голосов
/ 26 июня 2011
public bool IsInRange(T value) 
{
    return (value.CompareTo(_minimumValue) >= 0);
}

При работе с обобщениями IComparable все операторы, меньшие / большие, чем необходимо преобразовывать в вызовы CompareTo.Какой бы оператор вы ни использовали, сохраняйте сравниваемые значения в одном и том же порядке и сравнивайте их с нулем.(x <op> y становится x.CompareTo(y) <op> 0, где <op> равно >, >= и т.IComparable сам по себе означает, что объект можно сравнивать с чем угодно, вероятно, более подходящим является сравнение объекта с другими объектами того же типа.

3 голосов
/ 26 июня 2011

Вместо value >= _minimValue используйте Comparer класс:

public bool IsInRange(T value ) {
    var result = Comparer<T>.Default.Compare(value, _minimumValue);
    if ( result >= 0 ) { return true; }
    else { return false; }
}
2 голосов
/ 26 июня 2011

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

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

1 голос
/ 26 июня 2011

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

0 голосов
/ 14 декабря 2017

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

- EDIT -

Мне удалось это исправить, ограничив T до IComparable(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
...