diff --git a/MoreLinq.Test/FallbackIfEmptyTest.cs b/MoreLinq.Test/FallbackIfEmptyTest.cs index 8a9b28d55..f79141b0d 100644 --- a/MoreLinq.Test/FallbackIfEmptyTest.cs +++ b/MoreLinq.Test/FallbackIfEmptyTest.cs @@ -39,6 +39,28 @@ public void FallbackIfEmptyWithEmptySequence() } [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); + // ReSharper restore PossibleMultipleEnumeration + } + + [Test] + public void FallbackIfEmptyPreservesFallbackCollectionIfPossible() + { + var source = new int[0]; + var fallback = new int[] { 1 }; + Assert.AreSame(source.FallbackIfEmpty(fallback), fallback); + Assert.AreSame(source.FallbackIfEmpty(fallback.AsEnumerable()), fallback); + } + public void FallbackIfEmptyWithEmptySequenceCollectionOptimized() { var source = LinqEnumerable.Empty(); diff --git a/MoreLinq/FallbackIfEmpty.cs b/MoreLinq/FallbackIfEmpty.cs index 40f1fe57a..db08417b4 100644 --- a/MoreLinq/FallbackIfEmpty.cs +++ b/MoreLinq/FallbackIfEmpty.cs @@ -19,7 +19,6 @@ namespace MoreLinq { using System; using System.Collections.Generic; - using System.Linq; static partial class MoreEnumerable { @@ -155,52 +154,59 @@ public static IEnumerable FallbackIfEmpty(this IEnumerable source, IEnu { if (source == null) throw new ArgumentNullException(nameof(source)); if (fallback == null) throw new ArgumentNullException(nameof(fallback)); - return FallbackIfEmptyImpl(source, 0, default(T), default(T), default(T), default(T), fallback); + return FallbackIfEmptyImpl(source, null, default(T), default(T), default(T), default(T), fallback); } 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) + if (source is ICollection collection) { - // - // 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(); - } - - using (var e = source.GetEnumerator()) - { - if (e.MoveNext()) + if (collection.Count == 0) { - do { yield return e.Current; } - while (e.MoveNext()); + return Fallback(); } 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 + return collection; + } + } + + return _(); + + IEnumerable _() + { + using (var e = source.GetEnumerator()) + { + if (e.MoveNext()) { - foreach (var item in fallback) - yield return item; + do { yield return e.Current; } + while (e.MoveNext()); + yield break; } } + + foreach (var item in Fallback()) + yield return item; + } + + IEnumerable Fallback() + { + switch (count) + { + case null: return fallback; + case int n when n >= 1 && n <= 4: return FallbackOnArgs(); + default: throw new ArgumentOutOfRangeException(nameof(count), count, null); + } + + IEnumerable FallbackOnArgs() + { + yield return fallback1; + if (count > 1) yield return fallback2; + if (count > 2) yield return fallback3; + if (count > 3) yield return fallback4; + } } } }