using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Numerics; using System.Reflection; #if NET6_0_OR_GREATER using System.Diagnostics.CodeAnalysis; #endif namespace DynamicEnums { /// /// A dynamic enum is a class that represents enum-like single-instance value behavior with additional capabilities, including dynamic addition of new arbitrary values. /// A dynamic enum uses as its underlying type, allowing for an arbitrary number of enum values to be created, even when a -like structure is used that would only allow for up to 64 values in a regular enum. /// All boolean operations including , , and are supported and can be implemented in derived classes using operator overloads. /// To create a custom dynamic enum, simply create a class that extends . New values can then be added using , or . /// /// /// To include enum-like operator overloads in a dynamic enum named MyEnum, the following code can be used: /// /// public static implicit operator BigInteger(MyEnum value) => DynamicEnum.GetValue(value); /// public static implicit operator MyEnum(BigInteger value) => DynamicEnum.GetEnumValue<MyEnum>(value); /// public static MyEnum operator |(MyEnum left, MyEnum right) => DynamicEnum.Or(left, right); /// public static MyEnum operator &(MyEnum left, MyEnum right) => DynamicEnum.And(left, right); /// public static MyEnum operator ^(MyEnum left, MyEnum right) => DynamicEnum.Xor(left, right); /// public static MyEnum operator ~(MyEnum value) => DynamicEnum.Neg(value); /// /// public abstract class DynamicEnum { private static readonly Dictionary Storages = new Dictionary(); private readonly BigInteger value; private Dictionary allFlagsCache; private Dictionary anyFlagsCache; private string name; /// /// Creates a new dynamic enum instance. /// This constructor is protected as it is only invoked via reflection. /// /// The name of the enum value /// The value protected DynamicEnum(string name, BigInteger value) { this.value = value; this.name = name; } /// /// Returns true if this enum value has all of the given flags on it. /// This operation is equivalent to . /// /// /// The flags to query /// True if all of the flags are present, false otherwise public bool HasAllFlags(DynamicEnum flags) { if (this.allFlagsCache == null) this.allFlagsCache = new Dictionary(); if (!this.allFlagsCache.TryGetValue(flags, out var ret)) { ret = (DynamicEnum.GetValue(this) & DynamicEnum.GetValue(flags)) == DynamicEnum.GetValue(flags); this.allFlagsCache.Add(flags, ret); } return ret; } /// /// Returns true if this enum value has any of the given flags on it /// /// /// The flags to query /// True if one of the flags is present, false otherwise public bool HasAnyFlags(DynamicEnum flags) { if (this.anyFlagsCache == null) this.anyFlagsCache = new Dictionary(); if (!this.anyFlagsCache.TryGetValue(flags, out var ret)) { ret = (DynamicEnum.GetValue(this) & DynamicEnum.GetValue(flags)) != 0; this.anyFlagsCache.Add(flags, ret); } return ret; } /// Returns a string that represents the current object. /// A string that represents the current object. public override string ToString() { if (this.name == null) { var included = new List(); if (DynamicEnum.GetValue(this) != 0) { foreach (var v in DynamicEnum.GetValues(this.GetType())) { if (this.HasAllFlags(v) && DynamicEnum.GetValue(v) != 0) included.Add(v); } } this.name = included.Count > 0 ? string.Join(" | ", included) : DynamicEnum.GetValue(this).ToString(); } return this.name; } /// /// Adds a new enum value to the given enum type /// /// The name of the enum value to add /// The value to add /// The type to add this value to /// The newly created enum value /// Thrown if the name or value passed are already present public static T Add< #if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] #endif T>(string name, BigInteger value) where T : DynamicEnum { var storage = DynamicEnum.GetStorage(typeof(T)); // cached parsed values and names might be incomplete with new values storage.ClearCaches(); if (storage.Values.ContainsKey(value)) throw new ArgumentException($"Duplicate value {value}", nameof(value)); foreach (var v in storage.Values.Values) { if (v.name == name) throw new ArgumentException($"Duplicate name {name}", nameof(name)); } var ret = DynamicEnum.Construct(typeof(T), name, value); storage.Values.Add(value, ret); return (T) ret; } /// /// Adds a new enum value to the given enum type . /// This method differs from in that it automatically determines a value. /// The value determined will be the next free number in a sequence, which represents the default behavior in an enum if enum values are not explicitly numbered. /// /// The name of the enum value to add /// The type to add this value to /// The newly created enum value public static T AddValue< #if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] #endif T>(string name) where T : DynamicEnum { BigInteger value = 0; while (DynamicEnum.IsDefined(typeof(T), value)) value++; return DynamicEnum.Add(name, value); } /// /// Adds a new flag enum value to the given enum type . /// This method differs from in that it automatically determines a value. /// The value determined will be the next free power of two, allowing enum values to be combined using bitwise operations to create -like behavior. /// /// The name of the enum value to add /// The type to add this value to /// The newly created enum value public static T AddFlag< #if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] #endif T>(string name) where T : DynamicEnum { BigInteger value = 1; while (DynamicEnum.IsDefined(typeof(T), value)) value <<= 1; return DynamicEnum.Add(name, value); } /// /// Returns a collection of all of the enum values that are explicitly defined for the given dynamic enum type . /// A value counts as explicitly defined if it has been added using , or . /// /// The type whose values to get /// The defined values for the given type public static IEnumerable GetValues() where T : DynamicEnum { return DynamicEnum.GetValues(typeof(T)).Cast(); } /// /// Returns a collection of all of the enum values that are explicitly defined for the given dynamic enum type . /// A value counts as explicitly defined if it has been added using , or . /// /// The type whose values to get /// The defined values for the given type public static IEnumerable GetValues(Type type) { return DynamicEnum.GetStorage(type).Values.Values; } /// /// Returns all of the defined values from the given dynamic enum type which are contained in . /// Note that, if combined flags are defined in , and contains them, they will also be returned. /// /// The combined flags whose individual flags to return. /// Whether the enum value 0 should also be returned, if contains one. /// The type of enum. /// All of the flags that make up . public static IEnumerable GetFlags(T combinedFlag, bool includeZero = true) where T : DynamicEnum { foreach (var flag in DynamicEnum.GetValues()) { if (combinedFlag.HasAllFlags(flag) && (includeZero || DynamicEnum.GetValue(flag) != BigInteger.Zero)) yield return flag; } } /// /// Returns all of the defined unique flags from the given dynamic enum type which are contained in . /// Any combined flags (flags that aren't powers of two) which are defined in will not be returned. /// /// The combined flags whose individual flags to return. /// The type of enum. /// All of the unique flags that make up . public static IEnumerable GetUniqueFlags< #if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] #endif T>(T combinedFlag) where T : DynamicEnum { // we can't use the same method here as EnumHelper.GetUniqueFlags since DynamicEnum doesn't guarantee sorted values var max = DynamicEnum.GetValues().Max(DynamicEnum.GetValue); var uniqueFlag = BigInteger.One; while (uniqueFlag <= max) { if (DynamicEnum.IsDefined(typeof(T), uniqueFlag)) { var uniqueFlagValue = DynamicEnum.GetEnumValue(uniqueFlag); if (combinedFlag.HasAnyFlags(uniqueFlagValue)) yield return uniqueFlagValue; } uniqueFlag <<= 1; } } /// /// Returns the bitwise OR (|) combination of the two dynamic enum values /// /// The left value /// The right value /// The type of the values /// The bitwise OR (|) combination public static T Or< #if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] #endif T>(T left, T right) where T : DynamicEnum { var cache = DynamicEnum.GetStorage(typeof(T)).OrCache; if (!cache.TryGetValue((left, right), out var ret)) { ret = DynamicEnum.GetEnumValue(DynamicEnum.GetValue(left) | DynamicEnum.GetValue(right)); cache.Add((left, right), ret); } return (T) ret; } /// /// Returns the bitwise AND (&) combination of the two dynamic enum values /// /// The left value /// The right value /// The type of the values /// The bitwise AND (&) combination public static T And< #if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] #endif T>(T left, T right) where T : DynamicEnum { var cache = DynamicEnum.GetStorage(typeof(T)).AndCache; if (!cache.TryGetValue((left, right), out var ret)) { ret = DynamicEnum.GetEnumValue(DynamicEnum.GetValue(left) & DynamicEnum.GetValue(right)); cache.Add((left, right), ret); } return (T) ret; } /// /// Returns the bitwise XOR (^) combination of the two dynamic enum values /// /// The left value /// The right value /// The type of the values /// The bitwise XOR (^) combination public static T Xor< #if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] #endif T>(T left, T right) where T : DynamicEnum { var cache = DynamicEnum.GetStorage(typeof(T)).XorCache; if (!cache.TryGetValue((left, right), out var ret)) { ret = DynamicEnum.GetEnumValue(DynamicEnum.GetValue(left) ^ DynamicEnum.GetValue(right)); cache.Add((left, right), ret); } return (T) ret; } /// /// Returns the bitwise NEG (~) combination of the dynamic enum value /// /// The value /// The type of the values /// The bitwise NEG (~) value public static T Neg< #if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] #endif T>(T value) where T : DynamicEnum { var cache = DynamicEnum.GetStorage(typeof(T)).NegCache; if (!cache.TryGetValue(value, out var ret)) { ret = DynamicEnum.GetEnumValue(~DynamicEnum.GetValue(value)); cache.Add(value, ret); } return (T) ret; } /// /// Returns the representation of the given dynamic enum value /// /// The value whose number representation to get /// The value's number representation public static BigInteger GetValue(DynamicEnum value) { return value?.value ?? 0; } /// /// Returns the defined or combined dynamic enum value for the given representation /// /// The value whose dynamic enum value to get /// The type that the returned dynamic enum should have /// The defined or combined dynamic enum value public static T GetEnumValue< #if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] #endif T>(BigInteger value) where T : DynamicEnum { return (T) DynamicEnum.GetEnumValue(typeof(T), value); } /// /// Returns the defined or combined dynamic enum value for the given representation /// /// The type that the returned dynamic enum should have /// The value whose dynamic enum value to get /// The defined or combined dynamic enum value public static DynamicEnum GetEnumValue( #if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] #endif Type type, BigInteger value) { var storage = DynamicEnum.GetStorage(type); // get the defined value if it exists if (storage.Values.TryGetValue(value, out var defined)) return defined; // otherwise, cache the combined value if (!storage.FlagCache.TryGetValue(value, out var combined)) { combined = DynamicEnum.Construct(type, null, value); storage.FlagCache.Add(value, combined); } return combined; } /// /// Parses the given into a dynamic enum value and returns the result. /// This method supports defined enum values as well as values combined using the pipe (|) character and any number of spaces. /// If no enum value can be parsed, null is returned. /// /// The string to parse into a dynamic enum value /// The type of the dynamic enum value to parse /// The parsed enum value, or null if parsing fails public static T Parse< #if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] #endif T>(string strg) where T : DynamicEnum { return (T) DynamicEnum.Parse(typeof(T), strg); } /// /// Parses the given into a dynamic enum value and returns the result. /// This method supports defined enum values as well as values combined using the pipe (|) character and any number of spaces. /// If no enum value can be parsed, null is returned. /// /// The type of the dynamic enum value to parse /// The string to parse into a dynamic enum value /// The parsed enum value, or null if parsing fails public static DynamicEnum Parse( #if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] #endif Type type, string strg) { var cache = DynamicEnum.GetStorage(type).ParseCache; if (!cache.TryGetValue(strg, out var cached)) { BigInteger? accum = null; foreach (var val in strg.Split('|')) { foreach (var defined in DynamicEnum.GetValues(type)) { if (defined.name == val.Trim()) { accum = (accum ?? 0) | DynamicEnum.GetValue(defined); break; } } } if (accum != null) cached = DynamicEnum.GetEnumValue(type, accum.Value); cache.Add(strg, cached); } return cached; } /// /// Returns whether the given is defined in the given dynamic enum . /// A value counts as explicitly defined if it has been added using , or . /// /// The dynamic enum type to query. /// The value to query. /// Whether the is defined. public static bool IsDefined(Type type, BigInteger value) { return DynamicEnum.GetStorage(type).Values.ContainsKey(value); } /// /// Returns whether the given is defined in its dynamic enum type. /// A value counts as explicitly defined if it has been added using , or . /// /// The value to query. /// Whether the is defined. public static bool IsDefined(DynamicEnum value) { return value != null && DynamicEnum.IsDefined(value.GetType(), DynamicEnum.GetValue(value)); } private static Storage GetStorage(Type type) { if (!DynamicEnum.Storages.TryGetValue(type, out var storage)) { storage = new Storage(); DynamicEnum.Storages.Add(type, storage); } return storage; } private static DynamicEnum Construct( #if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] #endif Type type, string name, BigInteger value) { return (DynamicEnum) Activator.CreateInstance(type, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new object[] {name, value}, CultureInfo.InvariantCulture); } private class Storage { public readonly Dictionary Values = new Dictionary(); public readonly Dictionary FlagCache = new Dictionary(); public readonly Dictionary ParseCache = new Dictionary(); public readonly Dictionary<(DynamicEnum, DynamicEnum), DynamicEnum> OrCache = new Dictionary<(DynamicEnum, DynamicEnum), DynamicEnum>(); public readonly Dictionary<(DynamicEnum, DynamicEnum), DynamicEnum> AndCache = new Dictionary<(DynamicEnum, DynamicEnum), DynamicEnum>(); public readonly Dictionary<(DynamicEnum, DynamicEnum), DynamicEnum> XorCache = new Dictionary<(DynamicEnum, DynamicEnum), DynamicEnum>(); public readonly Dictionary NegCache = new Dictionary(); public void ClearCaches() { this.FlagCache.Clear(); this.ParseCache.Clear(); this.OrCache.Clear(); this.AndCache.Clear(); this.XorCache.Clear(); this.NegCache.Clear(); } } } }