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 ответов:
Это законно, потому что разрешение перегрузки оператора имеет уникальный Лучший оператор на выбор. Существует оператор==, который принимает два 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