diff --git a/OnixLabs.Core.UnitTests.Data/Mutable.cs b/OnixLabs.Core.UnitTests.Data/Mutable.cs new file mode 100644 index 0000000..93125bb --- /dev/null +++ b/OnixLabs.Core.UnitTests.Data/Mutable.cs @@ -0,0 +1,20 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace OnixLabs.Core.UnitTests.Data; + +public sealed class Mutable +{ + public required int Value { get; set; } +} diff --git a/OnixLabs.Core.UnitTests/ObjectExtensionTests.cs b/OnixLabs.Core.UnitTests/ObjectExtensionTests.cs index a00b188..f484d9f 100644 --- a/OnixLabs.Core.UnitTests/ObjectExtensionTests.cs +++ b/OnixLabs.Core.UnitTests/ObjectExtensionTests.cs @@ -20,34 +20,31 @@ namespace OnixLabs.Core.UnitTests; public sealed class ObjectExtensionTests { - [Theory(DisplayName = "IsWithinRangeInclusive should produce the expected result")] - [InlineData(2, 1, 3, true)] - [InlineData(1, 1, 3, true)] - [InlineData(3, 1, 3, true)] - [InlineData(0, 1, 3, false)] - [InlineData(4, 1, 3, false)] - public void IsWithinRangeInclusiveShouldProduceExpectedResult(int value, int min, int max, bool expected) + [Fact(DisplayName = "Apply should produce the expected result (reference type)")] + public void ApplyShouldProduceExpectedResultReferenceType() { + // Given + Mutable value = new() { Value = 123 }; + // When - bool actual = value.IsWithinRangeInclusive(min, max); + Mutable result = value.Apply(it => it.Value = 456); // Then - Assert.Equal(expected, actual); + Assert.Equal(456, value.Value); + Assert.Same(value, result); } - [Theory(DisplayName = "IsWithinRangeExclusive should produce the expected result")] - [InlineData(2, 1, 3, true)] - [InlineData(1, 1, 3, false)] - [InlineData(3, 1, 3, false)] - [InlineData(0, 1, 3, false)] - [InlineData(4, 1, 3, false)] - public void IsWithinRangeExclusiveShouldProduceExpectedResult(int value, int min, int max, bool expected) + [Fact(DisplayName = "Apply should produce the expected result (value type)")] + public void ApplyShouldProduceExpectedResultValueType() { + // Given + int value = 123; + // When - bool actual = value.IsWithinRangeExclusive(min, max); + value = value.Apply(it => it * 2); // Then - Assert.Equal(expected, actual); + Assert.Equal(246, value); } [Fact(DisplayName = "CompareToObject should produce zero if the current IComparable is equal to the specified object.")] @@ -112,6 +109,51 @@ public void CompareToObjectShouldThrowArgumentExceptionIfSpecifiedObjectIsOfInco Assert.Equal("Object must be of type System.Int32 (Parameter 'right')", exception.Message); } + [Theory(DisplayName = "IsWithinRangeInclusive should produce the expected result")] + [InlineData(2, 1, 3, true)] + [InlineData(1, 1, 3, true)] + [InlineData(3, 1, 3, true)] + [InlineData(0, 1, 3, false)] + [InlineData(4, 1, 3, false)] + public void IsWithinRangeInclusiveShouldProduceExpectedResult(int value, int min, int max, bool expected) + { + // When + bool actual = value.IsWithinRangeInclusive(min, max); + + // Then + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "IsWithinRangeExclusive should produce the expected result")] + [InlineData(2, 1, 3, true)] + [InlineData(1, 1, 3, false)] + [InlineData(3, 1, 3, false)] + [InlineData(0, 1, 3, false)] + [InlineData(4, 1, 3, false)] + public void IsWithinRangeExclusiveShouldProduceExpectedResult(int value, int min, int max, bool expected) + { + // When + bool actual = value.IsWithinRangeExclusive(min, max); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Let should produce the expected result")] + public void LetShouldProduceExpectedResult() + { + // Given + const string value = "123"; + + // When + int result = value + .Let(int.Parse) + .Let(it => it * 2); + + // Then + Assert.Equal(246, result); + } + [Fact(DisplayName = "ToRecordString should produce null when the object is null")] public void ToRecordStringShouldProduceNullWhenObjectIsNull() { diff --git a/OnixLabs.Core/Extensions.Object.cs b/OnixLabs.Core/Extensions.Object.cs index 4b33e1b..bd8e297 100644 --- a/OnixLabs.Core/Extensions.Object.cs +++ b/OnixLabs.Core/Extensions.Object.cs @@ -41,32 +41,31 @@ public static class ObjectExtensions private const string ObjectPropertyAssignment = " = "; /// - /// Determines whether the current value falls within range, inclusive of the specified minimum and maximum values. + /// Calls the specified with the current . /// - /// The value to test. - /// The inclusive minimum value. - /// The inclusive maximum value. - /// The underlying type. - /// - /// Returns if the current value falls within range, - /// inclusive of the specified minimum and maximum values; otherwise, . - /// - public static bool IsWithinRangeInclusive(this T value, T min, T max) where T : IComparable => - value.CompareTo(min) is 0 or 1 && value.CompareTo(max) is 0 or -1; + /// The value to pass to the specified . + /// The action into which the current will be passed. + /// The underlying type of the current value. + /// Returns the current once the specified has been executed. + public static T Apply(this T value, Action action) where T : class + { + RequireNotNull(action, "Action must not be null.", nameof(action)); + action(value); + return value; + } /// - /// Determines whether the current value falls within range, exclusive of the specified minimum and maximum values. + /// Calls the specified with the current . /// - /// The value to test. - /// The exclusive minimum value. - /// The exclusive maximum value. - /// The underlying type. - /// - /// Returns if the current value falls within range, - /// exclusive of the specified minimum and maximum values; otherwise, . - /// - public static bool IsWithinRangeExclusive(this T value, T min, T max) where T : IComparable => - value.CompareTo(min) is 1 && value.CompareTo(max) is -1; + /// The value to pass to the specified . + /// The function into which the current will be passed. + /// The underlying type of the current value. + /// Returns the result of the specified . + public static T Apply(this T value, Func function) where T : struct + { + RequireNotNull(function, "Function must not be null.", nameof(function)); + return function(value); + } /// /// Compares the current instance with the specified instance. @@ -108,6 +107,48 @@ public static int CompareToNullable(this T left, T? right) where T : struct, _ => throw new ArgumentException($"Object must be of type {typeof(T).FullName}", nameof(right)) }; + /// + /// Determines whether the current value falls within range, inclusive of the specified minimum and maximum values. + /// + /// The value to test. + /// The inclusive minimum value. + /// The inclusive maximum value. + /// The underlying type. + /// + /// Returns if the current value falls within range, + /// inclusive of the specified minimum and maximum values; otherwise, . + /// + public static bool IsWithinRangeInclusive(this T value, T min, T max) where T : IComparable => + value.CompareTo(min) is 0 or 1 && value.CompareTo(max) is 0 or -1; + + /// + /// Determines whether the current value falls within range, exclusive of the specified minimum and maximum values. + /// + /// The value to test. + /// The exclusive minimum value. + /// The exclusive maximum value. + /// The underlying type. + /// + /// Returns if the current value falls within range, + /// exclusive of the specified minimum and maximum values; otherwise, . + /// + public static bool IsWithinRangeExclusive(this T value, T min, T max) where T : IComparable => + value.CompareTo(min) is 1 && value.CompareTo(max) is -1; + + /// + /// Calls the specified with the current as its argument and returns the result. + /// + /// The value to pass to the specified . + /// The function into which the current will be passed. + /// The underlying type of the current value. + /// The underlying type of the result value. + /// Returns the result of the specified . + public static TResult Let(this TSource value, Func function) + { + RequireNotNull(function, "Function must not be null.", nameof(function)); + return function(value); + } + /// /// Gets a record-like representation of the current instance. /// This method is designed specifically for record-like objects and may produce undesirable results when applied to primitive-like objects.