Подтвердить что ты не робот

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

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

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

// 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, которая принимает значение a System.Decimal, является предпочтительной, если кто-то задается вопросом.)

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

Знаете ли вы, что спецификация языка С# говорит, что неявное преобразование из 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:

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

Там только явное преобразование от 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 спецификации С# 4 (Пользовательские явные конверсии). Я не собираюсь копировать весь текст, поскольку это было бы утомительно - именно то, что в нашем случае есть соответствующие результаты. Аналогично, я не буду использовать индексы, так как они плохо работают с шрифтом кода здесь:)

  • Определите типы S0 и T0: S0 is decimal, а T0 - NumberFixedPoint2.
  • Найдите набор типов D, из которых будут рассмотрены используемые операторы преобразования: just { 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 Хорошо, мне показалось удивительным, по крайней мере. Я не знаю об этом раньше.