Skip to content

Implement CopyTo on synchronized view lists - #127

Merged
neuecc merged 3 commits into
Cysharp:masterfrom
aetos382:issue-116
Oct 26, 2026
Merged

neuecc merged 3 commits into
Cysharp:masterfrom
aetos382:issue-116

Conversation

@aetos382

@aetos382 aetos382 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fix ICollection.CopyTo on synchronized view lists (fixes #116)

NotifyCollectionChangedSynchronizedViewList<TView> threw NotSupportedException from both CopyTo implementations.
Enumerable.ToList() takes the ICollection<T> fast path, so ToViewList().ToList() always threw on non-empty lists.

Adds public virtual void CopyTo(Span<TView> span), overridden by the three derived types, and implements both interface methods on top of it with the documented argument validation. ObservableList<T> gains CopyTo(Span<T>).

@aetos382
aetos382 requested review from neuecc and a lite review from Copilot September 1, 2026 12:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes CopyTo behavior for synchronized view lists so they can participate in the ICollection<T>.CopyTo fast path used by Enumerable.ToList(), addressing the ToViewList().ToList() exception reported in #116.

Changes:

  • Introduces CopyTo(Span<TView>) on NotifyCollectionChangedSynchronizedViewList<TView> and wires both ICollection<T>.CopyTo and ICollection.CopyTo to it with argument validation.
  • Implements/overrides CopyTo(Span<...>) in synchronized view list implementations.
  • Adds ObservableList<T>.CopyTo(Span<T>) and comprehensive unit tests for CopyTo on ObservableList<T> and synchronized view lists.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/ObservableCollections.Tests/CopyToTest.cs Adds coverage validating CopyTo semantics across ObservableList and synchronized view lists.
src/ObservableCollections/SynchronizedViewList.cs Implements CopyTo(Span<TView>) overrides on synchronized view lists.
src/ObservableCollections/ObservableList.OptimizeView.cs Delegates optimized view CopyTo(Span<T>) to the parent list.
src/ObservableCollections/ObservableList.cs Adds CopyTo(Span<T>) and refactors array CopyTo to use spans.
src/ObservableCollections/ObservableDictionary.Views.cs Cleans up usings/formatting.
src/ObservableCollections/IObservableCollection.cs Adds CopyTo(Span<TView>) and implements generic/non-generic CopyTo for view lists.
Suppressed comments (1)

src/ObservableCollections/IObservableCollection.cs:223

  • ICollection.CopyTo(Array, int) doesn't validate that arrayIndex is within the array's upper bound / that there is sufficient capacity before copying. In particular, if the collection is empty and the target is not TView[] (e.g., object[] as used by WPF), arrayIndex > array.Length will currently succeed silently because the foreach never executes.
            if (array is TView[] typedArray)
            {
                CopyTo(typedArray.AsSpan(arrayIndex));
            }

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 135 to +143
public void CopyTo(T[] array, int arrayIndex)
{
if (array is null)
{
throw new ArgumentNullException(nameof(array));
}

CopyTo(array.AsSpan(arrayIndex));
}
Comment on lines +188 to +196
void ICollection<TView>.CopyTo(TView[] array, int arrayIndex)
{
if (array is null)
{
throw new ArgumentNullException(nameof(array));
}

CopyTo(array.AsSpan(arrayIndex));
}
Comment on lines +110 to +120
/// <summary>
/// Ensures ArgumentOutOfRangeException is thrown when arrayIndex is negative.
/// </summary>
[Fact]
public void NegativeArrayIndexThrows()
{
Action act = () => CreateSource().CopyTo(new int[3], -1);

act.Should().ThrowExactly<ArgumentOutOfRangeException>();
}
}
@aetos382

Copy link
Copy Markdown
Contributor Author

I'm going to close and immediately reopen this PR to trigger PR Harness again.
Nothing in the PR itself changes.

The goal is to verify the fix for Cysharp/Actions#173 (Cysharp/Actions#175) in a real run.
That bug made unicode-security fail with bad object whenever the PR's merge base had fallen outside the fetch-depth: 2 clone.
This PR is in exactly that state: its merge base is 93d52ac, two merges behind the current master.
The current green result is from a run made before master advanced, so it does not exercise the fix.
Re-running that old run is not a reliable check either, because it may reuse the old event and workflow revision.

@aetos382 aetos382 closed this Sep 25, 2026
@aetos382 aetos382 reopened this Sep 25, 2026
@aetos382

Copy link
Copy Markdown
Contributor Author

The re-triggered run passed unicode-security, so the fix works: https://github.com/Cysharp/ObservableCollections/actions/runs/36114419146

neuecc and others added 2 commits October 26, 2026 17:14
…idate arrayIndex

Cysharp#139 removed the Unsafe.As-based CollectionsMarshal shim, so CopyTo(Span) of
ObservableList<T> and NonFilteredSynchronizedViewList now uses the real
CollectionsMarshal.AsSpan only on NET8_0_OR_GREATER and copies element by
element on netstandard.

CopyTo(T[], int) of ObservableList<T> and both ICollection.CopyTo of the
synchronized view lists now reject an arrayIndex beyond the end of the array
with ArgumentException, as List<T>.CopyTo does, instead of an
ArgumentOutOfRangeException from AsSpan or, for an empty collection copied
into object[], no exception at all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@neuecc
neuecc merged commit a78ba19 into Cysharp:master Oct 26, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SynchronizedViewList throws on ToList() / when used as IEnumerable

3 participants