c# - numeros - ¿Cómo comparar valores de tipos genéricos?
comparar numeros c# (8)
¿Cómo comparo los valores de los tipos genéricos?
Lo he reducido a una muestra mínima:
public class Foo<T> 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:
- ¿Por qué observamos este extraño comportamiento? ¿Qué nos impide comparar los valores de los tipos genéricos que se sabe que son
IComparable
? ¿De alguna manera no vence el propósito completo de las restricciones genéricas? - ¿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í).
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.
En lugar de value >= _minimValue
usa Comparer
clase Comparer
:
public bool IsInRange(T value ) {
var result = Comparer<T>.Default.Compare(value, _minimumValue);
if ( result >= 0 ) { return true; }
else { return false; }
}
Pude usar la respuesta de Peter Hedburg para crear algunos métodos de extensión sobrecargados para genéricos. 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.
<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
--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 <type>)
también.
<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
Si el value
puede ser nulo, la respuesta actual podría fallar. Use algo como esto en su lugar:
Comparer<T>.Default.Compare(value, _minimumValue) >= 0
IComparable
no sobrecarga el operador >=
. Deberías usar
value.CompareTo(_minimumValue) >= 0
IComparable
solo fuerza una función llamada CompareTo()
. Entonces no puede aplicar ninguno de los operadores que ha mencionado
Problema con la sobrecarga del operador
Desafortunadamente, las interfaces no pueden contener operadores sobrecargados. Prueba a escribir esto en tu compilador:
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);
}
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<T>
. ¿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<T>
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
public bool IsInRange(T value)
{
return (value.CompareTo(_minimumValue) >= 0);
}
Al trabajar con los genéricos 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 <op> y
convierte en x.CompareTo(y) <op> 0
, donde <op>
es >
, >=
, etc.)
Además, recomendaría que la restricción genérica que use esté where T : IComparable<T>
. 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.