Skip to content

Commit 9f05a46

Browse files
Updated lookup method.
1 parent 5fc279c commit 9f05a46

2 files changed

Lines changed: 208 additions & 12 deletions

File tree

Benchmarks/EnumParseTests.cs

Lines changed: 168 additions & 4 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;
@@ -92,6 +92,12 @@ private static bool TryParseBySwitch(string value, bool ignoreCase, out Greek e)
9292
return true;
9393
}
9494

95+
if (value.Equals(nameof(Greek.Phi), StringComparison.OrdinalIgnoreCase))
96+
{
97+
e = Greek.Phi;
98+
return true;
99+
}
100+
95101
if (value.Equals(nameof(Greek.Theta), StringComparison.OrdinalIgnoreCase))
96102
{
97103
e = Greek.Theta;
@@ -131,6 +137,9 @@ private static bool TryParseBySwitch(string value, bool ignoreCase, out Greek e)
131137
case nameof(Greek.Omega):
132138
e = Greek.Omega;
133139
return true;
140+
case nameof(Greek.Phi):
141+
e = Greek.Phi;
142+
return true;
134143
case nameof(Greek.Theta):
135144
e = Greek.Theta;
136145
return true;
@@ -143,8 +152,139 @@ private static bool TryParseBySwitch(string value, bool ignoreCase, out Greek e)
143152
}
144153
}
145154

