In "Effective C#", Bill Wagner says "Always create an override of ValueType.Equals() whenever you create a value type". His main consideration is the performance, because reflection is needed to compare two value types memberwisely.
In fact, the framework provides optimization for "simple" value type. Let's find out the magic.
Here is the code disassembled by Reflector:
bool Equals(object obj)
object a = this;
return FastEqualsCheck(a, obj);
//Compare using reflection
The magic is the two functions "CanCompareBits" and "FastEqualsCheck". They both have attribute "[MethodImpl(MethodImplOptions.InternalCall)]", which indicates that their implementations are in native dlls.
Walkthrough the source of rotor, you can find that these functions are in "clr\src\vm\comutilnative.cpp"
The comment of CanCompareBits says "Return true if the valuetype does not contain pointer and is tightly packed". And FastEqualsCheck uses "memcmp" to speed up the comparison.
Then you may think you can safely rely on this optimization and stick to the default ValueType.Equals implementation. But wait a minute. Do you find anything wrong in CanCompareBits?
The problem is that the condition in the comment doesn't ensure the bitwise comparison to work.
Imagine you have a structure which only contains a float. What will occur if one contains +0.0, and the other contains -0.0? They should be the same, but the underlying binary representation are different.
If you nest other structure which override the Equals method, that optimization will also fail.
This should be a bug. But it also provides another reason why you should always implement your own instance Equals for ValueType.