Manipulação de arquivos no C#
A manipulação de arquivos em C# é uma tarefa comum em boa parte dos sistemas, e o .NET oferece a classe File para criar, copiar, substituir, ler e escrever arquivos de forma simples e direta. Neste artigo, você vai conhecer cada operação, os erros mais comuns e as boas práticas para trabalhar com o disco sem surpresas.
O que é a manipulação de arquivos em C#
O .NET é um conjunto de bibliotecas que auxilia na criação de aplicações. Como você deve imaginar, ler e gravar dados no disco faz parte da rotina de quase todo software.
Logs, relatórios, arquivos de configuração e exportações em CSV são exemplos clássicos. Por isso, a manipulação de arquivos em C# aparece cedo na carreira de quem trabalha com a plataforma.
Na prática, manipular um arquivo significa criar, abrir, escrever, ler, copiar, mover, substituir e excluir esse arquivo. Além disso, envolve consultar informações como datas e atributos.
O .NET concentra essas operações no namespace System.IO. Assim, você escreve o mesmo código e ele funciona em Windows, Linux e macOS, com raras exceções.
A classe File e o namespace System.IO
A classe System.IO.File é estática e oferece métodos para criar, copiar, excluir, mover e abrir um único arquivo. Ela também ajuda a criar objetos FileStream, que representam o fluxo de bytes do arquivo.
Segundo a documentação oficial da classe File, todos os métodos exigem o caminho do arquivo que você deseja manipular. Esse caminho pode ser absoluto, relativo ou até um caminho UNC de rede.
Quando você vai repetir várias operações sobre o mesmo arquivo, a classe FileInfo pode ser mais eficiente. Isso acontece porque ela guarda o caminho em uma instância e evita verificações repetidas.
Para facilitar os exemplos a seguir, vamos começar com uma constante que define o caminho do arquivo. Como o exemplo roda no Windows, o caminho fica assim:
const string filePath = @"C:\dev\myfile.txt";
O @ antes da string cria um literal verbatim. Dessa forma, você não precisa escapar as barras invertidas do caminho.
Em projetos multiplataforma, prefira montar o caminho com Path.Combine. Esse método usa o separador correto de cada sistema operacional.
// Monta o caminho com o separador correto de cada sistema
var filePath = Path.Combine(AppContext.BaseDirectory, "data", "myfile.txt");
Tratamento de erros na manipulação de arquivos em C#
A manipulação de arquivos em C# é uma operação sensível. O disco pode estar cheio, a pasta pode não existir, outro processo pode estar usando o arquivo ou o usuário pode não ter permissão.
Por isso, a recomendação é sempre envolver essas operações em um bloco try/catch, conforme o exemplo abaixo:
try
{
// Manipulação de arquivo
}
catch (Exception ex)
{
// Capturar exceção
Console.WriteLine(ex.Message);
}
Capturar Exception funciona, mas tratar as exceções específicas deixa o código mais claro. Assim, você reage de forma diferente para cada tipo de falha.
try
{
File.Copy(filePath, @"C:\dev\other.txt");
}
catch (FileNotFoundException)
{
// O arquivo de origem não existe
Console.WriteLine("Source file not found.");
}
catch (DirectoryNotFoundException)
{
// A pasta informada no caminho não existe
Console.WriteLine("Directory not found.");
}
catch (UnauthorizedAccessException)
{
// Falta de permissão ou arquivo somente leitura
Console.WriteLine("Access denied.");
}
catch (IOException ex)
{
// Erro genérico de entrada e saída, como arquivo em uso
Console.WriteLine(ex.Message);
}
Repare na ordem dos blocos catch. Como FileNotFoundException e DirectoryNotFoundException herdam de IOException, elas precisam vir antes da classe base.
Já a UnauthorizedAccessException não herda de IOException. Portanto, ela precisa de um bloco próprio.
Operações básicas de manipulação de arquivos em C#
Com o caminho definido e o tratamento de erros em mente, podemos conhecer os métodos mais usados na manipulação de arquivos em C#. Cada um resolve uma etapa do ciclo de vida de um arquivo.
Criar um arquivo
O primeiro método é o File.Create, que cria ou sobrescreve um arquivo no disco. Se o arquivo não existe, o método cria um novo; se já existe, ele sobrescreve o conteúdo.
É importante destacar que o caminho das pastas precisa existir. Caso contrário, você recebe uma DirectoryNotFoundException, porque o método não cria a pasta automaticamente.
Além disso, se não houver permissão de escrita no diretório informado, o .NET lança uma UnauthorizedAccessException.
// File.Create devolve um FileStream, então feche o recurso
using var stream = File.Create(filePath);
Um detalhe que muita gente esquece: File.Create devolve um FileStream aberto. Se você não fechar esse objeto, o arquivo continua bloqueado e a próxima operação sobre ele falha.
Quando a pasta pode não existir, crie a estrutura antes com Directory.CreateDirectory. Esse método não gera erro se a pasta já estiver lá.
// Garante que a pasta existe antes de criar o arquivo
Directory.CreateDirectory(Path.GetDirectoryName(filePath)!);
using var stream = File.Create(filePath);
Adicionar conteúdo a um arquivo
Para escrever em um arquivo, podemos usar o AppendText ou o CreateText. A diferença é que o AppendText adiciona texto ao fim do arquivo, enquanto o CreateText sobrescreve o conteúdo atual.
using (var appendWriter = File.AppendText(filePath))
{
appendWriter.WriteLine("Test"); // Adiciona o texto ao fim do arquivo
}
using (var createWriter = File.CreateText(filePath))
{
createWriter.WriteLine("Test 2"); // Substitui o conteúdo do arquivo com este texto
}
Os dois métodos devolvem um StreamWriter. Com o using, o .NET fecha o arquivo ao fim do bloco, então você não precisa chamar Close manualmente.
Ao trabalhar com arquivos grandes, você não precisa ler o arquivo inteiro para adicionar conteúdo. Nesse caso, o AppendText é a escolha certa.
// ERRADO: lê o arquivo inteiro só para acrescentar uma linha
var data = File.ReadAllText(filePath);
data += "My additional text";
using (var writer = File.CreateText(filePath))
{
writer.WriteLine(data); // Substitui o conteúdo do arquivo com este texto
}
// CORRETO: escreve apenas no fim do arquivo
using (var appendWriter = File.AppendText(filePath))
{
appendWriter.WriteLine("My additional text"); // Adiciona o texto ao fim do arquivo
}
Para operações rápidas, a classe também oferece File.WriteAllText e File.AppendAllText. Esses métodos abrem, escrevem e fecham o arquivo em uma única chamada.
File.WriteAllText(filePath, "First line\n"); // Cria ou sobrescreve
File.AppendAllText(filePath, "Second line\n"); // Acrescenta ao fim
De acordo com a documentação, o AppendAllText cria o arquivo se ele não existir. No entanto, ele também não cria pastas, então o diretório precisa existir.
Copiar um arquivo
Na manipulação de arquivos em C#, a cópia fica a cargo do File.Copy, que recebe o caminho do arquivo atual e o destino. O caminho de destino precisa apontar para um arquivo, nunca para uma pasta.
File.Copy(filePath, @"C:\dev\other.txt");
Se já houver outro arquivo com o mesmo nome no destino, você recebe uma IOException. Para sobrescrever o arquivo de destino, use o parâmetro adicional overwrite.
File.Copy(filePath, @"C:\dev\other.txt", overwrite: true); // Sobrescreve o destino se precisar
Vale saber também que a cópia mantém os atributos do arquivo original. Por exemplo, um arquivo somente leitura gera uma cópia também somente leitura.
Mover um arquivo
Da mesma forma, o File.Move move um arquivo de um diretório para outro. Ele também serve para renomear, já que renomear nada mais é do que mover para um novo nome na mesma pasta.
File.Move(filePath, @"C:\dev\temp\new.txt", overwrite: true);
Os cuidados são os mesmos do método anterior. A pasta de destino precisa existir e, sem o parâmetro overwrite, um arquivo com o mesmo nome no destino gera erro.
Substituir um arquivo
O File.Replace substitui o conteúdo de um arquivo pelo conteúdo de outro. Em seguida, ele exclui o arquivo de origem e cria um backup do arquivo substituído.
// Origem, destino que será substituído e backup do destino
File.Replace(@"C:\dev\other.txt", filePath, @"C:\dev\backup.txt");
No exemplo, o conteúdo de other.txt vai para myfile.txt, e a versão antiga de myfile.txt vira backup.txt. Se você não quiser backup, passe null no terceiro parâmetro.
Segundo a documentação, origem e destino precisam estar no mesmo volume. Além disso, os dois arquivos precisam existir, ou o método lança uma FileNotFoundException.
Excluir um arquivo
Para excluir um arquivo, a manipulação de arquivos em C# conta com o método File.Delete, que recebe o caminho completo. O método não aceita curingas como *.txt.
File.Delete(filePath);
Um ponto que costuma surpreender: se o arquivo não existir, o File.Delete não lança exceção. Por outro lado, ele falha se o arquivo estiver em uso, se for somente leitura ou se a pasta do caminho não existir.
Verificar se um arquivo existe
Para verificar se um arquivo existe, usamos o File.Exists com o caminho completo. O método devolve true ou false.
var exists = File.Exists(filePath); // Retorna um booleano
Entretanto, o File.Exists não substitui o tratamento de erros. Entre a verificação e a operação seguinte, outro processo pode excluir ou bloquear o arquivo.
Exemplo completo de manipulação de arquivos em C#
Para fixar os conceitos, vale juntar as operações em um único fluxo. O exemplo abaixo mostra uma manipulação de arquivos em C# do início ao fim, com tratamento de erros.
var folder = Path.Combine(AppContext.BaseDirectory, "reports");
var reportPath = Path.Combine(folder, "report.txt");
var archivePath = Path.Combine(folder, "archive", "report-old.txt");
try
{
// Cria as pastas necessárias
Directory.CreateDirectory(Path.GetDirectoryName(archivePath)!);
// Escreve o conteúdo inicial e acrescenta uma linha
File.WriteAllText(reportPath, "Daily report\n");
File.AppendAllText(reportPath, $"Generated at {DateTime.Now}\n");
// Faz uma cópia de segurança e move o original para o arquivo morto
File.Copy(reportPath, reportPath + ".bak", overwrite: true);
File.Move(reportPath, archivePath, overwrite: true);
Console.WriteLine(File.Exists(archivePath)); // True
}
catch (IOException ex)
{
Console.WriteLine($"I/O error: {ex.Message}");
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine($"Access denied: {ex.Message}");
}
Primeiro, o código garante que a estrutura de pastas existe. Em seguida, ele grava o relatório, cria um backup e move o arquivo original para a pasta de arquivo morto.
Repare que nenhum stream fica aberto ao fim do fluxo. Isso acontece porque WriteAllText e AppendAllText abrem e fecham o arquivo sozinhos.
Na prática, esse padrão aparece em rotinas de exportação, geração de logs e processamento em lote. Basta trocar os nomes e os conteúdos para adaptar o fluxo ao seu cenário.
Abertura e leitura de arquivos
A leitura merece um conteúdo próprio, porque existem várias abordagens com vantagens diferentes. Para saber mais, confira o artigo como ler arquivos em C#.
De forma resumida, File.ReadAllText e File.ReadAllLines carregam o arquivo inteiro na memória. Já File.ReadLines e StreamReader leem linha a linha, o que funciona melhor para arquivos grandes.
// Lê linha a linha, sem carregar o arquivo inteiro na memória
foreach (var line in File.ReadLines(filePath))
{
Console.WriteLine(line);
}
Se o objetivo é gerar planilhas simples, o artigo sobre gerar CSV com C# mostra como aplicar esses métodos na prática.
Arquivos grandes e streams na manipulação de arquivos em C#
Para arquivos grandes, a recomendação na manipulação de arquivos em C# é usar o StreamWriter para escrita e o StreamReader para leitura. Essas classes processam o conteúdo em partes, sem colocar tudo na memória.
// Escrita em lote com StreamWriter, acrescentando ao fim do arquivo
using (var writer = new StreamWriter(filePath, append: true))
{
for (var i = 0; i < 10_000; i++)
{
writer.WriteLine($"Line {i}");
}
}
// Leitura linha a linha com StreamReader
using (var reader = new StreamReader(filePath))
{
string? line;
while ((line = reader.ReadLine()) is not null)
{
Console.WriteLine(line);
}
}
O mesmo vale para aplicações web e serviços que atendem muitas requisições. Nesses cenários, os métodos assíncronos como File.ReadAllTextAsync e WriteLineAsync liberam a thread enquanto o disco trabalha.
await File.WriteAllTextAsync(filePath, "Async content");
var content = await File.ReadAllTextAsync(filePath);
Datas e atributos na manipulação de arquivos em C#
Além das operações de conteúdo, a manipulação de arquivos em C# inclui consultar metadados. A classe File oferece métodos para datas e atributos de cada arquivo.
Obter a data de acesso e de modificação
Um método bem útil é o File.GetLastAccessTime, que devolve a data e a hora do último acesso a um arquivo. O valor vem em um DateTime no horário local.
var lastAccess = File.GetLastAccessTime(filePath); // DateTime
Console.WriteLine(lastAccess);
Contudo, a data de último acesso não é a mesma coisa que a data de última modificação. Para saber quando alguém escreveu no arquivo pela última vez, use o File.GetLastWriteTime.
var lastWrite = File.GetLastWriteTime(filePath); // Última escrita, horário local
var lastWriteUtc = File.GetLastWriteTimeUtc(filePath); // Última escrita, em UTC
var created = File.GetCreationTime(filePath); // Data de criação
A documentação avisa que esses valores podem ser imprecisos, porque o sistema operacional nem sempre os atualiza na hora. Se o arquivo não existir, o método devolve 1º de janeiro de 1601, ajustado para o horário local.
Para entender melhor o tipo devolvido, vale ler o artigo sobre DateTime no C# e .NET.
Obter atributos do arquivo
Para os demais atributos, usamos o File.GetAttributes, que devolve um valor do enumerador FileAttributes. Esse enumerador funciona como um conjunto de flags, ou seja, um arquivo pode ter vários atributos ao mesmo tempo.
var attributes = File.GetAttributes(filePath);
if ((attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
Console.WriteLine("read-only");
if ((attributes & FileAttributes.Compressed) == FileAttributes.Compressed)
Console.WriteLine("compressed");
O operador & isola cada flag dentro do valor. Também é possível usar o método HasFlag, que deixa a leitura mais simples.
if (attributes.HasFlag(FileAttributes.Hidden))
Console.WriteLine("hidden");
Métodos específicos de plataforma na manipulação de arquivos em C#
A maior parte da manipulação de arquivos em C# funciona em qualquer plataforma que suporte o .NET. No entanto, alguns métodos rodam exclusivamente no Windows.
Um exemplo é o File.Encrypt, que depende do sistema de criptografia de arquivos do NTFS. Em Linux, macOS ou em edições Home do Windows, ele lança PlatformNotSupportedException ou NotSupportedException.
O compilador e a IDE alertam sempre que você usa um método específico de plataforma. Isso acontece por causa do analisador CA1416, que verifica chamadas a APIs marcadas com o atributo SupportedOSPlatform.
// Só chama a API quando o código roda no Windows
if (OperatingSystem.IsWindows())
{
File.Encrypt(filePath);
}
Com essa verificação, o aviso desaparece e o código continua seguro em outros sistemas operacionais.
Boas práticas de manipulação de arquivos em C#
Depois de conhecer os métodos, vale reunir algumas boas práticas de manipulação de arquivos em C#. Elas evitam a maioria dos erros que aparecem em produção.
Primeiro, sempre feche os recursos com using. Um FileStream ou StreamWriter aberto mantém o arquivo bloqueado e impede outras operações.
Em seguida, monte caminhos com Path.Combine em vez de concatenar strings. Assim, o código funciona em qualquer sistema operacional.
Também trate exceções específicas, como IOException e UnauthorizedAccessException. Dessa forma, você dá mensagens claras ao usuário e registra o erro correto.
Para arquivos grandes, prefira streams e leitura linha a linha. Por fim, em aplicações web, use as versões assíncronas dos métodos.
Se o destino dos arquivos for a nuvem, o artigo sobre upload de imagem para o Azure Storage com C# mostra o próximo passo.
Conclusão
A classe File oferece um enorme leque de opções, e a manipulação de arquivos em C# é simples e direta. Com poucos métodos, você cria, escreve, copia, move, substitui e exclui arquivos.
O principal cuidado está no tratamento de erros, já que leitura e escrita dependem do disco, das permissões e de outros processos. Portanto, combine try/catch, using e caminhos bem montados para ter um código confiável.
Com essas práticas, a manipulação de arquivos em C# deixa de ser uma fonte de bugs e passa a ser uma ferramenta confiável no dia a dia. Agora, experimente aplicar os exemplos em um projeto de console e observe o comportamento de cada método.
FAQ
Qual a diferença entre File e FileInfo no C#?
A classe File é estática e ideal para operações pontuais. Já a FileInfo guarda o caminho em uma instância e funciona melhor quando você repete várias operações sobre o mesmo arquivo.
O File.Create cria as pastas do caminho?
Não. Se a pasta não existir, o método lança DirectoryNotFoundException, então crie a estrutura antes com Directory.CreateDirectory.
O File.Delete gera erro se o arquivo não existir?
Não. O método só lança exceção quando o arquivo está em uso, é somente leitura, a pasta não existe ou falta permissão.
Como renomear um arquivo em C#?
Use o File.Move, informando o caminho atual e o novo nome na mesma pasta. Para sobrescrever um arquivo existente, passe overwrite: true.
Quando usar StreamWriter em vez de File.WriteAllText?
Use o StreamWriter quando for escrever muito conteúdo em partes ou em arquivos grandes. Para gravar um texto curto de uma vez, o File.WriteAllText é mais prático.