Skip to content

Commit f25c7e1

Browse files
iamcarbonabergs
authored andcommitted
Eliminate allocations in EnumExtensions and make public again
1 parent c7402ce commit f25c7e1

File tree

3 files changed

+91
-52
lines changed

3 files changed

+91
-52
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Reflection;
4+
using System.Runtime.Serialization;
5+
6+
namespace Fido2NetLib
7+
{
8+
public static class EnumNameMapper<TEnum>
9+
where TEnum: struct, Enum
10+
{
11+
private static readonly Dictionary<TEnum, string> valueToNames = GetIdToNameMap();
12+
private static readonly Dictionary<string, TEnum> namesToValues = Invert(valueToNames);
13+
14+
private static Dictionary<string, TEnum> Invert(Dictionary<TEnum, string> map)
15+
{
16+
var result = new Dictionary<string, TEnum>(map.Count, StringComparer.OrdinalIgnoreCase);
17+
18+
foreach (var item in map)
19+
{
20+
result[item.Value] = item.Key;
21+
}
22+
23+
return result;
24+
}
25+
26+
public static bool TryGetValue(string name, bool ignoreCase, out TEnum value)
27+
{
28+
if (namesToValues.TryGetValue(name, out value))
29+
{
30+
if (!ignoreCase && !valueToNames[value].Equals(name, StringComparison.Ordinal))
31+
{
32+
value = default;
33+
34+
return false;
35+
}
36+
else
37+
{
38+
return true;
39+
}
40+
}
41+
else
42+
{
43+
value = default;
44+
45+
return false;
46+
}
47+
}
48+
49+
public static bool TryGetValue(string name, out TEnum value)
50+
{
51+
return namesToValues.TryGetValue(name, out value);
52+
}
53+
54+
public static string GetName(TEnum value)
55+
{
56+
return valueToNames[value];
57+
}
58+
59+
public static IEnumerable<string> GetNames()
60+
{
61+
return namesToValues.Keys;
62+
}
63+
64+
private static Dictionary<TEnum, string> GetIdToNameMap()
65+
{
66+
var dic = new Dictionary<TEnum, string>();
67+
68+
foreach (var field in typeof(TEnum).GetFields(BindingFlags.Public | BindingFlags.Static))
69+
{
70+
var description = field.GetCustomAttribute<EnumMemberAttribute>(false);
71+
72+
var value = (TEnum)field.GetValue(null);
73+
74+
dic[value] = description is not null ? description.Value : value.ToString();
75+
}
76+
77+
return dic;
78+
}
79+
}
80+
}
Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.Reflection;
4-
using System.Runtime.Serialization;
52
using System.Text.Json;
63
using System.Text.Json.Serialization;
74

85
namespace Fido2NetLib
96
{
7+
108
public sealed class FidoEnumConverter<T> : JsonConverter<T>
119
where T: struct, Enum
1210
{
13-
private static readonly Dictionary<T, string> valueToNames = GetIdToNameMap();
14-
private static readonly Dictionary<string, T> namesToValues = Invert(valueToNames);
15-
1611
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
1712
{
1813
string text = reader.GetString();
1914

20-
if (namesToValues.TryGetValue(reader.GetString(), out T value))
15+
if (EnumNameMapper<T>.TryGetValue(reader.GetString(), out T value))
2116
{
2217
return value;
2318
}
@@ -29,35 +24,7 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial
2924

3025
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
3126
{
32-
writer.WriteStringValue(valueToNames[value]);
33-
}
34-
35-
private static Dictionary<string, T> Invert(Dictionary<T, string> map)
36-
{
37-
var result = new Dictionary<string, T>(map.Count, StringComparer.OrdinalIgnoreCase);
38-
39-
foreach (var item in map)
40-
{
41-
result[item.Value] = item.Key;
42-
}
43-
44-
return result;
45-
}
46-
47-
private static Dictionary<T, string> GetIdToNameMap()
48-
{
49-
var dic = new Dictionary<T, string>();
50-
51-
foreach (var field in typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static))
52-
{
53-
var description = field.GetCustomAttribute<EnumMemberAttribute>(false);
54-
55-
var value = (T)field.GetValue(null);
56-
57-
dic[value] = description is not null ? description.Value : value.ToString();
58-
}
59-
60-
return dic;
27+
writer.WriteStringValue(EnumNameMapper<T>.GetName(value));
6128
}
6229
}
6330
}
Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
using System;
2-
using System.Linq;
3-
using System.Reflection;
4-
using System.Runtime.Serialization;
52

63
namespace Fido2NetLib
74
{
8-
internal static class EnumExtensions
5+
public static class EnumExtensions
96
{
107
/// <summary>
118
/// Gets the enum value from EnumMemberAttribute's value.
@@ -14,22 +11,20 @@ internal static class EnumExtensions
1411
/// <param name="value">The EnumMemberAttribute's value.</param>
1512
/// <param name="ignoreCase">ignores the case when comparing values.</param>
1613
/// <returns>TEnum.</returns>
17-
/// <exception cref="System.ArgumentException">No XmlEnumAttribute code exists for type " + typeof(TEnum).ToString() + " corresponding to value of " + value</exception>
14+
/// <exception cref="ArgumentException">No XmlEnumAttribute code exists for type " + typeof(TEnum).ToString() + " corresponding to value of " + value</exception>
1815
public static TEnum ToEnum<TEnum>(this string value, bool ignoreCase = true) where TEnum : struct, Enum
1916
{
2017
// Try to parse it normally on the first try
2118
if (Enum.TryParse<TEnum>(value, ignoreCase, out var result))
2219
return result;
2320

2421
// Try with value from EnumMemberAttribute
25-
var values = Enum.GetValues(typeof(TEnum)).OfType<TEnum>().ToArray();
26-
foreach (var val in values)
22+
if (EnumNameMapper<TEnum>.TryGetValue(value, ignoreCase, out result))
2723
{
28-
if (ToEnumMemberValue(val)!.Equals(value, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
29-
return val;
24+
return result;
3025
}
3126

32-
throw new ArgumentException($"Value '{value}' is not a valid enum name of '{typeof(TEnum)}' ({nameof(ignoreCase)}={ignoreCase}). Valid values are: {string.Join(", ", values.Select(v => v.ToEnumMemberValue()))}.");
27+
throw new ArgumentException($"Value '{value}' is not a valid enum name of '{typeof(TEnum)}' ({nameof(ignoreCase)}={ignoreCase}). Valid values are: {string.Join(", ", EnumNameMapper<TEnum>.GetNames())}.");
3328
}
3429

3530
/// <summary>
@@ -38,13 +33,10 @@ public static TEnum ToEnum<TEnum>(this string value, bool ignoreCase = true) whe
3833
/// <typeparam name="TEnum">The type of enum.</typeparam>
3934
/// <param name="value">The enum's value.</param>
4035
/// <returns>string.</returns>
41-
public static string? ToEnumMemberValue<TEnum>(this TEnum value) where TEnum : struct, Enum
36+
public static string ToEnumMemberValue<TEnum>(this TEnum value) where TEnum : struct, Enum
4237
{
43-
return typeof(TEnum).GetTypeInfo()
44-
.DeclaredMembers
45-
.SingleOrDefault(x => x.Name == value.ToString())
46-
?.GetCustomAttribute<EnumMemberAttribute>(false)
47-
?.Value;
38+
return EnumNameMapper<TEnum>.GetName(value);
4839
}
40+
4941
}
5042
}

0 commit comments

Comments
 (0)