< Summary

Information
Class: MyNet.Observable.BoundedValue<T>
Assembly: MyNet.Observable
File(s): https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Observable/Base/BoundedValue.cs
Tag: 323_28699572109
Line coverage
56%
Covered lines: 23
Uncovered lines: 18
Coverable lines: 41
Total lines: 146
Line coverage: 56%
Branch coverage
25%
Covered branches: 7
Total branches: 28
Branch coverage: 25%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
.ctor(...)100%11100%
.ctor(...)100%11100%
set_Value(...)75%44100%
get_Min()100%11100%
set_Min(...)0%2040%
get_Max()100%11100%
set_Max(...)0%2040%
CanReset()0%620%
Reset()100%11100%
ToString()0%620%
BuildRangeValidationMessage(...)25%88100%
GetBound(...)50%44100%
op_Implicit(...)100%210%
ToNullable()100%210%

File(s)

https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Observable/Base/BoundedValue.cs

#LineLine coverage
 1// -----------------------------------------------------------------------
 2// <copyright file="BoundedValue.cs" company="Stéphane ANDRE">
 3// Copyright (c) Stéphane ANDRE. All rights reserved.
 4// </copyright>
 5// -----------------------------------------------------------------------
 6
 7using System.Globalization;
 8using System.Numerics;
 9using MyNet.Globalization.Facade;
 10using MyNet.Observable.Behaviors;
 11using MyNet.Observable.Localization;
 12using MyNet.Observable.Validation.Validators;
 13using MyNet.Primitives;
 14using MyNet.Primitives.Intervals;
 15
 16#pragma warning disable IDE0130 // Namespace does not match folder structure
 17namespace MyNet.Observable;
 18#pragma warning restore IDE0130 // Namespace does not match folder structure
 19
 20/// <summary>
 21/// Observable numeric value with optional min/max bounds, reset support, and localized validation.
 22/// </summary>
 23/// <typeparam name="T">The numeric type.</typeparam>
 24public class BoundedValue<T> : ObservableObject, IBoundedValue<T>
 25    where T : struct, INumber<T>
 26{
 327    private static readonly Interval<T> UnboundedInterval = new(start: null, to: null);
 28
 29    /// <summary>
 30    /// Initializes a new instance of the <see cref="BoundedValue{T}"/> class without bounds.
 31    /// </summary>
 32    /// <param name="propertyDisplayKey">Translation key used as the field name in validation messages.</param>
 33    /// <param name="defaultValue">Optional default value used by <see cref="Reset"/>.</param>
 34    public BoundedValue(string propertyDisplayKey = nameof(Value), T? defaultValue = null)
 1235        : this(UnboundedInterval, propertyDisplayKey, defaultValue) { }
 36
 37    /// <summary>
 38    /// Initializes a new instance of the <see cref="BoundedValue{T}"/> class with inclusive bounds.
 39    /// </summary>
 40    /// <param name="min">Inclusive minimum, if any.</param>
 41    /// <param name="max">Inclusive maximum, if any.</param>
 42    /// <param name="propertyDisplayKey">Translation key used as the field name in validation messages.</param>
 43    /// <param name="defaultValue">Optional default value used by <see cref="Reset"/>.</param>
 44    public BoundedValue(T? min, T? max, string propertyDisplayKey = nameof(Value), T? defaultValue = null)
 3045        : this(new(min, max), propertyDisplayKey, defaultValue) { }
 46
 47    /// <summary>
 48    /// Initializes a new instance of the <see cref="BoundedValue{T}"/> class.
 49    /// </summary>
 50    /// <param name="range">Inclusive bounds.</param>
 51    /// <param name="propertyDisplayKey">Translation key used as the field name in validation messages.</param>
 52    /// <param name="defaultValue">Optional default value used by <see cref="Reset"/>.</param>
 2453    public BoundedValue(Interval<T> range, string propertyDisplayKey = nameof(Value), T? defaultValue = null)
 54    {
 2455        Range = range;
 2456        DefaultValue = defaultValue;
 2457        this.UseValidation(new BoundedValueValidator<T>(propertyDisplayKey));
 2458    }
 59
 60    /// <summary>
 61    /// Gets the configured inclusive bounds.
 62    /// </summary>
 63    public Interval<T> Range { get; private set; }
 64
 65    /// <inheritdoc />
 66    public T? Value
 67    {
 68        get;
 69        set
 70        {
 2771            if (SetProperty(ref field, value))
 2472                Behaviors.GetOrDefault<ValidationBehavior<BoundedValue<T>>>()?.ValidateProperty(nameof(Value));
 2773        }
 74    }
 75
 76    /// <inheritdoc />
 77    public T? Min
 78    {
 1879        get => GetBound(Range.Start);
 80        set
 81        {
 082            if (Equals(value, Min))
 083                return;
 84
 085            var before = Min;
 086            Range = new(value, Max);
 087            NotifyPropertyChanged(nameof(Min), before, value);
 088            Behaviors.GetOrDefault<ValidationBehavior<BoundedValue<T>>>()?.ValidateProperty(nameof(Value));
 089        }
 90    }
 91
 92    /// <inheritdoc />
 93    public T? Max
 94    {
 1895        get => GetBound(Range.End);
 96        set
 97        {
 098            if (Equals(value, Max))
 099                return;
 100
 0101            var before = Max;
 0102            Range = new(Min, value);
 0103            NotifyPropertyChanged(nameof(Max), before, value);
 0104            Behaviors.GetOrDefault<ValidationBehavior<BoundedValue<T>>>()?.ValidateProperty(nameof(Value));
 0105        }
 106    }
 107
 108    /// <inheritdoc />
 109    public T? DefaultValue { get; }
 110
 111    /// <inheritdoc />
 0112    public bool CanReset() => DefaultValue.HasValue && !Equals(Value, DefaultValue);
 113
 114    /// <inheritdoc />
 3115    public void Reset() => Value = DefaultValue;
 116
 117    /// <inheritdoc />
 0118    public override string? ToString() => Value?.ToString();
 119
 120    internal string BuildRangeValidationMessage(string propertyDisplayKey)
 121    {
 18122        var label = propertyDisplayKey.Translate();
 123
 18124        return Min is { } min && Max is { } max
 18125            ? ValidationResources.FieldMustBeBetween.FormatWith(CultureInfo.CurrentCulture, label, min, max)
 18126            : Min is { } minOnly
 18127            ? ValidationResources.FieldMustBeGreaterThanOrEqualTo.FormatWith(CultureInfo.CurrentCulture, label, minOnly)
 18128            : Max is { } maxOnly
 18129            ? ValidationResources.FieldMustBeLessThanOrEqualTo.FormatWith(CultureInfo.CurrentCulture, label, maxOnly)
 18130            : string.Empty;
 131    }
 132
 133    private static T? GetBound(IntervalBoundary<T>? boundary) =>
 36134        boundary is { IsInclusive: true } ? boundary.Value.Value : null;
 135
 136    /// <summary>
 137    /// Implicit conversion to the current value.
 138    /// </summary>
 0139    public static implicit operator T?(BoundedValue<T> value) => value.Value;
 140
 141    /// <summary>
 142    /// Returns the current value as a nullable type, which is useful when the value may be null and you want to avoid n
 143    /// </summary>
 0144    public T? ToNullable() => Value;
 145}
 146