LoginSignup
1
0

More than 1 year has passed since last update.

ProcessのインスタンスをDisposeする

Posted at

ProcessのインスタンスってDisposableだよね?

だから…

Process proc = Process.GetCurrentProcess();
Console.WriteLine(proc.Id);
proc.Dispose();

って書けば良いよね
でも、こういう時の為にusing構文がある

using(Process proc = Process.GetCurrentProcess()) {
    Console.WriteLine(proc.Id);
}

みたいな感じでスッキリおっけー

対象が配列だったらどうすれば…

つまり…

foreach(Process proc in Process.GetProcesses()) {
    Console.WriteLine(proc.Id);
    proc.Dispose();
}

んー
だと、Dispose()がいまいち不安なので、敢えて書くとこんな感じ?

foreach(Process proc in Process.GetProcesses()) {
    try {
        Console.WriteLine(proc.Id);
    }
    finally {
        proc.Dispose();
    }
}

あれー? なんか一世代巻き戻った感じ…
こういう時の為にusing構文があるんだけど、どうやって組み込めば良いんだろう?

海外でも疑問に思われる方達はいっぱいおられる様で、例えばここら辺

でも、大丈夫

ここにヒントがある
別にusingステートメントでインスタンスを取得しなくても、既出のインスタンスをただ置くだけ…
と云う書き方も可能

foreach(Process proc in Process.GetProcesses()) {
    using(proc) {
        Console.WriteLine(proc.Id);
    }
}

ただし、これはベストプラクティスではない、とも書いてある
何故なら

foreach(Process proc in Process.GetProcesses()) {
    using(proc) {
        Console.WriteLine(proc.Id);
    }   //  <== procはここでDisposeされる  ので、procの中身は無効
  //      <== ここでprocにはアクセス可能  な為、不正なprocを参照できちゃう
}

うん、じゃぁこうしよう

foreach(Process proc in Process.GetProcesses())
    using(proc) {
        Console.WriteLine(proc.Id);
    }

foreachの中身をusingステートメント単体だけに簡素化する事で、procのDispose後の隙間を作らない
まぁ、コーディングレベルの規約となるので「ベスト」な「プラクティス」ではない、と言われればそうかも知れないですけど…
ま、私個人としては、次善の策としてProcessインスタンスのDispose問題には結論が出たかな?
と一段落

もうちょっと踏み込んでみる

でも、本当にProcessインスタンスのDisposeは有効なんですよね?
MSのサイトにそう書いてあるんだから疑う必要はないんだろうけど、内部ではいったい何が起こっているかを掘り下げて、もう一段留飲を下げてみたいな、と

そこで、以下の単純なスニペットをILレベルで比較してみたいと思う

snippet#1
using(Process proc = Process.GetCurrentProcess()) {
	Console.WriteLine(proc.Id);
}

snippet#2
Process proc = Process.GetCurrentProcess();
using(proc) {
	Console.WriteLine(proc.Id);
}

ILの比較

ここでは、ILソースを手修正して比較し易く改変してあります

snippet#1
.method private hidebysig static 
	void Main (
		string[] args
	) cil managed 
{
	// Method begins at RVA 0x2050
	// Header size: 12
	// Code size: 35 (0x23)
	.maxstack 1
	.entrypoint
	.locals init (
		[0] class [System]System.Diagnostics.Process proc
	)

	// {
	IL_0000: nop
	// using Process process = Process.GetCurrentProcess();
	IL_0001: call class [System]System.Diagnostics.Process [System]System.Diagnostics.Process::GetCurrentProcess()
	IL_0006: stloc.0
	.try
	{
		// Console.WriteLine(process.Id);
		IL_0009: nop
		IL_000a: ldloc.0
		IL_000b: callvirt instance int32 [System]System.Diagnostics.Process::get_Id()
		IL_0010: call void [mscorlib]System.Console::WriteLine(int32)
		IL_0015: nop
		IL_0016: nop
		IL_0017: leave.s IL_0024
	} // end .try
	finally
	{
		IL_0019: ldloc.0
		IL_001a: brfalse.s IL_0023

		IL_001c: ldloc.0
		IL_001d: callvirt instance void [mscorlib]System.IDisposable::Dispose()
		IL_0022: nop

		IL_0023: endfinally
	} // end handler

	IL_0024: ret
} // end of method Program::Main
snippet#2
.method private hidebysig static 
	void Main (
		string[] args
	) cil managed 
{
	// Method begins at RVA 0x2050
	// Header size: 12
	// Code size: 37 (0x25)
	.maxstack 1
	.entrypoint
	.locals init (
		[0] class [System]System.Diagnostics.Process proc,
==> 	[1] class [System]System.Diagnostics.Process
	)

	// {
	IL_0000: nop
	// Process currentProcess = Process.GetCurrentProcess();
	IL_0001: call class [System]System.Diagnostics.Process [System]System.Diagnostics.Process::GetCurrentProcess()
	IL_0006: stloc.0
	// using (currentProcess)
==> IL_0007: ldloc.0
==> IL_0008: stloc.1
	.try
	{
		// Console.WriteLine(currentProcess.Id);
		IL_0009: nop
		IL_000a: ldloc.0
		IL_000b: callvirt instance int32 [System]System.Diagnostics.Process::get_Id()
		IL_0010: call void [mscorlib]System.Console::WriteLine(int32)
		IL_0015: nop
		IL_0016: nop
		IL_0017: leave.s IL_0024
	} // end .try
	finally
	{
==> 	IL_0019: ldloc.1
		IL_001a: brfalse.s IL_0023

==> 	IL_001c: ldloc.1
		IL_001d: callvirt instance void [mscorlib]System.IDisposable::Dispose()
		IL_0022: nop

		IL_0023: endfinally
	} // end handler

	IL_0024: ret
} // end of method Program::Main

snippet#2の行の先頭に==>を付けたところが、有意に異なる場所(5ヶ所)

  • 一つ目は、内部変数を一個余計に定義している
  • 二つ目三つ目で、内部変数をコピーしている
  • 四つ目と五つ目は、Dispose絡みの処理で、変数の参照先が異なっている

なんて、文字で書いても分かり難いので、図示してみる

snippet#1

image.png

こんな感じ?
using抜ける時にSlot[0]の変数に対してDispose()掛けて、どっかにあるアンマネージドなリソースの破棄も行われている(筈)

snippet#2

image.png

Slot[0]のローカル変数(GetCurrentProcess()したデータ)を、usingステートメントSlot[1]に単純コピー
なので、Processの唯一のインスタンスを、二つのSlotで共有している
usingを抜ける時には、Slot[1]の変数経由でDispose()を掛けている
でも、Slot[0]の変数が残っている、けど、ProcessのインスタンスはDispose済み、と云う例の状態がILレベルでも読み取れる

てな訳で

次善の策としてのコーディング規約を導入することで、プロセス列挙時においてもusingを使ったProcessインスタンスのDisposeを安心して実装できる、と結論付けたい

1
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0