Это ошибка 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

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

‹=== Добавлены мои исследования ===>

Я просмотрел скомпилированный код DynamicMethod MSIL, используя Визуализатор DynamicMethod и некоторые приемы отражения, чтобы получить 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

И эквивалентный метод, скомпилированный на C#, имеет следующее тело:

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

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


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


Ответы (3)


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

person Eric Lippert    schedule 04.11.2009

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

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

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

person Alexandra Rusina    schedule 04.11.2009
comment
Спасибо за ваш ответ, но да, важно, что поведение переполнения отличается! По какой причине поведение скомпилированного метода отличается от обычного кода? - person controlflow; 04.11.2009
comment
C# не использует никаких других методов, кроме кода операции conv.u4 MSIL для преобразования значений с плавающей запятой в целые числа без знака =) - person controlflow; 04.11.2009

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

Эти два метода построены по-разному. Выражение, скомпилированное в convert1, имеет целевой метод типа < strong>Динамический метод. Лямбда-метод, назначенный convert2, имеет целевой метод 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);

Есть некоторый смысл, почему результат отличается.

person Elisha    schedule 04.11.2009
comment
Нет, вы ошибаетесь, .NET использует тот же JIT-компилятор для создания нативного кода для компиляции статических сборок или динамических методов. - person controlflow; 04.11.2009