diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/EnumInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/EnumInfo.cs index 71f1c69ffe5018..2b6a284ee9a4c8 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/EnumInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/EnumInfo.cs @@ -8,6 +8,7 @@ using System.Runtime; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; namespace System.Reflection { @@ -41,6 +42,39 @@ public EnumInfo(Type underlyingType, TStorage[] values, string[] names, bool isF internal TStorage[] Values { get; } internal bool ValuesAreSequentialFromZero { get; } + // Lazily-built, cached case-insensitive name-to-value lookup used to accelerate + // case-insensitive parsing. Null until the first case-insensitive parse of this enum type. + private Dictionary? _namesToValuesIgnoreCase; + + /// + /// Gets a case-insensitive name-to-value lookup, building and caching it on first use. + /// + public Dictionary GetNamesToValuesIgnoreCase() + { + return Volatile.Read(ref _namesToValuesIgnoreCase) ?? Initialize(); + + Dictionary Initialize() + { + string[] names = Names; + TStorage[] values = Values; + var lookup = new Dictionary(names.Length, StringComparer.OrdinalIgnoreCase); + + // Insert in Names order (which is sorted by value). For names that differ only by case, + // TryAdd keeps the first (lowest-value) entry, matching the original linear scan's + // "first match wins" behavior. + for (int i = 0; i < names.Length; i++) + { + lookup.TryAdd(names[i], values[i]); + } + + // Publish atomically. If another thread raced and already published a lookup, + // reuse theirs so every caller observes a single cached instance. The fast-path + // Volatile.Read pairs an acquire load with this release publish so readers always + // observe a fully-initialized dictionary. + return Interlocked.CompareExchange(ref _namesToValuesIgnoreCase, lookup, null) ?? lookup; + } + } + /// Create a copy of . public unsafe TResult[] CloneValues() where TResult : struct { diff --git a/src/libraries/System.Private.CoreLib/src/System/Enum.EnumInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Enum.EnumInfo.cs index 07a205ce3016bd..6633a2db6549b5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Enum.EnumInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Enum.EnumInfo.cs @@ -1,9 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using System.Diagnostics; using System.Numerics; using System.Runtime.InteropServices; +using System.Threading; namespace System { @@ -17,6 +19,10 @@ internal sealed partial class EnumInfo public readonly TStorage[] Values; public readonly string[] Names; + // Lazily-built, cached case-insensitive name-to-value lookup used to accelerate + // case-insensitive parsing. Null until the first case-insensitive parse of this enum type. + private Dictionary? _namesToValuesIgnoreCase; + // Each entry contains a list of sorted pair of enum field names and values, sorted by values public EnumInfo(bool hasFlagsAttribute, TStorage[] values, string[] names) { @@ -32,6 +38,35 @@ public EnumInfo(bool hasFlagsAttribute, TStorage[] values, string[] names) ValuesAreSequentialFromZero = AreSequentialFromZero(values); } + /// + /// Gets a case-insensitive name-to-value lookup, building and caching it on first use. + /// + public Dictionary GetNamesToValuesIgnoreCase() + { + return Volatile.Read(ref _namesToValuesIgnoreCase) ?? Initialize(); + + Dictionary Initialize() + { + string[] names = Names; + TStorage[] values = Values; + var lookup = new Dictionary(names.Length, StringComparer.OrdinalIgnoreCase); + + // Insert in Names order (which is sorted by value). For names that differ only by case, + // TryAdd keeps the first (lowest-value) entry, matching the original linear scan's + // "first match wins" behavior. + for (int i = 0; i < names.Length; i++) + { + lookup.TryAdd(names[i], values[i]); + } + + // Publish atomically. If another thread raced and already published a lookup, + // reuse theirs so every caller observes a single cached instance. The fast-path + // Volatile.Read pairs an acquire load with this release publish so readers always + // observe a fully-initialized dictionary. + return Interlocked.CompareExchange(ref _namesToValuesIgnoreCase, lookup, null) ?? lookup; + } + } + /// Create a copy of . public unsafe TResult[] CloneValues() where TResult : struct { diff --git a/src/libraries/System.Private.CoreLib/src/System/Enum.cs b/src/libraries/System.Private.CoreLib/src/System/Enum.cs index e21515e05d3faf..6b0bdc323cec47 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Enum.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Enum.cs @@ -6,6 +6,7 @@ #define RARE_ENUMS #endif +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -1052,6 +1053,12 @@ private static bool TryParseByName(RuntimeType enumType, ReadOnlySpan< bool parsed = true; TStorage localResult = default; + + // For case-insensitive parsing, use a cached name->value lookup and match spans without allocating. + Dictionary.AlternateLookup> ignoreCaseLookup = ignoreCase ? + enumInfo.GetNamesToValuesIgnoreCase().GetAlternateLookup>() : + default; + while (value.Length > 0) { // Find the next separator. @@ -1080,14 +1087,10 @@ private static bool TryParseByName(RuntimeType enumType, ReadOnlySpan< bool success = false; if (ignoreCase) { - for (int i = 0; i < enumNames.Length; i++) + if (ignoreCaseLookup.TryGetValue(subvalue, out TStorage matchedValue)) { - if (subvalue.EqualsOrdinalIgnoreCase(enumNames[i])) - { - localResult |= enumValues[i]; - success = true; - break; - } + localResult |= matchedValue; + success = true; } } else