65

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 null, as structs are value types and can't be null. But now that I come to think of it, as equals signature is

public bool Equals(object obj)

it seems there is nothing preventing the user of my struct to be trying to compare it with an arbitrary reference type.

My second point concerns the casting I (think I) have to make before I compare my private fields in my struct. How am I supposed to cast the object to my struct's type? C#'s as keyword seems only suitable for reference types.

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
devoured elysium
  • 101,373
  • 131
  • 340
  • 557
  • 6
    Just a note that you are encouraged to avoid mutable structs in .Net. It's set up you should stick to reference types (classes) most of the time, and use structs only rarely. – Joel Coehoorn Mar 30 '10 at 03:34
  • 4
    I second that. Use immutable structs *without* subtypes. Then Equals and == should be the same for a given receiver (left-side value) where the only difference in implementation is Equals needs an 'is' check and then, for simplicity, dispatches to ==. Thus both contracts are fulfilled and surprises are mitigated. –  Mar 30 '10 at 03:40
  • Yes, this struct is immutable. I'm only comparing one int. – devoured elysium Mar 30 '10 at 03:51
  • 6
    Another side-note; make sure that you override GetHashCode() to match the logic. – Marc Gravell Mar 30 '10 at 04:12
  • possible duplicate of [What needs to be overriden in a struct to ensure equality operates properly?](http://stackoverflow.com/questions/1502451/what-needs-to-be-overriden-in-a-struct-to-ensure-equality-operates-properly) – nawfal Nov 10 '13 at 08:52

6 Answers6

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

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

   }

}
Timwi
  • 65,159
  • 33
  • 165
  • 230
James Curran
  • 101,701
  • 37
  • 181
  • 258
  • 1
    Can you add the `override` keyword here for clarity? Always good practice in Java, should be the same in C#. – Johan S Aug 06 '13 at 13:12
  • 1
    See also Microsoft's guidelines -- http://msdn.microsoft.com/en-us/library/ms173147(v=vs.80).aspx – yoyo Aug 26 '14 at 17:23
  • 14
    @JohanS In C# it's more than just good practice, if you omit `override` the method actually does something completely different. – Pharap Dec 23 '14 at 09:08
29

Thanks to pattern matching introduced in C# 7.0 there is an easier way to accomplish the accepted answer:

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

You could also make it even shorter as an expression-bodied function:

struct MyStruct 
{
    public override bool Equals(object obj) => 
        obj is MyStruct mys
            && mys.field1 == this.field1
            && mys.field2 == this.field2;
}
Robert Synoradzki
  • 1,766
  • 14
  • 20
13

I suppose, if one's using .NET 4.5, one can use the default implementation as noted in the documentation:

When you define your own type, that type inherits the functionality defined by the Equals method of its base type.

ValueType.Equals: Value equality; either direct byte-by-byte comparison or field-by-field comparison using reflection.

Albus Dumbledore
  • 12,368
  • 23
  • 64
  • 105
  • 1
    It actually pre-dates 4.5, I don't know when it was added but it's definitely available in 4. Though one comment on MSDN seems to indicate that it may not be accurate for floating point types. – Pharap Dec 23 '14 at 09:22
  • @Pharap: When defining `Equals`, Microsoft failed to make clear how "equal" things should have to be for it to return `true`; there are some contexts where it is useful to test floating-point values for *equivalence* (that positive and negative zero are numerically equal does not imply that they are equivalent, since if x and y are equivalent that should imply that 1/x==1/y, but that's not true of positive and negative zero). Floating-point values in some structures are tested for equivalence, but I know of no general means to request such a test. – supercat Feb 03 '15 at 00:40
  • The same documentation states: "For a value type, you should always override Equals, because tests for equality that rely on reflection offer poor performance." So this is not a good idea. – Lombas Jun 18 '21 at 23:55
10

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

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

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.

tne
  • 7,071
  • 2
  • 45
  • 68
  • Your test *is* flawed! Your benchmark is calling the `Foo.Equals(Foo)` method. `Foo.Equals(object)` is never executed. – Roman Reiner Jun 17 '15 at 06:39
  • @RomanReiner: Oh, the shame. I obviously meant to cast the objects and simply forgot; with great consequences (the actual results are very different) -- well, if anyone attaches any importance to microbenchmarks anyway. Thanks a lot! – tne Jun 17 '15 at 10:26
5

Use the is operator:

public bool Equals(object obj)
{
  if (obj is MyStruct)
  {
    var o = (MyStruct)obj;
    ...
  }
}
Dan Story
  • 9,985
  • 1
  • 23
  • 27
0

Adding to the existing answers.

You can still have nullable values if you append a ? after the struct name (this works for every value object)

int?

Casting is done also by calling (MyStructName)variableName

jpabluz
  • 1,200
  • 6
  • 16
  • 2
    You can, but nullables have a very high performance penalty that will more than cost any benefit you would have gained by using "as" instead of "is". – Dan Story Mar 30 '10 at 16:46
  • @DanStory I wouldn't be so quick. If you care to have a look at [this](http://stackoverflow.com/a/28281410), I'd be curious to know if there's anything I've missed. tl;dr: is+cast compiles a little nicer indeed, but there doesn't appear to be anything like a "very high performance penalty" to as+boxing. In fact, I can't make the is+cast run reliably faster (sometimes the as+boxing method will take the lead). – tne Feb 02 '15 at 15:49
  • @DanStory I was most definitely wrong about that previous comment. The penalty *is* high (compared to the alternative in a microbenchmark anyway). Same linked answer was edited. – tne Jun 17 '15 at 16:47