web-dev-qa-db-fra.com

Définition de l'opérateur "==" pour Double

Pour une raison quelconque, je me faufilais dans la source du .NET Framework pour la classe Double et j'ai découvert que la déclaration de == est:

public static bool operator ==(Double left, Double right) {
    return left == right;
}

La même logique s'applique pour chaque opérateur.


  • Quel est l'intérêt d'une telle définition?
  • Comment ça marche?
  • Pourquoi ne crée-t-il pas une récursion infinie?
125
Thomas Ayoub

En réalité, le compilateur tournera le == opérateur dans un code ceq IL, et l'opérateur que vous mentionnez ne sera pas appelé.

La raison de l'opérateur dans le code source est probablement qu'il peut être appelé à partir de langages autres que C # qui ne le traduisent pas directement en appel CEQ (ou par réflexion). Le code à l'intérieur l'opérateur va être compilé en CEQ, donc il n'y a pas de récursion infinie.

En fait, si vous appelez l'opérateur via la réflexion, vous pouvez voir que l'opérateur est appelé (plutôt qu'une instruction CEQ), et n'est évidemment pas infiniment récursif (puisque le programme se termine comme prévu):

double d1 = 1.1;
double d2 = 2.2;

MethodInfo mi = typeof(Double).GetMethod("op_Equality", BindingFlags.Static | BindingFlags.Public );

bool b = (bool)(mi.Invoke(null, new object[] {d1,d2}));

IL résultant (compilé par LinqPad 4):

IL_0000:  nop         
IL_0001:  ldc.r8      9A 99 99 99 99 99 F1 3F 
IL_000A:  stloc.0     // d1
IL_000B:  ldc.r8      9A 99 99 99 99 99 01 40 
IL_0014:  stloc.1     // d2
IL_0015:  ldtoken     System.Double
IL_001A:  call        System.Type.GetTypeFromHandle
IL_001F:  ldstr       "op_Equality"
IL_0024:  ldc.i4.s    18 
IL_0026:  call        System.Type.GetMethod
IL_002B:  stloc.2     // mi
IL_002C:  ldloc.2     // mi
IL_002D:  ldnull      
IL_002E:  ldc.i4.2    
IL_002F:  newarr      System.Object
IL_0034:  stloc.s     04 // CS$0$0000
IL_0036:  ldloc.s     04 // CS$0$0000
IL_0038:  ldc.i4.0    
IL_0039:  ldloc.0     // d1
IL_003A:  box         System.Double
IL_003F:  stelem.ref  
IL_0040:  ldloc.s     04 // CS$0$0000
IL_0042:  ldc.i4.1    
IL_0043:  ldloc.1     // d2
IL_0044:  box         System.Double
IL_0049:  stelem.ref  
IL_004A:  ldloc.s     04 // CS$0$0000
IL_004C:  callvirt    System.Reflection.MethodBase.Invoke
IL_0051:  unbox.any   System.Boolean
IL_0056:  stloc.3     // b
IL_0057:  ret 

Fait intéressant - les mêmes opérateurs n'existent PAS (ni dans la source de référence ni via la réflexion) pour les types intégraux, uniquement Single, Double, Decimal, String , et DateTime, ce qui réfute ma théorie selon laquelle ils existent pour être appelés à partir d'autres langues. Évidemment, vous pouvez assimiler deux entiers dans d'autres langues sans ces opérateurs, donc nous revenons à la question "pourquoi existent-ils pour double"?

62
D Stanley

La principale confusion ici est que vous supposez que toutes les bibliothèques .NET (dans ce cas, la bibliothèque numérique étendue, qui n'est pas une partie de la BCL) sont écrits en C # standard. Ce n'est pas toujours le cas, et différentes langues ont des règles différentes.

En C # standard, le morceau de code que vous voyez entraînerait un débordement de pile, en raison du fonctionnement de la résolution de surcharge de l'opérateur. Cependant, le code n'est pas réellement en C # standard - il utilise essentiellement des fonctionnalités non documentées du compilateur C #. Au lieu d'appeler l'opérateur, il émet ce code:

ldarg.0
ldarg.1
ceq
ret

C'est tout :) Il n'y a pas de code C # 100% équivalent - ce n'est tout simplement pas possible en C # avec votre propre type .

Même alors, l'opérateur réel n'est pas utilisé lors de la compilation du code C # - le compilateur fait un tas d'optimisations, comme dans ce cas, où il remplace l'appel op_Equality Par le simple ceq. Encore une fois, vous ne pouvez pas répliquer cela dans votre propre structure DoubleEx - c'est la magie du compilateur.

Ce n'est certainement pas une situation unique dans .NET - il y a beaucoup de code qui n'est pas valide, C # standard. Les raisons sont généralement (a) les hacks du compilateur et (b) un langage différent, avec les hacks d'exécution étranges (c) (je vous regarde, Nullable!).

Étant donné que le compilateur Roslyn C # est une source oepn, je peux vous indiquer où la résolution de surcharge est décidée:

L'endroit où tous les opérateurs binaires sont résolus

Les "raccourcis" pour les opérateurs intrinsèques

Lorsque vous regardez les raccourcis, vous verrez que l'égalité entre le double et le double entraîne l'opérateur double intrinsèque, jamais dans le == opérateur défini sur le type. Le système de type .NET doit prétendre que Double est un type comme les autres, mais C # ne le fait pas - double est une primitive en C #.

37
Luaan

La source des types primitifs peut prêter à confusion. Avez-vous vu la toute première ligne de la structure Double?

Normalement, vous ne pouvez pas définir une structure récursive comme celle-ci:

public struct Double : IComparable, IFormattable, IConvertible
        , IComparable<Double>, IEquatable<Double>
{
    internal double m_value; // Self-recursion with endless loop?
    // ...
}

Les types primitifs ont également leur support natif dans CIL. Normalement, ils ne sont pas traités comme des types orientés objet. Un double n'est qu'une valeur de 64 bits s'il est utilisé comme float64 en CIL. Cependant, s'il est géré comme un type .NET habituel, il contient une valeur réelle et il contient des méthodes comme tous les autres types.

Donc, ce que vous voyez ici est la même situation pour les opérateurs. Normalement, si vous utilisez directement le type de type double, il ne sera jamais appelé. BTW, sa source ressemble à ceci dans CIL:

.method public hidebysig specialname static bool op_Equality(float64 left, float64 right) cil managed
{
    .custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor()
    .custom instance void __DynamicallyInvokableAttribute::.ctor()
    .maxstack 8
    L_0000: ldarg.0
    L_0001: ldarg.1
    L_0002: ceq
    L_0004: ret
}

Comme vous pouvez le voir, il n'y a pas de boucle sans fin (l'instrument ceq est utilisé au lieu d'appeler le System.Double::op_Equality). Ainsi, lorsqu'un double est traité comme un objet, la méthode opérateur sera appelée, qui finira par le gérer comme float64 type primitif au niveau CIL.

12
György Kőszeg

J'ai jeté un œil au CIL avec JustDecompile. L'intérieur == est traduit en CIL ceq code op. En d'autres termes, c'est l'égalité primitive CLR.

J'étais curieux de voir si le compilateur C # ferait référence à ceq ou à == opérateur lors de la comparaison de deux valeurs doubles. Dans l'exemple trivial que j'ai trouvé (ci-dessous), il a utilisé ceq.

Ce programme:

void Main()
{
    double x = 1;
    double y = 2;

    if (x == y)
        Console.WriteLine("Something bad happened!");
    else
        Console.WriteLine("All is right with the world");
}

génère le CIL suivant (notez l'instruction avec l'étiquette IL_0017):

IL_0000:  nop
IL_0001:  ldc.r8      00 00 00 00 00 00 F0 3F
IL_000A:  stloc.0     // x
IL_000B:  ldc.r8      00 00 00 00 00 00 00 40
IL_0014:  stloc.1     // y
IL_0015:  ldloc.0     // x
IL_0016:  ldloc.1     // y
IL_0017:  ceq
IL_0019:  stloc.2
IL_001A:  ldloc.2
IL_001B:  brfalse.s   IL_002A
IL_001D:  ldstr       "Something bad happened!"
IL_0022:  call        System.Console.WriteLine
IL_0027:  nop
IL_0028:  br.s        IL_0035
IL_002A:  ldstr       "All is right with the world"
IL_002F:  call        System.Console.WriteLine
IL_0034:  nop
IL_0035:  ret
8
Daniel Pratt