Sintaxe do C#: guia rápido com exemplos do C# 14

A sintaxe do C# ficou mais enxuta a cada versão, e hoje o C# 14 com o .NET 10 permite escrever programas completos com quase nenhum código cerimonial. Este guia reúne o que você usa no dia a dia, da estrutura do projeto ao LINQ e ao async/await, sempre com exemplos curtos e prontos para adaptar.
O conteúdo tem como base o cheat sheet de C# publicado por Khalid Abuhakmeh, complementado pela documentação oficial da Microsoft. Por isso, todos os trechos usam idiomas modernos, como top-level statements, expressões de coleção, records e primary constructors.
O que é a sintaxe do C# moderno
O C# é uma linguagem orientada a objetos criada pela Microsoft que roda sobre o .NET. Em outras palavras, você escreve C# e o .NET fornece o runtime que executa o código e as bibliotecas base de coleções, arquivos, rede e LINQ.
A sintaxe do C# moderno vai muito além de if e for. Na prática, os recursos que tornam a linguagem produtiva no dia a dia são LINQ, async/await, records e pattern matching.
Além disso, o C# é uma linguagem expressiva, com várias formas de resolver o mesmo problema. Por isso, não existe um único jeito certo, e o estilo do código costuma ser uma escolha sua ou do seu time.
A versão atual com suporte de longo prazo é o .NET 10, que traz o C# 14 como padrão. Assim, todos os exemplos deste guia funcionam em um projeto novo criado com o SDK do .NET 10.
Estrutura de um programa e de um projeto
Antes de olhar variáveis e classes, vale entender como um programa e um projeto se organizam. Essa base explica por que o código moderno parece tão curto.
Top-level statements na sintaxe do C#
Com top-level statements, a sintaxe do C# dispensa a classe Program e o método Main. Ou seja, o próprio arquivo passa a ser o ponto de entrada da aplicação.
Console.WriteLine("Hello, World!");
Essa única linha já forma um programa completo e executável. Para criar e rodar o projeto, use a CLI do dotnet:
dotnet new console -o HelloWorld
cd HelloWorld
dotnet run
O comando dotnet new console gera um Program.cs que já contém essa linha. Em seguida, o dotnet run compila e executa o programa.
Projeto, solução e namespaces
Um projeto C# é uma pasta com um arquivo .csproj na raiz. Esse arquivo XML define o tipo de saída, a versão do .NET e os pacotes referenciados.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="4.1.0" />
</ItemGroup>
</Project>
Raramente você edita esse arquivo à mão, porque o dotnet add package e a IDE fazem isso por você. A opção ImplicitUsings adiciona usings comuns automaticamente, enquanto Nullable liga as verificações de tipos de referência anuláveis.
Uma solução agrupa projetos relacionados, como uma API, uma biblioteca compartilhada e um projeto de testes. Você também cria a solução pela CLI:
dotnet new sln -n Shop
dotnet sln add src/Shop.Api src/Shop.Core tests/Shop.Tests
Já o namespace agrupa tipos relacionados e evita colisões de nomes. O formato moderno usa o file-scoped namespace, uma única linha no topo do arquivo, sem chaves.
namespace Shop.Orders;
public class Order
{
public int Id { get; set; }
}
Para importar um namespace, use a diretiva using no topo do arquivo. Quando o mesmo namespace aparece em todo o projeto, um global using resolve isso uma única vez.
global using System.Text;
Variáveis, tipos e operadores na sintaxe do C#
Na sintaxe do C#, você declara uma variável com o tipo explícito ou usa var para que o compilador infira o tipo. Muitos desenvolvedores preferem var pela brevidade, mas a escolha é questão de estilo.
int count = 5; // inteiro de 32 bits
long big = 9_000_000_000; // inteiro de 64 bits
double price = 3.14; // ponto flutuante de 64 bits
decimal money = 9.99m; // 128 bits, ideal para dinheiro
bool isReady = true;
char letter = 'A';
string name = "Ada";
var inferred = "also a string"; // o compilador infere string
| Tipo | Uso indicado | Exemplo |
|---|---|---|
| int | Números inteiros | 42 |
| long | Inteiros grandes | 9_000_000_000 |
| double | Decimais em geral | 3.14 |
| decimal | Dinheiro e valores exatos | 9.99m |
| bool | Verdadeiro ou falso | true |
| char | Um único caractere | 'A' |
| string | Texto | "hello" |
Use decimal em valores monetários, porque o double não representa números como 0.1 de forma exata. Além disso, os underscores em 9_000_000_000 servem apenas como separadores de dígitos.
Os operadores da sintaxe do C# seguem o padrão das linguagens da família C. Por outro lado, os operadores de nulo merecem atenção especial, porque aparecem em todo código moderno.
int sum = 2 + 3; // 5
int remainder = 7 % 3; // 1
bool both = x && y; // E lógico com curto-circuito
bool either = x || y; // OU lógico com curto-circuito
string value = input ?? "default"; // coalescência de nulo
int? length = text?.Length; // acesso condicional
input ??= "fallback"; // atribui somente se for nulo
Strings e interpolação
Na sintaxe do C#, a interpolação com o prefixo $ é a forma mais legível de montar textos a partir de valores. Além dela, a classe string oferece métodos para os casos mais comuns.
string name = "Ada";
int count = 3;
string message = $"{name} has {count} posts.";
string upper = name.ToUpper();
string[] parts = "a,b,c".Split(',');
string joined = string.Join("-", parts);
bool empty = string.IsNullOrWhiteSpace(" ");
Para textos com várias linhas, use raw string literals com aspas triplas. Assim, aspas e barras invertidas ficam literais, sem nenhum escape.
string json = """
{ "name": "Ada", "active": true }
""";
Contudo, a propriedade Length conta unidades UTF-16, e não os caracteres que uma pessoa enxerga. Isso faz diferença com emojis e com alfabetos não latinos.
Controle de fluxo com if, switch e loops
Na sintaxe do C#, o if e o else funcionam como em qualquer linguagem da família C. Quando o objetivo é mapear uma entrada para um valor, porém, a switch expression deixa o código mais conciso.
string label = status switch
{
1 => "Active",
2 => "Paused",
_ => "Unknown" // o descarte _ funciona como caso padrão
};
Os loops também seguem o formato tradicional:
for (int i = 0; i < 5; i++)
Console.WriteLine(i);
foreach (var item in items)
Console.WriteLine(item);
while (condition)
DoWork();
Na prática, prefira o foreach para percorrer coleções. Use o for somente quando você precisar do índice.
Coleções e expressões de coleção na sintaxe do C#
As expressões de coleção trouxeram para a sintaxe do C# uma forma única de criar arrays, listas e conjuntos com colchetes. Com isso, o mesmo literal serve para vários tipos de coleção.
int[] numbers = [1, 2, 3, 4, 5];
List<string> names = ["Ada", "Alan"];
names.Add("Grace");
names.Remove("Alan");
Dictionary<string, int> ages = new()
{
["Ada"] = 36,
["Grace"] = 45
};
bool found = ages.TryGetValue("Ada", out int age);
HashSet<int> unique = [1, 2, 2, 3]; // resulta em {1, 2, 3}
| Coleção | Quando usar |
|---|---|
| T[] | Sequência de tamanho fixo |
| List<T> | Sequência redimensionável |
| Dictionary<TKey, TValue> | Busca por chave |
| HashSet<T> | Valores únicos e teste rápido de pertencimento |
Prefira TryGetValue ao acesso direto pelo indexador do Dictionary. Afinal, uma chave inexistente lança KeyNotFoundException.
Métodos e parâmetros
Na sintaxe do C#, métodos curtos ficam mais legíveis com expression-bodied members, que usam a seta => no lugar do bloco de chaves. Além disso, a linguagem oferece parâmetros opcionais, argumentos nomeados e params.
int Add(int a, int b) => a + b;
void Greet(string name, string greeting = "Hello")
=> Console.WriteLine($"{greeting}, {name}!");
Greet("Ada"); // Hello, Ada!
Greet("Ada", greeting: "Hi"); // Hi, Ada!
int SumAll(params int[] values) => values.Sum();
SumAll(1, 2, 3, 4); // 10
Classes, records e structs
A classe é um tipo de referência e o principal bloco da programação orientada a objetos em C#. Com primary constructors, você declara os parâmetros do construtor direto na assinatura da classe.
public class Account(string owner)
{
public string Owner { get; } = owner;
public decimal Balance { get; private set; }
public void Deposit(decimal amount) => Balance += amount;
}
O record também é um tipo de referência, mas foi pensado para dados imutáveis. Assim, ele entrega igualdade por valor, um ToString legível e cópias não destrutivas com with.
public record Movie(int Id, string Name, string Service);
var original = new Movie(1, "Dune", "max");
var copy = original with { Service = "netflix" }; // cópia com uma alteração
bool same = original == new Movie(1, "Dune", "max"); // true
Já a struct é um tipo de valor, copiado por valor, e funciona melhor para dados pequenos, como coordenadas.
public readonly struct Point(int X, int Y);
A regra prática é simples. Use record para dados que circulam pela aplicação, class para objetos com comportamento e identidade e struct apenas para valores pequenos.
Propriedades e a palavra-chave field do C# 14
A forma mais curta de propriedade é a auto-property. Por sua vez, o acessor init permite atribuir o valor na inicialização do objeto e bloqueia alterações depois disso.
public class Person
{
public string Name { get; set; }
public int Age { get; init; }
public string Greeting => $"Hi, {Name}"; // propriedade calculada
}
var person = new Person { Name = "Ada", Age = 36 };
No C# 14, a palavra-chave field dá acesso ao campo de apoio que o compilador gera. Dessa forma, você adiciona lógica ao acessor sem declarar um campo privado manualmente.
public string Name
{
get;
set => field = value?.Trim() ?? "";
}
Herança, interfaces e polimorfismo
Na sintaxe do C#, interfaces descrevem capacidades, ou seja, o que um tipo sabe fazer. Classes abstratas, por outro lado, definem uma base que as classes derivadas completam.
public interface IShape
{
double Area();
}
public class Circle(double radius) : IShape
{
public double Area() => Math.PI * radius * radius;
}
public abstract class Animal
{
public abstract string Speak();
}
public class Dog : Animal
{
public override string Speak() => "Woof";
}
O polimorfismo permite que uma variável do tipo base guarde qualquer instância derivada. Então, em tempo de execução, o runtime chama a sobrescrita correta.
Animal animal = new Dog();
Console.WriteLine(animal.Speak()); // Woof
Favoreça interfaces para descrever comportamentos e mantenha a hierarquia de herança rasa.
Pattern matching na sintaxe do C#
O pattern matching testa o formato de um valor e extrai dados no mesmo passo. Por isso, ele está entre os recursos da sintaxe do C# que mais vale aprender cedo.
// padrão de tipo com is
if (value is string text)
Console.WriteLine(text.Length);
// switch com padrões relacionais, lógicos e de tipo
string describe = input switch
{
0 => "zero",
> 0 and < 10 => "small",
int n => $"number {n}",
null => "nothing",
_ => "something else"
};
// padrões de propriedade
string Classify(Movie movie) => movie switch
{
{ Service: "max" } => "Max original",
{ Id: > 100 } => "high id",
_ => "other"
};
Na prática, esses padrões substituem cadeias longas de if e casts manuais. Além disso, o compilador avisa quando a switch expression não cobre todas as entradas possíveis.
Tratamento de nulos e de exceções
Com os nullable reference types ativos, o compilador avisa sempre que uma referência pode ser nula. Nesse caso, o ponto de interrogação no tipo indica que o valor aceita null.
string? maybeNull = GetNameOrNull();
int? length = maybeNull?.Length; // retorna null em vez de lançar exceção
string name = maybeNull ?? "Anonymous"; // valor alternativo
maybeNull ??= "default"; // atribui somente se for nulo
int definite = maybeNull!.Length; // supressão, use com moderação
O C# 14 adicionou a atribuição condicional a nulo. Com ela, a escrita através do operador ?. só acontece quando o lado esquerdo não é nulo.
// GetCurrentOrder só executa quando customer não é nulo
customer?.Order = GetCurrentOrder();
Para exceções, a estrutura try, catch e finally continua a mesma. Primeiro, capture os tipos mais específicos e, depois, os mais genéricos.
try
{
var text = File.ReadAllText("config.json");
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"Missing file: {ex.FileName}");
}
catch (Exception ex)
{
Console.WriteLine($"Something failed: {ex.Message}");
}
finally
{
Console.WriteLine("Done."); // executa sempre
}
ArgumentNullException.ThrowIfNull(input);
Contudo, lançar exceções custa caro em alocações, tempo de execução e pressão sobre o garbage collector. Então, evite usar exceções para controlar o fluxo da aplicação.
Outras novidades do C# 14 na sintaxe do C#
Além da palavra-chave field e da atribuição condicional a nulo, o C# 14 trouxe outros recursos para a sintaxe do C#. A maioria deles reduz código repetitivo em bibliotecas e em cenários de alto desempenho.
O destaque são os extension members, que ampliam os antigos extension methods. Com o novo bloco extension, você declara propriedades de extensão, membros estáticos e até operadores para tipos que não controla.
public static class EnumerableExtensions
{
extension<TSource>(IEnumerable<TSource> source)
{
// propriedade de extensão, usada como sequence.IsEmpty
public bool IsEmpty => !source.Any();
}
}
int[] numbers = [];
bool empty = numbers.IsEmpty; // true
Na prática, quem consome a extensão escreve sequence.IsEmpty como se a propriedade fosse do próprio tipo. Antes, a mesma ideia exigia um método como IsEmpty() com parênteses.
O operador nameof também passou a aceitar tipos genéricos sem argumento. Assim, nameof(List<>) devolve o texto List, o que ajuda em logs, diagnósticos e reflexão.
string typeName = nameof(List<>); // "List"
Outra mudança da sintaxe do C# 14 libera modificadores como ref, in e out em parâmetros de lambdas sem tipo explícito. Com isso, o compilador continua inferindo os tipos, e você declara apenas a intenção de passagem por referência.
O C# 14 também permite construtores e eventos partial, úteis para geradores de código. Além disso, ele aceita operadores de atribuição composta definidos pelo usuário, como +=, e amplia as conversões implícitas entre arrays, Span<T> e ReadOnlySpan<T>.
Por fim, aplicações baseadas em um único arquivo .cs ganharam diretivas próprias de pré-processamento. Desse modo, um script simples roda com dotnet run sem precisar de um arquivo .csproj.
Nenhum desses recursos é obrigatório para quem está começando. Mesmo assim, conhecê-los ajuda a ler código atual, já que bibliotecas e exemplos oficiais adotam essa sintaxe do C# rapidamente.
LINQ e async/await na sintaxe do C#
O LINQ filtra, transforma e agrega coleções com consultas legíveis e combináveis. Se você aprender só um recurso avançado da sintaxe do C#, comece por ele.
int[] numbers = [1, 2, 3, 4, 5, 6];
var evens = numbers.Where(n => n % 2 == 0); // 2, 4, 6
var doubled = numbers.Select(n => n * 2); // 2, 4, 6, 8, 10, 12
int sum = numbers.Sum(); // 21
bool any = numbers.Any(n => n > 5); // true
var ordered = numbers.OrderByDescending(n => n); // 6, 5, 4...
var list = evens.ToList(); // materializa o resultado
O LINQ é lazy, ou seja, nada executa até você iterar o resultado ou chamar ToList, ToArray ou Count. Com isso, você monta a consulta em partes sem tocar nos dados antes da hora.
Já o async/await executa operações de I/O sem bloquear uma thread. Um método async retorna Task ou Task<T>, e o await desembrulha o resultado quando ele fica pronto.
public async Task<string> FetchAsync(string url)
{
using var client = new HttpClient();
return await client.GetStringAsync(url);
}
// executa as duas chamadas em paralelo e aguarda ambas
Task<string> first = FetchAsync("https://a.com");
Task<string> second = FetchAsync("https://b.com");
string[] results = await Task.WhenAll(first, second);
Se um método faz I/O, torne-o async e propague o await por toda a pilha de chamadas. Para ir além, o artigo sobre streaming assíncrono com IAsyncEnumerable no ASP.NET mostra o mesmo modelo aplicado a respostas de API.
Arquivos, enums e genéricos
Na sintaxe do C#, a classe File resolve a maior parte do trabalho com arquivos em uma linha, sem pacotes extras. Ela também oferece versões assíncronas de cada método.
File.WriteAllText("notes.txt", "Hello, file!");
string text = File.ReadAllText("notes.txt");
string[] lines = File.ReadAllLines("notes.txt");
await File.WriteAllTextAsync("notes.txt", "Hello again");
bool exists = File.Exists("notes.txt");
Um enum é um conjunto nomeado de constantes, bem mais claro do que números mágicos espalhados pelo código. Além disso, ele combina muito bem com switch expressions.
public enum Status { Active, Paused, Archived }
Status current = Status.Active;
string label = current switch
{
Status.Active => "Running",
Status.Paused => "On hold",
_ => "Done"
};
Os genéricos permitem escrever um tipo ou método que funciona com qualquer tipo, com segurança e sem casts. Aliás, você já os usa sempre que escreve List<string> ou Dictionary<string, int>.
T First<T>(IList<T> items) => items[0];
public class Box<T>(T value)
{
public T Value { get; } = value;
}
var box = new Box<int>(42);
int number = box.Value; // tipado, sem cast
Onde o C# está presente
O C# roda em praticamente qualquer lugar através do .NET, que é multiplataforma e open source. Assim, a mesma linguagem atende cenários muito diferentes.
No desenvolvimento web, o ASP.NET Core constrói sites, APIs REST e GraphQL e aplicações em tempo real com SignalR. Enquanto isso, o Blazor permite escrever interfaces interativas em C# no lugar de JavaScript.
Para desktop, existem WPF, Windows Forms e WinUI no Windows, além de Avalonia e Uno Platform para várias plataformas. Já o .NET MAUI cria apps nativos para iOS e Android a partir de um único projeto.
O C# também é a linguagem de scripts da Unity e funciona no Godot. Na nuvem, ele aparece em workers, funções serverless, microsserviços e serviços gRPC, com suporte total a containers.
Por fim, o .NET roda em placas como o Raspberry Pi e, com o nanoFramework, em microcontroladores como o ESP32. Bibliotecas como ML.NET e Microsoft.Extensions.AI também levam machine learning e LLMs para dentro das aplicações.
Ferramentas e por onde começar com a sintaxe do C#
Tudo começa pela CLI do dotnet, que vem com o SDK. Com ela, você cria, compila, executa, testa e empacota projetos sem abrir um editor.
dotnet new console -o MyApp
dotnet add package Serilog
dotnet run
dotnet test
dotnet format
| Ferramenta | Plataformas | Ponto forte |
|---|---|---|
| Visual Studio | Windows | IDE completa, com depuração, profiling e designers |
| VS Code com C# Dev Kit | Windows, macOS e Linux | Editor leve e rápido com bom suporte a C# |
| JetBrains Rider | Windows, macOS e Linux | Refatorações e análise de código poderosas |
As três opções oferecem IntelliSense, destaque de erros em linha e refatorações com um clique. Além disso, o depurador permite inspecionar variáveis, usar breakpoints condicionais e alterar o código em execução com Hot Reload.
Para quem está começando, o caminho mais curto segue quatro passos. Primeiro, instale o SDK do .NET e crie um projeto com dotnet new console.
Em seguida, domine o básico da sintaxe do C#, ou seja, variáveis, controle de fluxo, métodos e coleções. Depois, avance para classes e records, pattern matching, LINQ e async/await.
Por fim, construa algo pequeno, como uma lista de tarefas, um renomeador de arquivos ou uma API que retorna JSON. Afinal, você aprende muito mais entregando código do que apenas lendo.
A linguagem continua evoluindo, e o .NET 11 já traz o C# 15 com novos recursos. Se quiser acompanhar o que vem por aí, veja a análise do lançamento do .NET 11 Preview 5 no balta.io e a página oficial de novidades do C# 14.
Conclusão sobre a sintaxe do C#
A sintaxe do C# moderno reduz a cerimônia e deixa o foco na intenção do código. Top-level statements, expressões de coleção, records, primary constructors e a palavra-chave field cortam boa parte do código repetitivo.
Mesmo assim, a produtividade de verdade vem de quatro recursos. Classes e records, pattern matching, LINQ e async/await são os pontos onde a linguagem mostra sua força.
Você não precisa decorar nada disso. Mantenha este guia aberto, escreva código e consulte cada recurso quando a necessidade aparecer, porque a sintaxe do C# fixa com o uso.
FAQ
O C# é difícil para iniciantes?
Não, o C# está entre as linguagens compiladas mais amigáveis para quem começa. A sintaxe é clara, as ferramentas são maduras e os erros do compilador explicam o que está errado.
Qual a diferença entre C# e .NET?
O C# é a linguagem de programação, e o .NET é a plataforma onde ela roda. O .NET fornece o runtime e as bibliotecas base que o seu código usa.
Qual a diferença entre class e record?
A class atende objetos com comportamento e identidade. O record foca em dados imutáveis, com igualdade por valor, ToString legível e cópias com with.
Preciso do Visual Studio para programar em C#?
Não, basta o SDK gratuito do .NET e qualquer editor. VS Code com C# Dev Kit e JetBrains Rider também funcionam muito bem, e o dotnet run executa tudo pelo terminal.
Qual versão do C# devo usar hoje?
Para projetos de longo prazo, use o .NET 10, versão LTS que traz o C# 14 como padrão. Ele recebe suporte estendido e reúne todos os recursos mostrados neste guia.