Remplacement de la méthode des égaux dans Structs

J’ai cherché des directives de remplacement pour les structures, mais tout ce que je peux trouver, c’est pour les classes.

Au début, je pensais que je n’aurais pas à vérifier si l’object passé était nul, car les structures sont des types de valeur et ne peuvent pas être nulles. Mais maintenant que j’y pense, la signature est égale à

public bool Equals(object obj) 

il semble que rien n’empêche l’utilisateur de ma structure d’essayer de le comparer avec un type de référence arbitraire.

Mon deuxième point concerne le casting que je (pense que) je dois faire avant de comparer mes champs privés dans ma structure. Comment suis-je censé lancer l’object sur le type de ma structure? Le mot clé C # ne semble convenir qu’aux types de référence.

 struct MyStruct { public override bool Equals(object obj) { if (!(obj is MyStruct)) return false; MyStruct mys = (MyStruct) obj; // compare elements here } } 

Je suppose que si l’on utilise .NET 4.5 , on peut utiliser l’implémentation par défaut comme indiqué dans la documentation :

Lorsque vous définissez votre propre type, ce type hérite des fonctionnalités définies par la méthode Equals de son type de base.

ValueType.Equals : égalité de valeur; soit comparaison directe octet par octet ou comparaison champ par champ en utilisant la reflection.

Dans le cas où quelqu’un s’interroge sur la performance de boxing de la structure dans un object Nullable (pour éviter le double contrôle de type is et cast), il y a un surcoût non négligeable.

tl; dr : Utilisez & cast dans ce scénario.

 struct Foo : IEquatable { public int a, b; public Foo(int a, int b) { this.a = a; this.b = b; } public override bool Equals(object obj) { #if BOXING var obj_ = obj as Foo?; return obj_ != null && Equals(obj_.Value); #elif DOUBLECHECK return obj is Foo && Equals((Foo)obj); #elif MAGIC ? #endif } public bool Equals(Foo other) { return a == other.a && b == other.b; } } class Program { static void Main(ssortingng[] args) { RunBenchmark(new Foo(42, 43), new Foo(42, 43)); RunBenchmark(new Foo(42, 43), new Foo(43, 44)); } static void RunBenchmark(object x, object y) { var sw = Stopwatch.StartNew(); for (var i = 0; i < 100000000; i++) x.Equals(y); sw.Stop(); Console.WriteLine(sw.ElapsedMilliseconds); } } 

Résultats:

 BOXING EQ 8012 7973 7981 8000 NEQ 7929 7715 7906 7888 DOUBLECHECK EQ 3654 3650 3638 3605 NEQ 3310 3301 3319 3297 

Avertissement: Ce test peut être erroné à bien des égards, même si j'ai vérifié que le code de référence lui-même n'était pas optimisé de manière étrange.

En regardant l'IL, la méthode de double vérification comstack un peu plus propre.

Boxe IL:

 .method public hidebysig virtual instance bool Equals ( object obj ) cil managed { // Method begins at RVA 0x2060 // Code size 37 (0x25) .maxstack 2 .locals init ( [0] valuetype [mscorlib]System.Nullable`1 obj_ ) IL_0000: ldarg.1 IL_0001: isinst valuetype [mscorlib]System.Nullable`1 IL_0006: unbox.any valuetype [mscorlib]System.Nullable`1 IL_000b: stloc.0 IL_000c: ldloca.s obj_ IL_000e: call instance bool valuetype [mscorlib]System.Nullable`1::get_HasValue() IL_0013: brfalse.s IL_0023 IL_0015: ldarg.0 IL_0016: ldloca.s obj_ IL_0018: call instance !0 valuetype [mscorlib]System.Nullable`1::get_Value() IL_001d: call instance bool StructIEqualsImpl.Foo::Equals(valuetype StructIEqualsImpl.Foo) IL_0022: ret IL_0023: ldc.i4.0 IL_0024: ret } // end of method Foo::Equals 

Vérifiez l'IL:

 .method public hidebysig virtual instance bool Equals ( object obj ) cil managed { // Method begins at RVA 0x2060 // Code size 23 (0x17) .maxstack 8 IL_0000: ldarg.1 IL_0001: isinst StructIEqualsImpl.Foo IL_0006: brfalse.s IL_0015 IL_0008: ldarg.0 IL_0009: ldarg.1 IL_000a: unbox.any StructIEqualsImpl.Foo IL_000f: call instance bool StructIEqualsImpl.Foo::Equals(valuetype StructIEqualsImpl.Foo) IL_0014: ret IL_0015: ldc.i4.0 IL_0016: ret } // end of method Foo::Equals 

Roman Reiner a été détecté pour avoir repéré une erreur qui ne me rendait pas bien.

Utilisez l’opérateur is :

 public bool Equals(object obj) { if (obj is MyStruct) { var o = (MyStruct)obj; ... } } 

Grâce aux nouvelles de C # 7.0, il existe un moyen plus simple d’accomplir la même chose que la réponse acceptée:

 struct MyStruct { public override bool Equals(object obj) { if (!(obj is MyStruct mys)) // type pattern here return false; return this.field1 == mys.field1 && this.field2 == mys.field2 // mys is already known here without explicit casting } } 

Ou mon préféré – le même que la fonction d’expression de corps:

 struct MyStruct { public override bool Equals(object obj) => obj is MyStruct mys ? true // the initial "true" doesn't affect the overall boolean operation yet allows nice line aligning below && this.field1 == mys.field1 && this.field2 == mys.field2 : false; // obj is not MyStruct } 

Ajout aux réponses existantes.

Vous pouvez toujours avoir des valeurs nullables si vous ajoutez un? après le nom de la structure (cela fonctionne pour chaque object de valeur)

 int? 

Le (MyStructName)variableName est également fait en appelant (MyStructName)variableName