(object)0 ==(object)0が((object)0).Equals((object)0)と異なるのはなぜですか?


117

次の表現が異なるのはなぜですか?

[1]  (object)0 == (object)0 //false
[2]  ((object)0).Equals((object)0) // true

実際には、おそらく.NETランタイムがbox整数になり、代わりに参照の比較を開始するため、[1]を完全に理解できます。しかし、なぜ[2]が異なるのですか?


36
short myShort = 0; int myInt = 0; Console.WriteLine("{0}{1}{2}", myShort.Equals(myInt), myInt.Equals(myShort), myInt == myShort); さて、この質問に対する答えを理解したところで、次の結果を予測して理解を確認してください。あなたの予測は正しかったですか?そうでない場合、違いを説明できますか?
Eric Lippert、

1
@Star、推奨される読み取りについては、msdn.microsoft.com / en-us / library / vstudio /…を参照してください。int16別名shortEqualsメソッドで使用可能なオーバーロードについては、msdn.microsoft.com / en-us / library / ms173105.aspxを参照してください。。Eric Lippertのパズルを台無しにしたくはありませんが、これらのページを読んだら、簡単に理解できるはずです。
Sam Skuce 2013

2
これはJavaの質問だと思いました。少なくともEqualsで「E」を見る前に。
seteropere 2013

4
@seteropere Javaは実際には異なります。Javaのオートボクシングはオブジェクトをキャッシュするため((Integer)0)==((Integer)0)、trueと評価されます。
ジュールズ

1
あなたも試すことができIFormattable x = 0; bool test = (object)x == (object)x;ます。構造体が既にボックス内にある場合、新しいボクシングは実行されません。
Jeppe Stig Nielsen

回答:


151

呼び出しの動作が異なる理由は、非常に異なるメソッドにバインドしているためです。

==場合は、静的参照の等価演算子にバインドします。2つの独立した箱入りがありますint値が作成されるため、それらは同じ参照ではありません。

2番目のケースでは、インスタンスメソッドにバインドしますObject.Equals。これは仮想メソッドであり、フィルター処理されInt32.Equalsて、ボックス化された整数をチェックします。両方の整数値が0であるため、等しい


==場合は呼び出しませんObject.ReferenceEqualsceq参照比較を実行するIL命令を生成するだけです。
Sam Harwell

8
@ 280Z28は、コンパイラがインライン化したからというだけではありませんか?
markmnl 2013

@ 280Z28それで?同様のケースは、それらのBoolean.ToStringメソッドに、公に公開されたBoolean.TrueStringおよびBoolean.FalseStringを返すのではなく、関数内にハードコードされた文字列が含まれているようです。それは無関係です。ポイントは、(とにかく)オブジェクト==と同じことをするということReferenceEqualsです。よく使われる関数の不要な内部関数呼び出しを避けるために、MS側で内部最適化を行うだけです。
Nyerguds 2013

6
C#言語仕様のパラグラフ7.10.6には、次のように記載されています。定義済みの参照型の等値演算子は次のとおりbool operator ==(object x, object y); bool operator !=(object x, object y);です。System.Object.ReferenceEquals結果を決定するためにこのメソッドを使用することは必須ではありません。@markmnlへ:いいえ、C#コンパイラーはインライン化しません。これはジッターが時々行うことです(ただし、この場合はそうではありません)。したがって、280Z28は正しいReferenceEquals方法です。この方法は実際には使用されていません。
Jeppe Stig Nielsen

@JaredPar:それが言語が実際に振る舞う方法ではないので、仕様がそれを言うのは興味深いです。上記のように定義された演算子と変数Cat Whiskers; Dog Fido; IDog Fred;(関連しないインターフェースICatIDog関連しないクラスCat:ICatとの場合Dog:IDog)を比較するWhiskers==Fidoと、比較Whiskers==34は合法になります(1つ目はWhiskersとFidoが両方ともnullの場合にのみtrueになり、2つ目は決してtrueになりません) )。実際、C#コンパイラは両方を拒否します。 封印Whiskers==Fred;されている場合Catは禁止されますが、封印されていない場合は許可されます。
スーパーキャット2013

26

int値0(またはその他の値タイプ)をobjectにキャストすると、値はボックス化されます。へのキャストごとにobject異なるボックス(つまり、異なるオブジェクトインスタンス)が生成されます。の==演算子object型は参照比較を実行します。したがって、左側と右側は同じインスタンスではないため、falseを返します。

一方、Equals仮想メソッドであるを使用すると、実際のボックス化された型の実装が使用されInt32.Equalsます。つまり、両方のオブジェクトが同じ値を持つため、trueを返します。


18

==オペレータは、静的であること、仮想ではありません。それは正確なコードを実行しますobjectクラスが定義( `objectはオペランドのコンパイル時の型)を実行し、どちらのオブジェクトのランタイム型にも関係なく参照比較を行います。

Equalsこの方法は、仮想インスタンスメソッドです。objectクラスのコードではなく、(最初の)オブジェクトの実際の実行時の型で定義されたコードを実行します。この場合、オブジェクトはであるintため、値の比較が実行されます。これは、int型がそのEqualsメソッドに対して定義するものであるためです。


==トークンは、実際には2つの演算子、オーバーロードとされていないの一つであるのいずれかを表します。2番目の演算子の動作は、(object、object)のオーバーロードの動作とは大きく異なります。
スーパーキャット2013

13

Equals()この方法は、仮想です。
したがって、コールサイトがにキャストされた場合でも、常に具体的な実装を呼び出しますobjectintオーバーライドEquals()して値で比較するため、値を比較できます。


10

== 使用する: Object.ReferenceEquals

Object.Equals 値を比較します。

object.ReferenceEqualsこの方法は、参照を比較します。オブジェクトを割り当てると、メモリヒープ上のオブジェクトのデータに加えて、そのメモリの場所を示す値を含む参照を受け取ります。

このobject.Equalsメソッドは、オブジェクトの内容を比較します。まず、object.ReferenceEqualsと同様に、参照が等しいかどうかをチェックします。ただし、派生したEqualsメソッドを呼び出して、同等性をさらにテストします。これを見てください:

   System.Object a = new System.Object();
System.Object b = a;
System.Object.ReferenceEquals(a, b);  //returns true

Object.ReferenceEqualsC#の使用方法と同様に振る舞う==そのオペランドに演算子を、(使用して表現されるC#の基準等価演算子演算子==過負荷が定義されていないオペランドタイプには)を呼び出すのではなく、特別な命令を使用しますReferenceEquals。さらに、Object.ReferenceEquals両方がnullである場合にのみ一致する可能性のあるオペランドを受け入れ、型強制される必要がありObject、したがって何にも一致しない可能性のあるオペランドを受け入れますが、の参照等価バージョンは==そのような使用法のコンパイルを拒否します。
スーパーキャット2013

9

C#演算子はトークンを使用します ==を、静的にオーバーロード可能な比較演算子と非オーバーロード可能な参照比較演算子の2つの異なる演算子を表し。出会った時==、オペランドのタイプに適用可能な任意の平等テスト過負荷が存在するかどうかを確認するために、トークン、それを最初にチェックします。もしそうなら、それはそのオーバーロードを呼び出します。それ以外の場合は、型が参照比較演算子に適用可能かどうかをチェックします。その場合、その演算子を使用します。どちらの演算子もオペランドタイプに適用できない場合、コンパイルは失敗します。

コード(Object)0は単にInt32to Object:をアップキャストするだけではありません。Int32すべての値の型と同様に、実際には2つの型を表します。1つは値と格納場所(リテラル0など)を記述しますが、何からも派生せず、1つは記述しますヒープオブジェクトとから派生Object; 後者のタイプのみがにアップキャストされる可能性があるためObject、コンパイラはその後者のタイプの新しいヒープオブジェクトを作成する必要があります。を呼び出すたびに(Object)0新しいヒープオブジェクトが作成されるため、への2つのオペランド==は異なるオブジェクトであり、それぞれが独立して、Int32値0をます。

クラスにObjectは、equals演算子に対して定義された使用可能なオーバーロードはありません。その結果、コンパイラーは多重定義された等価テスト演算子を使用できなくなり、参照等価テストの使用にフォールバックします。2つのオペランド==は別個のオブジェクトを参照するため、を報告しfalseます。2番目の比較が成功するのは、1つのヒープオブジェクトインスタンスInt32に、それが他のインスタンスと等しいかどうかを確認するためです。そのインスタンスは、別の個別のインスタンスと等しいとはどういう意味かを知っているため、答えることができますtrue


さらに、0コードにリテラルを書き込むたびに、それがそのためにヒープ内にintオブジェクトを作成すると思います。これは、1つのグローバル静的ゼロ値への一意の参照ではありません(新しい文字列を初期化するためだけに新しい空の文字列オブジェクトを作成しないようにString.Emptyを作成した方法など)。したがって0.ReferenceEquals(0)、両方の0が新しく作成されたInt32オブジェクト。
Nyerguds 2013

1
@Nyerguds、私はあなたが言ったすべてが間違っていると確信しています。int、ヒープ、履歴、グローバル静的など 0.ReferenceEquals(0)については、コンパイル時定数でメソッドを呼び出そうとしているので失敗します。ぶら下げるオブジェクトはありません。ボックス化されていないintは、スタックに格納された構造体です。でもint i = 0; i.ReferenceEquals(...)動作しません。からSystem.Int32継承しないためObject
Andrew Backer

@AndrewBacker、System.Int32is a struct、a structis System.ValueType、それ自体が継承しSystem.Objectます。そのためのToString()方法とEquals方法がありますSystem.Int32
セバスチャン

1
それにもかかわらず、Nyergudsは、Int32がヒープ上に作成されると述べるのは間違っていますが、そうではありません。
セバスチャン

@SebastianGodelet、私はその内部を無視しています。System.Int32自体がこれらのメソッドを実装します。GetType()はのexternでありObject、私がずっと前に心配しなくなったのはそのためです。遠くに行く必要は決してありませんでした。私の知る限り、CLRは2つのタイプを異なる方法で特別に処理します。それは単なる継承ではありません。それisにもかかわらずデータの2つのタイプのいずれか。文字列のインターンを無視する空の文字列についての奇妙さを含めて、私はだれでもそのコメントを読んで、それほど遠くに行かせたくありませんでした。
Andrew Backer

3

どちらのチェックも異なります。最初のものは同一性をチェックし、2番目のものは等価性をチェックしますます。一般に、同じオブジェクトを参照している場合、2つの用語は同一です。これは、それらが等しいことを意味します。それらの値が同じであれば、2つの項は等しいです。

プログラミングの観点から、アイデンティティは通常、参照の等価性によって測定されます。両方の用語へのポインタが等しい(!)場合、それらが指すオブジェクトはまったく同じです。ただし、ポインターが異なる場合でも、ポインターが指しているオブジェクトの値は同じである可能性があります。C#では、静的Object.ReferenceEqualsメンバーを使用して同一性をチェックしながら、静的メンバーを使用してIDをチェックできますObject.Equals。2つの整数をオブジェクトにキャストしているため( "ボクシング"と呼ばれます)、の演算子は最初のチェック==object実行します。これはデフォルトでマッピングされObject.ReferenceEquals、IDをチェックします。非静的Equalsメンバーを明示的に呼び出すと、動的ディスパッチによりが呼び出されInt32.Equals、等価性がチェックされます。

両方の概念は似ていますが、同じではありません。最初はわかりにくいかもしれませんが、小さな違いは非常に重要です。「アリス」と「ボブ」という2人を想像してみてください。彼らは両方とも黄色い家に住んでいます。アリスとボブは家の色だけが異なる地区に住んでいるという仮定に基づいて、両方が異なる黄色い家に住んでいる可能性があります。両方の家を比較すると、どちらも黄色なので、まったく同じであることがわかります。しかし、彼らは同じ家を共有していないので、家は同じですが同一ではありませ。アイデンティティは、彼らが同じ家に住んでいることを意味します。

:一部の言語では===、IDを確認するための演算子を定義しています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.