C# Arquivos

Até aqui, tudo o que seus programas sabiam sumia quando eles terminavam: as variáveis vivem na memória, e a memória é apagada no fim. Um arquivo fica no disco e continua lá no dia seguinte. Nesta aula você vai usar a classe File ("arquivo") para gravar a lista de compras da Ana, ler as notas da turma, acrescentar linhas num registro (log) e copiar, mover e apagar arquivos. Também vai conhecer os erros de arquivo mais comuns e como tratá-los.

Você vai aprender
  • Gravar um texto com File.WriteAllText e ler de volta com File.ReadAllText.
  • Trabalhar linha por linha com File.WriteAllLines, File.ReadAllLines e File.ReadLines.
  • Acrescentar no fim sem apagar o que havia: File.AppendAllText e File.AppendAllLines.
  • Perguntar e mexer no arquivo: File.Exists, File.Copy, File.Move e File.Delete.
  • O que é UTF-8, por que ã ocupa 2 bytes e como ler um arquivo antigo em Latin-1.
  • As exceções FileNotFoundException, DirectoryNotFoundException, IOException e UnauthorizedAccessException, e o "8.5 que virou 85".

Esta aula usa arrays, foreach e try e catch. Todos os programas foram rodados com dotnet run (aula Rodar com dotnet run) no .NET 10 da máquina da Ana (usuária ana, computador notebook), dentro da pasta ~/cs, que é /home/ana/cs.


A ideia: o caderno na gaveta

Pense na memória do computador como um quadro branco: rápido de escrever, mas alguém apaga tudo no fim da aula. O disco é um caderno guardado na gaveta: o que você escreve ali continua lá amanhã. Para usar o caderno você precisa de duas coisas: o nome dele (o caminho do arquivo, como compras.txt) e alguém que saiba abrir, ler, escrever e fechar. Em C#, esse alguém é a classe File.

A classe File fica no namespace System.IO (de input/output, entrada e saída). Você não precisa escrever using System.IO;: no .NET 10, ele já vem incluído nos programas de console (são os usings implícitos, da aula Namespaces e using). Todos os métodos desta aula são estáticos: você chama direto File.Método(…), sem criar objeto.

MétodoTraduçãoO que faz
File.WriteAllText(caminho, texto)escrever todo o textoCria (ou substitui) o arquivo com o texto
File.ReadAllText(caminho)ler todo o textoDevolve o arquivo inteiro numa string
File.WriteAllLines(caminho, linhas)escrever todas as linhasGrava cada item numa linha
File.ReadAllLines(caminho)ler todas as linhasDevolve um string[], uma linha por posição
File.AppendAllText(caminho, texto)acrescentar textoGrava no fim, mantendo o que havia

1. Gravar e ler um texto: WriteAllText e ReadAllText

A Ana quer guardar a lista de compras num arquivo. Este programa grava a lista, lê o arquivo de volta e mostra o que leu:

File.WriteAllText("compras.txt", "pão\nleite\n");
string texto = File.ReadAllText("compras.txt");
Console.Write(texto);
Console.WriteLine($"{texto.Length} caracteres");
File.WriteAllText("compras.txt", "pão\nleite\n");
Abre o arquivo compras.txt, grava o texto e fecha. Se o arquivo não existe, ele é criado. Se já existe, o conteúdo antigo é apagado e substituído. O \n é a quebra de linha (aula Escapes): o WriteAllText grava exatamente o que você mandar, sem pular linha sozinho.
string texto = File.ReadAllText("compras.txt");
Abre o arquivo, lê tudo, fecha e devolve o conteúdo numa única string, com as quebras de linha dentro.
Console.Write(texto);
Write, e não WriteLine: o texto já termina com \n. Com WriteLine apareceria uma linha em branco a mais.
texto.Length
Quantos caracteres a string tem: pão (3) + \n (1) + leite (5) + \n (1) = 10.
$ dotnet run compras.cs
pão
leite
10 caracteres
$ cat compras.txt               « o arquivo continua lá depois que o programa terminou
pão
leite

