Определение оператора "= = " для Double



по какой-то причине я пробирался в источник .NET Framework для класса Double и выяснил, что декларация == - это:



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


та же логика применима к оператора.






  • в чем смысл такого определения?

  • как это работает?

  • почему он не создает бесконечную рекурсию?

554   5  

5 ответов:

на самом деле компилятор превратит == оператор в ceq код IL, и оператор, который вы упомянули, не будет вызван.

причина для оператора в исходном коде, вероятно, поэтому он может быть вызван из языков, отличных от C# , которые не переводят его в CEQ вызов непосредственно (или через отражение). Код внутри оператор будет быть скомпилированы в CEQ, так что нет бесконечного рекурсия.

на самом деле, если вы вызываете оператор через отражение, вы можете видеть, что оператор вызывается (а не CEQ инструкция), и, очевидно, не является бесконечно рекурсивным (так как программа завершается, как ожидалось):

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 (скомпилированный 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 // CS00
IL_0036:  ldloc.s     04 // CS00
IL_0038:  ldc.i4.0    
IL_0039:  ldloc.0     // d1
IL_003A:  box         System.Double
IL_003F:  stelem.ref  
IL_0040:  ldloc.s     04 // CS00
IL_0042:  ldc.i4.1    
IL_0043:  ldloc.1     // d2
IL_0044:  box         System.Double
IL_0049:  stelem.ref  
IL_004A:  ldloc.s     04 // CS00
IL_004C:  callvirt    System.Reflection.MethodBase.Invoke
IL_0051:  unbox.any   System.Boolean
IL_0056:  stloc.3     // b
IL_0057:  ret 

интересно-одни и те же операторы не существуют (ни в ссылочном источнике, ни через отражение) для целых типов, только Single,Double,Decimal, String и DateTime, что опровергает мою теорию о том, что они существуют, чтобы называться из других языков. Очевидно, что вы можете приравнять два целых числа в других языках без этих операторов, поэтому мы возвращаемся к вопросу "почему они существуют для double"?

основная путаница здесь заключается в том, что вы предполагаете, что все библиотеки .NET (в данном случае Расширенная библиотека Numerics, которая не часть BCL) написаны на стандартном C#. Это не всегда так, и разные языки имеют разные правила.

в стандартном C# фрагмент кода, который вы видите, приведет к переполнению стека из-за того, как работает разрешение перегрузки оператора. Однако код на самом деле не находится в стандартном C# - он в основном использует недокументированные функции компилятора C#. Вместо вызова оператора он выдает следующий код:

ldarg.0
ldarg.1
ceq
ret

вот и все :) нет 100% эквивалентного кода C# - это просто невозможно в C# с свой тип.

даже то, собственно оператор не используется при компиляции кода C# - компилятор делает кучу оптимизаций, как в этом случае, где он заменяет op_Equality вызов с помощью простого ceq. Опять же, вы не можете повторить это в вашем собственный DoubleEx struct-это магия компилятора.

это, конечно, не уникальная ситуация в .NET - есть много кода, который не является допустимым, стандартный C#. Причины обычно (а) хаки компилятора и (б) другой язык, с нечетными (с) хаки времени выполнения (я смотрю на вас, Nullable!).

поскольку компилятор Roslyn C# является источником oepn, я могу указать вам место, где решается разрешение перегрузки:

место, где все бинарные операторы разрешены

"ярлыки" для встроенных операторов

когда вы посмотрите на ярлыки, вы увидите, что равенство между double и double приводит к встроенному оператору double,никогда в фактических == оператор определенного типа. Система типов .NET должна притворяться, что Double это тип, как и любой другой, но C# не - double является примитивом в C#.

источник примитивных типов может быть запутанным. Вы видели самую первую строку Double структура?

обычно вы не можете определить рекурсивную структуру следующим образом:

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

примитивные типы также имеют свою собственную поддержку в CIL. Обычно они не рассматриваются как объектно-ориентированные типы. Double - это всего лишь 64-разрядное значение, если оно используется как float64 в CIL. Однако если он обрабатывается как обычный тип .NET, он содержит фактическое значение и содержит методы, как и любые другие виды.

так что вы видите здесь такую же ситуацию для операторов. Обычно, если вы используете тип double type напрямую, он никогда не будет вызван. Кстати, его источник выглядит так в 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
}

как вы можете видеть, нет бесконечного цикла (ceq инструмент используется вместо вызова System.Double::op_Equality). Поэтому, когда double обрабатывается как объект, будет вызван метод оператора, который в конечном итоге обработает его как float64 примитивный тип на уровне CIL.

Я взглянул на CIL С JustDecompile. Внутренний == переводится в CIL ceq op код. Другими словами, это примитивное равенство CLR.

мне было любопытно посмотреть, будет ли компилятор C# ссылаться ceq или == оператор при сравнении двух значений Double. В тривиальном примере, который я придумал (ниже), он использовал ceq.

программы:

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");
}

генерирует следующий CIL (обратите внимание заявление с меткой 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

Как указано в документации Microsoft для системы.Во время выполнения.Пространство имен управления версиями: типы, найденные в этом пространстве имен, предназначены для использования в .NET Framework, а не для пользовательских приложений.система.Во время выполнения.Пространство имен управления версиями содержит расширенные типы, которые поддерживают управление версиями в параллельных реализациях платформы .NET Framework.

Comments

    Ничего не найдено.