L’opérateur == ne peut-il pas être appliqué aux types génériques en C #?

Selon la documentation de l’opérateur == dans MSDN ,

Pour les types de valeur prédéfinis, l’opérateur d’égalité (==) renvoie true si les valeurs de ses opérandes sont égales, false sinon. Pour les types de référence autres que ssortingng, == renvoie true si ses deux opérandes font référence au même object. Pour le type de chaîne, == compare les valeurs des chaînes. Les types de valeur définis par l’utilisateur peuvent surcharger l’opérateur == (voir opérateur). Il en est de même pour les types de référence définis par l’utilisateur, bien que par défaut == se comporte comme décrit ci-dessus pour les types de référence prédéfinis et définis par l’utilisateur.

Alors, pourquoi cet extrait de code ne parvient-il pas à comstackr?

 bool Compare(T x, T y) { return x == y; } 

Je reçois l’erreur L’ opérateur ‘==’ ne peut pas être appliqué aux opérandes de type ‘T’ et ‘T’ . Je me demande pourquoi, pour autant que je == opérateur == est prédéfini pour tous les types?

Edit: Merci à tous. Je n’ai pas remarqué que la déclaration concernait uniquement les types de référence. J’ai également pensé qu’une comparaison bit à bit est fournie pour tous les types de valeur, ce que je sais maintenant n’est pas correct.

Mais, si j’utilise un type de référence, l’opérateur == utilisera-t-il la comparaison de référence prédéfinie ou utilisera-t-il la version surchargée de l’opérateur si un type en définit un?

Edit 2: Par essais et erreurs, nous avons appris que l’opérateur == utilisera la comparaison de référence prédéfinie en utilisant un type générique non restreint. En fait, le compilateur utilisera la meilleure méthode qu’il peut trouver pour l’argument de type restreint, mais ne cherchera pas plus loin. Par exemple, le code ci-dessous affichera toujours true , même si Test.test(new B(), new B()) est appelé:

 class A { public static bool operator==(A x, A y) { return true; } } class B : A { public static bool operator==(B x, B y) { return false; } } class Test { void test(T a, T b) where T : A { Console.WriteLine(a == b); } } 

“… par défaut == se comporte comme décrit ci-dessus pour les types de référence prédéfinis et définis par l’utilisateur.”

Le type T n’est pas nécessairement un type de référence, le compilateur ne peut donc pas faire cette hypothèse.

Cependant, cela comstackra car il est plus explicite:

  bool Compare(T x, T y) where T : class { return x == y; } 

Suivi de la question supplémentaire, “Mais, si j’utilise un type de référence, l’opérateur the == utilisera-t-il la comparaison de référence prédéfinie ou utilisera-t-il la version surchargée de l’opérateur si un type en définit un?”

J’aurais pensé que == sur les génériques utiliserait la version surchargée, mais le test suivant démontre le contraire. Intéressant … j’aimerais savoir pourquoi! Si quelqu’un sait s’il vous plaît partager.

 namespace TestProject { class Program { static void Main(ssortingng[] args) { Test a = new Test(); Test b = new Test(); Console.WriteLine("Inline:"); bool x = a == b; Console.WriteLine("Generic:"); Compare(a, b); } static bool Compare(T x, T y) where T : class { return x == y; } } class Test { public static bool operator ==(Test a, Test b) { Console.WriteLine("Overloaded == called"); return a.Equals(b); } public static bool operator !=(Test a, Test b) { Console.WriteLine("Overloaded != called"); return a.Equals(b); } } } 

Sortie

Inline: Overloaded == appelé

Générique:

Appuyez sur n’importe quelle touche pour continuer . . .

Suivi 2

Je tiens à souligner que changer ma méthode de comparaison à

  static bool Compare(T x, T y) where T : Test { return x == y; } 

provoque l’appel de l’opérateur == surchargé. Je suppose que sans spécifier le type (en tant que ), le compilateur ne peut pas en déduire qu’il devrait utiliser l’opérateur surchargé … bien que je pense qu’il aurait assez d’informations pour prendre cette décision sans spécifier le type.

Comme d’autres l’ont dit, cela ne fonctionnera que si T est contraint d’être un type de référence. Sans aucune contrainte, vous pouvez comparer avec null, mais seulement null – et cette comparaison sera toujours fausse pour les types de valeur non NULL.

Au lieu d’appeler Equals, il est préférable d’utiliser un IComparer – et si vous n’avez plus d’informations, EqualityComparer.Default est un bon choix:

 public bool Compare(T x, T y) { return EqualityComparer.Default.Equals(x, y); } 

En dehors de tout, cela évite la boxe / le casting.

En général, EqualityComparer.Default.Equals doit faire le travail avec tout ce qui implémente IEquatable ou qui a une implémentation sensible Equals .

Si, cependant, == et Equals sont implémentés différemment pour une raison quelconque, alors mon travail sur les opérateurs génériques devrait être utile; il supporte les versions opérateur de (entre autres):

  • Égal (valeur T1, valeur T2)
  • NotEqual (valeur T1, valeur T2)
  • GreaterThan (valeur T1, valeur T2)
  • LessThan (valeur T1, valeur T2)
  • GreaterThanOrEqual (valeur T1, valeur T2)
  • LessThanOrEqual (valeur T1, valeur T2)

