强制 IEnumerable<T> 在不调用 .ToArray() 或 .ToList() 的情况下进行评估

Force IEnumerable<T> to evaluate without calling .ToArray() or .ToList()

如果我使用类似这样的方式查询 EF...

IEnumerable<FooBar> fooBars = db.FooBars.Where(o => o.SomeValue == something);

IIRC,这会在后台创建一个惰性求值的可迭代状态机,它还不包含任何结果;相反,它包含 "how" 的表达式以在需要时获取结果。

如果我想强制集合包含结果,我必须调用 .ToArray().ToList()

有没有办法在不调用 .ToArray().ToList(); 的情况下强制 IEnumerable<T> 集合包含结果?

理由

我不知道 CLR 是否能够做到这一点,但本质上我想强行创建一个实现 IEnumerable<T> 接口的评估集合,但由 CLR 在后台实现,因此不是一个List<T>Array<T>

大概这是不可能的,因为我不知道有任何 CLR 功能可以在内存中创建实现 IEnumerable<T>

的评估集合

提案

比如说,我可以这样写:

var x = IEnumerable<FooBar> fooBars = db.FooBars
        .Where(o => o.SomeValue == something)
        .Evaluate(); // Does NOT return a "concrete" impl such as List<T> or Array<T>

Console.WriteLine(x.GetType().Name);
// eg. <EvaluatedEnumerable>e__123

您可以使用 foreach 循环:

foreach (var item in fooBars) { }

请注意,这会评估 fooBars 中的所有项目,但会立即丢弃结果。下次您 运行 相同的 foreach 循环或 .ToArray().ToList() 时,枚举将再次计算。

Is there a way to force an IEnumerable<T> collection to contain results without calling .ToArray() or .ToList(); ?

是的,但这可能不是您想要的:

IEnumerable<T> source = …;
IEnumerable<T> cached = new List<T>(source);

问题是,IEnumerable<T> 不是具体类型。它是表示项目序列的接口(合同)。可以有任何具体类型 "hiding behind" 这个接口;有些可能只代表一个查询,有些实际上将查询的项目保存在内存中。

如果你想强制计算你的序列,以便结果实际存储在物理内存中,你需要确保 IEnumerable<T> 背后的具体类型是一个保存结果的内存集合的评价。上面的代码示例就是这样做的。

我 运行 涉及的一个具体用例围绕着需要确保包装数据库查询的 IEnumerable 在将控制权返回给调用方法。但是结果太大而无法完全评估,因此 IEnumerable 支持流式传输。

internal class EagerEvaluator<T>
{
    private readonly T _first;
    private readonly IEnumerator<T> _enumerator;
    private readonly bool _hasFirst;

    public EagerEvaluator(IEnumerable<T> enumerable)
    {
        _enumerator = enumerable.GetEnumerator();
        if (_enumerator.MoveNext())
        {
            _hasFirst = true;
            _first = _enumerator.Current;                
        }
    }

    public IEnumerable<T> ToEnumerable()
    {
        if (_hasFirst)
        {
            yield return _first;

            while (_enumerator.MoveNext())
            {
                yield return _enumerator.Current;                
            }
        }
    }
}

用法非常简单:

IEnumerable<FooBar> fooBars = new EagerEvaluator(fooBars).ToEnumerable()

另一个选项是:

<linq expression>.All( x => true);

我使用 Aggregate<T>() 来评估一个 IEnumerable<T> 有副作用:

private static IEnumerable<T> Evaluate<T>(IEnumerable<T> source)
    => source.Aggregate(Enumerable.Empty<T>(), (evaluated, s) => evaluated.Append(s));

查看实际效果:https://dotnetfiddle.net/iya2l0