Partilhar: WhatsApp
aulify · Sebenta
UC · Unidade de Competência · UC00620

Sebenta · C# (UC00620)

Linguagem .NET para backend, desktop e jogos
50h · 4.5 pontos crédito Curso: T. Desenv. Software ↗ Referencial oficial SNQ
Índice

Introdução

C# ("C-sharp") é a linguagem flagship da Microsoft. Lançada em 2002, evoluiu para uma linguagem moderna, expressiva e produtiva. Hoje suporta tudo: backend web (ASP.NET Core), aplicações desktop (WPF, MAUI), jogos (Unity, motor que faz mais de metade dos jogos indie), cloud (Azure), e até apps móveis (.NET MAUI).

Esta sebenta cobre C# moderno (.NET 8, LTS até 2026) com OOP completo, LINQ, Entity Framework e padrões da indústria. C# é uma das skills mais valorizadas em empresas grandes europeias e americanas.

1. C# em contexto

Por que aprender

Vs outras linguagens

vs Java: muito parecido sintacticamente, mas C# tem features modernas há mais tempo (properties, LINQ, async/await, records, pattern matching).

vs Python: muito mais rápido, tipo estático. Menos rápido de prototipar, mais robusto em produção.

vs JavaScript/TypeScript: backend mais rápido, tipos mais robustos. JS continua dominante no frontend.

Onde se usa

2. Setup

Instalar .NET

Baixa o .NET 8 SDK (LTS) de dotnet.microsoft.com/download.

Verifica:

dotnet --version
# 8.0.x

dotnet --info

IDE

Hello World

dotnet new console -n MinhaApp
cd MinhaApp

Program.cs:

Console.WriteLine("Olá mundo!");

Corre:

dotnet run

Top-level statements (.NET 6+): podes escrever código directamente, sem class Program { static void Main() }. Para projectos maiores, ainda existe a forma tradicional.

Estrutura de projecto

MinhaApp/
├── Program.cs              # Entry point
├── MinhaApp.csproj         # Project file (XML)
├── obj/                    # Build temporário
└── bin/                    # Output binário

MinhaApp.csproj:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
</Project>

Comandos essenciais

dotnet new console     # criar projecto console
dotnet new web         # ASP.NET Core
dotnet new classlib    # biblioteca
dotnet run             # correr
dotnet build           # compilar
dotnet test            # correr testes
dotnet add package X   # adicionar dependência
dotnet publish -c Release  # build produção

3. Sintaxe básica

Tipos

// Inteiros
int idade = 25;                  // 32-bit
long pop = 8_000_000_000L;       // 64-bit
short s = 100;
byte b = 255;

// Decimais
double preco = 9.99;             // 64-bit
float taxa = 0.21f;              // 32-bit
decimal valor = 100.50m;         // 128-bit, sem erro de arredondamento (financeiro)

// Outros
bool activo = true;
char letra = 'A';
string nome = "Ana";

// Inferência com var
var x = 42;          // int
var nome2 = "Ana";   // string
var lista = new List<int>();

Nullable types

int? idade = null;    // pode ser null
string? nome = null;  // string nullable (com Nullable enable)

// Operador ?? (coalesce)
string mostrar = nome ?? "Anónimo";

// Operador ?. (null-conditional)
int? len = nome?.Length;

// Operador ??=
nome ??= "Default";  // se null, atribui "Default"

Operadores

// Aritméticos
int soma = 10 + 5;
int div = 10 / 3;       // 3 (int)
double divReal = 10.0 / 3;  // 3.333

// Compostos
int x = 10;
x += 5;   // 15
x *= 2;   // 30

// Comparação
bool eq = (a == b);
bool neq = (a != b);

// Lógicos
bool e = (a && b);
bool ou = (a || b);
bool nao = !a;

// Ternário
string status = idade >= 18 ? "Adulto" : "Menor";

Strings

string nome = "Ana";
int idade = 25;

// Interpolação ($) — preferido
string saudacao = $"Olá, {nome}, tens {idade} anos";

// Composite formatting
string s = String.Format("Olá, {0}, tens {1} anos", nome, idade);

// Métodos comuns
nome.Length;                    // 3
nome.ToUpper();                 // "ANA"
nome.ToLower();
nome.Trim();
nome.Substring(0, 2);           // "An"
nome.Contains("a");             // true (case sensitive)
nome.StartsWith("A");
nome.Replace("a", "e");
nome.Split(',');

