C# e .NET do Zero a 100 Exemplos: Tipos, Propriedades, Classes, LINQ, Async e Exceções

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

Como um programa C# roda no .NET: o compilador C# transforma o código-fonte em IL dentro de um arquivo .dll, e o compilador JIT do runtime converte esse IL em código de máquina durante a execução
Como um programa C# roda no .NET: o compilador C# transforma o código-fonte em IL dentro de um arquivo .dll, e o compilador JIT do runtime converte esse IL em código de máquina durante a execuçã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.