Coding style
This commit is contained in:
@@ -1,243 +1,228 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Unosquare.Swan.Abstractions {
|
||||
/// <summary>
|
||||
/// Provides a generic implementation of an Atomic (interlocked) type
|
||||
///
|
||||
/// Idea taken from Memory model and .NET operations in article:
|
||||
/// http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The structure type backed by a 64-bit value.</typeparam>
|
||||
public abstract class AtomicTypeBase<T> : IComparable, IComparable<T>, IComparable<AtomicTypeBase<T>>, IEquatable<T>, IEquatable<AtomicTypeBase<T>>
|
||||
where T : struct, IComparable, IComparable<T>, IEquatable<T> {
|
||||
private Int64 _backingValue;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a generic implementation of an Atomic (interlocked) type
|
||||
///
|
||||
/// Idea taken from Memory model and .NET operations in article:
|
||||
/// http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/.
|
||||
/// Initializes a new instance of the <see cref="AtomicTypeBase{T}"/> class.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The structure type backed by a 64-bit value.</typeparam>
|
||||
public abstract class AtomicTypeBase<T> : IComparable, IComparable<T>, IComparable<AtomicTypeBase<T>>, IEquatable<T>, IEquatable<AtomicTypeBase<T>>
|
||||
where T : struct, IComparable, IComparable<T>, IEquatable<T>
|
||||
{
|
||||
private long _backingValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AtomicTypeBase{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="initialValue">The initial value.</param>
|
||||
protected AtomicTypeBase(long initialValue)
|
||||
{
|
||||
BackingValue = initialValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value.
|
||||
/// </summary>
|
||||
public T Value
|
||||
{
|
||||
get => FromLong(BackingValue);
|
||||
set => BackingValue = ToLong(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the backing value.
|
||||
/// </summary>
|
||||
protected long BackingValue
|
||||
{
|
||||
get => Interlocked.Read(ref _backingValue);
|
||||
set => Interlocked.Exchange(ref _backingValue, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator ==.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static bool operator ==(AtomicTypeBase<T> a, T b) => a?.Equals(b) == true;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator !=.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static bool operator !=(AtomicTypeBase<T> a, T b) => a?.Equals(b) == false;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator >.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static bool operator >(AtomicTypeBase<T> a, T b) => a.CompareTo(b) > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator <.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static bool operator <(AtomicTypeBase<T> a, T b) => a.CompareTo(b) < 0;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator >=.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static bool operator >=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) >= 0;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator <=.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static bool operator <=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) <= 0;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator ++.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static AtomicTypeBase<T> operator ++(AtomicTypeBase<T> instance)
|
||||
{
|
||||
Interlocked.Increment(ref instance._backingValue);
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator --.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static AtomicTypeBase<T> operator --(AtomicTypeBase<T> instance)
|
||||
{
|
||||
Interlocked.Decrement(ref instance._backingValue);
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator -<.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// <param name="operand">The operand.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static AtomicTypeBase<T> operator +(AtomicTypeBase<T> instance, long operand)
|
||||
{
|
||||
instance.BackingValue = instance.BackingValue + operand;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator -.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// <param name="operand">The operand.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static AtomicTypeBase<T> operator -(AtomicTypeBase<T> instance, long operand)
|
||||
{
|
||||
instance.BackingValue = instance.BackingValue - operand;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the value to the other instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The other instance.</param>
|
||||
/// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
|
||||
/// <exception cref="ArgumentException">When types are incompatible.</exception>
|
||||
public int CompareTo(object other)
|
||||
{
|
||||
switch (other)
|
||||
{
|
||||
case null:
|
||||
return 1;
|
||||
case AtomicTypeBase<T> atomic:
|
||||
return BackingValue.CompareTo(atomic.BackingValue);
|
||||
case T variable:
|
||||
return Value.CompareTo(variable);
|
||||
}
|
||||
|
||||
throw new ArgumentException("Incompatible comparison types");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the value to the other instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The other instance.</param>
|
||||
/// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
|
||||
public int CompareTo(T other) => Value.CompareTo(other);
|
||||
|
||||
/// <summary>
|
||||
/// Compares the value to the other instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The other instance.</param>
|
||||
/// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
|
||||
public int CompareTo(AtomicTypeBase<T> other) => BackingValue.CompareTo(other?.BackingValue ?? default);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="object" />, is equal to this instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The <see cref="object" /> to compare with this instance.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified <see cref="object" /> is equal to this instance; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
switch (other)
|
||||
{
|
||||
case AtomicTypeBase<T> atomic:
|
||||
return Equals(atomic);
|
||||
case T variable:
|
||||
return Equals(variable);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a hash code for this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
|
||||
/// </returns>
|
||||
public override int GetHashCode() => BackingValue.GetHashCode();
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(AtomicTypeBase<T> other) =>
|
||||
BackingValue == (other?.BackingValue ?? default);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(T other) => Equals(Value, other);
|
||||
|
||||
/// <summary>
|
||||
/// Converts from a long value to the target type.
|
||||
/// </summary>
|
||||
/// <param name="backingValue">The backing value.</param>
|
||||
/// <returns>The value converted form a long value.</returns>
|
||||
protected abstract T FromLong(long backingValue);
|
||||
|
||||
/// <summary>
|
||||
/// Converts from the target type to a long value.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>The value converted to a long value.</returns>
|
||||
protected abstract long ToLong(T value);
|
||||
}
|
||||
/// <param name="initialValue">The initial value.</param>
|
||||
protected AtomicTypeBase(Int64 initialValue) => this.BackingValue = initialValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value.
|
||||
/// </summary>
|
||||
public T Value {
|
||||
get => this.FromLong(this.BackingValue);
|
||||
set => this.BackingValue = this.ToLong(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the backing value.
|
||||
/// </summary>
|
||||
protected Int64 BackingValue {
|
||||
get => Interlocked.Read(ref this._backingValue);
|
||||
set => Interlocked.Exchange(ref this._backingValue, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator ==.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static Boolean operator ==(AtomicTypeBase<T> a, T b) => a?.Equals(b) == true;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator !=.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static Boolean operator !=(AtomicTypeBase<T> a, T b) => a?.Equals(b) == false;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator >.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static Boolean operator >(AtomicTypeBase<T> a, T b) => a.CompareTo(b) > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator <.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static Boolean operator <(AtomicTypeBase<T> a, T b) => a.CompareTo(b) < 0;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator >=.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static Boolean operator >=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) >= 0;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator <=.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static Boolean operator <=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) <= 0;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator ++.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static AtomicTypeBase<T> operator ++(AtomicTypeBase<T> instance) {
|
||||
_ = Interlocked.Increment(ref instance._backingValue);
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator --.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static AtomicTypeBase<T> operator --(AtomicTypeBase<T> instance) {
|
||||
_ = Interlocked.Decrement(ref instance._backingValue);
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator -<.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// <param name="operand">The operand.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static AtomicTypeBase<T> operator +(AtomicTypeBase<T> instance, Int64 operand) {
|
||||
instance.BackingValue += operand;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator -.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// <param name="operand">The operand.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static AtomicTypeBase<T> operator -(AtomicTypeBase<T> instance, Int64 operand) {
|
||||
instance.BackingValue -= operand;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the value to the other instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The other instance.</param>
|
||||
/// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
|
||||
/// <exception cref="ArgumentException">When types are incompatible.</exception>
|
||||
public Int32 CompareTo(Object other) {
|
||||
switch(other) {
|
||||
case null:
|
||||
return 1;
|
||||
case AtomicTypeBase<T> atomic:
|
||||
return this.BackingValue.CompareTo(atomic.BackingValue);
|
||||
case T variable:
|
||||
return this.Value.CompareTo(variable);
|
||||
}
|
||||
|
||||
throw new ArgumentException("Incompatible comparison types");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the value to the other instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The other instance.</param>
|
||||
/// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
|
||||
public Int32 CompareTo(T other) => this.Value.CompareTo(other);
|
||||
|
||||
/// <summary>
|
||||
/// Compares the value to the other instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The other instance.</param>
|
||||
/// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
|
||||
public Int32 CompareTo(AtomicTypeBase<T> other) => this.BackingValue.CompareTo(other?.BackingValue ?? default);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Object" />, is equal to this instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The <see cref="Object" /> to compare with this instance.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified <see cref="Object" /> is equal to this instance; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public override Boolean Equals(Object other) {
|
||||
switch(other) {
|
||||
case AtomicTypeBase<T> atomic:
|
||||
return this.Equals(atomic);
|
||||
case T variable:
|
||||
return this.Equals(variable);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a hash code for this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
|
||||
/// </returns>
|
||||
public override Int32 GetHashCode() => this.BackingValue.GetHashCode();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Boolean Equals(AtomicTypeBase<T> other) =>
|
||||
this.BackingValue == (other?.BackingValue ?? default);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Boolean Equals(T other) => Equals(this.Value, other);
|
||||
|
||||
/// <summary>
|
||||
/// Converts from a long value to the target type.
|
||||
/// </summary>
|
||||
/// <param name="backingValue">The backing value.</param>
|
||||
/// <returns>The value converted form a long value.</returns>
|
||||
protected abstract T FromLong(Int64 backingValue);
|
||||
|
||||
/// <summary>
|
||||
/// Converts from the target type to a long value.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>The value converted to a long value.</returns>
|
||||
protected abstract Int64 ToLong(T value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,197 +1,181 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Unosquare.Swan.Abstractions {
|
||||
/// <summary>
|
||||
/// A threading <see cref="_backingTimer"/> implementation that executes at most one cycle at a time
|
||||
/// in a <see cref="ThreadPool"/> thread. Callback execution is NOT guaranteed to be carried out
|
||||
/// on the same <see cref="ThreadPool"/> thread every time the timer fires.
|
||||
/// </summary>
|
||||
public sealed class ExclusiveTimer : IDisposable {
|
||||
private readonly Object _syncLock = new Object();
|
||||
private readonly ManualResetEventSlim _cycleDoneEvent = new ManualResetEventSlim(true);
|
||||
private readonly Timer _backingTimer;
|
||||
private readonly TimerCallback _userCallback;
|
||||
private readonly AtomicBoolean _isDisposing = new AtomicBoolean();
|
||||
private readonly AtomicBoolean _isDisposed = new AtomicBoolean();
|
||||
private Int32 _period;
|
||||
|
||||
/// <summary>
|
||||
/// A threading <see cref="_backingTimer"/> implementation that executes at most one cycle at a time
|
||||
/// in a <see cref="ThreadPool"/> thread. Callback execution is NOT guaranteed to be carried out
|
||||
/// on the same <see cref="ThreadPool"/> thread every time the timer fires.
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
public sealed class ExclusiveTimer : IDisposable
|
||||
{
|
||||
private readonly object _syncLock = new object();
|
||||
private readonly ManualResetEventSlim _cycleDoneEvent = new ManualResetEventSlim(true);
|
||||
private readonly Timer _backingTimer;
|
||||
private readonly TimerCallback _userCallback;
|
||||
private readonly AtomicBoolean _isDisposing = new AtomicBoolean();
|
||||
private readonly AtomicBoolean _isDisposed = new AtomicBoolean();
|
||||
private int _period;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
/// <param name="state">The state.</param>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public ExclusiveTimer(TimerCallback timerCallback, object state, int dueTime, int period)
|
||||
{
|
||||
_period = period;
|
||||
_userCallback = timerCallback;
|
||||
_backingTimer = new Timer(InternalCallback, state ?? this, dueTime, Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
/// <param name="state">The state.</param>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public ExclusiveTimer(TimerCallback timerCallback, object state, TimeSpan dueTime, TimeSpan period)
|
||||
: this(timerCallback, state, Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds))
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
public ExclusiveTimer(TimerCallback timerCallback)
|
||||
: this(timerCallback, null, Timeout.Infinite, Timeout.Infinite)
|
||||
{
|
||||
// placholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public ExclusiveTimer(Action timerCallback, int dueTime, int period)
|
||||
: this(s => { timerCallback?.Invoke(); }, null, dueTime, period)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public ExclusiveTimer(Action timerCallback, TimeSpan dueTime, TimeSpan period)
|
||||
: this(s => { timerCallback?.Invoke(); }, null, dueTime, period)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
public ExclusiveTimer(Action timerCallback)
|
||||
: this(timerCallback, Timeout.Infinite, Timeout.Infinite)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is disposing.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is disposing; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsDisposing => _isDisposing.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is disposed.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is disposed; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsDisposed => _isDisposed.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Changes the start time and the interval between method invocations for the internal timer.
|
||||
/// </summary>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public void Change(int dueTime, int period)
|
||||
{
|
||||
_period = period;
|
||||
|
||||
_backingTimer.Change(dueTime, Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the start time and the interval between method invocations for the internal timer.
|
||||
/// </summary>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public void Change(TimeSpan dueTime, TimeSpan period)
|
||||
=> Change(Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds));
|
||||
|
||||
/// <summary>
|
||||
/// Changes the interval between method invocations for the internal timer.
|
||||
/// </summary>
|
||||
/// <param name="period">The period.</param>
|
||||
public void Resume(int period) => Change(0, period);
|
||||
|
||||
/// <summary>
|
||||
/// Changes the interval between method invocations for the internal timer.
|
||||
/// </summary>
|
||||
/// <param name="period">The period.</param>
|
||||
public void Resume(TimeSpan period) => Change(TimeSpan.Zero, period);
|
||||
|
||||
/// <summary>
|
||||
/// Pauses this instance.
|
||||
/// </summary>
|
||||
public void Pause() => Change(Timeout.Infinite, Timeout.Infinite);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (_isDisposed == true || _isDisposing == true)
|
||||
return;
|
||||
|
||||
_isDisposing.Value = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_backingTimer.Dispose();
|
||||
_cycleDoneEvent.Wait();
|
||||
_cycleDoneEvent.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isDisposed.Value = true;
|
||||
_isDisposing.Value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logic that runs every time the timer hits the due time.
|
||||
/// </summary>
|
||||
/// <param name="state">The state.</param>
|
||||
private void InternalCallback(object state)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (IsDisposed || IsDisposing)
|
||||
return;
|
||||
}
|
||||
|
||||
if (_cycleDoneEvent.IsSet == false)
|
||||
return;
|
||||
|
||||
_cycleDoneEvent.Reset();
|
||||
|
||||
try
|
||||
{
|
||||
_userCallback(state);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_cycleDoneEvent?.Set();
|
||||
_backingTimer?.Change(_period, Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
/// <param name="state">The state.</param>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public ExclusiveTimer(TimerCallback timerCallback, Object state, Int32 dueTime, Int32 period) {
|
||||
this._period = period;
|
||||
this._userCallback = timerCallback;
|
||||
this._backingTimer = new Timer(this.InternalCallback, state ?? this, dueTime, Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
/// <param name="state">The state.</param>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public ExclusiveTimer(TimerCallback timerCallback, Object state, TimeSpan dueTime, TimeSpan period)
|
||||
: this(timerCallback, state, Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds)) {
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
public ExclusiveTimer(TimerCallback timerCallback)
|
||||
: this(timerCallback, null, Timeout.Infinite, Timeout.Infinite) {
|
||||
// placholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public ExclusiveTimer(Action timerCallback, Int32 dueTime, Int32 period)
|
||||
: this(s => timerCallback?.Invoke(), null, dueTime, period) {
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public ExclusiveTimer(Action timerCallback, TimeSpan dueTime, TimeSpan period)
|
||||
: this(s => timerCallback?.Invoke(), null, dueTime, period) {
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
public ExclusiveTimer(Action timerCallback)
|
||||
: this(timerCallback, Timeout.Infinite, Timeout.Infinite) {
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is disposing.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is disposing; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public Boolean IsDisposing => this._isDisposing.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is disposed.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is disposed; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public Boolean IsDisposed => this._isDisposed.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Changes the start time and the interval between method invocations for the internal timer.
|
||||
/// </summary>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public void Change(Int32 dueTime, Int32 period) {
|
||||
this._period = period;
|
||||
|
||||
_ = this._backingTimer.Change(dueTime, Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the start time and the interval between method invocations for the internal timer.
|
||||
/// </summary>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public void Change(TimeSpan dueTime, TimeSpan period)
|
||||
=> this.Change(Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds));
|
||||
|
||||
/// <summary>
|
||||
/// Changes the interval between method invocations for the internal timer.
|
||||
/// </summary>
|
||||
/// <param name="period">The period.</param>
|
||||
public void Resume(Int32 period) => this.Change(0, period);
|
||||
|
||||
/// <summary>
|
||||
/// Changes the interval between method invocations for the internal timer.
|
||||
/// </summary>
|
||||
/// <param name="period">The period.</param>
|
||||
public void Resume(TimeSpan period) => this.Change(TimeSpan.Zero, period);
|
||||
|
||||
/// <summary>
|
||||
/// Pauses this instance.
|
||||
/// </summary>
|
||||
public void Pause() => this.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
lock(this._syncLock) {
|
||||
if(this._isDisposed == true || this._isDisposing == true) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isDisposing.Value = true;
|
||||
}
|
||||
|
||||
try {
|
||||
this._backingTimer.Dispose();
|
||||
this._cycleDoneEvent.Wait();
|
||||
this._cycleDoneEvent.Dispose();
|
||||
} finally {
|
||||
this._isDisposed.Value = true;
|
||||
this._isDisposing.Value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logic that runs every time the timer hits the due time.
|
||||
/// </summary>
|
||||
/// <param name="state">The state.</param>
|
||||
private void InternalCallback(Object state) {
|
||||
lock(this._syncLock) {
|
||||
if(this.IsDisposed || this.IsDisposing) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(this._cycleDoneEvent.IsSet == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._cycleDoneEvent.Reset();
|
||||
|
||||
try {
|
||||
this._userCallback(state);
|
||||
} finally {
|
||||
this._cycleDoneEvent?.Set();
|
||||
_ = this._backingTimer?.Change(this._period, Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,94 +1,88 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Unosquare.Swan.Abstractions {
|
||||
/// <summary>
|
||||
/// Represents a generic expression parser.
|
||||
/// </summary>
|
||||
public abstract class ExpressionParser {
|
||||
/// <summary>
|
||||
/// Represents a generic expression parser.
|
||||
/// Resolves the expression.
|
||||
/// </summary>
|
||||
public abstract class ExpressionParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves the expression.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of expression result.</typeparam>
|
||||
/// <param name="tokens">The tokens.</param>
|
||||
/// <returns>The representation of the expression parsed.</returns>
|
||||
public virtual T ResolveExpression<T>(IEnumerable<Token> tokens)
|
||||
{
|
||||
var conversion = Expression.Convert(Parse(tokens), typeof(T));
|
||||
return Expression.Lambda<Func<T>>(conversion).Compile()();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the specified tokens.
|
||||
/// </summary>
|
||||
/// <param name="tokens">The tokens.</param>
|
||||
/// <returns>The final expression.</returns>
|
||||
public virtual Expression Parse(IEnumerable<Token> tokens)
|
||||
{
|
||||
var expressionStack = new List<Stack<Expression>>();
|
||||
|
||||
foreach (var token in tokens)
|
||||
{
|
||||
if (expressionStack.Any() == false)
|
||||
expressionStack.Add(new Stack<Expression>());
|
||||
|
||||
switch (token.Type)
|
||||
{
|
||||
case TokenType.Wall:
|
||||
expressionStack.Add(new Stack<Expression>());
|
||||
break;
|
||||
case TokenType.Number:
|
||||
expressionStack.Last().Push(Expression.Constant(Convert.ToDecimal(token.Value)));
|
||||
break;
|
||||
case TokenType.Variable:
|
||||
ResolveVariable(token.Value, expressionStack.Last());
|
||||
break;
|
||||
case TokenType.String:
|
||||
expressionStack.Last().Push(Expression.Constant(token.Value));
|
||||
break;
|
||||
case TokenType.Operator:
|
||||
ResolveOperator(token.Value, expressionStack.Last());
|
||||
break;
|
||||
case TokenType.Function:
|
||||
ResolveFunction(token.Value, expressionStack.Last());
|
||||
|
||||
if (expressionStack.Count > 1 && expressionStack.Last().Count == 1)
|
||||
{
|
||||
var lastValue = expressionStack.Last().Pop();
|
||||
expressionStack.Remove(expressionStack.Last());
|
||||
expressionStack.Last().Push(lastValue);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return expressionStack.Last().Pop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the variable.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="expressionStack">The expression stack.</param>
|
||||
public abstract void ResolveVariable(string value, Stack<Expression> expressionStack);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the operator.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="expressionStack">The expression stack.</param>
|
||||
public abstract void ResolveOperator(string value, Stack<Expression> expressionStack);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the function.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="expressionStack">The expression stack.</param>
|
||||
public abstract void ResolveFunction(string value, Stack<Expression> expressionStack);
|
||||
}
|
||||
/// <typeparam name="T">The type of expression result.</typeparam>
|
||||
/// <param name="tokens">The tokens.</param>
|
||||
/// <returns>The representation of the expression parsed.</returns>
|
||||
public virtual T ResolveExpression<T>(IEnumerable<Token> tokens) {
|
||||
UnaryExpression conversion = Expression.Convert(this.Parse(tokens), typeof(T));
|
||||
return Expression.Lambda<Func<T>>(conversion).Compile()();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the specified tokens.
|
||||
/// </summary>
|
||||
/// <param name="tokens">The tokens.</param>
|
||||
/// <returns>The final expression.</returns>
|
||||
public virtual Expression Parse(IEnumerable<Token> tokens) {
|
||||
List<Stack<Expression>> expressionStack = new List<Stack<Expression>>();
|
||||
|
||||
foreach(Token token in tokens) {
|
||||
if(expressionStack.Any() == false) {
|
||||
expressionStack.Add(new Stack<Expression>());
|
||||
}
|
||||
|
||||
switch(token.Type) {
|
||||
case TokenType.Wall:
|
||||
expressionStack.Add(new Stack<Expression>());
|
||||
break;
|
||||
case TokenType.Number:
|
||||
expressionStack.Last().Push(Expression.Constant(Convert.ToDecimal(token.Value)));
|
||||
break;
|
||||
case TokenType.Variable:
|
||||
this.ResolveVariable(token.Value, expressionStack.Last());
|
||||
break;
|
||||
case TokenType.String:
|
||||
expressionStack.Last().Push(Expression.Constant(token.Value));
|
||||
break;
|
||||
case TokenType.Operator:
|
||||
this.ResolveOperator(token.Value, expressionStack.Last());
|
||||
break;
|
||||
case TokenType.Function:
|
||||
this.ResolveFunction(token.Value, expressionStack.Last());
|
||||
|
||||
if(expressionStack.Count > 1 && expressionStack.Last().Count == 1) {
|
||||
Expression lastValue = expressionStack.Last().Pop();
|
||||
_ = expressionStack.Remove(expressionStack.Last());
|
||||
expressionStack.Last().Push(lastValue);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return expressionStack.Last().Pop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the variable.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="expressionStack">The expression stack.</param>
|
||||
public abstract void ResolveVariable(String value, Stack<Expression> expressionStack);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the operator.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="expressionStack">The expression stack.</param>
|
||||
public abstract void ResolveOperator(String value, Stack<Expression> expressionStack);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the function.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="expressionStack">The expression stack.</param>
|
||||
public abstract void ResolveFunction(String value, Stack<Expression> expressionStack);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Unosquare.Swan.Abstractions {
|
||||
/// <summary>
|
||||
/// Interface object map.
|
||||
/// </summary>
|
||||
public interface IObjectMap {
|
||||
/// <summary>
|
||||
/// Interface object map.
|
||||
/// Gets or sets the map.
|
||||
/// </summary>
|
||||
public interface IObjectMap
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the map.
|
||||
/// </summary>
|
||||
Dictionary<PropertyInfo, List<PropertyInfo>> Map { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the source.
|
||||
/// </summary>
|
||||
Type SourceType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the destination.
|
||||
/// </summary>
|
||||
Type DestinationType { get; }
|
||||
}
|
||||
Dictionary<PropertyInfo, List<PropertyInfo>> Map {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the source.
|
||||
/// </summary>
|
||||
Type SourceType {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the destination.
|
||||
/// </summary>
|
||||
Type DestinationType {
|
||||
get;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
|
||||
using System;
|
||||
|
||||
namespace Unosquare.Swan.Abstractions {
|
||||
/// <summary>
|
||||
/// Defines a generic interface for synchronized locking mechanisms.
|
||||
/// </summary>
|
||||
public interface ISyncLocker : IDisposable {
|
||||
/// <summary>
|
||||
/// Defines a generic interface for synchronized locking mechanisms.
|
||||
/// Acquires a writer lock.
|
||||
/// The lock is released when the returned locking object is disposed.
|
||||
/// </summary>
|
||||
public interface ISyncLocker : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Acquires a writer lock.
|
||||
/// The lock is released when the returned locking object is disposed.
|
||||
/// </summary>
|
||||
/// <returns>A disposable locking object.</returns>
|
||||
IDisposable AcquireWriterLock();
|
||||
|
||||
/// <summary>
|
||||
/// Acquires a reader lock.
|
||||
/// The lock is released when the returned locking object is disposed.
|
||||
/// </summary>
|
||||
/// <returns>A disposable locking object.</returns>
|
||||
IDisposable AcquireReaderLock();
|
||||
}
|
||||
/// <returns>A disposable locking object.</returns>
|
||||
IDisposable AcquireWriterLock();
|
||||
|
||||
/// <summary>
|
||||
/// Acquires a reader lock.
|
||||
/// The lock is released when the returned locking object is disposed.
|
||||
/// </summary>
|
||||
/// <returns>A disposable locking object.</returns>
|
||||
IDisposable AcquireReaderLock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
|
||||
namespace Unosquare.Swan.Abstractions {
|
||||
/// <summary>
|
||||
/// A simple Validator interface.
|
||||
/// </summary>
|
||||
public interface IValidator {
|
||||
/// <summary>
|
||||
/// A simple Validator interface.
|
||||
/// The error message.
|
||||
/// </summary>
|
||||
public interface IValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// The error message.
|
||||
/// </summary>
|
||||
string ErrorMessage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a value is valid.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type.</typeparam>
|
||||
/// <param name="value"> The value.</param>
|
||||
/// <returns>True if it is valid.False if it is not.</returns>
|
||||
bool IsValid<T>(T value);
|
||||
}
|
||||
String ErrorMessage {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a value is valid.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type.</typeparam>
|
||||
/// <param name="value"> The value.</param>
|
||||
/// <returns>True if it is valid.False if it is not.</returns>
|
||||
Boolean IsValid<T>(T value);
|
||||
}
|
||||
}
|
||||
@@ -1,57 +1,63 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
|
||||
using System;
|
||||
|
||||
namespace Unosquare.Swan.Abstractions {
|
||||
/// <summary>
|
||||
/// Provides a generalized API for ManualResetEvent and ManualResetEventSlim.
|
||||
/// </summary>
|
||||
/// <seealso cref="IDisposable" />
|
||||
public interface IWaitEvent : IDisposable {
|
||||
/// <summary>
|
||||
/// Provides a generalized API for ManualResetEvent and ManualResetEventSlim.
|
||||
/// Gets a value indicating whether the event is in the completed state.
|
||||
/// </summary>
|
||||
/// <seealso cref="IDisposable" />
|
||||
public interface IWaitEvent : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the event is in the completed state.
|
||||
/// </summary>
|
||||
bool IsCompleted { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the Begin method has been called.
|
||||
/// It returns false after the Complete method is called.
|
||||
/// </summary>
|
||||
bool IsInProgress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the underlying handle is not closed and it is still valid.
|
||||
/// </summary>
|
||||
bool IsValid { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is disposed.
|
||||
/// </summary>
|
||||
bool IsDisposed { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Enters the state in which waiters need to wait.
|
||||
/// All future waiters will block when they call the Wait method.
|
||||
/// </summary>
|
||||
void Begin();
|
||||
|
||||
/// <summary>
|
||||
/// Leaves the state in which waiters need to wait.
|
||||
/// All current waiters will continue.
|
||||
/// </summary>
|
||||
void Complete();
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the event to be completed.
|
||||
/// </summary>
|
||||
void Wait();
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the event to be completed.
|
||||
/// Returns <c>true</c> when there was no timeout. False if the timeout was reached.
|
||||
/// </summary>
|
||||
/// <param name="timeout">The maximum amount of time to wait for.</param>
|
||||
/// <returns><c>true</c> when there was no timeout. <c>false</c> if the timeout was reached.</returns>
|
||||
bool Wait(TimeSpan timeout);
|
||||
}
|
||||
Boolean IsCompleted {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the Begin method has been called.
|
||||
/// It returns false after the Complete method is called.
|
||||
/// </summary>
|
||||
Boolean IsInProgress {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the underlying handle is not closed and it is still valid.
|
||||
/// </summary>
|
||||
Boolean IsValid {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is disposed.
|
||||
/// </summary>
|
||||
Boolean IsDisposed {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enters the state in which waiters need to wait.
|
||||
/// All future waiters will block when they call the Wait method.
|
||||
/// </summary>
|
||||
void Begin();
|
||||
|
||||
/// <summary>
|
||||
/// Leaves the state in which waiters need to wait.
|
||||
/// All current waiters will continue.
|
||||
/// </summary>
|
||||
void Complete();
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the event to be completed.
|
||||
/// </summary>
|
||||
void Wait();
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the event to be completed.
|
||||
/// Returns <c>true</c> when there was no timeout. False if the timeout was reached.
|
||||
/// </summary>
|
||||
/// <param name="timeout">The maximum amount of time to wait for.</param>
|
||||
/// <returns><c>true</c> when there was no timeout. <c>false</c> if the timeout was reached.</returns>
|
||||
Boolean Wait(TimeSpan timeout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
namespace Unosquare.Swan.Abstractions {
|
||||
/// <summary>
|
||||
/// A simple interface for application workers.
|
||||
/// </summary>
|
||||
public interface IWorker {
|
||||
/// <summary>
|
||||
/// A simple interface for application workers.
|
||||
/// Should start the task immediately and asynchronously.
|
||||
/// </summary>
|
||||
public interface IWorker
|
||||
{
|
||||
/// <summary>
|
||||
/// Should start the task immediately and asynchronously.
|
||||
/// </summary>
|
||||
void Start();
|
||||
|
||||
/// <summary>
|
||||
/// Should stop the task immediately and synchronously.
|
||||
/// </summary>
|
||||
void Stop();
|
||||
}
|
||||
void Start();
|
||||
|
||||
/// <summary>
|
||||
/// Should stop the task immediately and synchronously.
|
||||
/// </summary>
|
||||
void Stop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,169 +1,155 @@
|
||||
#if !NETSTANDARD1_3
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Swan;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
namespace Unosquare.Swan.Abstractions {
|
||||
/// <summary>
|
||||
/// Represents an background worker abstraction with a life cycle and running at a independent thread.
|
||||
/// </summary>
|
||||
public abstract class RunnerBase {
|
||||
private Thread _worker;
|
||||
private CancellationTokenSource _cancelTokenSource;
|
||||
private ManualResetEvent _workFinished;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an background worker abstraction with a life cycle and running at a independent thread.
|
||||
/// Initializes a new instance of the <see cref="RunnerBase"/> class.
|
||||
/// </summary>
|
||||
public abstract class RunnerBase
|
||||
{
|
||||
private Thread _worker;
|
||||
private CancellationTokenSource _cancelTokenSource;
|
||||
private ManualResetEvent _workFinished;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RunnerBase"/> class.
|
||||
/// </summary>
|
||||
/// <param name="isEnabled">if set to <c>true</c> [is enabled].</param>
|
||||
protected RunnerBase(bool isEnabled)
|
||||
{
|
||||
Name = GetType().Name;
|
||||
IsEnabled = isEnabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the error messages.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The error messages.
|
||||
/// </value>
|
||||
public List<string> ErrorMessages { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name.
|
||||
/// </value>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is running.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is running; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsRunning { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is enabled.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is enabled; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsEnabled { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Starts this instance.
|
||||
/// </summary>
|
||||
public virtual void Start()
|
||||
{
|
||||
if (IsEnabled == false)
|
||||
return;
|
||||
|
||||
$"Start Requested".Debug(Name);
|
||||
_cancelTokenSource = new CancellationTokenSource();
|
||||
_workFinished = new ManualResetEvent(false);
|
||||
|
||||
_worker = new Thread(() =>
|
||||
{
|
||||
_workFinished.Reset();
|
||||
IsRunning = true;
|
||||
try
|
||||
{
|
||||
Setup();
|
||||
DoBackgroundWork(_cancelTokenSource.Token);
|
||||
}
|
||||
catch (ThreadAbortException)
|
||||
{
|
||||
$"{nameof(ThreadAbortException)} caught.".Warn(Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
$"{ex.GetType()}: {ex.Message}\r\n{ex.StackTrace}".Error(Name);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Cleanup();
|
||||
_workFinished?.Set();
|
||||
IsRunning = false;
|
||||
"Stopped Completely".Debug(Name);
|
||||
}
|
||||
})
|
||||
{
|
||||
IsBackground = true,
|
||||
Name = $"{Name}Thread",
|
||||
};
|
||||
|
||||
_worker.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops this instance.
|
||||
/// </summary>
|
||||
public virtual void Stop()
|
||||
{
|
||||
if (IsEnabled == false || IsRunning == false)
|
||||
return;
|
||||
|
||||
$"Stop Requested".Debug(Name);
|
||||
_cancelTokenSource.Cancel();
|
||||
var waitRetries = 5;
|
||||
while (waitRetries >= 1)
|
||||
{
|
||||
if (_workFinished.WaitOne(250))
|
||||
{
|
||||
waitRetries = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
waitRetries--;
|
||||
}
|
||||
|
||||
if (waitRetries < 0)
|
||||
{
|
||||
"Workbench stopped gracefully".Debug(Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
"Did not respond to stop request. Aborting thread and waiting . . .".Warn(Name);
|
||||
_worker.Abort();
|
||||
|
||||
if (_workFinished.WaitOne(5000) == false)
|
||||
"Waited and no response. Worker might have been left in an inconsistent state.".Error(Name);
|
||||
else
|
||||
"Waited for worker and it finally responded (OK).".Debug(Name);
|
||||
}
|
||||
|
||||
_workFinished.Dispose();
|
||||
_workFinished = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setups this instance.
|
||||
/// </summary>
|
||||
protected virtual void Setup()
|
||||
{
|
||||
// empty
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleanups this instance.
|
||||
/// </summary>
|
||||
protected virtual void Cleanup()
|
||||
{
|
||||
// empty
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does the background work.
|
||||
/// </summary>
|
||||
/// <param name="ct">The ct.</param>
|
||||
protected abstract void DoBackgroundWork(CancellationToken ct);
|
||||
}
|
||||
/// <param name="isEnabled">if set to <c>true</c> [is enabled].</param>
|
||||
protected RunnerBase(Boolean isEnabled) {
|
||||
this.Name = this.GetType().Name;
|
||||
this.IsEnabled = isEnabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the error messages.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The error messages.
|
||||
/// </value>
|
||||
public List<String> ErrorMessages { get; } = new List<String>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name.
|
||||
/// </value>
|
||||
public String Name {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is running.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is running; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public Boolean IsRunning {
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is enabled.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is enabled; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public Boolean IsEnabled {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts this instance.
|
||||
/// </summary>
|
||||
public virtual void Start() {
|
||||
if(this.IsEnabled == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$"Start Requested".Debug(this.Name);
|
||||
this._cancelTokenSource = new CancellationTokenSource();
|
||||
this._workFinished = new ManualResetEvent(false);
|
||||
|
||||
this._worker = new Thread(() => {
|
||||
_ = this._workFinished.Reset();
|
||||
this.IsRunning = true;
|
||||
try {
|
||||
this.Setup();
|
||||
this.DoBackgroundWork(this._cancelTokenSource.Token);
|
||||
} catch(ThreadAbortException) {
|
||||
$"{nameof(ThreadAbortException)} caught.".Warn(this.Name);
|
||||
} catch(Exception ex) {
|
||||
$"{ex.GetType()}: {ex.Message}\r\n{ex.StackTrace}".Error(this.Name);
|
||||
} finally {
|
||||
this.Cleanup();
|
||||
_ = this._workFinished?.Set();
|
||||
this.IsRunning = false;
|
||||
"Stopped Completely".Debug(this.Name);
|
||||
}
|
||||
}) {
|
||||
IsBackground = true,
|
||||
Name = $"{this.Name}Thread",
|
||||
};
|
||||
|
||||
this._worker.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops this instance.
|
||||
/// </summary>
|
||||
public virtual void Stop() {
|
||||
if(this.IsEnabled == false || this.IsRunning == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$"Stop Requested".Debug(this.Name);
|
||||
this._cancelTokenSource.Cancel();
|
||||
Int32 waitRetries = 5;
|
||||
while(waitRetries >= 1) {
|
||||
if(this._workFinished.WaitOne(250)) {
|
||||
waitRetries = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
waitRetries--;
|
||||
}
|
||||
|
||||
if(waitRetries < 0) {
|
||||
"Workbench stopped gracefully".Debug(this.Name);
|
||||
} else {
|
||||
"Did not respond to stop request. Aborting thread and waiting . . .".Warn(this.Name);
|
||||
this._worker.Abort();
|
||||
|
||||
if(this._workFinished.WaitOne(5000) == false) {
|
||||
"Waited and no response. Worker might have been left in an inconsistent state.".Error(this.Name);
|
||||
} else {
|
||||
"Waited for worker and it finally responded (OK).".Debug(this.Name);
|
||||
}
|
||||
}
|
||||
|
||||
this._workFinished.Dispose();
|
||||
this._workFinished = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setups this instance.
|
||||
/// </summary>
|
||||
protected virtual void Setup() {
|
||||
// empty
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleanups this instance.
|
||||
/// </summary>
|
||||
protected virtual void Cleanup() {
|
||||
// empty
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does the background work.
|
||||
/// </summary>
|
||||
/// <param name="ct">The ct.</param>
|
||||
protected abstract void DoBackgroundWork(CancellationToken ct);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,188 +1,184 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using Formatters;
|
||||
using Reflection;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using Unosquare.Swan.Formatters;
|
||||
using Unosquare.Swan.Reflection;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Unosquare.Swan.Abstractions {
|
||||
/// <summary>
|
||||
/// Represents a provider to save and load settings using a plain JSON file.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The following example shows how to save and load settings.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Abstractions;
|
||||
///
|
||||
/// public class Example
|
||||
/// {
|
||||
/// public static void Main()
|
||||
/// {
|
||||
/// // get user from settings
|
||||
/// var user = SettingsProvider<Settings>.Instance.Global.User;
|
||||
///
|
||||
/// // modify the port
|
||||
/// SettingsProvider<Settings>.Instance.Global.Port = 20;
|
||||
///
|
||||
/// // if we want these settings to persist
|
||||
/// SettingsProvider<Settings>.Instance.PersistGlobalSettings();
|
||||
/// }
|
||||
///
|
||||
/// public class Settings
|
||||
/// {
|
||||
/// public int Port { get; set; } = 9696;
|
||||
///
|
||||
/// public string User { get; set; } = "User";
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <typeparam name="T">The type of settings model.</typeparam>
|
||||
public sealed class SettingsProvider<T>
|
||||
: SingletonBase<SettingsProvider<T>> {
|
||||
private readonly Object _syncRoot = new Object();
|
||||
|
||||
private T _global;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a provider to save and load settings using a plain JSON file.
|
||||
/// Gets or sets the configuration file path. By default the entry assembly directory is used
|
||||
/// and the filename is 'appsettings.json'.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The following example shows how to save and load settings.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Abstractions;
|
||||
///
|
||||
/// public class Example
|
||||
/// {
|
||||
/// public static void Main()
|
||||
/// {
|
||||
/// // get user from settings
|
||||
/// var user = SettingsProvider<Settings>.Instance.Global.User;
|
||||
///
|
||||
/// // modify the port
|
||||
/// SettingsProvider<Settings>.Instance.Global.Port = 20;
|
||||
///
|
||||
/// // if we want these settings to persist
|
||||
/// SettingsProvider<Settings>.Instance.PersistGlobalSettings();
|
||||
/// }
|
||||
///
|
||||
/// public class Settings
|
||||
/// {
|
||||
/// public int Port { get; set; } = 9696;
|
||||
///
|
||||
/// public string User { get; set; } = "User";
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <typeparam name="T">The type of settings model.</typeparam>
|
||||
public sealed class SettingsProvider<T>
|
||||
: SingletonBase<SettingsProvider<T>>
|
||||
{
|
||||
private readonly object _syncRoot = new object();
|
||||
|
||||
private T _global;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the configuration file path. By default the entry assembly directory is used
|
||||
/// and the filename is 'appsettings.json'.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The configuration file path.
|
||||
/// </value>
|
||||
public string ConfigurationFilePath { get; set; } =
|
||||
/// <value>
|
||||
/// The configuration file path.
|
||||
/// </value>
|
||||
public String ConfigurationFilePath {
|
||||
get; set;
|
||||
} =
|
||||
#if NETSTANDARD1_3
|
||||
Path.Combine(Runtime.LocalStoragePath, "appsettings.json");
|
||||
#else
|
||||
Path.Combine(Runtime.EntryAssemblyDirectory, "appsettings.json");
|
||||
Path.Combine(Runtime.EntryAssemblyDirectory, "appsettings.json");
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets the global settings object.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The global settings object.
|
||||
/// </value>
|
||||
public T Global
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (Equals(_global, default(T)))
|
||||
ReloadGlobalSettings();
|
||||
|
||||
return _global;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reloads the global settings.
|
||||
/// </summary>
|
||||
public void ReloadGlobalSettings()
|
||||
{
|
||||
if (File.Exists(ConfigurationFilePath) == false || File.ReadAllText(ConfigurationFilePath).Length == 0)
|
||||
{
|
||||
ResetGlobalSettings();
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_syncRoot)
|
||||
_global = Json.Deserialize<T>(File.ReadAllText(ConfigurationFilePath));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Persists the global settings.
|
||||
/// </summary>
|
||||
public void PersistGlobalSettings() => File.WriteAllText(ConfigurationFilePath, Json.Serialize(Global, true));
|
||||
|
||||
/// <summary>
|
||||
/// Updates settings from list.
|
||||
/// </summary>
|
||||
/// <param name="propertyList">The list.</param>
|
||||
/// <returns>
|
||||
/// A list of settings of type ref="ExtendedPropertyInfo".
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">propertyList.</exception>
|
||||
public List<string> RefreshFromList(List<ExtendedPropertyInfo<T>> propertyList)
|
||||
{
|
||||
if (propertyList == null)
|
||||
throw new ArgumentNullException(nameof(propertyList));
|
||||
|
||||
var changedSettings = new List<string>();
|
||||
var globalProps = Runtime.PropertyTypeCache.RetrieveAllProperties<T>();
|
||||
|
||||
foreach (var property in propertyList)
|
||||
{
|
||||
var propertyInfo = globalProps.FirstOrDefault(x => x.Name == property.Property);
|
||||
|
||||
if (propertyInfo == null) continue;
|
||||
|
||||
var originalValue = propertyInfo.GetValue(Global);
|
||||
var isChanged = propertyInfo.PropertyType.IsArray
|
||||
? property.Value is IEnumerable enumerable && propertyInfo.TrySetArray(enumerable.Cast<object>(), Global)
|
||||
: SetValue(property.Value, originalValue, propertyInfo);
|
||||
|
||||
if (!isChanged) continue;
|
||||
|
||||
changedSettings.Add(property.Property);
|
||||
PersistGlobalSettings();
|
||||
}
|
||||
|
||||
return changedSettings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list.
|
||||
/// </summary>
|
||||
/// <returns>A List of ExtendedPropertyInfo of the type T.</returns>
|
||||
public List<ExtendedPropertyInfo<T>> GetList()
|
||||
{
|
||||
var jsonData = Json.Deserialize(Json.Serialize(Global)) as Dictionary<string, object>;
|
||||
|
||||
return jsonData?.Keys
|
||||
.Select(p => new ExtendedPropertyInfo<T>(p) { Value = jsonData[p] })
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the global settings.
|
||||
/// </summary>
|
||||
public void ResetGlobalSettings()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
_global = Activator.CreateInstance<T>();
|
||||
|
||||
PersistGlobalSettings();
|
||||
}
|
||||
|
||||
private bool SetValue(object property, object originalValue, PropertyInfo propertyInfo)
|
||||
{
|
||||
switch (property)
|
||||
{
|
||||
case null when originalValue == null:
|
||||
break;
|
||||
case null:
|
||||
propertyInfo.SetValue(Global, null);
|
||||
return true;
|
||||
default:
|
||||
if (propertyInfo.PropertyType.TryParseBasicType(property, out var propertyValue) &&
|
||||
!propertyValue.Equals(originalValue))
|
||||
{
|
||||
propertyInfo.SetValue(Global, propertyValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the global settings object.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The global settings object.
|
||||
/// </value>
|
||||
public T Global {
|
||||
get {
|
||||
lock(this._syncRoot) {
|
||||
if(Equals(this._global, default(T))) {
|
||||
this.ReloadGlobalSettings();
|
||||
}
|
||||
|
||||
return this._global;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reloads the global settings.
|
||||
/// </summary>
|
||||
public void ReloadGlobalSettings() {
|
||||
if(File.Exists(this.ConfigurationFilePath) == false || File.ReadAllText(this.ConfigurationFilePath).Length == 0) {
|
||||
this.ResetGlobalSettings();
|
||||
return;
|
||||
}
|
||||
|
||||
lock(this._syncRoot) {
|
||||
this._global = Json.Deserialize<T>(File.ReadAllText(this.ConfigurationFilePath));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Persists the global settings.
|
||||
/// </summary>
|
||||
public void PersistGlobalSettings() => File.WriteAllText(this.ConfigurationFilePath, Json.Serialize(this.Global, true));
|
||||
|
||||
/// <summary>
|
||||
/// Updates settings from list.
|
||||
/// </summary>
|
||||
/// <param name="propertyList">The list.</param>
|
||||
/// <returns>
|
||||
/// A list of settings of type ref="ExtendedPropertyInfo".
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">propertyList.</exception>
|
||||
public List<String> RefreshFromList(List<ExtendedPropertyInfo<T>> propertyList) {
|
||||
if(propertyList == null) {
|
||||
throw new ArgumentNullException(nameof(propertyList));
|
||||
}
|
||||
|
||||
List<String> changedSettings = new List<String>();
|
||||
IEnumerable<PropertyInfo> globalProps = Runtime.PropertyTypeCache.RetrieveAllProperties<T>();
|
||||
|
||||
foreach(ExtendedPropertyInfo<T> property in propertyList) {
|
||||
PropertyInfo propertyInfo = globalProps.FirstOrDefault(x => x.Name == property.Property);
|
||||
|
||||
if(propertyInfo == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object originalValue = propertyInfo.GetValue(this.Global);
|
||||
Boolean isChanged = propertyInfo.PropertyType.IsArray
|
||||
? property.Value is IEnumerable enumerable && propertyInfo.TrySetArray(enumerable.Cast<Object>(), this.Global)
|
||||
: this.SetValue(property.Value, originalValue, propertyInfo);
|
||||
|
||||
if(!isChanged) {
|
||||
continue;
|
||||
}
|
||||
|
||||
changedSettings.Add(property.Property);
|
||||
this.PersistGlobalSettings();
|
||||
}
|
||||
|
||||
return changedSettings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list.
|
||||
/// </summary>
|
||||
/// <returns>A List of ExtendedPropertyInfo of the type T.</returns>
|
||||
public List<ExtendedPropertyInfo<T>> GetList() {
|
||||
Dictionary<String, Object> jsonData = Json.Deserialize(Json.Serialize(this.Global)) as Dictionary<String, Object>;
|
||||
|
||||
return jsonData?.Keys
|
||||
.Select(p => new ExtendedPropertyInfo<T>(p) { Value = jsonData[p] })
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the global settings.
|
||||
/// </summary>
|
||||
public void ResetGlobalSettings() {
|
||||
lock(this._syncRoot) {
|
||||
this._global = Activator.CreateInstance<T>();
|
||||
}
|
||||
|
||||
this.PersistGlobalSettings();
|
||||
}
|
||||
|
||||
private Boolean SetValue(Object property, Object originalValue, PropertyInfo propertyInfo) {
|
||||
switch(property) {
|
||||
case null when originalValue == null:
|
||||
break;
|
||||
case null:
|
||||
propertyInfo.SetValue(this.Global, null);
|
||||
return true;
|
||||
default:
|
||||
if(propertyInfo.PropertyType.TryParseBasicType(property, out Object propertyValue) &&
|
||||
!propertyValue.Equals(originalValue)) {
|
||||
propertyInfo.SetValue(this.Global, propertyValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +1,57 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
|
||||
using System;
|
||||
|
||||
namespace Unosquare.Swan.Abstractions {
|
||||
/// <summary>
|
||||
/// Represents a singleton pattern abstract class.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of class.</typeparam>
|
||||
public abstract class SingletonBase<T> : IDisposable
|
||||
where T : class {
|
||||
/// <summary>
|
||||
/// Represents a singleton pattern abstract class.
|
||||
/// The static, singleton instance reference.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of class.</typeparam>
|
||||
public abstract class SingletonBase<T> : IDisposable
|
||||
where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// The static, singleton instance reference.
|
||||
/// </summary>
|
||||
protected static readonly Lazy<T> LazyInstance = new Lazy<T>(
|
||||
valueFactory: () => Activator.CreateInstance(typeof(T), true) as T,
|
||||
isThreadSafe: true);
|
||||
|
||||
private bool _isDisposing; // To detect redundant calls
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance that this singleton represents.
|
||||
/// If the instance is null, it is constructed and assigned when this member is accessed.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The instance.
|
||||
/// </value>
|
||||
public static T Instance => LazyInstance.Value;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => Dispose(true);
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// Call the GC.SuppressFinalize if you override this method and use
|
||||
/// a non-default class finalizer (destructor).
|
||||
/// </summary>
|
||||
/// <param name="disposeManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposeManaged)
|
||||
{
|
||||
if (_isDisposing) return;
|
||||
|
||||
_isDisposing = true;
|
||||
|
||||
// free managed resources
|
||||
if (LazyInstance == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
var disposableInstance = LazyInstance.Value as IDisposable;
|
||||
disposableInstance?.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// swallow
|
||||
}
|
||||
}
|
||||
}
|
||||
protected static readonly Lazy<T> LazyInstance = new Lazy<T>(
|
||||
valueFactory: () => Activator.CreateInstance(typeof(T), true) as T,
|
||||
isThreadSafe: true);
|
||||
|
||||
private Boolean _isDisposing; // To detect redundant calls
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance that this singleton represents.
|
||||
/// If the instance is null, it is constructed and assigned when this member is accessed.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The instance.
|
||||
/// </value>
|
||||
public static T Instance => LazyInstance.Value;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => this.Dispose(true);
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// Call the GC.SuppressFinalize if you override this method and use
|
||||
/// a non-default class finalizer (destructor).
|
||||
/// </summary>
|
||||
/// <param name="disposeManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(Boolean disposeManaged) {
|
||||
if(this._isDisposing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isDisposing = true;
|
||||
|
||||
// free managed resources
|
||||
if(LazyInstance == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
IDisposable disposableInstance = LazyInstance.Value as IDisposable;
|
||||
disposableInstance?.Dispose();
|
||||
} catch {
|
||||
// swallow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,143 +1,139 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Unosquare.Swan.Abstractions {
|
||||
/// <summary>
|
||||
/// Represents a generic tokenizer.
|
||||
/// </summary>
|
||||
public abstract class Tokenizer {
|
||||
private const Char PeriodChar = '.';
|
||||
private const Char CommaChar = ',';
|
||||
private const Char StringQuotedChar = '"';
|
||||
private const Char OpenFuncChar = '(';
|
||||
private const Char CloseFuncChar = ')';
|
||||
private const Char NegativeChar = '-';
|
||||
|
||||
private const String OpenFuncStr = "(";
|
||||
|
||||
private readonly List<Operator> _operators = new List<Operator>();
|
||||
|
||||
/// <summary>
|
||||
/// Represents a generic tokenizer.
|
||||
/// Initializes a new instance of the <see cref="Tokenizer"/> class.
|
||||
/// This constructor will use the following default operators:
|
||||
///
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>Operator</term>
|
||||
/// <description>Precedence</description>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term>=</term>
|
||||
/// <description>1</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>!=</term>
|
||||
/// <description>1</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>></term>
|
||||
/// <description>2</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term><</term>
|
||||
/// <description>2</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>>=</term>
|
||||
/// <description>2</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term><=</term>
|
||||
/// <description>2</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>+</term>
|
||||
/// <description>3</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>&</term>
|
||||
/// <description>3</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>-</term>
|
||||
/// <description>3</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>*</term>
|
||||
/// <description>4</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>(backslash)</term>
|
||||
/// <description>4</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>/</term>
|
||||
/// <description>4</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>^</term>
|
||||
/// <description>4</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public abstract class Tokenizer
|
||||
/// <param name="input">The input.</param>
|
||||
protected Tokenizer(String input) {
|
||||
this._operators.AddRange(this.GetDefaultOperators());
|
||||
this.Tokenize(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Tokenizer" /> class.
|
||||
/// </summary>
|
||||
/// <param name="input">The input.</param>
|
||||
/// <param name="operators">The operators to use.</param>
|
||||
protected Tokenizer(String input, IEnumerable<Operator> operators) {
|
||||
this._operators.AddRange(operators);
|
||||
this.Tokenize(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tokens.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The tokens.
|
||||
/// </value>
|
||||
public List<Token> Tokens { get; } = new List<Token>();
|
||||
|
||||
/// <summary>
|
||||
/// Validates the input and return the start index for tokenizer.
|
||||
/// </summary>
|
||||
/// <param name="input">The input.</param>
|
||||
/// <param name="startIndex">The start index.</param>
|
||||
/// <returns><c>true</c> if the input is valid, otherwise <c>false</c>.</returns>
|
||||
public abstract Boolean ValidateInput(String input, out Int32 startIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the type of the function or member.
|
||||
/// </summary>
|
||||
/// <param name="input">The input.</param>
|
||||
/// <returns>The token type.</returns>
|
||||
public abstract TokenType ResolveFunctionOrMemberType(String input);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the function or member.
|
||||
/// </summary>
|
||||
/// <param name="input">The input.</param>
|
||||
/// <param name="position">The position.</param>
|
||||
/// <returns><c>true</c> if the input is a valid function or variable, otherwise <c>false</c>.</returns>
|
||||
public virtual Boolean EvaluateFunctionOrMember(String input, Int32 position) => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default operators.
|
||||
/// </summary>
|
||||
/// <returns>An array with the operators to use for the tokenizer.</returns>
|
||||
public virtual Operator[] GetDefaultOperators() => new[]
|
||||
{
|
||||
private const char PeriodChar = '.';
|
||||
private const char CommaChar = ',';
|
||||
private const char StringQuotedChar = '"';
|
||||
private const char OpenFuncChar = '(';
|
||||
private const char CloseFuncChar = ')';
|
||||
private const char NegativeChar = '-';
|
||||
|
||||
private const string OpenFuncStr = "(";
|
||||
|
||||
private readonly List<Operator> _operators = new List<Operator>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Tokenizer"/> class.
|
||||
/// This constructor will use the following default operators:
|
||||
///
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>Operator</term>
|
||||
/// <description>Precedence</description>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term>=</term>
|
||||
/// <description>1</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>!=</term>
|
||||
/// <description>1</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>></term>
|
||||
/// <description>2</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term><</term>
|
||||
/// <description>2</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>>=</term>
|
||||
/// <description>2</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term><=</term>
|
||||
/// <description>2</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>+</term>
|
||||
/// <description>3</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>&</term>
|
||||
/// <description>3</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>-</term>
|
||||
/// <description>3</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>*</term>
|
||||
/// <description>4</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>(backslash)</term>
|
||||
/// <description>4</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>/</term>
|
||||
/// <description>4</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>^</term>
|
||||
/// <description>4</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
/// <param name="input">The input.</param>
|
||||
protected Tokenizer(string input)
|
||||
{
|
||||
_operators.AddRange(GetDefaultOperators());
|
||||
Tokenize(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Tokenizer" /> class.
|
||||
/// </summary>
|
||||
/// <param name="input">The input.</param>
|
||||
/// <param name="operators">The operators to use.</param>
|
||||
protected Tokenizer(string input, IEnumerable<Operator> operators)
|
||||
{
|
||||
_operators.AddRange(operators);
|
||||
Tokenize(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tokens.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The tokens.
|
||||
/// </value>
|
||||
public List<Token> Tokens { get; } = new List<Token>();
|
||||
|
||||
/// <summary>
|
||||
/// Validates the input and return the start index for tokenizer.
|
||||
/// </summary>
|
||||
/// <param name="input">The input.</param>
|
||||
/// <param name="startIndex">The start index.</param>
|
||||
/// <returns><c>true</c> if the input is valid, otherwise <c>false</c>.</returns>
|
||||
public abstract bool ValidateInput(string input, out int startIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the type of the function or member.
|
||||
/// </summary>
|
||||
/// <param name="input">The input.</param>
|
||||
/// <returns>The token type.</returns>
|
||||
public abstract TokenType ResolveFunctionOrMemberType(string input);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the function or member.
|
||||
/// </summary>
|
||||
/// <param name="input">The input.</param>
|
||||
/// <param name="position">The position.</param>
|
||||
/// <returns><c>true</c> if the input is a valid function or variable, otherwise <c>false</c>.</returns>
|
||||
public virtual bool EvaluateFunctionOrMember(string input, int position) => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default operators.
|
||||
/// </summary>
|
||||
/// <returns>An array with the operators to use for the tokenizer.</returns>
|
||||
public virtual Operator[] GetDefaultOperators() => new[]
|
||||
{
|
||||
new Operator {Name = "=", Precedence = 1},
|
||||
new Operator {Name = "!=", Precedence = 1},
|
||||
new Operator {Name = ">", Precedence = 2},
|
||||
@@ -151,309 +147,304 @@
|
||||
new Operator {Name = "/", Precedence = 4},
|
||||
new Operator {Name = "\\", Precedence = 4},
|
||||
new Operator {Name = "^", Precedence = 4},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Shunting the yard.
|
||||
/// </summary>
|
||||
/// <param name="includeFunctionStopper">if set to <c>true</c> [include function stopper] (Token type <c>Wall</c>).</param>
|
||||
/// <returns>
|
||||
/// Enumerable of the token in in.
|
||||
/// </returns>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Wrong token
|
||||
/// or
|
||||
/// Mismatched parenthesis.
|
||||
/// </exception>
|
||||
public virtual IEnumerable<Token> ShuntingYard(bool includeFunctionStopper = true)
|
||||
{
|
||||
var stack = new Stack<Token>();
|
||||
|
||||
foreach (var tok in Tokens)
|
||||
{
|
||||
switch (tok.Type)
|
||||
{
|
||||
case TokenType.Number:
|
||||
case TokenType.Variable:
|
||||
case TokenType.String:
|
||||
yield return tok;
|
||||
break;
|
||||
case TokenType.Function:
|
||||
stack.Push(tok);
|
||||
break;
|
||||
case TokenType.Operator:
|
||||
while (stack.Any() && stack.Peek().Type == TokenType.Operator &&
|
||||
CompareOperators(tok.Value, stack.Peek().Value))
|
||||
yield return stack.Pop();
|
||||
|
||||
stack.Push(tok);
|
||||
break;
|
||||
case TokenType.Comma:
|
||||
while (stack.Any() && (stack.Peek().Type != TokenType.Comma &&
|
||||
stack.Peek().Type != TokenType.Parenthesis))
|
||||
yield return stack.Pop();
|
||||
|
||||
break;
|
||||
case TokenType.Parenthesis:
|
||||
if (tok.Value == OpenFuncStr)
|
||||
{
|
||||
if (stack.Any() && stack.Peek().Type == TokenType.Function)
|
||||
{
|
||||
if (includeFunctionStopper)
|
||||
yield return new Token(TokenType.Wall, tok.Value);
|
||||
}
|
||||
|
||||
stack.Push(tok);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (stack.Peek().Value != OpenFuncStr)
|
||||
yield return stack.Pop();
|
||||
|
||||
stack.Pop();
|
||||
|
||||
if (stack.Any() && stack.Peek().Type == TokenType.Function)
|
||||
{
|
||||
yield return stack.Pop();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("Wrong token");
|
||||
}
|
||||
}
|
||||
|
||||
while (stack.Any())
|
||||
{
|
||||
var tok = stack.Pop();
|
||||
if (tok.Type == TokenType.Parenthesis)
|
||||
throw new InvalidOperationException("Mismatched parenthesis");
|
||||
|
||||
yield return tok;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CompareOperators(Operator op1, Operator op2) => op1.RightAssociative
|
||||
? op1.Precedence < op2.Precedence
|
||||
: op1.Precedence <= op2.Precedence;
|
||||
|
||||
private void Tokenize(string input)
|
||||
{
|
||||
if (!ValidateInput(input, out var startIndex))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = startIndex; i < input.Length; i++)
|
||||
{
|
||||
if (char.IsWhiteSpace(input, i)) continue;
|
||||
|
||||
if (input[i] == CommaChar)
|
||||
{
|
||||
Tokens.Add(new Token(TokenType.Comma, new string(new[] { input[i] })));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (input[i] == StringQuotedChar)
|
||||
{
|
||||
i = ExtractString(input, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char.IsLetter(input, i) || EvaluateFunctionOrMember(input, i))
|
||||
{
|
||||
i = ExtractFunctionOrMember(input, i);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char.IsNumber(input, i) || (
|
||||
input[i] == NegativeChar &&
|
||||
((Tokens.Any() && Tokens.Last().Type != TokenType.Number) || !Tokens.Any())))
|
||||
{
|
||||
i = ExtractNumber(input, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (input[i] == OpenFuncChar ||
|
||||
input[i] == CloseFuncChar)
|
||||
{
|
||||
Tokens.Add(new Token(TokenType.Parenthesis, new string(new[] { input[i] })));
|
||||
continue;
|
||||
}
|
||||
|
||||
i = ExtractOperator(input, i);
|
||||
}
|
||||
}
|
||||
|
||||
private int ExtractData(
|
||||
string input,
|
||||
int i,
|
||||
Func<string, TokenType> tokenTypeEvaluation,
|
||||
Func<char, bool> evaluation,
|
||||
int right = 0,
|
||||
int left = -1)
|
||||
{
|
||||
var charCount = 0;
|
||||
for (var j = i + right; j < input.Length; j++)
|
||||
{
|
||||
if (evaluation(input[j]))
|
||||
break;
|
||||
|
||||
charCount++;
|
||||
}
|
||||
|
||||
// Extract and set the value
|
||||
var value = input.SliceLength(i + right, charCount);
|
||||
Tokens.Add(new Token(tokenTypeEvaluation(value), value));
|
||||
|
||||
i += charCount + left;
|
||||
return i;
|
||||
}
|
||||
|
||||
private int ExtractOperator(string input, int i) =>
|
||||
ExtractData(input, i, x => TokenType.Operator, x => x == OpenFuncChar ||
|
||||
x == CommaChar ||
|
||||
x == PeriodChar ||
|
||||
x == StringQuotedChar ||
|
||||
char.IsWhiteSpace(x) ||
|
||||
char.IsNumber(x));
|
||||
|
||||
private int ExtractFunctionOrMember(string input, int i) =>
|
||||
ExtractData(input, i, ResolveFunctionOrMemberType, x => x == OpenFuncChar ||
|
||||
x == CloseFuncChar ||
|
||||
x == CommaChar ||
|
||||
char.IsWhiteSpace(x));
|
||||
|
||||
private int ExtractNumber(string input, int i) =>
|
||||
ExtractData(input, i, x => TokenType.Number,
|
||||
x => !char.IsNumber(x) && x != PeriodChar && x != NegativeChar);
|
||||
|
||||
private int ExtractString(string input, int i)
|
||||
{
|
||||
var length = ExtractData(input, i, x => TokenType.String, x => x == StringQuotedChar, 1, 1);
|
||||
|
||||
// open string, report issue
|
||||
if (length == input.Length && input[length - 1] != StringQuotedChar)
|
||||
throw new FormatException($"Parser error (Position {i}): Expected '\"' but got '{input[length - 1]}'.");
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
private bool CompareOperators(string op1, string op2)
|
||||
=> CompareOperators(GetOperatorOrDefault(op1), GetOperatorOrDefault(op2));
|
||||
|
||||
private Operator GetOperatorOrDefault(string op)
|
||||
=> _operators.FirstOrDefault(x => x.Name == op) ?? new Operator { Name = op, Precedence = 0 };
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Represents an operator with precedence.
|
||||
/// Shunting the yard.
|
||||
/// </summary>
|
||||
public class Operator
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name.
|
||||
/// </value>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the precedence.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The precedence.
|
||||
/// </value>
|
||||
public int Precedence { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [right associative].
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [right associative]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool RightAssociative { get; set; }
|
||||
}
|
||||
|
||||
/// <param name="includeFunctionStopper">if set to <c>true</c> [include function stopper] (Token type <c>Wall</c>).</param>
|
||||
/// <returns>
|
||||
/// Enumerable of the token in in.
|
||||
/// </returns>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Wrong token
|
||||
/// or
|
||||
/// Mismatched parenthesis.
|
||||
/// </exception>
|
||||
public virtual IEnumerable<Token> ShuntingYard(Boolean includeFunctionStopper = true) {
|
||||
Stack<Token> stack = new Stack<Token>();
|
||||
|
||||
foreach(Token tok in this.Tokens) {
|
||||
switch(tok.Type) {
|
||||
case TokenType.Number:
|
||||
case TokenType.Variable:
|
||||
case TokenType.String:
|
||||
yield return tok;
|
||||
break;
|
||||
case TokenType.Function:
|
||||
stack.Push(tok);
|
||||
break;
|
||||
case TokenType.Operator:
|
||||
while(stack.Any() && stack.Peek().Type == TokenType.Operator &&
|
||||
this.CompareOperators(tok.Value, stack.Peek().Value)) {
|
||||
yield return stack.Pop();
|
||||
}
|
||||
|
||||
stack.Push(tok);
|
||||
break;
|
||||
case TokenType.Comma:
|
||||
while(stack.Any() && stack.Peek().Type != TokenType.Comma &&
|
||||
stack.Peek().Type != TokenType.Parenthesis) {
|
||||
yield return stack.Pop();
|
||||
}
|
||||
|
||||
break;
|
||||
case TokenType.Parenthesis:
|
||||
if(tok.Value == OpenFuncStr) {
|
||||
if(stack.Any() && stack.Peek().Type == TokenType.Function) {
|
||||
if(includeFunctionStopper) {
|
||||
yield return new Token(TokenType.Wall, tok.Value);
|
||||
}
|
||||
}
|
||||
|
||||
stack.Push(tok);
|
||||
} else {
|
||||
while(stack.Peek().Value != OpenFuncStr) {
|
||||
yield return stack.Pop();
|
||||
}
|
||||
|
||||
_ = stack.Pop();
|
||||
|
||||
if(stack.Any() && stack.Peek().Type == TokenType.Function) {
|
||||
yield return stack.Pop();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("Wrong token");
|
||||
}
|
||||
}
|
||||
|
||||
while(stack.Any()) {
|
||||
Token tok = stack.Pop();
|
||||
if(tok.Type == TokenType.Parenthesis) {
|
||||
throw new InvalidOperationException("Mismatched parenthesis");
|
||||
}
|
||||
|
||||
yield return tok;
|
||||
}
|
||||
}
|
||||
|
||||
private static Boolean CompareOperators(Operator op1, Operator op2) => op1.RightAssociative
|
||||
? op1.Precedence < op2.Precedence
|
||||
: op1.Precedence <= op2.Precedence;
|
||||
|
||||
private void Tokenize(String input) {
|
||||
if(!this.ValidateInput(input, out Int32 startIndex)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for(Int32 i = startIndex; i < input.Length; i++) {
|
||||
if(Char.IsWhiteSpace(input, i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(input[i] == CommaChar) {
|
||||
this.Tokens.Add(new Token(TokenType.Comma, new String(new[] { input[i] })));
|
||||
continue;
|
||||
}
|
||||
|
||||
if(input[i] == StringQuotedChar) {
|
||||
i = this.ExtractString(input, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(Char.IsLetter(input, i) || this.EvaluateFunctionOrMember(input, i)) {
|
||||
i = this.ExtractFunctionOrMember(input, i);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if(Char.IsNumber(input, i) ||
|
||||
input[i] == NegativeChar &&
|
||||
(this.Tokens.Any() && this.Tokens.Last().Type != TokenType.Number || !this.Tokens.Any())) {
|
||||
i = this.ExtractNumber(input, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(input[i] == OpenFuncChar ||
|
||||
input[i] == CloseFuncChar) {
|
||||
this.Tokens.Add(new Token(TokenType.Parenthesis, new String(new[] { input[i] })));
|
||||
continue;
|
||||
}
|
||||
|
||||
i = this.ExtractOperator(input, i);
|
||||
}
|
||||
}
|
||||
|
||||
private Int32 ExtractData(
|
||||
String input,
|
||||
Int32 i,
|
||||
Func<String, TokenType> tokenTypeEvaluation,
|
||||
Func<Char, Boolean> evaluation,
|
||||
Int32 right = 0,
|
||||
Int32 left = -1) {
|
||||
Int32 charCount = 0;
|
||||
for(Int32 j = i + right; j < input.Length; j++) {
|
||||
if(evaluation(input[j])) {
|
||||
break;
|
||||
}
|
||||
|
||||
charCount++;
|
||||
}
|
||||
|
||||
// Extract and set the value
|
||||
String value = input.SliceLength(i + right, charCount);
|
||||
this.Tokens.Add(new Token(tokenTypeEvaluation(value), value));
|
||||
|
||||
i += charCount + left;
|
||||
return i;
|
||||
}
|
||||
|
||||
private Int32 ExtractOperator(String input, Int32 i) =>
|
||||
this.ExtractData(input, i, x => TokenType.Operator, x => x == OpenFuncChar ||
|
||||
x == CommaChar ||
|
||||
x == PeriodChar ||
|
||||
x == StringQuotedChar ||
|
||||
Char.IsWhiteSpace(x) ||
|
||||
Char.IsNumber(x));
|
||||
|
||||
private Int32 ExtractFunctionOrMember(String input, Int32 i) =>
|
||||
this.ExtractData(input, i, this.ResolveFunctionOrMemberType, x => x == OpenFuncChar ||
|
||||
x == CloseFuncChar ||
|
||||
x == CommaChar ||
|
||||
Char.IsWhiteSpace(x));
|
||||
|
||||
private Int32 ExtractNumber(String input, Int32 i) =>
|
||||
this.ExtractData(input, i, x => TokenType.Number,
|
||||
x => !Char.IsNumber(x) && x != PeriodChar && x != NegativeChar);
|
||||
|
||||
private Int32 ExtractString(String input, Int32 i) {
|
||||
Int32 length = this.ExtractData(input, i, x => TokenType.String, x => x == StringQuotedChar, 1, 1);
|
||||
|
||||
// open string, report issue
|
||||
if(length == input.Length && input[length - 1] != StringQuotedChar) {
|
||||
throw new FormatException($"Parser error (Position {i}): Expected '\"' but got '{input[length - 1]}'.");
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
private Boolean CompareOperators(String op1, String op2)
|
||||
=> CompareOperators(this.GetOperatorOrDefault(op1), this.GetOperatorOrDefault(op2));
|
||||
|
||||
private Operator GetOperatorOrDefault(String op)
|
||||
=> this._operators.FirstOrDefault(x => x.Name == op) ?? new Operator { Name = op, Precedence = 0 };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an operator with precedence.
|
||||
/// </summary>
|
||||
public class Operator {
|
||||
/// <summary>
|
||||
/// Represents a Token structure.
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
public struct Token
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Token"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
public Token(TokenType type, string value)
|
||||
{
|
||||
Type = type;
|
||||
Value = type == TokenType.Function || type == TokenType.Operator ? value.ToLowerInvariant() : value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The type.
|
||||
/// </value>
|
||||
public TokenType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The value.
|
||||
/// </value>
|
||||
public string Value { get; }
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The name.
|
||||
/// </value>
|
||||
public String Name {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enums the token types.
|
||||
/// Gets or sets the precedence.
|
||||
/// </summary>
|
||||
public enum TokenType
|
||||
{
|
||||
/// <summary>
|
||||
/// The number
|
||||
/// </summary>
|
||||
Number,
|
||||
|
||||
/// <summary>
|
||||
/// The string
|
||||
/// </summary>
|
||||
String,
|
||||
|
||||
/// <summary>
|
||||
/// The variable
|
||||
/// </summary>
|
||||
Variable,
|
||||
|
||||
/// <summary>
|
||||
/// The function
|
||||
/// </summary>
|
||||
Function,
|
||||
|
||||
/// <summary>
|
||||
/// The parenthesis
|
||||
/// </summary>
|
||||
Parenthesis,
|
||||
|
||||
/// <summary>
|
||||
/// The operator
|
||||
/// </summary>
|
||||
Operator,
|
||||
|
||||
/// <summary>
|
||||
/// The comma
|
||||
/// </summary>
|
||||
Comma,
|
||||
|
||||
/// <summary>
|
||||
/// The wall, used to specified the end of argument list of the following function
|
||||
/// </summary>
|
||||
Wall,
|
||||
}
|
||||
/// <value>
|
||||
/// The precedence.
|
||||
/// </value>
|
||||
public Int32 Precedence {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [right associative].
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [right associative]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public Boolean RightAssociative {
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Token structure.
|
||||
/// </summary>
|
||||
public struct Token {
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Token"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
public Token(TokenType type, String value) {
|
||||
this.Type = type;
|
||||
this.Value = type == TokenType.Function || type == TokenType.Operator ? value.ToLowerInvariant() : value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The type.
|
||||
/// </value>
|
||||
public TokenType Type {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The value.
|
||||
/// </value>
|
||||
public String Value {
|
||||
get;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enums the token types.
|
||||
/// </summary>
|
||||
public enum TokenType {
|
||||
/// <summary>
|
||||
/// The number
|
||||
/// </summary>
|
||||
Number,
|
||||
|
||||
/// <summary>
|
||||
/// The string
|
||||
/// </summary>
|
||||
String,
|
||||
|
||||
/// <summary>
|
||||
/// The variable
|
||||
/// </summary>
|
||||
Variable,
|
||||
|
||||
/// <summary>
|
||||
/// The function
|
||||
/// </summary>
|
||||
Function,
|
||||
|
||||
/// <summary>
|
||||
/// The parenthesis
|
||||
/// </summary>
|
||||
Parenthesis,
|
||||
|
||||
/// <summary>
|
||||
/// The operator
|
||||
/// </summary>
|
||||
Operator,
|
||||
|
||||
/// <summary>
|
||||
/// The comma
|
||||
/// </summary>
|
||||
Comma,
|
||||
|
||||
/// <summary>
|
||||
/// The wall, used to specified the end of argument list of the following function
|
||||
/// </summary>
|
||||
Wall,
|
||||
}
|
||||
}
|
||||
@@ -1,127 +1,123 @@
|
||||
namespace Unosquare.Swan.Lite.Abstractions
|
||||
{
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Unosquare.Swan.Lite.Abstractions {
|
||||
/// <summary>
|
||||
/// A base class for implementing models that fire notifications when their properties change.
|
||||
/// This class is ideal for implementing MVVM driven UIs.
|
||||
/// </summary>
|
||||
/// <seealso cref="INotifyPropertyChanged" />
|
||||
public abstract class ViewModelBase : INotifyPropertyChanged {
|
||||
private readonly ConcurrentDictionary<String, Boolean> QueuedNotifications = new ConcurrentDictionary<String, Boolean>();
|
||||
private readonly Boolean UseDeferredNotifications;
|
||||
|
||||
/// <summary>
|
||||
/// A base class for implementing models that fire notifications when their properties change.
|
||||
/// This class is ideal for implementing MVVM driven UIs.
|
||||
/// Initializes a new instance of the <see cref="ViewModelBase"/> class.
|
||||
/// </summary>
|
||||
/// <seealso cref="INotifyPropertyChanged" />
|
||||
public abstract class ViewModelBase : INotifyPropertyChanged
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, bool> QueuedNotifications = new ConcurrentDictionary<string, bool>();
|
||||
private readonly bool UseDeferredNotifications;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ViewModelBase"/> class.
|
||||
/// </summary>
|
||||
/// <param name="useDeferredNotifications">Set to <c>true</c> to use deferred notifications in the background.</param>
|
||||
protected ViewModelBase(bool useDeferredNotifications)
|
||||
{
|
||||
UseDeferredNotifications = useDeferredNotifications;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ViewModelBase"/> class.
|
||||
/// </summary>
|
||||
protected ViewModelBase()
|
||||
: this(false)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a property value changes.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary>Checks if a property already matches a desired value. Sets the property and
|
||||
/// notifies listeners only when necessary.</summary>
|
||||
/// <typeparam name="T">Type of the property.</typeparam>
|
||||
/// <param name="storage">Reference to a property with both getter and setter.</param>
|
||||
/// <param name="value">Desired value for the property.</param>
|
||||
/// <param name="propertyName">Name of the property used to notify listeners. This
|
||||
/// value is optional and can be provided automatically when invoked from compilers that
|
||||
/// support CallerMemberName.</param>
|
||||
/// <param name="notifyAlso">An rray of property names to notify in addition to notifying the changes on the current property name.</param>
|
||||
/// <returns>True if the value was changed, false if the existing value matched the
|
||||
/// desired value.</returns>
|
||||
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = "", string[] notifyAlso = null)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(storage, value))
|
||||
return false;
|
||||
|
||||
storage = value;
|
||||
NotifyPropertyChanged(propertyName, notifyAlso);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies one or more properties changed.
|
||||
/// </summary>
|
||||
/// <param name="propertyNames">The property names.</param>
|
||||
protected void NotifyPropertyChanged(params string[] propertyNames) => NotifyPropertyChanged(null, propertyNames);
|
||||
|
||||
/// <summary>
|
||||
/// Notifies one or more properties changed.
|
||||
/// </summary>
|
||||
/// <param name="mainProperty">The main property.</param>
|
||||
/// <param name="auxiliaryProperties">The auxiliary properties.</param>
|
||||
private void NotifyPropertyChanged(string mainProperty, string[] auxiliaryProperties)
|
||||
{
|
||||
// Queue property notification
|
||||
if (string.IsNullOrWhiteSpace(mainProperty) == false)
|
||||
QueuedNotifications[mainProperty] = true;
|
||||
|
||||
// Set the state for notification properties
|
||||
if (auxiliaryProperties != null)
|
||||
{
|
||||
foreach (var property in auxiliaryProperties)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(property) == false)
|
||||
QueuedNotifications[property] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Depending on operation mode, either fire the notifications in the background
|
||||
// or fire them immediately
|
||||
if (UseDeferredNotifications)
|
||||
Task.Run(() => NotifyQueuedProperties());
|
||||
else
|
||||
NotifyQueuedProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies the queued properties and resets the property name to a non-queued stated.
|
||||
/// </summary>
|
||||
private void NotifyQueuedProperties()
|
||||
{
|
||||
// get a snapshot of property names.
|
||||
var propertyNames = QueuedNotifications.Keys.ToArray();
|
||||
|
||||
// Iterate through the properties
|
||||
foreach (var property in propertyNames)
|
||||
{
|
||||
// don't notify if we don't have a change
|
||||
if (!QueuedNotifications[property]) continue;
|
||||
|
||||
// notify and reset queued state to false
|
||||
try { OnPropertyChanged(property); }
|
||||
finally { QueuedNotifications[property] = false; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a property changes its backing value.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Name of the property.</param>
|
||||
private void OnPropertyChanged(string propertyName) =>
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName ?? string.Empty));
|
||||
}
|
||||
/// <param name="useDeferredNotifications">Set to <c>true</c> to use deferred notifications in the background.</param>
|
||||
protected ViewModelBase(Boolean useDeferredNotifications) => this.UseDeferredNotifications = useDeferredNotifications;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ViewModelBase"/> class.
|
||||
/// </summary>
|
||||
protected ViewModelBase()
|
||||
: this(false) {
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a property value changes.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary>Checks if a property already matches a desired value. Sets the property and
|
||||
/// notifies listeners only when necessary.</summary>
|
||||
/// <typeparam name="T">Type of the property.</typeparam>
|
||||
/// <param name="storage">Reference to a property with both getter and setter.</param>
|
||||
/// <param name="value">Desired value for the property.</param>
|
||||
/// <param name="propertyName">Name of the property used to notify listeners. This
|
||||
/// value is optional and can be provided automatically when invoked from compilers that
|
||||
/// support CallerMemberName.</param>
|
||||
/// <param name="notifyAlso">An rray of property names to notify in addition to notifying the changes on the current property name.</param>
|
||||
/// <returns>True if the value was changed, false if the existing value matched the
|
||||
/// desired value.</returns>
|
||||
protected Boolean SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = "", String[] notifyAlso = null) {
|
||||
if(EqualityComparer<T>.Default.Equals(storage, value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
storage = value;
|
||||
this.NotifyPropertyChanged(propertyName, notifyAlso);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies one or more properties changed.
|
||||
/// </summary>
|
||||
/// <param name="propertyNames">The property names.</param>
|
||||
protected void NotifyPropertyChanged(params String[] propertyNames) => this.NotifyPropertyChanged(null, propertyNames);
|
||||
|
||||
/// <summary>
|
||||
/// Notifies one or more properties changed.
|
||||
/// </summary>
|
||||
/// <param name="mainProperty">The main property.</param>
|
||||
/// <param name="auxiliaryProperties">The auxiliary properties.</param>
|
||||
private void NotifyPropertyChanged(String mainProperty, String[] auxiliaryProperties) {
|
||||
// Queue property notification
|
||||
if(String.IsNullOrWhiteSpace(mainProperty) == false) {
|
||||
this.QueuedNotifications[mainProperty] = true;
|
||||
}
|
||||
|
||||
// Set the state for notification properties
|
||||
if(auxiliaryProperties != null) {
|
||||
foreach(String property in auxiliaryProperties) {
|
||||
if(String.IsNullOrWhiteSpace(property) == false) {
|
||||
this.QueuedNotifications[property] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Depending on operation mode, either fire the notifications in the background
|
||||
// or fire them immediately
|
||||
if(this.UseDeferredNotifications) {
|
||||
_ = Task.Run(() => this.NotifyQueuedProperties());
|
||||
} else {
|
||||
this.NotifyQueuedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies the queued properties and resets the property name to a non-queued stated.
|
||||
/// </summary>
|
||||
private void NotifyQueuedProperties() {
|
||||
// get a snapshot of property names.
|
||||
String[] propertyNames = this.QueuedNotifications.Keys.ToArray();
|
||||
|
||||
// Iterate through the properties
|
||||
foreach(String property in propertyNames) {
|
||||
// don't notify if we don't have a change
|
||||
if(!this.QueuedNotifications[property]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// notify and reset queued state to false
|
||||
try {
|
||||
this.OnPropertyChanged(property);
|
||||
} finally { this.QueuedNotifications[property] = false; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a property changes its backing value.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Name of the property.</param>
|
||||
private void OnPropertyChanged(String propertyName) =>
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName ?? String.Empty));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user