Code/SmartEnum.cs
using Sandbox;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SmarterEnum;
public abstract class SmartEnum
{
public string Name { get; }
[SkipHotload]
protected internal static readonly Dictionary<Type, Lazy<IReadOnlyList<SmartEnum>>> _allValues = []; // TODO remove and access values via better way
protected internal SmartEnum(string name)
{
Name = name;
}
public static bool TryFromName(Type smartEnumType, string str, bool ignoreCase, out SmartEnum value)
{
if(!IsSmartEnum(smartEnumType))
throw new ArgumentException($"Given type ({smartEnumType}) is not a ({nameof(SmartEnum)}).", nameof(smartEnumType));
var values = GetValues(smartEnumType);
foreach(var currentValue in values)
{
if(string.Equals(currentValue.Name, str, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
{
value = currentValue;
return true;
}
}
value = null!;
return false;
}
internal protected static List<SmartEnum> FindAllValues(Type type)
{
return TypeLibrary.GetType(type).Fields
.Where(x => x.FieldType.IsAssignableTo(type) && x.IsStatic && x.IsInitOnly)
.Select(x => ((SmartEnum)x.GetValue(null)))
.Order()
.ToList();
}
public static IEnumerable<SmartEnum> GetValues(Type smartEnumType)
{
if(!IsSmartEnum(smartEnumType))
throw new ArgumentException($"Given type ({smartEnumType}) is not a ({nameof(SmartEnum)}).", nameof(smartEnumType));
if(!_allValues.ContainsKey(smartEnumType))
_allValues[smartEnumType] = new(() => FindAllValues(smartEnumType));
return _allValues[smartEnumType].Value;
}
public static bool IsSmartEnum(Type type)
{
return type.IsAssignableToGenericType(typeof(SmartEnum<,>));
}
}
public class SmartEnum<TEnum> : SmartEnum<TEnum, int> where TEnum : SmartEnum<TEnum, int>
{
protected SmartEnum(string name, int value) : base(name, value)
{
}
}
public class SmartEnum<TEnum, TValue> : SmartEnum, IComparable<SmartEnum<TEnum, TValue>>, IComparable, IEquatable<SmartEnum<TEnum, TValue>>
where TEnum : SmartEnum<TEnum, TValue> where TValue : IComparable<TValue>, IEquatable<TValue>
{
private static readonly Lazy<IReadOnlyList<TEnum>> _values = new(FindAllValues, true);
private static readonly Lazy<Dictionary<string, TEnum>> _fromName = new(() => Values.ToDictionary(item => item.Name), true);
private static readonly Lazy<Dictionary<string, TEnum>> _fromNameIgnoreCase = new(() => Values.ToDictionary(item => item.Name, StringComparer.OrdinalIgnoreCase), true);
private static readonly Lazy<Dictionary<TValue, TEnum>> _fromValue =
new(() =>
{
var dictionary = new Dictionary<TValue, TEnum>(GetValueComparer());
foreach(var item in Values)
{
if(item.Value is not null)
dictionary.TryAdd(item.Value, item);
}
return dictionary;
}, true);
public TValue Value { get; }
public static IReadOnlyCollection<TEnum> Values => _values.Value;
protected SmartEnum(string name, TValue value) : base(name)
{
Value = value;
var type = GetType();
if(!_allValues.ContainsKey(type))
_allValues.Add(type, new(() => _values.Value));
}
private static List<TEnum> FindAllValues()
{
return SmartEnum.FindAllValues(typeof(TEnum)).Select(x => (TEnum)x).ToList();
}
public static bool IsDefined(TEnum value) => Values.Contains(value);
public static TEnum FromName(string name, bool ignoreCase = false)
{
ArgumentNullException.ThrowIfNullOrEmpty(name);
if(ignoreCase)
return FromName(_fromNameIgnoreCase.Value);
else
return FromName(_fromName.Value);
TEnum FromName(Dictionary<string, TEnum> dictionary)
{
if(!dictionary.TryGetValue(name, out var result))
throw new ArgumentException($"Name {name} not found.", nameof(name));
return result;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryFromName(string name, out TEnum result) =>
TryFromName(name, false, out result);
public static bool TryFromName(string name, bool ignoreCase, out TEnum result)
{
if(String.IsNullOrEmpty(name))
{
result = default!;
return false;
}
if(ignoreCase)
return _fromNameIgnoreCase.Value.TryGetValue(name, out result!);
else
return _fromName.Value.TryGetValue(name, out result!);
}
public static TEnum FromValue(TValue value)
{
if(!TryFromValue(value, out var result))
throw new ArgumentException($"Value {value} not found.", nameof(value));
return result;
}
public static TEnum FromValue(TValue value, TEnum defaultValue)
{
ArgumentNullException.ThrowIfNull(value);
if(!_fromValue.Value.TryGetValue(value, out var result))
return defaultValue;
return result;
}
public static bool TryFromValue(TValue value, out TEnum result)
{
if(value is not null)
{
if(!_fromValue.Value.TryGetValue(value, out result!))
return false;
}
else
{
var nulls = _values.Value.Where(x => x.Value is null);
result = nulls.FirstOrDefault()!;
if(!nulls.Any())
return false;
}
return true;
}
private static IEqualityComparer<TValue>? GetValueComparer()
{
var comparer = typeof(TEnum).GetCustomAttribute<SmartEnumComparerAttribute<TValue>>();
return comparer?.Comparer;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator <(SmartEnum<TEnum, TValue> left, SmartEnum<TEnum, TValue> right) =>
left.CompareTo(right) < 0;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator <=(SmartEnum<TEnum, TValue> left, SmartEnum<TEnum, TValue> right) =>
left.CompareTo(right) <= 0;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator >(SmartEnum<TEnum, TValue> left, SmartEnum<TEnum, TValue> right) =>
left.CompareTo(right) > 0;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator >=(SmartEnum<TEnum, TValue> left, SmartEnum<TEnum, TValue> right) =>
left.CompareTo(right) >= 0;
public static bool operator ==(SmartEnum<TEnum, TValue> left, SmartEnum<TEnum, TValue> right)
{
if(left is null)
return right is null;
return left.Equals(right);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(SmartEnum<TEnum, TValue> left, SmartEnum<TEnum, TValue> right) => !(left == right);
public int CompareTo(SmartEnum<TEnum, TValue>? other)
{
if(other is null)
return 1;
return Value.CompareTo(other.Value);
}
public int CompareTo(object? obj)
{
if(obj is null)
return 1;
if(obj is not SmartEnum<TEnum, TValue> other)
throw new ArgumentException($"Object is not a {typeof(SmartEnum<TEnum, TValue>)}.", nameof(obj));
return CompareTo(other);
}
public bool Equals(SmartEnum<TEnum, TValue>? other)
{
if(object.ReferenceEquals(this, other))
return true;
if(other is null)
return false;
return Value.Equals(other.Value);
}
public override bool Equals(object? obj) => obj is SmartEnum<TEnum, TValue> other && Equals(other);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator SmartEnum<TEnum, TValue>(TValue value) =>
FromValue(value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => Value.GetHashCode();
public override string ToString() => Name;
public class SmartEnumJsonConverter : JsonConverter<TEnum>
{
protected virtual string ValueToString(TEnum value) => value.ToString();
protected virtual bool TryParse(string str, out TEnum value) => TryFromName(str, out value);
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
Read(ref reader, typeToConvert, JsonTokenType.String);
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options) =>
writer.WriteStringValue(ValueToString(value));
public override TEnum ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
Read(ref reader, typeToConvert, JsonTokenType.PropertyName);
public override void WriteAsPropertyName(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options) =>
writer.WritePropertyName(ValueToString(value));
protected virtual TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonTokenType validTokenType)
{
if(reader.TokenType == validTokenType)
{
var str = reader.GetString();
if(!string.IsNullOrWhiteSpace(str) && TryParse(str, out var value))
return value;
}
Log.Warning($"{GetType().Name} - unable to read from {reader.TokenType}");
return default!;
}
}
}