Sebenta · C# (UC00620)
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
- Cross-platform desde .NET Core (2016): Windows, Linux, Mac.
- Performance: rival de Java, muito superior a Python/Node em CPU-bound.
- Sintaxe moderna: records, patterns, nullable types, async/await.
- Ecossistema: NuGet (50M+ packages), tooling top (Visual Studio).
- Microsoft + Unity = empregos garantidos.
- ASP.NET Core: dos frameworks web mais rápidos no mercado.
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
- Backend enterprise: ASP.NET Core (Microsoft, Stack Overflow).
- Jogos: Unity (maior motor 2D/3D do mundo).
- Desktop: WPF (Windows), .NET MAUI (cross-platform).
- Mobile: Xamarin/MAUI (alternativa a React Native).
- Cloud: Azure Functions, Azure App Service.
2. Setup
Instalar .NET
Baixa o .NET 8 SDK (LTS) de dotnet.microsoft.com/download.
Verifica:
dotnet --version
# 8.0.x
dotnet --info
IDE
- Visual Studio Community (Windows, gratuito): a melhor experiência C#.
- JetBrains Rider (multi-plataforma, pago): excelente.
- VSCode + extensão "C# Dev Kit" (gratuito, multi-plataforma).
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
- Classes/Records/Interfaces:
PascalCase(Pessoa,IAnimal). - Métodos/Propriedades:
PascalCase(GetNome(),Idade). - Variáveis locais/parâmetros:
camelCase(nome,idade). - Campos privados:
_camelCase(_saldo). - Constantes:
PascalCase(MaxTentativas). - Interfaces: prefixo
I(IComparable).
Princípios
- Properties > campos públicos sempre.
- var quando o tipo é óbvio do lado direito.
- String interpolation ($) > concatenação.
- LINQ > loops imperativos para colecções.
- async/await em I/O.
- using para
IDisposable. - Records para DTOs imutáveis.
- null-conditional (
?.) para evitar NullReferenceException.
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
- ASP.NET Core: framework web (REST APIs, MVC, Razor Pages).
- Blazor: SPAs em C# (alternativa a React).
- MAUI: apps cross-platform (iOS, Android, Windows, Mac).
- Unity: jogos 2D/3D.
- gRPC: serviços de alta performance.
- SignalR: WebSockets/real-time.
- Azure: cloud Microsoft (Functions, App Service).
Glossário
- CLR: Common Language Runtime — runtime do .NET.
- NuGet: package manager (como npm/pip).
- DLL: Dynamic Link Library, output de classlib.
- Assembly: unidade de deployment (DLL ou EXE).
- Namespace: equivalente a package em Java.
- Property: combinação de getter/setter como campo.
- LINQ: Language Integrated Query.
- EF Core: Entity Framework Core (ORM).
- Task: representação de operação async.
Recursos
- Docs oficiais: learn.microsoft.com/dotnet/csharp (excelentes).
- C# Yellow Book (Rob Miles) — livro grátis para iniciantes.
- Microsoft Learn — cursos interactivos gratuitos.
- Pluralsight, Udemy: cursos pagos de qualidade.
- YouTube: Nick Chapsas, IAmTimCorey.
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.