41

なぜ.NETで

null >= null

false として解決されますが、

null == null 

true として解決しますか?

言い換えれば、なぜ とnull >= null同等ではないのnull > null || null == nullですか?

誰も公式の答えを持っていますか?

4

11 に答える 11

34

この動作は、セクション 14.2.7の C# 仕様 ( ECMA-334 ) で定義されています (関連部分を強調表示しています)。

関係演算子の場合

< > <= >=

演算子の持ち上げられた形式は、オペランドの型が両方とも null 非許容値型であり、結果の型が である場合に存在しますbool?持ち上げられた形式は、各オペランドの型に単一の修飾子を追加することによって構築されます。一方または両方のオペランドが の場合、持ち上げられた演算子は値を生成しますfalsenull。それ以外の場合、リフトされた演算子はオペランドをアンラップし、基礎となる演算子を適用してbool結果を生成します。

特に、これは通常の関係の法則が成り立たないことを意味します。x >= yを意味するものではありません!(x < y)

血まみれの詳細

int?そもそもなぜこれがリフト演算子であるとコンパイラが判断したのかと尋ねる人もいます。みてみましょう。:)

14.2.4「二項演算子のオーバーロードの解決」から始めます。これにより、従うべき手順が詳しく説明されます。

  1. 最初に、ユーザー定義演算子の適合性が検査されます。>=これは、 ...の各側の型によって定義された演算子を調べることによって行われnullます。リテラルには、null実際には型が指定されるまで型がありません。それは単に「null リテラル」です。14.2.5 の指示に従うと、null リテラルでは演算子が定義されていないため、ここで適切な演算子がないことがわかります。

  2. このステップでは、事前定義された一連の演算子の適合性を調べるように指示します。(どちらの側も列挙型ではないため、列挙型もこのセクションでは除外されます。) 関連する事前定義された演算子はセクション 14.9.1 から 14.9.3 にリストされており、それらはすべてプリミティブ数値型の演算子であり、リフトされたバージョンのこれらの演算子 (s 演算子はここに含まれていないことに注意しstringてください)。

  3. 最後に、これらの演算子と 14.4.2 のルールを使用して、オーバーロードの解決を実行する必要があります。

実際にこの解決策を実行するのは非常に面倒ですが、幸いなことに近道があります。14.2.6 では、過負荷解決の結果に関する有益な例があり、次のように述べられています。

...バイナリ * 演算子の定義済みの実装を考えてみましょう:

int operator *(int x, int y);
uint operator *(uint x, uint y);
long operator *(long x, long y);
ulong operator *(ulong x, ulong y);
void operator *(long x, ulong y);
void operator *(ulong x, long y);
float operator *(float x, float y);
double operator *(double x, double y);
decimal operator *(decimal x, decimal y);

オーバーロードの解決規則 (§14.4.2) がこの一連の演算子に適用されると、オペランドの型から暗黙的な変換が存在する最初の演算子が選択されます。

両側があるので、null持ち上げられていないすべてのオペレーターをすぐに捨てることができます。これにより、すべてのプリミティブ数値型でリフトされた数値演算子が残ります。

次に、前の情報を使用して、暗黙的な変換が存在する最初の演算子を選択します。null リテラルは暗黙的に null 許容型に変換可能であり、null 許容型は に存在するためint、リストから最初の演算子である を選択しますint? >= int?

于 2011-01-19T01:15:20.687 に答える
27

多くの回答が仕様にアピールします。C# 4 の仕様では、2 つの null リテラルの比較について具体的に言及されている動作が正当化されていません。実際、仕様を厳密に読むと、「null == null」はあいまいなエラーになるはずです! (これは、C# 3 に備えて C# 2 仕様をクリーンアップする際に発生した編集エラーによるものです。これを違法にすることは、仕様作成者の意図ではありません。)

信じられない場合は、仕様を注意深く読んでください。int、uint、long、ulong、bool、decimal、double、float、string、enum、デリゲート、およびオブジェクトで定義された等値演算子に加えて、すべての値型演算子の null 許容バージョンにリフトされたバージョンがあることを示しています。

さて、すぐに問題が発生します。このセットは無限大です。実際には、考えられるすべてのデリゲートおよび列挙型について、すべての演算子の無限セットを形成するわけではありません。ここで仕様を修正して、候補セットに追加される列挙型およびデリゲート型の演算子のみが、いずれかの引数の型である列挙型またはデリゲート型の演算子であることに注意する必要があります。

したがって、どちらの引数にも型がないため、列挙型とデリゲート型を除外しましょう。

オーバーロード解決の問題が発生しました。最初に適用できない演算子をすべて排除し、次に適用可能な最適な演算子を決定する必要があります。

明らかに、すべての非 null 値型で定義された演算子は適用できません。これにより、null 許容値型、文字列、およびオブジェクトの演算子が残ります。

「より良い」という理由で、いくつかを削除できるようになりました。優れた演算子は、より具体的な型を持つ演算子です。整数?他の null 許容数値型よりも具体的であるため、それらはすべて削除されます。文字列はオブジェクトよりも具体的であるため、オブジェクトは削除されます。

