Skip to content

Commit 3102a65

Browse files
committed
feat(atc): add Enum<T>.ToReadOnlyDictionary mapping enum to underlying int
- New EnumHelper.ConvertEnumToReadOnlyDictionary<T>() returns IReadOnlyDictionary<TEnum, int> keyed by typed enum value with each member's underlying int as the value, replacing hand-rolled rank / threshold dictionaries at call sites - Reuses ShouldEnumValueBeSkipped so includeDefault, byFlagIncludeBase and byFlagIncludeCombined behave identically to ToDictionary - Uses Convert.ToInt32 with InvariantCulture so non-int-backed enums (byte, short, long) work correctly — a small improvement over the existing (int)box cast in ToDictionary - Add Enum<T>.ToReadOnlyDictionary wrapper next to ToDictionary - Add EnumHelper tests for happy path, includeDefault=false, [Flags] base/combined matrix, and a byte-backed fixture; add Enum<T> theory test exercising the wrapper through DayOfWeek
1 parent f1faa60 commit 3102a65

4 files changed

Lines changed: 150 additions & 0 deletions

File tree

src/Atc/Enum.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,29 @@ public static Dictionary<int, string> ToDictionary(
220220
byFlagIncludeBase,
221221
byFlagIncludeCombined);
222222

223+
/// <summary>
224+
/// Builds a read-only map from each defined enum member to its underlying <see cref="int"/>
225+
/// value. Useful for severity-rank or threshold lookup tables that previously required a
226+
/// hand-rolled <c>Dictionary&lt;TEnum, int&gt;</c> literal.
227+
/// </summary>
228+
/// <param name="includeDefault">If set to <see langword="true"/> the <c>0</c>-valued member is included.</param>
229+
/// <param name="byFlagIncludeBase">For <see cref="FlagsAttribute"/> enums, include the single-bit base values.</param>
230+
/// <param name="byFlagIncludeCombined">For <see cref="FlagsAttribute"/> enums, include the multi-bit combined values.</param>
231+
/// <returns>An <see cref="IReadOnlyDictionary{TKey, TValue}"/> keyed by the typed enum value with its underlying <see cref="int"/> as the value.</returns>
232+
/// <code><![CDATA[IReadOnlyDictionary<DayOfWeek, int> map = Enum<DayOfWeek>.ToReadOnlyDictionary();]]></code>
233+
/// <example><![CDATA[
234+
/// IReadOnlyDictionary<DayOfWeek, int> map = Enum<DayOfWeek>.ToReadOnlyDictionary();
235+
/// Assert.Equal(0, map[DayOfWeek.Sunday]);
236+
/// ]]></example>
237+
public static IReadOnlyDictionary<T, int> ToReadOnlyDictionary(
238+
bool includeDefault = true,
239+
bool byFlagIncludeBase = true,
240+
bool byFlagIncludeCombined = true)
241+
=> EnumHelper.ConvertEnumToReadOnlyDictionary<T>(
242+
includeDefault,
243+
byFlagIncludeBase,
244+
byFlagIncludeCombined);
245+
223246
/// <summary>Converts the enum to dictionary with key as string.</summary>
224247
/// <param name="dropDownFirstItemType">Type of the dropdown first item.</param>
225248
/// <param name="useDescriptionAttribute">true to use the display/description/localizeddescription attribute value if exist, default value is name; false to just use the name value.</param>

src/Atc/Helpers/Enums/EnumHelper.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,47 @@ public static Dictionary<int, string> ConvertEnumToDictionary(
282282
return orderList.ToDictionary(x => x.Key, x => x.Value);
283283
}
284284

285+
/// <summary>
286+
/// Builds a read-only map from each defined enum member to its underlying <see cref="int"/> value.
287+
/// </summary>
288+
/// <typeparam name="T">The enum type.</typeparam>
289+
/// <param name="includeDefault">If set to <see langword="true"/> the <c>0</c>-valued member is included.</param>
290+
/// <param name="byFlagIncludeBase">For <see cref="FlagsAttribute"/> enums, include the single-bit base values.</param>
291+
/// <param name="byFlagIncludeCombined">For <see cref="FlagsAttribute"/> enums, include the multi-bit combined values.</param>
292+
/// <returns>
293+
/// An <see cref="IReadOnlyDictionary{TKey, TValue}"/> keyed by the typed enum value with its
294+
/// underlying <see cref="int"/> as the value. Suitable for severity-rank or threshold lookup
295+
/// tables that previously required a hand-rolled <c>Dictionary&lt;TEnum, int&gt;</c> literal.
296+
/// </returns>
297+
public static IReadOnlyDictionary<T, int> ConvertEnumToReadOnlyDictionary<T>(
298+
bool includeDefault = true,
299+
bool byFlagIncludeBase = true,
300+
bool byFlagIncludeCombined = true)
301+
where T : Enum
302+
{
303+
var enumType = typeof(T);
304+
var hasFlagAttribute = enumType.IsDefined(typeof(FlagsAttribute), inherit: false);
305+
306+
var map = new Dictionary<T, int>();
307+
foreach (var raw in Enum.GetValues(enumType))
308+
{
309+
if (ShouldEnumValueBeSkipped(
310+
raw,
311+
includeDefault,
312+
hasFlagAttribute,
313+
byFlagIncludeBase,
314+
byFlagIncludeCombined))
315+
{
316+
continue;
317+
}
318+
319+
var typed = (T)raw!;
320+
map[typed] = Convert.ToInt32(typed, GlobalizationConstants.EnglishCultureInfo);
321+
}
322+
323+
return map;
324+
}
325+
285326
/// <summary>
286327
/// Converts the enum to dictionary with string key.
287328
/// </summary>

