116 lines
3.5 KiB
Plaintext
116 lines
3.5 KiB
Plaintext
== Why is this an issue?
|
|
|
|
In order to produce a formatted string, both `string.Create` and either `FormattableString.Invariant` or `FormattableString.CurrentCulture` can be used. However, `string.Create` rents array buffers from `ArrayPool<char>` making it more performant, as well as preventing unnecessary allocations and future stress on the Garbage Collector.
|
|
|
|
This applies to .NET versions after .NET 6, when these `string.Create` overloads were introduced.
|
|
|
|
|
|
=== What is the potential impact?
|
|
|
|
We measured a significant improvement both in execution time and memory allocation. For more details see the `Benchmarks` section from the `More info` tab.
|
|
|
|
|
|
== How to fix it
|
|
|
|
Replace calls to `FormattableString.CurrentCulture` or `FormattableString.Invariant` with calls to `string.Create(CultureInfo.CurrentCulture, ...)` or `string.Create(CultureInfo.InvariantCulture, ...)` respectively.
|
|
|
|
|
|
=== Code examples
|
|
|
|
==== Noncompliant code example
|
|
|
|
[source,csharp,diff-id=1,diff-type=noncompliant]
|
|
----
|
|
string Interpolate(string value) =>
|
|
FormattableString.Invariant($"Value: {value}");
|
|
----
|
|
|
|
[source,csharp,diff-id=2,diff-type=noncompliant]
|
|
----
|
|
string Interpolate(string value) =>
|
|
FormattableString.CurrentCulture($"Value: {value}");
|
|
----
|
|
|
|
==== Compliant solution
|
|
|
|
[source,csharp,diff-id=1,diff-type=compliant]
|
|
----
|
|
string Interpolate(string value) =>
|
|
string.Create(CultureInfo.InvariantCulture, $"Value: {value}");
|
|
----
|
|
|
|
[source,csharp,diff-id=2,diff-type=compliant]
|
|
----
|
|
string Interpolate(string value) =>
|
|
string.Create(CultureInfo.CurrentCulture, $"Value: {value}");
|
|
----
|
|
|
|
|
|
== Resources
|
|
|
|
=== Documentation
|
|
|
|
* https://learn.microsoft.com/en-us/dotnet/api/system.string.create?view=net-7.0[string.Create]
|
|
* https://learn.microsoft.com/en-us/dotnet/api/system.formattablestring.invariant[FormattableString.Invariant]
|
|
* https://learn.microsoft.com/en-us/dotnet/api/system.formattablestring.currentculture[FormattableString.CurrentCulture]
|
|
|
|
=== Articles & blog posts
|
|
* https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated#compilation-of-interpolated-strings[Compilation of interpolated strings]
|
|
|
|
=== Benchmarks
|
|
|
|
The results were generated by running the following snippet with https://github.com/dotnet/BenchmarkDotNet[BenchmarkDotNet]:
|
|
|
|
[options="header"]
|
|
|===
|
|
| Method | Runtime | Mean | Standard Deviation | Allocated
|
|
| StringCreate | .NET 7.0 | 152.5 ms | 3.09 ms | 83.92 MB
|
|
| FormattableString | .NET 7.0 | 191.8 ms | 6.92 ms | 198.36 MB
|
|
|===
|
|
|
|
==== Glossary
|
|
|
|
* https://en.wikipedia.org/wiki/Arithmetic_mean[Mean]
|
|
* https://en.wikipedia.org/wiki/Standard_deviation[Standard Deviation]
|
|
* https://en.wikipedia.org/wiki/Memory_management[Allocated]
|
|
|
|
The results were generated by running the following snippet with https://github.com/dotnet/BenchmarkDotNet[BenchmarkDotNet]:
|
|
|
|
[source,csharp]
|
|
----
|
|
int Value = 42;
|
|
DateTime Now = DateTime.UtcNow;
|
|
|
|
[Params(1_000_000)]
|
|
public int N;
|
|
|
|
[Benchmark]
|
|
public void StringCreate()
|
|
{
|
|
for (int i = 0; i < N; i++)
|
|
{
|
|
_ = string.Create(CultureInfo.InvariantCulture, $"{Now}: Value is {Value}");
|
|
}
|
|
}
|
|
|
|
[Benchmark]
|
|
public void FormattableStringInvariant()
|
|
{
|
|
for (int i = 0; i < N; i++)
|
|
{
|
|
_ = FormattableString.Invariant($"{Now}: Value is {Value}");
|
|
}
|
|
}
|
|
----
|
|
|
|
Hardware configuration:
|
|
[source]
|
|
----
|
|
BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.2728/22H2/2022Update)
|
|
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
|
|
.NET SDK=7.0.203
|
|
[Host] : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
|
|
.NET 7.0 : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
|
|
----
|
|
|