C#テンポラリディレクトリの作り方

C# コンピュータ
C#

C#を学習するにあたり最初にテンポラリディレクトリを作成してみたいと思います。

以前にPowershellで同様のテーマで記事を書きました。

PowerShellの場合New-TemporaryFileというコマンドレットをついかいテンポラリファイルの作成しファイルオブジェクトを返してくれます。テンポラリディレクトリ(フォルダ)を作成する場合は、テンポラリファイルを削除し同名(同じパス)でディレクトリを作成する方法でテンポラリディレクトリを作成していました。

C#で.Netを使い同様な処理を考えてみます。

// tempdir.cs

using System;
using System.IO;

public class TempDir {
	static void Main() {
		string name = Path.GetTempFileName();
		
		if (File.Exists(name)) {
			Console.WriteLine(name);
			File.Delete(name);
		}
		
	}
}

// csc tempdir.cs
// .\tempdir.exe
// C:\Users\karet\AppData\Local\Temp\tmp6CA9.tmp

.NetのSystem.IO.Path.GetTempFileName()でテンポラリファイルを作成出来るようです。
テンポラリファイルのパスをコンソールに表示後、テンポラリファイルを削除していいます。

続いてテンポラリディレクトリを作成してみます。

// tempdir.cs

using System;
using System.IO;

public class TempDir {
	static void Main() {
		string name = Path.GetTempFileName();
		
		// ファイルの削除
		File.Delete(name);
		
		// ディレクトリの作成
		Directory.CreateDirectory(name);
		
		if (Directory.Exists(name)) {
			Console.WriteLine(name);
			Directory.Delete(name);
		}
	}
}

// csc tempdir.cs
// .\tempdir.exe
// C:\Users\karet\AppData\Local\Temp\tmp6CA9.tmp

テンポラリファイル作成→削除→テンポラリディレクトリの作成を行っています。
概ねPowerShellで行ったテンポラリディレクトリ(フォルダ)の作成と同等な処理になったと思います。

テンポラリディレクトリが必要な場合ディレクトリを作成し、使い終わったらディレクトリを削除する流れになります。
使ったら後始末をしなければならないのは世の常です。プログラミングでもそれは同じ事ではありますが、削除を忘れてしまうことがあります。人為的ミスによるコーディングでの削除忘れを別にしても、例外が発生した場合でも削除処理はして欲しいところです。

C#にはusingなる構文があり後始末を強制してくれる使い方が出来るようですので、早速対応したコードに作り直してみます。

// tempdir1.cs

using System;
using System.IO;

class TemporaryDirectory : IDisposable {
	
	private string name;
	public string Name {
		get { return name; }
	}
	
	// コンストラクタ
	public TemporaryDirectory() {
		name = Path.GetTempFileName();
		if (File.Exists(name)) {
			File.Delete(name);
			Directory.CreateDirectory(name);
		}
		
	}
	
	public void Dispose() {
        Console.WriteLine("Dispose()");
        Dispose(true);
        GC.SuppressFinalize(this);
	}
	
    protected virtual void Dispose(bool disposing) {
        
        if (disposing) {
        	// Dispose()処理を記述
        }
        
        // ディレクトリの削除
		if (Directory.Exists(name)) {
			Console.WriteLine(String.Format("{0}が削除されました。", name));
			Directory.Delete(name);
		}
		
    }
    
   	// デストラクタ
	~TemporaryDirectory() {
        Console.WriteLine("デストラクタ");
		Dispose(false);
	}
}


public class TempDir {
	static void Main() {
		using (TemporaryDirectory d = new TemporaryDirectory()) {
			
			// テンポラリディレクトリを使う処理をここに記述
			
			if (Directory.Exists(d.Name)) {
				Console.WriteLine(String.Format("{0}が作成されました。", d.Name));
			}
			
			try {
				// 例外発生
				throw new Exception();
			} catch (Exception e) {
				Console.WriteLine(String.Format("catch:{0}", e.Message));
			}
		}
		
		Console.WriteLine("目印");
	}
}

// csc tempdir1.cs

実行すると以下のようになりました。