// Comparação
nome == "Ana";                  // true (em C# == compara valor para strings)
nome.Equals("ana", StringComparison.OrdinalIgnoreCase);

// StringBuilder (para muitas concatenações)
var sb = new StringBuilder();
sb.Append("Olá ");
sb.Append("mundo");
string resultado = sb.ToString();

Input

Console.Write("Nome: ");
string? nome = Console.ReadLine();

Console.Write("Idade: ");
int idade = int.Parse(Console.ReadLine() ?? "0");

// Mais seguro
if (int.TryParse(Console.ReadLine(), out int idade2)) {
    Console.WriteLine($"Idade: {idade2}");
} else {
    Console.WriteLine("Idade inválida");
}

Estruturas controlo

// If
if (idade >= 18) {
    Console.WriteLine("Adulto");
} else if (idade >= 13) {
    Console.WriteLine("Adolescente");
} else {
    Console.WriteLine("Criança");
}

// Switch tradicional
switch (dia) {
    case 1:
        Console.WriteLine("Segunda");
        break;
    case 2:
        Console.WriteLine("Terça");
        break;
    default:
        Console.WriteLine("Outro");
        break;
}

// Switch expression (C# 8+) — moderno
string tipo = dia switch {
    1 or 2 or 3 or 4 or 5 => "Útil",
    6 or 7 => "Weekend",
    _ => "Inválido"
};

// Pattern matching
object obj = "Ana";
if (obj is string s && s.Length > 2) {
    Console.WriteLine(s);
}

Loops

// For
for (int i = 0; i < 10; i++) {
    Console.WriteLine(i);
}

// Foreach
var nomes = new[] { "Ana", "Bruno", "Carla" };
foreach (var nome in nomes) {
    Console.WriteLine(nome);
}

// While
int x = 0;
while (x < 5) {
    Console.WriteLine(x);
    x++;
}

// Do-while
do {
    // ...
} while (cond);

// Break / continue
for (int i = 0; i < 100; i++) {
    if (i == 50) break;
    if (i % 2 == 0) continue;
    Console.WriteLine(i);
}

Arrays

int[] numeros = new int[5];           // {0, 0, 0, 0, 0}
int[] valores = { 1, 2, 3, 4, 5 };
int[] grande = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

valores[0] = 10;
int n = valores.Length;

// Multi-dimensional
int[,] matriz = { {1, 2, 3}, {4, 5, 6} };
int v = matriz[1, 2];   // 6

// Array de strings
string[] frutas = { "maçã", "uva" };

4. OOP

Classe

public class Pessoa
{
    // Campo privado (convenção _camelCase)
    private string _nome;

    // Propriedade auto (compilador cria campo)
    public int Idade { get; set; }

    // Propriedade com lógica
    public string Nome
    {
        get => _nome;
        set
        {
            if (string.IsNullOrEmpty(value))
                throw new ArgumentException("Nome vazio");
            _nome = value;
        }
    }

    // Read-only (init: pode ser set no construtor/inicializador)
    public DateTime Criada { get; init; } = DateTime.Now;

    // Construtor
    public Pessoa(string nome, int idade)
    {
        Nome = nome;
        Idade = idade;
    }

    // Método
    public void Apresentar()
    {
        Console.WriteLine($"Olá, sou {Nome}, {Idade} anos");
    }

    // Método com retorno
    public bool EhAdulto() => Idade >= 18;
}

Uso:

var p = new Pessoa("Ana", 25);
p.Apresentar();

// Object initializer
var p2 = new Pessoa("Bruno", 30)
{
    // (não funciona aqui porque construtor já recebe — exemplo geral)
};

// Em records ou com sets:
var endereco = new Endereco
{
    Rua = "Av. da Liberdade",
    Numero = 100
};

Encapsulamento

public        // qualquer lado
private       // só dentro da classe
protected     // classe + subclasses
internal      // só dentro do assembly (DLL)
protected internal  // protected + internal

Construtores múltiplos

public class Pessoa
{
    public string Nome { get; set; }
    public int Idade { get; set; }

    public Pessoa() : this("Anónimo", 0) { }

    public Pessoa(string nome) : this(nome, 0) { }

    public Pessoa(string nome, int idade)
    {
        Nome = nome;
        Idade = idade;
    }
}

this(...) chama outro construtor da mesma classe.

Herança

public class Empregado : Pessoa
{
    public double Salario { get; set; }
    public string Cargo { get; set; }

