Cheatsheet C#
Linguagem de programação da Microsoft
C#
Sintaxe Básica
Variáveis
int idade = 30; string nome = "Ana"; double preco = 9.99; bool ativo = true; char letra = 'A'; // Tipo inferido pelo compilador var total = 100; // int var mensagem = "Olá"; // string
Em C# declaras o tipo antes do nome. O var deixa o compilador inferir o tipo a partir do valor.
Interpolação de strings
string nome = "Ana";
int idade = 30;
string s = $"A {nome} tem {idade} anos";
string calc = $"Daqui a 5 anos: {idade + 5}";
// Formatação
double preco = 9.5;
string p = $"Preço: {preco:F2}€"; // 9,50€O prefixo $ permite inserir expressões dentro de { }. Os formatos (ex.: F2) controlam as casas decimais.
Comentários
// Comentário de uma linha /* Comentário de várias linhas */ /// <summary> /// Documentação XML (aparece no IntelliSense) /// </summary> int Somar(int a, int b) => a + b; // TODO: tarefa pendente // NOTE: nota importante
// comenta uma linha e /* */ várias. O /// gera documentação XML que o Visual Studio mostra no IntelliSense.
Tipos de dados
// Números inteiros int n = 10; // 32 bits long grande = 9999999999L; short pequeno = 100; // Números com vírgula float f = 3.14f; double d = 3.14; decimal preco = 9.99m; // dinheiro // Outros bool ok = true; char c = 'A'; string s = "texto"; object obj = 1;
Usa decimal para valores monetários (mais preciso). O sufixo (f, m, L) indica o tipo do literal.
Nullable
int? x = null; // int que aceita null
int y = x ?? 0; // 0 se x for null
int z = x!.Value; // força (cuidado!)
// Verificar antes de usar
if (x.HasValue) {
Console.WriteLine(x.Value);
}
string? nome = null; // referência nullablePor defeito, tipos de valor não aceitam null. O ? torna-os nullable. O ?? dá um valor alternativo se for null.
Console: entrada e saída
// Saída
Console.WriteLine("Olá!"); // com quebra de linha
Console.Write("sem quebra");
Console.WriteLine($"Valor: {10:F2}");
// Entrada (devolve sempre string)
string? nome = Console.ReadLine();
Console.WriteLine($"Olá, {nome}");
// Ler uma tecla
var tecla = Console.ReadKey();Console.WriteLine escreve e muda de linha; Console.Write não muda. Console.ReadLine() lê o que foi digitado (sempre como string).
Constantes
// Constante de compilação
const int Max = 100;
const string App = "Aula";
// Só de leitura (definida em runtime)
readonly int _id;
public Pessoa(int id) {
_id = id;
}const é fixa em tempo de compilação. readonly pode ser definida uma vez (no construtor) e depois não muda mais.
Conversões
// String para número
int n = int.Parse("123");
double d = double.Parse("3.14");
// Mais seguro (não lança exceção)
if (int.TryParse("abc", out int r)) {
Console.WriteLine(r);
}
// Entre tipos
double dd = Convert.ToDouble("3.14");
int i = (int)3.9; // cast: 3 (trunca)Usa TryParse em vez de Parse quando a entrada pode ser inválida — devolve false em vez de lançar exceção.
Verbatim strings (@)
// Sem @: precisa de escapar as barras string caminho = "C:\\pastas\\ficheiro.txt"; // Com @: literal, como está escrito string caminho2 = @"C:\pastas\ficheiro.txt"; // Várias linhas preservadas string sql = @"SELECT * FROM clientes WHERE ativo = 1";
O prefixo @ cria uma string literal: barras e quebras de linha são mantidas como estão. Ideal para caminhos de ficheiros e SQL.
Operadores
// Aritméticos 10 + 3 // 13 10 / 3 // 3 (inteiro!) 10.0 / 3 // 3.333 10 % 3 // 1 (resto) // Comparação 5 == 5 // true 5 != 3 // true 5 >= 3 // true // Lógicos true && false // false (E) true || false // true (OU) !true // false (NÃO)
Atenção: 10 / 3 dá 3 (divisão inteira). Usa 10.0 / 3 para obter decimais. && é E e || é OU.
Métodos de string úteis
string s = " Olá Mundo ";
s.Trim() // "Olá Mundo"
s.ToUpper() // " OLÁ MUNDO "
s.ToLower()
s.Replace("Mundo", "C#")
s.Split(" ") // array de palavras
s.Contains("Olá") // true
s.Length // 13Strings são imutáveis: os métodos devolvem uma nova string. Trim() remove espaços nas pontas e Split() divide em partes.
Controlo de Fluxo
if / else
int idade = 20;
if (idade >= 18) {
Console.WriteLine("adulto");
} else if (idade >= 13) {
Console.WriteLine("adolescente");
} else {
Console.WriteLine("criança");
}A condição vai entre parênteses e o bloco entre chavetas. O else if encadeia várias condições.
foreach
var frutas = new[] { "maçã", "pera", "uvas" };
foreach (var fruta in frutas) {
Console.WriteLine(fruta);
}
// Com índice (se precisares)
for (int i = 0; i < frutas.Length; i++) {
Console.WriteLine($"{i}: {frutas[i]}");
}O foreach percorre cada elemento de uma coleção sem precisares de índice. Mais simples e legível que o for para percorrer.
Loops aninhados
// Tabela de multiplicação
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
Console.Write($"{i * j,4}");
}
Console.WriteLine();
}
// break 2 não existe em C# — usa uma flag ou método
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
if (i * j > 6) goto fim;
}
}
fim:;Loops dentro de loops percorrem grelhas e matrizes. Para sair de vários níveis, extrai para um método (com return) ou usa uma variável de controlo.
switch
int dia = 1;
switch (dia) {
case 1:
Console.WriteLine("Segunda");
break;
case 2:
Console.WriteLine("Terça");
break;
default:
Console.WriteLine("Outro");
break;
}O switch compara um valor com vários case. Cada caso precisa de break (ou return). O default é o caso padrão.
while / do-while
int x = 5;
while (x > 0) {
Console.WriteLine(x);
x--;
}
// Executa pelo menos uma vez
do {
x++;
} while (x < 10);O while testa a condição antes de executar. O do-while executa primeiro e testa depois — corre sempre pelo menos uma vez.
switch expression (C# 8+)
int dia = 1;
string nome = dia switch {
1 => "Segunda",
2 => "Terça",
3 => "Quarta",
_ => "Outro" // caso padrão
};Versão moderna e compacta do switch. Usa => para devolver o valor e _ como caso padrão. Sem break.
Operador ternário
int idade = 20; string estado = idade >= 18 ? "adulto" : "menor"; // Equivalente a: // if (idade >= 18) estado = "adulto"; // else estado = "menor";
Forma compacta de um if/else numa linha: condição ? valor_se_verdadeiro : valor_se_falso.
for
for (int i = 0; i < 10; i++) {
Console.WriteLine(i); // 0 a 9
}
// Decrescente
for (int i = 10; i > 0; i--) {
Console.WriteLine(i);
}O for tem três partes: inicialização, condição e incremento. Ideal quando sabes quantas vezes queres repetir.
break e continue
for (int i = 0; i < 10; i++) {
if (i == 3) continue; // salta o 3
if (i == 7) break; // pára no 7
Console.WriteLine(i); // 0,1,2,4,5,6
}
foreach (var item in lista) {
if (item == null) continue;
Processar(item);
}continue salta para a próxima iteração; break termina o loop imediatamente. Úteis para ignorar casos ou parar cedo.
Métodos
Método simples
int Somar(int a, int b) {
return a + b;
}
// Sem retorno (void)
void Saudar(string nome) {
Console.WriteLine($"Olá, {nome}!");
}
int r = Somar(2, 3); // 5Um método declara o tipo de retorno, o nome e os parâmetros. Usa void quando não devolve nada.
params
int Somar(params int[] nums) {
return nums.Sum();
}
Somar(1, 2, 3); // 6
Somar(1, 2, 3, 4, 5); // 15
// Também aceita um array
Somar(new[] { 1, 2, 3 });O params permite passar um número variável de argumentos. Por dentro, chegam como um array.
Recursão
// Método que se chama a si próprio
long Fatorial(int n) {
if (n <= 1) return 1; // caso base (obrigatório!)
return n * Fatorial(n - 1);
}
Fatorial(5); // 120
// Fibonacci
int Fib(int n) => n <= 1 ? n : Fib(n - 1) + Fib(n - 2);Um método recursivo chama-se a si próprio. Precisa sempre de um caso base para parar, senão entra em loop infinito (StackOverflowException).
Expression-bodied
// Corpo numa expressão (=>)
int Dobro(int x) => x * 2;
string Saudar(string n) => $"Olá, {n}";
void Ola() => Console.WriteLine("Olá");Para métodos de uma linha, usa => sem chavetas nem return. Mais conciso e legível.
Método assíncrono
async Task<int> ObterAsync() {
await Task.Delay(1000); // simula espera
return 42;
}
// Sem retorno
async Task ImprimirAsync() {
await Task.Delay(500);
Console.WriteLine("feito");
}Métodos com async devolvem Task (sem valor) ou Task<T> (com valor). O await espera sem bloquear a thread.
Métodos de extensão
public static class StringExtensions {
// this no 1º parâmetro = extensão de string
public static string PrimeiraMaiuscula(this string s) {
if (string.IsNullOrEmpty(s)) return s;
return char.ToUpper(s[0]) + s.Substring(1);
}
}
// Uso: parece um método nativo
"olá".PrimeiraMaiuscula(); // "Olá"Um método de extensão adiciona métodos a tipos existentes sem os modificar. Declara-se numa classe static com this no primeiro parâmetro.
Parâmetros opcionais e nomeados
void Ligar(string host, int porta = 3306, int timeout = 30) {
Console.WriteLine($"{host}:{porta}");
}
Ligar("localhost"); // usa defaults
Ligar("localhost", 5432);
Ligar(porta: 5432, host: "db"); // por nomeParâmetros com = valor são opcionais. Podes passar argumentos por nome, em qualquer ordem, para mais clareza.
Métodos locais
int Calcular(int x) {
// Função dentro da função
int Quadrado(int n) => n * n;
return Quadrado(x) + Quadrado(x + 1);
}Podes definir funções dentro de funções. A função local (ex.: Quadrado) só existe dentro do método que a cria.
ref e out
// ref: entra com valor e pode ser alterado
void Incrementar(ref int x) {
x++;
}
int n = 5;
Incrementar(ref n); // n = 6
// out: só devolve valor
bool Tenta(out int resultado) {
resultado = 42;
return true;
}ref passa por referência (lê e escreve). out serve só para devolver valores — o método tem de lhe atribuir valor.
Sobrecarga de métodos
// Mesmo nome, assinaturas diferentes int Somar(int a, int b) => a + b; double Somar(double a, double b) => a + b; int Somar(int a, int b, int c) => a + b + c; Somar(1, 2); // chama a 1ª Somar(1.5, 2.5); // chama a 2ª Somar(1, 2, 3); // chama a 3ª
A sobrecarga permite vários métodos com o mesmo nome (ex.: Somar), desde que os parâmetros difiram (tipo ou quantidade). O compilador escolhe o certo.
Classes e Objetos
Classe com propriedades
public class Pessoa {
public string Nome { get; set; }
public int Idade { get; set; }
}
var p = new Pessoa {
Nome = "Ana",
Idade = 30
};
Console.WriteLine(p.Nome);As auto-propriedades ({ get; set; }) criam o campo interno automaticamente. A sintaxe new { ... } inicializa as propriedades.
Enum
enum Estado { Ativo, Inativo, Suspenso }
Estado e = Estado.Ativo;
if (e == Estado.Ativo) {
Console.WriteLine("ativo");
}
// Converter
Estado doTexto = Enum.Parse<Estado>("Inativo");Um enum define um conjunto de constantes nomeadas. Torna o código mais legível do que usar números (0, 1, 2...).
Indexadores
public class Semana {
private readonly string[] _dias = new string[7];
// Aceder com [] como um array
public string this[int indice] {
get => _dias[indice];
set => _dias[indice] = value;
}
}
var s = new Semana();
s[0] = "Segunda";
Console.WriteLine(s[0]);Um indexador (this[...]) permite aceder a um objeto com a sintaxe de array (obj[0]). Útil para classes que envolvem coleções.
Construtor
public class Pessoa {
public string Nome { get; }
public int Idade { get; }
public Pessoa(string nome, int idade) {
Nome = nome;
Idade = idade;
}
}
var p = new Pessoa("Ana", 30);O construtor tem o mesmo nome da classe e corre ao criar o objeto. Propriedades só com get são definidas aqui (imutáveis).
Static
public static class Matematica {
public static int Dobro(int x) => x * 2;
public const double Pi = 3.14159;
}
// Usa sem criar instância
int r = Matematica.Dobro(5);Membros static pertencem à classe, não a uma instância. Uma classe static só tem membros estáticos e não pode ser instanciada.
required (C# 11)
public class Cliente {
public required string Nome { get; init; }
public required string Email { get; init; }
public string? Telefone { get; init; } // opcional
}
// Obriga a definir Nome e Email
var c = new Cliente {
Nome = "Ana",
Email = "ana@mail.com"
};
// new Cliente(); // ERRO: faltam propriedades requiredO modificador required obriga a definir essas propriedades no inicializador. O compilador avisa se esqueceres alguma — evita objetos incompletos.
Construtor primário (C# 12)
public class Pessoa(string nome, int idade) {
public string Nome => nome;
public int Idade => idade;
public string Apresentar() => $"Sou {nome}";
}O construtor primário declara os parâmetros (ex.: nome, idade) logo na classe. Ficam disponíveis em todo o corpo. Sintaxe bem mais curta.
Struct
public struct Ponto {
public int X { get; set; }
public int Y { get; set; }
}
var p = new Ponto { X = 1, Y = 2 };Uma struct é um tipo de valor (como int), copiada ao atribuir. Ideal para dados pequenos e simples; classes são tipos de referência.
Record
public record Ponto(int X, int Y);
var p = new Ponto(1, 2);
var p2 = p with { X = 5 }; // cópia com X=5
Console.WriteLine(p); // Ponto { X = 1, Y = 2 }
Console.WriteLine(p == new Ponto(1, 2)); // trueUm record é um tipo imutável com igualdade por valor (compara o conteúdo, não a referência). O with cria uma cópia alterada.
Propriedades init-only
public class Pessoa {
public string Nome { get; init; }
public int Idade { get; init; }
}
// Só podem ser definidas na criação
var p = new Pessoa {
Nome = "Ana",
Idade = 30
};
// p.Nome = "Rui"; // ERRO de compilação!O modificador init (C# 9+) permite definir a propriedade apenas na inicialização. Depois disso fica imutável — ideal para objetos só de leitura.
Herança e Interfaces
Herança
public class Animal {
public virtual void Falar() {
Console.WriteLine("...");
}
}
public class Cao : Animal {
public override void Falar() {
Console.WriteLine("Au!");
}
}Uma classe herda de outra com :. O método virtual na base pode ser override (substituído) na derivada.
sealed
public sealed class Final {
// não pode ser herdada
}
// Também pode selar um método
public class A {
public sealed override void Falar() { }
}Uma classe sealed não pode ser herdada. Um método sealed override impede que seja substituído mais abaixo na hierarquia.
Classe abstrata
public abstract class Forma {
public abstract double Area();
public void Descrever() {
Console.WriteLine($"Área: {Area()}");
}
}
public class Circulo : Forma {
public override double Area() => 3.14;
}Uma classe abstract não pode ser instanciada e pode ter métodos abstratos (sem corpo) que as filhas são obrigadas a implementar.
is e as
object obj = new Cao();
// is: verifica o tipo (e pode extrair)
if (obj is Cao cao) {
cao.Falar();
}
// as: converte (null se falhar)
Cao? c = obj as Cao;
if (c != null) c.Falar();is verifica se um objeto é de um tipo (e extrai a variável). as tenta converter e devolve null se não conseguir.
Interface
public interface IAnimal {
void Falar();
string Nome { get; }
}
public class Cao : IAnimal {
public string Nome => "Rex";
public void Falar() => Console.WriteLine("Au");
}Uma interface é um contrato: define o que uma classe deve ter, sem implementar. Uma classe pode implementar várias interfaces.
Interfaces com implementação default
public interface ILog {
void Escrever(string msg);
// Implementação por defeito (C# 8+)
void Erro(string msg) {
Escrever($"ERRO: {msg}");
}
}Desde o C# 8, interfaces podem ter métodos com corpo (implementação default). As classes herdam-nos sem precisar de os reimplementar.
base
public class Animal {
public Animal(string nome) {
Nome = nome;
}
public string Nome { get; }
}
public class Cao : Animal {
public Cao(string nome) : base(nome) {
// chama o construtor da base
}
}O base(...) chama o construtor da classe pai. O base.Método() chama um método da classe pai.
Polimorfismo na prática
public abstract class Animal {
public abstract string Falar();
}
public class Cao : Animal {
public override string Falar() => "Au!";
}
public class Gato : Animal {
public override string Falar() => "Miau!";
}
// Cada objeto responde à sua maneira
var animais = new List<Animal> { new Cao(), new Gato() };
foreach (var a in animais) {
Console.WriteLine(a.Falar()); // Au! / Miau!
}Polimorfismo é tratar objetos diferentes de forma uniforme: a variável é do tipo Animal, mas cada subclasse executa o seu próprio override.
Coleções e LINQ
List<T>
var lista = new List<int> { 1, 2, 3 };
lista.Add(4); // adiciona
lista.Remove(1); // remove o valor 1
lista.RemoveAt(0); // remove pelo índice
lista.Contains(2); // true
lista.Count; // quantidade
int primeiro = lista[0];A List<T> é uma lista dinâmica (cresce sozinha). O <T> define o tipo dos elementos. Mais flexível que um array.
LINQ: agregação
var nums = new List<int> { 1, 5, 8, 12 };
nums.Count(x => x > 5) // 2
nums.Any(x => x > 10) // true (existe?)
nums.All(x => x > 0) // true (todos?)
nums.FirstOrDefault(x => x > 5) // 8
nums.Sum() // 26
nums.Average() // 6.5
nums.Max() // 12Operações que reduzem a coleção a um valor: Count, Sum, Average, Max. Any/All testam condições.
LINQ: GroupBy
var pessoas = new[] {
new { Nome = "Ana", Cidade = "Lisboa" },
new { Nome = "Rui", Cidade = "Porto" },
new { Nome = "Eva", Cidade = "Lisboa" },
};
var porCidade = pessoas
.GroupBy(p => p.Cidade)
.Select(g => new {
Cidade = g.Key,
Total = g.Count(),
Nomes = string.Join(", ", g.Select(x => x.Nome))
});GroupBy agrupa elementos por uma chave. Cada grupo (IGrouping) tem a Key e os elementos — usa Count(), Select, etc. dentro de cada um.
Dictionary
var dict = new Dictionary<string, int> {
["um"] = 1,
["dois"] = 2
};
dict["tres"] = 3; // adicionar
int v = dict["um"]; // aceder
dict.TryGetValue("um", out int r); // seguro
dict.ContainsKey("um"); // trueO Dictionary guarda pares chave/valor com acesso rápido pela chave. O TryGetValue evita erros se a chave não existir.
LINQ: ordenação
var pessoas = new List<Pessoa> { /* ... */ };
var ordenadas = pessoas
.OrderBy(p => p.Nome) // ascendente
.ThenByDescending(p => p.Idade) // depois idade (desc)
.ToList();
var top = pessoas
.OrderByDescending(p => p.Idade)
.Take(3);OrderBy ordena de forma ascendente e OrderByDescending descendente. ThenBy adiciona desempates. Take limita os resultados.
LINQ: Distinct, Union e Except
var a = new[] { 1, 2, 3, 3, 4 };
var b = new[] { 3, 4, 5 };
a.Distinct() // 1, 2, 3, 4 (sem repetidos)
a.Union(b) // 1, 2, 3, 4, 5 (junção sem duplicados)
a.Intersect(b) // 3, 4 (em comum)
a.Except(b) // 1, 2 (em A mas não em B)Operações de conjuntos: Distinct remove duplicados, Union junta, Intersect devolve os comuns e Except subtrai.
Array
int[] nums = { 1, 2, 3 };
nums[0] = 10;
nums.Length; // 3
// Criar com tamanho fixo
int[] arr = new int[5];
// Percorrer
for (int i = 0; i < nums.Length; i++) {
Console.WriteLine(nums[i]);
}Um array tem tamanho fixo (definido à criação) e é mais rápido que uma lista. Usa Length para o tamanho.
HashSet, Queue e Stack
// HashSet: valores únicos
var set = new HashSet<int> { 1, 2, 2, 3 }; // {1,2,3}
// Queue: FIFO (primeiro a entrar, primeiro a sair)
var fila = new Queue<string>();
fila.Enqueue("A");
fila.Dequeue(); // "A"
// Stack: LIFO (último a entrar, primeiro a sair)
var pilha = new Stack<int>();
pilha.Push(1);
pilha.Pop(); // 1HashSet guarda valores únicos. Queue é uma fila (FIFO) e Stack uma pilha (LIFO). Cada uma serve um padrão de acesso.
LINQ: First, Single e Last
var nums = new[] { 1, 2, 3, 4 };
nums.First(x => x > 2) // 3
nums.FirstOrDefault(x => x > 9) // 0 (não lança erro)
nums.Single(x => x == 4) // 4 (erro se houver 2)
nums.Last() // 4
nums.ElementAt(1) // 2
// Prefere as versões OrDefault para evitar exceçõesFirst devolve o primeiro, Last o último, Single o único (lança erro se houver vários). As versões OrDefault devolvem o valor padrão em vez de lançar exceção.
LINQ: Where / Select
using System.Linq;
var nums = new List<int> { 1, 5, 8, 12, 3 };
var resultado = nums
.Where(x => x > 5) // filtra: 8, 12
.Select(x => x * 2) // transforma: 16, 24
.ToList();O LINQ faz consultas sobre coleções. Where filtra e Select transforma cada elemento. Encadeia-se em cadeia.
IEnumerable vs List
// IEnumerable: só leitura, avaliação preguiçosa
IEnumerable<int> Pares() {
yield return 2;
yield return 4;
}
// List: em memória, permite Add/Remove
var lista = new List<int> { 1, 2, 3 };
// LINQ devolve IEnumerable (não executa já)
var consulta = lista.Where(x => x > 1);
var materializada = consulta.ToList(); // executa agoraIEnumerable<T> é só de leitura e avalia de forma preguiçosa (yield). List<T> está em memória e permite alterações. ToList() materializa uma consulta.
LINQ: Join
var clientes = new[] { new { Id = 1, Nome = "Ana" } };
var pedidos = new[] { new { ClienteId = 1, Total = 50 } };
var resultado = clientes.Join(
pedidos,
c => c.Id, // chave de clientes
p => p.ClienteId, // chave de pedidos
(c, p) => new { c.Nome, p.Total }
);
// Alternativa legível (query syntax)
var q = from c in clientes
join p in pedidos on c.Id equals p.ClienteId
select new { c.Nome, p.Total };Join cruza duas coleções por uma chave comum (como um JOIN de SQL). A query syntax com join ... on ... equals é mais legível.
Exceções
try / catch / finally
try {
int r = 10 / 0;
} catch (DivideByZeroException ex) {
Console.WriteLine($"Erro: {ex.Message}");
} catch (Exception ex) {
Console.WriteLine("Outro erro");
} finally {
Console.WriteLine("executa sempre");
}O try testa código; o catch trata o erro (podes capturar tipos específicos). O finally corre sempre, haja erro ou não.
Exceção personalizada
public class SaldoInsuficienteException : Exception {
public SaldoInsuficienteException(string msg)
: base(msg) { }
}
throw new SaldoInsuficienteException(
"Saldo insuficiente");Cria exceções próprias herdando de Exception. Útil para erros específicos da tua aplicação, com nomes claros.
throw
void DefinirIdade(int idade) {
if (idade < 0) {
throw new ArgumentException(
"A idade não pode ser negativa");
}
}
// Exceções comuns:
// ArgumentNullException
// InvalidOperationExceptionUsa throw para lançar uma exceção quando algo está errado. Isto interrompe a execução e informa quem chamou o método.
try/finally sem catch
var ligacao = AbrirLigacao();
try {
// usa a ligação (pode lançar erro)
ligacao.Executar();
} finally {
// corre SEMPRE (mesmo com exceção)
ligacao.Fechar();
}
// Melhor ainda: using (faz isto sozinho)
using var l2 = AbrirLigacao();
l2.Executar();finally sem catch garante a limpeza de recursos sem engolir o erro (a exceção continua a propagar). O using é a alternativa preferida.
using (recursos)
// Forma clássica
using (var fs = File.OpenRead("f.txt")) {
// usa o ficheiro
} // fecha automaticamente
// Forma moderna (C# 8+)
using var sr = new StreamReader("f.txt");
var texto = sr.ReadToEnd();O using liberta recursos automaticamente (ficheiros, ligações) no fim do bloco, mesmo com erro. Funciona com objetos IDisposable.
Boas práticas com exceções
// 1. Captura tipos específicos primeiro
catch (FileNotFoundException ex) { /* ... */ }
catch (Exception ex) { /* genérico por último */ }
// 2. Relança sem perder a stack trace
try { /* ... */ }
catch (Exception) {
throw; // certo (preserva a origem)
// throw ex; // errado (reinicia a stack)
}
// 3. Nunca faças isto:
catch { } // engole o erro em silêncioCaptura exceções específicas antes das genéricas. Usa throw; (sem a variável) para relançar preservando a stack trace. Nunca ignores erros em silêncio.
Filtros de exceção
try {
// código de risco
} catch (Exception ex) when (ex is IOException) {
Console.WriteLine("Erro de IO");
} catch (Exception ex) when (ex.Message.Contains("timeout")) {
Console.WriteLine("Tempo esgotado");
}A cláusula when filtra exceções por uma condição. Só captura se a condição for verdadeira — mais preciso que vários catch.
Async / Await
async / await
async Task<string> LerAsync() {
string texto = await File.ReadAllTextAsync("f.txt");
return texto;
}
// Chamar
string conteudo = await LerAsync();O async/await permite código assíncrono que parece síncrono. O await espera a tarefa sem bloquear a thread (útil em UI e web).
Cancelamento
var cts = new CancellationTokenSource();
async Task TrabalhoAsync(CancellationToken token) {
while (!token.IsCancellationRequested) {
await Task.Delay(100, token);
// faz trabalho...
}
}
cts.Cancel(); // pede cancelamentoO CancellationToken permite cancelar tarefas a meio. Passa-o para os métodos async e verifica IsCancellationRequested.
Task.WhenAll
async Task BuscarTudo() {
Task<string> t1 = BuscarA();
Task<string> t2 = BuscarB();
await Task.WhenAll(t1, t2); // espera ambas
string a = t1.Result;
string b = t2.Result;
}Task.WhenAll espera várias tarefas em paralelo. Mais rápido que esperar uma de cada vez quando são independentes.
Task.WhenAny
Task<string> t1 = BuscarServidorA();
Task<string> t2 = BuscarServidorB();
// Espera a PRIMEIRA a terminar
Task<string> vencedora = await Task.WhenAny(t1, t2);
string resultado = await vencedora;
// Timeout com WhenAny
var tarefa = BuscarDados();
var timeout = Task.Delay(5000);
if (await Task.WhenAny(tarefa, timeout) == timeout) {
Console.WriteLine("Tempo esgotado");
}Task.WhenAny devolve a primeira tarefa a terminar — útil para redundância (pedir a 2 servidores) ou para implementar timeouts.
Task.Run
async Task Processar() {
// Trabalho pesado noutra thread
int resultado = await Task.Run(() => {
return CalcularPesado();
});
}Task.Run executa trabalho pesado numa thread separada, libertando a thread principal (ex.: para a interface não "congelar").
await foreach (IAsyncEnumerable)
// Produz valores de forma assíncrona
async IAsyncEnumerable<int> GerarAsync() {
for (int i = 0; i < 5; i++) {
await Task.Delay(100);
yield return i;
}
}
// Consome de forma assíncrona
await foreach (var n in GerarAsync()) {
Console.WriteLine(n);
}IAsyncEnumerable<T> é uma sequência assíncrona: cada elemento pode ser produzido com await. Consome-se com await foreach (ideal para streams e paginação).
ValueTask
async ValueTask<int> ObterAsync() {
if (_cache != 0) return _cache; // síncrono
return await BuscarDaRede();
}ValueTask é uma alternativa mais leve a Task quando o resultado muitas vezes já está disponível (evita alocações). Usa com moderação.
async void: cuidados
// ERRADO: não dá para await nem capturar erros
async void Enviar() {
await Task.Delay(100);
throw new Exception(); // crasha o processo!
}
// CERTO: devolve Task
async Task EnviarAsync() {
await Task.Delay(100);
}
await EnviarAsync(); // pode esperar e capturarEvita async void: não dá para fazer await e as exceções escapam (podem crashar a aplicação). Usa async Task — async void só em handlers de eventos.
Dicas e Boas Práticas
Null-coalescing (??)
string nome = null; string s = nome ?? "Anónimo"; // "Anónimo" // Atribui se for null List<int> lista = null; lista ??= new List<int>(); // Encadear string r = a ?? b ?? c ?? "default";
O ?? devolve o valor da esquerda se não for null, senão o da direita. O ??= só atribui se a variável for null.
Properties com lógica
private int _idade;
public int Idade {
get => _idade;
set => _idade = value > 0 ? value : 0;
}
// Só leitura calculada
public string NomeCompleto => $"{Nome} {Apelido}";Uma propriedade pode ter lógica no get/set (validação, cálculo). O value é o valor recebido no set. => cria propriedades só de leitura.
Pattern matching com switch
string Classificar(object obj) => obj switch {
int n when n < 0 => "negativo",
int n => "positivo",
string { Length: 0 } => "string vazia",
string s => $"texto: {s}",
null => "nulo",
_ => "outro tipo"
};
Classificar(-5); // "negativo"
Classificar(""); // "string vazia"switch expression com padrões: combina tipo (int n), propriedade ({ Length: 0 }), condição (when) e null. Bem mais poderoso que o switch tradicional.
Null-conditional (?.)
string nome = null; int? len = nome?.Length; // null (sem erro!) lista?.Add(1); // só adiciona se lista != null // Encadear string cidade = pessoa?.Morada?.Cidade;
O ?. acede a um membro só se o objeto não for null — senão devolve null. Evita o temido NullReferenceException.
using static
using static System.Console;
using static System.Math;
WriteLine("Olá"); // em vez de Console.WriteLine
double r = Sqrt(16); // em vez de Math.SqrtO using static importa os membros estáticos de uma classe. Permite usá-los sem repetir o nome da classe.
Pattern matching
object obj = 42;
// Padrão com condição
if (obj is int n and > 0) {
Console.WriteLine($"positivo: {n}");
}
// Padrão de tipo com propriedade
if (obj is string { Length: > 3 } s) {
Console.WriteLine(s);
}O pattern matching verifica tipo, valor e propriedades numa só expressão. Combina-se com and, or e not.
var vs tipo explícito
// var: tipo inferido var lista = new List<int>(); var cliente = ObterCliente(); // Tipo explícito: mais claro int total = Calcular(); List<int> nums = new(); // target-typed new (C# 9+)
Usa var quando o tipo é óbvio (ex.: new List<int>()). Usa o tipo explícito quando torna o código mais legível. new() omite o tipo à direita.
Strings literais (C# 11+)
string json = """
{
"nome": "Ana",
"idade": 30
}
""";
// Com interpolação
string s = $"""
Olá {nome}
""";Strings com """ (três aspas) preservam quebras de linha e aspas sem escapar. Ideais para JSON, SQL ou HTML no código.
Ranges e índices (C# 8)
var nums = new[] { 0, 1, 2, 3, 4, 5 };
nums[^1] // 5 (último)
nums[^2] // 4 (penúltimo)
nums[1..4] // {1, 2, 3} (índices 1 a 3)
nums[..3] // {0, 1, 2} (do início até 3)
nums[3..] // {3, 4, 5} (do 3 até ao fim)
nums[..] // cópia de todos
Range r = 2..5;
nums[r] // {2, 3, 4}O operador ^ conta a partir do fim (^1 é o último). .. cria um intervalo (Range) para extrair sub-arrays. Disponível desde o C# 8.
Strings
StringBuilder
using System.Text;
var sb = new StringBuilder();
sb.Append("Olá"); // junta sem criar novo objeto
sb.Append(' ');
sb.AppendLine("Mundo"); // + quebra de linha
sb.Insert(0, ">>> "); // insere numa posição
sb.Replace("Mundo", "C#"); // substitui
string resultado = sb.ToString();StringBuilder constrói texto de forma eficiente quando há muitas concatenações (não cria um objeto novo a cada Append, ao contrário do +). Essencial dentro de loops.
Métodos char
char c = '7';
char.IsDigit(c) // true
char.IsLetter('a') // true
char.IsLetterOrDigit('?') // false
char.IsUpper('A') // true
char.IsWhiteSpace(' ') // true
char.ToUpper('a') // 'A'
char.ToLower('A') // 'a'
// Validar se uma string tem dígitos
"abc123".Any(char.IsDigit) // trueA classe char tem utilitários para validar caracteres: IsDigit, IsLetter, IsUpper, IsWhiteSpace. ToUpper/ToLower convertem.
Split e Join
string csv = "maçã,pera,uvas";
string[] frutas = csv.Split(','); // divide
// Ignorar entradas vazias
"a,,b".Split(',', StringSplitOptions.RemoveEmptyEntries);
// Juntar de volta
string unido = string.Join(" | ", frutas);
// "maçã | pera | uvas"
string linha = string.Join(",", new[] { "x", "y" });Split divide uma string por um separador. string.Join junta partes com um delimitador. RemoveEmptyEntries ignora partes vazias.
Comparação de strings
string a = "Olá";
string b = "olá";
a == b // false (maiúsculas importam)
a.Equals(b) // false
// Ignorar maiúsculas/minúsculas
a.Equals(b, StringComparison.OrdinalIgnoreCase); // true
a.StartsWith("Ol") // true
a.EndsWith("á") // true
a.Contains("LÁ", StringComparison.OrdinalIgnoreCase); // truePor padrão, a comparação distingue maiúsculas. Usa StringComparison.OrdinalIgnoreCase para ignorar. StartsWith/EndsWith verificam os extremos.
Substring e IndexOf
string s = "Olá Mundo C#";
s.Substring(4) // "Mundo C#"
s.Substring(4, 5) // "Mundo"
s.IndexOf("Mundo") // 4 (posição)
s.LastIndexOf("o") // última ocorrência
// Extrair domínio de um email
string email = "ana@mail.com";
string dominio = email.Substring(email.IndexOf('@') + 1);Substring extrai uma parte (posição inicial e, opcionalmente, comprimento). IndexOf devolve a posição da primeira ocorrência ou -1 se não existir.
string.Format
double preco = 1234.5;
string.Format("Preço: {0:C}", preco); // moeda
string.Format("{0,10}", "dir"); // alinhado à direita
string.Format("{0} e {1}", "A", "B"); // múltiplos
// Equivalente com interpolação
$"Preço: {preco:C}";
// Formatos numéricos comuns
$"{1234.5:N2}" // "1,234.50"
$"{0.85:P0}" // "85%"
$"{255:X}" // "FF" (hexadecimal)string.Format substitui {0}, {1} pelos valores. Os formatos :C (moeda), :N2 (número), :P0 (percentagem) e :X (hex) controlam a saída.
Padding e Trim
"42".PadLeft(5, '0') // "00042"
"Olá".PadRight(8, '.') // "Olá....."
" texto ".Trim() // "texto"
" texto ".TrimStart() // "texto "
" texto ".TrimEnd() // " texto"
// Alinhamento na interpolação
$"{"esq",-8}|" // "esq |" (esquerda)
$"{42,5}" // " 42" (direita)PadLeft/PadRight preenchem com um carácter até ao comprimento. Trim remove espaços. Na interpolação, ,N alinha (negativo = esquerda).
Concatenação
string a = "Olá";
string b = "Mundo";
string r1 = a + " " + b; // operador +
string r2 = string.Concat(a, " ", b); // método
string r3 = $"{a} {b}"; // interpolação (preferida)
// Juntar várias strings
var partes = new[] { "a", "b", "c" };
string r4 = string.Concat(partes); // "abc"
// Em loops, usa StringBuilderPara casos simples usa + ou interpolação ($""). string.Concat junta várias strings. Dentro de loops, prefere StringBuilder por desempenho.
Genéricos
Classes genéricas
public class Caixa<T> {
public T Conteudo { get; set; }
}
var caixaInt = new Caixa<int> { Conteudo = 42 };
var caixaStr = new Caixa<string> { Conteudo = "olá" };
// O tipo é definido no uso
int n = caixaInt.Conteudo; // int (sem cast)
string s = caixaStr.Conteudo; // stringUma classe genérica usa um parâmetro de tipo <T> definido no momento do uso. O mesmo código funciona para qualquer tipo, com segurança (sem object nem casts).
Interfaces genéricas
// As coleções do .NET assentam em interfaces genéricas IEnumerable<int> seq = new List<int>(); IList<string> lista = new List<string>(); IDictionary<string, int> mapa = new Dictionary<string, int>(); IComparer<int> comp = Comparer<int>.Default; // Programar contra a interface (flexível) int Somar(IEnumerable<int> nums) => nums.Sum();
As coleções do .NET são construídas sobre interfaces genéricas: IEnumerable<T>, IList<T>, IDictionary<K,V>. Programar contra a interface torna o código flexível.
Métodos genéricos
// O tipo é inferido pelo argumento
T Primeiro<T>(T[] itens) => itens[0];
Primeiro(new[] { 1, 2, 3 }); // int
Primeiro(new[] { "a", "b" }); // string
// Trocar dois valores de qualquer tipo
void Trocar<T>(ref T a, ref T b) {
(a, b) = (b, a);
}Um método genérico declara <T> a seguir ao nome. O compilador infere o tipo a partir dos argumentos — não precisas de o indicar. Funciona para qualquer tipo.
Constraints (where)
// T tem de ser uma classe
public class Repositorio<T> where T : class { }
// T tem de implementar IComparable
T Max<T>(T a, T b) where T : IComparable<T> {
return a.CompareTo(b) >= 0 ? a : b;
}
// Várias restrições em simultâneo
void Processar<T>(T item)
where T : class, IDisposable, new() { }where restringe os tipos aceites: class, struct, new() (construtor sem parâmetros), interfaces ou uma classe base. Permite usar membros específicos com segurança.
default(T)
default(int) // 0
default(string) // null
default(bool) // false
default(DateTime) // 01/01/0001
// Essencial em genéricos (tipo desconhecido)
T? Encontrar<T>(List<T> lista, Func<T, bool> filtro) {
foreach (var item in lista) {
if (filtro(item)) return item;
}
return default; // não encontrado
}default(T) devolve o valor padrão de um tipo: 0 para números, null para referências, false para bool. Indispensável em código genérico.
Delegates e Eventos
Expressões lambda
// Lambda: função anónima compacta
Func<int, int> dobro = x => x * 2;
dobro(5); // 10
// Com vários parâmetros
Func<int, int, int> soma = (a, b) => a + b;
// Com corpo (bloco)
Func<int, string> classifica = n => {
if (n >= 10) return "aprovado";
return "reprovado";
};
// Muito usadas no LINQ
nums.Where(x => x > 5);Uma lambda (x => expressão) é uma função anónima e concisa. É muito usada no LINQ e como argumento de métodos que recebem Func/Action.
Delegates personalizados
// Definir um delegate (assinatura de método) public delegate int Operacao(int a, int b); int Somar(int a, int b) => a + b; int Multiplicar(int a, int b) => a * b; Operacao op = Somar; // aponta para Somar op(2, 3); // 5 op = Multiplicar; // agora aponta para Multiplicar op(2, 3); // 6 // Em código novo, prefere Func<int, int, int>
Um delegate define uma assinatura de método que pode ser referenciada. Em código novo prefere Func/Action — delegates próprios ainda são usados em APIs públicas e eventos.
Action
// Action: delegate sem retorno (void)
Action<string> imprimir = msg => Console.WriteLine(msg);
imprimir("Olá");
// Com vários parâmetros
Action<int, int> somar = (a, b) => Console.WriteLine(a + b);
// Passar comportamento como argumento
void Executar(Action acao) => acao();
Executar(() => Console.WriteLine("feito"));
// List.ForEach usa Action
new List<int> { 1, 2 }.ForEach(n => Console.WriteLine(n));Action é um delegate que representa um método sem retorno (void), com 0 a 16 parâmetros. Ideal para passar comportamento como argumento.
Eventos (event)
public class Temporizador {
// Evento (usa o delegate EventHandler)
public event EventHandler? Tick;
public void Disparar() {
Tick?.Invoke(this, EventArgs.Empty);
}
}
// Subscrever (+=) e desubscrever (-=)
var t = new Temporizador();
t.Tick += (s, e) => Console.WriteLine("tick!");
t.Disparar(); // "tick!"event expõe uma notificação que código externo pode subscrever (+=) ou cancelar (-=). Dispara-se com Invoke. É a base do padrão observador no .NET.
Func
// Func: delegate COM retorno (último tipo = retorno)
Func<int> obter = () => 42;
obter(); // 42
Func<int, int> dobro = x => x * 2; // int -> int
Func<string, int, bool> maior = (s, n) => s.Length > n;
// Uso prático: receber um critério
int[] Filtrar(int[] nums, Func<int, bool> criterio) {
return nums.Where(criterio).ToArray();
}
Filtrar(new[] { 1, 5, 8 }, x => x > 3); // {5, 8}Func<T, TResult> é um delegate com retorno — o último tipo genérico é o tipo devolvido. Usa-se para passar transformações e critérios a métodos.
Predicate
// Predicate: recebe T, devolve bool
Predicate<int> isPar = n => n % 2 == 0;
isPar(4); // true
// Métodos de lista que usam Predicate
var nums = new List<int> { 1, 2, 3, 4, 5 };
nums.Find(isPar); // 2 (primeiro par)
nums.FindAll(isPar); // {2, 4}
nums.Exists(isPar); // true
nums.RemoveAll(n => n < 3); // remove 1 e 2Predicate<T> equivale a Func<T, bool>: recebe um valor e devolve true/false. É usado em Find, FindAll, Exists e RemoveAll.
Ficheiros e JSON
Ler ficheiro
using System.IO;
// Ler tudo de uma vez (ficheiros pequenos)
string texto = File.ReadAllText("notas.txt");
// Versão assíncrona (não bloqueia a UI)
string texto2 = await File.ReadAllTextAsync("notas.txt");
// Ler como bytes
byte[] dados = File.ReadAllBytes("imagem.png");File.ReadAllText lê o ficheiro inteiro para uma string. Para ficheiros grandes usa ReadLines ou StreamReader. A versão Async não bloqueia.
Path
string caminho = @"C:\dados\foto.jpg";
Path.GetFileName(caminho) // "foto.jpg"
Path.GetExtension(caminho) // ".jpg"
Path.GetFileNameWithoutExtension(caminho) // "foto"
Path.GetDirectoryName(caminho) // "C:\dados"
// Juntar caminhos com segurança
string novo = Path.Combine("C:", "dados", "f.txt");
// Pasta temporária do sistema
Path.GetTempPath();Path manipula caminhos: GetFileName, GetExtension, GetDirectoryName. Path.Combine junta partes com o separador correto.
Escrever ficheiro
// Sobrescreve (ou cria) o ficheiro
File.WriteAllText("saida.txt", "Olá Mundo");
// Acrescentar ao fim (append)
File.AppendAllText("log.txt", "nova linha\n");
// Versão assíncrona
await File.WriteAllTextAsync("saida.txt", "conteúdo");
// Escrever bytes
File.WriteAllBytes("dados.bin", new byte[] { 1, 2, 3 });File.WriteAllText cria ou sobrescreve; File.AppendAllText acrescenta ao fim. Ambos fecham o ficheiro automaticamente.
Verificar existência
// Verificar antes de usar
if (File.Exists("config.json")) {
var json = File.ReadAllText("config.json");
}
if (!Directory.Exists("logs")) {
Directory.CreateDirectory("logs");
}
// Detalhes sobre o ficheiro
var info = new FileInfo("foto.jpg");
info.Length; // tamanho em bytes
info.LastWriteTime; // última modificaçãoFile.Exists e Directory.Exists verificam a existência. FileInfo dá detalhes como o tamanho (Length) e a data (LastWriteTime).
Linhas (Read/WriteAllLines)
// Ler como array de linhas
string[] linhas = File.ReadAllLines("dados.csv");
// Ler de forma preguiçosa (eficiente em memória)
foreach (var linha in File.ReadLines("grande.csv")) {
if (linha.StartsWith("#")) continue;
}
// Escrever várias linhas (cada elemento = 1 linha)
File.WriteAllLines("lista.txt", new[] { "a", "b", "c" });ReadAllLines devolve um array de linhas. ReadLines lê linha a linha de forma preguiçosa — ideal para ficheiros grandes. WriteAllLines escreve cada elemento como uma linha.
JSON: serializar
using System.Text.Json;
var pessoa = new { Nome = "Ana", Idade = 30 };
// Objeto -> JSON
string json = JsonSerializer.Serialize(pessoa);
// {"Nome":"Ana","Idade":30}
// Com opções (indentado)
var opcoes = new JsonSerializerOptions { WriteIndented = true };
string bonito = JsonSerializer.Serialize(pessoa, opcoes);JsonSerializer.Serialize (de System.Text.Json) converte um objeto em JSON. A opção WriteIndented formata com indentação.
Directory
// Criar pasta
Directory.CreateDirectory("dados/backup");
// Listar ficheiros (com filtro opcional)
string[] ficheiros = Directory.GetFiles("dados");
string[] soTxt = Directory.GetFiles("dados", "*.txt");
// Listar subpastas
string[] pastas = Directory.GetDirectories("dados");
// Apagar pasta (true = recursivo)
Directory.Delete("dados/backup", true);Directory gere pastas: CreateDirectory, GetFiles (aceita filtros como *.txt), GetDirectories e Delete.
JSON: desserializar
using System.Text.Json;
string json = """{"Nome":"Ana","Idade":30}""";
// JSON -> objeto (classe ou record)
var pessoa = JsonSerializer.Deserialize<Pessoa>(json);
Console.WriteLine(pessoa!.Nome); // "Ana"
// Ignorar maiúsculas/minúsculas nos nomes
var opcoes = new JsonSerializerOptions {
PropertyNameCaseInsensitive = true
};JsonSerializer.Deserialize<T> converte JSON num objeto do tipo T. PropertyNameCaseInsensitive aceita nomes de propriedades em qualquer caixa.
Datas e Tempo
DateTime atual
DateTime agora = DateTime.Now; // data e hora local DateTime hoje = DateTime.Today; // só a data (00:00) DateTime utc = DateTime.UtcNow; // hora UTC agora.Year // 2026 agora.Month // 7 agora.Day // 24 agora.Hour // 14 agora.Minute // 30 agora.DayOfWeek // Thursday agora.DayOfYear // dia do ano
DateTime.Now é a data/hora local e DateTime.UtcNow é UTC. Acede aos componentes com Year, Month, Day, Hour e DayOfWeek.
Formatar datas
var d = new DateTime(2026, 7, 24, 15, 30, 0);
d.ToString("dd/MM/yyyy") // "24/07/2026"
d.ToString("dd/MM/yyyy HH:mm") // "24/07/2026 15:30"
d.ToString("yyyy-MM-dd") // "2026-07-24" (ISO)
d.ToString("dddd") // "sexta-feira"
d.ToString("MMM") // "jul"
// Formatos padrão
d.ToShortDateString() // "24/07/2026"
d.ToLongTimeString() // "15:30:00"
// Na interpolação
$"{d:dd/MM/yyyy}";Formata com ToString("..."): dd dia, MM mês, yyyy ano, HH:mm hora. Os mesmos formatos funcionam na interpolação ({d:dd/MM/yyyy}).
Criar datas
// Criar uma data específica var natal = new DateTime(2026, 12, 25); var reuniao = new DateTime(2026, 7, 24, 15, 30, 0); // DateOnly e TimeOnly (C# 10+) var data = new DateOnly(2026, 12, 25); var hora = new TimeOnly(15, 30); // Úteis natal.DayOfWeek // Friday DateTime.DaysInMonth(2026, 2) // 28 (fev)
Cria uma data com new DateTime(ano, mês, dia, hora, min, seg). DateOnly e TimeOnly (C# 10+) representam só a data ou só a hora.
Parse de datas
// Converter string em DateTime
DateTime d = DateTime.Parse("24/07/2026");
// Mais seguro (não lança exceção)
if (DateTime.TryParse("31/02/2026", out var data)) {
Console.WriteLine(data);
} else {
Console.WriteLine("Data inválida");
}
// Formato específico
DateTime.ParseExact("2026-07-24", "yyyy-MM-dd",
CultureInfo.InvariantCulture);DateTime.Parse converte uma string em data. Usa TryParse para entradas inválidas (não lança exceção). ParseExact exige o formato exato.
Somar e subtrair tempo
var hoje = DateTime.Now; hoje.AddDays(7) // daqui a uma semana hoje.AddHours(-2) // há 2 horas hoje.AddMonths(1) hoje.AddMinutes(90) // Diferença entre datas (TimeSpan) var natal = new DateTime(2026, 12, 25); TimeSpan falta = natal - hoje; falta.Days // dias restantes
Soma ou subtrai tempo com AddDays, AddHours, AddMonths — devolvem um novo DateTime (não alteram o original). Subtrair duas datas dá um TimeSpan.
TimeSpan
// Representa uma duração (não uma data) var duracao = new TimeSpan(2, 30, 0); // 2h30m var umDia = TimeSpan.FromDays(1); var meiaHora = TimeSpan.FromMinutes(30); duracao.Hours // 2 duracao.TotalMinutes // 150 // Operações var soma = umDia + meiaHora; var dobro = duracao * 2; // Usar com DateTime DateTime amanha = DateTime.Now + umDia;
TimeSpan representa uma duração. Cria-se com FromDays/FromMinutes ou com o construtor. TotalMinutes dá o valor total nessa unidade.
Tuplos
Tuplos básicos
// Tuplo: agrupar valores sem criar uma classe
var ponto = (1, 2);
ponto.Item1 // 1
ponto.Item2 // 2
var pessoa = ("Ana", 30, true);
pessoa.Item1 // "Ana"
// Com tipos explícitos
(int x, int y) pos = (5, 10);Um tuplo agrupa vários valores numa única estrutura sem criar uma classe. Sem nomes, acede-se por Item1, Item2, etc.
Retornar múltiplos valores
// Método que devolve vários valores
(int min, int max) MinMax(int[] nums) {
return (nums.Min(), nums.Max());
}
var resultado = MinMax(new[] { 3, 1, 7, 4 });
resultado.min // 1
resultado.max // 7
// Ou desconstruir logo
var (menor, maior) = MinMax(new[] { 3, 1, 7, 4 });Os tuplos são ideais para devolver vários valores de um método sem usar parâmetros out nem criar uma classe. Desconstrói-se no destino.
Campos nomeados
// Dar nomes aos campos
var pessoa = (Nome: "Ana", Idade: 30);
pessoa.Nome // "Ana"
pessoa.Idade // 30
// Em retornos de métodos
(string cidade, int cp) ObterLocal() {
return ("Lisboa", 1000);
}
var local = ObterLocal();
local.cidade // "Lisboa"Dá nomes aos campos (Nome:, Idade:) para tornar o tuplo mais legível. Acede-se pelo nome em vez de Item1.
Deconstruction
var pessoa = (Nome: "Ana", Idade: 30);
// Extrair para variáveis separadas
var (nome, idade) = pessoa;
Console.WriteLine($"{nome}, {idade}");
// Ignorar campos com _
var (n, _) = pessoa;
// Desconstruir um KeyValuePair
foreach (var (chave, valor) in dicionario) {
Console.WriteLine($"{chave} = {valor}");
}A desconstrução extrai os campos do tuplo para variáveis individuais: var (nome, idade) = pessoa. Usa _ para ignorar campos.
Trocar valores (swap)
int a = 1, b = 2; // Trocar sem variável temporária (a, b) = (b, a); // agora a = 2, b = 1 string x = "olá", y = "mundo"; (x, y) = (y, x);
Com tuplos, trocar dois valores é trivial: (a, b) = (b, a) faz o swap sem precisar de uma variável temporária.