Skip to content

Commit 31eb887

Browse files
Added more documentation.
1 parent 2bc30f0 commit 31eb887

6 files changed

Lines changed: 259 additions & 151 deletions

File tree

Source/EnumValue.cs

Lines changed: 225 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -8,172 +8,254 @@
88

99
namespace Open.Text
1010
{
11-
[DebuggerDisplay("{GetDebuggerDisplay()}")]
12-
public struct EnumValue<TEnum>
11+
/// <summary>
12+
/// A case struct representing an enum value that can be implicitly coerced from a string.
13+
/// </summary>
14+
/// <remarks>String parsing or coercion is case sensitve and must be exact.</remarks>
15+
[DebuggerDisplay("{GetDebuggerDisplay()}")]
16+
public struct EnumValue<TEnum>
1317
where TEnum : Enum
1418
{
19+
/// <summary>
20+
/// Constructs an EnumValue&lt;<typeparamref name="TEnum"/>&gt; using the provided enum value.
21+
/// </summary>
1522
public EnumValue(TEnum value)
1623
{
1724
Value = value;
1825
}
1926

20-
public EnumValue(string value)
27+
/// <summary>
28+
/// Parses the string value to construct an EnumValue&lt;<typeparamref name="TEnum"/>&gt; instance.
29+
/// </summary>
30+
/// <exception cref="ArgumentNullException">value is null.</exception>
31+
public EnumValue(string value)
32+
{
33+
Value = Parse(value);
34+
}
35+
36+
/// <summary>
37+
/// The actual enum value.
38+
/// </summary>
39+
public TEnum Value { get; }
40+
41+
/// <summary>
42+
/// Returns the string representation of the enum value.
43+
/// </summary>
44+
public override string ToString() => Value.ToString();
45+
46+
static readonly Func<string, TEnum> Parser = CreateParseEnumDelegate();
47+
48+
/// <summary>
49+
/// Uses a case-insenstive dictionary lookup to get a matching enum value.
50+
/// </summary>
51+
/// <returns>The enum the represents the string <paramref name="value"/> provided.</returns>
52+
/// <exception cref="ArgumentNullException">value is null</exception>
53+
/// <exception cref="ArgumentException">Requested <paramref name="value"/> was not found.</exception>
54+
public static TEnum Parse(string value)
2155
{
2256
if (value is null)
2357
throw new ArgumentNullException(nameof(value));
2458

25-
Value = Parse(value);
59+
try
60+
{
61+
return Parser(value);
62+
}
63+
catch (ArgumentException ex)
64+
{
65+
throw new ArgumentException($"Requested value '{value}' was not found.", nameof(value), ex);
66+
}
67+
}
68+
69+
/// <summary>
70+
/// Uses a case-insenstive dictionary lookup to get a matching enum value.
71+
/// </summary>
72+
/// <returns>true if the value found; otherwise false.</returns>
73+
/// <exception cref="ArgumentNullException"/>
74+
public static bool TryParse(string value, out TEnum e)
75+
{
76+
try
77+
{
78+
e = Parser(value);
79+
return true;
80+
}
81+
catch (ArgumentException)
82+
{
83+
e = default!;
84+
return false;
85+
}
86+
}
87+
88+
// https://stackoverflow.com/questions/26678181/enum-parse-vs-switch-performance
89+
static Func<string, TEnum> CreateParseEnumDelegate()
90+
{
91+
var eValue = Expression.Parameter(typeof(string), "value"); // (string value)
92+
var tEnum = typeof(TEnum);
93+
94+
return
95+
Expression.Lambda<Func<string, TEnum>>(
96+
Expression.Block(tEnum,
97+
Expression.Switch(tEnum, eValue,
98+
Expression.Block(tEnum,
99+
Expression.Throw(Expression.New(typeof(ArgumentException).GetConstructor(Type.EmptyTypes))),
100+
Expression.Default(tEnum)
101+
),
102+
null,
103+
Enum.GetValues(tEnum).Cast<object>().Select(v => Expression.SwitchCase(
104+
Expression.Constant(v),
105+
Expression.Constant(v.ToString())
106+
)).ToArray()
107+
)
108+
), eValue
109+
).Compile();
110+
}
111+
112+
/// <summary>
113+
/// Indicates whether this instance matches the enum value of <paramref name="other"/>.
114+
/// </summary>
115+
/// <returns>true if <paramref name="value"/>'s enum value and this instance's enum value are the same; otherwise false.</returns>
116+
public bool Equals(EnumValue<TEnum> other) => Value.Equals(other.Value);
117+
public static bool operator ==(EnumValue<TEnum> left, EnumValue<TEnum> right) => left.Value.Equals(right.Value);
118+
public static bool operator !=(EnumValue<TEnum> left, EnumValue<TEnum> right) => !left.Value.Equals(right.Value);
119+
120+
/// <inheritdoc cref="Equals(EnumValue{TEnum})"/>
121+
public bool Equals(EnumValueCaseIgnored<TEnum> other) => Value.Equals(other.Value);
122+
public static bool operator ==(EnumValue<TEnum> left, EnumValueCaseIgnored<TEnum> right) => left.Value.Equals(right.Value);
123+
public static bool operator !=(EnumValue<TEnum> left, EnumValueCaseIgnored<TEnum> right) => !left.Value.Equals(right.Value);
124+
125+
/// <summary>
126+
/// Indicates whether this instance matches the provided enum <paramref name="value"/>.
127+
/// </summary>
128+
/// <returns>true if <paramref name="value"/> and this instance's enum value are the same; otherwise false.</returns>
129+
public bool Equals(TEnum value) => Value.Equals(value);
130+
public static bool operator ==(EnumValue<TEnum> left, TEnum right) => left.Value.Equals(right);
131+
public static bool operator !=(EnumValue<TEnum> left, TEnum right) => !left.Value.Equals(right);
132+
133+
/// <inheritdoc />
134+
public override bool Equals(object? obj)
135+
{
136+
return obj is TEnum e && Value.Equals(e)
137+
|| obj is EnumValue<TEnum> v1 && Value.Equals(v1.Value)
138+
|| obj is EnumValueCaseIgnored<TEnum> v2 && Value.Equals(v2.Value);
139+
}
140+
141+
/// <inheritdoc />
142+
public override int GetHashCode() => Value.GetHashCode();
143+
144+
public static implicit operator EnumValue<TEnum>(EnumValueCaseIgnored<TEnum> value) => new(value.Value);
145+
146+
public static implicit operator TEnum(EnumValue<TEnum> value) => value.Value;
147+
148+
public static implicit operator EnumValue<TEnum>(string value) => new(value);
149+
150+
private string GetDebuggerDisplay()
151+
{
152+
var eType = typeof(TEnum);
153+
return $"{eType.Name}.{Value} [EnumValue<{eType.FullName}>]";
154+
}
155+
}
156+
157+
/// <summary>
158+
/// A case struct representing an enum value that when parsing or coercing from a string ignores case differences.
159+
/// </summary>
160+
[DebuggerDisplay("{GetDebuggerDisplay()}")]
161+
public struct EnumValueCaseIgnored<TEnum>
162+
where TEnum : Enum
163+
{
164+
/// <summary>
165+
/// Constructs an EnumValueCaseIgnored&lt;<typeparamref name="TEnum"/>&gt; using the provided enum value.
166+
/// </summary>
167+
public EnumValueCaseIgnored(TEnum value)
168+
{
169+
Value = value;
170+
}
171+
172+
/// <summary>
173+
/// Parses the string value to construct an EnumValueCaseIgnored&lt;<typeparamref name="TEnum"/>&gt; instance.
174+
/// </summary>
175+
/// <exception cref="ArgumentNullException">value is null.</exception>
176+
public EnumValueCaseIgnored(string value)
177+
{
178+
Value = Parse(value);
26179
}
27180

28181
public TEnum Value { get; }
29182

30-
public override string ToString() => Value.ToString();
183+
/// <inheritdoc cref="EnumValue{TEnum}.ToString"/>
184+
public override string ToString() => Value.ToString();
31185

32-
static readonly Func<string, TEnum> Parser = CreateParseEnumDelegate();
33-
public static TEnum Parse(string value)
34-
{
35-
try
36-
{
37-
return Parser(value);
38-
}
39-
catch (Exception ex)
186+
internal static readonly ImmutableDictionary<string, TEnum> CaseInsensitiveLookup
187+
= CreateCaseInsensitiveDictionary();
188+
189+
/// <inheritdoc cref="EnumValue{TEnum}.Parse(string)" />
190+
/// <summary>
191+
/// Uses an expression tree switch to get a matching enum value.
192+
/// </summary>
193+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
194+
public static TEnum Parse(string value)
195+
{
196+
try
197+
{
198+
return CaseInsensitiveLookup[value];
199+
}
200+
catch (KeyNotFoundException ex)
40201
{
41-
throw new ArgumentException($"Requested value '{value}' was not found.", nameof(value), ex);
202+
throw new ArgumentException($"Requested value '{value}' was not found.", nameof(value), ex);
42203
}
43-
}
44-
45-
// https://stackoverflow.com/questions/26678181/enum-parse-vs-switch-performance
46-
static Func<string, TEnum> CreateParseEnumDelegate()
47-
{
48-
var eValue = Expression.Parameter(typeof(string), "value"); // (string value)
49-
var tEnum = typeof(TEnum);
50-
51-
return
52-
Expression.Lambda<Func<string, TEnum>>(
53-
Expression.Block(tEnum,
54-
Expression.Switch(tEnum, eValue,
55-
Expression.Block(tEnum,
56-
Expression.Throw(Expression.New(typeof(Exception).GetConstructor(Type.EmptyTypes))),
57-
Expression.Default(tEnum)
58-
),
59-
null,
60-
Enum.GetValues(tEnum).Cast<object>().Select(v => Expression.SwitchCase(
61-
Expression.Constant(v),
62-
Expression.Constant(v.ToString())
63-
)).ToArray()
64-
)
65-
), eValue
66-
).Compile();
67-
}
68-
69-
public bool Equals(EnumValue<TEnum> other) => Value.Equals(other.Value);
70-
public static bool operator ==(EnumValue<TEnum> left, EnumValue<TEnum> right) => left.Value.Equals(right.Value);
71-
public static bool operator !=(EnumValue<TEnum> left, EnumValue<TEnum> right) => !left.Value.Equals(right.Value);
72-
73-
public bool Equals(EnumValueCaseIgnored<TEnum> other) => Value.Equals(other.Value);
74-
public static bool operator ==(EnumValue<TEnum> left, EnumValueCaseIgnored<TEnum> right) => left.Value.Equals(right.Value);
75-
public static bool operator !=(EnumValue<TEnum> left, EnumValueCaseIgnored<TEnum> right) => !left.Value.Equals(right.Value);
76-
77-
public bool Equals(TEnum other) => Value.Equals(other);
78-
public static bool operator ==(EnumValue<TEnum> left, TEnum right) => left.Value.Equals(right);
79-
public static bool operator !=(EnumValue<TEnum> left, TEnum right) => !left.Value.Equals(right);
80-
81-
public override bool Equals(object? obj)
204+
}
205+
206+
/// <inheritdoc cref="EnumValue{TEnum}.TryParse(string, out TEnum)"/>
207+
/// <summary>
208+
/// Uses an expression tree switch to get a matching enum value.
209+
/// </summary>
210+
public static bool TryParse(string value, out TEnum e)
211+
{
212+
if(CaseInsensitiveLookup.TryGetValue(value, out e!)) return true;
213+
e = default!;
214+
return false;
215+
}
216+
217+
static ImmutableDictionary<string, TEnum> CreateCaseInsensitiveDictionary()
218+
=> Enum
219+
.GetValues(typeof(TEnum))
220+
.Cast<TEnum>()
221+
.ToImmutableDictionary(v => v.ToString(), v => v, StringComparer.OrdinalIgnoreCase);
222+
223+
/// <inheritdoc cref="EnumValue{TEnum}.Equals(EnumValue{TEnum})"/>
224+
public bool Equals(EnumValue<TEnum> other) => Value.Equals(other.Value);
225+
public static bool operator ==(EnumValueCaseIgnored<TEnum> left, EnumValue<TEnum> right) => left.Value.Equals(right.Value);
226+
public static bool operator !=(EnumValueCaseIgnored<TEnum> left, EnumValue<TEnum> right) => !left.Value.Equals(right.Value);
227+
228+
/// <inheritdoc cref="EnumValue{TEnum}.Equals(EnumValue{TEnum})"/>
229+
public bool Equals(EnumValueCaseIgnored<TEnum> other) => Value.Equals(other.Value);
230+
public static bool operator ==(EnumValueCaseIgnored<TEnum> left, EnumValueCaseIgnored<TEnum> right) => left.Value.Equals(right.Value);
231+
public static bool operator !=(EnumValueCaseIgnored<TEnum> left, EnumValueCaseIgnored<TEnum> right) => !left.Value.Equals(right.Value);
232+
233+
/// <inheritdoc cref="EnumValue{TEnum}.Equals(TEnum)"/>
234+
public bool Equals(TEnum value) => Value.Equals(value);
235+
public static bool operator ==(EnumValueCaseIgnored<TEnum> left, TEnum right) => left.Value.Equals(right);
236+
public static bool operator !=(EnumValueCaseIgnored<TEnum> left, TEnum right) => !left.Value.Equals(right);
237+
238+
/// <inheritdoc />
239+
public override bool Equals(object? obj)
82240
{
83241
return obj is TEnum e && Value.Equals(e)
84-
|| obj is EnumValue<TEnum> v1 && Value.Equals(v1.Value)
85-
|| obj is EnumValueCaseIgnored<TEnum> v2 && Value.Equals(v2.Value);
242+
|| obj is EnumValueCaseIgnored<TEnum> v1 && Value.Equals(v1.Value)
243+
|| obj is EnumValue<TEnum> v2 && Value.Equals(v2.Value);
86244
}
87245

246+
/// <inheritdoc />
88247
public override int GetHashCode() => Value.GetHashCode();
89248

90-
public static implicit operator EnumValue<TEnum>(EnumValueCaseIgnored<TEnum> value) => new(value.Value);
249+
public static implicit operator EnumValueCaseIgnored<TEnum>(EnumValue<TEnum> value) => new(value.Value);
250+
251+
public static implicit operator TEnum(EnumValueCaseIgnored<TEnum> value) => value.Value;
91252

92-
public static implicit operator TEnum(EnumValue<TEnum> value) => value.Value;
93-
94-
public static implicit operator EnumValue<TEnum>(string value) => new(value);
95-
96-
private string GetDebuggerDisplay()
97-
{
98-
var eType = typeof(TEnum);
99-
return $"{eType.Name}.{Value} [EnumValue<{eType.FullName}>]";
100-
}
101-
}
102-
103-
[DebuggerDisplay("{GetDebuggerDisplay()}")]
104-
public struct EnumValueCaseIgnored<TEnum>
105-
where TEnum : Enum
106-
{
107-
public EnumValueCaseIgnored(TEnum value)
108-
{
109-
Value = value;
110-
}
111-
112-
public EnumValueCaseIgnored(string value)
113-
{
114-
if (value is null)
115-
throw new ArgumentNullException(nameof(value));
116-
117-
Value = Parse(value);
118-
}
119-
120-
public TEnum Value { get; }
121-
122-
public override string ToString() => Value.ToString();
123-
124-
internal static readonly ImmutableDictionary<string, TEnum> CaseInsensitiveLookup
125-
= CreateCaseInsensitiveDictionary();
126-
127-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
128-
public static TEnum Parse(string value)
129-
{
130-
try
131-
{
132-
return CaseInsensitiveLookup[value];
133-
}
134-
catch (KeyNotFoundException ex)
135-
{
136-
throw new ArgumentException($"Requested value '{value}' was not found.", nameof(value), ex);
137-
}
138-
}
139-
140-
static ImmutableDictionary<string, TEnum> CreateCaseInsensitiveDictionary()
141-
=> Enum
142-
.GetValues(typeof(TEnum))
143-
.Cast<TEnum>()
144-
.ToImmutableDictionary(v => v.ToString(), v => v, StringComparer.OrdinalIgnoreCase);
145-
146-
public bool Equals(EnumValue<TEnum> other) => Value.Equals(other.Value);
147-
public static bool operator ==(EnumValueCaseIgnored<TEnum> left, EnumValue<TEnum> right) => left.Value.Equals(right.Value);
148-
public static bool operator !=(EnumValueCaseIgnored<TEnum> left, EnumValue<TEnum> right) => !left.Value.Equals(right.Value);
149-
150-
public bool Equals(EnumValueCaseIgnored<TEnum> other) => Value.Equals(other.Value);
151-
public static bool operator ==(EnumValueCaseIgnored<TEnum> left, EnumValueCaseIgnored<TEnum> right) => left.Value.Equals(right.Value);
152-
public static bool operator !=(EnumValueCaseIgnored<TEnum> left, EnumValueCaseIgnored<TEnum> right) => !left.Value.Equals(right.Value);
153-
154-
public bool Equals(TEnum other) => Value.Equals(other);
155-
public static bool operator ==(EnumValueCaseIgnored<TEnum> left, TEnum right) => left.Value.Equals(right);
156-
public static bool operator !=(EnumValueCaseIgnored<TEnum> left, TEnum right) => !left.Value.Equals(right);
157-
158-
public override bool Equals(object? obj)
159-
{
160-
return obj is TEnum e && Value.Equals(e)
161-
|| obj is EnumValueCaseIgnored<TEnum> v1 && Value.Equals(v1.Value)
162-
|| obj is EnumValue<TEnum> v2 && Value.Equals(v2.Value);
163-
}
164-
165-
public override int GetHashCode() => Value.GetHashCode();
166-
167-
public static implicit operator EnumValueCaseIgnored<TEnum>(EnumValue<TEnum> value) => new(value.Value);
168-
169-
public static implicit operator TEnum(EnumValueCaseIgnored<TEnum> value) => value.Value;
170-
171-
public static implicit operator EnumValueCaseIgnored<TEnum>(string value) => new(value);
172-
173-
private string GetDebuggerDisplay()
174-
{
175-
var eType = typeof(TEnum);
176-
return $"{eType.Name}.{Value} [EnumValueCaseIgnored<{eType.FullName}>]";
177-
}
178-
}
253+
public static implicit operator EnumValueCaseIgnored<TEnum>(string value) => new(value);
254+
255+
private string GetDebuggerDisplay()
256+
{
257+
var eType = typeof(TEnum);
258+
return $"{eType.Name}.{Value} [EnumValueCaseIgnored<{eType.FullName}>]";
259+
}
260+
}
179261
}

0 commit comments

Comments
 (0)