views:

217

answers:

3
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
  }
}

Same different behavior can be founded when compiling Expression.Convert for this conversions:

Single -> UInt32 Single -> UInt64

Double -> UInt32 Double -> UInt64

Looks strange, isn't it?

<=== Added some my research ===>

I'm looked at the compiled DynamicMethod MSIL code using DynamicMethod Visualizer and some reflection hack to get DynamicMethod from the compiled 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);

And what I get is this MSIL code:

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

And the equal C#-compiled method has this body:

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

Do anybody see now that ExpressionTrees compiles not valid code for this conversion?

+2  A: 

I don't see a problem here. In the ideal case, you should get a compilation error in both situations. Becuase the result is actually a silent overflow. For example, the following simply would not compile:

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

Does it really matter that you get different values when doing a wrong thing in the first place? If you modify your code to use checked conversion ( x => checked((uint)x) ), you'll get the same result in both scenarios - a run time exception.

Alexandra Rusina
Thx for your answer, but Yes, it matters that the overflow behavior is different! What is a reason for compiled method to have different behavior from plain code?
ControlFlow
C# uses no other techniques except `conv.u4` MSIL opcode for converting floating-point values into unsigned integers =)
ControlFlow
A: 

I am not sure if it's a bug or not, but I can point to the direction of the difference:

The two methods are built differently. The expression compiled to converter1 has a target method of type DynamicMethod. The lambda method assigned to converter2 has a target method of RuntimeMethodInfo.

Both compiled JIT but through different mechanism. As I said, can't tell why they have different behavior but this is probably the cause to the difference.

Edit This is to what it compiles to (code using 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);

It make some sense why the result is different.

Elisha
No, you're wrong, .NET uses the same JIT-compiler for emitting native code for compiling static assemblies or DynamicMethods whatever.
ControlFlow
+11  A: 

This is clearly a bug, and it reproduces in today's build of C# 4.0. Thank you for bringing it to our attention. Odds are good that this issue will not make the bar for fixing before the final release; at this late stage we are taking only very high-priority fixes that we have confidence will not destabilize the release. What's more likely is that a fix will make it into a future service release; but of course, no promises.

Eric Lippert
Thank you, Eric!
ControlFlow