web-dev-qa-db-fra.com

Comment comparer les valeurs de types génériques?

Comment comparer les valeurs de types génériques?

Je l'ai réduit à un échantillon minimal:

public class Foo<T> where T : IComparable
{
    private T _minimumValue = default(T);

    public bool IsInRange(T value) 
    {
        return (value >= _minimumValue); // <-- Error here
    }
}

L'erreur est:

L'opérateur '> =' ne peut pas être appliqué aux opérandes de type 'T' et 'T'.

Quoi sur terre!? T est déjà contraint à IComparable et, même en le contraignant à des types valeur (where T: struct), nous ne pouvons toujours appliquer aucun des opérateurs <, >, <=, >=, == et !=. (Je sais que des solutions de contournement impliquant Equals() existent pour == et !=, mais cela n’aide en rien les opérateurs relationnels).

Donc, deux questions:

  1. Pourquoi observons-nous ce comportement étrange? Qu'est-ce qui nous empêche de comparer les valeurs de types génériques qui sont connus à IComparable? N’at-il pas en quelque sorte défait l’objet des contraintes génériques?
  2. Comment puis-je résoudre ce problème ou au moins y remédier?

(Je me rends compte qu'il y a déjà une poignée de questions liées à ce problème apparemment simple - mais aucun des fils ne donne une réponse exhaustive ou réalisable, alors voici.)

66
gstercken

IComparable ne surcharge pas l'opérateur >=. Tu devrais utiliser

value.CompareTo(_minimumValue) >= 0
83
faester

Problème de surcharge de l'opérateur

Malheureusement, les interfaces ne peuvent pas contenir d’opérateurs surchargés. Essayez de taper ceci dans votre compilateur:

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);
}

Je ne sais pas pourquoi ils ne l'ont pas autorisé, mais j'imagine que cela complique la définition du langage et que les utilisateurs auront du mal à mettre en œuvre correctement.

Soit ça, soit les concepteurs n'aimaient pas le potentiel d'abus. Par exemple, imaginons une comparaison >= avec un class MagicMrMeow. Ou même sur un class Matrix<T>. Que signifie le résultat sur les deux valeurs? Surtout quand il pourrait y avoir une ambiguïté?

Le work-around officiel

Puisque l'interface ci-dessus n'est pas légale, nous avons l'interface IComparable<T> pour contourner le problème. Il n'implémente aucun opérateur et n'expose qu'une seule méthode, int CompareTo(T other);.

Voir http://msdn.Microsoft.com/en-us/library/4d7sx9hd.aspx

Le résultat int est en fait un bit ou un tri-bit (semblable à un Boolean, mais avec trois états). Ce tableau explique la signification des résultats:

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.

Utilisation de la solution de contournement

Pour faire l'équivalent de value >= _minimumValue, vous devez plutôt écrire:

value.CompareTo(_minimumValue) >= 0
29

Si value peut être null, la réponse actuelle pourrait échouer. Utilisez quelque chose comme ceci à la place:

Comparer<T>.Default.Compare(value, _minimumValue) >= 0
18
Peter Hedberg
public bool IsInRange(T value) 
{
    return (value.CompareTo(_minimumValue) >= 0);
}

Lorsque vous travaillez avec des génériques IComparable, tous les opérateurs inférieurs à/supérieurs à doivent être convertis en appels à CompareTo. Quel que soit l'opérateur que vous utilisiez, conservez les valeurs comparées dans le même ordre et comparez-les avec zéro. (x <op> y devient x.CompareTo(y) <op> 0, où <op> est >, >=, etc.)

De plus, je recommanderais que la contrainte générique que vous utilisez soit where T : IComparable<T>. IComparable signifie en soi que l'objet peut être comparé à n'importe quoi, comparer un objet à d'autres du même type est probablement plus approprié.

6
David Yaw

Au lieu de value >= _minimValue, utilisez Comparer class:

public bool IsInRange(T value ) {
    var result = Comparer<T>.Default.Compare(value, _minimumValue);
    if ( result >= 0 ) { return true; }
    else { return false; }
}
3
TcKs

Comme d'autres l'ont dit, il est nécessaire d'utiliser explicitement la méthode CompareTo. La raison pour laquelle on ne peut pas utiliser d'interfaces avec des opérateurs est qu'il est possible pour une classe d'implémenter un nombre arbitraire d'interfaces, sans classement clair entre elles. Supposons que l'on essaie de calculer l'expression "a = toto + 5;" quand foo implémente six interfaces qui définissent toutes un opérateur "+" avec un second argument entier; quelle interface doit être utilisée pour l'opérateur?

Le fait que les classes puissent dériver de multiples interfaces rend les interfaces très puissantes. Malheureusement, cela oblige souvent à être plus explicite sur ce que l'on veut réellement faire.

2
supercat

IComparable force uniquement une fonction appelée CompareTo(). Donc, vous ne pouvez appliquer aucun des opérateurs que vous avez mentionnés

1
parapura rajkumar

J'ai pu utiliser la réponse de Peter Hedburg pour créer des méthodes d'extension surchargées pour les génériques. Notez que la méthode CompareTo ne fonctionne pas ici, car le type T est inconnu et ne présente pas cette interface. Cela dit, je suis intéressé à voir des alternatives.

Je voudrais avoir posté en C #, mais le convertisseur de Telerik échoue sur ce code. Je ne suis pas assez familier avec C # pour le convertir de manière fiable manuellement. Si quelqu'un souhaite faire les honneurs, je serais heureux de voir ceci édité en conséquence.

<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

--MODIFIER--

J'ai pu résoudre ce problème en contraignant T à IComparable(Of T) sur toutes les méthodes, comme indiqué par OP. Notez que cette contrainte nécessite que le type T implémente également 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
0
InteXX