diff --git a/MoreLinq.Test/FallbackIfEmptyTest.cs b/MoreLinq.Test/FallbackIfEmptyTest.cs index ba4a9219c..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; @@ -37,7 +38,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 +52,57 @@ 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 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 40f1fe57a..8442e8430 100644 --- a/MoreLinq/FallbackIfEmpty.cs +++ b/MoreLinq/FallbackIfEmpty.cs @@ -158,49 +158,205 @@ 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) { - 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; } } }