Серьезные ошибки с поднятыми / обнуляемыми преобразованиями из int, позволяющими преобразование из десятичного числа

Я думаю, что этот вопрос принесет мне мгновенную славу здесь, на переполнении стека.

Предположим, у вас есть следующий тип:

// represents a decimal number with at most two decimal places after the period
struct NumberFixedPoint2
{
    decimal number;

    // an integer has no fractional part; can convert to this type
    public static implicit operator NumberFixedPoint2(int integer)
    {
        return new NumberFixedPoint2 { number = integer };
    }

    // this type is a decimal number; can convert to System.Decimal
    public static implicit operator decimal(NumberFixedPoint2 nfp2)
    {
        return nfp2.number;
    }

    /* will add more nice members later */
}

Он написан так, что разрешены только безопасные преобразования, которые не теряют точности. Тем не менее, когда я пытаюсь этот код:

    static void Main()
    {
        decimal bad = 2.718281828m;
        NumberFixedPoint2 badNfp2 = (NumberFixedPoint2)bad;
        Console.WriteLine(badNfp2);
    }

Я удивлен, что это компилируется и, когда запускается, пишет 2, Преобразование из int (стоимости 2) чтобы NumberFixedPoint2 здесь важно (Перегрузка WriteLine это занимает System.Decimal предпочтительнее, на случай, если кто-нибудь спросит.)

Почему на Земле происходит преобразование из decimal в NumberFixedPoint2 позволил? (Кстати, в приведенном выше коде, если NumberFixedPoint2 изменяется от структуры к классу, ничего не меняется.)

Знаете ли вы, если спецификация языка C# говорит, что неявное преобразование из int к пользовательскому типу "подразумевает" существование "прямого" явного преобразования из decimal к этому пользовательскому типу?

Становится намного хуже. Попробуйте этот код вместо этого:

    static void Main()
    {
        decimal? moreBad = 7.3890560989m;
        NumberFixedPoint2? moreBadNfp2 = (NumberFixedPoint2?)moreBad;
        Console.WriteLine(moreBadNfp2.Value);
    }

Как видите, мы (подняли) Nullable<> преобразования здесь. Но да, это компилируется.

При компиляции в x86 "платформе" этот код записывает непредсказуемое числовое значение. Какой из них меняется время от времени. Как пример, однажды я получил 2289956, Это серьезная ошибка!

При компиляции для платформы x64 приведенный выше код вызывает сбой приложения с System.InvalidProgramException с сообщением Common Language Runtime обнаружил недопустимую программу. Согласно документации InvalidProgramException учебный класс:

Обычно это указывает на ошибку в компиляторе, который сгенерировал программу.

Кто-нибудь (как Эрик Липперт или кто-то, кто работал с поднятыми преобразованиями в компиляторе C#) знает причину этих ошибок? Например, что является достаточным условием, чтобы мы не сталкивались с ними в нашем коде? Потому что тип NumberFixedPoint2 на самом деле это то, что мы имеем в реальном коде (управление деньгами и вещами других людей).

2 ответа

Решение

Ваша вторая часть (с использованием обнуляемых типов) очень похожа на эту известную ошибку в текущем компиляторе. Из ответа на вопрос подключения:

Хотя в настоящее время у нас нет планов решения этой проблемы в следующем выпуске Visual Studio, мы планируем исследовать исправление в Roslyn.

Таким образом, мы надеемся, что эта ошибка будет исправлена ​​в следующем выпуске Visual Studio и компиляторов.

Я просто отвечаю на первую часть вопроса для начала. (Я предлагаю, чтобы вторая часть была отдельным вопросом; скорее всего, это ошибка.)

Там только явное преобразование из decimal в int, но это преобразование неявно вызывается в вашем коде. Преобразование происходит в этом IL:

IL_0010:  stloc.0
IL_0011:  ldloc.0
IL_0012:  call       int32 [mscorlib]System.Decimal::op_Explicit(valuetype [mscorlib]System.Decimal)
IL_0017:  call       valuetype NumberFixedPoint2 NumberFixedPoint2::op_Implicit(int32)

Я считаю, что это правильное поведение в соответствии со спецификацией, хотя это удивительно 1. Давайте проработаем раздел 6.4.5 спецификации C# 4 (пользовательские явные преобразования). Я не собираюсь копировать весь текст, так как это было бы утомительно - каковы соответствующие результаты в нашем случае. Точно так же я не собираюсь использовать подписки, так как они плохо работают со шрифтом кода здесь:)

  • Определить типы S0 а также T0: S0 является decimal, а также T0 является NumberFixedPoint2,
  • Найти множество типов, D, из которых будут рассматриваться используемые операторы преобразования: { decimal, NumberFixedPoint2 }
  • Найти набор применимых пользовательских и отмененных операторов преобразования, U, decimal охватывает int (раздел 6.4.3), потому что есть стандартное неявное преобразование из int в decimal, Таким образом, оператор явного преобразования находится в U и действительно единственный член U
  • Найти наиболее конкретный тип источника, Sx из операторов в U
    • Оператор не конвертирует из S (decimal) так что первая пуля вышла
    • Оператор не конвертирует из типа, который охватывает S (decimal охватывает int не наоборот) так что вторая пуля
    • Это просто оставляет третий пункт, который говорит о "наиболее охватывающем типе" - ну, у нас есть только один тип, так что все в порядке: Sx является int,
  • Найти наиболее конкретный тип цели, Tx из операторов в U
    • Оператор разговаривает прямо с NumberFixedPoint2 так Tx является NumberFixedPoint2,
  • Найдите наиболее конкретного оператора преобразования:
    • U содержит ровно один оператор, который действительно конвертирует из Sx в Tx так что это самый конкретный оператор
  • Наконец, примените преобразование:
    • Если S не является Sx , то стандартное явное преобразование из S в Sx выполняется. (Так вот decimal в int.)
    • Вызывается самый определенный пользовательский оператор преобразования (ваш оператор)
    • T является Tx так что нет необходимости в преобразовании в третью пулю

Строка, выделенная жирным шрифтом, является битом, который подтверждает, что стандартное явное преобразование действительно возможно, когда фактически указывается только явное преобразование из другого типа.


+1 Ну, я нашел это удивительным, по крайней мере. Я не знаю, чтобы увидеть это раньше.

Другие вопросы по тегам