Это ошибка ExpressionTrees?

using System;
using System.Linq.Expressions;

class Program
{
  static void Main()
  {
    Expression<Func<float, uint>> expr = x => (uint) x;

    Func<float,uint> converter1 = expr.Compile();
    Func<float,uint> converter2 = x => (uint) x;

    var aa = converter1(float.MaxValue); // == 2147483648
    var bb = converter2(float.MaxValue); // == 0
  }
}

Такое же поведение может быть создано при компиляции Expression.Convert для этих преобразований:

Single -> UInt32 Single -> UInt64

Double -> UInt32 Double -> UInt64

Выглядит странно, не так ли?

< === Добавлено несколько моих исследований === >

Я смотрю на скомпилированный код MSIL DynamicMethod, используя DynamicMethod Visualizer и некоторые размышления взломать get DynamicMethod из скомпилированного Expression<TDelegate>:

Expression<Func<float, uint>> expr = x => (uint) x;

Func<float,uint> converter1 = expr.Compile();
Func<float,uint> converter2 = x => (uint) x;

// get RTDynamicMethod - compiled MethodInfo
var rtMethodInfo = converter1.Method.GetType();

// get the field with the reference
var ownerField = rtMethodInfo.GetField(
  "m_owner", BindingFlags.NonPublic | BindingFlags.Instance);

// get the reference to the original DynamicMethod
var dynMethod = (DynamicMethod) ownerField.GetValue(converter1.Method);

// show me the MSIL
DynamicMethodVisualizer.Visualizer.Show(dynMethod);

И я получаю этот код MSIL:

IL_0000: ldarg.1
IL_0001: conv.i4
IL_0002: ret

И равный С# -компилированный метод имеет это тело:

IL_0000: ldarg.0
IL_0001: conv.u4
IL_0002: ret

Кто-нибудь теперь видит, что ExpressionTrees компилирует недопустимый код для этого преобразования?

Ответ 1

Это отчетливо ошибка, и она воспроизводит в сегодняшнем сборнике С# 4.0. Спасибо, что привлекли его к нашему вниманию. Коэффициенты хороши, что эта проблема не сделает планку для фиксации до финальной версии; на этом позднем этапе мы принимаем только очень высокоприоритетные исправления, которые у нас есть уверенность, не дестабилизируют выпуск. Скорее всего, исправление превратит его в будущий выпуск службы; но, конечно, не promises.

Ответ 2

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

var test = (uint)(float.MaxValue);

Действительно ли имеет значение, что вы получаете разные ценности, когда делаете неправильную вещь в первую очередь? Если вы измените свой код, чтобы использовать проверенное преобразование (x = > checked ((uint) x)), вы получите тот же результат в обоих сценариях - исключение времени выполнения.

Ответ 3

Я не уверен, что это ошибка или нет, но я могу указать направление разницы:

Два метода построены по-разному. Выражение, скомпилированное в преобразователь1, имеет целевой метод типа DynamicMethod. Метод лямбда, присвоенный преобразователю2, имеет целевой метод RuntimeMethodInfo.

Оба скомпилированных JIT, но с помощью другого механизма. Как я уже сказал, не могу понять, почему у них другое поведение, но это, вероятно, является причиной разницы.

Изменить Это то, что он компилирует (код с использованием Reflector).

ParameterExpression CS$0$0000;
Func<float, uint> converter1 = Expression.Lambda<Func<float, uint>>(Expression.Convert(CS$0$0000 = Expression.Parameter(typeof(float), "x"), typeof(uint)), new ParameterExpression[] { CS$0$0000 }).Compile();
Func<float, uint> converter2 = delegate (float x) { return (uint) x; };
uint aa = converter1(float.MaxValue);
uint bb = converter2(float.MaxValue);

Понятно, почему результат отличается.