Skip to content

Commit f8cd072

Browse files
gigi81ardalis
andauthored
#402 Added support for case insensitive enums of strings (#406)
* Implemented SmartEnumComparerAttribute<T> with tests * Aligned to other files formatting * Updated method names * Added usage example --------- Co-authored-by: Steve Smith <[email protected]>
1 parent d3cdf82 commit f8cd072

File tree

4 files changed

+180
-1
lines changed

4 files changed

+180
-1
lines changed

README.md

+23
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
* [Json.NET support](#jsonnet-support)
3030
* [Dapper support](#dapper-support)
3131
* [DapperSmartEnum](#dappersmartenum)
32+
* [Case Insensitive String Enum](#case-insensitive-string-enum)
3233
* [Examples in the Real World](#examples-in-the-real-world)
3334
* [References](#references)
3435

@@ -807,6 +808,28 @@ should have their `DbType` property set to the specified value. Use `DoNotSetDbT
807808
`IgnoreCaseAttribute` (e.g. `[IgnoreCase]`) when inheriting from `DapperSmartEnumByName` to specify
808809
that database values do not need to match the case of a SmartEnum Name.
809810

811+
### Case Insensitive String Enum
812+
813+
When creating enums of strings, the default behaviour of SmartEnum is to compare the strings with a case sensitive comparer.
814+
It is possible to specify a different equality comparer for the enum values, for example a case insensitive one:
815+
816+
```csharp
817+
[SmartEnumStringComparer(StringComparison.InvariantCultureIgnoreCase)]
818+
public class CaseInsensitiveEnum : SmartEnum<CaseInsensitiveEnum, string>
819+
{
820+
protected CaseInsensitiveEnum(string name, string value) : base(name, value) { }
821+
822+
public static CaseInsensitiveEnum One = new CaseInsensitiveEnum("One", "one");
823+
public static CaseInsensitiveEnum Two = new CaseInsensitiveEnum("Two", "two");
824+
}
825+
826+
var e1 = CaseInsensitiveEnum.FromValue("ONE");
827+
var e2 = CaseInsensitiveEnum.FromValue("one");
828+
829+
//e1 is equal to e2
830+
```
831+
832+
810833
## Examples in the Real World
811834

812835
- [Race](https://github.com/pdevito3/PeakLimsApi/blob/main/PeakLims/src/PeakLims/Domain/Races/Race.cs)

src/SmartEnum/SmartEnum.cs

+7-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public abstract class SmartEnum<TEnum, TValue> :
5252
new Lazy<Dictionary<TValue, TEnum>>(() =>
5353
{
5454
// multiple enums with same value are allowed but store only one per value
55-
var dictionary = new Dictionary<TValue, TEnum>();
55+
var dictionary = new Dictionary<TValue, TEnum>(GetValueComparer());
5656
foreach (var item in _enumOptions.Value)
5757
{
5858
if (item._value != null && !dictionary.ContainsKey(item._value))
@@ -72,6 +72,12 @@ private static TEnum[] GetAllOptions()
7272
.ToArray();
7373
}
7474

75+
private static IEqualityComparer<TValue> GetValueComparer()
76+
{
77+
var comparer = typeof(TEnum).GetCustomAttribute<SmartEnumComparerAttribute<TValue>>();
78+
return comparer?.Comparer;
79+
}
80+
7581
/// <summary>
7682
/// Gets a collection containing all the instances of <see cref="SmartEnum{TEnum, TValue}"/>.
7783
/// </summary>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace Ardalis.SmartEnum
5+
{
6+
/// <summary>
7+
/// Base class for an <see cref="Attribute"/> to specify the <see cref="IEqualityComparer{T}"/> for a SmartEnum.
8+
/// </summary>
9+
/// <typeparam name="T"></typeparam>
10+
[AttributeUsage(AttributeTargets.Class)]
11+
public class SmartEnumComparerAttribute<T> : Attribute
12+
{
13+
private readonly IEqualityComparer<T> _comparer;
14+
15+
protected SmartEnumComparerAttribute(IEqualityComparer<T> comparer)
16+
{
17+
_comparer = comparer;
18+
}
19+
20+
public IEqualityComparer<T> Comparer => _comparer;
21+
}
22+
23+
/// <summary>
24+
/// Attribute to apply to <see cref="SmartEnum{TEnum, string}"/> of type <see cref="string"/> to specify how to compare
25+
/// the enum values
26+
/// </summary>
27+
public class SmartEnumStringComparerAttribute : SmartEnumComparerAttribute<string>
28+
{
29+
public SmartEnumStringComparerAttribute(StringComparison comparison)
30+
: base(GetComparer(comparison))
31+
{
32+
}
33+
34+
private static IEqualityComparer<string> GetComparer(StringComparison comparison)
35+
{
36+
switch (comparison)
37+
{
38+
case StringComparison.Ordinal:
39+
return StringComparer.Ordinal;
40+
case StringComparison.OrdinalIgnoreCase:
41+
return StringComparer.OrdinalIgnoreCase;
42+
case StringComparison.CurrentCulture:
43+
return StringComparer.CurrentCulture;
44+
case StringComparison.CurrentCultureIgnoreCase:
45+
return StringComparer.CurrentCultureIgnoreCase;
46+
case StringComparison.InvariantCulture:
47+
return StringComparer.InvariantCulture;
48+
case StringComparison.InvariantCultureIgnoreCase:
49+
return StringComparer.InvariantCultureIgnoreCase;
50+
}
51+
52+
throw new ArgumentException($"StringComparison {comparison} is not supported", nameof(comparison));
53+
}
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
namespace Ardalis.SmartEnum.UnitTests
2+
{
3+
using FluentAssertions;
4+
using System;
5+
using Xunit;
6+
7+
public class SmartEnumComparerAttribute
8+
{
9+
public class VanillaStringEnum : SmartEnum<VanillaStringEnum, string>
10+
{
11+
protected VanillaStringEnum(string name, string value) : base(name, value) { }
12+
13+
public static VanillaStringEnum One = new VanillaStringEnum("One", "one");
14+
public static VanillaStringEnum Two = new VanillaStringEnum("Two", "two");
15+
}
16+
17+
[SmartEnumStringComparer(StringComparison.InvariantCultureIgnoreCase)]
18+
public class CaseInsensitiveEnum : SmartEnum<CaseInsensitiveEnum, string>
19+
{
20+
protected CaseInsensitiveEnum(string name, string value) : base(name, value) { }
21+
22+
public static CaseInsensitiveEnum One = new CaseInsensitiveEnum("One", "one");
23+
public static CaseInsensitiveEnum Two = new CaseInsensitiveEnum("Two", "two");
24+
}
25+
26+
[SmartEnumStringComparer(StringComparison.InvariantCulture)]
27+
public class CaseSensitiveEnum : SmartEnum<CaseSensitiveEnum, string>
28+
{
29+
protected CaseSensitiveEnum(string name, string value) : base(name, value) { }
30+
31+
public static CaseSensitiveEnum One = new CaseSensitiveEnum("One", "one");
32+
public static CaseSensitiveEnum Two = new CaseSensitiveEnum("Two", "two");
33+
}
34+
35+
[Fact]
36+
public void VanillaStringEnum_FromValue_WhenStringDoesNotMatchCase_Throws()
37+
{
38+
//act
39+
Assert.Throws<SmartEnumNotFoundException>(() =>
40+
{
41+
var actual = VanillaStringEnum.FromValue("ONE");
42+
});
43+
}
44+
45+
[Fact]
46+
public void CaseInsensitiveEnum_FromValue_WhenStringDoesNotMatchCase_ReturnsItem()
47+
{
48+
//act
49+
var actual = CaseInsensitiveEnum.FromValue("ONE");
50+
51+
//assert
52+
actual.Should().Be(CaseInsensitiveEnum.One);
53+
}
54+
55+
[Fact]
56+
public void CaseSensitiveEnum_FromValue_WhenStringDoesNotMatchCase_Throws()
57+
{
58+
//act
59+
Assert.Throws<SmartEnumNotFoundException>(() =>
60+
{
61+
var actual = CaseSensitiveEnum.FromValue("ONE");
62+
});
63+
}
64+
65+
[Fact]
66+
public void VanillaStringEnum_FromValue_WhenStringMatchesCase_ReturnsItem()
67+
{
68+
//act
69+
var actual = VanillaStringEnum.FromValue("one");
70+
71+
//assert
72+
actual.Should().Be(VanillaStringEnum.One);
73+
}
74+
75+
[Fact]
76+
public void CaseInsensitiveEnum_FromValue_WhenStringMatchesCase_ReturnsItem()
77+
{
78+
//act
79+
var actual = CaseInsensitiveEnum.FromValue("one");
80+
81+
//assert
82+
actual.Should().Be(CaseInsensitiveEnum.One);
83+
}
84+
85+
[Fact]
86+
public void CaseSensitiveEnum_FromValue_WhenStringMatchesCase_ReturnsItem()
87+
{
88+
//act
89+
var actual = CaseSensitiveEnum.FromValue("one");
90+
91+
//assert
92+
actual.Should().Be(CaseSensitiveEnum.One);
93+
}
94+
}
95+
}

0 commit comments

Comments
 (0)