22

I know that interpolation is syntactic sugar for string.Format(), but does it have any special behavior/recognition of when it is being used with a string formatting method?

If I have a method:

void Print(string format, params object[] parameters)

And the following call to it using interpolation:

Print($"{foo} {bar}");

Which of the following calls lines is most equivalent to the compiled result of string interpolation?

Print(string.Format("{0} {1}", new[] { foo, bar }));
Print("{0} {1}", new[] { foo, bar });

Reasoning behind the question: Logging frameworks such as NLog typically defer string formatting until they have determined that a log message will actually be written. In general I prefer the string interpolation syntax, but I need to know if it may incur an extra performance penalty.

Merad
  • 749
  • 1
  • 6
  • 14
  • 1
    I know of a really easy way to find out. Try both and compare the generated IL. – itsme86 May 20 '16 at 15:51
  • You can use LinqPad to see IL code faster. Or any decomplie tool like JustDecomplie or Reflector – Cihan Yakar May 20 '16 at 15:57
  • Your method is fundamentally unsafe. What if I call it with a single string that happens to contain braces? – SLaks May 20 '16 at 16:14
  • @SLaks It's no more safe or unsafe than `String.Format` already is? So long as it's documented in a similar way, I don't here a problem here? – James Thorpe May 20 '16 at 16:22
  • @JamesThorpe: `String.Format` has the same issue, but people hopefully don't do that. Whereas `Print($"{foo} {bar}");` is broken. – SLaks May 20 '16 at 17:08
  • @SLaks: Of course, in the real world you would want a set of method overloads to take care of that problem. – Merad May 20 '16 at 17:55

1 Answers1

32

It is compiled in one of two ways.

If you use a string interpolation expression where a string is expected, it is compiled into a call to string.Format.

Basically, this:

string s = $"now is {DateTime.Now}";

is turned into this:

string s = string.Format("now is {0}", DateTime.Now);

See it for yourself in Try Roslyn.

Nothing magical here.

Now, on the other hand, if you use it in a place where a FormattableString (a new type in .NET 4.6) is expected, it is compiled into a call to FormattableStringFactory.Create:

public void Test(FormattableString s)
{
}

Test($"now is {DateTime.Now}");

The call there is turned into this:

Test(FormattableStringFactory.Create("now is {0}", DateTime.Now));

See it for yourself in Try Roslyn.

So in essence, to answer your final question there:

This call:

Print($"{foo} {bar}");

Will be translated to this:

Print(string.Format("{0} {1}", foo, bar));

which will incur the cost of the formatting through string.Format before Print is even called.

If you could add, or find, an overload of Print that takes a FormattableString, then you could defer the actual cost of string.Format until after you've figured out if you need to log. Whether this has a measurable different in runtime is hard to say.

See it for yourself in Try Roslyn.


Bonus Round

Not only is the actual formatting deferred, but the ToString method of FormattableString allows you to specify a IFormatProvider.

This means that you can defer localized transformation as well.

public static void Print(FormattableString s)
{
    Console.WriteLine("norwegian: " + s.ToString(CultureInfo.GetCultureInfo("nb-NO")));
    Console.WriteLine("us: " + s.ToString(CultureInfo.GetCultureInfo("en-US")));
    Console.WriteLine("swedish: " + s.ToString(CultureInfo.GetCultureInfo("sv-SE")));
}
svick
  • 236,525
  • 50
  • 385
  • 514
Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • So in essence, to answer the OPs performance query, it results in a new class being constructed (`ConcreteFormattableString`, from [the factory](http://referencesource.microsoft.com/#mscorlib/system/Runtime/CompilerServices/FormattableStringFactory.cs,38)), but the arguments will continue to not be evaluated by the logging framework unless it calls the `ToString` method, ie it's going to end up logging it. – James Thorpe May 20 '16 at 15:59
  • **If** the logging method accepts a `FormattableString`. If the logging framework accepts a `string`, it will do the formatting through `string.Format` at the call site, before the logging framework figures out if it needs to log or not. – Lasse V. Karlsen May 20 '16 at 16:00
  • Yes, sorry, I skipped ahead - I had already had visions in my head of having replaced all the `String` arguments in my logging wrapper with `FormattableString` :) – James Thorpe May 20 '16 at 16:01
  • 2
    Note that adding an overload that accepts `FormattableString` won't help, because `string` has higher priority during overload resolution. You will have to use a separate method name. – svick May 20 '16 at 17:25
  • Actually, string interpolation could compile into string concatenation in some cases (e.g. when a `int` is used). `var a = "hello"; var b = $"{a} world";` compiles to string concatenation. `var a = "hello"; var b = $"{a} world {1}";` compiles to string format. – Omar Muscatello Nov 23 '18 at 17:37
  • @OmarMuscatello I tested this in LINQPad, it compiled to `string.Format` in both cases, how did you verify this? – Lasse V. Karlsen Nov 23 '18 at 18:31
  • I tested with ILSpy, ILDASM and [the tool](https://sharplab.io) you provided in the answer. – Omar Muscatello Nov 24 '18 at 10:22
  • Perhaps in a newer version of the compiler then? https://sharplab.io/#v2:EYLgZgpghgLgrgJwgZwLQFEAeAHJzkCWA9gHYBqUCBUwANigDQwgG0A+AAgAwAEHAjAG4AsACgOAZj4AmHgGEeAbzE9VfKRwAsPALIAKAJRKVa0wDdKPKDwC8PAEQALCLVpF7gnhYQ9gtngAk9opQAL48AO5ECLQAJh4mqqFioUA - switching to a newer version there does indeed use string concatenation. – Lasse V. Karlsen Nov 24 '18 at 22:01