• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #region Copyright notice and license
2 // Protocol Buffers - Google's data interchange format
3 // Copyright 2015 Google Inc.  All rights reserved.
4 // https://developers.google.com/protocol-buffers/
5 //
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are
8 // met:
9 //
10 //     * Redistributions of source code must retain the above copyright
11 // notice, this list of conditions and the following disclaimer.
12 //     * Redistributions in binary form must reproduce the above
13 // copyright notice, this list of conditions and the following disclaimer
14 // in the documentation and/or other materials provided with the
15 // distribution.
16 //     * Neither the name of Google Inc. nor the names of its
17 // contributors may be used to endorse or promote products derived from
18 // this software without specific prior written permission.
19 //
20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #endregion
32 
33 using Google.Protobuf.Compatibility;
34 using Google.Protobuf.Reflection;
35 using System;
36 using System.Collections;
37 using System.Collections.Generic;
38 using System.IO;
39 using System.Linq;
40 
41 namespace Google.Protobuf.Collections
42 {
43     /// <summary>
44     /// Representation of a map field in a Protocol Buffer message.
45     /// </summary>
46     /// <typeparam name="TKey">Key type in the map. Must be a type supported by Protocol Buffer map keys.</typeparam>
47     /// <typeparam name="TValue">Value type in the map. Must be a type supported by Protocol Buffers.</typeparam>
48     /// <remarks>
49     /// <para>
50     /// For string keys, the equality comparison is provided by <see cref="StringComparer.Ordinal" />.
51     /// </para>
52     /// <para>
53     /// Null values are not permitted in the map, either for wrapper types or regular messages.
54     /// If a map is deserialized from a data stream and the value is missing from an entry, a default value
55     /// is created instead. For primitive types, that is the regular default value (0, the empty string and so
56     /// on); for message types, an empty instance of the message is created, as if the map entry contained a 0-length
57     /// encoded value for the field.
58     /// </para>
59     /// <para>
60     /// This implementation does not generally prohibit the use of key/value types which are not
61     /// supported by Protocol Buffers (e.g. using a key type of <code>byte</code>) but nor does it guarantee
62     /// that all operations will work in such cases.
63     /// </para>
64     /// <para>
65     /// The order in which entries are returned when iterating over this object is undefined, and may change
66     /// in future versions.
67     /// </para>
68     /// </remarks>
69     public sealed class MapField<TKey, TValue> : IDeepCloneable<MapField<TKey, TValue>>, IDictionary<TKey, TValue>, IEquatable<MapField<TKey, TValue>>, IDictionary
70 #if !NET35
71         , IReadOnlyDictionary<TKey, TValue>
72 #endif
73     {
74         private static readonly EqualityComparer<TValue> ValueEqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<TValue>();
75         private static readonly EqualityComparer<TKey> KeyEqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<TKey>();
76 
77         // TODO: Don't create the map/list until we have an entry. (Assume many maps will be empty.)
78         private readonly Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> map =
79             new Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>(KeyEqualityComparer);
80         private readonly LinkedList<KeyValuePair<TKey, TValue>> list = new LinkedList<KeyValuePair<TKey, TValue>>();
81 
82         /// <summary>
83         /// Creates a deep clone of this object.
84         /// </summary>
85         /// <returns>
86         /// A deep clone of this object.
87         /// </returns>
Clone()88         public MapField<TKey, TValue> Clone()
89         {
90             var clone = new MapField<TKey, TValue>();
91             // Keys are never cloneable. Values might be.
92             if (typeof(IDeepCloneable<TValue>).IsAssignableFrom(typeof(TValue)))
93             {
94                 foreach (var pair in list)
95                 {
96                     clone.Add(pair.Key, ((IDeepCloneable<TValue>)pair.Value).Clone());
97                 }
98             }
99             else
100             {
101                 // Nothing is cloneable, so we don't need to worry.
102                 clone.Add(this);
103             }
104             return clone;
105         }
106 
107         /// <summary>
108         /// Adds the specified key/value pair to the map.
109         /// </summary>
110         /// <remarks>
111         /// This operation fails if the key already exists in the map. To replace an existing entry, use the indexer.
112         /// </remarks>
113         /// <param name="key">The key to add</param>
114         /// <param name="value">The value to add.</param>
115         /// <exception cref="System.ArgumentException">The given key already exists in map.</exception>
Add(TKey key, TValue value)116         public void Add(TKey key, TValue value)
117         {
118             // Validation of arguments happens in ContainsKey and the indexer
119             if (ContainsKey(key))
120             {
121                 throw new ArgumentException("Key already exists in map", nameof(key));
122             }
123             this[key] = value;
124         }
125 
126         /// <summary>
127         /// Determines whether the specified key is present in the map.
128         /// </summary>
129         /// <param name="key">The key to check.</param>
130         /// <returns><c>true</c> if the map contains the given key; <c>false</c> otherwise.</returns>
ContainsKey(TKey key)131         public bool ContainsKey(TKey key)
132         {
133             ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
134             return map.ContainsKey(key);
135         }
136 
137         private bool ContainsValue(TValue value) =>
138             list.Any(pair => ValueEqualityComparer.Equals(pair.Value, value));
139 
140         /// <summary>
141         /// Removes the entry identified by the given key from the map.
142         /// </summary>
143         /// <param name="key">The key indicating the entry to remove from the map.</param>
144         /// <returns><c>true</c> if the map contained the given key before the entry was removed; <c>false</c> otherwise.</returns>
Remove(TKey key)145         public bool Remove(TKey key)
146         {
147             ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
148             LinkedListNode<KeyValuePair<TKey, TValue>> node;
149             if (map.TryGetValue(key, out node))
150             {
151                 map.Remove(key);
152                 node.List.Remove(node);
153                 return true;
154             }
155             else
156             {
157                 return false;
158             }
159         }
160 
161         /// <summary>
162         /// Gets the value associated with the specified key.
163         /// </summary>
164         /// <param name="key">The key whose value to get.</param>
165         /// <param name="value">When this method returns, the value associated with the specified key, if the key is found;
166         /// otherwise, the default value for the type of the <paramref name="value"/> parameter.
167         /// This parameter is passed uninitialized.</param>
168         /// <returns><c>true</c> if the map contains an element with the specified key; otherwise, <c>false</c>.</returns>
TryGetValue(TKey key, out TValue value)169         public bool TryGetValue(TKey key, out TValue value)
170         {
171             LinkedListNode<KeyValuePair<TKey, TValue>> node;
172             if (map.TryGetValue(key, out node))
173             {
174                 value = node.Value.Value;
175                 return true;
176             }
177             else
178             {
179                 value = default(TValue);
180                 return false;
181             }
182         }
183 
184         /// <summary>
185         /// Gets or sets the value associated with the specified key.
186         /// </summary>
187         /// <param name="key">The key of the value to get or set.</param>
188         /// <exception cref="KeyNotFoundException">The property is retrieved and key does not exist in the collection.</exception>
189         /// <returns>The value associated with the specified key. If the specified key is not found,
190         /// a get operation throws a <see cref="KeyNotFoundException"/>, and a set operation creates a new element with the specified key.</returns>
191         public TValue this[TKey key]
192         {
193             get
194             {
195                 ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
196                 TValue value;
197                 if (TryGetValue(key, out value))
198                 {
199                     return value;
200                 }
201                 throw new KeyNotFoundException();
202             }
203             set
204             {
205                 ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
206                 // value == null check here is redundant, but avoids boxing.
207                 if (value == null)
208                 {
209                     ProtoPreconditions.CheckNotNullUnconstrained(value, nameof(value));
210                 }
211                 LinkedListNode<KeyValuePair<TKey, TValue>> node;
212                 var pair = new KeyValuePair<TKey, TValue>(key, value);
213                 if (map.TryGetValue(key, out node))
214                 {
215                     node.Value = pair;
216                 }
217                 else
218                 {
219                     node = list.AddLast(pair);
220                     map[key] = node;
221                 }
222             }
223         }
224 
225         /// <summary>
226         /// Gets a collection containing the keys in the map.
227         /// </summary>
228         public ICollection<TKey> Keys { get { return new MapView<TKey>(this, pair => pair.Key, ContainsKey); } }
229 
230         /// <summary>
231         /// Gets a collection containing the values in the map.
232         /// </summary>
233         public ICollection<TValue> Values { get { return new MapView<TValue>(this, pair => pair.Value, ContainsValue); } }
234 
235         /// <summary>
236         /// Adds the specified entries to the map. The keys and values are not automatically cloned.
237         /// </summary>
238         /// <param name="entries">The entries to add to the map.</param>
Add(IDictionary<TKey, TValue> entries)239         public void Add(IDictionary<TKey, TValue> entries)
240         {
241             ProtoPreconditions.CheckNotNull(entries, nameof(entries));
242             foreach (var pair in entries)
243             {
244                 Add(pair.Key, pair.Value);
245             }
246         }
247 
248         /// <summary>
249         /// Returns an enumerator that iterates through the collection.
250         /// </summary>
251         /// <returns>
252         /// An enumerator that can be used to iterate through the collection.
253         /// </returns>
GetEnumerator()254         public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
255         {
256             return list.GetEnumerator();
257         }
258 
259         /// <summary>
260         /// Returns an enumerator that iterates through a collection.
261         /// </summary>
262         /// <returns>
263         /// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
264         /// </returns>
IEnumerable.GetEnumerator()265         IEnumerator IEnumerable.GetEnumerator()
266         {
267             return GetEnumerator();
268         }
269 
270         /// <summary>
271         /// Adds the specified item to the map.
272         /// </summary>
273         /// <param name="item">The item to add to the map.</param>
Add(KeyValuePair<TKey, TValue> item)274         void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
275         {
276             Add(item.Key, item.Value);
277         }
278 
279         /// <summary>
280         /// Removes all items from the map.
281         /// </summary>
Clear()282         public void Clear()
283         {
284             list.Clear();
285             map.Clear();
286         }
287 
288         /// <summary>
289         /// Determines whether map contains an entry equivalent to the given key/value pair.
290         /// </summary>
291         /// <param name="item">The key/value pair to find.</param>
292         /// <returns></returns>
Contains(KeyValuePair<TKey, TValue> item)293         bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
294         {
295             TValue value;
296             return TryGetValue(item.Key, out value) && ValueEqualityComparer.Equals(item.Value, value);
297         }
298 
299         /// <summary>
300         /// Copies the key/value pairs in this map to an array.
301         /// </summary>
302         /// <param name="array">The array to copy the entries into.</param>
303         /// <param name="arrayIndex">The index of the array at which to start copying values.</param>
CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)304         void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
305         {
306             list.CopyTo(array, arrayIndex);
307         }
308 
309         /// <summary>
310         /// Removes the specified key/value pair from the map.
311         /// </summary>
312         /// <remarks>Both the key and the value must be found for the entry to be removed.</remarks>
313         /// <param name="item">The key/value pair to remove.</param>
314         /// <returns><c>true</c> if the key/value pair was found and removed; <c>false</c> otherwise.</returns>
Remove(KeyValuePair<TKey, TValue> item)315         bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
316         {
317             if (item.Key == null)
318             {
319                 throw new ArgumentException("Key is null", nameof(item));
320             }
321             LinkedListNode<KeyValuePair<TKey, TValue>> node;
322             if (map.TryGetValue(item.Key, out node) &&
323                 EqualityComparer<TValue>.Default.Equals(item.Value, node.Value.Value))
324             {
325                 map.Remove(item.Key);
326                 node.List.Remove(node);
327                 return true;
328             }
329             else
330             {
331                 return false;
332             }
333         }
334 
335         /// <summary>
336         /// Gets the number of elements contained in the map.
337         /// </summary>
338         public int Count { get { return list.Count; } }
339 
340         /// <summary>
341         /// Gets a value indicating whether the map is read-only.
342         /// </summary>
343         public bool IsReadOnly { get { return false; } }
344 
345         /// <summary>
346         /// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
347         /// </summary>
348         /// <param name="other">The <see cref="System.Object" /> to compare with this instance.</param>
349         /// <returns>
350         ///   <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
351         /// </returns>
Equals(object other)352         public override bool Equals(object other)
353         {
354             return Equals(other as MapField<TKey, TValue>);
355         }
356 
357         /// <summary>
358         /// Returns a hash code for this instance.
359         /// </summary>
360         /// <returns>
361         /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
362         /// </returns>
GetHashCode()363         public override int GetHashCode()
364         {
365             var keyComparer = KeyEqualityComparer;
366             var valueComparer = ValueEqualityComparer;
367             int hash = 0;
368             foreach (var pair in list)
369             {
370                 hash ^= keyComparer.GetHashCode(pair.Key) * 31 + valueComparer.GetHashCode(pair.Value);
371             }
372             return hash;
373         }
374 
375         /// <summary>
376         /// Compares this map with another for equality.
377         /// </summary>
378         /// <remarks>
379         /// The order of the key/value pairs in the maps is not deemed significant in this comparison.
380         /// </remarks>
381         /// <param name="other">The map to compare this with.</param>
382         /// <returns><c>true</c> if <paramref name="other"/> refers to an equal map; <c>false</c> otherwise.</returns>
Equals(MapField<TKey, TValue> other)383         public bool Equals(MapField<TKey, TValue> other)
384         {
385             if (other == null)
386             {
387                 return false;
388             }
389             if (other == this)
390             {
391                 return true;
392             }
393             if (other.Count != this.Count)
394             {
395                 return false;
396             }
397             var valueComparer = ValueEqualityComparer;
398             foreach (var pair in this)
399             {
400                 TValue value;
401                 if (!other.TryGetValue(pair.Key, out value))
402                 {
403                     return false;
404                 }
405                 if (!valueComparer.Equals(value, pair.Value))
406                 {
407                     return false;
408                 }
409             }
410             return true;
411         }
412 
413         /// <summary>
414         /// Adds entries to the map from the given stream.
415         /// </summary>
416         /// <remarks>
417         /// It is assumed that the stream is initially positioned after the tag specified by the codec.
418         /// This method will continue reading entries from the stream until the end is reached, or
419         /// a different tag is encountered.
420         /// </remarks>
421         /// <param name="input">Stream to read from</param>
422         /// <param name="codec">Codec describing how the key/value pairs are encoded</param>
AddEntriesFrom(CodedInputStream input, Codec codec)423         public void AddEntriesFrom(CodedInputStream input, Codec codec)
424         {
425             var adapter = new Codec.MessageAdapter(codec);
426             do
427             {
428                 adapter.Reset();
429                 input.ReadMessage(adapter);
430                 this[adapter.Key] = adapter.Value;
431             } while (input.MaybeConsumeTag(codec.MapTag));
432         }
433 
434         /// <summary>
435         /// Writes the contents of this map to the given coded output stream, using the specified codec
436         /// to encode each entry.
437         /// </summary>
438         /// <param name="output">The output stream to write to.</param>
439         /// <param name="codec">The codec to use for each entry.</param>
WriteTo(CodedOutputStream output, Codec codec)440         public void WriteTo(CodedOutputStream output, Codec codec)
441         {
442             var message = new Codec.MessageAdapter(codec);
443             foreach (var entry in list)
444             {
445                 message.Key = entry.Key;
446                 message.Value = entry.Value;
447                 output.WriteTag(codec.MapTag);
448                 output.WriteMessage(message);
449             }
450         }
451 
452         /// <summary>
453         /// Calculates the size of this map based on the given entry codec.
454         /// </summary>
455         /// <param name="codec">The codec to use to encode each entry.</param>
456         /// <returns></returns>
CalculateSize(Codec codec)457         public int CalculateSize(Codec codec)
458         {
459             if (Count == 0)
460             {
461                 return 0;
462             }
463             var message = new Codec.MessageAdapter(codec);
464             int size = 0;
465             foreach (var entry in list)
466             {
467                 message.Key = entry.Key;
468                 message.Value = entry.Value;
469                 size += CodedOutputStream.ComputeRawVarint32Size(codec.MapTag);
470                 size += CodedOutputStream.ComputeMessageSize(message);
471             }
472             return size;
473         }
474 
475         /// <summary>
476         /// Returns a string representation of this repeated field, in the same
477         /// way as it would be represented by the default JSON formatter.
478         /// </summary>
ToString()479         public override string ToString()
480         {
481             var writer = new StringWriter();
482             JsonFormatter.Default.WriteDictionary(writer, this);
483             return writer.ToString();
484         }
485 
486         #region IDictionary explicit interface implementation
IDictionary.Add(object key, object value)487         void IDictionary.Add(object key, object value)
488         {
489             Add((TKey)key, (TValue)value);
490         }
491 
IDictionary.Contains(object key)492         bool IDictionary.Contains(object key)
493         {
494             if (!(key is TKey))
495             {
496                 return false;
497             }
498             return ContainsKey((TKey)key);
499         }
500 
IDictionary.GetEnumerator()501         IDictionaryEnumerator IDictionary.GetEnumerator()
502         {
503             return new DictionaryEnumerator(GetEnumerator());
504         }
505 
IDictionary.Remove(object key)506         void IDictionary.Remove(object key)
507         {
508             ProtoPreconditions.CheckNotNull(key, nameof(key));
509             if (!(key is TKey))
510             {
511                 return;
512             }
513             Remove((TKey)key);
514         }
515 
ICollection.CopyTo(Array array, int index)516         void ICollection.CopyTo(Array array, int index)
517         {
518             // This is ugly and slow as heck, but with any luck it will never be used anyway.
519             ICollection temp = this.Select(pair => new DictionaryEntry(pair.Key, pair.Value)).ToList();
520             temp.CopyTo(array, index);
521         }
522 
523         bool IDictionary.IsFixedSize { get { return false; } }
524 
525         ICollection IDictionary.Keys { get { return (ICollection)Keys; } }
526 
527         ICollection IDictionary.Values { get { return (ICollection)Values; } }
528 
529         bool ICollection.IsSynchronized { get { return false; } }
530 
531         object ICollection.SyncRoot { get { return this; } }
532 
533         object IDictionary.this[object key]
534         {
535             get
536             {
537                 ProtoPreconditions.CheckNotNull(key, nameof(key));
538                 if (!(key is TKey))
539                 {
540                     return null;
541                 }
542                 TValue value;
543                 TryGetValue((TKey)key, out value);
544                 return value;
545             }
546 
547             set
548             {
549                 this[(TKey)key] = (TValue)value;
550             }
551         }
552         #endregion
553 
554         #region IReadOnlyDictionary explicit interface implementation
555 #if !NET35
556         IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => Keys;
557 
558         IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values;
559 #endif
560         #endregion
561 
562         private class DictionaryEnumerator : IDictionaryEnumerator
563         {
564             private readonly IEnumerator<KeyValuePair<TKey, TValue>> enumerator;
565 
DictionaryEnumerator(IEnumerator<KeyValuePair<TKey, TValue>> enumerator)566             internal DictionaryEnumerator(IEnumerator<KeyValuePair<TKey, TValue>> enumerator)
567             {
568                 this.enumerator = enumerator;
569             }
570 
MoveNext()571             public bool MoveNext()
572             {
573                 return enumerator.MoveNext();
574             }
575 
Reset()576             public void Reset()
577             {
578                 enumerator.Reset();
579             }
580 
581             public object Current { get { return Entry; } }
582             public DictionaryEntry Entry { get { return new DictionaryEntry(Key, Value); } }
583             public object Key { get { return enumerator.Current.Key; } }
584             public object Value { get { return enumerator.Current.Value; } }
585         }
586 
587         /// <summary>
588         /// A codec for a specific map field. This contains all the information required to encode and
589         /// decode the nested messages.
590         /// </summary>
591         public sealed class Codec
592         {
593             private readonly FieldCodec<TKey> keyCodec;
594             private readonly FieldCodec<TValue> valueCodec;
595             private readonly uint mapTag;
596 
597             /// <summary>
598             /// Creates a new entry codec based on a separate key codec and value codec,
599             /// and the tag to use for each map entry.
600             /// </summary>
601             /// <param name="keyCodec">The key codec.</param>
602             /// <param name="valueCodec">The value codec.</param>
603             /// <param name="mapTag">The map tag to use to introduce each map entry.</param>
Codec(FieldCodec<TKey> keyCodec, FieldCodec<TValue> valueCodec, uint mapTag)604             public Codec(FieldCodec<TKey> keyCodec, FieldCodec<TValue> valueCodec, uint mapTag)
605             {
606                 this.keyCodec = keyCodec;
607                 this.valueCodec = valueCodec;
608                 this.mapTag = mapTag;
609             }
610 
611             /// <summary>
612             /// The tag used in the enclosing message to indicate map entries.
613             /// </summary>
614             internal uint MapTag { get { return mapTag; } }
615 
616             /// <summary>
617             /// A mutable message class, used for parsing and serializing. This
618             /// delegates the work to a codec, but implements the <see cref="IMessage"/> interface
619             /// for interop with <see cref="CodedInputStream"/> and <see cref="CodedOutputStream"/>.
620             /// This is nested inside Codec as it's tightly coupled to the associated codec,
621             /// and it's simpler if it has direct access to all its fields.
622             /// </summary>
623             internal class MessageAdapter : IMessage
624             {
625                 private static readonly byte[] ZeroLengthMessageStreamData = new byte[] { 0 };
626 
627                 private readonly Codec codec;
628                 internal TKey Key { get; set; }
629                 internal TValue Value { get; set; }
630 
MessageAdapter(Codec codec)631                 internal MessageAdapter(Codec codec)
632                 {
633                     this.codec = codec;
634                 }
635 
Reset()636                 internal void Reset()
637                 {
638                     Key = codec.keyCodec.DefaultValue;
639                     Value = codec.valueCodec.DefaultValue;
640                 }
641 
MergeFrom(CodedInputStream input)642                 public void MergeFrom(CodedInputStream input)
643                 {
644                     uint tag;
645                     while ((tag = input.ReadTag()) != 0)
646                     {
647                         if (tag == codec.keyCodec.Tag)
648                         {
649                             Key = codec.keyCodec.Read(input);
650                         }
651                         else if (tag == codec.valueCodec.Tag)
652                         {
653                             Value = codec.valueCodec.Read(input);
654                         }
655                         else
656                         {
657                             input.SkipLastField();
658                         }
659                     }
660 
661                     // Corner case: a map entry with a key but no value, where the value type is a message.
662                     // Read it as if we'd seen an input stream with no data (i.e. create a "default" message).
663                     if (Value == null)
664                     {
665                         Value = codec.valueCodec.Read(new CodedInputStream(ZeroLengthMessageStreamData));
666                     }
667                 }
668 
WriteTo(CodedOutputStream output)669                 public void WriteTo(CodedOutputStream output)
670                 {
671                     codec.keyCodec.WriteTagAndValue(output, Key);
672                     codec.valueCodec.WriteTagAndValue(output, Value);
673                 }
674 
CalculateSize()675                 public int CalculateSize()
676                 {
677                     return codec.keyCodec.CalculateSizeWithTag(Key) + codec.valueCodec.CalculateSizeWithTag(Value);
678                 }
679 
680                 MessageDescriptor IMessage.Descriptor { get { return null; } }
681             }
682         }
683 
684         private class MapView<T> : ICollection<T>, ICollection
685         {
686             private readonly MapField<TKey, TValue> parent;
687             private readonly Func<KeyValuePair<TKey, TValue>, T> projection;
688             private readonly Func<T, bool> containsCheck;
689 
MapView( MapField<TKey, TValue> parent, Func<KeyValuePair<TKey, TValue>, T> projection, Func<T, bool> containsCheck)690             internal MapView(
691                 MapField<TKey, TValue> parent,
692                 Func<KeyValuePair<TKey, TValue>, T> projection,
693                 Func<T, bool> containsCheck)
694             {
695                 this.parent = parent;
696                 this.projection = projection;
697                 this.containsCheck = containsCheck;
698             }
699 
700             public int Count { get { return parent.Count; } }
701 
702             public bool IsReadOnly { get { return true; } }
703 
704             public bool IsSynchronized { get { return false; } }
705 
706             public object SyncRoot { get { return parent; } }
707 
Add(T item)708             public void Add(T item)
709             {
710                 throw new NotSupportedException();
711             }
712 
Clear()713             public void Clear()
714             {
715                 throw new NotSupportedException();
716             }
717 
Contains(T item)718             public bool Contains(T item)
719             {
720                 return containsCheck(item);
721             }
722 
CopyTo(T[] array, int arrayIndex)723             public void CopyTo(T[] array, int arrayIndex)
724             {
725                 if (arrayIndex < 0)
726                 {
727                     throw new ArgumentOutOfRangeException(nameof(arrayIndex));
728                 }
729                 if (arrayIndex + Count > array.Length)
730                 {
731                     throw new ArgumentException("Not enough space in the array", nameof(array));
732                 }
733                 foreach (var item in this)
734                 {
735                     array[arrayIndex++] = item;
736                 }
737             }
738 
GetEnumerator()739             public IEnumerator<T> GetEnumerator()
740             {
741                 return parent.list.Select(projection).GetEnumerator();
742             }
743 
Remove(T item)744             public bool Remove(T item)
745             {
746                 throw new NotSupportedException();
747             }
748 
IEnumerable.GetEnumerator()749             IEnumerator IEnumerable.GetEnumerator()
750             {
751                 return GetEnumerator();
752             }
753 
CopyTo(Array array, int index)754             public void CopyTo(Array array, int index)
755             {
756                 if (index < 0)
757                 {
758                     throw new ArgumentOutOfRangeException(nameof(index));
759                 }
760                 if (index + Count > array.Length)
761                 {
762                     throw new ArgumentException("Not enough space in the array", nameof(array));
763                 }
764                 foreach (var item in this)
765                 {
766                     array.SetValue(item, index++);
767                 }
768             }
769         }
770     }
771 }
772