Ficha 02 · C# avançado
Exercício 1 · LINQ — filtros e projecções
Dado este array:
var pessoas = new List<(string Nome, int Idade, string Cidade)>
{
("Ana", 25, "Lisboa"),
("Bruno", 32, "Porto"),
("Carla", 19, "Lisboa"),
("Diana", 41, "Coimbra"),
("Eduardo", 28, "Porto")
};
Usando LINQ: 1. Quantas pessoas têm > 25 anos? 2. Imprime nomes ordenados alfabeticamente. 3. Qual a média de idade? 4. Quem mora em Lisboa ou Porto?
Resposta:
// 1
int maiores = pessoas.Count(p => p.Idade > 25);
Console.WriteLine($"Maiores de 25: {maiores}");
// 2
var nomes = pessoas
.OrderBy(p => p.Nome)
.Select(p => p.Nome);
foreach (var n in nomes) Console.WriteLine(n);
// 3
double media = pessoas.Average(p => p.Idade);
Console.WriteLine($"Média idade: {media:F1}");
// 4
var grandesCidades = pessoas
.Where(p => p.Cidade == "Lisboa" || p.Cidade == "Porto")
.Select(p => $"{p.Nome} ({p.Cidade})");
foreach (var p in grandesCidades) Console.WriteLine(p);
Exercício 2 · GroupBy
Continuando o exercício 1, agrupa as pessoas por cidade e mostra para cada cidade: quantas pessoas e a média de idade.
Resposta:
var porCidade = pessoas
.GroupBy(p => p.Cidade)
.Select(g => new {
Cidade = g.Key,
Quantos = g.Count(),
Media = g.Average(p => p.Idade)
})
.OrderByDescending(x => x.Quantos);
foreach (var c in porCidade)
{
Console.WriteLine($"{c.Cidade}: {c.Quantos} pessoa(s), média {c.Media:F1} anos");
}
Exercício 3 · Ler ficheiro CSV
Cria um ficheiro produtos.csv com este conteúdo:
nome,preco,stock
Caneta,1.50,100
Caderno,5.00,50
Mochila,35.00,20
Estojo,8.00,30
Cria uma classe Produto (record) e lê o ficheiro para uma List<Produto>. Depois imprime apenas produtos com stock < 30.
Resposta:
public record Produto(string Nome, double Preco, int Stock);
// Main
var linhas = File.ReadAllLines("produtos.csv").Skip(1); // pula header
var produtos = linhas
.Select(l => l.Split(','))
.Select(c => new Produto(
c[0],
double.Parse(c[1], System.Globalization.CultureInfo.InvariantCulture),
int.Parse(c[2])
))
.ToList();
Console.WriteLine($"Total: {produtos.Count} produtos");
var poucoStock = produtos.Where(p => p.Stock < 30);
foreach (var p in poucoStock)
{
Console.WriteLine($"⚠️ {p.Nome}: apenas {p.Stock} em stock");
}
Exercício 4 · Escrever JSON
Cria 3 instâncias de Aluno(string Nome, double Nota) e guarda-as no ficheiro alunos.json (formato indented). Depois lê o ficheiro de volta e imprime.
Resposta:
using System.Text.Json;
public record Aluno(string Nome, double Nota);
// Main
var alunos = new List<Aluno>
{
new("Ana", 17.5),
new("Bruno", 12.3),
new("Carla", 19.0)
};
// Escrever
var opts = new JsonSerializerOptions { WriteIndented = true };
string json = JsonSerializer.Serialize(alunos, opts);
File.WriteAllText("alunos.json", json);
Console.WriteLine("Guardado!");
// Ler
string lido = File.ReadAllText("alunos.json");
var carregados = JsonSerializer.Deserialize<List<Aluno>>(lido);
foreach (var a in carregados ?? new())
{
Console.WriteLine($"{a.Nome}: {a.Nota}");
}
Exercício 5 · Try-catch e excepção custom
Cria IdadeInvalidaException : Exception e usa-a num método ValidarIdade(int idade) que lança a excepção se idade < 0 ou > 150. Demonstra try-catch com 3 casos (válido, negativo, maior que 150).
Resposta:
public class IdadeInvalidaException : Exception
{
public int IdadeInvalida { get; }
public IdadeInvalidaException(int idade, string message)
: base(message)
{
IdadeInvalida = idade;
}
}
public static void ValidarIdade(int idade)
{
if (idade < 0)
throw new IdadeInvalidaException(idade, "Idade não pode ser negativa");
if (idade > 150)
throw new IdadeInvalidaException(idade, "Idade demasiado alta");
}
// Main
int[] testes = { 25, -5, 200 };
foreach (var idade in testes)
{
try
{
ValidarIdade(idade);
Console.WriteLine($"OK: {idade}");
}
catch (IdadeInvalidaException ex)
{
Console.WriteLine($"❌ {ex.IdadeInvalida}: {ex.Message}");
}
}
Exercício 6 · EF Core — setup
Cria um projecto novo e adiciona EF Core SQLite. Define Tarefa (id, descricao, feita) e AppDbContext. Cria as migrations e a BD.
Resposta:
dotnet new console -n TodoApp
cd TodoApp
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet tool install --global dotnet-ef
Tarefa.cs:
public class Tarefa
{
public int Id { get; set; }
public string Descricao { get; set; } = "";
public bool Feita { get; set; }
}
AppDbContext.cs:
using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public DbSet<Tarefa> Tarefas => Set<Tarefa>();
protected override void OnConfiguring(DbContextOptionsBuilder o)
{
o.UseSqlite("Data Source=tarefas.db");
}
}
dotnet ef migrations add Inicial
dotnet ef database update
Verifica que tarefas.db foi criado.
Exercício 7 · EF Core — CRUD
Usando o setup do exercício 6, implementa as 4 operações no Main:
- Inserir 3 tarefas.
- Listar todas.
- Marcar a primeira como feita.
- Apagar a última.
Resposta:
using var db = new AppDbContext();
// CREATE
db.Tarefas.AddRange(
new Tarefa { Descricao = "Estudar C#" },
new Tarefa { Descricao = "Fazer ficha" },
new Tarefa { Descricao = "Apresentar projecto" }
);
db.SaveChanges();
// READ
Console.WriteLine("\n--- Todas ---");
foreach (var t in db.Tarefas)
{
Console.WriteLine($"[{(t.Feita ? "x" : " ")}] {t.Id} - {t.Descricao}");
}
// UPDATE — primeira como feita
var primeira = db.Tarefas.OrderBy(t => t.Id).First();
primeira.Feita = true;
db.SaveChanges();
Console.WriteLine($"\n✓ Marcada como feita: {primeira.Descricao}");
// DELETE — última
var ultima = db.Tarefas.OrderByDescending(t => t.Id).First();
db.Tarefas.Remove(ultima);
db.SaveChanges();
Console.WriteLine($"\n✗ Apagada: {ultima.Descricao}");
// READ de novo
Console.WriteLine("\n--- Depois ---");
foreach (var t in db.Tarefas)
{
Console.WriteLine($"[{(t.Feita ? "x" : " ")}] {t.Id} - {t.Descricao}");
}
Exercício 8 · Testes com xUnit
Cria a classe Calculadora com Somar, Subtrair, Multiplicar, Dividir (lança DivideByZeroException se divisor 0). Cria projecto xUnit e escreve 5 testes (inclui [Theory] para casos múltiplos).
Resposta:
# Estrutura
dotnet new sln -n Calc
dotnet new classlib -n Calc.Lib
dotnet new xunit -n Calc.Tests
dotnet sln add Calc.Lib Calc.Tests
cd Calc.Tests
dotnet add reference ../Calc.Lib
Calc.Lib/Calculadora.cs:
namespace Calc.Lib;
public class Calculadora
{
public double Somar(double a, double b) => a + b;
public double Subtrair(double a, double b) => a - b;
public double Multiplicar(double a, double b) => a * b;
public double Dividir(double a, double b)
{
if (b == 0)
throw new DivideByZeroException("Não dividir por zero");
return a / b;
}
}
Calc.Tests/CalculadoraTest.cs:
using Calc.Lib;
using Xunit;
public class CalculadoraTest
{
private readonly Calculadora _calc = new();
[Fact]
public void Somar_DoisInteiros_RetornaSoma()
{
Assert.Equal(5, _calc.Somar(2, 3));
}
[Theory]
[InlineData(2, 3, 5)]
[InlineData(0, 0, 0)]
[InlineData(-1, 1, 0)]
[InlineData(-5, -3, -8)]
public void Somar_VariosCasos(double a, double b, double esperado)
{
Assert.Equal(esperado, _calc.Somar(a, b));
}
[Fact]
public void Multiplicar_PorZero_RetornaZero()
{
Assert.Equal(0, _calc.Multiplicar(5, 0));
}
[Fact]
public void Dividir_PorZero_LancaExcepcao()
{
Assert.Throws<DivideByZeroException>(() => _calc.Dividir(10, 0));
}
[Fact]
public void Dividir_Normal_RetornaQuociente()
{
Assert.Equal(2.5, _calc.Dividir(5, 2));
}
}
Correr:
dotnet test