Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some FallbackIfEmpty improvements #294

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion MoreLinq.Test/FallbackIfEmptyTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// limitations under the License.
#endregion

using System;
using System.Linq;
using NUnit.Framework;
using LinqEnumerable = System.Linq.Enumerable;
Expand All @@ -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]
Expand All @@ -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<int>();
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()
{
Expand Down
224 changes: 190 additions & 34 deletions MoreLinq/FallbackIfEmpty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,49 +158,205 @@ public static IEnumerable<T> FallbackIfEmpty<T>(this IEnumerable<T> source, IEnu
return FallbackIfEmptyImpl(source, 0, default(T), default(T), default(T), default(T), fallback);
}

/// <summary>
/// Returns the elements of a sequence, but if it is empty then
/// returns an altenate sequence of values.
/// </summary>
/// <typeparam name="T">The type of the elements in the sequences.</typeparam>
/// <param name="source">The source sequence.</param>
/// <param name="fallbackFactory">A function that generates the first value of the alternate sequence that
/// is returned if <paramref name="source"/> is empty.</param>
/// <returns>
/// An <see cref="IEnumerable{T}"/> that containing fallback values
/// if <paramref name="source"/> is empty; otherwise, <paramref name="source"/>.
/// </returns>

public static IEnumerable<T> FallbackIfEmpty<T>(this IEnumerable<T> source, Func<T> 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);
}

/// <summary>
/// Returns the elements of a sequence, but if it is empty then
/// returns an altenate sequence of values.
/// </summary>
/// <typeparam name="T">The type of the elements in the sequences.</typeparam>
/// <param name="source">The source sequence.</param>
/// <param name="fallbackFactory1">A function that generates the first value of the alternate sequence that
/// is returned if <paramref name="source"/> is empty.</param>
/// <param name="fallbackFactory2">A function that generates the second value of the alternate sequence that
/// is returned if <paramref name="source"/> is empty.</param>
/// <returns>
/// An <see cref="IEnumerable{T}"/> that containing fallback values
/// if <paramref name="source"/> is empty; otherwise, <paramref name="source"/>.
/// </returns>

public static IEnumerable<T> FallbackIfEmpty<T>(this IEnumerable<T> source, Func<T> fallbackFactory1, Func<T> 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);
}

/// <summary>
/// Returns the elements of a sequence, but if it is empty then
/// returns an altenate sequence of values.
/// </summary>
/// <typeparam name="T">The type of the elements in the sequences.</typeparam>
/// <param name="source">The source sequence.</param>
/// <param name="fallbackFactory1">A function that generates the first value of the alternate sequence that
/// is returned if <paramref name="source"/> is empty.</param>
/// <param name="fallbackFactory2">A function that generates the second value of the alternate sequence that
/// is returned if <paramref name="source"/> is empty.</param>
/// <param name="fallbackFactory3">A function that generates the third value of the alternate sequence that
/// is returned if <paramref name="source"/> is empty.</param>
/// <returns>
/// An <see cref="IEnumerable{T}"/> that containing fallback values
/// if <paramref name="source"/> is empty; otherwise, <paramref name="source"/>.
/// </returns>

public static IEnumerable<T> FallbackIfEmpty<T>(this IEnumerable<T> source, Func<T> fallbackFactory1, Func<T> fallbackFactory2, Func<T> 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);
}

/// <summary>
/// Returns the elements of a sequence, but if it is empty then
/// returns an altenate sequence of values.
/// </summary>
/// <typeparam name="T">The type of the elements in the sequences.</typeparam>
/// <param name="source">The source sequence.</param>
/// <param name="fallbackFactory1">A function that generates the first value of the alternate sequence that
/// is returned if <paramref name="source"/> is empty.</param>
/// <param name="fallbackFactory2">A function that generates the second value of the alternate sequence that
/// is returned if <paramref name="source"/> is empty.</param>
/// <param name="fallbackFactory3">A function that generates the third value of the alternate sequence that
/// is returned if <paramref name="source"/> is empty.</param>
/// <param name="fallbackFactory4">A function that generates the fourth value of the alternate sequence that
/// is returned if <paramref name="source"/> is empty.</param>
/// <returns>
/// An <see cref="IEnumerable{T}"/> that containing fallback values
/// if <paramref name="source"/> is empty; otherwise, <paramref name="source"/>.
/// </returns>

public static IEnumerable<T> FallbackIfEmpty<T>(this IEnumerable<T> source, Func<T> fallbackFactory1, Func<T> fallbackFactory2, Func<T> fallbackFactory3, Func<T> 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<T> FallbackIfEmpty<T>(this IEnumerable<T> source, int count, Func<T> fallbackFactory1, Func<T> fallbackFactory2, Func<T> fallbackFactory3, Func<T> 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<T> fallback()
{
yield return fallbackFactory1();
if (count > 1) yield return fallbackFactory2();
if (count > 2) yield return fallbackFactory3();
if (count > 3) yield return fallbackFactory4();
}
}

/// <summary>
/// Returns the elements of a sequence, but if it is empty then
/// returns an altenate sequence from an array of values.
/// </summary>
/// <typeparam name="T">The type of the elements in the sequences.</typeparam>
/// <param name="source">The source sequence.</param>
/// <param name="fallbackFactories">The array of functions that generate the values of the alternate
/// sequence if <paramref name="source"/> is empty.</param>
/// <returns>
/// An <see cref="IEnumerable{T}"/> that containing fallback values
/// if <paramref name="source"/> is empty; otherwise, <paramref name="source"/>.
/// </returns>

public static IEnumerable<T> FallbackIfEmpty<T>(this IEnumerable<T> source, params Func<T>[] fallbackFactories)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (fallbackFactories == null) throw new ArgumentNullException(nameof(fallbackFactories));

return FallbackIfEmpty(source, (IEnumerable<Func<T>>)fallbackFactories);
}

/// <summary>
/// Returns the elements of a sequence, but if it is empty then
/// returns an altenate sequence from an array of values.
/// </summary>
/// <typeparam name="T">The type of the elements in the sequences.</typeparam>
/// <param name="source">The source sequence.</param>
/// <param name="fallbackFactories">The sequence of functions that generate the values of the alternate
/// sequence if <paramref name="source"/> is empty.</param>
/// <returns>
/// An <see cref="IEnumerable{T}"/> that containing fallback values
/// if <paramref name="source"/> is empty; otherwise, <paramref name="source"/>.
/// </returns>

public static IEnumerable<T> FallbackIfEmpty<T>(this IEnumerable<T> source, IEnumerable<Func<T>> 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<T> FallbackIfEmptyImpl<T>(IEnumerable<T> source,
int? count, T fallback1, T fallback2, T fallback3, T fallback4,
IEnumerable<T> fallback)
{
var collection = source as ICollection<T>;
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<T>();
if (source is ICollection<T> collection) {
if (collection.Count == 0) {
return fallbackChooser();
}
else {
return collection;
}
}

using (var e = source.GetEnumerator())
return nonCollectionIterator();

IEnumerable<T> 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<T> fallbackChooser()
{
if (count > 0 && count <= 4) {
return instancesFallback();
}
else {
return fallback;
}
}
IEnumerable<T> instancesFallback()
{
yield return fallback1;
if (count > 1) yield return fallback2;
if (count > 2) yield return fallback3;
if (count > 3) yield return fallback4;
}
}
}
Expand Down