LINQ 以函数结果作为源延迟执行(例如 Console.ReadLine)

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

函数的结果是 LINQ 查询 的来源。我希望每次使用查询时都对其进行延迟评估,而不是在创建它时将其锁定。这是我的意思的一个例子:

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() 只运行一次 - 当 query 创建时,即使没有像 ToList() 那样调用终止方法。我想要的是仅当我将查询与 ToList()Count()

一起使用时才执行 Console.WriteLine() (或其位置的任何其他函数)

我找到了 2 个解决方案,但它们真的很丑,我不想使用它们


解决方案 1

这个特别难看,因为你需要一个额外的功能(它不能是匿名的)

static IEnumerable<string> GetDeferredConsoleReadLine()
{
    yield return Console.ReadLine();
}

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

这使用可枚举函数的延迟执行yield return函数的结果。


解决方案 2

这在另一个查询中使用了另一个笨拙的 LINQ 查询构造,其中 returns 一个元素(问题是它需要一个源 - 我使用一个单元素字符串并丢弃结果但那不是真的很干净)

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

有没有其他方法可以做到这一点,可能不需要在查询中使用 SelectMany

如果您不介意一些额外的基础设施,那也不算太糟糕 - 您可以创建一个 DeferredEnumerable<T> class,它在每次请求迭代器时只执行给定的委托。然后,静态非泛型 class 可以帮助进行类型推断。完整示例:

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));
    }
}

您可以将查询放在单独的方法中。

static void Main(string[] args)
{
    while (true)
    {
        foreach (var y in RunQuery()) {
            Console.WriteLine($"{y.IsDigit}: {y.Count}");
        }
    }
}

class A{public bool IsDigit { get; set; } public int Count { get; set; } }

private static IEnumerable<A> RunQuery()
{
    return from c in Console.ReadLine()
                group c by char.IsDigit(c) into gr
                select new A { IsDigit = gr.Key, Count = gr.Count() };
}