125 votes

Définition de "==" opérateur pour le Double

Pour une raison que j'étais se faufiler dans le .NET Framework source pour la classe Double et a trouvé que la déclaration d' == est:

[System.Runtime.Versioning.NonVersionable]
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 fonctionne?
  • Pourquoi ne pas créer une récurrence infinie?

62voto

D Stanley Points 54768

En réalité, le compilateur va tourner à l' == opérateur en ceq IL code, et l'opérateur que vous mentionnez ne sera pas appelé.

La raison pour laquelle l'exploitant dans le code source est susceptible de sorte qu'il peut être appelé à partir d'autres langages que le C# qui ne se traduisent pas en CEQ de l'appeler directement (ou par l'intermédiaire de la réflexion). Le code à l'intérieur de l'opérateur va être compilé pour un CEQ, donc il n'y a pas de récursivité infinie.

En fait, si vous appelez de l'opérateur via la réflexion, vous pouvez voir que l'opérateur est appelé (plutôt qu'un CEQ de l'instruction), et, évidemment, n'est pas infiniment récursive (depuis 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}));

Résultant de l'IL (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 

Il est intéressant de noter - les mêmes opérateurs n'existent PAS (que ce soit dans la source de référence ou par réflexion) pour les types intégraux, seulement Single, Double, Decimal, String, et DateTime, ce qui réfute ma théorie qu'ils existent pour être appelé à partir d'autres langues. Bien évidemment, on peut comparer deux nombres entiers dans d'autres langues sans ces opérateurs, de sorte que nous sommes de retour à la question "pourquoi ils existent pour double"?

37voto

Luaan Points 8934

La confusion principale ici est que vous êtes en supposant que tous les .Bibliothèques NET (dans ce cas, l'extension d'objets Numériques de la Bibliothèque, qui n'est pas une partie de la BCL) sont écrits en C# standard. Ce n'est pas toujours le cas, et dans des langues différentes des règles différentes.

Dans la norme C#, le morceau de code que vous voyez seraient le résultat d'un débordement de la pile, en raison de la façon dont l'opérateur de résolution de surcharge fonctionne. Toutefois, le code n'est pas vraiment dans la norme C# - il utilise essentiellement des sans-papiers caractéristiques 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 100% équivalent en code C# - ce n'est tout simplement pas possible en C# avec votre propre type.

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

Ce n'est certainement pas une situation unique dans .NET - il y a beaucoup de code n'est pas valide, la norme C#. Les raisons sont généralement (a) compilateur hacks et (b) une autre langue, à l'étrange (c) exécution des hacks (je suis à la recherche à vous, Nullable!).

Depuis le Roslyn compilateur C# est oepn source, je peux effectivement vous pointer à l'endroit où la résolution de surcharge est décidé:

Le lieu où tous les opérateurs binaires sont résolus

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

Quand vous regardez les raccourcis, vous verrez que l'égalité entre le double et le double des résultats de la valeur intrinsèque du double de l'opérateur, jamais dans le réel == défini par l'opérateur sur le type. L' .NET type de système est de prétendre qu' Double est un type comme les autres, mais le C# n'est pas - double est une primitive en C#.

12voto

taffer Points 1433

La source des types primitifs peuvent être source de confusion. Avez-vous vu la première ligne de l' Double struct?

Normalement, vous ne pouvez pas définir un circuit qui se structure comme ceci:

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

Les types primitifs ont leur prise en charge native de CIL. Normalement, ils ne sont pas traités comme orientée objet types. Un double est juste une version 64 bits de valeur que si elle est utilisée comme float64 dans le CIL. Toutefois, si elle est traitée comme une habitude .Type de réseau, il contient une valeur réelle et il contient des méthodes comme tous les autres types.

Donc, ce que vous voyez ici, c'est la même situation pour les opérateurs. Normalement, si vous utilisez le type double taper directement, 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 infinie (l' ceq instrument est utilisé à la place de l'appel de la System.Double::op_Equality). Alors, quand un double est traité comme un objet, l'opérateur méthode sera appelée, qui finira par traiter comme l' float64 type primitif sur le CIL.

8voto

Daniel Pratt Points 8151

J'ai pris un coup d'oeil à la CIL avec JustDecompile. L'intérieur == se traduit à la CIL ceq op code. En d'autres termes, c'est primitif CLR égalité.

J'étais curieux de voir si le compilateur C# serait de référence ceq ou == de l'opérateur lors de la comparaison de deux valeurs. Dans l'exemple trivial, je suis venu avec (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 des CIL (note de la déclaration 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

-2voto

Comme indiqué dans la documentation de Microsoft pour le Système.Moment de l'exécution.Gestion des versions de l'espace de Noms:Les types trouvés dans cet espace de noms sont destinés à une utilisation au sein de la .NET Framework et pas pour les applications de l'utilisateur.Le Système.Moment de l'exécution.Gestion des versions de l'espace de noms contient des avancées types de charge la gestion des versions côte à côte dans les implémentations de la .NET Framework.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X