web-dev-qa-db-fra.com

Remplacement de la méthode Equals dans Structs

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.

59
devoured elysium
struct MyStruct 
{
   public override bool Equals(object obj) 
   {
       if (!(obj is MyStruct))
          return false;

       MyStruct mys = (MyStruct) obj;
       // compare elements here

   }

}
73
James Curran

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.

13
Albus Dumbledore

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;
}
7
ensisNoctis

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.

6
tne

Utilisez l'opérateur is:

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

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

0
jpabluz