Это ошибка ExpressionTrees? #2

Похоже, компилятор ExpressionTrees должен быть близок к спецификации C# во многих поведениях, но в отличие от C# нет поддержки преобразования из decimal в любой enum-type:

using System;
using System.Linq.Expressions;

class Program
{
  static void Main()
  {
    Func<decimal, ConsoleColor> converter1 = x => (ConsoleColor) x;
    ConsoleColor c1 = converter1(7m); // fine

    Expression<Func<decimal, ConsoleColor>> expr = x => (ConsoleColor) x;

    // System.InvalidOperationException was unhandled
    // No coercion operator is defined between types
    // 'System.Decimal' and 'System.ConsoleColor'.

    Func<decimal, ConsoleColor> converter2 = expr.Compile();

    ConsoleColor c2 = converter2(7m);
  }
}

Другие редко используемые явные преобразования C#, такие как double -> enum-type, существуют и работают, как описано в спецификации C#, но не decimal -> enum-type. Это ошибка?


person controlflow    schedule 09.11.2009    source источник


Ответы (2)


Вероятно, это ошибка, и, вероятно, это моя вина. Извини за это.

Правильное преобразование десятичных чисел было одной из самых сложных частей правильного построения кода дерева выражений в компиляторе и среде выполнения, потому что десятичные преобразования фактически реализованы как определяемые пользователем преобразования в среде выполнения, но обрабатываются как встроенные преобразования в среде выполнения. компилятор. Decimal — единственный тип с этим свойством, а потому для этих случаев в анализаторе есть всякие спецприспособления. На самом деле в анализаторе есть метод IsEnumToDecimalConversion для обработки специального случая преобразования перечисления, допускающего значение NULL, в десятичное число, допускающее значение NULL; довольно сложный частный случай.

Хороши шансы, что я не смог рассмотреть какой-то случай, идущий в другом направлении, и в результате сгенерировал плохой код. Спасибо за замечание; Я отправлю это группе тестировщиков, и мы посмотрим, сможем ли мы запустить воспроизведение. Есть хорошие шансы, что если это действительно окажется добросовестной ошибкой, она не будет исправлена ​​в первоначальном выпуске C# 4; на данный момент мы принимаем только ошибки «пользователь убит электрическим током компилятором», чтобы выпуск был стабильным.

person Eric Lippert    schedule 09.11.2009
comment
Я не знал, что люди пострадали при создании языка С# :) - person Joan Venge; 10.11.2009
comment
десятичные преобразования на самом деле реализованы как пользовательские преобразования во время выполнения, но обрабатываются компилятором как встроенные преобразования: что это значит и почему это было сделано именно так? - person Brian; 04.02.2010
comment
@Brian: Когда вы выполняете преобразование, изменяющее представление, скажем, int в double, есть инструкция IL, которая выполняет именно это преобразование. Когда вы выполняете преобразование десятичного числа в двойное, мы фактически генерируем код для вызова метода для выполнения преобразования; нет встроенной инструкции преобразования в CLR для десятичных дробей. Но с точки зрения языка мы хотим, чтобы десятичные преобразования выглядели как встроенные преобразования в язык; у нас разные правила для встроенных и пользовательских конверсий. Так что приходится строить какие-то специальные декорации, чтобы скрыть то, что происходит за кулисами с десятичными дробями. - person Eric Lippert; 04.02.2010
comment
Хорошо, это имеет смысл. Спасибо. - person Brian; 06.02.2010

Еще не настоящий ответ, я изучаю, но первая строка скомпилирована как:

Func<decimal, ConsoleColor> converter1 = x => (ConsoleColor)(int)x;

Если вы попытаетесь создать выражение из предыдущей лямбды, оно сработает.

РЕДАКТИРОВАТЬ: В спецификации С#, §6.2.2, вы можете прочитать:

Явное преобразование перечисления между двумя типами обрабатывается путем обработки любого участвующего перечисления-типа в качестве базового типа этого перечисления-типа, а затем выполнения неявного или явного числового преобразования между результирующими типами. Например, при заданном перечислимом типе E с базовым типом int преобразование из E в byte обрабатывается как явное числовое преобразование (§6.2.1) из int в byte, а преобразование из byte в E обрабатывается как неявное числовое преобразование (§6.1.2) из ​​byte в int.

Таким образом, явные приведения из перечисления к десятичному обрабатываются специально, поэтому вы получаете вложенные приведения (целое, затем десятичное). Но я не понимаю, почему компилятор не анализирует тело лямбды одинаково в обоих случаях.

person Romain Verdier    schedule 09.11.2009
comment
Компилятор, вероятно, выдает вложенное приведение в другом проходе. В этом случае он просто создает узел Convert, который дает сбой во время выполнения. Будь то ошибка компилятора, которая должна генерировать вложенное преобразование, или ошибка API-интерфейса выражения, которая должна понимать преобразование десятичного числа в перечисление, остается на усмотрение читателя. Я, например, думаю, что ответственность за создание правильного узла преобразования лежит на csc. - person Jb Evain; 09.11.2009
comment
Я согласен. На самом деле вы получаете ошибку компиляции в строке expr = lambda. Таким образом, компилятор не пытается создать дополнительный узел Convert или что-то еще на самом деле; он считает тело лямбда недопустимым, что не соответствует спецификации С#. - person Romain Verdier; 09.11.2009
comment
Для преобразования double -> enum-type csc не выдает Convert double -> int, просто напрямую `double -> enum-type', и компилятор ExpressionTrees прекрасно это понимает... - person controlflow; 09.11.2009
comment
ControlFlow: это потому, что десятичное число не является традиционным типом значения. Он преобразуется в int не стандартным преобразованием, а оператором, созданным компилятором. Ромен: на самом деле я думаю, что компилятор выдает узел decimal -> enum, думая, что он действителен. И метод фабрики дерева выражений выручает. - person Jb Evain; 09.11.2009