`nameof(X)` in Interpolated String Handlers are not always lowered to constants
#67,295 opened on Mar 14, 2023
Repository metrics
- Stars
- (20,414 stars)
- PR merge metrics
- (Avg merge 6d 17h) (256 merged PRs in 30d)
Description
Version Used: sharplab.io; main (24 Jan 2023)
Steps to Reproduce:
- Use an interpolated string with a constant string and a
nameofexpression. - Observe the whole string lowered to a constant.
- Change the interpolated string to add a property.
- Observe the
nameofexpression is now lowered toAppendFormatted(notAppendLiteral)
Source:
public int Property => 2;
public void Test() {
Console.WriteLine($"abc {nameof(Test)}");
Console.WriteLine($"abc {nameof(Test)} {Property}");
}
Expected Behavior:
public void Test()
{
Console.WriteLine("abc Test");
DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(9, 1);
defaultInterpolatedStringHandler.AppendLiteral("abc Test "); // <-- merged
defaultInterpolatedStringHandler.AppendFormatted(Property);
Console.WriteLine(defaultInterpolatedStringHandler.ToStringAndClear());
}
Actual Behavior:
public void Test()
{
Console.WriteLine("abc Test");
DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(5, 2);
defaultInterpolatedStringHandler.AppendLiteral("abc ");
defaultInterpolatedStringHandler.AppendFormatted("Test"); // <-- not merged
defaultInterpolatedStringHandler.AppendLiteral(" ");
defaultInterpolatedStringHandler.AppendFormatted(Property);
Console.WriteLine(defaultInterpolatedStringHandler.ToStringAndClear());
}
Note how the nameof expression was combined with the literal string next to it in the first, but not in the second. In addition, the second one lowers the nameof into an AppendFormatted call instead of an AppendLiteral.
Performance
public class InterpolatedBenchmarks
{
#pragma warning disable CA1822
public int Property => 2;
#pragma warning restore CA1822
[Benchmark(Baseline = true)]
public string TestA() =>
"test TestB " + Property;
[Benchmark]
public string TestB() =>
$"test TestB {Property}";
[Benchmark]
public string TestC() =>
$"test {nameof(TestB)} {Property}";
}
Running the above benchmark (with BenchmarkDotNet) on 7.0.323.6910 (x64 RyuJIT AVX2) shows a slight performance decrease for the actual compiler output compared to what I would expect:
| Method | Mean | Error | StdDev | Ratio | RatioSD |
|---|---|---|---|---|---|
| TestA | 14.84 ns | 0.343 ns | 0.321 ns | 1.00 | 0.00 |
| TestB | 36.03 ns | 0.702 ns | 0.656 ns | 2.43 | 0.08 |
| TestC | 46.31 ns | 0.516 ns | 0.458 ns | 3.12 | 0.08 |
In fact, performance is horrible compared to plain concatenation (lowered to string.Concat).