Skip to content

Commit 835d62a

Browse files
Migrated to pure dictionary lookup.
Improved tests.
1 parent 9e05ed7 commit 835d62a

3 files changed

Lines changed: 21 additions & 62 deletions

File tree

Benchmarks/EnumParseTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public class EnumParseTests
2222
static readonly string[] ValidValues = new string[] { Greek.Alpha.ToString(), Greek.Epsilon.ToString(), Greek.Phi.ToString() };
2323
static readonly string[] InvalidValues = new string[] { "Apple", "Orange", "Pineapple" };
2424

25-
[Benchmark(Baseline = true)]
25+
//[Benchmark(Baseline = true)]
2626
public Greek EnumParse()
2727
{
2828
Greek e = Greek.None;
@@ -71,7 +71,7 @@ public Greek EnumValueParse()
7171
}
7272

7373

74-
[Benchmark]
74+
//[Benchmark]
7575
public Greek FastEnumParse()
7676
{
7777
Greek e = Greek.None;

Source/EnumValue.cs

Lines changed: 15 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
using System;
2-
using System.Collections.Generic;
32
using System.Collections.Immutable;
43
using System.Diagnostics;
54
using System.Linq;
6-
using System.Linq.Expressions;
7-
using System.Runtime.CompilerServices;
85

96
namespace Open.Text
107
{
@@ -43,72 +40,35 @@ public EnumValue(string value)
4340
/// </summary>
4441
public override string ToString() => Value.ToString();
4542

46-
static readonly Func<string, TEnum> Parser = CreateParseEnumDelegate();
43+
internal static readonly ImmutableDictionary<string, TEnum> Lookup = CreateLookup();
4744

4845
/// <summary>
49-
/// Uses an expression tree switch to get a matching enum value.
46+
/// Uses a case-senstive dictionary lookup to get a matching enum value.
5047
/// </summary>
5148
/// <param name="value">The string represnting the enum to search for.</param>
5249
/// <returns>The enum that represents the string <paramref name="value"/> provided.</returns>
5350
/// <exception cref="ArgumentNullException">value is null</exception>
5451
/// <exception cref="ArgumentException">Requested <paramref name="value"/> was not found.</exception>
5552
public static TEnum Parse(string value)
56-
{
57-
if (value is null)
58-
throw new ArgumentNullException(nameof(value));
59-
60-
try
61-
{
62-
return Parser(value);
63-
}
64-
catch (ArgumentException ex)
65-
{
66-
throw new ArgumentException($"Requested value '{value}' was not found.", nameof(value), ex);
67-
}
68-
}
53+
=> TryParse(value, out var e) ? e : throw new ArgumentException($"Requested value '{value}' was not found.", nameof(value));
6954

7055
/// <summary>
71-
/// Uses an expression tree switch to get a matching enum value.
56+
/// Uses a case-senstive dictionary lookup to get a matching enum value.
7257
/// </summary>
7358
/// <returns>true if the value found; otherwise false.</returns>
7459
/// <exception cref="ArgumentNullException"/>
7560
public static bool TryParse(string value, out TEnum e)
7661
{
77-
try
78-
{
79-
e = Parser(value);
80-
return true;
81-
}
82-
catch (ArgumentException)
83-
{
84-
e = default!;
85-
return false;
86-
}
62+
if (Lookup.TryGetValue(value, out e!)) return true;
63+
e = default!;
64+
return false;
8765
}
8866

89-
// https://stackoverflow.com/questions/26678181/enum-parse-vs-switch-performance
90-
static Func<string, TEnum> CreateParseEnumDelegate()
91-
{
92-
var eValue = Expression.Parameter(typeof(string), "value"); // (string value)
93-
var tEnum = typeof(TEnum);
94-
95-
return
96-
Expression.Lambda<Func<string, TEnum>>(
97-
Expression.Block(tEnum,
98-
Expression.Switch(tEnum, eValue,
99-
Expression.Block(tEnum,
100-
Expression.Throw(Expression.New(typeof(ArgumentException).GetConstructor(Type.EmptyTypes))),
101-
Expression.Default(tEnum)
102-
),
103-
null,
104-
Enum.GetValues(tEnum).Cast<object>().Select(v => Expression.SwitchCase(
105-
Expression.Constant(v),
106-
Expression.Constant(v.ToString())
107-
)).ToArray()
108-
)
109-
), eValue
110-
).Compile();
111-
}
67+
static ImmutableDictionary<string, TEnum> CreateLookup()
68+
=> Enum
69+
.GetValues(typeof(TEnum))
70+
.Cast<TEnum>()
71+
.ToImmutableDictionary(v => v.ToString(), v => v);
11272

11373
/// <summary>
11474
/// Indicates whether this instance matches the enum value of <paramref name="other"/>.
@@ -184,14 +144,12 @@ public EnumValueCaseIgnored(string value)
184144
/// <inheritdoc cref="EnumValue{TEnum}.ToString"/>
185145
public override string ToString() => Value.ToString();
186146

187-
internal static readonly ImmutableDictionary<string, TEnum> CaseInsensitiveLookup
188-
= CreateCaseInsensitiveDictionary();
147+
internal static readonly ImmutableDictionary<string, TEnum> Lookup = CreateLookup();
189148

190149
/// <summary>
191150
/// Uses a case-insenstive dictionary lookup to get a matching enum value.
192151
/// </summary>
193152
/// <inheritdoc cref="EnumValue{TEnum}.Parse(string)" />
194-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
195153
public static TEnum Parse(string value)
196154
=> TryParse(value, out var e) ? e : throw new ArgumentException($"Requested value '{value}' was not found.", nameof(value));
197155

@@ -201,12 +159,12 @@ public static TEnum Parse(string value)
201159
/// <inheritdoc cref="EnumValue{TEnum}.TryParse(string, out TEnum)"/>
202160
public static bool TryParse(string value, out TEnum e)
203161
{
204-
if (CaseInsensitiveLookup.TryGetValue(value, out e!)) return true;
162+
if (Lookup.TryGetValue(value, out e!)) return true;
205163
e = default!;
206164
return false;
207165
}
208166

209-
static ImmutableDictionary<string, TEnum> CreateCaseInsensitiveDictionary()
167+
static ImmutableDictionary<string, TEnum> CreateLookup()
210168
=> Enum
211169
.GetValues(typeof(TEnum))
212170
.Cast<TEnum>()

Tests/EnumValueTests.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ public static void EvalValueParse(string value)
1919
CheckImplicit(value, (Greek)Enum.Parse(typeof(Greek), value));
2020
var lower = value.ToLower();
2121
CheckImplicitCaseIgnored(lower, (Greek)Enum.Parse(typeof(Greek), lower, true));
22-
Assert.True(EnumValueCaseIgnored<Greek>.TryParse(value, out _));
22+
Assert.True(EnumValue.TryParse<Greek>(lower, true, out _));
23+
Assert.False(EnumValue.TryParse<Greek>(lower, out _));
2324
}
2425

2526
[Theory]
@@ -28,10 +29,10 @@ public static void EvalValueParse(string value)
2829
public static void EvalValueParseFail(string value)
2930
{
3031
Assert.Throws<ArgumentException>(() => Enum.Parse(typeof(Greek), value));
31-
Assert.False(EnumValueCaseIgnored<Greek>.TryParse(value, out _));
32+
Assert.False(EnumValue.TryParse<Greek>(value, out _));
3233
Assert.Throws<ArgumentException>(() => _ = new EnumValue<Greek>(value));
3334
Assert.Throws<ArgumentException>(() => _ = new EnumValueCaseIgnored<Greek>(value));
34-
Assert.False(EnumValueCaseIgnored<Greek>.TryParse(value, out _));
35+
Assert.False(EnumValue.TryParse<Greek>(value, true, out _));
3536
}
3637

3738
static void CheckImplicit(EnumValue<Greek> value, Greek expected)

0 commit comments

Comments
 (0)