|
|
|
@ -42,14 +42,14 @@ namespace Google.Protobuf.Collections |
|
|
|
|
/// <summary> |
|
|
|
|
/// Representation of a map field in a Protocol Buffer message. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <typeparam name="TKey">Key type in the map. Must be a type supported by Protocol Buffer map keys.</typeparam> |
|
|
|
|
/// <typeparam name="TValue">Value type in the map. Must be a type supported by Protocol Buffers.</typeparam> |
|
|
|
|
/// <remarks> |
|
|
|
|
/// This implementation preserves insertion order for simplicity of testing |
|
|
|
|
/// code using maps fields. Overwriting an existing entry does not change the |
|
|
|
|
/// position of that entry within the map. Equality is not order-sensitive. |
|
|
|
|
/// For string keys, the equality comparison is provided by <see cref="StringComparer.Ordinal"/>. |
|
|
|
|
/// For string keys, the equality comparison is provided by <see cref="StringComparer.Ordinal" />. |
|
|
|
|
/// </remarks> |
|
|
|
|
/// <typeparam name="TKey">Key type in the map. Must be a type supported by Protocol Buffer map keys.</typeparam> |
|
|
|
|
/// <typeparam name="TValue">Value type in the map. Must be a type supported by Protocol Buffers.</typeparam> |
|
|
|
|
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.) |
|
|
|
@ -81,6 +81,12 @@ namespace Google.Protobuf.Collections |
|
|
|
|
this.allowNullValues = allowNullValues; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Creates a deep clone of this object. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <returns> |
|
|
|
|
/// A deep clone of this object. |
|
|
|
|
/// </returns> |
|
|
|
|
public MapField<TKey, TValue> Clone() |
|
|
|
|
{ |
|
|
|
|
var clone = new MapField<TKey, TValue>(allowNullValues); |
|
|
|
@ -100,6 +106,15 @@ namespace Google.Protobuf.Collections |
|
|
|
|
return clone; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Adds the specified key/value pair to the map. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <remarks> |
|
|
|
|
/// This operation fails if the key already exists in the map. To replace an existing entry, use the indexer. |
|
|
|
|
/// </remarks> |
|
|
|
|
/// <param name="key">The key to add</param> |
|
|
|
|
/// <param name="value">The value to add.</param> |
|
|
|
|
/// <exception cref="System.ArgumentException">The given key already exists in map.</exception> |
|
|
|
|
public void Add(TKey key, TValue value) |
|
|
|
|
{ |
|
|
|
|
// Validation of arguments happens in ContainsKey and the indexer |
|
|
|
@ -110,12 +125,22 @@ namespace Google.Protobuf.Collections |
|
|
|
|
this[key] = value; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Determines whether the specified key is present in the map. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <param name="key">The key to check.</param> |
|
|
|
|
/// <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"); |
|
|
|
|
return map.ContainsKey(key); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Removes the entry identified by the given key from the map. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <param name="key">The key indicating the entry to remove from the map.</param> |
|
|
|
|
/// <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"); |
|
|
|
@ -132,6 +157,14 @@ namespace Google.Protobuf.Collections |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Gets the value associated with the specified key. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <param name="key">The key whose value to get.</param> |
|
|
|
|
/// <param name="value">When this method returns, the value associated with the specified key, if the key is found; |
|
|
|
|
/// otherwise, the default value for the type of the <paramref name="value"/> parameter. |
|
|
|
|
/// This parameter is passed uninitialized.</param> |
|
|
|
|
/// <returns><c>true</c> if the map contains an element with the specified key; otherwise, <c>false</c>.</returns> |
|
|
|
|
public bool TryGetValue(TKey key, out TValue value) |
|
|
|
|
{ |
|
|
|
|
LinkedListNode<KeyValuePair<TKey, TValue>> node; |
|
|
|
@ -147,6 +180,13 @@ namespace Google.Protobuf.Collections |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Gets or sets the value associated with the specified key. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <param name="key">The key of the value to get or set.</param> |
|
|
|
|
/// <exception cref="KeyNotFoundException">The property is retrieved and key does not exist in the collection.</exception> |
|
|
|
|
/// <returns>The value associated with the specified key. If the specified key is not found, |
|
|
|
|
/// a get operation throws a <see cref="KeyNotFoundException"/>, and a set operation creates a new element with the specified key.</returns> |
|
|
|
|
public TValue this[TKey key] |
|
|
|
|
{ |
|
|
|
|
get |
|
|
|
@ -182,9 +222,21 @@ namespace Google.Protobuf.Collections |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// TODO: Make these views? |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Gets a collection containing the keys in the map. |
|
|
|
|
/// </summary> |
|
|
|
|
public ICollection<TKey> Keys { get { return list.Select(t => t.Key).ToList(); } } |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Gets a collection containing the values in the map. |
|
|
|
|
/// </summary> |
|
|
|
|
public ICollection<TValue> Values { get { return list.Select(t => t.Value).ToList(); } } |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Adds the specified entries to the map. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <param name="entries">The entries to add to the map.</param> |
|
|
|
|
public void Add(IDictionary<TKey, TValue> entries) |
|
|
|
|
{ |
|
|
|
|
Preconditions.CheckNotNull(entries, "entries"); |
|
|
|
@ -194,27 +246,51 @@ namespace Google.Protobuf.Collections |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Returns an enumerator that iterates through the collection. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <returns> |
|
|
|
|
/// An enumerator that can be used to iterate through the collection. |
|
|
|
|
/// </returns> |
|
|
|
|
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() |
|
|
|
|
{ |
|
|
|
|
return list.GetEnumerator(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Returns an enumerator that iterates through a collection. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <returns> |
|
|
|
|
/// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection. |
|
|
|
|
/// </returns> |
|
|
|
|
IEnumerator IEnumerable.GetEnumerator() |
|
|
|
|
{ |
|
|
|
|
return GetEnumerator(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Adds the specified item to the map. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <param name="item">The item to add to the map.</param> |
|
|
|
|
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) |
|
|
|
|
{ |
|
|
|
|
Add(item.Key, item.Value); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Removes all items from the map. |
|
|
|
|
/// </summary> |
|
|
|
|
public void Clear() |
|
|
|
|
{ |
|
|
|
|
list.Clear(); |
|
|
|
|
map.Clear(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Determines whether map contains an entry equivalent to the given key/value pair. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <param name="item">The key/value pair to find.</param> |
|
|
|
|
/// <returns></returns> |
|
|
|
|
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) |
|
|
|
|
{ |
|
|
|
|
TValue value; |
|
|
|
@ -222,11 +298,22 @@ namespace Google.Protobuf.Collections |
|
|
|
|
&& EqualityComparer<TValue>.Default.Equals(item.Value, value); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Copies the key/value pairs in this map to an array. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <param name="array">The array to copy the entries into.</param> |
|
|
|
|
/// <param name="arrayIndex">The index of the array at which to start copying values.</param> |
|
|
|
|
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) |
|
|
|
|
{ |
|
|
|
|
list.CopyTo(array, arrayIndex); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Removes the specified key/value pair from the map. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <remarks>Both the key and the value must be found for the entry to be removed.</remarks> |
|
|
|
|
/// <param name="item">The key/value pair to remove.</param> |
|
|
|
|
/// <returns><c>true</c> if the key/value pair was found and removed; <c>false</c> otherwise.</returns> |
|
|
|
|
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) |
|
|
|
|
{ |
|
|
|
|
if (item.Key == null) |
|
|
|
@ -252,14 +339,34 @@ namespace Google.Protobuf.Collections |
|
|
|
|
/// </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; } } |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Gets a value indicating whether the map is read-only. |
|
|
|
|
/// </summary> |
|
|
|
|
public bool IsReadOnly { get { return false; } } |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Determines whether the specified <see cref="System.Object" />, is equal to this instance. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <param name="other">The <see cref="System.Object" /> to compare with this instance.</param> |
|
|
|
|
/// <returns> |
|
|
|
|
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>. |
|
|
|
|
/// </returns> |
|
|
|
|
public override bool Equals(object other) |
|
|
|
|
{ |
|
|
|
|
return Equals(other as MapField<TKey, TValue>); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Returns a hash code for this instance. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <returns> |
|
|
|
|
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. |
|
|
|
|
/// </returns> |
|
|
|
|
public override int GetHashCode() |
|
|
|
|
{ |
|
|
|
|
var valueComparer = EqualityComparer<TValue>.Default; |
|
|
|
@ -271,6 +378,14 @@ namespace Google.Protobuf.Collections |
|
|
|
|
return hash; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Compares this map with another for equality. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <remarks> |
|
|
|
|
/// The order of the key/value pairs in the maps is not deemed significant in this comparison. |
|
|
|
|
/// </remarks> |
|
|
|
|
/// <param name="other">The map to compare this with.</param> |
|
|
|
|
/// <returns><c>true</c> if <paramref name="other"/> refers to an equal map; <c>false</c> otherwise.</returns> |
|
|
|
|
public bool Equals(MapField<TKey, TValue> other) |
|
|
|
|
{ |
|
|
|
|
if (other == null) |
|
|
|
@ -322,6 +437,12 @@ namespace Google.Protobuf.Collections |
|
|
|
|
} while (input.MaybeConsumeTag(codec.MapTag)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Writes the contents of this map to the given coded output stream, using the specified codec |
|
|
|
|
/// to encode each entry. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <param name="output">The output stream to write to.</param> |
|
|
|
|
/// <param name="codec">The codec to use for each entry.</param> |
|
|
|
|
public void WriteTo(CodedOutputStream output, Codec codec) |
|
|
|
|
{ |
|
|
|
|
var message = new Codec.MessageAdapter(codec); |
|
|
|
@ -334,6 +455,11 @@ namespace Google.Protobuf.Collections |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Calculates the size of this map based on the given entry codec. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <param name="codec">The codec to use to encode each entry.</param> |
|
|
|
|
/// <returns></returns> |
|
|
|
|
public int CalculateSize(Codec codec) |
|
|
|
|
{ |
|
|
|
|
if (Count == 0) |
|
|
|
@ -446,7 +572,7 @@ namespace Google.Protobuf.Collections |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// A codec for a specific map field. This contains all the information required to encoded and |
|
|
|
|
/// A codec for a specific map field. This contains all the information required to encode and |
|
|
|
|
/// decode the nested messages. |
|
|
|
|
/// </summary> |
|
|
|
|
public sealed class Codec |
|
|
|
@ -455,6 +581,13 @@ namespace Google.Protobuf.Collections |
|
|
|
|
private readonly FieldCodec<TValue> valueCodec; |
|
|
|
|
private readonly uint mapTag; |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Creates a new entry codec based on a separate key codec and value codec, |
|
|
|
|
/// and the tag to use for each map entry. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <param name="keyCodec">The key codec.</param> |
|
|
|
|
/// <param name="valueCodec">The value codec.</param> |
|
|
|
|
/// <param name="mapTag">The map tag to use to introduce each map entry.</param> |
|
|
|
|
public Codec(FieldCodec<TKey> keyCodec, FieldCodec<TValue> valueCodec, uint mapTag) |
|
|
|
|
{ |
|
|
|
|
this.keyCodec = keyCodec; |
|
|
|
|