Это ошибка 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);
Понятно, почему результат отличается.