Tuesday 14 February 2017

String Interpolation

Both ES6 and C# 6 feature string interpolation. It was an oddly missing feature bearing in mind that it has been present for so long in other languages (e.g. perl). The feature is nice, but don't fool yourself thinking that it can do more than it really can. It's well explained here. Though the article is about C#, the same limitation applies to javascript.

The basic idea is that you can not use string interpolation for a string that is defined outside of the source code (for example in a template that you get from disk or via htttp request). In C# the the compiler takes care of parsing the string literal into a FormattableString (you can think of it as doing something similar to String.Format), so it needs to know what variables to use for the place holders at compile time. In javascript compilation happens at the time of running, but the idea is the same, you need the compiler to parse the string, and for that the string has to be declared as a literal in code.

Well, I guess the solution is clear, if we need the compiler to work with a string that is not defined in code, we'll just have to call the compiler ourselves from our code.

The solution in javascript is so easy as using eval (yes, but sure don't use eval in a user provided string, it could contain malicious code).

let template = "hello ${user}";

function parseTemplate(template, user){
 return eval("`" + template + "`");
};

parsedTemplate = parseTemplate(template, "xose");
console.log(parsedTemplate);

In C#, we can use any of the existing options for compiling code at runtime. The Roslyn scripting is pretty straightforward.

public class Globals2
{
 public string Val1;
 public string Val2;
}

static string ParseStringTemplate(string st, string val1, string val2)
{
 var gl = new Globals2
 {
  Val1 = val1,
  Val2 = val2
 };
 string result = CSharpScript.EvaluateAsync<string>("$\"" + st + "\"", globals:gl).Result;
 return result;
}

var template = "This is a test with val1: {Val1} and val2: {Val2}";
 var parsed = ParseStringTemplate(template, "first", "second");

Notice however that Roslyn scripting is not an ideal solution as it will compile the code into a new assembly that will be loaded in the current Application Domain and hence will never be unloaded until the application exits. If you are usingthis it'll have an effect in the memory footprint. To avoid this we could use LCG or Expression Trees, maybe one day I'll give it a try, right now I don't feel like going low level. Another option that seems pretty more comfortable is using cs-script. Its script engine will use CodeDom and will load the compiled assembly in a separate Application Domain than then can be unloaded. Problem is that making it work with C# 6 is a bit tricky due to the stupid decisions taken by Microsoft regarding CodeDom Providers for C# 6.

No comments:

Post a Comment