Roslyn: [Feature Request] Concatenated interpolated strings could be optimized into a single string.Format

Created on 11 Aug 2017  路  9Comments  路  Source: dotnet/roslyn

Version Used:
Microsoft (R) Visual C# Compiler version 2.3.2.61928 (ec1cde8b)

Steps to Reproduce:
Compile the following program in release:
```C#
class Program
{
static void Main() { }

static string Create1(int a, int b, int c) =>
    string.Format(
        "{0} " +
        "{1} " +
        "{2} ",
        a, b, c);

static string Create2(int a, int b, int c) =>
    $"{a} " +
    $"{b} " +
    $"{c} ";

}

**Expected Behavior**:
In both cases, we'd end up with a single input string and array to the underlying Format call.

**Actual Behavior**:
The first example results in a single input string and array:

IL_0000: ldstr "{0} {1} {2} "
IL_0005: ldarg.0
IL_0006: box [System.Runtime]System.Int32
IL_000b: ldarg.1
IL_000c: box [System.Runtime]System.Int32
IL_0011: ldarg.2
IL_0012: box [System.Runtime]System.Int32
IL_0017: call string [System.Runtime]System.String::Format(string,
object,
object,
object)
````
but rather than effectively concatening the interpolations, the second example results in three distinct arrays and format calls, resulting in three intermediate strings that then need to be concatenated:

  IL_0000:  ldstr      "{0} "
  IL_0005:  ldarg.0
  IL_0006:  box        [System.Runtime]System.Int32
  IL_000b:  call       string [System.Runtime]System.String::Format(string,
                                                                    object)
  IL_0010:  ldstr      "{0} "
  IL_0015:  ldarg.1
  IL_0016:  box        [System.Runtime]System.Int32
  IL_001b:  call       string [System.Runtime]System.String::Format(string,
                                                                    object)
  IL_0020:  ldstr      "{0} "
  IL_0025:  ldarg.2
  IL_0026:  box        [System.Runtime]System.Int32
  IL_002b:  call       string [System.Runtime]System.String::Format(string,
                                                                    object)
  IL_0030:  call       string [System.Runtime]System.String::Concat(string,
                                                                    string,
                                                                    string)

This is relevant, because splitting string literals across lines with + is a common way of avoiding really long lines, and it'd be nice to be able to do the same thing with string interpolation without taking the further perf hit.

Area-Compilers Feature Request

Most helpful comment

I'm interpreting it as

    static string Create2(int a, int b, int c) =>
        $"{a} " +
        $"{b} " +
        $"{c} ";

to be translated into

    static string Create2(int a, int b, int c) =>
        $"{a} {b} {c} ";

All 9 comments

@stephentoub
The equivalent is;-
c# static string Create2(int a, int b, int c) => string.format("{0} ", a) + string.format("{0} ", b) + string.format("{0) ", c);
Hence the non-optimised code.

True there are possible optimisations for interpolation strings, provided they don't have format specifier eg {a,-2:C2}.

Should we optimise across multiple calls (in the same expression) to String.Format and Interpolation Strings?

The equivalent is

@AdamSpeight2008, I understand that's how it compiles today.

True there are possible optimisations for interpolation strings

That's the point of this issue.

I really hope that the team consider proposals regarding translating interpolation to string concatenations when possible (which in turn allows it in a constant context like attributes) as a part of this effort.

@stephentoub There been issues / proposals for optimising within a single interpolation string, not across multiple interpolation strings. Which is what your asking.

I'm interpreting it as

    static string Create2(int a, int b, int c) =>
        $"{a} " +
        $"{b} " +
        $"{c} ";

to be translated into

    static string Create2(int a, int b, int c) =>
        $"{a} {b} {c} ";

I'm interpreting it as

Exactly, that's what I'm suggesting it should be.

The proposed optimization is visible to the user code, as it changes the order of execution of evaluation of the arguments and the execution of number formatting implemented by a user-provided locale. As such, it would not be strictly correct. It would require the language specification to relax the required order of evaluation rules for components of an expression.

We are now taking language feature discussion in other repositories:

Features that are under active design or development, or which are "championed" by someone on the language design team, have already been moved either as issues or as checked-in design documents. For example, the proposal in this repo "Proposal: Partial interface implementation a.k.a. Traits" (issue 16139 and a few other issues that request the same thing) are now tracked by the language team at issue 52 in https://github.com/dotnet/csharplang/issues, and there is a draft spec at https://github.com/dotnet/csharplang/blob/master/proposals/default-interface-methods.md and further discussion at issue 288 in https://github.com/dotnet/csharplang/issues. Prototyping of the compiler portion of language features is still tracked here; see, for example, https://github.com/dotnet/roslyn/tree/features/DefaultInterfaceImplementation and issue 17952.

In order to facilitate that transition, we have started closing language design discussions from the roslyn repo with a note briefly explaining why. When we are aware of an existing discussion for the feature already in the new repo, we are adding a link to that. But we're not adding new issues to the new repos for existing discussions in this repo that the language design team does not currently envision taking on. Our intent is to eventually close the language design issues in the Roslyn repo and encourage discussion in one of the new repos instead.

Our intent is not to shut down discussion on language design - you can still continue discussion on the closed issues if you want - but rather we would like to encourage people to move discussion to where we are more likely to be paying attention (the new repo), or to abandon discussions that are no longer of interest to you.

If you happen to notice that one of the closed issues has a relevant issue in the new repo, and we have not added a link to the new issue, we would appreciate you providing a link from the old to the new discussion. That way people who are still interested in the discussion can start paying attention to the new issue.

Also, we'd welcome any ideas you might have on how we could better manage the transition. Comments and discussion about closing and/or moving issues should be directed to https://github.com/dotnet/roslyn/issues/18002. Comments and discussion about this issue can take place here or on an issue in the relevant repo.


Since this proposal changes the observable order of evaluation, it would require a language specification change. As such, I am moving it to the csharplang repo. @stephentoub is a member of the LDM, so he can promote it to a championed proposal if he wishes to proceed further.

Issue moved to dotnet/csharplang #939 via ZenHub

Was this page helpful?
0 / 5 - 0 ratings