C#のプリミティブの==とEquals()の違いは何ですか?


180

このコードを考えてみましょう:

int age = 25;
short newAge = 25;
Console.WriteLine(age == newAge);  //true
Console.WriteLine(newAge.Equals(age)); //false
Console.ReadLine();

intshortはどちらもプリミティブ型ですが、との比較==はtrue を返し、との比較Equalsはfalse を返します。

どうして?


9
@OrangeDog質問について考え、その後、近くに投票してください

4
これには明らかな逆の試みが欠けています:Console.WriteLine(age.Equals(newAge));
ANeves 2014年

3
複製はこの動作を説明していません。それEquals()は一般的なことです。
SLaks 2014年

37
私は数日前にCoverityブログでこの正確な質問に回答しました。blog.coverity.com/2014/01/13/inconsistent-equality
Eric Lippert

5
@CodesInChaos:仕様では実際には「プリミティブ型」という用語を2回使用していますが、これを定義することはありません。つまり、プリミティブ型は組み込みの値型ですが、これが明らかにされることはありません。Madsには、この用語が仕様から取り外されることをお勧めします。この用語を削除するよりも混乱が生じるようです。
Eric Lippert、2014年

回答:


262

短い答え:

平等は複雑です。

詳細な回答:

プリミティブタイプはベースobject.Equals(object)をオーバーライドし、ボックス化されたタイプと値objectが同じ場合はtrueを返します。(null許容型でも機能することに注意してください。nullでないnull許容型は常に、基になる型のインスタンスにボックス化されます。)

以来newAgeshort、そのEquals(object)あなたが箱入り渡す場合メソッドはtrueを返します短い同じ値を持つが。あなたは箱入りを渡しているintので、falseを返します。

対照的に、==演算子は2つintのs(またはshortsまたはlongs)を取ると定義されます。and
と一緒に呼び出すと、コンパイラは暗黙的にto を変換し、結果のsを値で比較します。intshortshortintint

それを機能させる他の方法

プリミティブ型にEquals()は、同じ型を受け入れる独自のメソッドもあります。
を記述したage.Equals(newAge)場合、コンパイラーはint.Equals(int)最適なオーバーロードとして選択し、暗黙的にに変換shortintます。trueこのメソッドはintsを直接比較するだけなので、を返します。

shortにもshort.Equals(short)メソッドintがありますが、暗黙的にに変換できないためshort、それを呼び出すことはありません。

キャストでこのメソッドを呼び出すように強制できます。

Console.WriteLine(newAge.Equals((short)age)); // true

これはshort.Equals(short)、ボクシングなしで直接呼び出されます。ageが32767より大きい場合、オーバーフロー例外がスローされます。

short.Equals(object)オーバーロードを呼び出すこともできますが、ボックス化されたオブジェクトを明示的に渡して、同じタイプを取得します。

Console.WriteLine(newAge.Equals((object)(short)age)); // true

以前の代替案と同様に、これがに収まらない場合、オーバーフローがスローされますshort。以前のソリューションとは異なり、それshortはオブジェクトにボックス化し、時間とメモリを浪費します。

ソースコード:

Equals()実際のソースコードからの両方のメソッドを次に示します。

    public override bool Equals(Object obj) {
        if (!(obj is Int16)) {
            return false;
        }
        return m_value == ((Int16)obj).m_value;
    }

    public bool Equals(Int16 obj)
    {
        return m_value == obj;
    }

参考文献:

参照してくださいエリックリッペルトを


3
@SLaksを呼び出すとlong == intint暗黙的にlong右に変換されますか?
SelmanGenç2014年

1
そして、はい、私は実際にそれを試さずにそれらすべてを書きました。
SLaks 2014年

1
質問のコードで、1がに変わるint age = 25;const int age = 25;、結果が変わることに注意してください。その場合、からintへの暗黙の変換shortが存在するためです。暗黙的な定数式変換を参照してください。
Jeppe Stig Nielsen 2014

2
@SLakはい ここで答えをまだ知らないカジュアルなユーザーは、前者としてそれを読むと
思います

2
@レイチェル:それは真実ではないことを除いて。デフォルトの ==演算子は、参照によって参照タイプを比較します。値の型、およびオーバーロードする型の==場合は、そうではありません。
SLaks 2014年

55

オーバーロードがないため、それはshort.Equalsを受け入れますint。したがって、これは次のように呼ばれます。

public override bool Equals(object obj)
{
    return obj is short && this == (short)obj;
}

objshort..ではないため、これは誤りです。


12

あなたが通過するときintshort君が通過等しいですobject

ここに画像の説明を入力してください したがって、この擬似コードは次のように実行されます。

return obj is short && this == (short)obj;


10

==等しい条件のチェックに使用されます。これは演算子(ブール演算子)と見なすことができ、2つの事柄を比較するだけです。ここでは、型キャストが行われ、Equals等しい条件のチェックにも使用されるため、データ型は重要ではありません。ただし、この場合、データ型は同じである必要があります。N Equalsは演算子ではなくメソッドです。