    public Empregado(string nome, int idade, double salario, string cargo)
        : base(nome, idade)
    {
        Salario = salario;
        Cargo = cargo;
    }

    // Sobrescrever método virtual do pai
    public override void Apresentar()
    {
        base.Apresentar();
        Console.WriteLine($"Cargo: {Cargo}, salário: {Salario}€");
    }
}

No pai, o método tem que ser virtual:

public class Pessoa
{
    public virtual void Apresentar() { ... }
}

sealed para impedir override:

public sealed override void Apresentar() { ... }

Ou sealed class para impedir herança.

Polimorfismo

Pessoa p = new Empregado("Ana", 25, 1500, "Dev");
p.Apresentar();  // chama versão de Empregado (dynamic dispatch)

Classes abstractas

public abstract class Figura
{
    public abstract double Area();   // sem implementação

    public void Descrever()
    {
        Console.WriteLine($"Área: {Area()}");
    }
}

public class Circulo : Figura
{
    public double Raio { get; set; }

    public Circulo(double raio) => Raio = raio;

    public override double Area() => Math.PI * Raio * Raio;
}

Não se pode instanciar Figura directamente.

Interfaces

public interface IAnimal
{
    string Nome { get; set; }
    void EmitirSom();

    // Default implementation (C# 8+)
    void Dormir() => Console.WriteLine("Zzz...");
}

public class Cao : IAnimal
{
    public string Nome { get; set; } = "";

    public void EmitirSom() => Console.WriteLine("Au au!");
}

Convenção: nomes de interfaces começam com I (IAnimal, IComparable, IDisposable).

Uma classe pode implementar múltiplas interfaces.

