Как сравнить значения универсальных типов?
Я сократил его до минимального образца:
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()
для ==
и существуют обходные пути !=
, но это не помогает для операторов отношения).
Итак, два вопроса:
- Почему мы наблюдаем это странное поведение? Что удерживает нас от сравнения значений общих типов , которые известны как
IComparable
? Разве это каким-то образом не уничтожает всю цель общих ограничений? - Как мне решить эту проблему или, по крайней мере, обойти это?
(Я понимаю, что уже есть несколько вопросов, связанных с этой, казалось бы, простой проблемой, но ни один из потоков не дает исчерпывающий или работоспособный ответ, поэтому вот.)
c#
.net
generics
icomparable
Gstercken
источник
источник
IComparable
перегрузкой операторов сравнения заключается в том, что есть ситуации, когдаX.Equals(Y)
должно возвращаться false, ноX.CompareTo(Y)
должно возвращаться ноль (предполагая, что ни один элемент не больше другого) [например, элементExpenseItem
может иметь естественный порядок относительноTotalCost
, и может нет естественного упорядочивания для статей расходов, стоимость которых одинакова, но это не означает, что каждая статья расходов, которая стоит 3141,59 доллара, должна считаться эквивалентной любой другой статье, которая стоит столько же.==
могут иметь логическое значение. В некоторых контекстах можетX==Y
быть истинным, покаX.Equals(Y)
является ложным, а в других контекстахX==Y
может быть ложным, покаX.Equals(Y)
истинно. Даже если операторы могут быть перегружены для интерфейсов, перегрузок<
,<=
,>
и>=
с точки зренияIComparable<T>
может дать такое впечатление , что==
и!=
также будет перегружены в таких условиях. Если бы C #, как и vb, запретил использование==
типов классов, для которых он не был перегружен, это могло бы быть не так уж плохо, но ...==
для представления как перегружаемого оператора равенства, так и неперегружаемого теста на равенство ссылок.Проблема с перегрузкой оператора
К сожалению, интерфейсы не могут содержать перегруженных операторов. Попробуйте ввести это в свой компилятор:
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
результате получается трехбитовый или трехзначный (аналогично aBoolean
, но с тремя состояниями). Эта таблица объясняет значение результатов: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
источник
Если
value
может быть нулевым, текущий ответ может быть неудачным. Вместо этого используйте что-то вроде этого:Comparer<T>.Default.Compare(value, _minimumValue) >= 0
источник
T
кIComparable
. Но твоя подсказка тем не менее меня сбила с толку.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 сам по себе означает, что объект можно сравнивать с чем угодно, сравнение объекта с другими объектами того же типа, вероятно, более уместно.источник
Вместо
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
необходимо реализоватьIComparable
?Как заявляли другие, необходимо явно использовать метод CompareTo. Причина, по которой нельзя использовать интерфейсы с операторами, заключается в том, что класс может реализовать произвольное количество интерфейсов без четкого ранжирования между ними. Предположим, что кто-то пытался вычислить выражение «a = foo + 5;» когда foo реализовал шесть интерфейсов, каждый из которых определяет оператор "+" с целым вторым аргументом; какой интерфейс использовать для оператора?
Тот факт, что классы могут выводить несколько интерфейсов, делает интерфейсы очень мощными. К сожалению, это часто заставляет более четко указывать, что на самом деле нужно делать.
источник
IComparable
только принудительно вызывает функциюCompareTo()
. Таким образом, вы не можете применять ни один из упомянутых вами операторов.источник
Я смог использовать ответ Питера Хедбурга для создания некоторых перегруженных методов расширения для дженериков. Обратите внимание, что
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
источник