6

I was looking for an anwer to question Get next N elements from enumerable didn't find any satisfying and brewed my own. What I came up with was

IEnumerable<T> Chunk<T, R>(IEnumerable<R> src, int n, Func<IEnumerable<R>, T> action){
  IEnumerable<R> head;
  IEnumerable<R> tail = src;
  while (tail.Any())
  {
    head = tail.Take(n);
    tail = tail.Skip(n);
    yield return action(head);
  }
}

What I would really like though, is to have action have a default of t=>t, but I can't figure out how to make that a default argument. The signature IEnumerable<T> Chunk<T, R>(IEnumerable<R> src, int n, Func<IEnumerable<R>, T> action = t=>t) gives a syntax error.

My question is, how do I do this?

I suppose this is identical to Specifying a lambda function as default argument but for C# instead of C++

As a side note, I know it doesn't make any syntactical difference, but would it be easier to read if I switched T and R?

Community
  • 1
  • 1
Martijn
  • 11,964
  • 12
  • 50
  • 96
  • Side note: Yes, I'd switch them at least, and possibly rename them TSource and TResult. (I think that way it'll match the default LINQ methods.) – Rawling Jun 21 '12 at 14:27

2 Answers2

13

Default values have to be constants, and the only constant value for a delegate is a null reference.

I suggest you use overloading instead. Note that t => t wouldn't be valid anyway, unless you know that there's a conversion from IEnumerable<R> to T, which I don't see anywhere.

Aside from the lambda expression validity problem, you could use the null coalescing operator:

IEnumerable<T> Chunk<T, R>(IEnumerable<R> src, int n,
                           Func<IEnumerable<R>, T> action = null)
{
    action = action ?? (t => t);
    ...
}

... but that would be sort of abusing it, and you wouldn't be able to tell whether the null was actually from a caller who thought they were passing a non-null value, and would prefer you to raise an ArgumentNullException.

EDIT: Additionally, note that your method is fundamentally problematic - iterating over the chunks will evaluate the original sequence several times (once per chunk) in order to skip the right amount. It would probably be better to write a method which eagerly read each chunk before returning it. We have a similar method in MoreLINQ, called Batch. Note that Batch has exactly the overload mentioned here - and in this case, the t => t works, because the overload has fewer type parameters, so we know the identity conversion is okay.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • The use http://pastebin.com/T3RDxg7x with t => t works as expected in linqpad (`yield return action(head)` -> `yield return head`). What am I missing here? – Martijn Jun 21 '12 at 14:38
  • @Martijn: In your case, the *calling* code knows that the conversion is okay, because it knows the type arguments (`IEnumerable` and `int`). The generic method doesn't know that. – Jon Skeet Jun 21 '12 at 14:39
  • Commenting on the edit: I hoped that `result = Skip(n)` would be O(n) rather than O(size of result), giving me "only" two iterations over the collection (plus whatever action does) - all assuming that it is a collection that can be iterated over more than once. Using a bucket as MoreLINQ does is probably the more sensible idea. – Martijn Jun 21 '12 at 14:47
  • sidenote: My first Skeet answer. Only now I feel like a true StackOverflow member. – Martijn Jun 21 '12 at 14:48
  • @Martijn: You're returning `src.Take(10)`, then `src.Skip(10).Take(10)`, then `src.Skip(10).Skip(10).Take(10)` etc. The result of each `Skip` is a new *lazily evaluated* sequence - each time you iterate over it (including if you build another sequence from it and iterate over that) it will start from scratch. Lazy evaluation can be a pain to think about :( – Jon Skeet Jun 21 '12 at 14:50
  • Ugh, that's nasty. I was sort of hoping that src.Skip(10) would return an Enumerable with the Enumerator pointing at the then current element or do some other form of caching. So much for that idea, I feel like the first time it was explained to me how not to do Fibonacci now. – Martijn Jun 21 '12 at 14:58
2

It's the same for C#: create an overload.

IEnumerable<T> Chunk<T, R>(IEnumerable<R> src, int n){
    return Chunk<T, R>(src, n, t => t);
}
Mattias Buelens
  • 19,609
  • 4
  • 45
  • 51