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 do 452 { 453 KeyValuePair<TKey, TValue> entry = ParsingPrimitivesMessages.ReadMapEntry(ref ctx, codec); 454 this[entry.Key] = entry.Value; 455 } while (ParsingPrimitives.MaybeConsumeTag(ref ctx.buffer, ref ctx.state, codec.MapTag)); 456 } 457 458 /// <summary> 459 /// Writes the contents of this map to the given coded output stream, using the specified codec 460 /// to encode each entry. 461 /// </summary> 462 /// <param name="output">The output stream to write to.</param> 463 /// <param name="codec">The codec to use for each entry.</param> WriteTo(CodedOutputStream output, Codec codec)464 public void WriteTo(CodedOutputStream output, Codec codec) 465 { 466 WriteContext.Initialize(output, out WriteContext ctx); 467 try 468 { 469 WriteTo(ref ctx, codec); 470 } 471 finally 472 { 473 ctx.CopyStateTo(output); 474 } 475 } 476 477 /// <summary> 478 /// Writes the contents of this map to the given write context, using the specified codec 479 /// to encode each entry. 480 /// </summary> 481 /// <param name="ctx">The write context to write to.</param> 482 /// <param name="codec">The codec to use for each entry.</param> 483 [SecuritySafeCritical] WriteTo(ref WriteContext ctx, Codec codec)484 public void WriteTo(ref WriteContext ctx, Codec codec) 485 { 486 foreach (var entry in list) 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 IDictionary.Add(object key, object value)535 void IDictionary.Add(object key, object value) 536 { 537 Add((TKey)key, (TValue)value); 538 } 539 IDictionary.Contains(object key)540 bool IDictionary.Contains(object key) 541 { 542 if (!(key is TKey)) 543 { 544 return false; 545 } 546 return ContainsKey((TKey)key); 547 } 548 IDictionary.GetEnumerator()549 IDictionaryEnumerator IDictionary.GetEnumerator() 550 { 551 return new DictionaryEnumerator(GetEnumerator()); 552 } 553 IDictionary.Remove(object key)554 void IDictionary.Remove(object key) 555 { 556 ProtoPreconditions.CheckNotNull(key, nameof(key)); 557 if (!(key is TKey)) 558 { 559 return; 560 } 561 Remove((TKey)key); 562 } 563 ICollection.CopyTo(Array array, int index)564 void ICollection.CopyTo(Array array, int index) 565 { 566 // This is ugly and slow as heck, but with any luck it will never be used anyway. 567 ICollection temp = this.Select(pair => new DictionaryEntry(pair.Key, pair.Value)).ToList(); 568 temp.CopyTo(array, index); 569 } 570 571 bool IDictionary.IsFixedSize { get { return false; } } 572 573 ICollection IDictionary.Keys { get { return (ICollection)Keys; } } 574 575 ICollection IDictionary.Values { get { return (ICollection)Values; } } 576 577 bool ICollection.IsSynchronized { get { return false; } } 578 579 object ICollection.SyncRoot { get { return this; } } 580 581 object IDictionary.this[object key] 582 { 583 get 584 { 585 ProtoPreconditions.CheckNotNull(key, nameof(key)); 586 if (!(key is TKey)) 587 { 588 return null; 589 } 590 TValue value; 591 TryGetValue((TKey)key, out value); 592 return value; 593 } 594 595 set 596 { 597 this[(TKey)key] = (TValue)value; 598 } 599 } 600 #endregion 601 602 #region IReadOnlyDictionary explicit interface implementation 603 #if !NET35 604 IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => Keys; 605 606 IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values; 607 #endif 608 #endregion 609 610 private class DictionaryEnumerator : IDictionaryEnumerator 611 { 612 private readonly IEnumerator<KeyValuePair<TKey, TValue>> enumerator; 613 DictionaryEnumerator(IEnumerator<KeyValuePair<TKey, TValue>> enumerator)614 internal DictionaryEnumerator(IEnumerator<KeyValuePair<TKey, TValue>> enumerator) 615 { 616 this.enumerator = enumerator; 617 } 618 MoveNext()619 public bool MoveNext() 620 { 621 return enumerator.MoveNext(); 622 } 623 Reset()624 public void Reset() 625 { 626 enumerator.Reset(); 627 } 628 629 public object Current { get { return Entry; } } 630 public DictionaryEntry Entry { get { return new DictionaryEntry(Key, Value); } } 631 public object Key { get { return enumerator.Current.Key; } } 632 public object Value { get { return enumerator.Current.Value; } } 633 } 634 635 /// <summary> 636 /// A codec for a specific map field. This contains all the information required to encode and 637 /// decode the nested messages. 638 /// </summary> 639 public sealed class Codec 640 { 641 private readonly FieldCodec<TKey> keyCodec; 642 private readonly FieldCodec<TValue> valueCodec; 643 private readonly uint mapTag; 644 645 /// <summary> 646 /// Creates a new entry codec based on a separate key codec and value codec, 647 /// and the tag to use for each map entry. 648 /// </summary> 649 /// <param name="keyCodec">The key codec.</param> 650 /// <param name="valueCodec">The value codec.</param> 651 /// <param name="mapTag">The map tag to use to introduce each map entry.</param> Codec(FieldCodec<TKey> keyCodec, FieldCodec<TValue> valueCodec, uint mapTag)652 public Codec(FieldCodec<TKey> keyCodec, FieldCodec<TValue> valueCodec, uint mapTag) 653 { 654 this.keyCodec = keyCodec; 655 this.valueCodec = valueCodec; 656 this.mapTag = mapTag; 657 } 658 659 /// <summary> 660 /// The key codec. 661 /// </summary> 662 internal FieldCodec<TKey> KeyCodec => keyCodec; 663 664 /// <summary> 665 /// The value codec. 666 /// </summary> 667 internal FieldCodec<TValue> ValueCodec => valueCodec; 668 669 /// <summary> 670 /// The tag used in the enclosing message to indicate map entries. 671 /// </summary> 672 internal uint MapTag => mapTag; 673 } 674 675 private class MapView<T> : ICollection<T>, ICollection 676 { 677 private readonly MapField<TKey, TValue> parent; 678 private readonly Func<KeyValuePair<TKey, TValue>, T> projection; 679 private readonly Func<T, bool> containsCheck; 680 MapView( MapField<TKey, TValue> parent, Func<KeyValuePair<TKey, TValue>, T> projection, Func<T, bool> containsCheck)681 internal MapView( 682 MapField<TKey, TValue> parent, 683 Func<KeyValuePair<TKey, TValue>, T> projection, 684 Func<T, bool> containsCheck) 685 { 686 this.parent = parent; 687 this.projection = projection; 688 this.containsCheck = containsCheck; 689 } 690 691 public int Count { get { return parent.Count; } } 692 693 public bool IsReadOnly { get { return true; } } 694 695 public bool IsSynchronized { get { return false; } } 696 697 public object SyncRoot { get { return parent; } } 698 Add(T item)699 public void Add(T item) 700 { 701 throw new NotSupportedException(); 702 } 703 Clear()704 public void Clear() 705 { 706 throw new NotSupportedException(); 707 } 708 Contains(T item)709 public bool Contains(T item) 710 { 711 return containsCheck(item); 712 } 713 CopyTo(T[] array, int arrayIndex)714 public void CopyTo(T[] array, int arrayIndex) 715 { 716 if (arrayIndex < 0) 717 { 718 throw new ArgumentOutOfRangeException(nameof(arrayIndex)); 719 } 720 if (arrayIndex + Count > array.Length) 721 { 722 throw new ArgumentException("Not enough space in the array", nameof(array)); 723 } 724 foreach (var item in this) 725 { 726 array[arrayIndex++] = item; 727 } 728 } 729 GetEnumerator()730 public IEnumerator<T> GetEnumerator() 731 { 732 return parent.list.Select(projection).GetEnumerator(); 733 } 734 Remove(T item)735 public bool Remove(T item) 736 { 737 throw new NotSupportedException(); 738 } 739 IEnumerable.GetEnumerator()740 IEnumerator IEnumerable.GetEnumerator() 741 { 742 return GetEnumerator(); 743 } 744 CopyTo(Array array, int index)745 public void CopyTo(Array array, int index) 746 { 747 if (index < 0) 748 { 749 throw new ArgumentOutOfRangeException(nameof(index)); 750 } 751 if (index + Count > array.Length) 752 { 753 throw new ArgumentException("Not enough space in the array", nameof(array)); 754 } 755 foreach (var item in this) 756 { 757 array.SetValue(item, index++); 758 } 759 } 760 } 761 } 762 } 763