-
-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathArraySection.cs
1017 lines (869 loc) · 52.2 KB
/
ArraySection.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#region Copyright
///////////////////////////////////////////////////////////////////////////////
// File: ArraySection.cs
///////////////////////////////////////////////////////////////////////////////
// Copyright (C) KGy SOFT, 2005-2024 - All Rights Reserved
//
// You should have received a copy of the LICENSE file at the top-level
// directory of this distribution.
//
// Please refer to the LICENSE file if you want to use this source code.
///////////////////////////////////////////////////////////////////////////////
#endregion
#region Usings
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
#if NETCOREAPP || NETSTANDARD2_1_OR_GREATER
using System.Buffers;
#endif
using System.Runtime.Serialization;
using System.Security;
using KGySoft.CoreLibraries;
using KGySoft.Reflection;
#endregion
#region Suppressions
#if !(NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER)
#pragma warning disable CS1574 // the documentation contains types that are not available in every target
#endif
#if !NET5_0_OR_GREATER
// ReSharper disable UnusedMember.Local - ArraySectionDebugView.Items
#endif
#endregion
namespace KGySoft.Collections
{
/// <summary>
/// Represents a one dimensional array or a section of an array.
/// This type is very similar to <see cref="ArraySegment{T}"/>/<see cref="Memory{T}"><![CDATA[Memory<T>]]></see> types but can be used on every platform in the same way,
/// allows span-like operations such as slicing, and it is faster than <see cref="Memory{T}"><![CDATA[Memory<T>]]></see> in most cases.
/// Depending on the used platform it supports <see cref="ArrayPool{T}"/> allocation.
/// <div style="display: none;"><br/>See the <a href="https://docs.kgysoft.net/corelibraries/html/T_KGySoft_Collections_ArraySection_1.htm">online help</a> for examples.</div>
/// </summary>
/// <typeparam name="T">The type of the elements in the collection.</typeparam>
/// <remarks>
/// <para>The <see cref="ArraySection{T}"/> type is similar to the combination of the <see cref="Memory{T}"/> type and the .NET Core version of the <see cref="ArraySegment{T}"/> type.</para>
/// <para>In .NET Core 2.1/.NET Standard 2.1 and above an <see cref="ArraySection{T}"/> instance can be easily turned to a <see cref="Span{T}"/> instance (either by cast or by the <see cref="AsSpan"/> property),
/// which is much faster than using the <see cref="Memory{T}.Span">Span</see> property of a <see cref="Memory{T}"/> instance.</para>
/// <para>If an <see cref="ArraySection{T}"/> is created by the <see cref="ArraySection{T}(int,bool)">constructor with a specified size</see>, then depending on the size and the
/// current platform the underlying array might be obtained by using the <see cref="ArrayPool{T}"/>.
/// <note>An <see cref="ArraySection{T}"/> instance that was instantiated by the <see cref="ArraySection{T}(int,bool)">self allocating constructor</see> must be released by calling the <see cref="Release">Release</see>
/// method when it is not used anymore. The <see cref="ArraySection{T}"/> type does not implement <see cref="IDisposable"/> because releasing is not required when <see cref="ArraySection{T}"/> is created
/// from an existing array but not calling it when it would be needed may lead to decreased application performance.</note></para>
/// <para>Though <see cref="ArraySection{T}"/> is practically immutable (has only <see langword="readonly"/> fields) it is not marked as <c>readonly</c>,
/// which is needed for the <see cref="Release">Release</see> method to work properly. As <see cref="ArraySection{T}"/> is a
/// non-<c>readonly</c> <see langword="struct"/> it is not recommended to use it as a <c>readonly</c> field; otherwise,
/// accessing its members would make the pre-C# 8.0 compilers to create defensive copies, which leads to a slight performance degradation.</para>
/// <note type="tip">
/// <list type="bullet">
/// <item>You can always easily reinterpret an <see cref="ArraySection{T}"/> instance as a two or three-dimensional array by the <see cref="AsArray2D">AsArray2D</see>
/// and <see cref="AsArray3D">AsArray3D</see> methods without any allocation on the heap.</item>
/// <item>You can also reinterpret the element type by the <see cref="Cast{TFrom,TTo}">Cast</see> method that returns a <see cref="CastArray{TFrom,TTo}"/> instance.
/// Casts are also available as two or three-dimensional views by the <see cref="Cast2D{TFrom,TTo}">Cast2D</see> and <see cref="Cast3D{TFrom,TTo}">Cast3D</see> methods.</item>
/// </list></note>
/// </remarks>
/// <example>
/// The following example demonstrates how to get various single and multidimensional views for an array without any heap allocation:
/// <code lang="C#"><![CDATA[
/// // The actual underlying buffer we want to use.
/// var buffer = new byte[256];
///
/// // Note that none of the lines below allocate anything on the heap.
///
/// // So far similar to AsSpan or AsMemory extensions, but this is available on all platforms:
/// ArraySection<byte> section = buffer.AsSection(25, 100); // 100 bytes starting at index 25
///
/// // But if you wish you can treat it as a 10x10 two-dimensional array:
/// Array2D<byte> as2d = section.AsArray2D(10, 10); // an Array3D can be created in a similar way
///
/// // 2D indexing works the same way as for a real multidimensional array. But this is actually faster:
/// byte element = as2d[2, 3]; // row index first, then column index (similarly to regular arrays)
///
/// // Slicing works the same way as for ArraySection/Spans:
/// Array2D<byte> someRows = as2d[1..^1]; // same as as2d.Slice(1, as2d.Height - 2)
///
/// // Or you can get a simple row:
/// ArraySection<byte> singleRow = as2d[0];
///
/// // You can even reinterpret the element type if you whish:
/// CastArray<byte, Color32> asColors = section.Cast<byte, Color32>();
///
/// // Now you can access the elements cast to the reinterpreted type:
/// Color32 c = asColors[0];
///
/// // Or the reference to them (if supported by the compiler you use):
/// ref Color32 cRef = ref asColors.GetElementReference(0);
///
/// // Casts also have their 2D/3D counterparts:
/// CastArray2D<byte, Color32> asColors2D = asColors.As2D(height: 4, width: 6);
///
/// // Same as above, but directly from the section:
/// asColors2D = section.Cast2D<byte, Color32>(4, 6);]]></code>
/// </example>
/// <seealso cref="Array2D{T}"/>
/// <seealso cref="Array3D{T}"/>
/// <seealso cref="CastArray{TFrom,TTo}"/>
/// <seealso cref="CastArray2D{TFrom,TTo}"/>
/// <seealso cref="CastArray3D{TFrom,TTo}"/>
[Serializable]
[DebuggerTypeProxy(typeof(ArraySection<>.ArraySectionDebugView))]
[DebuggerDisplay("{typeof(" + nameof(T) + ")." + nameof(Type.Name) + ",nq}[{" + nameof(Length) + "}]")]
public struct ArraySection<T> : IList<T>, IList, IEquatable<ArraySection<T>>
#if !(NET35 || NET40)
, IReadOnlyList<T>
#endif
{
#region Nested Types
private struct ArraySectionDebugView // strange error in VS2019: this must be a struct; otherwise, debug values will be completely misinterpreted
{
#region Fields
[SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "ArraySection is not readonly so it would generate defensive copies on platforms")]
private ArraySection<T> array;
#endregion
#region Properties
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
readonly public T[]? Items => array.ToArray();
#endregion
#region Constructors
internal ArraySectionDebugView(ArraySection<T> array) => this.array = array;
#endregion
}
#endregion
#region Constants
private const int poolArrayMask = 1 << 31;
private const int lengthMask = Int32.MaxValue;
#endregion
#region Fields
#region Static Fields
#region Public Fields
/// <summary>
/// Represents the <see langword="null"/> <see cref="ArraySection{T}"/>. This field is read-only.
/// </summary>
public static readonly ArraySection<T> Null = default;
/// <summary>
/// Represents the empty <see cref="ArraySection{T}"/>. This field is read-only.
/// </summary>
public static readonly ArraySection<T> Empty = new ArraySection<T>(Reflector<T>.EmptyArray);
#endregion
#region Private Fields
#if NETCOREAPP || NETSTANDARD2_1_OR_GREATER
private static readonly int poolingThreshold = Math.Max(2, 1024 / Reflector<T>.SizeOf);
#endif
#endregion
#endregion
#region Instance Fields
private readonly T[]? array;
private readonly int offset;
private readonly int length;
#endregion
#endregion
#region Properties and Indexers
#region Properties
#region Public Properties
/// <summary>
/// Gets the underlying array of this <see cref="ArraySection{T}"/>.
/// </summary>
[SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "Intended, same as for ArraySegment")]
public readonly T[]? UnderlyingArray => array;
/// <summary>
/// Gets the offset, which denotes the start position of this <see cref="ArraySection{T}"/> within the <see cref="UnderlyingArray"/>.
/// </summary>
public readonly int Offset => offset;
/// <summary>
/// Gets the number of elements in this <see cref="ArraySection{T}"/>.
/// </summary>
#if NETCOREAPP || NETSTANDARD2_1_OR_GREATER
public readonly int Length => length & lengthMask;
#else
public readonly int Length => length;
#endif
/// <summary>
/// Gets whether this <see cref="ArraySection{T}"/> instance represents a <see langword="null"/> array.
/// <br/>Please note that the <see cref="ToArray">ToArray</see> method returns <see langword="null"/> when this property returns <see langword="true"/>.
/// </summary>
public readonly bool IsNull => array == null;
/// <summary>
/// Gets whether this <see cref="ArraySection{T}"/> instance represents an empty array section or a <see langword="null"/> array.
/// </summary>
public readonly bool IsNullOrEmpty => length == 0; // empty sections are never pooled so we can use the field here
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
/// <summary>
/// Returns this <see cref="ArraySection{T}"/> as a <see cref="Memory{T}"/> instance.
/// </summary>
/// <remarks><note>This member is available in .NET Core 2.1/.NET Standard 2.1 and above.</note></remarks>
public readonly Memory<T> AsMemory => new Memory<T>(array, offset, Length);
/// <summary>
/// Returns this <see cref="ArraySection{T}"/> as a <see cref="Span{T}"/> instance.
/// </summary>
/// <remarks><note>This member is available in .NET Core 2.1/.NET Standard 2.1 and above.</note></remarks>
public readonly Span<T> AsSpan => new Span<T>(array, offset, Length);
#endif
/// <summary>
/// Returns the current <see cref="ArraySection{T}"/> instance as an <see cref="ArraySegment{T}"/>.
/// </summary>
public readonly ArraySegment<T> AsArraySegment => array == null ? default : new ArraySegment<T>(array, offset, Length);
#endregion
#region Explicitly Implemented Interface Properties
readonly bool ICollection<T>.IsReadOnly => true;
readonly int ICollection<T>.Count => Length;
// It actually should use a private field but as we never lock on this we could never cause a deadlock even if someone uses it.
readonly object ICollection.SyncRoot => array?.SyncRoot ?? Throw.InvalidOperationException<object>(Res.ArraySectionNull);
readonly bool ICollection.IsSynchronized => false;
readonly int ICollection.Count => Length;
readonly bool IList.IsReadOnly => false;
readonly bool IList.IsFixedSize => true;
#if !(NET35 || NET40)
readonly int IReadOnlyCollection<T>.Count => Length;
#endif
#endregion
#endregion
#region Indexers
#region Public Indexers
/// <summary>
/// Gets or sets the element at the specified <paramref name="index"/>.
/// </summary>
/// <param name="index">The zero-based index of the element to get or set.</param>
/// <returns>The element at the specified index.</returns>
/// <remarks>
/// <para>This member validates <paramref name="index"/> against <see cref="Length"/>. To allow getting/setting any element in the <see cref="UnderlyingArray"/> use
/// the <see cref="GetElementUnchecked">GetElementUnchecked</see>/<see cref="SetElementUnchecked">SetElementUnchecked</see> methods instead.</para>
/// <para>If the compiler you use supports members that return a value by reference, you can also use the <see cref="GetElementReference">GetElementReference</see> method.</para>
/// </remarks>
/// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is less than zero or greater or equal to <see cref="Length"/>.</exception>
public readonly T this[int index]
{
// NOTE: We could simply use just a ref returning indexer and implement IList<TTo>.this[int] explicitly,
// but that may cause a "not supported by the language" error when this library is used by older compilers that try to access the indexer.
[MethodImpl(MethodImpl.AggressiveInlining)]
get
{
if ((uint)index >= (uint)Length)
Throw.IndexOutOfRangeException();
return GetItemInternal(index);
}
[MethodImpl(MethodImpl.AggressiveInlining)]
set
{
if ((uint)index >= (uint)Length)
Throw.IndexOutOfRangeException();
array![offset + index] = value;
}
}
#endregion
#region Explicitly Implemented Interface Indexers
object? IList.this[int index]
{
readonly get => this[index];
set
{
Throw.ThrowIfNullIsInvalid<T>(value);
try
{
this[index] = (T)value!;
}
catch (InvalidCastException)
{
Throw.ArgumentException(Argument.value, Res.ICollectionNonGenericValueTypeInvalid(value, typeof(T)));
}
}
}
#endregion
#endregion
#endregion
#region Operators
/// <summary>
/// Performs an implicit conversion from array of <typeparamref name="T"/> to <see cref="ArraySection{T}"/>.
/// </summary>
/// <param name="array">The array to be converted to an <see cref="ArraySection{T}"/>.</param>
/// <returns>
/// An <see cref="ArraySection{T}"/> instance that represents the original array.
/// </returns>
public static implicit operator ArraySection<T>(T[]? array) => array == null ? Null : new ArraySection<T>(array);
/// <summary>
/// Performs an implicit conversion from <see cref="ArraySegment{T}"/> to <see cref="ArraySection{T}"/>.
/// </summary>
/// <param name="arraySegment">The <see cref="ArraySegment{T}"/> to be converted to an <see cref="ArraySection{T}"/>.</param>
/// <returns>
/// An <see cref="ArraySection{T}"/> instance that represents the original <see cref="ArraySegment{T}"/>.
/// </returns>
public static implicit operator ArraySection<T>(ArraySegment<T> arraySegment) => new ArraySection<T>(arraySegment);
/// <summary>
/// Performs an implicit conversion from <see cref="ArraySection{T}"/> to <see cref="ArraySegment{T}"/>.
/// </summary>
/// <param name="arraySection">The <see cref="ArraySection{T}"/> to be converted to an <see cref="ArraySegment{T}"/>.</param>
/// <returns>
/// An <see cref="ArraySegment{T}"/> instance that represents this <see cref="ArraySection{T}"/>.
/// </returns>
public static implicit operator ArraySegment<T>(ArraySection<T> arraySection) => arraySection.AsArraySegment;
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
/// <summary>
/// Performs an implicit conversion from <see cref="ArraySection{T}"/> to <see cref="Span{T}"/>.
/// </summary>
/// <param name="arraySection">The <see cref="ArraySection{T}"/> to be converted to a <see cref="Span{T}"/>.</param>
/// <returns>
/// A <see cref="Span{T}"/> instance that represents the specified <see cref="ArraySection{T}"/>.
/// </returns>
public static implicit operator Span<T>(ArraySection<T> arraySection) => arraySection.AsSpan;
#endif
/// <summary>
/// Determines whether two specified <see cref="ArraySection{T}"/> instances have the same value.
/// </summary>
/// <param name="a">The left argument of the equality check.</param>
/// <param name="b">The right argument of the equality check.</param>
/// <returns>The result of the equality check.</returns>
public static bool operator ==(ArraySection<T> a, ArraySection<T> b) => a.Equals(b);
/// <summary>
/// Determines whether two specified <see cref="ArraySection{T}"/> instances have different values.
/// </summary>
/// <param name="a">The left argument of the inequality check.</param>
/// <param name="b">The right argument of the inequality check.</param>
/// <returns>The result of the inequality check.</returns>
public static bool operator !=(ArraySection<T> a, ArraySection<T> b) => !(a == b);
#endregion
#region Constructors
#region Public Constructors
/// <summary>
/// Initializes a new instance of the <see cref="ArraySection{T}" /> struct using an internally allocated buffer.
/// <br/>When using this overload, the returned <see cref="ArraySection{T}"/> instance must be released
/// by the <see cref="Release">Release</see> method if it is not used anymore.
/// </summary>
/// <param name="length">The length of the <see cref="ArraySection{T}"/> to be created.</param>
/// <param name="assureClean"><see langword="true"/> to make sure the allocated underlying array is zero-initialized;
/// otherwise, <see langword="false"/>. May not have an effect on older targeted platforms. This parameter is optional.
/// <br/>Default value: <see langword="true"/>.</param>
#if NETFRAMEWORK || NETSTANDARD2_0
[SuppressMessage("ReSharper", "UnusedParameter.Local", Justification = "Used in .NET Core 2.1/Standard 2.1 and above")]
#endif
public ArraySection(int length, bool assureClean = true)
{
if (length < 0)
Throw.ArgumentOutOfRangeException(Argument.length);
offset = 0;
this.length = length;
#if NETCOREAPP || NETSTANDARD2_1_OR_GREATER
if (length >= poolingThreshold)
{
this.length |= poolArrayMask;
array = ArrayPool<T>.Shared.Rent(length);
if (assureClean)
Clear();
return;
}
#endif
if (length == 0)
{
array = Reflector.EmptyArray<T>();
return;
}
#if NET5_0_OR_GREATER
if (!assureClean)
{
array = GC.AllocateUninitializedArray<T>(length);
return;
}
#endif
array = new T[length];
}
/// <summary>
/// Initializes a new instance of the <see cref="ArraySection{T}" /> struct from the specified <paramref name="array"/>.
/// No heap allocation occurs when using this constructor overload.
/// </summary>
/// <param name="array">The array to initialize the new <see cref="ArraySection{T}"/> instance from.</param>
[SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "False alarm, array CAN be null, it is just not ALLOWED (exception is thrown from the overload)")]
public ArraySection(T[] array) : this(array, 0, array?.Length ?? 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ArraySection{T}" /> struct from the specified <paramref name="array"/>
/// using the specified <paramref name="offset"/> and <paramref name="length"/>.
/// No heap allocation occurs when using this constructor overload.
/// </summary>
/// <param name="array">The array to initialize the new <see cref="ArraySection{T}"/> instance from.</param>
/// <param name="offset">The index of the first element in the <paramref name="array"/> to include in the new <see cref="ArraySection{T}"/>.</param>
/// <param name="length">The number of items to include in the new <see cref="ArraySection{T}"/>.</param>
public ArraySection(T[] array, int offset, int length)
{
if (array == null!)
Throw.ArgumentNullException(Argument.array);
if ((uint)offset > (uint)array.Length)
Throw.ArgumentOutOfRangeException(Argument.offset);
if ((uint)length > (uint)(array.Length - offset))
Throw.ArgumentOutOfRangeException(Argument.length);
this.array = array;
this.offset = offset;
this.length = length;
}
/// <summary>
/// Initializes a new instance of the <see cref="ArraySection{T}" /> struct from the specified <paramref name="array"/>
/// using the specified <paramref name="offset"/>.
/// No heap allocation occurs when using this constructor overload.
/// </summary>
/// <param name="array">The array to initialize the new <see cref="ArraySection{T}"/> instance from.</param>
/// <param name="offset">The index of the first element in the <paramref name="array"/> to include in the new <see cref="ArraySection{T}"/>.</param>
[SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "False alarm, array CAN be null, it is just not ALLOWED (exception is thrown from the overload)")]
public ArraySection(T[] array, int offset) : this(array, offset, (array?.Length ?? 0) - offset)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ArraySection{T}" /> struct from the specified <see cref="ArraySegment{T}"/>.
/// No heap allocation occurs when using this constructor overload.
/// </summary>
/// <param name="arraySegment">The <see cref="ArraySegment{T}"/> to initialize the new <see cref="ArraySection{T}"/> from.</param>
public ArraySection(ArraySegment<T> arraySegment)
{
if (arraySegment.Array == null)
{
this = Null;
return;
}
array = arraySegment.Array;
offset = arraySegment.Offset;
length = arraySegment.Count;
}
#endregion
#region Private Constructors
private ArraySection(T[]? array, int offset, int length, bool _)
{
this.array = array;
this.offset = offset;
this.length = length;
}
#endregion
#endregion
#region Methods
#region Static Methods
private static bool CanAccept(object? value) => value is T || value == null && default(T) == null;
#endregion
#region Instance Methods
#region Public Methods
/// <summary>
/// Clears the items in this <see cref="ArraySection{T}"/> instance so all elements will have the default value of type <typeparamref name="T"/>.
/// To clear the items with a specific value use the <see cref="Fill">Fill</see> method instead.
/// </summary>
public readonly void Clear()
{
if (length == 0) // zero length sections never have pooled arrays so using the field is alright here
return;
Array.Clear(array!, offset, Length);
}
/// <summary>
/// Assigns the specified <paramref name="value"/> to all elements in this <see cref="ArraySection{T}"/> instance.
/// </summary>
/// <param name="value">The value to assign to all elements.</param>
public readonly void Fill(T value)
{
if (length == 0) // zero length sections never have pooled arrays so using the field is alright here
return;
#if NETCOREAPP || NETSTANDARD2_1_OR_GREATER
Array.Fill(array!, value, offset, Length);
#else
int end = offset + Length;
for (int i = offset; i < end; i++)
array![i] = value;
#endif
}
/// <summary>
/// Gets a new <see cref="ArraySection{T}"/> instance, which represents a subsection of the current instance with the specified <paramref name="startIndex"/>.
/// </summary>
/// <param name="startIndex">The offset that points to the first item of the returned section.</param>
/// <returns>The subsection of the current <see cref="ArraySection{T}"/> instance with the specified <paramref name="startIndex"/>.</returns>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="startIndex"/> is out of range.</exception>
public readonly ArraySection<T> Slice(int startIndex) => Slice(startIndex, Length - startIndex);
/// <summary>
/// Gets a new <see cref="ArraySection{T}"/> instance, which represents a subsection of the current instance with the specified <paramref name="startIndex"/> and <paramref name="length"/>.
/// </summary>
/// <param name="startIndex">The offset that points to the first item of the returned section.</param>
/// <param name="length">The desired length of the returned section.</param>
/// <returns>The subsection of the current <see cref="ArraySection{T}"/> instance with the specified <paramref name="startIndex"/> and <paramref name="length"/>.</returns>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="startIndex"/> or <paramref name="length"/> is out of range.</exception>
[SuppressMessage("ReSharper", "ParameterHidesMember", Justification = "Intended because it will be the new length of the returned instance")]
[MethodImpl(MethodImpl.AggressiveInlining)]
public readonly ArraySection<T> Slice(int startIndex, int length)
{
if (!IsNull)
return new ArraySection<T>(array!, offset + startIndex, length);
if (startIndex != 0)
Throw.ArgumentOutOfRangeException(Argument.offset); // to be compatible with the non-null case above
if (length != 0)
Throw.ArgumentOutOfRangeException(Argument.length);
return Null;
}
/// <summary>
/// Returns a reference to the first element in this <see cref="ArraySection{T}"/>.
/// This makes possible to use the <see cref="ArraySection{T}"/> in a <see langword="fixed"/> statement.
/// </summary>
/// <returns>A reference to the first element in this <see cref="ArraySection{T}"/>.</returns>
/// <exception cref="InvalidOperationException"><see cref="IsNullOrEmpty"/> is <see langword="true"/>.</exception>
/// <exception cref="VerificationException">.NET Framework only: you execute this method in a partially trusted <see cref="AppDomain"/> that does not allow executing unverifiable code.</exception>
public readonly ref T GetPinnableReference()
{
if (IsNullOrEmpty)
Throw.InvalidOperationException(Res.ArraySectionEmpty);
return ref GetStartElementReferenceInternal();
}
/// <summary>
/// Copies the elements of this <see cref="ArraySection{T}"/> to a new array.
/// </summary>
/// <returns>An array containing copies of the elements of this <see cref="ArraySection{T}"/>,
/// or <see langword="null"/> if <see cref="IsNull"/> is <see langword="true"/>.</returns>
public readonly T[]? ToArray()
{
if (length == 0) // it's alright, pooled arrays never have zero length
return IsNull ? null : Reflector.EmptyArray<T>();
T[] result = new T[Length];
array!.CopyElements(offset, result, 0, result.Length);
return result;
}
/// <summary>
/// Gets this <see cref="ArraySection{T}"/> as an <see cref="Array2D{T}"/> instance
/// using the specified <paramref name="height"/> and <paramref name="width"/>.
/// The <see cref="ArraySection{T}"/> must have enough capacity for the specified dimensions.
/// </summary>
/// <param name="height">The height of the array to be returned.</param>
/// <param name="width">The width of the array to be returned.</param>
/// <returns>An <see cref="Array2D{T}"/> instance using this <see cref="ArraySection{T}"/> as its underlying buffer that has the specified dimensions.</returns>
public readonly Array2D<T> AsArray2D(int height, int width) => new Array2D<T>(this, height, width);
/// <summary>
/// Gets this <see cref="ArraySection{T}"/> as an <see cref="Array3D{T}"/> instance
/// using the specified <paramref name="height"/> and <paramref name="width"/>.
/// The <see cref="ArraySection{T}"/> must have enough capacity for the specified dimensions.
/// </summary>
/// <param name="depth">The depth of the array to be returned.</param>
/// <param name="height">The height of the array to be returned.</param>
/// <param name="width">The width of the array to be returned.</param>
/// <returns>An <see cref="Array3D{T}"/> instance using this <see cref="ArraySection{T}"/> as its underlying buffer that has the specified dimensions.</returns>
public readonly Array3D<T> AsArray3D(int depth, int height, int width) => new Array3D<T>(this, depth, height, width);
/// <summary>
/// Reinterprets this <see cref="ArraySection{T}"/> by returning a <see cref="CastArray{TFrom,TTo}"/> struct,
/// so its element type is cast from <typeparamref name="TFrom"/> to <typeparamref name="TTo"/>.
/// This method can be used only when <typeparamref name="T"/> in this <see cref="ArraySection{T}"/> is a value type that contains no references.
/// </summary>
/// <typeparam name="TFrom">The actual element type of this <see cref="ArraySection{T}"/>. Must be the same as <typeparamref name="T"/>.</typeparam>
/// <typeparam name="TTo">The reinterpreted element type after casting.</typeparam>
/// <returns>A <see cref="CastArray{TFrom,TTo}"/> instance for this <see cref="ArraySection{T}"/>.</returns>
/// <remarks>
/// <para>If the size of <typeparamref name="TTo"/> cannot be divided by the size of <typeparamref name="TFrom"/>,
/// then the cast result may not cover the whole original <see cref="ArraySection{T}"/> to prevent exceeding beyond the available buffer.</para>
/// </remarks>
public readonly CastArray<TFrom, TTo> Cast<TFrom, TTo>()
#if NETFRAMEWORK // To make the method compatible with older compilers
where TFrom : struct, T
where TTo : struct
#else
where TFrom : unmanaged, T
where TTo : unmanaged
#endif
{
#if NETCOREAPP3_0_OR_GREATER
return new CastArray<TFrom, TTo>(Unsafe.As<ArraySection<T>, ArraySection<TFrom>>(ref Unsafe.AsRef(in this)));
#else
return new CastArray<TFrom, TTo>((ArraySection<TFrom>)(object)this);
#endif
}
/// <summary>
/// Reinterprets this <see cref="ArraySection{T}"/> as a two-dimensional <see cref="CastArray2D{TFrom,TTo}"/> struct,
/// while its element type is cast from <typeparamref name="TFrom"/> to <typeparamref name="TTo"/>.
/// This method can be used only when <typeparamref name="T"/> in this <see cref="ArraySection{T}"/> is a value type that contains no references.
/// </summary>
/// <typeparam name="TFrom">The actual element type of this <see cref="ArraySection{T}"/>. Must be the same as <typeparamref name="T"/>.</typeparam>
/// <typeparam name="TTo">The reinterpreted element type after casting.</typeparam>
/// <param name="height">The height of the array to be returned.</param>
/// <param name="width">The width of the array to be returned.</param>
/// <returns>A <see cref="CastArray2D{TFrom,TTo}"/> instance for this <see cref="ArraySection{T}"/>.</returns>
public readonly CastArray2D<TFrom, TTo> Cast2D<TFrom, TTo>(int height, int width)
#if NETFRAMEWORK // To make the method compatible with older compilers
where TFrom : struct, T
where TTo : struct
#else
where TFrom : unmanaged, T
where TTo : unmanaged
#endif
{
#if NETCOREAPP3_0_OR_GREATER
return new CastArray2D<TFrom, TTo>(Unsafe.As<ArraySection<T>, ArraySection<TFrom>>(ref Unsafe.AsRef(in this)), height, width);
#else
return new CastArray2D<TFrom, TTo>((ArraySection<TFrom>)(object)this, height, width);
#endif
}
/// <summary>
/// Reinterprets this <see cref="ArraySection{T}"/> as a three-dimensional <see cref="CastArray2D{TFrom,TTo}"/> struct,
/// while its element type is cast from <typeparamref name="TFrom"/> to <typeparamref name="TTo"/>.
/// This method can be used only when <typeparamref name="T"/> in this <see cref="ArraySection{T}"/> is a value type that contains no references.
/// </summary>
/// <typeparam name="TFrom">The actual element type of this <see cref="ArraySection{T}"/>. Must be the same as <typeparamref name="T"/>.</typeparam>
/// <typeparam name="TTo">The reinterpreted element type after casting.</typeparam>
/// <param name="depth">The depth of the array to be returned.</param>
/// <param name="height">The height of the array to be returned.</param>
/// <param name="width">The width of the array to be returned.</param>
/// <returns>A <see cref="CastArray2D{TFrom,TTo}"/> instance for this <see cref="ArraySection{T}"/>.</returns>
public readonly CastArray3D<TFrom, TTo> Cast3D<TFrom, TTo>(int depth, int height, int width)
#if NETFRAMEWORK // To make the method compatible with older compilers
where TFrom : struct, T
where TTo : struct
#else
where TFrom : unmanaged, T
where TTo : unmanaged
#endif
{
#if NETCOREAPP3_0_OR_GREATER
return new CastArray3D<TFrom, TTo>(Unsafe.As<ArraySection<T>, ArraySection<TFrom>>(ref Unsafe.AsRef(in this)), depth, height, width);
#else
return new CastArray3D<TFrom, TTo>((ArraySection<TFrom>)(object)this, depth, height, width);
#endif
}
/// <summary>
/// Gets the reference to the element at the specified <paramref name="index"/>.
/// </summary>
/// <param name="index">The index of the element to get the reference for.</param>
/// <returns>The reference to the element at the specified index.</returns>
/// <remarks>
/// <para>This method validates <paramref name="index"/> against <see cref="Length"/>.
/// To allow returning a reference to any element from the <see cref="UnderlyingArray"/>
/// (allowing even a negative <paramref name="index"/> if <see cref="Offset"/> is nonzero),
/// then use the <see cref="GetElementReferenceUnchecked">GetElementReferenceUnchecked</see> method instead.</para>
/// <note>This method returns a value by reference. If this library is used by an older compiler that does not support such members,
/// use the <see cref="this">indexer</see> instead.</note>
/// </remarks>
/// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is less than zero or greater or equal to <see cref="Length"/>.</exception>
/// <exception cref="VerificationException">.NET Framework only: you execute this method in a partially trusted <see cref="AppDomain"/> that does not allow executing unverifiable code.</exception>
[MethodImpl(MethodImpl.AggressiveInlining)]
public readonly ref T GetElementReference(int index)
{
if ((uint)index >= (uint)Length)
Throw.IndexOutOfRangeException();
return ref GetElementReferenceInternal(index);
}
/// <summary>
/// Gets the element at the specified <paramref name="index"/>, allowing it to point to any element in the <see cref="UnderlyingArray"/>.
/// To validate <paramref name="index"/> against <see cref="Length"/> use the <see cref="this">indexer</see> instead.
/// This method does not perform any validation, so it can even throw a <see cref="NullReferenceException"/> if the <see cref="IsNull"/> property returns <see langword="true"/>.
/// </summary>
/// <param name="index">The index of the element to get.</param>
/// <returns>The element at the specified index.</returns>
/// <exception cref="IndexOutOfRangeException"><paramref name="index"/> plus <see cref="Offset"/> is less than zero or greater or equal to the length of the <see cref="UnderlyingArray"/>.</exception>
/// <exception cref="NullReferenceException">The <see cref="IsNull"/> property returns <see langword="true"/>.</exception>
/// <remarks>
/// <para>If the compiler you use supports members that return a value by reference, you can also use
/// the <see cref="GetElementReferenceUnchecked">GetElementReferenceUnchecked</see> method.</para>
/// </remarks>
[MethodImpl(MethodImpl.AggressiveInlining)]
public readonly T GetElementUnchecked(int index)
{
// In .NET 9 the following check makes this method even slower than the indexer. Which is strange because .NET 8 has a much better performance. See ArraySectionPerformanceTest.CastTest
//if (IsNull)
// Throw.IndexOutOfRangeException();
return GetItemInternal(index);
}
/// <summary>
/// Sets the element at the specified <paramref name="index"/>, allowing it to point to any element in the <see cref="UnderlyingArray"/>.
/// To validate <paramref name="index"/> against <see cref="Length"/> use the <see cref="this">indexer</see> instead.
/// This method does not perform any validation, so it can even throw a <see cref="NullReferenceException"/> if the <see cref="IsNull"/> property returns <see langword="true"/>.
/// </summary>
/// <param name="index">The index of the element to set.</param>
/// <param name="value">The value to set.</param>
/// <exception cref="IndexOutOfRangeException"><paramref name="index"/> plus <see cref="Offset"/> is less than zero or greater or equal to the length of the <see cref="UnderlyingArray"/>.</exception>
/// <exception cref="NullReferenceException">The <see cref="IsNull"/> property returns <see langword="true"/>.</exception>
/// <remarks>
/// <para>If the compiler you use supports members that return a value by reference, you can also use
/// the <see cref="GetElementReferenceUnchecked">GetElementReferenceUnchecked</see> method.</para>
/// </remarks>
[MethodImpl(MethodImpl.AggressiveInlining)]
public readonly void SetElementUnchecked(int index, T value)
{
//if (IsNull)
// Throw.IndexOutOfRangeException();
SetItemInternal(index, value);
}
/// <summary>
/// Gets the reference to the element at the specified <paramref name="index"/>, allowing it to point to any element in the <see cref="UnderlyingArray"/>.
/// To validate <paramref name="index"/> against <see cref="Length"/> use the <see cref="GetElementReference">GetElementReference</see> method instead.
/// This method does not perform any validation, so it can even throw a <see cref="NullReferenceException"/> if the <see cref="IsNull"/> property returns <see langword="true"/>.
/// </summary>
/// <param name="index">The index of the element to get the reference for.</param>
/// <returns>The reference to the element at the specified index.</returns>
/// <exception cref="IndexOutOfRangeException"><paramref name="index"/> plus <see cref="Offset"/> is less than zero or greater or equal to the length of the <see cref="UnderlyingArray"/>.</exception>
/// <exception cref="VerificationException">.NET Framework only: you execute this method in a partially trusted <see cref="AppDomain"/> that does not allow executing unverifiable code.</exception>
/// <exception cref="NullReferenceException">The <see cref="IsNull"/> property returns <see langword="true"/>.</exception>
/// <remarks>
/// <note>This method returns a value by reference. If this library is used by an older compiler that does not support such members,
/// use the <see cref="GetElementUnchecked">GetElementUnchecked</see>/<see cref="SetElementUnchecked">SetElementUnchecked</see> methods instead.</note>
/// </remarks>
[MethodImpl(MethodImpl.AggressiveInlining)]
public readonly ref T GetElementReferenceUnchecked(int index)
{
//if (IsNull)
// Throw.IndexOutOfRangeException();
return ref GetElementReferenceInternal(index);
}
/// <summary>
/// Determines the index of a specific item in this <see cref="ArraySection{T}"/>.
/// </summary>
/// <param name="item">The object to locate in the <see cref="ArraySection{T}"/>.</param>
/// <returns>
/// The index of <paramref name="item"/> if found in the list; otherwise, -1.
/// </returns>
public readonly int IndexOf(T item)
{
if (length == 0) // it's alright, pooled arrays never have zero length
return -1;
int result = Array.IndexOf(array!, item, offset, Length);
return result < 0 ? result : result - offset;
}
/// <summary>
/// Determines whether this <see cref="ArraySection{T}"/> contains the specific <paramref name="item"/>.
/// </summary>
/// <returns>
/// <see langword="true"/> if <paramref name="item"/> is found in this <see cref="ArraySection{T}"/>; otherwise, <see langword="false"/>.
/// </returns>
/// <param name="item">The object to locate in this <see cref="ArraySection{T}"/>.</param>
public readonly bool Contains(T item) => IndexOf(item) >= 0;
/// <summary>
/// Copies the items of this <see cref="ArraySection{T}"/> to a compatible one-dimensional array, starting at a particular index.
/// </summary>
/// <param name="target">The one-dimensional <see cref="Array"/> that is the destination of the elements copied from this <see cref="ArraySection{T}"/>.</param>
/// <param name="targetIndex">The zero-based index in <paramref name="target"/> at which copying begins. This parameter is optional.
/// <br/>Default value: 0.</param>
public readonly void CopyTo(T[] target, int targetIndex = 0)
{
if (target == null!)
Throw.ArgumentNullException(Argument.target);
if (targetIndex < 0 || targetIndex > target.Length)
Throw.ArgumentOutOfRangeException(Argument.targetIndex);
int len = Length;
if (target.Length - targetIndex < len)
Throw.ArgumentException(Argument.target, Res.ICollectionCopyToDestArrayShort);
array?.CopyElements(offset, target, targetIndex, len);
}
/// <summary>
/// Copies the items of this <see cref="ArraySection{T}"/> to a compatible instance.
/// </summary>
/// <param name="target">The <see cref="ArraySection{T}"/> that is the destination of the elements copied from this instance.</param>
public readonly void CopyTo(ArraySection<T> target)
{
if (target.IsNull)
Throw.ArgumentNullException(Argument.target);
int len = Length;
if (target.length < len)
Throw.ArgumentException(Argument.target, Res.ICollectionCopyToDestArrayShort);
array?.CopyElements(offset, target.array!, target.offset, len);
}
/// <summary>
/// Returns an enumerator that iterates through the items of this <see cref="ArraySection{T}"/>.
/// </summary>
/// <returns>An <see cref="ArraySectionEnumerator{T}"/> instance that can be used to iterate though the elements of this <see cref="ArraySection{T}"/>.</returns>
/// <remarks>
/// <note>The returned enumerator supports the <see cref="IEnumerator.Reset">IEnumerator.Reset</see> method.</note>
/// </remarks>
public readonly ArraySectionEnumerator<T> GetEnumerator() => new ArraySectionEnumerator<T>(array, offset, Length);
/// <summary>
/// Releases the underlying array. If this <see cref="ArraySection{T}"/> instance was instantiated by the <see cref="ArraySection{T}(int,bool)">self allocating constructor</see>,
/// then this method must be called when the <see cref="ArraySection{T}"/> is not used anymore.
/// On platforms that do not support the <see cref="ArrayPool{T}"/> class this method simply sets the self instance to <see cref="Null"/>.
/// </summary>
public void Release()
{
#if NETCOREAPP || NETSTANDARD2_1_OR_GREATER
if (array != null && (length & poolArrayMask) != 0)
{
if (Reflector<T>.IsManaged)
Array.Clear(array, offset, Length);
ArrayPool<T>.Shared.Return(array);
}
#endif
// this is required to prevent possible multiple returns to ArrayPool
this = Null;
}
/// <summary>
/// Indicates whether the current <see cref="ArraySection{T}"/> instance is equal to another one specified in the <paramref name="other"/> parameter.
/// That is, when they both reference the same section of the same <see cref="UnderlyingArray"/> instance.
/// </summary>
/// <param name="other">An <see cref="ArraySection{T}"/> instance to compare with this instance.</param>
/// <returns><see langword="true"/> if the current object is equal to the <paramref name="other"/> parameter; otherwise, <see langword="false"/>.</returns>
public readonly bool Equals(ArraySection<T> other)
=> array == other.array && offset == other.offset && Length == other.Length; // Ignoring the pool array bit is intended.
/// <summary>
/// Determines whether the specified <see cref="object">object</see> is equal to this instance.
/// </summary>
/// <param name="obj">The object to compare with this instance.</param>
/// <returns><see langword="true"/> if the specified object is equal to this instance; otherwise, <see langword="false"/>.</returns>
public readonly override bool Equals(object? obj)
=> obj == null ? IsNull
: obj is ArraySection<T> other ? Equals(other)
: obj is T[] arr && Equals(new ArraySection<T>(arr)); // must check array because == operator supports it as well
/// <summary>
/// Returns a hash code for this <see cref="ArraySection{T}"/> instance.
/// </summary>
/// <returns>
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// </returns>
public readonly override int GetHashCode() => array == null ? 0 : (array, offset, Length).GetHashCode();
#endregion
#region Internal Methods
[MethodImpl(MethodImpl.AggressiveInlining)]
internal readonly T GetItemInternal(int index) => array![offset + index];
[MethodImpl(MethodImpl.AggressiveInlining)]
internal readonly void SetItemInternal(int index, T value) => array![offset + index] = value;
[MethodImpl(MethodImpl.AggressiveInlining)]
internal readonly ref T GetElementReferenceInternal(int index) => ref array![offset + index];
[MethodImpl(MethodImpl.AggressiveInlining)]
internal readonly ref T GetStartElementReferenceInternal() => ref array![offset];
#endregion
#region Private Methods
[OnDeserialized]
[SuppressMessage("ReSharper", "UnusedParameter.Local", Justification = "False alarm, the [OnDeserialized] method must have this signature.")]
private void OnDeserialized(StreamingContext ctx)
{
// This method is just to clear the poolArray flag from length after deserialization. Applying also on older frameworks because the serialized instance
// may come from any platform. length &= lengthMask would be more obvious but that would require to make length non-readonly.
// NOTE: not using this = Slice(0, length & lengthMask) because if an ArraySection is a field in another struct (e.g. Array2D, CastArray, etc.),
// then array is still null when deserializing by BinaryFormatter (would work by BinarySerializationFormatter though, but it supports it natively anyway).
this = new ArraySection<T>(array, offset, length & lengthMask, default);
}
#endregion
#region Explicitly Implemented Interface Methods
readonly void IList<T>.Insert(int index, T item) => Throw.NotSupportedException(Res.ICollectionReadOnlyModifyNotSupported);
readonly void IList<T>.RemoveAt(int index) => Throw.NotSupportedException(Res.ICollectionReadOnlyModifyNotSupported);
readonly void ICollection<T>.Add(T item) => Throw.NotSupportedException(Res.ICollectionReadOnlyModifyNotSupported);
readonly bool ICollection<T>.Remove(T item) => Throw.NotSupportedException<bool>(Res.ICollectionReadOnlyModifyNotSupported);
readonly IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
readonly int IList.IndexOf(object? value) => CanAccept(value) ? IndexOf((T)value!) : -1;
readonly bool IList.Contains(object? value) => CanAccept(value) && Contains((T)value!);
readonly void ICollection.CopyTo(Array targetArray, int index)
{
if (targetArray == null!)
Throw.ArgumentNullException(Argument.array);
if (targetArray is T[] typedArray)
{
CopyTo(typedArray, index);
return;
}
int len = Length;
if (index < 0 || index > len)
Throw.ArgumentOutOfRangeException(Argument.index);
if (targetArray.Length - index < len)
Throw.ArgumentException(Argument.array, Res.ICollectionCopyToDestArrayShort);
if (targetArray.Rank != 1)
Throw.ArgumentException(Argument.array, Res.ICollectionCopyToSingleDimArrayOnly);
if (targetArray is object?[] objectArray)
{
for (int i = 0; i < len; i++)
{
objectArray[index] = GetItemInternal(i);
index += 1;
}
return;