C# ではなぜ == と != の両方を定義する必要があるのでしょうか? 質問する

C# ではなぜ == と != の両方を定義する必要があるのでしょうか? 質問する

C#コンパイラでは、カスタム型が演算子を定義するときは常に==、演算子も定義する必要があります!=(参照:ここ)。

なぜ?

設計者がなぜそれを必要だと考えたのか、また、もう一方の演算子しか存在しない場合に、コンパイラがデフォルトでどちらかの演算子の適切な実装にできないのはなぜなのか、知りたいです。たとえば、Lua では等価演算子のみを定義でき、もう一方の演算子は無料で使用できます。C# でも、== または == と != の両方を定義するように要求し、不足している != 演算子を として自動的にコンパイルすることで、同じことができます!(left == right)

一部のエンティティが等しくも不等でもない可能性がある奇妙なコーナーケース (IEEE-754 NaN など) があることは理解していますが、それらは例外であり、規則ではないようです。したがって、これは、C# コンパイラの設計者が例外を規則にした理由を説明するものではありません。

等価演算子が定義されていて、不等価演算子がコピー アンド ペーストされ、比較がすべて逆にされ、すべての && が || に置き換えられている (要点はわかりますよね... 基本的に !(a==b) がド モルガンの規則によって展開されている) という、出来の悪い例を見たことがあります。これは、Lua の場合のように、コンパイラが設計によって排除できる悪い方法です。

注: 演算子 < > <= >= についても同様です。これらを不自然な方法で定義する必要がある場合は考えられません。Lua では < と <= のみを定義でき、前者の否定によって >= と > を自然に定義します。なぜ C# は同じこと (少なくとも「デフォルトでは」) を行わないのでしょうか。

編集

どうやら、プログラマーが好きなように等価性および不等価性のチェックを実装できるようにする正当な理由があるようです。回答の中には、それが良い場合があることを指摘しているものもあります。

しかし、私の質問の核心は、通常は論理的に必要ではないのに、なぜこれが C# で強制的に必要なのかということです。

これは、 などの .NET インターフェースの設計上の選択とは著しく対照的です。Object.Equalsでは、対応するIEquatable.Equals IEqualityComparer.Equalsが存在しないことから、NotEqualsフレーム!Equals()ワークはオブジェクトを不等と見なしており、それだけであることがわかります。 さらに、 などのクラスDictionaryや などのメソッドは、.Contains()前述のインターフェースにのみ依存しており、演算子が定義されていても直接使用しません。 実際、ReSharper が等価メンバーを生成する場合、 と の両方を と に基づいて定義します==!=Equals()その場合でも、ユーザーが演算子を生成することを選択した場合に限られます。 等価演算子は、フレームワークがオブジェクトの等価性を理解するために必要ではありません。

基本的に、.NET フレームワークはこれらの演算子を気にしません。気にするのはいくつかのEqualsメソッドだけです。== 演算子と != 演算子の両方をユーザーが同時に定義する必要があるという決定は、.NET に関する限り、純粋に言語設計に関連しており、オブジェクト セマンティクスとは関係ありません。

ベストアンサー1

言語設計者に代わって話すことはできませんが、私が推測するに、それは意図的で適切な設計上の決定だったようです。

この基本的な F# コードを見ると、これを作業ライブラリにコンパイルできます。これは F# の正当なコードであり、不等号演算子ではなく等号演算子のみをオーバーロードします。

module Module1

type Foo() =
    let mutable myInternalValue = 0
    member this.Prop
        with get () = myInternalValue
        and set (value) = myInternalValue <- value

    static member op_Equality (left : Foo, right : Foo) = left.Prop = right.Prop
    //static member op_Inequality (left : Foo, right : Foo) = left.Prop <> right.Prop

これはまさにその名の通りの動作をします。only に対して等価比較子を作成し==、クラスの内部値が等しいかどうかを確認します。

C# ではこのようなクラスを作成することはできませんが、 .NET 用にコンパイルされたクラスを使用できます。オーバーロードされた for 演算子が使用されることは明らかです。==では、ランタイムは for に何を使用するのでしょうか!=?

