这比看上去要复杂得多。简短的回答是:
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;
然而,它的意义远不止于此。以下是详细信息:
如果您声明一个结构并且不覆盖 .Equals(),NET Framework 将使用两种不同的策略之一,具体取决于您的结构是否只有“简单”值类型(“简单”定义如下):
如果结构仅包含“简单”值类型,则进行按位比较,基本上:
strncmp((byte*)&struct1, (byte*)&struct2, Marshal.Sizeof(struct1));
如果结构包含引用或非“简单”值类型,则将每个声明的字段与 object.Equals() 进行比较:
struct1.GetType()==struct2.GetType() &&
!struct1.GetType().GetFields(ALL_FIELDS).Any(field =>
!object.Equals(field.GetValue(struct1), field.GetValue(struct2)));
什么是“简单”类型?从我的测试来看,它似乎是任何基本的标量类型(int、long、decimal、double 等),以及任何没有 .Equals 覆盖且仅包含“简单”类型(递归)的结构。
这有一些有趣的后果。例如,在这段代码中:
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);
}
无论为 test1.value 和 test2.value 分配了什么,您都希望 valueEqual 始终与 structEqual 相同。不是这种情况!
这个令人惊讶的结果的原因是 double.Equals() 考虑了 IEEE 754 编码的一些复杂性,例如多个 NaN 和零表示,但按位比较没有。因为“double”被认为是一种简单类型,所以当位不同时,structEqual 返回 false,即使 valueEqual 返回 true。
上面的示例使用了备用零表示,但这也可能发生在多个 NaN 值中:
...
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;
}
在大多数普通情况下,这不会产生影响,但需要注意这一点。