TheSpecificationOfDispose - Houzkin/TreeStructures GitHub Wiki
Dispose is performed using the TreeNodeBase.DisposeProcess method in GeneralTree and its derived types.
The following code is used to trace the disposal process.
public class DisposingObservableNamedNode : ObservableNamedNode{
protected override void Dispose(bool disposing) {
Console.WriteLine($"sender : {this} Disposing");
base.Dispose(disposing);
}
}Console.WriteLine("Building a collection as an N-ary tree.");
var nodesDic = "ABCDEFGHI".ToCharArray().Select(x => x.ToString()).ToDictionary(x => x, x => new DisposingObservableNamedNode() { Name = x } as ObservableNamedNode);
var root = nodesDic.Values.AssembleAsNAryTree(2);
//before
Console.WriteLine(root.ToTreeDiagram(x => x.Name));
EventHandler<StructureChangedEventArgs<ObservableNamedNode>> structreChangedHdlr = (s, e) => {
Console.WriteLine($"sender:{s} \nTarget:{e.Target} TreeAction:{e.TreeAction} PreviousParentOfTarget:{e.PreviousParentOfTarget} OldIndex:{e.OldIndex} IsAncestorChanged:{e.IsAncestorChanged} IsDescendantChanged:{e.IsDescendantChanged}");
if (e.IsAncestorChanged) {
var info = e.AncestorInfo!;
Console.WriteLine($"MovedTarget:{info.MovedTarget} OldIndex:{info.OldIndex} PreviousParentOfTarget:{info.PreviousParentOfTarget} IsRootChanged:{info.IsRootChanged}");
}
if (e.IsDescendantChanged) {
var info = e.DescendantInfo!;
Console.WriteLine($"Target:{info.Target} SubtreeAction:{info.SubTreeAction} OldIndex:{info.OldIndex} PreviousParentOfTarget:{info.PreviousParentOfTarget}");
}
Console.Write("\n");
};
PropertyChangedEventHandler propertyChangedHdlr = (s, e) => { Console.WriteLine($"sender:{s} Parent Changed.\n"); };
EventHandler disposedHdlr = (s, e) => { Console.WriteLine($"sender:{s} Disposed.\n"); };
foreach (var node in new ObservableNamedNode[]{nodesDic["A"],nodesDic["B"],nodesDic["D"],nodesDic["H"],nodesDic["I"],nodesDic["E"] }){
node.StructureChanged += structreChangedHdlr;
node.PropertyChanged += propertyChangedHdlr;
node.Disposed += disposedHdlr;
}
nodesDic["B"].Dispose();
//after
Console.WriteLine(root.ToTreeDiagram(x => x.Name));

