C# Novidades do C# 8 ao 14
O C# ganha uma versão nova todo ano, em novembro, junto com o .NET. Muito do que este guia ensina como "o jeito normal" (dotnet run ola.cs sem class Program, [1, 2, 3], record, is not null) não existia há poucos anos, e você ainda vai encontrar muito código no estilo antigo. Esta aula é um mapa: o que entrou em cada versão, do C# 8 (2019) ao C# 14 (2025), onde cada coisa é ensinada no guia e, com mais detalhe, as novidades do C# 14, conferidas uma a uma no .NET 10 da máquina da Ana.
- A relação entre a versão do C# e a do .NET, e o que é o
LangVersion(erroCS9260). - A tabela das novidades do C# 8 ao 13, com um programa que usa várias delas.
- O C# 14 na prática: a palavra
field, os blocosextension, a atribuição condicional nula?.=,nameof(List<>), lambdas comouterefsem tipos, construtorespartial, operadores+=e spans de primeira classe. - As armadilhas: um campo chamado
field(CS9258) e o?.com++(CS1059).
Esta aula passa por muitos assuntos do guia e aponta a aula de cada um. Vale a pena voltar a ela depois de terminar o Nível 3. Os exemplos rodaram com dotnet run no .NET 10 (dotnet --version = 10.0.112) da máquina da Ana, na pasta ~/cs.
A ideia antes do código: linguagem × plataforma
Lembre a diferença entre o C# (a linguagem: as palavras e regras que você escreve) e o .NET (a plataforma: o compilador, as bibliotecas e o runtime que roda o programa), da aula .NET: SDK, Runtime e CLR. Cada .NET vem com um compilador que entende uma versão do C#, e cada projeto escolhe a versão da linguagem pelo .NET de destino:
| C# | .NET | Lançamento | Suporte |
|---|---|---|---|
| C# 8 | .NET Core 3.0 / 3.1 | 2019 | encerrado |
| C# 9 | .NET 5 | nov. 2020 | encerrado |
| C# 10 | .NET 6 (LTS) | nov. 2021 | encerrado |
| C# 11 | .NET 7 | nov. 2022 | encerrado |
| C# 12 | .NET 8 (LTS) | nov. 2023 | até nov. 2026 |
| C# 13 | .NET 9 | nov. 2024 | até nov. 2026 |
| C# 14 | .NET 10 (LTS) | nov. 2025 | até nov. 2028 |
LTS (Long Term Support, "suporte longo") são as versões de 3 anos de suporte, as preferidas das empresas. A aula Versões do C# e do .NET conta essa história com mais calma. O que importa aqui: no .NET 10, o padrão é o C# 14, e um projeto pode pedir uma versão mais antiga da linguagem com <LangVersion> no .csproj. Se o código usar algo mais novo que a versão pedida, o compilador avisa exatamente o quê:
$ dotnet run /home/ana/projetos/Velho/Program.cs(8,19): error CS9260: O recurso "campo palavra-chave" não está disponível no C# 13.0. Use a versão da linguagem 14.0 ou superior. [/home/ana/projetos/Velho/Velho.csproj] /home/ana/projetos/Velho/Program.cs(14,20): error CS9260: O recurso "campo palavra-chave" não está disponível no C# 13.0. Use a versão da linguagem 14.0 ou superior. [/home/ana/projetos/Velho/Velho.csproj] Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
Esse projeto tinha <LangVersion>13</LangVersion> e usava a palavra field (seção 3). "Campo palavra-chave" é a tradução, meio torta, de field keyword. O mesmo CS9260 aparece para cada recurso: "atribuição condicional nula", "listas de padrões"… É o jeito mais rápido de descobrir de que versão um recurso é.
1. A tabela: do C# 8 ao 13
As novidades mais importantes de cada versão, e a aula do guia que ensina cada uma:
| Versão | Recurso | Exemplo | Aula |
|---|---|---|---|
| C# 8 | Referências anuláveis | string? nome | Referências Anuláveis |
| Expressões switch | x switch { 1 => "um", _ => "?" } | Expressões switch | |
| Índices e intervalos | a[^1], a[1..3] | Índices e Intervalos | |
using sem chaves | using var arquivo = …; | using e IDisposable | |
??= | nome ??= "Ana"; | ?? e ??= | |
| Padrões de propriedade e posicionais | p is { Idade: 18 } | Pattern Matching | |
| C# 9 | Instruções de nível superior | sem class Program nem Main | Program.cs e o Main |
| Records | record Ponto(int X, int Y); | Records | |
init | public string Nome { get; init; } | readonly e init | |
new sem o tipo | Produto p = new("Caderno"); | Construtores | |
| Padrões relacionais e lógicos | n is >= 0 and <= 10, is not null | Pattern Matching | |
| C# 10 | global using e using implícitos | global using System.Text; | Namespaces e using |
| Namespace de arquivo | namespace Loja; | Namespaces e using | |
record struct | record struct Cor(byte R, byte G, byte B); | Records | |
[CallerArgumentExpression] | o texto do argumento | Atributos | |
| C# 11 | Strings brutas (raw strings) | """texto com "aspas" """ | Escapes e Strings Literais |
| Padrões de lista | l is [1, .., var ultimo] | Pattern Matching | |
required | public required string Nome { get; init; } | Inicializadores e required | |
| Matemática genérica | T Somar<T>(T a, T b) where T : INumber<T> | Restrições (where) | |
| Atributos genéricos | [Validar<int>] | Atributos | |
| C# 12 | Expressões de coleção | int[] a = [1, 2, 3];, [..a, 4] | Arrays, List |
| Construtores primários em classes | class Caixa(string operadora) { … } | Construtores | |
| Valores padrão em lambdas | (int x = 1) => x * 2 | Lambdas | |
| C# 13 | params com coleções | params ReadOnlySpan<int> valores | params |
O tipo Lock | private readonly Lock _trava = new(); | Sincronização (lock) | |
Propriedades partial | [GeneratedRegex(…)] static partial Regex R { get; } | Regex, partial | |
Escape \e | o caractere ESC, para cores no terminal | Escapes |
Um programa, várias versões
Este programa da lojinha usa um recurso de quase cada versão. Os comentários dizem de qual:
// C# 9: instruções de nível superior (sem class Program nem Main)
List<Produto> estoque = [new("Caderno", 15.90m), new("Caneta", 2.50m)]; // C# 12: [ ]
string? aviso = null;
aviso ??= "sem avisos"; // C# 8: ??=
Console.WriteLine(aviso);
foreach (var p in estoque)
{
string faixa = p.Preco switch // C# 8: expressão switch
{
< 5 => "barato", // C# 9: padrões relacionais
_ => "normal",
};
Console.WriteLine($"{p.Nome}: {faixa}");
}
Console.WriteLine(estoque is [var primeiro, ..] ? $"Primeiro: {primeiro.Nome}" : "vazio"); // C# 11
string json = """
{ "loja": "Ana", "aberta": true }
"""; // C# 11: raw string
Console.WriteLine(json);
Caixa caixa = new("Ana"); // C# 12: construtor primário
Console.WriteLine(caixa.Somar(10m, 5.5m, 2m)); // C# 13: params com coleções
record Produto(string Nome, decimal Preco); // C# 9: record
class Caixa(string operadora)
{
public string Somar(params ReadOnlySpan<decimal> valores)
{
decimal total = 0;
foreach (var v in valores) total += v;
return $"{operadora} somou {total}";
}
}
$ dotnet run tour.cs
sem avisos
Caderno: normal
Caneta: barato
Primeiro: Caderno
{ "loja": "Ana", "aberta": true }
Ana somou 17,5
No C# 7, esse mesmo programa precisaria de class Program e static void Main, de new List<Produto> { new Produto("Caderno", 15.90m), … }, de uma classe Produto inteira com construtor e propriedades, de um if no lugar do ??=, de um switch com case e break, de \" no JSON… A linguagem ficou bem mais enxuta.
2. O C# 14 em uma tabela C# 14
| Recurso | Em uma linha | Onde |
|---|---|---|
A palavra field | Acessar o campo escondido de uma propriedade automática, para validar no set | Seção 3 |
Blocos extension | Propriedades de extensão e membros estáticos de extensão, além de métodos | Seção 4 |
| Atribuição condicional nula | cliente?.Pontos = 10; só grava se cliente não for nulo | Seção 5 |
nameof com genérico aberto | nameof(List<>) dá "List" | Seção 6 |
| Lambdas com modificadores, sem tipos | (texto, out numero) => … | Seção 6 |
Construtores e eventos partial | Declarar num arquivo, implementar em outro | Seção 6 |
+=, ++ definidos pelo usuário | public void operator +=(int n) | Sobrecarga de Operadores |
| Spans de primeira classe | Array vira ReadOnlySpan sozinho, até em métodos de extensão | Span<T> |
3. A palavra field C# 14
Uma propriedade automática ({ get; set; }) tem um campo escondido, que o compilador cria. Até o C# 13, se você quisesse validar o valor no set, precisava desistir da propriedade automática e escrever o campo à mão (private decimal _preco;). No C# 14, a palavra field ("campo") é esse campo escondido, e você pode usá-la dentro do get ou do set:
Produto p = new() { Nome = " caderno ", Preco = -5 };
Console.WriteLine($"[{p.Nome}] custa {p.Preco}");
p.Preco = 15.90m;
Console.WriteLine($"[{p.Nome}] custa {p.Preco}");
class Produto
{
public string Nome
{
get;
set => field = value.Trim();
} = "";
public decimal Preco
{
get;
set => field = value < 0 ? 0 : value;
}
}
$ dotnet run field.cs [caderno] custa 0 [caderno] custa 15,90
- get;
- O
getcontinua automático: devolve o campo escondido. - set => field = value.Trim();
- O
settem corpo: tira os espaços das pontas e grava no campo escondido, que agora tem nome:field. - } = "";
- O valor inicial vai para o mesmo campo.
- value < 0 ? 0 : value
- Preço negativo vira 0. Por isso o
-5do inicializador virou0.
Antes, seriam 6 linhas a mais: private string _nome = "";, get => _nome;, set => _nome = value.Trim();, e o mesmo para o preço. E o campo _nome ficaria visível para o resto da classe, que poderia gravar nele sem passar pela validação. O field só existe dentro da propriedade.
Missão: uma nota precisa ficar entre 0 e 10. Troque o set; por um set que use field e Math.Clamp(value, 0, 10) ("prender" o valor na faixa). A saída deve ser Nota: 10, Nota: 0 e Nota: 7.
Aluno a = new();
a.Nota = 15;
Console.WriteLine($"Nota: {a.Nota}");
a.Nota = -3;
Console.WriteLine($"Nota: {a.Nota}");
a.Nota = 7;
Console.WriteLine($"Nota: {a.Nota}");
class Aluno
{
public int Nota { get; set; }
}
Aluno a = new();
a.Nota = 15;
Console.WriteLine($"Nota: {a.Nota}");
a.Nota = -3;
Console.WriteLine($"Nota: {a.Nota}");
a.Nota = 7;
Console.WriteLine($"Nota: {a.Nota}");
class Aluno
{
public int Nota { get; set => field = Math.Clamp(value, 0, 10); }
}
4. Blocos extension C# 14
Os métodos de extensão (com this no primeiro parâmetro) existem desde o C# 3, mas só serviam para métodos. O C# 14 trouxe uma forma nova: um bloco extension(tipo nome) { … } dentro de uma classe estática, onde cabem também propriedades de extensão e membros estáticos de extensão:
string frase = "a lojinha da Ana";
Console.WriteLine(frase.Palavras);
Console.WriteLine(frase.Iniciais());
Console.WriteLine(string.Repetir("-", 10));
int[] vazio = [];
Console.WriteLine(vazio.EstaVazia);
static class ExtensoesDeTexto
{
extension(string s)
{
public int Palavras => s.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
public string Iniciais() => string.Concat(s.Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(p => char.ToUpper(p[0])));
public static string Repetir(string pedaco, int vezes) => string.Concat(Enumerable.Repeat(pedaco, vezes));
}
extension<T>(IEnumerable<T> itens)
{
public bool EstaVazia => !itens.Any();
}
}
$ dotnet run extensao.cs 4 ALDA ---------- True
| Trecho | O que é |
|---|---|
extension(string s) { … } | "Tudo aqui dentro estende string; dentro dos membros, o texto se chama s." O s faz o papel do antigo this string s. |
public int Palavras => … | Uma propriedade de extensão: usa-se frase.Palavras, sem parênteses. Antes do C# 14, isso não existia. |
public string Iniciais() => … | Um método de extensão comum, no estilo novo. O jeito antigo (this string s) continua valendo, e os dois convivem. |
public static string Repetir(…) | Um membro estático de extensão: aparece como se fosse do próprio tipo, string.Repetir("-", 10). |
extension<T>(IEnumerable<T> itens) | Um bloco genérico: vale para qualquer coleção, como o int[]. |
Missão: crie, dentro de Extensoes, um bloco extension(string s) com a propriedade public int Palavras => s.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length; e tire os //. A saída deve ser 4 palavras e 2 palavras.
string a = "a lojinha da Ana";
string b = " caderno azul ";
// Console.WriteLine($"{a.Palavras} palavras");
// Console.WriteLine($"{b.Palavras} palavras");
Console.WriteLine(a);
static class Extensoes
{
// escreva o bloco extension aqui
}
string a = "a lojinha da Ana";
string b = " caderno azul ";
Console.WriteLine($"{a.Palavras} palavras");
Console.WriteLine($"{b.Palavras} palavras");
Console.WriteLine(a);
static class Extensoes
{
extension(string s)
{
public int Palavras => s.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
}
}
5. Atribuição condicional nula: ?.= C# 14
O operador ?. (veja ?. e ??) já servia para ler sem medo de null: cliente?.Nome dá null se cliente for nulo, em vez de lançar exceção. No C# 14, ele também serve para gravar: cliente?.Pontos = 10; grava se cliente existir e não faz nada se ele for nulo.
Cliente? ana = new() { Nome = "Ana" };
Cliente? ninguem = null;
ana?.Pontos = 10;
ninguem?.Pontos = 10;
ana?.Pontos += 5;
ninguem?.Pontos += 5;
Console.WriteLine(ana?.Pontos);
Console.WriteLine(ninguem?.Pontos is null ? "ninguém, nada aconteceu" : "?");
int[]? notas = [7, 8];
notas?[0] = 10;
Console.WriteLine(notas?[0]);
class Cliente
{
public string Nome { get; set; } = "";
public int Pontos { get; set; }
}
$ dotnet run nulo.cs 15 ninguém, nada aconteceu 10
- ana?.Pontos = 10;
- "Se
ananão for nulo, grave 10 emPontos." Antes:if (ana is not null) ana.Pontos = 10;. - ninguem?.Pontos = 10;
ningueménull: a linha não faz nada, e não háNullReferenceException.- ana?.Pontos += 5;
- Também vale com
+=,-=e companhia: 10 + 5 = 15. - notas?[0] = 10;
- E com colchetes: o
?[ ]grava no array só se ele existir.
Um detalhe importante: o lado direito só é calculado se o objeto não for nulo. Em ninguem?.Pontos = CalcularPontos();, o método CalcularPontos nem é chamado.
6. As outras novidades do C# 14
nameof com genéricos abertos
O nameof devolve o nome de algo como texto, conferido pelo compilador. Até o C# 13, para um tipo genérico era preciso inventar um tipo qualquer (nameof(List<int>)). Agora dá para deixar os <> vazios:
Console.WriteLine(nameof(List<>)); Console.WriteLine(nameof(Dictionary<,>)); Console.WriteLine(nameof(List<>.Count)); Console.WriteLine(nameof(List<int>));
$ dotnet run nomes.cs List Dictionary Count List
Repare que o resultado é só o nome, sem os <>, nas duas formas. A vírgula em Dictionary<,> diz "dois parâmetros de tipo".
Parâmetros de lambda com out e ref, sem tipos
Numa lambda, o C# descobre os tipos dos parâmetros pelo delegate. Mas, se um parâmetro tinha out ou ref, era obrigatório escrever todos os tipos: (string texto, out int numero) => …. No C# 14, o modificador basta:
Conversor conv = (texto, out numero) => int.TryParse(texto, out numero);
foreach (var t in new[] { "42", "abc" })
{
bool ok = conv(t, out int n);
Console.WriteLine($"{t}: {(ok ? n : "inválido")}");
}
Dobrar dobrar = (ref x) => x *= 2;
int v = 21;
dobrar(ref v);
Console.WriteLine(v);
delegate bool Conversor(string texto, out int numero);
delegate void Dobrar(ref int x);
$ dotnet run lambda.cs 42: 42 abc: inválido 42
O delegate Conversor já diz que texto é string e numero é out int; a lambda só repete o out. O ref x da segunda lambda dobra a variável original (veja ref, out e in): 21 virou 42.
Construtores e eventos partial
As classes parciais podem ter partes em arquivos diferentes, e desde o C# 13 propriedades também podem ser declaradas numa parte e implementadas em outra. O C# 14 estendeu isso a construtores e eventos. É um recurso pensado para geradores de código, como o do [GeneratedRegex]:
Conta c = new(100m);
Console.WriteLine(c.Saldo);
partial class Conta
{
public decimal Saldo { get; }
public partial Conta(decimal inicial);
}
partial class Conta
{
public partial Conta(decimal inicial)
{
Saldo = inicial;
}
}
$ dotnet run parcial.cs 100
Operadores += e spans de primeira classe
Os dois têm aula própria: o public void operator +=(…) de instância está em Sobrecarga de Operadores (seção 5), e a conversão automática de array para ReadOnlySpan em métodos de extensão está em Span<T> (seção 3), com o erro CS1929 que o mesmo código dá no C# 13.
O editor desta página compila com o C# 14 de verdade: todos os exemplos desta aula foram conferidos no testador do guia e deram a mesma saída que o dotnet 10 do laboratório.
Qual usar?
| Situação | Recomendação |
|---|---|
| Projeto novo no .NET 10 | Use o C# 14 à vontade: é o padrão e tem suporte até 2028. |
| Projeto que ainda roda no .NET 8 | Ele usa C# 12: nada de field, extension, ?.=, params ReadOnlySpan. Cada versão do C# só tem suporte oficial no .NET que a trouxe; para usar o C# 14, atualize o projeto para o .NET 10 (o .NET 8 sai de suporte em novembro de 2026). |
| Validar uma propriedade | field no set, em vez de um campo _nome à mão. |
| Uma "propriedade" a mais num tipo que não é seu | Bloco extension com uma propriedade. |
| Gravar num objeto que pode ser nulo | obj?.Prop = valor; (mas pense se o null ali não é um erro que deveria aparecer). |
| Ler código antigo | Use a tabela da seção 1 para reconhecer a forma nova de cada coisa. |
Quando dá errado
error CS9260: recurso novo numa versão antiga
Mensagem: O recurso "…" não está disponível no C# 13.0. Use a versão da linguagem 14.0 ou superior. (veja "A ideia antes do código"). Como consertar: tire o <LangVersion> do .csproj (o padrão do .NET 10 é o C# 14) ou, se o projeto precisa mesmo da versão antiga, escreva do jeito antigo. Atenção: os blocos extension numa versão antiga nem chegam a dar CS9260; o compilador não reconhece a sintaxe e mostra erros confusos, como CS1513: } esperada.
Uma classe que já tinha um campo chamado field
Caixa c = new();
c.Valor = 7;
Console.WriteLine(c.Valor);
Console.WriteLine(c.CampoAntigo);
class Caixa
{
private int field;
public int CampoAntigo => this.field;
public int Valor
{
get => field;
set => field = value;
}
}
$ dotnet run campo.cs /home/ana/cs/campo.cs(12,16): warning CS9258: Na versão da linguagem 14.0, a palavra-chave ''field'' é associada a um campo de suporte sintetizado para a propriedade. Para evitar a geração de um campo de suporte sintetizado e para se referir ao membro existente, use 'this.field' ou '@field'. /home/ana/cs/campo.cs(13,16): warning CS9258: Na versão da linguagem 14.0, a palavra-chave ''field'' é associada a um campo de suporte sintetizado para a propriedade. Para evitar a geração de um campo de suporte sintetizado e para se referir ao membro existente, use 'this.field' ou '@field'. /home/ana/cs/campo.cs(8,17): warning CS0649: Campo "Caixa.field" nunca é atribuído e sempre terá seu valor padrão 0 7 0
O que quer dizer: num código escrito antes do C# 14, field era um nome comum. Agora, dentro de uma propriedade, ele é a palavra nova: o Valor passou a usar um campo escondido, e o campo field da linha 8 ficou esquecido (por isso CampoAntigo mostrou 0). O compilador avisa com CS9258. Como consertar: se você quer o campo antigo, escreva this.field ou @field dentro da propriedade, como a mensagem sugere; melhor ainda, renomeie o campo para _valor.
?. com ++
Cliente? ana = new();
ana?.Pontos++;
class Cliente { public int Pontos { get; set; } }
$ dotnet run mais.cs /home/ana/cs/mais.cs(2,1): error CS1059: O operando de aumento ou diminuição deve ser uma variável, propriedade ou indexador Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que quer dizer: a atribuição condicional nula aceita =, +=, -= e afins, mas não ++ e --. Como consertar: ana?.Pontos += 1;.
Referência rápida: recurso → versão
| Recurso | Versão |
|---|---|
string?, switch expressão, ^1 e .., ??=, using var | C# 8 |
Sem Main, record, init, new(), is not null, and/or | C# 9 |
global using, namespace X;, record struct, { A.B: … } | C# 10 |
"""…""", [1, ..] em padrões, required, INumber<T> | C# 11 |
[1, 2, 3], class X(int a), lambdas com valor padrão | C# 12 |
params ReadOnlySpan<T>, Lock, propriedades partial, \e | C# 13 |
field, extension(…) { }, ?.=, nameof(List<>), (x, out y) =>, construtores partial, operator +=, spans de primeira classe | C# 14 |
Exercícios
1. Complete o set para gravar no campo escondido da propriedade:
2. Qual operador grava só se o objeto não for nulo?
3. Qual versão do C# vem com o .NET 10?
4. O que mostra Console.WriteLine(nameof(Dictionary<,>)); no C# 14?
5. Num bloco extension(string s) { public int Palavras => …; }, como se usa a propriedade?
6. Um projeto com <LangVersion>13</LangVersion> usa cliente?.Pontos = 10;. O que acontece?
Missão final: a promoção da loja dá 10 pontos a cada cliente da fila, mas algumas posições da fila estão vazias (null). Troque o if por uma atribuição condicional nula: c?.Pontos += 10;. A saída não muda (Ana: 15 pontos, Bruno: 20 pontos, ninguém mais), mas o código fica com uma linha só.
Cliente?[] fila = [new("Ana", 5), null, new("Bruno", 10), null];
foreach (var c in fila)
{
if (c is not null)
{
c.Pontos = c.Pontos + 10;
}
}
foreach (var c in fila)
{
if (c is not null) Console.WriteLine($"{c.Nome}: {c.Pontos} pontos");
}
Console.WriteLine("ninguém mais");
class Cliente(string nome, int pontos)
{
public string Nome { get; } = nome;
public int Pontos { get; set; } = pontos;
}
Cliente?[] fila = [new("Ana", 5), null, new("Bruno", 10), null];
foreach (var c in fila)
{
c?.Pontos += 10;
}
foreach (var c in fila)
{
if (c is not null) Console.WriteLine($"{c.Nome}: {c.Pontos} pontos");
}
Console.WriteLine("ninguém mais");
class Cliente(string nome, int pontos)
{
public string Nome { get; } = nome;
public int Pontos { get; set; } = pontos;
}
Resumo
- Cada .NET traz uma versão do C#: .NET 8 → C# 12, .NET 9 → C# 13, .NET 10 → C# 14 (LTS, até 2028). Um recurso mais novo que o
<LangVersion>do projeto dáCS9260, dizendo o nome do recurso e a versão necessária. - Do C# 8 ao 13, a linguagem ganhou referências anuláveis, expressões switch, índices
^, records, instruções de nível superior, padrões cada vez mais ricos, strings brutas,required, expressões de coleção, construtores primários eparamscom spans. - C# 14:
fieldnas propriedades (cuidado com campos chamadosfield:CS9258), blocosextensioncom propriedades e membros estáticos,obj?.Prop = valor(e+=, mas não++:CS1059),nameof(List<>), lambdas(x, out y) =>, construtores e eventospartial,operator +=e spans de primeira classe. - Todos os exemplos do C# 14 desta aula foram conferidos no .NET 10 do laboratório e no editor do guia.
Próxima aula: Arquivos (File), o começo do grupo Arquivos e Dados.