Почему я могу проверить, равна ли Struct равным нулю? [Дубликат]

Симон Моурир дал этот пример :

object o = null;
DateTime d = (DateTime)o;  // NullReferenceException

, где unboxing преобразование (литье) из object (или из одного из классов System.ValueType или System.Enum или из типа интерфейса) - тип значения (кроме Nullable<>) сам по себе дает NullReferenceException.

В другом направлении конверсия бокса из a Nullable<>, которая имеет HasValue, равную false , на ссылочный тип, может дать ссылку null, которая затем может привести к NullReferenceException. Классический пример:

DateTime? d = null;
var s = d.ToString();  // OK, no exception (no boxing), returns ""
var t = d.GetType();   // Bang! d is boxed, NullReferenceException

Иногда бокс происходит по-другому. Например, с помощью этого не общего метода расширения:

public static void MyExtension(this object x)
{
  x.ToString();
}

следующий код будет проблематичным:

DateTime? d = null;
d.MyExtension();  // Leads to boxing, NullReferenceException occurs inside the body of the called method, not here.

Эти случаи возникают из-за специальных правил, используемых во время выполнения при боксе Nullable<> экземпляров.

81
задан Joshua Belden 7 October 2011 в 16:39
поделиться

10 ответов

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

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

110
ответ дан Eric Lippert 4 September 2018 в 07:05
поделиться

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

5
ответ дан AK_ 4 September 2018 в 07:05
поделиться

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

Сторона Примечание: возможно использование Intable Int32 Int32 ? x вместо этого.

0
ответ дан GrayWizardx 4 September 2018 в 07:05
поделиться

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

1
ответ дан Greg 4 September 2018 в 07:05
поделиться

Компилятор позволит вам сравнить любую структуру, реализующую == значение 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
0
ответ дан hardkoded 4 September 2018 в 07:05
поделиться

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

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

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

0
ответ дан Lee.J.Baxter 4 September 2018 в 07:05
поделиться

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

Если вы сравниваете int с нулем, то применим оператор сравнения, который принимает два int. s

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

3
ответ дан Li0liQ 4 September 2018 в 07:05
поделиться

Это не ошибка, так как существует преобразование (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);
}
16
ответ дан Marc Gravell 4 September 2018 в 07:05
поделиться

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

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

0
ответ дан Mathieu Devost 4 September 2018 в 07:05
поделиться

[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;
    }
}
0
ответ дан yoyo 4 September 2018 в 07:05
поделиться
Другие вопросы по тегам:

Похожие вопросы: