多くの C# 教科書では、オブジェクトの平等性の概念が強調されます。 C# の世界には 2 種類の同等性があることは誰もが知っています。 1 つは論理的等価性です。2 つのオブジェクトが論理的に同じ値を表す場合、それらは論理的等価性があると言われます。もう 1 つは参照の等価性です。2 つの参照が同じオブジェクト インスタンスを指している場合、それらは参照の等価性があると言われます。
ご存知のとおり、Object 型には Equals というインスタンス メソッドがあり、2 つのオブジェクトが等しいかどうかを判断するために使用できます。 Object's Equals のデフォルトの実装では、2 つのオブジェクトの参照が等しいかどうかを比較します。 Object 派生クラス ValueTpye は、2 つのオブジェクトの論理的同等性を比較する Equals メソッドをオーバーライドします。つまり、C# では、参照型の Equals の既定バージョンは参照の等価性に重点を置いているのに対し、値の型は論理的な等価性に重点を置いています。もちろん、これが常に要件を満たしているわけではありません。したがって、参照型の論理的等価性をより重視する場合は常に、Equals メソッドをオーバーライドする必要があります。
参照型の Equals メソッドをオーバーライドしてデフォルトの比較方法を変更する有名な例は、String クラスです。 string1.Equals(string2) のようなコードを記述する場合、2 つの参照 string1 と string2 が同じインスタンスを指しているかどうか (参照の等価性) を比較しているのではなく、string1 と string2 に含まれる文字が同一であるかどうか (論理的) を比較しています。平等)。
誤解1: Equalsメソッドとoperator==のデフォルト動作は同じです。
参照型の場合、 == 演算子がそれに対してオーバーロードされておらず、その親型が Equals メソッドをオーバーライドしない場合、参照型の Equals メソッドと演算子 == は同じデフォルト動作を持ちます。つまり、両方を比較します。オブジェクトの参照の等価性。ただし、値型の場合、これはまったく当てはまりません。カスタム値型の演算子 == をオーバーロードしない場合、myStruct1 == myStruct2 のようなコードを記述することはできません。そうしないと、値型には等価演算子オーバーロードの既定の実装がないため、コンパイル エラーが発生します。
誤解2: カスタムクラスのEqualsメソッドのデフォルト実装は自動的にoperator==メソッドを呼び出すか、operator==メソッドのデフォルト実装は自動的にEqualsメソッドを呼び出します。
特定の型は参照型であるため、その Equals メソッドのデフォルトの実装では、operator== メソッドが自動的に呼び出される、という話をよく聞きます。この発言はまったく不合理です。前述したように、参照型の Equals メソッドのデフォルトの実装は Object から提供されますが、値型のデフォルトの実装は、== 演算子を使用する場合でも、Object または TypeValue のオーバーロードされたバージョンを使用します。原則として、クラスの Equals メソッドをオーバーライドしない限り、クラスはその親クラスの実装を継承し、親クラスはサブタイプ演算子のオーバーロードを使用する機会がありません。同様に、クラスの == 演算子オーバーロードで Equals メソッドを呼び出さない限り、このメソッドは自動的には呼び出されません。
誤解3: 値型のデフォルトのEquals実装は、2つのオブジェクトを少しずつ比較します。
値型に対する Equals のデフォルトの実装は、メモリ内の 2 つのオブジェクトのビット表現を比較することであると考える人もいます。つまり、すべてのバイナリ ビットが等しい場合、2 つのオブジェクトが等しいことを意味します。これは正確ではありません。実数値型に対する Equals のデフォルトの実装では、値型のフィールドごとにフィールド型の Equals メソッドを呼び出すため、すべてのフィールドの Equals メソッドが true を返す場合にのみ、それらが等しくなります。例を見てみましょう:
class MyClass { public override bool Equals(object obj) { Console.WriteLine("MyClass的Equals方法被调用了。"); return true; } } struct MyStruct { public MyClass Filed; } class Program { staticvoid Main(string[] args) { MyStruct a; MyStruct b; a.Filed = new MyClass(); b.Filed = new MyClass(); Console.WriteLine(a.Equals(b)); } }
明らかに、a と b は完全に異なるバイナリ ビット表現を持っています。しかし、最終的な出力結果は次のようになります:
MyClass の Equals メソッドが呼び出されました。
True
これは、値型の既定の実装では、バイナリ ビットが一貫しているかどうかを比較するのではなく、フィールドの Equals メソッドを呼び出すことによって 2 つのオブジェクトが等しいかどうかを判断することを示しています。
誤解4: Equalsは非常に基本的でよく使われるメソッドなので、デフォルトの実装ではパフォーマンスに問題はありません。
参照型の場合、Equals のデフォルトの実装は非常に簡単です。2 つの参照が同じ型であるかどうか、および 2 つの参照が同じメモリを指しているかどうかを判断するだけです。したがって、性能には問題ありません。しかし、値型の場合、Equals のタスクはそれほど単純ではありません。 2 つのオブジェクトのすべてのフィールドを比較する必要があります。つまり、フィールド タイプの Equals をフィールドごとに呼び出す必要があります。 ValueType (値の型の Equals メソッドがデフォルトで実装されている) では、そのすべてのサブタイプにどのフィールドが含まれているかを知ることは不可能であるため、サブタイプ フィールドの Equals メソッドを呼び出すには、ValueType の Equals で以下を使用する必要があります。反射技術。お気づきかと思いますが、リフレクションはパフォーマンスに適した手法ではないため、値型の Equals メソッドは効率的ではありません。このため、Microsoft ではカスタム値の型に対して Equals メソッドをオーバーライドすることを推奨しています。
C# 初心者の間で Equals メソッドについてよくある誤解に関するその他の関連記事については、PHP 中国語 Web サイトに注目してください。