C# e .NET do Zero a 100 Exemplos: Tipos, Propriedades, Classes, LINQ, Async e Exceções
C# é uma linguagem moderna para criar aplicativos Windows, serviços web, jogos e sistemas em nuvem sobre o .NET. Este artigo vai de um programa de uma linha até 100 exemplos executáveis. Você vai estudar variáveis e operadores, decisões e pattern matching, laços, métodos, arrays e listas, strings, classes e objetos, todos os tipos de propriedade, herança e interfaces, records e enums, coleções com LINQ, generics, delegates, código async e exceções. Cada exemplo foi compilado e executado no .NET 8, e a saída real aparece logo após o código.
O que você precisa
Instale o .NET 8 SDK (ou mais recente) pelo site da Microsoft e verifique com dotnet --version. Qualquer editor de texto serve, e o Visual Studio Code com o C# Dev Kit é uma boa opção gratuita. Não é preciso experiência em programação, mas o primeiro exemplo supõe que você saiba o que são números e palavras.
Como executar um exemplo
Crie um projeto de console com dotnet new console -o demo, substitua o arquivo Program.cs pelo código do exemplo e execute dotnet run dentro da pasta do projeto. A saída mostrada no artigo vem de uma execução real no .NET 8.
Ilustração
Tópico 1: Olá, variáveis e tipos
Todo programa C# começa com instruções executadas de cima para baixo. Estes exemplos mostram como imprimir texto, guardar valores em variáveis e escolher o tipo certo para cada valor.
Instruções de nível superior e saída
O C# moderno permite que um arquivo contenha instruções diretamente, sem uma classe ou um método Main. Console.WriteLine imprime uma linha; Console.Write continua na mesma linha. $"..." é uma string interpolada, que insere variáveis no texto entre chaves.
Tipos de valor e seus intervalos
int guarda inteiros de cerca de mais ou menos 2 bilhões, long guarda números bem maiores, e byte guarda valores de 0 a 255. double e float guardam decimais com precisão limitada, então 0.1 + 0.2 não é exatamente igual a 0.3. decimal guarda até 28 dígitos significativos com exatidão, e por isso é a escolha habitual para dinheiro.
var, const e constantes
var pede ao compilador que deduza o tipo a partir do valor, então var n = 10; continua sendo int. const cria um valor fixo em tempo de compilação. Tipos explícitos deixam o código mais claro em programas grandes, e var o torna mais curto quando o tipo é óbvio.
Exemplo 1: Olá, mundo
Imprima uma linha de texto no console.
Console.WriteLine("Hello, C#!");
Saída:
Hello, C#!
Exemplo 2: Write e WriteLine
Write continua na mesma linha; WriteLine passa para a linha seguinte.
Console.Write("Hello, ");
Console.WriteLine("world!");
Console.WriteLine("Two lines");
Saída:
Hello, world!
Two lines
Exemplo 3: var e tipo explícito
Declare uma variável com tipo, ou deixe o compilador deduzi-lo com var.
int count = 3;
var name = "Ada";
Console.WriteLine($"{name} has {count} items");
Saída:
Ada has 3 items
Exemplo 4: Tipos inteiros
int, long e byte guardam intervalos de inteiros diferentes.
int a = 2_000_000_000;
long b = 9_000_000_000L;
byte c = 255;
Console.WriteLine($"{a} {b} {c}");
Console.WriteLine(int.MaxValue);
Saída:
2000000000 9000000000 255
2147483647
Exemplo 5: Tipos decimais
double e float guardam decimais com precisão limitada.
double d = 3.14159;
float f = 2.5f;
Console.WriteLine(d * 2);
Console.WriteLine(f / 2);
Saída:
6.28318
1.25
Exemplo 6: decimal para dinheiro
decimal guarda os dígitos decimais com exatidão, o que é adequado para valores monetários.
decimal price = 19.99m;
decimal total = price * 3;
Console.WriteLine($"Total: {total}");
Saída:
Total: 59.97
Exemplo 7: bool
true e false, combinados com && e !.
bool isOpen = true;
bool isLate = false;
Console.WriteLine(isOpen && !isLate);
Saída:
True
Exemplo 8: char
Um caractere cujo código pode ser lido ao convertê-lo para int.
char letter = 'A';
char next = (char)(letter + 1);
Console.WriteLine($"{letter} {next} {(int)letter}");
Saída:
A B 65
Exemplo 9: const
Um valor definido em tempo de compilação.
const double Pi = 3.14159;
Console.WriteLine($"Area of unit circle: {Pi}");
Saída:
Area of unit circle: 3.14159
Exemplo 10: Alinhamento e formatação
Dê largura a textos e números dentro de uma string interpolada.
string item = "Coffee";
decimal price = 45.5m;
Console.WriteLine($"{item,-10}|{price,8:F2}");
Saída:
Coffee | 45.50
Tópico 2: Operadores e conversões
Operadores combinam valores. Converter entre tipos é uma tarefa comum, e o C# oferece formas seguras e formas diretas de fazer isso.
Aritmética e divisão inteira
+, -, *, / e % funcionam como esperado, mas dividir dois int descarta a parte decimal: 7 / 2 resulta em 3. Escreva 7.0 / 2 ou converta um dos operandos para double para manter a parte decimal. ++ e -- alteram a variável antes ou depois de seu valor ser usado, dependendo da posição.
Comparação, lógica e bits
== e != comparam valores, e && e || combinam condições com avaliação de curto-circuito. Operadores bit a bit como & e | atuam sobre bits individuais, e << desloca bits, o que é um jeito rápido de multiplicar por uma potência de dois.
Cast, Parse e TryParse
O cast (int) corta a parte decimal em vez de arredondá-la. Convert.ToInt32 e Math.Round usam regras de arredondamento diferentes. int.Parse lança exceção quando a entrada é inválida, enquanto int.TryParse devolve false, então TryParse é a escolha mais segura para dados digitados pelo usuário.
Exemplo 11: Aritmética
+, -, *, / e % com inteiros.
int a = 17, b = 5;
Console.WriteLine(a + b);
Console.WriteLine(a - b);
Console.WriteLine(a * b);
Console.WriteLine(a / b);
Console.WriteLine(a % b);
Saída:
22
12
85
3
2
Exemplo 12: Divisão inteira
Dividir dois int descarta a parte decimal.
Console.WriteLine(7 / 2);
Console.WriteLine(7.0 / 2);
Console.WriteLine((double)7 / 2);
Saída:
3
3.5
3.5
Exemplo 13: Incremento
++ antes ou depois da variável muda o valor em momentos diferentes.
int n = 5;
Console.WriteLine(n++);
Console.WriteLine(++n);
Console.WriteLine(n);
Saída:
5
7
7
Exemplo 14: Atribuição composta
+=, *= e -= atualizam a variável em um único passo.
int score = 10;
score += 5;
score *= 2;
score -= 4;
Console.WriteLine(score);
Saída:
26
Exemplo 15: Comparação
==, != e >= produzem true ou false.
int x = 4;
Console.WriteLine(x == 4);
Console.WriteLine(x != 4);
Console.WriteLine(x >= 5);
Saída:
True
False
False
Exemplo 16: Operadores lógicos
&&, || e ! combinam condições.
bool hasTicket = true;
bool isAdult = false;
Console.WriteLine(hasTicket && isAdult);
Console.WriteLine(hasTicket || isAdult);
Console.WriteLine(!hasTicket);
Saída:
False
True
False
Exemplo 17: Operadores bit a bit e de deslocamento
&, | e << atuam sobre bits individuais.
int flags = 0b0101;
Console.WriteLine(flags & 0b0100);
Console.WriteLine(flags | 0b0010);
Console.WriteLine(1 << 3);
Saída:
4
7
8
Exemplo 18: Cast e Convert
(int) corta; Math.Round arredonda antes da conversão.
double price = 9.99;
int whole = (int)price;
int rounded = Convert.ToInt32(Math.Round(price));
Console.WriteLine($"{whole} {rounded}");
Saída:
9 10
Exemplo 19: Parse e TryParse
Converta texto em número com segurança usando TryParse.
int a = int.Parse("42");
bool ok = int.TryParse("4x2", out int b);
Console.WriteLine($"{a} {ok} {b}");
Saída:
42 False 0
Exemplo 20: ?? e operador condicional
?? dá um valor padrão para null; ?: escolhe um dentre dois valores.
string? nickname = null;
string shown = nickname ?? "Guest";
int age = 20;
Console.WriteLine($"{shown} {(age >= 18 ? "adult" : "minor")}");
Saída:
Guest adult
Tópico 3: Decisões e pattern matching
Programas escolhem um caminho com if, switch e patterns. Os patterns modernos do C# permitem testar formato e valor em uma única expressão legível.
if e instrução switch
if, else if e else testam as condições de cima para baixo. A instrução switch escolhe um case pelo valor, e cada case precisa de break ou return, para que a execução não passe para o case seguinte por engano.
Expressão switch
A expressão switch devolve um valor e usa => em cada ramo. O ramo _ é o discard pattern, que trata tudo o que não combinou com os anteriores. O compilador avisa quando um ramo nunca pode ser alcançado.
Patterns: relacional, de tipo, de propriedade e de tupla
Um pattern relacional como >= 80 combina um intervalo. Um pattern de tipo testa o tipo em tempo de execução e pode acrescentar uma condição com when. Um pattern de propriedade combina campos de um objeto, como Total: > 100, e um pattern de tupla combina vários valores ao mesmo tempo.
Exemplo 21: if, else if e else
Escolha um ramo entre várias condições.
int temp = 31;
if (temp > 35) Console.WriteLine("Hot");
else if (temp > 25) Console.WriteLine("Warm");
else Console.WriteLine("Cool");
Saída:
Warm
Exemplo 22: Instrução switch
Escolha um case pelo valor; break encerra cada case.
string day = "Sat";
switch (day)
{
case "Sat":
case "Sun":
Console.WriteLine("Weekend");
break;
default:
Console.WriteLine("Weekday");
break;
}
Saída:
Weekend
Exemplo 23: Expressão switch
Devolva um valor diretamente do switch.
int code = 2;
string label = code switch
{
1 => "Draft",
2 => "Published",
_ => "Unknown"
};
Console.WriteLine(label);
Saída:
Published
Exemplo 24: Pattern relacional
Combine intervalos com os patterns >= e <=.
int score = 73;
string grade = score switch
{
>= 80 => "A",
>= 70 => "B",
_ => "C"
};
Console.WriteLine(grade);
Saída:
B
Exemplo 25: Pattern de tipo com when
Combine tipos e adicione condições com when.
object item = 42;
string kind = item switch
{
int n when n > 10 => $"big int {n}",
int n => $"small int {n}",
string s => $"text {s}",
_ => "other"
};
Console.WriteLine(kind);
Saída:
big int 42
Exemplo 26: Pattern is
Teste o tipo e declare a variável em uma única etapa.
object value = "hello";
if (value is string text)
{
Console.WriteLine($"Length {text.Length}");
}
Saída:
Length 5
Exemplo 27: Pattern de propriedade
Combine as propriedades de um objeto.
var order = new { Status = "Paid", Total = 120 };
if (order is { Status: "Paid", Total: > 100 })
Console.WriteLine("Large paid order");
Saída:
Large paid order
Exemplo 28: Pattern de tupla
Combine dois valores ao mesmo tempo.
int a = 3, b = 0;
string result = (a, b) switch
{
(0, 0) => "origin",
(_, 0) => "on x axis",
_ => "elsewhere"
};
Console.WriteLine(result);
Saída:
on x axis
Exemplo 29: Verificação de null
is null e ??= tratam valores ausentes.
string? name = null;
if (name is null) Console.WriteLine("No name");
name ??= "Guest";
Console.WriteLine(name);
Saída:
No name
Guest
Exemplo 30: if aninhado
Um if dentro de outro if verifica uma segunda condição.
int age = 25;
bool hasLicence = true;
if (age >= 18)
{
if (hasLicence) Console.WriteLine("May drive");
}
Saída:
May drive
Tópico 4: Laços
Laços repetem código. Escolha o laço conforme a tarefa: for quando a quantidade é conhecida, while quando uma condição decide, e foreach para visitar cada item.
for, while e do-while
for tem três partes: início, condição e passo. while verifica antes de cada volta, então pode não executar nenhuma vez. do-while executa pelo menos uma vez e depois verifica a condição.
foreach, break e continue
foreach visita cada item de um array ou coleção sem índice. break sai do laço imediatamente, e continue pula para a próxima volta. Use os dois para manter os laços curtos e fáceis de ler.
Laços aninhados e iteradores
Um laço dentro de outro laço monta tabelas ou grades. yield return produz valores um a um a partir de um método, então quem chama pode ler uma sequência longa sem montar a lista inteira na memória.
Exemplo 31: Laço for
Execute o código um número de vezes conhecido.
for (int i = 1; i <= 5; i++)
{
Console.Write(i + " ");
}
Console.WriteLine();
Saída:
1 2 3 4 5
Exemplo 32: Laço while
Repita enquanto uma condição for verdadeira.
int countdown = 3;
while (countdown > 0)
{
Console.WriteLine($"T-{countdown}");
countdown--;
}
Saída:
T-3
T-2
T-1
Exemplo 33: Laço do-while
Execute o corpo primeiro e depois verifique a condição.
int attempts = 0;
do
{
attempts++;
} while (attempts < 0);
Console.WriteLine($"Ran {attempts} time(s)");
Saída:
Ran 1 time(s)
Exemplo 34: Laço foreach
Visite cada item de uma coleção.
string[] fruits = { "apple", "banana", "cherry" };
foreach (string fruit in fruits)
{
Console.WriteLine(fruit.ToUpper());
}
Saída:
APPLE
BANANA
CHERRY
Exemplo 35: break
Saia do laço imediatamente.
for (int i = 0; i < 10; i++)
{
if (i == 4) break;
Console.WriteLine(i);
}
Saída:
0
1
2
3
Exemplo 36: continue
Pule para a próxima volta.
for (int i = 1; i <= 6; i++)
{
if (i % 2 == 0) continue;
Console.WriteLine(i);
}
Saída:
1
3
5
Exemplo 37: Laços aninhados
Um laço dentro de outro monta uma tabela.
for (int row = 1; row <= 3; row++)
{
for (int col = 1; col <= 3; col++)
{
Console.Write($"{row * col,3}");
}
Console.WriteLine();
}
Saída:
1 2 3
2 4 6
3 6 9
Exemplo 38: yield return
Produza valores um a um com um iterador.
foreach (int n in Evens(10))
{
Console.WriteLine(n);
}
static IEnumerable<int> Evens(int max)
{
for (int i = 0; i <= max; i += 2)
{
yield return i;
}
}
Saída:
0
2
4
6
8
10
Tópico 5: Métodos
Um método é um bloco nomeado que recebe entradas e pode devolver um resultado. Um bom método faz uma única tarefa e tem um nome claro.
Parâmetros, retorno e void
Declare o tipo de cada parâmetro e o tipo de retorno. void significa que o método não devolve nada. Parâmetros opcionais têm um valor padrão, e argumentos nomeados como tax: 0.2 deixam a chamada fácil de ler.
ref, out e params
Um parâmetro comum recebe uma cópia. ref passa a própria variável, para que o método possa alterá-la. out devolve valores adicionais, e TryParse usa esse padrão. params aceita qualquer quantidade de argumentos.
Sobrecarga e funções locais
Um método pode ter o mesmo nome quando os tipos dos parâmetros são diferentes, e o compilador escolhe o correto. Funções locais são declaradas dentro de outro método e são úteis para pequenos auxiliares que só esse método usa.
Exemplo 39: Método com retorno
O método recebe entradas e devolve um resultado.
Console.WriteLine(Add(4, 5));
static int Add(int a, int b)
{
return a + b;
}
Saída:
9
Exemplo 40: Método void
Um método que faz um trabalho e não devolve nada.
Greet("Ploy");
static void Greet(string name)
{
Console.WriteLine($"Hello, {name}!");
}
Saída:
Hello, Ploy!
Exemplo 41: Argumentos opcionais e nomeados
Valores padrão e argumentos nomeados deixam a chamada clara.
Console.WriteLine(Price(100));
Console.WriteLine(Price(100, tax: 0.2));
Console.WriteLine(Price(100, discount: 10));
static double Price(double amount, double tax = 0.07, double discount = 0)
{
return amount * (1 - discount / 100) * (1 + tax);
}
Saída:
107
120
96.30000000000001
Exemplo 42: Parâmetro ref
ref passa a própria variável, então a alteração permanece depois da chamada.
int total = 10;
AddTen(ref total);
Console.WriteLine(total);
static void AddTen(ref int value)
{
value += 10;
}
Saída:
20
Exemplo 43: Parâmetro out
out devolve valores adicionais de um método.
if (TryDivide(10, 2, out int result))
{
Console.WriteLine($"Result {result}");
}
static bool TryDivide(int a, int b, out int quotient)
{
if (b == 0) { quotient = 0; return false; }
quotient = a / b;
return true;
}
Saída:
Result 5
Exemplo 44: Parâmetros params
Aceita qualquer quantidade de argumentos.
Console.WriteLine(Sum(1, 2, 3));
Console.WriteLine(Sum());
static int Sum(params int[] numbers)
{
int total = 0;
foreach (int n in numbers) total += n;
return total;
}
Saída:
6
0
Exemplo 45: Sobrecarga de métodos
O mesmo nome de método com tipos de parâmetro diferentes.
Console.WriteLine(Printer.Show(7));
Console.WriteLine(Printer.Show("seven"));
Console.WriteLine(Printer.Show(7.5));
class Printer
{
public static string Show(int x) => $"int {x}";
public static string Show(string x) => $"string {x}";
public static string Show(double x) => $"double {x}";
}
Saída:
int 7
string seven
double 7.5
Exemplo 46: Função local
Uma função declarada dentro de um método.
Console.WriteLine(SumOfSquares(3));
static int SumOfSquares(int n)
{
int total = 0;
for (int i = 1; i <= n; i++) total += Square(i);
return total;
static int Square(int x) => x * x;
}
Saída:
14
Tópico 6: Arrays e listas
Um array tem tamanho fixo. Uma lista cresce conforme você adiciona itens. Ambos guardam valores de um só tipo, e ambos são indexados a partir de zero.
Arrays e sua forma
int[] a = { 1, 2, 3 } cria um array de tamanho fixo. Um array bidimensional int[,] é uma grade. Um array serrilhado int[][] é um array de arrays, cujas linhas podem ter tamanhos diferentes.
List<T> e operações comuns
List<T> adiciona, insere, remove e busca itens, e cresce automaticamente. Count dá a quantidade de itens, Contains verifica se um item existe, e Sort ordena a lista no lugar.
Copiar e buscar
Atribuir um array a outro copia a referência, e não os dados, então use Clone ou Array.Copy para uma cópia independente. Find e FindAll buscam em uma lista com uma condição lambda, como w => w.Length > 4.
Exemplo 47: Array
Uma coleção de tamanho fixo de um único tipo.
int[] scores = { 90, 75, 88 };
Console.WriteLine(scores.Length);
Console.WriteLine(scores[0]);
scores[2] = 95;
Console.WriteLine(string.Join(", ", scores));
Saída:
3
90
90, 75, 95
Exemplo 48: Valores padrão em um array novo
Um array novo é preenchido com valores padrão.
int[] zeros = new int[4];
string[] names = new string[2];
Console.WriteLine(string.Join(",", zeros));
Console.WriteLine(names[0] is null);
Saída:
0,0,0,0
True
Exemplo 49: Array bidimensional
Uma grade de valores com dois índices.
int[,] grid = new int[2, 3];
grid[1, 2] = 7;
Console.WriteLine(grid[1, 2]);
Console.WriteLine(grid.GetLength(1));
Saída:
7
3
Exemplo 50: Array serrilhado
Um array cujas linhas têm tamanhos diferentes.
int[][] rows = { new[] { 1 }, new[] { 2, 3 }, new[] { 4, 5, 6 } };
foreach (int[] row in rows)
{
Console.WriteLine(row.Length);
}
Saída:
1
2
3
Exemplo 51: List: Add, Insert e Remove
Uma List<T> cresce quando você adiciona itens.
var fruits = new List<string> { "apple" };
fruits.Add("banana");
fruits.Insert(0, "mango");
fruits.Remove("apple");
Console.WriteLine(string.Join(", ", fruits));
Saída:
mango, banana
Exemplo 52: List: Count, Contains e IndexOf
Verifique o tamanho da lista e encontre um item.
var numbers = new List<int> { 4, 8, 15 };
Console.WriteLine(numbers.Count);
Console.WriteLine(numbers.Contains(8));
Console.WriteLine(numbers.IndexOf(15));
Saída:
3
True
2
Exemplo 53: List: Sort e Reverse
Ordene os itens no lugar.
var values = new List<int> { 30, 10, 20 };
values.Sort();
Console.WriteLine(string.Join(" ", values));
values.Reverse();
Console.WriteLine(string.Join(" ", values));
Saída:
10 20 30
30 20 10
Exemplo 54: Array.Sort e Array.IndexOf
Ordene um array e encontre a posição de um item.
int[] data = { 5, 2, 9 };
Array.Sort(data);
Console.WriteLine(string.Join(" ", data));
Console.WriteLine(Array.IndexOf(data, 9));
Saída:
2 5 9
2
Exemplo 55: Clone cria uma cópia
O array clonado é separado do original.
int[] original = { 1, 2, 3 };
int[] copy = (int[])original.Clone();
copy[0] = 99;
Console.WriteLine(original[0]);
Console.WriteLine(copy[0]);
Saída:
1
99
Exemplo 56: Find e FindAll
Busque em uma lista com uma condição lambda.
var words = new List<string> { "tea", "coffee", "milk" };
string? first = words.Find(w => w.Length > 4);
Console.WriteLine(first);
Console.WriteLine(words.FindAll(w => w.Length == 3).Count);
Saída:
coffee
1
Tópico 7: Strings
Uma string é uma sequência de caracteres que não pode ser alterada. Os métodos devolvem uma nova string, e StringBuilder ajuda quando você monta um texto dentro de um laço.
Concatenação, Substring e busca
+ concatena strings. Substring corta um texto, IndexOf encontra uma posição e devolve -1 se o texto não existir, e Length conta os caracteres.
Limpar, dividir e juntar
Trim remove espaços nas duas extremidades, Replace troca um texto por outro, e ToUpper muda para maiúsculas. Split divide um texto em um separador, e string.Join junta um array novamente com um separador entre os itens.
Formato e literais de string
Códigos de formato como F2 mostram duas casas decimais, e N0 acrescenta separadores de milhar. O prefixo @ mantém as barras invertidas de um caminho de arquivo, e um raw string literal escrito entre três aspas duplas preserva um texto com várias linhas exatamente como foi escrito.
Exemplo 57: Concatenação de strings
Junte strings com +.
string first = "Hello";
string second = "World";
Console.WriteLine(first + ", " + second + "!");
Saída:
Hello, World!
Exemplo 58: Substring, IndexOf e Length
Corte e procure dentro de um texto.
string text = "C# programming";
Console.WriteLine(text.Length);
Console.WriteLine(text.Substring(0, 2));
Console.WriteLine(text.IndexOf("pro"));
Saída:
14
C#
3
Exemplo 59: Trim, Replace e ToUpper
Limpe e transforme um texto.
string raw = " hello world ";
string cleaned = raw.Trim().Replace("world", "C#");
Console.WriteLine(cleaned.ToUpper());
Saída:
HELLO C#
Exemplo 60: Split e Join
Divida um texto em partes e junte-as novamente.
string csv = "red,green,blue";
string[] parts = csv.Split(',');
Console.WriteLine(parts.Length);
Console.WriteLine(string.Join(" | ", parts));
Saída:
3
red | green | blue
Exemplo 61: StringBuilder
Monte um texto longo sem criar muitas cópias.
var sb = new System.Text.StringBuilder();
for (int i = 1; i <= 3; i++)
{
sb.Append(i).Append(';');
}
Console.WriteLine(sb.ToString());
Saída:
1;2;3;
Exemplo 62: Formatação de números
F2, N0 e formatos personalizados mostram números com clareza.
System.Globalization.CultureInfo.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
double amount = 1234.5;
Console.WriteLine(amount.ToString("F2"));
Console.WriteLine(amount.ToString("N0"));
Console.WriteLine($"{amount:0.000}");
Saída:
1234.50
1,234
1234.500
Exemplo 63: Strings verbatim e raw
@ mantém as barras invertidas; o raw literal guarda um texto com várias linhas.
string path = @"C:\Users\student\notes.txt";
string json = """
{ "name": "Ada" }
""";
Console.WriteLine(path);
Console.WriteLine(json);
Saída:
C:\Users\student\notes.txt
{ "name": "Ada" }
Exemplo 64: Palíndromo com LINQ
Compare uma string com o seu inverso.
string word = "level";
bool palindrome = word.SequenceEqual(word.Reverse());
Console.WriteLine($"{word} palindrome: {palindrome}");
Saída:
level palindrome: True
Tópico 8: Classes e objetos
Uma classe é um molde. Um objeto é uma instância criada a partir dela, com seus próprios dados. A classe é o centro de um programa C#.
Campos, construtores e métodos
Uma classe guarda dados em campos e propriedades, e comportamento em métodos. Um construtor roda quando new cria o objeto e define seus valores iniciais. this se refere ao objeto atual.
Membros estáticos e de instância
Um membro de instância pertence a um objeto. Um membro estático pertence à própria classe e é compartilhado por todos que a usam, como um contador. ToString é um override que define como o objeto aparece como texto.
Inicializador de objeto
new User { Name = "Mali", Age = 22 } cria o objeto e define suas propriedades em um único passo. Inicializadores são comuns em testes e em código de configuração, porque cada valor tem um nome.
Exemplo 65: Classe e objeto
A classe é um molde; new cria um objeto a partir dela.
var book = new Book();
book.Title = "Clean Code";
book.Pages = 464;
Console.WriteLine($"{book.Title} ({book.Pages} pages)");
class Book
{
public string Title = "";
public int Pages;
}
Saída:
Clean Code (464 pages)
Exemplo 66: Construtor
Defina valores quando o objeto for criado.
var p = new Point(3, 4);
Console.WriteLine($"Point at {p.X}, {p.Y}");
class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
}
Saída:
Point at 3, 4
Exemplo 67: Método de instância
Um método que trabalha sobre os dados de um objeto.
var account = new BankAccount(100);
account.Deposit(50);
Console.WriteLine(account.Balance);
class BankAccount
{
public decimal Balance { get; private set; }
public BankAccount(decimal opening) => Balance = opening;
public void Deposit(decimal amount) => Balance += amount;
}
Saída:
150
Exemplo 68: Membros estáticos
Compartilhados pela classe inteira, e não por um objeto.
Counter.Increment();
Counter.Increment();
Console.WriteLine(Counter.Count);
static class Counter
{
public static int Count { get; private set; }
public static void Increment() => Count++;
}
Saída:
2
Exemplo 69: this e ToString
this se refere ao objeto atual; ToString o mostra como texto.
var car = new Car("Civic", 2020);
Console.WriteLine(car);
class Car
{
private readonly string model;
private readonly int year;
public Car(string model, int year)
{
this.model = model;
this.year = year;
}
public override string ToString() => $"{model} ({year})";
}
Saída:
Civic (2020)
Exemplo 70: Inicializador de objeto
Defina propriedades entre chaves ao criar o objeto.
var user = new User { Name = "Mali", Age = 22 };
Console.WriteLine($"{user.Name} is {user.Age}");
class User
{
public string Name { get; set; } = "";
public int Age { get; set; }
}
Saída:
Mali is 22
Tópico 9: Propriedades de todos os tipos
Uma propriedade parece um campo de fora, mas executa código quando é lida ou escrita. O C# tem vários tipos de propriedade, e cada um serve para uma tarefa diferente.
Auto-propriedades e propriedades completas
Uma auto-propriedade como public int Score { get; set; } recebe automaticamente um campo de armazenamento oculto. Uma propriedade completa tem blocos get e set, que permitem verificar ou transformar o valor antes de guardá-lo.
Somente leitura, calculada e init
Uma propriedade que só tem get é somente leitura. Uma propriedade calculada como Area => Width * Height recalcula o valor a cada leitura. Uma propriedade init pode ser definida em um inicializador de objeto e depois fica somente leitura, o que é útil para objetos de configuração.
Membros obrigatórios, anuláveis e indexadores
required força quem cria o objeto a definir o membro. string? indica que a referência pode ser null, e o compilador avisa quando você a usa de forma insegura. Um indexador permite usar o objeto com colchetes, como um array.
Virtual, abstrata e propriedades de interface
Uma propriedade virtual pode ser substituída em uma subclasse com override. Uma propriedade abstrata precisa ser fornecida por cada subclasse. Uma propriedade de interface é uma promessa de que toda classe que implementa a interface terá aquela propriedade.
Exemplo 71: Auto-propriedade
Uma propriedade cujos get e set são criados para você.
var player = new Player();
player.Name = "Nok";
player.Score = 50;
Console.WriteLine($"{player.Name}: {player.Score}");
class Player
{
public string Name { get; set; } = "";
public int Score { get; set; }
}
Saída:
Nok: 50
Exemplo 72: Propriedade completa com validação
Verifique o valor antes de guardá-lo.
var t = new Thermometer();
t.Celsius = 25;
Console.WriteLine(t.Celsius);
t.Celsius = -300;
Console.WriteLine(t.Celsius);
class Thermometer
{
private double celsius;
public double Celsius
{
get => celsius;
set => celsius = value < -273.15 ? -273.15 : value;
}
}
Saída:
25
-273.15
Exemplo 73: Propriedade calculada somente leitura
Um valor calculado a partir de outros toda vez que é lido.
var rect = new Rectangle(4, 5);
Console.WriteLine($"Area {rect.Area}");
class Rectangle
{
public Rectangle(double w, double h)
{
Width = w;
Height = h;
}
public double Width { get; }
public double Height { get; }
public double Area => Width * Height;
}
Saída:
Area 20
Exemplo 74: Propriedade init
Definida no inicializador e depois somente leitura.
var config = new ServerConfig { Host = "localhost", Port = 8080 };
Console.WriteLine($"{config.Host}:{config.Port}");
class ServerConfig
{
public string Host { get; init; } = "";
public int Port { get; init; }
}
Saída:
localhost:8080
Exemplo 75: Membros obrigatórios
O compilador obriga quem chama a definir esses membros.
var emp = new Employee { Id = 7, Name = "Som" };
Console.WriteLine($"{emp.Id} {emp.Name}");
class Employee
{
public required int Id { get; init; }
public required string Name { get; init; }
}
Saída:
7 Som
Exemplo 76: Propriedade anulável
string? permite null; o compilador avisa sobre usos arriscados.
var profile = new Profile();
Console.WriteLine(profile.Nickname ?? "(none)");
profile.Nickname = "Bee";
Console.WriteLine(profile.Nickname.Length);
class Profile
{
public string? Nickname { get; set; }
}
Saída:
(none)
3
Exemplo 77: Indexador
Use o objeto com colchetes, como um array.
var shelf = new Shelf();
shelf[0] = "Novel";
shelf[1] = "Atlas";
Console.WriteLine(shelf[1]);
class Shelf
{
private readonly string[] books = new string[5];
public string this[int index]
{
get => books[index];
set => books[index] = value;
}
}
Saída:
Atlas
Exemplo 78: Propriedade virtual e override
Uma subclasse substitui o comportamento de uma propriedade.
Animal[] pets = { new Dog(), new Animal() };
foreach (Animal pet in pets)
{
Console.WriteLine($"{pet.Kind}: {pet.Sound}");
}
class Animal
{
public virtual string Kind => "Animal";
public virtual string Sound => "...";
}
class Dog : Animal
{
public override string Kind => "Dog";
public override string Sound => "Woof";
}
Saída:
Dog: Woof
Animal: ...
Exemplo 79: Propriedade abstrata
A classe base obriga cada subclasse a fornecer a propriedade.
Shape circle = new Circle(2);
Console.WriteLine($"{circle.Name} area {circle.Area:F2}");
abstract class Shape
{
public abstract string Name { get; }
public abstract double Area { get; }
}
class Circle : Shape
{
private readonly double radius;
public Circle(double r) => radius = r;
public override string Name => "Circle";
public override double Area => Math.PI * radius * radius;
}
Saída:
Circle area 12.57
Exemplo 80: Propriedade de interface
A interface promete uma propriedade a toda classe que a implementa.
ISensor sensor = new TempSensor();
Console.WriteLine($"{sensor.Unit}: {sensor.Reading}");
interface ISensor
{
string Unit { get; }
double Reading { get; }
}
sealed class TempSensor : ISensor
{
public string Unit => "Celsius";
public double Reading => 21.5;
}
Saída:
Celsius: 21.5
Tópico 10: Herança, interfaces e polimorfismo
A herança permite construir uma classe sobre outra. Uma interface descreve o que uma classe é capaz de fazer. O polimorfismo permite que um único trecho de código trabalhe com vários tipos.
Classe base e construtores
Uma subclasse indica a classe base com dois-pontos e pode passar valores ao construtor da classe-mãe com base(...). A herança reaproveita código, mas cadeias profundas são difíceis de mudar, então mantenha a hierarquia rasa.
Classe abstrata e interface
Uma classe abstrata pode guardar código comum e deixar algumas etapas abstratas. Uma interface lista os membros que uma classe deve fornecer. Uma classe pode implementar várias interfaces, mas herda apenas uma classe base.
Polimorfismo e verificação de tipo
Uma lista de objetos Figure pode conter quadrados e triângulos, e chamar Area() executa a versão correta para cada objeto. is e as testam ou convertem tipos com segurança, e sealed impede que uma classe seja herdada.
Exemplo 81: Construtor base e herança
A subclasse passa valores para a classe-mãe com base.
var child = new Student("Ana", 2);
Console.WriteLine($"{child.Name} year {child.Year}");
class Person
{
public string Name { get; }
public Person(string name) => Name = name;
}
class Student : Person
{
public int Year { get; }
public Student(string name, int year) : base(name) => Year = year;
}
Saída:
Ana year 2
Exemplo 82: Modelo com classe abstrata
A classe-mãe controla as etapas; cada filho preenche uma delas.
Report report = new SalesReport();
report.Print();
abstract class Report
{
public void Print()
{
Console.WriteLine("== Report ==");
Console.WriteLine(Body());
}
protected abstract string Body();
}
class SalesReport : Report
{
protected override string Body() => "Sales up 12%";
}
Saída:
== Report ==
Sales up 12%
Exemplo 83: Implementação de interface
Classes diferentes compartilham uma mesma interface.
IGreeter[] greeters = { new English(), new Spanish() };
foreach (IGreeter g in greeters) Console.WriteLine(g.Greet());
interface IGreeter { string Greet(); }
class English : IGreeter { public string Greet() => "Hello"; }
class Spanish : IGreeter { public string Greet() => "Hola"; }
Saída:
Hello
Hola
Exemplo 84: Polimorfismo em uma lista
Uma lista guarda vários tipos, e cada objeto executa a sua própria tarefa.
List<Figure> figures = new() { new Square(3), new Triangle(4, 5) };
foreach (Figure f in figures)
{
Console.WriteLine($"{f.GetType().Name}: {f.Area()}");
}
abstract class Figure
{
public abstract double Area();
}
class Square : Figure
{
private readonly double side;
public Square(double s) => side = s;
public override double Area() => side * side;
}
class Triangle : Figure
{
private readonly double b, h;
public Triangle(double b, double h) { this.b = b; this.h = h; }
public override double Area() => 0.5 * b * h;
}
Saída:
Square: 9
Triangle: 10
Exemplo 85: as e is
Verificação de tipo e conversão com segurança.
object item = "text";
string? s = item as string;
Console.WriteLine(s?.Length);
Console.WriteLine(item is int);
Saída:
4
False
Tópico 11: Records, structs e enums
Esses tipos descrevem dados. Um record é uma classe com igualdade por valor já pronta, um struct é um pequeno tipo de valor, e um enum é um conjunto de constantes nomeadas.
Records
Um record como record PersonRecord(string Name, int Age) ganha automaticamente construtor, propriedades, igualdade e um ToString legível. Dois records com os mesmos valores são iguais. with copia um record e altera apenas os campos que você indicar.
Structs
Um struct é um tipo de valor. Ele é copiado ao ser atribuído e armazenado in-line, o que é adequado para valores pequenos como um ponto ou um par de valores monetários. Mantenha os structs pequenos e imutáveis, porque as cópias podem surpreender.
Enums e flags
Um enum dá nome a números, então Status.Shipped é mais legível do que 5. Um enum marcado com [Flags] pode combinar opções com OR bit a bit, como Access.Read | Access.Write, e HasFlag verifica se uma delas está presente.
Exemplo 86: Record
Um tipo para dados com igualdade por valor embutida.
var a = new PersonRecord("Pim", 30);
var b = new PersonRecord("Pim", 30);
Console.WriteLine(a == b);
Console.WriteLine(a);
record PersonRecord(string Name, int Age);
Saída:
True
PersonRecord { Name = Pim, Age = 30 }
Exemplo 87: Record com a expressão with
Copie um record e altere um campo.
var original = new Settings("dark", 12);
var changed = original with { FontSize = 14 };
Console.WriteLine($"{original.FontSize} {changed.FontSize} {changed.Theme}");
record Settings(string Theme, int FontSize);
Saída:
12 14 dark
Exemplo 88: Struct com operador
Um tipo de valor pequeno que sobrecarrega o operador +.
var v1 = new Vec(1, 2);
var v2 = v1 + new Vec(3, 4);
Console.WriteLine(v2);
struct Vec
{
public int X { get; }
public int Y { get; }
public Vec(int x, int y) { X = x; Y = y; }
public static Vec operator +(Vec a, Vec b) => new Vec(a.X + b.X, a.Y + b.Y);
public override string ToString() => $"({X}, {Y})";
}
Saída:
(4, 6)
Exemplo 89: Enum
Um conjunto de constantes nomeadas.
Status s = Status.Shipped;
Console.WriteLine(s);
Console.WriteLine((int)s);
Console.WriteLine(Enum.Parse<Status>("Pending"));
enum Status { Pending, Paid, Shipped = 5 }
Saída:
Shipped
5
Pending
Exemplo 90: Enum com flags
Combine opções com flags de bits.
Access rights = Access.Read | Access.Write;
Console.WriteLine(rights);
Console.WriteLine(rights.HasFlag(Access.Write));
[Flags]
enum Access { None = 0, Read = 1, Write = 2, Execute = 4 }
Saída:
Read, Write
True
Tópico 12: Coleções, LINQ e generics
Coleções guardam grupos de valores. O LINQ faz consultas sobre elas de forma legível, e os generics permitem que uma classe funcione com vários tipos.
Dictionary, HashSet, Queue e Stack
Dictionary guarda pares chave-valor e busca valores pela chave. HashSet guarda itens únicos. Queue é first in, first out, e Stack é last in, first out. Escolha a que corresponde à forma como você precisa retirar os itens.
Consultas LINQ
Where filtra, Select transforma, OrderBy ordena e GroupBy agrupa. Cada etapa devolve uma nova sequência, e nada é executado até você ler o resultado, o que se chama execução adiada.
Classes genéricas
Uma classe como Box<T> funciona com qualquer tipo T que você indicar entre os sinais de maior e menor. O compilador verifica o tipo, então Box<int> não pode guardar uma string, e não é preciso fazer cast.
Exemplo 91: Dictionary
Guarde valores e busque-os pela chave.
var prices = new Dictionary<string, decimal>
{
["tea"] = 25m,
["cake"] = 60m
};
prices["juice"] = 35m;
Console.WriteLine(prices["cake"]);
Console.WriteLine(prices.ContainsKey("water"));
foreach (var (item, price) in prices)
{
Console.WriteLine($"{item}: {price}");
}
Saída:
60
False
tea: 25
cake: 60
juice: 35
Exemplo 92: HashSet
Uma coleção que não guarda duplicatas.
var tags = new HashSet<string> { "csharp", "dotnet", "csharp" };
Console.WriteLine(tags.Count);
Console.WriteLine(tags.Contains("dotnet"));
Saída:
2
True
Exemplo 93: Queue e Stack
First in, first out; e last in, first out.
var queue = new Queue<string>();
queue.Enqueue("first");
queue.Enqueue("second");
Console.WriteLine(queue.Dequeue());
var stack = new Stack<string>();
stack.Push("a");
stack.Push("b");
Console.WriteLine(stack.Pop());
Saída:
first
b
Exemplo 94: LINQ: Where e Select
Filtre e transforme uma coleção com uma consulta.
int[] numbers = { 1, 2, 3, 4, 5, 6 };
var evenSquares = numbers.Where(n => n % 2 == 0).Select(n => n * n);
Console.WriteLine(string.Join(", ", evenSquares));
Saída:
4, 16, 36
Exemplo 95: LINQ: GroupBy e OrderBy
Agrupe itens por uma chave e ordene os grupos.
var words = new[] { "apple", "avocado", "banana", "blueberry", "cherry" };
var groups = words.GroupBy(w => w[0]).OrderBy(g => g.Key);
foreach (var g in groups)
{
Console.WriteLine($"{g.Key}: {string.Join(", ", g.OrderBy(w => w.Length))}");
}
Saída:
a: apple, avocado
b: banana, blueberry
c: cherry
Exemplo 96: Classe genérica
Uma classe que funciona com o tipo que você indicar entre os sinais de maior e menor.
var intBox = new Box<int>(42);
var textBox = new Box<string>("hi");
Console.WriteLine(intBox.Value + 1);
Console.WriteLine(textBox.Value.ToUpper());
class Box<T>
{
public T Value { get; }
public Box(T value) => Value = value;
}
Saída:
43
HI
Tópico 13: Delegates, código async e exceções
Um delegate guarda um método dentro de uma variável. async e await mantêm o programa responsivo enquanto espera. Exceções informam erros e permitem recuperar-se deles.
Func, Action e lambdas
Func<int, int, int> é um tipo de delegate para um método que recebe dois ints e devolve um int. Action é usado para métodos que não devolvem nada. Uma lambda como (a, b) => a * b é uma forma curta de escrevê-los.
async e await
Um método async devolve um Task, e await pausa esse método sem bloquear a thread. Task.Delay espera sem usar tempo de CPU, e o programa continua quando a espera termina.
try, catch, finally e exceções personalizadas
try envolve o código que pode falhar, catch trata tipos específicos de exceção, e finally sempre é executado, então é ideal para limpeza. Uma exceção personalizada herda de Exception e leva uma mensagem clara para quem a captura.
Exemplo 97: Func e Action
Guarde métodos em variáveis.
Func<int, int, int> multiply = (a, b) => a * b;
Action<string> announce = msg => Console.WriteLine($"[{msg}]");
announce($"result {multiply(6, 7)}");
Saída:
[result 42]
Exemplo 98: async e await
Execute um trabalho sem bloquear o programa.
Console.WriteLine("Start");
int answer = await ComputeAsync();
Console.WriteLine($"Answer {answer}");
static async Task<int> ComputeAsync()
{
await Task.Delay(10);
return 42;
}
Saída:
Start
Answer 42
Exemplo 99: try, catch e finally
Trate um erro e execute a limpeza sempre.
try
{
int[] data = new int[2];
Console.WriteLine(data[5]);
}
catch (IndexOutOfRangeException ex)
{
Console.WriteLine($"Caught: {ex.GetType().Name}");
}
finally
{
Console.WriteLine("Cleanup done");
}
Saída:
Caught: IndexOutOfRangeException
Cleanup done
Exemplo 100: Exceção personalizada
Crie seu próprio tipo de erro com uma mensagem clara.
try
{
Withdraw(20, 50);
}
catch (InsufficientFundsException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
static void Withdraw(decimal balance, decimal amount)
{
if (amount > balance)
throw new InsufficientFundsException(balance, amount);
Console.WriteLine("Withdrawn");
}
class InsufficientFundsException : Exception
{
public InsufficientFundsException(decimal balance, decimal amount)
: base($"Balance {balance} is less than {amount}") { }
}
Saída:
Error: Balance 20 is less than 50
Projeto de prática: Relatório de notas
Junte as ideias em um único programa. Escreva primeiro sozinho e depois compare com os exemplos. Cada etapa usa o trabalho de um ou dois exemplos.
1. Crie um record Student com nome e nota, e depois uma lista com cinco alunos.
2. Use LINQ para guardar os alunos com nota 50 ou mais, ordenados da maior para a menor.
3. Imprima cada aluno aprovado com interpolação de string, com o nome preenchido até uma largura fixa e a nota exibida com F0.
4. Guarde o conceito de cada aluno em um Dictionary<string, string>, usando uma expressão switch: A para 80 ou mais, B para 70 ou mais, C para 50 ou mais, e F para os demais.
5. Crie uma exceção personalizada que seja lançada quando a nota for menor que 0 ou maior que 100, e capture-a em um bloco try que imprima a mensagem.
Próximos passos
Em seguida, crie aplicativos reais com dotnet new console e dotnet new webapi, adicione testes de unidade com xUnit, leia arquivos de configuração e estude o Entity Framework Core para bancos de dados. A saída mostrada para cada exemplo vem de uma execução real no .NET 8, então, se a sua saída for diferente, verifique primeiro a versão do seu SDK.
Resumo
Programas C# são construídos com instruções, métodos, tipos e objetos. Ao longo de 100 exemplos compilados, este artigo cobre variáveis, operadores, decisões e patterns, laços, métodos, arrays, listas, strings, classes, todos os tipos de propriedade, herança e interfaces, records, structs e enums, coleções com LINQ, generics, delegates, código async e exceções. Cada exemplo mostra o código completo e a saída real. A melhor forma de praticar é mudar um valor, prever o resultado e então executar o programa para conferir.