Як порівняти значення загальних типів?


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. Як я можу це вирішити, або, принаймні, обійти це?

(Я усвідомлюю, що вже є кілька питань, пов’язаних із цією, здавалося б, простою проблемою - але жодна з тем не дає вичерпної або дієвої відповіді, тому тут.)

Відповіді:


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)повернути нуль (припускаючи, що жоден елемент не більший за інший) [наприклад, a 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Результатом є фактично три-біт, або три-ні краплі ( по аналогії з 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 generics усі оператори, менші / більші за оператори, повинні бути перетворені на виклики до CompareTo. Яким би оператором ви не користувались, порівнюйте значення у тому самому порядку та порівнюйте проти нуля. ( x <op> yСтає x.CompareTo(y) <op> 0, де <op>знаходиться >, >=і т.д.)

Крім того, я б рекомендував використовувати загальне обмеження, яке ви використовуєте where T : IComparable<T>. Порівняння саме по собі означає, що об'єкт можна порівняти з чим завгодно, порівняння об'єкта з іншими однотипними, ймовірно, є більш доцільним.


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

Wny запровадити використання a, Comparerколи вже існує загальне обмеження, яке Tнеобхідно реалізувати IComparable?
Fredrik Mörk

Загальні обмеження @Fredrik, як правило, наростають. Я згоден з тим, щоб їх тут не вказати.
Марк Гравелл

2

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

Той факт, що класи можуть отримувати кілька інтерфейсів, робить інтерфейси дуже потужними. На жаль, часто це змушує бути більш чіткими щодо того, що насправді хочеться робити.


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

1

IComparableлише змушує функцію, що називається CompareTo(). Отже, ви не можете застосувати жодного із зазначених вами операторів


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

- EDIT--

Я був в змозі очистити це шляхом обмеження 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.