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