【发布时间】:2015-02-04 13:20:19
【问题描述】:
我遇到了迭代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)。
我正在使用带有 .NET Framework v4.5.51650 的 MSVS 2013(12.0.30110.00 更新 1)
【问题讨论】:
标签: c# ienumerable