mirror of
https://github.com/Ellpeck/DynamicEnums.git
synced 2024-11-23 08:58:34 +01:00
added content from MLEM, plus some added methods
This commit is contained in:
parent
10850533a0
commit
66008cd01b
9 changed files with 690 additions and 2 deletions
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
.idea
|
||||||
|
bin
|
||||||
|
obj
|
||||||
|
packages
|
||||||
|
*.nupkg
|
||||||
|
TestResults
|
22
DynamicEnums.sln
Normal file
22
DynamicEnums.sln
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamicEnums", "DynamicEnums\DynamicEnums.csproj", "{1657964D-2503-426A-8514-D020660BEE4D}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{8E110BC2-38FD-404A-B5BD-02C771B0D1D5}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{1657964D-2503-426A-8514-D020660BEE4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{1657964D-2503-426A-8514-D020660BEE4D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{1657964D-2503-426A-8514-D020660BEE4D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{1657964D-2503-426A-8514-D020660BEE4D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8E110BC2-38FD-404A-B5BD-02C771B0D1D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8E110BC2-38FD-404A-B5BD-02C771B0D1D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8E110BC2-38FD-404A-B5BD-02C771B0D1D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8E110BC2-38FD-404A-B5BD-02C771B0D1D5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
404
DynamicEnums/DynamicEnum.cs
Normal file
404
DynamicEnums/DynamicEnum.cs
Normal file
|
@ -0,0 +1,404 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace DynamicEnums {
|
||||||
|
/// <summary>
|
||||||
|
/// 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 <see cref="BigInteger"/> as its underlying type, allowing for an arbitrary number of enum values to be created, even when a <see cref="FlagsAttribute"/>-like structure is used that would only allow for up to 64 values in a regular enum.
|
||||||
|
/// All enum operations including <see cref="And{T}"/>, <see cref="Or{T}"/>, <see cref="Xor{T}"/> and <see cref="Neg{T}"/> are supported and can be implemented in derived classes using operator overloads.
|
||||||
|
/// To create a custom dynamic enum, simply create a class that extends <see cref="DynamicEnum"/>. New values can then be added using <see cref="Add{T}"/>, <see cref="AddValue{T}"/> or <see cref="AddFlag{T}"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// To include enum-like operator overloads in a dynamic enum named MyEnum, the following code can be used:
|
||||||
|
/// <code>
|
||||||
|
/// public static implicit operator BigInteger(MyEnum value) => GetValue(value);
|
||||||
|
/// public static implicit operator MyEnum(BigInteger value) => GetEnumValue<MyEnum>(value);
|
||||||
|
/// public static MyEnum operator |(MyEnum left, MyEnum right) => Or(left, right);
|
||||||
|
/// public static MyEnum operator &(MyEnum left, MyEnum right) => And(left, right);
|
||||||
|
/// public static MyEnum operator ^(MyEnum left, MyEnum right) => Xor(left, right);
|
||||||
|
/// public static MyEnum operator ~(MyEnum value) => Neg(value);
|
||||||
|
/// </code>
|
||||||
|
/// </remarks>
|
||||||
|
public abstract class DynamicEnum {
|
||||||
|
|
||||||
|
private static readonly Dictionary<Type, Storage> Storages = new Dictionary<Type, Storage>();
|
||||||
|
private readonly BigInteger value;
|
||||||
|
|
||||||
|
private Dictionary<DynamicEnum, bool> allFlagsCache;
|
||||||
|
private Dictionary<DynamicEnum, bool> anyFlagsCache;
|
||||||
|
private string name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new dynamic enum instance.
|
||||||
|
/// This constructor is protected as it is only invoked via reflection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the enum value</param>
|
||||||
|
/// <param name="value">The value</param>
|
||||||
|
protected DynamicEnum(string name, BigInteger value) {
|
||||||
|
this.value = value;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if this enum value has all of the given <see cref="DynamicEnum"/> flags on it.
|
||||||
|
/// This operation is equivalent to <see cref="Enum.HasFlag"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="HasAnyFlags"/>
|
||||||
|
/// <param name="flags">The flags to query</param>
|
||||||
|
/// <returns>True if all of the flags are present, false otherwise</returns>
|
||||||
|
public bool HasAllFlags(DynamicEnum flags) {
|
||||||
|
if (this.allFlagsCache == null)
|
||||||
|
this.allFlagsCache = new Dictionary<DynamicEnum, bool>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if this enum value has any of the given <see cref="DynamicEnum"/> flags on it
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="HasAllFlags"/>
|
||||||
|
/// <param name="flags">The flags to query</param>
|
||||||
|
/// <returns>True if one of the flags is present, false otherwise</returns>
|
||||||
|
public bool HasAnyFlags(DynamicEnum flags) {
|
||||||
|
if (this.anyFlagsCache == null)
|
||||||
|
this.anyFlagsCache = new Dictionary<DynamicEnum, bool>();
|
||||||
|
if (!this.anyFlagsCache.TryGetValue(flags, out var ret)) {
|
||||||
|
ret = (DynamicEnum.GetValue(this) & DynamicEnum.GetValue(flags)) != 0;
|
||||||
|
this.anyFlagsCache.Add(flags, ret);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Returns a string that represents the current object.</summary>
|
||||||
|
/// <returns>A string that represents the current object.</returns>
|
||||||
|
public override string ToString() {
|
||||||
|
if (this.name == null) {
|
||||||
|
var included = new List<DynamicEnum>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new enum value to the given enum type <typeparamref name="T"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the enum value to add</param>
|
||||||
|
/// <param name="value">The value to add</param>
|
||||||
|
/// <typeparam name="T">The type to add this value to</typeparam>
|
||||||
|
/// <returns>The newly created enum value</returns>
|
||||||
|
/// <exception cref="ArgumentException">Thrown if the name or value passed are already present</exception>
|
||||||
|
public static T Add<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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new enum value to the given enum type <typeparamref name="T"/>.
|
||||||
|
/// This method differs from <see cref="Add{T}"/> 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.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the enum value to add</param>
|
||||||
|
/// <typeparam name="T">The type to add this value to</typeparam>
|
||||||
|
/// <returns>The newly created enum value</returns>
|
||||||
|
public static T AddValue<T>(string name) where T : DynamicEnum {
|
||||||
|
BigInteger value = 0;
|
||||||
|
while (DynamicEnum.IsDefined(typeof(T), value))
|
||||||
|
value++;
|
||||||
|
return DynamicEnum.Add<T>(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new flag enum value to the given enum type <typeparamref name="T"/>.
|
||||||
|
/// This method differs from <see cref="Add{T}"/> 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 <see cref="FlagsAttribute"/>-like behavior.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the enum value to add</param>
|
||||||
|
/// <typeparam name="T">The type to add this value to</typeparam>
|
||||||
|
/// <returns>The newly created enum value</returns>
|
||||||
|
public static T AddFlag<T>(string name) where T : DynamicEnum {
|
||||||
|
BigInteger value = 1;
|
||||||
|
while (DynamicEnum.IsDefined(typeof(T), value))
|
||||||
|
value <<= 1;
|
||||||
|
return DynamicEnum.Add<T>(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a collection of all of the enum values that are explicitly defined for the given dynamic enum type <typeparamref name="T"/>.
|
||||||
|
/// A value counts as explicitly defined if it has been added using <see cref="Add{T}"/>, <see cref="AddValue{T}"/> or <see cref="AddFlag{T}"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type whose values to get</typeparam>
|
||||||
|
/// <returns>The defined values for the given type</returns>
|
||||||
|
public static IEnumerable<T> GetValues<T>() where T : DynamicEnum {
|
||||||
|
return DynamicEnum.GetValues(typeof(T)).Cast<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a collection of all of the enum values that are explicitly defined for the given dynamic enum type <paramref name="type"/>.
|
||||||
|
/// A value counts as explicitly defined if it has been added using <see cref="Add{T}"/>, <see cref="AddValue{T}"/> or <see cref="AddFlag{T}"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type whose values to get</param>
|
||||||
|
/// <returns>The defined values for the given type</returns>
|
||||||
|
public static IEnumerable<DynamicEnum> GetValues(Type type) {
|
||||||
|
return DynamicEnum.GetStorage(type).Values.Values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns all of the defined values from the given dynamic enum type <typeparamref name="T"/> which are contained in <paramref name="combinedFlag"/>.
|
||||||
|
/// Note that, if combined flags are defined in <typeparamref name="T"/>, and <paramref name="combinedFlag"/> contains them, they will also be returned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="combinedFlag">The combined flags whose individual flags to return.</param>
|
||||||
|
/// <param name="includeZero">Whether the enum value 0 should also be returned, if <typeparamref name="T"/> contains one.</param>
|
||||||
|
/// <typeparam name="T">The type of enum.</typeparam>
|
||||||
|
/// <returns>All of the flags that make up <paramref name="combinedFlag"/>.</returns>
|
||||||
|
public static IEnumerable<T> GetFlags<T>(T combinedFlag, bool includeZero = true) where T : DynamicEnum {
|
||||||
|
foreach (var flag in DynamicEnum.GetValues<T>()) {
|
||||||
|
if (combinedFlag.HasAllFlags(flag) && (includeZero || DynamicEnum.GetValue(flag) != BigInteger.Zero))
|
||||||
|
yield return flag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns all of the defined unique flags from the given dynamic enum type <typeparamref name="T"/> which are contained in <paramref name="combinedFlag"/>.
|
||||||
|
/// Any combined flags (flags that aren't powers of two) which are defined in <typeparamref name="T"/> will not be returned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="combinedFlag">The combined flags whose individual flags to return.</param>
|
||||||
|
/// <typeparam name="T">The type of enum.</typeparam>
|
||||||
|
/// <returns>All of the unique flags that make up <paramref name="combinedFlag"/>.</returns>
|
||||||
|
public static IEnumerable<T> GetUniqueFlags<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<T>().Max(DynamicEnum.GetValue);
|
||||||
|
var uniqueFlag = BigInteger.One;
|
||||||
|
while (uniqueFlag <= max) {
|
||||||
|
if (DynamicEnum.IsDefined(typeof(T), uniqueFlag)) {
|
||||||
|
var uniqueFlagValue = DynamicEnum.GetEnumValue<T>(uniqueFlag);
|
||||||
|
if (combinedFlag.HasAnyFlags(uniqueFlagValue))
|
||||||
|
yield return uniqueFlagValue;
|
||||||
|
}
|
||||||
|
uniqueFlag <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the bitwise OR (|) combination of the two dynamic enum values
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">The left value</param>
|
||||||
|
/// <param name="right">The right value</param>
|
||||||
|
/// <typeparam name="T">The type of the values</typeparam>
|
||||||
|
/// <returns>The bitwise OR (|) combination</returns>
|
||||||
|
public static T Or<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<T>(DynamicEnum.GetValue(left) | DynamicEnum.GetValue(right));
|
||||||
|
cache.Add((left, right), ret);
|
||||||
|
}
|
||||||
|
return (T) ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the bitwise AND (&) combination of the two dynamic enum values
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">The left value</param>
|
||||||
|
/// <param name="right">The right value</param>
|
||||||
|
/// <typeparam name="T">The type of the values</typeparam>
|
||||||
|
/// <returns>The bitwise AND (&) combination</returns>
|
||||||
|
public static T And<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<T>(DynamicEnum.GetValue(left) & DynamicEnum.GetValue(right));
|
||||||
|
cache.Add((left, right), ret);
|
||||||
|
}
|
||||||
|
return (T) ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the bitwise XOR (^) combination of the two dynamic enum values
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">The left value</param>
|
||||||
|
/// <param name="right">The right value</param>
|
||||||
|
/// <typeparam name="T">The type of the values</typeparam>
|
||||||
|
/// <returns>The bitwise XOR (^) combination</returns>
|
||||||
|
public static T Xor<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<T>(DynamicEnum.GetValue(left) ^ DynamicEnum.GetValue(right));
|
||||||
|
cache.Add((left, right), ret);
|
||||||
|
}
|
||||||
|
return (T) ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the bitwise NEG (~) combination of the dynamic enum value
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value</param>
|
||||||
|
/// <typeparam name="T">The type of the values</typeparam>
|
||||||
|
/// <returns>The bitwise NEG (~) value</returns>
|
||||||
|
public static T Neg<T>(T value) where T : DynamicEnum {
|
||||||
|
var cache = DynamicEnum.GetStorage(typeof(T)).NegCache;
|
||||||
|
if (!cache.TryGetValue(value, out var ret)) {
|
||||||
|
ret = DynamicEnum.GetEnumValue<T>(~DynamicEnum.GetValue(value));
|
||||||
|
cache.Add(value, ret);
|
||||||
|
}
|
||||||
|
return (T) ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the <see cref="BigInteger"/> representation of the given dynamic enum value
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value whose number representation to get</param>
|
||||||
|
/// <returns>The value's number representation</returns>
|
||||||
|
public static BigInteger GetValue(DynamicEnum value) {
|
||||||
|
return value?.value ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the defined or combined dynamic enum value for the given <see cref="BigInteger"/> representation
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value whose dynamic enum value to get</param>
|
||||||
|
/// <typeparam name="T">The type that the returned dynamic enum should have</typeparam>
|
||||||
|
/// <returns>The defined or combined dynamic enum value</returns>
|
||||||
|
public static T GetEnumValue<T>(BigInteger value) where T : DynamicEnum {
|
||||||
|
return (T) DynamicEnum.GetEnumValue(typeof(T), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the defined or combined dynamic enum value for the given <see cref="BigInteger"/> representation
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type that the returned dynamic enum should have</param>
|
||||||
|
/// <param name="value">The value whose dynamic enum value to get</param>
|
||||||
|
/// <returns>The defined or combined dynamic enum value</returns>
|
||||||
|
public static DynamicEnum GetEnumValue(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses the given <see cref="string"/> 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.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="strg">The string to parse into a dynamic enum value</param>
|
||||||
|
/// <typeparam name="T">The type of the dynamic enum value to parse</typeparam>
|
||||||
|
/// <returns>The parsed enum value, or null if parsing fails</returns>
|
||||||
|
public static T Parse<T>(string strg) where T : DynamicEnum {
|
||||||
|
return (T) DynamicEnum.Parse(typeof(T), strg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses the given <see cref="string"/> 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.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type of the dynamic enum value to parse</param>
|
||||||
|
/// <param name="strg">The string to parse into a dynamic enum value</param>
|
||||||
|
/// <returns>The parsed enum value, or null if parsing fails</returns>
|
||||||
|
public static DynamicEnum Parse(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether the given <paramref name="value"/> is defined in the given dynamic enum <paramref name="type"/>.
|
||||||
|
/// A value counts as explicitly defined if it has been added using <see cref="Add{T}"/>, <see cref="AddValue{T}"/> or <see cref="AddFlag{T}"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The dynamic enum type to query.</param>
|
||||||
|
/// <param name="value">The value to query.</param>
|
||||||
|
/// <returns>Whether the <paramref name="value"/> is defined.</returns>
|
||||||
|
public static bool IsDefined(Type type, BigInteger value) {
|
||||||
|
return DynamicEnum.GetStorage(type).Values.ContainsKey(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether the given <paramref name="value"/> is defined in its dynamic enum type.
|
||||||
|
/// A value counts as explicitly defined if it has been added using <see cref="Add{T}"/>, <see cref="AddValue{T}"/> or <see cref="AddFlag{T}"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to query.</param>
|
||||||
|
/// <returns>Whether the <paramref name="value"/> is defined.</returns>
|
||||||
|
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(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<BigInteger, DynamicEnum> Values = new Dictionary<BigInteger, DynamicEnum>();
|
||||||
|
public readonly Dictionary<BigInteger, DynamicEnum> FlagCache = new Dictionary<BigInteger, DynamicEnum>();
|
||||||
|
public readonly Dictionary<string, DynamicEnum> ParseCache = new Dictionary<string, DynamicEnum>();
|
||||||
|
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<DynamicEnum, DynamicEnum> NegCache = new Dictionary<DynamicEnum, DynamicEnum>();
|
||||||
|
|
||||||
|
public void ClearCaches() {
|
||||||
|
this.FlagCache.Clear();
|
||||||
|
this.ParseCache.Clear();
|
||||||
|
this.OrCache.Clear();
|
||||||
|
this.AndCache.Clear();
|
||||||
|
this.XorCache.Clear();
|
||||||
|
this.NegCache.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
26
DynamicEnums/DynamicEnums.csproj
Normal file
26
DynamicEnums/DynamicEnums.csproj
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>net452;netstandard2.0;net6.0</TargetFrameworks>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Authors>Ellpeck</Authors>
|
||||||
|
<Description>Enum-like single-instance values with additional capabilities, including dynamic addition of new arbitrary values and flags</Description>
|
||||||
|
<PackageTags>enum enumeration dynamic bigint biginteger long</PackageTags>
|
||||||
|
<PackageProjectUrl>https://github.com/Ellpeck/DynamicEnums</PackageProjectUrl>
|
||||||
|
<RepositoryUrl>https://github.com/Ellpeck/DynamicEnums</RepositoryUrl>
|
||||||
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
|
<VersionPrefix>0.0.1</VersionPrefix>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="System.ValueTuple" Version="4.5.0" Condition="'$(TargetFramework)'=='net452'" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="../README.md" Pack="true" PackagePath="" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
82
DynamicEnums/EnumHelper.cs
Normal file
82
DynamicEnums/EnumHelper.cs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace DynamicEnums {
|
||||||
|
/// <summary>
|
||||||
|
/// A helper class that allows easier usage of regular <see cref="Enum"/> values, as well as <see cref="FlagsAttribute"/> <see cref="Enum"/> values.
|
||||||
|
/// </summary>
|
||||||
|
public static class EnumHelper {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an array containing all of the values of the given enum type.
|
||||||
|
/// Note that this method is a version-independent equivalent of .NET 5's <c>Enum.GetValues<TEnum></c>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type whose enum to get</typeparam>
|
||||||
|
/// <returns>An enumerable of the values of the enum, in declaration order.</returns>
|
||||||
|
public static T[] GetValues<T>() where T : struct, Enum {
|
||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
return Enum.GetValues<T>();
|
||||||
|
#else
|
||||||
|
return (T[]) Enum.GetValues(typeof(T));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the given <paramref name="value"/> has all of the given <see cref="Enum"/> flags on it.
|
||||||
|
/// This operation is equivalent to <see cref="Enum.HasFlag"/>, but doesn't do any additional checks, making it faster.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="HasAnyFlags{T}"/>
|
||||||
|
/// <param name="value">The value to query flags on.</param>
|
||||||
|
/// <param name="flags">The flags to query.</param>
|
||||||
|
/// <returns>True if all of the flags are present, false otherwise.</returns>
|
||||||
|
public static bool HasAllFlags<T>(this T value, T flags) where T : struct, Enum {
|
||||||
|
return (Convert.ToInt64(value) & Convert.ToInt64(flags)) == Convert.ToInt64(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the given <paramref name="value"/> has any of the given <see cref="Enum"/> flags on it.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="HasAllFlags{T}"/>
|
||||||
|
/// <param name="value">The value to query flags on.</param>
|
||||||
|
/// <param name="flags">The flags to query.</param>
|
||||||
|
/// <returns>True if any of the flags are present, false otherwise.</returns>
|
||||||
|
public static bool HasAnyFlags<T>(this T value, T flags) where T : struct, Enum {
|
||||||
|
return (Convert.ToInt64(value) & Convert.ToInt64(flags)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns all of the defined values from the given enum type <typeparamref name="T"/> which are contained in <paramref name="combinedFlag"/>.
|
||||||
|
/// Note that, if combined flags are defined in <typeparamref name="T"/>, and <paramref name="combinedFlag"/> contains them, they will also be returned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="combinedFlag">The combined flags whose individual flags to return.</param>
|
||||||
|
/// <param name="includeZero">Whether the enum value 0 should also be returned, if <typeparamref name="T"/> contains one.</param>
|
||||||
|
/// <typeparam name="T">The type of enum.</typeparam>
|
||||||
|
/// <returns>All of the flags that make up <paramref name="combinedFlag"/>.</returns>
|
||||||
|
public static IEnumerable<T> GetFlags<T>(T combinedFlag, bool includeZero = true) where T : struct, Enum {
|
||||||
|
foreach (var flag in EnumHelper.GetValues<T>()) {
|
||||||
|
if (combinedFlag.HasAllFlags(flag) && (includeZero || Convert.ToInt64(flag) != 0))
|
||||||
|
yield return flag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns all of the defined unique flags from the given enum type <typeparamref name="T"/> which are contained in <paramref name="combinedFlag"/>.
|
||||||
|
/// Any combined flags (flags that aren't powers of two) which are defined in <typeparamref name="T"/> will not be returned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="combinedFlag">The combined flags whose individual flags to return.</param>
|
||||||
|
/// <typeparam name="T">The type of enum.</typeparam>
|
||||||
|
/// <returns>All of the unique flags that make up <paramref name="combinedFlag"/>.</returns>
|
||||||
|
public static IEnumerable<T> GetUniqueFlags<T>(T combinedFlag) where T : struct, Enum {
|
||||||
|
var uniqueFlag = 1L;
|
||||||
|
foreach (var flag in EnumHelper.GetValues<T>()) {
|
||||||
|
var flagValue = Convert.ToInt64(flag);
|
||||||
|
// GetValues is always ordered by binary value, so we can be sure that the next flag is bigger than the last
|
||||||
|
while (uniqueFlag < flagValue)
|
||||||
|
uniqueFlag <<= 1;
|
||||||
|
if (flagValue == uniqueFlag && combinedFlag.HasAllFlags(flag))
|
||||||
|
yield return flag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
35
Jenkinsfile
vendored
Normal file
35
Jenkinsfile
vendored
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
stages {
|
||||||
|
stage('Test') {
|
||||||
|
steps {
|
||||||
|
sh 'dotnet test --collect:"XPlat Code Coverage"'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Pack') {
|
||||||
|
steps {
|
||||||
|
sh 'find . -type f -name \\\'*.nupkg\\\' -delete'
|
||||||
|
sh 'dotnet pack --version-suffix ${BUILD_NUMBER}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Publish') {
|
||||||
|
when {
|
||||||
|
branch 'main'
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'dotnet nuget push -s https://nuget.ellpeck.de/v3/index.json **/*.nupkg -k $BAGET -n true'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
nunit testResultsPattern: '**/TestResults.xml'
|
||||||
|
cobertura coberturaReportFile: '**/coverage.cobertura.xml'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
environment {
|
||||||
|
BAGET = credentials('3db850d0-e6b5-43d5-b607-d180f4eab676')
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,2 +1,2 @@
|
||||||
# DynamicEnum
|
# DynamicEnums
|
||||||
Enum-like single-instance values with additional capabilities, including dynamic addition of new arbitrary values
|
Enum-like single-instance values with additional capabilities, including dynamic addition of new arbitrary values and flags
|
||||||
|
|
93
Tests/EnumTests.cs
Normal file
93
Tests/EnumTests.cs
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using DynamicEnums;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace Tests;
|
||||||
|
|
||||||
|
public class EnumTests {
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRegularEnums() {
|
||||||
|
Assert.AreEqual(
|
||||||
|
new[] {TestEnum.One, TestEnum.Two, TestEnum.Eight, TestEnum.Sixteen, TestEnum.EightSixteen},
|
||||||
|
EnumHelper.GetFlags(TestEnum.One | TestEnum.Sixteen | TestEnum.Eight | TestEnum.Two));
|
||||||
|
|
||||||
|
Assert.AreEqual(
|
||||||
|
new[] {TestEnum.One, TestEnum.Two, TestEnum.Eight, TestEnum.Sixteen},
|
||||||
|
EnumHelper.GetUniqueFlags(TestEnum.One | TestEnum.Sixteen | TestEnum.Eight | TestEnum.Two));
|
||||||
|
|
||||||
|
Assert.AreEqual(TestEnum.One.HasAnyFlags(TestEnum.Two | TestEnum.One), true);
|
||||||
|
Assert.AreEqual(TestEnum.One.HasAnyFlags(TestEnum.One), true);
|
||||||
|
Assert.AreEqual(TestEnum.One.HasAnyFlags(TestEnum.Two), false);
|
||||||
|
Assert.AreEqual(TestEnum.One.HasAnyFlags((TestEnum) 0), false);
|
||||||
|
|
||||||
|
Assert.AreEqual(TestEnum.One.HasAllFlags(TestEnum.Two | TestEnum.One), TestEnum.One.HasFlag(TestEnum.Two | TestEnum.One));
|
||||||
|
Assert.AreEqual(TestEnum.One.HasAllFlags(TestEnum.One), TestEnum.One.HasFlag(TestEnum.One));
|
||||||
|
Assert.AreEqual(TestEnum.One.HasAllFlags((TestEnum) 0), TestEnum.One.HasFlag((TestEnum) 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDynamicEnums() {
|
||||||
|
var flags = new TestDynamicEnum[100];
|
||||||
|
for (var i = 0; i < flags.Length; i++)
|
||||||
|
flags[i] = DynamicEnum.AddFlag<TestDynamicEnum>("Flag" + i);
|
||||||
|
var combined = DynamicEnum.Add<TestDynamicEnum>("Combined", DynamicEnum.GetValue(DynamicEnum.Or(flags[7], flags[13])));
|
||||||
|
|
||||||
|
Assert.AreEqual(DynamicEnum.GetValue(flags[7]), BigInteger.One << 7);
|
||||||
|
Assert.AreEqual(DynamicEnum.GetEnumValue<TestDynamicEnum>(BigInteger.One << 75), flags[75]);
|
||||||
|
|
||||||
|
Assert.AreEqual(DynamicEnum.GetValue(DynamicEnum.Or(flags[2], flags[17])), BigInteger.One << 2 | BigInteger.One << 17);
|
||||||
|
Assert.AreEqual(DynamicEnum.GetValue(DynamicEnum.And(flags[2], flags[3])), BigInteger.Zero);
|
||||||
|
Assert.AreEqual(DynamicEnum.And(DynamicEnum.Or(flags[24], flags[52]), DynamicEnum.Or(flags[52], flags[75])), flags[52]);
|
||||||
|
Assert.AreEqual(DynamicEnum.Xor(DynamicEnum.Or(flags[85], flags[73]), flags[73]), flags[85]);
|
||||||
|
Assert.AreEqual(DynamicEnum.Xor(DynamicEnum.Or(flags[85], DynamicEnum.Or(flags[73], flags[12])), flags[73]), DynamicEnum.Or(flags[85], flags[12]));
|
||||||
|
Assert.AreEqual(DynamicEnum.GetValue(DynamicEnum.Neg(flags[74])), ~(BigInteger.One << 74));
|
||||||
|
|
||||||
|
Assert.AreEqual(DynamicEnum.Or(flags[24], flags[52]).HasAllFlags(flags[24]), true);
|
||||||
|
Assert.AreEqual(DynamicEnum.Or(flags[24], flags[52]).HasAnyFlags(flags[24]), true);
|
||||||
|
Assert.AreEqual(DynamicEnum.Or(flags[24], flags[52]).HasAllFlags(DynamicEnum.Or(flags[24], flags[26])), false);
|
||||||
|
Assert.AreEqual(DynamicEnum.Or(flags[24], flags[52]).HasAnyFlags(DynamicEnum.Or(flags[24], flags[26])), true);
|
||||||
|
Assert.AreEqual(flags[24].HasAllFlags(DynamicEnum.GetEnumValue<TestDynamicEnum>(0)), true);
|
||||||
|
Assert.AreEqual(flags[24].HasAnyFlags(DynamicEnum.GetEnumValue<TestDynamicEnum>(0)), false);
|
||||||
|
|
||||||
|
Assert.AreEqual(DynamicEnum.Parse<TestDynamicEnum>("Flag24"), flags[24]);
|
||||||
|
Assert.AreEqual(DynamicEnum.Parse<TestDynamicEnum>("Flag24 | Flag43"), DynamicEnum.Or(flags[24], flags[43]));
|
||||||
|
Assert.AreEqual(flags[24].ToString(), "Flag24");
|
||||||
|
Assert.AreEqual(DynamicEnum.Or(flags[24], flags[43]).ToString(), "Flag24 | Flag43");
|
||||||
|
|
||||||
|
Assert.True(DynamicEnum.IsDefined(flags[27]));
|
||||||
|
Assert.True(DynamicEnum.IsDefined(combined));
|
||||||
|
Assert.False(DynamicEnum.IsDefined(DynamicEnum.Or(flags[17], flags[49])));
|
||||||
|
Assert.False(DynamicEnum.IsDefined(DynamicEnum.Or(combined, flags[49])));
|
||||||
|
|
||||||
|
Assert.AreEqual(
|
||||||
|
new[] {flags[0], flags[7], flags[13], combined},
|
||||||
|
DynamicEnum.GetFlags(DynamicEnum.Or(DynamicEnum.Or(flags[0], flags[13]), flags[7])));
|
||||||
|
|
||||||
|
Assert.AreEqual(
|
||||||
|
new[] {flags[0], flags[7], flags[13]},
|
||||||
|
DynamicEnum.GetUniqueFlags(DynamicEnum.Or(DynamicEnum.Or(flags[0], flags[13]), flags[7])));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
private enum TestEnum {
|
||||||
|
|
||||||
|
One = 1,
|
||||||
|
Two = 2,
|
||||||
|
Eight = 8,
|
||||||
|
Sixteen = 16,
|
||||||
|
EightSixteen = TestEnum.Eight | TestEnum.Sixteen,
|
||||||
|
ThirtyTwo = 32,
|
||||||
|
OneTwentyEight = 128,
|
||||||
|
OneTwentyEightTwoOne = TestEnum.OneTwentyEight | TestEnum.Two | TestEnum.One
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestDynamicEnum : DynamicEnum {
|
||||||
|
|
||||||
|
public TestDynamicEnum(string name, BigInteger value) : base(name, value) {}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
20
Tests/Tests.csproj
Normal file
20
Tests/Tests.csproj
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<VSTestLogger>nunit</VSTestLogger>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="3.1.2" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
|
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||||
|
<PackageReference Include="NunitXml.TestLogger" Version="3.0.117" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\DynamicEnums\DynamicEnums.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
Loading…
Reference in a new issue