19

I have some template string

this is my {0} template {1} string

which I plan to put user values in using String.Format().

The string actually is longer so for readability I use:

this is my {goodName1} template {goodName2} string

And then String.Replace each parameter with its value.

How can I get the highest performance and readability?

Maybe I should not have this template in a file (as now) but dynamically build it by concatanating to a string builder and adding the params when required? Although it's less readable.

What's my other options?

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
Yaron Naveh
  • 23,560
  • 32
  • 103
  • 158
  • 2
    It's a pity this question turned into a debate about speed. The repleated `String.Replace` solution has a worse problem. If the replacement text also contains substrings of the form `{goodNameN}` then will they be expanded or not? It turns out it depends on the order in which the replacements are done. This is the kind of subtle fuzziness than can lie harmless for years and then bite in mysterious ways long after the code is forgotten. – Adrian Ratnapala Oct 09 '13 at 13:53

9 Answers9

51

You can put the parameters in a dictionary and use the Regex.Replace method to replace all of the parameters in one replacement. That way the method scales well if the template string gets long or the number of parameters grows.

Example:

Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters.Add("goodName1", "asdf");
parameters.Add("goodName2", "qwerty");
string text = "this is my {goodName1} template {goodName2} string";
text = Regex.Replace(text, @"\{(.+?)\}", m => parameters[m.Groups[1].Value]);
Evan Wondrasek
  • 2,610
  • 3
  • 22
  • 21
Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • This solution is the most performant, you should use an extension method of some sort to take care of non-existant keys when looking up the dictionary though. – Yann Schwartz Jun 06 '09 at 16:00
  • And \{([^}]+)\} is faster than using the non-greedy operator. – Yann Schwartz Jun 06 '09 at 16:01
  • @Guffa - A good note to point out is `m.Value` should be `m.Groups[1].Value` as well. `m.Value` returns the same as `m.Groups[0].Value` which is the entire part matched. – Zack Jan 14 '12 at 20:01
  • 1
    harder to read, regex has no massive speed benefit and is makes it even more harder to read let alone script. `It just doesn't matter` - I spent too much time optimizing and getting 0.1ms speed boost. yay- and my code looked worse than assembler language. string.replace works great, is readable and even juniors understand it. But it must never be used in massive loops – Piotr Kula Mar 26 '13 at 13:14
  • @ppumkin: It scales better which is really the only thing that matters when it comes to performance, but perhaps more importantly, it's more robust. If you happen to have a value that looks like a parameter, it won't be replaced by mistake by a replacement later in the loop. – Guffa Jan 29 '14 at 14:49
  • Be warned, the sample code is not safe, when the template has variable pattern not defined in the `parameters` dictionary (which is usually the case with template files). – Ben Jul 16 '18 at 16:31
16

From Atwood: It. Just. Doesn't. Matter.

AgileJon
  • 53,070
  • 5
  • 41
  • 38
  • 27
    Have to disagree - it can matter. Just *make sure* it matters before optimising, rather than assuming so. – Brian Nov 09 '10 at 12:01
  • 4
    Sometimes it really does matter... as in this case where Replace was tuned from 20 seconds to 0.1... http://www.codeproject.com/Articles/298519/Fast-Token-Replacement-in-Csharp – Eric J. Jul 11 '12 at 21:52
  • 1
    Atwood - it doesn't matter over 100,000 cycles. Over 200,000,000 it might. – matt.chatterley May 29 '18 at 10:09
8

My spontaneous solution would look like this:

string data = "This is a {template1} that is {template2}.";

Dictionary<string, string> replacements = new Dictionary<string, string>(){
    {"{template1}", "car"},
    {"{template2}", "red"},
};

foreach (string key in replacements.Keys)
{
    data = data.Replace(key, replacements[key]);
}
Console.WriteLine(data); // outputs "This is a car that is red."

I have used this kind of template replacements in several real-world projects and have never found it to be a performance issue. Since it's easy to use and understand, I have not seen any reason to change it a lot.

Fredrik Mörk
  • 155,851
  • 29
  • 291
  • 343
7

Like anything, it depends. If the code is going to be called millions of times every day, then think about performance. If it's a few times a day then go for readability.

I've done some benchmarking between using normal (immutable) strings and StringBuilder. Until you start doing a huge amount in small bit of time, you don't need to worry about it too much.

Iain Holder
  • 14,172
  • 10
  • 66
  • 86
  • 1
    I agree with Iain. For more information check out this article on Code Project: http://www.codeproject.com/KB/string/string.aspx – Kane Jun 06 '09 at 15:42
4

The same thing above that Fredrick posted above, but with linq.

    public static string FindandReplace(this string inputText, Dictionary<string, string> placeHolderValues)
    {
        if (!string.IsNullOrWhiteSpace(inputText))
        {
            return placeHolderValues.Keys.Aggregate(inputText, (current, key) => current.Replace(key, placeHolderValues[key]));
        }
        else return inputText;
    }
Cubicle.Jockey
  • 3,288
  • 1
  • 19
  • 31
jaxxbo
  • 7,314
  • 4
  • 35
  • 48
4

Just modified the above answer to the following:

    string data = "This is a {template1} that is {template2}.";

    Dictionary<string, string> replacements = new Dictionary<string, string>(){
        {"{template1}", "car"},
        {"{template2}", "red"},
    };

    data.Parse(replacements);

Extension method:

public static class Parser
{
    public static string Parse(this string template, Dictionary<string, string> replacements)
    {
        if (replacements.Count > 0)
        {
            template = replacements.Keys
                        .Aggregate(template, (current, key) => current.Replace(key, replacements[key]));
        }
        return template;
    }
}

Hope this helps.. :)

4

BEWARE of getting bogged down with this type of thinking. Unless this code is running hundreds of time per minute and the template file is several K in size, it is more important to get it done. Do not waste a minute thinking about problems like this. In general, if you are doing a lot with string manipulations, then use a string builder. It even has a Replace method. But, why bother. When you are done, and IF you find that you have a performance problem, use PerfMon and fix the REAL bottlenecks at that time.

Bobby Ortiz
  • 3,077
  • 7
  • 35
  • 45
1

If you need a good, fast, but simple template engine, you should check out StringTemplate. For simple templates that don't require any logic or flow control in the template itself, StringTemplate is GREAT.

http://www.stringtemplate.org/download.html

jrista
  • 32,447
  • 15
  • 90
  • 130
0

The fastest way to do it is with a StringBuilder using individual calls to StringBuilder.Append(), like this:

string result = new StringBuilder("this is my ")
                 .Append(UserVar1)
                 .Append(" template ")
                 .Append(UserVar2)
                 .Append(" string")
                 .ToString();

I've thoroughly benchmarked the framework code, and this will be fastest. If you want to improve readability you could keep a separate string to show the user, and just use this in the background.

Community
  • 1
  • 1
Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • 3
    You don't need to do this. The C# compiler will compile this exactly the same as if you just did `string result = "this is my " + UserVar1 + " template " + UserVar2 + " string"` – Nick Whaley Nov 27 '12 at 21:35
  • In the case where performance and readability really matters, it's very unlikely the template will be hardcoded in code. can't see how this could help. – Ben Jul 16 '18 at 14:26