From e39fc669bddfb2557303e4cd4560fe890f25e1b5 Mon Sep 17 00:00:00 2001 From: Felipe Sateler Date: Fri, 12 May 2017 18:49:30 -0300 Subject: [PATCH 1/2] FallbackIfEmpty: add overloads taking Func parameters Fixes: #293 --- MoreLinq.Test/FallbackIfEmptyTest.cs | 22 +++- MoreLinq/FallbackIfEmpty.cs | 156 +++++++++++++++++++++++++++ 2 files changed, 177 insertions(+), 1 deletion(-) diff --git a/MoreLinq.Test/FallbackIfEmptyTest.cs b/MoreLinq.Test/FallbackIfEmptyTest.cs index ba4a9219c..c4043da0d 100644 --- a/MoreLinq.Test/FallbackIfEmptyTest.cs +++ b/MoreLinq.Test/FallbackIfEmptyTest.cs @@ -37,7 +37,7 @@ public void FallbackIfEmptyWithNullSequence() [Test] public void FallbackIfEmptyWithNullFallbackParams() { - Assert.ThrowsArgumentNullException("fallback", () => new[] { 1 }.FallbackIfEmpty(null)); + Assert.ThrowsArgumentNullException("fallback", () => new[] { 1 }.FallbackIfEmpty((int[])null)); } [Test] @@ -51,9 +51,29 @@ public void FallbackIfEmptyWithEmptySequence() source.FallbackIfEmpty(12, 23, 34, 45).AssertSequenceEqual(12, 23, 34, 45); source.FallbackIfEmpty(12, 23, 34, 45, 56).AssertSequenceEqual(12, 23, 34, 45, 56); source.FallbackIfEmpty(12, 23, 34, 45, 56, 67).AssertSequenceEqual(12, 23, 34, 45, 56, 67); + source.FallbackIfEmpty(() => 12).AssertSequenceEqual(12); + source.FallbackIfEmpty(() => 12, () => 23).AssertSequenceEqual(12, 23); + source.FallbackIfEmpty(() => 12, () => 23, () => 34).AssertSequenceEqual(12, 23, 34); + source.FallbackIfEmpty(() => 12, () => 23, () => 34, () => 45).AssertSequenceEqual(12, 23, 34, 45); + source.FallbackIfEmpty(() => 12, () => 23, () => 34, () => 45, () => 56).AssertSequenceEqual(12, 23, 34, 45, 56); + source.FallbackIfEmpty(() => 12, () => 23, () => 34, () => 45, () => 56, () => 67).AssertSequenceEqual(12, 23, 34, 45, 56, 67); // ReSharper restore PossibleMultipleEnumeration } + [Test] + public void FallbackIfEmptyDoesNotInvokeFunctionsIfCollectionIsNonEmpty() + { + var func = BreakingFunc.Of(); + var source = new[] { 1 }; + + source.FallbackIfEmpty(func).AssertSequenceEqual(source); + source.FallbackIfEmpty(func, func).AssertSequenceEqual(source); + source.FallbackIfEmpty(func, func, func).AssertSequenceEqual(source); + source.FallbackIfEmpty(func, func, func, func).AssertSequenceEqual(source); + source.FallbackIfEmpty(func, func, func, func, func).AssertSequenceEqual(source); + source.FallbackIfEmpty(func, func, func, func, func, func).AssertSequenceEqual(source); + } + [Test] public void FallbackIfEmptyWithEmptySequenceCollectionOptimized() { diff --git a/MoreLinq/FallbackIfEmpty.cs b/MoreLinq/FallbackIfEmpty.cs index 40f1fe57a..aeb3ede6f 100644 --- a/MoreLinq/FallbackIfEmpty.cs +++ b/MoreLinq/FallbackIfEmpty.cs @@ -158,6 +158,162 @@ public static IEnumerable FallbackIfEmpty(this IEnumerable source, IEnu return FallbackIfEmptyImpl(source, 0, default(T), default(T), default(T), default(T), fallback); } + /// + /// Returns the elements of a sequence, but if it is empty then + /// returns an altenate sequence of values. + /// + /// The type of the elements in the sequences. + /// The source sequence. + /// A function that generates the first value of the alternate sequence that + /// is returned if is empty. + /// + /// An that containing fallback values + /// if is empty; otherwise, . + /// + + public static IEnumerable FallbackIfEmpty(this IEnumerable source, Func fallbackFactory) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (fallbackFactory == null) throw new ArgumentNullException(nameof(fallbackFactory)); + return FallbackIfEmpty(source, 1, fallbackFactory, null, null, null); + } + + /// + /// Returns the elements of a sequence, but if it is empty then + /// returns an altenate sequence of values. + /// + /// The type of the elements in the sequences. + /// The source sequence. + /// A function that generates the first value of the alternate sequence that + /// is returned if is empty. + /// A function that generates the second value of the alternate sequence that + /// is returned if is empty. + /// + /// An that containing fallback values + /// if is empty; otherwise, . + /// + + public static IEnumerable FallbackIfEmpty(this IEnumerable source, Func fallbackFactory1, Func fallbackFactory2) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (fallbackFactory1 == null) throw new ArgumentNullException(nameof(fallbackFactory1)); + if (fallbackFactory2 == null) throw new ArgumentNullException(nameof(fallbackFactory2)); + return FallbackIfEmpty(source, 2, fallbackFactory1, fallbackFactory2, null, null); + } + + /// + /// Returns the elements of a sequence, but if it is empty then + /// returns an altenate sequence of values. + /// + /// The type of the elements in the sequences. + /// The source sequence. + /// A function that generates the first value of the alternate sequence that + /// is returned if is empty. + /// A function that generates the second value of the alternate sequence that + /// is returned if is empty. + /// A function that generates the third value of the alternate sequence that + /// is returned if is empty. + /// + /// An that containing fallback values + /// if is empty; otherwise, . + /// + + public static IEnumerable FallbackIfEmpty(this IEnumerable source, Func fallbackFactory1, Func fallbackFactory2, Func fallbackFactory3) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (fallbackFactory1 == null) throw new ArgumentNullException(nameof(fallbackFactory1)); + if (fallbackFactory2 == null) throw new ArgumentNullException(nameof(fallbackFactory2)); + if (fallbackFactory3 == null) throw new ArgumentNullException(nameof(fallbackFactory3)); + return FallbackIfEmpty(source, 3, fallbackFactory1, fallbackFactory2, fallbackFactory3, null); + } + + /// + /// Returns the elements of a sequence, but if it is empty then + /// returns an altenate sequence of values. + /// + /// The type of the elements in the sequences. + /// The source sequence. + /// A function that generates the first value of the alternate sequence that + /// is returned if is empty. + /// A function that generates the second value of the alternate sequence that + /// is returned if is empty. + /// A function that generates the third value of the alternate sequence that + /// is returned if is empty. + /// A function that generates the fourth value of the alternate sequence that + /// is returned if is empty. + /// + /// An that containing fallback values + /// if is empty; otherwise, . + /// + + public static IEnumerable FallbackIfEmpty(this IEnumerable source, Func fallbackFactory1, Func fallbackFactory2, Func fallbackFactory3, Func fallbackFactory4) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (fallbackFactory1 == null) throw new ArgumentNullException(nameof(fallbackFactory1)); + if (fallbackFactory2 == null) throw new ArgumentNullException(nameof(fallbackFactory2)); + if (fallbackFactory3 == null) throw new ArgumentNullException(nameof(fallbackFactory3)); + if (fallbackFactory4 == null) throw new ArgumentNullException(nameof(fallbackFactory4)); + return FallbackIfEmpty(source, 4, fallbackFactory1, fallbackFactory2, fallbackFactory3, fallbackFactory4); + } + + private static IEnumerable FallbackIfEmpty(this IEnumerable source, int count, Func fallbackFactory1, Func fallbackFactory2, Func fallbackFactory3, Func fallbackFactory4) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (count < 0 || count > 4) { + throw new ArgumentException(nameof(count)); + } + return FallbackIfEmptyImpl(source, null, default(T), default(T), default(T), default(T), fallback()); + IEnumerable fallback() + { + yield return fallbackFactory1(); + if (count > 1) yield return fallbackFactory2(); + if (count > 2) yield return fallbackFactory3(); + if (count > 3) yield return fallbackFactory4(); + } + } + + /// + /// Returns the elements of a sequence, but if it is empty then + /// returns an altenate sequence from an array of values. + /// + /// The type of the elements in the sequences. + /// The source sequence. + /// The array of functions that generate the values of the alternate + /// sequence if is empty. + /// + /// An that containing fallback values + /// if is empty; otherwise, . + /// + + public static IEnumerable FallbackIfEmpty(this IEnumerable source, params Func[] fallbackFactories) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (fallbackFactories == null) throw new ArgumentNullException(nameof(fallbackFactories)); + + return FallbackIfEmpty(source, (IEnumerable>)fallbackFactories); + } + + /// + /// Returns the elements of a sequence, but if it is empty then + /// returns an altenate sequence from an array of values. + /// + /// The type of the elements in the sequences. + /// The source sequence. + /// The sequence of functions that generate the values of the alternate + /// sequence if is empty. + /// + /// An that containing fallback values + /// if is empty; otherwise, . + /// + + public static IEnumerable FallbackIfEmpty(this IEnumerable source, IEnumerable> fallbackFactories) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (fallbackFactories == null) throw new ArgumentNullException(nameof(fallbackFactories)); + + return FallbackIfEmptyImpl(source, null, default(T), default(T), default(T), default(T), fallbackFactories.Select(f => f())); + } + static IEnumerable FallbackIfEmptyImpl(IEnumerable source, int? count, T fallback1, T fallback2, T fallback3, T fallback4, IEnumerable fallback) From b2c7bec9ebdd474488248ce26d738371d5fb72b7 Mon Sep 17 00:00:00 2001 From: Felipe Sateler Date: Fri, 12 May 2017 19:11:31 -0300 Subject: [PATCH 2/2] FallbackIfEmpty: preserve source and fallback collections if possible --- MoreLinq.Test/FallbackIfEmptyTest.cs | 29 ++++++++++++ MoreLinq/FallbackIfEmpty.cs | 68 ++++++++++++++-------------- 2 files changed, 63 insertions(+), 34 deletions(-) diff --git a/MoreLinq.Test/FallbackIfEmptyTest.cs b/MoreLinq.Test/FallbackIfEmptyTest.cs index c4043da0d..ee96fa923 100644 --- a/MoreLinq.Test/FallbackIfEmptyTest.cs +++ b/MoreLinq.Test/FallbackIfEmptyTest.cs @@ -15,6 +15,7 @@ // limitations under the License. #endregion +using System; using System.Linq; using NUnit.Framework; using LinqEnumerable = System.Linq.Enumerable; @@ -74,6 +75,34 @@ public void FallbackIfEmptyDoesNotInvokeFunctionsIfCollectionIsNonEmpty() source.FallbackIfEmpty(func, func, func, func, func, func).AssertSequenceEqual(source); } + [Test] + public void FallbackIfEmptyPreservesSourceCollectionIfPossible() + { + var source = new int[] { 1 }; + // ReSharper disable PossibleMultipleEnumeration + Assert.AreSame(source.FallbackIfEmpty(12), source); + Assert.AreSame(source.FallbackIfEmpty(12, 23), source); + Assert.AreSame(source.FallbackIfEmpty(12, 23, 34), source); + Assert.AreSame(source.FallbackIfEmpty(12, 23, 34, 45), source); + Assert.AreSame(source.FallbackIfEmpty(12, 23, 34, 45, 56), source); + Assert.AreSame(source.FallbackIfEmpty(12, 23, 34, 45, 56, 67), source); + Assert.AreSame(source.FallbackIfEmpty(() => 12), source); + Assert.AreSame(source.FallbackIfEmpty(() => 12, () => 23), source); + Assert.AreSame(source.FallbackIfEmpty(() => 12, () => 23, () => 34), source); + Assert.AreSame(source.FallbackIfEmpty(() => 12, () => 23, () => 34, () => 45), source); + Assert.AreSame(source.FallbackIfEmpty(() => 12, () => 23, () => 34, () => 45, () => 56), source); + Assert.AreSame(source.FallbackIfEmpty(() => 12, () => 23, () => 34, () => 45, () => 56, () => 67), source); + // ReSharper restore PossibleMultipleEnumeration + } + + [Test] + public void FallbackIfEmptyPreservesFallbackCollectionIfPossible() + { + var source = new int[0]; + var fallback = new int[] { 1 }; + Assert.AreSame(source.FallbackIfEmpty(fallback), fallback); + } + [Test] public void FallbackIfEmptyWithEmptySequenceCollectionOptimized() { diff --git a/MoreLinq/FallbackIfEmpty.cs b/MoreLinq/FallbackIfEmpty.cs index aeb3ede6f..8442e8430 100644 --- a/MoreLinq/FallbackIfEmpty.cs +++ b/MoreLinq/FallbackIfEmpty.cs @@ -318,45 +318,45 @@ static IEnumerable FallbackIfEmptyImpl(IEnumerable source, int? count, T fallback1, T fallback2, T fallback3, T fallback4, IEnumerable fallback) { - var collection = source as ICollection; - if (collection != null && collection.Count == 0) - { - // - // Replace the empty collection with an empty sequence and - // carry on. LINQ's Enumerable.Empty is implemented - // intelligently to return the same enumerator instance and so - // does not incur an allocation. However, the same cannot be - // said for a collection like an empty array or list. This - // permits the rest of the logic while keeping the call to - // source.GetEnumerator() cheap. - // - - source = Enumerable.Empty(); + if (source is ICollection collection) { + if (collection.Count == 0) { + return fallbackChooser(); + } + else { + return collection; + } } - using (var e = source.GetEnumerator()) + return nonCollectionIterator(); + + IEnumerable nonCollectionIterator() { - if (e.MoveNext()) - { - do { yield return e.Current; } - while (e.MoveNext()); - } - else - { - e.Dispose(); // eager disposal - if (count > 0 && count <= 4) - { - yield return fallback1; - if (count > 1) yield return fallback2; - if (count > 2) yield return fallback3; - if (count > 3) yield return fallback4; - } - else - { - foreach (var item in fallback) - yield return item; + using (var e = source.GetEnumerator()) { + if (e.MoveNext()) { + do { yield return e.Current; } + while (e.MoveNext()); + yield break; } } + + foreach (var item in fallbackChooser()) + yield return item; + } + IEnumerable fallbackChooser() + { + if (count > 0 && count <= 4) { + return instancesFallback(); + } + else { + return fallback; + } + } + IEnumerable instancesFallback() + { + yield return fallback1; + if (count > 1) yield return fallback2; + if (count > 2) yield return fallback3; + if (count > 3) yield return fallback4; } } }