blob: 722cc92322ef98d93032b2389a7eb2eb94ed5c41 [file] [log] [blame]
Jon Skeete1e9d3e2022-03-28 11:31:18 +01001#region Copyright notice and license
2// Protocol Buffers - Google's data interchange format
3// Copyright 2015 Google Inc. All rights reserved.
Jon Skeete1e9d3e2022-03-28 11:31:18 +01004//
Joshua Habermanda40ff62023-09-09 09:13:31 -07005// 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
Jon Skeete1e9d3e2022-03-28 11:31:18 +01008#endregion
9
10using Google.Protobuf.Compatibility;
Jon Skeete1e9d3e2022-03-28 11:31:18 +010011using System;
Jon Skeete1e9d3e2022-03-28 11:31:18 +010012using System.Collections;
13using System.Collections.Generic;
James Newton-King67b9c762023-09-26 00:21:32 -070014using System.Diagnostics;
Jon Skeete1e9d3e2022-03-28 11:31:18 +010015using System.IO;
16using System.Linq;
17using System.Security;
18
19namespace 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>
James Newton-King67b9c762023-09-26 00:21:32 -070047 [DebuggerDisplay("Count = {Count}")]
48 [DebuggerTypeProxy(typeof(MapField<,>.MapFieldDebugView))]
Jon Skeet837db772022-06-01 09:18:05 +010049 public sealed class MapField<TKey, TValue> : IDeepCloneable<MapField<TKey, TValue>>, IDictionary<TKey, TValue>, IEquatable<MapField<TKey, TValue>>, IDictionary, IReadOnlyDictionary<TKey, TValue>
Jon Skeete1e9d3e2022-03-28 11:31:18 +010050 {
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.)
Erik Mavrinac9f58ee32022-06-23 02:05:16 -070055 private readonly Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> map = new(KeyEqualityComparer);
56 private readonly LinkedList<KeyValuePair<TKey, TValue>> list = new();
Jon Skeete1e9d3e2022-03-28 11:31:18 +010057
58 /// <summary>
59 /// Creates a deep clone of this object.
60 /// </summary>
61 /// <returns>
62 /// A deep clone of this object.
63 /// </returns>
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>
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>
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>
121 public bool Remove(TKey key)
122 {
123 ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700124 if (map.TryGetValue(key, out LinkedListNode<KeyValuePair<TKey, TValue>> node))
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100125 {
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>
144 public bool TryGetValue(TKey key, out TValue value)
145 {
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700146 if (map.TryGetValue(key, out LinkedListNode<KeyValuePair<TKey, TValue>> node))
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100147 {
148 value = node.Value.Value;
149 return true;
150 }
151 else
152 {
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700153 value = default;
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100154 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));
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700170 if (TryGetValue(key, out TValue value))
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100171 {
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 }
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100184 var pair = new KeyValuePair<TKey, TValue>(key, value);
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700185 if (map.TryGetValue(key, out LinkedListNode<KeyValuePair<TKey, TValue>> node))
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100186 {
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>
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700200 public ICollection<TKey> Keys => new MapView<TKey>(this, pair => pair.Key, ContainsKey);
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100201
202 /// <summary>
203 /// Gets a collection containing the values in the map.
204 /// </summary>
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700205 public ICollection<TValue> Values => new MapView<TValue>(this, pair => pair.Value, ContainsValue);
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100206
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>
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>
Jon Skeet86fa3592022-08-02 09:38:35 +0100221 /// 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>
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>
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100236 /// 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>
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700241 public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => list.GetEnumerator();
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100242
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>
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700249 IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100250
251 /// <summary>
252 /// Adds the specified item to the map.
253 /// </summary>
254 /// <param name="item">The item to add to the map.</param>
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700255 void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100256
257 /// <summary>
258 /// Removes all items from the map.
259 /// </summary>
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>
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700271 bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) =>
272 TryGetValue(item.Key, out TValue value) && ValueEqualityComparer.Equals(item.Value, value);
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100273
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>
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700279 void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) =>
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100280 list.CopyTo(array, arrayIndex);
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100281
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>
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 }
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700294 if (map.TryGetValue(item.Key, out LinkedListNode<KeyValuePair<TKey, TValue>> node) &&
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100295 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>
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700310 public int Count => list.Count;
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100311
312 /// <summary>
313 /// Gets a value indicating whether the map is read-only.
314 /// </summary>
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700315 public bool IsReadOnly => false;
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100316
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>
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700324 public override bool Equals(object other) => Equals(other as MapField<TKey, TValue>);
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100325
326 /// <summary>
327 /// Returns a hash code for this instance.
328 /// </summary>
329 /// <returns>
Lydon Chandra96100bf2023-09-26 00:40:39 -0700330 /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100331 /// </returns>
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>
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 {
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700369 if (!other.TryGetValue(pair.Key, out TValue value))
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100370 {
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>
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]
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>
430 public void WriteTo(CodedOutputStream output, Codec codec)
431 {
432 WriteContext.Initialize(output, out WriteContext ctx);
433 try
434 {
Lydon Chandra96100bf2023-09-26 00:40:39 -0700435 IEnumerable<KeyValuePair<TKey, TValue>> listToWrite = list;
436
437 if (output.Deterministic)
438 {
439 listToWrite = GetSortedListCopy(list);
440 }
441 WriteTo(ref ctx, codec, listToWrite);
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100442 }
443 finally
444 {
445 ctx.CopyStateTo(output);
446 }
447 }
448
Lydon Chandra96100bf2023-09-26 00:40:39 -0700449 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
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100466 /// <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]
473 public void WriteTo(ref WriteContext ctx, Codec codec)
474 {
Lydon Chandra96100bf2023-09-26 00:40:39 -0700475 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]
484 private void WriteTo(ref WriteContext ctx, Codec codec, IEnumerable<KeyValuePair<TKey, TValue>> listKvp)
485 {
486 foreach (var entry in listKvp)
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100487 {
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>
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
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>
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
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100535
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700536 void IDictionary.Add(object key, object value) => Add((TKey)key, (TValue)value);
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100537
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700538 bool IDictionary.Contains(object key) => key is TKey k && ContainsKey(k);
539
540 IDictionaryEnumerator IDictionary.GetEnumerator() => new DictionaryEnumerator(GetEnumerator());
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100541
542 void IDictionary.Remove(object key)
543 {
544 ProtoPreconditions.CheckNotNull(key, nameof(key));
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700545 if (key is TKey k)
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100546 {
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700547 Remove(k);
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100548 }
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100549 }
550
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
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700558 bool IDictionary.IsFixedSize => false;
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100559
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700560 ICollection IDictionary.Keys => (ICollection)Keys;
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100561
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700562 ICollection IDictionary.Values => (ICollection)Values;
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100563
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700564 bool ICollection.IsSynchronized => false;
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100565
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700566 object ICollection.SyncRoot => this;
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100567
568 object IDictionary.this[object key]
569 {
570 get
571 {
572 ProtoPreconditions.CheckNotNull(key, nameof(key));
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700573 if (key is TKey k)
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100574 {
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700575 TryGetValue(k, out TValue value);
576 return value;
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100577 }
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700578 return null;
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100579 }
580
581 set
582 {
583 this[(TKey)key] = (TValue)value;
584 }
585 }
586 #endregion
587
588 #region IReadOnlyDictionary explicit interface implementation
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100589 IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => Keys;
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100590 IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values;
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100591 #endregion
592
593 private class DictionaryEnumerator : IDictionaryEnumerator
594 {
595 private readonly IEnumerator<KeyValuePair<TKey, TValue>> enumerator;
596
597 internal DictionaryEnumerator(IEnumerator<KeyValuePair<TKey, TValue>> enumerator)
598 {
599 this.enumerator = enumerator;
600 }
601
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700602 public bool MoveNext() => enumerator.MoveNext();
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100603
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700604 public void Reset() => enumerator.Reset();
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100605
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700606 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;
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100610 }
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>
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
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
Lydon Chandra96100bf2023-09-26 00:40:39 -0700668 public int Count => parent.Count;
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100669
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700670 public bool IsReadOnly => true;
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100671
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700672 public bool IsSynchronized => false;
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100673
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700674 public object SyncRoot => parent;
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100675
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700676 public void Add(T item) => throw new NotSupportedException();
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100677
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700678 public void Clear() => throw new NotSupportedException();
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100679
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700680 public bool Contains(T item) => containsCheck(item);
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100681
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
698 public IEnumerator<T> GetEnumerator()
699 {
700 return parent.list.Select(projection).GetEnumerator();
701 }
702
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700703 public bool Remove(T item) => throw new NotSupportedException();
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100704
Erik Mavrinac9f58ee32022-06-23 02:05:16 -0700705 IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100706
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 }
James Newton-King67b9c762023-09-26 00:21:32 -0700723
724 private sealed class MapFieldDebugView
725 {
726 private readonly MapField<TKey, TValue> map;
727
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 }
Jon Skeete1e9d3e2022-03-28 11:31:18 +0100736 }
737}