web-dev-qa-db-ja.com

クラスと構造体を辞書キーとして使用する

次のクラスと構造の定義があり、それぞれを辞書オブジェクトのキーとして使用するとします。

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つの個別のエントリとして扱います。参照と値の関係として保持されているデータと関係があると思いますが、その理由についてもっと詳しく説明したいと思います。

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

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

デフォルトの等式の動作が気に入らない場合は、Equalsおよび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>_によってアクセスされるデフォルトの_EqualityComparer<T>.Default_をグローバルに設定する場合は、 ndefault (GitHubで)を使用できます。

13
smartcaveman

_Dictionary<TKey, TValue>_は、キーの比較に_IEqualityComparer<TKey>_を使用します。ディクショナリの作成時に比較演算子を明示的に指定しない場合、 _EqualityComparer<TKey>.Default_ が使用されます。

MyClassMyStructも_IEquatable<T>_を実装していないため、デフォルトの等値比較子はインスタンスを比較するために_Object.Equals_と_Object.GetHashCode_を呼び出します。 MyClassObjectから派生しているため、実装は比較に参照の等価性を使用します。一方、MyStruct_System.ValueType_ (すべての構造体の基本クラス)から派生しているため、 _ValueType.Equals_ を使用します。インスタンスを比較するため。このメソッドのドキュメントには、次のことが記載されています。

ValueType.Equals(Object)メソッドはObject.Equals(Object)をオーバーライドし、.NET Frameworkのすべての値型に対して値の等価性のデフォルト実装を提供します。

現在のインスタンスとobjのフィールドのいずれも参照型でない場合、Equalsメソッドはメモリ内の2つのオブジェクトのバイトごとの比較を実行します。それ以外の場合、リフレクションを使用して、objの対応するフィールドとこのインスタンスを比較します。

「同じキーを持つ要素が[辞書]に既に存在する場合」 _IDictionary<TKey, TValue>.Add_ArgumentExceptionをスローするため、例外が発生します。構造体を使用する場合、_ValueType.Equals_によるバイトごとの比較により、両方の呼び出しが同じキーを追加しようとします。

54
Sam Harwell

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

型を辞書キーとして使用するには、そのEqualsメソッドとGetHashCodeメソッドに目的のセマンティクスが必要です。または、Dictionaryのコンストラクターを指定する必要がありますIEqualityComparer<T>目的のセマンティクスを実装します。クラスのデフォルトのEqualsおよびGetHashCodeメソッドは、オブジェクトIDにキーを設定します(可変オブジェクトのIDにキーを設定したい場合に便利です。それ以外ではあまり役に立ちません)。値型のデフォルトのEqualsおよびGetHashCodeメソッドは、通常、メンバーのEqualsおよびGetHashCodeメソッドに依存しますが、いくつかのしわがあります。

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

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

パフォーマンスがそれほど重要でない場合は、単純な構造体を定義し(適切なパブリックフィールドを単に宣言する)、それをディクショナリにスローし、値ベースのキーとして動作させることができます。それほど効率的ではありませんが、機能します。通常、辞書は不変クラスオブジェクトをより効率的に処理しますが、不変クラスオブジェクトの定義と使用は、「プレーンな古いデータ構造」の定義と使用よりも作業が多い場合があります。

7
supercat

structclassのように参照されません。

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

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

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を出力します! (比較機能を実装していないと仮定)参照が同じではないため

4
Jens Kloster

参照タイプキー(クラス)は個別の参照を指します。 value type key(struct)は同一の値を指します。それがあなたが例外を得る理由だと思います。

1
Mathieu Guindon