O nome "compras.txt" não diz em que pasta o arquivo fica. Um nome assim é um caminho relativo: ele é procurado (ou criado) na pasta atual, a pasta em que o terminal estava quando a Ana deu o dotnet run. Aqui, /home/ana/cs. A aula Pastas e Caminhos mostra como montar caminhos completos.

WriteAllText apaga o que havia

Rodar o programa duas vezes não deixa a lista com 4 itens: a segunda vez substitui o arquivo inteiro. Não há "desfazer" nem lixeira. Para acrescentar no fim, use AppendAllText (seção 3).


2. Linha por linha: WriteAllLines e ReadAllLines

Quase todo arquivo de texto é uma lista de linhas: uma compra por linha, um aluno por linha, um produto por linha. Os métodos …Lines trabalham direto com essas listas, sem você precisar mexer com \n.

string[] alunos = ["Ana;8,5", "Bruno;6", "Carla;9,5"];
File.WriteAllLines("notas.txt", alunos);

string[] linhas = File.ReadAllLines("notas.txt");
Console.WriteLine($"Linhas: {linhas.Length}");
foreach (string linha in linhas)
{
    string[] partes = linha.Split(';');
    Console.WriteLine($"{partes[0]} tirou {partes[1]}");
}
["Ana;8,5", "Bruno;6", "Carla;9,5"]
Um array de string escrito com a expressão de coleção C# 12 (aula Arrays). Cada texto tem o nome e a nota separados por ;.
File.WriteAllLines("notas.txt", alunos);
Grava cada item do array numa linha, com a quebra de linha no fim de cada uma (inclusive da última). Aceita um array, uma List<string> ou qualquer sequência de textos.
File.ReadAllLines("notas.txt")
Lê o arquivo e devolve um string[] com uma linha em cada posição, já sem as quebras de linha.
linha.Split(';')
Corta a linha em cada ; (aula Métodos de String): partes[0] é o nome e partes[1] é a nota, ainda como texto.
$ dotnet run notas.cs
Linhas: 3
Ana tirou 8,5
Bruno tirou 6
Carla tirou 9,5
$ cat notas.txt
Ana;8,5
Bruno;6
Carla;9,5

Um detalhe que confunde: o arquivo termina com uma quebra de linha depois de Carla;9,5, mas o ReadAllLines devolve 3 linhas, e não 4. A última quebra só fecha a última linha; ela não abre uma linha vazia. Um arquivo vazio dá um array vazio (Length = 0).

Missão: o programa grava a lista de compras e mostra quantos itens ela tem. Depois do WriteAllText, acrescente café no fim do arquivo com File.AppendAllText("compras.txt", "café\n"); e rode de novo: a saída deve ser 3 itens: pão, leite, café.

File.WriteAllText("compras.txt", "pão\nleite\n");

string[] itens = File.ReadAllLines("compras.txt");
Console.WriteLine($"{itens.Length} itens: {string.Join(", ", itens)}");
File.WriteAllText("compras.txt", "pão\nleite\n");
File.AppendAllText("compras.txt", "café\n");

string[] itens = File.ReadAllLines("compras.txt");
Console.WriteLine($"{itens.Length} itens: {string.Join(", ", itens)}");

O string.Join(", ", itens) junta os itens do array num texto só, com ", " entre eles (aula Métodos de String).


3. Acrescentar no fim: AppendAllText e AppendAllLines

Um log (registro) é um arquivo que só cresce: cada vez que algo acontece, o programa escreve uma linha nova no fim. Para isso existem os métodos Append… ("acrescentar"):

File.AppendAllText("log.txt", "Programa iniciado\n");
File.AppendAllLines("log.txt", ["Venda: caderno", "Venda: caneta"]);
Console.WriteLine($"O log tem {File.ReadAllLines("log.txt").Length} linhas");
File.AppendAllText("log.txt", "Programa iniciado\n");
Grava o texto no fim do arquivo, mantendo o que já estava lá. Se o arquivo não existe, ele é criado, como no WriteAllText. A quebra de linha \n continua sendo sua.
File.AppendAllLines("log.txt", [ … ]);
O mesmo, para uma lista: cada item vira uma linha no fim do arquivo, cada uma com a sua quebra.
$ dotnet run log.cs
O log tem 3 linhas
$ dotnet run log.cs               « de novo: as linhas antigas ficam
O log tem 6 linhas
$ cat log.txt
Programa iniciado
Venda: caderno
Venda: caneta
Programa iniciado
Venda: caderno
Venda: caneta

4. Arquivos grandes: ReadLines

ReadAllText e ReadAllLines carregam o arquivo inteiro na memória antes de devolver qualquer coisa. Para a lista de compras, ótimo. Para um log de 2 GB, o programa pode ficar sem memória. O File.ReadLines (sem o All) entrega uma linha por vez, conforme o foreach pede:

int n = 0;
foreach (string linha in File.ReadLines("log.txt"))
{
    n++;
    if (linha.StartsWith("Venda"))
    {
        Console.WriteLine($"{n}: {linha}");
    }
}
File.ReadLines("log.txt")
Não devolve um array: devolve uma sequência (IEnumerable<string>, aula IEnumerable e foreach). Cada volta do foreach lê a próxima linha do disco. Só uma linha fica na memória de cada vez.
n++
Conta as linhas, para mostrar o número de cada uma.
linha.StartsWith("Venda")
Verdadeiro se a linha começa com Venda.
$ dotnet run vendas.cs
2: Venda: caderno
3: Venda: caneta
5: Venda: caderno
6: Venda: caneta

A diferença não aparece na saída: aparece na memória. Se você precisa do Length ou de linhas[3], use ReadAllLines. Se só vai passar pelas linhas uma vez, do começo ao fim, prefira ReadLines. Para ler e escrever aos poucos com mais controle, a próxima aula apresenta o StreamReader e o StreamWriter.


5. Existe? Copiar, mover e apagar

Console.WriteLine(File.Exists("compras.txt"));
Console.WriteLine(File.Exists("fotos.zip"));

File.Copy("compras.txt", "compras-backup.txt", overwrite: true);
File.Move("compras-backup.txt", "backup.txt", overwrite: true);
Console.WriteLine(File.Exists("compras-backup.txt"));
Console.WriteLine(File.Exists("backup.txt"));

File.Delete("backup.txt");
File.Delete("backup.txt");
Console.WriteLine(File.Exists("backup.txt"));
File.Exists("compras.txt")
"O arquivo existe?" Devolve true ou false e nunca lança exceção, nem com um nome estranho.
File.Copy(origem, destino, overwrite: true)
Copia. Sem o overwrite: true ("sobrescrever", um argumento nomeado), ele recusa um destino que já existe e lança IOException (veja "Quando dá errado").
File.Move(origem, destino, overwrite: true)
Move ou renomeia: depois dele, o nome antigo não existe mais. O overwrite funciona como no Copy.
File.Delete("backup.txt")
Apaga de verdade, sem lixeira. Se o arquivo já não existe, ele não reclama: por isso a segunda linha igual não deu erro.
$ dotnet run backup.cs
True
False
False
True
False
Exists antes de ler

Conferir com if (File.Exists(nome)) antes de ler evita a exceção no caso comum ("o arquivo ainda não foi criado"). Mas o arquivo pode sumir entre o Exists e a leitura, ou existir e estar trancado. Por isso, em programas para outras pessoas, a leitura também fica dentro de um try (seção "Quando dá errado").


6. Texto e bytes: UTF-8

No disco, um arquivo é uma fila de bytes (números de 0 a 255). A codificação (encoding) é a tabela que diz qual byte vira qual letra. Para as letras sem acento, quase todas as tabelas concordam. Para ã, é e ç, não.

O .NET grava e lê em UTF-8 quando você não diz nada, que é a codificação padrão da internet e do Linux. Em UTF-8, cada letra acentuada ocupa 2 bytes. O comando xxd mostra os bytes do arquivo de compras (com o café acrescentado na missão):

$ xxd compras.txt
00000000: 70c3 a36f 0a6c 6569 7465 0a63 6166 c3a9  p..o.leite.caf..
00000010: 0a                                       .

70 é o p, c3 a3 é o ã (dois bytes), 6f é o o e 0a é a quebra de linha. São 15 caracteres, mas 17 bytes.

Arquivos antigos, gerados por programas velhos do Windows, às vezes vêm em Latin-1 (ISO-8859-1), em que ã é um byte só. A Ana recebeu uma lista de clientes assim. O comando printf do terminal cria um arquivo igual, e o comando file descobre a codificação:

using System.Text;

Console.WriteLine(File.ReadAllText("clientes-antigo.txt"));
Console.WriteLine(File.ReadAllText("clientes-antigo.txt", Encoding.Latin1));
$ printf 'Jo\343o;S\343o Paulo\n' > clientes-antigo.txt
$ file clientes-antigo.txt
clientes-antigo.txt: ISO-8859 text
$ dotnet run latin.cs
Jo�o;S�o Paulo

João;São Paulo
using System.Text;
A classe Encoding ("codificação") fica no namespace System.Text, que não vem incluído sozinho.
File.ReadAllText(caminho)
Lê como UTF-8. O byte do ã em Latin-1 não forma uma letra válida em UTF-8, e o .NET o troca pelo símbolo � ("caractere de substituição"). Não há exceção: o texto simplesmente sai estragado.
File.ReadAllText(caminho, Encoding.Latin1)
Todos os métodos de File aceitam a codificação como último argumento. Com a tabela certa, o texto sai certo.

As linhas em branco na saída são o \n do fim do arquivo mais o do WriteLine. Se aparecer �, ou textos como João (UTF-8 lido como Latin-1), o arquivo e o programa estão usando tabelas diferentes. Para gravar, fique sempre com o padrão, UTF-8.


Qual método usar

Você quer…Use
Gravar um texto (criar ou substituir o arquivo)File.WriteAllText(caminho, texto)
Gravar uma lista, um item por linhaFile.WriteAllLines(caminho, lista)
Acrescentar no fim (log, registro)File.AppendAllText ou File.AppendAllLines
Ler um arquivo pequeno inteiroFile.ReadAllText(caminho)
Ler as linhas e usar Length ou linhas[i]File.ReadAllLines(caminho)
Passar uma vez por um arquivo grandeforeach (… in File.ReadLines(caminho))
Ler ou escrever aos poucos, com controleStreamReader e StreamWriter (aula Ler e Escrever)
Ler sem travar o programa (em apps e servidores)File.ReadAllTextAsync e os outros …Async (aula async e await)

Quando dá errado

Os erros de arquivo são exceções (aula Exceções): o programa compila, mas para no meio, com o código de saída 134. Todas trazem o caminho completo na mensagem, o que ajuda muito a descobrir o problema. As mensagens das exceções saem em inglês.

FileNotFoundException: o arquivo não existe (ali)
string texto = File.ReadAllText("nota.txt");
Console.WriteLine(texto);
$ dotnet run ler.cs; echo "código: $?"
Unhandled exception. System.IO.FileNotFoundException: Could not find file '/home/ana/cs/nota.txt'.
File name: '/home/ana/cs/nota.txt'
   at Interop.ThrowExceptionForIoErrno(ErrorInfo errorInfo, String path, Boolean isDirError)
   at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String path, OpenFlags flags, Int32 mode, Boolean failForSymlink, Boolean& wasSymlink, Func`4 createOpenException)
   at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, UnixFileMode openPermissions, Int64& fileLength, UnixFileMode& filePermissions, Boolean failForSymlink, Boolean& wasSymlink, Func`4 createOpenException)
   at System.IO.Strategies.OSFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, Int64 preallocationSize)
   at System.IO.StreamReader.ValidateArgsAndOpenPath(String path, Int32 bufferSize)
   at System.IO.File.ReadAllText(String path, Encoding encoding)
   at Program.<Main>$(String[] args) in /home/ana/cs/ler.cs:line 1
código: 134

O que aconteceu: "Não foi possível encontrar o arquivo". O arquivo se chama notas.txt; faltou o s. A primeira linha diz o caminho exato que o .NET tentou; a última diz a linha do seu programa (ler.cs:line 1). As do meio são o caminho que a chamada fez dentro do .NET e podem ser ignoradas.

Como resolver: confira o nome (no Linux, Notas.txt e notas.txt são arquivos diferentes), a extensão e a pasta em que você rodou o programa. Num programa para outras pessoas, trate a exceção e mostre uma mensagem amigável:

try
{
    string texto = File.ReadAllText("nota.txt");
    Console.WriteLine(texto);
}
catch (FileNotFoundException e)
{
    Console.WriteLine($"Não achei o arquivo: {e.FileName}");
}
$ dotnet run tratar.cs
Não achei o arquivo: /home/ana/cs/nota.txt
DirectoryNotFoundException: a pasta não existe
File.WriteAllText("relatorios/outubro.txt", "Vendas: 1520\n");
$ dotnet run pasta.cs
Unhandled exception. System.IO.DirectoryNotFoundException: Could not find a part of the path '/home/ana/cs/relatorios/outubro.txt'.
   at Interop.ThrowExceptionForIoErrno(ErrorInfo errorInfo, String path, Boolean isDirError)
   …                                                « mais 4 linhas de dentro do .NET
   at Program.<Main>$(String[] args) in /home/ana/cs/pasta.cs:line 1

O que aconteceu: "Não foi possível encontrar uma parte do caminho". O WriteAllText cria o arquivo, mas não cria pastas, e a pasta relatorios não existe.

Como resolver: crie a pasta antes com Directory.CreateDirectory("relatorios");, assunto da aula Pastas e Caminhos.

IOException: o destino da cópia já existe
File.Copy("compras.txt", "copia.txt");
Console.WriteLine("Copiei");
$ dotnet run copiar.cs
Copiei
$ dotnet run copiar.cs               « de novo: agora copia.txt já existe
Unhandled exception. System.IO.IOException: The file '/home/ana/cs/copia.txt' already exists.
   at Interop.ThrowExceptionForIoErrno(ErrorInfo errorInfo, String path, Boolean isDirError)
   …                                                « mais 3 linhas de dentro do .NET
   at Program.<Main>$(String[] args) in /home/ana/cs/copiar.cs:line 1

O que aconteceu: "O arquivo já existe". O Copy e o Move se recusam a apagar um arquivo que já está no destino, para você não perder dados sem querer. (O WriteAllText, ao contrário, substitui sem perguntar.)

Como resolver: se substituir é o que você quer, diga isso: File.Copy("compras.txt", "copia.txt", overwrite: true);. Se não, confira antes com File.Exists("copia.txt").

UnauthorizedAccessException: sem permissão
$ echo segredo > trancado.txt
$ chmod 444 trancado.txt               « só leitura, para todo mundo
$ cat trancado.cs
File.WriteAllText("trancado.txt", "novo");
$ dotnet run trancado.cs
Unhandled exception. System.UnauthorizedAccessException: Access to the path '/home/ana/cs/trancado.txt' is denied.
 ---> System.IO.IOException: Permission denied
   --- End of inner exception stack trace ---
   at Interop.ThrowExceptionForIoErrno(ErrorInfo errorInfo, String path, Boolean isDirError)
   …                                                « mais 4 linhas de dentro do .NET
   at Program.<Main>$(String[] args) in /home/ana/cs/trancado.cs:line 1

O que aconteceu: "O acesso ao caminho foi negado". O arquivo é só de leitura (as permissões do Linux, que o Guia Bash explica com o chmod). O mesmo acontece ao tentar gravar numa pasta do sistema, como /etc, sem ser administradora.

Como resolver: grave em outro lugar (a sua pasta pessoal) ou devolva a permissão com chmod u+w trancado.txt. Para tratar todos os erros de arquivo de uma vez, use catch (IOException e) (pega FileNotFoundException, DirectoryNotFoundException e as outras filhas) seguido de catch (UnauthorizedAccessException e), que é de outra família.

As notas viraram 85, 60 e 95

A planilha da escola veio com as notas escritas com ponto, como nos países de língua inglesa: Ana;8.5. A Ana converteu o texto em número com double.Parse (aula Parse e TryParse):

string[] linhas = File.ReadAllLines("notas.txt");
foreach (string linha in linhas)
{
    string[] partes = linha.Split(';');
    double nota = double.Parse(partes[1]);
    Console.WriteLine($"{partes[0]} tirou {nota}");
}
$ cat notas.txt
Ana;8.5
Bruno;6.0
Carla;9.5
$ dotnet run media.cs
Ana tirou 85
Bruno tirou 60
Carla tirou 95

