C # okay z porównywaniem typów wartości do null

Wpadłem na to dzisiaj i nie mam pojęcia, dlaczego kompilator C# nie wyrzuca błędu.

Int32 x = 1;
if (x == null)
{
    Console.WriteLine("What the?");
}
Nie wiem, jak x może być null. Zwłaszcza, że to zadanie zdecydowanie rzuca błąd kompilatora:
Int32 x = null;

Czy to możliwe, że x może stać się null, czy Microsoft zdecydował się po prostu nie umieszczać tego sprawdzenia w kompilatorze, czy został całkowicie pominięty?

Update: po zamieszaniu w kod do napisania tego artykułu, nagle kompilator wymyślił ostrzeżenie, ż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 pozostało z pytaniem, czy typ wartości może skończyć się null.

public class Test
{
    public DateTime ADate = DateTime.Now;

    public Test ()
    {
        Test test = new Test();
        if (test.ADate == null)
        {
            Console.WriteLine("What the?");
        }
    }
}
 81
Author: Joshua Belden, 2009-12-29

10 answers

Jest to legalne, ponieważ operator overload resolution ma unikalny najlepszy operator do wyboru. Istnieje operator==, który przyjmuje dwa nullable int. Int local jest zamieniany na nullable int. Literał null jest zamieniany na nullable int. Dlatego jest to legalne użycie operatora == i zawsze będzie skutkowało false.

Podobnie, pozwalamy również powiedzieć " if (x == 12.6)", co również zawsze będzie fałszywe. Int lokalny jest zamieniany na podwójny, literalny jest zamienione na podwójne i oczywiście nigdy nie będą równe.

 110
Author: Eric Lippert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-29 00:28:48

Nie jest to błąd, ponieważ istnieje konwersja (int?); generuje ostrzeżenie w podanym przykładzie:

Wynik wyrażenia jest zawsze 'false', ponieważ wartość typu ' int 'nigdy nie jest równa 'null' typu ' int?'

Jeśli sprawdzisz IL, zobaczysz, że całkowicie usuwa nieosiągalną gałąź - nie istnieje ona w release build.

Zauważ jednak, że nie generuje tego ostrzeżenia dla niestandardowych struktur z operatory równości. Kiedyś w 2.0, ale nie w kompilatorze 3.0. Kod jest nadal usuwany (więc wie, że kod jest nieosiągalny), ale żadne ostrzeżenie nie jest generowane:

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 (dla Main) - UwagaWszystko z wyjątkiem MyValue(1) (które mogą 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 w zasadzie:

private static void Main()
{
    MyValue v = new MyValue(1);
}
 16
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-29 00:36:48

Fakt, że porównanie nigdy nie może być prawdziwe, nie oznacza, że jest nielegalne. Niemniej jednak nie, typem wartości może być null.

 5
Author: Adam Robinson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-09 09:51:37

Nie, Int32 x nigdy nie stanie się null.

Jeśli porównujesz int do null następnie operator porównawczy, który bierze dwa int?s ma zastosowanie.

"dlaczego porównanie typu wartości z null jest ostrzeżeniem?" artykuł ci pomoże.

 3
Author: Li0liQ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-29 00:24:34

Typ wartości nie może być null, chociaż może być równy null (rozważmy Nullable<>). W Twoim przypadku zmienna int i {[0] } są domyślnie rzucane do Nullable<Int32> i porównywane.

 1
Author: Greg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-29 00:42:17

Podejrzewam, że twój konkretny test jest po prostu optymalizowany przez kompilator, gdy generuje IL, ponieważ test nigdy nie będzie fałszywy.

Uwaga: możliwe jest, aby mieć nullable Int32 używać Int32? x zamiast.

 0
Author: GrayWizardx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-29 00:19:45

Domyślam się, że dzieje się tak dlatego, że "==" jest cukrem składniowym, który w rzeczywistości reprezentuje wywołanie metody System.Object.Equals, która akceptuje parametr System.Object. Null według specyfikacji ECMA jest specjalnym typem, który jest oczywiście pochodną System.Object.

Dlatego jest tylko Ostrzeżenie.
 0
Author: Vitaly,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-09 12:14:08

[EDITED: wprowadzono ostrzeżenia W błędy, a operatory wyraźnie mówiły o nullable, a nie o hakowaniu łańcuchów.]

Zgodnie z sprytną sugestią @supercat w komentarzu powyżej, następujące przeciążenia operatora pozwalają wygenerować błąd dotyczący porównania niestandardowego typu wartości do null.

Implementując operatory porównujące z wersjami typu null, użycie null w porównaniu pasuje do wersji operatora null, co pozwala na wygenerowanie błąd poprzez przestarzały atrybut.

Dopóki Microsoft nie zwróci nam naszego kompilatora Ostrzeżenie idę z tym obejściem, 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;
    }
}
 0
Author: yoyo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-26 19:20:49

Myślę, że najlepszą odpowiedzią na Dlaczego kompilator akceptuje to jest dla klas generycznych. Rozważ następującą klasę...

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

Jeśli kompilator nie zaakceptuje porównań z null dla typów wartości, to zasadniczo złamie tę klasę, mając do parametru type dołączone Ukryte ograniczenie(tzn. będzie działać tylko z typami nie bazującymi na wartości).

 0
Author: Lee.J.Baxter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-04 21:04:12

Kompilator pozwoli Ci porównać dowolną strukturę implementującą == do null. To nawet pozwala porównać int do null (można dostać ostrzeżenie choć).

Ale jeśli zdemontujesz kod, zobaczysz, że porównanie jest rozwiązywane podczas kompilacji kodu. Na przykład ten kod (gdzie {[7] } jest strukturą implementującą ==):

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 widać:

Console.WriteLine(new Foo() == new Foo());

Jest tłumaczone do:

IL_0013:  call       bool test3.Program/Foo::op_Equality(valuetype test3.Program/Foo,
                                                               valuetype test3.Program/Foo)

Console.WriteLine(new Foo() == null);

Jest tłumaczone na false:

IL_001e:  ldc.i4.0
 0
Author: hardkoded,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-27 14:47:55