为什么编译器跳过这个方法?

时间:2014-05-19 21:36:24

标签: c# recursion

我试图为我的客户提供多种访问排列的方法。我创建了以下代码,在每次访问时执行Action<T[]> output

    public void Permute<T>(T[][] sets, Action<T[]> output)
    {
        Permute(sets, 0, new T[sets.Length], output);
    }

    private void Permute<T>(T[][] sets, int set, T[] permutation, Action<T[]> output)
    {
        for (int i = 0; i < sets[set].Length; ++i)
        {
            permutation[set] = sets[set][i];

            if (set < (sets.Length - 1))
                Permute(sets, set + 1, permutation, output);
            else
                output(permutation);
        }
    }

并且它有效,所以我转向访问排列的下一种方式,即使用IEnumerable<int[]>yield return模式。这是我的实施:

    public IEnumerable<int[]> Permute(int[][] sets)
    {
       return  Permute(sets, 0, new int[sets.Length]); // <--skips this
    }

    private IEnumerable<int[]> Permute(int[][] sets, int set, int[] permutation)
    {
        for (int i = 0; i < sets[set].Length; ++i)
        {
            permutation[set] = sets[set][i];

            if (set < (sets.Length - 1))
                Permute(sets, set + 1, permutation);
            else
                yield return permutation;
        }
    }

但这不起作用。编译器跳过指定的代码行而不尝试执行它。

有人可以向我解释如何修改显示的代码,以便启用IEnumerable<int[]>yield return模式吗?

以下是用以下方法测试它的客户端代码:(我使用的是nunit)

    [Test]
    public void PermuteThreeDifferentSetsUsingTheirIndexValues()
    {
        var stopwatch = new Stopwatch();
        stopwatch.Start();
        var indexSets = new[]
                            {
                                new[] {0, 1, 2},
                                new[] {0, 1, 2},
                                new[] {0, 1, 2},
                            };

        var results = _generator.Permute(indexSets);

        foreach (var result in results)
        {
            _permCounter++;
            Console.Write(result[0]);
            Console.Write(" ");
            Console.Write(result[1]);
            Console.Write(" ");
            Console.Write(result[2]);
            Console.WriteLine();
        }
        stopwatch.Stop();

        Console.WriteLine("Permutation Visitor Elapsed Ticks: " + stopwatch.ElapsedTicks);

        Assert.AreEqual(27, _permCounter);
    }

我的预感是编译器不满意我使用递归的返回值。然而,这只是一种预感。提前谢谢。

1 个答案:

答案 0 :(得分:3)

这就是它的工作方式,以及大量代码使得自由使用:IEnumerable<T>实现被允许是懒惰的。你的Permute没有直接做任何工作:它返回一个对象,一旦你开始迭代它,开始做一些工作,并且只足以确定第一个结果。当你的循环然后请求下一个项目时,你的函数会继续,但只能直到它可以确定第二个结果。

这在enumerable.Where(some predicate).First()等代码中非常有用,因为它可以避免在找到第一个结果后评估谓词。

在你最外面的函数中,你会迭代_generator.Permute(indexSets)的结果,但在该函数中,你调用Permute(sets, set + 1, permutation)并丢弃结果(如你在问题中所述)。由于您不使用该递归调用的结果,效果就好像递归调用永远不会发生。

一般来说,当你想在迭代器函数中递归调用自己时,你需要yield return每个结果。一个愚蠢的例子:

IEnumerable<int> f(int n) {
    if (n > 0)
        foreach (var i in f(n - 1))
            yield return i;
    for (int i = 0; i != n; i++)
        yield return i;
}