C#: Exercício: LINQ e arquivos
1. Analisador de notas dos alunos
Serialize os dados dos alunos em um arquivo JSON; em seguida, deserialize-os e utilize o LINQ para realizar uma análise estatística multidimensional: nota média, os três melhores, número de aprovações e agrupamento por série.
(1) Requisitos
- Defina uma classe
Studentcom as propriedadesNameeScore - Serializar a lista de alunos para JSON e salvá-la em um arquivo temporário
- Ler do arquivo e deserializar
- Use o LINQ para calcular: nota média, os três melhores, número de aprovações (>=60) e agrupamento por série
▶ Exemplo
using System;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Collections.Generic;
public class Student
{
public string Name { get; set; }
public int Score { get; set; }
}
class Program
{
static void Main()
{
var students = new List<Student>
{
new Student { Name = "Zhang San", Score = 92 },
new Student { Name = "Li Si", Score = 45 },
new Student { Name = "Wang Wu", Score = 78 },
new Student { Name = "Zhao Liu", Score = 88 },
new Student { Name = "Qian Qi", Score = 55 },
new Student { Name = "Sun Ba", Score = 95 },
new Student { Name = "Zhou Jiu", Score = 33 },
new Student { Name = "Wu Shi", Score = 71 }
};
string tempDir = Path.GetTempPath();
string filePath = Path.Combine(tempDir, "students.json");
var options = new JsonSerializerOptions { WriteIndented = true };
string json = JsonSerializer.Serialize(students, options);
File.WriteAllText(filePath, json);
Console.WriteLine("Written to: " + filePath);
string readJson = File.ReadAllText(filePath);
var loaded = JsonSerializer.Deserialize<List<Student>>(readJson);
double average = loaded.Average(s => s.Score);
Console.WriteLine($"Average score: {average:F1}");
var top3 = loaded.OrderByDescending(s => s.Score).Take(3);
Console.WriteLine("Top three:");
foreach (var s in top3)
{
Console.WriteLine($" {s.Name} - {s.Score}");
}
int passCount = loaded.Count(s => s.Score >= 60);
Console.WriteLine($"Pass count: {passCount}");
var grouped = loaded.GroupBy(s => s.Score >= 90 ? "Excellent"
: s.Score >= 60 ? "Pass" : "Fail");
Console.WriteLine("Grade groups:");
foreach (var group in grouped)
{
Console.WriteLine($" {group.Key}: {string.Join(", ", group.Select(s => s.Name))}");
}
File.Delete(filePath);
}
}
Written to: /tmp/students.json
Average score: 69.6
Top three:
Sun Ba - 95
Zhang San - 92
Zhao Liu - 88
Pass count: 5
Grade groups:
Excellent: Zhang San, Sun Ba
Fail: Li Si, Qian Qi, Zhou Jiu
Pass: Wang Wu, Zhao Liu, Wu Shi
2. Analisador Simples de Logs
Leia um arquivo de log de texto, analise cada linha e use o LINQ para contar erros e avisos, identificar o erro mais frequente e filtrar por intervalo de datas.
(1) Requisitos
- Formato do registro:
[Date] [Level] Message, por exemplo,[2025-01-15] [ERROR] Disk full - Contar as entradas por nível
- Identifique a mensagem de ERRO que ocorre com mais frequência
- Filtrar registros por intervalo de datas
▶ Exemplo
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
class Program
{
static void Main()
{
string tempDir = Path.GetTempPath();
string logPath = Path.Combine(tempDir, "app.log");
var lines = new List```<string>```
{
"[2025-01-10] [INFO] System started",
"[2025-01-10] [WARN] Memory usage 80%",
"[2025-01-11] [ERROR] Database connection failed",
"[2025-01-11] [ERROR] Disk full",
"[2025-01-12] [INFO] User login",
"[2025-01-12] [ERROR] Database connection failed",
"[2025-01-13] [WARN] CPU usage 90%",
"[2025-01-13] [ERROR] Network timeout",
"[2025-01-14] [INFO] Scheduled task completed",
"[2025-01-14] [ERROR] Database connection failed"
};
File.WriteAllLines(logPath, lines);
var logEntries = File.ReadAllLines(logPath)
.Select(line =>
{
var parts = line.Split(']');
return new
{
Date = parts[0].TrimStart('[').Trim(),
Level = parts[1].TrimStart('[').Trim(),
Message = parts[2].Trim()
};
})
.ToList();
var levelCounts = logEntries
.GroupBy(e => e.Level)
.Select(g => new { Level = g.Key, Count = g.Count() })
.OrderByDescending(x => x.Count);
Console.WriteLine("Level counts:");
foreach (var item in levelCounts)
{
Console.WriteLine($" {item.Level}: {item.Count}");
}
var topError = logEntries
.Where(e => e.Level == "ERROR")
.GroupBy(e => e.Message)
.OrderByDescending(g => g.Count())
.First();
Console.WriteLine($"Most frequent error: {topError.Key} ({topError.Count()} times)");
var filtered = logEntries
.Where(e => string.Compare(e.Date, "2025-01-11") >= 0
&& string.Compare(e.Date, "2025-01-13") <= 0)
.ToList();
Console.WriteLine("Logs from Jan 11-13:");
foreach (var entry in filtered)
{
Console.WriteLine($" [{entry.Date}] [{entry.Level}] {entry.Message}");
}
File.Delete(logPath);
}
}
Level counts:
ERROR: 5
INFO: 3
WARN: 2
Most frequent error: Database connection failed (3 times)
Logs from Jan 11-13:
[2025-01-11] [ERROR] Database connection failed
[2025-01-11] [ERROR] Disk full
[2025-01-12] [INFO] User login
[2025-01-12] [ERROR] Database connection failed
[2025-01-13] [WARN] CPU usage 90%
[2025-01-13] [ERROR] Network timeout
3. Ferramenta para renomear arquivos em lote
Listar os arquivos de um diretório, filtrar por extensão usando LINQ e, em seguida, adicionar em lote um prefixo ou sufixo.
(1) Requisitos
- Criar arquivos de exemplo em um diretório temporário
- Filtrar arquivos por extensão
- Adicionar um prefixo e um sufixo aos nomes dos arquivos (preservando a extensão)
- Exibir a lógica de renomeação (comparar o nome antigo com o novo)
▶ Exemplo
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
class Program
{
static void Main()
{
string workDir = Path.Combine(Path.GetTempPath(), "rename_demo");
Directory.CreateDirectory(workDir);
var demoFiles = new[] { "photo1.jpg", "photo2.jpg", "doc.txt", "notes.txt", "data.csv" };
foreach (var f in demoFiles)
{
File.WriteAllText(Path.Combine(workDir, f), "demo");
}
Console.WriteLine("Original files:");
foreach (var f in Directory.GetFiles(workDir))
{
Console.WriteLine(" " + Path.GetFileName(f));
}
string targetExt = ".txt";
string prefix = "backup_";
var filesToRename = Directory.GetFiles(workDir)
.Where(f => Path.GetExtension(f).Equals(targetExt, StringComparison.OrdinalIgnoreCase))
.ToList();
Console.WriteLine($"\nFiltering extension {targetExt}:");
foreach (var f in filesToRename)
{
string dir = Path.GetDirectoryName(f);
string nameNoExt = Path.GetFileNameWithoutExtension(f);
string newName = prefix + nameNoExt + targetExt;
string newPath = Path.Combine(dir, newName);
Console.WriteLine($" {Path.GetFileName(f)} -> {newName}");
File.Move(f, newPath);
}
Console.WriteLine("\nAfter renaming:");
foreach (var f in Directory.GetFiles(workDir))
{
Console.WriteLine(" " + Path.GetFileName(f));
}
Directory.Delete(workDir, true);
}
}
Original files:
photo1.jpg
photo2.jpg
doc.txt
notes.txt
data.csv
Filtering extension .txt:
doc.txt -> backup_doc.txt
notes.txt -> backup_notes.txt
After renaming:
photo1.jpg
photo2.jpg
backup_doc.txt
backup_notes.txt
data.csv
❓ Perguntas Frequentes
P: E se os nomes das propriedades não corresponderem durante a desserialização de JSON? R: Use o atributo
[JsonPropertyName]para mapear os nomes ou personalizePropertyNamingPolicyemJsonSerializerOptions.
P: A ordem dos resultados do GroupBy é garantida? R: Não, o GroupBy não garante a ordem dos grupos. Use o OrderBy para ordenar antes de iterar.
P: O que acontece se o arquivo de destino já existir ao usar File.Move para renomear? R: É lançada uma IOException. Você deve verificar primeiro se o caminho de destino existe ou usar File.Replace.
P: O ReadAllLines é adequado para ler arquivos de log grandes? R: Não, o ReadAllLines carrega todo o conteúdo na memória de uma só vez. Para arquivos grandes, use o File.ReadLines para uma leitura gradual, linha por linha.
📖 Resumo
- Aplicar de forma abrangente o LINQ e a serialização JSON para persistência de dados e análise estatística
- Dominar a análise de logs, o agrupamento e a agregação com LINQ e as técnicas de filtragem por data
- Utilizar as classes Directory e Path em combinação com o LINQ para filtragem de arquivos e operações em lote
- Todos os exemplos de operações com arquivos utilizam o método Path.GetTempPath() para garantir a compatibilidade entre plataformas
- Os arquivos e diretórios temporários são removidos após o uso para evitar que fiquem resíduos
📝 Exercícios
- Ampliar o analisador de notas dos alunos para contabilizar os alunos por faixa de pontuação (0-59/60-79/80-89/90-100) e gerar um gráfico de barras (utilizando os caracteres
*) - Adicione um recurso ao analisador de logs: conte as entradas de ERROR por dia e exiba o intervalo de datas com o maior número de ocorrências consecutivas de ERROR
- Refatorar a ferramenta de renomeação de arquivos para aceitar argumentos de linha de comando para caminho do diretório, filtro de extensão e prefixo, e adicionar um modo
--dry-runque apenas mostre uma pré-visualização sem executar