Records (C# 9+)

Para tipos imutáveis de dados:

public record Ponto(double X, double Y);

var p1 = new Ponto(1, 2);
var p2 = new Ponto(1, 2);
Console.WriteLine(p1 == p2);    // true — value equality!
Console.WriteLine(p1);          // Ponto { X = 1, Y = 2 }

// With expression — cópia com modificação
var p3 = p1 with { Y = 5 };

// Deconstrução
var (x, y) = p1;

Records são fantásticos para DTOs, eventos, configurações imutáveis.

Static

public static class Util
{
    public static int Somar(int a, int b) => a + b;
    public static readonly double PI = 3.14159;
}

// Uso (sem instanciar)
int r = Util.Somar(2, 3);

static em métodos = pertencem à classe, não a instâncias.

5. Colecções

using System.Collections.Generic;

List

var nomes = new List<string> { "Ana", "Bruno", "Carla" };
nomes.Add("Diana");
nomes.Remove("Ana");
nomes.RemoveAt(0);

Console.WriteLine(nomes.Count);
Console.WriteLine(nomes[0]);
bool tem = nomes.Contains("Bruno");

nomes.Sort();
nomes.Reverse();

foreach (var n in nomes) {
    Console.WriteLine(n);
}

Dictionary

var idades = new Dictionary<string, int>
{
    ["Ana"] = 25,
    ["Bruno"] = 30
};

// Equivalente:
idades.Add("Carla", 22);
idades["Diana"] = 28;

// Acesso
int idade = idades["Ana"];
if (idades.TryGetValue("Eve", out int idadeEve)) {
    Console.WriteLine(idadeEve);
}

// Iterar
foreach (var kvp in idades) {
    Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}

HashSet

var unicos = new HashSet<int> { 1, 2, 3, 1 };
// {1, 2, 3}

unicos.Add(4);          // true
unicos.Add(1);          // false (já existia)
unicos.Contains(2);     // true

Operações entre sets

var a = new HashSet<int> { 1, 2, 3 };
var b = new HashSet<int> { 2, 3, 4 };

a.UnionWith(b);          // {1, 2, 3, 4}
a.IntersectWith(b);      // {2, 3}
a.ExceptWith(b);         // {1}

6. LINQ

LINQ (Language Integrated Query) é uma das features mais distintivas de C#.

Method syntax (preferido)

using System.Linq;

var numeros = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// Filter (Where)
var pares = numeros.Where(n => n % 2 == 0).ToList();

// Map (Select)
var dobrados = numeros.Select(n => n * 2).ToList();

// Reduce
int soma = numeros.Sum();
int max = numeros.Max();
double avg = numeros.Average();

// Take, Skip
var primeiros5 = numeros.Take(5).ToList();
var depoisDe5 = numeros.Skip(5).ToList();

// Ordenar
var ascending = numeros.OrderBy(n => n).ToList();
var descending = numeros.OrderByDescending(n => n).ToList();

// Distinct
var unicos = new[] { 1, 2, 2, 3, 3, 3 }.Distinct().ToList();

// First / Any / All
int primeiro = numeros.First(n => n > 5);
bool temPar = numeros.Any(n => n % 2 == 0);
bool todosPositivos = numeros.All(n => n > 0);

// Count
int quantosPares = numeros.Count(n => n % 2 == 0);

Cadeias

var resultado = produtos
    .Where(p => p.Preco > 100)
    .OrderByDescending(p => p.Preco)
    .Take(5)
    .Select(p => new { p.Nome, p.Preco })
    .ToList();

GroupBy

var porCategoria = produtos
    .GroupBy(p => p.Categoria)
    .Select(g => new {
        Categoria = g.Key,
        Total = g.Sum(p => p.Preco),
        Quantos = g.Count()
    })
    .ToList();

foreach (var g in porCategoria) {
    Console.WriteLine($"{g.Categoria}: {g.Quantos} produtos, total {g.Total}€");
}

Join

var query = from p in produtos
            join c in categorias on p.CategoriaId equals c.Id
            select new { Produto = p.Nome, Categoria = c.Nome };

Query syntax (SQL-like)

var query = from n in numeros
            where n > 2
            orderby n descending
            select n * 2;

Equivalente a:

var query = numeros
    .Where(n => n > 2)
    .OrderByDescending(n => n)
    .Select(n => n * 2);

7. Excepções

Try-catch-finally

try
{
    int[] arr = { 1, 2, 3 };
    Console.WriteLine(arr[10]);
}
catch (IndexOutOfRangeException ex)
{
    Console.WriteLine($"Índice: {ex.Message}");
}
catch (Exception ex)
{
    Console.WriteLine($"Outro: {ex.Message}");
}
finally
{
    Console.WriteLine("Sempre executa");
}

Throw

public void Retirar(double valor)
{
    if (valor <= 0)
        throw new ArgumentException("Valor inválido", nameof(valor));

    if (valor > _saldo)
        throw new InvalidOperationException("Saldo insuficiente");

    _saldo -= valor;
}

Excepções customizadas

public class SaldoInsuficienteException : Exception
{
    public double SaldoActual { get; }

    public SaldoInsuficienteException(double saldoActual, string message)
        : base(message)
    {
        SaldoActual = saldoActual;
    }
}

// Uso
throw new SaldoInsuficienteException(_saldo, $"Pediste {valor}, tens {_saldo}");

Using (auto-dispose)

using (var file = File.OpenRead("dados.txt"))
{
    // usar file
}  // file.Dispose() chamado automaticamente

// Forma moderna (C# 8+, sem braces)
using var file = File.OpenRead("dados.txt");
// usar file
// dispose no fim do scope

Funciona com qualquer IDisposable (ficheiros, conexões BD, HttpClient, etc).

8. Ficheiros e I/O

using System.IO;

Operações básicas

// Ler tudo
string conteudo = File.ReadAllText("dados.txt");

// Ler linhas
string[] linhas = File.ReadAllLines("dados.txt");

// Escrever (substitui)
File.WriteAllText("output.txt", "Olá");

// Escrever linhas
File.WriteAllLines("output.txt", new[] { "linha 1", "linha 2" });

// Append
File.AppendAllText("log.txt", "Nova entrada\n");

// Existir
bool existe = File.Exists("dados.txt");

// Copiar / mover / apagar
File.Copy("origem.txt", "destino.txt", overwrite: true);
File.Move("a.txt", "b.txt");
File.Delete("temp.txt");

Directórios

Directory.CreateDirectory("pasta/subpasta");
bool existe = Directory.Exists("pasta");
string[] ficheiros = Directory.GetFiles("pasta", "*.txt");
string[] pastas = Directory.GetDirectories(".");
Directory.Delete("pasta", recursive: true);

Streams (ficheiros grandes)

using var reader = new StreamReader("grande.txt");
string? linha;
while ((linha = reader.ReadLine()) != null)
{
    if (linha.Contains("erro"))
        Console.WriteLine(linha);
}

JSON

using System.Text.Json;

var pessoa = new { Nome = "Ana", Idade = 25 };

// Serializar
string json = JsonSerializer.Serialize(pessoa);
File.WriteAllText("pessoa.json", json);

// Indented
var opts = new JsonSerializerOptions { WriteIndented = true };
string bonito = JsonSerializer.Serialize(pessoa, opts);

// Desserializar
public class Pessoa
{
    public string Nome { get; set; } = "";
    public int Idade { get; set; }
}

string lido = File.ReadAllText("pessoa.json");
var p = JsonSerializer.Deserialize<Pessoa>(lido);

9. Entity Framework Core

EF Core é o ORM da Microsoft — abstrai acesso a BD para código C#.

Setup

dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet tool install --global dotnet-ef

Modelo + DbContext

public class Produto
{
    public int Id { get; set; }
    public string Nome { get; set; } = "";
    public double Preco { get; set; }
    public int Stock { get; set; }
}

public class AppDbContext : DbContext
{
    public DbSet<Produto> Produtos => Set<Produto>();

    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        options.UseSqlite("Data Source=loja.db");
    }
}

Migrations

dotnet ef migrations add Inicial
dotnet ef database update

EF gera SQL automaticamente e mantém schema sincronizado.

CRUD

using var db = new AppDbContext();

// Create
db.Produtos.Add(new Produto { Nome = "Caneta", Preco = 1.50, Stock = 100 });
db.SaveChanges();

// Read
var todos = db.Produtos.ToList();
var caros = db.Produtos.Where(p => p.Preco > 10).ToList();
var primeiro = db.Produtos.First();
var porId = db.Produtos.Find(1);

// Update
var p = db.Produtos.Find(1);
if (p != null) {
    p.Preco = 2.00;
    db.SaveChanges();
}

// Delete
db.Produtos.Remove(p);
db.SaveChanges();

Relações

public class Categoria
{
    public int Id { get; set; }
    public string Nome { get; set; } = "";
    public List<Produto> Produtos { get; set; } = new();
}

public class Produto
{
    public int Id { get; set; }
    public string Nome { get; set; } = "";
    public int CategoriaId { get; set; }
    public Categoria? Categoria { get; set; }
}

// Query com Include
var produtosComCategoria = db.Produtos
    .Include(p => p.Categoria)
    .ToList();

10. Async/await

public async Task<string> DownloadAsync(string url)
{
    using var client = new HttpClient();
    return await client.GetStringAsync(url);
}

// Uso
string html = await DownloadAsync("https://example.com");
Console.WriteLine(html.Length);

// Task.WhenAll — paralelo
var urls = new[] { "url1", "url2", "url3" };
string[] resultados = await Task.WhenAll(
    urls.Select(u => DownloadAsync(u))
);

async/await torna I/O não-bloqueante. Essencial em web servers, UIs, mobile.

11. Testes com xUnit

dotnet new xunit -n MinhaApp.Tests
dotnet add reference ../MinhaApp/MinhaApp.csproj
using Xunit;

public class CalculadoraTest
{
    [Fact]
    public void Somar_DoisInteiros_RetornaSoma()
    {
        var calc = new Calculadora();
        Assert.Equal(5, calc.Somar(2, 3));
    }

    [Theory]
    [InlineData(2, 3, 5)]
    [InlineData(0, 0, 0)]
    [InlineData(-1, 1, 0)]
    public void Somar_VariosCasos(int a, int b, int esperado)
    {
        var calc = new Calculadora();
        Assert.Equal(esperado, calc.Somar(a, b));
    }

    [Fact]
    public void Dividir_PorZero_LancaExcepcao()
    {
        var calc = new Calculadora();
        Assert.Throws<DivideByZeroException>(() => calc.Dividir(10, 0));
    }
}

Correr: dotnet test.

12. Boas práticas

Convenções

Princípios

Estilo moderno

// ❌ Antiquado
public string GetNome()
{
    return _nome;
}

// ✅ Property
public string Nome => _nome;

// ✅ Expression-bodied
public int Somar(int a, int b) => a + b;

// ✅ With expression em records
var novo = original with { Preco = 10 };

// ✅ Pattern matching
if (obj is string s) {
    Console.WriteLine(s.Length);
}

// ✅ Switch expression
var msg = status switch {
    Status.Ok => "OK",
    Status.Erro => "Erro",
    _ => "Desconhecido"
};

13. Próximos passos

Glossário

Recursos

C# é uma linguagem que recompensa o investimento. A sintaxe é familiar (parecida com Java/JavaScript), o tooling é excelente, e o ecossistema é gigantesco. Para quem quer trabalhar com Microsoft, jogos (Unity) ou empresas que valorizam tipo estático e tooling robusto — C# é uma escolha sólida.