J'ai rencontré cela aujourd'hui et je n'ai aucune idée de la raison pour laquelle le compilateur C # ne lève pas d'erreur.
Int32 x = 1;
if (x == null)
{
Console.WriteLine("What the?");
}
Je ne sais pas comment x pourrait être nul. D'autant plus que cette affectation lève 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-elle été complètement manquée?
Mise à jour: Après avoir manipulé le code pour écrire cet article, le compilateur a soudainement lancé un avertissement indiquant que l'expression ne serait jamais vraie. Maintenant je suis vraiment perdu. J'ai mis l'objet dans une classe et maintenant l'avertissement a disparu mais reste avec 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?");
}
}
}
if (1 == 2)
. Ce n'est pas le travail du compilateur d'effectuer une analyse de chemin de code; c'est à cela que servent les outils d'analyse statique et les tests unitaires.int
, le compilateur génère de beaux avertissements. Pour les types simples, l'==
opérateur est défini par la spécification du langage C #. Pour les autres structures (pas de type simple), le compilateur oublie d'émettre un avertissement. Consultez l' avertissement du compilateur incorrect lors de la comparaison de struct à null pour plus de détails. Pour les structures qui ne sont pas des types simples, l'==
opérateur doit être surchargé par uneopeartor ==
méthode qui est membre de la structure (sinon no==
est autorisé).Réponses:
Ceci est légal car la résolution de surcharge de l'opérateur a un meilleur opérateur unique à choisir. Il existe un opérateur == qui prend deux entiers Nullable. L'int local est convertible en un int nullable. Le littéral nul est convertible en un entier nullable. Il s'agit donc d'une utilisation légale de l'opérateur ==, et aboutira toujours à false.
De même, nous vous permettons également de dire "si (x == 12.6)", qui sera également toujours faux. L'int local est convertible en double, le littéral est convertible en double, et évidemment ils ne seront jamais égaux.
la source
static bool operator == (SomeID a, String b)
et de le taguerObsolete
? Si le deuxième opérande est un littéral non typénull
, ce serait une meilleure correspondance que toute forme nécessitant l'utilisation d'opérateurs levés, mais si c'est unSomeID?
qui se trouve être égalnull
, l'opérateur levé gagnerait.Ce n'est pas une erreur, car il y a une
int?
conversion ( ); il génère un avertissement dans l'exemple donné:Si vous vérifiez l'IL, vous verrez qu'il supprime complètement la branche inaccessible - elle n'existe pas dans une version de version.
Notez cependant qu'il ne génère pas cet avertissement pour les structures personnalisées avec des opérateurs d'égalité. Il le faisait auparavant dans 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
) - notez que tout sauf leMyValue(1)
(qui pourrait avoir des effets secondaires) a été supprimé:.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 essentiellement:
private static void Main() { MyValue v = new MyValue(1); }
la source
Le fait qu'une comparaison ne puisse jamais être vraie ne signifie pas qu'elle est illégale. Néanmoins, non, un type valeur peut jamais être
null
.la source
null
. Considérezint?
, quel est le sucre syntaxique pourNullable<Int32>
, qui est un type valeur. Une variable de typeint?
pourrait certainement être égale ànull
.==
opérateur. Cependant, il est important de noter que l'instance n'est pas réellement nulle.Non,
Int32 x
ne deviendra jamaisnull
."Pourquoi une comparaison d'un type valeur avec null est un avertissement?" l'article vous aidera.
la source
Un type valeur ne peut pas être
null
, bien qu'il puisse être égal ànull
(considérerNullable<>
). Dans votre cas, laint
variable etnull
sont implicitement convertiesNullable<Int32>
et comparées.la source
Je soupçonne que votre test particulier est simplement optimisé par le compilateur lorsqu'il génère l'IL puisque le test ne sera jamais faux.
Note latérale: Il est possible qu'un Int32 nullable utilise Int32? x à la place.
la source
Je suppose que c'est parce que "==" est un sucre de syntaxe qui représente en fait un appel à une
System.Object.Equals
méthode qui accepte unSystem.Object
paramètre. La spécification Null par ECMA est un type spécial qui est bien sûr dérivé deSystem.Object
.C'est pourquoi il n'y a qu'un avertissement.
la source
[EDITED: a fait des avertissements en erreurs, et a rendu les opérateurs explicites sur nullable plutôt que sur le hack de chaîne.]
Conformément à la suggestion intelligente de @ supercat dans un commentaire ci-dessus, les surcharges d'opérateurs suivantes vous permettent de générer une erreur sur les comparaisons de votre type de valeur personnalisé à null.
En implémentant des opérateurs qui comparent aux versions Nullable 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 rende notre avertissement de 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; } }
la source
Foo a; Foo? b; ... if (a == b)...
, même si une telle comparaison devrait être parfaitement légitime. La raison pour laquelle j'ai suggéré le "hack de chaîne" est que cela permettrait la comparaison ci-dessus mais grincer des dentsif (a == null)
. Au lieu d'utiliserstring
, on pourrait remplacer n'importe quel type de référence autre queObject
ouValueType
; si on le souhaite, on pourrait définir une classe factice avec un constructeur privé qui ne pourrait jamais être appelé et lui donner le droitReferenceThatCanOnlyBeNull
.Je pense que la meilleure réponse pour savoir pourquoi le compilateur accepte cela concerne les classes génériques. Considérez la classe suivante ...
public class NullTester<T> { public bool IsNull(T value) { return (value == null); } }
Si le compilateur n'acceptait pas les comparaisons avec les
null
types valeur, alors il casserait essentiellement cette classe, ayant une contrainte implicite attachée à son paramètre de type (c'est-à-dire qu'il ne fonctionnerait qu'avec des types non basés sur des valeurs).la source
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émontez le code, vous verrez que la comparaison est en cours de résolution lorsque le code est compilé. Donc, par exemple, ce code (où
Foo
est une struct implémentation==
):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 cet 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 en:
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 en faux:
IL_001e: ldc.i4.0
la source