2009-11-04 11 views
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 
    } 
} 

Same unterschiedliches Verhalten gegründet werden kann, wenn Expression.Convert für diese Umwandlungen Kompilieren:Ist das ein ExpressionTrees-Bug?

Single -> UInt32 Single -> UInt64

Double -> UInt32 Double -> UInt64

Sieht seltsam, nicht wahr?

< === einige meiner Forschung hinzugefügt ===>

ich bei der kompilierten DynamicMethod MSIL Code sah bin mit DynamicMethod Visualizer und einige Überlegungen hacken DynamicMethod von der kompilierten Expression<TDelegate> zu erhalten:

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); 

Und was ich bekommen, ist dies MSIL Code:

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

Und das gleich C# -compiled Methode hat diesen Körper:

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

jemand jetzt Sie sehen, dass ExpressionTrees für diese Umwandlung nicht gültig Code kompiliert?

Antwort

11

Dies ist klar ein Fehler, und es reproduziert in heutigen Build von C# 4.0. Danke, dass Sie uns darauf aufmerksam gemacht haben.Quoten sind gut, dass dieses Problem nicht die Bar für die Fixierung vor der endgültigen Veröffentlichung machen wird; Zu diesem späten Zeitpunkt nehmen wir nur sehr hochpriore Fixes, von denen wir überzeugt sind, dass sie das Release nicht destabilisieren werden. Es ist jedoch wahrscheinlicher, dass ein Fix zu einer zukünftigen Service-Version führt. aber natürlich keine Versprechungen.

2

Ich sehe kein Problem hier. Im Idealfall sollten Sie in beiden Fällen einen Kompilierungsfehler erhalten. Denn das Ergebnis ist eigentlich ein stiller Überlauf. Zum Beispiel würde die folgend einfach nicht kompilieren:

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

es wirklich eine Rolle, dass Sie unterschiedliche Werte erhalten, wenn in erster Linie eine falsche Sache zu tun? Wenn Sie Ihren Code so ändern, dass die überprüfte Konvertierung verwendet wird (x => checked ((uint) x)), erhalten Sie in beiden Szenarien das gleiche Ergebnis - eine Laufzeitausnahme.

+1

Thx für Ihre Antwort, aber ja, es ist wichtig, dass das Überlaufverhalten anders ist! Was ist ein Grund dafür, dass die kompilierte Methode ein anderes Verhalten als normaler Code hat? – ControlFlow

+0

C# verwendet keine anderen Techniken außer 'conv.u4' MSIL-Opcode zum Konvertieren von Gleitkommawerten in vorzeichenlose Ganzzahlen =) – ControlFlow

0

Ich bin nicht sicher, ob es ein Fehler ist oder nicht, aber ich kann auf die Richtung der Differenz zeigen:

Die beiden Methoden unterschiedlich aufgebaut sind. Der in converter1 kompilierte Ausdruck hat eine Zielmethode vom Typ DynamicMethod. Die dem Konverter2 zugeordnete Lambda-Methode hat eine Zielmethode von RuntimeMethodInfo.

Beide kompilierten JIT aber durch verschiedene Mechanismen. Wie gesagt, kann nicht sagen, warum sie unterschiedliche Verhaltensweisen haben, aber das ist wahrscheinlich der Grund für den Unterschied.

Bearbeiten Dies ist, was es kompiliert (Code mit Reflektor).

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); 

Es macht Sinn, warum das Ergebnis anders ist.

+0

Nein, Sie liegen falsch, .NET verwendet den gleichen JIT-Compiler zum Ausgeben von nativem Code zum Kompilieren statischer Baugruppen oder DynamicMethods was auch immer. – ControlFlow