test/Atc.Tests/EnumTests.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,4 +242,21 @@ public void ToKeyValuePairsWithStringKey<T>(
242242
.Should().NotBeNull()
243243
.And.HaveCount(expectedCount);
244244
}
245+
246+
[Theory]
247+
[InlineData(true, 7)]
248+
[InlineData(false, 6)]
249+
public void ToReadOnlyDictionary(
250+
bool includeDefault,
251+
int expectedCount)
252+
{
253+
// Act
254+
var actual = Enum<DayOfWeek>.ToReadOnlyDictionary(includeDefault);
255+
256+
// Assert
257+
actual
258+
.Should().NotBeNull()
259+
.And.HaveCount(expectedCount);
260+
actual[DayOfWeek.Monday].Should().Be(1);
261+
}
245262
}

test/Atc.Tests/Helpers/Enums/EnumHelperTests.cs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,4 +242,73 @@ public void GetIndividualValuesFromEnum_DayOfWeek(
242242
// Assert
243243
Assert.Equal(expected, actual.Count);
244244
}
245+
246+
[Fact]
247+
public void ConvertEnumToReadOnlyDictionary_ContainsAllDefinedMembers_WithUnderlyingIntValues()
248+
{
249+
// Act
250+
var actual = EnumHelper.ConvertEnumToReadOnlyDictionary<DayOfWeek>();
251+
252+
// Assert
253+
actual.Should().HaveCount(7);
254+
actual[DayOfWeek.Sunday].Should().Be(0);
255+
actual[DayOfWeek.Monday].Should().Be(1);
256+
actual[DayOfWeek.Tuesday].Should().Be(2);
257+
actual[DayOfWeek.Wednesday].Should().Be(3);
258+
actual[DayOfWeek.Thursday].Should().Be(4);
259+
actual[DayOfWeek.Friday].Should().Be(5);
260+
actual[DayOfWeek.Saturday].Should().Be(6);
261+
}
262+
263+
[Fact]
264+
public void ConvertEnumToReadOnlyDictionary_ExcludesZeroValue_WhenIncludeDefaultFalse()
265+
{
266+
// Act
267+
var actual = EnumHelper.ConvertEnumToReadOnlyDictionary<DayOfWeek>(includeDefault: false);
268+
269+
// Assert
270+
actual.Should().HaveCount(6);
271+
actual.ContainsKey(DayOfWeek.Sunday).Should().BeFalse();
272+
actual[DayOfWeek.Monday].Should().Be(1);
273+
}
274+
275+
[Theory]
276+
[InlineData(true, true, 20)] // 1 default + 16 single-bit + 3 combined
277+
[InlineData(true, false, 17)] // 1 default + 16 single-bit
278+
[InlineData(false, true, 4)] // 1 default + 3 combined
279+
public void ConvertEnumToReadOnlyDictionary_FlagsEnum_RespectsByFlagIncludeBaseAndCombined(
280+
bool byFlagIncludeBase,
281+
bool byFlagIncludeCombined,
282+
int expectedCount)
283+
{
284+
// Act
285+
var actual = EnumHelper.ConvertEnumToReadOnlyDictionary<CardinalDirectionType>(
286+
includeDefault: true,
287+
byFlagIncludeBase,
288+
byFlagIncludeCombined);
289+
290+
// Assert
291+
actual.Should().HaveCount(expectedCount);
292+
}
293+
294+
[Fact]
295+
public void ConvertEnumToReadOnlyDictionary_NonIntUnderlyingType_StillReturnsCorrectIntValues()
296+
{
297+
// Act
298+
var actual = EnumHelper.ConvertEnumToReadOnlyDictionary<ByteBackedTest>();
299+
300+
// Assert
301+
actual.Should().HaveCount(3);
302+
actual[ByteBackedTest.Zero].Should().Be(0);
303+
actual[ByteBackedTest.One].Should().Be(1);
304+
actual[ByteBackedTest.TwoHundred].Should().Be(200);
305+
}
306+
307+
[SuppressMessage("Minor Code Smell", "S4022:Enumerations should have \"Int32\" storage", Justification = "Test fixture verifying non-int underlying-type support.")]
308+
private enum ByteBackedTest : byte
309+
{
310+
Zero = 0,
311+
One = 1,
312+
TwoHundred = 200,
313+
}
245314
}

0 commit comments

Comments
 (0)