Commit 7532f025 authored by Jon Skeet's avatar Jon Skeet

Reimplement RepeatedField<T> using an array as the backing store.

This is effectively reimplementing List<T>, but with a few advantages:
- We know that an empty repeated field is common, so don't allocate an array until we need to
- With direct access to the array, we can easily convert enum values to int without boxing
- We can relax the restrictions over what happens if the repeated field is modified while iterating, avoiding so much checking

This is somewhat risky, in that reimplementing a building block like this is *always* risky, but hey...
(The performance benefits are significant...)
parent 5a33827e
...@@ -487,7 +487,7 @@ namespace Google.Protobuf ...@@ -487,7 +487,7 @@ namespace Google.Protobuf
uint tag; uint tag;
Assert.IsTrue(input.ReadTag(out tag)); Assert.IsTrue(input.ReadTag(out tag));
List<TestNegEnum> values = new List<TestNegEnum>(); RepeatedField<TestNegEnum> values = new RepeatedField<TestNegEnum>();
input.ReadEnumArray(tag, values); input.ReadEnumArray(tag, values);
Assert.AreEqual(6, values.Count); Assert.AreEqual(6, values.Count);
...@@ -511,7 +511,7 @@ namespace Google.Protobuf ...@@ -511,7 +511,7 @@ namespace Google.Protobuf
uint tag; uint tag;
Assert.IsTrue(input.ReadTag(out tag)); Assert.IsTrue(input.ReadTag(out tag));
List<TestNegEnum> values = new List<TestNegEnum>(); RepeatedField<TestNegEnum> values = new RepeatedField<TestNegEnum>();
input.ReadEnumArray(tag, values); input.ReadEnumArray(tag, values);
Assert.AreEqual(6, values.Count); Assert.AreEqual(6, values.Count);
......
...@@ -40,11 +40,12 @@ namespace Google.Protobuf ...@@ -40,11 +40,12 @@ namespace Google.Protobuf
[Test] [Test]
public void Add_RepeatedField() public void Add_RepeatedField()
{ {
var list = new RepeatedField<string>(); var list = new RepeatedField<string> { "original" };
list.Add(new RepeatedField<string> { "foo", "bar" }); list.Add(new RepeatedField<string> { "foo", "bar" });
Assert.AreEqual(2, list.Count); Assert.AreEqual(3, list.Count);
Assert.AreEqual("foo", list[0]); Assert.AreEqual("original", list[0]);
Assert.AreEqual("bar", list[1]); Assert.AreEqual("foo", list[1]);
Assert.AreEqual("bar", list[2]);
} }
} }
} }
...@@ -38,6 +38,7 @@ using System; ...@@ -38,6 +38,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using Google.Protobuf.Collections;
using Google.Protobuf.Descriptors; using Google.Protobuf.Descriptors;
namespace Google.Protobuf namespace Google.Protobuf
...@@ -700,7 +701,7 @@ namespace Google.Protobuf ...@@ -700,7 +701,7 @@ namespace Google.Protobuf
} }
} }
public void ReadEnumArray<T>(uint fieldTag, ICollection<T> list) public void ReadEnumArray<T>(uint fieldTag, RepeatedField<T> list)
where T : struct, IComparable, IFormattable where T : struct, IComparable, IFormattable
{ {
WireFormat.WireType wformat = WireFormat.GetTagWireType(fieldTag); WireFormat.WireType wformat = WireFormat.GetTagWireType(fieldTag);
...@@ -712,8 +713,8 @@ namespace Google.Protobuf ...@@ -712,8 +713,8 @@ namespace Google.Protobuf
int limit = PushLimit(length); int limit = PushLimit(length);
while (!ReachedLimit) while (!ReachedLimit)
{ {
// TODO(jonskeet): Avoid this horrible boxing! // Ghastly hack, but it works...
list.Add((T)(object) ReadEnum()); list.AddInt32(ReadEnum());
} }
PopLimit(limit); PopLimit(limit);
} }
......
...@@ -743,10 +743,11 @@ namespace Google.Protobuf ...@@ -743,10 +743,11 @@ namespace Google.Protobuf
{ {
return; return;
} }
// TODO(jonskeet): Avoid the Cast call here. Work out a better mass "T to int" conversion. // Bit of a hack, to access the values as ints
foreach (int value in list.Cast<int>()) var iterator = list.GetInt32Enumerator();
while (iterator.MoveNext())
{ {
WriteEnum(fieldNumber, value); WriteEnum(fieldNumber, iterator.Current);
} }
} }
...@@ -956,15 +957,19 @@ namespace Google.Protobuf ...@@ -956,15 +957,19 @@ namespace Google.Protobuf
{ {
return; return;
} }
// Obviously, we'll want to get rid of this hack... // Bit of a hack, to access the values as ints
var temporaryHack = new RepeatedField<int>(); var iterator = list.GetInt32Enumerator();
temporaryHack.Add(list.Cast<int>()); uint size = 0;
uint size = temporaryHack.CalculateSize(ComputeEnumSizeNoTag); while (iterator.MoveNext())
{
size += (uint) ComputeEnumSizeNoTag(iterator.Current);
}
iterator.Reset();
WriteTag(fieldNumber, WireFormat.WireType.LengthDelimited); WriteTag(fieldNumber, WireFormat.WireType.LengthDelimited);
WriteRawVarint32(size); WriteRawVarint32(size);
foreach (int value in temporaryHack) while (iterator.MoveNext())
{ {
WriteEnumNoTag(value); WriteEnumNoTag(iterator.Current);
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment