From da5740082ef7c39a5503e4d57ee73e11d3b43586 Mon Sep 17 00:00:00 2001 From: Tristan Pratt Date: Sat, 17 Aug 2019 19:12:00 -0400 Subject: [PATCH] c# feature(RepeatedField): Capacity property to resize the internal array This property is useful for efficiently initializing long RepeatedFields. I used System.Collections.Generic.List's design as a model. It should feel the most familiar to C# developers. --- .../Collections/RepeatedFieldTest.cs | 48 +++++++++++++++++++ .../Collections/RepeatedField.cs | 36 +++++++++++++- 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/csharp/src/Google.Protobuf.Test/Collections/RepeatedFieldTest.cs b/csharp/src/Google.Protobuf.Test/Collections/RepeatedFieldTest.cs index b3863a4965a1..527c7d404c46 100644 --- a/csharp/src/Google.Protobuf.Test/Collections/RepeatedFieldTest.cs +++ b/csharp/src/Google.Protobuf.Test/Collections/RepeatedFieldTest.cs @@ -33,6 +33,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Text; @@ -756,5 +757,52 @@ public void NaNValuesComparedBitwise() Assert.True(list1.Contains(SampleNaNs.SignallingFlipped)); Assert.False(list2.Contains(SampleNaNs.SignallingFlipped)); } + + [Test] + public void Capacity_Increase() + { + // Unfortunately this case tests implementation details of RepeatedField. This is necessary + + var list = new RepeatedField() { 1, 2, 3 }; + + Assert.AreEqual(8, list.Capacity); + Assert.AreEqual(3, list.Count); + + list.Capacity = 10; // Set capacity to a larger value to trigger growth + Assert.AreEqual(10, list.Capacity, "Capacity increased"); + Assert.AreEqual(3, list.Count); + + CollectionAssert.AreEqual(new int[] {1, 2, 3}, list.ToArray(), "We didn't lose our data in the resize"); + } + + [Test] + public void Capacity_Decrease() + { + var list = new RepeatedField() { 1, 2, 3 }; + + Assert.AreEqual(8, list.Capacity); + Assert.DoesNotThrow(() => list.Capacity = 5, "Can decrease capacity if new capacity is greater than list.Count"); + Assert.AreEqual(5, list.Capacity); + + Assert.DoesNotThrow(() => list.Capacity = 3, "Can set capacity exactly to list.Count" ); + + Assert.Throws(() => list.Capacity = 2, "Can't set the capacity smaller than list.Count" ); + + Assert.Throws(() => list.Capacity = 0, "Can't set the capacity to zero" ); + + Assert.Throws(() => list.Capacity = -1, "Can't set the capacity to negative" ); + } + + [Test] + public void Capacity_Zero() + { + var list = new RepeatedField() { 1 }; + list.RemoveAt(0); + Assert.AreEqual(0, list.Count); + Assert.AreEqual(8, list.Capacity); + + Assert.DoesNotThrow(() => list.Capacity = 0, "Can set Capacity to 0"); + Assert.AreEqual(0, list.Capacity); + } } } diff --git a/csharp/src/Google.Protobuf/Collections/RepeatedField.cs b/csharp/src/Google.Protobuf/Collections/RepeatedField.cs index 827bc711832b..9f239d91c79b 100644 --- a/csharp/src/Google.Protobuf/Collections/RepeatedField.cs +++ b/csharp/src/Google.Protobuf/Collections/RepeatedField.cs @@ -216,14 +216,46 @@ public void WriteTo(CodedOutputStream output, FieldCodec codec) } } + /// + /// Gets and sets the capacity of the RepeatedField's internal array. WHen set, the internal array is reallocated to the given capacity. + /// The new value is less than Count -or- when Count is less than 0. + /// + public int Capacity + { + get { return array.Length; } + set + { + if (value < count) + { + throw new ArgumentOutOfRangeException("Capacity", value, + $"Cannot set Capacity to a value smaller than the current item count, {count}"); + } + + if (value >= 0 && value != array.Length) + { + SetSize(value); + } + } + } + + // May increase the size of the internal array, but will never shrink it. private void EnsureSize(int size) { if (array.Length < size) { size = Math.Max(size, MinArraySize); int newSize = Math.Max(array.Length * 2, size); - var tmp = new T[newSize]; - Array.Copy(array, 0, tmp, 0, array.Length); + SetSize(newSize); + } + } + + // Sets the internal array to an exact size. + private void SetSize(int size) + { + if (size != array.Length) + { + var tmp = new T[size]; + Array.Copy(array, 0, tmp, 0, count); array = tmp; } }