C# 试图了解linq/延迟执行是如何工作的

C# 试图了解linq/延迟执行是如何工作的,c#,linq,lazy-evaluation,C#,Linq,Lazy Evaluation,我有以下方法,这是执行分层k折叠交叉验证逻辑的一部分 private static IEnumerable<IEnumerable<int>> GenerateFolds( IClassificationProblemData problemData, int numberOfFolds) { IRandom random = new MersenneTwister(); IEnumerable<double> values = probl

我有以下方法,这是执行分层k折叠交叉验证逻辑的一部分

private static IEnumerable<IEnumerable<int>> GenerateFolds(
   IClassificationProblemData problemData, int numberOfFolds) 
{
   IRandom random = new MersenneTwister();
   IEnumerable<double> values = problemData.Dataset.GetDoubleValues(problemData.TargetVariable, problemData.TrainingIndices);

   var valuesIndices = 
       problemData.TrainingIndices.Zip(values, (i, v) => new { Index = i, Value = v });

   IEnumerable<IEnumerable<IEnumerable<int>>> foldsByClass = 
        valuesIndices.GroupBy(x => x.Value, x => x.Index)
                     .Select(g => GenerateFolds(g, g.Count(), numberOfFolds));

   var enumerators = foldsByClass.Select(x => x.GetEnumerator()).ToList();

   while (enumerators.All(e => e.MoveNext())) 
   {
       var fold = enumerators.SelectMany(e => e.Current).OrderBy(x => random.Next());
       yield return fold.ToList();
   }
}
私有静态IEnumerable GenerateFold(
ICClassificationProblemData problemData,整数倍)
{
IRandom random=new MersenneTwister();
IEnumerable values=problemData.Dataset.GetDoubleValues(problemData.TargetVariable,problemData.TrainingDices);
var值分片=
problemData.trainingDices.Zip(值,(i,v)=>new{Index=i,Value=v});
IEnumerable foldsByClass=
valuesIndices.GroupBy(x=>x.Value,x=>x.Index)
.选择(g=>GenerateFolds(g,g.Count(),numberofolds));
var enumerators=foldsByClass.Select(x=>x.GetEnumerator()).ToList();
while(枚举数.All(e=>e.MoveNext())
{
var fold=enumerators.SelectMany(e=>e.Current).OrderBy(x=>random.Next());
收益率返回fold.ToList();
}
}
折叠生成:

private static IEnumerable<IEnumerable<T>> GenerateFolds<T>(
    IEnumerable<T> values, int valuesCount, int numberOfFolds) 
{
    // number of folds rounded to integer and remainder
    int f = valuesCount / numberOfFolds, r = valuesCount % numberOfFolds; 
    int start = 0, end = f;

    for (int i = 0; i < numberOfFolds; ++i)
    {
        if (r > 0) 
        {
          ++end;
          --r;
        }

        yield return values.Skip(start).Take(end - start);
        start = end;
        end += f;
    }
 }
私有静态IEnumerable GenerateFold(
IEnumerable values,int valuescont,int numberofolds)
{
//四舍五入为整数和余数的折叠数
int f=ValueScont/numberOfFolds,r=ValueScont%numberOfFolds;
int start=0,end=f;
for(int i=0;i0)
{
++结束;
--r;
}
产生返回值。跳过(开始)。获取(结束-开始);
开始=结束;
end+=f;
}
}

通用的
GenerateFolds让我们思考一下什么是
fold
变量:

var fold = enumerators.SelectMany(e => e.Current).OrderBy(x => random.Next());
它不是查询执行的结果。这是一个查询定义。因为
SelectMany
OrderBy
都是延迟执行方式的运算符。因此,它只保存了有关从所有枚举数中展平当前项并以随机顺序返回它们的知识。我突出显示了单词current,因为它是查询执行时的当前项

现在让我们考虑一下何时执行此查询。
GenerateFolds
方法执行的结果是
IEnumerable
查询的
IEnumerable
。以下代码不执行任何查询:

var folds = GenerateFolds(indices, values, numberOfFolds);
这又只是一个疑问。您可以通过调用
ToList()
或枚举来执行它:

var f = folds.ToList();
但即使现在,内部查询也不会执行。他们都被退回,但没有被执行。即,当您将查询保存到列表
f
时,执行了
GenerateFolds
中的
while
循环。和
e.MoveNext()
已被多次调用,直到您退出循环:

while (enumerators.All(e => e.MoveNext()))
{
    var fold = enumerators.SelectMany(e => e.Current).OrderBy(x => random.Next());
    yield return fold;
}
那么,什么是
f
?它保存查询列表。这样您就得到了所有的项,当前项是每个枚举数的最后一项(请记住,我们已经迭代了
,而此时
完全循环)。但是这些查询都还没有执行!在这里,您可以执行其中的第一个:

f[0].Count() 
您可以获得第一个查询返回的项目数(在问题顶部定义)。但这样您已经枚举了所有查询,当前项是最后一项。最后一项中的索引数

现在来看看

folds.First().Count()
在这里,您不会枚举所有查询以将它们保存在列表中。即,
循环只执行一次,当前项为第一项。这就是为什么在第一项中有索引计数。这就是为什么这些值是不同的


最后一个问题-为什么在
while
循环中添加
ToList()
时所有操作都可以正常工作。答案很简单——执行每个查询。并且您有索引列表,而不是查询定义。每次迭代都会执行每个查询,因此当前项总是不同的。而且您的代码运行良好。

旁注-
IEnumerable
很酷:)谢谢您的编辑,让我的问题看起来更漂亮了!:)只需验证您的代码-无需将折叠保存到列表即可正常工作。它返回
numberofolds
count的索引组这很奇怪,我注意到没有
ToList()
的情况下有不同的行为。使用VS 2013和.NET 4.5.50938。你模拟了索引/值吗?您的测试中一定有些不同。我使用简单int数组作为索引,双数组作为值hanks,我的印象是查询定义独立于e.Current的状态(即,
枚举数。SelectMany(e=>e.Current)
将在内部存储有关要展平的相应IEnumerable的信息)。当您被迫执行内部查询以强制执行正确的行为时,这并不是真正的延迟执行。