Tant de réponses, et pas une seule ne explique le POURQUOI? (ce que Giovanni a explicitement demandé) …

Les génériques .NET n’agissent pas comme des modèles C ++. Dans les modèles C ++, la résolution de la surcharge se produit une fois que les parameters du modèle sont connus.

Dans les génériques .NET (y compris C #), la résolution de la surcharge se produit sans connaître les parameters génériques réels. La seule information que le compilateur peut utiliser pour choisir la fonction à appeler provient des contraintes de type sur les parameters génériques.

La compilation ne peut pas savoir que T ne peut pas être une structure (type de valeur). Donc, vous devez le dire, il ne peut être que du type de référence je pense:

 bool Compare(T x, T y) where T : class { return x == y; } 

C’est parce que si T pouvait être un type de valeur, il pourrait y avoir des cas où x == y serait mal formé – dans les cas où un opérateur n’a pas d’opérateur == défini. La même chose se produira pour ce qui est plus évident:

 void CallFoo(T x) { x.foo(); } 

Cela échoue aussi, car vous pourriez passer un type T qui n’aurait pas de fonction foo. C # vous oblige à vous assurer que tous les types possibles ont toujours une fonction foo. C’est fait par la clause where.

Il semble que sans la contrainte de classe:

 bool Compare (T x, T y) where T: class { return x == y; } 

On devrait se rendre compte que tandis que la class contrainte Equals dans l’opérateur == hérite de Object.Equals , alors que celle d’une structure remplace ValueType.Equals .

Notez que:

 bool Compare (T x, T y) where T: struct { return x == y; } 

donne également la même erreur de compilation.

Pour l’instant, je ne comprends pas pourquoi le compilateur rejette une comparaison d’opérateur d’égalité de type valeur. Je sais cependant que cela fonctionne:

 bool Compare (T x, T y) { return x.Equals(y); } 

Il existe une entrée MSDN Connect pour cela ici

La réponse d’Alex Turner commence par:

Malheureusement, ce comportement est inhérent à la conception et il n’ya pas de solution facile pour permettre l’utilisation de == avec des parameters de type pouvant contenir des types de valeur.

Si vous voulez vous assurer que les opérateurs de votre type personnalisé sont appelés, vous pouvez le faire par reflection. Obtenez simplement le type en utilisant votre paramètre générique et récupérez MethodInfo pour l’opérateur souhaité (par exemple, op_Equality, op_Inequality, op_LessThan …).

 var methodInfo = typeof (T).GetMethod("op_Equality", BindingFlags.Static | BindingFlags.Public); 

Ensuite, exécutez l’opérateur en utilisant la méthode Invoke de MethodInfo et transmettez les objects en tant que parameters.

 var result = (bool) methodInfo.Invoke(null, new object[] { object1, object2}); 

Cela invoquera votre opérateur surchargé et non celui défini par les contraintes appliquées au paramètre générique. Peut ne pas être pratique, mais pourrait être utile pour tester vos opérateurs lorsque vous utilisez une classe de base générique contenant quelques tests.

Eh bien, dans mon cas, je voulais tester l’opérateur d’égalité de manière unitaire. J’avais besoin d’appeler le code sous les opérateurs d’égalité sans définir explicitement le type générique. Les conseils pour EqualityComparer n’étaient pas utiles car EqualityComparer appelé méthode Equals , mais pas l’opérateur d’égalité.

Voici comment cela fonctionne avec les types génériques en construisant un LINQ . Il appelle le bon code pour les opérateurs == et != :

 ///  /// Gets the result of "a == b" ///  public bool GetEqualityOperatorResult(T a, T b) { // declare the parameters var paramA = Expression.Parameter(typeof(T), nameof(a)); var paramB = Expression.Parameter(typeof(T), nameof(b)); // get equality expression for the parameters var body = Expression.Equal(paramA, paramB); // comstack it var invokeEqualityOperator = Expression.Lambda>(body, paramA, paramB).Comstack(); // call it return invokeEqualityOperator(a, b); } ///  /// Gets the result of "a =! b" ///  public bool GetInequalityOperatorResult(T a, T b) { // declare the parameters var paramA = Expression.Parameter(typeof(T), nameof(a)); var paramB = Expression.Parameter(typeof(T), nameof(b)); // get equality expression for the parameters var body = Expression.NotEqual(paramA, paramB); // comstack it var invokeInequalityOperator = Expression.Lambda>(body, paramA, paramB).Comstack(); // call it return invokeInequalityOperator(a, b); } 
bool Compare(T x, T y) where T : class { return x == y; }
bool Compare(T x, T y) where T : class { return x == y; } 

Ce qui précède fonctionnera car == est pris en compte dans le cas des types de référence définis par l’utilisateur.
Dans le cas des types de valeur, == peut être remplacé. Dans ce cas, “! =” Doit également être défini.

Je pense que cela pourrait être la raison, il interdit la comparaison générique en utilisant “==”.

J’ai écrit la fonction suivante en regardant le dernier msdn. Il peut facilement comparer deux objects x et y :

 static bool IsLessThan(T x, T y) { return ((IComparable)(x)).CompareTo(y) <= 0; }