2010-03-30 10 views
49

J'ai recherché des directives de remplacement pour les structures, mais tout ce que je peux trouver concerne les classes. Au début, je pensais que je n'aurais pas à vérifier si l'objet transmis était nul, car les structures sont des types de valeur et ne peuvent pas être nulles. Mais maintenant que je viens à penser, comme la signature est égaleSurcharger la méthode Equals dans Structs

public bool Equals(object obj) 

il semble qu'il n'y a rien qui empêche l'utilisateur de mon struct pour essayer de le comparer avec un type de référence arbitraire.

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

+6

Juste une note que vous sont encouragés à éviter les structures mutables dans .Net. C'est réglé, vous devriez vous en tenir aux types de référence (classes) la plupart du temps, et utiliser les structures rarement. –

+4

Je seconde cela. Utilisez des structures immuables * sans * sous-types. Alors Equals et == devraient être les mêmes pour un récepteur donné (valeur de gauche) où la seule différence dans l'implémentation est Equals a besoin d'une vérification 'is' et ensuite, par simplicité, les expéditions à ==. Ainsi, les deux contrats sont remplis et les surprises sont atténuées. –

+0

Oui, cette structure est immuable. Je compare seulement un int. –

Répondre

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

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

    } 

} 
+1

Pouvez-vous ajouter le mot clé 'override' ici pour plus de clarté? Toujours bonne pratique en Java, devrait être la même en C#. –

+1

Voir aussi les directives de Microsoft - http://msdn.microsoft.com/fr-fr/library/ms173147(v=vs.80).aspx – yoyo

+7

@JohanS En C# c'est plus que de la bonne pratique, si vous omettez 'override 'La méthode fait quelque chose de complètement différent. – Pharap

5

Utilisez l'opérateur is:

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

Ajout des réponses existantes.

Vous pouvez toujours avoir des valeurs NULL si vous ajoutez un? après le nom de struct (cela fonctionne pour tous les objets de valeur)

int? 

coulée se fait aussi en appelant (MyStructName)variableName

+2

Vous pouvez, mais les Nullables ont une pénalité de performance très élevée qui coûtera plus que tout avantage que vous auriez gagné en utilisant "as" au lieu de "is". –

+0

@DanStory Je ne serais pas si rapide. Si vous voulez regarder [ceci] (http://stackoverflow.com/a/28281410), je serais curieux de savoir s'il y a quelque chose que j'ai manqué. tl; dr: is + cast compile un peu mieux, mais il ne semble pas y avoir de "pénalité de très haute performance" en tant que + boxing. En fait, je ne peux pas faire fonctionner le lancer is + de manière fiable plus rapidement (la méthode as + boxing prendra parfois la tête). – tne

+0

@DanStory Je me trompais définitivement sur ce commentaire précédent. La pénalité * est * élevée (comparée à l'alternative dans un microbenchmark de toute façon). La même réponse liée a été éditée. – tne

12

Je suppose que, si l'on se sert de .NET 4.5, on peut utiliser l'implémentation par défaut comme indiqué dans le documentation:

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

ValueType.Equals: Égalité des valeurs; soit une comparaison directe byte by byte ou une comparaison champ par champ en utilisant la réflexion.

+1

En fait, il est antérieur à la version 4.5, je ne sais pas quand il a été ajouté mais il est certainement disponible dans 4. Un commentaire sur MSDN semble indiquer qu'il peut ne pas être précis pour les types à virgule flottante. – Pharap

+1

Voir http://stackoverflow.com/q/1009394 pour des considérations de performances. – tne

+0

@Pharap: Lors de la définition de 'Equals', Microsoft n'a pas précisé comment les choses" égales "devraient être pour qu'il retourne' true'; il y a des contextes où il est utile de tester les valeurs en virgule flottante pour * l'équivalence * (le fait que zéro positif et négatif soit numériquement égal n'implique pas qu'elles sont équivalentes, puisque si x et y sont équivalents cela devrait impliquer que 1/x = = 1/y, mais ce n'est pas vrai du zéro positif et négatif). Les valeurs à virgule flottante dans certaines structures sont testées pour l'équivalence, mais je ne connais aucun moyen général de demander un tel test. – supercat

6

Au cas où quelqu'un se demande au sujet de la baisse de performance de la boxe struct dans un objet Nullable (pour éviter la vérification de type double de is et la distribution), il est un surcoût 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 

Attention: Ce test pourrait être viciée à bien des égards, même si je ne vérifie que le code de référence lui-même n'a pas été optimisé de façon é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 

IL Revérifiez:

.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 

Props à Reiner Roman pour repérer une erreur qui était vraiment pas me fait bien paraître.

+0

Votre test * est * défectueux! Votre benchmark appelle la méthode 'Foo.Equals (Foo)'. 'Foo.Equals (object)' n'est jamais exécuté. –

+0

@RomanReiner: Oh, la honte. Je voulais évidemment jeter les objets et j'ai simplement oublié; avec de grandes conséquences (les résultats réels sont très différents) - eh bien, si quelqu'un attache une quelconque importance aux microbenchmarks de toute façon. Merci beaucoup! – tne

1

Merci à some news in C# 7.0 il existe un moyen plus facile d'accomplir la même acceptée réponse:

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 l'expression fonction carrossée:

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 
}