diff --git a/Maybe.Test/MaybeExtensionsTests.cs b/Maybe.Test/MaybeExtensionsTests.cs index aa0a2c6..27fabac 100644 --- a/Maybe.Test/MaybeExtensionsTests.cs +++ b/Maybe.Test/MaybeExtensionsTests.cs @@ -1,5 +1,6 @@ -using FluentAssertions; using System; +using System.Threading.Tasks; +using FluentAssertions; using Xunit; namespace ZBRA.Maybe.Test @@ -106,6 +107,15 @@ public void Select_WithNullableProperty_ReturnsSelectedProperty(Maybe subject, Maybe expected) + { + var result = await subject.SelectAsync(async o => await Task.FromResult(o.Count)); + + result.Should().Be(expected); + } + public static TheoryData, Maybe> Select_WithNullablePropertyTestCases() { return new TheoryData, Maybe> @@ -141,6 +151,15 @@ public void SelectMany_WithMaybeProperty_ReturnsSelectedProperty(Maybe subject, Maybe expected) + { + var result = await subject.SelectManyAsync(async o => await Task.FromResult(o.Count)); + + result.Should().Be(expected); + } + public static TheoryData, Maybe> SelectMany_WithMaybePropertyTestCases() { return new TheoryData, Maybe> @@ -158,5 +177,137 @@ public void SelectMany_NullArgument_ShouldThrow() subject.Should().ThrowExactly(); } + + [Fact] + public void Consume_WithValue_ShouldExecuteAction() + { + string result = null; + var expected = 1; + + expected.ToMaybe() + .Consume(i => result = i.ToString()); + + result.Should().Be(expected.ToString()); + } + + [Fact] + public async Task ConsumeAsync_WithValue_ShouldExecuteAction() + { + string result = null; + const int expected = 1; + + await expected.ToMaybe() + .ConsumeAsync(async i => + { + result = i.ToString(); + await Task.CompletedTask; + }); + + result.Should().Be(expected.ToString()); + } + + [Fact] + public void Consume_WithNoValue_ShouldNotExecuteAction() + { + var result = "a"; + + Maybe.Nothing + .Consume(i => result = "b"); + + result.Should().Be("a"); + } + + [Fact] + public async Task ConsumeAsync_WithNoValue_ShouldNotExecuteAction() + { + var result = "a"; + + await Maybe.Nothing + .ConsumeAsync(async _ => + { + result = "b"; + await Task.CompletedTask; + }); + + result.Should().Be("a"); + } + + [Fact] + public void Consume_NullArgument_ShouldThrow() + { + Action subject = () => 1.ToMaybe().Consume((Action)null); + + subject.Should().ThrowExactly(); + } + + [Theory] + [InlineData(null, null, null)] + [InlineData(null, 2, null)] + [InlineData(2, null, null)] + [InlineData(1, 2, "3")] + public void Zip_WithTransformer_ShouldZipValues(int? value, double? otherValue, string expected) + { + static string transformer(int v, double o) => (v + o).ToString(); + + var result = value.ToMaybe() + .Zip(otherValue.ToMaybe(), transformer); + + result.Should().Be(expected.ToMaybe()); + } + + [Theory] + [InlineData(null, null, null)] + [InlineData(null, 2, null)] + [InlineData(2, null, null)] + [InlineData(1, 2, "3")] + public void Zip_WithMaybeTransformer_ShouldZipValues(int? value, double? otherValue, string expected) + { + static Maybe transformer(int v, double o) => (v + o).ToString().ToMaybe(); + + var result = value.ToMaybe() + .Zip(otherValue.ToMaybe(), transformer); + + result.Should().Be(expected.ToMaybe()); + } + + [Fact] + public void Zip_NullArgument_ShouldThrow() + { + Action subject = () => 1.ToMaybe().Zip(Maybe.Nothing, (Func)null); + + subject.Should().ThrowExactly(); + } + + [Fact] + public void Zip_MaybeNullArgument_ShouldThrow() + { + Action subject = () => 1.ToMaybe().Zip(Maybe.Nothing, (Func>)null); + + subject.Should().ThrowExactly(); + } + + [Theory] + [InlineData(null, null, null)] + [InlineData(null, 2, null)] + [InlineData(2, null, null)] + [InlineData(1, 2, "3")] + public void ZipConsume_WhenBothValuesExist_ShouldExecuteAction(int? value, double? otherValue, string expected) + { + string result = null; + void action(int v, double o) => result = (v + o).ToString(); + + value.ToMaybe() + .ZipAndConsume(otherValue.ToMaybe(), action); + + result.Should().Be(expected); + } + + [Fact] + public void ZipAndConsume_NullArgument_ShouldThrow() + { + Action subject = () => 1.ToMaybe().ZipAndConsume(Maybe.Nothing, null); + + subject.Should().ThrowExactly(); + } } } diff --git a/Maybe.Test/MaybeTests.cs b/Maybe.Test/MaybeTests.cs index b38cff3..07f9901 100644 --- a/Maybe.Test/MaybeTests.cs +++ b/Maybe.Test/MaybeTests.cs @@ -1,5 +1,6 @@ -using FluentAssertions; using System; +using System.Threading.Tasks; +using FluentAssertions; using Xunit; namespace ZBRA.Maybe.Test @@ -73,107 +74,6 @@ public void OrThrow_NullArgument_ShouldThrow() subject.Should().ThrowExactly(); } - [Fact] - public void Consume_WithValue_ShouldExecuteAction() - { - string result = null; - var expected = 1; - - expected.ToMaybe() - .Consume(i => result = i.ToString()); - - result.Should().Be(expected.ToString()); - } - - [Fact] - public void Consume_WithNoValue_ShouldNotExecuteAction() - { - var result = "a"; - - Maybe.Nothing - .Consume(i => result = "b"); - - result.Should().Be("a"); - } - - [Fact] - public void Consume_NullArgument_ShouldThrow() - { - Action subject = () => 1.ToMaybe().Consume(null); - - subject.Should().ThrowExactly(); - } - - [Theory] - [InlineData(null, null, null)] - [InlineData(null, 2, null)] - [InlineData(2, null, null)] - [InlineData(1, 2, "3")] - public void Zip_WithTransformer_ShouldZipValues(int? value, double? otherValue, string expected) - { - static string transformer(int v, double o) => (v + o).ToString(); - - var result = value.ToMaybe() - .Zip(otherValue.ToMaybe(), transformer); - - result.Should().Be(expected.ToMaybe()); - } - - [Theory] - [InlineData(null, null, null)] - [InlineData(null, 2, null)] - [InlineData(2, null, null)] - [InlineData(1, 2, "3")] - public void Zip_WithMaybeTransformer_ShouldZipValues(int? value, double? otherValue, string expected) - { - static Maybe transformer(int v, double o) => (v + o).ToString().ToMaybe(); - - var result = value.ToMaybe() - .Zip(otherValue.ToMaybe(), transformer); - - result.Should().Be(expected.ToMaybe()); - } - - [Fact] - public void Zip_NullArgument_ShouldThrow() - { - Action subject = () => 1.ToMaybe().Zip(Maybe.Nothing, (Func)null); - - subject.Should().ThrowExactly(); - } - - [Fact] - public void Zip_MaybeNullArgument_ShouldThrow() - { - Action subject = () => 1.ToMaybe().Zip(Maybe.Nothing, (Func>)null); - - subject.Should().ThrowExactly(); - } - - [Theory] - [InlineData(null, null, null)] - [InlineData(null, 2, null)] - [InlineData(2, null, null)] - [InlineData(1, 2, "3")] - public void ZipConsume_WhenBothValuesExist_ShouldExecuteAction(int? value, double? otherValue, string expected) - { - string result = null; - void action(int v, double o) => result = (v + o).ToString(); - - value.ToMaybe() - .ZipAndConsume(otherValue.ToMaybe(), action); - - result.Should().Be(expected); - } - - [Fact] - public void ZipAndConsume_NullArgument_ShouldThrow() - { - Action subject = () => 1.ToMaybe().ZipAndConsume(Maybe.Nothing, null); - - subject.Should().ThrowExactly(); - } - [Fact] public void Equals_WhenObjectReferencesAreEqual() { diff --git a/Maybe/Maybe.cs b/Maybe/Maybe.cs index c8774c0..8f29a00 100644 --- a/Maybe/Maybe.cs +++ b/Maybe/Maybe.cs @@ -8,7 +8,7 @@ namespace ZBRA.Maybe /// public readonly struct Maybe : IEquatable>, IComparable> { - /// A Maybe without a value. + /// A Maybe without a value. public static readonly Maybe Nothing = new Maybe(default, false); private readonly T obj; @@ -25,10 +25,10 @@ internal Maybe(T obj) HasValue = true; } - /// True if there is a value present, otherwise false. + /// True if there is a value present, otherwise false. public bool HasValue { get; } - /// The encapsulated value. + /// The encapsulated value. /// Thrown if HasValue is false. public T Value { @@ -47,7 +47,7 @@ public T Value /// Returns the value or a default. /// /// - /// The value if HasValue is true, otherwise returns defaultValue + /// The value if HasValue is true, otherwise returns defaultValue /// /// The default value. public T Or(T defaultValue) => HasValue ? obj : defaultValue; @@ -137,20 +137,6 @@ public T OrThrow(Func errorSupplier) throw errorSupplier(); } - /// - /// Applies an action to the value if it's present. - /// - /// The action to be applied to the value. - public void Consume(Action consumer) - { - consumer = consumer ?? throw new ArgumentNullException(nameof(consumer)); - - if (HasValue) - { - consumer(obj); - } - } - /// /// Determines if the encapsulated value matches another value using Equals. /// @@ -216,7 +202,7 @@ public bool Equals(Maybe other) /// Determines if this instance is equals to another obj instance. /// /// - /// True if obj is an instance of Maybe<> + /// True if obj is an instance of Maybe<> /// and it matches the current instance using Equals(Maybe<T> other). /// False otherwise /// @@ -240,10 +226,10 @@ public bool Equals(Maybe other) public override string ToString() => HasValue ? Value.ToString() : string.Empty; /// - /// Comparison method responsible for ordering or sorting collections of .
- /// A return of 0 means that both maybes are equal.
- /// A return of -1 means that this instance of maybe is less than the other maybe being compared.
- /// A return of 1 means that this instance of maybe is greater than the other maybe being compared.
+ /// Comparison method responsible for ordering or sorting collections of .
+ /// A return of 0 means that both maybes are equal.
+ /// A return of -1 means that this instance of maybe is less than the other maybe being compared.
+ /// A return of 1 means that this instance of maybe is greater than the other maybe being compared.
///
/// The other to compare /// diff --git a/Maybe/MaybeExtensions.cs b/Maybe/MaybeExtensions.cs index b6964c8..bc4c17d 100644 --- a/Maybe/MaybeExtensions.cs +++ b/Maybe/MaybeExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; namespace ZBRA.Maybe { @@ -117,7 +118,22 @@ public static Maybe ToMaybe(this T value) #endregion - #region Operations + #region Operations + /// + /// Applies an action to the value if it's present. + /// + /// The subject that will be projected. + /// The action to be applied to the value. + public static void Consume(this Maybe subject, Action consumer) + { + consumer = consumer ?? throw new ArgumentNullException(nameof(consumer)); + + if (subject.HasValue) + { + consumer(subject.Value); + } + } + /// /// Projects the value according to the selector. /// Analogous to Linq's Select. @@ -228,5 +244,74 @@ public static void ZipAndConsume(this Maybe subject, Maybe other, Ac } } #endregion + + #region Async Operations + /// + /// Projects the value according to the selector. + /// Analogous to Linq's Select. + /// + /// + /// A Task that returns Maybe<>.Nothing if subject.HasValue is false, + /// otherwise returns an instance of Maybe<> with the projected value + /// + /// The subject that will be projected. + /// The selector to be applied. + public static async Task> SelectAsync(this Maybe subject, Func> selector) + { + selector = selector ?? throw new ArgumentNullException(nameof(selector)); + + return !subject.HasValue ? Maybe.Nothing : (await selector(subject.Value)).ToMaybe(); + } + + /// + /// Projects the value according to the selector. + /// Analogous to Linq's Select. + /// + /// + /// Maybe<>.Nothing if subject.HasValue is false, + /// otherwise returns an instance of Maybe<> with the projected value + /// + /// The subject that will be projected. + /// The selector to be applied. + public static async Task> SelectAsync(this Maybe subject, Func> selector) + where V : struct + { + selector = selector ?? throw new ArgumentNullException(nameof(selector)); + + return !subject.HasValue ? Maybe.Nothing : ToMaybe(await selector(subject.Value)); + } + + /// + /// Projects the value according to the selector and flatten it. + /// Analogous to Linq's SelectMany. + /// + /// + /// Maybe<>.Nothing if subject.HasValue is false, + /// otherwise returns an instance of Maybe<> with the projected value + /// + /// The subject that will be projected. + /// The selector to be applied. + public static async Task> SelectManyAsync(this Maybe subject, Func>> selector) + { + selector = selector ?? throw new ArgumentNullException(nameof(selector)); + + return !subject.HasValue ? Maybe.Nothing : await selector(subject.Value); + } + + /// + /// Applies an action to the value if it's present. + /// + /// The subject that will be projected. + /// The action to be applied to the value. + public static async Task ConsumeAsync(this Maybe subject, Func consumer) + { + consumer = consumer ?? throw new ArgumentNullException(nameof(consumer)); + + if (subject.HasValue) + { + await consumer(subject.Value); + } + } + #endregion } }