web-dev-qa-db-fra.com

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

J'ai par pur 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 != est traduit par "> "?

147
stakx

Réponse courte:

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

Il existe 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 un peu plus long équivalent (x == y) == false.

Il y a aussi une instruction "compare-supérieur-que" en IL (cgt) qui permet au compilateur de prendre certains raccourcis (ie générer un code IL plus court), l'une étant que les comparaisons d'inégalité d'objets par rapport à null, obj != null, sont traduites comme si elles étaient "obj > null".


Allons plus en détail.

S'il n'y a pas d'instruction "compare-not-equal" en 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 lorsque nous remplaçons y par la constante 0:

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

L'IL produit est un peu plus court 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 complément à deux (où, si les modèles de bits résultants sont interprétés comme des entiers non signés - c'est ce que signifie .un - 0 a la plus petite valeur possible), il traduit donc x == 0 comme s'il s'agissait de unchecked((uint)x) > 0.

Il s'avère que le compilateur peut faire de même pour les vérifications d'inégalité contre null:

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

Le compilateur produit presque le 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 modèle binaire de la référence null est le plus petit modèle binaire possible pour toute référence d'objet.

Ce raccourci est explicitement mentionné dans le Common Language Infrastructure Annotated Standard (1ère édition d'octobre 2003) (à la page 491, en tant que note de bas de page du tableau 6-4, "Comparaisons binaires ou opérations de branche"):

"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'instruction" compare-not-equal ", qui serait autrement une solution plus évidente). "

201
stakx