Is there a "String.Format" that can accept named input parameters instead of index placeholders? [duplicate]

This is what I know

str = String.Format("Her name is {0} and she's {1} years old", "Lisa", "10");

But I want something like

str = String("Her name is @name and she's @age years old");
str.addParameter(@name, "Lisa");
str.addParameter(@age, 10);

Solution 1:

In C# 6 you can use string interpolation:

string name = "Lisa";
int age = 20;
string str = $"Her name is {name} and she's {age} years old";

As Doug Clutter mentioned in his comment, string interpolation also supports format string. It's possible to change the format by specifying it after a colon. The following example will output a number with a comma and 2 decimal places:

var str = $"Your account balance is {balance:N2}"

As Bas mentioned in his answer, string interpolation doesn't support template string. Actually, it has no built in support for that. Fortunately it exists in some great libraries.


SmartFormat.NET for example has support for named placeholder:

Smart.Format("{Name} from {Address.City}, {Address.State}", user)

// The user object should at least be like that 

public class User
{
    public string Name { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public string City { get; set; }
    public string State { get; set; }
}

It is available on NuGet.


Mustache is also a great solution. Bas has described its pros well in his answer.

Solution 2:

If you don't have C#6 available in your project you can use Linq's .Aggregate():

    var str = "Her name is @name and she's @age years old";

    var parameters = new Dictionary<string, object>();
    parameters.Add("@name", "Lisa");
    parameters.Add("@age", 10);

    str = parameters.Aggregate(str, (current, parameter)=> current.Replace(parameter.Key, parameter.Value.ToString()));

If you want something matching the specific syntax in the question you can put together a pretty simple class based on Aggregate:

public class StringFormatter{

    public string Str {get;set;}

    public Dictionary<string, object> Parameters {get;set;}

    public StringFormatter(string p_str){
        Str = p_str;
        Parameters = new Dictionary<string, object>();
    }

    public void Add(string key, object val){
        Parameters.Add(key, val);
    }

    public override string ToString(){
        return Parameters.Aggregate(Str, (current, parameter)=> current.Replace(parameter.Key, parameter.Value.ToString()));
    }

}

Usable like:

var str = new StringFormatter("Her name is @name and she's @age years old");
str.Add("@name", "Lisa");
str.Add("@age", 10);

Console.WriteLine(str);

Note that this is clean-looking code that's geared to being easy-to-understand over performance.

Solution 3:

If you are ok assigning a local variable that contains the data you use to replace the template parameters, you can use the C# 6.0 string interpolation feature.

The basic principle is that you can do fairly advanced string replacement logic based on input data.

Simple example:

string name = "John";
string message = $"Hello, my name is {name}."

Complex example:

List<string> strings = ...
string summary = $"There are {strings.Count} strings. " 
  + $"The average length is {strings.Select(s => s.Length).Average()}"

Drawbacks:

  • No support for dynamic templates (e.g. from a resources file)

(Major) advantages:

  • It enforces compile time checks on your template replacement.

A nice open source solution that has almost the same syntax, is Mustache. It has two available C# implementations from what I could find - mustache-sharp and Nustache.

I have worked with mustache-sharp and found that it does not have the same power as the string interpolation, but comes close. E.g. you can do the following (stolen from it's github page).

Hello, {{Customer.Name}}
{{#newline}}
{{#newline}}
{{#with Order}}
{{#if LineItems}}
Here is a summary of your previous order:
{{#newline}}
{{#newline}}
{{#each LineItems}}
    {{ProductName}}: {{UnitPrice:C}} x {{Quantity}}
    {{#newline}}
{{/each}}
{{#newline}}
Your total was {{Total:C}}.
{{#else}}
You do not have any recent purchases.
{{/if}}
{{/with}}