-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
Description
PartitionedDataSource.InitializePartitions only checks for IList<T> here:
Lines 85 to 146 in 732f597
| if (source is IList<T> sourceAsList) | |
| { | |
| QueryOperatorEnumerator<T, int>[] partitions = new QueryOperatorEnumerator<T, int>[partitionCount]; | |
| // We use this below to specialize enumerators when possible. | |
| T[]? sourceAsArray = source as T[]; | |
| // If range partitioning is used, chunk size will be unlimited, i.e. -1. | |
| int maxChunkSize = -1; | |
| if (useStriping) | |
| { | |
| maxChunkSize = Scheduling.GetDefaultChunkSize<T>(); | |
| // The minimum chunk size is 1. | |
| if (maxChunkSize < 1) | |
| { | |
| maxChunkSize = 1; | |
| } | |
| } | |
| // Calculate indexes and construct enumerators that walk a subset of the input. | |
| for (int i = 0; i < partitionCount; i++) | |
| { | |
| if (sourceAsArray != null) | |
| { | |
| // If the source is an array, we can use a fast path below to index using | |
| // 'ldelem' instructions rather than making interface method calls. | |
| if (useStriping) | |
| { | |
| partitions[i] = new ArrayIndexRangeEnumerator(sourceAsArray, partitionCount, i, maxChunkSize); | |
| } | |
| else | |
| { | |
| partitions[i] = new ArrayContiguousIndexRangeEnumerator(sourceAsArray, partitionCount, i); | |
| } | |
| TraceHelpers.TraceInfo("ContiguousRangePartitionExchangeStream::MakePartitions - (array) #{0} {1}", i, maxChunkSize); | |
| } | |
| else | |
| { | |
| // Create a general purpose list enumerator object. | |
| if (useStriping) | |
| { | |
| partitions[i] = new ListIndexRangeEnumerator(sourceAsList, partitionCount, i, maxChunkSize); | |
| } | |
| else | |
| { | |
| partitions[i] = new ListContiguousIndexRangeEnumerator(sourceAsList, partitionCount, i); | |
| } | |
| TraceHelpers.TraceInfo("ContiguousRangePartitionExchangeStream::MakePartitions - (list) #{0} {1})", i, maxChunkSize); | |
| } | |
| } | |
| Debug.Assert(partitions.Length == partitionCount); | |
| _partitions = partitions; | |
| } | |
| else | |
| { | |
| // We couldn't use an in-place partition. Shucks. Defer to the other overload which | |
| // accepts an enumerator as input instead. | |
| _partitions = MakePartitions(source.GetEnumerator(), partitionCount); | |
| } |
ScanQueryOperator has paths specifically for IList<T> but not IReadOnlyList<T> here
Line 61 in 732f597
| if (_data is IList<TElement> dataAsList) |
Line 100 in 732f597
| return _data is IList<TElement> |
ParallelEnumerable.Count only uses the count from ICollection<T> and not IReadOnlyCollection<T> here
runtime/src/libraries/System.Linq.Parallel/src/System/Linq/ParallelEnumerable.cs
Line 1855 in 732f597
| if (sourceAsWrapper.WrappedEnumerable is ICollection<TSource> sourceAsCollection) |
runtime/src/libraries/System.Linq.Parallel/src/System/Linq/ParallelEnumerable.cs
Line 1926 in 732f597
| if (sourceAsWrapper.WrappedEnumerable is ICollection<TSource> sourceAsCollection) |
This looks like some holdover from the pre-netfx4.5 days. Consumers really shouldn't have to implement IList<T> with all the modifying members throwing exceptions for every read-only collection just to get better performance from PLINQ.