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 chaîne, == renvoie true si ses deux opérandes font référence au même objet. 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). Les types de référence définis par l'utilisateur peuvent donc l'être, 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 à se compiler?
bool Compare<T>(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, puisque, si je comprends bien, l'opérateur ==
est prédéfini pour tous les types?
Edit: Merci à tous. Au début, je n'avais pas remarqué que l'énoncé 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 être pas correct.
Mais, si j'utilise un type de référence, l'opérateur ==
utilisera-t-il la comparaison de références prédéfinie ou la version surchargée de l'opérateur si un type en définit un?
Éditer 2: Après essais et erreurs, nous avons appris que l'opérateur ==
utilisera la comparaison de référence prédéfinie lors de l'utilisation d'un type générique non restreint. En fait, le compilateur utilisera la meilleure méthode qu'il puisse trouver pour l'argument de type restreint, mais ne cherchera pas plus loin. Par exemple, le code ci-dessous affichera toujours true
, même lorsque Test.test<B>(new B(), new B())
sera 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>(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 supposer cette hypothèse.
Cependant, cela compilera car il est plus explicite:
bool Compare<T>(T x, T y) where T : class
{
return x == y;
}
Suite de la question supplémentaire, "Mais, si j'utilise un type de référence, l'opérateur == utiliserait-il la comparaison de références prédéfinie ou utiliserait-il la version surchargée de l'opérateur si un type défini par celui-ci ? "
J'aurais pensé que == sur les génériques utiliserait la version surchargée, mais le test suivant 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(string[] args)
{
Test a = new Test();
Test b = new Test();
Console.WriteLine("Inline:");
bool x = a == b;
Console.WriteLine("Generic:");
Compare<Test>(a, b);
}
static bool Compare<T>(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: surchargé == appelé
Générique:
Appuyez sur n'importe quelle touche pour continuer . . .
Suivi 2
Je tiens à souligner que changer ma méthode de comparaison pour
static bool Compare<T>(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 où ), le compilateur ne peut pas en déduire qu'il devrait utiliser l'opérateur surchargé ... bien que je pense qu'il aurait suffisamment d'informations pour prendre cette décision même sans préciser 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 uniquement avec null - et cette comparaison sera toujours fausse pour les types de valeur non nullable.
Au lieu d'appeler Egal, il est préférable d'utiliser un IComparer<T>
- et si vous n'avez pas plus d'informations, EqualityComparer<T>.Default
est un bon choix:
public bool Compare<T>(T x, T y)
{
return EqualityComparer<T>.Default.Equals(x, y);
}
En dehors de toute autre chose, cela évite la boxe/casting.
En général, EqualityComparer<T>.Default.Equals
devrait faire le travail avec tout ce qui implémente IEquatable<T>
, ou qui a une implémentation sensible Equals
.
Si, toutefois, ==
et Equals
sont implémentés différemment pour une raison quelconque, mon travail sur opérateurs génériques devrait être utile; il supporte l'opérateur des versions de (entre autres):
Tant de réponses, et pas une seule explique le POURQUOI? (que Giovanni a explicitement demandé) ...
Les génériques .NET ne se comportent pas comme des modèles C++. Dans les modèles C++, la résolution de la surcharge se produit une fois que les paramètres de modèle réels sont connus.
Dans les génériques .NET (y compris C #), la résolution de la surcharge se produit sans connaître les paramètres génériques réels. Les seules informations que le compilateur peut utiliser pour choisir la fonction à appeler proviennent des contraintes de type sur les paramètres génériques.
La compilation ne peut pas savoir que T ne peut pas être une structure (type valeur). Donc, vous devez le dire, il ne peut s'agir que d'un type de référence, je pense:
bool Compare<T>(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 type n'a pas d'opérateur == défini. La même chose se produira pour ce qui est plus évident:
void CallFoo<T>(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. Cela se fait par la clause where.
Il semble que sans la contrainte de classe:
bool Compare<T> (T x, T y) where T: class
{
return x == y;
}
On doit se rendre compte que si class
contrainte Equals
dans l'opérateur ==
hérite de Object.Equals
, tandis que celle d'une structure remplace ValueType.Equals
.
Notez que:
bool Compare<T> (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 refuse la comparaison d'opérateur d'égalité de type valeur. Je sais cependant que cela fonctionne:
bool Compare<T> (T x, T y)
{
return x.Equals(y);
}
Si vous voulez vous assurer que les opérateurs de votre type personnalisé sont appelés, vous pouvez le faire par réflexion. Obtenez simplement le type en utilisant votre paramètre générique et récupérez le 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);
Exécutez ensuite l'opérateur à l'aide de la méthode Invoke de MethodInfo et transmettez les objets en tant que paramètres.
var result = (bool) methodInfo.Invoke(null, new object[] { object1, object2});
Cela appellera votre opérateur surchargé et non celui défini par les contraintes appliquées au paramètre générique. Cela pourrait ne pas être pratique, mais pourrait s'avérer utile pour le test unitaire de vos opérateurs lors de l'utilisation d'une classe de base générique contenant quelques tests.
Il existe une entrée MSDN Connect pour cela ici
La réponse d'Alex Turner commence par:
Malheureusement, ce problème est inhérent au projet et il n’existe pas de solution simple pour permettre l’utilisation de == avec des paramètres de type pouvant contenir des types valeur.
Eh bien, dans mon cas, je voulais effectuer des tests unitaires auprès de l'opérateur d'égalité. 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
s'appelait la méthode Equals
mais pas l'opérateur d'égalité.
Voici comment cela fonctionne avec les types génériques en construisant une LINQ
. Il appelle le bon code pour les opérateurs ==
et !=
:
/// <summary>
/// Gets the result of "a == b"
/// </summary>
public bool GetEqualityOperatorResult<T>(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);
// compile it
var invokeEqualityOperator = Expression.Lambda<Func<T, T, bool>>(body, paramA, paramB).Compile();
// call it
return invokeEqualityOperator(a, b);
}
/// <summary>
/// Gets the result of "a =! b"
/// </summary>
public bool GetInequalityOperatorResult<T>(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);
// compile it
var invokeInequalityOperator = Expression.Lambda<Func<T, T, bool>>(body, paramA, paramB).Compile();
// call it
return invokeInequalityOperator(a, b);
}
J'ai écrit la fonction suivante en regardant le dernier msdn. Il peut facilement comparer deux objets x
et y
:
static bool IsLessThan(T x, T y)
{
return ((IComparable)(x)).CompareTo(y) <= 0;
}
bool Compare(T x, T y) where T : class { return x == y; }
Ce qui précède fonctionnera car == est pris en charge dans le cas de types de référence définis par l'utilisateur.
Dans le cas des types de valeur, == peut être remplacé. Dans ce cas, "! =" Devrait également être défini.
Je pense que cela pourrait être la raison, il interdit la comparaison générique en utilisant "==".