coding styles nullable

This commit is contained in:
BlubbFish 2019-12-08 19:54:52 +01:00
parent c78348324a
commit aa9fcd4a36
109 changed files with 12952 additions and 14462 deletions

View File

@ -3,50 +3,47 @@ using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Swan.Collections namespace Swan.Collections {
{ /// <summary>
/// A thread-safe collection cache repository for types.
/// </summary>
/// <typeparam name="TValue">The type of member to cache.</typeparam>
public class CollectionCacheRepository<TValue> {
private readonly Lazy<ConcurrentDictionary<Type, IEnumerable<TValue>>> _data = new Lazy<ConcurrentDictionary<Type, IEnumerable<TValue>>>(() => new ConcurrentDictionary<Type, IEnumerable<TValue>>(), true);
/// <summary> /// <summary>
/// A thread-safe collection cache repository for types. /// Determines whether the cache contains the specified key.
/// </summary> /// </summary>
/// <typeparam name="TValue">The type of member to cache.</typeparam> /// <param name="key">The key.</param>
public class CollectionCacheRepository<TValue> /// <returns><c>true</c> if the cache contains the key, otherwise <c>false</c>.</returns>
{ public Boolean ContainsKey(Type key) => this._data.Value.ContainsKey(key);
private readonly Lazy<ConcurrentDictionary<Type, IEnumerable<TValue>>> _data =
new Lazy<ConcurrentDictionary<Type, IEnumerable<TValue>>>(() => /// <summary>
new ConcurrentDictionary<Type, IEnumerable<TValue>>(), true); /// Retrieves the properties stored for the specified type.
/// If the properties are not available, it calls the factory method to retrieve them
/// <summary> /// and returns them as an array of PropertyInfo.
/// Determines whether the cache contains the specified key. /// </summary>
/// </summary> /// <param name="key">The key.</param>
/// <param name="key">The key.</param> /// <param name="factory">The factory.</param>
/// <returns><c>true</c> if the cache contains the key, otherwise <c>false</c>.</returns> /// <returns>
public bool ContainsKey(Type key) => _data.Value.ContainsKey(key); /// An array of the properties stored for the specified type.
/// </returns>
/// <summary> /// <exception cref="ArgumentNullException">
/// Retrieves the properties stored for the specified type. /// key
/// If the properties are not available, it calls the factory method to retrieve them /// or
/// and returns them as an array of PropertyInfo. /// factory.
/// </summary> /// </exception>
/// <param name="key">The key.</param> /// <exception cref="System.ArgumentNullException">type.</exception>
/// <param name="factory">The factory.</param> public IEnumerable<TValue> Retrieve(Type key, Func<Type, IEnumerable<TValue>> factory) {
/// <returns> if(key == null) {
/// An array of the properties stored for the specified type. throw new ArgumentNullException(nameof(key));
/// </returns> }
/// <exception cref="ArgumentNullException">
/// key if(factory == null) {
/// or throw new ArgumentNullException(nameof(factory));
/// factory. }
/// </exception>
/// <exception cref="System.ArgumentNullException">type.</exception> return this._data.Value.GetOrAdd(key, k => factory.Invoke(k).Where(item => item != null));
public IEnumerable<TValue> Retrieve(Type key, Func<Type, IEnumerable<TValue>> factory) }
{ }
if (key == null)
throw new ArgumentNullException(nameof(key));
if (factory == null)
throw new ArgumentNullException(nameof(factory));
return _data.Value.GetOrAdd(key, k => factory.Invoke(k).Where(item => item != null));
}
}
} }

View File

@ -3,74 +3,75 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Swan.Configuration; using Swan.Configuration;
namespace Swan.Collections namespace Swan.Collections {
{ /// <summary>
/// <para>Implements a collection of components.</para>
/// <para>Each component in the collection may be given a unique name for later retrieval.</para>
/// </summary>
/// <typeparam name="T">The type of components in the collection.</typeparam>
/// <seealso cref="IComponentCollection{T}" />
public class ComponentCollection<T> : ConfiguredObject, IComponentCollection<T> {
private readonly List<T> _components = new List<T>();
private readonly List<(String, T)> _componentsWithSafeNames = new List<(String, T)>();
private readonly Dictionary<String, T> _namedComponents = new Dictionary<String, T>();
/// <inheritdoc />
public Int32 Count => this._components.Count;
/// <inheritdoc />
public IReadOnlyDictionary<String, T> Named => this._namedComponents;
/// <inheritdoc />
public IReadOnlyList<(String SafeName, T Component)> WithSafeNames => this._componentsWithSafeNames;
/// <inheritdoc />
public T this[Int32 index] => this._components[index];
/// <inheritdoc />
public T this[String key] => this._namedComponents[key];
/// <inheritdoc />
public IEnumerator<T> GetEnumerator() => this._components.GetEnumerator();
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this._components).GetEnumerator();
/// <inheritdoc />
/// <exception cref="InvalidOperationException">The collection is locked.</exception>
public void Add(String name, T component) {
this.EnsureConfigurationNotLocked();
if(name != null) {
if(name.Length == 0) {
throw new ArgumentException("Component name is empty.", nameof(name));
}
if(this._namedComponents.ContainsKey(name)) {
throw new ArgumentException("Duplicate component name.", nameof(name));
}
}
if(component == null) {
throw new ArgumentNullException(nameof(component));
}
if(this._components.Contains(component)) {
throw new ArgumentException("Component has already been added.", nameof(component));
}
this._components.Add(component);
this._componentsWithSafeNames.Add((name ?? $"<{component.GetType().Name}>", component));
if(name != null) {
this._namedComponents.Add(name, component);
}
}
/// <summary> /// <summary>
/// <para>Implements a collection of components.</para> /// Locks the collection, preventing further additions.
/// <para>Each component in the collection may be given a unique name for later retrieval.</para>
/// </summary> /// </summary>
/// <typeparam name="T">The type of components in the collection.</typeparam> public void Lock() => this.LockConfiguration();
/// <seealso cref="IComponentCollection{T}" /> }
public class ComponentCollection<T> : ConfiguredObject, IComponentCollection<T>
{
private readonly List<T> _components = new List<T>();
private readonly List<(string, T)> _componentsWithSafeNames = new List<(string, T)>();
private readonly Dictionary<string, T> _namedComponents = new Dictionary<string, T>();
/// <inheritdoc />
public int Count => _components.Count;
/// <inheritdoc />
public IReadOnlyDictionary<string, T> Named => _namedComponents;
/// <inheritdoc />
public IReadOnlyList<(string SafeName, T Component)> WithSafeNames => _componentsWithSafeNames;
/// <inheritdoc />
public T this[int index] => _components[index];
/// <inheritdoc />
public T this[string key] => _namedComponents[key];
/// <inheritdoc />
public IEnumerator<T> GetEnumerator() => _components.GetEnumerator();
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_components).GetEnumerator();
/// <inheritdoc />
/// <exception cref="InvalidOperationException">The collection is locked.</exception>
public void Add(string name, T component)
{
EnsureConfigurationNotLocked();
if (name != null)
{
if (name.Length == 0)
throw new ArgumentException("Component name is empty.", nameof(name));
if (_namedComponents.ContainsKey(name))
throw new ArgumentException("Duplicate component name.", nameof(name));
}
if (component == null)
throw new ArgumentNullException(nameof(component));
if (_components.Contains(component))
throw new ArgumentException("Component has already been added.", nameof(component));
_components.Add(component);
_componentsWithSafeNames.Add((name ?? $"<{component.GetType().Name}>", component));
if (name != null)
_namedComponents.Add(name, component);
}
/// <summary>
/// Locks the collection, preventing further additions.
/// </summary>
public void Lock() => LockConfiguration();
}
} }

View File

@ -4,301 +4,266 @@ using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Swan.Collections namespace Swan.Collections {
{ /// <summary>
/// Represents a thread-safe collection of key/value pairs that does not store null values
/// and can be accessed by multiple threads concurrently.
/// </summary>
/// <typeparam name="TKey">The type of keys in the dictionary. This must be a reference type.</typeparam>
/// <typeparam name="TValue">The type of values in the dictionary. This must be a reference type.</typeparam>
/// <seealso cref="IDataDictionary{TKey,TValue}"/>
public sealed class ConcurrentDataDictionary<TKey, TValue> : IDataDictionary<TKey, TValue> where TKey : class where TValue : class {
#region Private data
private readonly ConcurrentDictionary<TKey, TValue> _dictionary;
#endregion
#region Instance management
/// <summary> /// <summary>
/// Represents a thread-safe collection of key/value pairs that does not store null values /// Initializes a new instance of the <see cref="ConcurrentDataDictionary{TKey,TValue}"/> class
/// and can be accessed by multiple threads concurrently. /// that is empty, has the default concurrency level, has the default initial capacity,
/// and uses the default comparer for <typeparamref name="TKey"/>.
/// </summary> /// </summary>
/// <typeparam name="TKey">The type of keys in the dictionary. This must be a reference type.</typeparam> /// <see cref="ConcurrentDictionary{TKey,TValue}()"/>
/// <typeparam name="TValue">The type of values in the dictionary. This must be a reference type.</typeparam> public ConcurrentDataDictionary() => this._dictionary = new ConcurrentDictionary<TKey, TValue>();
/// <seealso cref="IDataDictionary{TKey,TValue}"/>
public sealed class ConcurrentDataDictionary<TKey, TValue> : IDataDictionary<TKey, TValue> /// <summary>
where TKey : class /// Initializes a new instance of the <see cref="ConcurrentDataDictionary{TKey,TValue}"/> class
where TValue : class /// that contains elements copied from the specified <see cref="IEnumerable{T}"/>, has the default concurrency level,
{ /// has the default initial capacity, and uses the default comparer for <typeparamref name="TKey"/>.
#region Private data /// </summary>
/// <param name="collection">The <see cref="IEnumerable{T}"/> whose elements are copied
private readonly ConcurrentDictionary<TKey, TValue> _dictionary; /// to the new <see cref="ConcurrentDataDictionary{TKey,TValue}"/>.</param>
/// <exception cref="ArgumentNullException"><paramref name="collection"/> is <see langword="null"/>.</exception>
#endregion /// <remarks>
/// <para>Since <see cref="ConcurrentDataDictionary{TKey,TValue}"/> does not store null values,
#region Instance management /// key/value pairs whose value is <see langword="null"/> will not be copied from <paramref name="collection"/>.</para>
/// </remarks>
/// <summary> /// <see cref="ConcurrentDictionary{TKey,TValue}(IEnumerable{KeyValuePair{TKey,TValue}})"/>
/// Initializes a new instance of the <see cref="ConcurrentDataDictionary{TKey,TValue}"/> class public ConcurrentDataDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection) {
/// that is empty, has the default concurrency level, has the default initial capacity, if(collection == null) {
/// and uses the default comparer for <typeparamref name="TKey"/>. throw new ArgumentNullException(nameof(collection));
/// </summary> }
/// <see cref="ConcurrentDictionary{TKey,TValue}()"/>
public ConcurrentDataDictionary() this._dictionary = new ConcurrentDictionary<TKey, TValue>(collection.Where(pair => pair.Value != null));
{ }
_dictionary = new ConcurrentDictionary<TKey, TValue>();
} /// <summary>
/// Initializes a new instance of the <see cref="ConcurrentDataDictionary{TKey,TValue}"/> class
/// <summary> /// that is empty, has the default concurrency level and capacity, and uses the specified <see cref="IEqualityComparer{T}"/>.
/// Initializes a new instance of the <see cref="ConcurrentDataDictionary{TKey,TValue}"/> class /// </summary>
/// that contains elements copied from the specified <see cref="IEnumerable{T}"/>, has the default concurrency level, /// <param name="comparer">The equality comparison implementation to use when comparing keys.</param>
/// has the default initial capacity, and uses the default comparer for <typeparamref name="TKey"/>. /// <exception cref="ArgumentNullException"><paramref name="comparer"/> is <see langword="null"/>.</exception>
/// </summary> /// <see cref="ConcurrentDictionary{TKey,TValue}(IEqualityComparer{TKey})"/>
/// <param name="collection">The <see cref="IEnumerable{T}"/> whose elements are copied public ConcurrentDataDictionary(IEqualityComparer<TKey> comparer) => this._dictionary = new ConcurrentDictionary<TKey, TValue>(comparer);
/// to the new <see cref="ConcurrentDataDictionary{TKey,TValue}"/>.</param>
/// <exception cref="ArgumentNullException"><paramref name="collection"/> is <see langword="null"/>.</exception> /// <summary>
/// <remarks> /// Initializes a new instance of the <see cref="ConcurrentDataDictionary{TKey, TValue}"/> class
/// <para>Since <see cref="ConcurrentDataDictionary{TKey,TValue}"/> does not store null values, /// that contains elements copied from the specified <see cref="IEnumerable{T}"/>, has the default concurrency level,
/// key/value pairs whose value is <see langword="null"/> will not be copied from <paramref name="collection"/>.</para> /// has the default initial capacity, and uses the specified <see cref="IEqualityComparer{T}"/>.
/// </remarks> /// </summary>
/// <see cref="ConcurrentDictionary{TKey,TValue}(IEnumerable{KeyValuePair{TKey,TValue}})"/> /// <param name="collection">The <see cref="IEnumerable{T}"/> whose elements are copied
public ConcurrentDataDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection) /// to the new <see cref="ConcurrentDataDictionary{TKey,TValue}"/>.</param>
{ /// <param name="comparer">The equality comparison implementation to use when comparing keys.</param>
if (collection == null) /// <remarks>
throw new ArgumentNullException(nameof(collection)); /// <para>Since <see cref="ConcurrentDataDictionary{TKey,TValue}"/> does not store null values,
/// key/value pairs whose value is <see langword="null"/> will not be copied from <paramref name="collection"/>.</para>
_dictionary = new ConcurrentDictionary<TKey, TValue>(collection.Where(pair => pair.Value != null)); /// </remarks>
} /// <exception cref="ArgumentNullException">
/// <para><paramref name="collection"/> is <see langword="null"/>.</para>
/// <summary> /// <para>- or -.</para>
/// Initializes a new instance of the <see cref="ConcurrentDataDictionary{TKey,TValue}"/> class /// <para><paramref name="comparer"/> is <see langword="null"/>.</para>
/// that is empty, has the default concurrency level and capacity, and uses the specified <see cref="IEqualityComparer{T}"/>. /// </exception>
/// </summary> /// <see cref="ConcurrentDictionary{TKey,TValue}(IEnumerable{KeyValuePair{TKey,TValue}},IEqualityComparer{TKey})"/>
/// <param name="comparer">The equality comparison implementation to use when comparing keys.</param> public ConcurrentDataDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) {
/// <exception cref="ArgumentNullException"><paramref name="comparer"/> is <see langword="null"/>.</exception> if(collection == null) {
/// <see cref="ConcurrentDictionary{TKey,TValue}(IEqualityComparer{TKey})"/> throw new ArgumentNullException(nameof(collection));
public ConcurrentDataDictionary(IEqualityComparer<TKey> comparer) }
{
_dictionary = new ConcurrentDictionary<TKey, TValue>(comparer); this._dictionary = new ConcurrentDictionary<TKey, TValue>(collection.Where(pair => pair.Value != null), comparer);
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ConcurrentDataDictionary{TKey, TValue}"/> class /// Initializes a new instance of the <see cref="ConcurrentDataDictionary{TKey, TValue}"/> class
/// that contains elements copied from the specified <see cref="IEnumerable{T}"/>, has the default concurrency level, /// that is empty, has the specified concurrency level and capacity, and uses the default comparer for the key type.
/// has the default initial capacity, and uses the specified <see cref="IEqualityComparer{T}"/>. /// </summary>
/// </summary> /// <param name="concurrencyLevel">The estimated number of threads that will update
/// <param name="collection">The <see cref="IEnumerable{T}"/> whose elements are copied /// the <see cref="ConcurrentDataDictionary{TKey, TValue}"/> concurrently.</param>
/// to the new <see cref="ConcurrentDataDictionary{TKey,TValue}"/>.</param> /// <param name="capacity">The initial number of elements that the <see cref="ConcurrentDataDictionary{TKey, TValue}"/> can contain.</param>
/// <param name="comparer">The equality comparison implementation to use when comparing keys.</param> /// <exception cref="ArgumentOutOfRangeException">
/// <remarks> /// <para><paramref name="concurrencyLevel"/> is less than 1.</para>
/// <para>Since <see cref="ConcurrentDataDictionary{TKey,TValue}"/> does not store null values, /// <para>- or -.</para>
/// key/value pairs whose value is <see langword="null"/> will not be copied from <paramref name="collection"/>.</para> /// <para><paramref name="capacity"/> is less than 0.</para>
/// </remarks> /// </exception>
/// <exception cref="ArgumentNullException"> /// <see cref="ConcurrentDictionary{TKey,TValue}(Int32,Int32)"/>
/// <para><paramref name="collection"/> is <see langword="null"/>.</para> public ConcurrentDataDictionary(Int32 concurrencyLevel, Int32 capacity) => this._dictionary = new ConcurrentDictionary<TKey, TValue>(concurrencyLevel, capacity);
/// <para>- or -.</para>
/// <para><paramref name="comparer"/> is <see langword="null"/>.</para> /// <summary>
/// </exception> /// Initializes a new instance of the <see cref="ConcurrentDataDictionary{TKey, TValue}"/> class
/// <see cref="ConcurrentDictionary{TKey,TValue}(IEnumerable{KeyValuePair{TKey,TValue}},IEqualityComparer{TKey})"/> /// that contains elements copied from the specified <see cref="IEnumerable{T}"/>, has the specified concurrency level,
public ConcurrentDataDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) /// has the default initial capacity, and uses the specified <see cref="IEqualityComparer{T}"/>.
{ /// </summary>
if (collection == null) /// <param name="concurrencyLevel">The estimated number of threads that will update
throw new ArgumentNullException(nameof(collection)); /// the <see cref="ConcurrentDataDictionary{TKey, TValue}"/> concurrently.</param>
/// <param name="collection">The <see cref="IEnumerable{T}"/> whose elements are copied
_dictionary = new ConcurrentDictionary<TKey, TValue>(collection.Where(pair => pair.Value != null), comparer); /// to the new <see cref="ConcurrentDataDictionary{TKey,TValue}"/>.</param>
} /// <param name="comparer">The equality comparison implementation to use when comparing keys.</param>
/// <remarks>
/// <summary> /// <para>Since <see cref="ConcurrentDataDictionary{TKey,TValue}"/> does not store null values,
/// Initializes a new instance of the <see cref="ConcurrentDataDictionary{TKey, TValue}"/> class /// key/value pairs whose value is <see langword="null"/> will not be copied from <paramref name="collection"/>.</para>
/// that is empty, has the specified concurrency level and capacity, and uses the default comparer for the key type. /// </remarks>
/// </summary> /// <exception cref="ArgumentNullException">
/// <param name="concurrencyLevel">The estimated number of threads that will update /// <para><paramref name="collection"/> is <see langword="null"/>.</para>
/// the <see cref="ConcurrentDataDictionary{TKey, TValue}"/> concurrently.</param> /// <para>- or -.</para>
/// <param name="capacity">The initial number of elements that the <see cref="ConcurrentDataDictionary{TKey, TValue}"/> can contain.</param> /// <para><paramref name="comparer"/> is <see langword="null"/>.</para>
/// <exception cref="ArgumentOutOfRangeException"> /// </exception>
/// <para><paramref name="concurrencyLevel"/> is less than 1.</para> /// <exception cref="ArgumentOutOfRangeException"><paramref name="concurrencyLevel"/> is less than 1.</exception>
/// <para>- or -.</para> /// <see cref="ConcurrentDictionary{TKey,TValue}(Int32,IEnumerable{KeyValuePair{TKey,TValue}},IEqualityComparer{TKey})"/>
/// <para><paramref name="capacity"/> is less than 0.</para> public ConcurrentDataDictionary(Int32 concurrencyLevel, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) {
/// </exception> if(collection == null) {
/// <see cref="ConcurrentDictionary{TKey,TValue}(int,int)"/> throw new ArgumentNullException(nameof(collection));
public ConcurrentDataDictionary(int concurrencyLevel, int capacity) }
{
_dictionary = new ConcurrentDictionary<TKey, TValue>(concurrencyLevel, capacity); this._dictionary = new ConcurrentDictionary<TKey, TValue>(concurrencyLevel, collection.Where(pair => pair.Value != null), comparer);
} }
/// <summary> #endregion
/// Initializes a new instance of the <see cref="ConcurrentDataDictionary{TKey, TValue}"/> class
/// that contains elements copied from the specified <see cref="IEnumerable{T}"/>, has the specified concurrency level, #region Public APIs
/// has the default initial capacity, and uses the specified <see cref="IEqualityComparer{T}"/>.
/// </summary> /// <inheritdoc cref="IDataDictionary{TKey,TValue}.Count"/>
/// <param name="concurrencyLevel">The estimated number of threads that will update public Int32 Count => this._dictionary.Count;
/// the <see cref="ConcurrentDataDictionary{TKey, TValue}"/> concurrently.</param>
/// <param name="collection">The <see cref="IEnumerable{T}"/> whose elements are copied /// <inheritdoc cref="IDataDictionary{TKey,TValue}.IsEmpty"/>
/// to the new <see cref="ConcurrentDataDictionary{TKey,TValue}"/>.</param> public Boolean IsEmpty => this._dictionary.IsEmpty;
/// <param name="comparer">The equality comparison implementation to use when comparing keys.</param>
/// <remarks> /// <inheritdoc cref="IDictionary{TKey,TValue}.Keys"/>
/// <para>Since <see cref="ConcurrentDataDictionary{TKey,TValue}"/> does not store null values, public ICollection<TKey> Keys => this._dictionary.Keys;
/// key/value pairs whose value is <see langword="null"/> will not be copied from <paramref name="collection"/>.</para>
/// </remarks> /// <inheritdoc cref="IDictionary{TKey,TValue}.Values"/>
/// <exception cref="ArgumentNullException"> public ICollection<TValue> Values => this._dictionary.Values;
/// <para><paramref name="collection"/> is <see langword="null"/>.</para>
/// <para>- or -.</para> /// <inheritdoc cref="IDataDictionary{TKey,TValue}.this"/>
/// <para><paramref name="comparer"/> is <see langword="null"/>.</para> public TValue this[TKey key] {
/// </exception> get => this._dictionary.TryGetValue(key ?? throw new ArgumentNullException(nameof(key)), out TValue value) ? value : null;
/// <exception cref="ArgumentOutOfRangeException"><paramref name="concurrencyLevel"/> is less than 1.</exception> set {
/// <see cref="ConcurrentDictionary{TKey,TValue}(int,IEnumerable{KeyValuePair{TKey,TValue}},IEqualityComparer{TKey})"/> if(value != null) {
public ConcurrentDataDictionary(int concurrencyLevel, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) this._dictionary[key] = value;
{ } else {
if (collection == null) _ = this._dictionary.TryRemove(key, out _);
throw new ArgumentNullException(nameof(collection)); }
}
_dictionary = new ConcurrentDictionary<TKey, TValue>( }
concurrencyLevel,
collection.Where(pair => pair.Value != null), /// <inheritdoc cref="IDataDictionary{TKey,TValue}.Clear"/>
comparer); public void Clear() => this._dictionary.Clear();
}
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.ContainsKey"/>
#endregion public Boolean ContainsKey(TKey key) => this._dictionary.ContainsKey(key);
#region Public APIs /// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.GetOrAdd(TKey,TValue)"/>
public TValue GetOrAdd(TKey key, TValue value) {
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.Count"/> if(key == null) {
public int Count => _dictionary.Count; throw new ArgumentNullException(nameof(key));
}
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.IsEmpty"/>
public bool IsEmpty => _dictionary.IsEmpty; return value != null ? this._dictionary.GetOrAdd(key, value) : this._dictionary.TryGetValue(key, out TValue retrievedValue) ? retrievedValue : null;
}
/// <inheritdoc cref="IDictionary{TKey,TValue}.Keys"/>
public ICollection<TKey> Keys => _dictionary.Keys; /// <inheritdoc cref="IDictionary{TKey,TValue}.Remove(TKey)"/>
public Boolean Remove(TKey key) => this._dictionary.TryRemove(key, out _);
/// <inheritdoc cref="IDictionary{TKey,TValue}.Values"/>
public ICollection<TValue> Values => _dictionary.Values; /// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.TryAdd"/>
public Boolean TryAdd(TKey key, TValue value) {
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.this"/> if(key == null) {
public TValue? this[TKey key] throw new ArgumentNullException(nameof(key));
{ }
get => _dictionary.TryGetValue(key ?? throw new ArgumentNullException(nameof(key)), out var value) ? value : null;
set return value == null || this._dictionary.TryAdd(key, value);
{ }
if (value != null)
{ /// <inheritdoc cref="IDataDictionary{TKey,TValue}.TryGetValue"/>
_dictionary[key] = value; public Boolean TryGetValue(TKey key, out TValue value) => this._dictionary.TryGetValue(key, out value);
}
else /// <inheritdoc cref="IDataDictionary{TKey,TValue}.TryRemove"/>
{ public Boolean TryRemove(TKey key, out TValue value) => this._dictionary.TryRemove(key, out value);
_dictionary.TryRemove(key, out _);
} /// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.TryUpdate"/>
} public Boolean TryUpdate(TKey key, TValue newValue, TValue comparisonValue) {
} if(key == null) {
throw new ArgumentNullException(nameof(key));
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.Clear"/> }
public void Clear() => _dictionary.Clear();
return newValue != null && comparisonValue != null && this._dictionary.TryUpdate(key, newValue, comparisonValue);
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.ContainsKey"/> }
public bool ContainsKey(TKey key) => _dictionary.ContainsKey(key);
#endregion
/// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.GetOrAdd(TKey,TValue)"/>
public TValue? GetOrAdd(TKey key, TValue value) #region Implementation of IDictionary<TKey, TValue>
{
if (key == null) /// <inheritdoc cref="IDictionary{TKey,TValue}.Add(TKey,TValue)"/>
throw new ArgumentNullException(nameof(key)); void IDictionary<TKey, TValue>.Add(TKey key, TValue value) {
if(value != null) {
if (value != null) ((IDictionary<TKey, TValue>)this._dictionary).Add(key, value);
return _dictionary.GetOrAdd(key, value); } else {
_ = this._dictionary.TryRemove(key, out _);
return _dictionary.TryGetValue(key, out var retrievedValue) ? retrievedValue : null; }
} }
/// <inheritdoc cref="IDictionary{TKey,TValue}.Remove(TKey)"/> #endregion
public bool Remove(TKey key) => _dictionary.TryRemove(key, out _);
#region Implementation of IReadOnlyDictionary<TKey, TValue>
/// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.TryAdd"/>
public bool TryAdd(TKey key, TValue value) /// <inheritdoc cref="IReadOnlyDictionary{TKey,TValue}.Keys"/>
{ IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => this._dictionary.Keys;
if (key == null)
throw new ArgumentNullException(nameof(key)); /// <inheritdoc cref="IReadOnlyDictionary{TKey,TValue}.Values"/>
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => this._dictionary.Values;
return value == null || _dictionary.TryAdd(key, value);
} #endregion
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.TryGetValue"/> #region Implementation of ICollection<KeyValuePair<TKey, TValue>>
public bool TryGetValue(TKey key, out TValue value) => _dictionary.TryGetValue(key, out value);
/// <inheritdoc cref="ICollection{T}.IsReadOnly"/>
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.TryRemove"/> /// <remarks>
public bool TryRemove(TKey key, out TValue value) => _dictionary.TryRemove(key, out value); /// <para>This property is always <see langword="false"/> for a <see cref="ConcurrentDataDictionary{TKey,TValue}"/>.</para>
/// </remarks>
/// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.TryUpdate"/> Boolean ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false;
public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue)
{ /// <inheritdoc cref="ICollection{T}.Add"/>
if (key == null) void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) {
throw new ArgumentNullException(nameof(key)); if(item.Value != null) {
((ICollection<KeyValuePair<TKey, TValue>>)this._dictionary).Add(item);
return newValue != null && comparisonValue != null && _dictionary.TryUpdate(key, newValue, comparisonValue); } else {
} _ = this._dictionary.TryRemove(item.Key, out _);
}
#endregion }
#region Implementation of IDictionary<TKey, TValue> /// <inheritdoc cref="ICollection{T}.Contains"/>
Boolean ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) => ((ICollection<KeyValuePair<TKey, TValue>>)this._dictionary).Contains(item);
/// <inheritdoc cref="IDictionary{TKey,TValue}.Add(TKey,TValue)"/>
void IDictionary<TKey, TValue>.Add(TKey key, TValue value) /// <inheritdoc cref="ICollection{T}.CopyTo"/>
{ void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, Int32 arrayIndex) => ((ICollection<KeyValuePair<TKey, TValue>>)this._dictionary).CopyTo(array, arrayIndex);
if (value != null)
{ /// <inheritdoc cref="ICollection{T}.Remove"/>
((IDictionary<TKey, TValue>)_dictionary).Add(key, value); Boolean ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) => ((ICollection<KeyValuePair<TKey, TValue>>)this._dictionary).Remove(item);
}
else #endregion
{
_dictionary.TryRemove(key, out _); #region Implementation of IEnumerable<KeyValuePair<TKey, TValue>>
}
} /// <inheritdoc cref="IEnumerable{T}.GetEnumerator"/>
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() => this._dictionary.GetEnumerator();
#endregion
#endregion
#region Implementation of IReadOnlyDictionary<TKey, TValue>
#region Implementation of IEnumerable
/// <inheritdoc cref="IReadOnlyDictionary{TKey,TValue}.Keys"/>
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => _dictionary.Keys; /// <inheritdoc cref="IEnumerable.GetEnumerator"/>
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this._dictionary).GetEnumerator();
/// <inheritdoc cref="IReadOnlyDictionary{TKey,TValue}.Values"/>
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => _dictionary.Values; #endregion
}
#endregion
#region Implementation of ICollection<KeyValuePair<TKey, TValue>>
/// <inheritdoc cref="ICollection{T}.IsReadOnly"/>
/// <remarks>
/// <para>This property is always <see langword="false"/> for a <see cref="ConcurrentDataDictionary{TKey,TValue}"/>.</para>
/// </remarks>
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false;
/// <inheritdoc cref="ICollection{T}.Add"/>
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
{
if (item.Value != null)
{
((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).Add(item);
}
else
{
_dictionary.TryRemove(item.Key, out _);
}
}
/// <inheritdoc cref="ICollection{T}.Contains"/>
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
=> ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).Contains(item);
/// <inheritdoc cref="ICollection{T}.CopyTo"/>
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
=> ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).CopyTo(array, arrayIndex);
/// <inheritdoc cref="ICollection{T}.Remove"/>
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
=> ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).Remove(item);
#endregion
#region Implementation of IEnumerable<KeyValuePair<TKey, TValue>>
/// <inheritdoc cref="IEnumerable{T}.GetEnumerator"/>
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() => _dictionary.GetEnumerator();
#endregion
#region Implementation of IEnumerable
/// <inheritdoc cref="IEnumerable.GetEnumerator"/>
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_dictionary).GetEnumerator();
#endregion
}
} }

View File

@ -1,341 +1,312 @@
using System; 
using System;
using System.Collections; using System.Collections;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Swan.Collections namespace Swan.Collections {
{ /// <summary>
/// Represents a non-thread-safe collection of key/value pairs that does not store null values.
/// </summary>
/// <typeparam name="TKey">The type of keys in the dictionary. This must be a reference type.</typeparam>
/// <typeparam name="TValue">The type of values in the dictionary. This must be a reference type.</typeparam>
/// <seealso cref="IDataDictionary{TKey,TValue}"/>
public sealed class DataDictionary<TKey, TValue> : IDataDictionary<TKey, TValue>
where TKey : class
where TValue : class {
#region Private data
private readonly Dictionary<TKey, TValue> _dictionary;
#endregion
#region Instance management
/// <summary> /// <summary>
/// Represents a non-thread-safe collection of key/value pairs that does not store null values. /// Initializes a new instance of the <see cref="DataDictionary{TKey,TValue}"/> class
/// that is empty, has the default initial capacity,
/// and uses the default comparer for <typeparamref name="TKey"/>.
/// </summary> /// </summary>
/// <typeparam name="TKey">The type of keys in the dictionary. This must be a reference type.</typeparam> /// <see cref="Dictionary{TKey,TValue}()"/>
/// <typeparam name="TValue">The type of values in the dictionary. This must be a reference type.</typeparam> public DataDictionary() => this._dictionary = new Dictionary<TKey, TValue>();
/// <seealso cref="IDataDictionary{TKey,TValue}"/>
public sealed class DataDictionary<TKey, TValue> : IDataDictionary<TKey, TValue> /// <summary>
where TKey : class /// Initializes a new instance of the <see cref="DataDictionary{TKey,TValue}"/> class
where TValue : class /// that contains elements copied from the specified <see cref="IEnumerable{T}"/>,
{ /// has the default initial capacity, and uses the default comparer for <typeparamref name="TKey"/>.
#region Private data /// </summary>
/// <param name="collection">The <see cref="IEnumerable{T}"/> whose elements are copied
private readonly Dictionary<TKey, TValue> _dictionary; /// to the new <see cref="DataDictionary{TKey,TValue}"/>.</param>
/// <exception cref="ArgumentNullException"><paramref name="collection"/> is <see langword="null"/>.</exception>
#endregion /// <remarks>
/// <para>Since <see cref="DataDictionary{TKey,TValue}"/> does not store null values,
#region Instance management /// key/value pairs whose value is <see langword="null"/> will not be copied from <paramref name="collection"/>.</para>
/// </remarks>
/// <summary> /// <see cref="Dictionary{TKey,TValue}()"/>
/// Initializes a new instance of the <see cref="DataDictionary{TKey,TValue}"/> class public DataDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection) {
/// that is empty, has the default initial capacity, if(collection == null) {
/// and uses the default comparer for <typeparamref name="TKey"/>. throw new ArgumentNullException(nameof(collection));
/// </summary> }
/// <see cref="Dictionary{TKey,TValue}()"/>
public DataDictionary() this._dictionary = new Dictionary<TKey, TValue>();
{ foreach(KeyValuePair<TKey, TValue> pair in collection.Where(pair => pair.Value != null)) {
_dictionary = new Dictionary<TKey, TValue>(); this._dictionary.Add(pair.Key, pair.Value);
} }
}
/// <summary>
/// Initializes a new instance of the <see cref="DataDictionary{TKey,TValue}"/> class /// <summary>
/// that contains elements copied from the specified <see cref="IEnumerable{T}"/>, /// Initializes a new instance of the <see cref="DataDictionary{TKey,TValue}"/> class
/// has the default initial capacity, and uses the default comparer for <typeparamref name="TKey"/>. /// that is empty, has the default capacity, and uses the specified <see cref="IEqualityComparer{T}"/>.
/// </summary> /// </summary>
/// <param name="collection">The <see cref="IEnumerable{T}"/> whose elements are copied /// <param name="comparer">The equality comparison implementation to use when comparing keys.</param>
/// to the new <see cref="DataDictionary{TKey,TValue}"/>.</param> /// <exception cref="ArgumentNullException"><paramref name="comparer"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="collection"/> is <see langword="null"/>.</exception> /// <see cref="Dictionary{TKey,TValue}(IEqualityComparer{TKey})"/>
/// <remarks> public DataDictionary(IEqualityComparer<TKey> comparer) => this._dictionary = new Dictionary<TKey, TValue>(comparer);
/// <para>Since <see cref="DataDictionary{TKey,TValue}"/> does not store null values,
/// key/value pairs whose value is <see langword="null"/> will not be copied from <paramref name="collection"/>.</para> /// <summary>
/// </remarks> /// Initializes a new instance of the <see cref="DataDictionary{TKey, TValue}"/> class
/// <see cref="Dictionary{TKey,TValue}()"/> /// that contains elements copied from the specified <see cref="IEnumerable{T}"/>,
public DataDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection) /// has the default initial capacity, and uses the specified <see cref="IEqualityComparer{T}"/>.
{ /// </summary>
if (collection == null) /// <param name="collection">The <see cref="IEnumerable{T}"/> whose elements are copied
throw new ArgumentNullException(nameof(collection)); /// to the new <see cref="DataDictionary{TKey,TValue}"/>.</param>
/// <param name="comparer">The equality comparison implementation to use when comparing keys.</param>
_dictionary = new Dictionary<TKey, TValue>(); /// <remarks>
foreach (var pair in collection.Where(pair => pair.Value != null)) /// <para>Since <see cref="DataDictionary{TKey,TValue}"/> does not store null values,
{ /// key/value pairs whose value is <see langword="null"/> will not be copied from <paramref name="collection"/>.</para>
_dictionary.Add(pair.Key, pair.Value); /// </remarks>
} /// <exception cref="ArgumentNullException">
} /// <para><paramref name="collection"/> is <see langword="null"/>.</para>
/// <para>- or -.</para>
/// <summary> /// <para><paramref name="comparer"/> is <see langword="null"/>.</para>
/// Initializes a new instance of the <see cref="DataDictionary{TKey,TValue}"/> class /// </exception>
/// that is empty, has the default capacity, and uses the specified <see cref="IEqualityComparer{T}"/>. /// <see cref="Dictionary{TKey,TValue}(IEqualityComparer{TKey})"/>
/// </summary> public DataDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) {
/// <param name="comparer">The equality comparison implementation to use when comparing keys.</param> if(collection == null) {
/// <exception cref="ArgumentNullException"><paramref name="comparer"/> is <see langword="null"/>.</exception> throw new ArgumentNullException(nameof(collection));
/// <see cref="Dictionary{TKey,TValue}(IEqualityComparer{TKey})"/> }
public DataDictionary(IEqualityComparer<TKey> comparer)
{ this._dictionary = new Dictionary<TKey, TValue>(comparer);
_dictionary = new Dictionary<TKey, TValue>(comparer); foreach(KeyValuePair<TKey, TValue> pair in collection.Where(pair => pair.Value != null)) {
} this._dictionary.Add(pair.Key, pair.Value);
}
/// <summary> }
/// Initializes a new instance of the <see cref="DataDictionary{TKey, TValue}"/> class
/// that contains elements copied from the specified <see cref="IEnumerable{T}"/>, /// <summary>
/// has the default initial capacity, and uses the specified <see cref="IEqualityComparer{T}"/>. /// Initializes a new instance of the <see cref="DataDictionary{TKey, TValue}"/> class
/// </summary> /// that is empty, has the specified capacity, and uses the default comparer for the key type.
/// <param name="collection">The <see cref="IEnumerable{T}"/> whose elements are copied /// </summary>
/// to the new <see cref="DataDictionary{TKey,TValue}"/>.</param> /// <param name="capacity">The initial number of elements that the <see cref="DataDictionary{TKey, TValue}"/> can contain.</param>
/// <param name="comparer">The equality comparison implementation to use when comparing keys.</param> /// <exception cref="ArgumentOutOfRangeException"><paramref name="capacity"/> is less than 0.</exception>
/// <remarks> /// <see cref="Dictionary{TKey,TValue}(Int32)"/>
/// <para>Since <see cref="DataDictionary{TKey,TValue}"/> does not store null values, public DataDictionary(Int32 capacity) => this._dictionary = new Dictionary<TKey, TValue>(capacity);
/// key/value pairs whose value is <see langword="null"/> will not be copied from <paramref name="collection"/>.</para>
/// </remarks> /// <summary>
/// <exception cref="ArgumentNullException"> /// Initializes a new instance of the <see cref="DataDictionary{TKey, TValue}"/> class
/// <para><paramref name="collection"/> is <see langword="null"/>.</para> /// that contains elements copied from the specified <see cref="IEnumerable{T}"/>,
/// <para>- or -.</para> /// has the specified capacity, and uses the specified <see cref="IEqualityComparer{T}"/>.
/// <para><paramref name="comparer"/> is <see langword="null"/>.</para> /// </summary>
/// </exception> /// <param name="capacity">The initial number of elements that the <see cref="DataDictionary{TKey, TValue}"/> can contain.</param>
/// <see cref="Dictionary{TKey,TValue}(IEqualityComparer{TKey})"/> /// <param name="collection">The <see cref="IEnumerable{T}"/> whose elements are copied
public DataDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) /// to the new <see cref="ConcurrentDataDictionary{TKey,TValue}"/>.</param>
{ /// <param name="comparer">The equality comparison implementation to use when comparing keys.</param>
if (collection == null) /// <remarks>
throw new ArgumentNullException(nameof(collection)); /// <para>Since <see cref="ConcurrentDataDictionary{TKey,TValue}"/> does not store null values,
/// key/value pairs whose value is <see langword="null"/> will not be copied from <paramref name="collection"/>.</para>
_dictionary = new Dictionary<TKey, TValue>(comparer); /// </remarks>
foreach (var pair in collection.Where(pair => pair.Value != null)) /// <exception cref="ArgumentNullException">
{ /// <para><paramref name="collection"/> is <see langword="null"/>.</para>
_dictionary.Add(pair.Key, pair.Value); /// <para>- or -.</para>
} /// <para><paramref name="comparer"/> is <see langword="null"/>.</para>
} /// </exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="capacity"/> is less than 0.</exception>
/// <summary> /// <see cref="Dictionary{TKey,TValue}(Int32,IEqualityComparer{TKey})"/>
/// Initializes a new instance of the <see cref="DataDictionary{TKey, TValue}"/> class public DataDictionary(Int32 capacity, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) {
/// that is empty, has the specified capacity, and uses the default comparer for the key type. if(collection == null) {
/// </summary> throw new ArgumentNullException(nameof(collection));
/// <param name="capacity">The initial number of elements that the <see cref="DataDictionary{TKey, TValue}"/> can contain.</param> }
/// <exception cref="ArgumentOutOfRangeException"><paramref name="capacity"/> is less than 0.</exception>
/// <see cref="Dictionary{TKey,TValue}(int)"/> this._dictionary = new Dictionary<TKey, TValue>(capacity, comparer);
public DataDictionary(int capacity) foreach(KeyValuePair<TKey, TValue> pair in collection.Where(pair => pair.Value != null)) {
{ this._dictionary.Add(pair.Key, pair.Value);
_dictionary = new Dictionary<TKey, TValue>(capacity); }
} }
/// <summary> #endregion
/// Initializes a new instance of the <see cref="DataDictionary{TKey, TValue}"/> class
/// that contains elements copied from the specified <see cref="IEnumerable{T}"/>, #region Public APIs
/// has the specified capacity, and uses the specified <see cref="IEqualityComparer{T}"/>.
/// </summary> /// <inheritdoc cref="IDataDictionary{TKey,TValue}.Count"/>
/// <param name="capacity">The initial number of elements that the <see cref="DataDictionary{TKey, TValue}"/> can contain.</param> public Int32 Count => this._dictionary.Count;
/// <param name="collection">The <see cref="IEnumerable{T}"/> whose elements are copied
/// to the new <see cref="ConcurrentDataDictionary{TKey,TValue}"/>.</param> /// <inheritdoc cref="IDataDictionary{TKey,TValue}.IsEmpty"/>
/// <param name="comparer">The equality comparison implementation to use when comparing keys.</param> public Boolean IsEmpty => this._dictionary.Count == 0;
/// <remarks>
/// <para>Since <see cref="ConcurrentDataDictionary{TKey,TValue}"/> does not store null values, /// <inheritdoc cref="IDictionary{TKey,TValue}.Keys"/>
/// key/value pairs whose value is <see langword="null"/> will not be copied from <paramref name="collection"/>.</para> public ICollection<TKey> Keys => this._dictionary.Keys;
/// </remarks>
/// <exception cref="ArgumentNullException"> /// <inheritdoc cref="IDictionary{TKey,TValue}.Values"/>
/// <para><paramref name="collection"/> is <see langword="null"/>.</para> public ICollection<TValue> Values => this._dictionary.Values;
/// <para>- or -.</para>
/// <para><paramref name="comparer"/> is <see langword="null"/>.</para> /// <inheritdoc cref="IDataDictionary{TKey,TValue}.this"/>
/// </exception> public TValue this[TKey key] {
/// <exception cref="ArgumentOutOfRangeException"><paramref name="capacity"/> is less than 0.</exception> get => this._dictionary.TryGetValue(key ?? throw new ArgumentNullException(nameof(key)), out TValue value) ? value : null;
/// <see cref="Dictionary{TKey,TValue}(int,IEqualityComparer{TKey})"/> set {
public DataDictionary(int capacity, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) if(value != null) {
{ this._dictionary[key] = value;
if (collection == null) } else {
throw new ArgumentNullException(nameof(collection)); _ = this._dictionary.Remove(key);
}
_dictionary = new Dictionary<TKey, TValue>(capacity, comparer); }
foreach (var pair in collection.Where(pair => pair.Value != null)) }
{
_dictionary.Add(pair.Key, pair.Value); /// <inheritdoc cref="IDataDictionary{TKey,TValue}.Clear"/>
} public void Clear() => this._dictionary.Clear();
}
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.ContainsKey"/>
#endregion public Boolean ContainsKey(TKey key) =>
// _dictionary.ContainsKey will take care of throwing on a null key.
#region Public APIs this._dictionary.ContainsKey(key);
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.Count"/> /// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.GetOrAdd(TKey,TValue)"/>
public int Count => _dictionary.Count; public TValue GetOrAdd(TKey key, TValue value) {
// _dictionary.TryGetValue will take care of throwing on a null key.
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.IsEmpty"/> if(this._dictionary.TryGetValue(key, out TValue result)) {
public bool IsEmpty => _dictionary.Count == 0; return result;
}
/// <inheritdoc cref="IDictionary{TKey,TValue}.Keys"/>
public ICollection<TKey> Keys => _dictionary.Keys; if(value == null) {
return null;
/// <inheritdoc cref="IDictionary{TKey,TValue}.Values"/> }
public ICollection<TValue> Values => _dictionary.Values;
this._dictionary.Add(key, value);
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.this"/> return value;
public TValue? this[TKey key] }
{
get => _dictionary.TryGetValue(key ?? throw new ArgumentNullException(nameof(key)), out var value) ? value : null; /// <inheritdoc cref="IDictionary{TKey,TValue}.Remove(TKey)"/>
set public Boolean Remove(TKey key) =>
{ // _dictionary.Remove will take care of throwing on a null key.
if (value != null) this._dictionary.Remove(key);
{
_dictionary[key] = value; /// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.TryAdd"/>
} public Boolean TryAdd(TKey key, TValue value) {
else // _dictionary.ContainsKey will take care of throwing on a null key.
{ if(this._dictionary.ContainsKey(key)) {
_dictionary.Remove(key); return false;
} }
}
} if(value != null) {
this._dictionary.Add(key, value);
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.Clear"/> }
public void Clear() => _dictionary.Clear();
return true;
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.ContainsKey"/> }
public bool ContainsKey(TKey key)
{ /// <inheritdoc cref="IDataDictionary{TKey,TValue}.TryGetValue"/>
// _dictionary.ContainsKey will take care of throwing on a null key. public Boolean TryGetValue(TKey key, out TValue value) => this._dictionary.TryGetValue(key, out value);
return _dictionary.ContainsKey(key);
} /// <inheritdoc cref="IDataDictionary{TKey,TValue}.TryRemove"/>
public Boolean TryRemove(TKey key, out TValue value) {
/// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.GetOrAdd(TKey,TValue)"/> // TryGetValue will take care of throwing on a null key.
public TValue? GetOrAdd(TKey key, TValue value) if(!this._dictionary.TryGetValue(key, out value)) {
{ return false;
// _dictionary.TryGetValue will take care of throwing on a null key. }
if (_dictionary.TryGetValue(key, out var result))
return result; _ = this._dictionary.Remove(key);
return true;
if (value == null) }
return null;
/// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.TryUpdate"/>
_dictionary.Add(key, value); public Boolean TryUpdate(TKey key, TValue newValue, TValue comparisonValue) {
return value; // TryGetValue will take care of throwing on a null key.
} if(!this._dictionary.TryGetValue(key, out TValue value)) {
return false;
/// <inheritdoc cref="IDictionary{TKey,TValue}.Remove(TKey)"/> }
public bool Remove(TKey key)
{ if(value != comparisonValue) {
// _dictionary.Remove will take care of throwing on a null key. return false;
return _dictionary.Remove(key); }
}
this._dictionary[key] = newValue;
/// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.TryAdd"/> return true;
public bool TryAdd(TKey key, TValue value) }
{
// _dictionary.ContainsKey will take care of throwing on a null key. #endregion
if (_dictionary.ContainsKey(key))
return false; #region Implementation of IDictionary<TKey, TValue>
if (value != null) /// <inheritdoc cref="IDictionary{TKey,TValue}.Add(TKey,TValue)"/>
_dictionary.Add(key, value); void IDictionary<TKey, TValue>.Add(TKey key, TValue value) {
// Validating the key seems redundant, because both Add and Remove
return true; // will throw on a null key.
} // This way, though, the code path on null key does not depend on value.
// Without this validation, there should be two unit tests for null key,
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.TryGetValue"/> // one with a null value and one with a non-null value,
public bool TryGetValue(TKey key, out TValue value) => _dictionary.TryGetValue(key, out value); // which makes no sense.
if(key == null) {
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.TryRemove"/> throw new ArgumentNullException(nameof(key));
public bool TryRemove(TKey key, out TValue value) }
{
// TryGetValue will take care of throwing on a null key. if(value != null) {
if (!_dictionary.TryGetValue(key, out value)) this._dictionary.Add(key, value);
return false; } else {
_ = this._dictionary.Remove(key);
_dictionary.Remove(key); }
return true; }
}
#endregion
/// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.TryUpdate"/>
public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue) #region Implementation of IReadOnlyDictionary<TKey, TValue>
{
// TryGetValue will take care of throwing on a null key. /// <inheritdoc cref="IReadOnlyDictionary{TKey,TValue}.Keys"/>
if (!_dictionary.TryGetValue(key, out var value)) IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => this._dictionary.Keys;
return false;
/// <inheritdoc cref="IReadOnlyDictionary{TKey,TValue}.Values"/>
if (value != comparisonValue) IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => this._dictionary.Values;
return false;
#endregion
_dictionary[key] = newValue;
return true; #region Implementation of ICollection<KeyValuePair<TKey, TValue>>
}
/// <inheritdoc cref="ICollection{T}.IsReadOnly"/>
#endregion /// <remarks>
/// <para>This property is always <see langword="false"/> for a <see cref="DataDictionary{TKey,TValue}"/>.</para>
#region Implementation of IDictionary<TKey, TValue> /// </remarks>
Boolean ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false;
/// <inheritdoc cref="IDictionary{TKey,TValue}.Add(TKey,TValue)"/>
void IDictionary<TKey, TValue>.Add(TKey key, TValue value) /// <inheritdoc cref="ICollection{T}.Add"/>
{ void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) {
// Validating the key seems redundant, because both Add and Remove if(item.Value != null) {
// will throw on a null key. ((ICollection<KeyValuePair<TKey, TValue>>)this._dictionary).Add(item);
// This way, though, the code path on null key does not depend on value. } else {
// Without this validation, there should be two unit tests for null key, _ = this._dictionary.Remove(item.Key);
// one with a null value and one with a non-null value, }
// which makes no sense. }
if (key == null)
throw new ArgumentNullException(nameof(key)); /// <inheritdoc cref="ICollection{T}.Contains"/>
Boolean ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) => ((ICollection<KeyValuePair<TKey, TValue>>)this._dictionary).Contains(item);
if (value != null)
{ /// <inheritdoc cref="ICollection{T}.CopyTo"/>
_dictionary.Add(key, value); void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, Int32 arrayIndex) => ((ICollection<KeyValuePair<TKey, TValue>>)this._dictionary).CopyTo(array, arrayIndex);
}
else /// <inheritdoc cref="ICollection{T}.Remove"/>
{ Boolean ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) => ((ICollection<KeyValuePair<TKey, TValue>>)this._dictionary).Remove(item);
_dictionary.Remove(key);
} #endregion
}
#region Implementation of IEnumerable<KeyValuePair<TKey, TValue>>
#endregion
/// <inheritdoc cref="IEnumerable{T}.GetEnumerator"/>
#region Implementation of IReadOnlyDictionary<TKey, TValue> IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() => this._dictionary.GetEnumerator();
/// <inheritdoc cref="IReadOnlyDictionary{TKey,TValue}.Keys"/> #endregion
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => _dictionary.Keys;
#region Implementation of IEnumerable
/// <inheritdoc cref="IReadOnlyDictionary{TKey,TValue}.Values"/>
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => _dictionary.Values; /// <inheritdoc cref="IEnumerable.GetEnumerator"/>
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this._dictionary).GetEnumerator();
#endregion
#endregion
#region Implementation of ICollection<KeyValuePair<TKey, TValue>> }
/// <inheritdoc cref="ICollection{T}.IsReadOnly"/>
/// <remarks>
/// <para>This property is always <see langword="false"/> for a <see cref="DataDictionary{TKey,TValue}"/>.</para>
/// </remarks>
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false;
/// <inheritdoc cref="ICollection{T}.Add"/>
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
{
if (item.Value != null)
{
((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).Add(item);
}
else
{
_dictionary.Remove(item.Key);
}
}
/// <inheritdoc cref="ICollection{T}.Contains"/>
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
=> ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).Contains(item);
/// <inheritdoc cref="ICollection{T}.CopyTo"/>
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
=> ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).CopyTo(array, arrayIndex);
/// <inheritdoc cref="ICollection{T}.Remove"/>
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
=> ((ICollection<KeyValuePair<TKey, TValue>>) _dictionary).Remove(item);
#endregion
#region Implementation of IEnumerable<KeyValuePair<TKey, TValue>>
/// <inheritdoc cref="IEnumerable{T}.GetEnumerator"/>
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() => _dictionary.GetEnumerator();
#endregion
#region Implementation of IEnumerable
/// <inheritdoc cref="IEnumerable.GetEnumerator"/>
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_dictionary).GetEnumerator();
#endregion
}
} }

View File

@ -1,49 +1,46 @@
using System; using System;
namespace Swan.Collections namespace Swan.Collections {
{ /// <summary>
/// <para>Implements a collection of components that automatically disposes each component
/// implementing <see cref="IDisposable"/>.</para>
/// <para>Each component in the collection may be given a unique name for later retrieval.</para>
/// </summary>
/// <typeparam name="T">The type of components in the collection.</typeparam>
/// <seealso cref="ComponentCollection{T}" />
/// <seealso cref="IComponentCollection{T}" />
public class DisposableComponentCollection<T> : ComponentCollection<T>, IDisposable {
/// <summary> /// <summary>
/// <para>Implements a collection of components that automatically disposes each component /// Finalizes an instance of the <see cref="DisposableComponentCollection{T}"/> class.
/// implementing <see cref="IDisposable"/>.</para>
/// <para>Each component in the collection may be given a unique name for later retrieval.</para>
/// </summary> /// </summary>
/// <typeparam name="T">The type of components in the collection.</typeparam> ~DisposableComponentCollection() {
/// <seealso cref="ComponentCollection{T}" /> this.Dispose(false);
/// <seealso cref="IComponentCollection{T}" /> }
public class DisposableComponentCollection<T> : ComponentCollection<T>, IDisposable
{ /// <summary>
/// <summary> /// Releases unmanaged and - optionally - managed resources.
/// Finalizes an instance of the <see cref="DisposableComponentCollection{T}"/> class. /// </summary>
/// </summary> public void Dispose() {
~DisposableComponentCollection() this.Dispose(true);
{ GC.SuppressFinalize(this);
Dispose(false); }
}
/// <summary>
/// <summary> /// Releases unmanaged and - optionally - managed resources.
/// Releases unmanaged and - optionally - managed resources. /// </summary>
/// </summary> /// <param name="disposing">
public void Dispose() /// <see langword="true"/> to release both managed and unmanaged resources; <see langword="true"/> to release only unmanaged resources.
{ /// </param>
Dispose(true); protected virtual void Dispose(Boolean disposing) {
GC.SuppressFinalize(this); if(!disposing) {
} return;
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources. foreach(T component in this) {
/// </summary> if(component is IDisposable disposable) {
/// <param name="disposing"> disposable.Dispose();
/// <see langword="true"/> to release both managed and unmanaged resources; <see langword="true"/> to release only unmanaged resources. }
/// </param> }
protected virtual void Dispose(bool disposing) }
{ }
if (!disposing) return;
foreach (var component in this)
{
if (component is IDisposable disposable)
disposable.Dispose();
}
}
}
} }

View File

@ -1,54 +1,56 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Swan.Collections namespace Swan.Collections {
{ /// <summary>
/// <para>Represents a collection of components.</para>
/// <para>Each component in the collection may be given a unique name for later retrieval.</para>
/// </summary>
/// <typeparam name="T">The type of components in the collection.</typeparam>
public interface IComponentCollection<T> : IReadOnlyList<T> {
/// <summary> /// <summary>
/// <para>Represents a collection of components.</para> /// Gets an <see cref="IReadOnlyDictionary{TKey,TValue}"/> interface representing the named components.
/// <para>Each component in the collection may be given a unique name for later retrieval.</para>
/// </summary> /// </summary>
/// <typeparam name="T">The type of components in the collection.</typeparam> /// <value>
public interface IComponentCollection<T> : IReadOnlyList<T> /// The named components.
{ /// </value>
/// <summary> IReadOnlyDictionary<String, T> Named {
/// Gets an <see cref="IReadOnlyDictionary{TKey,TValue}"/> interface representing the named components. get;
/// </summary> }
/// <value>
/// The named components. /// <summary>
/// </value> /// <para>Gets an <see cref="IReadOnlyList{T}"/> interface representing all components
IReadOnlyDictionary<string, T> Named { get; } /// associated with safe names.</para>
/// <para>The safe name of a component is never <see langword="null"/>.
/// <summary> /// If a component's unique name if <see langword="null"/>, its safe name
/// <para>Gets an <see cref="IReadOnlyList{T}"/> interface representing all components /// will be some non-<see langword="null"/> string somehow identifying it.</para>
/// associated with safe names.</para> /// <para>Note that safe names are not necessarily unique.</para>
/// <para>The safe name of a component is never <see langword="null"/>. /// </summary>
/// If a component's unique name if <see langword="null"/>, its safe name /// <value>
/// will be some non-<see langword="null"/> string somehow identifying it.</para> /// A list of <see cref="ValueTuple{T1,T2}"/>s, each containing a safe name and a component.
/// <para>Note that safe names are not necessarily unique.</para> /// </value>
/// </summary> IReadOnlyList<(String SafeName, T Component)> WithSafeNames {
/// <value> get;
/// A list of <see cref="ValueTuple{T1,T2}"/>s, each containing a safe name and a component. }
/// </value>
IReadOnlyList<(string SafeName, T Component)> WithSafeNames { get; } /// <summary>
/// Gets the component with the specified name.
/// <summary> /// </summary>
/// Gets the component with the specified name. /// <value>
/// </summary> /// The component.
/// <value> /// </value>
/// The component. /// <param name="name">The name.</param>
/// </value> /// <returns>The component with the specified <paramref name="name"/>.</returns>
/// <param name="name">The name.</param> /// <exception cref="ArgumentNullException"><paramref name="name"/> is null.</exception>
/// <returns>The component with the specified <paramref name="name"/>.</returns> /// <exception cref="KeyNotFoundException">The property is retrieved and <paramref name="name"/> is not found.</exception>
/// <exception cref="ArgumentNullException"><paramref name="name"/> is null.</exception> T this[String name] { get; }
/// <exception cref="KeyNotFoundException">The property is retrieved and <paramref name="name"/> is not found.</exception>
T this[string name] { get; } /// <summary>
/// Adds a component to the collection,
/// <summary> /// giving it the specified <paramref name="name"/> if it is not <see langword="null"/>.
/// Adds a component to the collection, /// </summary>
/// giving it the specified <paramref name="name"/> if it is not <see langword="null"/>. /// <param name="name">The name given to the module, or <see langword="null"/>.</param>
/// </summary> /// <param name="component">The component.</param>
/// <param name="name">The name given to the module, or <see langword="null"/>.</param> void Add(String name, T component);
/// <param name="component">The component.</param> }
void Add(string name, T component);
}
} }

View File

@ -1,34 +1,32 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Swan.Collections namespace Swan.Collections {
{ /// <summary>
/// Represents a generic collection of key/value pairs that does not store
/// null values.
/// </summary>
/// <typeparam name="TKey">The type of keys in the dictionary. This must be a reference type.</typeparam>
/// <typeparam name="TValue">The type of values in the dictionary. This must be a reference type.</typeparam>
public interface IDataDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue> where TKey : class where TValue : class {
/// <summary> /// <summary>
/// Represents a generic collection of key/value pairs that does not store /// Gets a value that indicates whether the <see cref="IDataDictionary{TKey,TValue}"/> is empty.
/// null values.
/// </summary> /// </summary>
/// <typeparam name="TKey">The type of keys in the dictionary. This must be a reference type.</typeparam> /// <value>
/// <typeparam name="TValue">The type of values in the dictionary. This must be a reference type.</typeparam> /// <see langword="true"/> if the <see cref="IDataDictionary{TKey,TValue}"/> is empty; otherwise, <see langword="false"/>.
public interface IDataDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue> /// </value>
where TKey : class Boolean IsEmpty {
where TValue : class get;
{ }
/// <summary>
/// Gets a value that indicates whether the <see cref="IDataDictionary{TKey,TValue}"/> is empty. /// <summary>
/// </summary> /// Attempts to remove and return the value that has the specified key from the <see cref="IDataDictionary{TKey,TValue}"/>.
/// <value> /// </summary>
/// <see langword="true"/> if the <see cref="IDataDictionary{TKey,TValue}"/> is empty; otherwise, <see langword="false"/>. /// <param name="key">The key of the element to remove and return.</param>
/// </value> /// <param name="value">When this method returns, the value removed from the <see cref="IDataDictionary{TKey,TValue}"/>,
bool IsEmpty { get; } /// if the key is found; otherwise, <see langword="null"/>. This parameter is passed uninitialized.</param>
/// <returns><see langword="true"/> if the value was removed successfully; otherwise, <see langword="false"/>.</returns>
/// <summary> /// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
/// Attempts to remove and return the value that has the specified key from the <see cref="IDataDictionary{TKey,TValue}"/>. Boolean TryRemove(TKey key, out TValue value);
/// </summary> }
/// <param name="key">The key of the element to remove and return.</param>
/// <param name="value">When this method returns, the value removed from the <see cref="IDataDictionary{TKey,TValue}"/>,
/// if the key is found; otherwise, <see langword="null"/>. This parameter is passed uninitialized.</param>
/// <returns><see langword="true"/> if the value was removed successfully; otherwise, <see langword="false"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
bool TryRemove(TKey key, out TValue value);
}
} }

View File

@ -1,77 +1,70 @@
using System; using System;
namespace Swan.Configuration namespace Swan.Configuration {
{ /// <summary>
/// Base class for objects whose configuration may be locked,
/// thus becoming read-only, at a certain moment in their lifetime.
/// </summary>
public abstract class ConfiguredObject {
private readonly Object _syncRoot = new Object();
private Boolean _configurationLocked;
/// <summary> /// <summary>
/// Base class for objects whose configuration may be locked, /// Gets a value indicating whether s configuration has already been locked
/// thus becoming read-only, at a certain moment in their lifetime. /// and has therefore become read-only.
/// </summary> /// </summary>
public abstract class ConfiguredObject /// <value>
{ /// <see langword="true"/> if the configuration is locked; otherwise, <see langword="false"/>.
private readonly object _syncRoot = new object(); /// </value>
private bool _configurationLocked; /// <seealso cref="EnsureConfigurationNotLocked"/>
protected Boolean ConfigurationLocked {
/// <summary> get {
/// Gets a value indicating whether s configuration has already been locked lock(this._syncRoot) {
/// and has therefore become read-only. return this._configurationLocked;
/// </summary> }
/// <value> }
/// <see langword="true"/> if the configuration is locked; otherwise, <see langword="false"/>. }
/// </value>
/// <seealso cref="EnsureConfigurationNotLocked"/> /// <summary>
protected bool ConfigurationLocked /// <para>Locks this instance's configuration, preventing further modifications.</para>
{ /// </summary>
get /// <remarks>
{ /// <para>Configuration locking must be enforced by derived classes
lock (_syncRoot) /// by calling <see cref="EnsureConfigurationNotLocked"/> at the start
{ /// of methods and property setters that could change the object's
return _configurationLocked; /// configuration.</para>
} /// <para>Immediately before locking the configuration, this method calls <see cref="OnBeforeLockConfiguration"/>
} /// as a last chance to validate configuration data, and to lock the configuration of contained objects.</para>
} /// </remarks>
/// <seealso cref="OnBeforeLockConfiguration"/>
/// <summary> protected void LockConfiguration() {
/// <para>Locks this instance's configuration, preventing further modifications.</para> lock(this._syncRoot) {
/// </summary> if(this._configurationLocked) {
/// <remarks> return;
/// <para>Configuration locking must be enforced by derived classes }
/// by calling <see cref="EnsureConfigurationNotLocked"/> at the start
/// of methods and property setters that could change the object's this.OnBeforeLockConfiguration();
/// configuration.</para> this._configurationLocked = true;
/// <para>Immediately before locking the configuration, this method calls <see cref="OnBeforeLockConfiguration"/> }
/// as a last chance to validate configuration data, and to lock the configuration of contained objects.</para> }
/// </remarks>
/// <seealso cref="OnBeforeLockConfiguration"/> /// <summary>
protected void LockConfiguration() /// Called immediately before locking the configuration.
{ /// </summary>
lock (_syncRoot) /// <seealso cref="LockConfiguration"/>
{ protected virtual void OnBeforeLockConfiguration() {
if (_configurationLocked) }
return;
/// <summary>
OnBeforeLockConfiguration(); /// Checks whether a module's configuration has become read-only
_configurationLocked = true; /// and, if so, throws an <see cref="InvalidOperationException"/>.
} /// </summary>
} /// <exception cref="InvalidOperationException">The configuration is locked.</exception>
/// <seealso cref="ConfigurationLocked"/>
/// <summary> protected void EnsureConfigurationNotLocked() {
/// Called immediately before locking the configuration. if(this.ConfigurationLocked) {
/// </summary> throw new InvalidOperationException($"Configuration of this {this.GetType().Name} instance is locked.");
/// <seealso cref="LockConfiguration"/> }
protected virtual void OnBeforeLockConfiguration() }
{ }
}
/// <summary>
/// Checks whether a module's configuration has become read-only
/// and, if so, throws an <see cref="InvalidOperationException"/>.
/// </summary>
/// <exception cref="InvalidOperationException">The configuration is locked.</exception>
/// <seealso cref="ConfigurationLocked"/>
protected void EnsureConfigurationNotLocked()
{
if (ConfigurationLocked)
throw new InvalidOperationException($"Configuration of this {GetType().Name} instance is locked.");
}
}
} }

View File

@ -1,54 +1,62 @@
using System; using System;
namespace Swan.Configuration namespace Swan.Configuration {
{ /// <summary>
/// An attribute used to include additional information to a Property for serialization.
///
/// Previously we used DisplayAttribute from DataAnnotation.
/// </summary>
/// <seealso cref="System.Attribute" />
[AttributeUsage(AttributeTargets.Property)]
public sealed class PropertyDisplayAttribute : Attribute {
/// <summary> /// <summary>
/// An attribute used to include additional information to a Property for serialization. /// Gets or sets the name.
///
/// Previously we used DisplayAttribute from DataAnnotation.
/// </summary> /// </summary>
/// <seealso cref="System.Attribute" /> /// <value>
[AttributeUsage(AttributeTargets.Property)] /// The name.
public sealed class PropertyDisplayAttribute : Attribute /// </value>
{ public String Name {
/// <summary> get; set;
/// Gets or sets the name. }
/// </summary>
/// <value> /// <summary>
/// The name. /// Gets or sets the description.
/// </value> /// </summary>
public string Name { get; set; } /// <value>
/// The description.
/// <summary> /// </value>
/// Gets or sets the description. public String Description {
/// </summary> get; set;
/// <value> }
/// The description.
/// </value> /// <summary>
public string Description { get; set; } /// Gets or sets the name of the group.
/// </summary>
/// <summary> /// <value>
/// Gets or sets the name of the group. /// The name of the group.
/// </summary> /// </value>
/// <value> public String GroupName {
/// The name of the group. get; set;
/// </value> }
public string GroupName { get; set; }
/// <summary>
/// <summary> /// Gets or sets the default value.
/// Gets or sets the default value. /// </summary>
/// </summary> /// <value>
/// <value> /// The default value.
/// The default value. /// </value>
/// </value> public Object DefaultValue {
public object DefaultValue { get; set; } get; set;
}
/// <summary>
/// Gets or sets the format string to call with method <c>ToString</c>. /// <summary>
/// </summary> /// Gets or sets the format string to call with method <c>ToString</c>.
/// <value> /// </summary>
/// The format. /// <value>
/// </value> /// The format.
public string Format { get; set; } /// </value>
} public String Format {
get; set;
}
}
} }

View File

@ -41,10 +41,9 @@ namespace Swan.Configuration
/// </code> /// </code>
/// </example> /// </example>
/// <typeparam name="T">The type of settings model.</typeparam> /// <typeparam name="T">The type of settings model.</typeparam>
public sealed class SettingsProvider<T> public sealed class SettingsProvider<T> : SingletonBase<SettingsProvider<T>>
: SingletonBase<SettingsProvider<T>>
{ {
private readonly object _syncRoot = new object(); private readonly Object _syncRoot = new Object();
private T _global; private T _global;
@ -55,8 +54,7 @@ namespace Swan.Configuration
/// <value> /// <value>
/// The configuration file path. /// The configuration file path.
/// </value> /// </value>
public string ConfigurationFilePath { get; set; } = public String ConfigurationFilePath { get; set; } = Path.Combine(SwanRuntime.EntryAssemblyDirectory, "appsettings.json");
Path.Combine(SwanRuntime.EntryAssemblyDirectory, "appsettings.json");
/// <summary> /// <summary>
/// Gets the global settings object. /// Gets the global settings object.
@ -68,12 +66,13 @@ namespace Swan.Configuration
{ {
get get
{ {
lock (_syncRoot) lock (this._syncRoot)
{ {
if (Equals(_global, default(T))) if (Equals(this._global, default(T)!)) {
ReloadGlobalSettings(); this.ReloadGlobalSettings();
}
return _global;
return this._global;
} }
} }
} }
@ -83,20 +82,21 @@ namespace Swan.Configuration
/// </summary> /// </summary>
public void ReloadGlobalSettings() public void ReloadGlobalSettings()
{ {
if (File.Exists(ConfigurationFilePath) == false || File.ReadAllText(ConfigurationFilePath).Length == 0) if (File.Exists(this.ConfigurationFilePath) == false || File.ReadAllText(this.ConfigurationFilePath).Length == 0)
{ {
ResetGlobalSettings(); this.ResetGlobalSettings();
return; return;
} }
lock (_syncRoot) lock (this._syncRoot) {
_global = Json.Deserialize<T>(File.ReadAllText(ConfigurationFilePath)); this._global = Json.Deserialize<T>(File.ReadAllText(this.ConfigurationFilePath));
} }
}
/// <summary> /// <summary>
/// Persists the global settings. /// Persists the global settings.
/// </summary> /// </summary>
public void PersistGlobalSettings() => File.WriteAllText(ConfigurationFilePath, Json.Serialize(Global, true)); public void PersistGlobalSettings() => File.WriteAllText(this.ConfigurationFilePath, Json.Serialize(this.Global, true));
/// <summary> /// <summary>
/// Updates settings from list. /// Updates settings from list.
@ -106,29 +106,34 @@ namespace Swan.Configuration
/// A list of settings of type ref="ExtendedPropertyInfo". /// A list of settings of type ref="ExtendedPropertyInfo".
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">propertyList.</exception> /// <exception cref="ArgumentNullException">propertyList.</exception>
public List<string> RefreshFromList(List<ExtendedPropertyInfo<T>> propertyList) public List<String> RefreshFromList(List<ExtendedPropertyInfo<T>> propertyList)
{ {
if (propertyList == null) if (propertyList == null) {
throw new ArgumentNullException(nameof(propertyList)); throw new ArgumentNullException(nameof(propertyList));
}
List<String> changedSettings = new List<global::System.String>();
IEnumerable<PropertyInfo> globalProps = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties<T>();
var changedSettings = new List<string>(); foreach (ExtendedPropertyInfo<T> property in propertyList)
var globalProps = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties<T>(); {
PropertyInfo propertyInfo = globalProps.FirstOrDefault(x => x.Name == property.Property);
foreach (var property in propertyList) if (propertyInfo == null) {
{ continue;
var propertyInfo = globalProps.FirstOrDefault(x => x.Name == property.Property); }
Object originalValue = propertyInfo.GetValue(this.Global);
Boolean isChanged = propertyInfo.PropertyType.IsArray
? property.Value is IEnumerable enumerable && propertyInfo.TrySetArray(enumerable.Cast<Object>(), this.Global)
: this.SetValue(property.Value, originalValue, propertyInfo);
if (propertyInfo == null) continue; if (!isChanged) {
continue;
var originalValue = propertyInfo.GetValue(Global); }
var isChanged = propertyInfo.PropertyType.IsArray
? property.Value is IEnumerable enumerable && propertyInfo.TrySetArray(enumerable.Cast<object>(), Global) changedSettings.Add(property.Property);
: SetValue(property.Value, originalValue, propertyInfo); this.PersistGlobalSettings();
if (!isChanged) continue;
changedSettings.Add(property.Property);
PersistGlobalSettings();
} }
return changedSettings; return changedSettings;
@ -138,9 +143,9 @@ namespace Swan.Configuration
/// Gets the list. /// Gets the list.
/// </summary> /// </summary>
/// <returns>A List of ExtendedPropertyInfo of the type T.</returns> /// <returns>A List of ExtendedPropertyInfo of the type T.</returns>
public List<ExtendedPropertyInfo<T>>? GetList() public List<ExtendedPropertyInfo<T>> GetList()
{ {
var jsonData = Json.Deserialize(Json.Serialize(Global)) as Dictionary<string, object>; Dictionary<String, Object> jsonData = Json.Deserialize(Json.Serialize(this.Global)) as Dictionary<global::System.String, global::System.Object>;
return jsonData?.Keys return jsonData?.Keys
.Select(p => new ExtendedPropertyInfo<T>(p) { Value = jsonData[p] }) .Select(p => new ExtendedPropertyInfo<T>(p) { Value = jsonData[p] })
@ -152,26 +157,27 @@ namespace Swan.Configuration
/// </summary> /// </summary>
public void ResetGlobalSettings() public void ResetGlobalSettings()
{ {
lock (_syncRoot) lock (this._syncRoot) {
_global = Activator.CreateInstance<T>(); this._global = Activator.CreateInstance<T>();
}
PersistGlobalSettings();
this.PersistGlobalSettings();
} }
private bool SetValue(object property, object originalValue, PropertyInfo propertyInfo) private Boolean SetValue(Object property, Object originalValue, PropertyInfo propertyInfo)
{ {
switch (property) switch (property)
{ {
case null when originalValue == null: case null when originalValue == null:
break; break;
case null: case null:
propertyInfo.SetValue(Global, null); propertyInfo.SetValue(this.Global, null);
return true; return true;
default: default:
if (propertyInfo.PropertyType.TryParseBasicType(property, out var propertyValue) && if (propertyInfo.PropertyType.TryParseBasicType(property, out Object propertyValue) &&
!propertyValue.Equals(originalValue)) !propertyValue.Equals(originalValue))
{ {
propertyInfo.SetValue(Global, propertyValue); propertyInfo.SetValue(this.Global, propertyValue);
return true; return true;
} }

View File

@ -3,122 +3,123 @@ using System.IO;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
namespace Swan.Cryptography namespace Swan.Cryptography {
{ /// <summary>
/// Use this class to compute a hash in MD4, SHA1, SHA256 or SHA512.
/// </summary>
public static class Hasher {
private static readonly Lazy<MD5> Md5Hasher = new Lazy<MD5>(MD5.Create, true);
private static readonly Lazy<SHA1> SHA1Hasher = new Lazy<SHA1>(SHA1.Create, true);
private static readonly Lazy<SHA256> SHA256Hasher = new Lazy<SHA256>(SHA256.Create, true);
private static readonly Lazy<SHA512> SHA512Hasher = new Lazy<SHA512>(SHA512.Create, true);
/// <summary> /// <summary>
/// Use this class to compute a hash in MD4, SHA1, SHA256 or SHA512. /// Computes the MD5 hash of the given stream.
/// Do not use for large streams as this reads ALL bytes at once.
/// </summary> /// </summary>
public static class Hasher /// <param name="this">The stream.</param>
{ /// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
private static readonly Lazy<MD5> Md5Hasher = new Lazy<MD5>(MD5.Create, true); /// <returns>
private static readonly Lazy<SHA1> SHA1Hasher = new Lazy<SHA1>(SHA1.Create, true); /// The computed hash code.
private static readonly Lazy<SHA256> SHA256Hasher = new Lazy<SHA256>(SHA256.Create, true); /// </returns>
private static readonly Lazy<SHA512> SHA512Hasher = new Lazy<SHA512>(SHA512.Create, true); /// <exception cref="ArgumentNullException">stream.</exception>
[Obsolete("Use a better hasher.")]
/// <summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
/// Computes the MD5 hash of the given stream. [System.Diagnostics.CodeAnalysis.SuppressMessage("Stil", "IDE0045:In bedingten Ausdruck konvertieren", Justification = "<Ausstehend>")]
/// Do not use for large streams as this reads ALL bytes at once. public static Byte[] ComputeMD5(Stream @this, Boolean createHasher = false) {
/// </summary> if(@this == null) {
/// <param name="this">The stream.</param> throw new ArgumentNullException(nameof(@this));
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param> }
/// <returns>
/// The computed hash code. MD5 md5 = MD5.Create();
/// </returns> const Int32 bufferSize = 4096;
/// <exception cref="ArgumentNullException">stream.</exception>
[Obsolete("Use a better hasher.")] Byte[] readAheadBuffer = new Byte[bufferSize];
public static byte[] ComputeMD5(Stream @this, bool createHasher = false) Int32 readAheadBytesRead = @this.Read(readAheadBuffer, 0, readAheadBuffer.Length);
{
if (@this == null) do {
throw new ArgumentNullException(nameof(@this)); Int32 bytesRead = readAheadBytesRead;
Byte[] buffer = readAheadBuffer;
var md5 = MD5.Create();
const int bufferSize = 4096; readAheadBuffer = new Byte[bufferSize];
readAheadBytesRead = @this.Read(readAheadBuffer, 0, readAheadBuffer.Length);
var readAheadBuffer = new byte[bufferSize];
var readAheadBytesRead = @this.Read(readAheadBuffer, 0, readAheadBuffer.Length); if(readAheadBytesRead == 0) {
_ = md5.TransformFinalBlock(buffer, 0, bytesRead);
do } else {
{ _ = md5.TransformBlock(buffer, 0, bytesRead, buffer, 0);
var bytesRead = readAheadBytesRead; }
var buffer = readAheadBuffer; }
while(readAheadBytesRead != 0);
readAheadBuffer = new byte[bufferSize];
readAheadBytesRead = @this.Read(readAheadBuffer, 0, readAheadBuffer.Length); return md5.Hash;
}
if (readAheadBytesRead == 0)
md5.TransformFinalBlock(buffer, 0, bytesRead); /// <summary>
else /// Computes the MD5 hash of the given string using UTF8 byte encoding.
md5.TransformBlock(buffer, 0, bytesRead, buffer, 0); /// </summary>
} /// <param name="value">The input string.</param>
while (readAheadBytesRead != 0); /// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
/// <returns>The computed hash code.</returns>
return md5.Hash; [Obsolete("Use a better hasher.")]
} public static Byte[] ComputeMD5(String value, Boolean createHasher = false) => ComputeMD5(Encoding.UTF8.GetBytes(value), createHasher);
/// <summary> /// <summary>
/// Computes the MD5 hash of the given string using UTF8 byte encoding. /// Computes the MD5 hash of the given byte array.
/// </summary> /// </summary>
/// <param name="value">The input string.</param> /// <param name="data">The data.</param>
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param> /// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
/// <returns>The computed hash code.</returns> /// <returns>The computed hash code.</returns>
[Obsolete("Use a better hasher.")] [Obsolete("Use a better hasher.")]
public static byte[] ComputeMD5(string value, bool createHasher = false) => [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
ComputeMD5(Encoding.UTF8.GetBytes(value), createHasher); public static Byte[] ComputeMD5(Byte[] data, Boolean createHasher = false) => (createHasher ? MD5.Create() : Md5Hasher.Value).ComputeHash(data);
/// <summary> /// <summary>
/// Computes the MD5 hash of the given byte array. /// Computes the SHA-1 hash of the given string using UTF8 byte encoding.
/// </summary> /// </summary>
/// <param name="data">The data.</param> /// <param name="this">The input string.</param>
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param> /// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
/// <returns>The computed hash code.</returns> /// <returns>
[Obsolete("Use a better hasher.")] /// The computes a Hash-based Message Authentication Code (HMAC)
public static byte[] ComputeMD5(byte[] data, bool createHasher = false) => /// using the SHA1 hash function.
(createHasher ? MD5.Create() : Md5Hasher.Value).ComputeHash(data); /// </returns>
[Obsolete("Use a better hasher.")]
/// <summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
/// Computes the SHA-1 hash of the given string using UTF8 byte encoding. public static Byte[] ComputeSha1(String @this, Boolean createHasher = false) {
/// </summary> Byte[] inputBytes = Encoding.UTF8.GetBytes(@this);
/// <param name="this">The input string.</param> return (createHasher ? SHA1.Create() : SHA1Hasher.Value).ComputeHash(inputBytes);
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param> }
/// <returns>
/// The computes a Hash-based Message Authentication Code (HMAC)
/// using the SHA1 hash function. /// <summary>
/// </returns> /// Computes the SHA-256 hash of the given string using UTF8 byte encoding.
[Obsolete("Use a better hasher.")] /// </summary>
public static byte[] ComputeSha1(string @this, bool createHasher = false) /// <param name="value">The input string.</param>
{ /// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
var inputBytes = Encoding.UTF8.GetBytes(@this); /// <returns>
return (createHasher ? SHA1.Create() : SHA1Hasher.Value).ComputeHash(inputBytes); /// The computes a Hash-based Message Authentication Code (HMAC)
} /// by using the SHA256 hash function.
/// </returns>
/// <summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
/// Computes the SHA-256 hash of the given string using UTF8 byte encoding. public static Byte[] ComputeSha256(String value, Boolean createHasher = false) {
/// </summary> Byte[] inputBytes = Encoding.UTF8.GetBytes(value);
/// <param name="value">The input string.</param> return (createHasher ? SHA256.Create() : SHA256Hasher.Value).ComputeHash(inputBytes);
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param> }
/// <returns>
/// The computes a Hash-based Message Authentication Code (HMAC)
/// by using the SHA256 hash function. /// <summary>
/// </returns> /// Computes the SHA-512 hash of the given string using UTF8 byte encoding.
public static byte[] ComputeSha256(string value, bool createHasher = false) /// </summary>
{ /// <param name="value">The input string.</param>
var inputBytes = Encoding.UTF8.GetBytes(value); /// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
return (createHasher ? SHA256.Create() : SHA256Hasher.Value).ComputeHash(inputBytes); /// <returns>
} /// The computes a Hash-based Message Authentication Code (HMAC)
/// using the SHA512 hash function.
/// <summary> /// </returns>
/// Computes the SHA-512 hash of the given string using UTF8 byte encoding. [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
/// </summary> public static Byte[] ComputeSha512(String value, Boolean createHasher = false) {
/// <param name="value">The input string.</param> Byte[] inputBytes = Encoding.UTF8.GetBytes(value);
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param> return (createHasher ? SHA512.Create() : SHA512Hasher.Value).ComputeHash(inputBytes);
/// <returns> }
/// The computes a Hash-based Message Authentication Code (HMAC) }
/// using the SHA512 hash function.
/// </returns>
public static byte[] ComputeSha512(string value, bool createHasher = false)
{
var inputBytes = Encoding.UTF8.GetBytes(value);
return (createHasher ? SHA512.Create() : SHA512Hasher.Value).ComputeHash(inputBytes);
}
}
} }

View File

@ -1,174 +1,165 @@
using System; using System;
namespace Swan namespace Swan {
{ /// <summary>
/// Represents a struct of DateTimeSpan to compare dates and get in
/// separate fields the amount of time between those dates.
///
/// Based on https://stackoverflow.com/a/9216404/1096693.
/// </summary>
public struct DateTimeSpan {
/// <summary> /// <summary>
/// Represents a struct of DateTimeSpan to compare dates and get in /// Initializes a new instance of the <see cref="DateTimeSpan"/> struct.
/// separate fields the amount of time between those dates.
///
/// Based on https://stackoverflow.com/a/9216404/1096693.
/// </summary> /// </summary>
public struct DateTimeSpan /// <param name="years">The years.</param>
{ /// <param name="months">The months.</param>
/// <summary> /// <param name="days">The days.</param>
/// Initializes a new instance of the <see cref="DateTimeSpan"/> struct. /// <param name="hours">The hours.</param>
/// </summary> /// <param name="minutes">The minutes.</param>
/// <param name="years">The years.</param> /// <param name="seconds">The seconds.</param>
/// <param name="months">The months.</param> /// <param name="milliseconds">The milliseconds.</param>
/// <param name="days">The days.</param> public DateTimeSpan(Int32 years, Int32 months, Int32 days, Int32 hours, Int32 minutes, Int32 seconds, Int32 milliseconds) {
/// <param name="hours">The hours.</param> this.Years = years;
/// <param name="minutes">The minutes.</param> this.Months = months;
/// <param name="seconds">The seconds.</param> this.Days = days;
/// <param name="milliseconds">The milliseconds.</param> this.Hours = hours;
public DateTimeSpan(int years, int months, int days, int hours, int minutes, int seconds, int milliseconds) this.Minutes = minutes;
{ this.Seconds = seconds;
Years = years; this.Milliseconds = milliseconds;
Months = months; }
Days = days;
Hours = hours; /// <summary>
Minutes = minutes; /// Gets the years.
Seconds = seconds; /// </summary>
Milliseconds = milliseconds; /// <value>
} /// The years.
/// </value>
/// <summary> public Int32 Years {
/// Gets the years. get;
/// </summary> }
/// <value>
/// The years. /// <summary>
/// </value> /// Gets the months.
public int Years { get; } /// </summary>
/// <value>
/// <summary> /// The months.
/// Gets the months. /// </value>
/// </summary> public Int32 Months {
/// <value> get;
/// The months. }
/// </value>
public int Months { get; } /// <summary>
/// Gets the days.
/// <summary> /// </summary>
/// Gets the days. /// <value>
/// </summary> /// The days.
/// <value> /// </value>
/// The days. public Int32 Days {
/// </value> get;
public int Days { get; } }
/// <summary> /// <summary>
/// Gets the hours. /// Gets the hours.
/// </summary> /// </summary>
/// <value> /// <value>
/// The hours. /// The hours.
/// </value> /// </value>
public int Hours { get; } public Int32 Hours {
get;
/// <summary> }
/// Gets the minutes.
/// </summary> /// <summary>
/// <value> /// Gets the minutes.
/// The minutes. /// </summary>
/// </value> /// <value>
public int Minutes { get; } /// The minutes.
/// </value>
/// <summary> public Int32 Minutes {
/// Gets the seconds. get;
/// </summary> }
/// <value>
/// The seconds. /// <summary>
/// </value> /// Gets the seconds.
public int Seconds { get; } /// </summary>
/// <value>
/// <summary> /// The seconds.
/// Gets the milliseconds. /// </value>
/// </summary> public Int32 Seconds {
/// <value> get;
/// The milliseconds. }
/// </value>
public int Milliseconds { get; } /// <summary>
/// Gets the milliseconds.
internal static DateTimeSpan CompareDates(DateTime date1, DateTime date2) /// </summary>
{ /// <value>
if (date2 < date1) /// The milliseconds.
{ /// </value>
var sub = date1; public Int32 Milliseconds {
date1 = date2; get;
date2 = sub; }
}
internal static DateTimeSpan CompareDates(DateTime date1, DateTime date2) {
var current = date1; if(date2 < date1) {
var years = 0; DateTime sub = date1;
var months = 0; date1 = date2;
var days = 0; date2 = sub;
}
var phase = Phase.Years;
var span = new DateTimeSpan(); DateTime current = date1;
var officialDay = current.Day; Int32 years = 0;
Int32 months = 0;
while (phase != Phase.Done) Int32 days = 0;
{
switch (phase) Phase phase = Phase.Years;
{ DateTimeSpan span = new DateTimeSpan();
case Phase.Years: Int32 officialDay = current.Day;
if (current.AddYears(years + 1) > date2)
{ while(phase != Phase.Done) {
phase = Phase.Months; switch(phase) {
current = current.AddYears(years); case Phase.Years:
} if(current.AddYears(years + 1) > date2) {
else phase = Phase.Months;
{ current = current.AddYears(years);
years++; } else {
} years++;
}
break;
case Phase.Months: break;
if (current.AddMonths(months + 1) > date2) case Phase.Months:
{ if(current.AddMonths(months + 1) > date2) {
phase = Phase.Days; phase = Phase.Days;
current = current.AddMonths(months); current = current.AddMonths(months);
if (current.Day < officialDay && if(current.Day < officialDay &&
officialDay <= DateTime.DaysInMonth(current.Year, current.Month)) officialDay <= DateTime.DaysInMonth(current.Year, current.Month)) {
current = current.AddDays(officialDay - current.Day); current = current.AddDays(officialDay - current.Day);
} }
else } else {
{ months++;
months++; }
}
break;
break; case Phase.Days:
case Phase.Days: if(current.AddDays(days + 1) > date2) {
if (current.AddDays(days + 1) > date2) current = current.AddDays(days);
{ TimeSpan timespan = date2 - current;
current = current.AddDays(days); span = new DateTimeSpan(years, months, days, timespan.Hours, timespan.Minutes, timespan.Seconds, timespan.Milliseconds);
var timespan = date2 - current; phase = Phase.Done;
span = new DateTimeSpan( } else {
years, days++;
months, }
days,
timespan.Hours, break;
timespan.Minutes, }
timespan.Seconds, }
timespan.Milliseconds);
phase = Phase.Done; return span;
} }
else
{ private enum Phase {
days++; Years,
} Months,
Days,
break; Done,
} }
} }
return span;
}
private enum Phase
{
Years,
Months,
Days,
Done,
}
}
} }

View File

@ -3,136 +3,123 @@ using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using Swan.Reflection; using Swan.Reflection;
namespace Swan namespace Swan {
{ /// <summary>
/// Contains useful constants and definitions.
/// </summary>
public static partial class Definitions {
#region Main Dictionary Definition
/// <summary> /// <summary>
/// Contains useful constants and definitions. /// The basic types information.
/// </summary> /// </summary>
public static partial class Definitions public static readonly Lazy<Dictionary<Type, ExtendedTypeInfo>> BasicTypesInfo = new Lazy<Dictionary<Type, ExtendedTypeInfo>>(() => new Dictionary<Type, ExtendedTypeInfo> {
{ // Non-Nullables
#region Main Dictionary Definition {typeof(DateTime), new ExtendedTypeInfo<DateTime>()},
{typeof(Byte), new ExtendedTypeInfo<Byte>()},
/// <summary> {typeof(SByte), new ExtendedTypeInfo<SByte>()},
/// The basic types information. {typeof(Int32), new ExtendedTypeInfo<Int32>()},
/// </summary> {typeof(UInt32), new ExtendedTypeInfo<UInt32>()},
public static readonly Lazy<Dictionary<Type, ExtendedTypeInfo>> BasicTypesInfo = new Lazy<Dictionary<Type, ExtendedTypeInfo>>(() => {typeof(Int16), new ExtendedTypeInfo<Int16>()},
new Dictionary<Type, ExtendedTypeInfo> {typeof(UInt16), new ExtendedTypeInfo<UInt16>()},
{ {typeof(Int64), new ExtendedTypeInfo<Int64>()},
// Non-Nullables {typeof(UInt64), new ExtendedTypeInfo<UInt64>()},
{typeof(DateTime), new ExtendedTypeInfo<DateTime>()}, {typeof(Single), new ExtendedTypeInfo<Single>()},
{typeof(byte), new ExtendedTypeInfo<byte>()}, {typeof(Double), new ExtendedTypeInfo<Double>()},
{typeof(sbyte), new ExtendedTypeInfo<sbyte>()}, {typeof(Char), new ExtendedTypeInfo<Char>()},
{typeof(int), new ExtendedTypeInfo<int>()}, {typeof(Boolean), new ExtendedTypeInfo<Boolean>()},
{typeof(uint), new ExtendedTypeInfo<uint>()}, {typeof(Decimal), new ExtendedTypeInfo<Decimal>()},
{typeof(short), new ExtendedTypeInfo<short>()}, {typeof(Guid), new ExtendedTypeInfo<Guid>()},
{typeof(ushort), new ExtendedTypeInfo<ushort>()},
{typeof(long), new ExtendedTypeInfo<long>()},
{typeof(ulong), new ExtendedTypeInfo<ulong>()},
{typeof(float), new ExtendedTypeInfo<float>()},
{typeof(double), new ExtendedTypeInfo<double>()},
{typeof(char), new ExtendedTypeInfo<char>()},
{typeof(bool), new ExtendedTypeInfo<bool>()},
{typeof(decimal), new ExtendedTypeInfo<decimal>()},
{typeof(Guid), new ExtendedTypeInfo<Guid>()},
// Strings is also considered a basic type (it's the only basic reference type) // Strings is also considered a basic type (it's the only basic reference type)
{typeof(string), new ExtendedTypeInfo<string>()}, {typeof(String), new ExtendedTypeInfo<String>()},
// Nullables // Nullables
{typeof(DateTime?), new ExtendedTypeInfo<DateTime?>()}, {typeof(DateTime?), new ExtendedTypeInfo<DateTime?>()},
{typeof(byte?), new ExtendedTypeInfo<byte?>()}, {typeof(Byte?), new ExtendedTypeInfo<Byte?>()},
{typeof(sbyte?), new ExtendedTypeInfo<sbyte?>()}, {typeof(SByte?), new ExtendedTypeInfo<SByte?>()},
{typeof(int?), new ExtendedTypeInfo<int?>()}, {typeof(Int32?), new ExtendedTypeInfo<Int32?>()},
{typeof(uint?), new ExtendedTypeInfo<uint?>()}, {typeof(UInt32?), new ExtendedTypeInfo<UInt32?>()},
{typeof(short?), new ExtendedTypeInfo<short?>()}, {typeof(Int16?), new ExtendedTypeInfo<Int16?>()},
{typeof(ushort?), new ExtendedTypeInfo<ushort?>()}, {typeof(UInt16?), new ExtendedTypeInfo<UInt16?>()},
{typeof(long?), new ExtendedTypeInfo<long?>()}, {typeof(Int64?), new ExtendedTypeInfo<Int64?>()},
{typeof(ulong?), new ExtendedTypeInfo<ulong?>()}, {typeof(UInt64?), new ExtendedTypeInfo<UInt64?>()},
{typeof(float?), new ExtendedTypeInfo<float?>()}, {typeof(Single?), new ExtendedTypeInfo<Single?>()},
{typeof(double?), new ExtendedTypeInfo<double?>()}, {typeof(Double?), new ExtendedTypeInfo<Double?>()},
{typeof(char?), new ExtendedTypeInfo<char?>()}, {typeof(Char?), new ExtendedTypeInfo<Char?>()},
{typeof(bool?), new ExtendedTypeInfo<bool?>()}, {typeof(Boolean?), new ExtendedTypeInfo<Boolean?>()},
{typeof(decimal?), new ExtendedTypeInfo<decimal?>()}, {typeof(Decimal?), new ExtendedTypeInfo<Decimal?>()},
{typeof(Guid?), new ExtendedTypeInfo<Guid?>()}, {typeof(Guid?), new ExtendedTypeInfo<Guid?>()},
// Additional Types // Additional Types
{typeof(TimeSpan), new ExtendedTypeInfo<TimeSpan>()}, {typeof(TimeSpan), new ExtendedTypeInfo<TimeSpan>()},
{typeof(TimeSpan?), new ExtendedTypeInfo<TimeSpan?>()}, {typeof(TimeSpan?), new ExtendedTypeInfo<TimeSpan?>()},
{typeof(IPAddress), new ExtendedTypeInfo<IPAddress>()}, {typeof(IPAddress), new ExtendedTypeInfo<IPAddress>()},
}); });
#endregion #endregion
/// <summary> /// <summary>
/// Contains all basic types, including string, date time, and all of their nullable counterparts. /// Contains all basic types, including string, date time, and all of their nullable counterparts.
/// </summary> /// </summary>
/// <value> /// <value>
/// All basic types. /// All basic types.
/// </value> /// </value>
public static IReadOnlyCollection<Type> AllBasicTypes { get; } = new ReadOnlyCollection<Type>(BasicTypesInfo.Value.Keys.ToArray()); public static IReadOnlyCollection<Type> AllBasicTypes { get; } = new ReadOnlyCollection<Type>(BasicTypesInfo.Value.Keys.ToArray());
/// <summary> /// <summary>
/// Gets all numeric types including their nullable counterparts. /// Gets all numeric types including their nullable counterparts.
/// Note that Booleans and Guids are not considered numeric types. /// Note that Booleans and Guids are not considered numeric types.
/// </summary> /// </summary>
/// <value> /// <value>
/// All numeric types. /// All numeric types.
/// </value> /// </value>
public static IReadOnlyCollection<Type> AllNumericTypes { get; } = new ReadOnlyCollection<Type>( public static IReadOnlyCollection<Type> AllNumericTypes {
BasicTypesInfo get;
.Value } = new ReadOnlyCollection<Type>(BasicTypesInfo.Value.Where(kvp => kvp.Value.IsNumeric).Select(kvp => kvp.Key).ToArray());
.Where(kvp => kvp.Value.IsNumeric)
.Select(kvp => kvp.Key).ToArray()); /// <summary>
/// Gets all numeric types without their nullable counterparts.
/// <summary> /// Note that Booleans and Guids are not considered numeric types.
/// Gets all numeric types without their nullable counterparts. /// </summary>
/// Note that Booleans and Guids are not considered numeric types. /// <value>
/// </summary> /// All numeric value types.
/// <value> /// </value>
/// All numeric value types. public static IReadOnlyCollection<Type> AllNumericValueTypes {
/// </value> get;
public static IReadOnlyCollection<Type> AllNumericValueTypes { get; } = new ReadOnlyCollection<Type>( } = new ReadOnlyCollection<Type>(BasicTypesInfo.Value.Where(kvp => kvp.Value.IsNumeric && !kvp.Value.IsNullableValueType).Select(kvp => kvp.Key).ToArray());
BasicTypesInfo
.Value /// <summary>
.Where(kvp => kvp.Value.IsNumeric && !kvp.Value.IsNullableValueType) /// Contains all basic value types. i.e. excludes string and nullables.
.Select(kvp => kvp.Key).ToArray()); /// </summary>
/// <value>
/// <summary> /// All basic value types.
/// Contains all basic value types. i.e. excludes string and nullables. /// </value>
/// </summary> public static IReadOnlyCollection<Type> AllBasicValueTypes {
/// <value> get;
/// All basic value types. } = new ReadOnlyCollection<Type>(BasicTypesInfo.Value.Where(kvp => kvp.Value.IsValueType).Select(kvp => kvp.Key).ToArray());
/// </value>
public static IReadOnlyCollection<Type> AllBasicValueTypes { get; } = new ReadOnlyCollection<Type>( /// <summary>
BasicTypesInfo /// Contains all basic value types including the string type. i.e. excludes nullables.
.Value /// </summary>
.Where(kvp => kvp.Value.IsValueType) /// <value>
.Select(kvp => kvp.Key).ToArray()); /// All basic value and string types.
/// </value>
/// <summary> public static IReadOnlyCollection<Type> AllBasicValueAndStringTypes {
/// Contains all basic value types including the string type. i.e. excludes nullables. get;
/// </summary> } = new ReadOnlyCollection<Type>(BasicTypesInfo.Value.Where(kvp => kvp.Value.IsValueType || kvp.Key == typeof(String)).Select(kvp => kvp.Key).ToArray());
/// <value>
/// All basic value and string types. /// <summary>
/// </value> /// Gets all nullable value types. i.e. excludes string and all basic value types.
public static IReadOnlyCollection<Type> AllBasicValueAndStringTypes { get; } = new ReadOnlyCollection<Type>( /// </summary>
BasicTypesInfo /// <value>
.Value /// All basic nullable value types.
.Where(kvp => kvp.Value.IsValueType || kvp.Key == typeof(string)) /// </value>
.Select(kvp => kvp.Key).ToArray()); public static IReadOnlyCollection<Type> AllBasicNullableValueTypes {
get;
/// <summary> } = new ReadOnlyCollection<Type>(BasicTypesInfo.Value.Where(kvp => kvp.Value.IsNullableValueType).Select(kvp => kvp.Key).ToArray());
/// Gets all nullable value types. i.e. excludes string and all basic value types. }
/// </summary>
/// <value>
/// All basic nullable value types.
/// </value>
public static IReadOnlyCollection<Type> AllBasicNullableValueTypes { get; } = new ReadOnlyCollection<Type>(
BasicTypesInfo
.Value
.Where(kvp => kvp.Value.IsNullableValueType)
.Select(kvp => kvp.Key).ToArray());
}
} }

View File

@ -1,39 +1,34 @@
using System.Text; using System;
using System.Text;
namespace Swan namespace Swan {
{ /// <summary>
/// Contains useful constants and definitions.
/// </summary>
public static partial class Definitions {
/// <summary> /// <summary>
/// Contains useful constants and definitions. /// The MS Windows codepage 1252 encoding used in some legacy scenarios
/// such as default CSV text encoding from Excel.
/// </summary> /// </summary>
public static partial class Definitions public static readonly Encoding Windows1252Encoding;
{
/// <summary> /// <summary>
/// The MS Windows codepage 1252 encoding used in some legacy scenarios /// The encoding associated with the default ANSI code page in the operating
/// such as default CSV text encoding from Excel. /// system's regional and language settings.
/// </summary> /// </summary>
public static readonly Encoding Windows1252Encoding; public static readonly Encoding CurrentAnsiEncoding;
/// <summary> /// <summary>
/// The encoding associated with the default ANSI code page in the operating /// Initializes the <see cref="Definitions"/> class.
/// system's regional and language settings. /// </summary>
/// </summary> static Definitions() {
public static readonly Encoding CurrentAnsiEncoding; CurrentAnsiEncoding = Encoding.GetEncoding(default(Int32));
try {
/// <summary> Windows1252Encoding = Encoding.GetEncoding(1252);
/// Initializes the <see cref="Definitions"/> class. } catch {
/// </summary> // ignore, the codepage is not available use default
static Definitions() Windows1252Encoding = CurrentAnsiEncoding;
{ }
CurrentAnsiEncoding = Encoding.GetEncoding(default(int)); }
try }
{
Windows1252Encoding = Encoding.GetEncoding(1252);
}
catch
{
// ignore, the codepage is not available use default
Windows1252Encoding = CurrentAnsiEncoding;
}
}
}
} }

View File

@ -4,118 +4,102 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
namespace Swan.Diagnostics namespace Swan.Diagnostics {
{ /// <summary>
/// A simple benchmarking class.
/// </summary>
/// <example>
/// The following code demonstrates how to create a simple benchmark.
/// <code>
/// namespace Examples.Benchmark.Simple
/// {
/// using Swan.Diagnostics;
///
/// public class SimpleBenchmark
/// {
/// public static void Main()
/// {
/// using (Benchmark.Start("Test"))
/// {
/// // do some logic in here
/// }
///
/// // dump results into a string
/// var results = Benchmark.Dump();
/// }
/// }
///
/// }
/// </code>
/// </example>
public static partial class Benchmark {
private static readonly Object SyncLock = new Object();
private static readonly Dictionary<String, List<TimeSpan>> Measures = new Dictionary<String, List<TimeSpan>>();
/// <summary> /// <summary>
/// A simple benchmarking class. /// Starts measuring with the given identifier.
/// </summary> /// </summary>
/// <example> /// <param name="identifier">The identifier.</param>
/// The following code demonstrates how to create a simple benchmark. /// <returns>A disposable object that when disposed, adds a benchmark result.</returns>
/// <code> public static IDisposable Start(String identifier) => new BenchmarkUnit(identifier);
/// namespace Examples.Benchmark.Simple
/// { /// <summary>
/// using Swan.Diagnostics; /// Outputs the benchmark statistics.
/// /// </summary>
/// public class SimpleBenchmark /// <returns>A string containing human-readable statistics.</returns>
/// { public static String Dump() {
/// public static void Main() StringBuilder builder = new StringBuilder();
/// {
/// using (Benchmark.Start("Test")) lock(SyncLock) {
/// { foreach(KeyValuePair<String, List<TimeSpan>> kvp in Measures) {
/// // do some logic in here _ = builder.Append($"BID: {kvp.Key,-30} | ").Append($"CNT: {kvp.Value.Count,6} | ").Append($"AVG: {kvp.Value.Average(t => t.TotalMilliseconds),8:0.000} ms. | ").Append($"MAX: {kvp.Value.Max(t => t.TotalMilliseconds),8:0.000} ms. | ").Append($"MIN: {kvp.Value.Min(t => t.TotalMilliseconds),8:0.000} ms. | ").Append(Environment.NewLine);
/// } }
/// }
/// // dump results into a string
/// var results = Benchmark.Dump(); return builder.ToString().TrimEnd();
/// } }
/// }
/// /// <summary>
/// } /// Measures the elapsed time of the given action as a TimeSpan
/// </code> /// This method uses a high precision Stopwatch if it is available.
/// </example> /// </summary>
public static partial class Benchmark /// <param name="target">The target.</param>
{ /// <returns>
private static readonly object SyncLock = new object(); /// A time interval that represents a specified time, where the specification is in units of ticks.
private static readonly Dictionary<string, List<TimeSpan>> Measures = new Dictionary<string, List<TimeSpan>>(); /// </returns>
/// <exception cref="ArgumentNullException">target.</exception>
/// <summary> public static TimeSpan BenchmarkAction(Action target) {
/// Starts measuring with the given identifier. if(target == null) {
/// </summary> throw new ArgumentNullException(nameof(target));
/// <param name="identifier">The identifier.</param> }
/// <returns>A disposable object that when disposed, adds a benchmark result.</returns>
public static IDisposable Start(string identifier) => new BenchmarkUnit(identifier); Stopwatch sw = Stopwatch.IsHighResolution ? new HighResolutionTimer() : new Stopwatch();
/// <summary> try {
/// Outputs the benchmark statistics. sw.Start();
/// </summary> target.Invoke();
/// <returns>A string containing human-readable statistics.</returns> } catch {
public static string Dump() // swallow
{ } finally {
var builder = new StringBuilder(); sw.Stop();
}
lock (SyncLock)
{ return TimeSpan.FromTicks(sw.ElapsedTicks);
foreach (var kvp in Measures) }
{
builder.Append($"BID: {kvp.Key,-30} | ") /// <summary>
.Append($"CNT: {kvp.Value.Count,6} | ") /// Adds the specified result to the given identifier.
.Append($"AVG: {kvp.Value.Average(t => t.TotalMilliseconds),8:0.000} ms. | ") /// </summary>
.Append($"MAX: {kvp.Value.Max(t => t.TotalMilliseconds),8:0.000} ms. | ") /// <param name="identifier">The identifier.</param>
.Append($"MIN: {kvp.Value.Min(t => t.TotalMilliseconds),8:0.000} ms. | ") /// <param name="elapsed">The elapsed.</param>
.Append(Environment.NewLine); private static void Add(String identifier, TimeSpan elapsed) {
} lock(SyncLock) {
} if(Measures.ContainsKey(identifier) == false) {
Measures[identifier] = new List<TimeSpan>(1024 * 1024);
return builder.ToString().TrimEnd(); }
}
Measures[identifier].Add(elapsed);
/// <summary> }
/// Measures the elapsed time of the given action as a TimeSpan }
/// This method uses a high precision Stopwatch if it is available. }
/// </summary>
/// <param name="target">The target.</param>
/// <returns>
/// A time interval that represents a specified time, where the specification is in units of ticks.
/// </returns>
/// <exception cref="ArgumentNullException">target.</exception>
public static TimeSpan BenchmarkAction(Action target)
{
if (target == null)
throw new ArgumentNullException(nameof(target));
var sw = Stopwatch.IsHighResolution ? new HighResolutionTimer() : new Stopwatch();
try
{
sw.Start();
target.Invoke();
}
catch
{
// swallow
}
finally
{
sw.Stop();
}
return TimeSpan.FromTicks(sw.ElapsedTicks);
}
/// <summary>
/// Adds the specified result to the given identifier.
/// </summary>
/// <param name="identifier">The identifier.</param>
/// <param name="elapsed">The elapsed.</param>
private static void Add(string identifier, TimeSpan elapsed)
{
lock (SyncLock)
{
if (Measures.ContainsKey(identifier) == false)
Measures[identifier] = new List<TimeSpan>(1024 * 1024);
Measures[identifier].Add(elapsed);
}
}
}
} }

View File

@ -1,50 +1,47 @@
using System; #nullable enable
using System;
using System.Diagnostics; using System.Diagnostics;
namespace Swan.Diagnostics namespace Swan.Diagnostics {
{ public static partial class Benchmark {
public static partial class Benchmark /// <summary>
{ /// Represents a disposable benchmark unit.
/// <summary> /// </summary>
/// Represents a disposable benchmark unit. /// <seealso cref="IDisposable" />
/// </summary> private sealed class BenchmarkUnit : IDisposable {
/// <seealso cref="IDisposable" /> private readonly String _identifier;
private sealed class BenchmarkUnit : IDisposable private Boolean _isDisposed; // To detect redundant calls
{ private Stopwatch? _stopwatch = new Stopwatch();
private readonly string _identifier;
private bool _isDisposed; // To detect redundant calls /// <summary>
private Stopwatch? _stopwatch = new Stopwatch(); /// Initializes a new instance of the <see cref="BenchmarkUnit" /> class.
/// </summary>
/// <summary> /// <param name="identifier">The identifier.</param>
/// Initializes a new instance of the <see cref="BenchmarkUnit" /> class. public BenchmarkUnit(String identifier) {
/// </summary> this._identifier = identifier;
/// <param name="identifier">The identifier.</param> this._stopwatch?.Start();
public BenchmarkUnit(string identifier) }
{
_identifier = identifier; /// <inheritdoc />
_stopwatch?.Start(); public void Dispose() => this.Dispose(true);
}
/// <summary>
/// <inheritdoc /> /// Releases unmanaged and - optionally - managed resources.
public void Dispose() => Dispose(true); /// </summary>
/// <param name="alsoManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
/// <summary> private void Dispose(Boolean alsoManaged) {
/// Releases unmanaged and - optionally - managed resources. if(this._isDisposed) {
/// </summary> return;
/// <param name="alsoManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> }
private void Dispose(bool alsoManaged)
{ if(alsoManaged) {
if (_isDisposed) return; Add(this._identifier, this._stopwatch?.Elapsed ?? default);
this._stopwatch?.Stop();
if (alsoManaged) }
{
Add(_identifier, _stopwatch?.Elapsed ?? default); this._stopwatch = null;
_stopwatch?.Stop(); this._isDisposed = true;
} }
}
_stopwatch = null; }
_isDisposed = true;
}
}
}
} }

View File

@ -1,32 +1,30 @@
namespace Swan.Diagnostics using System;
{ using System.Diagnostics;
using System;
using System.Diagnostics; namespace Swan.Diagnostics {
/// <summary>
/// Provides access to a high-resolution, time measuring device.
/// </summary>
/// <seealso cref="Stopwatch" />
public class HighResolutionTimer : Stopwatch {
/// <summary> /// <summary>
/// Provides access to a high-resolution, time measuring device. /// Initializes a new instance of the <see cref="HighResolutionTimer"/> class.
/// </summary> /// </summary>
/// <seealso cref="Stopwatch" /> /// <exception cref="NotSupportedException">High-resolution timer not available.</exception>
public class HighResolutionTimer : Stopwatch public HighResolutionTimer() {
{ if(!IsHighResolution) {
/// <summary> throw new NotSupportedException("High-resolution timer not available");
/// Initializes a new instance of the <see cref="HighResolutionTimer"/> class. }
/// </summary> }
/// <exception cref="NotSupportedException">High-resolution timer not available.</exception>
public HighResolutionTimer() /// <summary>
{ /// Gets the number of microseconds per timer tick.
if (!IsHighResolution) /// </summary>
throw new NotSupportedException("High-resolution timer not available"); public static Double MicrosecondsPerTick { get; } = 1000000d / Frequency;
}
/// <summary>
/// <summary> /// Gets the elapsed microseconds.
/// Gets the number of microseconds per timer tick. /// </summary>
/// </summary> public Int64 ElapsedMicroseconds => (Int64)(this.ElapsedTicks * MicrosecondsPerTick);
public static double MicrosecondsPerTick { get; } = 1000000d / Frequency; }
/// <summary>
/// Gets the elapsed microseconds.
/// </summary>
public long ElapsedMicroseconds => (long)(ElapsedTicks * MicrosecondsPerTick);
}
} }

View File

@ -1,171 +1,114 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Swan.Collections; using Swan.Collections;
namespace Swan namespace Swan {
{ /// <summary>
/// Provide Enumerations helpers with internal cache.
/// </summary>
public class EnumHelper
: SingletonBase<CollectionCacheRepository<Tuple<String, Object>>> {
/// <summary> /// <summary>
/// Provide Enumerations helpers with internal cache. /// Gets all the names and enumerators from a specific Enum type.
/// </summary> /// </summary>
public class EnumHelper /// <typeparam name="T">The type of the attribute to be retrieved.</typeparam>
: SingletonBase<CollectionCacheRepository<Tuple<string, object>>> /// <returns>A tuple of enumerator names and their value stored for the specified type.</returns>
{ public static IEnumerable<Tuple<String, Object>> Retrieve<T>() where T : struct, IConvertible => Instance.Retrieve(typeof(T), t => Enum.GetValues(t).Cast<Object>().Select(item => Tuple.Create(Enum.GetName(t, item), item)));
/// <summary>
/// Gets all the names and enumerators from a specific Enum type. /// <summary>
/// </summary> /// Gets the cached items with the enum item value.
/// <typeparam name="T">The type of the attribute to be retrieved.</typeparam> /// </summary>
/// <returns>A tuple of enumerator names and their value stored for the specified type.</returns> /// <typeparam name="T">The type of enumeration.</typeparam>
public static IEnumerable<Tuple<string, object>> Retrieve<T>() /// <param name="humanize">if set to <c>true</c> [humanize].</param>
where T : struct, IConvertible /// <returns>
{ /// A collection of Type/Tuple pairs
return Instance.Retrieve(typeof(T), t => Enum.GetValues(t) /// that represents items with the enum item value.
.Cast<object>() /// </returns>
.Select(item => Tuple.Create(Enum.GetName(t, item), item))); public static IEnumerable<Tuple<Int32, String>> GetItemsWithValue<T>(Boolean humanize = true) where T : struct, IConvertible => Retrieve<T>().Select(x => Tuple.Create((Int32)x.Item2, humanize ? x.Item1.Humanize() : x.Item1));
}
/// <summary>
/// <summary> /// Gets the flag values.
/// Gets the cached items with the enum item value. /// </summary>
/// </summary> /// <typeparam name="TEnum">The type of the enum.</typeparam>
/// <typeparam name="T">The type of enumeration.</typeparam> /// <param name="value">The value.</param>
/// <param name="humanize">if set to <c>true</c> [humanize].</param> /// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
/// <returns> /// <returns>
/// A collection of Type/Tuple pairs /// A list of values in the flag.
/// that represents items with the enum item value. /// </returns>
/// </returns> public static IEnumerable<Int32> GetFlagValues<TEnum>(Int32 value, Boolean ignoreZero = false) where TEnum : struct, IConvertible => Retrieve<TEnum>().Select(x => (Int32)x.Item2).When(() => ignoreZero, q => q.Where(f => f != 0)).Where(x => (x & value) == x);
public static IEnumerable<Tuple<int, string>> GetItemsWithValue<T>(bool humanize = true)
where T : struct, IConvertible /// <summary>
{ /// Gets the flag values.
return Retrieve<T>() /// </summary>
.Select(x => Tuple.Create((int) x.Item2, humanize ? x.Item1.Humanize() : x.Item1)); /// <typeparam name="TEnum">The type of the enum.</typeparam>
} /// <param name="value">The value.</param>
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
/// <summary> /// <returns>
/// Gets the flag values. /// A list of values in the flag.
/// </summary> /// </returns>
/// <typeparam name="TEnum">The type of the enum.</typeparam> public static IEnumerable<Int64> GetFlagValues<TEnum>(Int64 value, Boolean ignoreZero = false) where TEnum : struct, IConvertible => Retrieve<TEnum>().Select(x => (Int64)x.Item2).When(() => ignoreZero, q => q.Where(f => f != 0)).Where(x => (x & value) == x);
/// <param name="value">The value.</param>
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param> /// <summary>
/// <returns> /// Gets the flag values.
/// A list of values in the flag. /// </summary>
/// </returns> /// <typeparam name="TEnum">The type of the enum.</typeparam>
public static IEnumerable<int> GetFlagValues<TEnum>(int value, bool ignoreZero = false) /// <param name="value">The value.</param>
where TEnum : struct, IConvertible /// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
{ /// <returns>
return Retrieve<TEnum>() /// A list of values in the flag.
.Select(x => (int) x.Item2) /// </returns>
.When(() => ignoreZero, q => q.Where(f => f != 0)) public static IEnumerable<Byte> GetFlagValues<TEnum>(Byte value, Boolean ignoreZero = false) where TEnum : struct, IConvertible => Retrieve<TEnum>().Select(x => (Byte)x.Item2).When(() => ignoreZero, q => q.Where(f => f != 0)).Where(x => (x & value) == x);
.Where(x => (x & value) == x);
} /// <summary>
/// Gets the flag names.
/// <summary> /// </summary>
/// Gets the flag values. /// <typeparam name="TEnum">The type of the enum.</typeparam>
/// </summary> /// <param name="value">the value.</param>
/// <typeparam name="TEnum">The type of the enum.</typeparam> /// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
/// <param name="value">The value.</param> /// <param name="humanize">if set to <c>true</c> [humanize].</param>
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param> /// <returns>
/// <returns> /// A list of flag names.
/// A list of values in the flag. /// </returns>
/// </returns> public static IEnumerable<String> GetFlagNames<TEnum>(Int32 value, Boolean ignoreZero = false, Boolean humanize = true) where TEnum : struct, IConvertible => Retrieve<TEnum>().When(() => ignoreZero, q => q.Where(f => (Int32)f.Item2 != 0)).Where(x => ((Int32)x.Item2 & value) == (Int32)x.Item2).Select(x => humanize ? x.Item1.Humanize() : x.Item1);
public static IEnumerable<long> GetFlagValues<TEnum>(long value, bool ignoreZero = false)
where TEnum : struct, IConvertible /// <summary>
{ /// Gets the flag names.
return Retrieve<TEnum>() /// </summary>
.Select(x => (long) x.Item2) /// <typeparam name="TEnum">The type of the enum.</typeparam>
.When(() => ignoreZero, q => q.Where(f => f != 0)) /// <param name="value">The value.</param>
.Where(x => (x & value) == x); /// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
} /// <param name="humanize">if set to <c>true</c> [humanize].</param>
/// <returns>
/// <summary> /// A list of flag names.
/// Gets the flag values. /// </returns>
/// </summary> public static IEnumerable<String> GetFlagNames<TEnum>(Int64 value, Boolean ignoreZero = false, Boolean humanize = true) where TEnum : struct, IConvertible => Retrieve<TEnum>().When(() => ignoreZero, q => q.Where(f => (Int64)f.Item2 != 0)).Where(x => ((Int64)x.Item2 & value) == (Int64)x.Item2).Select(x => humanize ? x.Item1.Humanize() : x.Item1);
/// <typeparam name="TEnum">The type of the enum.</typeparam>
/// <param name="value">The value.</param> /// <summary>
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param> /// Gets the flag names.
/// <returns> /// </summary>
/// A list of values in the flag. /// <typeparam name="TEnum">The type of the enum.</typeparam>
/// </returns> /// <param name="value">The value.</param>
public static IEnumerable<byte> GetFlagValues<TEnum>(byte value, bool ignoreZero = false) /// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
where TEnum : struct, IConvertible /// <param name="humanize">if set to <c>true</c> [humanize].</param>
{ /// <returns>
return Retrieve<TEnum>() /// A list of flag names.
.Select(x => (byte) x.Item2) /// </returns>
.When(() => ignoreZero, q => q.Where(f => f != 0)) public static IEnumerable<String> GetFlagNames<TEnum>(Byte value, Boolean ignoreZero = false, Boolean humanize = true) where TEnum : struct, IConvertible => Retrieve<TEnum>().When(() => ignoreZero, q => q.Where(f => (Byte)f.Item2 != 0)).Where(x => ((Byte)x.Item2 & value) == (Byte)x.Item2).Select(x => humanize ? x.Item1.Humanize() : x.Item1);
.Where(x => (x & value) == x);
} /// <summary>
/// Gets the cached items with the enum item index.
/// <summary> /// </summary>
/// Gets the flag names. /// <typeparam name="T">The type of enumeration.</typeparam>
/// </summary> /// <param name="humanize">if set to <c>true</c> [humanize].</param>
/// <typeparam name="TEnum">The type of the enum.</typeparam> /// <returns>
/// <param name="value">the value.</param> /// A collection of Type/Tuple pairs that represents items with the enum item value.
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param> /// </returns>
/// <param name="humanize">if set to <c>true</c> [humanize].</param> public static IEnumerable<Tuple<Int32, String>> GetItemsWithIndex<T>(Boolean humanize = true) where T : struct, IConvertible {
/// <returns> Int32 i = 0;
/// A list of flag names.
/// </returns> return Retrieve<T>().Select(x => Tuple.Create(i++, humanize ? x.Item1.Humanize() : x.Item1));
public static IEnumerable<string> GetFlagNames<TEnum>(int value, bool ignoreZero = false, bool humanize = true) }
where TEnum : struct, IConvertible }
{
return Retrieve<TEnum>()
.When(() => ignoreZero, q => q.Where(f => (int) f.Item2 != 0))
.Where(x => ((int) x.Item2 & value) == (int) x.Item2)
.Select(x => humanize ? x.Item1.Humanize() : x.Item1);
}
/// <summary>
/// Gets the flag names.
/// </summary>
/// <typeparam name="TEnum">The type of the enum.</typeparam>
/// <param name="value">The value.</param>
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
/// <param name="humanize">if set to <c>true</c> [humanize].</param>
/// <returns>
/// A list of flag names.
/// </returns>
public static IEnumerable<string> GetFlagNames<TEnum>(long value, bool ignoreZero = false, bool humanize = true)
where TEnum : struct, IConvertible
{
return Retrieve<TEnum>()
.When(() => ignoreZero, q => q.Where(f => (long) f.Item2 != 0))
.Where(x => ((long) x.Item2 & value) == (long) x.Item2)
.Select(x => humanize ? x.Item1.Humanize() : x.Item1);
}
/// <summary>
/// Gets the flag names.
/// </summary>
/// <typeparam name="TEnum">The type of the enum.</typeparam>
/// <param name="value">The value.</param>
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
/// <param name="humanize">if set to <c>true</c> [humanize].</param>
/// <returns>
/// A list of flag names.
/// </returns>
public static IEnumerable<string> GetFlagNames<TEnum>(byte value, bool ignoreZero = false, bool humanize = true)
where TEnum : struct, IConvertible
{
return Retrieve<TEnum>()
.When(() => ignoreZero, q => q.Where(f => (byte) f.Item2 != 0))
.Where(x => ((byte) x.Item2 & value) == (byte) x.Item2)
.Select(x => humanize ? x.Item1.Humanize() : x.Item1);
}
/// <summary>
/// Gets the cached items with the enum item index.
/// </summary>
/// <typeparam name="T">The type of enumeration.</typeparam>
/// <param name="humanize">if set to <c>true</c> [humanize].</param>
/// <returns>
/// A collection of Type/Tuple pairs that represents items with the enum item value.
/// </returns>
public static IEnumerable<Tuple<int, string>> GetItemsWithIndex<T>(bool humanize = true)
where T : struct, IConvertible
{
var i = 0;
return Retrieve<T>()
.Select(x => Tuple.Create(i++, humanize ? x.Item1.Humanize() : x.Item1));
}
}
} }

View File

@ -1,65 +1,61 @@
namespace Swan namespace Swan {
{ /// <summary>
/// Enumeration of Operating Systems.
/// </summary>
public enum OperatingSystem {
/// <summary> /// <summary>
/// Enumeration of Operating Systems. /// Unknown OS
/// </summary> /// </summary>
public enum OperatingSystem Unknown,
{
/// <summary>
/// Unknown OS
/// </summary>
Unknown,
/// <summary>
/// Windows
/// </summary>
Windows,
/// <summary>
/// UNIX/Linux
/// </summary>
Unix,
/// <summary>
/// macOS (OSX)
/// </summary>
Osx,
}
/// <summary> /// <summary>
/// Defines Endianness, big or little. /// Windows
/// </summary> /// </summary>
public enum Endianness Windows,
{
/// <summary>
/// In big endian, you store the most significant byte in the smallest address.
/// </summary>
Big,
/// <summary>
/// In little endian, you store the least significant byte in the smallest address.
/// </summary>
Little,
}
/// <summary> /// <summary>
/// Enumerates the JSON serializer cases to use: None (keeps the same case), PascalCase, or camelCase. /// UNIX/Linux
/// </summary> /// </summary>
public enum JsonSerializerCase Unix,
{
/// <summary> /// <summary>
/// The none /// macOS (OSX)
/// </summary> /// </summary>
None, Osx,
}
/// <summary>
/// The pascal case (eg. PascalCase) /// <summary>
/// </summary> /// Defines Endianness, big or little.
PascalCase, /// </summary>
public enum Endianness {
/// <summary> /// <summary>
/// The camel case (eg. camelCase) /// In big endian, you store the most significant byte in the smallest address.
/// </summary> /// </summary>
CamelCase, Big,
}
/// <summary>
/// In little endian, you store the least significant byte in the smallest address.
/// </summary>
Little,
}
/// <summary>
/// Enumerates the JSON serializer cases to use: None (keeps the same case), PascalCase, or camelCase.
/// </summary>
public enum JsonSerializerCase {
/// <summary>
/// The none
/// </summary>
None,
/// <summary>
/// The pascal case (eg. PascalCase)
/// </summary>
PascalCase,
/// <summary>
/// The camel case (eg. camelCase)
/// </summary>
CamelCase,
}
} }

View File

@ -7,498 +7,492 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Swan namespace Swan {
{ /// <summary>
/// Provides various extension methods for byte arrays and streams.
/// </summary>
public static class ByteArrayExtensions {
/// <summary> /// <summary>
/// Provides various extension methods for byte arrays and streams. /// Converts an array of bytes to its lower-case, hexadecimal representation.
/// </summary> /// </summary>
public static class ByteArrayExtensions /// <param name="bytes">The bytes.</param>
{ /// <param name="addPrefix">if set to <c>true</c> add the 0x prefix tot he output.</param>
/// <summary> /// <returns>
/// Converts an array of bytes to its lower-case, hexadecimal representation. /// The specified string instance; no actual conversion is performed.
/// </summary> /// </returns>
/// <param name="bytes">The bytes.</param> /// <exception cref="ArgumentNullException">bytes.</exception>
/// <param name="addPrefix">if set to <c>true</c> add the 0x prefix tot he output.</param> public static String ToLowerHex(this Byte[] bytes, Boolean addPrefix = false) => ToHex(bytes, addPrefix, "x2");
/// <returns>
/// The specified string instance; no actual conversion is performed. /// <summary>
/// </returns> /// Converts an array of bytes to its upper-case, hexadecimal representation.
/// <exception cref="ArgumentNullException">bytes.</exception> /// </summary>
public static string ToLowerHex(this byte[] bytes, bool addPrefix = false) /// <param name="bytes">The bytes.</param>
=> ToHex(bytes, addPrefix, "x2"); /// <param name="addPrefix">if set to <c>true</c> [add prefix].</param>
/// <returns>
/// <summary> /// The specified string instance; no actual conversion is performed.
/// Converts an array of bytes to its upper-case, hexadecimal representation. /// </returns>
/// </summary> /// <exception cref="ArgumentNullException">bytes.</exception>
/// <param name="bytes">The bytes.</param> public static String ToUpperHex(this Byte[] bytes, Boolean addPrefix = false) => ToHex(bytes, addPrefix, "X2");
/// <param name="addPrefix">if set to <c>true</c> [add prefix].</param>
/// <returns> /// <summary>
/// The specified string instance; no actual conversion is performed. /// Converts an array of bytes to a sequence of dash-separated, hexadecimal,
/// </returns> /// uppercase characters.
/// <exception cref="ArgumentNullException">bytes.</exception> /// </summary>
public static string ToUpperHex(this byte[] bytes, bool addPrefix = false) /// <param name="bytes">The bytes.</param>
=> ToHex(bytes, addPrefix, "X2"); /// <returns>
/// A string of hexadecimal pairs separated by hyphens, where each pair represents
/// <summary> /// the corresponding element in value; for example, "7F-2C-4A-00".
/// Converts an array of bytes to a sequence of dash-separated, hexadecimal, /// </returns>
/// uppercase characters. public static String ToDashedHex(this Byte[] bytes) => BitConverter.ToString(bytes);
/// </summary>
/// <param name="bytes">The bytes.</param> /// <summary>
/// <returns> /// Converts an array of bytes to a base-64 encoded string.
/// A string of hexadecimal pairs separated by hyphens, where each pair represents /// </summary>
/// the corresponding element in value; for example, "7F-2C-4A-00". /// <param name="bytes">The bytes.</param>
/// </returns> /// <returns>A <see cref="String" /> converted from an array of bytes.</returns>
public static string ToDashedHex(this byte[] bytes) => BitConverter.ToString(bytes); public static String ToBase64(this Byte[] bytes) => Convert.ToBase64String(bytes);
/// <summary> /// <summary>
/// Converts an array of bytes to a base-64 encoded string. /// Converts a set of hexadecimal characters (uppercase or lowercase)
/// </summary> /// to a byte array. String length must be a multiple of 2 and
/// <param name="bytes">The bytes.</param> /// any prefix (such as 0x) has to be avoided for this to work properly.
/// <returns>A <see cref="string" /> converted from an array of bytes.</returns> /// </summary>
public static string ToBase64(this byte[] bytes) => Convert.ToBase64String(bytes); /// <param name="this">The hexadecimal.</param>
/// <returns>
/// <summary> /// A byte array containing the results of encoding the specified set of characters.
/// Converts a set of hexadecimal characters (uppercase or lowercase) /// </returns>
/// to a byte array. String length must be a multiple of 2 and /// <exception cref="ArgumentNullException">hex.</exception>
/// any prefix (such as 0x) has to be avoided for this to work properly. public static Byte[] ConvertHexadecimalToBytes(this String @this) {
/// </summary> if(String.IsNullOrWhiteSpace(@this)) {
/// <param name="this">The hexadecimal.</param> throw new ArgumentNullException(nameof(@this));
/// <returns> }
/// A byte array containing the results of encoding the specified set of characters.
/// </returns> return Enumerable.Range(0, @this.Length / 2).Select(x => Convert.ToByte(@this.Substring(x * 2, 2), 16)).ToArray();
/// <exception cref="ArgumentNullException">hex.</exception> }
public static byte[] ConvertHexadecimalToBytes(this string @this)
{ /// <summary>
if (string.IsNullOrWhiteSpace(@this)) /// Gets the bit value at the given offset.
throw new ArgumentNullException(nameof(@this)); /// </summary>
/// <param name="this">The b.</param>
return Enumerable /// <param name="offset">The offset.</param>
.Range(0, @this.Length / 2) /// <param name="length">The length.</param>
.Select(x => Convert.ToByte(@this.Substring(x * 2, 2), 16)) /// <returns>
.ToArray(); /// Bit value at the given offset.
} /// </returns>
public static Byte GetBitValueAt(this Byte @this, Byte offset, Byte length = 1) => (Byte)((@this >> offset) & ~(0xff << length));
/// <summary>
/// Gets the bit value at the given offset. /// <summary>
/// </summary> /// Sets the bit value at the given offset.
/// <param name="this">The b.</param> /// </summary>
/// <param name="offset">The offset.</param> /// <param name="this">The b.</param>
/// <param name="length">The length.</param> /// <param name="offset">The offset.</param>
/// <returns> /// <param name="length">The length.</param>
/// Bit value at the given offset. /// <param name="value">The value.</param>
/// </returns> /// <returns>Bit value at the given offset.</returns>
public static byte GetBitValueAt(this byte @this, byte offset, byte length = 1) => (byte)((@this >> offset) & ~(0xff << length)); public static Byte SetBitValueAt(this Byte @this, Byte offset, Byte length, Byte value) {
Int32 mask = ~(0xff << length);
/// <summary> Byte valueAt = (Byte)(value & mask);
/// Sets the bit value at the given offset.
/// </summary> return (Byte)((valueAt << offset) | (@this & ~(mask << offset)));
/// <param name="this">The b.</param> }
/// <param name="offset">The offset.</param>
/// <param name="length">The length.</param> /// <summary>
/// <param name="value">The value.</param> /// Sets the bit value at the given offset.
/// <returns>Bit value at the given offset.</returns> /// </summary>
public static byte SetBitValueAt(this byte @this, byte offset, byte length, byte value) /// <param name="this">The b.</param>
{ /// <param name="offset">The offset.</param>
var mask = ~(0xff << length); /// <param name="value">The value.</param>
var valueAt = (byte)(value & mask); /// <returns>Bit value at the given offset.</returns>
public static Byte SetBitValueAt(this Byte @this, Byte offset, Byte value) => @this.SetBitValueAt(offset, 1, value);
return (byte)((valueAt << offset) | (@this & ~(mask << offset)));
} /// <summary>
/// Splits a byte array delimited by the specified sequence of bytes.
/// <summary> /// Each individual element in the result will contain the split sequence terminator if it is found to be delimited by it.
/// Sets the bit value at the given offset. /// 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
/// </summary> /// second one containing 4. Use the Trim extension methods to remove terminator sequences.
/// <param name="this">The b.</param> /// </summary>
/// <param name="offset">The offset.</param> /// <param name="this">The buffer.</param>
/// <param name="value">The value.</param> /// <param name="offset">The offset at which to start splitting bytes. Any bytes before this will be discarded.</param>
/// <returns>Bit value at the given offset.</returns> /// <param name="sequence">The sequence.</param>
public static byte SetBitValueAt(this byte @this, byte offset, byte value) => @this.SetBitValueAt(offset, 1, value); /// <returns>
/// A byte array containing the results the specified sequence of bytes.
/// <summary> /// </returns>
/// Splits a byte array delimited by the specified sequence of bytes. /// <exception cref="System.ArgumentNullException">
/// Each individual element in the result will contain the split sequence terminator if it is found to be delimited by it. /// buffer
/// 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 /// or
/// second one containing 4. Use the Trim extension methods to remove terminator sequences. /// sequence.
/// </summary> /// </exception>
/// <param name="this">The buffer.</param> public static List<Byte[]> Split(this Byte[] @this, Int32 offset, params Byte[] sequence) {
/// <param name="offset">The offset at which to start splitting bytes. Any bytes before this will be discarded.</param> if(@this == null) {
/// <param name="sequence">The sequence.</param> throw new ArgumentNullException(nameof(@this));
/// <returns> }
/// A byte array containing the results the specified sequence of bytes.
/// </returns> if(sequence == null) {
/// <exception cref="System.ArgumentNullException"> throw new ArgumentNullException(nameof(sequence));
/// buffer }
/// or
/// sequence. Int32 seqOffset = offset.Clamp(0, @this.Length - 1);
/// </exception>
public static List<byte[]> Split(this byte[] @this, int offset, params byte[] sequence) List<Byte[]> result = new List<Byte[]>();
{
if (@this == null) while(seqOffset < @this.Length) {
throw new ArgumentNullException(nameof(@this)); Int32 separatorStartIndex = @this.GetIndexOf(sequence, seqOffset);
if (sequence == null) if(separatorStartIndex >= 0) {
throw new ArgumentNullException(nameof(sequence)); Byte[] item = new Byte[separatorStartIndex - seqOffset + sequence.Length];
Array.Copy(@this, seqOffset, item, 0, item.Length);
var seqOffset = offset.Clamp(0, @this.Length - 1); result.Add(item);
seqOffset += item.Length;
var result = new List<byte[]>(); } else {
Byte[] item = new Byte[@this.Length - seqOffset];
while (seqOffset < @this.Length) Array.Copy(@this, seqOffset, item, 0, item.Length);
{ result.Add(item);
var separatorStartIndex = @this.GetIndexOf(sequence, seqOffset); break;
}
if (separatorStartIndex >= 0) }
{
var item = new byte[separatorStartIndex - seqOffset + sequence.Length]; return result;
Array.Copy(@this, seqOffset, item, 0, item.Length); }
result.Add(item);
seqOffset += item.Length; /// <summary>
} /// Clones the specified buffer, byte by byte.
else /// </summary>
{ /// <param name="this">The buffer.</param>
var item = new byte[@this.Length - seqOffset]; /// <returns>
Array.Copy(@this, seqOffset, item, 0, item.Length); /// A byte array containing the results of encoding the specified set of characters.
result.Add(item); /// </returns>
break; /// <exception cref="System.ArgumentNullException">this</exception>
} public static Byte[] DeepClone(this Byte[] @this) {
} if(@this == null) {
throw new ArgumentNullException(nameof(@this));
return result; }
}
Byte[] result = new Byte[@this.Length];
/// <summary> Array.Copy(@this, result, @this.Length);
/// Clones the specified buffer, byte by byte. return result;
/// </summary> }
/// <param name="this">The buffer.</param>
/// <returns> /// <summary>
/// A byte array containing the results of encoding the specified set of characters. /// Removes the specified sequence from the start of the buffer if the buffer begins with such sequence.
/// </returns> /// </summary>
/// <exception cref="System.ArgumentNullException">this</exception> /// <param name="buffer">The buffer.</param>
public static byte[] DeepClone(this byte[] @this) /// <param name="sequence">The sequence.</param>
{ /// <returns>
if (@this == null) /// A new trimmed byte array.
throw new ArgumentNullException(nameof(@this)); /// </returns>
/// <exception cref="ArgumentNullException">buffer.</exception>
var result = new byte[@this.Length]; public static Byte[] TrimStart(this Byte[] buffer, params Byte[] sequence) {
Array.Copy(@this, result, @this.Length); if(buffer == null) {
return result; throw new ArgumentNullException(nameof(buffer));
} }
/// <summary> if(buffer.StartsWith(sequence) == false) {
/// Removes the specified sequence from the start of the buffer if the buffer begins with such sequence. return buffer.DeepClone();
/// </summary> }
/// <param name="buffer">The buffer.</param>
/// <param name="sequence">The sequence.</param> Byte[] result = new Byte[buffer.Length - sequence.Length];
/// <returns> Array.Copy(buffer, sequence.Length, result, 0, result.Length);
/// A new trimmed byte array. return result;
/// </returns> }
/// <exception cref="ArgumentNullException">buffer.</exception>
public static byte[] TrimStart(this byte[] buffer, params byte[] sequence) /// <summary>
{ /// Removes the specified sequence from the end of the buffer if the buffer ends with such sequence.
if (buffer == null) /// </summary>
throw new ArgumentNullException(nameof(buffer)); /// <param name="buffer">The buffer.</param>
/// <param name="sequence">The sequence.</param>
if (buffer.StartsWith(sequence) == false) /// <returns>
return buffer.DeepClone(); /// A byte array containing the results of encoding the specified set of characters.
/// </returns>
var result = new byte[buffer.Length - sequence.Length]; /// <exception cref="ArgumentNullException">buffer.</exception>
Array.Copy(buffer, sequence.Length, result, 0, result.Length); public static Byte[] TrimEnd(this Byte[] buffer, params Byte[] sequence) {
return result; if(buffer == null) {
} throw new ArgumentNullException(nameof(buffer));
}
/// <summary>
/// Removes the specified sequence from the end of the buffer if the buffer ends with such sequence. if(buffer.EndsWith(sequence) == false) {
/// </summary> return buffer.DeepClone();
/// <param name="buffer">The buffer.</param> }
/// <param name="sequence">The sequence.</param>
/// <returns> Byte[] result = new Byte[buffer.Length - sequence.Length];
/// A byte array containing the results of encoding the specified set of characters. Array.Copy(buffer, 0, result, 0, result.Length);
/// </returns> return result;
/// <exception cref="ArgumentNullException">buffer.</exception> }
public static byte[] TrimEnd(this byte[] buffer, params byte[] sequence)
{ /// <summary>
if (buffer == null) /// Removes the specified sequence from the end and the start of the buffer
throw new ArgumentNullException(nameof(buffer)); /// if the buffer ends and/or starts with such sequence.
/// </summary>
if (buffer.EndsWith(sequence) == false) /// <param name="buffer">The buffer.</param>
return buffer.DeepClone(); /// <param name="sequence">The sequence.</param>
/// <returns>A byte array containing the results of encoding the specified set of characters.</returns>
var result = new byte[buffer.Length - sequence.Length]; public static Byte[] Trim(this Byte[] buffer, params Byte[] sequence) {
Array.Copy(buffer, 0, result, 0, result.Length); Byte[] trimStart = buffer.TrimStart(sequence);
return result; return trimStart.TrimEnd(sequence);
} }
/// <summary> /// <summary>
/// Removes the specified sequence from the end and the start of the buffer /// Determines if the specified buffer ends with the given sequence of bytes.
/// if the buffer ends and/or starts with such sequence. /// </summary>
/// </summary> /// <param name="buffer">The buffer.</param>
/// <param name="buffer">The buffer.</param> /// <param name="sequence">The sequence.</param>
/// <param name="sequence">The sequence.</param> /// <returns>
/// <returns>A byte array containing the results of encoding the specified set of characters.</returns> /// True if the specified buffer is ends; otherwise, false.
public static byte[] Trim(this byte[] buffer, params byte[] sequence) /// </returns>
{ /// <exception cref="ArgumentNullException">buffer.</exception>
var trimStart = buffer.TrimStart(sequence); public static Boolean EndsWith(this Byte[] buffer, params Byte[] sequence) {
return trimStart.TrimEnd(sequence); if(buffer == null) {
} throw new ArgumentNullException(nameof(buffer));
}
/// <summary>
/// Determines if the specified buffer ends with the given sequence of bytes. Int32 startIndex = buffer.Length - sequence.Length;
/// </summary> return buffer.GetIndexOf(sequence, startIndex) == startIndex;
/// <param name="buffer">The buffer.</param> }
/// <param name="sequence">The sequence.</param>
/// <returns> /// <summary>
/// True if the specified buffer is ends; otherwise, false. /// Determines if the specified buffer starts with the given sequence of bytes.
/// </returns> /// </summary>
/// <exception cref="ArgumentNullException">buffer.</exception> /// <param name="buffer">The buffer.</param>
public static bool EndsWith(this byte[] buffer, params byte[] sequence) /// <param name="sequence">The sequence.</param>
{ /// <returns><c>true</c> if the specified buffer starts; otherwise, <c>false</c>.</returns>
if (buffer == null) public static Boolean StartsWith(this Byte[] buffer, params Byte[] sequence) => buffer.GetIndexOf(sequence) == 0;
throw new ArgumentNullException(nameof(buffer));
/// <summary>
var startIndex = buffer.Length - sequence.Length; /// Determines whether the buffer contains the specified sequence.
return buffer.GetIndexOf(sequence, startIndex) == startIndex; /// </summary>
} /// <param name="buffer">The buffer.</param>
/// <param name="sequence">The sequence.</param>
/// <summary> /// <returns>
/// Determines if the specified buffer starts with the given sequence of bytes. /// <c>true</c> if [contains] [the specified sequence]; otherwise, <c>false</c>.
/// </summary> /// </returns>
/// <param name="buffer">The buffer.</param> public static Boolean Contains(this Byte[] buffer, params Byte[] sequence) => buffer.GetIndexOf(sequence) >= 0;
/// <param name="sequence">The sequence.</param>
/// <returns><c>true</c> if the specified buffer starts; otherwise, <c>false</c>.</returns> /// <summary>
public static bool StartsWith(this byte[] buffer, params byte[] sequence) => buffer.GetIndexOf(sequence) == 0; /// Determines whether the buffer exactly matches, byte by byte the specified sequence.
/// </summary>
/// <summary> /// <param name="buffer">The buffer.</param>
/// Determines whether the buffer contains the specified sequence. /// <param name="sequence">The sequence.</param>
/// </summary> /// <returns>
/// <param name="buffer">The buffer.</param> /// <c>true</c> if [is equal to] [the specified sequence]; otherwise, <c>false</c>.
/// <param name="sequence">The sequence.</param> /// </returns>
/// <returns> /// <exception cref="ArgumentNullException">buffer.</exception>
/// <c>true</c> if [contains] [the specified sequence]; otherwise, <c>false</c>. public static Boolean IsEqualTo(this Byte[] buffer, params Byte[] sequence) {
/// </returns> if(ReferenceEquals(buffer, sequence)) {
public static bool Contains(this byte[] buffer, params byte[] sequence) => buffer.GetIndexOf(sequence) >= 0; return true;
}
/// <summary>
/// Determines whether the buffer exactly matches, byte by byte the specified sequence. if(buffer == null) {
/// </summary> throw new ArgumentNullException(nameof(buffer));
/// <param name="buffer">The buffer.</param> }
/// <param name="sequence">The sequence.</param>
/// <returns> return buffer.Length == sequence.Length && buffer.GetIndexOf(sequence) == 0;
/// <c>true</c> if [is equal to] [the specified sequence]; otherwise, <c>false</c>. }
/// </returns>
/// <exception cref="ArgumentNullException">buffer.</exception> /// <summary>
public static bool IsEqualTo(this byte[] buffer, params byte[] sequence) /// Returns the first instance of the matched sequence based on the given offset.
{ /// If no matches are found then this method returns -1.
if (ReferenceEquals(buffer, sequence)) /// </summary>
return true; /// <param name="buffer">The buffer.</param>
/// <param name="sequence">The sequence.</param>
if (buffer == null) /// <param name="offset">The offset.</param>
throw new ArgumentNullException(nameof(buffer)); /// <returns>The index of the sequence.</returns>
/// <exception cref="ArgumentNullException">
return buffer.Length == sequence.Length && buffer.GetIndexOf(sequence) == 0; /// buffer
} /// or
/// sequence.
/// <summary> /// </exception>
/// Returns the first instance of the matched sequence based on the given offset. public static Int32 GetIndexOf(this Byte[] buffer, Byte[] sequence, Int32 offset = 0) {
/// If no matches are found then this method returns -1. if(buffer == null) {
/// </summary> throw new ArgumentNullException(nameof(buffer));
/// <param name="buffer">The buffer.</param> }
/// <param name="sequence">The sequence.</param>
/// <param name="offset">The offset.</param> if(sequence == null) {
/// <returns>The index of the sequence.</returns> throw new ArgumentNullException(nameof(sequence));
/// <exception cref="ArgumentNullException"> }
/// buffer
/// or if(sequence.Length == 0) {
/// sequence. return -1;
/// </exception> }
public static int GetIndexOf(this byte[] buffer, byte[] sequence, int offset = 0)
{ if(sequence.Length > buffer.Length) {
if (buffer == null) return -1;
throw new ArgumentNullException(nameof(buffer)); }
if (sequence == null)
throw new ArgumentNullException(nameof(sequence)); Int32 seqOffset = offset < 0 ? 0 : offset;
if (sequence.Length == 0)
return -1; Int32 matchedCount = 0;
if (sequence.Length > buffer.Length) for(Int32 i = seqOffset; i < buffer.Length; i++) {
return -1; if(buffer[i] == sequence[matchedCount]) {
matchedCount++;
var seqOffset = offset < 0 ? 0 : offset; } else {
matchedCount = 0;
var matchedCount = 0; }
for (var i = seqOffset; i < buffer.Length; i++)
{ if(matchedCount == sequence.Length) {
if (buffer[i] == sequence[matchedCount]) return i - (matchedCount - 1);
matchedCount++; }
else }
matchedCount = 0;
return -1;
if (matchedCount == sequence.Length) }
return i - (matchedCount - 1);
} /// <summary>
/// Appends the Memory Stream with the specified buffer.
return -1; /// </summary>
} /// <param name="stream">The stream.</param>
/// <param name="buffer">The buffer.</param>
/// <summary> /// <returns>
/// Appends the Memory Stream with the specified buffer. /// The same MemoryStream instance.
/// </summary> /// </returns>
/// <param name="stream">The stream.</param> /// <exception cref="ArgumentNullException">
/// <param name="buffer">The buffer.</param> /// stream
/// <returns> /// or
/// The same MemoryStream instance. /// buffer.
/// </returns> /// </exception>
/// <exception cref="ArgumentNullException"> public static MemoryStream Append(this MemoryStream stream, Byte[] buffer) {
/// stream if(stream == null) {
/// or throw new ArgumentNullException(nameof(stream));
/// buffer. }
/// </exception>
public static MemoryStream Append(this MemoryStream stream, byte[] buffer) if(buffer == null) {
{ throw new ArgumentNullException(nameof(buffer));
if (stream == null) }
throw new ArgumentNullException(nameof(stream));
stream.Write(buffer, 0, buffer.Length);
if (buffer == null) return stream;
throw new ArgumentNullException(nameof(buffer)); }
stream.Write(buffer, 0, buffer.Length); /// <summary>
return stream; /// Appends the Memory Stream with the specified buffer.
} /// </summary>
/// <param name="stream">The stream.</param>
/// <summary> /// <param name="buffer">The buffer.</param>
/// Appends the Memory Stream with the specified buffer. /// <returns>
/// </summary> /// Block of bytes to the current stream using data read from a buffer.
/// <param name="stream">The stream.</param> /// </returns>
/// <param name="buffer">The buffer.</param> /// <exception cref="ArgumentNullException">buffer.</exception>
/// <returns> public static MemoryStream Append(this MemoryStream stream, IEnumerable<Byte> buffer) => Append(stream, buffer?.ToArray());
/// Block of bytes to the current stream using data read from a buffer.
/// </returns> /// <summary>
/// <exception cref="ArgumentNullException">buffer.</exception> /// Appends the Memory Stream with the specified set of buffers.
public static MemoryStream Append(this MemoryStream stream, IEnumerable<byte> buffer) => Append(stream, buffer?.ToArray()); /// </summary>
/// <param name="stream">The stream.</param>
/// <summary> /// <param name="buffers">The buffers.</param>
/// Appends the Memory Stream with the specified set of buffers. /// <returns>
/// </summary> /// Block of bytes to the current stream using data read from a buffer.
/// <param name="stream">The stream.</param> /// </returns>
/// <param name="buffers">The buffers.</param> /// <exception cref="ArgumentNullException">buffers.</exception>
/// <returns> public static MemoryStream Append(this MemoryStream stream, IEnumerable<Byte[]> buffers) {
/// Block of bytes to the current stream using data read from a buffer. if(buffers == null) {
/// </returns> throw new ArgumentNullException(nameof(buffers));
/// <exception cref="ArgumentNullException">buffers.</exception> }
public static MemoryStream Append(this MemoryStream stream, IEnumerable<byte[]> buffers)
{ foreach(Byte[] buffer in buffers) {
if (buffers == null) _ = Append(stream, buffer);
throw new ArgumentNullException(nameof(buffers)); }
foreach (var buffer in buffers) return stream;
Append(stream, buffer); }
return stream; /// <summary>
} /// Converts an array of bytes into text with the specified encoding.
/// </summary>
/// <summary> /// <param name="buffer">The buffer.</param>
/// Converts an array of bytes into text with the specified encoding. /// <param name="encoding">The encoding.</param>
/// </summary> /// <returns>A <see cref="System.String" /> that contains the results of decoding the specified sequence of bytes.</returns>
/// <param name="buffer">The buffer.</param> public static String ToText(this IEnumerable<Byte> buffer, Encoding encoding) => encoding.GetString(buffer.ToArray());
/// <param name="encoding">The encoding.</param>
/// <returns>A <see cref="System.String" /> that contains the results of decoding the specified sequence of bytes.</returns> /// <summary>
public static string ToText(this IEnumerable<byte> buffer, Encoding encoding) => encoding.GetString(buffer.ToArray()); /// Converts an array of bytes into text with UTF8 encoding.
/// </summary>
/// <summary> /// <param name="buffer">The buffer.</param>
/// Converts an array of bytes into text with UTF8 encoding. /// <returns>A <see cref="System.String" /> that contains the results of decoding the specified sequence of bytes.</returns>
/// </summary> public static String ToText(this IEnumerable<Byte> buffer) => buffer.ToText(Encoding.UTF8);
/// <param name="buffer">The buffer.</param>
/// <returns>A <see cref="System.String" /> that contains the results of decoding the specified sequence of bytes.</returns> /// <summary>
public static string ToText(this IEnumerable<byte> buffer) => buffer.ToText(Encoding.UTF8); /// Reads the bytes asynchronous.
/// </summary>
/// <summary> /// <param name="stream">The stream.</param>
/// Reads the bytes asynchronous. /// <param name="length">The length.</param>
/// </summary> /// <param name="bufferLength">Length of the buffer.</param>
/// <param name="stream">The stream.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <param name="length">The length.</param> /// <returns>
/// <param name="bufferLength">Length of the buffer.</param> /// A byte array containing the results of encoding the specified set of characters.
/// <param name="cancellationToken">The cancellation token.</param> /// </returns>
/// <returns> /// <exception cref="ArgumentNullException">stream.</exception>
/// A byte array containing the results of encoding the specified set of characters. public static async Task<Byte[]> ReadBytesAsync(this Stream stream, Int64 length, Int32 bufferLength, CancellationToken cancellationToken = default) {
/// </returns> if(stream == null) {
/// <exception cref="ArgumentNullException">stream.</exception> throw new ArgumentNullException(nameof(stream));
public static async Task<byte[]> ReadBytesAsync(this Stream stream, long length, int bufferLength, CancellationToken cancellationToken = default) }
{
if (stream == null) using MemoryStream dest = new MemoryStream();
throw new ArgumentNullException(nameof(stream)); try {
Byte[] buff = new Byte[bufferLength];
using (var dest = new MemoryStream()) while(length > 0) {
{ if(length < bufferLength) {
try bufferLength = (Int32)length;
{ }
var buff = new byte[bufferLength];
while (length > 0) Int32 nread = await stream.ReadAsync(buff, 0, bufferLength, cancellationToken).ConfigureAwait(false);
{ if(nread == 0) {
if (length < bufferLength) break;
bufferLength = (int)length; }
var nread = await stream.ReadAsync(buff, 0, bufferLength, cancellationToken).ConfigureAwait(false); dest.Write(buff, 0, nread);
if (nread == 0) length -= nread;
break; }
} catch {
dest.Write(buff, 0, nread); // ignored
length -= nread; }
}
} return dest.ToArray();
catch }
{
// ignored /// <summary>
} /// Reads the bytes asynchronous.
/// </summary>
return dest.ToArray(); /// <param name="stream">The stream.</param>
} /// <param name="length">The length.</param>
} /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>
/// <summary> /// A byte array containing the results of encoding the specified set of characters.
/// Reads the bytes asynchronous. /// </returns>
/// </summary> /// <exception cref="ArgumentNullException">stream.</exception>
/// <param name="stream">The stream.</param> public static async Task<Byte[]> ReadBytesAsync(this Stream stream, Int32 length, CancellationToken cancellationToken = default) {
/// <param name="length">The length.</param> if(stream == null) {
/// <param name="cancellationToken">The cancellation token.</param> throw new ArgumentNullException(nameof(stream));
/// <returns> }
/// A byte array containing the results of encoding the specified set of characters.
/// </returns> Byte[] buff = new Byte[length];
/// <exception cref="ArgumentNullException">stream.</exception> Int32 offset = 0;
public static async Task<byte[]> ReadBytesAsync(this Stream stream, int length, CancellationToken cancellationToken = default) try {
{ while(length > 0) {
if (stream == null) Int32 nread = await stream.ReadAsync(buff, offset, length, cancellationToken).ConfigureAwait(false);
throw new ArgumentNullException(nameof(stream)); if(nread == 0) {
break;
var buff = new byte[length]; }
var offset = 0;
try offset += nread;
{ length -= nread;
while (length > 0) }
{ } catch {
var nread = await stream.ReadAsync(buff, offset, length, cancellationToken).ConfigureAwait(false); // ignored
if (nread == 0) }
break;
return new ArraySegment<Byte>(buff, 0, offset).ToArray();
offset += nread; }
length -= nread;
} private static String ToHex(Byte[] bytes, Boolean addPrefix, String format) {
} if(bytes == null) {
catch throw new ArgumentNullException(nameof(bytes));
{ }
// ignored
} StringBuilder sb = new StringBuilder(bytes.Length * 2);
return new ArraySegment<byte>(buff, 0, offset).ToArray(); foreach(Byte item in bytes) {
} _ = sb.Append(item.ToString(format, CultureInfo.InvariantCulture));
}
private static string ToHex(byte[] bytes, bool addPrefix, string format)
{ return $"{(addPrefix ? "0x" : String.Empty)}{sb}";
if (bytes == null) }
throw new ArgumentNullException(nameof(bytes)); }
var sb = new StringBuilder(bytes.Length * 2);
foreach (var item in bytes)
sb.Append(item.ToString(format, CultureInfo.InvariantCulture));
return $"{(addPrefix ? "0x" : string.Empty)}{sb}";
}
}
} }

View File

@ -1,21 +1,20 @@
using System; using System;
using Swan.Collections; using Swan.Collections;
namespace Swan namespace Swan {
{ /// <summary>
/// Provides extension methods for types implementing <see cref="IComponentCollection{T}"/>.
/// </summary>
public static class ComponentCollectionExtensions {
/// <summary> /// <summary>
/// Provides extension methods for types implementing <see cref="IComponentCollection{T}"/>. /// Adds the specified component to a collection, without giving it a name.
/// </summary> /// </summary>
public static class ComponentCollectionExtensions /// <typeparam name="T">The type of components in the collection.</typeparam>
{ /// <param name="this">The <see cref="IComponentCollection{T}" /> on which this method is called.</param>
/// <summary> /// <param name="component">The component to add.</param>
/// Adds the specified component to a collection, without giving it a name. /// <exception cref="NullReferenceException"><paramref name="this" /> is <see langword="null" />.</exception>
/// </summary> /// <seealso cref="IComponentCollection{T}.Add" />
/// <typeparam name="T">The type of components in the collection.</typeparam> public static void Add<T>(this IComponentCollection<T> @this, T component) => @this.Add(null, component);
/// <param name="this">The <see cref="IComponentCollection{T}" /> on which this method is called.</param> }
/// <param name="component">The component to add.</param>
/// <exception cref="NullReferenceException"><paramref name="this" /> is <see langword="null" />.</exception>
/// <seealso cref="IComponentCollection{T}.Add" />
public static void Add<T>(this IComponentCollection<T> @this, T component) => @this.Add(null, component);
}
} }

View File

@ -3,232 +3,224 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
namespace Swan namespace Swan {
{ /// <summary>
/// Provides extension methods for <see cref="DateTime"/>.
/// </summary>
public static class DateExtensions {
private static readonly Dictionary<String, Int32> DateRanges = new Dictionary<String, Int32>() {
{ "minute", 59},
{ "hour", 23},
{ "dayOfMonth", 31},
{ "month", 12},
{ "dayOfWeek", 6},
};
/// <summary> /// <summary>
/// Provides extension methods for <see cref="DateTime"/>. /// Converts the date to a YYYY-MM-DD string.
/// </summary> /// </summary>
public static class DateExtensions /// <param name="this">The <see cref="DateTime"/> on which this method is called.</param>
{ /// <returns>The concatenation of date.Year, date.Month and date.Day.</returns>
private static readonly Dictionary<string, int> DateRanges = new Dictionary<string, int>() public static String ToSortableDate(this DateTime @this) => $"{@this.Year:0000}-{@this.Month:00}-{@this.Day:00}";
{
{ "minute", 59}, /// <summary>
{ "hour", 23}, /// Converts the date to a YYYY-MM-DD HH:II:SS string.
{ "dayOfMonth", 31}, /// </summary>
{ "month", 12}, /// <param name="this">The <see cref="DateTime"/> on which this method is called.</param>
{ "dayOfWeek", 6}, /// <returns>The concatenation of date.Year, date.Month, date.Day, date.Hour, date.Minute and date.Second.</returns>
}; public static String ToSortableDateTime(this DateTime @this) => $"{@this.Year:0000}-{@this.Month:00}-{@this.Day:00} {@this.Hour:00}:{@this.Minute:00}:{@this.Second:00}";
/// <summary> /// <summary>
/// Converts the date to a YYYY-MM-DD string. /// Parses a YYYY-MM-DD and optionally it time part, HH:II:SS into a DateTime.
/// </summary> /// </summary>
/// <param name="this">The <see cref="DateTime"/> on which this method is called.</param> /// <param name="this">The sortable date.</param>
/// <returns>The concatenation of date.Year, date.Month and date.Day.</returns> /// <returns>
public static string ToSortableDate(this DateTime @this) /// A new instance of the DateTime structure to
=> $"{@this.Year:0000}-{@this.Month:00}-{@this.Day:00}"; /// the specified year, month, day, hour, minute and second.
/// </returns>
/// <summary> /// <exception cref="ArgumentNullException">sortableDate.</exception>
/// Converts the date to a YYYY-MM-DD HH:II:SS string. /// <exception cref="Exception">
/// </summary> /// Represents errors that occur during application execution.
/// <param name="this">The <see cref="DateTime"/> on which this method is called.</param> /// </exception>
/// <returns>The concatenation of date.Year, date.Month, date.Day, date.Hour, date.Minute and date.Second.</returns> /// <exception cref="ArgumentException">
public static string ToSortableDateTime(this DateTime @this) /// Unable to parse sortable date and time. - sortableDate.
=> $"{@this.Year:0000}-{@this.Month:00}-{@this.Day:00} {@this.Hour:00}:{@this.Minute:00}:{@this.Second:00}"; /// </exception>
public static DateTime ToDateTime(this String @this) {
/// <summary> if(String.IsNullOrWhiteSpace(@this)) {
/// Parses a YYYY-MM-DD and optionally it time part, HH:II:SS into a DateTime. throw new ArgumentNullException(nameof(@this));
/// </summary> }
/// <param name="this">The sortable date.</param>
/// <returns> Int32 hour = 0;
/// A new instance of the DateTime structure to Int32 minute = 0;
/// the specified year, month, day, hour, minute and second. Int32 second = 0;
/// </returns>
/// <exception cref="ArgumentNullException">sortableDate.</exception> String[] dateTimeParts = @this.Split(' ');
/// <exception cref="Exception">
/// Represents errors that occur during application execution. try {
/// </exception> if(dateTimeParts.Length != 1 && dateTimeParts.Length != 2) {
/// <exception cref="ArgumentException"> throw new Exception();
/// Unable to parse sortable date and time. - sortableDate. }
/// </exception>
public static DateTime ToDateTime(this string @this) String[] dateParts = dateTimeParts[0].Split('-');
{ if(dateParts.Length != 3) {
if (string.IsNullOrWhiteSpace(@this)) throw new Exception();
throw new ArgumentNullException(nameof(@this)); }
var hour = 0; Int32 year = Int32.Parse(dateParts[0]);
var minute = 0; Int32 month = Int32.Parse(dateParts[1]);
var second = 0; Int32 day = Int32.Parse(dateParts[2]);
var dateTimeParts = @this.Split(' '); if(dateTimeParts.Length > 1) {
String[] timeParts = dateTimeParts[1].Split(':');
try if(timeParts.Length != 3) {
{ throw new Exception();
if (dateTimeParts.Length != 1 && dateTimeParts.Length != 2) }
throw new Exception();
hour = Int32.Parse(timeParts[0]);
var dateParts = dateTimeParts[0].Split('-'); minute = Int32.Parse(timeParts[1]);
if (dateParts.Length != 3) throw new Exception(); second = Int32.Parse(timeParts[2]);
}
var year = int.Parse(dateParts[0]);
var month = int.Parse(dateParts[1]); return new DateTime(year, month, day, hour, minute, second);
var day = int.Parse(dateParts[2]); } catch(Exception) {
throw new ArgumentException("Unable to parse sortable date and time.", nameof(@this));
if (dateTimeParts.Length > 1) }
{ }
var timeParts = dateTimeParts[1].Split(':');
if (timeParts.Length != 3) throw new Exception(); /// <summary>
/// Creates a date range.
hour = int.Parse(timeParts[0]); /// </summary>
minute = int.Parse(timeParts[1]); /// <param name="startDate">The start date.</param>
second = int.Parse(timeParts[2]); /// <param name="endDate">The end date.</param>
} /// <returns>
/// A sequence of integral numbers within a specified date's range.
return new DateTime(year, month, day, hour, minute, second); /// </returns>
} public static IEnumerable<DateTime> DateRange(this DateTime startDate, DateTime endDate) => Enumerable.Range(0, (endDate - startDate).Days + 1).Select(d => startDate.AddDays(d));
catch (Exception)
{ /// <summary>
throw new ArgumentException("Unable to parse sortable date and time.", nameof(@this)); /// Rounds up a date to match a timespan.
} /// </summary>
} /// <param name="date">The datetime.</param>
/// <param name="timeSpan">The timespan to match.</param>
/// <summary> /// <returns>
/// Creates a date range. /// A new instance of the DateTime structure to the specified datetime and timespan ticks.
/// </summary> /// </returns>
/// <param name="startDate">The start date.</param> public static DateTime RoundUp(this DateTime date, TimeSpan timeSpan) => new DateTime(((date.Ticks + timeSpan.Ticks - 1) / timeSpan.Ticks) * timeSpan.Ticks);
/// <param name="endDate">The end date.</param>
/// <returns> /// <summary>
/// A sequence of integral numbers within a specified date's range. /// Get this datetime as a Unix epoch timestamp (seconds since Jan 1, 1970, midnight UTC).
/// </returns> /// </summary>
public static IEnumerable<DateTime> DateRange(this DateTime startDate, DateTime endDate) /// <param name="this">The <see cref="DateTime"/> on which this method is called.</param>
=> Enumerable.Range(0, (endDate - startDate).Days + 1).Select(d => startDate.AddDays(d)); /// <returns>Seconds since Unix epoch.</returns>
public static Int64 ToUnixEpochDate(this DateTime @this) => new DateTimeOffset(@this).ToUniversalTime().ToUnixTimeSeconds();
/// <summary>
/// Rounds up a date to match a timespan. /// <summary>
/// </summary> /// Compares a Date to another and returns a <c>DateTimeSpan</c>.
/// <param name="date">The datetime.</param> /// </summary>
/// <param name="timeSpan">The timespan to match.</param> /// <param name="dateStart">The date start.</param>
/// <returns> /// <param name="dateEnd">The date end.</param>
/// A new instance of the DateTime structure to the specified datetime and timespan ticks. /// <returns>A DateTimeSpan with the Years, Months, Days, Hours, Minutes, Seconds and Milliseconds between the dates.</returns>
/// </returns> public static DateTimeSpan GetDateTimeSpan(this DateTime dateStart, DateTime dateEnd) => DateTimeSpan.CompareDates(dateStart, dateEnd);
public static DateTime RoundUp(this DateTime date, TimeSpan timeSpan)
=> new DateTime(((date.Ticks + timeSpan.Ticks - 1) / timeSpan.Ticks) * timeSpan.Ticks); /// <summary>
/// Compare the Date elements(Months, Days, Hours, Minutes).
/// <summary> /// </summary>
/// Get this datetime as a Unix epoch timestamp (seconds since Jan 1, 1970, midnight UTC). /// <param name="this">The <see cref="DateTime"/> on which this method is called.</param>
/// </summary> /// <param name="minute">The minute (0-59).</param>
/// <param name="this">The <see cref="DateTime"/> on which this method is called.</param> /// <param name="hour">The hour. (0-23).</param>
/// <returns>Seconds since Unix epoch.</returns> /// <param name="dayOfMonth">The day of month. (1-31).</param>
public static long ToUnixEpochDate(this DateTime @this) => new DateTimeOffset(@this).ToUniversalTime().ToUnixTimeSeconds(); /// <param name="month">The month. (1-12).</param>
/// <param name="dayOfWeek">The day of week. (0-6)(Sunday = 0).</param>
/// <summary> /// <returns>Returns <c>true</c> if Months, Days, Hours and Minutes match, otherwise <c>false</c>.</returns>
/// Compares a Date to another and returns a <c>DateTimeSpan</c>. public static Boolean AsCronCanRun(this DateTime @this, Int32? minute = null, Int32? hour = null, Int32? dayOfMonth = null, Int32? month = null, Int32? dayOfWeek = null) {
/// </summary> List<Boolean?> results = new List<Boolean?> {
/// <param name="dateStart">The date start.</param> GetElementParts(minute, @this.Minute),
/// <param name="dateEnd">The date end.</param> GetElementParts(hour, @this.Hour),
/// <returns>A DateTimeSpan with the Years, Months, Days, Hours, Minutes, Seconds and Milliseconds between the dates.</returns> GetElementParts(dayOfMonth, @this.Day),
public static DateTimeSpan GetDateTimeSpan(this DateTime dateStart, DateTime dateEnd) GetElementParts(month, @this.Month),
=> DateTimeSpan.CompareDates(dateStart, dateEnd); GetElementParts(dayOfWeek, (Int32) @this.DayOfWeek),
};
/// <summary>
/// Compare the Date elements(Months, Days, Hours, Minutes). return results.Any(x => x != false);
/// </summary> }
/// <param name="this">The <see cref="DateTime"/> on which this method is called.</param>
/// <param name="minute">The minute (0-59).</param> /// <summary>
/// <param name="hour">The hour. (0-23).</param> /// Compare the Date elements(Months, Days, Hours, Minutes).
/// <param name="dayOfMonth">The day of month. (1-31).</param> /// </summary>
/// <param name="month">The month. (1-12).</param> /// <param name="this">The <see cref="DateTime"/> on which this method is called.</param>
/// <param name="dayOfWeek">The day of week. (0-6)(Sunday = 0).</param> /// <param name="minute">The minute (0-59).</param>
/// <returns>Returns <c>true</c> if Months, Days, Hours and Minutes match, otherwise <c>false</c>.</returns> /// <param name="hour">The hour. (0-23).</param>
public static bool AsCronCanRun(this DateTime @this, int? minute = null, int? hour = null, int? dayOfMonth = null, int? month = null, int? dayOfWeek = null) /// <param name="dayOfMonth">The day of month. (1-31).</param>
{ /// <param name="month">The month. (1-12).</param>
var results = new List<bool?> /// <param name="dayOfWeek">The day of week. (0-6)(Sunday = 0).</param>
{ /// <returns>Returns <c>true</c> if Months, Days, Hours and Minutes match, otherwise <c>false</c>.</returns>
GetElementParts(minute, @this.Minute), public static Boolean AsCronCanRun(this DateTime @this, String minute = "*", String hour = "*", String dayOfMonth = "*", String month = "*", String dayOfWeek = "*") {
GetElementParts(hour, @this.Hour), List<Boolean?> results = new List<Boolean?> {
GetElementParts(dayOfMonth, @this.Day), GetElementParts(minute, nameof(minute), @this.Minute),
GetElementParts(month, @this.Month), GetElementParts(hour, nameof(hour), @this.Hour),
GetElementParts(dayOfWeek, (int) @this.DayOfWeek), GetElementParts(dayOfMonth, nameof(dayOfMonth), @this.Day),
}; GetElementParts(month, nameof(month), @this.Month),
GetElementParts(dayOfWeek, nameof(dayOfWeek), (Int32) @this.DayOfWeek),
return results.Any(x => x != false); };
}
return results.Any(x => x != false);
/// <summary> }
/// Compare the Date elements(Months, Days, Hours, Minutes).
/// </summary> /// <summary>
/// <param name="this">The <see cref="DateTime"/> on which this method is called.</param> /// Converts a <see cref="DateTime"/> to the <see href="https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings#RFC1123">RFC1123 format</see>.
/// <param name="minute">The minute (0-59).</param> /// </summary>
/// <param name="hour">The hour. (0-23).</param> /// <param name="this">The <see cref="DateTime"/> on which this method is called.</param>
/// <param name="dayOfMonth">The day of month. (1-31).</param> /// <returns>The string representation of <paramref name="this"/> according to <see href="https://tools.ietf.org/html/rfc1123#page-54">RFC1123</see>.</returns>
/// <param name="month">The month. (1-12).</param> /// <remarks>
/// <param name="dayOfWeek">The day of week. (0-6)(Sunday = 0).</param> /// <para>If <paramref name="this"/> is not a UTC date / time, its UTC equivalent is converted, leaving <paramref name="this"/> unchanged.</para>
/// <returns>Returns <c>true</c> if Months, Days, Hours and Minutes match, otherwise <c>false</c>.</returns> /// </remarks>
public static bool AsCronCanRun(this DateTime @this, string minute = "*", string hour = "*", string dayOfMonth = "*", string month = "*", string dayOfWeek = "*") public static String ToRfc1123String(this DateTime @this) => @this.ToUniversalTime().ToString("R", CultureInfo.InvariantCulture);
{
var results = new List<bool?> private static Boolean? GetElementParts(Int32? status, Int32 value) => status.HasValue ? status.Value == value : (Boolean?)null;
{
GetElementParts(minute, nameof(minute), @this.Minute), private static Boolean? GetElementParts(String parts, String type, Int32 value) {
GetElementParts(hour, nameof(hour), @this.Hour), if(String.IsNullOrWhiteSpace(parts) || parts == "*") {
GetElementParts(dayOfMonth, nameof(dayOfMonth), @this.Day), return null;
GetElementParts(month, nameof(month), @this.Month), }
GetElementParts(dayOfWeek, nameof(dayOfWeek), (int) @this.DayOfWeek),
}; if(parts.Contains(",")) {
return parts.Split(',').Select(Int32.Parse).Contains(value);
return results.Any(x => x != false); }
}
Int32 stop = DateRanges[type];
/// <summary>
/// Converts a <see cref="DateTime"/> to the <see href="https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings#RFC1123">RFC1123 format</see>. if(parts.Contains("/")) {
/// </summary> Int32 multiple = Int32.Parse(parts.Split('/').Last());
/// <param name="this">The <see cref="DateTime"/> on which this method is called.</param> Int32 start = type == "dayOfMonth" || type == "month" ? 1 : 0;
/// <returns>The string representation of <paramref name="this"/> according to <see href="https://tools.ietf.org/html/rfc1123#page-54">RFC1123</see>.</returns>
/// <remarks> for(Int32 i = start; i <= stop; i += multiple) {
/// <para>If <paramref name="this"/> is not a UTC date / time, its UTC equivalent is converted, leaving <paramref name="this"/> unchanged.</para> if(i == value) {
/// </remarks> return true;
public static string ToRfc1123String(this DateTime @this) }
=> @this.ToUniversalTime().ToString("R", CultureInfo.InvariantCulture); }
private static bool? GetElementParts(int? status, int value) => status.HasValue ? status.Value == value : (bool?) null; return false;
}
private static bool? GetElementParts(string parts, string type, int value)
{ if(parts.Contains("-")) {
if (string.IsNullOrWhiteSpace(parts) || parts == "*") String[] range = parts.Split('-');
return null; Int32 start = Int32.Parse(range.First());
stop = Math.Max(stop, Int32.Parse(range.Last()));
if (parts.Contains(","))
{ if((type == "dayOfMonth" || type == "month") && start == 0) {
return parts.Split(',').Select(int.Parse).Contains(value); start = 1;
} }
var stop = DateRanges[type]; for(Int32 i = start; i <= stop; i++) {
if(i == value) {
if (parts.Contains("/")) return true;
{ }
var multiple = int.Parse(parts.Split('/').Last()); }
var start = type == "dayOfMonth" || type == "month" ? 1 : 0;
return false;
for (var i = start; i <= stop; i += multiple) }
if (i == value) return true;
return Int32.Parse(parts) == value;
return false; }
} }
if (parts.Contains("-"))
{
var range = parts.Split('-');
var start = int.Parse(range.First());
stop = Math.Max(stop, int.Parse(range.Last()));
if ((type == "dayOfMonth" || type == "month") && start == 0)
start = 1;
for (var i = start; i <= stop; i++)
if (i == value) return true;
return false;
}
return int.Parse(parts) == value;
}
}
} }

View File

@ -1,86 +1,86 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Swan namespace Swan {
{ /// <summary>
/// Extension methods.
/// </summary>
public static partial class Extensions {
/// <summary> /// <summary>
/// Extension methods. /// Gets the value if exists or default.
/// </summary> /// </summary>
public static partial class Extensions /// <typeparam name="TKey">The type of the key.</typeparam>
{ /// <typeparam name="TValue">The type of the value.</typeparam>
/// <summary> /// <param name="dict">The dictionary.</param>
/// Gets the value if exists or default. /// <param name="key">The key.</param>
/// </summary> /// <param name="defaultValue">The default value.</param>
/// <typeparam name="TKey">The type of the key.</typeparam> /// <returns>
/// <typeparam name="TValue">The type of the value.</typeparam> /// The value of the provided key or default.
/// <param name="dict">The dictionary.</param> /// </returns>
/// <param name="key">The key.</param> /// <exception cref="ArgumentNullException">dict.</exception>
/// <param name="defaultValue">The default value.</param> public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue defaultValue = default) {
/// <returns> if(dict == null) {
/// The value of the provided key or default. throw new ArgumentNullException(nameof(dict));
/// </returns> }
/// <exception cref="ArgumentNullException">dict.</exception>
public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue defaultValue = default) return dict.ContainsKey(key) ? dict[key] : defaultValue;
{ }
if (dict == null)
throw new ArgumentNullException(nameof(dict)); /// <summary>
/// Adds a key/value pair to the Dictionary if the key does not already exist.
return dict.ContainsKey(key) ? dict[key] : defaultValue; /// If the value is null, the key will not be updated.
} /// Based on <c>ConcurrentDictionary.GetOrAdd</c> method.
/// </summary>
/// <summary> /// <typeparam name="TKey">The type of the key.</typeparam>
/// Adds a key/value pair to the Dictionary if the key does not already exist. /// <typeparam name="TValue">The type of the value.</typeparam>
/// If the value is null, the key will not be updated. /// <param name="dict">The dictionary.</param>
/// Based on <c>ConcurrentDictionary.GetOrAdd</c> method. /// <param name="key">The key.</param>
/// </summary> /// <param name="valueFactory">The value factory.</param>
/// <typeparam name="TKey">The type of the key.</typeparam> /// <returns>
/// <typeparam name="TValue">The type of the value.</typeparam> /// The value for the key.
/// <param name="dict">The dictionary.</param> /// </returns>
/// <param name="key">The key.</param> /// <exception cref="ArgumentNullException">
/// <param name="valueFactory">The value factory.</param> /// dict
/// <returns> /// or
/// The value for the key. /// valueFactory.
/// </returns> /// </exception>
/// <exception cref="ArgumentNullException"> public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<TKey, TValue> valueFactory) {
/// dict if(dict == null) {
/// or throw new ArgumentNullException(nameof(dict));
/// valueFactory. }
/// </exception>
public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<TKey, TValue> valueFactory) if(valueFactory == null) {
{ throw new ArgumentNullException(nameof(valueFactory));
if (dict == null) }
throw new ArgumentNullException(nameof(dict));
if(!dict.ContainsKey(key)) {
if (valueFactory == null) TValue value = valueFactory(key);
throw new ArgumentNullException(nameof(valueFactory)); if(Equals(value, default)) {
return default;
if (!dict.ContainsKey(key)) }
{
var value = valueFactory(key); dict[key] = value;
if (Equals(value, default)) return default; }
dict[key] = value;
} return dict[key];
}
return dict[key];
} /// <summary>
/// Executes the item action for each element in the Dictionary.
/// <summary> /// </summary>
/// Executes the item action for each element in the Dictionary. /// <typeparam name="TKey">The type of the key.</typeparam>
/// </summary> /// <typeparam name="TValue">The type of the value.</typeparam>
/// <typeparam name="TKey">The type of the key.</typeparam> /// <param name="dict">The dictionary.</param>
/// <typeparam name="TValue">The type of the value.</typeparam> /// <param name="itemAction">The item action.</param>
/// <param name="dict">The dictionary.</param> /// <exception cref="ArgumentNullException">dict.</exception>
/// <param name="itemAction">The item action.</param> public static void ForEach<TKey, TValue>(this IDictionary<TKey, TValue> dict, Action<TKey, TValue> itemAction) {
/// <exception cref="ArgumentNullException">dict.</exception> if(dict == null) {
public static void ForEach<TKey, TValue>(this IDictionary<TKey, TValue> dict, Action<TKey, TValue> itemAction) throw new ArgumentNullException(nameof(dict));
{ }
if (dict == null)
throw new ArgumentNullException(nameof(dict)); foreach(KeyValuePair<TKey, TValue> kvp in dict) {
itemAction(kvp.Key, kvp.Value);
foreach (var kvp in dict) }
{ }
itemAction(kvp.Key, kvp.Value); }
}
}
}
} }

View File

@ -2,49 +2,31 @@
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
namespace Swan namespace Swan {
{ /// <summary>
/// Provides extension methods for <see cref="Exception"/>.
/// </summary>
public static class ExceptionExtensions {
/// <summary> /// <summary>
/// Provides extension methods for <see cref="Exception"/>. /// Returns a value that tells whether an <see cref="Exception"/> is of a type that
/// we better not catch and ignore.
/// </summary> /// </summary>
public static class ExceptionExtensions /// <param name="this">The exception being thrown.</param>
{ /// <returns><see langword="true"/> if <paramref name="this"/> is a critical exception;
/// <summary> /// otherwise, <see langword="false"/>.</returns>
/// Returns a value that tells whether an <see cref="Exception"/> is of a type that public static Boolean IsCriticalException(this Exception @this) => @this.IsCriticalExceptionCore() || (@this.InnerException?.IsCriticalException() ?? false) || @this is AggregateException aggregateException && aggregateException.InnerExceptions.Any(e => e.IsCriticalException());
/// we better not catch and ignore.
/// </summary> /// <summary>
/// <param name="this">The exception being thrown.</param> /// Returns a value that tells whether an <see cref="Exception"/> is of a type that
/// <returns><see langword="true"/> if <paramref name="this"/> is a critical exception; /// will likely cause application failure.
/// otherwise, <see langword="false"/>.</returns> /// </summary>
public static bool IsCriticalException(this Exception @this) /// <param name="this">The exception being thrown.</param>
=> @this.IsCriticalExceptionCore() /// <returns><see langword="true"/> if <paramref name="this"/> is a fatal exception;
|| (@this.InnerException?.IsCriticalException() ?? false) /// otherwise, <see langword="false"/>.</returns>
|| (@this is AggregateException aggregateException && aggregateException.InnerExceptions.Any(e => e.IsCriticalException())); public static Boolean IsFatalException(this Exception @this) => @this.IsFatalExceptionCore() || (@this.InnerException?.IsFatalException() ?? false) || @this is AggregateException aggregateException && aggregateException.InnerExceptions.Any(e => e.IsFatalException());
/// <summary> private static Boolean IsCriticalExceptionCore(this Exception @this) => IsFatalExceptionCore(@this) || @this is AppDomainUnloadedException || @this is BadImageFormatException || @this is CannotUnloadAppDomainException || @this is InvalidProgramException || @this is NullReferenceException;
/// Returns a value that tells whether an <see cref="Exception"/> is of a type that
/// will likely cause application failure. private static Boolean IsFatalExceptionCore(this Exception @this) => @this is StackOverflowException || @this is OutOfMemoryException || @this is ThreadAbortException || @this is AccessViolationException;
/// </summary> }
/// <param name="this">The exception being thrown.</param>
/// <returns><see langword="true"/> if <paramref name="this"/> is a fatal exception;
/// otherwise, <see langword="false"/>.</returns>
public static bool IsFatalException(this Exception @this)
=> @this.IsFatalExceptionCore()
|| (@this.InnerException?.IsFatalException() ?? false)
|| (@this is AggregateException aggregateException && aggregateException.InnerExceptions.Any(e => e.IsFatalException()));
private static bool IsCriticalExceptionCore(this Exception @this)
=> IsFatalExceptionCore(@this)
|| @this is AppDomainUnloadedException
|| @this is BadImageFormatException
|| @this is CannotUnloadAppDomainException
|| @this is InvalidProgramException
|| @this is NullReferenceException;
private static bool IsFatalExceptionCore(this Exception @this)
=> @this is StackOverflowException
|| @this is OutOfMemoryException
|| @this is ThreadAbortException
|| @this is AccessViolationException;
}
} }

View File

@ -2,178 +2,172 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Swan namespace Swan {
{ /// <summary>
/// Functional programming extension methods.
/// </summary>
public static class FunctionalExtensions {
/// <summary> /// <summary>
/// Functional programming extension methods. /// Whens the specified condition.
/// </summary> /// </summary>
public static class FunctionalExtensions /// <typeparam name="T">The type of IQueryable.</typeparam>
{ /// <param name="list">The list.</param>
/// <summary> /// <param name="condition">The condition.</param>
/// Whens the specified condition. /// <param name="fn">The function.</param>
/// </summary> /// <returns>
/// <typeparam name="T">The type of IQueryable.</typeparam> /// The IQueryable.
/// <param name="list">The list.</param> /// </returns>
/// <param name="condition">The condition.</param> /// <exception cref="ArgumentNullException">
/// <param name="fn">The function.</param> /// this
/// <returns> /// or
/// The IQueryable. /// condition
/// </returns> /// or
/// <exception cref="ArgumentNullException"> /// fn.
/// this /// </exception>
/// or public static IQueryable<T> When<T>(this IQueryable<T> list, Func<Boolean> condition, Func<IQueryable<T>, IQueryable<T>> fn) {
/// condition if(list == null) {
/// or throw new ArgumentNullException(nameof(list));
/// fn. }
/// </exception>
public static IQueryable<T> When<T>( if(condition == null) {
this IQueryable<T> list, throw new ArgumentNullException(nameof(condition));
Func<bool> condition, }
Func<IQueryable<T>, IQueryable<T>> fn)
{ if(fn == null) {
if (list == null) throw new ArgumentNullException(nameof(fn));
throw new ArgumentNullException(nameof(list)); }
if (condition == null) return condition() ? fn(list) : list;
throw new ArgumentNullException(nameof(condition)); }
if (fn == null) /// <summary>
throw new ArgumentNullException(nameof(fn)); /// Whens the specified condition.
/// </summary>
return condition() ? fn(list) : list; /// <typeparam name="T">The type of IEnumerable.</typeparam>
} /// <param name="list">The list.</param>
/// <param name="condition">The condition.</param>
/// <summary> /// <param name="fn">The function.</param>
/// Whens the specified condition. /// <returns>
/// </summary> /// The IEnumerable.
/// <typeparam name="T">The type of IEnumerable.</typeparam> /// </returns>
/// <param name="list">The list.</param> /// <exception cref="ArgumentNullException">
/// <param name="condition">The condition.</param> /// this
/// <param name="fn">The function.</param> /// or
/// <returns> /// condition
/// The IEnumerable. /// or
/// </returns> /// fn.
/// <exception cref="ArgumentNullException"> /// </exception>
/// this public static IEnumerable<T> When<T>(this IEnumerable<T> list, Func<Boolean> condition, Func<IEnumerable<T>, IEnumerable<T>> fn) {
/// or if(list == null) {
/// condition throw new ArgumentNullException(nameof(list));
/// or }
/// fn.
/// </exception> if(condition == null) {
public static IEnumerable<T> When<T>( throw new ArgumentNullException(nameof(condition));
this IEnumerable<T> list, }
Func<bool> condition,
Func<IEnumerable<T>, IEnumerable<T>> fn) if(fn == null) {
{ throw new ArgumentNullException(nameof(fn));
if (list == null) }
throw new ArgumentNullException(nameof(list));
return condition() ? fn(list) : list;
if (condition == null) }
throw new ArgumentNullException(nameof(condition));
/// <summary>
if (fn == null) /// Adds the value when the condition is true.
throw new ArgumentNullException(nameof(fn)); /// </summary>
/// <typeparam name="T">The type of IList element.</typeparam>
return condition() ? fn(list) : list; /// <param name="list">The list.</param>
} /// <param name="condition">The condition.</param>
/// <param name="value">The value.</param>
/// <summary> /// <returns>
/// Adds the value when the condition is true. /// The IList.
/// </summary> /// </returns>
/// <typeparam name="T">The type of IList element.</typeparam> /// <exception cref="ArgumentNullException">
/// <param name="list">The list.</param> /// this
/// <param name="condition">The condition.</param> /// or
/// <param name="value">The value.</param> /// condition
/// <returns> /// or
/// The IList. /// value.
/// </returns> /// </exception>
/// <exception cref="ArgumentNullException"> public static IList<T> AddWhen<T>(this IList<T> list, Func<Boolean> condition, Func<T> value) {
/// this if(list == null) {
/// or throw new ArgumentNullException(nameof(list));
/// condition }
/// or
/// value. if(condition == null) {
/// </exception> throw new ArgumentNullException(nameof(condition));
public static IList<T> AddWhen<T>( }
this IList<T> list,
Func<bool> condition, if(value == null) {
Func<T> value) throw new ArgumentNullException(nameof(value));
{ }
if (list == null)
throw new ArgumentNullException(nameof(list)); if(condition()) {
list.Add(value());
if (condition == null) }
throw new ArgumentNullException(nameof(condition));
return list;
if (value == null) }
throw new ArgumentNullException(nameof(value));
/// <summary>
if (condition()) /// Adds the value when the condition is true.
list.Add(value()); /// </summary>
/// <typeparam name="T">The type of IList element.</typeparam>
return list; /// <param name="list">The list.</param>
} /// <param name="condition">if set to <c>true</c> [condition].</param>
/// <param name="value">The value.</param>
/// <summary> /// <returns>
/// Adds the value when the condition is true. /// The IList.
/// </summary> /// </returns>
/// <typeparam name="T">The type of IList element.</typeparam> /// <exception cref="ArgumentNullException">list.</exception>
/// <param name="list">The list.</param> public static IList<T> AddWhen<T>(this IList<T> list, Boolean condition, T value) {
/// <param name="condition">if set to <c>true</c> [condition].</param> if(list == null) {
/// <param name="value">The value.</param> throw new ArgumentNullException(nameof(list));
/// <returns> }
/// The IList.
/// </returns> if(condition) {
/// <exception cref="ArgumentNullException">list.</exception> list.Add(value);
public static IList<T> AddWhen<T>( }
this IList<T> list,
bool condition, return list;
T value) }
{
if (list == null) /// <summary>
throw new ArgumentNullException(nameof(list)); /// Adds the range when the condition is true.
/// </summary>
if (condition) /// <typeparam name="T">The type of List element.</typeparam>
list.Add(value); /// <param name="list">The list.</param>
/// <param name="condition">The condition.</param>
return list; /// <param name="value">The value.</param>
} /// <returns>
/// The List.
/// <summary> /// </returns>
/// Adds the range when the condition is true. /// <exception cref="ArgumentNullException">
/// </summary> /// this
/// <typeparam name="T">The type of List element.</typeparam> /// or
/// <param name="list">The list.</param> /// condition
/// <param name="condition">The condition.</param> /// or
/// <param name="value">The value.</param> /// value.
/// <returns> /// </exception>
/// The List. public static List<T> AddRangeWhen<T>(this List<T> list, Func<Boolean> condition, Func<IEnumerable<T>> value) {
/// </returns> if(list == null) {
/// <exception cref="ArgumentNullException"> throw new ArgumentNullException(nameof(list));
/// this }
/// or
/// condition if(condition == null) {
/// or throw new ArgumentNullException(nameof(condition));
/// value. }
/// </exception>
public static List<T> AddRangeWhen<T>( if(value == null) {
this List<T> list, throw new ArgumentNullException(nameof(value));
Func<bool> condition, }
Func<IEnumerable<T>> value)
{ if(condition()) {
if (list == null) list.AddRange(value());
throw new ArgumentNullException(nameof(list)); }
if (condition == null) return list;
throw new ArgumentNullException(nameof(condition)); }
}
if (value == null)
throw new ArgumentNullException(nameof(value));
if (condition())
list.AddRange(value());
return list;
}
}
} }

View File

@ -1,455 +1,411 @@
using System; #nullable enable
using System;
using System.Collections; using System.Collections;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Swan.Configuration; using Swan.Configuration;
using Swan.Reflection; using Swan.Reflection;
namespace Swan namespace Swan {
{ /// <summary>
/// Provides various extension methods for Reflection and Types.
/// </summary>
public static class ReflectionExtensions {
private static readonly Lazy<ConcurrentDictionary<Tuple<Boolean, PropertyInfo>, Func<Object, Object>>> CacheGetMethods = new Lazy<ConcurrentDictionary<Tuple<Boolean, PropertyInfo>, Func<Object, Object>>>(() => new ConcurrentDictionary<Tuple<Boolean, PropertyInfo>, Func<Object, Object>>(), true);
private static readonly Lazy<ConcurrentDictionary<Tuple<Boolean, PropertyInfo>, Action<Object, Object[]>>> CacheSetMethods = new Lazy<ConcurrentDictionary<Tuple<Boolean, PropertyInfo>, Action<Object, Object[]>>>(() => new ConcurrentDictionary<Tuple<Boolean, PropertyInfo>, Action<Object, Object[]>>(), true);
#region Assembly Extensions
/// <summary> /// <summary>
/// Provides various extension methods for Reflection and Types. /// Gets all types within an assembly in a safe manner.
/// </summary> /// </summary>
public static class ReflectionExtensions /// <param name="assembly">The assembly.</param>
{ /// <returns>
private static readonly Lazy<ConcurrentDictionary<Tuple<bool, PropertyInfo>, Func<object, object>>> CacheGetMethods = /// Array of Type objects representing the types specified by an assembly.
new Lazy<ConcurrentDictionary<Tuple<bool, PropertyInfo>, Func<object, object>>>(() => new ConcurrentDictionary<Tuple<bool, PropertyInfo>, Func<object, object>>(), true); /// </returns>
/// <exception cref="ArgumentNullException">assembly.</exception>
private static readonly Lazy<ConcurrentDictionary<Tuple<bool, PropertyInfo>, Action<object, object[]>>> CacheSetMethods = public static IEnumerable<Type> GetAllTypes(this Assembly assembly) {
new Lazy<ConcurrentDictionary<Tuple<bool, PropertyInfo>, Action<object, object[]>>>(() => new ConcurrentDictionary<Tuple<bool, PropertyInfo>, Action<object, object[]>>(), true); if(assembly == null) {
throw new ArgumentNullException(nameof(assembly));
#region Assembly Extensions }
/// <summary> try {
/// Gets all types within an assembly in a safe manner. return assembly.GetTypes();
/// </summary> } catch(ReflectionTypeLoadException e) {
/// <param name="assembly">The assembly.</param> return e.Types.Where(t => t != null);
/// <returns> }
/// Array of Type objects representing the types specified by an assembly. }
/// </returns>
/// <exception cref="ArgumentNullException">assembly.</exception> #endregion
public static IEnumerable<Type> GetAllTypes(this Assembly assembly)
{ #region Type Extensions
if (assembly == null)
throw new ArgumentNullException(nameof(assembly)); /// <summary>
/// The closest programmatic equivalent of default(T).
try /// </summary>
{ /// <param name="type">The type.</param>
return assembly.GetTypes(); /// <returns>
} /// Default value of this type.
catch (ReflectionTypeLoadException e) /// </returns>
{ /// <exception cref="ArgumentNullException">type.</exception>
return e.Types.Where(t => t != null); public static Object? GetDefault(this Type type) {
} if(type == null) {
} throw new ArgumentNullException(nameof(type));
}
#endregion
return type.IsValueType ? Activator.CreateInstance(type) : default;
#region Type Extensions }
/// <summary> /// <summary>
/// The closest programmatic equivalent of default(T). /// Determines whether this type is compatible with ICollection.
/// </summary> /// </summary>
/// <param name="type">The type.</param> /// <param name="sourceType">The type.</param>
/// <returns> /// <returns>
/// Default value of this type. /// <c>true</c> if the specified source type is collection; otherwise, <c>false</c>.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">type.</exception> /// <exception cref="ArgumentNullException">sourceType.</exception>
public static object? GetDefault(this Type type) public static Boolean IsCollection(this Type sourceType) {
{ if(sourceType == null) {
if (type == null) throw new ArgumentNullException(nameof(sourceType));
throw new ArgumentNullException(nameof(type)); }
return type.IsValueType ? Activator.CreateInstance(type) : default; return sourceType != typeof(String) && typeof(IEnumerable).IsAssignableFrom(sourceType);
} }
/// <summary> /// <summary>
/// Determines whether this type is compatible with ICollection. /// Gets a method from a type given the method name, binding flags, generic types and parameter types.
/// </summary> /// </summary>
/// <param name="sourceType">The type.</param> /// <param name="type">Type of the source.</param>
/// <returns> /// <param name="bindingFlags">The binding flags.</param>
/// <c>true</c> if the specified source type is collection; otherwise, <c>false</c>. /// <param name="methodName">Name of the method.</param>
/// </returns> /// <param name="genericTypes">The generic types.</param>
/// <exception cref="ArgumentNullException">sourceType.</exception> /// <param name="parameterTypes">The parameter types.</param>
public static bool IsCollection(this Type sourceType) /// <returns>
{ /// An object that represents the method with the specified name.
if (sourceType == null) /// </returns>
throw new ArgumentNullException(nameof(sourceType)); /// <exception cref="System.Reflection.AmbiguousMatchException">
/// The exception that is thrown when binding to a member results in more than one member matching the
return sourceType != typeof(string) && /// binding criteria. This class cannot be inherited.
typeof(IEnumerable).IsAssignableFrom(sourceType); /// </exception>
} public static MethodInfo GetMethod(this Type type, BindingFlags bindingFlags, String methodName, Type[] genericTypes, Type[] parameterTypes) {
if(type == null) {
/// <summary> throw new ArgumentNullException(nameof(type));
/// Gets a method from a type given the method name, binding flags, generic types and parameter types. }
/// </summary>
/// <param name="type">Type of the source.</param> if(methodName == null) {
/// <param name="bindingFlags">The binding flags.</param> throw new ArgumentNullException(nameof(methodName));
/// <param name="methodName">Name of the method.</param> }
/// <param name="genericTypes">The generic types.</param>
/// <param name="parameterTypes">The parameter types.</param> if(genericTypes == null) {
/// <returns> throw new ArgumentNullException(nameof(genericTypes));
/// An object that represents the method with the specified name. }
/// </returns>
/// <exception cref="System.Reflection.AmbiguousMatchException"> if(parameterTypes == null) {
/// The exception that is thrown when binding to a member results in more than one member matching the throw new ArgumentNullException(nameof(parameterTypes));
/// binding criteria. This class cannot be inherited. }
/// </exception>
public static MethodInfo GetMethod( List<MethodInfo> methods = type.GetMethods(bindingFlags)
this Type type, .Where(mi => String.Equals(methodName, mi.Name, StringComparison.Ordinal))
BindingFlags bindingFlags, .Where(mi => mi.ContainsGenericParameters)
string methodName, .Where(mi => mi.GetGenericArguments().Length == genericTypes.Length)
Type[] genericTypes, .Where(mi => mi.GetParameters().Length == parameterTypes.Length).Select(mi => mi.MakeGenericMethod(genericTypes))
Type[] parameterTypes) .Where(mi => mi.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameterTypes)).ToList();
{
if (type == null) return methods.Count > 1 ? throw new AmbiguousMatchException() : methods.FirstOrDefault();
throw new ArgumentNullException(nameof(type)); }
if (methodName == null) /// <summary>
throw new ArgumentNullException(nameof(methodName)); /// Determines whether [is i enumerable request].
/// </summary>
if (genericTypes == null) /// <param name="type">The type.</param>
throw new ArgumentNullException(nameof(genericTypes)); /// <returns>
/// <c>true</c> if [is i enumerable request] [the specified type]; otherwise, <c>false</c>.
if (parameterTypes == null) /// </returns>
throw new ArgumentNullException(nameof(parameterTypes)); /// <exception cref="ArgumentNullException">type.</exception>
public static Boolean IsIEnumerable(this Type type) => type == null ? throw new ArgumentNullException(nameof(type)) : type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>);
var methods = type
.GetMethods(bindingFlags) #endregion
.Where(mi => string.Equals(methodName, mi.Name, StringComparison.Ordinal))
.Where(mi => mi.ContainsGenericParameters) /// <summary>
.Where(mi => mi.GetGenericArguments().Length == genericTypes.Length) /// Tries to parse using the basic types.
.Where(mi => mi.GetParameters().Length == parameterTypes.Length) /// </summary>
.Select(mi => mi.MakeGenericMethod(genericTypes)) /// <param name="type">The type.</param>
.Where(mi => mi.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameterTypes)) /// <param name="value">The value.</param>
.ToList(); /// <param name="result">The result.</param>
/// <returns>
return methods.Count > 1 ? throw new AmbiguousMatchException() : methods.FirstOrDefault(); /// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
} /// </returns>
/// <exception cref="ArgumentNullException">type</exception>
/// <summary> public static Boolean TryParseBasicType(this Type type, Object value, out Object? result) {
/// Determines whether [is i enumerable request]. if(type == null) {
/// </summary> throw new ArgumentNullException(nameof(type));
/// <param name="type">The type.</param> }
/// <returns>
/// <c>true</c> if [is i enumerable request] [the specified type]; otherwise, <c>false</c>. if(type != typeof(Boolean)) {
/// </returns> return TryParseBasicType(type, value.ToStringInvariant(), out result);
/// <exception cref="ArgumentNullException">type.</exception> }
public static bool IsIEnumerable(this Type type)
=> type == null result = value.ToBoolean();
? throw new ArgumentNullException(nameof(type)) return true;
: type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>); }
#endregion /// <summary>
/// Tries to parse using the basic types.
/// <summary> /// </summary>
/// Tries to parse using the basic types. /// <param name="type">The type.</param>
/// </summary> /// <param name="value">The value.</param>
/// <param name="type">The type.</param> /// <param name="result">The result.</param>
/// <param name="value">The value.</param> /// <returns>
/// <param name="result">The result.</param> /// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
/// <returns> /// </returns>
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>. /// <exception cref="ArgumentNullException">type</exception>
/// </returns> public static Boolean TryParseBasicType(this Type type, String value, out Object? result) {
/// <exception cref="ArgumentNullException">type</exception> if(type == null) {
public static bool TryParseBasicType(this Type type, object value, out object? result) throw new ArgumentNullException(nameof(type));
{ }
if (type == null)
throw new ArgumentNullException(nameof(type)); result = null;
if (type != typeof(bool)) return Definitions.BasicTypesInfo.Value.ContainsKey(type) && Definitions.BasicTypesInfo.Value[type].TryParse(value, out result);
return TryParseBasicType(type, value.ToStringInvariant(), out result); }
result = value.ToBoolean(); /// <summary>
return true; /// Tries the type of the set basic value to a property.
} /// </summary>
/// <param name="propertyInfo">The property information.</param>
/// <summary> /// <param name="value">The value.</param>
/// Tries to parse using the basic types. /// <param name="target">The object.</param>
/// </summary> /// <returns>
/// <param name="type">The type.</param> /// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
/// <param name="value">The value.</param> /// </returns>
/// <param name="result">The result.</param> /// <exception cref="ArgumentNullException">propertyInfo.</exception>
/// <returns> public static Boolean TrySetBasicType(this PropertyInfo propertyInfo, Object value, Object target) {
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>. if(propertyInfo == null) {
/// </returns> throw new ArgumentNullException(nameof(propertyInfo));
/// <exception cref="ArgumentNullException">type</exception> }
public static bool TryParseBasicType(this Type type, string value, out object? result)
{ try {
if (type == null) if(propertyInfo.PropertyType.TryParseBasicType(value, out Object? propertyValue)) {
throw new ArgumentNullException(nameof(type)); propertyInfo.SetValue(target, propertyValue);
return true;
result = null; }
} catch {
return Definitions.BasicTypesInfo.Value.ContainsKey(type) && Definitions.BasicTypesInfo.Value[type].TryParse(value, out result); // swallow
} }
/// <summary> return false;
/// Tries the type of the set basic value to a property. }
/// </summary>
/// <param name="propertyInfo">The property information.</param> /// <summary>
/// <param name="value">The value.</param> /// Tries the type of the set to an array a basic type.
/// <param name="target">The object.</param> /// </summary>
/// <returns> /// <param name="type">The type.</param>
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>. /// <param name="value">The value.</param>
/// </returns> /// <param name="target">The array.</param>
/// <exception cref="ArgumentNullException">propertyInfo.</exception> /// <param name="index">The index.</param>
public static bool TrySetBasicType(this PropertyInfo propertyInfo, object value, object target) /// <returns>
{ /// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
if (propertyInfo == null) /// </returns>
throw new ArgumentNullException(nameof(propertyInfo)); /// <exception cref="ArgumentNullException">type</exception>
public static Boolean TrySetArrayBasicType(this Type type, Object value, Array target, Int32 index) {
try if(type == null) {
{ throw new ArgumentNullException(nameof(type));
if (propertyInfo.PropertyType.TryParseBasicType(value, out var propertyValue)) }
{
propertyInfo.SetValue(target, propertyValue); if(target == null) {
return true; return false;
} }
}
catch try {
{ if(value == null) {
// swallow target.SetValue(null, index);
} return true;
}
return false;
} if(type.TryParseBasicType(value, out Object? propertyValue)) {
target.SetValue(propertyValue, index);
/// <summary> return true;
/// Tries the type of the set to an array a basic type. }
/// </summary>
/// <param name="type">The type.</param> if(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) {
/// <param name="value">The value.</param> target.SetValue(null, index);
/// <param name="target">The array.</param> return true;
/// <param name="index">The index.</param> }
/// <returns> } catch {
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>. // swallow
/// </returns> }
/// <exception cref="ArgumentNullException">type</exception>
public static bool TrySetArrayBasicType(this Type type, object value, Array target, int index) return false;
{ }
if (type == null)
throw new ArgumentNullException(nameof(type)); /// <summary>
/// Tries to set a property array with another array.
if (target == null) /// </summary>
return false; /// <param name="propertyInfo">The property.</param>
/// <param name="value">The value.</param>
try /// <param name="obj">The object.</param>
{ /// <returns>
if (value == null) /// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
{ /// </returns>
target.SetValue(null, index); /// <exception cref="ArgumentNullException">propertyInfo.</exception>
return true; public static Boolean TrySetArray(this PropertyInfo propertyInfo, IEnumerable<Object>? value, Object obj) {
} if(propertyInfo == null) {
throw new ArgumentNullException(nameof(propertyInfo));
if (type.TryParseBasicType(value, out var propertyValue)) }
{
target.SetValue(propertyValue, index); Type? elementType = propertyInfo.PropertyType.GetElementType();
return true;
} if(elementType == null || value == null) {
return false;
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) }
{
target.SetValue(null, index); Array targetArray = Array.CreateInstance(elementType, value.Count());
return true;
} Int32 i = 0;
}
catch foreach(Object sourceElement in value) {
{ Boolean result = elementType.TrySetArrayBasicType(sourceElement, targetArray, i++);
// swallow
} if(!result) {
return false;
return false; }
} }
/// <summary> propertyInfo.SetValue(obj, targetArray);
/// Tries to set a property array with another array.
/// </summary> return true;
/// <param name="propertyInfo">The property.</param> }
/// <param name="value">The value.</param>
/// <param name="obj">The object.</param> /// <summary>
/// <returns> /// Gets property actual value or <c>PropertyDisplayAttribute.DefaultValue</c> if presented.
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>. ///
/// </returns> /// If the <c>PropertyDisplayAttribute.Format</c> value is presented, the property value
/// <exception cref="ArgumentNullException">propertyInfo.</exception> /// will be formatted accordingly.
public static bool TrySetArray(this PropertyInfo propertyInfo, IEnumerable<object>? value, object obj) ///
{ /// If the object contains a null value, a empty string will be returned.
if (propertyInfo == null) /// </summary>
throw new ArgumentNullException(nameof(propertyInfo)); /// <param name="propertyInfo">The property information.</param>
/// <param name="target">The object.</param>
var elementType = propertyInfo.PropertyType.GetElementType(); /// <returns>The property value or null.</returns>
/// <exception cref="ArgumentNullException">propertyInfo.</exception>
if (elementType == null || value == null) public static String? ToFormattedString(this PropertyInfo propertyInfo, Object target) {
return false; if(propertyInfo == null) {
throw new ArgumentNullException(nameof(propertyInfo));
var targetArray = Array.CreateInstance(elementType, value.Count()); }
var i = 0; try {
Object? value = propertyInfo.GetValue(target);
foreach (var sourceElement in value) PropertyDisplayAttribute attr = AttributeCache.DefaultCache.Value.RetrieveOne<PropertyDisplayAttribute>(propertyInfo);
{
var result = elementType.TrySetArrayBasicType(sourceElement, targetArray, i++); if(attr == null) {
return value?.ToString() ?? String.Empty;
if (!result) return false; }
}
Object valueToFormat = value ?? attr.DefaultValue;
propertyInfo.SetValue(obj, targetArray);
return String.IsNullOrEmpty(attr.Format) ? (valueToFormat?.ToString() ?? String.Empty) : ConvertObjectAndFormat(propertyInfo.PropertyType, valueToFormat, attr.Format);
return true; } catch {
} return null;
}
/// <summary> }
/// Gets property actual value or <c>PropertyDisplayAttribute.DefaultValue</c> if presented.
/// /// <summary>
/// If the <c>PropertyDisplayAttribute.Format</c> value is presented, the property value /// Gets a MethodInfo from a Property Get method.
/// will be formatted accordingly. /// </summary>
/// /// <param name="propertyInfo">The property information.</param>
/// If the object contains a null value, a empty string will be returned. /// <param name="nonPublic">if set to <c>true</c> [non public].</param>
/// </summary> /// <returns>
/// <param name="propertyInfo">The property information.</param> /// The cached MethodInfo.
/// <param name="target">The object.</param> /// </returns>
/// <returns>The property value or null.</returns> public static Func<Object, Object>? GetCacheGetMethod(this PropertyInfo propertyInfo, Boolean nonPublic = false) {
/// <exception cref="ArgumentNullException">propertyInfo.</exception> Tuple<Boolean, PropertyInfo> key = Tuple.Create(!nonPublic, propertyInfo);
public static string? ToFormattedString(this PropertyInfo propertyInfo, object target)
{ // TODO: Fix public logic
if (propertyInfo == null) return !nonPublic && !CacheGetMethods.Value.ContainsKey(key) && !propertyInfo.GetGetMethod(true)!.IsPublic ? null : CacheGetMethods.Value.GetOrAdd(key, x => y => x.Item2.GetGetMethod(nonPublic)?.Invoke(y, null)!);
throw new ArgumentNullException(nameof(propertyInfo)); //y => x => y.Item2.CreatePropertyProxy().GetValue(x));
}
try
{ /// <summary>
var value = propertyInfo.GetValue(target); /// Gets a MethodInfo from a Property Set method.
var attr = AttributeCache.DefaultCache.Value.RetrieveOne<PropertyDisplayAttribute>(propertyInfo); /// </summary>
/// <param name="propertyInfo">The property information.</param>
if (attr == null) return value?.ToString() ?? string.Empty; /// <param name="nonPublic">if set to <c>true</c> [non public].</param>
/// <returns>
var valueToFormat = value ?? attr.DefaultValue; /// The cached MethodInfo.
/// </returns>
return string.IsNullOrEmpty(attr.Format) public static Action<Object, Object[]>? GetCacheSetMethod(this PropertyInfo propertyInfo, Boolean nonPublic = false) {
? (valueToFormat?.ToString() ?? string.Empty) Tuple<Boolean, PropertyInfo> key = Tuple.Create(!nonPublic, propertyInfo);
: ConvertObjectAndFormat(propertyInfo.PropertyType, valueToFormat, attr.Format);
} 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));
catch //y => (obj, args) => y.Item2.CreatePropertyProxy().SetValue(obj, args));
{ }
return null;
} /// <summary>
} /// Convert a string to a boolean.
/// </summary>
/// <summary> /// <param name="str">The string.</param>
/// Gets a MethodInfo from a Property Get method. /// <returns>
/// </summary> /// <c>true</c> if the string represents a valid truly value, otherwise <c>false</c>.
/// <param name="propertyInfo">The property information.</param> /// </returns>
/// <param name="nonPublic">if set to <c>true</c> [non public].</param> public static Boolean ToBoolean(this String str) {
/// <returns> try {
/// The cached MethodInfo. return Convert.ToBoolean(str);
/// </returns> } catch(FormatException) {
public static Func<object, object>? GetCacheGetMethod(this PropertyInfo propertyInfo, bool nonPublic = false) // ignored
{ }
var key = Tuple.Create(!nonPublic, propertyInfo);
try {
// TODO: Fix public logic return Convert.ToBoolean(Convert.ToInt32(str));
return !nonPublic && !CacheGetMethods.Value.ContainsKey(key) && !propertyInfo.GetGetMethod(true).IsPublic } catch {
? null // ignored
: CacheGetMethods.Value }
.GetOrAdd(key,
x => y => x.Item2.GetGetMethod(nonPublic).Invoke(y, null)); return false;
//y => x => y.Item2.CreatePropertyProxy().GetValue(x)); }
}
/// <summary>
/// <summary> /// Creates a property proxy that stores getter and setter delegates.
/// Gets a MethodInfo from a Property Set method. /// </summary>
/// </summary> /// <param name="this">The property information.</param>
/// <param name="propertyInfo">The property information.</param> /// <returns>
/// <param name="nonPublic">if set to <c>true</c> [non public].</param> /// The property proxy.
/// <returns> /// </returns>
/// The cached MethodInfo. /// <exception cref="ArgumentNullException">this.</exception>
/// </returns> public static IPropertyProxy? CreatePropertyProxy(this PropertyInfo @this) {
public static Action<object, object[]>? GetCacheSetMethod(this PropertyInfo propertyInfo, bool nonPublic = false) if(@this == null) {
{ throw new ArgumentNullException(nameof(@this));
var key = Tuple.Create(!nonPublic, propertyInfo); }
return !nonPublic && !CacheSetMethods.Value.ContainsKey(key) && !propertyInfo.GetSetMethod(true).IsPublic Type genericType = typeof(PropertyProxy<,>).MakeGenericType(@this.DeclaringType!, @this.PropertyType);
? null
: CacheSetMethods.Value return Activator.CreateInstance(genericType, @this) as IPropertyProxy;
.GetOrAdd(key, }
x => (obj, args) => x.Item2.GetSetMethod(nonPublic).Invoke(obj, args));
//y => (obj, args) => y.Item2.CreatePropertyProxy().SetValue(obj, args)); /// <summary>
} /// Convert a object to a boolean.
/// </summary>
/// <summary> /// <param name="value">The value.</param>
/// Convert a string to a boolean. /// <returns>
/// </summary> /// <c>true</c> if the string represents a valid truly value, otherwise <c>false</c>.
/// <param name="str">The string.</param> /// </returns>
/// <returns> public static Boolean ToBoolean(this Object value) => value.ToStringInvariant().ToBoolean();
/// <c>true</c> if the string represents a valid truly value, otherwise <c>false</c>.
/// </returns> private static String ConvertObjectAndFormat(Type propertyType, Object value, String format) =>
public static bool ToBoolean(this string str) propertyType == typeof(DateTime) || propertyType == typeof(DateTime?)
{ ? Convert.ToDateTime(value, CultureInfo.InvariantCulture).ToString(format)
try : propertyType == typeof(Int32) || propertyType == typeof(Int32?)
{ ? Convert.ToInt32(value, CultureInfo.InvariantCulture).ToString(format)
return Convert.ToBoolean(str); : propertyType == typeof(Decimal) || propertyType == typeof(Decimal?)
} ? Convert.ToDecimal(value, CultureInfo.InvariantCulture).ToString(format)
catch (FormatException) : propertyType == typeof(Double) || propertyType == typeof(Double?)
{ ? Convert.ToDouble(value, CultureInfo.InvariantCulture).ToString(format)
// ignored : propertyType == typeof(Byte) || propertyType == typeof(Byte?)
} ? Convert.ToByte(value, CultureInfo.InvariantCulture).ToString(format)
: value?.ToString() ?? String.Empty;
try }
{
return Convert.ToBoolean(Convert.ToInt32(str));
}
catch
{
// ignored
}
return false;
}
/// <summary>
/// Creates a property proxy that stores getter and setter delegates.
/// </summary>
/// <param name="this">The property information.</param>
/// <returns>
/// The property proxy.
/// </returns>
/// <exception cref="ArgumentNullException">this.</exception>
public static IPropertyProxy? CreatePropertyProxy(this PropertyInfo @this)
{
if (@this == null)
throw new ArgumentNullException(nameof(@this));
var genericType = typeof(PropertyProxy<,>)
.MakeGenericType(@this.DeclaringType, @this.PropertyType);
return Activator.CreateInstance(genericType, @this) as IPropertyProxy;
}
/// <summary>
/// Convert a object to a boolean.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>
/// <c>true</c> if the string represents a valid truly value, otherwise <c>false</c>.
/// </returns>
public static bool ToBoolean(this object value) => value.ToStringInvariant().ToBoolean();
private static string ConvertObjectAndFormat(Type propertyType, object value, string format)
{
if (propertyType == typeof(DateTime) || propertyType == typeof(DateTime?))
return Convert.ToDateTime(value, CultureInfo.InvariantCulture).ToString(format);
if (propertyType == typeof(int) || propertyType == typeof(int?))
return Convert.ToInt32(value, CultureInfo.InvariantCulture).ToString(format);
if (propertyType == typeof(decimal) || propertyType == typeof(decimal?))
return Convert.ToDecimal(value, CultureInfo.InvariantCulture).ToString(format);
if (propertyType == typeof(double) || propertyType == typeof(double?))
return Convert.ToDouble(value, CultureInfo.InvariantCulture).ToString(format);
if (propertyType == typeof(byte) || propertyType == typeof(byte?))
return Convert.ToByte(value, CultureInfo.InvariantCulture).ToString(format);
return value?.ToString() ?? string.Empty;
}
}
} }

View File

@ -1,405 +1,364 @@
using System; #nullable enable
using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Swan.Formatters; using Swan.Formatters;
namespace Swan namespace Swan {
{ /// <summary>
/// String related extension methods.
/// </summary>
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<Regex> SplitLinesRegex = new Lazy<Regex>(() => new Regex("\r\n|\r|\n", StandardRegexOptions));
private static readonly Lazy<Regex> UnderscoreRegex = new Lazy<Regex>(() => new Regex(@"_", StandardRegexOptions));
private static readonly Lazy<Regex> CamelCaseRegEx = new Lazy<Regex>(() => new Regex(@"[a-z][A-Z]", StandardRegexOptions));
private static readonly Lazy<MatchEvaluator> SplitCamelCaseString = new Lazy<MatchEvaluator>(() => m => {
String x = m.ToString();
return x[0] + " " + x[1..];
});
private static readonly Lazy<String[]> InvalidFilenameChars = new Lazy<String[]>(() => Path.GetInvalidFileNameChars().Select(c => c.ToString()).ToArray());
#endregion
/// <summary> /// <summary>
/// String related extension methods. /// Returns a string that represents the given item
/// It tries to use InvariantCulture if the ToString(IFormatProvider)
/// overload exists.
/// </summary> /// </summary>
public static class StringExtensions /// <param name="this">The item.</param>
{ /// <returns>A <see cref="String" /> that represents the current object.</returns>
#region Private Declarations public static String ToStringInvariant(this Object? @this) {
if(@this == null) {
private const RegexOptions StandardRegexOptions = return String.Empty;
RegexOptions.Multiline | RegexOptions.Compiled | RegexOptions.CultureInvariant; }
private static readonly string[] ByteSuffixes = { "B", "KB", "MB", "GB", "TB" }; Type itemType = @this.GetType();
private static readonly Lazy<Regex> SplitLinesRegex = return itemType == typeof(String) ? @this as String ?? String.Empty : Definitions.BasicTypesInfo.Value.ContainsKey(itemType) ? Definitions.BasicTypesInfo.Value[itemType].ToStringInvariant(@this) : @this.ToString()!;
new Lazy<Regex>(() => new Regex("\r\n|\r|\n", StandardRegexOptions)); }
private static readonly Lazy<Regex> UnderscoreRegex = /// <summary>
new Lazy<Regex>(() => new Regex(@"_", StandardRegexOptions)); /// Returns a string that represents the given item
/// It tries to use InvariantCulture if the ToString(IFormatProvider)
private static readonly Lazy<Regex> CamelCaseRegEx = /// overload exists.
new Lazy<Regex>(() => new Regex(@"[a-z][A-Z]", StandardRegexOptions)); /// </summary>
/// <typeparam name="T">The type to get the string.</typeparam>
private static readonly Lazy<MatchEvaluator> SplitCamelCaseString = new Lazy<MatchEvaluator>(() => m => /// <param name="item">The item.</param>
{ /// <returns>A <see cref="String" /> that represents the current object.</returns>
var x = m.ToString(); public static String ToStringInvariant<T>(this T item) => typeof(String) == typeof(T) ? item as String ?? String.Empty : ToStringInvariant(item as Object);
return x[0] + " " + x.Substring(1, x.Length - 1);
}); /// <summary>
/// Removes the control characters from a string except for those specified.
private static readonly Lazy<string[]> InvalidFilenameChars = /// </summary>
new Lazy<string[]>(() => Path.GetInvalidFileNameChars().Select(c => c.ToString()).ToArray()); /// <param name="value">The input.</param>
/// <param name="excludeChars">When specified, these characters will not be removed.</param>
#endregion /// <returns>
/// A string that represents the current object.
/// <summary> /// </returns>
/// Returns a string that represents the given item /// <exception cref="ArgumentNullException">input.</exception>
/// It tries to use InvariantCulture if the ToString(IFormatProvider) public static String RemoveControlCharsExcept(this String value, params Char[]? excludeChars) {
/// overload exists. if(value == null) {
/// </summary> throw new ArgumentNullException(nameof(value));
/// <param name="this">The item.</param> }
/// <returns>A <see cref="string" /> that represents the current object.</returns>
public static string ToStringInvariant(this object? @this) if(excludeChars == null) {
{ excludeChars = Array.Empty<Char>();
if (@this == null) }
return string.Empty;
return new String(value.Where(c => Char.IsControl(c) == false || excludeChars.Contains(c)).ToArray());
var itemType = @this.GetType(); }
if (itemType == typeof(string)) /// <summary>
return @this as string ?? string.Empty; /// Removes all control characters from a string, including new line sequences.
/// </summary>
return Definitions.BasicTypesInfo.Value.ContainsKey(itemType) /// <param name="value">The input.</param>
? Definitions.BasicTypesInfo.Value[itemType].ToStringInvariant(@this) /// <returns>A <see cref="String" /> that represents the current object.</returns>
: @this.ToString(); /// <exception cref="ArgumentNullException">input.</exception>
} public static String RemoveControlChars(this String value) => value.RemoveControlCharsExcept(null);
/// <summary> /// <summary>
/// Returns a string that represents the given item /// Outputs JSON string representing this object.
/// It tries to use InvariantCulture if the ToString(IFormatProvider) /// </summary>
/// overload exists. /// <param name="this">The object.</param>
/// </summary> /// <param name="format">if set to <c>true</c> format the output.</param>
/// <typeparam name="T">The type to get the string.</typeparam> /// <returns>A <see cref="String" /> that represents the current object.</returns>
/// <param name="item">The item.</param> public static String ToJson(this Object @this, Boolean format = true) => @this == null ? String.Empty : Json.Serialize(@this, format);
/// <returns>A <see cref="string" /> that represents the current object.</returns>
public static string ToStringInvariant<T>(this T item) /// <summary>
=> typeof(string) == typeof(T) ? item as string ?? string.Empty : ToStringInvariant(item as object); /// 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
/// <summary> /// examine objects.
/// Removes the control characters from a string except for those specified. /// </summary>
/// </summary> /// <param name="this">The object.</param>
/// <param name="value">The input.</param> /// <returns>A <see cref="String" /> that represents the current object.</returns>
/// <param name="excludeChars">When specified, these characters will not be removed.</param> public static String Stringify(this Object @this) {
/// <returns> if(@this == null) {
/// A string that represents the current object. return "(null)";
/// </returns> }
/// <exception cref="ArgumentNullException">input.</exception>
public static string RemoveControlCharsExcept(this string value, params char[] excludeChars) try {
{ String jsonText = Json.Serialize(@this, false, "$type");
if (value == null) Object? jsonData = Json.Deserialize(jsonText);
throw new ArgumentNullException(nameof(value));
return new HumanizeJson(jsonData, 0).GetResult();
if (excludeChars == null) } catch {
excludeChars = Array.Empty<char>(); return @this.ToStringInvariant();
}
return new string(value }
.Where(c => char.IsControl(c) == false || excludeChars.Contains(c))
.ToArray()); /// <summary>
} /// 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
/// <summary> /// If the string is null it returns an empty string.
/// Removes all control characters from a string, including new line sequences. /// </summary>
/// </summary> /// <param name="this">The string.</param>
/// <param name="value">The input.</param> /// <param name="startIndex">The start index.</param>
/// <returns>A <see cref="string" /> that represents the current object.</returns> /// <param name="endIndex">The end index.</param>
/// <exception cref="ArgumentNullException">input.</exception> /// <returns>Retrieves a substring from this instance.</returns>
public static string RemoveControlChars(this string value) => value.RemoveControlCharsExcept(null); public static String Slice(this String @this, Int32 startIndex, Int32 endIndex) {
if(@this == null) {
/// <summary> return String.Empty;
/// Outputs JSON string representing this object. }
/// </summary>
/// <param name="this">The object.</param> Int32 end = endIndex.Clamp(startIndex, @this.Length - 1);
/// <param name="format">if set to <c>true</c> format the output.</param>
/// <returns>A <see cref="string" /> that represents the current object.</returns> return startIndex >= end ? String.Empty : @this.Substring(startIndex, end - startIndex + 1);
public static string ToJson(this object @this, bool format = true) => }
@this == null ? string.Empty : Json.Serialize(@this, format);
/// <summary>
/// <summary> /// Gets a part of the string clamping the length and startIndex parameters to safe values.
/// Returns text representing the properties of the specified object in a human-readable format. /// If the string is null it returns an empty string. This is basically just a safe version
/// While this method is fairly expensive computationally speaking, it provides an easy way to /// of string.Substring.
/// examine objects. /// </summary>
/// </summary> /// <param name="this">The string.</param>
/// <param name="this">The object.</param> /// <param name="startIndex">The start index.</param>
/// <returns>A <see cref="string" /> that represents the current object.</returns> /// <param name="length">The length.</param>
public static string Stringify(this object @this) /// <returns>Retrieves a substring from this instance.</returns>
{ public static String SliceLength(this String @this, Int32 startIndex, Int32 length) {
if (@this == null) if(@this == null) {
return "(null)"; return String.Empty;
}
try
{ Int32 start = startIndex.Clamp(0, @this.Length - 1);
var jsonText = Json.Serialize(@this, false, "$type"); Int32 len = length.Clamp(0, @this.Length - start);
var jsonData = Json.Deserialize(jsonText);
return len == 0 ? String.Empty : @this.Substring(start, len);
return new HumanizeJson(jsonData, 0).GetResult(); }
}
catch /// <summary>
{ /// Splits the specified text into r, n or rn separated lines.
return @this.ToStringInvariant(); /// </summary>
} /// <param name="this">The text.</param>
} /// <returns>
/// An array whose elements contain the substrings from this instance
/// <summary> /// that are delimited by one or more characters in separator.
/// Retrieves a section of the string, inclusive of both, the start and end indexes. /// </returns>
/// This behavior is unlike JavaScript's Slice behavior where the end index is non-inclusive public static String[] ToLines(this String @this) => @this == null ? Array.Empty<String>() : SplitLinesRegex.Value.Split(@this);
/// If the string is null it returns an empty string.
/// </summary> /// <summary>
/// <param name="this">The string.</param> /// Humanizes (make more human-readable) an identifier-style string
/// <param name="startIndex">The start index.</param> /// in either camel case or snake case. For example, CamelCase will be converted to
/// <param name="endIndex">The end index.</param> /// Camel Case and Snake_Case will be converted to Snake Case.
/// <returns>Retrieves a substring from this instance.</returns> /// </summary>
public static string Slice(this string @this, int startIndex, int endIndex) /// <param name="value">The identifier-style string.</param>
{ /// <returns>A <see cref="String" /> humanized.</returns>
if (@this == null) public static String Humanize(this String value) {
return string.Empty; if(value == null) {
return String.Empty;
var end = endIndex.Clamp(startIndex, @this.Length - 1); }
return startIndex >= end ? string.Empty : @this.Substring(startIndex, (end - startIndex) + 1); String returnValue = UnderscoreRegex.Value.Replace(value, " ");
} returnValue = CamelCaseRegEx.Value.Replace(returnValue, SplitCamelCaseString.Value);
return returnValue;
/// <summary> }
/// 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 /// <summary>
/// of string.Substring. /// Humanizes (make more human-readable) an boolean.
/// </summary> /// </summary>
/// <param name="this">The string.</param> /// <param name="value">if set to <c>true</c> [value].</param>
/// <param name="startIndex">The start index.</param> /// <returns>A <see cref="String" /> that represents the current boolean.</returns>
/// <param name="length">The length.</param> public static String Humanize(this Boolean value) => value ? "Yes" : "No";
/// <returns>Retrieves a substring from this instance.</returns>
public static string SliceLength(this string @this, int startIndex, int length) /// <summary>
{ /// Humanizes (make more human-readable) the specified value.
if (@this == null) /// </summary>
return string.Empty; /// <param name="value">The value.</param>
/// <returns>A <see cref="String" /> that represents the current object.</returns>
var start = startIndex.Clamp(0, @this.Length - 1); public static String Humanize(this Object value) =>
var len = length.Clamp(0, @this.Length - start);
return len == 0 ? string.Empty : @this.Substring(start, len);
}
/// <summary>
/// Splits the specified text into r, n or rn separated lines.
/// </summary>
/// <param name="this">The text.</param>
/// <returns>
/// An array whose elements contain the substrings from this instance
/// that are delimited by one or more characters in separator.
/// </returns>
public static string[] ToLines(this string @this) =>
@this == null ? Array.Empty<string>() : SplitLinesRegex.Value.Split(@this);
/// <summary>
/// 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.
/// </summary>
/// <param name="value">The identifier-style string.</param>
/// <returns>A <see cref="string" /> humanized.</returns>
public static string Humanize(this string value)
{
if (value == null)
return string.Empty;
var returnValue = UnderscoreRegex.Value.Replace(value, " ");
returnValue = CamelCaseRegEx.Value.Replace(returnValue, SplitCamelCaseString.Value);
return returnValue;
}
/// <summary>
/// Humanizes (make more human-readable) an boolean.
/// </summary>
/// <param name="value">if set to <c>true</c> [value].</param>
/// <returns>A <see cref="string" /> that represents the current boolean.</returns>
public static string Humanize(this bool value) => value ? "Yes" : "No";
/// <summary>
/// Humanizes (make more human-readable) the specified value.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>A <see cref="string" /> that represents the current object.</returns>
public static string Humanize(this object value) =>
value switch value switch
{ {
string stringValue => stringValue.Humanize(), String stringValue => stringValue.Humanize(),
bool boolValue => boolValue.Humanize(), Boolean boolValue => boolValue.Humanize(),
_ => value.Stringify() _ => value.Stringify()
}; };
/// <summary> /// <summary>
/// Indents the specified multi-line text with the given amount of leading spaces /// Indents the specified multi-line text with the given amount of leading spaces
/// per line. /// per line.
/// </summary> /// </summary>
/// <param name="value">The text.</param> /// <param name="value">The text.</param>
/// <param name="spaces">The spaces.</param> /// <param name="spaces">The spaces.</param>
/// <returns>A <see cref="string" /> that represents the current object.</returns> /// <returns>A <see cref="String" /> that represents the current object.</returns>
public static string Indent(this string value, int spaces = 4) public static String Indent(this String value, Int32 spaces = 4) {
{ if(value == null) {
if (value == null) value = string.Empty; value = String.Empty;
if (spaces <= 0) return value; }
var lines = value.ToLines(); if(spaces <= 0) {
var builder = new StringBuilder(); return value;
var indentStr = new string(' ', spaces); }
foreach (var line in lines) String[] lines = value.ToLines();
{ StringBuilder builder = new StringBuilder();
builder.AppendLine($"{indentStr}{line}"); String indentStr = new String(' ', spaces);
}
foreach(String line in lines) {
return builder.ToString().TrimEnd(); _ = builder.AppendLine($"{indentStr}{line}");
} }
/// <summary> 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. /// <summary>
/// Please not that the tuple contains first the line number and then the /// Gets the line and column number (i.e. not index) of the
/// column number. /// specified character index. Useful to locate text in a multi-line
/// </summary> /// string the same way a text editor does.
/// <param name="value">The string.</param> /// Please not that the tuple contains first the line number and then the
/// <param name="charIndex">Index of the character.</param> /// column number.
/// <returns>A 2-tuple whose value is (item1, item2).</returns> /// </summary>
public static Tuple<int, int> TextPositionAt(this string value, int charIndex) /// <param name="value">The string.</param>
{ /// <param name="charIndex">Index of the character.</param>
if (value == null) /// <returns>A 2-tuple whose value is (item1, item2).</returns>
return Tuple.Create(0, 0); public static Tuple<Int32, Int32> TextPositionAt(this String value, Int32 charIndex) {
if(value == null) {
var index = charIndex.Clamp(0, value.Length - 1); return Tuple.Create(0, 0);
}
var lineIndex = 0;
var colNumber = 0; Int32 index = charIndex.Clamp(0, value.Length - 1);
for (var i = 0; i <= index; i++) Int32 lineIndex = 0;
{ Int32 colNumber = 0;
if (value[i] == '\n')
{ for(Int32 i = 0; i <= index; i++) {
lineIndex++; if(value[i] == '\n') {
colNumber = 0; lineIndex++;
continue; colNumber = 0;
} continue;
}
if (value[i] != '\r')
colNumber++; if(value[i] != '\r') {
} colNumber++;
}
return Tuple.Create(lineIndex + 1, colNumber); }
}
return Tuple.Create(lineIndex + 1, colNumber);
/// <summary> }
/// Makes the file name system safe.
/// </summary> /// <summary>
/// <param name="value">The s.</param> /// Makes the file name system safe.
/// <returns> /// </summary>
/// A string with a safe file name. /// <param name="value">The s.</param>
/// </returns> /// <returns>
/// <exception cref="ArgumentNullException">s.</exception> /// A string with a safe file name.
public static string ToSafeFilename(this string value) => /// </returns>
value == null /// <exception cref="ArgumentNullException">s.</exception>
? throw new ArgumentNullException(nameof(value)) 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);
: InvalidFilenameChars.Value
.Aggregate(value, (current, c) => current.Replace(c, string.Empty)) /// <summary>
.Slice(0, 220); /// Formats a long into the closest bytes string.
/// </summary>
/// <summary> /// <param name="bytes">The bytes length.</param>
/// Formats a long into the closest bytes string. /// <returns>
/// </summary> /// The string representation of the current Byte object, formatted as specified by the format parameter.
/// <param name="bytes">The bytes length.</param> /// </returns>
/// <returns> public static String FormatBytes(this Int64 bytes) => ((UInt64)bytes).FormatBytes();
/// The string representation of the current Byte object, formatted as specified by the format parameter.
/// </returns> /// <summary>
public static string FormatBytes(this long bytes) => ((ulong)bytes).FormatBytes(); /// Formats a long into the closest bytes string.
/// </summary>
/// <summary> /// <param name="bytes">The bytes length.</param>
/// Formats a long into the closest bytes string. /// <returns>
/// </summary> /// A copy of format in which the format items have been replaced by the string
/// <param name="bytes">The bytes length.</param> /// representations of the corresponding arguments.
/// <returns> /// </returns>
/// A copy of format in which the format items have been replaced by the string public static String FormatBytes(this UInt64 bytes) {
/// representations of the corresponding arguments. Int32 i;
/// </returns> Double dblSByte = bytes;
public static string FormatBytes(this ulong bytes)
{ for(i = 0; i < ByteSuffixes.Length && bytes >= 1024; i++, bytes /= 1024) {
int i; dblSByte = bytes / 1024.0;
double dblSByte = bytes; }
for (i = 0; i < ByteSuffixes.Length && bytes >= 1024; i++, bytes /= 1024) return $"{dblSByte:0.##} {ByteSuffixes[i]}";
{ }
dblSByte = bytes / 1024.0;
} /// <summary>
/// Truncates the specified value.
return $"{dblSByte:0.##} {ByteSuffixes[i]}"; /// </summary>
} /// <param name="value">The value.</param>
/// <param name="maximumLength">The maximum length.</param>
/// <summary> /// <returns>
/// Truncates the specified value. /// Retrieves a substring from this instance.
/// </summary> /// The substring starts at a specified character position and has a specified length.
/// <param name="value">The value.</param> /// </returns>
/// <param name="maximumLength">The maximum length.</param> public static String? Truncate(this String value, Int32 maximumLength) => Truncate(value, maximumLength, String.Empty);
/// <returns>
/// Retrieves a substring from this instance. /// <summary>
/// The substring starts at a specified character position and has a specified length. /// Truncates the specified value and append the omission last.
/// </returns> /// </summary>
public static string? Truncate(this string value, int maximumLength) => /// <param name="value">The value.</param>
Truncate(value, maximumLength, string.Empty); /// <param name="maximumLength">The maximum length.</param>
/// <param name="omission">The omission.</param>
/// <summary> /// <returns>
/// Truncates the specified value and append the omission last. /// Retrieves a substring from this instance.
/// </summary> /// The substring starts at a specified character position and has a specified length.
/// <param name="value">The value.</param> /// </returns>
/// <param name="maximumLength">The maximum length.</param> 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;
/// <param name="omission">The omission.</param>
/// <returns> /// <summary>
/// Retrieves a substring from this instance. /// Determines whether the specified <see cref="String"/> contains any of characters in
/// The substring starts at a specified character position and has a specified length. /// the specified array of <see cref="Char"/>.
/// </returns> /// </summary>
public static string? Truncate(this string value, int maximumLength, string omission) /// <returns>
{ /// <c>true</c> if <paramref name="value"/> contains any of <paramref name="chars"/>;
if (value == null) /// otherwise, <c>false</c>.
return null; /// </returns>
/// <param name="value">
return value.Length > maximumLength /// A <see cref="String"/> to test.
? value.Substring(0, maximumLength) + (omission ?? string.Empty) /// </param>
: value; /// <param name="chars">
} /// An array of <see cref="Char"/> that contains characters to find.
/// </param>
/// <summary> public static Boolean Contains(this String value, params Char[] chars) => chars?.Length == 0 || !String.IsNullOrEmpty(value) && chars != null && value.IndexOfAny(chars) > -1;
/// Determines whether the specified <see cref="string"/> contains any of characters in
/// the specified array of <see cref="char"/>. /// <summary>
/// </summary> /// Replaces all chars in a string.
/// <returns> /// </summary>
/// <c>true</c> if <paramref name="value"/> contains any of <paramref name="chars"/>; /// <param name="value">The value.</param>
/// otherwise, <c>false</c>. /// <param name="replaceValue">The replace value.</param>
/// </returns> /// <param name="chars">The chars.</param>
/// <param name="value"> /// <returns>The string with the characters replaced.</returns>
/// A <see cref="string"/> to test. public static String ReplaceAll(this String value, String replaceValue, params Char[] chars) => chars.Aggregate(value, (current, c) => current.Replace(new String(new[] { c }), replaceValue));
/// </param>
/// <param name="chars"> /// <summary>
/// An array of <see cref="char"/> that contains characters to find. /// Convert hex character to an integer. Return -1 if char is something
/// </param> /// other than a hex char.
public static bool Contains(this string value, params char[] chars) => /// </summary>
chars?.Length == 0 || (!string.IsNullOrEmpty(value) && value.IndexOfAny(chars) > -1); /// <param name="value">The c.</param>
/// <returns>Converted integer.</returns>
/// <summary> 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;
/// Replaces all chars in a string. }
/// </summary>
/// <param name="value">The value.</param>
/// <param name="replaceValue">The replace value.</param>
/// <param name="chars">The chars.</param>
/// <returns>The string with the characters replaced.</returns>
public static string ReplaceAll(this string value, string replaceValue, params char[] chars) =>
chars.Aggregate(value, (current, c) => current.Replace(new string(new[] { c }), replaceValue));
/// <summary>
/// Convert hex character to an integer. Return -1 if char is something
/// other than a hex char.
/// </summary>
/// <param name="value">The c.</param>
/// <returns>Converted integer.</returns>
public static int 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;
}
} }

View File

@ -1,63 +1,59 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Swan namespace Swan {
{ /// <summary>
/// Provides extension methods for <see cref="Task"/> and <see cref="Task{TResult}"/>.
/// </summary>
public static class TaskExtensions {
/// <summary> /// <summary>
/// Provides extension methods for <see cref="Task"/> and <see cref="Task{TResult}"/>. /// <para>Suspends execution until the specified <see cref="Task"/> is completed.</para>
/// <para>This method operates similarly to the <see langword="await"/> C# operator,
/// but is meant to be called from a non-<see langword="async"/> method.</para>
/// </summary> /// </summary>
public static class TaskExtensions /// <param name="this">The <see cref="Task"/> on which this method is called.</param>
{ /// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <summary> public static void Await(this Task @this) => @this.GetAwaiter().GetResult();
/// <para>Suspends execution until the specified <see cref="Task"/> is completed.</para>
/// <para>This method operates similarly to the <see langword="await"/> C# operator, /// <summary>
/// but is meant to be called from a non-<see langword="async"/> method.</para> /// <para>Suspends execution until the specified <see cref="Task"/> is completed
/// </summary> /// and returns its result.</para>
/// <param name="this">The <see cref="Task"/> on which this method is called.</param> /// <para>This method operates similarly to the <see langword="await"/> C# operator,
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception> /// but is meant to be called from a non-<see langword="async"/> method.</para>
public static void Await(this Task @this) => @this.GetAwaiter().GetResult(); /// </summary>
/// <typeparam name="TResult">The type of the task's result.</typeparam>
/// <summary> /// <param name="this">The <see cref="Task{TResult}"/> on which this method is called.</param>
/// <para>Suspends execution until the specified <see cref="Task"/> is completed /// <returns>The result of <paramref name="this"/>.</returns>
/// and returns its result.</para> /// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <para>This method operates similarly to the <see langword="await"/> C# operator, public static TResult Await<TResult>(this Task<TResult> @this) => @this.GetAwaiter().GetResult();
/// but is meant to be called from a non-<see langword="async"/> method.</para>
/// </summary> /// <summary>
/// <typeparam name="TResult">The type of the task's result.</typeparam> /// <para>Suspends execution until the specified <see cref="Task"/> is completed.</para>
/// <param name="this">The <see cref="Task{TResult}"/> on which this method is called.</param> /// <para>This method operates similarly to the <see langword="await" /> C# operator,
/// <returns>The result of <paramref name="this"/>.</returns> /// but is meant to be called from a non-<see langword="async" /> method.</para>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception> /// </summary>
public static TResult Await<TResult>(this Task<TResult> @this) => @this.GetAwaiter().GetResult(); /// <param name="this">The <see cref="Task" /> on which this method is called.</param>
/// <param name="continueOnCapturedContext">If set to <see langword="true"/>,
/// <summary> /// attempts to marshal the continuation back to the original context captured.
/// <para>Suspends execution until the specified <see cref="Task"/> is completed.</para> /// This parameter has the same effect as calling the <see cref="Task.ConfigureAwait"/>
/// <para>This method operates similarly to the <see langword="await" /> C# operator, /// method.</param>
/// but is meant to be called from a non-<see langword="async" /> method.</para> /// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// </summary> public static void Await(this Task @this, Boolean continueOnCapturedContext) => @this.ConfigureAwait(continueOnCapturedContext).GetAwaiter().GetResult();
/// <param name="this">The <see cref="Task" /> on which this method is called.</param>
/// <param name="continueOnCapturedContext">If set to <see langword="true"/>, /// <summary>
/// attempts to marshal the continuation back to the original context captured. /// <para>Suspends execution until the specified <see cref="Task"/> is completed
/// This parameter has the same effect as calling the <see cref="Task.ConfigureAwait"/> /// and returns its result.</para>
/// method.</param> /// <para>This method operates similarly to the <see langword="await"/> C# operator,
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception> /// but is meant to be called from a non-<see langword="async"/> method.</para>
public static void Await(this Task @this, bool continueOnCapturedContext) /// </summary>
=> @this.ConfigureAwait(continueOnCapturedContext).GetAwaiter().GetResult(); /// <typeparam name="TResult">The type of the task's result.</typeparam>
/// <param name="this">The <see cref="Task{TResult}"/> on which this method is called.</param>
/// <summary> /// <param name="continueOnCapturedContext">If set to <see langword="true"/>,
/// <para>Suspends execution until the specified <see cref="Task"/> is completed /// attempts to marshal the continuation back to the original context captured.
/// and returns its result.</para> /// This parameter has the same effect as calling the <see cref="Task.ConfigureAwait"/>
/// <para>This method operates similarly to the <see langword="await"/> C# operator, /// method.</param>
/// but is meant to be called from a non-<see langword="async"/> method.</para> /// <returns>The result of <paramref name="this"/>.</returns>
/// </summary> /// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <typeparam name="TResult">The type of the task's result.</typeparam> public static TResult Await<TResult>(this Task<TResult> @this, Boolean continueOnCapturedContext) => @this.ConfigureAwait(continueOnCapturedContext).GetAwaiter().GetResult();
/// <param name="this">The <see cref="Task{TResult}"/> on which this method is called.</param> }
/// <param name="continueOnCapturedContext">If set to <see langword="true"/>,
/// attempts to marshal the continuation back to the original context captured.
/// This parameter has the same effect as calling the <see cref="Task.ConfigureAwait"/>
/// method.</param>
/// <returns>The result of <paramref name="this"/>.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
public static TResult Await<TResult>(this Task<TResult> @this, bool continueOnCapturedContext)
=> @this.ConfigureAwait(continueOnCapturedContext).GetAwaiter().GetResult();
}
} }

View File

@ -1,165 +1,135 @@
using System; using System;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Swan.Reflection; using Swan.Reflection;
namespace Swan namespace Swan {
{ /// <summary>
/// Provides various extension methods for value types and structs.
/// </summary>
public static class ValueTypeExtensions {
/// <summary> /// <summary>
/// Provides various extension methods for value types and structs. /// Clamps the specified value between the minimum and the maximum.
/// </summary> /// </summary>
public static class ValueTypeExtensions /// <typeparam name="T">The type of value to clamp.</typeparam>
{ /// <param name="this">The value.</param>
/// <summary> /// <param name="min">The minimum.</param>
/// Clamps the specified value between the minimum and the maximum. /// <param name="max">The maximum.</param>
/// </summary> /// <returns>A value that indicates the relative order of the objects being compared.</returns>
/// <typeparam name="T">The type of value to clamp.</typeparam> public static T Clamp<T>(this T @this, T min, T max) where T : struct, IComparable => @this.CompareTo(min) < 0 ? min : @this.CompareTo(max) > 0 ? max : @this;
/// <param name="this">The value.</param>
/// <param name="min">The minimum.</param> /// <summary>
/// <param name="max">The maximum.</param> /// Clamps the specified value between the minimum and the maximum.
/// <returns>A value that indicates the relative order of the objects being compared.</returns> /// </summary>
public static T Clamp<T>(this T @this, T min, T max) /// <param name="this">The value.</param>
where T : struct, IComparable /// <param name="min">The minimum.</param>
{ /// <param name="max">The maximum.</param>
if (@this.CompareTo(min) < 0) return min; /// <returns>A value that indicates the relative order of the objects being compared.</returns>
public static Int32 Clamp(this Int32 @this, Int32 min, Int32 max) => @this < min ? min : (@this > max ? max : @this);
return @this.CompareTo(max) > 0 ? max : @this;
} /// <summary>
/// Determines whether the specified value is between a minimum and a maximum value.
/// <summary> /// </summary>
/// Clamps the specified value between the minimum and the maximum. /// <typeparam name="T">The type of value to check.</typeparam>
/// </summary> /// <param name="this">The value.</param>
/// <param name="this">The value.</param> /// <param name="min">The minimum.</param>
/// <param name="min">The minimum.</param> /// <param name="max">The maximum.</param>
/// <param name="max">The maximum.</param> /// <returns>
/// <returns>A value that indicates the relative order of the objects being compared.</returns> /// <c>true</c> if the specified minimum is between; otherwise, <c>false</c>.
public static int Clamp(this int @this, int min, int max) /// </returns>
=> @this < min ? min : (@this > max ? max : @this); public static Boolean IsBetween<T>(this T @this, T min, T max) where T : struct, IComparable => @this.CompareTo(min) >= 0 && @this.CompareTo(max) <= 0;
/// <summary> /// <summary>
/// Determines whether the specified value is between a minimum and a maximum value. /// Converts an array of bytes into the given struct type.
/// </summary> /// </summary>
/// <typeparam name="T">The type of value to check.</typeparam> /// <typeparam name="T">The type of structure to convert.</typeparam>
/// <param name="this">The value.</param> /// <param name="this">The data.</param>
/// <param name="min">The minimum.</param> /// <returns>a struct type derived from convert an array of bytes ref=ToStruct".</returns>
/// <param name="max">The maximum.</param> public static T ToStruct<T>(this Byte[] @this) where T : struct => @this == null ? throw new ArgumentNullException(nameof(@this)) : ToStruct<T>(@this, 0, @this.Length);
/// <returns>
/// <c>true</c> if the specified minimum is between; otherwise, <c>false</c>. /// <summary>
/// </returns> /// Converts an array of bytes into the given struct type.
public static bool IsBetween<T>(this T @this, T min, T max) /// </summary>
where T : struct, IComparable /// <typeparam name="T">The type of structure to convert.</typeparam>
{ /// <param name="this">The data.</param>
return @this.CompareTo(min) >= 0 && @this.CompareTo(max) <= 0; /// <param name="offset">The offset.</param>
} /// <param name="length">The length.</param>
/// <returns>
/// <summary> /// A managed object containing the data pointed to by the ptr parameter.
/// Converts an array of bytes into the given struct type. /// </returns>
/// </summary> /// <exception cref="ArgumentNullException">data.</exception>
/// <typeparam name="T">The type of structure to convert.</typeparam> public static T ToStruct<T>(this Byte[] @this, Int32 offset, Int32 length) where T : struct {
/// <param name="this">The data.</param> if(@this == null) {
/// <returns>a struct type derived from convert an array of bytes ref=ToStruct".</returns> throw new ArgumentNullException(nameof(@this));
public static T ToStruct<T>(this byte[] @this) }
where T : struct
{ Byte[] buffer = new Byte[length];
return @this == null ? throw new ArgumentNullException(nameof(@this)) : ToStruct<T>(@this, 0, @this.Length); Array.Copy(@this, offset, buffer, 0, buffer.Length);
} GCHandle handle = GCHandle.Alloc(GetStructBytes<T>(buffer), GCHandleType.Pinned);
/// <summary> try {
/// Converts an array of bytes into the given struct type. return Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());
/// </summary> } finally {
/// <typeparam name="T">The type of structure to convert.</typeparam> handle.Free();
/// <param name="this">The data.</param> }
/// <param name="offset">The offset.</param> }
/// <param name="length">The length.</param>
/// <returns> /// <summary>
/// A managed object containing the data pointed to by the ptr parameter. /// Converts a struct to an array of bytes.
/// </returns> /// </summary>
/// <exception cref="ArgumentNullException">data.</exception> /// <typeparam name="T">The type of structure to convert.</typeparam>
public static T ToStruct<T>(this byte[] @this, int offset, int length) /// <param name="this">The object.</param>
where T : struct /// <returns>A byte array containing the results of encoding the specified set of characters.</returns>
{ public static Byte[] ToBytes<T>(this T @this) where T : struct {
if (@this == null) Byte[] data = new Byte[Marshal.SizeOf(@this)];
throw new ArgumentNullException(nameof(@this)); GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
var buffer = new byte[length]; try {
Array.Copy(@this, offset, buffer, 0, buffer.Length); Marshal.StructureToPtr(@this, handle.AddrOfPinnedObject(), false);
var handle = GCHandle.Alloc(GetStructBytes<T>(buffer), GCHandleType.Pinned); return GetStructBytes<T>(data);
} finally {
try handle.Free();
{ }
return Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject()); }
}
finally /// <summary>
{ /// Swaps the endianness of an unsigned long to an unsigned integer.
handle.Free(); /// </summary>
} /// <param name="this">The bytes contained in a long.</param>
} /// <returns>
/// A 32-bit unsigned integer equivalent to the ulong
/// <summary> /// contained in longBytes.
/// Converts a struct to an array of bytes. /// </returns>
/// </summary> public static UInt32 SwapEndianness(this UInt64 @this) => (UInt32)(((@this & 0x000000ff) << 24) + ((@this & 0x0000ff00) << 8) + ((@this & 0x00ff0000) >> 8) + ((@this & 0xff000000) >> 24));
/// <typeparam name="T">The type of structure to convert.</typeparam>
/// <param name="this">The object.</param> private static Byte[] GetStructBytes<T>(Byte[] data) {
/// <returns>A byte array containing the results of encoding the specified set of characters.</returns> if(data == null) {
public static byte[] ToBytes<T>(this T @this) throw new ArgumentNullException(nameof(data));
where T : struct }
{
var data = new byte[Marshal.SizeOf(@this)]; FieldInfo[] fields = typeof(T).GetTypeInfo()
var handle = GCHandle.Alloc(data, GCHandleType.Pinned); .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
try StructEndiannessAttribute endian = AttributeCache.DefaultCache.Value.RetrieveOne<StructEndiannessAttribute, T>();
{
Marshal.StructureToPtr(@this, handle.AddrOfPinnedObject(), false); foreach(FieldInfo field in fields) {
return GetStructBytes<T>(data); if(endian == null && !field.IsDefined(typeof(StructEndiannessAttribute), false)) {
} continue;
finally }
{
handle.Free(); Int32 offset = Marshal.OffsetOf<T>(field.Name).ToInt32();
} Int32 length = Marshal.SizeOf(field.FieldType);
}
endian ??= AttributeCache.DefaultCache.Value.RetrieveOne<StructEndiannessAttribute>(field);
/// <summary>
/// Swaps the endianness of an unsigned long to an unsigned integer. if(endian != null && (endian.Endianness == Endianness.Big && BitConverter.IsLittleEndian ||
/// </summary> endian.Endianness == Endianness.Little && !BitConverter.IsLittleEndian)) {
/// <param name="this">The bytes contained in a long.</param> Array.Reverse(data, offset, length);
/// <returns> }
/// A 32-bit unsigned integer equivalent to the ulong }
/// contained in longBytes.
/// </returns> return data;
public static uint SwapEndianness(this ulong @this) }
=> (uint)(((@this & 0x000000ff) << 24) + }
((@this & 0x0000ff00) << 8) +
((@this & 0x00ff0000) >> 8) +
((@this & 0xff000000) >> 24));
private static byte[] GetStructBytes<T>(byte[] data)
{
if (data == null)
throw new ArgumentNullException(nameof(data));
var fields = typeof(T).GetTypeInfo()
.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
var endian = AttributeCache.DefaultCache.Value.RetrieveOne<StructEndiannessAttribute, T>();
foreach (var field in fields)
{
if (endian == null && !field.IsDefined(typeof(StructEndiannessAttribute), false))
continue;
var offset = Marshal.OffsetOf<T>(field.Name).ToInt32();
var length = Marshal.SizeOf(field.FieldType);
endian = endian ?? AttributeCache.DefaultCache.Value.RetrieveOne<StructEndiannessAttribute>(field);
if (endian != null && (endian.Endianness == Endianness.Big && BitConverter.IsLittleEndian ||
endian.Endianness == Endianness.Little && !BitConverter.IsLittleEndian))
{
Array.Reverse(data, offset, length);
}
}
return data;
}
}
} }

View File

@ -1,276 +1,230 @@
using System; #nullable enable
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Swan.Lite.Reflection; using Swan.Lite.Reflection;
using Swan.Mappers; using Swan.Mappers;
using Swan.Reflection; using Swan.Reflection;
namespace Swan namespace Swan {
{ /// <summary>
/// Extension methods.
/// </summary>
public static partial class Extensions {
/// <summary> /// <summary>
/// Extension methods. /// 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.
/// </summary> /// </summary>
public static partial class Extensions /// <typeparam name="T">The type of the source.</typeparam>
{ /// <param name="source">The source.</param>
/// <summary> /// <param name="target">The target.</param>
/// Iterates over the public, instance, readable properties of the source and /// <param name="ignoreProperties">The ignore properties.</param>
/// tries to write a compatible value to a public, instance, writable property in the destination. /// <returns>
/// </summary> /// Number of properties that was copied successful.
/// <typeparam name="T">The type of the source.</typeparam> /// </returns>
/// <param name="source">The source.</param> public static Int32 CopyPropertiesTo<T>(this T source, Object? target, params String[]? ignoreProperties) where T : class => ObjectMapper.Copy(source, target, GetCopyableProperties(target), ignoreProperties);
/// <param name="target">The target.</param>
/// <param name="ignoreProperties">The ignore properties.</param> /// <summary>
/// <returns> /// Iterates over the public, instance, readable properties of the source and
/// Number of properties that was copied successful. /// tries to write a compatible value to a public, instance, writable property in the destination.
/// </returns> /// </summary>
public static int CopyPropertiesTo<T>(this T source, object target, params string[]? ignoreProperties) /// <param name="source">The source.</param>
where T : class => /// <param name="target">The destination.</param>
ObjectMapper.Copy(source, target, GetCopyableProperties(target), ignoreProperties); /// <param name="propertiesToCopy">Properties to copy.</param>
/// <returns>
/// <summary> /// Number of properties that were successfully copied.
/// Iterates over the public, instance, readable properties of the source and /// </returns>
/// tries to write a compatible value to a public, instance, writable property in the destination. public static Int32 CopyOnlyPropertiesTo(this Object source, Object target, params String[]? propertiesToCopy) => ObjectMapper.Copy(source, target, propertiesToCopy);
/// </summary>
/// <param name="source">The source.</param> /// <summary>
/// <param name="target">The destination.</param> /// Copies the properties to new instance of T.
/// <param name="propertiesToCopy">Properties to copy.</param> /// </summary>
/// <returns> /// <typeparam name="T">The new object type.</typeparam>
/// Number of properties that were successfully copied. /// <param name="source">The source.</param>
/// </returns> /// <param name="ignoreProperties">The ignore properties.</param>
public static int CopyOnlyPropertiesTo(this object source, object target, params string[]? propertiesToCopy) /// <returns>
=> ObjectMapper.Copy(source, target, propertiesToCopy); /// The specified type with properties copied.
/// </returns>
/// <summary> /// <exception cref="ArgumentNullException">source.</exception>
/// Copies the properties to new instance of T. public static T CopyPropertiesToNew<T>(this Object source, String[]? ignoreProperties = null) where T : class {
/// </summary> if(source == null) {
/// <typeparam name="T">The new object type.</typeparam> throw new ArgumentNullException(nameof(source));
/// <param name="source">The source.</param> }
/// <param name="ignoreProperties">The ignore properties.</param>
/// <returns> T target = Activator.CreateInstance<T>();
/// The specified type with properties copied. _ = ObjectMapper.Copy(source, target, GetCopyableProperties(target), ignoreProperties);
/// </returns>
/// <exception cref="ArgumentNullException">source.</exception> return target;
public static T CopyPropertiesToNew<T>(this object source, string[]? ignoreProperties = null) }
where T : class
{ /// <summary>
if (source == null) /// Copies the only properties to new instance of T.
throw new ArgumentNullException(nameof(source)); /// </summary>
/// <typeparam name="T">Object Type.</typeparam>
var target = Activator.CreateInstance<T>(); /// <param name="source">The source.</param>
ObjectMapper.Copy(source, target, GetCopyableProperties(target), ignoreProperties); /// <param name="propertiesToCopy">The properties to copy.</param>
/// <returns>
return target; /// The specified type with properties copied.
} /// </returns>
/// <exception cref="ArgumentNullException">source.</exception>
/// <summary> public static T CopyOnlyPropertiesToNew<T>(this Object source, params String[] propertiesToCopy) where T : class {
/// Copies the only properties to new instance of T. if(source == null) {
/// </summary> throw new ArgumentNullException(nameof(source));
/// <typeparam name="T">Object Type.</typeparam> }
/// <param name="source">The source.</param>
/// <param name="propertiesToCopy">The properties to copy.</param> T target = Activator.CreateInstance<T>();
/// <returns> _ = ObjectMapper.Copy(source, target, propertiesToCopy);
/// The specified type with properties copied.
/// </returns> return target;
/// <exception cref="ArgumentNullException">source.</exception> }
public static T CopyOnlyPropertiesToNew<T>(this object source, params string[] propertiesToCopy)
where T : class /// <summary>
{ /// Iterates over the keys of the source and tries to write a compatible value to a public,
if (source == null) /// instance, writable property in the destination.
throw new ArgumentNullException(nameof(source)); /// </summary>
/// <param name="source">The source.</param>
var target = Activator.CreateInstance<T>(); /// <param name="target">The target.</param>
ObjectMapper.Copy(source, target, propertiesToCopy); /// <param name="ignoreKeys">The ignore keys.</param>
/// <returns>Number of properties that was copied successful.</returns>
return target; public static Int32 CopyKeyValuePairTo(this IDictionary<String, Object> source, Object? target, params String[] ignoreKeys) => source == null ? throw new ArgumentNullException(nameof(source)) : ObjectMapper.Copy(source, target, null, ignoreKeys);
}
/// <summary>
/// <summary> /// Iterates over the keys of the source and tries to write a compatible value to a public,
/// Iterates over the keys of the source and tries to write a compatible value to a public, /// instance, writable property in the destination.
/// instance, writable property in the destination. /// </summary>
/// </summary> /// <typeparam name="T">Object Type.</typeparam>
/// <param name="source">The source.</param> /// <param name="source">The source.</param>
/// <param name="target">The target.</param> /// <param name="ignoreKeys">The ignore keys.</param>
/// <param name="ignoreKeys">The ignore keys.</param> /// <returns>
/// <returns>Number of properties that was copied successful.</returns> /// The specified type with properties copied.
public static int CopyKeyValuePairTo( /// </returns>
this IDictionary<string, object> source, public static T CopyKeyValuePairToNew<T>(this IDictionary<String, Object> source, params String[] ignoreKeys) {
object target, if(source == null) {
params string[] ignoreKeys) => throw new ArgumentNullException(nameof(source));
source == null }
? throw new ArgumentNullException(nameof(source))
: ObjectMapper.Copy(source, target, null, ignoreKeys); T target = Activator.CreateInstance<T>();
_ = source.CopyKeyValuePairTo(target, ignoreKeys);
/// <summary> 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.
/// </summary> /// <summary>
/// <typeparam name="T">Object Type.</typeparam> /// Does the specified action.
/// <param name="source">The source.</param> /// </summary>
/// <param name="ignoreKeys">The ignore keys.</param> /// <param name="action">The action.</param>
/// <returns> /// <param name="retryInterval">The retry interval.</param>
/// The specified type with properties copied. /// <param name="retryCount">The retry count.</param>
/// </returns> public static void Retry(this Action action, TimeSpan retryInterval = default, Int32 retryCount = 3) {
public static T CopyKeyValuePairToNew<T>( if(action == null) {
this IDictionary<string, object> source, throw new ArgumentNullException(nameof(action));
params string[] ignoreKeys) }
{
if (source == null) _ = Retry<Object?>(() => { action(); return null; }, retryInterval, retryCount);
throw new ArgumentNullException(nameof(source)); }
var target = Activator.CreateInstance<T>(); /// <summary>
source.CopyKeyValuePairTo(target, ignoreKeys); /// Does the specified action.
return target; /// </summary>
} /// <typeparam name="T">The type of the source.</typeparam>
/// <param name="action">The action.</param>
/// <summary> /// <param name="retryInterval">The retry interval.</param>
/// Does the specified action. /// <param name="retryCount">The retry count.</param>
/// </summary> /// <returns>
/// <param name="action">The action.</param> /// The return value of the method that this delegate encapsulates.
/// <param name="retryInterval">The retry interval.</param> /// </returns>
/// <param name="retryCount">The retry count.</param> /// <exception cref="ArgumentNullException">action.</exception>
public static void Retry( /// <exception cref="AggregateException">Represents one or many errors that occur during application execution.</exception>
this Action action, public static T Retry<T>(this Func<T> action, TimeSpan retryInterval = default, Int32 retryCount = 3) {
TimeSpan retryInterval = default, if(action == null) {
int retryCount = 3) throw new ArgumentNullException(nameof(action));
{ }
if (action == null)
throw new ArgumentNullException(nameof(action)); if(retryInterval == default) {
retryInterval = TimeSpan.FromSeconds(1);
Retry<object?>(() => }
{
action(); global::System.Collections.Generic.List<global::System.Exception> exceptions = new List<Exception>();
return null;
}, for(Int32 retry = 0; retry < retryCount; retry++) {
retryInterval, try {
retryCount); if(retry > 0) {
} Task.Delay(retryInterval).Wait();
}
/// <summary>
/// Does the specified action. return action();
/// </summary> } catch(Exception ex) {
/// <typeparam name="T">The type of the source.</typeparam> exceptions.Add(ex);
/// <param name="action">The action.</param> }
/// <param name="retryInterval">The retry interval.</param> }
/// <param name="retryCount">The retry count.</param>
/// <returns> throw new AggregateException(exceptions);
/// The return value of the method that this delegate encapsulates. }
/// </returns>
/// <exception cref="ArgumentNullException">action.</exception> /// <summary>
/// <exception cref="AggregateException">Represents one or many errors that occur during application execution.</exception> /// Gets the copyable properties.
public static T Retry<T>( ///
this Func<T> action, /// If there is no properties with the attribute <c>AttributeCache</c> returns all the properties.
TimeSpan retryInterval = default, /// </summary>
int retryCount = 3) /// <param name="this">The object.</param>
{ /// <returns>
if (action == null) /// Array of properties.
throw new ArgumentNullException(nameof(action)); /// </returns>
/// <exception cref="ArgumentNullException">model.</exception>
if (retryInterval == default) /// <seealso cref="AttributeCache"/>
retryInterval = TimeSpan.FromSeconds(1); public static IEnumerable<String> GetCopyableProperties(this Object? @this) {
if(@this == null) {
var exceptions = new List<Exception>(); throw new ArgumentNullException(nameof(@this));
}
for (var retry = 0; retry < retryCount; retry++)
{ global::System.Collections.Generic.IEnumerable<global::System.Reflection.PropertyInfo> collection = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties(@this.GetType(), true);
try
{ global::System.Collections.Generic.IEnumerable<global::System.String> properties = collection.Select(x => new {
if (retry > 0) x.Name,
Task.Delay(retryInterval).Wait(); HasAttribute = AttributeCache.DefaultCache.Value.RetrieveOne<CopyableAttribute>(x) != null,
}).Where(x => x.HasAttribute).Select(x => x.Name);
return action();
} return properties.Any() ? properties : collection.Select(x => x.Name);
catch (Exception ex) }
{
exceptions.Add(ex); internal static void CreateTarget(this Object source, Type targetType, Boolean includeNonPublic, ref Object? target) {
} switch(source) {
} // do nothing. Simply skip creation
case String _:
throw new AggregateException(exceptions); break;
} // When using arrays, there is no default constructor, attempt to build a compatible array
case IList sourceObjectList when targetType.IsArray:
/// <summary> Type? elementType = targetType.GetElementType();
/// Gets the copyable properties.
/// if(elementType != null) {
/// If there is no properties with the attribute <c>AttributeCache</c> returns all the properties. target = Array.CreateInstance(elementType, sourceObjectList.Count);
/// </summary> }
/// <param name="this">The object.</param>
/// <returns> break;
/// Array of properties. default:
/// </returns> IEnumerable<Tuple<System.Reflection.ConstructorInfo, System.Reflection.ParameterInfo[]>> constructors = ConstructorTypeCache.DefaultCache.Value.RetrieveAllConstructors(targetType, includeNonPublic);
/// <exception cref="ArgumentNullException">model.</exception>
/// <seealso cref="AttributeCache"/> // Try to check if empty constructor is available
public static IEnumerable<string> GetCopyableProperties(this object @this) if(constructors.Any(x => x.Item2.Length == 0)) {
{ target = Activator.CreateInstance(targetType, includeNonPublic);
if (@this == null) } else {
throw new ArgumentNullException(nameof(@this)); Tuple<System.Reflection.ConstructorInfo, System.Reflection.ParameterInfo[]> firstCtor = constructors.OrderBy(x => x.Item2.Length).FirstOrDefault();
var collection = PropertyTypeCache.DefaultCache.Value target = Activator.CreateInstance(targetType, firstCtor?.Item2.Select(arg => arg.GetType().GetDefault()).ToArray());
.RetrieveAllProperties(@this.GetType(), true); }
var properties = collection break;
.Select(x => new }
{ }
x.Name,
HasAttribute = AttributeCache.DefaultCache.Value.RetrieveOne<CopyableAttribute>(x) != null, internal static String GetNameWithCase(this String name, JsonSerializerCase jsonSerializerCase) => jsonSerializerCase switch
}) {
.Where(x => x.HasAttribute) JsonSerializerCase.PascalCase => Char.ToUpperInvariant(name[0]) + name.Substring(1),
.Select(x => x.Name); JsonSerializerCase.CamelCase => Char.ToLowerInvariant(name[0]) + name.Substring(1),
JsonSerializerCase.None => name,
return properties.Any() _ => throw new ArgumentOutOfRangeException(nameof(jsonSerializerCase), jsonSerializerCase, null)
? properties };
: collection.Select(x => x.Name); }
}
internal static void CreateTarget(
this object source,
Type targetType,
bool 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:
var elementType = targetType.GetElementType();
if (elementType != null)
target = Array.CreateInstance(elementType, sourceObjectList.Count);
break;
default:
var 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
{
var 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)
};
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -7,453 +7,415 @@ using System.Reflection;
using System.Text; using System.Text;
using Swan.Reflection; using Swan.Reflection;
namespace Swan.Formatters namespace Swan.Formatters {
{ /// <summary>
/// A CSV writer useful for exporting a set of objects.
/// </summary>
/// <example>
/// The following code describes how to save a list of objects into a CSV file.
/// <code>
/// using System.Collections.Generic;
/// using Swan.Formatters;
///
/// class Example
/// {
/// class Person
/// {
/// public string Name { get; set; }
/// public int Age { get; set; }
/// }
///
/// static void Main()
/// {
/// // create a list of people
/// var people = new List&lt;Person&gt;
/// {
/// new Person { Name = "Artyom", Age = 20 },
/// new Person { Name = "Aloy", Age = 18 }
/// }
///
/// // write items inside file.csv
/// CsvWriter.SaveRecords(people, "C:\\Users\\user\\Documents\\file.csv");
///
/// // output
/// // | Name | Age |
/// // | Artyom | 20 |
/// // | Aloy | 18 |
/// }
/// }
/// </code>
/// </example>
public class CsvWriter : IDisposable {
private static readonly PropertyTypeCache TypeCache = new PropertyTypeCache();
private readonly Object _syncLock = new Object();
private readonly Stream _outputStream;
private readonly Encoding _encoding;
private readonly Boolean _leaveStreamOpen;
private Boolean _isDisposing;
private UInt64 _mCount;
#region Constructors
/// <summary> /// <summary>
/// A CSV writer useful for exporting a set of objects. /// Initializes a new instance of the <see cref="CsvWriter" /> class.
/// </summary> /// </summary>
/// <example> /// <param name="outputStream">The output stream.</param>
/// The following code describes how to save a list of objects into a CSV file. /// <param name="leaveOpen">if set to <c>true</c> [leave open].</param>
/// <code> /// <param name="encoding">The encoding.</param>
/// using System.Collections.Generic; public CsvWriter(Stream outputStream, Boolean leaveOpen, Encoding encoding) {
/// using Swan.Formatters; this._outputStream = outputStream;
/// this._encoding = encoding;
/// class Example this._leaveStreamOpen = leaveOpen;
/// { }
/// class Person
/// { /// <summary>
/// public string Name { get; set; } /// Initializes a new instance of the <see cref="CsvWriter"/> class.
/// public int Age { get; set; } /// It automatically closes the stream when disposing this writer.
/// } /// </summary>
/// /// <param name="outputStream">The output stream.</param>
/// static void Main() /// <param name="encoding">The encoding.</param>
/// { public CsvWriter(Stream outputStream, Encoding encoding) : this(outputStream, false, encoding) {
/// // create a list of people // placeholder
/// var people = new List&lt;Person&gt; }
/// {
/// new Person { Name = "Artyom", Age = 20 }, /// <summary>
/// new Person { Name = "Aloy", Age = 18 } /// Initializes a new instance of the <see cref="CsvWriter"/> class.
/// } /// It uses the Windows 1252 encoding and automatically closes
/// /// the stream upon disposing this writer.
/// // write items inside file.csv /// </summary>
/// CsvWriter.SaveRecords(people, "C:\\Users\\user\\Documents\\file.csv"); /// <param name="outputStream">The output stream.</param>
/// public CsvWriter(Stream outputStream) : this(outputStream, false, Definitions.Windows1252Encoding) {
/// // output // placeholder
/// // | Name | Age | }
/// // | Artyom | 20 |
/// // | Aloy | 18 | /// <summary>
/// } /// Initializes a new instance of the <see cref="CsvWriter"/> class.
/// } /// It opens the file given file, automatically closes the stream upon
/// </code> /// disposing of this writer, and uses the Windows 1252 encoding.
/// </example> /// </summary>
public class CsvWriter : IDisposable /// <param name="filename">The filename.</param>
{ public CsvWriter(String filename) : this(File.OpenWrite(filename), false, Definitions.Windows1252Encoding) {
private static readonly PropertyTypeCache TypeCache = new PropertyTypeCache(); // placeholder
}
private readonly object _syncLock = new object();
private readonly Stream _outputStream; /// <summary>
private readonly Encoding _encoding; /// Initializes a new instance of the <see cref="CsvWriter"/> class.
private readonly bool _leaveStreamOpen; /// It opens the file given file, automatically closes the stream upon
private bool _isDisposing; /// disposing of this writer, and uses the given text encoding for output.
private ulong _mCount; /// </summary>
/// <param name="filename">The filename.</param>
#region Constructors /// <param name="encoding">The encoding.</param>
public CsvWriter(String filename, Encoding encoding) : this(File.OpenWrite(filename), false, encoding) {
/// <summary> // placeholder
/// Initializes a new instance of the <see cref="CsvWriter" /> class. }
/// </summary>
/// <param name="outputStream">The output stream.</param> #endregion
/// <param name="leaveOpen">if set to <c>true</c> [leave open].</param>
/// <param name="encoding">The encoding.</param> #region Properties
public CsvWriter(Stream outputStream, bool leaveOpen, Encoding encoding)
{ /// <summary>
_outputStream = outputStream; /// Gets or sets the field separator character.
_encoding = encoding; /// </summary>
_leaveStreamOpen = leaveOpen; /// <value>
} /// The separator character.
/// </value>
/// <summary> public Char SeparatorCharacter { get; set; } = ',';
/// Initializes a new instance of the <see cref="CsvWriter"/> class.
/// It automatically closes the stream when disposing this writer. /// <summary>
/// </summary> /// Gets or sets the escape character to use to escape field values.
/// <param name="outputStream">The output stream.</param> /// </summary>
/// <param name="encoding">The encoding.</param> /// <value>
public CsvWriter(Stream outputStream, Encoding encoding) /// The escape character.
: this(outputStream, false, encoding) /// </value>
{ public Char EscapeCharacter { get; set; } = '"';
// placeholder
} /// <summary>
/// Gets or sets the new line character sequence to use when writing a line.
/// <summary> /// </summary>
/// Initializes a new instance of the <see cref="CsvWriter"/> class. /// <value>
/// It uses the Windows 1252 encoding and automatically closes /// The new line sequence.
/// the stream upon disposing this writer. /// </value>
/// </summary> public String NewLineSequence { get; set; } = Environment.NewLine;
/// <param name="outputStream">The output stream.</param>
public CsvWriter(Stream outputStream) /// <summary>
: this(outputStream, false, Definitions.Windows1252Encoding) /// Defines a list of properties to ignore when outputting CSV lines.
{ /// </summary>
// placeholder /// <value>
} /// The ignore property names.
/// </value>
/// <summary> public List<String> IgnorePropertyNames { get; } = new List<String>();
/// Initializes a new instance of the <see cref="CsvWriter"/> class.
/// It opens the file given file, automatically closes the stream upon /// <summary>
/// disposing of this writer, and uses the Windows 1252 encoding. /// Gets number of lines that have been written, including the headings line.
/// </summary> /// </summary>
/// <param name="filename">The filename.</param> /// <value>
public CsvWriter(string filename) /// The count.
: this(File.OpenWrite(filename), false, Definitions.Windows1252Encoding) /// </value>
{ public UInt64 Count {
// placeholder get {
} lock(this._syncLock) {
return this._mCount;
/// <summary> }
/// Initializes a new instance of the <see cref="CsvWriter"/> class. }
/// It opens the file given file, automatically closes the stream upon }
/// disposing of this writer, and uses the given text encoding for output.
/// </summary> #endregion
/// <param name="filename">The filename.</param>
/// <param name="encoding">The encoding.</param> #region Helpers
public CsvWriter(string filename, Encoding encoding)
: this(File.OpenWrite(filename), false, encoding) /// <summary>
{ /// Saves the items to a stream.
// placeholder /// It uses the Windows 1252 text encoding for output.
} /// </summary>
/// <typeparam name="T">The type of enumeration.</typeparam>
#endregion /// <param name="items">The items.</param>
/// <param name="stream">The stream.</param>
#region Properties /// <param name="truncateData"><c>true</c> if stream is truncated, default <c>false</c>.</param>
/// <returns>Number of item saved.</returns>
/// <summary> public static Int32 SaveRecords<T>(IEnumerable<T> items, Stream stream, Boolean truncateData = false) {
/// Gets or sets the field separator character. // truncate the file if it had data
/// </summary> if(truncateData && stream.Length > 0) {
/// <value> stream.SetLength(0);
/// The separator character. }
/// </value>
public char SeparatorCharacter { get; set; } = ','; using CsvWriter writer = new CsvWriter(stream);
writer.WriteHeadings<T>();
/// <summary> writer.WriteObjects(items);
/// Gets or sets the escape character to use to escape field values. return (Int32)writer.Count;
/// </summary> }
/// <value>
/// The escape character. /// <summary>
/// </value> /// Saves the items to a CSV file.
public char EscapeCharacter { get; set; } = '"'; /// If the file exits, it overwrites it. If it does not, it creates it.
/// It uses the Windows 1252 text encoding for output.
/// <summary> /// </summary>
/// Gets or sets the new line character sequence to use when writing a line. /// <typeparam name="T">The type of enumeration.</typeparam>
/// </summary> /// <param name="items">The items.</param>
/// <value> /// <param name="filePath">The file path.</param>
/// The new line sequence. /// <returns>Number of item saved.</returns>
/// </value> public static Int32 SaveRecords<T>(IEnumerable<T> items, String filePath) => SaveRecords(items, File.OpenWrite(filePath), true);
public string NewLineSequence { get; set; } = Environment.NewLine;
#endregion
/// <summary>
/// Defines a list of properties to ignore when outputting CSV lines. #region Generic, main Write Line Method
/// </summary>
/// <value> /// <summary>
/// The ignore property names. /// Writes a line of CSV text. Items are converted to strings.
/// </value> /// If items are found to be null, empty strings are written out.
public List<string> IgnorePropertyNames { get; } = new List<string>(); /// If items are not string, the ToStringInvariant() method is called on them.
/// </summary>
/// <summary> /// <param name="items">The items.</param>
/// Gets number of lines that have been written, including the headings line. public void WriteLine(params Object[] items) => this.WriteLine(items.Select(x => x == null ? String.Empty : x.ToStringInvariant()));
/// </summary>
/// <value> /// <summary>
/// The count. /// Writes a line of CSV text. Items are converted to strings.
/// </value> /// If items are found to be null, empty strings are written out.
public ulong Count /// If items are not string, the ToStringInvariant() method is called on them.
{ /// </summary>
get /// <param name="items">The items.</param>
{ public void WriteLine(IEnumerable<Object> items) => this.WriteLine(items.Select(x => x == null ? String.Empty : x.ToStringInvariant()));
lock (_syncLock)
{ /// <summary>
return _mCount; /// Writes a line of CSV text.
} /// If items are found to be null, empty strings are written out.
} /// </summary>
} /// <param name="items">The items.</param>
public void WriteLine(params String[] items) => this.WriteLine((IEnumerable<String>)items);
#endregion
/// <summary>
#region Helpers /// Writes a line of CSV text.
/// If items are found to be null, empty strings are written out.
/// <summary> /// </summary>
/// Saves the items to a stream. /// <param name="items">The items.</param>
/// It uses the Windows 1252 text encoding for output. public void WriteLine(IEnumerable<String> items) {
/// </summary> lock(this._syncLock) {
/// <typeparam name="T">The type of enumeration.</typeparam> Int32 length = items.Count();
/// <param name="items">The items.</param> Byte[] separatorBytes = this._encoding.GetBytes(new[] { this.SeparatorCharacter });
/// <param name="stream">The stream.</param> Byte[] endOfLineBytes = this._encoding.GetBytes(this.NewLineSequence);
/// <param name="truncateData"><c>true</c> if stream is truncated, default <c>false</c>.</param>
/// <returns>Number of item saved.</returns> // Declare state variables here to avoid recreation, allocation and
public static int SaveRecords<T>(IEnumerable<T> items, Stream stream, bool truncateData = false) // reassignment in every loop
{ Boolean needsEnclosing;
// truncate the file if it had data String textValue;
if (truncateData && stream.Length > 0) Byte[] output;
stream.SetLength(0);
for(Int32 i = 0; i < length; i++) {
using (var writer = new CsvWriter(stream)) textValue = items.ElementAt(i);
{
writer.WriteHeadings<T>(); // Determine if we need the string to be enclosed
writer.WriteObjects(items); // (it either contains an escape, new line, or separator char)
return (int)writer.Count; needsEnclosing = textValue.IndexOf(this.SeparatorCharacter) >= 0 || textValue.IndexOf(this.EscapeCharacter) >= 0 || textValue.IndexOf('\r') >= 0 || textValue.IndexOf('\n') >= 0;
}
} // Escape the escape characters by repeating them twice for every instance
textValue = textValue.Replace($"{this.EscapeCharacter}", $"{this.EscapeCharacter}{this.EscapeCharacter}");
/// <summary>
/// Saves the items to a CSV file. // Enclose the text value if we need to
/// If the file exits, it overwrites it. If it does not, it creates it. if(needsEnclosing) {
/// It uses the Windows 1252 text encoding for output. textValue = String.Format($"{this.EscapeCharacter}{textValue}{this.EscapeCharacter}", textValue);
/// </summary> }
/// <typeparam name="T">The type of enumeration.</typeparam>
/// <param name="items">The items.</param> // Get the bytes to write to the stream and write them
/// <param name="filePath">The file path.</param> output = this._encoding.GetBytes(textValue);
/// <returns>Number of item saved.</returns> this._outputStream.Write(output, 0, output.Length);
public static int SaveRecords<T>(IEnumerable<T> items, string filePath) => SaveRecords(items, File.OpenWrite(filePath), true);
// only write a separator if we are moving in between values.
#endregion // the last value should not be written.
if(i < length - 1) {
#region Generic, main Write Line Method this._outputStream.Write(separatorBytes, 0, separatorBytes.Length);
}
/// <summary> }
/// Writes a line of CSV text. Items are converted to strings.
/// If items are found to be null, empty strings are written out. // output the newline sequence
/// If items are not string, the ToStringInvariant() method is called on them. this._outputStream.Write(endOfLineBytes, 0, endOfLineBytes.Length);
/// </summary> this._mCount += 1;
/// <param name="items">The items.</param> }
public void WriteLine(params object[] items) }
=> WriteLine(items.Select(x => x == null ? string.Empty : x.ToStringInvariant()));
#endregion
/// <summary>
/// Writes a line of CSV text. Items are converted to strings. #region Write Object Method
/// If items are found to be null, empty strings are written out.
/// If items are not string, the ToStringInvariant() method is called on them. /// <summary>
/// </summary> /// Writes a row of CSV text. It handles the special cases where the object is
/// <param name="items">The items.</param> /// a dynamic object or and array. It also handles non-collection objects fine.
public void WriteLine(IEnumerable<object> items) /// If you do not like the way the output is handled, you can simply write an extension
=> WriteLine(items.Select(x => x == null ? string.Empty : x.ToStringInvariant())); /// method of this class and use the WriteLine method instead.
/// </summary>
/// <summary> /// <param name="item">The item.</param>
/// Writes a line of CSV text. /// <exception cref="System.ArgumentNullException">item.</exception>
/// If items are found to be null, empty strings are written out. public void WriteObject(Object item) {
/// </summary> if(item == null) {
/// <param name="items">The items.</param> throw new ArgumentNullException(nameof(item));
public void WriteLine(params string[] items) => WriteLine((IEnumerable<string>) items); }
/// <summary> lock(this._syncLock) {
/// Writes a line of CSV text. switch(item) {
/// If items are found to be null, empty strings are written out. case IDictionary typedItem:
/// </summary> this.WriteLine(this.GetFilteredDictionary(typedItem));
/// <param name="items">The items.</param> return;
public void WriteLine(IEnumerable<string> items) case ICollection typedItem:
{ this.WriteLine(typedItem.Cast<Object>());
lock (_syncLock) return;
{ default:
var length = items.Count(); this.WriteLine(this.GetFilteredTypeProperties(item.GetType()).Select(x => x.ToFormattedString(item)));
var separatorBytes = _encoding.GetBytes(new[] { SeparatorCharacter }); break;
var endOfLineBytes = _encoding.GetBytes(NewLineSequence); }
}
// Declare state variables here to avoid recreation, allocation and }
// reassignment in every loop
bool needsEnclosing; /// <summary>
string textValue; /// Writes a row of CSV text. It handles the special cases where the object is
byte[] output; /// a dynamic object or and array. It also handles non-collection objects fine.
/// If you do not like the way the output is handled, you can simply write an extension
for (var i = 0; i < length; i++) /// method of this class and use the WriteLine method instead.
{ /// </summary>
textValue = items.ElementAt(i); /// <typeparam name="T">The type of object to write.</typeparam>
/// <param name="item">The item.</param>
// Determine if we need the string to be enclosed public void WriteObject<T>(T item) => this.WriteObject(item as Object);
// (it either contains an escape, new line, or separator char)
needsEnclosing = textValue.IndexOf(SeparatorCharacter) >= 0 /// <summary>
|| textValue.IndexOf(EscapeCharacter) >= 0 /// Writes a set of items, one per line and atomically by repeatedly calling the
|| textValue.IndexOf('\r') >= 0 /// WriteObject method. For more info check out the description of the WriteObject
|| textValue.IndexOf('\n') >= 0; /// method.
/// </summary>
// Escape the escape characters by repeating them twice for every instance /// <typeparam name="T">The type of object to write.</typeparam>
textValue = textValue.Replace($"{EscapeCharacter}", /// <param name="items">The items.</param>
$"{EscapeCharacter}{EscapeCharacter}"); public void WriteObjects<T>(IEnumerable<T> items) {
lock(this._syncLock) {
// Enclose the text value if we need to foreach(T item in items) {
if (needsEnclosing) this.WriteObject(item);
textValue = string.Format($"{EscapeCharacter}{textValue}{EscapeCharacter}", textValue); }
}
// Get the bytes to write to the stream and write them }
output = _encoding.GetBytes(textValue);
_outputStream.Write(output, 0, output.Length); #endregion
// only write a separator if we are moving in between values. #region Write Headings Methods
// the last value should not be written.
if (i < length - 1) /// <summary>
_outputStream.Write(separatorBytes, 0, separatorBytes.Length); /// Writes the headings.
} /// </summary>
/// <param name="type">The type of object to extract headings.</param>
// output the newline sequence /// <exception cref="System.ArgumentNullException">type.</exception>
_outputStream.Write(endOfLineBytes, 0, endOfLineBytes.Length); public void WriteHeadings(Type type) {
_mCount += 1; if(type == null) {
} throw new ArgumentNullException(nameof(type));
} }
#endregion IEnumerable<Object> properties = this.GetFilteredTypeProperties(type).Select(p => p.Name).Cast<Object>();
this.WriteLine(properties);
#region Write Object Method }
/// <summary> /// <summary>
/// Writes a row of CSV text. It handles the special cases where the object is /// Writes the headings.
/// a dynamic object or and array. It also handles non-collection objects fine. /// </summary>
/// If you do not like the way the output is handled, you can simply write an extension /// <typeparam name="T">The type of object to extract headings.</typeparam>
/// method of this class and use the WriteLine method instead. public void WriteHeadings<T>() => this.WriteHeadings(typeof(T));
/// </summary>
/// <param name="item">The item.</param> /// <summary>
/// <exception cref="System.ArgumentNullException">item.</exception> /// Writes the headings.
public void WriteObject(object item) /// </summary>
{ /// <param name="dictionary">The dictionary to extract headings.</param>
if (item == null) /// <exception cref="System.ArgumentNullException">dictionary.</exception>
throw new ArgumentNullException(nameof(item)); public void WriteHeadings(IDictionary dictionary) {
if(dictionary == null) {
lock (_syncLock) throw new ArgumentNullException(nameof(dictionary));
{ }
switch (item)
{ this.WriteLine(this.GetFilteredDictionary(dictionary, true));
case IDictionary typedItem: }
WriteLine(GetFilteredDictionary(typedItem));
return; /// <summary>
case ICollection typedItem: /// Writes the headings.
WriteLine(typedItem.Cast<object>()); /// </summary>
return; /// <param name="obj">The object to extract headings.</param>
default: /// <exception cref="ArgumentNullException">obj.</exception>
WriteLine(GetFilteredTypeProperties(item.GetType()) public void WriteHeadings(Object obj) {
.Select(x => x.ToFormattedString(item))); if(obj == null) {
break; throw new ArgumentNullException(nameof(obj));
} }
}
} this.WriteHeadings(obj.GetType());
}
/// <summary>
/// Writes a row of CSV text. It handles the special cases where the object is #endregion
/// a dynamic object or and array. It also handles non-collection objects fine.
/// If you do not like the way the output is handled, you can simply write an extension #region IDisposable Support
/// method of this class and use the WriteLine method instead.
/// </summary> /// <inheritdoc />
/// <typeparam name="T">The type of object to write.</typeparam> public void Dispose() => this.Dispose(true);
/// <param name="item">The item.</param>
public void WriteObject<T>(T item) => WriteObject(item as object); /// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// <summary> /// </summary>
/// Writes a set of items, one per line and atomically by repeatedly calling the /// <param name="disposeAlsoManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
/// WriteObject method. For more info check out the description of the WriteObject protected virtual void Dispose(Boolean disposeAlsoManaged) {
/// method. if(this._isDisposing) {
/// </summary> return;
/// <typeparam name="T">The type of object to write.</typeparam> }
/// <param name="items">The items.</param>
public void WriteObjects<T>(IEnumerable<T> items) if(disposeAlsoManaged) {
{ if(this._leaveStreamOpen == false) {
lock (_syncLock) this._outputStream.Dispose();
{ }
foreach (var item in items) }
WriteObject(item);
} this._isDisposing = true;
} }
#endregion #endregion
#region Write Headings Methods #region Support Methods
/// <summary> private IEnumerable<String> GetFilteredDictionary(IDictionary dictionary, Boolean filterKeys = false) => dictionary.Keys.Cast<Object>()
/// Writes the headings. .Select(key => key == null ? String.Empty : key.ToStringInvariant())
/// </summary> .Where(stringKey => !this.IgnorePropertyNames.Contains(stringKey))
/// <param name="type">The type of object to extract headings.</param> .Select(stringKey => filterKeys ? stringKey : dictionary[stringKey] == null ? String.Empty : dictionary[stringKey].ToStringInvariant());
/// <exception cref="System.ArgumentNullException">type.</exception>
public void WriteHeadings(Type type) private IEnumerable<PropertyInfo> GetFilteredTypeProperties(Type type) => TypeCache.Retrieve(type, t => t.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanRead)).Where(p => !this.IgnorePropertyNames.Contains(p.Name));
{
if (type == null) #endregion
throw new ArgumentNullException(nameof(type));
}
var properties = GetFilteredTypeProperties(type).Select(p => p.Name).Cast<object>();
WriteLine(properties);
}
/// <summary>
/// Writes the headings.
/// </summary>
/// <typeparam name="T">The type of object to extract headings.</typeparam>
public void WriteHeadings<T>() => WriteHeadings(typeof(T));
/// <summary>
/// Writes the headings.
/// </summary>
/// <param name="dictionary">The dictionary to extract headings.</param>
/// <exception cref="System.ArgumentNullException">dictionary.</exception>
public void WriteHeadings(IDictionary dictionary)
{
if (dictionary == null)
throw new ArgumentNullException(nameof(dictionary));
WriteLine(GetFilteredDictionary(dictionary, true));
}
/// <summary>
/// Writes the headings.
/// </summary>
/// <param name="obj">The object to extract headings.</param>
/// <exception cref="ArgumentNullException">obj.</exception>
public void WriteHeadings(object obj)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
WriteHeadings(obj.GetType());
}
#endregion
#region IDisposable Support
/// <inheritdoc />
public void Dispose() => Dispose(true);
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposeAlsoManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposeAlsoManaged)
{
if (_isDisposing) return;
if (disposeAlsoManaged)
{
if (_leaveStreamOpen == false)
{
_outputStream.Dispose();
}
}
_isDisposing = true;
}
#endregion
#region Support Methods
private IEnumerable<string> GetFilteredDictionary(IDictionary dictionary, bool filterKeys = false)
=> dictionary
.Keys
.Cast<object>()
.Select(key => key == null ? string.Empty : key.ToStringInvariant())
.Where(stringKey => !IgnorePropertyNames.Contains(stringKey))
.Select(stringKey =>
filterKeys
? stringKey
: dictionary[stringKey] == null ? string.Empty : dictionary[stringKey].ToStringInvariant());
private IEnumerable<PropertyInfo> GetFilteredTypeProperties(Type type)
=> TypeCache.Retrieve(type, t =>
t.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanRead))
.Where(p => !IgnorePropertyNames.Contains(p.Name));
#endregion
}
} }

View File

@ -1,150 +1,126 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System;
namespace Swan.Formatters namespace Swan.Formatters {
{ internal class HumanizeJson {
internal class HumanizeJson private readonly StringBuilder _builder = new StringBuilder();
{ private readonly Int32 _indent;
private readonly StringBuilder _builder = new StringBuilder(); private readonly String _indentStr;
private readonly int _indent; private readonly Object _obj;
private readonly string _indentStr;
private readonly object _obj; public HumanizeJson(Object obj, Int32 indent) {
if(obj == null) {
public HumanizeJson(object obj, int indent) return;
{ }
if (obj == null)
{ this._indent = indent;
return; this._indentStr = new String(' ', indent * 4);
} this._obj = obj;
_indent = indent; this.ParseObject();
_indentStr = new string(' ', indent * 4); }
_obj = obj;
public String GetResult() => this._builder == null ? String.Empty : this._builder.ToString().TrimEnd();
ParseObject();
} private void ParseObject() {
switch(this._obj) {
public string GetResult() => _builder == null ? string.Empty : _builder.ToString().TrimEnd(); case Dictionary<String, Object> dictionary:
this.AppendDictionary(dictionary);
private void ParseObject() break;
{ case List<Object> list:
switch (_obj) this.AppendList(list);
{ break;
case Dictionary<string, object> dictionary: default:
AppendDictionary(dictionary); this.AppendString();
break; break;
case List<object> list: }
AppendList(list); }
break;
default: private void AppendDictionary(Dictionary<String, Object> objects) {
AppendString(); foreach(KeyValuePair<String, Object> kvp in objects) {
break; if(kvp.Value == null) {
} continue;
} }
private void AppendDictionary(Dictionary<string, object> objects) Boolean writeOutput = false;
{
foreach (var kvp in objects) switch(kvp.Value) {
{ case Dictionary<String, Object> valueDictionary:
if (kvp.Value == null) continue; if(valueDictionary.Count > 0) {
writeOutput = true;
var writeOutput = false; _ = this._builder.Append($"{this._indentStr}{kvp.Key,-16}: object").AppendLine();
}
switch (kvp.Value)
{ break;
case Dictionary<string, object> valueDictionary: case List<Object> valueList:
if (valueDictionary.Count > 0) if(valueList.Count > 0) {
{ writeOutput = true;
writeOutput = true; _ = this._builder.Append($"{this._indentStr}{kvp.Key,-16}: array[{valueList.Count}]").AppendLine();
_builder }
.Append($"{_indentStr}{kvp.Key,-16}: object")
.AppendLine(); break;
} default:
writeOutput = true;
break; _ = this._builder.Append($"{this._indentStr}{kvp.Key,-16}: ");
case List<object> valueList: break;
if (valueList.Count > 0) }
{
writeOutput = true; if(writeOutput) {
_builder _ = this._builder.AppendLine(new HumanizeJson(kvp.Value, this._indent + 1).GetResult());
.Append($"{_indentStr}{kvp.Key,-16}: array[{valueList.Count}]") }
.AppendLine(); }
} }
break; private void AppendList(List<Object> objects) {
default: Int32 index = 0;
writeOutput = true; foreach(Object value in objects) {
_builder.Append($"{_indentStr}{kvp.Key,-16}: "); Boolean writeOutput = false;
break;
} switch(value) {
case Dictionary<String, Object> valueDictionary:
if (writeOutput) if(valueDictionary.Count > 0) {
_builder.AppendLine(new HumanizeJson(kvp.Value, _indent + 1).GetResult()); writeOutput = true;
} _ = this._builder.Append($"{this._indentStr}[{index}]: object").AppendLine();
} }
private void AppendList(List<object> objects) break;
{ case List<Object> valueList:
var index = 0; if(valueList.Count > 0) {
foreach (var value in objects) writeOutput = true;
{ _ = this._builder.Append($"{this._indentStr}[{index}]: array[{valueList.Count}]").AppendLine();
var writeOutput = false; }
switch (value) break;
{ default:
case Dictionary<string, object> valueDictionary: writeOutput = true;
if (valueDictionary.Count > 0) _ = this._builder.Append($"{this._indentStr}[{index}]: ");
{ break;
writeOutput = true; }
_builder
.Append($"{_indentStr}[{index}]: object") index++;
.AppendLine();
} if(writeOutput) {
_ = this._builder.AppendLine(new HumanizeJson(value, this._indent + 1).GetResult());
break; }
case List<object> valueList: }
if (valueList.Count > 0) }
{
writeOutput = true; private void AppendString() {
_builder String stringValue = this._obj.ToString();
.Append($"{_indentStr}[{index}]: array[{valueList.Count}]")
.AppendLine(); if(stringValue.Length + this._indentStr.Length > 96 || stringValue.IndexOf('\r') >= 0 ||
} stringValue.IndexOf('\n') >= 0) {
_ = this._builder.AppendLine();
break; IEnumerable<String> stringLines = stringValue.ToLines().Select(l => l.Trim());
default:
writeOutput = true; foreach(String line in stringLines) {
_builder.Append($"{_indentStr}[{index}]: "); _ = this._builder.AppendLine($"{this._indentStr}{line}");
break; }
} } else {
_ = this._builder.Append($"{stringValue}");
index++; }
}
if (writeOutput) }
_builder.AppendLine(new HumanizeJson(value, _indent + 1).GetResult());
}
}
private void AppendString()
{
var stringValue = _obj.ToString();
if (stringValue.Length + _indentStr.Length > 96 || stringValue.IndexOf('\r') >= 0 ||
stringValue.IndexOf('\n') >= 0)
{
_builder.AppendLine();
var stringLines = stringValue.ToLines().Select(l => l.Trim());
foreach (var line in stringLines)
{
_builder.AppendLine($"{_indentStr}{line}");
}
}
else
{
_builder.Append($"{stringValue}");
}
}
}
} }

View File

@ -1,4 +1,5 @@
using System; #nullable enable
using System;
using System.Collections; using System.Collections;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@ -7,332 +8,252 @@ using System.Reflection;
using System.Text; using System.Text;
using Swan.Reflection; using Swan.Reflection;
namespace Swan.Formatters namespace Swan.Formatters {
{ /// <summary>
/// <summary> /// A very simple, light-weight JSON library written by Mario
/// A very simple, light-weight JSON library written by Mario /// to teach Geo how things are done
/// to teach Geo how things are done ///
/// /// This is an useful helper for small tasks but it doesn't represent a full-featured
/// This is an useful helper for small tasks but it doesn't represent a full-featured /// serializer such as the beloved Json.NET.
/// serializer such as the beloved Json.NET. /// </summary>
/// </summary> public static partial class Json {
public static partial class Json private class Converter {
{ private static readonly ConcurrentDictionary<MemberInfo, String> MemberInfoNameCache = new ConcurrentDictionary<MemberInfo, global::System.String>();
private class Converter
{ private static readonly ConcurrentDictionary<Type, Type> ListAddMethodCache = new ConcurrentDictionary<Type, Type>();
private static readonly ConcurrentDictionary<MemberInfo, string> MemberInfoNameCache =
new ConcurrentDictionary<MemberInfo, string>(); private readonly Object? _target;
private readonly Type _targetType;
private static readonly ConcurrentDictionary<Type, Type> ListAddMethodCache = new ConcurrentDictionary<Type, Type>(); private readonly Boolean _includeNonPublic;
private readonly JsonSerializerCase _jsonSerializerCase;
private readonly object? _target;
private readonly Type _targetType; private Converter(Object? source, Type targetType, ref Object? targetInstance, Boolean includeNonPublic, JsonSerializerCase jsonSerializerCase) {
private readonly bool _includeNonPublic; this._targetType = targetInstance != null ? targetInstance.GetType() : targetType;
private readonly JsonSerializerCase _jsonSerializerCase; this._includeNonPublic = includeNonPublic;
this._jsonSerializerCase = jsonSerializerCase;
private Converter(
object? source, if(source == null) {
Type targetType, return;
ref object? targetInstance, }
bool includeNonPublic,
JsonSerializerCase jsonSerializerCase) Type sourceType = source.GetType();
{
_targetType = targetInstance != null ? targetInstance.GetType() : targetType; if(this._targetType == null || this._targetType == typeof(Object)) {
_includeNonPublic = includeNonPublic; this._targetType = sourceType;
_jsonSerializerCase = jsonSerializerCase; }
if (source == null) if(sourceType == this._targetType) {
{ this._target = source;
return; return;
} }
var sourceType = source.GetType(); if(!this.TrySetInstance(targetInstance, source, ref this._target)) {
return;
if (_targetType == null || _targetType == typeof(object)) _targetType = sourceType; }
if (sourceType == _targetType)
{ this.ResolveObject(source, ref this._target);
_target = source; }
return;
} internal static Object? FromJsonResult(Object? source, JsonSerializerCase jsonSerializerCase, Type? targetType = null, Boolean includeNonPublic = false) {
Object? nullRef = null;
if (!TrySetInstance(targetInstance, source, ref _target)) return new Converter(source, targetType ?? typeof(Object), ref nullRef, includeNonPublic, jsonSerializerCase).GetResult();
return; }
ResolveObject(source, ref _target); 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!);
internal static object? FromJsonResult(
object? source, private static void GetByteArray(String sourceString, ref Object? target) {
JsonSerializerCase jsonSerializerCase, try {
Type? targetType = null, target = Convert.FromBase64String(sourceString);
bool includeNonPublic = false) } // Try conversion from Base 64
{ catch(FormatException) {
object? nullRef = null; target = Encoding.UTF8.GetBytes(sourceString);
return new Converter(source, targetType ?? typeof(object), ref nullRef, includeNonPublic, jsonSerializerCase).GetResult(); } // Get the string bytes in UTF8
} }
private static object? FromJsonResult(object source, private Object GetSourcePropertyValue(IDictionary<String, Object> sourceProperties, MemberInfo targetProperty) {
Type targetType, String targetPropertyName = MemberInfoNameCache.GetOrAdd(targetProperty, x => AttributeCache.DefaultCache.Value.RetrieveOne<JsonPropertyAttribute>(x)?.PropertyName ?? x.Name.GetNameWithCase(this._jsonSerializerCase));
ref object? targetInstance,
bool includeNonPublic) return sourceProperties.GetValueOrDefault(targetPropertyName);
{ }
return new Converter(source, targetType, ref targetInstance, includeNonPublic, JsonSerializerCase.None).GetResult();
} private Boolean TrySetInstance(Object? targetInstance, Object source, ref Object? target) {
if(targetInstance == null) {
private static Type? GetAddMethodParameterType(Type targetType) // Try to create a default instance
=> ListAddMethodCache.GetOrAdd(targetType, try {
x => x.GetMethods() source.CreateTarget(this._targetType, this._includeNonPublic, ref target);
.FirstOrDefault( } catch {
m => m.Name == AddMethodName && m.IsPublic && m.GetParameters().Length == 1)? return false;
.GetParameters()[0] }
.ParameterType); } else {
target = targetInstance;
private static void GetByteArray(string sourceString, ref object? target) }
{
try return true;
{ }
target = Convert.FromBase64String(sourceString);
} // Try conversion from Base 64 private Object? GetResult() => this._target ?? this._targetType.GetDefault();
catch (FormatException)
{ private void ResolveObject(Object source, ref Object? target) {
target = Encoding.UTF8.GetBytes(sourceString); switch(source) {
} // Get the string bytes in UTF8 // 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[]):
private object GetSourcePropertyValue( GetByteArray(sourceString, ref target);
IDictionary<string, object> sourceProperties, break;
MemberInfo targetProperty)
{ // Case 1.1: Source is Dictionary, Target is IDictionary
var targetPropertyName = MemberInfoNameCache.GetOrAdd( case Dictionary<String, Object> sourceProperties when target is IDictionary targetDictionary:
targetProperty, this.PopulateDictionary(sourceProperties, targetDictionary);
x => AttributeCache.DefaultCache.Value.RetrieveOne<JsonPropertyAttribute>(x)?.PropertyName ?? x.Name.GetNameWithCase(_jsonSerializerCase)); break;
return sourceProperties.GetValueOrDefault(targetPropertyName); // Case 1.2: Source is Dictionary, Target is not IDictionary (i.e. it is a complex type)
} case Dictionary<String, Object> sourceProperties:
this.PopulateObject(sourceProperties);
private bool TrySetInstance(object? targetInstance, object source, ref object? target) break;
{
if (targetInstance == null) // Case 2.1: Source is List, Target is Array
{ case List<Object> sourceList when target is Array targetArray:
// Try to create a default instance this.PopulateArray(sourceList, targetArray);
try break;
{
source.CreateTarget(_targetType, _includeNonPublic, ref target); // Case 2.2: Source is List, Target is IList
} case List<Object> sourceList when target is IList targetList:
catch this.PopulateIList(sourceList, targetList);
{ break;
return false;
} // Case 3: Source is a simple type; Attempt conversion
} default:
else String sourceStringValue = source.ToStringInvariant();
{
target = targetInstance; // Handle basic types or enumerations if not
} if(!this._targetType.TryParseBasicType(sourceStringValue, out target)) {
this.GetEnumValue(sourceStringValue, ref target);
return true; }
}
break;
private object? GetResult() => _target ?? _targetType.GetDefault(); }
}
private void ResolveObject(object source, ref object? target)
{ private void PopulateIList(IEnumerable<Object> objects, IList list) {
switch (source) Type? parameterType = GetAddMethodParameterType(this._targetType);
{ if(parameterType == null) {
// Case 0: Special Cases Handling (Source and Target are of specific convertible types) return;
// Case 0.1: Source is string, Target is byte[] }
case string sourceString when _targetType == typeof(byte[]):
GetByteArray(sourceString, ref target); foreach(Object item in objects) {
break; try {
_ = list.Add(FromJsonResult(item, this._jsonSerializerCase, parameterType, this._includeNonPublic));
// Case 1.1: Source is Dictionary, Target is IDictionary } catch {
case Dictionary<string, object> sourceProperties when target is IDictionary targetDictionary: // ignored
PopulateDictionary(sourceProperties, targetDictionary); }
break; }
}
// Case 1.2: Source is Dictionary, Target is not IDictionary (i.e. it is a complex type)
case Dictionary<string, object> sourceProperties: private void PopulateArray(IList<Object> objects, Array array) {
PopulateObject(sourceProperties); Type? elementType = this._targetType.GetElementType();
break;
for(Int32 i = 0; i < objects.Count; i++) {
// Case 2.1: Source is List, Target is Array try {
case List<object> sourceList when target is Array targetArray: Object? targetItem = FromJsonResult(objects[i], this._jsonSerializerCase, elementType, this._includeNonPublic);
PopulateArray(sourceList, targetArray); array.SetValue(targetItem, i);
break; } catch {
// ignored
// Case 2.2: Source is List, Target is IList }
case List<object> sourceList when target is IList targetList: }
PopulateIList(sourceList, targetList); }
break;
private void GetEnumValue(String sourceStringValue, ref Object? target) {
// Case 3: Source is a simple type; Attempt conversion Type? enumType = Nullable.GetUnderlyingType(this._targetType);
default: if(enumType == null && this._targetType.IsEnum) {
var sourceStringValue = source.ToStringInvariant(); enumType = this._targetType;
}
// Handle basic types or enumerations if not
if (!_targetType.TryParseBasicType(sourceStringValue, out target)) if(enumType == null) {
GetEnumValue(sourceStringValue, ref target); return;
}
break;
} try {
} target = Enum.Parse(enumType, sourceStringValue);
} catch {
private void PopulateIList(IEnumerable<object> objects, IList list) // ignored
{ }
var parameterType = GetAddMethodParameterType(_targetType); }
if (parameterType == null) return;
private void PopulateDictionary(IDictionary<String, Object> sourceProperties, IDictionary targetDictionary) {
foreach (var item in objects) // find the add method of the target dictionary
{ MethodInfo addMethod = this._targetType.GetMethods().FirstOrDefault(m => m.Name == AddMethodName && m.IsPublic && m.GetParameters().Length == 2);
try
{ // skip if we don't have a compatible add method
list.Add(FromJsonResult( if(addMethod == null) {
item, return;
_jsonSerializerCase, }
parameterType,
_includeNonPublic)); global::System.Reflection.ParameterInfo[] addMethodParameters = addMethod.GetParameters();
} if(addMethodParameters[0].ParameterType != typeof(String)) {
catch return;
{ }
// ignored
} // Retrieve the target entry type
} Type targetEntryType = addMethodParameters[1].ParameterType;
}
// Add the items to the target dictionary
private void PopulateArray(IList<object> objects, Array array) foreach(KeyValuePair<String, Object> sourceProperty in sourceProperties) {
{ try {
var elementType = _targetType.GetElementType(); Object? targetEntryValue = FromJsonResult(sourceProperty.Value, this._jsonSerializerCase, targetEntryType, this._includeNonPublic);
targetDictionary.Add(sourceProperty.Key, targetEntryValue);
for (var i = 0; i < objects.Count; i++) } catch {
{ // ignored
try }
{ }
var targetItem = FromJsonResult( }
objects[i],
_jsonSerializerCase, private void PopulateObject(IDictionary<String, Object> sourceProperties) {
elementType, if(this._targetType.IsValueType) {
_includeNonPublic); this.PopulateFields(sourceProperties);
array.SetValue(targetItem, i); }
}
catch this.PopulateProperties(sourceProperties);
{ }
// ignored
} private void PopulateProperties(IDictionary<String, Object> sourceProperties) {
} global::System.Collections.Generic.IEnumerable<global::System.Reflection.PropertyInfo> properties = PropertyTypeCache.DefaultCache.Value.RetrieveFilteredProperties(this._targetType, false, p => p.CanWrite);
}
foreach(PropertyInfo property in properties) {
private void GetEnumValue(string sourceStringValue, ref object? target) Object sourcePropertyValue = this.GetSourcePropertyValue(sourceProperties, property);
{ if(sourcePropertyValue == null) {
var enumType = Nullable.GetUnderlyingType(_targetType); continue;
if (enumType == null && _targetType.IsEnum) enumType = _targetType; }
if (enumType == null) return;
try {
try Object? currentPropertyValue = !property.PropertyType.IsArray ? property?.GetCacheGetMethod(this._includeNonPublic)!(this._target!) : null;
{
target = Enum.Parse(enumType, sourceStringValue); Object? targetPropertyValue = FromJsonResult(sourcePropertyValue, property.PropertyType, ref currentPropertyValue, this._includeNonPublic);
}
catch property?.GetCacheSetMethod(this._includeNonPublic)!(this._target!, new[] { targetPropertyValue }!);
{ } catch {
// ignored // ignored
} }
} }
}
private void PopulateDictionary(IDictionary<string, object> sourceProperties, IDictionary targetDictionary)
{ private void PopulateFields(IDictionary<String, Object> sourceProperties) {
// find the add method of the target dictionary foreach(FieldInfo field in FieldTypeCache.DefaultCache.Value.RetrieveAllFields(this._targetType)) {
var addMethod = _targetType.GetMethods() Object sourcePropertyValue = this.GetSourcePropertyValue(sourceProperties, field);
.FirstOrDefault( if(sourcePropertyValue == null) {
m => m.Name == AddMethodName && m.IsPublic && m.GetParameters().Length == 2); continue;
}
// skip if we don't have a compatible add method
if (addMethod == null) return; Object? targetPropertyValue = FromJsonResult(sourcePropertyValue, this._jsonSerializerCase, field.FieldType, this._includeNonPublic);
var addMethodParameters = addMethod.GetParameters();
if (addMethodParameters[0].ParameterType != typeof(string)) return; try {
field.SetValue(this._target, targetPropertyValue);
// Retrieve the target entry type } catch {
var targetEntryType = addMethodParameters[1].ParameterType; // ignored
}
// Add the items to the target dictionary }
foreach (var sourceProperty in sourceProperties) }
{ }
try }
{
var targetEntryValue = FromJsonResult(
sourceProperty.Value,
_jsonSerializerCase,
targetEntryType,
_includeNonPublic);
targetDictionary.Add(sourceProperty.Key, targetEntryValue);
}
catch
{
// ignored
}
}
}
private void PopulateObject(IDictionary<string, object> sourceProperties)
{
if (_targetType.IsValueType)
{
PopulateFields(sourceProperties);
}
PopulateProperties(sourceProperties);
}
private void PopulateProperties(IDictionary<string, object> sourceProperties)
{
var properties = PropertyTypeCache.DefaultCache.Value.RetrieveFilteredProperties(_targetType, false, p => p.CanWrite);
foreach (var property in properties)
{
var sourcePropertyValue = GetSourcePropertyValue(sourceProperties, property);
if (sourcePropertyValue == null) continue;
try
{
var currentPropertyValue = !property.PropertyType.IsArray
? property.GetCacheGetMethod(_includeNonPublic)(_target)
: null;
var targetPropertyValue = FromJsonResult(
sourcePropertyValue,
property.PropertyType,
ref currentPropertyValue,
_includeNonPublic);
property.GetCacheSetMethod(_includeNonPublic)(_target, new[] { targetPropertyValue });
}
catch
{
// ignored
}
}
}
private void PopulateFields(IDictionary<string, object> sourceProperties)
{
foreach (var field in FieldTypeCache.DefaultCache.Value.RetrieveAllFields(_targetType))
{
var sourcePropertyValue = GetSourcePropertyValue(sourceProperties, field);
if (sourcePropertyValue == null) continue;
var targetPropertyValue = FromJsonResult(
sourcePropertyValue,
_jsonSerializerCase,
field.FieldType,
_includeNonPublic);
try
{
field.SetValue(_target, targetPropertyValue);
}
catch
{
// ignored
}
}
}
}
}
} }

View File

@ -1,348 +1,332 @@
using System; #nullable enable
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
namespace Swan.Formatters namespace Swan.Formatters {
{ /// <summary>
/// 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.
/// </summary>
public partial class Json {
/// <summary> /// <summary>
/// A very simple, light-weight JSON library written by Mario /// A simple JSON Deserializer.
/// 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.
/// </summary> /// </summary>
public partial class Json private class Deserializer {
{ #region State Variables
/// <summary>
/// A simple JSON Deserializer. private readonly Object? _result;
/// </summary> private readonly String _json;
private class Deserializer
{ private Dictionary<String, Object?>? _resultObject;
#region State Variables private List<Object?>? _resultArray;
private ReadState _state = ReadState.WaitingForRootOpen;
private readonly object? _result; private String? _currentFieldName;
private readonly string _json;
private Int32 _index;
private Dictionary<string, object> _resultObject;
private List<object> _resultArray; #endregion
private ReadState _state = ReadState.WaitingForRootOpen;
private string? _currentFieldName; private Deserializer(String? json, Int32 startIndex) {
if(json == null) {
private int _index; this._json = "";
return;
#endregion }
this._json = json;
private Deserializer(string json, int startIndex)
{ for(this._index = startIndex; this._index < this._json.Length; this._index++) {
_json = json; switch(this._state) {
case ReadState.WaitingForRootOpen:
for (_index = startIndex; _index < _json.Length; _index++) this.WaitForRootOpen();
{ continue;
switch (_state) case ReadState.WaitingForField when Char.IsWhiteSpace(this._json, this._index):
{ continue;
case ReadState.WaitingForRootOpen: case ReadState.WaitingForField when this._resultObject != null && this._json[this._index] == CloseObjectChar || this._resultArray != null && this._json[this._index] == CloseArrayChar:
WaitForRootOpen(); // Handle empty arrays and empty objects
continue; this._result = this._resultObject ?? this._resultArray as Object;
case ReadState.WaitingForField when char.IsWhiteSpace(_json, _index): return;
continue; case ReadState.WaitingForField when this._json[this._index] != StringQuotedChar:
case ReadState.WaitingForField when (_resultObject != null && _json[_index] == CloseObjectChar) throw this.CreateParserException($"'{StringQuotedChar}'");
|| (_resultArray != null && _json[_index] == CloseArrayChar): case ReadState.WaitingForField: {
// Handle empty arrays and empty objects Int32 charCount = this.GetFieldNameCount();
_result = _resultObject ?? _resultArray as object;
return; this._currentFieldName = Unescape(this._json.SliceLength(this._index + 1, charCount));
case ReadState.WaitingForField when _json[_index] != StringQuotedChar: this._index += charCount + 1;
throw CreateParserException($"'{StringQuotedChar}'"); this._state = ReadState.WaitingForColon;
case ReadState.WaitingForField: continue;
{ }
var charCount = GetFieldNameCount();
case ReadState.WaitingForColon when Char.IsWhiteSpace(this._json, this._index):
_currentFieldName = Unescape(_json.SliceLength(_index + 1, charCount)); continue;
_index += charCount + 1; case ReadState.WaitingForColon when this._json[this._index] != ValueSeparatorChar:
_state = ReadState.WaitingForColon; throw this.CreateParserException($"'{ValueSeparatorChar}'");
continue; case ReadState.WaitingForColon:
} this._state = ReadState.WaitingForValue;
continue;
case ReadState.WaitingForColon when char.IsWhiteSpace(_json, _index): case ReadState.WaitingForValue when Char.IsWhiteSpace(this._json, this._index):
continue; continue;
case ReadState.WaitingForColon when _json[_index] != ValueSeparatorChar: case ReadState.WaitingForValue when this._resultObject != null && this._json[this._index] == CloseObjectChar || this._resultArray != null && this._json[this._index] == CloseArrayChar:
throw CreateParserException($"'{ValueSeparatorChar}'"); // Handle empty arrays and empty objects
case ReadState.WaitingForColon: this._result = this._resultObject ?? this._resultArray as Object;
_state = ReadState.WaitingForValue; return;
continue; case ReadState.WaitingForValue:
case ReadState.WaitingForValue when char.IsWhiteSpace(_json, _index): this.ExtractValue();
continue; continue;
case ReadState.WaitingForValue when (_resultObject != null && _json[_index] == CloseObjectChar) }
|| (_resultArray != null && _json[_index] == CloseArrayChar):
// Handle empty arrays and empty objects if(this._state != ReadState.WaitingForNextOrRootClose || Char.IsWhiteSpace(this._json, this._index)) {
_result = _resultObject ?? _resultArray as object; continue;
return; }
case ReadState.WaitingForValue:
ExtractValue(); if(this._json[this._index] == FieldSeparatorChar) {
continue; if(this._resultObject != null) {
} this._state = ReadState.WaitingForField;
this._currentFieldName = null;
if (_state != ReadState.WaitingForNextOrRootClose || char.IsWhiteSpace(_json, _index)) continue; continue;
}
if (_json[_index] == FieldSeparatorChar)
{ this._state = ReadState.WaitingForValue;
if (_resultObject != null) continue;
{ }
_state = ReadState.WaitingForField;
_currentFieldName = null; if((this._resultObject == null || this._json[this._index] != CloseObjectChar) && (this._resultArray == null || this._json[this._index] != CloseArrayChar)) {
continue; throw this.CreateParserException($"'{FieldSeparatorChar}' '{CloseObjectChar}' or '{CloseArrayChar}'");
} }
_state = ReadState.WaitingForValue; this._result = this._resultObject ?? this._resultArray as Object;
continue; return;
} }
}
if ((_resultObject == null || _json[_index] != CloseObjectChar) &&
(_resultArray == null || _json[_index] != CloseArrayChar)) internal static Object? DeserializeInternal(String? json) => new Deserializer(json, 0)._result;
{
throw CreateParserException($"'{FieldSeparatorChar}' '{CloseObjectChar}' or '{CloseArrayChar}'"); private void WaitForRootOpen() {
} if(Char.IsWhiteSpace(this._json, this._index)) {
return;
_result = _resultObject ?? _resultArray as object; }
return;
} switch(this._json[this._index]) {
} case OpenObjectChar:
this._resultObject = new Dictionary<String, Object?>();
internal static object? DeserializeInternal(string json) => new Deserializer(json, 0)._result; this._state = ReadState.WaitingForField;
return;
private void WaitForRootOpen() case OpenArrayChar:
{ this._resultArray = new List<Object?>();
if (char.IsWhiteSpace(_json, _index)) return; this._state = ReadState.WaitingForValue;
return;
switch (_json[_index]) default:
{ throw this.CreateParserException($"'{OpenObjectChar}' or '{OpenArrayChar}'");
case OpenObjectChar: }
_resultObject = new Dictionary<string, object>(); }
_state = ReadState.WaitingForField;
return; private void ExtractValue() {
case OpenArrayChar: // determine the value based on what it starts with
_resultArray = new List<object>(); switch(this._json[this._index]) {
_state = ReadState.WaitingForValue; case StringQuotedChar: // expect a string
return; this.ExtractStringQuoted();
default: break;
throw CreateParserException($"'{OpenObjectChar}' or '{OpenArrayChar}'");
} case OpenObjectChar: // expect object
} case OpenArrayChar: // expect array
this.ExtractObject();
private void ExtractValue() break;
{
// determine the value based on what it starts with case 't': // expect true
switch (_json[_index]) this.ExtractConstant(TrueLiteral, true);
{ break;
case StringQuotedChar: // expect a string
ExtractStringQuoted(); case 'f': // expect false
break; this.ExtractConstant(FalseLiteral, false);
break;
case OpenObjectChar: // expect object
case OpenArrayChar: // expect array case 'n': // expect null
ExtractObject(); this.ExtractConstant(NullLiteral, null);
break; break;
case 't': // expect true default: // expect number
ExtractConstant(TrueLiteral, true); this.ExtractNumber();
break; break;
}
case 'f': // expect false
ExtractConstant(FalseLiteral, false); this._currentFieldName = null;
break; this._state = ReadState.WaitingForNextOrRootClose;
}
case 'n': // expect null
ExtractConstant(NullLiteral, null); private static String Unescape(String str) {
break; // check if we need to unescape at all
if(str.IndexOf(StringEscapeChar) < 0) {
default: // expect number return str;
ExtractNumber(); }
break;
} StringBuilder builder = new StringBuilder(str.Length);
for(Int32 i = 0; i < str.Length; i++) {
_currentFieldName = null; if(str[i] != StringEscapeChar) {
_state = ReadState.WaitingForNextOrRootClose; _ = builder.Append(str[i]);
} continue;
}
private static string Unescape(string str)
{ if(i + 1 > str.Length - 1) {
// check if we need to unescape at all break;
if (str.IndexOf(StringEscapeChar) < 0) }
return str;
// escape sequence begins here
var builder = new StringBuilder(str.Length); switch(str[i + 1]) {
for (var i = 0; i < str.Length; i++) case 'u':
{ i = ExtractEscapeSequence(str, i, builder);
if (str[i] != StringEscapeChar) break;
{ case 'b':
builder.Append(str[i]); _ = builder.Append('\b');
continue; i += 1;
} break;
case 't':
if (i + 1 > str.Length - 1) _ = builder.Append('\t');
break; i += 1;
break;
// escape sequence begins here case 'n':
switch (str[i + 1]) _ = builder.Append('\n');
{ i += 1;
case 'u': break;
i = ExtractEscapeSequence(str, i, builder); case 'f':
break; _ = builder.Append('\f');
case 'b': i += 1;
builder.Append('\b'); break;
i += 1; case 'r':
break; _ = builder.Append('\r');
case 't': i += 1;
builder.Append('\t'); break;
i += 1; default:
break; _ = builder.Append(str[i + 1]);
case 'n': i += 1;
builder.Append('\n'); break;
i += 1; }
break; }
case 'f':
builder.Append('\f'); return builder.ToString();
i += 1; }
break;
case 'r': private static Int32 ExtractEscapeSequence(String str, Int32 i, StringBuilder builder) {
builder.Append('\r'); Int32 startIndex = i + 2;
i += 1; Int32 endIndex = i + 5;
break; if(endIndex > str.Length - 1) {
default: _ = builder.Append(str[i + 1]);
builder.Append(str[i + 1]); i += 1;
i += 1; return i;
break; }
}
} Byte[] hexCode = str.Slice(startIndex, endIndex).ConvertHexadecimalToBytes();
_ = builder.Append(Encoding.BigEndianUnicode.GetChars(hexCode));
return builder.ToString(); i += 5;
} return i;
}
private static int ExtractEscapeSequence(string str, int i, StringBuilder builder)
{ private Int32 GetFieldNameCount() {
var startIndex = i + 2; Int32 charCount = 0;
var endIndex = i + 5; for(Int32 j = this._index + 1; j < this._json.Length; j++) {
if (endIndex > str.Length - 1) if(this._json[j] == StringQuotedChar && this._json[j - 1] != StringEscapeChar) {
{ break;
builder.Append(str[i + 1]); }
i += 1;
return i; charCount++;
} }
var hexCode = str.Slice(startIndex, endIndex).ConvertHexadecimalToBytes(); return charCount;
builder.Append(Encoding.BigEndianUnicode.GetChars(hexCode)); }
i += 5;
return i; private void ExtractObject() {
} // Extract and set the value
Deserializer deserializer = new Deserializer(this._json, this._index);
private int GetFieldNameCount()
{ if(this._currentFieldName != null) {
var charCount = 0; this._resultObject![this._currentFieldName] = deserializer._result!;
for (var j = _index + 1; j < _json.Length; j++) } else {
{ this._resultArray!.Add(deserializer._result!);
if (_json[j] == StringQuotedChar && _json[j - 1] != StringEscapeChar) }
break;
this._index = deserializer._index;
charCount++; }
}
private void ExtractNumber() {
return charCount; Int32 charCount = 0;
} for(Int32 j = this._index; j < this._json.Length; j++) {
if(Char.IsWhiteSpace(this._json[j]) || this._json[j] == FieldSeparatorChar || this._resultObject != null && this._json[j] == CloseObjectChar || this._resultArray != null && this._json[j] == CloseArrayChar) {
private void ExtractObject() break;
{ }
// Extract and set the value
var deserializer = new Deserializer(_json, _index); charCount++;
}
if (_currentFieldName != null)
_resultObject[_currentFieldName] = deserializer._result; // Extract and set the value
else String stringValue = this._json.SliceLength(this._index, charCount);
_resultArray.Add(deserializer._result);
if(Decimal.TryParse(stringValue, System.Globalization.NumberStyles.Number, System.Globalization.CultureInfo.InvariantCulture, out Decimal value) == false) {
_index = deserializer._index; throw this.CreateParserException("[number]");
} }
private void ExtractNumber() if(this._currentFieldName != null) {
{ this._resultObject![this._currentFieldName] = value;
var charCount = 0; } else {
for (var j = _index; j < _json.Length; j++) this._resultArray!.Add(value);
{ }
if (char.IsWhiteSpace(_json[j]) || _json[j] == FieldSeparatorChar
|| (_resultObject != null && _json[j] == CloseObjectChar) this._index += charCount - 1;
|| (_resultArray != null && _json[j] == CloseArrayChar)) }
break;
private void ExtractConstant(String boolValue, Boolean? value) {
charCount++; if(this._json.SliceLength(this._index, boolValue.Length) != boolValue) {
} throw this.CreateParserException($"'{ValueSeparatorChar}'");
}
// Extract and set the value
var stringValue = _json.SliceLength(_index, charCount); // Extract and set the value
if(this._currentFieldName != null) {
if (decimal.TryParse(stringValue, System.Globalization.NumberStyles.Number, System.Globalization.CultureInfo.InvariantCulture, out var value) == false) this._resultObject![this._currentFieldName] = value;
throw CreateParserException("[number]"); } else {
this._resultArray!.Add(value);
if (_currentFieldName != null) }
_resultObject[_currentFieldName] = value;
else this._index += boolValue.Length - 1;
_resultArray.Add(value); }
_index += charCount - 1; private void ExtractStringQuoted() {
} Int32 charCount = 0;
Boolean escapeCharFound = false;
private void ExtractConstant(string boolValue, bool? value) for(Int32 j = this._index + 1; j < this._json.Length; j++) {
{ if(this._json[j] == StringQuotedChar && !escapeCharFound) {
if (_json.SliceLength(_index, boolValue.Length) != boolValue) break;
throw CreateParserException($"'{ValueSeparatorChar}'"); }
// Extract and set the value escapeCharFound = this._json[j] == StringEscapeChar && !escapeCharFound;
if (_currentFieldName != null) charCount++;
_resultObject[_currentFieldName] = value; }
else
_resultArray.Add(value); // Extract and set the value
String value = Unescape(this._json.SliceLength(this._index + 1, charCount));
_index += boolValue.Length - 1; if(this._currentFieldName != null) {
} this._resultObject![this._currentFieldName] = value;
} else {
private void ExtractStringQuoted() this._resultArray!.Add(value);
{ }
var charCount = 0;
var escapeCharFound = false; this._index += charCount + 1;
for (var j = _index + 1; j < _json.Length; j++) }
{
if (_json[j] == StringQuotedChar && !escapeCharFound) private FormatException CreateParserException(String expected) {
break; Tuple<Int32, Int32> textPosition = this._json.TextPositionAt(this._index);
return new FormatException($"Parser error (Line {textPosition.Item1}, Col {textPosition.Item2}, State {this._state}): Expected {expected} but got '{this._json[this._index]}'.");
escapeCharFound = _json[j] == StringEscapeChar && !escapeCharFound; }
charCount++;
} /// <summary>
/// Defines the different JSON read states.
// Extract and set the value /// </summary>
var value = Unescape(_json.SliceLength(_index + 1, charCount)); private enum ReadState {
if (_currentFieldName != null) WaitingForRootOpen,
_resultObject[_currentFieldName] = value; WaitingForField,
else WaitingForColon,
_resultArray.Add(value); WaitingForValue,
WaitingForNextOrRootClose,
_index += charCount + 1; }
} }
}
private FormatException CreateParserException(string expected)
{
var textPosition = _json.TextPositionAt(_index);
return new FormatException(
$"Parser error (Line {textPosition.Item1}, Col {textPosition.Item2}, State {_state}): Expected {expected} but got '{_json[_index]}'.");
}
/// <summary>
/// Defines the different JSON read states.
/// </summary>
private enum ReadState
{
WaitingForRootOpen,
WaitingForField,
WaitingForColon,
WaitingForValue,
WaitingForNextOrRootClose,
}
}
}
} }

View File

@ -1,348 +1,331 @@
using System; #nullable enable
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
namespace Swan.Formatters namespace Swan.Formatters {
{ /// <summary>
/// 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.
/// </summary>
public partial class Json {
/// <summary> /// <summary>
/// A very simple, light-weight JSON library written by Mario /// A simple JSON serializer.
/// 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.
/// </summary> /// </summary>
public partial class Json private class Serializer {
{ #region Private Declarations
/// <summary>
/// A simple JSON serializer. private static readonly Dictionary<Int32, String> IndentStrings = new Dictionary<global::System.Int32, global::System.String>();
/// </summary>
private class Serializer private readonly SerializerOptions? _options;
{ private readonly String _result;
#region Private Declarations private readonly StringBuilder? _builder;
private readonly String? _lastCommaSearch;
private static readonly Dictionary<int, string> IndentStrings = new Dictionary<int, string>();
#endregion
private readonly SerializerOptions _options;
private readonly string _result; #region Constructors
private readonly StringBuilder _builder;
private readonly string _lastCommaSearch; /// <summary>
/// Initializes a new instance of the <see cref="Serializer" /> class.
#endregion /// </summary>
/// <param name="obj">The object.</param>
#region Constructors /// <param name="depth">The depth.</param>
/// <param name="options">The options.</param>
/// <summary> private Serializer(Object? obj, Int32 depth, SerializerOptions options) {
/// Initializes a new instance of the <see cref="Serializer" /> class. if(depth > 20) {
/// </summary> throw new InvalidOperationException("The max depth (20) has been reached. Serializer can not continue.");
/// <param name="obj">The object.</param> }
/// <param name="depth">The depth.</param>
/// <param name="options">The options.</param> // Basic Type Handling (nulls, strings, number, date and bool)
private Serializer(object? obj, int depth, SerializerOptions options) this._result = ResolveBasicType(obj);
{
if (depth > 20) if(!String.IsNullOrWhiteSpace(this._result)) {
{ return;
throw new InvalidOperationException( }
"The max depth (20) has been reached. Serializer can not continue.");
} this._options = options;
// Basic Type Handling (nulls, strings, number, date and bool) // Handle circular references correctly and avoid them
_result = ResolveBasicType(obj); if(options.IsObjectPresent(obj!)) {
this._result = $"{{ \"$circref\": \"{Escape(obj!.GetHashCode().ToStringInvariant(), false)}\" }}";
if (!string.IsNullOrWhiteSpace(_result)) return;
return; }
_options = options; // At this point, we will need to construct the object with a StringBuilder.
this._lastCommaSearch = FieldSeparatorChar + (this._options.Format ? Environment.NewLine : String.Empty);
// Handle circular references correctly and avoid them this._builder = new StringBuilder();
if (options.IsObjectPresent(obj!))
{ this._result = obj switch
_result = $"{{ \"$circref\": \"{Escape(obj!.GetHashCode().ToStringInvariant(), false)}\" }}"; {
return; IDictionary itemsZero when itemsZero.Count == 0 => EmptyObjectLiteral,
} IDictionary items => this.ResolveDictionary(items, depth),
IEnumerable enumerableZero when !enumerableZero.Cast<Object>().Any() => EmptyArrayLiteral,
// At this point, we will need to construct the object with a StringBuilder. IEnumerable enumerableBytes when enumerableBytes is Byte[] bytes => Serialize(bytes.ToBase64(), depth, this._options),
_lastCommaSearch = FieldSeparatorChar + (_options.Format ? Environment.NewLine : string.Empty); IEnumerable enumerable => this.ResolveEnumerable(enumerable, depth),
_builder = new StringBuilder(); _ => this.ResolveObject(obj!, depth)
};
_result = obj switch }
{
IDictionary itemsZero when itemsZero.Count == 0 => EmptyObjectLiteral, internal static String Serialize(Object? obj, Int32 depth, SerializerOptions options) => new Serializer(obj, depth, options)._result;
IDictionary items => ResolveDictionary(items, depth),
IEnumerable enumerableZero when !enumerableZero.Cast<object>().Any() => EmptyArrayLiteral, #endregion
IEnumerable enumerableBytes when enumerableBytes is byte[] bytes => Serialize(bytes.ToBase64(), depth, _options),
IEnumerable enumerable => ResolveEnumerable(enumerable, depth), #region Helper Methods
_ => ResolveObject(obj!, depth)
}; private static String ResolveBasicType(Object? obj) {
} switch(obj) {
case null:
internal static string Serialize(object? obj, int depth, SerializerOptions options) => new Serializer(obj, depth, options)._result; return NullLiteral;
case String s:
#endregion return Escape(s, true);
case Boolean b:
#region Helper Methods return b ? TrueLiteral : FalseLiteral;
case Type _:
private static string ResolveBasicType(object? obj) case Assembly _:
{ case MethodInfo _:
switch (obj) case PropertyInfo _:
{ case EventInfo _:
case null: return Escape(obj.ToString()!, true);
return NullLiteral; case DateTime d:
case string s: return $"{StringQuotedChar}{d:s}{StringQuotedChar}";
return Escape(s, true); default:
case bool b: Type targetType = obj.GetType();
return b ? TrueLiteral : FalseLiteral;
case Type _: if(!Definitions.BasicTypesInfo.Value.ContainsKey(targetType)) {
case Assembly _: return String.Empty;
case MethodInfo _: }
case PropertyInfo _:
case EventInfo _: String escapedValue = Escape(Definitions.BasicTypesInfo.Value[targetType].ToStringInvariant(obj), false);
return Escape(obj.ToString(), true);
case DateTime d: return Decimal.TryParse(escapedValue, out _) ? $"{escapedValue}" : $"{StringQuotedChar}{escapedValue}{StringQuotedChar}";
return $"{StringQuotedChar}{d:s}{StringQuotedChar}"; }
default: }
var targetType = obj.GetType();
private static Boolean IsNonEmptyJsonArrayOrObject(String serialized) {
if (!Definitions.BasicTypesInfo.Value.ContainsKey(targetType)) if(serialized == EmptyObjectLiteral || serialized == EmptyArrayLiteral) {
return string.Empty; return false;
}
var escapedValue = Escape(Definitions.BasicTypesInfo.Value[targetType].ToStringInvariant(obj), false);
// find the first position the character is not a space
return decimal.TryParse(escapedValue, out _) return serialized.Where(c => c != ' ').Select(c => c == OpenObjectChar || c == OpenArrayChar).FirstOrDefault();
? $"{escapedValue}" }
: $"{StringQuotedChar}{escapedValue}{StringQuotedChar}";
} private static String Escape(String str, Boolean quoted) {
} if(str == null) {
return String.Empty;
private static bool IsNonEmptyJsonArrayOrObject(string serialized) }
{
if (serialized == EmptyObjectLiteral || serialized == EmptyArrayLiteral) return false; StringBuilder builder = new StringBuilder(str.Length * 2);
if(quoted) {
// find the first position the character is not a space _ = builder.Append(StringQuotedChar);
return serialized.Where(c => c != ' ').Select(c => c == OpenObjectChar || c == OpenArrayChar).FirstOrDefault(); }
}
Escape(str, builder);
private static string Escape(string str, bool quoted) if(quoted) {
{ _ = builder.Append(StringQuotedChar);
if (str == null) }
return string.Empty;
return builder.ToString();
var builder = new StringBuilder(str.Length * 2); }
if (quoted) builder.Append(StringQuotedChar);
Escape(str, builder); private static void Escape(String str, StringBuilder builder) {
if (quoted) builder.Append(StringQuotedChar); foreach(Char currentChar in str) {
return builder.ToString(); switch(currentChar) {
} case '\\':
case '"':
private static void Escape(string str, StringBuilder builder) case '/':
{ _ = builder
foreach (var currentChar in str) .Append('\\')
{ .Append(currentChar);
switch (currentChar) break;
{ case '\b':
case '\\': _ = builder.Append("\\b");
case '"': break;
case '/': case '\t':
builder _ = builder.Append("\\t");
.Append('\\') break;
.Append(currentChar); case '\n':
break; _ = builder.Append("\\n");
case '\b': break;
builder.Append("\\b"); case '\f':
break; _ = builder.Append("\\f");
case '\t': break;
builder.Append("\\t"); case '\r':
break; _ = builder.Append("\\r");
case '\n': break;
builder.Append("\\n"); default:
break; if(currentChar < ' ') {
case '\f': Byte[] escapeBytes = BitConverter.GetBytes((UInt16)currentChar);
builder.Append("\\f"); if(BitConverter.IsLittleEndian == false) {
break; Array.Reverse(escapeBytes);
case '\r': }
builder.Append("\\r");
break; _ = builder.Append("\\u")
default:
if (currentChar < ' ')
{
var escapeBytes = BitConverter.GetBytes((ushort)currentChar);
if (BitConverter.IsLittleEndian == false)
Array.Reverse(escapeBytes);
builder.Append("\\u")
.Append(escapeBytes[1].ToString("X").PadLeft(2, '0')) .Append(escapeBytes[1].ToString("X").PadLeft(2, '0'))
.Append(escapeBytes[0].ToString("X").PadLeft(2, '0')); .Append(escapeBytes[0].ToString("X").PadLeft(2, '0'));
} } else {
else _ = builder.Append(currentChar);
{ }
builder.Append(currentChar);
} break;
}
break; }
} }
}
} private Dictionary<String, Object?> CreateDictionary(Dictionary<String, MemberInfo> fields, String targetType, Object target) {
// Create the dictionary and extract the properties
private Dictionary<string, object?> CreateDictionary( global::System.Collections.Generic.Dictionary<global::System.String, global::System.Object?> objectDictionary = new Dictionary<global::System.String, global::System.Object?>();
Dictionary<string, MemberInfo> fields,
string targetType, if(String.IsNullOrWhiteSpace(this._options?.TypeSpecifier) == false) {
object target) objectDictionary[this._options?.TypeSpecifier!] = targetType;
{ }
// Create the dictionary and extract the properties
var objectDictionary = new Dictionary<string, object?>(); foreach(global::System.Collections.Generic.KeyValuePair<global::System.String, global::System.Reflection.MemberInfo> field in fields) {
// Build the dictionary using property names and values
if (string.IsNullOrWhiteSpace(_options.TypeSpecifier) == false) // Note: used to be: property.GetValue(target); but we would be reading private properties
objectDictionary[_options.TypeSpecifier] = targetType; try {
objectDictionary[field.Key] = field.Value is PropertyInfo property ? property.GetCacheGetMethod((Boolean)(this._options?.IncludeNonPublic)!)?.Invoke(target) : (field.Value as FieldInfo)?.GetValue(target);
foreach (var field in fields) } catch {
{ /* ignored */
// Build the dictionary using property names and values }
// Note: used to be: property.GetValue(target); but we would be reading private properties }
try
{ return objectDictionary;
objectDictionary[field.Key] = field.Value is PropertyInfo property }
? property.GetCacheGetMethod(_options.IncludeNonPublic)?.Invoke(target)
: (field.Value as FieldInfo)?.GetValue(target); private String ResolveDictionary(IDictionary items, Int32 depth) {
} this.Append(OpenObjectChar, depth);
catch this.AppendLine();
{
/* ignored */ // Iterate through the elements and output recursively
} Int32 writeCount = 0;
} foreach(Object? key in items.Keys) {
// Serialize and append the key (first char indented)
return objectDictionary; this.Append(StringQuotedChar, depth + 1);
} Escape(key?.ToString()!, this._builder!);
_ = this._builder?.Append(StringQuotedChar).Append(ValueSeparatorChar).Append(" ");
private string ResolveDictionary(IDictionary items, int depth)
{ // Serialize and append the value
Append(OpenObjectChar, depth); String serializedValue = Serialize(items[key!], depth + 1, this._options!);
AppendLine();
if(IsNonEmptyJsonArrayOrObject(serializedValue)) {
// Iterate through the elements and output recursively this.AppendLine();
var writeCount = 0; }
foreach (var key in items.Keys)
{ this.Append(serializedValue, 0);
// Serialize and append the key (first char indented)
Append(StringQuotedChar, depth + 1); // Add a comma and start a new line -- We will remove the last one when we are done writing the elements
Escape(key.ToString(), _builder); this.Append(FieldSeparatorChar, 0);
_builder this.AppendLine();
.Append(StringQuotedChar) writeCount++;
.Append(ValueSeparatorChar) }
.Append(" ");
// Output the end of the object and set the result
// Serialize and append the value this.RemoveLastComma();
var serializedValue = Serialize(items[key], depth + 1, _options); this.Append(CloseObjectChar, writeCount > 0 ? depth : 0);
return this._builder!.ToString();
if (IsNonEmptyJsonArrayOrObject(serializedValue)) AppendLine(); }
Append(serializedValue, 0);
private String ResolveObject(Object target, Int32 depth) {
// Add a comma and start a new line -- We will remove the last one when we are done writing the elements Type targetType = target.GetType();
Append(FieldSeparatorChar, 0);
AppendLine(); if(targetType.IsEnum) {
writeCount++; return Convert.ToInt64(target, System.Globalization.CultureInfo.InvariantCulture).ToString();
} }
// Output the end of the object and set the result global::System.Collections.Generic.Dictionary<global::System.String, global::System.Reflection.MemberInfo> fields = this._options!.GetProperties(targetType);
RemoveLastComma();
Append(CloseObjectChar, writeCount > 0 ? depth : 0); if(fields.Count == 0 && String.IsNullOrWhiteSpace(this._options.TypeSpecifier)) {
return _builder.ToString(); return EmptyObjectLiteral;
} }
private string ResolveObject(object target, int depth) // If we arrive here, then we convert the object into a
{ // dictionary of property names and values and call the serialization
var targetType = target.GetType(); // function again
global::System.Collections.Generic.Dictionary<global::System.String, global::System.Object?> objectDictionary = this.CreateDictionary(fields, targetType.ToString(), target);
if (targetType.IsEnum)
return Convert.ToInt64(target, System.Globalization.CultureInfo.InvariantCulture).ToString(); return Serialize(objectDictionary, depth, this._options);
}
var fields = _options.GetProperties(targetType);
private String ResolveEnumerable(IEnumerable target, Int32 depth) {
if (fields.Count == 0 && string.IsNullOrWhiteSpace(_options.TypeSpecifier)) // Cast the items as a generic object array
return EmptyObjectLiteral; global::System.Collections.Generic.IEnumerable<global::System.Object> items = target.Cast<global::System.Object>();
// If we arrive here, then we convert the object into a this.Append(OpenArrayChar, depth);
// dictionary of property names and values and call the serialization this.AppendLine();
// function again
var objectDictionary = CreateDictionary(fields, targetType.ToString(), target); // Iterate through the elements and output recursively
Int32 writeCount = 0;
return Serialize(objectDictionary, depth, _options); foreach(Object entry in items) {
} String serializedValue = Serialize(entry, depth + 1, this._options!);
private string ResolveEnumerable(IEnumerable target, int depth) if(IsNonEmptyJsonArrayOrObject(serializedValue)) {
{ this.Append(serializedValue, 0);
// Cast the items as a generic object array } else {
var items = target.Cast<object>(); this.Append(serializedValue, depth + 1);
}
Append(OpenArrayChar, depth);
AppendLine(); this.Append(FieldSeparatorChar, 0);
this.AppendLine();
// Iterate through the elements and output recursively writeCount++;
var writeCount = 0; }
foreach (var entry in items)
{ // Output the end of the array and set the result
var serializedValue = Serialize(entry, depth + 1, _options); this.RemoveLastComma();
this.Append(CloseArrayChar, writeCount > 0 ? depth : 0);
if (IsNonEmptyJsonArrayOrObject(serializedValue)) return this._builder!.ToString();
Append(serializedValue, 0); }
else
Append(serializedValue, depth + 1); private void SetIndent(Int32 depth) {
if(this._options!.Format == false || depth <= 0) {
Append(FieldSeparatorChar, 0); return;
AppendLine(); }
writeCount++;
} _ = this._builder!.Append(IndentStrings.GetOrAdd(depth, x => new String(' ', x * 4)));
}
// Output the end of the array and set the result
RemoveLastComma(); /// <summary>
Append(CloseArrayChar, writeCount > 0 ? depth : 0); /// Removes the last comma in the current string builder.
return _builder.ToString(); /// </summary>
} private void RemoveLastComma() {
if(this._builder!.Length < this._lastCommaSearch!.Length) {
private void SetIndent(int depth) return;
{ }
if (_options.Format == false || depth <= 0) return;
if(this._lastCommaSearch.Where((t, i) => this._builder[this._builder.Length - this._lastCommaSearch.Length + i] != t).Any()) {
_builder.Append(IndentStrings.GetOrAdd(depth, x => new string(' ', x * 4))); return;
} }
/// <summary> // If we got this far, we simply remove the comma character
/// Removes the last comma in the current string builder. _ = this._builder.Remove(this._builder.Length - this._lastCommaSearch.Length, 1);
/// </summary> }
private void RemoveLastComma()
{ private void Append(String text, Int32 depth) {
if (_builder.Length < _lastCommaSearch.Length) this.SetIndent(depth);
return; _ = this._builder!.Append(text);
}
if (_lastCommaSearch.Where((t, i) => _builder[_builder.Length - _lastCommaSearch.Length + i] != t).Any())
{ private void Append(Char text, Int32 depth) {
return; this.SetIndent(depth);
} _ = this._builder!.Append(text);
}
// If we got this far, we simply remove the comma character
_builder.Remove(_builder.Length - _lastCommaSearch.Length, 1); private void AppendLine() {
} if(this._options!.Format == false) {
return;
private void Append(string text, int depth) }
{
SetIndent(depth); _ = this._builder!.Append(Environment.NewLine);
_builder.Append(text); }
}
#endregion
private void Append(char text, int depth) }
{ }
SetIndent(depth);
_builder.Append(text);
}
private void AppendLine()
{
if (_options.Format == false) return;
_builder.Append(Environment.NewLine);
}
#endregion
}
}
} }

View File

@ -1,144 +1,133 @@
using System; #nullable enable
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Swan.Reflection; using Swan.Reflection;
namespace Swan.Formatters namespace Swan.Formatters {
{ /// <summary>
/// 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.
/// </summary>
public class SerializerOptions {
private static readonly ConcurrentDictionary<Type, Dictionary<Tuple<String, String>, MemberInfo>>
TypeCache = new ConcurrentDictionary<Type, Dictionary<Tuple<String, String>, MemberInfo>>();
private readonly String[]? _includeProperties;
private readonly String[]? _excludeProperties;
private readonly Dictionary<Int32, List<WeakReference>> _parentReferences = new Dictionary<Int32, List<WeakReference>>();
/// <summary> /// <summary>
/// A very simple, light-weight JSON library written by Mario /// Initializes a new instance of the <see cref="SerializerOptions"/> class.
/// 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.
/// </summary> /// </summary>
public class SerializerOptions /// <param name="format">if set to <c>true</c> [format].</param>
{ /// <param name="typeSpecifier">The type specifier.</param>
private static readonly ConcurrentDictionary<Type, Dictionary<Tuple<string, string>, MemberInfo>> /// <param name="includeProperties">The include properties.</param>
TypeCache = new ConcurrentDictionary<Type, Dictionary<Tuple<string, string>, MemberInfo>>(); /// <param name="excludeProperties">The exclude properties.</param>
/// <param name="includeNonPublic">if set to <c>true</c> [include non public].</param>
private readonly string[]? _includeProperties; /// <param name="parentReferences">The parent references.</param>
private readonly string[]? _excludeProperties; /// <param name="jsonSerializerCase">The json serializer case.</param>
private readonly Dictionary<int, List<WeakReference>> _parentReferences = new Dictionary<int, List<WeakReference>>(); public SerializerOptions(Boolean format, String? typeSpecifier, String[]? includeProperties, String[]? excludeProperties = null, Boolean includeNonPublic = true, IReadOnlyCollection<WeakReference>? parentReferences = null, JsonSerializerCase jsonSerializerCase = JsonSerializerCase.None) {
this._includeProperties = includeProperties;
/// <summary> this._excludeProperties = excludeProperties;
/// Initializes a new instance of the <see cref="SerializerOptions"/> class.
/// </summary> this.IncludeNonPublic = includeNonPublic;
/// <param name="format">if set to <c>true</c> [format].</param> this.Format = format;
/// <param name="typeSpecifier">The type specifier.</param> this.TypeSpecifier = typeSpecifier;
/// <param name="includeProperties">The include properties.</param> this.JsonSerializerCase = jsonSerializerCase;
/// <param name="excludeProperties">The exclude properties.</param>
/// <param name="includeNonPublic">if set to <c>true</c> [include non public].</param> if(parentReferences == null) {
/// <param name="parentReferences">The parent references.</param> return;
/// <param name="jsonSerializerCase">The json serializer case.</param> }
public SerializerOptions(
bool format, foreach(WeakReference parentReference in parentReferences.Where(x => x.IsAlive)) {
string? typeSpecifier, _ = this.IsObjectPresent(parentReference.Target);
string[]? includeProperties, }
string[]? excludeProperties = null, }
bool includeNonPublic = true,
IReadOnlyCollection<WeakReference>? parentReferences = null, /// <summary>
JsonSerializerCase jsonSerializerCase = JsonSerializerCase.None) /// Gets a value indicating whether this <see cref="SerializerOptions"/> is format.
{ /// </summary>
_includeProperties = includeProperties; /// <value>
_excludeProperties = excludeProperties; /// <c>true</c> if format; otherwise, <c>false</c>.
/// </value>
IncludeNonPublic = includeNonPublic; public Boolean Format {
Format = format; get;
TypeSpecifier = typeSpecifier; }
JsonSerializerCase = jsonSerializerCase;
/// <summary>
if (parentReferences == null) /// Gets the type specifier.
return; /// </summary>
/// <value>
foreach (var parentReference in parentReferences.Where(x => x.IsAlive)) /// The type specifier.
{ /// </value>
IsObjectPresent(parentReference.Target); public String? TypeSpecifier {
} get;
} }
/// <summary> /// <summary>
/// Gets a value indicating whether this <see cref="SerializerOptions"/> is format. /// Gets a value indicating whether [include non public].
/// </summary> /// </summary>
/// <value> /// <value>
/// <c>true</c> if format; otherwise, <c>false</c>. /// <c>true</c> if [include non public]; otherwise, <c>false</c>.
/// </value> /// </value>
public bool Format { get; } public Boolean IncludeNonPublic {
get;
/// <summary> }
/// Gets the type specifier.
/// </summary> /// <summary>
/// <value> /// Gets the json serializer case.
/// The type specifier. /// </summary>
/// </value> /// <value>
public string? TypeSpecifier { get; } /// The json serializer case.
/// </value>
/// <summary> public JsonSerializerCase JsonSerializerCase {
/// Gets a value indicating whether [include non public]. get;
/// </summary> }
/// <value>
/// <c>true</c> if [include non public]; otherwise, <c>false</c>. internal Boolean IsObjectPresent(Object? target) {
/// </value> if(target == null) {
public bool IncludeNonPublic { get; } return false;
}
/// <summary> Int32 hashCode = target.GetHashCode();
/// Gets the json serializer case.
/// </summary> if(this._parentReferences.ContainsKey(hashCode)) {
/// <value> if(this._parentReferences[hashCode].Any(p => ReferenceEquals(p.Target, target))) {
/// The json serializer case. return true;
/// </value> }
public JsonSerializerCase JsonSerializerCase { get; }
this._parentReferences[hashCode].Add(new WeakReference(target));
internal bool IsObjectPresent(object target) return false;
{ }
var hashCode = target.GetHashCode();
this._parentReferences.Add(hashCode, new List<WeakReference> { new WeakReference(target) });
if (_parentReferences.ContainsKey(hashCode)) return false;
{ }
if (_parentReferences[hashCode].Any(p => ReferenceEquals(p.Target, target)))
return true; internal Dictionary<String, MemberInfo> GetProperties(Type targetType) => this.GetPropertiesCache(targetType).When(() => this._includeProperties?.Length > 0, query => query.Where(p => this._includeProperties.Contains(p.Key.Item1))).When(() => this._excludeProperties?.Length > 0, query => query.Where(p => !this._excludeProperties.Contains(p.Key.Item1))).ToDictionary(x => x.Key.Item2, x => x.Value);
_parentReferences[hashCode].Add(new WeakReference(target)); private Dictionary<Tuple<String, String>, MemberInfo> GetPropertiesCache(Type targetType) {
return false; if(TypeCache.TryGetValue(targetType, out Dictionary<Tuple<String, String>, MemberInfo>? current)) {
} return current;
}
_parentReferences.Add(hashCode, new List<WeakReference> { new WeakReference(target) });
return false; List<MemberInfo> fields = new List<MemberInfo>(PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties(targetType).Where(p => p.CanRead));
}
// If the target is a struct (value type) navigate the fields.
internal Dictionary<string, MemberInfo> GetProperties(Type targetType) if(targetType.IsValueType) {
=> GetPropertiesCache(targetType) fields.AddRange(FieldTypeCache.DefaultCache.Value.RetrieveAllFields(targetType));
.When(() => _includeProperties?.Length > 0, }
query => query.Where(p => _includeProperties.Contains(p.Key.Item1)))
.When(() => _excludeProperties?.Length > 0, Dictionary<Tuple<String, String>, MemberInfo> value = fields.ToDictionary(x => Tuple.Create(x.Name, x.GetCustomAttribute<JsonPropertyAttribute>()?.PropertyName ?? x.Name.GetNameWithCase(this.JsonSerializerCase)), x => x);
query => query.Where(p => !_excludeProperties.Contains(p.Key.Item1)))
.ToDictionary(x => x.Key.Item2, x => x.Value); TypeCache.TryAdd(targetType, value);
private Dictionary<Tuple<string, string>, MemberInfo> GetPropertiesCache(Type targetType) return value;
{ }
if (TypeCache.TryGetValue(targetType, out var current)) }
return current;
var fields =
new List<MemberInfo>(PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties(targetType).Where(p => p.CanRead));
// If the target is a struct (value type) navigate the fields.
if (targetType.IsValueType)
{
fields.AddRange(FieldTypeCache.DefaultCache.Value.RetrieveAllFields(targetType));
}
var value = fields
.ToDictionary(
x => Tuple.Create(x.Name,
x.GetCustomAttribute<JsonPropertyAttribute>()?.PropertyName ?? x.Name.GetNameWithCase(JsonSerializerCase)),
x => x);
TypeCache.TryAdd(targetType, value);
return value;
}
}
} }

View File

@ -1,379 +1,340 @@
using System; #nullable enable
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Swan.Collections; using Swan.Collections;
using Swan.Reflection; using Swan.Reflection;
namespace Swan.Formatters namespace Swan.Formatters {
{ /// <summary>
/// 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.
/// </summary>
public static partial class Json {
#region Constants
internal const String AddMethodName = "Add";
private const Char OpenObjectChar = '{';
private const Char CloseObjectChar = '}';
private const Char OpenArrayChar = '[';
private const Char CloseArrayChar = ']';
private const Char FieldSeparatorChar = ',';
private const Char ValueSeparatorChar = ':';
private const Char StringEscapeChar = '\\';
private const Char StringQuotedChar = '"';
private const String EmptyObjectLiteral = "{ }";
private const String EmptyArrayLiteral = "[ ]";
private const String TrueLiteral = "true";
private const String FalseLiteral = "false";
private const String NullLiteral = "null";
#endregion
private static readonly CollectionCacheRepository<String> IgnoredPropertiesCache = new CollectionCacheRepository<global::System.String>();
#region Public API
/// <summary> /// <summary>
/// A very simple, light-weight JSON library written by Mario /// Serializes the specified object into a JSON string.
/// 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.
/// </summary> /// </summary>
public static partial class Json /// <param name="obj">The object.</param>
{ /// <param name="format">if set to <c>true</c> it formats and indents the output.</param>
#region Constants /// <param name="typeSpecifier">The type specifier. Leave null or empty to avoid setting.</param>
/// <param name="includeNonPublic">if set to <c>true</c> non-public getters will be also read.</param>
internal const string AddMethodName = "Add"; /// <param name="includedNames">The included property names.</param>
/// <param name="excludedNames">The excluded property names.</param>
private const char OpenObjectChar = '{'; /// <returns>
private const char CloseObjectChar = '}'; /// A <see cref="System.String" /> that represents the current object.
/// </returns>
private const char OpenArrayChar = '['; /// <example>
private const char CloseArrayChar = ']'; /// The following example describes how to serialize a simple object.
/// <code>
private const char FieldSeparatorChar = ','; /// using Swan.Formatters;
private const char ValueSeparatorChar = ':'; ///
/// class Example
private const char StringEscapeChar = '\\'; /// {
private const char StringQuotedChar = '"'; /// static void Main()
/// {
private const string EmptyObjectLiteral = "{ }"; /// var obj = new { One = "One", Two = "Two" };
private const string EmptyArrayLiteral = "[ ]"; ///
private const string TrueLiteral = "true"; /// var serial = Json.Serialize(obj); // {"One": "One","Two": "Two"}
private const string FalseLiteral = "false"; /// }
private const string NullLiteral = "null"; /// }
/// </code>
#endregion ///
/// The following example details how to serialize an object using the <see cref="JsonPropertyAttribute"/>.
private static readonly CollectionCacheRepository<string> IgnoredPropertiesCache = new CollectionCacheRepository<string>(); ///
/// <code>
#region Public API /// using Swan.Attributes;
/// using Swan.Formatters;
/// <summary> ///
/// Serializes the specified object into a JSON string. /// class Example
/// </summary> /// {
/// <param name="obj">The object.</param> /// class JsonPropertyExample
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param> /// {
/// <param name="typeSpecifier">The type specifier. Leave null or empty to avoid setting.</param> /// [JsonProperty("data")]
/// <param name="includeNonPublic">if set to <c>true</c> non-public getters will be also read.</param> /// public string Data { get; set; }
/// <param name="includedNames">The included property names.</param> ///
/// <param name="excludedNames">The excluded property names.</param> /// [JsonProperty("ignoredData", true)]
/// <returns> /// public string IgnoredData { get; set; }
/// A <see cref="System.String" /> that represents the current object. /// }
/// </returns> ///
/// <example> /// static void Main()
/// The following example describes how to serialize a simple object. /// {
/// <code> /// var obj = new JsonPropertyExample() { Data = "OK", IgnoredData = "OK" };
/// using Swan.Formatters; ///
/// /// // {"data": "OK"}
/// class Example /// var serializedObj = Json.Serialize(obj);
/// { /// }
/// static void Main() /// }
/// { /// </code>
/// var obj = new { One = "One", Two = "Two" }; /// </example>
/// public static String Serialize(Object? obj, Boolean format = false, String? typeSpecifier = null, Boolean includeNonPublic = false, String[]? includedNames = null, params String[] excludedNames) => Serialize(obj, format, typeSpecifier, includeNonPublic, includedNames, excludedNames, null, JsonSerializerCase.None);
/// var serial = Json.Serialize(obj); // {"One": "One","Two": "Two"}
/// } /// <summary>
/// } /// Serializes the specified object into a JSON string.
/// </code> /// </summary>
/// /// <param name="obj">The object.</param>
/// The following example details how to serialize an object using the <see cref="JsonPropertyAttribute"/>. /// <param name="jsonSerializerCase">The json serializer case.</param>
/// /// <param name="format">if set to <c>true</c> [format].</param>
/// <code> /// <param name="typeSpecifier">The type specifier.</param>
/// using Swan.Attributes; /// <returns>
/// using Swan.Formatters; /// A <see cref="System.String" /> that represents the current object.
/// /// </returns>
/// class Example public static String Serialize(Object? obj, JsonSerializerCase jsonSerializerCase, Boolean format = false, String? typeSpecifier = null) => Serialize(obj, format, typeSpecifier, false, null, null, null, jsonSerializerCase);
/// {
/// class JsonPropertyExample /// <summary>
/// { /// Serializes the specified object into a JSON string.
/// [JsonProperty("data")] /// </summary>
/// public string Data { get; set; } /// <param name="obj">The object.</param>
/// /// <param name="format">if set to <c>true</c> it formats and indents the output.</param>
/// [JsonProperty("ignoredData", true)] /// <param name="typeSpecifier">The type specifier. Leave null or empty to avoid setting.</param>
/// public string IgnoredData { get; set; } /// <param name="includeNonPublic">if set to <c>true</c> non-public getters will be also read.</param>
/// } /// <param name="includedNames">The included property names.</param>
/// /// <param name="excludedNames">The excluded property names.</param>
/// static void Main() /// <param name="parentReferences">The parent references.</param>
/// { /// <param name="jsonSerializerCase">The json serializer case.</param>
/// var obj = new JsonPropertyExample() { Data = "OK", IgnoredData = "OK" }; /// <returns>
/// /// A <see cref="System.String" /> that represents the current object.
/// // {"data": "OK"} /// </returns>
/// var serializedObj = Json.Serialize(obj); public static String Serialize(Object? obj, Boolean format, String? typeSpecifier, Boolean includeNonPublic, String[]? includedNames, String[]? excludedNames, List<WeakReference>? parentReferences, JsonSerializerCase jsonSerializerCase) {
/// } if(obj != null && (obj is String || Definitions.AllBasicValueTypes.Contains(obj.GetType()))) {
/// } return SerializePrimitiveValue(obj);
/// </code> }
/// </example>
public static string Serialize( SerializerOptions options = new SerializerOptions(format, typeSpecifier, includedNames, GetExcludedNames(obj?.GetType(), excludedNames), includeNonPublic, parentReferences, jsonSerializerCase);
object? obj,
bool format = false, return Serialize(obj, options);
string? typeSpecifier = null, }
bool includeNonPublic = false,
string[]? includedNames = null, /// <summary>
params string[] excludedNames) /// Serializes the specified object using the SerializerOptions provided.
{ /// </summary>
return Serialize(obj, format, typeSpecifier, includeNonPublic, includedNames, excludedNames, null, JsonSerializerCase.None); /// <param name="obj">The object.</param>
} /// <param name="options">The options.</param>
/// <returns>
/// <summary> /// A <see cref="String" /> that represents the current object.
/// Serializes the specified object into a JSON string. /// </returns>
/// </summary> public static String Serialize(Object? obj, SerializerOptions options) => Serializer.Serialize(obj, 0, options);
/// <param name="obj">The object.</param>
/// <param name="jsonSerializerCase">The json serializer case.</param> /// <summary>
/// <param name="format">if set to <c>true</c> [format].</param> /// Serializes the specified object only including the specified property names.
/// <param name="typeSpecifier">The type specifier.</param> /// </summary>
/// <returns> /// <param name="obj">The object.</param>
/// A <see cref="System.String" /> that represents the current object. /// <param name="format">if set to <c>true</c> it formats and indents the output.</param>
/// </returns> /// <param name="includeNames">The include names.</param>
public static string Serialize( /// <returns>A <see cref="String" /> that represents the current object.</returns>
object? obj, /// <example>
JsonSerializerCase jsonSerializerCase, /// The following example shows how to serialize a simple object including the specified properties.
bool format = false, /// <code>
string? typeSpecifier = null) => Serialize(obj, format, typeSpecifier, false, null, null, null, jsonSerializerCase); /// using Swan.Formatters;
///
/// <summary> /// class Example
/// Serializes the specified object into a JSON string. /// {
/// </summary> /// static void Main()
/// <param name="obj">The object.</param> /// {
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param> /// // object to serialize
/// <param name="typeSpecifier">The type specifier. Leave null or empty to avoid setting.</param> /// var obj = new { One = "One", Two = "Two", Three = "Three" };
/// <param name="includeNonPublic">if set to <c>true</c> non-public getters will be also read.</param> ///
/// <param name="includedNames">The included property names.</param> /// // the included names
/// <param name="excludedNames">The excluded property names.</param> /// var includedNames = new[] { "Two", "Three" };
/// <param name="parentReferences">The parent references.</param> ///
/// <param name="jsonSerializerCase">The json serializer case.</param> /// // serialize only the included names
/// <returns> /// var data = Json.SerializeOnly(basicObject, true, includedNames);
/// A <see cref="System.String" /> that represents the current object. /// // {"Two": "Two","Three": "Three" }
/// </returns> /// }
public static string Serialize( /// }
object? obj, /// </code>
bool format, /// </example>
string? typeSpecifier, public static String SerializeOnly(Object? obj, Boolean format, params String[] includeNames) => Serialize(obj, new SerializerOptions(format, null, includeNames));
bool includeNonPublic,
string[]? includedNames, /// <summary>
string[]? excludedNames, /// Serializes the specified object excluding the specified property names.
List<WeakReference>? parentReferences, /// </summary>
JsonSerializerCase jsonSerializerCase) /// <param name="obj">The object.</param>
{ /// <param name="format">if set to <c>true</c> it formats and indents the output.</param>
if (obj != null && (obj is string || Definitions.AllBasicValueTypes.Contains(obj.GetType()))) /// <param name="excludeNames">The exclude names.</param>
{ /// <returns>A <see cref="String" /> that represents the current object.</returns>
return SerializePrimitiveValue(obj); /// <example>
} /// The following code shows how to serialize a simple object excluding the specified properties.
/// <code>
var options = new SerializerOptions( /// using Swan.Formatters;
format, ///
typeSpecifier, /// class Example
includedNames, /// {
GetExcludedNames(obj?.GetType(), excludedNames), /// static void Main()
includeNonPublic, /// {
parentReferences, /// // object to serialize
jsonSerializerCase); /// var obj = new { One = "One", Two = "Two", Three = "Three" };
///
return Serialize(obj, options); /// // the excluded names
} /// var excludeNames = new[] { "Two", "Three" };
///
/// <summary> /// // serialize excluding
/// Serializes the specified object using the SerializerOptions provided. /// var data = Json.SerializeExcluding(basicObject, false, includedNames);
/// </summary> /// // {"One": "One"}
/// <param name="obj">The object.</param> /// }
/// <param name="options">The options.</param> /// }
/// <returns> /// </code>
/// A <see cref="string" /> that represents the current object. /// </example>
/// </returns> public static String SerializeExcluding(Object? obj, Boolean format, params String[] excludeNames) => Serialize(obj, new SerializerOptions(format, null, null, excludeNames));
public static string Serialize(object? obj, SerializerOptions options) => Serializer.Serialize(obj, 0, options);
/// <summary>
/// <summary> /// Deserializes the specified json string as either a Dictionary[string, object] or as a List[object]
/// Serializes the specified object only including the specified property names. /// depending on the syntax of the JSON string.
/// </summary> /// </summary>
/// <param name="obj">The object.</param> /// <param name="json">The JSON string.</param>
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param> /// <param name="jsonSerializerCase">The json serializer case.</param>
/// <param name="includeNames">The include names.</param> /// <returns>
/// <returns>A <see cref="string" /> that represents the current object.</returns> /// Type of the current deserializes.
/// <example> /// </returns>
/// The following example shows how to serialize a simple object including the specified properties. /// <example>
/// <code> /// The following code shows how to deserialize a JSON string into a Dictionary.
/// using Swan.Formatters; /// <code>
/// /// using Swan.Formatters;
/// class Example /// class Example
/// { /// {
/// static void Main() /// static void Main()
/// { /// {
/// // object to serialize /// // json to deserialize
/// var obj = new { One = "One", Two = "Two", Three = "Three" }; /// var basicJson = "{\"One\":\"One\",\"Two\":\"Two\",\"Three\":\"Three\"}";
/// /// // deserializes the specified json into a Dictionary&lt;string, object&gt;.
/// // the included names /// var data = Json.Deserialize(basicJson, JsonSerializerCase.None);
/// var includedNames = new[] { "Two", "Three" }; /// }
/// /// }
/// // serialize only the included names /// </code></example>
/// var data = Json.SerializeOnly(basicObject, true, includedNames); public static Object? Deserialize(String? json, JsonSerializerCase jsonSerializerCase) => Converter.FromJsonResult(Deserializer.DeserializeInternal(json), jsonSerializerCase);
/// // {"Two": "Two","Three": "Three" }
/// } /// <summary>
/// } /// Deserializes the specified json string as either a Dictionary[string, object] or as a List[object]
/// </code> /// depending on the syntax of the JSON string.
/// </example> /// </summary>
public static string SerializeOnly(object? obj, bool format, params string[] includeNames) /// <param name="json">The JSON string.</param>
=> Serialize(obj, new SerializerOptions(format, null, includeNames)); /// <returns>
/// Type of the current deserializes.
/// <summary> /// </returns>
/// Serializes the specified object excluding the specified property names. /// <example>
/// </summary> /// The following code shows how to deserialize a JSON string into a Dictionary.
/// <param name="obj">The object.</param> /// <code>
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param> /// using Swan.Formatters;
/// <param name="excludeNames">The exclude names.</param> /// class Example
/// <returns>A <see cref="string" /> that represents the current object.</returns> /// {
/// <example> /// static void Main()
/// The following code shows how to serialize a simple object excluding the specified properties. /// {
/// <code> /// // json to deserialize
/// using Swan.Formatters; /// var basicJson = "{\"One\":\"One\",\"Two\":\"Two\",\"Three\":\"Three\"}";
/// /// // deserializes the specified json into a Dictionary&lt;string, object&gt;.
/// class Example /// var data = Json.Deserialize(basicJson);
/// { /// }
/// static void Main() /// }
/// { /// </code></example>
/// // object to serialize public static Object? Deserialize(String? json) => Deserialize(json, JsonSerializerCase.None);
/// var obj = new { One = "One", Two = "Two", Three = "Three" };
/// /// <summary>
/// // the excluded names /// Deserializes the specified JSON string and converts it to the specified object type.
/// var excludeNames = new[] { "Two", "Three" }; /// Non-public constructors and property setters are ignored.
/// /// </summary>
/// // serialize excluding /// <typeparam name="T">The type of object to deserialize.</typeparam>
/// var data = Json.SerializeExcluding(basicObject, false, includedNames); /// <param name="json">The JSON string.</param>
/// // {"One": "One"} /// <param name="jsonSerializerCase">The JSON serializer case.</param>
/// } /// <returns>
/// } /// The deserialized specified type object.
/// </code> /// </returns>
/// </example> /// <example>
public static string SerializeExcluding(object? obj, bool format, params string[] excludeNames) /// The following code describes how to deserialize a JSON string into an object of type T.
=> Serialize(obj, new SerializerOptions(format, null, null, excludeNames)); /// <code>
/// using Swan.Formatters;
/// <summary> /// class Example
/// Deserializes the specified json string as either a Dictionary[string, object] or as a List[object] /// {
/// depending on the syntax of the JSON string. /// static void Main()
/// </summary> /// {
/// <param name="json">The JSON string.</param> /// // json type BasicJson to serialize
/// <param name="jsonSerializerCase">The json serializer case.</param> /// var basicJson = "{\"One\":\"One\",\"Two\":\"Two\",\"Three\":\"Three\"}";
/// <returns> /// // deserializes the specified string in a new instance of the type BasicJson.
/// Type of the current deserializes. /// var data = Json.Deserialize&lt;BasicJson&gt;(basicJson);
/// </returns> /// }
/// <example> /// }
/// The following code shows how to deserialize a JSON string into a Dictionary. /// </code></example>
/// <code> public static T Deserialize<T>(String json, JsonSerializerCase jsonSerializerCase = JsonSerializerCase.None) where T : notnull => (T)Deserialize(json, typeof(T), jsonSerializerCase: jsonSerializerCase)!;
/// using Swan.Formatters;
/// class Example /// <summary>
/// { /// Deserializes the specified JSON string and converts it to the specified object type.
/// static void Main() /// </summary>
/// { /// <typeparam name="T">The type of object to deserialize.</typeparam>
/// // json to deserialize /// <param name="json">The JSON string.</param>
/// var basicJson = "{\"One\":\"One\",\"Two\":\"Two\",\"Three\":\"Three\"}"; /// <param name="includeNonPublic">if set to true, it also uses the non-public constructors and property setters.</param>
/// // deserializes the specified json into a Dictionary&lt;string, object&gt;. /// <returns>The deserialized specified type object.</returns>
/// var data = Json.Deserialize(basicJson, JsonSerializerCase.None); public static T Deserialize<T>(String json, Boolean includeNonPublic) where T : notnull => (T)Deserialize(json, typeof(T), includeNonPublic)!;
/// }
/// } /// <summary>
/// </code></example> /// Deserializes the specified JSON string and converts it to the specified object type.
public static object? Deserialize( /// </summary>
string json, /// <param name="json">The JSON string.</param>
JsonSerializerCase jsonSerializerCase) /// <param name="resultType">Type of the result.</param>
=> Converter.FromJsonResult(Deserializer.DeserializeInternal(json), jsonSerializerCase); /// <param name="includeNonPublic">if set to true, it also uses the non-public constructors and property setters.</param>
/// <param name="jsonSerializerCase">The json serializer case.</param>
/// <summary> /// <returns>
/// Deserializes the specified json string as either a Dictionary[string, object] or as a List[object] /// Type of the current conversion from json result.
/// depending on the syntax of the JSON string. /// </returns>
/// </summary> public static Object? Deserialize(String json, Type resultType, Boolean includeNonPublic = false, JsonSerializerCase jsonSerializerCase = JsonSerializerCase.None) => Converter.FromJsonResult(Deserializer.DeserializeInternal(json), jsonSerializerCase, resultType, includeNonPublic);
/// <param name="json">The JSON string.</param>
/// <returns> #endregion
/// Type of the current deserializes.
/// </returns> #region Private API
/// <example>
/// The following code shows how to deserialize a JSON string into a Dictionary. private static String[]? GetExcludedNames(Type? type, String[]? excludedNames) {
/// <code> if(type == null) {
/// using Swan.Formatters; return excludedNames;
/// class Example }
/// {
/// static void Main() global::System.Collections.Generic.IEnumerable<global::System.String> excludedByAttr = IgnoredPropertiesCache.Retrieve(type, t => t.GetProperties()
/// {
/// // json to deserialize
/// var basicJson = "{\"One\":\"One\",\"Two\":\"Two\",\"Three\":\"Three\"}";
/// // deserializes the specified json into a Dictionary&lt;string, object&gt;.
/// var data = Json.Deserialize(basicJson);
/// }
/// }
/// </code></example>
public static object? Deserialize(string json)
=> Deserialize(json, JsonSerializerCase.None);
/// <summary>
/// Deserializes the specified JSON string and converts it to the specified object type.
/// Non-public constructors and property setters are ignored.
/// </summary>
/// <typeparam name="T">The type of object to deserialize.</typeparam>
/// <param name="json">The JSON string.</param>
/// <param name="jsonSerializerCase">The JSON serializer case.</param>
/// <returns>
/// The deserialized specified type object.
/// </returns>
/// <example>
/// The following code describes how to deserialize a JSON string into an object of type T.
/// <code>
/// using Swan.Formatters;
/// class Example
/// {
/// static void Main()
/// {
/// // json type BasicJson to serialize
/// var basicJson = "{\"One\":\"One\",\"Two\":\"Two\",\"Three\":\"Three\"}";
/// // deserializes the specified string in a new instance of the type BasicJson.
/// var data = Json.Deserialize&lt;BasicJson&gt;(basicJson);
/// }
/// }
/// </code></example>
public static T Deserialize<T>(string json, JsonSerializerCase jsonSerializerCase = JsonSerializerCase.None)
=> (T)Deserialize(json, typeof(T), jsonSerializerCase: jsonSerializerCase);
/// <summary>
/// Deserializes the specified JSON string and converts it to the specified object type.
/// </summary>
/// <typeparam name="T">The type of object to deserialize.</typeparam>
/// <param name="json">The JSON string.</param>
/// <param name="includeNonPublic">if set to true, it also uses the non-public constructors and property setters.</param>
/// <returns>The deserialized specified type object.</returns>
public static T Deserialize<T>(string json, bool includeNonPublic) => (T)Deserialize(json, typeof(T), includeNonPublic);
/// <summary>
/// Deserializes the specified JSON string and converts it to the specified object type.
/// </summary>
/// <param name="json">The JSON string.</param>
/// <param name="resultType">Type of the result.</param>
/// <param name="includeNonPublic">if set to true, it also uses the non-public constructors and property setters.</param>
/// <param name="jsonSerializerCase">The json serializer case.</param>
/// <returns>
/// Type of the current conversion from json result.
/// </returns>
public static object? Deserialize(string json, Type resultType, bool includeNonPublic = false, JsonSerializerCase jsonSerializerCase = JsonSerializerCase.None)
=> Converter.FromJsonResult(Deserializer.DeserializeInternal(json), jsonSerializerCase, resultType, includeNonPublic);
#endregion
#region Private API
private static string[]? GetExcludedNames(Type? type, string[]? excludedNames)
{
if (type == null)
return excludedNames;
var excludedByAttr = IgnoredPropertiesCache.Retrieve(type, t => t.GetProperties()
.Where(x => AttributeCache.DefaultCache.Value.RetrieveOne<JsonPropertyAttribute>(x)?.Ignored == true) .Where(x => AttributeCache.DefaultCache.Value.RetrieveOne<JsonPropertyAttribute>(x)?.Ignored == true)
.Select(x => x.Name)); .Select(x => x.Name));
if (excludedByAttr?.Any() != true) if(excludedByAttr?.Any() != true) {
return excludedNames; return excludedNames;
}
return excludedNames?.Any(string.IsNullOrWhiteSpace) == true
? excludedByAttr.Intersect(excludedNames.Where(y => !string.IsNullOrWhiteSpace(y))).ToArray() return excludedNames?.Any(String.IsNullOrWhiteSpace) == true
: excludedByAttr.ToArray(); ? excludedByAttr.Intersect(excludedNames.Where(y => !String.IsNullOrWhiteSpace(y))).ToArray()
} : excludedByAttr.ToArray();
}
private static string SerializePrimitiveValue(object obj) =>
obj switch private static String SerializePrimitiveValue(Object obj) => obj switch
{ {
string stringValue => stringValue, String stringValue => stringValue,
bool boolValue => boolValue ? TrueLiteral : FalseLiteral, Boolean boolValue => boolValue ? TrueLiteral : FalseLiteral,
_ => obj.ToString() _ => obj.ToString()!
}; };
#endregion #endregion
} }
} }

View File

@ -1,39 +1,40 @@
using System; using System;
namespace Swan.Formatters namespace Swan.Formatters {
{ /// <summary>
/// An attribute used to help setup a property behavior when serialize/deserialize JSON.
/// </summary>
/// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Property)]
public sealed class JsonPropertyAttribute : Attribute {
/// <summary> /// <summary>
/// An attribute used to help setup a property behavior when serialize/deserialize JSON. /// Initializes a new instance of the <see cref="JsonPropertyAttribute" /> class.
/// </summary> /// </summary>
/// <seealso cref="Attribute" /> /// <param name="propertyName">Name of the property.</param>
[AttributeUsage(AttributeTargets.Property)] /// <param name="ignored">if set to <c>true</c> [ignored].</param>
public sealed class JsonPropertyAttribute : Attribute public JsonPropertyAttribute(String propertyName, Boolean ignored = false) {
{ this.PropertyName = propertyName ?? throw new ArgumentNullException(nameof(propertyName));
/// <summary> this.Ignored = ignored;
/// Initializes a new instance of the <see cref="JsonPropertyAttribute" /> class. }
/// </summary>
/// <param name="propertyName">Name of the property.</param> /// <summary>
/// <param name="ignored">if set to <c>true</c> [ignored].</param> /// Gets or sets the name of the property.
public JsonPropertyAttribute(string propertyName, bool ignored = false) /// </summary>
{ /// <value>
PropertyName = propertyName ?? throw new ArgumentNullException(nameof(propertyName)); /// The name of the property.
Ignored = ignored; /// </value>
} public String PropertyName {
get;
/// <summary> }
/// Gets or sets the name of the property.
/// </summary> /// <summary>
/// <value> /// Gets or sets a value indicating whether this <see cref="JsonPropertyAttribute" /> is ignored.
/// The name of the property. /// </summary>
/// </value> /// <value>
public string PropertyName { get; } /// <c>true</c> if ignored; otherwise, <c>false</c>.
/// </value>
/// <summary> public Boolean Ignored {
/// Gets or sets a value indicating whether this <see cref="JsonPropertyAttribute" /> is ignored. get;
/// </summary> }
/// <value> }
/// <c>true</c> if ignored; otherwise, <c>false</c>.
/// </value>
public bool Ignored { get; }
}
} }

View File

@ -1,344 +1,302 @@
using System; #nullable enable
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.ComponentModel; using System.ComponentModel;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
namespace Swan namespace Swan {
{ /// <summary>
/// Provides a standard way to convert strings to different types.
/// </summary>
public static class FromString {
// It doesn't matter which converter we get here: ConvertFromInvariantString is not virtual.
private static readonly MethodInfo ConvertFromInvariantStringMethod = new Func<String, Object>(TypeDescriptor.GetConverter(typeof(Int32)).ConvertFromInvariantString).Method;
private static readonly MethodInfo? TryConvertToInternalMethod = typeof(FromString).GetMethod(nameof(TryConvertToInternal), BindingFlags.Static | BindingFlags.NonPublic);
private static readonly MethodInfo? ConvertToInternalMethod = typeof(FromString).GetMethod(nameof(ConvertToInternal), BindingFlags.Static | BindingFlags.NonPublic);
private static readonly ConcurrentDictionary<Type, Func<String[], (Boolean Success, Object Result)>> GenericTryConvertToMethods = new ConcurrentDictionary<Type, Func<String[], (Boolean Success, Object Result)>>();
private static readonly ConcurrentDictionary<Type, Func<String[], Object>> GenericConvertToMethods = new ConcurrentDictionary<Type, Func<String[], Object>>();
/// <summary> /// <summary>
/// Provides a standard way to convert strings to different types. /// Determines whether a string can be converted to the specified type.
/// </summary> /// </summary>
public static class FromString /// <param name="type">The type resulting from the conversion.</param>
{ /// <returns><see langword="true" /> if the conversion is possible;
// It doesn't matter which converter we get here: ConvertFromInvariantString is not virtual. /// otherwise, <see langword="false" />.</returns>
private static readonly MethodInfo ConvertFromInvariantStringMethod /// <exception cref="ArgumentNullException"><paramref name="type" /> is <see langword="null" />.</exception>
= new Func<string, object>(TypeDescriptor.GetConverter(typeof(int)).ConvertFromInvariantString).Method; public static Boolean CanConvertTo(Type type) => TypeDescriptor.GetConverter(type).CanConvertFrom(typeof(String));
private static readonly MethodInfo TryConvertToInternalMethod /// <summary>
= typeof(FromString).GetMethod(nameof(TryConvertToInternal), BindingFlags.Static | BindingFlags.NonPublic); /// Determines whether a string can be converted to the specified type.
/// </summary>
private static readonly MethodInfo ConvertToInternalMethod /// <typeparam name="TResult">The type resulting from the conversion.</typeparam>
= typeof(FromString).GetMethod(nameof(ConvertToInternal), BindingFlags.Static | BindingFlags.NonPublic); /// <returns><see langword="true" /> if the conversion is possible;
/// otherwise, <see langword="false" />.</returns>
private static readonly ConcurrentDictionary<Type, Func<string[], (bool Success, object Result)>> GenericTryConvertToMethods public static Boolean CanConvertTo<TResult>() => TypeDescriptor.GetConverter(typeof(TResult)).CanConvertFrom(typeof(String));
= new ConcurrentDictionary<Type, Func<string[], (bool Success, object Result)>>();
/// <summary>
private static readonly ConcurrentDictionary<Type, Func<string[], object>> GenericConvertToMethods /// Attempts to convert a string to the specified type.
= new ConcurrentDictionary<Type, Func<string[], object>>(); /// </summary>
/// <param name="type">The type resulting from the conversion.</param>
/// <summary> /// <param name="str">The string to convert.</param>
/// Determines whether a string can be converted to the specified type. /// <param name="result">When this method returns <see langword="true" />,
/// </summary> /// the result of the conversion. This parameter is passed uninitialized.</param>
/// <param name="type">The type resulting from the conversion.</param> /// <returns><see langword="true" /> if the conversion is successful;
/// <returns><see langword="true" /> if the conversion is possible; /// otherwise, <see langword="false" />.</returns>
/// otherwise, <see langword="false" />.</returns> /// <exception cref="ArgumentNullException"><paramref name="type" /> is <see langword="null" />.</exception>
/// <exception cref="ArgumentNullException"><paramref name="type" /> is <see langword="null" />.</exception> public static Boolean TryConvertTo(Type type, String str, out Object? result) {
public static bool CanConvertTo(Type type) TypeConverter converter = TypeDescriptor.GetConverter(type);
=> TypeDescriptor.GetConverter(type).CanConvertFrom(typeof(string)); if(!converter.CanConvertFrom(typeof(String))) {
result = null;
/// <summary> return false;
/// Determines whether a string can be converted to the specified type. }
/// </summary>
/// <typeparam name="TResult">The type resulting from the conversion.</typeparam> try {
/// <returns><see langword="true" /> if the conversion is possible; result = converter.ConvertFromInvariantString(str);
/// otherwise, <see langword="false" />.</returns> return true;
public static bool CanConvertTo<TResult>() } catch(Exception e) when(!e.IsCriticalException()) {
=> TypeDescriptor.GetConverter(typeof(TResult)).CanConvertFrom(typeof(string)); result = null;
return false;
/// <summary> }
/// Attempts to convert a string to the specified type. }
/// </summary>
/// <param name="type">The type resulting from the conversion.</param> /// <summary>
/// <param name="str">The string to convert.</param> /// Attempts to convert a string to the specified type.
/// <param name="result">When this method returns <see langword="true" />, /// </summary>
/// the result of the conversion. This parameter is passed uninitialized.</param> /// <typeparam name="TResult">The type resulting from the conversion.</typeparam>
/// <returns><see langword="true" /> if the conversion is successful; /// <param name="str">The string to convert.</param>
/// otherwise, <see langword="false" />.</returns> /// <param name="result">When this method returns <see langword="true" />,
/// <exception cref="ArgumentNullException"><paramref name="type" /> is <see langword="null" />.</exception> /// the result of the conversion. This parameter is passed uninitialized.</param>
public static bool TryConvertTo(Type type, string str, out object? result) /// <returns><see langword="true" /> if the conversion is successful;
{ /// otherwise, <see langword="false" />.</returns>
var converter = TypeDescriptor.GetConverter(type); public static Boolean TryConvertTo<TResult>(String str, out TResult result) where TResult : notnull {
if (!converter.CanConvertFrom(typeof(string))) TypeConverter converter = TypeDescriptor.GetConverter(typeof(TResult));
{ if(!converter.CanConvertFrom(typeof(String))) {
result = null; result = default!;
return false; return false;
} }
try try {
{ result = (TResult)converter.ConvertFromInvariantString(str);
result = converter.ConvertFromInvariantString(str); return true;
return true; } catch(Exception e) when(!e.IsCriticalException()) {
} result = default!;
catch (Exception e) when (!e.IsCriticalException()) return false;
{ }
result = null; }
return false;
} /// <summary>
} /// Converts a string to the specified type.
/// </summary>
/// <summary> /// <param name="type">The type resulting from the conversion.</param>
/// Attempts to convert a string to the specified type. /// <param name="str">The string to convert.</param>
/// </summary> /// <returns>An instance of <paramref name="type" />.</returns>
/// <typeparam name="TResult">The type resulting from the conversion.</typeparam> /// <exception cref="ArgumentNullException"><paramref name="type" /> is <see langword="null" />.</exception>
/// <param name="str">The string to convert.</param> /// <exception cref="StringConversionException">The conversion was not successful.</exception>
/// <param name="result">When this method returns <see langword="true" />, public static Object ConvertTo(Type type, String str) {
/// the result of the conversion. This parameter is passed uninitialized.</param> if(type == null) {
/// <returns><see langword="true" /> if the conversion is successful; throw new ArgumentNullException(nameof(type));
/// otherwise, <see langword="false" />.</returns> }
public static bool TryConvertTo<TResult>(string str, out TResult result)
{ try {
var converter = TypeDescriptor.GetConverter(typeof(TResult)); return TypeDescriptor.GetConverter(type).ConvertFromInvariantString(str);
if (!converter.CanConvertFrom(typeof(string))) } catch(Exception e) when(!e.IsCriticalException()) {
{ throw new StringConversionException(type, e);
result = default; }
return false; }
}
/// <summary>
try /// Converts a string to the specified type.
{ /// </summary>
result = (TResult)converter.ConvertFromInvariantString(str); /// <typeparam name="TResult">The type resulting from the conversion.</typeparam>
return true; /// <param name="str">The string to convert.</param>
} /// <returns>An instance of <typeparamref name="TResult" />.</returns>
catch (Exception e) when (!e.IsCriticalException()) /// <exception cref="StringConversionException">
{ /// The conversion was not successful.
result = default; /// </exception>
return false; public static TResult ConvertTo<TResult>(String str) {
} try {
} return (TResult)TypeDescriptor.GetConverter(typeof(TResult)).ConvertFromInvariantString(str);
} catch(Exception e) when(!e.IsCriticalException()) {
/// <summary> throw new StringConversionException(typeof(TResult), e);
/// Converts a string to the specified type. }
/// </summary> }
/// <param name="type">The type resulting from the conversion.</param>
/// <param name="str">The string to convert.</param> /// <summary>
/// <returns>An instance of <paramref name="type" />.</returns> /// Attempts to convert an array of strings to an array of the specified type.
/// <exception cref="ArgumentNullException"><paramref name="type" /> is <see langword="null" />.</exception> /// </summary>
/// <exception cref="StringConversionException">The conversion was not successful.</exception> /// <param name="type">The type resulting from the conversion of each
public static object ConvertTo(Type type, string str) /// element of <paramref name="strings"/>.</param>
{ /// <param name="strings">The array to convert.</param>
if (type == null) /// <param name="result">When this method returns <see langword="true" />,
throw new ArgumentNullException(nameof(type)); /// the result of the conversion. This parameter is passed uninitialized.</param>
/// <returns><see langword="true" /> if the conversion is successful;
try /// otherwise, <see langword="false" />.</returns>
{ /// <exception cref="ArgumentNullException"><paramref name="type" /> is <see langword="null" />.</exception>
return TypeDescriptor.GetConverter(type).ConvertFromInvariantString(str); public static Boolean TryConvertTo(Type type, String[] strings, out Object? result) {
} if(strings == null) {
catch (Exception e) when (!e.IsCriticalException()) result = null;
{ return false;
throw new StringConversionException(type, e); }
}
} Func<String[], (Boolean Success, Object Result)> method = GenericTryConvertToMethods.GetOrAdd(type, BuildNonGenericTryConvertLambda);
(Boolean success, Object methodResult) = method(strings);
/// <summary> result = methodResult;
/// Converts a string to the specified type. return success;
/// </summary> }
/// <typeparam name="TResult">The type resulting from the conversion.</typeparam>
/// <param name="str">The string to convert.</param> /// <summary>
/// <returns>An instance of <typeparamref name="TResult" />.</returns> /// Attempts to convert an array of strings to an array of the specified type.
/// <exception cref="StringConversionException"> /// </summary>
/// The conversion was not successful. /// <typeparam name="TResult">The type resulting from the conversion of each
/// </exception> /// element of <paramref name="strings"/>.</typeparam>
public static TResult ConvertTo<TResult>(string str) /// <param name="strings">The array to convert.</param>
{ /// <param name="result">When this method returns <see langword="true" />,
try /// the result of the conversion. This parameter is passed uninitialized.</param>
{ /// <returns><see langword="true" /> if the conversion is successful;
return (TResult)TypeDescriptor.GetConverter(typeof(TResult)).ConvertFromInvariantString(str); /// otherwise, <see langword="false" />.</returns>
} public static Boolean TryConvertTo<TResult>(String[] strings, out TResult[]? result) {
catch (Exception e) when (!e.IsCriticalException()) if(strings == null) {
{ result = null;
throw new StringConversionException(typeof(TResult), e); return false;
} }
}
TypeConverter converter = TypeDescriptor.GetConverter(typeof(TResult));
/// <summary> if(!converter.CanConvertFrom(typeof(String))) {
/// Attempts to convert an array of strings to an array of the specified type. result = null;
/// </summary> return false;
/// <param name="type">The type resulting from the conversion of each }
/// element of <paramref name="strings"/>.</param>
/// <param name="strings">The array to convert.</param> try {
/// <param name="result">When this method returns <see langword="true" />, result = new TResult[strings.Length];
/// the result of the conversion. This parameter is passed uninitialized.</param> Int32 i = 0;
/// <returns><see langword="true" /> if the conversion is successful; foreach(String str in strings) {
/// otherwise, <see langword="false" />.</returns> result[i++] = (TResult)converter.ConvertFromInvariantString(str);
/// <exception cref="ArgumentNullException"><paramref name="type" /> is <see langword="null" />.</exception> }
public static bool TryConvertTo(Type type, string[] strings, out object? result)
{ return true;
if (strings == null) } catch(Exception e) when(!e.IsCriticalException()) {
{ result = null;
result = null; return false;
return false; }
} }
var method = GenericTryConvertToMethods.GetOrAdd(type, BuildNonGenericTryConvertLambda); /// <summary>
var (success, methodResult) = method(strings); /// Converts an array of strings to an array of the specified type.
result = methodResult; /// </summary>
return success; /// <param name="type">The type resulting from the conversion of each
} /// element of <paramref name="strings"/>.</param>
/// <param name="strings">The array to convert.</param>
/// <summary> /// <returns>An array of <paramref name="type" />.</returns>
/// Attempts to convert an array of strings to an array of the specified type. /// <exception cref="ArgumentNullException"><paramref name="type" /> is <see langword="null" />.</exception>
/// </summary> /// <exception cref="StringConversionException">The conversion of at least one
/// <typeparam name="TResult">The type resulting from the conversion of each /// of the elements of <paramref name="strings"/>was not successful.</exception>
/// element of <paramref name="strings"/>.</typeparam> public static Object? ConvertTo(Type type, String[] strings) {
/// <param name="strings">The array to convert.</param> if(strings == null) {
/// <param name="result">When this method returns <see langword="true" />, return null;
/// the result of the conversion. This parameter is passed uninitialized.</param> }
/// <returns><see langword="true" /> if the conversion is successful;
/// otherwise, <see langword="false" />.</returns> Func<String[], Object> method = GenericConvertToMethods.GetOrAdd(type, BuildNonGenericConvertLambda);
public static bool TryConvertTo<TResult>(string[] strings, out TResult[]? result) return method(strings);
{ }
if (strings == null)
{ /// <summary>
result = null; /// Converts an array of strings to an array of the specified type.
return false; /// </summary>
} /// <typeparam name="TResult">The type resulting from the conversion of each
/// element of <paramref name="strings"/>.</typeparam>
var converter = TypeDescriptor.GetConverter(typeof(TResult)); /// <param name="strings">The array to convert.</param>
if (!converter.CanConvertFrom(typeof(string))) /// <returns>An array of <typeparamref name="TResult" />.</returns>
{ /// <exception cref="StringConversionException">The conversion of at least one
result = null; /// of the elements of <paramref name="strings"/>was not successful.</exception>
return false; public static TResult[]? ConvertTo<TResult>(String[] strings) {
} if(strings == null) {
return null;
try }
{
result = new TResult[strings.Length]; TypeConverter converter = TypeDescriptor.GetConverter(typeof(TResult));
var i = 0; TResult[] result = new TResult[strings.Length];
foreach (var str in strings) Int32 i = 0;
result[i++] = (TResult)converter.ConvertFromInvariantString(str); try {
foreach(String str in strings) {
return true; result[i++] = (TResult)converter.ConvertFromInvariantString(str);
} }
catch (Exception e) when (!e.IsCriticalException()) } catch(Exception e) when(!e.IsCriticalException()) {
{ throw new StringConversionException(typeof(TResult), e);
result = null; }
return false;
} return result;
} }
/// <summary> /// <summary>
/// Converts an array of strings to an array of the specified type. /// Converts a expression, if the type can be converted to string, to a new expression including
/// </summary> /// the conversion to string.
/// <param name="type">The type resulting from the conversion of each /// </summary>
/// element of <paramref name="strings"/>.</param> /// <param name="type">The type.</param>
/// <param name="strings">The array to convert.</param> /// <param name="str">The string.</param>
/// <returns>An array of <paramref name="type" />.</returns> /// <returns>A new expression where the previous expression is converted to string.</returns>
/// <exception cref="ArgumentNullException"><paramref name="type" /> is <see langword="null" />.</exception> public static Expression? ConvertExpressionTo(Type type, Expression str) {
/// <exception cref="StringConversionException">The conversion of at least one TypeConverter converter = TypeDescriptor.GetConverter(type);
/// of the elements of <paramref name="strings"/>was not successful.</exception>
public static object? ConvertTo(Type type, string[] strings) return converter.CanConvertFrom(typeof(String))
{ ? Expression.Convert(
if (strings == null) Expression.Call(Expression.Constant(converter), ConvertFromInvariantStringMethod, str),
return null; type)
: null;
var method = GenericConvertToMethods.GetOrAdd(type, BuildNonGenericConvertLambda); }
return method(strings);
} private static Func<String[], (Boolean Success, Object Result)> BuildNonGenericTryConvertLambda(Type type) {
MethodInfo? methodInfo = TryConvertToInternalMethod?.MakeGenericMethod(type);
/// <summary> ParameterExpression parameter = Expression.Parameter(typeof(String[]));
/// Converts an array of strings to an array of the specified type. MethodCallExpression body = Expression.Call(methodInfo, parameter);
/// </summary> Expression<Func<String[], (Boolean Success, Object Result)>> lambda = Expression.Lambda<Func<String[], (Boolean Success, Object Result)>>(body, parameter);
/// <typeparam name="TResult">The type resulting from the conversion of each return lambda.Compile();
/// element of <paramref name="strings"/>.</typeparam> }
/// <param name="strings">The array to convert.</param>
/// <returns>An array of <typeparamref name="TResult" />.</returns> private static (Boolean Success, Object? Result) TryConvertToInternal<TResult>(String[] strings) {
/// <exception cref="StringConversionException">The conversion of at least one TypeConverter converter = TypeDescriptor.GetConverter(typeof(TResult));
/// of the elements of <paramref name="strings"/>was not successful.</exception> if(!converter.CanConvertFrom(typeof(String))) {
public static TResult[]? ConvertTo<TResult>(string[] strings) return (false, null);
{ }
if (strings == null)
return null; TResult[] result = new TResult[strings.Length];
Int32 i = 0;
var converter = TypeDescriptor.GetConverter(typeof(TResult)); try {
var result = new TResult[strings.Length]; foreach(String str in strings) {
var i = 0; result[i++] = (TResult)converter.ConvertFromInvariantString(str);
try }
{
foreach (var str in strings) return (true, result);
result[i++] = (TResult)converter.ConvertFromInvariantString(str); } catch(Exception e) when(!e.IsCriticalException()) {
} return (false, null);
catch (Exception e) when (!e.IsCriticalException()) }
{ }
throw new StringConversionException(typeof(TResult), e);
} private static Func<String[], Object> BuildNonGenericConvertLambda(Type type) {
MethodInfo? methodInfo = ConvertToInternalMethod?.MakeGenericMethod(type);
return result; ParameterExpression parameter = Expression.Parameter(typeof(String[]));
} MethodCallExpression body = Expression.Call(methodInfo, parameter);
Expression<Func<String[], Object>> lambda = Expression.Lambda<Func<String[], Object>>(body, parameter);
/// <summary> return lambda.Compile();
/// Converts a expression, if the type can be converted to string, to a new expression including }
/// the conversion to string.
/// </summary> private static Object ConvertToInternal<TResult>(String[] strings) {
/// <param name="type">The type.</param> TypeConverter converter = TypeDescriptor.GetConverter(typeof(TResult));
/// <param name="str">The string.</param> TResult[] result = new TResult[strings.Length];
/// <returns>A new expression where the previous expression is converted to string.</returns> Int32 i = 0;
public static Expression? ConvertExpressionTo(Type type, Expression str) try {
{ foreach(String str in strings) {
var converter = TypeDescriptor.GetConverter(type); result[i++] = (TResult)converter.ConvertFromInvariantString(str);
}
return converter.CanConvertFrom(typeof(string))
? Expression.Convert( return result;
Expression.Call(Expression.Constant(converter), ConvertFromInvariantStringMethod, str), } catch(Exception e) when(!e.IsCriticalException()) {
type) throw new StringConversionException(typeof(TResult), e);
: null; }
} }
}
private static Func<string[], (bool Success, object Result)> BuildNonGenericTryConvertLambda(Type type)
{
var methodInfo = TryConvertToInternalMethod.MakeGenericMethod(type);
var parameter = Expression.Parameter(typeof(string[]));
var body = Expression.Call(methodInfo, parameter);
var lambda = Expression.Lambda<Func<string[], (bool Success, object Result)>>(body, parameter);
return lambda.Compile();
}
private static (bool Success, object? Result) TryConvertToInternal<TResult>(string[] strings)
{
var converter = TypeDescriptor.GetConverter(typeof(TResult));
if (!converter.CanConvertFrom(typeof(string)))
return (false, null);
var result = new TResult[strings.Length];
var i = 0;
try
{
foreach (var str in strings)
result[i++] = (TResult)converter.ConvertFromInvariantString(str);
return (true, result);
}
catch (Exception e) when (!e.IsCriticalException())
{
return (false, null);
}
}
private static Func<string[], object> BuildNonGenericConvertLambda(Type type)
{
var methodInfo = ConvertToInternalMethod.MakeGenericMethod(type);
var parameter = Expression.Parameter(typeof(string[]));
var body = Expression.Call(methodInfo, parameter);
var lambda = Expression.Lambda<Func<string[], object>>(body, parameter);
return lambda.Compile();
}
private static object ConvertToInternal<TResult>(string[] strings)
{
var converter = TypeDescriptor.GetConverter(typeof(TResult));
var result = new TResult[strings.Length];
var i = 0;
try
{
foreach (var str in strings)
result[i++] = (TResult)converter.ConvertFromInvariantString(str);
return result;
}
catch (Exception e) when (!e.IsCriticalException())
{
throw new StringConversionException(typeof(TResult), e);
}
}
}
} }

View File

@ -1,146 +1,139 @@
using System; using System;
using Swan.Lite.Logging; using Swan.Lite.Logging;
namespace Swan.Logging namespace Swan.Logging {
{ /// <summary>
/// Represents a Console implementation of <c>ILogger</c>.
/// </summary>
/// <seealso cref="ILogger" />
public class ConsoleLogger : TextLogger, ILogger {
/// <summary> /// <summary>
/// Represents a Console implementation of <c>ILogger</c>. /// Initializes a new instance of the <see cref="ConsoleLogger"/> class.
/// </summary> /// </summary>
/// <seealso cref="ILogger" /> protected ConsoleLogger() {
public class ConsoleLogger : TextLogger, ILogger // Empty
{ }
/// <summary>
/// Initializes a new instance of the <see cref="ConsoleLogger"/> class. /// <summary>
/// </summary> /// Gets the current instance of ConsoleLogger.
protected ConsoleLogger() /// </summary>
{ /// <value>
// Empty /// The instance.
} /// </value>
public static ConsoleLogger Instance { get; } = new ConsoleLogger();
/// <summary>
/// Gets the current instance of ConsoleLogger. /// <summary>
/// </summary> /// Gets or sets the debug logging prefix.
/// <value> /// </summary>
/// The instance. /// <value>
/// </value> /// The debug prefix.
public static ConsoleLogger Instance { get; } = new ConsoleLogger(); /// </value>
public static String DebugPrefix { get; set; } = "DBG";
/// <summary>
/// Gets or sets the debug logging prefix. /// <summary>
/// </summary> /// Gets or sets the trace logging prefix.
/// <value> /// </summary>
/// The debug prefix. /// <value>
/// </value> /// The trace prefix.
public static string DebugPrefix { get; set; } = "DBG"; /// </value>
public static String TracePrefix { get; set; } = "TRC";
/// <summary>
/// Gets or sets the trace logging prefix. /// <summary>
/// </summary> /// Gets or sets the warning logging prefix.
/// <value> /// </summary>
/// The trace prefix. /// <value>
/// </value> /// The warn prefix.
public static string TracePrefix { get; set; } = "TRC"; /// </value>
public static String WarnPrefix { get; set; } = "WRN";
/// <summary>
/// Gets or sets the warning logging prefix. /// <summary>
/// </summary> /// Gets or sets the fatal logging prefix.
/// <value> /// </summary>
/// The warn prefix. /// <value>
/// </value> /// The fatal prefix.
public static string WarnPrefix { get; set; } = "WRN"; /// </value>
public static String FatalPrefix { get; set; } = "FAT";
/// <summary>
/// Gets or sets the fatal logging prefix. /// <summary>
/// </summary> /// Gets or sets the error logging prefix.
/// <value> /// </summary>
/// The fatal prefix. /// <value>
/// </value> /// The error prefix.
public static string FatalPrefix { get; set; } = "FAT"; /// </value>
public static String ErrorPrefix { get; set; } = "ERR";
/// <summary>
/// Gets or sets the error logging prefix. /// <summary>
/// </summary> /// Gets or sets the information logging prefix.
/// <value> /// </summary>
/// The error prefix. /// <value>
/// </value> /// The information prefix.
public static string ErrorPrefix { get; set; } = "ERR"; /// </value>
public static String InfoPrefix { get; set; } = "INF";
/// <summary>
/// Gets or sets the information logging prefix. /// <summary>
/// </summary> /// Gets or sets the color of the information output logging.
/// <value> /// </summary>
/// The information prefix. /// <value>
/// </value> /// The color of the information.
public static string InfoPrefix { get; set; } = "INF"; /// </value>
public static ConsoleColor InfoColor { get; set; } = ConsoleColor.Cyan;
/// <summary>
/// Gets or sets the color of the information output logging. /// <summary>
/// </summary> /// Gets or sets the color of the debug output logging.
/// <value> /// </summary>
/// The color of the information. /// <value>
/// </value> /// The color of the debug.
public static ConsoleColor InfoColor { get; set; } = ConsoleColor.Cyan; /// </value>
public static ConsoleColor DebugColor { get; set; } = ConsoleColor.Gray;
/// <summary>
/// Gets or sets the color of the debug output logging. /// <summary>
/// </summary> /// Gets or sets the color of the trace output logging.
/// <value> /// </summary>
/// The color of the debug. /// <value>
/// </value> /// The color of the trace.
public static ConsoleColor DebugColor { get; set; } = ConsoleColor.Gray; /// </value>
public static ConsoleColor TraceColor { get; set; } = ConsoleColor.DarkGray;
/// <summary>
/// Gets or sets the color of the trace output logging. /// <summary>
/// </summary> /// Gets or sets the color of the warning logging.
/// <value> /// </summary>
/// The color of the trace. /// <value>
/// </value> /// The color of the warn.
public static ConsoleColor TraceColor { get; set; } = ConsoleColor.DarkGray; /// </value>
public static ConsoleColor WarnColor { get; set; } = ConsoleColor.Yellow;
/// <summary>
/// Gets or sets the color of the warning logging. /// <summary>
/// </summary> /// Gets or sets the color of the error logging.
/// <value> /// </summary>
/// The color of the warn. /// <value>
/// </value> /// The color of the error.
public static ConsoleColor WarnColor { get; set; } = ConsoleColor.Yellow; /// </value>
public static ConsoleColor ErrorColor { get; set; } = ConsoleColor.DarkRed;
/// <summary>
/// Gets or sets the color of the error logging. /// <summary>
/// </summary> /// Gets or sets the color of the error logging.
/// <value> /// </summary>
/// The color of the error. /// <value>
/// </value> /// The color of the error.
public static ConsoleColor ErrorColor { get; set; } = ConsoleColor.DarkRed; /// </value>
public static ConsoleColor FatalColor { get; set; } = ConsoleColor.Red;
/// <summary>
/// Gets or sets the color of the error logging. /// <inheritdoc />
/// </summary> public LogLevel LogLevel { get; set; } = DebugLogger.IsDebuggerAttached ? LogLevel.Trace : LogLevel.Info;
/// <value>
/// The color of the error. /// <inheritdoc />
/// </value> public void Log(LogMessageReceivedEventArgs logEvent) {
public static ConsoleColor FatalColor { get; set; } = ConsoleColor.Red; // Select the writer based on the message type
TerminalWriters writer = logEvent.MessageType == LogLevel.Error ? TerminalWriters.StandardError : TerminalWriters.StandardOutput;
/// <inheritdoc />
public LogLevel LogLevel { get; set; } = DebugLogger.IsDebuggerAttached ? LogLevel.Trace : LogLevel.Info; (String outputMessage, ConsoleColor color) = this.GetOutputAndColor(logEvent);
/// <inheritdoc /> Terminal.Write(outputMessage, color, writer);
public void Log(LogMessageReceivedEventArgs logEvent) }
{
// Select the writer based on the message type /// <inheritdoc />
var writer = logEvent.MessageType == LogLevel.Error public void Dispose() {
? TerminalWriters.StandardError // Do nothing
: TerminalWriters.StandardOutput; }
}
var (outputMessage, color) = GetOutputAndColor(logEvent);
Terminal.Write(outputMessage, color, writer);
}
/// <inheritdoc />
public void Dispose()
{
// Do nothing
}
}
} }

View File

@ -1,53 +1,49 @@
using Swan.Lite.Logging; using System;
using Swan.Lite.Logging;
namespace Swan.Logging namespace Swan.Logging {
{ /// <summary>
/// Represents a logger target. This target will write to the
/// Debug console using System.Diagnostics.Debug.
/// </summary>
/// <seealso cref="ILogger" />
public class DebugLogger : TextLogger, ILogger {
/// <summary> /// <summary>
/// Represents a logger target. This target will write to the /// Initializes a new instance of the <see cref="DebugLogger"/> class.
/// Debug console using System.Diagnostics.Debug.
/// </summary> /// </summary>
/// <seealso cref="ILogger" /> protected DebugLogger() {
public class DebugLogger : TextLogger, ILogger // Empty
{ }
/// <summary>
/// Initializes a new instance of the <see cref="DebugLogger"/> class. /// <summary>
/// </summary> /// Gets the current instance of DebugLogger.
protected DebugLogger() /// </summary>
{ /// <value>
// Empty /// The instance.
} /// </value>
public static DebugLogger Instance { get; } = new DebugLogger();
/// <summary>
/// Gets the current instance of DebugLogger. /// <summary>
/// </summary> /// Gets a value indicating whether a debugger is attached.
/// <value> /// </summary>
/// The instance. /// <value>
/// </value> /// <c>true</c> if this instance is debugger attached; otherwise, <c>false</c>.
public static DebugLogger Instance { get; } = new DebugLogger(); /// </value>
public static Boolean IsDebuggerAttached => System.Diagnostics.Debugger.IsAttached;
/// <summary>
/// Gets a value indicating whether a debugger is attached. /// <inheritdoc/>
/// </summary> public LogLevel LogLevel { get; set; } = IsDebuggerAttached ? LogLevel.Trace : LogLevel.None;
/// <value>
/// <c>true</c> if this instance is debugger attached; otherwise, <c>false</c>. /// <inheritdoc/>
/// </value> public void Log(LogMessageReceivedEventArgs logEvent) {
public static bool IsDebuggerAttached => System.Diagnostics.Debugger.IsAttached; (String outputMessage, ConsoleColor _) = this.GetOutputAndColor(logEvent);
/// <inheritdoc/> System.Diagnostics.Debug.Write(outputMessage);
public LogLevel LogLevel { get; set; } = IsDebuggerAttached ? LogLevel.Trace : LogLevel.None; }
/// <inheritdoc/> /// <inheritdoc/>
public void Log(LogMessageReceivedEventArgs logEvent) public void Dispose() {
{ // do nothing
var (outputMessage, _) = GetOutputAndColor(logEvent); }
}
System.Diagnostics.Debug.Write(outputMessage);
}
/// <inheritdoc/>
public void Dispose()
{
// do nothing
}
}
} }

View File

@ -6,129 +6,118 @@ using System.Threading.Tasks;
using Swan.Lite.Logging; using Swan.Lite.Logging;
using Swan.Threading; using Swan.Threading;
namespace Swan.Logging namespace Swan.Logging {
{ /// <summary>
/// A helper class to write into files the messages sent by the <see cref="Terminal" />.
/// </summary>
/// <seealso cref="ILogger" />
public class FileLogger : TextLogger, ILogger {
private readonly ManualResetEventSlim _doneEvent = new ManualResetEventSlim(true);
private readonly ConcurrentQueue<String> _logQueue = new ConcurrentQueue<String>();
private readonly ExclusiveTimer _timer;
private readonly String _filePath;
private Boolean _disposedValue; // To detect redundant calls
/// <summary> /// <summary>
/// A helper class to write into files the messages sent by the <see cref="Terminal" />. /// Initializes a new instance of the <see cref="FileLogger"/> class.
/// </summary> /// </summary>
/// <seealso cref="ILogger" /> public FileLogger() : this(SwanRuntime.EntryAssemblyDirectory, true) {
public class FileLogger : TextLogger, ILogger }
{
private readonly ManualResetEventSlim _doneEvent = new ManualResetEventSlim(true); /// <summary>
private readonly ConcurrentQueue<string> _logQueue = new ConcurrentQueue<string>(); /// Initializes a new instance of the <see cref="FileLogger"/> class.
private readonly ExclusiveTimer _timer; /// </summary>
private readonly string _filePath; /// <param name="filePath">The filePath.</param>
/// <param name="dailyFile">if set to <c>true</c> [daily file].</param>
private bool _disposedValue; // To detect redundant calls public FileLogger(String filePath, Boolean dailyFile) {
this._filePath = filePath;
/// <summary> this.DailyFile = dailyFile;
/// Initializes a new instance of the <see cref="FileLogger"/> class.
/// </summary> this._timer = new ExclusiveTimer(async () => await this.WriteLogEntries().ConfigureAwait(false), TimeSpan.Zero, TimeSpan.FromSeconds(5));
public FileLogger() }
: this(SwanRuntime.EntryAssemblyDirectory, true)
{ /// <inheritdoc />
} public LogLevel LogLevel {
get; set;
/// <summary> }
/// Initializes a new instance of the <see cref="FileLogger"/> class.
/// </summary> /// <summary>
/// <param name="filePath">The filePath.</param> /// Gets the file path.
/// <param name="dailyFile">if set to <c>true</c> [daily file].</param> /// </summary>
public FileLogger(string filePath, bool dailyFile) /// <value>
{ /// The file path.
_filePath = filePath; /// </value>
DailyFile = dailyFile; public String FilePath => this.DailyFile ? Path.Combine(Path.GetDirectoryName(this._filePath), Path.GetFileNameWithoutExtension(this._filePath) + $"_{DateTime.UtcNow:yyyyMMdd}" + Path.GetExtension(this._filePath)) : this._filePath;
_timer = new ExclusiveTimer( /// <summary>
async () => await WriteLogEntries().ConfigureAwait(false), /// Gets a value indicating whether [daily file].
TimeSpan.Zero, /// </summary>
TimeSpan.FromSeconds(5)); /// <value>
} /// <c>true</c> if [daily file]; otherwise, <c>false</c>.
/// </value>
/// <inheritdoc /> public Boolean DailyFile {
public LogLevel LogLevel { get; set; } get;
}
/// <summary>
/// Gets the file path. /// <inheritdoc />
/// </summary> public void Log(LogMessageReceivedEventArgs logEvent) {
/// <value> (String outputMessage, ConsoleColor _) = this.GetOutputAndColor(logEvent);
/// The file path.
/// </value> this._logQueue.Enqueue(outputMessage);
public string FilePath => DailyFile }
? Path.Combine(Path.GetDirectoryName(_filePath), Path.GetFileNameWithoutExtension(_filePath) + $"_{DateTime.UtcNow:yyyyMMdd}" + Path.GetExtension(_filePath))
: _filePath; /// <inheritdoc />
public void Dispose() {
/// <summary> this.Dispose(true);
/// Gets a value indicating whether [daily file]. GC.SuppressFinalize(this);
/// </summary> }
/// <value>
/// <c>true</c> if [daily file]; otherwise, <c>false</c>. /// <summary>
/// </value> /// Releases unmanaged and - optionally - managed resources.
public bool DailyFile { get; } /// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
/// <inheritdoc /> protected virtual void Dispose(Boolean disposing) {
public void Log(LogMessageReceivedEventArgs logEvent) if(this._disposedValue) {
{ return;
var (outputMessage, _) = GetOutputAndColor(logEvent); }
_logQueue.Enqueue(outputMessage); if(disposing) {
} this._timer.Pause();
this._timer.Dispose();
/// <inheritdoc />
public void Dispose() this._doneEvent.Wait();
{ this._doneEvent.Reset();
Dispose(true); this.WriteLogEntries(true).Await();
GC.SuppressFinalize(this); this._doneEvent.Dispose();
} }
/// <summary> this._disposedValue = true;
/// Releases unmanaged and - optionally - managed resources. }
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> private async Task WriteLogEntries(Boolean finalCall = false) {
protected virtual void Dispose(bool disposing) if(this._logQueue.IsEmpty) {
{ return;
if (_disposedValue) return; }
if (disposing) if(!finalCall && !this._doneEvent.IsSet) {
{ return;
_timer.Pause(); }
_timer.Dispose();
this._doneEvent.Reset();
_doneEvent.Wait();
_doneEvent.Reset(); try {
WriteLogEntries(true).Await(); using StreamWriter file = File.AppendText(this.FilePath);
_doneEvent.Dispose(); while(!this._logQueue.IsEmpty) {
} if(this._logQueue.TryDequeue(out String entry)) {
await file.WriteAsync(entry).ConfigureAwait(false);
_disposedValue = true; }
} }
} finally {
private async Task WriteLogEntries(bool finalCall = false) if(!finalCall) {
{ this._doneEvent.Set();
if (_logQueue.IsEmpty) }
return; }
}
if (!finalCall && !_doneEvent.IsSet) }
return;
_doneEvent.Reset();
try
{
using (var file = File.AppendText(FilePath))
{
while (!_logQueue.IsEmpty)
{
if (_logQueue.TryDequeue(out var entry))
await file.WriteAsync(entry).ConfigureAwait(false);
}
}
}
finally
{
if (!finalCall)
_doneEvent.Set();
}
}
}
} }

View File

@ -1,24 +1,24 @@
namespace Swan.Logging using System;
{
using System; namespace Swan.Logging {
/// <summary>
/// Interface for a logger implementation.
/// </summary>
public interface ILogger : IDisposable {
/// <summary> /// <summary>
/// Interface for a logger implementation. /// Gets the log level.
/// </summary> /// </summary>
public interface ILogger : IDisposable /// <value>
{ /// The log level.
/// <summary> /// </value>
/// Gets the log level. LogLevel LogLevel {
/// </summary> get;
/// <value> }
/// The log level.
/// </value> /// <summary>
LogLevel LogLevel { get; } /// Logs the specified log event.
/// </summary>
/// <summary> /// <param name="logEvent">The <see cref="LogMessageReceivedEventArgs"/> instance containing the event data.</param>
/// Logs the specified log event. void Log(LogMessageReceivedEventArgs logEvent);
/// </summary> }
/// <param name="logEvent">The <see cref="LogMessageReceivedEventArgs"/> instance containing the event data.</param>
void Log(LogMessageReceivedEventArgs logEvent);
}
} }

View File

@ -1,43 +1,41 @@
namespace Swan namespace Swan {
{ /// <summary>
/// Defines the log levels.
/// </summary>
public enum LogLevel {
/// <summary> /// <summary>
/// Defines the log levels. /// The none message type
/// </summary> /// </summary>
public enum LogLevel None,
{
/// <summary> /// <summary>
/// The none message type /// The trace message type
/// </summary> /// </summary>
None, Trace,
/// <summary> /// <summary>
/// The trace message type /// The debug message type
/// </summary> /// </summary>
Trace, Debug,
/// <summary> /// <summary>
/// The debug message type /// The information message type
/// </summary> /// </summary>
Debug, Info,
/// <summary> /// <summary>
/// The information message type /// The warning message type
/// </summary> /// </summary>
Info, Warning,
/// <summary> /// <summary>
/// The warning message type /// The error message type
/// </summary> /// </summary>
Warning, Error,
/// <summary> /// <summary>
/// The error message type /// The fatal message type
/// </summary> /// </summary>
Error, Fatal,
}
/// <summary>
/// The fatal message type
/// </summary>
Fatal,
}
} }

View File

@ -1,131 +1,147 @@
using System; #nullable enable
using System;
namespace Swan namespace Swan {
{ /// <summary>
/// Event arguments representing the message that is logged
/// on to the terminal. Use the properties to forward the data to
/// your logger of choice.
/// </summary>
/// <seealso cref="System.EventArgs" />
public class LogMessageReceivedEventArgs : EventArgs {
/// <summary> /// <summary>
/// Event arguments representing the message that is logged /// Initializes a new instance of the <see cref="LogMessageReceivedEventArgs" /> class.
/// on to the terminal. Use the properties to forward the data to
/// your logger of choice.
/// </summary> /// </summary>
/// <seealso cref="System.EventArgs" /> /// <param name="sequence">The sequence.</param>
public class LogMessageReceivedEventArgs : EventArgs /// <param name="messageType">Type of the message.</param>
{ /// <param name="utcDate">The UTC date.</param>
/// <summary> /// <param name="source">The source.</param>
/// Initializes a new instance of the <see cref="LogMessageReceivedEventArgs" /> class. /// <param name="message">The message.</param>
/// </summary> /// <param name="extendedData">The extended data.</param>
/// <param name="sequence">The sequence.</param> /// <param name="callerMemberName">Name of the caller member.</param>
/// <param name="messageType">Type of the message.</param> /// <param name="callerFilePath">The caller file path.</param>
/// <param name="utcDate">The UTC date.</param> /// <param name="callerLineNumber">The caller line number.</param>
/// <param name="source">The source.</param> public LogMessageReceivedEventArgs(
/// <param name="message">The message.</param> UInt64 sequence,
/// <param name="extendedData">The extended data.</param> LogLevel messageType,
/// <param name="callerMemberName">Name of the caller member.</param> DateTime utcDate,
/// <param name="callerFilePath">The caller file path.</param> String source,
/// <param name="callerLineNumber">The caller line number.</param> String message,
public LogMessageReceivedEventArgs( Object? extendedData,
ulong sequence, String callerMemberName,
LogLevel messageType, String callerFilePath,
DateTime utcDate, Int32 callerLineNumber) {
string source, this.Sequence = sequence;
string message, this.MessageType = messageType;
object? extendedData, this.UtcDate = utcDate;
string callerMemberName, this.Source = source;
string callerFilePath, this.Message = message;
int callerLineNumber) this.CallerMemberName = callerMemberName;
{ this.CallerFilePath = callerFilePath;
Sequence = sequence; this.CallerLineNumber = callerLineNumber;
MessageType = messageType; this.ExtendedData = extendedData;
UtcDate = utcDate; }
Source = source;
Message = message; /// <summary>
CallerMemberName = callerMemberName; /// Gets logging message sequence.
CallerFilePath = callerFilePath; /// </summary>
CallerLineNumber = callerLineNumber; /// <value>
ExtendedData = extendedData; /// The sequence.
} /// </value>
public UInt64 Sequence {
/// <summary> get;
/// Gets logging message sequence. }
/// </summary>
/// <value> /// <summary>
/// The sequence. /// Gets the type of the message.
/// </value> /// It can be a combination as the enumeration is a set of bitwise flags.
public ulong Sequence { get; } /// </summary>
/// <value>
/// <summary> /// The type of the message.
/// Gets the type of the message. /// </value>
/// It can be a combination as the enumeration is a set of bitwise flags. public LogLevel MessageType {
/// </summary> get;
/// <value> }
/// The type of the message.
/// </value> /// <summary>
public LogLevel MessageType { get; } /// Gets the UTC date at which the event at which the message was logged.
/// </summary>
/// <summary> /// <value>
/// Gets the UTC date at which the event at which the message was logged. /// The UTC date.
/// </summary> /// </value>
/// <value> public DateTime UtcDate {
/// The UTC date. get;
/// </value> }
public DateTime UtcDate { get; }
/// <summary>
/// <summary> /// Gets the name of the source where the logging message
/// Gets the name of the source where the logging message /// came from. This can come empty if the logger did not set it.
/// came from. This can come empty if the logger did not set it. /// </summary>
/// </summary> /// <value>
/// <value> /// The source.
/// The source. /// </value>
/// </value> public String Source {
public string Source { get; } get;
}
/// <summary>
/// Gets the body of the message. /// <summary>
/// </summary> /// Gets the body of the message.
/// <value> /// </summary>
/// The message. /// <value>
/// </value> /// The message.
public string Message { get; } /// </value>
public String Message {
/// <summary> get;
/// Gets the name of the caller member. }
/// </summary>
/// <value> /// <summary>
/// The name of the caller member. /// Gets the name of the caller member.
/// </value> /// </summary>
public string CallerMemberName { get; } /// <value>
/// The name of the caller member.
/// <summary> /// </value>
/// Gets the caller file path. public String CallerMemberName {
/// </summary> get;
/// <value> }
/// The caller file path.
/// </value> /// <summary>
public string CallerFilePath { get; } /// Gets the caller file path.
/// </summary>
/// <summary> /// <value>
/// Gets the caller line number. /// The caller file path.
/// </summary> /// </value>
/// <value> public String CallerFilePath {
/// The caller line number. get;
/// </value> }
public int CallerLineNumber { get; }
/// <summary>
/// <summary> /// Gets the caller line number.
/// Gets an object representing extended data. /// </summary>
/// It could be an exception or anything else. /// <value>
/// </summary> /// The caller line number.
/// <value> /// </value>
/// The extended data. public Int32 CallerLineNumber {
/// </value> get;
public object? ExtendedData { get; } }
/// <summary> /// <summary>
/// Gets the Extended Data properties cast as an Exception (if possible) /// Gets an object representing extended data.
/// Otherwise, it return null. /// It could be an exception or anything else.
/// </summary> /// </summary>
/// <value> /// <value>
/// The exception. /// The extended data.
/// </value> /// </value>
public Exception? Exception => ExtendedData as Exception; public Object? ExtendedData {
} get;
}
/// <summary>
/// Gets the Extended Data properties cast as an Exception (if possible)
/// Otherwise, it return null.
/// </summary>
/// <value>
/// The exception.
/// </value>
public Exception? Exception => this.ExtendedData as Exception;
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,89 +1,63 @@
using Swan.Logging; using Swan.Logging;
using System; using System;
namespace Swan.Lite.Logging namespace Swan.Lite.Logging {
{ /// <summary>
/// Use this class for text-based logger.
/// </summary>
public abstract class TextLogger {
/// <summary> /// <summary>
/// Use this class for text-based logger. /// Gets or sets the logging time format.
/// set to null or empty to prevent output.
/// </summary> /// </summary>
public abstract class TextLogger /// <value>
{ /// The logging time format.
/// <summary> /// </value>
/// Gets or sets the logging time format. public static String LoggingTimeFormat { get; set; } = "HH:mm:ss.fff";
/// set to null or empty to prevent output.
/// </summary> /// <summary>
/// <value> /// Gets the color of the output of the message (the output message has a new line char in the end).
/// The logging time format. /// </summary>
/// </value> /// <param name="logEvent">The <see cref="LogMessageReceivedEventArgs" /> instance containing the event data.</param>
public static string LoggingTimeFormat { get; set; } = "HH:mm:ss.fff"; /// <returns>
/// The output message formatted and the color of the console to be used.
/// <summary> /// </returns>
/// Gets the color of the output of the message (the output message has a new line char in the end). protected (String outputMessage, ConsoleColor color) GetOutputAndColor(LogMessageReceivedEventArgs logEvent) {
/// </summary> (String prefix, ConsoleColor color) = GetConsoleColorAndPrefix(logEvent.MessageType);
/// <param name="logEvent">The <see cref="LogMessageReceivedEventArgs" /> instance containing the event data.</param>
/// <returns> String loggerMessage = String.IsNullOrWhiteSpace(logEvent.Message) ? String.Empty : logEvent.Message.RemoveControlCharsExcept('\n');
/// The output message formatted and the color of the console to be used.
/// </returns> String outputMessage = CreateOutputMessage(logEvent.Source, loggerMessage, prefix, logEvent.UtcDate);
protected (string outputMessage, ConsoleColor color) GetOutputAndColor(LogMessageReceivedEventArgs logEvent)
{ // Further format the output in the case there is an exception being logged
var (prefix , color) = GetConsoleColorAndPrefix(logEvent.MessageType); if(logEvent.MessageType == LogLevel.Error && logEvent.Exception != null) {
try {
var loggerMessage = string.IsNullOrWhiteSpace(logEvent.Message) outputMessage += $"{logEvent.Exception.Stringify().Indent()}{Environment.NewLine}";
? string.Empty } catch {
: logEvent.Message.RemoveControlCharsExcept('\n'); // Ignore
}
var outputMessage = CreateOutputMessage(logEvent.Source, loggerMessage, prefix, logEvent.UtcDate); }
// Further format the output in the case there is an exception being logged return (outputMessage, color);
if (logEvent.MessageType == LogLevel.Error && logEvent.Exception != null) }
{
try private static (String Prefix, ConsoleColor color) GetConsoleColorAndPrefix(LogLevel messageType) => messageType switch
{ {
outputMessage += $"{logEvent.Exception.Stringify().Indent()}{Environment.NewLine}"; LogLevel.Debug => (ConsoleLogger.DebugPrefix, ConsoleLogger.DebugColor),
} LogLevel.Error => (ConsoleLogger.ErrorPrefix, ConsoleLogger.ErrorColor),
catch LogLevel.Info => (ConsoleLogger.InfoPrefix, ConsoleLogger.InfoColor),
{ LogLevel.Trace => (ConsoleLogger.TracePrefix, ConsoleLogger.TraceColor),
// Ignore LogLevel.Warning => (ConsoleLogger.WarnPrefix, ConsoleLogger.WarnColor),
} LogLevel.Fatal => (ConsoleLogger.FatalPrefix, ConsoleLogger.FatalColor),
} _ => (new String(' ', ConsoleLogger.InfoPrefix.Length), Terminal.Settings.DefaultColor),
};
return (outputMessage, color);
} private static String CreateOutputMessage(String sourceName, String loggerMessage, String prefix, DateTime date) {
String friendlySourceName = String.IsNullOrWhiteSpace(sourceName) ? String.Empty : sourceName.SliceLength(sourceName.LastIndexOf('.') + 1, sourceName.Length);
private static (string Prefix, ConsoleColor color) GetConsoleColorAndPrefix(LogLevel messageType)
{ String outputMessage = String.IsNullOrWhiteSpace(sourceName) ? loggerMessage : $"[{friendlySourceName}] {loggerMessage}";
switch (messageType)
{ return String.IsNullOrWhiteSpace(LoggingTimeFormat) ? $" {prefix} >> {outputMessage}{Environment.NewLine}" : $" {date.ToLocalTime().ToString(LoggingTimeFormat)} {prefix} >> {outputMessage}{Environment.NewLine}";
case LogLevel.Debug: }
return (ConsoleLogger.DebugPrefix, ConsoleLogger.DebugColor); }
case LogLevel.Error:
return (ConsoleLogger.ErrorPrefix, ConsoleLogger.ErrorColor);
case LogLevel.Info:
return (ConsoleLogger.InfoPrefix, ConsoleLogger.InfoColor);
case LogLevel.Trace:
return (ConsoleLogger.TracePrefix, ConsoleLogger.TraceColor);
case LogLevel.Warning:
return (ConsoleLogger.WarnPrefix, ConsoleLogger.WarnColor);
case LogLevel.Fatal:
return (ConsoleLogger.FatalPrefix, ConsoleLogger.FatalColor);
default:
return (new string(' ', ConsoleLogger.InfoPrefix.Length), Terminal.Settings.DefaultColor);
}
}
private static string CreateOutputMessage(string sourceName, string loggerMessage, string prefix, DateTime date)
{
var friendlySourceName = string.IsNullOrWhiteSpace(sourceName)
? string.Empty
: sourceName.SliceLength(sourceName.LastIndexOf('.') + 1, sourceName.Length);
var outputMessage = string.IsNullOrWhiteSpace(sourceName)
? loggerMessage
: $"[{friendlySourceName}] {loggerMessage}";
return string.IsNullOrWhiteSpace(LoggingTimeFormat)
? $" {prefix} >> {outputMessage}{Environment.NewLine}"
: $" {date.ToLocalTime().ToString(LoggingTimeFormat)} {prefix} >> {outputMessage}{Environment.NewLine}";
}
}
} }

View File

@ -1,13 +1,11 @@
using System; using System;
namespace Swan.Mappers namespace Swan.Mappers {
{ /// <summary>
/// <summary> /// Represents an attribute to select which properties are copyable between objects.
/// Represents an attribute to select which properties are copyable between objects. /// </summary>
/// </summary> /// <seealso cref="Attribute" />
/// <seealso cref="Attribute" /> [AttributeUsage(AttributeTargets.Property)]
[AttributeUsage(AttributeTargets.Property)] public class CopyableAttribute : Attribute {
public class CopyableAttribute : Attribute }
{
}
} }

View File

@ -2,26 +2,30 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
namespace Swan.Mappers namespace Swan.Mappers {
{ /// <summary>
/// Interface object map.
/// </summary>
public interface IObjectMap {
/// <summary> /// <summary>
/// Interface object map. /// Gets or sets the map.
/// </summary> /// </summary>
public interface IObjectMap Dictionary<PropertyInfo, List<PropertyInfo>> Map {
{ get;
/// <summary> }
/// Gets or sets the map.
/// </summary> /// <summary>
Dictionary<PropertyInfo, List<PropertyInfo>> Map { get; } /// Gets or sets the type of the source.
/// </summary>
/// <summary> Type SourceType {
/// Gets or sets the type of the source. get;
/// </summary> }
Type SourceType { get; }
/// <summary>
/// <summary> /// Gets or sets the type of the destination.
/// Gets or sets the type of the destination. /// </summary>
/// </summary> Type DestinationType {
Type DestinationType { get; } get;
} }
}
} }

View File

@ -4,112 +4,106 @@ using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
namespace Swan.Mappers namespace Swan.Mappers {
{ /// <summary>
/// Represents an object map.
/// </summary>
/// <typeparam name="TSource">The type of the source.</typeparam>
/// <typeparam name="TDestination">The type of the destination.</typeparam>
/// <seealso cref="IObjectMap" />
public class ObjectMap<TSource, TDestination> : IObjectMap {
internal ObjectMap(IEnumerable<PropertyInfo> intersect) {
this.SourceType = typeof(TSource);
this.DestinationType = typeof(TDestination);
this.Map = intersect.ToDictionary(property => this.DestinationType.GetProperty(property.Name), property => new List<PropertyInfo> { this.SourceType.GetProperty(property.Name) });
}
/// <inheritdoc/>
public Dictionary<PropertyInfo, List<PropertyInfo>> Map {
get;
}
/// <inheritdoc/>
public Type SourceType {
get;
}
/// <inheritdoc/>
public Type DestinationType {
get;
}
/// <summary> /// <summary>
/// Represents an object map. /// Maps the property.
/// </summary> /// </summary>
/// <typeparam name="TSource">The type of the source.</typeparam> /// <typeparam name="TDestinationProperty">The type of the destination property.</typeparam>
/// <typeparam name="TDestination">The type of the destination.</typeparam> /// <typeparam name="TSourceProperty">The type of the source property.</typeparam>
/// <seealso cref="IObjectMap" /> /// <param name="destinationProperty">The destination property.</param>
public class ObjectMap<TSource, TDestination> : IObjectMap /// <param name="sourceProperty">The source property.</param>
{ /// <returns>
internal ObjectMap(IEnumerable<PropertyInfo> intersect) /// An object map representation of type of the destination property
{ /// and type of the source property.
SourceType = typeof(TSource); /// </returns>
DestinationType = typeof(TDestination); public ObjectMap<TSource, TDestination> MapProperty<TDestinationProperty, TSourceProperty>(Expression<Func<TDestination, TDestinationProperty>> destinationProperty, Expression<Func<TSource, TSourceProperty>> sourceProperty) {
Map = intersect.ToDictionary( PropertyInfo propertyDestinationInfo = (destinationProperty.Body as MemberExpression)?.Member as PropertyInfo;
property => DestinationType.GetProperty(property.Name),
property => new List<PropertyInfo> {SourceType.GetProperty(property.Name)}); if(propertyDestinationInfo == null) {
} throw new ArgumentException("Invalid destination expression", nameof(destinationProperty));
}
/// <inheritdoc/>
public Dictionary<PropertyInfo, List<PropertyInfo>> Map { get; } List<PropertyInfo> sourceMembers = GetSourceMembers(sourceProperty);
/// <inheritdoc/> if(sourceMembers.Any() == false) {
public Type SourceType { get; } throw new ArgumentException("Invalid source expression", nameof(sourceProperty));
}
/// <inheritdoc/>
public Type DestinationType { get; } // reverse order
sourceMembers.Reverse();
/// <summary> this.Map[propertyDestinationInfo] = sourceMembers;
/// Maps the property.
/// </summary> return this;
/// <typeparam name="TDestinationProperty">The type of the destination property.</typeparam> }
/// <typeparam name="TSourceProperty">The type of the source property.</typeparam>
/// <param name="destinationProperty">The destination property.</param> /// <summary>
/// <param name="sourceProperty">The source property.</param> /// Removes the map property.
/// <returns> /// </summary>
/// An object map representation of type of the destination property /// <typeparam name="TDestinationProperty">The type of the destination property.</typeparam>
/// and type of the source property. /// <param name="destinationProperty">The destination property.</param>
/// </returns> /// <returns>
public ObjectMap<TSource, TDestination> MapProperty /// An object map representation of type of the destination property
<TDestinationProperty, TSourceProperty>( /// and type of the source property.
Expression<Func<TDestination, TDestinationProperty>> destinationProperty, /// </returns>
Expression<Func<TSource, TSourceProperty>> sourceProperty) /// <exception cref="System.Exception">Invalid destination expression.</exception>
{ public ObjectMap<TSource, TDestination> RemoveMapProperty<TDestinationProperty>(Expression<Func<TDestination, TDestinationProperty>> destinationProperty) {
var propertyDestinationInfo = (destinationProperty.Body as MemberExpression)?.Member as PropertyInfo; PropertyInfo propertyDestinationInfo = (destinationProperty.Body as MemberExpression)?.Member as PropertyInfo;
if (propertyDestinationInfo == null) if(propertyDestinationInfo == null) {
{ throw new ArgumentException("Invalid destination expression", nameof(destinationProperty));
throw new ArgumentException("Invalid destination expression", nameof(destinationProperty)); }
}
if(this.Map.ContainsKey(propertyDestinationInfo)) {
var sourceMembers = GetSourceMembers(sourceProperty); _ = this.Map.Remove(propertyDestinationInfo);
}
if (sourceMembers.Any() == false)
{ return this;
throw new ArgumentException("Invalid source expression", nameof(sourceProperty)); }
}
private static List<PropertyInfo> GetSourceMembers<TSourceProperty>(Expression<Func<TSource, TSourceProperty>> sourceProperty) {
// reverse order List<PropertyInfo> sourceMembers = new List<PropertyInfo>();
sourceMembers.Reverse(); MemberExpression initialExpression = sourceProperty.Body as MemberExpression;
Map[propertyDestinationInfo] = sourceMembers;
while(true) {
return this; PropertyInfo propertySourceInfo = initialExpression?.Member as PropertyInfo;
}
if(propertySourceInfo == null) {
/// <summary> break;
/// Removes the map property. }
/// </summary>
/// <typeparam name="TDestinationProperty">The type of the destination property.</typeparam> sourceMembers.Add(propertySourceInfo);
/// <param name="destinationProperty">The destination property.</param> initialExpression = initialExpression.Expression as MemberExpression;
/// <returns> }
/// An object map representation of type of the destination property
/// and type of the source property. return sourceMembers;
/// </returns> }
/// <exception cref="System.Exception">Invalid destination expression.</exception> }
public ObjectMap<TSource, TDestination> RemoveMapProperty<TDestinationProperty>(
Expression<Func<TDestination, TDestinationProperty>> destinationProperty)
{
var propertyDestinationInfo = (destinationProperty.Body as MemberExpression)?.Member as PropertyInfo;
if (propertyDestinationInfo == null)
throw new ArgumentException("Invalid destination expression", nameof(destinationProperty));
if (Map.ContainsKey(propertyDestinationInfo))
{
Map.Remove(propertyDestinationInfo);
}
return this;
}
private static List<PropertyInfo> GetSourceMembers<TSourceProperty>(Expression<Func<TSource, TSourceProperty>> sourceProperty)
{
var sourceMembers = new List<PropertyInfo>();
var initialExpression = sourceProperty.Body as MemberExpression;
while (true)
{
var propertySourceInfo = initialExpression?.Member as PropertyInfo;
if (propertySourceInfo == null) break;
sourceMembers.Add(propertySourceInfo);
initialExpression = initialExpression.Expression as MemberExpression;
}
return sourceMembers;
}
}
} }

View File

@ -1,24 +1,20 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Reflection; using System.Reflection;
namespace Swan.Mappers namespace Swan.Mappers {
{ /// <summary>
/// <summary> /// Represents an AutoMapper-like object to map from one object type
/// Represents an AutoMapper-like object to map from one object type /// to another using defined properties map or using the default behaviour
/// to another using defined properties map or using the default behaviour /// to copy same named properties from one object to another.
/// to copy same named properties from one object to another. ///
/// /// The extension methods like CopyPropertiesTo use the default behaviour.
/// The extension methods like CopyPropertiesTo use the default behaviour. /// </summary>
/// </summary> public partial class ObjectMapper {
public partial class ObjectMapper internal class PropertyInfoComparer : IEqualityComparer<PropertyInfo> {
{ public Boolean Equals(PropertyInfo x, PropertyInfo y) => x != null && y != null && x.Name == y.Name && x.PropertyType == y.PropertyType;
internal class PropertyInfoComparer : IEqualityComparer<PropertyInfo>
{ public Int32 GetHashCode(PropertyInfo obj) => obj.Name.GetHashCode() + obj.PropertyType.Name.GetHashCode();
public bool Equals(PropertyInfo x, PropertyInfo y) }
=> x != null && y != null && x.Name == y.Name && x.PropertyType == y.PropertyType; }
public int GetHashCode(PropertyInfo obj)
=> obj.Name.GetHashCode() + obj.PropertyType.Name.GetHashCode();
}
}
} }

View File

@ -1,372 +1,309 @@
using System; #nullable enable
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Swan.Reflection; using Swan.Reflection;
namespace Swan.Mappers namespace Swan.Mappers {
{ /// <summary>
/// Represents an AutoMapper-like object to map from one object type
/// to another using defined properties map or using the default behaviour
/// to copy same named properties from one object to another.
///
/// The extension methods like CopyPropertiesTo use the default behaviour.
/// </summary>
/// <example>
/// The following code explains how to map an object's properties into an instance of type T.
/// <code>
/// using Swan.Mappers;
///
/// class Example
/// {
/// class Person
/// {
/// public string Name { get; set; }
/// public int Age { get; set; }
/// }
///
/// static void Main()
/// {
/// var obj = new { Name = "John", Age = 42 };
///
/// var person = Runtime.ObjectMapper.Map&lt;Person&gt;(obj);
/// }
/// }
/// </code>
///
/// The following code explains how to explicitly map certain properties.
/// <code>
/// using Swan.Mappers;
///
/// class Example
/// {
/// class User
/// {
/// public string Name { get; set; }
/// public Role Role { get; set; }
/// }
///
/// public class Role
/// {
/// public string Name { get; set; }
/// }
///
/// class UserDto
/// {
/// public string Name { get; set; }
/// public string Role { get; set; }
/// }
///
/// static void Main()
/// {
/// // create a User object
/// var person =
/// new User { Name = "Phillip", Role = new Role { Name = "Admin" } };
///
/// // create an Object Mapper
/// var mapper = new ObjectMapper();
///
/// // map the User's Role.Name to UserDto's Role
/// mapper.CreateMap&lt;User, UserDto&gt;()
/// .MapProperty(d => d.Role, x => x.Role.Name);
///
/// // apply the previous map and retrieve a UserDto object
/// var destination = mapper.Map&lt;UserDto&gt;(person);
/// }
/// }
/// </code>
/// </example>
public partial class ObjectMapper {
private static readonly Lazy<ObjectMapper> LazyInstance = new Lazy<ObjectMapper>(() => new ObjectMapper());
private readonly List<IObjectMap> _maps = new List<IObjectMap>();
/// <summary> /// <summary>
/// Represents an AutoMapper-like object to map from one object type /// Gets the current.
/// to another using defined properties map or using the default behaviour
/// to copy same named properties from one object to another.
///
/// The extension methods like CopyPropertiesTo use the default behaviour.
/// </summary> /// </summary>
/// <example> /// <value>
/// The following code explains how to map an object's properties into an instance of type T. /// The current.
/// <code> /// </value>
/// using Swan.Mappers; public static ObjectMapper Current => LazyInstance.Value;
///
/// class Example /// <summary>
/// { /// Copies the specified source.
/// class Person /// </summary>
/// { /// <param name="source">The source.</param>
/// public string Name { get; set; } /// <param name="target">The target.</param>
/// public int Age { get; set; } /// <param name="propertiesToCopy">The properties to copy.</param>
/// } /// <param name="ignoreProperties">The ignore properties.</param>
/// /// <returns>
/// static void Main() /// Copied properties count.
/// { /// </returns>
/// var obj = new { Name = "John", Age = 42 }; /// <exception cref="ArgumentNullException">
/// /// source
/// var person = Runtime.ObjectMapper.Map&lt;Person&gt;(obj); /// or
/// } /// target.
/// } /// </exception>
/// </code> public static Int32 Copy(Object source, Object? target, IEnumerable<String>? propertiesToCopy = null, params String[]? ignoreProperties) {
/// if(source == null) {
/// The following code explains how to explicitly map certain properties. throw new ArgumentNullException(nameof(source));
/// <code> }
/// using Swan.Mappers;
/// if(target == null) {
/// class Example throw new ArgumentNullException(nameof(target));
/// { }
/// class User
/// { return CopyInternal(target, GetSourceMap(source), propertiesToCopy, ignoreProperties);
/// public string Name { get; set; } }
/// public Role Role { get; set; }
/// } /// <summary>
/// /// Copies the specified source.
/// public class Role /// </summary>
/// { /// <param name="source">The source.</param>
/// public string Name { get; set; } /// <param name="target">The target.</param>
/// } /// <param name="propertiesToCopy">The properties to copy.</param>
/// /// <param name="ignoreProperties">The ignore properties.</param>
/// class UserDto /// <returns>
/// { /// Copied properties count.
/// public string Name { get; set; } /// </returns>
/// public string Role { get; set; } /// <exception cref="ArgumentNullException">
/// } /// source
/// /// or
/// static void Main() /// target.
/// { /// </exception>
/// // create a User object public static Int32 Copy(IDictionary<String, Object> source, Object? target, IEnumerable<String>? propertiesToCopy = null, params String[] ignoreProperties) {
/// var person = if(source == null) {
/// new User { Name = "Phillip", Role = new Role { Name = "Admin" } }; throw new ArgumentNullException(nameof(source));
/// }
/// // create an Object Mapper
/// var mapper = new ObjectMapper(); if(target == null) {
/// throw new ArgumentNullException(nameof(target));
/// // map the User's Role.Name to UserDto's Role }
/// mapper.CreateMap&lt;User, UserDto&gt;()
/// .MapProperty(d => d.Role, x => x.Role.Name); return CopyInternal(target, source.ToDictionary(x => x.Key.ToLowerInvariant(), x => Tuple.Create(typeof(Object), x.Value)), propertiesToCopy, ignoreProperties);
/// }
/// // apply the previous map and retrieve a UserDto object
/// var destination = mapper.Map&lt;UserDto&gt;(person); /// <summary>
/// } /// Creates the map.
/// } /// </summary>
/// </code> /// <typeparam name="TSource">The type of the source.</typeparam>
/// </example> /// <typeparam name="TDestination">The type of the destination.</typeparam>
public partial class ObjectMapper /// <returns>
{ /// An object map representation of type of the destination property
private static readonly Lazy<ObjectMapper> LazyInstance = new Lazy<ObjectMapper>(() => new ObjectMapper()); /// and type of the source property.
/// </returns>
private readonly List<IObjectMap> _maps = new List<IObjectMap>(); /// <exception cref="InvalidOperationException">
/// You can't create an existing map
/// <summary> /// or
/// Gets the current. /// Types doesn't match.
/// </summary> /// </exception>
/// <value> public ObjectMap<TSource, TDestination> CreateMap<TSource, TDestination>() {
/// The current. if(this._maps.Any(x => x.SourceType == typeof(TSource) && x.DestinationType == typeof(TDestination))) {
/// </value> throw new InvalidOperationException("You can't create an existing map");
public static ObjectMapper Current => LazyInstance.Value; }
/// <summary> IEnumerable<PropertyInfo> sourceType = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties<TSource>(true);
/// Copies the specified source. IEnumerable<PropertyInfo> destinationType = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties<TDestination>(true);
/// </summary>
/// <param name="source">The source.</param> PropertyInfo[] intersect = sourceType.Intersect(destinationType, new PropertyInfoComparer()).ToArray();
/// <param name="target">The target.</param>
/// <param name="propertiesToCopy">The properties to copy.</param> if(!intersect.Any()) {
/// <param name="ignoreProperties">The ignore properties.</param> throw new InvalidOperationException("Types doesn't match");
/// <returns> }
/// Copied properties count.
/// </returns> ObjectMap<TSource, TDestination> map = new ObjectMap<TSource, TDestination>(intersect);
/// <exception cref="ArgumentNullException">
/// source this._maps.Add(map);
/// or
/// target. return map;
/// </exception> }
public static int Copy(
object source, /// <summary>
object target, /// Maps the specified source.
IEnumerable<string>? propertiesToCopy = null, /// </summary>
params string[]? ignoreProperties) /// <typeparam name="TDestination">The type of the destination.</typeparam>
{ /// <param name="source">The source.</param>
if (source == null) /// <param name="autoResolve">if set to <c>true</c> [automatic resolve].</param>
throw new ArgumentNullException(nameof(source)); /// <returns>
/// A new instance of the map.
if (target == null) /// </returns>
throw new ArgumentNullException(nameof(target)); /// <exception cref="ArgumentNullException">source.</exception>
/// <exception cref="InvalidOperationException">You can't map from type {source.GetType().Name} to {typeof(TDestination).Name}.</exception>
return CopyInternal( public TDestination Map<TDestination>(Object source, Boolean autoResolve = true) {
target, if(source == null) {
GetSourceMap(source), throw new ArgumentNullException(nameof(source));
propertiesToCopy, }
ignoreProperties);
} TDestination destination = Activator.CreateInstance<TDestination>();
IObjectMap map = this._maps.FirstOrDefault(x => x.SourceType == source.GetType() && x.DestinationType == typeof(TDestination));
/// <summary>
/// Copies the specified source. if(map != null) {
/// </summary> foreach(KeyValuePair<PropertyInfo, List<PropertyInfo>> property in map.Map) {
/// <param name="source">The source.</param> Object finalSource = property.Value.Aggregate(source, (current, sourceProperty) => sourceProperty.GetValue(current)!);
/// <param name="target">The target.</param>
/// <param name="propertiesToCopy">The properties to copy.</param> property.Key.SetValue(destination, finalSource);
/// <param name="ignoreProperties">The ignore properties.</param> }
/// <returns> } else {
/// Copied properties count. if(!autoResolve) {
/// </returns> throw new InvalidOperationException($"You can't map from type {source.GetType().Name} to {typeof(TDestination).Name}");
/// <exception cref="ArgumentNullException"> }
/// source
/// or // Missing mapping, try to use default behavior
/// target. _ = Copy(source, destination);
/// </exception> }
public static int Copy(
IDictionary<string, object> source, return destination;
object target, }
IEnumerable<string>? propertiesToCopy = null,
params string[] ignoreProperties) private static Int32 CopyInternal(Object target, Dictionary<String, Tuple<Type, Object>> sourceProperties, IEnumerable<String>? propertiesToCopy, IEnumerable<String>? ignoreProperties) {
{ // Filter properties
if (source == null) IEnumerable<String>? requiredProperties = propertiesToCopy?.Where(p => !String.IsNullOrWhiteSpace(p)).Select(p => p.ToLowerInvariant());
throw new ArgumentNullException(nameof(source));
IEnumerable<String>? ignoredProperties = ignoreProperties?.Where(p => !String.IsNullOrWhiteSpace(p)).Select(p => p.ToLowerInvariant());
if (target == null)
throw new ArgumentNullException(nameof(target)); IEnumerable<PropertyInfo> properties = PropertyTypeCache.DefaultCache.Value.RetrieveFilteredProperties(target.GetType(), true, x => x.CanWrite);
return CopyInternal( return properties.Select(x => x.Name).Distinct().ToDictionary(x => x.ToLowerInvariant(), x => properties.First(y => y.Name == x)).Where(x => sourceProperties.Keys.Contains(x.Key)).When(() => requiredProperties != null, q => q.Where(y => requiredProperties.Contains(y.Key))).When(() => ignoredProperties != null, q => q.Where(y => !ignoredProperties.Contains(y.Key))).ToDictionary(x => x.Value, x => sourceProperties[x.Key]).Sum(x => TrySetValue(x.Key, x.Value, target) ? 1 : 0);
target, }
source.ToDictionary(
x => x.Key.ToLowerInvariant(), private static Boolean TrySetValue(PropertyInfo propertyInfo, Tuple<Type, Object> property, Object target) {
x => Tuple.Create(typeof(object), x.Value)), try {
propertiesToCopy, (Type type, Object value) = property;
ignoreProperties);
} if(type.IsEnum) {
propertyInfo.SetValue(target,
/// <summary> Enum.ToObject(propertyInfo.PropertyType, value));
/// Creates the map.
/// </summary> return true;
/// <typeparam name="TSource">The type of the source.</typeparam> }
/// <typeparam name="TDestination">The type of the destination.</typeparam>
/// <returns> if(type.IsValueType || propertyInfo.PropertyType != type) {
/// An object map representation of type of the destination property return propertyInfo.TrySetBasicType(value, target);
/// and type of the source property. }
/// </returns>
/// <exception cref="InvalidOperationException"> if(propertyInfo.PropertyType.IsArray) {
/// You can't create an existing map _ = propertyInfo.TrySetArray(value as IEnumerable<Object>, target);
/// or return true;
/// Types doesn't match. }
/// </exception>
public ObjectMap<TSource, TDestination> CreateMap<TSource, TDestination>() propertyInfo.SetValue(target, GetValue(value, propertyInfo.PropertyType));
{
if (_maps.Any(x => x.SourceType == typeof(TSource) && x.DestinationType == typeof(TDestination))) return true;
throw new InvalidOperationException("You can't create an existing map"); } catch {
// swallow
var sourceType = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties<TSource>(true); }
var destinationType = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties<TDestination>(true);
return false;
var intersect = sourceType.Intersect(destinationType, new PropertyInfoComparer()).ToArray(); }
if (!intersect.Any()) private static Object? GetValue(Object source, Type targetType) {
throw new InvalidOperationException("Types doesn't match"); if(source == null) {
return null;
var map = new ObjectMap<TSource, TDestination>(intersect); }
_maps.Add(map); Object? target = null;
return map; source.CreateTarget(targetType, false, ref target);
}
switch(source) {
/// <summary> case String _:
/// Maps the specified source. target = source;
/// </summary> break;
/// <typeparam name="TDestination">The type of the destination.</typeparam> case IList sourceList when target is IList targetList:
/// <param name="source">The source.</param> MethodInfo addMethod = targetType.GetMethods().FirstOrDefault(m => m.Name == Formatters.Json.AddMethodName && m.IsPublic && m.GetParameters().Length == 1);
/// <param name="autoResolve">if set to <c>true</c> [automatic resolve].</param>
/// <returns> if(addMethod == null) {
/// A new instance of the map. return target;
/// </returns> }
/// <exception cref="ArgumentNullException">source.</exception>
/// <exception cref="InvalidOperationException">You can't map from type {source.GetType().Name} to {typeof(TDestination).Name}.</exception> Boolean? isItemValueType = targetList.GetType().GetElementType()?.IsValueType;
public TDestination Map<TDestination>(object source, bool autoResolve = true)
{ foreach(Object? item in sourceList) {
if (source == null) try {
{ if(isItemValueType != null) {
throw new ArgumentNullException(nameof(source)); _ = targetList.Add((Boolean)isItemValueType ? item : item?.CopyPropertiesToNew<Object>());
} }
} catch {
var destination = Activator.CreateInstance<TDestination>(); // ignored
var map = _maps }
.FirstOrDefault(x => x.SourceType == source.GetType() && x.DestinationType == typeof(TDestination)); }
if (map != null) break;
{ default:
foreach (var property in map.Map) _ = source.CopyPropertiesTo(target);
{ break;
var finalSource = property.Value.Aggregate(source, }
(current, sourceProperty) => sourceProperty.GetValue(current));
return target;
property.Key.SetValue(destination, finalSource); }
}
} private static Dictionary<String, Tuple<Type, Object>> GetSourceMap(Object source) {
else // select distinct properties because they can be duplicated by inheritance
{ PropertyInfo[] sourceProperties = PropertyTypeCache.DefaultCache.Value.RetrieveFilteredProperties(source.GetType(), true, x => x.CanRead).ToArray();
if (!autoResolve)
{ return sourceProperties.Select(x => x.Name).Distinct().ToDictionary(x => x.ToLowerInvariant(), x => Tuple.Create(sourceProperties.First(y => y.Name == x).PropertyType, sourceProperties.First(y => y.Name == x).GetValue(source)))!;
throw new InvalidOperationException( }
$"You can't map from type {source.GetType().Name} to {typeof(TDestination).Name}"); }
}
// Missing mapping, try to use default behavior
Copy(source, destination);
}
return destination;
}
private static int CopyInternal(
object target,
Dictionary<string, Tuple<Type, object>> sourceProperties,
IEnumerable<string>? propertiesToCopy,
IEnumerable<string>? ignoreProperties)
{
// Filter properties
var requiredProperties = propertiesToCopy?
.Where(p => !string.IsNullOrWhiteSpace(p))
.Select(p => p.ToLowerInvariant());
var ignoredProperties = ignoreProperties?
.Where(p => !string.IsNullOrWhiteSpace(p))
.Select(p => p.ToLowerInvariant());
var properties = PropertyTypeCache.DefaultCache.Value
.RetrieveFilteredProperties(target.GetType(), true, x => x.CanWrite);
return properties
.Select(x => x.Name)
.Distinct()
.ToDictionary(x => x.ToLowerInvariant(), x => properties.First(y => y.Name == x))
.Where(x => sourceProperties.Keys.Contains(x.Key))
.When(() => requiredProperties != null, q => q.Where(y => requiredProperties.Contains(y.Key)))
.When(() => ignoredProperties != null, q => q.Where(y => !ignoredProperties.Contains(y.Key)))
.ToDictionary(x => x.Value, x => sourceProperties[x.Key])
.Sum(x => TrySetValue(x.Key, x.Value, target) ? 1 : 0);
}
private static bool TrySetValue(PropertyInfo propertyInfo, Tuple<Type, object> property, object target)
{
try
{
var (type, value) = property;
if (type.IsEnum)
{
propertyInfo.SetValue(target,
Enum.ToObject(propertyInfo.PropertyType, value));
return true;
}
if (type.IsValueType || propertyInfo.PropertyType != type)
return propertyInfo.TrySetBasicType(value, target);
if (propertyInfo.PropertyType.IsArray)
{
propertyInfo.TrySetArray(value as IEnumerable<object>, target);
return true;
}
propertyInfo.SetValue(target, GetValue(value, propertyInfo.PropertyType));
return true;
}
catch
{
// swallow
}
return false;
}
private static object? GetValue(object source, Type targetType)
{
if (source == null)
return null;
object? target = null;
source.CreateTarget(targetType, false, ref target);
switch (source)
{
case string _:
target = source;
break;
case IList sourceList when target is IList targetList:
var addMethod = targetType.GetMethods()
.FirstOrDefault(
m => m.Name == Formatters.Json.AddMethodName && m.IsPublic && m.GetParameters().Length == 1);
if (addMethod == null) return target;
var isItemValueType = targetList.GetType().GetElementType().IsValueType;
foreach (var item in sourceList)
{
try
{
targetList.Add(isItemValueType
? item
: item.CopyPropertiesToNew<object>());
}
catch
{
// ignored
}
}
break;
default:
source.CopyPropertiesTo(target);
break;
}
return target;
}
private static Dictionary<string, Tuple<Type, object>> GetSourceMap(object source)
{
// select distinct properties because they can be duplicated by inheritance
var sourceProperties = PropertyTypeCache.DefaultCache.Value
.RetrieveFilteredProperties(source.GetType(), true, x => x.CanRead)
.ToArray();
return sourceProperties
.Select(x => x.Name)
.Distinct()
.ToDictionary(
x => x.ToLowerInvariant(),
x => Tuple.Create(sourceProperties.First(y => y.Name == x).PropertyType,
sourceProperties.First(y => y.Name == x).GetValue(source)));
}
}
} }

View File

@ -5,189 +5,176 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using Swan.Reflection; using Swan.Reflection;
namespace Swan namespace Swan {
{ /// <summary>
/// Represents a quick object comparer using the public properties of an object
/// or the public members in a structure.
/// </summary>
public static class ObjectComparer {
/// <summary> /// <summary>
/// Represents a quick object comparer using the public properties of an object /// Compare if two variables of the same type are equal.
/// or the public members in a structure.
/// </summary> /// </summary>
public static class ObjectComparer /// <typeparam name="T">The type of objects to compare.</typeparam>
{ /// <param name="left">The left.</param>
/// <summary> /// <param name="right">The right.</param>
/// Compare if two variables of the same type are equal. /// <returns><c>true</c> if the variables are equal; otherwise, <c>false</c>.</returns>
/// </summary> public static Boolean AreEqual<T>(T left, T right) => AreEqual(left, right, typeof(T));
/// <typeparam name="T">The type of objects to compare.</typeparam>
/// <param name="left">The left.</param> /// <summary>
/// <param name="right">The right.</param> /// Compare if two variables of the same type are equal.
/// <returns><c>true</c> if the variables are equal; otherwise, <c>false</c>.</returns> /// </summary>
public static bool AreEqual<T>(T left, T right) => AreEqual(left, right, typeof(T)); /// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <summary> /// <param name="targetType">Type of the target.</param>
/// Compare if two variables of the same type are equal. /// <returns>
/// </summary> /// <c>true</c> if the variables are equal; otherwise, <c>false</c>.
/// <param name="left">The left.</param> /// </returns>
/// <param name="right">The right.</param> /// <exception cref="ArgumentNullException">targetType.</exception>
/// <param name="targetType">Type of the target.</param> public static Boolean AreEqual(Object left, Object right, Type targetType) {
/// <returns> if(targetType == null) {
/// <c>true</c> if the variables are equal; otherwise, <c>false</c>. throw new ArgumentNullException(nameof(targetType));
/// </returns> }
/// <exception cref="ArgumentNullException">targetType.</exception>
public static bool AreEqual(object left, object right, Type targetType) if(Definitions.BasicTypesInfo.Value.ContainsKey(targetType)) {
{ return Equals(left, right);
if (targetType == null) }
throw new ArgumentNullException(nameof(targetType));
return targetType.IsValueType || targetType.IsArray ? AreStructsEqual(left, right, targetType) : AreObjectsEqual(left, right, targetType);
if (Definitions.BasicTypesInfo.Value.ContainsKey(targetType)) }
return Equals(left, right);
/// <summary>
return targetType.IsValueType || targetType.IsArray /// Compare if two objects of the same type are equal.
? AreStructsEqual(left, right, targetType) /// </summary>
: AreObjectsEqual(left, right, targetType); /// <typeparam name="T">The type of objects to compare.</typeparam>
} /// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <summary> /// <returns><c>true</c> if the objects are equal; otherwise, <c>false</c>.</returns>
/// Compare if two objects of the same type are equal. public static Boolean AreObjectsEqual<T>(T left, T right) where T : class => AreObjectsEqual(left, right, typeof(T));
/// </summary>
/// <typeparam name="T">The type of objects to compare.</typeparam> /// <summary>
/// <param name="left">The left.</param> /// Compare if two objects of the same type are equal.
/// <param name="right">The right.</param> /// </summary>
/// <returns><c>true</c> if the objects are equal; otherwise, <c>false</c>.</returns> /// <param name="left">The left.</param>
public static bool AreObjectsEqual<T>(T left, T right) /// <param name="right">The right.</param>
where T : class /// <param name="targetType">Type of the target.</param>
{ /// <returns><c>true</c> if the objects are equal; otherwise, <c>false</c>.</returns>
return AreObjectsEqual(left, right, typeof(T)); /// <exception cref="ArgumentNullException">targetType.</exception>
} public static Boolean AreObjectsEqual(Object left, Object right, Type targetType) {
if(targetType == null) {
/// <summary> throw new ArgumentNullException(nameof(targetType));
/// Compare if two objects of the same type are equal. }
/// </summary>
/// <param name="left">The left.</param> PropertyInfo[] properties = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties(targetType).ToArray();
/// <param name="right">The right.</param>
/// <param name="targetType">Type of the target.</param> foreach(PropertyInfo propertyTarget in properties) {
/// <returns><c>true</c> if the objects are equal; otherwise, <c>false</c>.</returns> Func<Object, Object> targetPropertyGetMethod = propertyTarget.GetCacheGetMethod();
/// <exception cref="ArgumentNullException">targetType.</exception>
public static bool AreObjectsEqual(object left, object right, Type targetType) if(propertyTarget.PropertyType.IsArray) {
{ IEnumerable leftObj = targetPropertyGetMethod(left) as IEnumerable;
if (targetType == null) IEnumerable rightObj = targetPropertyGetMethod(right) as IEnumerable;
throw new ArgumentNullException(nameof(targetType));
if(!AreEnumerationsEquals(leftObj, rightObj)) {
var properties = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties(targetType).ToArray(); return false;
}
foreach (var propertyTarget in properties) } else {
{ if(!Equals(targetPropertyGetMethod(left), targetPropertyGetMethod(right))) {
var targetPropertyGetMethod = propertyTarget.GetCacheGetMethod(); return false;
}
if (propertyTarget.PropertyType.IsArray) }
{ }
var leftObj = targetPropertyGetMethod(left) as IEnumerable;
var rightObj = targetPropertyGetMethod(right) as IEnumerable; return true;
}
if (!AreEnumerationsEquals(leftObj, rightObj))
return false; /// <summary>
} /// Compare if two structures of the same type are equal.
else /// </summary>
{ /// <typeparam name="T">The type of structs to compare.</typeparam>
if (!Equals(targetPropertyGetMethod(left), targetPropertyGetMethod(right))) /// <param name="left">The left.</param>
return false; /// <param name="right">The right.</param>
} /// <returns><c>true</c> if the structs are equal; otherwise, <c>false</c>.</returns>
} public static Boolean AreStructsEqual<T>(T left, T right) where T : struct => AreStructsEqual(left, right, typeof(T));
return true; /// <summary>
} /// Compare if two structures of the same type are equal.
/// </summary>
/// <summary> /// <param name="left">The left.</param>
/// Compare if two structures of the same type are equal. /// <param name="right">The right.</param>
/// </summary> /// <param name="targetType">Type of the target.</param>
/// <typeparam name="T">The type of structs to compare.</typeparam> /// <returns>
/// <param name="left">The left.</param> /// <c>true</c> if the structs are equal; otherwise, <c>false</c>.
/// <param name="right">The right.</param> /// </returns>
/// <returns><c>true</c> if the structs are equal; otherwise, <c>false</c>.</returns> /// <exception cref="ArgumentNullException">targetType.</exception>
public static bool AreStructsEqual<T>(T left, T right) public static Boolean AreStructsEqual(Object left, Object right, Type targetType) {
where T : struct if(targetType == null) {
{ throw new ArgumentNullException(nameof(targetType));
return AreStructsEqual(left, right, typeof(T)); }
}
IEnumerable<MemberInfo> fields = new List<MemberInfo>(FieldTypeCache.DefaultCache.Value.RetrieveAllFields(targetType)).Union(PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties(targetType));
/// <summary>
/// Compare if two structures of the same type are equal. foreach(MemberInfo targetMember in fields) {
/// </summary> switch(targetMember) {
/// <param name="left">The left.</param> case FieldInfo field:
/// <param name="right">The right.</param> if(Equals(field.GetValue(left), field.GetValue(right)) == false) {
/// <param name="targetType">Type of the target.</param> return false;
/// <returns> }
/// <c>true</c> if the structs are equal; otherwise, <c>false</c>.
/// </returns> break;
/// <exception cref="ArgumentNullException">targetType.</exception> case PropertyInfo property:
public static bool AreStructsEqual(object left, object right, Type targetType) Func<Object, Object> targetPropertyGetMethod = property.GetCacheGetMethod();
{
if (targetType == null) if(targetPropertyGetMethod != null &&
throw new ArgumentNullException(nameof(targetType)); !Equals(targetPropertyGetMethod(left), targetPropertyGetMethod(right))) {
return false;
var fields = new List<MemberInfo>(FieldTypeCache.DefaultCache.Value.RetrieveAllFields(targetType)) }
.Union(PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties(targetType));
break;
foreach (var targetMember in fields) }
{ }
switch (targetMember)
{ return true;
case FieldInfo field: }
if (Equals(field.GetValue(left), field.GetValue(right)) == false)
return false; /// <summary>
break; /// Compare if two enumerables are equal.
case PropertyInfo property: /// </summary>
var targetPropertyGetMethod = property.GetCacheGetMethod(); /// <typeparam name="T">The type of enums to compare.</typeparam>
/// <param name="left">The left.</param>
if (targetPropertyGetMethod != null && /// <param name="right">The right.</param>
!Equals(targetPropertyGetMethod(left), targetPropertyGetMethod(right))) /// <returns>
return false; /// <c>true</c> if two specified types are equal; otherwise, <c>false</c>.
break; /// </returns>
} /// <exception cref="ArgumentNullException">
} /// left
/// or
return true; /// right.
} /// </exception>
public static Boolean AreEnumerationsEquals<T>(T left, T right) where T : IEnumerable {
/// <summary> if(Equals(left, default(T))) {
/// Compare if two enumerables are equal. throw new ArgumentNullException(nameof(left));
/// </summary> }
/// <typeparam name="T">The type of enums to compare.</typeparam>
/// <param name="left">The left.</param> if(Equals(right, default(T))) {
/// <param name="right">The right.</param> throw new ArgumentNullException(nameof(right));
/// <returns> }
/// <c>true</c> if two specified types are equal; otherwise, <c>false</c>.
/// </returns> Object[] leftEnumerable = left.Cast<Object>().ToArray();
/// <exception cref="ArgumentNullException"> Object[] rightEnumerable = right.Cast<Object>().ToArray();
/// left
/// or if(leftEnumerable.Length != rightEnumerable.Length) {
/// right. return false;
/// </exception> }
public static bool AreEnumerationsEquals<T>(T left, T right)
where T : IEnumerable for(Int32 i = 0; i < leftEnumerable.Length; i++) {
{ Object leftEl = leftEnumerable[i];
if (Equals(left, default(T))) Object rightEl = rightEnumerable[i];
throw new ArgumentNullException(nameof(left));
if(!AreEqual(leftEl, rightEl, leftEl.GetType())) {
if (Equals(right, default(T))) return false;
throw new ArgumentNullException(nameof(right)); }
}
var leftEnumerable = left.Cast<object>().ToArray();
var rightEnumerable = right.Cast<object>().ToArray(); return true;
}
if (leftEnumerable.Length != rightEnumerable.Length) }
return false;
for (var i = 0; i < leftEnumerable.Length; i++)
{
var leftEl = leftEnumerable[i];
var rightEl = rightEnumerable[i];
if (!AreEqual(leftEl, rightEl, leftEl.GetType()))
{
return false;
}
}
return true;
}
}
} }

View File

@ -1,99 +1,85 @@
using System; using System;
namespace Swan namespace Swan {
{ /// <summary>
/// A utility class to compute paging or batching offsets.
/// </summary>
public class Paginator {
/// <summary> /// <summary>
/// A utility class to compute paging or batching offsets. /// Initializes a new instance of the <see cref="Paginator" /> class.
/// </summary> /// </summary>
public class Paginator /// <param name="totalCount">The total count of items to page over.</param>
{ /// <param name="pageSize">The desired size of individual pages.</param>
/// <summary> public Paginator(Int32 totalCount, Int32 pageSize) {
/// Initializes a new instance of the <see cref="Paginator" /> class. this.TotalCount = totalCount;
/// </summary> this.PageSize = pageSize;
/// <param name="totalCount">The total count of items to page over.</param> this.PageCount = this.ComputePageCount();
/// <param name="pageSize">The desired size of individual pages.</param> }
public Paginator(int totalCount, int pageSize)
{ /// <summary>
TotalCount = totalCount; /// Gets the desired number of items per page.
PageSize = pageSize; /// </summary>
PageCount = ComputePageCount(); public Int32 PageSize {
} get;
}
/// <summary>
/// Gets the desired number of items per page. /// <summary>
/// </summary> /// Gets the total number of items to page over.
public int PageSize { get; } /// </summary>
public Int32 TotalCount {
/// <summary> get;
/// Gets the total number of items to page over. }
/// </summary>
public int TotalCount { get; } /// <summary>
/// Gets the computed number of pages.
/// <summary> /// </summary>
/// Gets the computed number of pages. public Int32 PageCount {
/// </summary> get;
public int PageCount { get; } }
/// <summary> /// <summary>
/// Gets the start item index of the given page. /// Gets the start item index of the given page.
/// </summary> /// </summary>
/// <param name="pageIndex">Zero-based index of the page.</param> /// <param name="pageIndex">Zero-based index of the page.</param>
/// <returns>The start item index.</returns> /// <returns>The start item index.</returns>
public int GetFirstItemIndex(int pageIndex) public Int32 GetFirstItemIndex(Int32 pageIndex) {
{ pageIndex = this.FixPageIndex(pageIndex);
pageIndex = FixPageIndex(pageIndex); return pageIndex * this.PageSize;
return pageIndex * PageSize; }
}
/// <summary>
/// <summary> /// Gets the end item index of the given page.
/// Gets the end item index of the given page. /// </summary>
/// </summary> /// <param name="pageIndex">Zero-based index of the page.</param>
/// <param name="pageIndex">Zero-based index of the page.</param> /// <returns>The end item index.</returns>
/// <returns>The end item index.</returns> public Int32 GetLastItemIndex(Int32 pageIndex) {
public int GetLastItemIndex(int pageIndex) Int32 startIndex = this.GetFirstItemIndex(pageIndex);
{ return Math.Min(startIndex + this.PageSize - 1, this.TotalCount - 1);
var startIndex = GetFirstItemIndex(pageIndex); }
return Math.Min(startIndex + PageSize - 1, TotalCount - 1);
} /// <summary>
/// Gets the item count of the given page index.
/// <summary> /// </summary>
/// Gets the item count of the given page index. /// <param name="pageIndex">Zero-based index of the page.</param>
/// </summary> /// <returns>The number of items that the page contains.</returns>
/// <param name="pageIndex">Zero-based index of the page.</param> public Int32 GetItemCount(Int32 pageIndex) {
/// <returns>The number of items that the page contains.</returns> pageIndex = this.FixPageIndex(pageIndex);
public int GetItemCount(int pageIndex) return (pageIndex >= this.PageCount - 1) ? this.GetLastItemIndex(pageIndex) - this.GetFirstItemIndex(pageIndex) + 1 : this.PageSize;
{ }
pageIndex = FixPageIndex(pageIndex);
return (pageIndex >= PageCount - 1) /// <summary>
? GetLastItemIndex(pageIndex) - GetFirstItemIndex(pageIndex) + 1 /// Fixes the index of the page by applying bound logic.
: PageSize; /// </summary>
} /// <param name="pageIndex">Index of the page.</param>
/// <returns>A limit-bound index.</returns>
/// <summary> private Int32 FixPageIndex(Int32 pageIndex) => pageIndex < 0 ? 0 : pageIndex >= this.PageCount ? this.PageCount - 1 : pageIndex;
/// Fixes the index of the page by applying bound logic.
/// </summary> /// <summary>
/// <param name="pageIndex">Index of the page.</param> /// Computes the number of pages for the paginator.
/// <returns>A limit-bound index.</returns> /// </summary>
private int FixPageIndex(int pageIndex) /// <returns>The page count.</returns>
{ private Int32 ComputePageCount() =>
if (pageIndex < 0) return 0; // include this if when you always want at least 1 page
this.TotalCount == 0 ? 0 : this.TotalCount % this.PageSize != 0 ? (this.TotalCount / this.PageSize) + 1 : this.TotalCount / this.PageSize;
return pageIndex >= PageCount ? PageCount - 1 : pageIndex; }
}
/// <summary>
/// Computes the number of pages for the paginator.
/// </summary>
/// <returns>The page count.</returns>
private int ComputePageCount()
{
// include this if when you always want at least 1 page
if (TotalCount == 0)
return 0;
return TotalCount % PageSize != 0
? (TotalCount / PageSize) + 1
: TotalCount / PageSize;
}
}
} }

View File

@ -1,102 +1,105 @@
using System; using System;
namespace Swan.Parsers namespace Swan.Parsers {
{ /// <summary>
/// Models an option specification.
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public sealed class ArgumentOptionAttribute
: Attribute {
/// <summary> /// <summary>
/// Models an option specification. /// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.). /// The default long name will be inferred from target property.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Property)] public ArgumentOptionAttribute()
public sealed class ArgumentOptionAttribute : this(String.Empty, String.Empty) {
: Attribute }
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class. /// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
/// The default long name will be inferred from target property. /// </summary>
/// </summary> /// <param name="longName">The long name of the option.</param>
public ArgumentOptionAttribute() public ArgumentOptionAttribute(String longName)
: this(string.Empty, string.Empty) : this(String.Empty, longName) {
{ }
}
/// <summary>
/// <summary> /// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
/// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class. /// </summary>
/// </summary> /// <param name="shortName">The short name of the option.</param>
/// <param name="longName">The long name of the option.</param> /// <param name="longName">The long name of the option or null if not used.</param>
public ArgumentOptionAttribute(string longName) public ArgumentOptionAttribute(Char shortName, String longName)
: this(string.Empty, longName) : this(new String(shortName, 1), longName) {
{ }
}
/// <summary>
/// <summary> /// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
/// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class. /// </summary>
/// </summary> /// <param name="shortName">The short name of the option..</param>
/// <param name="shortName">The short name of the option.</param> public ArgumentOptionAttribute(Char shortName)
/// <param name="longName">The long name of the option or null if not used.</param> : this(new String(shortName, 1), String.Empty) {
public ArgumentOptionAttribute(char shortName, string longName) }
: this(new string(shortName, 1), longName)
{ private ArgumentOptionAttribute(String shortName, String longName) {
} this.ShortName = shortName ?? throw new ArgumentNullException(nameof(shortName));
this.LongName = longName ?? throw new ArgumentNullException(nameof(longName));
/// <summary> }
/// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
/// </summary> /// <summary>
/// <param name="shortName">The short name of the option..</param> /// Gets long name of this command line option. This name is usually a single English word.
public ArgumentOptionAttribute(char shortName) /// </summary>
: this(new string(shortName, 1), string.Empty) /// <value>
{ /// The long name.
} /// </value>
public String LongName {
private ArgumentOptionAttribute(string shortName, string longName) get;
{ }
ShortName = shortName ?? throw new ArgumentNullException(nameof(shortName));
LongName = longName ?? throw new ArgumentNullException(nameof(longName)); /// <summary>
} /// Gets a short name of this command line option, made of one character.
/// </summary>
/// <summary> /// <value>
/// Gets long name of this command line option. This name is usually a single English word. /// The short name.
/// </summary> /// </value>
/// <value> public String ShortName {
/// The long name. get;
/// </value> }
public string LongName { get; }
/// <summary>
/// <summary> /// When applying attribute to <see cref="System.Collections.Generic.IEnumerable{T}"/> target properties,
/// Gets a short name of this command line option, made of one character. /// it allows you to split an argument and consume its content as a sequence.
/// </summary> /// </summary>
/// <value> public Char Separator { get; set; } = '\0';
/// The short name.
/// </value> /// <summary>
public string ShortName { get; } /// Gets or sets mapped property default value.
/// </summary>
/// <summary> /// <value>
/// When applying attribute to <see cref="System.Collections.Generic.IEnumerable{T}"/> target properties, /// The default value.
/// it allows you to split an argument and consume its content as a sequence. /// </value>
/// </summary> public Object DefaultValue {
public char Separator { get; set; } = '\0'; get; set;
}
/// <summary>
/// Gets or sets mapped property default value. /// <summary>
/// </summary> /// Gets or sets a value indicating whether a command line option is required.
/// <value> /// </summary>
/// The default value. /// <value>
/// </value> /// <c>true</c> if required; otherwise, <c>false</c>.
public object DefaultValue { get; set; } /// </value>
public Boolean Required {
/// <summary> get; set;
/// Gets or sets a value indicating whether a command line option is required. }
/// </summary>
/// <value> /// <summary>
/// <c>true</c> if required; otherwise, <c>false</c>. /// Gets or sets a short description of this command line option. Usually a sentence summary.
/// </value> /// </summary>
public bool Required { get; set; } /// <value>
/// The help text.
/// <summary> /// </value>
/// Gets or sets a short description of this command line option. Usually a sentence summary. public String HelpText {
/// </summary> get; set;
/// <value> }
/// The help text. }
/// </value>
public string HelpText { get; set; }
}
} }

View File

@ -4,157 +4,126 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using Swan.Reflection; using Swan.Reflection;
namespace Swan.Parsers namespace Swan.Parsers {
{ /// <summary>
/// <summary> /// Provides methods to parse command line arguments.
/// Provides methods to parse command line arguments. ///
/// /// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors).
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors). /// </summary>
/// </summary> public partial class ArgumentParser {
public partial class ArgumentParser private sealed class Validator {
{ private readonly Object _instance;
private sealed class Validator private readonly IEnumerable<String> _args;
{ private readonly List<PropertyInfo> _updatedList = new List<PropertyInfo>();
private readonly object _instance; private readonly ArgumentParserSettings _settings;
private readonly IEnumerable<string> _args;
private readonly List<PropertyInfo> _updatedList = new List<PropertyInfo>(); private readonly PropertyInfo[] _properties;
private readonly ArgumentParserSettings _settings;
public Validator(PropertyInfo[] properties, IEnumerable<String> args, Object instance, ArgumentParserSettings settings) {
private readonly PropertyInfo[] _properties; this._args = args;
this._instance = instance;
public Validator( this._settings = settings;
PropertyInfo[] properties, this._properties = properties;
IEnumerable<string> args,
object instance, this.PopulateInstance();
ArgumentParserSettings settings) this.SetDefaultValues();
{ this.GetRequiredList();
_args = args; }
_instance = instance;
_settings = settings; public List<String> UnknownList { get; } = new List<String>();
_properties = properties; public List<String> RequiredList { get; } = new List<String>();
PopulateInstance(); public Boolean IsValid() => (this._settings.IgnoreUnknownArguments || !this.UnknownList.Any()) && !this.RequiredList.Any();
SetDefaultValues();
GetRequiredList(); public IEnumerable<ArgumentOptionAttribute> GetPropertiesOptions() => this._properties.Select(p => AttributeCache.DefaultCache.Value.RetrieveOne<ArgumentOptionAttribute>(p)).Where(x => x != null);
}
private void GetRequiredList() {
public List<string> UnknownList { get; } = new List<string>(); foreach(PropertyInfo targetProperty in this._properties) {
public List<string> RequiredList { get; } = new List<string>(); ArgumentOptionAttribute optionAttr = AttributeCache.DefaultCache.Value.RetrieveOne<ArgumentOptionAttribute>(targetProperty);
public bool IsValid() => (_settings.IgnoreUnknownArguments || !UnknownList.Any()) && !RequiredList.Any(); if(optionAttr == null || optionAttr.Required == false) {
continue;
public IEnumerable<ArgumentOptionAttribute> GetPropertiesOptions() }
=> _properties.Select(p => AttributeCache.DefaultCache.Value.RetrieveOne<ArgumentOptionAttribute>(p))
.Where(x => x != null); if(targetProperty.GetValue(this._instance) == null) {
this.RequiredList.Add(optionAttr.LongName ?? optionAttr.ShortName);
private void GetRequiredList() }
{ }
foreach (var targetProperty in _properties) }
{
var optionAttr = AttributeCache.DefaultCache.Value.RetrieveOne<ArgumentOptionAttribute>(targetProperty); private void SetDefaultValues() {
foreach(PropertyInfo targetProperty in this._properties.Except(this._updatedList)) {
if (optionAttr == null || optionAttr.Required == false) ArgumentOptionAttribute optionAttr = AttributeCache.DefaultCache.Value.RetrieveOne<ArgumentOptionAttribute>(targetProperty);
continue;
Object defaultValue = optionAttr?.DefaultValue;
if (targetProperty.GetValue(_instance) == null)
{ if(defaultValue == null) {
RequiredList.Add(optionAttr.LongName ?? optionAttr.ShortName); continue;
} }
}
} if(this.SetPropertyValue(targetProperty, defaultValue.ToString(), this._instance, optionAttr)) {
this._updatedList.Add(targetProperty);
private void SetDefaultValues() }
{ }
foreach (var targetProperty in _properties.Except(_updatedList)) }
{
var optionAttr = AttributeCache.DefaultCache.Value.RetrieveOne<ArgumentOptionAttribute>(targetProperty); private void PopulateInstance() {
const Char dash = '-';
var defaultValue = optionAttr?.DefaultValue; String propertyName = String.Empty;
if (defaultValue == null) foreach(String arg in this._args) {
continue; Boolean ignoreSetValue = String.IsNullOrWhiteSpace(propertyName);
if (SetPropertyValue(targetProperty, defaultValue.ToString(), _instance, optionAttr)) if(ignoreSetValue) {
_updatedList.Add(targetProperty); if(String.IsNullOrWhiteSpace(arg) || arg[0] != dash) {
} continue;
} }
private void PopulateInstance() propertyName = arg.Substring(1);
{
const char dash = '-'; if(!String.IsNullOrWhiteSpace(propertyName) && propertyName[0] == dash) {
var propertyName = string.Empty; propertyName = propertyName.Substring(1);
}
foreach (var arg in _args) }
{
var ignoreSetValue = string.IsNullOrWhiteSpace(propertyName); PropertyInfo targetProperty = this.TryGetProperty(propertyName);
if (ignoreSetValue) if(targetProperty == null) {
{ // Skip if the property is not found
if (string.IsNullOrWhiteSpace(arg) || arg[0] != dash) continue; this.UnknownList.Add(propertyName);
continue;
propertyName = arg.Substring(1); }
if (!string.IsNullOrWhiteSpace(propertyName) && propertyName[0] == dash) if(!ignoreSetValue && this.SetPropertyValue(targetProperty, arg, this._instance)) {
propertyName = propertyName.Substring(1); this._updatedList.Add(targetProperty);
} propertyName = String.Empty;
} else if(targetProperty.PropertyType == typeof(Boolean)) {
var targetProperty = TryGetProperty(propertyName); // If the arg is a boolean property set it to true.
targetProperty.SetValue(this._instance, true);
if (targetProperty == null)
{ this._updatedList.Add(targetProperty);
// Skip if the property is not found propertyName = String.Empty;
UnknownList.Add(propertyName); }
continue; }
}
if(!String.IsNullOrEmpty(propertyName)) {
if (!ignoreSetValue && SetPropertyValue(targetProperty, arg, _instance)) this.UnknownList.Add(propertyName);
{ }
_updatedList.Add(targetProperty); }
propertyName = string.Empty;
} private Boolean SetPropertyValue(PropertyInfo targetProperty, String propertyValueString, Object result, ArgumentOptionAttribute optionAttr = null) {
else if (targetProperty.PropertyType == typeof(bool)) if(!targetProperty.PropertyType.IsEnum) {
{ return targetProperty.PropertyType.IsArray ? targetProperty.TrySetArray(propertyValueString.Split(optionAttr?.Separator ?? ','), result) : targetProperty.TrySetBasicType(propertyValueString, result);
// If the arg is a boolean property set it to true. }
targetProperty.SetValue(_instance, true);
Object parsedValue = Enum.Parse(targetProperty.PropertyType, propertyValueString, this._settings.CaseInsensitiveEnumValues);
_updatedList.Add(targetProperty);
propertyName = string.Empty; targetProperty.SetValue(result, Enum.ToObject(targetProperty.PropertyType, parsedValue));
}
} return true;
}
if (!string.IsNullOrEmpty(propertyName))
{ private PropertyInfo TryGetProperty(String propertyName) => this._properties.FirstOrDefault(p => String.Equals(AttributeCache.DefaultCache.Value.RetrieveOne<ArgumentOptionAttribute>(p)?.LongName, propertyName, this._settings.NameComparer) || String.Equals(AttributeCache.DefaultCache.Value.RetrieveOne<ArgumentOptionAttribute>(p)?.ShortName, propertyName, this._settings.NameComparer));
UnknownList.Add(propertyName); }
} }
}
private bool SetPropertyValue(
PropertyInfo targetProperty,
string propertyValueString,
object result,
ArgumentOptionAttribute optionAttr = null)
{
if (!targetProperty.PropertyType.IsEnum)
{
return targetProperty.PropertyType.IsArray
? targetProperty.TrySetArray(propertyValueString.Split(optionAttr?.Separator ?? ','), result)
: targetProperty.TrySetBasicType(propertyValueString, result);
}
var parsedValue = Enum.Parse(
targetProperty.PropertyType,
propertyValueString,
_settings.CaseInsensitiveEnumValues);
targetProperty.SetValue(result, Enum.ToObject(targetProperty.PropertyType, parsedValue));
return true;
}
private PropertyInfo TryGetProperty(string propertyName)
=> _properties.FirstOrDefault(p =>
string.Equals(AttributeCache.DefaultCache.Value.RetrieveOne<ArgumentOptionAttribute>(p)?.LongName, propertyName, _settings.NameComparer) ||
string.Equals(AttributeCache.DefaultCache.Value.RetrieveOne<ArgumentOptionAttribute>(p)?.ShortName, propertyName, _settings.NameComparer));
}
}
} }

View File

@ -1,57 +1,49 @@
using System; #nullable enable
using System;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Swan.Reflection; using Swan.Reflection;
namespace Swan.Parsers namespace Swan.Parsers {
{ /// <summary>
/// <summary> /// Provides methods to parse command line arguments.
/// Provides methods to parse command line arguments. /// </summary>
/// </summary> public partial class ArgumentParser {
public partial class ArgumentParser private sealed class TypeResolver<T> {
{ private readonly String _selectedVerb;
private sealed class TypeResolver<T>
{ private PropertyInfo[]? _properties;
private readonly string _selectedVerb;
public TypeResolver(String selectedVerb) => this._selectedVerb = selectedVerb;
private PropertyInfo[]? _properties;
public PropertyInfo[]? Properties => this._properties?.Any() == true ? this._properties : null;
public TypeResolver(string selectedVerb)
{ public Object? GetOptionsObject(T instance) {
_selectedVerb = selectedVerb; this._properties = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties<T>(true).ToArray();
}
if(!this._properties.Any(x => x.GetCustomAttributes(typeof(VerbOptionAttribute), false).Any())) {
public PropertyInfo[]? Properties => _properties?.Any() == true ? _properties : null; return instance;
}
public object? GetOptionsObject(T instance)
{ PropertyInfo? selectedVerb = String.IsNullOrWhiteSpace(this._selectedVerb) ? null : this._properties.FirstOrDefault(x => AttributeCache.DefaultCache.Value.RetrieveOne<VerbOptionAttribute>(x).Name == this._selectedVerb);
_properties = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties<T>(true).ToArray();
if(selectedVerb == null) {
if (!_properties.Any(x => x.GetCustomAttributes(typeof(VerbOptionAttribute), false).Any())) return null;
return instance; }
var selectedVerb = string.IsNullOrWhiteSpace(_selectedVerb) Type? type = instance?.GetType();
? null
: _properties.FirstOrDefault(x => PropertyInfo? verbProperty = type?.GetProperty(selectedVerb.Name);
AttributeCache.DefaultCache.Value.RetrieveOne<VerbOptionAttribute>(x).Name == _selectedVerb);
if(verbProperty?.GetValue(instance) == null) {
if (selectedVerb == null) return null; Object? propertyInstance = Activator.CreateInstance(selectedVerb.PropertyType);
verbProperty?.SetValue(instance, propertyInstance);
var type = instance.GetType(); }
var verbProperty = type.GetProperty(selectedVerb.Name); this._properties = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties(selectedVerb.PropertyType, true).ToArray();
if (verbProperty?.GetValue(instance) == null) return verbProperty?.GetValue(instance);
{ }
var propertyInstance = Activator.CreateInstance(selectedVerb.PropertyType); }
verbProperty?.SetValue(instance, propertyInstance); }
}
_properties = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties(selectedVerb.PropertyType, true)
.ToArray();
return verbProperty?.GetValue(instance);
}
}
}
} }

View File

@ -3,251 +3,240 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Swan.Parsers namespace Swan.Parsers {
{ /// <summary>
/// Provides methods to parse command line arguments.
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
/// </summary>
/// <example>
/// The following example shows how to parse CLI arguments into objects.
/// <code>
/// class Example
/// {
/// using System;
/// using Swan.Parsers;
///
/// static void Main(string[] args)
/// {
/// // parse the supplied command-line arguments into the options object
/// var res = Runtime.ArgumentParser.ParseArguments(args, out var options);
/// }
///
/// class Options
/// {
/// [ArgumentOption('v', "verbose", HelpText = "Set verbose mode.")]
/// public bool Verbose { get; set; }
///
/// [ArgumentOption('u', Required = true, HelpText = "Set user name.")]
/// public string Username { get; set; }
///
/// [ArgumentOption('n', "names", Separator = ',',
/// Required = true, HelpText = "A list of files separated by a comma")]
/// public string[] Files { get; set; }
///
/// [ArgumentOption('p', "port", DefaultValue = 22, HelpText = "Set port.")]
/// public int Port { get; set; }
///
/// [ArgumentOption("color", DefaultValue = ConsoleColor.Red,
/// HelpText = "Set a color.")]
/// public ConsoleColor Color { get; set; }
/// }
/// }
/// </code>
/// The following code describes how to parse CLI verbs.
/// <code>
/// class Example2
/// {
/// using Swan;
/// using Swan.Parsers;
///
/// static void Main(string[] args)
/// {
/// // create an instance of the VerbOptions class
/// var options = new VerbOptions();
///
/// // parse the supplied command-line arguments into the options object
/// var res = Runtime.ArgumentParser.ParseArguments(args, options);
///
/// // if there were no errors parsing
/// if (res)
/// {
/// if(options.Run != null)
/// {
/// // run verb was selected
/// }
///
/// if(options.Print != null)
/// {
/// // print verb was selected
/// }
/// }
///
/// // flush all error messages
/// Terminal.Flush();
/// }
///
/// class VerbOptions
/// {
/// [VerbOption("run", HelpText = "Run verb.")]
/// public RunVerbOption Run { get; set; }
///
/// [VerbOption("print", HelpText = "Print verb.")]
/// public PrintVerbOption Print { get; set; }
/// }
///
/// class RunVerbOption
/// {
/// [ArgumentOption('o', "outdir", HelpText = "Output directory",
/// DefaultValue = "", Required = false)]
/// public string OutDir { get; set; }
/// }
///
/// class PrintVerbOption
/// {
/// [ArgumentOption('t', "text", HelpText = "Text to print",
/// DefaultValue = "", Required = false)]
/// public string Text { get; set; }
/// }
/// }
/// </code>
/// </example>
public partial class ArgumentParser {
/// <summary> /// <summary>
/// Provides methods to parse command line arguments. /// Initializes a new instance of the <see cref="ArgumentParser"/> class.
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
/// </summary> /// </summary>
/// <example> public ArgumentParser() : this(new ArgumentParserSettings()) {
/// The following example shows how to parse CLI arguments into objects. }
/// <code>
/// class Example /// <summary>
/// { /// Initializes a new instance of the <see cref="ArgumentParser" /> class,
/// using System; /// configurable with <see cref="ArgumentParserSettings" /> using a delegate.
/// using Swan.Parsers; /// </summary>
/// /// <param name="parseSettings">The parse settings.</param>
/// static void Main(string[] args) public ArgumentParser(ArgumentParserSettings parseSettings) => this.Settings = parseSettings ?? throw new ArgumentNullException(nameof(parseSettings));
/// {
/// // parse the supplied command-line arguments into the options object /// <summary>
/// var res = Runtime.ArgumentParser.ParseArguments(args, out var options); /// Gets the current.
/// } /// </summary>
/// /// <value>
/// class Options /// The current.
/// { /// </value>
/// [ArgumentOption('v', "verbose", HelpText = "Set verbose mode.")] public static ArgumentParser Current { get; } = new ArgumentParser();
/// public bool Verbose { get; set; }
/// /// <summary>
/// [ArgumentOption('u', Required = true, HelpText = "Set user name.")] /// Gets the instance that implements <see cref="ArgumentParserSettings" /> in use.
/// public string Username { get; set; } /// </summary>
/// /// <value>
/// [ArgumentOption('n', "names", Separator = ',', /// The settings.
/// Required = true, HelpText = "A list of files separated by a comma")] /// </value>
/// public string[] Files { get; set; } public ArgumentParserSettings Settings {
/// get;
/// [ArgumentOption('p', "port", DefaultValue = 22, HelpText = "Set port.")] }
/// public int Port { get; set; }
/// /// <summary>
/// [ArgumentOption("color", DefaultValue = ConsoleColor.Red, /// Parses a string array of command line arguments constructing values in an instance of type <typeparamref name="T" />.
/// HelpText = "Set a color.")] /// </summary>
/// public ConsoleColor Color { get; set; } /// <typeparam name="T">The type of the options.</typeparam>
/// } /// <param name="args">The arguments.</param>
/// } /// <param name="instance">The instance.</param>
/// </code> /// <returns>
/// The following code describes how to parse CLI verbs. /// <c>true</c> if was converted successfully; otherwise, <c>false</c>.
/// <code> /// </returns>
/// class Example2 /// <exception cref="ArgumentNullException">
/// { /// The exception that is thrown when a null reference (Nothing in Visual Basic)
/// using Swan; /// is passed to a method that does not accept it as a valid argument.
/// using Swan.Parsers; /// </exception>
/// /// <exception cref="InvalidOperationException">
/// static void Main(string[] args) /// The exception that is thrown when a method call is invalid for the object's current state.
/// { /// </exception>
/// // create an instance of the VerbOptions class public Boolean ParseArguments<T>(IEnumerable<String> args, out T instance) {
/// var options = new VerbOptions(); instance = Activator.CreateInstance<T>();
/// return this.ParseArguments(args, instance);
/// // parse the supplied command-line arguments into the options object }
/// var res = Runtime.ArgumentParser.ParseArguments(args, options);
/// /// <summary>
/// // if there were no errors parsing /// Parses a string array of command line arguments constructing values in an instance of type <typeparamref name="T" />.
/// if (res) /// </summary>
/// { /// <typeparam name="T">The type of the options.</typeparam>
/// if(options.Run != null) /// <param name="args">The arguments.</param>
/// { /// <param name="instance">The instance.</param>
/// // run verb was selected /// <returns>
/// } /// <c>true</c> if was converted successfully; otherwise, <c>false</c>.
/// /// </returns>
/// if(options.Print != null) /// <exception cref="ArgumentNullException">
/// { /// The exception that is thrown when a null reference (Nothing in Visual Basic)
/// // print verb was selected /// is passed to a method that does not accept it as a valid argument.
/// } /// </exception>
/// } /// <exception cref="InvalidOperationException">
/// /// The exception that is thrown when a method call is invalid for the object's current state.
/// // flush all error messages /// </exception>
/// Terminal.Flush(); public Boolean ParseArguments<T>(IEnumerable<String> args, T instance) {
/// } if(args == null) {
/// throw new ArgumentNullException(nameof(args));
/// class VerbOptions }
/// {
/// [VerbOption("run", HelpText = "Run verb.")] if(Equals(instance, default(T))) {
/// public RunVerbOption Run { get; set; } throw new ArgumentNullException(nameof(instance));
/// }
/// [VerbOption("print", HelpText = "Print verb.")]
/// public PrintVerbOption Print { get; set; } TypeResolver<T> typeResolver = new TypeResolver<T>(args.FirstOrDefault());
/// } Object options = typeResolver.GetOptionsObject(instance);
///
/// class RunVerbOption if(options == null) {
/// { ReportUnknownVerb<T>();
/// [ArgumentOption('o', "outdir", HelpText = "Output directory", return false;
/// DefaultValue = "", Required = false)] }
/// public string OutDir { get; set; }
/// } if(typeResolver.Properties == null) {
/// throw new InvalidOperationException($"Type {typeof(T).Name} is not valid");
/// class PrintVerbOption }
/// {
/// [ArgumentOption('t', "text", HelpText = "Text to print", Validator validator = new Validator(typeResolver.Properties, args, options, this.Settings);
/// DefaultValue = "", Required = false)]
/// public string Text { get; set; } if(validator.IsValid()) {
/// } return true;
/// } }
/// </code>
/// </example> this.ReportIssues(validator);
public partial class ArgumentParser return false;
{ }
/// <summary>
/// Initializes a new instance of the <see cref="ArgumentParser"/> class. private static void ReportUnknownVerb<T>() {
/// </summary> Terminal.WriteLine("No verb was specified", ConsoleColor.Red);
public ArgumentParser() Terminal.WriteLine("Valid verbs:", ConsoleColor.Cyan);
: this(new ArgumentParserSettings())
{ PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties<T>(true).Select(x => AttributeCache.DefaultCache.Value.RetrieveOne<VerbOptionAttribute>(x)).Where(x => x != null).ToList().ForEach(x => Terminal.WriteLine(x.ToString(), ConsoleColor.Cyan));
} }
/// <summary> private void ReportIssues(Validator validator) {
/// Initializes a new instance of the <see cref="ArgumentParser" /> class, if(this.Settings.WriteBanner) {
/// configurable with <see cref="ArgumentParserSettings" /> using a delegate. Terminal.WriteWelcomeBanner();
/// </summary> }
/// <param name="parseSettings">The parse settings.</param>
public ArgumentParser(ArgumentParserSettings parseSettings) IEnumerable<ArgumentOptionAttribute> options = validator.GetPropertiesOptions();
{
Settings = parseSettings ?? throw new ArgumentNullException(nameof(parseSettings)); foreach(ArgumentOptionAttribute option in options) {
} Terminal.WriteLine(String.Empty);
/// <summary> // TODO: If Enum list values
/// Gets the current. String shortName = String.IsNullOrWhiteSpace(option.ShortName) ? String.Empty : $"-{option.ShortName}";
/// </summary> String longName = String.IsNullOrWhiteSpace(option.LongName) ? String.Empty : $"--{option.LongName}";
/// <value> String comma = String.IsNullOrWhiteSpace(shortName) || String.IsNullOrWhiteSpace(longName) ? String.Empty : ", ";
/// The current. String defaultValue = option.DefaultValue == null ? String.Empty : $"(Default: {option.DefaultValue}) ";
/// </value>
public static ArgumentParser Current { get; } = new ArgumentParser(); Terminal.WriteLine($" {shortName}{comma}{longName}\t\t{defaultValue}{option.HelpText}", ConsoleColor.Cyan);
}
/// <summary>
/// Gets the instance that implements <see cref="ArgumentParserSettings" /> in use. Terminal.WriteLine(String.Empty);
/// </summary> Terminal.WriteLine(" --help\t\tDisplay this help screen.", ConsoleColor.Cyan);
/// <value>
/// The settings. if(validator.UnknownList.Any()) {
/// </value> Terminal.WriteLine($"Unknown arguments: {String.Join(", ", validator.UnknownList)}", ConsoleColor.Red);
public ArgumentParserSettings Settings { get; } }
/// <summary> if(validator.RequiredList.Any()) {
/// Parses a string array of command line arguments constructing values in an instance of type <typeparamref name="T" />. Terminal.WriteLine($"Required arguments: {String.Join(", ", validator.RequiredList)}", ConsoleColor.Red);
/// </summary> }
/// <typeparam name="T">The type of the options.</typeparam> }
/// <param name="args">The arguments.</param> }
/// <param name="instance">The instance.</param>
/// <returns>
/// <c>true</c> if was converted successfully; otherwise, <c>false</c>.
/// </returns>
/// <exception cref="ArgumentNullException">
/// The exception that is thrown when a null reference (Nothing in Visual Basic)
/// is passed to a method that does not accept it as a valid argument.
/// </exception>
/// <exception cref="InvalidOperationException">
/// The exception that is thrown when a method call is invalid for the object's current state.
/// </exception>
public bool ParseArguments<T>(IEnumerable<string> args, out T instance)
{
instance = Activator.CreateInstance<T>();
return ParseArguments(args, instance);
}
/// <summary>
/// Parses a string array of command line arguments constructing values in an instance of type <typeparamref name="T" />.
/// </summary>
/// <typeparam name="T">The type of the options.</typeparam>
/// <param name="args">The arguments.</param>
/// <param name="instance">The instance.</param>
/// <returns>
/// <c>true</c> if was converted successfully; otherwise, <c>false</c>.
/// </returns>
/// <exception cref="ArgumentNullException">
/// The exception that is thrown when a null reference (Nothing in Visual Basic)
/// is passed to a method that does not accept it as a valid argument.
/// </exception>
/// <exception cref="InvalidOperationException">
/// The exception that is thrown when a method call is invalid for the object's current state.
/// </exception>
public bool ParseArguments<T>(IEnumerable<string> args, T instance)
{
if (args == null)
throw new ArgumentNullException(nameof(args));
if (Equals(instance, default(T)))
throw new ArgumentNullException(nameof(instance));
var typeResolver = new TypeResolver<T>(args.FirstOrDefault());
var options = typeResolver.GetOptionsObject(instance);
if (options == null)
{
ReportUnknownVerb<T>();
return false;
}
if (typeResolver.Properties == null)
throw new InvalidOperationException($"Type {typeof(T).Name} is not valid");
var validator = new Validator(typeResolver.Properties, args, options, Settings);
if (validator.IsValid())
return true;
ReportIssues(validator);
return false;
}
private static void ReportUnknownVerb<T>()
{
Terminal.WriteLine("No verb was specified", ConsoleColor.Red);
Terminal.WriteLine("Valid verbs:", ConsoleColor.Cyan);
PropertyTypeCache.DefaultCache.Value
.RetrieveAllProperties<T>(true)
.Select(x => AttributeCache.DefaultCache.Value.RetrieveOne<VerbOptionAttribute>(x))
.Where(x => x != null)
.ToList()
.ForEach(x => Terminal.WriteLine(x.ToString(), ConsoleColor.Cyan));
}
private void ReportIssues(Validator validator)
{
if (Settings.WriteBanner)
Terminal.WriteWelcomeBanner();
var options = validator.GetPropertiesOptions();
foreach (var option in options)
{
Terminal.WriteLine(string.Empty);
// TODO: If Enum list values
var shortName = string.IsNullOrWhiteSpace(option.ShortName) ? string.Empty : $"-{option.ShortName}";
var longName = string.IsNullOrWhiteSpace(option.LongName) ? string.Empty : $"--{option.LongName}";
var comma = string.IsNullOrWhiteSpace(shortName) || string.IsNullOrWhiteSpace(longName)
? string.Empty
: ", ";
var defaultValue = option.DefaultValue == null ? string.Empty : $"(Default: {option.DefaultValue}) ";
Terminal.WriteLine($" {shortName}{comma}{longName}\t\t{defaultValue}{option.HelpText}", ConsoleColor.Cyan);
}
Terminal.WriteLine(string.Empty);
Terminal.WriteLine(" --help\t\tDisplay this help screen.", ConsoleColor.Cyan);
if (validator.UnknownList.Any())
Terminal.WriteLine($"Unknown arguments: {string.Join(", ", validator.UnknownList)}", ConsoleColor.Red);
if (validator.RequiredList.Any())
Terminal.WriteLine($"Required arguments: {string.Join(", ", validator.RequiredList)}", ConsoleColor.Red);
}
}
} }

View File

@ -1,53 +1,51 @@
using System; using System;
namespace Swan.Parsers namespace Swan.Parsers {
{ /// <summary>
/// Provides settings for <see cref="ArgumentParser"/>.
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
/// </summary>
public class ArgumentParserSettings {
/// <summary> /// <summary>
/// Provides settings for <see cref="ArgumentParser"/>. /// Gets or sets a value indicating whether [write banner].
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
/// </summary> /// </summary>
public class ArgumentParserSettings /// <value>
{ /// <c>true</c> if [write banner]; otherwise, <c>false</c>.
/// <summary> /// </value>
/// Gets or sets a value indicating whether [write banner]. public Boolean WriteBanner { get; set; } = true;
/// </summary>
/// <value> /// <summary>
/// <c>true</c> if [write banner]; otherwise, <c>false</c>. /// Gets or sets a value indicating whether perform case sensitive comparisons.
/// </value> /// Note that case insensitivity only applies to <i>parameters</i>, not the values
public bool WriteBanner { get; set; } = true; /// assigned to them (for example, enum parsing).
/// </summary>
/// <summary> /// <value>
/// Gets or sets a value indicating whether perform case sensitive comparisons. /// <c>true</c> if [case sensitive]; otherwise, <c>false</c>.
/// Note that case insensitivity only applies to <i>parameters</i>, not the values /// </value>
/// assigned to them (for example, enum parsing). public Boolean CaseSensitive { get; set; } = false;
/// </summary>
/// <value> /// <summary>
/// <c>true</c> if [case sensitive]; otherwise, <c>false</c>. /// Gets or sets a value indicating whether perform case sensitive comparisons of <i>values</i>.
/// </value> /// Note that case insensitivity only applies to <i>values</i>, not the parameters.
public bool CaseSensitive { get; set; } = false; /// </summary>
/// <value>
/// <summary> /// <c>true</c> if [case insensitive enum values]; otherwise, <c>false</c>.
/// Gets or sets a value indicating whether perform case sensitive comparisons of <i>values</i>. /// </value>
/// Note that case insensitivity only applies to <i>values</i>, not the parameters. public Boolean CaseInsensitiveEnumValues { get; set; } = true;
/// </summary>
/// <value> /// <summary>
/// <c>true</c> if [case insensitive enum values]; otherwise, <c>false</c>. /// Gets or sets a value indicating whether the parser shall move on to the next argument and ignore the given argument if it
/// </value> /// encounter an unknown arguments.
public bool CaseInsensitiveEnumValues { get; set; } = true; /// </summary>
/// <value>
/// <summary> /// <c>true</c> to allow parsing the arguments with different class options that do not have all the arguments.
/// Gets or sets a value indicating whether the parser shall move on to the next argument and ignore the given argument if it /// </value>
/// encounter an unknown arguments. /// <remarks>
/// </summary> /// This allows fragmented version class parsing, useful for project with add-on where add-ons also requires command line arguments but
/// <value> /// when these are unknown by the main program at build time.
/// <c>true</c> to allow parsing the arguments with different class options that do not have all the arguments. /// </remarks>
/// </value> public Boolean IgnoreUnknownArguments { get; set; } = true;
/// <remarks>
/// This allows fragmented version class parsing, useful for project with add-on where add-ons also requires command line arguments but internal StringComparison NameComparer => this.CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
/// when these are unknown by the main program at build time. }
/// </remarks>
public bool IgnoreUnknownArguments { get; set; } = true;
internal StringComparison NameComparer => CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
}
} }

View File

@ -3,115 +3,107 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
namespace Swan.Parsers namespace Swan.Parsers {
{ /// <summary>
/// Represents a generic expression parser.
/// </summary>
public abstract class ExpressionParser {
/// <summary> /// <summary>
/// Represents a generic expression parser. /// Resolves the expression.
/// </summary> /// </summary>
public abstract class ExpressionParser /// <typeparam name="T">The type of expression result.</typeparam>
{ /// <param name="tokens">The tokens.</param>
/// <summary> /// <returns>The representation of the expression parsed.</returns>
/// Resolves the expression. public virtual T ResolveExpression<T>(IEnumerable<Token> tokens) => this.ResolveExpression<T>(tokens, System.Globalization.CultureInfo.InvariantCulture);
/// </summary>
/// <typeparam name="T">The type of expression result.</typeparam> /// <summary>
/// <param name="tokens">The tokens.</param> /// Resolves the expression.
/// <returns>The representation of the expression parsed.</returns> /// </summary>
public virtual T ResolveExpression<T>(IEnumerable<Token> tokens) => /// <typeparam name="T">The type of expression result.</typeparam>
ResolveExpression<T>(tokens, System.Globalization.CultureInfo.InvariantCulture); /// <param name="tokens">The tokens.</param>
/// <param name="formatProvider">The format provider.</param>
/// <summary> /// <returns>The representation of the expression parsed.</returns>
/// Resolves the expression. public virtual T ResolveExpression<T>(IEnumerable<Token> tokens, IFormatProvider formatProvider) {
/// </summary> UnaryExpression conversion = Expression.Convert(this.Parse(tokens, formatProvider), typeof(T));
/// <typeparam name="T">The type of expression result.</typeparam> return Expression.Lambda<Func<T>>(conversion).Compile()();
/// <param name="tokens">The tokens.</param> }
/// <param name="formatProvider">The format provider.</param>
/// <returns>The representation of the expression parsed.</returns> /// <summary>
public virtual T ResolveExpression<T>(IEnumerable<Token> tokens, IFormatProvider formatProvider) /// Parses the specified tokens.
{ /// </summary>
var conversion = Expression.Convert(Parse(tokens,formatProvider), typeof(T)); /// <param name="tokens">The tokens.</param>
return Expression.Lambda<Func<T>>(conversion).Compile()(); /// <returns>
} /// The final expression.
/// </returns>
/// <summary> public virtual Expression Parse(IEnumerable<Token> tokens) => this.Parse(tokens, System.Globalization.CultureInfo.InvariantCulture);
/// Parses the specified tokens.
/// </summary> /// <summary>
/// <param name="tokens">The tokens.</param> /// Parses the specified tokens.
/// <returns> /// </summary>
/// The final expression. /// <param name="tokens">The tokens.</param>
/// </returns> /// <param name="formatProvider">The format provider.</param>
public virtual Expression Parse(IEnumerable<Token> tokens) => /// <returns>
Parse(tokens, System.Globalization.CultureInfo.InvariantCulture); /// The final expression.
/// </returns>
/// <summary> public virtual Expression Parse(IEnumerable<Token> tokens, IFormatProvider formatProvider) {
/// Parses the specified tokens. List<Stack<Expression>> expressionStack = new List<Stack<Expression>>();
/// </summary>
/// <param name="tokens">The tokens.</param> foreach(Token token in tokens) {
/// <param name="formatProvider">The format provider.</param> if(expressionStack.Any() == false) {
/// <returns> expressionStack.Add(new Stack<Expression>());
/// The final expression. }
/// </returns>
public virtual Expression Parse(IEnumerable<Token> tokens, IFormatProvider formatProvider) switch(token.Type) {
{ case TokenType.Wall:
var expressionStack = new List<Stack<Expression>>(); expressionStack.Add(new Stack<Expression>());
break;
foreach (var token in tokens) case TokenType.Number:
{ expressionStack.Last().Push(Expression.Constant(Convert.ToDecimal(token.Value, formatProvider)));
if (expressionStack.Any() == false) break;
expressionStack.Add(new Stack<Expression>()); case TokenType.Variable:
this.ResolveVariable(token.Value, expressionStack.Last());
switch (token.Type) break;
{ case TokenType.String:
case TokenType.Wall: expressionStack.Last().Push(Expression.Constant(token.Value));
expressionStack.Add(new Stack<Expression>()); break;
break; case TokenType.Operator:
case TokenType.Number: this.ResolveOperator(token.Value, expressionStack.Last());
expressionStack.Last().Push(Expression.Constant(Convert.ToDecimal(token.Value, formatProvider))); break;
break; case TokenType.Function:
case TokenType.Variable: this.ResolveFunction(token.Value, expressionStack.Last());
ResolveVariable(token.Value, expressionStack.Last());
break; if(expressionStack.Count > 1 && expressionStack.Last().Count == 1) {
case TokenType.String: Expression lastValue = expressionStack.Last().Pop();
expressionStack.Last().Push(Expression.Constant(token.Value)); _ = expressionStack.Remove(expressionStack.Last());
break; expressionStack.Last().Push(lastValue);
case TokenType.Operator: }
ResolveOperator(token.Value, expressionStack.Last());
break; break;
case TokenType.Function: }
ResolveFunction(token.Value, expressionStack.Last()); }
if (expressionStack.Count > 1 && expressionStack.Last().Count == 1) return expressionStack.Last().Pop();
{ }
var lastValue = expressionStack.Last().Pop();
expressionStack.Remove(expressionStack.Last()); /// <summary>
expressionStack.Last().Push(lastValue); /// Resolves the variable.
} /// </summary>
/// <param name="value">The value.</param>
break; /// <param name="expressionStack">The expression stack.</param>
} public abstract void ResolveVariable(String value, Stack<Expression> expressionStack);
}
/// <summary>
return expressionStack.Last().Pop(); /// Resolves the operator.
} /// </summary>
/// <param name="value">The value.</param>
/// <summary> /// <param name="expressionStack">The expression stack.</param>
/// Resolves the variable. public abstract void ResolveOperator(String value, Stack<Expression> expressionStack);
/// </summary>
/// <param name="value">The value.</param> /// <summary>
/// <param name="expressionStack">The expression stack.</param> /// Resolves the function.
public abstract void ResolveVariable(string value, Stack<Expression> expressionStack); /// </summary>
/// <param name="value">The value.</param>
/// <summary> /// <param name="expressionStack">The expression stack.</param>
/// Resolves the operator. public abstract void ResolveFunction(String value, Stack<Expression> expressionStack);
/// </summary> }
/// <param name="value">The value.</param>
/// <param name="expressionStack">The expression stack.</param>
public abstract void ResolveOperator(string value, Stack<Expression> expressionStack);
/// <summary>
/// Resolves the function.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="expressionStack">The expression stack.</param>
public abstract void ResolveFunction(string value, Stack<Expression> expressionStack);
}
} }

View File

@ -1,32 +1,38 @@
namespace Swan.Parsers using System;
{
namespace Swan.Parsers {
/// <summary>
/// Represents an operator with precedence.
/// </summary>
public class Operator {
/// <summary> /// <summary>
/// Represents an operator with precedence. /// Gets or sets the name.
/// </summary> /// </summary>
public class Operator /// <value>
{ /// The name.
/// <summary> /// </value>
/// Gets or sets the name. public String Name {
/// </summary> get; set;
/// <value> }
/// The name.
/// </value> /// <summary>
public string Name { get; set; } /// Gets or sets the precedence.
/// </summary>
/// <summary> /// <value>
/// Gets or sets the precedence. /// The precedence.
/// </summary> /// </value>
/// <value> public Int32 Precedence {
/// The precedence. get; set;
/// </value> }
public int Precedence { get; set; }
/// <summary>
/// <summary> /// Gets or sets a value indicating whether [right associative].
/// Gets or sets a value indicating whether [right associative]. /// </summary>
/// </summary> /// <value>
/// <value> /// <c>true</c> if [right associative]; otherwise, <c>false</c>.
/// <c>true</c> if [right associative]; otherwise, <c>false</c>. /// </value>
/// </value> public Boolean RightAssociative {
public bool RightAssociative { get; set; } get; set;
} }
}
} }

View File

@ -1,35 +1,38 @@
namespace Swan.Parsers using System;
{
namespace Swan.Parsers {
/// <summary>
/// Represents a Token structure.
/// </summary>
public struct Token {
/// <summary> /// <summary>
/// Represents a Token structure. /// Initializes a new instance of the <see cref="Token"/> struct.
/// </summary> /// </summary>
public struct Token /// <param name="type">The type.</param>
{ /// <param name="value">The value.</param>
/// <summary> public Token(TokenType type, String value) {
/// Initializes a new instance of the <see cref="Token"/> struct. this.Type = type;
/// </summary> this.Value = type == TokenType.Function || type == TokenType.Operator ? value.ToLowerInvariant() : value;
/// <param name="type">The type.</param> }
/// <param name="value">The value.</param>
public Token(TokenType type, string value) /// <summary>
{ /// Gets or sets the type.
Type = type; /// </summary>
Value = type == TokenType.Function || type == TokenType.Operator ? value.ToLowerInvariant() : value; /// <value>
} /// The type.
/// </value>
/// <summary> public TokenType Type {
/// Gets or sets the type. get; set;
/// </summary> }
/// <value>
/// The type. /// <summary>
/// </value> /// Gets the value.
public TokenType Type { get; set; } /// </summary>
/// <value>
/// <summary> /// The value.
/// Gets the value. /// </value>
/// </summary> public String Value {
/// <value> get;
/// The value. }
/// </value> }
public string Value { get; }
}
} }

View File

@ -1,48 +1,46 @@
namespace Swan.Parsers namespace Swan.Parsers {
{ /// <summary>
/// Enums the token types.
/// </summary>
public enum TokenType {
/// <summary> /// <summary>
/// Enums the token types. /// The number
/// </summary> /// </summary>
public enum TokenType Number,
{
/// <summary> /// <summary>
/// The number /// The string
/// </summary> /// </summary>
Number, String,
/// <summary> /// <summary>
/// The string /// The variable
/// </summary> /// </summary>
String, Variable,
/// <summary> /// <summary>
/// The variable /// The function
/// </summary> /// </summary>
Variable, Function,
/// <summary> /// <summary>
/// The function /// The parenthesis
/// </summary> /// </summary>
Function, Parenthesis,
/// <summary> /// <summary>
/// The parenthesis /// The operator
/// </summary> /// </summary>
Parenthesis, Operator,
/// <summary> /// <summary>
/// The operator /// The comma
/// </summary> /// </summary>
Operator, Comma,
/// <summary> /// <summary>
/// The comma /// The wall, used to specified the end of argument list of the following function
/// </summary> /// </summary>
Comma, Wall,
}
/// <summary>
/// The wall, used to specified the end of argument list of the following function
/// </summary>
Wall,
}
} }

View File

@ -2,142 +2,138 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Swan.Parsers namespace Swan.Parsers {
{ /// <summary>
/// Represents a generic tokenizer.
/// </summary>
public abstract class Tokenizer {
private const Char PeriodChar = '.';
private const Char CommaChar = ',';
private const Char StringQuotedChar = '"';
private const Char OpenFuncChar = '(';
private const Char CloseFuncChar = ')';
private const Char NegativeChar = '-';
private const String OpenFuncStr = "(";
private readonly List<Operator> _operators = new List<Operator>();
/// <summary> /// <summary>
/// Represents a generic tokenizer. /// Initializes a new instance of the <see cref="Tokenizer"/> class.
/// This constructor will use the following default operators:
///
/// <list type="table">
/// <listheader>
/// <term>Operator</term>
/// <description>Precedence</description>
/// </listheader>
/// <item>
/// <term>=</term>
/// <description>1</description>
/// </item>
/// <item>
/// <term>!=</term>
/// <description>1</description>
/// </item>
/// <item>
/// <term>&gt;</term>
/// <description>2</description>
/// </item>
/// <item>
/// <term>&lt;</term>
/// <description>2</description>
/// </item>
/// <item>
/// <term>&gt;=</term>
/// <description>2</description>
/// </item>
/// <item>
/// <term>&lt;=</term>
/// <description>2</description>
/// </item>
/// <item>
/// <term>+</term>
/// <description>3</description>
/// </item>
/// <item>
/// <term>&amp;</term>
/// <description>3</description>
/// </item>
/// <item>
/// <term>-</term>
/// <description>3</description>
/// </item>
/// <item>
/// <term>*</term>
/// <description>4</description>
/// </item>
/// <item>
/// <term>(backslash)</term>
/// <description>4</description>
/// </item>
/// <item>
/// <term>/</term>
/// <description>4</description>
/// </item>
/// <item>
/// <term>^</term>
/// <description>4</description>
/// </item>
/// </list>
/// </summary> /// </summary>
public abstract class Tokenizer /// <param name="input">The input.</param>
protected Tokenizer(String input) {
this._operators.AddRange(this.GetDefaultOperators());
this.Tokenize(input);
}
/// <summary>
/// Initializes a new instance of the <see cref="Tokenizer" /> class.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="operators">The operators to use.</param>
protected Tokenizer(String input, IEnumerable<Operator> operators) {
this._operators.AddRange(operators);
this.Tokenize(input);
}
/// <summary>
/// Gets the tokens.
/// </summary>
/// <value>
/// The tokens.
/// </value>
public List<Token> Tokens { get; } = new List<Token>();
/// <summary>
/// Validates the input and return the start index for tokenizer.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="startIndex">The start index.</param>
/// <returns><c>true</c> if the input is valid, otherwise <c>false</c>.</returns>
public abstract Boolean ValidateInput(String input, out Int32 startIndex);
/// <summary>
/// Resolves the type of the function or member.
/// </summary>
/// <param name="input">The input.</param>
/// <returns>The token type.</returns>
public abstract TokenType ResolveFunctionOrMemberType(String input);
/// <summary>
/// Evaluates the function or member.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="position">The position.</param>
/// <returns><c>true</c> if the input is a valid function or variable, otherwise <c>false</c>.</returns>
public virtual Boolean EvaluateFunctionOrMember(String input, Int32 position) => false;
/// <summary>
/// Gets the default operators.
/// </summary>
/// <returns>An array with the operators to use for the tokenizer.</returns>
public virtual Operator[] GetDefaultOperators() => new[]
{ {
private const char PeriodChar = '.';
private const char CommaChar = ',';
private const char StringQuotedChar = '"';
private const char OpenFuncChar = '(';
private const char CloseFuncChar = ')';
private const char NegativeChar = '-';
private const string OpenFuncStr = "(";
private readonly List<Operator> _operators = new List<Operator>();
/// <summary>
/// Initializes a new instance of the <see cref="Tokenizer"/> class.
/// This constructor will use the following default operators:
///
/// <list type="table">
/// <listheader>
/// <term>Operator</term>
/// <description>Precedence</description>
/// </listheader>
/// <item>
/// <term>=</term>
/// <description>1</description>
/// </item>
/// <item>
/// <term>!=</term>
/// <description>1</description>
/// </item>
/// <item>
/// <term>&gt;</term>
/// <description>2</description>
/// </item>
/// <item>
/// <term>&lt;</term>
/// <description>2</description>
/// </item>
/// <item>
/// <term>&gt;=</term>
/// <description>2</description>
/// </item>
/// <item>
/// <term>&lt;=</term>
/// <description>2</description>
/// </item>
/// <item>
/// <term>+</term>
/// <description>3</description>
/// </item>
/// <item>
/// <term>&amp;</term>
/// <description>3</description>
/// </item>
/// <item>
/// <term>-</term>
/// <description>3</description>
/// </item>
/// <item>
/// <term>*</term>
/// <description>4</description>
/// </item>
/// <item>
/// <term>(backslash)</term>
/// <description>4</description>
/// </item>
/// <item>
/// <term>/</term>
/// <description>4</description>
/// </item>
/// <item>
/// <term>^</term>
/// <description>4</description>
/// </item>
/// </list>
/// </summary>
/// <param name="input">The input.</param>
protected Tokenizer(string input)
{
_operators.AddRange(GetDefaultOperators());
Tokenize(input);
}
/// <summary>
/// Initializes a new instance of the <see cref="Tokenizer" /> class.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="operators">The operators to use.</param>
protected Tokenizer(string input, IEnumerable<Operator> operators)
{
_operators.AddRange(operators);
Tokenize(input);
}
/// <summary>
/// Gets the tokens.
/// </summary>
/// <value>
/// The tokens.
/// </value>
public List<Token> Tokens { get; } = new List<Token>();
/// <summary>
/// Validates the input and return the start index for tokenizer.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="startIndex">The start index.</param>
/// <returns><c>true</c> if the input is valid, otherwise <c>false</c>.</returns>
public abstract bool ValidateInput(string input, out int startIndex);
/// <summary>
/// Resolves the type of the function or member.
/// </summary>
/// <param name="input">The input.</param>
/// <returns>The token type.</returns>
public abstract TokenType ResolveFunctionOrMemberType(string input);
/// <summary>
/// Evaluates the function or member.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="position">The position.</param>
/// <returns><c>true</c> if the input is a valid function or variable, otherwise <c>false</c>.</returns>
public virtual bool EvaluateFunctionOrMember(string input, int position) => false;
/// <summary>
/// Gets the default operators.
/// </summary>
/// <returns>An array with the operators to use for the tokenizer.</returns>
public virtual Operator[] GetDefaultOperators() => new[]
{
new Operator {Name = "=", Precedence = 1}, new Operator {Name = "=", Precedence = 1},
new Operator {Name = "!=", Precedence = 1}, new Operator {Name = "!=", Precedence = 1},
new Operator {Name = ">", Precedence = 2}, new Operator {Name = ">", Precedence = 2},
@ -151,211 +147,162 @@ namespace Swan.Parsers
new Operator {Name = "/", Precedence = 4}, new Operator {Name = "/", Precedence = 4},
new Operator {Name = "\\", Precedence = 4}, new Operator {Name = "\\", Precedence = 4},
new Operator {Name = "^", Precedence = 4}, new Operator {Name = "^", Precedence = 4},
}; };
/// <summary> /// <summary>
/// Shunting the yard. /// Shunting the yard.
/// </summary> /// </summary>
/// <param name="includeFunctionStopper">if set to <c>true</c> [include function stopper] (Token type <c>Wall</c>).</param> /// <param name="includeFunctionStopper">if set to <c>true</c> [include function stopper] (Token type <c>Wall</c>).</param>
/// <returns> /// <returns>
/// Enumerable of the token in in. /// Enumerable of the token in in.
/// </returns> /// </returns>
/// <exception cref="InvalidOperationException"> /// <exception cref="InvalidOperationException">
/// Wrong token /// Wrong token
/// or /// or
/// Mismatched parenthesis. /// Mismatched parenthesis.
/// </exception> /// </exception>
public virtual IEnumerable<Token> ShuntingYard(bool includeFunctionStopper = true) public virtual IEnumerable<Token> ShuntingYard(Boolean includeFunctionStopper = true) {
{ Stack<Token> stack = new Stack<Token>();
var stack = new Stack<Token>();
foreach(Token tok in this.Tokens) {
foreach (var tok in Tokens) switch(tok.Type) {
{ case TokenType.Number:
switch (tok.Type) case TokenType.Variable:
{ case TokenType.String:
case TokenType.Number: yield return tok;
case TokenType.Variable: break;
case TokenType.String: case TokenType.Function:
yield return tok; stack.Push(tok);
break; break;
case TokenType.Function: case TokenType.Operator:
stack.Push(tok); while(stack.Any() && stack.Peek().Type == TokenType.Operator && this.CompareOperators(tok.Value, stack.Peek().Value)) {
break; yield return stack.Pop();
case TokenType.Operator: }
while (stack.Any() && stack.Peek().Type == TokenType.Operator &&
CompareOperators(tok.Value, stack.Peek().Value)) stack.Push(tok);
yield return stack.Pop(); break;
case TokenType.Comma:
stack.Push(tok); while(stack.Any() && stack.Peek().Type != TokenType.Comma && stack.Peek().Type != TokenType.Parenthesis) {
break; yield return stack.Pop();
case TokenType.Comma: }
while (stack.Any() && (stack.Peek().Type != TokenType.Comma &&
stack.Peek().Type != TokenType.Parenthesis)) break;
yield return stack.Pop(); case TokenType.Parenthesis:
if(tok.Value == OpenFuncStr) {
break; if(stack.Any() && stack.Peek().Type == TokenType.Function) {
case TokenType.Parenthesis: if(includeFunctionStopper) {
if (tok.Value == OpenFuncStr) yield return new Token(TokenType.Wall, tok.Value);
{ }
if (stack.Any() && stack.Peek().Type == TokenType.Function) }
{
if (includeFunctionStopper) stack.Push(tok);
yield return new Token(TokenType.Wall, tok.Value); } else {
} while(stack.Peek().Value != OpenFuncStr) {
yield return stack.Pop();
stack.Push(tok); }
}
else _ = stack.Pop();
{
while (stack.Peek().Value != OpenFuncStr) if(stack.Any() && stack.Peek().Type == TokenType.Function) {
yield return stack.Pop(); yield return stack.Pop();
}
stack.Pop(); }
if (stack.Any() && stack.Peek().Type == TokenType.Function) break;
{ default:
yield return stack.Pop(); throw new InvalidOperationException("Wrong token");
} }
} }
break; while(stack.Any()) {
default: Token tok = stack.Pop();
throw new InvalidOperationException("Wrong token"); if(tok.Type == TokenType.Parenthesis) {
} throw new InvalidOperationException("Mismatched parenthesis");
} }
while (stack.Any()) yield return tok;
{ }
var tok = stack.Pop(); }
if (tok.Type == TokenType.Parenthesis)
throw new InvalidOperationException("Mismatched parenthesis"); private static Boolean CompareOperators(Operator op1, Operator op2) => op1.RightAssociative ? op1.Precedence < op2.Precedence : op1.Precedence <= op2.Precedence;
yield return tok; private void Tokenize(String input) {
} if(!this.ValidateInput(input, out Int32 startIndex)) {
} return;
}
private static bool CompareOperators(Operator op1, Operator op2) => op1.RightAssociative
? op1.Precedence < op2.Precedence for(Int32 i = startIndex; i < input.Length; i++) {
: op1.Precedence <= op2.Precedence; if(Char.IsWhiteSpace(input, i)) {
continue;
private void Tokenize(string input) }
{
if (!ValidateInput(input, out var startIndex)) if(input[i] == CommaChar) {
{ this.Tokens.Add(new Token(TokenType.Comma, new String(new[] { input[i] })));
return; continue;
} }
for (var i = startIndex; i < input.Length; i++) if(input[i] == StringQuotedChar) {
{ i = this.ExtractString(input, i);
if (char.IsWhiteSpace(input, i)) continue; continue;
}
if (input[i] == CommaChar)
{ if(Char.IsLetter(input, i) || this.EvaluateFunctionOrMember(input, i)) {
Tokens.Add(new Token(TokenType.Comma, new string(new[] { input[i] }))); i = this.ExtractFunctionOrMember(input, i);
continue;
} continue;
}
if (input[i] == StringQuotedChar)
{ if(Char.IsNumber(input, i) || input[i] == NegativeChar && (this.Tokens.Any() && this.Tokens.Last().Type != TokenType.Number || !this.Tokens.Any())) {
i = ExtractString(input, i); i = this.ExtractNumber(input, i);
continue; continue;
} }
if (char.IsLetter(input, i) || EvaluateFunctionOrMember(input, i)) if(input[i] == OpenFuncChar || input[i] == CloseFuncChar) {
{ this.Tokens.Add(new Token(TokenType.Parenthesis, new String(new[] { input[i] })));
i = ExtractFunctionOrMember(input, i); continue;
}
continue;
} i = this.ExtractOperator(input, i);
}
if (char.IsNumber(input, i) || ( }
input[i] == NegativeChar &&
((Tokens.Any() && Tokens.Last().Type != TokenType.Number) || !Tokens.Any()))) private Int32 ExtractData(String input, Int32 i, Func<String, TokenType> tokenTypeEvaluation, Func<Char, Boolean> evaluation, Int32 right = 0, Int32 left = -1) {
{ Int32 charCount = 0;
i = ExtractNumber(input, i); for(Int32 j = i + right; j < input.Length; j++) {
continue; if(evaluation(input[j])) {
} break;
}
if (input[i] == OpenFuncChar ||
input[i] == CloseFuncChar) charCount++;
{ }
Tokens.Add(new Token(TokenType.Parenthesis, new string(new[] { input[i] })));
continue; // Extract and set the value
} String value = input.SliceLength(i + right, charCount);
this.Tokens.Add(new Token(tokenTypeEvaluation(value), value));
i = ExtractOperator(input, i);
} i += charCount + left;
} return i;
}
private int ExtractData(
string input, private Int32 ExtractOperator(String input, Int32 i) => this.ExtractData(input, i, x => TokenType.Operator, x => x == OpenFuncChar || x == CommaChar || x == PeriodChar || x == StringQuotedChar || Char.IsWhiteSpace(x) || Char.IsNumber(x));
int i,
Func<string, TokenType> tokenTypeEvaluation, private Int32 ExtractFunctionOrMember(String input, Int32 i) => this.ExtractData(input, i, this.ResolveFunctionOrMemberType, x => x == OpenFuncChar || x == CloseFuncChar || x == CommaChar || Char.IsWhiteSpace(x));
Func<char, bool> evaluation,
int right = 0, private Int32 ExtractNumber(String input, Int32 i) => this.ExtractData(input, i, x => TokenType.Number, x => !Char.IsNumber(x) && x != PeriodChar && x != NegativeChar);
int left = -1)
{ private Int32 ExtractString(String input, Int32 i) {
var charCount = 0; Int32 length = this.ExtractData(input, i, x => TokenType.String, x => x == StringQuotedChar, 1, 1);
for (var j = i + right; j < input.Length; j++)
{ // open string, report issue
if (evaluation(input[j])) if(length == input.Length && input[length - 1] != StringQuotedChar) {
break; throw new FormatException($"Parser error (Position {i}): Expected '\"' but got '{input[length - 1]}'.");
}
charCount++;
} return length;
}
// Extract and set the value
var value = input.SliceLength(i + right, charCount); private Boolean CompareOperators(String op1, String op2) => CompareOperators(this.GetOperatorOrDefault(op1), this.GetOperatorOrDefault(op2));
Tokens.Add(new Token(tokenTypeEvaluation(value), value));
private Operator GetOperatorOrDefault(String op) => this._operators.FirstOrDefault(x => x.Name == op) ?? new Operator { Name = op, Precedence = 0 };
i += charCount + left; }
return i;
}
private int ExtractOperator(string input, int i) =>
ExtractData(
input,
i,
x => TokenType.Operator,
x => x == OpenFuncChar ||
x == CommaChar ||
x == PeriodChar ||
x == StringQuotedChar ||
char.IsWhiteSpace(x) ||
char.IsNumber(x));
private int ExtractFunctionOrMember(string input, int i) =>
ExtractData(
input,
i,
ResolveFunctionOrMemberType,
x => x == OpenFuncChar ||
x == CloseFuncChar ||
x == CommaChar ||
char.IsWhiteSpace(x));
private int ExtractNumber(string input, int i) =>
ExtractData(
input,
i,
x => TokenType.Number,
x => !char.IsNumber(x) && x != PeriodChar && x != NegativeChar);
private int ExtractString(string input, int i)
{
var length = ExtractData(input, i, x => TokenType.String, x => x == StringQuotedChar, 1, 1);
// open string, report issue
if (length == input.Length && input[length - 1] != StringQuotedChar)
throw new FormatException($"Parser error (Position {i}): Expected '\"' but got '{input[length - 1]}'.");
return length;
}
private bool CompareOperators(string op1, string op2)
=> CompareOperators(GetOperatorOrDefault(op1), GetOperatorOrDefault(op2));
private Operator GetOperatorOrDefault(string op)
=> _operators.FirstOrDefault(x => x.Name == op) ?? new Operator { Name = op, Precedence = 0 };
}
} }

View File

@ -1,40 +1,39 @@
using System; using System;
namespace Swan.Parsers namespace Swan.Parsers {
{ /// <summary>
/// Models a verb option.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public sealed class VerbOptionAttribute : Attribute {
/// <summary> /// <summary>
/// Models a verb option. /// Initializes a new instance of the <see cref="VerbOptionAttribute" /> class.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Property)] /// <param name="name">The name.</param>
public sealed class VerbOptionAttribute : Attribute /// <exception cref="ArgumentNullException">name.</exception>
{ public VerbOptionAttribute(String name) => this.Name = name ?? throw new ArgumentNullException(nameof(name));
/// <summary>
/// Initializes a new instance of the <see cref="VerbOptionAttribute" /> class. /// <summary>
/// </summary> /// Gets the name of the verb option.
/// <param name="name">The name.</param> /// </summary>
/// <exception cref="ArgumentNullException">name.</exception> /// <value>
public VerbOptionAttribute(string name) /// Name.
{ /// </value>
Name = name ?? throw new ArgumentNullException(nameof(name)); public String Name {
} get;
}
/// <summary>
/// Gets the name of the verb option. /// <summary>
/// </summary> /// Gets or sets a short description of this command line verb. Usually a sentence summary.
/// <value> /// </summary>
/// Name. /// <value>
/// </value> /// The help text.
public string Name { get; } /// </value>
public String HelpText {
/// <summary> get; set;
/// Gets or sets a short description of this command line verb. Usually a sentence summary. }
/// </summary>
/// <value> /// <inheritdoc />
/// The help text. public override String ToString() => $" {this.Name}\t\t{this.HelpText}";
/// </value> }
public string HelpText { get; set; }
/// <inheritdoc />
public override string ToString() => $" {Name}\t\t{HelpText}";
}
} }

View File

@ -1,188 +1,161 @@
using System; #nullable enable
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
namespace Swan.Reflection namespace Swan.Reflection {
{ /// <summary>
/// A thread-safe cache of attributes belonging to a given key (MemberInfo or Type).
///
/// The Retrieve method is the most useful one in this class as it
/// calls the retrieval process if the type is not contained
/// in the cache.
/// </summary>
public class AttributeCache {
private readonly Lazy<ConcurrentDictionary<Tuple<Object, Type>, IEnumerable<Object>>> _data = new Lazy<ConcurrentDictionary<Tuple<Object, Type>, IEnumerable<Object>>>(() => new ConcurrentDictionary<Tuple<Object, Type>, IEnumerable<Object>>(), true);
/// <summary> /// <summary>
/// A thread-safe cache of attributes belonging to a given key (MemberInfo or Type). /// Initializes a new instance of the <see cref="AttributeCache"/> class.
///
/// The Retrieve method is the most useful one in this class as it
/// calls the retrieval process if the type is not contained
/// in the cache.
/// </summary> /// </summary>
public class AttributeCache /// <param name="propertyCache">The property cache object.</param>
{ public AttributeCache(PropertyTypeCache? propertyCache = null) => this.PropertyTypeCache = propertyCache ?? PropertyTypeCache.DefaultCache.Value;
private readonly Lazy<ConcurrentDictionary<Tuple<object, Type>, IEnumerable<object>>> _data =
new Lazy<ConcurrentDictionary<Tuple<object, Type>, IEnumerable<object>>>(() => /// <summary>
new ConcurrentDictionary<Tuple<object, Type>, IEnumerable<object>>(), true); /// Gets the default cache.
/// </summary>
/// <summary> /// <value>
/// Initializes a new instance of the <see cref="AttributeCache"/> class. /// The default cache.
/// </summary> /// </value>
/// <param name="propertyCache">The property cache object.</param> public static Lazy<AttributeCache> DefaultCache { get; } = new Lazy<AttributeCache>(() => new AttributeCache());
public AttributeCache(PropertyTypeCache? propertyCache = null)
{ /// <summary>
PropertyTypeCache = propertyCache ?? PropertyTypeCache.DefaultCache.Value; /// A PropertyTypeCache object for caching properties and their attributes.
} /// </summary>
public PropertyTypeCache PropertyTypeCache {
/// <summary> get;
/// Gets the default cache. }
/// </summary>
/// <value> /// <summary>
/// The default cache. /// Determines whether [contains] [the specified member].
/// </value> /// </summary>
public static Lazy<AttributeCache> DefaultCache { get; } = new Lazy<AttributeCache>(() => new AttributeCache()); /// <typeparam name="T">The type of the attribute to be retrieved.</typeparam>
/// <param name="member">The member.</param>
/// <summary> /// <returns>
/// A PropertyTypeCache object for caching properties and their attributes. /// <c>true</c> if [contains] [the specified member]; otherwise, <c>false</c>.
/// </summary> /// </returns>
public PropertyTypeCache PropertyTypeCache { get; } public Boolean Contains<T>(MemberInfo member) => this._data.Value.ContainsKey(new Tuple<Object, Type>(member, typeof(T)));
/// <summary> /// <summary>
/// Determines whether [contains] [the specified member]. /// Gets specific attributes from a member constrained to an attribute.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the attribute to be retrieved.</typeparam> /// <typeparam name="T">The type of the attribute to be retrieved.</typeparam>
/// <param name="member">The member.</param> /// <param name="member">The member.</param>
/// <returns> /// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param>
/// <c>true</c> if [contains] [the specified member]; otherwise, <c>false</c>. /// <returns>An array of the attributes stored for the specified type.</returns>
/// </returns> public IEnumerable<Object> Retrieve<T>(MemberInfo member, Boolean inherit = false) where T : Attribute {
public bool Contains<T>(MemberInfo member) => _data.Value.ContainsKey(new Tuple<object, Type>(member, typeof(T))); if(member == null) {
throw new ArgumentNullException(nameof(member));
/// <summary> }
/// Gets specific attributes from a member constrained to an attribute.
/// </summary> return this.Retrieve(new Tuple<Object, Type>(member, typeof(T)), t => member.GetCustomAttributes<T>(inherit));
/// <typeparam name="T">The type of the attribute to be retrieved.</typeparam> }
/// <param name="member">The member.</param>
/// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param> /// <summary>
/// <returns>An array of the attributes stored for the specified type.</returns> /// Gets all attributes of a specific type from a member.
public IEnumerable<object> Retrieve<T>(MemberInfo member, bool inherit = false) /// </summary>
where T : Attribute /// <param name="member">The member.</param>
{ /// <param name="type">The attribute type.</param>
if (member == null) /// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param>
throw new ArgumentNullException(nameof(member)); /// <returns>An array of the attributes stored for the specified type.</returns>
public IEnumerable<Object> Retrieve(MemberInfo member, Type type, Boolean inherit = false) {
return Retrieve(new Tuple<object, Type>(member, typeof(T)), t => member.GetCustomAttributes<T>(inherit)); if(member == null) {
} throw new ArgumentNullException(nameof(member));
}
/// <summary>
/// Gets all attributes of a specific type from a member. if(type == null) {
/// </summary> throw new ArgumentNullException(nameof(type));
/// <param name="member">The member.</param> }
/// <param name="type">The attribute type.</param>
/// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param> return this.Retrieve(new Tuple<Object, Type>(member, type), t => member.GetCustomAttributes(type, inherit));
/// <returns>An array of the attributes stored for the specified type.</returns> }
public IEnumerable<object> Retrieve(MemberInfo member, Type type, bool inherit = false)
{ /// <summary>
if (member == null) /// Gets one attribute of a specific type from a member.
throw new ArgumentNullException(nameof(member)); /// </summary>
/// <typeparam name="T">The attribute type.</typeparam>
if (type == null) /// <param name="member">The member.</param>
throw new ArgumentNullException(nameof(type)); /// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param>
/// <returns>An attribute stored for the specified type.</returns>
return Retrieve( public T RetrieveOne<T>(MemberInfo member, Boolean inherit = false) where T : Attribute {
new Tuple<object, Type>(member, type), if(member == null) {
t => member.GetCustomAttributes(type, inherit)); return default!;
} }
/// <summary> IEnumerable<Object> attr = this.Retrieve(new Tuple<Object, Type>(member, typeof(T)), t => member.GetCustomAttributes(typeof(T), inherit));
/// Gets one attribute of a specific type from a member.
/// </summary> return ConvertToAttribute<T>(attr);
/// <typeparam name="T">The attribute type.</typeparam> }
/// <param name="member">The member.</param>
/// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param> /// <summary>
/// <returns>An attribute stored for the specified type.</returns> /// Gets one attribute of a specific type from a generic type.
public T RetrieveOne<T>(MemberInfo member, bool inherit = false) /// </summary>
where T : Attribute /// <typeparam name="TAttribute">The type of the attribute.</typeparam>
{ /// <typeparam name="T">The type to retrieve the attribute.</typeparam>
if (member == null) /// <param name="inherit">if set to <c>true</c> [inherit].</param>
return default; /// <returns>An attribute stored for the specified type.</returns>
public TAttribute RetrieveOne<TAttribute, T>(Boolean inherit = false) where TAttribute : Attribute {
var attr = Retrieve( IEnumerable<Object> attr = this.Retrieve(new Tuple<Object, Type>(typeof(T), typeof(TAttribute)), t => typeof(T).GetCustomAttributes(typeof(TAttribute), inherit));
new Tuple<object, Type>(member, typeof(T)),
t => member.GetCustomAttributes(typeof(T), inherit)); return ConvertToAttribute<TAttribute>(attr);
}
return ConvertToAttribute<T>(attr);
} /// <summary>
/// Gets all properties an their attributes of a given type constrained to only attributes.
/// <summary> /// </summary>
/// Gets one attribute of a specific type from a generic type. /// <typeparam name="T">The type of the attribute to retrieve.</typeparam>
/// </summary> /// <param name="type">The type of the object.</param>
/// <typeparam name="TAttribute">The type of the attribute.</typeparam> /// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param>
/// <typeparam name="T">The type to retrieve the attribute.</typeparam> /// <returns>A dictionary of the properties and their attributes stored for the specified type.</returns>
/// <param name="inherit">if set to <c>true</c> [inherit].</param> public Dictionary<PropertyInfo, IEnumerable<Object>> Retrieve<T>(Type type, Boolean inherit = false) where T : Attribute => this.PropertyTypeCache.RetrieveAllProperties(type, true).ToDictionary(x => x, x => this.Retrieve<T>(x, inherit));
/// <returns>An attribute stored for the specified type.</returns>
public TAttribute RetrieveOne<TAttribute, T>(bool inherit = false) /// <summary>
where TAttribute : Attribute /// Gets all properties and their attributes of a given type.
{ /// </summary>
var attr = Retrieve( /// <typeparam name="T">The object type used to extract the properties from.</typeparam>
new Tuple<object, Type>(typeof(T), typeof(TAttribute)), /// <typeparam name="TAttribute">The type of the attribute.</typeparam>
t => typeof(T).GetCustomAttributes(typeof(TAttribute), inherit)); /// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param>
/// <returns>
return ConvertToAttribute<TAttribute>(attr); /// A dictionary of the properties and their attributes stored for the specified type.
} /// </returns>
public Dictionary<PropertyInfo, IEnumerable<Object>> RetrieveFromType<T, TAttribute>(Boolean inherit = false) => this.RetrieveFromType<T>(typeof(TAttribute), inherit);
/// <summary>
/// Gets all properties an their attributes of a given type constrained to only attributes. /// <summary>
/// </summary> /// Gets all properties and their attributes of a given type.
/// <typeparam name="T">The type of the attribute to retrieve.</typeparam> /// </summary>
/// <param name="type">The type of the object.</param> /// <typeparam name="T">The object type used to extract the properties from.</typeparam>
/// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param> /// <param name="attributeType">Type of the attribute.</param>
/// <returns>A dictionary of the properties and their attributes stored for the specified type.</returns> /// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param>
public Dictionary<PropertyInfo, IEnumerable<object>> Retrieve<T>(Type type, bool inherit = false) /// <returns>
where T : Attribute => /// A dictionary of the properties and their attributes stored for the specified type.
PropertyTypeCache.RetrieveAllProperties(type, true) /// </returns>
.ToDictionary(x => x, x => Retrieve<T>(x, inherit)); public Dictionary<PropertyInfo, IEnumerable<Object>> RetrieveFromType<T>(Type attributeType, Boolean inherit = false) {
if(attributeType == null) {
/// <summary> throw new ArgumentNullException(nameof(attributeType));
/// Gets all properties and their attributes of a given type. }
/// </summary>
/// <typeparam name="T">The object type used to extract the properties from.</typeparam> return this.PropertyTypeCache.RetrieveAllProperties<T>(true).ToDictionary(x => x, x => this.Retrieve(x, attributeType, inherit));
/// <typeparam name="TAttribute">The type of the attribute.</typeparam> }
/// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param>
/// <returns> private static T ConvertToAttribute<T>(IEnumerable<Object> attr) where T : Attribute => attr?.Any() != true ? (default!) : attr.Count() == 1 ? (T)Convert.ChangeType(attr.First(), typeof(T)) : throw new AmbiguousMatchException("Multiple custom attributes of the same type found.");
/// A dictionary of the properties and their attributes stored for the specified type.
/// </returns> private IEnumerable<Object> Retrieve(Tuple<Object, Type> key, Func<Tuple<Object, Type>, IEnumerable<Object>> factory) {
public Dictionary<PropertyInfo, IEnumerable<object>> RetrieveFromType<T, TAttribute>(bool inherit = false) if(factory == null) {
=> RetrieveFromType<T>(typeof(TAttribute), inherit); throw new ArgumentNullException(nameof(factory));
}
/// <summary>
/// Gets all properties and their attributes of a given type. return this._data.Value.GetOrAdd(key, k => factory.Invoke(k).Where(item => item != null));
/// </summary> }
/// <typeparam name="T">The object type used to extract the properties from.</typeparam> }
/// <param name="attributeType">Type of the attribute.</param>
/// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param>
/// <returns>
/// A dictionary of the properties and their attributes stored for the specified type.
/// </returns>
public Dictionary<PropertyInfo, IEnumerable<object>> RetrieveFromType<T>(Type attributeType, bool inherit = false)
{
if (attributeType == null)
throw new ArgumentNullException(nameof(attributeType));
return PropertyTypeCache.RetrieveAllProperties<T>(true)
.ToDictionary(x => x, x => Retrieve(x, attributeType, inherit));
}
private static T ConvertToAttribute<T>(IEnumerable<object> attr)
where T : Attribute
{
if (attr?.Any() != true)
return default;
return attr.Count() == 1
? (T) Convert.ChangeType(attr.First(), typeof(T))
: throw new AmbiguousMatchException("Multiple custom attributes of the same type found.");
}
private IEnumerable<object> Retrieve(Tuple<object, Type> key, Func<Tuple<object, Type>, IEnumerable<object>> factory)
{
if (factory == null)
throw new ArgumentNullException(nameof(factory));
return _data.Value.GetOrAdd(key, k => factory.Invoke(k).Where(item => item != null));
}
}
} }

View File

@ -4,48 +4,39 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
namespace Swan.Lite.Reflection namespace Swan.Lite.Reflection {
{ /// <summary>
/// A thread-safe cache of constructors belonging to a given type.
/// </summary>
public class ConstructorTypeCache : TypeCache<Tuple<ConstructorInfo, ParameterInfo[]>> {
/// <summary> /// <summary>
/// A thread-safe cache of constructors belonging to a given type. /// Gets the default cache.
/// </summary> /// </summary>
public class ConstructorTypeCache : TypeCache<Tuple<ConstructorInfo, ParameterInfo[]>> /// <value>
{ /// The default cache.
/// <summary> /// </value>
/// Gets the default cache. public static Lazy<ConstructorTypeCache> DefaultCache { get; } = new Lazy<ConstructorTypeCache>(() => new ConstructorTypeCache());
/// </summary>
/// <value> /// <summary>
/// The default cache. /// Retrieves all constructors order by the number of parameters ascending.
/// </value> /// </summary>
public static Lazy<ConstructorTypeCache> DefaultCache { get; } = /// <typeparam name="T">The type to inspect.</typeparam>
new Lazy<ConstructorTypeCache>(() => new ConstructorTypeCache()); /// <param name="includeNonPublic">if set to <c>true</c> [include non public].</param>
/// <returns>
/// <summary> /// A collection with all the constructors in the given type.
/// Retrieves all constructors order by the number of parameters ascending. /// </returns>
/// </summary> public IEnumerable<Tuple<ConstructorInfo, ParameterInfo[]>> RetrieveAllConstructors<T>(Boolean includeNonPublic = false) => this.Retrieve<T>(GetConstructors(includeNonPublic));
/// <typeparam name="T">The type to inspect.</typeparam>
/// <param name="includeNonPublic">if set to <c>true</c> [include non public].</param> /// <summary>
/// <returns> /// Retrieves all constructors order by the number of parameters ascending.
/// A collection with all the constructors in the given type. /// </summary>
/// </returns> /// <param name="type">The type.</param>
public IEnumerable<Tuple<ConstructorInfo, ParameterInfo[]>> RetrieveAllConstructors<T>(bool includeNonPublic = false) /// <param name="includeNonPublic">if set to <c>true</c> [include non public].</param>
=> Retrieve<T>(GetConstructors(includeNonPublic)); /// <returns>
/// A collection with all the constructors in the given type.
/// <summary> /// </returns>
/// Retrieves all constructors order by the number of parameters ascending. public IEnumerable<Tuple<ConstructorInfo, ParameterInfo[]>> RetrieveAllConstructors(Type type, Boolean includeNonPublic = false) => this.Retrieve(type, GetConstructors(includeNonPublic));
/// </summary>
/// <param name="type">The type.</param> private static Func<Type, IEnumerable<Tuple<ConstructorInfo, ParameterInfo[]>>> GetConstructors(Boolean includeNonPublic) => t => t.GetConstructors(includeNonPublic ? BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance : BindingFlags.Public | BindingFlags.Instance).Select(x => Tuple.Create(x, x.GetParameters())).OrderBy(x => x.Item2.Length).ToList();
/// <param name="includeNonPublic">if set to <c>true</c> [include non public].</param> }
/// <returns>
/// A collection with all the constructors in the given type.
/// </returns>
public IEnumerable<Tuple<ConstructorInfo, ParameterInfo[]>> RetrieveAllConstructors(Type type, bool includeNonPublic = false)
=> Retrieve(type, GetConstructors(includeNonPublic));
private static Func<Type, IEnumerable<Tuple<ConstructorInfo, ParameterInfo[]>>> GetConstructors(bool includeNonPublic)
=> t => t.GetConstructors(includeNonPublic ? BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance : BindingFlags.Public | BindingFlags.Instance)
.Select(x => Tuple.Create(x, x.GetParameters()))
.OrderBy(x => x.Item2.Length)
.ToList();
}
} }

View File

@ -2,106 +2,112 @@
using System.Reflection; using System.Reflection;
using Swan.Configuration; using Swan.Configuration;
namespace Swan.Reflection namespace Swan.Reflection {
{ /// <summary>
/// Represents a Property object from a Object Reflection Property with extended values.
/// </summary>
public class ExtendedPropertyInfo {
/// <summary> /// <summary>
/// Represents a Property object from a Object Reflection Property with extended values. /// Initializes a new instance of the <see cref="ExtendedPropertyInfo"/> class.
/// </summary> /// </summary>
public class ExtendedPropertyInfo /// <param name="propertyInfo">The property information.</param>
{ public ExtendedPropertyInfo(PropertyInfo propertyInfo) {
/// <summary> if(propertyInfo == null) {
/// Initializes a new instance of the <see cref="ExtendedPropertyInfo"/> class. throw new ArgumentNullException(nameof(propertyInfo));
/// </summary> }
/// <param name="propertyInfo">The property information.</param>
public ExtendedPropertyInfo(PropertyInfo propertyInfo) this.Property = propertyInfo.Name;
{ this.DataType = propertyInfo.PropertyType.Name;
if (propertyInfo == null)
{ foreach(PropertyDisplayAttribute display in AttributeCache.DefaultCache.Value.Retrieve<PropertyDisplayAttribute>(propertyInfo, true)) {
throw new ArgumentNullException(nameof(propertyInfo)); this.Name = display.Name;
} this.Description = display.Description;
this.GroupName = display.GroupName;
Property = propertyInfo.Name; this.DefaultValue = display.DefaultValue;
DataType = propertyInfo.PropertyType.Name; }
}
foreach (PropertyDisplayAttribute display in AttributeCache.DefaultCache.Value.Retrieve<PropertyDisplayAttribute>(propertyInfo, true))
{
Name = display.Name;
Description = display.Description;
GroupName = display.GroupName;
DefaultValue = display.DefaultValue;
}
}
/// <summary>
/// Gets or sets the property.
/// </summary>
/// <value>
/// The property.
/// </value>
public string Property { get; }
/// <summary>
/// Gets or sets the type of the data.
/// </summary>
/// <value>
/// The type of the data.
/// </value>
public string DataType { get; }
/// <summary>
/// Gets or sets the value.
/// </summary>
/// <value>
/// The value.
/// </value>
public object Value { get; set; }
/// <summary>
/// Gets or sets the default value.
/// </summary>
/// <value>
/// The default value.
/// </value>
public object DefaultValue { get; }
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>
/// The name.
/// </value>
public string Name { get; }
/// <summary>
/// Gets or sets the description.
/// </summary>
/// <value>
/// The description.
/// </value>
public string Description { get; }
/// <summary>
/// Gets or sets the name of the group.
/// </summary>
/// <value>
/// The name of the group.
/// </value>
public string GroupName { get; }
}
/// <summary> /// <summary>
/// Represents a Property object from a Object Reflection Property with extended values. /// Gets or sets the property.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the object.</typeparam> /// <value>
public class ExtendedPropertyInfo<T> : ExtendedPropertyInfo /// The property.
{ /// </value>
/// <summary> public String Property {
/// Initializes a new instance of the <see cref="ExtendedPropertyInfo{T}"/> class. get;
/// </summary> }
/// <param name="property">The property.</param>
public ExtendedPropertyInfo(string property) /// <summary>
: base(typeof(T).GetProperty(property)) /// Gets or sets the type of the data.
{ /// </summary>
} /// <value>
} /// The type of the data.
/// </value>
public String DataType {
get;
}
/// <summary>
/// Gets or sets the value.
/// </summary>
/// <value>
/// The value.
/// </value>
public Object Value {
get; set;
}
/// <summary>
/// Gets or sets the default value.
/// </summary>
/// <value>
/// The default value.
/// </value>
public Object DefaultValue {
get;
}
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>
/// The name.
/// </value>
public String Name {
get;
}
/// <summary>
/// Gets or sets the description.
/// </summary>
/// <value>
/// The description.
/// </value>
public String Description {
get;
}
/// <summary>
/// Gets or sets the name of the group.
/// </summary>
/// <value>
/// The name of the group.
/// </value>
public String GroupName {
get;
}
}
/// <summary>
/// Represents a Property object from a Object Reflection Property with extended values.
/// </summary>
/// <typeparam name="T">The type of the object.</typeparam>
public class ExtendedPropertyInfo<T> : ExtendedPropertyInfo {
/// <summary>
/// Initializes a new instance of the <see cref="ExtendedPropertyInfo{T}"/> class.
/// </summary>
/// <param name="property">The property.</param>
public ExtendedPropertyInfo(String property) : base(typeof(T).GetProperty(property)) {
}
}
} }

View File

@ -1,267 +1,247 @@
using System; #nullable enable
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
namespace Swan.Reflection namespace Swan.Reflection {
{ /// <summary>
/// <summary> /// Provides extended information about a type.
/// Provides extended information about a type. ///
/// /// This class is mainly used to define sets of types within the Definition class
/// This class is mainly used to define sets of types within the Definition class /// and it is not meant for other than querying the BasicTypesInfo dictionary.
/// and it is not meant for other than querying the BasicTypesInfo dictionary. /// </summary>
/// </summary> public class ExtendedTypeInfo {
public class ExtendedTypeInfo private const String TryParseMethodName = nameof(Byte.TryParse);
private const String ToStringMethodName = nameof(ToString);
private static readonly Type[] NumericTypes =
{ {
private const string TryParseMethodName = nameof(byte.TryParse); typeof(Byte),
private const string ToStringMethodName = nameof(ToString); typeof(SByte),
typeof(Decimal),
private static readonly Type[] NumericTypes = typeof(Double),
{ typeof(Single),
typeof(byte), typeof(Int32),
typeof(sbyte), typeof(UInt32),
typeof(decimal), typeof(Int64),
typeof(double), typeof(UInt64),
typeof(float), typeof(Int16),
typeof(int), typeof(UInt16),
typeof(uint), };
typeof(long),
typeof(ulong), private readonly ParameterInfo[]? _tryParseParameters;
typeof(short), private readonly Int32 _toStringArgumentLength;
typeof(ushort),
}; #region Constructors
private readonly ParameterInfo[]? _tryParseParameters;
private readonly int _toStringArgumentLength;
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="ExtendedTypeInfo"/> class.
/// </summary>
/// <param name="t">The t.</param>
public ExtendedTypeInfo(Type t)
{
Type = t ?? throw new ArgumentNullException(nameof(t));
IsNullableValueType = Type.IsGenericType
&& Type.GetGenericTypeDefinition() == typeof(Nullable<>);
IsValueType = t.IsValueType;
UnderlyingType = IsNullableValueType ?
new NullableConverter(Type).UnderlyingType :
Type;
IsNumeric = NumericTypes.Contains(UnderlyingType);
// Extract the TryParse method info
try
{
TryParseMethodInfo = UnderlyingType.GetMethod(TryParseMethodName,
new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider), UnderlyingType.MakeByRefType() }) ??
UnderlyingType.GetMethod(TryParseMethodName,
new[] { typeof(string), UnderlyingType.MakeByRefType() });
_tryParseParameters = TryParseMethodInfo?.GetParameters();
}
catch
{
// ignored
}
// Extract the ToString method Info
try
{
ToStringMethodInfo = UnderlyingType.GetMethod(ToStringMethodName,
new[] { typeof(IFormatProvider) }) ??
UnderlyingType.GetMethod(ToStringMethodName,
Array.Empty<Type>());
_toStringArgumentLength = ToStringMethodInfo?.GetParameters().Length ?? 0;
}
catch
{
// ignored
}
}
#endregion
#region Properties
/// <summary>
/// Gets the type this extended info class provides for.
/// </summary>
/// <value>
/// The type.
/// </value>
public Type Type { get; }
/// <summary>
/// Gets a value indicating whether the type is a nullable value type.
/// </summary>
/// <value>
/// <c>true</c> if this instance is nullable value type; otherwise, <c>false</c>.
/// </value>
public bool IsNullableValueType { get; }
/// <summary>
/// Gets a value indicating whether the type or underlying type is numeric.
/// </summary>
/// <value>
/// <c>true</c> if this instance is numeric; otherwise, <c>false</c>.
/// </value>
public bool IsNumeric { get; }
/// <summary>
/// Gets a value indicating whether the type is value type.
/// Nullable value types have this property set to False.
/// </summary>
public bool IsValueType { get; }
/// <summary>
/// When dealing with nullable value types, this property will
/// return the underlying value type of the nullable,
/// Otherwise it will return the same type as the Type property.
/// </summary>
/// <value>
/// The type of the underlying.
/// </value>
public Type UnderlyingType { get; }
/// <summary>
/// Gets the try parse method information. If the type does not contain
/// a suitable TryParse static method, it will return null.
/// </summary>
/// <value>
/// The try parse method information.
/// </value>
public MethodInfo TryParseMethodInfo { get; }
/// <summary>
/// Gets the ToString method info
/// It will prefer the overload containing the IFormatProvider argument.
/// </summary>
/// <value>
/// To string method information.
/// </value>
public MethodInfo ToStringMethodInfo { get; }
/// <summary>
/// Gets a value indicating whether the type contains a suitable TryParse method.
/// </summary>
/// <value>
/// <c>true</c> if this instance can parse natively; otherwise, <c>false</c>.
/// </value>
public bool CanParseNatively => TryParseMethodInfo != null;
#endregion
#region Methods
/// <summary>
/// Tries to parse the string into an object of the type this instance represents.
/// Returns false when no suitable TryParse methods exists for the type or when parsing fails
/// for any reason. When possible, this method uses CultureInfo.InvariantCulture and NumberStyles.Any.
/// </summary>
/// <param name="s">The s.</param>
/// <param name="result">The result.</param>
/// <returns><c>true</c> if parse was converted successfully; otherwise, <c>false</c>.</returns>
public bool TryParse(string s, out object? result)
{
result = Type.GetDefault();
try
{
if (Type == typeof(string))
{
result = Convert.ChangeType(s, Type, CultureInfo.InvariantCulture);
return true;
}
if ((IsNullableValueType && string.IsNullOrEmpty(s)) || !CanParseNatively)
{
return true;
}
// Build the arguments of the TryParse method
var dynamicArguments = new List<object?> { s };
for (var pi = 1; pi < _tryParseParameters.Length - 1; pi++)
{
var argInfo = _tryParseParameters[pi];
if (argInfo.ParameterType == typeof(IFormatProvider))
dynamicArguments.Add(CultureInfo.InvariantCulture);
else if (argInfo.ParameterType == typeof(NumberStyles))
dynamicArguments.Add(NumberStyles.Any);
else
dynamicArguments.Add(null);
}
dynamicArguments.Add(null);
var parseArguments = dynamicArguments.ToArray();
if ((bool) TryParseMethodInfo.Invoke(null, parseArguments))
{
result = parseArguments[parseArguments.Length - 1];
return true;
}
}
catch
{
// Ignore
}
return false;
}
/// <summary>
/// Converts this instance to its string representation,
/// trying to use the CultureInfo.InvariantCulture
/// IFormat provider if the overload is available.
/// </summary>
/// <param name="instance">The instance.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public string ToStringInvariant(object instance)
{
if (instance == null)
return string.Empty;
return _toStringArgumentLength != 1
? instance.ToString()
: ToStringMethodInfo.Invoke(instance, new object[] {CultureInfo.InvariantCulture}) as string ?? string.Empty;
}
#endregion
}
/// <summary> /// <summary>
/// Provides extended information about a type. /// Initializes a new instance of the <see cref="ExtendedTypeInfo"/> class.
///
/// This class is mainly used to define sets of types within the Constants class
/// and it is not meant for other than querying the BasicTypesInfo dictionary.
/// </summary> /// </summary>
/// <typeparam name="T">The type of extended type information.</typeparam> /// <param name="t">The t.</param>
public class ExtendedTypeInfo<T> : ExtendedTypeInfo public ExtendedTypeInfo(Type t) {
{ this.Type = t ?? throw new ArgumentNullException(nameof(t));
/// <summary> this.IsNullableValueType = this.Type.IsGenericType && this.Type.GetGenericTypeDefinition() == typeof(Nullable<>);
/// Initializes a new instance of the <see cref="ExtendedTypeInfo{T}"/> class.
/// </summary> this.IsValueType = t.IsValueType;
public ExtendedTypeInfo()
: base(typeof(T)) this.UnderlyingType = this.IsNullableValueType ? new NullableConverter(this.Type).UnderlyingType : this.Type;
{
// placeholder this.IsNumeric = NumericTypes.Contains(this.UnderlyingType);
}
// Extract the TryParse method info
/// <summary> try {
/// Converts this instance to its string representation, this.TryParseMethodInfo = this.UnderlyingType.GetMethod(TryParseMethodName, new[] { typeof(String), typeof(NumberStyles), typeof(IFormatProvider), this.UnderlyingType.MakeByRefType() }) ?? this.UnderlyingType.GetMethod(TryParseMethodName, new[] { typeof(String), this.UnderlyingType.MakeByRefType() });
/// trying to use the CultureInfo.InvariantCulture
/// IFormat provider if the overload is available. this._tryParseParameters = this.TryParseMethodInfo?.GetParameters();
/// </summary> } catch {
/// <param name="instance">The instance.</param> // ignored
/// <returns>A <see cref="System.String" /> that represents the current object.</returns> }
public string ToStringInvariant(T instance) => base.ToStringInvariant(instance);
} // Extract the ToString method Info
try {
this.ToStringMethodInfo = this.UnderlyingType.GetMethod(ToStringMethodName, new[] { typeof(IFormatProvider) }) ?? this.UnderlyingType.GetMethod(ToStringMethodName, Array.Empty<Type>());
this._toStringArgumentLength = this.ToStringMethodInfo?.GetParameters().Length ?? 0;
} catch {
// ignored
}
}
#endregion
#region Properties
/// <summary>
/// Gets the type this extended info class provides for.
/// </summary>
/// <value>
/// The type.
/// </value>
public Type Type {
get;
}
/// <summary>
/// Gets a value indicating whether the type is a nullable value type.
/// </summary>
/// <value>
/// <c>true</c> if this instance is nullable value type; otherwise, <c>false</c>.
/// </value>
public Boolean IsNullableValueType {
get;
}
/// <summary>
/// Gets a value indicating whether the type or underlying type is numeric.
/// </summary>
/// <value>
/// <c>true</c> if this instance is numeric; otherwise, <c>false</c>.
/// </value>
public Boolean IsNumeric {
get;
}
/// <summary>
/// Gets a value indicating whether the type is value type.
/// Nullable value types have this property set to False.
/// </summary>
public Boolean IsValueType {
get;
}
/// <summary>
/// When dealing with nullable value types, this property will
/// return the underlying value type of the nullable,
/// Otherwise it will return the same type as the Type property.
/// </summary>
/// <value>
/// The type of the underlying.
/// </value>
public Type UnderlyingType {
get;
}
/// <summary>
/// Gets the try parse method information. If the type does not contain
/// a suitable TryParse static method, it will return null.
/// </summary>
/// <value>
/// The try parse method information.
/// </value>
public MethodInfo? TryParseMethodInfo {
get;
}
/// <summary>
/// Gets the ToString method info
/// It will prefer the overload containing the IFormatProvider argument.
/// </summary>
/// <value>
/// To string method information.
/// </value>
public MethodInfo? ToStringMethodInfo {
get;
}
/// <summary>
/// Gets a value indicating whether the type contains a suitable TryParse method.
/// </summary>
/// <value>
/// <c>true</c> if this instance can parse natively; otherwise, <c>false</c>.
/// </value>
public Boolean CanParseNatively => this.TryParseMethodInfo != null;
#endregion
#region Methods
/// <summary>
/// Tries to parse the string into an object of the type this instance represents.
/// Returns false when no suitable TryParse methods exists for the type or when parsing fails
/// for any reason. When possible, this method uses CultureInfo.InvariantCulture and NumberStyles.Any.
/// </summary>
/// <param name="s">The s.</param>
/// <param name="result">The result.</param>
/// <returns><c>true</c> if parse was converted successfully; otherwise, <c>false</c>.</returns>
public Boolean TryParse(String s, out Object? result) {
result = this.Type.GetDefault();
try {
if(this.Type == typeof(String)) {
result = Convert.ChangeType(s, this.Type, CultureInfo.InvariantCulture);
return true;
}
if(this.IsNullableValueType && String.IsNullOrEmpty(s) || !this.CanParseNatively) {
return true;
}
// Build the arguments of the TryParse method
List<Object?> dynamicArguments = new List<Object?> { s };
if(this._tryParseParameters != null) {
for(Int32 pi = 1; pi < this._tryParseParameters.Length - 1; pi++) {
ParameterInfo argInfo = this._tryParseParameters[pi];
if(argInfo.ParameterType == typeof(IFormatProvider)) {
dynamicArguments.Add(CultureInfo.InvariantCulture);
} else if(argInfo.ParameterType == typeof(NumberStyles)) {
dynamicArguments.Add(NumberStyles.Any);
} else {
dynamicArguments.Add(null);
}
}
}
dynamicArguments.Add(null);
Object?[] parseArguments = dynamicArguments.ToArray();
if((Boolean)this.TryParseMethodInfo?.Invoke(null, parseArguments)!) {
result = parseArguments[^1];
return true;
}
} catch {
// Ignore
}
return false;
}
/// <summary>
/// Converts this instance to its string representation,
/// trying to use the CultureInfo.InvariantCulture
/// IFormat provider if the overload is available.
/// </summary>
/// <param name="instance">The instance.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public String ToStringInvariant(Object? instance) => instance == null ? String.Empty : this._toStringArgumentLength != 1 ? instance.ToString()! : this.ToStringMethodInfo?.Invoke(instance, new Object[] { CultureInfo.InvariantCulture }) as String ?? String.Empty;
#endregion
}
/// <summary>
/// Provides extended information about a type.
///
/// This class is mainly used to define sets of types within the Constants class
/// and it is not meant for other than querying the BasicTypesInfo dictionary.
/// </summary>
/// <typeparam name="T">The type of extended type information.</typeparam>
public class ExtendedTypeInfo<T> : ExtendedTypeInfo {
/// <summary>
/// Initializes a new instance of the <see cref="ExtendedTypeInfo{T}"/> class.
/// </summary>
public ExtendedTypeInfo() : base(typeof(T)) {
// placeholder
}
/// <summary>
/// Converts this instance to its string representation,
/// trying to use the CultureInfo.InvariantCulture
/// IFormat provider if the overload is available.
/// </summary>
/// <param name="instance">The instance.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public String ToStringInvariant(T instance) => base.ToStringInvariant(instance);
}
} }

View File

@ -1,22 +1,22 @@
namespace Swan.Reflection using System;
{
namespace Swan.Reflection {
/// <summary>
/// Represents a generic interface to store getters and setters.
/// </summary>
public interface IPropertyProxy {
/// <summary> /// <summary>
/// Represents a generic interface to store getters and setters. /// Gets the property value via a stored delegate.
/// </summary> /// </summary>
public interface IPropertyProxy /// <param name="instance">The instance.</param>
{ /// <returns>The property value.</returns>
/// <summary> Object GetValue(Object instance);
/// Gets the property value via a stored delegate.
/// </summary> /// <summary>
/// <param name="instance">The instance.</param> /// Sets the property value via a stored delegate.
/// <returns>The property value.</returns> /// </summary>
object GetValue(object instance); /// <param name="instance">The instance.</param>
/// <param name="value">The value.</param>
/// <summary> void SetValue(Object instance, Object value);
/// Sets the property value via a stored delegate. }
/// </summary>
/// <param name="instance">The instance.</param>
/// <param name="value">The value.</param>
void SetValue(object instance, object value);
}
} }

View File

@ -2,116 +2,109 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Reflection; using System.Reflection;
namespace Swan.Reflection namespace Swan.Reflection {
{ /// <summary>
/// Represents a Method Info Cache.
/// </summary>
public class MethodInfoCache : ConcurrentDictionary<String, MethodInfo> {
/// <summary> /// <summary>
/// Represents a Method Info Cache. /// 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.
/// </summary> /// </summary>
public class MethodInfoCache : ConcurrentDictionary<string, MethodInfo> /// <typeparam name="T">The type of type.</typeparam>
{ /// <param name="name">The name.</param>
/// <summary> /// <param name="alias">The alias.</param>
/// Retrieves the properties stored for the specified type. /// <param name="types">The types.</param>
/// If the properties are not available, it calls the factory method to retrieve them /// <returns>
/// and returns them as an array of PropertyInfo. /// The cached MethodInfo.
/// </summary> /// </returns>
/// <typeparam name="T">The type of type.</typeparam> /// <exception cref="ArgumentNullException">name
/// <param name="name">The name.</param> /// or
/// <param name="alias">The alias.</param> /// factory.</exception>
/// <param name="types">The types.</param> public MethodInfo Retrieve<T>(String name, String alias, params Type[] types) => this.Retrieve(typeof(T), name, alias, types);
/// <returns>
/// The cached MethodInfo. /// <summary>
/// </returns> /// Retrieves the specified name.
/// <exception cref="ArgumentNullException">name /// </summary>
/// or /// <typeparam name="T">The type of type.</typeparam>
/// factory.</exception> /// <param name="name">The name.</param>
public MethodInfo Retrieve<T>(string name, string alias, params Type[] types) /// <param name="types">The types.</param>
=> Retrieve(typeof(T), name, alias, types); /// <returns>
/// The cached MethodInfo.
/// <summary> /// </returns>
/// Retrieves the specified name. public MethodInfo Retrieve<T>(String name, params Type[] types) => this.Retrieve(typeof(T), name, name, types);
/// </summary>
/// <typeparam name="T">The type of type.</typeparam> /// <summary>
/// <param name="name">The name.</param> /// Retrieves the specified type.
/// <param name="types">The types.</param> /// </summary>
/// <returns> /// <param name="type">The type.</param>
/// The cached MethodInfo. /// <param name="name">The name.</param>
/// </returns> /// <param name="types">The types.</param>
public MethodInfo Retrieve<T>(string name, params Type[] types) /// <returns>
=> Retrieve(typeof(T), name, name, types); /// An array of the properties stored for the specified type.
/// </returns>
/// <summary> public MethodInfo Retrieve(Type type, String name, params Type[] types) => this.Retrieve(type, name, name, types);
/// Retrieves the specified type.
/// </summary> /// <summary>
/// <param name="type">The type.</param> /// Retrieves the specified type.
/// <param name="name">The name.</param> /// </summary>
/// <param name="types">The types.</param> /// <param name="type">The type.</param>
/// <returns> /// <param name="name">The name.</param>
/// An array of the properties stored for the specified type. /// <param name="alias">The alias.</param>
/// </returns> /// <param name="types">The types.</param>
public MethodInfo Retrieve(Type type, string name, params Type[] types) /// <returns>
=> Retrieve(type, name, name, types); /// The cached MethodInfo.
/// </returns>
/// <summary> public MethodInfo Retrieve(Type type, String name, String alias, params Type[] types) {
/// Retrieves the specified type. if(type == null) {
/// </summary> throw new ArgumentNullException(nameof(type));
/// <param name="type">The type.</param> }
/// <param name="name">The name.</param>
/// <param name="alias">The alias.</param> if(alias == null) {
/// <param name="types">The types.</param> throw new ArgumentNullException(nameof(alias));
/// <returns> }
/// The cached MethodInfo.
/// </returns> if(name == null) {
public MethodInfo Retrieve(Type type, string name, string alias, params Type[] types) throw new ArgumentNullException(nameof(name));
{ }
if (type == null)
throw new ArgumentNullException(nameof(type)); return this.GetOrAdd(alias, x => type.GetMethod(name, types ?? Array.Empty<Type>()));
}
if (alias == null)
throw new ArgumentNullException(nameof(alias)); /// <summary>
/// Retrieves the specified name.
if (name == null) /// </summary>
throw new ArgumentNullException(nameof(name)); /// <typeparam name="T">The type of type.</typeparam>
/// <param name="name">The name.</param>
return GetOrAdd( /// <returns>
alias, /// The cached MethodInfo.
x => type.GetMethod(name, types ?? Array.Empty<Type>())); /// </returns>
} public MethodInfo Retrieve<T>(String name) => this.Retrieve(typeof(T), name);
/// <summary> /// <summary>
/// Retrieves the specified name. /// Retrieves the specified type.
/// </summary> /// </summary>
/// <typeparam name="T">The type of type.</typeparam> /// <param name="type">The type.</param>
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <returns> /// <returns>
/// The cached MethodInfo. /// The cached MethodInfo.
/// </returns> /// </returns>
public MethodInfo Retrieve<T>(string name) /// <exception cref="ArgumentNullException">
=> Retrieve(typeof(T), name); /// type
/// or
/// <summary> /// name.
/// Retrieves the specified type. /// </exception>
/// </summary> public MethodInfo Retrieve(Type type, String name) {
/// <param name="type">The type.</param> if(type == null) {
/// <param name="name">The name.</param> throw new ArgumentNullException(nameof(type));
/// <returns> }
/// The cached MethodInfo.
/// </returns> if(name == null) {
/// <exception cref="ArgumentNullException"> throw new ArgumentNullException(nameof(name));
/// type }
/// or
/// name. return this.GetOrAdd(name, type.GetMethod);
/// </exception> }
public MethodInfo Retrieve(Type type, string name) }
{
if (type == null)
throw new ArgumentNullException(nameof(type));
if (name == null)
throw new ArgumentNullException(nameof(name));
return GetOrAdd(
name,
type.GetMethod);
}
}
} }

View File

@ -2,46 +2,43 @@
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Swan.Reflection namespace Swan.Reflection {
{ /// <summary>
/// Represents a generic class to store getters and setters.
/// </summary>
/// <typeparam name="TClass">The type of the class.</typeparam>
/// <typeparam name="TProperty">The type of the property.</typeparam>
/// <seealso cref="IPropertyProxy" />
public sealed class PropertyProxy<TClass, TProperty> : IPropertyProxy where TClass : class {
private readonly Func<TClass, TProperty> _getter;
private readonly Action<TClass, TProperty> _setter;
/// <summary> /// <summary>
/// Represents a generic class to store getters and setters. /// Initializes a new instance of the <see cref="PropertyProxy{TClass, TProperty}"/> class.
/// </summary> /// </summary>
/// <typeparam name="TClass">The type of the class.</typeparam> /// <param name="property">The property.</param>
/// <typeparam name="TProperty">The type of the property.</typeparam> public PropertyProxy(PropertyInfo property) {
/// <seealso cref="IPropertyProxy" /> if(property == null) {
public sealed class PropertyProxy<TClass, TProperty> : IPropertyProxy throw new ArgumentNullException(nameof(property));
where TClass : class }
{
private readonly Func<TClass, TProperty> _getter; MethodInfo getterInfo = property.GetGetMethod(false);
private readonly Action<TClass, TProperty> _setter; if(getterInfo != null) {
this._getter = (Func<TClass, TProperty>)Delegate.CreateDelegate(typeof(Func<TClass, TProperty>), getterInfo);
/// <summary> }
/// Initializes a new instance of the <see cref="PropertyProxy{TClass, TProperty}"/> class.
/// </summary> MethodInfo setterInfo = property.GetSetMethod(false);
/// <param name="property">The property.</param> if(setterInfo != null) {
public PropertyProxy(PropertyInfo property) this._setter = (Action<TClass, TProperty>)Delegate.CreateDelegate(typeof(Action<TClass, TProperty>), setterInfo);
{ }
if (property == null) }
throw new ArgumentNullException(nameof(property));
/// <inheritdoc />
var getterInfo = property.GetGetMethod(false); [MethodImpl(MethodImplOptions.AggressiveInlining)]
if (getterInfo != null) Object IPropertyProxy.GetValue(Object instance) => this._getter(instance as TClass);
_getter = (Func<TClass, TProperty>)Delegate.CreateDelegate(typeof(Func<TClass, TProperty>), getterInfo);
/// <inheritdoc />
var setterInfo = property.GetSetMethod(false); [MethodImpl(MethodImplOptions.AggressiveInlining)]
if (setterInfo != null) void IPropertyProxy.SetValue(Object instance, Object value) => this._setter(instance as TClass, (TProperty)value);
_setter = (Action<TClass, TProperty>)Delegate.CreateDelegate(typeof(Action<TClass, TProperty>), setterInfo); }
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
object IPropertyProxy.GetValue(object instance) =>
_getter(instance as TClass);
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void IPropertyProxy.SetValue(object instance, object value) =>
_setter(instance as TClass, (TProperty)value);
}
} }

View File

@ -3,72 +3,54 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
namespace Swan.Reflection namespace Swan.Reflection {
{ /// <summary>
/// A thread-safe cache of properties belonging to a given type.
/// </summary>
public class PropertyTypeCache : TypeCache<PropertyInfo> {
/// <summary> /// <summary>
/// A thread-safe cache of properties belonging to a given type. /// Gets the default cache.
/// </summary> /// </summary>
public class PropertyTypeCache : TypeCache<PropertyInfo> /// <value>
{ /// The default cache.
/// <summary> /// </value>
/// Gets the default cache. public static Lazy<PropertyTypeCache> DefaultCache { get; } = new Lazy<PropertyTypeCache>(() => new PropertyTypeCache());
/// </summary>
/// <value> /// <summary>
/// The default cache. /// Retrieves all properties.
/// </value> /// </summary>
public static Lazy<PropertyTypeCache> DefaultCache { get; } = new Lazy<PropertyTypeCache>(() => new PropertyTypeCache()); /// <typeparam name="T">The type to inspect.</typeparam>
/// <param name="onlyPublic">if set to <c>true</c> [only public].</param>
/// <summary> /// <returns>
/// Retrieves all properties. /// A collection with all the properties in the given type.
/// </summary> /// </returns>
/// <typeparam name="T">The type to inspect.</typeparam> public IEnumerable<PropertyInfo> RetrieveAllProperties<T>(Boolean onlyPublic = false) => this.Retrieve<T>(onlyPublic ? GetAllPublicPropertiesFunc() : GetAllPropertiesFunc());
/// <param name="onlyPublic">if set to <c>true</c> [only public].</param>
/// <returns> /// <summary>
/// A collection with all the properties in the given type. /// Retrieves all properties.
/// </returns> /// </summary>
public IEnumerable<PropertyInfo> RetrieveAllProperties<T>(bool onlyPublic = false) /// <param name="type">The type.</param>
=> Retrieve<T>(onlyPublic ? GetAllPublicPropertiesFunc() : GetAllPropertiesFunc()); /// <param name="onlyPublic">if set to <c>true</c> [only public].</param>
/// <returns>
/// <summary> /// A collection with all the properties in the given type.
/// Retrieves all properties. /// </returns>
/// </summary> public IEnumerable<PropertyInfo> RetrieveAllProperties(Type type, Boolean onlyPublic = false) => this.Retrieve(type, onlyPublic ? GetAllPublicPropertiesFunc() : GetAllPropertiesFunc());
/// <param name="type">The type.</param>
/// <param name="onlyPublic">if set to <c>true</c> [only public].</param> /// <summary>
/// <returns> /// Retrieves the filtered properties.
/// A collection with all the properties in the given type. /// </summary>
/// </returns> /// <param name="type">The type.</param>
public IEnumerable<PropertyInfo> RetrieveAllProperties(Type type, bool onlyPublic = false) /// <param name="onlyPublic">if set to <c>true</c> [only public].</param>
=> Retrieve(type, onlyPublic ? GetAllPublicPropertiesFunc() : GetAllPropertiesFunc()); /// <param name="filter">The filter.</param>
/// <returns>
/// <summary> /// A collection with all the properties in the given type.
/// Retrieves the filtered properties. /// </returns>
/// </summary> public IEnumerable<PropertyInfo> RetrieveFilteredProperties(Type type, Boolean onlyPublic, Func<PropertyInfo, Boolean> filter) => this.Retrieve(type, onlyPublic ? GetAllPublicPropertiesFunc(filter) : GetAllPropertiesFunc(filter));
/// <param name="type">The type.</param>
/// <param name="onlyPublic">if set to <c>true</c> [only public].</param> private static Func<Type, IEnumerable<PropertyInfo>> GetAllPropertiesFunc(Func<PropertyInfo, Boolean> filter = null) => GetPropertiesFunc(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, filter);
/// <param name="filter">The filter.</param>
/// <returns> private static Func<Type, IEnumerable<PropertyInfo>> GetAllPublicPropertiesFunc(Func<PropertyInfo, Boolean> filter = null) => GetPropertiesFunc(BindingFlags.Public | BindingFlags.Instance, filter);
/// A collection with all the properties in the given type.
/// </returns> private static Func<Type, IEnumerable<PropertyInfo>> GetPropertiesFunc(BindingFlags flags, Func<PropertyInfo, Boolean> filter = null) => t => t.GetProperties(flags).Where(filter ?? (p => p.CanRead || p.CanWrite));
public IEnumerable<PropertyInfo> RetrieveFilteredProperties( }
Type type,
bool onlyPublic,
Func<PropertyInfo, bool> filter)
=> Retrieve(type,
onlyPublic ? GetAllPublicPropertiesFunc(filter) : GetAllPropertiesFunc(filter));
private static Func<Type, IEnumerable<PropertyInfo>> GetAllPropertiesFunc(
Func<PropertyInfo, bool> filter = null)
=> GetPropertiesFunc(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
filter);
private static Func<Type, IEnumerable<PropertyInfo>> GetAllPublicPropertiesFunc(
Func<PropertyInfo, bool> filter = null)
=> GetPropertiesFunc(BindingFlags.Public | BindingFlags.Instance, filter);
private static Func<Type, IEnumerable<PropertyInfo>> GetPropertiesFunc(BindingFlags flags,
Func<PropertyInfo, bool> filter = null)
=> t => t.GetProperties(flags)
.Where(filter ?? (p => p.CanRead || p.CanWrite));
}
} }

View File

@ -3,76 +3,69 @@ using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using Swan.Collections; using Swan.Collections;
namespace Swan.Reflection namespace Swan.Reflection {
{ /// <summary>
/// A thread-safe cache of members belonging to a given type.
///
/// The Retrieve method is the most useful one in this class as it
/// calls the retrieval process if the type is not contained
/// in the cache.
/// </summary>
/// <typeparam name="T">The type of Member to be cached.</typeparam>
public abstract class TypeCache<T> : CollectionCacheRepository<T> {
/// <summary> /// <summary>
/// A thread-safe cache of members belonging to a given type. /// Determines whether the cache contains the specified type.
///
/// The Retrieve method is the most useful one in this class as it
/// calls the retrieval process if the type is not contained
/// in the cache.
/// </summary> /// </summary>
/// <typeparam name="T">The type of Member to be cached.</typeparam> /// <typeparam name="TOut">The type of the out.</typeparam>
public abstract class TypeCache<T> : CollectionCacheRepository<T> /// <returns>
{ /// <c>true</c> if [contains]; otherwise, <c>false</c>.
/// <summary> /// </returns>
/// Determines whether the cache contains the specified type. public Boolean Contains<TOut>() => this.ContainsKey(typeof(TOut));
/// </summary>
/// <typeparam name="TOut">The type of the out.</typeparam>
/// <returns>
/// <c>true</c> if [contains]; otherwise, <c>false</c>.
/// </returns>
public bool Contains<TOut>() => ContainsKey(typeof(TOut));
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="TOut">The type of the out.</typeparam>
/// <param name="factory">The factory.</param>
/// <returns>An array of the properties stored for the specified type.</returns>
public IEnumerable<T> Retrieve<TOut>(Func<Type, IEnumerable<T>> factory)
=> Retrieve(typeof(TOut), factory);
}
/// <summary> /// <summary>
/// A thread-safe cache of fields belonging to a given type /// Retrieves the properties stored for the specified type.
/// The Retrieve method is the most useful one in this class as it /// If the properties are not available, it calls the factory method to retrieve them
/// calls the retrieval process if the type is not contained /// and returns them as an array of PropertyInfo.
/// in the cache.
/// </summary> /// </summary>
public class FieldTypeCache : TypeCache<FieldInfo> /// <typeparam name="TOut">The type of the out.</typeparam>
{ /// <param name="factory">The factory.</param>
/// <summary> /// <returns>An array of the properties stored for the specified type.</returns>
/// Gets the default cache. public IEnumerable<T> Retrieve<TOut>(Func<Type, IEnumerable<T>> factory) => this.Retrieve(typeof(TOut), factory);
/// </summary> }
/// <value>
/// The default cache. /// <summary>
/// </value> /// A thread-safe cache of fields belonging to a given type
public static Lazy<FieldTypeCache> DefaultCache { get; } = new Lazy<FieldTypeCache>(() => new FieldTypeCache()); /// The Retrieve method is the most useful one in this class as it
/// calls the retrieval process if the type is not contained
/// <summary> /// in the cache.
/// Retrieves all fields. /// </summary>
/// </summary> public class FieldTypeCache : TypeCache<FieldInfo> {
/// <typeparam name="T">The type to inspect.</typeparam> /// <summary>
/// <returns> /// Gets the default cache.
/// A collection with all the fields in the given type. /// </summary>
/// </returns> /// <value>
public IEnumerable<FieldInfo> RetrieveAllFields<T>() /// The default cache.
=> Retrieve<T>(GetAllFieldsFunc()); /// </value>
public static Lazy<FieldTypeCache> DefaultCache { get; } = new Lazy<FieldTypeCache>(() => new FieldTypeCache());
/// <summary>
/// Retrieves all fields. /// <summary>
/// </summary> /// Retrieves all fields.
/// <param name="type">The type.</param> /// </summary>
/// <returns> /// <typeparam name="T">The type to inspect.</typeparam>
/// A collection with all the fields in the given type. /// <returns>
/// </returns> /// A collection with all the fields in the given type.
public IEnumerable<FieldInfo> RetrieveAllFields(Type type) /// </returns>
=> Retrieve(type, GetAllFieldsFunc()); public IEnumerable<FieldInfo> RetrieveAllFields<T>() => this.Retrieve<T>(GetAllFieldsFunc());
private static Func<Type, IEnumerable<FieldInfo>> GetAllFieldsFunc() /// <summary>
=> t => t.GetFields(BindingFlags.Public | BindingFlags.Instance); /// Retrieves all fields.
} /// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// A collection with all the fields in the given type.
/// </returns>
public IEnumerable<FieldInfo> RetrieveAllFields(Type type) => this.Retrieve(type, GetAllFieldsFunc());
private static Func<Type, IEnumerable<FieldInfo>> GetAllFieldsFunc() => t => t.GetFields(BindingFlags.Public | BindingFlags.Instance);
}
} }

View File

@ -1,59 +1,54 @@
using System; using System;
namespace Swan namespace Swan {
{ /// <summary>
/// Represents a singleton pattern abstract class.
/// </summary>
/// <typeparam name="T">The type of class.</typeparam>
public abstract class SingletonBase<T> : IDisposable where T : class {
/// <summary> /// <summary>
/// Represents a singleton pattern abstract class. /// The static, singleton instance reference.
/// </summary> /// </summary>
/// <typeparam name="T">The type of class.</typeparam> protected static readonly Lazy<T> LazyInstance = new Lazy<T>(valueFactory: () => Activator.CreateInstance(typeof(T), true) as T, isThreadSafe: true);
public abstract class SingletonBase<T> : IDisposable
where T : class private Boolean _isDisposing; // To detect redundant calls
{
/// <summary> /// <summary>
/// The static, singleton instance reference. /// Gets the instance that this singleton represents.
/// </summary> /// If the instance is null, it is constructed and assigned when this member is accessed.
protected static readonly Lazy<T> LazyInstance = new Lazy<T>( /// </summary>
valueFactory: () => Activator.CreateInstance(typeof(T), true) as T, /// <value>
isThreadSafe: true); /// The instance.
/// </value>
private bool _isDisposing; // To detect redundant calls public static T Instance => LazyInstance.Value;
/// <summary> /// <inheritdoc />
/// Gets the instance that this singleton represents. public void Dispose() => this.Dispose(true);
/// If the instance is null, it is constructed and assigned when this member is accessed.
/// </summary> /// <summary>
/// <value> /// Releases unmanaged and - optionally - managed resources.
/// The instance. /// Call the GC.SuppressFinalize if you override this method and use
/// </value> /// a non-default class finalizer (destructor).
public static T Instance => LazyInstance.Value; /// </summary>
/// <param name="disposeManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
/// <inheritdoc /> protected virtual void Dispose(Boolean disposeManaged) {
public void Dispose() => Dispose(true); if(this._isDisposing) {
return;
/// <summary> }
/// Releases unmanaged and - optionally - managed resources.
/// Call the GC.SuppressFinalize if you override this method and use this._isDisposing = true;
/// a non-default class finalizer (destructor).
/// </summary> // free managed resources
/// <param name="disposeManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> if(LazyInstance == null) {
protected virtual void Dispose(bool disposeManaged) return;
{ }
if (_isDisposing) return;
try {
_isDisposing = true; IDisposable disposableInstance = LazyInstance.Value as IDisposable;
disposableInstance?.Dispose();
// free managed resources } catch {
if (LazyInstance == null) return; // swallow
}
try }
{ }
var disposableInstance = LazyInstance.Value as IDisposable;
disposableInstance?.Dispose();
}
catch
{
// swallow
}
}
}
} }

View File

@ -1,76 +1,62 @@
using System; using System;
using System.Runtime.Serialization; using System.Runtime.Serialization;
namespace Swan namespace Swan {
{ /// <summary>
/// The exception that is thrown when a conversion from a string to a
/// specified type fails.
/// </summary>
/// <seealso cref="FromString" />
[Serializable]
public class StringConversionException : Exception {
/// <summary> /// <summary>
/// The exception that is thrown when a conversion from a string to a /// Initializes a new instance of the <see cref="StringConversionException"/> class.
/// specified type fails.
/// </summary> /// </summary>
/// <seealso cref="FromString" /> public StringConversionException() {
[Serializable] }
public class StringConversionException : Exception
{ /// <summary>
/// <summary> /// Initializes a new instance of the <see cref="StringConversionException"/> class.
/// Initializes a new instance of the <see cref="StringConversionException"/> class. /// </summary>
/// </summary> /// <param name="message">The error message that explains the reason for the exception.</param>
public StringConversionException() public StringConversionException(String message) : base(message) {
{ }
}
/// <summary>
/// <summary> /// Initializes a new instance of the <see cref="StringConversionException"/> class.
/// Initializes a new instance of the <see cref="StringConversionException"/> class. /// </summary>
/// </summary> /// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="message">The error message that explains the reason for the exception.</param> /// <param name="innerException">The exception that is the cause of the current exception,
public StringConversionException(string message) /// or <see langword="null" /> if no inner exception is specified.</param>
: base(message) public StringConversionException(String message, Exception innerException) : base(message, innerException) {
{ }
}
/// <summary>
/// <summary> /// Initializes a new instance of the <see cref="StringConversionException"/> class.
/// Initializes a new instance of the <see cref="StringConversionException"/> class. /// </summary>
/// </summary> /// <param name="type">The desired resulting type of the attempted conversion.</param>
/// <param name="message">The error message that explains the reason for the exception.</param> public StringConversionException(Type type) : base(BuildStandardMessageForType(type)) {
/// <param name="innerException">The exception that is the cause of the current exception, }
/// or <see langword="null" /> if no inner exception is specified.</param>
public StringConversionException(string message, Exception innerException) /// <summary>
: base(message, innerException) /// Initializes a new instance of the <see cref="StringConversionException"/> class.
{ /// </summary>
} /// <param name="type">The desired resulting type of the attempted conversion.</param>
/// <param name="innerException">The exception that is the cause of the current exception,
/// <summary> /// or <see langword="null" /> if no inner exception is specified.</param>
/// Initializes a new instance of the <see cref="StringConversionException"/> class. public StringConversionException(Type type, Exception innerException) : base(BuildStandardMessageForType(type), innerException) {
/// </summary> }
/// <param name="type">The desired resulting type of the attempted conversion.</param>
public StringConversionException(Type type) /// <summary>
: base(BuildStandardMessageForType(type)) /// Initializes a new instance of the <see cref="StringConversionException"/> class.
{ /// </summary>
} /// <param name="info">The <see cref="SerializationInfo" /> that holds the serialized object data
/// about the exception being thrown.</param>
/// <summary> /// <param name="context">The <see cref="StreamingContext" /> that contains contextual information
/// Initializes a new instance of the <see cref="StringConversionException"/> class. /// about the source or destination.</param>
/// </summary> protected StringConversionException(SerializationInfo info, StreamingContext context) : base(info, context) {
/// <param name="type">The desired resulting type of the attempted conversion.</param> }
/// <param name="innerException">The exception that is the cause of the current exception,
/// or <see langword="null" /> if no inner exception is specified.</param> private static String BuildStandardMessageForType(Type type) => $"Cannot convert a string to an instance of {type.FullName}";
public StringConversionException(Type type, Exception innerException) }
: base(BuildStandardMessageForType(type), innerException)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="StringConversionException"/> class.
/// </summary>
/// <param name="info">The <see cref="SerializationInfo" /> that holds the serialized object data
/// about the exception being thrown.</param>
/// <param name="context">The <see cref="StreamingContext" /> that contains contextual information
/// about the source or destination.</param>
protected StringConversionException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
private static string BuildStandardMessageForType(Type type)
=> $"Cannot convert a string to an instance of {type.FullName}";
}
} }

View File

@ -1,30 +1,27 @@
using System; using System;
namespace Swan namespace Swan {
{ /// <summary>
/// An attribute used to help conversion structs back and forth into arrays of bytes via
/// extension methods included in this library ToStruct and ToBytes.
/// </summary>
/// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Struct)]
public class StructEndiannessAttribute : Attribute {
/// <summary> /// <summary>
/// An attribute used to help conversion structs back and forth into arrays of bytes via /// Initializes a new instance of the <see cref="StructEndiannessAttribute"/> class.
/// extension methods included in this library ToStruct and ToBytes.
/// </summary> /// </summary>
/// <seealso cref="Attribute" /> /// <param name="endianness">The endianness.</param>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Struct)] public StructEndiannessAttribute(Endianness endianness) => this.Endianness = endianness;
public class StructEndiannessAttribute : Attribute
{ /// <summary>
/// <summary> /// Gets the endianness.
/// Initializes a new instance of the <see cref="StructEndiannessAttribute"/> class. /// </summary>
/// </summary> /// <value>
/// <param name="endianness">The endianness.</param> /// The endianness.
public StructEndiannessAttribute(Endianness endianness) /// </value>
{ public Endianness Endianness {
Endianness = endianness; get;
} }
}
/// <summary>
/// Gets the endianness.
/// </summary>
/// <value>
/// The endianness.
/// </value>
public Endianness Endianness { get; }
}
} }

View File

@ -13,6 +13,5 @@
<PackageLicenseUrl>https://raw.githubusercontent.com/unosquare/swan/master/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://raw.githubusercontent.com/unosquare/swan/master/LICENSE</PackageLicenseUrl>
<PackageTags>best-practices netcore network objectmapper json-serialization</PackageTags> <PackageTags>best-practices netcore network objectmapper json-serialization</PackageTags>
<LangVersion>8.0</LangVersion> <LangVersion>8.0</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View File

@ -4,230 +4,197 @@ using System.IO;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
namespace Swan namespace Swan {
{ /// <summary>
/// Provides utility methods to retrieve information about the current application.
/// </summary>
public static class SwanRuntime {
private static readonly Lazy<Assembly> EntryAssemblyLazy = new Lazy<Assembly>(Assembly.GetEntryAssembly);
private static readonly Lazy<String> CompanyNameLazy = new Lazy<String>(() => {
AssemblyCompanyAttribute attribute = EntryAssembly.GetCustomAttribute(typeof(AssemblyCompanyAttribute)) as AssemblyCompanyAttribute;
return attribute?.Company ?? String.Empty;
});
private static readonly Lazy<String> ProductNameLazy = new Lazy<String>(() => {
AssemblyProductAttribute attribute = EntryAssembly.GetCustomAttribute(typeof(AssemblyProductAttribute)) as AssemblyProductAttribute;
return attribute?.Product ?? String.Empty;
});
private static readonly Lazy<String> ProductTrademarkLazy = new Lazy<String>(() => {
AssemblyTrademarkAttribute attribute = EntryAssembly.GetCustomAttribute(typeof(AssemblyTrademarkAttribute)) as AssemblyTrademarkAttribute;
return attribute?.Trademark ?? String.Empty;
});
private static readonly String ApplicationMutexName = "Global\\{{" + EntryAssembly.FullName + "}}";
private static readonly Object SyncLock = new Object();
private static OperatingSystem? _oS;
#region Properties
/// <summary> /// <summary>
/// Provides utility methods to retrieve information about the current application. /// Gets the current Operating System.
/// </summary> /// </summary>
public static class SwanRuntime /// <value>
{ /// The os.
private static readonly Lazy<Assembly> EntryAssemblyLazy = new Lazy<Assembly>(Assembly.GetEntryAssembly); /// </value>
public static OperatingSystem OS {
private static readonly Lazy<string> CompanyNameLazy = new Lazy<string>(() => get {
{ if(_oS.HasValue == false) {
var attribute = String windowsDirectory = Environment.GetEnvironmentVariable("windir");
EntryAssembly.GetCustomAttribute(typeof(AssemblyCompanyAttribute)) as AssemblyCompanyAttribute; _oS = String.IsNullOrEmpty(windowsDirectory) == false && windowsDirectory.Contains(@"\") && Directory.Exists(windowsDirectory)
return attribute?.Company ?? string.Empty; ? (OperatingSystem?)OperatingSystem.Windows
}); : (OperatingSystem?)(File.Exists(@"/proc/sys/kernel/ostype") ? OperatingSystem.Unix : OperatingSystem.Osx);
}
private static readonly Lazy<string> ProductNameLazy = new Lazy<string>(() =>
{ return _oS ?? OperatingSystem.Unknown;
var attribute = }
EntryAssembly.GetCustomAttribute(typeof(AssemblyProductAttribute)) as AssemblyProductAttribute; }
return attribute?.Product ?? string.Empty;
});
/// <summary>
private static readonly Lazy<string> ProductTrademarkLazy = new Lazy<string>(() => /// Checks if this application (including version number) is the only instance currently running.
{ /// </summary>
var attribute = /// <value>
EntryAssembly.GetCustomAttribute(typeof(AssemblyTrademarkAttribute)) as AssemblyTrademarkAttribute; /// <c>true</c> if this instance is the only instance; otherwise, <c>false</c>.
return attribute?.Trademark ?? string.Empty; /// </value>
}); [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
public static Boolean IsTheOnlyInstance {
private static readonly string ApplicationMutexName = "Global\\{{" + EntryAssembly.FullName + "}}"; get {
lock(SyncLock) {
private static readonly object SyncLock = new object(); try {
// Try to open existing mutex.
private static OperatingSystem? _oS; _ = Mutex.OpenExisting(ApplicationMutexName);
} catch {
#region Properties try {
// If exception occurred, there is no such mutex.
/// <summary> Mutex appMutex = new Mutex(true, ApplicationMutexName);
/// Gets the current Operating System. $"Application Mutex created {appMutex} named '{ApplicationMutexName}'".Debug(typeof(SwanRuntime));
/// </summary>
/// <value> // Only one instance.
/// The os. return true;
/// </value> } catch {
public static OperatingSystem OS // Sometimes the user can't create the Global Mutex
{ }
get }
{
if (_oS.HasValue == false) // More than one instance.
{ return false;
var windowsDirectory = Environment.GetEnvironmentVariable("windir"); }
if (string.IsNullOrEmpty(windowsDirectory) == false }
&& windowsDirectory.Contains(@"\") }
&& Directory.Exists(windowsDirectory))
{ /// <summary>
_oS = OperatingSystem.Windows; /// Gets a value indicating whether this application instance is using the MONO runtime.
} /// </summary>
else /// <value>
{ /// <c>true</c> if this instance is using MONO runtime; otherwise, <c>false</c>.
_oS = File.Exists(@"/proc/sys/kernel/ostype") ? OperatingSystem.Unix : OperatingSystem.Osx; /// </value>
} public static Boolean IsUsingMonoRuntime => Type.GetType("Mono.Runtime") != null;
}
/// <summary>
return _oS ?? OperatingSystem.Unknown; /// Gets the assembly that started the application.
} /// </summary>
} /// <value>
/// The entry assembly.
/// <summary> /// </value>
/// Checks if this application (including version number) is the only instance currently running. public static Assembly EntryAssembly => EntryAssemblyLazy.Value;
/// </summary>
/// <value> /// <summary>
/// <c>true</c> if this instance is the only instance; otherwise, <c>false</c>. /// Gets the name of the entry assembly.
/// </value> /// </summary>
public static bool IsTheOnlyInstance /// <value>
{ /// The name of the entry assembly.
get /// </value>
{ public static AssemblyName EntryAssemblyName => EntryAssemblyLazy.Value.GetName();
lock (SyncLock)
{ /// <summary>
try /// Gets the entry assembly version.
{ /// </summary>
// Try to open existing mutex. public static Version EntryAssemblyVersion => EntryAssemblyName.Version;
Mutex.OpenExisting(ApplicationMutexName);
} /// <summary>
catch /// Gets the full path to the folder containing the assembly that started the application.
{ /// </summary>
try /// <value>
{ /// The entry assembly directory.
// If exception occurred, there is no such mutex. /// </value>
var appMutex = new Mutex(true, ApplicationMutexName); public static String EntryAssemblyDirectory {
$"Application Mutex created {appMutex} named '{ApplicationMutexName}'".Debug( get {
typeof(SwanRuntime)); UriBuilder uri = new UriBuilder(EntryAssembly.CodeBase);
String path = Uri.UnescapeDataString(uri.Path);
// Only one instance. return Path.GetDirectoryName(path);
return true; }
} }
catch
{ /// <summary>
// Sometimes the user can't create the Global Mutex /// Gets the name of the company.
} /// </summary>
} /// <value>
/// The name of the company.
// More than one instance. /// </value>
return false; public static String CompanyName => CompanyNameLazy.Value;
}
} /// <summary>
} /// Gets the name of the product.
/// </summary>
/// <summary> /// <value>
/// Gets a value indicating whether this application instance is using the MONO runtime. /// The name of the product.
/// </summary> /// </value>
/// <value> public static String ProductName => ProductNameLazy.Value;
/// <c>true</c> if this instance is using MONO runtime; otherwise, <c>false</c>.
/// </value> /// <summary>
public static bool IsUsingMonoRuntime => Type.GetType("Mono.Runtime") != null; /// Gets the trademark.
/// </summary>
/// <summary> /// <value>
/// Gets the assembly that started the application. /// The product trademark.
/// </summary> /// </value>
/// <value> public static String ProductTrademark => ProductTrademarkLazy.Value;
/// The entry assembly.
/// </value> /// <summary>
public static Assembly EntryAssembly => EntryAssemblyLazy.Value; /// Gets a local storage path with a version.
/// </summary>
/// <summary> /// <value>
/// Gets the name of the entry assembly. /// The local storage path.
/// </summary> /// </value>
/// <value> public static String LocalStoragePath {
/// The name of the entry assembly. get {
/// </value> String localAppDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), EntryAssemblyName.Name);
public static AssemblyName EntryAssemblyName => EntryAssemblyLazy.Value.GetName();
String returnPath = Path.Combine(localAppDataPath, EntryAssemblyVersion.ToString());
/// <summary>
/// Gets the entry assembly version. if(!Directory.Exists(returnPath)) {
/// </summary> _ = Directory.CreateDirectory(returnPath);
public static Version EntryAssemblyVersion => EntryAssemblyName.Version; }
/// <summary> return returnPath;
/// Gets the full path to the folder containing the assembly that started the application. }
/// </summary> }
/// <value>
/// The entry assembly directory. #endregion
/// </value>
public static string EntryAssemblyDirectory #region Methods
{
get /// <summary>
{ /// Build a full path pointing to the current user's desktop with the given filename.
var uri = new UriBuilder(EntryAssembly.CodeBase); /// </summary>
var path = Uri.UnescapeDataString(uri.Path); /// <param name="filename">The filename.</param>
return Path.GetDirectoryName(path); /// <returns>
} /// The fully qualified location of path, such as "C:\MyFile.txt".
} /// </returns>
/// <exception cref="ArgumentNullException">filename.</exception>
/// <summary> public static String GetDesktopFilePath(String filename) {
/// Gets the name of the company. if(String.IsNullOrWhiteSpace(filename)) {
/// </summary> throw new ArgumentNullException(nameof(filename));
/// <value> }
/// The name of the company.
/// </value> String pathWithFilename = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), filename);
public static string CompanyName => CompanyNameLazy.Value;
return Path.GetFullPath(pathWithFilename);
/// <summary> }
/// Gets the name of the product.
/// </summary> #endregion
/// <value> }
/// The name of the product.
/// </value>
public static string ProductName => ProductNameLazy.Value;
/// <summary>
/// Gets the trademark.
/// </summary>
/// <value>
/// The product trademark.
/// </value>
public static string ProductTrademark => ProductTrademarkLazy.Value;
/// <summary>
/// Gets a local storage path with a version.
/// </summary>
/// <value>
/// The local storage path.
/// </value>
public static string LocalStoragePath
{
get
{
var localAppDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
EntryAssemblyName.Name);
var returnPath = Path.Combine(localAppDataPath, EntryAssemblyVersion.ToString());
if (!Directory.Exists(returnPath))
{
Directory.CreateDirectory(returnPath);
}
return returnPath;
}
}
#endregion
#region Methods
/// <summary>
/// Build a full path pointing to the current user's desktop with the given filename.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>
/// The fully qualified location of path, such as "C:\MyFile.txt".
/// </returns>
/// <exception cref="ArgumentNullException">filename.</exception>
public static string GetDesktopFilePath(string filename)
{
if (string.IsNullOrWhiteSpace(filename))
throw new ArgumentNullException(nameof(filename));
var pathWithFilename = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory),
filename);
return Path.GetFullPath(pathWithFilename);
}
#endregion
}
} }

View File

@ -1,37 +1,36 @@
namespace Swan using System;
{
namespace Swan {
/// <summary>
/// A console terminal helper to create nicer output and receive input from the user
/// This class is thread-safe :).
/// </summary>
public static partial class Terminal {
/// <summary> /// <summary>
/// A console terminal helper to create nicer output and receive input from the user /// Represents a Table to print in console.
/// This class is thread-safe :).
/// </summary> /// </summary>
public static partial class Terminal private static class Table {
{ public static void Vertical() => Write('\u2502', Settings.BorderColor);
/// <summary>
/// Represents a Table to print in console. public static void RightTee() => Write('\u2524', Settings.BorderColor);
/// </summary>
private static class Table public static void TopRight() => Write('\u2510', Settings.BorderColor);
{
public static void Vertical() => Write('\u2502', Settings.BorderColor); public static void BottomLeft() => Write('\u2514', Settings.BorderColor);
public static void RightTee() => Write('\u2524', Settings.BorderColor); public static void BottomTee() => Write('\u2534', Settings.BorderColor);
public static void TopRight() => Write('\u2510', Settings.BorderColor); public static void TopTee() => Write('\u252c', Settings.BorderColor);
public static void BottomLeft() => Write('\u2514', Settings.BorderColor); public static void LeftTee() => Write('\u251c', Settings.BorderColor);
public static void BottomTee() => Write('\u2534', Settings.BorderColor); public static void Horizontal(Int32 length) => Write(new String('\u2500', length), Settings.BorderColor);
public static void TopTee() => Write('\u252c', Settings.BorderColor); public static void Tee() => Write('\u253c', Settings.BorderColor);
public static void LeftTee() => Write('\u251c', Settings.BorderColor); public static void BottomRight() => Write('\u2518', Settings.BorderColor);
public static void Horizontal(int length) => Write(new string('\u2500', length), Settings.BorderColor); public static void TopLeft() => Write('\u250C', Settings.BorderColor);
}
public static void Tee() => Write('\u253c', Settings.BorderColor); }
public static void BottomRight() => Write('\u2518', Settings.BorderColor);
public static void TopLeft() => Write('\u250C', Settings.BorderColor);
}
}
} }

View File

@ -1,261 +1,238 @@
using System; #nullable enable
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Swan.Lite.Logging;
using System.Globalization; using System.Globalization;
using Swan.Logging;
using Swan.Lite.Logging;
namespace Swan namespace Swan {
{ /// <summary>
/// A console terminal helper to create nicer output and receive input from the user
/// This class is thread-safe :).
/// </summary>
public static partial class Terminal {
#region ReadKey
/// <summary> /// <summary>
/// A console terminal helper to create nicer output and receive input from the user /// Reads a key from the Terminal. This is the closest equivalent to Console.ReadKey.
/// This class is thread-safe :).
/// </summary> /// </summary>
public static partial class Terminal /// <param name="intercept">if set to <c>true</c> the pressed key will not be rendered to the output.</param>
{ /// <param name="disableLocking">if set to <c>true</c> the output will continue to be shown.
#region ReadKey /// This is useful for services and daemons that are running as console applications and wait for a key to exit the program.</param>
/// <returns>The console key information.</returns>
/// <summary> public static ConsoleKeyInfo ReadKey(Boolean intercept, Boolean disableLocking = false) {
/// Reads a key from the Terminal. This is the closest equivalent to Console.ReadKey. if(!IsConsolePresent) {
/// </summary> return default;
/// <param name="intercept">if set to <c>true</c> the pressed key will not be rendered to the output.</param> }
/// <param name="disableLocking">if set to <c>true</c> the output will continue to be shown.
/// This is useful for services and daemons that are running as console applications and wait for a key to exit the program.</param> if(disableLocking) {
/// <returns>The console key information.</returns> return Console.ReadKey(intercept);
public static ConsoleKeyInfo ReadKey(bool intercept, bool disableLocking = false) }
{
if (!IsConsolePresent) return default; lock(SyncLock) {
if (disableLocking) return Console.ReadKey(intercept); Flush();
InputDone.Reset();
lock (SyncLock)
{ try {
Flush(); Console.CursorVisible = true;
InputDone.Reset(); return Console.ReadKey(intercept);
} finally {
try Console.CursorVisible = false;
{ InputDone.Set();
Console.CursorVisible = true; }
return Console.ReadKey(intercept); }
} }
finally
{ /// <summary>
Console.CursorVisible = false; /// Reads a key from the Terminal.
InputDone.Set(); /// </summary>
} /// <param name="prompt">The prompt.</param>
} /// <param name="preventEcho">if set to <c>true</c> [prevent echo].</param>
} /// <returns>The console key information.</returns>
public static ConsoleKeyInfo ReadKey(String prompt, Boolean preventEcho = true) {
/// <summary> if(!IsConsolePresent) {
/// Reads a key from the Terminal. return default;
/// </summary> }
/// <param name="prompt">The prompt.</param>
/// <param name="preventEcho">if set to <c>true</c> [prevent echo].</param> lock(SyncLock) {
/// <returns>The console key information.</returns> if(prompt != null) {
public static ConsoleKeyInfo ReadKey(string prompt, bool preventEcho = true) Write($"{GetNowFormatted()}{Settings.UserInputPrefix} << {prompt} ", ConsoleColor.White);
{ }
if (!IsConsolePresent) return default;
ConsoleKeyInfo input = ReadKey(true);
lock (SyncLock) String echo = preventEcho ? String.Empty : input.Key.ToString();
{ WriteLine(echo);
if (prompt != null) return input;
{ }
Write($"{GetNowFormatted()}{Settings.UserInputPrefix} << {prompt} ", ConsoleColor.White); }
}
#endregion
var input = ReadKey(true);
var echo = preventEcho ? string.Empty : input.Key.ToString(); #region Other Terminal Read Methods
WriteLine(echo);
return input; /// <summary>
} /// Clears the screen.
} /// </summary>
public static void Clear() {
#endregion Flush();
Console.Clear();
#region Other Terminal Read Methods }
/// <summary> /// <summary>
/// Clears the screen. /// Reads a line of text from the console.
/// </summary> /// </summary>
public static void Clear() /// <returns>The read line.</returns>
{ public static String? ReadLine() {
Flush(); if(IsConsolePresent == false) {
Console.Clear(); return default;
} }
/// <summary> lock(SyncLock) {
/// Reads a line of text from the console. Flush();
/// </summary> InputDone.Reset();
/// <returns>The read line.</returns>
public static string? ReadLine() try {
{ Console.CursorVisible = true;
if (IsConsolePresent == false) return default; return Console.ReadLine();
} finally {
lock (SyncLock) Console.CursorVisible = false;
{ InputDone.Set();
Flush(); }
InputDone.Reset(); }
}
try
{ /// <summary>
Console.CursorVisible = true; /// Reads a line from the input.
return Console.ReadLine(); /// </summary>
} /// <param name="prompt">The prompt.</param>
finally /// <returns>The read line.</returns>
{ public static String? ReadLine(String prompt) {
Console.CursorVisible = false; if(!IsConsolePresent) {
InputDone.Set(); return null;
} }
}
} lock(SyncLock) {
Write($"{GetNowFormatted()}{Settings.UserInputPrefix} << {prompt}: ", ConsoleColor.White);
/// <summary>
/// Reads a line from the input. return ReadLine();
/// </summary> }
/// <param name="prompt">The prompt.</param> }
/// <returns>The read line.</returns>
public static string? ReadLine(string prompt) /// <summary>
{ /// Reads a number from the input. If unable to parse, it returns the default number.
if (!IsConsolePresent) return null; /// </summary>
/// <param name="prompt">The prompt.</param>
lock (SyncLock) /// <param name="defaultNumber">The default number.</param>
{ /// <returns>
Write($"{GetNowFormatted()}{Settings.UserInputPrefix} << {prompt}: ", ConsoleColor.White); /// Conversions of string representation of a number to its 32-bit signed integer equivalent.
/// </returns>
return ReadLine(); public static Int32 ReadNumber(String prompt, Int32 defaultNumber) {
} if(!IsConsolePresent) {
} return defaultNumber;
}
/// <summary>
/// Reads a number from the input. If unable to parse, it returns the default number. lock(SyncLock) {
/// </summary> Write($"{GetNowFormatted()}{Settings.UserInputPrefix} << {prompt} (default is {defaultNumber}): ", ConsoleColor.White);
/// <param name="prompt">The prompt.</param>
/// <param name="defaultNumber">The default number.</param> String? input = ReadLine();
/// <returns> return Int32.TryParse(input, out Int32 parsedInt) ? parsedInt : defaultNumber;
/// Conversions of string representation of a number to its 32-bit signed integer equivalent. }
/// </returns> }
public static int ReadNumber(string prompt, int defaultNumber)
{ /// <summary>
if (!IsConsolePresent) return defaultNumber; /// Creates a table prompt where the user can enter an option based on the options dictionary provided.
/// </summary>
lock (SyncLock) /// <param name="title">The title.</param>
{ /// <param name="options">The options.</param>
Write($"{GetNowFormatted()}{Settings.UserInputPrefix} << {prompt} (default is {defaultNumber}): ", /// <param name="anyKeyOption">Any key option.</param>
ConsoleColor.White); /// <returns>
/// A value that identifies the console key that was pressed.
var input = ReadLine(); /// </returns>
return int.TryParse(input, out var parsedInt) ? parsedInt : defaultNumber; /// <exception cref="ArgumentNullException">options.</exception>
} public static ConsoleKeyInfo ReadPrompt(String title, IDictionary<ConsoleKey, String> options, String anyKeyOption) {
} if(!IsConsolePresent) {
return default;
/// <summary> }
/// Creates a table prompt where the user can enter an option based on the options dictionary provided.
/// </summary> if(options == null) {
/// <param name="title">The title.</param> throw new ArgumentNullException(nameof(options));
/// <param name="options">The options.</param> }
/// <param name="anyKeyOption">Any key option.</param>
/// <returns> const ConsoleColor textColor = ConsoleColor.White;
/// A value that identifies the console key that was pressed. Int32 lineLength = Console.WindowWidth;
/// </returns> Int32 lineAlign = -(lineLength - 2);
/// <exception cref="ArgumentNullException">options.</exception> String textFormat = "{0," + lineAlign + "}";
public static ConsoleKeyInfo ReadPrompt(
string title, // lock the output as an atomic operation
IDictionary<ConsoleKey, string> options, lock(SyncLock) {
string anyKeyOption) {
{ // Top border
if (!IsConsolePresent) return default; Table.TopLeft();
Table.Horizontal(-lineAlign);
if (options == null) Table.TopRight();
throw new ArgumentNullException(nameof(options)); }
const ConsoleColor textColor = ConsoleColor.White; {
var lineLength = Console.WindowWidth; // Title
var lineAlign = -(lineLength - 2); Table.Vertical();
var textFormat = "{0," + lineAlign + "}"; String titleText = String.Format(CultureInfo.CurrentCulture, textFormat, String.IsNullOrWhiteSpace(title) ? " Select an option from the list below." : $" {title}");
Write(titleText, textColor);
// lock the output as an atomic operation Table.Vertical();
lock (SyncLock) }
{
{ {
// Top border // Title Bottom
Table.TopLeft(); Table.LeftTee();
Table.Horizontal(-lineAlign); Table.Horizontal(lineLength - 2);
Table.TopRight(); Table.RightTee();
} }
{ // Options
// Title foreach(KeyValuePair<ConsoleKey, String> kvp in options) {
Table.Vertical(); Table.Vertical();
var titleText = string.Format(CultureInfo.CurrentCulture, Write(String.Format(CultureInfo.CurrentCulture, textFormat, $" {"[ " + kvp.Key + " ]",-10} {kvp.Value}"), textColor);
textFormat, Table.Vertical();
string.IsNullOrWhiteSpace(title) ? " Select an option from the list below." : $" {title}"); }
Write(titleText, textColor);
Table.Vertical(); // Any Key Options
} if(String.IsNullOrWhiteSpace(anyKeyOption) == false) {
Table.Vertical();
{ Write(String.Format(CultureInfo.CurrentCulture, textFormat, " "), ConsoleColor.Gray);
// Title Bottom Table.Vertical();
Table.LeftTee();
Table.Horizontal(lineLength - 2); Table.Vertical();
Table.RightTee(); Write(String.Format(CultureInfo.CurrentCulture, textFormat, $" {" ",-10} {anyKeyOption}"), ConsoleColor.Gray);
} Table.Vertical();
}
// Options
foreach (var kvp in options) {
{ // Input section
Table.Vertical(); Table.LeftTee();
Write(string.Format( Table.Horizontal(lineLength - 2);
CultureInfo.CurrentCulture, Table.RightTee();
textFormat,
$" {"[ " + kvp.Key + " ]",-10} {kvp.Value}"), Table.Vertical();
textColor); Write(String.Format(CultureInfo.CurrentCulture, textFormat, Settings.UserOptionText), ConsoleColor.Green);
Table.Vertical(); Table.Vertical();
}
Table.BottomLeft();
// Any Key Options Table.Horizontal(lineLength - 2);
if (string.IsNullOrWhiteSpace(anyKeyOption) == false) Table.BottomRight();
{ }
Table.Vertical(); }
Write(string.Format(CultureInfo.CurrentCulture, textFormat, " "), ConsoleColor.Gray);
Table.Vertical(); Int32 inputLeft = Settings.UserOptionText.Length + 3;
Table.Vertical(); SetCursorPosition(inputLeft, CursorTop - 1);
Write(string.Format( ConsoleKeyInfo userInput = ReadKey(true);
CultureInfo.CurrentCulture, Write(userInput.Key.ToString(), ConsoleColor.Gray);
textFormat,
$" {" ",-10} {anyKeyOption}"), SetCursorPosition(0, CursorTop + 2);
ConsoleColor.Gray); return userInput;
Table.Vertical(); }
}
#endregion
{
// Input section private static String GetNowFormatted() => $" {(String.IsNullOrWhiteSpace(TextLogger.LoggingTimeFormat) ? String.Empty : DateTime.Now.ToString(TextLogger.LoggingTimeFormat) + " ")}";
Table.LeftTee(); }
Table.Horizontal(lineLength - 2);
Table.RightTee();
Table.Vertical();
Write(string.Format(CultureInfo.CurrentCulture, textFormat, Settings.UserOptionText),
ConsoleColor.Green);
Table.Vertical();
Table.BottomLeft();
Table.Horizontal(lineLength - 2);
Table.BottomRight();
}
}
var inputLeft = Settings.UserOptionText.Length + 3;
SetCursorPosition(inputLeft, CursorTop - 1);
var userInput = ReadKey(true);
Write(userInput.Key.ToString(), ConsoleColor.Gray);
SetCursorPosition(0, CursorTop + 2);
return userInput;
}
#endregion
private static string GetNowFormatted() =>
$" {(string.IsNullOrWhiteSpace(TextLogger.LoggingTimeFormat) ? string.Empty : DateTime.Now.ToString(TextLogger.LoggingTimeFormat) + " ")}";
}
} }

View File

@ -1,97 +1,88 @@
using System; #nullable enable
using System;
namespace Swan namespace Swan {
{ /// <summary>
/// A console terminal helper to create nicer output and receive input from the user
/// This class is thread-safe :).
/// </summary>
public static partial class Terminal {
/// <summary> /// <summary>
/// A console terminal helper to create nicer output and receive input from the user /// Writes a character a number of times, optionally adding a new line at the end.
/// This class is thread-safe :).
/// </summary> /// </summary>
public static partial class Terminal /// <param name="charCode">The character code.</param>
{ /// <param name="color">The color.</param>
/// <summary> /// <param name="count">The count.</param>
/// Writes a character a number of times, optionally adding a new line at the end. /// <param name="newLine">if set to <c>true</c> [new line].</param>
/// </summary> /// <param name="writerFlags">The writer flags.</param>
/// <param name="charCode">The character code.</param> public static void Write(Char charCode, ConsoleColor? color = null, Int32 count = 1, Boolean newLine = false, TerminalWriters writerFlags = TerminalWriters.StandardOutput) {
/// <param name="color">The color.</param> lock(SyncLock) {
/// <param name="count">The count.</param> String text = new String(charCode, count);
/// <param name="newLine">if set to <c>true</c> [new line].</param>
/// <param name="writerFlags">The writer flags.</param> if(newLine) {
public static void Write(char charCode, ConsoleColor? color = null, int count = 1, bool newLine = false, TerminalWriters writerFlags = TerminalWriters.StandardOutput) text += Environment.NewLine;
{ }
lock (SyncLock)
{ Byte[] buffer = OutputEncoding.GetBytes(text);
var text = new string(charCode, count); OutputContext context = new OutputContext {
OutputColor = color ?? Settings.DefaultColor,
if (newLine) OutputText = OutputEncoding.GetChars(buffer),
{ OutputWriters = writerFlags,
text += Environment.NewLine; };
}
EnqueueOutput(context);
var buffer = OutputEncoding.GetBytes(text); }
var context = new OutputContext }
{
OutputColor = color ?? Settings.DefaultColor, /// <summary>
OutputText = OutputEncoding.GetChars(buffer), /// Writes the specified text in the given color.
OutputWriters = writerFlags, /// </summary>
}; /// <param name="text">The text.</param>
/// <param name="color">The color.</param>
EnqueueOutput(context); /// <param name="writerFlags">The writer flags.</param>
} public static void Write(String? text, ConsoleColor? color = null, TerminalWriters writerFlags = TerminalWriters.StandardOutput) {
} if(text == null) {
return;
/// <summary> }
/// Writes the specified text in the given color.
/// </summary> lock(SyncLock) {
/// <param name="text">The text.</param> Byte[] buffer = OutputEncoding.GetBytes(text);
/// <param name="color">The color.</param> OutputContext context = new OutputContext {
/// <param name="writerFlags">The writer flags.</param> OutputColor = color ?? Settings.DefaultColor,
public static void Write(string? text, ConsoleColor? color = null, TerminalWriters writerFlags = TerminalWriters.StandardOutput) OutputText = OutputEncoding.GetChars(buffer),
{ OutputWriters = writerFlags,
if (text == null) return; };
lock (SyncLock) EnqueueOutput(context);
{ }
var buffer = OutputEncoding.GetBytes(text); }
var context = new OutputContext
{ /// <summary>
OutputColor = color ?? Settings.DefaultColor, /// Writes a New Line Sequence to the standard output.
OutputText = OutputEncoding.GetChars(buffer), /// </summary>
OutputWriters = writerFlags, /// <param name="writerFlags">The writer flags.</param>
}; public static void WriteLine(TerminalWriters writerFlags = TerminalWriters.StandardOutput) => Write(Environment.NewLine, Settings.DefaultColor, writerFlags);
EnqueueOutput(context); /// <summary>
} /// Writes a line of text in the current console foreground color
} /// to the standard output.
/// </summary>
/// <summary> /// <param name="text">The text.</param>
/// Writes a New Line Sequence to the standard output. /// <param name="color">The color.</param>
/// </summary> /// <param name="writerFlags">The writer flags.</param>
/// <param name="writerFlags">The writer flags.</param> public static void WriteLine(String text, ConsoleColor? color = null, TerminalWriters writerFlags = TerminalWriters.StandardOutput) => Write($"{text ?? String.Empty}{Environment.NewLine}", color, writerFlags);
public static void WriteLine(TerminalWriters writerFlags = TerminalWriters.StandardOutput)
=> Write(Environment.NewLine, Settings.DefaultColor, writerFlags); /// <summary>
/// As opposed to WriteLine methods, it prepends a Carriage Return character to the text
/// <summary> /// so that the console moves the cursor one position up after the text has been written out.
/// Writes a line of text in the current console foreground color /// </summary>
/// to the standard output. /// <param name="text">The text.</param>
/// </summary> /// <param name="color">The color.</param>
/// <param name="text">The text.</param> /// <param name="writerFlags">The writer flags.</param>
/// <param name="color">The color.</param> public static void OverwriteLine(String text, ConsoleColor? color = null, TerminalWriters writerFlags = TerminalWriters.StandardOutput) {
/// <param name="writerFlags">The writer flags.</param> Write($"\r{text ?? String.Empty}", color, writerFlags);
public static void WriteLine(string text, ConsoleColor? color = null, TerminalWriters writerFlags = TerminalWriters.StandardOutput) Flush();
=> Write($"{text ?? string.Empty}{Environment.NewLine}", color, writerFlags); CursorLeft = 0;
}
/// <summary> }
/// As opposed to WriteLine methods, it prepends a Carriage Return character to the text
/// so that the console moves the cursor one position up after the text has been written out.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="color">The color.</param>
/// <param name="writerFlags">The writer flags.</param>
public static void OverwriteLine(string text, ConsoleColor? color = null, TerminalWriters writerFlags = TerminalWriters.StandardOutput)
{
Write($"\r{text ?? string.Empty}", color, writerFlags);
Flush();
CursorLeft = 0;
}
}
} }

View File

@ -1,49 +1,46 @@
using System; using System;
namespace Swan namespace Swan {
{ /// <summary>
/// A console terminal helper to create nicer output and receive input from the user
/// This class is thread-safe :).
/// </summary>
public static partial class Terminal {
/// <summary> /// <summary>
/// A console terminal helper to create nicer output and receive input from the user /// Terminal global settings.
/// This class is thread-safe :).
/// </summary> /// </summary>
public static partial class Terminal public static class Settings {
{ /// <summary>
/// <summary> /// Gets or sets the default output color.
/// Terminal global settings. /// </summary>
/// </summary> /// <value>
public static class Settings /// The default color.
{ /// </value>
/// <summary> public static ConsoleColor DefaultColor { get; set; } = Console.ForegroundColor;
/// Gets or sets the default output color.
/// </summary> /// <summary>
/// <value> /// Gets the color of the border.
/// The default color. /// </summary>
/// </value> /// <value>
public static ConsoleColor DefaultColor { get; set; } = Console.ForegroundColor; /// The color of the border.
/// </value>
/// <summary> public static ConsoleColor BorderColor { get; } = ConsoleColor.DarkGreen;
/// Gets the color of the border.
/// </summary> /// <summary>
/// <value> /// Gets or sets the user input prefix.
/// The color of the border. /// </summary>
/// </value> /// <value>
public static ConsoleColor BorderColor { get; } = ConsoleColor.DarkGreen; /// The user input prefix.
/// </value>
/// <summary> public static String UserInputPrefix { get; set; } = "USR";
/// Gets or sets the user input prefix.
/// </summary> /// <summary>
/// <value> /// Gets or sets the user option text.
/// The user input prefix. /// </summary>
/// </value> /// <value>
public static string UserInputPrefix { get; set; } = "USR"; /// The user option text.
/// </value>
/// <summary> public static String UserOptionText { get; set; } = " Option: ";
/// Gets or sets the user option text. }
/// </summary> }
/// <value>
/// The user option text.
/// </value>
public static string UserOptionText { get; set; } = " Option: ";
}
}
} }

View File

@ -2,338 +2,329 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using Swan.Threading; using Swan.Threading;
namespace Swan namespace Swan {
{ /// <summary>
/// A console terminal helper to create nicer output and receive input from the user.
/// This class is thread-safe :).
/// </summary>
public static partial class Terminal {
#region Private Declarations
private const Int32 OutputFlushInterval = 15;
private static readonly ExclusiveTimer DequeueOutputTimer;
private static readonly Object SyncLock = new Object();
private static readonly ConcurrentQueue<OutputContext> OutputQueue = new ConcurrentQueue<OutputContext>();
private static readonly ManualResetEventSlim OutputDone = new ManualResetEventSlim(false);
private static readonly ManualResetEventSlim InputDone = new ManualResetEventSlim(true);
private static Boolean? _isConsolePresent;
#endregion
#region Constructors
/// <summary> /// <summary>
/// A console terminal helper to create nicer output and receive input from the user. /// Initializes static members of the <see cref="Terminal"/> class.
/// This class is thread-safe :).
/// </summary> /// </summary>
public static partial class Terminal static Terminal() {
{ lock(SyncLock) {
#region Private Declarations if(DequeueOutputTimer != null) {
return;
private const int OutputFlushInterval = 15; }
private static readonly ExclusiveTimer DequeueOutputTimer;
private static readonly object SyncLock = new object(); if(IsConsolePresent) {
private static readonly ConcurrentQueue<OutputContext> OutputQueue = new ConcurrentQueue<OutputContext>(); Console.CursorVisible = false;
}
private static readonly ManualResetEventSlim OutputDone = new ManualResetEventSlim(false);
private static readonly ManualResetEventSlim InputDone = new ManualResetEventSlim(true); // Here we start the output task, fire-and-forget
DequeueOutputTimer = new ExclusiveTimer(DequeueOutputCycle);
private static bool? _isConsolePresent; DequeueOutputTimer.Resume(OutputFlushInterval);
}
#endregion }
#region Constructors #endregion
/// <summary> #region Synchronized Cursor Movement
/// Initializes static members of the <see cref="Terminal"/> class.
/// </summary> /// <summary>
static Terminal() /// Gets or sets the cursor left position.
{ /// </summary>
lock (SyncLock) /// <value>
{ /// The cursor left.
if (DequeueOutputTimer != null) return; /// </value>
public static Int32 CursorLeft {
if (IsConsolePresent) get {
{ if(IsConsolePresent == false) {
Console.CursorVisible = false; return -1;
} }
// Here we start the output task, fire-and-forget lock(SyncLock) {
DequeueOutputTimer = new ExclusiveTimer(DequeueOutputCycle); Flush();
DequeueOutputTimer.Resume(OutputFlushInterval); return Console.CursorLeft;
} }
} }
set {
#endregion if(IsConsolePresent == false) {
return;
#region Synchronized Cursor Movement }
/// <summary> lock(SyncLock) {
/// Gets or sets the cursor left position. Flush();
/// </summary> Console.CursorLeft = value;
/// <value> }
/// The cursor left. }
/// </value> }
public static int CursorLeft
{ /// <summary>
get /// Gets or sets the cursor top position.
{ /// </summary>
if (IsConsolePresent == false) return -1; /// <value>
lock (SyncLock) /// The cursor top.
{ /// </value>
Flush(); public static Int32 CursorTop {
return Console.CursorLeft; get {
} if(IsConsolePresent == false) {
} return -1;
set }
{
if (IsConsolePresent == false) return; lock(SyncLock) {
lock (SyncLock) Flush();
{ return Console.CursorTop;
Flush(); }
Console.CursorLeft = value; }
} set {
} if(IsConsolePresent == false) {
} return;
}
/// <summary>
/// Gets or sets the cursor top position. lock(SyncLock) {
/// </summary> Flush();
/// <value> Console.CursorTop = value;
/// The cursor top. }
/// </value> }
public static int CursorTop }
{
get #endregion
{
if (IsConsolePresent == false) return -1; #region Properties
lock (SyncLock) /// <summary>
{ /// Gets a value indicating whether the Console is present.
Flush(); /// </summary>
return Console.CursorTop; /// <value>
} /// <c>true</c> if this instance is console present; otherwise, <c>false</c>.
} /// </value>
set public static Boolean IsConsolePresent {
{ get {
if (IsConsolePresent == false) return; if(_isConsolePresent == null) {
_isConsolePresent = true;
lock (SyncLock) try {
{ Int32 windowHeight = Console.WindowHeight;
Flush(); _isConsolePresent = windowHeight >= 0;
Console.CursorTop = value; } catch {
} _isConsolePresent = false;
} }
} }
#endregion return _isConsolePresent.Value;
}
#region Properties }
/// <summary> /// <summary>
/// Gets a value indicating whether the Console is present. /// Gets the available output writers in a bitwise mask.
/// </summary> /// </summary>
/// <value> /// <value>
/// <c>true</c> if this instance is console present; otherwise, <c>false</c>. /// The available writers.
/// </value> /// </value>
public static bool IsConsolePresent public static TerminalWriters AvailableWriters => IsConsolePresent ? TerminalWriters.StandardError | TerminalWriters.StandardOutput : TerminalWriters.None;
{
get /// <summary>
{ /// Gets or sets the output encoding for the current console.
if (_isConsolePresent == null) /// </summary>
{ /// <value>
_isConsolePresent = true; /// The output encoding.
try /// </value>
{ public static Encoding OutputEncoding {
var windowHeight = Console.WindowHeight; get => Console.OutputEncoding;
_isConsolePresent = windowHeight >= 0; set => Console.OutputEncoding = value;
} }
catch
{ #endregion
_isConsolePresent = false;
} #region Methods
}
/// <summary>
return _isConsolePresent.Value; /// Waits for all of the queued output messages to be written out to the console.
} /// Call this method if it is important to display console text before
} /// quitting the application such as showing usage or help.
/// Set the timeout to null or TimeSpan.Zero to wait indefinitely.
/// <summary> /// </summary>
/// Gets the available output writers in a bitwise mask. /// <param name="timeout">The timeout. Set the amount of time to black before this method exits.</param>
/// </summary> public static void Flush(TimeSpan? timeout = null) {
/// <value> if(timeout == null) {
/// The available writers. timeout = TimeSpan.Zero;
/// </value> }
public static TerminalWriters AvailableWriters =>
IsConsolePresent DateTime startTime = DateTime.UtcNow;
? TerminalWriters.StandardError | TerminalWriters.StandardOutput
: TerminalWriters.None; while(OutputQueue.Count > 0) {
// Manually trigger a timer cycle to run immediately
/// <summary> DequeueOutputTimer.Change(0, OutputFlushInterval);
/// Gets or sets the output encoding for the current console.
/// </summary> // Wait for the output to finish
/// <value> if(OutputDone.Wait(OutputFlushInterval)) {
/// The output encoding. break;
/// </value> }
public static Encoding OutputEncoding
{ // infinite timeout
get => Console.OutputEncoding; if(timeout.Value == TimeSpan.Zero) {
set => Console.OutputEncoding = value; continue;
} }
#endregion // break if we have reached a timeout condition
if(DateTime.UtcNow.Subtract(startTime) >= timeout.Value) {
#region Methods break;
}
/// <summary> }
/// Waits for all of the queued output messages to be written out to the console. }
/// Call this method if it is important to display console text before
/// quitting the application such as showing usage or help. /// <summary>
/// Set the timeout to null or TimeSpan.Zero to wait indefinitely. /// Sets the cursor position.
/// </summary> /// </summary>
/// <param name="timeout">The timeout. Set the amount of time to black before this method exits.</param> /// <param name="left">The left.</param>
public static void Flush(TimeSpan? timeout = null) /// <param name="top">The top.</param>
{ public static void SetCursorPosition(Int32 left, Int32 top) {
if (timeout == null) timeout = TimeSpan.Zero; if(!IsConsolePresent) {
var startTime = DateTime.UtcNow; return;
}
while (OutputQueue.Count > 0)
{ lock(SyncLock) {
// Manually trigger a timer cycle to run immediately Flush();
DequeueOutputTimer.Change(0, OutputFlushInterval); Console.SetCursorPosition(left.Clamp(0, left), top.Clamp(0, top));
}
// Wait for the output to finish }
if (OutputDone.Wait(OutputFlushInterval))
break; /// <summary>
/// Moves the output cursor one line up starting at left position 0
// infinite timeout /// Please note that backlining the cursor does not clear the contents of the
if (timeout.Value == TimeSpan.Zero) /// previous line so you might need to clear it by writing an empty string the
continue; /// length of the console width.
/// </summary>
// break if we have reached a timeout condition public static void BacklineCursor() => SetCursorPosition(0, CursorTop - 1);
if (DateTime.UtcNow.Subtract(startTime) >= timeout.Value)
break; /// <summary>
} /// Writes a standard banner to the standard output
} /// containing the company name, product name, assembly version and trademark.
/// </summary>
/// <summary> /// <param name="color">The color.</param>
/// Sets the cursor position. public static void WriteWelcomeBanner(ConsoleColor color = ConsoleColor.Gray) {
/// </summary> WriteLine($"{SwanRuntime.CompanyName} {SwanRuntime.ProductName} [Version {SwanRuntime.EntryAssemblyVersion}]", color);
/// <param name="left">The left.</param> WriteLine($"{SwanRuntime.ProductTrademark}", color);
/// <param name="top">The top.</param> }
public static void SetCursorPosition(int left, int top)
{ /// <summary>
if (!IsConsolePresent) return; /// Enqueues the output to be written to the console
/// This is the only method that should enqueue to the output
lock (SyncLock) /// Please note that if AvailableWriters is None, then no output will be enqueued.
{ /// </summary>
Flush(); /// <param name="context">The context.</param>
Console.SetCursorPosition(left.Clamp(0, left), top.Clamp(0, top)); private static void EnqueueOutput(OutputContext context) {
} lock(SyncLock) {
} TerminalWriters availableWriters = AvailableWriters;
/// <summary> if(availableWriters == TerminalWriters.None || context.OutputWriters == TerminalWriters.None) {
/// Moves the output cursor one line up starting at left position 0 OutputDone.Set();
/// Please note that backlining the cursor does not clear the contents of the return;
/// previous line so you might need to clear it by writing an empty string the }
/// length of the console width.
/// </summary> if((context.OutputWriters & availableWriters) == TerminalWriters.None) {
public static void BacklineCursor() => SetCursorPosition(0, CursorTop - 1); return;
}
/// <summary>
/// Writes a standard banner to the standard output OutputDone.Reset();
/// containing the company name, product name, assembly version and trademark. OutputQueue.Enqueue(context);
/// </summary> }
/// <param name="color">The color.</param> }
public static void WriteWelcomeBanner(ConsoleColor color = ConsoleColor.Gray)
{ /// <summary>
WriteLine($"{SwanRuntime.CompanyName} {SwanRuntime.ProductName} [Version {SwanRuntime.EntryAssemblyVersion}]", color); /// Runs a Terminal I/O cycle in the <see cref="ThreadPool"/> thread.
WriteLine($"{SwanRuntime.ProductTrademark}", color); /// </summary>
} private static void DequeueOutputCycle() {
if(AvailableWriters == TerminalWriters.None) {
/// <summary> OutputDone.Set();
/// Enqueues the output to be written to the console return;
/// This is the only method that should enqueue to the output }
/// Please note that if AvailableWriters is None, then no output will be enqueued.
/// </summary> InputDone.Wait();
/// <param name="context">The context.</param>
private static void EnqueueOutput(OutputContext context) if(OutputQueue.Count <= 0) {
{ OutputDone.Set();
lock (SyncLock) return;
{ }
var availableWriters = AvailableWriters;
OutputDone.Reset();
if (availableWriters == TerminalWriters.None || context.OutputWriters == TerminalWriters.None)
{ while(OutputQueue.Count > 0) {
OutputDone.Set(); if(!OutputQueue.TryDequeue(out OutputContext context)) {
return; continue;
} }
if ((context.OutputWriters & availableWriters) == TerminalWriters.None) // Process Console output and Skip over stuff we can't display so we don't stress the output too much.
return; if(!IsConsolePresent) {
continue;
OutputDone.Reset(); }
OutputQueue.Enqueue(context);
} Console.ForegroundColor = context.OutputColor;
}
// Output to the standard output
/// <summary> if(context.OutputWriters.HasFlag(TerminalWriters.StandardOutput)) {
/// Runs a Terminal I/O cycle in the <see cref="ThreadPool"/> thread. Console.Out.Write(context.OutputText);
/// </summary> }
private static void DequeueOutputCycle()
{ // output to the standard error
if (AvailableWriters == TerminalWriters.None) if(context.OutputWriters.HasFlag(TerminalWriters.StandardError)) {
{ Console.Error.Write(context.OutputText);
OutputDone.Set(); }
return;
} Console.ResetColor();
Console.ForegroundColor = context.OriginalColor;
InputDone.Wait(); }
}
if (OutputQueue.Count <= 0)
{ #endregion
OutputDone.Set();
return; #region Output Context
}
/// <summary>
OutputDone.Reset(); /// Represents an asynchronous output context.
/// </summary>
while (OutputQueue.Count > 0) private sealed class OutputContext {
{ /// <summary>
if (!OutputQueue.TryDequeue(out var context)) continue; /// Initializes a new instance of the <see cref="OutputContext"/> class.
/// </summary>
// Process Console output and Skip over stuff we can't display so we don't stress the output too much. public OutputContext() {
if (!IsConsolePresent) continue; this.OriginalColor = Settings.DefaultColor;
this.OutputWriters = IsConsolePresent ? TerminalWriters.StandardOutput : TerminalWriters.None;
Console.ForegroundColor = context.OutputColor; }
// Output to the standard output public ConsoleColor OriginalColor {
if (context.OutputWriters.HasFlag(TerminalWriters.StandardOutput)) get;
{ }
Console.Out.Write(context.OutputText); public ConsoleColor OutputColor {
} get; set;
}
// output to the standard error public Char[] OutputText {
if (context.OutputWriters.HasFlag(TerminalWriters.StandardError)) get; set;
{ }
Console.Error.Write(context.OutputText); public TerminalWriters OutputWriters {
} get; set;
}
Console.ResetColor(); }
Console.ForegroundColor = context.OriginalColor;
} #endregion
} }
#endregion
#region Output Context
/// <summary>
/// Represents an asynchronous output context.
/// </summary>
private sealed class OutputContext
{
/// <summary>
/// Initializes a new instance of the <see cref="OutputContext"/> class.
/// </summary>
public OutputContext()
{
OriginalColor = Settings.DefaultColor;
OutputWriters = IsConsolePresent
? TerminalWriters.StandardOutput
: TerminalWriters.None;
}
public ConsoleColor OriginalColor { get; }
public ConsoleColor OutputColor { get; set; }
public char[] OutputText { get; set; }
public TerminalWriters OutputWriters { get; set; }
}
#endregion
}
} }

View File

@ -1,31 +1,29 @@
using System; using System;
namespace Swan namespace Swan {
{ /// <summary>
/// Defines a set of bitwise standard terminal writers.
/// </summary>
[Flags]
public enum TerminalWriters {
/// <summary> /// <summary>
/// Defines a set of bitwise standard terminal writers. /// Prevents output
/// </summary> /// </summary>
[Flags] None = 0,
public enum TerminalWriters
{ /// <summary>
/// <summary> /// Writes to the Console.Out
/// Prevents output /// </summary>
/// </summary> StandardOutput = 1,
None = 0,
/// <summary>
/// <summary> /// Writes to the Console.Error
/// Writes to the Console.Out /// </summary>
/// </summary> StandardError = 2,
StandardOutput = 1,
/// <summary>
/// <summary> /// Writes to all possible terminal writers
/// Writes to the Console.Error /// </summary>
/// </summary> All = StandardOutput | StandardError,
StandardError = 2, }
/// <summary>
/// Writes to all possible terminal writers
/// </summary>
All = StandardOutput | StandardError,
}
} }

View File

@ -1,24 +1,22 @@
namespace Swan.Threading using System;
{
namespace Swan.Threading {
/// <summary>
/// Fast, atomic boolean combining interlocked to write value and volatile to read values.
/// </summary>
public sealed class AtomicBoolean : AtomicTypeBase<Boolean> {
/// <summary> /// <summary>
/// Fast, atomic boolean combining interlocked to write value and volatile to read values. /// Initializes a new instance of the <see cref="AtomicBoolean"/> class.
/// </summary> /// </summary>
public sealed class AtomicBoolean : AtomicTypeBase<bool> /// <param name="initialValue">if set to <c>true</c> [initial value].</param>
{ public AtomicBoolean(Boolean initialValue = default) : base(initialValue ? 1 : 0) {
/// <summary> // placeholder
/// Initializes a new instance of the <see cref="AtomicBoolean"/> class. }
/// </summary>
/// <param name="initialValue">if set to <c>true</c> [initial value].</param> /// <inheritdoc/>
public AtomicBoolean(bool initialValue = default) protected override Boolean FromLong(Int64 backingValue) => backingValue != 0;
: base(initialValue ? 1 : 0)
{ /// <inheritdoc/>
// placeholder protected override Int64 ToLong(Boolean value) => value ? 1 : 0;
} }
/// <inheritdoc/>
protected override bool FromLong(long backingValue) => backingValue != 0;
/// <inheritdoc/>
protected override long ToLong(bool value) => value ? 1 : 0;
}
} }

View File

@ -1,26 +1,22 @@
using System; using System;
namespace Swan.Threading namespace Swan.Threading {
{ /// <summary>
/// Defines an atomic DateTime.
/// </summary>
public sealed class AtomicDateTime : AtomicTypeBase<DateTime> {
/// <summary> /// <summary>
/// Defines an atomic DateTime. /// Initializes a new instance of the <see cref="AtomicDateTime"/> class.
/// </summary> /// </summary>
public sealed class AtomicDateTime : AtomicTypeBase<DateTime> /// <param name="initialValue">The initial value.</param>
{ public AtomicDateTime(DateTime initialValue) : base(initialValue.Ticks) {
/// <summary> // placeholder
/// Initializes a new instance of the <see cref="AtomicDateTime"/> class. }
/// </summary>
/// <param name="initialValue">The initial value.</param> /// <inheritdoc />
public AtomicDateTime(DateTime initialValue) protected override DateTime FromLong(Int64 backingValue) => new DateTime(backingValue);
: base(initialValue.Ticks)
{ /// <inheritdoc />
// placeholder protected override Int64 ToLong(DateTime value) => value.Ticks;
} }
/// <inheritdoc />
protected override DateTime FromLong(long backingValue) => new DateTime(backingValue);
/// <inheritdoc />
protected override long ToLong(DateTime value) => value.Ticks;
}
} }

View File

@ -1,28 +1,22 @@
using System; using System;
namespace Swan.Threading namespace Swan.Threading {
{ /// <summary>
/// Fast, atomic double combining interlocked to write value and volatile to read values.
/// </summary>
public sealed class AtomicDouble : AtomicTypeBase<Double> {
/// <summary> /// <summary>
/// Fast, atomic double combining interlocked to write value and volatile to read values. /// Initializes a new instance of the <see cref="AtomicDouble"/> class.
/// </summary> /// </summary>
public sealed class AtomicDouble : AtomicTypeBase<double> /// <param name="initialValue">if set to <c>true</c> [initial value].</param>
{ public AtomicDouble(Double initialValue = default) : base(BitConverter.DoubleToInt64Bits(initialValue)) {
/// <summary> // placeholder
/// Initializes a new instance of the <see cref="AtomicDouble"/> class. }
/// </summary>
/// <param name="initialValue">if set to <c>true</c> [initial value].</param> /// <inheritdoc/>
public AtomicDouble(double initialValue = default) protected override Double FromLong(Int64 backingValue) => BitConverter.Int64BitsToDouble(backingValue);
: base(BitConverter.DoubleToInt64Bits(initialValue))
{ /// <inheritdoc/>
// placeholder protected override Int64 ToLong(Double value) => BitConverter.DoubleToInt64Bits(value);
} }
/// <inheritdoc/>
protected override double FromLong(long backingValue) =>
BitConverter.Int64BitsToDouble(backingValue);
/// <inheritdoc/>
protected override long ToLong(double value) =>
BitConverter.DoubleToInt64Bits(value);
}
} }

View File

@ -1,43 +1,38 @@
using System; using System;
using System.Threading; using System.Threading;
namespace Swan.Threading namespace Swan.Threading {
{ /// <summary>
/// Defines an atomic generic Enum.
/// </summary>
/// <typeparam name="T">The type of enum.</typeparam>
public sealed class AtomicEnum<T> where T : struct, IConvertible {
private Int64 _backingValue;
/// <summary> /// <summary>
/// Defines an atomic generic Enum. /// Initializes a new instance of the <see cref="AtomicEnum{T}"/> class.
/// </summary> /// </summary>
/// <typeparam name="T">The type of enum.</typeparam> /// <param name="initialValue">The initial value.</param>
public sealed class AtomicEnum<T> /// <exception cref="ArgumentException">T must be an enumerated type.</exception>
where T : struct, IConvertible public AtomicEnum(T initialValue) {
{ if(!Enum.IsDefined(typeof(T), initialValue)) {
private long _backingValue; throw new ArgumentException("T must be an enumerated type");
}
/// <summary>
/// Initializes a new instance of the <see cref="AtomicEnum{T}"/> class. this.Value = initialValue;
/// </summary> }
/// <param name="initialValue">The initial value.</param>
/// <exception cref="ArgumentException">T must be an enumerated type.</exception> /// <summary>
public AtomicEnum(T initialValue) /// Gets or sets the value.
{ /// </summary>
if (!Enum.IsDefined(typeof(T), initialValue)) public T Value {
throw new ArgumentException("T must be an enumerated type"); get => (T)Enum.ToObject(typeof(T), this.BackingValue);
set => this.BackingValue = Convert.ToInt64(value);
Value = initialValue; }
}
private Int64 BackingValue {
/// <summary> get => Interlocked.Read(ref this._backingValue);
/// Gets or sets the value. set => Interlocked.Exchange(ref this._backingValue, value);
/// </summary> }
public T Value }
{
get => (T)Enum.ToObject(typeof(T), BackingValue);
set => BackingValue = Convert.ToInt64(value);
}
private long BackingValue
{
get => Interlocked.Read(ref _backingValue);
set => Interlocked.Exchange(ref _backingValue, value);
}
}
} }

View File

@ -1,28 +1,22 @@
using System; using System;
namespace Swan.Threading namespace Swan.Threading {
{ /// <summary>
/// Represents an atomically readable or writable integer.
/// </summary>
public class AtomicInteger : AtomicTypeBase<Int32> {
/// <summary> /// <summary>
/// Represents an atomically readable or writable integer. /// Initializes a new instance of the <see cref="AtomicInteger"/> class.
/// </summary> /// </summary>
public class AtomicInteger : AtomicTypeBase<int> /// <param name="initialValue">if set to <c>true</c> [initial value].</param>
{ public AtomicInteger(Int32 initialValue = default) : base(Convert.ToInt64(initialValue)) {
/// <summary> // placeholder
/// Initializes a new instance of the <see cref="AtomicInteger"/> class. }
/// </summary>
/// <param name="initialValue">if set to <c>true</c> [initial value].</param> /// <inheritdoc/>
public AtomicInteger(int initialValue = default) protected override Int32 FromLong(Int64 backingValue) => Convert.ToInt32(backingValue);
: base(Convert.ToInt64(initialValue))
{ /// <inheritdoc/>
// placeholder protected override Int64 ToLong(Int32 value) => Convert.ToInt64(value);
} }
/// <inheritdoc/>
protected override int FromLong(long backingValue) =>
Convert.ToInt32(backingValue);
/// <inheritdoc/>
protected override long ToLong(int value) =>
Convert.ToInt64(value);
}
} }

View File

@ -1,24 +1,22 @@
namespace Swan.Threading using System;
{
namespace Swan.Threading {
/// <summary>
/// Fast, atomic long combining interlocked to write value and volatile to read values.
/// </summary>
public sealed class AtomicLong : AtomicTypeBase<Int64> {
/// <summary> /// <summary>
/// Fast, atomic long combining interlocked to write value and volatile to read values. /// Initializes a new instance of the <see cref="AtomicLong"/> class.
/// </summary> /// </summary>
public sealed class AtomicLong : AtomicTypeBase<long> /// <param name="initialValue">if set to <c>true</c> [initial value].</param>
{ public AtomicLong(Int64 initialValue = default) : base(initialValue) {
/// <summary> // placeholder
/// Initializes a new instance of the <see cref="AtomicLong"/> class. }
/// </summary>
/// <param name="initialValue">if set to <c>true</c> [initial value].</param> /// <inheritdoc />
public AtomicLong(long initialValue = default) protected override Int64 FromLong(Int64 backingValue) => backingValue;
: base(initialValue)
{ /// <inheritdoc />
// placeholder protected override Int64 ToLong(Int64 value) => value;
} }
/// <inheritdoc />
protected override long FromLong(long backingValue) => backingValue;
/// <inheritdoc />
protected override long ToLong(long value) => value;
}
} }

View File

@ -1,26 +1,22 @@
using System; using System;
namespace Swan.Threading namespace Swan.Threading {
{ /// <summary>
/// Represents an atomic TimeSpan type.
/// </summary>
public sealed class AtomicTimeSpan : AtomicTypeBase<TimeSpan> {
/// <summary> /// <summary>
/// Represents an atomic TimeSpan type. /// Initializes a new instance of the <see cref="AtomicTimeSpan" /> class.
/// </summary> /// </summary>
public sealed class AtomicTimeSpan : AtomicTypeBase<TimeSpan> /// <param name="initialValue">The initial value.</param>
{ public AtomicTimeSpan(TimeSpan initialValue) : base(initialValue.Ticks) {
/// <summary> // placeholder
/// Initializes a new instance of the <see cref="AtomicTimeSpan" /> class. }
/// </summary>
/// <param name="initialValue">The initial value.</param> /// <inheritdoc />
public AtomicTimeSpan(TimeSpan initialValue) protected override TimeSpan FromLong(Int64 backingValue) => TimeSpan.FromTicks(backingValue);
: base(initialValue.Ticks)
{ /// <inheritdoc />
// placeholder protected override Int64 ToLong(TimeSpan value) => value.Ticks < 0 ? 0 : value.Ticks;
} }
/// <inheritdoc />
protected override TimeSpan FromLong(long backingValue) => TimeSpan.FromTicks(backingValue);
/// <inheritdoc />
protected override long ToLong(TimeSpan value) => value.Ticks < 0 ? 0 : value.Ticks;
}
} }

View File

@ -1,243 +1,219 @@
using System; using System;
using System.Threading; using System.Threading;
namespace Swan.Threading namespace Swan.Threading {
{ /// <summary>
/// Provides a generic implementation of an Atomic (interlocked) type
///
/// Idea taken from Memory model and .NET operations in article:
/// http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/.
/// </summary>
/// <typeparam name="T">The structure type backed by a 64-bit value.</typeparam>
public abstract class AtomicTypeBase<T> : IComparable, IComparable<T>, IComparable<AtomicTypeBase<T>>, IEquatable<T>, IEquatable<AtomicTypeBase<T>> where T : struct, IComparable, IComparable<T>, IEquatable<T> {
private Int64 _backingValue;
/// <summary> /// <summary>
/// Provides a generic implementation of an Atomic (interlocked) type /// Initializes a new instance of the <see cref="AtomicTypeBase{T}"/> class.
///
/// Idea taken from Memory model and .NET operations in article:
/// http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/.
/// </summary> /// </summary>
/// <typeparam name="T">The structure type backed by a 64-bit value.</typeparam> /// <param name="initialValue">The initial value.</param>
public abstract class AtomicTypeBase<T> : IComparable, IComparable<T>, IComparable<AtomicTypeBase<T>>, IEquatable<T>, IEquatable<AtomicTypeBase<T>> protected AtomicTypeBase(Int64 initialValue) => this.BackingValue = initialValue;
where T : struct, IComparable, IComparable<T>, IEquatable<T>
{ /// <summary>
private long _backingValue; /// Gets or sets the value.
/// </summary>
/// <summary> public T Value {
/// Initializes a new instance of the <see cref="AtomicTypeBase{T}"/> class. get => this.FromLong(this.BackingValue);
/// </summary> set => this.BackingValue = this.ToLong(value);
/// <param name="initialValue">The initial value.</param> }
protected AtomicTypeBase(long initialValue)
{ /// <summary>
BackingValue = initialValue; /// Gets or sets the backing value.
} /// </summary>
protected Int64 BackingValue {
/// <summary> get => Interlocked.Read(ref this._backingValue);
/// Gets or sets the value. set => Interlocked.Exchange(ref this._backingValue, value);
/// </summary> }
public T Value
{ /// <summary>
get => FromLong(BackingValue); /// Implements the operator ==.
set => BackingValue = ToLong(value); /// </summary>
} /// <param name="a">a.</param>
/// <param name="b">The b.</param>
/// <summary> /// <returns>
/// Gets or sets the backing value. /// The result of the operator.
/// </summary> /// </returns>
protected long BackingValue public static Boolean operator ==(AtomicTypeBase<T> a, T b) => a?.Equals(b) == true;
{
get => Interlocked.Read(ref _backingValue); /// <summary>
set => Interlocked.Exchange(ref _backingValue, value); /// Implements the operator !=.
} /// </summary>
/// <param name="a">a.</param>
/// <summary> /// <param name="b">The b.</param>
/// Implements the operator ==. /// <returns>
/// </summary> /// The result of the operator.
/// <param name="a">a.</param> /// </returns>
/// <param name="b">The b.</param> public static Boolean operator !=(AtomicTypeBase<T> a, T b) => a?.Equals(b) == false;
/// <returns>
/// The result of the operator. /// <summary>
/// </returns> /// Implements the operator &gt;.
public static bool operator ==(AtomicTypeBase<T> a, T b) => a?.Equals(b) == true; /// </summary>
/// <param name="a">a.</param>
/// <summary> /// <param name="b">The b.</param>
/// Implements the operator !=. /// <returns>
/// </summary> /// The result of the operator.
/// <param name="a">a.</param> /// </returns>
/// <param name="b">The b.</param> public static Boolean operator >(AtomicTypeBase<T> a, T b) => a.CompareTo(b) > 0;
/// <returns>
/// The result of the operator. /// <summary>
/// </returns> /// Implements the operator &lt;.
public static bool operator !=(AtomicTypeBase<T> a, T b) => a?.Equals(b) == false; /// </summary>
/// <param name="a">a.</param>
/// <summary> /// <param name="b">The b.</param>
/// Implements the operator &gt;. /// <returns>
/// </summary> /// The result of the operator.
/// <param name="a">a.</param> /// </returns>
/// <param name="b">The b.</param> public static Boolean operator <(AtomicTypeBase<T> a, T b) => a.CompareTo(b) < 0;
/// <returns>
/// The result of the operator. /// <summary>
/// </returns> /// Implements the operator &gt;=.
public static bool operator >(AtomicTypeBase<T> a, T b) => a.CompareTo(b) > 0; /// </summary>
/// <param name="a">a.</param>
/// <summary> /// <param name="b">The b.</param>
/// Implements the operator &lt;. /// <returns>
/// </summary> /// The result of the operator.
/// <param name="a">a.</param> /// </returns>
/// <param name="b">The b.</param> public static Boolean operator >=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) >= 0;
/// <returns>
/// The result of the operator. /// <summary>
/// </returns> /// Implements the operator &lt;=.
public static bool operator <(AtomicTypeBase<T> a, T b) => a.CompareTo(b) < 0; /// </summary>
/// <param name="a">a.</param>
/// <summary> /// <param name="b">The b.</param>
/// Implements the operator &gt;=. /// <returns>
/// </summary> /// The result of the operator.
/// <param name="a">a.</param> /// </returns>
/// <param name="b">The b.</param> public static Boolean operator <=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) <= 0;
/// <returns>
/// The result of the operator. /// <summary>
/// </returns> /// Implements the operator ++.
public static bool operator >=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) >= 0; /// </summary>
/// <param name="instance">The instance.</param>
/// <summary> /// <returns>
/// Implements the operator &lt;=. /// The result of the operator.
/// </summary> /// </returns>
/// <param name="a">a.</param> public static AtomicTypeBase<T> operator ++(AtomicTypeBase<T> instance) {
/// <param name="b">The b.</param> _ = Interlocked.Increment(ref instance._backingValue);
/// <returns> return instance;
/// The result of the operator. }
/// </returns>
public static bool operator <=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) <= 0; /// <summary>
/// Implements the operator --.
/// <summary> /// </summary>
/// Implements the operator ++. /// <param name="instance">The instance.</param>
/// </summary> /// <returns>
/// <param name="instance">The instance.</param> /// The result of the operator.
/// <returns> /// </returns>
/// The result of the operator. public static AtomicTypeBase<T> operator --(AtomicTypeBase<T> instance) {
/// </returns> _ = Interlocked.Decrement(ref instance._backingValue);
public static AtomicTypeBase<T> operator ++(AtomicTypeBase<T> instance) return instance;
{ }
Interlocked.Increment(ref instance._backingValue);
return instance; /// <summary>
} /// Implements the operator -&lt;.
/// </summary>
/// <summary> /// <param name="instance">The instance.</param>
/// Implements the operator --. /// <param name="operand">The operand.</param>
/// </summary> /// <returns>
/// <param name="instance">The instance.</param> /// The result of the operator.
/// <returns> /// </returns>
/// The result of the operator. public static AtomicTypeBase<T> operator +(AtomicTypeBase<T> instance, Int64 operand) {
/// </returns> instance.BackingValue += operand;
public static AtomicTypeBase<T> operator --(AtomicTypeBase<T> instance) return instance;
{ }
Interlocked.Decrement(ref instance._backingValue);
return instance; /// <summary>
} /// Implements the operator -.
/// </summary>
/// <summary> /// <param name="instance">The instance.</param>
/// Implements the operator -&lt;. /// <param name="operand">The operand.</param>
/// </summary> /// <returns>
/// <param name="instance">The instance.</param> /// The result of the operator.
/// <param name="operand">The operand.</param> /// </returns>
/// <returns> public static AtomicTypeBase<T> operator -(AtomicTypeBase<T> instance, Int64 operand) {
/// The result of the operator. instance.BackingValue -= operand;
/// </returns> return instance;
public static AtomicTypeBase<T> operator +(AtomicTypeBase<T> instance, long operand) }
{
instance.BackingValue = instance.BackingValue + operand; /// <summary>
return instance; /// Compares the value to the other instance.
} /// </summary>
/// <param name="other">The other instance.</param>
/// <summary> /// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
/// Implements the operator -. /// <exception cref="ArgumentException">When types are incompatible.</exception>
/// </summary> public Int32 CompareTo(Object other) => other switch
/// <param name="instance">The instance.</param> {
/// <param name="operand">The operand.</param> null => 1,
/// <returns> AtomicTypeBase<T> atomic => this.BackingValue.CompareTo(atomic.BackingValue),
/// The result of the operator. T variable => this.Value.CompareTo(variable),
/// </returns>
public static AtomicTypeBase<T> operator -(AtomicTypeBase<T> instance, long operand) _ => throw new ArgumentException("Incompatible comparison types"),
{ };
instance.BackingValue = instance.BackingValue - operand;
return instance; /// <summary>
} /// Compares the value to the other instance.
/// </summary>
/// <summary> /// <param name="other">The other instance.</param>
/// Compares the value to the other instance. /// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
/// </summary> public Int32 CompareTo(T other) => this.Value.CompareTo(other);
/// <param name="other">The other instance.</param>
/// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns> /// <summary>
/// <exception cref="ArgumentException">When types are incompatible.</exception> /// Compares the value to the other instance.
public int CompareTo(object other) /// </summary>
{ /// <param name="other">The other instance.</param>
switch (other) /// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
{ public Int32 CompareTo(AtomicTypeBase<T> other) => this.BackingValue.CompareTo(other?.BackingValue ?? default);
case null:
return 1; /// <summary>
case AtomicTypeBase<T> atomic: /// Determines whether the specified <see cref="Object" />, is equal to this instance.
return BackingValue.CompareTo(atomic.BackingValue); /// </summary>
case T variable: /// <param name="other">The <see cref="Object" /> to compare with this instance.</param>
return Value.CompareTo(variable); /// <returns>
} /// <c>true</c> if the specified <see cref="Object" /> is equal to this instance; otherwise, <c>false</c>.
/// </returns>
throw new ArgumentException("Incompatible comparison types"); public override Boolean Equals(Object other) => other switch
} {
AtomicTypeBase<T> atomic => this.Equals(atomic),
/// <summary> T variable => this.Equals(variable),
/// Compares the value to the other instance.
/// </summary> _ => false,
/// <param name="other">The other instance.</param> };
/// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
public int CompareTo(T other) => Value.CompareTo(other); /// <summary>
/// Returns a hash code for this instance.
/// <summary> /// </summary>
/// Compares the value to the other instance. /// <returns>
/// </summary> /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// <param name="other">The other instance.</param> /// </returns>
/// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns> public override Int32 GetHashCode() => this.BackingValue.GetHashCode();
public int CompareTo(AtomicTypeBase<T> other) => BackingValue.CompareTo(other?.BackingValue ?? default);
/// <inheritdoc />
/// <summary> public Boolean Equals(AtomicTypeBase<T> other) => this.BackingValue == (other?.BackingValue ?? default);
/// Determines whether the specified <see cref="object" />, is equal to this instance.
/// </summary> /// <inheritdoc />
/// <param name="other">The <see cref="object" /> to compare with this instance.</param> public Boolean Equals(T other) => Equals(this.Value, other);
/// <returns>
/// <c>true</c> if the specified <see cref="object" /> is equal to this instance; otherwise, <c>false</c>. /// <summary>
/// </returns> /// Converts from a long value to the target type.
public override bool Equals(object other) /// </summary>
{ /// <param name="backingValue">The backing value.</param>
switch (other) /// <returns>The value converted form a long value.</returns>
{ protected abstract T FromLong(Int64 backingValue);
case AtomicTypeBase<T> atomic:
return Equals(atomic); /// <summary>
case T variable: /// Converts from the target type to a long value.
return Equals(variable); /// </summary>
} /// <param name="value">The value.</param>
/// <returns>The value converted to a long value.</returns>
return false; protected abstract Int64 ToLong(T value);
} }
/// <summary>
/// Returns a hash code for this instance.
/// </summary>
/// <returns>
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// </returns>
public override int GetHashCode() => BackingValue.GetHashCode();
/// <inheritdoc />
public bool Equals(AtomicTypeBase<T> other) =>
BackingValue == (other?.BackingValue ?? default);
/// <inheritdoc />
public bool Equals(T other) => Equals(Value, other);
/// <summary>
/// Converts from a long value to the target type.
/// </summary>
/// <param name="backingValue">The backing value.</param>
/// <returns>The value converted form a long value.</returns>
protected abstract T FromLong(long backingValue);
/// <summary>
/// Converts from the target type to a long value.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>The value converted to a long value.</returns>
protected abstract long ToLong(T value);
}
} }

View File

@ -1,68 +1,61 @@
using System; using System;
using System.Threading; using System.Threading;
namespace Swan.Threading namespace Swan.Threading {
{ /// <summary>
/// Acts as a <see cref="CancellationTokenSource"/> but with reusable tokens.
/// </summary>
public sealed class CancellationTokenOwner : IDisposable {
private readonly Object _syncLock = new Object();
private Boolean _isDisposed;
private CancellationTokenSource _tokenSource = new CancellationTokenSource();
/// <summary> /// <summary>
/// Acts as a <see cref="CancellationTokenSource"/> but with reusable tokens. /// Gets the token of the current.
/// </summary> /// </summary>
public sealed class CancellationTokenOwner : IDisposable public CancellationToken Token {
{ get {
private readonly object _syncLock = new object(); lock(this._syncLock) {
private bool _isDisposed; return this._isDisposed ? CancellationToken.None : this._tokenSource.Token;
private CancellationTokenSource _tokenSource = new CancellationTokenSource(); }
}
/// <summary> }
/// Gets the token of the current.
/// </summary> /// <summary>
public CancellationToken Token /// Cancels the last referenced token and creates a new token source.
{ /// </summary>
get public void Cancel() {
{ lock(this._syncLock) {
lock (_syncLock) if(this._isDisposed) {
{ return;
return _isDisposed }
? CancellationToken.None
: _tokenSource.Token; this._tokenSource.Cancel();
} this._tokenSource.Dispose();
} this._tokenSource = new CancellationTokenSource();
} }
}
/// <summary>
/// Cancels the last referenced token and creates a new token source. /// <inheritdoc />
/// </summary> public void Dispose() => this.Dispose(true);
public void Cancel()
{ /// <summary>
lock (_syncLock) /// Releases unmanaged and - optionally - managed resources.
{ /// </summary>
if (_isDisposed) return; /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
_tokenSource.Cancel(); private void Dispose(Boolean disposing) {
_tokenSource.Dispose(); lock(this._syncLock) {
_tokenSource = new CancellationTokenSource(); if(this._isDisposed) {
} return;
} }
/// <inheritdoc /> if(disposing) {
public void Dispose() => Dispose(true); this._tokenSource.Cancel();
this._tokenSource.Dispose();
/// <summary> }
/// Releases unmanaged and - optionally - managed resources.
/// </summary> this._isDisposed = true;
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> }
private void Dispose(bool disposing) }
{ }
lock (_syncLock)
{
if (_isDisposed) return;
if (disposing)
{
_tokenSource.Cancel();
_tokenSource.Dispose();
}
_isDisposed = true;
}
}
}
} }

View File

@ -1,236 +1,206 @@
using System; using System;
using System.Threading; using System.Threading;
namespace Swan.Threading namespace Swan.Threading {
{ /// <summary>
/// A threading <see cref="_backingTimer"/> implementation that executes at most one cycle at a time
/// in a <see cref="ThreadPool"/> thread. Callback execution is NOT guaranteed to be carried out
/// on the same <see cref="ThreadPool"/> thread every time the timer fires.
/// </summary>
public sealed class ExclusiveTimer : IDisposable {
private readonly Object _syncLock = new Object();
private readonly ManualResetEventSlim _cycleDoneEvent = new ManualResetEventSlim(true);
private readonly Timer _backingTimer;
private readonly TimerCallback _userCallback;
private readonly AtomicBoolean _isDisposing = new AtomicBoolean();
private readonly AtomicBoolean _isDisposed = new AtomicBoolean();
private Int32 _period;
/// <summary> /// <summary>
/// A threading <see cref="_backingTimer"/> implementation that executes at most one cycle at a time /// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// in a <see cref="ThreadPool"/> thread. Callback execution is NOT guaranteed to be carried out
/// on the same <see cref="ThreadPool"/> thread every time the timer fires.
/// </summary> /// </summary>
public sealed class ExclusiveTimer : IDisposable /// <param name="timerCallback">The timer callback.</param>
{ /// <param name="state">The state.</param>
private readonly object _syncLock = new object(); /// <param name="dueTime">The due time.</param>
private readonly ManualResetEventSlim _cycleDoneEvent = new ManualResetEventSlim(true); /// <param name="period">The period.</param>
private readonly Timer _backingTimer; public ExclusiveTimer(TimerCallback timerCallback, Object state, Int32 dueTime, Int32 period) {
private readonly TimerCallback _userCallback; this._period = period;
private readonly AtomicBoolean _isDisposing = new AtomicBoolean(); this._userCallback = timerCallback;
private readonly AtomicBoolean _isDisposed = new AtomicBoolean(); this._backingTimer = new Timer(this.InternalCallback, state ?? this, dueTime, Timeout.Infinite);
private int _period; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class. /// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary> /// </summary>
/// <param name="timerCallback">The timer callback.</param> /// <param name="timerCallback">The timer callback.</param>
/// <param name="state">The state.</param> /// <param name="state">The state.</param>
/// <param name="dueTime">The due time.</param> /// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param> /// <param name="period">The period.</param>
public ExclusiveTimer(TimerCallback timerCallback, object state, int dueTime, int period) public ExclusiveTimer(TimerCallback timerCallback, Object state, TimeSpan dueTime, TimeSpan period) : this(timerCallback, state, Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds)) {
{ // placeholder
_period = period; }
_userCallback = timerCallback;
_backingTimer = new Timer(InternalCallback, state ?? this, dueTime, Timeout.Infinite); /// <summary>
} /// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary>
/// <summary> /// <param name="timerCallback">The timer callback.</param>
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class. public ExclusiveTimer(TimerCallback timerCallback) : this(timerCallback, null, Timeout.Infinite, Timeout.Infinite) {
/// </summary> // placeholder
/// <param name="timerCallback">The timer callback.</param> }
/// <param name="state">The state.</param>
/// <param name="dueTime">The due time.</param> /// <summary>
/// <param name="period">The period.</param> /// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
public ExclusiveTimer(TimerCallback timerCallback, object state, TimeSpan dueTime, TimeSpan period) /// </summary>
: this(timerCallback, state, Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds)) /// <param name="timerCallback">The timer callback.</param>
{ /// <param name="dueTime">The due time.</param>
// placeholder /// <param name="period">The period.</param>
} public ExclusiveTimer(Action timerCallback, Int32 dueTime, Int32 period) : this(s => timerCallback?.Invoke(), null, dueTime, period) {
// placeholder
/// <summary> }
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary> /// <summary>
/// <param name="timerCallback">The timer callback.</param> /// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
public ExclusiveTimer(TimerCallback timerCallback) /// </summary>
: this(timerCallback, null, Timeout.Infinite, Timeout.Infinite) /// <param name="timerCallback">The timer callback.</param>
{ /// <param name="dueTime">The due time.</param>
// placeholder /// <param name="period">The period.</param>
} public ExclusiveTimer(Action timerCallback, TimeSpan dueTime, TimeSpan period) : this(s => timerCallback?.Invoke(), null, dueTime, period) {
// placeholder
/// <summary> }
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary> /// <summary>
/// <param name="timerCallback">The timer callback.</param> /// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// <param name="dueTime">The due time.</param> /// </summary>
/// <param name="period">The period.</param> /// <param name="timerCallback">The timer callback.</param>
public ExclusiveTimer(Action timerCallback, int dueTime, int period) public ExclusiveTimer(Action timerCallback) : this(timerCallback, Timeout.Infinite, Timeout.Infinite) {
: this(s => { timerCallback?.Invoke(); }, null, dueTime, period) // placeholder
{ }
// placeholder
} /// <summary>
/// Gets a value indicating whether this instance is disposing.
/// <summary> /// </summary>
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class. /// <value>
/// </summary> /// <c>true</c> if this instance is disposing; otherwise, <c>false</c>.
/// <param name="timerCallback">The timer callback.</param> /// </value>
/// <param name="dueTime">The due time.</param> public Boolean IsDisposing => this._isDisposing.Value;
/// <param name="period">The period.</param>
public ExclusiveTimer(Action timerCallback, TimeSpan dueTime, TimeSpan period) /// <summary>
: this(s => { timerCallback?.Invoke(); }, null, dueTime, period) /// Gets a value indicating whether this instance is disposed.
{ /// </summary>
// placeholder /// <value>
} /// <c>true</c> if this instance is disposed; otherwise, <c>false</c>.
/// </value>
/// <summary> public Boolean IsDisposed => this._isDisposed.Value;
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary> /// <summary>
/// <param name="timerCallback">The timer callback.</param> /// Waits until the time is elapsed.
public ExclusiveTimer(Action timerCallback) /// </summary>
: this(timerCallback, Timeout.Infinite, Timeout.Infinite) /// <param name="untilDate">The until date.</param>
{ /// <param name="cancellationToken">The cancellation token.</param>
// placeholder public static void WaitUntil(DateTime untilDate, CancellationToken cancellationToken = default) {
} static void Callback(IWaitEvent waitEvent) {
try {
/// <summary> waitEvent.Complete();
/// Gets a value indicating whether this instance is disposing. waitEvent.Begin();
/// </summary> } catch {
/// <value> // ignore
/// <c>true</c> if this instance is disposing; otherwise, <c>false</c>. }
/// </value> }
public bool IsDisposing => _isDisposing.Value;
using IWaitEvent delayLock = WaitEventFactory.Create(true);
/// <summary> using ExclusiveTimer _ = new ExclusiveTimer(() => Callback(delayLock), 0, 15);
/// Gets a value indicating whether this instance is disposed. while(!cancellationToken.IsCancellationRequested && DateTime.UtcNow < untilDate) {
/// </summary> delayLock.Wait();
/// <value> }
/// <c>true</c> if this instance is disposed; otherwise, <c>false</c>. }
/// </value>
public bool IsDisposed => _isDisposed.Value; /// <summary>
/// Waits the specified wait time.
/// <summary> /// </summary>
/// Waits until the time is elapsed. /// <param name="waitTime">The wait time.</param>
/// </summary> /// <param name="cancellationToken">The cancellation token.</param>
/// <param name="untilDate">The until date.</param> public static void Wait(TimeSpan waitTime, CancellationToken cancellationToken = default) =>
/// <param name="cancellationToken">The cancellation token.</param> WaitUntil(DateTime.UtcNow.Add(waitTime), cancellationToken);
public static void WaitUntil(DateTime untilDate, CancellationToken cancellationToken = default)
{ /// <summary>
void Callback(IWaitEvent waitEvent) /// Changes the start time and the interval between method invocations for the internal timer.
{ /// </summary>
try /// <param name="dueTime">The due time.</param>
{ /// <param name="period">The period.</param>
waitEvent.Complete(); public void Change(Int32 dueTime, Int32 period) {
waitEvent.Begin(); this._period = period;
}
catch _ = this._backingTimer.Change(dueTime, Timeout.Infinite);
{ }
// ignore
} /// <summary>
} /// Changes the start time and the interval between method invocations for the internal timer.
/// </summary>
using (var delayLock = WaitEventFactory.Create(true)) /// <param name="dueTime">The due time.</param>
{ /// <param name="period">The period.</param>
using (var _ = new ExclusiveTimer(() => Callback(delayLock), 0, 15)) public void Change(TimeSpan dueTime, TimeSpan period) => this.Change(Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds));
{
while (!cancellationToken.IsCancellationRequested && DateTime.UtcNow < untilDate) /// <summary>
delayLock.Wait(); /// Changes the interval between method invocations for the internal timer.
} /// </summary>
} /// <param name="period">The period.</param>
} public void Resume(Int32 period) => this.Change(0, period);
/// <summary> /// <summary>
/// Waits the specified wait time. /// Changes the interval between method invocations for the internal timer.
/// </summary> /// </summary>
/// <param name="waitTime">The wait time.</param> /// <param name="period">The period.</param>
/// <param name="cancellationToken">The cancellation token.</param> public void Resume(TimeSpan period) => this.Change(TimeSpan.Zero, period);
public static void Wait(TimeSpan waitTime, CancellationToken cancellationToken = default) =>
WaitUntil(DateTime.UtcNow.Add(waitTime), cancellationToken); /// <summary>
/// Pauses this instance.
/// <summary> /// </summary>
/// Changes the start time and the interval between method invocations for the internal timer. public void Pause() => this.Change(Timeout.Infinite, Timeout.Infinite);
/// </summary>
/// <param name="dueTime">The due time.</param> /// <inheritdoc />
/// <param name="period">The period.</param> public void Dispose() {
public void Change(int dueTime, int period) lock(this._syncLock) {
{ if(this._isDisposed == true || this._isDisposing == true) {
_period = period; return;
}
_backingTimer.Change(dueTime, Timeout.Infinite);
} this._isDisposing.Value = true;
}
/// <summary>
/// Changes the start time and the interval between method invocations for the internal timer. try {
/// </summary> this._cycleDoneEvent.Wait();
/// <param name="dueTime">The due time.</param> this._cycleDoneEvent.Dispose();
/// <param name="period">The period.</param> this.Pause();
public void Change(TimeSpan dueTime, TimeSpan period) this._backingTimer.Dispose();
=> Change(Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds)); } finally {
this._isDisposed.Value = true;
/// <summary> this._isDisposing.Value = false;
/// Changes the interval between method invocations for the internal timer. }
/// </summary> }
/// <param name="period">The period.</param>
public void Resume(int period) => Change(0, period); /// <summary>
/// Logic that runs every time the timer hits the due time.
/// <summary> /// </summary>
/// Changes the interval between method invocations for the internal timer. /// <param name="state">The state.</param>
/// </summary> private void InternalCallback(Object state) {
/// <param name="period">The period.</param> lock(this._syncLock) {
public void Resume(TimeSpan period) => Change(TimeSpan.Zero, period); if(this.IsDisposed || this.IsDisposing) {
return;
/// <summary> }
/// Pauses this instance. }
/// </summary>
public void Pause() => Change(Timeout.Infinite, Timeout.Infinite); if(this._cycleDoneEvent.IsSet == false) {
return;
/// <inheritdoc /> }
public void Dispose()
{ this._cycleDoneEvent.Reset();
lock (_syncLock)
{ try {
if (_isDisposed == true || _isDisposing == true) this._userCallback(state);
return; } finally {
this._cycleDoneEvent?.Set();
_isDisposing.Value = true; _ = this._backingTimer?.Change(this._period, Timeout.Infinite);
} }
}
try }
{
_cycleDoneEvent.Wait();
_cycleDoneEvent.Dispose();
Pause();
_backingTimer.Dispose();
}
finally
{
_isDisposed.Value = true;
_isDisposing.Value = false;
}
}
/// <summary>
/// Logic that runs every time the timer hits the due time.
/// </summary>
/// <param name="state">The state.</param>
private void InternalCallback(object state)
{
lock (_syncLock)
{
if (IsDisposed || IsDisposing)
return;
}
if (_cycleDoneEvent.IsSet == false)
return;
_cycleDoneEvent.Reset();
try
{
_userCallback(state);
}
finally
{
_cycleDoneEvent?.Set();
_backingTimer?.Change(_period, Timeout.Infinite);
}
}
}
} }

View File

@ -1,24 +1,22 @@
using System; using System;
namespace Swan.Threading namespace Swan.Threading {
{ /// <summary>
/// Defines a generic interface for synchronized locking mechanisms.
/// </summary>
public interface ISyncLocker : IDisposable {
/// <summary> /// <summary>
/// Defines a generic interface for synchronized locking mechanisms. /// Acquires a writer lock.
/// The lock is released when the returned locking object is disposed.
/// </summary> /// </summary>
public interface ISyncLocker : IDisposable /// <returns>A disposable locking object.</returns>
{ IDisposable AcquireWriterLock();
/// <summary>
/// Acquires a writer lock. /// <summary>
/// The lock is released when the returned locking object is disposed. /// Acquires a reader lock.
/// </summary> /// The lock is released when the returned locking object is disposed.
/// <returns>A disposable locking object.</returns> /// </summary>
IDisposable AcquireWriterLock(); /// <returns>A disposable locking object.</returns>
IDisposable AcquireReaderLock();
/// <summary> }
/// Acquires a reader lock.
/// The lock is released when the returned locking object is disposed.
/// </summary>
/// <returns>A disposable locking object.</returns>
IDisposable AcquireReaderLock();
}
} }

View File

@ -1,57 +1,63 @@
using System; using System;
namespace Swan.Threading namespace Swan.Threading {
{ /// <summary>
/// Provides a generalized API for ManualResetEvent and ManualResetEventSlim.
/// </summary>
/// <seealso cref="IDisposable" />
public interface IWaitEvent : IDisposable {
/// <summary> /// <summary>
/// Provides a generalized API for ManualResetEvent and ManualResetEventSlim. /// Gets a value indicating whether the event is in the completed state.
/// </summary> /// </summary>
/// <seealso cref="IDisposable" /> Boolean IsCompleted {
public interface IWaitEvent : IDisposable get;
{ }
/// <summary>
/// Gets a value indicating whether the event is in the completed state. /// <summary>
/// </summary> /// Gets a value indicating whether the Begin method has been called.
bool IsCompleted { get; } /// It returns false after the Complete method is called.
/// </summary>
/// <summary> Boolean IsInProgress {
/// Gets a value indicating whether the Begin method has been called. get;
/// It returns false after the Complete method is called. }
/// </summary>
bool IsInProgress { get; } /// <summary>
/// Returns true if the underlying handle is not closed and it is still valid.
/// <summary> /// </summary>
/// Returns true if the underlying handle is not closed and it is still valid. Boolean IsValid {
/// </summary> get;
bool IsValid { get; } }
/// <summary> /// <summary>
/// Gets a value indicating whether this instance is disposed. /// Gets a value indicating whether this instance is disposed.
/// </summary> /// </summary>
bool IsDisposed { get; } Boolean IsDisposed {
get;
/// <summary> }
/// Enters the state in which waiters need to wait.
/// All future waiters will block when they call the Wait method. /// <summary>
/// </summary> /// Enters the state in which waiters need to wait.
void Begin(); /// All future waiters will block when they call the Wait method.
/// </summary>
/// <summary> void Begin();
/// Leaves the state in which waiters need to wait.
/// All current waiters will continue. /// <summary>
/// </summary> /// Leaves the state in which waiters need to wait.
void Complete(); /// All current waiters will continue.
/// </summary>
/// <summary> void Complete();
/// Waits for the event to be completed.
/// </summary> /// <summary>
void Wait(); /// Waits for the event to be completed.
/// </summary>
/// <summary> void Wait();
/// Waits for the event to be completed.
/// Returns <c>true</c> when there was no timeout. False if the timeout was reached. /// <summary>
/// </summary> /// Waits for the event to be completed.
/// <param name="timeout">The maximum amount of time to wait for.</param> /// Returns <c>true</c> when there was no timeout. False if the timeout was reached.
/// <returns><c>true</c> when there was no timeout. <c>false</c> if the timeout was reached.</returns> /// </summary>
bool Wait(TimeSpan timeout); /// <param name="timeout">The maximum amount of time to wait for.</param>
} /// <returns><c>true</c> when there was no timeout. <c>false</c> if the timeout was reached.</returns>
Boolean Wait(TimeSpan timeout);
}
} }

View File

@ -1,69 +1,77 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Swan.Threading namespace Swan.Threading {
{ /// <summary>
/// Defines a standard API to control background application workers.
/// </summary>
public interface IWorker {
/// <summary> /// <summary>
/// Defines a standard API to control background application workers. /// Gets the current state of the worker.
/// </summary> /// </summary>
public interface IWorker WorkerState WorkerState {
{ get;
/// <summary> }
/// Gets the current state of the worker.
/// </summary> /// <summary>
WorkerState WorkerState { get; } /// Gets a value indicating whether this instance is disposed.
/// </summary>
/// <summary> /// <value>
/// Gets a value indicating whether this instance is disposed. /// <c>true</c> if this instance is disposed; otherwise, <c>false</c>.
/// </summary> /// </value>
/// <value> Boolean IsDisposed {
/// <c>true</c> if this instance is disposed; otherwise, <c>false</c>. get;
/// </value> }
bool IsDisposed { get; }
/// <summary>
/// <summary> /// Gets a value indicating whether this instance is currently being disposed.
/// Gets a value indicating whether this instance is currently being disposed. /// </summary>
/// </summary> /// <value>
/// <value> /// <c>true</c> if this instance is disposing; otherwise, <c>false</c>.
/// <c>true</c> if this instance is disposing; otherwise, <c>false</c>. /// </value>
/// </value> Boolean IsDisposing {
bool IsDisposing { get; } get;
}
/// <summary>
/// Gets or sets the time interval used to execute cycles. /// <summary>
/// </summary> /// Gets or sets the time interval used to execute cycles.
TimeSpan Period { get; set; } /// </summary>
TimeSpan Period {
/// <summary> get; set;
/// Gets the name identifier of this worker. }
/// </summary>
string Name { get; } /// <summary>
/// Gets the name identifier of this worker.
/// <summary> /// </summary>
/// Starts execution of worker cycles. String Name {
/// </summary> get;
/// <returns>The awaitable task.</returns> }
Task<WorkerState> StartAsync();
/// <summary>
/// <summary> /// Starts execution of worker cycles.
/// Pauses execution of worker cycles. /// </summary>
/// </summary> /// <returns>The awaitable task.</returns>
/// <returns>The awaitable task.</returns> Task<WorkerState> StartAsync();
Task<WorkerState> PauseAsync();
/// <summary>
/// <summary> /// Pauses execution of worker cycles.
/// Resumes execution of worker cycles. /// </summary>
/// </summary> /// <returns>The awaitable task.</returns>
/// <returns>The awaitable task.</returns> Task<WorkerState> PauseAsync();
Task<WorkerState> ResumeAsync();
/// <summary>
/// <summary> /// Resumes execution of worker cycles.
/// Permanently stops execution of worker cycles. /// </summary>
/// An interrupt is always sent to the worker. If you wish to stop /// <returns>The awaitable task.</returns>
/// the worker without interrupting then call the <see cref="PauseAsync"/> Task<WorkerState> ResumeAsync();
/// method, await it, and finally call the <see cref="StopAsync"/> method.
/// </summary> /// <summary>
/// <returns>The awaitable task.</returns> /// Permanently stops execution of worker cycles.
Task<WorkerState> StopAsync(); /// An interrupt is always sent to the worker. If you wish to stop
} /// the worker without interrupting then call the <see cref="PauseAsync"/>
/// method, await it, and finally call the <see cref="StopAsync"/> method.
/// </summary>
/// <returns>The awaitable task.</returns>
Task<WorkerState> StopAsync();
}
} }

View File

@ -1,21 +1,20 @@
using System.Threading; using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Swan.Threading namespace Swan.Threading {
{ /// <summary>
/// An interface for a worker cycle delay provider.
/// </summary>
public interface IWorkerDelayProvider {
/// <summary> /// <summary>
/// An interface for a worker cycle delay provider. /// Suspends execution queues a new cycle for execution. The delay is given in
/// milliseconds. When overridden in a derived class the wait handle will be set
/// whenever an interrupt is received.
/// </summary> /// </summary>
public interface IWorkerDelayProvider /// <param name="wantedDelay">The remaining delay to wait for in the cycle.</param>
{ /// <param name="delayTask">Contains a reference to a task with the scheduled period delay.</param>
/// <summary> /// <param name="token">The cancellation token to cancel waiting.</param>
/// Suspends execution queues a new cycle for execution. The delay is given in void ExecuteCycleDelay(Int32 wantedDelay, Task delayTask, CancellationToken token);
/// milliseconds. When overridden in a derived class the wait handle will be set }
/// whenever an interrupt is received.
/// </summary>
/// <param name="wantedDelay">The remaining delay to wait for in the cycle.</param>
/// <param name="delayTask">Contains a reference to a task with the scheduled period delay.</param>
/// <param name="token">The cancellation token to cancel waiting.</param>
void ExecuteCycleDelay(int wantedDelay, Task delayTask, CancellationToken token);
}
} }

Some files were not shown because too many files have changed in this diff Show More