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