Partilhar: WhatsApp
aulify
UC UC00620 · T. Desenv. Software

Ficha 02 · C# avançado

LINQ, ficheiros, EF Core, testes
Versão · Aluno
Tempo · 45 minutos
Aluno(a)
Turma
Data

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