Skip to main content
added 1278 characters in body
Source Link
Denis
  • 8.6k
  • 5
  • 33
  • 76

The base class is now generic, which allows the derived class to have an extremely simplified creation.

All of the helper methods no longer need to declare a generic type argument as they can use the class' one. Also most of the overloads have been removed, due to now having 2 private dictionary caches, which allows for a StringComparer to be passed during initialization, which also means that all Names will be treated using the StringComparer.OrdinalIgnoreCase.

All of the interfaces' implementations have been separated into partial files. IEquatable<Enumeration<T>> has been inherited and implemented along with some changes to the equality members as a whole. Now they take only the Value of an object in to consideration. As multiple repetitive values are NOT allowed. It's intended to work this way because from my experience with enums it's quite a headache to work with duplicate values inside the same enum.

2 operators have been overridden < && > and an explicit cast from int to Enumeration<T> has be added.

That's pretty much all the changes, the class looks a lot cleaner and definitely faster, because reflection is no longer needed, and we can make use of the constant time complexity of the dictionary's look up.

The base class is now generic, which allows the derived class to have an extremely simplified creation.

All of the helper methods no longer need to declare a generic type argument as they can use the class' one. Also most of the overloads have been removed, due to now having 2 private dictionary caches, which allows for a StringComparer to be passed during initialization, which also means that all Names will be treated using the StringComparer.OrdinalIgnoreCase.

All of the interfaces' implementations have been separated into partial files. IEquatable<Enumeration<T>> has been inherited and implemented along with some changes to the equality members as a whole. Now they take only the Value of an object in to consideration. As multiple repetitive values are NOT allowed. It's intended to work this way because from my experience with enums it's quite a headache to work with duplicate values inside the same enum.

2 operators have been overridden < && > and an explicit cast from int to Enumeration<T> has be added.

That's pretty much all the changes, the class looks a lot cleaner and definitely faster, because reflection is no longer needed, and we can make use of the constant time complexity of the dictionary's look up.

Source Link
Denis
  • 8.6k
  • 5
  • 33
  • 76

After incorporating the suggestions by t3chb0t, the class now looks like this:

[DebuggerDisplay("{Name} = {Value}")]
public abstract partial class Enumeration<T>
    where T : Enumeration<T>
{
    private static readonly IDictionary<int, Enumeration<T>> _valueCache =
        new Dictionary<int, Enumeration<T>>();
    public static IDictionary<int, Enumeration<T>> ValueCache
    {
        get
        {
            RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle);
            return _valueCache;
        }
    }

    private static readonly IDictionary<string, Enumeration<T>> _nameCache =
        new Dictionary<string, Enumeration<T>>(StringComparer.OrdinalIgnoreCase);
    public static IDictionary<string, Enumeration<T>> NameCache
    {
        get
        {
            RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle);
            return _nameCache;
        }
    }

    public string Name { get; }
    public int Value { get; }

    public static IEnumerable<string> Names => NameCache.Keys;
    public static IEnumerable<int> Values => ValueCache.Keys;

    protected Enumeration(string name, int value)
    {
        Name = name;
        Value = value;
        ValueCache.Add(value, this);
        NameCache.Add(name, this);
    }

    public static T Parse(string name)
    {
        if (TryParse(name, out var value))
        {
            return value;
        }
        throw new InvalidOperationException($"Requested value {name} was not found.");
    }

    public static bool TryParse(string name, out T value)
    {
        if (NameCache.TryGetValue(name, out var containedValue))
        {
            value = (T)containedValue;
            return true;
        }
        value = default(T);
        return false;
    }

    public static string Format(T value, string format)
    {
        return value.ToString(format);
    }

    public static bool IsDefined(string name)
    {
        return NameCache.ContainsKey(name);
    }
}

public abstract partial class Enumeration<T> : IConvertible
{
    TypeCode IConvertible.GetTypeCode() => TypeCode.Int32;
    bool IConvertible.ToBoolean(IFormatProvider provider) => Convert.ToBoolean(Value, provider);
    char IConvertible.ToChar(IFormatProvider provider) => Convert.ToChar(Value, provider);
    sbyte IConvertible.ToSByte(IFormatProvider provider) => Convert.ToSByte(Value, provider);
    byte IConvertible.ToByte(IFormatProvider provider) => Convert.ToByte(Value, provider);
    short IConvertible.ToInt16(IFormatProvider provider) => Convert.ToInt16(Value, provider);
    ushort IConvertible.ToUInt16(IFormatProvider provider) => Convert.ToUInt16(Value, provider);
    int IConvertible.ToInt32(IFormatProvider provider) => Value;
    uint IConvertible.ToUInt32(IFormatProvider provider) => Convert.ToUInt32(Value, provider);
    long IConvertible.ToInt64(IFormatProvider provider) => Convert.ToInt64(Value, provider);
    ulong IConvertible.ToUInt64(IFormatProvider provider) => Convert.ToUInt64(Value, provider);
    float IConvertible.ToSingle(IFormatProvider provider) => Convert.ToSingle(Value, provider);
    double IConvertible.ToDouble(IFormatProvider provider) => Convert.ToDouble(Value, provider);
    decimal IConvertible.ToDecimal(IFormatProvider provider) => Convert.ToDecimal(Value, provider);
    DateTime IConvertible.ToDateTime(IFormatProvider provider) => throw new InvalidCastException("Invalid cast.");
    string IConvertible.ToString(IFormatProvider provider) => ToString();

    object IConvertible.ToType(Type conversionType, IFormatProvider provider)
        => Convert.ChangeType(this, conversionType, provider);
}

public abstract partial class Enumeration<T> : IComparable<Enumeration<T>>
{
    public int CompareTo(Enumeration<T> other)
    {
        if (ReferenceEquals(this, other)) return 0;
        if (ReferenceEquals(null, other)) return 1;
        return Value.CompareTo(other.Value);
    }
}

public abstract partial class Enumeration<T> : IFormattable
{
    public string ToString(string format)
    {
        if (string.IsNullOrEmpty(format))
        {
            format = "G";
        }
        if (string.Compare(format, "G", StringComparison.OrdinalIgnoreCase) == 0)
        {
            return Name;
        }
        if (string.Compare(format, "D", StringComparison.OrdinalIgnoreCase) == 0)
        {
            return Value.ToString();
        }
        if (string.Compare(format, "X", StringComparison.OrdinalIgnoreCase) == 0)
        {
            return Value.ToString("X8");
        }
        throw new FormatException("Invalid format");
    }

    public override string ToString() => ToString("G");
    public string ToString(string format, IFormatProvider formatProvider) => ToString(format);
}

public abstract partial class Enumeration<T> : IEquatable<Enumeration<T>>
{
    public bool Equals(Enumeration<T> other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return Value == other.Value;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != GetType()) return false;
        return Equals((Enumeration<T>)obj);
    }

    public override int GetHashCode()
    {
        return Value.GetHashCode();
    }

    public static bool operator >(Enumeration<T> item1, Enumeration<T> item2) => item1.CompareTo(item2) > 0;
    public static bool operator <(Enumeration<T> item1, Enumeration<T> item2) => item1.CompareTo(item2) < 0;

    public static explicit operator Enumeration<T>(int value)
    {
        return (Enumeration<T>)Activator.CreateInstance(
            typeof(T),
            BindingFlags.NonPublic | BindingFlags.Instance, null,
            new object[] { value.ToString(), value }, null);
    }
}