Overriding Equals method in Structs
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开发者_Go百科 only suitable for reference types.
struct MyStruct
{
public override bool Equals(object obj)
{
if (!(obj is MyStruct))
return false;
MyStruct mys = (MyStruct) obj;
// compare elements here
}
}
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;
}
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.
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.
Use the is
operator:
public bool Equals(object obj)
{
if (obj is MyStruct)
{
var o = (MyStruct)obj;
...
}
}
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
精彩评论