web-dev-qa-db-fra.com

C # ok avec la comparaison des types de valeur à null

Je me suis heurté à cela aujourd'hui et je ne sais pas pourquoi le compilateur C # ne génère pas d'erreur.

Int32 x = 1;
if (x == null)
{
    Console.WriteLine("What the?");
}

Je ne comprends pas comment x pourrait être nul. D'autant que cette affectation génère définitivement une erreur de compilation:

Int32 x = null;

Est-il possible que x devienne nul, Microsoft a-t-il simplement décidé de ne pas mettre cette vérification dans le compilateur, ou a-t-il été complètement oublié?

Mise à jour: Après avoir manipulé le code pour écrire cet article, le compilateur a soudainement averti que l'expression ne serait jamais vraie. Maintenant je suis vraiment perdu. J'ai placé l'objet dans une classe et maintenant l'avertissement est parti mais il reste la question: un type de valeur peut-il finir par être nul?.

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

    public Test ()
    {
        Test test = new Test();
        if (test.ADate == null)
        {
            Console.WriteLine("What the?");
        }
    }
}
83
Joshua Belden

Ceci est légal car la résolution de la surcharge de l'opérateur a un choix unique d'opérateur. Il existe un opérateur == qui prend deux ints nullables. Le int local est convertible en int nullable. Le littéral nul est convertible en un int nullable. Il s’agit donc d’un usage légal de l’opérateur == et donnera toujours la valeur false.

De même, nous vous permettons également de dire "if (x == 12.6)", ce qui sera toujours faux. Le int local est convertible en un double, le littéral est convertible en un double, et évidemment ils ne seront jamais égaux.

114
Eric Lippert

Ce n'est pas une erreur, car il y a une conversion (int?); cela génère un avertissement dans l'exemple donné:

Le résultat de l'expression est toujours 'faux' car une valeur de type 'int' n'est jamais égale à 'null' de type 'int?'

Si vous vérifiez l'IL, vous verrez qu'il complètement supprime la branche inaccessible - elle n'existe pas dans une version validée.

Notez cependant que ne ne génère pas cet avertissement pour les structures personnalisées avec des opérateurs d'égalité. Il était utilisé dans la version 2.0, mais pas dans le compilateur 3.0. Le code est toujours supprimé (il sait donc que le code est inaccessible), mais aucun avertissement n'est généré:

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
    }
}

Avec l'IL (pour Main) - la note tout à l'exception de MyValue(1) (qui pourrait avoir des effets secondaires) a été supprimée:

.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 
}

c'est fondamentalement:

private static void Main()
{
    MyValue v = new MyValue(1);
}
17
Marc Gravell

Le fait qu'une comparaison ne puisse jamais être vraie ne signifie pas que c'est illégal. Néanmoins, non, un type de valeur peut jamais être null.

5
Adam Robinson

Non, Int32 x ne deviendra jamais null

Si vous comparez un int à null alors l'opérateur de comparaison que prend deux int? s est applicable.

"Pourquoi une comparaison d'un type de valeur avec null est un avertissement?" article va vous aider.

3
Li0liQ

Un type de valeur ne peut pas être null, bien qu'il puisse être égal à null (considérez Nullable<>). Dans votre cas, les variables int et null sont implicitement converties en Nullable<Int32> et comparées.

1
Greg

[EDITED: convertit les avertissements en erreurs et expliqua les opérateurs à propos de nullable plutôt que du hack de chaîne.]

Selon la suggestion astucieuse de @ supercat dans un commentaire ci-dessus, les surcharges d'opérateur suivantes vous permettent de générer une erreur concernant les comparaisons de votre type de valeur personnalisé avec la valeur null.

En implémentant des opérateurs comparant les versions nullables de votre type, l'utilisation de null dans une comparaison correspond à la version nullable de l'opérateur, ce qui vous permet de générer l'erreur via l'attribut Obsolete.

Jusqu'à ce que Microsoft nous renvoie notre avertissement du compilateur, je vais utiliser cette solution de contournement, merci @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
yoyo

Je suspecte que votre compilateur optimise juste votre test particulier lorsqu'il génère l'IL car le test ne sera jamais faux.

Note latérale: Il est possible d’avoir une utilisation nullable Int32 Int32? x à la place.

0
GrayWizardx

Je pense que la meilleure réponse quant à pourquoi le compilateur accepte cela est pour les classes génériques. Considérez le cours suivant ...

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

Si le compilateur n'accepte pas les comparaisons avec null pour les types de valeur, il cassera essentiellement cette classe en ayant une contrainte implicite attachée à son paramètre de type (c'est-à-dire qu'il ne fonctionnera qu'avec des types non basés sur des valeurs).

0
Lee.J.Baxter

J'imagine que c'est parce que "==" est un sucre de syntaxe qui représente en fait l'appel à la méthode System.Object.Equals qui accepte le paramètre System.Object. La spécification Null by ECMA est un type spécial qui est bien entendu dérivé de System.Object.

C'est pourquoi il n'y a qu'un avertissement.

0
Vitaly

Le compilateur vous permettra de comparer toute structure implémentant le == à null. Il vous permet même de comparer un int à null (vous obtiendrez cependant un avertissement).

Mais si vous désassemblez le code, vous verrez que la comparaison est en cours de résolution lorsque le code est compilé. Ainsi, par exemple, ce code (où Foo est une structure implémentant ==):

public static void Main()
{
    Console.WriteLine(new Foo() == new Foo());
    Console.WriteLine(new Foo() == null);
    Console.WriteLine(5 == null);
    Console.WriteLine(new Foo() != null);
}

Génère cette 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

Comme vous pouvez le voir:

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

Est traduit à:

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

Tandis que:

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

Est traduit à faux:

IL_001e:  ldc.i4.0
0
hardkoded