По какой-то причине я пробирался к исходному тексту .NET Framework для этого класса Double
и обнаружил, что объявление ==
:
public static bool operator ==(Double left, Double right) {
return left == right;
}
Та же самая логика применима к каждому оператору.
- Какой смысл в таком определении?
- Как это работает?
- Почему он не создает бесконечную рекурсию?
c#
.net
language-lawyer
Томас Аюб
источник
источник
ceq
выдается в IL. Это просто для заполнения некоторой документации, но не могу найти источник.Ответы:
На самом деле компилятор превратит
==
оператор вceq
код IL, и упомянутый вами оператор не будет вызван.Причина использования оператора в исходном коде, вероятно, заключается в том, что его можно вызвать из языков, отличных от C #, которые не переводят его в
CEQ
вызов напрямую (или через отражение). Код внутри оператора будет скомпилирован в aCEQ
, поэтому бесконечной рекурсии нет.Фактически, если вы вызываете оператор через отражение, вы можете увидеть, что оператор вызывается (а не
CEQ
инструкция) и, очевидно, не является бесконечно рекурсивным (поскольку программа завершается, как ожидалось):Результирующий IL (скомпилированный LinqPad 4):
Интересно - те же операторы не существуют (либо в опорном источнике или с помощью отражения) для целочисленных типов, только
Single
,Double
,Decimal
,String
, иDateTime
, которые опровергают мою теорию о том , что они существуют , чтобы быть вызваны из других языков. Очевидно, что вы можете приравнять два целых числа на других языках без этих операторов, поэтому мы снова возвращаемся к вопросу «зачем они существуют дляdouble
»?источник
Основная путаница здесь заключается в том, что вы предполагаете, что все библиотеки .NET (в данном случае Библиотека расширенных числовых данных, которая не является частью BCL) написаны на стандартном C #. Это не всегда так, и на разных языках действуют разные правила.
В стандартном C # фрагмент кода, который вы видите, приведет к переполнению стека из-за того, как работает разрешение перегрузки оператора. Однако код на самом деле не в стандартном C # - он в основном использует недокументированные функции компилятора C #. Вместо вызова оператора он выдает такой код:
Вот и все :) Не существует 100% эквивалентного кода C # - это просто невозможно в C # с вашим собственным типом.
Даже в этом случае фактический оператор не используется при компиляции кода C # - компилятор выполняет несколько оптимизаций, как в этом случае, когда он заменяет
op_Equality
вызов простымceq
. Опять же, вы не можете воспроизвести это в своей собственнойDoubleEx
структуре - это магия компилятора.Это, конечно, не уникальная ситуация в .NET - там много кода, который не соответствует стандарту C #. Причинами обычно являются (а) взлом компилятора и (б) другой язык, а также странные (в) взломы времени выполнения (я смотрю на вас
Nullable
!).Поскольку компилятор Roslyn C # является исходным кодом oepn, я могу указать вам место, где решается разрешение перегрузки:
Место, где разрешены все бинарные операторы
"Ярлыки" для внутренних операторов
Когда вы посмотрите на ярлыки, вы увидите, что равенство между double и double приводит к внутреннему оператору double, а не к фактическому
==
оператору, определенному для типа. Система типов .NET должна делать вид, чтоDouble
это такой же тип, как и любой другой, но C # этого не делает -double
это примитив в C #.источник
#if
и другие артефакты, которых нет в скомпилированном коде. Плюс, если он был реконструирован,double
то почему он не был реконструирован дляint
илиlong
? Я действительно думаю, что для исходного кода есть причина, но считаю, что использование==
внутри оператора компилируется в,CEQ
что предотвращает рекурсию. Поскольку оператор является «предопределенным» оператором для этого типа (и не может быть переопределен), правила перегрузки не применяются.double
это не часть BCL - это отдельная библиотека, которая случайно включена в спецификацию C #. Да,==
компилируется в aceq
, но это по-прежнему означает, что это взлом компилятора, который вы не можете воспроизвести в собственном коде, и то, что не является частью спецификации C # (как иfloat64
поле вDouble
структуре). Это не контрактная часть C #, поэтому нет смысла рассматривать ее как действительный C #, даже если она была скомпилирована с помощью компилятора C #.double
к ним так же, какint
иlong
-int
иlong
являются примитивными типами, которые должны поддерживать все языки .NET.float
,decimal
иdouble
нет.Источник примитивных типов может сбивать с толку. Вы видели самую первую строку
Double
структуры?Обычно вы не можете определить такую рекурсивную структуру:
Примитивные типы также имеют встроенную поддержку в CIL. Обычно они не рассматриваются как объектно-ориентированные типы. Двойное значение - это просто 64-битное значение, если оно используется как
float64
в CIL. Однако, если он обрабатывается как обычный тип .NET, он содержит фактическое значение и методы, как и любые другие типы.То же самое и с операторами. Обычно, если вы используете тип типа double напрямую, он никогда не будет вызван. Кстати, его источник в CIL выглядит так:
Как видите, бесконечного цикла нет (
ceq
вместо вызова инструмента используется инструментSystem.Double::op_Equality
). Поэтому, когда двойник обрабатывается как объект, будет вызван метод оператора, который в конечном итоге обработает его какfloat64
примитивный тип на уровне CIL.источник
public struct MyNumber { internal MyNumber m_value; }
. Конечно, его нельзя скомпилировать. Ошибка - это ошибка CS0523: член структуры MyNumber.m_value типа MyNumber вызывает цикл в макете структурыЯ взглянул на CIL с JustDecompile. Внутренний
==
переводится в код операции CIL ceq . Другими словами, это примитивное равенство CLR.Мне было любопытно узнать, будет ли компилятор C # ссылаться
ceq
или на==
оператор при сравнении двух значений типа double. В тривиальном примере, который я придумал (ниже), он использовалceq
.Эта программа:
генерирует следующий CIL (обратите внимание на оператор с меткой
IL_0017
):источник
Как указано в документации Microsoft для пространства имен System.Runtime.Versioning: типы, обнаруженные в этом пространстве имен, предназначены для использования в .NET Framework, а не для пользовательских приложений. Пространство имен System.Runtime.Versioning содержит расширенные типы, которые поддерживают управление версиями в бок о бок реализации .NET Framework.
источник
System.Runtime.Versioning
тутSystem.Double
?