Suppose we have an interface:
public interface ICalculator { decimal Calculate(decimal x, decimal y); }
the calculation logic is implemented in javascript code (actually TypeScript), we want to dynamically create the next implementation using Reflection.Emit so that we can separate unit tests with a C # implementation
public class Calculator : ICalculator { private ScriptEngine ScriptEngine; public Calculator(ScriptEngine scriptEngine, string jsFileFullPath) { this.ScriptEngine = scriptEngine; var jsFileContent = File.ReadAllText(jsFileFullPath); this.ScriptEngine.Execute(jsFileContent); } public decimal Calculate(decimal x, decimal y) { string script = @" var rf1013 = new TotalTaxation.TaxformCalculation.RF1013({0},{1}); rf1013.Calculate(); var result = rf1013.RF1013Sum; "; this.ScriptEngine.Evaluate(string.Format(script, x, y)); var result = this.ScriptEngine.Evaluate("result"); return Convert.ToDecimal(result); } }
we can get IL from IL DASM:
.class public auto ansi beforefieldinit Calculator extends [mscorlib]System.Object implements ICalculator { } // end of class Calculator .field private class [ClearScript]Microsoft.ClearScript.ScriptEngine ScriptEngine .method public hidebysig specialname rtspecialname instance void .ctor(class [ClearScript]Microsoft.ClearScript.ScriptEngine scriptEngine, string jsFileFullPath) cil managed { // Code size 37 (0x25) .maxstack 2 .locals init ([0] string jsFileContent) IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: nop IL_0007: nop IL_0008: ldarg.0 IL_0009: ldarg.1 IL_000a: stfld class [ClearScript]Microsoft.ClearScript.ScriptEngine Calculator::ScriptEngine IL_000f: ldarg.2 IL_0010: call string [mscorlib]System.IO.File::ReadAllText(string) IL_0015: stloc.0 IL_0016: ldarg.0 IL_0017: ldfld class [ClearScript]Microsoft.ClearScript.ScriptEngine Calculator::ScriptEngine IL_001c: ldloc.0 IL_001d: callvirt instance void [ClearScript]Microsoft.ClearScript.ScriptEngine::Execute(string) IL_0022: nop IL_0023: nop IL_0024: ret } // end of method JsRF1013Wrapper::.ctor .method public hidebysig newslot virtual final instance valuetype [mscorlib]System.Decimal Calculate(valuetype [mscorlib]System.Decimal x, valuetype [mscorlib]System.Decimal y) cil managed { // Code size 65 (0x41) .maxstack 4 .locals init ([0] string script, [1] object result, [2] valuetype [mscorlib]System.Decimal CS$1$0000) IL_0000: nop IL_0001: ldstr "\r\n var rf1013 = new TotalTaxati" + "on.TaxformCalculation.RF1013({0},{1});\r\n rf1013.Calc" + "ulate();\r\n var result = rf1013.RF1013Sum;\r\n " + " " IL_0006: stloc.0 IL_0007: ldarg.0 IL_0008: ldfld class [ClearScript]Microsoft.ClearScript.ScriptEngine Calculator::ScriptEngine IL_000d: ldloc.0 IL_000e: ldarg.1 IL_000f: box [mscorlib]System.Decimal IL_0014: ldarg.2 IL_0015: box [mscorlib]System.Decimal IL_001a: call string [mscorlib]System.String::Format(string, object, object) IL_001f: callvirt instance object [ClearScript]Microsoft.ClearScript.ScriptEngine::Evaluate(string) IL_0024: pop IL_0025: ldarg.0 IL_0026: ldfld class [ClearScript]Microsoft.ClearScript.ScriptEngine Calculator::ScriptEngine IL_002b: ldstr "result" IL_0030: callvirt instance object [ClearScript]Microsoft.ClearScript.ScriptEngine::Evaluate(string) IL_0035: stloc.1 IL_0036: ldloc.1 IL_0037: call valuetype [mscorlib]System.Decimal [mscorlib]System.Convert::ToDecimal(object) IL_003c: stloc.2 IL_003d: br.s IL_003f IL_003f: ldloc.2 IL_0040: ret } // end of method Calculator::Calculate
We created TypeCreator for this:
namespace TypeCreator { public interface ICalculator { decimal Calculate(decimal x, decimal y); } public class TypeCreator { private Type targetType; private ScriptEngine scriptEngine; private string jsFileFullPath; public TypeCreator(Type targetType, ScriptEngine scriptEngine, string jsFileFullPath) { this.targetType = targetType; this.scriptEngine = scriptEngine; this.jsFileFullPath = jsFileFullPath; } public Type build() { AppDomain currentAppDomain = AppDomain.CurrentDomain; AssemblyName assyName = new AssemblyName(); assyName.Name = "MyAssyFor_" + targetType.Name; AssemblyBuilder assyBuilder = currentAppDomain.DefineDynamicAssembly(assyName, AssemblyBuilderAccess.Run); ModuleBuilder modBuilder = assyBuilder.DefineDynamicModule("MyModFor_" + targetType.Name); String newTypeName = "Imp_" + targetType.Name; TypeAttributes newTypeAttribute = TypeAttributes.Class | TypeAttributes.Public; Type[] ctorParams = new Type[] { typeof(ScriptEngine), typeof(string) }; Type newTypeParent; Type[] newTypeInterfaces; if (targetType.IsInterface) { newTypeParent = null; newTypeInterfaces = new Type[] { targetType }; } else { newTypeParent = targetType; newTypeInterfaces = new Type[0]; } TypeBuilder typeBuilder = modBuilder.DefineType(newTypeName, newTypeAttribute, newTypeParent, newTypeInterfaces); FieldBuilder scriptEngineField = typeBuilder.DefineField("scriptEngine", typeof(ScriptEngine), FieldAttributes.Public); FieldBuilder jsFileFullPathField = typeBuilder.DefineField("jsFileFullPath", typeof(string), FieldAttributes.Public); Type objType = Type.GetType("System.Object"); ConstructorInfo objCtor = objType.GetConstructor(new Type[0]); ConstructorBuilder wrapperCtor = typeBuilder.DefineConstructor( MethodAttributes.Public, CallingConventions.Standard, ctorParams); ILGenerator ctorIL = wrapperCtor.GetILGenerator(); ctorIL.Emit(OpCodes.Ldarg_0); ctorIL.Emit(OpCodes.Call, objCtor); ctorIL.Emit(OpCodes.Nop); ctorIL.Emit(OpCodes.Nop); ctorIL.Emit(OpCodes.Ldarg_0); ctorIL.Emit(OpCodes.Ldarg_1); ctorIL.Emit(OpCodes.Stfld, scriptEngineField); ctorIL.Emit(OpCodes.Ldarg_2); ctorIL.Emit(OpCodes.Call, typeof(File).GetMethod("ReadAllText", new Type[] { typeof(string) })); ctorIL.Emit(OpCodes.Stloc_0); ctorIL.Emit(OpCodes.Ldarg_0); ctorIL.Emit(OpCodes.Ldfld, scriptEngineField); ctorIL.Emit(OpCodes.Ldloc_0); ctorIL.Emit(OpCodes.Callvirt, typeof(ScriptEngine).GetMethod("Execute", new Type[] { typeof(string) })); ctorIL.Emit(OpCodes.Nop); ctorIL.Emit(OpCodes.Nop);
Use it as follows:
var jsFileFullPath = "JsFiles\\Total.js"; TypeCreator tc = new TypeCreator(typeof(ICalculator), new JScriptEngine(), jsFileFullPath); Type t = tc.build(); // Prepares the parameters var scriptArgs = new System.Collections.ArrayList(); scriptArgs.Add(new JScriptEngine()); scriptArgs.Add(jsFileFullPath); ICalculator calculator = (ICalculator)Activator.CreateInstance(t, scriptArgs); var result = calculator.Calculate(3.0m, 5.0m); Console.Write(string.Format("calculator.Calculate(3.0m, 5.0m)={0}", result)); Console.Read();
Throws an exception:
The "Calculate" method of type "Imp_ICalculator" from the assembly "MyAssyFor_ICalculator", Version = 0.0.0.0, Culture = neutral, PublicKeyToken = null 'has no implementation.
What is the problem?