C#で拡張子を取得・判定・変更する方法|Path.GetExtensionの使い方と注意点

はじめに

C#でファイルを扱うとき、拡張子の取得・判定・変更はよく使う処理です。たとえば、アップロードされたファイルが画像かどうかを確認したい場合、CSVファイルだけを読み込みたい場合、保存時にファイル名の拡張子を変更したい場合などに必要になります。

C#では、拡張子を扱う処理にSystem.IO.Pathクラスを使うのが基本です。特にPath.GetExtensionを使うと、ファイル名やフルパスから簡単に拡張子を取得できます。

一方で、Path.GetExtensionには「戻り値にドットが含まれる」「拡張子がない場合は空文字になる」「URLのクエリ文字列にはそのまま使わない方がよい」など、実装時に知っておきたい注意点もあります。

この記事では、C#で拡張子を取得・判定・変更する方法を、Path.GetExtensionPath.ChangeExtensionPath.GetFileNameWithoutExtensionなどの使い方とあわせて解説します。

1. C#で拡張子を扱う基本

1-1. 拡張子とは何か

拡張子とは、ファイル名の末尾に付く.txt.csv.jpg.pdfのような文字列のことです。一般的には、ファイルの種類を表すために使われます。

たとえば、次のファイル名を見てみましょう。

C#
sample.txt
image.jpg
data.csv
document.pdf

この場合、それぞれの拡張子は次のようになります。

C#
.txt
.jpg
.csv
.pdf

C#で拡張子を取得する場合、基本的には最後のドット以降の文字列を拡張子として扱います。

たとえば、次のようなファイル名があります。

C#
archive.tar.gz

この場合、Path.GetExtensionで取得できる拡張子は.gzです。.tar.gz全体を複合拡張子として扱いたい場合は、別途判定処理を書く必要があります。

1-2. C#で拡張子を取得・判定・変更する主な方法

C#で拡張子を扱うときによく使うメソッドは、主に次のとおりです。

C#
Path.GetExtension(path)
Path.ChangeExtension(path, extension)
Path.GetFileName(path)
Path.GetFileNameWithoutExtension(path)
Path.GetDirectoryName(path)
Path.Combine(path1, path2)

それぞれの役割は次のようになります。

Path.GetExtensionは、ファイル名やパスから拡張子を取得します。

C#
string extension = Path.GetExtension("sample.txt");
// .txt

Path.ChangeExtensionは、ファイル名やパスの拡張子を変更します。

C#
string newPath = Path.ChangeExtension("sample.txt", ".csv");
// sample.csv

Path.GetFileNameWithoutExtensionは、拡張子を除いたファイル名を取得します。

C#
string name = Path.GetFileNameWithoutExtension("sample.txt");
// sample

このように、C#では文字列操作で無理に拡張子を取り出すよりも、Pathクラスを使うのが基本です。

1-3. この記事で扱うサンプルコードの前提

この記事のサンプルコードでは、次の名前空間を使用します。

C#
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;

.NET 6以降でも、.NET Frameworkでも、基本的なPathクラスの使い方はほぼ同じです。

ただし、OSによってパス区切り文字が異なります。Windowsでは\、LinuxやmacOSでは/が使われます。そのため、パスを連結するときは文字列結合ではなく、Path.Combineを使うことをおすすめします。

2. Path.GetExtensionで拡張子を取得する方法

2-1. Path.GetExtensionの基本構文

C#で拡張子を取得するには、Path.GetExtensionを使います。

C#
string extension = Path.GetExtension(path);

pathには、ファイル名またはフルパスを指定できます。戻り値は文字列で、拡張子がある場合はドットを含んだ形式で返されます。

C#
string extension = Path.GetExtension("sample.txt");

Console.WriteLine(extension);

実行結果は次のとおりです。

C#
.txt

Path.GetExtensionは、ファイル名の最後のドット以降を拡張子として取得します。

2-2. ファイル名から拡張子を取得する

ファイル名だけを指定して拡張子を取得する例です。

C#
string fileName = "report.pdf";

string extension = Path.GetExtension(fileName);

Console.WriteLine(extension);

実行結果は次のようになります。

C#
.pdf

画像ファイルの場合も同じです。

C#
string fileName = "photo.jpg";

string extension = Path.GetExtension(fileName);

Console.WriteLine(extension);

実行結果は次のとおりです。

C#
.jpg

ファイル名だけを扱う場合でも、SplitSubstringではなくPath.GetExtensionを使うと、コードが読みやすくなります。

2-3. フルパスから拡張子を取得する

Path.GetExtensionは、フルパスにも使えます。

C#
string path = @"C:\Users\user\Documents\data.csv";

string extension = Path.GetExtension(path);

Console.WriteLine(extension);

実行結果は次のようになります。

C#
.csv

LinuxやmacOSのようなパスでも同じように使えます。

C#
string path = "/home/user/documents/data.csv";

string extension = Path.GetExtension(path);

Console.WriteLine(extension);

実行結果は次のとおりです。

C#
.csv

フルパスを渡した場合でも、最後のファイル名部分から拡張子が取得されます。

2-4. 戻り値にはドットが含まれる点に注意

Path.GetExtensionの戻り値には、先頭のドットが含まれます。

C#
string extension = Path.GetExtension("sample.txt");

Console.WriteLine(extension == ".txt");
Console.WriteLine(extension == "txt");

実行結果は次のようになります。

C#
True
False

そのため、拡張子を比較するときは、.txtのようにドット付きで比較する必要があります。

C#
string extension = Path.GetExtension("sample.txt");

if (extension == ".txt")
{
Console.WriteLine("テキストファイルです");
}

ドットなしで扱いたい場合は、TrimStartを使う方法があります。

C#
string extension = Path.GetExtension("sample.txt").TrimStart('.');

Console.WriteLine(extension);

実行結果は次のとおりです。

C#
txt

ただし、プロジェクト内で「拡張子はドット付きで扱う」「拡張子はドットなしで扱う」のルールが混在するとバグの原因になります。C#のPath.GetExtensionを使う場合は、ドット付きで統一する方が自然です。

2-5. 拡張子がない場合の戻り値

拡張子がないファイル名を渡した場合、Path.GetExtensionは空文字を返します。

C#
string extension = Path.GetExtension("README");

Console.WriteLine($"[{extension}]");

実行結果は次のようになります。

C#
[]

拡張子がないかどうかを判定する場合は、string.IsNullOrEmptyを使うとわかりやすいです。

C#
string fileName = "README";
string extension = Path.GetExtension(fileName);

if (string.IsNullOrEmpty(extension))
{
Console.WriteLine("拡張子はありません");
}

実行結果は次のとおりです。

C#
拡張子はありません

3. Path.GetExtensionの注意点と例外ケース

3-1. null・空文字を渡した場合

Path.GetExtensionnullを渡すと、戻り値はnullになります。

C#
string? path = null;

string? extension = Path.GetExtension(path);

Console.WriteLine(extension == null);

実行結果は次のようになります。

C#
True

空文字を渡した場合は、空文字が返ります。

C#
string extension = Path.GetExtension("");

Console.WriteLine($"[{extension}]");

実行結果は次のようになります。

C#
[]

実務では、ユーザー入力やアップロードファイル名がnullや空文字になる可能性もあります。そのため、事前にチェックしておくと安全です。

C#
string? fileName = "";

if (string.IsNullOrWhiteSpace(fileName))
{
Console.WriteLine("ファイル名が指定されていません");
return;
}

string extension = Path.GetExtension(fileName);

3-2. ドットで終わるファイル名の場合

ファイル名がドットで終わる場合、拡張子は空文字として扱われます。

C#
string extension = Path.GetExtension("sample.");

Console.WriteLine($"[{extension}]");

実行結果は次のようになります。

C#
[]

sample.は末尾にドットがありますが、ドットの後ろに文字がないため、拡張子としては扱われません。

ファイル名の検証を行う場合は、単にドットが含まれているかどうかではなく、Path.GetExtensionの結果が空でないかを確認する方が適切です。

C#
string fileName = "sample.";
string extension = Path.GetExtension(fileName);

if (string.IsNullOrEmpty(extension))
{
Console.WriteLine("有効な拡張子はありません");
}

3-3. 隠しファイルや先頭がドットのファイル名の場合

Unix系OSでは、.gitignore.envのように、先頭がドットのファイル名が隠しファイルとして使われます。

C#
string extension = Path.GetExtension(".gitignore");

Console.WriteLine($"[{extension}]");

この場合、実行結果は次のようになります。

C#
[]

.gitignoreはドットから始まっていますが、通常の意味での拡張子はありません。

一方で、次のようなファイル名の場合は拡張子が取得されます。

C#
string extension = Path.GetExtension(".config.json");

Console.WriteLine(extension);

実行結果は次のとおりです。

C#
.json

先頭がドットのファイル名を扱う場合は、「隠しファイル名」と「拡張子」を混同しないように注意しましょう。

3-4. 複数ドットを含むファイル名の場合

ファイル名に複数のドットが含まれる場合、Path.GetExtensionは最後のドット以降を拡張子として取得します。

C#
string extension = Path.GetExtension("backup.2024.01.01.zip");

Console.WriteLine(extension);

実行結果は次のようになります。

C#
.zip

.tar.gzのような複合拡張子の場合も、最後の.gzだけが返ります。

C#
string extension = Path.GetExtension("archive.tar.gz");

Console.WriteLine(extension);

実行結果は次のとおりです。

C#
.gz

.tar.gzをひとまとまりの拡張子として扱いたい場合は、EndsWithなどを使って判定します。

C#
string fileName = "archive.tar.gz";

