Skip to content

Instantly share code, notes, and snippets.

@green224
Last active June 15, 2019 06:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save green224/8c0f3eacc17eabf1bf8aea9d00cdbde1 to your computer and use it in GitHub Desktop.
Save green224/8c0f3eacc17eabf1bf8aea9d00cdbde1 to your computer and use it in GitHub Desktop.

【C#】Enumerableのデバッグ表示でEnumerableが解放されない問題

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 を使えば回避できそうかな?

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