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