¿Cómo comparo valores de 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 de tipo 'T' y 'T'.
¿¡Que demonios!? T
Ya se ve limitado a IComparable
, e incluso cuando constreñir a los tipos de valor ( where T: struct
), todavía no podemos aplicar cualquiera de los operadores <
, >
, <=
, >=
, ==
o !=
. (Sé que Equals()
existen soluciones alternativas para ==
y !=
, pero no ayuda para los operadores relacionales).
Entonces, dos preguntas:
- ¿Por qué observamos este comportamiento extraño? ¿Qué nos impide comparar los valores de los tipos genéricos que se sabe que son
IComparable
? ¿No frustra de alguna manera todo el propósito de las restricciones genéricas? - ¿Cómo resuelvo esto, o al menos lo soluciono?
(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í).
c#
.net
generics
icomparable
Gstercken
fuente
fuente
IComparable
sobrecarga de los operadores de comparación es que hay situaciones en las queX.Equals(Y)
debería devolver falso, peroX.CompareTo(Y)
debería devolver cero (lo que sugiere que ningún elemento es más grande que el otro) [por ejemplo, unExpenseItem
puede tener un orden natural con respecto aTotalCost
, y puede haber ningún pedido natural para artículos de gastos cuyo costo es el mismo, pero eso no significa que todos los artículos de gastos que cuestan $ 3,141.59 deben considerarse equivalentes a todos los demás artículos que cuestan lo mismo.==
lógicamente podrían significar. En algunos contextos, es posibleX==Y
que sea verdadero, whileX.Equals(Y)
es falso, y mientras que en otros contextosX==Y
podría ser falso, whileX.Equals(Y)
es verdadero. Incluso si los operadores pueden ser sobrecargados de interfaces, sobrecarga<
,<=
,>
y>=
en cuanto aIComparable<T>
que podría dar la impresión de que==
y!=
también sería sobrecargado en tales términos. Si C #, como vb, hubiera rechazado el uso de==
tipos de clases para los que no estaba sobrecargado, eso podría no haber sido tan malo, pero ...==
para representar tanto un operador de igualdad sobrecargable como una prueba de igualdad de referencia no sobrecargable.Problema con la sobrecarga del operador
Desafortunadamente, las interfaces no pueden contener operadores sobrecargados. Intente escribir esto en su 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 permitieron esto, 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, imagina hacer una
>=
comparación en aclass MagicMrMeow
. O incluso en unclass Matrix<T>
. ¿Qué significa el resultado sobre los dos valores ?; ¿Especialmente cuando podría haber una ambigüedad?La solución temporal oficial
Dado que la interfaz anterior no es legal, tenemos la
IComparable<T>
interfaz para solucionar 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
int
resultado es en realidad un tri-bit, o un tri-nary (similar a aBoolean
, 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 la solución alternativa
Para hacer el equivalente de
value >= _minimumValue
, debes escribir:value.CompareTo(_minimumValue) >= 0
fuente
Si
value
puede ser nulo, la respuesta actual podría fallar. Use algo como esto en su lugar:Comparer<T>.Default.Compare(value, _minimumValue) >= 0
fuente
T
hacerloIComparable
. Pero tu propina me ayudó a superar el problema.public bool IsInRange(T value) { return (value.CompareTo(_minimumValue) >= 0); }
Cuando se trabaja con genéricos IComparable, todos los operadores menores / mayores que deben convertirse en llamadas a CompareTo. Independientemente del operador que utilice, mantenga los valores que se comparan en el mismo orden y compárelos con cero. (
x <op> y
Se conviertex.CompareTo(y) <op> 0
, en donde<op>
es>
,>=
, etc.)Además, recomendaría que la restricción genérica que use sea
where T : IComparable<T>
. IComparable por sí mismo significa que el objeto se puede comparar con cualquier cosa, comparar un objeto con otros del mismo tipo probablemente sea más apropiado.fuente
En lugar de
value >= _minimValue
usar laComparer
clase:public bool IsInRange(T value ) { var result = Comparer<T>.Default.Compare(value, _minimumValue); if ( result >= 0 ) { return true; } else { return false; } }
fuente
Comparer
cuando ya existe una restricción genérica que seT
debe implementarIComparable
?Como han dicho otros, es necesario utilizar explícitamente el método CompareTo. La razón por la que no se pueden usar interfaces con operadores es que es posible que una clase implemente un número arbitrario de interfaces, sin una clasificación clara entre ellas. Suponga 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 se debe utilizar para el operador?
El hecho de que las clases puedan derivar múltiples interfaces hace que las interfaces sean muy poderosas. Desafortunadamente, a menudo obliga a uno a ser más explícito sobre lo que realmente quiere hacer.
fuente
IComparable
solo fuerza una función llamadaCompareTo()
. Entonces no puede aplicar ninguno de los operadores que ha mencionadofuente
Pude usar la respuesta de Peter Hedburg para crear algunos métodos de extensión sobrecargados para genéricos. Tenga en cuenta que el
CompareTo
método no funciona aquí, ya que el tipoT
es desconocido y no presenta esa interfaz. Dicho esto, me interesa ver alternativas.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 manualmente de manera confiable. Si alguien quisiera hacer los honores, me complacería 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--
Yo era capaz de limpiar esto al limitar
T
aIComparable(Of T)
sobre todos los métodos, según lo indicado por la OP. Tenga en cuenta que esta restricción también requiereT
que se implemente un tipoIComparable(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
fuente