Skip to content

Instantly share code, notes, and snippets.

@kkadir
Created September 14, 2019 10:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kkadir/d7ee1f8d064c1702679ee7f5c3766a47 to your computer and use it in GitHub Desktop.
Save kkadir/d7ee1f8d064c1702679ee7f5c3766a47 to your computer and use it in GitHub Desktop.
An enumeration wrapper with lazy initialization.
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