0% acharam este documento útil (0 voto)
94 visualizações1.522 páginas

Guia Completo de Programação em C#

O documento fornece uma introdução abrangente à linguagem de programação C#, destacando sua popularidade na plataforma .NET e suas características, como programação orientada a objetos e suporte a LINQ. Inclui exemplos práticos, como o clássico programa 'Hello, World', e discute recursos avançados como programação assíncrona e correspondência de padrões. Além disso, compara C# com Java, enfatizando semelhanças na sintaxe e paradigmas de programação.

Enviado por

Breno Oliveira
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
94 visualizações1.522 páginas

Guia Completo de Programação em C#

O documento fornece uma introdução abrangente à linguagem de programação C#, destacando sua popularidade na plataforma .NET e suas características, como programação orientada a objetos e suporte a LINQ. Inclui exemplos práticos, como o clássico programa 'Hello, World', e discute recursos avançados como programação assíncrona e correspondência de padrões. Além disso, compara C# com Java, enfatizando semelhanças na sintaxe e paradigmas de programação.

Enviado por

Breno Oliveira
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd

Dê a sua opinião sobre a experiência de download do PDF.

Documentação do C#
Saiba como escrever qualquer aplicativo usando a linguagem de programação C# na
plataforma .NET.

Aprender a programar em C#

b COMEÇAR AGORA

Aprenda C# | Tutoriais, cursos, vídeos e muito mais

q VIDEO

Série de vídeos para iniciantes no C#

g TUTORIAL

Tutoriais autoguiados

Tutorial no navegador

i REFERÊNCIA

C# no Q&A

Linguagens em fóruns da comunidade de tecnologia do .NET

C# no Stack Overflow

C# no Discord

Princípios básicos do C#

e VISÃO GERAL

Um tour pelo C#

Por dentro de um programa em C#

Série de vídeos sobre os destaques do C#

p CONCEITO

Sistema de tipos
Programação orientada a objeto

Técnicas funcionais

Exceções

Estilo de codificação

g TUTORIAL

Exibir linha de comando

Introdução às classes

C# orientado a objeto

Converter tipos

Correspondência de padrões

Usar o LINQ para consultar dados

Novidades

h NOVIDADES

O que há de novo no C# 13

Novidades do C# 12

Novidades do C# 11

Novidades do C# 10

g TUTORIAL

Explorar tipos de registro

Explorar instruções de nível superior

Explorar novos padrões

Escrever um manipulador de interpolação de cadeia de caracteres personalizado

i REFERÊNCIA

Alterações de falha no compilador C#

Compatibilidade de versões
Conceitos principais

e VISÃO GERAL

Estratégia de linguagem C#

Conceitos de programação

p CONCEITO

LINQ (Consulta Integrada à Linguagem)

Programação assíncrona

Conceitos avançados

i REFERÊNCIA

Reflexão e atributos

Árvores de expressão

Interoperabilidade nativa

Engenharia de desempenho

SDK da Plataforma do Compilador .NET

Mantenha contato

i REFERÊNCIA

.NET Developer Community

YouTube

Twitter
Um tour pela linguagem C#
Artigo • 09/05/2024

A linguagem C# é a linguagem mais popular para a plataforma .NET, um ambiente de


desenvolvimento gratuito, multiplataforma e de código aberto. Os programas C#
podem ser executados em muitos dispositivos diferentes, desde dispositivos de Internet
das Coisas (IoT) até a nuvem e todos os outros lugares. Você pode escrever aplicativos
para telefones, desktops e laptops e servidores.

O C# é uma linguagem de uso geral multiplataforma que torna os desenvolvedores


produtivos ao escrever um código de alto desempenho. Com milhões de
desenvolvedores, o C# é a linguagem .NET mais popular. O C# tem amplo suporte no
ecossistema e em todas as cargas de trabalho do .NET. Com base em princípios
orientados a objetos, ele incorpora muitos recursos de outros paradigmas,
especialmente a programação funcional. Recursos de baixo nível dão suporte a cenários
de alta eficiência sem escrever código não seguro. A maioria dos runtimes e bibliotecas
do .NET são escritos em C# e avanços no C# geralmente beneficiam todos os
desenvolvedores do .NET.

Hello world
O programa "Hello, World" é usado tradicionalmente para introduzir uma linguagem de
programação. Este é para C#:

C#

// This line prints "Hello, World"


Console.WriteLine("Hello, World");

A linha que começa com // é um comentário de linha única. Comentários de linha única
em C# começam com // e continuam até o final da linha atual. C# também suporta
comentários multilinhas. Comentários de várias linhas começam com /* e terminam
com */ . O método WriteLine da classe Console , que está no System namespace,
produz a saída do programa. Essa classe é fornecida pelas bibliotecas de classes padrão,
que, por padrão, são referenciadas automaticamente em todos os programas C#.

O exemplo anterior mostra uma forma de programa "Hello, World", usando instruções
de nível superior. Versões anteriores do C# exigiam que você definisse o ponto de
entrada do programa em um método. Esse formato ainda é válido e você o verá em
muitos exemplos de C# existentes. Você também deve estar familiarizado com este
formulário, conforme mostrado no exemplo a seguir:

C#

using System;

class Hello
{
static void Main()
{
// This line prints "Hello, World"
Console.WriteLine("Hello, World");
}
}

Essa versão mostra os blocos de construção que você usa em seus programas. O
programa "Hello, World" começa com uma diretiva using que faz referência ao
namespace System . Namespaces fornecem um meio hierárquico de organizar
bibliotecas e programas em C#. Namespaces contêm tipos e outros namespaces—por
exemplo, o namespace System contém muitos tipos, como a classe Console
referenciada no programa, e muitos outros namespaces, como IO e Collections . A
diretiva using que faz referência a um determinado namespace permite o uso não
qualificado dos tipos que são membros desse namespace. Devido à diretiva using , o
programa pode usar Console.WriteLine como um atalho para
System.Console.WriteLine . No exemplo anterior, esse namespace foi implicitamente

incluído.

A classe Hello declarada pelo programa "Hello, World" tem um único membro, o
método chamado Main . O método Main é declarado com o modificador static .
Embora os métodos de instância possam fazer referência a uma determinada instância
de objeto delimitador usando a palavra-chave this , métodos estáticos operam sem
referência a um objeto específico. Por convenção, quando não há instruções de nível
superior, um método estático chamado Main serve como ponto de entrada de um
programa C#.

Ambos os formulários de ponto de entrada produzem código equivalente. Quando você


usa instruções de nível superior, o compilador sintetiza a classe e o método que o
contém para o ponto de entrada do programa.

 Dica
Os exemplos neste artigo fornecem uma primeira visão do código C#. Alguns
exemplos podem mostrar elementos de C# com os quais você não está
familiarizado. Quando estiver pronto para aprender C#, comece com nossos
tutoriais para iniciantes ou mergulhe nos links de cada seção. Se você tem
experiência em Java, JavaScript, TypeScript ou Python, leia nossas dicas para
ajudá-lo a encontrar as informações necessárias para aprender C# rapidamente.

Recursos familiares do C#
C# é acessível para iniciantes, mas oferece recursos avançados para desenvolvedores
experientes que escrevem aplicativos especializados. Você pode ser produtivo
rapidamente. Você pode aprender técnicas mais especializadas conforme necessário
para suas aplicações.

Os aplicativos C# se beneficiam do gerenciamento automático de memória do .NET


Runtime. Os aplicativos C# também usam as extensas bibliotecas de tempo de execução
fornecidas pelo .NET SDK. Alguns componentes são independentes de plataforma,
como bibliotecas de sistemas de arquivos, coleções de dados e bibliotecas matemáticas.
Outras são específicas para uma única carga de trabalho, como as bibliotecas Web
ASP.NET Core ou a biblioteca .NET MAUI UI. Um rico ecossistema de código aberto no
NuGet aumenta as bibliotecas que fazem parte do tempo de execução. Essas
bibliotecas fornecem ainda mais componentes que você pode usar.

C# está na família de linguagens C. A sintaxe C# é familiar se você usou C, C++,


JavaScript ou Java. Como todas as linguagens da família C, ponto e vírgula ( ; ) definem
o final das instruções. Os identificadores C# diferenciam maiúsculas de minúsculas. C#
tem o mesmo uso de colchetes, { e } , instruções de controle como if , else e switch ,
e construções de loop como for , e while . C# também possui uma instrução foreach
para qualquer tipo de coleção.

C# é uma linguagem fortemente tipada. Cada variável que você declara possui um tipo
conhecido em tempo de compilação. O compilador ou ferramentas de edição informam
se você está usando esse tipo incorretamente. Você pode corrigir esses erros antes
mesmo de executar o programa. Tipos de dados fundamentais são integrados à
linguagem e ao tempo de execução: tipos de valor como int , double , char , tipos de
referência como string , matrizes e outras coleções. Ao escrever seus programas, você
cria seus próprios tipos. Esses tipos podem ser struct tipos para valores ou class tipos
que definem o comportamento orientado a objetos. Você pode adicionar o modificador
record aos tipos struct ou class para que o compilador sintetize o código para

comparações de igualdade. Você também pode criar definições interface , que definem
um contrato, ou um conjunto de membros, que um tipo que implementa essa interface
deve fornecer. Você também pode definir tipos e métodos genéricos. Genéricos usam
parâmetros de tipo para fornecer um espaço reservado para um tipo real quando
usados.

Ao escrever o código, você define funções, também chamadas de métodos, como


membros dos tipos struct e class . Esses métodos definem o comportamento dos seus
tipos. Os métodos podem estar sobrecarregados, com diferentes números ou tipos de
parâmetros. Os métodos podem opcionalmente retornar um valor. Além dos métodos,
os tipos C# podem ter propriedades, que são elementos de dados apoiados por funções
chamadas acessadores. Os tipos C# podem definir events, que permitem que um tipo
notifique os assinantes sobre ações importantes. C# oferece suporte a técnicas
orientadas a objetos, como herança e polimorfismo para tipos class .

Os aplicativos C# usam exceções para relatar e tratar erros. Você estará familiarizado
com essa prática se tiver usado C++ ou Java. Seu código lança uma exceção quando
não consegue fazer o que foi planejado. Outro código, não importa quantos níveis
subam na pilha de chamadas, pode opcionalmente ser recuperado usando um bloco
try - catch .

Recursos distintos do C#
Alguns elementos do C# podem ser menos familiares. Consulta integrada de linguagem
(LINQ) fornece uma sintaxe comum baseada em padrões para consultar ou transformar
qualquer coleção de dados. LINQ unifica a sintaxe para consultar coleções na memória,
dados estruturados como XML ou JSON, armazenamento de banco de dados e até
mesmo APIs de dados baseadas em nuvem. Você aprende um conjunto de sintaxe e
pode pesquisar e manipular dados independentemente de seu armazenamento. A
consulta a seguir encontra todos os alunos cuja média de notas é superior a 3,5:

C#

var honorRoll = from student in Students


where student.GPA > 3.5
select student;

A consulta anterior funciona para muitos tipos de armazenamento representados por


Students . Pode ser uma coleção de objetos, uma tabela de banco de dados, um blob de

armazenamento em nuvem ou uma estrutura XML. A mesma sintaxe de consulta


funciona para todos os tipos de armazenamento.
O modelo de programação assíncrona baseado em tarefas permite que você escreva
código que parece ser executado de forma síncrona, mesmo que seja executado de
forma assíncrona. Ele utiliza as palavras-chave async e await para descrever métodos
assíncronos e quando uma expressão é avaliada de forma assíncrona. O exemplo a
seguir aguarda uma solicitação da Web assíncrona. Quando a operação assíncrona for
concluída, o método retornará o comprimento da resposta:

C#

public static async Task<int> GetPageLengthAsync(string endpoint)


{
var client = new HttpClient();
var uri = new Uri(endpoint);
byte[] content = await client.GetByteArrayAsync(uri);
return content.Length;
}

C# também oferece suporte a uma instrução await foreach para iterar uma coleção
apoiada por uma operação assíncrona, como uma API de paginação GraphQL. O
exemplo a seguir lê dados em partes, retornando um iterador que fornece acesso a cada
elemento quando estiver disponível:

C#

public static async IAsyncEnumerable<int> ReadSequence()


{
int index = 0;
while (index < 100)
{
int[] nextChunk = await GetNextChunk(index);
if (nextChunk.Length == 0)
{
yield break;
}
foreach (var item in nextChunk)
{
yield return item;
}
index++;
}
}

Os chamadores podem iterar a coleção usando uma instrução await foreach :

C#

await foreach (var number in ReadSequence())


{
Console.WriteLine(number);
}

C# fornece correspondência de padrões. Essas expressões permitem inspecionar dados


e tomar decisões com base em suas características. A correspondência de padrões
fornece uma ótima sintaxe para fluxo de controle baseado em dados. O código a seguir
mostra como os métodos para as operações booleanas e, or e xor podem ser expressos
usando a sintaxe de correspondência de padrões:

C#

public static bool Or(bool left, bool right) =>


(left, right) switch
{
(true, true) => true,
(true, false) => true,
(false, true) => true,
(false, false) => false,
};

public static bool And(bool left, bool right) =>


(left, right) switch
{
(true, true) => true,
(true, false) => false,
(false, true) => false,
(false, false) => false,
};
public static bool Xor(bool left, bool right) =>
(left, right) switch
{
(true, true) => false,
(true, false) => true,
(false, true) => true,
(false, false) => false,
};

As expressões de correspondência de padrões podem ser simplificadas usando _ como


um resumo para qualquer valor. O exemplo a seguir mostra como você pode simplificar
os métodos e:

C#

public static bool ReducedAnd(bool left, bool right) =>


(left, right) switch
{
(true, true) => true,
(_, _) => false,
};
Finalmente, como parte do ecossistema .NET, você pode usar o Visual Studio ou o
Visual Studio Code com o C# DevKit . Essas ferramentas fornecem uma
compreensão avançada de C#, incluindo o código que você escreve. Eles também
fornecem recursos de depuração.

6 Colaborar conosco no Comentários do .NET


GitHub O .NET é um projeto código aberto.
A fonte deste conteúdo pode Selecione um link para fornecer
ser encontrada no GitHub, onde comentários:
você também pode criar e
revisar problemas e solicitações  Abrir um problema de
de pull. Para obter mais documentação
informações, confira o nosso
guia para colaboradores.  Fornecer comentários sobre o
produto
Roteiro para desenvolvedores Java
aprendendo C#
Artigo • 12/04/2024

O C# e o Java têm muitas semelhanças. Ao aprender C#, você pode aplicar grande parte
do seu conhecimento prévio de programação em Java:

1. Sintaxe semelhante: Java e C# estão na família C de idiomas. Essa similaridade


significa que você já pode ler e entender C#. Há algumas diferenças, mas a maior
parte da sintaxe é igual a Java e C. As chaves e os ponto-e-vírgula são familiares.
As instruções de controle, como if , else e switch , são as mesmas. As instruções
de loop for , while e do ... while são as mesmas. As mesmas palavras-chave para
class , struct e interface estão em ambos os idiomas. Os modificadores de

acesso de public até private são os mesmos. Mesmo muitos dos tipos internos
usam as mesmas palavras-chave: int , string e double .
2. Paradigma orientado a objeto: Java e C# são linguagens orientadas a objetos. Os
conceitos de polimorfismo, abstração e encapsulamento se aplicam em ambas as
linguagens. Ambos adicionaram novos constructos, mas os principais recursos
ainda são relevantes.
3. Fortemente tipado: Java e C# são linguagens fortemente tipadas. Você declara o
tipo de dados de variáveis, explicitamente ou implicitamente. O compilador impõe
a segurança do tipo. O compilador captura erros relacionados ao tipo em seu
código antes de executar o código.
4. Multiplataforma: Java e C# são multiplataforma. Você pode executar suas
ferramentas de desenvolvimento em sua plataforma favorita. Seu aplicativo pode
ser executado em várias plataformas. Sua plataforma de desenvolvimento não
precisa necessariamente corresponder à plataforma de destino.
5. Tratamento de exceção: Java e C# geram exceções para indicar erros. Ambos usam
blocos try - catch - finally para lidar com exceções. As classes exception têm
nomes e hierarquias de herança semelhantes. Uma diferença é que o C# não tem o
conceito de exceções verificadas. Qualquer método pode (em teoria) gerar
qualquer exceção.
6. Bibliotecas padrão: o runtime do .NET e a Biblioteca Padrão Java (JSL) têm suporte
para tarefas comuns. Ambos têm ecossistemas extensos para outros pacotes de
software livre. No C#, o gerenciador de pacotes é NuGet . É análogo ao Maven.
7. Coleta de lixo: ambos os idiomas empregam o gerenciamento automático de
memória por meio da coleta de lixo. O runtime recupera a memória de objetos
que não são referenciados. Uma diferença é que o C# permite que você crie tipos
de valor, como tipos struct .
Você será produtivo em C# quase imediatamente devido às semelhanças. À medida que
você progride, você deve aprender recursos e idiomas em C# que não estão disponíveis
em Java:

1. Correspondência de padrões: a correspondência de padrões permite instruções


condicionais concisas e expressões com base na forma de estruturas de dados
complexas. A instrução is verifica se uma variável "é" algum padrão. A expressão
baseada em padrão switch fornece uma sintaxe avançada para inspecionar uma
variável e tomar decisões com base em suas características.
2. Interpolação de cadeia de caracteres e literais de cadeia de caracteres brutos: a
interpolação de cadeia de caracteres permite inserir expressões avaliadas em uma
cadeia de caracteres, em vez de usar identificadores posicionais. Literais de cadeia
de caracteres brutos fornecem uma maneira de minimizar sequências de escape
no texto.
3. Tipos anuláveis e não anuláveis: O C# dá suporte a tipos de valor anuláveis e tipos
de referência anuláveis acrescentando o sufixo ? a um tipo. Para tipos que
permitem valor nulo, o compilador avisa se você não verificar null antes de
desreferenciar a expressão. Para tipos não anuláveis, o compilador avisa se você
pode estar atribuindo um valor null a essa variável. Tipos de referência não
anuláveis minimizam erros de programação que lançam um
System.NullReferenceException.
4. Métodos de extensão: em C#, você pode criar métodos que estendem uma classe
ou interface. Os métodos de extensão estendem o comportamento de um tipo de
uma biblioteca ou todos os tipos que implementam uma determinada interface.
5. LINQ: a consulta integrada à linguagem (LINQ) fornece uma sintaxe comum para
consultar e transformar dados, independentemente de seu armazenamento.
6. Funções locais: em C#, você pode aninhar funções dentro de métodos ou outras
funções locais. As funções locais fornecem mais uma camada de encapsulamento.

Há outros recursos em C# que não existem em Java. Você verá recursos como async e
await e instruções using para liberar automaticamente recursos não obrigatórios.

Há também alguns recursos semelhantes entre C# e Java que têm diferenças sutis, mas
importantes:

1. Propriedades e indexadores: ambas as propriedades e indexadores (tratando uma


classe como uma matriz ou dicionário) têm suporte de idioma. Em Java, eles estão
nomeando convenções para métodos que começam com get e set .
2. Registros: em C#, os registros podem ser tipos class (referência) ou struct
(valor). Os registros C# podem ser imutáveis, mas não precisam necessariamente
ser imutáveis.
3. As tuplas têm sintaxe diferente em C# e Java.
4. Os atributos são semelhantes às anotações Java.

Por fim, há recursos de linguagem Java que não estão disponíveis em C#:

1. Exceções verificadas: em C#, qualquer método poderia teoricamente gerar


qualquer exceção.
2. Covariância de matriz marcada: em C#, as matrizes não são covariantes com
segurança. Você deve usar as classes e interfaces de coleção genéricas se precisar
de estruturas covariantes.

No geral, aprender C# para um desenvolvedor experiente em Java deve ser suave. Você
encontrará expressões familiares suficientes para ser rapidamente produtivo, e
aprenderá os novos idiomas rapidamente.

6 Colaborar conosco no Comentários do .NET


GitHub O .NET é um projeto código aberto.
A fonte deste conteúdo pode Selecione um link para fornecer
ser encontrada no GitHub, onde comentários:
você também pode criar e
revisar problemas e solicitações  Abrir um problema de
de pull. Para obter mais documentação
informações, confira o nosso
guia para colaboradores.  Fornecer comentários sobre o
produto
Roteiro para desenvolvedores JavaScript
e TypeScript aprendendo C#
Artigo • 16/04/2024

C#, TypeScript e JavaScript são todos membros da família C de linguagens de


programação. As semelhanças entre as linguagens de programação ajudam você a se
tornar produtivo rapidamente em C#.

1. Sintaxe semelhante: JavaScript, TypeScript e C# estão na família C de linguagens


de programação. Essa similaridade significa que você já pode ler e entender C#. Há
algumas diferenças, mas a maior parte da sintaxe é igual a JavaScript e C. As
chaves e os pontos-e-vírgulas são familiares. As instruções de controle, como if ,
else e switch , são as mesmas. As instruções de loop for , while e do ... while são

as mesmas. As mesmas palavras-chave para class , struct e interface estão em


ambos C# e TypeScript. Os modificadores de acesso em TypeScript e C#, de
public até private , são os mesmos.

2. O token => : todas as linguagens de programação dão suporte a definições de


função leves. Em C#, elas são conhecidas como expressões lambda, em JavaScript,
normalmente são chamadas de funções de seta.
3. Hierarquias de função: todas as três linguagens dão suporte a funções locais, que
são funções definidas em outras funções.
4. Assíncrono/Espera: todas as três linguagens compartilham as mesmas palavras-
chave async e await para programação assíncrona.
5. coleta de lixo: todas as três linguagens de programação dependem de um coletor
de lixo para o gerenciamento automático de memória.
6. Modelo de evento: a sintaxe event do C#é semelhante ao modelo do JavaScript
para eventos do DOM (Modelo de Objeto do Documento).
7. Gerenciador de pacotes: o NuGet é o gerenciador de pacotes mais comum para
C# e .NET, semelhante ao npm para aplicativos JavaScript. As bibliotecas C# são
entregues em assemblies.

À medida que continuar aprendendo C#, você aprenderá conceitos que não fazem parte
do JavaScript. Alguns desses conceitos podem ser familiares se você usa o TypeScript:

1. Sistema de tipagem do C#: o C# é uma linguagem fortemente tipada. Cada


variável tem um tipo e esse tipo não pode ser alterado. Você define os tipos class
ou struct . Você pode definir definições interface que definem o comportamento
implementado por outros tipos. O TypeScript inclui muitos desses conceitos, mas
como o TypeScript é criado em JavaScript, o sistema de tipos não é tão estrito.
2. Correspondência de padrões: a correspondência de padrões permite instruções
condicionais concisas e expressões com base na forma de estruturas de dados
complexas. A expressão is verifica se uma variável "é" algum padrão. A expressão
switch baseada em padrão fornece uma sintaxe avançada para inspecionar uma
variável e tomar decisões com base nas características dela.
3. Interpolação de cadeia de caracteres e literais de cadeia de caracteres brutos: a
interpolação de cadeia de caracteres permite inserir expressões avaliadas em uma
cadeia de caracteres, em vez de usar identificadores posicionais. Literais de cadeia
de caracteres brutos fornecem uma maneira de minimizar sequências de escape
no texto.
4. Tipos anuláveis e não anuláveis: O C# dá suporte a tipos de valor anuláveis e tipos
de referência anuláveis acrescentando o sufixo ? a um tipo. Para tipos que
permitem valor nulo, o compilador avisa se você não verificar null antes de
desreferenciar a expressão. Para tipos não anuláveis, o compilador avisa se você
pode estar atribuindo um valor null a essa variável. Esses recursos podem
minimizar a geração de uma System.NullReferenceException pelo seu aplicativo. A
sintaxe pode ser familiar do uso de ? pelo TypeScript para propriedades opcionais.
5. LINQ: a consulta integrada à linguagem (LINQ) fornece uma sintaxe comum para
consultar e transformar dados, independentemente de seu armazenamento.

À medida que você aprende mais, outras diferenças se tornam aparentes, mas muitas
dessas diferenças são menores no escopo.

Alguns recursos e expressões familiares de JavaScript e TypeScript não estão disponíveis


em C#:

1. tipos dinâmicos: o C# usa tipagem estática. Uma declaração de variável inclui o


tipo e esse tipo não pode ser alterado. Há um tipo dynamic em C# que fornece
associação em tempo de execução.
2. Herança prototípica: a herança de C# faz parte da declaração de tipo. Uma
declaração class de C# declara qualquer classe base. No JavaScript, você pode
definir a propriedade __proto__ para definir o tipo base em qualquer instância.
3. Linguagem interpretada: o código C# precisa ser compilado antes de você
executá-lo. O código JavaScript pode ser executado diretamente no navegador.

Além disso, mais alguns recursos do TypeScript não estão disponíveis em C#:

1. Tipos de união: o C# não dá suporte a tipos de união. No entanto, há propostas de


design em andamento.
2. Decoradores: o C# não tem decoradores. Alguns decoradores comuns, como
@sealed , são palavras-chave reservadas em C#. Outros decoradores comuns
podem ter Atributos correspondentes. Para outros decoradores, você pode criar
seus atributos.
3. Sintaxe mais indulgente: o compilador C# analisa o código mais estritamente do
que o JavaScript.

Se você estiver criando um aplicativo Web, considere usar o Blazor para criar seu
aplicativo. O Blazor é uma estrutura da Web de pilha completa criada para .NET e C#. Os
componentes do Blazor podem ser executados no servidor como assemblies .NET ou
então no cliente, usando WebAssembly. O Blazor dá suporte à interoperabilidade com
suas bibliotecas JavaScript ou TypeScript favoritas.

6 Colaborar conosco no Comentários do .NET


GitHub O .NET é um projeto código aberto.
A fonte deste conteúdo pode Selecione um link para fornecer
ser encontrada no GitHub, onde comentários:
você também pode criar e
revisar problemas e solicitações  Abrir um problema de
de pull. Para obter mais documentação
informações, confira o nosso
guia para colaboradores.  Fornecer comentários sobre o
produto
Roteiro para desenvolvedores do
Python aprendendo o C#
Artigo • 05/07/2024

C# e Python compartilham conceitos semelhantes. Esses constructos familiares o


ajudam a aprender o C# quando você já conhece o Python.

1. Orientado a objeto: Python e C# são linguagens orientadas aos objetos. Todos os


conceitos em torno das classes no Python se aplicam no C#, mesmo que a sintaxe
seja diferente.
2. Multiplataforma: Python e C# são linguagens de multiplataforma. Os aplicativos
escritos em qualquer idioma podem ser executados em várias plataformas.
3. Coleta de lixo: ambas as linguagens empregam o gerenciamento automático de
memória pela coleta de lixo. O runtime recupera a memória de objetos que não
são referenciados.
4. Fortemente tipado: Python e C# são linguagens fortemente tipadas. A coerção de
tipos não ocorre de maneira implícita. Há diferenças descritas posteriormente, pois
o C# é tipado estaticamente, enquanto o Python é digitado dinamicamente.
5. Assíncrono/Aguardar: os recurso async e await do Python foram inspirados
diretamente pelos suportes async e await do C#.
6. Padrões correspondentes: a expressão match do Python e os padrões
correspondentes são semelhantes à expressão padrões correspondentes switch do
C#. Use-as para inspecionar uma expressão de dados complexa para determinar se
ela corresponde a um padrão.
7. Palavras-chave de instrução: Python e C# compartilham muitas palavras-chave,
como if , else , while , for , e muitas outras. Embora nem todas as sintaxes sejam
iguais, há similaridade suficiente para ler o C# se você conhecer o Python.

Ao começar a aprender o C#, você aprenderá esses conceitos importantes em que o C#


é diferente do Python:

1. Recuo versus tokens: no Python, as linhas novas e o recuo são elementos sintáticos
de primeira classe. No C#, o espaço em branco não é significativo. Tokens, como ;
instruções separadas e outros tokens { e } escopo do bloco de controle para if
e outras instruções de bloco. Entretanto, para facilitar a leitura, a maioria dos
estilos de codificação (incluindo o estilo usado nesses documentos) usa o recuo
para reforçar os escopos de bloco declarados por { e } .
2. Digitação estática: no C#, uma declaração de variável inclui seu tipo. Reatribuir
uma variável em um objeto de um tipo diferente gera um erro do compilador. No
Python, o tipo poderá ser alterado quando for reatribuído.
3. Tipos que permitem valor nulo: as variáveis no C# podem ser anuláveis ou não
anuláveis. Um tipo não anulável é aquele que não pode ser nulo (ou nada). Ele
sempre se refere a um objeto válido. Por outro lado, um tipo anulável pode se
referir a um objeto válido ou nulo.
4. LINQ: as palavras-chave da expressão de consulta que compõem a Consulta
Integrada à Linguagem (LINQ) não são palavras-chave no Python. Porém, as
bibliotecas do Python, como itertools , more-itertools e py-linq , fornecem
funcionalidades semelhantes.
5. Genéricos: os genéricos no C# usam digitação estática no C# para fazer
declarações sobre os argumentos fornecidos para os parâmetros de tipo. Um
algoritmo genérico pode precisar especificar restrições que um tipo de argumento
deve satisfazer.

Por fim, há os recursos de linguagem Python que não estão disponíveis no C#:

1. Digitação estrutural (pato): no C#, os tipos têm nomes e declarações. Exceto por
tuplas, os tipos com a mesma estrutura não são intercambiáveis.
2. REPL: o C# não tem um REPL (Read-Eval-Print Loop) para criar rapidamente
protótipos de soluções.
3. Espaço em branco significativo: você precisa usar corretamente as chaves { e }
para observar o escopo de bloco.

Aprenda no C# se você souber que o Python é uma jornada tranquila. Os idiomas têm
conceitos e expressões semelhantes a serem usados.

6 Colaborar conosco no Comentários do .NET


GitHub O .NET é um projeto código aberto.
A fonte deste conteúdo pode Selecione um link para fornecer
ser encontrada no GitHub, onde comentários:
você também pode criar e
revisar problemas e solicitações  Abrir um problema de
de pull. Para obter mais documentação
informações, confira o nosso
guia para colaboradores.  Fornecer comentários sobre o
produto
Estratégia de C# anotada
Artigo • 09/05/2023

Continuaremos desenvolvendo o C# para atender às necessidades em constante


mudança dos desenvolvedores e continuaremos sendo uma linguagem de programação
de última geração. Inovaremos de forma ávida e ampla em colaboração com as equipes
responsáveis pelas bibliotecas do .NET, pelas ferramentas de desenvolvedor e pelo
suporte à carga de trabalho, ao mesmo tempo em que temos o cuidado de permanecer
dentro manter o espírito da linguagem. Reconhecendo a diversidade de domínios em
que o C# está sendo usado, preferiremos aprimoramentos de linguagem e desempenho
que beneficiem todos ou a maioria dos desenvolvedores e mantenham um alto
compromisso com a compatibilidade com versões anteriores. Continuaremos
capacitando o ecossistema mais amplo do .NET e aumentando seu papel no futuro do
C#, mantendo a administração de decisões de design.

Como a estratégia orienta o C#


A estratégia do C# orienta nossas decisões sobre a evolução do C#, e essas anotações
fornecem insights sobre como pensamos sobre as principais instruções.

"vamos inovar de forma ávida e ampla"

A comunidade C# continua crescendo e a linguagem C# continua evoluindo para


atender às necessidades e expectativas da comunidade. Buscamos inspirações de
diversas fontes para selecionar recursos que beneficiem um grande segmento de
desenvolvedores C# e que forneçam melhorias consistentes em relação à produtividade,
legibilidade e desempenho.

"ter cuidado para permanecer dentro do espírito da linguagem"

Avaliamos novas ideias no espírito e na história da linguagem C#. Priorizamos inovações


que fazem sentido para a maioria dos desenvolvedores de C# existentes.

"melhorias que beneficiam todos ou a maioria dos desenvolvedores"

Os desenvolvedores usam C# em todas as cargas de trabalho do .NET, como front e


back-ends da Web, desenvolvimento nativo de nuvem, desenvolvimento para desktop e
criação de aplicativos multiplataforma. Mantemos o foco em novos recursos que têm
mais impacto direto ou capacitando melhorias para bibliotecas comuns. O
desenvolvimento de recursos de linguagem inclui a integração com nossas ferramentas
de desenvolvedor e recursos de aprendizagem.

"alto compromisso com compatibilidade com versões anteriores"

Respeitamos que haja uma grande quantidade de código C# em uso atualmente.


Qualquer possível alteração interruptiva é cuidadosamente considerada em relação à
escala e ao impacto da interrupção na comunidade C#.

"mantendo a administração"

O design da linguagem C# ocorre ao ar livre com a participação da comunidade.


Qualquer pessoa pode propor novos recursos do C# em nossos repositórios do
GitHub . A Equipe de Design de Linguagem toma as decisões finais depois de
ponderar os cometários da comunidade.

6 Collaborate with us on
GitHub .NET feedback
The .NET documentation is open
The source for this content can
source. Provide feedback here.
be found on GitHub, where you
can also create and review
 Open a documentation issue
issues and pull requests. For
more information, see our
 Provide product feedback
contributor guide.
Introdução ao C#
Artigo • 11/04/2024

Bem-vindo aos tutoriais de introdução ao C#. Essas lições começam com código
interativo que pode ser executado em seu navegador. Você pode aprender as noções
básicas do C# na série de vídeos de introdução ao C# antes de iniciar essas lições
interativas.
https://docs.microsoft.com/shows/CSharp-101/What-is-C/player

As primeiras lições explicam os conceitos de C# usando pequenos snippets de código.


Você aprenderá os conceitos básicos da sintaxe de C# e como trabalhar com tipos de
dados como cadeias de caracteres, números e valores boolianos. É tudo interativo e
você começará a gravar e executar o código em questão de minutos. Estas primeiras
lições não exigem conhecimento prévio de programação ou da linguagem C#.

Você pode experimentar esses tutoriais em ambientes diferentes. Os conceitos que você
aprenderá são os mesmos. A diferença é qual experiência você prefere:

No navegador, na plataforma de documentos: essa experiência incorpora uma


janela de código C# executável em páginas de documentos. Você escreve e
executa o código C# no navegador.
Na experiência do Microsoft Learn. Este roteiro de aprendizagem contém vários
módulos que ensinam as noções básicas de C#.
No Jupyter no Binder . Você pode experimentar o código C# em um notebook
Jupyter no Binder.
Em seu computador local. Depois de explorar online, você pode baixar o SDK do
.NET e criar programas em seu computador.

Todos os tutoriais de introdução posteriores à lição Olá, Mundo estão disponíveis por
meio da experiência de navegador online ou em seu próprio ambiente de
desenvolvimento local. No final de cada tutorial, você decidirá se deseja continuar com
a próxima lição online ou no próprio computador. Há links para ajudar você a configurar
seu ambiente e continuar com o próximo tutorial no computador.

Olá, Mundo
No tutorial Olá, Mundo, você criará o programa C# mais básico. Você explorará o tipo
string e como trabalhar com texto. Você também pode usar o caminho no Microsoft

Learn ou o Jupyter no Binder .


Números em C#
No tutorial Números em C#, você aprenderá como os computadores armazenam
números e como executar cálculos com diferentes tipos de número. Você aprenderá os
conceitos básicos de arredondamento e como executar cálculos matemáticos usando
C#. Este tutorial também está disponível para execução local no seu computador.

Esse tutorial pressupõe que você já tenha concluído a lição Olá, Mundo.

Loops e branches
O tutorial Branches e loops ensina os conceitos básicos da seleção de diferentes
caminhos de execução de código com base nos valores armazenados em variáveis. Você
aprenderá os conceitos básicos do fluxo de controle, que são os fundamentos de como
os programas tomam decisões e escolhem ações diferentes. Este tutorial também está
disponível para execução local no seu computador.

Esse tutorial pressupõe que você já tenha concluído as lições Olá, Mundo e Números
em C#.

Coleções de lista
A lição Coleções de lista fornece um tour pelo tipo Coleções de lista que armazena as
sequências de dados. Você aprenderá a adicionar e remover itens, pesquisar itens e
classificar listas. Você explorará os diferentes tipos de listas. Este tutorial também está
disponível para execução local no seu computador.

Esse tutorial pressupõe que você já tenha concluído as lições listadas acima.

Exemplos básicos do LINQ


Este exemplo requer a ferramenta global dotnet-try . Depois de instalar a ferramenta e
clonar o repositório try-samples , você poderá aprender LINQ (Consulta Integrada à
Linguagem) por meio de um conjunto de amostras básicas que você pode executar
interativamente. Você pode explorar diferentes maneiras de consultar, explorar e
transformar sequências de dados.

Comentários
Esta página foi útil?  Yes  No

Fornecer comentários sobre o produto


Configurar o ambiente local
Artigo • 10/05/2023

A primeira etapa para executar um tutorial em seu computador é configurar um


ambiente de desenvolvimento.

Recomendamos o Visual Studio para Windows ou Mac. Você pode baixar uma
versão gratuita na página de downloads do Visual Studio . O Visual Studio inclui
o SDK do .NET.
Você também pode usar o editor do Visual Studio Code . Será preciso instalar o
SDK do .NET mais recente separadamente.
Se preferir outro editor, você precisará instalar o SDK do .NET mais recente.

Fluxo de desenvolvimento de aplicativos básico


As instruções nesses tutoriais pressupõem que você esteja usando a CLI do .NET para
criar, compilar e executar aplicativos. Você usará os comandos a seguir:

dotnet new cria um aplicativo. Este comando gera os arquivos e ativos necessários
para o seu aplicativo. Todos os tutoriais de introdução ao C# usam o tipo de
aplicativo console . Depois de conhecer as noções básicas, você poderá expandir
para outros tipos de aplicativo.
dotnet build cria o executável.
dotnet run executa o executável.

Se você usar o Visual Studio 2019 para estes tutoriais, escolherá uma seleção de menu
do Visual Studio quando um tutorial o orientar a executar um destes comandos da CLI:

File>New>Project cria um aplicativo.


O modelo de projeto Console Application é recomendado.
Você terá a opção de especificar uma estrutura de destino. Os tutoriais abaixo
funcionam melhor ao direcionar o .NET 5 ou superior.
Build>Build Solution cria o executável.
Debug>Start Without Debugging executa o executável.

Escolha seu tutorial


Você pode iniciar com qualquer um dos seguintes tutoriais:
Números em C#
No tutorial Números em C#, você aprenderá como os computadores armazenam
números e como executar cálculos com diferentes tipos de número. Você aprenderá os
conceitos básicos de arredondamento e como executar cálculos matemáticos usando
C#.

Esse tutorial pressupõe a conclusão da lição Olá, Mundo.

Loops e branches
O tutorial Branches e loops ensina os conceitos básicos da seleção de diferentes
caminhos de execução de código com base nos valores armazenados em variáveis. Você
aprenderá os conceitos básicos do fluxo de controle, que são os fundamentos de como
os programas tomam decisões e escolhem ações diferentes.

Esse tutorial pressupõe a conclusão das lições Olá, Mundo e Números em C#.

Coleções de lista
A lição Coleções de lista fornece um tour pelo tipo Coleções de lista que armazena as
sequências de dados. Você aprenderá a adicionar e remover itens, pesquisar itens e
classificar listas. Você explorará os diferentes tipos de listas.

Esse tutorial pressupõe a conclusão das lições listadas acima.


Como usar números inteiros e de ponto
flutuante em C#
Artigo • 10/05/2023

Este tutorial ensina tipos numéricos em C#. Você escreverá pequenas quantidades de
código, depois compilará e executará esse código. O tutorial contém uma série de lições
que exploram números e operações matemáticas em C#. Estas lições ensinam os
princípios básicos da linguagem C#.

 Dica

Para colar um snippet de código dentro do modo de foco, você deve usar o atalho
de teclado ( Ctrl + v ou cmd + v ).

Pré-requisitos
Este tutorial espera que você tenha um computador configurado para desenvolvimento
local. Consulte Configurar seu ambiente local para obter instruções de instalação e uma
visão geral do desenvolvimento de aplicativos no .NET.

Se você não quiser configurar um ambiente local, consulte a versão interativa no


navegador deste tutorial.

Explorar a matemática de inteiros


Crie um diretório chamado numbers-quickstart. Torne esse o diretório atual e execute o
seguinte comando:

CLI do .NET

dotnet new console -n NumbersInCSharp -o .

) Importante

Os modelos C# para o .NET 6 usam instruções de nível superior. Se você já tiver


atualizado para o .NET 6, talvez seu aplicativo não corresponda ao código descrito
neste artigo. Para obter mais informações, consulte o artigo sobre Novos modelos
C# geram instruções de nível superior
O SDK do .NET 6 também adiciona um conjunto de diretivas implícitas global
using para projetos que usam os seguintes SDKs:

Microsoft.NET.Sdk
Microsoft.NET.Sdk.Web
Microsoft.NET.Sdk.Worker

Essas diretivas implícitas global using incluem os namespaces mais comuns para o
tipo de projeto.

Abra Program.cs em seu editor favorito e substitua o conteúdo do arquivo pelo seguinte
código:

C#

int a = 18;
int b = 6;
int c = a + b;
Console.WriteLine(c);

Execute este código digitando dotnet run na janela de comando.

Você viu apenas uma das operações matemáticas fundamentais com números inteiros.
O tipo int representa um inteiro, zero, um número inteiro positivo ou negativo. Você
usa o símbolo + para adição. Outras operações matemáticas comuns para inteiros
incluem:

- para subtração
* para multiplicação

/ para divisão

Comece explorando essas diferentes operações. Adicione estas linhas após a linha que
grava o valor de c :

C#

// subtraction
c = a - b;
Console.WriteLine(c);

// multiplication
c = a * b;
Console.WriteLine(c);

// division
c = a / b;
Console.WriteLine(c);

Execute este código digitando dotnet run na janela de comando.

Você também pode experimentar executar várias operações matemáticas na mesma


linha, se quiser. Experimente c = a + b - 12 * 17; , por exemplo. É permitido misturar
variáveis e números constantes.

 Dica

À medida que explora C# (ou qualquer linguagem de programação), você


cometerá erros ao escrever o código. O compilador encontrará esses erros e os
reportará a você. Quando a saída contiver mensagens de erro, analise atentamente
o código de exemplo e o código em sua janela para ver o que deve ser corrigido.
Esse exercício ajudará você a conhecer a estrutura do código C#.

Você terminou a primeira etapa. Antes de iniciar a próxima seção, vamos passar o
código atual para um método separado. Um método é uma série de instruções
agrupadas que receberam um nome. Você nomeia um método escrevendo o nome do
método seguido por () . Organizar seu código em métodos torna mais fácil começar a
trabalhar com um novo exemplo. Quando você terminar, seu código deverá ter a
seguinte aparência:

C#

WorkWithIntegers();

void WorkWithIntegers()
{
int a = 18;
int b = 6;
int c = a + b;
Console.WriteLine(c);

// subtraction
c = a - b;
Console.WriteLine(c);

// multiplication
c = a * b;
Console.WriteLine(c);

// division
c = a / b;
Console.WriteLine(c);
}

A linha WorkWithIntegers(); invoca o método. O código a seguir declara o método e o


define.

Explorar a ordem das operações


Comente a chamada para WorkingWithIntegers() . Isso tornará a saída menos
congestionada enquanto você trabalha nesta seção:

C#

//WorkWithIntegers();

O // inicia um comentário em C#. Os comentários são qualquer texto que você queira
manter em seu código-fonte, mas não queria executar como código. O compilador não
gera nenhum código executável a partir dos comentários. Como WorkWithIntegers() é
um método, você precisa apenas comentar uma linha.

A linguagem C# define a precedência de operações matemáticas diferentes com regras


consistentes às regras que você aprendeu em matemática. Multiplicação e divisão têm
precedência sobre adição e subtração. Explore isso adicionando o seguinte código a
WorkWithIntegers() após a chamada e executando dotnet run :

C#

int a = 5;
int b = 4;
int c = 2;
int d = a + b * c;
Console.WriteLine(d);

A saída demonstra que a multiplicação é executada antes da adição.

Você pode forçar uma ordem diferente de operações, adicionando parênteses para
delimitar a operação, ou operações, que você quer realizar primeiro. Adicione as
seguintes linhas e execute novamente:

C#

d = (a + b) * c;
Console.WriteLine(d);
Explore mais, combinando várias operações diferentes. Adicione algo semelhante às
linhas a seguir. Tente dotnet run novamente.

C#

d = (a + b) - 6 * c + (12 * 4) / 3 + 12;
Console.WriteLine(d);

Talvez você tenha observado um comportamento interessante com relação aos números
inteiros. A divisão de inteiros sempre produz um resultado inteiro, mesmo quando você
espera que o resultado inclua uma parte decimal ou fracionária.

Se você ainda não viu esse comportamento, tente o seguinte:

C#

int e = 7;
int f = 4;
int g = 3;
int h = (e + f) / g;
Console.WriteLine(h);

Digite dotnet run novamente para ver os resultados.

Antes de avançarmos, pegue todo código que você escreveu nesta seção e coloque-o
em um novo método. Chame esse novo método de OrderPrecedence . Seu código deve
ter a seguinte aparência:

C#

// WorkWithIntegers();
OrderPrecedence();

void WorkWithIntegers()
{
int a = 18;
int b = 6;
int c = a + b;
Console.WriteLine(c);

// subtraction
c = a - b;
Console.WriteLine(c);

// multiplication
c = a * b;
Console.WriteLine(c);
// division
c = a / b;
Console.WriteLine(c);
}

void OrderPrecedence()
{
int a = 5;
int b = 4;
int c = 2;
int d = a + b * c;
Console.WriteLine(d);

d = (a + b) * c;
Console.WriteLine(d);

d = (a + b) - 6 * c + (12 * 4) / 3 + 12;
Console.WriteLine(d);

int e = 7;
int f = 4;
int g = 3;
int h = (e + f) / g;
Console.WriteLine(h);
}

Explorar a precisão de inteiros e limites


Esse último exemplo mostrou que uma divisão de inteiros trunca o resultado. Você pode
obter o restante usando o operador module, o caractere % . Tente o seguinte código
após a chamada de método para OrderPrecedence() :

C#

int a = 7;
int b = 4;
int c = 3;
int d = (a + b) / c;
int e = (a + b) % c;
Console.WriteLine($"quotient: {d}");
Console.WriteLine($"remainder: {e}");

O tipo de inteiro C# difere do inteiros matemáticos de outra forma: o tipo int tem
limites mínimo e máximo. Adicione este código para ver esses limites:

C#
int max = int.MaxValue;
int min = int.MinValue;
Console.WriteLine($"The range of integers is {min} to {max}");

Se um cálculo produzir um valor que excede esses limites, você terá uma condição de
estouro negativo ou estouro. A resposta parece quebrar de um limite para o outro.
Adicione estas duas linhas para ver um exemplo:

C#

int what = max + 3;


Console.WriteLine($"An example of overflow: {what}");

Observe que a resposta é muito próxima do mínimo inteiro (negativo). É o mesmo que
min + 2 . A operação de adição estourou os valores permitidos para números inteiros. A

resposta é um número negativo muito grande, pois um estouro "envolve" do maior


valor de inteiro possível para o menor.

Há outros tipos numéricos com limites e precisão diferentes que você usaria quando o
tipo int não atendesse às suas necessidades. Vamos explorar esses outros tipos em
seguida. Antes de iniciar a próxima seção, mova o código que você escreveu nesta
seção para um método separado. Nomeie-o como TestLimits .

Trabalhar com o tipo Double


O tipo numérico double representa um número de ponto flutuante de precisão dupla.
Esses termos podem ser novidade para você. Um número de ponto flutuante é útil para
representar números não integrais que podem ser muito grandes ou pequenos em
magnitude. Precisão dupla é um termo relativo que descreve o número de dígitos
binários usados para armazenar o valor. Os números de precisão dupla têm o dobro do
número de dígitos binários do que os de precisão simples. Em computadores
modernos, é mais comum usar números de precisão dupla do que de precisão simples.
Números de precisão simples são declarados usando a palavra-chave float . Vamos
explorar. Adicione o seguinte código e veja o resultado:

C#

double a = 5;
double b = 4;
double c = 2;
double d = (a + b) / c;
Console.WriteLine(d);
Observe que a resposta inclui a parte decimal do quociente. Experimente uma expressão
ligeiramente mais complicada com duplos:

C#

double e = 19;
double f = 23;
double g = 8;
double h = (e + f) / g;
Console.WriteLine(h);

O intervalo de um valor duplo é muito maior do que valores inteiros. Experimente o


código a seguir abaixo do código que você escreveu até o momento:

C#

double max = double.MaxValue;


double min = double.MinValue;
Console.WriteLine($"The range of double is {min} to {max}");

Esses valores são impressos em notação científica. O número à esquerda do E é o


significando. O número à direita é o expoente, como uma potência de 10. Assim como
os números decimais em matemática, os duplos em C# podem ter erros de
arredondamento. Experimente esse código:

C#

double third = 1.0 / 3.0;


Console.WriteLine(third);

Você sabe que 0.3 repetido um número finito de vezes não é exatamente o mesmo que
1/3 .

Desafio

Experimente outros cálculos com números grandes, números pequenos, multiplicação e


divisão usando o tipo double . Experimente cálculos mais complicados. Após algum
tempo no desafio, pegue o código que você escreveu e coloque-o em um novo
método. Chame esse novo método de WorkWithDoubles .

Trabalhar com tipos decimais


Você viu os tipos numéricos básicos em C#: inteiros e duplos. Ainda há outro tipo: o tipo
decimal . O tipo decimal tem um intervalo menor, mas precisão maior do que double .
Vamos dar uma olhada:

C#

decimal min = decimal.MinValue;


decimal max = decimal.MaxValue;
Console.WriteLine($"The range of the decimal type is {min} to {max}");

Observe que o intervalo é menor do que o tipo double . Veja a precisão maior com o
tipo decimal experimentando o código a seguir:

C#

double a = 1.0;
double b = 3.0;
Console.WriteLine(a / b);

decimal c = 1.0M;
decimal d = 3.0M;
Console.WriteLine(c / d);

O sufixo M nos números é o modo como você indica que uma constante deve usar o
tipo decimal . Caso contrário, o compilador assumirá o tipo double .

7 Observação

A letra M foi escolhida como a letra mais visualmente distinta entre as palavras-
chave double e decimal .

Observe que o cálculo usando o tipo decimal tem mais dígitos à direita da vírgula
decimal.

Desafio

Agora que você viu os diferentes tipos numéricos, escreva um código que calcula a área
de um círculo cujo raio é de 2,50 centímetros. Lembre-se de que a área de um círculo é
o quadrado do raio multiplicado por PI. Uma dica: o .NET contém uma constante para
PI, Math.PI, que você pode usar para esse valor. Math.PI, como todas as constantes
declaradas no namespace System.Math , é um valor double . Por esse motivo, você deve
usar valores double em vez de decimal para esse desafio.
Você deve obter uma resposta entre 19 e 20. Confira sua resposta analisando o código
de exemplo finalizado no GitHub .

Experimente outras fórmulas, se quiser.

Você concluiu o início rápido "Números em C#". Continue com o início rápido Branches
e loops em seu próprio ambiente de desenvolvimento.

Saiba mais sobre os números em C# nos artigos a seguir:

Tipos numéricos integrais


Tipos numéricos de ponto flutuante
Conversões numéricas internas
Instruções e loops em C# if – tutorial
de lógica condicional
Artigo • 10/05/2023

Este tutorial ensina a escrever código C# que examina variáveis e muda o caminho de
execução com base nessas variáveis. Escreva o código em C# e veja os resultados da
compilação e da execução. O tutorial contém uma série de lições que exploram
construções de branches e loops em C#. Estas lições ensinam os princípios básicos da
linguagem C#.

 Dica

Para colar um snippet de código dentro do modo de foco, você deve usar o atalho
de teclado ( Ctrl + v ou cmd + v ).

Pré-requisitos
Este tutorial espera que você tenha um computador configurado para desenvolvimento
local. Consulte Configurar seu ambiente local para obter instruções de instalação e uma
visão geral do desenvolvimento de aplicativos no .NET.

Se você preferir executar o código sem precisar configurar um ambiente local, consulte
a versão interativa no navegador deste tutorial.

Tome decisões usando a instrução if


Crie um diretório chamado branches-tutorial. Torne esse o diretório atual e execute o
seguinte comando:

CLI do .NET

dotnet new console -n BranchesAndLoops -o .

) Importante

Os modelos C# para o .NET 6 usam instruções de nível superior. Se você já tiver


atualizado para o .NET 6, talvez seu aplicativo não corresponda ao código descrito
neste artigo. Para obter mais informações, consulte o artigo sobre Novos modelos
C# geram instruções de nível superior

O SDK do .NET 6 também adiciona um conjunto de diretivas implícitas global


using para projetos que usam os seguintes SDKs:

Microsoft.NET.Sdk
Microsoft.NET.Sdk.Web
Microsoft.NET.Sdk.Worker

Essas diretivas implícitas global using incluem os namespaces mais comuns para o
tipo de projeto.

Esse comando cria um novo aplicativo de console .NET no diretório atual. Abra
Program.cs em seu editor favorito e substitua o conteúdo pelo seguinte código:

C#

int a = 5;
int b = 6;
if (a + b > 10)
Console.WriteLine("The answer is greater than 10.");

Experimente este código digitando dotnet run na sua janela do console. Você deverá
ver a mensagem "A resposta é maior que 10" impressa no console. Modifique a
declaração de b para que a soma seja inferior a 10:

C#

int b = 3;

Digite dotnet run novamente. Como a resposta é inferior a 10, nada é impresso. A
condição que você está testando é falsa. Não há qualquer código para execução,
porque você escreveu apenas uma das ramificações possíveis para uma instrução if : a
ramificação verdadeira.

 Dica

À medida que explora C# (ou qualquer linguagem de programação), você


cometerá erros ao escrever o código. O compilador encontrará e reportará esses
erros. Verifique atentamente a saída do erro e o código que gerou o erro. O erro
do compilador geralmente pode ajudá-lo a localizar o problema.
Este primeiro exemplo mostra o poder dos tipos if e Booliano. Um Booliano é uma
variável que pode ter um dos dois valores: true ou false . C# define um tipo especial,
bool para variáveis Boolianas. A instrução if verifica o valor de um bool . Quando o

valor é true , a instrução após if é executada. Caso contrário, ela é ignorada. Esse
processo de verificação de condições e execução de instruções com base nessas
condições é eficiente.

Faça if e else funcionam juntas


Para executar um código diferente nos branches true e false, crie um branch else que
será executado quando a condição for false. Experimente uma ramificação else .
Adicione as duas últimas linhas do código abaixo (você já deve ter as quatro primeiras):

C#

int a = 5;
int b = 3;
if (a + b > 10)
Console.WriteLine("The answer is greater than 10");
else
Console.WriteLine("The answer is not greater than 10");

A instrução após a palavra-chave else é executada somente quando a condição que


estiver sendo testada for false . A combinação de if e else com condições Boolianas
fornece todos os recursos que você precisa para lidar com uma condição true e false .

) Importante

O recuo sob as instruções if e else é para leitores humanos. A linguagem C# não


considera recuos ou espaços em branco como significativos. A instrução após a
palavra-chave if ou else será executada com base na condição. Todos os
exemplos neste tutorial seguem uma prática comum para recuar linhas com base
no fluxo de controle de instruções.

Como o recuo não é significativo, você precisa usar { e } para indicar quando você
quer que mais de uma instrução faça parte do bloco executado condicionalmente. Os
programadores em C# normalmente usam essas chaves em todas as cláusulas if e
else . O exemplo a seguir é igual ao que você acabou de criar. Modifique o código

acima para coincidir com o código a seguir:


C#

int a = 5;
int b = 3;
if (a + b > 10)
{
Console.WriteLine("The answer is greater than 10");
}
else
{
Console.WriteLine("The answer is not greater than 10");
}

 Dica

No restante deste tutorial, todos os exemplos de código incluem as chaves,


seguindo as práticas aceitas.

Você pode testar condições mais complicadas. Adicione o código a seguir após o que
você escreveu até o momento:

C#

int c = 4;
if ((a + b + c > 10) && (a == b))
{
Console.WriteLine("The answer is greater than 10");
Console.WriteLine("And the first number is equal to the second");
}
else
{
Console.WriteLine("The answer is not greater than 10");
Console.WriteLine("Or the first number is not equal to the second");
}

O símbolo == testa a igualdade. Usar == distingue o teste de igualdade de atribuição,


que você viu em a = 5 .

O && representa "e". Isso significa que as duas condições devem ser verdadeiras para
executar a instrução no branch verdadeiro. Estes exemplos também mostram que você
pode ter várias instruções em cada branch condicional, desde que você coloque-as
entre { e } . Você também pode usar || para representar "ou". Adicione o código a
seguir após o que você escreveu até o momento:

C#
if ((a + b + c > 10) || (a == b))
{
Console.WriteLine("The answer is greater than 10");
Console.WriteLine("Or the first number is equal to the second");
}
else
{
Console.WriteLine("The answer is not greater than 10");
Console.WriteLine("And the first number is not equal to the second");
}

Modifique os valores de a , b e c e alterne entre && e || para explorar. Você obterá


mais compreensão de como os operadores && e || funcionam.

Você terminou a primeira etapa. Antes de iniciar a próxima seção, vamos passar o
código atual para um método separado. Isso facilita o começo do trabalho com um
exemplo novo. Coloque o código existente em um método chamado ExploreIf() .
Chame-o pela parte superior do programa. Quando você terminar essas alterações, seu
código deverá estar parecido com o seguinte:

C#

ExploreIf();

void ExploreIf()
{
int a = 5;
int b = 3;
if (a + b > 10)
{
Console.WriteLine("The answer is greater than 10");
}
else
{
Console.WriteLine("The answer is not greater than 10");
}

int c = 4;
if ((a + b + c > 10) && (a > b))
{
Console.WriteLine("The answer is greater than 10");
Console.WriteLine("And the first number is greater than the
second");
}
else
{
Console.WriteLine("The answer is not greater than 10");
Console.WriteLine("Or the first number is not greater than the
second");
}
if ((a + b + c > 10) || (a > b))
{
Console.WriteLine("The answer is greater than 10");
Console.WriteLine("Or the first number is greater than the second");
}
else
{
Console.WriteLine("The answer is not greater than 10");
Console.WriteLine("And the first number is not greater than the
second");
}
}

Comente a chamada para ExploreIf() . Isso tornará a saída menos congestionada


enquanto você trabalha nesta seção:

C#

//ExploreIf();

O // inicia um comentário em C#. Os comentários são qualquer texto que você queira
manter em seu código-fonte, mas não queria executar como código. O compilador não
gera qualquer código executável a partir dos comentários.

Use loops para repetir operações


Nesta seção, você usa loops repetir as instruções. Adicione esse código após a chamada
para ExploreIf :

C#

int counter = 0;
while (counter < 10)
{
Console.WriteLine($"Hello World! The counter is {counter}");
counter++;
}

A instrução while verifica uma condição e executa a instrução, ou bloco de instruções,


após o while . Ela verifica repetidamente a condição e executa essas instruções até que
a condição seja falsa.

Há outro operador novo neste exemplo. O ++ após a variável counter é o operador


increment. Ele adiciona 1 ao valor de counter e armazena esse valor na variável
counter .

) Importante

Verifique se a condição de loop while muda para false ao executar o código. Caso
contrário, crie um loop infinito, para que seu programa nunca termine. Isso não é
demonstrado neste exemplo, porque você tem que forçar o programa a encerrar
usando CTRL-C ou outros meios.

O loop while testa a condição antes de executar o código seguindo while . O loop do ...
while executa o código primeiro e, em seguida, verifica a condição. O loop do while é

mostrado no código a seguir:

C#

int counter = 0;
do
{
Console.WriteLine($"Hello World! The counter is {counter}");
counter++;
} while (counter < 10);

Esse loop do e o loop while anterior produzem a mesma saída.

Trabalhar com o loop for


O loop for é usado normalmente em C#. Experimente esse código:

C#

for (int index = 0; index < 10; index++)


{
Console.WriteLine($"Hello World! The index is {index}");
}

Ele faz o mesmo trabalho do loop while e do loop do que você já usou. A instrução
for tem três partes que controlam o modo como ela funciona.

A primeira parte é o inicializador for: int index = 0; declara que index é a variável do
loop, e define seu valor inicial como 0 .

A parte central é a condição for: index < 10 declara que este loop for continuará
sendo executado desde que o valor do contador seja inferior a 10.
A parte final é o iterador for: index++ especifica como modificar a variável de loop
depois de executar o bloco após a instrução for . Aqui, ela especifica que index deve
ser incrementado com 1 sempre que o bloco for executado.

Experimente você mesmo. Experimente cada uma das seguintes variações:

Altere o inicializador para iniciar em um valor diferente.


Altere a condição para parar em um valor diferente.

Quando terminar, vamos escrever um código para usar o que você aprendeu.

Há uma outra instrução de loop que não é abordada neste tutorial: a instrução foreach .
A instrução foreach repete sua instrução para cada item em uma sequência de itens. Ela
é usada com mais frequência com coleções, portanto, será abordada no próximo tutorial.

Loops aninhados criados


Um loop while , do ou for pode ser aninhado dentro de outro loop para criar uma
matriz usando a combinação de cada item no loop externo com cada item no loop
interno. Vamos fazer isso para criar um conjunto de pares alfanuméricos para
representar linhas e colunas.

Um loop for pode gerar as linhas:

C#

for (int row = 1; row < 11; row++)


{
Console.WriteLine($"The row is {row}");
}

Outro loop pode gerar as colunas:

C#

for (char column = 'a'; column < 'k'; column++)


{
Console.WriteLine($"The column is {column}");
}

Você pode aninhar um loop dentro do outro para formar pares:

C#
for (int row = 1; row < 11; row++)
{
for (char column = 'a'; column < 'k'; column++)
{
Console.WriteLine($"The cell is ({row}, {column})");
}
}

Você pode ver que o loop externo incrementa uma vez para cada execução completa do
loop interno. Inverta o aninhamento de linha e da coluna e confira as alterações.
Quando terminar, coloque o código desta seção em um método chamado
ExploreLoops() .

Combinar branches e loops


Agora que você viu a instrução if e as construções de loop na linguagem C#, verifique
se você pode escrever o código C# para encontrar a soma de todos os inteiros de 1 a 20
divisíveis por 3. Veja algumas dicas:

O operador % retorna o restante de uma operação de divisão.


A instrução if retorna a condição para ver se um número deve ser parte da soma.
O loop for pode ajudar você a repetir uma série de etapas para todos os números
de 1 a 20.

Tente você mesmo. Depois verifique como você fez. Você deve obter 63 como resposta.
Veja uma resposta possível exibindo o código completo no GitHub .

Você concluiu o tutorial "branches e loops".

Continue com o tutorial Matrizes e coleções em seu próprio ambiente de


desenvolvimento.

Saiba mais sobre esses conceitos nesses artigos:

Instruções de seleção
Instruções de iteração
Saiba como gerenciar coletas de dados
usando List<T> em C #
Artigo • 10/05/2023

Este tutorial de introdução fornece uma introdução à linguagem C# e os conceitos


básicos da classe List<T>.

Pré-requisitos
Este tutorial espera que você tenha um computador configurado para desenvolvimento
local. Consulte Configurar seu ambiente local para obter instruções de instalação e uma
visão geral do desenvolvimento de aplicativos no .NET.

Se você preferir executar o código sem precisar configurar um ambiente local, consulte
a versão interativa no navegador deste tutorial.

Um exemplo de lista básica


Crie um diretório chamado list-tutorial. Torne-o o diretório atual e execute dotnet new
console .

) Importante

Os modelos C# para o .NET 6 usam instruções de nível superior. Se você já tiver


atualizado para o .NET 6, talvez seu aplicativo não corresponda ao código descrito
neste artigo. Para obter mais informações, consulte o artigo sobre Novos modelos
C# geram instruções de nível superior

O SDK do .NET 6 também adiciona um conjunto de diretivas implícitas global


using para projetos que usam os seguintes SDKs:

Microsoft.NET.Sdk
Microsoft.NET.Sdk.Web
Microsoft.NET.Sdk.Worker

Essas diretivas global using implícitas incluem os namespaces mais comuns para o
tipo de projeto.

Abra Program.cs em seu editor favorito e substitua o código existente pelo seguinte:
C#

var names = new List<string> { "<name>", "Ana", "Felipe" };


foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}

Substitua <name> pelo seu nome. Salve o Program.cs. Digite dotnet run na janela de
console para testá-lo.

Você criou uma lista de cadeias de caracteres, adicionou três nomes a essa lista e
imprimiu os nomes em MAIÚSCULAS. Você está usando conceitos que aprendeu em
tutoriais anteriores para executar um loop pela lista.

O código para exibir nomes utiliza o recurso de interpolação de cadeia de caracteres.


Quando você precede um string com o caractere $ , pode inserir o código C# na
declaração da cadeia de caracteres. A cadeia de caracteres real substitui esse código C#
pelo valor gerado. Neste exemplo, ela substitui o {name.ToUpper()} por cada nome,
convertido em letras maiúsculas, pois você chamou o método ToUpper.

Vamos continuar explorando.

Modificar conteúdo da lista


A coleção que você criou usa o tipo List<T>. Esse tipo armazena sequências de
elementos. Especifique o tipo dos elementos entre os colchetes.

Um aspecto importante desse tipo List<T> é que ele pode aumentar ou diminuir,
permitindo que você adicione ou remova elementos. Adicione este código ao final do
programa:

C#

Console.WriteLine();
names.Add("Maria");
names.Add("Bill");
names.Remove("Ana");
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}

Você adicionou mais dois nomes ao final da lista. Também removeu um. Salve o arquivo
e digite dotnet run para testá-lo.
O List<T> também permite fazer referência a itens individuais por índice. Coloque o
índice entre os tokens [ e ] após o nome da lista. C# usa 0 para o primeiro índice.
Adicione este código diretamente abaixo do código que você acabou de adicionar e
teste-o:

C#

Console.WriteLine($"My name is {names[0]}");


Console.WriteLine($"I've added {names[2]} and {names[3]} to the list");

Você não pode acessar um índice além do fim da lista. Lembre-se de que os índices
começam com 0, portanto, o maior índice válido é uma unidade a menos do que o
número de itens na lista. Você pode verificar há quanto tempo a lista está usando a
propriedade Count. Adicione o código a seguir ao final de seu programa:

C#

Console.WriteLine($"The list has {names.Count} people in it");

Salve o arquivo e digite dotnet run novamente para ver os resultados.

Pesquisar e classificar listas


Nossos exemplos usam listas relativamente pequenas, mas seus aplicativos podem criar
listas com muitos outros elementos, chegando, às vezes, a milhares. Para localizar
elementos nessas coleções maiores, pesquise por itens diferentes na lista. O método
IndexOf procura um item e retorna o índice do item. Se o item não estiver na lista,
IndexOf retornará -1 . Adicione este código à parte inferior de seu programa:

C#

var index = names.IndexOf("Felipe");


if (index == -1)
{
Console.WriteLine($"When an item is not found, IndexOf returns
{index}");
}
else
{
Console.WriteLine($"The name {names[index]} is at index {index}");
}

index = names.IndexOf("Not Found");


if (index == -1)
{
Console.WriteLine($"When an item is not found, IndexOf returns
{index}");
}
else
{
Console.WriteLine($"The name {names[index]} is at index {index}");

Os itens em sua lista também podem ser classificados. O método Sort classifica todos os
itens na lista na ordem normal (em ordem alfabética para cadeias de caracteres).
Adicione este código à parte inferior de seu programa:

C#

names.Sort();
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}

Salve o arquivo e digite dotnet run para experimentar a versão mais recente.

Antes de iniciar a próxima seção, vamos passar o código atual para um método
separado. Isso facilita o começo do trabalho com um exemplo novo. Coloque todo o
código que você escreveu em um novo método chamado WorkWithStrings() . Chame
esse método na parte superior do programa. Quando você terminar, seu código deverá
ter a seguinte aparência:

C#

WorkWithStrings();

void WorkWithStrings()
{
var names = new List<string> { "<name>", "Ana", "Felipe" };
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}

Console.WriteLine();
names.Add("Maria");
names.Add("Bill");
names.Remove("Ana");
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}
Console.WriteLine($"My name is {names[0]}");
Console.WriteLine($"I've added {names[2]} and {names[3]} to the list");

Console.WriteLine($"The list has {names.Count} people in it");

var index = names.IndexOf("Felipe");


if (index == -1)
{
Console.WriteLine($"When an item is not found, IndexOf returns
{index}");
}
else
{
Console.WriteLine($"The name {names[index]} is at index {index}");
}

index = names.IndexOf("Not Found");


if (index == -1)
{
Console.WriteLine($"When an item is not found, IndexOf returns
{index}");
}
else
{
Console.WriteLine($"The name {names[index]} is at index {index}");

names.Sort();
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}
}

Listas de outros tipos


Você usou o tipo string nas listas até o momento. Vamos fazer List<T> usar um tipo
diferente. Vamos compilar um conjunto de números.

Adicione o seguinte ao seu programa depois de chamar WorkWithStrings() :

C#

var fibonacciNumbers = new List<int> {1, 1};

Isso cria uma lista de números inteiros e define os primeiros dois inteiros como o valor
1. Estes são os dois primeiros valores de uma sequência Fibonacci, uma sequência de
números. Cada número Fibonacci seguinte é encontrado considerando a soma dos dois
números anteriores. Adicione este código:

C#

var previous = fibonacciNumbers[fibonacciNumbers.Count - 1];


var previous2 = fibonacciNumbers[fibonacciNumbers.Count - 2];

fibonacciNumbers.Add(previous + previous2);

foreach (var item in fibonacciNumbers)


{
Console.WriteLine(item);
}

Salve o arquivo e digite dotnet run para ver os resultados.

 Dica

Para se concentrar apenas nesta seção, comente o código que chama


WorkWithStrings(); . Coloque apenas dois caracteres / na frente da chamada,
desta forma: // WorkWithStrings(); .

Desafio
Veja se você consegue combinar alguns dos conceitos desta lição e de lições anteriores.
Expanda o que você compilou até o momento com números Fibonacci. Tente escrever o
código para gerar os 20 primeiros números na sequência. (Como uma dica, o vigésimo
número Fibonacci é 6765.)

Desafio concluído
Veja um exemplo de solução analisando o código de exemplo finalizado no GitHub .

Com cada iteração do loop, você está pegando os últimos dois inteiros na lista,
somando-os e adicionando esse valor à lista. O loop será repetido até que você tenha
adicionado 20 itens à lista.

Parabéns, você concluiu o tutorial de lista. Você pode continuar com tutoriais adicionais
em seu próprio ambiente de desenvolvimento.
Saiba mais sobre como trabalhar com o tipo List no artigo Noções básicas do .NET em
coleções. Você também aprenderá muitos outros tipos de coleção.
Estrutura geral de um programa em C#
Artigo • 07/04/2023

Os programas C# podem consistir em um ou mais arquivos. Cada arquivo pode conter


zero ou mais namespaces. Um namespace pode conter tipos como classes, estruturas,
interfaces, enumerações e delegados, além de outros namespaces. Veja a seguir o
esqueleto de um programa em C# que contém todos esses elementos.

C#

// A skeleton of a C# program
using System;

// Your program starts here:


Console.WriteLine("Hello world!");

namespace YourNamespace
{
class YourClass
{
}

struct YourStruct
{
}

interface IYourInterface
{
}

delegate int YourDelegate();

enum YourEnum
{
}

namespace YourNestedNamespace
{
struct YourStruct
{
}
}
}

O exemplo anterior usa instruções de nível superior para o ponto de entrada do


programa. Esse recurso foi adicionado no C# 9. Antes do C# 9, o ponto de entrada era
um método estático chamado Main , como mostra o exemplo a seguir:
C#

// A skeleton of a C# program
using System;
namespace YourNamespace
{
class YourClass
{
}

struct YourStruct
{
}

interface IYourInterface
{
}

delegate int YourDelegate();

enum YourEnum
{
}

namespace YourNestedNamespace
{
struct YourStruct
{
}
}

class Program
{
static void Main(string[] args)
{
//Your program starts here...
Console.WriteLine("Hello world!");
}
}
}

Seções relacionadas
Você aprenderá sobre esses elementos de programa na seção sobre tipos do guia de
conceitos básicos:

Classes
Estruturas
Namespaces
Interfaces
Enumerações
Representantes

Especificação da Linguagem C#
Para obter mais informações, veja Noções básicas na Especificação da linguagem C#. A
especificação da linguagem é a fonte definitiva para a sintaxe e o uso de C#.
Main() e argumentos de linha de
comando
Artigo • 27/06/2024

O método Main é o ponto de entrada de um aplicativo C#. Quando o aplicativo é


iniciado, o método Main é o primeiro método invocado.

Pode haver apenas um ponto de entrada em um programa C#. Se tiver mais de uma
classe que tenha um método Main , você deverá compilar seu programa com a opção do
compilador StartupObject para especificar qual método Main será usado como ponto
de entrada. Para obter mais informações, consulte StartupObject (opções de
compilador do C#).

C#

class TestClass
{
static void Main(string[] args)
{
// Display the number of command line arguments.
Console.WriteLine(args.Length);
}
}

Você também pode usar Instruções de nível superior em um arquivo como o ponto de
entrada no seu aplicativo. Assim como o método Main , as instruções de nível superior
também podem retornar valores e acessar argumentos de linha de comando. Para obter
mais informações, consulte Instruções de nível superior.

C#

using System.Text;

StringBuilder builder = new();


builder.AppendLine("The following arguments are passed:");

// Display the command line arguments using the args variable.


foreach (var arg in args)
{
builder.AppendLine($"Argument={arg}");
}

Console.WriteLine(builder.ToString());
// Return a success code.
return 0;

Visão geral
O método Main é o ponto de entrada de um programa executável; é onde o
controle do programa começa e termina.
Main precisa ser declarado dentro de uma classe ou struct. A delimitação class

pode ser static .


Main deve ser static.
Main pode ter qualquer modificador de acesso (exceto file ).

Main pode ter o tipo de retorno void , int , Task ou Task<int> .

Se e somente se Main retornar um Task ou Task<int> , a declaração de Main pode


incluir o modificador async. Isso exclui especificamente um método async void
Main .

O método Main pode ser declarado com ou sem um parâmetro string[] que
contém os argumentos de linha de comando. Ao usar o Visual Studio para criar
aplicativos do Windows, você pode adicionar o parâmetro manualmente ou usar o
método GetCommandLineArgs() para obter os argumentos de linha de comando.
Os parâmetros são lidos como argumentos de linha de comando indexados por
zero. Ao contrário do C e C++, o nome do programa não é tratado como o
primeiro argumento de linha de comando na matriz args , mas é o primeiro
elemento do método GetCommandLineArgs().

A seguinte lista mostra as declarações Main mais comuns:

C#

static void Main() { }


static int Main() { }
static void Main(string[] args) { }
static int Main(string[] args) { }
static async Task Main() { }
static async Task<int> Main() { }
static async Task Main(string[] args) { }
static async Task<int> Main(string[] args) { }

Os exemplos anteriores não especificam um modificador de acesso, portanto, eles são


implicitamente private por padrão. Isso é típico, mas é possível especificar qualquer
modificador de acesso explícito.
 Dica

A adição dos tipos de retorno async , Task e Task<int> simplifica o código do


programa quando os aplicativos do console precisam iniciar e realizar operações
assíncronas await no Main .

Valores de retorno de Main()


Você pode retornar um int do método Main ao definir o método de uma das seguintes
maneiras:

ノ Expandir a tabela

Declaração Main Código do método Main

static int Main() Nenhum uso de args ou await

static int Main(string[] args) Usa args , nenhum uso de await

static async Task<int> Main() Nenhum uso de args , usa await

static async Task<int> Main(string[] args) Usa args e await

Se o valor retornado de Main não for usado, o retorno de void ou Task permite um
código um pouco mais simples.

ノ Expandir a tabela

Declaração Main Código do método Main

static void Main() Nenhum uso de args ou await

static void Main(string[] args) Usa args , nenhum uso de await

static async Task Main() Nenhum uso de args , usa await

static async Task Main(string[] args) Usa args e await

No entanto, o retorno de int ou Task<int> habilita o programa a comunicar


informações de status para outros programas ou scripts, que invocam o arquivo
executável.

O exemplo a seguir mostra como o código de saída para o processo pode ser acessado.
Este exemplo usa ferramentas de linha de comando do .NET Core. Se você não estiver
familiarizado com as ferramentas de linha de comando do .NET Core, poderá aprender
sobre elas neste artigo de introdução.

Crie um novo aplicativo ao executar dotnet new console . Modifique o método Main em
Program.cs da seguinte maneira:

C#

// Save this program as MainReturnValTest.cs.


class MainReturnValTest
{
static int Main()
{
//...
return 0;
}
}

Quando um programa é executado no Windows, qualquer valor retornado da função


Main é armazenado em uma variável de ambiente. Essa variável de ambiente pode ser

recuperada usando ERRORLEVEL de um arquivo em lotes ou $LastExitCode do


PowerShell.

Você pode criar o aplicativo usando o comando dotnet build da CLI do dotnet.

Em seguida, crie um script do PowerShell para executar o aplicativo e exibir o resultado.


Cole o código a seguir em um arquivo de texto e salve-o como test.ps1 na pasta que
contém o projeto. Execute o script do PowerShell ao digitar test.ps1 no prompt do
PowerShell.

Como o código retorna zero, o arquivo em lotes relatará êxito. No entanto, se você
alterar o MainReturnValTest.cs para retornar um valor diferente de zero e recompilar o
programa, a execução subsequente do script do PowerShell reportará falha.

PowerShell

dotnet run
if ($LastExitCode -eq 0) {
Write-Host "Execution succeeded"
} else
{
Write-Host "Execution Failed"
}
Write-Host "Return value = " $LastExitCode
Saída

Execution succeeded
Return value = 0

Valores retornados de Async Main


Quando você declara um valor retornado async para Main , o compilador gera o código
clichê para chamar métodos assíncronos em Main . Se você não especificar a palavra-
chave async , precisará escrever esse código por conta própria, conforme mostrado no
exemplo a seguir. O código no exemplo garante que o programa seja executado até
que a operação assíncrona seja concluída:

C#

class AsyncMainReturnValTest
{
public static int Main()
{
return AsyncConsoleWork().GetAwaiter().GetResult();
}

private static async Task<int> AsyncConsoleWork()


{
// Main body here
return 0;
}
}

Este código clichê pode ser substituído por:

C#

class Program
{
static async Task<int> Main(string[] args)
{
return await AsyncConsoleWork();
}

private static async Task<int> AsyncConsoleWork()


{
// main body here
return 0;
}
}
A vantagem de declarar Main como async é que o compilador sempre gera o código
correto.

Quando o ponto de entrada do aplicativo retorna um Task ou Task<int> , o compilador


gera um novo ponto de entrada que chama o método de ponto de entrada declarado
no código do aplicativo. Supondo que esse ponto de entrada é chamado
$GeneratedMain , o compilador gera o código a seguir para esses pontos de entrada:

static Task Main() resulta no compilador emitindo o equivalente a private

static void $GeneratedMain() => Main().GetAwaiter().GetResult();


static Task Main(string[]) resulta no compilador emitindo o equivalente a

private static void $GeneratedMain(string[] args) =>

Main(args).GetAwaiter().GetResult();
static Task<int> Main() resulta no compilador emitindo o equivalente a private

static int $GeneratedMain() => Main().GetAwaiter().GetResult();


static Task<int> Main(string[]) resulta no compilador emitindo o equivalente a

private static int $GeneratedMain(string[] args) =>

Main(args).GetAwaiter().GetResult();

7 Observação

Se os exemplos usassem o modificador async no método Main , o compilador


geraria o mesmo código.

Argumentos de linha de comando


Você pode enviar argumentos para o método Main definindo o método de uma das
seguintes maneiras:

ノ Expandir a tabela

Declaração Main Código do método Main

static void Main(string[] args) Nenhum valor retornado, nenhum uso de await

static int Main(string[] args) Valor retornado, nenhum uso de await

static async Task Main(string[] args) Nenhum valor retornado, usa await

static async Task<int> Main(string[] args) Valor retornado, usa await


Se os argumentos não forem usados, você poderá omitir args da assinatura do método
para um código ligeiramente mais simples:

ノ Expandir a tabela

Declaração Main Código do método Main

static void Main() Nenhum valor retornado, nenhum uso de await

static int Main() Valor retornado, nenhum uso de await

static async Task Main() Nenhum valor retornado, usa await

static async Task<int> Main() Valor retornado, usa await

7 Observação

Você também pode usar Environment.CommandLine ou


Environment.GetCommandLineArgs para acessar os argumentos de linha de
comando de qualquer ponto em um console ou um aplicativo do Windows Forms.
Para habilitar os argumentos de linha de comando na declaração do método Main
em um aplicativo do Windows Forms, você precisa modificar manualmente a
declaração de Main . O código gerado pelo designer do Windows Forms cria um
Main sem um parâmetro de entrada.

O parâmetro do método Main é uma matriz String que representa os argumentos de


linha de comando. Geralmente você determina se os argumentos existem testando a
propriedade Length , por exemplo:

C#

if (args.Length == 0)
{
System.Console.WriteLine("Please enter a numeric argument.");
return 1;
}

 Dica

A matriz args não pode ser nula. Portanto, é seguro acessar a propriedade Length
sem verificação de nulos.
Você também pode converter os argumentos de cadeia de caracteres em tipos
numéricos, usando a classe Convert ou o método Parse . Por exemplo, a instrução a
seguir converte o string em um número long usando o método Parse:

C#

long num = Int64.Parse(args[0]);

Também é possível usar o tipo long de C#, que funciona como alias de Int64 :

C#

long num = long.Parse(args[0]);

Você também pode usar o método da classe Convert , o ToInt64 , para fazer a mesma
coisa:

C#

long num = Convert.ToInt64(s);

Para obter mais informações, consulte Parse e Convert.

 Dica

A análise de argumentos de linha de comando pode ser complexa. Considere usar


a biblioteca System.CommandLine (atualmente em beta) para simplificar o
processo.

O exemplo a seguir mostra como usar argumentos de linha de comando em um


aplicativo de console. O aplicativo recebe um argumento em tempo de execução,
converte o argumento em um número inteiro e calcula o fatorial do número. Se nenhum
argumento for fornecido, o aplicativo emitirá uma mensagem que explica o uso correto
do programa.

Para compilar e executar o aplicativo em um prompt de comando, siga estas etapas:

1. Cole o código a seguir em qualquer editor de texto e, em seguida, salve o arquivo


como um arquivo de texto com o nome Factorial.cs.

C#
public class Functions
{
public static long Factorial(int n)
{
// Test for invalid input.
if ((n < 0) || (n > 20))
{
return -1;
}

// Calculate the factorial iteratively rather than recursively.


long tempResult = 1;
for (int i = 1; i <= n; i++)
{
tempResult *= i;
}
return tempResult;
}
}

class MainClass
{
static int Main(string[] args)
{
// Test if input arguments were supplied.
if (args.Length == 0)
{
Console.WriteLine("Please enter a numeric argument.");
Console.WriteLine("Usage: Factorial <num>");
return 1;
}

// Try to convert the input arguments to numbers. This will


throw
// an exception if the argument is not a number.
// num = int.Parse(args[0]);
int num;
bool test = int.TryParse(args[0], out num);
if (!test)
{
Console.WriteLine("Please enter a numeric argument.");
Console.WriteLine("Usage: Factorial <num>");
return 1;
}

// Calculate factorial.
long result = Functions.Factorial(num);

// Print result.
if (result == -1)
Console.WriteLine("Input must be >= 0 and <= 20.");
else
Console.WriteLine($"The Factorial of {num} is {result}.");
return 0;
}
}
// If 3 is entered on command line, the
// output reads: The factorial of 3 is 6.

2. Na tela Inicial ou no menu Iniciar, abra uma janela Prompt de Comando do


Desenvolvedor do Visual Studio e, em seguida, navegue até a pasta que contém o
arquivo que você acabou de criar.

3. Digite o seguinte comando para compilar o aplicativo.

dotnet build

Se seu aplicativo não tiver erros de compilação, um arquivo executável chamado


Factorial.exe será criado.

4. Digite o seguinte comando para calcular o fatorial de 3:

dotnet run -- 3

5. O comando produz esta saída: The factorial of 3 is 6.

7 Observação

Ao executar um aplicativo no Visual Studio, você pode especificar argumentos de


linha de comando na Página de depuração, Designer de Projeto.

Especificação da linguagem C#
Para obter mais informações, consulte a Especificação da linguagem C#. A especificação
da linguagem é a fonte definitiva para a sintaxe e o uso de C#.

Confira também
System.Environment
Como exibir argumentos de linha de comando

6 Colaborar conosco no Comentários do .NET


GitHub
A fonte deste conteúdo pode O .NET é um projeto código aberto.
ser encontrada no GitHub, onde Selecione um link para fornecer
você também pode criar e comentários:
revisar problemas e solicitações
de pull. Para obter mais  Abrir um problema de
informações, confira o nosso documentação
guia para colaboradores.
 Fornecer comentários sobre o
produto
Instruções de nível superior –
Programas sem métodos Main
Artigo • 05/03/2024

Não é preciso incluir explicitamente um método Main em um projeto de aplicativo de


console. Em vez disso, você pode usar o recurso de instruções de nível superior para
minimizar o volume de código que precisa escrever.

Instruções de alto nível permitem que você escreva código executável diretamente na
raiz de um arquivo, eliminando a necessidade de encapsular seu código em uma classe
ou método. Isso significa que você pode criar programas sem a cerimônia de uma classe
Program e um método Main . Nesse caso, o compilador gera uma classe Program com

um método de ponto de entrada para o aplicativo. O nome do método gerado não é


Main , é um detalhe de implementação que seu código não pode referenciar

diretamente.

Aqui está um arquivo Program.cs que corresponde a um programa C# completo no C#


10:

C#

Console.WriteLine("Hello World!");

Instruções de nível superior permitem que você escreva programas simples para
utilitários pequenos, como o Azure Functions e o GitHub Actions. Elas também
simplificam para novos programadores C# começar a aprender e escrever código.

As seções a seguir explicam as regras sobre o que você pode ou não fazer com
instruções de nível superior.

Apenas um arquivo de nível superior


Cada aplicativo deve ter apenas um ponto de entrada. Cada projeto pode ter apenas um
arquivo com instruções de nível superior. Colocar instruções de nível superior em mais
de um arquivo de um projeto resulta no seguinte erro do compilador:

CS8802 Somente uma unidade de compilação pode conter instruções de nível


superior.
Os projetos podem ter um número indefinido de arquivos de código-fonte adicionais
que não têm instruções de nível superior.

Nenhum outro ponto de entrada


Você pode escrever um método Main explicitamente, mas ele não pode funcionar como
um ponto de entrada. O compilador emite o seguinte aviso:

CS7022 O ponto de entrada do programa é o código global, ignorando o ponto de


entrada "Main()".

Em um projeto com instruções de nível superior, você não pode usar a opção do
compilador -main para selecionar o ponto de entrada, mesmo que o projeto tenha um
ou mais métodos Main .

using diretivas
Se você incluir o uso de diretivas, elas deverão vir primeiro no arquivo, como neste
exemplo:

C#

using System.Text;

StringBuilder builder = new();


builder.AppendLine("Hello");
builder.AppendLine("World!");

Console.WriteLine(builder.ToString());

Namespace global
As instruções de nível superior estão implicitamente no namespace global.

Namespaces e definições de tipo


Um arquivo com instruções de nível superior também pode conter namespaces e
definições de tipo, mas devem vir após as instruções de nível superior. Por exemplo:

C#
MyClass.TestMethod();
MyNamespace.MyClass.MyMethod();

public class MyClass


{
public static void TestMethod()
{
Console.WriteLine("Hello World!");
}
}

namespace MyNamespace
{
class MyClass
{
public static void MyMethod()
{
Console.WriteLine("Hello World from
MyNamespace.MyClass.MyMethod!");
}
}
}

args
Instruções de nível superior podem fazer referência à variável args para acessar
quaisquer argumentos de linha de comando que foram inseridos. A variável args nunca
será nula, mas o Length dela será zero se nenhum argumento de linha de comando
tiver sido fornecido. Por exemplo:

C#

if (args.Length > 0)
{
foreach (var arg in args)
{
Console.WriteLine($"Argument={arg}");
}
}
else
{
Console.WriteLine("No arguments");
}

await
Você pode chamar um método assíncrono usando await . Por exemplo:

C#

Console.Write("Hello ");
await Task.Delay(5000);
Console.WriteLine("World!");

Código de saída do processo


Para retornar um valor int quando o aplicativo terminar, use a instrução return como
faria em um método Main que retorna um int . Por exemplo:

C#

string? s = Console.ReadLine();

int returnValue = int.Parse(s ?? "-1");


return returnValue;

Método de ponto de entrada implícito


O compilador gera um método para servir como o ponto de entrada do programa para
um projeto com instruções de nível superior. A assinatura do método depende se as
instruções de nível superior contêm a palavra-chave await ou a instrução return . A
tabela a seguir mostra como seria a assinatura do método, usando o nome de método
Main na tabela para fins de conveniência.

ノ Expandir a tabela

O código de nível superior contém Assinatura Main implícita

await e return static async Task<int> Main(string[] args)

await static async Task Main(string[] args)

return static int Main(string[] args)

Não await nem return static void Main(string[] args)

Especificação da linguagem C#
Para obter mais informações, consulte a Especificação da linguagem C#. A especificação
da linguagem é a fonte definitiva para a sintaxe e o uso de C#.

Especificação de recurso – Instruções de nível superior

6 Colaborar conosco no Comentários do .NET


GitHub O .NET é um projeto código aberto.
A fonte deste conteúdo pode Selecione um link para fornecer
ser encontrada no GitHub, onde comentários:
você também pode criar e
revisar problemas e solicitações  Abrir um problema de
de pull. Para obter mais documentação
informações, confira o nosso
guia para colaboradores.  Fornecer comentários sobre o
produto
O sistema do tipo C#
Artigo • 11/04/2024

C# é uma linguagem fortemente tipada. Todas as variáveis e constantes têm um tipo,


assim como cada expressão que é avaliada como um valor. Cada declaração de método
especifica um nome, o tipo e a variante (valor, referência ou saída) de cada parâmetro
de entrada e do valor retornado. A biblioteca de classes do .NET define tipos numéricos
internos e tipos complexos que representam uma grande variedade de constructos. Isso
inclui o sistema de arquivos, conexões de rede, coleções e matrizes de objetos e datas.
Um programa em C# típico usa tipos da biblioteca de classes e tipos definidos pelo
usuário que modelam os conceitos que são específicos para o domínio do problema do
programa.

As informações armazenadas em um tipo podem incluir os seguintes itens:

O espaço de armazenamento que uma variável do tipo requer.


Os valores mínimo e máximo que ele pode representar.
Os membros (métodos, campos, eventos e etc.) que ele contém.
O tipo base do qual ele herda.
A interface implementada.
Os tipos de operações que são permitidos.

O compilador usa as informações de tipo para garantir que todas as operações que são
realizadas em seu código sejam fortemente tipadas. Por exemplo, se você declarar uma
variável do tipo int, o compilador permitirá que você use a variável nas operações de
adição e subtração. Se você tentar executar as mesmas operações em uma variável do
tipo bool, o compilador gerará um erro, como mostrado no exemplo a seguir:

C#

int a = 5;
int b = a + 2; //OK

bool test = true;

// Error. Operator '+' cannot be applied to operands of type 'int' and


'bool'.
int c = a + test;

7 Observação
Desenvolvedores de C e C++, observem que, em C#, bool não é conversível em
int .

O compilador insere as informações de tipo no arquivo executável como metadados. O


CLR (Common Language Runtime) usa esses metadados em tempo de execução para
assegurar mais segurança de tipos ao alocar e recuperar a memória.

Especificando tipos em declarações de variável


Quando declara uma variável ou constante em um programa, você deve especificar seu
tipo ou usar a palavra-chave var para permitir que o compilador infira o tipo. O exemplo
a seguir mostra algumas declarações de variáveis que usam tipos numéricos internos e
tipos complexos definidos pelo usuário:

C#

// Declaration only:
float temperature;
string name;
MyClass myClass;

// Declaration with initializers (four examples):


char firstLetter = 'C';
var limit = 3;
int[] source = { 0, 1, 2, 3, 4, 5 };
var query = from item in source
where item <= limit
select item;

Os tipos de parâmetros de método e valores de retorno são especificados na declaração


do método. A assinatura a seguir mostra um método que requer um int como um
argumento de entrada e retorna uma cadeia de caracteres:

C#

public string GetName(int ID)


{
if (ID < names.Length)
return names[ID];
else
return String.Empty;
}
private string[] names = { "Spencer", "Sally", "Doug" };
Depois de declarar uma variável, não será possível reenviá-la com um novo tipo, nem
atribuir um valor incompatível com seu tipo declarado. Por exemplo, você não pode
declarar um int e, em seguida, atribuir a ele um valor booliano de true . No entanto, os
valores podem ser convertidos em outros tipos, por exemplo, quando são passados
como argumentos de método ou atribuídos a novas variáveis. Uma conversão de tipo
que não causa a perda de dados é executada automaticamente pelo compilador. Uma
conversão que pode causar perda de dados requer um cast no código-fonte.

Para obter mais informações, consulte Conversões Cast e Conversões de Tipo.

Tipos internos
O C# fornece um conjunto padrão de tipos internos. Eles representam números inteiros,
valores de ponto flutuante, expressões boolianas, caracteres de texto, valores decimais e
outros tipos de dados. Também há tipos string e object internos. Esses tipos estão
disponíveis para uso em qualquer programa em C#. Para obter a lista completa tipos
internos, consulte Tipos internos.

Tipos personalizados
Você usa os constructos struct, class, interface, enum e record para criar seus próprios
tipos personalizados. A biblioteca de classes do .NET em si é uma coleção de tipos
personalizados que você pode usar em seus próprios aplicativos. Por padrão, os tipos
usados com mais frequência na biblioteca de classes estão disponíveis em qualquer
programa em C#. Outros ficam disponíveis somente quando você adiciona
explicitamente uma referência de projeto ao assembly que os define. Depois que o
compilador tiver uma referência ao assembly, você pode declarar variáveis (e
constantes) dos tipos declarados nesse assembly no código-fonte. Para saber mais,
confira Biblioteca de classes do .NET.

O Common Type System


É importante entender os dois pontos fundamentais sobre o sistema de tipos do .NET:

Ele dá suporte ao conceito de herança. Os tipos podem derivar de outros tipos,


chamados tipos base. O tipo derivado herda (com algumas restrições) os métodos,
as propriedades e outros membros do tipo base. O tipo base, por sua vez, pode
derivar de algum outro tipo, nesse caso, o tipo derivado herda os membros de
ambos os tipos base na sua hierarquia de herança. Todos os tipos, incluindo tipos
numéricos internos, como o System.Int32 (palavra-chave do C#: int ), derivam, em
última análise, de um único tipo base, que é o System.Object (palavra-chave do C#:
object). Essa hierarquia unificada de tipos é chamada de CTS (Common Type
System). Para obter mais informações sobre herança em C#, consulte Herança.
Cada tipo no CTS é definido como um tipo de valor ou um tipo de referência. Esses
tipos incluem todos os tipos personalizados na biblioteca de classes do .NET, além
de tipos personalizados definidos pelo usuário. Os tipos que você define usando a
palavra-chave struct são tipos de valor. Todos os tipos numéricos internos são
structs . Os tipos que você define usando a palavra-chave class ou record são

tipos de referência. Os tipos de referência e os tipos de valor têm diferentes regras


de tempo de compilação e comportamento de tempo de execução diferente.

A ilustração a seguir mostra a relação entre tipos de referência e tipos de valor no CTS.

7 Observação

Você pode ver que os tipos mais usados normalmente são todos organizados no
namespace System. No entanto, o namespace no qual um tipo está contido não
tem relação com a possibilidade de ele ser um tipo de valor ou um tipo de
referência.

Classes e structs são duas das construções básicas do Common Type System no .NET. O
C# 9 adiciona registros, que são um tipo de classe. Cada um é, essencialmente, uma
estrutura de dados que encapsula um conjunto de dados e os comportamentos que são
uma unidade lógica. Os dados e comportamentos são os membros da classe, struct ou
registro. Os membros incluem seus métodos, propriedades, eventos e assim por diante,
conforme listado posteriormente neste artigo.

Uma declaração de classe, struct ou registro é como um plano que é usado para criar
instâncias ou objetos em tempo de execução. Se você definir uma classe, struct ou
registro denominado Person , Person será o nome do tipo. Se você declarar e inicializar
um p variável do tipo Person , p será considerado um objeto ou uma instância de
Person . Várias instâncias do mesmo tipo Person podem ser criadas, e cada instância

pode ter valores diferentes em suas propriedades e campos.

Uma classe é um tipo de referência. Quando um objeto do tipo é criado, a variável à


qual o objeto é atribuído armazena apenas uma referência na memória. Quando a
referência de objeto é atribuída a uma nova variável, a nova variável refere-se ao objeto
original. As alterações feitas por meio de uma variável são refletidas na outra variável
porque ambas se referem aos mesmos dados.

Um struct é um tipo de valor. Quando um struct é criado, a variável à qual o struct está
atribuído contém os dados reais do struct. Quando o struct é atribuído a uma nova
variável, ele é copiado. A nova variável e a variável original, portanto, contêm duas
cópias separadas dos mesmos dados. As alterações feitas em uma cópia não afetam a
outra cópia.

Os tipos de registro podem ser tipos de referência ( record class ) ou tipos de valor
( record struct ).

Em geral, as classes são usadas para modelar um comportamento mais complexo. As


classes normalmente armazenam dados que devem ser modificados depois que um
objeto de classe é criado. Structs são mais adequados para estruturas de dados
pequenas. Os structs normalmente armazenam dados que não se destinam a serem
modificados após a criação do struct. Os tipos de registro são estruturas de dados com
membros sintetizados de compilador adicionais. Os registros normalmente armazenam
dados que não se destinam a serem modificados após a criação do objeto.

Tipos de valor
Os tipos de valor derivam de System.ValueType, que deriva de System.Object. Os tipos
que derivam de System.ValueType apresentam um comportamento especial no CLR. As
variáveis de tipo de valor contêm diretamente seus valores. A memória de um struct é
embutida em qualquer contexto em que a variável seja declarada. Não há nenhuma
alocação de heap separada ou sobrecarga de coleta de lixo para variáveis do tipo de
valor. É possível declarar tipos record struct que são tipos de valor e incluir os
membros sintetizados para registros.
Há duas categorias de tipos de valor: struct e enum .

Os tipos numéricos internos são structs e têm campos e métodos que você pode
acessar:

C#

// constant field on type byte.


byte b = byte.MaxValue;

Mas você declara e atribui valores a eles como se fossem tipos de não agregação
simples:

C#

byte num = 0xA;


int i = 5;
char c = 'Z';

Os tipos de valor são selados. Não é possível derivar um tipo de qualquer tipo de valor,
por exemplo System.Int32. Você não pode definir um struct a ser herdado de qualquer
classe definida pelo usuário ou struct, porque um struct só pode ser herdado de
System.ValueType. No entanto, um struct pode implementar uma ou mais interfaces. É
possível converter um tipo struct em qualquer tipo de interface que ele implementa.
Essa conversão faz com que uma operação de conversão boxing encapsule o struct
dentro de um objeto de tipo de referência no heap gerenciado. As operações de
conversão boxing ocorrem quando você passa um tipo de valor para um método que
usa um System.Object ou qualquer tipo de interface como parâmetro de entrada. Para
obter mais informações, consulte Conversões boxing e unboxing.

Você usa a palavra-chave struct para criar seus próprios tipos de valor personalizados.
Normalmente, um struct é usado como um contêiner para um pequeno conjunto de
variáveis relacionadas, conforme mostrado no exemplo a seguir:

C#

public struct Coords


{
public int x, y;

public Coords(int p1, int p2)


{
x = p1;
y = p2;
}
}

Para obter mais informações sobre structs, consulte Tipos de estrutura. Para saber mais
sobre os tipos de valor, confira Tipos de valor.

A outra categoria de tipos de valor é enum . Uma enum define um conjunto de


constantes integrais nomeadas. Por exemplo, a enumeração System.IO.FileMode na
biblioteca de classes do .NET contém um conjunto de números inteiros constantes
nomeados que especificam como um arquivo deve ser aberto. Ela é definida conforme
mostrado no exemplo abaixo:

C#

public enum FileMode


{
CreateNew = 1,
Create = 2,
Open = 3,
OpenOrCreate = 4,
Truncate = 5,
Append = 6,
}

A constante System.IO.FileMode.Create tem um valor de 2. No entanto, o nome é muito


mais significativo para a leitura do código-fonte por humanos e, por esse motivo, é
melhor usar enumerações em vez de números literais constantes. Para obter mais
informações, consulte System.IO.FileMode.

Todas as enumerações herdam de System.Enum, que herda de System.ValueType. Todas


as regras que se aplicam a structs também se aplicam a enums. Para obter mais
informações sobre enums, consulte Tipos de enumeração.

Tipos de referência
Um tipo que é definido como class , record , delegate, matriz ou interface é um
reference type.

Ao declarar uma variável de um reference type, ele contém o valor null até que você o
atribua com uma instância desse tipo ou crie uma usando o operador new. A criação e a
atribuição de uma classe são demonstradas no exemplo a seguir:

C#
MyClass myClass = new MyClass();
MyClass myClass2 = myClass;

Não é possível criar uma instância direta de interface usando o operador new. Em vez
disso, crie e atribua uma instância de uma classe que implemente a interface. Considere
o seguinte exemplo:

C#

MyClass myClass = new MyClass();

// Declare and assign using an existing value.


IMyInterface myInterface = myClass;

// Or create and assign a value in a single statement.


IMyInterface myInterface2 = new MyClass();

Quando o objeto é criado, a memória é alocada no heap gerenciado. A variável contém


apenas uma referência ao local do objeto. Os tipos no heap gerenciado exigem
sobrecarga quando são alocados e recuperados. A coleta de lixo é a funcionalidade de
gerenciamento automático de memória do CLR, que executa a recuperação. No entanto,
a coleta de lixo também é altamente otimizada e, na maioria dos cenários, não cria um
problema de desempenho. Para obter mais informações sobre a coleta de lixo, consulte
Gerenciamento automático de memória.

Todas as matrizes são tipos de referência, mesmo se seus elementos forem tipos de
valor. As matrizes derivam implicitamente da classe System.Array. Você declara e usa as
matrizes com a sintaxe simplificada fornecida pelo C#, conforme mostrado no exemplo
a seguir:

C#

// Declare and initialize an array of integers.


int[] nums = { 1, 2, 3, 4, 5 };

// Access an instance property of System.Array.


int len = nums.Length;

Os tipos de referência dão suporte completo à herança. Quando você cria uma classe, é
possível herdar de qualquer outra interface ou classe que não esteja definida como
selada. Outras classes podem herdar de sua classe e substituir seus métodos virtuais.
Para obter mais informações sobre como criar suas próprias classes, consulte Classes,
structs e registros. Para obter mais informações sobre herança e métodos virtuais,
consulte Herança.
Tipos de valores literais
No C#, valores literais recebem um tipo do compilador. Você pode especificar como um
literal numérico deve ser digitado anexando uma letra ao final do número. Por exemplo,
para especificar que o valor 4.56 deve ser tratado como um float , acrescente um "f"
ou "F" após o número: 4.56f . Se nenhuma letra for anexada, o compilador inferirá um
tipo para o literal. Para obter mais informações sobre quais tipos podem ser
especificados com sufixos de letra, consulte Tipos numéricos integrais e Tipos
numéricos de ponto flutuante.

Como os literais são tipados e todos os tipos derivam basicamente de System.Object,


você pode escrever e compilar o código como o seguinte:

C#

string s = "The answer is " + 5.ToString();


// Outputs: "The answer is 5"
Console.WriteLine(s);

Type type = 12345.GetType();


// Outputs: "System.Int32"
Console.WriteLine(type);

Tipos genéricos
Um tipo pode ser declarado com um ou mais parâmetros de tipo que servem como um
espaço reservado para o tipo real (o tipo concreto). O código do cliente fornece o tipo
concreto quando ele cria uma instância do tipo. Esses tipos são chamados de tipos
genéricos. Por exemplo, o tipo do .NET System.Collections.Generic.List<T> tem um
parâmetro de tipo que, por convenção, recebe o nome T . Ao criar uma instância do
tipo, você pode especificar o tipo dos objetos que a lista conterá, por exemplo, string :

C#

List<string> stringList = new List<string>();


stringList.Add("String example");
// compile time error adding a type other than a string:
stringList.Add(4);

O uso do parâmetro de tipo possibilita a reutilização da mesma classe para conter


qualquer tipo de elemento sem precisar converter cada elemento em objeto. As classes
de coleção genéricas são chamadas de coleções fortemente tipadas porque o compilador
sabe o tipo específico dos elementos da coleção e pode gerar um erro em tempo de
compilação se, por exemplo, você tentar adicionar um inteiro ao objeto stringList no
exemplo anterior. Para obter mais informações, consulte Genéricos.

Tipos implícitos, tipos anônimos e tipos que


permitem valor nulo
Você pode digitar implicitamente uma variável local (mas não os membros de classe)
usando a palavra-chave var. A variável ainda recebe um tipo em tempo de compilação,
mas o tipo é fornecido pelo compilador. Para obter mais informações, consulte Variáveis
locais de tipo implícito.

Pode ser inconveniente criar um tipo nomeado para conjuntos simples de valores
relacionados que você não pretende armazenar ou transmitir fora dos limites de
método. Você pode criar tipos anônimos para essa finalidade. Para obter mais
informações, consulte Tipos Anônimos.

Os tipos comuns de valor não podem ter um valor null. No entanto, você pode criar
tipos de valor anulável acrescentando uma ? após o tipo. Por exemplo, int? é um tipo
int que também pode ter o valor null. Os tipos que permitem valor nulo são instâncias

do tipo struct genérico System.Nullable<T>. Os tipos que permitem valor nulo são
especialmente úteis quando você está passando dados entre bancos de dados nos quais
os valores numéricos podem ser null . Para obter mais informações, consulte Tipos que
permitem valor nulo.

Tipo de tempo de compilação e tipo de tempo


de execução
Uma variável pode ter diferentes tipos de tempo de compilação e tempo de execução.
O tipo de tempo de compilação é o tipo declarado ou inferido da variável no código-
fonte. O tipo de tempo de execução é o tipo da instância referenciada por essa variável.
Geralmente, esses dois tipos são iguais, como no exemplo a seguir:

C#

string message = "This is a string of characters";

Em outros casos, o tipo de tempo de compilação é diferente, conforme mostrado nos


dois exemplos a seguir:

C#
object anotherMessage = "This is another string of characters";
IEnumerable<char> someCharacters = "abcdefghijklmnopqrstuvwxyz";

Em ambos os exemplos acima, o tipo de tempo de execução é um string . O tipo de


tempo de compilação é object na primeira linha e IEnumerable<char> na segunda.

Se os dois tipos forem diferentes para uma variável, é importante entender quando o
tipo de tempo de compilação e o tipo de tempo de execução se aplicam. O tipo de
tempo de compilação determina todas as ações executadas pelo compilador. Essas
ações do compilador incluem resolução de chamada de método, resolução de
sobrecarga e conversões implícitas e explícitas disponíveis. O tipo de tempo de
execução determina todas as ações que são resolvidas em tempo de execução. Essas
ações de tempo de execução incluem a expedição de chamadas de método virtual,
avaliação das expressões is e switch e outras APIs de teste de tipo. Para entender
melhor como seu código interage com tipos, reconheça qual ação se aplica a qual tipo.

Seções relacionadas
Para obter mais informações, consulte os seguintes artigos:

Tipos internos
Tipos de valor
Tipos de referência

Especificação da linguagem C#
Para obter mais informações, consulte a Especificação da linguagem C#. A especificação
da linguagem é a fonte definitiva para a sintaxe e o uso de C#.

Comentários
Esta página foi útil?  Yes  No

Fornecer comentários sobre o produto


Declarar namespaces para organizar
tipos
Artigo • 04/07/2024

Os namespaces são usados intensamente em programações de C# de duas maneiras.


Em primeiro lugar, o .NET usa namespaces para organizar suas muitas classes, da
seguinte maneira:

C#

System.Console.WriteLine("Hello World!");

System é um namespace e Console é uma classe nesse namespace. A palavra-chave


using pode ser usada para que o nome completo não seja necessário, como no

exemplo a seguir:

C#

using System;

C#

Console.WriteLine("Hello World!");

Para saber mais, confira Diretiva using.

) Importante

Os modelos C# para o .NET 6 usam instruções de nível superior. Se você já tiver


atualizado para o .NET 6, talvez seu aplicativo não corresponda ao código descrito
neste artigo. Para obter mais informações, consulte o artigo sobre Novos modelos
C# geram instruções de nível superior

O SDK do .NET 6 também adiciona um conjunto de diretivas implícitas global


using para projetos que usam os seguintes SDKs:

Microsoft.NET.Sdk
Microsoft.NET.Sdk.Web
Microsoft.NET.Sdk.Worker
Essas diretivas implícitas global using incluem os namespaces mais comuns para o
tipo de projeto.

Para obter mais informações, consulte o artigo sobre Diretivas de uso implícito

Em segundo lugar, declarar seus próprios namespaces pode ajudar a controlar o escopo
dos nomes de classe e de método em projetos de programação maiores. Use a palavra-
chave namespace para declarar um namespace, como no exemplo a seguir:

C#

namespace SampleNamespace
{
class SampleClass
{
public void SampleMethod()
{
System.Console.WriteLine(
"SampleMethod inside SampleNamespace");
}
}
}

O nome do namespace deve ser um nome do identificador válido em C#.

A partir do C# 10, você pode declarar um namespace para todos os tipos definidos
nesse arquivo, como mostra o exemplo a seguir:

C#

namespace SampleNamespace;

class AnotherSampleClass
{
public void AnotherSampleMethod()
{
System.Console.WriteLine(
"SampleMethod inside SampleNamespace");
}
}

A vantagem dessa nova sintaxe é que ela é mais simples, economizando espaço
horizontal e chaves. Isso facilita a leitura do seu código.

Visão geral dos namespaces


Os namespaces têm as seguintes propriedades:

Eles organizam projetos de códigos grandes.


Eles são delimitados com o uso do operador . .
A diretiva using elimina a necessidade de especificar o nome do namespace para
cada classe.
O namespace global é o namespace "raiz": global::System sempre fará referência
ao namespace do .NET System.

Especificação da linguagem C#
Para saber mais, confira a seção Namespaces da Especificação da linguagem C#.

6 Colaborar conosco no Comentários do .NET


GitHub O .NET é um projeto código aberto.
A fonte deste conteúdo pode Selecione um link para fornecer
ser encontrada no GitHub, onde comentários:
você também pode criar e
revisar problemas e solicitações  Abrir um problema de
de pull. Para obter mais documentação
informações, confira o nosso
guia para colaboradores.  Fornecer comentários sobre o
produto
Introdução às classes
Artigo • 15/08/2024

Tipos de referência
Um tipo que é definido como uma class é um tipo de referência. No runtime, quando
você declara uma variável de um tipo de referência, a variável contém o valor null até
que você crie explicitamente uma instância da classe usando o operador new ou atribua
a ela um objeto de um tipo compatível criado em outro lugar, conforme mostrado no
exemplo a seguir:

C#

//Declaring an object of type MyClass.


MyClass mc = new MyClass();

//Declaring another object of the same type, assigning it the value of the
first object.
MyClass mc2 = mc;

Quando o objeto é criado, memória suficiente é alocada no heap gerenciado para o


objeto específico, e a variável contém apenas uma referência para o local do objeto. A
memória usada por um objeto é recuperada pela funcionalidade de gerenciamento
automático de memória do CLR, que é conhecida como coleta de lixo. Para obter mais
informações sobre a coleta de lixo, consulte Gerenciamento automático de memória e
coleta de lixo.

Declarando classes
As classes são declaradas usando a palavra-chave class , seguida por um identificador
exclusivo, conforme mostrado no exemplo a seguir:

C#

//[access modifier] - [class] - [identifier]


public class Customer
{
// Fields, properties, methods and events go here...
}
Um modificador de acesso opcional precede a palavra-chave class . O acesso padrão
para um tipo class é internal . Como public é usado nesse caso, qualquer pessoa pode
criar instâncias dessa classe. O nome da classe segue a palavra-chave class . O nome da
classe deve ser um nome do identificador válido em C#. O restante da definição é o
corpo da classe, em que o comportamento e os dados são definidos. Campos,
propriedades, métodos e eventos em uma classe são coletivamente denominados de
membros de classe.

Criando objetos
Embora eles sejam usados algumas vezes de maneira intercambiável, uma classe e um
objeto são coisas diferentes. Uma classe define um tipo de objeto, mas não é um objeto
em si. Um objeto é uma entidade concreta com base em uma classe e, às vezes, é
conhecido como uma instância de uma classe.

Os objetos podem ser criados usando a palavra-chave new seguida pelo nome da
classe, dessa maneira:

C#

Customer object1 = new Customer();

Quando uma instância de uma classe é criada, uma referência ao objeto é passada de
volta para o programador. No exemplo anterior, object1 é uma referência a um objeto
que é baseado em Customer . Esta referência refere-se ao novo objeto, mas não contém
os dados de objeto. Na verdade, você pode criar uma referência de objeto sem criar um
objeto:

C#

Customer object2;

Não recomendamos a criação de referências de objeto, que não faz referência a um


objeto, porque tentar acessar um objeto por meio de uma referência desse tipo falhará
em tempo de execução. Uma referência pode se referir a um objeto, criando um novo
objeto ou atribuindo-a a um objeto existente, como abaixo:

C#

Customer object3 = new Customer();


Customer object4 = object3;
Esse código cria duas referências de objeto que fazem referência ao mesmo objeto.
Portanto, qualquer alteração no objeto feita por meio de object3 será refletida no usos
posteriores de object4 . Como os objetos que são baseados em classes são
referenciados por referência, as classes são conhecidas como tipos de referência.

Construtores e inicialização
As seções anteriores introduziram a sintaxe para declarar um tipo de classe e criar uma
instância desse tipo. Ao criar uma instância de um tipo, você deseja garantir que seus
campos e propriedades sejam inicializados para valores úteis. Há várias maneiras de
inicializar valores:

Aceitar valores padrão


Inicializadores de campo
Parâmetros do construtor
Inicializadores de objeto

Cada tipo .NET tem um valor padrão. Normalmente, esse valor é 0 para tipos de número
e null para todos os tipos de referência. Você pode contar com esse valor padrão
quando for razoável em seu aplicativo.

Quando o padrão .NET não é o valor certo, você pode definir um valor inicial usando um
inicializador de campo:

C#

public class Container


{
// Initialize capacity field to a default value of 10:
private int _capacity = 10;
}

Você pode exigir que os chamadores forneçam um valor inicial definindo um construtor
responsável por definir esse valor inicial:

C#

public class Container


{
private int _capacity;

public Container(int capacity) => _capacity = capacity;


}
A partir do C# 12, você pode definir um construtor primário como parte da declaração
de classe:

C#

public class Container(int capacity)


{
private int _capacity = capacity;
}

Adicionar parâmetros ao nome da classe define o construtor primário. Esses parâmetros


estão disponíveis no corpo da classe, que inclui seus membros. Você pode usá-los para
inicializar campos ou em qualquer outro lugar em que eles sejam necessários.

Você também pode usar o modificador required em uma propriedade e permitir que os
chamadores usem um inicializador de objeto para definir o valor inicial da propriedade:

C#

public class Person


{
public required string LastName { get; set; }
public required string FirstName { get; set; }
}

A adição da palavra-chave required determina que os chamadores devem definir essas


propriedades como parte de uma expressão new :

C#

var p1 = new Person(); // Error! Required properties not set


var p2 = new Person() { FirstName = "Grace", LastName = "Hopper" };

Herança de classe
As classes dão suporte completo à herança, uma característica fundamental da
programação orientada a objetos. Quando você cria uma classe, é possível herdar de
qualquer outra classe que não esteja definida como sealed. Outras classes podem
herdar de sua classe e substituir métodos virtuais de classe. Além disso, você pode
implementar uma ou mais interfaces.

A herança é realizada usando uma derivação, o que significa que uma classe é declarada
usando uma classe base, da qual ela herda o comportamento e os dados. Uma classe
base é especificada ao acrescentar dois-pontos e o nome de classe base depois do
nome de classe derivada, dessa maneira:

C#

public class Manager : Employee


{
// Employee fields, properties, methods and events are inherited
// New Manager fields, properties, methods and events go here...
}

Quando uma declaração de classe inclui uma classe base, ela herda todos os membros
da classe base, exceto os construtores. Para obter mais informações, consulte Herança.

Uma classe no C# só pode herdar diretamente de uma classe base. No entanto, como
uma classe base pode herdar de outra classe, uma classe poderia herdar indiretamente
várias classes base. Além disso, uma classe pode implementar diretamente uma ou mais
interfaces. Para obter mais informações, consulte Interfaces.

Uma classe pode ser declarada como abstract. Uma classe abstrata contém métodos
abstratos que têm uma definição de assinatura, mas não têm implementação. As classes
abstratas não podem ser instanciadas. Elas só podem ser usadas por meio de classes
derivadas que implementam os métodos abstratos. Por outro lado, uma classe lacrada
não permite que outras classes sejam derivadas dela. Para obter mais informações,
consulte Classes e Membros de Classes Abstratos e Lacrados.

As definições de classe podem ser divididas entre arquivos de origem diferentes. Para
obter mais informações, consulte Classes parciais e métodos.

Especificação da Linguagem C#
Para obter mais informações, consulte a Especificação da linguagem C#. A especificação
da linguagem é a fonte definitiva para a sintaxe e o uso de C#.
Introdução aos tipos de registro em C#
Artigo • 21/05/2024

Um registro em C# é uma classe ou um struct que fornece sintaxe e comportamento


especiais para trabalhar com modelos de dados. O modificador record instrui o
compilador a sintetizar membros que são úteis para tipos cuja função primária é
armazenar dados. Esses membros incluem uma sobrecarga de ToString() e membros
que dão suporte à igualdade de valor.

Quando usar registros


Considere usar um registro no lugar de uma classe ou struct nos seguintes cenários:

Você deseja definir um modelo de dados que depende da igualdade de valor.


Você deseja definir um tipo para o qual os objetos são imutáveis.

Igualdade de valor
Para registros, a igualdade de valores significa que duas variáveis de um tipo de registro
são iguais se os tipos corresponderem e todos os valores de propriedades e campos
forem iguais. Para outros tipos de referência, como classes, igualdade significa
igualdade de referência por padrão, a menos que a igualdade de valor tenha sido
implementada. Ou seja, duas variáveis de um tipo de classe são iguais quando se
referem ao mesmo objeto. Métodos e operadores que determinam a igualdade de duas
instâncias de registro usam igualdade de valor.

Nem todos os modelos de dados funcionam bem com igualdade de valor. Por exemplo,
o Entity Framework Core depende da igualdade de referência para garantir que ele use
apenas uma instância de um tipo de entidade para o que é conceitualmente uma
entidade. Por esse motivo, os tipos de registro não são apropriados para uso como tipos
de entidade no Entity Framework Core.

Imutabilidade
Um tipo imutável é aquele que impede que você altere qualquer propriedade ou valor
de campo de um objeto após ser instanciado. A imutabilidade pode ser útil quando
você precisa que um tipo seja thread-safe ou que um código hash permaneça o mesmo
em uma tabela de hash. Os registros fornecem sintaxe concisa para criar e trabalhar
com tipos imutáveis.
A imutabilidade não é apropriada para todos os cenários de dados. O Entity Framework
Core, por exemplo, não dá suporte à atualização com tipos de entidade imutáveis.

Como os registros diferem das classes e dos


structs
A mesma sintaxe que declara e instancia classes ou structs pode ser usada com
registros. Basta substituir a palavra-chave class por record , ou usar record struct em
vez de struct . Da mesma forma, as classes de registro dão suporte à mesma sintaxe
para expressar relações de herança. Os registros diferem das classes das seguintes
maneiras:

Você pode usar parâmetros posicionais em um construtor primário para criar e


instanciar um tipo com propriedades imutáveis.
Os mesmos métodos e operadores que indicam igualdade de referência ou
desigualdade em classes (como Object.Equals(Object) e == ), indicam igualdade de
valor ou desigualdade nos registros.
Você pode usar uma expressão with para criar uma cópia de um objeto imutável
com novos valores em propriedades selecionadas.
O método ToString de um registro cria uma cadeia de caracteres formatada que
mostra o nome do tipo de um objeto e os nomes e valores de todas as
propriedades públicas dele.
Um registro pode herdar de outro registro. Um registro não pode herdar de uma
classe, e uma classe não pode herdar de um registro.

Os structs de registro diferem dos structs, pois o compilador sintetiza os métodos de


igualdade e ToString . O compilador sintetiza o método Deconstruct para structs de
registro posicional.

O compilador sintetiza uma propriedade public init-only para cada parâmetro de


construtor primário em um record class . Em um record struct , o compilador sintetiza
uma propriedade pública de leitura/gravação. O compilador não cria propriedades para
parâmetros de construtor primário em tipos class e struct que não incluem
modificador record .

Exemplos
O exemplo a seguir define um registro público que usa parâmetros posicionais para
declarar e instanciar um registro. Em seguida, ele imprime o nome do tipo e os valores
de propriedade:
C#

public record Person(string FirstName, string LastName);

public static class Program


{
public static void Main()
{
Person person = new("Nancy", "Davolio");
Console.WriteLine(person);
// output: Person { FirstName = Nancy, LastName = Davolio }
}

O seguinte exemplo demonstra a igualdade de valor em registros:

C#

public record Person(string FirstName, string LastName, string[]


PhoneNumbers);
public static class Program
{
public static void Main()
{
var phoneNumbers = new string[2];
Person person1 = new("Nancy", "Davolio", phoneNumbers);
Person person2 = new("Nancy", "Davolio", phoneNumbers);
Console.WriteLine(person1 == person2); // output: True

person1.PhoneNumbers[0] = "555-1234";
Console.WriteLine(person1 == person2); // output: True

Console.WriteLine(ReferenceEquals(person1, person2)); // output:


False
}
}

O seguinte exemplo demonstra o uso de uma expressão with para copiar um objeto
imutável e alterar uma das propriedades:

C#

public record Person(string FirstName, string LastName)


{
public required string[] PhoneNumbers { get; init; }
}

public class Program


{
public static void Main()
{
Person person1 = new("Nancy", "Davolio") { PhoneNumbers = new
string[1] };
Console.WriteLine(person1);
// output: Person { FirstName = Nancy, LastName = Davolio,
PhoneNumbers = System.String[] }

Person person2 = person1 with { FirstName = "John" };


Console.WriteLine(person2);
// output: Person { FirstName = John, LastName = Davolio,
PhoneNumbers = System.String[] }
Console.WriteLine(person1 == person2); // output: False

person2 = person1 with { PhoneNumbers = new string[1] };


Console.WriteLine(person2);
// output: Person { FirstName = Nancy, LastName = Davolio,
PhoneNumbers = System.String[] }
Console.WriteLine(person1 == person2); // output: False

person2 = person1 with { };


Console.WriteLine(person1 == person2); // output: True
}
}

Para obter mais informações, confira Registros (referência de C#).

Especificação da Linguagem C#
Para obter mais informações, consulte a Especificação da linguagem C#. A especificação
da linguagem é a fonte definitiva para a sintaxe e o uso de C#.

6 Colaborar conosco no Comentários do .NET


GitHub O .NET é um projeto código aberto.
A fonte deste conteúdo pode Selecione um link para fornecer
ser encontrada no GitHub, onde comentários:
você também pode criar e
revisar problemas e solicitações  Abrir um problema de
de pull. Para obter mais documentação
informações, confira o nosso
guia para colaboradores.  Fornecer comentários sobre o
produto
Interfaces – definir comportamento
para vários tipos
Artigo • 18/03/2023

Uma interface contém definições para um grupo de funcionalidades relacionadas que


uma class ou um struct podem implementar. Uma interface pode definir métodos
static , que devem ter uma implementação. Uma interface pode definir uma

implementação padrão para membros. Uma interface pode não declarar dados de
instância, como campos, propriedades implementadas automaticamente ou eventos
semelhantes a propriedades.

Usando interfaces, você pode, por exemplo, incluir o comportamento de várias fontes
em uma classe. Essa funcionalidade é importante em C# porque a linguagem não dá
suporte a várias heranças de classes. Além disso, use uma interface se você deseja
simular a herança para structs, pois eles não podem herdar de outro struct ou classe.

Você define uma interface usando a palavra-chave interface, como mostra o exemplo a
seguir.

C#

interface IEquatable<T>
{
bool Equals(T obj);
}

O nome da interface deve ser um nome do identificador válido em C#. Por convenção,
os nomes de interface começam com uma letra maiúscula I .

Qualquer classe ou struct que implemente a interface IEquatable<T> deve conter uma
definição para um método Equals que corresponda à assinatura que a interface
especifica. Como resultado, você pode contar com uma classe que implementa
IEquatable<T> para conter um método Equals com o qual uma instância da classe pode
determinar se é igual a outra instância da mesma classe.

A definição de IEquatable<T> não fornece uma implementação para Equals . Uma classe
ou estrutura pode implementar várias interfaces, mas uma classe só pode herdar de
uma única classe.

Para obter mais informações sobre classes abstratas, consulte Classes e membros de
classes abstratos e lacrados.
As interfaces podem conter métodos, propriedades, eventos, indexadores ou qualquer
combinação desses quatro tipos de membros. As interfaces podem conter construtores
estáticos, campos, constantes ou operadores. A partir do C# 11, os membros da
interface que não são campos podem ser static abstract . Uma interface não pode
conter campos de instância, construtores de instância ou finalizadores. Os membros da
interface são públicos por padrão, e você pode especificar explicitamente modificadores
de acessibilidade, como public , protected , internal , private , protected internal ou
private protected . Um membro private deve ter uma implementação padrão.

Para implementar um membro de interface, o membro correspondente da classe de


implementação deve ser público, não estático e ter o mesmo nome e assinatura do
membro de interface.

7 Observação

Quando uma interface declara membros estáticos, um tipo que implementa essa
interface também pode declarar membros estáticos com a mesma assinatura. Eles
são distintos e identificados exclusivamente pelo tipo que declara o membro. O
membro estático declarado em um tipo não substitui o membro estático declarado
na interface.

Uma classe ou struct que implementa uma interface deve fornecer uma implementação
para todos os membros declarados sem uma implementação padrão fornecida pela
interface. No entanto, se uma classe base implementa uma interface, qualquer classe
que é derivada da classe base herda essa implementação.

O exemplo a seguir mostra uma implementação da interface IEquatable<T>. A classe de


implementação, Car , deverá fornecer uma implementação do método Equals.

C#

public class Car : IEquatable<Car>


{
public string? Make { get; set; }
public string? Model { get; set; }
public string? Year { get; set; }

// Implementation of IEquatable<T> interface


public bool Equals(Car? car)
{
return (this.Make, this.Model, this.Year) ==
(car?.Make, car?.Model, car?.Year);
}
}
As propriedades e os indexadores de uma classe podem definir acessadores extras para
uma propriedade ou o indexador que é definido em uma interface. Por exemplo, uma
interface pode declarar uma propriedade que tem um acessador get. A classe que
implementa a interface pode declarar a mesma propriedade tanto com um acessador
get quanto com um set. No entanto, se a propriedade ou o indexador usa a

implementação explícita, os acessadores devem corresponder. Para obter mais


informações sobre a implementação explícita, consulte Implementação de interface
explícita e Propriedades da interface.

Uma interface pode herdar de uma ou mais interfaces. A interface derivada herda os
membros de suas interfaces base. Uma classe que implementa uma interface derivada
deve implementar todos os membros na interface derivada, incluindo todos os
membros das interfaces base da interface derivada. Essa classe pode ser convertida
implicitamente para a interface derivada ou para qualquer uma de suas interfaces base.
Uma classe pode incluir uma interface várias vezes por meio das classes base que ela
herda ou por meio de interfaces que outras interfaces herdam. No entanto, a classe
poderá fornecer uma implementação de uma interface apenas uma vez e somente se a
classe declarar a interface como parte da definição de classe ( class ClassName :
InterfaceName ). Se a interface é herdada porque é herdada de uma classe base que

implementa a interface, a classe base fornece a implementação dos membros da


interface. No entanto, a classe derivada pode reimplementar qualquer membro de
interface virtual em vez de usar a implementação herdada. Quando as interfaces
declaram uma implementação padrão de um método, qualquer classe que implemente
essa interface herda essa implementação (você precisa converter a instância da classe
para o tipo de interface para acessar a implementação padrão no membro da interface).

Uma classe base também pode implementar membros de interface usando membros
virtuais. Nesse caso, uma classe derivada pode alterar o comportamento da interface
substituindo os membros virtuais. Para obter mais informações sobre membros virtuais,
consulte Polimorfismo.

Resumo de interfaces
Uma interface tem as propriedades a seguir:

Nas versões do C# anteriores à 8.0, uma interface é como uma classe base abstrata
que contém apenas membros abstratos. Qualquer classe ou struct que implemente
uma interface deve implementar todos os seus membros.
A partir do C# 8.0, uma interface pode definir implementações padrão para alguns
ou todos os seus membros. Uma classe ou struct que implementa a interface não
precisa implementar membros que tenham implementações padrão. Para obter
mais informações, consulte método de interface padrão.
Uma interface não pode ser instanciada diretamente. Seus membros são
implementados por qualquer classe ou struct que implemente a interface.
Uma classe ou struct pode implementar várias interfaces. Uma classe pode herdar
uma classe base e também implementar uma ou mais interfaces.
Classes e métodos genéricos
Artigo • 22/03/2024

Os genéricos introduzem o conceito de parâmetros de tipo no .NET. Os genéricos


possibilitam projetar classes e métodos que adiam a especificação de um ou mais
parâmetros de tipo até que você use a classe ou o método em seu código. Por exemplo,
ao usar um parâmetro de tipo genérico T , você pode escrever uma única classe que
outro código de cliente pode usar sem incorrer o custo ou risco de conversões de
runtime ou operações de conversão boxing, conforme mostrado aqui:

C#

// Declare the generic class.


public class GenericList<T>
{
public void Add(T input) { }
}
class TestGenericList
{
private class ExampleClass { }
static void Main()
{
// Declare a list of type int.
GenericList<int> list1 = new GenericList<int>();
list1.Add(1);

// Declare a list of type string.


GenericList<string> list2 = new GenericList<string>();
list2.Add("");

// Declare a list of type ExampleClass.


GenericList<ExampleClass> list3 = new GenericList<ExampleClass>();
list3.Add(new ExampleClass());
}
}

As classes e os métodos genéricos combinam reutilização, segurança de tipo e eficiência


de uma forma que suas contrapartes não genéricas não conseguem. Os parâmetros de
tipo genérico são substituídos pelos argumentos de tipo durante a compilação. No
exemplo anterior, o compilador substitui T por int . Os genéricos são usados com mais
frequência com coleções e com os métodos que operam nelas. O namespace
System.Collections.Generic contém várias classes de coleção de base genérica. As
coleções não genéricas, como ArrayList, não são recomendadas e são mantidas apenas
para fins de compatibilidade. Para saber mais, confira Genéricos no .NET.
Você também pode criar tipos e métodos genéricos personalizados para fornecer suas
próprias soluções e padrões de design generalizados que sejam fortemente tipados e
eficientes. O exemplo de código a seguir mostra uma classe de lista vinculada genérica
simples para fins de demonstração. (Na maioria dos casos, você deve usar a classe
List<T> fornecida pelo .NET em vez de criar a sua própria.) O parâmetro de tipo T é
usado em vários locais em que um tipo concreto normalmente seria usado para indicar
o tipo do item armazenado na lista:

Como o tipo de um parâmetro de método no método AddHead .


Como o tipo de retorno da propriedade Data na classe Node aninhada.
Como o tipo de data do membro particular na classe aninhada.

T está disponível para a classe aninhada Node . Quando GenericList<T> é instanciado

com um tipo concreto, por exemplo, como GenericList<int> , cada ocorrência de T é


substituída por int .

C#

// type parameter T in angle brackets


public class GenericList<T>
{
// The nested class is also generic on T.
private class Node
{
// T used in non-generic constructor.
public Node(T t)
{
next = null;
data = t;
}

private Node? next;


public Node? Next
{
get { return next; }
set { next = value; }
}

// T as private member data type.


private T data;

// T as return type of property.


public T Data
{
get { return data; }
set { data = value; }
}
}
private Node? head;

// constructor
public GenericList()
{
head = null;
}

// T as method parameter type:


public void AddHead(T t)
{
Node n = new Node(t);
n.Next = head;
head = n;
}

public IEnumerator<T> GetEnumerator()


{
Node? current = head;

while (current != null)


{
yield return current.Data;
current = current.Next;
}
}
}

O exemplo de código a seguir mostra como o código cliente usa a classe


GenericList<T> genérica para criar uma lista de inteiros. Se você alterar o argumento

de tipo, o código a seguir criará listas de cadeias de caracteres ou qualquer outro tipo
personalizado:

C#

class TestGenericList
{
static void Main()
{
// int is the type argument
GenericList<int> list = new GenericList<int>();

for (int x = 0; x < 10; x++)


{
list.AddHead(x);
}

foreach (int i in list)


{
System.Console.Write(i + " ");
}
System.Console.WriteLine("\nDone");
}
}

7 Observação

Os tipos genéricos não estão limitados a classes. Os exemplos anteriores usam os


tipos class , mas você pode definir os tipos genéricos interface e struct ,
inclusive os tipos record .

Visão geral de genéricos


Use tipos genéricos para maximizar a reutilização de código, o desempenho e a
segurança de tipo.
O uso mais comum de genéricos é para criar classes de coleção.
A biblioteca de classes do .NET contém várias classes de coleção de genéricos no
namespace System.Collections.Generic. As coleções genéricas devem ser usadas
sempre que possível, em vez de classes como ArrayList no namespace
System.Collections.
Você pode criar suas próprias interfaces genéricas, classes, métodos, eventos e
delegados.
As classes genéricas podem ser restringidas para permitir o acesso a métodos em
tipos de dados específicos.
Você pode obter informações em runtime sobre os tipos que são usados em um
tipo de dados genérico usando reflexão.

Especificação da linguagem C#
Para obter mais informações, consulte a Especificação da linguagem C#.

Confira também
Generics in .NET (Genéricos no .NET)
System.Collections.Generic

6 Colaborar conosco no Comentários do .NET


GitHub O .NET é um projeto código aberto.
Selecione um link para fornecer
A fonte deste conteúdo pode comentários:
ser encontrada no GitHub, onde
você também pode criar e  Abrir um problema de
revisar problemas e solicitações documentação
de pull. Para obter mais
informações, confira o nosso  Fornecer comentários sobre o
guia para colaboradores. produto
Tipos anônimos
Artigo • 06/12/2023

Os tipos anônimos fornecem um modo conveniente de encapsular um conjunto de


propriedades somente leitura em um único objeto sem a necessidade de primeiro
definir explicitamente um tipo. O nome do tipo é gerado pelo compilador e não está
disponível no nível do código-fonte. O tipo de cada propriedade é inferido pelo
compilador.

Crie tipos anônimos usando o operador new com um inicializador de objeto. Para obter
mais informações sobre inicializadores de objeto, consulte Inicializadores de Objeto e
Coleção.

O exemplo a seguir mostra um tipo anônimo que é inicializado com duas propriedades
chamadas Amount e Message .

C#

var v = new { Amount = 108, Message = "Hello" };

// Rest the mouse pointer over v.Amount and v.Message in the following
// statement to verify that their inferred types are int and string.
Console.WriteLine(v.Amount + v.Message);

Os tipos anônimos são normalmente usados na cláusula select de uma expressão de


consulta para retornar um subconjunto das propriedades de cada objeto na sequência
de origem. Para obter mais informações sobre consultas, consulte LINQ no C#.

Os tipos anônimos contêm uma ou mais propriedades públicas somente leitura.


Nenhum outro tipo de membros da classe, como métodos ou eventos, é válido. A
expressão que é usada para inicializar uma propriedade não pode ser null , uma função
anônima ou um tipo de ponteiro.

O cenário mais comum é inicializar um tipo anônimo com propriedades de outro tipo.
No exemplo a seguir, suponha que existe uma classe com o nome Product . A classe
Product inclui as propriedades Color e Price , além de outras propriedades que não lhe

interessam. A variável products é uma coleção de objetos do Product . A declaração do


tipo anônimo começa com a palavra-chave new . A declaração inicializa um novo tipo
que usa apenas duas propriedades de Product . O uso de tipos anônimos faz com que
uma menor quantidade de dados seja retornada na consulta.
Quando você não especifica os nomes de membros no tipo anônimo, o compilador dá
aos membros de tipo anônimo o mesmo nome da propriedade que está sendo usada
para inicializá-los. Forneça um nome para a propriedade que está sendo inicializada
com uma expressão, como mostrado no exemplo anterior. No exemplo a seguir, os
nomes das propriedades do tipo anônimo são Color e Price .

C#

var productQuery =
from prod in products
select new { prod.Color, prod.Price };

foreach (var v in productQuery)


{
Console.WriteLine("Color={0}, Price={1}", v.Color, v.Price);
}

 Dica

Você pode usar a regra de estilo .NET IDE0037 para impor se os nomes de
membros inferidos ou explícitos são preferenciais.

Também é possível definir um campo por objeto de outro tipo: classe, struct ou até
mesmo outro tipo anônimo. Isso é feito usando a variável que contém esse objeto
exatamente como no exemplo a seguir, onde dois tipos anônimos são criados usando
tipos definidos pelo usuário já instanciados. Em ambos os casos o campo product nos
tipos anônimos shipment e shipmentWithBonus serão do tipo Product contendo seus
valores padrão de cada campo. E o campo bonus será do tipo anônimo criado pelo
compilador.

C#

var product = new Product();


var bonus = new { note = "You won!" };
var shipment = new { address = "Nowhere St.", product };
var shipmentWithBonus = new { address = "Somewhere St.", product, bonus };

Normalmente, ao usar um tipo anônimo para inicializar uma variável, a variável é


declarada como uma variável local de tipo implícito usando var. O nome do tipo não
pode ser especificado na declaração da variável, porque apenas o compilador tem
acesso ao nome subjacente do tipo anônimo. Para obter mais informações sobre var ,
consulte Variáveis de local digitadas implicitamente.
Você pode criar uma matriz de elementos de tipo anônimo combinando uma variável
local de tipo implícito e uma matriz de tipo implícito, como mostrado no exemplo a
seguir.

C#

var anonArray = new[] { new { name = "apple", diam = 4 }, new { name =


"grape", diam = 1 }};

Os tipos anônimos são tipos class que derivam diretamente de object e que não podem
ser convertidos para qualquer tipo, exceto para object. O compilador fornece um nome
para cada tipo anônimo, embora o seu aplicativo não possa acessá-lo. Do ponto de vista
do Common Language Runtime, um tipo anônimo não é diferente de qualquer outro
tipo de referência.

Se dois ou mais inicializadores de objeto anônimos em um assembly especificarem uma


sequência de propriedades que estão na mesma ordem e que têm os mesmos nomes e
tipos, o compilador tratará os objetos como instâncias do mesmo tipo. Eles
compartilham o mesmo tipo de informação gerado pelo compilador.

Tipos anônimos dão suporte a mutações não destrutivas na forma de expressões with.
Isso permite que você crie uma nova instância de um tipo anônimo em que uma ou
mais propriedades têm novos valores:

C#

var apple = new { Item = "apples", Price = 1.35 };


var onSale = apple with { Price = 0.79 };
Console.WriteLine(apple);
Console.WriteLine(onSale);

Você não pode declarar que um campo, uma propriedade, um evento ou um tipo de
retorno de um método tem um tipo anônimo. Da mesma forma, não pode declarar que
um parâmetro formal de um método, propriedade, construtor ou indexador tem um
tipo anônimo. Para passar um tipo anônimo ou uma coleção que contenha tipos
anônimos como um argumento para um método, você pode declarar o parâmetro
como tipo object . No entanto, usar object para tipos anônimos anula o propósito da
tipagem forte. Se você precisa armazenar os resultados da consulta ou passá-los fora do
limite do método, considere o uso de uma estrutura ou classe com denominação
comum em vez de um tipo anônimo.

Como os métodos Equals e GetHashCode em tipos anônimos são definidos em termos


dos métodos das propriedades Equals e GetHashCode , duas instâncias do mesmo tipo
anônimo são iguais somente se todas as suas propriedades forem iguais.

7 Observação

O nível de acessibilidade de um tipo anônimo é internal , portanto, dois tipos


anônimos definidos em assemblies diferentes não são do mesmo tipo. Portanto,
instâncias de tipos anônimos não podem ser iguais umas às outras quando
definidas em assemblies diferentes, mesmo quando todas as suas propriedades são
iguais.

Tipos anônimos substituem o método ToString, concatenando o nome e a saída


ToString de cada propriedade cercada por chaves.

var v = new { Title = "Hello", Age = 24 };

Console.WriteLine(v.ToString()); // "{ Title = Hello, Age = 24 }"

6 Colaborar conosco no Comentários do .NET


GitHub O .NET é um projeto código aberto.
A fonte deste conteúdo pode Selecione um link para fornecer
ser encontrada no GitHub, onde comentários:
você também pode criar e
revisar problemas e solicitações  Abrir um problema de
de pull. Para obter mais documentação
informações, confira o nosso
guia para colaboradores.  Fornecer comentários sobre o
produto
Visão geral de classes, structs e registros
em C#
Artigo • 11/04/2024

Em C#, a definição de um tipo — uma classe, um struct ou um registro — é como um


blueprint que especifica o que o tipo pode fazer. Um objeto é basicamente um bloco de
memória que foi alocado e configurado de acordo com o esquema. Este artigo fornece
uma visão geral desses blueprints e os respectivos recursos. O próximo artigo desta
série apresenta objetos.

Encapsulamento
Encapsulamento é chamado, ocasionalmente, de primeiro pilar ou princípio da
programação orientada a objeto. Uma classe ou um struct pode especificar qual
membro será codificado fora da classe ou do struct. Os métodos e as variáveis que não
serão usados fora da classe ou assembly poderão ser ocultados para limitar erros de
codificação potenciais ou explorações maliciosas. Para obter mais informações, confira o
tutorial de Programação orientada a objeto.

Membros
Os membros de um tipo incluem todos os métodos, campos, constantes, propriedades e
eventos. No C#, não existem variáveis globais ou métodos como em algumas das outras
linguagens. Mesmo o ponto de entrada de um programa, o método Main , deve ser
declarado dentro de uma classe ou de um struct (implicitamente quando você usa
instruções de nível superior).

A lista a seguir inclui todos os vários tipos de membros que podem ser declarados em
uma classe, um struct ou um registro.

Campos
Constantes
Propriedades
Métodos
Construtores
Eventos
Finalizadores
Indexadores
Operadores
Tipos aninhados

Para obter mais informações, confira Membros.

Acessibilidade
Alguns métodos e propriedades devem ser chamados ou acessado pelo código fora da
classe ou do struct, também conhecido como código de cliente. Outros métodos e
propriedades podem ser usados apenas na classe ou struct em si. É importante limitar o
acessibilidade do código para que somente o código do cliente desejado possa fazer
contato. Você especifica o grau de acessibilidade dos tipos e os respectivos membros ao
código do cliente usando os seguintes modificadores de acesso:

público
protected
interno
internos protegidos
private
protegido de forma particular.

A acessibilidade padrão é private .

Herança
Classes (mas não structs) dão suporte ao conceito de herança. Uma classe que deriva de
outra classe, chamada classe base, contém automaticamente todos os membros
públicos, protegidos e internos da classe base, exceto seus construtores e finalizadores.

As classes podem ser declaradas como abstratas, o que significa que um ou mais dos
seus métodos não têm nenhuma implementação. Embora as classes abstratas não
possam ser instanciadas diretamente, elas servem como classes base para outras classes
que fornecem a implementação ausente. As classes também podem ser declaradas
como lacradas para impedir que outras classes herdem delas.

Para obter mais informações, consulte Herança e Polimorfismo.

Interfaces
Classes, structs e registros podem implementar várias interfaces. Implementar a partir de
uma interface significa que o tipo implementa todos os métodos definidos na interface.
Para obter mais informações, consulte Interfaces.
Tipos genéricos
Classes, structs e registros podem ser definidos com um ou mais parâmetros de tipo. O
código do cliente fornece o tipo quando ele cria uma instância do tipo. Por exemplo a
classe List<T> no namespace System.Collections.Generic é definida com um parâmetro
de tipo. O código do cliente cria uma instância de um List<string> ou List<int> para
especificar o tipo que a lista conterá. Para obter mais informações, consulte Genéricos.

Tipos estáticos
Classes (mas não structs ou registros) podem ser declaradas como static . Uma classe
estática pode conter apenas membros estáticos e não pode ser instanciada com a
palavra-chave new . Uma cópia da classe é carregada na memória quando o programa é
carregado e seus membros são acessados pelo nome da classe. Classes, structs e
registros podem conter membros estáticos. Para obter mais informações, consulte
Classes estáticas e membros de classes estáticas.

Tipos aninhados
Uma classe, struct ou registro pode ser aninhado dentro de outra classe, struct ou
registro. Para obter mais informações, consulte Tipos aninhados.

Tipos parciais
Você pode definir parte de uma classe, struct ou método em um arquivo de código e
outra parte em um arquivo de código separado. Para obter mais informações, consulte
Classes parciais e métodos.

Inicializadores de objeto
Você pode criar uma instância e inicializar objetos de classe ou struct e coleções de
objetos, atribuindo valores às respectivas propriedades. Para mais informações, consulte
Como inicializar objetos usando um inicializador de objeto.

Tipos anônimos
Em situações em que não é conveniente ou necessário criar uma classe nomeada, você
usa tipos anônimos. Tipos anônimos são definidos pelos membros de dados nomeados.
Para obter mais informações, consulte Tipos anônimos.

Métodos de Extensão
Você pode "estender" uma classe sem criar uma classe derivada criando um tipo
separado. Esse tipo contém métodos que podem ser chamados como se pertencessem
ao tipo original. Para obter mais informações, consulte Métodos de extensão.

Variáveis Locais Tipadas Implicitamente


Dentro de um método de classe ou struct, você pode usar digitação implícita para
instruir o compilador para determinar o tipo de variável no tempo de compilação. Para
obter mais informações, confira var (referência C#).

Registros
O C# 9 introduz o tipo record , um tipo de referência que você pode criar em vez de
uma classe ou um struct. Os registros são classes com comportamento interno para
encapsular dados em tipos imutáveis. O C# 10 introduz o tipo de valor record struct .
Um registro ( record class ou record struct ) fornece os seguintes recursos:

Sintaxe concisa para criar um tipo de referência com propriedades imutáveis.


Igualdade de valor. Duas variáveis de um tipo de registro são iguais se tiverem o
mesmo tipo e se, para cada campo, os valores em ambos os registros forem iguais.
As classes usam igualdade de referência: duas variáveis de um tipo de classe são
iguais se elas se referirem ao mesmo objeto.
Sintaxe concisa para mutação não destrutiva. Uma expressão with permite criar
uma nova instância de registro que seja uma cópia de uma instância existente, mas
com valores de propriedade especificados alterados.
Formatação interna para exibição. O método ToString imprime o nome do tipo de
registro e os nomes e valores das propriedades públicas.
Suporte para hierarquias de herança em classes de registro. Classes de registro
dão suporte à herança. Os structs de registro não dão suporte à herança.

Para obter mais informações, confira Registros.

Especificação da Linguagem C#
Para obter mais informações, consulte a Especificação da linguagem C#. A especificação
da linguagem é a fonte definitiva para a sintaxe e o uso de C#.
Comentários
Esta página foi útil?  Yes  No

Fornecer comentários sobre o produto


Objetos – criar instâncias de tipos
Artigo • 10/05/2023

Uma definição de classe ou struct é como um esquema que especifica o que o tipo
pode fazer. Um objeto é basicamente um bloco de memória que foi alocado e
configurado de acordo com o esquema. Um programa pode criar vários objetos da
mesma classe. Objetos também são chamados de instâncias e podem ser armazenados
em uma variável nomeada ou em uma matriz ou coleção. O código de cliente é o
código que usa essas variáveis para chamar os métodos e acessar as propriedades
públicas do objeto. Em uma linguagem orientada a objetos, como o C#, um programa
típico consiste em vários objetos que interagem dinamicamente.

7 Observação

Tipos estáticos se comportam de modo diferente do que está descrito aqui. Para
obter mais informações, consulte Classes estáticas e membros de classes estáticas.

Instâncias Struct vs. Instâncias de classe


Como as classes são tipos de referência, uma variável de um objeto de classe contém
uma referência ao endereço do objeto no heap gerenciado. Se uma segunda variável do
mesmo tipo for atribuída à primeira variável, as duas variáveis farão referência ao objeto
nesse endereço. Esse ponto é abordado com mais detalhes posteriormente neste artigo.

Instâncias de classes são criadas usando o new operador. No exemplo a seguir, Person é
o tipo e person1 e person2 são instâncias ou objetos desse tipo.

C#

public class Person


{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
// Other properties, methods, events...
}

class Program
{
static void Main()
{
Person person1 = new Person("Leopold", 6);
Console.WriteLine("person1 Name = {0} Age = {1}", person1.Name,
person1.Age);

// Declare new person, assign person1 to it.


Person person2 = person1;

// Change the name of person2, and person1 also changes.


person2.Name = "Molly";
person2.Age = 16;

Console.WriteLine("person2 Name = {0} Age = {1}", person2.Name,


person2.Age);
Console.WriteLine("person1 Name = {0} Age = {1}", person1.Name,
person1.Age);
}
}
/*
Output:
person1 Name = Leopold Age = 6
person2 Name = Molly Age = 16
person1 Name = Molly Age = 16
*/

Como structs são tipos de valor, uma variável de um objeto de struct mantém uma
cópia do objeto inteiro. Instâncias de structs também podem ser criadas usando o
operador new , mas isso não é obrigatório, conforme mostrado no exemplo a seguir:

C#

namespace Example;

public struct Person


{
public string Name;
public int Age;
public Person(string name, int age)
{
Name = name;
Age = age;
}
}

public class Application


{
static void Main()
{
// Create struct instance and initialize by using "new".
// Memory is allocated on thread stack.
Person p1 = new Person("Alex", 9);
Console.WriteLine("p1 Name = {0} Age = {1}", p1.Name, p1.Age);

// Create new struct object. Note that struct can be initialized


// without using "new".
Person p2 = p1;

// Assign values to p2 members.


p2.Name = "Spencer";
p2.Age = 7;
Console.WriteLine("p2 Name = {0} Age = {1}", p2.Name, p2.Age);

// p1 values remain unchanged because p2 is copy.


Console.WriteLine("p1 Name = {0} Age = {1}", p1.Name, p1.Age);
}
}
/*
Output:
p1 Name = Alex Age = 9
p2 Name = Spencer Age = 7
p1 Name = Alex Age = 9
*/

A memória de p1 e p2 é alocada na pilha de thread. Essa memória é recuperada em


conjunto com o tipo ou método em que ela é declarada. Esse é um dos motivos pelos
quais os structs são copiados na atribuição. Por outro lado, a memória alocada a uma
instância de classe é recuperada automaticamente (o lixo é coletado) pelo Common
Language Runtime quando todas as referências ao objeto tiveram saído do escopo. Não
é possível destruir de forma determinista um objeto de classe, como é possível no C++.
Para obter mais informações sobre a coleta de lixo no , consulte Coleta de lixo.

7 Observação

A alocação e a desalocação de memória no heap gerenciado é altamente otimizada


no Common Language Runtime. Na maioria dos casos, não há uma diferença
significativa quanto ao custo do desempenho de alocar uma instância da classe no
heap em vez de alocar uma instância de struct na pilha.

Identidade do Objeto vs. Igualdade de Valor


Quando compara dois objetos quanto à igualdade, primeiro você precisa distinguir se
quer saber se as duas variáveis representam o mesmo objeto na memória ou se os
valores de um ou mais de seus campos são equivalentes. Se quiser comparar valores,
você precisa considerar se os objetos são instâncias de tipos de valor (structs) ou tipos
de referência (classes, delegados, matrizes).
Para determinar se duas instâncias de classe se referem ao mesmo local na
memória (o que significa que elas têm a mesma identidade), use o método
Object.Equals estático. (System.Object é a classe base implícita para todos os tipos
de valor e tipos de referência, incluindo classes e structs definidos pelo usuário.)

Para determinar se os campos de instância em duas instâncias de struct têm os


mesmos valores, use o método ValueType.Equals. Como todos os structs herdam
implicitamente de System.ValueType, você chama o método diretamente em seu
objeto, conforme mostrado no exemplo a seguir:

C#

// Person is defined in the previous example.

//public struct Person


//{
// public string Name;
// public int Age;
// public Person(string name, int age)
// {
// Name = name;
// Age = age;
// }
//}

Person p1 = new Person("Wallace", 75);


Person p2 = new Person("", 42);
p2.Name = "Wallace";
p2.Age = 75;

if (p2.Equals(p1))
Console.WriteLine("p2 and p1 have the same values.");

// Output: p2 and p1 have the same values.

A implementaçãoSystem.ValueType de Equals usa conversão boxing e reflexão em


alguns casos. Para obter informações sobre como fornecer um algoritmo de
igualdade eficiente específico ao seu tipo, consulte Como definir a igualdade de
valor para um tipo. Os registros são tipos de referência que usam semântica de
valor para igualdade.

Para determinar se os valores dos campos em duas instâncias de classe são iguais,
você pode usar o método Equals ou o Operador ==. No entanto, use-os apenas se
a classe os tiver substituído ou sobrecarregado para fornecer uma definição
personalizada do que "igualdade" significa para objetos desse tipo. A classe
também pode implementar a interface IEquatable<T> ou a interface
IEqualityComparer<T>. As duas interfaces fornecem métodos que podem ser
usados para testar a igualdade de valores. Ao criar suas próprias classes que
substituem Equals , certifique-se de seguir as diretrizes informadas em Como
definir a igualdade de valor para um tipo e Object.Equals(Object).

Seções relacionadas
Para mais informações:

Classes
Construtores
Finalizadores
Eventos
object
Herança
class
Tipos de estrutura
Operador new
Common Type System
Herança – derivar tipos para criar um
comportamento mais especializado
Artigo • 07/04/2023

A herança, assim como o encapsulamento e o polimorfismo, é uma das três principais


características da programação orientada ao objeto. A herança permite que você crie
novas classes que reutilizam, estendem e modificam o comportamento definido em
outras classes. A classe cujos membros são herdados é chamada classe base e a classe
que herda esses membros é chamada classe derivada. Uma classe derivada pode ter
apenas uma classe base direta. No entanto, a herança é transitiva. Se ClassC for
derivado de ClassB e ClassB for derivado de ClassA , ClassC herdará os membros
declarados em ClassB e ClassA .

7 Observação

Structs não dão suporte a herança, mas podem implementar interfaces.

Conceitualmente, uma classe derivada é uma especialização da classe base. Por


exemplo, se tiver uma classe base Animal , você pode ter uma classe derivada chamada
Mammal e outra classe derivada chamada Reptile . Um Mammal é um Animal e um

Reptile é um Animal , mas cada classe derivada representa especializações diferentes


da classe base.

As declarações de interface podem definir uma implementação padrão para seus


membros. Essas implementações são herdadas por interfaces derivadas e por classes
que implementam essas interfaces. Para obter mais informações sobre métodos de
interface padrão, confira o artigo sobre interfaces.

Quando você define uma classe para derivar de outra classe, a classe derivada obtém
implicitamente todos os membros da classe base, exceto por seus construtores e
finalizadores. A classe derivada reutiliza o código na classe base sem precisar
implementá-lo novamente. Na classe derivada, você pode adicionar mais membros. A
classe derivada estende a funcionalidade da classe base.

A ilustração a seguir mostra uma classe WorkItem que representa um item de trabalho
em um processo comercial. Como todas as classes, ela deriva de System.Object e herda
todos os seus métodos. WorkItem adiciona seis membros próprios. Esses membros
incluem um construtor, porque os construtores não são herdados. A classe
ChangeRequest herda de WorkItem e representa um tipo específico de item de trabalho.
ChangeRequest adiciona mais dois membros aos membros que herda de WorkItem e de

Object. Ele deve adicionar seu próprio construtor e também adiciona originalItemID . A
propriedade originalItemID permite que a instância ChangeRequest seja associada ao
WorkItem original a que a solicitação de alteração se aplica.

O exemplo a seguir mostra como as relações entre as classes demonstradas na


ilustração anterior são expressos em C#. O exemplo também mostra como WorkItem
substitui o método virtual Object.ToString e como a classe ChangeRequest herda a
implementação de WorkItem do método. O primeiro bloco define as classes:

C#

// WorkItem implicitly inherits from the Object class.


public class WorkItem
{
// Static field currentID stores the job ID of the last WorkItem that
// has been created.
private static int currentID;

//Properties.
protected int ID { get; set; }
protected string Title { get; set; }
protected string Description { get; set; }
protected TimeSpan jobLength { get; set; }

// Default constructor. If a derived class does not invoke a base-


// class constructor explicitly, the default constructor is called
// implicitly.
public WorkItem()
{
ID = 0;
Title = "Default title";
Description = "Default description.";
jobLength = new TimeSpan();
}

// Instance constructor that has three parameters.


public WorkItem(string title, string desc, TimeSpan joblen)
{
this.ID = GetNextID();
this.Title = title;
this.Description = desc;
this.jobLength = joblen;
}

// Static constructor to initialize the static member, currentID. This


// constructor is called one time, automatically, before any instance
// of WorkItem or ChangeRequest is created, or currentID is referenced.
static WorkItem() => currentID = 0;

// currentID is a static field. It is incremented each time a new


// instance of WorkItem is created.
protected int GetNextID() => ++currentID;

// Method Update enables you to update the title and job length of an
// existing WorkItem object.
public void Update(string title, TimeSpan joblen)
{
this.Title = title;
this.jobLength = joblen;
}

// Virtual method override of the ToString method that is inherited


// from System.Object.
public override string ToString() =>
$"{this.ID} - {this.Title}";
}

// ChangeRequest derives from WorkItem and adds a property (originalItemID)


// and two constructors.
public class ChangeRequest : WorkItem
{
protected int originalItemID { get; set; }

// Constructors. Because neither constructor calls a base-class


// constructor explicitly, the default constructor in the base class
// is called implicitly. The base class must contain a default
// constructor.

// Default constructor for the derived class.


public ChangeRequest() { }

// Instance constructor that has four parameters.


public ChangeRequest(string title, string desc, TimeSpan jobLen,
int originalID)
{
// The following properties and the GetNexID method are inherited
// from WorkItem.
this.ID = GetNextID();
this.Title = title;
this.Description = desc;
this.jobLength = jobLen;

// Property originalItemID is a member of ChangeRequest, but not


// of WorkItem.
this.originalItemID = originalID;
}
}

Este próximo bloco mostra como usar as classes base e derivadas:

C#

// Create an instance of WorkItem by using the constructor in the


// base class that takes three arguments.
WorkItem item = new WorkItem("Fix Bugs",
"Fix all bugs in my code branch",
new TimeSpan(3, 4, 0, 0));

// Create an instance of ChangeRequest by using the constructor in


// the derived class that takes four arguments.
ChangeRequest change = new ChangeRequest("Change Base Class Design",
"Add members to the class",
new TimeSpan(4, 0, 0),
1);

// Use the ToString method defined in WorkItem.


Console.WriteLine(item.ToString());

// Use the inherited Update method to change the title of the


// ChangeRequest object.
change.Update("Change the Design of the Base Class",
new TimeSpan(4, 0, 0));

// ChangeRequest inherits WorkItem's override of ToString.


Console.WriteLine(change.ToString());
/* Output:
1 - Fix Bugs
2 - Change the Design of the Base Class
*/

Métodos abstratos e virtuais


Quando uma classe base declara um método como virtual, uma classe derivada poderá
override o método com a própria implementação. Se uma classe base declarar um
membro como abstract, esse método deve ser substituído em qualquer classe não
abstrata que herdar diretamente da classe. Se uma classe derivada for abstrato, ele
herdará membros abstratos sem implementá-los. Membros abstratos e virtuais são a
base do polimorfismo, que é a segunda característica principal da programação
orientada a objetos. Para obter mais informações, consulte Polimorfismo.

Classes base abstratas


Você poderá declarar uma classe como abstrata se quiser impedir a instanciação direta
usando o operador new. Uma classe abstrata poderá ser usada somente se uma nova
classe for derivada dela. Uma classe abstrata pode conter uma ou mais assinaturas de
método que também são declaradas como abstratas. Essas assinaturas especificam os
parâmetros e o valor retornado, mas não têm nenhuma implementação (corpo do
método). Uma classe abstrata não precisa conter membros abstratos. No entanto, se
uma classe contiver um membro abstrato, a própria classe deverá ser declarada como
abstrata. Classes derivadas que não são abstratas devem fornecer a implementação para
qualquer método abstrato de uma classe base abstrata.

Interfaces
Uma interface é um tipo de referência que define um conjunto de membros. Todas as
classes e structs que implementam essa interface devem implementar esse conjunto de
membros. Uma interface pode definir uma implementação padrão para qualquer um
desses membros. Uma classe pode implementar várias interfaces, mesmo que ela possa
derivar de apenas uma classe base direta.

Interfaces são usadas para definir recursos específicos para classes que não têm
necessariamente uma relação do tipo “é um”. Por exemplo, a interface
System.IEquatable<T> pode ser implementada por qualquer classe ou struct para
determinar se dois objetos do tipo são equivalentes (como quer que o tipo defina a
equivalência). IEquatable<T> não implica o mesmo tipo de relação "é um" existente
entre uma classe base e uma classe derivada (por exemplo, um Mammal é um Animal ).
Para obter mais informações, consulte Interfaces.

Impedindo derivações adicionais


Uma classe pode impedir que outras classes herdem dela ou de qualquer um de seus
membros ao declarar a si mesma ou o membro como sealed.

Ocultação de membros da classe base pela


classe derivada
Uma classe derivada pode ocultar membros da classe base declarando membros com
mesmo nome e assinatura. O modificador new pode ser usado para indicar
explicitamente que o membro não pretende ser uma substituição do membro base. O
uso de new não é necessário, mas um aviso do compilador será gerado se new não for
usado. Para obter mais informações, consulte Controle de versão com as palavras-chave
override e new e Quando usar as palavras-chave override e new.
Polimorfismo
Artigo • 07/04/2023

O polimorfismo costuma ser chamado de o terceiro pilar da programação orientada a


objetos, depois do encapsulamento e a herança. O polimorfismo é uma palavra grega
que significa "de muitas formas" e tem dois aspectos distintos:

Em tempo de execução, os objetos de uma classe derivada podem ser tratados


como objetos de uma classe base, em locais como parâmetros de método,
coleções e matrizes. Quando esse polimorfismo ocorre, o tipo declarado do objeto
não é mais idêntico ao seu tipo de tempo de runtime.
As classes base podem definir e implementar métodosvirtuais e as classes
derivadas podem substituí-los, o que significa que elas fornecem sua própria
definição e implementação. Em tempo de execução, quando o código do cliente
chama o método, o CLR procura o tipo de tempo de execução do objeto e invoca
a substituição do método virtual. No código-fonte, você pode chamar um método
em uma classe base e fazer com que a versão de uma classe derivada do método
seja executada.

Os métodos virtuais permitem que você trabalhe com grupos de objetos relacionados
de maneira uniforme. Por exemplo, suponha que você tem um aplicativo de desenho
que permite que um usuário crie vários tipos de formas sobre uma superfície de
desenho. Você não sabe no tempo de compilação quais tipos específicos de formas o
usuário criará. No entanto, o aplicativo precisa manter controle de todos os diferentes
tipos de formas que são criados e atualizá-los em resposta às ações do mouse do
usuário. Você pode usar o polimorfismo para resolver esse problema em duas etapas
básicas:

1. Crie uma hierarquia de classes em que cada classe de forma específica derive de
uma classe base comum.
2. Use um método virtual para invocar o método adequado em qualquer classe
derivada por meio de uma única chamada para o método da classe base.

Primeiro, crie uma classe base chamada Shape e as classes derivadas como Rectangle ,
Circle e Triangle . Atribua à classe Shape um método virtual chamado Draw e
substitua-o em cada classe derivada para desenhar a forma especial que a classe
representa. Crie um objeto List<Shape> e adicione um Circle , Triangle e Rectangle a
ele.

C#
public class Shape
{
// A few example members
public int X { get; private set; }
public int Y { get; private set; }
public int Height { get; set; }
public int Width { get; set; }

// Virtual method
public virtual void Draw()
{
Console.WriteLine("Performing base class drawing tasks");
}
}

public class Circle : Shape


{
public override void Draw()
{
// Code to draw a circle...
Console.WriteLine("Drawing a circle");
base.Draw();
}
}
public class Rectangle : Shape
{
public override void Draw()
{
// Code to draw a rectangle...
Console.WriteLine("Drawing a rectangle");
base.Draw();
}
}
public class Triangle : Shape
{
public override void Draw()
{
// Code to draw a triangle...
Console.WriteLine("Drawing a triangle");
base.Draw();
}
}

Para atualizar a superfície de desenho, use um loop foreach para iterar na lista e chamar
o método Draw em cada objeto Shape na lista. Mesmo que cada objeto na lista tenha
um tipo declarado Shape , é o tipo de runtime (a versão de substituição do método em
cada classe derivada) que será invocado.

C#
// Polymorphism at work #1: a Rectangle, Triangle and Circle
// can all be used wherever a Shape is expected. No cast is
// required because an implicit conversion exists from a derived
// class to its base class.
var shapes = new List<Shape>
{
new Rectangle(),
new Triangle(),
new Circle()
};

// Polymorphism at work #2: the virtual method Draw is


// invoked on each of the derived classes, not the base class.
foreach (var shape in shapes)
{
shape.Draw();
}
/* Output:
Drawing a rectangle
Performing base class drawing tasks
Drawing a triangle
Performing base class drawing tasks
Drawing a circle
Performing base class drawing tasks
*/

Em C#, cada tipo é polimórfico porque todos os tipos, incluindo tipos definidos pelo
usuário, herdam de Object.

Visão geral sobre o polimorfismo

Membros virtuais
Quando uma classe derivada herda de uma classe base, ela inclui todos os membros da
classe base. Todo o comportamento declarado na classe base faz parte da classe
derivada. Isso permite que objetos da classe derivada sejam tratados como objetos da
classe base. Os modificadores de acesso ( public , protected private e assim por diante)
determinam se esses membros estão acessíveis a partir da implementação de classe
derivada. Os métodos virtuais dão ao designer opções diferentes para o
comportamento da classe derivada:

A classe derivada pode substituir membros virtuais na classe base, definindo o


novo comportamento.
A classe derivada pode herdar o método de classe base mais próximo sem
substituí-lo, preservando o comportamento existente, mas permitindo que outras
classes derivadas substituam o método.
A classe derivada pode definir uma nova implementação não virtual desses
membros que ocultam as implementações de classe base.

Uma classe derivada poderá substituir um membro de classe base somente se o


membro da classe base tiver sido declarado como virtual ou abstrato. O membro
derivado deve usar a palavra-chave override para indicar explicitamente que o método
destina-se a participar da invocação virtual. O código a seguir mostra um exemplo:

C#

public class BaseClass


{
public virtual void DoWork() { }
public virtual int WorkProperty
{
get { return 0; }
}
}
public class DerivedClass : BaseClass
{
public override void DoWork() { }
public override int WorkProperty
{
get { return 0; }
}
}

Os campos não podem ser virtuais, apenas os métodos, as propriedades, os eventos e


os indexadores podem ser virtuais. Quando uma classe derivada substitui um membro
virtual, esse membro é chamado, mesmo quando uma instância dessa classe está sendo
acessada como uma instância da classe base. O código a seguir mostra um exemplo:

C#

DerivedClass B = new DerivedClass();


B.DoWork(); // Calls the new method.

BaseClass A = B;
A.DoWork(); // Also calls the new method.

Os métodos e propriedades virtuais permitem que classes derivadas estendam uma


classe base sem a necessidade de usar a implementação da classe base de um método.
Para obter mais informações, consulte Controle de versão com as palavras-chave
override e new. Uma interface fornece uma outra maneira de definir um método ou
conjunto de métodos cuja implementação é deixada para classes derivadas.
Ocultar membros da classe base com novos membros
Se quiser que sua classe derivada tenha um membro com o mesmo nome que um
membro em uma classe base, você poderá usar a palavra-chave new para ocultar o
membro da classe base. A palavra-chave new é colocada antes do tipo de retorno de
um membro de classe que está sendo substituído. O código a seguir mostra um
exemplo:

C#

public class BaseClass


{
public void DoWork() { WorkField++; }
public int WorkField;
public int WorkProperty
{
get { return 0; }
}
}

public class DerivedClass : BaseClass


{
public new void DoWork() { WorkField++; }
public new int WorkField;
public new int WorkProperty
{
get { return 0; }
}
}

Os membros da classe base oculta podem ser acessados do código do cliente, por meio
da seleção da instância da classe derivada em uma instância da classe base. Por
exemplo:

C#

DerivedClass B = new DerivedClass();


B.DoWork(); // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork(); // Calls the old method.

Impedir que classes derivadas substituam membros


virtuais
Os membros virtuais permanecem virtuais, independentemente de quantas classes
foram declaradas entre o membro virtual e a classe que o declarou originalmente. Se a
classe A declara um membro virtual, a classe B deriva de A e a classe C deriva de B , a
classe C herda o membro virtual e tem a opção de substituí-lo, independentemente de
a classe B ter declarado uma substituição para esse membro. O código a seguir mostra
um exemplo:

C#

public class A
{
public virtual void DoWork() { }
}
public class B : A
{
public override void DoWork() { }
}

Uma classe derivada pode interromper a herança virtual, declarando uma substituição
como sealed. Isso exige a colocação da palavra-chave sealed antes da palavra-chave
override na declaração de membro de classe. O código a seguir mostra um exemplo:

C#

public class C : B
{
public sealed override void DoWork() { }
}

No exemplo anterior, o método DoWork não é mais virtual para nenhuma classe derivada
de C . Ele ainda é virtual para as instâncias de C , mesmo se elas foram convertidas para
o tipo B ou tipo A . Os métodos lacrados podem ser substituídos por classes derivadas
usando a palavra-chave new , como mostra o exemplo a seguir:

C#

public class D : C
{
public new void DoWork() { }
}

Neste caso, se DoWork é chamado em D usando uma variável do tipo D , o novo DoWork
é chamado. Se uma variável do tipo C , B ou A é usada para acessar uma instância de D ,
uma chamada de DoWork seguirá as regras de herança virtual, encaminhando as
chamadas para a implementação de DoWork na classe C .

Acessar membros virtuais da classe base de classes


derivadas
A classe derivada que substituiu um método ou propriedade ainda pode acessar o
método ou propriedade na classe base usando a palavra-chave base . O código a seguir
mostra um exemplo:

C#

public class Base


{
public virtual void DoWork() {/*...*/ }
}
public class Derived : Base
{
public override void DoWork()
{
//Perform Derived's work here
//...
// Call DoWork on base class
base.DoWork();
}
}

Para obter mais informações, consulte base.

7 Observação

Recomendamos que os membros virtuais usem base para chamar a


implementação da classe base do membro em sua própria implementação. Deixar
o comportamento da classe base ocorrer permite que a classe derivada se
concentre na implementação de comportamento específico para a classe derivada.
Se a implementação da classe base não é chamado, cabe à classe derivada tornar
seu comportamento compatível com o comportamento da classe base.
Visão geral dos padrões
correspondentes
Artigo • 13/03/2024

A correspondência de padrões é uma técnica em que você testa uma expressão para
determinar se ela tem determinadas características. A correspondência de padrões C#
fornece uma sintaxe mais concisa para testar expressões e tomar medidas quando uma
expressão corresponde. A "expressão is" dá suporte à correspondência de padrões para
testar uma expressão e declarar condicionalmente uma nova variável para o resultado
dessa expressão. A "expressão switch" permite que você execute ações com base no
padrão de primeira correspondência de uma expressão. Essas duas expressões dão
suporte a um vocabulário avançado de padrões.

Este artigo fornece uma visão geral dos cenários em que você pode usar a
correspondência de padrões. Essas técnicas podem melhorar a legibilidade e a correção
do código. Para obter uma discussão completa sobre todos os padrões que você pode
aplicar, consulte o artigo sobre padrões na referência de linguagem.

Verificações nulas
Um dos cenários mais comuns para correspondência de padrões é garantir que os
valores não sejam null . Você pode testar e converter um tipo de valor anulável em seu
tipo subjacente durante o teste de null usando o seguinte exemplo:

C#

int? maybe = 12;

if (maybe is int number)


{
Console.WriteLine($"The nullable int 'maybe' has the value {number}");
}
else
{
Console.WriteLine("The nullable int 'maybe' doesn't hold a value");
}

O código anterior é um padrão de declaração para testar o tipo da variável e atribuí-lo a


uma nova variável. As regras de idioma tornam essa técnica mais segura do que muitas
outras. A variável number só é acessível e atribuída na parte verdadeira da cláusula if .
Se você tentar acessá-la em outro lugar, na cláusula else ou após o bloco if , o
compilador emitirá um erro. Em segundo lugar, como você não está usando o operador
== , esse padrão funciona quando um tipo sobrecarrega o operador == . Isso o torna

uma maneira ideal de verificar os valores de referência nulos, adicionando o padrão


not :

C#

string? message = ReadMessageOrDefault();

if (message is not null)


{
Console.WriteLine(message);
}

O exemplo anterior usou um padrão constante para comparar a variável com null . not
é um padrão lógico que corresponde quando o padrão negado não corresponde.

Testes de tipo
Outro uso comum para correspondência de padrões é testar uma variável para ver se
ela corresponde a um determinado tipo. Por exemplo, o código a seguir testa se uma
variável não é nula e implementa a interface System.Collections.Generic.IList<T>. Se isso
acontecer, ele usará a propriedade ICollection<T>.Count nessa lista para localizar o
índice do meio. O padrão de declaração não corresponde a um valor null ,
independentemente do tipo de tempo de compilação da variável. O código abaixo
protege contra null e um tipo que não implementa IList .

C#

public static T MidPoint<T>(IEnumerable<T> sequence)


{
if (sequence is IList<T> list)
{
return list[list.Count / 2];
}
else if (sequence is null)
{
throw new ArgumentNullException(nameof(sequence), "Sequence can't be
null.");
}
else
{
int halfLength = sequence.Count() / 2 - 1;
if (halfLength < 0) halfLength = 0;
return sequence.Skip(halfLength).First();
}
}

Os mesmos testes podem ser aplicados em uma expressão switch para testar uma
variável em vários tipos diferentes. Você pode usar essas informações para criar
algoritmos melhores com base no tipo em tempo de execução específico.

Comparar valores discretos


Você também pode testar uma variável para encontrar uma correspondência em valores
específicos. O código a seguir mostra um exemplo em que você testa um valor em
relação a todos os valores possíveis declarados em uma enumeração:

C#

public State PerformOperation(Operation command) =>


command switch
{
Operation.SystemTest => RunDiagnostics(),
Operation.Start => StartSystem(),
Operation.Stop => StopSystem(),
Operation.Reset => ResetToReady(),
_ => throw new ArgumentException("Invalid enum value for command",
nameof(command)),
};

O exemplo anterior demonstra uma expedição de método com base no valor de uma
enumeração. O caso _ final é um padrão de descarte que corresponde a todos os
valores. Ele identifica quaisquer condições de erro em que o valor não corresponda a
um dos valores enum definidos. Se você omitir esse braço de comutador, o compilador
avisará que sua expressão padrão não manipula todos os valores de entrada possíveis.
Em tempo de execução, a expressão switch lançará uma exceção se o objeto que está
sendo examinado não corresponder a nenhum braço switch. Você pode usar constantes
numéricas em vez de um conjunto de valores de enumeração. Você também pode usar
essa técnica semelhante para valores de cadeia de caracteres constantes que
representam os comandos:

C#

public State PerformOperation(string command) =>


command switch
{
"SystemTest" => RunDiagnostics(),
"Start" => StartSystem(),
"Stop" => StopSystem(),
"Reset" => ResetToReady(),
_ => throw new ArgumentException("Invalid string value for command",
nameof(command)),
};

O exemplo anterior mostra o mesmo algoritmo, mas usa valores de cadeia de caracteres
em vez de uma enumeração. Você usaria esse cenário se seu aplicativo respondesse a
comandos de texto em vez de um formato de dados regular. A partir do C# 11, você
também pode usar um Span<char> ou ReadOnlySpan<char> para testar valores de cadeia
de caracteres constantes, conforme mostrado no exemplo a seguir:

C#

public State PerformOperation(ReadOnlySpan<char> command) =>


command switch
{
"SystemTest" => RunDiagnostics(),
"Start" => StartSystem(),
"Stop" => StopSystem(),
"Reset" => ResetToReady(),
_ => throw new ArgumentException("Invalid string value for command",
nameof(command)),
};

Em todos esses exemplos, o padrão de descarte garante que você identifique todas as
entradas. O compilador ajuda você a garantir que todos os valores de entrada possíveis
sejam identificados.

Padrões relacionais
Os padrões relacionais podem ser usados para testar como um valor se compara às
constantes. Por exemplo, o código a seguir retorna o estado da água com base na
temperatura em Fahrenheit:

C#

string WaterState(int tempInFahrenheit) =>


tempInFahrenheit switch
{
(> 32) and (< 212) => "liquid",
< 32 => "solid",
> 212 => "gas",
32 => "solid/liquid transition",
212 => "liquid / gas transition",
};
O código anterior também demonstra o and padrão lógico conjuntivo para verificar se
ambos os padrões relacionais correspondem. Você também pode usar um padrão
disjuntivo or para verificar se um dos padrões corresponde. Os dois padrões relacionais
ficam entre parênteses, que podem ser usados em qualquer padrão para maior clareza.
Os dois últimos braços switch identificam os casos para o ponto de fusão e o ponto de
ebulição. Sem esses dois braços, o compilador avisa que sua lógica não cobre todas as
entradas possíveis.

O código anterior também demonstra outro recurso importante que o compilador


fornece para expressões de correspondência de padrões: o compilador avisa se você
não identifica todos os valores de entrada. O compilador também emitirá um aviso se o
padrão de um braço de comutador for coberto por um padrão anterior. Isso lhe dá
liberdade para refatorar e reordenar as expressões de alternância. Outra maneira de
gravar a mesma expressão pode ser:

C#

string WaterState2(int tempInFahrenheit) =>


tempInFahrenheit switch
{
< 32 => "solid",
32 => "solid/liquid transition",
< 212 => "liquid",
212 => "liquid / gas transition",
_ => "gas",
};

A lição principal no exemplo anterior e qualquer outra refatoração ou reordenação é


que o compilador valida que seu código manipula todas as entradas possíveis.

Várias entradas
Todos os padrões vistos até agora foram para verificar uma entrada. Você pode gravar
padrões que examinam várias propriedades de um objeto. Considere o registro Order a
seguir:

C#

public record Order(int Items, decimal Cost);

O tipo de registro posicional anterior declara dois membros em posições explícitas.


Primeiro, Items aparece e, em seguida, o Cost da ordem. Para saber mais, confira
Registros.
O código a seguir examina o número de itens e o valor de uma ordem para calcular um
preço com desconto:

C#

public decimal CalculateDiscount(Order order) =>


order switch
{
{ Items: > 10, Cost: > 1000.00m } => 0.10m,
{ Items: > 5, Cost: > 500.00m } => 0.05m,
{ Cost: > 250.00m } => 0.02m,
null => throw new ArgumentNullException(nameof(order), "Can't
calculate discount on null order"),
var someObject => 0m,
};

Os dois primeiros braços examinam duas propriedades do Order . O terceiro examina


apenas o custo. O próximo verifica null e o final corresponde a qualquer outro valor. Se
o tipo Order definir um método Deconstruct adequado, você poderá omitir os nomes
de propriedade do padrão e usar a desconstrução para examinar as propriedades:

C#

public decimal CalculateDiscount(Order order) =>


order switch
{
( > 10, > 1000.00m) => 0.10m,
( > 5, > 50.00m) => 0.05m,
{ Cost: > 250.00m } => 0.02m,
null => throw new ArgumentNullException(nameof(order), "Can't
calculate discount on null order"),
var someObject => 0m,
};

O código anterior demonstra o padrão posicional em que as propriedades são


desconstruídas para a expressão.

Padrões de lista
Você pode verificar os elementos em uma lista ou uma matriz usando um padrão de
lista. Um padrão de lista fornece um meio para aplicar um padrão a qualquer elemento
de uma sequência. Além disso, você pode aplicar o padrão de descarte ( _ ) para
corresponder a qualquer elemento ou aplicar um padrão de fatia para corresponder a
zero ou mais elementos.
Os padrões de lista são uma ferramenta valiosa quando os dados não seguem uma
estrutura regular. Você pode usar a correspondência de padrões para testar a forma e os
valores dos dados em vez de transformá-los em um conjunto de objetos.

Considere o seguinte trecho de um arquivo de texto que contém transações bancárias:

Saída

04-01-2020, DEPOSIT, Initial deposit, 2250.00


04-15-2020, DEPOSIT, Refund, 125.65
04-18-2020, DEPOSIT, Paycheck, 825.65
04-22-2020, WITHDRAWAL, Debit, Groceries, 255.73
05-01-2020, WITHDRAWAL, #1102, Rent, apt, 2100.00
05-02-2020, INTEREST, 0.65
05-07-2020, WITHDRAWAL, Debit, Movies, 12.57
04-15-2020, FEE, 5.55

É um formato CSV, mas algumas das linhas têm mais colunas do que outras. Pior ainda
para o processamento, uma coluna do tipo WITHDRAWAL tem texto gerado pelo usuário e
pode conter uma vírgula no texto. Um padrão de lista que inclui o padrão descarte, o
padrão constante e o padrão var para capturar os dados de processos de valor neste
formato:

C#

decimal balance = 0m;


foreach (string[] transaction in ReadRecords())
{
balance += transaction switch
{
[_, "DEPOSIT", _, var amount] => decimal.Parse(amount),
[_, "WITHDRAWAL", .., var amount] => -decimal.Parse(amount),
[_, "INTEREST", var amount] => decimal.Parse(amount),
[_, "FEE", var fee] => -decimal.Parse(fee),
_ => throw new
InvalidOperationException($"Record {string.Join(", ", transaction)} is not
in the expected format!"),
};
Console.WriteLine($"Record: {string.Join(", ", transaction)}, New
balance: {balance:C}");
}

O exemplo anterior usa uma matriz de cadeia de caracteres, em que cada elemento é
um campo na linha. As teclas de expressão switch no segundo campo, que determina o
tipo de transação, e o número de colunas restantes. Cada linha garante que os dados
estão no formato correto. O padrão de descarte ( _ ) ignora o primeiro campo, com a
data da transação. O segundo campo corresponde ao tipo de transação. As
correspondências de elemento restantes pulam para o campo com a quantidade. A
partida final usa o padrão var para capturar a representação de cadeia de caracteres do
valor. A expressão calcula o valor a ser adicionado ou subtraído do saldo.

Os padrões de lista permitem que você corresponda na forma de uma sequência de


elementos de dados. Você usa os padrões de descarte e fatia para corresponder ao local
dos elementos. Você usa outros padrões para corresponder às características sobre
elementos individuais.

Este artigo fez um tour pelos tipos de código que você pode gravar com
correspondência de padrões em C#. Os artigos a seguir mostram mais exemplos de uso
dos padrões em cenários e o vocabulário completo dos padrões disponíveis para uso.

Confira também
Usar padrões correspondentes para evitar a verificação 'is' seguida por uma
conversão (regras de estilo IDE0020 e IDE0038)
Exploração: usar a correspondência de padrões para criar seu comportamento de
classe para obter um código melhor
Tutorial: Usar padrões correspondentes para criar algoritmos controlados por tipo
e controlados por dados
Referência: Correspondência de padrões

6 Colaborar conosco no Comentários do .NET


GitHub O .NET é um projeto código aberto.
A fonte deste conteúdo pode Selecione um link para fornecer
ser encontrada no GitHub, onde comentários:
você também pode criar e
revisar problemas e solicitações  Abrir um problema de
de pull. Para obter mais documentação
informações, confira o nosso
guia para colaboradores.  Fornecer comentários sobre o
produto
Descartes – conceitos básicos do C#
Artigo • 14/11/2023

Descartes são variáveis de espaço reservado intencionalmente não utilizadas no código


do aplicativo. Descartes são equivalentes a variáveis não atribuídas; eles não têm um
valor. Um descarte comunica uma intenção para o compilador e outras pessoas que
leem seu código: você pretendia ignorar o resultado de uma expressão. Talvez você
queira ignorar o resultado de uma expressão, um ou mais membros de uma expressão
de tupla, um parâmetro out para um método ou o destino de uma expressão de
correspondência de padrões.

Os descartes tornam a intenção do seu código clara. Um descarte indica que nosso
código nunca usa a variável. Eles aprimoram sua legibilidade e manutenção.

Você indica que uma variável é um descarte atribuindo a ela o sublinhado ( _ ) como seu
nome. Por exemplo, a chamada de método a seguir retorna uma tupla na qual o
primeiro e o segundo valores são descartes. area é uma variável declarada
anteriormente definida como o terceiro componente retornado por GetCityInformation :

C#

(_, _, area) = city.GetCityInformation(cityName);

Você pode usar discards para especificar parâmetros de entrada não utilizados de uma
expressão lambda. Para obter mais informações, consulte a seção Parâmetros de
entrada de uma expressão lambda do artigo Expressões lambda.

Quando _ é um descarte válido, tentar recuperar seu valor ou usá-lo em uma operação
de atribuição gerará o erro do compilador CS0103, "O nome '_' não existe no contexto
atual". Esse erro ocorre porque não há um valor atribuído a _ , e pode não haver nem
mesmo um local de armazenamento atribuído a ela. Se ela fosse uma variável real, você
não poderia descartar mais de um valor, tal como ocorreu no exemplo anterior.

Desconstrução de objeto e de tupla


Descartes são úteis para trabalhar com tuplas quando seu código de aplicativo usa
alguns elementos da tupla, mas ignora outros. Por exemplo, o método
QueryCityDataForYears a seguir retorna uma tupla de com o nome de uma cidade, sua
área, um ano, a população da cidade nesse ano, um segundo ano e população da
cidade nesse segundo ano. O exemplo mostra a alteração na população entre esses dois
anos. Entre os dados disponíveis da tupla, não estamos preocupados com a área da
cidade e sabemos o nome da cidade e as duas datas em tempo de design. Como
resultado, estamos interessados apenas nos dois valores de população armazenados na
tupla e podemos lidar com seus valores restantes como descartes.

C#

var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960,


2010);

Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");

static (string, double, int, int, int, int) QueryCityDataForYears(string


name, int year1, int year2)
{
int population1 = 0, population2 = 0;
double area = 0;

if (name == "New York City")


{
area = 468.48;
if (year1 == 1960)
{
population1 = 7781984;
}
if (year2 == 2010)
{
population2 = 8175133;
}
return (name, area, year1, population1, year2, population2);
}

return ("", 0, 0, 0, 0, 0);


}
// The example displays the following output:
// Population change, 1960 to 2010: 393,149

Para obter mais informações sobre desconstruir tuplas com descartes, consulte
Desconstruindo tuplas e outros tipos.

O método Deconstruct de uma classe, estrutura ou interface também permite que você
recupere e decomponha um conjunto específico de dados de um objeto. Você poderá
usar descartes quando estiver interessado em trabalhar com apenas um subconjunto
dos valores desconstruídos. O exemplo a seguir desconstrói um objeto Person em
quatro cadeias de caracteres (os nomes e sobrenomes, a cidade e o estado), mas
descarta o sobrenome e o estado.

C#
using System;

namespace Discards
{
public class Person
{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
public string State { get; set; }

public Person(string fname, string mname, string lname,


string cityName, string stateName)
{
FirstName = fname;
MiddleName = mname;
LastName = lname;
City = cityName;
State = stateName;
}

// Return the first and last name.


public void Deconstruct(out string fname, out string lname)
{
fname = FirstName;
lname = LastName;
}

public void Deconstruct(out string fname, out string mname, out


string lname)
{
fname = FirstName;
mname = MiddleName;
lname = LastName;
}

public void Deconstruct(out string fname, out string lname,


out string city, out string state)
{
fname = FirstName;
lname = LastName;
city = City;
state = State;
}
}
class Example
{
public static void Main()
{
var p = new Person("John", "Quincy", "Adams", "Boston", "MA");

// Deconstruct the person object.


var (fName, _, city, _) = p;
Console.WriteLine($"Hello {fName} of {city}!");
// The example displays the following output:
// Hello John of Boston!
}
}
}

Para obter mais informações sobre desconstruir tipos definidos pelo usuário com
descartes, consulte Desconstruindo tuplas e outros tipos.

Correspondência de padrões com switch


O padrão de descarte pode ser usado na correspondência de padrões com a expressão
switch. Toda expressão, incluindo null , sempre corresponde ao padrão de descarte.

O exemplo a seguir define um método ProvidesFormatInfo que usa uma expressão


switch para determinar se um objeto fornece uma implementação de IFormatProvider e

testa se o objeto é null . Ele também usa o padrão de descarte para manipular objetos
não nulos de qualquer outro tipo.

C#

object?[] objects = [CultureInfo.CurrentCulture,


CultureInfo.CurrentCulture.DateTimeFormat,
CultureInfo.CurrentCulture.NumberFormat,
new ArgumentException(), null];
foreach (var obj in objects)
ProvidesFormatInfo(obj);

static void ProvidesFormatInfo(object? obj) =>


Console.WriteLine(obj switch
{
IFormatProvider fmt => $"{fmt.GetType()} object",
null => "A null object reference: Its use could result in a
NullReferenceException",
_ => "Some object type without format information"
});
// The example displays the following output:
// System.Globalization.CultureInfo object
// System.Globalization.DateTimeFormatInfo object
// System.Globalization.NumberFormatInfo object
// Some object type without format information
// A null object reference: Its use could result in a
NullReferenceException

Chamadas para métodos com parâmetros out


Ao chamar o método Deconstruct para desconstruir um tipo definido pelo usuário (uma
instância de uma classe, estrutura ou interface), você pode descartar os valores de
argumentos out individuais. Mas você também pode descartar o valor de argumentos
out ao chamar qualquer método com um parâmetro out .

A exemplo a seguir chama o método DateTime.TryParse(String, out DateTime) para


determinar se a representação de cadeia de caracteres de uma data é válida na cultura
atual. Já que o exemplo está preocupado apenas em validar a cadeia de caracteres de
data e não em analisá-lo para extrair a data, o argumento out para o método é um
descarte.

C#

string[] dateStrings = ["05/01/2018 14:57:32.8", "2018-05-01 14:57:32.8",


"2018-05-01T14:57:32.8375298-04:00", "5/01/2018",
"5/01/2018 14:57:32.80 -07:00",
"1 May 2018 2:57:32.8 PM", "16-05-2018 1:00:32 PM",
"Fri, 15 May 2018 20:10:57 GMT"];
foreach (string dateString in dateStrings)
{
if (DateTime.TryParse(dateString, out _))
Console.WriteLine($"'{dateString}': valid");
else
Console.WriteLine($"'{dateString}': invalid");
}
// The example displays output like the following:
// '05/01/2018 14:57:32.8': valid
// '2018-05-01 14:57:32.8': valid
// '2018-05-01T14:57:32.8375298-04:00': valid
// '5/01/2018': valid
// '5/01/2018 14:57:32.80 -07:00': valid
// '1 May 2018 2:57:32.8 PM': valid
// '16-05-2018 1:00:32 PM': invalid
// 'Fri, 15 May 2018 20:10:57 GMT': invalid

Um descarte autônomo
Você pode usar um descarte autônomo para indicar qualquer variável que você opte
por ignorar. Um uso típico é usar uma atribuição para garantir que um argumento não
seja nulo. O código a seguir usa um descarte para forçar uma atribuição. O lado direito
da atribuição usa o operador de avaliação de nulo para lançar um
System.ArgumentNullException quando o argumento é null . O código não precisa do
resultado da atribuição; portanto, ele é descartado. A expressão força uma verificação
de nulo. O descarte esclarece sua intenção: o resultado da atribuição não é necessário
ou usado.
C#

public static void Method(string arg)


{
_ = arg ?? throw new ArgumentNullException(paramName: nameof(arg),
message: "arg can't be null");

// Do work with arg.


}

O exemplo a seguir usa um descarte autônomo para ignorar o objeto Task retornado
por uma operação assíncrona. A atribuição da tarefa tem o efeito de suprimir a exceção
que a operação gera quando está prestes a ser concluída. Isso deixa clara sua intenção:
você deseja descartar Task e ignorar todos os erros gerados a partir dessa operação
assíncrona.

C#

private static async Task ExecuteAsyncMethods()


{
Console.WriteLine("About to launch a task...");
_ = Task.Run(() =>
{
var iterations = 0;
for (int ctr = 0; ctr < int.MaxValue; ctr++)
iterations++;
Console.WriteLine("Completed looping operation...");
throw new InvalidOperationException();
});
await Task.Delay(5000);
Console.WriteLine("Exiting after 5 second delay");
}
// The example displays output like the following:
// About to launch a task...
// Completed looping operation...
// Exiting after 5 second delay

Sem atribuir a tarefa a um descarte, o código a seguir gera um aviso do compilador:

C#

private static async Task ExecuteAsyncMethods()


{
Console.WriteLine("About to launch a task...");
// CS4014: Because this call is not awaited, execution of the current
method continues before the call is completed.
// Consider applying the 'await' operator to the result of the call.
Task.Run(() =>
{
var iterations = 0;
for (int ctr = 0; ctr < int.MaxValue; ctr++)
iterations++;
Console.WriteLine("Completed looping operation...");
throw new InvalidOperationException();
});
await Task.Delay(5000);
Console.WriteLine("Exiting after 5 second delay");

7 Observação

Se você executar um dos dois exemplos anteriores usando um depurador, o


depurador interromperá o programa quando a exceção for lançada. Sem um
depurador anexado, a exceção é silenciosamente ignorada em ambos os casos.

_ também é um identificador válido. Quando usado fora de um contexto com suporte,

_ não é tratado como um descarte, mas como uma variável válida. Se um identificador
chamado _ já está no escopo, o uso de _ como um descarte autônomo pode resultar
em:

A modificação acidental do valor da variável _ no escopo atribuindo a ela o valor


do descarte pretendido. Por exemplo:

C#

private static void ShowValue(int _)


{
byte[] arr = [0, 0, 1, 2];
_ = BitConverter.ToInt32(arr, 0);
Console.WriteLine(_);
}
// The example displays the following output:
// 33619968

Um erro do compilador por violação de segurança de tipo. Por exemplo:

C#

private static bool RoundTrips(int _)


{
string value = _.ToString();
int newValue = 0;
_ = Int32.TryParse(value, out newValue);
return _ == newValue;
}
// The example displays the following compiler error:
// error CS0029: Cannot implicitly convert type 'bool' to 'int'
Erro do compilador CS0136, "Um local ou um parâmetro denominado '_' não pode
ser declarado neste escopo porque esse nome é usado em um escopo delimitador
de local para definir um local ou parâmetro." Por exemplo:

C#

public void DoSomething(int _)


{
var _ = GetValue(); // Error: cannot declare local _ when one is
already in scope
}
// The example displays the following compiler error:
// error CS0136:
// A local or parameter named '_' cannot be declared in this
scope
// because that name is used in an enclosing local scope
// to define a local or parameter

Confira também
Remover valor de expressão desnecessária (regra de estilo IDE0058)
Remover atribuição de valor desnecessária (regra de estilo IDE0059)
Remover parâmetro não usado (regra de estilo IDE0060)
Desconstruindo tuplas e outros tipos
Operador is
Expressão switch

6 Colaborar conosco no Comentários do .NET


GitHub O .NET é um projeto código aberto.
A fonte deste conteúdo pode Selecione um link para fornecer
ser encontrada no GitHub, onde comentários:
você também pode criar e
revisar problemas e solicitações  Abrir um problema de
de pull. Para obter mais documentação
informações, confira o nosso
guia para colaboradores.  Fornecer comentários sobre o
produto
Desconstruindo tuplas e outros tipos
Artigo • 07/04/2023

Uma tupla fornece uma maneira leve de recuperar vários valores de uma chamada de
método. Mas depois de recuperar a tupla, você precisa lidar com seus elementos
individuais. Trabalhar elemento por elemento é incômodo, conforme mostra o exemplo
a seguir. O método QueryCityData retorna uma tupla de três e cada um de seus
elementos é atribuído a uma variável em uma operação separada.

C#

public class Example


{
public static void Main()
{
var result = QueryCityData("New York City");

var city = result.Item1;


var pop = result.Item2;
var size = result.Item3;

// Do something with the data.


}

private static (string, int, double) QueryCityData(string name)


{
if (name == "New York City")
return (name, 8175133, 468.48);

return ("", 0, 0);


}
}

Recuperar vários valores de propriedade e de campo de um objeto pode ser igualmente


complicado: é preciso atribuir um valor de campo ou de propriedade a uma variável,
membro por membro.

Você pode recuperar vários elementos de uma tupla ou recuperar vários valores de
campo, propriedade e computação de um objeto em uma única operação de
desconstrução . Para desconstruir uma tupla, você atribui os elementos dela a variáveis
individuais. Quando você desconstrói um objeto, você atribui os elementos dela a
variáveis individuais.

Tuplas
O C# conta com suporte interno à desconstrução de tuplas, que permite que você
descompacte todos os itens em uma tupla em uma única operação. A sintaxe geral para
desconstruir uma tupla é semelhante à sintaxe para definir uma: coloque as variáveis
para as quais cada elemento deve ser atribuído entre parênteses no lado esquerdo de
uma instrução de atribuição. Por exemplo, a instrução a seguir atribui os elementos de
uma tupla de quatro a quatro variáveis separadas:

C#

var (name, address, city, zip) = contact.GetAddressInfo();

Há três maneiras de desconstruir uma tupla:

Você pode declarar explicitamente o tipo de cada campo dentro de parênteses. O


exemplo a seguir usa essa abordagem para desconstruir a tupla de três retornada
pelo método QueryCityData .

C#

public static void Main()


{
(string city, int population, double area) = QueryCityData("New
York City");

// Do something with the data.


}

Você pode usar a palavra-chave var de modo que o C# infira o tipo de cada
variável. Você coloca a palavra-chave var fora dos parênteses. O exemplo a seguir
usa a inferência de tipos ao desconstruir a tupla de três retornada pelo método
QueryCityData .

C#

public static void Main()


{
var (city, population, area) = QueryCityData("New York City");

// Do something with the data.


}

Você também pode usar a palavra-chave var individualmente com qualquer uma
ou todas as declarações de variável dentro dos parênteses.

C#
public static void Main()
{
(string city, var population, var area) = QueryCityData("New York
City");

// Do something with the data.


}

Isso é difícil e não é recomendado.

Por fim, você pode desconstruir a tupla em variáveis que já foram declaradas.

C#

public static void Main()


{
string city = "Raleigh";
int population = 458880;
double area = 144.8;

(city, population, area) = QueryCityData("New York City");

// Do something with the data.


}

A partir do C# 10, você pode misturar declaração de variável e atribuição em uma


desconstrução.

C#

public static void Main()


{
string city = "Raleigh";
int population = 458880;

(city, population, double area) = QueryCityData("New York City");

// Do something with the data.


}

Você não pode especificar um tipo específico fora dos parênteses, mesmo se todos os
campos na tupla tiverem o mesmo tipo. Isso gera o erro do compilador CS8136, "O
formulário de desconstrução 'var (...)' não permite um tipo específico para 'var'.".

Você deve atribuir cada elemento da tupla a uma variável. Se você omitir qualquer
elemento, o compilador gerará o erro CS8132, "Não é possível desconstruir uma tupla
de 'x' elementos em 'y' variáveis".
Elementos tupla com descartes
Geralmente, ao desconstruir uma tupla, você está interessado nos valores de apenas
alguns elementos. Você pode aproveitar o suporte do C#para descartes, que são
variáveis somente gravação cujos valores você escolheu ignorar. Um descarte é
escolhido por um caractere de sublinhado ("_") em uma atribuição. Você pode descartar
tantos valores quantos desejar; todos são representados pelo descarte único, _ .

O exemplo a seguir ilustra o uso de tuplas com descartes. O método


QueryCityDataForYears a seguir retorna uma tupla de seis com o nome de uma cidade,

sua área, um ano, a população da cidade nesse ano, um segundo ano e população da
cidade nesse segundo ano. O exemplo mostra a alteração na população entre esses dois
anos. Entre os dados disponíveis da tupla, não estamos preocupados com a área da
cidade e sabemos o nome da cidade e as duas datas em tempo de design. Como
resultado, estamos interessados apenas nos dois valores de população armazenados na
tupla e podemos lidar com seus valores restantes como descartes.

C#

using System;

public class ExampleDiscard


{
public static void Main()
{
var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York
City", 1960, 2010);

Console.WriteLine($"Population change, 1960 to 2010: {pop2 -


pop1:N0}");
}

private static (string, double, int, int, int, int)


QueryCityDataForYears(string name, int year1, int year2)
{
int population1 = 0, population2 = 0;
double area = 0;

if (name == "New York City")


{
area = 468.48;
if (year1 == 1960)
{
population1 = 7781984;
}
if (year2 == 2010)
{
population2 = 8175133;
}
return (name, area, year1, population1, year2, population2);
}

return ("", 0, 0, 0, 0, 0);


}
}
// The example displays the following output:
// Population change, 1960 to 2010: 393,149

Tipos definidos pelo usuário


O C# não oferece suporte interno para desconstruir tipos não tupla diferentes dos
record tipos e DictionaryEntry . No entanto, como o autor de uma classe, um struct ou
uma interface, você pode permitir instâncias do tipo a ser desconstruído
implementando um ou mais métodos Deconstruct . O método retorna void e cada valor
a ser desconstruído é indicado por um parâmetro out na assinatura do método. Por
exemplo, o método Deconstruct a seguir de uma classe Person retorna o nome, o
segundo nome e o sobrenome:

C#

public void Deconstruct(out string fname, out string mname, out string
lname)

Em seguida, você pode desconstruir uma instância da classe Person denominada p com
uma atribuição semelhante à seguinte:

C#

var (fName, mName, lName) = p;

O exemplo a seguir sobrecarrega o método Deconstruct para retornar várias


combinações de propriedades de um objeto Person . As sobrecargas individuais
retornam:

Um nome e um sobrenome.
Um nome, nome do meio e sobrenome.
Um nome, um sobrenome, um nome de cidade e um nome de estado.

C#

using System;

public class Person


{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
public string State { get; set; }

public Person(string fname, string mname, string lname,


string cityName, string stateName)
{
FirstName = fname;
MiddleName = mname;
LastName = lname;
City = cityName;
State = stateName;
}

// Return the first and last name.


public void Deconstruct(out string fname, out string lname)
{
fname = FirstName;
lname = LastName;
}

public void Deconstruct(out string fname, out string mname, out string
lname)
{
fname = FirstName;
mname = MiddleName;
lname = LastName;
}

public void Deconstruct(out string fname, out string lname,


out string city, out string state)
{
fname = FirstName;
lname = LastName;
city = City;
state = State;
}
}

public class ExampleClassDeconstruction


{
public static void Main()
{
var p = new Person("John", "Quincy", "Adams", "Boston", "MA");

// Deconstruct the person object.


var (fName, lName, city, state) = p;
Console.WriteLine($"Hello {fName} {lName} of {city}, {state}!");
}
}
// The example displays the following output:
// Hello John Adams of Boston, MA!
Vários métodos Deconstruct com o mesmo número de parâmetros são ambíguos. Você
deve ter cuidado ao definir métodos Deconstruct com diferentes números de
parâmetros ou "aridade". Métodos Deconstruct com o mesmo número de parâmetros
não podem ser distinguidos durante a resolução de sobrecarga.

Tipo definido pelo usuário com descartes


Assim como você faria com tuplas, você pode usar descartes para ignorar os itens
selecionados retornados por um método Deconstruct . Cada descarte é definido por
uma variável chamada "_", sendo que uma única operação de desconstrução pode
incluir vários descartes.

O exemplo a seguir desconstrói um objeto Person em quatro cadeias de caracteres (os


nomes e sobrenomes, a cidade e o estado), mas descarta o sobrenome e o estado.

C#

// Deconstruct the person object.


var (fName, _, city, _) = p;
Console.WriteLine($"Hello {fName} of {city}!");
// The example displays the following output:
// Hello John of Boston!

Métodos de extensão para tipos definidos pelo


usuário
Se você não criar uma classe, struct ou interface, você ainda poderá decompor objetos
desse tipo implementando um ou mais Deconstruct métodos de extensão para retornar
os valores nos quais você estiver interessado.

O exemplo a seguir define dois métodos de extensão Deconstruct para a classe


System.Reflection.PropertyInfo. O primeiro retorna um conjunto de valores que indicam
as características da propriedade, incluindo seu tipo, se ela é estática ou instância, se ela
é somente leitura e se é indexada. O segundo indica a acessibilidade da propriedade. Já
que a acessibilidade dos acessadores get e set pode ser diferente, valores boolianos
indicam se a propriedade acessadores get e set separados e, em caso afirmativo, se eles
têm a mesma acessibilidade. Se houver apenas um acessador ou ambos os acessadores
get e set têm a mesma acessibilidade, a variável access indica a acessibilidade da
propriedade como um todo. Caso contrário, a acessibilidade dos acessadores get e set é
indicada pelas variáveis getAccess e setAccess .

C#

using System;
using System.Collections.Generic;
using System.Reflection;

public static class ReflectionExtensions


{
public static void Deconstruct(this PropertyInfo p, out bool isStatic,
out bool isReadOnly, out bool isIndexed,
out Type propertyType)
{
var getter = p.GetMethod;

// Is the property read-only?


isReadOnly = ! p.CanWrite;

// Is the property instance or static?


isStatic = getter.IsStatic;

// Is the property indexed?


isIndexed = p.GetIndexParameters().Length > 0;

// Get the property type.


propertyType = p.PropertyType;
}

public static void Deconstruct(this PropertyInfo p, out bool


hasGetAndSet,
out bool sameAccess, out string access,
out string getAccess, out string
setAccess)
{
hasGetAndSet = sameAccess = false;
string getAccessTemp = null;
string setAccessTemp = null;

MethodInfo getter = null;


if (p.CanRead)
getter = p.GetMethod;

MethodInfo setter = null;


if (p.CanWrite)
setter = p.SetMethod;

if (setter != null && getter != null)


hasGetAndSet = true;

if (getter != null)
{
if (getter.IsPublic)
getAccessTemp = "public";
else if (getter.IsPrivate)
getAccessTemp = "private";
else if (getter.IsAssembly)
getAccessTemp = "internal";
else if (getter.IsFamily)
getAccessTemp = "protected";
else if (getter.IsFamilyOrAssembly)
getAccessTemp = "protected internal";
}

if (setter != null)
{
if (setter.IsPublic)
setAccessTemp = "public";
else if (setter.IsPrivate)
setAccessTemp = "private";
else if (setter.IsAssembly)
setAccessTemp = "internal";
else if (setter.IsFamily)
setAccessTemp = "protected";
else if (setter.IsFamilyOrAssembly)
setAccessTemp = "protected internal";
}

// Are the accessibility of the getter and setter the same?


if (setAccessTemp == getAccessTemp)
{
sameAccess = true;
access = getAccessTemp;
getAccess = setAccess = String.Empty;
}
else
{
access = null;
getAccess = getAccessTemp;
setAccess = setAccessTemp;
}
}
}

public class ExampleExtension


{
public static void Main()
{
Type dateType = typeof(DateTime);
PropertyInfo prop = dateType.GetProperty("Now");
var (isStatic, isRO, isIndexed, propType) = prop;
Console.WriteLine($"\nThe {dateType.FullName}.{prop.Name}
property:");
Console.WriteLine($" PropertyType: {propType.Name}");
Console.WriteLine($" Static: {isStatic}");
Console.WriteLine($" Read-only: {isRO}");
Console.WriteLine($" Indexed: {isIndexed}");
Type listType = typeof(List<>);
prop = listType.GetProperty("Item",
BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
var (hasGetAndSet, sameAccess, accessibility, getAccessibility,
setAccessibility) = prop;
Console.Write($"\nAccessibility of the {listType.FullName}.
{prop.Name} property: ");

if (!hasGetAndSet | sameAccess)
{
Console.WriteLine(accessibility);
}
else
{
Console.WriteLine($"\n The get accessor: {getAccessibility}");
Console.WriteLine($" The set accessor: {setAccessibility}");
}
}
}
// The example displays the following output:
// The System.DateTime.Now property:
// PropertyType: DateTime
// Static: True
// Read-only: True
// Indexed: False
//
// Accessibility of the System.Collections.Generic.List`1.Item
property: public

Método de extensão para tipos de sistema


Alguns tipos de sistema fornecem o método Deconstruct como uma conveniência. Por
exemplo, o tipo System.Collections.Generic.KeyValuePair<TKey,TValue> fornece essa
funcionalidade. Quando você está iterando em
System.Collections.Generic.Dictionary<TKey,TValue>, cada elemento é um
KeyValuePair<TKey, TValue> e pode ser desconstruído. Considere o seguinte exemplo:

C#

Dictionary<string, int> snapshotCommitMap =


new(StringComparer.OrdinalIgnoreCase)
{
["https://github.com/dotnet/docs"] = 16_465,
["https://github.com/dotnet/runtime"] = 114_223,
["https://github.com/dotnet/installer"] = 22_436,
["https://github.com/dotnet/roslyn"] = 79_484,
["https://github.com/dotnet/aspnetcore"] = 48_386
};
foreach (var (repo, commitCount) in snapshotCommitMap)
{
Console.WriteLine(
$"The {repo} repository had {commitCount:N0} commits as of November
10th, 2021.");
}

Você pode adicionar um método Deconstruct aos tipos de sistema que não têm um.
Considere o seguinte método de extensão:

C#

public static class NullableExtensions


{
public static void Deconstruct<T>(
this T? nullable,
out bool hasValue,
out T value) where T : struct
{
hasValue = nullable.HasValue;
value = nullable.GetValueOrDefault();
}
}

Esse método de extensão permite que todos os tipos Nullable<T> sejam


desconstruídos em uma tupla de (bool hasValue, T value) . O exemplo a seguir mostra
o código que usa este método de extensão:

C#

DateTime? questionableDateTime = default;


var (hasValue, value) = questionableDateTime;
Console.WriteLine(
$"{{ HasValue = {hasValue}, Value = {value} }}");

questionableDateTime = DateTime.Now;
(hasValue, value) = questionableDateTime;
Console.WriteLine(
$"{{ HasValue = {hasValue}, Value = {value} }}");

// Example outputs:
// { HasValue = False, Value = 1/1/0001 12:00:00 AM }
// { HasValue = True, Value = 11/10/2021 6:11:45 PM }

Tipos record
Quando você declara um tipo de registro usando dois ou mais parâmetros posicionais,
o compilador cria um método Deconstruct com um parâmetro out para cada
parâmetro posicional na declaração record . Para obter mais informações, consulte
Sintaxe posicional para definição de propriedade e Comportamento de desconstrutor
em registros derivados.

Confira também
Declaração de variável de desconstrução (regra de estilo IDE0042)
Descartes
Tipos de tupla
Exceções e manipulação de exceções
Artigo • 11/04/2024

Os recursos de manipulação de exceção da linguagem C# ajudam você a lidar com


quaisquer situações excepcionais ou inesperadas que ocorram quando um programa for
executado. A manipulação de exceções usa as palavras-chave try , catch e finally
para executar ações que talvez não sejam bem-sucedidas, lidar com falhas quando você
decidir que é razoável fazê-lo e limpar recursos depois disso. Podem ser geradas
exceções pelo CLR (Common Language Runtime), pelo .NET, por quaisquer bibliotecas
de terceiros ou pelo código do aplicativo. As exceções são criadas usando a palavra-
chave throw .

Em muitos casos, uma exceção pode ser lançada não por um método que seu código
chamou diretamente, mas por outro método mais abaixo na pilha de chamadas.
Quando isso acontecer, o CLR desenrolará a pilha, em busca de um método com um
bloco catch para o tipo de exceção específico e executará o primeiro o bloco catch
desse tipo que encontrar. Se ele não encontrar um bloco catch apropriado na pilha de
chamadas, ele encerrará o processo e exibirá uma mensagem para o usuário.

Neste exemplo, um método testa a divisão por zero e captura o erro. Sem a
manipulação de exceção, esse programa encerraria com um DivideByZeroException
não resolvido.

C#

public class ExceptionTest


{
static double SafeDivision(double x, double y)
{
if (y == 0)
throw new DivideByZeroException();
return x / y;
}

public static void Main()


{
// Input for test purposes. Change the values to see
// exception handling behavior.
double a = 98, b = 0;
double result;

try
{
result = SafeDivision(a, b);
Console.WriteLine("{0} divided by {1} = {2}", a, b, result);
}
catch (DivideByZeroException)
{
Console.WriteLine("Attempted divide by zero.");
}
}
}

Visão geral sobre exceções


As exceções têm as seguintes propriedades:

As exceções são tipos que derivam, por fim, de System.Exception .


Use um bloco try nas instruções que podem lançar exceções.
Quando ocorre uma exceção no bloco try , o fluxo de controle vai para o primeiro
manipulador de exceção associada que está presente em qualquer lugar na pilha
de chamadas. No C#, a palavra-chave catch é usada para definir um manipulador
de exceção.
Se nenhum manipulador de exceção para uma determinada exceção estiver
presente, o programa interromperá a execução com uma mensagem de erro.
Não capture uma exceção, a menos que você possa manipulá-la e deixar o
aplicativo em um estado conhecido. Se você capturar System.Exception , relance-o
usando a palavra-chave throw no final do bloco catch .
Se um bloco catch define uma variável de exceção, você pode usá-lo para obter
mais informações sobre o tipo de exceção que ocorreu.
As exceções podem ser geradas explicitamente por um programa usando a
palavra-chave throw .
Os objetos de exceção contêm informações detalhadas sobre o erro, como o
estado da pilha de chamadas e uma descrição de texto do erro.
O código em um bloco finally será executado mesmo que seja lançada uma
exceção. Use um bloco finally para liberar recursos, por exemplo, para fechar
todos os fluxos ou arquivos que foram abertos no bloco try .
As exceções gerenciadas no .NET são implementadas sobre o mecanismo de
manipulação de exceções estruturadas do Win32. Para obter mais informações,
consulte Manipulação de exceções estruturadas (C/C++) e Curso rápido sobre a
manipulação de exceções estruturadas do Win32 .

Especificação da Linguagem C#
Para obter mais informações, veja Exceções na Especificação da linguagem C#. A
especificação da linguagem é a fonte definitiva para a sintaxe e o uso de C#.
Confira também
SystemException
Palavras-chave do C#
throw
try-catch
try-finally
try-catch-finally
Exceções

Comentários
Esta página foi útil?  Yes  No

Fornecer comentários sobre o produto


Usar exceções
Artigo • 10/05/2023

No C#, os erros no programa em tempo de execução são propagados pelo programa


usando um mecanismo chamado exceções. As exceções são geradas pelo código que
encontra um erro e capturadas pelo código que pode corrigir o erro. As exceções
podem ser geradas pelo tempo de execução do .NET ou pelo código em um programa.
Uma vez que uma exceção é gerada, ela é propagada acima na pilha de chamadas até
uma instrução catch para a exceção ser encontrada. As exceções não capturadas são
tratadas por um manipulador de exceção genérico fornecido pelo sistema que exibe
uma caixa de diálogo.

As exceções são representadas por classes derivadas de Exception. Essa classe identifica
o tipo de exceção e contém propriedades que têm detalhes sobre a exceção. Gerar uma
exceção envolve criar uma instância de uma classe derivada de exceção, opcionalmente
configurar propriedades da exceção e, em seguida, gerar o objeto usando a palavra-
chave throw . Por exemplo:

C#

class CustomException : Exception


{
public CustomException(string message)
{
}
}
private static void TestThrow()
{
throw new CustomException("Custom exception in TestThrow()");
}

Depois que uma exceção é gerada, o runtime verifica a instrução atual para ver se ela
está dentro de um bloco try . Se estiver, todos os blocos catch associados ao bloco
try serão verificados para ver se eles podem capturar a exceção. Os blocos Catch

normalmente especificam os tipos de exceção. Se o tipo do bloco catch for do mesmo


tipo que a exceção ou uma classe base da exceção, o bloco catch poderá manipular o
método. Por exemplo:

C#

try
{
TestThrow();
}
catch (CustomException ex)
{
System.Console.WriteLine(ex.ToString());
}

Se a instrução que gera uma exceção não estiver dentro de um bloco try ou se o bloco
try que o contém não tiver um bloco catch correspondente, o runtime verificará o
método de chamada quanto a uma instrução try e blocos catch . O runtime continuará
acima na pilha de chamada, pesquisando um bloco catch compatível. Depois que o
bloco catch for localizado e executado, o controle será passado para a próxima
instrução após aquele bloco catch .

Uma instrução try pode conter mais de um bloco catch . A primeira instrução catch
que pode manipular a exceção é executado, todas as instruções catch posteriores,
mesmo se forem compatíveis, são ignoradas. Ordenar blocos catch do mais específico
(ou mais derivado) para o menos específico. Por exemplo:

C#

using System;
using System.IO;

namespace Exceptions
{
public class CatchOrder
{
public static void Main()
{
try
{
using (var sw = new StreamWriter("./test.txt"))
{
sw.WriteLine("Hello");
}
}
// Put the more specific exceptions first.
catch (DirectoryNotFoundException ex)
{
Console.WriteLine(ex);
}
catch (FileNotFoundException ex)
{
Console.WriteLine(ex);
}
// Put the least specific exception last.
catch (IOException ex)
{
Console.WriteLine(ex);
}
Console.WriteLine("Done");
}
}
}

Antes de o bloco catch ser executado, o runtime verifica se há blocos finally . Os


blocos Finally permitem que o programador limpe qualquer estado ambíguo que
pode ser deixado de um bloco try cancelado ou libere quaisquer recursos externos
(como identificadores de gráfico, conexões de banco de dados ou fluxos de arquivo)
sem esperar o coletor de lixo no runtime finalizar os objetos. Por exemplo:

C#

static void TestFinally()


{
FileStream? file = null;
//Change the path to something that works on your machine.
FileInfo fileInfo = new System.IO.FileInfo("./file.txt");

try
{
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
}
finally
{
// Closing the file allows you to reopen it immediately - otherwise
IOException is thrown.
file?.Close();
}

try
{
file = fileInfo.OpenWrite();
Console.WriteLine("OpenWrite() succeeded");
}
catch (IOException)
{
Console.WriteLine("OpenWrite() failed");
}
}

Se WriteByte() gerou uma exceção, o código no segundo bloco try que tentar reabrir
o arquivo falhará se file.Close() não for chamado e o arquivo permanecerá
bloqueado. Como os blocos finally são executados mesmo se uma exceção for
gerada, o bloco finally no exemplo anterior permite que o arquivo seja fechado
corretamente e ajuda a evitar um erro.
Se não for encontrado nenhum bloco catch compatível na pilha de chamadas após
uma exceção ser gerada, ocorrerá uma das três coisas:

Se a exceção estiver em um finalizador, o finalizador será anulado e o finalizador


base, se houver, será chamado.
Se a pilha de chamadas contiver um construtor estático ou um inicializador de
campo estático, uma TypeInitializationException será gerada, com a exceção
original atribuída à propriedade InnerException da nova exceção.
Se o início do thread for atingido, o thread será encerrado.
Manipulação de exceções (Guia de
Programação em C#)
Artigo • 20/03/2023

Um bloco try é usado por programadores de C# para particionar o código que pode ser
afetado por uma exceção. Os blocos catch associados são usados para tratar qualquer
exceção resultante. Um bloco finally contém código que será executado de uma
exceção ser ou não ser lançada no bloco try , como a liberação de recursos que estão
alocados no bloco try . Um bloco try exige um ou mais blocos catch associados ou
um bloco finally ou ambos.

Os exemplos a seguir mostram uma instrução try-catch , uma instrução try-finally e


um instrução try-catch-finally .

C#

try
{
// Code to try goes here.
}
catch (SomeSpecificException ex)
{
// Code to handle the exception goes here.
// Only catch exceptions that you know how to handle.
// Never catch base class System.Exception without
// rethrowing it at the end of the catch block.
}

C#

try
{
// Code to try goes here.
}
finally
{
// Code to execute after the try block goes here.
}

C#

try
{
// Code to try goes here.
}
catch (SomeSpecificException ex)
{
// Code to handle the exception goes here.
}
finally
{
// Code to execute after the try (and possibly catch) blocks
// goes here.
}

Um bloco try sem um bloco catch ou finally causa um erro do compilador.

Blocos catch
Um bloco catch pode especificar o tipo de exceção a ser capturado. A especificação de
tipo é chamada de filtro de exceção. O tipo de exceção deve ser derivado de Exception.
Em geral, não especifique Exception como o filtro de exceção, a menos que você saiba
como tratar todas as exceções que podem ser lançadas no bloco try ou incluiu uma
instrução throw no final do seu bloco catch .

Vários blocos catch com filtros de exceção diferentes podem ser encadeados. Os blocos
catch são avaliados de cima para baixo no seu código, mas somente um bloco catch
será executado para cada exceção que é lançada. O primeiro bloco catch que especifica
o tipo exato ou uma classe base da exceção lançada será executado. Se nenhum bloco
catch especificar uma classe de exceção correspondente, um bloco catch que não tem

nenhum tipo será selecionado, caso haja algum na instrução. É importante posicionar os
blocos catch com as classes de exceção mais específicas (ou seja, os mais derivados)
primeiro.

Capture exceções quando as seguintes condições forem verdadeiras:

Você tem uma boa compreensão de porque a exceção seria lançada e pode
implementar uma recuperação específica, como solicitar que o usuário insira um
novo nome de arquivo, quando você capturar um objeto FileNotFoundException.
Você pode criar e lançar uma exceção nova e mais específica.

C#

int GetInt(int[] array, int index)


{
try
{
return array[index];
}
catch (IndexOutOfRangeException e)
{
throw new ArgumentOutOfRangeException(
"Parameter index is out of range.", e);
}
}

Você deseja tratar parcialmente uma exceção antes de passá-la para mais
tratamento. No exemplo a seguir, um bloco catch é usado para adicionar uma
entrada a um log de erros antes de lançar novamente a exceção.

C#

try
{
// Try to access a resource.
}
catch (UnauthorizedAccessException e)
{
// Call a custom error logging procedure.
LogError(e);
// Re-throw the error.
throw;
}

Você também pode especificar filtros de exceção para adicionar uma expressão booliana
a uma cláusula catch. Filtros de exceção indicam que uma cláusula catch específica
corresponde somente quando essa condição é verdadeira. No exemplo a seguir, ambas
as cláusulas catch usam a mesma classe de exceção, mas uma condição extra é
verificada para criar uma mensagem de erro diferente:

C#

int GetInt(int[] array, int index)


{
try
{
return array[index];
}
catch (IndexOutOfRangeException e) when (index < 0)
{
throw new ArgumentOutOfRangeException(
"Parameter index cannot be negative.", e);
}
catch (IndexOutOfRangeException e)
{
throw new ArgumentOutOfRangeException(
"Parameter index cannot be greater than the array size.", e);
}
}
Um filtro de exceção que sempre retorna false pode ser usado para examinar todas as
exceções, mas não processá-las. Um uso típico é registrar exceções:

C#

public static void Main()


{
try
{
string? s = null;
Console.WriteLine(s.Length);
}
catch (Exception e) when (LogException(e))
{
}
Console.WriteLine("Exception must have been handled");
}

private static bool LogException(Exception e)


{
Console.WriteLine($"\tIn the log routine. Caught {e.GetType()}");
Console.WriteLine($"\tMessage: {e.Message}");
return false;
}

O método LogException sempre retorna false , nenhuma cláusula catch que usa esse
filtro de exceção corresponde. A cláusula catch pode ser geral, usando
System.Exception, e cláusulas posteriores podem processar classes de exceção mais
específicas.

Blocos Finally
Um bloco finally permite que você limpe as ações que são realizadas em um bloco
try . Se estiver presente, o bloco finally será executado por último, depois do bloco

try e de qualquer bloco catch de correspondência. Um bloco finally sempre é

executado, independentemente de uma exceção ser lançada ou de um bloco catch


correspondente ao tipo de exceção ser encontrado.

O bloco finally pode ser usado para liberar recursos, como fluxos de arquivos,
conexões de banco de dados e identificadores de gráficos, sem esperar que o coletor de
lixo no runtime finalize os objetos.

No exemplo a seguir, o bloco finally é usado para fechar um arquivo está aberto no
bloco try . Observe que o estado do identificador de arquivo é selecionado antes do
arquivo ser fechado. Se o bloco try não puder abrir o arquivo, o identificador de
arquivo ainda terá o valor null e o bloco finally não tentará fechá-lo. Em vez disso, se
o arquivo for aberto com êxito no bloco try , o bloco finally fechará o arquivo aberto.

C#

FileStream? file = null;


FileInfo fileinfo = new System.IO.FileInfo("./file.txt");
try
{
file = fileinfo.OpenWrite();
file.WriteByte(0xF);
}
finally
{
// Check for null because OpenWrite might have failed.
file?.Close();
}

Especificação da Linguagem C#
Para obter mais informações, veja Exceções e A declaração try na Especificação da
Linguagem C#. A especificação da linguagem é a fonte definitiva para a sintaxe e o uso
de C#.

Confira também
Referência de C#
try-catch
try-finally
try-catch-finally
Instrução using
Criar e lançar exceções
Artigo • 23/12/2023

As exceções são usadas para indicar que ocorreu um erro durante a execução do
programa. Objetos de exceção que descrevem um erro são criados e, em seguida,
lançados com a instrução ou expressão throw. Então, o runtime procura o manipulador
de exceção mais compatível.

Os programadores devem lançar exceções quando uma ou mais das seguintes


condições forem verdadeiras:

O método não pode concluir sua funcionalidade definida. Por exemplo, se um


parâmetro para um método tem um valor inválido:

C#

static void CopyObject(SampleClass original)


{
_ = original ?? throw new ArgumentException("Parameter cannot be
null", nameof(original));
}

É feita uma chamada inadequada a um objeto, com base no estado do objeto. Um


exemplo pode ser a tentativa de gravar em um arquivo somente leitura. Em casos
em que um estado do objeto não permita uma operação, lance uma instância de
InvalidOperationException ou um objeto com base em uma derivação dessa classe.
O código a seguir é um exemplo de um método que gera um objeto
InvalidOperationException:

C#

public class ProgramLog


{
FileStream logFile = null!;
public void OpenLog(FileInfo fileName, FileMode mode) { }

public void WriteLog()


{
if (!logFile.CanWrite)
{
throw new InvalidOperationException("Logfile cannot be
read-only");
}
// Else write data to the log and return.
}
}

Quando um argumento para um método causa uma exceção. Nesse caso, a


exceção original deve ser capturada e uma instância de ArgumentException deve
ser criada. A exceção original deve ser passada para o construtor do
ArgumentException como o parâmetro InnerException:

C#

static int GetValueFromArray(int[] array, int index)


{
try
{
return array[index];
}
catch (IndexOutOfRangeException e)
{
throw new ArgumentOutOfRangeException(
"Parameter index is out of range.", e);
}
}

7 Observação

O exemplo anterior mostra como usar a propriedade InnerException . Ele foi


simplificado intencionalmente. Na prática, você deve verificar se um índice
está no intervalo antes de usá-lo. Você pode usar essa técnica de encapsular
uma exceção quando um membro de um parâmetro gera uma exceção que
você não poderia prever antes de chamar o membro.

As exceções contêm uma propriedade chamada StackTrace. Essa cadeia de caracteres


contém o nome dos métodos na pilha de chamadas atual, junto com o nome de arquivo
e o número de linha em que a exceção foi lançada para cada método. Um objeto
StackTrace é criado automaticamente pelo CLR (Common Language Runtime) no ponto
da instrução throw , de modo que as exceções devem ser lançadas do ponto em que o
rastreamento de pilha deve começar.

Todas as exceções contêm uma propriedade chamada Message. Essa cadeia de


caracteres deve ser definida para explicar o motivo da exceção. As informações que são
sensíveis à segurança não devem ser colocadas no texto da mensagem. Além Message,
ArgumentException contém uma propriedade chamada ParamName que deve ser
definida como o nome do argumento que causou a exceção a ser lançada. Em um setter
de propriedade, ParamName deve ser definido como value .

Os métodos públicos e protegidos lançam exceções sempre que não puderem concluir
suas funções pretendidas. A classe de exceção lançada é a exceção mais específica
disponível que se adapta às condições do erro. Essas exceções devem ser
documentadas como parte da funcionalidade de classe e as classes derivadas ou as
atualizações da classe original devem manter o mesmo comportamento para
compatibilidade com versões anteriores.

O que deve ser evitado na geração de exceções


A lista a seguir identifica as práticas a serem evitadas ao lançar exceções:

Não use exceções para alterar o fluxo de um programa como parte da execução
normal. Use exceções para relatar e lidar com condições de erro.
As exceções não devem ser retornadas como um valor retornado ou um
parâmetro em vez de serem lançadas.
Não lance System.Exception, System.SystemException,
System.NullReferenceException ou System.IndexOutOfRangeException
intencionalmente de seu próprio código-fonte.
Não crie exceções que podem ser lançadas no modo de depuração, mas não no
modo de versão. Em vez disso, use o Debug Assert para identificar erros em tempo
de execução durante a fase de desenvolvimento.

Exceções em métodos de retorno de tarefa


Os métodos declarados com o modificador async têm algumas considerações especiais
quando se trata de exceções. As exceções geradas em um método async são
armazenadas na tarefa retornada e não surgem até que, por exemplo, a tarefa seja
aguardada. Para obter mais informações sobre exceções armazenadas, confira exceções
assíncronas.

Recomendamos que você valide argumentos e gere exceções correspondentes, como


ArgumentException e ArgumentNullException, antes de inserir as partes assíncronas de
seus métodos. Ou seja, essas exceções de validação devem surgir de forma síncrona
antes do início do trabalho. O snippet de código a seguir mostra um exemplo em que,
se as exceções fossem geradas, as exceções ArgumentException surgiriam de forma
síncrona, enquanto as InvalidOperationException seriam armazenadas na tarefa
retornada.
C#

// Non-async, task-returning method.


// Within this method (but outside of the local function),
// any thrown exceptions emerge synchronously.
public static Task<Toast> ToastBreadAsync(int slices, int toastTime)
{
if (slices is < 1 or > 4)
{
throw new ArgumentException(
"You must specify between 1 and 4 slices of bread.",
nameof(slices));
}

if (toastTime < 1)
{
throw new ArgumentException(
"Toast time is too short.", nameof(toastTime));
}

return ToastBreadAsyncCore(slices, toastTime);

// Local async function.


// Within this function, any thrown exceptions are stored in the task.
static async Task<Toast> ToastBreadAsyncCore(int slices, int time)
{
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("Putting a slice of bread in the toaster");
}
// Start toasting.
await Task.Delay(time);

if (time > 2_000)


{
throw new InvalidOperationException("The toaster is on fire!");
}

Console.WriteLine("Toast is ready!");

return new Toast();


}
}

Definir classes de exceção


Os programas podem lançar uma classe de exceção predefinida no namespace System
(exceto quando observado anteriormente) ou criar suas próprias classes de exceção,
derivando de Exception. As classes derivadas devem definir pelo menos três
construtores: um construtor sem parâmetros, um que define a propriedade de
mensagem e um que define as propriedades Message e InnerException. Por exemplo:

C#

[Serializable]
public class InvalidDepartmentException : Exception
{
public InvalidDepartmentException() : base() { }
public InvalidDepartmentException(string message) : base(message) { }
public InvalidDepartmentException(string message, Exception inner) :
base(message, inner) { }
}

Adicione novas propriedades à classe de exceção quando os dados que elas fornecem
forem úteis para resolver a exceção. Se forem adicionadas novas propriedades à classe
de exceção derivada, ToString() deverá ser substituído para retornar as informações
adicionadas.

Especificação da linguagem C#
Para obter mais informações, veja Exceções e A declaração throw na Especificação da
Linguagem C#. A especificação da linguagem é a fonte definitiva para a sintaxe e o uso
de C#.

Confira também
Hierarquia de exceções

6 Colaborar conosco no Comentários do .NET


GitHub O .NET é um projeto código aberto.
A fonte deste conteúdo pode Selecione um link para fornecer
ser encontrada no GitHub, onde comentários:
você também pode criar e
revisar problemas e solicitações  Abrir um problema de
de pull. Para obter mais documentação
informações, confira o nosso
guia para colaboradores.  Fornecer comentários sobre o
produto
Exceções geradas pelo compilador
Artigo • 01/06/2023

Algumas exceções são geradas automaticamente pelo runtime do .NET quando


operações básicas falham. Essas exceções e suas condições de erro são listadas na
tabela a seguir.

Exceção Descrição

ArithmeticException Uma classe base para exceções que ocorrem durante operações
aritméticas, tais como DivideByZeroException e
OverflowException.

ArrayTypeMismatchException Gerada quando uma matriz não pode armazenar determinado


elemento porque o tipo real do elemento é incompatível com o
tipo real da matriz.

DivideByZeroException Lançada quando é feita uma tentativa de dividir um valor inteiro


por zero.

IndexOutOfRangeException Lançada quando é feita uma tentativa de indexar uma matriz


quando o índice é menor que zero ou fora dos limites da matriz.

InvalidCastException Gerada quando uma conversão explícita de um tipo base para


uma interface ou um tipo derivado falha em tempo de execução.

NullReferenceException Gerada quando há uma tentativa de fazer referência a um objeto


cujo valor é null.

OutOfMemoryException Lançada quando uma tentativa de alocar memória usando o


operador new falha. Essa exceção indica que a memória
disponível para o Common Language Runtime está esgotada.

OverflowException Lançada quando uma operação aritmética em um contexto


checked estoura.

StackOverflowException Lançada quando a pilha de execução acaba tendo muitas


chamadas de método pendentes, normalmente indica uma
recursão muito profunda ou infinita.

TypeInitializationException Lançada quando um construtor estático lança uma exceção e não


há nenhuma cláusula catch compatível para capturá-la.

Confira também
Instruções para manipulação de exceções
Regras e convenções de nomenclatura
do identificador C#
Artigo • 04/02/2024

Um identificador é o nome que você atribui a um tipo (classe, interface, struct,


delegado ou enumerado), membro, variável ou namespace.

Regras de nomenclatura
Os identificadores válidos devem seguir essas regras. O compilador de C# produz um
erro para qualquer identificador que não siga estas regras:

Os identificadores devem começar com letra ou sublinhado ( _ ).


Os identificadores podem conter caracteres de letra Unicode, caracteres de dígitos
decimais, caracteres de conexão Unicode, caracteres de combinação Unicode ou
caracteres de formatação Unicode. Para obter mais informações sobre as
categorias Unicode, consulte o Banco de dados da categoria Unicode .

É possível declarar identificadores que correspondem às palavras-chave em C# usando


o prefixo @ no identificador. O @ faz parte do nome do identificador. Por exemplo, @if
declara um identificador chamado if . Esses identificadores textuais são destinados
principalmente para interoperabilidade com os identificadores declarados em outras
linguagens.

Para obter uma definição completa de identificadores válidos, confira o artigo


Identificadores na especificação da linguagem C#.

) Importante

A especificação da linguagem C# permite apenas as categorias de letras (Lu, Ll, Lt,


Lm, Lo ou Nl), dígitos (Nd), a conexão (Pc), a combinação (Mn ou Mc) e a
formatação (Cf). Qualquer coisa fora disso será automaticamente substituída
usando _ . Isso pode afetar determinados caracteres Unicode.

Convenções de nomenclatura
Além das regras, as convenções para nomes de identificador são usadas em todas as
APIs do .NET. Essas convenções fornecem consistência para nomes, mas o compilador
não as impõe. Você é livre para usar convenções diferentes em seus projetos.

Por convenção, os programas C# usam PascalCase para nomes de tipo, namespaces e


todos os membros públicos. Além disso, a equipe dotnet/docs usa as seguintes
convenções, adotadas a partir do estilo de codificação da equipe do .NET Runtime :

Os nomes de interface começam com I maiúsculo.

Os tipos de atributo terminam com a palavra Attribute .

Os tipos enumerados usam um substantivo singular para nonflags e um


substantivo plural para sinalizadores.

Os identificadores não devem conter dois caracteres de sublinhado ( _ )


consecutivos. Esses nomes estão reservados para identificadores gerados por
compilador.

Use nomes significativos e descritivos para variáveis, métodos e classes.

Prefira clareza em vez de brevidade.

Use PascalCase para nomes de classe e nomes de método.

Use camelCase para argumentos de método, variáveis locais e campos privados.

Use PascalCase para nomes de constantes, tanto para constantes de campo


quanto para constantes de local.

Os campos de instância privada começam com um sublinhado ( _ ).

Os campos estáticos começam com s_ . Essa convenção não é o comportamento


padrão do Visual Studio, nem parte das Diretrizes de design de estrutura, mas é
configurável no editorconfig.

Evite usar abreviações ou acrônimos em nomes, exceto para abreviações


amplamente conhecidas e aceitas.

Use namespaces significativos e descritivos que seguem a notação de nome de


domínio inverso.

Escolha os nomes de assembly que representam a finalidade primária do assembly.

Evite usar nomes de letra única, exceto para contadores de loop simples. Além
disso, os exemplos de sintaxe que descrevem a sintaxe de constructos C#
geralmente usam os seguintes nomes de letra única que correspondem à
convenção usada na especificação da linguagem C#. Os exemplos de sintaxe são
uma exceção à regra.
Use S para structs, C para classes.
Use M para métodos.
Use v para variáveis, p para parâmetros.
Use r para parâmetros ref .

 Dica

Você pode impor convenções de nomenclatura que dizem respeito à capitalização,


prefixos, sufixos e separadores de palavras usando regras de nomenclatura de
estilo de código.

Nos exemplos a seguir, as diretrizes relativas aos elementos marcados com public são
também aplicáveis ao trabalhar com elementos protected e protected internal , todos
os quais devem ser visíveis para chamadores externos.

Pascal case
Use pascal casing ("PascalCasing") ao nomear um tipo class , interface , struct ou
delegate .

C#

public class DataService


{
}

C#

public record PhysicalAddress(


string Street,
string City,
string StateOrProvince,
string ZipCode);

C#

public struct ValueCoordinate


{
}
C#

public delegate void DelegateType(string message);

Ao nomear um interface , use Pascal Case, além de aplicar ao nome um prefixo I . Esse
prefixo indica claramente aos consumidores que ele é um interface .

C#

public interface IWorkerQueue


{
}

Ao nomear membros public de tipos, como campos, propriedades, eventos, use pascal
casing. Além disso, use pascal casing para todos os métodos e funções locais.

C#

public class ExampleEvents


{
// A public field, these should be used sparingly
public bool IsValid;

// An init-only property
public IWorkerQueue WorkerQueue { get; init; }

// An event
public event Action EventProcessing;

// Method
public void StartEventProcessing()
{
// Local function
static int CountQueueItems() => WorkerQueue.Count;
// ...
}
}

Ao gravar registros posicionais, use Pascal Case para parâmetros, pois são as
propriedades públicas do registro.

C#

public record PhysicalAddress(


string Street,
string City,
string StateOrProvince,
string ZipCode);

Para obter mais informações sobre registros posicionais, confira Sintaxe posicional para
definição de propriedade.

Camel Case
Use camel casing ("camelCasing") ao nomear campos private ou internal e dê a eles o
prefixo _ . Use camel casing ao nomear variáveis locais, incluindo instâncias de um tipo
delegado.

C#

public class DataService


{
private IWorkerQueue _workerQueue;
}

 Dica

Ao editar o código do C# que segue essas convenções de nomenclatura em um


IDE que permite a conclusão da instrução, digitar _ mostrará todos os membros no
escopo do objeto.

Ao trabalhar com os campos static que são private ou internal , use o prefixo s_ e,
para thread estático, use t_ .

C#

public class DataService


{
private static IWorkerQueue s_workerQueue;

[ThreadStatic]
private static TimeSpan t_timeSpan;
}

Ao gravar os parâmetros de método, use Camel Case.

C#

public T SomeMethod<T>(int someNumber, bool isValid)


{
}

Para obter mais informações sobre convenções de nomenclatura em C#, confira o estilo
de codificação da equipe do .NET Runtime .

Diretrizes para a nomenclatura de parâmetros de tipo


As diretrizes a seguir se aplicam a parâmetros de tipo em parâmetros de tipo genérico.
Parâmetros de tipo são os espaços reservados para argumentos em um tipo genérico
ou um método genérico. Você pode ler mais sobre parâmetros de tipo genérico no guia
de programação em C#.

Nomeie parâmetros de tipo genérico com nomes descritivos, a menos que um


nome de letra única seja completamente autoexplicativo e um nome descritivo
não adicione valor.

./snippets/coding-conventions

public interface ISessionChannel<TSession> { /*...*/ }


public delegate TOutput Converter<TInput, TOutput>(TInput from);
public class List<T> { /*...*/ }

Considere usar T como o nome do parâmetro de tipo para tipos com um


parâmetro de tipo de letra única.

./snippets/coding-conventions

public int IComparer<T>() { return 0; }


public delegate bool Predicate<T>(T item);
public struct Nullable<T> where T : struct { /*...*/ }

Insira o prefixo “T” em nomes descritivos de parâmetro de tipo.

./snippets/coding-conventions

public interface ISessionChannel<TSession>


{
TSession Session { get; }
}

Considere indicar as restrições colocadas em um parâmetro de tipo no nome do


parâmetro. Por exemplo, um parâmetro restrito a ISession pode ser nomeado
como TSession .
A regra de análise de código CA1715 pode ser usada para garantir que os parâmetros
de tipo sejam nomeados adequadamente.

Convenções de nomenclatura extras


Em exemplos que não incluem o uso de diretivas, use as qualificações do
namespace. Se você souber que um namespace é importado por padrão em um
projeto, não precisará qualificar totalmente os nomes desse namespace. Os nomes
qualificados poderão ser quebrados após um ponto (.) se forem muito longos para
uma única linha, conforme mostrado no exemplo a seguir.

C#

var currentPerformanceCounterCategory = new System.Diagnostics.


PerformanceCounterCategory();

Não é necessário alterar os nomes de objetos que foram criados usando as


ferramentas de designer do Visual Studio para adequá-los a outras diretrizes.

6 Colaborar conosco no Comentários do .NET


GitHub O .NET é um projeto código aberto.
A fonte deste conteúdo pode Selecione um link para fornecer
ser encontrada no GitHub, onde comentários:
você também pode criar e
revisar problemas e solicitações  Abrir um problema de
de pull. Para obter mais documentação
informações, confira o nosso
guia para colaboradores.  Fornecer comentários sobre o
produto
Convenções comuns de código C#
Artigo • 27/09/2024

Convenções de código são essenciais para manter a legibilidade, a consistência e a


colaboração de código em uma equipe de desenvolvimento. É mais fácil de entender,
manter e estender o código que segue as práticas do setor e as diretrizes estabelecidas.
A maioria dos projetos impõe um estilo consistente por meio de convenções de código.
Os projetos dotnet/docs e dotnet/samples não são exceção. Nesta série de artigos,
você aprenderá nossas convenções de codificação e as ferramentas que usamos para
aplicá-las. Você pode tomar nossas convenções como estão ou modificá-las para
atender às necessidades de sua equipe.

Escolhemos nossas convenções com base nas seguintes metas:

1. Correção: nossos exemplos são copiados e colados em seus aplicativos. Esperamos


isso, portanto, precisamos criar um código resiliente e correto, mesmo após várias
edições.
2. Ensino: a finalidade de nossos exemplos é ensinar tudo do .NET e C#. Por esse
motivo, não colocamos restrições em nenhum recurso de idioma ou API. Em vez
disso, esses exemplos ensinam quando um recurso é uma boa opção.
3. Consistência: os leitores esperam uma experiência consistente em nosso conteúdo.
Todos os exemplos devem estar em conformidade com o mesmo estilo.
4. Adoção: atualizamos agressivamente nossos exemplos para usar novos recursos de
linguagem. Essa prática conscientiza os novos recursos e os torna mais familiares
para todos os desenvolvedores C#.

) Importante

Essas diretrizes são usadas pela Microsoft para desenvolver exemplos e


documentação. Elas foram adotados das diretrizes do .NET runtime, estilo de
codificação C# e compilador C# (roslyn) . Escolhemos essas diretrizes porque
elas foram testadas ao longo de vários anos de desenvolvimento de software livre.
Elas ajudaram os membros da comunidade a participar dos projetos de runtime e
compilador. Elas devem ser um exemplo de convenções comuns em C# e não uma
lista autoritativa (confira Diretrizes de design de estrutura para isso).

As metas de ensino e adoção são as razões pelas quais a convenção de codificação


de documentos difere das convenções de runtime e compilador. O runtime e o
compilador têm métricas de desempenho estritas para caminhos frequentes. Não
se pode dizer o mesmo de muitos outros aplicativos. Nossa meta de ensino
determina que não proibimos nenhuma construção. Em vez disso, os exemplos
mostram quando os constructos devem ser usados. Atualizamos exemplos mais
agressivamente do que a maioria dos aplicativos de produção. Nossa meta de
adoção determina que mostramos o código que você deve escrever hoje, mesmo
quando o código escrito no ano passado não precisa de alterações.

Este artigo explica nossas diretrizes. As diretrizes evoluíram ao longo do tempo e você
encontrará exemplos que não seguem nossas diretrizes. Damos as boas-vindas a PRs
que trazem esses exemplos para a conformidade, ou problemas que chamam nossa
atenção para exemplos que devemos atualizar. Nossas diretrizes são de software livre e
damos as boas-vindas a PRs e problemas. No entanto, se o envio alterar essas
recomendações, abra um problema para discussão primeiro. Você é bem-vindo a usar
nossas diretrizes ou adaptá-las às suas necessidades.

Ferramentas e analisadores
As ferramentas podem ajudar sua equipe a impor as convenções deles. Você pode
habilitar a análise de código para impor as regras que preferir. Você também pode criar
uma editorconfig para que o Visual Studio imponha automaticamente suas diretrizes de
estilo. Como ponto de partida, você pode copiar o arquivo do repositório dotnet/docs
para usar nosso estilo.

Essas ferramentas facilitam a adoção de suas diretrizes preferenciais para sua equipe. O
Visual Studio aplica as regras em todos os arquivos .editorconfig no escopo para
formatar seu código. Você pode usar várias configurações para impor convenções
corporativas, convenções de equipe e até convenções de projeto granulares.

A análise de código produz avisos e diagnósticos quando as regras habilitadas são


violadas. Você configura as regras que deseja aplicar ao seu projeto. Em seguida, cada
build de CI notifica os desenvolvedores quando eles violam qualquer uma das regras.

IDs de diagnóstico
Escolha IDs de diagnóstico apropriadas ao criar seus analisadores

Diretrizes de linguagem
As seções a seguir descrevem as práticas que a equipe de documentos do .NET segue
para preparar exemplos e exemplos de código. Em geral, siga estas práticas:

Utilize recursos de linguagem moderna e versões em C# sempre que possível.


Evite construções de linguagem obsoletas ou desatualizadas.
Somente capture exceções que possam ser tratadas corretamente; evite capturar
exceções genéricas.
Use tipos de exceção específicos para fornecer mensagens de erro significativas.
Use consultas LINQ e métodos para manipulação de coleção para melhorar a
legibilidade do código.
Use programação assíncrona com assíncrono e aguarde operações associadas a
E/S.
Tenha cuidado com deadlocks e use Task.ConfigureAwait quando apropriado.
Use as palavras-chave de idioma para tipos de dados em vez dos tipos de runtime.
Por exemplo, use string em vez de System.String, ou int em vez de System.Int32.
Use int em vez de tipos não assinados. O uso de int é comum em todo o C# e é
mais fácil interagir com outras bibliotecas quando você usa int . As exceções são
para a documentação específica para tipos de dados não assinados.
Use var somente quando um leitor puder inferir o tipo da expressão. Os leitores
exibem nossos exemplos na plataforma de documentos. Eles não têm dicas de
focalização ou ferramenta que exibem o tipo de variáveis.
Escreva código com clareza e simplicidade em mente.
Evite lógica de código excessivamente complexa e complicada.

Há diretrizes mais específicas a seguir.

Dados de cadeia de caracteres


Use a interpolação de cadeia de caracteres para concatenar cadeias de caracteres
curtas, como é mostrado no código a seguir.

C#

string displayName = $"{nameList[n].LastName},


{nameList[n].FirstName}";

Para acrescentar cadeias de caracteres em loops, especialmente quando você


estiver trabalhando com grandes quantidades de texto, use um objeto
System.Text.StringBuilder.

C#

var phrase =
"lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala";
var manyPhrases = new StringBuilder();
for (var i = 0; i < 10000; i++)
{
manyPhrases.Append(phrase);
}
//Console.WriteLine("tra" + manyPhrases);

Matrizes
Use a sintaxe concisa ao inicializar matrizes na linha da declaração. No exemplo a
seguir, você não pode usar var em vez de string[] .

C#

string[] vowels1 = { "a", "e", "i", "o", "u" };

Se você usar uma instanciação explícita, poderá usar var .

C#

var vowels2 = new string[] { "a", "e", "i", "o", "u" };

Delegados
Use Func<> e Action<>, em vez de definir tipos delegados. Em uma classe, defina
o método delegado.

C#

Action<string> actionExample1 = x => Console.WriteLine($"x is: {x}");

Action<string, string> actionExample2 = (x, y) =>


Console.WriteLine($"x is: {x}, y is {y}");

Func<string, int> funcExample1 = x => Convert.ToInt32(x);

Func<int, int, int> funcExample2 = (x, y) => x + y;

Chame o método usando a assinatura definida pelo delegado Func<> ou Action<> .

C#

actionExample1("string for x");

actionExample2("string for x", "string for y");

Console.WriteLine($"The value is {funcExample1("1")}");


Console.WriteLine($"The sum is {funcExample2(1, 2)}");

Se você criar instâncias de um tipo delegado, use a sintaxe concisa. Em uma classe,
defina o tipo delegado e um método que tenha uma assinatura correspondente.

C#

public delegate void Del(string message);

public static void DelMethod(string str)


{
Console.WriteLine("DelMethod argument: {0}", str);
}

Crie uma instância do tipo delegado e a chame. A declaração a seguir mostra a


sintaxe condensada.

C#

Del exampleDel2 = DelMethod;


exampleDel2("Hey");

A declaração a seguir usa a sintaxe completa.

C#

Del exampleDel1 = new Del(DelMethod);


exampleDel1("Hey");

try-catch Instruções e using no tratamento de exceções

Use uma instrução try-catch para a maioria da manipulação de exceções.

C#

static double ComputeDistance(double x1, double y1, double x2, double


y2)
{
try
{
return Math.Sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 -
y2));
}
catch (System.ArithmeticException ex)
{
Console.WriteLine($"Arithmetic overflow or underflow: {ex}");
throw;
}
}

Simplifique o código usando a instrução using do #C. Se você tiver uma instrução
try-finally na qual o único código do bloco finally é uma chamada para o
método Dispose, use, em vez disso, uma instrução using .

No exemplo a seguir, a instrução try-finally chama apenas Dispose no bloco


finally .

C#

Font bodyStyle = new Font("Arial", 10.0f);


try
{
byte charset = bodyStyle.GdiCharSet;
}
finally
{
if (bodyStyle != null)
{
((IDisposable)bodyStyle).Dispose();
}
}

Você pode fazer a mesma coisa com uma instrução using .

C#

using (Font arial = new Font("Arial", 10.0f))


{
byte charset2 = arial.GdiCharSet;
}

Use a nova sintaxe using que não requer chaves:

C#

using Font normalStyle = new Font("Arial", 10.0f);


byte charset3 = normalStyle.GdiCharSet;

Operadores && e ||
Use && em vez de & e || em vez de | quando executar comparações, conforme
mostrado no exemplo a seguir.
C#

Console.Write("Enter a dividend: ");


int dividend = Convert.ToInt32(Console.ReadLine());

Console.Write("Enter a divisor: ");


int divisor = Convert.ToInt32(Console.ReadLine());

if ((divisor != 0) && (dividend / divisor) is var result)


{
Console.WriteLine("Quotient: {0}", result);
}
else
{
Console.WriteLine("Attempted division by 0 ends up here.");
}

Se o divisor for 0, a segunda cláusula na instrução if causará um erro em tempo de


execução. Mas o operador && entra em curto-circuito quando a primeira expressão é
falsa. Ou seja, ele não avalia a segunda expressão. O operador & avalia ambas,
resultando em um erro em tempo de execução quando divisor é 0.

Operador new
Use uma das formas concisas de instanciação de objeto, conforme mostrado nas
declarações a seguir.

C#

var firstExample = new ExampleClass();

C#

ExampleClass instance2 = new();

As declarações anteriores são equivalentes à declaração a seguir.

C#

ExampleClass secondExample = new ExampleClass();

Use inicializadores de objeto para simplificar a criação de objeto, conforme


mostrado no exemplo a seguir.

C#
var thirdExample = new ExampleClass { Name = "Desktop", ID = 37414,
Location = "Redmond", Age = 2.3 };

O exemplo a seguir define as mesmas propriedades do exemplo anterior, mas não


usa inicializadores.

C#

var fourthExample = new ExampleClass();


fourthExample.Name = "Desktop";
fourthExample.ID = 37414;
fourthExample.Location = "Redmond";
fourthExample.Age = 2.3;

Manipulação de eventos
Use uma expressão lambda para definir um manipulador de eventos que você não
precisa remover mais tarde:

C#

public Form2()
{
this.Click += (s, e) =>
{
MessageBox.Show(
((MouseEventArgs)e).Location.ToString());
};
}

A expressão lambda reduz a definição convencional a seguir.

C#

public Form1()
{
this.Click += new EventHandler(Form1_Click);
}

void Form1_Click(object? sender, EventArgs e)


{
MessageBox.Show(((MouseEventArgs)e).Location.ToString());
}

Membros estáticos
Chame membros estáticos usando o nome de classe: ClassName.StaticMember. Essa
prática torna o código mais legível, tornando o acesso estático limpo. Não qualifique
um membro estático definido em uma classe base com o nome de uma classe derivada.
Enquanto esse código é compilado, a leitura do código fica incorreta e o código poderá
ser danificado no futuro se você adicionar um membro estático com o mesmo nome da
classe derivada.

Consultas LINQ
Use nomes significativos para variáveis de consulta. O exemplo a seguir usa
seattleCustomers para os clientes que estão localizados em Seattle.

C#

var seattleCustomers = from customer in customers


where customer.City == "Seattle"
select customer.Name;

Use aliases para se certificar de que os nomes de propriedades de tipos anônimos


sejam colocados corretamente em maiúsculas, usando o padrão Pascal-Case.

C#

var localDistributors =
from customer in customers
join distributor in distributors on customer.City equals
distributor.City
select new { Customer = customer, Distributor = distributor };

Renomeie propriedades quando os nomes de propriedades no resultado forem


ambíguos. Por exemplo, se a sua consulta retornar um nome de cliente e uma ID
de distribuidor, em vez de deixá-los como Name e ID no resultado, renomeie-os
para esclarecer que Name é o nome de um cliente, e ID é a identificação de um
distribuidor.

C#

var localDistributors2 =
from customer in customers
join distributor in distributors on customer.City equals
distributor.City
select new { CustomerName = customer.Name, DistributorID =
distributor.ID };
Usa a digitação implícita na declaração de variáveis de consulta e de intervalo. Essa
orientação sobre digitação implícita em consultas LINQ substitui as regras gerais
para variáveis locais de tipo implícito. As consultas LINQ geralmente usam
projeções que criam tipos anônimos. Outras expressões de consulta criam
resultados com tipos genéricos aninhados. Variáveis de tipo implícito geralmente
são mais legíveis.

C#

var seattleCustomers = from customer in customers


where customer.City == "Seattle"
select customer.Name;

Alinhe as cláusulas de consulta na cláusula from, conforme mostrado nos


exemplos anteriores.

Use as cláusulas where antes de outras cláusulas de consulta, para garantir que as
cláusulas de consulta posteriores operem no conjunto de dados filtrado e
reduzido.

C#

var seattleCustomers2 = from customer in customers


where customer.City == "Seattle"
orderby customer.Name
select customer;

Use várias cláusulas from , em vez de uma cláusula join, para acessar as coleções
internas. Por exemplo, cada coleção de objetos Student pode conter um conjunto
de pontuações no teste. Quando a próxima consulta for executada, ela retorna
cada pontuação que seja acima de 90, juntamente com o sobrenome do estudante
que recebeu a pontuação.

C#

var scoreQuery = from student in students


from score in student.Scores!
where score > 90
select new { Last = student.LastName, score };

Variáveis locais de tipo implícito


Use a digitação implícita para variáveis locais quando o tipo da variável for óbvio
do lado direito da atribuição.
C#

var message = "This is clearly a string.";


var currentTemperature = 27;

Não use var quando o tipo não for aparente do lado direito da atribuição. Não
suponha que o tipo esteja claro partir do nome de um método. Um tipo de
variável é considerado claro se for um operador new , uma conversão explícita ou
uma atribuição a um valor literal.

C#

int numberOfIterations = Convert.ToInt32(Console.ReadLine());


int currentMaximum = ExampleClass.ResultSoFar();

Não use nomes de variáveis para especificar o tipo da variável. Ele pode não estar
correto. Em vez disso, use o tipo para especificar o tipo e use o nome da variável
para indicar as informações semânticas da variável. O exemplo a seguir deve usar
string para o tipo e algo como iterations indicar o significado das informações

lidas do console.

C#

var inputInt = Console.ReadLine();


Console.WriteLine(inputInt);

Evite o uso de var em vez de dynamic. Use dynamic quando quiser uma inferência
de tipo em tempo de execução. Para obter mais informações, confira Como usar o
tipo dinâmico (Guia de Programação do C#).

Use a digitação implícita para a variável de loop em loops for.

O exemplo a seguir usa digitação implícita em uma instrução for .

C#

var phrase =
"lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala";
var manyPhrases = new StringBuilder();
for (var i = 0; i < 10000; i++)
{
manyPhrases.Append(phrase);
}
//Console.WriteLine("tra" + manyPhrases);
Não use a digitação implícita para determinar o tipo da variável em loop nos loops
foreach. Na maioria dos casos, o tipo de elementos na coleção não é
imediatamente evidente. O nome da coleção não deve ser usado apenas para
inferir o tipo dos elementos.

O exemplo a seguir usa digitação explícita em uma instrução foreach .

C#

foreach (char ch in laugh)


{
if (ch == 'h')
{
Console.Write("H");
}
else
{
Console.Write(ch);
}
}
Console.WriteLine();

use o tipo implícito para as sequências de resultados em consultas LINQ. A seção


em LINQ explica que muitas consultas LINQ resultam em tipos anônimos em que
tipos implícitos devem ser usados. Outras consultas resultam em tipos genéricos
aninhados em var que é mais legível.

7 Observação

Tenha cuidado para não alterar acidentalmente um tipo de elemento da


coleção iterável. Por exemplo, é fácil alternar de System.Linq.IQueryable para
System.Collections.IEnumerable em uma instrução foreach , o que altera a
execução de uma consulta.

Alguns de nossos exemplos explicam o tipo natural de uma expressão. Esses exemplos
devem usar var para que o compilador escolha o tipo natural. Embora esses exemplos
sejam menos óbvios, o uso de var é necessário para o exemplo. O texto deve explicar o
comportamento.

Coloque as diretivas “using” fora da declaração de


namespace
Quando uma diretiva using está fora de uma declaração de namespace, esse
namespace importado é seu nome totalmente qualificado. O nome totalmente
qualificado é mais claro. Quando a diretiva using está dentro do namespace, ela pode
ser relativa a esse namespace ou ao seu nome totalmente qualificado.

C#

using Azure;

namespace CoolStuff.AwesomeFeature
{
public class Awesome
{
public void Stuff()
{
WaitUntil wait = WaitUntil.Completed;
// ...
}
}
}

Supondo que haja uma referência (direta ou indireta) à classe WaitUntil.

Agora, vamos fazer uma pequena alteração:

C#

namespace CoolStuff.AwesomeFeature
{
using Azure;

public class Awesome


{
public void Stuff()
{
WaitUntil wait = WaitUntil.Completed;
// ...
}
}
}

E ela será compilada hoje. E amanhã. Mas, em algum momento da próxima semana, o
código anterior (intocado) falha com dois erros:

Console

- error CS0246: The type or namespace name 'WaitUntil' could not be found
(are you missing a using directive or an assembly reference?)
- error CS0103: The name 'WaitUntil' does not exist in the current context

Uma das dependências introduziu esta classe em um namespace e termina com .Azure :

C#

namespace CoolStuff.Azure
{
public class SecretsManagement
{
public string FetchFromKeyVault(string vaultId, string secretId) {
return null; }
}
}

Uma diretiva using colocada dentro de um namespace diferencia contexto e complica a


resolução do nome. Neste exemplo, é o primeiro namespace que ela encontra.

CoolStuff.AwesomeFeature.Azure

CoolStuff.Azure
Azure

Adicionar um novo namespace que corresponda a CoolStuff.Azure ou


CoolStuff.AwesomeFeature.Azure seria combinado antes do namespace Azure global.

Você poderia resolver isso adicionando o modificador global:: à declaração using . No


entanto, é mais fácil colocar declarações using fora do namespace.

C#

namespace CoolStuff.AwesomeFeature
{
using global::Azure;

public class Awesome


{
public void Stuff()
{
WaitUntil wait = WaitUntil.Completed;
// ...
}
}
}

Diretrizes de estilo
Em geral, use o seguinte formato para exemplos de código:

Use quatro espaços para recuo. Não use guias.


Alinhe o código consistentemente para melhorar a legibilidade.
Limite linhas a 65 caracteres para aprimorar a legibilidade de código em
documentos, especialmente em telas de dispositivos móveis.
Divida instruções longas em várias linhas para melhorar a clareza.
Use o estilo "Allman" para chaves: abra e feche sua própria nova linha. As chaves
se alinham com o nível de recuo atual.
As quebras de linha devem ocorrer antes dos operadores binários, se necessário.

Estilo de comentário
Use comentários de linha única ( // ) para breves explicações.

Evite comentários de várias linhas ( /* */ ) para explicações mais longas.


Os comentários nos exemplos de código não são localizados. Isso significa que as
explicações incorporadas no código não serão traduzidas. Um texto explicativo
mais longo deve ser colocado no artigo complementar, para que possa ser
localizado.

Para descrever métodos, classes, campos e todos os membros públicos, use


comentários XML.

Coloque o comentário em uma linha separada, não no final de uma linha de


código.

Comece o texto do comentário com uma letra maiúscula.

Termine o texto do comentário com um ponto final.

Insira um espaço entre o delimitador de comentário ( // ) e o texto do comentário,


conforme mostrado no exemplo a seguir.

C#

// The following declaration creates a query. It does not run


// the query.

Convenções de layout
Um bom layout usa formatação para enfatizar a estrutura do código e para facilitar a
leitura de código. Exemplos e amostras Microsoft estão em conformidade com as
seguintes convenções:

Use as configurações padrão do Editor de códigos (recuo inteligente, recuos de


quatro caracteres, guias salvas como espaços). Para obter mais informações,
consulte Opções, Editor de Texto, C#, Formatação.

Gravar apenas uma instrução por linha.

Gravar apenas uma declaração por linha.

Se as linhas de continuação não forem recuadas automaticamente, recue-as uma


parada de tabulação (quatro espaços).

Adicione pelo menos uma linha em branco entre as definições de método e de


propriedade.

Use parênteses para criar cláusulas em uma expressão aparente, conforme


mostrado no código a seguir.

C#

if ((startX > endX) && (startX > previousX))


{
// Take appropriate action.
}

As exceções são quando o exemplo explica o operador ou a precedência de expressão.

Segurança
Siga as diretrizes em Diretrizes de codificação segura.
Como exibir argumentos de linha de
comando
Artigo • 08/06/2023

Os argumentos fornecidos a um executável na linha de comando são acessíveis em


instruções de nível superior ou por meio de um parâmetro opcional para Main . Os
argumentos são fornecidos na forma de uma matriz de cadeias de caracteres. Cada
elemento da matriz contém um argumento. O espaço em branco entre os argumentos é
removido. Por exemplo, considere essas invocações de linha de comando de um
executável fictício:

Entrada na linha de comando Matriz de cadeias de caracteres passada a Main

executável.exe a b c "a"

"b"

"c"

executável.exe um dois "um"

"dois"

executável.exe "um dois" três "one two"

"three"

7 Observação

Quando estiver executando um aplicativo no Visual Studio, você pode especificar


argumentos de linha de comando na Página de depuração, Designer de Projeto.

Exemplo
Este exemplo exibe os argumentos de linha de comando passados a um aplicativo de
linha de comando. A saída mostrada é para a primeira entrada da tabela acima.

C#

// The Length property provides the number of array elements.


Console.WriteLine($"parameter count = {args.Length}");
for (int i = 0; i < args.Length; i++)
{
Console.WriteLine($"Arg[{i}] = [{args[i]}]");
}

/* Output (assumes 3 cmd line args):


parameter count = 3
Arg[0] = [a]
Arg[1] = [b]
Arg[2] = [c]
*/

Confira também
Visão geral de System.CommandLine
Tutorial: introdução a System.CommandLine
Explorar programação orientada a
objeto com classes e objetos
Artigo • 02/06/2023

Neste tutorial, você vai compilar um aplicativo de console e ver os recursos básicos
orientados a objeto que fazem parte da linguagem C#.

Pré-requisitos
Recomendamos o Visual Studio para Windows ou Mac. Você pode baixar uma
versão gratuita na página de downloads do Visual Studio . O Visual Studio inclui
o SDK do .NET.
Você também pode usar o editor do Visual Studio Code . Será preciso instalar o
SDK do .NET mais recente separadamente.
Se preferir outro editor, você precisará instalar o SDK do .NET mais recente.

Criar o aplicativo
Usando uma janela de terminal, crie um diretório chamado classes. Você compilará o
aplicativo nesse diretório. Altere para esse diretório e digite dotnet new console na
janela do console. Esse comando cria o aplicativo. Abra Program.cs. Ele deverá ser
parecido com:

C#

// See https://aka.ms/new-console-template for more information


Console.WriteLine("Hello, World!");

Neste tutorial, você criará novos tipos que representam uma conta bancária.
Normalmente, os desenvolvedores definem cada classe em um arquivo de texto
diferente. Isso facilita o gerenciamento à medida que o tamanho do programa aumenta.
Crie um novo arquivo chamado BankAccount.cs no diretório Classes.

Esse arquivo conterá a definição de uma conta bancária. A programação Orientada a


Objetos organiza o código por meio da criação de tipos na forma de classes. Essas
classes contêm o código que representa uma entidade específica. A classe BankAccount
representa uma conta bancária. O código implementa operações específicas por meio
de métodos e propriedades. Neste tutorial, a conta bancária dá suporte a este
comportamento:
1. Ela tem um número com 10 dígitos que identifica exclusivamente a conta bancária.
2. Ela tem uma cadeia de caracteres que armazena o nome ou os nomes dos
proprietários.
3. O saldo pode ser recuperado.
4. Ela aceita depósitos.
5. Ele aceita saques.
6. O saldo inicial deve ser positivo.
7. Os saques não podem resultar em um saldo negativo.

Definir o tipo de conta bancária


Você pode começar criando as noções básicas de uma classe que define esse
comportamento. Crie um novo arquivo usando o comando File:New. Nomeie-o
bankAccount.cs. Adicione o seguinte código ao arquivo BankAccount.cs:

C#

namespace Classes;

public class BankAccount


{
public string Number { get; }
public string Owner { get; set; }
public decimal Balance { get; }

public void MakeDeposit(decimal amount, DateTime date, string note)


{
}

public void MakeWithdrawal(decimal amount, DateTime date, string note)


{
}
}

Antes de continuar, vamos dar uma olhada no que você compilou. A declaração
namespace fornece uma maneira de organizar logicamente seu código. Este tutorial é

relativamente pequeno, portanto, você colocará todo o código em um namespace.

public class BankAccount define a classe ou o tipo que você está criando. Tudo que
vem dentro de { e } logo após a declaração da classe define o estado e o
comportamento da classe. Há cinco membros na classe BankAccount . Os três primeiros
são propriedades. Propriedades são elementos de dados que podem ter um código que
impõe a validação ou outras regras. Os dois últimos são métodos. Os métodos são
blocos de código que executam uma única função. A leitura dos nomes de cada um dos
membros deve fornecer informações suficientes para você, ou outro desenvolvedor,
entender o que a classe faz.

Abrir uma nova conta


O primeiro recurso a ser implementado serve para abrir uma conta bancária. Quando
um cliente abre uma conta, ele deve fornecer um saldo inicial e informações sobre o
proprietário, ou proprietários, dessa conta.

A criação de novo objeto do tipo BankAccount significa a definição de um construtor


que atribui esses valores. Um construtor é um membro que tem o mesmo nome da
classe. Ele é usado para inicializar objetos desse tipo de classe. Adicione o seguinte
construtor ao tipo BankAccount . Insira o código a seguir acima da declaração de
MakeDeposit :

C#

public BankAccount(string name, decimal initialBalance)


{
this.Owner = name;
this.Balance = initialBalance;
}

O código anterior identifica as propriedades do objeto que está sendo construído,


incluindo o qualificador this . Esse qualificador geralmente é opcional e omitido. Você
também poderia ter escrito:

C#

public BankAccount(string name, decimal initialBalance)


{
Owner = name;
Balance = initialBalance;
}

O qualificador this só é necessário quando uma variável ou parâmetro locais têm o


mesmo nome desse campo ou propriedade. O qualificador this é omitido durante
todo o restante deste artigo, a menos que seja necessário.

Construtores são chamados quando você cria um objeto usando new. Substitua a linha
Console.WriteLine("Hello World!"); no arquivo Program.cs pelo seguinte código

(substitua <name> pelo seu nome):


C#

using Classes;

var account = new BankAccount("<name>", 1000);


Console.WriteLine($"Account {account.Number} was created for {account.Owner}
with {account.Balance} initial balance.");

Vamos executar o que você construiu até agora. Se você estiver usando o Visual Studio,
selecione Iniciar sem depuração no menu Depurar. Se estiver usando uma linha de
comando, digite dotnet run no diretório em que criou seu projeto.

Você notou que o número da conta está em branco? É hora de corrigir isso. O número
da conta deve ser atribuído na construção do objeto. Mas não é responsabilidade do
chamador criá-lo. O código da classe BankAccount deve saber como atribuir novos
números de conta. Uma maneira simples de fazer isso é começar com um número de 10
dígitos. Incremente-o à medida que novas contas são criadas. Por fim, armazene o
número da conta atual quando um objeto for construído.

Adicione uma declaração de membro à classe BankAccount . Coloque a seguinte linha de


código após a chave de abertura { no início da classe BankAccount :

C#

private static int accountNumberSeed = 1234567890;

O accountNumberSeed é um membro de dados. Ele é private , o que significa que ele só


pode ser acessado pelo código dentro da classe BankAccount . É uma maneira de separar
as responsabilidades públicas (como ter um número de conta) da implementação
privada (como os números de conta são gerados). Ele também é static , o que significa
que é compartilhado por todos os objetos BankAccount . O valor de uma variável não
estática é exclusivo para cada instância do objeto BankAccount . Adicione as duas linhas a
seguir ao construtor para atribuir o número da conta. Coloque-as depois da linha que
diz this.Balance = initialBalance :

C#

this.Number = accountNumberSeed.ToString();
accountNumberSeed++;

Digite dotnet run para ver os resultados.


Criar depósitos e saques
A classe da conta bancária precisa aceitar depósitos e saques para funcionar
corretamente. Vamos implementar depósitos e saques criando um diário de todas as
transações da conta. Rastrear todas as transações tem algumas vantagens em
comparação à simples atualização do saldo em cada transação. O histórico pode ser
usado para auditar todas as transações e gerenciar os saldos diários. Calcular o saldo do
histórico de todas as transações quando necessário assegura que todos os erros
corrigidos em uma única transação serão refletidos corretamente no saldo no próximo
cálculo.

Vamos começar criando um novo tipo para representar uma transação. Essa transação é
um tipo simples que não tem qualquer responsabilidade. Ele precisa de algumas
propriedades. Crie um novo arquivo chamado Transaction.cs. Adicione os seguintes
códigos a ela:

C#

namespace Classes;

public class Transaction


{
public decimal Amount { get; }
public DateTime Date { get; }
public string Notes { get; }

public Transaction(decimal amount, DateTime date, string note)


{
Amount = amount;
Date = date;
Notes = note;
}
}

Agora, vamos adicionar um List<T> de Transaction objetos à classe BankAccount .


Adicione a seguinte declaração após o construtor no arquivo BankAccount.cs:

C#

private List<Transaction> allTransactions = new List<Transaction>();

Agora, vamos calcular corretamente o Balance . O saldo atual pode ser encontrado
somando os valores de todas as transações. De acordo com a forma atual do código,
você só pode obter o saldo inicial da conta. Portanto, você terá que atualizar a
propriedade Balance . Substitua a linha public decimal Balance { get; } em
BankAccount.cs pelo seguinte código:

C#

public decimal Balance


{
get
{
decimal balance = 0;
foreach (var item in allTransactions)
{
balance += item.Amount;
}

return balance;
}
}

Este exemplo mostra um aspecto importante das propriedades. Agora, você está
calculando o saldo quando outro programador solicita o valor. Seu cálculo enumera
todas as transações e fornece a soma como o saldo atual.

Depois, implemente os métodos MakeDeposit e MakeWithdrawal . Esses métodos vão


impor as duas últimas regras: que o saldo inicial deve ser positivo, e que qualquer saque
não pode criar um saldo negativo.

Essas regras introduzem o conceito de exceções. A forma padrão de indicar que um


método não pode concluir seu trabalho com êxito é lançar uma exceção. O tipo de
exceção e a mensagem associada a ele descrevem o erro. Aqui, o método MakeDeposit
lançará uma exceção se o valor do depósito for menor ou igual a 0. O método
MakeWithdrawal lançará uma exceção se o valor do saque for menor ou igual a 0 ou se a

aplicação do saque resultar em um saldo negativo. Adicione o seguinte código depois


da declaração da lista allTransactions :

C#

public void MakeDeposit(decimal amount, DateTime date, string note)


{
if (amount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Amount of
deposit must be positive");
}
var deposit = new Transaction(amount, date, note);
allTransactions.Add(deposit);
}
public void MakeWithdrawal(decimal amount, DateTime date, string note)
{
if (amount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Amount of
withdrawal must be positive");
}
if (Balance - amount < 0)
{
throw new InvalidOperationException("Not sufficient funds for this
withdrawal");
}
var withdrawal = new Transaction(-amount, date, note);
allTransactions.Add(withdrawal);
}

A instrução throwgera uma exceção. A execução do bloco atual é encerrada e o controle


transferido para o bloco catch da primeira correspondência encontrado na pilha de
chamadas. Você adicionará um bloco catch para testar esse código um pouco mais
tarde.

O construtor deve receber uma alteração para que adicione uma transação inicial, em
vez de atualizar o saldo diretamente. Como você já escreveu o método MakeDeposit ,
chame-o de seu construtor. O construtor concluído deve ter esta aparência:

C#

public BankAccount(string name, decimal initialBalance)


{
Number = accountNumberSeed.ToString();
accountNumberSeed++;

Owner = name;
MakeDeposit(initialBalance, DateTime.Now, "Initial balance");
}

DateTime.Now é uma propriedade que retorna a data e a hora atuais. Teste esse código
adicionando alguns depósitos e saques em seu método Main , seguindo o código que
cria um novo BankAccount :

C#

account.MakeWithdrawal(500, DateTime.Now, "Rent payment");


Console.WriteLine(account.Balance);
account.MakeDeposit(100, DateTime.Now, "Friend paid me back");
Console.WriteLine(account.Balance);
Em seguida, teste se você está recebendo condições de erro ao tentar criar uma conta
com um saldo negativo. Adicione o seguinte código após o código anterior que você
acabou de adicionar:

C#

// Test that the initial balances must be positive.


BankAccount invalidAccount;
try
{
invalidAccount = new BankAccount("invalid", -55);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine("Exception caught creating account with negative
balance");
Console.WriteLine(e.ToString());
return;
}

Use a instrução try-catch para marcar um bloco de código que possa gerar exceções e
detectar esses erros esperados. Use a mesma técnica a fim de testar o código que gera
uma exceção para um saldo negativo. Adicione o seguinte código antes da declaração
de invalidAccount no seu método Main :

C#

// Test for a negative balance.


try
{
account.MakeWithdrawal(750, DateTime.Now, "Attempt to overdraw");
}
catch (InvalidOperationException e)
{
Console.WriteLine("Exception caught trying to overdraw");
Console.WriteLine(e.ToString());
}

Salve o arquivo e digite dotnet run para testá-lo.

Desafio – registrar em log todas as transações


Para concluir este tutorial, escreva o método GetAccountHistory que cria um string
para o histórico de transações. Adicione esse método ao tipo BankAccount :

C#
public string GetAccountHistory()
{
var report = new System.Text.StringBuilder();

decimal balance = 0;
report.AppendLine("Date\t\tAmount\tBalance\tNote");
foreach (var item in allTransactions)
{
balance += item.Amount;
report.AppendLine($"
{item.Date.ToShortDateString()}\t{item.Amount}\t{balance}\t{item.Notes}");
}

return report.ToString();
}

O histórico usa a classe StringBuilder para formatar uma cadeia de caracteres que
contém uma linha para cada transação. Você viu o código de formatação da cadeia de
caracteres anteriormente nesses tutoriais. Um caractere novo é \t . Ele insere uma guia
para formatar a saída.

Adicione esta linha para testá-la no Program.cs:

C#

Console.WriteLine(account.GetAccountHistory());

Execute o programa para ver os resultados.

Próximas etapas
Se você não conseguir avançar, veja a origem deste tutorial em nosso repositório
GitHub .

Você pode continuar com o tutorial de programação orientada a objeto.

Saiba mais sobre esses conceitos nestes artigos:

Instruções de seleção
Instruções de iteração
Programação orientada a objeto (C#)
Artigo • 08/06/2023

O C# é uma linguagem de programação orientada a objeto. Os quatro princípios


básicos da programação orientada a objetos são:

Abstração Modelando os atributos e interações relevantes de entidades como


classes para definir uma representação abstrata de um sistema.
Encapsulamento Ocultando o estado interno e a funcionalidade de um objeto e
permitindo apenas o acesso por meio de um conjunto público de funções.
Herança Capacidade de criar novas abstrações com base em abstrações existentes.
Polimorfismo Capacidade de implementar propriedades ou métodos herdados de
diferentes maneiras em várias abstrações.

No tutorial anterior, introdução às classes que você viu abstração e encapsulamento. A


classe BankAccount forneceu uma abstração para o conceito de conta bancária. Você
pode modificar sua implementação sem afetar nenhum dos códigos que usaram a
classe BankAccount . As classes BankAccount e Transaction classes fornecem
encapsulamento dos componentes necessários para descrever esses conceitos no
código.

Neste tutorial, você estenderá esse aplicativo para usar herança e polimorfismo para
adicionar novos recursos. Você também adicionará recursos à BankAccount classe,
aproveitando as técnicas de abstração e encapsulamento que aprendeu no tutorial
anterior.

Criar diferentes tipos de contas


Depois de criar este programa, você recebe solicitações para adicionar recursos a ele.
Funciona muito bem na situação em que há apenas um tipo de conta bancária. Ao
longo do tempo, as necessidades são alteradas e os tipos de conta relacionados são
solicitados:

Uma conta de ganho de juros que acumula juros no final de cada mês.
Uma linha de crédito que pode ter saldo negativo, mas quando há saldo, há
cobrança de juros a cada mês.
Uma conta de cartão presente pré-paga que começa com um único depósito, e só
pode ser paga. Ela pode ser preenchida novamente uma vez no início de cada mês.

Todas essas contas diferentes são semelhantes à classe BankAccount definida no tutorial
anterior. Você pode copiar esse código, renomear as classes e fazer modificações. Essa
técnica funcionaria a curto prazo, mas seria mais trabalho ao longo do tempo. Todas as
alterações seriam copiadas em todas as classes afetadas.

Em vez disso, você pode criar novos tipos de conta bancária que herdam métodos e
dados da classe BankAccount criada no tutorial anterior. Essas novas classes podem
estender a classe BankAccount com o comportamento específico necessário para cada
tipo:

C#

public class InterestEarningAccount : BankAccount


{
}

public class LineOfCreditAccount : BankAccount


{
}

public class GiftCardAccount : BankAccount


{
}

Cada uma dessas classes herda o comportamento compartilhado de sua classe base
compartilhada, a classe BankAccount . Escreva as implementações para funcionalidades
novas e diferentes em cada uma das classes derivadas. Essas classes derivadas já têm
todo o comportamento definido na classe BankAccount .

É uma boa prática criar cada nova classe em um arquivo de origem diferente. No Visual
Studio , você pode clicar com o botão direito do mouse no projeto e selecionar
adicionar classe para adicionar uma nova classe em um novo arquivo. No Visual Studio
Code , selecione Arquivo e Novo para criar um novo arquivo de origem. Em qualquer
ferramenta, nomeie o arquivo para corresponder à classe: InterestEarningAccount.cs,
LineOfCreditAccount.cs e GiftCardAccount.cs.

Ao criar as classes, conforme mostrado no exemplo anterior, você descobrirá que


nenhuma das suas classes derivadas é compilada. Um construtor é responsável por
inicializar um objeto. Um construtor de classe derivada deve inicializar a classe derivada
e fornecer instruções sobre como inicializar o objeto de classe base incluído na classe
derivada. A inicialização adequada normalmente ocorre sem nenhum código extra. A
classe BankAccount declara um construtor público com a seguinte assinatura:

C#

public BankAccount(string name, decimal initialBalance)


O compilador não gera um construtor padrão quando você define um construtor por
conta própria. Isso significa que cada classe derivada deve chamar explicitamente esse
construtor. Você declara um construtor que pode passar argumentos para o construtor
de classe base. O código a seguir mostra o construtor para o InterestEarningAccount :

C#

public InterestEarningAccount(string name, decimal initialBalance) :


base(name, initialBalance)
{
}

Os parâmetros para esse novo construtor correspondem ao tipo de parâmetro e aos


nomes do construtor de classe base. Use a sintaxe : base() para indicar uma chamada
para um construtor de classe base. Algumas classes definem vários construtores e essa
sintaxe permite que você escolha qual construtor de classe base você chama. Depois de
atualizar os construtores, você pode desenvolver o código para cada uma das classes
derivadas. Os requisitos para as novas classes podem ser declarados da seguinte
maneira:

Uma conta de ganho de juros:


Obterá um crédito de 2% do saldo final do mês.
Uma linha de crédito:
Pode ter um saldo negativo, mas não ser maior em valor absoluto do que o
limite de crédito.
Incorrerá em uma cobrança de juros a cada mês em que o saldo de fim de mês
não seja 0.
Incorrerá em uma taxa em cada saque que ultrapassa o limite de crédito.
Uma conta de cartão presente:
Pode ser preenchido novamente com um valor especificado uma vez por mês,
no último dia do mês.

Você pode ver que todos os três tipos de conta têm uma ação que ocorre no final de
cada mês. No entanto, cada tipo de conta faz tarefas diferentes. Você usa polimorfismo
para implementar esse código. Crie um único método virtual na classe BankAccount :

C#

public virtual void PerformMonthEndTransactions() { }

O código anterior mostra como você usa a palavra-chave virtual para declarar um
método na classe base para o qual uma classe derivada pode fornecer uma
implementação diferente. Um método virtual é um método em que qualquer classe
derivada pode optar por reimplementar. As classes derivadas usam a palavra-chave
override para definir a nova implementação. Normalmente, você se refere a isso como

"substituindo a implementação da classe base". A palavra-chave virtual especifica que


as classes derivadas podem substituir o comportamento. Você também pode declarar
métodos abstract em que classes derivadas devem substituir o comportamento. A
classe base não fornece uma implementação para um método abstract . Em seguida,
você precisa definir a implementação para duas das novas classes que você criou. Inicie
com um InterestEarningAccount :

C#

public override void PerformMonthEndTransactions()


{
if (Balance > 500m)
{
decimal interest = Balance * 0.05m;
MakeDeposit(interest, DateTime.Now, "apply monthly interest");
}
}

Adicione o seguinte código ao LineOfCreditAccount . O código nega o saldo para


calcular uma taxa de juros positiva que é retirada da conta:

C#

public override void PerformMonthEndTransactions()


{
if (Balance < 0)
{
// Negate the balance to get a positive interest charge:
decimal interest = -Balance * 0.07m;
MakeWithdrawal(interest, DateTime.Now, "Charge monthly interest");
}
}

A classe GiftCardAccount precisa de duas alterações para implementar sua


funcionalidade de fim de mês. Primeiro, modifique o construtor para incluir um valor
opcional a ser adicionado a cada mês:

C#

private readonly decimal _monthlyDeposit = 0m;

public GiftCardAccount(string name, decimal initialBalance, decimal


monthlyDeposit = 0) : base(name, initialBalance)
=> _monthlyDeposit = monthlyDeposit;

O construtor fornece um valor padrão para o valor monthlyDeposit para que os


chamadores possam omitir um 0 sem depósito mensal. Em seguida, substitua o
método PerformMonthEndTransactions para adicionar o depósito mensal, se ele foi
definido como um valor diferente de zero no construtor:

C#

public override void PerformMonthEndTransactions()


{
if (_monthlyDeposit != 0)
{
MakeDeposit(_monthlyDeposit, DateTime.Now, "Add monthly deposit");
}
}

A substituição aplica o conjunto de depósito mensal no construtor. Adicione o seguinte


código ao método Main para testar essas alterações para GiftCardAccount e
InterestEarningAccount :

C#

var giftCard = new GiftCardAccount("gift card", 100, 50);


giftCard.MakeWithdrawal(20, DateTime.Now, "get expensive coffee");
giftCard.MakeWithdrawal(50, DateTime.Now, "buy groceries");
giftCard.PerformMonthEndTransactions();
// can make additional deposits:
giftCard.MakeDeposit(27.50m, DateTime.Now, "add some additional spending
money");
Console.WriteLine(giftCard.GetAccountHistory());

var savings = new InterestEarningAccount("savings account", 10000);


savings.MakeDeposit(750, DateTime.Now, "save some money");
savings.MakeDeposit(1250, DateTime.Now, "Add more savings");
savings.MakeWithdrawal(250, DateTime.Now, "Needed to pay monthly bills");
savings.PerformMonthEndTransactions();
Console.WriteLine(savings.GetAccountHistory());

Verifique os resultados. Agora, adicione um conjunto semelhante de código de teste


para LineOfCreditAccount :

C#

var lineOfCredit = new LineOfCreditAccount("line of credit", 0);


// How much is too much to borrow?
lineOfCredit.MakeWithdrawal(1000m, DateTime.Now, "Take out monthly
advance");
lineOfCredit.MakeDeposit(50m, DateTime.Now, "Pay back small amount");
lineOfCredit.MakeWithdrawal(5000m, DateTime.Now, "Emergency funds for
repairs");
lineOfCredit.MakeDeposit(150m, DateTime.Now, "Partial restoration on
repairs");
lineOfCredit.PerformMonthEndTransactions();
Console.WriteLine(lineOfCredit.GetAccountHistory());

Ao adicionar o código anterior e executar o programa, você verá algo semelhante ao


seguinte erro:

Console

Unhandled exception. System.ArgumentOutOfRangeException: Amount of deposit


must be positive (Parameter 'amount')
at OOProgramming.BankAccount.MakeDeposit(Decimal amount, DateTime date,
String note) in BankAccount.cs:line 42
at OOProgramming.BankAccount..ctor(String name, Decimal initialBalance)
in BankAccount.cs:line 31
at OOProgramming.LineOfCreditAccount..ctor(String name, Decimal
initialBalance) in LineOfCreditAccount.cs:line 9
at OOProgramming.Program.Main(String[] args) in Program.cs:line 29

7 Observação

A saída real inclui o caminho completo para a pasta com o projeto. Os nomes das
pastas foram omitidos para brevidade. Além disso, dependendo do formato de
código, os números de linha podem ser ligeiramente diferentes.

Esse código falha porque BankAccount pressupõe que o saldo inicial deve ser maior que
0. Outra suposição feita na classe BankAccount é que o saldo não pode ficar negativo.
Em vez disso, qualquer retirada que sobrecarrega a conta é rejeitada. Ambas as
suposições precisam mudar. A linha de conta de crédito começa em 0 e geralmente terá
um saldo negativo. Além disso, se um cliente empresta muito dinheiro, ele incorre em
uma taxa. A transação é aceita, só custa mais. A primeira regra pode ser implementada
adicionando um argumento opcional ao construtor BankAccount que especifica o saldo
mínimo. O padrão é 0 . A segunda regra requer um mecanismo que permite que classes
derivadas modifiquem o algoritmo padrão. De certa forma, a classe base "pergunta" ao
tipo derivado o que deve acontecer quando há um cheque especial. O comportamento
padrão é rejeitar a transação lançando uma exceção.
Vamos começar adicionando um segundo construtor que inclui um parâmetro
minimumBalance opcional. Esse novo construtor faz todas as ações feitas pelo construtor
existente. Além disso, ele define a propriedade de saldo mínimo. Você pode copiar o
corpo do construtor existente, mas isso significa dois locais a serem alterados no futuro.
Em vez disso, você pode usar o encadeamento de construtor para que um construtor
chame outro. O código a seguir mostra os dois construtores e o novo campo adicional:

C#

private readonly decimal _minimumBalance;

public BankAccount(string name, decimal initialBalance) : this(name,


initialBalance, 0) { }

public BankAccount(string name, decimal initialBalance, decimal


minimumBalance)
{
Number = s_accountNumberSeed.ToString();
s_accountNumberSeed++;

Owner = name;
_minimumBalance = minimumBalance;
if (initialBalance > 0)
MakeDeposit(initialBalance, DateTime.Now, "Initial balance");
}

O código anterior mostra duas novas técnicas. Primeiro, o campo minimumBalance é


marcado como readonly . Isso significa que o valor não pode ser alterado depois que o
objeto é construído. Depois que BankAccount é criado, minimumBalance não pode alterar.
Em segundo lugar, o construtor que usa dois parâmetros : this(name, initialBalance,
0) { } como implementação. A expressão : this() chama o outro construtor, aquele
com três parâmetros. Essa técnica permite que você tenha uma única implementação
para inicializar um objeto, embora o código do cliente possa escolher um dos muitos
construtores.

Essa implementação chamará MakeDeposit somente se o saldo inicial for maior que 0 .
Isso preserva a regra de que os depósitos devem ser positivos, mas permite que a conta
de crédito abra com um saldo 0 .

Agora que a classe BankAccount tem um campo somente leitura para o saldo mínimo, a
alteração final é alterar o código físico 0 para minimumBalance no método
MakeWithdrawal :

C#
if (Balance - amount < minimumBalance)

Depois de estender a classe BankAccount , você pode modificar o construtor


LineOfCreditAccount para chamar o novo construtor base, conforme mostrado no
seguinte código:

C#

public LineOfCreditAccount(string name, decimal initialBalance, decimal


creditLimit) : base(name, initialBalance, -creditLimit)
{
}

Observe que o construtor LineOfCreditAccount altera o sinal do parâmetro creditLimit


para que corresponda ao significado do parâmetro minimumBalance .

Regras de cheque especial diferentes


O último recurso a ser adicionado permite que LineOfCreditAccount cobre uma taxa por
ultrapassar o limite de crédito em vez de recusar a transação.

Uma técnica é definir uma função virtual na qual você implementa o comportamento
necessário. A classe BankAccount refatora o método MakeWithdrawal em dois métodos.
O novo método faz a ação especificada quando o saque leva o saldo abaixo do mínimo.
O método existente MakeWithdrawal tem o seguinte código:

C#

public void MakeWithdrawal(decimal amount, DateTime date, string note)


{
if (amount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Amount of
withdrawal must be positive");
}
if (Balance - amount < _minimumBalance)
{
throw new InvalidOperationException("Not sufficient funds for this
withdrawal");
}
var withdrawal = new Transaction(-amount, date, note);
allTransactions.Add(withdrawal);
}
Substitua-o pelo seguinte código:

C#

public void MakeWithdrawal(decimal amount, DateTime date, string note)


{
if (amount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Amount of
withdrawal must be positive");
}
Transaction? overdraftTransaction = CheckWithdrawalLimit(Balance -
amount < _minimumBalance);
Transaction? withdrawal = new(-amount, date, note);
_allTransactions.Add(withdrawal);
if (overdraftTransaction != null)
_allTransactions.Add(overdraftTransaction);
}

protected virtual Transaction? CheckWithdrawalLimit(bool isOverdrawn)


{
if (isOverdrawn)
{
throw new InvalidOperationException("Not sufficient funds for this
withdrawal");
}
else
{
return default;
}
}

O método adicionado é protected , o que significa que ele pode ser chamado apenas de
classes derivadas. Essa declaração impede que outros clientes chamem o método. É
também virtual para que classes derivadas possam alterar o comportamento. O tipo
de retorno é Transaction? . A anotação ? indica que o método pode retornar null .
Adicione a seguinte implementação para LineOfCreditAccount cobrar uma taxa quando
o limite de saque for excedido:

C#

protected override Transaction? CheckWithdrawalLimit(bool isOverdrawn) =>


isOverdrawn
? new Transaction(-20, DateTime.Now, "Apply overdraft fee")
: default;

A substituição retorna uma transação de taxa quando a conta é sacada. Se o saque não
ultrapassar o limite, o método retornará uma transação null . Isso indica que não há
nenhuma taxa. Teste essas alterações adicionando o seguinte código ao seu método
Main na classe Program :

C#

var lineOfCredit = new LineOfCreditAccount("line of credit", 0, 2000);


// How much is too much to borrow?
lineOfCredit.MakeWithdrawal(1000m, DateTime.Now, "Take out monthly
advance");
lineOfCredit.MakeDeposit(50m, DateTime.Now, "Pay back small amount");
lineOfCredit.MakeWithdrawal(5000m, DateTime.Now, "Emergency funds for
repairs");
lineOfCredit.MakeDeposit(150m, DateTime.Now, "Partial restoration on
repairs");
lineOfCredit.PerformMonthEndTransactions();
Console.WriteLine(lineOfCredit.GetAccountHistory());

Execute o programa e verifique os resultados.

Resumo
Se você não conseguir avançar, veja a origem deste tutorial em nosso repositório
GitHub .

Este tutorial demonstrou muitas das técnicas usadas na programação Orientada por
objeto:

Você usou Abstração quando definiu classes para cada um dos diferentes tipos de
conta. Essas classes descreveram o comportamento desse tipo de conta.
Você usou Encapsulamento quando manteve muitos detalhes private em cada
classe.
Você usou Herança quando aproveitou a implementação já criada na classe para
salvar o código BankAccount .
Você usou Polimorfismo ao criar métodos virtual que as classes derivadas
poderiam substituir para criar um comportamento específico para esse tipo de
conta.
Herança em C# e .NET
Artigo • 02/06/2023

Este tutorial apresenta a herança em C#. Herança é um recurso das linguagens de


programação orientadas a objeto que permite a definição de uma classe base que, por
sua vez, fornece uma funcionalidade específica (dados e comportamento), e a definição
de classes derivadas que herdam ou substituem essa funcionalidade.

Pré-requisitos
Recomendamos o Visual Studio para Windows ou Mac. Você pode baixar uma
versão gratuita na página de downloads do Visual Studio . O Visual Studio inclui
o SDK do .NET.
Você também pode usar o editor do Visual Studio Code . Será preciso instalar o
SDK do .NET mais recente separadamente.
Se preferir outro editor, você precisará instalar o SDK do .NET mais recente.

Como executar os exemplos


Para criar e executar os exemplos neste tutorial, use o utilitário dotnet na linha de
comando. Execute estas etapas para cada exemplo:

1. Crie um diretório para armazenar o exemplo.

2. Insira o comando dotnet new console no prompt de comando para criar um novo
projeto do .NET Core.

3. Copie e cole o código do exemplo em seu editor de código.

4. Insira o comando dotnet restore na linha de comando para carregar ou restaurar


as dependências do projeto.

Não é necessário executar dotnet restore, pois ele é executado implicitamente por
todos os comandos que exigem uma restauração, como dotnet new , dotnet build ,
dotnet run , dotnet test , dotnet publish e dotnet pack . Para desabilitar a
restauração implícita, use a opção --no-restore .

O comando dotnet restore ainda é útil em determinados cenários em que realizar


uma restauração explícita faz sentido, como compilações de integração contínua
no Azure DevOps Services ou em sistemas de compilação que precisam controlar
explicitamente quando a restauração ocorrerá.
Para obter informações sobre como gerenciar feeds do NuGet, confira a
documentação do dotnet restore.

5. Insira o comando dotnet run para compilar e executar o exemplo.

Informações: O que é a herança?


Herança é um dos atributos fundamentais da programação orientada a objeto. Ela
permite que você defina uma classe filha que reutiliza (herda), estende ou modifica o
comportamento de uma classe pai. A classe cujos membros são herdados é chamada de
classe base. A classe que herda os membros da classe base é chamada de classe
derivada.

C# e .NET oferecem suporte apenas à herança única. Ou seja, uma classe pode herdar
apenas de uma única classe. No entanto, a herança é transitiva, o que permite que você
defina uma hierarquia de herança para um conjunto de tipos. Em outras palavras, o tipo
D pode herdar do tipo C , que herda do tipo B , que herda do tipo de classe base A .

Como a herança é transitiva, os membros do tipo A estão disponíveis ao tipo D .

Nem todos os membros de uma classe base são herdados por classes derivadas. Os
membros a seguir não são herdados:

Construtores estáticos, que inicializam os dados estáticos de uma classe.

Construtores de instância, que você chama para criar uma nova instância da classe.
Cada classe deve definir seus próprios construtores.

Finalizadores, que são chamados pelo coletor de lixo do runtime para destruir as
instâncias de uma classe.

Enquanto todos os outros membros de uma classe base são herdados por classes
derivadas, o fato de serem visíveis ou não depende de sua acessibilidade. A
acessibilidade de um membro afeta sua visibilidade para classes derivadas da seguinte
maneira:

Membros Privados são visíveis apenas em classes derivadas que estão aninhadas
em sua classe base. Caso contrário, eles não são visíveis em classes derivadas. No
exemplo a seguir, A.B é uma classe aninhada derivada de A , e C deriva de A . O
campo particular A._value é visível em A.B. No entanto, se você remover os
comentários do método C.GetValue e tentar compilar o exemplo, ele produzirá
um erro do compilador CS0122: "'A._value' está inacessível em virtude do nível de
proteção dele".
C#

public class A
{
private int _value = 10;

public class B : A
{
public int GetValue()
{
return _value;
}
}
}

public class C : A
{
// public int GetValue()
// {
// return _value;
// }
}

public class AccessExample


{
public static void Main(string[] args)
{
var b = new A.B();
Console.WriteLine(b.GetValue());
}
}
// The example displays the following output:
// 10

Membros Protegidos são visíveis apenas em classes derivadas.

Membros Internos são visíveis apenas em classes derivadas localizadas no mesmo


assembly que a classe base. Eles não são visíveis em classes derivadas localizadas
em um assembly diferente da classe base.

Membros Públicos são visíveis em classes derivadas e fazem parte da interface


pública da classe derivada. Os membros públicos herdados podem ser chamados
como se estivessem definidos na classe derivada. No exemplo a seguir, a classe A
define um método chamado Method1 , e a classe B herda da classe A . Depois, o
exemplo chama Method1 como se fosse um método de instância em B .

C#

public class A
{
public void Method1()
{
// Method implementation.
}
}

public class B : A
{ }

public class Example


{
public static void Main()
{
B b = new ();
b.Method1();
}
}

Classes derivadas também podem substituir membros herdados fornecendo uma


implementação alternativa. Para poder substituir um membro, o membro na classe base
deve ser marcado com a palavra-chave virtual. Por padrão, os membros da classe base
não são marcados como virtual e não podem ser substituídos. A tentativa de substituir
um membro não virtual, como o seguinte exemplo faz, gera o erro do compilador
CS0506: "O <membro> não pode substituir o membro herdado <membro>, pois não
está marcado como virtual, abstrato ou de substituição."

C#

public class A
{
public void Method1()
{
// Do something.
}
}

public class B : A
{
public override void Method1() // Generates CS0506.
{
// Do something else.
}
}

Em alguns casos, uma classe derivada deve substituir a implementação da classe base.
Membros de classe base marcados com a palavra-chave abstrato exigem que as classes
derivadas os substituam. A tentativa de compilar o exemplo a seguir gera um erro do
compilador CS0534, a "<classe> não implementa o membro abstrato herdado
<membro>", pois a classe B não fornece uma implementação para A.Method1 .

C#

public abstract class A


{
public abstract void Method1();
}

public class B : A // Generates CS0534.


{
public void Method3()
{
// Do something.
}
}

A herança se aplica apenas a classes e interfaces. Outras categorias de tipo (structs,


delegados e enumerações) não dão suporte à herança. Devido a essas regras, tentar
compilar código como o seguinte exemplo, produz o erro do compilador CS0527: "Tipo
'ValueType' na lista de interfaces não é uma interface". A mensagem de erro indica que,
embora você possa definir as interfaces que um struct implementa, não há suporte para
a herança.

C#

public struct ValueStructure : ValueType // Generates CS0527.


{
}

Herança implícita
Apesar dos tipos possíveis de herança por meio de herança única, todos os tipos no
sistema de tipo .NET herdam implicitamente de Object ou de um tipo derivado dele. A
funcionalidade comum de Object está disponível para qualquer tipo.

Para ver o que significa herança implícita, vamos definir uma nova classe, SimpleClass ,
que é simplesmente uma definição de classe vazia:

C#

public class SimpleClass


{ }
É possível usar reflexão (o que permite inspecionar os metadados de um tipo para obter
informações sobre esse tipo) para obter uma lista dos membros que pertencem ao tipo
SimpleClass . Embora você ainda não tenha definido membros na classe SimpleClass , a

saída do exemplo indica que, na verdade, ela tem nove membros. Um desses membros
é um construtor sem parâmetros (ou padrão) que é fornecido automaticamente para o
tipo SimpleClass pelo compilador de C#. Os oito são membros do Object, o tipo do
qual todas as classes e interfaces no sistema do tipo .NET herdam implicitamente.

C#

using System.Reflection;

public class SimpleClassExample


{
public static void Main()
{
Type t = typeof(SimpleClass);
BindingFlags flags = BindingFlags.Instance | BindingFlags.Static |
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.FlattenHierarchy;
MemberInfo[] members = t.GetMembers(flags);
Console.WriteLine($"Type {t.Name} has {members.Length} members: ");
foreach (MemberInfo member in members)
{
string access = "";
string stat = "";
var method = member as MethodBase;
if (method != null)
{
if (method.IsPublic)
access = " Public";
else if (method.IsPrivate)
access = " Private";
else if (method.IsFamily)
access = " Protected";
else if (method.IsAssembly)
access = " Internal";
else if (method.IsFamilyOrAssembly)
access = " Protected Internal ";
if (method.IsStatic)
stat = " Static";
}
string output = $"{member.Name} ({member.MemberType}): {access}
{stat}, Declared by {member.DeclaringType}";
Console.WriteLine(output);
}
}
}
// The example displays the following output:
// Type SimpleClass has 9 members:
// ToString (Method): Public, Declared by System.Object
// Equals (Method): Public, Declared by System.Object
// Equals (Method): Public Static, Declared by System.Object
// ReferenceEquals (Method): Public Static, Declared by System.Object
// GetHashCode (Method): Public, Declared by System.Object
// GetType (Method): Public, Declared by System.Object
// Finalize (Method): Internal, Declared by System.Object
// MemberwiseClone (Method): Internal, Declared by System.Object
// .ctor (Constructor): Public, Declared by SimpleClass

A herança implícita da classe Object torna esses métodos disponíveis para a classe
SimpleClass :

O método ToString público, que converte um objeto SimpleClass em sua


representação de cadeia de caracteres, retorna o nome de tipo totalmente
qualificado. Nesse caso, o método ToString retorna a cadeia de caracteres
"SimpleClass".

Três métodos de teste de igualdade de dois objetos: o método Equals(Object) da


instância pública, o método Equals(Object, Object) do público estático e o
método ReferenceEquals(Object, Object) de público estático. Por padrão, esses
métodos testam a igualdade de referência; ou seja, para que seja iguais, duas
variáveis de objeto devem fazer referência ao mesmo objeto.

O método público GetHashCode , que calcula um valor que permite o uso de uma
instância do tipo em conjuntos de hash.

O método público GetType , que retorna um objeto Type que representa o tipo
SimpleClass .

O método protegido Finalize, que é projetado para liberar recursos não


gerenciados antes que a memória de um objeto seja reivindicada pelo coletor de
lixo.

O método protegido MemberwiseClone, que cria um clone superficial do objeto


atual.

Devido à herança implícita, você pode chamar qualquer membro herdado de um objeto
SimpleClass como se ele fosse, na verdade, um membro definido na classe

SimpleClass . Por exemplo, o exemplo a seguir chama o método SimpleClass.ToString ,

que SimpleClass herda de Object.

C#

public class EmptyClass


{ }
public class ClassNameExample
{
public static void Main()
{
EmptyClass sc = new();
Console.WriteLine(sc.ToString());
}
}
// The example displays the following output:
// EmptyClass

A tabela a seguir lista as categorias de tipos que você pode criar em C#, e os tipos de
onde eles herdam implicitamente. Cada tipo base disponibiliza um conjunto diferente
de membros por meio de herança para tipos derivados implicitamente.

Categoria do tipo Herda implicitamente de

classe Object

struct ValueType, Object

enum Enum, ValueType, Object

delegado MulticastDelegate, Delegate, Object

Herança e um relacionamento "é um(a)"


Normalmente, a herança é usada para expressar um relacionamento "é um(a)" entre
uma classe base e uma ou mais classes derivadas, em que as classes derivadas são
versões especializadas da classe base; a classe derivada é um tipo de classe base. Por
exemplo, a classe Publication representa uma publicação de qualquer tipo e as classes
Book e Magazine representam tipos específicos de publicações.

7 Observação

Uma classe ou struct pode implementar uma ou mais interfaces. Embora a


implementação da interface seja apresentada geralmente como uma alternativa
para herança única, ou como uma forma de usar a herança com structs, ela tem
como objetivo expressar um relacionamento diferente (um relacionamento "pode
fazer") entre uma interface e seu tipo de implementação em comparação com a
herança. Uma interface define um subconjunto de funcionalidades (como a
capacidade de testar a igualdade, comparar ou classificar objetos ou dar suporte à
formatação e análise sensível à cultura) que disponibiliza para seus tipos de
implementação.
Observe que "é um(a)" também expressa o relacionamento entre um tipo e uma
instanciação específica desse tipo. No exemplo a seguir, Automobile é uma classe que
tem três propriedades somente leitura exclusivas: Make , o fabricante do automóvel;
Model , o tipo de automóvel; e Year , o ano de fabricação. A classe Automobile também
tem um construtor cujos argumentos são atribuídos aos valores de propriedade. Ela
também substitui o método Object.ToString para produzir uma cadeia de caracteres que
identifica exclusivamente a instância Automobile em vez da classe Automobile .

C#

public class Automobile


{
public Automobile(string make, string model, int year)
{
if (make == null)
throw new ArgumentNullException(nameof(make), "The make cannot
be null.");
else if (string.IsNullOrWhiteSpace(make))
throw new ArgumentException("make cannot be an empty string or
have space characters only.");
Make = make;

if (model == null)
throw new ArgumentNullException(nameof(model), "The model cannot
be null.");
else if (string.IsNullOrWhiteSpace(model))
throw new ArgumentException("model cannot be an empty string or
have space characters only.");
Model = model;

if (year < 1857 || year > DateTime.Now.Year + 2)


throw new ArgumentException("The year is out of range.");
Year = year;
}

public string Make { get; }

public string Model { get; }

public int Year { get; }

public override string ToString() => $"{Year} {Make} {Model}";


}

Nesse caso, você não deve depender da herança para representar marcas e modelos de
carro específicos. Por exemplo, você não precisa definir um tipo Packard para
representar automóveis fabricados pela empresa Packard Motor Car. Nesse caso, é
possível representá-los criando um objeto Automobile com os valores apropriados
passados ao construtor de classe, como no exemplo a seguir.

C#

using System;

public class Example


{
public static void Main()
{
var packard = new Automobile("Packard", "Custom Eight", 1948);
Console.WriteLine(packard);
}
}
// The example displays the following output:
// 1948 Packard Custom Eight

Um relacionamento é-um(a) baseado na herança é mais bem aplicado a uma classe


base e em classes derivadas que adicionam outros membros à classe base, ou que
exigem funcionalidades adicionais não incluídas na classe base.

Criação da classe base e das classes derivadas


Vamos examinar o processo de criação de uma classe base e de suas classes derivadas.
Nesta seção, você definirá uma classe base, Publication , que representa uma
publicação de qualquer natureza, como um livro, uma revista, um jornal, um diário, um
artigo etc. Você também definirá uma classe Book que deriva de Publication . É possível
estender facilmente o exemplo para definir outras classes derivadas, como Magazine ,
Journal , Newspaper e Article .

A classe base de Publicação


Em projetar a classe Publication , você precisa tomar várias decisões de design:

Quais membros devem ser incluídos na classe base Publication e se os membros


de Publication fornecem implementações de método, ou se Publication é uma
classe base abstrata que funciona como um modelo para suas classes derivadas.

Nesse caso, a classe Publication fornecerá implementações de método. A seção


Criação de classes base abstratas e de suas classes derivadas contém um exemplo
que usa uma classe base abstrata para definir os métodos que as classes derivadas
devem substituir. As classes derivadas são livres para fornecer qualquer
implementação adequada ao tipo derivado.

A capacidade de reutilizar o código (ou seja, várias classes derivadas compartilham


a declaração e a implementação dos métodos de classe base, e não é necessário
substituí-las) é uma vantagem das classes base não abstratas. Portanto, você
deverá adicionar membros à Publication se o código precisar ser compartilhado
por um ou mais tipos Publication especializados. Se você não conseguir fornecer
implementações da classe base de forma eficiente, será necessário fornecer
implementações de membro praticamente idênticas em classes derivadas em vez
de uma única implementação na classe base. A necessidade de manter o código
duplicado em vários locais é uma possível fonte de bugs.

Para maximizar a reutilização do código e criar uma hierarquia de herança lógica e


intuitiva, inclua na classe Publication apenas dos dados e a funcionalidade
comuns a todas as publicações ou à maioria delas. Depois, as classes derivadas
implementam os membros exclusivos a determinados tipos de publicação que eles
representam.

O quanto devemos ampliar a hierarquia de classe. Você deseja desenvolver uma


hierarquia de três ou mais classes, em vez de simplesmente uma classe base e uma
ou mais classes derivadas? Por exemplo, Publication poderia ser uma classe base
de Periodical , que por sua vez é uma classe base de Magazine , Journal e
Newspaper .

Para o seu exemplo, você usará a hierarquia pequena de uma classe Publication e
uma única classe derivada, a Book . É possível ampliar o exemplo facilmente para
criar várias classes adicionais derivadas de Publication , como Magazine e Article .

Se faz sentido instanciar a classe base. Se não fizer, você deverá aplicar a palavra-
chave abstract à classe. Caso contrário, a instância da classe Publication poderá
ser criada chamando seu construtor de classe. Se for feita uma tentativa de criar
uma instância de classe marcada com a palavra-chave abstract por uma chamada
direta ao construtor dela, o compilador C# vai gerar o erro CS0144, "Impossível
criar uma instância da interface ou da classe abstrata". Se for feita uma tentativa de
instanciar a classe usando reflexão, o método de reflexão vai gerar o erro
MemberAccessException.

Por padrão, uma classe base pode ser instanciada chamando seu construtor de
classe. Você não precisa definir um construtor de classe explicitamente. Se não
houver um presente no código-fonte da classe base, o compilador de C# fornecerá
automaticamente um construtor (sem parâmetros) padrão.
No seu exemplo, você marcará a classe Publication como abstract, para que não
seja possível criar uma instância dela. Uma classe abstract sem nenhum método
abstract indica que essa classe representa um conceito abstrato que é

compartilhado entre várias classes concretas (como Book , Journal ).

Se as classes derivadas precisam herdar a implementação de membros específicos


da classe base, se elas têm a opção de substituir a implementação da classe base
ou se precisam fornecer uma implementação. Use a palavra-chave abstract para
forçar as classes derivadas a fornecer uma implementação. Use a palavra-chave
virtual para permitir que as classes derivadas substituam um método de classe
base. Por padrão, os métodos definidos na classe base não são substituíveis.

A classe Publication não tem nenhum método abstract , mas a classe em si é


abstract .

Se uma classe derivada representa a classe final na hierarquia de herança e não


pode se ser usada como uma classe base para outras classes derivadas. Por
padrão, qualquer classe pode servir como classe base. Você pode aplicar a palavra-
chave sealed para indicar que uma classe não pode funcionar como uma classe
base para quaisquer classes adicionais. A tentativa de derivar de uma classe selada
gerou o erro do compilador CS0509, "Não é possível derivar do tipo selado
<typeName>."

No seu exemplo, você marcará a classe derivada como sealed .

O exemplo a seguir mostra o código-fonte para a classe Publication , bem como uma
enumeração PublicationType que é retornada pela propriedade
Publication.PublicationType . Além dos membros herdados de Object, a classe

Publication define os seguintes membros exclusivos e substituições de membro:

C#

public enum PublicationType { Misc, Book, Magazine, Article };

public abstract class Publication


{
private bool _published = false;
private DateTime _datePublished;
private int _totalPages;

public Publication(string title, string publisher, PublicationType type)


{
if (string.IsNullOrWhiteSpace(publisher))
throw new ArgumentException("The publisher is required.");
Publisher = publisher;
if (string.IsNullOrWhiteSpace(title))
throw new ArgumentException("The title is required.");
Title = title;

Type = type;
}

public string Publisher { get; }

public string Title { get; }

public PublicationType Type { get; }

public string? CopyrightName { get; private set; }

public int CopyrightDate { get; private set; }

public int Pages


{
get { return _totalPages; }
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value), "The
number of pages cannot be zero or negative.");
_totalPages = value;
}
}

public string GetPublicationDate()


{
if (!_published)
return "NYP";
else
return _datePublished.ToString("d");
}

public void Publish(DateTime datePublished)


{
_published = true;
_datePublished = datePublished;
}

public void Copyright(string copyrightName, int copyrightDate)


{
if (string.IsNullOrWhiteSpace(copyrightName))
throw new ArgumentException("The name of the copyright holder is
required.");
CopyrightName = copyrightName;

int currentYear = DateTime.Now.Year;


if (copyrightDate < currentYear - 10 || copyrightDate > currentYear
+ 2)
throw new ArgumentOutOfRangeException($"The copyright year must
be between {currentYear - 10} and {currentYear + 1}");
CopyrightDate = copyrightDate;
}

public override string ToString() => Title;


}

Um construtor

Como a classe Publication é abstract , sua instância não pode ser criada
diretamente no código, com no exemplo a seguir:

C#

var publication = new Publication("Tiddlywinks for Experts", "Fun and


Games",
PublicationType.Book);

No entanto, o construtor de instância pode ser chamado diretamente dos


construtores de classe derivada, como mostra o código-fonte para a classe Book .

Duas propriedades relacionadas à publicação

Title é uma propriedade String somente leitura cujo valor é fornecido pela

chamada do construtor Publication .

Pages é uma propriedade Int32 de leitura-gravação que indica o número total de


páginas da publicação. O valor é armazenado em um campo privado chamado
totalPages . O lançamento deve ser de um número positivo ou de um
ArgumentOutOfRangeException.

Membros relacionados ao publicador

Duas propriedades somente leitura, Publisher e Type . Originalmente, os valores


eram fornecidos pela chamada para o construtor da classe Publication .

Membros relacionados à publicação

Dois métodos, Publish e GetPublicationDate , definem e retornam a data de


publicação. O método Publish define um sinalizador particular published como
true quando é chamado e atribui a data passada para ele como argumento ao
campo particular datePublished . O método GetPublicationDate retorna a cadeia
de caracteres "NYP" se o sinalizador published for false , e o valor do campo
datePublished for true .
Membros relacionados a direitos autorais

O método Copyright usa o nome do proprietário dos direitos autorais e o ano dos
direitos autorais como argumentos e os atribui às propriedades CopyrightName e
CopyrightDate .

Uma substituição do método ToString

Se um tipo não substituir o método Object.ToString, ele retornará o nome


totalmente qualificado do tipo, que é de pouca utilidade na diferenciação de uma
instância para outra. A classe Publication substitui Object.ToString para retornar o
valor da propriedade Title .

A figura a seguir ilustra o relacionamento entre a classe base Publication e sua classe
herdada implicitamente Object.

A classe Book
A classe Book representa um livro como tipo especializado de publicação. O exemplo a
seguir mostra o código-fonte para a classe Book .
C#

using System;

public sealed class Book : Publication


{
public Book(string title, string author, string publisher) :
this(title, string.Empty, author, publisher)
{ }

public Book(string title, string isbn, string author, string publisher)


: base(title, publisher, PublicationType.Book)
{
// isbn argument must be a 10- or 13-character numeric string
without "-" characters.
// We could also determine whether the ISBN is valid by comparing
its checksum digit
// with a computed checksum.
//
if (!string.IsNullOrEmpty(isbn))
{
// Determine if ISBN length is correct.
if (!(isbn.Length == 10 | isbn.Length == 13))
throw new ArgumentException("The ISBN must be a 10- or 13-
character numeric string.");
if (!ulong.TryParse(isbn, out _))
throw new ArgumentException("The ISBN can consist of numeric
characters only.");
}
ISBN = isbn;

Author = author;
}

public string ISBN { get; }

public string Author { get; }

public decimal Price { get; private set; }

// A three-digit ISO currency symbol.


public string? Currency { get; private set; }

// Returns the old price, and sets a new price.


public decimal SetPrice(decimal price, string currency)
{
if (price < 0)
throw new ArgumentOutOfRangeException(nameof(price), "The price
cannot be negative.");
decimal oldValue = Price;
Price = price;

if (currency.Length != 3)
throw new ArgumentException("The ISO currency symbol is a 3-
character string.");
Currency = currency;

return oldValue;
}

public override bool Equals(object? obj)


{
if (obj is not Book book)
return false;
else
return ISBN == book.ISBN;
}

public override int GetHashCode() => ISBN.GetHashCode();

public override string ToString() => $"{(string.IsNullOrEmpty(Author) ?


"" : Author + ", ")}{Title}";
}

Além dos membros herdados de Publication , a classe Book define os seguintes


membros exclusivos e substituições de membro:

Dois construtores

Os dois construtores Book compartilham três parâmetros comuns. Dois, title e


publisher, correspondem aos parâmetros do construtor Publication . O terceiro é
author, que é armazenado em uma propriedade pública Author imutável. Um
construtor inclui um parâmetro isbn, que é armazenado na propriedade
automática ISBN .

O primeiro construtor usa a palavra-chave this para chamar o outro construtor. O


encadeamento do construtor é um padrão comum na definição de construtores.
Construtores com menos parâmetros fornecem valores padrão ao chamar o
construtor com o maior número de parâmetros.

O segundo construtor usa a palavra-chave base para passar o título e o nome do


publicador para o construtor da classe base. Se você não fizer uma chamada
explícita para um construtor de classe base em seu código-fonte, o compilador de
C# fornecerá automaticamente uma chamada para a classe base padrão ou para o
construtor sem parâmetros.

Uma propriedade ISBN somente leitura, que retorna o ISBN (International


Standard Book Number) do objeto Book , um número exclusivo com 10 ou 13
dígitos. O ISBN é fornecido como um argumento para um dos construtores Book .
O ISBN é armazenado em um campo de suporte particular, gerado
automaticamente pelo compilador.

Uma propriedade Author somente leitura. O nome do autor é fornecido como um


argumento para os dois construtores Book e é armazenado na propriedade.

Duas propriedades somente leitura relacionadas ao preço, Price e Currency . Seus


valores são fornecidos como argumentos em uma chamada do método SetPrice .
A propriedade Currency é o símbolo de moeda ISO de três dígitos (por exemplo,
USD para dólar americano). Símbolos de moeda ISO podem ser obtidos da
propriedade ISOCurrencySymbol. Ambas as propriedades são somente leitura
externamente, mas podem ser definidas por código na classe Book .

Um método SetPrice , que define os valores das propriedades Price e Currency .


Esses valores são retornados por essas mesmas propriedades.

Substitui o método ToString (herdado de Publication ) e os métodos


Object.Equals(Object) e GetHashCode (herdados de Object).

A menos que seja substituído, o método Object.Equals(Object) testa a igualdade


de referência. Ou seja, duas variáveis de objeto são consideradas iguais se fizerem
referência ao mesmo objeto. Na classe Book , por outro lado, dois objetos Book
devem ser iguais quando têm o mesmo ISBN.

Ao substituir o método Object.Equals(Object), substitua também o método


GetHashCode, que retorna um valor usado pelo runtime para armazenar itens em
coleções de hash para uma recuperação eficiente. O código de hash deve retornar
um valor que é consistente com o teste de igualdade. Como você substituiu
Object.Equals(Object) para retornar true , se as propriedades de ISBN de dois
objetos Book forem iguais, retorne o código hash computado chamando o
método GetHashCode da cadeia de caracteres retornada pela propriedade ISBN .

A figura a seguir ilustra o relacionamento entre a classe Book e Publication , sua classe
base.
Agora você pode criar a instância de um objeto Book , invocar seus membros exclusivos
e herdados e passá-lo como um argumento a um método que espera um parâmetro do
tipo Publication ou do tipo Book , como mostra o exemplo a seguir.

C#

public class ClassExample


{
public static void Main()
{
var book = new Book("The Tempest", "0971655819", "Shakespeare,
William",
"Public Domain Press");
ShowPublicationInfo(book);
book.Publish(new DateTime(2016, 8, 18));
ShowPublicationInfo(book);

var book2 = new Book("The Tempest", "Classic Works Press",


"Shakespeare, William");
Console.Write($"{book.Title} and {book2.Title} are the same
publication: " +
$"{((Publication)book).Equals(book2)}");
}

public static void ShowPublicationInfo(Publication pub)


{
string pubDate = pub.GetPublicationDate();
Console.WriteLine($"{pub.Title}, " +
$"{(pubDate == "NYP" ? "Not Yet Published" : "published on
" + pubDate):d} by {pub.Publisher}");
}
}
// The example displays the following output:
// The Tempest, Not Yet Published by Public Domain Press
// The Tempest, published on 8/18/2016 by Public Domain Press
// The Tempest and The Tempest are the same publication: False

Criando classes base abstratas e suas classes


derivadas
No exemplo anterior, você definiu uma classe base que forneceu uma implementação
de diversos métodos para permitir que classes derivadas compartilhem código. Em
muitos casos, no entanto, não espera-se que a classe base forneça uma implementação.
Nesse caso, a classe base é uma classe abstrata que declara métodos abstratos. Ela
funciona como um modelo que define os membros que cada classe derivada precisa
implementar. Normalmente em uma classe base abstrata, a implementação de cada tipo
derivado é exclusiva para esse tipo. Você marcou a classe com a palavra-chave abstract
porque não fazia sentido criar uma instância de um objeto Publication , embora a
classe fornecesse implementações de funcionalidades comuns para publicações.

Por exemplo, cada forma geométrica bidimensional fechada inclui duas propriedades:
área, a extensão interna da forma; e perímetro, ou a distância entre as bordas da forma.
A maneira com a qual essas propriedades são calculadas, no entanto, depende
completamente da forma específica. A fórmula para calcular o perímetro (ou a
circunferência) de um círculo, por exemplo, é diferente do quadrado. A classe Shape é
uma classe abstract com métodos abstract . Isso indica que as classes derivadas
compartilham a mesma funcionalidade, mas essas classes derivadas implementam essa
funcionalidade de forma diferente.

O exemplo a seguir define uma classe base abstrata denominada Shape que define duas
propriedades: Area e Perimeter . Além da classe ser marcada com a palavra-chave
abstract, cada membro da instância também é marcado com a palavra-chave abstract.
Nesse caso, o Shape também substitui o método Object.ToString para retornar o nome
do tipo, em vez de seu nome totalmente qualificado. E define dois membros estáticos,
GetArea e GetPerimeter , que permitem a recuperação fácil da área e do perímetro de

uma instância de qualquer classe derivada. Quando você passa uma instância de uma
classe derivada para um desses métodos, o runtime chama a substituição do método da
classe derivada.

C#

public abstract class Shape


{
public abstract double Area { get; }

public abstract double Perimeter { get; }

public override string ToString() => GetType().Name;

public static double GetArea(Shape shape) => shape.Area;

public static double GetPerimeter(Shape shape) => shape.Perimeter;


}

Em seguida, você pode derivar algumas classes de Shape que representam formas
específicas. O exemplo a seguir define três classes, Square , Rectangle e Circle . Cada
uma usa uma fórmula exclusiva para essa forma específica para calcular a área e o
perímetro. Algumas das classes derivadas também definem propriedades, como
Rectangle.Diagonal e Circle.Diameter , que são exclusivas para a forma que
representam.

C#

using System;

public class Square : Shape


{
public Square(double length)
{
Side = length;
}

public double Side { get; }


public override double Area => Math.Pow(Side, 2);

public override double Perimeter => Side * 4;

public double Diagonal => Math.Round(Math.Sqrt(2) * Side, 2);


}

public class Rectangle : Shape


{
public Rectangle(double length, double width)
{
Length = length;
Width = width;
}

public double Length { get; }

public double Width { get; }

public override double Area => Length * Width;

public override double Perimeter => 2 * Length + 2 * Width;

public bool IsSquare() => Length == Width;

public double Diagonal => Math.Round(Math.Sqrt(Math.Pow(Length, 2) +


Math.Pow(Width, 2)), 2);
}

public class Circle : Shape


{
public Circle(double radius)
{
Radius = radius;
}

public override double Area => Math.Round(Math.PI * Math.Pow(Radius, 2),


2);

public override double Perimeter => Math.Round(Math.PI * 2 * Radius, 2);

// Define a circumference, since it's the more familiar term.


public double Circumference => Perimeter;

public double Radius { get; }

public double Diameter => Radius * 2;


}

O exemplo a seguir usa objetos derivados de Shape . Ele cria uma matriz de objetos
derivados de Shape e chama os métodos estáticos da classe Shape , que retorna valores
de propriedade Shape . O runtime recupera os valores das propriedades substituídas dos
tipos derivados. O exemplo também converte cada objeto Shape na matriz ao seu tipo
derivado e, se a conversão for bem-sucedida, recupera as propriedades dessa subclasse
específica de Shape .

C#

using System;

public class Example


{
public static void Main()
{
Shape[] shapes = { new Rectangle(10, 12), new Square(5),
new Circle(3) };
foreach (Shape shape in shapes)
{
Console.WriteLine($"{shape}: area, {Shape.GetArea(shape)}; " +
$"perimeter, {Shape.GetPerimeter(shape)}");
if (shape is Rectangle rect)
{
Console.WriteLine($" Is Square: {rect.IsSquare()},
Diagonal: {rect.Diagonal}");
continue;
}
if (shape is Square sq)
{
Console.WriteLine($" Diagonal: {sq.Diagonal}");
continue;
}
}
}
}
// The example displays the following output:
// Rectangle: area, 120; perimeter, 44
// Is Square: False, Diagonal: 15.62
// Square: area, 25; perimeter, 20
// Diagonal: 7.07
// Circle: area, 28.27; perimeter, 18.85
Como converter com segurança usando
a correspondência de padrões e os
operadores is e as
Artigo • 07/04/2023

Como os objetos são polimórficos, é possível que uma variável de tipo de classe base
tenha um tipo derivado. Para acessar os métodos de instância do tipo derivado, é
necessário converter o valor de volta no tipo derivado. No entanto, uma conversão cria
o risco de lançar um InvalidCastException. O C# fornece instruções de correspondência
de padrões que executarão uma conversão condicionalmente somente quando ela tiver
êxito. O C# também oferece os operadores is e as para testar se um valor é de um
determinado tipo.

O exemplo a seguir mostra como usar a instrução is de correspondência de padrões:

C#

var g = new Giraffe();


var a = new Animal();
FeedMammals(g);
FeedMammals(a);
// Output:
// Eating.
// Animal is not a Mammal

SuperNova sn = new SuperNova();


TestForMammals(g);
TestForMammals(sn);

static void FeedMammals(Animal a)


{
if (a is Mammal m)
{
m.Eat();
}
else
{
// variable 'm' is not in scope here, and can't be used.
Console.WriteLine($"{a.GetType().Name} is not a Mammal");
}
}

static void TestForMammals(object o)


{
// You also can use the as operator and test for null
// before referencing the variable.
var m = o as Mammal;
if (m != null)
{
Console.WriteLine(m.ToString());
}
else
{
Console.WriteLine($"{o.GetType().Name} is not a Mammal");
}
}
// Output:
// I am an animal.
// SuperNova is not a Mammal

class Animal
{
public void Eat() { Console.WriteLine("Eating."); }
public override string ToString()
{
return "I am an animal.";
}
}
class Mammal : Animal { }
class Giraffe : Mammal { }

class SuperNova { }

O exemplo anterior demonstra alguns recursos da sintaxe de correspondência de


padrões. A instrução if (a is Mammal m) combina o teste com uma atribuição de
inicialização. A atribuição ocorre apenas quando o teste é bem-sucedido. A variável m
está somente no escopo na instrução if inserida em ela foi atribuída. Não é possível
acessar m posteriormente no mesmo método. O exemplo anterior também mostra
como usar o as operador para converter um objeto em um tipo especificado.

Também é possível usar a mesma sintaxe para testar se um tipo que permite valor nulo
tem um valor, como mostra o código de exemplo a seguir:

C#

int i = 5;
PatternMatchingNullable(i);

int? j = null;
PatternMatchingNullable(j);

double d = 9.78654;
PatternMatchingNullable(d);

PatternMatchingSwitch(i);
PatternMatchingSwitch(j);
PatternMatchingSwitch(d);
static void PatternMatchingNullable(ValueType? val)
{
if (val is int j) // Nullable types are not allowed in patterns
{
Console.WriteLine(j);
}
else if (val is null) // If val is a nullable type with no value, this
expression is true
{
Console.WriteLine("val is a nullable type with the null value");
}
else
{
Console.WriteLine("Could not convert " + val.ToString());
}
}

static void PatternMatchingSwitch(ValueType? val)


{
switch (val)
{
case int number:
Console.WriteLine(number);
break;
case long number:
Console.WriteLine(number);
break;
case decimal number:
Console.WriteLine(number);
break;
case float number:
Console.WriteLine(number);
break;
case double number:
Console.WriteLine(number);
break;
case null:
Console.WriteLine("val is a nullable type with the null value");
break;
default:
Console.WriteLine("Could not convert " + val.ToString());
break;
}
}

O exemplo anterior demonstra outros recursos da correspondência de padrões a serem


usados com conversões. É possível testar se uma variável tem padrão nulo verificando
especificamente o valor null . Quando o valor de runtime da variável é null , uma
instrução is que verifica um tipo retorna sempre false . A instrução is da
correspondência de padrões não permite um tipo de valor anulável, como int? ou
Nullable<int> , mas é possível testar qualquer outro tipo de valor. Os padrões is do

exemplo anterior não se limitam aos tipos que permitem valor nulo. Você também pode
usar esses padrões para testar se uma variável de um tipo de referência tem um valor ou
é null .

O exemplo anterior também mostra como usar o padrão de tipo em uma expressão
switch em que a variável pode ser um de muitos tipos diferentes.

Se você quiser testar se uma variável é um determinado tipo sem atribuição a uma nova
variável, poderá usar os operadores is e as para tipos de referência e tipos que
permitem valor nulo. O seguinte código mostra como usar as instruções is e as que
faziam parte da linguagem C# antes que a correspondência de padrões fosse
introduzida para testar se uma variável é de um determinado tipo:

C#

// Use the is operator to verify the type.


// before performing a cast.
Giraffe g = new();
UseIsOperator(g);

// Use the as operator and test for null


// before referencing the variable.
UseAsOperator(g);

// Use pattern matching to test for null


// before referencing the variable
UsePatternMatchingIs(g);

// Use the as operator to test


// an incompatible type.
SuperNova sn = new();
UseAsOperator(sn);

// Use the as operator with a value type.


// Note the implicit conversion to int? in
// the method body.
int i = 5;
UseAsWithNullable(i);

double d = 9.78654;
UseAsWithNullable(d);

static void UseIsOperator(Animal a)


{
if (a is Mammal)
{
Mammal m = (Mammal)a;
m.Eat();
}
}

static void UsePatternMatchingIs(Animal a)


{
if (a is Mammal m)
{
m.Eat();
}
}

static void UseAsOperator(object o)


{
Mammal? m = o as Mammal;
if (m is not null)
{
Console.WriteLine(m.ToString());
}
else
{
Console.WriteLine($"{o.GetType().Name} is not a Mammal");
}
}

static void UseAsWithNullable(System.ValueType val)


{
int? j = val as int?;
if (j is not null)
{
Console.WriteLine(j);
}
else
{
Console.WriteLine("Could not convert " + val.ToString());
}
}
class Animal
{
public void Eat() => Console.WriteLine("Eating.");
public override string ToString() => "I am an animal.";
}
class Mammal : Animal { }
class Giraffe : Mammal { }

class SuperNova { }

Como você pode ver na comparação desse código com o de correspondência de


padrões, a sintaxe de correspondência de padrões oferece recursos mais robustos
combinando o teste e a atribuição em uma única instrução. Use a sintaxe de
correspondência de padrões sempre que possível.
Tutorial: Usar padrões correspondentes
para criar algoritmos controlados por
tipo e controlados por dados
Artigo • 10/05/2023

É possível escrever uma funcionalidade que se comporte como se você tivesse


estendido tipos que poderiam estar em outras bibliotecas. Outro uso dos padrões é
criar a funcionalidade de que seu aplicativo precisa, mas que não é um recurso
fundamental do tipo que está sendo estendido.

Neste tutorial, você aprenderá como:

" Reconhecer situações em que a correspondência de padrões deverá ser usada.


" Usar expressões de correspondência de padrões para implementar o
comportamento com base em tipos e valores de propriedade.
" Combinar a correspondência de padrões com outras técnicas para criar algoritmos
completos.

Pré-requisitos
Recomendamos o Visual Studio para Windows ou Mac. Você pode baixar uma
versão gratuita na página de downloads do Visual Studio . O Visual Studio inclui
o SDK do .NET.
Você também pode usar o editor do Visual Studio Code . Será preciso instalar o
SDK do .NET mais recente separadamente.
Se preferir outro editor, você precisará instalar o SDK do .NET mais recente.

Este tutorial pressupõe que você esteja familiarizado com o C# e .NET, incluindo o Visual
Studio ou a CLI do .NET.

Cenários para a correspondência de padrões


O desenvolvimento moderno geralmente inclui a integração de dados de várias fontes e
a apresentação de informações e insights de dados em um único aplicativo coeso. Você
e sua equipe não terão controle ou acesso a todos os tipos que representam os dados
de entrada.

O design orientado a objeto clássico exigiria a criação de tipos em seu aplicativo que
representassem cada tipo de dados das várias fontes de dados. O aplicativo, então,
trabalharia com esses novos tipos, criaria hierarquias de herança, métodos virtuais e
implementaria abstrações. Essas técnicas funcionam e, às vezes, são as melhores
ferramentas. Outras vezes, é possível escrever menos código. Você pode escrever um
código mais claro usando técnicas que separam os dados das operações que
manipulam esses dados.

Neste tutorial, você vai criar e explorar um aplicativo que usa dados recebidos de várias
fontes externas para um único cenário. Você verá como a correspondência de padrões
fornece uma maneira eficiente para consumir e processar esses dados de maneiras que
não eram parte do sistema original.

Imagine, por exemplo, uma área metropolitana principal que está implantando pedágios
e preços diferenciados em horário de pico para gerenciar o tráfego. Você escreve um
aplicativo que calcula o pedágio de um veículo com base em seu tipo. Posteriormente,
as melhorias vão incorporar preços com base no número de ocupantes do veículo.
Outros aprimoramentos vão adicionar o preço com base na hora e no dia da semana.

Com base nessa breve descrição, você pode ter elaborado rapidamente uma hierarquia
de objetos para modelar esse sistema. No entanto, seus dados são provenientes de
várias fontes, como outros sistemas de gerenciamento de registro do veículo. Esses
sistemas fornecem classes diferentes para modelar aqueles dados, e você não tem um
modelo de objeto único o qual seja possível usar. Neste tutorial, você usará essas
classes simplificadas para criar o modelo para os dados do veículo, a partir desses
sistemas externos, conforme mostrado no código a seguir:

C#

namespace ConsumerVehicleRegistration
{
public class Car
{
public int Passengers { get; set; }
}
}

namespace CommercialRegistration
{
public class DeliveryTruck
{
public int GrossWeightClass { get; set; }
}
}

namespace LiveryRegistration
{
public class Taxi
{
public int Fares { get; set; }
}

public class Bus


{
public int Capacity { get; set; }
public int Riders { get; set; }
}
}

Faça o download do código inicial no repositório dotnet/samples do GitHub. É


possível ver que as classes de veículos são de sistemas diferentes, e estão em
namespaces diferentes. Nenhuma base comum de classe, além da System.Object pode
ser aproveitada.

Designs de correspondência de padrões


O cenário usado neste tutorial destaca os tipos de problemas que a correspondência de
padrões pode resolver de forma adequada:

Os objetos com os quais você precisa trabalhar não estão em uma hierarquia de
objetos que corresponde aos seus objetivos. É possível que você esteja
trabalhando com classes que fazem parte dos sistemas não relacionados.
A funcionalidade que você está adicionando não faz parte da abstração central
dessas classes. A tarifa paga por um veículo muda de acordo com diferentes tipos
de veículos, mas o pedágio não é uma função principal do veículo.

Quando a forma dos dados e as operações nos dados não são descritas em conjunto, o
recurso de correspondência padrões no C# facilita o trabalho.

Implementar os cálculos básicos de pedágio


O cálculo mais básico do pedágio dependerá apenas do tipo do veículo:

Um Car será R$2,00.


Um Taxi será R$3,50.
Um Bus será R$5,00.
Um DeliveryTruck será R$10,00.

Crie uma nova classe TollCalculator e implemente a correspondência de padrões no


tipo de veículo para obter a quantidade do pedágio. O código a seguir mostra a
implementação inicial do TollCalculator .
C#

using System;
using CommercialRegistration;
using ConsumerVehicleRegistration;
using LiveryRegistration;

namespace Calculators;

public class TollCalculator


{
public decimal CalculateToll(object vehicle) =>
vehicle switch
{
Car c => 2.00m,
Taxi t => 3.50m,
Bus b => 5.00m,
DeliveryTruck t => 10.00m,
{ } => throw new ArgumentException(message: "Not a known
vehicle type", paramName: nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))
};
}

O código anterior usa uma expressão switch (não igual a uma instrução switch) que
testa o padrão de instrução. A expressão switch inicia-se com a variável, vehicle no
código anterior, seguida pela palavra-chave switch . Em seguida, estão os braços switch
dentro de chaves. A expressão switch faz outros refinamentos na sintaxe que circunda a
instrução switch . A palavra-chave case é omitida, e o resultado de cada braço é uma
expressão. Os dois últimos braços apresentam um novo recurso de linguagem. O caso {
} corresponde a qualquer objeto não nulo que não correspondia a um braço anterior.
Este braço captura qualquer tipo incorreto passado para esse método. O caso { }
precisa seguir os casos para cada tipo de veículo. Se a ordem for revertida, o caso { }
terá precedência. Por fim, o padrão constante null detecta quando null é passado
para esse método. O padrão null pode ser o último, porque os outros padrões
correspondem apenas a um objeto não nulo do tipo correto.

Você pode testar esse código usando o seguinte código no Program.cs :

C#

using System;
using CommercialRegistration;
using ConsumerVehicleRegistration;
using LiveryRegistration;

using toll_calculator;
var tollCalc = new TollCalculator();

var car = new Car();


var taxi = new Taxi();
var bus = new Bus();
var truck = new DeliveryTruck();

Console.WriteLine($"The toll for a car is {tollCalc.CalculateToll(car)}");


Console.WriteLine($"The toll for a taxi is {tollCalc.CalculateToll(taxi)}");
Console.WriteLine($"The toll for a bus is {tollCalc.CalculateToll(bus)}");
Console.WriteLine($"The toll for a truck is
{tollCalc.CalculateToll(truck)}");

try
{
tollCalc.CalculateToll("this will fail");
}
catch (ArgumentException e)
{
Console.WriteLine("Caught an argument exception when using the wrong
type");
}
try
{
tollCalc.CalculateToll(null!);
}
catch (ArgumentNullException e)
{
Console.WriteLine("Caught an argument exception when using null");
}

Esse código está incluído no projeto inicial, mas é comentado. Remova os comentários e
você pode testar o que escreveu.

Você está começando a ver como os padrões podem ajudar a criar algoritmos em que o
código e os dados estão separados. A expressão switch testa o tipo e produz valores
diferentes com base nos resultados. Mas isso é somente o começo.

Adicionar preços de acordo com a ocupação do


veículo
A autoridade de pedágio deseja incentivar que os veículos viagem com a capacidade
máxima de pessoas. Eles decidiram cobrar valores mais altos quando os veículos tiverem
poucos passageiros e oferecer redução da tarifa para veículos com a capacidade total
ocupada:

Os carros e táxis com nenhum passageiro pagam uma taxa adicional de R$ 0,50.
Os carros e táxis com dois passageiros obtêm um desconto de R$ 0,50.
Os carros e táxis com três ou mais passageiros obtêm um desconto de R$ 1,00.
Os ônibus com menos de 50% da capacidade completa pagam uma taxa adicional
de R$ 2,00.
Os ônibus com 90% da capacidade de passageiros completa, ganham um
desconto de R$ 1,00.

Essas regras podem ser implementadas usando o padrão de propriedade na mesma


expressão switch. Um padrão de propriedade compara um valor de propriedade com
um valor constante. O padrão de propriedade examina as propriedades do objeto
depois que o tipo foi determinado. O único caso de um Car se expande para quatro
casos diferentes:

C#

vehicle switch
{
Car {Passengers: 0} => 2.00m + 0.50m,
Car {Passengers: 1} => 2.0m,
Car {Passengers: 2} => 2.0m - 0.50m,
Car => 2.00m - 1.0m,

// ...
};

Os três primeiros casos testam o tipo como um Car , em seguida, verificam o valor da
propriedade Passengers . Se ambos corresponderem, essa expressão é avaliada e
retornada.

Você também expande os casos para táxis de maneira semelhante:

C#

vehicle switch
{
// ...

Taxi {Fares: 0} => 3.50m + 1.00m,


Taxi {Fares: 1} => 3.50m,
Taxi {Fares: 2} => 3.50m - 0.50m,
Taxi => 3.50m - 1.00m,

// ...
};

Em seguida, implemente as regras de ocupação expandindo os casos para os ônibus,


conforme mostrado no exemplo a seguir:
C#

vehicle switch
{
// ...

Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m +


2.00m,
Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m -
1.00m,
Bus => 5.00m,

// ...
};

A autoridade de pedágio não está preocupada com o número de passageiros nos


caminhões de carga. Em vez disso, ela ajusta a quantidade de pedágios com base na
classe de peso dos caminhões da seguinte maneira:

Os caminhões mais de 5000 quilos pagam uma taxa adicional de R$ 5,00.


Os caminhões leves abaixo de 3.000 lb recebem um desconto de US$ 2,00.

Essa regra é implementada com o código a seguir:

C#

vehicle switch
{
// ...

DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m,


DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m,
DeliveryTruck => 10.00m,
};

O código anterior mostra a cláusula when de um braço switch. Você usa a cláusula when
para testar as condições, com exceção da igualdade, em uma propriedade. Ao terminar,
o método será muito semelhante ao seguinte código:

C#

vehicle switch
{
Car {Passengers: 0} => 2.00m + 0.50m,
Car {Passengers: 1} => 2.0m,
Car {Passengers: 2} => 2.0m - 0.50m,
Car => 2.00m - 1.0m,

Taxi {Fares: 0} => 3.50m + 1.00m,


Taxi {Fares: 1} => 3.50m,
Taxi {Fares: 2} => 3.50m - 0.50m,
Taxi => 3.50m - 1.00m,

Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m +


2.00m,
Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m -
1.00m,
Bus => 5.00m,

DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m,


DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m,
DeliveryTruck => 10.00m,

{ } => throw new ArgumentException(message: "Not a known vehicle


type", paramName: nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))
};

Muitos desses braços switch são exemplos de padrões recursivos. Por exemplo, Car {
Passengers: 1} mostra um padrão constante dentro de um padrão de propriedade.

É possível fazer esse código menos repetitivo, usando switches aninhados. O Car e Taxi
têm quatro braços diferentes nos exemplos anteriores. Em ambos os casos, você pode
criar um padrão de declaração que alimenta um padrão constante. Essa técnica é
mostrada no código a seguir:

C#

public decimal CalculateToll(object vehicle) =>


vehicle switch
{
Car c => c.Passengers switch
{
0 => 2.00m + 0.5m,
1 => 2.0m,
2 => 2.0m - 0.5m,
_ => 2.00m - 1.0m
},

Taxi t => t.Fares switch


{
0 => 3.50m + 1.00m,
1 => 3.50m,
2 => 3.50m - 0.50m,
_ => 3.50m - 1.00m
},

Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m +


2.00m,
Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m -
1.00m,
Bus b => 5.00m,

DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m,


DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m,
DeliveryTruck t => 10.00m,

{ } => throw new ArgumentException(message: "Not a known vehicle


type", paramName: nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))
};

Na amostra anterior, o uso de uma expressão recursiva significa não repetir os braços
Car e Taxi contendo braços filho que testam o valor da propriedade. Essa técnica não é

usada para os braços Bus e DeliveryTruck porque esses estão testando intervalos da
propriedade, e não valores discretos.

Adicionar preço de horário de pico


Para um último recurso, a autoridade de pedágio quer adicionar um preço os horários
de pico. Durante os horários de pico da manhã e do final da tarde, os pedágios serão
dobrados. Essa regra afetará apenas o tráfego em uma direção: entrada para a cidade,
no período da manhã, e de saída da cidade, no período da tarde. Em outros períodos
durante o dia útil, os pedágios aumentam 50%. Nos períodos da noite e madrugada e
de manhã cedo, as tarifas são 25% mais baratas. Durante o fim de semana, a taxa é
normal, independentemente da hora. Você pode usar uma série de instruções if e
else para expressar isso usando o seguinte código:

C#

public decimal PeakTimePremiumIfElse(DateTime timeOfToll, bool inbound)


{
if ((timeOfToll.DayOfWeek == DayOfWeek.Saturday) ||
(timeOfToll.DayOfWeek == DayOfWeek.Sunday))
{
return 1.0m;
}
else
{
int hour = timeOfToll.Hour;
if (hour < 6)
{
return 0.75m;
}
else if (hour < 10)
{
if (inbound)
{
return 2.0m;
}
else
{
return 1.0m;
}
}
else if (hour < 16)
{
return 1.5m;
}
else if (hour < 20)
{
if (inbound)
{
return 1.0m;
}
else
{
return 2.0m;
}
}
else // Overnight
{
return 0.75m;
}
}
}

O código anterior funciona corretamente, mas não é legível. Você precisa encadear
todos os casos de entrada e as instruções aninhadas if para raciocinar sobre o código.
Em vez disso, você usará a correspondência de padrões para esse recurso, mas poderá
integrá-lo a outras técnicas. É possível criar uma única expressão de correspondência de
padrões que leva em conta todas as combinações de direção, dia da semana e hora. O
resultado seria uma expressão complicada. Seria difícil de ler e entender. O que dificulta
garantir a exatidão. Em vez disso, combine esses métodos para criar uma tupla de
valores que descreve de forma concisa todos os estados. Em seguida, use a
correspondência de padrões para calcular um multiplicador para o pedágio. A tupla
contém três condições distintas:

O dia é um dia da semana ou do fim de semana.


A faixa de tempo é quando o pedágio é coletado.
A direção é para a cidade ou da cidade

A tabela a seguir mostra as combinações de valores de entrada e multiplicador de


preços para os horários de pico:
Dia Hora Direção Premium

Weekday horário de pico da manhã entrada x 2,00

Weekday horário de pico da manhã saída x 1,00

Weekday hora do dia entrada x 1,50

Weekday hora do dia saída x 1,50

Weekday horário de pico do fim da tarde entrada x 1,00

Weekday horário de pico do fim da tarde saída x 2,00

Weekday noite e madrugada entrada x 0,75

Weekday noite e madrugada saída x 0,75

Fim de Semana horário de pico da manhã entrada x 1,00

Fim de Semana horário de pico da manhã saída x 1,00

Fim de Semana hora do dia entrada x 1,00

Fim de Semana hora do dia saída x 1,00

Fim de Semana horário de pico do fim da tarde entrada x 1,00

Fim de Semana horário de pico do fim da tarde saída x 1,00

Fim de Semana noite e madrugada entrada x 1,00

Fim de Semana noite e madrugada saída x 1,00

Há 16 combinações diferentes das três variáveis. Ao combinar algumas das condições,


você simplificará a expressão switch.

O sistema que coleta os pedágios usa uma estrutura DateTime para a hora em que o
pedágio foi cobrado. Construa métodos de membro que criam as variáveis da tabela
anterior. A seguinte função usa como correspondência de padrões a expressão switch
para expressar se um DateTime representa um fim de semana ou um dia útil:

C#

private static bool IsWeekDay(DateTime timeOfToll) =>


timeOfToll.DayOfWeek switch
{
DayOfWeek.Monday => true,
DayOfWeek.Tuesday => true,
DayOfWeek.Wednesday => true,
DayOfWeek.Thursday => true,
DayOfWeek.Friday => true,
DayOfWeek.Saturday => false,
DayOfWeek.Sunday => false
};

Esse método está correto, mas é redundante. Para simplificar, faça conforme mostrado
no código a seguir:

C#

private static bool IsWeekDay(DateTime timeOfToll) =>


timeOfToll.DayOfWeek switch
{
DayOfWeek.Saturday => false,
DayOfWeek.Sunday => false,
_ => true
};

Depois, adicione uma função semelhante para categorizar o tempo nos blocos:

C#

private enum TimeBand


{
MorningRush,
Daytime,
EveningRush,
Overnight
}

private static TimeBand GetTimeBand(DateTime timeOfToll) =>


timeOfToll.Hour switch
{
< 6 or > 19 => TimeBand.Overnight,
< 10 => TimeBand.MorningRush,
< 16 => TimeBand.Daytime,
_ => TimeBand.EveningRush,
};

Adicione um enum privado para converter cada intervalo de tempo em um valor


discreto. Em seguida, o método GetTimeBand usa padrões relacionais e padrões or
conjuntivos, ambos adicionados no C# 9.0. Um padrão relacional permite testar um
valor numérico usando < , > , <= ou >= . O padrão or testa se uma expressão
corresponde a um ou mais padrões. Você também pode usar um padrão and para
garantir que uma expressão corresponda a dois padrões distintos e um padrão not para
testar se uma expressão não corresponde a um padrão.
Depois de criar esses métodos, é possível usar outra expressão switch com o padrão de
tupla para calcular o preço premium. Você pode construir uma expressão switch com
todos os 16 braços:

C#

public decimal PeakTimePremiumFull(DateTime timeOfToll, bool inbound) =>


(IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
{
(true, TimeBand.MorningRush, true) => 2.00m,
(true, TimeBand.MorningRush, false) => 1.00m,
(true, TimeBand.Daytime, true) => 1.50m,
(true, TimeBand.Daytime, false) => 1.50m,
(true, TimeBand.EveningRush, true) => 1.00m,
(true, TimeBand.EveningRush, false) => 2.00m,
(true, TimeBand.Overnight, true) => 0.75m,
(true, TimeBand.Overnight, false) => 0.75m,
(false, TimeBand.MorningRush, true) => 1.00m,
(false, TimeBand.MorningRush, false) => 1.00m,
(false, TimeBand.Daytime, true) => 1.00m,
(false, TimeBand.Daytime, false) => 1.00m,
(false, TimeBand.EveningRush, true) => 1.00m,
(false, TimeBand.EveningRush, false) => 1.00m,
(false, TimeBand.Overnight, true) => 1.00m,
(false, TimeBand.Overnight, false) => 1.00m,
};

O código acima funciona, mas pode ser simplificado. Todas as oito combinações para o
fim de semana têm o mesmo pedágio. É possível substituir todas as oito pela seguinte
linha:

C#

(false, _, _) => 1.0m,

Tanto o tráfego de entrada quanto o de saída têm o mesmo multiplicador durante o dia
e a noite, nos dias úteis. Esses quatro braços switch podem ser substituídos por estas
duas linhas:

C#

(true, TimeBand.Overnight, _) => 0.75m,


(true, TimeBand.Daytime, _) => 1.5m,

O código deverá ser semelhante ao seguinte após essas duas alterações:

C#
public decimal PeakTimePremium(DateTime timeOfToll, bool inbound) =>
(IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
{
(true, TimeBand.MorningRush, true) => 2.00m,
(true, TimeBand.MorningRush, false) => 1.00m,
(true, TimeBand.Daytime, _) => 1.50m,
(true, TimeBand.EveningRush, true) => 1.00m,
(true, TimeBand.EveningRush, false) => 2.00m,
(true, TimeBand.Overnight, _) => 0.75m,
(false, _, _) => 1.00m,
};

Por fim, você pode remover os dois horários de pico em que é pago o preço normal.
Quando remover essas braços, substitua o false por um descarte ( _ ) no braço switch
final. O método concluído será o seguinte:

C#

public decimal PeakTimePremium(DateTime timeOfToll, bool inbound) =>


(IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
{
(true, TimeBand.Overnight, _) => 0.75m,
(true, TimeBand.Daytime, _) => 1.5m,
(true, TimeBand.MorningRush, true) => 2.0m,
(true, TimeBand.EveningRush, false) => 2.0m,
_ => 1.0m,
};

Este exemplo destaca uma das vantagens da correspondência de padrões: os branches


de padrões são avaliados na ordem. Se você os reorganizar para que um branch
anterior trate um dos casos posteriores, o compilador emitirá um aviso sobre o código
inacessível. Essas regras de linguagem tornam as simplificações anteriores mais fáceis
com a certeza de que o código não foi alterado.

A correspondência de padrões torna alguns tipos de código mais legíveis e oferece uma
alternativa às técnicas orientadas a objeto quando não é possível adicionar o código às
classes. A nuvem está fazendo com que os dados e a funcionalidade existam
separadamente. A forma dos dados e as operações nela não são necessariamente
descritas juntas. Neste tutorial, você utilizou os dados existentes de maneiras
completamente diferentes de sua função original. A correspondência de padrões
proporcionou a capacidade de escrever a funcionalidade que substituiu esses tipos,
ainda que não tenha sido possível estendê-los.

Próximas etapas
Baixe o código concluído no repositório dotnet/samples do GitHub. Explore os
padrões por conta própria e adicione essa técnica em suas atividades regulares de
codificação. Aprender essas técnicas lhe oferece outra maneira de abordar problemas e
criar novas funcionalidades.

Confira também
Padrões
Expressão switch
Como manipular uma exceção usando
try/catch
Artigo • 07/04/2023

A finalidade de um bloco try-catch é capturar e manipular uma exceção gerada pelo


código de trabalho. Algumas exceções podem ser manipuladas em um bloco catch e o
problema pode ser resolvido sem que a exceção seja gerada novamente. No entanto,
com mais frequência, a única coisa que você pode fazer é certificar-se de que a exceção
apropriada seja gerada.

Exemplo
Neste exemplo, IndexOutOfRangeException não é a exceção mais apropriada:
ArgumentOutOfRangeException faz mais sentido para o método porque o erro é
causado pelo argumento index passado pelo chamador.

C#

static int GetInt(int[] array, int index)


{
try
{
return array[index];
}
catch (IndexOutOfRangeException e) // CS0168
{
Console.WriteLine(e.Message);
// Set IndexOutOfRangeException to the new exception's
InnerException.
throw new ArgumentOutOfRangeException("index parameter is out of
range.", e);
}
}

Comentários
O código que causa uma exceção fica dentro do bloco try . Uma instrução catch é
adicionada imediatamente após para lidar com IndexOutOfRangeException , caso ocorra.
O bloco catch manipula o IndexOutOfRangeException e gera o
ArgumentOutOfRangeException mais apropriado. Para fornecer ao chamador tantas

informações quanto possível, considere especificar a exceção original como o


InnerException da nova exceção. Uma vez que a propriedade InnerException é somente
leitura, você precisa atribuí-la no construtor da nova exceção.
Como executar código de limpeza
usando finally
Artigo • 12/03/2024

O propósito de uma instrução finally é garantir que a limpeza necessária de objetos,


normalmente objetos que estão mantendo recursos externos, ocorra imediatamente,
mesmo que uma exceção seja lançada. Um exemplo dessa limpeza é chamar Close em
um FileStream imediatamente após o uso, em vez de esperar que o objeto passe pela
coleta de lixo feita pelo Common Language Runtime, da seguinte maneira:

C#

static void CodeWithoutCleanup()


{
FileStream? file = null;
FileInfo fileInfo = new FileInfo("./file.txt");

file = fileInfo.OpenWrite();
file.WriteByte(0xF);

file.Close();
}

Exemplo
Para transformar o código anterior em uma instrução try-catch-finally , o código de
limpeza é separado do código funcional, da seguinte maneira.

C#

static void CodeWithCleanup()


{
FileStream? file = null;
FileInfo? fileInfo = null;

try
{
fileInfo = new FileInfo("./file.txt");

file = fileInfo.OpenWrite();
file.WriteByte(0xF);
}
catch (UnauthorizedAccessException e)
{
Console.WriteLine(e.Message);
}
finally
{
file?.Close();
}
}

Como uma exceção pode ocorrer a qualquer momento no bloco try antes da chamada
OpenWrite() ou a própria chamada OpenWrite() poderia falhar, não há garantia de que

o arquivo esteja aberto quanto tentarmos fechá-lo. O bloco finally adiciona uma
verificação para checar se o objeto FileStream não é null antes de chamar o método
Close. Sem a verificação de null , o bloco finally poderia gerar uma
NullReferenceException própria, mas a geração de exceções em blocos finally deve
ser evitada, se possível.

Uma conexão de banco de dados é outra boa candidata a ser fechada em um bloco
finally . Como o número de conexões permitidas para um servidor de banco de dados

é, às vezes, limitado, você deve fechar conexões de banco de dados assim que possível.
Se uma exceção for gerada antes que você possa fechar a conexão, usar o bloco
finally será melhor do que aguardar a coleta de lixo.

Confira também
Instrução using
Instruções para manipulação de exceções

6 Colaborar conosco no Comentários do .NET


GitHub O .NET é um projeto código aberto.
A fonte deste conteúdo pode Selecione um link para fornecer
ser encontrada no GitHub, onde comentários:
você também pode criar e
revisar problemas e solicitações  Abrir um problema de
de pull. Para obter mais documentação
informações, confira o nosso
guia para colaboradores.  Fornecer comentários sobre o
produto
O que há de novo no C# 13
Artigo • 28/03/2024

O C# 13 inclui os novos recursos a seguir. Você pode testar esses recursos usando a
versão mais recente do Visual Studio 2022 ou a versão prévia do SDK do.NET 9 .

Nova sequência de escape – \e.


Melhorias do tipo natural do grupo de métodos
Acesso implícito de indexador em inicializadores de objeto

C# 13 é compatível com .NET 9. Para obter mais informações, consulte Controle de


versão da linguagem C#.

Você pode baixar a versão prévia do SDK do .NET 9 mais recente na página de
downloads do .NET . Você também pode baixar o Visual Studio 2022 - versão prévia ,
que inclui a versão prévia do SDK do .NET 9.

Novos recursos são adicionados à página "Novidades no C#" quando estão disponíveis
em versões prévias. A seção conjunto de trabalho da página de status do recurso
roslyn acompanha quando os recursos futuros são mesclados na ramificação
principal.

7 Observação

Estamos interessados em seus comentários sobre esses recursos. Se você encontrar


problemas com qualquer um desses novos recursos, crie um problema no
repositório dotnet/roslyn .

Nova sequência de escape


Você pode usar \e como uma sequência de escape de literal de caractere para o
caractere ESCAPE , U+001B Unicode. Anteriormente, você usava \u001b ou \x1b . O uso de
\x1b não foi recomendado porque se os próximos caracteres após 1b forem dígitos

hexadecimal válidos, esses caracteres se tornarão parte da sequência de escape.

Tipo natural do grupo de métodos


Esse recurso faz pequenas otimizações para sobrecarregar a resolução envolvendo
grupos de métodos. O comportamento anterior era que o compilador construísse o
conjunto completo de métodos candidatos para um grupo de métodos. Se um tipo
natural fosse necessário, o tipo natural era determinado a partir do conjunto completo
de métodos candidatos.

O novo comportamento é remover o conjunto de métodos candidatos em cada escopo,


removendo os métodos candidatos que não são aplicáveis. Normalmente, esses são
métodos genéricos com a aridade errada ou restrições que não são cumpridas. O
processo continuará para o próximo escopo externo somente se nenhum método
candidato tiver sido encontrado. Esse processo segue mais de perto o algoritmo geral
para resolução de sobrecarga. Se todos os métodos candidatos encontrados em um
determinado escopo não corresponderem, o grupo de métodos não terá um tipo
natural.

Você pode ler os detalhes das alterações na especificação da proposta.

Acesso implícito ao índice


O operador de índice "do final" implícito ^ , agora é permitido em uma expressão de
inicializador de objeto. Por exemplo, agora você pode inicializar uma matriz em um
inicializador de objeto, conforme mostrado no seguinte código:

C#

var v = new S()


{
buffer =
{
[^1] = 0,
[^2] = 1,
[^3] = 2,
[^4] = 3,
[^5] = 4,
[^6] = 5,
[^7] = 6,
[^8] = 7,
[^9] = 8,
[^10] = 9
}
};

Em versões anteriores ao C# 13, o operador ^ não pode ser usado em um inicializador


de objeto. Você precisa indexar os elementos da frente.

Confira também
Novidades do .NET 9
6 Colaborar conosco no Comentários do .NET
GitHub O .NET é um projeto código aberto.
A fonte deste conteúdo pode Selecione um link para fornecer
ser encontrada no GitHub, onde comentários:
você também pode criar e
revisar problemas e solicitações  Abrir um problema de
de pull. Para obter mais documentação
informações, confira o nosso
guia para colaboradores.  Fornecer comentários sobre o
produto
Novidades do C# 12
Artigo • 21/03/2024

O C# 12 inclui os novos recursos a seguir. Você pode testar esses recursos usando a
versão mais recente do Visual Studio 2022 ou o SDK do.NET 8 .

Construtores primários: introduzidos no Visual Studio 2022 versão 17.6 versão


prévia 2.

Expressões de coleção: introduzidas no Visual Studio 2022 versão 17.7 versão


prévia 5.

Matrizes embutidas: introduzidas no Visual Studio 2022 versão 17.7 versão prévia
3.

Parâmetros opcionais em expressões lambda: introduzidos no Visual Studio 2022


versão 17.5, versão prévia 2.

parâmetros ref readonly: introduzidos no Visual Studio 2022 versão 17.8 versão
prévia 2.

Alias qualquer tipo: introduzido no Visual Studio 2022 versão 17.6 versão prévia 3.

Atributo experimental: introduzido no Visual Studio 2022 versão 17.7 versão prévia
3.

Interceptores - versão prévia do recurso introduzida no Visual Studio 2022 versão


17.7 versão prévia 3.

C# 12 é compatível com .NET 8. Para obter mais informações, consulte Controle de


versão da linguagem C#.

Você pode baixar o SDK do .NET 8 mais recente na página de downloads do .NET .
Você também pode baixar o Visual Studio 2022 , que inclui o SDK do .NET 8.

7 Observação

Estamos interessados em seus comentários sobre esses recursos. Se você encontrar


problemas com qualquer um desses novos recursos, crie um problema no
repositório dotnet/roslyn .

Construtores primários
Agora, você pode criar construtores primários em qualquer class e struct . Os
construtores primários não são mais restritos a tipos record . Os parâmetros do
construtor primário estão no escopo de todo o corpo da classe. Para garantir que todos
os parâmetros do construtor primário sejam atribuídos definitivamente, todos os
construtores declarados explicitamente devem chamar o construtor primário usando a
sintaxe this() . Adicionar um construtor primário a um class impede que o compilador
declare um construtor implícito sem parâmetros. Em um struct , o construtor implícito
sem parâmetros inicializa todos os campos, incluindo parâmetros de construtor primário
para o padrão de 0 bits.

O compilador gera propriedades públicas para parâmetros de construtor primários


somente em tipos record , tipos record class ou record struct . Classes e structs não
registrados podem nem sempre querer esse comportamento para parâmetros de
construtor primário.

Você pode saber mais sobre construtores primários no tutorial para explorar
construtores primários e no artigo sobre construtores de instâncias.

Expressões de coleção
As expressões de coleção apresentam uma nova sintaxe concisa para criar valores
comuns de coleção. É possível incorporar outras coleções nesses valores usando um
operador spread .. .

Vários tipos semelhantes à coleção podem ser criados sem a necessidade de suporte a
BCL externo. Esses tipos são:

Tipos de matriz, como int[] .


System.Span<T> e System.ReadOnlySpan<T>.
Tipos que dão suporte a inicializadores de coleção, como
System.Collections.Generic.List<T>.

Os exemplos a seguir mostram usos de expressões de coleção:

C#

// Create an array:
int[] a = [1, 2, 3, 4, 5, 6, 7, 8];

// Create a list:
List<string> b = ["one", "two", "three"];

// Create a span
Span<char> c = ['a', 'b', 'c', 'd', 'e', 'f', 'h', 'i'];
// Create a jagged 2D array:
int[][] twoD = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];

// Create a jagged 2D array from variables:


int[] row0 = [1, 2, 3];
int[] row1 = [4, 5, 6];
int[] row2 = [7, 8, 9];
int[][] twoDFromVariables = [row0, row1, row2];

O operador spread, .. em uma expressão de coleção substitui seu argumento pelos


elementos dessa coleção. O argumento deve ser um tipo de coleção. Os exemplos a
seguir mostram como o operador de spread funciona:

C#

int[] row0 = [1, 2, 3];


int[] row1 = [4, 5, 6];
int[] row2 = [7, 8, 9];
int[] single = [.. row0, .. row1, .. row2];
foreach (var element in single)
{
Console.Write($"{element}, ");
}
// output:
// 1, 2, 3, 4, 5, 6, 7, 8, 9,

O operando de um operador spread é uma expressão que pode ser enumerada. O


operador spread avalia cada elemento da expressão de enumerações.

Você pode usar expressões de coleção em qualquer lugar que precisar de uma coleção
de elementos. Eles podem especificar o valor inicial de uma coleção ou ser passados
como argumentos para métodos que utilizam tipos de coleção. Você pode saber mais
sobre expressões de coleção no artigo de referência de linguagem sobre expressões de
coleção ou a especificação do recurso.

Parâmetros ref readonly


O C# adicionou parâmetros in como uma maneira de passar referências somente
leitura. Os parâmetros in permitem variáveis e valores e podem ser usados sem
nenhuma anotação nos argumentos.

A adição de parâmetros ref readonly permite mais clareza para APIs que podem estar
usando parâmetros ref ou parâmetros in :
As APIs criadas antes da introdução de in podem usar ref , mesmo que o
argumento não tenha sido modificado. Essas APIs podem ser atualizadas com ref
readonly . Não será uma alteração significativa para os chamadores, como seria se

o parâmetro ref fosse alterado para in . Um exemplo é


System.Runtime.InteropServices.Marshal.QueryInterface.
APIs que tomam um parâmetro in , mas logicamente exigem uma variável. Uma
expressão de valor não funciona. Um exemplo é
System.ReadOnlySpan<T>.ReadOnlySpan<T>(T).
APIs que usam ref porque exigem uma variável, mas não modificam essa variável.
Um exemplo é System.Runtime.CompilerServices.Unsafe.IsNullRef.

Para saber mais sobre parâmetros ref readonly , consulte o artigo sobre modificadores
de parâmetro na referência de idioma ou a especificação de recurso de parâmetros
somente leitura ref.

Parâmetros lambda padrão


Agora, você pode definir valores padrão para parâmetros em expressões lambda. A
sintaxe e as regras são iguais à adição de valores padrão para argumentos a qualquer
método ou função local.

Saiba mais sobre parâmetros padrão em expressões lambda no artigo sobre expressões
lambda.

Alias de qualquer tipo


Você pode usar a diretiva de alias using para alias de qualquer tipo, não apenas tipos
nomeados. Isso significa que você pode criar aliases semânticos para tipos de tupla,
tipos de matriz, tipos de ponteiro ou outros tipos não seguros. Para obter mais
informações, confira a especificação de recurso.

Matrizes embutidas
Matrizes embutidas são usadas pela equipe de runtime e outros autores de biblioteca
para melhorar o desempenho em seus aplicativos. Matrizes embutidas permitem que
um desenvolvedor crie uma matriz de tamanho fixo em um tipo struct . Um struct com
um buffer embutido deve fornecer características de desempenho semelhantes a um
buffer de tamanho fixo não seguro. Você provavelmente não declarará suas próprias
matrizes embutidas, mas as usará de forma transparente quando elas forem expostas
como System.Span<T> ou System.ReadOnlySpan<T> objetos de APIs de runtime.
Uma matriz embutida é declarada semelhante à seguinte struct :

C#

[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer
{
private int _element0;
}

Você os usa como qualquer outra matriz:

C#

var buffer = new Buffer();


for (int i = 0; i < 10; i++)
{
buffer[i] = i;
}

foreach (var i in buffer)


{
Console.WriteLine(i);
}

A diferença é que o compilador pode aproveitar as informações conhecidas sobre uma


matriz embutida. Você provavelmente consumirá matrizes embutidas como faria com
qualquer outra matriz. Para obter mais informações sobre como declarar matrizes
embutidas, confira a referência de linguagem sobre tiposstruct.

Atributo experimental
Tipos, métodos ou assemblies podem ser marcados com a indicação
System.Diagnostics.CodeAnalysis.ExperimentalAttribute de um recurso experimental. O
compilador emitirá um aviso se você acessar um método ou digitar anotado com o
ExperimentalAttribute. Todos os tipos incluídos em um assembly marcado com o
atributo Experimental são experimentais. Você pode ler mais no artigo sobre Atributos
gerais lidos pelo compilador ou na especificação de recursos.

Interceptores

2 Aviso
Os interceptores são um recurso experimental, disponível no modo de visualização
com C# 12. O recurso pode estar sujeito a alterações significativas ou à remoção
em uma versão futura. Portanto, não é recomendável para aplicativos de produção
ou liberados.

Para usar interceptadores, o projeto do usuário deve especificar a propriedade


<InterceptorsPreviewNamespaces> . Essa é uma lista de namespaces que têm

permissão para conter interceptadores.

Por exemplo:
<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);Microsoft.AspN

etCore.Http.Generated;MyLibrary.Generated</InterceptorsPreviewNamespaces>

Um interceptador é um método que pode substituir declarativamente uma chamada a


um método interceptável por uma chamada a si mesmo em tempo de compilação. Essa
substituição ocorre pelo fato de o interceptador declarar os locais de origem das
chamadas que ele intercepta. Os interceptadores fornecem uma instalação limitada para
alterar a semântica do código existente adicionando um novo código a uma
compilação, por exemplo, em um gerador de origem.

Você usa um interceptador como parte de um gerador de origem para modificar uma
compilação de origem existente, em vez de adicionar código a ela. O gerador de fontes
de dados substitui as chamadas para um método interceptável por uma chamada para
o método interceptor.

Se você estiver interessado em experimentar interceptadores, saiba mais lendo a


especificação do recurso . Se você usar o recurso, certifique-se de estar atualizado com
todas as alterações na especificação do recurso para esse recurso experimental. Se o
recurso for finalizado, adicionaremos mais diretrizes neste site.

Confira também
Novidades do .NET 8

6 Colaborar conosco no Comentários do .NET


GitHub O .NET é um projeto código aberto.
A fonte deste conteúdo pode Selecione um link para fornecer
ser encontrada no GitHub, onde comentários:
você também pode criar e
revisar problemas e solicitações
de pull. Para obter mais  Abrir um problema de
informações, confira o nosso
documentação
guia para colaboradores.
 Fornecer comentários sobre o
produto
Novidades do C# 11
Artigo • 15/03/2024

Os seguintes recursos foram adicionados em C# 11:

Literais brutos de cadeia de caracteres


Suporte matemático genérico
Atributos genéricos
Cadeia de caracteres UTF-8 literais
Linhas novas em expressões de interpolação de cadeia de caracteres
Padrões de lista
Tipos de locais de arquivos
Membros necessários
Structs de padrão automático
Correspondência de padrão Span<char> em uma constante string
Escopo estendido nameof
IntPtr numérico
Campos ref e scoped ref
Conversão aprimorada do grupo de métodos para delegado
Ciclo de aviso 7

O C# 11 tem suporte no .NET 7. Para obter mais informações, consulte Controle de


versão da linguagem C#.

Você pode baixar o SDK mais recente do .NET 7 na página de downloads do .NET .
Você também pode baixar o Visual Studio 2022 , que inclui o SDK do .NET 7.

7 Observação

Estamos interessados em seus comentários sobre esses recursos. Se você encontrar


problemas com qualquer um desses novos recursos, crie um problema no
repositório dotnet/roslyn .

Atributos genéricos
Você pode declarar uma classe genérica cuja classe base é System.Attribute. Este recurso
fornece uma sintaxe mais conveniente para atributos que exigem um parâmetro
System.Type. Anteriormente, você precisaria criar um atributo com um Type como
parâmetro de construtor:
C#

// Before C# 11:
public class TypeAttribute : Attribute
{
public TypeAttribute(Type t) => ParamType = t;

public Type ParamType { get; }


}

E para aplicar o atributo, você usa o operador typeof:

C#

[TypeAttribute(typeof(string))]
public string Method() => default;

Usando esse novo recurso, você pode criar um atributo genérico em vez disso:

C#

// C# 11 feature:
public class GenericAttribute<T> : Attribute { }

Em seguida, especifique o tipo de parâmetro para usar o atributo:

C#

[GenericAttribute<string>()]
public string Method() => default;

Você deve fornecer todos os parâmetros de tipo ao aplicar o atributo. Em outras


palavras, o tipo genérico deve ser totalmente construído. No exemplo acima, os
parênteses vazios ( ( e ) ) podem ser omitidos, pois o atributo não tem argumentos.

C#

public class GenericType<T>


{
[GenericAttribute<T>()] // Not allowed! generic attributes must be fully
constructed types.
public string Method() => default;
}

Os argumentos de tipo devem atender às mesmas restrições do operador typeof. Tipos


que exigem anotações de metadados não são permitidos. Por exemplo, os seguintes
tipos não são permitidos como o parâmetro de tipo:

dynamic

string? (ou qualquer tipo de referência anulável)


(int X, int Y) (ou qualquer outro tipo de tupla usando a sintaxe de tupla C#).

Esses tipos não são representados diretamente em metadados. Elas incluem anotações
que descrevem o tipo. Em todos os casos, você pode usar o tipo subjacente em vez
disso:

object para dynamic .


string em vez de string? .

ValueTuple<int, int> em vez de (int X, int Y) .

Suporte matemático genérico


Há vários recursos de linguagem que permitem suporte matemático genérico:

Membros static virtual em interfaces


operadores verificados definidos pelo usuário
operadores de deslocamento flexíveis
operador de deslocamento para a direita sem sinal

Você pode adicionar membros static abstract ou static virtual em interfaces para
definir interfaces que incluam operadores sobrecarregados, outros membros estáticos e
propriedades estáticas. O cenário principal para esse recurso é usar operadores
matemáticos em tipos genéricos. Por exemplo, você pode implementar a interface
System.IAdditionOperators<TSelf, TOther, TResult> em um tipo que implementa o

operator + . Outras interfaces definem outras operações matemáticas ou valores bem

definidos. Você pode aprender sobre a nova sintaxe no artigo sobre interfaces.
Interfaces que incluem métodos static virtual normalmente são interfaces genéricas.
Além disso, a maioria declarará uma restrição de que o parâmetro de tipo implemente a
interface declarada.

Você pode saber mais e experimentar o recurso no tutorial Explorar membros da


interface abstrata estática ou na postagem no blog sobre Versão prévia do recurso no
.NET 6 – matemática genérica .

A matemática genérica criou outros requisitos na linguagem.

Operador de deslocamento para a direita sem sinal: antes do C# 11, para forçar um
deslocamento para a direita sem sinal, você precisaria converter qualquer tipo
inteiro com sinal em um tipo sem sinal, executar a mudança e, em seguida,
converter o resultado de volta para um tipo com sinal. A partir do C# 11, você
pode usar o >>> , o operador de deslocamento sem sinal.
Requisitos de operador de deslocamento para a direita sem sinal: o C# 11 remove o
requisito de que o segundo operando deve ser um int ou implicitamente
conversível para int . Essa alteração permite que os tipos que implementam
interfaces matemáticas genéricas sejam usados nesses locais.
operadores marcados e desmarcados definidos pelo usuário: os desenvolvedores
agora podem definir operadores aritméticos checked e unchecked . O compilador
gera chamadas para a variante correta com base no contexto atual. Você pode ler
mais sobre operadores checked no artigo sobre operadores aritméticos.

Numérico IntPtr e UIntPtr


Os tipos nint e nuint agora têm como alias System.IntPtr e System.UIntPtr,
respectivamente.

Novas linhas em interpolações de cadeia de


caracteres
O texto dentro dos caracteres { e } para uma interpolação de cadeia de caracteres
agora pode abranger várias linhas. O texto entre os marcadores { e } é analisado como
C#. Qualquer C# legal, inclusive novas linhas, é permitido. Esse recurso facilita a leitura
de interpolações de cadeia de caracteres que usam expressões C# mais longas, como
expressões switch de padrões correspondentes ou consultas LINQ.

Você pode saber mais sobre o recurso de novas linhas no artigo de interpolações de
cadeia de caracteres na referência de linguagem.

Padrões de lista
Padrões de lista estendem a correspondência de padrões para corresponder a
sequências de elementos em uma lista ou em uma matriz. Por exemplo, sequence is [1,
2, 3] é true quando a sequence é um matriz ou uma lista de três inteiros (1, 2 e 3).

Você pode fazer a correspondência de elementos usando qualquer padrão, inclusive


uma constante, tipo, propriedade e padrões relacionais. O padrão de descarte ( _ )
corresponde a qualquer elemento único e o novo padrão de intervalo ( .. ) corresponde
a qualquer sequência de zero ou mais elementos.
Você pode saber mais detalhes sobre padrões de lista no artigo de padrões
correspondentes na referência de linguagem.

Conversão aprimorada do grupo de métodos


para delegado
O padrão C# em conversões de grupo de métodos agora inclui o seguinte item:

A conversão é permitida (mas não é necessária) para usar uma instância


delegada existente que já contenha essas referências.

As versões anteriores do padrão proibiam o compilador de reutilizar o objeto delegado


criado para uma conversão de grupo de métodos. O compilador C# 11 armazena em
cache o objeto delegado criado de uma conversão de grupo de métodos e reutiliza esse
único objeto delegado. Esse recurso foi disponibilizado pela primeira vez no Visual
Studio 2022 versão 17.2 como uma versão prévia do recurso e no .NET 7 Versão Prévia
2.

Literais de cadeia de caracteres bruta


Literais de cadeia de caracteres bruta são um novo formato para literais de cadeia de
caracteres. Literais de cadeia de caracteres bruta podem conter texto arbitrário,
incluindo espaços em branco, novas linhas, aspas inseridas e outros caracteres especiais
sem a necessidade de sequências de escape. Um literal de cadeia de caracteres bruta
começa com pelo menos três caracteres de aspas duplas ("""). Ele termina com o
mesmo número de caracteres de aspas duplas. Normalmente, um literal de cadeia de
caracteres bruta usa três aspas duplas em uma única linha para iniciar a cadeia de
caracteres e três aspas duplas em uma linha separada para terminar a cadeia de
caracteres. As novas linhas que seguem as aspas de abertura e precedem as aspas de
fechamento não estão incluídas no conteúdo final:

C#

string longMessage = """


This is a long message.
It has several lines.
Some are indented
more than others.
Some should start at the first column.
Some have "quoted text" in them.
""";
Qualquer espaço em branco à esquerda das aspas duplas de fechamento será removido
do literal da cadeia de caracteres. Os literais da cadeia de caracteres bruta podem ser
combinados com a interpolação de cadeia de caracteres para incluir chaves no texto de
saída. Vários caracteres $ indicam quantas chaves consecutivas iniciam e terminam a
interpolação:

C#

var location = $$"""


You are at {{{Longitude}}, {{Latitude}}}
""";

O exemplo anterior especifica que duas chaves iniciam e terminam uma interpolação. A
terceira chave de abertura e de fechamento repetidas são incluídas na cadeia de
caracteres de saída.

Você pode saber mais sobre literais de cadeia de caracteres bruta no artigo sobre
cadeias de caracteres no guia de programação e nos artigos de referência de linguagem
sobre literais de cadeia de caracteres e cadeias de caracteres interpoladas.

Structs de padrão automático


O compilador C# 11 garante que todos os campos de um tipo struct sejam
inicializados para seu valor padrão como parte da execução de um construtor. Essa
alteração significa que qualquer campo ou propriedade automática não inicializados por
um construtor são inicializados automaticamente pelo compilador. Os structs em que o
construtor não atribui definitivamente todos os campos agora são compilados, e todos
os campos não inicializados explicitamente são definidos para o valor padrão. Você
pode ler mais sobre como essa alteração afeta a inicialização de structs no artigo sobre
structs.

Correspondência de padrão Span<char> e


ReadOnlySpan<char> em uma constante string
Você conseguiu testar se um string tinha um valor constante específico usando
padrões correspondentes para várias versões. Agora você pode usar a mesma lógica de
padrões correspondentes com variáveis que são Span<char> ou ReadOnlySpan<char> .

Escopo nameof estendido


Nomes de parâmetros de tipo e nomes de parâmetro agora estão no escopo quando
usados em uma expressão nameof em uma declaração de atributo nesse método. Essa
funcionalidade significa que você pode usar o operador nameof para especificar o nome
de um parâmetro de método em um atributo na declaração de método ou parâmetro.
Esse recurso costuma ser útil para adicionar atributos para análise anulável.

Cadeia de caracteres UTF-8 literais


Você pode especificar o sufixo u8 em um literal de cadeia de caracteres para especificar
a codificação de caracteres UTF-8. Se o aplicativo precisar de cadeias de caracteres UTF-
8, para constantes de cadeia de caracteres HTTP ou protocolos de texto semelhantes,
você pode usar esse recurso para simplificar a criação de cadeias de caracteres UTF-8.

Você pode saber mais sobre literais de cadeia de caracteres UTF-8 na seção literal de
cadeia de caracteres do artigo sobre tipos de referência internos.

Membros necessários
Você pode adicionar o modificador required a propriedades e campos para impor
construtores e chamadores para inicializar esses valores. O
System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute pode ser adicionado
aos construtores para informar ao compilador que um construtor inicializa todos os
membros necessários.

Para obter mais informações sobre os membros necessários, consulte a seção somente
init do artigo de propriedades.

Campos ref e variáveis ref scoped


Você pode declarar campos ref dentro de uma classe ref struct. Isso dá suporte a tipos
como System.Span<T> sem atributos especiais ou tipos internos ocultos.

Você pode adicionar o modificador scoped a qualquer declaração ref . Isso limita o
escopo para o qual a referência pode escapar.

Tipos de locais de arquivo


A partir do C# 11, você pode usar o modificador de acesso file para criar um tipo cuja
visibilidade está no escopo do arquivo de origem no qual ele é declarado. Esse recurso
ajuda os autores do gerador de origem a evitar colisões de nomenclatura. Você pode
saber mais sobre esse recurso no artigo sobre tipos com escopo de arquivo na
referência de linguagem.

Confira também
Novidades do .NET 7

6 Colaborar conosco no Comentários do .NET


GitHub O .NET é um projeto código aberto.
A fonte deste conteúdo pode Selecione um link para fornecer
ser encontrada no GitHub, onde comentários:
você também pode criar e
revisar problemas e solicitações  Abrir um problema de
de pull. Para obter mais documentação
informações, confira o nosso
guia para colaboradores.  Fornecer comentários sobre o
produto
Novidades do C# 10
Artigo • 10/05/2023

O C# 10 adiciona os seguintes recursos e aprimoramentos à linguagem C#:

Estruturas de registro
Aprimoramentos de tipos de estrutura
Manipuladores de cadeia de caracteres interpolada
Diretivas global using
Declaração de namespace com escopo de arquivo
Padrões de propriedade estendida
Aprimoramentos nas expressões lambda
Permissão de cadeias de caracteres interpoladas const
Tipos de registro podem selar ToString()
Atribuição definida aprimorada
Permissão de atribuição e declaração na mesma desconstrução
Permissão do atributo AsyncMethodBuilder em métodos
Atributo CallerArgumentExpression
Pragma #line aprimorado
Ciclo de aviso 6

O C# 10 tem suporte no .NET 6. Para obter mais informações, confira Controle de


versão da linguagem C#.

Você pode baixar o SDK mais recente do .NET 6 na página de downloads do .NET .
Você também pode baixar o Visual Studio 2022 , que inclui o SDK do .NET 6.

7 Observação

Estamos interessados em seus comentários sobre esses recursos. Se você encontrar


problemas com qualquer um desses novos recursos, crie um problema no
repositório dotnet/roslyn .

Structs de registro
Você pode declarar registros de tipo de valor usando as declarações record struct ou
readonly record struct. Agora você pode esclarecer que record é um tipo de referência
com a declaração record class .
Aprimoramentos de tipos de estrutura
O C# 10 apresenta as seguintes melhorias relacionadas aos tipos de estrutura:

Você pode declarar um construtor sem parâmetros de instância em um tipo de


estrutura e inicializar um campo ou propriedade de instância na declaração. Para
obter mais informações, consulte a seção Inicialização de struct e valores padrão
do artigo Tipos de estrutura.
Um operando à esquerda da expressão with pode ser de qualquer tipo de
estrutura ou um tipo anônimo (referência).

Manipuladores de cadeia de caracteres


interpolada
Você pode criar um tipo que compila a cadeia de caracteres resultante de uma
expressão de cadeia de caracteres interpolada. As bibliotecas .NET usam esse recurso
em muitas APIs. Você pode criar um seguindo este tutorial.

Diretivas using globais


Você pode adicionar o modificador global a qualquer diretiva de uso para instruir o
compilador ao qual a diretiva se aplica a todos os arquivos de origem na compilação.
Normalmente, são todos os arquivos de origem em um projeto.

Declaração de namespace com escopo de


arquivo
Você pode usar uma nova forma da declaração namespace para declarar que todas as
seguintes declarações são membros do namespace declarado:

C#

namespace MyNamespace;

Essa nova sintaxe salva espaço horizontal e vertical para declarações namespace .

Padrões de propriedade estendida


A partir do C# 10, você pode referenciar propriedades aninhadas ou campos dentro de
um padrão de propriedade. Por exemplo, um padrão do formulário

C#

{ Prop1.Prop2: pattern }

é válido em C# 10 e posterior e equivalente a

C#

{ Prop1: { Prop2: pattern } }

válido em C# 8.0 e posterior.

Para obter mais informações, confira a nota de proposta de recursos de Padrões de


propriedade estendida. Para obter mais informações sobre um padrão de propriedade,
confira a seção Padrão de propriedade do artigo Padrões.

Aprimoramentos da expressão lambda


O C# 10 inclui muitas melhorias na forma como as expressões lambda são tratadas:

As expressões Lambda podem ter um tipo natural, em que o compilador pode


inferir um tipo delegado da expressão lambda ou do grupo de métodos.
As expressões Lambda podem declarar um tipo de retorno quando o compilador
não pode inferi-lo.
Os atributos podem ser aplicados a expressões lambda.

Esses recursos tornam as expressões lambda mais semelhantes a métodos e funções


locais. Elas facilitam o uso de expressões lambda sem declarar uma variável de um tipo
delegado e funcionam mais perfeitamente com as novas APIs mínimas de ASP.NET
Core.

Cadeias de caracteres interpoladas constantes


No C# 10, as cadeias de caracteres const poderão ser inicializadas usando a
interpolação de cadeia de caracteres se todos os espaços reservados forem cadeias de
caracteres constantes. A interpolação de cadeia de caracteres pode criar cadeias de
caracteres constantes mais legíveis à medida que elas são criadas e usadas no aplicativo.
As expressões de espaço reservado não podem ser constantes numéricas porque essas
constantes são convertidas em cadeias de caracteres em tempo de execução. A cultura
atual pode afetar a representação de cadeia de caracteres. Saiba mais na referência de
linguagem sobre expressõesconst.

Tipos de registro podem selar ToString


No C# 10, você pode adicionar o modificador sealed ao substituir ToString em um tipo
de registro. Selar o método ToString impede que o compilador sintetize um método
ToString para qualquer tipo de registro derivado. Um sealed ToString garante que
todos os tipos de registro derivados usem o método ToString definido em um tipo de
registro base comum. Você pode saber mais sobre esse recurso no artigo sobre
registros.

Atribuição e declaração na mesma


desconstrução
Essa alteração remove uma restrição de versões anteriores do C#. Anteriormente, uma
desconstrução poderia atribuir todos os valores a variáveis existentes ou inicializar
variáveis recém-declaradas:

C#

// Initialization:
(int x, int y) = point;

// assignment:
int x1 = 0;
int y1 = 0;
(x1, y1) = point;

O C# 10 remove essa restrição:

C#

int x = 0;
(x, int y) = point;

Atribuição definitiva aprimorada


Antes do C# 10, havia muitos cenários em que a atribuição definitiva e a análise de
estado nulo produziam avisos que eram falsos positivos. Geralmente, elas envolviam
comparações com constantes boolianas, acesso a uma variável somente nas instruções
true ou false em uma instrução if e expressões de associação nulas. Esses exemplos

geraram avisos em versões anteriores do C#, mas não no C# 10:

C#

string representation = "N/A";


if ((c != null && c.GetDependentValue(out object obj)) == true)
{
representation = obj.ToString(); // undesired error
}

// Or, using ?.
if (c?.GetDependentValue(out object obj) == true)
{
representation = obj.ToString(); // undesired error
}

// Or, using ??
if (c?.GetDependentValue(out object obj) ?? false)
{
representation = obj.ToString(); // undesired error
}

O principal impacto dessa melhoria é que os avisos para atribuição definitiva e análise
de estado nulo são mais precisos.

Permitir atributo AsyncMethodBuilder em


métodos
No C# 10 e posterior, você pode especificar um construtor de métodos assíncrono
diferente para um método único, além de especificar o tipo de construtor de métodos
para todos os métodos que retornam um determinado tipo de tarefa. Um construtor de
métodos assíncrono personalizado permite cenários avançados de ajuste de
desempenho em que um determinado método pode se beneficiar de um construtor
personalizado.

Para saber mais, confira a seção sobre AsyncMethodBuilder no artigo que aborda
atributos lidos pelo compilador.

Diagnóstico de atributo
CallerArgumentExpression
Você pode usar o parâmetro
System.Runtime.CompilerServices.CallerArgumentExpressionAttribute para especificar
um parâmetro que o compilador substitui pela representação de texto de outro
argumento. Esse recurso permite que as bibliotecas criem diagnósticos mais específicos.
O código a seguir testa uma condição. Se a condição for falsa, a mensagem de exceção
conterá a representação de texto do argumento passado para condition :

C#

public static void Validate(bool condition,


[CallerArgumentExpression("condition")] string? message=null)
{
if (!condition)
{
throw new InvalidOperationException($"Argument failed validation:
<{message}>");
}
}

Você pode saber mais sobre esse recurso no artigo sobre Atributos de informações do
chamador na seção de referência de linguagem.

Pragma de #line avançado


O C# 10 dá suporte a um novo formato para o pragma #line . Você provavelmente não
usará o novo formato, mas verá os respectivos. Os aprimoramentos permitem uma saída
mais refinada em DSLs (linguagens específicas do domínio), como Razor. O mecanismo
Razor usa essas melhorias para aprimorar a experiência de depuração. Você encontrará
depuradores que podem realçar a fonte Razor com mais precisão. Para saber mais sobre
a nova sintaxe, confira o artigo sobre Diretivas de pré-processador na referência de
linguagem. Você também pode ler a especificação de recurso para exemplos baseados
em Razor.
Saiba mais sobre alterações
interruptivas no compilador C#
Artigo • 10/05/2023

Você pode localizar alterações significativas desde a versão do C# 10 aqui.

A equipe da Roslyn mantém uma lista de alterações interruptivas nos compiladores


do C# e do Visual Basic. Você pode encontrar informações sobre essas alterações nesses
links no repositório GitHub:

Alterações interruptivas em Roslyn no C# 10.0/.NET 6


Alterações interruptivas em Roslyn após .NET 5
Alterações interruptivas em VS2019 versão 16.8 introduzidas com o .NET 5 e o C#
9.0
Alterações interruptivas no VS2019 Atualização 1 e posteriores em comparação
com o VS2019
Alterações interruptivas desde o VS2017 (C# 7)
Alterações interruptivas no Roslyn 3.0 (VS2019) do Roslyn 2.* (VS2017)
Alterações interruptivas no Roslyn 2.0 (VS2017) do Roslyn 1. * (VS2015) e do
compilador nativo C# (VS2013 e anterior).
Alterações interruptivas no Roslyn 1.0 (VS2015) do compilador nativo C# (VS2013 e
anterior).
Alteração de versão Unicode no C# 6
O histórico da linguagem C#
Artigo • 06/03/2024

Este artigo fornece um histórico de cada versão principal da linguagem C#. A equipe C#
continua a inovar e a adicionar novos recursos. Os status detalhados do recurso de
linguagem, incluindo os recursos considerados para as versões futuras, podem ser
encontrados no repositório dotnet/roslyn no GitHub.

) Importante

A linguagem C# depende de tipos e métodos nos quais a especificação C# é


definida como uma biblioteca padrão para alguns dos recursos. A plataforma .NET
fornece esses tipos e métodos em alguns pacotes. Um exemplo é o processamento
de exceção. Cada instrução ou expressão throw é verificada para garantir que o
objeto que está sendo gerado é derivado de Exception. Da mesma forma, cada
catch é verificado para garantir que o tipo que está sendo capturado é derivado

de Exception. Cada versão pode adicionar novos requisitos. Para usar os recursos
de linguagem mais recentes em ambientes mais antigos, talvez seja necessário
instalar bibliotecas específicas. Essas dependências estão documentadas na página
de cada versão específica. Saiba mais sobre as relações entre linguagem e
biblioteca para obter informações sobre essa dependência.

C# versão 12
Lançado em novembro de 2023

Os seguintes recursos foram adicionados no C# 12:

Construtores primários – você pode criar construtores primários em qualquer tipo


class ou struct .

Expressões de coleção – uma nova sintaxe para especificar expressões de coleção,


incluindo o operador de distribuição, ( .. ), para expandir qualquer coleção.
Matrizes embutidas – permitem que um desenvolvedor crie uma matriz de
tamanho fixo em um tipo struct .
Parâmetros opcionais em expressões lambda – você pode definir valores padrão
para parâmetros em expressões lambda.
ref readonly parâmetros - ref readonly – permitem mais clareza para APIs que
possam estar usando parâmetros ref ou parâmetros in .
Alias de qualquer tipo – Você pode usar a diretiva de alias using para alias de
qualquer tipo, não apenas tipos nomeados.
Atributo experimental – indica um recurso experimental.

E Interceptores – foi lançado como Versão prévia do recurso.

No geral, o C# 12 fornece novos recursos que tornam você mais produtivo ao


escrevercódigo C#. A sintaxe que você já conhecia está disponível em mais locais. Outra
sintaxe permite consistência para conceitos relacionados.

C# versão 11
Lançado em novembro de 2022

Os seguintes recursos foram adicionados em C# 11:

Literais brutos de cadeia de caracteres


Suporte matemático genérico
Atributos genéricos
Cadeia de caracteres UTF-8 literais
Linhas novas em expressões de interpolação de cadeia de caracteres
Padrões de lista
Tipos de locais de arquivos
Membros necessários
Structs de padrão automático
Correspondência de padrão Span<char> em uma constante string
Escopo estendido nameof
IntPtr numérico
Campos ref e scoped ref
Conversão aprimorada do grupo de métodos para delegado
Ciclo de aviso 7

O C# 11 apresenta matemática genérica e vários recursos que dão suporte a essa meta.
Você pode escrever algoritmos numéricos uma vez para todos os tipos de número. Há
mais recursos para facilitar o trabalho com os tipos de struct , como membros
necessários e structs de padrão automático. O trabalho com cadeias de caracteres fica
mais fácil com literais de cadeia de caracteres brutas, nova linha em interpolações de
cadeia de caracteres e literais de cadeia de caracteres UTF-8. Recursos como os tipos de
locais de arquivo permitem que os geradores de origem sejam mais simples. Por fim, os
padrões de lista adicionam mais suporte para correspondência de padrões.
C# versão 10
Lançado em novembro de 2021

O C# 10 adiciona os seguintes recursos e aprimoramentos à linguagem C#:

Estruturas de registro
Aprimoramentos de tipos de estrutura
Manipuladores de cadeia de caracteres interpolada
Diretivas global using
Declaração de namespace com escopo de arquivo
Padrões de propriedade estendida
Aprimoramentos nas expressões lambda
Permissão de cadeias de caracteres interpoladas const
Tipos de registro podem selar ToString()
Atribuição definida aprimorada
Permissão de atribuição e declaração na mesma desconstrução
Permissão do atributo AsyncMethodBuilder em métodos
Atributo CallerArgumentExpression
Pragma #line aprimorado

Mais recursos estavam disponíveis no modo de visualização. Para usar esses recursos,
você deve definir <LangVersion> como Preview em seu projeto:

Atributos genéricos, posteriormente neste artigo.


membros abstratos estáticos em interfaces.

C# 10 continua trabalhando em temas de remoção de cerimônia, separação de dados


de algoritmos e aprimoramento do desempenho para o Runtime do .NET.

Muitos dos recursos significam que você digitará menos código para expressar os
mesmos conceitos. Structs de registro sintetizam muitos dos mesmos métodos que as
classes de registro. Structs e tipos anônimos dão suporte a expressões. Diretivas de uso
global e declarações de namespace com escopo de arquivo significam expressar
dependências e organização de namespace com mais clareza. Melhorias lambda
facilitam a declaração de expressões lambda onde são usadas. Novos padrões de
propriedade e melhorias de desconstrução criam um código mais conciso.

Os novos manipuladores de cadeia de caracteres interpolados e o comportamento


AsyncMethodBuilder podem melhorar o desempenho. Esses recursos de linguagem

foram aproveitados no .NET Runtime para obter aprimoramentos de desempenho no


.NET 6.
C# 10 também marca uma mudança para a cadência anual de versões do .NET. Como
nem todos os recursos podem ser concluídos em um período anual, você pode
experimentar alguns recursos de "versão prévia" no C# 10. Os atributos genéricos e os
membros abstratos estáticos em interfaces podem ser usados, mas eles são versões
prévias do recurso e podem ser alterados antes da versão final.

C# versão 9
Lançado em novembro de 2020

C# 9 foi lançada com o .NET 5. É a versão de linguagem padrão para qualquer assembly
direcionado à versão do .NET 5. Ela contém os seguintes recursos novos e aprimorados:

Registros
Setters somente init
Instruções de nível superior
Aprimoramentos nos padrões correspondentes: padrões relacionais e padrões
lógicos
Desempenho e interoperabilidade
Inteiros de tamanho nativo
Ponteiros de função
Suprimir a emissão do sinalizador localsinit
Inicializadores de módulo
Novos recursos para métodos parciais
Ajustar e concluir recursos
Expressões com tipo de destino new
static funções anônimas
Expressão condicional com tipo de destino
Tipos de retorno covariantes
Suporte à extensão GetEnumerator para loops foreach
Parâmetros discard de lambda
Atributos em funções locais

C# 9 continua três dos temas de versões anteriores: remoção da cerimônia, separação


dos dados de algoritmos e fornecimento de mais padrões em mais lugares.

Instruções de nível superior significam que seu programa principal é mais simples de ler.
Há menos necessidade de cerimônia: um namespace, uma classe Program e static void
Main() são todos desnecessários.

A introdução de records fornece uma sintaxe concisa para tipos de referência que
seguem semântica de valor para manter a igualdade. Você usará esses tipos para definir
contêineres de dados que normalmente definem o comportamento mínimo. Os setters
somente init fornecem o recurso de mutação não destrutiva (expressões with ) nos
registros. C# 9 também adiciona tipos de retorno covariantes para que os registros
derivados possam substituir métodos virtuais e retornar um tipo derivado do tipo de
retorno do método base.

Os recursos de padrões correspondentes foram expandidos de várias maneiras. Agora,


os tipos numéricos dão suporte aos padrões de intervalo. Os padrões podem ser
combinados usando padrões and , or e not . É possível adicionar parênteses para
esclarecer padrões mais complexos:

O C# 9 inclui novas melhorias de correspondência de padrões:

Padrões de tipo correspondem a um objeto correspondente a um tipo específico


Padrões entre parênteses impõem ou enfatizam a precedência de combinações de
padrões
Padrões conjuntivos and exigem que ambos os padrões correspondam
Padrões conjuntivos or exigem que ambos os padrões correspondam
Padrões conjuntivos not exigem que ambos os padrões correspondam
Os padrões relacionais exigem que a entrada seja menor que, maior que, menor
ou igual ou maior que ou igual a uma determinada constante

Esses padrões enriquecem a sintaxe para padrões. Considere estes exemplos:

C#

public static bool IsLetter(this char c) =>


c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';

Com parênteses opcionais para deixar claro que and tem precedência maior que or :

C#

public static bool IsLetterOrSeparator(this char c) =>


c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ',';

Um dos usos mais comuns é uma nova sintaxe para uma verificação nula:

C#

if (e is not null)
{
// ...
}
Qualquer um desses padrões pode ser usado em qualquer contexto em que os padrões
são permitidos: expressões de padrão is , expressões switch , padrões aninhados e o
padrão de uma instrução switch do rótulo case .

Outro conjunto de recursos dá suporte à computação de alto desempenho em C#:

Os tipos nint e nuint modelam os tipos inteiros de tamanho nativo na CPU de


destino.
Os ponteiros de função fornecem funcionalidade semelhante a delegado, evitando
as alocações necessárias para criar um objeto delegado.
A instrução localsinit pode ser omitida para salvar instruções.

Desempenho e interoperabilidade
Outro conjunto de aprimoramentos dá suporte a cenários em que os geradores de
código adicionam funcionalidade:

Inicializadores de módulo são métodos que o runtime chama quando um


assembly é carregado.
Métodos parciais dão suporte a novos modificadores acessíveis e tipos de retorno
não nulos. Nesses casos, uma implementação deve ser fornecida.

Ajustar e concluir recursos


C# 9 adiciona muitos outros pequenos recursos que melhoram a produtividade do
desenvolvedor, escrevendo e lendo código:

Expressões new de tipo de destino


Funções anônimas de static
Expressão condicional com tipo de destino
Suporte a GetEnumerator() de extensão para loops foreach
Expressões Lambda podem declarar parâmetros de descarte
Atributos podem ser aplicados a funções locais

A versão C# 9 continua o trabalho para manter C# uma linguagem de programação


moderna e de uso geral. Os recursos continuam a dar suporte a cargas de trabalho e
tipos de aplicativo modernos.

C# versão 8.0
Lançado em setembro de 2019
C# 8.0 é a primeira grande versão em C# que tem como destino especificamente o .NET
Core. Alguns recursos dependem de novos recursos de Common Language Runtime
(CLR), outros, de tipos de biblioteca adicionados somente no .NET Core. O C# 8.0
adiciona os seguintes recursos e aprimoramentos à linguagem C#:

Membros somente leitura


Métodos de interface padrão
Aprimoramentos de correspondência de padrões:
Expressões Switch
Padrões da propriedade
Padrões de tupla
Padrões posicionais
Declarações using
Funções locais estáticas
Estruturas ref descartáveis
Tipos de referência anuláveis
Fluxos assíncronos
Índices e intervalos
Atribuição de coalescência nula
Tipos construídos não gerenciados
Stackalloc em expressões aninhadas
Aprimoramento de cadeias de caracteres verbatim interpoladas

Os membros de interface padrão exigem aprimoramentos na CLR. Esses recursos foram


adicionados na CLR para .NET Core 3.0. Intervalos e índices, além de fluxos assíncronos,
exigem novos tipos nas bibliotecas do .NET Core 3.0. Tipos de referência anuláveis,
implementados no compilador, são muito mais úteis quando bibliotecas são anotadas
para fornecer informações semânticas sobre o estado nulo de argumentos e valores
retornados. Essas anotações estão sendo adicionadas nas bibliotecas do .NET Core.

C# versão 7.3
Lançado em maio de 2018

Há dois temas principais para a versão C# 7.3. Um tema fornece recursos que permitem
que o código seguro tenha o mesmo desempenho que o código não seguro. O
segundo tema fornece melhorias incrementais aos recursos existentes. Novas opções do
compilador também foram adicionadas a essa versão.

Os novos recursos a seguir são compatíveis com o tema de melhor desempenho para
código seguro:
Você pode acessar campos fixos sem fixação.
Você pode reatribuir variáveis locais ref .
Você pode usar inicializadores em matrizes stackalloc .
Você pode usar instruções fixed com qualquer tipo compatível com um padrão.
Você pode usar mais restrições genéricas.

Os seguintes recursos e aprimoramentos foram feitos nos recursos existentes:

Você pode testar == e != com tipos de tupla.


Você pode usar variáveis de expressão em mais locais.
Você pode anexar atributos ao campo de suporte de propriedades
autoimplementadas.
A resolução de métodos quando os argumentos se diferenciam por in foi
aprimorada.
A resolução de sobrecarga agora tem menos casos ambíguos.

As novas opções do compilador são:

-publicsign para habilitar a assinatura de Software de código aberto (OSS) de


assemblies.
-pathmap para fornecer um mapeamento para diretórios de origem.

C# versão 7.2
Lançado em novembro de 2017

C# 7.2 adicionou vários recursos de linguagem menores:

Inicializadores em matrizes stackalloc .


Uso de instruções fixed com qualquer tipo compatível com um padrão.
Acesso a campos fixos sem fixação.
Reatribuição de variáveis locais ref .
Declaração de tipos readonly struct , para indicar que uma struct é imutável e
deve ser passada como um parâmetro in para seus métodos de membro.
Adição do modificador in em parâmetros, para especificar que um argumento
seja passado por referência, mas não modificado pelo método chamado.
Uso do modificador ref readonly nos retornos de método, para indicar que um
método retorna seu valor por referência, mas não permite gravações nesse objeto.
Declaração de tipos ref struct , para indicar que um tipo de struct acessa a
memória gerenciada diretamente e deve sempre ser alocado por pilha.
Uso de restrições genéricas adicionais.
Argumentos nomeados que não estejam à direita::
Os argumentos nomeados podem ser seguidos por argumentos posicionais.
Sublinhados à esquerda em literais numéricos:
Agora os literais numéricos podem ter sublinhados à esquerda, antes dos
dígitos impressos.
private protectedmodificadores de acesso:
O modificador de acesso private protected permite o acesso a classes
derivadas no mesmo assembly.
Expressões ref condicionais:
O resultado de uma expressão condicional ( ?: ) agora já pode ser uma
referência.

C# versão 7.1
Lançado em agosto de 2017

C# começou a lançar versões de ponto com C# 7.1. Essa versão adiciona a o elemento de
configuração de seleção de versão da linguagem, três novos recursos de linguagem e
um novo comportamento do compilador.

Os novos recursos de linguagem nesta versão são:

asyncMain método
O ponto de entrada para um aplicativo pode ter o modificador async .
Expressões literais default
Use expressões literais padrão em expressões de valor padrão quando o tipo de
destino pode ser inferido.
Nomes de elementos de tupla inferidos
Em muitos casos, os nomes dos elementos de tupla podem ser inferidos com
base na inicialização da tupla.
Restrições em parâmetros de tipo genérico
Você pode usar expressões de correspondência de padrão em variáveis cujo
tipo é um parâmetro de tipo genérico.

Por fim, o compilador traz duas opções -refout e -refonly, que controlam a geração de
assembly de referência.

C# versão 7.0
Lançado em março de 2017
A versão 7.0 de C# foi lançada com o Visual Studio 2017. Esta versão tem algumas
coisas interessantes e evolutivas na mesma direção que o C# 6.0. Aqui estão alguns dos
novos recursos:

Variáveis out
Tuplas e desconstrução
Correspondência de padrões
Funções locais
Membros aptos para expressão expandidos
Ref locals
Retornos de referências

Outros recursos incluíam:

Descartes
Literais binários e os separadores de dígito
Expressões throw

Todas essas funcionalidades oferecem novos recursos para desenvolvedores e a


oportunidade de escrever um código mais limpo do que nunca. Um ponto alto é a
condensação da declaração de variáveis a serem usadas com a palavra-chave out e a
permissão de vários valores retornados por meio de tupla. Agora o .NET Core tem
qualquer sistema operacional como destino e tem a visão firme na nuvem e na
portabilidade. Essas novas funcionalidades certamente ocupam a mente e o tempo dos
designers da linguagem, além de levarem a novos recursos.

C# versão 6.0
Lançado em julho de 2015

A versão 6.0, lançada com o Visual Studio 2015, trouxe muitos recursos menores que
tornaram a programação em C# mais produtiva. Eis algumas delas:

Importações estáticas
Filtros de exceção
Inicializadores de propriedade automática
Membros aptos para expressão
Propagador nulo
Interpolação de cadeia de caracteres
Operador nameof

Outros novos recursos incluem:


Inicializadores de índice
Await em blocos catch/finally
Valores padrão para propriedades somente getter

Mas, se você os observar em conjunto, verá um padrão interessante. Nesta versão, o C#


começou a eliminar o clichê de linguagem para tornar o código mais conciso e legível.
Portanto, para os fãs de código simples e conciso, essa versão da linguagem foi um
grande benefício.

Fizeram ainda outra coisa com esta versão, embora não seja um recurso de linguagem
tradicional em si. Lançaram Roslyn, o compilador como um serviço . Agora o
compilador de C# é escrito em C#, e você pode usar o compilador como parte de seus
esforços de programação.

C# versão 5.0
Lançado em agosto de 2012

A versão 5.0 de C#, lançada com o Visual Studio 2012, era uma versão focada da
linguagem. Quase todo o esforço para essa versão foi dedicado a outro conceito
inovador de linguagem: os modelos async e await para programação assíncrona. Aqui
está a lista dos recursos principais:

Membros assíncronos
Atributos de informações do chamador
Code Project: Caller Info Attributes in C# 5.0 (Code Project: Atributos de
informações do chamador em C# 5.0)

O atributo de informações do chamador permite facilmente recuperar informações


sobre o contexto no qual você está executando sem recorrer a uma infinidade de
código de reflexão clichê. Ele tem muitos usos em diagnóstico e tarefas de registro em
log.

Mas async e await são as verdadeiras estrelas dessa versão. Quando esses recursos
foram lançados em 2012, o C# virou o jogo novamente, implantando a assincronia na
linguagem como uma participante da maior importância.

C# versão 4.0
Lançado em abril de 2010
O C# versão 4.0, lançado com o Visual Studio 2010, introduziu alguns novos recursos
interessantes:

Associação dinâmica
Argumentos opcionais/nomeados
Genérico covariante e contravariante
Tipos de interoperabilidade inseridos

Tipos de interoperabilidade inseridos facilitaram os problemas de implantação da


criação de assemblies de interoperabilidade COM para seu aplicativo. A contravariância
e a covariância genérica oferecem maior capacidade para usar genéricos, mas eles são
um tanto acadêmicos e provavelmente mais apreciados por autores de estruturas e
bibliotecas. Os parâmetros nomeados e opcionais permitem eliminar várias sobrecargas
de método e oferecem conveniência. Mas nenhum desses recursos é exatamente uma
alteração de paradigma.

O recurso principal foi a introdução da palavra-chave dynamic . A palavra-chave dynamic


introduziu na versão 4.0 do C# a capacidade de substituir o compilador na tipagem em
tempo de compilação. Com o uso da palavra-chave dinâmica, você pode criar
constructos semelhantes a linguagens dinamicamente tipadas, como JavaScript. Você
pode criar um dynamic x = "a string" e, em seguida, adicionar seis a ela, deixando que
o runtime decida o que acontece em seguida.

Associação dinâmica tem potencial de erros, mas também grande eficiência na


linguagem.

C# versão 3.0
Lançado em novembro de 2007

O C# versão 3.0 chegou no final de 2007, juntamente com o Visual Studio 2008, porém
o pacote completo de recursos de linguagem veio, na verdade, com o C# versão 3.5.
Esta versão foi o marco de uma alteração significativa no crescimento do C#. Ela
estabeleceu o C# como uma linguagem de programação realmente formidável. Vamos
dar uma olhada em alguns recursos importantes nesta versão:

Propriedades autoimplementadas
Tipos anônimos
Expressões de consulta
Expressões lambda
Árvores de expressão
Métodos de extensão
Variáveis locais de tipo implícito
Métodos parciais
Inicializadores de objeto e de coleção

Numa retrospectiva, muitos desses recursos parecerem inevitáveis e inseparáveis. Todos


eles se encaixam estrategicamente. O melhor recurso dessa versão do C# era a
expressão de consulta, também conhecida como Consulta Integrada à Linguagem
(LINQ).

Uma exibição mais detalhada analisa árvores de expressão, expressões lambda e tipos
anônimos como a base na qual o LINQ é construído. Mas, de uma forma ou de outra, o
C# 3.0 apresentou um conceito revolucionário. O C# 3.0 começou a definir as bases para
transformar o C# em uma linguagem híbrida orientada a objeto e funcional.

Especificamente, agora você pode escrever consultas declarativas no estilo SQL para
executar operações em coleções, entre outras coisas. Em vez de escrever um loop for
para calcular a média de uma lista de inteiros, agora você pode fazer isso simplesmente
como list.Average() . A combinação de expressões de consulta e métodos de extensão
tornou uma lista de inteiros muito mais inteligente.

C# versão 2.0
Lançado em novembro de 2005

Vamos dar uma olhada em alguns recursos principais do C# 2.0, lançado em 2005, junto
com o Visual Studio 2005:

Genéricos
Tipos parciais
Métodos anônimos
Tipos de valor anuláveis
Iteradores
Covariância e contravariância

Outros recursos do C# 2.0 adicionaram funcionalidades a recursos existentes:

Acessibilidade separada getter/setter


Conversões de grupo de método (delegados)
Classes estáticas
Inferência de delegado

Embora C# possa ter começado como uma linguagem orientada a objeto (OO)
genérica, a versão 2.0 do C# tratou de mudar isso rapidamente. Com genéricos, tipos e
métodos podem operar em um tipo arbitrário enquanto ainda mantêm a segurança de
tipos. Por exemplo, ter um List<T> permite que você tenha List<string> ou List<int>
e execute operações fortemente tipadas nessas cadeias de caracteres ou inteiros
enquanto itera neles. Usar genéricos é melhor do que criar um ListInt que deriva de
ArrayList ou converter de Object para cada operação.

A versão 2.0 do C# trouxe iteradores. Em resumo, o iteradores permitem que você


examine todos os itens em um List (ou outros tipos Enumeráveis) com um loop
foreach . Ter iteradores como uma parte importante da linguagem aprimorou

drasticamente a legibilidade da linguagem e a capacidade das pessoas de raciocinar a


respeito do código.

C# versão 1.2
Lançado em abril de 2003

C# versão 1.2 fornecida com o Visual Studio .NET 2003. Ele continha algumas pequenas
melhorias na linguagem. Muito notável é que, a partir desta versão, o código gerado em
um loop foreach chamou Dispose, em um IEnumerator, quando o IEnumerator
implementou IDisposable.

C# versão 1.0
Lançado em janeiro de 2002

Ao olhar para trás, a versão 1.0 de C#, lançada com o Visual Studio .NET 2002, ficou
muito parecida com o Java. Como parte de suas metas de design declaradas para
ECMA , ela procurou ser uma "linguagem simples, moderna e orientada a objetos para
uso geral". Na época, parecer com Java significava que havia atingido essas metas de
design iniciais.

Mas agora, se examinar novamente a C# 1.0, você poderá se sentir um pouco confuso.
Carecia das funcionalidades assíncronas internas e algumas das funcionalidades
relacionadas a genéricos que você nem valoriza. Na verdade, ela não tinha nada
relacionado a genéricos. E a LINQ? Ainda não estava disponível. Essas adições levariam
alguns anos para sair.

A versão 1.0 do C# parecia ter poucos recursos, em comparação com os dias de hoje.
Você teria que escrever código um tanto detalhado. Mas, ainda assim, você poderia
iniciar em algum lugar. A versão 1.0 do C# era uma alternativa viável ao Java na
plataforma Windows.
Os principais recursos do C# 1.0 incluíam:

Classes
Estruturas
Interfaces
Eventos
Propriedades
Representantes
Operadores e expressões
Instruções
Atributos

Artigooriginalmente publicado no blog NDepend , cortesia de Erik Dietrich e Patrick


Smacchia.

6 Colaborar conosco no Comentários do .NET


GitHub O .NET é um projeto código aberto.
A fonte deste conteúdo pode Selecione um link para fornecer
ser encontrada no GitHub, onde comentários:
você também pode criar e
revisar problemas e solicitações  Abrir um problema de
de pull. Para obter mais documentação
informações, confira o nosso
guia para colaboradores.  Fornecer comentários sobre o
produto
Relações entre os recursos de
linguagem e os tipos de bibliotecas
Artigo • 30/11/2023

A definição de linguagem C# exige que uma biblioteca padrão tenha determinados


tipos e determinados membros acessíveis nesses tipos. O compilador gera o código que
usa esses tipos e membros necessários para muitos recursos de linguagem diferentes.
Por esse motivo, as versões do C# têm suporte apenas para a versão correspondente do
.NET e mais recentes. Isso garante o comportamento correto em tempo de execução e a
disponibilidade de todos os tipos e membros necessários.

Essa dependência da funcionalidade da biblioteca padrão faz parte da linguagem C#


desde sua primeira versão. Nessa versão, os exemplos incluíam:

Exception – usado para todas as exceções geradas pelo compilador.


String – sinônimo de string .
Int32 – sinônimo de int .

A primeira versão era simples: o compilador e a biblioteca padrão eram fornecidos


juntos e havia somente uma versão de cada um.

As versões posteriores do C# ocasionalmente adicionaram novos tipos ou membros às


dependências. Os exemplos incluem: INotifyCompletion, CallerFilePathAttribute e
CallerMemberNameAttribute. O C# 7.0 adicionou uma dependência em ValueTuple para
implementar o recurso de linguagem de tuplas. O C# 8 requer System.Index e
System.Range para intervalos e índices, entre outros recursos. Cada nova versão pode
adicionar requisitos adicionais.

A equipe de design de linguagem trabalha para minimizar a área de superfície dos tipos
e membros necessários em uma biblioteca padrão em conformidade. Essa meta é
equilibrada em relação a um design limpo no qual novos recursos de biblioteca são
incorporados diretamente na linguagem. Haverá novos recursos em versões futuras do
C# que exigem novos tipos e membros em uma biblioteca padrão. As ferramentas do
compilador C# agora são desacopladas do ciclo de liberação das bibliotecas .NET em
plataformas com suporte.

6 Colaborar conosco no .NET feedback


GitHub .NET is an open source project.
Select a link to provide feedback:
A fonte deste conteúdo pode
ser encontrada no GitHub, onde  Abrir um problema de
você também pode criar e documentação
revisar problemas e solicitações
de pull. Para obter mais  Fornecer comentários sobre o
informações, confira o nosso produto
guia para colaboradores.
Considerações sobre versão e
atualização para os desenvolvedores de
C#
Artigo • 30/06/2023

A compatibilidade é uma meta importante quando novos recursos são adicionados à


linguagem C#. Em quase todos os casos, o código existente pode ser recompilado com
uma nova versão do compilador sem nenhum problema. A equipe de runtime do .NET
também tem uma meta para garantir a compatibilidade das bibliotecas atualizadas. Em
quase todos os casos, quando seu aplicativo é iniciado de um runtime atualizado com
bibliotecas atualizadas, o comportamento é exatamente o mesmo que nas versões
anteriores.

A versão de linguagem usada para compilar seu aplicativo normalmente corresponde ao


TFM (moniker da estrutura de destino) de runtime referenciado em seu projeto. Para
obter mais informações sobre como alterar a versão de linguagem padrão, confira o
artigo intitulado configurar sua versão de linguagem. Esse comportamento padrão
garante a compatibilidade máxima.

Quando alterações interruptivas são introduzidas, elas são classificadas como:

Alteração interruptiva binária: uma alteração interruptiva binária causa um


comportamento diferente, incluindo possivelmente falha, em seu aplicativo ou
biblioteca quando iniciado usando um novo runtime. Você deve recompilar seu
aplicativo para incorporar essas alterações. O binário existente não funcionará
corretamente.
Alteração interruptiva de origem: uma alteração interruptiva de origem altera o
significado do código-fonte. Você precisa fazer edições de código-fonte antes de
compilar seu aplicativo com a versão mais recente do idioma. O binário existente
será executado corretamente com o host e o runtime mais recentes. Observe que,
para a sintaxe da linguagem, uma alteração interruptiva de origem também é uma
alteração comportamental, conforme definido nas alterações interruptivas do
runtime.

Quando uma alteração interruptiva binária afeta seu aplicativo, você precisa recompilar
seu aplicativo, mas não precisa editar nenhum código-fonte. Quando uma alteração
interruptiva de origem afeta seu aplicativo, o binário existente ainda é executado
corretamente em ambientes com o runtime e as bibliotecas atualizados. No entanto,
você deve fazer alterações de origem para recompilar com a nova versão do idioma e o
runtime. Se uma alteração for interruptiva de origem e interruptiva binária, você deverá
recompilar seu aplicativo com a versão mais recente e fazer atualizações de origem.

Devido à meta de evitar alterações interruptivas da equipe de linguagem C# e da


equipe de runtime, atualizar seu aplicativo normalmente é uma questão de atualizar o
TFM e recompilar o aplicativo. No entanto, para bibliotecas distribuídas publicamente,
você deve avaliar cuidadosamente sua política quanto a TFMs com suporte e versões de
linguagem com suporte. Você poderá estar criando uma nova biblioteca com recursos
encontrados na versão mais recente, e precisará garantir que os aplicativos criados com
versões anteriores do compilador possam usá-la. Ou você pode estar atualizando uma
biblioteca existente e muitos dos seus usuários podem não ter versões atualizadas
ainda.

Como introduzir alterações interruptivas em


suas bibliotecas
Ao adotar novos recursos de linguagem na API pública da biblioteca, você deve avaliar
se a adoção do recurso introduz uma alteração interruptiva binária ou de origem para
os usuários da biblioteca. Todas as alterações na implementação interna que não
aparecem nas interfaces public ou protected são compatíveis.

7 Observação

Se você usar o System.Runtime.CompilerServices.InternalsVisibleToAttribute para


habilitar tipos para ver membros internos, os membros internos poderão introduzir
alterações interruptivas.

Uma alteração interruptiva binária exige que os usuários recompilem o código para usar
a nova versão. Por exemplo, considere este método público:

C#

public double CalculateSquare(double value) => value * value;

Se você adicionar o modificador in ao método, essa será uma alteração interruptiva


binária:

C#

public double CalculateSquare(in double value) => value * value;


Os usuários devem recompilar qualquer aplicativo que use o método CalculateSquare
para que a nova biblioteca funcione corretamente.

Uma alteração interruptiva de origem exige que os usuários alterem o código antes de
recompilarem. Por exemplo, pense neste tipo:

C#

public class Person


{
public string FirstName { get; }
public string LastName { get; }

public Person(string firstName, string lastName) => (FirstName,


LastName) = (firstName, lastName);

// other details omitted


}

Em uma versão mais recente, você gostaria de aproveitar os membros sintetizados


gerados para tipos record . Você faz as seguintes alterações:

C#

public record class Person(string FirstName, string LastName);

A alteração anterior requer alterações para qualquer tipo derivado de Person . Todas
essas declarações devem adicionar o modificador record às declarações.

Impacto de alterações interruptivas


Ao adicionar uma alteração interruptiva binária à biblioteca, você força todos os
projetos que usam sua biblioteca a recompilar. No entanto, nenhum código-fonte
nesses projetos precisa ser alterado. Como resultado, o impacto da alteração
interruptiva é razoavelmente pequeno para cada projeto.

Ao fazer uma alteração interruptiva de origem em sua biblioteca, você exige que todos
os projetos façam alterações de origem para usar sua nova biblioteca. Se a alteração
necessária exigir novos recursos de linguagem, você força esses projetos a atualizar para
a mesma versão de idioma e TFM que você está usando agora. Você exigiu mais
trabalho para seus usuários e, possivelmente, forçou-os a atualizar também.

O impacto de qualquer alteração interruptiva que você faz depende do número de


projetos que têm uma dependência em sua biblioteca. Se sua biblioteca for usada
internamente por alguns aplicativos, você poderá reagir a todas alterações interruptivas
em todos os projetos afetados. No entanto, se sua biblioteca for baixada publicamente,
você deverá avaliar o impacto potencial e considerar alternativas:

Você pode adicionar novas APIs que são APIs existentes paralelas.
Você pode considerar builds paralelos para diferentes TFMs.
Você pode considerar a multiplataforma.
Tutorial: Explorar construtores primários
Artigo • 28/06/2023

O C# 12 apresenta construtores primários, uma sintaxe concisa para declarar


construtores cujos parâmetros estão disponíveis em qualquer lugar no corpo do tipo.

Neste tutorial, você aprenderá como:

" Quando declarar um construtor primário em seu tipo


" Como chamar construtores primários de outros construtores
" Como usar parâmetros do construtor primário em membros do tipo
" Onde os parâmetros do construtor primário são armazenados

Pré-requisitos
Você precisará configurar seu computador para executar o .NET 8 ou posterior,
incluindo o compilador C# 12 ou posterior. O compilador C# 12 está disponível a partir
do Visual Studio 2022 versão 17.7 ou do SDK do .NET 8 .

Construtores primários
Você pode adicionar parâmetros a uma declaração struct ou class para criar um
construtor primário. Os parâmetros do construtor primário estão no escopo em toda a
definição de classe. É importante exibir os parâmetros do construtor primário como
parâmetros mesmo que estejam no escopo em toda a definição de classe. Várias regras
esclarecem que eles são parâmetros:

1. Pode ser que os parâmetros do construtor primário não sejam armazenados se


não forem necessários.
2. Os parâmetros do construtor primário não são membros da classe. Por exemplo, o
parâmetro de construtor primário param não pode ser acessado como this.param .
3. Parâmetros de construtor primário podem ser atribuídos.
4. Os parâmetros do construtor primário não se tornam propriedades, exceto em
tipos record.

Essas regras são iguais aos parâmetros de qualquer método, incluindo outras
declarações de construtor.

Os usos mais comuns para um parâmetro de construtor primário são:

1. Como um argumento para uma invocação de construtor base() .


2. Para inicializar um campo ou propriedade de membro.
3. Referenciando o parâmetro de construtor em um membro de instância.

Todos os outros construtores de uma classe devem chamar o construtor primário, direta
ou indiretamente, por meio de uma invocação de construtor this() . Essa regra garante
que os parâmetros do construtor primário sejam atribuídos em qualquer lugar no corpo
do tipo.

Inicializar propriedade
O código a seguir inicializa duas propriedades somente leitura computadas a partir dos
parâmetros do construtor primário:

C#

public readonly struct Distance(double dx, double dy)


{
public readonly double Magnitude = Math.Sqrt(dx * dx + dy * dy);
public readonly double Direction = Math.Atan2(dy, dx);
}

O código anterior demonstra um construtor primário usado para inicializar


propriedades somente leitura calculadas. Os inicializadores de campo para Magnitude e
Direction usam os parâmetros do construtor primário. Os parâmetros do construtor

primário não são usados em nenhum outro lugar no struct. O struct anterior é como se
você tivesse escrito o seguinte código:

C#

public readonly struct Distance


{
public readonly double Magnitude { get; }

public readonly double Direction { get; }

public Distance(double dx, double dy)


{
Magnitude = Math.Sqrt(dx * dx + dy * dy);
Direction = Math.Atan2(dy, dx);
}
}

O novo recurso facilita o uso de inicializadores de campo quando você precisa de


argumentos a fim de inicializar um campo ou propriedade.
Criar estado mutável
Os exemplos anteriores usam os parâmetros do construtor primário para inicializar
propriedades somente leitura. Você também pode usar construtores primários quando
as propriedades não forem somente leitura. Considere o seguinte código:

C#

public struct Distance(double dx, double dy)


{
public readonly double Magnitude => Math.Sqrt(dx * dx + dy * dy);
public readonly double Direction => Math.Atan2(dy, dx);

public void Translate(double deltaX, double deltaY)


{
dx += deltaX;
dy += deltaY;
}

public Distance() : this(0,0) { }


}

No exemplo anterior, o método Translate altera os componentes dx e dy . Isso requer


que as propriedades Magnitude e Direction sejam computadas quando acessadas. O
operador => designa um acessador get apto para expressão, enquanto o operador =
designa um inicializador. Esta versão adiciona um construtor sem parâmetros ao struct.
O construtor sem parâmetros deve invocar o construtor primário para que todos os
parâmetros do construtor primário sejam inicializados.

No exemplo anterior, as propriedades do construtor primário são acessadas em um


método. Portanto, o compilador cria campos ocultos para representar cada parâmetro.
O código a seguir mostra aproximadamente o que o compilador gera. Os nomes de
campos reais são identificadores MSIL válidos, mas não identificadores C# válidos.

C#

public struct Distance


{
private double __unspeakable_dx;
private double __unspeakable_dy;

public readonly double Magnitude => Math.Sqrt(__unspeakable_dx *


__unspeakable_dx + __unspeakable_dy * __unspeak