從完整檔案路徑解析目錄、檔名、副檔名是檔案處理基礎操作。本文比較 Path 類別、FileInfo、Uri、字串操作與正則表達式等多種方法優劣,提供跨平台相容的最佳實踐。
using System.IO;
string fullPath = @"C:\Projects\MyApp\bin\Debug\net6.0\appsettings.json";
string directory = Path.GetDirectoryName(fullPath);
// C:\Projects\MyApp\bin\Debug\net6.0
string fileName = Path.GetFileName(fullPath);
// appsettings.json
string fileNameWithoutExt = Path.GetFileNameWithoutExtension(fullPath);
// appsettings
string extension = Path.GetExtension(fullPath);
// .json
string root = Path.GetPathRoot(fullPath);
// C:\
// 組合路徑
string newPath = Path.Combine(directory, "config", "settings.json");
// C:\Projects\MyApp\bin\Debug\net6.0\config\settings.json
var fileInfo = new FileInfo(fullPath);
string directory = fileInfo.DirectoryName;
string fileName = fileInfo.Name;
string nameOnly = Path.GetFileNameWithoutExtension(fileInfo.Name); // 或自行實作
string extension = fileInfo.Extension;
long length = fileInfo.Length;
DateTime created = fileInfo.CreationTime;
bool exists = fileInfo.Exists;
bool isReadOnly = fileInfo.IsReadOnly;
var uri = new Uri(fullPath); // file:///C:/Projects/... 或 https://...
string localPath = uri.LocalPath; // 解碼後的本機路徑
string fileName = Path.GetFileName(uri.LocalPath);
string directory = Path.GetDirectoryName(uri.LocalPath);
// 處理 URL 編碼路徑
string urlPath = "https://example.com/files/my%20document.pdf";
var urlUri = new Uri(urlPath);
string decodedName = Path.GetFileName(urlUri.LocalPath); // "my document.pdf"
// 原始字串分割 (脆弱、跨平台失效)
string path = @"C:\Folder\file.txt";
int lastSlash = path.LastIndexOf('\\');
int lastDot = path.LastIndexOf('.');
string dir = path.Substring(0, lastSlash); // C:\Folder
string name = path.Substring(lastSlash + 1, lastDot - lastSlash - 1); // file
string ext = path.Substring(lastDot); // .txt
// 問題:Unix 路徑用 '/'、相對路徑、無副檔名、根目錄等邊界情況皆會失效
using System.Text.RegularExpressions;
var regex = new Regex(@"^(?.*[\\/])?(?[^\\/]+)(\.(?[^\\/.]+))?$");
var match = regex.Match(fullPath);
if (match.Success)
{
string dir = match.Groups["dir"].Value.TrimEnd('\\', '/');
string name = match.Groups["name"].Value;
string ext = match.Groups["ext"].Success ? "." + match.Groups["ext"].Value : "";
}
public static class PathHelper
{
///
/// 取得目錄路徑 (跨平台安全)
///
public static string GetDirectory(string path)
{
if (string.IsNullOrEmpty(path)) return "";
return Path.GetDirectoryName(path.Replace('/', Path.DirectorySeparatorChar));
}
///
/// 取得檔名 (含副檔名)
///
public static string GetFileName(string path)
{
if (string.IsNullOrEmpty(path)) return "";
return Path.GetFileName(path.Replace('/', Path.DirectorySeparatorChar));
}
///
/// 取得不含副檔名的檔名
///
public static string GetFileNameWithoutExtension(string path)
{
return Path.GetFileNameWithoutExtension(GetFileName(path));
}
///
/// 取得副檔名 (含點號)
///
public static string GetExtension(string path)
{
return Path.GetExtension(GetFileName(path));
}
///
/// 正規化路徑分隔符
///
public static string Normalize(string path)
{
return path?.Replace('/', Path.DirectorySeparatorChar)
.Replace('\\', Path.DirectorySeparatorChar);
}
///
/// 確保目錄存在
///
public static void EnsureDirectory(string filePath)
{
string dir = GetDirectory(filePath);
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
Directory.CreateDirectory(dir);
}
}
| 輸入路徑 | GetDirectoryName | GetFileName | GetExtension | GetFileNameWithoutExtension |
|---|---|---|---|---|
| C:\Folder\file.txt | C:\Folder | file.txt | .txt | file |
| C:\Folder\ | C:\ | Folder | Folder | |
| C:\Folder | C:\ | Folder | Folder | |
| file.txt | file.txt | .txt | file | |
| .hidden | .hidden | .hidden | ||
| archive.tar.gz | archive.tar.gz | .gz | archive.tar | |
| /home/user/file | /home/user | file | file | |
| \\server\share\file.txt | \\server\share | file.txt | .txt | file |
public static IEnumerable GetPathSegments(string path)
{
string normalized = Normalize(path);
string root = Path.GetPathRoot(normalized);
string relative = normalized.Substring(root.Length);
return relative.Split(new[] { Path.DirectorySeparatorChar },
StringSplitOptions.RemoveEmptyEntries);
}
// 使用
foreach (string segment in GetPathSegments(@"C:\A\B\C\file.txt"))
{
Console.WriteLine(segment); // A, B, C, file.txt
}
using System.Diagnostics;
public void Benchmark()
{
string path = @"C:\Very\Long\Path\With\Many\Segments\And\A\File\Name\With\Extension.txt";
int iterations = 1_000_000;
var sw = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
Path.GetFileName(path);
Console.WriteLine($"Path.GetFileName: {sw.ElapsedMilliseconds} ms");
sw.Restart();
for (int i = 0; i < iterations; i++)
new FileInfo(path).Name;
Console.WriteLine($"FileInfo.Name: {sw.ElapsedMilliseconds} ms");
sw.Restart();
for (int i = 0; i < iterations; i++)
{
int idx = path.LastIndexOf('\\');
string _ = path.Substring(idx + 1);
}
Console.WriteLine($"String.LastIndexOf: {sw.ElapsedMilliseconds} ms");
}
/*
典型結果 (100萬次):
Path.GetFileName: 45 ms
FileInfo.Name: 120 ms (建立物件開銷)
String.LastIndexOf: 18 ms (最快但不安全)
*/
| 問題 | 原因 | 解決方案 |
|---|---|---|
| Unix 路徑失效 | 硬編碼 '\\' | 使用 Path.DirectorySeparatorChar 或 Path.GetDirectoryName |
| 副檔名判斷錯誤 | 多重副檔名 .tar.gz | Path.GetExtension 只取最後一段,需自行處理 |
| 相對路徑解析錯誤 | 無根目錄 | Path.GetFullPath 轉絕對路徑後再解析 |
| URL 路徑含編碼 | %20 等編碼 | 使用 Uri.LocalPath 自動解碼 |
| 網路路徑 UNC | 雙反斜線開頭 | Path 類別原生支援 UNC 路徑 |
解析檔案路徑首選 System.IO.Path 靜態方法:跨平台、效能佳、處理邊界情況完善。FileInfo 適合需同時取得檔案屬性 (大小、時間、權限) 時使用。避免手動字串分割,除非極簡單且確定路徑格式。跨平台專案務必使用 Path.DirectorySeparatorChar 而非硬編碼分隔符。處理 URL/網路路徑時善用 Uri 類別自動解碼與正規化。