if (fileName.EndsWith(".tar.gz", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("tar.gzファイルです");
}

3-5. URLやクエリ文字列にはそのまま使わない

Path.GetExtensionはファイルシステムのパスを扱うためのメソッドです。そのため、URLに対してそのまま使うと、意図しない結果になることがあります。

たとえば、次のようなURLがあります。

C#
string url = "https://example.com/files/report.pdf?download=true";

string extension = Path.GetExtension(url);

Console.WriteLine(extension);

この場合、環境や文字列の内容によっては、.pdfではなくクエリ文字列を含んだ結果になる可能性があります。

URLから拡張子を取得したい場合は、まずUriでパス部分を取り出してからPath.GetExtensionを使います。

C#
string url = "https://example.com/files/report.pdf?download=true";

Uri uri = new Uri(url);
string extension = Path.GetExtension(uri.AbsolutePath);

Console.WriteLine(extension);

実行結果は次のようになります。

C#
.pdf

URLを扱う場合は、Path.GetExtension(url)のように直接渡すのではなく、URLのパス部分を明確に取り出してから処理しましょう。

4. C#で拡張子を判定する方法

4-1. 特定の拡張子か判定する基本コード

C#で特定の拡張子かどうかを判定するには、Path.GetExtensionで取得した値を比較します。

C#
string fileName = "data.csv";
string extension = Path.GetExtension(fileName);

if (extension == ".csv")
{
Console.WriteLine("CSVファイルです");
}

実行結果は次のとおりです。

C#
CSVファイルです

ただし、このコードでは.CSV.Csvのような大文字・小文字違いを判定できません。

C#
string fileName = "data.CSV";
string extension = Path.GetExtension(fileName);

Console.WriteLine(extension == ".csv");

実行結果は次のようになります。

C#
False

そのため、実務では大文字・小文字を区別しない比較を使うのが一般的です。

4-2. 大文字・小文字を区別せずに比較する

拡張子を比較するときは、StringComparison.OrdinalIgnoreCaseを使うのがおすすめです。

C#
string fileName = "data.CSV";
string extension = Path.GetExtension(fileName);

if (string.Equals(extension, ".csv", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("CSVファイルです");
}

実行結果は次のとおりです。

C#
CSVファイルです

ToLowerToUpperで変換してから比較する方法もありますが、比較の目的が明確な場合はStringComparison.OrdinalIgnoreCaseを指定する方が意図が伝わりやすくなります。

C#
if (string.Equals(extension, ".pdf", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("PDFファイルです");
}

拡張子は通常、文化依存の比較をする必要がないため、OrdinalIgnoreCaseを使うとよいでしょう。

4-3. 複数の拡張子を許可する判定方法

画像ファイルのように、複数の拡張子を許可したい場合があります。

C#
.jpg
.jpeg
.png
.gif
.webp

このような場合は、許可する拡張子の配列やHashSetを用意して判定します。

C#
string fileName = "photo.JPG";
string extension = Path.GetExtension(fileName);

string[] allowedExtensions = { ".jpg", ".jpeg", ".png", ".gif", ".webp" };

bool isAllowed = allowedExtensions.Contains(
extension,
StringComparer.OrdinalIgnoreCase
);

if (isAllowed)
{
Console.WriteLine("許可された画像ファイルです");
}

大量の拡張子を判定する場合や、何度も判定する場合はHashSet<string>を使うと便利です。

C#
var allowedExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".jpg",
".jpeg",
".png",
".gif",
".webp"
};

string fileName = "photo.PNG";
string extension = Path.GetExtension(fileName);

if (allowedExtensions.Contains(extension))
{
Console.WriteLine("許可された拡張子です");
}

HashSetを作成するときにStringComparer.OrdinalIgnoreCaseを指定しておくと、判定時に大文字・小文字を気にしなくて済みます。

4-4. 画像・CSV・PDFなどファイル種別ごとの判定例

画像ファイルかどうかを判定する例です。

C#
static bool IsImageFile(string fileName)
{
var imageExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".jpg",
".jpeg",
".png",
".gif",
".bmp",
".webp"
};

string extension = Path.GetExtension(fileName);
return imageExtensions.Contains(extension);
}

使用例です。

C#
Console.WriteLine(IsImageFile("photo.jpg"));
Console.WriteLine(IsImageFile("photo.PNG"));
Console.WriteLine(IsImageFile("document.pdf"));

実行結果は次のようになります。

C#
True
True
False

CSVファイルかどうかを判定する例です。

C#
static bool IsCsvFile(string fileName)
{
string extension = Path.GetExtension(fileName);

return string.Equals(
extension,
".csv",
StringComparison.OrdinalIgnoreCase
);
}

PDFファイルかどうかを判定する例です。

C#
static bool IsPdfFile(string fileName)
{
string extension = Path.GetExtension(fileName);

return string.Equals(
extension,
".pdf",
StringComparison.OrdinalIgnoreCase
);
}

拡張子判定をメソッドにまとめておくと、アップロード処理やファイル読み込み処理で再利用しやすくなります。

4-5. 拡張子だけで安全性を判断してはいけない理由

拡張子は、あくまでファイル名の一部です。ファイルの中身を保証するものではありません。

たとえば、実行ファイルの名前を次のように変更することは可能です。

C#
malware.exe
malware.jpg

拡張子が.jpgになっていても、中身が本当に画像ファイルとは限りません。

そのため、Webアプリでファイルアップロードを扱う場合は、拡張子チェックだけで安全と判断してはいけません。必要に応じて、次のような確認も組み合わせます。

C#
拡張子のチェック
MIMEタイプのチェック
ファイルサイズのチェック
ファイル内容の検証
保存先の制限
ファイル名の無害化

拡張子判定は便利ですが、セキュリティ対策としては不十分です。特にユーザーがアップロードするファイルを扱う場合は、複数の検証を組み合わせることが重要です。

5. C#で拡張子を変更する方法

5-1. Path.ChangeExtensionの基本構文

C#で拡張子を変更するには、Path.ChangeExtensionを使います。

C#
string newPath = Path.ChangeExtension(path, extension);

第1引数にファイル名またはパス、第2引数に新しい拡張子を指定します。

C#
string fileName = "sample.txt";

string newFileName = Path.ChangeExtension(fileName, ".csv");

Console.WriteLine(newFileName);

実行結果は次のようになります。

C#
sample.csv

Path.ChangeExtensionは、文字列としてのファイル名やパスを変更するメソッドです。実際のファイル名をディスク上で変更するわけではありません。

5-2. ファイル名の拡張子を変更する

ファイル名の拡張子を変更する例です。

C#
string fileName = "report.txt";

string changed = Path.ChangeExtension(fileName, ".pdf");

Console.WriteLine(changed);

実行結果は次のとおりです。

C#
report.pdf

新しい拡張子には、ドット付きでもドットなしでも指定できます。

C#
string fileName = "report.txt";

Console.WriteLine(Path.ChangeExtension(fileName, ".pdf"));
Console.WriteLine(Path.ChangeExtension(fileName, "pdf"));

実行結果は次のようになります。

C#
report.pdf
report.pdf

ただし、コードの一貫性を保つためには、ドット付きで指定するルールにしておくとわかりやすいです。

5-3. フルパスの拡張子を変更する

Path.ChangeExtensionは、フルパスにも使えます。

C#
string path = @"C:\work\data.txt";

string newPath = Path.ChangeExtension(path, ".csv");

Console.WriteLine(newPath);

実行結果は次のようになります。

C#
C:\work\data.csv

ディレクトリ部分はそのままで、ファイル名の拡張子だけが変更されます。

C#
string path = "/home/user/data.txt";

string newPath = Path.ChangeExtension(path, ".json");

Console.WriteLine(newPath);

実行結果は次のとおりです。

C#
/home/user/data.json

ログファイルや一時ファイルの保存名を作るときにも便利です。

C#
string inputPath = @"C:\work\report.xlsx";
string outputPath = Path.ChangeExtension(inputPath, ".csv");

Console.WriteLine(outputPath);

5-4. 拡張子を削除する方法

拡張子を削除したい場合は、Path.ChangeExtensionの第2引数にnullを指定します。

C#
string fileName = "sample.txt";

string result = Path.ChangeExtension(fileName, null);

Console.WriteLine(result);

実行結果は次のようになります。

C#
sample

フルパスの場合も同じです。

C#
string path = @"C:\work\sample.txt";

string result = Path.ChangeExtension(path, null);

Console.WriteLine(result);

実行結果は次のとおりです。

C#
C:\work\sample

拡張子なしのファイル名を作りたい場合は、Path.GetFileNameWithoutExtensionを使う方法もあります。

C#
string name = Path.GetFileNameWithoutExtension("sample.txt");

Console.WriteLine(name);

実行結果は次のとおりです。

C#
sample

5-5. 実ファイル名を変更する場合はFile.Moveを使う

Path.ChangeExtensionは、あくまで文字列を変更するだけです。実際のファイル名を変更したい場合は、File.Moveを使います。

C#
string oldPath = @"C:\work\sample.txt";
string newPath = Path.ChangeExtension(oldPath, ".csv");

File.Move(oldPath, newPath);

このコードを実行すると、実ファイルの名前がsample.txtからsample.csvに変更されます。

ただし、移動先に同名ファイルが存在する場合や、ファイルが使用中の場合は例外が発生する可能性があります。実務では、事前に存在確認を行うと安全です。

C#
string oldPath = @"C:\work\sample.txt";
string newPath = Path.ChangeExtension(oldPath, ".csv");

if (!File.Exists(oldPath))
{
Console.WriteLine("元のファイルが存在しません");
return;
}

if (File.Exists(newPath))
{
Console.WriteLine("変更後のファイルがすでに存在します");
return;
}

File.Move(oldPath, newPath);

拡張子を変更するとファイルの中身が変換されるわけではありません。たとえば、.txt.pdfに変更しても、テキストファイルがPDF形式に変換されるわけではない点に注意しましょう。

6. 拡張子なしのファイル名やパスを取得する方法

6-1. Path.GetFileNameでファイル名を取得する

フルパスからファイル名だけを取得したい場合は、Path.GetFileNameを使います。

C#
string path = @"C:\work\reports\sample.csv";

string fileName = Path.GetFileName(path);

Console.WriteLine(fileName);

実行結果は次のようになります。

C#
sample.csv

LinuxやmacOS形式のパスでも同様です。

C#
string path = "/home/user/reports/sample.csv";

string fileName = Path.GetFileName(path);

Console.WriteLine(fileName);

実行結果は次のとおりです。

C#
sample.csv

Path.GetFileNameは、拡張子を含むファイル名を取得します。

6-2. Path.GetFileNameWithoutExtensionで拡張子なしの名前を取得する

拡張子を除いたファイル名だけを取得したい場合は、Path.GetFileNameWithoutExtensionを使います。

C#
string path = @"C:\work\reports\sample.csv";

string name = Path.GetFileNameWithoutExtension(path);

Console.WriteLine(name);

実行結果は次のようになります。

C#
sample

ファイル名だけを渡しても使えます。

C#
string fileName = "document.pdf";

string name = Path.GetFileNameWithoutExtension(fileName);

Console.WriteLine(name);

実行結果は次のとおりです。

C#
document

拡張子を除いた名前を使って、別のファイル名を作ることもできます。

C#
string fileName = "document.pdf";

string name = Path.GetFileNameWithoutExtension(fileName);
string extension = Path.GetExtension(fileName);

string newFileName = $"{name}_backup{extension}";

Console.WriteLine(newFileName);

実行結果は次のようになります。

C#
document_backup.pdf

6-3. Path.GetDirectoryNameでディレクトリ部分を取得する

フルパスからディレクトリ部分を取得したい場合は、Path.GetDirectoryNameを使います。

C#
string path = @"C:\work\reports\sample.csv";

string? directory = Path.GetDirectoryName(path);

Console.WriteLine(directory);

実行結果は次のようになります。

C#
C:\work\reports

ファイル名、拡張子なしのファイル名、拡張子、ディレクトリを分けて取得する例です。

C#
string path = @"C:\work\reports\sample.csv";

string? directory = Path.GetDirectoryName(path);
string fileName = Path.GetFileName(path);
string nameWithoutExtension = Path.GetFileNameWithoutExtension(path);
string extension = Path.GetExtension(path);

Console.WriteLine(directory);
Console.WriteLine(fileName);
Console.WriteLine(nameWithoutExtension);
Console.WriteLine(extension);

実行結果は次のようになります。

C#
C:\work\reports
sample.csv
sample
.csv

6-4. Path.Combineで安全にパスを組み立てる

パスを組み立てるときは、文字列結合ではなくPath.Combineを使います。

C#
string directory = @"C:\work\reports";
string fileName = "sample.csv";

string path = Path.Combine(directory, fileName);

Console.WriteLine(path);

実行結果は次のようになります。

C#
C:\work\reports\sample.csv

文字列結合で書くと、区切り文字の不足や重複が起きやすくなります。

C#
string path = directory + "\\" + fileName;

このような書き方は、OSごとの差にも弱くなります。

Path.Combineを使えば、環境に応じたパス区切り文字で安全に結合できます。

C#
string directory = Path.GetDirectoryName(@"C:\work\reports\sample.csv")!;
string name = Path.GetFileNameWithoutExtension("sample.csv");
string newFileName = $"{name}_backup.csv";

string newPath = Path.Combine(directory, newFileName);

Console.WriteLine(newPath);

7. よくある実装パターン

7-1. アップロードファイルの拡張子チェック

Webアプリでファイルアップロードを受け付ける場合、まず拡張子をチェックすることがあります。

C#
static bool IsAllowedUploadFile(string fileName)
{
var allowedExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".jpg",
".jpeg",
".png",
".pdf"
};

string extension = Path.GetExtension(fileName);

return allowedExtensions.Contains(extension);
}

