C#値型をnullと比較しても大丈夫


85

私は今日これに遭遇しましたが、C#コンパイラがエラーをスローしない理由がわかりません。

Int32 x = 1;
if (x == null)
{
    Console.WriteLine("What the?");
}

xがnullになる可能性があるかどうかについて私は混乱しています。特に、この割り当ては間違いなくコンパイラエラーをスローするためです。

Int32 x = null;

xがnullになる可能性はありますか、Microsoftはこのチェックをコンパイラに入れないことを決定しただけですか、それとも完全に見逃されていましたか?

更新:この記事を書くためのコードをいじった後、突然、コンパイラーは式が真になることはないという警告を出しました。今、私は本当に迷っています。オブジェクトをクラスに入れましたが、警告は消えましたが、質問が残っています。値型がnullになる可能性があります。

public class Test
{
    public DateTime ADate = DateTime.Now;

    public Test ()
    {
        Test test = new Test();
        if (test.ADate == null)
        {
            Console.WriteLine("What the?");
        }
    }
}

9
あなたも書くことができif (1 == 2)ます。コードパス分析を実行するのはコンパイラの仕事ではありません。それが静的分析ツールと単体テストの目的です。
アーロノート2009

警告が消えた理由については、私の答えを参照してください。いいえ-nullにすることはできません。
MarcGravell

1
(1 == 2)に同意し、状況についてもっと疑問に思っていました(1 == null)
Joshua Belden

回答してくださった皆様、ありがとうございました。今ではすべてが理にかなっています。
Joshua Belden

警告または警告なしの問題について:問題の構造体がのようないわゆる「単純型」であるint場合、コンパイラーは適切な警告を生成します。単純型の場合、==演算子はC#言語仕様によって定義されます。他の(単純型ではない)構造体の場合、コンパイラーは警告を出すのを忘れます。詳細については、structをnullと比較するときの間違ったコンパイラ警告を参照してください。単純型ではない構造体の場合、==演算子opeartor ==は構造体のメンバーであるメソッドによってオーバーロードされる必要があります(そうでない場合==は許可されません)。
Jeppe Stig Nielsen 2013

回答:


119

演算子のオーバーロード解決には、選択するのに最適な一意の演算子があるため、これは合法です。2つのnull許容intを受け取る==演算子があります。int localは、null許容のintに変換できます。nullリテラルはnull許容整数に変換可能です。したがって、これは==演算子の合法的な使用法であり、常にfalseになります。

同様に、「if(x == 12.6)」と言うこともできますが、これも常にfalseになります。int localはdoubleに変換可能であり、リテラルはdoubleに変換可能であり、明らかにそれらが等しくなることはありません。



5
@James :(削除した以前の誤ったコメントを撤回します。)ユーザー定義の等式演算子もデフォルトで定義されているユーザー定義の値型は、リフトされたユーザー定義の等式演算子が生成されます。解除されたユーザー定義の等式演算子は、次の理由で適用できます。すべての値型は、nullリテラルと同様に、対応するnull許容型に暗黙的に変換可能です。しないユーザ定義の値型いる場合欠くユーザ定義の比較演算子がnullリテラルに匹敵します。
Eric Lippert 2010年

3
@James:もちろん、null許容構造体をとる独自の演算子==と演算子!=を実装できます。それらが存在する場合、コンパイラーはそれらを自動的に生成するのではなく、それらを使用します。(ちなみに、null許容でないオペランドに対する意味のないリフト演算子の警告が警告を生成しないことを残念に思います。これはコンパイラーのエラーであり、修正に取り掛かっていません。)
Eric Lippert 2010年

2
警告が欲しい!私たちはそれに値する。
Jeppe Stig Nielsen 2013

3
@JamesDunne:を定義しstatic bool operator == (SomeID a, String b)てタグ付けするのはObsoleteどうですか?2番目のオペランドが型指定されていないリテラルnullである場合、それはリフトされた演算子の使用を必要とするどの形式よりもよく一致しますが、SomeID?それがたまたま等しいnull場合、リフトされた演算子が勝ちます。
スーパーキャット2013年

17

int?)変換があるので、エラーではありません。与えられた例では警告を生成します:

「int」型の値が「int?」型の「null」と等しくなることはないため、式の結果は常に「false」になります。

ILを確認すると、到達不能なブランチが完全に削除されていることがわかります。リリースビルドには存在しません。

