Pourquoi le compilateur C # traduit-il cette comparaison! = Comme s’il s’agissait d’une comparaison?

J’ai par hasard découvert que le compilateur C # transforme cette méthode:

static bool IsNotNull(object obj) { return obj != null; } 

… Dans ce CIL :

 .method private hidebysig static bool IsNotNull(object obj) cil managed { ldarg.0 // obj ldnull cgt.un ret } 

… Ou, si vous préférez regarder le code C # décompilé:

 static bool IsNotNull(object obj) { return obj > null; // (note: this is not a valid C# expression) } 

Comment se fait-il que le != Soit traduit par un ” > “?

    Réponse courte:

    Il n’y a pas d’instruction “compare-not-equal” dans IL, donc l’opérateur C # != N’a pas de correspondance exacte et ne peut pas être traduit littéralement.

    Il y a cependant une instruction “compare-equal” ( ceq , une correspondance directe avec l’opérateur == ), donc dans le cas général, x != y est traduit comme son équivalent légèrement plus long (x == y) == false .

    Il y a aussi une instruction “compare-than-than” dans IL ( cgt ) qui permet au compilateur de prendre certains raccourcis (c.-à-d. Générer un code IL plus court), l’un étant que les comparaisons d’inégalités des objects contre null, obj != null comme s’ils étaient ” obj > null “.


    Passons à plus de détails.

    S’il n’y a pas d’instruction “compare-not-equal” dans IL, alors comment la méthode suivante sera-t-elle traduite par le compilateur?

     static bool IsNotEqual(int x, int y) { return x != y; } 

    Comme déjà dit ci-dessus, le compilateur transformera le x != y en (x == y) == false :

     .method private hidebysig static bool IsNotEqual(int32 x, int32 y) cil managed { ldarg.0 // x ldarg.1 // y ceq ldc.i4.0 // false ceq // (note: two comparisons in total) ret } 

    Il s’avère que le compilateur ne produit pas toujours ce modèle assez long. Voyons ce qui se passe quand on remplace y par la constante 0:

     static bool IsNotZero(int x) { return x != 0; } 

    La IL produite est un peu plus courte que dans le cas général:

     .method private hidebysig static bool IsNotZero(int32 x) cil managed { ldarg.0 // x ldc.i4.0 // 0 cgt.un // (note: just one comparison) ret } 

    Le compilateur peut tirer parti du fait que les entiers signés sont stockés dans le complément à deux (si, si les motifs binarys résultants sont interprétés comme des entiers non signés – c’est ce .un signifie .un – 0 a la plus petite valeur possible), donc x == 0 comme si elle était unchecked((uint)x) > 0 .

    Il s’avère que le compilateur peut faire la même chose pour les vérifications d’inégalité par rapport à null :

     static bool IsNotNull(object obj) { return obj != null; } 

    Le compilateur produit presque la même IL que pour IsNotZero :

     .method private hidebysig static bool IsNotNull(object obj) cil managed { ldarg.0 ldnull // (note: this is the only difference) cgt.un ret } 

    Apparemment, le compilateur est autorisé à supposer que le motif binary de la référence null est le plus petit motif possible pour toute référence d’object.

    Ce raccourci est explicitement mentionné dans la norme annotée Common Language Infrastructure (1ère édition d’octobre 2003) (page 491, en tant que note du tableau 6-4, “Comparaisons binarys ou opérations de twig”):

    cgt.un est autorisé et vérifiable sur ObjectRefs (O). Ceci est couramment utilisé lors de la comparaison d’un ObjectRef avec null (il n’y a pas d’instructions” compare-not-equal “, ce qui serait une solution plus évidente).”