How is C# string interpolation compiled?

2019-04-18 14:21发布

问题:

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.

回答1:

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")));
}