180

デフォルトの実装はどのように機能しますGetHashCode()か? また、構造体、クラス、配列などを効率的かつ十分に処理できますか?

どのような場合に自分でパックする必要があるか、どのような場合にデフォルトの実装に安全に依存してうまくいくかを決定しようとしています. 可能であれば、車輪の再発明はしたくありません。

4

6 に答える 6

94

クラスの場合、デフォルトは本質的に参照の等価性であり、通常はそれで問題ありません。構造体を書く場合、(特にボックス化を避けるために) 等式をオーバーライドするのがより一般的ですが、とにかく構造体を書くことは非常にまれです!

等価性をオーバーライドする場合は、常に一致するEquals()andを使用する必要がありますGetHashCode()(つまり、2 つの値の場合、Equals()true を返す場合、同じハッシュ コードを返す必要がありますが、逆は必要ありませ==ん)。また、 /!=演算子も提供することが一般的であり、多くの場合、も実施IEquatable<T>

ハッシュ コードを生成するには、因数分解した合計を使用するのが一般的です。これにより、ペアの値の衝突が回避されます。たとえば、基本的な 2 フィールド ハッシュの場合です。

unchecked // disable overflow, for the unlikely possibility that you
{         // are compiling with overflow-checking enabled
    int hash = 27;
    hash = (13 * hash) + field1.GetHashCode();
    hash = (13 * hash) + field2.GetHashCode();
    return hash;
}

これには次の利点があります。

  • {1,2} のハッシュは {2,1} のハッシュと同じではありません
  • {1,1} のハッシュは {2,2} のハッシュと同じではありません

^など - 重み付けされていない合計、または xor ( ) などを使用する場合に一般的です。

于 2009-04-06T04:29:32.273 に答える
90
namespace System {
    public class Object {
        [MethodImpl(MethodImplOptions.InternalCall)]
        internal static extern int InternalGetHashCode(object obj);

        public virtual int GetHashCode() {
            return InternalGetHashCode(this);
        }
    }
}

InternalGetHashCodeは、次のように、CLR のObjectNative::GetHashCode関数にマップされます。

FCIMPL1(INT32, ObjectNative::GetHashCode, Object* obj) {  
    CONTRACTL  
    {  
        THROWS;  
        DISABLED(GC_NOTRIGGER);  
        INJECT_FAULT(FCThrow(kOutOfMemoryException););  
        MODE_COOPERATIVE;  
        SO_TOLERANT;  
    }  
    CONTRACTL_END;  

    VALIDATEOBJECTREF(obj);  

    DWORD idx = 0;  

    if (obj == 0)  
        return 0;  

    OBJECTREF objRef(obj);  

    HELPER_METHOD_FRAME_BEGIN_RET_1(objRef);        // Set up a frame  

    idx = GetHashCodeEx(OBJECTREFToObject(objRef));  

    HELPER_METHOD_FRAME_END();  

    return idx;  
}  
FCIMPLEND

GetHashCodeExの完全な実装はかなり大きいため、C++ ソース コードにリンクするだけの方が簡単です。

于 2009-04-06T03:43:04.400 に答える
7

ObjectGetHashCodeのメソッドのドキュメントには、「このメソッドのデフォルトの実装は、ハッシュ目的で一意のオブジェクト識別子として使用してはなりません」と記載されています。ValueTypeの場合は、「派生型の GetHashCode メソッドを呼び出す場合、戻り値はハッシュ テーブルのキーとして使用するのに適していない可能性があります」と書かれています。.

、、、、などの基本的なデータ型は、優れた GetHashCode メソッドを実装してbyteいます。たとえば、特定のニーズに適している場合と適していない場合があるメソッドを実装するなど、他のいくつかのクラスと構造があります。試してみて、それで十分かどうかを確認するだけです。shortintlongcharstringPointGetHashCode

各クラスまたは構造体のドキュメントは、デフォルトの実装をオーバーライドするかどうかを示します。オーバーライドしない場合は、独自の実装を使用する必要があります。メソッドを使用する必要がある場所で独自に作成するクラスまたは構造体についてGetHashCodeは、適切なメンバーを使用してハッシュ コードを計算する独自の実装を作成する必要があります。

于 2009-04-06T04:02:42.293 に答える
2

一般的に言えば、Equals をオーバーライドする場合は、GetHashCode をオーバーライドします。この理由は、両方がクラス/構造体の同等性を比較するために使用されるためです。

Equals は、Foo A、B をチェックするときに使用されます。

もし (A == B)

ポインターが一致する可能性が低いことがわかっているため、内部メンバーを比較できます。

Equals(obj o)
{
    if (o == null) return false;
    MyType Foo = o as MyType;
    if (Foo == null) return false;
    if (Foo.Prop1 != this.Prop1) return false;

    return Foo.Prop2 == this.Prop2;
}

GetHashCode は通常、ハッシュ テーブルで使用されます。クラスによって生成されるハッシュコードは、クラスが状態を与えるために常に同じである必要があります。

私は通常、

GetHashCode()
{
    int HashCode = this.GetType().ToString().GetHashCode();
    HashCode ^= this.Prop1.GetHashCode();
    etc.

    return HashCode;
}

ハッシュコードはオブジェクトの存続期間ごとに 1 回だけ計算する必要があると言う人もいますが、私はそれに同意しません (おそらく間違っています)。

オブジェクトによって提供されるデフォルトの実装を使用すると、クラスの 1 つに同じ参照がない限り、それらは互いに等しくなりません。Equals と GetHashCode をオーバーライドすることで、オブジェクト参照ではなく内部値に基づいて等しいことを報告できます。

于 2009-04-06T03:42:40.297 に答える