System.IO.Path.Combineメソッドはファイル名を組み立てるための便利なメソッドですが、ファイル名に使用できない文字を含んでいる場合に例外を投げるケースと投げないケースがあることに気付きました。
検証プログラム §
using System;
using System.IO;
class ClassTest
{
[STAThread]
static void Main(string[] args)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
for( char ch = (char)0x20; ch<(char)0x7f; ch++ )
{
string fullpath = "c:\\[" + ch + "].please_delete_me.tmp";
try
{
using( StreamWriter writer = File.CreateText(fullpath) )
{
}
}
catch( Exception )
{
}
bool flag = false;
try
{
if( File.Exists(fullpath) )
{
File.Delete(fullpath);
//Console.WriteLine("deleted {0}",fullpath);
}
else
{
flag = true;
}
}
catch( Exception )
{
flag = true;
}
if( flag )
{
sb.Append(ch);
}
}
Console.WriteLine("ファイル作成に使用できなかった文字: {0}",sb.ToString() );
foreach( char ch in sb.ToString() )
{
bool flag = false;
string text;
try
{
text = "";
Path.Combine("c:\\",new string(ch,1) );
}
catch( Exception ex )
{
text = ex.GetType().FullName;
flag = true;
}
Console.WriteLine("Path.Combineにおいて、文字 '{0}' は例外を{1}。{2}",
ch,flag?"投げます":"投げません",text);
}
}
}
実行結果 §
ファイル作成に使用できなかった文字: "*/:<>?\|
Path.Combineにおいて、文字 '"' は例外を投げます。System.ArgumentException
Path.Combineにおいて、文字 '*' は例外を投げません。
Path.Combineにおいて、文字 '/' は例外を投げません。
Path.Combineにおいて、文字 ':' は例外を投げません。
Path.Combineにおいて、文字 '<' は例外を投げます。System.ArgumentException
Path.Combineにおいて、文字 '>' は例外を投げます。System.ArgumentException
Path.Combineにおいて、文字 '?' は例外を投げません。
Path.Combineにおいて、文字 '\' は例外を投げません。
Path.Combineにおいて、文字 '|' は例外を投げます。System.ArgumentException
考察 §
例外を投げない文字のいくつかは、投げない理由が明確に分かります。
"*"と"?"については、ワイルドカード指定に使われるので、ファイル作成に使用できないとしても、Combineメソッドで使用するのは正当です。
"/"と"\"については、実際にはファイル名ではなく、パスの区切り文字として認識されているだけ、ということが推測されます。
残りは":"ですが、これが良く分かりません。":"はドライブ名などの区切り文字であって、これをファイル名に許す必然性が分かりません。"\\.\C:"のような表記に対応するために許しているとも考えられますが、おそらくドライブ表記が2回出てくるパスは無いような気がするので(根拠無し)、既に先頭に"c:"が付いているパス名にもう1回":"が入るのは不正なパスであるような気がします。しかし、Combineメソッドは例外を投げる対象としていません。
結論 §
任意の文字を受け付けて、それを引数にCombineメソッドを呼び出すコードの単体テストを書くときには注意しよう。作成できないパスであるにも関わらず、例外を投げるパターンと投げないパターンがあり得るので。双方のケースについて、テストケースを作成することが望ましい。