| Index: third_party/protobuf/csharp/src/Google.Protobuf/Collections/MapField.cs
|
| diff --git a/third_party/protobuf/csharp/src/Google.Protobuf/Collections/MapField.cs b/third_party/protobuf/csharp/src/Google.Protobuf/Collections/MapField.cs
|
| index c0ed28ae57def0e511e9764097825fc19b6aa596..993a89d7765f7387b38e8fbeff514c5716c4bf93 100644
|
| --- a/third_party/protobuf/csharp/src/Google.Protobuf/Collections/MapField.cs
|
| +++ b/third_party/protobuf/csharp/src/Google.Protobuf/Collections/MapField.cs
|
| @@ -34,6 +34,7 @@ using Google.Protobuf.Reflection;
|
| using System;
|
| using System.Collections;
|
| using System.Collections.Generic;
|
| +using System.IO;
|
| using System.Linq;
|
| using System.Text;
|
| using Google.Protobuf.Compatibility;
|
| @@ -53,6 +54,13 @@ namespace Google.Protobuf.Collections
|
| /// For string keys, the equality comparison is provided by <see cref="StringComparer.Ordinal" />.
|
| /// </para>
|
| /// <para>
|
| + /// Null values are not permitted in the map, either for wrapper types or regular messages.
|
| + /// If a map is deserialized from a data stream and the value is missing from an entry, a default value
|
| + /// is created instead. For primitive types, that is the regular default value (0, the empty string and so
|
| + /// on); for message types, an empty instance of the message is created, as if the map entry contained a 0-length
|
| + /// encoded value for the field.
|
| + /// </para>
|
| + /// <para>
|
| /// This implementation does not generally prohibit the use of key/value types which are not
|
| /// supported by Protocol Buffers (e.g. using a key type of <code>byte</code>) but nor does it guarantee
|
| /// that all operations will work in such cases.
|
| @@ -61,35 +69,11 @@ namespace Google.Protobuf.Collections
|
| public sealed class MapField<TKey, TValue> : IDeepCloneable<MapField<TKey, TValue>>, 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 readonly bool allowNullValues;
|
| 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;
|
| - }
|
| -
|
| - /// <summary>
|
| /// Creates a deep clone of this object.
|
| /// </summary>
|
| /// <returns>
|
| @@ -97,13 +81,13 @@ namespace Google.Protobuf.Collections
|
| /// </returns>
|
| public MapField<TKey, TValue> Clone()
|
| {
|
| - var clone = new MapField<TKey, TValue>(allowNullValues);
|
| + var clone = new MapField<TKey, TValue>();
|
| // Keys are never cloneable. Values might be.
|
| if (typeof(IDeepCloneable<TValue>).IsAssignableFrom(typeof(TValue)))
|
| {
|
| foreach (var pair in list)
|
| {
|
| - clone.Add(pair.Key, pair.Value == null ? pair.Value : ((IDeepCloneable<TValue>)pair.Value).Clone());
|
| + clone.Add(pair.Key, ((IDeepCloneable<TValue>)pair.Value).Clone());
|
| }
|
| }
|
| else
|
| @@ -140,7 +124,7 @@ namespace Google.Protobuf.Collections
|
| /// <returns><c>true</c> if the map contains the given key; <c>false</c> otherwise.</returns>
|
| public bool ContainsKey(TKey key)
|
| {
|
| - Preconditions.CheckNotNullUnconstrained(key, "key");
|
| + ProtoPreconditions.CheckNotNullUnconstrained(key, "key");
|
| return map.ContainsKey(key);
|
| }
|
|
|
| @@ -157,7 +141,7 @@ namespace Google.Protobuf.Collections
|
| /// <returns><c>true</c> if the map contained the given key before the entry was removed; <c>false</c> otherwise.</returns>
|
| public bool Remove(TKey key)
|
| {
|
| - Preconditions.CheckNotNullUnconstrained(key, "key");
|
| + ProtoPreconditions.CheckNotNullUnconstrained(key, "key");
|
| LinkedListNode<KeyValuePair<TKey, TValue>> node;
|
| if (map.TryGetValue(key, out node))
|
| {
|
| @@ -205,7 +189,7 @@ namespace Google.Protobuf.Collections
|
| {
|
| get
|
| {
|
| - Preconditions.CheckNotNullUnconstrained(key, "key");
|
| + ProtoPreconditions.CheckNotNullUnconstrained(key, "key");
|
| TValue value;
|
| if (TryGetValue(key, out value))
|
| {
|
| @@ -215,11 +199,11 @@ namespace Google.Protobuf.Collections
|
| }
|
| set
|
| {
|
| - Preconditions.CheckNotNullUnconstrained(key, "key");
|
| + ProtoPreconditions.CheckNotNullUnconstrained(key, "key");
|
| // value == null check here is redundant, but avoids boxing.
|
| - if (value == null && !allowNullValues)
|
| + if (value == null)
|
| {
|
| - Preconditions.CheckNotNullUnconstrained(value, "value");
|
| + ProtoPreconditions.CheckNotNullUnconstrained(value, "value");
|
| }
|
| LinkedListNode<KeyValuePair<TKey, TValue>> node;
|
| var pair = new KeyValuePair<TKey, TValue>(key, value);
|
| @@ -246,12 +230,12 @@ namespace Google.Protobuf.Collections
|
| public ICollection<TValue> Values { get { return new MapView<TValue>(this, pair => pair.Value, ContainsValue); } }
|
|
|
| /// <summary>
|
| - /// Adds the specified entries to the map.
|
| + /// Adds the specified entries to the map. The keys and values are not automatically cloned.
|
| /// </summary>
|
| /// <param name="entries">The entries to add to the map.</param>
|
| public void Add(IDictionary<TKey, TValue> entries)
|
| {
|
| - Preconditions.CheckNotNull(entries, "entries");
|
| + ProtoPreconditions.CheckNotNull(entries, "entries");
|
| foreach (var pair in entries)
|
| {
|
| Add(pair.Key, pair.Value);
|
| @@ -347,11 +331,6 @@ namespace Google.Protobuf.Collections
|
| }
|
|
|
| /// <summary>
|
| - /// Returns whether or not this map allows values to be null.
|
| - /// </summary>
|
| - public bool AllowsNullValues { get { return allowNullValues; } }
|
| -
|
| - /// <summary>
|
| /// Gets the number of elements contained in the map.
|
| /// </summary>
|
| public int Count { get { return list.Count; } }
|
| @@ -496,9 +475,9 @@ namespace Google.Protobuf.Collections
|
| /// </summary>
|
| public override string ToString()
|
| {
|
| - var builder = new StringBuilder();
|
| - JsonFormatter.Default.WriteDictionary(builder, this);
|
| - return builder.ToString();
|
| + var writer = new StringWriter();
|
| + JsonFormatter.Default.WriteDictionary(writer, this);
|
| + return writer.ToString();
|
| }
|
|
|
| #region IDictionary explicit interface implementation
|
| @@ -523,7 +502,7 @@ namespace Google.Protobuf.Collections
|
|
|
| void IDictionary.Remove(object key)
|
| {
|
| - Preconditions.CheckNotNull(key, "key");
|
| + ProtoPreconditions.CheckNotNull(key, "key");
|
| if (!(key is TKey))
|
| {
|
| return;
|
| @@ -552,7 +531,7 @@ namespace Google.Protobuf.Collections
|
| {
|
| get
|
| {
|
| - Preconditions.CheckNotNull(key, "key");
|
| + ProtoPreconditions.CheckNotNull(key, "key");
|
| if (!(key is TKey))
|
| {
|
| return null;
|
| @@ -632,6 +611,8 @@ namespace Google.Protobuf.Collections
|
| /// </summary>
|
| internal class MessageAdapter : IMessage
|
| {
|
| + private static readonly byte[] ZeroLengthMessageStreamData = new byte[] { 0 };
|
| +
|
| private readonly Codec codec;
|
| internal TKey Key { get; set; }
|
| internal TValue Value { get; set; }
|
| @@ -665,6 +646,13 @@ namespace Google.Protobuf.Collections
|
| input.SkipLastField();
|
| }
|
| }
|
| +
|
| + // Corner case: a map entry with a key but no value, where the value type is a message.
|
| + // Read it as if we'd seen an input stream with no data (i.e. create a "default" message).
|
| + if (Value == null)
|
| + {
|
| + Value = codec.valueCodec.Read(new CodedInputStream(ZeroLengthMessageStreamData));
|
| + }
|
| }
|
|
|
| public void WriteTo(CodedOutputStream output)
|
|
|