CodeGym /행동 /C# SELF /접근 방식 비교 File vs <...

접근 방식 비교 File vs FileInfo

C# SELF
레벨 39 , 레슨 3
사용 가능

1. 도입

.NET에는 파일과 폴더를 다루기 위한 철학적으로 다른 두 그룹의 클래스가 있습니다:

정적 클래스: File, Directory — 유틸리티 함수 집합처럼 동작합니다: 메서드를 호출하고 경로를 전달하면 결과를 얻습니다.

인스턴스 클래스: FileInfo, DirectoryInfo — 특정 파일과 폴더를 자신의 프로퍼티와 메서드를 가진 객체로 표현합니다.

왜 둘 다 있냐고요? 이건 사용의 단순성(정적 메서드)과 객체지향적 유연성(인스턴스 클래스) 사이의 절충안입니다. 각 방식은 특정 시나리오에서 편리합니다.

2. 정적 클래스 철학

FileDirectory 클래스는 "그냥 해" 원칙을 따릅니다. 중간 객체를 만들 필요 없이 파일 시스템 작업을 수행하는 직관적인 인터페이스를 제공합니다.

// 파일 존재 확인
if (File.Exists("document.txt"))
{
    // 전체 텍스트 읽기
    string content = File.ReadAllText("document.txt");
    
    // 백업 생성
    File.Copy("document.txt", "document_backup.txt");
    
    // 원본 삭제
    File.Delete("document.txt");
}

단발성 간단한 작업에 편리합니다: 객체나 수명주기, 상태를 고민할 필요 없이 경로만 넘겨서 메서드를 호출하면 됩니다.

Directory도 마찬가지입니다:

// 폴더 생성
Directory.CreateDirectory(@"C:\MyProject\Data");

// 모든 텍스트 파일 얻기
string[] textFiles = Directory.GetFiles(@"C:\MyProject", "*.txt");

// 폴더 존재 확인
if (Directory.Exists(@"C:\Temp"))
{
    Directory.Delete(@"C:\Temp", recursive: true);
}

3. 실전 예제 비교

예제 1: 간단한 파일 복사

정적 접근:

string sourcePath = "original.txt";
string destinationPath = "copy.txt";

if (File.Exists(sourcePath))
{
    File.Copy(sourcePath, destinationPath, overwrite: true);
    Console.WriteLine("파일이 복사되었습니다");
}
else
{
    Console.WriteLine("원본 파일을 찾을 수 없습니다");
}

인스턴스 접근:

var sourceFile = new FileInfo("original.txt");
var destinationFile = new FileInfo("copy.txt");

if (sourceFile.Exists)
{
    sourceFile.CopyTo(destinationFile.FullName, overwrite: true);
    Console.WriteLine("파일이 복사되었습니다");
}
else
{
    Console.WriteLine("원본 파일을 찾을 수 없습니다");
}

여기서는 정적 접근이 더 간결합니다. 하지만 추가 정보가 필요하면?

예제 2: 크기 체크를 동반한 복사

정적 접근:

string sourcePath = "largefile.zip";
string destinationPath = "backup.zip";

if (File.Exists(sourcePath))
{
    var fileInfo = new FileInfo(sourcePath); // 결국 객체를 만들어야 함!
    if (fileInfo.Length > 100 * 1024 * 1024) // 100MB 초과
    {
        Console.WriteLine($"주의: 큰 파일을 복사합니다 ({fileInfo.Length / 1024 / 1024} MB)");
    }
    
    File.Copy(sourcePath, destinationPath, overwrite: true);
}

인스턴스 접근:

var sourceFile = new FileInfo("largefile.zip");

if (sourceFile.Exists)
{
    if (sourceFile.Length > 100 * 1024 * 1024) // 100MB 초과
    {
        Console.WriteLine($"주의: 큰 파일을 복사합니다 ({sourceFile.Length / 1024 / 1024} MB)");
    }
    
    sourceFile.CopyTo("backup.zip", overwrite: true);
}

여기선 객체 지향 접근이 자연스럽습니다: 파일을 객체로 다루며 그 프로퍼티를 사용합니다.

예제 3: 폴더 내용 분석

정적 접근:

string folderPath = @"C:\Documents";

if (Directory.Exists(folderPath))
{
    string[] files = Directory.GetFiles(folderPath);
    string[] subdirs = Directory.GetDirectories(folderPath);
    
    Console.WriteLine($"파일 수: {files.Length}, 폴더 수: {subdirs.Length}");
    
    // 크기 정보를 얻으려면 어쨌든 FileInfo 객체가 필요함
    long totalSize = 0;
    foreach (string filePath in files)
    {
        var fileInfo = new FileInfo(filePath);
        totalSize += fileInfo.Length;
    }
    
    Console.WriteLine($"총 크기: {totalSize / 1024} KB");
}

인스턴스 접근:

var folder = new DirectoryInfo(@"C:\Documents");

if (folder.Exists)
{
    var files = folder.GetFiles();
    var subdirs = folder.GetDirectories();
    
    Console.WriteLine($"파일 수: {files.Length}, 폴더 수: {subdirs.Length}");
    
    long totalSize = files.Sum(f => f.Length); // 정보가 이미 있음!
    Console.WriteLine($"총 크기: {totalSize / 1024} KB");
}

여기서는 DirectoryInfo가 유리합니다: FileInfo[] 컬렉션에 메타데이터가 이미 포함되어 있습니다.

4. 접근 방식 선택 기준

다음 경우 정적 클래스(File/Directory)를 사용하세요:

  • 작고 일회성인 작업. 파일 존재 확인, 삭제, 내용 읽기 등 빠른 작업에 정적 메서드가 이상적입니다.
// 간단한 설정 읽기
if (File.Exists("config.json"))
{
    string config = File.ReadAllText("config.json");
    // 설정 처리...
}
  • 파일 프로퍼티 정보가 필요하지 않을 때. 크기, 날짜, 속성이 중요하지 않으면 정적 호출이 더 짧고 적합합니다.
  • 경로를 다루고 파일을 객체로 다루지 않을 때. 로직이 문자열 경로를 중심으로 돌아가면 정적 메서드가 자연스럽습니다.

다음 경우 인스턴스 클래스(FileInfo/DirectoryInfo)를 사용하세요:

  • 하나의 파일/폴더에 대해 많은 정보가 필요할 때. Length, CreationTime, Attributes 같은 프로퍼티를 직접 사용할 수 있습니다.
var logFile = new FileInfo("application.log");
Console.WriteLine($"로그 크기: {logFile.Length / 1024} KB");
Console.WriteLine($"마지막 수정: {logFile.LastWriteTime}");
Console.WriteLine($"위치: {logFile.Directory.FullName}");
  • 하나의 파일에 대해 여러 작업을 수행할 때. 객체를 한 번 만들어 여러 작업에 재사용합니다.
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($"문서가 보관되었고, 복사본 {backup.Name}이 생성되었습니다");
}
  • 파일 컬렉션을 다룰 때. GetFiles()GetDirectories()는 완전한 정보를 가진 객체를 반환합니다.
  • 객체지향 아키텍처가 필요할 때. 파일/폴더를 객체로 다루면 LINQ나 전달, 저장이 더 쉬워집니다.

5. 성능 및 캐싱 특성

인스턴스 클래스의 캐싱

FileInfo/DirectoryInfo의 핵심 특성 중 하나는 메타데이터 캐싱입니다. 처음 프로퍼티(예: Length 또는 CreationTime)에 접근할 때 .NET은 시스템 호출을 해서 모든 정보를 불러오고 그 객체 내부에 캐시합니다.

var file = new FileInfo("document.txt");

// 첫 접근 - 메타데이터를 불러오기 위한 시스템 호출
long size = file.Length;

// 이후 접근은 캐시 사용 - 매우 빠름
DateTime created = file.CreationTime;
DateTime modified = file.LastWriteTime;
bool readOnly = file.IsReadOnly;

하나의 파일에서 여러 프로퍼티가 필요하면 객체 접근이 효율적입니다: 여러 번의 시스템 호출 대신 한 번으로 해결됩니다.

캐시의 오래됨 문제

단점은 파일이 외부에서 변경되면 정보가 오래된 상태가 될 수 있다는 점입니다. 이때는 Refresh()를 사용하세요:

var file = new FileInfo("data.txt");
Console.WriteLine($"크기: {file.Length}"); // 예: 1000 바이트

// 이 사이에 다른 프로그램이 파일을 변경...

Console.WriteLine($"크기: {file.Length}"); // 여전히 캐시된 1000 바이트!

// 강제 갱신
file.Refresh();
Console.WriteLine($"크기: {file.Length}"); // 이제 최신 크기

정적 호출은 항상 파일 시스템에 직접 접근하므로 이런 문제는 없습니다.

대량 작업

많은 파일에 대해 작업할 때 API 선택이 성능에 영향을 줍니다:

// 정적 접근 — 많은 시스템 호출
string[] files = Directory.GetFiles(@"C:\Photos");
foreach (string filePath in files)
{
    var info = new FileInfo(filePath); // 파일별로 시스템 호출
    if (info.Length > 10 * 1024 * 1024) // 10MB 초과
    {
        Console.WriteLine($"큰 사진: {info.Name}");
    }
}

// 인스턴스 접근 — 폴더당 한 번의 시스템 호출
var photosDir = new DirectoryInfo(@"C:\Photos");
foreach (var file in photosDir.GetFiles()) // 정보가 한 번에 로드됨
{
    if (file.Length > 10 * 1024 * 1024)
    {
        Console.WriteLine($"큰 사진: {file.Name}");
    }
}

6. API와 기능 차이

인스턴스 클래스의 고유 기능

var file = new FileInfo(@"C:\Projects\MyApp\source\Program.cs");

// 폴더 계층 탐색
DirectoryInfo projectDir = file.Directory.Parent; // MyApp
DirectoryInfo sourceDir = file.Directory; // source

// 파일 상세 정보
Console.WriteLine($"확장자: {file.Extension}");
Console.WriteLine($"읽기 전용: {file.IsReadOnly}");
Console.WriteLine($"속성: {file.Attributes}");

// 디렉토리를 객체로 다루기
var dir = new DirectoryInfo(@"C:\Projects");
DirectoryInfo parent = dir.Parent; // C:\
DirectoryInfo root = dir.Root;     // C:\

정적 클래스의 고유 기능

// 한 줄로 텍스트 읽기/쓰기
string content = File.ReadAllText("config.txt");
File.WriteAllText("output.txt", "Hello World");

// 문자열 작업
string[] lines = File.ReadAllLines("data.txt");
File.WriteAllLines("output.txt", new[] { "Line 1", "Line 2" });

// 파일에 텍스트 추가
File.AppendAllText("log.txt", $"{DateTime.Now}: Application started\n");

// 바이트 작업
byte[] data = File.ReadAllBytes("image.jpg");
File.WriteAllBytes("copy.jpg", data);

7. 실용적인 권장사항

시나리오 1: 백업 유틸리티

크기와 날짜를 확인하며 복사할 때는 인스턴스 클래스가 더 편합니다:

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));
        
        // 파일이 더 최신이거나 존재하지 않을 때만 복사
        if (!backupFile.Exists || file.LastWriteTime > backupFile.LastWriteTime)
        {
            file.CopyTo(backupFile.FullName, overwrite: true);
            Console.WriteLine($"복사됨: {file.Name} ({file.Length / 1024} KB)");
        }
    }
}

시나리오 2: 텍스트 파일 간단 처리

단순한 데이터 로드/저장은 정적 메서드로 충분합니다:

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))
    {
        // 기본 설정 생성
        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;
}

시나리오 3: 파일 시스템 분석

디스크 사용량 리포트를 만들 때는 인스턴스 클래스가 최적입니다:

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 = "(확장자 없음)";
            
        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. 흔한 실수와 함정

실수: 불필요한 방식 혼합

가끔 정적 메서드로 시작한 뒤 메타데이터 때문에 객체를 만들면 불필요한 중복 호출이 생깁니다:

// 비효율적 - 파일 시스템에 두 번 접근
if (File.Exists("document.txt"))
{
    var fileInfo = new FileInfo("document.txt"); // 존재 여부 확인 중복
    Console.WriteLine($"크기: {fileInfo.Length}");
}

// 객체 접근으로 바로 하는 것이 낫다
var fileInfo = new FileInfo("document.txt");
if (fileInfo.Exists)
{
    Console.WriteLine($"크기: {fileInfo.Length}");
}

실수: 캐시 갱신 무시

외부에서 변경될 수 있는 파일을 장시간 다룰 때는 Refresh()로 캐시를 갱신하세요:

var logFile = new FileInfo("application.log");

while (true)
{
    logFile.Refresh(); // 파일 정보 갱신
    
    if (logFile.Length > 100 * 1024 * 1024) // 100MB
    {
        // 로그 보관
        logFile.MoveTo($"logs/archived_{DateTime.Now:yyyyMMdd_HHmmss}.log");
        break;
    }
    
    Thread.Sleep(60000); // 1분마다 확인
}

실수: 대량 작업에 대한 부적절한 선택

수천 개 파일을 처리할 때는 API 선택이 성능을 좌우합니다:

// 느림 - 파일 시스템에 작은 호출이 많음
string[] allFiles = Directory.GetFiles(@"C:\BigFolder", "*", SearchOption.AllDirectories);
var largeFiles = allFiles.Where(path => new FileInfo(path).Length > 1024 * 1024).ToList();

// 빠름 - 이미 준비된 정보를 사용
var folder = new DirectoryInfo(@"C:\BigFolder");
var largeFiles = folder.GetFiles("*", SearchOption.AllDirectories)
                      .Where(file => file.Length > 1024 * 1024)
                      .ToList();
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION