The Scenario

A few days ago I was thinking of a system to add some scripting capabilities into an application. The scenario is quite simple: You have a file (xml, json or whatever you prefer) that defines some abstract action with a set of well-known parameters, the action consist in sending data to a remote endpoint. When an action is invoked the software must build on the fly the the actual data to be sent to remote service and send it using a socket or whatever else.
To increase flexibility the idea is to associate each action with a simple script that performs basic calculations and then returns output to the caller, then the caller can use this result to build the actual data and complete the action... just as an example we can have an action "send temperature in klingon degrees" that has custom code to convert data in the format used by Klingon people: when invoked our software gets the temperature from the sensor, passes value(s) to the script that converts that value into the required format, and then sends it to the remote system.
Why should I use scripting for this simple task ? because you can add new conversions on the fly, without the need to stop our application and without any prior knowledge of what the conversion is... A new planet has been discovered and ask for temperature ? no problem...

So I decided to explore a way to add scripting capabilities to may application, the requirements are that script may be added by a non-developer too, and must be as simple as adding some text in a portion of the xml, without the complication of a too complex syntax.

Roslyn

I've performad some search and decided that Roslyn is the best fit for this need because

  • Can use plain C# code
  • Does not requires classes definition or any other stuff
  • Can work with input parameters
  • Works on any platform
  • Can be precompiled
  • Once precomiled runs fast

A very useful overview can be found here:
https://daveaglick.com/posts/compiler-platform-scripting

Test Application

Let's create a test console application, I've used Visual Studio for Mac targeting the .NET Core 3.1
To use Roslyn scripting capabilities you need to use nuget to add a reference to Microsoft.CodeAnalysis.CSharp.Scripting.dll, after that you are ready to use the CSharpScript class.
The application defines a class Globals used to pass input parametes.
The Main method does the following

  • creates a stopwatch to evaluate execution time
  • precompiles the script using CSharpScript.Create method
  • then runs two times the script with different iput values
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;

namespace ConsoleApp1
{

    public class Globals
    {
        public int input;
        public int output;
    }

    class Program
    {

        static void Main(string[] args)
        {
            string source =
                        @"
int a = 1;
int b = 2;
return a + b + input + output;
            ";

            Stopwatch sw = new Stopwatch();
            sw.Start();

            Console.WriteLine($"Compiling script...");
            var scr = CSharpScript.Create(source, globalsType: typeof(Globals));
            scr.Compile();
            Console.WriteLine($"Compilation took {sw.ElapsedMilliseconds}ms");
            var g = new Globals { input = 3, output = 2 };

            sw.Restart();
            Console.WriteLine($"Start 1st run");
            Task<ScriptState<object>> t1 = scr.RunAsync(globals: g);
            t1.Wait();
            Console.WriteLine($"run took {sw.ElapsedMilliseconds}ms, Result = {t1.Result.ReturnValue}");

            sw.Restart();
            Console.WriteLine($"Start 2nd run");
            g.output = 3;
            Task<ScriptState<object>> t2 = scr.RunAsync(globals: g);
            t2.Wait();
            Console.WriteLine($"run took {sw.ElapsedMilliseconds}ms, Result = {t2.Result.ReturnValue}");

            Console.ReadLine();
        }

    }
}

This is the output on my MacBook Pro Retina, 13-inch, Mid 2014

Compiling script...
Compilation took 2648ms
Start 1st run
run took 7ms, Result = 8
Start 2nd run
run took 0ms, Result = 9

As expected compling the script takes a while (more that 2 seconds), but after that the execution time is really interesting, so at the cost of having to manage and cache precompiled scripts you can have a very fast script execution engine.

Enjoy!

Last modified: April 13, 2020

Author

Comments

Write a Reply or Comment

Your email address will not be published.