使用例です。

C#
string fileName = "profile.PNG";

if (IsAllowedUploadFile(fileName))
{
Console.WriteLine("アップロード可能なファイルです");
}
else
{
Console.WriteLine("許可されていないファイルです");
}

ただし、アップロード処理では拡張子だけで安全性を判断してはいけません。ファイルサイズ、MIMEタイプ、ファイル内容、保存先、ファイル名の扱いも含めて検証しましょう。

特に、ユーザーが送信したファイル名をそのまま保存パスに使うのは避けるべきです。保存時は、システム側で新しいファイル名を生成する方が安全です。

C#
string originalFileName = "profile.png";
string extension = Path.GetExtension(originalFileName);

string savedFileName = $"{Guid.NewGuid()}{extension}";

Console.WriteLine(savedFileName);

7-2. CSVファイルだけを読み込む処理

指定されたファイルがCSVの場合だけ読み込む例です。

C#
static void ReadCsvIfValid(string path)
{
string extension = Path.GetExtension(path);

if (!string.Equals(extension, ".csv", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("CSVファイルではありません");
return;
}

string[] lines = File.ReadAllLines(path);

foreach (string line in lines)
{
Console.WriteLine(line);
}
}

使用例です。

C#
ReadCsvIfValid(@"C:\work\data.csv");

実務では、ファイルの存在確認も行うと安全です。

C#
static void ReadCsvIfValid(string path)
{
if (!File.Exists(path))
{
Console.WriteLine("ファイルが存在しません");
return;
}

string extension = Path.GetExtension(path);

if (!string.Equals(extension, ".csv", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("CSVファイルではありません");
return;
}

string[] lines = File.ReadAllLines(path);

foreach (string line in lines)
{
Console.WriteLine(line);
}
}

7-3. 画像ファイルだけを抽出する処理

フォルダ内のファイルから画像ファイルだけを抽出する例です。

C#
static IEnumerable<string> GetImageFiles(string directory)
{
var imageExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".jpg",
".jpeg",
".png",
".gif",
".bmp",
".webp"
};

return Directory.EnumerateFiles(directory)
.Where(path => imageExtensions.Contains(Path.GetExtension(path)));
}

使用例です。

C#
string directory = @"C:\work\images";

foreach (string imagePath in GetImageFiles(directory))
{
Console.WriteLine(imagePath);
}

Directory.EnumerateFilesを使うと、ファイルを列挙しながら処理できます。大量のファイルを扱う場合は、Directory.GetFilesよりも効率的な場合があります。

サブフォルダも含めて画像を抽出したい場合は、SearchOption.AllDirectoriesを指定します。

C#
static IEnumerable<string> GetImageFilesAllDirectories(string directory)
{
var imageExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".jpg",
".jpeg",
".png",
".gif",
".bmp",
".webp"
};

return Directory.EnumerateFiles(directory, "*", SearchOption.AllDirectories)
.Where(path => imageExtensions.Contains(Path.GetExtension(path)));
}

