1. Giới thiệu
Trong .NET để làm việc với tập tin và thư mục có hai nhóm lớp khác nhau về triết lý:
Lớp tĩnh: File, Directory — hoạt động như một tập các hàm tiện ích: gọi phương thức, truyền đường dẫn, nhận kết quả.
Lớp thể hiện (instance): FileInfo, DirectoryInfo — đại diện cho tập tin và thư mục cụ thể như các object với thuộc tính và phương thức riêng.
Tại sao có cả hai? Đó là sự đánh đổi giữa độ đơn giản khi dùng (phương thức tĩnh) và sự linh hoạt hướng đối tượng (lớp thể hiện). Mỗi cách phù hợp trong từng tình huống.
2. Triết lý của các lớp tĩnh
Các lớp File và Directory theo nguyên tắc "Chỉ làm đi!". Chúng cung cấp giao diện thẳng thắn để thực hiện thao tác với hệ thống tập tin mà không cần tạo object trung gian.
// Kiểm tra sự tồn tại của tập tin
if (File.Exists("document.txt"))
{
// Đọc toàn bộ văn bản
string content = File.ReadAllText("document.txt");
// Tạo bản sao lưu
File.Copy("document.txt", "document_backup.txt");
// Xóa bản gốc
File.Delete("document.txt");
}
Tiện khi làm các thao tác đơn giản, thực hiện một lần: không cần nghĩ về object, vòng đời hay trạng thái của chúng — chỉ gọi phương thức với đường dẫn tập tin.
Tương tự với Directory:
// Tạo thư mục
Directory.CreateDirectory(@"C:\MyProject\Data");
// Lấy tất cả tập tin text
string[] textFiles = Directory.GetFiles(@"C:\MyProject", "*.txt");
// Kiểm tra tồn tại thư mục
if (Directory.Exists(@"C:\Temp"))
{
Directory.Delete(@"C:\Temp", recursive: true);
}
3. So sánh cách tiếp cận qua ví dụ thực tế
Ví dụ 1: Sao chép tập tin đơn giản
Cách tĩnh:
string sourcePath = "original.txt";
string destinationPath = "copy.txt";
if (File.Exists(sourcePath))
{
File.Copy(sourcePath, destinationPath, overwrite: true);
Console.WriteLine("Tập tin đã được sao chép");
}
else
{
Console.WriteLine("Không tìm thấy tập tin nguồn");
}
Cách đối tượng:
var sourceFile = new FileInfo("original.txt");
var destinationFile = new FileInfo("copy.txt");
if (sourceFile.Exists)
{
sourceFile.CopyTo(destinationFile.FullName, overwrite: true);
Console.WriteLine("Tập tin đã được sao chép");
}
else
{
Console.WriteLine("Không tìm thấy tập tin nguồn");
}
Ở đây cách tĩnh ngắn gọn hơn. Nhưng nếu cần thông tin bổ sung thì sao?
Ví dụ 2: Sao chép với kiểm tra kích thước
Cách tĩnh:
string sourcePath = "largefile.zip";
string destinationPath = "backup.zip";
if (File.Exists(sourcePath))
{
var fileInfo = new FileInfo(sourcePath); // Dù sao cũng phải tạo object!
if (fileInfo.Length > 100 * 1024 * 1024) // Lớn hơn 100 MB
{
Console.WriteLine($"Chú ý: đang sao chép tập tin lớn ({fileInfo.Length / 1024 / 1024} MB)");
}
File.Copy(sourcePath, destinationPath, overwrite: true);
}
Cách đối tượng:
var sourceFile = new FileInfo("largefile.zip");
if (sourceFile.Exists)
{
if (sourceFile.Length > 100 * 1024 * 1024) // Lớn hơn 100 MB
{
Console.WriteLine($"Chú ý: đang sao chép tập tin lớn ({sourceFile.Length / 1024 / 1024} MB)");
}
sourceFile.CopyTo("backup.zip", overwrite: true);
}
Ở đây cách hướng đối tượng tự nhiên hơn: làm việc với tập tin như object và dùng thuộc tính của nó.
Ví dụ 3: Phân tích nội dung thư mục
Cách tĩnh:
string folderPath = @"C:\Documents";
if (Directory.Exists(folderPath))
{
string[] files = Directory.GetFiles(folderPath);
string[] subdirs = Directory.GetDirectories(folderPath);
Console.WriteLine($"Số tập tin: {files.Length}, Số thư mục: {subdirs.Length}");
// Để lấy kích thước vẫn cần FileInfo objects
long totalSize = 0;
foreach (string filePath in files)
{
var fileInfo = new FileInfo(filePath);
totalSize += fileInfo.Length;
}
Console.WriteLine($"Tổng kích thước: {totalSize / 1024} KB");
}
Cách đối tượng:
var folder = new DirectoryInfo(@"C:\Documents");
if (folder.Exists)
{
var files = folder.GetFiles();
var subdirs = folder.GetDirectories();
Console.WriteLine($"Số tập tin: {files.Length}, Số thư mục: {subdirs.Length}");
long totalSize = files.Sum(f => f.Length); // Thông tin đã sẵn sàng!
Console.WriteLine($"Tổng kích thước: {totalSize / 1024} KB");
}
Ở đây DirectoryInfo thắng thế: mảng FileInfo[] đã chứa metadata.
4. Tiêu chí chọn cách tiếp cận
Dùng các lớp tĩnh (File/Directory) khi:
- Thao tác đơn giản và thực hiện một lần. Cần nhanh kiểm tra tồn tại tập tin, xóa nó hoặc đọc nội dung — phương thức tĩnh là lý tưởng.
// Đọc cấu hình đơn giản
if (File.Exists("config.json"))
{
string config = File.ReadAllText("config.json");
// xử lý cấu hình...
}
- Không cần thông tin về thuộc tính tập tin. Nếu không quan tâm kích thước, ngày tháng hay attributes — gọi tĩnh ngắn gọn hơn và phù hợp hơn.
- Làm việc với đường dẫn chứ không phải tập tin như object. Khi logic xử lý chuỗi đường dẫn, phương thức tĩnh hợp lý hơn.
Dùng lớp thể hiện (FileInfo/DirectoryInfo) khi:
- Cần nhiều thông tin về một tập tin/thư mục. Thuộc tính như Length, CreationTime, Attributes truy cập trực tiếp.
var logFile = new FileInfo("application.log");
Console.WriteLine($"Kích thước log: {logFile.Length / 1024} KB");
Console.WriteLine($"Lần sửa cuối: {logFile.LastWriteTime}");
Console.WriteLine($"Vị trí: {logFile.Directory.FullName}");
- Thực hiện nhiều thao tác trên cùng một tập tin. Tạo object một lần — dùng cho nhiều hành động khác nhau.
var document = new FileInfo("report.docx");
if (document.Exists)
{
var backup = document.CopyTo($"report_backup_{DateTime.Now:yyyyMMdd}.docx");
document.MoveTo("archive/report.docx");
Console.WriteLine($"Tài liệu đã được lưu trữ, tạo bản sao {backup.Name}");
}
- Làm việc với bộ sưu tập tập tin. GetFiles() và GetDirectories() trả về object có đầy đủ thông tin.
- Cần kiến trúc hướng đối tượng. Tập tin/thư mục như object dễ truyền, lưu trữ và sử dụng trong LINQ.
5. Hiệu năng và caching
Caching trong lớp thể hiện
Một trong những đặc điểm chính của FileInfo/DirectoryInfo là caching metadata. Khi truy cập thuộc tính lần đầu (ví dụ Length hoặc CreationTime) .NET thực hiện gọi hệ thống, tải toàn bộ thông tin và cache nó trong object.
var file = new FileInfo("document.txt");
// Lần truy cập đầu - gọi hệ thống để nạp metadata
long size = file.Length;
// Các truy cập sau dùng cache - rất nhanh
DateTime created = file.CreationTime;
DateTime modified = file.LastWriteTime;
bool readOnly = file.IsReadOnly;
Nếu cần nhiều thuộc tính của cùng một tập tin, cách đối tượng hiệu quả hơn: một lần gọi hệ thống thay vì nhiều lần.
Vấn đề cache lỗi thời
Mặt trái — thông tin có thể lỗi thời nếu tập tin bị thay đổi ngoài chương trình của bạn. Dùng Refresh() để cập nhật:
var file = new FileInfo("data.txt");
Console.WriteLine($"Kích thước: {file.Length}"); // Ví dụ 1000 byte
// Trong lúc này chương trình khác thay đổi tập tin...
Console.WriteLine($"Kích thước: {file.Length}"); // Vẫn 1000 byte từ cache!
// Ép cập nhật
file.Refresh();
Console.WriteLine($"Kích thước: {file.Length}"); // Bây giờ là giá trị mới
Các gọi tĩnh luôn truy vấn trực tiếp hệ thống tập tin, nên không có vấn đề cache này.
Thao tác hàng loạt
Với tập hợp lớn tập tin, lựa chọn API ảnh hưởng tới hiệu năng:
// Cách tĩnh — nhiều lần gọi hệ thống
string[] files = Directory.GetFiles(@"C:\Photos");
foreach (string filePath in files)
{
var info = new FileInfo(filePath); // Gọi hệ thống cho mỗi tập tin
if (info.Length > 10 * 1024 * 1024) // Lớn hơn 10 MB
{
Console.WriteLine($"Ảnh lớn: {info.Name}");
}
}
// Cách đối tượng — một lần gọi hệ thống trên thư mục
var photosDir = new DirectoryInfo(@"C:\Photos");
foreach (var file in photosDir.GetFiles()) // Thông tin được nạp ngay
{
if (file.Length > 10 * 1024 * 1024)
{
Console.WriteLine($"Ảnh lớn: {file.Name}");
}
}
6. Khác biệt về API và khả năng
Khả năng độc đáo của lớp thể hiện
var file = new FileInfo(@"C:\Projects\MyApp\source\Program.cs");
// Duyệt theo cấu trúc thư mục
DirectoryInfo projectDir = file.Directory.Parent; // MyApp
DirectoryInfo sourceDir = file.Directory; // source
// Thông tin chi tiết về tập tin
Console.WriteLine($"Phần mở rộng: {file.Extension}");
Console.WriteLine($"Chỉ đọc: {file.IsReadOnly}");
Console.WriteLine($"Attributes: {file.Attributes}");
// Làm việc với thư mục như object
var dir = new DirectoryInfo(@"C:\Projects");
DirectoryInfo parent = dir.Parent; // C:\
DirectoryInfo root = dir.Root; // C:\
Khả năng độc đáo của lớp tĩnh
// Đọc và ghi text trong một dòng
string content = File.ReadAllText("config.txt");
File.WriteAllText("output.txt", "Hello World");
// Làm việc với mảng dòng
string[] lines = File.ReadAllLines("data.txt");
File.WriteAllLines("output.txt", new[] { "Line 1", "Line 2" });
// Thêm text vào tập tin
File.AppendAllText("log.txt", $"{DateTime.Now}: Application started\n");
// Làm việc với byte
byte[] data = File.ReadAllBytes("image.jpg");
File.WriteAllBytes("copy.jpg", data);
7. Khuyến nghị thực tế
Kịch bản 1: Công cụ sao lưu
Để sao chép với kiểm tra kích thước và ngày thì lớp thể hiện tiện hơn:
public void BackupDirectory(string sourcePath, string backupPath)
{
var sourceDir = new DirectoryInfo(sourcePath);
var backupDir = new DirectoryInfo(backupPath);
if (!backupDir.Exists)
backupDir.Create();
foreach (var file in sourceDir.GetFiles())
{
var backupFile = new FileInfo(Path.Combine(backupPath, file.Name));
// Chỉ sao chép nếu tập tin mới hơn hoặc chưa tồn tại
if (!backupFile.Exists || file.LastWriteTime > backupFile.LastWriteTime)
{
file.CopyTo(backupFile.FullName, overwrite: true);
Console.WriteLine($"Đã sao chép: {file.Name} ({file.Length / 1024} KB)");
}
}
}
Kịch bản 2: Làm việc đơn giản với tập tin văn bản
Để tải/lưu dữ liệu đơn giản dùng phương thức tĩnh là đủ:
public void SaveUserPreferences(string username, string theme, bool notifications)
{
string configPath = "user.config";
string[] settings = {
$"Username={username}",
$"Theme={theme}",
$"Notifications={notifications}"
};
File.WriteAllLines(configPath, settings);
}
public Dictionary<string, string> LoadUserPreferences()
{
string configPath = "user.config";
var preferences = new Dictionary<string, string>();
if (!File.Exists(configPath))
{
// Tạo cấu hình mặc định
SaveUserPreferences("User", "Light", true);
return LoadUserPreferences();
}
string[] lines = File.ReadAllLines(configPath);
foreach (string line in lines)
{
if (line.Contains('='))
{
string[] parts = line.Split('=', 2);
preferences[parts[0]] = parts[1];
}
}
return preferences;
}
Kịch bản 3: Phân tích hệ thống tập tin
Để báo cáo sử dụng đĩa thì lớp thể hiện là lý tưởng:
public void AnalyzeDiskUsage(string path)
{
var directory = new DirectoryInfo(path);
var report = new Dictionary<string, long>();
foreach (var file in directory.GetFiles("*", SearchOption.AllDirectories))
{
string extension = file.Extension.ToLower();
if (string.IsNullOrEmpty(extension))
extension = "(không có phần mở rộng)";
if (!report.ContainsKey(extension))
report[extension] = 0;
report[extension] += file.Length;
}
var sortedReport = report.OrderByDescending(kvp => kvp.Value);
foreach (var item in sortedReport.Take(10))
{
Console.WriteLine($"{item.Key}: {item.Value / 1024 / 1024} MB");
}
}
8. Lỗi thường gặp và bẫy
Lỗi: Trộn cách tiếp cận không cần thiết
Đôi khi bắt đầu với phương thức tĩnh, rồi lại tạo object để lấy metadata — dẫn đến gọi thừa:
// Không hiệu quả - hai lần gọi hệ thống
if (File.Exists("document.txt"))
{
var fileInfo = new FileInfo("document.txt"); // Lặp lại việc kiểm tra tồn tại
Console.WriteLine($"Kích thước: {fileInfo.Length}");
}
// Tốt hơn dùng luôn cách đối tượng
var fileInfo = new FileInfo("document.txt");
if (fileInfo.Exists)
{
Console.WriteLine($"Kích thước: {fileInfo.Length}");
}
Lỗi: Bỏ qua cache lỗi thời
Khi chạy lâu với tập tin có thể thay đổi bên ngoài, cập nhật cache bằng Refresh():
var logFile = new FileInfo("application.log");
while (true)
{
logFile.Refresh(); // Cập nhật thông tin tập tin
if (logFile.Length > 100 * 1024 * 1024) // 100 MB
{
// Lưu trữ log
logFile.MoveTo($"logs/archived_{DateTime.Now:yyyyMMdd_HHmmss}.log");
break;
}
Thread.Sleep(60000); // Kiểm tra mỗi phút
}
Lỗi: Chọn sai cho thao tác hàng loạt
Khi xử lý hàng nghìn tập tin, chọn API ảnh hưởng hiệu năng:
// Chậm - nhiều lần gọi hệ thống nhỏ lẻ
string[] allFiles = Directory.GetFiles(@"C:\BigFolder", "*", SearchOption.AllDirectories);
var largeFiles = allFiles.Where(path => new FileInfo(path).Length > 1024 * 1024).ToList();
// Nhanh hơn - dùng thông tin đã có sẵn
var folder = new DirectoryInfo(@"C:\BigFolder");
var largeFiles = folder.GetFiles("*", SearchOption.AllDirectories)
.Where(file => file.Length > 1024 * 1024)
.ToList();
GO TO FULL VERSION