以下はあなたが提供したものから取った小さな例であり、これは簡単に違いを明らかにします。

int x=1;
short y=1;
x==y;//true
y.Equals(x);//false

上記の例では、XとYは同じ値、つまり1を持ち、を使用==するとtrueを返します。これは、の場合==、short型がコンパイラによってintに変換され、結果が返されるためです。

を使用するEqualsと、比較は行われますが、型キャストはコンパイラーによって行われないため、falseが返されます。

みんな、私が間違っていたら教えてください。


6

メソッドまたは演算子の引数が必要な型ではない多くのコンテキストでは、C#コンパイラは暗黙的な型変換を実行しようとします。コンパイラーが暗黙の変換を追加することによってすべての引数が演算子とメソッドを満たすことができる場合、一部のケース(特に等価テストで!)は驚くべき結果になるかもしれませんが、文句なしにそれを行います。

さらに、各値タイプ、intまたはshort実際には、ある種類の値とある種類のオブジェクト(*)の両方が実際に記述されています。暗黙的な変換は、値を他の種類の値に変換するため、および任意の種類の値を対応する種類のオブジェクトに変換するために存在しますが、異なる種類のオブジェクトは暗黙的に相互に変換できません。

==演算子を使用してa shortとを比較すると、intshort暗黙的にに変換されますint。その数値と同等であった場合intintそれが変換されたには等しくなりint、それが比較されます。ただし、Equalsshortでメソッドを使用してと比較しようとした場合、メソッドintのオーバーロードを満たす暗黙的な変換は、にEquals対応するオブジェクト型への変換のみintです。ときshort、それは渡されたオブジェクトと一致するかどうか尋ねている、それは問題のオブジェクトであることを観察するintのではなくshort、したがって、それはおそらく同じことができないと結論付けています。

一般に、コンパイラーはそれについて文句を言わないでしょうが、同じタイプでないものを比較することは避けなければなりません。共通のフォームへの変換によって同じ結果が得られるかどうかに関心がある場合は、そのような変換を明示的に実行する必要があります。たとえば、

int i = 16777217;
float f = 16777216.0f;

Console.WriteLine("{0}", i==f);

1を比較したい場合があります3つの方法がありますintにはfloat。知りたいかもしれません:

  1. に最も近い可能なfloat値はintfloat
  2. floatマッチの整数部分はint
  3. int行いfloat、同じ数値を表します。

intandをfloat直接比較しようとすると、コンパイルされたコードが最初の質問に答えます。しかし、それがプログラマの意図したものであるかどうかは、明白ではありません。比較をに変更すると(float)i == f、最初の意味が意図されていたことが明確になるか(double)i == (double)f、コードが3番目の質問に答えるようになります(そして意図されていたことがそれが明確になります)。

(*)C#仕様がタイプの値をタイプSystem.Int32のオブジェクトと見なす場合でもSystem.Int32、そのようなビューは、コードが値とオブジェクトを異なるユニバースに存在すると見なすプラットフォームで実行するという要件と矛盾します。さらに、Tが参照型でありxTである場合、型の参照はTを参照できる必要がありますx。したがって、v型の変数がをInt32保持している場合、型Objectの参照はObject参照vまたはその内容を保持できる必要があります。実際、型の参照は、Objectからコピーされたデータを保持するオブジェクトを指すことができますvが、vそれ自体やその内容を指すことはできません。それはどちらもvまた、その内容も実際にはありませんObject


1
the only implicit conversion which would satisfy an overload of the Equals method would be the conversion to the object type corresponding to int違う。Javaとは異なり、C#にはプリミティブ型とボックス型の独立した型はありません。object他の唯一のオーバーロードであるため、ボックス化されていEquals()ます。
SLaks 2014年

最初と3番目の質問は同じです。に変換すると、正確な値はすでに失われていfloatます。a floatをaにキャストしても、double魔法のように新しい精度は作成されません。
SLaks 2014年

@SLaks:C#が実行される仮想マシンを記述するECMA仕様によれば、各値タイプ定義は2つの異なるタイプを作成します。C#仕様では、タイプの格納場所とタイプList<String>.Enumeratorのヒープオブジェクトの内容はList<String>.Enumerator同じであると記載されている場合がありますが、ECMA / CLI仕様では、それらは異なるものであり、C#で使用した場合でも動作が異なります。
スーパーキャット2014年

@SLaks:ifdouble比較前にそれぞれ変換された場合、それらは16777217.0と16777216.0を生成し、これらは等しくないものとして比較されます。変換i floatすると、に等しい16777216.0fが生成されfます。
スーパーキャット2014年