7-4. 拡張子ごとに処理を分岐する方法

拡張子ごとに処理を変えたい場合は、switch式やswitch文を使うと見通しがよくなります。

C#
string fileName = "data.csv";
string extension = Path.GetExtension(fileName).ToLowerInvariant();

switch (extension)
{
case ".csv":
Console.WriteLine("CSVとして処理します");
break;

case ".json":
Console.WriteLine("JSONとして処理します");
break;

case ".xml":
Console.WriteLine("XMLとして処理します");
break;

case ".txt":
Console.WriteLine("テキストとして処理します");
break;

default:
Console.WriteLine("未対応の拡張子です");
break;
}

switch式で書くこともできます。

C#
string fileName = "data.json";
string extension = Path.GetExtension(fileName);

string fileType = extension.ToLowerInvariant() switch
{
".csv" => "CSV",
".json" => "JSON",
".xml" => "XML",
".txt" => "Text",
_ => "Unknown"
};

Console.WriteLine(fileType);

ただし、単純な比較ではToLowerInvariantを使うよりも、StringComparer.OrdinalIgnoreCaseを使ったDictionaryにする方法もあります。

C#
var handlers = new Dictionary<string, Action>(StringComparer.OrdinalIgnoreCase)
{
[".csv"] = () => Console.WriteLine("CSVを処理します"),
[".json"] = () => Console.WriteLine("JSONを処理します"),
[".xml"] = () => Console.WriteLine("XMLを処理します")
};

