これは見た目よりも複雑です。簡単な答えは次のとおりです。
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 は、構造体に "単純な" 値の型しかないかどうかに応じて、2 つの異なる戦略のいずれかを使用します ("単純な" は以下で定義されています)。
構造体に「単純な」値型のみが含まれている場合、基本的にビットごとの比較が行われます。
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() が、複数の NaN やゼロ表現など、IEEE 754 エンコーディングの複雑さの一部を考慮に入れているが、ビットごとの比較では考慮していないためです。「double」は単純な型と見なされるため、valueEqual が true を返す場合でも、ビットが異なる場合、structEqual は false を返します。
上記の例では代替のゼロ表現を使用しましたが、これは複数の 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;
}
ほとんどの通常の状況では、これで違いはありませんが、注意が必要です。