Skip to content

Conversation

@fsateler
Copy link
Member

@fsateler fsateler commented Aug 8, 2017

No description provided.

{
var source = new int[] { 1 };
// ReSharper disable PossibleMultipleEnumeration
Assert.AreSame(source.FallbackIfEmpty(12), source);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider turning these into test cases.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did this on ccca164 , but I'm not sure it ended up being better, as it is a bunch of extra code noise.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that ccca164 isn't any better. What I had more in mind was this:

[TestCase(new[] { 12                     })]
[TestCase(new[] { 12, 23                 })]
[TestCase(new[] { 12, 23, 34             })]
[TestCase(new[] { 12, 23, 34, 45         })]
[TestCase(new[] { 12, 23, 34, 45, 56     })]
[TestCase(new[] { 12, 23, 34, 45, 56, 67 })]
public void FallbackIfEmptyPreservesSourceCollectionIfPossible(int[] fallback)
{
    var source = new[] { 1 };
    Assert.AreSame(source.FallbackIfEmpty(fallback), source);
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But those test cases don't add much value, as they all test the same overload. The idea is to test that all overloads preserve the source collection.

Copy link
Member

@atifaziz atifaziz Aug 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duh! My bad. All the overloading made it very opaque at the call site. Let's revert ccca164 then and ignore the remark about breaking it down into test cases.

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

IEnumerable<T> nonCollectionIterator()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This local method could be called just _, as we have done in practically all other cases. It is the most default case whereas the rest is just mere optimizations.

{
foreach (var item in fallback)
yield return item;
using (var e = source.GetEnumerator()) {
Copy link
Member

@atifaziz atifaziz Aug 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For sake of consistency with the rest of the code base, please use the BDS style for placement of braces (with the exception of the do…while loop which looks fine as-is since the do block is a simple one-liner).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arrgh. I really need to finish up the editorconfig so that this stops becoming an issue.

}
IEnumerable<T> fallbackChooser()
{
if (count > 0 && count <= 4) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not make this as simple as predicating on fallback being null or not?

IEnumerable<T> fallbackChooser() => fallback ?? instancesFallback();

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we don't check that (count == 0 || count > 4) => (fallback == null). I didn't want to introduce a nullness requirement, when previously all that was required was that it be non-null when it was to be used.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I understand you here. Could you re-phrase?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this method, we never assert that fallback must be null when count is between 1 and 4 (inclusive). We only assert that it must be non-null if count is not within that range.
Now, the callers do indeed pass null in that case, but I saw no reason to impose that as a requirement.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Do we want to add the assertions? I guess what bothers me is having to worry about the range and seeing the 4 hard-coded. This is one way to get rid of that:

IEnumerable<T> FallbackChooser()
{
    return count == null ? fallback : InstancesFallback(count.Value);

    IEnumerable<T> InstancesFallback(int c)
    {
        if (c < 0) throw new ArgumentOutOfRangeException(nameof(count));
        if (c > 0) { c--; yield return fallback1; }
        if (c > 0) { c--; yield return fallback2; }
        if (c > 0) { c--; yield return fallback3; }
        if (c > 0) { c--; yield return fallback4; }
        if (c > 0) throw new ArgumentOutOfRangeException(nameof(count));
    }
}

The downside is that the last throw won't happen until after the last yield so yet another way is:

IEnumerable<T> FallbackChooser()
{
    switch (count)
    {
        case null: return fallback;
        case int n when n == 1: return Fallback1();
        case int n when n == 2: return Fallback2();
        case int n when n == 3: return Fallback3();
        case int n when n == 4: return Fallback4();
        default: throw new ArgumentOutOfRangeException(nameof(count));
    }

    IEnumerable<T> Fallback1() { yield return fallback1; }
    IEnumerable<T> Fallback2() => Fallback1().Concat(fallback2);
    IEnumerable<T> Fallback3() => Fallback2().Concat(fallback3);
    IEnumerable<T> Fallback4() => Fallback3().Concat(fallback4);
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the last version check. I made the switch is slightly different though as I preserved InstancesFallback. What do you think?

return fallback;
}
}
IEnumerable<T> instancesFallback()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be further localized to your fallbackChooser.

Local functions, like methods, should still use the PascalCase naming convention.

foreach (var item in fallbackChooser())
yield return item;
}
IEnumerable<T> fallbackChooser()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Local functions, like methods, should still use the PascalCase naming convention.

Copy link
Member

@atifaziz atifaziz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about reverting ccca164? Then I think we're done. 🏁

@fsateler
Copy link
Member Author

What about reverting ccca164? Then I think we're done. 🏁

Awesome. Just pushed a revert of that commit.

@atifaziz
Copy link
Member

atifaziz commented Aug 10, 2017 via email

@fsateler
Copy link
Member Author

Did you forget to push the revert commit? I don't see it yet in the PR branch.

Oops. The push was rejected due to me not having pulled your commits first. Now its there.

@atifaziz atifaziz added this to the 2.7.0 milestone Aug 23, 2017
@atifaziz atifaziz merged commit 24d1d93 into morelinq:master Aug 23, 2017
@fsateler fsateler deleted the fallback-if-empty-collection branch August 24, 2017 15:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants