web-dev-qa-db-ja.com

完全なIEquatable実装リファレンスはありますか?

ここでのSOに関する質問の多くは、IEquatableの実装に関するものです。ナイーブな実装には多くの隠れたバグがあり、それについて見つけた記事はかなり多いため、正しく実装するのは非常に難しいことがわかりました。不完全です。以下を含める必要がある決定的な参照を検索または記述したいと思います。

  • IEquatableを正しく実装する方法
  • Equalsを正しくオーバーライドする方法
  • GetHashCodeを正しくオーバーライドする方法
  • ToStringメソッドを正しく実装する方法
  • 演算子==を正しく実装する方法
  • 演算子を正しく実装する方法!=

そのような完全な参照はすでに存在しますか?

PS: MSDNリファレンス でも私には欠陥があるようです

45
Jader Dias

値型にIEquatable<T>を実装する

値型にIEquatable<T>を実装することは、参照型に実装することとは少し異なります。複素数構造体であるImplement-Your-Own-Value-Typeアーキタイプがあると仮定しましょう。

public struct Complex
{
    public double RealPart { get; set; }
    public double ImaginaryPart { get; set; }
}

最初のステップは、IEquatable<T>を実装し、Object.EqualsObject.GetHashCodeをオーバーライドすることです。

public bool Equals(Complex other)
{
    // Complex is a value type, thus we don't have to check for null
    // if (other == null) return false;

    return (this.RealPart == other.RealPart)
        && (this.ImaginaryPart == other.ImaginaryPart);
}

public override bool Equals(object other)
{
    // other could be a reference type, the is operator will return false if null
    if (other is Complex)
        return this.Equals((Complex)other);
    else
        return false;
}

public override int GetHashCode()
{
    return this.RealPart.GetHashCode() ^ this.ImaginaryPart.GetHashCode();
}

わずかな労力で、演算子を除いて正しい実装ができます。演算子の追加も簡単なプロセスです。

public static bool operator ==(Complex term1, Complex term2)
{
    return term1.Equals(term2);
}

public static bool operator !=(Complex term1, Complex term2)
{
    return !term1.Equals(term2);
}

Complexの数値は基になる値型と交換可能である可能性があるため、賢明な読者はおそらくIEquatable<double>を実装する必要があることに気付くでしょう。

public bool Equals(double otherReal)
{
    return (this.RealPart == otherReal) && (this.ImaginaryPart == 0.0);
}

public override bool Equals(object other)
{
    // other could be a reference type, thus we check for null
    if (other == null) return base.Equals(other);

    if (other is Complex)
    {
        return this.Equals((Complex)other);
    }
    else if (other is double)
    {
        return this.Equals((double)other);
    }
    else
    {
        return false;
    }
}

IEquatable<double>またはComplex == double(およびdouble == Complexについても同じ)を使用できるため、operator !=を追加する場合は4つの演算子が必要です。

public static bool operator ==(Complex term1, double term2)
{
    return term1.Equals(term2);
}

public static bool operator ==(double term1, Complex term2)
{
    return term2.Equals(term1);
}

public static bool operator !=(Complex term1, double term2)
{
    return !term1.Equals(term2);
}

public static bool operator !=(double term1, Complex term2)
{
    return !term2.Equals(term1);
}

これで、最小限の労力で、値型の正しくて便利な実装IEquatable<T>ができました。

public struct Complex : IEquatable<Complex>, IEquatable<double>
{
}
23
user7116

オブジェクトの同等性が正しいかどうかをチェックするのと同じくらい簡単なものを取得することは、.NETの設計では少し注意が必要だと思います。

構造体の場合

1)_IEquatable<T>_を実装します。パフォーマンスが大幅に向上します。

2)現在は独自のEqualsを使用しているので、GetHashCodeをオーバーライドし、さまざまな等価性チェックのオーバーライド_object.Equals_と一貫性を保つようにします。

3)_==_および_!=_演算子のオーバーロードは、意図せずに構造体を_==_または_!=_と同一視した場合にコンパイラーが警告するため、宗教的に行う必要はありませんが、それは良いことです。 Equalsメソッドと一貫性を保つためにそうします。

_public struct Entity : IEquatable<Entity>
{
    public bool Equals(Entity other)
    {
        throw new NotImplementedException("Your equality check here...");
    }

    public override bool Equals(object obj)
    {
        if (obj == null || !(obj is Entity))
            return false;

        return Equals((Entity)obj);
    }

    public static bool operator ==(Entity e1, Entity e2)
    {
        return e1.Equals(e2);
    }

    public static bool operator !=(Entity e1, Entity e2)
    {
        return !(e1 == e2);
    }

    public override int GetHashCode()
    {
        throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
    }
}
_

クラスの場合

MSから:

ほとんどの参照型は、Equalsをオーバーライドする場合でも、equality演算子をオーバーロードしないでください。

私にとって、_==_は値の平等のように感じ、Equalsメソッドの構文糖衣のように感じます。 _a == b_の記述は、a.Equals(b)の記述よりもはるかに直感的です。参照の同等性を確認する必要があることはめったにありません。物理オブジェクトの論理表現を扱う抽象的なレベルでは、これを確認する必要はありません。 _==_とEqualsのセマンティクスが異なると、実際には混乱する可能性があると思います。そもそも、値の平等には_==_、参照(またはEqualsのようなより良い名前)の平等にはIsSameAsである必要があったと思います。 ここでMSガイドラインを真剣に受け止めたくないのは、それが私にとって自然ではないという理由だけでなく、_==_のオーバーロードが大きな害を及ぼさないからです。これは、非ジェネリックなEqualsまたはGetHashCodeをオーバーライドしないのとは異なります。これは、フレームワークが_==_をどこでも使用しないためです。 _==_と_!=_をオーバーロードしないことで得られる唯一の本当の利点は、私が制御できないフレームワーク全体の設計との一貫性です。そして、それは確かに大きなことです、悲しいことに私はそれに固執します

