Coding style
This commit is contained in:
@@ -1,159 +1,146 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Attributes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Provides methods to parse command line arguments.
|
||||
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
|
||||
/// </summary>
|
||||
public partial class ArgumentParser
|
||||
{
|
||||
private sealed class Validator
|
||||
{
|
||||
private readonly object _instance;
|
||||
private readonly IEnumerable<string> _args;
|
||||
private readonly List<PropertyInfo> _updatedList = new List<PropertyInfo>();
|
||||
private readonly ArgumentParserSettings _settings;
|
||||
|
||||
private readonly PropertyInfo[] _properties;
|
||||
|
||||
public Validator(
|
||||
PropertyInfo[] properties,
|
||||
IEnumerable<string> args,
|
||||
object instance,
|
||||
ArgumentParserSettings settings)
|
||||
{
|
||||
_args = args;
|
||||
_instance = instance;
|
||||
_settings = settings;
|
||||
_properties = properties;
|
||||
|
||||
PopulateInstance();
|
||||
SetDefaultValues();
|
||||
GetRequiredList();
|
||||
}
|
||||
|
||||
public List<string> UnknownList { get; } = new List<string>();
|
||||
public List<string> RequiredList { get; } = new List<string>();
|
||||
|
||||
public bool IsValid() => (_settings.IgnoreUnknownArguments || !UnknownList.Any()) && !RequiredList.Any();
|
||||
|
||||
public IEnumerable<ArgumentOptionAttribute> GetPropertiesOptions()
|
||||
=> _properties.Select(p => Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(p))
|
||||
.Where(x => x != null);
|
||||
|
||||
private void GetRequiredList()
|
||||
{
|
||||
foreach (var targetProperty in _properties)
|
||||
{
|
||||
var optionAttr = Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(targetProperty);
|
||||
|
||||
if (optionAttr == null || optionAttr.Required == false)
|
||||
continue;
|
||||
|
||||
if (targetProperty.GetValue(_instance) == null)
|
||||
{
|
||||
RequiredList.Add(optionAttr.LongName ?? optionAttr.ShortName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetDefaultValues()
|
||||
{
|
||||
foreach (var targetProperty in _properties.Except(_updatedList))
|
||||
{
|
||||
var optionAttr = Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(targetProperty);
|
||||
|
||||
var defaultValue = optionAttr?.DefaultValue;
|
||||
|
||||
if (defaultValue == null)
|
||||
continue;
|
||||
|
||||
if (SetPropertyValue(targetProperty, defaultValue.ToString(), _instance, optionAttr))
|
||||
_updatedList.Add(targetProperty);
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateInstance()
|
||||
{
|
||||
const char dash = '-';
|
||||
var propertyName = string.Empty;
|
||||
|
||||
foreach (var arg in _args)
|
||||
{
|
||||
var ignoreSetValue = string.IsNullOrWhiteSpace(propertyName);
|
||||
|
||||
if (ignoreSetValue)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(arg) || arg[0] != dash) continue;
|
||||
|
||||
propertyName = arg.Substring(1);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(propertyName) && propertyName[0] == dash)
|
||||
propertyName = propertyName.Substring(1);
|
||||
}
|
||||
|
||||
var targetProperty = TryGetProperty(propertyName);
|
||||
|
||||
if (targetProperty == null)
|
||||
{
|
||||
// Skip if the property is not found
|
||||
UnknownList.Add(propertyName);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ignoreSetValue && SetPropertyValue(targetProperty, arg, _instance))
|
||||
{
|
||||
_updatedList.Add(targetProperty);
|
||||
propertyName = string.Empty;
|
||||
}
|
||||
else if (targetProperty.PropertyType == typeof(bool))
|
||||
{
|
||||
// If the arg is a boolean property set it to true.
|
||||
targetProperty.SetValue(_instance, true);
|
||||
|
||||
_updatedList.Add(targetProperty);
|
||||
propertyName = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(propertyName))
|
||||
{
|
||||
UnknownList.Add(propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
private bool SetPropertyValue(
|
||||
PropertyInfo targetProperty,
|
||||
string propertyValueString,
|
||||
object result,
|
||||
ArgumentOptionAttribute optionAttr = null)
|
||||
{
|
||||
if (targetProperty.PropertyType.GetTypeInfo().IsEnum)
|
||||
{
|
||||
var parsedValue = Enum.Parse(
|
||||
targetProperty.PropertyType,
|
||||
propertyValueString,
|
||||
_settings.CaseInsensitiveEnumValues);
|
||||
|
||||
targetProperty.SetValue(result, Enum.ToObject(targetProperty.PropertyType, parsedValue));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return targetProperty.PropertyType.IsArray
|
||||
? targetProperty.TrySetArray(propertyValueString.Split(optionAttr?.Separator ?? ','), result)
|
||||
: targetProperty.TrySetBasicType(propertyValueString, result);
|
||||
}
|
||||
|
||||
private PropertyInfo TryGetProperty(string propertyName)
|
||||
=> _properties.FirstOrDefault(p =>
|
||||
string.Equals(Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(p)?.LongName, propertyName, _settings.NameComparer) ||
|
||||
string.Equals(Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(p)?.ShortName, propertyName, _settings.NameComparer));
|
||||
}
|
||||
}
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Unosquare.Swan.Attributes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unosquare.Swan.Components {
|
||||
/// <summary>
|
||||
/// Provides methods to parse command line arguments.
|
||||
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
|
||||
/// </summary>
|
||||
public partial class ArgumentParser {
|
||||
private sealed class Validator {
|
||||
private readonly Object _instance;
|
||||
private readonly IEnumerable<String> _args;
|
||||
private readonly List<PropertyInfo> _updatedList = new List<PropertyInfo>();
|
||||
private readonly ArgumentParserSettings _settings;
|
||||
|
||||
private readonly PropertyInfo[] _properties;
|
||||
|
||||
public Validator(
|
||||
PropertyInfo[] properties,
|
||||
IEnumerable<String> args,
|
||||
Object instance,
|
||||
ArgumentParserSettings settings) {
|
||||
this._args = args;
|
||||
this._instance = instance;
|
||||
this._settings = settings;
|
||||
this._properties = properties;
|
||||
|
||||
this.PopulateInstance();
|
||||
this.SetDefaultValues();
|
||||
this.GetRequiredList();
|
||||
}
|
||||
|
||||
public List<String> UnknownList { get; } = new List<String>();
|
||||
public List<String> RequiredList { get; } = new List<String>();
|
||||
|
||||
public Boolean IsValid() => (this._settings.IgnoreUnknownArguments || !this.UnknownList.Any()) && !this.RequiredList.Any();
|
||||
|
||||
public IEnumerable<ArgumentOptionAttribute> GetPropertiesOptions()
|
||||
=> this._properties.Select(p => Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(p))
|
||||
.Where(x => x != null);
|
||||
|
||||
private void GetRequiredList() {
|
||||
foreach(PropertyInfo targetProperty in this._properties) {
|
||||
ArgumentOptionAttribute optionAttr = Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(targetProperty);
|
||||
|
||||
if(optionAttr == null || optionAttr.Required == false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(targetProperty.GetValue(this._instance) == null) {
|
||||
this.RequiredList.Add(optionAttr.LongName ?? optionAttr.ShortName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetDefaultValues() {
|
||||
foreach(PropertyInfo targetProperty in this._properties.Except(this._updatedList)) {
|
||||
ArgumentOptionAttribute optionAttr = Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(targetProperty);
|
||||
|
||||
Object defaultValue = optionAttr?.DefaultValue;
|
||||
|
||||
if(defaultValue == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(this.SetPropertyValue(targetProperty, defaultValue.ToString(), this._instance, optionAttr)) {
|
||||
this._updatedList.Add(targetProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateInstance() {
|
||||
const Char dash = '-';
|
||||
String propertyName = String.Empty;
|
||||
|
||||
foreach(String arg in this._args) {
|
||||
Boolean ignoreSetValue = String.IsNullOrWhiteSpace(propertyName);
|
||||
|
||||
if(ignoreSetValue) {
|
||||
if(String.IsNullOrWhiteSpace(arg) || arg[0] != dash) {
|
||||
continue;
|
||||
}
|
||||
|
||||
propertyName = arg.Substring(1);
|
||||
|
||||
if(!String.IsNullOrWhiteSpace(propertyName) && propertyName[0] == dash) {
|
||||
propertyName = propertyName.Substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
PropertyInfo targetProperty = this.TryGetProperty(propertyName);
|
||||
|
||||
if(targetProperty == null) {
|
||||
// Skip if the property is not found
|
||||
this.UnknownList.Add(propertyName);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!ignoreSetValue && this.SetPropertyValue(targetProperty, arg, this._instance)) {
|
||||
this._updatedList.Add(targetProperty);
|
||||
propertyName = String.Empty;
|
||||
} else if(targetProperty.PropertyType == typeof(Boolean)) {
|
||||
// If the arg is a boolean property set it to true.
|
||||
targetProperty.SetValue(this._instance, true);
|
||||
|
||||
this._updatedList.Add(targetProperty);
|
||||
propertyName = String.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
if(!String.IsNullOrEmpty(propertyName)) {
|
||||
this.UnknownList.Add(propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
private Boolean SetPropertyValue(
|
||||
PropertyInfo targetProperty,
|
||||
String propertyValueString,
|
||||
Object result,
|
||||
ArgumentOptionAttribute optionAttr = null) {
|
||||
if(targetProperty.PropertyType.GetTypeInfo().IsEnum) {
|
||||
Object parsedValue = Enum.Parse(
|
||||
targetProperty.PropertyType,
|
||||
propertyValueString,
|
||||
this._settings.CaseInsensitiveEnumValues);
|
||||
|
||||
targetProperty.SetValue(result, Enum.ToObject(targetProperty.PropertyType, parsedValue));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return targetProperty.PropertyType.IsArray
|
||||
? targetProperty.TrySetArray(propertyValueString.Split(optionAttr?.Separator ?? ','), result)
|
||||
: targetProperty.TrySetBasicType(propertyValueString, result);
|
||||
}
|
||||
|
||||
private PropertyInfo TryGetProperty(String propertyName)
|
||||
=> this._properties.FirstOrDefault(p =>
|
||||
String.Equals(Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(p)?.LongName, propertyName, this._settings.NameComparer) ||
|
||||
String.Equals(Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(p)?.ShortName, propertyName, this._settings.NameComparer));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +1,52 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Attributes;
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Provides methods to parse command line arguments.
|
||||
/// </summary>
|
||||
public partial class ArgumentParser
|
||||
{
|
||||
private sealed class TypeResolver<T>
|
||||
{
|
||||
private readonly string _selectedVerb;
|
||||
|
||||
private PropertyInfo[] _properties;
|
||||
|
||||
public TypeResolver(string selectedVerb)
|
||||
{
|
||||
_selectedVerb = selectedVerb;
|
||||
}
|
||||
|
||||
public PropertyInfo[] GetProperties() => _properties?.Any() == true ? _properties : null;
|
||||
|
||||
public object GetOptionsObject(T instance)
|
||||
{
|
||||
_properties = Runtime.PropertyTypeCache.RetrieveAllProperties<T>(true).ToArray();
|
||||
|
||||
if (!_properties.Any(x => x.GetCustomAttributes(typeof(VerbOptionAttribute), false).Any()))
|
||||
return instance;
|
||||
|
||||
var selectedVerb = string.IsNullOrWhiteSpace(_selectedVerb)
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Unosquare.Swan.Attributes;
|
||||
using System;
|
||||
|
||||
namespace Unosquare.Swan.Components {
|
||||
/// <summary>
|
||||
/// Provides methods to parse command line arguments.
|
||||
/// </summary>
|
||||
public partial class ArgumentParser {
|
||||
private sealed class TypeResolver<T> {
|
||||
private readonly String _selectedVerb;
|
||||
|
||||
private PropertyInfo[] _properties;
|
||||
|
||||
public TypeResolver(String selectedVerb) => this._selectedVerb = selectedVerb;
|
||||
|
||||
public PropertyInfo[] GetProperties() => this._properties?.Any() == true ? this._properties : null;
|
||||
|
||||
public Object GetOptionsObject(T instance) {
|
||||
this._properties = Runtime.PropertyTypeCache.RetrieveAllProperties<T>(true).ToArray();
|
||||
|
||||
if(!this._properties.Any(x => x.GetCustomAttributes(typeof(VerbOptionAttribute), false).Any())) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
PropertyInfo selectedVerb = String.IsNullOrWhiteSpace(this._selectedVerb)
|
||||
? null
|
||||
: _properties.FirstOrDefault(x =>
|
||||
Runtime.AttributeCache.RetrieveOne<VerbOptionAttribute>(x).Name.Equals(_selectedVerb));
|
||||
|
||||
if (selectedVerb == null) return null;
|
||||
|
||||
var type = instance.GetType();
|
||||
|
||||
var verbProperty = type.GetProperty(selectedVerb.Name);
|
||||
|
||||
if (verbProperty?.GetValue(instance) == null)
|
||||
{
|
||||
var propertyInstance = Activator.CreateInstance(selectedVerb.PropertyType);
|
||||
verbProperty?.SetValue(instance, propertyInstance);
|
||||
}
|
||||
|
||||
_properties = Runtime.PropertyTypeCache.RetrieveAllProperties(selectedVerb.PropertyType, true)
|
||||
.ToArray();
|
||||
|
||||
return verbProperty?.GetValue(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
: this._properties.FirstOrDefault(x =>
|
||||
Runtime.AttributeCache.RetrieveOne<VerbOptionAttribute>(x).Name.Equals(this._selectedVerb));
|
||||
|
||||
if(selectedVerb == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Type type = instance.GetType();
|
||||
|
||||
PropertyInfo verbProperty = type.GetProperty(selectedVerb.Name);
|
||||
|
||||
if(verbProperty?.GetValue(instance) == null) {
|
||||
Object propertyInstance = Activator.CreateInstance(selectedVerb.PropertyType);
|
||||
verbProperty?.SetValue(instance, propertyInstance);
|
||||
}
|
||||
|
||||
this._properties = Runtime.PropertyTypeCache.RetrieveAllProperties(selectedVerb.PropertyType, true)
|
||||
.ToArray();
|
||||
|
||||
return verbProperty?.GetValue(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,230 +1,228 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Attributes;
|
||||
using System.Linq;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unosquare.Swan.Attributes;
|
||||
using System.Linq;
|
||||
|
||||
namespace Unosquare.Swan.Components {
|
||||
/// <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 Unosquare.Swan;
|
||||
/// using Unosquare.Swan.Attributes;
|
||||
///
|
||||
/// static void Main(string[] args)
|
||||
/// {
|
||||
/// // create an instance of the Options class
|
||||
/// var options = new Options();
|
||||
///
|
||||
/// // parse the supplied command-line arguments into the options object
|
||||
/// var res = Runtime.ArgumentParser.ParseArguments(args, 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 Unosquare.Swan;
|
||||
/// using Unosquare.Swan.Attributes;
|
||||
///
|
||||
/// 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>
|
||||
/// Provides methods to parse command line arguments.
|
||||
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
|
||||
/// Initializes a new instance of the <see cref="ArgumentParser"/> class.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The following example shows how to parse CLI arguments into objects.
|
||||
/// <code>
|
||||
/// class Example
|
||||
/// {
|
||||
/// using System;
|
||||
/// using Unosquare.Swan;
|
||||
/// using Unosquare.Swan.Attributes;
|
||||
///
|
||||
/// static void Main(string[] args)
|
||||
/// {
|
||||
/// // create an instance of the Options class
|
||||
/// var options = new Options();
|
||||
///
|
||||
/// // parse the supplied command-line arguments into the options object
|
||||
/// var res = Runtime.ArgumentParser.ParseArguments(args, 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 Unosquare.Swan;
|
||||
/// using Unosquare.Swan.Attributes;
|
||||
///
|
||||
/// 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>
|
||||
/// Initializes a new instance of the <see cref="ArgumentParser"/> class.
|
||||
/// </summary>
|
||||
public ArgumentParser()
|
||||
: this(new ArgumentParserSettings())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArgumentParser" /> class,
|
||||
/// configurable with <see cref="ArgumentParserSettings" /> using a delegate.
|
||||
/// </summary>
|
||||
/// <param name="parseSettings">The parse settings.</param>
|
||||
public ArgumentParser(ArgumentParserSettings parseSettings)
|
||||
{
|
||||
Settings = parseSettings ?? throw new ArgumentNullException(nameof(parseSettings));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance that implements <see cref="ArgumentParserSettings" /> in use.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The settings.
|
||||
/// </value>
|
||||
public ArgumentParserSettings Settings { get; }
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
var properties = typeResolver.GetProperties();
|
||||
|
||||
if (properties == null)
|
||||
throw new InvalidOperationException($"Type {typeof(T).Name} is not valid");
|
||||
|
||||
var validator = new Validator(properties, args, options, Settings);
|
||||
|
||||
if (validator.IsValid())
|
||||
return true;
|
||||
|
||||
ReportIssues(validator);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void ReportUnknownVerb<T>()
|
||||
{
|
||||
"No verb was specified".WriteLine(ConsoleColor.Red);
|
||||
"Valid verbs:".WriteLine(ConsoleColor.Cyan);
|
||||
|
||||
Runtime.PropertyTypeCache.RetrieveAllProperties<T>(true)
|
||||
.Select(x => Runtime.AttributeCache.RetrieveOne<VerbOptionAttribute>(x))
|
||||
.Where(x => x != null)
|
||||
.ToList()
|
||||
.ForEach(x => x.ToString().WriteLine(ConsoleColor.Cyan));
|
||||
}
|
||||
|
||||
private void ReportIssues(Validator validator)
|
||||
{
|
||||
#if !NETSTANDARD1_3
|
||||
if (Settings.WriteBanner)
|
||||
Runtime.WriteWelcomeBanner();
|
||||
public ArgumentParser()
|
||||
: this(new ArgumentParserSettings()) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArgumentParser" /> class,
|
||||
/// configurable with <see cref="ArgumentParserSettings" /> using a delegate.
|
||||
/// </summary>
|
||||
/// <param name="parseSettings">The parse settings.</param>
|
||||
public ArgumentParser(ArgumentParserSettings parseSettings) => this.Settings = parseSettings ?? throw new ArgumentNullException(nameof(parseSettings));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance that implements <see cref="ArgumentParserSettings" /> in use.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The settings.
|
||||
/// </value>
|
||||
public ArgumentParserSettings Settings {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <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 Boolean 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));
|
||||
}
|
||||
|
||||
TypeResolver<T> typeResolver = new TypeResolver<T>(args.FirstOrDefault());
|
||||
Object options = typeResolver.GetOptionsObject(instance);
|
||||
|
||||
if(options == null) {
|
||||
ReportUnknownVerb<T>();
|
||||
return false;
|
||||
}
|
||||
|
||||
System.Reflection.PropertyInfo[] properties = typeResolver.GetProperties();
|
||||
|
||||
if(properties == null) {
|
||||
throw new InvalidOperationException($"Type {typeof(T).Name} is not valid");
|
||||
}
|
||||
|
||||
Validator validator = new Validator(properties, args, options, this.Settings);
|
||||
|
||||
if(validator.IsValid()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.ReportIssues(validator);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void ReportUnknownVerb<T>() {
|
||||
"No verb was specified".WriteLine(ConsoleColor.Red);
|
||||
"Valid verbs:".WriteLine(ConsoleColor.Cyan);
|
||||
|
||||
Runtime.PropertyTypeCache.RetrieveAllProperties<T>(true)
|
||||
.Select(x => Runtime.AttributeCache.RetrieveOne<VerbOptionAttribute>(x))
|
||||
.Where(x => x != null)
|
||||
.ToList()
|
||||
.ForEach(x => x.ToString().WriteLine(ConsoleColor.Cyan));
|
||||
}
|
||||
|
||||
private void ReportIssues(Validator validator) {
|
||||
#if !NETSTANDARD1_3
|
||||
if(this.Settings.WriteBanner) {
|
||||
Runtime.WriteWelcomeBanner();
|
||||
}
|
||||
#endif
|
||||
|
||||
var options = validator.GetPropertiesOptions();
|
||||
|
||||
foreach (var option in options)
|
||||
{
|
||||
string.Empty.WriteLine();
|
||||
|
||||
// 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}) ";
|
||||
|
||||
$" {shortName}{comma}{longName}\t\t{defaultValue}{option.HelpText}".WriteLine(ConsoleColor.Cyan);
|
||||
}
|
||||
|
||||
string.Empty.WriteLine();
|
||||
" --help\t\tDisplay this help screen.".WriteLine(ConsoleColor.Cyan);
|
||||
|
||||
if (validator.UnknownList.Any())
|
||||
$"Unknown arguments: {string.Join(", ", validator.UnknownList)}".WriteLine(ConsoleColor.Red);
|
||||
|
||||
if (validator.RequiredList.Any())
|
||||
$"Required arguments: {string.Join(", ", validator.RequiredList)}".WriteLine(ConsoleColor.Red);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<ArgumentOptionAttribute> options = validator.GetPropertiesOptions();
|
||||
|
||||
foreach(ArgumentOptionAttribute option in options) {
|
||||
String.Empty.WriteLine();
|
||||
|
||||
// TODO: If Enum list values
|
||||
String shortName = String.IsNullOrWhiteSpace(option.ShortName) ? String.Empty : $"-{option.ShortName}";
|
||||
String longName = String.IsNullOrWhiteSpace(option.LongName) ? String.Empty : $"--{option.LongName}";
|
||||
String comma = String.IsNullOrWhiteSpace(shortName) || String.IsNullOrWhiteSpace(longName)
|
||||
? String.Empty
|
||||
: ", ";
|
||||
String defaultValue = option.DefaultValue == null ? String.Empty : $"(Default: {option.DefaultValue}) ";
|
||||
|
||||
$" {shortName}{comma}{longName}\t\t{defaultValue}{option.HelpText}".WriteLine(ConsoleColor.Cyan);
|
||||
}
|
||||
|
||||
String.Empty.WriteLine();
|
||||
" --help\t\tDisplay this help screen.".WriteLine(ConsoleColor.Cyan);
|
||||
|
||||
if(validator.UnknownList.Any()) {
|
||||
$"Unknown arguments: {String.Join(", ", validator.UnknownList)}".WriteLine(ConsoleColor.Red);
|
||||
}
|
||||
|
||||
if(validator.RequiredList.Any()) {
|
||||
$"Required arguments: {String.Join(", ", validator.RequiredList)}".WriteLine(ConsoleColor.Red);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +1,51 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
|
||||
using System;
|
||||
|
||||
namespace Unosquare.Swan.Components {
|
||||
/// <summary>
|
||||
/// Provides settings for <see cref="ArgumentParser"/>.
|
||||
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
|
||||
/// </summary>
|
||||
public class ArgumentParserSettings {
|
||||
/// <summary>
|
||||
/// Provides settings for <see cref="ArgumentParser"/>.
|
||||
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
|
||||
/// Gets or sets a value indicating whether [write banner].
|
||||
/// </summary>
|
||||
public class ArgumentParserSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [write banner].
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [write banner]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool WriteBanner { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether perform case sensitive comparisons.
|
||||
/// Note that case insensitivity only applies to <i>parameters</i>, not the values
|
||||
/// assigned to them (for example, enum parsing).
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [case sensitive]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool CaseSensitive { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether perform case sensitive comparisons of <i>values</i>.
|
||||
/// Note that case insensitivity only applies to <i>values</i>, not the parameters.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [case insensitive enum values]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool CaseInsensitiveEnumValues { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the parser shall move on to the next argument and ignore the given argument if it
|
||||
/// encounter an unknown arguments.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> to allow parsing the arguments with different class options that do not have all the arguments.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// This allows fragmented version class parsing, useful for project with add-on where add-ons also requires command line arguments but
|
||||
/// 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;
|
||||
}
|
||||
/// <value>
|
||||
/// <c>true</c> if [write banner]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public Boolean WriteBanner { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether perform case sensitive comparisons.
|
||||
/// Note that case insensitivity only applies to <i>parameters</i>, not the values
|
||||
/// assigned to them (for example, enum parsing).
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [case sensitive]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public Boolean CaseSensitive { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether perform case sensitive comparisons of <i>values</i>.
|
||||
/// Note that case insensitivity only applies to <i>values</i>, not the parameters.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [case insensitive enum values]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public Boolean CaseInsensitiveEnumValues { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the parser shall move on to the next argument and ignore the given argument if it
|
||||
/// encounter an unknown arguments.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> to allow parsing the arguments with different class options that do not have all the arguments.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// This allows fragmented version class parsing, useful for project with add-on where add-ons also requires command line arguments but
|
||||
/// when these are unknown by the main program at build time.
|
||||
/// </remarks>
|
||||
public Boolean IgnoreUnknownArguments { get; set; } = true;
|
||||
|
||||
internal StringComparison NameComparer => this.CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
|
||||
}
|
||||
}
|
||||
@@ -1,130 +1,122 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Unosquare.Swan.Components {
|
||||
/// <summary>
|
||||
/// A simple benchmarking class.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The following code demonstrates how to create a simple benchmark.
|
||||
/// <code>
|
||||
/// namespace Examples.Benchmark.Simple
|
||||
/// {
|
||||
/// using Unosquare.Swan.Components;
|
||||
///
|
||||
/// 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 class Benchmark {
|
||||
private static readonly Object SyncLock = new Object();
|
||||
private static readonly Dictionary<String, List<TimeSpan>> Measures = new Dictionary<String, List<TimeSpan>>();
|
||||
|
||||
/// <summary>
|
||||
/// A simple benchmarking class.
|
||||
/// Starts measuring with the given identifier.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The following code demonstrates how to create a simple benchmark.
|
||||
/// <code>
|
||||
/// namespace Examples.Benchmark.Simple
|
||||
/// {
|
||||
/// using Unosquare.Swan.Components;
|
||||
///
|
||||
/// 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 class Benchmark
|
||||
{
|
||||
private static readonly object SyncLock = new object();
|
||||
private static readonly Dictionary<string, List<TimeSpan>> Measures = new Dictionary<string, List<TimeSpan>>();
|
||||
|
||||
/// <summary>
|
||||
/// Starts measuring with the given identifier.
|
||||
/// </summary>
|
||||
/// <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);
|
||||
|
||||
/// <summary>
|
||||
/// Outputs the benchmark statistics.
|
||||
/// </summary>
|
||||
/// <returns>A string containing human-readable statistics.</returns>
|
||||
public static string Dump()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
lock (SyncLock)
|
||||
{
|
||||
foreach (var kvp in Measures)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a disposable benchmark unit.
|
||||
/// </summary>
|
||||
/// <seealso cref="IDisposable" />
|
||||
private sealed class BenchmarkUnit : IDisposable
|
||||
{
|
||||
private readonly string _identifier;
|
||||
private bool _isDisposed; // To detect redundant calls
|
||||
private Stopwatch _stopwatch = new Stopwatch();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BenchmarkUnit" /> class.
|
||||
/// </summary>
|
||||
/// <param name="identifier">The identifier.</param>
|
||||
public BenchmarkUnit(string identifier)
|
||||
{
|
||||
_identifier = identifier;
|
||||
_stopwatch.Start();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => Dispose(true);
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <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 (_isDisposed) return;
|
||||
|
||||
if (alsoManaged)
|
||||
{
|
||||
Add(_identifier, _stopwatch.Elapsed);
|
||||
_stopwatch?.Stop();
|
||||
}
|
||||
|
||||
_stopwatch = null;
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <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);
|
||||
|
||||
/// <summary>
|
||||
/// Outputs the benchmark statistics.
|
||||
/// </summary>
|
||||
/// <returns>A string containing human-readable statistics.</returns>
|
||||
public static String Dump() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
lock(SyncLock) {
|
||||
foreach(KeyValuePair<String, List<TimeSpan>> kvp in Measures) {
|
||||
_ = 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);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a disposable benchmark unit.
|
||||
/// </summary>
|
||||
/// <seealso cref="IDisposable" />
|
||||
private sealed class BenchmarkUnit : IDisposable {
|
||||
private readonly String _identifier;
|
||||
private Boolean _isDisposed; // To detect redundant calls
|
||||
private Stopwatch _stopwatch = new Stopwatch();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BenchmarkUnit" /> class.
|
||||
/// </summary>
|
||||
/// <param name="identifier">The identifier.</param>
|
||||
public BenchmarkUnit(String identifier) {
|
||||
this._identifier = identifier;
|
||||
this._stopwatch.Start();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => this.Dispose(true);
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <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(Boolean alsoManaged) {
|
||||
if(this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(alsoManaged) {
|
||||
Add(this._identifier, this._stopwatch.Elapsed);
|
||||
this._stopwatch?.Stop();
|
||||
}
|
||||
|
||||
this._stopwatch = null;
|
||||
this._isDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,42 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Unosquare.Swan.Components {
|
||||
/// <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>
|
||||
/// A thread-safe collection cache repository for types.
|
||||
/// Determines whether the cache contains the specified key.
|
||||
/// </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>
|
||||
/// Determines whether the cache contains the specified key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <returns><c>true</c> if the cache contains the key, otherwise <c>false</c>.</returns>
|
||||
public bool ContainsKey(Type key) => _data.Value.ContainsKey(key);
|
||||
|
||||
/// <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>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="factory">The factory.</param>
|
||||
/// <returns>
|
||||
/// An array of the properties stored for the specified type.
|
||||
/// </returns>
|
||||
/// <exception cref="System.ArgumentNullException">type.</exception>
|
||||
public IEnumerable<TValue> Retrieve(Type key, Func<Type, IEnumerable<TValue>> factory)
|
||||
{
|
||||
if (factory == null)
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
|
||||
return _data.Value.GetOrAdd(key, k => factory.Invoke(k).Where(item => item != null));
|
||||
}
|
||||
}
|
||||
/// <param name="key">The key.</param>
|
||||
/// <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);
|
||||
|
||||
/// <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>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="factory">The factory.</param>
|
||||
/// <returns>
|
||||
/// An array of the properties stored for the specified type.
|
||||
/// </returns>
|
||||
/// <exception cref="System.ArgumentNullException">type.</exception>
|
||||
public IEnumerable<TValue> Retrieve(Type key, Func<Type, IEnumerable<TValue>> factory) {
|
||||
if(factory == null) {
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return this._data.Value.GetOrAdd(key, k => factory.Invoke(k).Where(item => item != null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,171 +1,144 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Abstractions;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unosquare.Swan.Abstractions;
|
||||
|
||||
namespace Unosquare.Swan.Components {
|
||||
/// <summary>
|
||||
/// Provide Enumerations helpers with internal cache.
|
||||
/// </summary>
|
||||
public class EnumHelper
|
||||
: SingletonBase<CollectionCacheRepository<Tuple<String, Object>>> {
|
||||
/// <summary>
|
||||
/// Provide Enumerations helpers with internal cache.
|
||||
/// Gets all the names and enumerators from a specific Enum type.
|
||||
/// </summary>
|
||||
public class EnumHelper
|
||||
: SingletonBase<CollectionCacheRepository<Tuple<string, object>>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all the names and enumerators from a specific Enum type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the attribute to be retrieved.</typeparam>
|
||||
/// <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
|
||||
{
|
||||
return Instance.Retrieve(typeof(T), t => Enum.GetValues(t)
|
||||
.Cast<object>()
|
||||
.Select(item => Tuple.Create(Enum.GetName(t, item), item)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cached items with the enum item value.
|
||||
/// </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>> GetItemsWithValue<T>(bool humanize = true)
|
||||
where T : struct, IConvertible
|
||||
{
|
||||
return Retrieve<T>()
|
||||
.Select(x => Tuple.Create((int) x.Item2, humanize ? x.Item1.Humanize() : x.Item1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the flag values.
|
||||
/// </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>
|
||||
/// <returns>
|
||||
/// A list of values in the flag.
|
||||
/// </returns>
|
||||
public static IEnumerable<int> GetFlagValues<TEnum>(int value, bool ignoreZero = false)
|
||||
where TEnum : struct, IConvertible
|
||||
{
|
||||
return Retrieve<TEnum>()
|
||||
.Select(x => (int) x.Item2)
|
||||
.When(() => ignoreZero, q => q.Where(f => f != 0))
|
||||
.Where(x => (x & value) == x);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the flag values.
|
||||
/// </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>
|
||||
/// <returns>
|
||||
/// A list of values in the flag.
|
||||
/// </returns>
|
||||
public static IEnumerable<long> GetFlagValues<TEnum>(long value, bool ignoreZero = false)
|
||||
where TEnum : struct, IConvertible
|
||||
{
|
||||
return Retrieve<TEnum>()
|
||||
.Select(x => (long) x.Item2)
|
||||
.When(() => ignoreZero, q => q.Where(f => f != 0))
|
||||
.Where(x => (x & value) == x);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the flag values.
|
||||
/// </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>
|
||||
/// <returns>
|
||||
/// A list of values in the flag.
|
||||
/// </returns>
|
||||
public static IEnumerable<byte> GetFlagValues<TEnum>(byte value, bool ignoreZero = false)
|
||||
where TEnum : struct, IConvertible
|
||||
{
|
||||
return Retrieve<TEnum>()
|
||||
.Select(x => (byte) x.Item2)
|
||||
.When(() => ignoreZero, q => q.Where(f => f != 0))
|
||||
.Where(x => (x & value) == x);
|
||||
}
|
||||
|
||||
/// <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>(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));
|
||||
}
|
||||
}
|
||||
/// <typeparam name="T">The type of the attribute to be retrieved.</typeparam>
|
||||
/// <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 the cached items with the enum item value.
|
||||
/// </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<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>
|
||||
/// Gets the flag values.
|
||||
/// </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>
|
||||
/// <returns>
|
||||
/// A list of values in the flag.
|
||||
/// </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);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the flag values.
|
||||
/// </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>
|
||||
/// <returns>
|
||||
/// A list of values in the flag.
|
||||
/// </returns>
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the flag values.
|
||||
/// </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>
|
||||
/// <returns>
|
||||
/// A list of values in the flag.
|
||||
/// </returns>
|
||||
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);
|
||||
|
||||
/// <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>(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);
|
||||
|
||||
/// <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>(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);
|
||||
|
||||
/// <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, 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);
|
||||
|
||||
/// <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<Int32, String>> GetItemsWithIndex<T>(Boolean humanize = true)
|
||||
where T : struct, IConvertible {
|
||||
Int32 i = 0;
|
||||
|
||||
return Retrieve<T>()
|
||||
.Select(x => Tuple.Create(i++, humanize ? x.Item1.Humanize() : x.Item1));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,193 +1,183 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Unosquare.Swan.Components {
|
||||
/// <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>
|
||||
/// Represents a quick object comparer using the public properties of an object
|
||||
/// or the public members in a structure.
|
||||
/// Compare if two variables of the same type are equal.
|
||||
/// </summary>
|
||||
public static class ObjectComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare if two variables of the same type are equal.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of objects to compare.</typeparam>
|
||||
/// <param name="left">The left.</param>
|
||||
/// <param name="right">The right.</param>
|
||||
/// <returns><c>true</c> if the variables are equal; otherwise, <c>false</c>.</returns>
|
||||
public static bool AreEqual<T>(T left, T right) => AreEqual(left, right, typeof(T));
|
||||
|
||||
/// <summary>
|
||||
/// Compare if two variables of the same type are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The left.</param>
|
||||
/// <param name="right">The right.</param>
|
||||
/// <param name="targetType">Type of the target.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the variables are equal; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">targetType.</exception>
|
||||
public static bool AreEqual(object left, object right, Type targetType)
|
||||
{
|
||||
if (targetType == null)
|
||||
throw new ArgumentNullException(nameof(targetType));
|
||||
|
||||
if (Definitions.BasicTypesInfo.ContainsKey(targetType))
|
||||
return Equals(left, right);
|
||||
|
||||
if (targetType.IsValueType() || targetType.IsArray)
|
||||
return AreStructsEqual(left, right, targetType);
|
||||
|
||||
return AreObjectsEqual(left, right, targetType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare if two objects of the same type are equal.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of objects to compare.</typeparam>
|
||||
/// <param name="left">The left.</param>
|
||||
/// <param name="right">The right.</param>
|
||||
/// <returns><c>true</c> if the objects are equal; otherwise, <c>false</c>.</returns>
|
||||
public static bool AreObjectsEqual<T>(T left, T right)
|
||||
where T : class
|
||||
{
|
||||
return AreObjectsEqual(left, right, typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare if two objects of the same type are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The left.</param>
|
||||
/// <param name="right">The right.</param>
|
||||
/// <param name="targetType">Type of the target.</param>
|
||||
/// <returns><c>true</c> if the objects are equal; otherwise, <c>false</c>.</returns>
|
||||
/// <exception cref="ArgumentNullException">targetType.</exception>
|
||||
public static bool AreObjectsEqual(object left, object right, Type targetType)
|
||||
{
|
||||
if (targetType == null)
|
||||
throw new ArgumentNullException(nameof(targetType));
|
||||
|
||||
var properties = Runtime.PropertyTypeCache.RetrieveAllProperties(targetType).ToArray();
|
||||
|
||||
foreach (var propertyTarget in properties)
|
||||
{
|
||||
var targetPropertyGetMethod = propertyTarget.GetCacheGetMethod();
|
||||
|
||||
if (propertyTarget.PropertyType.IsArray)
|
||||
{
|
||||
var leftObj = targetPropertyGetMethod(left) as IEnumerable;
|
||||
var rightObj = targetPropertyGetMethod(right) as IEnumerable;
|
||||
|
||||
if (!AreEnumerationsEquals(leftObj, rightObj))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Equals(targetPropertyGetMethod(left), targetPropertyGetMethod(right)))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare if two structures of the same type are equal.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of structs to compare.</typeparam>
|
||||
/// <param name="left">The left.</param>
|
||||
/// <param name="right">The right.</param>
|
||||
/// <returns><c>true</c> if the structs are equal; otherwise, <c>false</c>.</returns>
|
||||
public static bool AreStructsEqual<T>(T left, T right)
|
||||
where T : struct
|
||||
{
|
||||
return AreStructsEqual(left, right, typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare if two structures of the same type are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The left.</param>
|
||||
/// <param name="right">The right.</param>
|
||||
/// <param name="targetType">Type of the target.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the structs are equal; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">targetType.</exception>
|
||||
public static bool AreStructsEqual(object left, object right, Type targetType)
|
||||
{
|
||||
if (targetType == null)
|
||||
throw new ArgumentNullException(nameof(targetType));
|
||||
|
||||
var fields = new List<MemberInfo>(Runtime.FieldTypeCache.RetrieveAllFields(targetType))
|
||||
.Union(Runtime.PropertyTypeCache.RetrieveAllProperties(targetType));
|
||||
|
||||
foreach (var targetMember in fields)
|
||||
{
|
||||
switch (targetMember)
|
||||
{
|
||||
case FieldInfo field:
|
||||
if (Equals(field.GetValue(left), field.GetValue(right)) == false)
|
||||
return false;
|
||||
break;
|
||||
case PropertyInfo property:
|
||||
var targetPropertyGetMethod = property.GetCacheGetMethod();
|
||||
|
||||
if (targetPropertyGetMethod != null &&
|
||||
!Equals(targetPropertyGetMethod(left), targetPropertyGetMethod(right)))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare if two enumerables are equal.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of enums to compare.</typeparam>
|
||||
/// <param name="left">The left.</param>
|
||||
/// <param name="right">The right.</param>
|
||||
/// <returns>
|
||||
/// True if two specified types are equal; otherwise, false.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// left
|
||||
/// or
|
||||
/// right.
|
||||
/// </exception>
|
||||
public static bool AreEnumerationsEquals<T>(T left, T right)
|
||||
where T : IEnumerable
|
||||
{
|
||||
if (Equals(left, default(T)))
|
||||
throw new ArgumentNullException(nameof(left));
|
||||
|
||||
if (Equals(right, default(T)))
|
||||
throw new ArgumentNullException(nameof(right));
|
||||
|
||||
var leftEnumerable = left.Cast<object>().ToArray();
|
||||
var rightEnumerable = right.Cast<object>().ToArray();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
/// <typeparam name="T">The type of objects to compare.</typeparam>
|
||||
/// <param name="left">The left.</param>
|
||||
/// <param name="right">The right.</param>
|
||||
/// <returns><c>true</c> if the variables are equal; otherwise, <c>false</c>.</returns>
|
||||
public static Boolean AreEqual<T>(T left, T right) => AreEqual(left, right, typeof(T));
|
||||
|
||||
/// <summary>
|
||||
/// Compare if two variables of the same type are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The left.</param>
|
||||
/// <param name="right">The right.</param>
|
||||
/// <param name="targetType">Type of the target.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the variables are equal; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">targetType.</exception>
|
||||
public static Boolean AreEqual(Object left, Object right, Type targetType) {
|
||||
if(targetType == null) {
|
||||
throw new ArgumentNullException(nameof(targetType));
|
||||
}
|
||||
|
||||
return Definitions.BasicTypesInfo.ContainsKey(targetType)
|
||||
? Equals(left, right)
|
||||
: targetType.IsValueType() || targetType.IsArray
|
||||
? AreStructsEqual(left, right, targetType)
|
||||
: AreObjectsEqual(left, right, targetType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare if two objects of the same type are equal.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of objects to compare.</typeparam>
|
||||
/// <param name="left">The left.</param>
|
||||
/// <param name="right">The right.</param>
|
||||
/// <returns><c>true</c> if the objects are equal; otherwise, <c>false</c>.</returns>
|
||||
public static Boolean AreObjectsEqual<T>(T left, T right)
|
||||
where T : class => AreObjectsEqual(left, right, typeof(T));
|
||||
|
||||
/// <summary>
|
||||
/// Compare if two objects of the same type are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The left.</param>
|
||||
/// <param name="right">The right.</param>
|
||||
/// <param name="targetType">Type of the target.</param>
|
||||
/// <returns><c>true</c> if the objects are equal; otherwise, <c>false</c>.</returns>
|
||||
/// <exception cref="ArgumentNullException">targetType.</exception>
|
||||
public static Boolean AreObjectsEqual(Object left, Object right, Type targetType) {
|
||||
if(targetType == null) {
|
||||
throw new ArgumentNullException(nameof(targetType));
|
||||
}
|
||||
|
||||
PropertyInfo[] properties = Runtime.PropertyTypeCache.RetrieveAllProperties(targetType).ToArray();
|
||||
|
||||
foreach(PropertyInfo propertyTarget in properties) {
|
||||
Func<Object, Object> targetPropertyGetMethod = propertyTarget.GetCacheGetMethod();
|
||||
|
||||
if(propertyTarget.PropertyType.IsArray) {
|
||||
IEnumerable leftObj = targetPropertyGetMethod(left) as IEnumerable;
|
||||
IEnumerable rightObj = targetPropertyGetMethod(right) as IEnumerable;
|
||||
|
||||
if(!AreEnumerationsEquals(leftObj, rightObj)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if(!Equals(targetPropertyGetMethod(left), targetPropertyGetMethod(right))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare if two structures of the same type are equal.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of structs to compare.</typeparam>
|
||||
/// <param name="left">The left.</param>
|
||||
/// <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));
|
||||
|
||||
/// <summary>
|
||||
/// Compare if two structures of the same type are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The left.</param>
|
||||
/// <param name="right">The right.</param>
|
||||
/// <param name="targetType">Type of the target.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the structs are equal; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">targetType.</exception>
|
||||
public static Boolean AreStructsEqual(Object left, Object right, Type targetType) {
|
||||
if(targetType == null) {
|
||||
throw new ArgumentNullException(nameof(targetType));
|
||||
}
|
||||
|
||||
IEnumerable<MemberInfo> fields = new List<MemberInfo>(Runtime.FieldTypeCache.RetrieveAllFields(targetType))
|
||||
.Union(Runtime.PropertyTypeCache.RetrieveAllProperties(targetType));
|
||||
|
||||
foreach(MemberInfo targetMember in fields) {
|
||||
switch(targetMember) {
|
||||
case FieldInfo field:
|
||||
if(Equals(field.GetValue(left), field.GetValue(right)) == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
case PropertyInfo property:
|
||||
Func<Object, Object> targetPropertyGetMethod = property.GetCacheGetMethod();
|
||||
|
||||
if(targetPropertyGetMethod != null &&
|
||||
!Equals(targetPropertyGetMethod(left), targetPropertyGetMethod(right))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare if two enumerables are equal.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of enums to compare.</typeparam>
|
||||
/// <param name="left">The left.</param>
|
||||
/// <param name="right">The right.</param>
|
||||
/// <returns>
|
||||
/// True if two specified types are equal; otherwise, false.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// left
|
||||
/// or
|
||||
/// right.
|
||||
/// </exception>
|
||||
public static Boolean AreEnumerationsEquals<T>(T left, T right)
|
||||
where T : IEnumerable {
|
||||
if(Equals(left, default(T))) {
|
||||
throw new ArgumentNullException(nameof(left));
|
||||
}
|
||||
|
||||
if(Equals(right, default(T))) {
|
||||
throw new ArgumentNullException(nameof(right));
|
||||
}
|
||||
|
||||
Object[] leftEnumerable = left.Cast<Object>().ToArray();
|
||||
Object[] rightEnumerable = right.Cast<Object>().ToArray();
|
||||
|
||||
if(leftEnumerable.Length != rightEnumerable.Length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for(Int32 i = 0; i < leftEnumerable.Length; i++) {
|
||||
Object leftEl = leftEnumerable[i];
|
||||
Object rightEl = rightEnumerable[i];
|
||||
|
||||
if(!AreEqual(leftEl, rightEl, leftEl.GetType())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,116 +1,116 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Abstractions;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Unosquare.Swan.Abstractions;
|
||||
|
||||
namespace Unosquare.Swan.Components {
|
||||
/// <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="Unosquare.Swan.Abstractions.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>
|
||||
/// Represents an object map.
|
||||
/// Maps the property.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">The type of the source.</typeparam>
|
||||
/// <typeparam name="TDestination">The type of the destination.</typeparam>
|
||||
/// <seealso cref="Unosquare.Swan.Abstractions.IObjectMap" />
|
||||
public class ObjectMap<TSource, TDestination> : IObjectMap
|
||||
{
|
||||
internal ObjectMap(IEnumerable<PropertyInfo> intersect)
|
||||
{
|
||||
SourceType = typeof(TSource);
|
||||
DestinationType = typeof(TDestination);
|
||||
Map = intersect.ToDictionary(
|
||||
property => DestinationType.GetProperty(property.Name),
|
||||
property => new List<PropertyInfo> {SourceType.GetProperty(property.Name)});
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Dictionary<PropertyInfo, List<PropertyInfo>> Map { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Type SourceType { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Type DestinationType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Maps the property.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
/// <param name="sourceProperty">The source property.</param>
|
||||
/// <returns>
|
||||
/// An object map representation of type of the destination property
|
||||
/// and type of the source property.
|
||||
/// </returns>
|
||||
public ObjectMap<TSource, TDestination> MapProperty
|
||||
<TDestinationProperty, TSourceProperty>(
|
||||
Expression<Func<TDestination, TDestinationProperty>> destinationProperty,
|
||||
Expression<Func<TSource, TSourceProperty>> sourceProperty)
|
||||
{
|
||||
var propertyDestinationInfo = (destinationProperty.Body as MemberExpression)?.Member as PropertyInfo;
|
||||
|
||||
if (propertyDestinationInfo == null)
|
||||
{
|
||||
throw new ArgumentException("Invalid destination expression", nameof(destinationProperty));
|
||||
}
|
||||
|
||||
var sourceMembers = GetSourceMembers(sourceProperty);
|
||||
|
||||
if (sourceMembers.Any() == false)
|
||||
{
|
||||
throw new ArgumentException("Invalid source expression", nameof(sourceProperty));
|
||||
}
|
||||
|
||||
// reverse order
|
||||
sourceMembers.Reverse();
|
||||
Map[propertyDestinationInfo] = sourceMembers;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the map property.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDestinationProperty">The type of the destination property.</typeparam>
|
||||
/// <param name="destinationProperty">The destination property.</param>
|
||||
/// <returns>
|
||||
/// An object map representation of type of the destination property
|
||||
/// and type of the source property.
|
||||
/// </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;
|
||||
}
|
||||
}
|
||||
/// <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>
|
||||
/// <param name="sourceProperty">The source property.</param>
|
||||
/// <returns>
|
||||
/// An object map representation of type of the destination property
|
||||
/// and type of the source property.
|
||||
/// </returns>
|
||||
public ObjectMap<TSource, TDestination> MapProperty
|
||||
<TDestinationProperty, TSourceProperty>(
|
||||
Expression<Func<TDestination, TDestinationProperty>> destinationProperty,
|
||||
Expression<Func<TSource, TSourceProperty>> sourceProperty) {
|
||||
PropertyInfo propertyDestinationInfo = (destinationProperty.Body as MemberExpression)?.Member as PropertyInfo;
|
||||
|
||||
if(propertyDestinationInfo == null) {
|
||||
throw new ArgumentException("Invalid destination expression", nameof(destinationProperty));
|
||||
}
|
||||
|
||||
List<PropertyInfo> sourceMembers = GetSourceMembers(sourceProperty);
|
||||
|
||||
if(sourceMembers.Any() == false) {
|
||||
throw new ArgumentException("Invalid source expression", nameof(sourceProperty));
|
||||
}
|
||||
|
||||
// reverse order
|
||||
sourceMembers.Reverse();
|
||||
this.Map[propertyDestinationInfo] = sourceMembers;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the map property.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDestinationProperty">The type of the destination property.</typeparam>
|
||||
/// <param name="destinationProperty">The destination property.</param>
|
||||
/// <returns>
|
||||
/// An object map representation of type of the destination property
|
||||
/// and type of the source property.
|
||||
/// </returns>
|
||||
/// <exception cref="System.Exception">Invalid destination expression.</exception>
|
||||
public ObjectMap<TSource, TDestination> RemoveMapProperty<TDestinationProperty>(
|
||||
Expression<Func<TDestination, TDestinationProperty>> destinationProperty) {
|
||||
PropertyInfo propertyDestinationInfo = (destinationProperty.Body as MemberExpression)?.Member as PropertyInfo;
|
||||
|
||||
if(propertyDestinationInfo == null) {
|
||||
throw new ArgumentException("Invalid destination expression", nameof(destinationProperty));
|
||||
}
|
||||
|
||||
if(this.Map.ContainsKey(propertyDestinationInfo)) {
|
||||
_ = this.Map.Remove(propertyDestinationInfo);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private static List<PropertyInfo> GetSourceMembers<TSourceProperty>(Expression<Func<TSource, TSourceProperty>> sourceProperty) {
|
||||
List<PropertyInfo> sourceMembers = new List<PropertyInfo>();
|
||||
MemberExpression initialExpression = sourceProperty.Body as MemberExpression;
|
||||
|
||||
while(true) {
|
||||
PropertyInfo propertySourceInfo = initialExpression?.Member as PropertyInfo;
|
||||
|
||||
if(propertySourceInfo == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
sourceMembers.Add(propertySourceInfo);
|
||||
initialExpression = initialExpression.Expression as MemberExpression;
|
||||
}
|
||||
|
||||
return sourceMembers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,411 +1,385 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Abstractions;
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Unosquare.Swan.Abstractions;
|
||||
|
||||
namespace Unosquare.Swan.Components {
|
||||
/// <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 Unosquare.Swan
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// class Person
|
||||
/// {
|
||||
/// public string Name { get; set; }
|
||||
/// public int Age { get; set; }
|
||||
/// }
|
||||
///
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// var obj = new { Name = "Søren", Age = 42 };
|
||||
///
|
||||
/// var person = Runtime.ObjectMapper.Map<Person>(obj);
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// The following code explains how to explicitly map certain properties.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan
|
||||
///
|
||||
/// 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<User, UserDto>()
|
||||
/// .MapProperty(d => d.Role, x => x.Role.Name);
|
||||
///
|
||||
/// // apply the previous map and retrieve a UserDto object
|
||||
/// var destination = mapper.Map<UserDto>(person);
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class ObjectMapper {
|
||||
private readonly List<IObjectMap> _maps = new List<IObjectMap>();
|
||||
|
||||
/// <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.
|
||||
/// Copies the specified source.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The following code explains how to map an object's properties into an instance of type T.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// class Person
|
||||
/// {
|
||||
/// public string Name { get; set; }
|
||||
/// public int Age { get; set; }
|
||||
/// }
|
||||
///
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// var obj = new { Name = "Søren", Age = 42 };
|
||||
///
|
||||
/// var person = Runtime.ObjectMapper.Map<Person>(obj);
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// The following code explains how to explicitly map certain properties.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan
|
||||
///
|
||||
/// 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<User, UserDto>()
|
||||
/// .MapProperty(d => d.Role, x => x.Role.Name);
|
||||
///
|
||||
/// // apply the previous map and retrieve a UserDto object
|
||||
/// var destination = mapper.Map<UserDto>(person);
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class ObjectMapper
|
||||
{
|
||||
private readonly List<IObjectMap> _maps = new List<IObjectMap>();
|
||||
|
||||
/// <summary>
|
||||
/// Copies the specified source.
|
||||
/// </summary>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="propertiesToCopy">The properties to copy.</param>
|
||||
/// <param name="ignoreProperties">The ignore properties.</param>
|
||||
/// <returns>
|
||||
/// Copied properties count.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// source
|
||||
/// or
|
||||
/// target.
|
||||
/// </exception>
|
||||
public static int Copy(
|
||||
object source,
|
||||
object target,
|
||||
string[] propertiesToCopy = null,
|
||||
string[] ignoreProperties = null)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
|
||||
if (target == null)
|
||||
throw new ArgumentNullException(nameof(target));
|
||||
|
||||
return Copy(
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="propertiesToCopy">The properties to copy.</param>
|
||||
/// <param name="ignoreProperties">The ignore properties.</param>
|
||||
/// <returns>
|
||||
/// Copied properties count.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// source
|
||||
/// or
|
||||
/// target.
|
||||
/// </exception>
|
||||
public static Int32 Copy(
|
||||
Object source,
|
||||
Object target,
|
||||
String[] propertiesToCopy = null,
|
||||
String[] ignoreProperties = null) {
|
||||
if(source == null) {
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
}
|
||||
|
||||
if(target == null) {
|
||||
throw new ArgumentNullException(nameof(target));
|
||||
}
|
||||
|
||||
return Copy(
|
||||
target,
|
||||
propertiesToCopy,
|
||||
ignoreProperties,
|
||||
GetSourceMap(source));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the specified source.
|
||||
/// </summary>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="propertiesToCopy">The properties to copy.</param>
|
||||
/// <param name="ignoreProperties">The ignore properties.</param>
|
||||
/// <returns>
|
||||
/// Copied properties count.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// source
|
||||
/// or
|
||||
/// target.
|
||||
/// </exception>
|
||||
public static int Copy(
|
||||
IDictionary<string, object> source,
|
||||
object target,
|
||||
string[] propertiesToCopy = null,
|
||||
string[] ignoreProperties = null)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
|
||||
if (target == null)
|
||||
throw new ArgumentNullException(nameof(target));
|
||||
|
||||
return Copy(
|
||||
GetSourceMap(source));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the specified source.
|
||||
/// </summary>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="propertiesToCopy">The properties to copy.</param>
|
||||
/// <param name="ignoreProperties">The ignore properties.</param>
|
||||
/// <returns>
|
||||
/// Copied properties count.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// source
|
||||
/// or
|
||||
/// target.
|
||||
/// </exception>
|
||||
public static Int32 Copy(
|
||||
IDictionary<String, Object> source,
|
||||
Object target,
|
||||
String[] propertiesToCopy = null,
|
||||
String[] ignoreProperties = null) {
|
||||
if(source == null) {
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
}
|
||||
|
||||
if(target == null) {
|
||||
throw new ArgumentNullException(nameof(target));
|
||||
}
|
||||
|
||||
return Copy(
|
||||
target,
|
||||
propertiesToCopy,
|
||||
ignoreProperties,
|
||||
source.ToDictionary(
|
||||
x => x.Key.ToLowerInvariant(),
|
||||
x => new TypeValuePair(typeof(object), x.Value)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the map.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">The type of the source.</typeparam>
|
||||
/// <typeparam name="TDestination">The type of the destination.</typeparam>
|
||||
/// <returns>
|
||||
/// An object map representation of type of the destination property
|
||||
/// and type of the source property.
|
||||
/// </returns>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// You can't create an existing map
|
||||
/// or
|
||||
/// Types doesn't match.
|
||||
/// </exception>
|
||||
public ObjectMap<TSource, TDestination> CreateMap<TSource, TDestination>()
|
||||
{
|
||||
if (_maps.Any(x => x.SourceType == typeof(TSource) && x.DestinationType == typeof(TDestination)))
|
||||
{
|
||||
throw new InvalidOperationException("You can't create an existing map");
|
||||
}
|
||||
|
||||
var sourceType = Runtime.PropertyTypeCache.RetrieveAllProperties<TSource>(true);
|
||||
var destinationType = Runtime.PropertyTypeCache.RetrieveAllProperties<TDestination>(true);
|
||||
|
||||
var intersect = sourceType.Intersect(destinationType, new PropertyInfoComparer()).ToArray();
|
||||
|
||||
if (intersect.Any() == false)
|
||||
{
|
||||
throw new InvalidOperationException("Types doesn't match");
|
||||
}
|
||||
|
||||
var map = new ObjectMap<TSource, TDestination>(intersect);
|
||||
|
||||
_maps.Add(map);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps the specified source.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDestination">The type of the destination.</typeparam>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="autoResolve">if set to <c>true</c> [automatic resolve].</param>
|
||||
/// <returns>
|
||||
/// A new instance of the map.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">source.</exception>
|
||||
/// <exception cref="InvalidOperationException">You can't map from type {source.GetType().Name} to {typeof(TDestination).Name}.</exception>
|
||||
public TDestination Map<TDestination>(object source, bool autoResolve = true)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
}
|
||||
|
||||
var destination = Activator.CreateInstance<TDestination>();
|
||||
var map = _maps
|
||||
.FirstOrDefault(x => x.SourceType == source.GetType() && x.DestinationType == typeof(TDestination));
|
||||
|
||||
if (map != null)
|
||||
{
|
||||
foreach (var property in map.Map)
|
||||
{
|
||||
var finalSource = property.Value.Aggregate(source,
|
||||
(current, sourceProperty) => sourceProperty.GetValue(current));
|
||||
|
||||
property.Key.SetValue(destination, finalSource);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!autoResolve)
|
||||
{
|
||||
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 Copy(
|
||||
object target,
|
||||
IEnumerable<string> propertiesToCopy,
|
||||
IEnumerable<string> ignoreProperties,
|
||||
Dictionary<string, TypeValuePair> sourceProperties)
|
||||
{
|
||||
// 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 = Runtime.PropertyTypeCache
|
||||
.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, target) ? 1 : 0);
|
||||
}
|
||||
|
||||
private static bool TrySetValue(KeyValuePair<PropertyInfo, TypeValuePair> property, object target)
|
||||
{
|
||||
try
|
||||
{
|
||||
SetValue(property, target);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// swallow
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void SetValue(KeyValuePair<PropertyInfo, TypeValuePair> property, object target)
|
||||
{
|
||||
if (property.Value.Type.GetTypeInfo().IsEnum)
|
||||
{
|
||||
property.Key.SetValue(target,
|
||||
Enum.ToObject(property.Key.PropertyType, property.Value.Value));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!property.Value.Type.IsValueType() && property.Key.PropertyType == property.Value.Type)
|
||||
{
|
||||
property.Key.SetValue(target, GetValue(property.Value.Value, property.Key.PropertyType));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (property.Key.PropertyType == typeof(bool))
|
||||
{
|
||||
property.Key.SetValue(target,
|
||||
Convert.ToBoolean(property.Value.Value));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
property.Key.TrySetBasicType(property.Value.Value, target);
|
||||
}
|
||||
|
||||
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 Array targetArray:
|
||||
for (var i = 0; i < sourceList.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
targetArray.SetValue(
|
||||
sourceList[i].GetType().IsValueType()
|
||||
? sourceList[i]
|
||||
: sourceList[i].CopyPropertiesToNew<object>(), i);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case IList sourceList when target is IList targetList:
|
||||
var addMethod = targetType.GetMethods()
|
||||
.FirstOrDefault(
|
||||
m => m.Name.Equals(Formatters.Json.AddMethodName) && m.IsPublic &&
|
||||
m.GetParameters().Length == 1);
|
||||
|
||||
if (addMethod == null) return target;
|
||||
|
||||
foreach (var item in sourceList)
|
||||
{
|
||||
try
|
||||
{
|
||||
targetList.Add(item.GetType().IsValueType()
|
||||
? item
|
||||
: item.CopyPropertiesToNew<object>());
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
source.CopyPropertiesTo(target);
|
||||
break;
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
private static Dictionary<string, TypeValuePair> GetSourceMap(object source)
|
||||
{
|
||||
// select distinct properties because they can be duplicated by inheritance
|
||||
var sourceProperties = Runtime.PropertyTypeCache
|
||||
.RetrieveFilteredProperties(source.GetType(), true, x => x.CanRead)
|
||||
.ToArray();
|
||||
|
||||
return sourceProperties
|
||||
.Select(x => x.Name)
|
||||
.Distinct()
|
||||
.ToDictionary(
|
||||
x => x.ToLowerInvariant(),
|
||||
x => new TypeValuePair(sourceProperties.First(y => y.Name == x).PropertyType,
|
||||
sourceProperties.First(y => y.Name == x).GetValue(source)));
|
||||
}
|
||||
|
||||
internal class TypeValuePair
|
||||
{
|
||||
public TypeValuePair(Type type, object value)
|
||||
{
|
||||
Type = type;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public Type Type { get; }
|
||||
|
||||
public object Value { get; }
|
||||
}
|
||||
|
||||
internal class PropertyInfoComparer : IEqualityComparer<PropertyInfo>
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
x => new TypeValuePair(typeof(Object), x.Value)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the map.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">The type of the source.</typeparam>
|
||||
/// <typeparam name="TDestination">The type of the destination.</typeparam>
|
||||
/// <returns>
|
||||
/// An object map representation of type of the destination property
|
||||
/// and type of the source property.
|
||||
/// </returns>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// You can't create an existing map
|
||||
/// or
|
||||
/// Types doesn't match.
|
||||
/// </exception>
|
||||
public ObjectMap<TSource, TDestination> CreateMap<TSource, TDestination>() {
|
||||
if(this._maps.Any(x => x.SourceType == typeof(TSource) && x.DestinationType == typeof(TDestination))) {
|
||||
throw new InvalidOperationException("You can't create an existing map");
|
||||
}
|
||||
|
||||
IEnumerable<PropertyInfo> sourceType = Runtime.PropertyTypeCache.RetrieveAllProperties<TSource>(true);
|
||||
IEnumerable<PropertyInfo> destinationType = Runtime.PropertyTypeCache.RetrieveAllProperties<TDestination>(true);
|
||||
|
||||
PropertyInfo[] intersect = sourceType.Intersect(destinationType, new PropertyInfoComparer()).ToArray();
|
||||
|
||||
if(intersect.Any() == false) {
|
||||
throw new InvalidOperationException("Types doesn't match");
|
||||
}
|
||||
|
||||
ObjectMap<TSource, TDestination> map = new ObjectMap<TSource, TDestination>(intersect);
|
||||
|
||||
this._maps.Add(map);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps the specified source.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDestination">The type of the destination.</typeparam>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="autoResolve">if set to <c>true</c> [automatic resolve].</param>
|
||||
/// <returns>
|
||||
/// A new instance of the map.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">source.</exception>
|
||||
/// <exception cref="InvalidOperationException">You can't map from type {source.GetType().Name} to {typeof(TDestination).Name}.</exception>
|
||||
public TDestination Map<TDestination>(Object source, Boolean autoResolve = true) {
|
||||
if(source == null) {
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
}
|
||||
|
||||
TDestination destination = Activator.CreateInstance<TDestination>();
|
||||
IObjectMap map = this._maps
|
||||
.FirstOrDefault(x => x.SourceType == source.GetType() && x.DestinationType == typeof(TDestination));
|
||||
|
||||
if(map != null) {
|
||||
foreach(KeyValuePair<PropertyInfo, List<PropertyInfo>> property in map.Map) {
|
||||
Object finalSource = property.Value.Aggregate(source,
|
||||
(current, sourceProperty) => sourceProperty.GetValue(current));
|
||||
|
||||
property.Key.SetValue(destination, finalSource);
|
||||
}
|
||||
} else {
|
||||
if(!autoResolve) {
|
||||
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 Int32 Copy(
|
||||
Object target,
|
||||
IEnumerable<String> propertiesToCopy,
|
||||
IEnumerable<String> ignoreProperties,
|
||||
Dictionary<String, TypeValuePair> sourceProperties) {
|
||||
// Filter properties
|
||||
IEnumerable<String> requiredProperties = propertiesToCopy?
|
||||
.Where(p => !String.IsNullOrWhiteSpace(p))
|
||||
.Select(p => p.ToLowerInvariant());
|
||||
|
||||
IEnumerable<String> ignoredProperties = ignoreProperties?
|
||||
.Where(p => !String.IsNullOrWhiteSpace(p))
|
||||
.Select(p => p.ToLowerInvariant());
|
||||
|
||||
IEnumerable<PropertyInfo> properties = Runtime.PropertyTypeCache
|
||||
.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, target) ? 1 : 0);
|
||||
}
|
||||
|
||||
private static Boolean TrySetValue(KeyValuePair<PropertyInfo, TypeValuePair> property, Object target) {
|
||||
try {
|
||||
SetValue(property, target);
|
||||
return true;
|
||||
} catch {
|
||||
// swallow
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void SetValue(KeyValuePair<PropertyInfo, TypeValuePair> property, Object target) {
|
||||
if(property.Value.Type.GetTypeInfo().IsEnum) {
|
||||
property.Key.SetValue(target,
|
||||
Enum.ToObject(property.Key.PropertyType, property.Value.Value));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(!property.Value.Type.IsValueType() && property.Key.PropertyType == property.Value.Type) {
|
||||
property.Key.SetValue(target, GetValue(property.Value.Value, property.Key.PropertyType));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(property.Key.PropertyType == typeof(Boolean)) {
|
||||
property.Key.SetValue(target,
|
||||
Convert.ToBoolean(property.Value.Value));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_ = property.Key.TrySetBasicType(property.Value.Value, target);
|
||||
}
|
||||
|
||||
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 Array targetArray:
|
||||
for(Int32 i = 0; i < sourceList.Count; i++) {
|
||||
try {
|
||||
targetArray.SetValue(
|
||||
sourceList[i].GetType().IsValueType()
|
||||
? sourceList[i]
|
||||
: sourceList[i].CopyPropertiesToNew<Object>(), i);
|
||||
} catch {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case IList sourceList when target is IList targetList:
|
||||
MethodInfo addMethod = targetType.GetMethods()
|
||||
.FirstOrDefault(
|
||||
m => m.Name.Equals(Formatters.Json.AddMethodName) && m.IsPublic &&
|
||||
m.GetParameters().Length == 1);
|
||||
|
||||
if(addMethod == null) {
|
||||
return target;
|
||||
}
|
||||
|
||||
foreach(Object item in sourceList) {
|
||||
try {
|
||||
_ = targetList.Add(item.GetType().IsValueType()
|
||||
? item
|
||||
: item.CopyPropertiesToNew<Object>());
|
||||
} catch {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
_ = source.CopyPropertiesTo(target);
|
||||
break;
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
private static Dictionary<String, TypeValuePair> GetSourceMap(Object source) {
|
||||
// select distinct properties because they can be duplicated by inheritance
|
||||
PropertyInfo[] sourceProperties = Runtime.PropertyTypeCache
|
||||
.RetrieveFilteredProperties(source.GetType(), true, x => x.CanRead)
|
||||
.ToArray();
|
||||
|
||||
return sourceProperties
|
||||
.Select(x => x.Name)
|
||||
.Distinct()
|
||||
.ToDictionary(
|
||||
x => x.ToLowerInvariant(),
|
||||
x => new TypeValuePair(sourceProperties.First(y => y.Name == x).PropertyType,
|
||||
sourceProperties.First(y => y.Name == x).GetValue(source)));
|
||||
}
|
||||
|
||||
internal class TypeValuePair {
|
||||
public TypeValuePair(Type type, Object value) {
|
||||
this.Type = type;
|
||||
this.Value = value;
|
||||
}
|
||||
|
||||
public Type Type {
|
||||
get;
|
||||
}
|
||||
|
||||
public Object Value {
|
||||
get;
|
||||
}
|
||||
}
|
||||
|
||||
internal class PropertyInfoComparer : IEqualityComparer<PropertyInfo> {
|
||||
public Boolean Equals(PropertyInfo x, PropertyInfo y)
|
||||
=> x != null && y != null && x.Name == y.Name && x.PropertyType == y.PropertyType;
|
||||
|
||||
public Int32 GetHashCode(PropertyInfo obj)
|
||||
=> obj.Name.GetHashCode() + obj.PropertyType.Name.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,204 +1,207 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using Abstractions;
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using Unosquare.Swan.Abstractions;
|
||||
|
||||
namespace Unosquare.Swan.Components {
|
||||
/// <summary>
|
||||
/// Represents an object validator.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The following code describes how to perform a simple object validation.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Components;
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// public static void Main()
|
||||
/// {
|
||||
/// // create an instance of ObjectValidator
|
||||
/// var obj = new ObjectValidator();
|
||||
///
|
||||
/// // Add a validation to the 'Simple' class with a custom error message
|
||||
/// obj.AddValidator<Simple>(x =>
|
||||
/// !string.IsNullOrEmpty(x.Name), "Name must not be empty");
|
||||
///
|
||||
/// // check if object is valid
|
||||
/// var res = obj.IsValid(new Simple { Name = "Name" });
|
||||
/// }
|
||||
///
|
||||
/// class Simple
|
||||
/// {
|
||||
/// public string Name { get; set; }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// The following code shows of to validate an object with a custom validator and some attributes using the Runtime ObjectValidator singleton.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Components;
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// public static void Main()
|
||||
/// {
|
||||
/// // create an instance of ObjectValidator
|
||||
/// Runtime.ObjectValidator
|
||||
/// .AddValidator<Simple>(x =>
|
||||
/// !x.Name.Equals("Name"), "Name must not be 'Name'");
|
||||
///
|
||||
/// // validate object
|
||||
/// var res = Runtime.ObjectValidator
|
||||
/// .Validate(new Simple{ Name = "name", Number = 5, Email ="email@mail.com"})
|
||||
/// }
|
||||
///
|
||||
/// class Simple
|
||||
/// {
|
||||
/// [NotNull]
|
||||
/// public string Name { get; set; }
|
||||
///
|
||||
/// [Range(1, 10)]
|
||||
/// public int Number { get; set; }
|
||||
///
|
||||
/// [Email]
|
||||
/// public string Email { get; set; }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class ObjectValidator {
|
||||
private readonly ConcurrentDictionary<Type, List<Tuple<Delegate, String>>> _predicates =
|
||||
new ConcurrentDictionary<Type, List<Tuple<Delegate, String>>>();
|
||||
|
||||
/// <summary>
|
||||
/// Represents an object validator.
|
||||
/// Validates an object given the specified validators and attributes.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The following code describes how to perform a simple object validation.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Components;
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// public static void Main()
|
||||
/// {
|
||||
/// // create an instance of ObjectValidator
|
||||
/// var obj = new ObjectValidator();
|
||||
///
|
||||
/// // Add a validation to the 'Simple' class with a custom error message
|
||||
/// obj.AddValidator<Simple>(x =>
|
||||
/// !string.IsNullOrEmpty(x.Name), "Name must not be empty");
|
||||
///
|
||||
/// // check if object is valid
|
||||
/// var res = obj.IsValid(new Simple { Name = "Name" });
|
||||
/// }
|
||||
///
|
||||
/// class Simple
|
||||
/// {
|
||||
/// public string Name { get; set; }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// The following code shows of to validate an object with a custom validator and some attributes using the Runtime ObjectValidator singleton.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Components;
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// public static void Main()
|
||||
/// {
|
||||
/// // create an instance of ObjectValidator
|
||||
/// Runtime.ObjectValidator
|
||||
/// .AddValidator<Simple>(x =>
|
||||
/// !x.Name.Equals("Name"), "Name must not be 'Name'");
|
||||
///
|
||||
/// // validate object
|
||||
/// var res = Runtime.ObjectValidator
|
||||
/// .Validate(new Simple{ Name = "name", Number = 5, Email ="email@mail.com"})
|
||||
/// }
|
||||
///
|
||||
/// class Simple
|
||||
/// {
|
||||
/// [NotNull]
|
||||
/// public string Name { get; set; }
|
||||
///
|
||||
/// [Range(1, 10)]
|
||||
/// public int Number { get; set; }
|
||||
///
|
||||
/// [Email]
|
||||
/// public string Email { get; set; }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class ObjectValidator
|
||||
{
|
||||
private readonly ConcurrentDictionary<Type, List<Tuple<Delegate, string>>> _predicates =
|
||||
new ConcurrentDictionary<Type, List<Tuple<Delegate, string>>>();
|
||||
|
||||
/// <summary>
|
||||
/// Validates an object given the specified validators and attributes.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the object.</typeparam>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <returns cref="ObjectValidationResult">A validation result. </returns>
|
||||
public ObjectValidationResult Validate<T>(T obj)
|
||||
{
|
||||
var errorList = new ObjectValidationResult();
|
||||
ValidateObject(obj, false, errorList.Add);
|
||||
|
||||
return errorList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates an object given the specified validators and attributes.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type.</typeparam>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified object is valid; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">obj.</exception>
|
||||
public bool IsValid<T>(T obj) => ValidateObject(obj);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a validator to a specific class.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the object.</typeparam>
|
||||
/// <param name="predicate">The predicate that will be evaluated.</param>
|
||||
/// <param name="message">The message.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// predicate
|
||||
/// or
|
||||
/// message.
|
||||
/// </exception>
|
||||
public void AddValidator<T>(Predicate<T> predicate, string message)
|
||||
where T : class
|
||||
{
|
||||
if (predicate == null)
|
||||
throw new ArgumentNullException(nameof(predicate));
|
||||
|
||||
if (string.IsNullOrEmpty(message))
|
||||
throw new ArgumentNullException(message);
|
||||
|
||||
if (!_predicates.TryGetValue(typeof(T), out var existing))
|
||||
{
|
||||
existing = new List<Tuple<Delegate, string>>();
|
||||
_predicates[typeof(T)] = existing;
|
||||
}
|
||||
|
||||
existing.Add(Tuple.Create((Delegate) predicate, message));
|
||||
}
|
||||
|
||||
private bool ValidateObject<T>(T obj, bool returnOnError = true, Action<string, string> action = null)
|
||||
{
|
||||
if (Equals(obj, null))
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
|
||||
if (_predicates.ContainsKey(typeof(T)))
|
||||
{
|
||||
foreach (var validation in _predicates[typeof(T)])
|
||||
{
|
||||
if ((bool) validation.Item1.DynamicInvoke(obj)) continue;
|
||||
|
||||
action?.Invoke(string.Empty, validation.Item2);
|
||||
if (returnOnError) return false;
|
||||
}
|
||||
}
|
||||
|
||||
var properties = Runtime.AttributeCache.RetrieveFromType<T>(typeof(IValidator));
|
||||
|
||||
foreach (var prop in properties)
|
||||
{
|
||||
foreach (var attribute in prop.Value)
|
||||
{
|
||||
var val = (IValidator) attribute;
|
||||
|
||||
if (val.IsValid(prop.Key.GetValue(obj, null))) continue;
|
||||
|
||||
action?.Invoke(prop.Key.Name, val.ErrorMessage);
|
||||
if (returnOnError) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <typeparam name="T">The type of the object.</typeparam>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <returns cref="ObjectValidationResult">A validation result. </returns>
|
||||
public ObjectValidationResult Validate<T>(T obj) {
|
||||
ObjectValidationResult errorList = new ObjectValidationResult();
|
||||
_ = this.ValidateObject(obj, false, errorList.Add);
|
||||
|
||||
return errorList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a validation result containing all validation errors and their properties.
|
||||
/// Validates an object given the specified validators and attributes.
|
||||
/// </summary>
|
||||
public class ObjectValidationResult
|
||||
{
|
||||
/// <summary>
|
||||
/// A list of errors.
|
||||
/// </summary>
|
||||
public List<ValidationError> Errors { get; set; } = new List<ValidationError>();
|
||||
|
||||
/// <summary>
|
||||
/// <c>true</c> if there are no errors; otherwise, <c>false</c>.
|
||||
/// </summary>
|
||||
public bool IsValid => !Errors.Any();
|
||||
|
||||
/// <summary>
|
||||
/// Adds an error with a specified property name.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">The property name.</param>
|
||||
/// <param name="errorMessage">The error message.</param>
|
||||
public void Add(string propertyName, string errorMessage) =>
|
||||
Errors.Add(new ValidationError {ErrorMessage = errorMessage, PropertyName = propertyName});
|
||||
|
||||
/// <summary>
|
||||
/// Defines a validation error.
|
||||
/// </summary>
|
||||
public class ValidationError
|
||||
{
|
||||
/// <summary>
|
||||
/// The property name.
|
||||
/// </summary>
|
||||
public string PropertyName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The message error.
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; set; }
|
||||
}
|
||||
}
|
||||
/// <typeparam name="T">The type.</typeparam>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified object is valid; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">obj.</exception>
|
||||
public Boolean IsValid<T>(T obj) => this.ValidateObject(obj);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a validator to a specific class.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the object.</typeparam>
|
||||
/// <param name="predicate">The predicate that will be evaluated.</param>
|
||||
/// <param name="message">The message.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// predicate
|
||||
/// or
|
||||
/// message.
|
||||
/// </exception>
|
||||
public void AddValidator<T>(Predicate<T> predicate, String message)
|
||||
where T : class {
|
||||
if(predicate == null) {
|
||||
throw new ArgumentNullException(nameof(predicate));
|
||||
}
|
||||
|
||||
if(String.IsNullOrEmpty(message)) {
|
||||
throw new ArgumentNullException(message);
|
||||
}
|
||||
|
||||
if(!this._predicates.TryGetValue(typeof(T), out List<Tuple<Delegate, String>> existing)) {
|
||||
existing = new List<Tuple<Delegate, String>>();
|
||||
this._predicates[typeof(T)] = existing;
|
||||
}
|
||||
|
||||
existing.Add(Tuple.Create((Delegate)predicate, message));
|
||||
}
|
||||
|
||||
private Boolean ValidateObject<T>(T obj, Boolean returnOnError = true, Action<String, String> action = null) {
|
||||
if(Equals(obj, null)) {
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
if(this._predicates.ContainsKey(typeof(T))) {
|
||||
foreach(Tuple<Delegate, String> validation in this._predicates[typeof(T)]) {
|
||||
if((Boolean)validation.Item1.DynamicInvoke(obj)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
action?.Invoke(String.Empty, validation.Item2);
|
||||
if(returnOnError) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<System.Reflection.PropertyInfo, IEnumerable<Object>> properties = Runtime.AttributeCache.RetrieveFromType<T>(typeof(IValidator));
|
||||
|
||||
foreach(KeyValuePair<System.Reflection.PropertyInfo, IEnumerable<Object>> prop in properties) {
|
||||
foreach(Object attribute in prop.Value) {
|
||||
IValidator val = (IValidator)attribute;
|
||||
|
||||
if(val.IsValid(prop.Key.GetValue(obj, null))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
action?.Invoke(prop.Key.Name, val.ErrorMessage);
|
||||
if(returnOnError) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a validation result containing all validation errors and their properties.
|
||||
/// </summary>
|
||||
public class ObjectValidationResult {
|
||||
/// <summary>
|
||||
/// A list of errors.
|
||||
/// </summary>
|
||||
public List<ValidationError> Errors { get; set; } = new List<ValidationError>();
|
||||
|
||||
/// <summary>
|
||||
/// <c>true</c> if there are no errors; otherwise, <c>false</c>.
|
||||
/// </summary>
|
||||
public Boolean IsValid => !this.Errors.Any();
|
||||
|
||||
/// <summary>
|
||||
/// Adds an error with a specified property name.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">The property name.</param>
|
||||
/// <param name="errorMessage">The error message.</param>
|
||||
public void Add(String propertyName, String errorMessage) =>
|
||||
this.Errors.Add(new ValidationError { ErrorMessage = errorMessage, PropertyName = propertyName });
|
||||
|
||||
/// <summary>
|
||||
/// Defines a validation error.
|
||||
/// </summary>
|
||||
public class ValidationError {
|
||||
/// <summary>
|
||||
/// The property name.
|
||||
/// </summary>
|
||||
public String PropertyName {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The message error.
|
||||
/// </summary>
|
||||
public String ErrorMessage {
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,48 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Abstractions;
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Unosquare.Swan.Abstractions;
|
||||
|
||||
namespace Unosquare.Swan.Components {
|
||||
/// <summary>
|
||||
/// Provides factory methods to create synchronized reader-writer locks
|
||||
/// that support a generalized locking and releasing api and syntax.
|
||||
/// </summary>
|
||||
public static class SyncLockerFactory {
|
||||
#region Enums and Interfaces
|
||||
|
||||
/// <summary>
|
||||
/// Provides factory methods to create synchronized reader-writer locks
|
||||
/// that support a generalized locking and releasing api and syntax.
|
||||
/// Enumerates the locking operations.
|
||||
/// </summary>
|
||||
public static class SyncLockerFactory
|
||||
{
|
||||
#region Enums and Interfaces
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the locking operations.
|
||||
/// </summary>
|
||||
private enum LockHolderType
|
||||
{
|
||||
Read,
|
||||
Write,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines methods for releasing locks.
|
||||
/// </summary>
|
||||
private interface ISyncReleasable
|
||||
{
|
||||
/// <summary>
|
||||
/// Releases the writer lock.
|
||||
/// </summary>
|
||||
void ReleaseWriterLock();
|
||||
|
||||
/// <summary>
|
||||
/// Releases the reader lock.
|
||||
/// </summary>
|
||||
void ReleaseReaderLock();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Factory Methods
|
||||
|
||||
#if !NETSTANDARD1_3
|
||||
/// <summary>
|
||||
/// Creates a reader-writer lock backed by a standard ReaderWriterLock.
|
||||
/// </summary>
|
||||
/// <returns>The synchronized locker.</returns>
|
||||
public static ISyncLocker Create() => new SyncLocker();
|
||||
private enum LockHolderType {
|
||||
Read,
|
||||
Write,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines methods for releasing locks.
|
||||
/// </summary>
|
||||
private interface ISyncReleasable {
|
||||
/// <summary>
|
||||
/// Releases the writer lock.
|
||||
/// </summary>
|
||||
void ReleaseWriterLock();
|
||||
|
||||
/// <summary>
|
||||
/// Releases the reader lock.
|
||||
/// </summary>
|
||||
void ReleaseReaderLock();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Factory Methods
|
||||
|
||||
#if !NETSTANDARD1_3
|
||||
/// <summary>
|
||||
/// Creates a reader-writer lock backed by a standard ReaderWriterLock.
|
||||
/// </summary>
|
||||
/// <returns>The synchronized locker.</returns>
|
||||
public static ISyncLocker Create() => new SyncLocker();
|
||||
#else
|
||||
/// <summary>
|
||||
/// Creates a reader-writer lock backed by a standard ReaderWriterLockSlim when
|
||||
@@ -55,143 +51,142 @@
|
||||
/// <returns>The synchronized locker</returns>
|
||||
public static ISyncLocker Create() => new SyncLockerSlim();
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Creates a reader-writer lock backed by a ReaderWriterLockSlim.
|
||||
/// </summary>
|
||||
/// <returns>The synchronized locker.</returns>
|
||||
public static ISyncLocker CreateSlim() => new SyncLockerSlim();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a reader-writer lock.
|
||||
/// </summary>
|
||||
/// <param name="useSlim">if set to <c>true</c> it uses the Slim version of a reader-writer lock.</param>
|
||||
/// <returns>The Sync Locker.</returns>
|
||||
public static ISyncLocker Create(bool useSlim) => useSlim ? CreateSlim() : Create();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Classes
|
||||
|
||||
/// <summary>
|
||||
/// The lock releaser. Calling the dispose method releases the lock entered by the parent SyncLocker.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.IDisposable" />
|
||||
private sealed class SyncLockReleaser : IDisposable
|
||||
{
|
||||
private readonly ISyncReleasable _parent;
|
||||
private readonly LockHolderType _operation;
|
||||
|
||||
private bool _isDisposed;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SyncLockReleaser"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent.</param>
|
||||
/// <param name="operation">The operation.</param>
|
||||
public SyncLockReleaser(ISyncReleasable parent, LockHolderType operation)
|
||||
{
|
||||
_parent = parent;
|
||||
_operation = operation;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
_isDisposed = true;
|
||||
|
||||
if (_operation == LockHolderType.Read)
|
||||
_parent.ReleaseReaderLock();
|
||||
else
|
||||
_parent.ReleaseWriterLock();
|
||||
}
|
||||
}
|
||||
|
||||
#if !NETSTANDARD1_3
|
||||
/// <summary>
|
||||
/// The Sync Locker backed by a ReaderWriterLock.
|
||||
/// </summary>
|
||||
/// <seealso cref="ISyncLocker" />
|
||||
/// <seealso cref="ISyncReleasable" />
|
||||
private sealed class SyncLocker : ISyncLocker, ISyncReleasable
|
||||
{
|
||||
private bool _isDisposed;
|
||||
private ReaderWriterLock _locker = new ReaderWriterLock();
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDisposable AcquireReaderLock()
|
||||
{
|
||||
_locker?.AcquireReaderLock(Timeout.Infinite);
|
||||
return new SyncLockReleaser(this, LockHolderType.Read);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDisposable AcquireWriterLock()
|
||||
{
|
||||
_locker?.AcquireWriterLock(Timeout.Infinite);
|
||||
return new SyncLockReleaser(this, LockHolderType.Write);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReleaseWriterLock() => _locker?.ReleaseWriterLock();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReleaseReaderLock() => _locker?.ReleaseReaderLock();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
_isDisposed = true;
|
||||
_locker?.ReleaseLock();
|
||||
_locker = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a reader-writer lock backed by a ReaderWriterLockSlim.
|
||||
/// </summary>
|
||||
/// <returns>The synchronized locker.</returns>
|
||||
public static ISyncLocker CreateSlim() => new SyncLockerSlim();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a reader-writer lock.
|
||||
/// </summary>
|
||||
/// <param name="useSlim">if set to <c>true</c> it uses the Slim version of a reader-writer lock.</param>
|
||||
/// <returns>The Sync Locker.</returns>
|
||||
public static ISyncLocker Create(Boolean useSlim) => useSlim ? CreateSlim() : Create();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Classes
|
||||
|
||||
/// <summary>
|
||||
/// The lock releaser. Calling the dispose method releases the lock entered by the parent SyncLocker.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.IDisposable" />
|
||||
private sealed class SyncLockReleaser : IDisposable {
|
||||
private readonly ISyncReleasable _parent;
|
||||
private readonly LockHolderType _operation;
|
||||
|
||||
private Boolean _isDisposed;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SyncLockReleaser"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent.</param>
|
||||
/// <param name="operation">The operation.</param>
|
||||
public SyncLockReleaser(ISyncReleasable parent, LockHolderType operation) {
|
||||
this._parent = parent;
|
||||
this._operation = operation;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
if(this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isDisposed = true;
|
||||
|
||||
if(this._operation == LockHolderType.Read) {
|
||||
this._parent.ReleaseReaderLock();
|
||||
} else {
|
||||
this._parent.ReleaseWriterLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !NETSTANDARD1_3
|
||||
/// <summary>
|
||||
/// The Sync Locker backed by a ReaderWriterLock.
|
||||
/// </summary>
|
||||
/// <seealso cref="ISyncLocker" />
|
||||
/// <seealso cref="ISyncReleasable" />
|
||||
private sealed class SyncLocker : ISyncLocker, ISyncReleasable {
|
||||
private Boolean _isDisposed;
|
||||
private ReaderWriterLock _locker = new ReaderWriterLock();
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDisposable AcquireReaderLock() {
|
||||
this._locker?.AcquireReaderLock(Timeout.Infinite);
|
||||
return new SyncLockReleaser(this, LockHolderType.Read);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDisposable AcquireWriterLock() {
|
||||
this._locker?.AcquireWriterLock(Timeout.Infinite);
|
||||
return new SyncLockReleaser(this, LockHolderType.Write);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReleaseWriterLock() => this._locker?.ReleaseWriterLock();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReleaseReaderLock() => this._locker?.ReleaseReaderLock();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
if(this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isDisposed = true;
|
||||
_ = this._locker?.ReleaseLock();
|
||||
this._locker = null;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The Sync Locker backed by ReaderWriterLockSlim.
|
||||
/// </summary>
|
||||
/// <seealso cref="ISyncLocker" />
|
||||
/// <seealso cref="ISyncReleasable" />
|
||||
private sealed class SyncLockerSlim : ISyncLocker, ISyncReleasable
|
||||
{
|
||||
private bool _isDisposed;
|
||||
|
||||
private ReaderWriterLockSlim _locker
|
||||
= new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDisposable AcquireReaderLock()
|
||||
{
|
||||
_locker?.EnterReadLock();
|
||||
return new SyncLockReleaser(this, LockHolderType.Read);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDisposable AcquireWriterLock()
|
||||
{
|
||||
_locker?.EnterWriteLock();
|
||||
return new SyncLockReleaser(this, LockHolderType.Write);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReleaseWriterLock() => _locker?.ExitWriteLock();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReleaseReaderLock() => _locker?.ExitReadLock();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
_isDisposed = true;
|
||||
_locker?.Dispose();
|
||||
_locker = null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Sync Locker backed by ReaderWriterLockSlim.
|
||||
/// </summary>
|
||||
/// <seealso cref="ISyncLocker" />
|
||||
/// <seealso cref="ISyncReleasable" />
|
||||
private sealed class SyncLockerSlim : ISyncLocker, ISyncReleasable {
|
||||
private Boolean _isDisposed;
|
||||
|
||||
private ReaderWriterLockSlim _locker
|
||||
= new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDisposable AcquireReaderLock() {
|
||||
this._locker?.EnterReadLock();
|
||||
return new SyncLockReleaser(this, LockHolderType.Read);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDisposable AcquireWriterLock() {
|
||||
this._locker?.EnterWriteLock();
|
||||
return new SyncLockReleaser(this, LockHolderType.Write);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReleaseWriterLock() => this._locker?.ExitWriteLock();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReleaseReaderLock() => this._locker?.ExitReadLock();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
if(this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isDisposed = true;
|
||||
this._locker?.Dispose();
|
||||
this._locker = null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,63 +1,55 @@
|
||||
#if !NETSTANDARD1_3
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Abstractions;
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Unosquare.Swan.Abstractions;
|
||||
namespace Unosquare.Swan.Components {
|
||||
/// <summary>
|
||||
/// Use this singleton to wait for a specific <c>TimeSpan</c> or time.
|
||||
///
|
||||
/// Internally this class will use a <c>Timer</c> and a <c>ManualResetEvent</c> to block until
|
||||
/// the time condition is satisfied.
|
||||
/// </summary>
|
||||
/// <seealso cref="SingletonBase{TimerControl}" />
|
||||
public class TimerControl : SingletonBase<TimerControl> {
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0052:Ungelesene private Member entfernen", Justification = "<Ausstehend>")]
|
||||
private readonly Timer _innerTimer;
|
||||
private readonly IWaitEvent _delayLock = WaitEventFactory.Create(true);
|
||||
|
||||
/// <summary>
|
||||
/// Use this singleton to wait for a specific <c>TimeSpan</c> or time.
|
||||
///
|
||||
/// Internally this class will use a <c>Timer</c> and a <c>ManualResetEvent</c> to block until
|
||||
/// the time condition is satisfied.
|
||||
/// Initializes a new instance of the <see cref="TimerControl"/> class.
|
||||
/// </summary>
|
||||
/// <seealso cref="SingletonBase{TimerControl}" />
|
||||
public class TimerControl : SingletonBase<TimerControl>
|
||||
{
|
||||
private readonly Timer _innerTimer;
|
||||
private readonly IWaitEvent _delayLock = WaitEventFactory.Create(true);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TimerControl"/> class.
|
||||
/// </summary>
|
||||
protected TimerControl()
|
||||
{
|
||||
_innerTimer = new Timer(
|
||||
x =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_delayLock.Complete();
|
||||
_delayLock.Begin();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
protected TimerControl() => this._innerTimer = new Timer(
|
||||
x => {
|
||||
try {
|
||||
this._delayLock.Complete();
|
||||
this._delayLock.Begin();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
},
|
||||
null,
|
||||
0,
|
||||
15);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits until the time is elapsed.
|
||||
/// </summary>
|
||||
/// <param name="untilDate">The until date.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
public void WaitUntil(DateTime untilDate, CancellationToken ct = default)
|
||||
{
|
||||
while (!ct.IsCancellationRequested && DateTime.UtcNow < untilDate)
|
||||
_delayLock.Wait();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits the specified wait time.
|
||||
/// </summary>
|
||||
/// <param name="waitTime">The wait time.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
public void Wait(TimeSpan waitTime, CancellationToken ct = default) =>
|
||||
WaitUntil(DateTime.UtcNow.Add(waitTime), ct);
|
||||
}
|
||||
15);
|
||||
|
||||
/// <summary>
|
||||
/// Waits until the time is elapsed.
|
||||
/// </summary>
|
||||
/// <param name="untilDate">The until date.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
public void WaitUntil(DateTime untilDate, CancellationToken ct = default) {
|
||||
while(!ct.IsCancellationRequested && DateTime.UtcNow < untilDate) {
|
||||
this._delayLock.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits the specified wait time.
|
||||
/// </summary>
|
||||
/// <param name="waitTime">The wait time.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
public void Wait(TimeSpan waitTime, CancellationToken ct = default) =>
|
||||
this.WaitUntil(DateTime.UtcNow.Add(waitTime), ct);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,222 +1,196 @@
|
||||
#if !NETSTANDARD1_3
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Abstractions;
|
||||
|
||||
|
||||
|
||||
#if !NETSTANDARD1_3
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Unosquare.Swan.Abstractions;
|
||||
namespace Unosquare.Swan.Components {
|
||||
/// <summary>
|
||||
/// Provides a Manual Reset Event factory with a unified API.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The following example shows how to use the WaitEventFactory class.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Components;
|
||||
///
|
||||
/// public class Example
|
||||
/// {
|
||||
/// // create a WaitEvent using the slim version
|
||||
/// private static readonly IWaitEvent waitEvent = WaitEventFactory.CreateSlim(false);
|
||||
///
|
||||
/// public static void Main()
|
||||
/// {
|
||||
/// Task.Factory.StartNew(() =>
|
||||
/// {
|
||||
/// DoWork(1);
|
||||
/// });
|
||||
///
|
||||
/// Task.Factory.StartNew(() =>
|
||||
/// {
|
||||
/// DoWork(2);
|
||||
/// });
|
||||
///
|
||||
/// // send first signal
|
||||
/// waitEvent.Complete();
|
||||
/// waitEvent.Begin();
|
||||
///
|
||||
/// Thread.Sleep(TimeSpan.FromSeconds(2));
|
||||
///
|
||||
/// // send second signal
|
||||
/// waitEvent.Complete();
|
||||
///
|
||||
/// Console.Readline();
|
||||
/// }
|
||||
///
|
||||
/// public static void DoWork(int taskNumber)
|
||||
/// {
|
||||
/// $"Data retrieved:{taskNumber}".WriteLine();
|
||||
/// waitEvent.Wait();
|
||||
///
|
||||
/// Thread.Sleep(TimeSpan.FromSeconds(2));
|
||||
/// $"All finished up {taskNumber}".WriteLine();
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static class WaitEventFactory {
|
||||
#region Factory Methods
|
||||
|
||||
/// <summary>
|
||||
/// Provides a Manual Reset Event factory with a unified API.
|
||||
/// Creates a Wait Event backed by a standard ManualResetEvent.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The following example shows how to use the WaitEventFactory class.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Components;
|
||||
///
|
||||
/// public class Example
|
||||
/// {
|
||||
/// // create a WaitEvent using the slim version
|
||||
/// private static readonly IWaitEvent waitEvent = WaitEventFactory.CreateSlim(false);
|
||||
///
|
||||
/// public static void Main()
|
||||
/// {
|
||||
/// Task.Factory.StartNew(() =>
|
||||
/// {
|
||||
/// DoWork(1);
|
||||
/// });
|
||||
///
|
||||
/// Task.Factory.StartNew(() =>
|
||||
/// {
|
||||
/// DoWork(2);
|
||||
/// });
|
||||
///
|
||||
/// // send first signal
|
||||
/// waitEvent.Complete();
|
||||
/// waitEvent.Begin();
|
||||
///
|
||||
/// Thread.Sleep(TimeSpan.FromSeconds(2));
|
||||
///
|
||||
/// // send second signal
|
||||
/// waitEvent.Complete();
|
||||
///
|
||||
/// Console.Readline();
|
||||
/// }
|
||||
///
|
||||
/// public static void DoWork(int taskNumber)
|
||||
/// {
|
||||
/// $"Data retrieved:{taskNumber}".WriteLine();
|
||||
/// waitEvent.Wait();
|
||||
///
|
||||
/// Thread.Sleep(TimeSpan.FromSeconds(2));
|
||||
/// $"All finished up {taskNumber}".WriteLine();
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static class WaitEventFactory
|
||||
{
|
||||
#region Factory Methods
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Wait Event backed by a standard ManualResetEvent.
|
||||
/// </summary>
|
||||
/// <param name="isCompleted">if initially set to completed. Generally true.</param>
|
||||
/// <returns>The Wait Event.</returns>
|
||||
public static IWaitEvent Create(bool isCompleted) => new WaitEvent(isCompleted);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Wait Event backed by a ManualResetEventSlim.
|
||||
/// </summary>
|
||||
/// <param name="isCompleted">if initially set to completed. Generally true.</param>
|
||||
/// <returns>The Wait Event.</returns>
|
||||
public static IWaitEvent CreateSlim(bool isCompleted) => new WaitEventSlim(isCompleted);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Wait Event backed by a ManualResetEventSlim.
|
||||
/// </summary>
|
||||
/// <param name="isCompleted">if initially set to completed. Generally true.</param>
|
||||
/// <param name="useSlim">if set to <c>true</c> creates a slim version of the wait event.</param>
|
||||
/// <returns>The Wait Event.</returns>
|
||||
public static IWaitEvent Create(bool isCompleted, bool useSlim) => useSlim ? CreateSlim(isCompleted) : Create(isCompleted);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Backing Classes
|
||||
|
||||
/// <summary>
|
||||
/// Defines a WaitEvent backed by a ManualResetEvent.
|
||||
/// </summary>
|
||||
private class WaitEvent : IWaitEvent
|
||||
{
|
||||
private ManualResetEvent _event;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WaitEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="isCompleted">if set to <c>true</c> [is completed].</param>
|
||||
public WaitEvent(bool isCompleted)
|
||||
{
|
||||
_event = new ManualResetEvent(isCompleted);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsValid
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsDisposed || _event == null)
|
||||
return false;
|
||||
|
||||
if (_event?.SafeWaitHandle?.IsClosed ?? true)
|
||||
return false;
|
||||
|
||||
return !(_event?.SafeWaitHandle?.IsInvalid ?? true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsValid == false)
|
||||
return true;
|
||||
|
||||
return _event?.WaitOne(0) ?? true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsInProgress => !IsCompleted;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Begin() => _event?.Reset();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Complete() => _event?.Set();
|
||||
|
||||
/// <inheritdoc />
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
if (IsDisposed) return;
|
||||
IsDisposed = true;
|
||||
|
||||
_event?.Set();
|
||||
_event?.Dispose();
|
||||
_event = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Wait() => _event?.WaitOne();
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Wait(TimeSpan timeout) => _event?.WaitOne(timeout) ?? true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a WaitEvent backed by a ManualResetEventSlim.
|
||||
/// </summary>
|
||||
private class WaitEventSlim : IWaitEvent
|
||||
{
|
||||
private ManualResetEventSlim _event;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WaitEventSlim"/> class.
|
||||
/// </summary>
|
||||
/// <param name="isCompleted">if set to <c>true</c> [is completed].</param>
|
||||
public WaitEventSlim(bool isCompleted)
|
||||
{
|
||||
_event = new ManualResetEventSlim(isCompleted);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsValid
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsDisposed || _event?.WaitHandle?.SafeWaitHandle == null) return false;
|
||||
|
||||
return !_event.WaitHandle.SafeWaitHandle.IsClosed && !_event.WaitHandle.SafeWaitHandle.IsInvalid;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsCompleted => IsValid == false || _event.IsSet;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsInProgress => !IsCompleted;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Begin() => _event?.Reset();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Complete() => _event?.Set();
|
||||
|
||||
/// <inheritdoc />
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
if (IsDisposed) return;
|
||||
IsDisposed = true;
|
||||
|
||||
_event?.Set();
|
||||
_event?.Dispose();
|
||||
_event = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Wait() => _event?.Wait();
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Wait(TimeSpan timeout) => _event?.Wait(timeout) ?? true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
/// <param name="isCompleted">if initially set to completed. Generally true.</param>
|
||||
/// <returns>The Wait Event.</returns>
|
||||
public static IWaitEvent Create(Boolean isCompleted) => new WaitEvent(isCompleted);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Wait Event backed by a ManualResetEventSlim.
|
||||
/// </summary>
|
||||
/// <param name="isCompleted">if initially set to completed. Generally true.</param>
|
||||
/// <returns>The Wait Event.</returns>
|
||||
public static IWaitEvent CreateSlim(Boolean isCompleted) => new WaitEventSlim(isCompleted);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Wait Event backed by a ManualResetEventSlim.
|
||||
/// </summary>
|
||||
/// <param name="isCompleted">if initially set to completed. Generally true.</param>
|
||||
/// <param name="useSlim">if set to <c>true</c> creates a slim version of the wait event.</param>
|
||||
/// <returns>The Wait Event.</returns>
|
||||
public static IWaitEvent Create(Boolean isCompleted, Boolean useSlim) => useSlim ? CreateSlim(isCompleted) : Create(isCompleted);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Backing Classes
|
||||
|
||||
/// <summary>
|
||||
/// Defines a WaitEvent backed by a ManualResetEvent.
|
||||
/// </summary>
|
||||
private class WaitEvent : IWaitEvent {
|
||||
private ManualResetEvent _event;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WaitEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="isCompleted">if set to <c>true</c> [is completed].</param>
|
||||
public WaitEvent(Boolean isCompleted) => this._event = new ManualResetEvent(isCompleted);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Boolean IsDisposed {
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Boolean IsValid => this.IsDisposed || this._event == null
|
||||
? false
|
||||
: this._event?.SafeWaitHandle?.IsClosed ?? true ? false : !(this._event?.SafeWaitHandle?.IsInvalid ?? true);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Boolean IsCompleted => this.IsValid == false ? true : this._event?.WaitOne(0) ?? true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Boolean IsInProgress => !this.IsCompleted;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Begin() => this._event?.Reset();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Complete() => this._event?.Set();
|
||||
|
||||
/// <inheritdoc />
|
||||
void IDisposable.Dispose() {
|
||||
if(this.IsDisposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.IsDisposed = true;
|
||||
|
||||
_ = this._event?.Set();
|
||||
this._event?.Dispose();
|
||||
this._event = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Wait() => this._event?.WaitOne();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Boolean Wait(TimeSpan timeout) => this._event?.WaitOne(timeout) ?? true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a WaitEvent backed by a ManualResetEventSlim.
|
||||
/// </summary>
|
||||
private class WaitEventSlim : IWaitEvent {
|
||||
private ManualResetEventSlim _event;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WaitEventSlim"/> class.
|
||||
/// </summary>
|
||||
/// <param name="isCompleted">if set to <c>true</c> [is completed].</param>
|
||||
public WaitEventSlim(Boolean isCompleted) => this._event = new ManualResetEventSlim(isCompleted);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Boolean IsDisposed {
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Boolean IsValid => this.IsDisposed || this._event?.WaitHandle?.SafeWaitHandle == null
|
||||
? false
|
||||
: !this._event.WaitHandle.SafeWaitHandle.IsClosed && !this._event.WaitHandle.SafeWaitHandle.IsInvalid;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Boolean IsCompleted => this.IsValid == false || this._event.IsSet;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Boolean IsInProgress => !this.IsCompleted;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Begin() => this._event?.Reset();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Complete() => this._event?.Set();
|
||||
|
||||
/// <inheritdoc />
|
||||
void IDisposable.Dispose() {
|
||||
if(this.IsDisposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.IsDisposed = true;
|
||||
|
||||
this._event?.Set();
|
||||
this._event?.Dispose();
|
||||
this._event = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Wait() => this._event?.Wait();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Boolean Wait(TimeSpan timeout) => this._event?.Wait(timeout) ?? true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user