IEnumerableを継承したクラスを、Watch式などでデバッグ中に表示した際に、GetEnumeratorが自動で呼ばれてしまう。
例えばこういうコード
// ShaderのSwizzle operator相当の事をしたい
class A : IEnumerable {
public IEnumerator GetEnumerator { ~~~ }
}
これを含む自作クラスをWatchすると、IEnumerable用のデバッグ表示機能により、要素羅列のために自動でGetEnumeratorが呼ばれる。 ところが、ここで取得したEnumeratorは、Debuggerによっては Disposeされない という問題がある。(VisualStudioなど)
普通IEnumeratorを実装する場合は、foreachでのヒープ確保を回避するために、structで自作Enumeratorを実装する。
/** Foreachループ用のEnumerator */
public struct Enumerator : IEnumerator<Elem> {
public void Dispose() {
// foreachループが抜けたタイミングで、リソースを開放する
~~~
}
:
}
こういう風にDisposeで一時リソースを開放する処理を書いていた場合、Watchするとリークが発生する。
IEnumerable派生のクラスには、Debugger表示中にはあたかも IEnumerator というフィールドがあるように表示される。
これはIEnumerableに DebuggerTypeProxyAttribute が設定されているのかと思いきや、特にそういうことも無いのでDebbugger側で特殊処理を行っているのかもしれない。(ここら辺は調べても何も見つけられなかった)
DebuggerBrowsableAttribute 指定を付ける事をまず考えるが、IEnumeratorプロパティはコード上には存在しないためこれはできない。
表示を上書きするために、同名のダミープロパティを作成しみた
IEnumerator IEnumerator => null;
これでDebbuggerView上の表示を上書きすることができる。 しかし、表示を上書きしてもGetEnumeratorが呼ばれてしまうようで、解決にはならない。
次に、Debuggerの表示を丸ごと差し替える方法を考えた
[DebuggerTypeProxy(typeof(MyDebugView))]
class MyEnumerable : IEnumerable {
...
}
しかしこれでもDebuggerによってはRawView表示が可能なため、解決にはならない。
Debuggerから呼ばれるのは IEnumerable.GetEnumerator() のようだ。したがって、これをnullを返すように変更することで問題を回避することができた
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => null;
幸い、このIEnumerable.GetEnumeratorは普通呼ぶことはない。呼ぶたびにBoxingが発生する時代遅れな実装だし、あえてジェネリクス版を使用しないケースは、自分でコードを書く限りはほとんどない。
コイツは使用しないと割り切って、潔くnull固定にするのも手だが、まれに意図的に呼ばなければならないケースも存在しないわけではない。
なので、せめてDebuggerがアタッチされている時のみnullを返すようにすればまだ幾分マシだろう。
ジェネリクスでない GetEnumerator で、デバッグのアタッチ中のみnullを返すようにする。
System.Collections.IEnumeratorSystem.Collections.IEnumerable.GetEnumerator( {
#if Debug
// VisualStudioなど、EnumeratorをDisposeしないDebuggerViewでバグらないために、
// アタッチ中はnullを返すようにする
if ( System.Diagnostics.Debugger.IsAttached ) return null;
#endif
return new Enumerator(this);
}
しかしこれだと、実際にGetEnumeratorを意図的に呼びたい箇所をデバッグしたい際に、正常に列挙できない事になる。
そのケースにまだ遭遇していないので、そのときの対処法については考え中・・・ DebuggerStepperBoundaryAttribute を使えば回避できそうかな?