¿Cómo comparar valores de tipos genéricos?

81

¿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!? TYa 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:

  1. ¿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?
  2. ¿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í).

Gstercken
fuente

Respuestas:

96

IComparableno sobrecarga al >=operador. Deberías usar

value.CompareTo(_minimumValue) >= 0
faester
fuente
7
Genial, esto funciona (y lo explica, por supuesto) - ¡muchas gracias! Pero es un poco insatisfactorio y deja la pregunta: ¿ Por qué IComparable no sobrecarga los operadores de comparación? ¿Es esta una decisión de diseño consciente y deliberada, con una buena razón, o algo que se pasó por alto en el diseño del marco? Después de todo, 'x.CompareTo (y)> = 0' es menos legible que 'x> = y', ¿no?
gstercken
Definitivamente entiendo tu punto. Supongo que el problema es que los operadores son estáticos, lo que significa que no pueden caber en una interfaz. No juzgaré si esta es una buena elección o no, pero tiendo a pensar que los métodos con nombres propios son más fáciles de leer que los operadores cuando los tipos no son primitivos; Sin embargo, esto es una cuestión de gustos.
faester
5
@gstercken: Un problema con la IComparablesobrecarga de los operadores de comparación es que hay situaciones en las que X.Equals(Y)debería devolver falso, pero X.CompareTo(Y)debería devolver cero (lo que sugiere que ningún elemento es más grande que el otro) [por ejemplo, un ExpenseItempuede tener un orden natural con respecto a TotalCost, 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.
supercat
1
@gstercken: Fundamentalmente, hay una serie de cosas que ==lógicamente podrían significar. En algunos contextos, es posible X==Yque sea verdadero, while X.Equals(Y)es falso, y mientras que en otros contextos X==Ypodría ser falso, while X.Equals(Y)es verdadero. Incluso si los operadores pueden ser sobrecargados de interfaces, sobrecarga <, <=, >y >=en cuanto a IComparable<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 ...
supercat
2
... ay, C # decidió usar el token ==para representar tanto un operador de igualdad sobrecargable como una prueba de igualdad de referencia no sobrecargable.
supercat
35

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 a class MagicMrMeow. O incluso en un class 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 intresultado es en realidad un tri-bit, o un tri-nary (similar a a 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 la solución alternativa

Para hacer el equivalente de value >= _minimumValue, debes escribir:

value.CompareTo(_minimumValue) >= 0
Merlyn Morgan-Graham
fuente
2
Ah, cierto, tiene sentido. Olvidé que las interfaces en C # no pueden sobrecargar a los operadores.
gstercken
32

Si valuepuede ser nulo, la respuesta actual podría fallar. Use algo como esto en su lugar:

Comparer<T>.Default.Compare(value, _minimumValue) >= 0
Peter Hedberg
fuente
1
Gracias por el consejo. Necesitaba esto para algunos métodos de extensión en los que estaba trabajando. Vea abajo.
InteXX
1
Ok lo tengo. No me estaba limitando a Thacerlo IComparable. Pero tu propina me ayudó a superar el problema.
InteXX
6
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> ySe convierte x.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.

David Yaw
fuente
3

En lugar de value >= _minimValueusar la Comparerclase:

public bool IsInRange(T value ) {
    var result = Comparer<T>.Default.Compare(value, _minimumValue);
    if ( result >= 0 ) { return true; }
    else { return false; }
}
TcKs
fuente
¿Por qué introducir el uso de a Comparercuando ya existe una restricción genérica que se Tdebe implementar IComparable?
Fredrik Mörk
Las restricciones genéricas de @Fredrik tienden a acumularse. Estoy de acuerdo en omitirlos aquí.
Marc Gravell
2

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.

Super gato
fuente
1
No creo que MI ya no sea un problema con los operadores sobrecargados como lo es con los métodos regulares. Son solo una sintaxis divertida para los métodos. Por lo tanto, podría resolver ese problema utilizando las mismas reglas que se usarían para resolverlos como métodos regulares en interfaces. Uno de los objetivos del diseño de C # era que fuera algo familiar para los programadores de C ++, y se abusaba de los operadores sobrecargados en ese lenguaje. Supongo que los diseñadores prefirieron métodos con nombre que lo obligan a proporcionar algún tipo de documentación para la intención de esos métodos.
Merlyn Morgan-Graham
1

IComparablesolo fuerza una función llamada CompareTo(). Entonces no puede aplicar ninguno de los operadores que ha mencionado

parapura rajkumar
fuente
0

Pude usar la respuesta de Peter Hedburg para crear algunos métodos de extensión sobrecargados para genéricos. Tenga en cuenta que el CompareTométodo no funciona aquí, ya que el tipo Tes 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 Ta IComparable(Of T)sobre todos los métodos, según lo indicado por la OP. Tenga en cuenta que esta restricción también requiere Tque se implemente un tipo 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
InteXX
fuente