【问题标题】:Iterator bug of (more likely) a very subtle behavior?(更可能)非常微妙的行为的迭代器错误?
【发布时间】:2011-03-27 17:11:58
【问题描述】:

我的问题需要 3 个(不是太长的函数)才能重现(VS2010 / .NET 4)
在第一种情况下,不评估我的 IEnumerable(通过 ToList() 方法)
我不明白为什么..

//主程序

private void ButtonTest_Click(object sender, RoutedEventArgs args)  
{  
    int[] indexes = new int[] { 2, 2, 2, 2, 2, 2 };  
    var query = Odometer(indexes);  
    // 1) Iterator not evaluated ???  
    var temp = query.ToList();  
    MessageBox.Show(AsString(temp[3]));   

    // 2) OK in this case  
    int count = 0;  
    foreach (int[] item in query)  
    {  
        count++;  
        if (count == 3)  
            MessageBox.Show(AsString(item));  
    }  
}  

/// <summary>  
/// Generate all tuples between 0 and indexes[i]-1  
/// Ex :   
/// Odometer(new int[]{2, 3}); // (0, 0) (0, 1) (0, 2) (1, 0) (1, 1) (1, 2)  
/// </summary>  
/// <param name="indexes"></param>  
/// <returns></returns>  
public static IEnumerable<int[]> Odometer(int[] indexes)  
{  
    int[] result = new int[indexes.Length];  
    for (int i = 0; i < indexes.Length; i++)  
        result[i] = -1;  

    int ptr = 0;  
    while (ptr >= 0)  
    {  
        while (ptr < indexes.Length)  
        {  
            result[ptr++]++;  
            continue;  
        }  

        ptr--;  
        while (result[ptr] < indexes[ptr])  
        {  
            yield return result;  
            result[ptr]++;  
        }  

        result[ptr]--;  
        while (result[ptr] == indexes[ptr] - 1)  
        {  
            result[ptr] = -1;  
            ptr--;  
            if (ptr < 0)  
                break;  
        }  
    }  
}  

/// <summary>  
/// Format an IList of T    
/// </summary>  
/// <typeparam name="T"></typeparam>  
/// <param name="array"></param>  
/// <returns></returns>  
private static string AsString<T>(IList<T> array)  
{  
    StringBuilder builder = new StringBuilder();  
    foreach (T item in array)  
        builder.AppendFormat("{0}, ", item);  
    if (builder.Length >= 2)  
        builder.Length -= 2;  
    return builder.ToString();  
}  

提前感谢您的帮助
菲利普

【问题讨论】:

  • 如何确定 IEnumerable 是否被评估?
  • 在第一种情况下,temp 的大小合适,但包含 {-1, -1, -1, -1, -1} 的列表

标签: c# linq iterator


【解决方案1】:

您的 IEnumerable 在以下情况下运行:

var temp = query.ToList();

我在里程表中做了一个断点,果然它断了。它包含大量的-1列表。也许您需要更好的里程计方法?

编辑: 问题是您一直在 yield 返回相同的数组。所以它将始终具有相同的值。您应该阅读一些关于 .Net/C# 中的引用的内容。此外,该方法只需要长度,因此只需发送长度即可。

public static IEnumerable<int[]> Odometer(int[] indexes)
{
    int[] result = new int[indexes.Length];
    for (int i = 0; i < indexes.Length; i++)
        result[i] = -1;

    int ptr = 0;
    while (ptr >= 0)
    {
        while (ptr < indexes.Length)
        {
            result[ptr++]++;
            continue;
        }

        ptr--;
        while (result[ptr] < indexes[ptr])
        {
            //HERE
            //Clones the array so you are returning a new array - thanks Jon, for improvement on the Array.Copy code.
            yield return result.ToArray();

            result[ptr]++;
        }

        result[ptr]--;
        while (result[ptr] == indexes[ptr] - 1)
        {
            result[ptr] = -1;
            ptr--;
            if (ptr < 0)
                break;
        }
    }
}

【讨论】:

  • +1 表示“也许您需要更好的里程计方法”。该错误可能在该类中。
【解决方案2】:

正如 lasseespeholt 所说,问题在于您反复产生对同一个数组的引用,然后对该数组进行变异。

调用ToList() 将遍历整个迭代器,因此您将多次获得相同引用的列表,而当您检查内容时,它将全部为-1。

一个简单的解决方法是将 Odometer 的 yield return 语句更改为:

yield return result.ToArray();

这将在您产生它的位置克隆数组,因此列表将具有不同的数组引用。

【讨论】:

  • 嗨,乔恩,感谢您抽出宝贵时间和明确的回答:现在您已经说过了,这很明显。一个愚蠢的参考错误。 Philippe PS:我有你的书“C# id Depth”。太好了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多