С# в порядке со сравнением типов значений с нулевым

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

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

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

Int32 x = null;

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

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

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

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

Ответ 1

Это законно, потому что разрешение перегрузки оператора имеет уникальный лучший оператор для выбора. Существует оператор ==, который принимает два значения NULL. Внутренний объект int преобразуется в значение null. Нулевой литерал преобразуется в значение с нулевым значением int. Поэтому это законное использование оператора == и всегда приводит к ложному.

Аналогично, мы также разрешаем вам говорить "if (x == 12.6)", который также всегда будет ложным. Внутренний является конвертируемым в double, буквально конвертируется в double, и, очевидно, они никогда не будут равными.

Ответ 2

Это не ошибка, так как есть преобразование (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);
}

Ответ 3

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

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

Вот почему есть только предупреждение.

Ответ 8

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

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

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

Пока 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;
    }
}

Ответ 9

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

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

Если компилятор не принимал сравнения с null для типов значений, тогда он существенно побил бы этот класс, имея неявное ограничение, связанное с его параметром типа (то есть оно будет работать только с типами, не основанными на стоимости).