なぜ.NET
null >= null
falseとして解決されますが、
null == null
真として解決しますか?
つまり、null >= null
がnull > null || null == null
と同等ではないのはなぜですか。
誰かが公式の答えを持っていますか?
この動作は、セクション14.2.7のC#仕様( ECMA-334 )で定義されています(関連する部分を強調表示しました)。
関係演算子の場合
_< > <= >=
_オペランドタイプが両方ともnull許容でない値タイプであり、結果タイプが
bool
である場合、演算子のリフト形式が存在します。リフトされた形式は、各オペランドタイプに単一の_?
_修飾子を追加することによって構築されます。 一方または両方のオペランドがfalse
の場合、リフトされた演算子は値null
を生成します。それ以外の場合、持ち上げられた演算子はオペランドをアンラップし、基になる演算子を適用してbool
結果を生成します。
特に、これは通常の関係法が成り立たないことを意味します。 _x >= y
_は!(x < y)
を意味しません。
そもそも、これが_int?
_の解除された演算子であるとコンパイラーが判断する理由を尋ねる人もいます。みてみましょう。 :)
14.2.4「二項演算子のオーバーロード解決」から始めます。これは、従うべき手順の詳細です。
最初に、ユーザー定義の演算子が適切かどうかが調べられます。これは、_>=
_...の両側の型によって定義された演算子を調べることによって行われ、null
の型が何であるかという疑問が生じます。 null
リテラルは、実際には、指定されるまで型を持ちません。これは単に「nullリテラル」です。 14.2.5の指示に従うと、nullリテラルは演算子を定義しないため、ここに適した演算子がないことがわかります。
このステップでは、事前定義された演算子のセットが適切かどうかを調べるように指示します。 (どちらの側も列挙型ではないため、列挙もこのセクションでは除外されます。)関連する定義済み演算子はセクション14.9.1から14.9.3にリストされており、これらはすべてプリミティブ数値型の演算子であり、これらの演算子(string
s演算子はここに含まれていないことに注意してください)。
最後に、これらの演算子と14.4.2のルールを使用してオーバーロードの解決を実行する必要があります。
実際にこの解決を実行するのは非常に面倒ですが、幸いなことに近道があります。 14.2.6の下では、過負荷解決の結果を示す有益な例があり、次のように述べられています。
...事前定義された2項*演算子の実装を検討します。
_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許容型に変換可能であり、int
にnull許容型が存在するため、リストから最初の演算子、つまり_int? >= int?
_を選択します。
多くの回答が仕様にアピールしています。 C#4仕様は、2つのnullリテラルの比較について具体的に言及された動作を正当化しません。実際、仕様を厳密に読むと、「null == null」はあいまいなエラーを引き起こすはずです。 (これは、C#3に備えてC#2仕様のクリーンアップ中に行われた編集エラーが原因です。これを違法にすることは、仕様作成者の意図ではありません。)
私を信じないなら、仕様を注意深く読んでください。 int、uint、long、ulong、bool、decimal、double、float、string、enums、delegate、objectsで定義された等式演算子に加えて、すべての値型演算子のnull許容バージョンがあります。
さて、すぐに問題が発生します。このセットは無限に大きいです。実際には、すべての可能なデリゲート型と列挙型ですべての演算子の無限セットを形成するわけではありません。候補セットに追加される列挙型とデリゲート型の演算子は、いずれかの引数の型である列挙型またはデリゲート型の演算子のみであることに注意するために、ここで仕様を修正する必要があります。
したがって、どちらの引数にも型がないため、列挙型とデリゲート型は除外しておきましょう。
過負荷の解決の問題が発生しました。最初に、適用されない演算子をすべて削除し、次に、適用可能な演算子の最適なものを決定する必要があります。
明らかに、null許容でないすべての値型で定義された演算子は適用できません。これにより、null許容値型、文字列、およびオブジェクトに演算子が残ります。
「より良い」という理由で、いくつかを取り除くことができるようになりました。より適切な演算子は、より具体的なタイプの演算子です。 int?は他のnull許容数値型よりも具体的であるため、これらはすべて削除されます。文字列はオブジェクトよりも具体的であるため、オブジェクトは削除されます。
それは文字列、intの等式演算子を残しますか?とブール?該当する演算子として。どれが一番いいですか? それらのどれも他より優れていません。したがって、これはあいまいなエラーになるはずです。
この動作を仕様で正当化するには、仕様を修正して、「null == null」が文字列の同等性のセマンティクスを持つものとして定義されていることに注意する必要があります。それがコンパイル時定数trueであること。
私は実際に昨日この事実を発見しました。あなたがそれについて尋ねるのがどれほど奇妙か。
null >= null
がintとの比較について警告を出す理由について、他の回答で提起された質問に答えるには? -さて、先ほどと同じ分析を適用します。 nullを許容しない値型の>=
演算子は適用できず、残っている演算子のうち、intの演算子は?一番です。 boolに>=
演算子が定義されていないため、>=
のあいまいさエラーはありませんか?または文字列。コンパイラは、演算子を2つのnull許容整数の比較として正しく分析しています。
Nullvaluesの演算子(literalsとは対照的に)が特定の異常な動作をする理由に関するより一般的な質問に答えるために、- 重複する質問に対する私の回答を参照してください。この決定を正当化する設計基準を明確に説明しています。つまり、nullに対する操作には、「わからない」に対する操作のセマンティクスが必要です。あなたが知らない量はあなたが知らない別の量以上ですか?唯一の賢明な答えは「わからない!」です。しかし、それをブール値に変える必要があり、賢明なブール値は「偽」です。しかし、等しいかどうかを比較するとき、ほとんどの人は、等しいかどうかわからない2つのものを比較しても「わからない」という結果になるはずですが、nullはnullと等しいはずだと思います。この設計上の決定は、多くの望ましくない結果を互いにトレードオフして、機能を機能させる最も悪い結果を見つけた結果です。それは言語を幾分一貫性のないものにします、私は同意します。
コンパイラーは、比較演算子の場合、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*
Visual Studioは警告を提供します:
*タイプ「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 Spec の関連セクションを次に示します。
Nullリテラル§2.4.4.6:
Nullリテラルは、暗黙的に参照型またはnull許容型に変換できます。
バイナリ数値プロモーション7.3.6.2:
二項数値昇格は、事前定義された+、–、*、/、%、&、|、^、==、!=、>、<、> =、および<=二項演算子のオペランドに対して発生します。 2進数の昇格は、両方のオペランドを共通の型に暗黙的に変換します。これは、非関係演算子の場合、演算の結果型にもなります。 2進数の昇格は、次のルールをここに表示される順に適用することで構成されます。
•一方のオペランドが10進数型の場合、もう一方のオペランドは10進数型に変換されます。または、他方のオペランドが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許容でない値タイプであり、結果タイプがブール値である場合、演算子のリフト形式が存在します。持ち上げられたフォームは、単一の?各オペランドタイプの修飾子。リフトされた演算子は、一方または両方のオペランドがnullの場合、値falseを生成します。それ以外の場合、リフトされた演算子はオペランドをアンラップし、基になる演算子を適用してブール結果を生成します。
Nullリテラルだけでは、実際には型がありません。それは何に割り当てられているかによって推測されます。ただし、ここでは割り当ては行われません。言語サポート付きの組み込み型(キーワード付きのもの)のみを検討する場合は、object
またはnull許容型が適しています。ただし、object
は比較できないため除外されます。これにより、null許容型の候補が残ります。しかし、どのタイプですか?左と右のどちらのオペランドも指定された型を持たないため、デフォルトでは(ヌル可能)int
に変換されます。 null許容値は両方ともnullであるため、falseを返します。
コンパイラはnullを整数型であるかのように扱うようです。 VS2008の備考:"Comparing with null of type 'int?' always produces 'false'"
走ると
null >= null
次のような警告が表示されます。
タイプ 'int?'のnullとの比較常に「false」を生成します
なぜintにキャストされるのかしら。
これは、コンパイラが>= null
がalwaysがfalseであり、式を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);
}
}
これは「公式」の答えではありません、それは私の最善の推測です。ただし、null許容のintを処理してそれらを比較している場合、nullの2つの「int?」を処理している場合は常に比較がfalseを返すようにする必要があります。そうすれば、それがtrueを返す場合、2つのnull値ではなく、2つの整数を実際に比較したと確信できます。個別のnullチェックの必要がなくなるだけです。
とはいえ、期待した動作でない場合は混乱する可能性があります。
このページでは、動作について Nullable Types について説明しています。理由はわかりませんが、私の解釈は次のとおりです。 >
および<
は、null
に関しては意味がありません。 null
は値がないため、値のない別の変数とのみ等しくなります。 >
と<
には意味がないため、>=
と<=
に引き継がれます。
彼らはCとSQLのパラダイムを混ぜ合わせているようです。
Null許容変数のコンテキストでは、どちらの値もわからない場合は等式が意味をなさないため、null == nullは実際にはfalseになりますが、C#全体でこれを行うと、参照を比較するときに問題が発生します。
この質問はDeja vuです。
==がnull値に対してtrueを返すのに、> =がfalseを返すのはなぜですか?
私が他の答えから覚えていることは:
平等は比較可能性とは別に定義されているためです。 x == nullをテストできますが、x> nullは無意味です。 C#では、常にfalseになります。
簡単に言えば、「これは、これらの演算子が仕様で定義されている方法だから」です。
ECMA C#仕様 のセクション8.19から:
==
および!=
演算子のリフト形式は、2つのnull値が等しく、null値が非null値と等しくないと見なします。<
、>
、<=
、および>=
演算子のリフトされた形式は、一方または両方のオペランドがnullの場合、falseを返します。