Skip to content

Improve enum parsing in config binder generator #88873

@layomia

Description

@layomia

from @eerhardt in #88067:

Looking at your current generated code:

    public static ConsoleLoggerFormat ParseConsoleLoggerFormat(string value, Func<string?> getPath)
    {
        try
        {
            return (ConsoleLoggerFormat)Enum.Parse(typeof(ConsoleLoggerFormat), value, ignoreCase: true);
        }
        catch (Exception exception)
        {
            throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(ConsoleLoggerFormat)}'.", exception);
        }
    }

There are a couple comments here:

  1. Why do we need a new method for every enum type in the graph? Can't we write 1 general "ParseEnum" method, like this one?
  2. We really should use Enum.Parse<T> when possible. I did a quick benchmark:
using BenchmarkDotNet.Attributes;

namespace Benchmark;

public enum ConsoleLoggerFormat
{
    Default,
    Systemd,
}

[MemoryDiagnoser]
public class EnumParseBenchmark
{
    [Benchmark]
    public ConsoleLoggerFormat ParseConsoleLoggerFormatGeneric() => ParseConsoleLoggerFormatGeneric("Systemd", () => "");
    [Benchmark]
    public ConsoleLoggerFormat ParseConsoleLoggerFormat() => ParseConsoleLoggerFormat("Systemd", () => "");

    public static ConsoleLoggerFormat ParseConsoleLoggerFormatGeneric(string value, Func<string?> getPath)
    {
        try
        {
            return Enum.Parse<ConsoleLoggerFormat>(value, ignoreCase: true);
        }
        catch (Exception exception)
        {
            throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(ConsoleLoggerFormat)}'.", exception);
        }
    }

    public static ConsoleLoggerFormat ParseConsoleLoggerFormat(string value, Func<string?> getPath)
    {
        try
        {
            return (ConsoleLoggerFormat)Enum.Parse(typeof(ConsoleLoggerFormat), value, ignoreCase: true);
        }
        catch (Exception exception)
        {
            throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(ConsoleLoggerFormat)}'.", exception);
        }
    }
}

on my machine I see:

Method Mean Error StdDev Gen 0 Allocated
ParseConsoleLoggerFormatGeneric 27.92 ns 0.855 ns 0.985 ns - -
ParseConsoleLoggerFormat 72.91 ns 0.270 ns 0.226 ns 0.0036 24 B

The generic version is faster and doesn't allocate.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions