36

次のクラスと構造体の定義があり、それぞれをディクショナリ オブジェクトのキーとして使用したとします。

public class MyClass { }
public struct MyStruct { }

public Dictionary<MyClass, string> ClassDictionary;
public Dictionary<MyStruct, string> StructDictionary;

ClassDictionary = new Dictionary<MyClass, string>();
StructDictionary = new Dictionary<MyStruct, string>();

これが機能するのはなぜですか:

MyClass classA = new MyClass();
MyClass classB = new MyClass();
this.ClassDictionary.Add(classA, "Test");
this.ClassDictionary.Add(classB, "Test");

しかし、これは実行時にクラッシュします:

MyStruct structA = new MyStruct();
MyStruct structB = new MyStruct();
this.StructDictionary.Add(structA, "Test");
this.StructDictionary.Add(structB, "Test");

予想どおり、キーは既に存在すると言われていますが、構造体に対してのみです。クラスはそれを 2 つの別個のエントリとして扱います。参考値として保持しているデータと関係があると思いますが、その理由をもう少し詳しく説明していただきたいです。

4

5 に答える 5

16
  1. new object() == new object()参照型には参照の等価性があり、2 つのインスタンスは同じ参照ではないため、falseです。

  2. new int() == new int()値型には値が等しく、2 つの既定の整数の値は同じ値であるため、trueです。構造体にインクリメンタルな参照型またはデフォルト値がある場合、構造体のデフォルトも等しくない可能性があることに注意してください。

デフォルトの等価動作が気に入らない場合は、構造体とクラスの両方のEqualsandメソッドと等価演算子をオーバーライドできます。GetHashCode

また、辞書の値を安全に設定する方法が必要な場合はdictionary[key] = value;、新しい値を追加するか、古い値を同じキーで更新することができます。

アップデート

@ 280Z28は、この回答が誤解を招く可能性があることを指摘するコメントを投稿しました。私はこれを認識し、対処したいと考えています。次のことを知っておくことが重要です。

  1. デフォルトでは、内部で参照型のEquals(object obj)メソッドと==演算子が呼び出さobject.ReferenceEquals(this, obj)れます。

  2. 動作を伝播するには、最終的に演算子とインスタンス メソッドをオーバーライドする必要があります。(たとえば、ネストされた呼び出しが明示的に追加されない限り、Equals実装を変更しても実装には影響しません)。==

  3. デフォルトの .NET ジェネリック コレクションはすべて、IEqualityComparer<T>(インスタンス メソッドではなく) 実装を使用して等価性を判断します。はIEqualityComparer<T>、その実装でインスタンス メソッドを呼び出す可能性があります (そして頻繁に呼び出します) が、これは当てにできるものではありません。IEqualityComparer<T>使用される実装には、次の 2 つのソースが考えられます。

    1. コンストラクターで明示的に指定できます。

    2. から自動的に取得さEqualityComparer<T>.Defaultれます (デフォルト)。によってアクセスされるデフォルトIEqualityComparer<T>をグローバルに設定する場合は、 Undefault (GitHub で)EqualityComparer<T>.Defaultを使用できます。

于 2013-05-09T22:30:08.040 に答える
7

一般に、ディクショナリ キーには、可変クラス オブジェクトのID、不変クラス オブジェクトの値、または構造体の値の 3 つの適切なタイプがあります。公開されたパブリック フィールドを持つ構造体は、そうでない構造体と同様にディクショナリ キーとしての使用に適していることに注意してください。これは、ディクショナリ内に格納されている構造体のコピーが変更される唯一の方法は、構造体が読み取られ、変更され、書き込まれる場合であるためです。戻る。対照的に、公開された変更可能なプロパティを持つクラスは、一般に、オブジェクトの内容ではなく、オブジェクトのアイデンティティをキーにしたい場合を除いて、お粗末な辞書キーを作成します。

型をディクショナリ キーとして使用するには、そのEqualsおよびメソッドが目的のセマンティクスを持っているか、目的のセマンティクスを実装するGetHashCodeのコンストラクタをDictionary指定する必要があります。IEqualityComparer<T>クラスのデフォルトEqualsGetHashCodeメソッドは、オブジェクト ID をキーにします (変更可能なオブジェクトの ID をキーにしたい場合に役立ちますが、それ以外の場合はあまり役に立ちません)。値型のデフォルトのEqualsおよびメソッドは、通常、メンバーのおよびメソッドにGetHashCode基づいていますが、いくつかの問題があります。EqualsGetHashCode

  • 構造体でデフォルトのメソッドを使用するコードは、多くの場合、カスタム作成のメソッドを使用するコードよりもはるかに遅くなります (場合によっては 1 桁)。

  • プリミティブ型のみを含む構造体は、他の型も含む構造体とは異なる方法で浮動小数点比較を実行します。たとえば、値 posZero=(1.0/(1.0/0.0)) と negZero=(-1.0/(1.0/0.0)) はどちらも等しいと比較されますが、プリミティブのみを含む構造体に格納されている場合、それらは等しくないと比較されます。1.0/posZero を計算すると正の無限大が得られ、1.0/negZero を計算すると負の無限大が得られるため、値が等しいと考えても意味的には同じではないことに注意してください。

パフォーマンスがそれほど重要でない場合は、単純な構造体を定義して [適切なパブリック フィールドを宣言するだけ]、それを Dictionary にスローして、値ベースのキーとして動作させることができます。それほど効率的ではありませんが、うまくいきます。通常、辞書は不変クラス オブジェクトをいくらか効率的に処理しますが、不変クラス オブジェクトを定義して使用することは、「単純な古いデータ構造」を定義して使用するよりも手間がかかる場合があります。

于 2013-05-10T16:27:59.990 に答える
4

astructは a のように参照されないためclassです。

構造体は、クラスのように参照を解析する代わりに、それ自体のコピーを作成します。

したがって、これを試してみると:

var a =  new MyStruct(){Prop = "Test"};
var b =  new MyStruct(){Prop = "Test"};

Console.WriteLine(a.Equals(b));

// true を出力します

クラスで同じことを行う場合:

var a =  new MyClass(){Prop = "Test"};
var b =  new MyClass(){Prop = "Test"};

Console.WriteLine(a.Equals(b));

// false を出力します! (比較機能を実装していないと仮定します)参照が同じではないため

于 2013-05-09T22:30:11.163 に答える
1

参照タイプのキー (クラス) は個別の参照を指します。値型のキー (構造体) は同一の値を指しています。それが例外を受け取る理由だと思います。

于 2013-05-09T22:29:53.493 に答える