それは、文字列、int の等値演算子を残しますか? そしてブール?該当する事業者として。どれが最高ですか? それらのどれも他よりも優れていません。したがって、これはあいまいなエラーです。

この動作が仕様によって正当化されるためには、仕様を修正して、「null == null」が文字列等価のセマンティクスを持つものとして定義され、コンパイル時の定数 true であることに注意する必要があります。

実は昨日、この事実を発見したばかりです。あなたがそれについて尋ねなければならないなんて奇妙なことです。

null >= nullint との比較に関する警告が表示される理由について、他の回答で提起された質問に答えるには? -- さて、今行ったのと同じ分析を適用してください。>=null 非許容値型の演算子は適用できず、残りの演算子のうち int? の演算子は適用されません。最高です。bool に演算子が定義されて>=いないため、あいまいなエラーはありませんか? >=または文字列。コンパイラは、演算子を 2 つの null 許容 int の比較として正しく分析しています。

nullの演算子(リテラルではなく) が特定の異常な動作をする理由に関するより一般的な質問に答えるには、重複した質問に対する私の回答を参照してください。. この決定を正当化する設計基準を明確に説明しています。つまり、null に対する操作は、「わからない」に対する操作のセマンティクスを持つ必要があります。あなたが知らない量は、あなたが知らない別の量以上ですか? 唯一の賢明な答えは「わかりません!」です。しかし、それをブール値に変換する必要があり、賢明なブール値は「false」です。しかし、等しいかどうかを比較する場合、ほとんどの人は null は null と等しいと考えますが、等しいかどうかわからない 2 つのことを比較すると「わかりません」という結果になるはずです。この設計上の決定は、多くの望ましくない結果を相互にトレードオフして、機能を機能させる最も悪い結果を見つけた結果です。それは言語を多少矛盾させます、私は同意します。

于 2011-01-19T02:55:40.037 に答える
5

nullコンパイラーは、比較演算子の場合、は暗黙的にとして入力されていると推測していint?ます。

Console.WriteLine(null == null); // true
Console.WriteLine(null != null); // false
Console.WriteLine(null < null);  // false*
Console.WriteLine(null <= null); // false*
Console.WriteLine(null > null);  // false*
Console.WriteLine(null >= null); // false*

VisualStudioは警告を提供します:

*タイプ'int?'のnullとの比較 常に「false」を生成します

これは、次のコードで確認できます。

static void PrintTypes(LambdaExpression expr)
{
    Console.WriteLine(expr);
    ConstantExpression cexpr = expr.Body as ConstantExpression;
    if (cexpr != null)
    {
        Console.WriteLine("\t{0}", cexpr.Type);
        return;
    }
    BinaryExpression bexpr = expr.Body as BinaryExpression;
    if (bexpr != null)
    {
        Console.WriteLine("\t{0}", bexpr.Left.Type);
        Console.WriteLine("\t{0}", bexpr.Right.Type);
        return;
    }
    return;
}
PrintTypes((Expression<Func<bool>>)(() => null == null)); // constant folded directly to bool
PrintTypes((Expression<Func<bool>>)(() => null != null)); // constant folded directly to bool
PrintTypes((Expression<Func<bool>>)(() => null < null));
PrintTypes((Expression<Func<bool>>)(() => null <= null));
PrintTypes((Expression<Func<bool>>)(() => null > null));
PrintTypes((Expression<Func<bool>>)(() => null >= null));

出力:

() => True
        System.Boolean
() => False
        System.Boolean
() => (null < null)
        System.Nullable`1[System.Int32]
        System.Nullable`1[System.Int32]
() => (null <= null)
        System.Nullable`1[System.Int32]
        System.Nullable`1[System.Int32]
() => (null > null)
        System.Nullable`1[System.Int32]
        System.Nullable`1[System.Int32]
() => (null >= null)
        System.Nullable`1[System.Int32]
        System.Nullable`1[System.Int32]

なんで?

これは私には論理的に思えます。まず、C#4.0仕様の関連セクションを次に示します。

nullリテラル§2.4.4.6:

nullリテラルは、暗黙的に参照型またはnull許容型に変換できます。

2進数のプロモーション§7.3.6.2:

二項数値昇格は、事前定義された+、–、*、/、%、&、|、^、==、!=、>、<、> =、および<=二項演算子のオペランドに対して発生します。2進数の昇格は、両方のオペランドを暗黙的に共通の型に変換します。これは、非関係演算子の場合は、演算の結果型にもなります。2進数の昇格は、次のルールをここに表示される順序で適用することで構成されます。

