Natknąłem się na to dzisiaj i nie mam pojęcia, dlaczego kompilator C # nie zgłasza błędu.
Int32 x = 1;
if (x == null)
{
Console.WriteLine("What the?");
}
Nie wiem, jak x może kiedykolwiek być zerowe. Zwłaszcza, że to przypisanie zdecydowanie generuje błąd kompilatora:
Int32 x = null;
Czy to możliwe, że x może stać się zerowe, czy Microsoft po prostu zdecydował nie umieszczać tego sprawdzenia w kompilatorze, czy też całkowicie go pominięto?
Aktualizacja: Po pomieszaniu z kodem, aby napisać ten artykuł, nagle kompilator pojawił się z ostrzeżeniem, że wyrażenie nigdy nie będzie prawdziwe. Teraz jestem naprawdę zagubiony. Umieściłem obiekt w klasie i teraz ostrzeżenie zniknęło, ale zostawiłem pytanie, czy typ wartości może być pusty.
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)
. Nie jest zadaniem kompilatora przeprowadzanie analizy ścieżki kodu; do tego służą narzędzia do analizy statycznej i testy jednostkowe.int
, kompilator generuje ładne ostrzeżenia. W przypadku typów prostych==
operator jest zdefiniowany w specyfikacji języka C #. W przypadku innych struktur (nie typu prostego) kompilator zapomina o wyświetleniu ostrzeżenia. Aby uzyskać szczegółowe informacje, zobacz nieprawidłowe ostrzeżenie kompilatora podczas porównywania struktury z wartością null . W przypadku struktur, które nie są typami prostymi,==
operator musi być przeciążonyopeartor ==
metodą, która jest elementem członkowskim struktury (w przeciwnym razie nie==
jest dozwolone).Odpowiedzi:
Jest to zgodne z prawem, ponieważ rozwiązanie przeciążenia operatora ma do wyboru jedynego najlepszego operatora. Istnieje operator ==, który przyjmuje dwie liczby całkowite dopuszczające wartość null. Int local można zamienić na int nullable. Literał o wartości null można zamienić na int o wartości null. Dlatego jest to legalne użycie operatora == i zawsze skutkuje fałszem.
Podobnie, pozwalamy ci powiedzieć „if (x == 12,6)”, co również zawsze będzie fałszywe. Int local można zamienić na double, literał można zamienić na double i oczywiście nigdy nie będą równe.
źródło
static bool operator == (SomeID a, String b)
i tagowaniem goObsolete
? Jeśli drugi operand jest nietypowym literałemnull
, byłoby to lepsze dopasowanie niż jakakolwiek forma wymagająca użycia podniesionych operatorów, ale jeśli jest to a,SomeID?
co jest równenull
, wygrałby operator podniesiony.To nie jest błąd, ponieważ występuje
int?
konwersja ( ); generuje ostrzeżenie w podanym przykładzie:Jeśli zaznaczysz IL, zobaczysz, że całkowicie usuwa nieosiągalną gałąź - nie istnieje w kompilacji wydania.
Należy jednak pamiętać, że nie generuje tego ostrzeżenia dla niestandardowych struktur z operatorami równości. Kiedyś w 2.0, ale nie w kompilatorze 3.0. Kod jest nadal usuwany (dzięki czemu wie, że kod jest nieosiągalny), ale nie jest generowane żadne ostrzeżenie:
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 } }
Z IL (for
Main
) - zauważ, że wszystko opróczMyValue(1)
(które może mieć skutki uboczne) zostało usunięte:.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 }
to jest w zasadzie:
private static void Main() { MyValue v = new MyValue(1); }
źródło
Fakt, że porównanie nigdy nie może być prawdziwe, nie oznacza, że jest nielegalne. Niemniej jednak nie, typ wartości może być kiedykolwiek
null
.źródło
null
. Zastanów sięint?
, dla czego jest cukier syntaktycznyNullable<Int32>
, który jest typem wartości. Zint?
pewnością zmienna typu mogłaby być równanull
.==
operatora. Należy jednak pamiętać, że instancja nie jest w rzeczywistości zerowa.Nie,
Int32 x
nigdy się nie stanienull
.„Dlaczego porównanie typu wartości z wartością null jest ostrzeżeniem?” artykuł pomoże.
źródło
Typ wartości nie może być
null
, chociaż może być równynull
(rozważNullable<>
). W twoim przypadkuint
zmienne inull
są niejawnie rzutowaneNullable<Int32>
i porównywane.źródło
Podejrzewam, że twój konkretny test jest właśnie optymalizowany przez kompilator, kiedy generuje IL, ponieważ test nigdy nie będzie fałszywy.
Uwaga boczna: możliwe jest użycie Int32 z dopuszczalną wartością null? x zamiast tego.
źródło
Wydaje mi się, że dzieje się tak, ponieważ „==” to cukier składni, który w rzeczywistości reprezentuje wywołanie
System.Object.Equals
metody, która akceptujeSystem.Object
parametr. Specyfikacja Null według ECMA to specjalny typ, z którego oczywiście wywodzi sięSystem.Object
.Dlatego jest tylko ostrzeżenie.
źródło
[EDYTOWANO: zamieniono ostrzeżenia w błędy i wyraźnie zaznaczono, że operatory mają wartość null, a nie ciąg znaków.]
Zgodnie ze sprytną sugestią @ supercat w powyższym komentarzu, poniższe przeciążenia operatorów pozwalają wygenerować błąd dotyczący porównania niestandardowego typu wartości do null.
Implementując operatory, które porównują się z wersjami dopuszczającymi wartość null danego typu, użycie wartości null w porównaniu jest zgodne z wersją operatora dopuszczającą wartość null, co umożliwia wygenerowanie błędu za pomocą atrybutu Obsolete.
Dopóki Microsoft nie zwróci nam naszego ostrzeżenia kompilatora, będę stosować to obejście, dzięki @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; } }
źródło
Foo a; Foo? b; ... if (a == b)...
, nawet jeśli takie porównanie powinno być całkowicie uzasadnione. Powodem, dla którego zasugerowałem "łamanie ciągów" jest to, że pozwoliłoby to na powyższe porównanie, ale wrzeszczałoif (a == null)
. Zamiast używaćstring
, można by zastąpić dowolny typ referencyjny inny niżObject
lubValueType
; w razie potrzeby można zdefiniować atrapę klasy z prywatnym konstruktorem, którego nigdy nie można wywołać i nadać jej uprawnieniaReferenceThatCanOnlyBeNull
.Myślę, że najlepszą odpowiedzią na pytanie, dlaczego kompilator akceptuje to jest dla klas ogólnych. Rozważ następującą klasę ...
public class NullTester<T> { public bool IsNull(T value) { return (value == null); } }
Gdyby kompilator nie akceptował porównań z
null
typami wartości, to zasadniczo przerwałby tę klasę, mając niejawne ograniczenie dołączone do jej parametru typu (tj. Działałby tylko z typami nieopartymi na wartościach).źródło
Kompilator pozwoli ci porównać dowolną strukturę implementującą
==
to null. Pozwala nawet porównać wartość int do null (ale pojawi się ostrzeżenie).Ale jeśli zdemontujesz kod, zobaczysz, że porównanie jest rozwiązywane, gdy kod jest kompilowany. Na przykład ten kod (gdzie
Foo
jest implementacją struktury==
):public static void Main() { Console.WriteLine(new Foo() == new Foo()); Console.WriteLine(new Foo() == null); Console.WriteLine(5 == null); Console.WriteLine(new Foo() != null); }
Generuje ten 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
Jak widzisz:
Console.WriteLine(new Foo() == new Foo());
Jest tłumaczone na:
IL_0013: call bool test3.Program/Foo::op_Equality(valuetype test3.Program/Foo, valuetype test3.Program/Foo)
Natomiast:
Console.WriteLine(new Foo() == null);
Jest tłumaczone na fałsz:
IL_001e: ldc.i4.0
źródło