diff --git a/Swan.Tiny/Collections/CollectionCacheRepository.cs b/Swan.Tiny/Collections/CollectionCacheRepository.cs
new file mode 100644
index 0000000..008b9a8
--- /dev/null
+++ b/Swan.Tiny/Collections/CollectionCacheRepository.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Swan.Collections {
+ ///
+ /// A thread-safe collection cache repository for types.
+ ///
+ /// The type of member to cache.
+ public class CollectionCacheRepository {
+ private readonly Lazy>> _data = new Lazy>>(() => new ConcurrentDictionary>(), true);
+
+ ///
+ /// Determines whether the cache contains the specified key.
+ ///
+ /// The key.
+ /// true if the cache contains the key, otherwise false .
+ public Boolean ContainsKey(Type key) => this._data.Value.ContainsKey(key);
+
+ ///
+ /// Retrieves the properties stored for the specified type.
+ /// If the properties are not available, it calls the factory method to retrieve them
+ /// and returns them as an array of PropertyInfo.
+ ///
+ /// The key.
+ /// The factory.
+ ///
+ /// An array of the properties stored for the specified type.
+ ///
+ ///
+ /// key
+ /// or
+ /// factory.
+ ///
+ /// type.
+ public IEnumerable Retrieve(Type key, Func> factory) {
+ if(key == null) {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ if(factory == null) {
+ throw new ArgumentNullException(nameof(factory));
+ }
+
+ return this._data.Value.GetOrAdd(key, k => factory.Invoke(k).Where(item => item != null));
+ }
+ }
+}
diff --git a/Swan.Tiny/Configuration/PropertyDisplayAttribute.cs b/Swan.Tiny/Configuration/PropertyDisplayAttribute.cs
new file mode 100644
index 0000000..c5e9d97
--- /dev/null
+++ b/Swan.Tiny/Configuration/PropertyDisplayAttribute.cs
@@ -0,0 +1,62 @@
+using System;
+
+namespace Swan.Configuration {
+ ///
+ /// An attribute used to include additional information to a Property for serialization.
+ ///
+ /// Previously we used DisplayAttribute from DataAnnotation.
+ ///
+ ///
+ [AttributeUsage(AttributeTargets.Property)]
+ public sealed class PropertyDisplayAttribute : Attribute {
+ ///
+ /// Gets or sets the name.
+ ///
+ ///
+ /// The name.
+ ///
+ public String Name {
+ get; set;
+ }
+
+ ///
+ /// Gets or sets the description.
+ ///
+ ///
+ /// The description.
+ ///
+ public String Description {
+ get; set;
+ }
+
+ ///
+ /// Gets or sets the name of the group.
+ ///
+ ///
+ /// The name of the group.
+ ///
+ public String GroupName {
+ get; set;
+ }
+
+ ///
+ /// Gets or sets the default value.
+ ///
+ ///
+ /// The default value.
+ ///
+ public Object DefaultValue {
+ get; set;
+ }
+
+ ///
+ /// Gets or sets the format string to call with method ToString .
+ ///
+ ///
+ /// The format.
+ ///
+ public String Format {
+ get; set;
+ }
+ }
+}
diff --git a/Swan.Tiny/Definitions.Types.cs b/Swan.Tiny/Definitions.Types.cs
new file mode 100644
index 0000000..6fc4742
--- /dev/null
+++ b/Swan.Tiny/Definitions.Types.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Net;
+
+using Swan.Reflection;
+
+namespace Swan {
+ ///
+ /// Contains useful constants and definitions.
+ ///
+ public static partial class Definitions {
+ #region Main Dictionary Definition
+
+ ///
+ /// The basic types information.
+ ///
+ public static readonly Lazy> BasicTypesInfo = new Lazy>(() => new Dictionary {
+ // Non-Nullables
+ {typeof(DateTime), new ExtendedTypeInfo()},
+ {typeof(Byte), new ExtendedTypeInfo()},
+ {typeof(SByte), new ExtendedTypeInfo()},
+ {typeof(Int32), new ExtendedTypeInfo()},
+ {typeof(UInt32), new ExtendedTypeInfo()},
+ {typeof(Int16), new ExtendedTypeInfo()},
+ {typeof(UInt16), new ExtendedTypeInfo()},
+ {typeof(Int64), new ExtendedTypeInfo()},
+ {typeof(UInt64), new ExtendedTypeInfo()},
+ {typeof(Single), new ExtendedTypeInfo()},
+ {typeof(Double), new ExtendedTypeInfo()},
+ {typeof(Char), new ExtendedTypeInfo()},
+ {typeof(Boolean), new ExtendedTypeInfo()},
+ {typeof(Decimal), new ExtendedTypeInfo()},
+ {typeof(Guid), new ExtendedTypeInfo()},
+
+ // Strings is also considered a basic type (it's the only basic reference type)
+ {typeof(String), new ExtendedTypeInfo()},
+
+ // Nullables
+ {typeof(DateTime?), new ExtendedTypeInfo()},
+ {typeof(Byte?), new ExtendedTypeInfo()},
+ {typeof(SByte?), new ExtendedTypeInfo()},
+ {typeof(Int32?), new ExtendedTypeInfo()},
+ {typeof(UInt32?), new ExtendedTypeInfo()},
+ {typeof(Int16?), new ExtendedTypeInfo()},
+ {typeof(UInt16?), new ExtendedTypeInfo()},
+ {typeof(Int64?), new ExtendedTypeInfo()},
+ {typeof(UInt64?), new ExtendedTypeInfo()},
+ {typeof(Single?), new ExtendedTypeInfo()},
+ {typeof(Double?), new ExtendedTypeInfo()},
+ {typeof(Char?), new ExtendedTypeInfo()},
+ {typeof(Boolean?), new ExtendedTypeInfo()},
+ {typeof(Decimal?), new ExtendedTypeInfo()},
+ {typeof(Guid?), new ExtendedTypeInfo()},
+
+ // Additional Types
+ {typeof(TimeSpan), new ExtendedTypeInfo()},
+ {typeof(TimeSpan?), new ExtendedTypeInfo()},
+ {typeof(IPAddress), new ExtendedTypeInfo()},
+ });
+
+ #endregion
+
+ ///
+ /// Contains all basic types, including string, date time, and all of their nullable counterparts.
+ ///
+ ///
+ /// All basic types.
+ ///
+ public static IReadOnlyCollection AllBasicTypes { get; } = new ReadOnlyCollection(BasicTypesInfo.Value.Keys.ToArray());
+
+ ///
+ /// Gets all numeric types including their nullable counterparts.
+ /// Note that Booleans and Guids are not considered numeric types.
+ ///
+ ///
+ /// All numeric types.
+ ///
+ public static IReadOnlyCollection AllNumericTypes {
+ get;
+ } = new ReadOnlyCollection(BasicTypesInfo.Value.Where(kvp => kvp.Value.IsNumeric).Select(kvp => kvp.Key).ToArray());
+
+ ///
+ /// Gets all numeric types without their nullable counterparts.
+ /// Note that Booleans and Guids are not considered numeric types.
+ ///
+ ///
+ /// All numeric value types.
+ ///
+ public static IReadOnlyCollection AllNumericValueTypes {
+ get;
+ } = new ReadOnlyCollection(BasicTypesInfo.Value.Where(kvp => kvp.Value.IsNumeric && !kvp.Value.IsNullableValueType).Select(kvp => kvp.Key).ToArray());
+
+ ///
+ /// Contains all basic value types. i.e. excludes string and nullables.
+ ///
+ ///
+ /// All basic value types.
+ ///
+ public static IReadOnlyCollection AllBasicValueTypes {
+ get;
+ } = new ReadOnlyCollection(BasicTypesInfo.Value.Where(kvp => kvp.Value.IsValueType).Select(kvp => kvp.Key).ToArray());
+
+ ///
+ /// Contains all basic value types including the string type. i.e. excludes nullables.
+ ///
+ ///
+ /// All basic value and string types.
+ ///
+ public static IReadOnlyCollection AllBasicValueAndStringTypes {
+ get;
+ } = new ReadOnlyCollection(BasicTypesInfo.Value.Where(kvp => kvp.Value.IsValueType || kvp.Key == typeof(String)).Select(kvp => kvp.Key).ToArray());
+
+ ///
+ /// Gets all nullable value types. i.e. excludes string and all basic value types.
+ ///
+ ///
+ /// All basic nullable value types.
+ ///
+ public static IReadOnlyCollection AllBasicNullableValueTypes {
+ get;
+ } = new ReadOnlyCollection(BasicTypesInfo.Value.Where(kvp => kvp.Value.IsNullableValueType).Select(kvp => kvp.Key).ToArray());
+ }
+}
diff --git a/Swan.Tiny/Definitions.cs b/Swan.Tiny/Definitions.cs
new file mode 100644
index 0000000..d91bed1
--- /dev/null
+++ b/Swan.Tiny/Definitions.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Text;
+
+namespace Swan {
+ ///
+ /// Contains useful constants and definitions.
+ ///
+ public static partial class Definitions {
+ ///
+ /// The MS Windows codepage 1252 encoding used in some legacy scenarios
+ /// such as default CSV text encoding from Excel.
+ ///
+ public static readonly Encoding Windows1252Encoding;
+
+ ///
+ /// The encoding associated with the default ANSI code page in the operating
+ /// system's regional and language settings.
+ ///
+ public static readonly Encoding CurrentAnsiEncoding;
+
+ ///
+ /// Initializes the class.
+ ///
+ static Definitions() {
+ CurrentAnsiEncoding = Encoding.GetEncoding(default(Int32));
+ try {
+ Windows1252Encoding = Encoding.GetEncoding(1252);
+ } catch {
+ // ignore, the codepage is not available use default
+ Windows1252Encoding = CurrentAnsiEncoding;
+ }
+ }
+ }
+}
diff --git a/Swan.Tiny/DependencyInjection/DependencyContainer.cs b/Swan.Tiny/DependencyInjection/DependencyContainer.cs
new file mode 100644
index 0000000..efbe114
--- /dev/null
+++ b/Swan.Tiny/DependencyInjection/DependencyContainer.cs
@@ -0,0 +1,575 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace Swan.DependencyInjection {
+ ///
+ /// The concrete implementation of a simple IoC container
+ /// based largely on TinyIoC (https://github.com/grumpydev/TinyIoC).
+ ///
+ ///
+ public partial class DependencyContainer : IDisposable {
+ private readonly Object _autoRegisterLock = new Object();
+
+ private Boolean _disposed;
+
+ static DependencyContainer() {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public DependencyContainer() {
+ this.RegisteredTypes = new TypesConcurrentDictionary(this);
+ _ = this.Register(this);
+ }
+
+ private DependencyContainer(DependencyContainer parent) : this() => this.Parent = parent;
+
+ ///
+ /// Lazy created Singleton instance of the container for simple scenarios.
+ ///
+ public static DependencyContainer Current { get; } = new DependencyContainer();
+
+ internal DependencyContainer Parent {
+ get;
+ }
+
+ internal TypesConcurrentDictionary RegisteredTypes {
+ get;
+ }
+
+ ///
+ public void Dispose() {
+ if(this._disposed) {
+ return;
+ }
+
+ this._disposed = true;
+
+ foreach(IDisposable disposable in this.RegisteredTypes.Values.Select(item => item as IDisposable)) {
+ disposable?.Dispose();
+ }
+
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Gets the child container.
+ ///
+ /// A new instance of the class.
+ public DependencyContainer GetChildContainer() => new DependencyContainer(this);
+
+ #region Registration
+
+ ///
+ /// Attempt to automatically register all non-generic classes and interfaces in the current app domain.
+ /// Types will only be registered if they pass the supplied registration predicate.
+ ///
+ /// What action to take when encountering duplicate implementations of an interface/base class.
+ /// Predicate to determine if a particular type should be registered.
+ public void AutoRegister(DependencyContainerDuplicateImplementationAction duplicateAction = DependencyContainerDuplicateImplementationAction.RegisterSingle, Func registrationPredicate = null) => this.AutoRegister(AppDomain.CurrentDomain.GetAssemblies().Where(a => !IsIgnoredAssembly(a)), duplicateAction, registrationPredicate);
+
+ ///
+ /// Attempt to automatically register all non-generic classes and interfaces in the specified assemblies
+ /// Types will only be registered if they pass the supplied registration predicate.
+ ///
+ /// Assemblies to process.
+ /// What action to take when encountering duplicate implementations of an interface/base class.
+ /// Predicate to determine if a particular type should be registered.
+ public void AutoRegister(IEnumerable assemblies, DependencyContainerDuplicateImplementationAction duplicateAction = DependencyContainerDuplicateImplementationAction.RegisterSingle, Func registrationPredicate = null) {
+ lock(this._autoRegisterLock) {
+ List types = assemblies.SelectMany(a => a.GetAllTypes()).Where(t => !IsIgnoredType(t, registrationPredicate)).ToList();
+
+ List concreteTypes = types.Where(type => type.IsClass && !type.IsAbstract && type != this.GetType() && type.DeclaringType != this.GetType() && !type.IsGenericTypeDefinition).ToList();
+
+ foreach(Type type in concreteTypes) {
+ try {
+ _ = this.RegisteredTypes.Register(type, String.Empty, GetDefaultObjectFactory(type, type));
+ } catch(MethodAccessException) {
+ // Ignore methods we can't access - added for Silverlight
+ }
+ }
+
+ IEnumerable abstractInterfaceTypes = types.Where(type => (type.IsInterface || type.IsAbstract) && type.DeclaringType != this.GetType() && !type.IsGenericTypeDefinition);
+
+ foreach(Type type in abstractInterfaceTypes) {
+ Type localType = type;
+ List implementations = concreteTypes.Where(implementationType => localType.IsAssignableFrom(implementationType)).ToList();
+
+ if(implementations.Skip(1).Any()) {
+ if(duplicateAction == DependencyContainerDuplicateImplementationAction.Fail) {
+ throw new DependencyContainerRegistrationException(type, implementations);
+ }
+
+ if(duplicateAction == DependencyContainerDuplicateImplementationAction.RegisterMultiple) {
+ _ = this.RegisterMultiple(type, implementations);
+ }
+ }
+
+ Type firstImplementation = implementations.FirstOrDefault();
+
+ if(firstImplementation == null) {
+ continue;
+ }
+
+ try {
+ _ = this.RegisteredTypes.Register(type, String.Empty, GetDefaultObjectFactory(type, firstImplementation));
+ } catch(MethodAccessException) {
+ // Ignore methods we can't access - added for Silverlight
+ }
+ }
+ }
+ }
+
+ ///
+ /// Creates/replaces a named container class registration with default options.
+ ///
+ /// Type to register.
+ /// Name of registration.
+ /// RegisterOptions for fluent API.
+ public RegisterOptions Register(Type registerType, String name = "") => this.RegisteredTypes.Register(registerType, name, GetDefaultObjectFactory(registerType, registerType));
+
+ ///
+ /// Creates/replaces a named container class registration with a given implementation and default options.
+ ///
+ /// Type to register.
+ /// Type to instantiate that implements RegisterType.
+ /// Name of registration.
+ /// RegisterOptions for fluent API.
+ public RegisterOptions Register(Type registerType, Type registerImplementation, String name = "") => this.RegisteredTypes.Register(registerType, name, GetDefaultObjectFactory(registerType, registerImplementation));
+
+ ///
+ /// Creates/replaces a named container class registration with a specific, strong referenced, instance.
+ ///
+ /// Type to register.
+ /// Instance of RegisterType to register.
+ /// Name of registration.
+ /// RegisterOptions for fluent API.
+ public RegisterOptions Register(Type registerType, Object instance, String name = "") => this.RegisteredTypes.Register(registerType, name, new InstanceFactory(registerType, registerType, instance));
+
+ ///
+ /// Creates/replaces a named container class registration with a specific, strong referenced, instance.
+ ///
+ /// Type to register.
+ /// Type of instance to register that implements RegisterType.
+ /// Instance of RegisterImplementation to register.
+ /// Name of registration.
+ /// RegisterOptions for fluent API.
+ public RegisterOptions Register(Type registerType, Type registerImplementation, Object instance, String name = "") => this.RegisteredTypes.Register(registerType, name, new InstanceFactory(registerType, registerImplementation, instance));
+
+ ///
+ /// Creates/replaces a container class registration with a user specified factory.
+ ///
+ /// Type to register.
+ /// Factory/lambda that returns an instance of RegisterType.
+ /// Name of registration.
+ /// RegisterOptions for fluent API.
+ public RegisterOptions Register(Type registerType, Func, Object> factory, String name = "") => this.RegisteredTypes.Register(registerType, name, new DelegateFactory(registerType, factory));
+
+ ///
+ /// Creates/replaces a named container class registration with default options.
+ ///
+ /// Type to register.
+ /// Name of registration.
+ /// RegisterOptions for fluent API.
+ public RegisterOptions Register(String name = "") where TRegister : class => this.Register(typeof(TRegister), name);
+
+ ///
+ /// Creates/replaces a named container class registration with a given implementation and default options.
+ ///
+ /// Type to register.
+ /// Type to instantiate that implements RegisterType.
+ /// Name of registration.
+ /// RegisterOptions for fluent API.
+ public RegisterOptions Register(String name = "") where TRegister : class where TRegisterImplementation : class, TRegister => this.Register(typeof(TRegister), typeof(TRegisterImplementation), name);
+
+ ///
+ /// Creates/replaces a named container class registration with a specific, strong referenced, instance.
+ ///
+ /// Type to register.
+ /// Instance of RegisterType to register.
+ /// Name of registration.
+ /// RegisterOptions for fluent API.
+ public RegisterOptions Register(TRegister instance, String name = "") where TRegister : class => this.Register(typeof(TRegister), instance, name);
+
+ ///
+ /// Creates/replaces a named container class registration with a specific, strong referenced, instance.
+ ///
+ /// Type to register.
+ /// Type of instance to register that implements RegisterType.
+ /// Instance of RegisterImplementation to register.
+ /// Name of registration.
+ /// RegisterOptions for fluent API.
+ public RegisterOptions Register(TRegisterImplementation instance, String name = "") where TRegister : class where TRegisterImplementation : class, TRegister => this.Register(typeof(TRegister), typeof(TRegisterImplementation), instance, name);
+
+ ///
+ /// Creates/replaces a named container class registration with a user specified factory.
+ ///
+ /// Type to register.
+ /// Factory/lambda that returns an instance of RegisterType.
+ /// Name of registration.
+ /// RegisterOptions for fluent API.
+ public RegisterOptions Register(Func, TRegister> factory, String name = "") where TRegister : class {
+ if(factory == null) {
+ throw new ArgumentNullException(nameof(factory));
+ }
+
+ return this.Register(typeof(TRegister), factory, name);
+ }
+
+ ///
+ /// Register multiple implementations of a type.
+ ///
+ /// Internally this registers each implementation using the full name of the class as its registration name.
+ ///
+ /// Type that each implementation implements.
+ /// Types that implement RegisterType.
+ /// MultiRegisterOptions for the fluent API.
+ public MultiRegisterOptions RegisterMultiple(IEnumerable implementationTypes) => this.RegisterMultiple(typeof(TRegister), implementationTypes);
+
+ ///
+ /// Register multiple implementations of a type.
+ ///
+ /// Internally this registers each implementation using the full name of the class as its registration name.
+ ///
+ /// Type that each implementation implements.
+ /// Types that implement RegisterType.
+ /// MultiRegisterOptions for the fluent API.
+ public MultiRegisterOptions RegisterMultiple(Type registrationType, IEnumerable implementationTypes) {
+ if(implementationTypes == null) {
+ throw new ArgumentNullException(nameof(implementationTypes), "types is null.");
+ }
+
+ foreach(Type type in implementationTypes.Where(type => !registrationType.IsAssignableFrom(type))) {
+ throw new ArgumentException($"types: The type {registrationType.FullName} is not assignable from {type.FullName}");
+ }
+
+ if(implementationTypes.Count() != implementationTypes.Distinct().Count()) {
+ IEnumerable queryForDuplicatedTypes = implementationTypes.GroupBy(i => i).Where(j => j.Count() > 1).Select(j => j.Key.FullName);
+
+ String fullNamesOfDuplicatedTypes = String.Join(",\n", queryForDuplicatedTypes.ToArray());
+
+ throw new ArgumentException($"types: The same implementation type cannot be specified multiple times for {registrationType.FullName}\n\n{fullNamesOfDuplicatedTypes}");
+ }
+
+ List registerOptions = implementationTypes.Select(type => this.Register(registrationType, type, type.FullName)).ToList();
+
+ return new MultiRegisterOptions(registerOptions);
+ }
+
+ #endregion
+
+ #region Unregistration
+
+ ///
+ /// Remove a named container class registration.
+ ///
+ /// Type to unregister.
+ /// Name of registration.
+ /// true if the registration is successfully found and removed; otherwise, false .
+ public Boolean Unregister(String name = "") => this.Unregister(typeof(TRegister), name);
+
+ ///
+ /// Remove a named container class registration.
+ ///
+ /// Type to unregister.
+ /// Name of registration.
+ /// true if the registration is successfully found and removed; otherwise, false .
+ public Boolean Unregister(Type registerType, String name = "") => this.RegisteredTypes.RemoveRegistration(new TypeRegistration(registerType, name));
+
+ #endregion
+
+ #region Resolution
+
+ ///
+ /// Attempts to resolve a named type using specified options and the supplied constructor parameters.
+ ///
+ /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists).
+ /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail.
+ ///
+ /// Type to resolve.
+ /// Name of registration.
+ /// Resolution options.
+ /// Instance of type.
+ /// Unable to resolve the type.
+ public Object Resolve(Type resolveType, String name = null, DependencyContainerResolveOptions options = null) => this.RegisteredTypes.ResolveInternal(new TypeRegistration(resolveType, name), options ?? DependencyContainerResolveOptions.Default);
+
+ ///
+ /// Attempts to resolve a named type using specified options and the supplied constructor parameters.
+ ///
+ /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists).
+ /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail.
+ ///
+ /// Type to resolve.
+ /// Name of registration.
+ /// Resolution options.
+ /// Instance of type.
+ /// Unable to resolve the type.
+ public TResolveType Resolve(String name = null, DependencyContainerResolveOptions options = null) where TResolveType : class => (TResolveType)this.Resolve(typeof(TResolveType), name, options);
+
+ ///
+ /// Attempts to predict whether a given type can be resolved with the supplied constructor parameters options.
+ /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists).
+ /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail.
+ /// Note: Resolution may still fail if user defined factory registrations fail to construct objects when called.
+ ///
+ /// Type to resolve.
+ /// The name.
+ /// Resolution options.
+ ///
+ /// Bool indicating whether the type can be resolved.
+ ///
+ public Boolean CanResolve(Type resolveType, String name = null, DependencyContainerResolveOptions options = null) => this.RegisteredTypes.CanResolve(new TypeRegistration(resolveType, name), options);
+
+ ///
+ /// Attempts to predict whether a given named type can be resolved with the supplied constructor parameters options.
+ ///
+ /// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists).
+ /// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail.
+ ///
+ /// Note: Resolution may still fail if user defined factory registrations fail to construct objects when called.
+ ///
+ /// Type to resolve.
+ /// Name of registration.
+ /// Resolution options.
+ /// Bool indicating whether the type can be resolved.
+ public Boolean CanResolve(String name = null, DependencyContainerResolveOptions options = null) where TResolveType : class => this.CanResolve(typeof(TResolveType), name, options);
+
+ ///
+ /// Attempts to resolve a type using the default options.
+ ///
+ /// Type to resolve.
+ /// Resolved type or default if resolve fails.
+ /// true if resolved successfully, false otherwise.
+ public Boolean TryResolve(Type resolveType, out Object resolvedType) {
+ try {
+ resolvedType = this.Resolve(resolveType);
+ return true;
+ } catch(DependencyContainerResolutionException) {
+ resolvedType = null;
+ return false;
+ }
+ }
+
+ ///
+ /// Attempts to resolve a type using the given options.
+ ///
+ /// Type to resolve.
+ /// Resolution options.
+ /// Resolved type or default if resolve fails.
+ /// true if resolved successfully, false otherwise.
+ public Boolean TryResolve(Type resolveType, DependencyContainerResolveOptions options, out Object resolvedType) {
+ try {
+ resolvedType = this.Resolve(resolveType, options: options);
+ return true;
+ } catch(DependencyContainerResolutionException) {
+ resolvedType = null;
+ return false;
+ }
+ }
+
+ ///
+ /// Attempts to resolve a type using the default options and given name.
+ ///
+ /// Type to resolve.
+ /// Name of registration.
+ /// Resolved type or default if resolve fails.
+ /// true if resolved successfully, false otherwise.
+ public Boolean TryResolve(Type resolveType, String name, out Object resolvedType) {
+ try {
+ resolvedType = this.Resolve(resolveType, name);
+ return true;
+ } catch(DependencyContainerResolutionException) {
+ resolvedType = null;
+ return false;
+ }
+ }
+
+ ///
+ /// Attempts to resolve a type using the given options and name.
+ ///
+ /// Type to resolve.
+ /// Name of registration.
+ /// Resolution options.
+ /// Resolved type or default if resolve fails.
+ /// true if resolved successfully, false otherwise.
+ public Boolean TryResolve(Type resolveType, String name, DependencyContainerResolveOptions options, out Object resolvedType) {
+ try {
+ resolvedType = this.Resolve(resolveType, name, options);
+ return true;
+ } catch(DependencyContainerResolutionException) {
+ resolvedType = null;
+ return false;
+ }
+ }
+
+ ///
+ /// Attempts to resolve a type using the default options.
+ ///
+ /// Type to resolve.
+ /// Resolved type or default if resolve fails.
+ /// true if resolved successfully, false otherwise.
+ public Boolean TryResolve(out TResolveType resolvedType) where TResolveType : class {
+ try {
+ resolvedType = this.Resolve();
+ return true;
+ } catch(DependencyContainerResolutionException) {
+ resolvedType = default;
+ return false;
+ }
+ }
+
+ ///
+ /// Attempts to resolve a type using the given options.
+ ///
+ /// Type to resolve.
+ /// Resolution options.
+ /// Resolved type or default if resolve fails.
+ /// true if resolved successfully, false otherwise.
+ public Boolean TryResolve(DependencyContainerResolveOptions options, out TResolveType resolvedType) where TResolveType : class {
+ try {
+ resolvedType = this.Resolve(options: options);
+ return true;
+ } catch(DependencyContainerResolutionException) {
+ resolvedType = default;
+ return false;
+ }
+ }
+
+ ///
+ /// Attempts to resolve a type using the default options and given name.
+ ///
+ /// Type to resolve.
+ /// Name of registration.
+ /// Resolved type or default if resolve fails.
+ /// true if resolved successfully, false otherwise.
+ public Boolean TryResolve(String name, out TResolveType resolvedType) where TResolveType : class {
+ try {
+ resolvedType = this.Resolve(name);
+ return true;
+ } catch(DependencyContainerResolutionException) {
+ resolvedType = default;
+ return false;
+ }
+ }
+
+ ///
+ /// Attempts to resolve a type using the given options and name.
+ ///
+ /// Type to resolve.
+ /// Name of registration.
+ /// Resolution options.
+ /// Resolved type or default if resolve fails.
+ /// true if resolved successfully, false otherwise.
+ public Boolean TryResolve(String name, DependencyContainerResolveOptions options, out TResolveType resolvedType) where TResolveType : class {
+ try {
+ resolvedType = this.Resolve(name, options);
+ return true;
+ } catch(DependencyContainerResolutionException) {
+ resolvedType = default;
+ return false;
+ }
+ }
+
+ ///
+ /// Returns all registrations of a type.
+ ///
+ /// Type to resolveAll.
+ /// Whether to include un-named (default) registrations.
+ /// IEnumerable.
+ public IEnumerable ResolveAll(Type resolveType, Boolean includeUnnamed = false) => this.RegisteredTypes.Resolve(resolveType, includeUnnamed);
+
+ ///
+ /// Returns all registrations of a type.
+ ///
+ /// Type to resolveAll.
+ /// Whether to include un-named (default) registrations.
+ /// IEnumerable.
+ public IEnumerable ResolveAll(Boolean includeUnnamed = true) where TResolveType : class => this.ResolveAll(typeof(TResolveType), includeUnnamed).Cast();
+
+ ///
+ /// Attempts to resolve all public property dependencies on the given object using the given resolve options.
+ ///
+ /// Object to "build up".
+ /// Resolve options to use.
+ public void BuildUp(Object input, DependencyContainerResolveOptions resolveOptions = null) {
+ if(resolveOptions == null) {
+ resolveOptions = DependencyContainerResolveOptions.Default;
+ }
+
+ IEnumerable properties = input.GetType().GetProperties().Where(property => property.GetCacheGetMethod() != null && property.GetCacheSetMethod() != null && !property.PropertyType.IsValueType);
+
+ foreach(PropertyInfo property in properties.Where(property => property.GetValue(input, null) == null)) {
+ try {
+ property.SetValue(input, this.RegisteredTypes.ResolveInternal(new TypeRegistration(property.PropertyType), resolveOptions), null);
+ } catch(DependencyContainerResolutionException) {
+ // Catch any resolution errors and ignore them
+ }
+ }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal static Boolean IsValidAssignment(Type registerType, Type registerImplementation) {
+ if(!registerType.IsGenericTypeDefinition) {
+ if(!registerType.IsAssignableFrom(registerImplementation)) {
+ return false;
+ }
+ } else {
+ if(registerType.IsInterface && registerImplementation.GetInterfaces().All(t => t.Name != registerType.Name)) {
+ return false;
+ }
+
+ if(registerType.IsAbstract && registerImplementation.BaseType != registerType) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static Boolean IsIgnoredAssembly(Assembly assembly) {
+ // TODO - find a better way to remove "system" assemblies from the auto registration
+ List> ignoreChecks = new List>
+ {
+ asm => asm.FullName.StartsWith("Microsoft.", StringComparison.Ordinal),
+ asm => asm.FullName.StartsWith("System.", StringComparison.Ordinal),
+ asm => asm.FullName.StartsWith("System,", StringComparison.Ordinal),
+ asm => asm.FullName.StartsWith("CR_ExtUnitTest", StringComparison.Ordinal),
+ asm => asm.FullName.StartsWith("mscorlib,", StringComparison.Ordinal),
+ asm => asm.FullName.StartsWith("CR_VSTest", StringComparison.Ordinal),
+ asm => asm.FullName.StartsWith("DevExpress.CodeRush", StringComparison.Ordinal),
+ asm => asm.FullName.StartsWith("xunit.", StringComparison.Ordinal),
+ };
+
+ return ignoreChecks.Any(check => check(assembly));
+ }
+
+ private static Boolean IsIgnoredType(Type type, Func registrationPredicate) {
+ // TODO - find a better way to remove "system" types from the auto registration
+ List> ignoreChecks = new List>()
+ {
+ t => t.FullName?.StartsWith("System.", StringComparison.Ordinal) ?? false,
+ t => t.FullName?.StartsWith("Microsoft.", StringComparison.Ordinal) ?? false,
+ t => t.IsPrimitive,
+ t => t.IsGenericTypeDefinition,
+ t => t.GetConstructors(BindingFlags.Instance | BindingFlags.Public).Length == 0 &&
+ !(t.IsInterface || t.IsAbstract),
+ };
+
+ if(registrationPredicate != null) {
+ ignoreChecks.Add(t => !registrationPredicate(t));
+ }
+
+ return ignoreChecks.Any(check => check(type));
+ }
+
+ private static ObjectFactoryBase GetDefaultObjectFactory(Type registerType, Type registerImplementation) => registerType.IsInterface || registerType.IsAbstract ? (ObjectFactoryBase)new SingletonFactory(registerType, registerImplementation) : new MultiInstanceFactory(registerType, registerImplementation);
+
+ #endregion
+ }
+}
diff --git a/Swan.Tiny/DependencyInjection/DependencyContainerRegistrationException.cs b/Swan.Tiny/DependencyInjection/DependencyContainerRegistrationException.cs
new file mode 100644
index 0000000..38dfae6
--- /dev/null
+++ b/Swan.Tiny/DependencyInjection/DependencyContainerRegistrationException.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Swan.DependencyInjection {
+ ///
+ /// Generic Constraint Registration Exception.
+ ///
+ ///
+ public class DependencyContainerRegistrationException : Exception {
+ private const String ConvertErrorText = "Cannot convert current registration of {0} to {1}";
+ private const String RegisterErrorText = "Cannot register type {0} - abstract classes or interfaces are not valid implementation types for {1}.";
+ private const String ErrorText = "Duplicate implementation of type {0} found ({1}).";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Type of the register.
+ /// The types.
+ public DependencyContainerRegistrationException(Type registerType, IEnumerable types) : base(String.Format(ErrorText, registerType, GetTypesString(types))) {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The type.
+ /// The method.
+ /// if set to true [is type factory].
+ public DependencyContainerRegistrationException(Type type, String method, Boolean isTypeFactory = false) : base(isTypeFactory ? String.Format(RegisterErrorText, type.FullName, method) : String.Format(ConvertErrorText, type.FullName, method)) {
+ }
+
+ private static String GetTypesString(IEnumerable types) => String.Join(",", types.Select(type => type.FullName));
+ }
+}
\ No newline at end of file
diff --git a/Swan.Tiny/DependencyInjection/DependencyContainerResolutionException.cs b/Swan.Tiny/DependencyInjection/DependencyContainerResolutionException.cs
new file mode 100644
index 0000000..63d6795
--- /dev/null
+++ b/Swan.Tiny/DependencyInjection/DependencyContainerResolutionException.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace Swan.DependencyInjection {
+ ///
+ /// An exception for dependency resolutions.
+ ///
+ ///
+ [Serializable]
+ public class DependencyContainerResolutionException : Exception {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The type.
+ public DependencyContainerResolutionException(Type type) : base($"Unable to resolve type: {type.FullName}") {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The type.
+ /// The inner exception.
+ public DependencyContainerResolutionException(Type type, Exception innerException) : base($"Unable to resolve type: {type.FullName}", innerException) {
+ }
+ }
+}
diff --git a/Swan.Tiny/DependencyInjection/DependencyContainerResolveOptions.cs b/Swan.Tiny/DependencyInjection/DependencyContainerResolveOptions.cs
new file mode 100644
index 0000000..4bf2546
--- /dev/null
+++ b/Swan.Tiny/DependencyInjection/DependencyContainerResolveOptions.cs
@@ -0,0 +1,106 @@
+using System.Collections.Generic;
+
+namespace Swan.DependencyInjection {
+ ///
+ /// Resolution settings.
+ ///
+ public class DependencyContainerResolveOptions {
+ ///
+ /// Gets the default options (attempt resolution of unregistered types, fail on named resolution if name not found).
+ ///
+ public static DependencyContainerResolveOptions Default { get; } = new DependencyContainerResolveOptions();
+
+ ///
+ /// Gets or sets the unregistered resolution action.
+ ///
+ ///
+ /// The unregistered resolution action.
+ ///
+ public DependencyContainerUnregisteredResolutionAction UnregisteredResolutionAction { get; set; } = DependencyContainerUnregisteredResolutionAction.AttemptResolve;
+
+ ///
+ /// Gets or sets the named resolution failure action.
+ ///
+ ///
+ /// The named resolution failure action.
+ ///
+ public DependencyContainerNamedResolutionFailureAction NamedResolutionFailureAction { get; set; } = DependencyContainerNamedResolutionFailureAction.Fail;
+
+ ///
+ /// Gets the constructor parameters.
+ ///
+ ///
+ /// The constructor parameters.
+ ///
+ public Dictionary ConstructorParameters { get; } = new Dictionary();
+
+ ///
+ /// Clones this instance.
+ ///
+ ///
+ public DependencyContainerResolveOptions Clone() => new DependencyContainerResolveOptions {
+ NamedResolutionFailureAction = NamedResolutionFailureAction,
+ UnregisteredResolutionAction = UnregisteredResolutionAction,
+ };
+ }
+
+ ///
+ /// Defines Resolution actions.
+ ///
+ public enum DependencyContainerUnregisteredResolutionAction {
+ ///
+ /// Attempt to resolve type, even if the type isn't registered.
+ ///
+ /// Registered types/options will always take precedence.
+ ///
+ AttemptResolve,
+
+ ///
+ /// Fail resolution if type not explicitly registered
+ ///
+ Fail,
+
+ ///
+ /// Attempt to resolve unregistered type if requested type is generic
+ /// and no registration exists for the specific generic parameters used.
+ ///
+ /// Registered types/options will always take precedence.
+ ///
+ GenericsOnly,
+ }
+
+ ///
+ /// Enumerates failure actions.
+ ///
+ public enum DependencyContainerNamedResolutionFailureAction {
+ ///
+ /// The attempt unnamed resolution
+ ///
+ AttemptUnnamedResolution,
+
+ ///
+ /// The fail
+ ///
+ Fail,
+ }
+
+ ///
+ /// Enumerates duplicate definition actions.
+ ///
+ public enum DependencyContainerDuplicateImplementationAction {
+ ///
+ /// The register single
+ ///
+ RegisterSingle,
+
+ ///
+ /// The register multiple
+ ///
+ RegisterMultiple,
+
+ ///
+ /// The fail
+ ///
+ Fail,
+ }
+}
\ No newline at end of file
diff --git a/Swan.Tiny/DependencyInjection/DependencyContainerWeakReferenceException.cs b/Swan.Tiny/DependencyInjection/DependencyContainerWeakReferenceException.cs
new file mode 100644
index 0000000..22953f4
--- /dev/null
+++ b/Swan.Tiny/DependencyInjection/DependencyContainerWeakReferenceException.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace Swan.DependencyInjection {
+ ///
+ /// Weak Reference Exception.
+ ///
+ ///
+ public class DependencyContainerWeakReferenceException : Exception {
+ private const String ErrorText = "Unable to instantiate {0} - referenced object has been reclaimed";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The type.
+ public DependencyContainerWeakReferenceException(Type type) : base(String.Format(ErrorText, type.FullName)) {
+ }
+ }
+}
diff --git a/Swan.Tiny/DependencyInjection/ObjectFactoryBase.cs b/Swan.Tiny/DependencyInjection/ObjectFactoryBase.cs
new file mode 100644
index 0000000..d2189ee
--- /dev/null
+++ b/Swan.Tiny/DependencyInjection/ObjectFactoryBase.cs
@@ -0,0 +1,352 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+
+namespace Swan.DependencyInjection {
+ ///
+ /// Represents an abstract class for Object Factory.
+ ///
+ public abstract class ObjectFactoryBase {
+ ///
+ /// Whether to assume this factory successfully constructs its objects
+ ///
+ /// Generally set to true for delegate style factories as CanResolve cannot delve
+ /// into the delegates they contain.
+ ///
+ public virtual Boolean AssumeConstruction => false;
+
+ ///
+ /// The type the factory instantiates.
+ ///
+ public abstract Type CreatesType {
+ get;
+ }
+
+ ///
+ /// Constructor to use, if specified.
+ ///
+ public ConstructorInfo Constructor {
+ get; private set;
+ }
+
+ ///
+ /// Gets the singleton variant.
+ ///
+ ///
+ /// The singleton variant.
+ ///
+ /// singleton.
+ public virtual ObjectFactoryBase SingletonVariant => throw new DependencyContainerRegistrationException(this.GetType(), "singleton");
+
+ ///
+ /// Gets the multi instance variant.
+ ///
+ ///
+ /// The multi instance variant.
+ ///
+ /// multi-instance.
+ public virtual ObjectFactoryBase MultiInstanceVariant => throw new DependencyContainerRegistrationException(this.GetType(), "multi-instance");
+
+ ///
+ /// Gets the strong reference variant.
+ ///
+ ///
+ /// The strong reference variant.
+ ///
+ /// strong reference.
+ public virtual ObjectFactoryBase StrongReferenceVariant => throw new DependencyContainerRegistrationException(this.GetType(), "strong reference");
+
+ ///
+ /// Gets the weak reference variant.
+ ///
+ ///
+ /// The weak reference variant.
+ ///
+ /// weak reference.
+ public virtual ObjectFactoryBase WeakReferenceVariant => throw new DependencyContainerRegistrationException(this.GetType(), "weak reference");
+
+ ///
+ /// Create the type.
+ ///
+ /// Type user requested to be resolved.
+ /// Container that requested the creation.
+ /// The options.
+ /// Instance of type.
+ public abstract Object GetObject(Type requestedType, DependencyContainer container, DependencyContainerResolveOptions options);
+
+ ///
+ /// Gets the factory for child container.
+ ///
+ /// The type.
+ /// The parent.
+ /// The child.
+ ///
+ public virtual ObjectFactoryBase GetFactoryForChildContainer(Type type, DependencyContainer parent, DependencyContainer child) => this;
+ }
+
+ ///
+ ///
+ /// IObjectFactory that creates new instances of types for each resolution.
+ ///
+ internal class MultiInstanceFactory : ObjectFactoryBase {
+ private readonly Type _registerType;
+ private readonly Type _registerImplementation;
+
+ public MultiInstanceFactory(Type registerType, Type registerImplementation) {
+ if(registerImplementation.IsAbstract || registerImplementation.IsInterface) {
+ throw new DependencyContainerRegistrationException(registerImplementation, "MultiInstanceFactory", true);
+ }
+
+ if(!DependencyContainer.IsValidAssignment(registerType, registerImplementation)) {
+ throw new DependencyContainerRegistrationException(registerImplementation, "MultiInstanceFactory", true);
+ }
+
+ this._registerType = registerType;
+ this._registerImplementation = registerImplementation;
+ }
+
+ public override Type CreatesType => this._registerImplementation;
+
+ public override ObjectFactoryBase SingletonVariant =>
+ new SingletonFactory(this._registerType, this._registerImplementation);
+
+ public override ObjectFactoryBase MultiInstanceVariant => this;
+
+ public override Object GetObject(Type requestedType, DependencyContainer container, DependencyContainerResolveOptions options) {
+ try {
+ return container.RegisteredTypes.ConstructType(this._registerImplementation, this.Constructor, options);
+ } catch(DependencyContainerResolutionException ex) {
+ throw new DependencyContainerResolutionException(this._registerType, ex);
+ }
+ }
+ }
+
+ ///
+ ///
+ /// IObjectFactory that invokes a specified delegate to construct the object.
+ ///
+ internal class DelegateFactory : ObjectFactoryBase {
+ private readonly Type _registerType;
+
+ private readonly Func, Object> _factory;
+
+ public DelegateFactory(
+ Type registerType,
+ Func, Object> factory) {
+ this._factory = factory ?? throw new ArgumentNullException(nameof(factory));
+
+ this._registerType = registerType;
+ }
+
+ public override Boolean AssumeConstruction => true;
+
+ public override Type CreatesType => this._registerType;
+
+ public override ObjectFactoryBase WeakReferenceVariant => new WeakDelegateFactory(this._registerType, this._factory);
+
+ public override ObjectFactoryBase StrongReferenceVariant => this;
+
+ public override Object GetObject(Type requestedType, DependencyContainer container, DependencyContainerResolveOptions options) {
+ try {
+ return this._factory.Invoke(container, options.ConstructorParameters);
+ } catch(Exception ex) {
+ throw new DependencyContainerResolutionException(this._registerType, ex);
+ }
+ }
+ }
+
+ ///
+ ///
+ /// IObjectFactory that invokes a specified delegate to construct the object
+ /// Holds the delegate using a weak reference.
+ ///
+ internal class WeakDelegateFactory : ObjectFactoryBase {
+ private readonly Type _registerType;
+
+ private readonly WeakReference _factory;
+
+ public WeakDelegateFactory(Type registerType, Func, Object> factory) {
+ if(factory == null) {
+ throw new ArgumentNullException(nameof(factory));
+ }
+
+ this._factory = new WeakReference(factory);
+
+ this._registerType = registerType;
+ }
+
+ public override Boolean AssumeConstruction => true;
+
+ public override Type CreatesType => this._registerType;
+
+ public override ObjectFactoryBase StrongReferenceVariant {
+ get {
+ if(!(this._factory.Target is Func, global::System.Object> factory)) {
+ throw new DependencyContainerWeakReferenceException(this._registerType);
+ }
+
+ return new DelegateFactory(this._registerType, factory);
+ }
+ }
+
+ public override ObjectFactoryBase WeakReferenceVariant => this;
+
+ public override Object GetObject(Type requestedType, DependencyContainer container, DependencyContainerResolveOptions options) {
+ if(!(this._factory.Target is Func, global::System.Object> factory)) {
+ throw new DependencyContainerWeakReferenceException(this._registerType);
+ }
+
+ try {
+ return factory.Invoke(container, options.ConstructorParameters);
+ } catch(Exception ex) {
+ throw new DependencyContainerResolutionException(this._registerType, ex);
+ }
+ }
+ }
+
+ ///
+ /// Stores an particular instance to return for a type.
+ ///
+ internal class InstanceFactory : ObjectFactoryBase, IDisposable {
+ private readonly Type _registerType;
+ private readonly Type _registerImplementation;
+ private readonly Object _instance;
+
+ public InstanceFactory(Type registerType, Type registerImplementation, Object instance) {
+ if(!DependencyContainer.IsValidAssignment(registerType, registerImplementation)) {
+ throw new DependencyContainerRegistrationException(registerImplementation, "InstanceFactory", true);
+ }
+
+ this._registerType = registerType;
+ this._registerImplementation = registerImplementation;
+ this._instance = instance;
+ }
+
+ public override Boolean AssumeConstruction => true;
+
+ public override Type CreatesType => this._registerImplementation;
+
+ public override ObjectFactoryBase MultiInstanceVariant => new MultiInstanceFactory(this._registerType, this._registerImplementation);
+
+ public override ObjectFactoryBase WeakReferenceVariant => new WeakInstanceFactory(this._registerType, this._registerImplementation, this._instance);
+
+ public override ObjectFactoryBase StrongReferenceVariant => this;
+
+ public override Object GetObject(Type requestedType, DependencyContainer container, DependencyContainerResolveOptions options) => this._instance;
+
+ public void Dispose() {
+ IDisposable disposable = this._instance as IDisposable;
+
+ disposable?.Dispose();
+ }
+ }
+
+ ///
+ /// Stores the instance with a weak reference.
+ ///
+ internal class WeakInstanceFactory : ObjectFactoryBase, IDisposable {
+ private readonly Type _registerType;
+ private readonly Type _registerImplementation;
+ private readonly WeakReference _instance;
+
+ public WeakInstanceFactory(Type registerType, Type registerImplementation, Object instance) {
+ if(!DependencyContainer.IsValidAssignment(registerType, registerImplementation)) {
+ throw new DependencyContainerRegistrationException(registerImplementation, "WeakInstanceFactory", true);
+ }
+
+ this._registerType = registerType;
+ this._registerImplementation = registerImplementation;
+ this._instance = new WeakReference(instance);
+ }
+
+ public override Type CreatesType => this._registerImplementation;
+
+ public override ObjectFactoryBase MultiInstanceVariant => new MultiInstanceFactory(this._registerType, this._registerImplementation);
+
+ public override ObjectFactoryBase WeakReferenceVariant => this;
+
+ public override ObjectFactoryBase StrongReferenceVariant {
+ get {
+ Object instance = this._instance.Target;
+
+ if(instance == null) {
+ throw new DependencyContainerWeakReferenceException(this._registerType);
+ }
+
+ return new InstanceFactory(this._registerType, this._registerImplementation, instance);
+ }
+ }
+
+ public override Object GetObject(Type requestedType, DependencyContainer container, DependencyContainerResolveOptions options) {
+ Object instance = this._instance.Target;
+
+ if(instance == null) {
+ throw new DependencyContainerWeakReferenceException(this._registerType);
+ }
+
+ return instance;
+ }
+
+ public void Dispose() => (this._instance.Target as IDisposable)?.Dispose();
+ }
+
+ ///
+ /// A factory that lazy instantiates a type and always returns the same instance.
+ ///
+ internal class SingletonFactory : ObjectFactoryBase, IDisposable {
+ private readonly Type _registerType;
+ private readonly Type _registerImplementation;
+ private readonly Object _singletonLock = new Object();
+ private Object _current;
+
+ public SingletonFactory(Type registerType, Type registerImplementation) {
+ if(registerImplementation.IsAbstract || registerImplementation.IsInterface) {
+ throw new DependencyContainerRegistrationException(registerImplementation, nameof(SingletonFactory), true);
+ }
+
+ if(!DependencyContainer.IsValidAssignment(registerType, registerImplementation)) {
+ throw new DependencyContainerRegistrationException(registerImplementation, nameof(SingletonFactory), true);
+ }
+
+ this._registerType = registerType;
+ this._registerImplementation = registerImplementation;
+ }
+
+ public override Type CreatesType => this._registerImplementation;
+
+ public override ObjectFactoryBase SingletonVariant => this;
+
+ public override ObjectFactoryBase MultiInstanceVariant =>
+ new MultiInstanceFactory(this._registerType, this._registerImplementation);
+
+ public override Object GetObject(
+ Type requestedType,
+ DependencyContainer container,
+ DependencyContainerResolveOptions options) {
+ if(options.ConstructorParameters.Count != 0) {
+ throw new ArgumentException("Cannot specify parameters for singleton types");
+ }
+
+ lock(this._singletonLock) {
+ if(this._current == null) {
+ this._current = container.RegisteredTypes.ConstructType(this._registerImplementation, this.Constructor, options);
+ }
+ }
+
+ return this._current;
+ }
+
+ public override ObjectFactoryBase GetFactoryForChildContainer(
+ Type type,
+ DependencyContainer parent,
+ DependencyContainer child) {
+ // We make sure that the singleton is constructed before the child container takes the factory.
+ // Otherwise the results would vary depending on whether or not the parent container had resolved
+ // the type before the child container does.
+ _ = this.GetObject(type, parent, DependencyContainerResolveOptions.Default);
+ return this;
+ }
+
+ public void Dispose() => (this._current as IDisposable)?.Dispose();
+ }
+}
diff --git a/Swan.Tiny/DependencyInjection/RegisterOptions.cs b/Swan.Tiny/DependencyInjection/RegisterOptions.cs
new file mode 100644
index 0000000..4c83ed2
--- /dev/null
+++ b/Swan.Tiny/DependencyInjection/RegisterOptions.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Swan.DependencyInjection {
+ ///
+ /// Registration options for "fluent" API.
+ ///
+ public sealed class RegisterOptions {
+ private readonly TypesConcurrentDictionary _registeredTypes;
+ private readonly DependencyContainer.TypeRegistration _registration;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The registered types.
+ /// The registration.
+ public RegisterOptions(TypesConcurrentDictionary registeredTypes, DependencyContainer.TypeRegistration registration) {
+ this._registeredTypes = registeredTypes;
+ this._registration = registration;
+ }
+
+ ///
+ /// Make registration a singleton (single instance) if possible.
+ ///
+ /// A registration options for fluent API.
+ /// Generic constraint registration exception.
+ public RegisterOptions AsSingleton() {
+ ObjectFactoryBase currentFactory = this._registeredTypes.GetCurrentFactory(this._registration);
+
+ if(currentFactory == null) {
+ throw new DependencyContainerRegistrationException(this._registration.Type, "singleton");
+ }
+
+ return this._registeredTypes.AddUpdateRegistration(this._registration, currentFactory.SingletonVariant);
+ }
+
+ ///
+ /// Make registration multi-instance if possible.
+ ///
+ /// A registration options for fluent API.
+ /// Generic constraint registration exception.
+ public RegisterOptions AsMultiInstance() {
+ ObjectFactoryBase currentFactory = this._registeredTypes.GetCurrentFactory(this._registration);
+
+ if(currentFactory == null) {
+ throw new DependencyContainerRegistrationException(this._registration.Type, "multi-instance");
+ }
+
+ return this._registeredTypes.AddUpdateRegistration(this._registration, currentFactory.MultiInstanceVariant);
+ }
+
+ ///
+ /// Make registration hold a weak reference if possible.
+ ///
+ /// A registration options for fluent API.
+ /// Generic constraint registration exception.
+ public RegisterOptions WithWeakReference() {
+ ObjectFactoryBase currentFactory = this._registeredTypes.GetCurrentFactory(this._registration);
+
+ if(currentFactory == null) {
+ throw new DependencyContainerRegistrationException(this._registration.Type, "weak reference");
+ }
+
+ return this._registeredTypes.AddUpdateRegistration(this._registration, currentFactory.WeakReferenceVariant);
+ }
+
+ ///
+ /// Make registration hold a strong reference if possible.
+ ///
+ /// A registration options for fluent API.
+ /// Generic constraint registration exception.
+ public RegisterOptions WithStrongReference() {
+ ObjectFactoryBase currentFactory = this._registeredTypes.GetCurrentFactory(this._registration);
+
+ if(currentFactory == null) {
+ throw new DependencyContainerRegistrationException(this._registration.Type, "strong reference");
+ }
+
+ return this._registeredTypes.AddUpdateRegistration(this._registration, currentFactory.StrongReferenceVariant);
+ }
+ }
+
+ ///
+ /// Registration options for "fluent" API when registering multiple implementations.
+ ///
+ public sealed class MultiRegisterOptions {
+ private IEnumerable _registerOptions;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The register options.
+ public MultiRegisterOptions(IEnumerable registerOptions) => this._registerOptions = registerOptions;
+
+ ///
+ /// Make registration a singleton (single instance) if possible.
+ ///
+ /// A registration multi-instance for fluent API.
+ /// Generic Constraint Registration Exception.
+ public MultiRegisterOptions AsSingleton() {
+ this._registerOptions = this.ExecuteOnAllRegisterOptions(ro => ro.AsSingleton());
+ return this;
+ }
+
+ ///
+ /// Make registration multi-instance if possible.
+ ///
+ /// A registration multi-instance for fluent API.
+ /// Generic Constraint Registration Exception.
+ public MultiRegisterOptions AsMultiInstance() {
+ this._registerOptions = this.ExecuteOnAllRegisterOptions(ro => ro.AsMultiInstance());
+ return this;
+ }
+
+ private IEnumerable ExecuteOnAllRegisterOptions(
+ Func action) => this._registerOptions.Select(action).ToList();
+ }
+}
\ No newline at end of file
diff --git a/Swan.Tiny/DependencyInjection/TypeRegistration.cs b/Swan.Tiny/DependencyInjection/TypeRegistration.cs
new file mode 100644
index 0000000..2af00c0
--- /dev/null
+++ b/Swan.Tiny/DependencyInjection/TypeRegistration.cs
@@ -0,0 +1,61 @@
+using System;
+
+namespace Swan.DependencyInjection {
+ public partial class DependencyContainer {
+ ///
+ /// Represents a Type Registration within the IoC Container.
+ ///
+ public sealed class TypeRegistration {
+ private readonly Int32 _hashCode;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The type.
+ /// The name.
+ public TypeRegistration(Type type, String name = null) {
+ this.Type = type;
+ this.Name = name ?? String.Empty;
+
+ this._hashCode = String.Concat(this.Type.FullName, "|", this.Name).GetHashCode();
+ }
+
+ ///
+ /// Gets the type.
+ ///
+ ///
+ /// The type.
+ ///
+ public Type Type {
+ get;
+ }
+
+ ///
+ /// Gets the name.
+ ///
+ ///
+ /// The name.
+ ///
+ public String Name {
+ get;
+ }
+
+ ///
+ /// Determines whether the specified , is equal to this instance.
+ ///
+ /// The to compare with this instance.
+ ///
+ /// true if the specified is equal to this instance; otherwise, false .
+ ///
+ public override Boolean Equals(Object obj) => !(obj is TypeRegistration typeRegistration) || typeRegistration.Type != this.Type ? false : String.Compare(this.Name, typeRegistration.Name, StringComparison.Ordinal) == 0;
+
+ ///
+ /// Returns a hash code for this instance.
+ ///
+ ///
+ /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+ ///
+ public override Int32 GetHashCode() => this._hashCode;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Swan.Tiny/DependencyInjection/TypesConcurrentDictionary.cs b/Swan.Tiny/DependencyInjection/TypesConcurrentDictionary.cs
new file mode 100644
index 0000000..143ffb1
--- /dev/null
+++ b/Swan.Tiny/DependencyInjection/TypesConcurrentDictionary.cs
@@ -0,0 +1,265 @@
+#nullable enable
+using System;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Collections.Generic;
+using System.Linq;
+using System.Collections.Concurrent;
+
+namespace Swan.DependencyInjection {
+ ///
+ /// Represents a Concurrent Dictionary for TypeRegistration.
+ ///
+ public class TypesConcurrentDictionary : ConcurrentDictionary {
+ private static readonly ConcurrentDictionary ObjectConstructorCache = new ConcurrentDictionary();
+
+ private readonly DependencyContainer _dependencyContainer;
+
+ internal TypesConcurrentDictionary(DependencyContainer dependencyContainer) => this._dependencyContainer = dependencyContainer;
+
+ ///
+ /// Represents a delegate to build an object with the parameters.
+ ///
+ /// The parameters.
+ /// The built object.
+ public delegate Object ObjectConstructor(params Object?[] parameters);
+
+ internal IEnumerable Resolve(Type resolveType, Boolean includeUnnamed) {
+ IEnumerable registrations = this.Keys.Where(tr => tr.Type == resolveType).Concat(this.GetParentRegistrationsForType(resolveType)).Distinct();
+
+ if(!includeUnnamed) {
+ registrations = registrations.Where(tr => !String.IsNullOrEmpty(tr.Name));
+ }
+
+ return registrations.Select(registration => this.ResolveInternal(registration, DependencyContainerResolveOptions.Default));
+ }
+
+ internal ObjectFactoryBase GetCurrentFactory(DependencyContainer.TypeRegistration registration) {
+ _ = this.TryGetValue(registration, out ObjectFactoryBase? current);
+
+ return current!;
+ }
+
+ internal RegisterOptions Register(Type registerType, String name, ObjectFactoryBase factory) => this.AddUpdateRegistration(new DependencyContainer.TypeRegistration(registerType, name), factory);
+
+ internal RegisterOptions AddUpdateRegistration(DependencyContainer.TypeRegistration typeRegistration, ObjectFactoryBase factory) {
+ this[typeRegistration] = factory;
+
+ return new RegisterOptions(this, typeRegistration);
+ }
+
+ internal Boolean RemoveRegistration(DependencyContainer.TypeRegistration typeRegistration) => this.TryRemove(typeRegistration, out _);
+
+ internal Object ResolveInternal(DependencyContainer.TypeRegistration registration, DependencyContainerResolveOptions? options = null) {
+ if(options == null) {
+ options = DependencyContainerResolveOptions.Default;
+ }
+
+ // Attempt container resolution
+ if(this.TryGetValue(registration, out ObjectFactoryBase? factory)) {
+ try {
+ return factory.GetObject(registration.Type, this._dependencyContainer, options);
+ } catch(DependencyContainerResolutionException) {
+ throw;
+ } catch(Exception ex) {
+ throw new DependencyContainerResolutionException(registration.Type, ex);
+ }
+ }
+
+ // Attempt to get a factory from parent if we can
+ ObjectFactoryBase? bubbledObjectFactory = this.GetParentObjectFactory(registration);
+ if(bubbledObjectFactory != null) {
+ try {
+ return bubbledObjectFactory.GetObject(registration.Type, this._dependencyContainer, options);
+ } catch(DependencyContainerResolutionException) {
+ throw;
+ } catch(Exception ex) {
+ throw new DependencyContainerResolutionException(registration.Type, ex);
+ }
+ }
+
+ // Fail if requesting named resolution and settings set to fail if unresolved
+ if(!String.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction == DependencyContainerNamedResolutionFailureAction.Fail) {
+ throw new DependencyContainerResolutionException(registration.Type);
+ }
+
+ // Attempted unnamed fallback container resolution if relevant and requested
+ if(!String.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction == DependencyContainerNamedResolutionFailureAction.AttemptUnnamedResolution) {
+ if(this.TryGetValue(new DependencyContainer.TypeRegistration(registration.Type, String.Empty), out factory)) {
+ try {
+ return factory.GetObject(registration.Type, this._dependencyContainer, options);
+ } catch(DependencyContainerResolutionException) {
+ throw;
+ } catch(Exception ex) {
+ throw new DependencyContainerResolutionException(registration.Type, ex);
+ }
+ }
+ }
+
+ // Attempt unregistered construction if possible and requested
+ Boolean isValid = options.UnregisteredResolutionAction == DependencyContainerUnregisteredResolutionAction.AttemptResolve || registration.Type.IsGenericType && options.UnregisteredResolutionAction == DependencyContainerUnregisteredResolutionAction.GenericsOnly;
+
+ return isValid && !registration.Type.IsAbstract && !registration.Type.IsInterface ? this.ConstructType(registration.Type, null, options) : throw new DependencyContainerResolutionException(registration.Type);
+ }
+
+ internal Boolean CanResolve(DependencyContainer.TypeRegistration registration, DependencyContainerResolveOptions? options = null) {
+ if(options == null) {
+ options = DependencyContainerResolveOptions.Default;
+ }
+
+ Type checkType = registration.Type;
+ String name = registration.Name;
+
+ if(this.TryGetValue(new DependencyContainer.TypeRegistration(checkType, name), out ObjectFactoryBase? factory)) {
+ return factory.AssumeConstruction ? true : factory.Constructor == null ? this.GetBestConstructor(factory.CreatesType, options) != null : this.CanConstruct(factory.Constructor, options);
+ }
+
+ // Fail if requesting named resolution and settings set to fail if unresolved
+ // Or bubble up if we have a parent
+ if(!String.IsNullOrEmpty(name) && options.NamedResolutionFailureAction == DependencyContainerNamedResolutionFailureAction.Fail) {
+ return this._dependencyContainer.Parent?.RegisteredTypes.CanResolve(registration, options.Clone()) ?? false;
+ }
+
+ // Attempted unnamed fallback container resolution if relevant and requested
+ if(!String.IsNullOrEmpty(name) && options.NamedResolutionFailureAction == DependencyContainerNamedResolutionFailureAction.AttemptUnnamedResolution) {
+ if(this.TryGetValue(new DependencyContainer.TypeRegistration(checkType), out factory)) {
+ return factory.AssumeConstruction ? true : this.GetBestConstructor(factory.CreatesType, options) != null;
+ }
+ }
+
+ // Check if type is an automatic lazy factory request or an IEnumerable
+ if(IsAutomaticLazyFactoryRequest(checkType) || registration.Type.IsIEnumerable()) {
+ return true;
+ }
+
+ // Attempt unregistered construction if possible and requested
+ // If we cant', bubble if we have a parent
+ if(options.UnregisteredResolutionAction == DependencyContainerUnregisteredResolutionAction.AttemptResolve || checkType.IsGenericType && options.UnregisteredResolutionAction == DependencyContainerUnregisteredResolutionAction.GenericsOnly) {
+ return this.GetBestConstructor(checkType, options) != null || (this._dependencyContainer.Parent?.RegisteredTypes.CanResolve(registration, options.Clone()) ?? false);
+ }
+
+ // Bubble resolution up the container tree if we have a parent
+ return this._dependencyContainer.Parent != null && this._dependencyContainer.Parent.RegisteredTypes.CanResolve(registration, options.Clone());
+ }
+
+ internal Object ConstructType(Type implementationType, ConstructorInfo? constructor, DependencyContainerResolveOptions? options = null) {
+ Type typeToConstruct = implementationType;
+
+ if(constructor == null) {
+ // Try and get the best constructor that we can construct
+ // if we can't construct any then get the constructor
+ // with the least number of parameters so we can throw a meaningful
+ // resolve exception
+ constructor = this.GetBestConstructor(typeToConstruct, options) ?? GetTypeConstructors(typeToConstruct).LastOrDefault();
+ }
+
+ if(constructor == null) {
+ throw new DependencyContainerResolutionException(typeToConstruct);
+ }
+
+ ParameterInfo[] ctorParams = constructor.GetParameters();
+ Object?[] args = new Object?[ctorParams.Length];
+
+ for(Int32 parameterIndex = 0; parameterIndex < ctorParams.Length; parameterIndex++) {
+ ParameterInfo currentParam = ctorParams[parameterIndex];
+
+ try {
+ args[parameterIndex] = options?.ConstructorParameters.GetValueOrDefault(currentParam.Name, this.ResolveInternal(new DependencyContainer.TypeRegistration(currentParam.ParameterType), options.Clone()));
+ } catch(DependencyContainerResolutionException ex) {
+ // If a constructor parameter can't be resolved
+ // it will throw, so wrap it and throw that this can't
+ // be resolved.
+ throw new DependencyContainerResolutionException(typeToConstruct, ex);
+ } catch(Exception ex) {
+ throw new DependencyContainerResolutionException(typeToConstruct, ex);
+ }
+ }
+
+ try {
+ return CreateObjectConstructionDelegateWithCache(constructor).Invoke(args);
+ } catch(Exception ex) {
+ throw new DependencyContainerResolutionException(typeToConstruct, ex);
+ }
+ }
+
+ private static ObjectConstructor CreateObjectConstructionDelegateWithCache(ConstructorInfo constructor) {
+ if(ObjectConstructorCache.TryGetValue(constructor, out ObjectConstructor? objectConstructor)) {
+ return objectConstructor;
+ }
+
+ // We could lock the cache here, but there's no real side
+ // effect to two threads creating the same ObjectConstructor
+ // at the same time, compared to the cost of a lock for
+ // every creation.
+ ParameterInfo[] constructorParams = constructor.GetParameters();
+ ParameterExpression lambdaParams = Expression.Parameter(typeof(Object[]), "parameters");
+ Expression[] newParams = new Expression[constructorParams.Length];
+
+ for(Int32 i = 0; i < constructorParams.Length; i++) {
+ BinaryExpression paramsParameter = Expression.ArrayIndex(lambdaParams, Expression.Constant(i));
+
+ newParams[i] = Expression.Convert(paramsParameter, constructorParams[i].ParameterType);
+ }
+
+ NewExpression newExpression = Expression.New(constructor, newParams);
+
+ LambdaExpression constructionLambda = Expression.Lambda(typeof(ObjectConstructor), newExpression, lambdaParams);
+
+ objectConstructor = (ObjectConstructor)constructionLambda.Compile();
+
+ ObjectConstructorCache[constructor] = objectConstructor;
+ return objectConstructor;
+ }
+
+ private static IEnumerable GetTypeConstructors(Type type) => type.GetConstructors().OrderByDescending(ctor => ctor.GetParameters().Length);
+
+ private static Boolean IsAutomaticLazyFactoryRequest(Type type) {
+ if(!type.IsGenericType) {
+ return false;
+ }
+
+ Type genericType = type.GetGenericTypeDefinition();
+
+ // Just a func
+ if(genericType == typeof(Func<>)) {
+ return true;
+ }
+
+ // 2 parameter func with string as first parameter (name)
+ if(genericType == typeof(Func<,>) && type.GetGenericArguments()[0] == typeof(String)) {
+ return true;
+ }
+
+ // 3 parameter func with string as first parameter (name) and IDictionary as second (parameters)
+ return genericType == typeof(Func<,,>) && type.GetGenericArguments()[0] == typeof(String) && type.GetGenericArguments()[1] == typeof(IDictionary);
+ }
+
+ private ObjectFactoryBase? GetParentObjectFactory(DependencyContainer.TypeRegistration registration) => this._dependencyContainer.Parent == null
+ ? null
+ : this._dependencyContainer.Parent.RegisteredTypes.TryGetValue(registration, out ObjectFactoryBase? factory) ? factory.GetFactoryForChildContainer(registration.Type, this._dependencyContainer.Parent, this._dependencyContainer) : this._dependencyContainer.Parent.RegisteredTypes.GetParentObjectFactory(registration);
+
+ private ConstructorInfo? GetBestConstructor(Type type, DependencyContainerResolveOptions? options) => type.IsValueType ? null : GetTypeConstructors(type).FirstOrDefault(ctor => this.CanConstruct(ctor, options));
+
+ private Boolean CanConstruct(MethodBase ctor, DependencyContainerResolveOptions? options) {
+ foreach(ParameterInfo parameter in ctor.GetParameters()) {
+ if(String.IsNullOrEmpty(parameter.Name)) {
+ return false;
+ }
+
+ Boolean isParameterOverload = options!.ConstructorParameters.ContainsKey(parameter.Name);
+
+ if(parameter.ParameterType.IsPrimitive && !isParameterOverload) {
+ return false;
+ }
+
+ if(!isParameterOverload && !this.CanResolve(new DependencyContainer.TypeRegistration(parameter.ParameterType), options.Clone())) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private IEnumerable GetParentRegistrationsForType(Type resolveType) => this._dependencyContainer.Parent == null ? Array.Empty() : this._dependencyContainer.Parent.RegisteredTypes.Keys.Where(tr => tr.Type == resolveType).Concat(this._dependencyContainer.Parent.RegisteredTypes.GetParentRegistrationsForType(resolveType));
+ }
+}
diff --git a/Swan.Tiny/Diagnostics/HighResolutionTimer.cs b/Swan.Tiny/Diagnostics/HighResolutionTimer.cs
new file mode 100644
index 0000000..ad96c40
--- /dev/null
+++ b/Swan.Tiny/Diagnostics/HighResolutionTimer.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Diagnostics;
+
+namespace Swan.Diagnostics {
+ ///
+ /// Provides access to a high-resolution, time measuring device.
+ ///
+ ///
+ public class HighResolutionTimer : Stopwatch {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// High-resolution timer not available.
+ public HighResolutionTimer() {
+ if(!IsHighResolution) {
+ throw new NotSupportedException("High-resolution timer not available");
+ }
+ }
+
+ ///
+ /// Gets the number of microseconds per timer tick.
+ ///
+ public static Double MicrosecondsPerTick { get; } = 1000000d / Frequency;
+
+ ///
+ /// Gets the elapsed microseconds.
+ ///
+ public Int64 ElapsedMicroseconds => (Int64)(this.ElapsedTicks * MicrosecondsPerTick);
+ }
+}
diff --git a/Swan.Tiny/Enums.cs b/Swan.Tiny/Enums.cs
new file mode 100644
index 0000000..a586509
--- /dev/null
+++ b/Swan.Tiny/Enums.cs
@@ -0,0 +1,61 @@
+namespace Swan {
+ ///
+ /// Enumeration of Operating Systems.
+ ///
+ public enum OperatingSystem {
+ ///
+ /// Unknown OS
+ ///
+ Unknown,
+
+ ///
+ /// Windows
+ ///
+ Windows,
+
+ ///
+ /// UNIX/Linux
+ ///
+ Unix,
+
+ ///
+ /// macOS (OSX)
+ ///
+ Osx,
+ }
+
+ ///
+ /// Defines Endianness, big or little.
+ ///
+ public enum Endianness {
+ ///
+ /// In big endian, you store the most significant byte in the smallest address.
+ ///
+ Big,
+
+ ///
+ /// In little endian, you store the least significant byte in the smallest address.
+ ///
+ Little,
+ }
+
+ ///
+ /// Enumerates the JSON serializer cases to use: None (keeps the same case), PascalCase, or camelCase.
+ ///
+ public enum JsonSerializerCase {
+ ///
+ /// The none
+ ///
+ None,
+
+ ///
+ /// The pascal case (eg. PascalCase)
+ ///
+ PascalCase,
+
+ ///
+ /// The camel case (eg. camelCase)
+ ///
+ CamelCase,
+ }
+}
diff --git a/Swan.Tiny/Extensions.ByteArrays.cs b/Swan.Tiny/Extensions.ByteArrays.cs
new file mode 100644
index 0000000..5abc946
--- /dev/null
+++ b/Swan.Tiny/Extensions.ByteArrays.cs
@@ -0,0 +1,498 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Swan {
+ ///
+ /// Provides various extension methods for byte arrays and streams.
+ ///
+ public static class ByteArrayExtensions {
+ ///
+ /// Converts an array of bytes to its lower-case, hexadecimal representation.
+ ///
+ /// The bytes.
+ /// if set to true add the 0x prefix tot he output.
+ ///
+ /// The specified string instance; no actual conversion is performed.
+ ///
+ /// bytes.
+ public static String ToLowerHex(this Byte[] bytes, Boolean addPrefix = false) => ToHex(bytes, addPrefix, "x2");
+
+ ///
+ /// Converts an array of bytes to its upper-case, hexadecimal representation.
+ ///
+ /// The bytes.
+ /// if set to true [add prefix].
+ ///
+ /// The specified string instance; no actual conversion is performed.
+ ///
+ /// bytes.
+ public static String ToUpperHex(this Byte[] bytes, Boolean addPrefix = false) => ToHex(bytes, addPrefix, "X2");
+
+ ///
+ /// Converts an array of bytes to a sequence of dash-separated, hexadecimal,
+ /// uppercase characters.
+ ///
+ /// The bytes.
+ ///
+ /// A string of hexadecimal pairs separated by hyphens, where each pair represents
+ /// the corresponding element in value; for example, "7F-2C-4A-00".
+ ///
+ public static String ToDashedHex(this Byte[] bytes) => BitConverter.ToString(bytes);
+
+ ///
+ /// Converts an array of bytes to a base-64 encoded string.
+ ///
+ /// The bytes.
+ /// A converted from an array of bytes.
+ public static String ToBase64(this Byte[] bytes) => Convert.ToBase64String(bytes);
+
+ ///
+ /// Converts a set of hexadecimal characters (uppercase or lowercase)
+ /// to a byte array. String length must be a multiple of 2 and
+ /// any prefix (such as 0x) has to be avoided for this to work properly.
+ ///
+ /// The hexadecimal.
+ ///
+ /// A byte array containing the results of encoding the specified set of characters.
+ ///
+ /// hex.
+ public static Byte[] ConvertHexadecimalToBytes(this String @this) {
+ if(String.IsNullOrWhiteSpace(@this)) {
+ throw new ArgumentNullException(nameof(@this));
+ }
+
+ return Enumerable.Range(0, @this.Length / 2).Select(x => Convert.ToByte(@this.Substring(x * 2, 2), 16)).ToArray();
+ }
+
+ ///
+ /// Gets the bit value at the given offset.
+ ///
+ /// The b.
+ /// The offset.
+ /// The length.
+ ///
+ /// Bit value at the given offset.
+ ///
+ public static Byte GetBitValueAt(this Byte @this, Byte offset, Byte length = 1) => (Byte)((@this >> offset) & ~(0xff << length));
+
+ ///
+ /// Sets the bit value at the given offset.
+ ///
+ /// The b.
+ /// The offset.
+ /// The length.
+ /// The value.
+ /// Bit value at the given offset.
+ public static Byte SetBitValueAt(this Byte @this, Byte offset, Byte length, Byte value) {
+ Int32 mask = ~(0xff << length);
+ Byte valueAt = (Byte)(value & mask);
+
+ return (Byte)((valueAt << offset) | (@this & ~(mask << offset)));
+ }
+
+ ///
+ /// Sets the bit value at the given offset.
+ ///
+ /// The b.
+ /// The offset.
+ /// The value.
+ /// Bit value at the given offset.
+ public static Byte SetBitValueAt(this Byte @this, Byte offset, Byte value) => @this.SetBitValueAt(offset, 1, value);
+
+ ///
+ /// Splits a byte array delimited by the specified sequence of bytes.
+ /// Each individual element in the result will contain the split sequence terminator if it is found to be delimited by it.
+ /// For example if you split [1,2,3,4] by a sequence of [2,3] this method will return a list with 2 byte arrays, one containing [1,2,3] and the
+ /// second one containing 4. Use the Trim extension methods to remove terminator sequences.
+ ///
+ /// The buffer.
+ /// The offset at which to start splitting bytes. Any bytes before this will be discarded.
+ /// The sequence.
+ ///
+ /// A byte array containing the results the specified sequence of bytes.
+ ///
+ ///
+ /// buffer
+ /// or
+ /// sequence.
+ ///
+ public static List Split(this Byte[] @this, Int32 offset, params Byte[] sequence) {
+ if(@this == null) {
+ throw new ArgumentNullException(nameof(@this));
+ }
+
+ if(sequence == null) {
+ throw new ArgumentNullException(nameof(sequence));
+ }
+
+ Int32 seqOffset = offset.Clamp(0, @this.Length - 1);
+
+ List result = new List();
+
+ while(seqOffset < @this.Length) {
+ Int32 separatorStartIndex = @this.GetIndexOf(sequence, seqOffset);
+
+ if(separatorStartIndex >= 0) {
+ Byte[] item = new Byte[separatorStartIndex - seqOffset + sequence.Length];
+ Array.Copy(@this, seqOffset, item, 0, item.Length);
+ result.Add(item);
+ seqOffset += item.Length;
+ } else {
+ Byte[] item = new Byte[@this.Length - seqOffset];
+ Array.Copy(@this, seqOffset, item, 0, item.Length);
+ result.Add(item);
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Clones the specified buffer, byte by byte.
+ ///
+ /// The buffer.
+ ///
+ /// A byte array containing the results of encoding the specified set of characters.
+ ///
+ /// this
+ public static Byte[] DeepClone(this Byte[] @this) {
+ if(@this == null) {
+ throw new ArgumentNullException(nameof(@this));
+ }
+
+ Byte[] result = new Byte[@this.Length];
+ Array.Copy(@this, result, @this.Length);
+ return result;
+ }
+
+ ///
+ /// Removes the specified sequence from the start of the buffer if the buffer begins with such sequence.
+ ///
+ /// The buffer.
+ /// The sequence.
+ ///
+ /// A new trimmed byte array.
+ ///
+ /// buffer.
+ public static Byte[] TrimStart(this Byte[] buffer, params Byte[] sequence) {
+ if(buffer == null) {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+
+ if(buffer.StartsWith(sequence) == false) {
+ return buffer.DeepClone();
+ }
+
+ Byte[] result = new Byte[buffer.Length - sequence.Length];
+ Array.Copy(buffer, sequence.Length, result, 0, result.Length);
+ return result;
+ }
+
+ ///
+ /// Removes the specified sequence from the end of the buffer if the buffer ends with such sequence.
+ ///
+ /// The buffer.
+ /// The sequence.
+ ///
+ /// A byte array containing the results of encoding the specified set of characters.
+ ///
+ /// buffer.
+ public static Byte[] TrimEnd(this Byte[] buffer, params Byte[] sequence) {
+ if(buffer == null) {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+
+ if(buffer.EndsWith(sequence) == false) {
+ return buffer.DeepClone();
+ }
+
+ Byte[] result = new Byte[buffer.Length - sequence.Length];
+ Array.Copy(buffer, 0, result, 0, result.Length);
+ return result;
+ }
+
+ ///
+ /// Removes the specified sequence from the end and the start of the buffer
+ /// if the buffer ends and/or starts with such sequence.
+ ///
+ /// The buffer.
+ /// The sequence.
+ /// A byte array containing the results of encoding the specified set of characters.
+ public static Byte[] Trim(this Byte[] buffer, params Byte[] sequence) {
+ Byte[] trimStart = buffer.TrimStart(sequence);
+ return trimStart.TrimEnd(sequence);
+ }
+
+ ///
+ /// Determines if the specified buffer ends with the given sequence of bytes.
+ ///
+ /// The buffer.
+ /// The sequence.
+ ///
+ /// True if the specified buffer is ends; otherwise, false.
+ ///
+ /// buffer.
+ public static Boolean EndsWith(this Byte[] buffer, params Byte[] sequence) {
+ if(buffer == null) {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+
+ Int32 startIndex = buffer.Length - sequence.Length;
+ return buffer.GetIndexOf(sequence, startIndex) == startIndex;
+ }
+
+ ///
+ /// Determines if the specified buffer starts with the given sequence of bytes.
+ ///
+ /// The buffer.
+ /// The sequence.
+ /// true if the specified buffer starts; otherwise, false .
+ public static Boolean StartsWith(this Byte[] buffer, params Byte[] sequence) => buffer.GetIndexOf(sequence) == 0;
+
+ ///
+ /// Determines whether the buffer contains the specified sequence.
+ ///
+ /// The buffer.
+ /// The sequence.
+ ///
+ /// true if [contains] [the specified sequence]; otherwise, false .
+ ///
+ public static Boolean Contains(this Byte[] buffer, params Byte[] sequence) => buffer.GetIndexOf(sequence) >= 0;
+
+ ///
+ /// Determines whether the buffer exactly matches, byte by byte the specified sequence.
+ ///
+ /// The buffer.
+ /// The sequence.
+ ///
+ /// true if [is equal to] [the specified sequence]; otherwise, false .
+ ///
+ /// buffer.
+ public static Boolean IsEqualTo(this Byte[] buffer, params Byte[] sequence) {
+ if(ReferenceEquals(buffer, sequence)) {
+ return true;
+ }
+
+ if(buffer == null) {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+
+ return buffer.Length == sequence.Length && buffer.GetIndexOf(sequence) == 0;
+ }
+
+ ///
+ /// Returns the first instance of the matched sequence based on the given offset.
+ /// If no matches are found then this method returns -1.
+ ///
+ /// The buffer.
+ /// The sequence.
+ /// The offset.
+ /// The index of the sequence.
+ ///
+ /// buffer
+ /// or
+ /// sequence.
+ ///
+ public static Int32 GetIndexOf(this Byte[] buffer, Byte[] sequence, Int32 offset = 0) {
+ if(buffer == null) {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+
+ if(sequence == null) {
+ throw new ArgumentNullException(nameof(sequence));
+ }
+
+ if(sequence.Length == 0) {
+ return -1;
+ }
+
+ if(sequence.Length > buffer.Length) {
+ return -1;
+ }
+
+ Int32 seqOffset = offset < 0 ? 0 : offset;
+
+ Int32 matchedCount = 0;
+ for(Int32 i = seqOffset; i < buffer.Length; i++) {
+ if(buffer[i] == sequence[matchedCount]) {
+ matchedCount++;
+ } else {
+ matchedCount = 0;
+ }
+
+ if(matchedCount == sequence.Length) {
+ return i - (matchedCount - 1);
+ }
+ }
+
+ return -1;
+ }
+
+ ///
+ /// Appends the Memory Stream with the specified buffer.
+ ///
+ /// The stream.
+ /// The buffer.
+ ///
+ /// The same MemoryStream instance.
+ ///
+ ///
+ /// stream
+ /// or
+ /// buffer.
+ ///
+ public static MemoryStream Append(this MemoryStream stream, Byte[] buffer) {
+ if(stream == null) {
+ throw new ArgumentNullException(nameof(stream));
+ }
+
+ if(buffer == null) {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+
+ stream.Write(buffer, 0, buffer.Length);
+ return stream;
+ }
+
+ ///
+ /// Appends the Memory Stream with the specified buffer.
+ ///
+ /// The stream.
+ /// The buffer.
+ ///
+ /// Block of bytes to the current stream using data read from a buffer.
+ ///
+ /// buffer.
+ public static MemoryStream Append(this MemoryStream stream, IEnumerable buffer) => Append(stream, buffer?.ToArray());
+
+ ///
+ /// Appends the Memory Stream with the specified set of buffers.
+ ///
+ /// The stream.
+ /// The buffers.
+ ///
+ /// Block of bytes to the current stream using data read from a buffer.
+ ///
+ /// buffers.
+ public static MemoryStream Append(this MemoryStream stream, IEnumerable buffers) {
+ if(buffers == null) {
+ throw new ArgumentNullException(nameof(buffers));
+ }
+
+ foreach(Byte[] buffer in buffers) {
+ _ = Append(stream, buffer);
+ }
+
+ return stream;
+ }
+
+ ///
+ /// Converts an array of bytes into text with the specified encoding.
+ ///
+ /// The buffer.
+ /// The encoding.
+ /// A that contains the results of decoding the specified sequence of bytes.
+ public static String ToText(this IEnumerable buffer, Encoding encoding) => encoding.GetString(buffer.ToArray());
+
+ ///
+ /// Converts an array of bytes into text with UTF8 encoding.
+ ///
+ /// The buffer.
+ /// A that contains the results of decoding the specified sequence of bytes.
+ public static String ToText(this IEnumerable buffer) => buffer.ToText(Encoding.UTF8);
+
+ ///
+ /// Reads the bytes asynchronous.
+ ///
+ /// The stream.
+ /// The length.
+ /// Length of the buffer.
+ /// The cancellation token.
+ ///
+ /// A byte array containing the results of encoding the specified set of characters.
+ ///
+ /// stream.
+ public static async Task ReadBytesAsync(this Stream stream, Int64 length, Int32 bufferLength, CancellationToken cancellationToken = default) {
+ if(stream == null) {
+ throw new ArgumentNullException(nameof(stream));
+ }
+
+ using MemoryStream dest = new MemoryStream();
+ try {
+ Byte[] buff = new Byte[bufferLength];
+ while(length > 0) {
+ if(length < bufferLength) {
+ bufferLength = (Int32)length;
+ }
+
+ Int32 nread = await stream.ReadAsync(buff, 0, bufferLength, cancellationToken).ConfigureAwait(false);
+ if(nread == 0) {
+ break;
+ }
+
+ dest.Write(buff, 0, nread);
+ length -= nread;
+ }
+ } catch {
+ // ignored
+ }
+
+ return dest.ToArray();
+ }
+
+ ///
+ /// Reads the bytes asynchronous.
+ ///
+ /// The stream.
+ /// The length.
+ /// The cancellation token.
+ ///
+ /// A byte array containing the results of encoding the specified set of characters.
+ ///
+ /// stream.
+ public static async Task ReadBytesAsync(this Stream stream, Int32 length, CancellationToken cancellationToken = default) {
+ if(stream == null) {
+ throw new ArgumentNullException(nameof(stream));
+ }
+
+ Byte[] buff = new Byte[length];
+ Int32 offset = 0;
+ try {
+ while(length > 0) {
+ Int32 nread = await stream.ReadAsync(buff, offset, length, cancellationToken).ConfigureAwait(false);
+ if(nread == 0) {
+ break;
+ }
+
+ offset += nread;
+ length -= nread;
+ }
+ } catch {
+ // ignored
+ }
+
+ return new ArraySegment(buff, 0, offset).ToArray();
+ }
+
+ private static String ToHex(Byte[] bytes, Boolean addPrefix, String format) {
+ if(bytes == null) {
+ throw new ArgumentNullException(nameof(bytes));
+ }
+
+ StringBuilder sb = new StringBuilder(bytes.Length * 2);
+
+ foreach(Byte item in bytes) {
+ _ = sb.Append(item.ToString(format, CultureInfo.InvariantCulture));
+ }
+
+ return $"{(addPrefix ? "0x" : String.Empty)}{sb}";
+ }
+ }
+}
diff --git a/Swan.Tiny/Extensions.Dictionaries.cs b/Swan.Tiny/Extensions.Dictionaries.cs
new file mode 100644
index 0000000..273e9e8
--- /dev/null
+++ b/Swan.Tiny/Extensions.Dictionaries.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+
+namespace Swan {
+ ///
+ /// Extension methods.
+ ///
+ public static partial class Extensions {
+ ///
+ /// Gets the value if exists or default.
+ ///
+ /// The type of the key.
+ /// The type of the value.
+ /// The dictionary.
+ /// The key.
+ /// The default value.
+ ///
+ /// The value of the provided key or default.
+ ///
+ /// dict.
+ public static TValue GetValueOrDefault(this IDictionary dict, TKey key, TValue defaultValue = default) {
+ if(dict == null) {
+ throw new ArgumentNullException(nameof(dict));
+ }
+
+ return dict.ContainsKey(key) ? dict[key] : defaultValue;
+ }
+
+ ///
+ /// Adds a key/value pair to the Dictionary if the key does not already exist.
+ /// If the value is null, the key will not be updated.
+ /// Based on ConcurrentDictionary.GetOrAdd method.
+ ///
+ /// The type of the key.
+ /// The type of the value.
+ /// The dictionary.
+ /// The key.
+ /// The value factory.
+ ///
+ /// The value for the key.
+ ///
+ ///
+ /// dict
+ /// or
+ /// valueFactory.
+ ///
+ public static TValue GetOrAdd(this IDictionary dict, TKey key, Func valueFactory) {
+ if(dict == null) {
+ throw new ArgumentNullException(nameof(dict));
+ }
+
+ if(valueFactory == null) {
+ throw new ArgumentNullException(nameof(valueFactory));
+ }
+
+ if(!dict.ContainsKey(key)) {
+ TValue value = valueFactory(key);
+ if(Equals(value, default)) {
+ return default;
+ }
+
+ dict[key] = value;
+ }
+
+ return dict[key];
+ }
+
+ ///
+ /// Executes the item action for each element in the Dictionary.
+ ///
+ /// The type of the key.
+ /// The type of the value.
+ /// The dictionary.
+ /// The item action.
+ /// dict.
+ public static void ForEach(this IDictionary dict, Action itemAction) {
+ if(dict == null) {
+ throw new ArgumentNullException(nameof(dict));
+ }
+
+ foreach(KeyValuePair kvp in dict) {
+ itemAction(kvp.Key, kvp.Value);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Swan.Tiny/Extensions.Functional.cs b/Swan.Tiny/Extensions.Functional.cs
new file mode 100644
index 0000000..e6d8491
--- /dev/null
+++ b/Swan.Tiny/Extensions.Functional.cs
@@ -0,0 +1,173 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Swan {
+ ///
+ /// Functional programming extension methods.
+ ///
+ public static class FunctionalExtensions {
+ ///
+ /// Whens the specified condition.
+ ///
+ /// The type of IQueryable.
+ /// The list.
+ /// The condition.
+ /// The function.
+ ///
+ /// The IQueryable.
+ ///
+ ///
+ /// this
+ /// or
+ /// condition
+ /// or
+ /// fn.
+ ///
+ public static IQueryable When(this IQueryable list, Func condition, Func, IQueryable> fn) {
+ if(list == null) {
+ throw new ArgumentNullException(nameof(list));
+ }
+
+ if(condition == null) {
+ throw new ArgumentNullException(nameof(condition));
+ }
+
+ if(fn == null) {
+ throw new ArgumentNullException(nameof(fn));
+ }
+
+ return condition() ? fn(list) : list;
+ }
+
+ ///
+ /// Whens the specified condition.
+ ///
+ /// The type of IEnumerable.
+ /// The list.
+ /// The condition.
+ /// The function.
+ ///
+ /// The IEnumerable.
+ ///
+ ///
+ /// this
+ /// or
+ /// condition
+ /// or
+ /// fn.
+ ///
+ public static IEnumerable When(this IEnumerable list, Func condition, Func, IEnumerable> fn) {
+ if(list == null) {
+ throw new ArgumentNullException(nameof(list));
+ }
+
+ if(condition == null) {
+ throw new ArgumentNullException(nameof(condition));
+ }
+
+ if(fn == null) {
+ throw new ArgumentNullException(nameof(fn));
+ }
+
+ return condition() ? fn(list) : list;
+ }
+
+ ///
+ /// Adds the value when the condition is true.
+ ///
+ /// The type of IList element.
+ /// The list.
+ /// The condition.
+ /// The value.
+ ///
+ /// The IList.
+ ///
+ ///
+ /// this
+ /// or
+ /// condition
+ /// or
+ /// value.
+ ///
+ public static IList AddWhen(this IList list, Func condition, Func value) {
+ if(list == null) {
+ throw new ArgumentNullException(nameof(list));
+ }
+
+ if(condition == null) {
+ throw new ArgumentNullException(nameof(condition));
+ }
+
+ if(value == null) {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ if(condition()) {
+ list.Add(value());
+ }
+
+ return list;
+ }
+
+ ///
+ /// Adds the value when the condition is true.
+ ///
+ /// The type of IList element.
+ /// The list.
+ /// if set to true [condition].
+ /// The value.
+ ///
+ /// The IList.
+ ///
+ /// list.
+ public static IList AddWhen(this IList list, Boolean condition, T value) {
+ if(list == null) {
+ throw new ArgumentNullException(nameof(list));
+ }
+
+ if(condition) {
+ list.Add(value);
+ }
+
+ return list;
+ }
+
+ ///
+ /// Adds the range when the condition is true.
+ ///
+ /// The type of List element.
+ /// The list.
+ /// The condition.
+ /// The value.
+ ///
+ /// The List.
+ ///
+ ///
+ /// this
+ /// or
+ /// condition
+ /// or
+ /// value.
+ ///
+ public static List AddRangeWhen(this List list, Func condition, Func> value) {
+ if(list == null) {
+ throw new ArgumentNullException(nameof(list));
+ }
+
+ if(condition == null) {
+ throw new ArgumentNullException(nameof(condition));
+ }
+
+ if(value == null) {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ if(condition()) {
+ list.AddRange(value());
+ }
+
+ return list;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Swan.Tiny/Extensions.Reflection.cs b/Swan.Tiny/Extensions.Reflection.cs
new file mode 100644
index 0000000..8953aae
--- /dev/null
+++ b/Swan.Tiny/Extensions.Reflection.cs
@@ -0,0 +1,411 @@
+#nullable enable
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+
+using Swan.Configuration;
+using Swan.Reflection;
+
+namespace Swan {
+ ///
+ /// Provides various extension methods for Reflection and Types.
+ ///
+ public static class ReflectionExtensions {
+ private static readonly Lazy, Func>> CacheGetMethods = new Lazy, Func>>(() => new ConcurrentDictionary, Func>(), true);
+
+ private static readonly Lazy, Action>> CacheSetMethods = new Lazy, Action>>(() => new ConcurrentDictionary, Action>(), true);
+
+ #region Assembly Extensions
+
+ ///
+ /// Gets all types within an assembly in a safe manner.
+ ///
+ /// The assembly.
+ ///
+ /// Array of Type objects representing the types specified by an assembly.
+ ///
+ /// assembly.
+ public static IEnumerable GetAllTypes(this Assembly assembly) {
+ if(assembly == null) {
+ throw new ArgumentNullException(nameof(assembly));
+ }
+
+ try {
+ return assembly.GetTypes();
+ } catch(ReflectionTypeLoadException e) {
+ return e.Types.Where(t => t != null);
+ }
+ }
+
+ #endregion
+
+ #region Type Extensions
+
+ ///
+ /// The closest programmatic equivalent of default(T).
+ ///
+ /// The type.
+ ///
+ /// Default value of this type.
+ ///
+ /// type.
+ public static Object? GetDefault(this Type type) {
+ if(type == null) {
+ throw new ArgumentNullException(nameof(type));
+ }
+
+ return type.IsValueType ? Activator.CreateInstance(type) : default;
+ }
+
+ ///
+ /// Determines whether this type is compatible with ICollection.
+ ///
+ /// The type.
+ ///
+ /// true if the specified source type is collection; otherwise, false .
+ ///
+ /// sourceType.
+ public static Boolean IsCollection(this Type sourceType) {
+ if(sourceType == null) {
+ throw new ArgumentNullException(nameof(sourceType));
+ }
+
+ return sourceType != typeof(String) && typeof(IEnumerable).IsAssignableFrom(sourceType);
+ }
+
+ ///
+ /// Gets a method from a type given the method name, binding flags, generic types and parameter types.
+ ///
+ /// Type of the source.
+ /// The binding flags.
+ /// Name of the method.
+ /// The generic types.
+ /// The parameter types.
+ ///
+ /// An object that represents the method with the specified name.
+ ///
+ ///
+ /// The exception that is thrown when binding to a member results in more than one member matching the
+ /// binding criteria. This class cannot be inherited.
+ ///
+ public static MethodInfo GetMethod(this Type type, BindingFlags bindingFlags, String methodName, Type[] genericTypes, Type[] parameterTypes) {
+ if(type == null) {
+ throw new ArgumentNullException(nameof(type));
+ }
+
+ if(methodName == null) {
+ throw new ArgumentNullException(nameof(methodName));
+ }
+
+ if(genericTypes == null) {
+ throw new ArgumentNullException(nameof(genericTypes));
+ }
+
+ if(parameterTypes == null) {
+ throw new ArgumentNullException(nameof(parameterTypes));
+ }
+
+ List methods = type.GetMethods(bindingFlags)
+ .Where(mi => String.Equals(methodName, mi.Name, StringComparison.Ordinal))
+ .Where(mi => mi.ContainsGenericParameters)
+ .Where(mi => mi.GetGenericArguments().Length == genericTypes.Length)
+ .Where(mi => mi.GetParameters().Length == parameterTypes.Length).Select(mi => mi.MakeGenericMethod(genericTypes))
+ .Where(mi => mi.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameterTypes)).ToList();
+
+ return methods.Count > 1 ? throw new AmbiguousMatchException() : methods.FirstOrDefault();
+ }
+
+ ///
+ /// Determines whether [is i enumerable request].
+ ///
+ /// The type.
+ ///
+ /// true if [is i enumerable request] [the specified type]; otherwise, false .
+ ///
+ /// type.
+ public static Boolean IsIEnumerable(this Type type) => type == null ? throw new ArgumentNullException(nameof(type)) : type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>);
+
+ #endregion
+
+ ///
+ /// Tries to parse using the basic types.
+ ///
+ /// The type.
+ /// The value.
+ /// The result.
+ ///
+ /// true if parsing was successful; otherwise, false .
+ ///
+ /// type
+ public static Boolean TryParseBasicType(this Type type, Object value, out Object? result) {
+ if(type == null) {
+ throw new ArgumentNullException(nameof(type));
+ }
+
+ if(type != typeof(Boolean)) {
+ return TryParseBasicType(type, value.ToStringInvariant(), out result);
+ }
+
+ result = value.ToBoolean();
+ return true;
+ }
+
+ ///
+ /// Tries to parse using the basic types.
+ ///
+ /// The type.
+ /// The value.
+ /// The result.
+ ///
+ /// true if parsing was successful; otherwise, false .
+ ///
+ /// type
+ public static Boolean TryParseBasicType(this Type type, String value, out Object? result) {
+ if(type == null) {
+ throw new ArgumentNullException(nameof(type));
+ }
+
+ result = null;
+
+ return Definitions.BasicTypesInfo.Value.ContainsKey(type) && Definitions.BasicTypesInfo.Value[type].TryParse(value, out result);
+ }
+
+ ///
+ /// Tries the type of the set basic value to a property.
+ ///
+ /// The property information.
+ /// The value.
+ /// The object.
+ ///
+ /// true if parsing was successful; otherwise, false .
+ ///
+ /// propertyInfo.
+ public static Boolean TrySetBasicType(this PropertyInfo propertyInfo, Object value, Object target) {
+ if(propertyInfo == null) {
+ throw new ArgumentNullException(nameof(propertyInfo));
+ }
+
+ try {
+ if(propertyInfo.PropertyType.TryParseBasicType(value, out Object? propertyValue)) {
+ propertyInfo.SetValue(target, propertyValue);
+ return true;
+ }
+ } catch {
+ // swallow
+ }
+
+ return false;
+ }
+
+ ///
+ /// Tries the type of the set to an array a basic type.
+ ///
+ /// The type.
+ /// The value.
+ /// The array.
+ /// The index.
+ ///
+ /// true if parsing was successful; otherwise, false .
+ ///
+ /// type
+ public static Boolean TrySetArrayBasicType(this Type type, Object value, Array target, Int32 index) {
+ if(type == null) {
+ throw new ArgumentNullException(nameof(type));
+ }
+
+ if(target == null) {
+ return false;
+ }
+
+ try {
+ if(value == null) {
+ target.SetValue(null, index);
+ return true;
+ }
+
+ if(type.TryParseBasicType(value, out Object? propertyValue)) {
+ target.SetValue(propertyValue, index);
+ return true;
+ }
+
+ if(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) {
+ target.SetValue(null, index);
+ return true;
+ }
+ } catch {
+ // swallow
+ }
+
+ return false;
+ }
+
+ ///
+ /// Tries to set a property array with another array.
+ ///
+ /// The property.
+ /// The value.
+ /// The object.
+ ///
+ /// true if parsing was successful; otherwise, false .
+ ///
+ /// propertyInfo.
+ public static Boolean TrySetArray(this PropertyInfo propertyInfo, IEnumerable? value, Object obj) {
+ if(propertyInfo == null) {
+ throw new ArgumentNullException(nameof(propertyInfo));
+ }
+
+ Type? elementType = propertyInfo.PropertyType.GetElementType();
+
+ if(elementType == null || value == null) {
+ return false;
+ }
+
+ Array targetArray = Array.CreateInstance(elementType, value.Count());
+
+ Int32 i = 0;
+
+ foreach(Object sourceElement in value) {
+ Boolean result = elementType.TrySetArrayBasicType(sourceElement, targetArray, i++);
+
+ if(!result) {
+ return false;
+ }
+ }
+
+ propertyInfo.SetValue(obj, targetArray);
+
+ return true;
+ }
+
+ ///
+ /// Gets property actual value or PropertyDisplayAttribute.DefaultValue if presented.
+ ///
+ /// If the PropertyDisplayAttribute.Format value is presented, the property value
+ /// will be formatted accordingly.
+ ///
+ /// If the object contains a null value, a empty string will be returned.
+ ///
+ /// The property information.
+ /// The object.
+ /// The property value or null.
+ /// propertyInfo.
+ public static String? ToFormattedString(this PropertyInfo propertyInfo, Object target) {
+ if(propertyInfo == null) {
+ throw new ArgumentNullException(nameof(propertyInfo));
+ }
+
+ try {
+ Object? value = propertyInfo.GetValue(target);
+ PropertyDisplayAttribute attr = AttributeCache.DefaultCache.Value.RetrieveOne(propertyInfo);
+
+ if(attr == null) {
+ return value?.ToString() ?? String.Empty;
+ }
+
+ Object valueToFormat = value ?? attr.DefaultValue;
+
+ return String.IsNullOrEmpty(attr.Format) ? (valueToFormat?.ToString() ?? String.Empty) : ConvertObjectAndFormat(propertyInfo.PropertyType, valueToFormat, attr.Format);
+ } catch {
+ return null;
+ }
+ }
+
+ ///
+ /// Gets a MethodInfo from a Property Get method.
+ ///
+ /// The property information.
+ /// if set to true [non public].
+ ///
+ /// The cached MethodInfo.
+ ///
+ public static Func? GetCacheGetMethod(this PropertyInfo propertyInfo, Boolean nonPublic = false) {
+ Tuple key = Tuple.Create(!nonPublic, propertyInfo);
+
+ // TODO: Fix public logic
+ return !nonPublic && !CacheGetMethods.Value.ContainsKey(key) && !propertyInfo.GetGetMethod(true)!.IsPublic ? null : CacheGetMethods.Value.GetOrAdd(key, x => y => x.Item2.GetGetMethod(nonPublic)?.Invoke(y, null)!);
+ //y => x => y.Item2.CreatePropertyProxy().GetValue(x));
+ }
+
+ ///
+ /// Gets a MethodInfo from a Property Set method.
+ ///
+ /// The property information.
+ /// if set to true [non public].
+ ///
+ /// The cached MethodInfo.
+ ///
+ public static Action? GetCacheSetMethod(this PropertyInfo propertyInfo, Boolean nonPublic = false) {
+ Tuple key = Tuple.Create(!nonPublic, propertyInfo);
+
+ return !nonPublic && !CacheSetMethods.Value.ContainsKey(key) && !propertyInfo.GetSetMethod(true)!.IsPublic ? null : CacheSetMethods.Value.GetOrAdd(key, x => (obj, args) => x.Item2.GetSetMethod(nonPublic)!.Invoke(obj, args));
+ //y => (obj, args) => y.Item2.CreatePropertyProxy().SetValue(obj, args));
+ }
+
+ ///
+ /// Convert a string to a boolean.
+ ///
+ /// The string.
+ ///
+ /// true if the string represents a valid truly value, otherwise false .
+ ///
+ public static Boolean ToBoolean(this String str) {
+ try {
+ return Convert.ToBoolean(str);
+ } catch(FormatException) {
+ // ignored
+ }
+
+ try {
+ return Convert.ToBoolean(Convert.ToInt32(str));
+ } catch {
+ // ignored
+ }
+
+ return false;
+ }
+
+ ///
+ /// Creates a property proxy that stores getter and setter delegates.
+ ///
+ /// The property information.
+ ///
+ /// The property proxy.
+ ///
+ /// this.
+ public static IPropertyProxy? CreatePropertyProxy(this PropertyInfo @this) {
+ if(@this == null) {
+ throw new ArgumentNullException(nameof(@this));
+ }
+
+ Type genericType = typeof(PropertyProxy<,>).MakeGenericType(@this.DeclaringType!, @this.PropertyType);
+
+ return Activator.CreateInstance(genericType, @this) as IPropertyProxy;
+ }
+
+ ///
+ /// Convert a object to a boolean.
+ ///
+ /// The value.
+ ///
+ /// true if the string represents a valid truly value, otherwise false .
+ ///
+ public static Boolean ToBoolean(this Object value) => value.ToStringInvariant().ToBoolean();
+
+ private static String ConvertObjectAndFormat(Type propertyType, Object value, String format) =>
+ propertyType == typeof(DateTime) || propertyType == typeof(DateTime?)
+ ? Convert.ToDateTime(value, CultureInfo.InvariantCulture).ToString(format)
+ : propertyType == typeof(Int32) || propertyType == typeof(Int32?)
+ ? Convert.ToInt32(value, CultureInfo.InvariantCulture).ToString(format)
+ : propertyType == typeof(Decimal) || propertyType == typeof(Decimal?)
+ ? Convert.ToDecimal(value, CultureInfo.InvariantCulture).ToString(format)
+ : propertyType == typeof(Double) || propertyType == typeof(Double?)
+ ? Convert.ToDouble(value, CultureInfo.InvariantCulture).ToString(format)
+ : propertyType == typeof(Byte) || propertyType == typeof(Byte?)
+ ? Convert.ToByte(value, CultureInfo.InvariantCulture).ToString(format)
+ : value?.ToString() ?? String.Empty;
+ }
+}
diff --git a/Swan.Tiny/Extensions.Strings.cs b/Swan.Tiny/Extensions.Strings.cs
new file mode 100644
index 0000000..e8bf398
--- /dev/null
+++ b/Swan.Tiny/Extensions.Strings.cs
@@ -0,0 +1,364 @@
+#nullable enable
+using System;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+
+using Swan.Formatters;
+
+namespace Swan {
+ ///
+ /// String related extension methods.
+ ///
+ public static class StringExtensions {
+ #region Private Declarations
+
+ private const RegexOptions StandardRegexOptions = RegexOptions.Multiline | RegexOptions.Compiled | RegexOptions.CultureInvariant;
+
+ private static readonly String[] ByteSuffixes = { "B", "KB", "MB", "GB", "TB" };
+
+ private static readonly Lazy SplitLinesRegex = new Lazy(() => new Regex("\r\n|\r|\n", StandardRegexOptions));
+
+ private static readonly Lazy UnderscoreRegex = new Lazy(() => new Regex(@"_", StandardRegexOptions));
+
+ private static readonly Lazy CamelCaseRegEx = new Lazy(() => new Regex(@"[a-z][A-Z]", StandardRegexOptions));
+
+ private static readonly Lazy SplitCamelCaseString = new Lazy(() => m => {
+ String x = m.ToString();
+ return x[0] + " " + x[1..];
+ });
+
+ private static readonly Lazy InvalidFilenameChars = new Lazy(() => Path.GetInvalidFileNameChars().Select(c => c.ToString()).ToArray());
+
+ #endregion
+
+ ///
+ /// Returns a string that represents the given item
+ /// It tries to use InvariantCulture if the ToString(IFormatProvider)
+ /// overload exists.
+ ///
+ /// The item.
+ /// A that represents the current object.
+ public static String ToStringInvariant(this Object? @this) {
+ if(@this == null) {
+ return String.Empty;
+ }
+
+ Type itemType = @this.GetType();
+
+ return itemType == typeof(String) ? @this as String ?? String.Empty : Definitions.BasicTypesInfo.Value.ContainsKey(itemType) ? Definitions.BasicTypesInfo.Value[itemType].ToStringInvariant(@this) : @this.ToString()!;
+ }
+
+ ///
+ /// Returns a string that represents the given item
+ /// It tries to use InvariantCulture if the ToString(IFormatProvider)
+ /// overload exists.
+ ///
+ /// The type to get the string.
+ /// The item.
+ /// A that represents the current object.
+ public static String ToStringInvariant(this T item) => typeof(String) == typeof(T) ? item as String ?? String.Empty : ToStringInvariant(item as Object);
+
+ ///
+ /// Removes the control characters from a string except for those specified.
+ ///
+ /// The input.
+ /// When specified, these characters will not be removed.
+ ///
+ /// A string that represents the current object.
+ ///
+ /// input.
+ public static String RemoveControlCharsExcept(this String value, params Char[]? excludeChars) {
+ if(value == null) {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ if(excludeChars == null) {
+ excludeChars = Array.Empty();
+ }
+
+ return new String(value.Where(c => Char.IsControl(c) == false || excludeChars.Contains(c)).ToArray());
+ }
+
+ ///
+ /// Removes all control characters from a string, including new line sequences.
+ ///
+ /// The input.
+ /// A that represents the current object.
+ /// input.
+ public static String RemoveControlChars(this String value) => value.RemoveControlCharsExcept(null);
+
+ ///
+ /// Outputs JSON string representing this object.
+ ///
+ /// The object.
+ /// if set to true format the output.
+ /// A that represents the current object.
+ public static String ToJson(this Object @this, Boolean format = true) => @this == null ? String.Empty : Json.Serialize(@this, format);
+
+ ///
+ /// Returns text representing the properties of the specified object in a human-readable format.
+ /// While this method is fairly expensive computationally speaking, it provides an easy way to
+ /// examine objects.
+ ///
+ /// The object.
+ /// A that represents the current object.
+ public static String Stringify(this Object @this) {
+ if(@this == null) {
+ return "(null)";
+ }
+
+ try {
+ String jsonText = Json.Serialize(@this, false, "$type");
+ Object? jsonData = Json.Deserialize(jsonText);
+
+ return new HumanizeJson(jsonData, 0).GetResult();
+ } catch {
+ return @this.ToStringInvariant();
+ }
+ }
+
+ ///
+ /// Retrieves a section of the string, inclusive of both, the start and end indexes.
+ /// This behavior is unlike JavaScript's Slice behavior where the end index is non-inclusive
+ /// If the string is null it returns an empty string.
+ ///
+ /// The string.
+ /// The start index.
+ /// The end index.
+ /// Retrieves a substring from this instance.
+ public static String Slice(this String @this, Int32 startIndex, Int32 endIndex) {
+ if(@this == null) {
+ return String.Empty;
+ }
+
+ Int32 end = endIndex.Clamp(startIndex, @this.Length - 1);
+
+ return startIndex >= end ? String.Empty : @this.Substring(startIndex, end - startIndex + 1);
+ }
+
+ ///
+ /// Gets a part of the string clamping the length and startIndex parameters to safe values.
+ /// If the string is null it returns an empty string. This is basically just a safe version
+ /// of string.Substring.
+ ///
+ /// The string.
+ /// The start index.
+ /// The length.
+ /// Retrieves a substring from this instance.
+ public static String SliceLength(this String @this, Int32 startIndex, Int32 length) {
+ if(@this == null) {
+ return String.Empty;
+ }
+
+ Int32 start = startIndex.Clamp(0, @this.Length - 1);
+ Int32 len = length.Clamp(0, @this.Length - start);
+
+ return len == 0 ? String.Empty : @this.Substring(start, len);
+ }
+
+ ///
+ /// Splits the specified text into r, n or rn separated lines.
+ ///
+ /// The text.
+ ///
+ /// An array whose elements contain the substrings from this instance
+ /// that are delimited by one or more characters in separator.
+ ///
+ public static String[] ToLines(this String @this) => @this == null ? Array.Empty() : SplitLinesRegex.Value.Split(@this);
+
+ ///
+ /// Humanizes (make more human-readable) an identifier-style string
+ /// in either camel case or snake case. For example, CamelCase will be converted to
+ /// Camel Case and Snake_Case will be converted to Snake Case.
+ ///
+ /// The identifier-style string.
+ /// A humanized.
+ public static String Humanize(this String value) {
+ if(value == null) {
+ return String.Empty;
+ }
+
+ String returnValue = UnderscoreRegex.Value.Replace(value, " ");
+ returnValue = CamelCaseRegEx.Value.Replace(returnValue, SplitCamelCaseString.Value);
+ return returnValue;
+ }
+
+ ///
+ /// Humanizes (make more human-readable) an boolean.
+ ///
+ /// if set to true [value].
+ /// A that represents the current boolean.
+ public static String Humanize(this Boolean value) => value ? "Yes" : "No";
+
+ ///
+ /// Humanizes (make more human-readable) the specified value.
+ ///
+ /// The value.
+ /// A that represents the current object.
+ public static String Humanize(this Object value) =>
+ value switch
+ {
+ String stringValue => stringValue.Humanize(),
+ Boolean boolValue => boolValue.Humanize(),
+ _ => value.Stringify()
+ };
+
+ ///
+ /// Indents the specified multi-line text with the given amount of leading spaces
+ /// per line.
+ ///
+ /// The text.
+ /// The spaces.
+ /// A that represents the current object.
+ public static String Indent(this String value, Int32 spaces = 4) {
+ if(value == null) {
+ value = String.Empty;
+ }
+
+ if(spaces <= 0) {
+ return value;
+ }
+
+ String[] lines = value.ToLines();
+ StringBuilder builder = new StringBuilder();
+ String indentStr = new String(' ', spaces);
+
+ foreach(String line in lines) {
+ _ = builder.AppendLine($"{indentStr}{line}");
+ }
+
+ return builder.ToString().TrimEnd();
+ }
+
+ ///
+ /// Gets the line and column number (i.e. not index) of the
+ /// specified character index. Useful to locate text in a multi-line
+ /// string the same way a text editor does.
+ /// Please not that the tuple contains first the line number and then the
+ /// column number.
+ ///
+ /// The string.
+ /// Index of the character.
+ /// A 2-tuple whose value is (item1, item2).
+ public static Tuple TextPositionAt(this String value, Int32 charIndex) {
+ if(value == null) {
+ return Tuple.Create(0, 0);
+ }
+
+ Int32 index = charIndex.Clamp(0, value.Length - 1);
+
+ Int32 lineIndex = 0;
+ Int32 colNumber = 0;
+
+ for(Int32 i = 0; i <= index; i++) {
+ if(value[i] == '\n') {
+ lineIndex++;
+ colNumber = 0;
+ continue;
+ }
+
+ if(value[i] != '\r') {
+ colNumber++;
+ }
+ }
+
+ return Tuple.Create(lineIndex + 1, colNumber);
+ }
+
+ ///
+ /// Makes the file name system safe.
+ ///
+ /// The s.
+ ///
+ /// A string with a safe file name.
+ ///
+ /// s.
+ public static String ToSafeFilename(this String value) => value == null ? throw new ArgumentNullException(nameof(value)) : InvalidFilenameChars.Value.Aggregate(value, (current, c) => current.Replace(c, String.Empty)).Slice(0, 220);
+
+ ///
+ /// Formats a long into the closest bytes string.
+ ///
+ /// The bytes length.
+ ///
+ /// The string representation of the current Byte object, formatted as specified by the format parameter.
+ ///
+ public static String FormatBytes(this Int64 bytes) => ((UInt64)bytes).FormatBytes();
+
+ ///
+ /// Formats a long into the closest bytes string.
+ ///
+ /// The bytes length.
+ ///
+ /// A copy of format in which the format items have been replaced by the string
+ /// representations of the corresponding arguments.
+ ///
+ public static String FormatBytes(this UInt64 bytes) {
+ Int32 i;
+ Double dblSByte = bytes;
+
+ for(i = 0; i < ByteSuffixes.Length && bytes >= 1024; i++, bytes /= 1024) {
+ dblSByte = bytes / 1024.0;
+ }
+
+ return $"{dblSByte:0.##} {ByteSuffixes[i]}";
+ }
+
+ ///
+ /// Truncates the specified value.
+ ///
+ /// The value.
+ /// The maximum length.
+ ///
+ /// Retrieves a substring from this instance.
+ /// The substring starts at a specified character position and has a specified length.
+ ///
+ public static String? Truncate(this String value, Int32 maximumLength) => Truncate(value, maximumLength, String.Empty);
+
+ ///
+ /// Truncates the specified value and append the omission last.
+ ///
+ /// The value.
+ /// The maximum length.
+ /// The omission.
+ ///
+ /// Retrieves a substring from this instance.
+ /// The substring starts at a specified character position and has a specified length.
+ ///
+ public static String? Truncate(this String value, Int32 maximumLength, String omission) => value == null ? null : value.Length > maximumLength ? value.Substring(0, maximumLength) + (omission ?? String.Empty) : value;
+
+ ///
+ /// Determines whether the specified contains any of characters in
+ /// the specified array of .
+ ///
+ ///
+ /// true if contains any of ;
+ /// otherwise, false .
+ ///
+ ///
+ /// A to test.
+ ///
+ ///
+ /// An array of that contains characters to find.
+ ///
+ public static Boolean Contains(this String value, params Char[] chars) => chars?.Length == 0 || !String.IsNullOrEmpty(value) && chars != null && value.IndexOfAny(chars) > -1;
+
+ ///
+ /// Replaces all chars in a string.
+ ///
+ /// The value.
+ /// The replace value.
+ /// The chars.
+ /// The string with the characters replaced.
+ public static String ReplaceAll(this String value, String replaceValue, params Char[] chars) => chars.Aggregate(value, (current, c) => current.Replace(new String(new[] { c }), replaceValue));
+
+ ///
+ /// Convert hex character to an integer. Return -1 if char is something
+ /// other than a hex char.
+ ///
+ /// The c.
+ /// Converted integer.
+ public static Int32 Hex2Int(this Char value) => value >= '0' && value <= '9' ? value - '0' : value >= 'A' && value <= 'F' ? value - 'A' + 10 : value >= 'a' && value <= 'f' ? value - 'a' + 10 : -1;
+ }
+}
diff --git a/Swan.Tiny/Extensions.ValueTypes.cs b/Swan.Tiny/Extensions.ValueTypes.cs
new file mode 100644
index 0000000..c509011
--- /dev/null
+++ b/Swan.Tiny/Extensions.ValueTypes.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+using Swan.Reflection;
+
+namespace Swan {
+ ///
+ /// Provides various extension methods for value types and structs.
+ ///
+ public static class ValueTypeExtensions {
+ ///
+ /// Clamps the specified value between the minimum and the maximum.
+ ///
+ /// The type of value to clamp.
+ /// The value.
+ /// The minimum.
+ /// The maximum.
+ /// A value that indicates the relative order of the objects being compared.
+ public static T Clamp(this T @this, T min, T max) where T : struct, IComparable => @this.CompareTo(min) < 0 ? min : @this.CompareTo(max) > 0 ? max : @this;
+
+ ///
+ /// Clamps the specified value between the minimum and the maximum.
+ ///
+ /// The value.
+ /// The minimum.
+ /// The maximum.
+ /// A value that indicates the relative order of the objects being compared.
+ public static Int32 Clamp(this Int32 @this, Int32 min, Int32 max) => @this < min ? min : (@this > max ? max : @this);
+
+ ///
+ /// Determines whether the specified value is between a minimum and a maximum value.
+ ///
+ /// The type of value to check.
+ /// The value.
+ /// The minimum.
+ /// The maximum.
+ ///
+ /// true if the specified minimum is between; otherwise, false .
+ ///
+ public static Boolean IsBetween(this T @this, T min, T max) where T : struct, IComparable => @this.CompareTo(min) >= 0 && @this.CompareTo(max) <= 0;
+
+ ///
+ /// Converts an array of bytes into the given struct type.
+ ///
+ /// The type of structure to convert.
+ /// The data.
+ /// a struct type derived from convert an array of bytes ref=ToStruct".
+ public static T ToStruct(this Byte[] @this) where T : struct => @this == null ? throw new ArgumentNullException(nameof(@this)) : ToStruct(@this, 0, @this.Length);
+
+ ///
+ /// Converts an array of bytes into the given struct type.
+ ///
+ /// The type of structure to convert.
+ /// The data.
+ /// The offset.
+ /// The length.
+ ///
+ /// A managed object containing the data pointed to by the ptr parameter.
+ ///
+ /// data.
+ public static T ToStruct(this Byte[] @this, Int32 offset, Int32 length) where T : struct {
+ if(@this == null) {
+ throw new ArgumentNullException(nameof(@this));
+ }
+
+ Byte[] buffer = new Byte[length];
+ Array.Copy(@this, offset, buffer, 0, buffer.Length);
+ GCHandle handle = GCHandle.Alloc(GetStructBytes(buffer), GCHandleType.Pinned);
+
+ try {
+ return Marshal.PtrToStructure(handle.AddrOfPinnedObject());
+ } finally {
+ handle.Free();
+ }
+ }
+
+ ///
+ /// Converts a struct to an array of bytes.
+ ///
+ /// The type of structure to convert.
+ /// The object.
+ /// A byte array containing the results of encoding the specified set of characters.
+ public static Byte[] ToBytes(this T @this) where T : struct {
+ Byte[] data = new Byte[Marshal.SizeOf(@this)];
+ GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
+
+ try {
+ Marshal.StructureToPtr(@this, handle.AddrOfPinnedObject(), false);
+ return GetStructBytes(data);
+ } finally {
+ handle.Free();
+ }
+ }
+
+ ///
+ /// Swaps the endianness of an unsigned long to an unsigned integer.
+ ///
+ /// The bytes contained in a long.
+ ///
+ /// A 32-bit unsigned integer equivalent to the ulong
+ /// contained in longBytes.
+ ///
+ public static UInt32 SwapEndianness(this UInt64 @this) => (UInt32)(((@this & 0x000000ff) << 24) + ((@this & 0x0000ff00) << 8) + ((@this & 0x00ff0000) >> 8) + ((@this & 0xff000000) >> 24));
+
+ private static Byte[] GetStructBytes(Byte[] data) {
+ if(data == null) {
+ throw new ArgumentNullException(nameof(data));
+ }
+
+ FieldInfo[] fields = typeof(T).GetTypeInfo()
+ .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+
+ StructEndiannessAttribute endian = AttributeCache.DefaultCache.Value.RetrieveOne();
+
+ foreach(FieldInfo field in fields) {
+ if(endian == null && !field.IsDefined(typeof(StructEndiannessAttribute), false)) {
+ continue;
+ }
+
+ Int32 offset = Marshal.OffsetOf(field.Name).ToInt32();
+ Int32 length = Marshal.SizeOf(field.FieldType);
+
+ endian ??= AttributeCache.DefaultCache.Value.RetrieveOne(field);
+
+ if(endian != null && (endian.Endianness == Endianness.Big && BitConverter.IsLittleEndian ||
+ endian.Endianness == Endianness.Little && !BitConverter.IsLittleEndian)) {
+ Array.Reverse(data, offset, length);
+ }
+ }
+
+ return data;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Swan.Tiny/Extensions.cs b/Swan.Tiny/Extensions.cs
new file mode 100644
index 0000000..0f67daa
--- /dev/null
+++ b/Swan.Tiny/Extensions.cs
@@ -0,0 +1,230 @@
+#nullable enable
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+using Swan.Lite.Reflection;
+using Swan.Mappers;
+using Swan.Reflection;
+
+namespace Swan {
+ ///
+ /// Extension methods.
+ ///
+ public static partial class Extensions {
+ ///
+ /// Iterates over the public, instance, readable properties of the source and
+ /// tries to write a compatible value to a public, instance, writable property in the destination.
+ ///
+ /// The type of the source.
+ /// The source.
+ /// The target.
+ /// The ignore properties.
+ ///
+ /// Number of properties that was copied successful.
+ ///
+ public static Int32 CopyPropertiesTo(this T source, Object? target, params String[]? ignoreProperties) where T : class => ObjectMapper.Copy(source, target, GetCopyableProperties(target), ignoreProperties);
+
+ ///
+ /// Iterates over the public, instance, readable properties of the source and
+ /// tries to write a compatible value to a public, instance, writable property in the destination.
+ ///
+ /// The source.
+ /// The destination.
+ /// Properties to copy.
+ ///
+ /// Number of properties that were successfully copied.
+ ///
+ public static Int32 CopyOnlyPropertiesTo(this Object source, Object target, params String[]? propertiesToCopy) => ObjectMapper.Copy(source, target, propertiesToCopy);
+
+ ///
+ /// Copies the properties to new instance of T.
+ ///
+ /// The new object type.
+ /// The source.
+ /// The ignore properties.
+ ///
+ /// The specified type with properties copied.
+ ///
+ /// source.
+ public static T CopyPropertiesToNew(this Object source, String[]? ignoreProperties = null) where T : class {
+ if(source == null) {
+ throw new ArgumentNullException(nameof(source));
+ }
+
+ T target = Activator.CreateInstance();
+ _ = ObjectMapper.Copy(source, target, GetCopyableProperties(target), ignoreProperties);
+
+ return target;
+ }
+
+ ///
+ /// Copies the only properties to new instance of T.
+ ///
+ /// Object Type.
+ /// The source.
+ /// The properties to copy.
+ ///
+ /// The specified type with properties copied.
+ ///
+ /// source.
+ public static T CopyOnlyPropertiesToNew(this Object source, params String[] propertiesToCopy) where T : class {
+ if(source == null) {
+ throw new ArgumentNullException(nameof(source));
+ }
+
+ T target = Activator.CreateInstance();
+ _ = ObjectMapper.Copy(source, target, propertiesToCopy);
+
+ return target;
+ }
+
+ ///
+ /// Iterates over the keys of the source and tries to write a compatible value to a public,
+ /// instance, writable property in the destination.
+ ///
+ /// The source.
+ /// The target.
+ /// The ignore keys.
+ /// Number of properties that was copied successful.
+ public static Int32 CopyKeyValuePairTo(this IDictionary source, Object? target, params String[] ignoreKeys) => source == null ? throw new ArgumentNullException(nameof(source)) : ObjectMapper.Copy(source, target, null, ignoreKeys);
+
+ ///
+ /// Iterates over the keys of the source and tries to write a compatible value to a public,
+ /// instance, writable property in the destination.
+ ///
+ /// Object Type.
+ /// The source.
+ /// The ignore keys.
+ ///
+ /// The specified type with properties copied.
+ ///
+ public static T CopyKeyValuePairToNew(this IDictionary source, params String[] ignoreKeys) {
+ if(source == null) {
+ throw new ArgumentNullException(nameof(source));
+ }
+
+ T target = Activator.CreateInstance();
+ _ = source.CopyKeyValuePairTo(target, ignoreKeys);
+ return target;
+ }
+
+ ///
+ /// Does the specified action.
+ ///
+ /// The action.
+ /// The retry interval.
+ /// The retry count.
+ public static void Retry(this Action action, TimeSpan retryInterval = default, Int32 retryCount = 3) {
+ if(action == null) {
+ throw new ArgumentNullException(nameof(action));
+ }
+
+ _ = Retry(() => { action(); return null; }, retryInterval, retryCount);
+ }
+
+ ///
+ /// Does the specified action.
+ ///
+ /// The type of the source.
+ /// The action.
+ /// The retry interval.
+ /// The retry count.
+ ///
+ /// The return value of the method that this delegate encapsulates.
+ ///
+ /// action.
+ /// Represents one or many errors that occur during application execution.
+ public static T Retry(this Func action, TimeSpan retryInterval = default, Int32 retryCount = 3) {
+ if(action == null) {
+ throw new ArgumentNullException(nameof(action));
+ }
+
+ if(retryInterval == default) {
+ retryInterval = TimeSpan.FromSeconds(1);
+ }
+
+ global::System.Collections.Generic.List exceptions = new List();
+
+ for(Int32 retry = 0; retry < retryCount; retry++) {
+ try {
+ if(retry > 0) {
+ Task.Delay(retryInterval).Wait();
+ }
+
+ return action();
+ } catch(Exception ex) {
+ exceptions.Add(ex);
+ }
+ }
+
+ throw new AggregateException(exceptions);
+ }
+
+ ///
+ /// Gets the copyable properties.
+ ///
+ /// If there is no properties with the attribute AttributeCache returns all the properties.
+ ///
+ /// The object.
+ ///
+ /// Array of properties.
+ ///
+ /// model.
+ ///
+ public static IEnumerable GetCopyableProperties(this Object? @this) {
+ if(@this == null) {
+ throw new ArgumentNullException(nameof(@this));
+ }
+
+ global::System.Collections.Generic.IEnumerable collection = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties(@this.GetType(), true);
+
+ global::System.Collections.Generic.IEnumerable properties = collection.Select(x => new {
+ x.Name,
+ HasAttribute = AttributeCache.DefaultCache.Value.RetrieveOne(x) != null,
+ }).Where(x => x.HasAttribute).Select(x => x.Name);
+
+ return properties.Any() ? properties : collection.Select(x => x.Name);
+ }
+
+ internal static void CreateTarget(this Object source, Type targetType, Boolean includeNonPublic, ref Object? target) {
+ switch(source) {
+ // do nothing. Simply skip creation
+ case String _:
+ break;
+ // When using arrays, there is no default constructor, attempt to build a compatible array
+ case IList sourceObjectList when targetType.IsArray:
+ Type? elementType = targetType.GetElementType();
+
+ if(elementType != null) {
+ target = Array.CreateInstance(elementType, sourceObjectList.Count);
+ }
+
+ break;
+ default:
+ IEnumerable> constructors = ConstructorTypeCache.DefaultCache.Value.RetrieveAllConstructors(targetType, includeNonPublic);
+
+ // Try to check if empty constructor is available
+ if(constructors.Any(x => x.Item2.Length == 0)) {
+ target = Activator.CreateInstance(targetType, includeNonPublic);
+ } else {
+ Tuple firstCtor = constructors.OrderBy(x => x.Item2.Length).FirstOrDefault();
+
+ target = Activator.CreateInstance(targetType, firstCtor?.Item2.Select(arg => arg.GetType().GetDefault()).ToArray());
+ }
+
+ break;
+ }
+ }
+
+ internal static String GetNameWithCase(this String name, JsonSerializerCase jsonSerializerCase) => jsonSerializerCase switch
+ {
+ JsonSerializerCase.PascalCase => Char.ToUpperInvariant(name[0]) + name.Substring(1),
+ JsonSerializerCase.CamelCase => Char.ToLowerInvariant(name[0]) + name.Substring(1),
+ JsonSerializerCase.None => name,
+ _ => throw new ArgumentOutOfRangeException(nameof(jsonSerializerCase), jsonSerializerCase, null)
+ };
+ }
+}
diff --git a/Swan.Tiny/Formatters/HumanizeJson.cs b/Swan.Tiny/Formatters/HumanizeJson.cs
new file mode 100644
index 0000000..8099413
--- /dev/null
+++ b/Swan.Tiny/Formatters/HumanizeJson.cs
@@ -0,0 +1,126 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System;
+
+namespace Swan.Formatters {
+ internal class HumanizeJson {
+ private readonly StringBuilder _builder = new StringBuilder();
+ private readonly Int32 _indent;
+ private readonly String _indentStr;
+ private readonly Object _obj;
+
+ public HumanizeJson(Object obj, Int32 indent) {
+ if(obj == null) {
+ return;
+ }
+
+ this._indent = indent;
+ this._indentStr = new String(' ', indent * 4);
+ this._obj = obj;
+
+ this.ParseObject();
+ }
+
+ public String GetResult() => this._builder == null ? String.Empty : this._builder.ToString().TrimEnd();
+
+ private void ParseObject() {
+ switch(this._obj) {
+ case Dictionary dictionary:
+ this.AppendDictionary(dictionary);
+ break;
+ case List list:
+ this.AppendList(list);
+ break;
+ default:
+ this.AppendString();
+ break;
+ }
+ }
+
+ private void AppendDictionary(Dictionary objects) {
+ foreach(KeyValuePair kvp in objects) {
+ if(kvp.Value == null) {
+ continue;
+ }
+
+ Boolean writeOutput = false;
+
+ switch(kvp.Value) {
+ case Dictionary valueDictionary:
+ if(valueDictionary.Count > 0) {
+ writeOutput = true;
+ _ = this._builder.Append($"{this._indentStr}{kvp.Key,-16}: object").AppendLine();
+ }
+
+ break;
+ case List valueList:
+ if(valueList.Count > 0) {
+ writeOutput = true;
+ _ = this._builder.Append($"{this._indentStr}{kvp.Key,-16}: array[{valueList.Count}]").AppendLine();
+ }
+
+ break;
+ default:
+ writeOutput = true;
+ _ = this._builder.Append($"{this._indentStr}{kvp.Key,-16}: ");
+ break;
+ }
+
+ if(writeOutput) {
+ _ = this._builder.AppendLine(new HumanizeJson(kvp.Value, this._indent + 1).GetResult());
+ }
+ }
+ }
+
+ private void AppendList(List objects) {
+ Int32 index = 0;
+ foreach(Object value in objects) {
+ Boolean writeOutput = false;
+
+ switch(value) {
+ case Dictionary valueDictionary:
+ if(valueDictionary.Count > 0) {
+ writeOutput = true;
+ _ = this._builder.Append($"{this._indentStr}[{index}]: object").AppendLine();
+ }
+
+ break;
+ case List valueList:
+ if(valueList.Count > 0) {
+ writeOutput = true;
+ _ = this._builder.Append($"{this._indentStr}[{index}]: array[{valueList.Count}]").AppendLine();
+ }
+
+ break;
+ default:
+ writeOutput = true;
+ _ = this._builder.Append($"{this._indentStr}[{index}]: ");
+ break;
+ }
+
+ index++;
+
+ if(writeOutput) {
+ _ = this._builder.AppendLine(new HumanizeJson(value, this._indent + 1).GetResult());
+ }
+ }
+ }
+
+ private void AppendString() {
+ String stringValue = this._obj.ToString();
+
+ if(stringValue.Length + this._indentStr.Length > 96 || stringValue.IndexOf('\r') >= 0 ||
+ stringValue.IndexOf('\n') >= 0) {
+ _ = this._builder.AppendLine();
+ IEnumerable stringLines = stringValue.ToLines().Select(l => l.Trim());
+
+ foreach(String line in stringLines) {
+ _ = this._builder.AppendLine($"{this._indentStr}{line}");
+ }
+ } else {
+ _ = this._builder.Append($"{stringValue}");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Swan.Tiny/Formatters/Json.Converter.cs b/Swan.Tiny/Formatters/Json.Converter.cs
new file mode 100644
index 0000000..6878bcb
--- /dev/null
+++ b/Swan.Tiny/Formatters/Json.Converter.cs
@@ -0,0 +1,259 @@
+#nullable enable
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using Swan.Reflection;
+
+namespace Swan.Formatters {
+ ///
+ /// A very simple, light-weight JSON library written by Mario
+ /// to teach Geo how things are done
+ ///
+ /// This is an useful helper for small tasks but it doesn't represent a full-featured
+ /// serializer such as the beloved Json.NET.
+ ///
+ public static partial class Json {
+ private class Converter {
+ private static readonly ConcurrentDictionary MemberInfoNameCache = new ConcurrentDictionary();
+
+ private static readonly ConcurrentDictionary ListAddMethodCache = new ConcurrentDictionary();
+
+ private readonly Object? _target;
+ private readonly Type _targetType;
+ private readonly Boolean _includeNonPublic;
+ private readonly JsonSerializerCase _jsonSerializerCase;
+
+ private Converter(Object? source, Type targetType, ref Object? targetInstance, Boolean includeNonPublic, JsonSerializerCase jsonSerializerCase) {
+ this._targetType = targetInstance != null ? targetInstance.GetType() : targetType;
+ this._includeNonPublic = includeNonPublic;
+ this._jsonSerializerCase = jsonSerializerCase;
+
+ if(source == null) {
+ return;
+ }
+
+ Type sourceType = source.GetType();
+
+ if(this._targetType == null || this._targetType == typeof(Object)) {
+ this._targetType = sourceType;
+ }
+
+ if(sourceType == this._targetType) {
+ this._target = source;
+ return;
+ }
+
+ if(!this.TrySetInstance(targetInstance, source, ref this._target)) {
+ return;
+ }
+
+ this.ResolveObject(source, ref this._target);
+ }
+
+ internal static Object? FromJsonResult(Object? source, JsonSerializerCase jsonSerializerCase, Type? targetType = null, Boolean includeNonPublic = false) {
+ Object? nullRef = null;
+ return new Converter(source, targetType ?? typeof(Object), ref nullRef, includeNonPublic, jsonSerializerCase).GetResult();
+ }
+
+ private static Object? FromJsonResult(Object source, Type targetType, ref Object? targetInstance, Boolean includeNonPublic) => new Converter(source, targetType, ref targetInstance, includeNonPublic, JsonSerializerCase.None).GetResult();
+
+ private static Type? GetAddMethodParameterType(Type targetType) => ListAddMethodCache.GetOrAdd(targetType, x => x.GetMethods().FirstOrDefault(m => m.Name == AddMethodName && m.IsPublic && m.GetParameters().Length == 1)?.GetParameters()[0].ParameterType!);
+
+ private static void GetByteArray(String sourceString, ref Object? target) {
+ try {
+ target = Convert.FromBase64String(sourceString);
+ } // Try conversion from Base 64
+ catch(FormatException) {
+ target = Encoding.UTF8.GetBytes(sourceString);
+ } // Get the string bytes in UTF8
+ }
+
+ private Object GetSourcePropertyValue(IDictionary sourceProperties, MemberInfo targetProperty) {
+ String targetPropertyName = MemberInfoNameCache.GetOrAdd(targetProperty, x => AttributeCache.DefaultCache.Value.RetrieveOne(x)?.PropertyName ?? x.Name.GetNameWithCase(this._jsonSerializerCase));
+
+ return sourceProperties.GetValueOrDefault(targetPropertyName);
+ }
+
+ private Boolean TrySetInstance(Object? targetInstance, Object source, ref Object? target) {
+ if(targetInstance == null) {
+ // Try to create a default instance
+ try {
+ source.CreateTarget(this._targetType, this._includeNonPublic, ref target);
+ } catch {
+ return false;
+ }
+ } else {
+ target = targetInstance;
+ }
+
+ return true;
+ }
+
+ private Object? GetResult() => this._target ?? this._targetType.GetDefault();
+
+ private void ResolveObject(Object source, ref Object? target) {
+ switch(source) {
+ // Case 0: Special Cases Handling (Source and Target are of specific convertible types)
+ // Case 0.1: Source is string, Target is byte[]
+ case String sourceString when this._targetType == typeof(Byte[]):
+ GetByteArray(sourceString, ref target);
+ break;
+
+ // Case 1.1: Source is Dictionary, Target is IDictionary
+ case Dictionary sourceProperties when target is IDictionary targetDictionary:
+ this.PopulateDictionary(sourceProperties, targetDictionary);
+ break;
+
+ // Case 1.2: Source is Dictionary, Target is not IDictionary (i.e. it is a complex type)
+ case Dictionary sourceProperties:
+ this.PopulateObject(sourceProperties);
+ break;
+
+ // Case 2.1: Source is List, Target is Array
+ case List sourceList when target is Array targetArray:
+ this.PopulateArray(sourceList, targetArray);
+ break;
+
+ // Case 2.2: Source is List, Target is IList
+ case List sourceList when target is IList targetList:
+ this.PopulateIList(sourceList, targetList);
+ break;
+
+ // Case 3: Source is a simple type; Attempt conversion
+ default:
+ String sourceStringValue = source.ToStringInvariant();
+
+ // Handle basic types or enumerations if not
+ if(!this._targetType.TryParseBasicType(sourceStringValue, out target)) {
+ this.GetEnumValue(sourceStringValue, ref target);
+ }
+
+ break;
+ }
+ }
+
+ private void PopulateIList(IEnumerable objects, IList list) {
+ Type? parameterType = GetAddMethodParameterType(this._targetType);
+ if(parameterType == null) {
+ return;
+ }
+
+ foreach(Object item in objects) {
+ try {
+ _ = list.Add(FromJsonResult(item, this._jsonSerializerCase, parameterType, this._includeNonPublic));
+ } catch {
+ // ignored
+ }
+ }
+ }
+
+ private void PopulateArray(IList objects, Array array) {
+ Type? elementType = this._targetType.GetElementType();
+
+ for(Int32 i = 0; i < objects.Count; i++) {
+ try {
+ Object? targetItem = FromJsonResult(objects[i], this._jsonSerializerCase, elementType, this._includeNonPublic);
+ array.SetValue(targetItem, i);
+ } catch {
+ // ignored
+ }
+ }
+ }
+
+ private void GetEnumValue(String sourceStringValue, ref Object? target) {
+ Type? enumType = Nullable.GetUnderlyingType(this._targetType);
+ if(enumType == null && this._targetType.IsEnum) {
+ enumType = this._targetType;
+ }
+
+ if(enumType == null) {
+ return;
+ }
+
+ try {
+ target = Enum.Parse(enumType, sourceStringValue);
+ } catch {
+ // ignored
+ }
+ }
+
+ private void PopulateDictionary(IDictionary sourceProperties, IDictionary targetDictionary) {
+ // find the add method of the target dictionary
+ MethodInfo addMethod = this._targetType.GetMethods().FirstOrDefault(m => m.Name == AddMethodName && m.IsPublic && m.GetParameters().Length == 2);
+
+ // skip if we don't have a compatible add method
+ if(addMethod == null) {
+ return;
+ }
+
+ global::System.Reflection.ParameterInfo[] addMethodParameters = addMethod.GetParameters();
+ if(addMethodParameters[0].ParameterType != typeof(String)) {
+ return;
+ }
+
+ // Retrieve the target entry type
+ Type targetEntryType = addMethodParameters[1].ParameterType;
+
+ // Add the items to the target dictionary
+ foreach(KeyValuePair sourceProperty in sourceProperties) {
+ try {
+ Object? targetEntryValue = FromJsonResult(sourceProperty.Value, this._jsonSerializerCase, targetEntryType, this._includeNonPublic);
+ targetDictionary.Add(sourceProperty.Key, targetEntryValue);
+ } catch {
+ // ignored
+ }
+ }
+ }
+
+ private void PopulateObject(IDictionary