During disposal, each node is detached from its parent node, starting from the descendants, and all descendant nodes are disposed of before disposal is completed.
Disposed nodes, including nodeB, are all left in a detached state.
- nodeB Disposing
- nodeB Removed -> Notify ParentProperty and Structure Changed
- nodeI Disposing
- nodeI Removed -> Notify ParentProperty and Structure Changed
- nodeI Disposed
- nodeH Disposing
- nodeH Removed -> Notify ParentProperty and Structure Changed
- nodeH Disposed
- nodeE Disposing
- nodeE Removed -> Notify ParentProperty and Structure Changed
- nodeE Disposed
- nodeD Disposing
- nodeD Removed -> Notify ParentProperty and Structure Changed
- nodeD Disposed
- Disposed nodeB
This disposal process is executed on the ITreeNode.Children property.
Therefore, it is not executed on hidden child nodes, filtered out using methods such as the following:
From version 1.8.0 onward, the Dispose process is also executed for hidden child nodes.
If you modify the DisposingObservableNamedNode as shown below and execute it, there will be no change notifications from nodeH, but you can confirm that the Dispose process has been executed.
public class DisposingObservableNamedNode : ObservableNamedNode{
protected override IEnumerable<ObservableNamedNode> SetupPublicChildCollection(IEnumerable<ObservableNamedNode> innerCollection) {
var flitered = (innerCollection as ObservableCollection<ObservableNamedNode>).ToSortFilterObservable();
flitered.FilterBy(x => x.Name != "H", x => x.Name);
return flitered;
//return base.SetupPublicChildCollection(innerCollection);
}
protected override void Dispose(bool disposing) {
Console.WriteLine($"sender : {this} Disposing");
base.Dispose(disposing);
}
}Let's trace the disposal process of the BindableTreeNodeWrapper.
We define and execute the wrapper as follows:
public class NamedNodeWrapper : BindableTreeNodeWrapper<ObservableNamedNode,NamedNodeWrapper>{
//constructor
public NamedNodeWrapper(ObservableNamedNode node) : base(node) { }
protected override NamedNodeWrapper GenerateChild(ObservableNamedNode sourceChildNode)
=> new NamedNodeWrapper(sourceChildNode);
public virtual string Name => Source.Name;
protected override void HandleRemovedChild(NamedNodeWrapper removedNode) {
Console.WriteLine($"{removedNode.Name} : Removed");
base.HandleRemovedChild(removedNode);
}
protected override void Dispose(bool disposing) {
Console.WriteLine($"{this.Name} : Disposing");
base.Dispose(disposing);
}
}var sources = "ABCDEFGHIJKLMN".ToCharArray().Select(x => x.ToString()).ToDictionary(x => x, x => new ObservableNamedNode() { Name = x });
//assemble source tree.
var SrcRoot = sources.Values.AssembleAsNAryTree(2);
sources["H"].AddChild(sources["F"]);
//wrapping source tree.
var WrprRoot = new NamedNodeWrapper(SrcRoot);
Console.WriteLine(WrprRoot.ToTreeDiagram(x => $"{x.Name}, IsDisposed : {x.IsDisposed}"));
//dispose node D wrapper.
Console.WriteLine("Dispose node D Wrapper.");
WrprRoot.Preorder().First(x => x.Name == "D").Dispose();
Console.WriteLine(WrprRoot.ToTreeDiagram(x => $"{x.Name}, IsDisposed : {x.IsDisposed}"));
//remove node D.
Console.WriteLine("Remove node D.");
sources["D"].TryRemoveOwn();
Console.WriteLine(WrprRoot.ToTreeDiagram(x => $"{x.Name}, IsDisposed : {x.IsDisposed}"));
//remove node B.
Console.WriteLine("Remove node B");
sources["B"].TryRemoveOwn();
Console.WriteLine(WrprRoot.ToTreeDiagram(x => $"{x.Name}, IsDisposed : {x.IsDisposed}"));
When the wrapper of nodeD is disposed, its descendant nodes are also disposed of, but the wrapper of nodeD itself is not removed from the parent node.
Additionally, wrappers that have already been disposed are not handled by the HandleRemovedChild method.
Nodes targeted for disposal in the BindableWrapper are the wrappers combined in the CombinableChildWrapperCollection.
Therefore, nodes hidden by filtering are also targeted for disposal.
Let's append and execute the NamedNodeWrapper defined earlier as follows:
public class NamedNodeWrapper : BindableTreeNodeWrapper<ObservableNamedNode,NamedNodeWrapper>{
//...
ObservableCollection<NamedNodeWrapper> artificals = new();
ReadOnlySortFilterObservableCollection<NamedNodeWrapper> filterable;
protected override IEnumerable<NamedNodeWrapper> SetupPublicChildCollection(CombinableChildWrapperCollection<NamedNodeWrapper> children) {
children.AppendCollection(artificals);
filterable = children.ToSortFilterObservable();
return filterable;
}
public void Hide(string name){
filterable.FilterBy(x => x.Name != name, x => x.Name);
}
public void AddArtificalChild(string name){
this.artificals.Add(new AnnonymousNamedWrapper(name));
}
}
public class AnnonymousNamedWrapper : NamedNodeWrapper{
public AnnonymousNamedWrapper(string name) : base(null) {
this.Name = name;
}
public override string Name { get; }
}The code to execute is as follows:
var sources = "ABCDEFGHIJKL".ToCharArray().Select(x => x.ToString()).ToDictionary(x => x, x => new ObservableNamedNode() { Name = x });
//assemble source tree.
var SrcRoot = sources.Values.AssembleAsNAryTree(2);
//wrapping source tree.
var WrprRoot = new NamedNodeWrapper(SrcRoot);
Console.WriteLine(WrprRoot.ToTreeDiagram(x => $"{x.Name}, IsDisposed : {x.IsDisposed}"));
//hide nodeB and add dummy wrapper child.
WrprRoot.Hide("B");
WrprRoot.AddArtificalChild("Dmy");
Console.WriteLine(WrprRoot.ToTreeDiagram(x => $"{x.Name}, IsDisposed : {x.IsDisposed}"));
WrprRoot.Dispose();
The hidden nodeB and the dummy Dispose method are also being called.