ただし、等価演算子を使用したカスタム構造体に対しては、この警告生成されないことに注意してください。以前は2.0でしたが、3.0コンパイラではそうではありませんでした。コードは引き続き削除されますが(コードに到達できないことがわかります)、警告は生成されません。

using System;

struct MyValue
{
    private readonly int value;
    public MyValue(int value) { this.value = value; }
    public static bool operator ==(MyValue x, MyValue y) {
        return x.value == y.value;
    }
    public static bool operator !=(MyValue x, MyValue y) {
        return x.value != y.value;
    }
}
class Program
{
    static void Main()
    {
        int i = 1;
        MyValue v = new MyValue(1);
        if (i == null) { Console.WriteLine("a"); } // warning
        if (v == null) { Console.WriteLine("a"); } // no warning
    }
}

IL(for Main)の場合- (副作用が発生する可能性がある)以外のすべてMyValue(1)が削除されていることに注意してください。

.method private hidebysig static void Main() cil managed
{
    .entrypoint
    .maxstack 2
    .locals init (
        [0] int32 i,
        [1] valuetype MyValue v)
    L_0000: ldc.i4.1 
    L_0001: stloc.0 
    L_0002: ldloca.s v
    L_0004: ldc.i4.1 
    L_0005: call instance void MyValue::.ctor(int32)
    L_000a: ret 
}

これは基本的に:

private static void Main()
{
    MyValue v = new MyValue(1);
}

1
最近、誰かがこれを私に内部的に報告しました。なぜその警告の生成を停止したのかわかりません。バグとして入力しました。
Eric Lippert


5

比較が決して真実ではないという事実は、それが違法であるという意味ではありません。それにもかかわらず、いいえ、値型はnull


1
しかし、値の型は、可能性が同等null。値型であるのint?構文糖衣であるNullable<Int32>、を考えてみましょう。型の変数はint?確かにに等しい可能性がありますnull
グレッグ

1
@Greg:はい、参照している「等しい」が==演算子の結果であると仮定すると、nullに等しくなる可能性があります。ただし、インスタンスは実際にはnullではないことに注意することが重要です。
アダムロビンソン


1

値型は(consider )nullと等しくなる可能性がありますが、にすることはできません。あなたの場合、変数とは暗黙的にキャストされて比較されます。nullNullable<>intnullNullable<Int32>


0

テストが偽になることは決してないので、ILを生成するときにコンパイラによって特定のテストが最適化されているだけだと思います。

補足:null許容のInt32でInt32を使用することは可能ですか?代わりにx。


0

これは、「==」がパラメータSystem.Object.Equalsを受け入れるメソッドの呼び出しを実際に表すシンタックスシュガーだからだと思いSystem.Objectます。ECMA仕様によるNullは、もちろんから派生した特殊な型System.Objectです。

そのため、警告のみが表示されます。


これは、2つの理由で正しくありません。まず、==はObject.Equalsと同じセマンティクスを持たず、引数の1つが参照型の場合です。第二に、nullはタイプではありません。参照等式演算子がどのように機能するかを理解したい場合は、仕様のセクション7.9.6を参照してください。
Eric Lippert

「nullリテラル(§9.4.4.6)は、オブジェクトまたは配列を指していない参照、または値がないことを示すために使用されるnull値に評価されます。nullタイプには単一の値があります。これはnullです。したがって、型がnull型である式は、null値にのみ評価できます。null型を明示的に書き込む方法はないため、宣言された型で使用する方法はありません。」-これはECMAからの引用です。あなたは何について話していますか?また、どのバージョンのECMAを使用していますか?私の中に7.9.6は見当たりません。
Vitaly

0

[編集:警告をエラーにし、演算子を文字列ハックではなくnull許容型について明示的にしました。]

上記のコメントでの@supercatの巧妙な提案によると、次の演算子のオーバーロードにより、カスタム値型とnullの比較に関するエラーを生成できます。

タイプのnull許容バージョンと比較する演算子を実装することにより、比較でnullを使用すると、null許容バージョンの演算子と一致します。これにより、Obsolete属性を介してエラーを生成できます。

マイクロソフトからコンパイラの警告が返されるまで、この回避策を使用します。@ supercatに感謝します!

public struct Foo
{
    private readonly int x;
    public Foo(int x)
    {
        this.x = x;
    }

    public override string ToString()
    {
        return string.Format("Foo {{x={0}}}", x);
    }

