Equality Compateur .Default vs T.Equals
-
27-10-2019 - |
Question
Supposons que j'ai un générique MyClass<T>
qui doit comparer deux objets de type <T>
. Habituellement, je ferais quelque chose comme ...
void DoSomething(T o1, T o2)
{
if(o1.Equals(o2))
{
...
}
}
Supposons maintenant mon MyClass<T>
a un constructeur qui prend en charge le passage d'une coutume IEqualityComparer<T>
, semblable à Dictionary<T>
. Dans ce cas, j'aurais besoin de faire ...
private IEqualityComparer<T> _comparer;
public MyClass() {}
public MyClass(IEqualityComparer<T> comparer)
{
_comparer = comparer;
}
void DoSomething(T o1, T o2)
{
if((_comparer != null && _comparer.Equals(o1, o2)) || (o1.Equals(o2)))
{
...
}
}
Pour supprimer cette longue déclaration, ce serait bien si je pouvais avoir _comparer
par défaut à un «comparateur par défaut» si le constructeur régulier est utilisé. J'ai cherché quelque chose comme typeof(T).GetDefaultComparer()
Mais n'a pas pu trouver quelque chose de tel.
J'ai trouvé EqualityComparer<T>.Default
, pourrais-je utiliser ça? Et alors cet extrait ...
public MyClass()
{
_comparer = EqualityComparer<T>.Default;
}
void DoSomething(T o1, T o2)
{
if(_comparer.Equals(o1, o2))
{
...
}
}
... fournir les mêmes résultats que l'utilisation o1.Equals(o2)
pour tous les cas possibles?
(En tant que note secondaire, cela signifierait-il également que je devrais également utiliser toute contrainte générique spéciale pour <T>
?)
La solution
Cela devrait être identique, mais il n'est pas garanti, car cela dépend des détails de l'implémentation du type T
.
Explication:
Sans contrainte à T
, o1.equals (o2) appellera Object.Equals
, même si T
met en oeuvre IEquatable<T>
.
EqualityComparer<T>.Default
Cependant, vous utilisera Object.Equals
seulement si T
ne met pas en œuvre IEquatable<T>
. Si ça Est-ce que implémenter cette interface, il utilise IEquatable<T>.Equals
.
Aussi longtemps que T
la mise en œuvre de Object.Equals
les appels justes IEquatable<T>.Equals
le résultat est le même. Mais dans l'exemple suivant, le résultat n'est pas le même:
public class MyObject : IEquatable<MyObject>
{
public int ID {get;set;}
public string Name {get;set;}
public override bool Equals(object o)
{
var other = o as MyObject;
return other == null ? false : other.ID == ID;
}
public bool Equals(MyObject o)
{
return o.Name == Name;
}
}
Maintenant, il n'a aucun sens d'implémenter une classe comme celle-ci. Mais vous aurez le même problème, si l'ex-implémentateur de MyObject
J'ai simplement oublié de remplacer Object.Equals
.
Conclusion:
Utilisant EqualityComparer<T>.Default
est une bonne façon de procéder, car vous n'avez pas besoin de prendre en charge les objets buggy!
Autres conseils
Par défaut, jusqu'à dépasser dans une classe, Object.Equals(a,b)
/a.Equals(b)
Effectue une comparaison par référence.
Quel comparateur sera retourné par EqualityComparer<T>.Default
dépend de T
. Par exemple, si T : IEquatable<>
alors le approprié EqualityComparer<T>
sera créé.
Vous pouvez utiliser l'opérateur coaelescense nul ?? Pour raccourcir le si c'est vraiment important
if ((_comparer ?? EqualityComparer<T>.Default).Equals(o1, o2))
{
}
Oui, je pense qu'il serait sage d'utiliser le EqualityComparer<T>.Default
, parce qu'il utilise la mise en œuvre de IEquatable<T>
Si le type T
L'implémente, ou le remplacement de Object.Equals
Par ailleurs. Vous pouvez le faire comme suit:
private IEqualityComparer<T> _comparer;
public IEqualityComparer<T> Comparer
{
get { return _comparer?? EqualityComparer<T>.Default;}
set { _comparer=value;}
}
public MyClass(IEqualityComparer<T> comparer)
{
_comparer = comparer;
}
void DoSomething(T o1, T o2)
{
if(Comparer.Equals(o1, o2))
{
...
}
}
C'est exactement ce Dictionary<>
et d'autres collections génériques dans le BCL font si vous ne spécifiez pas de comparateur lors de la construction de l'objet. L'avantage de cela est que EqualityComparer<T>.Default
retournera le bon comparateur pour IEquatable<T>
types, types nullables et énumérations. Si T n'est rien de ceux-ci, il fera une comparaison égale simple comme vous le faites Old Code.