参照セマンティクス(可変オブジェクト)を使用

1)EqualsGetHashCodeを上書きします。

2)_IEquatable<T>_の実装は必須ではありませんが、実装している場合は便利です。

_public class Entity : IEquatable<Entity>
{
    public bool Equals(Entity other)
    {
        if (ReferenceEquals(this, other))
            return true;

        if (ReferenceEquals(null, other))
            return false;

        //if your below implementation will involve objects of derived classes, then do a 
        //GetType == other.GetType comparison
        throw new NotImplementedException("Your equality check here...");
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as Entity);
    }

    public override int GetHashCode()
    {
        throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
    }
}
_

値セマンティクス(不変オブジェクト)を使用

これは難しい部分です。世話をしないと簡単に台無しになる可能性があります。

1)EqualsGetHashCodeを上書きします。

2)Equalsに一致するように_==_と_!=_をオーバーロードします。 nullに対して機能することを確認してください

2)_IEquatable<T>_の実装は必須ではありませんが、実装している場合は便利です。

_public class Entity : IEquatable<Entity>
{
    public bool Equals(Entity other)
    {
        if (ReferenceEquals(this, other))
            return true;

        if (ReferenceEquals(null, other))
            return false;

        //if your below implementation will involve objects of derived classes, then do a 
        //GetType == other.GetType comparison
        throw new NotImplementedException("Your equality check here...");
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as Entity);
    }

    public static bool operator ==(Entity e1, Entity e2)
    {
        if (ReferenceEquals(e1, null))
            return ReferenceEquals(e2, null);

        return e1.Equals(e2);
    }

    public static bool operator !=(Entity e1, Entity e2)
    {
        return !(e1 == e2);
    }

    public override int GetHashCode()
    {
        throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
    }
}
_

クラスを継承できるかどうかを特に注意してください。そのような場合、基本クラスオブジェクトを派生クラスオブジェクトと等しくできるかどうかを判断する必要があります。理想的には、派生クラスのオブジェクトがない場合が等価性チェックに使用される場合、基本クラスインスタンスは派生クラスインスタンスと等しくなる可能性があり、そのような場合、基本クラスの汎用TypeEqualsの等価性をチェックする必要はありません。

一般に、コードを重複させないように注意してください。汎用の抽象基本クラス(_IEqualizable<T>_など)をテンプレートとして作成して再利用を容易にすることもできましたが、残念ながらC#では追加のクラスから派生できなくなりました。

11
nawfal

別の参照を見つけました。それは.NET匿名型の実装です。プロパティとしてintとdoubleを持つ匿名型の場合、次のC#コードを逆アセンブルしました。

public class f__AnonymousType0
{
    // Fields
    public int A { get; }
    public double B { get; }

    // Methods
    public override bool Equals(object value)
    {
        var type = value as f__AnonymousType0;
        return (((type != null)
            && EqualityComparer<int>.Default.Equals(this.A, type.A))
            && EqualityComparer<double>.Default.Equals(this.B, type.B));
    }

    public override int GetHashCode()
    {
        int num = -1134271262;
        num = (-1521134295 * num) + EqualityComparer<int>.Default.GetHashCode(this.A);
        return ((-1521134295 * num) + EqualityComparer<double>.Default.GetHashCode(this.B);
    }

    public override string ToString()
    {
        StringBuilder builder = new StringBuilder();
        builder.Append("{ A = ");
        builder.Append(this.A);
        builder.Append(", B = ");
        builder.Append(this.B);
        builder.Append(" }");
        return builder.ToString();
    }
}
4
Jader Dias

MSDNを読んだとき、適切な実装の最良の例は IEquatable.Equalsメソッド ページにあると確信しています。私の唯一の逸脱は次のとおりです。

public override bool Equals(Object obj)
{
   if (obj == null) return base.Equals(obj);

   if (! (obj is Person))
      return false; // Instead of throw new InvalidOperationException
   else
      return Equals(obj as Person);   
}

偏差について疑問に思っている人のために、それは Object.Equals(Object) MSDNページから派生しています:

Equalsの実装は、例外をスローしてはなりません。

4
user7116

私はこのクラスから派生する必要があるだけです

public abstract class DataClass : IEquatable<DataClass>
{
    public override bool Equals(object obj)
    {
        var other = obj as DataClass;
        return this.Equals(other);
    }

    public bool Equals(DataClass other)
    {
        return (!ReferenceEquals(null, other))
            && this.Execute((self2, other2) =>
                other2.Execute((other3, self3) => self3.Equals(other3), self2)
                , other);
    }

    public override int GetHashCode()
    {
        return this.Execute(obj => obj.GetHashCode());
    }

    public override string ToString()
    {
        return this.Execute(obj => obj.ToString());
    }

    private TOutput Execute<TOutput>(Func<object, TOutput> function)
    {
        return this.Execute((obj, other) => function(obj), new object());
    }

    protected abstract TOutput Execute<TParameter, TOutput>(
        Func<object, TParameter, TOutput> function,
        TParameter other);
}

そして、このような抽象メソッドを実装します

public class Complex : DataClass
{
    public double Real { get; set; }

    public double Imaginary { get; set; }

    protected override TOutput Execute<TParameter, TOutput>(
        Func<object, TParameter, TOutput> function,
        TParameter other)
    {
        return function(new
        {
            Real = this.Real,
            Imaginary = this.Imaginary,
        }, other);
    }
}
1
Jader Dias