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