C:\Users\karet\AppData\Local\Temp\tmp2946.tmpが作成されました。
catch:種類 'System.Exception' の例外がスローされました。
Dispose()
C:\Users\karet\AppData\Local\Temp\tmp2946.tmpが削除されました。
目印

意図的に例外を発生させてみましたが、usingがDispose()を呼びだしてくれたようで、テンポラリディレクトリの削除ルーチンは実行されています。

Powershellでも.Netを触ってみて今一つ理解できていないのがDispose()です。リソースの解放はガーベージコレクタが自動的に実行してくれるそうですが、使わなくなったリソースは即座に解放したほうが良さそうな気もします。usingのように解放忘れを防止する機能があったりもしますし、意図的にDisplose()する場面とガーベージコレクタに任せる場面の使い分けを考えると結構悩ましいです。

ファイルのストリームなど一般的にOpenしてCloseするタイプのリソースは意図的にDisposeする必要があります。また、画像などのリソースも無限に確保できるわけではないので、確保するリソースの量を制限するか、使い終わったらDisposeする方が好ましいようにおもえます。そう言えばフォームに配置するコントロールオブジェクトはDisposeを意識していませんでした。コントロールはアプリケーションの終了までリソースが解放されても困るので、フォームを閉じた後ガーベージコレクタが解放してくれることを期待することにします。

 

気になったので先程のTemporayDirectoryクラスをusingを使わない場合ディレクトリが削除されるか試してみます。

// tempdir2.cs

using System;
using System.IO;

class TemporaryDirectory : IDisposable {
	
	private string name;
	public string Name {
		get { return name; }
	}
	
	// コンストラクタ
	public TemporaryDirectory() {
		name = Path.GetTempFileName();
		if (File.Exists(name)) {
			File.Delete(name);
			Directory.CreateDirectory(name);
		}
		
	}
	
	public void Dispose() {
        Console.WriteLine("Dispose()");
        Dispose(true);
        GC.SuppressFinalize(this);
	}
	
    protected virtual void Dispose(bool disposing) {
        
        if (disposing) {
        	// Dispose()処理を記述
        }
        
        // ディレクトリの削除
		if (Directory.Exists(name)) {
			Console.WriteLine(String.Format("{0}が削除されました。", name));
			Directory.Delete(name);
		}
		
    }
    
   	// デストラクタ
	~TemporaryDirectory() {
        Console.WriteLine("デストラクタ");
		Dispose(false);
	}
}


public class TempDir {
	static void Main() {
		TemporaryDirectory d = new TemporaryDirectory();
		
		// テンポラリディレクトリを使う処理をここに記述
		
		if (Directory.Exists(d.Name)) {
			Console.WriteLine(String.Format("{0}が作成されました。", d.Name));
		}
			
		try {
			// 例外発生
			throw new Exception();
		} catch (Exception e) {
			Console.WriteLine(String.Format("catch:{0}", e.Message));
		}
		
		Console.WriteLine("目印");
	}
}

// csc tempdir2.cs

実行すると以下のようになりました。

C:\Users\karet\AppData\Local\Temp\tmp1738.tmpが作成されました。
catch:種類 'System.Exception' の例外がスローされました。
目印
デストラクタ
C:\Users\karet\AppData\Local\Temp\tmp1738.tmpが削除されました。

先程と異なりDispose()ではなくデストラクタが呼び出されています。先程はusingがDispose()を呼び出していたので、Dispose()→削除→目印の順番でしたが、こちらは目印→デストラクタ→削除の順番に処理されています。

デストラクタの呼び出しを誰が行っているかを私は知らないので、これがガーベージコレクタの仕事なのかは不明なのですが、とりあえずこの記述方法でディレクトリは削除されるようなので解放漏れはなさそうです。

 

と思いましたが、例外を補足せずに投げっぱなしにするとどうなるでしょう?

ソースは割愛しますが結果はusingの方はディレクトリが削除されますが、usingを使わない方はディレクトリは削除されませんでした。

そうなると、テンポラリディレクトリの様な処理の場合usingを使っておいた方が良さそうです。

 

 

コメント