这是我的 C# 实现,它使用了(我认为)我的所有依赖项的扩展方法。
我知道这似乎需要为这么简单的事情发布很多代码,但它主要是验证代码,并且在我正在编写的应用程序中非常关键,它可以完美地使用定义明确的行为。很多很多其他方法都建立在这两个功能之上。
以这种方式迭代的性能似乎也相当不错,相对于应用于迭代值的操作而言,它从来都不是瓶颈。
Span 和 SpanRange 扩展方法
using System;
using System.Collections.Generic;
using Common.FluentValidation;
namespace Common.Extensions
{
public static partial class ExtensionMethods
{
/// <summary>
/// Gets the index for an array relative to an anchor point, seamlessly crossing array boundaries in either direction.
/// Returns calculated index value of an element within a collection as if the collection was a ring of contiguous elements (Ring Buffer).
/// </summary>
/// <param name="p_rollover">Index value after which the iterator should return back to zero.</param>
/// <param name="p_anchor">A fixed or variable position to offset the iteration from.</param>
/// <param name="p_offset">A fixed or variable position to offset from the anchor.</param>
/// <returns>calculated index value of an element within a collection as if the collection was a ring of contiguous elements (Ring Buffer).</returns>
public static int Span(this int p_rollover, int p_anchor, int p_offset)
{
// Prevent absolute value of `n` from being larger than count
int n = (p_anchor + p_offset) % p_rollover;
// If `n` is negative, then result is n less than rollover
if (n < 0)
n = n + p_rollover;
return n;
}
/// <summary>
/// Iterates over a collection from a specified start position to an inclusive end position. Iterator always increments
/// from start to end, treating the original collection as a contiguous ring of items to be projected into a new form.
/// Returns a projected collection of items that can contain all of the items from the original collection or a subset of the items.
/// The first item in the projected collection will be the item from the original collection at index position p_first.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="p_collection">The collection to project into a new form.</param>
/// <param name="p_first">Zero based index value of the first element in the projected sequence.</param>
/// <param name="p_last">Zero based index value of the last element in the projected sequence.</param>
/// <returns>a projected collection of items that can contain all of the items from the original collection or a subset of the items.
/// The first item in the projected collection will be the item from the original collection at index position p_first.</returns>
public static IEnumerable<T> SpanRange<T>(this IList<T> p_collection, int p_first, int p_last)
{
// Validate
p_collection
.CannotBeNullOrEmpty("p_collection");
p_first
.MustBeWithinRange(0, p_collection.Count - 1, "p_first");
p_last
.MustBeWithinRange(0, p_collection.Count - 1, "p_last");
// Init
int Rollover = p_collection.Count;
int Count = (p_first <= p_last) ? p_last - p_first : (Rollover - p_first) + p_last;
// Iterate
for (int i = 0; i <= Count; i++)
{
var n = Rollover.Span(p_first, i);
yield return p_collection[n];
}
}
}
}
不能为空或空的依赖
using System;
using System.Collections.Generic;
using System.Linq;
namespace Common.FluentValidation
{
public static partial class Validate
{
/// <summary>
/// Validates the passed in parameter is not null or an empty collection, throwing a detailed exception message if the test fails.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="p_parameter">Parameter to validate.</param>
/// <param name="p_name">Name of tested parameter to assist with debugging.</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static void CannotBeNullOrEmpty<T>(this ICollection<T> p_parameter, string p_name)
{
if (p_parameter == null)
throw
new
ArgumentNullException(string.Format(
"The collection \"{0}\" cannot be null.",
p_name), default(Exception));
if (p_parameter.Count <= 0)
throw
new
ArgumentOutOfRangeException(string.Format(
"The collection \"{0}\" cannot be empty.",
p_name), default(Exception));
}
/// <summary>
/// Validates the passed in parameter is not null or an empty collection, throwing a detailed exception message if the test fails.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="p_parameter">Parameter to validate.</param>
/// <param name="p_name">Name of tested parameter to assist with debugging.</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static void CannotBeNullOrEmpty<T>(this IEnumerable<T> p_parameter, string p_name)
{
if (p_parameter == null)
throw
new
ArgumentNullException(string.Format(
"The collection \"{0}\" cannot be null.",
p_name), default(Exception));
if (p_parameter.Count() <= 0)
throw
new
ArgumentOutOfRangeException(string.Format(
"The collection \"{0}\" cannot be empty.",
p_name), default(Exception));
}
/// <summary>
/// Validates the passed in parameter is not null or empty, throwing a detailed exception message if the test fails.
/// </summary>
/// <param name="p_parameter">Parameter to validate.</param>
/// <param name="p_name">Name of tested parameter to assist with debugging.</param>
/// <exception cref="ArgumentException"></exception>
public static void CannotBeNullOrEmpty(this string p_parameter, string p_name)
{
if (string.IsNullOrEmpty(p_parameter))
throw
new
ArgumentException(string.Format(
"The string \"{0}\" cannot be null or empty.",
p_name), default(Exception));
}
}
}
MustBeWithinRange 依赖
using System;
namespace Common.FluentValidation
{
public static partial class Validate
{
/// <summary>
/// Validates the passed in parameter is within a specified range of values, throwing a detailed exception message if the test fails.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="p_parameter">Parameter to validate.</param>
/// <param name="p_minInclusive">The minimum valid value of the parameter.</param>
/// <param name="p_maxInclusive">The maximum valid value of the parameter.</param>
/// <param name="p_name">Name of tested parameter to assist with debugging.</param>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static void MustBeWithinRange<T>(this IComparable<T> p_parameter, T p_minInclusive, T p_maxInclusive, string p_name)
where T : struct
{
if (p_parameter.CompareTo(p_minInclusive) == -1)
throw
new
ArgumentOutOfRangeException(string.Format(
"Parameter cannot be less than {0}, but \"{1}\" was {2}.",
p_minInclusive, p_name, p_parameter), default(Exception));
if (p_parameter.CompareTo(p_maxInclusive) == 1)
throw
new
ArgumentOutOfRangeException(string.Format(
"Parameter cannot be greater than {0}, but \"{1}\" was {2}.",
p_maxInclusive, p_name, p_parameter), default(Exception));
}
}
}
注意:包含我的单元测试依赖项的代码太多了,所以如果你有自己的类似扩展方法,希望测试用例名称的冗长可以说明问题,或者只是注释掉像我的Print()
声明这样的东西,它只是转储所有值以半智能方式到 StringBuilder。您将需要 NuGet 提供的 FluentValidation,或者只需将 ...Should().Be(...) 替换为您的标准 Assert(...) 方法。
单元测试跨度(NUnit 框架)
using System;
using System.Text;
using Common.Extensions;
using Common.FluentValidation;
using FluentAssertions;
using NUnit.Framework;
namespace UnitTests.CommonTests.Extensions
{
[TestFixture, Timeout(1000)]
public class Span_Tests
{
// Test Anchoring
[Test]
public void Span_10_anchored_at_0_with_no_offset_should_be_0_Tests()
{
10.Span(0, 0).Should().Be(0);
}
[Test]
public void Span_10_anchored_at_1_with_no_offset_should_be_1_Tests()
{
10.Span(1, 0).Should().Be(1);
}
[Test]
public void Span_10_anchored_at_negative_1_with_no_offset_should_be_9_Tests()
{
10.Span(-1, 0).Should().Be(9);
}
[Test]
public void Span_10_anchored_at_negative_10_with_no_offset_should_be_0_Tests()
{
10.Span(-10, 0).Should().Be(0);
}
// Test Offset
[Test]
public void Span_10_anchored_at_0_with_offset_of_1_should_be_1_Tests()
{
10.Span(0, 1).Should().Be(1);
}
[Test]
public void Span_10_anchored_at_0_with_offset_of_negative_1_should_be_9_Tests()
{
10.Span(0, -1).Should().Be(9);
}
[Test]
public void Span_10_anchored_at_0_with_offset_of_negative_10_should_be_0_Tests()
{
10.Span(0, -10).Should().Be(0);
}
// Test Iterations
[Test]
public void Span_array_of_10_anchored_at_0_can_walk_forward_thru_elements_twice_Tests()
{
var sb = new StringBuilder();
try
{
var SequentialData = new int[10];
for (int i = 0; i < SequentialData.Length; i++)
SequentialData[i] = i;
sb.AppendFormat("SequentialData:\r\n{0}", SequentialData.Print());
for (int i = 0; i < 20; i++)
{
var n = SequentialData.Length.Span(0, i);
SequentialData[n].Should().Be(i % 10);
}
}
catch (Exception)
{
Console.WriteLine(sb.ToString());
throw;
}
}
[Test]
public void Span_array_of_10_anchored_at_0_can_walk_backwards_thru_elements_twice_Tests()
{
var sb = new StringBuilder();
try
{
var SequentialData = new int[10];
for (int i = 0; i < SequentialData.Length; i++)
SequentialData[i] = i;
sb.AppendFormat("SequentialData:\r\n{0}", SequentialData.Print());
for (int i = 0; i > -20; i--)
{
var n = SequentialData.Length.Span(0, i);
var j = (i % 10);
switch (j)
{
case 0: SequentialData[n].Should().Be(0); break;
case -1: SequentialData[n].Should().Be(9); break;
case -2: SequentialData[n].Should().Be(8); break;
case -3: SequentialData[n].Should().Be(7); break;
case -4: SequentialData[n].Should().Be(6); break;
case -5: SequentialData[n].Should().Be(5); break;
case -6: SequentialData[n].Should().Be(4); break;
case -7: SequentialData[n].Should().Be(3); break;
case -8: SequentialData[n].Should().Be(2); break;
case -9: SequentialData[n].Should().Be(1); break;
default:
throw
j.ToString().CannotBeSwitchedToDefault("j");
}
}
}
catch (Exception)
{
Console.WriteLine(sb.ToString());
throw;
}
}
}
}
单元测试 SpanRange(NUnit 框架)
using System;
using System.Collections.Generic;
using System.Text;
using Common.Extensions;
using FluentAssertions;
using NUnit.Framework;
namespace UnitTests.CommonTests.Extensions
{
[TestFixture, Timeout(1000)]
public class SpanRange_Tests
{
[Test]
public void SpanRange_array_of_10_from_0_to_9_should_be_0_thru_9_Tests()
{
var sb = new StringBuilder();
try
{
var Expected = new List<int>() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var SequentialData = new int[10];
for (int i = 0; i < SequentialData.Length; i++)
SequentialData[i] = i;
sb.AppendFormat("SequentialData:\r\n{0}", SequentialData.Print());
sb.AppendLine();
var Range = new List<int>(SequentialData.SpanRange(0, 9));
sb.AppendFormat("Range:\r\n{0}", Range.Print());
Range.ShouldBeEquivalentTo(Expected);
}
catch (Exception)
{
Console.WriteLine(sb.ToString());
throw;
}
}
[Test]
public void SpanRange_array_of_10_from_0_to_9_should_not_throw_ArgumentOutOfRangeException_Tests()
{
foreach (var item in new int[10].SpanRange(0, 9))
{ }
}
[Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void SpanRange_array_of_10_from_0_to_10_should_throw_ArgumentOutOfRangeException_Tests()
{
foreach (var item in new int[10].SpanRange(0, 10))
{ }
}
[Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void SpanRange_array_of_10_from_negative_1_to_9_should_throw_ArgumentOutOfRangeException_Tests()
{
foreach (var item in new int[10].SpanRange(-1, 9))
{ }
}
[Test]
public void SpanRange_array_of_10_from_1_to_0_should_be_1_thru_9_then_0_Tests()
{
var sb = new StringBuilder();
try
{
var Expected = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
var SequentialData = new int[10];
for (int i = 0; i < SequentialData.Length; i++)
SequentialData[i] = i;
sb.AppendFormat("SequentialData:\r\n{0}", SequentialData.Print());
sb.AppendLine();
var Range = new List<int>(SequentialData.SpanRange(1, 0));
sb.AppendFormat("Range:\r\n{0}", Range.Print());
Range.ShouldBeEquivalentTo(Expected);
}
catch (Exception)
{
Console.WriteLine(sb.ToString());
throw;
}
}
[Test]
public void SpanRange_array_of_10_from_1_to_3_should_be_1_thru_3_Tests()
{
var sb = new StringBuilder();
try
{
var Expected = new List<int>() { 1, 2, 3 };
var SequentialData = new int[10];
for (int i = 0; i < SequentialData.Length; i++)
SequentialData[i] = i;
sb.AppendFormat("SequentialData:\r\n{0}", SequentialData.Print());
sb.AppendLine();
var Range = new List<int>(SequentialData.SpanRange(1, 3));
sb.AppendFormat("Range:\r\n{0}", Range.Print());
Range.ShouldBeEquivalentTo(Expected);
}
catch (Exception)
{
Console.WriteLine(sb.ToString());
throw;
}
}
[Test]
public void SpanRange_array_of_10_from_9_to_1_should_be_9_0_and_1_Tests()
{
var sb = new StringBuilder();
try
{
var Expected = new List<int>() { 9, 0, 1 };
var SequentialData = new int[10];
for (int i = 0; i < SequentialData.Length; i++)
SequentialData[i] = i;
sb.AppendFormat("SequentialData:\r\n{0}", SequentialData.Print());
sb.AppendLine();
var Range = new List<int>(SequentialData.SpanRange(9, 1));
sb.AppendFormat("Range:\r\n{0}", Range.Print());
Range.ShouldBeEquivalentTo(Expected);
}
catch (Exception)
{
Console.WriteLine(sb.ToString());
throw;
}
}
[Test]
public void SpanRange_array_of_5_from_1_to_4_should_be_1_thru_4_Tests()
{
var sb = new StringBuilder();
try
{
var Expected = new List<int>() { 1, 2, 3, 4 };
var SequentialData = new int[5];
for (int i = 0; i < SequentialData.Length; i++)
SequentialData[i] = i;
sb.AppendFormat("SequentialData:\r\n{0}", SequentialData.Print());
sb.AppendLine();
var Range = new List<int>(SequentialData.SpanRange(1, 4));
sb.AppendFormat("Range:\r\n{0}", Range.Print());
Range.ShouldBeEquivalentTo(Expected);
}
catch (Exception)
{
Console.WriteLine(sb.ToString());
throw;
}
}
[Test]
public void SpanRange_array_of_5_from_0_to_0_should_be_0_Tests()
{
var sb = new StringBuilder();
try
{
var Expected = new List<int>() { 0 };
var SequentialData = new int[5];
for (int i = 0; i < SequentialData.Length; i++)
SequentialData[i] = i;
sb.AppendFormat("SequentialData:\r\n{0}", SequentialData.Print());
sb.AppendLine();
var Range = new List<int>(SequentialData.SpanRange(0, 0));
sb.AppendFormat("Range:\r\n{0}", Range.Print());
Range.ShouldBeEquivalentTo(Expected);
}
catch (Exception)
{
Console.WriteLine(sb.ToString());
throw;
}
}
[Test]
public void SpanRange_array_of_5_from_1_to_1_should_be_1_Tests()
{
var sb = new StringBuilder();
try
{
var Expected = new List<int>() { 1 };
var SequentialData = new int[5];
for (int i = 0; i < SequentialData.Length; i++)
SequentialData[i] = i;
sb.AppendFormat("SequentialData:\r\n{0}", SequentialData.Print());
sb.AppendLine();
var Range = new List<int>(SequentialData.SpanRange(1, 1));
sb.AppendFormat("Range:\r\n{0}", Range.Print());
Range.ShouldBeEquivalentTo(Expected);
}
catch (Exception)
{
Console.WriteLine(sb.ToString());
throw;
}
}
[Test]
public void SpanRange_array_of_5_from_4_to_0_should_be_4_then_0_Tests()
{
var sb = new StringBuilder();
try
{
var Expected = new List<int>() { 4, 0 };
var SequentialData = new int[5];
for (int i = 0; i < SequentialData.Length; i++)
SequentialData[i] = i;
sb.AppendFormat("SequentialData:\r\n{0}", SequentialData.Print());
sb.AppendLine();
var Range = new List<int>(SequentialData.SpanRange(4, 0));
sb.AppendFormat("Range:\r\n{0}", Range.Print());
Range.ShouldBeEquivalentTo(Expected);
}
catch (Exception)
{
Console.WriteLine(sb.ToString());
throw;
}
}
[Test]
public void SpanRange_array_of_5_from_1_to_0_should_be_1_thru_4_then_0_Tests()
{
var sb = new StringBuilder();
try
{
var Expected = new List<int>() { 1, 2, 3, 4, 0 };
var SequentialData = new int[5];
for (int i = 0; i < SequentialData.Length; i++)
SequentialData[i] = i;
sb.AppendFormat("SequentialData:\r\n{0}", SequentialData.Print());
sb.AppendLine();
var Range = new List<int>(SequentialData.SpanRange(1, 0));
sb.AppendFormat("Range:\r\n{0}", Range.Print());
Range.ShouldBeEquivalentTo(Expected);
}
catch (Exception)
{
Console.WriteLine(sb.ToString());
throw;
}
}
}
}