string fileName = "data.JSON";
string extension = Path.GetExtension(fileName);

if (handlers.TryGetValue(extension, out Action? handler))
{
handler();
}
else
{
Console.WriteLine("未対応の拡張子です");
}

7-5. ファイル名に日時を付けて拡張子を維持する方法

ログ出力やバックアップ作成では、元の拡張子を維持したままファイル名に日時を付けたいことがあります。

C#
string fileName = "report.pdf";

string name = Path.GetFileNameWithoutExtension(fileName);
string extension = Path.GetExtension(fileName);

string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");

string newFileName = $"{name}_{timestamp}{extension}";

Console.WriteLine(newFileName);

実行結果の例です。

C#
report_20260615_103000.pdf

フルパスを扱う場合は、ディレクトリ部分も取得してPath.Combineで組み立てます。

C#
string path = @"C:\work\report.pdf";

string? directory = Path.GetDirectoryName(path);
string name = Path.GetFileNameWithoutExtension(path);
string extension = Path.GetExtension(path);

string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string newFileName = $"{name}_{timestamp}{extension}";

string newPath = Path.Combine(directory!, newFileName);

Console.WriteLine(newPath);

このようにすると、拡張子を維持したまま安全に新しいファイルパスを作成できます。

8. Path.GetExtensionと文字列操作の違い

8-1. SplitやSubstringで取得する方法との比較

拡張子は文字列操作でも取得できます。

C#
string fileName = "sample.txt";

string extension = "." + fileName.Split('.').Last();

Console.WriteLine(extension);

実行結果は次のようになります。

C#
.txt

一見すると問題なさそうですが、この方法には注意点があります。

たとえば、拡張子がないファイル名では期待どおりに動きません。

C#
string fileName = "README";

string extension = "." + fileName.Split('.').Last();

Console.WriteLine(extension);

実行結果は次のようになります。

C#
.README

本来は拡張子なしとして扱いたいのに、ファイル名全体を拡張子のように扱ってしまいます。

Path.GetExtensionを使えば、拡張子がない場合は空文字になります。

C#
string extension = Path.GetExtension("README");

Console.WriteLine($"[{extension}]");

実行結果は次のとおりです。

C#
[]

8-2. LastIndexOfで拡張子を取得する場合の問題点

LastIndexOfSubstringを使って拡張子を取得する方法もあります。

C#
string fileName = "sample.txt";

int index = fileName.LastIndexOf('.');
string extension = fileName.Substring(index);

Console.WriteLine(extension);

実行結果は次のようになります。

C#
.txt

しかし、このコードはドットがないファイル名で問題が起きます。

C#
string fileName = "README";

int index = fileName.LastIndexOf('.');
string extension = fileName.Substring(index);

LastIndexOfの結果が-1になるため、Substringで例外が発生します。

対策として条件分岐を書くことはできます。

C#
string fileName = "README";

int index = fileName.LastIndexOf('.');
string extension = index >= 0 ? fileName.Substring(index) : "";

Console.WriteLine($"[{extension}]");

ただし、先頭がドットのファイル名、複数ドットを含むファイル名、パス区切り文字を含む文字列などを考慮すると、独自実装は複雑になりがちです。

8-3. 基本的にはPathクラスを使うべき理由

C#で拡張子を扱う場合は、基本的にPathクラスを使うべきです。

理由は、コードの意図が明確になるからです。

C#
string extension = Path.GetExtension(fileName);

このコードは「拡張子を取得している」とすぐにわかります。

一方、文字列操作で書くと、何をしたいコードなのか読み取りづらくなります。

C#
string extension = fileName.Substring(fileName.LastIndexOf('.'));

また、Pathクラスには拡張子以外にも、ファイル名、ディレクトリ名、拡張子なしの名前、パス結合など、ファイルパスを扱うためのメソッドがそろっています。

C#
Path.GetExtension(path)
Path.GetFileName(path)
Path.GetFileNameWithoutExtension(path)
Path.GetDirectoryName(path)
Path.Combine(directory, fileName)
Path.ChangeExtension(path, extension)

拡張子の取得だけを見れば文字列操作でも実装できますが、保守性や安全性を考えると、Pathクラスを使う方が適しています。

9. C#で拡張子を扱うときのベストプラクティス

9-1. 比較にはStringComparison.OrdinalIgnoreCaseを使う

拡張子を比較するときは、大文字・小文字を区別しない比較を使うのが基本です。

C#
string extension = Path.GetExtension("DATA.CSV");

if (string.Equals(extension, ".csv", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("CSVファイルです");
}

==で比較すると、大文字・小文字の違いで判定に失敗することがあります。

C#
Console.WriteLine(".CSV" == ".csv");

実行結果は次のとおりです。

C#
False

拡張子の比較では、StringComparison.OrdinalIgnoreCaseを使うと、意図が明確で安全です。

9-2. 拡張子のドット有無を統一する

Path.GetExtensionの戻り値にはドットが含まれます。

C#
string extension = Path.GetExtension("sample.txt");
// .txt

そのため、比較対象もドット付きに統一するのがおすすめです。

C#
if (string.Equals(extension, ".txt", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("テキストファイルです");
}

ドットなしで扱う場合は、取得時に変換します。

C#
string extensionWithoutDot = Path.GetExtension("sample.txt").TrimStart('.');

Console.WriteLine(extensionWithoutDot);

実行結果は次のとおりです。

C#
txt

ただし、プロジェクト内でドット付きとドットなしが混在すると、比較ミスの原因になります。どちらかに統一しましょう。

9-3. ユーザー入力のファイル名は必ず検証する

ユーザーが入力したファイル名や、アップロード時に送られてきたファイル名は、そのまま信用してはいけません。

たとえば、次のような値が渡される可能性があります。

C#
""
null
"../../secret.txt"
"sample.exe"
"image.jpg.exe"

ファイル名を扱う前に、最低限の検証を行いましょう。

C#
static bool IsValidFileName(string? fileName)
{
if (string.IsNullOrWhiteSpace(fileName))
{
return false;
}

string nameOnly = Path.GetFileName(fileName);

if (fileName != nameOnly)
{
return false;
}

return true;
}

この例では、パスを含むファイル名を拒否しています。

C#
Console.WriteLine(IsValidFileName("sample.txt"));
Console.WriteLine(IsValidFileName("../sample.txt"));

実行結果は次のようになります。

C#
True
False

保存時には、ユーザーが送信したファイル名をそのまま使わず、Guidなどで新しい名前を生成する方法も有効です。

C#
string originalFileName = "photo.jpg";
string extension = Path.GetExtension(originalFileName);

string safeFileName = $"{Guid.NewGuid()}{extension}";

9-4. MIMEタイプやファイル内容の確認も併用する

拡張子は簡単に変更できます。そのため、拡張子だけでファイルの種類を完全に判断することはできません。

Webアプリでアップロードファイルを扱う場合は、拡張子の確認に加えて、MIMEタイプやファイル内容の確認も検討します。

C#
拡張子が許可されているか
MIMEタイプが想定どおりか
ファイルサイズが上限以下か
ファイルの中身が想定形式か
保存先が公開ディレクトリ外になっているか

画像ファイルであれば、画像として読み込めるか確認する方法もあります。CSVであれば、想定した列数や文字コードを検証することも重要です。

拡張子チェックは入口のチェックとしては便利ですが、それだけで安全性を保証できるわけではありません。

9-5. OSごとのパス区切り文字に依存しない書き方

Windowsではパス区切り文字に\を使います。

C#
C:\work\data.csv

LinuxやmacOSでは/を使います。

C#
/home/user/data.csv

C#でパスを組み立てるときに、区切り文字を直接書くと、OSによって問題が起きることがあります。

C#
string path = directory + "\\" + fileName;

このような書き方ではなく、Path.Combineを使いましょう。

C#
string path = Path.Combine(directory, fileName);

拡張子の取得や変更だけでなく、ファイルパス全体を扱う場合は、Pathクラスを中心に書くことでOS差異に強いコードになります。

10. よくある質問

10-1. Path.GetExtensionでドットなしの拡張子を取得できる?

Path.GetExtensionの戻り値には、基本的にドットが含まれます。

C#
string extension = Path.GetExtension("sample.txt");

Console.WriteLine(extension);

実行結果は次のとおりです。

C#
.txt

ドットなしで取得したい場合は、TrimStart('.')を使います。

C#
string extension = Path.GetExtension("sample.txt").TrimStart('.');

Console.WriteLine(extension);

実行結果は次のようになります。

C#
txt

ただし、C#のPath.GetExtensionを使う場合は、ドット付きで扱う方が自然です。比較対象も.txt.csvのようにドット付きで統一するとよいでしょう。

10-2. 拡張子がないファイルを判定するには?

拡張子がないかどうかを判定するには、Path.GetExtensionの戻り値をstring.IsNullOrEmptyで確認します。

C#
string fileName = "README";
string extension = Path.GetExtension(fileName);

if (string.IsNullOrEmpty(extension))
{
Console.WriteLine("拡張子がありません");
}

実行結果は次のとおりです。

C#
拡張子がありません

空文字やnullもまとめて判定したい場合は、次のように書けます。

C#
static bool HasExtension(string? path)
{
return !string.IsNullOrEmpty(Path.GetExtension(path));
}

使用例です。

C#
Console.WriteLine(HasExtension("sample.txt"));
Console.WriteLine(HasExtension("README"));
Console.WriteLine(HasExtension(null));

実行結果は次のようになります。

C#
True
False
False

10-3. .tar.gzのような複合拡張子はどう扱う?

Path.GetExtensionは、最後のドット以降を拡張子として返します。

C#
string extension = Path.GetExtension("archive.tar.gz");

Console.WriteLine(extension);

実行結果は次のとおりです。

C#
.gz

.tar.gz全体を複合拡張子として扱いたい場合は、EndsWithで個別に判定します。

C#
string fileName = "archive.tar.gz";

if (fileName.EndsWith(".tar.gz", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("tar.gzファイルです");
}

複合拡張子を複数扱う場合は、許可リストを用意するとよいでしょう。

C#
var allowedExtensions = new[]
{
".tar.gz",
".tar.bz2",
".tar.xz"
};

string fileName = "backup.tar.gz";

bool isAllowed = allowedExtensions.Any(ext =>
fileName.EndsWith(ext, StringComparison.OrdinalIgnoreCase)
);

Console.WriteLine(isAllowed);

実行結果は次のとおりです。

C#
True

10-4. ファイルの拡張子を変更すると中身も変わる?

ファイルの拡張子を変更しても、中身は変わりません。

たとえば、次のように.txt.pdfに変更しても、テキストファイルがPDFファイルに変換されるわけではありません。

C#
string newFileName = Path.ChangeExtension("sample.txt", ".pdf");

Console.WriteLine(newFileName);

実行結果は次のとおりです。

C#
sample.pdf

これはファイル名の文字列を変更しているだけです。

実際にファイル形式を変換したい場合は、形式に対応したライブラリや変換処理が必要です。画像形式の変換、PDF生成、CSVからExcelへの変換などは、拡張子の変更だけでは実現できません。

10-5. Webアプリで拡張子チェックをする際の注意点

Webアプリで拡張子チェックを行う場合は、拡張子だけで安全と判断しないことが重要です。

最低限、次のような点を確認しましょう。

C#
許可する拡張子だけを受け付ける
大文字・小文字を区別せずに判定する
ファイルサイズの上限を設定する
MIMEタイプを確認する
ファイル内容を検証する
保存ファイル名をシステム側で生成する
アップロード先を適切に制限する

拡張子チェックの例です。

C#
static bool IsAllowedExtension(string fileName)
{
var allowedExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".jpg",
".jpeg",
".png",
".pdf"
};

string extension = Path.GetExtension(fileName);

return allowedExtensions.Contains(extension);
}

保存ファイル名を安全に生成する例です。

C#
string originalFileName = "sample.jpg";
string extension = Path.GetExtension(originalFileName);

string saveFileName = $"{Guid.NewGuid()}{extension}";

ユーザーがアップロードしたファイル名をそのまま保存すると、パストラバーサルやファイル上書きなどのリスクにつながる場合があります。拡張子チェックとあわせて、保存方法も慎重に設計しましょう。

まとめ

C#で拡張子を取得する場合は、Path.GetExtensionを使うのが基本です。

C#
string extension = Path.GetExtension("sample.txt");
// .txt

Path.GetExtensionの戻り値にはドットが含まれるため、比較するときは.txt.csvのようにドット付きで比較します。

C#
if (string.Equals(extension, ".csv", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("CSVファイルです");
}

拡張子を変更したい場合は、Path.ChangeExtensionを使います。

C#
string newFileName = Path.ChangeExtension("sample.txt", ".csv");
// sample.csv

拡張子なしのファイル名を取得したい場合は、Path.GetFileNameWithoutExtensionを使います。

C#
string name = Path.GetFileNameWithoutExtension("sample.txt");
// sample

また、パスを組み立てるときは、文字列結合ではなくPath.Combineを使うのがおすすめです。

C#
string path = Path.Combine(directory, fileName);

C#で拡張子を扱うときは、次のポイントを意識しましょう。

C#
Path.GetExtensionで拡張子を取得する
戻り値にはドットが含まれる
比較にはStringComparison.OrdinalIgnoreCaseを使う
複数拡張子の判定にはHashSetを使う
拡張子変更にはPath.ChangeExtensionを使う
実ファイル名の変更にはFile.Moveを使う
拡張子だけでファイルの安全性を判断しない

拡張子の処理は一見シンプルですが、null、空文字、拡張子なし、複数ドット、.tar.gzのような複合拡張子、URL、アップロードファイルなど、実務では注意すべきケースが多くあります。

基本的にはPathクラスを使い、比較方法やセキュリティ面にも配慮しながら実装することで、読みやすく安全なコードになります。