diff options
Diffstat (limited to 'csharp/src/ProtocolBuffers')
5 files changed, 166 insertions, 31 deletions
diff --git a/csharp/src/ProtocolBuffers/CodedOutputStream.ComputeSize.cs b/csharp/src/ProtocolBuffers/CodedOutputStream.ComputeSize.cs index a3e7c1bb..bf221c9c 100644 --- a/csharp/src/ProtocolBuffers/CodedOutputStream.ComputeSize.cs +++ b/csharp/src/ProtocolBuffers/CodedOutputStream.ComputeSize.cs @@ -129,8 +129,7 @@ namespace Google.Protobuf public static int ComputeStringSize(String value)
{
int byteArraySize = Utf8Encoding.GetByteCount(value);
- return ComputeRawVarint32Size((uint) byteArraySize) +
- byteArraySize;
+ return ComputeLengthSize(byteArraySize) + byteArraySize;
}
/// <summary>
@@ -149,7 +148,7 @@ namespace Google.Protobuf public static int ComputeMessageSize(IMessage value)
{
int size = value.CalculateSize();
- return ComputeRawVarint32Size((uint) size) + size;
+ return ComputeLengthSize(size) + size;
}
/// <summary>
@@ -158,8 +157,7 @@ namespace Google.Protobuf /// </summary>
public static int ComputeBytesSize(ByteString value)
{
- return ComputeRawVarint32Size((uint) value.Length) +
- value.Length;
+ return ComputeLengthSize(value.Length) + value.Length;
}
/// <summary>
diff --git a/csharp/src/ProtocolBuffers/CodedOutputStream.cs b/csharp/src/ProtocolBuffers/CodedOutputStream.cs index 53f04c77..b91d6d70 100644 --- a/csharp/src/ProtocolBuffers/CodedOutputStream.cs +++ b/csharp/src/ProtocolBuffers/CodedOutputStream.cs @@ -274,7 +274,7 @@ namespace Google.Protobuf /// <param name="value">The value to write</param>
public void WriteMessage(IMessage value)
{
- WriteRawVarint32((uint) value.CalculateSize());
+ WriteLength(value.CalculateSize());
value.WriteTo(this);
}
@@ -285,7 +285,7 @@ namespace Google.Protobuf /// <param name="value">The value to write</param>
public void WriteBytes(ByteString value)
{
- WriteRawVarint32((uint) value.Length);
+ WriteLength(value.Length);
value.WriteRawBytesTo(this);
}
diff --git a/csharp/src/ProtocolBuffers/Collections/MapField.cs b/csharp/src/ProtocolBuffers/Collections/MapField.cs index 0f379eaa..ead45737 100644 --- a/csharp/src/ProtocolBuffers/Collections/MapField.cs +++ b/csharp/src/ProtocolBuffers/Collections/MapField.cs @@ -51,14 +51,38 @@ namespace Google.Protobuf.Collections public sealed class MapField<TKey, TValue> : IDeepCloneable<MapField<TKey, TValue>>, IFreezable, IDictionary<TKey, TValue>, IEquatable<MapField<TKey, TValue>>, IDictionary { // TODO: Don't create the map/list until we have an entry. (Assume many maps will be empty.) + private bool allowNullValues; private bool frozen; private readonly Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> map = new Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>(); private readonly LinkedList<KeyValuePair<TKey, TValue>> list = new LinkedList<KeyValuePair<TKey, TValue>>(); + /// <summary> + /// Constructs a new map field, defaulting the value nullability to only allow null values for message types + /// and non-nullable value types. + /// </summary> + public MapField() : this(typeof(IMessage).IsAssignableFrom(typeof(TValue)) || Nullable.GetUnderlyingType(typeof(TValue)) != null) + { + } + + /// <summary> + /// Constructs a new map field, overriding the choice of whether null values are permitted in the map. + /// This is used by wrapper types, where maps with string and bytes wrappers as the value types + /// support null values. + /// </summary> + /// <param name="allowNullValues">Whether null values are permitted in the map or not.</param> + public MapField(bool allowNullValues) + { + if (allowNullValues && typeof(TValue).IsValueType && Nullable.GetUnderlyingType(typeof(TValue)) == null) + { + throw new ArgumentException("allowNullValues", "Non-nullable value types do not support null values"); + } + this.allowNullValues = allowNullValues; + } + public MapField<TKey, TValue> Clone() { - var clone = new MapField<TKey, TValue>(); + var clone = new MapField<TKey, TValue>(allowNullValues); // Keys are never cloneable. Values might be. if (typeof(IDeepCloneable<TValue>).IsAssignableFrom(typeof(TValue))) { @@ -138,7 +162,8 @@ namespace Google.Protobuf.Collections set { ThrowHelper.ThrowIfNull(key, "key"); - if (value == null && (typeof(TValue) == typeof(ByteString) || typeof(TValue) == typeof(string))) + // value == null check here is redundant, but avoids boxing. + if (value == null && !allowNullValues) { ThrowHelper.ThrowIfNull(value, "value"); } @@ -225,6 +250,11 @@ namespace Google.Protobuf.Collections } } + /// <summary> + /// Returns whether or not this map allows values to be null. + /// </summary> + public bool AllowsNullValues { get { return allowNullValues; } } + public int Count { get { return list.Count; } } public bool IsReadOnly { get { return frozen; } } diff --git a/csharp/src/ProtocolBuffers/Collections/RepeatedField.cs b/csharp/src/ProtocolBuffers/Collections/RepeatedField.cs index 8375ae0b..9bab41ea 100644 --- a/csharp/src/ProtocolBuffers/Collections/RepeatedField.cs +++ b/csharp/src/ProtocolBuffers/Collections/RepeatedField.cs @@ -123,7 +123,7 @@ namespace Google.Protobuf.Collections { int dataSize = CalculatePackedDataSize(codec); return CodedOutputStream.ComputeRawVarint32Size(tag) + - CodedOutputStream.ComputeRawVarint32Size((uint)dataSize) + + CodedOutputStream.ComputeLengthSize(dataSize) + dataSize; } else diff --git a/csharp/src/ProtocolBuffers/FieldCodec.cs b/csharp/src/ProtocolBuffers/FieldCodec.cs index c72a3e7b..caf03286 100644 --- a/csharp/src/ProtocolBuffers/FieldCodec.cs +++ b/csharp/src/ProtocolBuffers/FieldCodec.cs @@ -29,7 +29,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #endregion - + using System; using System.Collections.Generic; @@ -43,7 +43,7 @@ namespace Google.Protobuf // TODO: Avoid the "dual hit" of lambda expressions: create open delegates instead. (At least test...) public static FieldCodec<string> ForString(uint tag) { - return new FieldCodec<string>(input => input.ReadString(), (output, value) => output.WriteString(value), CodedOutputStream.ComputeStringSize, tag); + return new FieldCodec<string>(input => input.ReadString(), (output, value) => output.WriteString(value), CodedOutputStream.ComputeStringSize, tag); } public static FieldCodec<ByteString> ForBytes(uint tag) @@ -131,6 +131,106 @@ namespace Google.Protobuf return new FieldCodec<T>(input => { T message = parser.CreateTemplate(); input.ReadMessage(message); return message; }, (output, value) => output.WriteMessage(value), message => CodedOutputStream.ComputeMessageSize(message), tag); } + + /// <summary> + /// Creates a codec for a wrapper type of a class - which must be string or ByteString. + /// </summary> + public static FieldCodec<T> ForClassWrapper<T>(uint tag) where T : class + { + var nestedCodec = WrapperCodecs.GetCodec<T>(); + return new FieldCodec<T>( + input => WrapperCodecs.Read<T>(input, nestedCodec), + (output, value) => WrapperCodecs.Write<T>(output, value, nestedCodec), + value => WrapperCodecs.CalculateSize<T>(value, nestedCodec), + tag, + null); // Default value for the wrapper + } + + /// <summary> + /// Creates a codec for a wrapper type of a struct - which must be Int32, Int64, UInt32, UInt64, + /// Bool, Single or Double. + /// </summary> + public static FieldCodec<T?> ForStructWrapper<T>(uint tag) where T : struct + { + var nestedCodec = WrapperCodecs.GetCodec<T>(); + return new FieldCodec<T?>( + input => WrapperCodecs.Read<T>(input, nestedCodec), + (output, value) => WrapperCodecs.Write<T>(output, value.Value, nestedCodec), + value => value == null ? 0 : WrapperCodecs.CalculateSize<T>(value.Value, nestedCodec), + tag, + null); // Default value for the wrapper + } + + // Helper code to create codecs for wrapper types. Somewhat ugly with all the + private static class WrapperCodecs + { + private static readonly Dictionary<Type, object> Codecs = new Dictionary<Type, object> + { + { typeof(bool), ForBool(WireFormat.MakeTag(1, WireFormat.WireType.Varint)) }, + { typeof(int), ForInt32(WireFormat.MakeTag(1, WireFormat.WireType.Varint)) }, + { typeof(long), ForInt64(WireFormat.MakeTag(1, WireFormat.WireType.Varint)) }, + { typeof(uint), ForUInt32(WireFormat.MakeTag(1, WireFormat.WireType.Varint)) }, + { typeof(ulong), ForUInt64(WireFormat.MakeTag(1, WireFormat.WireType.Varint)) }, + { typeof(float), ForFloat(WireFormat.MakeTag(1, WireFormat.WireType.Fixed32)) }, + { typeof(double), ForDouble(WireFormat.MakeTag(1, WireFormat.WireType.Fixed64)) }, + { typeof(string), ForString(WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited)) }, + { typeof(ByteString), ForBytes(WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited)) } + }; + + /// <summary> + /// Returns a field codec which effectively wraps a value of type T in a message. + /// + /// </summary> + internal static FieldCodec<T> GetCodec<T>() + { + object value; + if (!Codecs.TryGetValue(typeof(T), out value)) + { + throw new InvalidOperationException("Invalid type argument requested for wrapper codec: " + typeof(T)); + } + return (FieldCodec<T>) value; + } + + internal static T Read<T>(CodedInputStream input, FieldCodec<T> codec) + { + int length = input.ReadLength(); + int oldLimit = input.PushLimit(length); + + uint tag; + T value = codec.DefaultValue; + while (input.ReadTag(out tag)) + { + if (tag == 0) + { + throw InvalidProtocolBufferException.InvalidTag(); + } + if (tag == codec.Tag) + { + value = codec.Read(input); + } + if (WireFormat.IsEndGroupTag(tag)) + { + break; + } + } + input.CheckLastTagWas(0); + input.PopLimit(oldLimit); + + return value; + } + + internal static void Write<T>(CodedOutputStream output, T value, FieldCodec<T> codec) + { + output.WriteLength(codec.CalculateSizeWithTag(value)); + codec.WriteTagAndValue(output, value); + } + + internal static int CalculateSize<T>(T value, FieldCodec<T> codec) + { + int fieldLength = codec.CalculateSizeWithTag(value); + return CodedOutputStream.ComputeLengthSize(fieldLength) + fieldLength; + } + } } /// <summary> @@ -144,31 +244,19 @@ namespace Google.Protobuf /// </remarks> public sealed class FieldCodec<T> { - private static readonly Func<T, bool> IsDefault; - private static readonly T Default; + private static readonly T DefaultDefault; static FieldCodec() { if (typeof(T) == typeof(string)) { - Default = (T)(object)""; - IsDefault = CreateDefaultValueCheck<string>(x => x.Length == 0); + DefaultDefault = (T)(object)""; } else if (typeof(T) == typeof(ByteString)) { - Default = (T)(object)ByteString.Empty; - IsDefault = CreateDefaultValueCheck<ByteString>(x => x.Length == 0); - } - else if (!typeof(T).IsValueType) - { - // Default default - IsDefault = CreateDefaultValueCheck<T>(x => x == null); - } - else - { - // Default default - IsDefault = CreateDefaultValueCheck<T>(x => EqualityComparer<T>.Default.Equals(x, default(T))); + DefaultDefault = (T)(object)ByteString.Empty; } + // Otherwise it's the default value of the CLR type } private static Func<T, bool> CreateDefaultValueCheck<TTmp>(Func<TTmp, bool> check) @@ -182,18 +270,32 @@ namespace Google.Protobuf private readonly uint tag; private readonly int tagSize; private readonly int fixedSize; + // Default value for this codec. Usually the same for every instance of the same type, but + // for string/ByteString wrapper fields the codec's default value is null, whereas for + // other string/ByteString fields it's "" or ByteString.Empty. + private readonly T defaultValue; internal FieldCodec( Func<CodedInputStream, T> reader, Action<CodedOutputStream, T> writer, Func<T, int> sizeCalculator, - uint tag) + uint tag) : this(reader, writer, sizeCalculator, tag, DefaultDefault) + { + } + + internal FieldCodec( + Func<CodedInputStream, T> reader, + Action<CodedOutputStream, T> writer, + Func<T, int> sizeCalculator, + uint tag, + T defaultValue) { this.reader = reader; this.writer = writer; this.sizeCalculator = sizeCalculator; this.fixedSize = 0; this.tag = tag; + this.defaultValue = defaultValue; tagSize = CodedOutputStream.ComputeRawVarint32Size(tag); } @@ -234,7 +336,7 @@ namespace Google.Protobuf public uint Tag { get { return tag; } } - public T DefaultValue { get { return Default; } } + public T DefaultValue { get { return defaultValue; } } /// <summary> /// Write a tag and the given value, *if* the value is not the default. @@ -260,6 +362,11 @@ namespace Google.Protobuf public int CalculateSizeWithTag(T value) { return IsDefault(value) ? 0 : sizeCalculator(value) + tagSize; - } + } + + private bool IsDefault(T value) + { + return EqualityComparer<T>.Default.Equals(value, defaultValue); + } } } |