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