Created
September 14, 2019 10:22
-
-
Save kkadir/d7ee1f8d064c1702679ee7f5c3766a47 to your computer and use it in GitHub Desktop.
An enumeration wrapper with lazy initialization.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Models.Enums | |
{ | |
using System; | |
using System.Collections.Generic; | |
using System.Globalization; | |
using System.Linq; | |
using System.Reflection; | |
/// <summary> | |
/// The base type to create enumeration classes. | |
/// </summary> | |
/// <typeparam name="T">The enumeration type that is inheriting from this class. </typeparam> | |
/// <typeparam name="TKey">Any numeric struct type to identify the instances of this class.</typeparam> | |
public abstract class Enumeration<T, TKey> : | |
IEquatable<Enumeration<T, TKey>>, | |
IComparable, | |
IComparable<Enumeration<T, TKey>>, | |
IFormattable | |
where T : Enumeration<T, TKey> | |
where TKey : struct, IComparable, IComparable<TKey>, IConvertible, IEquatable<TKey>, IFormattable | |
{ | |
private static readonly Lazy<Dictionary<string, T>> _cacheByValues = | |
new Lazy<Dictionary<string, T>>(DistinctByValue); | |
private static readonly Lazy<Dictionary<TKey, T>> _cacheByKeys = | |
new Lazy<Dictionary<TKey, T>>(DistinctByKey); | |
/// <summary> | |
/// Constructs a new <see cref="Enumeration{T, TKey}"/> instance with a valid key-value pair. | |
/// </summary> | |
/// <param name="key">The key of the enumeration instance.</param> | |
/// <param name="value">The value of the enumeration instance.</param> | |
/// <remarks> | |
/// While the <paramref name="key"/> is a struct type, we are not checking against null values, | |
/// and default values are totally acceptable as keys. | |
/// An empty or white-space <paramref name="value"/> is also acceptable as it may be intended to | |
/// be like that. | |
/// </remarks> | |
protected Enumeration(TKey key, string value) | |
{ | |
Key = key; | |
Value = value | |
?? throw new ArgumentException($"The value of {nameof(key)} is null."); | |
} | |
/// <summary> | |
/// Gets the key of the <see cref="Enumeration{T, TKey}"/> instance. | |
/// </summary> | |
public TKey Key { get; } | |
/// <summary> | |
/// Gets the value of the <see cref="Enumeration{T, TKey}"/> instance. | |
/// </summary> | |
public string Value { get; } | |
/// <summary> | |
/// Gets the <see cref="Enumeration{T, TKey}"/> instance from the cached list | |
/// with the specified <paramref name="key"/>. | |
/// </summary> | |
/// <param name="key">The key of the <see cref="Enumeration{T, TKey}"/> instance to get.</param> | |
/// <returns> | |
/// The <see cref="Enumeration{T, TKey}"/> with the specified <paramref name="key"/>. | |
/// If the specified <paramref name="key"/> is not found through the cached list, throws a | |
/// <see cref="ArgumentException"/> | |
/// </returns> | |
/// <exception cref="ArgumentException"><see cref="Enumeration{T, TKey}"/> cannot be found.</exception> | |
public static T GetFromKey(TKey key) | |
{ | |
if (!_cacheByKeys.Value.TryGetValue(key, out var result)) | |
{ | |
throw new ArgumentException($"The value-key pair for the key:{key} cannot be found."); | |
} | |
return result; | |
} | |
/// <summary> | |
/// Tries to get the <see cref="Enumeration{T, TKey}"/> instance from the cached list | |
/// with the specified <paramref name="key"/>. | |
/// </summary> | |
/// <param name="key">The key of the <see cref="Enumeration{T, TKey}"/> instance to get.</param> | |
/// <param name="result"> | |
/// Contains the <see cref="Enumeration{T, TKey}"/> instance with the specified <paramref name="key"/> | |
/// if the key is found; otherwise, <c>null</c>. This parameter is passed uninitialized. | |
/// </param> | |
/// <returns> | |
/// <c>true</c> if the specified <paramref name="key"/> is found through the cached list; | |
/// otherwise, <c>false</c>. | |
/// </returns> | |
public static bool TryGetFromKey(TKey key, out T result) | |
{ | |
if (_cacheByKeys.Value.TryGetValue(key, out var enumResult)) | |
{ | |
result = enumResult; | |
return true; | |
} | |
result = default; | |
return false; | |
} | |
/// <summary> | |
/// Gets the <see cref="Enumeration{T, TKey}"/> instance from the cached list | |
/// with the specified <paramref name="value"/>. | |
/// </summary> | |
/// <param name="value">The value of the <see cref="Enumeration{T, TKey}"/> instance to get.</param> | |
/// <returns> | |
/// The <see cref="Enumeration{T, TKey}"/> with the specified <paramref name="value"/>. | |
/// If the specified <paramref name="value"/> is not found through the cached list, throws a | |
/// <see cref="ArgumentException"/> | |
/// </returns> | |
/// <exception cref="ArgumentException"><see cref="Enumeration{T, TKey}"/> cannot be found.</exception> | |
public static T GetFromValue(string value) | |
{ | |
if (!_cacheByValues.Value.TryGetValue(value, out var result)) | |
{ | |
throw new ArgumentException($"The value-key pair for the value:{value} cannot be found."); | |
} | |
return result; | |
} | |
/// <summary> | |
/// Tries to get the <see cref="Enumeration{T, TKey}"/> instance from the cached list | |
/// with the specified <paramref name="value"/>. | |
/// </summary> | |
/// <param name="value">The value of the <see cref="Enumeration{T, TKey}"/> instance to get.</param> | |
/// <param name="result"> | |
/// Contains the <see cref="Enumeration{T, TKey}"/> instance with the specified <paramref name="value"/> | |
/// if the value is found; otherwise, <c>null</c>. This parameter is passed uninitialized. | |
/// </param> | |
/// <returns> | |
/// <c>true</c> if the specified <paramref name="value"/> is found through the cached list; | |
/// otherwise, <c>false</c>. | |
/// </returns> | |
public static bool TryGetFromValue(string value, out T result) | |
{ | |
if (_cacheByValues.Value.TryGetValue(value, out var enumResult)) | |
{ | |
result = enumResult; | |
return true; | |
} | |
result = default; | |
return false; | |
} | |
/// <summary> | |
/// Gets the value of the <see cref="Enumeration{T, TKey}"/> instance from the cached list | |
/// with the specified <paramref name="key"/>. | |
/// </summary> | |
/// <param name="key">The key of the <see cref="Enumeration{T, TKey}"/> instance to get.</param> | |
/// <returns> | |
/// The value of the <see cref="Enumeration{T, TKey}"/> with the specified <paramref name="key"/>. | |
/// If the specified <paramref name="key"/> is not found through the cached list, throws a | |
/// <see cref="ArgumentException"/> | |
/// </returns> | |
/// <exception cref="ArgumentException"><see cref="Enumeration{T, TKey}"/> cannot be found.</exception> | |
public static string GetValueFromKey(TKey key, string defaultValue = null) | |
{ | |
if (!_cacheByKeys.Value.TryGetValue(key, out var result) | |
&& defaultValue == null) | |
{ | |
throw new ArgumentException($"The value for the key:{key} cannot be found."); | |
} | |
return result?.Value ?? defaultValue; | |
} | |
/// <summary> | |
/// Tries to get the value of the <see cref="Enumeration{T, TKey}"/> instance from the cached list | |
/// with the specified <paramref name="key"/>. | |
/// </summary> | |
/// <param name="key">The key of the <see cref="Enumeration{T, TKey}"/> instance to get.</param> | |
/// <param name="result"> | |
/// Contains the value of the <see cref="Enumeration{T, TKey}"/> instance with the specified <paramref name="key"/> | |
/// if the key is found; otherwise, <c>null</c>. This parameter is passed uninitialized. | |
/// </param> | |
/// <returns> | |
/// <c>true</c> if the specified <paramref name="key"/> is found through the cached list; | |
/// otherwise, <c>false</c>. | |
/// </returns> | |
public static bool TryGetValueFromKey(TKey key, out string result) | |
{ | |
if (_cacheByKeys.Value.TryGetValue(key, out var enumResult)) | |
{ | |
result = enumResult.Value; | |
return true; | |
} | |
result = default; | |
return false; | |
} | |
/// <summary> | |
/// Gets the key of the <see cref="Enumeration{T, TKey}"/> instance from the cached list | |
/// with the specified <paramref name="value"/>. | |
/// </summary> | |
/// <param name="value">The value of the <see cref="Enumeration{T, TKey}"/> instance to get.</param> | |
/// <returns> | |
/// The key of the <see cref="Enumeration{T, TKey}"/> with the specified <paramref name="value"/>. | |
/// If the specified <paramref name="value"/> is not found through the cached list, throws a | |
/// <see cref="ArgumentException"/> | |
/// </returns> | |
/// <exception cref="ArgumentException"><see cref="Enumeration{T, TKey}"/> cannot be found.</exception> | |
public static TKey GetKeyFromValue(string value) | |
{ | |
if (!_cacheByValues.Value.TryGetValue(value, out var result)) | |
{ | |
throw new ArgumentException($"The key for the value:{value} cannot be found."); | |
} | |
return result.Key; | |
} | |
/// <summary> | |
/// Tries to get the key of the <see cref="Enumeration{T, TKey}"/> instance from the cached list | |
/// with the specified <paramref name="value"/>. | |
/// </summary> | |
/// <param name="value">The value of the <see cref="Enumeration{T, TKey}"/> instance to get.</param> | |
/// <param name="result"> | |
/// Contains the key of the <see cref="Enumeration{T, TKey}"/> instance with the specified <paramref name="value"/> | |
/// if the value is found; otherwise, <c>null</c>. This parameter is passed uninitialized. | |
/// </param> | |
/// <returns> | |
/// <c>true</c> if the specified <paramref name="value"/> is found through the cached list; | |
/// otherwise, <c>false</c>. | |
/// </returns> | |
public static bool TryGetKeyFromValue(string value, out TKey result) | |
{ | |
if (_cacheByValues.Value.TryGetValue(value, out var enumResult)) | |
{ | |
result = enumResult.Key; | |
return true; | |
} | |
result = default; | |
return false; | |
} | |
/// <summary> | |
/// Serves as the default hash function. | |
/// </summary> | |
/// <returns>A hash code for the current object.</returns> | |
public override int GetHashCode() | |
{ | |
return (Key, Value).GetHashCode(); | |
} | |
/// <summary> | |
/// Indicates whether the current object is equal to another object of the same type. | |
/// </summary> | |
/// <param name="other">An object to compare with this object.</param> | |
/// <returns> | |
/// <c>true</c> if the current object is equal to the <paramref name="other" /> parameter; | |
/// <c>otherwise</c>, false.</returns> | |
public virtual bool Equals(Enumeration<T, TKey> other) | |
{ | |
if (ReferenceEquals(this, other)) | |
{ | |
return true; | |
} | |
if (other is null) | |
{ | |
return false; | |
} | |
return EqualityComparer<TKey>.Default.Equals(Key, other.Key) | |
&& string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); | |
} | |
/// <summary> | |
/// Indicates whether the current object is equal to another object. | |
/// </summary> | |
/// <param name="obj">An object to compare with this object.</param> | |
/// <returns> | |
/// <c>true</c> if the current object is equal to the <paramref name="obj" /> parameter; | |
/// <c>otherwise</c>, false.</returns> | |
public override bool Equals(object obj) | |
{ | |
return (obj is T other) && Equals(other); | |
} | |
/// <summary> | |
/// Compares the current instance with another object of the same type and returns an integer | |
/// that indicates whether the current instance precedes, follows, or occurs in the same position | |
/// in the sort order as the other object. | |
/// </summary> | |
/// <param name="other">An object to compare with this instance.</param> | |
/// <returns> | |
/// A value that indicates the relative order of the objects being compared. The return value has | |
/// these meanings: | |
/// Less Than Zero => This instance precedes <paramref name="other"/> in the sort order. | |
/// Zero => This instance occurs in the same position in the sort order as <paramref name="other"/>. | |
/// Greater Than >ero => This instance follows <paramref name="other"/> in the sort order. | |
/// </returns> | |
public int CompareTo(Enumeration<T, TKey> other) | |
{ | |
return Key.CompareTo(other.Key); | |
} | |
/// <summary> | |
/// Compares the current instance with another object and returns an integer that indicates | |
/// whether the current instance precedes, follows, or occurs in the same position in the | |
/// sort order as the other object. | |
/// </summary> | |
/// <param name="obj">An object to compare with this instance.</param> | |
/// <returns> | |
/// A value that indicates the relative order of the objects being compared. The return value has | |
/// these meanings: | |
/// Less Than Zero => This instance precedes <paramref name="obj"/> in the sort order. | |
/// Zero => This instance occurs in the same position in the sort order as <paramref name="obj"/>. | |
/// Greater Than >ero => This instance follows <paramref name="obj"/> in the sort order. | |
/// </returns> | |
/// <remarks> | |
/// If the <param name="obj"> is not the same type of the current instance, 0 is returned. | |
/// </remarks> | |
public int CompareTo(object obj) | |
{ | |
return (obj is T other) ? CompareTo(other) : 0; | |
} | |
/// <summary> | |
/// Returns a string that represents the current object. | |
/// </summary> | |
/// <returns>A string that represents the current object.</returns> | |
public override string ToString() | |
{ | |
return ToString("V"); | |
} | |
/// <summary> | |
/// Returns a string that represents the current object with the specified format. | |
/// </summary> | |
/// <param name="format">The format describing how the string will be presented.</param> | |
/// <returns>A formatted string that represents the current object.</returns> | |
public string ToString(string format) | |
{ | |
return ToString(format, CultureInfo.InvariantCulture); | |
} | |
/// <summary> | |
/// Formats the value of the current instance using the specified format. | |
/// </summary> | |
/// <param name="format">The format to use or a null reference to use the default format defined.</param> | |
/// <param name="formatProvider"> | |
/// The provider to use to format the value or null reference to obtain the value format | |
/// </param> | |
/// <returns>A string that represents the current instance in the specified format.</returns> | |
public string ToString(string format, IFormatProvider formatProvider) | |
{ | |
if (string.IsNullOrWhiteSpace(format)) | |
{ | |
format = "V"; | |
} | |
if (formatProvider == null) | |
{ | |
formatProvider = CultureInfo.InvariantCulture; | |
} | |
switch (format.ToUpperInvariant()) | |
{ | |
case "V": | |
return Value; | |
case "K": | |
return Key.ToString(formatProvider); | |
case "F": | |
return $"[{Key}:{Value}]"; | |
default: | |
return Value; | |
} | |
} | |
public static bool operator ==(Enumeration<T, TKey> left, Enumeration<T, TKey> right) | |
{ | |
if (left is null) | |
{ | |
return right is null; | |
} | |
return left.Equals(right); | |
} | |
public static bool operator !=(Enumeration<T, TKey> left, Enumeration<T, TKey> right) => !(left == right); | |
public static bool operator <(Enumeration<T, TKey> left, Enumeration<T, TKey> right) => left.CompareTo(right) < 0; | |
public static bool operator <=(Enumeration<T, TKey> left, Enumeration<T, TKey> right) => left.CompareTo(right) <= 0; | |
public static bool operator >(Enumeration<T, TKey> left, Enumeration<T, TKey> right) => left.CompareTo(right) > 0; | |
public static bool operator >=(Enumeration<T, TKey> left, Enumeration<T, TKey> right) => left.CompareTo(right) >= 0; | |
public static implicit operator TKey(Enumeration<T, TKey> enumeration) => enumeration.Key; | |
public static implicit operator string(Enumeration<T, TKey> enumeration) => enumeration.Value; | |
public static explicit operator Enumeration<T, TKey>(TKey key) => GetFromKey(key); | |
public static explicit operator Enumeration<T, TKey>(string value) => GetFromValue(value); | |
/// <summary> | |
/// Gets all <c>public static</c> instances of a <see cref="Enumeration{T, TKey}"/> based type. | |
/// </summary> | |
/// <returns>A list of <see cref="Enumeration{T, TKey}"/> based type instances.</returns> | |
private static IEnumerable<T> GetAllEnumerations() | |
{ | |
return typeof(T) | |
.GetFields(BindingFlags.Public | |
| BindingFlags.Static | |
| BindingFlags.DeclaredOnly | |
| BindingFlags.FlattenHierarchy) | |
.Where(field => typeof(T).IsAssignableFrom(field.FieldType)) | |
.Select(field => (T)field.GetValue(null)); | |
} | |
/// <summary> | |
/// Projects a list of <see cref="Enumeration{T, TKey}"/> based type instances by value and gets | |
/// the dictionary. | |
/// </summary> | |
/// <returns> | |
/// A dictionary with values as the key and <see cref="Enumeration{T, TKey}"/> based type | |
/// instances as values. | |
/// </returns> | |
/// <remarks>In case of duplicate values, only the first value is projected to the dictionary.</remarks> | |
private static Dictionary<string, T> DistinctByValue() | |
{ | |
var keyDictionary = new Dictionary<string, T>(); | |
foreach (var item in GetAllEnumerations()) | |
{ | |
if (!keyDictionary.ContainsKey(item.Value)) | |
{ | |
keyDictionary.Add(item.Value, item); | |
} | |
} | |
return keyDictionary; | |
} | |
/// <summary> | |
/// Projects a list of <see cref="Enumeration{T, TKey}"/> based type instances by key and gets | |
/// the dictionary. | |
/// </summary> | |
/// <returns> | |
/// A dictionary with keys as the key and <see cref="Enumeration{T, TKey}"/> based type | |
/// instances as values. | |
/// </returns> | |
/// /// <remarks>In case of duplicate keys, only the first key is projected to the dictionary.</remarks> | |
private static Dictionary<TKey, T> DistinctByKey() | |
{ | |
var keyDictionary = new Dictionary<TKey, T>(); | |
foreach (var item in GetAllEnumerations()) | |
{ | |
if (!keyDictionary.ContainsKey(item.Key)) | |
{ | |
keyDictionary.Add(item.Key, item); | |
} | |
} | |
return keyDictionary; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment