diff --git a/GFramework.Core.Tests/Extensions/ArrayExtensionsTests.cs b/GFramework.Core.Tests/Extensions/ArrayExtensionsTests.cs new file mode 100644 index 0000000..928dff9 --- /dev/null +++ b/GFramework.Core.Tests/Extensions/ArrayExtensionsTests.cs @@ -0,0 +1,111 @@ +namespace GFramework.Core.Tests.Extensions; + +/// +/// 测试 ArrayExtensions 扩展方法的关键行为与边界语义。 +/// +[TestFixture] +public class ArrayExtensionsTests +{ + /// + /// 验证 TryGet 在坐标有效时返回 true,并输出目标位置的元素值。 + /// + [Test] + public void TryGet_Should_Return_True_And_Assign_Value_When_Coordinates_Are_In_Bounds() + { + // Arrange + var array = new[,] + { + { 1, 2 }, + { 3, 4 } + }; + + // Act + var result = array.TryGet(1, 0, out var value); + + // Assert + Assert.Multiple(() => + { + Assert.That(result, Is.True); + Assert.That(value, Is.EqualTo(3)); + }); + } + + /// + /// 验证 TryGet 在值类型越界时返回 false,并将输出值重置为该类型的默认值。 + /// + [Test] + public void TryGet_Should_Return_False_And_Default_Value_When_Value_Type_Is_Out_Of_Bounds() + { + // Arrange + var array = new[,] + { + { 1, 2 }, + { 3, 4 } + }; + + // Act + var result = array.TryGet(2, 0, out int value); + + // Assert + Assert.Multiple(() => + { + Assert.That(result, Is.False); + Assert.That(value, Is.EqualTo(default(int))); + }); + } + + /// + /// 验证 TryGet 在引用类型越界时返回 false,并将输出值设置为 null。 + /// + [Test] + public void TryGet_Should_Return_False_And_Null_When_Reference_Type_Is_Out_Of_Bounds() + { + // Arrange + var array = new string[,] + { + { "A", "B" }, + { "C", "D" } + }; + + // Act + var result = array.TryGet(-1, 0, out string? value); + + // Assert + Assert.Multiple(() => + { + Assert.That(result, Is.False); + Assert.That(value, Is.Null); + }); + } + + /// + /// 验证 Enumerate 会按第一维优先、第二维递增的顺序返回所有坐标和值。 + /// + [Test] + public void Enumerate_Should_Return_All_Coordinates_And_Values_In_Deterministic_Order() + { + // Arrange + var array = new[,] + { + { 10, 20, 30 }, + { 40, 50, 60 } + }; + + // Act + var result = array.Enumerate().ToArray(); + + // Assert + Assert.That( + result, + Is.EqualTo( + new (int x, int y, int value)[] + { + (0, 0, 10), + (0, 1, 20), + (0, 2, 30), + (1, 0, 40), + (1, 1, 50), + (1, 2, 60) + })); + } +} \ No newline at end of file diff --git a/GFramework.Core/Extensions/ArrayExtensions.cs b/GFramework.Core/Extensions/ArrayExtensions.cs index 9f26b3a..014d444 100644 --- a/GFramework.Core/Extensions/ArrayExtensions.cs +++ b/GFramework.Core/Extensions/ArrayExtensions.cs @@ -11,6 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.Diagnostics.CodeAnalysis; + namespace GFramework.Core.Extensions; /// @@ -28,9 +30,13 @@ public static class ArrayExtensions /// 如果坐标在数组边界内则返回 true;否则返回 false。 public static bool IsInBounds(this T[,] array, int x, int y) { + // 在热路径中缓存维度长度,避免每次比较都重复读取数组元数据。 + var width = array.GetLength(0); + var height = array.GetLength(1); + return x >= 0 && y >= 0 && - x < array.GetLength(0) && - y < array.GetLength(1); + x < width && + y < height; } @@ -68,9 +74,11 @@ public static class ArrayExtensions /// 要访问的二维数组。 /// X 坐标(第一维索引)。 /// Y 坐标(第二维索引)。 - /// 输出参数,用于存储获取到的元素值。 + /// + /// 输出参数,用于存储获取到的元素值;当返回 false 时,该参数会被赋值为 default(T)。 + /// /// 如果成功获取元素则返回 true;否则返回 false。 - public static bool TryGet(this T[,] array, int x, int y, out T value) + public static bool TryGet(this T[,] array, int x, int y, [MaybeNullWhen(false)] out T value) { if (array.IsInBounds(x, y)) { @@ -141,9 +149,13 @@ public static class ArrayExtensions /// 依次返回每个元素的坐标和值的元组。 public static IEnumerable<(int x, int y, T value)> Enumerate(this T[,] array) { - for (var x = 0; x < array.GetLength(0); x++) + // 缓存维度长度,确保双层循环不会在每次迭代时重复读取数组元数据。 + var width = array.GetLength(0); + var height = array.GetLength(1); + + for (var x = 0; x < width; x++) { - for (var y = 0; y < array.GetLength(1); y++) + for (var y = 0; y < height; y++) { yield return (x, y, array[x, y]); }