diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d88dfb9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.idea +bin +obj +packages +*.nupkg +TestResults \ No newline at end of file diff --git a/DynamicEnums.sln b/DynamicEnums.sln new file mode 100644 index 0000000..e8f240f --- /dev/null +++ b/DynamicEnums.sln @@ -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 diff --git a/DynamicEnums/DynamicEnum.cs b/DynamicEnums/DynamicEnum.cs new file mode 100644 index 0000000..dad3182 --- /dev/null +++ b/DynamicEnums/DynamicEnum.cs @@ -0,0 +1,404 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Numerics; +using System.Reflection; + +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 enum 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) => 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); + /// + /// + 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(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(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(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(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(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(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(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(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(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(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(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(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(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(); + } + + } + + } +} \ No newline at end of file diff --git a/DynamicEnums/DynamicEnums.csproj b/DynamicEnums/DynamicEnums.csproj new file mode 100644 index 0000000..371151e --- /dev/null +++ b/DynamicEnums/DynamicEnums.csproj @@ -0,0 +1,26 @@ + + + net452;netstandard2.0;net6.0 + true + + + + Ellpeck + Enum-like single-instance values with additional capabilities, including dynamic addition of new arbitrary values and flags + enum enumeration dynamic bigint biginteger long + https://github.com/Ellpeck/DynamicEnums + https://github.com/Ellpeck/DynamicEnums + MIT + README.md + 0.0.1 + + + + + + + + + + + \ No newline at end of file diff --git a/DynamicEnums/EnumHelper.cs b/DynamicEnums/EnumHelper.cs new file mode 100644 index 0000000..83e43e7 --- /dev/null +++ b/DynamicEnums/EnumHelper.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; + +namespace DynamicEnums { + /// + /// A helper class that allows easier usage of regular values, as well as values. + /// + public static class EnumHelper { + + /// + /// 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 Enum.GetValues<TEnum>. + /// + /// The type whose enum to get + /// An enumerable of the values of the enum, in declaration order. + public static T[] GetValues() where T : struct, Enum { + #if NET6_0_OR_GREATER + return Enum.GetValues(); + #else + return (T[]) Enum.GetValues(typeof(T)); + #endif + } + + /// + /// Returns true if the given has all of the given flags on it. + /// This operation is equivalent to , but doesn't do any additional checks, making it faster. + /// + /// + /// The value to query flags on. + /// The flags to query. + /// True if all of the flags are present, false otherwise. + public static bool HasAllFlags(this T value, T flags) where T : struct, Enum { + return (Convert.ToInt64(value) & Convert.ToInt64(flags)) == Convert.ToInt64(flags); + } + + /// + /// Returns true if the given has any of the given flags on it. + /// + /// + /// The value to query flags on. + /// The flags to query. + /// True if any of the flags are present, false otherwise. + public static bool HasAnyFlags(this T value, T flags) where T : struct, Enum { + return (Convert.ToInt64(value) & Convert.ToInt64(flags)) != 0; + } + + /// + /// Returns all of the defined values from the given 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 : struct, Enum { + foreach (var flag in EnumHelper.GetValues()) { + if (combinedFlag.HasAllFlags(flag) && (includeZero || Convert.ToInt64(flag) != 0)) + yield return flag; + } + } + + /// + /// Returns all of the defined unique flags from the given 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(T combinedFlag) where T : struct, Enum { + var uniqueFlag = 1L; + foreach (var flag in EnumHelper.GetValues()) { + 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; + } + } + + } +} \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..f96ffd3 --- /dev/null +++ b/Jenkinsfile @@ -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') + } +} diff --git a/README.md b/README.md index de39ace..1e96d16 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# DynamicEnum -Enum-like single-instance values with additional capabilities, including dynamic addition of new arbitrary values +# DynamicEnums +Enum-like single-instance values with additional capabilities, including dynamic addition of new arbitrary values and flags diff --git a/Tests/EnumTests.cs b/Tests/EnumTests.cs new file mode 100644 index 0000000..8d5ab6f --- /dev/null +++ b/Tests/EnumTests.cs @@ -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("Flag" + i); + var combined = DynamicEnum.Add("Combined", DynamicEnum.GetValue(DynamicEnum.Or(flags[7], flags[13]))); + + Assert.AreEqual(DynamicEnum.GetValue(flags[7]), BigInteger.One << 7); + Assert.AreEqual(DynamicEnum.GetEnumValue(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(0)), true); + Assert.AreEqual(flags[24].HasAnyFlags(DynamicEnum.GetEnumValue(0)), false); + + Assert.AreEqual(DynamicEnum.Parse("Flag24"), flags[24]); + Assert.AreEqual(DynamicEnum.Parse("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) {} + + } + +} \ No newline at end of file diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj new file mode 100644 index 0000000..d87f80b --- /dev/null +++ b/Tests/Tests.csproj @@ -0,0 +1,20 @@ + + + net6.0 + nunit + false + + + + + + + + + + + + + + +