Overriding Equals method in Structs

前端 未结 6 555
-上瘾入骨i
-上瘾入骨i 2020-12-28 11:24

I\'ve looked for overriding guidelines for structs, but all I can find is for classes.

At first I thought I wouldn\'t have to check to see if the passed object was n

6条回答
  •  执念已碎
    2020-12-28 12:20

    In case anyone's wondering about the performance hit of boxing the struct in a Nullable object (to avoid the double type check from is and the cast), there is a non-negligible overhead.

    tl;dr: Use is & cast in this scenario.

    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(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);
        }
    }
    

    Results:

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

    Warning: This test might be flawed in many ways, though I did verify that the benchmark code itself wasn't optimized in an odd fashion.

    Looking at the IL, the double-check method compiles a little cleaner.

    Boxing 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
    

    Double-check 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
    

    Props to Roman Reiner for spotting a mistake that really wasn't making me look good.

提交回复
热议问题