gpt4 book ai didi

c# - 使用 foreach 遍历 IEnumerable 会跳过一些元素

转载 作者:太空宇宙 更新时间:2023-11-03 18:28:24 25 4
gpt4 key购买 nike

我遇到过遍历 enumerable 之间的行为差​​异及以上enumerable.ToList() .

public static void Kill(Point location)
{
Wound(location);
foreach(var point in GetShipPointsAndTheirNeighbors(location).ToList())
{
CellsWithShips[point.X, point.Y] = false;
}
}

/// <summary>
/// This version does not work for strange reasons, it just skips a half of points. See TestKill_DoesNotWork_1 test case
/// </summary>
/// <param name="location"></param>
public static void Kill_DoesNotWork(Point location)
{
Wound(location);
foreach(var point in GetShipPointsAndTheirNeighbors(location))
{
CellsWithShips[point.X, point.Y] = false;
}
}

如您所见,这些方法之间的唯一区别是第一个方法遍历 List。点数,而 Kill_DoesNotWork遍历 IEnumerable<Point> .但是,最后一种方法有时会跳过元素 ( Ideone example )。

有完整的代码(170行代码我很抱歉,但我不能再压缩了)

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace SampleAi
{
[DebuggerDisplay("Pont({X}, {Y})")]
public class Point
{
#region Constructors

public Point(int x, int y)
{
X = x;
Y = y;
}

#endregion // Constructors

#region Properties

public int X
{
get;
private set;
}

public int Y
{
get;
private set;
}

#endregion // Properties

#region Methods

public Point Add(Point point)
{
return new Point(X + point.X, Y + point.Y);
}

#endregion // Methods

#region Overrides of Object

/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>
/// A string that represents the current object.
/// </returns>
public override string ToString()
{
return string.Format("Point({0}, {1})", X, Y);
}

#endregion
}

public static class Map
{
#region Properties

private static bool[,] CellsWithShips
{
get;
set;
}

#endregion // Properties

#region Methods

public static IEnumerable<Point> GetAllShipPoints()
{
return Enumerable.Range(0, CellsWithShips.GetLength(0))
.SelectMany(x => Enumerable.Range(0, CellsWithShips.GetLength(1)).Select(y => new Point(x, y)))
.Where(p => CellsWithShips[p.X, p.Y]);
}

public static void Init(int width, int height)
{
CellsWithShips = new bool[width, height];
}

public static void Wound(Point location)
{
CellsWithShips[location.X, location.Y] = true;
}

public static void Kill(Point location)
{
Wound(location);
foreach(var point in GetShipPointsAndTheirNeighbors(location).ToList())
{
CellsWithShips[point.X, point.Y] = false;
}
}

/// <summary>
/// This version does not work for strange reasons, it just skips a half of points. See TestKill_DoesNotWork_1 test case
/// </summary>
/// <param name="location"></param>
public static void Kill_DoesNotWork(Point location)
{
Wound(location);
foreach(var point in GetShipPointsAndTheirNeighbors(location))
{
CellsWithShips[point.X, point.Y] = false;
}
}

private static IEnumerable<Point> GetShipPointsAndTheirNeighbors(Point location)
{
return GetShipPoints(location).SelectMany(Near);
}

private static IEnumerable<Point> Near(Point location)
{
return new[]
{
location.Add(new Point(0, -1)),
location.Add(new Point(0, 0))
};
}

private static IEnumerable<Point> GetShipPoints(Point location)
{
var beforePoint = new[]
{
location,
location.Add(new Point(0, -1)),
location.Add(new Point(0, -2)),
location.Add(new Point(0, -3))
};
return beforePoint.TakeWhile(p => CellsWithShips[p.X, p.Y]);
}

#endregion // Methods
}

public static class Program
{
private static void LoadMap()
{
Map.Init(20, 20);

Map.Wound(new Point(1, 4));
Map.Wound(new Point(1, 5));
Map.Wound(new Point(1, 6));
}

private static int TestKill()
{
LoadMap();
Map.Kill(new Point(1, 7));
return Map.GetAllShipPoints().Count();
}

private static int TestKillDoesNotWork()
{
LoadMap();
Map.Kill_DoesNotWork(new Point(1, 7));
return Map.GetAllShipPoints().Count();
}

private static void Main()
{
Console.WriteLine("Test kill: {0}", TestKill());
Console.WriteLine("Test kill (does not work): {0}", TestKillDoesNotWork());
}
}
}

由于这是压缩代码,所以大部分功能并不完全符合它们应有的功能。如果你想削减更多,你可以使用 this gist用于共享您的代码 ( gist with unit tests )。

我正在使用 MSVS 2013(12.0.30110.00 更新 1)和 .NET Framework v4.5.51650

最佳答案

调用 ToList() 将实现项目的结果选择,因为它在那个时间点查看。迭代 IEnumerable 将评估为每个项目给出的表达式并逐一生成它们,因此现实可能在迭代之间发生变化。事实上,很可能会发生这种情况,因为您在迭代之间更改了项的属性。

在你的迭代体中,你设置

CellsWithShips[point.X, point.Y] = false;

在选择你的方法时,你查询

things.Where(p => CellsWithShips[p.X, p.Y]);

这意味着此类查询的固有动态结果将发生变化,因为您已将其中一些结果设置为 false。但只是因为它会根据需要逐一评估每个项目。这称为延迟执行,最常用于优化大型查询或长时间运行的动态大小操作。

关于c# - 使用 foreach 遍历 IEnumerable 会跳过一些元素,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28322474/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com