O que aconteceu: nenhuma exceção, e o resultado está errado, o pior tipo de erro. O computador da Ana está em português, e em pt-BR a vírgula separa os decimais e o ponto separa os milhares (como em 1.500). Para o double.Parse, o ponto de 8.5 é um separador de milhares, que ele aceita e ignora: sobra 85. A aula Cultura explica isso em detalhes.

Como resolver: quando o arquivo usa ponto, diga ao Parse que ele está no formato "neutro", o CultureInfo.InvariantCulture:

using System.Globalization;

string[] linhas = File.ReadAllLines("notas.txt");
double soma = 0;
foreach (string linha in linhas)
{
    string[] partes = linha.Split(';');
    double nota = double.Parse(partes[1], CultureInfo.InvariantCulture);
    Console.WriteLine($"{partes[0]} tirou {nota}");
    soma += nota;
}
Console.WriteLine($"Média: {soma / linhas.Length:F2}");
$ dotnet run media.cs
Ana tirou 8,5
Bruno tirou 6
Carla tirou 9,5
Média: 8,00

O arquivo continua com ponto; na tela, os números aparecem do jeito brasileiro.

Missão: o notas.txt da outra aba usa ponto nas notas, e a média saiu 80,00. Conserte o double.Parse com CultureInfo.InvariantCulture para a saída terminar com Média: 8,00.

using System.Globalization;

string[] linhas = File.ReadAllLines("notas.txt");
double soma = 0;
foreach (string linha in linhas)
{
    string[] partes = linha.Split(';');
    double nota = double.Parse(partes[1]);
    Console.WriteLine($"{partes[0]} tirou {nota}");
    soma += nota;
}
Console.WriteLine($"Média: {soma / linhas.Length:F2}");
Ana;8.5
Bruno;6.0
Carla;9.5
using System.Globalization;

string[] linhas = File.ReadAllLines("notas.txt");
double soma = 0;
foreach (string linha in linhas)
{
    string[] partes = linha.Split(';');
    double nota = double.Parse(partes[1], CultureInfo.InvariantCulture);
    Console.WriteLine($"{partes[0]} tirou {nota}");
    soma += nota;
}
Console.WriteLine($"Média: {soma / linhas.Length:F2}");
CS0029: ReadAllText no lugar de ReadAllLines
string[] linhas = File.ReadAllText("notas.txt");
Console.WriteLine(linhas.Length);
$ dotnet run erro.cs
/home/ana/cs/erro.cs(1,19): error CS0029: Não é possível converter implicitamente tipo "string" em "string[]"

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O que aconteceu: um erro de compilação (em inglês, Cannot implicitly convert type 'string' to 'string[]'). O ReadAllText devolve uma string, e a variável é um array de strings. Os nomes são parecidos, e o autocompletar do editor troca um pelo outro com facilidade.

Como resolver: para uma linha em cada posição, File.ReadAllLines. Para o texto inteiro, string texto = File.ReadAllText(…).


Referência rápida

CódigoO que faz
File.WriteAllText(c, texto)Cria ou substitui o arquivo com o texto
File.WriteAllLines(c, lista)Cria ou substitui, um item por linha
File.AppendAllText(c, texto)Acrescenta no fim (cria se não existir)
File.AppendAllLines(c, lista)Acrescenta linhas no fim
File.ReadAllText(c)Arquivo inteiro → string
File.ReadAllLines(c)Arquivo → string[], sem as quebras
File.ReadLines(c)Linhas uma por vez, para o foreach
File.Exists(c)true se o arquivo existe; nunca lança exceção
File.Copy(a, b) / File.Copy(a, b, overwrite: true)Copia; sem overwrite, recusa um destino existente
File.Move(a, b)Move ou renomeia
File.Delete(c)Apaga (sem lixeira); não reclama se o arquivo não existe
File.ReadAllText(c, Encoding.Latin1)Lê com outra codificação (using System.Text;)
File.ReadAllTextAsync(c)Versão assíncrona (aula async e await)

Exercícios

1. O arquivo inteiro

Complete para ler o arquivo inteiro numa única string:

string texto = File.("compras.txt");

2. No fim, sem apagar

Complete para acrescentar uma linha no fim do log, mantendo o que já existe:

File.("log.txt", "Venda: régua\n");

3. Copiar por cima

Complete para a cópia substituir o backup.txt, se ele já existir:

File.Copy("compras.txt", "backup.txt", );

4. O arquivo log.txt tem 10 linhas. O que acontece com File.WriteAllText("log.txt", "oi\n")?

5. Um arquivo tem as linhas a, b e c, cada uma terminada por uma quebra de linha. Quanto vale File.ReadAllLines(…).Length?

6. Num computador em pt-BR, quanto vale double.Parse("2.5")?

Missão final: o relatório do estoque

Missão: o estoque.csv (na outra aba) tem, em cada linha, o produto, a quantidade e o preço (com ponto). O programa já lê o arquivo, pula o cabeçalho e calcula o valor de cada produto. Complete-o:

  1. dentro do laço, acrescente na lista relatorio o texto $"{campos[0]}: {valor:F2}" e some o valor no total;
  2. depois do laço, acrescente $"Total: {total:F2}" na lista;
  3. grave a lista em relatorio.txt com File.WriteAllLines;
  4. leia o relatorio.txt de volta com File.ReadAllText e mostre com Console.Write.

A saída deve ser:

Caderno: 190,80
Caneta azul: 100,00
Cola branca: 34,00
Total: 324,80
using System.Globalization;

string[] linhas = File.ReadAllLines("estoque.csv");
List<string> relatorio = [];
decimal total = 0;
for (int i = 1; i < linhas.Length; i++)
{
    string[] campos = linhas[i].Split(';');
    int qtd = int.Parse(campos[1]);
    decimal preco = decimal.Parse(campos[2], CultureInfo.InvariantCulture);
    decimal valor = qtd * preco;
    // 1. acrescente o produto na lista e some o valor no total
}
// 2. acrescente o total na lista
// 3. grave a lista em relatorio.txt
// 4. leia o relatorio.txt e mostre
produto;qtd;preco
Caderno;12;15.90
Caneta azul;40;2.50
Cola branca;8;4.25
using System.Globalization;

string[] linhas = File.ReadAllLines("estoque.csv");
List<string> relatorio = [];
decimal total = 0;
for (int i = 1; i < linhas.Length; i++)
{
    string[] campos = linhas[i].Split(';');
    int qtd = int.Parse(campos[1]);
    decimal preco = decimal.Parse(campos[2], CultureInfo.InvariantCulture);
    decimal valor = qtd * preco;
    relatorio.Add($"{campos[0]}: {valor:F2}");
    total += valor;
}
relatorio.Add($"Total: {total:F2}");
File.WriteAllLines("relatorio.txt", relatorio);
Console.Write(File.ReadAllText("relatorio.txt"));

O preço está em decimal, o tipo certo para dinheiro (aula float, double e decimal), e o for começa em 1 para pular o cabeçalho produto;qtd;preco. Se quiser ir além: acrescente uma linha Régua;5;1.20 no CSV e veja o total mudar para 330,80.


Resumo

  • A classe File (namespace System.IO, já incluído) lê e grava arquivos com uma linha de código. Um nome sem pasta, como "compras.txt", é procurado na pasta em que o programa foi rodado.
  • WriteAllText e WriteAllLines criam ou substituem o arquivo. AppendAllText e AppendAllLines acrescentam no fim.
  • ReadAllText devolve uma string; ReadAllLines, um string[] sem as quebras. Os dois leem tudo de uma vez; para arquivos grandes, ReadLines entrega uma linha por vez.
  • Exists pergunta sem lançar exceção; Copy e Move recusam um destino existente sem overwrite: true; Delete apaga de verdade.
  • O padrão é UTF-8, em que ã ocupa 2 bytes. Para um arquivo antigo, passe a codificação: Encoding.Latin1.
  • Erros comuns: FileNotFoundException, DirectoryNotFoundException, IOException e UnauthorizedAccessException. E números com ponto: double.Parse(texto, CultureInfo.InvariantCulture).