¿Cómo comparar valores de tipos generics?

¿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:

  1. ¿Por qué observamos este extraño comportamiento? ¿Qué nos impide comparar los valores de los tipos generics que se sabe que son IComparable ? ¿De alguna manera no vence el propósito completo de las restricciones genéricas?
  2. ¿Cómo lo soluciono, o al menos soluciono el problema?

(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 y convierte en x.CompareTo(y) 0 , 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