我面临着区别的行为迭代之间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;
}
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,这些方法之间的唯一区别是第一个迭代List点,而Kill_DoesNotWork迭代遍历IEnumerable<Point>.但是,最后一种方法有时会跳过元素(Ideone示例).
有完整的代码(我很抱歉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());
}
}
}
Run Code Online (Sandbox Code Playgroud)
由于这是压缩代码,因此大多数功能并不完全符合它们的要求.如果你想更多地削减它,你可以使用这个要点来共享你的代码(要点单元测试).
我在.NET Framework v4.5.51650中使用MSVS 2013(12.0.30110.00 Update 1)
调用ToList()将在查看该时间点时实现所得到的项目选择.迭代一个IEnumerable将评估为每个项目给出的表达式并逐个产生它们,因此现实可以在迭代之间发生变化.实际上,由于您在迭代之间更改项的属性,因此很可能发生这种情况.
在迭代的主体中,您设置
CellsWithShips[point.X, point.Y] = false;
Run Code Online (Sandbox Code Playgroud)
在选择方法时,您将查询
things.Where(p => CellsWithShips[p.X, p.Y]);
Run Code Online (Sandbox Code Playgroud)
这意味着此类查询的固有动态结果将发生变化,因为您已将其中一些结果设置为false.但这只是因为它根据需要逐个评估每个项目.这称为延迟执行,通常用于优化大型查询或长时间运行的动态大小的操作.