C# EMCA 標準には、等価性を評価するときにどの演算子を使用するかを決定する方法を説明する一連のルール (セクション 14.9) があります。過度に単純化して完全に正確ではない言い方をすると、比較する型が同じ型で、オーバーロードされた等価演算子が存在する場合、そのオーバーロードが使用され、Object から継承された標準の参照等価演算子は使用されません。したがって、演算子が 1 つだけ存在する場合は、すべてのオブジェクトが持つデフォルトの参照等価演算子が使用され、オーバーロードが存在しないことは驚くことではありません。1

これが事実だとわかったら、本当の疑問は、なぜこれがこのように設計され、なぜコンパイラが独自にそれを理解しないのか、ということです。多くの人が、これは設計上の決定ではないと言っていますが、特にすべてのオブジェクトにデフォルトの等価演算子があるという事実に関しては、このように考え抜かれたものだと私は考えています。

では、なぜコンパイラは自動的に!=演算子を作成しないのでしょうか? Microsoft の誰かがこれを確認しない限り、確かなことはわかりませんが、事実に基づいて推論すると、これが判断できます。


予期しない動作を防ぐため

おそらく、等価性をテストするために値の比較を行いたいのでしょう==。しかし、!=参照が等しくない限り、値が等しいかどうかはまったく気にしませんでした。プログラムがそれらを等しいと見なすためには、参照が一致するかどうかだけを気にするからです。結局のところ、これは実際には C# のデフォルトの動作として概説されています (他の言語で書かれた一部の .net ライブラリの場合のように、両方の演算子がオーバーロードされていない場合)。コンパイラがコードを自動的に追加していた場合、準拠するはずのコードを出力するコンパイラに頼ることはできなくなりました。特に、記述したコードが C# と CLI の両方の標準の範囲内にある場合、コンパイラは動作を変更する隠れたコードを書き込むべきではありません。

デフォルトの動作ではなくオーバーロードを強制するというでは、それが標準 (EMCA-334 17.9.2) 2にあると断言できます。標準ではその理由は明記されていません。これは、C# が C++ から多くの動作を借用しているためだと考えています。詳細については、以下を参照してください。


!=およびをオーバーライドする場合==、bool を返す必要はありません。

これも考えられる理由の 1 つです。C# では、この関数は次のようになります。

public static int operator ==(MyClass a, MyClass b) { return 0; }

これは次のように有効です:

public static bool operator ==(MyClass a, MyClass b) { return true; }

bool 以外の値を返す場合、コンパイラは自動的に反対の型を推論できません。さらに、演算子が bool返す場合、その特定のケースでのみ存在するコードや、上で述べたように CLR のデフォルトの動作を隠すコードを生成しても意味がありません。


C#はC++ 3から多くのものを借用している

C# が導入されたとき、MSDN マガジンに C# について書かれた記事がありました。

多くの開発者は、Visual Basic のように書きやすく、読みやすく、保守しやすい言語でありながら、C++ のパワーと柔軟性も備えた言語があればいいのにと思っています。

はい、C# の設計目標は、厳格な型安全性やガベージ コレクションなどの利便性のために少しだけ犠牲にしながら、C++ とほぼ同じ程度の機能を提供することでした。C# は C++ を強くモデル化しました。

C++では、等価演算子はboolを返す必要がないことを知っても驚かないかもしれません。このサンプルプログラム

現在、C++ では、相補演算子を直接オーバーロードする必要はありません。サンプル プログラムのコードをコンパイルすると、エラーなしで実行されることがわかります。ただし、次の行を追加しようとすると、

cout << (a != b);

あなたは得るでしょう

コンパイラ エラー C2678 (MSVC): バイナリ '!=': 'Test' 型の左側のオペランドを取る演算子が見つかりません (または許容される変換がありません)。

したがって、C++ 自体はペアでオーバーロードする必要はありませんが、カスタム クラスでオーバーロードしていない等価演算子を使用することはできません。これは、すべてのオブジェクトにデフォルトの等価演算子があるため、.NET では有効ですが、C++ にはそれがありません。


1. 補足として、C# 標準では、どちらかの演算子をオーバーロードする場合、演算子のペアをオーバーロードする必要があります。これは標準の一部であり単にコンパイラーだけではありません。ただし、同じ要件を持たない別の言語で記述された .net ライブラリにアクセスする場合も、呼び出す演算子の決定に関する同じルールが適用されます。

2.EMCA-334 (pdf)http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-334.pdf

3. そしてJavaですが、それはここでは重要ではありません

おすすめ記事