From 8f17d596940afa08cac85e7c2669ab264f9dcf80 Mon Sep 17 00:00:00 2001 From: Chris Cameron Date: Thu, 26 Jul 2018 12:10:50 -0400 Subject: [PATCH] perf: Massive performance improvements to enum HasFlag and HasFlags extensions --- ICD.Common.Utils.Tests/EnumUtilsTest.cs | 14 -- ICD.Common.Utils.Tests/Xml/XmlUtilsTest.cs | 1 + ICD.Common.Utils/EnumUtils.cs | 261 +++++++++++++++++---- 3 files changed, 213 insertions(+), 63 deletions(-) diff --git a/ICD.Common.Utils.Tests/EnumUtilsTest.cs b/ICD.Common.Utils.Tests/EnumUtilsTest.cs index c36a4db..48820b4 100644 --- a/ICD.Common.Utils.Tests/EnumUtilsTest.cs +++ b/ICD.Common.Utils.Tests/EnumUtilsTest.cs @@ -273,20 +273,6 @@ namespace ICD.Common.Utils.Tests Assert.AreEqual(eTestFlagsEnum.None, outputB); } - [Test] - public void ToEnumGenericTest() - { - Assert.AreEqual(eTestEnum.A, EnumUtils.ToEnum(eTestEnum.A)); - Assert.AreNotEqual(eTestEnum.B, EnumUtils.ToEnum(eTestEnum.A)); - } - - [Test] - public void ToEnumTest() - { - Assert.AreEqual(eTestEnum.A, EnumUtils.ToEnum((object)eTestEnum.A)); - Assert.AreNotEqual(eTestEnum.B, EnumUtils.ToEnum((object)eTestEnum.A)); - } - #endregion } } diff --git a/ICD.Common.Utils.Tests/Xml/XmlUtilsTest.cs b/ICD.Common.Utils.Tests/Xml/XmlUtilsTest.cs index 6d3025c..027455d 100644 --- a/ICD.Common.Utils.Tests/Xml/XmlUtilsTest.cs +++ b/ICD.Common.Utils.Tests/Xml/XmlUtilsTest.cs @@ -253,6 +253,7 @@ namespace ICD.Common.Utils.Tests.Xml [TestCase("A", "Child", true, eTestEnum.A)] [TestCase("A, B", "Child", true, eTestEnum.A | eTestEnum.B)] public void ReadChildElementContentAsEnumTest(string xml, string childElement, bool ignoreCase, T expected) + where T : struct, IConvertible { Assert.AreEqual(expected, XmlUtils.ReadChildElementContentAsEnum(xml, childElement, ignoreCase)); } diff --git a/ICD.Common.Utils/EnumUtils.cs b/ICD.Common.Utils/EnumUtils.cs index 729f5d6..781be84 100644 --- a/ICD.Common.Utils/EnumUtils.cs +++ b/ICD.Common.Utils/EnumUtils.cs @@ -25,12 +25,13 @@ namespace ICD.Common.Utils s_EnumFlagsCache = new Dictionary>(); } + #region Validation + /// /// Returns true if the given type is an enum. /// /// public static bool IsEnumType() - where T : struct, IConvertible { return IsEnumType(typeof(T)); } @@ -56,9 +57,34 @@ namespace ICD.Common.Utils /// /// public static bool IsEnum(T value) - where T : struct, IConvertible { - return IsEnumType(value.GetType()); + return value != null && IsEnumType(value.GetType()); + } + + /// + /// Returns true if the given enum type has the Flags attribute set. + /// + /// + /// + public static bool IsFlagsEnum() + { + return IsFlagsEnum(typeof(T)); + } + + /// + /// Returns true if the given enum type has the Flags attribute set. + /// + /// + public static bool IsFlagsEnum(Type type) + { + if (type == null) + throw new ArgumentNullException("type"); + + return type +#if !SIMPLSHARP + .GetTypeInfo() +#endif + .IsDefined(typeof(FlagsAttribute), false); } /// @@ -73,21 +99,30 @@ namespace ICD.Common.Utils if (!IsEnumType()) throw new InvalidOperationException(string.Format("{0} is not an enum", typeof(T).Name)); - if (!IsFlagsEnum()) - return GetValues().Any(v => v.Equals(value)); - - int valueInt = (int)(object)value; - - // Check if all of the flag values are defined - foreach (T flag in GetFlags(value)) - { - int flagInt = (int)(object)flag; - valueInt = valueInt - flagInt; - } - - return valueInt == 0; + T all = GetFlagsAllValue(); + return HasFlags(all, value); } + /// + /// Returns true if the given value is defined as part of the given enum type. + /// + /// + /// + /// + public static bool IsDefined(Type type, int value) + { + if (type == null) + throw new ArgumentNullException("type"); + + if (!IsEnumType(type)) + throw new InvalidOperationException(string.Format("{0} is not an enum", type.Name)); + + int all = GetFlagsAllValue(type); + return HasFlags(all, value); + } + + #endregion + #region Values /// @@ -117,7 +152,10 @@ namespace ICD.Common.Utils /// public static IEnumerable GetValues(Type type) { - return GetValuesUncached(type).Cast(); + if (type == null) + throw new ArgumentNullException("type"); + + return GetValuesUncached(type); } /// @@ -134,8 +172,11 @@ namespace ICD.Common.Utils /// Gets the values from an enumeration without performing any caching. This is slow because of reflection. /// /// - private static IEnumerable GetValuesUncached(Type type) + private static IEnumerable GetValuesUncached(Type type) { + if (type == null) + throw new ArgumentNullException("type"); + if (!IsEnumType(type)) throw new InvalidOperationException(string.Format("{0} is not an enum", type.Name)); @@ -146,7 +187,8 @@ namespace ICD.Common.Utils .GetTypeInfo() #endif .GetFields(BindingFlags.Static | BindingFlags.Public) - .Select(x => x.GetValue(null)); + .Select(x => x.GetValue(null)) + .Cast(); } /// @@ -166,6 +208,9 @@ namespace ICD.Common.Utils /// public static IEnumerable GetValuesExceptNone(Type type) { + if (type == null) + throw new ArgumentNullException("type"); + return GetValues(type).Except(0); } @@ -173,30 +218,6 @@ namespace ICD.Common.Utils #region Flags - /// - /// Returns true if the given enum type has the Flags attribute set. - /// - /// - /// - public static bool IsFlagsEnum() - where T : struct, IConvertible - { - return IsFlagsEnum(typeof(T)); - } - - /// - /// Returns true if the given enum type has the Flags attribute set. - /// - /// - public static bool IsFlagsEnum(Type type) - { - return type -#if !SIMPLSHARP - .GetTypeInfo() -#endif - .IsDefined(typeof(FlagsAttribute), false); - } - /// /// Gets the overlapping values of the given enum flags. /// @@ -206,14 +227,32 @@ namespace ICD.Common.Utils public static T GetFlagsIntersection(params T[] values) where T : struct, IConvertible { + if (values == null) + throw new ArgumentNullException("values"); + if (values.Length == 0) return default(T); - int output = (int)(object)values.First(); - foreach (T item in values.Skip(1)) - output &= (int)(object)item; + int output = 0; + bool first = true; - return (T)Enum.ToObject(typeof(T), output); + foreach (T value in values) + { + if (first) + { + output = (int)(object)value; + first = false; + } + else + { + output &= (int)(object)value; + } + + if (output == 0) + return default(T); + } + + return (T)(object)output; } /// @@ -229,7 +268,7 @@ namespace ICD.Common.Utils int aInt = (int)(object)a; int bInt = (int)(object)b; - return (T)Enum.ToObject(typeof(T), aInt & bInt); + return (T)(object)(aInt & bInt); } /// @@ -328,6 +367,22 @@ namespace ICD.Common.Utils return (T)Enum.ToObject(typeof(T), output); } + /// + /// Gets an enum value of the given type with every flag set. + /// + /// + /// + public static int GetFlagsAllValue(Type type) + { + if (type == null) + throw new ArgumentNullException("type"); + + if (!IsEnumType(type)) + throw new ArgumentException(string.Format("{0} is not an enum", type.Name)); + + return GetValuesUncached(type).Aggregate(0, (current, value) => current | value); + } + /// /// Returns true if the enum contains the given flag. /// @@ -347,6 +402,17 @@ namespace ICD.Common.Utils return HasFlags(value, flag); } + /// + /// Returns true if the enum contains the given flag. + /// + /// + /// + /// + public static bool HasFlag(int value, int flag) + { + return HasFlags(value, flag); + } + /// /// Returns true if the enum contains all of the given flags. /// @@ -366,7 +432,18 @@ namespace ICD.Common.Utils int a = (int)(object)value; int b = (int)(object)flags; - return (a & b) == b; + return HasFlags(a, b); + } + + /// + /// Returns true if the enum contains the given flag. + /// + /// + /// + /// + public static bool HasFlags(int value, int flags) + { + return (value & flags) == flags; } /// @@ -471,6 +548,29 @@ namespace ICD.Common.Utils throw new FormatException(message); } + /// + /// Shorthand for parsing string to enum. + /// + /// + /// + /// + /// + public static int Parse(Type type, string data, bool ignoreCase) + { + if (type == null) + throw new ArgumentNullException("type"); + + if (!IsEnumType(type)) + throw new ArgumentException(string.Format("{0} is not an enum", type.Name)); + + int output; + if (TryParse(type, data, ignoreCase, out output)) + return output; + + string message = string.Format("Failed to parse {0} as {1}", StringUtils.ToRepresentation(data), type.Name); + throw new FormatException(message); + } + /// /// Shorthand for parsing a string to enum. Returns false if the parse failed. /// @@ -498,6 +598,35 @@ namespace ICD.Common.Utils } } + /// + /// Shorthand for parsing a string to enum. Returns false if the parse failed. + /// + /// + /// + /// + /// + /// + public static bool TryParse(Type type, string data, bool ignoreCase, out int result) + { + if (type == null) + throw new ArgumentNullException("type"); + + if (!IsEnumType(type)) + throw new ArgumentException(string.Format("{0} is not an enum", type.Name)); + + result = 0; + + try + { + result = (int)Enum.Parse(type, data, ignoreCase); + return true; + } + catch (Exception) + { + return false; + } + } + /// /// Shorthand for parsing string to enum. /// Will fail if the resulting value is not defined as part of the enum. @@ -530,6 +659,40 @@ namespace ICD.Common.Utils return output; } + /// + /// Shorthand for parsing string to enum. + /// Will fail if the resulting value is not defined as part of the enum. + /// + /// + /// + /// + /// + public static int ParseStrict(Type type, string data, bool ignoreCase) + { + if (type == null) + throw new ArgumentNullException("type"); + + if (!IsEnumType(type)) + throw new ArgumentException(string.Format("{0} is not an enum", type.Name)); + + int output; + + try + { + output = Parse(type, data, ignoreCase); + } + catch (Exception e) + { + throw new FormatException( + string.Format("Failed to parse {0} as {1}", StringUtils.ToRepresentation(data), type.Name), e); + } + + if (!IsDefined(type, output)) + throw new ArgumentOutOfRangeException(string.Format("{0} is not a valid {1}", output, type.Name)); + + return output; + } + /// /// Shorthand for parsing a string to enum. Returns false if the parse failed. /// Will fail if the resulting value is not defined as part of the enum.