C# хорошо при сравнении типов значений с null


я столкнулся с этим сегодня и понятия не имею, почему компилятор C# не выдает ошибку.

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

Я в замешательстве относительно того, как x может когда-либо быть нулевым. Тем более, что это назначение определенно вызывает ошибку компилятора:

Int32 x = null;

возможно ли, что x может стать null, Microsoft просто решила не помещать эту проверку в компилятор или она была полностью пропущена?

обновление: после возиться с кодом, чтобы написать эту статью, вдруг компилятор пришел с предупреждением, что выражение никогда не будет истинным. Теперь я действительно потерялся. Я поместил объект в класс, и теперь предупреждение ушло, но осталось с вопросом, Может ли тип значения оказаться нулевым.

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

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

10 ответов:

Это законно, потому что разрешение перегрузки оператора имеет уникальный Лучший оператор на выбор. Существует оператор==, который принимает два nullable ints. Int local преобразуется в nullable int. Литерал null преобразуется в nullable int. Поэтому это законное использование оператора==, и всегда будут ложными.

аналогично, мы также позволяем вам сказать " если (x == 12.6)", что также всегда будет ложным. Int local преобразуется в double, литерал-это конвертируемые в двойные, и очевидно, что они никогда не будут равны.

это не ошибка, так как есть (int?) преобразование; он генерирует предупреждение в приведенном примере:

результат выражения всегда 'false', так как значение типа 'int' никогда не равно 'null' типа 'int?-

если вы проверите 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 (для 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);
}

тот факт, что сравнение никогда не может быть истинным, не означает, что это незаконно. Тем не менее, нет, тип значения никогда не может быть null.

нет, Int32 x никогда не станет null.

Если вы сравниваете int с null тогда оператор сравнения, что принимает два int?s применимо.

" почему сравнение типа значения с null является предупреждением?" статья поможет вам.

тип значения не может быть null, хотя это может быть равно null (считать Nullable<>). В вашем случае int переменной и null неявно приводятся к Nullable<Int32> и по сравнению.

Я подозреваю, что ваш конкретный тест просто оптимизируется компилятором, когда он генерирует IL, так как тест никогда не будет ложным.

боковое Примечание:Можно ли использовать Int32 с нулевым значением Int32? x вместо этого.

Я думаю, это потому, что "= = " - это синтаксический сахар, который на самом деле представляет собой вызов System.Object.Equals метод, который принимает

[отредактировано: сделаны предупреждения на ошибки, и делали операторы четко, допускающий значение null вместо строки взломать.]

согласно умному предложению @supercat в комментарии выше, следующие перегрузки оператора позволяют вам генерировать ошибку о сравнении вашего пользовательского типа значения с null.

реализуя операторы, которые сравниваются с nullable версиями вашего типа, использование null в сравнении соответствует nullable версии оператора , что позволяет генерировать ошибку с помощью устаревшего атрибута.

пока Microsoft не вернет нам наше предупреждение компилятора я собираюсь с этим обходным путем, спасибо @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;
    }
}

Я думаю, что лучший ответ почему компилятор принимает это для универсальных классов. Рассмотрим следующий класс...

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

если компилятор не принимает сравнения с 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