Add a rule to convert Enum.ToString() to nameof

Description

I think a rule would be useful to replace usages of ToString() on statically known values with equivalent nameof calls.

This means turning this:

Console.WriteLine( SomeEnum.SomeValue.ToString() );

into this:

Console.WriteLine( nameof(SomeEnum.SomeValue) );

Reasoning

nameof replaces the name of the symbol at compile time, while Enum.ToString() is evaluated at runtime. Applying this rule should thus provide some (slight) performance gain.

Type

This is a performance issue and not an error, so I think code smell.

1 Like

I like the reasoning. However, how often does this occur? In most cases, SomeEnum.SomeValue appear in an interpolated string, or in string.Format(). I would argue that $"{nameof(SomeEnum.Value)} etc." is harder to read then $"{SomeEnum.Value} etc.", and in terms of speed, I doubt that you gain anything.

On top of that, there are rules that report on unnecessarily .ToString() calls. So instead of a required SomeEnum.SomeValue.ToString() it might both lead to easier to read code, and gives a small peformance gain, but where .ToString() is not required, I would prefer my colleagues to write SomeEnum.SomeValue and not nameof(SomeEnum.SomeValue).

Just my 2cts.

Hi @m-gallesio, thanks for your suggestion. Did you try already to do a benchmark to see what would be the possible gain by removing these kind of calls?

This would help us evaluate how useful this rule would be for general user.

I apologize for not being used to benchmarking. As a naïve test I did some runs of this code through this REPL (should be Mono on some Linux):

using System;

enum MyEnum {
    A
}

class Program {

    const int iterations = 1_000_000;

    static void Main(string[] args) {
        DateTime start;

        start = DateTime.UtcNow;        
        for (int i = 0; i < iterations; i++) {
            string s = $"A:{MyEnum.A.ToString()}";
        }
        Console.WriteLine((DateTime.UtcNow - start).Milliseconds);

        start = DateTime.UtcNow;  
        for (int i = 0; i < iterations; i++) {
            string s = $"A{nameof(MyEnum.A)}";
        }
        Console.WriteLine((DateTime.UtcNow - start).Milliseconds);
    }

}

Most of the times I have observed the second loop to be 20-40% faster than the first, but also several times in which the first happens to be faster. I guess this warrants further investigation (and again, better benchmarking code in more varied environments) and some more concrete use cases.

Using Benchmark.NET, this are my results:

Method Mean Error StdDev
ToString() 39.4018 ns 0.4002 ns 0.3547 ns
nameof() 0.6920 ns 0.0273 ns 0.0228 ns

So it way faster, but is also rather insignificant.

The benchmark I used:

public class NameOfEnum
{
    enum Enumeration { SomeValue };

    [Benchmark(Description = "ToString()")]
    public string WithToString() => $"Prefix:{Enumeration.SomeValue}";

    [Benchmark(Description = "nameof()")]
    public string WithNameOf() => $"Prefix:{nameof(Enumeration.SomeValue)}";
}

Thanks both @m-gallesio and @Corniel for sharing your results. Considering the findings I think using one or a another it’s mostly a mater of preference. Since we cannot add this kind of rules to the default profile (SonarWay) the priority for adding the rule is very low.

3 Likes

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.