What is the algorithm used by the memberwise equality test in .NET structs? I would like to know this so that I can use it as the basis for my own algorithm.
I am trying
This is more complex than meets the eye. The short answer would be:
public bool MyEquals(object obj1, object obj2)
{
if(obj1==null || obj2==null)
return obj1==obj2;
else if(...)
... // Your custom code here
else if(obj1.GetType().IsValueType)
return
obj1.GetType()==obj2.GetType() &&
!struct1.GetType().GetFields(ALL_FIELDS).Any(field =>
!MyEquals(field.GetValue(struct1), field.GetValue(struct2)));
else
return object.Equals(obj1, obj2);
}
const BindingFlags ALL_FIELDS =
BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic;
However there is much more to it than that. Here are the details:
If you declare a struct and don't override .Equals(), NET Framework will use one of two different strategies depending on whether your struct has only "simple" value types ("simple" is defined below):
If the struct contains only "simple" value types, a bitwise comparison is done, basically:
strncmp((byte*)&struct1, (byte*)&struct2, Marshal.Sizeof(struct1));
If the struct contains references or non-"simple" value types, each declared field is compared as with object.Equals():
struct1.GetType()==struct2.GetType() &&
!struct1.GetType().GetFields(ALL_FIELDS).Any(field =>
!object.Equals(field.GetValue(struct1), field.GetValue(struct2)));
What qualifies as a "simple" type? From my tests it appears to be any basic scalar type (int, long, decimal, double, etc), plus any struct that doesn't have a .Equals override and contains only "simple" types (recursively).
This has some interesting ramifications. For example, in this code:
struct DoubleStruct
{
public double value;
}
public void TestDouble()
{
var test1 = new DoubleStruct { value = 1 / double.PositiveInfinity };
var test2 = new DoubleStruct { value = 1 / double.NegativeInfinity };
bool valueEqual = test1.value.Equals(test2.value);
bool structEqual = test1.Equals(test2);
MessageBox.Show("valueEqual=" + valueEqual + ", structEqual=" + structEqual);
}
you would expect valueEqual to always be identical to structEqual, no matter what was assigned to test1.value and test2.value. This is not the case!
The reason for this surprising result is that double.Equals() takes into account some of the intricacies of the IEEE 754 encoding such as multiple NaN and zero representations, but a bitwise comparison does not. Because "double" is considered a simple type, the structEqual returns false when the bits are different, even when valueEqual returns true.
The above example used alternate zero representations, but this can also occur with multiple NaN values:
...
var test1 = new DoubleStruct { value = CreateNaN(1) };
var test2 = new DoubleStruct { value = CreateNaN(2) };
...
public unsafe double CreateNaN(byte lowByte)
{
double result = double.NaN;
((byte*)&result)[0] = lowByte;
return result;
}
In most ordinary situations this won't make a difference, but it is something to be aware of.