155+
private static bool TryParseByLengthSwitch(string value, bool ignoreCase, out Greek e)
156+
{
157+
var len = value.Length;
158+
switch(len)
159+
{
160+
case 3:
161+
if (value.Equals(nameof(Greek.Phi), ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
162+
{
163+
e = Greek.Phi;
164+
return true;
165+
}
166+
e = default!;
167+
return false;
168+
169+
case 4:
170+
if (ignoreCase)
171+
{
172+
if (value.Equals(nameof(Greek.Beta), StringComparison.OrdinalIgnoreCase))
173+
{
174+
e = Greek.Beta;
175+
return true;
176+
}
177+
178+
if (value.Equals(nameof(Greek.None), StringComparison.OrdinalIgnoreCase))
179+
{
180+
e = Greek.None;
181+
return true;
182+
}
183+
184+
e = default!;
185+
return false;
186+
}
187+
188+
switch (value)
189+
{
190+
case nameof(Greek.Beta):
191+
e = Greek.Beta;
192+
return true;
193+
case nameof(Greek.None):
194+
e = Greek.None;
195+
return true;
196+
default:
197+
e = default!;
198+
return false;
199+
}
200+
201+
case 5:
202+
203+
if (ignoreCase)
204+
{
205+
if (value.Equals(nameof(Greek.Alpha), StringComparison.OrdinalIgnoreCase))
206+
{
207+
e = Greek.Alpha;
208+
return true;
209+
}
210+
211+
if (value.Equals(nameof(Greek.Cappa), StringComparison.OrdinalIgnoreCase))
212+
{
213+
e = Greek.Cappa;
214+
return true;
215+
}
216+
217+
if (value.Equals(nameof(Greek.Delta), StringComparison.OrdinalIgnoreCase))
218+
{
219+
e = Greek.Delta;
220+
return true;
221+
}
222+
223+
if (value.Equals(nameof(Greek.Gamma), StringComparison.OrdinalIgnoreCase))
224+
{
225+
e = Greek.Gamma;
226+
return true;
227+
}
228+
229+
if (value.Equals(nameof(Greek.Omega), StringComparison.OrdinalIgnoreCase))
230+
{
231+
e = Greek.Omega;
232+
return true;
233+
}
234+
235+
if (value.Equals(nameof(Greek.Theta), StringComparison.OrdinalIgnoreCase))
236+
{
237+
e = Greek.Theta;
238+
return true;
239+
}
240+
241+
e = default!;
242+
return false;
243+
}
244+
245+
switch (value)
246+
{
247+
case nameof(Greek.Alpha):
248+
e = Greek.Alpha;
249+
return true;
250+
case nameof(Greek.Cappa):
251+
e = Greek.Cappa;
252+
return true;
253+
case nameof(Greek.Delta):
254+
e = Greek.Delta;
255+
return true;
256+
case nameof(Greek.Gamma):
257+
e = Greek.Gamma;
258+
return true;
259+
case nameof(Greek.Omega):
260+
e = Greek.Omega;
261+
return true;
262+
case nameof(Greek.Theta):
263+
e = Greek.Theta;
264+
return true;
265+
default:
266+
e = default!;
267+
return false;
268+
}
269+
270+
case 7:
271+
if (value.Equals(nameof(Greek.Epsilon), ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
272+
{
273+
e = Greek.Epsilon;
274+
return true;
275+
}
276+
e = default!;
277+
return false;
278+
}
279+
280+
e = default!;
281+
return false;
282+
283+
}
284+
285+
146286
[Benchmark]
147-
public Greek Switch()
287+
public Greek CompiledSwitch()
148288
{
149289
Greek e = Greek.None;
150290
if (UseValid)
@@ -167,7 +307,31 @@ public Greek Switch()
167307
return e;
168308
}
169309

170-
//[Benchmark]
310+
[Benchmark]
311+
public Greek CompiledSwitchWithLengths()
312+
{
313+
Greek e = Greek.None;
314+
if (UseValid)
315+
{
316+
foreach (string s in ValidValues)
317+
{
318+
if (!TryParseByLengthSwitch(s, IgnoreCase, out e))
319+
throw new Exception("Invalid.");
320+
}
321+
}
322+
else
323+
{
324+
foreach (string s in InvalidValues)
325+
{
326+
if (TryParseByLengthSwitch(s, IgnoreCase, out e))
327+
throw new Exception("Valid.");
328+
}
329+
330+
}
331+
return e;
332+
}
333+
334+
[Benchmark]
171335
public Greek EnumValueParse()
172336
{
173337
Greek e = Greek.None;
@@ -192,7 +356,7 @@ public Greek EnumValueParse()
192356
}
193357

194358

195-
//[Benchmark]
359+
[Benchmark]
196360
public Greek FastEnumParse()
197361
{
198362
Greek e = Greek.None;

Source/EnumValue.cs

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Collections.Immutable;
34
using System.Diagnostics;
45
using System.Linq;
@@ -40,7 +41,7 @@ public EnumValue(string value)
4041
/// </summary>
4142
public override string ToString() => Value.ToString();
4243

43-
internal static readonly ImmutableDictionary<string, TEnum> Lookup = CreateLookup();
44+
static readonly (string Name, TEnum Value)[]?[] Lookup = CreateLookup();
4445

4546
/// <summary>
4647
/// Uses a case-senstive dictionary lookup to get a matching enum value.
@@ -57,18 +58,49 @@ public static TEnum Parse(string value)
5758
/// </summary>
5859
/// <returns>true if the value found; otherwise false.</returns>
5960
/// <exception cref="ArgumentNullException"/>
60-
public static bool TryParse(string value, out TEnum e)
61+
public static bool TryParse(string? value, out TEnum e)
6162
{
62-
if (Lookup.TryGetValue(value, out e!)) return true;
63+
if (value is null) goto notFound;
64+
var len = value.Length;
65+
if (len > Lookup.Length) goto notFound;
66+
var r = Lookup[len];
67+
if (r is null) goto notFound;
68+
69+
foreach(var item in r)
70+
{
71+
if (item.Name != value) continue;
72+
e = item.Value;
73+
return true;
74+
}
75+
76+
notFound:
6377
e = default!;
6478
return false;
6579
}
6680

67-
static ImmutableDictionary<string, TEnum> CreateLookup()
68-
=> Enum
81+
static (string Name, TEnum Value)[]?[] CreateLookup()
82+
{
83+
var longest = 0;
84+
var d = new Dictionary<int, List<(string Name, TEnum Value)>>();
85+
var values = Enum
6986
.GetValues(typeof(TEnum))
70-
.Cast<TEnum>()
71-
.ToImmutableDictionary(v => v.ToString(), v => v);
87+
.Cast<TEnum>();
88+
89+
foreach (var e in values)
90+
{
91+
var n = e.ToString();
92+
var len = n.Length;
93+
if (len > longest) longest = len;
94+
if (!d.TryGetValue(len, out var v)) d.Add(len, v = new());
95+
v.Add((n, e));
96+
}
97+
98+
var result = new (string Name, TEnum Value)[longest][];
99+
foreach (var i in d.Keys)
100+
result[i] = d[i].ToArray();
101+
102+
return result;
103+
}
72104

73105
/// <summary>
74106
/// Indicates whether this instance matches the enum value of <paramref name="other"/>.
@@ -157,7 +189,7 @@ public static TEnum Parse(string value)
157189
/// Uses a case-insenstive dictionary lookup to get a matching enum value.
158190
/// </summary>
159191
/// <inheritdoc cref="EnumValue{TEnum}.TryParse(string, out TEnum)"/>
160-
public static bool TryParse(string value, out TEnum e)
192+
public static bool TryParse(string? value, out TEnum e)
161193
{
162194
if (Lookup.TryGetValue(value, out e!)) return true;
163195
e = default!;

0 commit comments

Comments
 (0)