Skip to content

TreeNodeCollection indexer setter throws NullReferenceException for a TreeNode not attached to a TreeView (regression since .NET 6) #15190

Description

@ricardobossan

.NET version

We verified the failure on .NET 6.0, .NET 8.0, .NET 10.0, and the current .NET 11 main branch at commit 8efd9a920dba90722f8db1f7e943f74837d26579 from 2026-09-10. The same scenario is clean on .NET Framework 4.7.2, .NET Core 3.1, and .NET 5.0.

Did it work in .NET Framework?

Yes. We confirmed the behavior is correct on .NET Framework 4.7.2, where the TreeView fills in normally and no exception is raised.

Did it work in any of the earlier releases of .NET Core or .NET 5+? If so, which version?

Yes. We checked the earlier releases and confirmed the same code path works on .NET Core 3.1 and .NET 5.0. The first failing release is .NET 6.0.

Issue description

When we swap items through the indexed setter on a TreeNode's Nodes collection (nodes[i] = nodes[j]), the in-place sort throws NullReferenceException if that node has never been attached to a TreeView. The exception is raised by System.Windows.Forms.TreeNodeCollection.set_Item(Int32, TreeNode).

The current implementation reads the owning node's TreeView into a local without checking for null, then dereferences it while checking whether the handle is already present. In a detached node, _owner._treeView is null, which makes this fail before the collection ever has a parent view. The relevant code is here:

TreeView tv = _owner._treeView!;
TreeNode actual = _owner._children[index];
if (value._treeView is not null && value._treeView.Handle != tv.Handle)
{
throw new ArgumentException(string.Format(SR.TreeNodeBoundToAnotherTreeView), nameof(value));
}
if (tv._nodesByHandle.ContainsKey(value.Handle) && value._index != index)

This regression appears to have been introduced by #4297, which was merged on 2020-12-05 to stop a node from being added twice. The new TreeView/nodesByHandle validation was added there, but it did not guard the detached-owner case. See: #4297

The same code path is present on release/6.0 and absent on release/5.0, which matches the observed version split. Relevant references:

We also observed a second, distinct exception when the parent node is attached to a TreeView before the same swap-based reorder is attempted. In a minimal console harness on .NET 6, .NET 8, and .NET 10, the reorder throws ArgumentException: Cannot add or insert the item '2' in more than one place. You must first remove it from its current location or clone it. from the same indexed setter. This is the duplicate-node check added in the same PR (SR.OnlyOneControl), and it rejects the transient intermediate state created by the swap idiom. We observed this in a bare console reproduction rather than in a fully created Form/TreeView scenario, so we describe it as observed behavior rather than assuming it is the only valid workaround.

Image

Steps to reproduce

No Form or TreeView is required to make this fail. A console app that references System.Windows.Forms with UseWindowsForms reproduces it in the same way, because the exception happens before the node is connected to any tree.

using System.Windows.Forms;

var parentNode = new TreeNode("a");
parentNode.Nodes.Add(new TreeNode("3"));
parentNode.Nodes.Add(new TreeNode("2"));
parentNode.Nodes.Add(new TreeNode("1"));

Sort(parentNode.Nodes); // throws NullReferenceException on .NET 6+

static void Sort(TreeNodeCollection nodes)
{
    for (int i = 0; i < nodes.Count; i++)
    {
        for (int j = i + 1; j < nodes.Count; j++)
        {
            if (StringComparer.Ordinal.Compare(nodes[i].Text, nodes[j].Text) > 0)
            {
                TreeNode temp = nodes[i];
                nodes[i] = nodes[j];
                nodes[j] = temp;
            }
        }
    }
}
  1. Create a TreeNode and populate its child nodes without attaching the parent to a TreeView.
  2. Reorder the children by assigning through the indexer (nodes[i] = nodes[j]).
  3. Observe the NullReferenceException on .NET 6.0 and later. This does not happen on .NET Framework 4.7.2, .NET Core 3.1, or .NET 5.0.

Expected Behavior (Before .NET 6)

Image
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

💥 regression-frameworkRegression from .NET Framework💥 regression-releaseRegression from a public releaseuntriagedThe team needs to look at this issue in the next triage

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions