dotnet/roslyn

[suggestion] Compiler should call ToString on non-string types used with string.operator+

Open

#10,966 opened on Apr 29, 2016

View on GitHub
 (16 comments) (4 reactions) (0 assignees)C# (4,257 forks)batch import
Area-CompilersCode Gen QualityFeature - TuplesFeature Requesthelp wanted

Repository metrics

Stars
 (20,414 stars)
PR merge metrics
 (Avg merge 6d 17h) (256 merged PRs in 30d)

Description

Background

There's a lot of code out there that uses string concatenation to append arbitrary objects to strings, like so:

public class MutablePerson
{
    public string Name { get; set; }
    public int Age { get; set; }

    public override void ToString()
    {
        return this.Name + " is " + this.Age + " years old.";
    }
}

At compile-time, Roslyn tranforms calls to string.operator+ to string.Concat to avoid allocating intermediary strings, so what we end up with is this:

public override void ToString()
{
    return string.Concat(this.Name, " is ", this.Age, " years old.");
}

Unfortunately, string.Concat only accepts object parameters, meaning that Age (which is an int) is implicitly boxed when it is passed into the function. Of course, boxing is no good for performance, so if the developer wants to avoid this, he/she has to explicitly call ToString on the variable, e.g.

return this.Name + " is " + this.Age.ToString() + " years old.";

and the compiler will do the right thing and omit the box instruction.

An example of where this really became a pain was dotnet/corefx#8025, where a bunch of ToString calls had to be added to avoid boxing on each item.

Proposal

Instead of calling the string.Concat overloads that accept objects, the compiler should exclusively generate code targeting the ones accepting a string. For value types, the code generated would coerce the variable into a string via ToString:

"foo" + someStruct + "bar"; // what was written
string.Concat("foo", someStruct.ToString(), "bar"); // what was emitted

Existing strings could simply be concatenated directly, without a ToString call (same behavior as today):

"quux" + someString; // what was written
string.Concat("quux", someString); // what was emitted

And for other reference types, a ToString would be emitted with an extra null check:

"baz" + someClass; // what was written
string.Concat("baz", someClass?.ToString()); // what was emitted

Contributor guide