From ab86e64bb2cc99fb3c743c93fdeacea29cb80b2a Mon Sep 17 00:00:00 2001 From: Martin Smith Date: Sun, 12 Jan 2025 00:03:09 +0000 Subject: [PATCH] Major code classes cloned - some tests --- DNX.Extensions.sln.DotSettings | 1 + .../Assemblies/AssemblyDetails.cs | 257 ++++++++ .../Assemblies/AssemblyExtensions.cs | 71 ++- .../Assemblies/IAssemblyDetails.cs | 100 ++++ .../Assemblies/VersionExtensions.cs | 40 ++ src/DNX.Extensions/Comparers/ComparerFunc.cs | 40 ++ .../Comparers/EqualityComparerFunc.cs | 50 ++ .../StringComparisonEqualityComparer.cs | 2 +- .../Conversion/ConvertExtensions.cs | 83 +-- .../Conversion/ConvertExtensionsOLD.cs | 105 ++++ src/DNX.Extensions/DNX.Extensions.csproj | 4 + .../DateTimes/DateTimeExtensions.cs | 79 +++ src/DNX.Extensions/Defaults.cs | 29 + .../Enumerations/EnumerationExtensions.cs | 463 +++++++++++++++ src/DNX.Extensions/Enums/EnumExtensions.cs | 52 -- .../Exceptions/ConversionException.cs | 46 ++ .../Exceptions/EnumTypeException.cs | 42 ++ .../Exceptions/EnumValueException.cs | 48 ++ .../Exceptions/ExceptionExtensions.cs | 50 ++ .../Exceptions/ParameterException.cs | 72 +++ .../Exceptions/ParameterInvalidException.cs | 54 ++ .../Exceptions/ReadOnlyListException.cs | 27 + src/DNX.Extensions/Interfaces/IConverter.cs | 16 + src/DNX.Extensions/Interfaces/IExtractable.cs | 14 + src/DNX.Extensions/Interfaces/IPopulatable.cs | 14 + .../Linq/DictionaryExtensions.cs | 234 ++++++++ .../Linq/EnumerableExtensions.cs | 108 ++++ src/DNX.Extensions/Linq/ListExtensions.cs | 145 +++++ src/DNX.Extensions/Linq/MergeTechnique.cs | 23 + .../Linq/NameValueExtensions.cs | 40 ++ src/DNX.Extensions/Linq/TupleExtensions.cs | 95 +++ src/DNX.Extensions/Linq/TupleList.cs | 129 ++++ .../Objects/ObjectExtensions.cs | 61 ++ .../Providers/FormatProviderFunc.cs | 37 ++ .../Reflection/AttributeExtensions.cs | 103 ++++ .../Reflection/ExpressionExtensions.cs | 116 ++++ .../InstanceResolutionExtensions.cs | 44 ++ .../Reflection/ReflectionExtensions.cs | 346 +++++++++++ .../Reflection/TypeExtensions.cs | 85 +++ .../Streams/StreamExtensions.cs | 79 +++ .../Interfaces/INamedInstance.cs | 20 + .../Strings/Interpolation/NamedInstance.cs | 66 +++ .../Interpolation/StringInterpolation.cs | 135 +++++ .../Strings/RegexStringExtensions.cs | 119 ++++ .../Strings/StringExtensions.cs | 6 +- .../Threading/IProducerConsumerQueue.cs | 33 ++ src/DNX.Extensions/Threading/Mutexes/Mutex.cs | 115 ++++ .../Threading/Mutexes/MutexManager.cs | 96 +++ .../Threading/Mutexes/MutexState.cs | 22 + .../Threading/ProducerConsumerQueue.cs | 558 ++++++++++++++++++ .../Threading/ProducerConsumerQueueAction.cs | 60 ++ src/DNX.Extensions/Threading/ThreadHelper.cs | 116 ++++ src/DNX.Extensions/Validation/Guard.cs | 163 +++++ src/DNX.Extensions/Validation/GuardEnums.cs | 92 +++ .../DateTimes/CustomDateTimeFormatProvider.cs | 29 + .../DateTimes/DateTimeExtensionsTests.cs | 157 +++++ .../Enums/EnumExtensionsTests.cs | 19 +- .../Strings/StringExtensionsTests.cs | 4 +- 58 files changed, 5083 insertions(+), 131 deletions(-) create mode 100644 src/DNX.Extensions/Assemblies/AssemblyDetails.cs create mode 100644 src/DNX.Extensions/Assemblies/IAssemblyDetails.cs create mode 100644 src/DNX.Extensions/Assemblies/VersionExtensions.cs create mode 100644 src/DNX.Extensions/Comparers/ComparerFunc.cs create mode 100644 src/DNX.Extensions/Comparers/EqualityComparerFunc.cs create mode 100644 src/DNX.Extensions/Conversion/ConvertExtensionsOLD.cs create mode 100644 src/DNX.Extensions/Defaults.cs create mode 100644 src/DNX.Extensions/Enumerations/EnumerationExtensions.cs delete mode 100644 src/DNX.Extensions/Enums/EnumExtensions.cs create mode 100644 src/DNX.Extensions/Exceptions/ConversionException.cs create mode 100644 src/DNX.Extensions/Exceptions/EnumTypeException.cs create mode 100644 src/DNX.Extensions/Exceptions/EnumValueException.cs create mode 100644 src/DNX.Extensions/Exceptions/ExceptionExtensions.cs create mode 100644 src/DNX.Extensions/Exceptions/ParameterException.cs create mode 100644 src/DNX.Extensions/Exceptions/ParameterInvalidException.cs create mode 100644 src/DNX.Extensions/Exceptions/ReadOnlyListException.cs create mode 100644 src/DNX.Extensions/Interfaces/IConverter.cs create mode 100644 src/DNX.Extensions/Interfaces/IExtractable.cs create mode 100644 src/DNX.Extensions/Interfaces/IPopulatable.cs create mode 100644 src/DNX.Extensions/Linq/DictionaryExtensions.cs create mode 100644 src/DNX.Extensions/Linq/ListExtensions.cs create mode 100644 src/DNX.Extensions/Linq/MergeTechnique.cs create mode 100644 src/DNX.Extensions/Linq/NameValueExtensions.cs create mode 100644 src/DNX.Extensions/Linq/TupleExtensions.cs create mode 100644 src/DNX.Extensions/Linq/TupleList.cs create mode 100644 src/DNX.Extensions/Objects/ObjectExtensions.cs create mode 100644 src/DNX.Extensions/Providers/FormatProviderFunc.cs create mode 100644 src/DNX.Extensions/Reflection/AttributeExtensions.cs create mode 100644 src/DNX.Extensions/Reflection/ExpressionExtensions.cs create mode 100644 src/DNX.Extensions/Reflection/InstanceResolutionExtensions.cs create mode 100644 src/DNX.Extensions/Reflection/TypeExtensions.cs create mode 100644 src/DNX.Extensions/Streams/StreamExtensions.cs create mode 100644 src/DNX.Extensions/Strings/Interpolation/Interfaces/INamedInstance.cs create mode 100644 src/DNX.Extensions/Strings/Interpolation/NamedInstance.cs create mode 100644 src/DNX.Extensions/Strings/Interpolation/StringInterpolation.cs create mode 100644 src/DNX.Extensions/Strings/RegexStringExtensions.cs create mode 100644 src/DNX.Extensions/Threading/IProducerConsumerQueue.cs create mode 100644 src/DNX.Extensions/Threading/Mutexes/Mutex.cs create mode 100644 src/DNX.Extensions/Threading/Mutexes/MutexManager.cs create mode 100644 src/DNX.Extensions/Threading/Mutexes/MutexState.cs create mode 100644 src/DNX.Extensions/Threading/ProducerConsumerQueue.cs create mode 100644 src/DNX.Extensions/Threading/ProducerConsumerQueueAction.cs create mode 100644 src/DNX.Extensions/Threading/ThreadHelper.cs create mode 100644 src/DNX.Extensions/Validation/Guard.cs create mode 100644 src/DNX.Extensions/Validation/GuardEnums.cs create mode 100644 tests/DNX.Extensions.Tests/DateTimes/CustomDateTimeFormatProvider.cs diff --git a/DNX.Extensions.sln.DotSettings b/DNX.Extensions.sln.DotSettings index 85a9b75..01a0a74 100644 --- a/DNX.Extensions.sln.DotSettings +++ b/DNX.Extensions.sln.DotSettings @@ -1,2 +1,3 @@  + True True \ No newline at end of file diff --git a/src/DNX.Extensions/Assemblies/AssemblyDetails.cs b/src/DNX.Extensions/Assemblies/AssemblyDetails.cs new file mode 100644 index 0000000..6a86acb --- /dev/null +++ b/src/DNX.Extensions/Assemblies/AssemblyDetails.cs @@ -0,0 +1,257 @@ +using System; +using System.IO; +using System.Reflection; +using DNX.Extensions.Validation; + +namespace DNX.Extensions.Assemblies; + +/// +/// Implementation for obtaining Assembly Attributes +/// +/// +public class AssemblyDetails : IAssemblyDetails +{ + #region Properties + + /// + /// Internal Assembly field + /// + public Assembly Assembly { get; } + + #endregion + + #region Constructor(s) + + /// + /// Initializes a new instance of the class. + /// + public AssemblyDetails() + : this(Assembly.GetCallingAssembly()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The assembly. + public AssemblyDetails(Assembly assembly) + { + Guard.IsNotNull(() => assembly); + + Assembly = assembly; + } + + #endregion + + #region Internal Methods + + /// + /// Returns the value of attribute T or String.Empty if no value is available. + /// + /// The type of attribute to interrogate + /// The get value. + /// The result of the specified Func, executed on the found attribute, if any. + /// null if not matching attribute can be found + protected string GetValue(Func getValue) + where T : Attribute + { + var a = (T)Attribute.GetCustomAttribute(Assembly, typeof(T)); + + return a == null + ? null + : getValue(a); + } + + #endregion + + #region IAssemblyDetails Members + + /// + /// Gets the AssemblyName + /// + /// The name of the assembly. + public AssemblyName AssemblyName + { + get { return Assembly.GetName(); } + } + + /// + /// Gets the name of the assembly. + /// + /// The name. + public string Name + { + get { return AssemblyName.Name; } + } + + /// + /// Gets the location of the assembly. + /// + /// The location. + public string Location + { + get { return Assembly.Location; } + } + + /// + /// Gets the file name of the assembly. + /// + /// The name of the file. + public string FileName + { + get { return Path.GetFileName(Assembly.Location); } + } + + /// + /// Gets the title attribute value. + /// + /// The title. + public string Title + { + get { return GetValue(a => a.Title); } + } + + /// + /// Gets the product attribute value. + /// + /// The product. + public string Product + { + get { return GetValue(a => a.Product); } + } + + /// + /// Gets the copyright attribute value. + /// + /// The copyright. + public string Copyright + { + get { return GetValue(a => a.Copyright); } + } + + /// + /// Gets the company attribute value. + /// + /// The company. + public string Company + { + get { return GetValue(a => a.Company); } + } + + /// + /// Gets the description attribute value. + /// + /// The description. + public string Description + { + get { return GetValue(a => a.Description); } + } + + /// + /// Gets the trademark attribute value. + /// + /// The trademark. + public string Trademark + { + get { return GetValue(a => a.Trademark); } + } + + /// + /// Gets the configuration attribute value. + /// + /// The configuration. + public string Configuration + { + get { return GetValue(a => a.Configuration); } + } + + /// + /// Gets the assembly version. + /// + /// The version. + public Version Version + { + get { return AssemblyName.Version; } + } + + /// + /// Gets or sets the simplified version. + /// + /// The simplified version. + public string SimplifiedVersion + { + get { return Version.Simplify(); } + } + + /// + /// Gets the file version attribute value. + /// + /// The file version. + public string FileVersion + { + get { return GetValue(a => a.Version); } + } + + /// + /// Gets the informational version attribute value. + /// + /// The informational version. + public string InformationalVersion + { + get { return GetValue(a => a.InformationalVersion); } + } + + #endregion + + #region Static Methods + + /// + /// Creates an AssemblyDetails from the specified assembly. + /// + /// The assembly. + /// IAssemblyDetails. + public static IAssemblyDetails ForAssembly(Assembly assembly) + { + return new AssemblyDetails(assembly); + } + + /// + /// Creates an AssemblyDetails for the calling assembly + /// + /// IAssemblyDetails. + public static IAssemblyDetails ForMe() + { + return ForAssembly(Assembly.GetCallingAssembly()); + } + + /// + /// Creates an AssemblyDetails for the entry point assembly. + /// + /// IAssemblyDetails. + public static IAssemblyDetails ForEntryPoint() + { + return ForAssembly(Assembly.GetEntryAssembly()); + } + + /// + /// Creates an AssemblyDetails fors the assembly containing the specified Type + /// + /// The type. + /// IAssemblyDetails. + public static IAssemblyDetails ForAssemblyContaining(Type type) + { + return ForAssembly(type.Assembly); + } + + /// + /// Creates an AssemblyDetails fors the assembly containing the specified Type + /// + /// + /// IAssemblyDetails. + public static IAssemblyDetails ForAssemblyContaining() + { + return ForAssembly(typeof(T).Assembly); + } + + #endregion +} diff --git a/src/DNX.Extensions/Assemblies/AssemblyExtensions.cs b/src/DNX.Extensions/Assemblies/AssemblyExtensions.cs index 17c3c06..2325199 100644 --- a/src/DNX.Extensions/Assemblies/AssemblyExtensions.cs +++ b/src/DNX.Extensions/Assemblies/AssemblyExtensions.cs @@ -1,7 +1,10 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Reflection; using System.Resources; +using DNX.Extensions.Reflection; // ReSharper disable ConvertToUsingDeclaration @@ -13,13 +16,71 @@ namespace DNX.Extensions.Assemblies; public static class AssemblyExtensions { /// - /// Gets the embedded resource text. + /// Gets the assembly details. /// /// The assembly. - /// Name of the relative resource. - /// The name space. - /// - /// + /// IAssemblyDetails. + public static IAssemblyDetails GetAssemblyDetails(this Assembly assembly) + { + return AssemblyDetails.ForAssembly(assembly); + } + + /// + /// Find the types in the Assembly that implement the specified Type + /// + /// + /// The assembly. + /// IList<Type>. + public static IList FindTypesThatImplementType(this Assembly assembly) + { + var types = assembly.GetTypes() + .Where(t => t.IsA(typeof(T))) + .ToList(); + + return types; + } + + /// + /// Find the concrete types in the Assembly that implement the specified Type + /// + /// The type to find classes that implement + /// The assembly to search + /// List of + public static IList FindConcreteTypesThatImplementType(this Assembly assembly) + { + var concreteClassTypes = FindTypesThatImplementType(assembly) + .Where((t => t.IsClass && !t.IsAbstract)) + .ToList(); + + return concreteClassTypes; + } + + /// + /// Create instances of concrete types in an assembly that implement the specified type + /// + /// + /// The assembly to search + /// List of instances of T> + public static IList CreateInstancesOfConcreteTypesThatImplementType(this Assembly assembly) + { + var types = assembly.FindConcreteTypesThatImplementType(); + + var instances = types + .Select(Activator.CreateInstance) + .Cast() + .ToList(); + + return instances; + } + + /// + /// Gets the embedded resource text. + /// + /// The assembly. + /// Name of the relative resource. + /// The name space. + /// + /// public static string GetEmbeddedResourceText(this Assembly assembly, string relativeResourceName, string nameSpace = null) { try diff --git a/src/DNX.Extensions/Assemblies/IAssemblyDetails.cs b/src/DNX.Extensions/Assemblies/IAssemblyDetails.cs new file mode 100644 index 0000000..6c7408f --- /dev/null +++ b/src/DNX.Extensions/Assemblies/IAssemblyDetails.cs @@ -0,0 +1,100 @@ +using System; +using System.Reflection; + +namespace DNX.Extensions.Assemblies; + +/// +/// Interface IAssemblyDetail +/// +public interface IAssemblyDetails +{ + /// + /// Gets the AssemblyName + /// + /// The name of the assembly. + AssemblyName AssemblyName { get; } + + /// + /// Gets the name of the assembly. + /// + /// The name. + string Name { get; } + + /// + /// Gets the location of the assembly. + /// + /// The location. + string Location { get; } + + /// + /// Gets the file name of the assembly. + /// + /// The name of the file. + string FileName { get; } + + /// + /// Gets the title attribute value. + /// + /// The title. + string Title { get; } + + /// + /// Gets the product attribute value. + /// + /// The product. + string Product { get; } + + /// + /// Gets the copyright attribute value. + /// + /// The copyright. + string Copyright { get; } + + /// + /// Gets the company attribute value. + /// + /// The company. + string Company { get; } + + /// + /// Gets the description attribute value. + /// + /// The description. + string Description { get; } + + /// + /// Gets the trademark attribute value. + /// + /// The trademark. + string Trademark { get; } + + /// + /// Gets the configuration attribute value. + /// + /// The configuration. + string Configuration { get; } + + /// + /// Gets the assembly version. + /// + /// The version. + Version Version { get; } + + /// + /// Gets the simplified version. + /// + /// The simplified version. + string SimplifiedVersion { get; } + + /// + /// Gets the file version attribute value. + /// + /// The file version. + string FileVersion { get; } + + /// + /// Gets the informational version attribute value. + /// + /// The informational version. + string InformationalVersion { get; } +} diff --git a/src/DNX.Extensions/Assemblies/VersionExtensions.cs b/src/DNX.Extensions/Assemblies/VersionExtensions.cs new file mode 100644 index 0000000..86a3468 --- /dev/null +++ b/src/DNX.Extensions/Assemblies/VersionExtensions.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using DNX.Extensions.Validation; + +namespace DNX.Extensions.Assemblies; + +/// +/// Class VersionExtensions. +/// +public static class VersionExtensions +{ + /// + /// Simplifies the Version to the specified minimum positions + /// + /// The version. + /// The minimum positions. + /// System.String. + public static string Simplify(this Version version, int minimumPositions = 1) + { + // TODO: Restore when available + //Guard.IsBetween(() => minimumPositions, 1, 4, IsBetweenBoundsType.IncludeLowerAndUpper); + + if (version == null) + { + return null; + } + + var parts = new List( version.ToString().Split('.')); + + while (parts.Any() && parts.Last().Equals("0") && parts.Count > minimumPositions) + { + parts = parts.Take(parts.Count - 1).ToList(); + } + + var versionText = string.Join(".", parts); + + return versionText; + } +} diff --git a/src/DNX.Extensions/Comparers/ComparerFunc.cs b/src/DNX.Extensions/Comparers/ComparerFunc.cs new file mode 100644 index 0000000..745cec5 --- /dev/null +++ b/src/DNX.Extensions/Comparers/ComparerFunc.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; + +namespace DNX.Extensions.Comparers; + +/// +/// Class ComparerFunc. +/// +/// +/// +public class ComparerFunc : IComparer +{ + private readonly Func _func; + + private ComparerFunc(Func func) + { + _func = func; + } + + /// + /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. + /// + /// The first object to compare. + /// The second object to compare. + /// A signed integer that indicates the relative values of and , as shown in the following table.Value Meaning Less than zero is less than .Zero equals .Greater than zero is greater than . + public int Compare(T x, T y) + { + return _func(x, y); + } + + /// + /// Creates a comparer for the specified type + /// + /// The function. + /// ActionComparer<T>. + public static ComparerFunc Create(Func func) + { + return new ComparerFunc(func); + } +} diff --git a/src/DNX.Extensions/Comparers/EqualityComparerFunc.cs b/src/DNX.Extensions/Comparers/EqualityComparerFunc.cs new file mode 100644 index 0000000..8ad57c7 --- /dev/null +++ b/src/DNX.Extensions/Comparers/EqualityComparerFunc.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; + +namespace DNX.Extensions.Comparers; + +/// +/// Class EqualityComparerFunc. +/// +/// +/// +public class EqualityComparerFunc : IEqualityComparer +{ + private readonly Func _func; + + private EqualityComparerFunc(Func func) + { + _func = func; + } + + /// + /// Determines whether the specified objects are equal. + /// + /// The first object to compare. + /// The second object to compare. + /// true if the specified objects are equal; otherwise, false. + public bool Equals(T x, T y) + { + return _func(x, y); + } + + /// + /// Returns a hash code for this instance. + /// + /// The for which a hash code is to be returned. + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + public int GetHashCode(T obj) + { + return 0; + } + + /// + /// Creates a comparer for the specified type + /// + /// The function. + /// ActionEqualityComparer<T>. + public static EqualityComparerFunc Create(Func func) + { + return new EqualityComparerFunc(func); + } +} diff --git a/src/DNX.Extensions/Comparers/StringComparisonEqualityComparer.cs b/src/DNX.Extensions/Comparers/StringComparisonEqualityComparer.cs index 194ad7a..0a2a7db 100644 --- a/src/DNX.Extensions/Comparers/StringComparisonEqualityComparer.cs +++ b/src/DNX.Extensions/Comparers/StringComparisonEqualityComparer.cs @@ -57,7 +57,7 @@ public bool Equals(string x, string y) public int GetHashCode(string obj) { if (Equals(obj?.ToLowerInvariant(), obj?.ToUpperInvariant())) - return obj?.ToLowerInvariant().GetHashCode() ?? default; + return obj?.ToLowerInvariant().GetHashCode() ?? 0; return obj.GetHashCode(); } diff --git a/src/DNX.Extensions/Conversion/ConvertExtensions.cs b/src/DNX.Extensions/Conversion/ConvertExtensions.cs index 04f8112..d135bae 100644 --- a/src/DNX.Extensions/Conversion/ConvertExtensions.cs +++ b/src/DNX.Extensions/Conversion/ConvertExtensions.cs @@ -3,97 +3,44 @@ namespace DNX.Extensions.Conversion; /// -/// Extensions to simplify type conversion +/// Conversion Extensions. /// public static class ConvertExtensions { /// - /// Converts to string, or default. - /// - /// The object. - /// The default value. - /// System.String. - public static string ToStringOrDefault(this object obj, string defaultValue = "") - { - return obj?.ToString() ?? defaultValue; - } - - /// - /// Converts to boolean. - /// - /// The text. - /// The default value. - /// true/false if can be converted, defaultValue otherwise. - public static bool ToBoolean(this string text, bool defaultValue = default) - { - return bool.TryParse(text, out var value) - ? value - : defaultValue; - } - - /// - /// Converts to int32. - /// - /// The text. - /// The default value. - /// System.Int32. - public static int ToInt32(this string text, int defaultValue = default) - { - return int.TryParse(text, out var value) - ? value - : defaultValue; - } - - /// - /// Converts to enum. + /// Changes the value to the specified type /// /// - /// The text. - /// The default value. + /// The value. /// T. - public static T ToEnum(this string text, T defaultValue = default) - where T : struct + public static T ChangeType(this object value) { - return Enum.TryParse(text, true, out T value) - ? value - : defaultValue; + return (T)ChangeType(value, typeof(T)); } /// - /// Converts to guid. + /// Changes the value to the specified type /// - /// The text. - /// Guid. - public static Guid ToGuid(this string text) - { - return text.ToGuid(Guid.Empty); - } - - /// - /// Converts to guid. - /// - /// The text. - /// The default value. - /// Guid. - public static Guid ToGuid(this string text, Guid defaultValue) + /// The value. + /// The type. + /// object + public static object ChangeType(this object value, Type type) { - return Guid.TryParse(text, out var result) - ? result - : defaultValue; + return Convert.ChangeType(value, type); } /// - /// Converts to the specified type. + /// Changes the value to the specified type, with a default value if conversion fails /// /// - /// The object. + /// The value. /// The default value. /// T. - public static T To(this object obj, T defaultValue = default) + public static T ChangeType(this object value, T defaultValue) { try { - return (T)obj ?? defaultValue; + return ChangeType(value); } catch { diff --git a/src/DNX.Extensions/Conversion/ConvertExtensionsOLD.cs b/src/DNX.Extensions/Conversion/ConvertExtensionsOLD.cs new file mode 100644 index 0000000..9a957d5 --- /dev/null +++ b/src/DNX.Extensions/Conversion/ConvertExtensionsOLD.cs @@ -0,0 +1,105 @@ +using System; + +namespace DNX.Extensions.Conversion; + +/// +/// Extensions to simplify type conversion +/// +public static class ConvertExtensionsOLD +{ + // TODO: These move to specific code-generated classes + + /// + /// Converts to string, or default. + /// + /// The object. + /// The default value. + /// System.String. + public static string ToStringOrDefault(this object obj, string defaultValue = "") + { + return obj?.ToString() ?? defaultValue; + } + + /// + /// Converts to boolean. + /// + /// The text. + /// The default value. + /// true/false the value if it can be converted, defaultValue otherwise. + public static bool ToBoolean(this string text, bool defaultValue = false) + { + return bool.TryParse(text, out var value) + ? value + : defaultValue; + } + + /// + /// Converts to int32. + /// + /// The text. + /// The default value. + /// System.Int32. + public static int ToInt32(this string text, int defaultValue = 0) + { + return int.TryParse(text, out var value) + ? value + : defaultValue; + } + + /// + /// Converts to enum. + /// + /// + /// The text. + /// The default value. + /// T. + public static T ToEnum(this string text, T defaultValue = default) + where T : struct + { + return Enum.TryParse(text, true, out T value) + ? value + : defaultValue; + } + + /// + /// Converts to guid. + /// + /// The text. + /// Guid. + public static Guid ToGuid(this string text) + { + return text.ToGuid(Guid.Empty); + } + + /// + /// Converts to guid. + /// + /// The text. + /// The default value. + /// Guid. + public static Guid ToGuid(this string text, Guid defaultValue) + { + return Guid.TryParse(text, out var result) + ? result + : defaultValue; + } + + /// + /// Converts to the specified type. + /// + /// + /// The object. + /// The default value. + /// T. + public static T To(this object obj, T defaultValue = default) + { + try + { + return (T)obj ?? defaultValue; + } + catch + { + return defaultValue; + } + } +} diff --git a/src/DNX.Extensions/DNX.Extensions.csproj b/src/DNX.Extensions/DNX.Extensions.csproj index 244a8f3..1bb1ec5 100644 --- a/src/DNX.Extensions/DNX.Extensions.csproj +++ b/src/DNX.Extensions/DNX.Extensions.csproj @@ -38,4 +38,8 @@ + + + + diff --git a/src/DNX.Extensions/DateTimes/DateTimeExtensions.cs b/src/DNX.Extensions/DateTimes/DateTimeExtensions.cs index 15370b8..7fda2cd 100644 --- a/src/DNX.Extensions/DateTimes/DateTimeExtensions.cs +++ b/src/DNX.Extensions/DateTimes/DateTimeExtensions.cs @@ -7,6 +7,85 @@ namespace DNX.Extensions.DateTimes; /// public static class DateTimeExtensions { + /// + /// Gets the unix epoch. + /// + /// The unix epoch. + public static DateTime UnixEpoch + { + get { return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); } + } + + /// + /// Parses the date as UTC. + /// + /// The date string. + /// DateTime. + public static DateTime ParseDateAsUtc(this string dateString) + { + var dateTime = DateTime.Parse(dateString); + + var dateTimeUtc = DateTime.SpecifyKind(dateTime, DateTimeKind.Utc); + + return dateTimeUtc; + } + + /// + /// Parses the date as UTC. + /// + /// The date string. + /// The format provider. + /// DateTime. + public static DateTime ParseDateAsUtc(this string dateString, IFormatProvider formatProvider) + { + var dateTime = DateTime.Parse(dateString, formatProvider); + + var dateTimeUtc = DateTime.SpecifyKind(dateTime, DateTimeKind.Utc); + + return dateTimeUtc; + } + + /// + /// Parses the date as UTC. + /// + /// The date string. + /// The default date time. + /// DateTime. + public static DateTime ParseDateAsUtc(this string dateString, DateTime defaultDateTime) + { + try + { + var dateTime = ParseDateAsUtc(dateString); + + return dateTime; + } + catch + { + return DateTime.SpecifyKind(defaultDateTime, DateTimeKind.Utc); + } + } + + /// + /// Parses the date as UTC. + /// + /// The date string. + /// The format provider. + /// The default date time. + /// System.DateTime. + public static DateTime ParseDateAsUtc(this string dateString, IFormatProvider formatProvider, DateTime defaultDateTime) + { + try + { + var dateTime = ParseDateAsUtc(dateString, formatProvider); + + return dateTime; + } + catch + { + return DateTime.SpecifyKind(defaultDateTime, DateTimeKind.Utc); + } + } + /// /// Sets the year. /// diff --git a/src/DNX.Extensions/Defaults.cs b/src/DNX.Extensions/Defaults.cs new file mode 100644 index 0000000..7285977 --- /dev/null +++ b/src/DNX.Extensions/Defaults.cs @@ -0,0 +1,29 @@ +using System.Reflection; + +namespace DNX.Extensions; + +/// +/// Default values +/// +public class Defaults +{ + /// + /// The default property information binding flags for reading via reflection + /// + public const BindingFlags PropertyInfoReaderBindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty; + + /// + /// The default property information binding flags for writing via reflection + /// + public const BindingFlags PropertyInfoWriterBindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty; + + /// + /// The default field information binding flags for reading via reflection + /// + public const BindingFlags FieldInfoReaderBindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetField; + + /// + /// The default field information binding flags for writing via reflection + /// + public const BindingFlags FieldInfoWriterBindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetField; +} diff --git a/src/DNX.Extensions/Enumerations/EnumerationExtensions.cs b/src/DNX.Extensions/Enumerations/EnumerationExtensions.cs new file mode 100644 index 0000000..9a3f2ce --- /dev/null +++ b/src/DNX.Extensions/Enumerations/EnumerationExtensions.cs @@ -0,0 +1,463 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using DNX.Extensions.Exceptions; +using System.Linq; +using DNX.Extensions.Linq; +using DNX.Extensions.Reflection; +using DNX.Extensions.Strings; + +namespace DNX.Extensions.Enumerations; + +/// +/// Enum Extensions +/// +public static class EnumerationExtensions +{ + /// + /// Translate a given string to an enumeration value. + /// May throw a translation exception + /// + /// The enum type to translate to + /// The string representation of an enum value of T + /// The result + public static T ParseEnum(this string item) + where T : struct + { + return ParseEnum(item, false); + } + + /// + /// Translate a given string to an enumeration value. + /// May throw a translation exception + /// + /// The enum type to translate to + /// The string representation of an enum value of T + /// if set to true ignore the case of item. + /// The result + /// + public static T ParseEnum(this string item, bool ignoreCase) + where T : struct + { + if (typeof(T).IsEnum == false) + { + throw new EnumTypeException(typeof(T)); + } + + return (T)Enum.Parse(typeof(T), item, ignoreCase); + } + + /// + /// Attempt to safely translate a string to an enumeration value. + /// If translation is not possible return a default value + /// + /// The enum type to translate to + /// The string representation of an enum value of T + /// The default value to return if translation cannot complete + /// T. + /// + public static T ParseEnumOrDefault(this string item, T defaultValue) + where T : struct + { + return ParseEnumOrDefault(item, false, defaultValue); + } + + /// + /// Attempt to safely translate a string to an enumeration value. + /// If translation is not possible return a default value + /// + /// The enum type to translate to + /// The string representation of an enum value of T + /// if set to true [ignore case]. + /// The default value to return if translation cannot complete + /// T. + /// + public static T ParseEnumOrDefault(this string item, bool ignoreCase, T defaultValue) + where T : struct + { + if (typeof(T).IsEnum == false) + { + throw new EnumTypeException(typeof(T)); + } + + try + { + return (T)Enum.Parse(typeof(T), item, ignoreCase); + } + catch + { + return defaultValue; + } + } + + /// + /// Gets the specified attributes from an enum value + /// + /// + /// The value. + /// IList<T>. + public static IList GetAttributes(this Enum value) + { + return value.GetAttributes(false); + } + + /// + /// Gets the specified attributes from an enum value + /// + /// + /// The value. + /// if set to true [inherit]. + /// IList<T>. + public static IList GetAttributes(this Enum value, bool inherit) + { + var type = value.GetType(); + + var memInfo = type.GetMember(value.ToString()) + .SingleOrDefault(); + + var attributes = memInfo?.GetMemberAttributes(inherit); + + return attributes; + } + + /// + /// Gets the specified attribute from an enum value + /// + /// + /// The value. + /// T. + public static T GetAttribute(this Enum value) + { + return value.GetAttribute(false); + } + + /// + /// Gets the specified attribute from an enum value + /// + /// + /// The value. + /// if set to true [inherit]. + /// T. + public static T GetAttribute(this Enum value, bool inherit) + { + var attributes = value.GetAttributes(inherit); + + return attributes.FirstOrDefault(); + } + + /// + /// Gets the description property from the Description attribute from an enum value + /// + /// The value. + /// System.String. + public static string GetDescription(this Enum value) + { + return value.GetDescription(false); + } + + /// + /// Gets the description property from the Description attribute from an enum value + /// + /// The value. + /// if set to true [inherit]. + /// System.String. + public static string GetDescription(this Enum value, bool inherit) + { + var descriptionAttributes = value.GetAttributes(inherit); + + return descriptionAttributes.HasAny() + ? descriptionAttributes.First().Description + : null; + } + + /// + /// Gets the description of an enum value via Attribute or Name + /// + /// The value. + /// System.String. + public static string GetDescriptionOrName(this Enum value) + { + return value.GetDescriptionOrName(false); + } + /// + /// + /// The Enumeration + /// Whether inherited classes are interrogated + /// A string representing the friendly name + public static string GetDescriptionOrName(this Enum value, bool inherit) + { + return value.GetDescription() + .CoalesceNullWith(value.ToString()); + } + + /// + /// Determines whether the specified enum value is a valid enum name. + /// + /// + /// The enum value. + /// true if the specified value is valid; otherwise, false. + public static bool IsValidEnum(this T value) + where T : struct + { + return Convert.ToString(value).IsValidEnum(); + } + + /// + /// Determines whether the specified enum value is a valid enum name. + /// + /// + /// The enum Name. + /// true if the specified value is valid; otherwise, false. + public static bool IsValidEnum(this string value) + where T : struct + { + return IsValidEnum(value, typeof(T), false); + } + + /// + /// Determines whether the specified enum value is a valid enum name. + /// + /// + /// The value. + /// if set to true [ignore case]. + /// true if [is valid enum] [the specified ignore case]; otherwise, false. + public static bool IsValidEnum(this string value, bool ignoreCase) + where T : struct + { + return IsValidEnum(value, typeof(T), ignoreCase); + } + + /// + /// Determines whether the text is a valid enum value of the specified enum type + /// + /// The value. + /// The type. + /// if set to true [ignore case]. + /// true if [is valid enum] [the specified type]; otherwise, false. + /// type + /// + public static bool IsValidEnum(this string value, Type type, bool ignoreCase) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (!type.IsEnum) + { + throw new EnumTypeException(type); + } + + var comparison = ignoreCase + ? StringComparison.CurrentCultureIgnoreCase + : StringComparison.CurrentCulture; + + var names = Enum.GetNames(type) + .ToList(); + + return names.Any(n => n.Equals(value, comparison)); + } + + /// + /// Gets the max enum value. + /// + /// + /// T. + /// + public static T GetMaxValue() + where T : struct + { + if (!typeof(T).IsEnum) + { + throw new EnumTypeException(typeof(T)); + } + + return Enum.GetValues(typeof(T)) + .Cast() + .Max(); + } + + /// + /// Gets the min enum value. + /// + /// + /// T. + /// + public static T GetMinValue() + where T : struct + { + if (!typeof(T).IsEnum) + { + throw new EnumTypeException(typeof(T)); + } + + return Enum.GetValues(typeof(T)) + .Cast() + .Min(); + } + + /// + /// Determines whether [is value one of] [the specified args]. + /// + /// + /// The value. + /// The allowed. + /// true if [is value one of] [the specified args]; otherwise, false. + public static bool IsValueOneOf(this T value, params T[] allowed) + where T : struct + { + return value.IsValueOneOf(allowed.ToList()); + } + + /// + /// Determines whether [is value one of] [the specified args]. + /// + /// + /// The value. + /// The allowed. + /// true if [is value one of] [the specified args]; otherwise, false. + /// + public static bool IsValueOneOf(this T value, IList allowed) + where T : struct + { + if (!typeof(T).IsEnum) + { + throw new EnumTypeException(typeof(T)); + } + + return allowed.Contains(value); + } + + /// + /// Sets the flag. + /// + /// + /// The value. + /// The flag. + /// if set to true [set]. + /// T. + public static T ManipulateFlag(this Enum value, T flag, bool set) + { + var underlyingType = Enum.GetUnderlyingType(value.GetType()); + + // note: AsInt mean: math integer vs enum (not the c# int type) + dynamic valueAsInt = Convert.ChangeType(value, underlyingType); + dynamic flagAsInt = Convert.ChangeType(flag, underlyingType); + + if (set) + { + valueAsInt |= flagAsInt; + } + else + { + valueAsInt &= ~flagAsInt; + } + return (T)valueAsInt; + } + + /// + /// Sets the flag. + /// + /// + /// The value. + /// The flag. + /// T. + public static T SetFlag(this Enum value, T flag) + { + return ManipulateFlag(value, flag, true); + } + + /// + /// Unsets the flag. + /// + /// + /// The value. + /// The flag. + /// T. + public static T UnsetFlag(this Enum value, T flag) + { + return ManipulateFlag(value, flag, false); + } + + /// + /// Gets the set values list. + /// + /// + /// The enum value. + /// List<T>. + /// + public static List GetSetValuesList(this Enum enumValue) + where T : struct + { + if (!typeof(T).IsEnum) + { + throw new EnumTypeException(typeof(T)); + } + + var list = new List(); + + foreach (var name in Enum.GetNames(typeof(T))) + { + var nameValue = (Enum)Enum.Parse(typeof(T), name); + + if ((enumValue.HasFlag(nameValue))) + { + var actualValue = (T)Enum.Parse(typeof(T), name); + + list.Add(actualValue); + } + } + + return list; + } + + /// + /// Converts the entire enum to a dictionary with Name as the key + /// + /// + /// IDictionary<System.String, T>. + /// + public static IDictionary ToDictionaryByName() + where T : struct + { + if (!typeof(T).IsEnum) + { + throw new EnumTypeException(typeof(T)); + } + + var dictionary = Enum.GetValues(typeof(T)) + .Cast() + .Distinct() + .ToDictionary( + t => t.ToString(), + t => t + ); + + return dictionary; + } + + /// + /// Converts the entire enum to a dictionary with Value as the key + /// + /// + /// IDictionary<T, System.String>. + /// + public static IDictionary ToDictionaryByValue() + where T : struct + { + if (!typeof(T).IsEnum) + { + throw new EnumTypeException(typeof(T)); + } + + var dictionary = Enum.GetValues(typeof(T)) + .Cast() + .Distinct() + .ToDictionary( + t => t, + t => t.ToString() + ); + + return dictionary; + } +} diff --git a/src/DNX.Extensions/Enums/EnumExtensions.cs b/src/DNX.Extensions/Enums/EnumExtensions.cs deleted file mode 100644 index 149107d..0000000 --- a/src/DNX.Extensions/Enums/EnumExtensions.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.ComponentModel; - -namespace DNX.Extensions.Enums; - -/// -/// Enum Extensions -/// -public static class EnumExtensions -{ - /// - /// Gets the attribute. - /// - /// - /// The en. - /// - public static T GetAttribute(this Enum en) - { - var type = en.GetType(); - - var memInfo = type.GetMember(en.ToString()); - - if (memInfo.Length > 0) - { - var attrs = memInfo[0].GetCustomAttributes(typeof(T), false); - - if (attrs.Length > 0) - { - return (T)attrs[0]; - } - } - - return default; - } - - /// - /// Retrieve the description on the enum, e.g. - /// [Description("Bright Pink")] - /// BrightPink = 2, - /// Then when you pass in the enum, it will retrieve the description - /// - /// The Enumeration - /// A string representing the friendly name - public static string GetDescription(this Enum en) - { - var attr = en.GetAttribute(); - - return attr == null - ? en.ToString() - : attr.Description; - } -} diff --git a/src/DNX.Extensions/Exceptions/ConversionException.cs b/src/DNX.Extensions/Exceptions/ConversionException.cs new file mode 100644 index 0000000..22653c8 --- /dev/null +++ b/src/DNX.Extensions/Exceptions/ConversionException.cs @@ -0,0 +1,46 @@ +using System; + +namespace DNX.Extensions.Exceptions; + +/// +/// Conversion Exception. +/// +/// +/// Thrown when a conversion to a specified type fails +public class ConversionException : Exception +{ + /// + /// Gets the value. + /// + /// The value. + public string Value { get; private set; } + + /// + /// Gets the type of the convert. + /// + /// The type of the convert. + public Type ConvertType { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The value. + /// The message. + public ConversionException(string value, string message) + : this(value, message, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The value. + /// The message. + /// The type. + public ConversionException(string value, string message, Type type) + : base(message) + { + Value = value; + ConvertType = type; + } +} diff --git a/src/DNX.Extensions/Exceptions/EnumTypeException.cs b/src/DNX.Extensions/Exceptions/EnumTypeException.cs new file mode 100644 index 0000000..ee3d18d --- /dev/null +++ b/src/DNX.Extensions/Exceptions/EnumTypeException.cs @@ -0,0 +1,42 @@ +using System; + +namespace DNX.Extensions.Exceptions; + +/// +/// EnumTypeException. +/// +/// +public class EnumTypeException : Exception +{ + /// + /// The message template + /// + private const string MessageTemplate = "{0} is not an enumeration type"; + + /// + /// The type the exception was thrown for + /// + /// The type. + public Type Type { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The type. + /// Uses the default message template + public EnumTypeException(Type type) + : this(type, MessageTemplate) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The type. + /// The message template. + public EnumTypeException(Type type, string messageTemplate) + : base(string.Format(messageTemplate, type.Name)) + { + Type = type; + } +} diff --git a/src/DNX.Extensions/Exceptions/EnumValueException.cs b/src/DNX.Extensions/Exceptions/EnumValueException.cs new file mode 100644 index 0000000..604c327 --- /dev/null +++ b/src/DNX.Extensions/Exceptions/EnumValueException.cs @@ -0,0 +1,48 @@ +using System; + +namespace DNX.Extensions.Exceptions; + +/// +/// Class EnumValueException. +/// +/// +public class EnumValueException : Exception +{ + /// + /// The message template + /// + private const string MessageTemplate = "{0} is not a valid {1} value"; + + /// + /// Gets the type. + /// + /// The type. + public Type Type { get; private set; } + + /// + /// Gets the value. + /// + /// The value. + public T Value { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The value. + public EnumValueException(T value) + : this(value, MessageTemplate) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The value. + /// The message template. + public EnumValueException(T value, string messageTemplate) + : base(string.Format(messageTemplate, value, typeof(T).Name)) + { + Type = typeof(T); + Value = value; + } +} diff --git a/src/DNX.Extensions/Exceptions/ExceptionExtensions.cs b/src/DNX.Extensions/Exceptions/ExceptionExtensions.cs new file mode 100644 index 0000000..7cc1f38 --- /dev/null +++ b/src/DNX.Extensions/Exceptions/ExceptionExtensions.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; + +namespace DNX.Extensions.Exceptions; + +/// +/// Exception Extensions. +/// +public static class ExceptionExtensions +{ + /// + /// Gets the list of messages from an Exception and inner exceptions + /// + /// The ex. + /// IList<System.String>. + /// Also available as an extension method + public static IList GetMessageList(this Exception ex) + { + var list = new List(); + + while (ex != null) + { + list.Add(ex.Message); + + ex = ex.InnerException; + } + + return list; + } + + /// + /// Gets the list of messages from an Exception and inner exceptions + /// + /// The ex. + /// IList<System.String>. + /// Also available as an extension method + public static IList GetExceptionList(this Exception ex) + { + var list = new List(); + + while (ex != null) + { + list.Add(ex); + + ex = ex.InnerException; + } + + return list; + } +} diff --git a/src/DNX.Extensions/Exceptions/ParameterException.cs b/src/DNX.Extensions/Exceptions/ParameterException.cs new file mode 100644 index 0000000..9858839 --- /dev/null +++ b/src/DNX.Extensions/Exceptions/ParameterException.cs @@ -0,0 +1,72 @@ +using System; + +namespace DNX.Extensions.Exceptions; + +/// +/// An exception for idenifying issues with expected parameters +/// +/// +public class ParameterException : Exception +{ + /// + /// The Parameter Name + /// + /// The name of the parameter. + public string ParamName { get; private set; } + + /// + /// The value specified for the Parameter + /// + /// The parameter value. + public object ParamValue { get; private set; } + + /// + /// Create a ParameterException with a parameter name and a message + /// + /// Name of the parameter. + /// The message. + public ParameterException(string paramName, string message) + : base(message) + { + ParamName = paramName; + } + + /// + /// Create a ParameterException with a parameter name and value, and a message + /// + /// Name of the parameter. + /// The parameter value. + /// The message. + public ParameterException(string paramName, object paramValue, string message) + : base(message) + { + ParamName = paramName; + ParamValue = paramValue; + } + + /// + /// Create a ParameterException with a parameter name, message and inner Exception + /// + /// Name of the parameter. + /// The message. + /// The inner exception. + public ParameterException(string paramName, string message, Exception innerException) + : base(message, innerException) + { + ParamName = paramName; + } + + /// + /// Create a ParameterException with a parameter name and value, message and inner Exception + /// + /// Name of the parameter. + /// The parameter value. + /// The message. + /// The inner exception. + public ParameterException(string paramName, object paramValue, string message, Exception innerException) + : base(message, innerException) + { + ParamName = paramName; + ParamValue = paramValue; + } +} diff --git a/src/DNX.Extensions/Exceptions/ParameterInvalidException.cs b/src/DNX.Extensions/Exceptions/ParameterInvalidException.cs new file mode 100644 index 0000000..d6ab88e --- /dev/null +++ b/src/DNX.Extensions/Exceptions/ParameterInvalidException.cs @@ -0,0 +1,54 @@ +using System; + +namespace DNX.Extensions.Exceptions; + +/// +/// An exception for idenifying an invalid value issue with expected parameters +/// +/// +public class ParameterInvalidException : ParameterException +{ + /// + /// Create a ParameterInvalidException with a parameter name and a message + /// + /// Name of the parameter. + /// The message. + public ParameterInvalidException(string paramName, string message) + : base(paramName, message) + { + } + + /// + /// Create a ParameterInvalidException with a parameter name and value, and a message + /// + /// Name of the parameter. + /// The parameter value. + /// The message. + public ParameterInvalidException(string paramName, object paramValue, string message) + : base(paramName, paramValue, message) + { + } + + /// + /// Create a ParameterInvalidException with a parameter name, message and inner Exception + /// + /// Name of the parameter. + /// The message. + /// The inner exception. + public ParameterInvalidException(string paramName, string message, Exception innerException) + : base(paramName, message, innerException) + { + } + + /// + /// Create a ParameterInvalidException with a parameter name and value, message and inner Exception + /// + /// Name of the parameter. + /// The parameter value. + /// The message. + /// The inner exception. + public ParameterInvalidException(string paramName, object paramValue, string message, Exception innerException) + : base(paramName, paramValue, message, innerException) + { + } +} diff --git a/src/DNX.Extensions/Exceptions/ReadOnlyListException.cs b/src/DNX.Extensions/Exceptions/ReadOnlyListException.cs new file mode 100644 index 0000000..113afee --- /dev/null +++ b/src/DNX.Extensions/Exceptions/ReadOnlyListException.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; + +namespace DNX.Extensions.Exceptions; + +/// +/// Class ReadOnlyListException. +/// +/// +/// +public class ReadOnlyListException : Exception +{ + /// + /// Gets the list. + /// + /// The list. + public IList List { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The list. + public ReadOnlyListException(IList list) + { + List = list; + } +} diff --git a/src/DNX.Extensions/Interfaces/IConverter.cs b/src/DNX.Extensions/Interfaces/IConverter.cs new file mode 100644 index 0000000..154c7bc --- /dev/null +++ b/src/DNX.Extensions/Interfaces/IConverter.cs @@ -0,0 +1,16 @@ +namespace DNX.Extensions.Interfaces; + +/// +/// Converter Interface for transposing objects between 2 types +/// +/// The type of the t1. +/// The type of the t2. +public interface IConverter +{ + /// + /// Converts the specified source. + /// + /// The source. + /// T2. + T2 Convert(T1 source); +} diff --git a/src/DNX.Extensions/Interfaces/IExtractable.cs b/src/DNX.Extensions/Interfaces/IExtractable.cs new file mode 100644 index 0000000..f52dc4d --- /dev/null +++ b/src/DNX.Extensions/Interfaces/IExtractable.cs @@ -0,0 +1,14 @@ +namespace DNX.Extensions.Interfaces; + +/// +/// Extractable Interface for populating another instance from this one +/// +/// The type of the t1. +public interface IExtractable +{ + /// + /// Extracts this object into another + /// + /// The target. + void ExtractInto(T1 target); +} diff --git a/src/DNX.Extensions/Interfaces/IPopulatable.cs b/src/DNX.Extensions/Interfaces/IPopulatable.cs new file mode 100644 index 0000000..8c847d8 --- /dev/null +++ b/src/DNX.Extensions/Interfaces/IPopulatable.cs @@ -0,0 +1,14 @@ +namespace DNX.Extensions.Interfaces; + +/// +/// Populatable Interface for populating this object from another +/// +/// The type of the t1. +public interface IPopulatable +{ + /// + /// Populates from an instance of T1 + /// + /// The source. + void PopulateFrom(T1 source); +} diff --git a/src/DNX.Extensions/Linq/DictionaryExtensions.cs b/src/DNX.Extensions/Linq/DictionaryExtensions.cs new file mode 100644 index 0000000..fc97595 --- /dev/null +++ b/src/DNX.Extensions/Linq/DictionaryExtensions.cs @@ -0,0 +1,234 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using DNX.Extensions.Exceptions; +using DNX.Extensions.Validation; + +// ReSharper disable InconsistentNaming + +namespace DNX.Extensions.Linq; + +/// +/// Dictionary Extensions. +/// +public static class DictionaryExtensions +{ + /// + /// Sets the value. + /// + /// The type of the tk. + /// The type of the tv. + /// The dictionary. + /// Name of the key. + /// The value. + /// dictionary or keyName + public static void SetValue(this IDictionary dictionary, TK keyName, TV value) + { + Guard.IsNotNull(() => dictionary); + + if (keyName == null) + { + throw new ArgumentNullException(nameof(keyName)); + } + + if (dictionary.ContainsKey(keyName)) + { + dictionary[keyName] = value; + } + else + { + dictionary.Add(keyName, value); + } + } + + /// + /// Gets the value. + /// + /// The type of the tk. + /// The type of the tv. + /// The dictionary. + /// Name of the key. + /// The default value. + /// TV. + /// dictionary or keyName + public static TV GetValue(this IDictionary dictionary, TK keyName, TV defaultValue = default(TV)) + { + Guard.IsNotNull(() => dictionary); + + if (keyName == null) + { + throw new ArgumentNullException(nameof(keyName)); + } + + return dictionary.TryGetValue(keyName, out var value) + ? value + : defaultValue; + } + + /// + /// Renames the key. + /// + /// + /// The dictionary. + /// Name of from key. + /// Name of to key. + /// fromKeyName or toKeyName + public static void RenameKey(this IDictionary dictionary, string fromKeyName, string toKeyName) + { + if (fromKeyName == null) + { + throw new ArgumentNullException(nameof(fromKeyName)); + } + if (toKeyName == null) + { + throw new ArgumentNullException(nameof(toKeyName)); + } + + if (dictionary == null || !dictionary.ContainsKey(fromKeyName) || dictionary.ContainsKey(toKeyName)) + { + return; + } + + var old = dictionary[fromKeyName]; + dictionary.Remove(fromKeyName); + dictionary.SetValue(toKeyName, old); + } + + /// + /// Merges this dictionary with another one + /// + /// The type of the tk. + /// The type of the tv. + /// The dictionary. + /// The other. + /// The merge technique. + /// Dictionary<TK, TV>. + /// Source and target dictionaries are left untouched + public static IDictionary MergeWith(this IDictionary dict, IDictionary other, MergeTechnique mergeTechnique = MergeTechnique.Unique) + { + var result = Merge(mergeTechnique, dict, other); + + return result; + } + + /// + /// Merges dictionaries + /// + /// The type of the tk. + /// The type of the tv. + /// The merge technique. + /// The dictionaries. + /// Dictionary<TK, TV>. + /// Invalid or unsupported Merge Technique - mergeTechnique + public static IDictionary Merge(MergeTechnique mergeTechnique, params IDictionary[] dictionaries) + { + return mergeTechnique switch + { + MergeTechnique.Unique => MergeUnique(dictionaries), + MergeTechnique.TakeFirst => MergeFirst(dictionaries), + MergeTechnique.TakeLast => MergeLast(dictionaries), + _ => throw new EnumValueException(mergeTechnique) + }; + } + + /// + /// Merges dictionaries assuming all keys are unique + /// + /// The type of the tk. + /// The type of the tv. + /// The dictionaries. + /// Dictionary<TK, TV>. + public static IDictionary MergeUnique(params IDictionary[] dictionaries) + { + var dict = dictionaries.SelectMany(d => d) + .ToDictionary(pair => pair.Key, pair => pair.Value); + + return dict; + } + + /// + /// Merges dictionaries using the first found key value + /// + /// The type of the tk. + /// The type of the tv. + /// The dictionaries. + /// Dictionary<TK, TV>. + public static IDictionary MergeFirst(params IDictionary[] dictionaries) + { + var result = dictionaries.SelectMany(dict => dict) + .ToLookup(pair => pair.Key, pair => pair.Value) + .ToDictionary(group => group.Key, group => group.First()); + + return result; + } + + /// + /// Merges dictionaries using the last found key value + /// + /// The type of the tk. + /// The type of the tv. + /// The dictionaries. + /// Dictionary<TK, TV>. + public static IDictionary MergeLast(params IDictionary[] dictionaries) + { + var result = dictionaries.SelectMany(dict => dict) + .ToLookup(pair => pair.Key, pair => pair.Value) + .ToDictionary(group => group.Key, group => group.Last()); + + return result; + } + + /// + /// Extension method that turns a dictionary of string and object to an ExpandoObject + /// + public static ExpandoObject ToExpando(this IDictionary dictionary) + { + // TODO: Should really use AsDictionary + + var expando = new ExpandoObject(); + IDictionary expandoDict = expando; + + // go through the items in the dictionary and copy over the key value pairs) + foreach (var kvp in dictionary) + { + switch (kvp.Value) + { + // if the value can also be turned into an ExpandoObject, then do it! + case IDictionary valueDictionary: + { + var expandoValue = valueDictionary.ToExpando(); + expandoDict.Add(kvp.Key, expandoValue); + break; + } + case ICollection valueCollection: + { + // iterate through the collection and convert any strin-object dictionaries + // along the way into expando objects + var itemList = new List(); + foreach (var item in valueCollection) + { + if (item is IDictionary itemDictionary) + { + var expandoItem = itemDictionary.ToExpando(); + itemList.Add(expandoItem); + } + else + { + itemList.Add(item); + } + } + + expandoDict.Add(kvp.Key, itemList); + break; + } + default: + expandoDict.Add(kvp.Key, kvp.Value); + break; + } + } + + return expando; + } +} diff --git a/src/DNX.Extensions/Linq/EnumerableExtensions.cs b/src/DNX.Extensions/Linq/EnumerableExtensions.cs index ba40738..7a1d6d0 100644 --- a/src/DNX.Extensions/Linq/EnumerableExtensions.cs +++ b/src/DNX.Extensions/Linq/EnumerableExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using DNX.Extensions.Comparers; // ReSharper disable PossibleMultipleEnumeration @@ -92,6 +93,72 @@ public static bool IsOneOf(this T input, IEnumerable candidates, IEquality return candidates != null && candidates.Any() && candidates.Contains(input, comparer); } + /// + /// Determines whether the specified value is in the list. + /// + /// + /// The value. + /// The list. + /// true if the list is not null and value is in the list; otherwise, false. + public static bool IsIn(this T value, params T[] list) + { + return value.IsIn(list?.ToList()); + } + + /// + /// Determines whether the specified value is in the list. + /// + /// + /// The value. + /// The list. + /// The comparer. + /// The value to return if the list is null + /// true if the value is in the list; otherwise, false. + public static bool IsIn(this T value, IList list, IEqualityComparer comparer = null, bool treatNullListAs = false) + { + if (list == null) + { + return treatNullListAs; + } + + return comparer == null + ? list.Contains(value) + : list.Contains(value, comparer); + } + + /// + /// Determines whether the specified value is not in the list. + /// + /// + /// The value. + /// The list. + /// true if the list is not null and value is not in the list; otherwise, false. + public static bool IsNotIn(this T value, params T[] list) + { + return value.IsNotIn(list?.ToList()); + } + + /// + /// Determines whether the specified value is not in the list. + /// + /// + /// The value. + /// The list. + /// The comparer. + /// The value to return if the list is null + /// true if the value is not in the list; otherwise, false. + public static bool IsNotIn(this T value, IList list, IEqualityComparer comparer = null, bool treatNullListAs = false) + { + if (list == null) + { + return treatNullListAs; + } + + return comparer == null + ? !list.Contains(value) + : !list.Contains(value, comparer); + } + /// /// Gets the random item. /// @@ -126,4 +193,45 @@ public static T GetAt(this IList items, int index) ? items[index] : default; } + + /// + /// Converts an Enumerable to a List or to an empty list if null + /// + /// + /// The enumerable. + /// IList<T>. + public static IList ToConcreteList(this IEnumerable enumerable) + { + return enumerable == null + ? [] + : enumerable.ToList(); + } + + /// + /// Appends the specified item instance. + /// + /// + /// The enumerable. + /// The instance. + /// IEnumerable<T>. + public static IEnumerable AppendItem(this IEnumerable enumerable, T instance) + { + var falseComparer = EqualityComparerFunc.Create((arg1, arg2) => false); + + return enumerable.AppendItem(instance, falseComparer); + } + + /// + /// Appends the specified instance. + /// + /// + /// The enumerable. + /// The instance. + /// The comparer. + /// IEnumerable<T>. + public static IEnumerable AppendItem(this IEnumerable enumerable, T instance, IEqualityComparer comparer) + { + return (enumerable ?? []) + .Union([instance], comparer); + } } diff --git a/src/DNX.Extensions/Linq/ListExtensions.cs b/src/DNX.Extensions/Linq/ListExtensions.cs new file mode 100644 index 0000000..407fec2 --- /dev/null +++ b/src/DNX.Extensions/Linq/ListExtensions.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using DNX.Extensions.Exceptions; + +namespace DNX.Extensions.Linq; + +/// +/// List Extensions. +/// +public static class ListExtensions +{ + /// + /// Determines whether the index is valid for the list + /// + /// + /// The list. + /// The index. + /// true if [is index valid] [the specified index]; otherwise, false. + public static bool IsIndexValid(this IList list, int index) + { + return list.HasAny() && index >= 0 && index < list.Count; + } + + /// + /// Gets the absolute index value for the list. + /// + /// + /// The list. + /// The index. + /// System.Int32. + public static int GetAbsoluteIndex(this IList list, int index) + { + if (list.HasAny()) + { + while (index < 0) + { + index = (list.Count + index) % list.Count; + } + } + + return index; + } + + /// + /// Gets at. + /// + /// + /// The list. + /// The index. + /// The default value. + /// T. + public static T GetAt(this IList list, int index, T defaultValue = default(T)) + { + index = list.GetAbsoluteIndex(index); + + return !list.IsIndexValid(index) + ? defaultValue + : list[index]; + } + + /// + /// Moves an item to the new specified index + /// + /// + /// The list. + /// The old index. + /// The new index. + /// + /// oldIndex + /// or + /// newIndex + /// + /// + public static void Move(this IList list, int oldIndex, int newIndex) + { + oldIndex = list.GetAbsoluteIndex(oldIndex); + newIndex = list.GetAbsoluteIndex(newIndex); + + if (!list.IsIndexValid(oldIndex)) + { + throw new ArgumentOutOfRangeException(nameof(oldIndex)); + } + + if (!list.IsIndexValid(newIndex)) + { + throw new ArgumentOutOfRangeException(nameof(newIndex)); + } + + if (list.IsReadOnly) + { + throw new ReadOnlyListException(list); + } + + var item = list[oldIndex]; + list.RemoveAt(oldIndex); + list.Insert(newIndex, item); + } + + /// + /// Swaps the items at the 2 specified indexes + /// + /// + /// The list. + /// The old index. + /// The new index. + /// + /// oldIndex + /// or + /// newIndex + /// + /// + public static void Swap(this IList list, int oldIndex, int newIndex) + { + oldIndex = list.GetAbsoluteIndex(oldIndex); + newIndex = list.GetAbsoluteIndex(newIndex); + + if (!list.IsIndexValid(oldIndex)) + { + throw new ArgumentOutOfRangeException(nameof(oldIndex)); + } + + if (!list.IsIndexValid(newIndex)) + { + throw new ArgumentOutOfRangeException(nameof(newIndex)); + } + + if (list.IsReadOnly) + { + throw new ReadOnlyListException(list); + } + + (list[newIndex], list[oldIndex]) = (list[oldIndex], list[newIndex]); + } + + /// + /// Create a list from an arbitrary supplied list of arguments + /// + /// + /// + /// + public static IList CreateList(params T[] values) + { + return values.ToConcreteList(); + } +} diff --git a/src/DNX.Extensions/Linq/MergeTechnique.cs b/src/DNX.Extensions/Linq/MergeTechnique.cs new file mode 100644 index 0000000..5b08dc4 --- /dev/null +++ b/src/DNX.Extensions/Linq/MergeTechnique.cs @@ -0,0 +1,23 @@ +namespace DNX.Extensions.Linq; + +/// +/// MergeTechnique +/// +/// The technique to use when merging dictionaries +public enum MergeTechnique +{ + /// + /// All keys must be unique + /// + Unique = 1, + + /// + /// When keys clash, take the first found key value + /// + TakeFirst = 2, + + /// + /// When keys clash, take the last found key value + /// + TakeLast = 3 +} diff --git a/src/DNX.Extensions/Linq/NameValueExtensions.cs b/src/DNX.Extensions/Linq/NameValueExtensions.cs new file mode 100644 index 0000000..d4cdfa6 --- /dev/null +++ b/src/DNX.Extensions/Linq/NameValueExtensions.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; + +namespace DNX.Extensions.Linq; + +/// +/// Class NameValueExtensions. +/// +public static class NameValueExtensions +{ + /// + /// Create a Dictionary from a NameValueCollection + /// + /// The collection. + /// The merge technique. + /// IDictionary<System.String, System.String>. + public static IDictionary ToDictionary(this NameValueCollection collection, MergeTechnique mergeTechnique) + { + var dictionaries = new List>(); + + for (var i=0; i < collection.Count; ++i) + { + var key = collection.GetKey(i); + var values = collection.GetValues(i) ?? Enumerable.Empty(); + + foreach (var value in values) + { + var dict = new Dictionary() + { + { key, value } + }; + + dictionaries.Add(dict); + } + } + + return DictionaryExtensions.Merge(mergeTechnique, dictionaries.ToArray()); + } +} diff --git a/src/DNX.Extensions/Linq/TupleExtensions.cs b/src/DNX.Extensions/Linq/TupleExtensions.cs new file mode 100644 index 0000000..62787fb --- /dev/null +++ b/src/DNX.Extensions/Linq/TupleExtensions.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; + +namespace DNX.Extensions.Linq; + +/// +/// Class TupleListExtensions. +/// +public static class TupleExtensions +{ + /// + /// Adds the specified item1. + /// + /// The type of the t1. + /// The type of the t2. + /// The list. + /// The item1. + /// The item2. + public static void Add(this IList> list, T1 item1, T2 item2) + { + list.Add(Tuple.Create(item1, item2)); + } + + /// + /// Adds the specified item1. + /// + /// The type of the t1. + /// The type of the t2. + /// The type of the t3. + /// The list. + /// The item1. + /// The item2. + /// The item3. + public static void Add(this IList> list, T1 item1, T2 item2, T3 item3) + { + list.Add(Tuple.Create(item1, item2, item3)); + } + + /// + /// Adds the specified item1. + /// + /// The type of the t1. + /// The type of the t2. + /// The type of the t3. + /// The type of the t4. + /// The list. + /// The item1. + /// The item2. + /// The item3. + /// The item4. + public static void Add(this IList> list, T1 item1, T2 item2, T3 item3, T4 item4) + { + list.Add(Tuple.Create(item1, item2, item3, item4)); + } + + /// + /// Adds the specified item1. + /// + /// The type of the t1. + /// The type of the t2. + /// The type of the t3. + /// The type of the t4. + /// The type of the t5. + /// The list. + /// The item1. + /// The item2. + /// The item3. + /// The item4. + /// The item5. + public static void Add(this IList> list, T1 item1, T2 item2, T3 item3, T4 item4, T5 item5) + { + list.Add(Tuple.Create(item1, item2, item3, item4, item5)); + } + + /// + /// Adds the specified item1. + /// + /// The type of the t1. + /// The type of the t2. + /// The type of the t3. + /// The type of the t4. + /// The type of the t5. + /// The type of the t6. + /// The list. + /// The item1. + /// The item2. + /// The item3. + /// The item4. + /// The item5. + /// The item6. + public static void Add(this IList> list, T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6) + { + list.Add(Tuple.Create(item1, item2, item3, item4, item5, item6)); + } +} diff --git a/src/DNX.Extensions/Linq/TupleList.cs b/src/DNX.Extensions/Linq/TupleList.cs new file mode 100644 index 0000000..d36be7a --- /dev/null +++ b/src/DNX.Extensions/Linq/TupleList.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; + +// +// Class TupleExtensions. +// +// To allow Tuple initializers +// See: https://stackoverflow.com/questions/8002455/how-to-easily-initialize-a-list-of-tuples +namespace DNX.Extensions.Linq; + +/// +/// +/// Class TupleList to allow Tuple initializers +/// +/// The type of the t1. +/// The type of the t2. +/// +public class TupleList : List> +{ + /// + /// Adds the specified item. + /// + /// The item. + /// The item2. + public void Add(T1 item, T2 item2) + { + Add(new Tuple(item, item2)); + } +} + +/// +/// +/// Class TupleList to allow Tuple initializers +/// +/// The type of the t1. +/// The type of the t2. +/// The type of the t3. +/// +public class TupleList : List> +{ + /// + /// Adds the specified item. + /// + /// The item. + /// The item2. + /// The item3. + public void Add(T1 item, T2 item2, T3 item3) + { + Add(new Tuple(item, item2, item3)); + } +} + +/// +/// +/// Class TupleList to allow Tuple initializers +/// +/// The type of the t1. +/// The type of the t2. +/// The type of the t3. +/// The type of the t4. +/// +public class TupleList : List> +{ + /// + /// Adds the specified item. + /// + /// The item. + /// The item2. + /// The item3. + /// The item4. + public void Add(T1 item, T2 item2, T3 item3, T4 item4) + { + Add(new Tuple(item, item2, item3, item4)); + } +} + +/// +/// +/// Class TupleList to allow Tuple initializers +/// +/// The type of the t1. +/// The type of the t2. +/// The type of the t3. +/// The type of the t4. +/// The type of the t5. +/// +public class TupleList : List> +{ + /// + /// Adds the specified item. + /// + /// The item. + /// The item2. + /// The item3. + /// The item4. + /// The item5. + public void Add(T1 item, T2 item2, T3 item3, T4 item4, T5 item5) + { + Add(new Tuple(item, item2, item3, item4, item5)); + } +} + +/// +/// +/// Class TupleList to allow Tuple initializers +/// +/// The type of the t1. +/// The type of the t2. +/// The type of the t3. +/// The type of the t4. +/// The type of the t5. +/// The type of the t6. +/// +public class TupleList : List> +{ + /// + /// Adds the specified item. + /// + /// The item. + /// The item2. + /// The item3. + /// The item4. + /// The item5. + /// The item6. + public void Add(T1 item, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6) + { + Add(new Tuple(item, item2, item3, item4, item5, item6)); + } +} diff --git a/src/DNX.Extensions/Objects/ObjectExtensions.cs b/src/DNX.Extensions/Objects/ObjectExtensions.cs new file mode 100644 index 0000000..57f207c --- /dev/null +++ b/src/DNX.Extensions/Objects/ObjectExtensions.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using System.Linq; +using DNX.Extensions.Strings; + +// ReSharper disable UseStringInterpolation + +namespace DNX.Extensions.Objects; + +/// +/// Class ObjectExtensions. +/// +public static class ObjectExtensions +{ + /// + /// Gets the unique instance identifier. + /// + /// The object. + /// System.String. + public static string GetUniqueInstanceId(this object obj) + { + return obj.GetUniqueInstanceId(null); + } + + /// + /// Gets the unique instance identifier. + /// + /// The object. + /// The instance identifier override. + /// System.String. + public static string GetUniqueInstanceId(this object obj, string instanceIdOverride) + { + return string.Format("{0}:{1}", + obj.GetType().FullName, + instanceIdOverride.CoalesceNullOrEmptyWith(obj.GetHashCode().ToString()) + ); + } + + /// + /// Coalesces the list of objects to find the first not null + /// + /// The objects. + /// System.String. + /// Also available as an extension method + public static object CoalesceNull(params object[] objects) + { + return objects.CoalesceNull(); + } + + /// + /// Coalesces the list of objects to find the first not null + /// + /// The objects. + /// System.String. + /// Also available as an extension method + public static object CoalesceNull(this IList objects) + { + var value = objects.FirstOrDefault(o => o != null); + + return value; + } +} diff --git a/src/DNX.Extensions/Providers/FormatProviderFunc.cs b/src/DNX.Extensions/Providers/FormatProviderFunc.cs new file mode 100644 index 0000000..57edff8 --- /dev/null +++ b/src/DNX.Extensions/Providers/FormatProviderFunc.cs @@ -0,0 +1,37 @@ +using System; + +namespace DNX.Extensions.Providers; + +/// +/// Class FormatProviderFunc. +/// +/// +public class FormatProviderFunc : IFormatProvider +{ + private readonly Func _func; + + private FormatProviderFunc(Func func) + { + _func = func; + } + + /// + /// Returns an object that provides formatting services for the specified type. + /// + /// An object that specifies the type of format object to return. + /// An instance of the object specified by , if the implementation can supply that type of object; otherwise, null. + public object GetFormat(Type formatType) + { + return _func(formatType); + } + + /// + /// Creates the specified function. + /// + /// The function. + /// FormatProviderFunc. + public static FormatProviderFunc Create(Func func) + { + return new FormatProviderFunc(func); + } +} diff --git a/src/DNX.Extensions/Reflection/AttributeExtensions.cs b/src/DNX.Extensions/Reflection/AttributeExtensions.cs new file mode 100644 index 0000000..bf90316 --- /dev/null +++ b/src/DNX.Extensions/Reflection/AttributeExtensions.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using DNX.Extensions.Validation; + +namespace DNX.Extensions.Reflection; + +/// +/// Attribute Extensions. +/// +public static class AttributeExtensions +{ + /// + /// Gets the custom attributes for the Type. + /// + /// + /// The type. + /// if set to true [inherit]. + /// IList<T>. + public static IList GetTypeAttributes(this Type type, bool inherit) + { + Guard.IsNotNull(() => type); + + var attributes = type.GetCustomAttributes(typeof(T), inherit) + .Cast() + .ToList(); + + if (inherit) + { + attributes.AddRange( + type.GetInterfaces() + .SelectMany(i => i.GetTypeAttributes(inherit)) + .ToList() + ); + } + + return attributes; + } + + /// + /// Gets the custom attributes for the type from an instance. + /// + /// + /// The object. + /// if set to true [inherit]. + /// IList<T>. + public static IList GetTypeAttributesFromInstance(this object instance, bool inherit) + { + Guard.IsNotNull(() => instance); + + var attributes = instance.GetType() + .GetTypeAttributes(inherit); + + return attributes; + } + + /// + /// Gets the custom attributes from a member (property / field) + /// + /// + /// The member information. + /// The inherit. + /// System.Collections.Generic.IList<T>. + public static IList GetMemberAttributes(this MemberInfo memberInfo, bool inherit) + { + Guard.IsNotNull(() => memberInfo); + + var attributes = memberInfo.GetCustomAttributes(typeof(T), inherit); + + return attributes + .Cast() + .ToList(); + } + + /// + /// Does the instance object have type attributes + /// + /// + /// The type. + /// if set to true [inherit]. + /// true if XXXX, false otherwise. + public static bool HasTypeAttributes(this Type type, bool inherit) + { + var attributes = type.GetTypeAttributes(inherit); + + return attributes.Any(); + } + + /// + /// Does the instance object have type attributes + /// + /// + /// The object. + /// if set to true [inherit]. + /// true if XXXX, false otherwise. + public static bool InstanceHasTypeAttributes(this object obj, bool inherit) + { + var attributes = obj.GetTypeAttributesFromInstance(inherit); + + return attributes.Any(); + } +} diff --git a/src/DNX.Extensions/Reflection/ExpressionExtensions.cs b/src/DNX.Extensions/Reflection/ExpressionExtensions.cs new file mode 100644 index 0000000..6999d60 --- /dev/null +++ b/src/DNX.Extensions/Reflection/ExpressionExtensions.cs @@ -0,0 +1,116 @@ +using System; +using System.Linq.Expressions; + +namespace DNX.Extensions.Reflection; + +/// +/// Class ExpressionExtensions. +/// +public static class ExpressionExtensions +{ + /// + /// Determines whether the expression is a member expression + /// + /// + /// The exp. + /// + public static bool IsMemberExpression(Expression> exp) + { + var memberExpression = exp.Body as MemberExpression; + + return memberExpression != null; + } + + /// + /// Determines whether the expression is a lambda expression + /// + /// + /// The exp. + /// + public static bool IsLambdaExpression(Expression> exp) + { + var lambdaExpression = exp.Body as LambdaExpression; + + return lambdaExpression != null; + } + + /// + /// Determines whether the expression is a unary expression + /// + /// + /// The exp. + /// + public static bool IsUnaryExpression(Expression> exp) + { + var unaryExpression = exp.Body as UnaryExpression; + + return unaryExpression != null; + } + + /// + /// Gets the name of the member expression. + /// + /// + /// The exp. + /// + public static string GetMemberName(Expression> exp) + { + return exp.Body is MemberExpression memberExpression + ? memberExpression.Member.Name + : null; + } + + /// + /// Gets the name of the lambda expression. + /// + /// + /// The exp. + /// + public static string GetLambdaName(Expression> exp) + { + return exp.Body is LambdaExpression lambdaExpression + ? lambdaExpression.Name + : null; + } + + /// + /// Gets the name of the unary expression. + /// + /// + /// The exp. + /// + public static string GetUnaryName(Expression> exp) + { + var unaryExpression = exp.Body as UnaryExpression; //((UnaryExpression)exp.Body).Operand; + + return unaryExpression?.Operand is MemberExpression operand + ? operand.Member.Name + : null; + } + + /// + /// Gets the name of the expression. + /// + /// + /// The exp. + /// + public static string GetExpressionName(Expression> exp) + { + if (IsMemberExpression(exp)) + { + return GetMemberName(exp); + } + + if (IsLambdaExpression(exp)) + { + return GetLambdaName(exp); + } + + if (IsUnaryExpression(exp)) + { + return GetUnaryName(exp); + } + + return null; + } +} diff --git a/src/DNX.Extensions/Reflection/InstanceResolutionExtensions.cs b/src/DNX.Extensions/Reflection/InstanceResolutionExtensions.cs new file mode 100644 index 0000000..0e0e18e --- /dev/null +++ b/src/DNX.Extensions/Reflection/InstanceResolutionExtensions.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace DNX.Extensions.Reflection; + +/// +/// Instance Resolution Extensions. +/// +public static class InstanceResolutionExtensions +{ + /// + /// Find the instances of a Type that are decorated with an attribute that satisfies the given func condition + /// + /// + /// The type of the ta. + /// The instances. + /// The func to run against the decoration attribute + /// if set to true include inherited attributes. + /// List of T + public static IList ResolveInstancesOfTypeWithAttributeAndCondition(this IList instances, Func func, bool inherit = true) + { + var candidates = new List(); + + if (instances != null) + { + foreach (var instance in instances) + { + var attributes = instance.GetTypeAttributesFromInstance(inherit); + + var attributeMatches = attributes + .Where(func) + .ToList(); + + if (attributeMatches.Any()) + { + candidates.Add(instance); + } + } + } + + return candidates; + } +} diff --git a/src/DNX.Extensions/Reflection/ReflectionExtensions.cs b/src/DNX.Extensions/Reflection/ReflectionExtensions.cs index d3f5a74..fcb0e40 100644 --- a/src/DNX.Extensions/Reflection/ReflectionExtensions.cs +++ b/src/DNX.Extensions/Reflection/ReflectionExtensions.cs @@ -1,4 +1,10 @@ +using System.Collections.Generic; +using System.Collections; +using System.Dynamic; +using System.Linq; +using System; using System.Reflection; +using DNX.Extensions.Linq; namespace DNX.Extensions.Reflection; @@ -45,4 +51,344 @@ public static object GetPrivatePropertyValue(this T instance, string property { return instance.GetPropertyValueByName(propertyName, BindingFlags.Instance | BindingFlags.NonPublic, defaultValue); } + + /// + /// Gets the object properties as a dictionary. + /// + /// The object. + /// IDictionary<System.String, System.Object>. + public static IDictionary GetPropertiesDictionary(object obj) + { + var dictionary = GetPropertiesDictionary(obj, + Defaults.PropertyInfoReaderBindingFlags + ); + + return dictionary; + } + + /// + /// Gets the object properties as a dictionary. + /// + /// The object. + /// The binding flags. + /// IDictionary<System.String, System.Object>. + public static IDictionary GetPropertiesDictionary(object obj, BindingFlags bindingFlags) + { + if (obj == null) + { + return null; + } + + var properties = GetPropertiesForType(obj.GetType(), bindingFlags) + .ToDictionary( + pi => pi.Name, + pi => pi.GetValue(obj) + ); + + var fields = GetFieldsForType(obj.GetType(), bindingFlags) + .ToDictionary( + fi => fi.Name, + fi => fi.GetValue(obj) + ); + + var dictionary = properties + .MergeWith(fields, MergeTechnique.TakeFirst); + + return dictionary; + } + + /// + /// Gets the properties for the specified type. + /// + /// The type. + /// IList<PropertyInfo>. + public static IList GetPropertiesForType(Type type) + { + var list = GetPropertiesForType( + type, + Defaults.PropertyInfoReaderBindingFlags + ); + + return list; + } + + /// + /// Gets the properties for the specified type. + /// + /// The type. + /// The binding flags. + /// IList<PropertyInfo>. + public static IList GetPropertiesForType(Type type, BindingFlags bindingFlags) + { + var propertyInfos = type.GetProperties(bindingFlags); + + return propertyInfos; + } + + /// + /// Gets the properties for the specified types. + /// + /// The types. + /// IList<PropertyInfo>. + public static IList GetPropertiesForTypes(IEnumerable types) + { + var list = GetPropertiesForTypes( + types, + Defaults.PropertyInfoReaderBindingFlags + ); + + return list; + } + + /// + /// Gets the properties for the specified types. + /// + /// The types. + /// The binding flags. + /// IList<PropertyInfo>. + public static IList GetPropertiesForTypes(IEnumerable types, BindingFlags bindingFlags) + { + var properties = types + .SelectMany(t => GetPropertiesForType(t, bindingFlags)) + .ToList(); + + return properties; + } + + /// + /// Gets an objects property info by name. Supports dot notation for child properties + /// + /// The instance. + /// Name of the property. + /// PropertyInfo. + public static PropertyInfo GetPropertyInfoByName(this object instance, string propertyName) + { + var propertyNames = propertyName.Split(".".ToCharArray()); + + var instancePropertyName = propertyNames + .FirstOrDefault(); + + var childPropertyNames = propertyNames + .Skip(1) + .ToList(); + + var childPropertyNamesText = childPropertyNames.Any() + ? string.Join(".", childPropertyNames) + : string.Empty; + + var propertyInfo = instance == null + ? null + : GetPropertiesForType(instance.GetType()) + .FirstOrDefault(p => p.Name.Equals(instancePropertyName, StringComparison.CurrentCultureIgnoreCase)); + + var instanceValue = propertyInfo == null + ? null + : propertyInfo.GetValue(instance, null); + + var returnValue = childPropertyNames.Any() + ? GetPropertyInfoByName(instanceValue, childPropertyNamesText) + : propertyInfo; + + return returnValue; + } + + /// + /// Determines whether the specified property name is valid for the specified object + /// + /// The instance. + /// Name of the property. + /// System.Boolean. + public static bool IsPropertyNameValid(this object instance, string propertyName) + { + var propertyInfo = GetPropertyInfoByName(instance, propertyName); + + var exists = propertyInfo != null; + + return exists; + } + + /// + /// Gets he value of an objects property by name + /// + /// The instance. + /// Name of the property. + /// System.Object. + public static object GetNestedPropertyValueByName(this object instance, string propertyName) + { + var propertyNames = propertyName.Split(".".ToCharArray()); + + var instancePropertyName = propertyNames + .FirstOrDefault(); + + var childPropertyNames = propertyNames + .Skip(1) + .ToList(); + var childPropertyNamesText = childPropertyNames.Any() + ? string.Join(".", childPropertyNames) + : string.Empty; + + var propertyInfo = GetPropertiesForType(instance.GetType()) + .FirstOrDefault(p => p.Name.Equals(instancePropertyName, StringComparison.CurrentCultureIgnoreCase)); + + var instanceValue = propertyInfo == null + ? null + : propertyInfo.GetValue(instance, null); + + var returnValue = childPropertyNames.Any() + ? GetNestedPropertyValueByName(instanceValue, childPropertyNamesText) + : instanceValue; + + return returnValue; + } + + /// + /// Gets the fields for the specified type. + /// + /// The type. + /// IList<PropertyInfo>. + public static IList GetFieldsForType(Type type) + { + var list = GetFieldsForType( + type, + Defaults.FieldInfoReaderBindingFlags + ); + + return list; + } + + /// + /// Gets the fields for the specified type. + /// + /// The type. + /// The binding flags. + /// IList<PropertyInfo>. + public static IList GetFieldsForType(Type type, BindingFlags bindingFlags) + { + var fieldInfos = type.GetFields(bindingFlags); + + return fieldInfos; + } + + /// + /// Serialises an object instance to a Dictionary + /// + /// + /// The instance. + /// The binding flags. + /// IDictionary<System.String, System.Object>. + public static IDictionary AsDictionaryTyped(this T instance, BindingFlags bindingFlags = Defaults.PropertyInfoReaderBindingFlags) + { + if (instance == null) + { + return null; + } + + return instance.AsDictionary(bindingFlags); + } + + /// + /// Serialises an object instance to a Dictionary + /// + /// The instance. + /// The binding flags. + /// IDictionary<System.String, System.Object>. + public static IDictionary AsDictionary(this object instance, BindingFlags bindingFlags = Defaults.PropertyInfoReaderBindingFlags) + { + if (instance == null) + { + return null; + } + + var dict = new Dictionary(); + + switch (instance) + { + case IDictionary instanceDict: + { + foreach (DictionaryEntry kvp in instanceDict) + { + var key = Convert.ToString(kvp.Key); + + if (!string.IsNullOrWhiteSpace(key)) + { + dict[key] = kvp.Value; + } + } + + break; + } + case ExpandoObject expando: + { + foreach (var kvp in expando) + { + dict[kvp.Key] = kvp.Value; + } + + break; + } + default: + { + dict = (Dictionary)GetPropertiesDictionary(instance, bindingFlags); + + break; + } + } + + return dict; + } + + /// + /// Creates a populated object instance from a Dictionary + /// + /// + /// The dictionary. + /// The binding flags. + /// T. + public static T AsInstance(this IDictionary dict, BindingFlags bindingFlags = Defaults.PropertyInfoWriterBindingFlags) + where T : new() + { + if (dict == null) + { + return default(T); + } + + var instance = new T(); + + instance.PopulateFrom(dict, bindingFlags); + + return instance; + } + + /// + /// Populates an object instance from a Dictionary + /// + /// + /// The instance. + /// The dictionary. + /// The binding flags. + /// T. + public static T PopulateFrom(this T instance, IDictionary dict, BindingFlags bindingFlags = Defaults.PropertyInfoWriterBindingFlags) + where T : new() + { + if (dict == null) + { + return instance; + } + + var properties = typeof(T).GetProperties(bindingFlags) + .Where(p => p.CanWrite) + .ToList(); + + foreach (var property in properties) + { + if (!dict.ContainsKey(property.Name)) + { + continue; + } + + property.SetValue(instance, dict[property.Name]); + } + + return instance; + } } diff --git a/src/DNX.Extensions/Reflection/TypeExtensions.cs b/src/DNX.Extensions/Reflection/TypeExtensions.cs new file mode 100644 index 0000000..6048616 --- /dev/null +++ b/src/DNX.Extensions/Reflection/TypeExtensions.cs @@ -0,0 +1,85 @@ +using System; +using DNX.Extensions.Validation; + +namespace DNX.Extensions.Reflection; + +/// +/// Type Extensions. +/// +public static class TypeExtensions +{ + /// + /// Determines whether the specified type is nullable. + /// + /// The type. + /// true if the specified type is nullable; otherwise, false. + /// Also available as an extension method + public static bool IsNullable(this Type type) + { + Guard.IsNotNull(() => type); + + if (!type.IsValueType) + { + return true; + } + + return Nullable.GetUnderlyingType(type) != null; + } + + /// + /// Determines whether the specified type is a. + /// + /// + /// The type. + /// true if the specified type is a; otherwise, false. + public static bool IsA(this Type type) + { + return type.IsA(typeof(T)); + } + + /// + /// Determines whether the specified type is a. + /// + /// The type. + /// The base class or interface. + /// System.Boolean. + public static bool IsA(this Type type, Type baseClassOrInterface) + { + return baseClassOrInterface.IsAssignableFrom(type); + } + + /// + /// Gets the default value. + /// + /// The type. + /// System.Object. + /// Also available as an extension method + public static object GetDefaultValue(this Type type) + { + return type.IsValueType + ? Activator.CreateInstance(type) + : null; + } + + /// + /// Gets a default instance. + /// + /// + /// System.Object. + /// Also available as an extension method + public static T CreateDefaultInstance() + { + return Activator.CreateInstance(); + } + + /// + /// Gets a default instance. + /// + /// The type. + /// System.Object. + /// Also available as an extension method + public static object CreateDefaultInstance(this Type type) + { + return Activator.CreateInstance(type); + } +} diff --git a/src/DNX.Extensions/Streams/StreamExtensions.cs b/src/DNX.Extensions/Streams/StreamExtensions.cs new file mode 100644 index 0000000..72c8098 --- /dev/null +++ b/src/DNX.Extensions/Streams/StreamExtensions.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using System.IO; + +namespace DNX.Extensions.Streams; + +/// +/// Class StreamProcessor. +/// +public static class StreamExtensions +{ + /// + /// Reads the entire stream as text + /// + /// The stream. + /// System.String. + public static string ReadAllText(this Stream stream) + { + if (stream == null) + { + return null; + } + + using var streamReader = new StreamReader(stream); + var text = streamReader.ReadToEnd(); + + return text; + } + + /// + /// Reads the entire stream as a list of lines + /// + /// The stream. + /// The estimated capacity. + /// IList<System.String>. + public static IList ReadAllLines(this Stream stream, int? estimatedCapacity = null) + { + if (stream == null) + { + return null; + } + + var lines = estimatedCapacity.HasValue + ? new List(estimatedCapacity.Value) + : new List(); + + using var streamReader = new StreamReader(stream); + while (streamReader.ReadLine() is { } line) + { + lines.Add(line); + } + + return lines; + } + + /// + /// Reads the entire stream as a byte array + /// + /// The stream. + /// Size of the buffer. + /// System.Byte[]. + public static byte[] ReadAllBytes(this Stream stream, int bufferSize = 4096) + { + if (stream == null) + { + return null; + } + + var buffer = new byte[bufferSize]; + + using var memoryStream = new MemoryStream(); + int bytesRead; + while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0) + { + memoryStream.Write(buffer, 0, bytesRead); + } + + return memoryStream.ToArray(); + } +} diff --git a/src/DNX.Extensions/Strings/Interpolation/Interfaces/INamedInstance.cs b/src/DNX.Extensions/Strings/Interpolation/Interfaces/INamedInstance.cs new file mode 100644 index 0000000..ebc2b34 --- /dev/null +++ b/src/DNX.Extensions/Strings/Interpolation/Interfaces/INamedInstance.cs @@ -0,0 +1,20 @@ +namespace DNX.Extensions.Strings.Interpolation.Interfaces; + +/// +/// Interface INamedInstance +/// +public interface INamedInstance +{ + /// + /// Gets the name. + /// + /// The name. + string Name { get; } + + /// + /// Gets the value. + /// + /// Name of the property. + /// System.Object. + object GetValue(string propertyName); +} diff --git a/src/DNX.Extensions/Strings/Interpolation/NamedInstance.cs b/src/DNX.Extensions/Strings/Interpolation/NamedInstance.cs new file mode 100644 index 0000000..f24040e --- /dev/null +++ b/src/DNX.Extensions/Strings/Interpolation/NamedInstance.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using DNX.Extensions.Linq; +using DNX.Extensions.Reflection; + +// ReSharper disable InconsistentNaming + +#pragma warning disable IDE0290 // Use Primary Constructor + +namespace DNX.Extensions.Strings.Interpolation; + +/// +/// Class NamedInstance. +/// +public class NamedInstance +{ + private const BindingFlags InstanceBindingFlags = Defaults.PropertyInfoReaderBindingFlags | BindingFlags.GetField; + private const BindingFlags StaticBindingFlags = (Defaults.PropertyInfoReaderBindingFlags ^ BindingFlags.Instance) | BindingFlags.GetField | BindingFlags.Static; + + /// + /// Gets or sets the instance. + /// + /// The instance. + public object Instance { get; } + + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The instance. + /// The name. + public NamedInstance(object instance, string name = null) + { + Instance = instance; + Name = name; + } + + /// + /// Returns the named instance as a Dictionary + /// + /// IDictionary<System.String, System.Object>. + public IDictionary ToDictionary() + { + if (Instance == null) + return null; + + var dict = Instance.AsDictionary(InstanceBindingFlags) + .MergeWith(Instance.AsDictionary(StaticBindingFlags), MergeTechnique.TakeFirst); + + if (!string.IsNullOrWhiteSpace(Name)) + { + dict = dict.ToDictionary( + x => $"{Name}.{x.Key}", + y => y.Value + ); + } + + return dict; + } +} diff --git a/src/DNX.Extensions/Strings/Interpolation/StringInterpolation.cs b/src/DNX.Extensions/Strings/Interpolation/StringInterpolation.cs new file mode 100644 index 0000000..bcd6403 --- /dev/null +++ b/src/DNX.Extensions/Strings/Interpolation/StringInterpolation.cs @@ -0,0 +1,135 @@ +using System.Collections.Generic; +using System.Linq; +using DNX.Extensions.Linq; + +namespace DNX.Extensions.Strings.Interpolation; + +/// +/// StringInterpolator to simulate and extend named string interpolation available in C# 6.0 +/// +public static class StringInterpolator +{ + /// + /// Interpolates the text with the optionally named instance + /// + /// The text. + /// The instance. + /// The name prefix. + /// if set to true [ignore errors]. + /// System.String. + public static string InterpolateWith(this string text, object instance, string namePrefix = null, bool ignoreErrors = false) + { + var namedInstance = new NamedInstance(instance, namePrefix); + + return InterpolateWith(text, namedInstance, ignoreErrors); + } + + /// + /// Interpolates the text with the named instance + /// + /// The text. + /// The named instance. + /// if set to true [ignore errors]. + /// System.String. + public static string InterpolateWith(this string text, NamedInstance namedInstance, bool ignoreErrors = false) + { + var namedInstances = new List() + { + namedInstance + }; + + return InterpolateWithAll(text, namedInstances, ignoreErrors); + } + + /// + /// Interpolates text with properties from a list of object instances + /// + /// The text. + /// The named instances. + /// if set to true [ignore errors]. + /// System.String. + public static string InterpolateWithAll(this string text, IList namedInstances, bool ignoreErrors = false) + { + var dictionaries = namedInstances + .Select(d => d.ToDictionary()) + .ToArray(); + + var paramValues = DictionaryExtensions.MergeFirst(dictionaries); + + FilterParameterValues(paramValues, text); + + var result = text.InterpolateWithAll(paramValues, ignoreErrors); + + return result; + } + + /// + /// Interpolates text with properties from a Dictionary of parameter names and values + /// + /// The text. + /// The parameter values. + /// if set to true [ignore errors]. + /// System.String. + public static string InterpolateWithAll(this string text, IDictionary paramValues, bool ignoreErrors = false) + { + if (!paramValues.HasAny()) + { + return text; + } + + var format = text ?? string.Empty; + + var index = 0; + foreach (var keyValuePair in paramValues) + { + var identifier = "{" + keyValuePair.Key; + + format = format + .Replace(identifier + "}", "{" + index + "}") + .Replace(identifier + ":", "{" + index + ":") + ; + + ++index; + } + + try + { + var values = paramValues + .Select(x => (object)x.Value); + + var formattedText = string.Format(format, values.ToArray()); + + return formattedText; + } + catch + { + if (ignoreErrors) + { + return text; + } + + throw; + } + } + + /// + /// Builds the parameter values for named instance. + /// + /// The parameter values. + /// The format. + /// System.String. + internal static void FilterParameterValues(IDictionary parameterValues, string format) + { + if (parameterValues == null || string.IsNullOrEmpty(format)) + return; + + var unwantedKeys = parameterValues + .Where(pv => !format.Contains("{" + pv.Key)) + .ToArray(); + + foreach (var unwantedKey in unwantedKeys) + { + parameterValues.Remove(unwantedKey); + } + } +} diff --git a/src/DNX.Extensions/Strings/RegexStringExtensions.cs b/src/DNX.Extensions/Strings/RegexStringExtensions.cs new file mode 100644 index 0000000..4cf3bc4 --- /dev/null +++ b/src/DNX.Extensions/Strings/RegexStringExtensions.cs @@ -0,0 +1,119 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using DNX.Extensions.Linq; + +// ReSharper disable once LoopCanBeConvertedToQuery +namespace DNX.Extensions.Strings; + +/// +/// Regex String Extensions. +/// +public static class RegexStringExtensions +{ + /// + /// Parses to key value pair. + /// + /// The input. + /// The reg expression. + /// Name of the key group. + /// Name of the value group. + /// KeyValuePair<System.String, System.String>. + public static KeyValuePair ParseToKeyValuePair(this string input, string regExpression, string keyGroupName = "1", string valueGroupName = "2") + { + var values = input.ParseFirstMatchToDictionary(regExpression); + + if (values == null) + { + return default(KeyValuePair); + } + + var result = new KeyValuePair( + values.GetValue(keyGroupName), + values.GetValue(valueGroupName) + ); + + return result; + } + + /// + /// Parses to dictionary. + /// + /// The input. + /// The reg expression. + /// Name of the key group. + /// Name of the value group. + /// Dictionary<System.String, System.String>. + public static Dictionary ParseToDictionary(this IEnumerable input, string regExpression, string keyGroupName = "1", string valueGroupName = "2") + { + var values = input + .Select(x => x.ParseToKeyValuePair(regExpression, keyGroupName, valueGroupName)) + .Where(x => !string.IsNullOrEmpty(x.Key)) + .ToDictionary( + a => a.Key, + a => a.Value + ); + + return values; + } + + /// + /// Parses to a list of dictionaries. + /// + /// The input. + /// The reg expression. + /// List<Dictionary<System.String, System.String>>. + public static List> ParseToDictionaryList(this string input, string regExpression) + { + var regex = new Regex(regExpression); + + var matches = regex.Matches(input); + + var result = new List>(); + + foreach (Match match in matches) + { + var index = 0; + var dictionary = match.Groups + .Cast() + .ToDictionary( + g => GetGroupName(regex, index++), + v => v.Value + ); + + result.Add(dictionary); + } + + return result; + } + + /// + /// Parses the first match to dictionary. + /// + /// The input. + /// The regular expression. + /// Dictionary<System.String, System.String>. + public static Dictionary ParseFirstMatchToDictionary(this string input, string regExpression) + { + var dictionary = ParseToDictionaryList(input, regExpression); + + return dictionary.FirstOrDefault(); + } + + /// + /// Gets the name of the group. + /// + /// The regex. + /// The index. + /// System.String. + public static string GetGroupName(this Regex regex, int index) + { + var groupNames = regex.GetGroupNames(); + + index = groupNames.GetAbsoluteIndex(index); + + return groupNames.IsIndexValid(index) + ? groupNames[index] + : index.ToString(); + } +} diff --git a/src/DNX.Extensions/Strings/StringExtensions.cs b/src/DNX.Extensions/Strings/StringExtensions.cs index ab2cda3..360ba01 100644 --- a/src/DNX.Extensions/Strings/StringExtensions.cs +++ b/src/DNX.Extensions/Strings/StringExtensions.cs @@ -820,10 +820,12 @@ public static string Wordify(this string text) /// System.String. public static string Wordify(this string text, IList preservedWords) { - if (string.IsNullOrEmpty(text)) + if (string.IsNullOrWhiteSpace(text)) return text; - var result = Wordify_Regex.Replace(text, " $0"); + var result = Wordify_Regex + .Replace(text, " $0") + .Replace(" ", " "); if (preservedWords.HasAny()) { diff --git a/src/DNX.Extensions/Threading/IProducerConsumerQueue.cs b/src/DNX.Extensions/Threading/IProducerConsumerQueue.cs new file mode 100644 index 0000000..ccecf1e --- /dev/null +++ b/src/DNX.Extensions/Threading/IProducerConsumerQueue.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; + +namespace DNX.Extensions.Threading; + +/// +/// Interface IProducerConsumerQueue +/// +/// +public interface IProducerConsumerQueue where T : class +{ + /// + /// A Comparer to use if the Queue is to be ordered + /// + /// The comparer. + IComparer Comparer { get; } + + /// + /// Clear the Queue of all items + /// + void Clear(); + + /// + /// Add an item to the Queue to be processed + /// + /// The item to be queued. + void AddItem(T item); + + /// + /// Adds a batch of items to the Queue to be processed + /// + /// The items to be queued. + void AddItems(T[] items); +} diff --git a/src/DNX.Extensions/Threading/Mutexes/Mutex.cs b/src/DNX.Extensions/Threading/Mutexes/Mutex.cs new file mode 100644 index 0000000..6ff1df6 --- /dev/null +++ b/src/DNX.Extensions/Threading/Mutexes/Mutex.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; + +// ReSharper disable ConditionIsAlwaysTrueOrFalse + +namespace DNX.Extensions.Threading.Mutexes; + +/// +/// +/// Class Mutex. +/// +/// +public class Mutex : IDisposable +{ + private static readonly object MutexLocker = new object(); + + private static readonly ConcurrentDictionary LockObjects; + + + /// + /// Gets or sets the lock object. + /// + /// The lock object. + public object LockObject { get; private set; } + + /// + /// Gets the state. + /// + /// The state. + public MutexState State { get; private set; } + + /// + /// Gets the name. + /// + /// The name. + public string Name { get; private set; } + + /// + /// Gets the timestamp. + /// + /// The timestamp. + public DateTime Timestamp { get; private set; } + + /// + /// Gets the thread identifier. + /// + /// The thread identifier. + public int ThreadId { get; private set; } + + static Mutex() + { + lock (MutexLocker) + { + if (LockObjects == null) + { + LockObjects = new ConcurrentDictionary(); + } + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// The lock object. + internal Mutex(string name, object lockObject) + { + State = MutexState.Waiting; + LockObject = lockObject; + Name = name; + Timestamp = DateTime.UtcNow; + ThreadId = Thread.CurrentThread.ManagedThreadId; + + AcquireLock(); + MutexManager.Register(this); + } + + /// + /// + /// Initializes a new instance of the class. + /// + /// The name. + public Mutex(string name) + : this(name, GetLockObject(name)) + { + } + + private static object GetLockObject(string name) + { + return LockObjects.GetOrAdd(name, new object()); + } + + /// + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + MutexManager.Release(this); + ReleaseLock(); + } + + internal void AcquireLock() + { + Monitor.Enter(LockObject); + State = MutexState.Acquired; + } + + internal void ReleaseLock() + { + Monitor.Exit(LockObject); + State = MutexState.Released; + } +} diff --git a/src/DNX.Extensions/Threading/Mutexes/MutexManager.cs b/src/DNX.Extensions/Threading/Mutexes/MutexManager.cs new file mode 100644 index 0000000..57c8438 --- /dev/null +++ b/src/DNX.Extensions/Threading/Mutexes/MutexManager.cs @@ -0,0 +1,96 @@ +using System.Collections.Generic; + +namespace DNX.Extensions.Threading.Mutexes; + +/// +/// Class MutexManager. +/// +public static class MutexManager +{ + /// + /// The locker + /// + private static readonly object MutexManagerLocker = new object(); + + /// + /// Gets the mutexes. + /// + /// The mutexes. + public static IDictionary Mutexes { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + static MutexManager() + { + lock (MutexManagerLocker) + { + if (Mutexes == null) + { + Mutexes = new Dictionary(); + } + } + } + + /// + /// Acquires a named Mutex waiting for it to become available + /// + /// The name. + /// Mutex. + public static Mutex Acquire(string name) + { + lock (MutexManagerLocker) + { + var mutex = new Mutex(name); + + return mutex; + } + } + + /// + /// Acquires a named Mutex or returns null immediately if unable + /// + /// The name. + /// DNX.Helpers.Threading.Mutexes.Mutex. + public static Mutex AcquireNoWait(string name) + { + lock (MutexManagerLocker) + { + if (Mutexes.ContainsKey(name)) + { + return null; + } + } + + return Acquire(name); + } + + /// + /// Determines whether the named Mutex could be acquired + /// + /// The name. + /// true if this instance can acquire the specified name; otherwise, false. + public static bool CanAcquire(string name) + { + lock (MutexManagerLocker) + { + return !Mutexes.ContainsKey(name); + } + } + + internal static void Register(Mutex mutex) + { + lock (MutexManagerLocker) + { + Mutexes.Add(mutex.Name, mutex); + } + } + + internal static void Release(Mutex mutex) + { + lock (MutexManagerLocker) + { + Mutexes.Remove(mutex.Name); + } + } +} diff --git a/src/DNX.Extensions/Threading/Mutexes/MutexState.cs b/src/DNX.Extensions/Threading/Mutexes/MutexState.cs new file mode 100644 index 0000000..2cacdb6 --- /dev/null +++ b/src/DNX.Extensions/Threading/Mutexes/MutexState.cs @@ -0,0 +1,22 @@ +namespace DNX.Extensions.Threading.Mutexes; + +/// +/// The waiting +/// +public enum MutexState +{ + /// + /// Waiting to acquire a lock + /// + Waiting = 0, + + /// + /// Lock acquired + /// + Acquired = 1, + + /// + /// Lock released + /// + Released = 2 +} diff --git a/src/DNX.Extensions/Threading/ProducerConsumerQueue.cs b/src/DNX.Extensions/Threading/ProducerConsumerQueue.cs new file mode 100644 index 0000000..425e947 --- /dev/null +++ b/src/DNX.Extensions/Threading/ProducerConsumerQueue.cs @@ -0,0 +1,558 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Threading; + +namespace DNX.Extensions.Threading; + +/// +/// Generic Producer/Consumer Queue object +/// +/// class / interface type of objects to queue +/// +/// +/// Provides virtual methods hooks to allow a descendant class to vary the behaviour. +/// Allows an to process queued items in a defined order. +/// Abstract to allow implementor to provide ProcessItem method to handle a queued item. +// ReSharper disable LoopCanBeConvertedToQuery +public abstract class ProducerConsumerQueue : IDisposable, IProducerConsumerQueue where T : class +{ + #region Fields + + /// + /// Locking control object to regulate access to the queue objects + /// + private readonly object _lockerQueue = new object(); + + /// + /// Locking control object to regulate access to the process executing count. + /// + private readonly object _lockerExecuting = new object(); + + /// + /// WaitHandle for synchronising threads + /// + private readonly EventWaitHandle _waitHandle = new AutoResetEvent(false); + + /// + /// Queue processor thread + /// + private readonly Thread _workerThread; + + /// + /// The queued items + /// + private List _queuedItems = new List(); + + /// + /// The currently executing items + /// + private readonly List _executingItems = new List(); + + #endregion + + #region Properties + + /// + /// Gets or sets a value indicating whether items are automatically disposed. + /// + /// + /// true if items are to be automatically disposed; otherwise, false. + /// + public bool AutoCleanupItems + { + get; + set; + } + + /// + /// Gets a value indicating whether this queue operates on a timeout. + /// + /// + /// true if this instance is on a timeout; otherwise, false. + /// + public bool IsTimed + { + get { return Timeout < TimeSpan.MaxValue; } + } + + /// + /// Gets the timeout. + /// + /// The timeout. + public TimeSpan Timeout + { + get; + private set; + } + + /// + /// Gets the items. + /// + public T[] Items + { + get + { + T[] items = null; + + lock (_lockerQueue) + { + if (_queuedItems != null) + { + items = _queuedItems.ToArray(); + } + } + + return items; + } + } + + /// + /// Gets the executing items. + /// + public T[] ExecutingItems + { + get + { + T[] items; + + lock (_lockerExecuting) + { + items = _executingItems.ToArray(); + } + + return items; + } + } + + /// + /// Gets the executing count. + /// + public int ExecutingCount + { + get + { + lock (_lockerExecuting) + { + return _executingItems.Count; + } + } + } + + /// + /// Gets a value indicating whether this instance is executing. + /// + /// + /// true if this instance is executing; otherwise, false. + /// + public bool IsExecuting + { + get { return ExecutingCount > 0; } + } + + #endregion + + #region Static Methods + + /// + /// Describes the specified exception. + /// + /// The exception. + /// A text representation of the Exception + protected static string Describe(Exception ex) + { + var sb = new StringBuilder(); + + if (ex != null) + { + sb.AppendFormat("Message: {0}\nSource: {1}\nStackTrace: {2}\n", + ex.Message, + ex.Source, + ex.StackTrace + ); + } + + return sb.ToString(); + } + + #endregion + + #region Constructors + + /// + /// Default Constructor + /// + protected ProducerConsumerQueue() + : this(true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// if set to true items will be automatically disposed. + protected ProducerConsumerQueue(bool autoCleanup) + : this(autoCleanup, TimeSpan.MaxValue, null) + { + } + + /// + /// Constructor accepting a Wake timeout and a Comparer (for Ordered Queue) + /// + /// if set to true [auto cleanup]. + /// The timeout. + /// The comparer. + protected ProducerConsumerQueue(bool autoCleanup, TimeSpan timeout, IComparer comparer) + { + AutoCleanupItems = autoCleanup; + Timeout = timeout; + Comparer = comparer; + + _workerThread = new Thread(Work); + _workerThread.Start(); + } + + #endregion + + #region Internal Methods + + /// + /// Queue an item to be processed + /// + /// The item to be queued. + /// + /// Internal use only. + /// + private void EnqueueItem(T item) + { + EnqueueItems( + item == null + ? null + : new [] { item } + ); + } + + /// + /// Queue a batch of items to be processed + /// + /// The items. + /// + /// Internal use only. + /// + private void EnqueueItems(IReadOnlyCollection items) + { + lock (_lockerQueue) + { + if (items == null) + { + _queuedItems.Clear(); + _queuedItems = null; + } + else + { + // Just in case someone tries to add while we're dying + if (_queuedItems != null) + { + // Add each item + foreach (var item in items) + { + _queuedItems.Add(item); + ItemAdded(item); + } + + // Sort them, if we have a Comparer + if (Comparer != null) + { + _queuedItems.Sort(Comparer); + } + } + } + } + + _waitHandle.Set(); + } + + /// + /// Queue handler thread method for dealing with queued items + /// + private void Work() + { + while (true) + { + T item = null; + lock (_lockerQueue) + { + // Exit ? + if (_queuedItems == null) + { + return; + } + + // Is there anything waiting ? + + if (_queuedItems.Count > 0) + { + // Peek at the first item + item = _queuedItems[0]; + + // Are we able to handle this item yet ? + if (CanHandleItemNow(item)) + { + // Dequeue + _queuedItems.RemoveAt(0); + } + else + { + // Don't handle it + item = null; + } + } + } + + // Have we got an item to handle ? + if (item != null) + { + try + { + StartExecuting(item); + + HandleItem(item); + } + catch (Exception ex) + { + Trace.WriteLine("Exception handled by the Queue: {0}", Describe(ex)); + } + finally + { + StopExecuting(item); + } + } + else + { + // No more items - wait for a signal + if (IsTimed) + { + _waitHandle.WaitOne(Timeout); + } + else + { + _waitHandle.WaitOne(); + } + } + } + } + + /// + /// Adds the item to the executing items list. + /// + /// The item to add. + private void StartExecuting(T item) + { + lock (_lockerExecuting) + { + _executingItems.Add(item); + } + } + + /// + /// Removes the item from the executing items list. + /// + /// + /// Also performs cleanup of the item, if necessary + /// + /// The item to remove. + private void StopExecuting(T item) + { + lock (_lockerExecuting) + { + _executingItems.Remove(item); + } + + // Clean up items ? + if (AutoCleanupItems) + { + // See if we can Dispose of it + var itemDisposable = item as IDisposable; + if (itemDisposable != null) + { + itemDisposable.Dispose(); + } + } + } + + #endregion + + #region Overridable Methods + + /// + /// Waits for consumers to complete. + /// + protected virtual void WaitForConsumersToComplete() + { + if (_workerThread != null) + { + _workerThread.Join(); // Wait for the consumer's thread to finish. + } + } + + /// + /// Hook to determine if we can handle the item yet. + /// + /// The item about to be handled + /// + /// true/false - depending on whether we can handle the item yet or not + /// + /// + /// Allows implementor to create custom lists (not just queues) based around the Producer/Consumer pattern + /// + protected virtual bool CanHandleItemNow(T item) + { + return true; + } + + /// + /// Queue processor to handle an item when it gets to the front of the queue. + /// + /// The item to be handled + /// + /// Performs any processing required prior to calling specific implementor of ProcessItem + /// + protected virtual void HandleItem(T item) + { + // Process it + try + { + Trace.WriteLine("Processing Queue item: {0}", item == null ? "NULL" : item.GetType().Name); + + // Simply hand-off to ProcessItem as we're operating in single-thread mode + ProcessItem(item); + } + catch (Exception ex) + { + Trace.WriteLine("Exception from ProcessItem for Queue item: {0}", Describe(ex)); + } + + // Run any completion steps + try + { + Trace.WriteLine("Completing Queue item: {0}", item == null ? "NULL" : item.GetType().Name); + + ItemComplete(item); + } + catch (Exception ex) + { + Trace.WriteLine("Exception from ItemComplete for Queue item: {0}", Describe(ex)); + } + } + + /// + /// Hook called when an item has been added to the queue. + /// + /// The item just added. + protected virtual void ItemAdded(T item) + { + } + + /// + /// Hook called when an item has been processed + /// + /// The item just successfully processed + protected virtual void ItemComplete(T item) + { + } + + /// + /// Abstract method for processing a queued item after being picked up by the Queue handler + /// + /// The item to be processed + protected abstract void ProcessItem(T item); + + #endregion + + #region IProducerConsumerQueue Members + + /// + /// The Comparer to use when ordering Queued items + /// + /// The comparer. + /// + /// Specify NULL for a regular FIFO queue + /// + public IComparer Comparer + { + get; + private set; + } + + /// + /// Remove all items from the Queue + /// + public void Clear() + { + lock (_lockerQueue) + { + _queuedItems.Clear(); + } + } + + /// + /// Queue an item to be processed + /// + /// The item to be added. + /// + /// Public interface. Validates item to be a valid instance. + /// + public void AddItem(T item) + { + if (item == null) + { + throw new NullReferenceException("Must add a valid item"); + } + + EnqueueItem(item); + } + + /// + /// Adds a batch of items to the Queue to be processed + /// + /// The items. + public void AddItems(T[] items) + { + var validated = new List(); + + // Remove any NULL instances + if (items != null) + { + foreach (var item in items) + { + if (item != null) + { + validated.Add(item); + } + } + } + + // Is there something to queue? + if (validated.Count > 0) + { + EnqueueItems(validated.ToArray()); + } + } + + #endregion + + #region IDisposable Members + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + EnqueueItem(null); // Signal the consumer to exit. + WaitForConsumersToComplete(); // Wait for the consumer's thread to finish. + _waitHandle.Close(); // Release any OS resources. + } + + #endregion +} diff --git a/src/DNX.Extensions/Threading/ProducerConsumerQueueAction.cs b/src/DNX.Extensions/Threading/ProducerConsumerQueueAction.cs new file mode 100644 index 0000000..c021fb1 --- /dev/null +++ b/src/DNX.Extensions/Threading/ProducerConsumerQueueAction.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; + +namespace DNX.Extensions.Threading; + +/// +/// Class ProducerConsumerQueueAction. +/// +/// +/// +public class ProducerConsumerQueueAction : ProducerConsumerQueue where T : class +{ + /// + /// The action + /// + private readonly Action _action; + + /// + /// Initializes a new instance of the class. + /// + /// The action. + public ProducerConsumerQueueAction(Action action) + :base() + { + _action = action; + } + + /// + /// Initializes a new instance of the class. + /// + /// The action. + /// if set to true [automatic clean up]. + public ProducerConsumerQueueAction(Action action, bool autoCleanUp) + : base(autoCleanUp) + { + _action = action; + } + + /// + /// Initializes a new instance of the class. + /// + /// The action. + /// if set to true [automatic clean up]. + /// The timeout. + /// The comparer. + public ProducerConsumerQueueAction(Action action, bool autoCleanUp, TimeSpan timeout, IComparer comparer) + : base(autoCleanUp, timeout, comparer) + { + _action = action; + } + + /// + /// Method for processing a queued item after being picked up by the Queue handler + /// + /// The item to be processed + protected override void ProcessItem(T item) + { + _action(item); + } +} diff --git a/src/DNX.Extensions/Threading/ThreadHelper.cs b/src/DNX.Extensions/Threading/ThreadHelper.cs new file mode 100644 index 0000000..4578fce --- /dev/null +++ b/src/DNX.Extensions/Threading/ThreadHelper.cs @@ -0,0 +1,116 @@ +using System.Threading; +using DNX.Extensions.Validation; + +namespace DNX.Extensions.Threading; + +/// +/// Generic parameterized delegate. +/// +/// Type of method parameter. +/// The strongly typed value to pass to the method. +public delegate void ParameterizedThreadStart(T value); + +/// +/// Helper class for Thread operations +/// +/// +/// // Start new thread with type safe parameter. +/// RunThread.Start(new ParameterizedThreadStart<int>(RunThis), 8); +/// // Start new thread pool thread with type safe parameter. +/// RunThread.StartOnThreadPool(new ParameterizedThreadStart<int>(RunThis), 9); +/// // Start anonymous method on the thread pool. +/// RunThread.StartOnThreadPool(delegate(int i) +/// { +/// Console.WriteLine("Anonymous method run on thread pool and passed: " + i); +/// }, 10); +/// // Method with parm to run on a new thread. +/// private void RunThis(int value) +/// { +/// Console.WriteLine("Value: " + value + " ThreadPool: " + +/// Thread.CurrentThread.IsThreadPoolThread + +/// " ID: " + Thread.CurrentThread.ManagedThreadId); +/// } +/// +public static class ThreadHelper +{ + /// + /// Creates and starts a new Thread which runs the parameterized delegate. + /// + /// The type of parameter the delegate accepts. + /// The generic delegate. + /// The type to pass to delegate. + /// The Thread instance. + public static Thread Start(ParameterizedThreadStart start, T value) + { + Guard.IsNotNull(() => start); + + var t = new Thread( + delegate() + { + start(value); + } + ); + + t.Start(); + + return t; + } + + /// + /// Creates and starts a new Thread. + /// + /// ThreadStart delegate. + /// The Thread instance. + public static Thread Start(ThreadStart start) + { + Guard.IsNotNull(() => start); + + var t = new Thread(start); + + t.Start(); + + return t; + } + + /// + /// Queues a delegate to run on a thread pool thread. + /// + /// The type of parameter the delegate accepts. + /// The generic delegate. + /// The type to pass to delegate. + /// + /// True if the delegate is queued successfully; False otherwise + /// + public static bool StartOnThreadPool(ParameterizedThreadStart start, T value) + { + Guard.IsNotNull(() => start); + + // We use this method instead of BeginInvoke so we get fire and forget semantics. + return ThreadPool.QueueUserWorkItem( + delegate + { + start(value); + } + ); + } + + /// + /// Queues a delegate to run on a thread pool thread. + /// + /// ThreadStart delegate. + /// + /// True if the delegate is queued successfully; False otherwise + /// + public static bool StartOnThreadPool(ThreadStart start) + { + Guard.IsNotNull(() => start); + + // We use this method instead of BeginInvoke so we get fire and forget semantics. + return ThreadPool.QueueUserWorkItem( + delegate + { + start(); + } + ); + } +} diff --git a/src/DNX.Extensions/Validation/Guard.cs b/src/DNX.Extensions/Validation/Guard.cs new file mode 100644 index 0000000..465a659 --- /dev/null +++ b/src/DNX.Extensions/Validation/Guard.cs @@ -0,0 +1,163 @@ +using System; +using System.Linq.Expressions; +using DNX.Extensions.Reflection; + +// ReSharper disable InvertIf + +namespace DNX.Extensions.Validation; + +/// +/// Guard Extensions. +/// +public static partial class Guard +{ + /// + /// Ensures the specified exp is true. + /// + /// The exp. + public static void IsTrue(Expression> exp) + { + IsTrue(exp, exp.Compile().Invoke()); + } + + /// + /// Ensures the specified exp is true. + /// + /// The exp. + /// The value. + /// + public static void IsTrue(Expression> exp, bool val) + { + if (val) + { + return; + } + + var memberName = ExpressionExtensions.GetMemberName(exp); + + throw new ArgumentOutOfRangeException( + memberName, + false, + $"{memberName} must be true" + ); + } + + /// + /// Ensures the specified exp is false. + /// + /// The exp. + public static void IsFalse(Expression> exp) + { + IsFalse(exp, exp.Compile().Invoke()); + } + + /// + /// Ensures the specified exp is false. + /// + /// The exp. + /// The value. + /// + public static void IsFalse(Expression> exp, bool val) + { + if (!val) + { + return; + } + + var memberName = ExpressionExtensions.GetMemberName(exp); + + throw new ArgumentOutOfRangeException( + memberName, + true, + $"{memberName} must be false" + ); + } + + /// + /// Ensures the specified exp is not null + /// + /// Any reference type + /// The linq expression of the argument to check + public static void IsNotNull(Expression> exp) + where T : class + { + var val = exp.Compile().Invoke(); + + IsNotNull(exp, val); + } + + /// + /// Ensures the specified exp is not null + /// + /// Any reference type + /// The linq expression of the argument to check + /// value of argument in exp + /// Use this if you are not happy that the expression exp will be invoked more than once by your method. + public static void IsNotNull(Expression> exp, T val) + where T : class + { + if (val == null) + { + var memberName = ExpressionExtensions.GetMemberName(exp); + + throw new ArgumentNullException( + memberName, + $"{memberName} must not be null" + ); + } + } + + /// + /// Ensures the specified exp is not null or empty + /// + /// The linq expression of the argument to check + public static void IsNotNullOrEmpty(Expression> exp) + { + IsNotNullOrEmpty(exp, exp.Compile().Invoke()); + } + + /// + /// Ensures the specified exp is not null or empty + /// + /// The linq expression of the argument to check + /// value of argument in exp + /// Use this if you are not happy that the expression exp will be invoked more than once by your method. + public static void IsNotNullOrEmpty(Expression> exp, string val) + { + if (string.IsNullOrEmpty(val)) + { + var memberName = ExpressionExtensions.GetMemberName(exp); + + throw new ArgumentException( + $"{memberName} must not be null or empty", memberName + ); + } + } + + /// + /// Ensures the specified exp is not null or whitespace + /// + /// The linq expression of the argument to check + public static void IsNotNullOrWhitespace(Expression> exp) + { + IsNotNullOrWhitespace(exp, exp.Compile().Invoke()); + } + + /// + /// Ensures the specified exp is not null or whitespace + /// + /// The linq expression of the argument to check + /// value of argument in exp + /// Use this if you are not happy that the expression exp will be invoked more than once by your method. + public static void IsNotNullOrWhitespace(Expression> exp, string val) + { + if (string.IsNullOrWhiteSpace(val)) + { + var memberName = ExpressionExtensions.GetMemberName(exp); + + throw new ArgumentException( + $"{memberName} must not be null or whitespace", memberName + ); + } + } +} diff --git a/src/DNX.Extensions/Validation/GuardEnums.cs b/src/DNX.Extensions/Validation/GuardEnums.cs new file mode 100644 index 0000000..10e91f6 --- /dev/null +++ b/src/DNX.Extensions/Validation/GuardEnums.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using DNX.Extensions.Enumerations; +using DNX.Extensions.Reflection; + +namespace DNX.Extensions.Validation +{ + public static partial class Guard + { + /// + /// Ensures the expression is a valid Enum value + /// + /// + /// The exp. + public static void IsValidEnum(this Expression> exp) + where T : struct + { + IsValidEnum(exp, exp.Compile().Invoke()); + } + + /// + /// Ensures the expression is a valid Enum value + /// + /// + /// The exp. + /// The value. + /// + public static void IsValidEnum(this Expression> exp, T val) + where T : struct + { + if (!val.IsValidEnum()) + { + var memberName = ExpressionExtensions.GetMemberName(exp); + + throw new ArgumentException( + $"{memberName} must be a valid {typeof(T).Name} value", memberName + ); + } + } + + /// + /// Ensures the expression is a valid Enum value + /// + /// + /// The exp. + /// The allowed. + public static void IsEnumOneOf(this Expression> exp, params T[] allowed) + where T : struct + { + IsEnumOneOf(exp, exp.Compile().Invoke(), allowed); + } + + /// + /// Ensures the expression is a valid Enum value + /// + /// + /// The exp. + /// The allowed. + public static void IsEnumOneOf(this Expression> exp, IList allowed) + where T : struct + { + IsEnumOneOf(exp, exp.Compile().Invoke(), allowed); + } + + /// + /// Ensures the expression is a valid Enum value + /// + /// + /// The exp. + /// The value. + /// The allowed. + /// + public static void IsEnumOneOf(this Expression> exp, T val, IList allowed) + where T : struct + { + if (!val.IsValueOneOf(allowed)) + { + var memberName = ExpressionExtensions.GetMemberName(exp); + + var allowedValues = allowed + .Select(a => Convert.ToString(a)); + + throw new ArgumentException( + $"{memberName} must be an allowed {typeof(T).Name} value: {string.Join(",", allowedValues)}", + memberName + ); + } + } + } +} diff --git a/tests/DNX.Extensions.Tests/DateTimes/CustomDateTimeFormatProvider.cs b/tests/DNX.Extensions.Tests/DateTimes/CustomDateTimeFormatProvider.cs new file mode 100644 index 0000000..f0cdfc2 --- /dev/null +++ b/tests/DNX.Extensions.Tests/DateTimes/CustomDateTimeFormatProvider.cs @@ -0,0 +1,29 @@ +using System.Globalization; + +namespace DNX.Extensions.Tests.DateTimes; + +internal class CustomDateTimeFormatProvider : IFormatProvider, ICustomFormatter +{ + public const string FormatString = "dd-MMM-yyyy HH:mm:ss"; + + public object GetFormat(Type formatType) + { + return (formatType == typeof(DateTimeFormatInfo)) ? this : null; + } + + public string Format(string format, object arg, IFormatProvider formatProvider) + { + if (arg is DateTime dt) + { + // if user supplied own format use it + return string.IsNullOrEmpty(format) + ? dt.ToString(FormatString) + : dt.ToString(format); + } + + // format everything else normally + return arg is IFormattable formattable + ? formattable.ToString(format, formatProvider) + : arg.ToString(); + } +} diff --git a/tests/DNX.Extensions.Tests/DateTimes/DateTimeExtensionsTests.cs b/tests/DNX.Extensions.Tests/DateTimes/DateTimeExtensionsTests.cs index 19710df..7d0588c 100644 --- a/tests/DNX.Extensions.Tests/DateTimes/DateTimeExtensionsTests.cs +++ b/tests/DNX.Extensions.Tests/DateTimes/DateTimeExtensionsTests.cs @@ -8,6 +8,163 @@ public class DateTimeExtensionsTests { private static readonly Random Randomizer = new(); + [Fact] + public void Unix_Epoch_returns_the_correct_value() + { + // Arrange + + // Act + var epoch = DateTimeExtensions.UnixEpoch; + + // Assert + epoch.Kind.Should().Be(DateTimeKind.Utc); + epoch.Year.Should().Be(1970); + epoch.Month.Should().Be(1); + epoch.Day.Should().Be(1); + epoch.Hour.Should().Be(0); + epoch.Minute.Should().Be(0); + epoch.Second.Should().Be(0); + epoch.Millisecond.Should().Be(0); + } + + [Fact] + public void ParseDateAsUtc_can_parse_a_date() + { + // Arrange + var theDateTime = DateTime.Now; + var dateTimeString = theDateTime.ToLongDateString() + " " + theDateTime.ToLongTimeString(); + + // Act + var parsedDateTime = dateTimeString.ParseDateAsUtc(); + + // Assert + theDateTime.Kind.Should().Be(DateTimeKind.Local); + parsedDateTime.Kind.Should().Be(DateTimeKind.Utc); + parsedDateTime.Year.Should().Be(theDateTime.Year); + parsedDateTime.Month.Should().Be(theDateTime.Month); + parsedDateTime.Day.Should().Be(theDateTime.Day); + parsedDateTime.Hour.Should().Be(theDateTime.Hour); + parsedDateTime.Minute.Should().Be(theDateTime.Minute); + parsedDateTime.Second.Should().Be(theDateTime.Second); + parsedDateTime.Millisecond.Should().Be(0); + } + + [Fact] + public void ParseDateAsUtc_with_default_value_can_parse_a_date() + { + // Arrange + var defaultDateTime = DateTime.Now.Subtract(TimeSpan.FromDays(60)); + var theDateTime = DateTime.Now; + var dateTimeString = theDateTime.ToLongDateString() + " " + theDateTime.ToLongTimeString(); + + // Act + var parsedDateTime = dateTimeString.ParseDateAsUtc(defaultDateTime); + + // Assert + parsedDateTime.Kind.Should().Be(DateTimeKind.Utc); + parsedDateTime.Year.Should().Be(theDateTime.Year); + parsedDateTime.Month.Should().Be(theDateTime.Month); + parsedDateTime.Day.Should().Be(theDateTime.Day); + parsedDateTime.Hour.Should().Be(theDateTime.Hour); + parsedDateTime.Minute.Should().Be(theDateTime.Minute); + parsedDateTime.Second.Should().Be(theDateTime.Second); + parsedDateTime.Millisecond.Should().Be(0); + } + + [Fact] + public void ParseDateAsUtc_with_default_value_returns_default_value_when_it_fails_to_parse_a_date() + { + // Arrange + var defaultDateTime = DateTime.Now; + var dateTimeString = "Not a datetime string"; + + // Act + var parsedDateTime = dateTimeString.ParseDateAsUtc(defaultDateTime); + + // Assert + parsedDateTime.Kind.Should().Be(DateTimeKind.Utc); + parsedDateTime.Year.Should().Be(defaultDateTime.Year); + parsedDateTime.Month.Should().Be(defaultDateTime.Month); + parsedDateTime.Day.Should().Be(defaultDateTime.Day); + parsedDateTime.Hour.Should().Be(defaultDateTime.Hour); + parsedDateTime.Minute.Should().Be(defaultDateTime.Minute); + parsedDateTime.Second.Should().Be(defaultDateTime.Second); + parsedDateTime.Millisecond.Should().Be(defaultDateTime.Millisecond); + } + + [Fact] + public void ParseDateAsUtc_can_parse_a_date_using_a_format_provider() + { + // Arrange + var formatProvider = new CustomDateTimeFormatProvider(); + + var theDateTime = DateTime.Now; + var dateTimeString = theDateTime.ToString(CustomDateTimeFormatProvider.FormatString); + + // Act + var parsedDateTime = dateTimeString.ParseDateAsUtc(formatProvider); + + // Assert + theDateTime.Kind.Should().Be(DateTimeKind.Local); + parsedDateTime.Kind.Should().Be(DateTimeKind.Utc); + parsedDateTime.Year.Should().Be(theDateTime.Year); + parsedDateTime.Month.Should().Be(theDateTime.Month); + parsedDateTime.Day.Should().Be(theDateTime.Day); + parsedDateTime.Hour.Should().Be(theDateTime.Hour); + parsedDateTime.Minute.Should().Be(theDateTime.Minute); + parsedDateTime.Second.Should().Be(theDateTime.Second); + parsedDateTime.Millisecond.Should().Be(0); + } + + [Fact] + public void ParseDateAsUtc_with_default_value_returns_can_parse_a_date_using_a_format_provider() + { + // Arrange + var formatProvider = new CustomDateTimeFormatProvider(); + + var defaultDateTime = DateTime.Now.Subtract(TimeSpan.FromDays(60)); + var theDateTime = DateTime.Now; + var dateTimeString = theDateTime.ToString(CustomDateTimeFormatProvider.FormatString); + + // Act + var parsedDateTime = dateTimeString.ParseDateAsUtc(formatProvider, defaultDateTime); + + // Assert + parsedDateTime.Kind.Should().Be(DateTimeKind.Utc); + parsedDateTime.Year.Should().Be(theDateTime.Year); + parsedDateTime.Month.Should().Be(theDateTime.Month); + parsedDateTime.Day.Should().Be(theDateTime.Day); + parsedDateTime.Hour.Should().Be(theDateTime.Hour); + parsedDateTime.Minute.Should().Be(theDateTime.Minute); + parsedDateTime.Second.Should().Be(theDateTime.Second); + parsedDateTime.Millisecond.Should().Be(0); + } + + [Fact] + public void ParseDateAsUtc_with_default_value_returns_default_value_when_it_fails_to_parse_a_date_using_a_format_provider() + { + // Arrange + var formatProvider = new CustomDateTimeFormatProvider(); + + var defaultDateTime = DateTime.Now; + var dateTimeString = "Not a datetime string"; + + // Act + var parsedDateTime = dateTimeString.ParseDateAsUtc(formatProvider, defaultDateTime); + + // Assert + parsedDateTime.Kind.Should().Be(DateTimeKind.Utc); + parsedDateTime.Year.Should().Be(defaultDateTime.Year); + parsedDateTime.Month.Should().Be(defaultDateTime.Month); + parsedDateTime.Day.Should().Be(defaultDateTime.Day); + parsedDateTime.Hour.Should().Be(defaultDateTime.Hour); + parsedDateTime.Minute.Should().Be(defaultDateTime.Minute); + parsedDateTime.Second.Should().Be(defaultDateTime.Second); + parsedDateTime.Millisecond.Should().Be(defaultDateTime.Millisecond); + } + + + [Theory] [MemberData(nameof(ResetDateTime_Data))] public void SetYear_can_operate_as_expected(DateTime dateTime) diff --git a/tests/DNX.Extensions.Tests/Enums/EnumExtensionsTests.cs b/tests/DNX.Extensions.Tests/Enums/EnumExtensionsTests.cs index a64622a..7d56819 100644 --- a/tests/DNX.Extensions.Tests/Enums/EnumExtensionsTests.cs +++ b/tests/DNX.Extensions.Tests/Enums/EnumExtensionsTests.cs @@ -1,5 +1,5 @@ using System.ComponentModel; -using DNX.Extensions.Enums; +using DNX.Extensions.Enumerations; using FluentAssertions; using Xunit; @@ -25,7 +25,7 @@ public enum MyType public class EnumExtensionsTests { [Theory] - [InlineData(MyType.One, "One")] + [InlineData(MyType.One, null)] [InlineData(MyType.Two, "Number 2")] [InlineData(MyType.Three, "")] [InlineData(MyType.Four, null)] @@ -38,5 +38,20 @@ public void GetDescription_can_retrieve_value_correctly(MyType myType, string ex // Assert result.Should().Be(expectedResult, $"{myType} has description: {result}"); } + + [Theory] + [InlineData(MyType.One, "One")] + [InlineData(MyType.Two, "Number 2")] + [InlineData(MyType.Three, "")] + [InlineData(MyType.Four, "Four")] + [InlineData(MyType.Five, "")] + public void GetDescriptionOrName_can_retrieve_value_correctly(MyType myType, string expectedResult) + { + // Act + var result = myType.GetDescriptionOrName(); + + // Assert + result.Should().Be(expectedResult, $"{myType} has description: {result}"); + } } } diff --git a/tests/DNX.Extensions.Tests/Strings/StringExtensionsTests.cs b/tests/DNX.Extensions.Tests/Strings/StringExtensionsTests.cs index fd65d35..fff3ae8 100644 --- a/tests/DNX.Extensions.Tests/Strings/StringExtensionsTests.cs +++ b/tests/DNX.Extensions.Tests/Strings/StringExtensionsTests.cs @@ -1128,7 +1128,9 @@ public void Wordify_Tests(string text, string expectedResult) [InlineData("BobCarolgeesIsALegend", "FC", "Bob Carolgees Is A Legend")] public void Wordify_with_reserved_words_Tests(string text, string reservedWordsText, string expectedResult) { - var reservedWordsList = reservedWordsText?.Split("|").ToArray(); + var reservedWordsList = reservedWordsText? + .Split("|") + .ToArray(); // Act var result = text.Wordify(reservedWordsList);