Overriding Equals method in Structs

C#StructEqualsOverriding

C# Problem Overview


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.

C# Solutions


Solution 1 - C#

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

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

   }

}

Solution 2 - C#

Thanks to pattern matching 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;
}

Solution 3 - C#

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.

Solution 4 - C#

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.

Solution 5 - C#

Use the is operator:

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

Solution 6 - C#

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questiondevoured elysiumView Question on Stackoverflow
Solution 1 - C#James CurranView Answer on Stackoverflow
Solution 2 - C#Robert SynoradzkiView Answer on Stackoverflow
Solution 3 - C#Albus DumbledoreView Answer on Stackoverflow
Solution 4 - C#tneView Answer on Stackoverflow
Solution 5 - C#Dan StoryView Answer on Stackoverflow
Solution 6 - C#jpabluzView Answer on Stackoverflow