Search code examples
javascriptc#emitclearscript

How to create a C# class (according to an existing class) dynamically at runtime


Background:

We have a project with client side(Javascript) and server side(C#). There is a calculation logic need to run in both sides, so it is written in both Javascript and C#. We have many unit tests for the C# version classes. Our goal is to share the unit tests for both C# and Javascript implementation.

Current situation:

We are able to run the Javascript code in an embeded JS engine (Microsoft ClearScript). The code looks like this:

public decimal Calulate(decimal x, decimal y) 
{
     string script = @"
            var calc = new Com.Example.FormCalculater();
            var result = calc.Calculate({0}, {1});";

     this.ScriptEngine.Evaluate(string.Format(script, x, y));

     var result = this.ScriptEngine.Evaluate("result");
     return Convert.ToDecimal(result);
}

However, writing such classes takes a lot of effort. We are looking for a way to create such classes dynamically at runtime.

For exmample, we have a C# class (also has it JS version in a JS fle):

public class Calculator {
    public decimal Add(decimal x, decimal y){ ... }
    public decimal Substract(decimal x, decimal y){ ... }
    public decimal Multiply(decimal x, decimal y){ ... }
    public decimal Divide(decimal x, decimal y){ ... }
}

We want to create a dynamic class having the same methods but calling the Script engine to call the related JS code.

Is it possible to do it?


Solution

  • Sounds pretty easy. You don't even need to manually emit any IL nowadays :)

    The easiest way would be to ignore the "create it dynamically" part. You can simply use a T4 template to create the class automatically at compile-time. If your only consideration is unit tests, this is a pretty easy way to solve your problem.

    Now, if you want to really create the type dynamically (at runtime), this gets a bit more complicated.

    First, create an interface that contains all the required methods. The C# class will just implement this interface directly, while we'll generate the helper class to conform to this interface.

    Next, we create the helper class:

    var assemblyName = new AssemblyName("MyDynamicAssembly");
    var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
    var moduleBuilder = assemblyBuilder.DefineDynamicModule("Module");
    
    var typeBuilder = moduleBuilder.DefineType("MyNewType", TypeAttributes.Public | TypeAttributes.Class | TypeAttributes, typeof(YourClassBase), new[] { typeof(IYourInterface) } );
    

    The TypeBuilder allows us to define all those methods, so let's do that next.

    // Get all the methods in the interface
    foreach (var method in typeof(IYourInterface).GetMethods())
    {
      var parameters = method.GetParameters().Select(i => i.ParameterType).ToArray();
    
      // We can only compile lambda expressions into a static method, so we'll have this helper. this is going to be YourClassBase.
      var helperMethod = typeBuilder.DefineMethod
            (
                "s:" + method.Name,
                MethodAttributes.Private | MethodAttributes.Static,
                method.ReturnType,
                new [] { method.DeclaringType }.Union(parameters).ToArray()
            );
    
      // The actual instance method
      var newMethod = 
        typeBuilder.DefineMethod
            (
                method.Name, 
                MethodAttributes.Public | MethodAttributes.Virtual, 
                method.ReturnType,
                parameters
            );
    
      // Compile the static helper method      
      Build(method).CompileToMethod(helperMethod);
    
      // We still need raw IL to call the helper method
      var ilGenerator = newMethod.GetILGenerator();
    
      // First argument is (YourClassBase)this, then we emit all the other arguments.
      ilGenerator.Emit(OpCodes.Ldarg_0);
      ilGenerator.Emit(OpCodes.Castclass, typeof(YourClassBase));
      for (var i = 0; i < parameters.Length; i++) ilGenerator.Emit(OpCodes.Ldarg, i + 1);
    
      ilGenerator.Emit(OpCodes.Call, helperMethod);
      ilGenerator.Emit(OpCodes.Ret);
    
      // "This method is an implementation of the given IYourInterface method."
      typeBuilder.DefineMethodOverride(newMethod, method);
    }
    

    To create the helper method body, I'm using these two helper methods:

    LambdaExpression Build(MethodInfo methodInfo)
    {
      // This + all the method parameters.
      var parameters = 
        new [] { Expression.Parameter(typeof(YourClassBase)) }
        .Union(methodInfo.GetParameters().Select(i => Expression.Parameter(i.ParameterType)))
        .ToArray();
    
      return
        Expression.Lambda
        (
          Expression.Call
          (
            ((Func<MethodInfo, YourClassBase, object[], object>)InvokeInternal).Method,
            Expression.Constant(methodInfo, typeof(MethodInfo)),
            parameters[0],
            Expression.NewArrayInit(typeof(object), parameters.Skip(1).Select(i => Expression.Convert(i, typeof(object))).ToArray())
          ),     
          parameters
        );
    }
    
    public static object InvokeInternal(MethodInfo method, YourClassBase @this, object[] arguments)
    {
      var script = @"
        var calc = new Com.Example.FormCalculater();
        var result = calc.{0}({1});";
    
      script = string.Format(script, method.Name, string.Join(", ", arguments.Select(i => Convert.ToString(i))));
    
      @this.ScriptEngine.Evaluate(script);
    
      return (object)Convert.ChangeType(@this.ScriptEngine.Evaluate("result"), method.ReturnType);
    }
    

    If you want, you can make this a lot more specific (generate the expression tree to be a better match for the given method), but this saves us a lot of trouble and allows us to use C# for most of the hard stuff.

    I'm assuming all your methods have a return value. If not, you'll have to adjust for that.

    And finally:

    var resultingType = typeBuilder.CreateType();
    
    var instance = (IYourInterface)Activator.CreateInstance(resultingType);
    var init = (YourClassBase)instance;
    init.ScriptEngine = new ScriptEngine();
    
    var result = instance.Add(12, 30);
    Assert.AreEqual(42M, result);
    

    Just for completeness, here's the IYourInterface and YourClassBase I've used:

    public interface IYourInterface
    {
      decimal Add(decimal x, decimal y);
    }
    
    public abstract class YourClassBase
    {
      public ScriptEngine ScriptEngine { get; set; }
    }
    

    I do strongly suggest using text templates to generate the source code in compile-time, if you can, though. Dynamic code tends to be tricky to debug (and write, of course). On the other hand, if you just generate this stuff from a template, you'll see the whole generated helper class in code.