LINQ deferred execution with a function's result as source (e.g. Console.ReadLine)

A function's result is the source for a LINQ query. I want it to be evaluated lazily, every time I use the query, not be locked when I create it. This is an example of what I mean:

var query = from c in Console.ReadLine()
            group c by char.IsDigit(c) into gr
            select new { IsDigit = gr.Key, Count = gr.Count() };

Console.WriteLine() runs only once - when query is created, even without calling a terminating method on it like ToList(). What I would want is for Console.WriteLine() (or any other function in its place) to be executed only when I use the query with ToList() or Count() etc.

Jon Skeet
people
quotationmark

If you don't mind a bit of extra infrastructure, it's not too bad - you can create a DeferredEnumerable<T> class that just executes the given delegate every time it's asked for an iterator. A static non-generic class can then help with type inference. Complete example:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

// Just for type inference...
public static class DeferredEnumerable
{
    public static IEnumerable<T> For<T>(Func<IEnumerable<T>> func) =>
        new DeferredEnumerable<T>(func);
}

public sealed class DeferredEnumerable<T> : IEnumerable<T>
{
    private readonly Func<IEnumerable<T>> func;

    public DeferredEnumerable(Func<IEnumerable<T>> func)
    {
        this.func = func;
    }

    public IEnumerator<T> GetEnumerator() => func().GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

class Test
{
    static void Main()
    {
        var query = 
            from c in DeferredEnumerable.For(Console.ReadLine)
            group c by char.IsDigit(c) into gr
            select new { IsDigit = gr.Key, Count = gr.Count() };


        Console.WriteLine("First go round");
        Console.WriteLine(string.Join(Environment.NewLine, query));

        Console.WriteLine("Second go round");
        Console.WriteLine(string.Join(Environment.NewLine, query));
    }
}

people

See more on this question at Stackoverflow