C#.NETでのオブジェクトの比較

C#.NETは、オブジェクト、クラスのインスタンス、および構造の両方を比較する多くの方法を提供します。 これらの方法を合理化し、それらの適切な使用と実装を理解しなければ(再定義の可能性がある場合)、ポリッジは必然的に頭の中で形成されます。



そのため、System.Objectクラスは次のメソッドを提供します。



もちろん、もちろん:

 public static bool operator == (Foo left, Foo right);
      
      





IEquatable、IStructuralEquatableを継承することもできます。



ReferenceEquals


ReferenceEqualsメソッドは、2つのリンクを比較します。 オブジェクトへの参照が同一の場合、trueを返します。 これは、このメソッドがインスタンスの等価性ではなく、同一性をチェックすることを意味します。 重要な型のインスタンスをこのメソッドに渡すと(同じインスタンスを渡しても)、常にfalseを返します。 これは、転送中に重要なタイプのパッケージングが行われ、それらへのリンクが異なるために発生します。

ここで、この方法による2行の比較についても言及したいと思います。 例:

 class Program { static void Main(string[] args) { string a = "Hello"; string b = "Hello"; if(object.ReferenceEquals(a,b)) Console.WriteLine("Same objects"); else Console.WriteLine("Not the same objects"); Console.ReadLine(); } }
      
      





このようなプログラムは、「同じオブジェクト」を簡単に出力できます。 心配しないでください、これは文字列インターンによるものです。 しかし、これは完全に異なる話であり、これについての話はありません。



public static bool Equals(オブジェクトobjA、オブジェクトobjB)


最初に、このメソッドはインスタンスのIDをチェックし、オブジェクトが同一でない場合、nullをチェックし、再定義されたインスタンスメソッドEqualsを比較する責任を委任します。



パブリック仮想ブールEquals(オブジェクトobj)


デフォルトでは、このメソッドはReferenceEqualsとまったく同じように動作します。 ただし、意味のある型については、System.ValueTypeで次のようにオーバーライドされます。

 public override bool Equals(object obj) { if (obj == null) { return false; } RuntimeType runtimeType = (RuntimeType)base.GetType(); RuntimeType left = (RuntimeType)obj.GetType(); if (left != runtimeType) { return false; } if (ValueType.CanCompareBits(this)) { return ValueType.FastEqualsCheck(this, obj); } FieldInfo[] fields = runtimeType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); for (int i = 0; i < fields.Length; i++) { object obj2 = ((RtFieldInfo)fields[i]).InternalGetValue(this, false); object obj3 = ((RtFieldInfo)fields[i]).InternalGetValue(obj, false); if (obj2 == null) { if (obj3 != null) { return false; } } else { if (!obj2.Equals(obj3)) { return false; } } } return true; }
      
      





大規模なセットでこのような実装を使用することは誰にも禁止されています。 BCL開発者は、事前にこれらのフィールドについて何も知らなくても、リフレクションを使用してフィールドごとに重要なタイプのインスタンスを定義および比較する重要なタイプを知ることができません。 もちろん、これは非常に生産的な比較方法ではありません。 したがって、コンパイル段階で既知の重要な型を使用する場合、このメソッドをオーバーライドする必要があります。開発した2つのオブジェクトを比較する方法を知っている人は誰よりも優れているからです。 参照型の場合、重要な型の方法で2つのインスタンスを比較する必要がないため、このメソッドのオーバーライドはオプションです。

このメソッドの有能な再定義の例を見て、すぐにIEquatableを実装しましょう。

 class Vehicle:IEquatable<Vehicle> { protected int speed; public int Speed { get { return this.speed; } set { this.speed = value; } } protected string name; public string Name { get { return this.name; } set { this.name = value; } } public Vehicle(){} public Vehicle(int speed, string name) { this.speed = speed; this.name = name; } public override bool Equals(object other) { //     . //    null  other,  other.GetType()   //NullReferenceException. if (other == null) return false; //        ,    . if (object.ReferenceEquals(this, other)) return true; //          ,    // Vehicle tmp = other as Vehicle; if(tmp==null) return false; //   ,     tmp. if (this.GetType() != other.GetType()) return false; return this.Equals(other as Vehicle); } public bool Equals(Vehicle other) { if (other == null) return false; //    . //  ,            - // . if (object.ReferenceEquals(this, other)) return true; //   ,         , //           . if (this.GetType() != other.GetType()) return false; if (string.Compare(this.Name, other.Name, StringComparison.CurrentCulture) == 0 && this.speed.Equals(other.speed)) return true; else return false; } }
      
      





仮想メソッドの再定義における階層の最上部に関するコメントは、理由のために作成されます。 Vehicleクラスの継承者(たとえば、Bike)を作成します。これには、オーバーライドされた仮想Equalsメソッドも含まれます。このメソッドでは、GetTypeによる型比較は行われませんが、 Bike tmp = other as Bike; if(tmp!=null) this.Equals(tmp);



にキャストしようとしBike tmp = other as Bike; if(tmp!=null) this.Equals(tmp);



Bike tmp = other as Bike; if(tmp!=null) this.Equals(tmp);



この場合、次のコードが問題を引き起こす可能性があります。

 Vehicle vehicle = new Vehicle(); Bike bike = new Bike(); object vehicleObj = vehicle; object bikeObject = bike; bike.Equals(vehicleObj); //      .  ,   //    
      
      







public static bool operator ==(Foo left、Foo right)


意味のある型については、仮想Equals()と同様に、常に再定義する必要があります。 参照型の場合、デフォルトでは、参照型の==から、参照振る舞いはReferenceEquals()メソッドと同様に予期されるため、オーバーライドしない方が良いです。 したがって、ここではすべてが簡単です。



IStructuralEquatable


IStructuralEquatableは、IEqualityComparerインターフェイスと連携します。 IStructuralEquatableインターフェイスは、System.ArrayやSystem.Tupleなどのクラスによって実装されます。 Bill Wagnerによれば、IStructuralEqualityは、型が重要な型のセマンティクスを実装するより大きなオブジェクトを構成できることを宣言しており、自分で実装する必要はないと考えています。 しかし、その実装で難しいことは何ですか? System.Arrayでの実装を見てください:

 bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer) { if (other == null) { return false; } if (object.ReferenceEquals(this, other)) { return true; } Array array = other as Array; if (array == null || array.Length != this.Length) { return false; } for (int i = 0; i < array.Length; i++) { object value = this.GetValue(i); object value2 = array.GetValue(i); if (!comparer.Equals(value, value2)) { return false; } } return true; }
      
      





実際には、オブジェクトのIDが最初にチェックされ、次に同じタイプにキャストされ、長さが比較されます。 長さが等しい場合、要素ごとの比較は、この比較の責任をEqualsインターフェイスメソッド(IEqualityComparer)に委任することから始まります。



C#.NETでオブジェクトを比較することについて本質的に言えることはこれだけです。しかし、もう1つ小さな、しかし重要な詳細が残っています:GetHashCode()メソッドです。



public virtual int GetHashCode()


一般に、このメソッドの標準実装は一意の識別子ジェネレーターのように動作します。 このアプローチの欠点は、同じセマンティックオブジェクトが異なるハッシュ値を返す可能性があることです。 リヒターは、標準実装のパフォーマンスも低いと訴えています。 このメソッドの有能な実装には非常に問題があります。 ハッシュを迅速に計算し、十分に大きなセットで繰り返しが発生しないように、結果として大きな広がりを持つ必要があります。 実際、ほとんどの場合、GetHashCode()の実装は非常に簡単です。 どこでも、「ビット単位のOR」または「排他的なOR」のシフトが行われます。 Richter自身が、int型の2つのフィールドを持つ構造の例を示しています。 GetHashCode()次のようなものを実装することを提案します。

 internal sealed class Point { private int a; private int b; public override int GetHashCode() { return a ^ b; } }
      
      





また、System.CharでGetHasCode()を再定義する方法は次のとおりです。

 public override int GetHashCode() { return (int)this | (int)this << 16; }
      
      





多くの例を挙げることができ、ヒューリスティックインジケーターは、除外などを行うシフトのほぼすべての場所で使用されます。



記事を書くとき、よく知られているソースが使用されました:

J.リヒター、C#経由のCLR

B.ワグナー効果C#

また、インターネットでの経験と情報源を使用しましたが、これはあまり意味がありません。





ここでの翻訳



All Articles