J'ai cherché des directives primordiales pour les structures, mais tout ce que je peux trouver est pour les classes.
Au début, je pensais que je n'aurais pas à vérifier si l'objet passé était nul, car les structures sont des types de valeur et ne peuvent pas être nulles. Mais maintenant que j'y pense, comme la signature égale est
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 (je pense) devoir faire avant de comparer mes champs privés dans ma structure. Comment suis-je censé convertir l'objet en fonction du type de ma structure? Le mot clé as
de C # ne semble approprié que pour les 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 une comparaison directe octet par octet, soit une comparaison champ par champ utilisant la réflexion.
Grâce à quelques nouvelles en C # 7. il y a 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é - la même chose que la fonction d'expression corporelle:
struct MyStruct
{
public override bool Equals(object obj) =>
obj is MyStruct mys
&& mys.field1 == this.field1
&& mys.field2 == this.field2;
}
Dans le cas où quelqu'un s'interroge sur les performances de la boxe, la structure dans un objet Nullable (pour éviter la vérification de type double de is
et du cast), il y a une surcharge non négligeable.
tl; dr: utilisez is
& cast dans ce scénario.
struct Foo : IEquatable<Foo>
{
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(string[] 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 imparfait à 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 compile 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<valuetype StructIEqualsImpl.Foo> obj_
)
IL_0000: ldarg.1
IL_0001: isinst valuetype [mscorlib]System.Nullable`1<valuetype StructIEqualsImpl.Foo>
IL_0006: unbox.any valuetype [mscorlib]System.Nullable`1<valuetype StructIEqualsImpl.Foo>
IL_000b: stloc.0
IL_000c: ldloca.s obj_
IL_000e: call instance bool valuetype [mscorlib]System.Nullable`1<valuetype StructIEqualsImpl.Foo>::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<valuetype StructIEqualsImpl.Foo>::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
Revérifiez 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
Accessoires à Roman Reiner pour avoir repéré une erreur qui ne me faisait pas vraiment bonne mine.
Utilisez l'opérateur is
:
public bool Equals(object obj)
{
if (obj is MyStruct)
{
var o = (MyStruct)obj;
...
}
}
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 objet valeur)
int?
La diffusion se fait également en appelant (MyStructName)variableName