Created
August 24, 2019 22:43
-
-
Save jnm2/18cde11aae7e765465f64f5e2d387616 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
/// <summary> | |
/// Enables building an array by setting indexes in any order without having to use an <c>Add</c> method. | |
/// </summary> | |
public struct ArrayBuilder<T> | |
{ | |
private T[] array; | |
private int usedLength; | |
/// <summary> | |
/// Initializes a new instance of the <see cref="ArrayBuilder{T}"/> struct. | |
/// </summary> | |
/// <param name="initialCapacity"> | |
/// If non-zero, creates a starting array at the specified size. If zero, an array is not created until the | |
/// first index is set. | |
/// </param> | |
/// <exception cref="ArgumentOutOfRangeException"> | |
/// Thrown when <paramref name="initialCapacity"/> is less than zero. | |
/// </exception> | |
public ArrayBuilder(int initialCapacity) | |
{ | |
if (initialCapacity <= 0) | |
{ | |
if (initialCapacity < 0) | |
throw new ArgumentOutOfRangeException(nameof(initialCapacity), initialCapacity, "Initial capacity must be greater than or equal to zero."); | |
array = null; | |
} | |
else | |
{ | |
array = new T[initialCapacity]; | |
} | |
usedLength = 0; | |
} | |
/// <summary> | |
/// <para> | |
/// Gets or sets a value at the specified index in the array being built. The array is expanded as necessary to | |
/// accommodate setting any index. | |
/// </para> | |
/// <para> | |
/// Getting an index past the current end does not expand the array. It returns the default value of | |
/// <typeparamref name="T"/>, which is what getting an unset index within the array would return. | |
/// </para> | |
/// </summary> | |
/// <exception cref="ArgumentOutOfRangeException"> | |
/// Thrown when <paramref name="index"/> is less than zero. | |
/// </exception> | |
public T this[int index] | |
{ | |
get | |
{ | |
if (index < 0) | |
throw new ArgumentOutOfRangeException(nameof(index), index, "Index must be greater than or equal to zero."); | |
return array != null && index < array.Length ? array[index] : default; | |
} | |
set | |
{ | |
if (index < 0) | |
throw new ArgumentOutOfRangeException(nameof(index), index, "Index must be greater than or equal to zero."); | |
var requiredLength = index + 1; | |
if (usedLength < requiredLength) | |
{ | |
usedLength = requiredLength; | |
CommonUtils.EnsureCapacity(ref array, requiredLength); | |
} | |
array[index] = value; | |
} | |
} | |
/// <summary> | |
/// Obtains the built result in <see cref="ArraySegment{T}"/> form and clears the builder. | |
/// </summary> | |
public ArraySegment<T> MoveToArraySegment() | |
{ | |
var segment = new ArraySegment<T>(array ?? Array.Empty<T>(), offset: 0, usedLength); | |
this = default; | |
return segment; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Runtime.CompilerServices; | |
using NUnit.Framework; | |
using Shouldly; | |
public static class ArrayBuilderTests | |
{ | |
[Test] | |
public static void Initial_capacity_must_not_be_negative() | |
{ | |
var ex = Should.Throw<ArgumentOutOfRangeException>(() => new ArrayBuilder<int>(initialCapacity: -1)); | |
ex.ParamName.ShouldBe("initialCapacity"); | |
ex.ActualValue.ShouldBe(-1); | |
} | |
[Test] | |
public static void Initial_capacity_of_zero_is_identical_to_default_value() | |
{ | |
RuntimeHelpers.Equals(new ArrayBuilder<int>(initialCapacity: 0), default(ArrayBuilder<int>)).ShouldBeTrue(); | |
} | |
[Test] | |
public static void Default_instance_returns_valid_segment() | |
{ | |
default(ArrayBuilder<int>).MoveToArraySegment().Array.ShouldBeSameAs(Array.Empty<int>()); | |
} | |
[Test] | |
public static void Default_instance_allows_reads_at_any_nonnegative_index([Values(0, 1, int.MaxValue)] int index) | |
{ | |
default(ArrayBuilder<int>)[index].ShouldBe(default); | |
} | |
[Test] | |
public static void Default_instance_allows_writes_at_any_nonnegative_index([Values(0, 1, 10_000)] int index) | |
{ | |
var builder = default(ArrayBuilder<int>); | |
builder[index] = 42; | |
builder[index].ShouldBe(42); | |
builder.MoveToArraySegment().Array[index].ShouldBe(42); | |
} | |
[Test] | |
public static void Reads_are_not_allowed_at_negative_indexes() | |
{ | |
var builder = new ArrayBuilder<int>(); | |
var ex = Should.Throw<ArgumentOutOfRangeException>(() => _ = builder[-1]); | |
ex.ParamName.ShouldBe("index"); | |
ex.ActualValue.ShouldBe(-1); | |
} | |
[Test] | |
public static void Writes_are_not_allowed_at_negative_indexes() | |
{ | |
var builder = new ArrayBuilder<int>(); | |
var ex = Should.Throw<ArgumentOutOfRangeException>(() => builder[-1] = 42); | |
ex.ParamName.ShouldBe("index"); | |
ex.ActualValue.ShouldBe(-1); | |
} | |
[Test] | |
public static void Segment_length_is_less_than_array_length_when_not_all_capacity_is_used() | |
{ | |
var builder = new ArrayBuilder<int>(initialCapacity: 10) | |
{ | |
[8] = 42 | |
}; | |
var segment = builder.MoveToArraySegment(); | |
segment.Array.Length.ShouldBe(10); | |
segment.Count.ShouldBe(9); | |
} | |
[Test] | |
public static void Writes_past_initial_capacity_doubles_the_current_capacity() | |
{ | |
var builder = new ArrayBuilder<int>(initialCapacity: 10) | |
{ | |
[10] = 42 | |
}; | |
builder.MoveToArraySegment().Array.Length.ShouldBe(20); | |
} | |
[Test] | |
public static void MoveToArraySegment_clears_the_builder() | |
{ | |
var builder = new ArrayBuilder<int> { [0] = 42 }; | |
_ = builder.MoveToArraySegment(); | |
builder[0].ShouldBe(default); | |
builder.MoveToArraySegment().Array.ShouldBeSameAs(Array.Empty<int>()); | |
} | |
[Test] | |
public static void Changes_to_builder_do_not_affect_build_segment() | |
{ | |
var builder = new ArrayBuilder<int> { [0] = 42 }; | |
var segment = builder.MoveToArraySegment(); | |
builder[0] = 41; | |
segment.Array[0].ShouldBe(42); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment