The following code causes boxing of the 'salary' value type.
double salary = 1200;
string text = $"Salary {salary:C2}";
Console.WriteLine(text);
Boxing is evil because it is “slow”. It creates a reference type (the boxed value) which the garbage collector has to clean up (which is also “slow”). You can easily do this by adding a ToString().
double salary = 1200;
string text = $"Salary {salary.ToString("C2")}";
Console.WriteLine(text);
I think this should be mentioned in the docs.
⚠ Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.
Thanks for pointing this out @sonnemaf
You are correct, this will box. It should be mentioned, and developers can weigh the convenience against performance based on good engineering measurements. I've added this to our backlog to update when we next update this article.
I've also added the "up for grabs" label, in case you or other community members want to submit a PR to address this issue.
@sonnemaf But won't explicit ToString() create an extra intermediate string? So we have a boxed double vs extra string, no gain at all.
Note that which approach allocates less changes depending on the framework you're using. On newer versions of .Net Core, if you box the double, the framework can produce the final string without allocating an intermediate string for the number. This means if you add a call to ToString(), you'll end up allocating more.
To see this, consider the following BenchmarkDotNet code:
```c#
public class Benchmark
{
[Benchmark]
public string Box()
{
double salary = 1200;
return $"Salary {salary:C2}";
}
[Benchmark]
public new string ToString()
{
double salary = 1200;
return $"Salary {salary.ToString("C2")}";
}
}
```
It produces the following results:
| Method | Toolchain | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|--------- |-------------- |---------:|---------:|--------:|-------:|------:|------:|----------:|
| Box | .NET Core 2.0 | 739.2 ns | 14.95 ns | 0.82 ns | 0.0525 | - | - | 168 B |
| ToString | .NET Core 2.0 | 552.9 ns | 43.59 ns | 2.39 ns | 0.0353 | - | - | 112 B |
| Box | .NET Core 3.1 | 638.9 ns | 93.95 ns | 5.15 ns | 0.0277 | - | - | 88 B |
| ToString | .NET Core 3.1 | 504.7 ns | 24.26 ns | 1.33 ns | 0.0353 | - | - | 112 B |
| Box | .NET Framework 4.8 | 875.5 ns | 68.49 ns | 3.75 ns | 0.0858 | - | - | 273 B |
| ToString | .NET Framework 4.8 | 550.2 ns | 97.95 ns | 5.37 ns | 0.0353 | - | - | 112 B |
Notice that on .Net Core 3.1, Box allocates less, while on older frameworks, ToString is better.
@svick I didn't know allocation was so much improved in 3.1. The performance is still at least 25% improved, in all frameworks.
I am closing this without updating this article. You can see our thinking at https://github.com/dotnet/docs/pull/16650#pullrequestreview-341972487.
As the tests show, the performance difference of this change is measured in nano seconds (on that case). Other specific values would create different results. As discussed in the link above, some specific instances would show that the boxing version is faster than the ToString version.
For any low-level performance optimization, our general guidance is to measure carefully, and as closely to your production scenario as possible. Then, under those conditions make small changes and measure again.
We can't responsibly provide guidance on "A is faster than B" very detailed knowledge of the specific scenario.
Most helpful comment
Note that which approach allocates less changes depending on the framework you're using. On newer versions of .Net Core, if you box the
double, the framework can produce the final string without allocating an intermediate string for the number. This means if you add a call toToString(), you'll end up allocating more.To see this, consider the following BenchmarkDotNet code:
```c#
public class Benchmark
{
[Benchmark]
public string Box()
{
double salary = 1200;
return $"Salary {salary:C2}";
}
}
```
It produces the following results:
| Method | Toolchain | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|--------- |-------------- |---------:|---------:|--------:|-------:|------:|------:|----------:|
| Box | .NET Core 2.0 | 739.2 ns | 14.95 ns | 0.82 ns | 0.0525 | - | - | 168 B |
| ToString | .NET Core 2.0 | 552.9 ns | 43.59 ns | 2.39 ns | 0.0353 | - | - | 112 B |
| Box | .NET Core 3.1 | 638.9 ns | 93.95 ns | 5.15 ns | 0.0277 | - | - | 88 B |
| ToString | .NET Core 3.1 | 504.7 ns | 24.26 ns | 1.33 ns | 0.0353 | - | - | 112 B |
| Box | .NET Framework 4.8 | 875.5 ns | 68.49 ns | 3.75 ns | 0.0858 | - | - | 273 B |
| ToString | .NET Framework 4.8 | 550.2 ns | 97.95 ns | 5.37 ns | 0.0353 | - | - | 112 B |
Notice that on .Net Core 3.1,
Boxallocates less, while on older frameworks,ToStringis better.