Симон Моурир дал этот пример :
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<>
экземпляров.
Это законно, потому что разрешение перегрузки оператора имеет уникальный лучший оператор для выбора. Существует оператор ==, который принимает два значения NULL. Внутренний объект int преобразуется в значение null. Нулевой литерал преобразуется в значение с нулевым значением int. Поэтому это законное использование оператора == и всегда будет приводить к ложному.
Аналогичным образом мы также разрешаем вам сказать «if (x == 12.6)», который также всегда будет ложным , Интер-локаль преобразуется в double, буквальство конвертируется в double, и, очевидно, они никогда не будут равны.
Тот факт, что сравнение никогда не может быть правдой, не означает, что это незаконно. Тем не менее, нет, тип значения может быть null
.
Я подозреваю, что ваш конкретный тест просто оптимизируется компилятором, когда он генерирует IL, так как тест никогда не будет ложным.
Сторона Примечание: возможно использование Intable Int32 Int32 ? x вместо этого.
Тип значения не может быть null
, хотя он может быть равен null
(рассмотрим Nullable<>
). В вашем случае переменная int
и null
неявно передаются в Nullable<Int32>
и сравниваются.
Компилятор позволит вам сравнить любую структуру, реализующую ==
значение 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
Я думаю, что лучший ответ о том, почему компилятор принимает это, относится к родовым классам. Рассмотрим следующий класс ...
public class NullTester<T>
{
public bool IsNull(T value)
{
return (value == null);
}
}
Если компилятор не принимал сравнения с null
для типов значений, тогда он существенно побил бы этот класс, имея неявное ограничение, связанное с его параметром типа (т. е. он будет работать только с типами, не основанными на стоимости).
Нет, Int32 x
никогда не станет null
.
Если вы сравниваете int с нулем, то применим оператор сравнения, который принимает два int. s
blockquote>«Почему сравнение тип значения с нулем является предупреждением? » статья поможет вам.
Это не ошибка, так как существует преобразование (int?
); он генерирует предупреждение в приведенном примере:
Результат выражения всегда «false», поскольку значение типа «int» никогда не равно «null» типа 'int? '
blockquote>Если вы проверите 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); }
Я предполагаю, что это потому, что «==» представляет собой синтаксический сахар, который фактически представляет собой вызов метода System.Object.Equals
, который принимает параметр System.Object
. Null по спецификации ECMA - это особый тип, который, конечно, получен из System.Object
.
Вот почему есть только предупреждение.
[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;
}
}