|  | #region Copyright notice and license | 
|  | // Protocol Buffers - Google's data interchange format | 
|  | // Copyright 2015 Google Inc.  All rights reserved. | 
|  | // https://developers.google.com/protocol-buffers/ | 
|  | // | 
|  | // Redistribution and use in source and binary forms, with or without | 
|  | // modification, are permitted provided that the following conditions are | 
|  | // met: | 
|  | // | 
|  | //     * Redistributions of source code must retain the above copyright | 
|  | // notice, this list of conditions and the following disclaimer. | 
|  | //     * Redistributions in binary form must reproduce the above | 
|  | // copyright notice, this list of conditions and the following disclaimer | 
|  | // in the documentation and/or other materials provided with the | 
|  | // distribution. | 
|  | //     * Neither the name of Google Inc. nor the names of its | 
|  | // contributors may be used to endorse or promote products derived from | 
|  | // this software without specific prior written permission. | 
|  | // | 
|  | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 
|  | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 
|  | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
|  | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 
|  | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
|  | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 
|  | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
|  | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
|  | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
|  | // (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 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; | 
|  |  | 
|  | 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> | 
|  | /// <para> | 
|  | /// 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" />. | 
|  | /// </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. | 
|  | /// </para> | 
|  | /// </remarks> | 
|  | 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 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> | 
|  | /// 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>(); | 
|  | // Keys are never cloneable. Values might be. | 
|  | if (typeof(IDeepCloneable<TValue>).IsAssignableFrom(typeof(TValue))) | 
|  | { | 
|  | foreach (var pair in list) | 
|  | { | 
|  | clone.Add(pair.Key, ((IDeepCloneable<TValue>)pair.Value).Clone()); | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | // Nothing is cloneable, so we don't need to worry. | 
|  | clone.Add(this); | 
|  | } | 
|  | 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 | 
|  | if (ContainsKey(key)) | 
|  | { | 
|  | throw new ArgumentException("Key already exists in map", "key"); | 
|  | } | 
|  | 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) | 
|  | { | 
|  | ProtoPreconditions.CheckNotNullUnconstrained(key, "key"); | 
|  | return map.ContainsKey(key); | 
|  | } | 
|  |  | 
|  | private bool ContainsValue(TValue value) | 
|  | { | 
|  | var comparer = EqualityComparer<TValue>.Default; | 
|  | return list.Any(pair => comparer.Equals(pair.Value, value)); | 
|  | } | 
|  |  | 
|  | /// <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) | 
|  | { | 
|  | ProtoPreconditions.CheckNotNullUnconstrained(key, "key"); | 
|  | LinkedListNode<KeyValuePair<TKey, TValue>> node; | 
|  | if (map.TryGetValue(key, out node)) | 
|  | { | 
|  | map.Remove(key); | 
|  | node.List.Remove(node); | 
|  | return true; | 
|  | } | 
|  | else | 
|  | { | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// <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; | 
|  | if (map.TryGetValue(key, out node)) | 
|  | { | 
|  | value = node.Value.Value; | 
|  | return true; | 
|  | } | 
|  | else | 
|  | { | 
|  | value = default(TValue); | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// <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 | 
|  | { | 
|  | ProtoPreconditions.CheckNotNullUnconstrained(key, "key"); | 
|  | TValue value; | 
|  | if (TryGetValue(key, out value)) | 
|  | { | 
|  | return value; | 
|  | } | 
|  | throw new KeyNotFoundException(); | 
|  | } | 
|  | set | 
|  | { | 
|  | ProtoPreconditions.CheckNotNullUnconstrained(key, "key"); | 
|  | // value == null check here is redundant, but avoids boxing. | 
|  | if (value == null) | 
|  | { | 
|  | ProtoPreconditions.CheckNotNullUnconstrained(value, "value"); | 
|  | } | 
|  | LinkedListNode<KeyValuePair<TKey, TValue>> node; | 
|  | var pair = new KeyValuePair<TKey, TValue>(key, value); | 
|  | if (map.TryGetValue(key, out node)) | 
|  | { | 
|  | node.Value = pair; | 
|  | } | 
|  | else | 
|  | { | 
|  | node = list.AddLast(pair); | 
|  | map[key] = node; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Gets a collection containing the keys in the map. | 
|  | /// </summary> | 
|  | public ICollection<TKey> Keys { get { return new MapView<TKey>(this, pair => pair.Key, ContainsKey); } } | 
|  |  | 
|  | /// <summary> | 
|  | /// Gets a collection containing the values in the map. | 
|  | /// </summary> | 
|  | public ICollection<TValue> Values { get { return new MapView<TValue>(this, pair => pair.Value, ContainsValue); } } | 
|  |  | 
|  | /// <summary> | 
|  | /// 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) | 
|  | { | 
|  | ProtoPreconditions.CheckNotNull(entries, "entries"); | 
|  | foreach (var pair in entries) | 
|  | { | 
|  | Add(pair.Key, pair.Value); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// <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; | 
|  | return TryGetValue(item.Key, out value) | 
|  | && 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) | 
|  | { | 
|  | throw new ArgumentException("Key is null", "item"); | 
|  | } | 
|  | LinkedListNode<KeyValuePair<TKey, TValue>> node; | 
|  | if (map.TryGetValue(item.Key, out node) && | 
|  | EqualityComparer<TValue>.Default.Equals(item.Value, node.Value.Value)) | 
|  | { | 
|  | map.Remove(item.Key); | 
|  | node.List.Remove(node); | 
|  | return true; | 
|  | } | 
|  | else | 
|  | { | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// <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; | 
|  | int hash = 0; | 
|  | foreach (var pair in list) | 
|  | { | 
|  | hash ^= pair.Key.GetHashCode() * 31 + valueComparer.GetHashCode(pair.Value); | 
|  | } | 
|  | 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) | 
|  | { | 
|  | return false; | 
|  | } | 
|  | if (other == this) | 
|  | { | 
|  | return true; | 
|  | } | 
|  | if (other.Count != this.Count) | 
|  | { | 
|  | return false; | 
|  | } | 
|  | var valueComparer = EqualityComparer<TValue>.Default; | 
|  | foreach (var pair in this) | 
|  | { | 
|  | TValue value; | 
|  | if (!other.TryGetValue(pair.Key, out value)) | 
|  | { | 
|  | return false; | 
|  | } | 
|  | if (!valueComparer.Equals(value, pair.Value)) | 
|  | { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Adds entries to the map from the given stream. | 
|  | /// </summary> | 
|  | /// <remarks> | 
|  | /// It is assumed that the stream is initially positioned after the tag specified by the codec. | 
|  | /// This method will continue reading entries from the stream until the end is reached, or | 
|  | /// a different tag is encountered. | 
|  | /// </remarks> | 
|  | /// <param name="input">Stream to read from</param> | 
|  | /// <param name="codec">Codec describing how the key/value pairs are encoded</param> | 
|  | public void AddEntriesFrom(CodedInputStream input, Codec codec) | 
|  | { | 
|  | var adapter = new Codec.MessageAdapter(codec); | 
|  | do | 
|  | { | 
|  | adapter.Reset(); | 
|  | input.ReadMessage(adapter); | 
|  | this[adapter.Key] = adapter.Value; | 
|  | } 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); | 
|  | foreach (var entry in list) | 
|  | { | 
|  | message.Key = entry.Key; | 
|  | message.Value = entry.Value; | 
|  | output.WriteTag(codec.MapTag); | 
|  | output.WriteMessage(message); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// <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) | 
|  | { | 
|  | return 0; | 
|  | } | 
|  | var message = new Codec.MessageAdapter(codec); | 
|  | int size = 0; | 
|  | foreach (var entry in list) | 
|  | { | 
|  | message.Key = entry.Key; | 
|  | message.Value = entry.Value; | 
|  | size += CodedOutputStream.ComputeRawVarint32Size(codec.MapTag); | 
|  | size += CodedOutputStream.ComputeMessageSize(message); | 
|  | } | 
|  | return size; | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Returns a string representation of this repeated field, in the same | 
|  | /// way as it would be represented by the default JSON formatter. | 
|  | /// </summary> | 
|  | public override string ToString() | 
|  | { | 
|  | var writer = new StringWriter(); | 
|  | JsonFormatter.Default.WriteDictionary(writer, this); | 
|  | return writer.ToString(); | 
|  | } | 
|  |  | 
|  | #region IDictionary explicit interface implementation | 
|  | void IDictionary.Add(object key, object value) | 
|  | { | 
|  | Add((TKey)key, (TValue)value); | 
|  | } | 
|  |  | 
|  | bool IDictionary.Contains(object key) | 
|  | { | 
|  | if (!(key is TKey)) | 
|  | { | 
|  | return false; | 
|  | } | 
|  | return ContainsKey((TKey)key); | 
|  | } | 
|  |  | 
|  | IDictionaryEnumerator IDictionary.GetEnumerator() | 
|  | { | 
|  | return new DictionaryEnumerator(GetEnumerator()); | 
|  | } | 
|  |  | 
|  | void IDictionary.Remove(object key) | 
|  | { | 
|  | ProtoPreconditions.CheckNotNull(key, "key"); | 
|  | if (!(key is TKey)) | 
|  | { | 
|  | return; | 
|  | } | 
|  | Remove((TKey)key); | 
|  | } | 
|  |  | 
|  | void ICollection.CopyTo(Array array, int index) | 
|  | { | 
|  | // This is ugly and slow as heck, but with any luck it will never be used anyway. | 
|  | ICollection temp = this.Select(pair => new DictionaryEntry(pair.Key, pair.Value)).ToList(); | 
|  | temp.CopyTo(array, index); | 
|  | } | 
|  |  | 
|  | bool IDictionary.IsFixedSize { get { return false; } } | 
|  |  | 
|  | ICollection IDictionary.Keys { get { return (ICollection)Keys; } } | 
|  |  | 
|  | ICollection IDictionary.Values { get { return (ICollection)Values; } } | 
|  |  | 
|  | bool ICollection.IsSynchronized { get { return false; } } | 
|  |  | 
|  | object ICollection.SyncRoot { get { return this; } } | 
|  |  | 
|  | object IDictionary.this[object key] | 
|  | { | 
|  | get | 
|  | { | 
|  | ProtoPreconditions.CheckNotNull(key, "key"); | 
|  | if (!(key is TKey)) | 
|  | { | 
|  | return null; | 
|  | } | 
|  | TValue value; | 
|  | TryGetValue((TKey)key, out value); | 
|  | return value; | 
|  | } | 
|  |  | 
|  | set | 
|  | { | 
|  | this[(TKey)key] = (TValue)value; | 
|  | } | 
|  | } | 
|  | #endregion | 
|  |  | 
|  | private class DictionaryEnumerator : IDictionaryEnumerator | 
|  | { | 
|  | private readonly IEnumerator<KeyValuePair<TKey, TValue>> enumerator; | 
|  |  | 
|  | internal DictionaryEnumerator(IEnumerator<KeyValuePair<TKey, TValue>> enumerator) | 
|  | { | 
|  | this.enumerator = enumerator; | 
|  | } | 
|  |  | 
|  | public bool MoveNext() | 
|  | { | 
|  | return enumerator.MoveNext(); | 
|  | } | 
|  |  | 
|  | public void Reset() | 
|  | { | 
|  | enumerator.Reset(); | 
|  | } | 
|  |  | 
|  | public object Current { get { return Entry; } } | 
|  | public DictionaryEntry Entry { get { return new DictionaryEntry(Key, Value); } } | 
|  | public object Key { get { return enumerator.Current.Key; } } | 
|  | public object Value { get { return enumerator.Current.Value; } } | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// 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 | 
|  | { | 
|  | private readonly FieldCodec<TKey> keyCodec; | 
|  | 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; | 
|  | this.valueCodec = valueCodec; | 
|  | this.mapTag = mapTag; | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// The tag used in the enclosing message to indicate map entries. | 
|  | /// </summary> | 
|  | internal uint MapTag { get { return mapTag; } } | 
|  |  | 
|  | /// <summary> | 
|  | /// A mutable message class, used for parsing and serializing. This | 
|  | /// delegates the work to a codec, but implements the <see cref="IMessage"/> interface | 
|  | /// for interop with <see cref="CodedInputStream"/> and <see cref="CodedOutputStream"/>. | 
|  | /// This is nested inside Codec as it's tightly coupled to the associated codec, | 
|  | /// and it's simpler if it has direct access to all its fields. | 
|  | /// </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; } | 
|  |  | 
|  | internal MessageAdapter(Codec codec) | 
|  | { | 
|  | this.codec = codec; | 
|  | } | 
|  |  | 
|  | internal void Reset() | 
|  | { | 
|  | Key = codec.keyCodec.DefaultValue; | 
|  | Value = codec.valueCodec.DefaultValue; | 
|  | } | 
|  |  | 
|  | public void MergeFrom(CodedInputStream input) | 
|  | { | 
|  | uint tag; | 
|  | while ((tag = input.ReadTag()) != 0) | 
|  | { | 
|  | if (tag == codec.keyCodec.Tag) | 
|  | { | 
|  | Key = codec.keyCodec.Read(input); | 
|  | } | 
|  | else if (tag == codec.valueCodec.Tag) | 
|  | { | 
|  | Value = codec.valueCodec.Read(input); | 
|  | } | 
|  | else | 
|  | { | 
|  | 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) | 
|  | { | 
|  | codec.keyCodec.WriteTagAndValue(output, Key); | 
|  | codec.valueCodec.WriteTagAndValue(output, Value); | 
|  | } | 
|  |  | 
|  | public int CalculateSize() | 
|  | { | 
|  | return codec.keyCodec.CalculateSizeWithTag(Key) + codec.valueCodec.CalculateSizeWithTag(Value); | 
|  | } | 
|  |  | 
|  | MessageDescriptor IMessage.Descriptor { get { return null; } } | 
|  | } | 
|  | } | 
|  |  | 
|  | private class MapView<T> : ICollection<T>, ICollection | 
|  | { | 
|  | private readonly MapField<TKey, TValue> parent; | 
|  | private readonly Func<KeyValuePair<TKey, TValue>, T> projection; | 
|  | private readonly Func<T, bool> containsCheck; | 
|  |  | 
|  | internal MapView( | 
|  | MapField<TKey, TValue> parent, | 
|  | Func<KeyValuePair<TKey, TValue>, T> projection, | 
|  | Func<T, bool> containsCheck) | 
|  | { | 
|  | this.parent = parent; | 
|  | this.projection = projection; | 
|  | this.containsCheck = containsCheck; | 
|  | } | 
|  |  | 
|  | public int Count { get { return parent.Count; } } | 
|  |  | 
|  | public bool IsReadOnly { get { return true; } } | 
|  |  | 
|  | public bool IsSynchronized { get { return false; } } | 
|  |  | 
|  | public object SyncRoot { get { return parent; } } | 
|  |  | 
|  | public void Add(T item) | 
|  | { | 
|  | throw new NotSupportedException(); | 
|  | } | 
|  |  | 
|  | public void Clear() | 
|  | { | 
|  | throw new NotSupportedException(); | 
|  | } | 
|  |  | 
|  | public bool Contains(T item) | 
|  | { | 
|  | return containsCheck(item); | 
|  | } | 
|  |  | 
|  | public void CopyTo(T[] array, int arrayIndex) | 
|  | { | 
|  | if (arrayIndex < 0) | 
|  | { | 
|  | throw new ArgumentOutOfRangeException("arrayIndex"); | 
|  | } | 
|  | if (arrayIndex + Count  >= array.Length) | 
|  | { | 
|  | throw new ArgumentException("Not enough space in the array", "array"); | 
|  | } | 
|  | foreach (var item in this) | 
|  | { | 
|  | array[arrayIndex++] = item; | 
|  | } | 
|  | } | 
|  |  | 
|  | public IEnumerator<T> GetEnumerator() | 
|  | { | 
|  | return parent.list.Select(projection).GetEnumerator(); | 
|  | } | 
|  |  | 
|  | public bool Remove(T item) | 
|  | { | 
|  | throw new NotSupportedException(); | 
|  | } | 
|  |  | 
|  | IEnumerator IEnumerable.GetEnumerator() | 
|  | { | 
|  | return GetEnumerator(); | 
|  | } | 
|  |  | 
|  | public void CopyTo(Array array, int index) | 
|  | { | 
|  | if (index < 0) | 
|  | { | 
|  | throw new ArgumentOutOfRangeException("index"); | 
|  | } | 
|  | if (index + Count >= array.Length) | 
|  | { | 
|  | throw new ArgumentException("Not enough space in the array", "array"); | 
|  | } | 
|  | foreach (var item in this) | 
|  | { | 
|  | array.SetValue(item, index++); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } |