这是一个ExpressionTrees的bug吗?

11
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 Visualizer和一些反射技巧从编译的Expression<TDelegate>获取DynamicMethod的编译后MSIL代码:

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为此转换编译的代码无效?

3个回答

11

这显然是一个bug,并且在今天的C# 4.0版本中重现。感谢您提供它的关注。很可能在最终发布之前,这个问题不会达到修复的标准。在这个晚期阶段,我们只采取非常高优先级的修复措施,并且我们有信心这些措施不会使发布不稳定。更可能的是,修复将在未来的服务发布中实现,但当然,不能保证。


2

我在这里看不到问题。在理想情况下,这两种情况都应该出现编译错误。因为实际上结果是一种静默的溢出。 例如,以下代码将无法编译:

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

如果你一开始就做错了事情,那么得到不同的值真的很重要吗?如果你修改代码使用 checked 转换 ( x => checked((uint)x) ),你将在两种情况下获得相同的结果 - 运行时异常。


1
谢谢您的回答,但是溢出行为不同确实很重要!编译方法与普通代码具有不同行为的原因是什么? - controlflow
C#在将浮点数转换为无符号整数时,除了使用conv.u4 MSIL操作码外,不使用任何其他技术 =) - controlflow

0

我不确定这是否是一个bug,但我可以指出差异的方向:

这两种方法构建方式不同。编译为converter1的表达式具有类型为DynamicMethod的目标方法。分配给converter2的lambda方法具有类型为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);

结果不同的原因有一定道理。


不,你错了,.NET使用相同的JIT编译器来发出本机代码,用于编译静态程序集或DynamicMethods。 - controlflow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接