•一方のオペランドがdecimal型の場合、もう一方のオペランドはdecimal型に変換されます。または、他方のオペランドがfloat型またはdouble型の場合、バインディング時エラーが発生します。
•それ以外の場合、一方のオペランドがdouble型の場合、もう一方のオペランドはdouble型に変換されます。
•それ以外の場合、一方のオペランドがfloat型の場合、もう一方のオペランドはfloat型に変換されます。
•それ以外の場合、いずれかのオペランドがulong型の場合、もう一方のオペランドはulong型に変換されます。または、他方のオペランドがsbyte、short、int、またはlong型の場合、バインディング時エラーが発生します。
•それ以外の場合、一方のオペランドがlong型の場合、もう一方のオペランドはlong型に変換されます。
•それ以外の場合、一方のオペランドがuint型で、もう一方のオペランドがsbyte、short、またはint型の場合、両方のオペランドがlong型に変換されます。
•それ以外の場合、一方のオペランドがuint型の場合、もう一方のオペランドはuint型に変換されます。
•それ以外の場合、両方のオペランドはint型に変換されます。

持ち上げられたオペレーター§7.3.7:

リフト演算子を使用すると、null許容でない値型を操作する事前定義およびユーザー定義の演算子を、それらの型のnull許容形式でも使用できます。リフトされた演算子は、以下に説明するように、特定の要件を満たす事前定義されたユーザー定義の演算子から構成されます。

•関係演算子の場合
<><=>=
オペランドタイプが両方ともnull許容でない値タイプであり、結果タイプがboolの場合、演算子のリフト形式が存在します。持ち上げられたフォームは、単一の?を追加することによって構築されます。各オペランドタイプの修飾子。リフトされた演算子は、一方または両方のオペランドがnullの場合、値falseを生成します。それ以外の場合、リフトされた演算子はオペランドのラップを解除し、基になる演算子を適用してブール結果を生成します。

nullリテラルだけでは、実際には型がありません。それはそれが割り当てられているものによって推測されます。ただし、ここでは割り当ては行われません。言語をサポートする組み込み型(キーワードを含む型)、objectまたはnull許容型を検討することだけが適切な候補です。ただしobject、比較できないため、除外されます。これにより、null許容型が適切な候補になります。しかし、どのタイプですか?左オペランドも右オペランドも指定されたタイプがないため、デフォルトで(null許容)に変換さintれます。null許容値は両方ともnullであるため、falseを返します。

于 2011-01-19T02:44:34.003 に答える
3

コンパイラはnullを整数型であるかのように扱うようです。VS2008備考:"Comparing with null of type 'int?' always produces 'false'"

于 2011-01-19T00:50:47.453 に答える
2

私が走るとき

null >= null

次のような警告が表示されます。

タイプ'int?'のnullとの比較 常に「false」を生成します

なぜそれがintにキャストされるのだろうか。

于 2011-01-19T00:50:19.163 に答える
2

これは、コンパイラが常に>= nullfalseになることを理解し、式を定数値.に置き換えるのに十分なほど賢いためです。false

この例を確認してください。

using System;

class Example
{
    static void Main()
    {
        int? i = null;

        Console.WriteLine(i >= null);
        Console.WriteLine(i == null);
    }
}

これは、次のコードにコンパイルされます。

class Example
{
    private static void Main()
    {
        int? i = new int?();
        Console.WriteLine(false);
        Console.WriteLine(!i.HasValue);
    }
}
于 2011-01-19T00:53:46.720 に答える
2

彼らは、C と SQL のパラダイムを混ぜ合わせているようです。

null 許容変数のコンテキストでは、 null == null は実際には false になるはずです。どちらの値も不明な場合、等価性は意味をなさないためです。ただし、C# 全体でそれを行うと、参照を比較するときに問題が発生します。

于 2011-01-19T01:24:48.130 に答える
2

これは「公式」の回答ではなく、私の推測です。しかし、null 許容の int を処理してそれらを比較する場合、null である 2 つの "int?" を処理している場合、常に比較で false を返したいと思うでしょう。そうすれば、true が返された場合、2 つの null 値ではなく、2 つの整数を実際に比較したことを確認できます。個別の null チェックが不要になるだけです。

とはいえ、期待どおりの動作でない場合、混乱を招く可能性があります。

于 2011-01-19T00:58:07.453 に答える
2

この動作は、 Nullable 型に関するこのページに記載されています。その理由についての本当の説明はありませんが、私の解釈は次のとおりです。>に関しては意味があり<ませんnullnullは値が存在しないため、値を持たない別の変数とのみ等しくなります。>andには意味がないので、and<に引き継がれます。>=<=

于 2011-01-19T01:18:10.200 に答える
1

簡単に言えば、「これらの演算子が仕様で定義されているため」です。

ECMA C# 仕様のセクション 8.19 から:

==and演算子の持ち上げられた形式は、!= 2 つの null 値が等しいと見なし、null 値は null 以外の値と等しくないと見なします。、、、および演算子の持ち上げられた形式は、 <一方または両方のオペランドが null の場合に false を返します。><=>=

于 2011-01-19T01:22:52.877 に答える
1

この質問はDeja vuでした。ああ、待ってください...

== が null 値に対して true を返すのに、>= が false を返すのはなぜですか?

他の回答から覚えていることは次のとおりです。

同等性は比較可能性とは別に定義されているためです。x == null をテストできますが、 x > null は無意味です。C# では常に false になります。

于 2011-01-19T01:58:16.537 に答える