【C#】継承できるクラスでDisposeパターンを実装するときは、とりあえずFinalizeも実装すべき？

# はじめに
`IDisposable`なんて別に`public void Dispose()`を実装するだけでも事足りることばかり。でもどうせなら`Disposeパターン`なんてより丁寧に書くための物があるんだから使いたくなるのが開発者の心情というもの。と言うわけで`IDisposable`を使うときの個人的なメモ。
「より安全で丁寧なコードを書くにはどうしたら良いだろう？」という考え方が、以前書いた「[【Windows/C#】なるべく丁寧にDllImportを使う](https://qiita.com/mitsu_at3/items/94807ee0b3bf34ffb6b2)」に通じるものがあるかも・・・[^1]

[^1]: 書き出しを合わせてみたけど、シリーズ化する予定はない模様

# Disposeパターン

C#でよくリソースを破棄するために使う`IDisposable`インターフェイス。
それを丁寧に扱うためのテンプレートとして有名な`Disposeパターン`。
Visual Studio 2019だと`class A : IDisposable`と打ち込んで、右クリックメニューから「クイックアクションとリファクタリング...」から「破棄パターン[^2]を使ってインターフェイスを実装します」を押すと自動的に生成してくれるアレ。

[^2]: 破棄パターン（笑）昔は「Disposeパターン」と表記されてたのに機械翻訳でこうなってしまったのかな・・・

```cs
class A : IDisposable
{
	private bool disposedValue;

	protected virtual void Dispose(bool disposing)
	{
		if (!disposedValue)
		{
			if (disposing)
			{
				// TODO: マネージド状態を破棄します (マネージド オブジェクト)
			}

			// TODO: アンマネージド リソース (アンマネージド オブジェクト) を解放し、ファイナライザーをオーバーライドします
			// TODO: 大きなフィールドを null に設定します
			disposedValue = true;
		}
	}

	// // TODO: 'Dispose(bool disposing)' にアンマネージド リソースを解放するコードが含まれる場合にのみ、ファイナライザーをオーバーライドします
	// ~A()
	// {
	//     // このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します
	//     Dispose(disposing: false);
	// }

	public void Dispose()
	{
		// このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します
		Dispose(disposing: true);
		GC.SuppressFinalize(this);
	}
}
```

# ファイナライザ いる？いらない？

自動生成されるコードでは、ファイナライザがコメントアウトされていて「アンマネージド リソースを解放するコードが含まれる場合にのみ、ファイナライザーをオーバーライドします」と書いてある。
また`Disposeパターン`の正しい実装を求めてグーグル先生に尋ねてみたけど、どの情報も「アンマネージドリソースを使う場合はファイナライザを実装する」みたいな書き方になってた。
**本当にそれで安全なのかな？**

# 継承先でアンマネージドリソースが使われると...？

```cs
protected virtual void Dispose(bool disposing)
```

と`virtual`を付けているくらいだから継承できるはず。
とすると、そのクラスでアンマネージドリソースを使わなかったとしても、継承先で使われる可能性は十分あり得るのでは・・・？
もしその時に継承元がファイナライザを実装していないとどうなるのかな？

# 継承元のファイナライザが無いとどうなる？

### まずファイナライザを実装した場合を実験

継承元のAクラス。

```cs
class A : IDisposable
{
	protected virtual void Dispose(bool disposing)
	{
		Console.WriteLine($"A.Dispose({disposing})");
	}

	// TODO: 'Dispose(bool disposing)' にアンマネージド リソースを解放するコードが含まれる場合にのみ、ファイナライザーをオーバーライドします
	~A()
	{
		Console.WriteLine($"A.~A()");

		// このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します
		Dispose(disposing: false);
	}

	public void Dispose()
	{
		Console.WriteLine($"A.Dispose()");

		// このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します
		Dispose(disposing: true);
		GC.SuppressFinalize(this);
	}
}
```

継承先のBクラス。

```cs
class B : A
{
	protected override void Dispose(bool disposing)
	{
		Console.WriteLine($"B.Dispose({disposing})");

		base.Dispose(disposing);
	}
}
```

あと実験用のProgram.cs。

```cs
class Program
{
	static void Test()
	{
		B b = new B();
	}

	static void Main(string[] args)
	{
		Test();
		GC.Collect();
	}
}
```

単純に`Dispose`とファイナライザを呼び出したときにコンソール出力するだけのクラス。
それをわざと`Dispose`をすっぽかしてみる。

結果

```
A.~A()
B.Dispose(False)
A.Dispose(False)
```

当然Bクラスの`Dispose`も`False`で呼ばれている。
ちなみに`GC.Collect()`を呼ばないと、ファイナライザを実装してても呼ばれなかった。

### 次にファイナライザを実装しなかった場合を実験

アンマネージドリソースを使ってないからとAクラスのファイナライザは実装しない。

```cs
class A : IDisposable
{
	protected virtual void Dispose(bool disposing)
	{
		Console.WriteLine($"A.Dispose({disposing})");
	}

	// // TODO: 'Dispose(bool disposing)' にアンマネージド リソースを解放するコードが含まれる場合にのみ、ファイナライザーをオーバーライドします
	// ~A()
	// {
	//     // このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します
	//     Dispose(disposing: false);
	// }

	public void Dispose()
	{
		Console.WriteLine($"A.Dispose()");

		// このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します
		Dispose(disposing: true);
		GC.SuppressFinalize(this);
	}
}
```

継承先のBクラスでは、ファイナライザで`Dispose(false)`を呼んでくれると信じて、アンマネージドリソースの破棄処理を実装したとする。

```cs
class B : A
{
	protected override void Dispose(bool disposing)
	{
		Console.WriteLine($"B.Dispose({disposing})");

		if (disposing)
		{
			Console.WriteLine($"マネージド リソースを破棄！");
		}

		Console.WriteLine($"アンマネージド リソースを破棄したい！");

		base.Dispose(disposing);
	}
}
```

そしてさっきと同じように実行。

結果

```

```

しーん・・・
やはり実行されなかった。

# どうすべきか？

少なくとも`sealed`が付いていないクラス[^3]で`IDisposable`を実装するときは、
「継承してもアンマネージドリソースを使うわけがないクラス」
「そもそも継承する予定のないクラス」
「Disposeは必ず呼ぶから大丈夫」
色々あるだろうけど、別にファイナライザを実装しても行数が増える程度で特に問題はないので、なにも考えずに**ファイナライザのコメントを解除して実装してしまう**のが１番安全な気がする。
作成当時は「有り得ない」と思ってても、時間が経ってから継承してアンマネージドリソースを使うかもしれない。

[^3]: 別に`sealed`とか気にせずファイナライザを実装して良い気もする

もちろん「アンマネージドリソースを使った側でファイナライザを実装する」というのもアリかもしれない。

```cs
class B : A
{
	~B()
	{
		Dispose(disposing: false);
	}

	protected override void Dispose(bool disposing)
	{
		Console.WriteLine($"B.Dispose({disposing})");

		if (disposing)
		{
			Console.WriteLine($"マネージド リソースを破棄！");
		}

		Console.WriteLine($"アンマネージド リソースを破棄！");

		base.Dispose(disposing);
	}
}
```

むしろ「アンマネージド リソースを解放するコードが含まれる場合にのみ、ファイナライザーをオーバーライドします」という説明的には、こっちの方が正しい気さえする。
でも継承先で`Dispose`をオーバライドしても`IDisposable`のテンプレートみたいにファイナライザは自動生成されないし、**絶っっ対にファイナライザの実装を忘れる**と思う。
なによりいちいち継承先で実装するのは面倒臭い。

というわけで、`IDisposable`を`Disposeパターン`で実装するときは、自動生成のコメントに惑わされず**常にファイナライザは実装**していこうかなーって話でした。

# 余談

自動生成される`Disposeパターン`のコードがいつの間にか微妙に変わってる？

```cs
public void Dispose()
{
	// このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します
	Dispose(disposing: true);
	GC.SuppressFinalize(this);
}
```

なんで引数に`disposing:`をわざわざ付けるんだろう・・・
以前は`GC.SuppressFinalize(this);`もコメントアウトされてて、アンマネージドリソースを使う場合のみ呼び出す形だったような・・・