2015-11-06 46 views
6

Ho un codice come questo per emettere il codice IL che carica valori interi o stringa. Ma non so come aggiungere il tipo decimal a quello. Non è supportato nel metodo Emit. Qualche soluzione a questo?Emettere il codice IL per caricare un valore decimale

ILGenerator ilGen = methodBuilder.GetILGenerator(); 
if (type == typeof(int)) 
{ 
    ilGen.Emit(OpCodes.Ldc_I4, Convert.ToInt32(value, CultureInfo.InvariantCulture)); 
} 
else if (type == typeof(double)) 
{ 
    ilGen.Emit(OpCodes.Ldc_R8, Convert.ToDouble(value, CultureInfo.InvariantCulture)); 
} 
else if (type == typeof(string)) 
{ 
    ilGen.Emit(OpCodes.Ldstr, Convert.ToString(value, CultureInfo.InvariantCulture)); 
} 

Non funziona:

else if (type == typeof(decimal)) 
{ 
    ilGen.Emit(OpCodes.Ld_???, Convert.ToDecimal(value, CultureInfo.InvariantCulture)); 
} 

Edit: Bene, ecco quello che ho fatto:

else if (type == typeof(decimal)) 
{ 
    decimal d = Convert.ToDecimal(value, CultureInfo.InvariantCulture); 
    // Source: https://msdn.microsoft.com/en-us/library/bb1c1a6x.aspx 
    var bits = decimal.GetBits(d); 
    bool sign = (bits[3] & 0x80000000) != 0; 
    byte scale = (byte)((bits[3] >> 16) & 0x7f); 
    ilGen.Emit(OpCodes.Ldc_I4, bits[0]); 
    ilGen.Emit(OpCodes.Ldc_I4, bits[1]); 
    ilGen.Emit(OpCodes.Ldc_I4, bits[2]); 
    ilGen.Emit(sign ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); 
    ilGen.Emit(OpCodes.Ldc_I4, scale); 
    var ctor = typeof(decimal).GetConstructor(new[] { typeof(int), typeof(int), typeof(int), typeof(bool), typeof(byte) }); 
    ilGen.Emit(OpCodes.Newobj, ctor); 
} 

Ma non genera un codice operativo newobj, ma invece nop e stloc.0. Il costruttore viene trovato e inoltrato alla chiamata Emit. Cosa c'è che non va qui? Ovviamente viene generato un InvalidProgramException quando si tenta di eseguire il codice generato perché lo stack è completamente incasinato.

+1

A quanto pare (ma non prendere la mia parola per esso) per "carico decimali" Non c'è un codice operativo diretta, si caricano gli argomenti e chiama il costruttore decimali: vedi http : //stackoverflow.com/a/485834/266143 – CodeCaster

+1

Vedere anche http://codeblog.jonskeet.uk/2014/08/22/when-is-a-constant-not-a-constant-when-its-a -decimale/. In breve: i decimali non sono tipi primitivi CLR e non esiste alcun codice operativo IL per il caricamento diretto. –

+0

Vedere la mia modifica sopra per una soluzione non funzionante. – ygoe

risposta

9

Avanti, basta decompilare un codice C# che fa la stessa cosa: vedrai che non esiste una primitiva decimale.

42M 

compila per

ldc.i4.s 2A 
newobj  System.Decimal..ctor 

Per un numero decimale, questo è molto più complicato:

42.3M 

ldc.i4  A7 01 00 00 
ldc.i4.0  
ldc.i4.0  
ldc.i4.0  
ldc.i4.1  
newobj  System.Decimal..ctor 

Il modo più semplice per ottenere questo valore per un decimale arbitrario è utilizzare l'overload int[] del costruttore e il metodo statico GetBits. È anche possibile decodificare il metodo per consentire all'utente di chiamare il costruttore più semplice con i valori corretti, oppure utilizzare la riflessione per leggere lo stato interno: sono disponibili numerose opzioni.

EDIT:

Sei vicino, ma hai rotto l'Ilgen - mentre l'ultimo argomento per il costruttore è un byte, la costante si sta caricando deve essere unint. I seguenti lavori come previsto:

var bits = decimal.GetBits(d); 
bool sign = (bits[3] & 0x80000000) != 0; 
int scale = (byte)((bits[3] >> 16) & 0x7f); 
gen.Emit(OpCodes.Ldc_I4, bits[0]); 
gen.Emit(OpCodes.Ldc_I4, bits[1]); 
gen.Emit(OpCodes.Ldc_I4, bits[2]); 
gen.Emit(sign ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); 
gen.Emit(OpCodes.Ldc_I4, scale); 
var ctor = typeof(decimal).GetConstructor(new[] { typeof(int), typeof(int), 
               typeof(int), typeof(bool), typeof(byte) }); 
gen.Emit(OpCodes.Newobj, ctor); 
gen.Emit(OpCodes.Ret); 

EDIT 2:

Un semplice esempio di come è possibile utilizzare alberi di espressione (in questo caso l'albero è costruito dal compilatore C#, ma che sta a te) per definire i corpi metodo dinamico:

var assembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Test"), 
                AssemblyBuilderAccess.Run); 
var module = assembly.DefineDynamicModule("Test"); 
var type = module.DefineType("TestType"); 

var methodBuilder = type.DefineMethod("MyMethod", MethodAttributes.Public 
                | MethodAttributes.Static); 
methodBuilder.SetReturnType(typeof(decimal)); 

Expression<Func<decimal>> decimalExpression =() => 42M; 

decimalExpression.CompileToMethod(methodBuilder); 

var t = type.CreateType(); 

var result = (decimal)t.GetMethod("MyMethod").Invoke(null, new object[] {}); 

result.Dump(); // 42 :) 
+3

Questo è apparentemente dovuto al fatto che _ "The Extended Numerics Library" _ non fa parte delle specifiche CIL, perché _ "alcuni processori comunemente disponibili non forniscono supporto diretto per i tipi di dati" _ (fonte: http: //www.ecma -international.org/publications/files/ECMA-ST/ECMA-335.pdf, PDF di grandi dimensioni). Ecco perché non esiste un codice operativo per caricare un 'decimale' (né un' singolo'). – CodeCaster

+0

Grazie per gli indizi. Sfortunatamente non funziona ancora correttamente. Vedi la mia modifica nella domanda. – ygoe

+1

@LonelyPixel Aggiornato con il codice corretto - 'ldc.i4' * deve * essere passato a' int'. È un peccato che ILGen ti permetta di fare questo, ma devi solo stare attento :) Tuttavia, non hai davvero bisogno di ILGen tanto oggi - perché non usare 'Expression.Compile'? – Luaan

0

Come Luaan accennato prima, è possibile utilizzare il metodo decimal.GetBits e il costruttore int[].Date un'occhiata a questo esempio:

public static decimal RecreateDecimal(decimal input) 
{ 
    var bits = decimal.GetBits(input); 

    var d = new DynamicMethod("recreate", typeof(decimal), null); 
    var il = d.GetILGenerator(); 

    il.Emit(OpCodes.Ldc_I4_4); 
    il.Emit(OpCodes.Newarr, typeof(int)); 

    il.Emit(OpCodes.Dup); 
    il.Emit(OpCodes.Ldc_I4_0); 
    il.Emit(OpCodes.Ldc_I4, bits[0]); 
    il.Emit(OpCodes.Stelem_I4); 

    il.Emit(OpCodes.Dup); 
    il.Emit(OpCodes.Ldc_I4_1); 
    il.Emit(OpCodes.Ldc_I4, bits[1]); 
    il.Emit(OpCodes.Stelem_I4); 

    il.Emit(OpCodes.Dup); 
    il.Emit(OpCodes.Ldc_I4_2); 
    il.Emit(OpCodes.Ldc_I4, bits[2]); 
    il.Emit(OpCodes.Stelem_I4); 

    il.Emit(OpCodes.Dup); 
    il.Emit(OpCodes.Ldc_I4_3); 
    il.Emit(OpCodes.Ldc_I4, bits[3]); 
    il.Emit(OpCodes.Stelem_I4); 

    il.Emit(OpCodes.Newobj, typeof(decimal).GetConstructor(new[] {typeof(int[])})); 

    il.Emit(OpCodes.Ret); 
    return (decimal) d.Invoke(null, null); 
}