4

I had a look at the implementation of several extension methods of the genious MoreLINQ- project. And I came across a style habit that I cannot explain. Maybe some of you can?

It happens for example in Pairwise.cs, cited below.

So why would the author author a local function named _() just to call it in the return expression? Wouldn't it be straight forward just to implement the yield return/yield break in the very function? My suspicion is that it has something to do with the way the compiler generates the Enumerator-object off the yield implementation. But I don't see a difference. Actually there is even some closure-ing happening - I regard that as even worse (!?)
Edit: No, it should not be closured, because it is not a lambda but a local function which will just grab the outer scope variables whatever they are.

    public static IEnumerable<TResult> Pairwise<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TSource, TResult> resultSelector)
    {
        if (source == null) throw new ArgumentNullException(nameof(source));
        if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector));

        return _(); IEnumerable<TResult> _()
        {
            using (var e = source.GetEnumerator())
            {
                if (!e.MoveNext())
                    yield break;

                var previous = e.Current;
                while (e.MoveNext())
                {
                    yield return resultSelector(previous, e.Current);
                    previous = e.Current;
                }
            }
        }
    }
Robetto
  • 739
  • 7
  • 20
  • 2
    *My suspicion is that it has something to do with the way the compiler generates the Enumerator-object off the yield implementation.* Yes. See [Why is a local function required when it is only called once?](https://github.com/morelinq/MoreLINQ/issues/412) and [Dissecting the local functions in C# 7](https://blogs.msdn.microsoft.com/seteplia/2017/10/03/dissecting-the-local-functions-in-c-7/) – sloth Feb 05 '19 at 11:36
  • To implement IEnumerable you need to have IEnumerate() which is the MoveNext() method, and Current – jdweng Feb 05 '19 at 11:36
  • Thanks, it's explained right in his issue tracking - I rather thought about code size efficiencies or code runtime efficiencies. – Robetto Feb 05 '19 at 11:39

1 Answers1

7

It's to make argument validation eager but allow the rest of the method to be written using yield. With it, you get an exception relating to the arguments at the point at which you call Pairwise<TSource, TResult>.

Without it, you'd get the exception when you first call MoveNext on the returned enumerator.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
  • 1
    side note: you often see similar things in `async` code (especially if there's a good chance of it actually being synchronous lots of the time), or in code where there is an uncommon `case` (etc) that takes more code than the rest, and you want the JIT to try and inline the common-cases (so: you want to minimize the size of the IL in the most likely scenarios, but you don't need the implementation of the awkward `case` to be available outside of that method) – Marc Gravell Feb 05 '19 at 11:39