C# 列表<;IEnumerator>;。All(e=>;e.MoveNext())不';别把我的统计员挪动

C# 列表<;IEnumerator>;。All(e=>;e.MoveNext())不';别把我的统计员挪动,c#,linq,enumerator,C#,Linq,Enumerator,我正在努力追踪代码中的一个bug。我把它归结为下面的片段。在下面的示例中,我有一个整数网格(行列表),但我想找到具有1的列的索引。其实现是通过使枚举数保持同步,为每一行和每一列依次创建一个枚举数 class Program { static void Main(string[] args) { var ints = new List<List<int>> { new List<int> {0, 0, 1}

我正在努力追踪代码中的一个bug。我把它归结为下面的片段。在下面的示例中,我有一个整数网格(行列表),但我想找到具有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!!!");
        }
    }

}

所以这只是列表的问题吗?

或者,您可以使用lambda扩展来完成

var ids = Enumerable.Range(0,ints.Max (row => row.Count)).
      Where(col => ints.Any(row => (row.Count>col)? row[col] == (1) : false));


这是因为
列表
的枚举数是
结构
,而
数组
的枚举数是

因此,当您使用结构调用
Enumerable.All
时,将创建枚举数的副本,并将其作为参数传递给
Func
,因为结构是按值复制的。因此,对副本而不是原始文件调用
e.MoveNext

试试这个:

Console.WriteLine(new List<int>().GetEnumerator().GetType().IsValueType);
Console.WriteLine(new int[]{}.GetEnumerator().GetType().IsValueType);
如前所述,问题是由于缺少装箱,并且意外地列表枚举器实现是一个
结构
,而不是引用类型。通常,不会期望枚举数的值类型行为,因为大多数是由
IEnumerator
/
IEnumerator
接口公开的,或者是引用类型本身。一个快速的方法是改变这条线

var enumerators = myIntsGrid.Select(c => c.GetEnumerator()).ToList();

相反

上面的代码将构造一个已经装箱的枚举数列表,由于接口转换,这些枚举数将被视为引用类型实例。从那一刻起,它们的行为应该与您在以后的代码中所期望的一样


如果需要泛型枚举器(以避免后者使用
枚举器.Current
属性时强制转换),可以强制转换到相应的泛型
IEnumerator
界面:

c => (IEnumerator<int>) c.GetEnumerator()
c=>(IEnumerator)c.GetEnumerator()
甚至更好

c => c.GetEnumerator() as IEnumerator<int>
c=>c.GetEnumerator()作为IEnumerator

据说,
as
关键字的性能比直接强制转换好得多,在循环的情况下,它可以带来基本的性能优势。请注意,如果根据以下注释进行的强制转换失败,
as
将返回
null
。在OP的情况下,可以保证枚举器实现
IEnumerator
,因此可以安全地进行
as
转换。

为什么不能像这样获取这些索引:

var result = ints.Select (i => i.IndexOf(1)).Distinct().OrderBy(i => i);

似乎要容易得多…

下面是一个使用循环和
yield
的简单实现:

private static IEnumerable<int> IndexesWhereThereIsOneInTheColumn(
    IEnumerable<List<int>> myIntsGrid)
{
    for (int i=0; myIntsGrid.Max(l=>l.Count) > i;i++)
    {
        foreach(var row in myIntsGrid)
        {
            if (row.Count > i && row[i] == 1)
            {
                yield return i;
                break;
            }
        }
    }       
}

为了好玩,这里有一个简洁的LINQ查询,它不会在代码中造成难以跟踪的副作用:

IEnumerable<int> IndexesWhereThereIsOneInTheColumn(IEnumerable<IEnumerable<int>> myIntsGrid)
{
    return myIntsGrid
        // Collapse the rows into a single row of the maximum value of all rows
        .Aggregate((acc, x) => acc.Zip(x, Math.Max))
        // Enumerate the row
        .Select((Value,Index) => new { Value, Index })
        .Where(x => x.Value == 1)
        .Select(x => x.Index);
}
IEnumerable Indexes(IEnumerable myIntsGrid)不在列中
{
返回myIntsGrid
//将行折叠为所有行的最大值的单行
.Aggregate((acc,x)=>acc.Zip(x,数学最大值))
//列举该行
.Select((值,索引)=>new{Value,Index})
.其中(x=>x.值==1)
.选择(x=>x.Index);
}

为什么不简单地使用
foreach
循环?@Rik使用
foreach
我找不到一个好看的实现。我的第一次尝试中有两个
break
s,我认为Linq实现更干净。传递给Linq的函数不应该有副作用。令人发狂!我只是花了一个小时在这个问题上,试图说服自己我不是疯了…这对这个例子来说是可行的,但我想减少我发布的代码量。我的实际错误更复杂,但我发布的代码说明了问题的症结所在。它返回包含1的行的索引,我需要列。啊哈,我误解了。编辑过。挑剔:使用
(IEnumerator)
而不是
(IEnumerator)
,这样你就不必在
循环时在
中施放
e.Current
。@DominicKexel,很好,我会把它作为一个编辑。谢谢,这不是因为拳击。OP的代码变异了一个副本,但不是盒装副本。@IvayloSlavov:可能需要注意的是,
foo as Bar
在强制转换失败时返回null,而
(Bar)foo
抛出异常。它们在各自的方面都很有用,这取决于你想如何处理错误的铸造:)@IvayloSlavov:是的,我认为这与本案无关。但是有人可以从你的帖子中得出结论,fooasbar总是更好。我提到的是那些人:)这不是因为拳击。OP的代码变异了一个副本,但不是一个盒装副本。@CodesIn混乱很好,修改了我的答案。看看这是否有意义:)这是我读这个问题时的第一个想法,但我无法想象枚举数有时会被实现为值类型。很容易出错。@Mene:想象一下;这是真的!它容易出错,但请记住,99.9999%的时间只有为
foreach
循环生成的代码才能看到枚举数;创建自己的未绑定枚举数是一种罕见的情况。设计团队认为,避免收集压力的性能胜利是值得的,这种情况非常罕见,代码混乱。解决了发布(减少)问题,但我并没有在网格中实际使用ints来解决真正的问题。但是,我可能可以创建一个聚合器,将我想要的值冒泡到顶部。当然,您只需要将
Math.Max
更改为一个函数,该函数可以拾取您选择的值,然后显然还需要
Where
操作符
c => c.GetEnumerator() as IEnumerator<int>
var result = ints.Select (i => i.IndexOf(1)).Distinct().OrderBy(i => i);
private static IEnumerable<int> IndexesWhereThereIsOneInTheColumn(
    IEnumerable<List<int>> myIntsGrid)
{
    for (int i=0; myIntsGrid.Max(l=>l.Count) > i;i++)
    {
        foreach(var row in myIntsGrid)
        {
            if (row.Count > i && row[i] == 1)
            {
                yield return i;
                break;
            }
        }
    }       
}
if (myIntsGrid.Any(row => row.Count > i && row[i] == 1)) yield return i;
IEnumerable<int> IndexesWhereThereIsOneInTheColumn(IEnumerable<IEnumerable<int>> myIntsGrid)
{
    return myIntsGrid
        // Collapse the rows into a single row of the maximum value of all rows
        .Aggregate((acc, x) => acc.Zip(x, Math.Max))
        // Enumerate the row
        .Select((Value,Index) => new { Value, Index })
        .Where(x => x.Value == 1)
        .Select(x => x.Index);
}