Skip to content

Conversation

@HackerDelphi
Copy link
Contributor

What does the pull request do?

Fixes #14437 & #13497

What is the updated/expected behavior with this PR?

ControlCatalog doesn't fail anymore using scenario from #13497

How was the solution implemented (if it's not obvious)?

implemented new SafeEnumerableList class using "Copy-on-change" behavior and LogicalChildren/VisualChildren collections are changed to be instanced using class

Checklist

  • Added unit tests?

Fixed issues

Fixes #14437
Fixes #13497

Changed Visual/Logical Children properties to use CopyOnWrite
@MrJul MrJul added enhancement backport-candidate-11.3.x Consider this PR for backporting to 11.3 branch labels Nov 10, 2025

for (var i = 0; i < visualChildrenCount; i++)

foreach (var child in VisualChildren)
Copy link
Member

Choose a reason for hiding this comment

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

You haven't replaced VisualChildren with the new list type, is that an oversight?

{
internal class SafeEnumerableList<T> : IAvaloniaList<T>, INotifyCollectionChanged
{
private AvaloniaList<T> _list = new();
Copy link
Member

Choose a reason for hiding this comment

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

I'm a bit worried about the allocations here. We have this inner list, plus the two event handlers in the constructor, or 3 extra allocations. I wouldn't worry about such a number in most other classes, but this is used in every visual, twice. That's 6 long-lived objects per visual.


public void AddRange(IEnumerable<T> items)
{
((IAvaloniaList<T>)GetList()).AddRange(items);
Copy link
Member

Choose a reason for hiding this comment

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

Here and below: the cast seems unnecessary.

return GetEnumerator();
}

private AvaloniaList<T> GetList()
Copy link
Member

Choose a reason for hiding this comment

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

Nit: rename to GetWriteableList (or something along those lines) to make the difference clearer.

}
};
});
/*IDataTemplate windowTemplate = new FuncDataTemplate<object?>((i, ns) =>
Copy link
Member

Choose a reason for hiding this comment

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

Remove all the commented code in this file.


namespace Avalonia.Base.UnitTests.Collections
{
public class SafeEnumerateCollectionTests
Copy link
Member

Choose a reason for hiding this comment

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

While it's neat to have a test corresponding to real issue, a handful of tests for the SafeEnumerableList itself would be nice, i.e. checking that adding or removing an item doesn't affect enumerations that are already in progress.

{
private readonly SafeEnumerableList<T> _owner;
private readonly int _generation;
private readonly IEnumerator<T> _enumerator;
Copy link
Member

Choose a reason for hiding this comment

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

This should be typed as AvaloniaList<T>.Enumerator to avoid boxing. (Make it non-readonly in that case.)


object IEnumerator.Current => _enumerator.Current!;

public void Reset() => throw new InvalidOperationException();
Copy link
Member

Choose a reason for hiding this comment

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

It's unlikely to be used, but why is Reset not implemented? The underlying list cannot change so it should be safe to call _enumerator.Reset().

@MrJul MrJul changed the title Fix for issues 14437 and 13497 Implement copy-on-write collection for LogicalChildren/VisualChildren Dec 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-candidate-11.3.x Consider this PR for backporting to 11.3 branch enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ArgumentOutOfRangeException in OnDetachedFromVisualTreeCore ArgumentOutOfRangeException in OnDetachedFromLogicalTreeCore

2 participants