List <IEnumerator> .All(e => e.MoveNext())不会移动我的枚举器

Jon*_*nny 17 c# linq enumerator

我正在尝试追踪代码中的错误.我把它煮成下面的片段.在下面的示例中,我有一个int网格(行列表),但我想找到具有1的列的索引.这个的实现是为每一行创建一个枚举器并逐步遍历每一列转而将调查员保持在一步.

class Program
{
    static void Main(string[] args)
    {
        var ints = new List<List<int>> {
            new List<int> {0, 0, 1},    // This row has a 1 at index 2
            new List<int> {0, 1, 0},    // This row has a 1 at index 1
            new List<int> {0, 0, 1}     // This row also has a 1 at index 2
        };
        var result = IndexesWhereThereIsOneInTheColumn(ints);

        Console.WriteLine(string.Join(", ", result)); // Expected: "1, 2"
        Console.ReadKey();
    }


    private static IEnumerable<int> IndexesWhereThereIsOneInTheColumn(
        IEnumerable<List<int>> myIntsGrid)
    {
        var enumerators = myIntsGrid.Select(c => c.GetEnumerator()).ToList();

        short i = 0;
        while (enumerators.All(e => e.MoveNext())) {
            if (enumerators.Any(e => e.Current == 1))
                yield return i;
            i++;

            if (i > 1000)
                throw new Exception("You have gone too far!!!");
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

但是我注意到MoveNext()每次while循环都不记得.MoveNext() 始终返回true,Current始终为0.这是LINQ的的有意义的特性,使其更方免费的效果?

我注意到这有效:

    private static IEnumerable<int> IndexesWhereThereIsOneInTheColumn(
        IEnumerable<List<int>> myIntsGrid)
    {
        var enumerators = myIntsGrid.Select(c => 
            c.ToArray().GetEnumerator()).ToList(); // added ToArray() 

        short i = 0;
        while (enumerators.All(e => e.MoveNext())) {
            if (enumerators.Any(e => (int)e.Current == 1)) // added cast to int
                yield return i;
            i++;
        }
    }
Run Code Online (Sandbox Code Playgroud)

那么这只是List的一个问题吗?

Sri*_*vel 17

这是因为枚举器List<T>是a struct而枚举器Array是a class.

因此,当您Enumerable.All使用结构调用时,将生成枚举数的副本并作为参数传递,Func因为结构是按值复制的.所以e.MoveNext在副本上调用,而不是原件.

试试这个:

Console.WriteLine(new List<int>().GetEnumerator().GetType().IsValueType);
Console.WriteLine(new int[]{}.GetEnumerator().GetType().IsValueType);
Run Code Online (Sandbox Code Playgroud)

它打印:

True
False
Run Code Online (Sandbox Code Playgroud)

  • @Mene:想象一下; 这是真的!它容易出错,但要记住99.9999%的枚举数只能由为`foreach`循环生成的代码看到; 创建自己的未装箱的枚举器是一种罕见的情况.设计团队认为,避免收集压力的性能胜利值得极少见,例如代码令人困惑的情况. (2认同)

Iva*_*vov 14

正如Sriram Sakthivel的回答所说,问题是由于缺乏装箱而且意外地列表枚举器实现是一个struct,而不是引用类型.通常,人们不会期望枚举器的值类型行为,因为大多数都是由IEnumerator/ IEnumerator<T>interfaces 公开的,或者是引用类型本身.解决这个问题的一个快速方法是更改​​此行

var enumerators = myIntsGrid.Select(c => c.GetEnumerator()).ToList();
Run Code Online (Sandbox Code Playgroud)

var enumerators 
    = myIntsGrid.Select(c => (IEnumerator) c.GetEnumerator()).ToList();
Run Code Online (Sandbox Code Playgroud)

代替.

上面的代码将构造一个已经装箱的枚举器列表,由于接口强制转换,它将被视为引用类型实例.从那一刻起,它们应该在您以后的代码中按照您的期望行事.


如果你需要一个通用的枚举器(为了避免在后者使用enumerator.Current属性时进行强制转换),你可以转换为适当的通用IEnumerator<T>接口:

c => (IEnumerator<int>) c.GetEnumerator()
Run Code Online (Sandbox Code Playgroud)

甚至更好

c => c.GetEnumerator() as IEnumerator<int>
Run Code Online (Sandbox Code Playgroud)

as据说该关键字比直接转换更好,并且在循环的情况下,它可以带来重要的性能优势.请小心,如果演员表失败则as返回根据Flater的评论请求:.在OP的情况下,保证枚举器实现,因此可以安全地进行演员表演.nullIEnumerator<int>as

  • 挑剔:使用`(IEnumerator <int>)`而不是`(IEnumerator)`所以你不必在`while`循环中强制转换`e.Current`. (2认同)