自作ライブラリの話。
Tree構造をなすインスタンスを破棄するとき、その子孫ノードも破棄する。
破棄したくない場合はClearメソッドやDismantleDescendantsメソッドで対応可能であり、自分で使ってみた感じこの仕様で不便はない。
ただ、破棄の過程が気になったので修正を考える
構想
要望としては、Disposeを呼ばれたノードをまず切り離す。(子孫ノードから対象ノードまで遡ると、StructureChangedイベントが連発される。特定のノードが切り離されたかどうかを知りたかったらEventArgsから辿れば良い)
その後、切り離されたノードの子孫を分解して破棄。
破棄の順番は子孫から対象ノードに向けて。の方がいいのかな?どういう場面を想定してかと言われても例が思い当たらないが。
テストコードは以下
public static void Main(string[] args) {
var node1 = new TestNode() { Name = "A", };
var node2 = new TestNode() { Name = "B", };
var node3 = new TestNode() { Name = "C", };
var node4 = new TestNode() { Name = "D", };
var node5 = new TestNode() { Name = "E" };
var node6 = new TestNode() { Name = "F", };
node1.AddChild(node2);
node2.AddChild(node3);
node3.AddChild(node4);
node4.AddChild(node5);
node5.AddChild(node6);
Console.WriteLine("init set");
Console.WriteLine(string.Join("-", node1.Levelorder().Select(x => x.Name)));
Console.WriteLine("dispose start");
node3.Dispose();
Console.WriteLine(string.Join("-",node1.Levelorder().Select(x=>x.Name)));
Console.ReadKey();
}
public class TestNode : ObservableTreeNode<TestNode> {
public TestNode() {
this.Disposed += TestNode_Disposed;
this.StructureChanged += TestNode_StructureChanged;
}
private void TestNode_StructureChanged(object? sender, StructureChangedEventArgs<TestNode> e) {
Console.WriteLine($"Notice from {this.Name} structure changed {e.TreeAction} : {e.Target.Name}");
}
private void TestNode_Disposed(object? sender, EventArgs e) {
Console.WriteLine($"Dispose {this.Name}");
}
public string Name { get; set; }
}
変更前
protected virtual void Dispose(bool disposing) {
if (IsDisposed) return;
if (disposing) {
this.Parent = null;
foreach (var cld in this.Levelorder().Skip(1).ToArray()) {
cld.Dispose();
}
}
IsDisposed = true;
}
挙動の解説
- CをDispose開始
- Cが切り離される
- ノードA,B,C,D,E,FにCが切り離されたことを通知
- 再帰的にDをDispose開始
- Dが切り離される
- ノードC,D,E,FにDが切り離されたことを通知
- 再帰的にEをDispose開始
- Eが切り離される
- ノードD,E,FにEが切り離されたことを通知
- 再帰的にFをDispose開始
- Fが切り離される
- ノードE,FにFが切り離されたことを通知
- FのDispose完了
- EのDispose完了
- DのDispose完了
- CのDispose完了
不満な点
Treeが深くなればネストも深くなる。
なるべく再帰処理にならないように書いたつもりだったが再帰処理になってしまっていた。
Disposeが再帰的に呼び出されるのに、さらにforeachで呼ばれる無駄。
変更後
protected virtual void Dispose(bool disposing) {
if (IsDisposed) return;
if (disposing) {
this.Parent = null;
foreach (var cld in this.Levelorder().Skip(1).Reverse().ToArray()) { //Reverse()を追加
cld.Dispose();
}
}
IsDisposed = true;
}
挙動の解説
- CをDispose開始
- Cが切り離される
- ノードA,B,C,D,E,FにCが切り離されたことを通知
- FをDispose開始
- Fが切り離される
- ノードC,D,E,FにFが切り離されたことを通知
- FのDispose完了
- EをDispose開始
- Eが切り離される
- ノードC,D,EにEが切り離されたことを通知
- EのDispose完了
- DをDispose開始
- Dが切り離される
- ノードC,DにDが切り離されたことを通知
- DのDispose完了
- CのDispose完了
切り離しの順番が変更されたが問題はないと思う。