では複数のTreeViewの内容を同期しています。そして対応するノードをみつけるためにはTreeNode.Nameプロパティを利用しています。TreeNodeをそのまま使うと同期を保てなくなる場合があるのでTreeNodeExという型を新たに定義してこれを使用しています。
TreeNodeExはこのような構造になっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
internal TreeNodeEx GetTreeNodeEx(TreeNode node) { if (node == null) return null; TreeNodeEx ex = new TreeNodeEx(this.SyncGroup); ex._text = node.Text; ex._level = node.Level; ex._key = node.Name; ex._toolTipText = node.ToolTipText; ex._backColorArgb = node.BackColor.ToArgb(); ex._checked = node.Checked; ex._foreColorArgb = node.ForeColor.ToArgb(); ex._imageIndex = node.ImageIndex; ex._isExpanded = node.IsExpanded; ex._selectedImageIndex = node.SelectedImageIndex; ex._data = node.Tag; return ex; } |
Keyプロパティが同じなら同じノードということになるのですが、同じTreeNodeでも実行するたびに異なるオブジェクトが返されてしまいます。
そこで便利な方法が演算子のオーバーライド。ところがあることを見落としてしまい見事にハマってしまいました。
1 2 3 4 5 6 7 |
public static bool operator ==(TreeNodeEx a, TreeNodeEx b) { if (a.Key == b.Key) return true; else return false; } |
まず、これはダメ。例外が発生し「aの値がnull」といって怒られます。それから==をオーバーライドするときは!=、Equals、GetHashCodeもオーバーライドしないといけません。
ではこれならどうでしょうか?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public static bool operator ==(TreeNodeEx a, TreeNodeEx b) { if (a == null && b == null) return true; if (a != null && b == null) return false; if (a == null && b != null) return false; if (a.Key == b.Key) return true; else return false; } |
aとbがnullの場合もあるので、その場合の処理も書いています。ところが実行するとまたまた問題が・・・。今度はスタックオーバーフローだという。どうなっているのだ?
原因はここに書いています。
Equals() と演算子 == のオーバーロードに関するガイドライン (C# プログラミング ガイド)
演算子 == のオーバーロードでは、(a == b)、(a == null)、または (b == null) を使用して参照が等価であることを確認するというエラーがよくあります。このような場合は、オーバーロードされた演算子 == が呼び出され、無限ループが生じます。無限ループを避けるには、ReferenceEquals を使用するか、型を Object にキャストしてください。
1 2 3 4 5 6 7 |
public static bool operator ==(TreeNodeEx a, TreeNodeEx b) { if (a == null && b == null) return true; // 以下、省略 } |
この書き方では演算子==をどうするかを書いているのに、そのなかで==を使ってしまうと無限ループのような状態になってしまうのです。ここはObject にキャストすることにしましょう。
これなら例外は発生しません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public static bool operator ==(TreeNodeEx a, TreeNodeEx b) { object ao = (object)a; object bo = (object)b; if (ao == null && bo == null) return true; if (ao != null && bo == null) return false; if (ao == null && bo != null) return false; if (a.Key == b.Key) return true; else return false; } |