@SLaks:保管場所タイプとボックス化オブジェクトタイプの違いの簡単な例については、メソッドを検討してくださいbool SelfSame<T>(T p) { return Object.ReferenceEquals((Object)p,(Object)p);}。値の型に対応するボックス化されたオブジェクト型はReferenceEqualsID保持アップキャストを介してのパラメーター型を満たすことができます。ただし、保管場所タイプには、IDを保持しない変換が必要です。Tto Uをキャストして元の以外への参照が生成される場合T、それはa Tが実際にはでないことを示唆していUます。
スーパーキャット2014年

5

Equals()System.Objectクラスのメソッドです。
構文:Public virtual bool Equals()
2つのオブジェクトの状態を比較する場合は、Equals()メソッドを 使用する必要があります

上記のように、==演算子は値が同じであることを比較します。

ReferenceEqualと混同しないでください

Reference Equals()
構文:public static bool ReferenceEquals()
指定されたオブジェクトインスタンスが同じインスタンスかどうかを判別します


8
これは質問にまったく答えません。
SLaks 2014年

SLaksは、これが上記の質問の基本である例で説明しました。
Sugat Mankar 2014年

4

理解する必要があるのは、実行==すると必ずメソッドが呼び出されることです。問題は、電話==をしEqualsて、同じことを電話/実行することになるかどうかです。

参照タイプの場合、==最初に参照が同じかどうかを常に確認します(Object.ReferenceEquals)。Equals一方、オーバーライドすることができ、いくつかの値が等しいかどうかをチェックする場合があります。

編集:svickに答えてSLaksコメントを追加するには、いくつかのILコードがあります

int i1 = 0x22; // ldc.i4.s ie pushes an int32 on the stack
int i2 = 0x33; // ldc.i4.s 
short s1 = 0x11; // ldc.i4.s (same as for int32)
short s2 = 0x22; // ldc.i4.s 

s1 == i1 // ceq
i1 == s1 // ceq
i1 == i2 // ceq
s1 == s2 // ceq
// no difference between int and short for those 4 cases,
// anyway the shorts are pushed as integers.

i1.Equals(i2) // calls System.Int32.Equals
s1.Equals(s2) // calls System.Int16.Equals
i1.Equals(s1) // calls System.Int32.Equals: s1 is considered as an integer
// - again it was pushed as such on the stack)
s1.Equals(i1) // boxes the int32 then calls System.Int16.Equals
// - int16 has 2 Equals methods: one for in16 and one for Object.
// Casting an int32 into an int16 is not safe, so the Object overload
// must be used instead.

では、2つintのを==で比較する方法は何ですか?ヒント:にはoperator ==メソッドはありませんInt32が、にはStringメソッドがあります。
2014年

2
これは質問にまったく答えません。
SLaks 2014年

@SLaks:確かに、intおよびshortの比較に関する特定の質問には答えません。すでに答えました。それ==が魔法をかけるだけではなく、最終的には単にメソッドを呼び出すだけだと説明するのは興味深いと思います(ほとんどのプログラマーはおそらく演算子を実装/オーバーライドしていません)。たぶん、私は自分の答えを追加する代わりに、あなたの質問にコメントを追加したかもしれません。私が言ったことに関連があると感じた場合は、自由に更新してください。
user276648 2014年

==プリミティブ型はオーバーロードされた演算子ではなく、ceqIL命令にコンパイルされる組み込み言語機能であることに注意してください。
SLaks 2014年

3

==プリミティブ

Console.WriteLine(age == newAge);          // true

プリミティブ比較では==演算子の動作は非常に明白ですが、C#では多くの==演算子のオーバーロードを使用できます。

  • 文字列==文字列
  • int == int
  • uint == uint
  • 長い==長い
  • もっともっと

したがって、この場合、to からinttoへの暗黙の変換はありません。そのため、newAgeはintに変換され、両方が同じ値を保持するためtrueを返す比較が行われます。したがって、以下と同等です。shortshortint

Console.WriteLine(age == (int)newAge);          // true

プリミティブの.Equals()

Console.WriteLine(newAge.Equals(age));         //false

ここでは、Equals()メソッドが何であるかを確認する必要があります。short型の変数を使用してEqualsを呼び出します。したがって、3つの可能性があります。

  • Equals(object、object)//オブジェクトからの静的メソッド
  • Equals(object)//オブジェクトからの仮想メソッド
  • Equals(short)// IEquatable.Equals(short)を実装します

最初の型は、int型の引数を1つだけ使用して呼び出す引数の数が異なるため、ここでは当てはまりません。前述のように、intからshortへの暗黙的な変換は不可能なので、3番目も削除されます。そこで、ここでは2番目のタイプEquals(object)が呼び出されます。short.Equals(object)次のとおりです。

bool Equals(object z)
{
  return z is short && (short)z == this;
}

したがって、ここz is shortではzがintであるためfalseを返す条件がテストされ、falseを返します。

エリック・リペルトの詳細記事はこちら

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