Nullをチェックするために static Object.Equals を使用するコードは、==演算子または regular Object.Equals ?を使用するコードより堅牢です。後者の2つは、nullのチェックが期待どおりに機能しないような方法でオーバーライドされる脆弱性ではありません(たとえば、比較値is ヌル)?
言い換えれば、これは:
if (Equals(item, null)) { /* Do Something */ }
これよりも堅牢:
if (item == null) { /* Do Something */ }
私は個人的に後者の構文が読みやすいと感じています。作成者の制御外のオブジェクト(ライブラリなど)を処理するコードを記述するときは、避けるべきですか?常に回避する必要がありますか(nullをチェックする場合)?これは単なる髪の毛の分割ですか?
この質問には簡単な答えはありません。私の意見では、常にどちらか一方を使用すると言っている人はあなたに悪いアドバイスを与えています。
実際には、オブジェクトインスタンスを比較するために呼び出すことができるいくつかの異なるメソッドがあります。 2つのオブジェクトインスタンスa
とb
を指定すると、次のように記述できます。
Object.Equals(a,b)
Object.ReferenceEquals(a,b)
a.Equals(b)
a == b
_これらはすべて異なることをすることができます!
Object.Equals(a,b)
は(デフォルトで)参照型の参照等価比較と値型のビット単位比較を実行します。 MSDNドキュメントから:
Equalsのデフォルト実装は、参照型の参照等値と、値型のビット単位の等式をサポートしています。参照の等価性とは、比較されるオブジェクト参照が同じオブジェクトを参照することを意味します。ビット単位の等式とは、比較されるオブジェクトが同じバイナリ表現を持っていることを意味します。
値の等価性を実装するために、派生型がEqualsメソッドをオーバーライドする場合があることに注意してください。値が等しいということは、比較されるオブジェクトの値は同じですが、バイナリ表現が異なることを意味します。
上記の最後の段落に注意してください...これについては少し後で説明します。
Object.ReferenceEquals(a,b)
は、参照の等価比較のみを実行します。渡される型がボックス化された値型である場合、結果は常にfalse
になります。
a.Equals(b)
は、Object
の仮想インスタンスメソッドを呼び出します。このメソッドは、a
のタイプをオーバーライドして、必要な処理を実行できます。呼び出しは仮想ディスパッチを使用して実行されるため、実行されるコードはa
のランタイムタイプに依存します。
_a == b
_a
の**コンパイル時タイプ*の静的オーバーロード演算子を呼び出します。その演算子の実装がa
またはb
のいずれかのインスタンスメソッドを呼び出す場合、パラメーターのランタイムタイプにも依存します。ディスパッチは式の型に基づいているため、次の結果は異なる結果になる場合があります。
_Frog aFrog = new Frog();
Frog bFrog = new Frog();
Animal aAnimal = aFrog;
Animal bAnimal = bFrog;
// not necessarily equal...
bool areEqualFrogs = aFrog == bFrog;
bool areEqualAnimals = aAnimal = bAnimal;
_
つまり、はい、_operator ==
_を使用してnullをチェックする脆弱性があります。実際には、ほとんどのタイプしないオーバーロード_==
_-しかし、保証。
ここでは、インスタンスメソッドEquals()
の方が優れています。デフォルトの実装は参照/ビットごとの等価性チェックを実行しますが、型がEquals()
メンバーメソッドをオーバーライドする可能性があります。その場合、この実装が呼び出されます。ユーザー提供の実装は、nullと比較する場合でも、必要なものを返すことができます。
しかし、あなたが尋ねるObject.Equals()
の静的バージョンはどうでしょうか?これでユーザーコードを実行できますか?答えはイエスです。 Object.Equals(a,b)
の実装は、次の行に沿って何かに展開されます。
_((object)a == (object)b) || (a != null && b != null && a.Equals(b))
_
あなたはこれを自分で試すことができます:
_class Foo {
public override bool Equals(object obj) { return true; } }
var a = new Foo();
var b = new Foo();
Console.WriteLine( Object.Equals(a,b) ); // outputs "True!"
_
その結果、次のステートメントObject.Equals(a,b)
は、呼び出しのいずれのタイプもnull
でない場合にユーザーコードを実行できます。 Object.Equals(a,b)
しない引数のいずれかがnullの場合、Equals()
のインスタンスバージョンを呼び出すことに注意してください。
つまり、どのメソッドを呼び出すかによって、取得する比較動作の種類は大きく異なります。ただし、ここで1つコメントがあります。MicrosoftはObject.Equals(a,b)
の内部動作を公式に文書化していません。 他のコードを実行せずに参照とnullを比較するという確固たる保証が必要な場合は、Object.ReferenceEquals()
:が必要です。
_Object.ReferenceEquals(item, null);
_
この方法により、意図が非常に明確になります。特に、結果が参照の等価性に関する2つの参照の比較であると期待しています。ここでObject.Equals(a,null)
のようなものを使用することの利点は、誰かが後になって次のように言う可能性が低いことです。
"ねえ、これは厄介です。それをa.Equals(null)
または_a == null
_に置き換えましょう。
潜在的に異なる可能性があります。
ただし、いくつかのプラグマティズムをここに挿入しましょう。これまで、さまざまな結果が得られるさまざまな比較方法の可能性について説明してきました。確かにそうですが、_a == null
_を書くのが安全な特定の型があります。 String
や_Nullable<T>
_などの組み込み.NETクラスには、比較のために明確に定義されたセマンティクスがあります。さらに、それらはsealed
であり、継承による動作の変更を防ぎます。以下は非常に一般的です(そして正しい):
_string s = ...
if( s == null ) { ... }
_
書く必要はありません(そして見苦しいです):
_if( ReferenceEquals(s,null) ) { ... }
_
そのため、特定の限られたケースでは、_==
_の使用が安全で適切です。
if (Equals(item, null))
はif (item == null)
ほど堅牢ではなく、起動するのがより混乱します。
IDENTITY(メモリ内の同じ場所)をテストする場合:
ReferenceEquals(a, b)
ヌルを処理します。そして、オーバーライドできません。 100%安全です。
ただし、IDENTITYテストが本当に必要であることを確認してください。以下を考慮してください。
ReferenceEquals(new String("abc"), new String("abc"))
false
を返します。対照的に:
Object.Equals(new String("abc"), new String("abc"))
そして
_
(new String("abc")) == (new String("abc"))
_
どちらもtrue
を返します。
この状況でtrue
の回答を期待している場合、IDENTITYテストではなくEQUALITYテストが必要です。次の部分を参照してください。
等値(同じ内容)をテストする場合:
コンパイラが文句を言わない場合、「_a == b
_」を使用します。
それが拒否された場合(変数aのタイプが「==」演算子を定義していない場合)、「Object.Equals(a, b)
」を使用します。
[〜#〜] if [〜#〜]あなたはaはnullではないことが知られているのロジックの内側にいるので、より読みやすい "a.Equals(b)
"。たとえば、「this.Equals(b)」は安全です。または、「a」が構築時に初期化されるフィールドであり、そのフィールドで使用される値としてnullが渡された場合、コンストラクターは例外をスローします。
今、元の質問に対処するには:
Q:これらは、nullを正しく処理しないコードで、あるクラスでオーバーライドされやすく、例外が発生しますか?
A:はい。 100%安全なEQUALITYテストを取得する唯一の方法は、ヌルを事前にテストすることです。
しかし、あなたはすべきですか?バグはその中にあり(仮想の将来の悪いクラス)、それは単純なタイプの失敗です。デバッグと修正が簡単です(クラスを提供する人なら誰でも)。私は、それが頻繁に起こる問題であるか、それが起こっても長く続く問題だとは思いません。
より詳細なA:Object.Equals(a, b)
は、適切に記述されていないクラスに直面しても機能する可能性が最も高いです。 "a"がnullの場合、Objectクラスはそれ自体を処理するので、リスクはありません。 「b」がnullの場合、「a」のDYNAMIC(実行時ではなく実行時)タイプが、「Equals」メソッドの呼び出しを決定します。 「b」がnullの場合、呼び出されたメソッドは単に正しく動作する必要があります。呼び出されたメソッドの記述が極端に不適切でない限り、最初のステップは、「b」が理解できるタイプであるかどうかを判別することです。
したがって、Object.Equals(a, b)
は、可読性/コーディング努力と安全性の間の妥当な妥協案です。
フレームワークガイドライン は、Equals
を値の等しいものとして扱うこと(2つのオブジェクトが同じ情報を表すかどうか、つまりプロパティを比較することを確認する)、および==
を参照の等しいものとして扱うことを示唆しています。不変オブジェクトを除いて、==
をオーバーライドして値が等しくなるようにする必要があります。
したがって、ここでガイドラインが適用されると仮定して、意味的に適切な方を選択してください。不変オブジェクトを処理していて、両方のメソッドが同じ結果を生成することが予想される場合、わかりやすくするために==
を使用します。
「...作成者の制御外のオブジェクトを処理するコードを記述する...」を参照して、静的Object.Equals
そしてその ==
演算子は静的メソッドであるため、仮想/オーバーライドすることはできません。どの実装が呼び出されるかはコンパイル時静的型に基づいて決定されます。つまり、外部ライブラリが、コンパイルされたコードに異なるバージョンのルーチンを提供する方法はありません。