10

In C# there is a string interpolation support like this:

$"Constant with {Value}" 

which will format this string using in-scope variable Value.

But the following won't compile in current C# syntax.

Say, I have a static Dictionary<string, string> of templates:

templates = new Dictionary<string, string> { { "Key1", $"{Value1}" }, { "Key2", $"Constant with {Value2}" } } 

And then on every run of this method I want to fill in the placeholders:

public IDictionary<string, string> FillTemplate(IDictionary<string, string> placeholderValues) { return templates.ToDictionary( t => t.Key, t => string.FormatByNames(t.Value, placeholderValues)); } 

Is it achievable without implementing Regex parsing of those placeholders and then a replace callback on that Regex? What are the most performant options that can suit this method as being a hot path?

For example, it is easily achievable in Python:

>>> templates = { "Key1": "{Value1}", "Key2": "Constant with {Value2}" } >>> values = { "Value1": "1", "Value2": "example 2" } >>> result = dict(((k, v.format(**values)) for k, v in templates.items())) >>> result {'Key2': 'Constant with example 2', 'Key1': '1'} >>> values2 = { "Value1": "another", "Value2": "different" } >>> result2 = dict(((k, v.format(**values2)) for k, v in templates.items())) >>> result2 {'Key2': 'Constant with different', 'Key1': 'another'} 
2
  • Internally, Python's format must be using a Regular Expression, what do you have against using one? Commented Apr 27, 2018 at 22:24
  • My initial implementation is using Regex and is slow, showing high contention on Regex matching. I surely could leave it like that, but this method is the hot path. Commented Apr 27, 2018 at 22:45

3 Answers 3

6

No, it's not possible, instead you should use String.Format.

With String format your string template would look like string template = "The temperature is {0}°C." and then to insert the value you could just:

decimal temp = 20.4m; string s = String.Format(template, temp); 

As shown in the Microsoft examples.

Sign up to request clarification or add additional context in comments.

3 Comments

string interpolation uses string.format essentially .. AFAIK
From your example, it moves towards positional format. Notice that the template dictionary had {Value1} and {Value2} placeholders in different values, so they both look like {0}, but should apply different values from placeholderValues dictionary. I guess I could have Tuple<string, string[]>("{0}", new[] { "Value1" }) and then use it to prepare a formatting array. Is there a cleaner way?
I don’t think there’s a cleaner way, you must declare both the template and the object array somehow.
4

Using an extension method that does a substitution based on regular expressions, I get a good speed up over using multiple Replace calls for each value.

Here is my extension method for expanding brace surrounded variables:

public static class ExpandExt { static Regex varPattern = new Regex(@"{(?<var>\w+)}", RegexOptions.Compiled); public static string Expand(this string src, Dictionary<string, string> vals) => varPattern.Replace(src, m => vals.TryGetValue(m.Groups[1].Value, out var v) ? v : m.Value); } 

And here is the sample code using it:

var ans = templates.ToDictionary(kv => kv.Key, kv => kv.Value.Expand(values)); 

Over 10,000 repeating expansions with values at 18 entries and typically only one replacement, I get 3x faster than multiple String.Replace calls.

4 Comments

Use RegexOptions.Compiled. You'll probably get a speed up as well
@pinkfloydx33 Changes from about 3x faster to about 3.5x faster.
My Regex was a bit different (\\{([^}]*)\\}), and it didn't have the Compiled flag. So Regex is actually better than string Replace. I'll mark this accepted.
This is a great way to solve it, however, the code sample was not complete to try it out instantly - so I am providing a .Net fiddle here for those, who are interested.
1

I think your best alternative to Regular Expressions is doing Replace on each possible Value key, but which is faster would depend on how many values you have and how common replacements for those values are.

var templates = new Dictionary<string, string> { { "Key1", "{Value1}" }, { "Key2", "Constant with {Value2}" } }; var values = new Dictionary<string, string> { { "Value1", "1" }, { "Value2", "example 2" } }; var ans = templates.ToDictionary(kv => kv.Key, kv => values.Aggregate(kv.Value, (s, v) => s.Replace($"{{{v.Key}}}", v.Value))); 

Note that a foreach instead of Aggregate would be marginally faster, again depending on how many entries are in values. Also, pre-building new key and values Arrays with key already surrounded by braces can bring about a 4x speedup, but we are talking about milliseconds in your example.

var subkeys = values.Select(kv => $"{{{kv.Key}}}").ToArray(); var subvals = values.Select(kv => kv.Value).ToArray(); var ans4 = templates.ToDictionary(kv => kv.Key, kv => { var final = kv.Value; for (int j1 = 0; j1 < subkeys.Length; ++j1) final = final.Replace(subkeys[j1], subvals[j1]); return final; }); 

Of course, if your values array changes with each method call, using two Lists with braces around the keys would be a better storage structure as translating every time will eat up the time savings.

2 Comments

values dictionary is expected to have 15-20 keys. Executed your Aggregate example on a unit test - got around ten times speed up and no contentions in profiling (comparing to Regex). Looking into this direction.
@SergeyIonov Running a Regex on an expanded example using a values with 18 keys without braces versus Aggregate and it was 3X faster. I'll post it as another answer.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.