Я столкнулся с этим сегодня и понятия не имею, почему компилятор C # не выдает ошибку.
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?");
}
}
}
if (1 == 2)
. Анализ пути кода - не задача компилятора; для этого нужны инструменты статического анализа и модульные тесты.int
, компилятор генерирует приятные предупреждения. Для простых типов==
оператор определяется спецификацией языка C #. Для других структур (не простого типа) компилятор забывает выдать предупреждение. Дополнительные сведения см. В разделе « Неправильное предупреждение компилятора при сравнении struct с null» . Для структур, которые не являются простыми типами,==
оператор должен быть перегруженopeartor ==
методом, который является членом структуры (в противном случае==
разрешено «нет»).Ответы:
Это законно, потому что решение о перегрузке оператора позволяет выбрать лучшего оператора. Существует оператор ==, который принимает два целых числа, допускающих значение NULL. Int local можно преобразовать в обнуляемое int. Литерал null можно преобразовать в int, допускающий значение null. Следовательно, это законное использование оператора == и всегда приводит к ложному результату.
Точно так же мы также разрешаем вам сказать «if (x == 12.6)», что также всегда будет ложным. Int local можно преобразовать в double, литерал можно преобразовать в double, и, очевидно, они никогда не будут равны.
источник
static bool operator == (SomeID a, String b)
теговObsolete
? Если второй операнд является нетипизированным литераломnull
, это будет лучше, чем любая форма, требующая использования поднятых операторов, но если это a,SomeID?
который оказывается равным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
.источник
null
. Подумайтеint?
, что является синтаксическим сахаром дляNullable<Int32>
, что является типом значения. Переменная типаint?
определенно может быть равнаnull
.==
оператора. Однако важно отметить, что экземпляр на самом деле не является нулевым.Нет,
Int32 x
никогда не станетnull
.«Почему сравнение типа значения с null является предупреждением?» статья вам поможет.
источник
Тип значения не может быть
null
, хотя он может быть равенnull
(рассмотримNullable<>
). В вашем случаеint
переменная иnull
неявно приводятсяNullable<Int32>
и сравниваются.источник
Я подозреваю, что ваш конкретный тест просто оптимизируется компилятором, когда он генерирует IL, поскольку тест никогда не будет ложным.
Боковое примечание: возможно ли использовать Int32 для nullable Int32? x вместо этого.
источник
Я предполагаю, что это потому, что "==" - это синтаксический сахар, который фактически представляет вызов
System.Object.Equals
метода, который принимаетSystem.Object
параметр. Null по спецификации ECMA - это особый тип, который, конечно же, является производным отSystem.Object
.Вот почему есть только предупреждение.
источник
[EDITED: превратили предупреждения в ошибки и сделали операторы явными для определения значения NULL, а не для взлома строки.]
Согласно умному предложению @supercat в комментарии выше, следующие перегрузки операторов позволяют генерировать ошибку при сравнении вашего настраиваемого типа значения с null.
Реализуя операторы, которые сравниваются с версиями вашего типа, допускающими значение NULL, использование NULL в сравнении соответствует версии оператора, допускающей значение 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; } }
источник
Foo a; Foo? b; ... if (a == b)...
, даже если такое сравнение должно быть совершенно законным. Причина, по которой я предложил "взлом строки", заключается в том, что он позволяет проводить вышеупомянутое сравнение, но не отвечаетif (a == null)
. Вместо использованияstring
можно заменить любой ссылочный тип, отличный отObject
илиValueType
; при желании можно определить фиктивный класс с частным конструктором, который никогда не может быть вызван и присвоить ему правоReferenceThatCanOnlyBeNull
.Я думаю, что лучший ответ на вопрос, почему компилятор принимает это, касается общих классов. Рассмотрим следующий класс ...
public class NullTester<T> { public bool IsNull(T value) { return (value == null); } }
Если компилятор не принимал сравнения
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
источник