    public override int GetHashCode()
    {
        return x.GetHashCode();
    }

    public override bool Equals(Object obj)
    {
        return x.Equals(obj);
    }

    public static bool operator ==(Foo a, Foo b)
    {
        return a.x == b.x;
    }

    public static bool operator !=(Foo a, Foo b)
    {
        return a.x != b.x;
    }

    [Obsolete("The result of the expression is always 'false' since a value of type 'Foo' is never equal to 'null'", true)]
    public static bool operator ==(Foo a, Foo? b)
    {
        return false;
    }
    [Obsolete("The result of the expression is always 'true' since a value of type 'Foo' is never equal to 'null'", true)]
    public static bool operator !=(Foo a, Foo? b)
    {
        return true;
    }
    [Obsolete("The result of the expression is always 'false' since a value of type 'Foo' is never equal to 'null'", true)]
    public static bool operator ==(Foo? a, Foo b)
    {
        return false;
    }
    [Obsolete("The result of the expression is always 'true' since a value of type 'Foo' is never equal to 'null'", true)]
    public static bool operator !=(Foo? a, Foo b)
    {
        return true;
    }
}

私が何かを見逃していない限りFoo a; Foo? b; ... if (a == b)...、そのような比較は完全に正当であるはずですが、あなたのアプローチはコンパイラーを混乱させるでしょう。私が「ストリングハック」を提案した理由は、それが上記の比較を可能にするが、でしゃがむからif (a == null)です。を使用する代わりにstringObjectまたはValueType;以外の任意の参照型に置き換えることができます。必要に応じて、呼び出すことのできないプライベートコンストラクターを使用してダミークラスを定義し、資格を与えることができReferenceThatCanOnlyBeNullます。
スーパーキャット2015

あなたは絶対に正しいです。私の提案がnullableの使用を壊していることを明確にすべきでした...私が作業しているコードベースでは、とにかく罪深いと見なされています(不要なボクシングなど)。;)
ヨーヨー2015

0

コンパイラがこれを受け入れる理由についての最良の答えは、ジェネリッククラスの場合だと思います。次のクラスを考えてみましょう...

public class NullTester<T>
{
    public bool IsNull(T value)
    {
        return (value == null);
    }
}

コンパイラーがnull値型に対する比較を受け入れなかった場合、コンパイラーは本質的にこのクラスを壊し、型パラメーターに暗黙の制約が付加されます(つまり、値ベースでない型でのみ機能します)。


0

コンパイラを使用すると、実装している構造体を比較できます。 ==ををnullます。intとnullを比較することもできます(ただし、警告が表示されます)。

ただし、コードを逆アセンブルすると、コードのコンパイル時に比較が解決されていることがわかります。したがって、たとえば、このコード(Foo構造体を実装する場所==):

public static void Main()
{
    Console.WriteLine(new Foo() == new Foo());
    Console.WriteLine(new Foo() == null);
    Console.WriteLine(5 == null);
    Console.WriteLine(new Foo() != null);
}

このILを生成します:

.method public hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       45 (0x2d)
  .maxstack  2
  .locals init ([0] valuetype test3.Program/Foo V_0)
  IL_0000:  nop
  IL_0001:  ldloca.s   V_0
  IL_0003:  initobj    test3.Program/Foo
  IL_0009:  ldloc.0
  IL_000a:  ldloca.s   V_0
  IL_000c:  initobj    test3.Program/Foo
  IL_0012:  ldloc.0
  IL_0013:  call       bool test3.Program/Foo::op_Equality(valuetype test3.Program/Foo,
                                                           valuetype test3.Program/Foo)
  IL_0018:  call       void [mscorlib]System.Console::WriteLine(bool)
  IL_001d:  nop
  IL_001e:  ldc.i4.0
  IL_001f:  call       void [mscorlib]System.Console::WriteLine(bool)
  IL_0024:  nop
  IL_0025:  ldc.i4.1
  IL_0026:  call       void [mscorlib]System.Console::WriteLine(bool)
  IL_002b:  nop
  IL_002c:  ret
} // end of method Program::Main

ご覧のように:

Console.WriteLine(new Foo() == new Foo());

翻訳されます:

IL_0013:  call       bool test3.Program/Foo::op_Equality(valuetype test3.Program/Foo,
                                                               valuetype test3.Program/Foo)

一方:

Console.WriteLine(new Foo() == null);

falseに翻訳されます:

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