¿Cómo comparo los valores de los tipos generics?
Lo he reducido a una muestra mínima:
public class Foo where T : IComparable { private T _minimumValue = default(T); public bool IsInRange(T value) { return (value >= _minimumValue); // <-- Error here } }
El error es:
El operador ‘> =’ no se puede aplicar a operandos del tipo ‘T’ y ‘T’.
¿¡Qué en la tierra!? T
ya está limitado a IComparable
, e incluso cuando lo restringe a tipos de valores ( where T: struct
), todavía no podemos aplicar ninguno de los operadores <
, >
, <=
, >=
, ==
o !=
. (Sé que existen soluciones provisionales que implican Equals()
para ==
y !=
, Pero no ayuda para los operadores relacionales).
Entonces, dos preguntas:
IComparable
? ¿De alguna manera no vence el propósito completo de las restricciones genéricas? (Me doy cuenta de que ya hay un puñado de preguntas relacionadas con este problema aparentemente simple, pero ninguno de los hilos da una respuesta exhaustiva o viable, así que aquí).
IComparable
no sobrecarga el operador >=
. Deberías usar
value.CompareTo(_minimumValue) >= 0
Problema con la sobrecarga del operador
Desafortunadamente, las interfaces no pueden contener operadores sobrecargados. Prueba a escribir esto en tu comstackdor:
public interface IInequalityComaparable { bool operator >(T lhs, T rhs); bool operator >=(T lhs, T rhs); bool operator <(T lhs, T rhs); bool operator <=(T lhs, T rhs); }
No sé por qué no lo permitieron, pero supongo que complicó la definición del lenguaje y sería difícil para los usuarios implementarlo correctamente.
O eso, o a los diseñadores no les gustó el potencial de abuso. Por ejemplo, imagine hacer una comparación >=
en una class MagicMrMeow
. O incluso en una class Matrix
. ¿Qué significa el resultado sobre los dos valores ?; ¿Especialmente cuando podría haber una ambigüedad?
La solución oficial
Como la interfaz anterior no es legal, tenemos la interfaz IComparable
para evitar el problema. No implementa operadores, y expone solo un método, int CompareTo(T other);
Ver http://msdn.microsoft.com/en-us/library/4d7sx9hd.aspx
El resultado int
es en realidad un tri-bit, o un tri-nary (similar a un Boolean
, pero con tres estados). Esta tabla explica el significado de los resultados:
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.
Usando el trabajo-alrededor
Para hacer el equivalente de value >= _minimumValue
, en su lugar debe escribir:
value.CompareTo(_minimumValue) >= 0
Si el value
puede ser nulo, la respuesta actual podría fallar. Use algo como esto en su lugar:
Comparer.Default.Compare(value, _minimumValue) >= 0
public bool IsInRange(T value) { return (value.CompareTo(_minimumValue) >= 0); }
Al trabajar con los generics de IComparable, todos los operadores menores a / mayores deben convertirse a llamadas a CompareTo. Cualquiera que sea el operador que use, mantenga los valores comparados en el mismo orden y compare con cero. ( x
convierte en x.CompareTo(y)
, donde
es >
, >=
, etc.)
Además, recomendaría que la restricción genérica que use esté where T : IComparable
. Ineparable por sí mismo significa que el objeto puede compararse con cualquier cosa, y es más apropiado comparar un objeto con otros del mismo tipo.
En lugar de value >= _minimValue
usa Comparer
clase Comparer
:
public bool IsInRange(T value ) { var result = Comparer.Default.Compare(value, _minimumValue); if ( result >= 0 ) { return true; } else { return false; } }
Como otros han declarado, uno necesita usar explícitamente el método CompareTo. La razón por la que no se pueden usar las interfaces con los operadores es porque es posible que una clase implemente un número arbitrario de interfaces, sin una clasificación clara entre ellas. Supongamos que uno intenta calcular la expresión “a = foo + 5;” cuando foo implementó seis interfaces, todas las cuales definen un operador “+” con un segundo argumento entero; ¿Qué interfaz debe usarse para el operador?
El hecho de que las clases puedan derivar múltiples interfaces hace que las interfaces sean muy poderosas. Desafortunadamente, a menudo lo fuerza a uno a ser más explícito sobre lo que realmente quiere hacer.
IComparable
solo fuerza una función llamada CompareTo()
. Entonces no puede aplicar ninguno de los operadores que ha mencionado
Pude usar la respuesta de Peter Hedburg para crear algunos métodos de extensión sobrecargados para generics. Tenga en cuenta que el método CompareTo
no funciona aquí, ya que el tipo T
es desconocido y no presenta esa interfaz. Dicho esto, estoy interesado en ver alguna alternativa.
Me gustaría haber publicado en C #, pero el convertidor de Telerik falla en este código. No estoy lo suficientemente familiarizado con C # para convertirlo de forma confiable de forma manual. Si alguien quisiera hacer los honores, estaría encantado de ver esto editado en consecuencia.
Public Sub RemoveDuplicates(Of T)(Instance As List(Of T)) Instance.RemoveDuplicates(Function(X, Y) Comparer(Of T).Default.Compare(X, Y)) End Sub 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 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
–EDITAR–
Pude limpiar esto al limitar T
a IComparable(Of T)
en todos los métodos, como lo indica OP. Tenga en cuenta que esta restricción requiere el tipo T
para implementar IComparable(Of
también.
Public Sub RemoveDuplicates(Of T As IComparable(Of T))(Instance As List(Of T)) Instance.RemoveDuplicates(Function(X, Y) X.CompareTo(Y)) End Sub