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