0% acharam este documento útil (0 voto)
5 visualizações

dotnet-csharp

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
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
5 visualizações

dotnet-csharp

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
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
Você está na página 1/ 1522

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 * __unspeakable_dy);
public readonly double Direction => Math.Atan2(__unspeakable_dy,
__unspeakable_dx);

public void Translate(double deltaX, double deltaY)


{
__unspeakable_dx += deltaX;
__unspeakable_dy += deltaY;
}

public Distance(double dx, double dy)


{
__unspeakable_dx = dx;
__unspeakable_dy = dy;
}
public Distance() : this(0, 0) { }
}

É importante entender que o primeiro exemplo não exigia que o compilador criasse um
campo para armazenar o valor dos parâmetros do construtor primário. O segundo
exemplo usou o parâmetro de construtor primário dentro de um método, portanto,
exigia que o compilador criasse armazenamento para eles. O compilador cria o
armazenamento para um construtor primário somente quando esse parâmetro é
acessado no corpo de um membro do seu tipo. Caso contrário, os parâmetros do
construtor primário não serão armazenados no objeto.

Injeção de dependência
Outro uso comum dos construtores primários é especificar parâmetros para injeção de
dependência. O código a seguir cria um controlador simples que requer uma interface
de serviço para seu uso:

C#

public interface IService


{
Distance GetDistance();
}

public class ExampleController(IService service) : ControllerBase


{
[HttpGet]
public ActionResult<Distance> Get()
{
return service.GetDistance();
}
}

O construtor primário indica claramente os parâmetros necessários na classe. Use os


parâmetros do construtor primário como faria com qualquer outra variável na classe.
Inicializar classe base
Você pode invocar o construtor primário de uma classe base a partir do construtor
primário da classe derivada. É a maneira mais fácil de escrever uma classe derivada que
deve invocar um construtor primário na classe base. Por exemplo, considere uma
hierarquia de classes que representa diferentes tipos de conta como um banco. A classe
base seria semelhante ao seguinte código:

C#

public class BankAccount(string accountID, string owner)


{
public string AccountID { get; } = accountID;
public string Owner { get; } = owner;

public override string ToString() => $"Account ID: {AccountID}, Owner:


{Owner}";
}

Todas as contas bancárias, independentemente do tipo, têm propriedades como o


número da conta e um proprietário. No aplicativo concluído, outras funcionalidades
comuns seriam adicionadas à classe base.

Muitos tipos exigem uma validação mais específica em parâmetros de construtor. Por
exemplo, o BankAccount tem requisitos específicos para os parâmetros owner e
accountID : o owner não deve ser null ou um espaço em branco e accountID deve ser

uma cadeia de caracteres que contém 10 dígitos. Você pode adicionar essa validação ao
atribuir as propriedades correspondentes:

C#

public class BankAccount(string accountID, string owner)


{
public string AccountID { get; } = ValidAccountNumber(accountID)
? accountID
: throw new ArgumentException("Invalid account number",
nameof(accountID));

public string Owner { get; } = string.IsNullOrWhiteSpace(owner)


? throw new ArgumentException("Owner name cannot be empty",
nameof(owner))
: owner;

public override string ToString() => $"Account ID: {AccountID}, Owner:


{Owner}";

public static bool ValidAccountNumber(string accountID) =>


accountID?.Length == 10 && accountID.All(c => char.IsDigit(c));
}

O exemplo anterior mostra como você pode validar os parâmetros do construtor antes
de atribuí-los às propriedades. Você pode usar métodos internos, como
String.IsNullOrWhiteSpace(String), ou seu método de validação, como
ValidAccountNumber . No exemplo anterior, todas as exceções são geradas pelo

construtor quando ele invoca os inicializadores. Se um parâmetro de construtor não for


usado para atribuir um campo, todas as exceções serão geradas quando o parâmetro
do construtor for acessado pela primeira vez.

Uma classe derivada apresentaria uma conta corrente:

C#

public class CheckAccount(string accountID, string owner, decimal


overdraftLimit = 0) : BankAccount(accountID, owner)
{
public decimal CurrentBalance { get; private set; } = 0;

public void Deposit(decimal amount)


{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Deposit
amount must be positive");
}
CurrentBalance += amount;
}

public void Withdrawal(decimal amount)


{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount),
"Withdrawal amount must be positive");
}
if (CurrentBalance - amount < -overdraftLimit)
{
throw new InvalidOperationException("Insufficient funds for
withdrawal");
}
CurrentBalance -= amount;
}

public override string ToString() => $"Account ID: {AccountID}, Owner:


{Owner}, Balance: {CurrentBalance}";
}
A classe derivada CheckingAccount tem um construtor primário que usa todos os
parâmetros necessários na classe base, e outro parâmetro com um valor padrão. O
construtor primário chama o construtor base usando a sintaxe :
BankAccount(accountID, owner) . Essa expressão especifica o tipo para a classe base e os

argumentos para o construtor primário.

Não é necessário que sua classe derivada use um construtor primário. Você pode criar
um construtor na classe derivada que invoca o construtor primário da classe base, como
mostrado no exemplo a seguir:

C#

public class LineOfCreditAccount : BankAccount


{
private readonly decimal _creditLimit;
public LineOfCreditAccount(string accountID, string owner, decimal
creditLimit) : base(accountID, owner)
{
_creditLimit = creditLimit;
}
public decimal CurrentBalance { get; private set; } = 0;

public void Deposit(decimal amount)


{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Deposit
amount must be positive");
}
CurrentBalance += amount;
}

public void Withdrawal(decimal amount)


{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount),
"Withdrawal amount must be positive");
}
if (CurrentBalance - amount < -_creditLimit)
{
throw new InvalidOperationException("Insufficient funds for
withdrawal");
}
CurrentBalance -= amount;
}

public override string ToString() => $"{base.ToString()}, Balance:


{CurrentBalance}";
}
Há uma preocupação potencial com hierarquias de classe e construtores primários: é
possível criar várias cópias de um parâmetro de construtor primário, já que ele é usado
em classes base e derivadas. O exemplo de código a seguir cria duas cópias de cada um
dos campos owner e accountID :

C#

public class SavingsAccount(string accountID, string owner, decimal


interestRate) : BankAccount(accountID, owner)
{
public SavingsAccount() : this("default", "default", 0.01m) { }
public decimal CurrentBalance { get; private set; } = 0;

public void Deposit(decimal amount)


{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Deposit
amount must be positive");
}
CurrentBalance += amount;
}

public void Withdrawal(decimal amount)


{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount),
"Withdrawal amount must be positive");
}
if (CurrentBalance - amount < 0)
{
throw new InvalidOperationException("Insufficient funds for
withdrawal");
}
CurrentBalance -= amount;
}

public void ApplyInterest()


{
CurrentBalance *= 1 + interestRate;
}

public override string ToString() => $"Account ID: {accountID}, Owner:


{owner}, Balance: {CurrentBalance}";
}

A linha realçada mostra que o método ToString usa os parâmetros do construtor


primário ( owner e accountID ) em vez das propriedades da classe base ( Owner e
AccountID ). O resultado é que a classe derivada SavingsAccount cria o armazenamento
para essas cópias. A cópia na classe derivada é diferente da propriedade na classe base.
Se for possível modificar a propriedade da classe base, a instância da classe derivada
não verá essa modificação. O compilador emite um aviso para os parâmetros do
construtor primário usados em uma classe derivada e passados para um construtor de
classe base. Nessa instância, a correção envolve usar as propriedades na interface de
classe base.

Resumo
Você pode usar os construtores primários da maneira mais adequada ao seu design. Em
classes e structs, os parâmetros do construtor primário são os parâmetros para um
construtor que deve ser invocado. Você pode usá-los para inicializar propriedades. Você
pode inicializar campos. Essas propriedades ou campos podem ser mutáveis ou
imutáveis. Você pode usá-los em métodos. Esses são parâmetros para usar da maneira
mais adequada ao seu design. Você pode saber mais sobre construtores primários no
artigo do guia de programação em C# sobre construtores de instâncias e a
especificação de construtor primário proposta.
Tutorial: Explorar o recurso C# 11 –
membros virtuais estáticos em
interfaces
Artigo • 09/05/2023

O C# 11 e o .NET 7 incluem membros virtuais estáticos em interfaces. Esse recurso


permite definir interfaces que incluem operadores sobrecarregados ou outros membros
estáticos. Depois de definir interfaces com membros estáticos, você pode usar essas
interfaces como restrições para criar tipos genéricos que usam operadores ou outros
métodos estáticos. Mesmo que você não crie interfaces com operadores
sobrecarregados, provavelmente se beneficiará desse recurso e das classes matemáticas
genéricas habilitadas pela atualização da linguagem.

Neste tutorial, você aprenderá como:

" Definir interfaces com membros estáticos.


" Use interfaces para definir classes que implementam interfaces com operadores
definidos.
" Crie algoritmos genéricos que dependem de métodos de interface estática.

Pré-requisitos
Você precisará configurar seu computador para executar o .NET 7, que dá suporte ao C#
11. O compilador C# 11 está disponível a partir do Visual Studio 2022, versão 17.3 ou
do .NET 7 SDK .

Métodos de interface abstrato estáticos


Vamos começar com um exemplo. O método a seguir retorna o ponto médio de dois
números double :

C#

public static double MidPoint(double left, double right) =>


(left + right) / (2.0);

A mesma lógica funcionaria para qualquer tipo numérico: int , short , long ,
float decimal ou qualquer tipo que represente um número. Você precisa ter uma
maneira de usar os operadores + e / , e definir um valor para 2 . Você pode usar a
interface System.Numerics.INumber<TSelf> para escrever o método anterior como o
seguinte método genérico:

C#

public static T MidPoint<T>(T left, T right)


where T : INumber<T> => (left + right) / T.CreateChecked(2); // note:
the addition of left and right may overflow here; it's just for
demonstration purposes

Qualquer tipo que implemente a interface INumber<TSelf> deve incluir uma definição
para operator + e operator / . O denominador é definido por T.CreateChecked(2) para
criar o valor 2 para qualquer tipo numérico, o que força o denominador a ser do
mesmo tipo que os dois parâmetros. INumberBase<TSelf>.CreateChecked<TOther>
(TOther) cria uma instância do tipo do valor especificado e gera um OverflowException
se o valor ficar fora do intervalo representável. (Essa implementação tem o potencial de
estouro se left e right são valores grandes o suficiente. Há algoritmos alternativos
que podem evitar esse possível problema.)

Você define membros abstratos estáticos em uma interface usando a sintaxe familiar:
você adiciona os modificadores static e abstract a qualquer membro estático que
não forneça uma implementação. O exemplo a seguir define uma interface IGetNext<T>
que pode ser aplicada a qualquer tipo que substitua operator ++ :

C#

public interface IGetNext<T> where T : IGetNext<T>


{
static abstract T operator ++(T other);
}

A restrição de que o argumento de tipo, T implementa IGetNext<T> , garante que a


assinatura do operador inclua o tipo que contém ou seu argumento de tipo. Muitos
operadores impõem que seus parâmetros devem corresponder ao tipo ou ser o
parâmetro de tipo restrito para implementar o tipo que contém. Sem essa restrição, o
operador ++ não pôde ser definido na interface IGetNext<T> .

Você pode criar uma estrutura que cria uma cadeia de caracteres 'A' em que cada
incremento adiciona outro caractere à cadeia de caracteres usando o seguinte código:

C#
public struct RepeatSequence : IGetNext<RepeatSequence>
{
private const char Ch = 'A';
public string Text = new string(Ch, 1);

public RepeatSequence() {}

public static RepeatSequence operator ++(RepeatSequence other)


=> other with { Text = other.Text + Ch };

public override string ToString() => Text;


}

De modo mais geral, você pode criar qualquer algoritmo em que queira definir ++ para
significar "produzir o próximo valor desse tipo". O uso dessa interface produz código e
resultados claros:

C#

var str = new RepeatSequence();

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


Console.WriteLine(str++);

O código anterior gerencia a saída a seguir:

PowerShell

A
AA
AAA
AAAA
AAAAA
AAAAAA
AAAAAAA
AAAAAAAA
AAAAAAAAA
AAAAAAAAAA

Este pequeno exemplo demonstra a motivação para esse recurso. Você pode usar
sintaxe natural para operadores, valores constantes e outras operações estáticas. Você
pode explorar essas técnicas ao criar vários tipos que dependem de membros estáticos,
incluindo operadores sobrecarregados. Defina as interfaces que correspondem aos
recursos de seus tipos e declare o suporte desses tipos para a nova interface.

Matemática genérica
O cenário motivador para permitir métodos estáticos, incluindo operadores, em
interfaces é dar suporte a algoritmos matemáticos genéricos. A biblioteca de classes
base do .NET 7 contém definições de interface para muitos operadores aritméticos e
interfaces derivadas que combinam muitos operadores aritméticos em uma interface
INumber<T> . Vamos aplicar esses tipos para criar um registro Point<T> que possa usar

qualquer tipo numérico para T . O ponto pode ser movido por alguns XOffset e
YOffset usando o operador + .

Comece criando um novo aplicativo console usando dotnet new ou o Visual Studio.
Defina a versão da linguagem C# como "versão prévia", o que habilita recursos de
visualização do C# 11. Adicione o seguinte elemento ao arquivo csproj dentro de um
elemento <PropertyGroup> :

XML

<LangVersion>preview</LangVersion>

7 Observação

Esse elemento não pode ser definido usando a interface do usuário do Visual
Studio. Você precisa editar o arquivo de projeto diretamente.

A interface pública para o Translation<T> e Point<T> deve se parecer com o seguinte


código:

C#

// Note: Not complete. This won't compile yet.


public record Translation<T>(T XOffset, T YOffset);

public record Point<T>(T X, T Y)


{
public static Point<T> operator +(Point<T> left, Translation<T> right);
}

Você usa o tipo record para os tipos Translation<T> e Point<T> : ambos armazenam
dois valores e eles representam o armazenamento de dados em vez de um
comportamento sofisticado. A implementação de operator + seria semelhante ao
seguinte código:

C#
public static Point<T> operator +(Point<T> left, Translation<T> right) =>
left with { X = left.X + right.XOffset, Y = left.Y + right.YOffset };

Para que o código anterior seja compilado, você precisará declarar que T dá suporte à
interface IAdditionOperators<TSelf, TOther, TResult> . Essa interface inclui o método
estático operator + . Ele declara três parâmetros de tipo: um para o operando à
esquerda, um para o operando à direita e outro para o resultado. Alguns tipos
implementam + para diferentes tipos de operando e de resultado. Adicione uma
declaração de que o argumento T de tipo implementa IAdditionOperators<T, T, T> :

C#

public record Point<T>(T X, T Y) where T : IAdditionOperators<T, T, T>

Depois de adicionar essa restrição, sua classe Point<T> poderá usar o + para seu
operador de adição. Adicione a mesma restrição à declaração Translation<T> :

C#

public record Translation<T>(T XOffset, T YOffset) where T :


IAdditionOperators<T, T, T>;

A restrição IAdditionOperators<T, T, T> impede que um desenvolvedor que usa sua


classe crie um Translation usando um tipo que não atenda à restrição para a adição a
um ponto. Você adicionou as restrições necessárias ao parâmetro de tipo para
Translation<T> e Point<T> , portanto, esse código funciona. Você pode testar

adicionando código como o seguinte acima das declarações de Translation e Point


em seu arquivo Program.cs:

C#

var pt = new Point<int>(3, 4);

var translate = new Translation<int>(5, 10);

var final = pt + translate;

Console.WriteLine(pt);
Console.WriteLine(translate);
Console.WriteLine(final);

Você pode tornar esse código mais reutilizável declarando que esses tipos
implementam as interfaces aritméticas apropriadas. A primeira alteração a ser executada
é declarar que Point<T, T> implementa a interface IAdditionOperators<Point<T>,
Translation<T>, Point<T>> . O tipo Point usa diferentes tipos para operandos e o

resultado. O tipo Point já implementa um operator + com essa assinatura, portanto,


adicionar a interface à declaração é tudo o que você precisa:

C#

public record Point<T>(T X, T Y) : IAdditionOperators<Point<T>,


Translation<T>, Point<T>>
where T : IAdditionOperators<T, T, T>

Por fim, ao executar a adição, é útil ter uma propriedade que defina o valor de
identidade aditivo para esse tipo. Há uma nova interface para esse recurso:
IAdditiveIdentity<TSelf,TResult>. Uma tradução de {0, 0} é a identidade aditiva: o
ponto resultante é o mesmo que o operando à esquerda. A interface
IAdditiveIdentity<TSelf, TResult> define uma propriedade readonly,

AdditiveIdentity , que retorna o valor de identidade. A Translation<T> precisa de

algumas alterações para implementar essa interface:

C#

using System.Numerics;

public record Translation<T>(T XOffset, T YOffset) :


IAdditiveIdentity<Translation<T>, Translation<T>>
where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T>
{
public static Translation<T> AdditiveIdentity =>
new Translation<T>(XOffset: T.AdditiveIdentity, YOffset:
T.AdditiveIdentity);
}

Há algumas alterações aqui, então vamos analisá-las uma por uma. Primeiro, você
declara que o tipo Translation implementa a interface IAdditiveIdentity :

C#

public record Translation<T>(T XOffset, T YOffset) :


IAdditiveIdentity<Translation<T>, Translation<T>>

Em seguida, você pode tentar implementar o membro da interface, conforme mostrado


no seguinte código:

C#
public static Translation<T> AdditiveIdentity =>
new Translation<T>(XOffset: 0, YOffset: 0);

O código anterior não será compilado, pois 0 depende do tipo. A resposta: use
IAdditiveIdentity<T>.AdditiveIdentity para 0 . Essa alteração significa que suas

restrições agora devem incluir essa T implementa IAdditiveIdentity<T> . Isso resulta na


seguinte implementação:

C#

public static Translation<T> AdditiveIdentity =>


new Translation<T>(XOffset: T.AdditiveIdentity, YOffset:
T.AdditiveIdentity);

Agora que você adicionou essa restrição em Translation<T> , precisa adicionar a mesma
restrição para Point<T> :

C#

using System.Numerics;

public record Point<T>(T X, T Y) : IAdditionOperators<Point<T>,


Translation<T>, Point<T>>
where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T>
{
public static Point<T> operator +(Point<T> left, Translation<T> right)
=>
left with { X = left.X + right.XOffset, Y = left.Y + right.YOffset
};
}

Este exemplo deu uma olhada em como as interfaces para composição matemática
genérica. Você aprendeu a:

" Escrever um método que dependia da interface INumber<T> para que esse método
pudesse ser usado com qualquer tipo numérico.
" Criar um tipo que depende das interfaces de adição para implementar um tipo que
dá suporte apenas a uma operação matemática. Que o tipo declara seu suporte
para essas mesmas interfaces para que ele possa ser composto de outras maneiras.
Os algoritmos são escritos usando a sintaxe mais natural dos operadores
matemáticos.

Experimente esses recursos e registre comentários. Você pode usar o item de menu
Enviar Comentários no Visual Studio ou criar um novo problema no repositório roslyn
no GitHub. Crie algoritmos genéricos que funcionam com qualquer tipo numérico. Criar
algoritmos usando essas interfaces em que o argumento de tipo só pode implementar
um subconjunto de recursos semelhantes a números. Mesmo que você não crie novas
interfaces que usam essas funcionalidades, pode experimentar usá-las em seus
algoritmos.

Confira também
Matemática genérica

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.
Usar a correspondência de padrões para
criar o comportamento de classe para
um código melhor
Artigo • 09/05/2023

Os recursos de correspondência de padrões em C# fornecem sintaxe para expressar os


algoritmos. Você pode usar essas técnicas para implementar o comportamento nas
classes. Você pode combinar o design de classe orientada a objeto com uma
implementação orientada a dados para fornecer código conciso durante a modelagem
de objetos do mundo real.

Neste tutorial, você aprenderá como:

" Expressar as classes orientadas a objeto usando padrões de dados.


" Implementar esses padrões usando os recursos de correspondência de padrões do
C#.
" Aproveitar o diagnóstico do compilador para validar a implementação.

Pré-requisitos
Você precisará configurar o computador para executar o .NET 5, incluindo o compilador
C# 9. O compilador C# 9 está disponível a partir do Visual Studio 2019, versão 16.8 ou
do .NET 5 SDK .

Criar uma simulação de um bloqueio de canal


Neste tutorial, você criará uma classe C# que simula uma eclusa de canal .
Resumidamente, uma eclusa de canal é um dispositivo que levanta e abaixa os barcos à
medida que viajam entre dois trechos de água em diferentes níveis. Um bloqueio tem
dois portões e algum mecanismo para alterar o nível da água.

Em sua operação normal, um barco entra em um dos portões enquanto o nível da água
na eclusa coincide com o nível da água do lado em que o barco entra. Uma vez na
eclusa, o nível da água é alterado para corresponder ao nível da água no qual o barco
sairá da eclusa. Quando o nível da água corresponder a esse lado, o portão do lado de
saída se abre. Medidas de segurança garantem que um operador não possa criar uma
situação perigosa no canal. O nível da água só pode ser alterado quando ambos os
portões são fechados. No máximo, um portão pode ser aberto. Para abrir um portão, o
nível da água na eclusa deve corresponder ao nível da água fora do portão que está
sendo aberto.

Você pode criar uma classe C# para modelar esse comportamento. Uma classe
CanalLock daria suporte a comandos para abrir ou fechar um dos portões. Ela teria
outros comandos para subir ou descer a água. A classe também deve dar suporte a
propriedades para ler o estado atual dos portões e do nível da água. Os métodos
implementam as medidas de segurança.

Definir um classe
Você criará um aplicativo de console para testar a classe CanalLock . Crie um novo
projeto de console para o .NET 5 usando o Visual Studio ou a CLI do .NET. Depois,
adicione uma nova classe e nomeie-a CanalLock . Em seguida, crie a API pública, mas
deixe os métodos não implementados:

C#

public enum WaterLevel


{
Low,
High
}
public class CanalLock
{
// Query canal lock state:
public WaterLevel CanalLockWaterLevel { get; private set; } =
WaterLevel.Low;
public bool HighWaterGateOpen { get; private set; } = false;
public bool LowWaterGateOpen { get; private set; } = false;

// Change the upper gate.


public void SetHighGate(bool open)
{
throw new NotImplementedException();
}

// Change the lower gate.


public void SetLowGate(bool open)
{
throw new NotImplementedException();
}

// Change water level.


public void SetWaterLevel(WaterLevel newLevel)
{
throw new NotImplementedException();
}
public override string ToString() =>
$"The lower gate is {(LowWaterGateOpen ? "Open" : "Closed")}. " +
$"The upper gate is {(HighWaterGateOpen ? "Open" : "Closed")}. " +
$"The water level is {CanalLockWaterLevel}.";
}

O código anterior inicializa o objeto para que ambos os portões sejam fechados e o
nível da água seja baixo. Em seguida, escreva o seguinte código de teste no método
Main para orientar você ao criar uma primeira implementação da classe:

C#

// Create a new canal lock:


var canalGate = new CanalLock();

// State should be doors closed, water level low:


Console.WriteLine(canalGate);

canalGate.SetLowGate(open: true);
Console.WriteLine($"Open the lower gate: {canalGate}");

Console.WriteLine("Boat enters lock from lower gate");

canalGate.SetLowGate(open: false);
Console.WriteLine($"Close the lower gate: {canalGate}");

canalGate.SetWaterLevel(WaterLevel.High);
Console.WriteLine($"Raise the water level: {canalGate}");

canalGate.SetHighGate(open: true);
Console.WriteLine($"Open the higher gate: {canalGate}");

Console.WriteLine("Boat exits lock at upper gate");


Console.WriteLine("Boat enters lock from upper gate");

canalGate.SetHighGate(open: false);
Console.WriteLine($"Close the higher gate: {canalGate}");

canalGate.SetWaterLevel(WaterLevel.Low);
Console.WriteLine($"Lower the water level: {canalGate}");

canalGate.SetLowGate(open: true);
Console.WriteLine($"Open the lower gate: {canalGate}");

Console.WriteLine("Boat exits lock at upper gate");

canalGate.SetLowGate(open: false);
Console.WriteLine($"Close the lower gate: {canalGate}");

Em seguida, adicione uma primeira implementação de cada método na classe


CanalLock . O código a seguir implementa os métodos da classe sem preocupação com
as regras de segurança. Você adicionará testes de segurança mais tarde:

C#

// Change the upper gate.


public void SetHighGate(bool open)
{
HighWaterGateOpen = open;
}

// Change the lower gate.


public void SetLowGate(bool open)
{
LowWaterGateOpen = open;
}

// Change water level.


public void SetWaterLevel(WaterLevel newLevel)
{
CanalLockWaterLevel = newLevel;
}

Os testes que você escreveu até agora são aprovados. Você implementou as noções
básicas. Agora, escreva um teste para a primeira condição de falha. No final dos testes
anteriores, ambos os portões são fechados, e o nível da água é definido como baixo.
Adicione um teste para tentar abrir o portão superior:

C#

Console.WriteLine("=============================================");
Console.WriteLine(" Test invalid commands");
// Open "wrong" gate (2 tests)
try
{
canalGate = new CanalLock();
canalGate.SetHighGate(open: true);
}
catch (InvalidOperationException)
{
Console.WriteLine("Invalid operation: Can't open the high gate. Water is
low.");
}
Console.WriteLine($"Try to open upper gate: {canalGate}");

Esse teste não deu certo porque o portão foi aberto. Como uma primeira
implementação, você pode corrigi-la com o seguinte código:

C#
// Change the upper gate.
public void SetHighGate(bool open)
{
if (open && (CanalLockWaterLevel == WaterLevel.High))
HighWaterGateOpen = true;
else if (open && (CanalLockWaterLevel == WaterLevel.Low))
throw new InvalidOperationException("Cannot open high gate when the
water is low");
}

Os testes são aprovados. Mas, à medida que mais testes são adicionados, você
adicionará mais e mais cláusulas if e testará propriedades diferentes. Em breve, esses
métodos ficarão muito complicados à medida que você adicionar mais condicionais.

Implementar os comandos com padrões


Uma outra maneira melhor é usar padrões para determinar se o objeto está em um
estado válido para executar um comando. Você poderá expressar se um comando for
permitido como uma função de três variáveis: o estado do portão, o nível da água e a
nova configuração:

Nova configuração Estado do portão Nível da Água Result

Fechado Fechado Alto Fechado

Fechado Fechado Baixo Fechado

Fechado Abrir Alto Fechadas

Fechadas Abrir Baixo Fechadas

Abrir Fechadas Alto Abrir

Abrir Fechadas Baixo Fechado (Erro)

Abrir Abrir Alto Abrir

Abrir Abrir Baixo Fechado (Erro)

As quartas e últimas linhas da tabela atingiram o texto porque são inválidas. O código
que você está adicionando agora deve garantir que o portão de água alta nunca seja
aberto quando a água estiver baixa. Esses estados podem ser codificados como uma
expressão única de comutador (lembre-se de que false indica "Fechado"):

C#
HighWaterGateOpen = (open, HighWaterGateOpen, CanalLockWaterLevel) switch
{
(false, false, WaterLevel.High) => false,
(false, false, WaterLevel.Low) => false,
(false, true, WaterLevel.High) => false,
(false, true, WaterLevel.Low) => false, // should never happen
(true, false, WaterLevel.High) => true,
(true, false, WaterLevel.Low) => throw new
InvalidOperationException("Cannot open high gate when the water is low"),
(true, true, WaterLevel.High) => true,
(true, true, WaterLevel.Low) => false, // should never happen
};

Experimente esta versão. Os testes passam, validando o código. A tabela completa


mostra as possíveis combinações de entradas e resultados. Isso significa que você e
outros desenvolvedores podem examinar rapidamente a tabela e ver que todas as
entradas possíveis foram abrangidas. Ainda mais fácil, o compilador também pode
ajudar. Depois de adicionar o código anterior, você pode ver que o compilador gera um
aviso: CS8524 indica que a expressão de comutador não abrange todas as entradas
possíveis. O motivo desse aviso é que uma das entradas é um tipo enum . O compilador
interpreta "todas as entradas possíveis" como todas as entradas do tipo subjacente,
normalmente um int . Essa expressão switch verifica apenas os valores declarados no
enum . Para remover o aviso, você pode adicionar um padrão de descarte catch-all para o

último braço da expressão. Essa condição gera uma exceção, pois indica uma entrada
inválida:

C#

_ => throw new InvalidOperationException("Invalid internal state"),

O braço de comutador anterior deve ser o último na expressão switch porque


corresponde a todas as entradas. Experimente movendo-o anteriormente na ordem.
Isso causa um erro do compilador CS8510 para código inacessível em um padrão. A
estrutura natural de expressões de comutador permite que o compilador gere erros e
avisos para possíveis erros. A "rede de segurança" do compilador facilita a criação de
código correto em menos iterações e a liberdade de combinar armas com curingas. O
compilador emitirá erros se a combinação resultar em braços inacessíveis que você não
esperava e avisos se você remover um braço necessário.

A primeira alteração é combinar todos os braços em que o comando deve fechar o


portão; isso é sempre permitido. Adicione o seguinte código como o primeiro braço na
expressão de comutador:
C#

(false, _, _) => false,

Depois de adicionar o braço de comutador anterior, você receberá quatro erros do


compilador, um em cada um dos braços em que o comando é false . Esses braços já
estão cobertos por aquele recém-adicionado. Você pode remover essas quatro linhas
com segurança. Você pretendia que este novo braço do interruptor substituísse essas
condições.

Em seguida, você pode simplificar os quatro braços em que o comando é abrir o portão.
Em ambos os casos em que o nível da água é alto, o portão pode ser aberto. (Em um
deles, ele já está aberto.) Um caso em que o nível da água é baixo gera uma exceção e o
outro não deve acontecer. Deve ser seguro lançar a mesma exceção se o bloqueio de
água já estiver em um estado inválido. Você pode fazer as seguintes simplificações para
esses braços:

C#

(true, _, WaterLevel.High) => true,


(true, false, WaterLevel.Low) => throw new InvalidOperationException("Cannot
open high gate when the water is low"),
_ => throw new InvalidOperationException("Invalid internal state"),

Execute os testes novamente e eles serão aprovados. Esta é a versão final do método
SetHighGate :

C#

// Change the upper gate.


public void SetHighGate(bool open)
{
HighWaterGateOpen = (open, HighWaterGateOpen, CanalLockWaterLevel)
switch
{
(false, _, _) => false,
(true, _, WaterLevel.High) => true,
(true, false, WaterLevel.Low) => throw new
InvalidOperationException("Cannot open high gate when the water is low"),
_ => throw new
InvalidOperationException("Invalid internal state"),
};
}

Implementar padrões por conta própria


Agora que você já viu a técnica, preencha os métodos SetLowGate e SetWaterLevel por
conta própria. Comece adicionando o seguinte código para testar operações inválidas
nesses métodos:

C#

Console.WriteLine();
Console.WriteLine();
try
{
canalGate = new CanalLock();
canalGate.SetWaterLevel(WaterLevel.High);
canalGate.SetLowGate(open: true);
}
catch (InvalidOperationException)
{
Console.WriteLine("invalid operation: Can't open the lower gate. Water
is high.");
}
Console.WriteLine($"Try to open lower gate: {canalGate}");
// change water level with gate open (2 tests)
Console.WriteLine();
Console.WriteLine();
try
{
canalGate = new CanalLock();
canalGate.SetLowGate(open: true);
canalGate.SetWaterLevel(WaterLevel.High);
}
catch (InvalidOperationException)
{
Console.WriteLine("invalid operation: Can't raise water when the lower
gate is open.");
}
Console.WriteLine($"Try to raise water with lower gate open: {canalGate}");
Console.WriteLine();
Console.WriteLine();
try
{
canalGate = new CanalLock();
canalGate.SetWaterLevel(WaterLevel.High);
canalGate.SetHighGate(open: true);
canalGate.SetWaterLevel(WaterLevel.Low);
}
catch (InvalidOperationException)
{
Console.WriteLine("invalid operation: Can't lower water when the high
gate is open.");
}
Console.WriteLine($"Try to lower water with high gate open: {canalGate}");
Execute o aplicativo novamente. Você pode ver que os novos testes falham e o bloqueio
do canal entra em um estado inválido. Tente implementar os métodos restantes por
conta própria. O método para definir a porta inferior deve ser semelhante àquele para
definir o portão superior. O método que altera o nível da água tem verificações
diferentes, mas deve seguir uma estrutura semelhante. Você pode achar útil usar o
mesmo processo para o método que define o nível da água. Comece com todas as
quatro entradas: o estado de ambos os portões, o estado atual do nível da água e o
novo nível de água solicitado. A expressão de comutador deve começar com:

C#

CanalLockWaterLevel = (newLevel, CanalLockWaterLevel, LowWaterGateOpen,


HighWaterGateOpen) switch
{
// elided
};

Você terá 16 braços de comutador totais para preencher. Em seguida, teste e


simplifique.

Você fez métodos como este?

C#

// Change the lower gate.


public void SetLowGate(bool open)
{
LowWaterGateOpen = (open, LowWaterGateOpen, CanalLockWaterLevel) switch
{
(false, _, _) => false,
(true, _, WaterLevel.Low) => true,
(true, false, WaterLevel.High) => throw new
InvalidOperationException("Cannot open high gate when the water is low"),
_ => throw new InvalidOperationException("Invalid internal state"),
};
}

// Change water level.


public void SetWaterLevel(WaterLevel newLevel)
{
CanalLockWaterLevel = (newLevel, CanalLockWaterLevel, LowWaterGateOpen,
HighWaterGateOpen) switch
{
(WaterLevel.Low, WaterLevel.Low, true, false) => WaterLevel.Low,
(WaterLevel.High, WaterLevel.High, false, true) => WaterLevel.High,
(WaterLevel.Low, _, false, false) => WaterLevel.Low,
(WaterLevel.High, _, false, false) => WaterLevel.High,
(WaterLevel.Low, WaterLevel.High, false, true) => throw new
InvalidOperationException("Cannot lower water when the high gate is open"),
(WaterLevel.High, WaterLevel.Low, true, false) => throw new
InvalidOperationException("Cannot raise water when the low gate is open"),
_ => throw new InvalidOperationException("Invalid internal state"),
};
}

Os testes devem ser aprovados e o bloqueio do canal deve operar com segurança.

Resumo
Neste tutorial, você aprendeu a usar a correspondência de padrões para verificar o
estado interno de um objeto antes de aplicar quaisquer alterações a ele. Você pode
verificar combinações de propriedades. Depois de criar tabelas para qualquer uma
dessas transições, você testa o código e, em seguida, simplifica a legibilidade e a
manutenção. Essas refatorações iniciais podem sugerir refatorações adicionais que
validam o estado interno ou gerenciam outras alterações de API. Este tutorial combinou
classes e objetos com uma abordagem mais orientada a dados e baseada em padrões
para implementar essas classes.
Tutorial: Escrever um manipulador de
interpolação de cadeia de caracteres
personalizado
Artigo • 06/04/2023

Neste tutorial, você aprenderá como:

" Implementar o padrão de manipulador de interpolação de cadeia de caracteres


" Interaja com o receptor em uma operação de interpolação de cadeia de caracteres.
" Adicionar argumentos ao manipulador de interpolação de cadeia de caracteres
" Entender os novos recursos de biblioteca para interpolação de cadeia de caracteres

Pré-requisitos
Você precisará configurar seu computador para executar o .NET 6, incluindo o
compilador C# 10. O compilador C# 10 está disponível a partir do Visual Studio 2022
ou do .NET 6 SDK .

Este tutorial pressupõe que você esteja familiarizado com o C# e .NET, incluindo o Visual
Studio ou a CLI do .NET.

Nova estrutura de tópicos


O C# 10 adiciona suporte para um manipulador de cadeia de caracteres interpolado
personalizado. Um manipulador de cadeia de caracteres interpolado é um tipo que
processa a expressão de espaço reservado em uma cadeia de caracteres interpolada.
Sem um manipulador personalizado, os espaços reservados são processados de forma
semelhante a String.Format. Cada espaço reservado é formatado como texto e, em
seguida, os componentes são concatenados para formar a cadeia de caracteres
resultante.

Você pode escrever um manipulador para qualquer cenário em que use informações
sobre a cadeia de caracteres resultante. Será usado? Quais restrições estão no formato?
Alguns exemplos incluem:

Você pode exigir que nenhuma das cadeias de caracteres resultantes seja maior
que algum limite, como 80 caracteres. Você pode processar as cadeias de
caracteres interpoladas para preencher um buffer de comprimento fixo e
interromper o processamento depois que o comprimento do buffer for atingido.
Você pode ter um formato tabular e cada espaço reservado deve ter um
comprimento fixo. Um manipulador personalizado pode impor isso, em vez de
forçar que todo o código do cliente esteja em conformidade.

Neste tutorial, você criará um manipulador de interpolação de cadeia de caracteres para


um dos principais cenários de desempenho: registrar bibliotecas em log. Dependendo
do nível de log configurado, o trabalho para construir uma mensagem de log não é
necessário. Se o registro em log estiver desativado, o trabalho para construir uma cadeia
de caracteres a partir de uma expressão de cadeia de caracteres interpolada não será
necessário. A mensagem nunca é impressa, portanto, qualquer concatenação de cadeia
de caracteres pode ser ignorada. Além disso, todas as expressões usadas nos espaços
reservados, incluindo a geração de rastreamentos de pilha, não precisam ser feitas.

Um manipulador de cadeia de caracteres interpolado pode determinar se a cadeia de


caracteres formatada será usada e só executará o trabalho necessário, se necessário.

Implementação inicial
Vamos começar de uma classe básica Logger que dá suporte a diferentes níveis:

C#

public enum LogLevel


{
Off,
Critical,
Error,
Warning,
Information,
Trace
}

public class Logger


{
public LogLevel EnabledLevel { get; init; } = LogLevel.Error;

public void LogMessage(LogLevel level, string msg)


{
if (EnabledLevel < level) return;
Console.WriteLine(msg);
}
}

Esse Logger dá suporte a seis níveis diferentes. Quando uma mensagem não passa no
filtro de nível de log, não há saída. A API pública do agente aceita uma cadeia de
caracteres (totalmente formatada) como a mensagem. Todo o trabalho para criar a
cadeia de caracteres já foi feito.

Implementar o padrão de manipulador


Essa etapa é para criar um manipulador de cadeia de caracteres interpolado que recrie o
comportamento atual. Um manipulador de cadeia de caracteres interpolado é um tipo
que deve ter as seguintes características:

O System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute aplicado
ao tipo.
Um construtor que tem dois parâmetros int , literalLength e formatCount . (Mais
parâmetros são permitidos).
Um método público AppendLiteral com a assinatura: public void
AppendLiteral(string s) .

Um método público AppendFormatted genérico com a assinatura: public void


AppendFormatted<T>(T t) .

Internamente, o construtor cria a cadeia de caracteres formatada e fornece um membro


para um cliente recuperar essa cadeia de caracteres. O código a seguir mostra um tipo
LogInterpolatedStringHandler que atende a esses requisitos:

C#

[InterpolatedStringHandler]
public ref struct LogInterpolatedStringHandler
{
// Storage for the built-up string
StringBuilder builder;

public LogInterpolatedStringHandler(int literalLength, int


formattedCount)
{
builder = new StringBuilder(literalLength);
Console.WriteLine($"\tliteral length: {literalLength},
formattedCount: {formattedCount}");
}

public void AppendLiteral(string s)


{
Console.WriteLine($"\tAppendLiteral called: {{{s}}}");

builder.Append(s);
Console.WriteLine($"\tAppended the literal string");
}

public void AppendFormatted<T>(T t)


{
Console.WriteLine($"\tAppendFormatted called: {{{t}}} is of type
{typeof(T)}");

builder.Append(t?.ToString());
Console.WriteLine($"\tAppended the formatted object");
}

internal string GetFormattedText() => builder.ToString();


}

Agora você pode adicionar uma sobrecarga a LogMessage na classe Logger para
experimentar o novo manipulador de cadeia de caracteres interpolado:

C#

public void LogMessage(LogLevel level, LogInterpolatedStringHandler builder)


{
if (EnabledLevel < level) return;
Console.WriteLine(builder.GetFormattedText());
}

Você não precisa remover o método original LogMessage , o compilador preferirá um


método com um parâmetro de manipulador interpolado em vez de um método com
um parâmetro string quando o argumento for uma expressão de cadeia de caracteres
interpolada.

Você pode verificar se o novo manipulador é invocado usando o seguinte código como
o programa principal:

C#

var logger = new Logger() { EnabledLevel = LogLevel.Warning };


var time = DateTime.Now;

logger.LogMessage(LogLevel.Error, $"Error Level. CurrentTime: {time}. This


is an error. It will be printed.");
logger.LogMessage(LogLevel.Trace, $"Trace Level. CurrentTime: {time}. This
won't be printed.");
logger.LogMessage(LogLevel.Warning, "Warning Level. This warning is a
string, not an interpolated string expression.");

A execução do aplicativo produz uma saída semelhante ao seguinte texto:

PowerShell

literal length: 65, formattedCount: 1


AppendLiteral called: {Error Level. CurrentTime: }
Appended the literal string
AppendFormatted called: {10/20/2021 12:19:10 PM} is of type
System.DateTime
Appended the formatted object
AppendLiteral called: {. This is an error. It will be printed.}
Appended the literal string
Error Level. CurrentTime: 10/20/2021 12:19:10 PM. This is an error. It will
be printed.
literal length: 50, formattedCount: 1
AppendLiteral called: {Trace Level. CurrentTime: }
Appended the literal string
AppendFormatted called: {10/20/2021 12:19:10 PM} is of type
System.DateTime
Appended the formatted object
AppendLiteral called: {. This won't be printed.}
Appended the literal string
Warning Level. This warning is a string, not an interpolated string
expression.

Rastreando a saída, você pode ver como o compilador adiciona código para chamar o
manipulador e criar a cadeia de caracteres:

O compilador adiciona uma chamada para construir o manipulador, passando o


comprimento total do texto literal na cadeia de caracteres de formato e o número
de espaços reservados.
O compilador adiciona chamadas a AppendLiteral e AppendFormatted para cada
seção da cadeia de caracteres literal e para cada espaço reservado.
O compilador invoca o método LogMessage usando o
CoreInterpolatedStringHandler como argumento.

Por fim, observe que o último aviso não invoca o manipulador de cadeia de caracteres
interpolado. O argumento é um string , de modo que a chamada invoca a outra
sobrecarga com um parâmetro de cadeia de caracteres.

Adicionar mais recursos ao manipulador


A versão anterior do manipulador de cadeia de caracteres interpolada implementa o
padrão. Para evitar o processamento de cada expressão de espaço reservado, você
precisará de mais informações no manipulador. Nesta seção, você melhorará seu
manipulador para que ele funcione menos quando a cadeia de caracteres construída
não for gravada no log. Você usa
System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute para
especificar um mapeamento entre parâmetros para uma API pública e parâmetros para
o construtor de um manipulador. Isso fornece ao manipulador as informações
necessárias para determinar se a cadeia de caracteres interpolada deve ser avaliada.
Vamos começar com alterações no Manipulador. Primeiro, adicione um campo para
acompanhar se o manipulador está habilitado. Adicione dois parâmetros ao construtor:
um para especificar o nível de log dessa mensagem e o outro como referência ao objeto
de log:

C#

private readonly bool enabled;

public LogInterpolatedStringHandler(int literalLength, int formattedCount,


Logger logger, LogLevel logLevel)
{
enabled = logger.EnabledLevel >= logLevel;
builder = new StringBuilder(literalLength);
Console.WriteLine($"\tliteral length: {literalLength}, formattedCount:
{formattedCount}");
}

Em seguida, use o campo para que o manipulador acrescente somente literais ou


objetos formatados quando a cadeia de caracteres final for usada:

C#

public void AppendLiteral(string s)


{
Console.WriteLine($"\tAppendLiteral called: {{{s}}}");
if (!enabled) return;

builder.Append(s);
Console.WriteLine($"\tAppended the literal string");
}

public void AppendFormatted<T>(T t)


{
Console.WriteLine($"\tAppendFormatted called: {{{t}}} is of type
{typeof(T)}");
if (!enabled) return;

builder.Append(t?.ToString());
Console.WriteLine($"\tAppended the formatted object");
}

Em seguida, você precisará atualizar a declaração LogMessage para que o compilador


passe os parâmetros adicionais para o construtor do manipulador. Isso é tratado usando
System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute no
argumento do manipulador:

C#
public void LogMessage(LogLevel level,
[InterpolatedStringHandlerArgument("", "level")]
LogInterpolatedStringHandler builder)
{
if (EnabledLevel < level) return;
Console.WriteLine(builder.GetFormattedText());
}

Esse atributo especifica a lista de argumentos para LogMessage esse mapa para os
parâmetros que seguem os parâmetros literalLength e formattedCount necessários. A
cadeia de caracteres vazia ("") especifica o receptor. O compilador substitui o valor do
objeto Logger representado pelo this pelo argumento seguinte para o construtor do
manipulador. O compilador substitui o valor de level pelo argumento a seguir. Você
pode fornecer qualquer número de argumentos para qualquer manipulador que
escrever. Os argumentos que você adiciona são argumentos de cadeia de caracteres.

Você pode executar essa versão usando o mesmo código de teste. Desta vez, você verá
os seguintes resultados:

PowerShell

literal length: 65, formattedCount: 1


AppendLiteral called: {Error Level. CurrentTime: }
Appended the literal string
AppendFormatted called: {10/20/2021 12:19:10 PM} is of type
System.DateTime
Appended the formatted object
AppendLiteral called: {. This is an error. It will be printed.}
Appended the literal string
Error Level. CurrentTime: 10/20/2021 12:19:10 PM. This is an error. It will
be printed.
literal length: 50, formattedCount: 1
AppendLiteral called: {Trace Level. CurrentTime: }
AppendFormatted called: {10/20/2021 12:19:10 PM} is of type
System.DateTime
AppendLiteral called: {. This won't be printed.}
Warning Level. This warning is a string, not an interpolated string
expression.

Você pode ver que os métodos AppendLiteral e AppendFormat estão sendo chamados,
mas eles não estão fazendo nenhum trabalho. O manipulador determinou que a cadeia
de caracteres final não será necessária, portanto, o manipulador não a compila. Ainda há
alguns aprimoramentos a serem feitos.

Primeiro, você pode adicionar uma sobrecarga de AppendFormatted que restringe o


argumento a um tipo que implementa System.IFormattable. Essa sobrecarga permite
que os chamadores adicionem cadeias de caracteres de formato nos espaços
reservados. Ao fazer essa alteração, também vamos alterar o tipo de retorno dos outros
métodos AppendFormatted e AppendLiteral , de void para bool (se algum desses
métodos tiver tipos de retorno diferentes, você receberá um erro de compilação). Essa
alteração permite curto-circuito. Os métodos retornam false para indicar que o
processamento da expressão de cadeia de caracteres interpolada deve ser interrompido.
Retornar true indica que ele deve continuar. Neste exemplo, você está usando-o para
interromper o processamento quando a cadeia de caracteres resultante não for
necessária. O curto-circuito dá suporte a ações mais refinadas. Você pode parar de
processar a expressão quando ela atingir um determinado comprimento, para dar
suporte a buffers de comprimento fixo. Ou alguma condição pode indicar que
elementos restantes não são necessários.

C#

public void AppendFormatted<T>(T t, string format) where T : IFormattable


{
Console.WriteLine($"\tAppendFormatted (IFormattable version) called: {t}
with format {{{format}}} is of type {typeof(T)},");

builder.Append(t?.ToString(format, null));
Console.WriteLine($"\tAppended the formatted object");
}

Com essa adição, você pode especificar cadeias de caracteres de formato em sua
expressão de cadeia de caracteres interpolada:

C#

var time = DateTime.Now;

logger.LogMessage(LogLevel.Error, $"Error Level. CurrentTime: {time}. The


time doesn't use formatting.");
logger.LogMessage(LogLevel.Error, $"Error Level. CurrentTime: {time:t}. This
is an error. It will be printed.");
logger.LogMessage(LogLevel.Trace, $"Trace Level. CurrentTime: {time:t}. This
won't be printed.");

O :t na primeira mensagem especifica o "formato de hora curto" para a hora atual. O


exemplo anterior mostrou uma das sobrecargas para o método AppendFormatted que
você pode criar para o manipulador. Você não precisa especificar um argumento
genérico para o objeto que está sendo formatado. Você pode ter maneiras mais
eficientes de converter tipos criados em cadeia de caracteres. Você pode escrever
sobrecargas de AppendFormatted que adota esses tipos em vez de um argumento
genérico. O compilador escolherá a melhor sobrecarga. O runtime usa essa técnica para
converter System.Span<T> em saída de cadeia de caracteres. Você pode adicionar um
parâmetro inteiro para especificar o alinhamento da saída, com ou sem um
IFormattable. O System.Runtime.CompilerServices.DefaultInterpolatedStringHandler que
é fornecido com o .NET 6 contém nove sobrecargas de AppendFormatted para usos
diferentes. Você pode usá-lo como referência ao criar um manipulador para suas
finalidades.

Execute o exemplo agora e você verá que, para a mensagem Trace , apenas o primeiro
AppendLiteral é chamado:

PowerShell

literal length: 60, formattedCount: 1


AppendLiteral called: Error Level. CurrentTime:
Appended the literal string
AppendFormatted called: 10/20/2021 12:18:29 PM is of type
System.DateTime
Appended the formatted object
AppendLiteral called: . The time doesn't use formatting.
Appended the literal string
Error Level. CurrentTime: 10/20/2021 12:18:29 PM. The time doesn't use
formatting.
literal length: 65, formattedCount: 1
AppendLiteral called: Error Level. CurrentTime:
Appended the literal string
AppendFormatted (IFormattable version) called: 10/20/2021 12:18:29
PM with format {t} is of type System.DateTime,
Appended the formatted object
AppendLiteral called: . This is an error. It will be printed.
Appended the literal string
Error Level. CurrentTime: 12:18 PM. This is an error. It will be printed.
literal length: 50, formattedCount: 1
AppendLiteral called: Trace Level. CurrentTime:
Warning Level. This warning is a string, not an interpolated string
expression.

Você pode fazer uma atualização final para o construtor do manipulador que melhora a
eficiência. O manipulador pode adicionar um parâmetro final out bool . Definir esse
parâmetro para false indica que o manipulador não deve ser chamado para processar
a expressão de cadeia de caracteres interpolada:

C#

public LogInterpolatedStringHandler(int literalLength, int formattedCount,


Logger logger, LogLevel level, out bool isEnabled)
{
isEnabled = logger.EnabledLevel >= level;
Console.WriteLine($"\tliteral length: {literalLength}, formattedCount:
{formattedCount}");
builder = isEnabled ? new StringBuilder(literalLength) : default!;
}

Essa alteração significa que você pode remover o campo enabled . Em seguida, você
pode alterar o tipo de retorno de AppendLiteral e AppendFormatted para void . Agora,
ao executar o exemplo, você verá a seguinte saída:

PowerShell

literal length: 60, formattedCount: 1


AppendLiteral called: Error Level. CurrentTime:
Appended the literal string
AppendFormatted called: 10/20/2021 12:19:10 PM is of type
System.DateTime
Appended the formatted object
AppendLiteral called: . The time doesn't use formatting.
Appended the literal string
Error Level. CurrentTime: 10/20/2021 12:19:10 PM. The time doesn't use
formatting.
literal length: 65, formattedCount: 1
AppendLiteral called: Error Level. CurrentTime:
Appended the literal string
AppendFormatted (IFormattable version) called: 10/20/2021 12:19:10
PM with format {t} is of type System.DateTime,
Appended the formatted object
AppendLiteral called: . This is an error. It will be printed.
Appended the literal string
Error Level. CurrentTime: 12:19 PM. This is an error. It will be printed.
literal length: 50, formattedCount: 1
Warning Level. This warning is a string, not an interpolated string
expression.

A única saída quando LogLevel.Trace foi especificado é a saída do construtor. O


manipulador indicou que não está habilitado, portanto, nenhum dos métodos Append
foi invocado.

Esse exemplo ilustra um ponto importante para manipuladores de cadeias de caracteres


interpoladas, especialmente quando bibliotecas de log são usadas. Quaisquer efeitos
colaterais nos espaços reservados podem não ocorrer. Adicione o seguinte código ao
programa principal e veja esse comportamento em ação:

C#

int index = 0;
int numberOfIncrements = 0;
for (var level = LogLevel.Critical; level <= LogLevel.Trace; level++)
{
Console.WriteLine(level);
logger.LogMessage(level, $"{level}: Increment index a few times
{index++}, {index++}, {index++}, {index++}, {index++}");
numberOfIncrements += 5;
}
Console.WriteLine($"Value of index {index}, value of numberOfIncrements:
{numberOfIncrements}");

Você pode ver que a variável index é incrementada cinco vezes a cada iteração do loop.
Como os espaços reservados são avaliados apenas para Critical , Error e Warning
níveis, não para Information e Trace , o valor final de index não corresponde à
expectativa:

PowerShell

Critical
Critical: Increment index a few times 0, 1, 2, 3, 4
Error
Error: Increment index a few times 5, 6, 7, 8, 9
Warning
Warning: Increment index a few times 10, 11, 12, 13, 14
Information
Trace
Value of index 15, value of numberOfIncrements: 25

Manipuladores de cadeia de caracteres interpolados fornecem maior controle sobre


como uma expressão de cadeia de caracteres interpolada é convertida em uma cadeia
de caracteres. A equipe de runtime do .NET já usou esse recurso para melhorar o
desempenho em várias áreas. Você pode usar a mesma funcionalidade em suas próprias
bibliotecas. Para explorar mais, confira
System.Runtime.CompilerServices.DefaultInterpolatedStringHandler. Ele fornece uma
implementação mais completa do que a que você criou aqui. Você verá muitas outras
sobrecargas possíveis para os métodos Append .
Criar tipos de registro
Artigo • 14/11/2023

Os registros são tipos que usam a igualdade baseada em valor. O C# 10 adiciona


estruturas de registro para que você possa definir registros como tipos de valor. Duas
variáveis de um tipo de registro são iguais se as definições de tipo de registro forem
idênticas e se, para cada campo, os valores em ambos os registros forem iguais. Duas
variáveis de um tipo de classe são iguais se os objetos referidos forem do mesmo tipo
de classe e as variáveis se referirem ao mesmo objeto. A igualdade baseada em valor
implica outros recursos que você provavelmente desejará em tipos de registro. O
compilador gera muitos desses membros quando você declara um em vez de um
record class arquivo . O compilador gera esses mesmos métodos para record struct

tipos.

Neste tutorial, irá aprender a:

" Decida se você adiciona o record modificador a um class tipo.


" Declare tipos de registro e tipos de registro posicional.
" Substitua seus métodos por métodos gerados pelo compilador em registros.

Pré-requisitos
Você precisará configurar sua máquina para executar o .NET 6 ou posterior, incluindo o
compilador C# 10 ou posterior. O compilador C# 10 está disponível a partir do Visual
Studio 2022 ou do SDK do .NET 6.

Características dos registos


Você define um registro declarando um tipo com a record palavra-chave, modificando
uma class ou struct declaração. Opcionalmente, você pode omitir a class palavra-
chave para criar um record class arquivo . Um registro segue a semântica de igualdade
baseada em valor. Para impor a semântica de valores, o compilador gera vários métodos
para seu tipo de registro (tanto para tipos quanto record struct para record class
tipos):

Uma substituição de Object.Equals(Object).


Um método virtual Equals cujo parâmetro é o tipo de registro.
Uma substituição de Object.GetHashCode().
Métodos para operator == e operator != .
Os tipos de registro implementam System.IEquatable<T>o .

Os registros também fornecem uma substituição de Object.ToString(). O compilador


sintetiza métodos para exibir registros usando Object.ToString(). Você explorará esses
membros enquanto escreve o código para este tutorial. Os registros suportam with
expressões para permitir a mutação não destrutiva dos registros.

Você também pode declarar registros posicionais usando uma sintaxe mais concisa. O
compilador sintetiza mais métodos para você quando você declara registros posicionais:

Um construtor primário cujos parâmetros correspondem aos parâmetros


posicionais na declaração de registro.
Propriedades públicas para cada parâmetro de um construtor primário. Essas
propriedades são somente de inicialização para record class tipos e readonly
record struct tipos. Para record struct tipos, eles são leitura-gravação.

Um Deconstruct método para extrair propriedades do registro.

Dados de temperatura da construção


Dados e estatísticas estão entre os cenários em que você vai querer usar registros. Para
este tutorial, você criará um aplicativo que calcula os dias de graduação para diferentes
usos. Os graus-dias são uma medida de calor (ou falta de calor) durante um período de
dias , semanas ou meses. Os graus-dias rastreiam e preveem o uso de energia. Dias mais
quentes significam mais ar condicionado e dias mais frios significam mais utilização do
forno. Os graus-dias ajudam a gerir as populações de plantas e correlacionam-se com o
crescimento das plantas à medida que as estações mudam. Os graus-dias ajudam a
rastrear as migrações de animais para espécies que viajam para corresponder ao clima.

A fórmula baseia-se na temperatura média num determinado dia e numa temperatura


de base. Para calcular graus-dias ao longo do tempo, você precisará da temperatura alta
e baixa todos os dias por um período de tempo. Vamos começar criando um novo
aplicativo. Crie um novo aplicativo de console. Crie um novo tipo de registro em um
novo arquivo chamado "DailyTemperature.cs":

C#

public readonly record struct DailyTemperature(double HighTemp, double


LowTemp);

O código anterior define um registro posicional. O DailyTemperature registro é um


readonly record struct , porque você não pretende herdar dele, e deve ser imutável. As

HighTemp propriedades e LowTemp são init only properties, o que significa que podem ser
definidas no construtor ou usando um inicializador de propriedade. Se você quiser que
os parâmetros posicionais sejam leitura-gravação, declare um em vez de um record
struct readonly record struct arquivo . O DailyTemperature tipo também tem um

construtor primário que tem dois parâmetros que correspondem às duas propriedades.
Use o construtor primário para inicializar um DailyTemperature registro. O código a
seguir cria e inicializa vários DailyTemperature registros. O primeiro usa parâmetros
nomeados para esclarecer o HighTemp e LowTemp . Os inicializadores restantes usam
parâmetros posicionais para inicializar o HighTemp e LowTemp :

C#

private static DailyTemperature[] data = [


new DailyTemperature(HighTemp: 57, LowTemp: 30),
new DailyTemperature(60, 35),
new DailyTemperature(63, 33),
new DailyTemperature(68, 29),
new DailyTemperature(72, 47),
new DailyTemperature(75, 55),
new DailyTemperature(77, 55),
new DailyTemperature(72, 58),
new DailyTemperature(70, 47),
new DailyTemperature(77, 59),
new DailyTemperature(85, 65),
new DailyTemperature(87, 65),
new DailyTemperature(85, 72),
new DailyTemperature(83, 68),
new DailyTemperature(77, 65),
new DailyTemperature(72, 58),
new DailyTemperature(77, 55),
new DailyTemperature(76, 53),
new DailyTemperature(80, 60),
new DailyTemperature(85, 66)
];

Você pode adicionar suas próprias propriedades ou métodos aos registros, incluindo
registros posicionais. Você precisará calcular a temperatura média para cada dia. Você
pode adicionar essa propriedade ao DailyTemperature registro:

C#

public readonly record struct DailyTemperature(double HighTemp, double


LowTemp)
{
public double Mean => (HighTemp + LowTemp) / 2.0;
}
Vamos nos certificar de que você pode usar esses dados. Adicione o seguinte código ao
seu Main método:

C#

foreach (var item in data)


Console.WriteLine(item);

Execute seu aplicativo e você verá uma saída semelhante à exibição a seguir (várias
linhas removidas por espaço):

.NET CLI

DailyTemperature { HighTemp = 57, LowTemp = 30, Mean = 43.5 }


DailyTemperature { HighTemp = 60, LowTemp = 35, Mean = 47.5 }

DailyTemperature { HighTemp = 80, LowTemp = 60, Mean = 70 }


DailyTemperature { HighTemp = 85, LowTemp = 66, Mean = 75.5 }

O código anterior mostra a saída da substituição de sintetizado ToString pelo


compilador. Se você preferir um texto diferente, você pode escrever sua própria versão
que impede que ToString o compilador sintetize uma versão para você.

Calcular dias de grau


Para calcular graus-dias, você tira a diferença de uma temperatura de linha de base e a
temperatura média em um determinado dia. Para medir o calor ao longo do tempo,
você descarta todos os dias em que a temperatura média está abaixo da linha de base.
Para medir o frio ao longo do tempo, você descarta todos os dias em que a temperatura
média está acima da linha de base. Por exemplo, os EUA usam 65F como base para
graus-dias de aquecimento e resfriamento. Essa é a temperatura em que não é
necessário aquecimento ou refrigeração. Se um dia tem uma temperatura média de 70F,
esse dia é cinco graus dias de resfriamento e zero graus de aquecimento dias. Por outro
lado, se a temperatura média for de 55F, esse dia é de 10 graus dias de aquecimento e 0
graus de resfriamento dias.

Você pode expressar essas fórmulas como uma pequena hierarquia de tipos de registro:
um tipo abstrato de dia de grau e dois tipos concretos para graus-dias de aquecimento
e graus-dias de resfriamento. Esses tipos também podem ser registros posicionais. Eles
tomam uma temperatura de linha de base e uma sequência de registros diários de
temperatura como argumentos para o construtor primário:
C#

public abstract record DegreeDays(double BaseTemperature,


IEnumerable<DailyTemperature> TempRecords);

public sealed record HeatingDegreeDays(double BaseTemperature,


IEnumerable<DailyTemperature> TempRecords)
: DegreeDays(BaseTemperature, TempRecords)
{
public double DegreeDays => TempRecords.Where(s => s.Mean <
BaseTemperature).Sum(s => BaseTemperature - s.Mean);
}

public sealed record CoolingDegreeDays(double BaseTemperature,


IEnumerable<DailyTemperature> TempRecords)
: DegreeDays(BaseTemperature, TempRecords)
{
public double DegreeDays => TempRecords.Where(s => s.Mean >
BaseTemperature).Sum(s => s.Mean - BaseTemperature);
}

O registro abstrato DegreeDays é a classe base compartilhada para os


HeatingDegreeDays registros e CoolingDegreeDays . As declarações do construtor

primário nos registros derivados mostram como gerenciar a inicialização do registro


base. Seu registro derivado declara parâmetros para todos os parâmetros no construtor
primário do registro base. O registro base declara e inicializa essas propriedades. O
registro derivado não os oculta, mas apenas cria e inicializa propriedades para
parâmetros que não são declarados em seu registro base. Neste exemplo, os registros
derivados não adicionam novos parâmetros primários do construtor. Teste seu código
adicionando o seguinte código ao seu Main método:

C#

var heatingDegreeDays = new HeatingDegreeDays(65, data);


Console.WriteLine(heatingDegreeDays);

var coolingDegreeDays = new CoolingDegreeDays(65, data);


Console.WriteLine(coolingDegreeDays);

Você obterá uma saída como a seguinte exibição:

.NET CLI

HeatingDegreeDays { BaseTemperature = 65, TempRecords =


record_types.DailyTemperature[], DegreeDays = 85 }
CoolingDegreeDays { BaseTemperature = 65, TempRecords =
record_types.DailyTemperature[], DegreeDays = 71.5 }
Definir métodos sintetizados pelo compilador
Seu código calcula o número correto de graus-dias de aquecimento e resfriamento
durante esse período de tempo. Mas este exemplo mostra por que você pode querer
substituir alguns dos métodos sintetizados para registros. Você pode declarar sua
própria versão de qualquer um dos métodos sintetizados pelo compilador em um tipo
de registro, exceto o método clone. O método clone tem um nome gerado pelo
compilador e você não pode fornecer uma implementação diferente. Esses métodos
sintetizados incluem um construtor de cópia, os membros da interface, testes de
System.IEquatable<T> igualdade e desigualdade e GetHashCode(). Para isso, você vai
sintetizar PrintMembers . Você também pode declarar o seu próprio ToString , mas
PrintMembers fornece uma opção melhor para cenários de herança. Para fornecer sua
própria versão de um método sintetizado, a assinatura deve corresponder ao método
sintetizado.

O TempRecords elemento na saída do console não é útil. Ele exibe o tipo, mas nada mais.
Você pode alterar esse comportamento fornecendo sua própria implementação do
método sintetizado PrintMembers . A assinatura depende dos modificadores aplicados à
record declaração:

Se um tipo de registo for , ou um record struct , a assinatura for sealed private


bool PrintMembers(StringBuilder builder);

Se um tipo de registro não for e derivar de object (ou seja, não sealed declarar
um registro base), a assinatura será protected virtual bool
PrintMembers(StringBuilder builder);

Se um tipo de registro não sealed for e derivar de outro registro, a assinatura será
protected override bool PrintMembers(StringBuilder builder);

Estas regras são mais fáceis de compreender através da compreensão do propósito do


PrintMembers . PrintMembers Adiciona informações sobre cada propriedade em um tipo

de registro a uma cadeia de caracteres. O contrato exige registros básicos para


adicionar seus membros à exibição e pressupõe que os membros derivados adicionarão
seus membros. Cada tipo de registro sintetiza uma ToString substituição semelhante ao
exemplo a seguir para HeatingDegreeDays :

C#

public override string ToString()


{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append("HeatingDegreeDays");
stringBuilder.Append(" { ");
if (PrintMembers(stringBuilder))
{
stringBuilder.Append(" ");
}
stringBuilder.Append("}");
return stringBuilder.ToString();
}

Você declara um PrintMembers método no DegreeDays registro que não imprime o tipo
da coleção:

C#

protected virtual bool PrintMembers(StringBuilder stringBuilder)


{
stringBuilder.Append($"BaseTemperature = {BaseTemperature}");
return true;
}

A assinatura declara um virtual protected método para corresponder à versão do


compilador. Não se preocupe se você errar os acessadores; o idioma impõe a assinatura
correta. Se você esquecer os modificadores corretos para qualquer método sintetizado,
o compilador emitirá avisos ou erros que ajudam você a obter a assinatura correta.

Em C# 10 e posterior, você pode declarar o ToString método como sealed em um tipo


de registro. Isso impede que os registros derivados forneçam uma nova implementação.
Os registros derivados ainda conterão a PrintMembers substituição. Você selaria
ToString se não quisesse que ele exibisse o tipo de tempo de execução do registro. No

exemplo anterior, você perderia as informações sobre onde o registro estava medindo
graus-dias de aquecimento ou resfriamento.

Mutação não destrutiva


Os membros sintetizados em uma classe de registro posicional não modificam o estado
do registro. O objetivo é que você possa criar registros imutáveis com mais facilidade.
Lembre-se de que você declara um readonly record struct para criar uma struct de
registro imutável. Analise novamente as declarações anteriores para HeatingDegreeDays
e CoolingDegreeDays . Os membros adicionados executam cálculos nos valores para o
registro, mas não mudam de estado. Os registros posicionais facilitam a criação de tipos
de referência imutáveis.

Criar tipos de referência imutáveis significa que você vai querer usar mutações não
destrutivas. Você cria novas instâncias de registro que são semelhantes às instâncias de
registro existentes usando with expressões. Essas expressões são uma construção de
cópia com atribuições adicionais que modificam a cópia. O resultado é uma nova
instância de registro em que cada propriedade foi copiada do registro existente e,
opcionalmente, modificada. O registo original mantém-se inalterado.

Vamos adicionar alguns recursos ao seu programa que demonstram with expressões.
Primeiro, vamos criar um novo registro para calcular graus-dias crescentes usando os
mesmos dados. Os graus-dias de crescimento normalmente usam 41F como linha de
base e medem as temperaturas acima da linha de base. Para usar os mesmos dados,
você pode criar um novo registro semelhante ao coolingDegreeDays , mas com uma
temperatura base diferente:

C#

// Growing degree days measure warming to determine plant growing rates


var growingDegreeDays = coolingDegreeDays with { BaseTemperature = 41 };
Console.WriteLine(growingDegreeDays);

Você pode comparar o número de graus computados com os números gerados com
uma temperatura de base mais alta. Lembre-se de que os registros são tipos de
referência e essas cópias são cópias superficiais. A matriz dos dados não é copiada, mas
ambos os registros se referem aos mesmos dados. Esse facto é uma vantagem num
outro cenário. Para os dias de grau, é útil acompanhar o total dos cinco dias anteriores.
Você pode criar novos registros com dados de origem diferentes usando with
expressões. O código a seguir cria uma coleção dessas acumulações e, em seguida,
exibe os valores:

C#

// showing moving accumulation of 5 days using range syntax


List<CoolingDegreeDays> movingAccumulation = new();
int rangeSize = (data.Length > 5) ? 5 : data.Length;
for (int start = 0; start < data.Length - rangeSize; start++)
{
var fiveDayTotal = growingDegreeDays with { TempRecords = data[start..
(start + rangeSize)] };
movingAccumulation.Add(fiveDayTotal);
}
Console.WriteLine();
Console.WriteLine("Total degree days in the last five days");
foreach(var item in movingAccumulation)
{
Console.WriteLine(item);
}
Você também pode usar with expressões para criar cópias de registros. Não especifique
nenhuma propriedade entre as chaves para a with expressão. Isso significa criar uma
cópia e não alterar nenhuma propriedade:

C#

var growingDegreeDaysCopy = growingDegreeDays with { };

Execute o aplicativo concluído para ver os resultados.

Resumo
Este tutorial mostrou vários aspetos dos registros. Os registros fornecem sintaxe concisa
para tipos em que o uso fundamental é o armazenamento de dados. Para classes
orientadas a objetos, o uso fundamental é definir responsabilidades. Este tutorial
concentrou-se em registros posicionais, onde você pode usar uma sintaxe concisa para
declarar as propriedades de um registro. O compilador sintetiza vários membros do
registro para copiar e comparar registros. Pode adicionar quaisquer outros membros de
que necessite para os seus tipos de registo. Você pode criar tipos de registro imutáveis
sabendo que nenhum dos membros gerados pelo compilador mudaria de estado. E
with as expressões facilitam o suporte a mutações não destrutivas.

Os registros adicionam outra maneira de definir tipos. Você usa class definições para
criar hierarquias orientadas a objetos que se concentram nas responsabilidades e no
comportamento dos objetos. Você cria struct tipos para estruturas de dados que
armazenam dados e são pequenas o suficiente para copiar com eficiência. Você cria
record tipos quando deseja igualdade e comparação baseadas em valor, não deseja

copiar valores e deseja usar variáveis de referência. Você cria record struct tipos
quando deseja os recursos de registros para um tipo que é pequeno o suficiente para
copiar com eficiência.

Você pode saber mais sobre registros no artigo de referência da linguagem C# para o
tipo de registro e a especificação de tipo de registro proposta e a especificação de
estrutura de registro.

6 Colabore connosco no Comentários do .NET


GitHub O .NET é um projeto código aberto.
A origem deste conteúdo pode Selecione um link para fornecer
ser encontrada no GitHub, onde comentários:
também pode criar e rever  Abrir um problema de
problemas e pedidos Pull. Para
documentação
mais informações, consulte o
nosso guia do contribuidor.  Fornecer comentários sobre o
produto
Tutorial: Explorar ideias usando
instruções de nível superior para criar
código conforme você aprende
Artigo • 15/11/2023

Neste tutorial, você aprenderá como:

" Conheça as regras que regem o uso de instruções de nível superior.


" Use instruções de nível superior para explorar algoritmos.
" Refatore explorações em componentes reutilizáveis.

Pré-requisitos
Você precisará configurar seu computador para executar o .NET 6, que inclui o
compilador C# 10. O compilador C# 10 está disponível a partir do Visual Studio 2022
ou do .NET 6 SDK .

Este tutorial pressupõe que você esteja familiarizado com o C# e .NET, incluindo o Visual
Studio ou a CLI do .NET.

Comece a explorar
As instruções de nível superior permitem evitar a cerimônia extra necessária colocando
o ponto de entrada do programa em um método estático em uma classe. O ponto de
partida típico para um novo aplicativo de console se parece com o seguinte código:

C#

using System;

namespace Application
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
O código anterior é o resultado da execução do comando dotnet new console e da
criação de um novo aplicativo de console. Essas 11 linhas contêm apenas uma linha de
código executável. Você pode simplificar esse programa com o novo recurso de
instruções de nível superior. Isso permite que você remova todas, exceto duas das linhas
deste programa:

C#

// See https://aka.ms/new-console-template for more information


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

) 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

Esse recurso simplifica o que é necessário para começar a explorar novas ideias. Você
pode usar instruções de nível superior em cenários de script ou para explorar. Assim que
o básico estiver funcionando, você pode começar a refatorar o código e criar métodos,
classes ou outros assemblies para componentes reutilizáveis criados. As instruções de
nível superior habilitam experimentações rápidas e tutoriais iniciantes. Elas também
fornecem um caminho tranquilo da experimentação a programas completos.

As instruções de nível superior são executadas na ordem em que aparecem no arquivo.


As instruções de nível superior só podem ser usadas em um arquivo de origem em seu
aplicativo. O compilador gera um erro se você usá-los em mais de um arquivo.
Criar um computador de resposta mágico do
.NET
Neste tutorial, vamos criar um aplicativo de console que responda a uma pergunta "sim"
ou "não" com uma resposta aleatória. Você criará a funcionalidade passo a passo. Você
pode se concentrar em sua tarefa em vez da cerimônia necessária para a estrutura de
um programa típico. Em seguida, quando estiver satisfeito com a funcionalidade, você
poderá refatorar o aplicativo conforme julgar adequado.

Um bom ponto de partida é gravar a pergunta de volta para o console. Você pode
começar gravando o seguinte código:

C#

Console.WriteLine(args);

Você não declara uma variável args . Para o arquivo de origem único que contém suas
instruções de nível superior, o compilador reconhece args como significando os
argumentos de linha de comando. O tipo de argumento é um string[] , como em
todos os programas C#.

O código pode ser testado executando o comando dotnet run a seguir:

CLI do .NET

dotnet run -- Should I use top level statements in all my programs?

Os argumentos após a linha de comando -- são passados para o programa. Você pode
ver o tipo da variável args , porque é isso que é impresso no console:

Console

System.String[]

Para gravar a pergunta no console, você precisará enumerar os argumentos e separá-los


com um espaço. Substitua a chamada WriteLine pelo código a seguir:

C#

Console.WriteLine();
foreach(var s in args)
{
Console.Write(s);
Console.Write(' ');
}
Console.WriteLine();

Agora, ao executar o programa, ele exibirá corretamente a pergunta como uma cadeia
de argumentos.

Responder com uma resposta aleatória


Após ecoar a pergunta, você pode adicionar o código para gerar a resposta aleatória.
Comece adicionando uma matriz de respostas possíveis:

C#

string[] answers =
[
"It is certain.", "Reply hazy, try again.", "Don’t count on
it.",
"It is decidedly so.", "Ask again later.", "My reply is no.",
"Without a doubt.", "Better not tell you now.", "My sources say
no.",
"Yes – definitely.", "Cannot predict now.", "Outlook not so
good.",
"You may rely on it.", "Concentrate and ask again.", "Very doubtful.",
"As I see it, yes.",
"Most likely.",
"Outlook good.",
"Yes.",
"Signs point to yes.",
];

Essa matriz tem dez respostas afirmativas, cinco não confirmadas e cinco negativas. Em
seguida, adicione o seguinte código para gerar e exibir uma resposta aleatória da
matriz:

C#

var index = new Random().Next(answers.Length - 1);


Console.WriteLine(answers[index]);

Você pode executar o aplicativo novamente para ver os resultados. Você deve ver algo
semelhante à seguinte saída:

CLI do .NET
dotnet run -- Should I use top level statements in all my programs?

Should I use top level statements in all my programs?


Better not tell you now.

Esse código responde às perguntas, mas vamos adicionar mais um recurso. Você
gostaria que seu aplicativo de perguntas simulasse o pensamento sobre a resposta.
Você pode fazer isso adicionando um pouco de animação do ASCII e pausando
enquanto trabalha. Adicione o seguinte código após a linha que ecoa a pergunta:

C#

for (int i = 0; i < 20; i++)


{
Console.Write("| -");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("/ \\");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("- |");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("\\ /");
await Task.Delay(50);
Console.Write("\b\b\b");
}
Console.WriteLine();

Também será necessário adicionar uma instrução using à parte superior do arquivo de
origem:

C#

using System.Threading.Tasks;

As instruções using devem aparecer antes de qualquer outra instrução no arquivo. Caso
contrário, é um erro do compilador. Você pode executar o programa novamente e ver a
animação. Isso torna uma experiência melhor. Escolha um comprimento do atraso que
corresponda ao seu gosto.

O código anterior cria um conjunto de linhas de rotação separadas por um espaço. A


adição da palavra-chave await instrui o compilador a gerar o ponto de entrada do
programa como um método que tem o modificador async e retorna um
System.Threading.Tasks.Task. Esse programa não retorna um valor, portanto, o ponto de
entrada do programa retorna um Task . Se o programa retornar um valor inteiro, você
adicionará uma instrução return ao final de suas instruções de nível superior. Essa
instrução return especificaria o valor inteiro a ser retornado. Se suas instruções de nível
superior incluírem uma expressão await , o tipo de retorno se tornará
System.Threading.Tasks.Task<TResult>.

Refatoração para o futuro


O programa deverá ser semelhante ao seguinte código:

C#

Console.WriteLine();
foreach(var s in args)
{
Console.Write(s);
Console.Write(' ');
}
Console.WriteLine();

for (int i = 0; i < 20; i++)


{
Console.Write("| -");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("/ \\");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("- |");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("\\ /");
await Task.Delay(50);
Console.Write("\b\b\b");
}
Console.WriteLine();

string[] answers =
[
"It is certain.", "Reply hazy, try again.", "Don't count on
it.",
"It is decidedly so.", "Ask again later.", "My reply is no.",
"Without a doubt.", "Better not tell you now.", "My sources say
no.",
"Yes – definitely.", "Cannot predict now.", "Outlook not so
good.",
"You may rely on it.", "Concentrate and ask again.", "Very doubtful.",
"As I see it, yes.",
"Most likely.",
"Outlook good.",
"Yes.",
"Signs point to yes.",
];

var index = new Random().Next(answers.Length - 1);


Console.WriteLine(answers[index]);

O código anterior é razoável. Funciona. Mas não é reutilizável. Agora que o aplicativo
está funcionando, é hora de retirar as partes reutilizáveis.

Um candidato é o código que exibe a animação de espera. Esse snippet pode se tornar
um método:

Você pode começar criando uma função local em seu arquivo. Substitua a animação
atual pelo seguinte:

C#

await ShowConsoleAnimation();

static async Task ShowConsoleAnimation()


{
for (int i = 0; i < 20; i++)
{
Console.Write("| -");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("/ \\");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("- |");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("\\ /");
await Task.Delay(50);
Console.Write("\b\b\b");
}
Console.WriteLine();
}

O código anterior cria uma função local no método principal. Ainda não é reutilizável.
Então, extraia esse código em uma classe. Crie um novo arquivo chamado utilities.cs e
adicione o seguinte código:

C#

namespace MyNamespace
{
public static class Utilities
{
public static async Task ShowConsoleAnimation()
{
for (int i = 0; i < 20; i++)
{
Console.Write("| -");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("/ \\");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("- |");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("\\ /");
await Task.Delay(50);
Console.Write("\b\b\b");
}
Console.WriteLine();
}
}
}

Um arquivo com instruções de nível superior também pode conter namespaces e tipos
no final do arquivo, após as instruções de nível superior. Porém, para este tutorial, você
coloca o método de animação em um arquivo separado para torná-lo mais facilmente
reutilizável.

Por fim, é possível limpar o código de animação para remover algumas duplicações:

C#

foreach (string s in animations)


{
Console.Write(s);
await Task.Delay(50);
Console.Write("\b\b\b");
}

Agora você tem um aplicativo completo e refatorou as partes reutilizáveis para uso
posterior. Você pode chamar o novo método utilitário de suas instruções de nível
superior, conforme exibido abaixo na versão final do programa principal:

C#

using MyNamespace;

Console.WriteLine();
foreach(var s in args)
{
Console.Write(s);
Console.Write(' ');
}
Console.WriteLine();

await Utilities.ShowConsoleAnimation();

string[] answers =
[
"It is certain.", "Reply hazy, try again.", "Don’t count on
it.",
"It is decidedly so.", "Ask again later.", "My reply is no.",
"Without a doubt.", "Better not tell you now.", "My sources say
no.",
"Yes – definitely.", "Cannot predict now.", "Outlook not so
good.",
"You may rely on it.", "Concentrate and ask again.", "Very doubtful.",
"As I see it, yes.",
"Most likely.",
"Outlook good.",
"Yes.",
"Signs point to yes.",
];

var index = new Random().Next(answers.Length - 1);


Console.WriteLine(answers[index]);

O exemplo anterior adiciona a chamada Utilities.ShowConsoleAnimation e uma


instrução adicional using .

Resumo
As instruções de nível superior facilitam a criação de programas simples para uso para
explorar novos algoritmos. É possível experimentar algoritmos tentando diferentes
snippets de código. Após verificar o que funciona, é possível refatorar o código para ser
mais sustentável.

As instruções de nível superior simplificam programas baseados em aplicativos de


console. Elas incluem funções do Azure, ações do GitHub e outros utilitários pequenos.
Para obter mais informações, consulte Instruções de nível superior (Guia de
Programação em C#).

6 Colaborar conosco no .NET feedback


GitHub The .NET documentation is open
A fonte deste conteúdo pode source. Provide feedback here.
ser encontrada no GitHub, onde
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
Índices e intervalos
Artigo • 14/11/2023

Intervalos e índices fornecem uma sintaxe sucinta para acessar elementos únicos ou
intervalos em uma sequência.

Neste tutorial, você aprenderá como:

" Use a sintaxe para intervalos em uma sequência.


" Defina implicitamente uma classe Range.
" Compreenda as decisões de design para o início e o fim de cada sequência.
" Conheça cenários para os tipos Index e Range.

Suporte a idioma para intervalos e índices


Índices e intervalos fornecem uma sintaxe sucinta para acessar elementos únicos ou
intervalos em uma sequência.

Este suporte à linguagem depende de dois tipos novos e dois operadores novos:

System.Index representa um índice em uma sequência.


O índice do operador final ^, que especifica que um índice é relativo ao final de
uma sequência.
System.Range representa um subintervalo de uma sequência.
O operador de intervalo .., que especifica o início e o fim de um intervalo como
seus operandos.

Vamos começar com as regras para índices. Considere uma matriz sequence . O índice 0
é o mesmo que sequence[0] . O índice ^0 é o mesmo que sequence[sequence.Length] . A
expressão sequence[^0] gera uma exceção, assim como sequence[sequence.Length] o
faz. Para qualquer número n , o índice ^n é o mesmo que sequence.Length - n .

C#

string[] words = [
// index from start index from end
"The", // 0 ^9
"quick", // 1 ^8
"brown", // 2 ^7
"fox", // 3 ^6
"jumps", // 4 ^5
"over", // 5 ^4
"the", // 6 ^3
"lazy", // 7 ^2
"dog" // 8 ^1
]; // 9 (or words.Length) ^0

Você pode recuperar a última palavra com o índice ^1 . Adicione o código a seguir
abaixo da inicialização:

C#

Console.WriteLine($"The last word is {words[^1]}");

Um intervalo especifica o início e o final de um intervalo. O início do intervalo é


inclusivo, mas o final do intervalo é exclusivo, o que significa que o início está incluído
no intervalo, mas o final não está incluído no intervalo. O intervalo [0..^0] representa
todo o intervalo, assim como [0..sequence.Length] representa todo o intervalo.

O código a seguir cria um subintervalo com as palavras "quick", "brown" e "fox". Ele
inclui words[1] até words[3] . O elemento words[4] não está no intervalo.

C#

string[] quickBrownFox = words[1..4];


foreach (var word in quickBrownFox)
Console.Write($"< {word} >");
Console.WriteLine();

O código a seguir retorna o intervalo com "lazy" e "dog". Ele inclui words[^2] e
words[^1] . O índice final words[^0] não está incluído. Adicione o seguinte código

também:

C#

string[] lazyDog = words[^2..^0];


foreach (var word in lazyDog)
Console.Write($"< {word} >");
Console.WriteLine();

Os exemplos a seguir criam intervalos abertos para o início, fim ou ambos:

C#

string[] allWords = words[..]; // contains "The" through "dog".


string[] firstPhrase = words[..4]; // contains "The" through "fox"
string[] lastPhrase = words[6..]; // contains "the", "lazy" and "dog"
foreach (var word in allWords)
Console.Write($"< {word} >");
Console.WriteLine();
foreach (var word in firstPhrase)
Console.Write($"< {word} >");
Console.WriteLine();
foreach (var word in lastPhrase)
Console.Write($"< {word} >");
Console.WriteLine();

Você também pode declarar intervalos ou índices como variáveis. A variável então pode
ser usada dentro dos caracteres [ e ] :

C#

Index the = ^3;


Console.WriteLine(words[the]);
Range phrase = 1..4;
string[] text = words[phrase];
foreach (var word in text)
Console.Write($"< {word} >");
Console.WriteLine();

O exemplo a seguir mostra muitos dos motivos para essas escolhas. Modifique x , y e z
para tentar combinações diferentes. Quando você testar, use valores em que x é menor
que y e y é menor que z para as combinações válidas. Adicione o seguinte código a
um novo método. Tente usar combinações diferentes:

C#

int[] numbers = [..Enumerable.Range(0, 100)];


int x = 12;
int y = 25;
int z = 36;

Console.WriteLine($"{numbers[^x]} is the same as {numbers[numbers.Length -


x]}");
Console.WriteLine($"{numbers[x..y].Length} is the same as {y - x}");

Console.WriteLine("numbers[x..y] and numbers[y..z] are consecutive and


disjoint:");
Span<int> x_y = numbers[x..y];
Span<int> y_z = numbers[y..z];
Console.WriteLine($"\tnumbers[x..y] is {x_y[0]} through {x_y[^1]},
numbers[y..z] is {y_z[0]} through {y_z[^1]}");

Console.WriteLine("numbers[x..^x] removes x elements at each end:");


Span<int> x_x = numbers[x..^x];
Console.WriteLine($"\tnumbers[x..^x] starts with {x_x[0]} and ends with
{x_x[^1]}");

Console.WriteLine("numbers[..x] means numbers[0..x] and numbers[x..] means


numbers[x..^0]");
Span<int> start_x = numbers[..x];
Span<int> zero_x = numbers[0..x];
Console.WriteLine($"\t{start_x[0]}..{start_x[^1]} is the same as
{zero_x[0]}..{zero_x[^1]}");
Span<int> z_end = numbers[z..];
Span<int> z_zero = numbers[z..^0];
Console.WriteLine($"\t{z_end[0]}..{z_end[^1]} is the same as {z_zero[0]}..
{z_zero[^1]}");

Não apenas matrizes dão suporte a índices e intervalos. Você também pode usar índices
e intervalos com cadeia de caracteres, Span<T> ou ReadOnlySpan<T>.

Conversões de expressão de operador de intervalo


implícito
Ao usar a sintaxe de expressão do operador de intervalo, o compilador converte
implicitamente os valores inicial e final em um Index e a partir deles cria uma instância
Range. O código a seguir mostra um exemplo de conversão implícita da sintaxe de
expressão do operador de intervalo e sua alternativa explícita correspondente:

C#

Range implicitRange = 3..^5;

Range explicitRange = new(


start: new Index(value: 3, fromEnd: false),
end: new Index(value: 5, fromEnd: true));

if (implicitRange.Equals(explicitRange))
{
Console.WriteLine(
$"The implicit range '{implicitRange}' equals the explicit range
'{explicitRange}'");
}
// Sample output:
// The implicit range '3..^5' equals the explicit range '3..^5'

) Importante

Conversões implícitas de Int32 para Index lançam uma


ArgumentOutOfRangeException quando o valor é negativo. Da mesma forma, o
construtor Index lança uma ArgumentOutOfRangeException quando o parâmetro
value é negativo.
Suporte de tipo para índices e intervalos
Índices e intervalos fornecem sintaxe clara e concisa para acessar um único elemento ou
um intervalo de elementos em uma sequência. Uma expressão de índice normalmente
retorna o tipo dos elementos de uma sequência. Uma expressão de intervalo
normalmente retorna o mesmo tipo de sequência que a sequência de origem.

Qualquer tipo que forneça um indexador com um parâmetro Index ou Range dá suporte
explicitamente a índices ou intervalos, respectivamente. Um indexador que usa um
único parâmetro Range pode retornar um tipo de sequência diferente, como
System.Span<T>.

) Importante

O desempenho do código usando o operador de intervalo depende do tipo do


operando da sequência.

A complexidade temporal do operador de intervalo depende do tipo de sequência.


Por exemplo, se a sequência for uma string ou uma matriz, o resultado será uma
cópia da seção especificada da entrada. Portanto, a complexidade de tempo será
O(N) (em que N é o comprimento do intervalo). Por outro lado, se for um
System.Span<T> ou um System.Memory<T>, o resultado fará referência ao
mesmo repositório de backup, o que significa que não há cópia e a operação é
O(1).

Além da complexidade de tempo, isso causa alocações e cópias extras, afetando o


desempenho. No código sensível ao desempenho, considere usar Span<T> ou
Memory<T> como o tipo de sequência, já que o operador de intervalo não aloca

para eles.

Um tipo é contável se tiver uma propriedade nomeada Length ou Count com um getter
acessível e um tipo de retorno int . Um tipo contável que não dá suporte explicitamente
a índices ou intervalos pode fornecer um suporte implícito para eles. Para obter mais
informações, consulte as seções de suporte a índice implícito e suporte a intervalo
implícito da nota de proposta de recurso. Intervalos que usam suporte de intervalo
implícito retornam o mesmo tipo de sequência da sequência de origem.

Por exemplo, os seguintes tipos .NET dão suporte a índices e intervalos: String, Span<T>
e ReadOnlySpan<T>. O List<T> suporta índices, mas não dá suporte a intervalos.

Array tem um comportamento com mais nuances. Matrizes de dimensão única dão
suporte a índices e intervalos. Matrizes multidimensionais não dão suporte a
indexadores ou intervalos. O indexador de uma matriz multidimensional tem vários
parâmetros, não um único parâmetro. Matrizes denteadas, também conhecidas como
uma matriz de matrizes, dão suporte a intervalos e indexadores. O exemplo a seguir
mostra como iterar uma subseção retangular de uma matriz denteada. É iterada a seção
no centro, excluindo-se as três primeiras e últimas linhas e as duas primeiras e últimas
colunas de cada linha selecionada:

C#

int[][] jagged =
[
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[10,11,12,13,14,15,16,17,18,19],
[20,21,22,23,24,25,26,27,28,29],
[30,31,32,33,34,35,36,37,38,39],
[40,41,42,43,44,45,46,47,48,49],
[50,51,52,53,54,55,56,57,58,59],
[60,61,62,63,64,65,66,67,68,69],
[70,71,72,73,74,75,76,77,78,79],
[80,81,82,83,84,85,86,87,88,89],
[90,91,92,93,94,95,96,97,98,99],
];

var selectedRows = jagged[3..^3];

foreach (var row in selectedRows)


{
var selectedColumns = row[2..^2];
foreach (var cell in selectedColumns)
{
Console.Write($"{cell}, ");
}
Console.WriteLine();
}

Em todos os casos, o operador de intervalo para Array aloca uma matriz para armazenar
os elementos retornados.

Cenários para intervalos e índices


Geralmente, você usará intervalos e índices quando quiser analisar uma parte de uma
sequência maior. A nova sintaxe é mais clara porque lê exatamente qual parte da
sequência está envolvida. A função local MovingAverage usa um Range como seu
argumento. O método então enumera apenas esse intervalo ao calcular o mínimo,
máximo e média. Experimente o seguinte código em seu projeto:

C#
int[] sequence = Sequence(1000);

for(int start = 0; start < sequence.Length; start += 100)


{
Range r = start..(start+10);
var (min, max, average) = MovingAverage(sequence, r);
Console.WriteLine($"From {r.Start} to {r.End}: \tMin: {min},\tMax:
{max},\tAverage: {average}");
}

for (int start = 0; start < sequence.Length; start += 100)


{
Range r = ^(start + 10)..^start;
var (min, max, average) = MovingAverage(sequence, r);
Console.WriteLine($"From {r.Start} to {r.End}: \tMin: {min},\tMax:
{max},\tAverage: {average}");
}

(int min, int max, double average) MovingAverage(int[] subSequence, Range


range) =>
(
subSequence[range].Min(),
subSequence[range].Max(),
subSequence[range].Average()
);

int[] Sequence(int count) => [..Enumerable.Range(0, count).Select(x => (int)


(Math.Sqrt(x) * 100))];

Uma observação sobre índices e matrizes de


intervalo
Ao tirar um intervalo de uma matriz, o resultado é uma matriz copiada da matriz inicial,
em vez de referenciada. Modificar valores na matriz resultante não altera os valores na
matriz inicial.

Por exemplo:

C#

var arrayOfFiveItems = new[] { 1, 2, 3, 4, 5 };

var firstThreeItems = arrayOfFiveItems[..3]; // contains 1,2,3


firstThreeItems[0] = 11; // now contains 11,2,3

Console.WriteLine(string.Join(",", firstThreeItems));
Console.WriteLine(string.Join(",", arrayOfFiveItems));

// output:
// 11,2,3
// 1,2,3,4,5

Confira também
Operadores e expressões de acesso de membro

6 Colaborar conosco no .NET feedback


GitHub The .NET documentation is open
A fonte deste conteúdo pode source. Provide feedback here.
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
Tutorial: Expressar sua intenção de
design mais claramente com tipos de
referência que permitem valor nulo e
tipos que não permitem valor nulo
Artigo • 10/05/2023

Tipos de referência que permitem valor nulo complementam os tipos de referência da


mesma maneira que os tipos de valor que permitem valor nulo complementam os tipos
de valor. Para declarar que uma variável é um tipo de referência que permite valor
nulo, anexe um ? ao tipo. Por exemplo, string? representa uma string que permite
valor nulo. Você pode usar esses novos tipos para expressar mais claramente sua
intenção de design: algumas variáveis devem sempre ter um valor , outras podem ter um
valor ausente.

Neste tutorial, você aprenderá como:

" Incorporar tipos de referência que permitem valores nulos e tipos de referência que
não permitem valores nulos aos designs
" Habilitar verificações de tipo de referência que permitem valor nulo em todo o
código.
" Gravar código em locais onde o compilador imponha essas decisões de design.
" Usar o recurso de referência que permite valor nulo em seus próprios designs

Pré-requisitos
Você precisará configurar o computador para executar o .NET, incluindo o compilador
C#. O compilador C# está disponível com o Visual Studio 2022 ou o SDK do .NET .

Este tutorial pressupõe que você esteja familiarizado com o C# e .NET, incluindo o Visual
Studio ou a CLI do .NET.

Incorporar tipos de referência que permitem


valor nulo aos designs
Neste tutorial, você criará uma biblioteca para modelar a executar uma pesquisa. O
código usa tipos de referência que permitem valores nulos e tipos de referência que não
permitem valores nulos para representar os conceitos do mundo real. As perguntas da
pesquisa nunca podem ser um valor nulo. Um entrevistado pode optar por não
responder a uma pergunta. As respostas podem ser null nesse caso.

O código que você gravará para este exemplo expressa essa intenção e o compilador a
aplica.

Criar o aplicativo e habilitar os tipos de


referência que permitem valor nulo
Crie um novo aplicativo de console no Visual Studio ou na linha de comando usando
dotnet new console . Dê o nome NullableIntroduction ao aplicativo. Depois de criar o

aplicativo, você precisará especificar que todo o projeto é compilado em um contexto


de anotação anulável habilitado. Abra o arquivo .csproj e adicione um elemento
Nullable ao elemento PropertyGroup . Defina seu valor como enable . Você precisa

aceitar o recurso de tipos de referência que permitem valor nulo em projetos


anteriores ao C# 11. Isso porque, quando o recurso é ativado, as declarações de
variáveis de referência existentes tornam-se tipos de referência que não permitem
valor nulo. Embora essa decisão auxilie na localização de problemas em que o código
existente pode não ter verificações de valores nulos adequadas, ela pode não refletir
com precisão a intenção original do design:

XML

<Nullable>enable</Nullable>

Antes do .NET 6, novos projetos não incluem o elemento Nullable . Do .NET 6 em


diante, os novos projetos incluem o elemento <Nullable>enable</Nullable> no arquivo
de projeto.

Criar os tipos para o aplicativo


Este aplicativo de pesquisa requer a criação de várias classes:

Uma classe que modela a lista de perguntas.


Uma classe que modela uma lista de pessoas contatadas para a pesquisa.
Uma classe que modela as respostas de uma pessoa que participou da pesquisa.

Esses tipos usarão os tipos de referência que permitem valor nulo e tipos de referência
que não permitem valor nulo para expressar quais membros são obrigatórios e quais
são opcionais. Os tipos de referência que permitem valor nulo informam claramente
essa intenção de design:
As perguntas que fazem parte da pesquisa nunca podem ser valores nulos: não faz
sentido fazer uma pergunta vazia.
Os entrevistados nunca poderão ser nulos. Convém controlar as pessoas
contatadas, mesmo os entrevistados que se recusaram a participar.
Qualquer resposta a uma pergunta pode ser um valor nulo. Os entrevistados
podem se recusar a responder a algumas ou a todas as perguntas.

Se já tiver programado em C#, pode estar tão acostumado a fazer referência a tipos que
permitem valores null que poderá ter perdido outras oportunidades de declarar
instâncias não anuláveis:

O conjunto de perguntas não deve permitir um valor nulo.


O conjunto de entrevistados não deve permitir um valor nulo.

Ao escrever o código, você verá que um tipo de referência não anulável como o padrão
para referências evita erros comuns que poderiam levar a NullReferenceExceptions. Uma
das lições retirada deste tutorial é que você tomou decisões sobre quais variáveis
poderiam ou não ser null . O idioma não forneceu sintaxe para expressar essas
decisões. Agora ele já fornece.

O aplicativo que você criará executa as seguintes etapas:

1. Cria uma pesquisa e adiciona perguntas a ela.


2. Cria um conjunto pseudo aleatório de entrevistados para a pesquisa.
3. Entre em contato com os entrevistados até que o tamanho da pesquisa preenchida
atinja o número da meta.
4. Grava estatísticas importantes nas respostas da pesquisa.

Criar a pesquisa com tipos de referência


anuláveis e não anuláveis
O primeiro código gravado criará a pesquisa. Você escreverá classes para modelar uma
pergunta da pesquisa e uma execução da pesquisa. A pesquisa tem três tipos de
perguntas, diferenciadas pelo formato da resposta: respostas do tipo Sim/Não,
respostas com números e respostas com texto. Crie uma classe public SurveyQuestion :

C#

namespace NullableIntroduction
{
public class SurveyQuestion
{
}
}

O compilador interpreta cada declaração de variável de tipo de referência como um tipo


de referência não anulável para o código em um contexto de anotação anulável
habilitado. Para ver seu primeiro aviso, adicione propriedades ao texto da pergunta e
tipo de pergunta, conforme mostrado no código a seguir:

C#

namespace NullableIntroduction
{
public enum QuestionType
{
YesNo,
Number,
Text
}

public class SurveyQuestion


{
public string QuestionText { get; }
public QuestionType TypeOfQuestion { get; }
}
}

Como você não inicializou QuestionText , o compilador emitirá um aviso informando


que uma propriedade que não permite valor nulo não foi inicializada. Seu design exige
que o texto da pergunta não seja um valor nulo, portanto, você inclui um construtor
para inicializá-lo e o valor QuestionType também. A definição da classe concluída se
parece com o código a seguir:

C#

namespace NullableIntroduction;

public enum QuestionType


{
YesNo,
Number,
Text
}

public class SurveyQuestion


{
public string QuestionText { get; }
public QuestionType TypeOfQuestion { get; }

public SurveyQuestion(QuestionType typeOfQuestion, string text) =>


(TypeOfQuestion, QuestionText) = (typeOfQuestion, text);
}

A adição do construtor removerá o aviso. O argumento do construtor também é um


tipo de referência que não permite valor nulo, portanto, o compilador não emite avisos.

Em seguida, crie uma classe public chamada SurveyRun . Esta classe contém uma lista
de métodos e objetos SurveyQuestion para adicionar perguntas à pesquisa, conforme
mostrado no código a seguir:

C#

using System.Collections.Generic;

namespace NullableIntroduction
{
public class SurveyRun
{
private List<SurveyQuestion> surveyQuestions = new
List<SurveyQuestion>();

public void AddQuestion(QuestionType type, string question) =>


AddQuestion(new SurveyQuestion(type, question));
public void AddQuestion(SurveyQuestion surveyQuestion) =>
surveyQuestions.Add(surveyQuestion);
}
}

Como foi feito anteriormente, você deve inicializar o objeto de lista com um valor não
nulo ou o compilador emitirá um aviso. Não há verificações de valores nulos na segunda
sobrecarga de AddQuestion , pois elas são desnecessárias: você declarou que a variável
não permite valor nulo. Seu valor não pode ser null .

Alterne para Program.cs em seu editor e substitua o conteúdo de Main pelas seguintes
linhas de código:

C#

var surveyRun = new SurveyRun();


surveyRun.AddQuestion(QuestionType.YesNo, "Has your code ever thrown a
NullReferenceException?");
surveyRun.AddQuestion(new SurveyQuestion(QuestionType.Number, "How many
times (to the nearest 100) has that happened?"));
surveyRun.AddQuestion(QuestionType.Text, "What is your favorite color?");

Como o projeto inteiro está em um contexto de anotação anulável habilitado, você


receberá avisos quando passar null para qualquer método que espera um tipo de
referência não anulável. Experimente adicionar a seguinte linha a Main :

C#

surveyRun.AddQuestion(QuestionType.Text, default);

Criar entrevistados e obter respostas para a


pesquisa
Em seguida, grave o código que gerará respostas para a pesquisa. Esse processo
envolve várias tarefas pequenas:

1. Criar um método para gerar objetos dos entrevistados. Eles representam pessoas
solicitadas a preencher a pesquisa.
2. Criar lógica para simular a realização de perguntas para um pesquisado e coletar
respostas ou perceber que um pesquisado não respondeu.
3. Repetir até que entrevistados suficientes tenham respondido à pesquisa.

Será necessária uma classe para representar uma resposta da pesquisa. Adicione-a
agora. Habilitar o suporte para tipos que permitem valor nulo. Adicione uma
propriedade Id e um construtor para inicializá-la, conforme mostrado no código a
seguir:

C#

namespace NullableIntroduction
{
public class SurveyResponse
{
public int Id { get; }

public SurveyResponse(int id) => Id = id;


}
}

Em seguida, adicione um método static para criar novos participantes ao gerar uma ID
aleatória:

C#

private static readonly Random randomGenerator = new Random();


public static SurveyResponse GetRandomId() => new
SurveyResponse(randomGenerator.Next());
A principal responsabilidade dessa classe é gerar as respostas de um participante para
as perguntas da pesquisa. Essa responsabilidade conta com algumas etapas:

1. Peça para participar da pesquisa. Se a pessoa não consentir, retorne uma resposta
de ausente (ou de valor nulo).
2. Faça as perguntas e registre a resposta. As respostas também pode ser ausentes
(ou de valor nulo).

Adicione o seguinte código à classe SurveyResponse :

C#

private Dictionary<int, string>? surveyResponses;


public bool AnswerSurvey(IEnumerable<SurveyQuestion> questions)
{
if (ConsentToSurvey())
{
surveyResponses = new Dictionary<int, string>();
int index = 0;
foreach (var question in questions)
{
var answer = GenerateAnswer(question);
if (answer != null)
{
surveyResponses.Add(index, answer);
}
index++;
}
}
return surveyResponses != null;
}

private bool ConsentToSurvey() => randomGenerator.Next(0, 2) == 1;

private string? GenerateAnswer(SurveyQuestion question)


{
switch (question.TypeOfQuestion)
{
case QuestionType.YesNo:
int n = randomGenerator.Next(-1, 2);
return (n == -1) ? default : (n == 0) ? "No" : "Yes";
case QuestionType.Number:
n = randomGenerator.Next(-30, 101);
return (n < 0) ? default : n.ToString();
case QuestionType.Text:
default:
switch (randomGenerator.Next(0, 5))
{
case 0:
return default;
case 1:
return "Red";
case 2:
return "Green";
case 3:
return "Blue";
}
return "Red. No, Green. Wait.. Blue... AAARGGGGGHHH!";
}
}

O armazenamento das respostas da pesquisa é um Dictionary<int, string>? ,


indicando que ele pode ser um valor nulo. Você está usando o novo recurso de idioma
para declarar sua intenção de design, tanto para o compilador quanto para qualquer
pessoa que leia seu código posteriormente. Se, em algum momento, você
desreferenciar surveyResponses sem primeiro verificar o valor de null , será gerado um
aviso do compilador. Você não receberá um aviso no método AnswerSurvey porque o
compilador pode determinar que a variável surveyResponses foi definida como um valor
não nulo acima.

O uso de null para respostas ausentes destaca um ponto importante para trabalhar
com tipos de referência anuláveis: seu objetivo não é remover todos os valores null de
seu programa. Em vez disso, sua meta é garantir que o código escrito expresse a
intenção do seu design. Os valores ausentes representam um conceito que precisa ser
expresso em seu código. O valor null é uma forma clara de expressar esses valores
ausentes. Tentar remover todos os valores null leva somente à definição de alguma
outra maneira de expressar esses valores ausentes sem null .

Em seguida, é necessário gravar o método PerformSurvey na classe SurveyRun . Adicione


o seguinte código à classe SurveyRun :

C#

private List<SurveyResponse>? respondents;


public void PerformSurvey(int numberOfRespondents)
{
int respondentsConsenting = 0;
respondents = new List<SurveyResponse>();
while (respondentsConsenting < numberOfRespondents)
{
var respondent = SurveyResponse.GetRandomId();
if (respondent.AnswerSurvey(surveyQuestions))
respondentsConsenting++;
respondents.Add(respondent);
}
}
Aqui, novamente, sua opção por uma List<SurveyResponse>? que permite valor nulo
indica que a resposta pode ser um valor nulo. Isso indica que a pesquisa ainda não foi
entregue a nenhum pesquisado. Observe que os entrevistados são adicionados até que
um suficiente de pessoas tiver consentido.

A última etapa para executar a pesquisa é adicionar uma chamada para executar a
pesquisa no final do método Main :

C#

surveyRun.PerformSurvey(50);

Examinar as respostas da pesquisa


A última etapa é exibir os resultados da pesquisa. Você adicionará código a várias
classes gravadas. Este código demonstra o valor da distinção dos tipos de referência
que permitem valor nulo e tipos de referência que não permitem valor nulo. Comece
adicionando os dois membros com corpo de expressão à classe SurveyResponse :

C#

public bool AnsweredSurvey => surveyResponses != null;


public string Answer(int index) => surveyResponses?.GetValueOrDefault(index)
?? "No answer";

Como surveyResponses é um tipo de referência anulável, verificações de nulo são


necessárias antes de desreferenciá-la. O método Answer retorna uma cadeia de
caracteres não anulável, portanto, precisamos cobrir o caso de ausência de resposta
usando o operador de avaliação de nulo.

Em seguida, adicione esses três membros com corpo de expressão à classe SurveyRun :

C#

public IEnumerable<SurveyResponse> AllParticipants => (respondents ??


Enumerable.Empty<SurveyResponse>());
public ICollection<SurveyQuestion> Questions => surveyQuestions;
public SurveyQuestion GetQuestion(int index) => surveyQuestions[index];

O membro AllParticipants deve levar em conta que a variável respondents pode ser
um valor nulo, mas o valor de retorno não pode ser nulo. Se você alterar essa expressão
removendo ?? e a sequência vazia que se segue, o compilador avisará que o método
poderá retornar null e sua assinatura de retorno retornará um tipo que não permite
valor nulo.

Por fim, adicione o seguinte loop à parte inferior do método Main :

C#

foreach (var participant in surveyRun.AllParticipants)


{
Console.WriteLine($"Participant: {participant.Id}:");
if (participant.AnsweredSurvey)
{
for (int i = 0; i < surveyRun.Questions.Count; i++)
{
var answer = participant.Answer(i);
Console.WriteLine($"\t{surveyRun.GetQuestion(i).QuestionText} :
{answer}");
}
}
else
{
Console.WriteLine("\tNo responses");
}
}

Você não precisa de verificações de null neste código porque criou as interfaces
subjacentes para que todas elas retornem tipos de referência que não permitem valor
nulo.

Obter o código
Obtenha o código do tutorial concluído em nosso repositório de amostras na pasta
csharp/NullableIntroduction .

Experimente alterar as declarações de tipo entre os tipos de referência que permitem


valor nulo e tipos de referência que não permitem valor nulo. Veja como isso gera
avisos diferentes para garantir que um null não será acidentalmente cancelado.

Próximas etapas
Saiba como usar o tipo de referência anulável ao trabalhar com o Entity Framework:

Conceitos Básicos do Entity Framework Core: trabalhando com tipos de referência


anuláveis
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.
Usar interpolação de cadeia de
caracteres para construir cadeia de
caracteres formatadas
Artigo • 10/05/2023

Este tutorial ensina como usar a interpolação de cadeias de caracteres em C# para


inserir valores em uma única cadeia de caracteres de resultado. 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 mostram como inserir valores em uma cadeia de caracteres e formatar esses valores
de diferentes maneiras.

Este tutorial espera que você tenha um computador que possa usar para
desenvolvimento. O tutorial Olá, Mundo em 10 minutos tem instruções para
configurar o ambiente de desenvolvimento local no Windows, no Linux ou no macOS.
Também é possível concluir a versão interativa deste tutorial em seu navegador.

Criar uma cadeia de caracteres interpolada


Crie um diretório chamado interpolated. Faça com que esse seja o diretório atual e
execute o seguinte comando em uma janela do console:

CLI do .NET

dotnet new console

Esse comando cria um novo aplicativo de console .NET Core no diretório atual.

Abra Program.cs em seu editor favorito e substitua a linha Console.WriteLine("Hello


World!"); pelo seguinte código, em que você substitui <name> pelo seu nome:

C#

var name = "<name>";


Console.WriteLine($"Hello, {name}. It's a pleasure to meet you!");

Experimente este código digitando dotnet run na sua janela do console. Ao executar o
programa, ele exibe uma única cadeia de caracteres que inclui seu nome na saudação. A
cadeia de caracteres incluída na chamada de método WriteLine é uma expressão de
cadeia de caracteres interpolada. Ela é um tipo de modelo que permite que você
construa uma única cadeia de caracteres (chamado de cadeia de caracteres de resultado)
com base em uma cadeia de caracteres que inclui o código inserido. As cadeias de
caracteres interpoladas são particularmente úteis para inserir valores em uma cadeia de
caracteres ou para concatenar (unir) cadeias de caracteres.

Esse exemplo simples contém os dois elementos que toda cadeia de caracteres
interpolada deve ter:

Um literal de cadeia de caracteres que começa com o caractere $ antes do


caractere de aspas de abertura. Não pode haver nenhum espaço entre o símbolo
$ e o caractere de aspas. (Se você quiser ver o que acontece ao incluir um espaço,
insira um após o caractere $ , salve o arquivo e execute novamente o programa,
digitando dotnet run na janela do console. O compilador C# exibe uma
mensagem de erro: "erro CS1056: caractere inesperado '$'".)

Uma ou mais expressões de interpolação. Uma expressão de interpolação é


indicada por chaves de abertura e fechamento ( { e } ). Você pode colocar
qualquer expressão de C# que retorne um valor (incluindo null ) dentro das
chaves.

Vamos testar mais alguns exemplos de interpolação de cadeias de caracteres com


outros tipos de dados.

Incluir diferentes tipos de dados


Na seção anterior, você usou a interpolação de cadeias de caracteres para inserir uma
cadeia de caracteres dentro de outra. Entretanto, o resultado de uma expressão de
interpolação pode ser de qualquer tipo de dados. Vamos incluir valores de vários tipos
de dados em uma cadeia de caracteres interpolada.

No exemplo a seguir, primeiramente definimos um tipo de dados de classe Vegetable


que tem uma propriedade Name e um método ToString , que substitui o comportamento
do método Object.ToString(). O modificador de acesso public disponibiliza esse método
para qualquer código de cliente para obter a representação de cadeia de caracteres de
uma instância de Vegetable . No exemplo, o método Vegetable.ToString retorna o valor
da propriedade Name que é inicializada no construtor Vegetable :

C#

public Vegetable(string name) => Name = name;


Em seguida, criamos uma instância da classe Vegetable chamada item usando o new
operador e fornecendo um nome para o construtor Vegetable :

C#

var item = new Vegetable("eggplant");

Por fim, incluímos a variável item em uma cadeia de caracteres interpolada que
também contém um valor DateTime, um valor Decimal e um valor de enumeração Unit
valor. Substitua todo o código C# em seu editor pelo seguinte código e, depois, use o
comando dotnet run para executá-lo:

C#

using System;

public class Vegetable


{
public Vegetable(string name) => Name = name;

public string Name { get; }

public override string ToString() => Name;


}

public class Program


{
public enum Unit { item, kilogram, gram, dozen };

public static void Main()


{
var item = new Vegetable("eggplant");
var date = DateTime.Now;
var price = 1.99m;
var unit = Unit.item;
Console.WriteLine($"On {date}, the price of {item} was {price} per
{unit}.");
}
}

Observe que a expressão de interpolação item na cadeia de caracteres interpolada é


resolvida como o texto "eggplant" na cadeia de caracteres de resultado. Isso ocorre
porque, quando o tipo do resultado da expressão não é uma cadeia de caracteres, o
resultado é resolvido como uma cadeia de caracteres da seguinte maneira:

Se a expressão de interpolação for avaliada como null , uma cadeia de caracteres


vazia ("" ou String.Empty) será usada.
Se a expressão de interpolação não foi avaliada como null , normalmente o
método ToString do tipo de resultado será chamado. Você pode testar isso
atualizando a implementação do método Vegetable.ToString . Talvez nem seja
necessário implementar o método ToString , pois cada tipo tem algum modo de
implementação desse método. Para testar isso, comente a definição do método
Vegetable.ToString no exemplo (para isso, coloque o símbolo de comentário //

na frente dele). Na saída, a cadeia de caracteres "eggplant" é substituída pelo


nome do tipo totalmente qualificado, ("Vegetable" neste exemplo), que é o
comportamento padrão do método Object.ToString(). O comportamento padrão
do método ToString para um valor de enumeração é retornar a representação de
cadeia de caracteres do valor.

Na saída deste exemplo, a data é muito precisa (o preço de "eggplant" não muda a cada
segundo) e o valor do preço não indica uma unidade monetária. Na próxima seção,
você aprenderá como corrigir esses problemas controlando o formato das
representações das cadeias de caracteres dos resultados de expressão.

Controlar a formatação de expressões de


interpolação
Na seção anterior, duas cadeias de caracteres formatadas de maneira inadequada foram
inseridas na cadeia de caracteres de resultado. Uma era um valor de data e hora para a
qual apenas a data era adequada. A segunda era um preço que não indicava a unidade
monetária. Os dois problemas são fáceis de se resolver. A interpolação de cadeias de
caracteres permite especificar cadeias de caracteres de formato que controlam a
formatação de tipos específicos. Modifique a chamada a Console.WriteLine no exemplo
anterior para incluir as cadeias de caracteres de formato para as expressões de data e de
preço, conforme mostrado na linha a seguir:

C#

Console.WriteLine($"On {date:d}, the price of {item} was {price:C2} per


{unit}.");

Você especifica uma cadeia de caracteres de formato colocando dois-pontos (":") e a


cadeia de caracteres de formato após a expressão de interpolação. "d" é uma cadeia de
caracteres de formato de data e hora padrão que representa o formato de data
abreviada. "C2" é um cadeia de caracteres de formato numérico padrão que representa
um número como um valor de moeda com dois dígitos após o ponto decimal.
Diversos tipos nas bibliotecas do .NET são compatíveis com um conjunto predefinido de
cadeias de caracteres de formato. Isso inclui todos os tipos numéricos e os tipos de data
e hora. Para obter uma lista completa dos tipos que são compatíveis com as cadeias de
caracteres de formato, consulte Cadeias de caracteres de formato e tipos da biblioteca
de classes do .NET no artigo Tipos de formatação no .NET.

Tente modificar as cadeias de caracteres de formato em seu editor de texto e, sempre


que fizer uma alteração, execute novamente o programa para ver como as alterações
afetam a formatação da data e hora e do valor numérico. Altere o "d" em {date:d} para
"t" (para exibir o formato de hora abreviada), para "y" (para exibir o ano e o mês) e para
"yyyy" (para exibir o ano como um número de quatro dígitos). Altere o "C2" em
{price:C2} para "e" (para obter notação exponencial) e para "F3" (para um valor

numérico com três dígitos após o ponto decimal).

Além de controlar a formatação, você também pode controlar a largura do campo e o


alinhamento das cadeias de caracteres formatadas incluídas na cadeia de caracteres de
resultado. Na próxima seção, você aprenderá como fazer isso.

Controlar a largura do campo e o alinhamento


de expressões de interpolação
Normalmente, quando o resultado de uma expressão de interpolação é formatado em
uma cadeia de caracteres, essa cadeia de caracteres é incluída em uma cadeia de
caracteres sem espaços à esquerda nem à direita. Especialmente quando você trabalha
com um conjunto de dados, poder controlar a largura do campo e o alinhamento do
texto ajuda a produzir uma saída mais legível. Para ver isso, substitua todo o código em
seu editor de texto pelo código a seguir e, em seguida, digite dotnet run para executar
o programa:

C#

using System;
using System.Collections.Generic;

public class Example


{
public static void Main()
{
var titles = new Dictionary<string, string>()
{
["Doyle, Arthur Conan"] = "Hound of the Baskervilles, The",
["London, Jack"] = "Call of the Wild, The",
["Shakespeare, William"] = "Tempest, The"
};
Console.WriteLine("Author and Title List");
Console.WriteLine();
Console.WriteLine($"|{"Author",-25}|{"Title",30}|");
foreach (var title in titles)
Console.WriteLine($"|{title.Key,-25}|{title.Value,30}|");
}
}

Os nomes de autores são alinhados à esquerda e os títulos que eles escreveram são
alinhados à direita. Você especifica o alinhamento adicionando uma vírgula (",") após a
expressão de interpolação e designando a largura mínima do campo. Se o valor
especificado for um número positivo, o campo será alinhado à direita. Se for um
número negativo, o campo será alinhado à esquerda.

Tente remover os sinais negativos do código {"Author",-25} e {title.Key,-25} e


execute o exemplo novamente, como feito no código a seguir:

C#

Console.WriteLine($"|{"Author",25}|{"Title",30}|");
foreach (var title in titles)
Console.WriteLine($"|{title.Key,25}|{title.Value,30}|");

Desta vez, as informações sobre o autor são alinhadas à direita.

Você pode combinar um especificador de alinhamento e uma cadeia de caracteres de


formato em uma única expressão de interpolação. Para fazer isso, especifique o
alinhamento primeiro, seguido por dois-pontos e pela cadeia de caracteres de formato.
Substitua todo o código dentro do método Main pelo código a seguir, que exibe três
cadeias de caracteres formatadas com larguras de campo definidas. Em seguida,
execute o programa inserindo o comando dotnet run .

C#

Console.WriteLine($"[{DateTime.Now,-20:d}] Hour [{DateTime.Now,-10:HH}]


[{1063.342,15:N2}] feet");

A saída é semelhante ao seguinte:

Console

[04/14/2018 ] Hour [16 ] [ 1,063.34] feet

Você concluiu o tutorial de interpolação de cadeias de caracteres.


Para obter mais informações, confira o tópico Interpolação de cadeia de caracteres e o
tutorial Interpolação de cadeia de caracteres no C#.
Interpolação de cadeias de caracteres
em C#
Artigo • 03/09/2023

Este tutorial mostra como usar a interpolação de cadeia de caracteres para formatar e
incluir resultados de expressão em uma cadeia de caracteres de resultado. Os exemplos
pressupõem que você esteja familiarizado com os conceitos básicos do C# e a
formatação de tipos do .NET. Se você não estiver familiarizado com a interpolação de
cadeia de caracteres ou com a formatação de tipos do .NET, confira primeiro o tutorial
interativo sobre a interpolação de cadeia de caracteres. Para obter mais informações
sobre tipos de formatação no .NET, confira Tipos de formatação no .NET.

Introdução
Para identificar uma literal de cadeia de caracteres como uma cadeia de caracteres
interpolada, preceda-o com o símbolo $ . Você pode inserir qualquer expressão C#
válida que retorna um valor em uma cadeia de caracteres interpolada. No seguinte
exemplo, assim que uma expressão é avaliada, o resultado é convertido em uma cadeia
de caracteres e incluído em uma cadeia de caracteres de resultado:

C#

double a = 3;
double b = 4;
Console.WriteLine($"Area of the right triangle with legs of {a} and {b} is
{0.5 * a * b}");
Console.WriteLine($"Length of the hypotenuse of the right triangle with legs
of {a} and {b} is {CalculateHypotenuse(a, b)}");
double CalculateHypotenuse(double leg1, double leg2) => Math.Sqrt(leg1 *
leg1 + leg2 * leg2);
// Output:
// Area of the right triangle with legs of 3 and 4 is 6
// Length of the hypotenuse of the right triangle with legs of 3 and 4 is 5

Como mostra o exemplo, você inclui uma expressão em uma cadeia de caracteres
interpolada colocando-a com chaves:

C#

{<interpolationExpression>}
Cadeia de caracteres interpoladas são compatíveis com todos os recursos do recurso
formatação composta de cadeia de caracteres. Isso as torna uma alternativa mais legível
ao uso do método String.Format.

Como especificar uma cadeia de caracteres de


formato para uma expressão de interpolação
Para especificar uma cadeia de caracteres de formato compatível com o tipo do
resultado de expressão, siga a expressão de interpolação com dois pontos (“:”) e a
cadeia de caracteres de formato:

C#

{<interpolationExpression>:<formatString>}

O seguinte exemplo mostra como especificar cadeias de caracteres de formato padrão e


personalizadas para expressões que produzem resultados numéricos ou de data e hora:

C#

var date = new DateTime(1731, 11, 25);


Console.WriteLine($"On {date:dddd, MMMM dd, yyyy} L. Euler introduced the
letter e to denote {Math.E:F5}.");
// Output:
// On Sunday, November 25, 1731 L. Euler introduced the letter e to denote
2.71828.

Para obter mais informações, consulte a seção Componente de cadeia de caracteres de


formato do artigo Formatação composta.

Como controlar a largura do campo e o


alinhamento da expressão de interpolação
formatada
Para especificar a largura mínima do campo e o alinhamento do resultado de expressão
formatada, siga a expressão de interpolação com uma vírgula (“,”) e a expressão de
constante:

C#
{<interpolationExpression>,<alignment>}

Se o valor alignment for positivo, o resultado da expressão formatada será alinhado à


direita; se for negativo, ele será alinhado à esquerda.

Caso precise especificar o alinhamento e uma cadeia de caracteres de formato, comece


com o componente de alinhamento:

C#

{<interpolationExpression>,<alignment>:<formatString>}

O seguinte exemplo mostra como especificar o alinhamento e usa caracteres de barra


vertical ("|") para delimitar campos de texto:

C#

const int NameAlignment = -9;


const int ValueAlignment = 7;
double a = 3;
double b = 4;
Console.WriteLine($"Three classical Pythagorean means of {a} and {b}:");
Console.WriteLine($"|{"Arithmetic",NameAlignment}|{0.5 * (a +
b),ValueAlignment:F3}|");
Console.WriteLine($"|{"Geometric",NameAlignment}|{Math.Sqrt(a *
b),ValueAlignment:F3}|");
Console.WriteLine($"|{"Harmonic",NameAlignment}|{2 / (1 / a + 1 /
b),ValueAlignment:F3}|");
// Output:
// Three classical Pythagorean means of 3 and 4:
// |Arithmetic| 3.500|
// |Geometric| 3.464|
// |Harmonic | 3.429|

Como mostra a saída de exemplo, se o tamanho do resultado da expressão formatada


exceder a largura de campo especificada, o valor alignment será ignorado.

Para obter mais informações, consulte a seção Componente de alinhamento do artigo


Formatação composta.

Como usar sequências de escape em uma


cadeia de caracteres interpolada
Cadeias de caracteres interpoladas dão suporte a todas as sequências de escape que
podem ser usadas em literais de cadeia de caracteres comuns. Para obter mais
informações, consulte Sequências de escape de cadeia de caracteres.

Para interpretar sequências de escape literalmente, use um literal de cadeia de


caracteres textual. Uma cadeia de caracteres verbatim interpolada começa com os
caracteres $ e @ . Você pode usar $ e @ em qualquer ordem: $@"..." e @$"..." são
cadeias de caracteres verbatim interpoladas válidas.

Para incluir uma chave, "{" ou "}", em uma cadeia de caracteres de resultado, use duas
chaves, "{{" ou "}}". Para obter mais informações, consulte a seção Chaves de escape do
artigo Formatação composta.

O seguinte exemplo mostra como incluir chaves em uma cadeia de caracteres de


resultado e construir uma cadeia de caracteres interpolada textual:

C#

var xs = new int[] { 1, 2, 7, 9 };


var ys = new int[] { 7, 9, 12 };
Console.WriteLine($"Find the intersection of the {{{string.Join(", ",xs)}}}
and {{{string.Join(", ",ys)}}} sets.");
// Output:
// Find the intersection of the {1, 2, 7, 9} and {7, 9, 12} sets.

var userName = "Jane";


var stringWithEscapes = $"C:\\Users\\{userName}\\Documents";
var verbatimInterpolated = $@"C:\Users\{userName}\Documents";
Console.WriteLine(stringWithEscapes);
Console.WriteLine(verbatimInterpolated);
// Output:
// C:\Users\Jane\Documents
// C:\Users\Jane\Documents

A partir do C# 11, você pode usar literais de cadeia de caracteres bruta e interpolada.

Como usar um operador condicional ternário


?: em uma expressão de interpolação
Como os dois-pontos (:) têm um significado especial em um item com uma expressão
de interpolação, para usar um operador condicional em uma expressão, coloque-a entre
parênteses, como mostra o seguinte exemplo:

C#
var rand = new Random();
for (int i = 0; i < 7; i++)
{
Console.WriteLine($"Coin flip: {(rand.NextDouble() < 0.5 ? "heads" :
"tails")}");
}

Como criar uma cadeia de caracteres de


resultado específica a uma cultura com a
interpolação de cadeia de caracteres
Por padrão, uma cadeia de caracteres interpolada usa a cultura atual definida pela
propriedade CultureInfo.CurrentCulture para todas as operações de formatação.

A partir do .NET 6, você pode usar o método String.Create(IFormatProvider,


DefaultInterpolatedStringHandler) para resolve uma cadeia de caracteres interpolada
para uma cadeia de caracteres de resultado específica da cultura, como mostra o
exemplo a seguir:

C#

var cultures = new System.Globalization.CultureInfo[]


{
System.Globalization.CultureInfo.GetCultureInfo("en-US"),
System.Globalization.CultureInfo.GetCultureInfo("en-GB"),
System.Globalization.CultureInfo.GetCultureInfo("nl-NL"),
System.Globalization.CultureInfo.InvariantCulture
};
var date = DateTime.Now;
var number = 31_415_926.536;
foreach (var culture in cultures)
{
var cultureSpecificMessage = string.Create(culture, $"{date,23}
{number,20:N3}");
Console.WriteLine($"{culture.Name,-10}{cultureSpecificMessage}");
}
// Output is similar to:
// en-US 8/27/2023 12:35:31 PM 31,415,926.536
// en-GB 27/08/2023 12:35:31 31,415,926.536
// nl-NL 27-08-2023 12:35:31 31.415.926,536
// 08/27/2023 12:35:31 31,415,926.536

Em versões anteriores do .NET, use a conversão implícita de uma cadeia de caracteres


interpolada em uma instância System.FormattableString e chame seu método
ToString(IFormatProvider) para criar uma cadeia de caracteres de resultado específica da
cultura. O seguinte exemplo mostra como fazer isso:

C#

var cultures = new System.Globalization.CultureInfo[]


{
System.Globalization.CultureInfo.GetCultureInfo("en-US"),
System.Globalization.CultureInfo.GetCultureInfo("en-GB"),
System.Globalization.CultureInfo.GetCultureInfo("nl-NL"),
System.Globalization.CultureInfo.InvariantCulture
};
var date = DateTime.Now;
var number = 31_415_926.536;
FormattableString message = $"{date,23}{number,20:N3}";
foreach (var culture in cultures)
{
var cultureSpecificMessage = message.ToString(culture);
Console.WriteLine($"{culture.Name,-10}{cultureSpecificMessage}");
}
// Output is similar to:
// en-US 8/27/2023 12:35:31 PM 31,415,926.536
// en-GB 27/08/2023 12:35:31 31,415,926.536
// nl-NL 27-08-2023 12:35:31 31.415.926,536
// 08/27/2023 12:35:31 31,415,926.536

Como mostra o exemplo, você pode usar uma instância FormattableString para gerar
várias cadeias de caracteres de resultado para várias culturas.

Como criar uma cadeia de caracteres de


resultado usando a cultura invariável
A partir do .NET 6, use o método String.Create(IFormatProvider,
DefaultInterpolatedStringHandler) para resolver uma cadeia de caracteres interpolada
em uma cadeia de caracteres de resultado para InvariantCulture, como mostra o
exemplo a seguir:

C#

string message = string.Create(CultureInfo.InvariantCulture, $"Date and time


in invariant culture: {DateTime.Now}");
Console.WriteLine(message);
// Output is similar to:
// Date and time in invariant culture: 05/17/2018 15:46:24
Em versões anteriores do .NET, juntamente com o método
FormattableString.ToString(IFormatProvider), você pode usar o método estático
FormattableString.Invariant, como mostra o exemplo a seguir:

C#

string message = FormattableString.Invariant($"Date and time in invariant


culture: {DateTime.Now}");
Console.WriteLine(message);
// Output is similar to:
// Date and time in invariant culture: 05/17/2018 15:46:24

Conclusão
Este tutorial descreve cenários comuns de uso da interpolação de cadeia de caracteres.
Para obter mais informações sobre a interpolação de cadeia de caracteres, consulte
Interpolação de cadeia de caracteres. Para obter mais informações sobre os tipos de
formatação no .NET, confira os artigos Tipos de formatação no .NET e Formatação
composta.

Confira também
String.Format
System.FormattableString
System.IFormattable
Cadeias de caracteres
Aplicativo de console
Artigo • 14/03/2023

Este tutorial ensina vários recursos em .NET e na linguagem C#. O que você aprenderá:

Noções básicas da CLI do .NET


A estrutura de um aplicativo de console C#
E/S do Console
Fundamentos das APIs de E/S de arquivo no .NET
Os fundamentos da programação assíncrona controlada por tarefas no .NET Core

Você criará um aplicativo que lê um arquivo de texto e exibe o conteúdo desse arquivo
de texto no console. A saída para o console é conduzida a fim de corresponder à leitura
em voz alta. É possível acelerar ou diminuir o ritmo pressionando as teclas "<" (menor
que) ou ">" (maior que). Execute esse aplicativo no Windows, no Linux, no macOS ou
em um contêiner do Docker.

Há vários recursos neste tutorial. Vamos criá-los individualmente.

Pré-requisitos
SDK do .NET 6 .
Um editor de código.

Criar o aplicativo
A primeira etapa é criar um novo aplicativo. Abra um prompt de comando e crie um
novo diretório para seu aplicativo. Torne ele o diretório atual. Digite o comando dotnet
new console no prompt de comando. Isso cria os arquivos iniciais de um aplicativo "Olá,

Mundo" básico.

Antes de começar as modificações, vamos executar um simples aplicativo Olá, Mundo.


Depois de criar o aplicativo, digite dotnet run no prompt de comando. Esse comando
executa o processo de restauração do pacote NuGet, cria o executável do aplicativo e o
executa.

O código do aplicativo simples Olá, Mundo está inteiro em Program.cs. Abra esse
arquivo com o seu editor de texto favorito. Substitua o código em Program.cs pelo
código a seguir:

C#
namespace TeleprompterConsole;

internal class Program


{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}

Na parte superior do arquivo, observe uma instrução namespace . Assim como em outras
linguagens orientadas a objeto que você pode ter usado, o C# usa namespaces para
organizar tipos. Este programa Olá, Mundo não é diferente. Você pode ver que o
programa está no namespace com o nome TeleprompterConsole .

Como ler e exibir o arquivo


O primeiro recurso a ser adicionado é a capacidade de ler um arquivo de texto e a
exibição de todo esse texto para um console. Primeiro, vamos adicionar um arquivo de
texto. Copie o arquivo sampleQuotes.txt do repositório do GitHub para este
exemplo no diretório de seu projeto. Isso servirá como o script de seu aplicativo. Para
obter informações sobre como baixar o aplicativo de exemplo para este tutorial,
consulte as instruções em Exemplos e Tutoriais.

Em seguida, adicione o seguinte método em sua classe Program (logo abaixo do


método Main ):

C#

static IEnumerable<string> ReadFrom(string file)


{
string? line;
using (var reader = File.OpenText(file))
{
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}

Esse método é um tipo especial de método C# chamado de Método iterador. Os


métodos de iterador retornam sequências que são avaliadas lentamente. Isso significa
que cada item na sequência é gerado conforme a solicitação do código que está
consumindo a sequência. Os métodos de iterador contêm uma ou mais instruções yield
return. O objeto retornado pelo método ReadFrom contém o código para gerar cada
item na sequência. Neste exemplo, isso envolve a leitura da próxima linha de texto do
arquivo de origem e o retorno dessa cadeia de caracteres. Toda vez que o código de
chamada solicita o próximo item da sequência, o código lê a próxima linha de texto do
arquivo e a retorna. Após a leitura completa do arquivo, a sequência indicará que não
há mais itens.

Há dois elementos da sintaxe em C# que podem ser novidade para você. A instrução
using nesse método gerencia a limpeza de recursos. A variável inicializada na instrução
using ( reader , neste exemplo) deve implementar a interface IDisposable. Essa interface
define um único método, Dispose , que deve ser chamado quando o recurso for
liberado. O compilador gera essa chamada quando a execução atingir a chave de
fechamento da instrução using . O código gerado pelo compilador garante que o
recurso seja liberado, mesmo se uma exceção for lançada do código no bloco definido
pela instrução using.

A variável reader é definida usando a palavra-chave var . var define uma variável local
de tipo implícito. Isso significa que o tipo da variável é determinado pelo tipo de tempo
de compilação do objeto atribuído à variável. Aqui, esse é o valor retornado do método
OpenText(String), que é um objeto StreamReader.

Agora, vamos preencher o código para ler o arquivo no método Main :

C#

var lines = ReadFrom("sampleQuotes.txt");


foreach (var line in lines)
{
Console.WriteLine(line);
}

Execute o programa (usando dotnet run , e você poderá ver todas as linhas impressa no
console).

Adicionar atrasos e formatar a saída


O que você possui está sendo exibido muito rápido para permitir a leitura em voz alta.
Agora você precisa adicionar os atrasos na saída. Ao começar, você criará parte do
código principal que permite o processamento assíncrono. No entanto, essas primeiras
etapas seguirão alguns antipadrões. Os antipadrões são indicados nos comentários
durante a adição do código, e o código será atualizado em etapas posteriores.
Há duas etapas nesta seção. Primeiro, você atualizará o método iterador a fim de
retornar palavras individuais em vez de linhas inteiras. Isso é feito com estas
modificações. Substitua a instrução yield return line; pelo seguinte código:

C#

var words = line.Split(' ');


foreach (var word in words)
{
yield return word + " ";
}
yield return Environment.NewLine;

Em seguida, será necessário modificar a forma como você consume as linhas do arquivo,
e adicionar um atraso depois de escrever cada palavra. Substitua a instrução
Console.WriteLine(line) no método Main pelo seguinte bloco:

C#

Console.Write(line);
if (!string.IsNullOrWhiteSpace(line))
{
var pause = Task.Delay(200);
// Synchronously waiting on a task is an
// anti-pattern. This will get fixed in later
// steps.
pause.Wait();
}

Execute o exemplo e verifique a saída. Agora, cada palavra única é impressa, seguida
por um atraso de 200 ms. No entanto, a saída exibida mostra alguns problemas, pois o
arquivo de texto de origem contém várias linhas com mais de 80 caracteres sem uma
quebra de linha. Isso pode ser difícil de ler durante a rolagem da tela. Mas também é
fácil de corrigir. Apenas mantenha o controle do comprimento de cada linha e gere uma
nova linha sempre que o comprimento atingir um certo limite. Declare uma variável
local após a declaração de words no método ReadFrom que contém o comprimento da
linha:

C#

var lineLength = 0;

Em seguida, adicione o seguinte código após a instrução yield return word + " ";
(antes da chave de fechamento):
C#

lineLength += word.Length + 1;
if (lineLength > 70)
{
yield return Environment.NewLine;
lineLength = 0;
}

Execute o exemplo e você poderá ler em voz alta de acordo com o ritmo pré-
configurado.

Tarefas assíncronas
Nesta etapa final, você adicionará o código para gravar a saída de forma assíncrona em
uma tarefa, enquanto executa também outra tarefa para ler a entrada do usuário, caso
ele queira acelerar ou diminuir o ritmo da exibição do texto ou interromper a exibição
do texto por completo. Essa etapa tem alguns passos e, no final, você terá todas as
atualizações necessárias. A primeira etapa é criar um método de retorno Task assíncrono
que representa o código que você criou até agora para ler e exibir o arquivo.

Adicione este método à sua classe Program (ele é obtido do corpo de seu método
Main ):

C#

private static async Task ShowTeleprompter()


{
var words = ReadFrom("sampleQuotes.txt");
foreach (var word in words)
{
Console.Write(word);
if (!string.IsNullOrWhiteSpace(word))
{
await Task.Delay(200);
}
}
}

Você observará duas alterações. Primeiro, no corpo do método, em vez de chamar


Wait() para aguardar de forma síncrona a conclusão de uma tarefa, essa versão usa a
palavra-chave await . Para fazer isso, você precisa adicionar o modificador async à
assinatura do método. Esse método retorna Task . Observe que não há instruções return
que retornam um objeto Task . Em vez disso, esse objeto Task é criado pelo código
gerado pelo compilador quando você usa o operador await . Você pode imaginar que
esse método retorna quando atinge um await . A Task retornada indica que o trabalho
não foi concluído. O método será retomado quando a tarefa em espera for concluída.
Após a execução completa, a Task retornada indicará a conclusão. O código de
chamada pode monitorar essa Task retornada para determinar quando ela foi
concluída.

Adicione uma palavra-chave await antes da chamada para ShowTeleprompter :

C#

await ShowTeleprompter();

Isso exige que você altere a assinatura do método Main para:

C#

static async Task Main(string[] args)

Saiba mais sobre o método async Main em nossa seção de conceitos básicos.

Em seguida, é necessário escrever o segundo método assíncrono a ser lido no Console e


ficar atento às teclas "<" (menor que), ">" (maior que) e "X" ou "x". Este é o método que
você adiciona à tarefa:

C#

private static async Task GetInput()


{
var delay = 200;
Action work = () =>
{
do {
var key = Console.ReadKey(true);
if (key.KeyChar == '>')
{
delay -= 10;
}
else if (key.KeyChar == '<')
{
delay += 10;
}
else if (key.KeyChar == 'X' || key.KeyChar == 'x')
{
break;
}
} while (true);
};
await Task.Run(work);
}

Isso cria uma expressão lambda para representar um delegado Action que lê uma chave
no Console e modifica uma variável local que representa o atraso quando o usuário
pressiona as teclas "<" (menor que) ou ">" (maior que). O método delegado termina
quando o usuário pressiona a tecla "X" ou "x", que permitem ao usuário interromper a
exibição de texto a qualquer momento. Esse método usa ReadKey() para bloquear e
aguardar até que o usuário pressione uma tecla.

Para concluir esse recurso, você precisa criar um novo método de retorno async Task
que inicia essas duas tarefas ( GetInput e ShowTeleprompter ) e também gerencia os
dados compartilhados entre essas tarefas.

É hora de criar uma classe que pode manipular os dados compartilhados entre essas
duas tarefas. Essa classe contém duas propriedades públicas: o atraso e um sinalizador
Done para indicar que o arquivo foi lido completamente:

C#

namespace TeleprompterConsole;

internal class TelePrompterConfig


{
public int DelayInMilliseconds { get; private set; } = 200;
public void UpdateDelay(int increment) // negative to speed up
{
var newDelay = Min(DelayInMilliseconds + increment, 1000);
newDelay = Max(newDelay, 20);
DelayInMilliseconds = newDelay;
}
public bool Done { get; private set; }
public void SetDone()
{
Done = true;
}
}

Coloque essa classe em um novo arquivo e inclua-a no namespace


TeleprompterConsole , conforme mostrado anteriormente. Também é necessário

adicionar uma instrução using static na parte superior do arquivo para que você possa
fazer referência aos métodos Min e Max sem os nomes de classe ou namespace
delimitadores. Uma instrução using static importa os métodos de uma classe. Isso
contrasta com a instrução using sem static , que importa todas as classes de um
namespace.
C#

using static System.Math;

Em seguida, atualize os métodos ShowTeleprompter e GetInput para usar o novo objeto


config . Escreva um método final async de retorno de Task para iniciar as duas tarefas e

sair quando a primeira tarefa for concluída:

C#

private static async Task RunTeleprompter()


{
var config = new TelePrompterConfig();
var displayTask = ShowTeleprompter(config);

var speedTask = GetInput(config);


await Task.WhenAny(displayTask, speedTask);
}

O novo método aqui é a chamada WhenAny(Task[]). Isso cria uma Task que termina
assim que qualquer uma das tarefas na lista de argumentos for concluída.

Depois, atualize os métodos ShowTeleprompter e GetInput para usar o objeto config


para o atraso:

C#

private static async Task ShowTeleprompter(TelePrompterConfig config)


{
var words = ReadFrom("sampleQuotes.txt");
foreach (var word in words)
{
Console.Write(word);
if (!string.IsNullOrWhiteSpace(word))
{
await Task.Delay(config.DelayInMilliseconds);
}
}
config.SetDone();
}

private static async Task GetInput(TelePrompterConfig config)


{
Action work = () =>
{
do {
var key = Console.ReadKey(true);
if (key.KeyChar == '>')
config.UpdateDelay(-10);
else if (key.KeyChar == '<')
config.UpdateDelay(10);
else if (key.KeyChar == 'X' || key.KeyChar == 'x')
config.SetDone();
} while (!config.Done);
};
await Task.Run(work);
}

Essa nova versão de ShowTeleprompter chama um novo método na classe


TeleprompterConfig . Agora, você precisa atualizar Main para chamar RunTeleprompter

em vez de ShowTeleprompter :

C#

await RunTeleprompter();

Conclusão
Este tutorial mostrou a você alguns recursos da linguagem C# e as bibliotecas .NET Core
relacionadas ao trabalho em aplicativos de Console. Use esse conhecimento como base
para explorar mais sobre a linguagem e sobre as classes apresentadas aqui. Você já viu
os fundamentos de E/S do Arquivo e do Console, uso com bloqueio e sem bloqueio da
programação assíncrona controlada por tarefa, um tour pela linguagem C# e como os
programas em C# são organizados, além da CLI do .NET.

Para obter mais informações sobre E/S de arquivo, consulte E/S de arquivo e de fluxo.
Para obter mais informações sobre o modelo de programação assíncrona usado neste
tutorial, consulte Programação assíncrona controlada por tarefas e Programação
assíncrona.
Tutorial: Fazer solicitações HTTP em um
aplicativo de console .NET usando C #
Artigo • 10/05/2023

Esse tutorial cria um aplicativo que emite solicitações HTTP para um serviço REST no
GitHub. O aplicativo lê informações no formato JSON e converte o JSON em objetos C#.
A conversão de objetos JSON em C# é conhecida como desserialização.

Este tutorial mostra como:

" Enviar solicitações HTTP.


" Desserializar respostas JSON.
" Configurar a desserialização com atributos.

Se preferir seguir com o exemplo final desse tutorial, você pode baixá-lo. Para obter
instruções de download, consulte Exemplos e tutoriais.

Pré-requisitos
SDK do .NET 6.0 ou posterior
Um editor de código como [Visual Studio Code (um editor de software de
código aberto e multiplataforma). Você pode executar o aplicativo de exemplo no
Windows, Linux ou macOS ou em um contêiner do Docker.

Criar o aplicativo cliente


1. Abra um prompt de comando e crie um novo diretório para seu aplicativo. Torne
ele o diretório atual.

2. Insira o seguinte comando em uma janela do console:

CLI do .NET

dotnet new console --name WebAPIClient

Esse comando cria os arquivos iniciais de um aplicativo "Hello World" básico. O


nome do projeto é "WebAPIClient".

3. Navegue até o diretório "WebAPIClient" e execute o aplicativo.

CLI do .NET
cd WebAPIClient

CLI do .NET

dotnet run

dotnet run executa dotnet restore automaticamente para restaurar as


dependências de que o aplicativo precisa. Ele também executa dotnet build se
necessário. Você deve ver a saída do aplicativo "Hello, World!" . No terminal,
pressione Ctrl + C para interromper o aplicativo.

Fazer solicitações HTTP


Esse aplicativo chama a API do GitHub para obter informações sobre os projetos no
escopo do .NET Foundation . O ponto de extremidade é
https://api.github.com/orgs/dotnet/repos . Para recuperar informações, ele faz uma
solicitação HTTP GET. O navegador também faz solicitações HTTP GET, para que você
possa colar essa URL na barra de endereços do seu navegador e ver as informações
recebidas e processadas.

Use a classe HttpClient para fazer solicitações HTTP. HttpClient dá suporte apenas a
métodos assíncronos para suas APIs de execução longa. Portanto, as etapas a seguir
criam um método assíncrono e o chamam do método Main.

1. Abra o arquivo Program.cs no diretório do projeto e substitua seu conteúdo pelo


seguinte:

C#

await ProcessRepositoriesAsync();

static async Task ProcessRepositoriesAsync(HttpClient client)


{
}

Esse código:

Substitui a instrução Console.WriteLine por uma chamada para


ProcessRepositoriesAsync , a qual usa a palavra-chave await .
Define um método ProcessRepositoriesAsync vazio.
2. Na classe Program , use um HttpClient para manipular solicitações e respostas ao
substituir o conteúdo pelo C# a seguir.

C#

using System.Net.Http.Headers;

using HttpClient client = new();


client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new
MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json"));
client.DefaultRequestHeaders.Add("User-Agent", ".NET Foundation
Repository Reporter");

await ProcessRepositoriesAsync(client);

static async Task ProcessRepositoriesAsync(HttpClient client)


{
}

Esse código:

Configura cabeçalhos HTTP de todas as solicitações:


Um cabeçalho Accept para aceitar respostas JSON
Um cabeçalho User-Agent . Esses cabeçalhos são verificados pelo código
do servidor GitHub e são necessários para recuperar informações do
GitHub.

3. No método ProcessRepositoriesAsync , chame o ponto de extremidade do GitHub


que retorna uma lista de todos os repositórios na organização do .NET Foundation:

C#

static async Task ProcessRepositoriesAsync(HttpClient client)


{
var json = await client.GetStringAsync(
"https://api.github.com/orgs/dotnet/repos");

Console.Write(json);
}

Esse código:

Aguarda a tarefa retornada do método de chamada


HttpClient.GetStringAsync(String). Esse método envia uma solicitação HTTP
GET para o URI especificado. O corpo da resposta é retornado como um
String, que está disponível quando a tarefa é concluída.
A cadeia de caracteres de resposta json é impressa no console.

4. Compile o aplicativo e execute-o.

CLI do .NET

dotnet run

Não há nenhum aviso de build porque o ProcessRepositoriesAsync agora contém


um operador await . A saída é uma longa exibição de texto JSON.

Desserializar o resultado JSON


As etapas a seguir convertem a resposta JSON em objetos C#. Use a classe
System.Text.Json.JsonSerializer para desserializar o JSON em objetos.

1. Crie um arquivo chamado Repository.cs e adicione o código a seguir:

C#

public record class Repository(string name);

O código anterior define uma classe para representar o objeto JSON retornado da
API do GitHub. Você usará essa classe para exibir uma lista de nomes de
repositório.

O JSON de um objeto de repositório contém dezenas de propriedades, mas


apenas a propriedade name será desserializada. O serializador ignora
automaticamente as propriedades JSON para as quais não há correspondência na
classe de destino. Esse recurso facilita a criação de tipos que funcionam apenas
com um subconjunto de campos em um pacote JSON grande.

A convenção C# é capitalizar a primeira letra de nomes de propriedade, mas a


propriedade name aqui começa com uma letra minúscula porque corresponde
exatamente ao que está no JSON. Posteriormente, você verá como usar nomes de
propriedade C# que não correspondem aos nomes de propriedade JSON.

2. Use o serializador para converter JSON em objetos C#. Substitua a chamada para
GetStringAsync(String) no método ProcessRepositoriesAsync pelas duas linhas a
seguir:
C#

await using Stream stream =


await
client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos");
var repositories =
await JsonSerializer.DeserializeAsync<List<Repository>>(stream);

O código atualizado substitui GetStringAsync(String) por GetStreamAsync(String).


Esse método de serializador usa um fluxo, em vez de uma cadeia de caracteres,
como sua fonte.

O primeiro argumento JsonSerializer.DeserializeAsync<TValue>(Stream,


JsonSerializerOptions, CancellationToken) é uma expressão await . Expressões
await podem aparecer em quase todo lugar em seu código, apesar de que até o

momento, você apenas as viu como parte de uma instrução de atribuição. Os


outros dois parâmetros JsonSerializerOptions e CancellationToken , são opcionais
e são omitidos no snippet de código.

O método DeserializeAsync é genérico, o que significa que você fornece


argumentos de tipo para que tipo de objetos devem ser criados a partir do texto
JSON. Neste exemplo, você está desserializando para um List<Repository> , que é
outro objeto genérico, um System.Collections.Generic.List<T>. A classe List<T>
armazena uma coleção de objetos. O argumento type declara o tipo de objetos
armazenados no List<T> . O argumento de tipo é seu registro Repository , pois o
texto JSON representa uma coleção de objetos de repositório.

3. Adicione código para exibir o nome de cada repositório. Substitua as linhas que
mostram:

C#

Console.Write(json);

pelo código a seguir:

C#

foreach (var repo in repositories ?? Enumerable.Empty<Repository>())


Console.Write(repo.name);

4. As seguintes diretivas using devem estar presentes na parte superior do arquivo:


C#

using System.Net.Http.Headers;
using System.Text.Json;

5. Execute o aplicativo.

CLI do .NET

dotnet run

A saída é uma lista dos nomes dos repositórios que fazem parte do .NET
Foundation.

Configurar a desserialização
1. Em Repository.cs, substitua o conteúdo do arquivo pelo C# a seguir.

C#

using System.Text.Json.Serialization;

public record class Repository(


[property: JsonPropertyName("name")] string Name);

Esse código:

Altera o nome da propriedade name para Name .


Adiciona o JsonPropertyNameAttribute para especificar como essa
propriedade aparece no JSON.

2. Em Program.cs, atualize o código para usar a nova capitalização da propriedade


Name :

C#

foreach (var repo in repositories)


Console.Write(repo.Name);

3. Execute o aplicativo.

A saída é a mesma.
Refatorar o código
O método ProcessRepositoriesAsync pode fazer o trabalho assíncrono e retornar uma
coleção de repositórios. Altere esse método para retornar Task<List<Repository>> e
mova o código que grava no console perto do chamador.

1. Altere a assinatura de ProcessRepositoriesAsync para retornar uma tarefa cujo


resultado é uma lista de objetos Repository :

C#

static async Task<List<Repository>> ProcessRepositoriesAsync()

2. Retorne os repositórios depois de processar a resposta JSON:

C#

await using Stream stream =


await
client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos");
var repositories =
await JsonSerializer.DeserializeAsync<List<Repository>>(stream);
return repositories ?? new();

O compilador gera o objeto Task<T> para o calor de retorno porque você marcou
esse método como async .

3. Modifique o arquivo Program.cs, substituindo a chamada para


ProcessRepositoriesAsync pelo seguinte para capturar os resultados e gravar cada
nome do repositório no console.

C#

var repositories = await ProcessRepositoriesAsync(client);

foreach (var repo in repositories)


Console.Write(repo.Name);

4. Execute o aplicativo.

A saída é a mesma.

Desserializar mais propriedades


As etapas a seguir adicionam código para processar mais das propriedades no pacote
JSON recebido. Você provavelmente não vai querer processar todas as propriedades,
mas adicionar mais algumas demonstra outros recursos de C#.

1. Substitua o conteúdo da classe Repository pela seguinte definição record :

C#

using System.Text.Json.Serialization;

public record class Repository(


[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("description")] string Description,
[property: JsonPropertyName("html_url")] Uri GitHubHomeUrl,
[property: JsonPropertyName("homepage")] Uri Homepage,
[property: JsonPropertyName("watchers")] int Watchers);

Os tipos Uri e int têm funcionalidade interna para converter de e para a


representação de cadeia de caracteres. Nenhum código extra é necessário para
desserializar do formato de cadeia de caracteres JSON para esses tipos de destino.
Se o pacote JSON contiver dados que não são convertidos em um tipo de destino,
a ação de serialização gerará uma exceção.

2. Atualize o loop foreach no arquivo Program.cs para exibir os valores da


propriedade:

C#

foreach (var repo in repositories)


{
Console.WriteLine($"Name: {repo.Name}");
Console.WriteLine($"Homepage: {repo.Homepage}");
Console.WriteLine($"GitHub: {repo.GitHubHomeUrl}");
Console.WriteLine($"Description: {repo.Description}");
Console.WriteLine($"Watchers: {repo.Watchers:#,0}");
Console.WriteLine();
}

3. Execute o aplicativo.

A lista agora inclui as propriedades adicionais.

Adicionar uma propriedade date


A data da última operação de push é formatada dessa forma na resposta JSON:
JSON

2016-02-08T21:27:00Z

Esse formato é para UTC (Tempo Universal Coordenado), portanto, o resultado da


desserialização é um valor DateTime cuja propriedade Kind é Utc.

Para obter uma data e hora representadas em seu fuso horário, você precisa escrever
um método de conversão personalizado.

1. Em Repository.cs, adicione uma propriedade para a representação UTC da data e


hora e uma propriedade LastPush somente leitura que retorna a data convertida
em hora local. O arquivo deve se parecer com o seguinte:

C#

using System.Text.Json.Serialization;

public record class Repository(


[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("description")] string Description,
[property: JsonPropertyName("html_url")] Uri GitHubHomeUrl,
[property: JsonPropertyName("homepage")] Uri Homepage,
[property: JsonPropertyName("watchers")] int Watchers,
[property: JsonPropertyName("pushed_at")] DateTime LastPushUtc)
{
public DateTime LastPush => LastPushUtc.ToLocalTime();
}

A propriedade LastPush é definida usando um membro com corpo de expressão


para o acessador get . Não há acessador set . Omitir o acessador set é uma
maneira de definir uma propriedade somente leitura em C#. (Sim, você pode criar
propriedades somente gravação em C#, mas o valor delas é limitado.)

2. Adicione outra instrução de saída em Program.cs novamente:

C#

Console.WriteLine($"Last push: {repo.LastPush}");

3. O aplicativo completo deve ser semelhante ao seguinte arquivo Program.cs:

C#

using System.Net.Http.Headers;
using System.Text.Json;
using HttpClient client = new();
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new
MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json"));
client.DefaultRequestHeaders.Add("User-Agent", ".NET Foundation
Repository Reporter");

var repositories = await ProcessRepositoriesAsync(client);

foreach (var repo in repositories)


{
Console.WriteLine($"Name: {repo.Name}");
Console.WriteLine($"Homepage: {repo.Homepage}");
Console.WriteLine($"GitHub: {repo.GitHubHomeUrl}");
Console.WriteLine($"Description: {repo.Description}");
Console.WriteLine($"Watchers: {repo.Watchers:#,0}");
Console.WriteLine($"{repo.LastPush}");
Console.WriteLine();
}

static async Task<List<Repository>> ProcessRepositoriesAsync(HttpClient


client)
{
await using Stream stream =
await
client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos");
var repositories =
await JsonSerializer.DeserializeAsync<List<Repository>>
(stream);
return repositories ?? new();
}

4. Execute o aplicativo.

A saída inclui a data e a hora do último push de cada repositório.

Próximas etapas
Neste tutorial, você criou um aplicativo que faz solicitações da Web e analisa os
resultados. Agora, sua versão do aplicativo deve corresponder ao exemplo finalizado.

Saiba mais sobre como configurar a serialização JSON em Como serializar e desserializar
(marshal and unmarshal) JSON no .NET.
Trabalhar com a LINQ (Consulta
Integrada à Linguagem)
Artigo • 10/05/2023

Introdução
Este tutorial ensina os recursos no .NET Core e da linguagem C#. Você aprenderá a:

Gerar sequências com a LINQ.


Escrever métodos que podem ser usados com facilidade em consultas LINQ.
Distinguir entre avaliação lenta e detalhada.

Você aprenderá essas técnicas ao compilar um aplicativo que demonstra uma das
habilidades básicas de qualquer mágico: o embaralhamento faro . Em resumo, um
embaralhamento faro é uma técnica em que você divide um baralho de cartas
exatamente na metade, então as cartas de cada metade são colocadas em ordem
aleatória até recriar o conjunto original.

Os mágicos usam essa técnica porque cada carta é fica em um local conhecido após o
embaralhamento e a ordem é um padrão de repetição.

Para os seus propósitos, vamos examinar rapidamente as sequências de manipulação de


dados. O aplicativo que você cria constrói um baralho de cartas e executa uma
sequência de embaralhamento, sempre gravando a sequência de saída. Você também
comparará a ordem atualizada com a ordem original.

Este tutorial tem várias etapas. Após cada etapa, você poderá executar o aplicativo e ver
o progresso. Você também poderá ver o exemplo concluído no repositório
dotnet/samples do GitHub. Para obter instruções de download, consulte Exemplos e
tutoriais.

Pré-requisitos
Você precisará configurar seu computador para executar o .NET Core. Você vai encontrar
as instruções de instalação na página de download do .NET Core . Você pode executar
esse aplicativo no Windows, no Ubuntu Linux, no OS X ou em um contêiner do Docker.
Será necessário instalar o editor de código de sua preferência. As descrições a seguir
usam o Visual Studio Code , que é um Editor de código aberto multiplataforma. No
entanto, você pode usar quaisquer ferramentas que esteja familiarizado.
Criar o aplicativo
A primeira etapa é criar um novo aplicativo. Abra um prompt de comando e crie um
novo diretório para seu aplicativo. Torne ele o diretório atual. Digite o comando dotnet
new console no prompt de comando. Isso cria os arquivos iniciais de um aplicativo "Olá,
Mundo" básico.

Se você nunca usou C# antes, este tutorial explicará a estrutura de um programa C#.
Você pode ler e, em seguida, voltar aqui para saber mais sobre o LINQ.

Criar o conjunto de dados


Antes de começar, verifique se as linhas a seguir estão na parte superior do arquivo
Program.cs gerado pelo dotnet new console :

C#

// Program.cs
using System;
using System.Collections.Generic;
using System.Linq;

Se essas três linhas (instruções using ) não estiverem na parte superior do arquivo,
nosso programa não será compilado.

Agora que você tem todas as referências necessárias, considere o que forma um baralho
de cartas. Um baralho de cartas costuma ter quatro naipes, e cada naipe tem treze
valores. Normalmente, talvez você pense em criar uma classe Card logo de cara e
preencher uma coleção de objetos Card manualmente. Com o LINQ, dá para ser mais
conciso do que a forma comum de criação de um baralho de cartas. Em vez de criar
uma classe Card , você pode criar duas sequências para representar naipes e valores,
respectivamente. Você vai criar um par muito simples de métodos iteradores que gerará
as valores e naipes como IEnumerable<T>s de cadeias de caracteres:

C#

// Program.cs
// The Main() method

static IEnumerable<string> Suits()


{
yield return "clubs";
yield return "diamonds";
yield return "hearts";
yield return "spades";
}

static IEnumerable<string> Ranks()


{
yield return "two";
yield return "three";
yield return "four";
yield return "five";
yield return "six";
yield return "seven";
yield return "eight";
yield return "nine";
yield return "ten";
yield return "jack";
yield return "queen";
yield return "king";
yield return "ace";
}

Coloque-as sob o método Main em seu arquivo Program.cs . Esses dois métodos
utilizam a sintaxe yield return para produzir uma sequência à medida que eles são
executados. O compilador compila um objeto que implementa IEnumerable<T> e gera
a sequência de cadeias de caracteres conforme solicitado.

Agora, use esses métodos iteradores para criar o baralho de cartas. Você colocará a
consulta do LINQ em nosso método Main . Dê uma olhada:

C#

// Program.cs
static void Main(string[] args)
{
var startingDeck = from s in Suits()
from r in Ranks()
select new { Suit = s, Rank = r };

// Display each card that we've generated and placed in startingDeck in


the console
foreach (var card in startingDeck)
{
Console.WriteLine(card);
}
}

As várias cláusulas from produzem um SelectMany, que cria uma única sequência da
combinação entre cada elemento na primeira sequência com cada elemento na
segunda sequência. A ordem é importante para nossos objetivos. O primeiro elemento
na primeira sequência de fonte (Naipes) é combinado com cada elemento na segunda
sequência (Valores). Isso produz todas as treze cartas do primeiro naipe. Esse processo é
repetido com cada elemento na primeira sequência (naipes). O resultado final é um
baralho ordenado por naipes, seguido pelos valores.

É importante lembrar que se você optar por escrever seu LINQ na sintaxe de consulta
usada acima, ou se decidir usar a sintaxe de método, sempre será possível alternar entre
as formas de sintaxe. A consulta acima escrita em sintaxe de consulta pode ser escrita na
sintaxe de método como:

C#

var startingDeck = Suits().SelectMany(suit => Ranks().Select(rank => new {


Suit = suit, Rank = rank }));

O compilador traduz instruções LINQ escritas com a sintaxe de consulta na sintaxe de


chamada do método equivalente. Portanto, independentemente de sua escolha de
sintaxe, as duas versões da consulta produzem o mesmo resultado. Escolha qual sintaxe
funciona melhor para a sua situação: por exemplo, se você estiver trabalhando em uma
equipe em que alguns dos membros têm dificuldade com a sintaxe de método, prefira
usar a sintaxe de consulta.

Vá em frente e execute o exemplo que você criou neste momento. Ele exibirá todas as
52 cartas do baralho. Talvez seja muito útil executar esse exemplo em um depurador
para observar como os métodos Suits() e Ranks() são executados. Você pode ver
claramente que cada cadeia de caracteres em cada sequência é gerada apenas
conforme o necessário.
Manipular a ordem
Em seguida, concentre-se em como você vai embaralhar as cartas no baralho. A
primeira etapa de qualquer embaralhada é dividir o baralho em dois. Os métodos Take e
Skip que fazem parte das APIs do LINQ fornecem esse recurso para você. Coloque-os
sob o loop foreach :

C#

// Program.cs
public static void Main(string[] args)
{
var startingDeck = from s in Suits()
from r in Ranks()
select new { Suit = s, Rank = r };

foreach (var c in startingDeck)


{
Console.WriteLine(c);
}

// 52 cards in a deck, so 52 / 2 = 26
var top = startingDeck.Take(26);
var bottom = startingDeck.Skip(26);
}

No entanto, não há método de embaralhamento na biblioteca padrão, portanto, você


precisará escrever o seu. O método de embaralhamento que você criará ilustra várias
técnicas que você usará com programas baseados em LINQ. Portanto, cada parte desse
processo será explicado nas etapas.

Para adicionar funcionalidade ao seu modo de interação com o IEnumerable<T>


recebido de volta das consultas do LINQ, precisará escrever alguns tipos especiais de
métodos chamados métodos de extensão. Em resumo, um método de extensão é um
método estático de objetivo especial que adiciona novas funcionalidades a um tipo já
existentes, sem ter que modificar o tipo original ao qual você deseja adicionar
funcionalidade.

Dê aos seus métodos de extensão uma nova casa adicionando um novo arquivo de
classe estático ao seu programa chamado Extensions.cs , depois, comece a criar o
primeiro método de extensão:

C#

// Extensions.cs
using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqFaroShuffle
{
public static class Extensions
{
public static IEnumerable<T> InterleaveSequenceWith<T>(this
IEnumerable<T> first, IEnumerable<T> second)
{
// Your implementation will go here soon enough
}
}
}

Examine a assinatura do método por um momento, principalmente os parâmetros:

C#

public static IEnumerable<T> InterleaveSequenceWith<T> (this IEnumerable<T>


first, IEnumerable<T> second)

Você pode ver a adição do modificador this no primeiro argumento para o método.
Isso significa que você chama o método como se fosse um método de membro do tipo
do primeiro argumento. Esta declaração de método também segue um idioma padrão
no qual os tipos de entrada e saídas são IEnumerable<T> . Essa prática permite que os
métodos LINQ sejam encadeados para executar consultas mais complexas.

Naturalmente, como você dividiu o baralho em metades, precisará unir essas metades.
No código, isso significa que você vai enumerar as duas sequências adquiridas por meio
de Take e Skip ao mesmo tempo, interleaving os elementos e criará uma sequência:
seu baralho não embaralhado. Escrever um método LINQ que funciona com duas
sequências exige que você compreenda como IEnumerable<T> funciona.

A interface IEnumerable<T> tem um método: GetEnumerator. O objeto retornado por


GetEnumerator tem um método para mover para o próximo elemento e uma
propriedade que recupera o elemento atual na sequência. Você usará esses dois
membros para enumerar a coleção e retornar os elementos. Esse método de
Intercalação será um método iterador, portanto, em vez de criar uma coleção e retornar
a coleção, você usará a sintaxe yield return mostrada acima.

Aqui está a implementação desse método:

C#
public static IEnumerable<T> InterleaveSequenceWith<T>
(this IEnumerable<T> first, IEnumerable<T> second)
{
var firstIter = first.GetEnumerator();
var secondIter = second.GetEnumerator();

while (firstIter.MoveNext() && secondIter.MoveNext())


{
yield return firstIter.Current;
yield return secondIter.Current;
}
}

Agora que você escreveu esse método, vá até o método Main e embaralhe uma vez:

C#

// Program.cs
public static void Main(string[] args)
{
var startingDeck = from s in Suits()
from r in Ranks()
select new { Suit = s, Rank = r };

foreach (var c in startingDeck)


{
Console.WriteLine(c);
}

var top = startingDeck.Take(26);


var bottom = startingDeck.Skip(26);
var shuffle = top.InterleaveSequenceWith(bottom);

foreach (var c in shuffle)


{
Console.WriteLine(c);
}
}

Comparações
Quantos embaralhamentos são necessários para colocar o baralho em sua ordem
original? Para descobrir, você precisará escrever um método que determina se duas
sequências são iguais. Depois de ter esse método, você precisará colocar o código de
embaralhamento em um loop e verificar quando a apresentação estiver na ordem.

Escrever um método para determinar se as duas sequências são iguais deve ser simples.
É uma estrutura semelhante para o método que você escreveu para embaralhar as
cartas. Somente desta vez, em vez de o yield return rendimento retornar cada
elemento, você comparará os elementos correspondentes de cada sequência. Quando
toda a sequência tiver sido enumerada, se os elementos corresponderem, as sequências
serão as mesmas:

C#

public static bool SequenceEquals<T>


(this IEnumerable<T> first, IEnumerable<T> second)
{
var firstIter = first.GetEnumerator();
var secondIter = second.GetEnumerator();

while ((firstIter?.MoveNext() == true) && secondIter.MoveNext())


{
if ((firstIter.Current is not null) &&
!firstIter.Current.Equals(secondIter.Current))
{
return false;
}
}

return true;
}

Isso mostra uma segunda linguagem LINQ: métodos de terminal. Eles consideram uma
sequência como entrada (ou, neste caso, duas sequências) e retornam um único valor
escalar. Ao usar métodos de terminal, eles são sempre o método final em uma cadeia de
métodos para uma consulta LINQ, por isso, o nome "terminal".

Você pode ver isso em ação ao usá-lo para determinar quando o baralho está em sua
ordem original. Coloque o código de embaralhamento dentro de um loop e pare
quando a sequência estiver em sua ordem original, aplicando o método
SequenceEquals() . Você pode ver que esse sempre será o método final em qualquer

consulta, porque ele retorna um valor único em vez de uma sequência:

C#

// Program.cs
static void Main(string[] args)
{
// Query for building the deck

// Shuffling using InterleaveSequenceWith<T>();

var times = 0;
// We can re-use the shuffle variable from earlier, or you can make a
new one
shuffle = startingDeck;
do
{
shuffle = shuffle.Take(26).InterleaveSequenceWith(shuffle.Skip(26));

foreach (var card in shuffle)


{
Console.WriteLine(card);
}
Console.WriteLine();
times++;

} while (!startingDeck.SequenceEquals(shuffle));

Console.WriteLine(times);
}

Execute o código que obtivemos até agora e observe como o baralho é reorganizado
em cada embaralhamento. Após 8 embaralhamentos (iterações do loop do-while), o
baralho retorna à configuração original que estava quando você o criou pela primeira
vez a partir da consulta LINQ inicial.

Otimizações
O exemplo que você compilou até agora executa um embaralhamento externo, no qual
as cartas superiores e inferiores permanecem as mesmas em cada execução. Vamos
fazer uma alteração: em vez disso, usaremos um embaralhamento interno, em que todas
as 52 cartas trocam de posição. Para um embaralhamento interno, intercale o baralho
para que a primeira carta da metade inferior torne-se a primeira carta do baralho. Isso
significa que a última carta na metade superior torna-se a carta inferior. Essa é uma
alteração simples em uma única linha de código. Atualize a consulta atual de
embaralhamento, alternando as posições de Take e Skip. Isso alterará a ordem das
metades superior e inferior do baralho:

C#

shuffle = shuffle.Skip(26).InterleaveSequenceWith(shuffle.Take(26));

Execute o programa novamente e você verá que leva 52 iterações para o baralho ser
reordenado. Você também começará a observar algumas degradações de desempenho
graves à medida que o programa continuar a ser executado.

Existem muitas razões para isso. Você pode abordar uma das principais causas dessa
queda de desempenho: uso ineficiente da avaliação lenta.
Em resumo, a avaliação lenta informa que a avaliação de uma instrução não será
executada até que seu valor seja necessário. Consultas LINQ são instruções avaliadas
lentamente. As sequências são geradas somente quando os elementos são solicitados.
Geralmente, esse é o principal benefício do LINQ. No entanto, em uso como esse
programa, isso causa um crescimento exponencial no tempo de execução.

Lembre-se de que geramos o baralho original usando uma consulta LINQ. Cada
embaralhamento é gerado executando três consultas LINQ no baralho anterior. Todos
eles são executados lentamente. Isso também significa que eles são executados
novamente sempre que a sequência é solicitada. Ao obter a 52ª iteração, você estará
regenerando o baralho original muitas e muitas vezes. Vamos escrever um log para
demonstrar esse comportamento. Em seguida, você poderá corrigir isso.

Em seu arquivo Extensions.cs , digite ou copie o método a seguir. Esse método de


extensão cria um novo arquivo chamado debug.log em seu diretório do projeto, e
registra qual consulta está sendo executada atualmente para o arquivo de log. Este
método de extensão pode ser anexado a qualquer consulta para marcar que a consulta
foi executada.

C#

public static IEnumerable<T> LogQuery<T>


(this IEnumerable<T> sequence, string tag)
{
// File.AppendText creates a new file if the file doesn't exist.
using (var writer = File.AppendText("debug.log"))
{
writer.WriteLine($"Executing Query {tag}");
}

return sequence;
}

Você verá um rabisco vermelho sob File , que significa que ele não existe. Ele não será
compilado, pois o compilador não sabe o que é File . Para resolver esse problema, é
preciso que você adicione a linha de código a seguir abaixo da primeira linha em
Extensions.cs :

C#

using System.IO;

Isso deve resolver o problema e o erro vermelho desaparece.

Em seguida, instrumente a definição de cada consulta com uma mensagem de log:


C#

// Program.cs
public static void Main(string[] args)
{
var startingDeck = (from s in Suits().LogQuery("Suit Generation")
from r in Ranks().LogQuery("Rank Generation")
select new { Suit = s, Rank = r
}).LogQuery("Starting Deck");

foreach (var c in startingDeck)


{
Console.WriteLine(c);
}

Console.WriteLine();
var times = 0;
var shuffle = startingDeck;

do
{
// Out shuffle
/*
shuffle = shuffle.Take(26)
.LogQuery("Top Half")
.InterleaveSequenceWith(shuffle.Skip(26)
.LogQuery("Bottom Half"))
.LogQuery("Shuffle");
*/

// In shuffle
shuffle = shuffle.Skip(26).LogQuery("Bottom Half")
.InterleaveSequenceWith(shuffle.Take(26).LogQuery("Top
Half"))
.LogQuery("Shuffle");

foreach (var c in shuffle)


{
Console.WriteLine(c);
}

times++;
Console.WriteLine(times);
} while (!startingDeck.SequenceEquals(shuffle));

Console.WriteLine(times);
}

Observe que você não precisa fazer o registro sempre que acessar uma consulta. Você
faz o registro ao criar a consulta original. O programa ainda leva muito tempo para ser
executado, mas agora você pode ver o motivo. Se você não tiver paciência para executar
o embaralhamento interno com o registro em log ativado, volte para o embaralhamento
externo. Você ainda verá os efeitos da avaliação lenta. Em uma execução, ele faz 2592
consultas, incluindo a geração de todos os valores e naipes.

Aqui, você pode melhorar o desempenho do código para reduzir o número de


execuções feitas. Uma correção simples possível é armazenar em cache os resultados da
consulta do LINQ original que constrói o baralho de cartas. Atualmente, você executa as
consultas novamente sempre que o loop do-while passa por uma iteração, construindo
novamente o baralho de cartas e o embaralhamento de novo todas as vezes. Para
armazenar em cache o baralho de cartas, aproveite os métodos LINQ ToArray e ToList;
ao anexá-los às consultas, eles executarão as mesmas ações paras quais foram
instruídos, mas agora armazenarão os resultados em uma matriz ou lista, dependendo
de qual método você optar por chamar. Anexe o método LINQ ToArray às duas
consultas e execute o programa novamente:

C#

public static void Main(string[] args)


{
IEnumerable<Suit>? suits = Suits();
IEnumerable<Rank>? ranks = Ranks();

if ((suits is null) || (ranks is null))


return;

var startingDeck = (from s in suits.LogQuery("Suit Generation")


from r in ranks.LogQuery("Value Generation")
select new { Suit = s, Rank = r })
.LogQuery("Starting Deck")
.ToArray();

foreach (var c in startingDeck)


{
Console.WriteLine(c);
}

Console.WriteLine();

var times = 0;
var shuffle = startingDeck;

do
{
/*
shuffle = shuffle.Take(26)
.LogQuery("Top Half")
.InterleaveSequenceWith(shuffle.Skip(26).LogQuery("Bottom
Half"))
.LogQuery("Shuffle")
.ToArray();
*/
shuffle = shuffle.Skip(26)
.LogQuery("Bottom Half")
.InterleaveSequenceWith(shuffle.Take(26).LogQuery("Top Half"))
.LogQuery("Shuffle")
.ToArray();

foreach (var c in shuffle)


{
Console.WriteLine(c);
}

times++;
Console.WriteLine(times);
} while (!startingDeck.SequenceEquals(shuffle));

Console.WriteLine(times);
}

Agora, o embaralhamento externo contém 30 consultas. Execute novamente com o


embaralhamento interno e você verá melhorias semelhantes: agora, executa 162
consultas.

Observe que esse exemplo é projetado para realçar os casos de uso em que a avaliação
lenta pode causar problemas de desempenho. Embora seja importante ver onde a
avaliação lenta pode afetar o desempenho do código, é igualmente importante
entender que nem todas as consultas devem ser executadas avidamente. O
desempenho incorrido sem usar ToArray ocorre porque cada nova disposição do
baralho de cartas é criada com base na disposição anterior. Usar a avaliação lenta
significa que cada nova disposição do baralho é criada do baralho original, até mesmo a
execução do código que criou o startingDeck . Isso causa uma grande quantidade de
trabalho extra.

Na prática, alguns algoritmos funcionam bem usando a avaliação detalhada, e outros


executam funcionam melhor usando a avaliação lenta. Para o uso diário, a avaliação
lenta é uma opção melhor quando a fonte de dados é um processo separado, como um
mecanismo de banco de dados. Para os bancos de dados, a avaliação lenta permite que
as consultas mais complexas executem apenas uma viagem de ida e volta para o
processo de banco de dados e de volta para o restante do seu código. O LINQ é flexível,
não importa se você optar por utilizar a avaliação lenta ou detalhada, portanto, meça
seus processos e escolha o tipo de avaliação que ofereça o melhor desempenho.

Conclusão
Neste projeto, abordamos:
o uso de consultas LINQ para agregar dados em uma sequência significativa
a produção de métodos de Extensão para adicionar nossa própria funcionalidade
personalizada a consultas LINQ
a localização de áreas em nosso código nas quais nossas consultas LINQ podem
enfrentar problemas de desempenho, como diminuição da velocidade
avaliação lenta e detalhada com relação às consultas LINQ, e as implicações que
elas podem ter no desempenho da consulta

Além do LINQ, você aprendeu um pouco sobre uma técnica usada por mágicos para
truques de carta. Os mágicos usam o embaralhamento Faro porque podem controlar
onde cada carta fica no baralho. Agora que você sabe, não conte para os outros!

Para saber mais sobre o LINQ, consulte:

LINQ (Consulta Integrada à Linguagem)


Introdução ao LINQ
Operações de consulta LINQ básica (C#)
Transformações de dados com LINQ (C#)
Sintaxe de consulta e sintaxe de método em LINQ (C#)
funcionalidades do C# que dão suporte a LINQ
LINQ (Consulta Integrada à Linguagem)
Artigo • 11/04/2024

O LINQ (consulta integrada à linguagem) é o nome de um conjunto de tecnologias com


base na integração de recursos de consulta diretamente na linguagem C#.
Tradicionalmente, consultas feitas em dados são expressas como cadeias de caracteres
simples sem verificação de tipo no tempo de compilação ou suporte a IntelliSense. Além
disso, você precisará aprender uma linguagem de consulta diferente para cada tipo de
fonte de dados: bancos de dados SQL, documentos XML, vários serviços Web etc. Com
o LINQ, uma consulta é um constructo de linguagem de primeira classe, como classes,
métodos, eventos.

Para um desenvolvedor que escreve consultas, a parte mais visível "integrada à


linguagem" do LINQ é a expressão de consulta. As expressões de consulta são uma
sintaxe declarativa de consulta. Usando a sintaxe de consulta, você pode executar
operações de filtragem, ordenação e agrupamento em fontes de dados com o mínimo
de código. Você pode usar os mesmos padrões de expressão de consulta básica para
consultar e transformar dados em bancos de dados SQL, conjuntos de dados do
ADO.NET, documentos XML e fluxos e coleções .NET.

O exemplo a seguir mostra a operação de consulta completa. A operação completa


inclui a criação de uma fonte de dados, definição da expressão de consulta e execução
da consulta em uma instrução foreach .

C#

// Specify the data source.


int[] scores = { 97, 92, 81, 60 };

// Define the query expression.


IEnumerable<int> scoreQuery =
from score in scores
where score > 80
select score;

// Execute the query.


foreach (int i in scoreQuery)
{
Console.Write(i + " ");
}

// Output: 97 92 81
Visão geral da expressão de consulta
Expressões de consulta podem ser usadas para consultar e transformar dados de
qualquer fonte de dados habilitada para LINQ. Por exemplo, uma única consulta
pode recuperar dados de um Banco de Dados SQL e produzir um fluxo XML como
saída.

As expressões de consulta são fáceis de entender porque elas usam muitos


constructos de linguagem C# familiares.

As variáveis em uma expressão de consulta são fortemente tipadas, embora em


muitos casos você não precise fornecer o tipo explicitamente, pois o compilador
pode inferir nele. Para obter mais informações, consulte Relacionamentos de tipo
em operações de consulta LINQ.

Uma consulta não é executada até que você itere sobre a variável de consulta, por
exemplo, em uma instrução foreach . Para obter mais informações, consulte
Introdução a consultas LINQ.

No tempo de compilação, as expressões de consulta são convertidas em chamadas


de método do operador de consulta padrão de acordo com as regras definidas na
especificação do C#. Qualquer consulta que pode ser expressa usando sintaxe de
consulta também pode ser expressa usando sintaxe de método. No entanto, na
maioria dos casos, a sintaxe de consulta é mais legível e concisa. Para obter mais
informações, consulte Especificação da linguagem C# e Visão geral de operadores
de consulta padrão.

Como uma regra ao escrever consultas LINQ, recomendamos que você use a
sintaxe de consulta sempre que possível e a sintaxe de método sempre que
necessário. Não há semântica ou diferença de desempenho entre as duas formas.
As expressões de consulta são geralmente mais legíveis do que as expressões
equivalentes escritas na sintaxe de método.

Algumas operações de consulta, como Count ou Max, não apresentam cláusulas


de expressão de consulta equivalentes e, portanto, devem ser expressas como
chamadas de método. A sintaxe de método pode ser combinada com a sintaxe de
consulta de várias maneiras. Para obter mais informações, consulte Sintaxe de
consulta e sintaxe de método em LINQ.

As expressões de consulta podem ser compiladas para árvores de expressão ou


delegados, dependendo do tipo ao qual a consulta é aplicada. As consultas
IEnumerable<T> são compiladas para representantes. As consultas IQueryable e
IQueryable<T> são compiladas para árvores de expressão. Para obter mais
informações, consulte Árvores de expressão.

Próximas etapas
Para obter mais detalhes sobre o LINQ, comece se familiarizando com alguns conceitos
básicos em Noções básicas sobre expressões de consulta, e, em seguida, leia a
documentação para a tecnologia LINQ na qual você está interessado:

Documentos XML: LINQ to XML

ADO.NET Entity Framework: LINQ to Entities

Coleções do .NET, arquivos, cadeias de caracteres, etc.: LINQ to Objects

Para saber mais sobre o LINQ, consulte LINQ em C#.

Para começar a trabalhar com o LINQ em C#, consulte o tutorial Trabalhando com LINQ.

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

Fornecer comentários sobre o produto


Introdução às consultas LINQ no C#
Artigo • 29/04/2024

Uma consulta é uma expressão que recupera dados de uma fonte de dados. Diferentes
fontes de dados têm diferentes linguagens de consulta nativas, por exemplo, SQL para
bancos de dados relacionais e XQuery para XML. Os desenvolvedores devem aprender
uma nova linguagem de consulta para cada tipo de fonte de dados ou formato de
dados para o qual eles devem dar suporte. O LINQ simplifica essa situação, oferecendo
um modelo de linguagem C# consistente para tipos de fontes de dados e formatos. Em
uma consulta LINQ, você sempre trabalha com objetos C#. Use os mesmos padrões
básicos de codificação para consultar e transformar dados em documentos XML, bancos
de dados SQL, coleções .NET e qualquer outro formato quando um provedor LINQ
estiver disponível.

Três Partes de uma Operação de Consulta


Todos as operações de consulta LINQ consistem em três ações distintas:

1. Obter a fonte de dados.


2. Criar a consulta.
3. Executar a consulta.

O exemplo a seguir mostra como as três partes de uma operação de consulta são
expressas em código-fonte. O exemplo usa uma matriz de inteiros como uma fonte de
dados para sua conveniência. No entanto, os mesmos conceitos também se aplicam a
outras fontes de dados. Este exemplo é referenciado em todo o restante deste artigo.

C#

// The Three Parts of a LINQ Query:


// 1. Data source.
int[] numbers = [ 0, 1, 2, 3, 4, 5, 6 ];

// 2. Query creation.
// numQuery is an IEnumerable<int>
var numQuery =
from num in numbers
where (num % 2) == 0
select num;

// 3. Query execution.
foreach (int num in numQuery)
{
Console.Write("{0,1} ", num);
}

A ilustração a seguir mostra a operação de consulta completa. No LINQ, a execução da


consulta é distinta da própria consulta. Em outras palavras, você não recupera dados
criando uma variável de consulta.

A Fonte de Dados
A fonte de dados no exemplo anterior é uma matriz, que dá suporte à interface genérica
IEnumerable<T>. Isso significa que ela pode ser consultada com o LINQ. Uma consulta
é executada em uma instrução foreach , e foreach requer IEnumerable ou
IEnumerable<T>. Tipos que dão suporte a IEnumerable<T> ou uma interface derivada,
como a genérica IQueryable<T>, são chamados tipos passíveis de consulta.

Um tipo passível de consulta não exige modificação ou tratamento especial para servir
como uma fonte de dados do LINQ. Se os dados de origem ainda não estiverem na
memória como um tipo queryable, o provedor LINQ deverá representá-los como tal. Por
exemplo, LINQ to XML carrega um documento XML em um tipo XElement passível de
consulta:

C#

// Create a data source from an XML document.


// using System.Xml.Linq;
XElement contacts = XElement.Load(@"c:\myContactList.xml");
Com o EntityFramework, você cria um mapeamento relacional de objeto entre classes
C# e seu esquema de banco de dados. Você grava suas consultas nos objetos e, em
tempo de execução, o EntityFramework manipula a comunicação com o banco de
dados. No exemplo a seguir, Customers representa uma tabela específica no banco de
dados, e o tipo do resultado da consulta, IQueryable<T>, deriva de IEnumerable<T>.

C#

Northwnd db = new Northwnd(@"c:\northwnd.mdf");

// Query for customers in London.


IQueryable<Customer> custQuery =
from cust in db.Customers
where cust.City == "London"
select cust;

Para obter mais informações sobre como criar tipos específicos de fontes de dados,
consulte a documentação para os diversos provedores LINQ. No entanto, a regra básica
é simples: uma fonte de dados LINQ é qualquer objeto que dê suporte à interface
genérica IEnumerable<T> ou uma interface que herde dela, normalmente
IQueryable<T>.

7 Observação

Tipos como ArrayList, que dão suporte à interface IEnumerable não genérica,
também podem ser usados como uma fonte de dados LINQ. Para obter mais
informações, consulte Como consultar um ArrayList com LINQ (C#).

A consulta
A consulta especifica quais informações devem ser recuperadas da fonte (ou fontes) de
dados. Opcionalmente, uma consulta também especifica como essas informações
devem ser classificadas, agrupadas e moldadas antes de serem retornadas. Uma
consulta é armazenada em uma variável de consulta e é inicializada com uma expressão
de consulta. Use a sintaxe de consulta em C# para gravar consultas.

A consulta no exemplo anterior retorna todos os números pares da matriz de inteiros. A


expressão de consulta contém três cláusulas: from , where e select . (Se você está
familiarizado com o SQL, deve ter notado que a ordenação das cláusulas é ao contrário
da ordenação no SQL.) A cláusula from especifica a fonte de dados, a cláusula where
aplica o filtro e a cláusula select especifica o tipo dos elementos retornados. Todas as
cláusulas de consulta são discutidas em detalhes nesta seção. Por enquanto, o ponto
importante é que no LINQ a variável de consulta não faz nada e não retorna nenhum
dado. Ele apenas armazena as informações necessárias para produzir os resultados
quando a consulta for executada em um momento posterior. Para obter mais
informações sobre como as consultas são construídas, confira Visão geral dos
operadores de consulta padrão (C#).

7 Observação

As consultas também podem ser expressas usando a sintaxe de método. Para obter
mais informações, consulte Sintaxe de consulta e sintaxe de método em LINQ.

Classificação de operadores de consulta padrão


por maneira de execução
As implementações dos métodos de operador de consulta padrão no LINQ to Objects
são executadas de duas maneiras principais: imediata ou adiada. Os operadores de
consulta que usam a execução adiada podem, adicionalmente, ser divididos em duas
categorias: com fluxo de dados e sem fluxo de dados.

Imediata
A execução imediata significa que a fonte de dados é lida e a operação é executada
uma vez. Todos os operadores de consulta padrão que retornam um resultado escalar
são executados imediatamente. Exemplos dessas consultas são Count , Max , Average e
First . Esses métodos são executados sem uma instrução explícita foreach porque a

consulta em si deve usar foreach para retornar um resultado. Essas consultas retornam
um único valor, não uma coleção IEnumerable . Você pode forçar a execução imediata de
qualquer consulta usando os métodos Enumerable.ToList ou Enumerable.ToArray. A
execução imediata fornece reutilização dos resultados da consulta, não uma declaração
de consulta. Os resultados são recuperados uma vez e armazenados para uso futuro. A
consulta a seguir retorna uma contagem de números pares na matriz de origem:

C#

var evenNumQuery =
from num in numbers
where (num % 2) == 0
select num;

int evenNumCount = evenNumQuery.Count();


Para forçar a execução imediata de qualquer consulta e armazenar seus resultados em
cache, você pode chamar os métodos ToList ou ToArray.

C#

List<int> numQuery2 =
(from num in numbers
where (num % 2) == 0
select num).ToList();

// or like this:
// numQuery3 is still an int[]

var numQuery3 =
(from num in numbers
where (num % 2) == 0
select num).ToArray();

Você também pode forçar a execução colocando o loop foreach imediatamente após a
expressão de consulta. No entanto, ao chamar ToList ou ToArray , você também
armazena em cache todos os dados em um único objeto de coleção.

Adiado
Uma execução adiada significa que a operação não é realizada no ponto do código em
que a consulta está declarada. A operação será realizada somente quando a variável de
consulta for enumerada, por exemplo, usando uma instrução foreach . Os resultados da
execução da consulta dependem do conteúdo da fonte de dados no momento em que
a consulta for executada, não no momento em que a consulta for definida. Se a variável
de consulta for enumerada várias vezes, os resultados poderão ser diferentes a cada vez.
Quase todos os operadores de consulta padrão cujo tipo de retorno é IEnumerable<T>
ou IOrderedEnumerable<TElement> executam de maneira adiada. A execução adiada
oferece a facilidade da reutilização da consulta, uma vez que ela busca os dados
atualizados da fonte de dados sempre que os seus resultados são iterados. O código a
seguir mostra um exemplo de execução adiada:

C#

foreach (int num in numQuery)


{
Console.Write("{0,1} ", num);
}
A instrução foreach também é o local em que os resultados da consulta são
recuperados. Por exemplo, na consulta anterior, a variável de iteração num armazena
cada valor (um de cada vez) na sequência retornada.

Como a variável de consulta em si nunca contém os resultados da consulta, você pode


executá-la repetidamente para recuperar dados atualizados. Por exemplo, um aplicativo
separado talvez atualize um banco de dados continuamente. No seu aplicativo, você
poderia criar uma consulta que recuperasse os dados mais recentes e executá-la a
intervalos determinados para recuperar os resultados atualizados.

Os operadores de consulta que usam a execução adiada podem, adicionalmente, ser


classificados como "com fluxo de dados" e "sem fluxo de dados".

Streaming
Os operadores com fluxos de dados não precisam ler todos os dados da origem antes
de gerar elementos. No momento da execução, um operador streaming realiza sua
operação em cada elemento de origem enquanto eles são lidos, gerando o elemento, se
apropriado. Um operador streaming continua a ler os elementos de origem até que um
elemento de resultado possa ser produzido. Isso significa que mais de um elemento de
origem poderá ser lido para produzir um elemento de resultado.

Sem fluxo de dados


Os operadores sem fluxo de dados precisam ler todos os dados da origem antes de
gerar um elemento resultante. Operações como classificação ou agrupamento se
enquadram nesta categoria. No momento da execução, os operadores de consulta sem
fluxo de dados leem todos os dados da origem, os colocam em uma estrutura de dados,
executam a operação e geram os elementos resultantes.

Tabela de classificação
A tabela a seguir classifica cada método de operador de consulta padrão de acordo com
o respectivo método de execução.

7 Observação

Se um operador estiver marcado em duas colunas, duas sequências de entrada


estarão envolvidas na operação e cada sequência será avaliada de forma diferente.
Nesses casos, a primeira sequência na lista de parâmetros é a que sempre será
avaliada de maneira adiada e em modo streaming.

ノ Expandir a tabela

Operador de Tipo de retorno Execução Execução Execução


consulta padrão imediata adiada de sem fluxo
streaming de dados
adiada

Aggregate TSource X

All Boolean X

Any Boolean X

AsEnumerable IEnumerable<T> X

Average Valor numérico único X

Cast IEnumerable<T> X

Concat IEnumerable<T> X

Contains Boolean X

Count Int32 X

DefaultIfEmpty IEnumerable<T> X

Distinct IEnumerable<T> X

ElementAt TSource X

ElementAtOrDefault TSource? X

Empty IEnumerable<T> X

Except IEnumerable<T> X X

First TSource X

FirstOrDefault TSource? X

GroupBy IEnumerable<T> X

GroupJoin IEnumerable<T> X X

Intersect IEnumerable<T> X X

Join IEnumerable<T> X X
Operador de Tipo de retorno Execução Execução Execução
consulta padrão imediata adiada de sem fluxo
streaming de dados
adiada

Last TSource X

LastOrDefault TSource? X

LongCount Int64 X

Max Valor numérico único, TSource X


ou TResult?

Min Valor numérico único, TSource X


ou TResult?

OfType IEnumerable<T> X

OrderBy IOrderedEnumerable<TElement> X

OrderByDescending IOrderedEnumerable<TElement> X

Range IEnumerable<T> X

Repeat IEnumerable<T> X

Reverse IEnumerable<T> X

Select IEnumerable<T> X

SelectMany IEnumerable<T> X

SequenceEqual Boolean X

Single TSource X

SingleOrDefault TSource? X

Skip IEnumerable<T> X

SkipWhile IEnumerable<T> X

Sum Valor numérico único X

Take IEnumerable<T> X

TakeWhile IEnumerable<T> X

ThenBy IOrderedEnumerable<TElement> X

ThenByDescending IOrderedEnumerable<TElement> X
Operador de Tipo de retorno Execução Execução Execução
consulta padrão imediata adiada de sem fluxo
streaming de dados
adiada

ToArray Matriz TSource[] X

ToDictionary Dictionary<TKey,TValue> X

ToList IList<T> X

ToLookup ILookup<TKey,TElement> X

Union IEnumerable<T> X

Where IEnumerable<T> X

LINQ to objects
"LINQ to Objects" se refere ao uso de consultas LINQ com qualquer coleta IEnumerable
ou IEnumerable<T> diretamente. Você pode usar o LINQ para consultar qualquer
coleção enumerável, ​como List<T>, Array ou Dictionary<TKey,TValue>. A coleção pode
ser definida pelo usuário ou um tipo retornado por uma API do .NET. Na abordagem da
LINQ, você escreve o código declarativo que descreve o que você deseja recuperar. O
recurso LINQ to Objects fornece uma ótima introdução à programação com o LINQ.

As consultas LINQ oferecem três vantagens principais sobre os loops foreach


tradicionais:

São mais concisas e mais legíveis, especialmente quando você filtra várias
condições.
Elas fornecem poderosos recursos de filtragem, ordenação e agrupamento com
um mínimo de código do aplicativo.
Elas podem ser movidas para outras fontes de dados com pouca ou nenhuma
modificação.

Quanto mais complexa for a operação que você quiser executar nos dados, maior será a
vantagem obtida ao usar o LINQ em vez de técnicas de iteração tradicionais.

Armazenar os resultados de uma consulta na


memória
Uma consulta é basicamente um conjunto de instruções sobre como recuperar e
organizar os dados. As consultas são executadas lentamente, conforme cada item
subsequente no resultado é solicitado. Quando você usa foreach para iterar os
resultados, os itens são retornados conforme acessado. Para avaliar uma consulta e
armazenar os resultados sem executar um loop foreach , basta chamar um dos métodos
a seguir na variável de consulta:

ToList
ToArray
ToDictionary
ToLookup

Ao armazenar os resultados da sua consulta, você deve atribuir o objeto da coleta


retornado a uma nova variável, conforme mostrado no exemplo a seguir:

C#

List<int> numbers = [1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20];

IEnumerable<int> queryFactorsOfFour =
from num in numbers
where num % 4 == 0
select num;

// Store the results in a new variable


// without executing a foreach loop.
var factorsofFourList = queryFactorsOfFour.ToList();

// Read and write from the newly created list to demonstrate that it holds
data.
Console.WriteLine(factorsofFourList[2]);
factorsofFourList[2] = 0;
Console.WriteLine(factorsofFourList[2]);

Confira também
Passo a passo: escrevendo consultas em C#
foreach, in
Palavras-chave de Consulta (LINQ)

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
Noções básicas sobre expressões de
consulta
Artigo • 06/03/2024

Este artigo apresenta os conceitos básicos relacionados a expressões de consulta em C#.

O que é uma consulta e o que ela faz?


Uma consulta é um conjunto de instruções que descreve quais dados recuperar de uma
determinada fonte de dados (ou fontes) e que forma e organização os dados
retornados devem ter. Uma consulta é diferente dos resultados que ela produz.

Em geral, os dados de origem são organizados de forma lógica como uma sequência de
elementos do mesmo tipo. Por exemplo, uma tabela de banco de dados SQL contém
uma sequência de linhas. Em um arquivo XML, há uma "sequência" de elementos XML
(embora eles estejam organizados hierarquicamente em uma estrutura de árvore). Uma
coleção na memória contém uma sequência de objetos.

Do ponto de vista do aplicativo, o tipo específico e a estrutura dos dados de origem


original não são importantes. O aplicativo sempre vê os dados de origem como uma
coleção de IEnumerable<T> ou de IQueryable<T>. Por exemplo, no LINQ to XML, os
dados de origem ficam visíveis como um IEnumerable <XElement>.

Dada essa sequência de origem, uma consulta pode executar uma das três ações:

Recuperar um subconjunto dos elementos para produzir uma nova sequência sem
modificar os elementos individuais. A consulta pode, em seguida, classificar ou
agrupar a sequência retornada de várias maneiras, conforme mostrado no
exemplo a seguir (suponha que scores é um int[] ):

C#

IEnumerable<int> highScoresQuery =
from score in scores
where score > 80
orderby score descending
select score;

Recuperar uma sequência de elementos, como no exemplo anterior, mas


transformá-los em um novo tipo de objeto. Por exemplo, uma consulta pode
recuperar apenas os nomes de família de determinados registros de clientes em
uma fonte de dados. Ou pode recuperar o registro completo e, em seguida, usá-lo
para construir outro tipo de objeto na memória ou até mesmo dados XML antes
de gerar a sequência de resultados final. O exemplo a seguir mostra uma projeção
de um int para um string . Observe o novo tipo de highScoresQuery .

C#

IEnumerable<string> highScoresQuery2 =
from score in scores
where score > 80
orderby score descending
select $"The score is {score}";

Recuperar um valor singleton sobre os dados de origem, como:

O número de elementos que correspondem a uma determinada condição.

O elemento que tem o maior ou menor valor.

O primeiro elemento que corresponde a uma condição ou a soma dos valores


específicos em um conjunto de elementos especificado. Por exemplo, a consulta
a seguir retorna o número pontuações maiores que 80 na matriz de inteiros
scores :

C#

var highScoreCount = (
from score in scores
where score > 80
select score
).Count();

No exemplo anterior, observe o uso de parênteses ao redor da expressão de


consulta antes da chamada para o método Enumerable.Count. Você também
pode usar uma nova variável para armazenar o resultado concreto.

C#

IEnumerable<int> highScoresQuery3 =
from score in scores
where score > 80
select score;

var scoreCount = highScoresQuery3.Count();


No exemplo anterior, a consulta é executada na chamada para Count , pois Count deve
iterar os resultados para determinar o número de elementos retornados por
highScoresQuery .

O que é uma expressão de consulta?


Uma expressão de consulta é uma consulta expressada na sintaxe da consulta. Uma
expressão de consulta é um constructo de linguagem de primeira classe. É exatamente
como qualquer outra expressão e pode ser usada em qualquer contexto em que uma
expressão C# seja válida. Uma expressão de consulta consiste em um conjunto de
cláusulas escritas em uma sintaxe declarativa semelhante ao SQL ou XQuery. Cada
cláusula, por sua vez, contém uma ou mais expressões C# e essas expressões podem ser
ou conter uma expressão de consulta.

Uma expressão de consulta deve começar com uma cláusula from e deve terminar com
uma cláusula select ou group. Entre a primeira cláusula from e a última cláusula select
ou group , ela pode conter uma ou mais dessas cláusulas opcionais: where, orderby, join,
let e até mesmo outras cláusulas from. Você também pode usar a palavra-chave into
para permitir que o resultado de uma cláusula join ou group sirva como a fonte para
mais cláusulas de consulta na mesma expressão de consulta.

Variável da consulta
Em LINQ, uma variável de consulta é qualquer variável que armazena uma consulta em
vez dos resultados de uma consulta. Mais especificamente, uma variável de consulta é
sempre um tipo enumerável que produzirá uma sequência de elementos quando for
iterada em uma instrução foreach ou uma chamada direta para seu método
IEnumerator.MoveNext().

7 Observação

Os exemplos nesse artigo usam a seguinte fonte de dados e dados de amostra.

C#

record City(string Name, long Population);


record Country(string Name, double Area, long Population, List<City>
Cities);
record Product(string Name, string Category);
C#

static readonly City[] cities = [


new City("Tokyo", 37_833_000),
new City("Delhi", 30_290_000),
new City("Shanghai", 27_110_000),
new City("São Paulo", 22_043_000),
new City("Mumbai", 20_412_000),
new City("Beijing", 20_384_000),
new City("Cairo", 18_772_000),
new City("Dhaka", 17_598_000),
new City("Osaka", 19_281_000),
new City("New York-Newark", 18_604_000),
new City("Karachi", 16_094_000),
new City("Chongqing", 15_872_000),
new City("Istanbul", 15_029_000),
new City("Buenos Aires", 15_024_000),
new City("Kolkata", 14_850_000),
new City("Lagos", 14_368_000),
new City("Kinshasa", 14_342_000),
new City("Manila", 13_923_000),
new City("Rio de Janeiro", 13_374_000),
new City("Tianjin", 13_215_000)
];

static readonly Country[] countries = [


new Country ("Vatican City", 0.44, 526, [new City("Vatican City",
826)]),
new Country ("Monaco", 2.02, 38_000, [new City("Monte Carlo", 38_000)]),
new Country ("Nauru", 21, 10_900, [new City("Yaren", 1_100)]),
new Country ("Tuvalu", 26, 11_600, [new City("Funafuti", 6_200)]),
new Country ("San Marino", 61, 33_900, [new City("San Marino", 4_500)]),
new Country ("Liechtenstein", 160, 38_000, [new City("Vaduz", 5_200)]),
new Country ("Marshall Islands", 181, 58_000, [new City("Majuro",
28_000)]),
new Country ("Saint Kitts & Nevis", 261, 53_000, [new City("Basseterre",
13_000)])
];

O exemplo de código a seguir mostra uma expressão de consulta simples com uma
fonte de dados, uma cláusula de filtragem, uma cláusula de ordenação e nenhuma
transformação dos elementos de origem. A cláusula select termina a consulta.

C#

// Data source.
int[] scores = [90, 71, 82, 93, 75, 82];

// Query Expression.
IEnumerable<int> scoreQuery = //query variable
from score in scores //required
where score > 80 // optional
orderby score descending // optional
select score; //must end with select or group

// Execute the query to produce the results


foreach (var testScore in scoreQuery)
{
Console.WriteLine(testScore);
}

// Output: 93 90 82 82

No exemplo anterior, scoreQuery é uma variável de consulta, o que às vezes é chamado


apenas de uma consulta. A variável de consulta não armazena nenhum dado de
resultado real, que é produzido no loop foreach . E quando a instrução foreach é
executada, os resultados da consulta não são retornados pela variável de consulta
scoreQuery . Em vez disso, eles são retornados pela variável de iteração testScore . A

variável scoreQuery pode ser iterada em um segundo loop foreach . Ela produzirá os
mesmos resultados contanto que nem ela nem a fonte de dados tenham sido
modificadas.

Uma variável de consulta pode armazenar uma consulta que é expressa na sintaxe de
consulta ou na sintaxe de método ou uma combinação das duas. Nos exemplos a
seguir, queryMajorCities e queryMajorCities2 são variáveis de consulta:

C#

City[] cities = [
new City("Tokyo", 37_833_000),
new City("Delhi", 30_290_000),
new City("Shanghai", 27_110_000),
new City("São Paulo", 22_043_000)
];

//Query syntax
IEnumerable<City> queryMajorCities =
from city in cities
where city.Population > 100000
select city;

// Execute the query to produce the results


foreach (City city in queryMajorCities)
{
Console.WriteLine(city);
}

// Output:
// City { Population = 120000 }
// City { Population = 112000 }
// City { Population = 150340 }
// Method-based syntax
IEnumerable<City> queryMajorCities2 = cities.Where(c => c.Population >
100000);

Por outro lado, os dois exemplos a seguir mostram variáveis que não são variáveis de
consulta, embora sejam inicializadas com uma consulta. Elas não são variáveis de
consulta porque armazenam resultados:

C#

var highestScore = (
from score in scores
select score
).Max();

// or split the expression


IEnumerable<int> scoreQuery =
from score in scores
select score;

var highScore = scoreQuery.Max();


// the following returns the same result
highScore = scores.Max();

C#

var largeCitiesList = (
from country in countries
from city in country.Cities
where city.Population > 10000
select city
).ToList();

// or split the expression


IEnumerable<City> largeCitiesQuery =
from country in countries
from city in country.Cities
where city.Population > 10000
select city;
var largeCitiesList2 = largeCitiesQuery.ToList();

Tipagem explícita e implícita de variáveis de consulta

Esta documentação normalmente fornece o tipo explícito da variável de consulta para


mostrar a relação de tipo entre a variável de consulta e a cláusula select. No entanto,
você também pode usar a palavra-chave var para instruir o compilador a inferir o tipo
de uma variável de consulta (ou qualquer outra variável local) em tempo de compilação.
Por exemplo, o exemplo de consulta que foi mostrado anteriormente neste artigo
também pode ser expressado usando a tipagem implícita:

C#

var queryCities =
from city in cities
where city.Population > 100000
select city;

No exemplo anterior, o uso de “var” é opcional. queryCities é um tipo


IEnumerable<City> digitado implícita ou explicitamente.

Iniciando uma expressão de consulta


Uma expressão de consulta deve começar com uma cláusula from . Especifica uma fonte
de dados junto com uma variável de intervalo. A variável de intervalo representa cada
elemento sucessivo na sequência de origem como a sequência de origem que está
sendo percorrida. A variável de intervalo é fortemente tipada com base no tipo dos
elementos na fonte de dados. No exemplo a seguir, como countries é uma matriz de
objetos Country , a variável de intervalo também é tipada como Country . Como a
variável de intervalo é fortemente tipada, você pode usar o operador ponto para acessar
todos os membros disponíveis do tipo.

C#

IEnumerable<Country> countryAreaQuery =
from country in countries
where country.Area > 500000 //sq km
select country;

A variável de intervalo está no escopo até a consulta ser encerrada com ponto e vírgula
ou com uma cláusula continuation.

Uma expressão de consulta pode conter várias cláusulas from . Use mais cláusulas from
quando cada elemento na sequência de origem for ele próprio uma coleção ou contiver
uma coleção. Por exemplo, suponha que você tem uma coleção de objetos Country e
cada um dos quais contém uma coleção de objetos City chamada Cities . Para
consular os objetos City em cada Country , use duas cláusulas from como mostrado
aqui:

C#
IEnumerable<City> cityQuery =
from country in countries
from city in country.Cities
where city.Population > 10000
select city;

Para obter mais informações, consulte Cláusula from.

Encerrando uma expressão de consulta


Uma expressão de consulta deve ser encerrada com uma cláusula group ou uma
cláusula select .

Cláusula group
Use a cláusula group para produzir uma sequência de grupos organizada por uma
chave que você especificar. A chave pode ter qualquer tipo de dados. Por exemplo, a
consulta a seguir cria uma sequência de grupos que contém um ou mais objetos
Country e cuja chave é um tipo char com o valor sendo a primeira letra dos nomes dos
países.

C#

var queryCountryGroups =
from country in countries
group country by country.Name[0];

Para obter mais informações sobre o agrupamento, consulte Cláusula group.

Cláusula select
Use a cláusula select para produzir todos os outros tipos de sequências. Uma cláusula
select simples produz apenas uma sequência do mesmo tipo dos objetos contidos na

fonte de dados. Neste exemplo, a fonte de dados contém objetos Country . A cláusula
orderby simplesmente classifica os elementos em uma nova ordem e a cláusula select
produz uma sequência dos objetos Country reordenados.

C#

IEnumerable<Country> sortedQuery =
from country in countries
orderby country.Area
select country;

A cláusula select pode ser usada para transformar dados de origem em sequências de
novos tipos. Essa transformação também é chamada de projeção. No exemplo a seguir,
a cláusula select projeta uma sequência de tipos anônimos que contém apenas um
subconjunto dos campos no elemento original. Os novos objetos são inicializados
usando um inicializador de objeto.

C#

var queryNameAndPop =
from country in countries
select new
{
Name = country.Name,
Pop = country.Population
};

Portanto, nesse exemplo, o var é obrigatório porque a consulta produz um tipo


anônimo.

Para obter mais informações sobre todas as maneiras que uma cláusula select pode ser
usada para transformar os dados de origem, consulte Cláusula select.

Continuações com into

Você pode usar a palavra-chave into em uma cláusula select ou group para criar um
identificador temporário que armazena uma consulta. Use a cláusula into quando
precisar executar operações de consulta adicionais em uma consulta após a operação
de agrupamento ou seleção. No exemplo a seguir, countries são agrupados de acordo
com a população em intervalos de 10 milhões. Depois que esses grupos são criados,
mais cláusulas filtram alguns grupos e, em seguida, classificam os grupos em ordem
crescente. Para executar essas operações extras, a continuação representada por
countryGroup é necessária.

C#

// percentileQuery is an IEnumerable<IGrouping<int, Country>>


var percentileQuery =
from country in countries
let percentile = (int)country.Population / 10_000_000
group country by percentile into countryGroup
where countryGroup.Key >= 20
orderby countryGroup.Key
select countryGroup;

// grouping is an IGrouping<int, Country>


foreach (var grouping in percentileQuery)
{
Console.WriteLine(grouping.Key);
foreach (var country in grouping)
{
Console.WriteLine(country.Name + ":" + country.Population);
}
}

Para obter mais informações, consulte into.

Filtragem, ordenação e junção


Entre a cláusula from inicial e a cláusula select ou group final, todas as outras cláusulas
( where , join , orderby , from , let ) são opcionais. Todas as cláusulas opcionais podem
ser usadas várias vezes ou nenhuma vez no corpo de uma consulta.

Cláusula where

Use a cláusula where para filtrar os elementos dos dados de origem com base em uma
ou mais expressões de predicado. A cláusula where no exemplo a seguir tem um
predicado com duas condições.

C#

IEnumerable<City> queryCityPop =
from city in cities
where city.Population is < 200000 and > 100000
select city;

Para obter mais informações, consulte Cláusula where.

Cláusula orderby
Use a cláusula orderby para classificar os resultados em ordem crescente ou
decrescente. Você também pode especificar as ordens de classificação secundárias. O
exemplo a seguir executa uma classificação primária nos objetos country usando a
propriedade Area . Em seguida, ele executa a classificação secundária usando a
propriedade Population .

C#
IEnumerable<Country> querySortedCountries =
from country in countries
orderby country.Area, country.Population descending
select country;

A palavra-chave ascending é opcional. Será a ordem de classificação padrão se


nenhuma ordem for especificada. Para obter mais informações, consulte Cláusula
orderby.

Cláusula join
Use a cláusula join para associar e/ou combinar elementos de uma fonte de dados
com elementos de outra fonte de dados com base em uma comparação de igualdade
entre as chaves especificadas em cada elemento. Na LINQ, as operações join são
executadas em sequências de objetos cujos elementos são de tipos diferentes. Após ter
unido duas sequências, você deve usar uma instrução select ou group para especificar
qual elemento armazenar na sequência de saída. Você também pode usar um tipo
anônimo para combinar propriedades de cada conjunto de elementos associados em
um novo tipo para a sequência de saída. O exemplo a seguir associa objetos prod cuja
propriedade Category corresponde a uma das categorias na matriz de cadeias de
caracteres categories . Produtos cuja Category não corresponda a nenhuma cadeia de
caracteres em categories são filtrados. A instrução select projeta um novo tipo cujas
propriedades são tiradas de cat e prod .

C#

var categoryQuery =
from cat in categories
join prod in products on cat equals prod.Category
select new
{
Category = cat,
Name = prod.Name
};

Você também pode executar uma junção de grupo armazenando os resultados da


operação join em uma variável temporária usando a palavra-chave into. Para obter
mais informações, consulte Cláusula join.

Cláusula let
Use a cláusula let para armazenar o resultado de uma expressão, como uma chamada
de método, em uma nova variável de intervalo. No exemplo a seguir, a variável de
intervalo firstName armazena o primeiro elemento da matriz de cadeias de caracteres
retornado pelo Split .

C#

string[] names = ["Svetlana Omelchenko", "Claire O'Donnell", "Sven


Mortensen", "Cesar Garcia"];
IEnumerable<string> queryFirstNames =
from name in names
let firstName = name.Split(' ')[0]
select firstName;

foreach (var s in queryFirstNames)


{
Console.Write(s + " ");
}

//Output: Svetlana Claire Sven Cesar

Para obter mais informações, consulte Cláusula let.

Subconsultas em uma expressão de consulta


Uma cláusula de consulta pode conter uma expressão de consulta, que às vezes é
chamada de subconsulta. Cada subconsulta começa com sua própria cláusula from que
não necessariamente aponta para a mesma fonte de dados na primeira cláusula from .
Por exemplo, a consulta a seguir mostra uma expressão de consulta que é usada na
instrução select para recuperar os resultados de uma operação de agrupamento.

C#

var queryGroupMax =
from student in students
group student by student.Year into studentGroup
select new
{
Level = studentGroup.Key,
HighestScore = (
from student2 in studentGroup
select student2.ExamScores.Average()
).Max()
};

Para obter mais informações, confira Executar uma subconsulta em uma operação de
agrupamento.
Confira também
Palavras-chave de consulta (LINQ)
Visão geral de operadores de consulta padrã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
Gravar consultas LINQ em C# para
consultar dados
Artigo • 02/05/2024

A maioria das consultas na documentação introdutória do LINQ (Consulta Integrada à


Linguagem) é escrita com o uso da sintaxe de consulta declarativa do LINQ. No entanto,
a sintaxe de consulta deve ser convertida em chamadas de método para o CLR
(Common Language Runtime) do .NET quando o código for compilado. Essas chamadas
de método invocam os operadores de consulta padrão, que têm nomes como Where ,
Select , GroupBy , Join , Max e Average . Você pode chamá-los diretamente usando a

sintaxe de método em vez da sintaxe de consulta.

A sintaxe de consulta e a sintaxe do método são semanticamente idênticas, mas a


sintaxe de consulta geralmente é mais simples e fácil de ler. Algumas consultas devem
ser expressadas como chamadas de método. Por exemplo, você deve usar uma
chamada de método para expressar uma consulta que recupera o número de elementos
que correspondem a uma condição especificada. Você também deve usar uma chamada
de método para uma consulta que recupera o elemento que tem o valor máximo em
uma sequência de origem. A documentação de referência para os operadores de
consulta padrão no namespace System.Linq geralmente usa a sintaxe de método. Você
deve se familiarizar com como usar a sintaxe do método em consultas e nas próprias
expressões de consulta.

Métodos de extensão do operador de consulta


padrão
O exemplo a seguir mostra uma expressão de consulta simples e a consulta
semanticamente equivalente escrita como uma consulta baseada em método.

C#

int[] numbers = [ 5, 10, 8, 3, 6, 12 ];

//Query syntax:
IEnumerable<int> numQuery1 =
from num in numbers
where num % 2 == 0
orderby num
select num;

//Method syntax:
IEnumerable<int> numQuery2 = numbers.Where(num => num % 2 == 0).OrderBy(n =>
n);

foreach (int i in numQuery1)


{
Console.Write(i + " ");
}
Console.WriteLine(System.Environment.NewLine);
foreach (int i in numQuery2)
{
Console.Write(i + " ");
}

A saída dos dois exemplos é idêntica. Você pode ver que o tipo da variável de consulta é
o mesmo em ambas as formas: IEnumerable<T>.

Para entender a consulta baseada em método, vamos examiná-la melhor. No lado


direito da expressão, observe que a cláusula where agora é expressa como um método
de instância no objeto numbers , que tem um tipo de IEnumerable<int> . Se você estiver
familiarizado com a interface genérica IEnumerable<T>, você saberá que ela não possui
um método Where . No entanto, se você invocar a lista de conclusão do IntelliSense no
IDE do Visual Studio, verá não apenas um método Where , mas muitos outros métodos,
como Select , SelectMany , Join e Orderby . Esses métodos implementam os operadores
de consulta padrão.

Embora pareça que IEnumerable<T> inclui mais métodos, ele não inclui. Os operadores
de consulta padrão são implementados como métodos de extensão. Métodos de
extensão "estendem" um tipo existente, eles podem ser chamados como se fossem
métodos de instância no tipo. Os operadores de consulta padrão estendem
IEnumerable<T> e que é por esse motivo que você pode escrever numbers.Where(...) .

Para usar métodos de extensão, você os coloca no escopo com diretivas using . Do
ponto de vista do aplicativo, um método de extensão e um método de instância normal
são iguais.
Para obter mais informações sobre os métodos de extensão, consulte Métodos de
extensão. Para obter mais informações sobre os operadores de consulta padrão,
consulte Visão geral de operadores de consulta padrão (C#). Alguns provedores LINQ,
como Entity Framework e LINQ to XML, implementam seus próprios operadores de
consulta padrão e métodos de extensão para outros tipos além de IEnumerable<T>.

Expressões lambda
No exemplo anterior, observe que a expressão condicional ( num % 2 == 0 ) é passada
como um argumento em linha para o método Enumerable.Where: Where(num => num % 2
== 0). essa expressão embutida é uma expressão lambda. É uma maneira conveniente

de escrever código que, de outra forma, teria que ser escrito de forma mais complicada.
O num à esquerda do operador é a variável de entrada, que corresponde a num na
expressão de consulta. O compilador pode inferir o tipo de num porque ele sabe que
numbers é um tipo IEnumerable<T> genérico. O corpo do lambda é igual à expressão

na sintaxe da consulta ou em qualquer outra expressão ou instrução C#. Ele pode incluir
chamadas de método e outras lógicas complexas. O valor retornado é apenas o
resultado da expressão. Determinadas consultas só podem ser expressas na sintaxe do
método e algumas delas exigem expressões lambda. As expressões lambda são uma
ferramenta poderosa e flexível em sua caixa de ferramentas LINQ.

Possibilidade de composição das consultas


No exemplo de código anterior, o método Enumerable.OrderBy é invocado usando o
operador de ponto na chamada para Where . Where produz uma sequência filtrada e, em
seguida, Orderby classifica a sequência produzida por Where . Como as consultas
retornam uma IEnumerable , você pode escrevê-las na sintaxe de método encadeando
as chamadas de método. O compilador faz essa composição quando você escreve
consultas usando a sintaxe de consulta. Como uma variável de consulta não armazena
os resultados da consulta, você pode modificá-la ou usá-la como base para uma nova
consulta a qualquer momento, mesmo depois de executá-la.

Os exemplos a seguir demonstram algumas consultas LINQ simples usando cada


abordagem listada anteriormente.

7 Observação

Essas consultas funcionam em coleções na memória simples, no entanto, a sintaxe


básica é idêntica àquela usada no LINQ to Entities e no LINQ to XML.
Exemplo – sintaxe de consulta
Você escreve a maioria das consultas com sintaxe de consulta para criar expressões de
consulta. O exemplo a seguir mostra três expressões de consulta. A primeira expressão
de consulta demonstra como filtrar ou restringir os resultados aplicando condições com
uma cláusula where . Ela retorna todos os elementos na sequência de origem cujos
valores são maiores que 7 ou menores que 3. A segunda expressão demonstra como
ordenar os resultados retornados. A terceira expressão demonstra como agrupar
resultados de acordo com uma chave. Esta consulta retorna dois grupos com base na
primeira letra da palavra.

C#

List<int> numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];

// The query variables can also be implicitly typed by using var

// Query #1.
IEnumerable<int> filteringQuery =
from num in numbers
where num is < 3 or > 7
select num;

// Query #2.
IEnumerable<int> orderingQuery =
from num in numbers
where num is < 3 or > 7
orderby num ascending
select num;

// Query #3.
string[] groupingQuery = ["carrots", "cabbage", "broccoli", "beans",
"barley"];
IEnumerable<IGrouping<char, string>> queryFoodGroups =
from item in groupingQuery
group item by item[0];

O tipo das consultas é IEnumerable<T>. Todas essas consultas poderiam ser escritas
usando var conforme mostrado no exemplo a seguir:

var query = from num in numbers...

Em cada exemplo anterior, as consultas não são realmente executadas até que você
itere sobre a variável de consulta em uma instrução foreach ou outra instrução.

Exemplo – sintaxe de método


Algumas operações de consulta devem ser expressas como uma chamada de método.
Os métodos mais comuns são aqueles que retornam valores numéricos singleton, como
Sum, Max, Min, Average, e assim por diante. Esses métodos sempre devem ser
chamados por último em qualquer consulta porque retornam um único valor e não
podem servir como a origem de uma operação de consulta extra. O exemplo a seguir
mostra uma chamada de método em uma expressão de consulta:

C#

List<int> numbers1 = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];


List<int> numbers2 = [15, 14, 11, 13, 19, 18, 16, 17, 12, 10];

// Query #4.
double average = numbers1.Average();

// Query #5.
IEnumerable<int> concatenationQuery = numbers1.Concat(numbers2);

Se o método tiver parâmetros System.Action ou System.Func<TResult>, esses


argumentos serão fornecidos na forma de uma expressão lambda, conforme mostrado
no exemplo a seguir:

C#

// Query #6.
IEnumerable<int> largeNumbersQuery = numbers2.Where(c => c > 15);

Nas consultas anteriores, somente a Consulta nº 4 é executada imediatamente, pois


retorna um único valor e não uma coleção genérica IEnumerable<T>. O método em si
usa foreach ou código semelhante para calcular seu valor.

Cada uma das consultas anteriores pode ser escrita usando digitação implícita com
`var``, conforme mostrado no exemplo a seguir:

C#

// var is used for convenience in these queries


double average = numbers1.Average();
var concatenationQuery = numbers1.Concat(numbers2);
var largeNumbersQuery = numbers2.Where(c => c > 15);

Exemplo – sintaxe mista de consulta e do


método
Este exemplo mostra como usar a sintaxe do método nos resultados de uma cláusula de
consulta. Simplesmente coloque a expressão de consulta entre parênteses e, em
seguida, aplique o operador de ponto e chame o método. No exemplo a seguir, a Query
#7 retorna uma contagem dos números cujo valor está entre 3 e 7. Em geral, no
entanto, é melhor usar uma segunda variável para armazenar o resultado da chamada
de método. Dessa forma, é menos provável que a consulta seja confundida com os
resultados da consulta.

C#

// Query #7.

// Using a query expression with method syntax


var numCount1 = (
from num in numbers1
where num is > 3 and < 7
select num
).Count();

// Better: Create a new variable to store


// the method call result
IEnumerable<int> numbersQuery =
from num in numbers1
where num is > 3 and < 7
select num;

var numCount2 = numbersQuery.Count();

Como a Query #7 retorna um único valor e não uma coleção, a consulta é executada
imediatamente.

A consulta anterior pode ser escrita usando a tipagem implícita com var , da seguinte
maneira:

C#

var numCount = (from num in numbers...

Ela pode ser escrita na sintaxe de método da seguinte maneira:

C#

var numCount = numbers.Count(n => n is > 3 and < 7);

Ela pode ser escrita usando a tipagem explícita da seguinte maneira:


C#

int numCount = numbers.Count(n => n is > 3 and < 7);

Especificar filtros de predicado dinamicamente


em tempo de execução
Em alguns casos, você não sabe até o tempo de execução quantos predicados precisa
aplicar aos elementos de origem na cláusula where . Uma maneira de especificar
dinamicamente vários filtros de predicados é usar o método Contains, conforme
mostrado no exemplo a seguir. A consulta retorna resultados diferentes com base no
valor de id quando a consulta é executada.

C#

int[] ids = [111, 114, 112];

var queryNames =
from student in students
where ids.Contains(student.ID)
select new
{
student.LastName,
student.ID
};

foreach (var name in queryNames)


{
Console.WriteLine($"{name.LastName}: {name.ID}");
}

/* Output:
Garcia: 114
O'Donnell: 112
Omelchenko: 111
*/

// Change the ids.


ids = [122, 117, 120, 115];

// The query will now return different results


foreach (var name in queryNames)
{
Console.WriteLine($"{name.LastName}: {name.ID}");
}

/* Output:
Adams: 120
Feng: 117
Garcia: 115
Tucker: 122
*/

Você pode usar instruções de fluxo de controle, como if... else ou switch , para
selecionar entre consultas alternativas predeterminadas. No exemplo a seguir,
studentQuery usará outra cláusula where se o valor do runtime de oddYear é true ou
false .

C#

void FilterByYearType(bool oddYear)


{
IEnumerable<Student> studentQuery = oddYear
? (from student in students
where student.Year is GradeLevel.FirstYear or
GradeLevel.ThirdYear
select student)
: (from student in students
where student.Year is GradeLevel.SecondYear or
GradeLevel.FourthYear
select student);
var descr = oddYear ? "odd" : "even";
Console.WriteLine($"The following students are at an {descr} year
level:");
foreach (Student name in studentQuery)
{
Console.WriteLine($"{name.LastName}: {name.ID}");
}
}

FilterByYearType(true);

/* Output:
The following students are at an odd year level:
Fakhouri: 116
Feng: 117
Garcia: 115
Mortensen: 113
Tucker: 119
Tucker: 122
*/

FilterByYearType(false);

/* Output:
The following students are at an even year level:
Adams: 120
Garcia: 114
Garcia: 118
O'Donnell: 112
Omelchenko: 111
Zabokritski: 121
*/

Manipular valores nulos em expressões de


consulta
Este exemplo mostra como tratar os possíveis valores nulos em coleções de origem.
Uma coleção de objetos, tal como uma IEnumerable<T>, pode conter elementos cujo
valor é null. Se uma coleção de origem for null ou contiver um elemento cujo valor seja
null e a consulta não lidar com valores null , uma NullReferenceException será gerada

ao executar a consulta.

Você pode escrever o código defensivamente para evitar uma exceção de referência
nula conforme mostrado no exemplo a seguir:

C#

var query1 =
from c in categories
where c != null
join p in products on c.ID equals p?.CategoryID
select new
{
Category = c.Name,
Name = p.Name
};

No exemplo anterior, a cláusula where filtra todos os elementos nulos na sequência de


categorias. Essa técnica é independente da verificação de nulos na cláusula join. A
expressão condicional com null nesse exemplo funciona porque Products.CategoryID é
do tipo int? , que é uma abreviação para Nullable<int> .

Em uma cláusula join, se apenas uma das chaves de comparação for um tipo de valor
anulável que, você pode converter a outra para um tipo de valor anulável na expressão
de consulta. No exemplo a seguir, suponha que EmployeeID é uma coluna que contém
os valores do tipo int? :

C#

var query =
from o in db.Orders
join e in db.Employees
on o.EmployeeID equals (int?)e.EmployeeID
select new { o.OrderID, e.FirstName };

Em cada um dos exemplos, a palavra-chave de consulta equals é usada. Use também


padrões correspondentes, que incluem padrões para is null e is not null . Esses
padrões não são recomendados em consultas LINQ porque os provedores de consulta
podem não interpretar a nova sintaxe C# corretamente. Um provedor de consultas é
uma biblioteca que converte expressões de consulta C# em um formato de dados
nativo, como o Entity Framework Core. Os provedores de consulta implementam a
interface System.Linq.IQueryProvider para criar fontes de dados que implementam a
interface System.Linq.IQueryable<T>.

Tratar exceções em expressões de consulta


É possível chamar qualquer método no contexto de uma expressão de consulta. Não
chame qualquer método em uma expressão de consulta que possa criar um efeito
colateral, como modificar o conteúdo da fonte de dados ou gerar uma exceção. Este
exemplo mostra como evitar exceções ao chamar métodos em uma expressão de
consulta, sem violar as diretrizes gerais sobre tratamento de exceção do .NET. Essas
diretrizes declaram que é aceitável capturar uma exceção específica quando você
entende por que ela é gerada em um determinado contexto. Para obter mais
informações, consulte Melhores práticas para exceções.

O último exemplo mostra como tratar os casos em que é necessário lançar uma exceção
durante a execução de uma consulta.

O exemplo a seguir mostra como mover o código de tratamento de exceção para fora
de uma expressão de consulta. Essa refatoração só é possível quando o método não
depende de nenhuma variável que seja local para a consulta. É mais fácil lidar com
exceções fora da expressão de consulta.

C#

// A data source that is very likely to throw an exception!


IEnumerable<int> GetData() => throw new InvalidOperationException();

// DO THIS with a datasource that might


// throw an exception.
IEnumerable<int>? dataSource = null;
try
{
dataSource = GetData();
}
catch (InvalidOperationException)
{
Console.WriteLine("Invalid operation");
}

if (dataSource is not null)


{
// If we get here, it is safe to proceed.
var query =
from i in dataSource
select i * i;

foreach (var i in query)


{
Console.WriteLine(i.ToString());
}
}

No bloco catch (InvalidOperationException) no exemplo anterior, manipule (ou não


manipule) a exceção da maneira apropriada para seu aplicativo.

Em alguns casos, a melhor resposta para uma exceção que é lançada de dentro de uma
consulta poderá ser a interrupção imediata da execução da consulta. O exemplo a
seguir mostra como tratar exceções que podem ser geradas de dentro de um corpo de
consulta. Suponha que SomeMethodThatMightThrow possa causar uma exceção que exija
que a execução da consulta seja interrompida.

O bloco try inclui o loop foreach e não a própria consulta. O loop foreach é o ponto
em que a consulta é executada. As exceções de runtime são gerada quando a consulta
for executada. Portanto, elas devem ser manipuladas no loop foreach .

C#

// Not very useful as a general purpose method.


string SomeMethodThatMightThrow(string s) =>
s[4] == 'C' ?
throw new InvalidOperationException() :
@"C:\newFolder\" + s;

// Data source.
string[] files = ["fileA.txt", "fileB.txt", "fileC.txt"];

// Demonstration query that throws.


var exceptionDemoQuery =
from file in files
let n = SomeMethodThatMightThrow(file)
select n;

try
{
foreach (var item in exceptionDemoQuery)
{
Console.WriteLine($"Processing {item}");
}
}
catch (InvalidOperationException e)
{
Console.WriteLine(e.Message);
}

/* Output:
Processing C:\newFolder\fileA.txt
Processing C:\newFolder\fileB.txt
Operation is not valid due to the current state of the object.
*/

Lembre-se de capturar qualquer exceção que você espera gerar e/ou fazer qualquer
limpeza necessária em um bloco finally .

Confira também
Passo a passo: escrevendo consultas em C#
Cláusula where
Como realizar consultas com base no estado do runtime
Nullable<T>
Tipos de valor anuláveis

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
Relacionamentos de tipo em operações
de consulta LINQ (C#)
Artigo • 22/12/2023

Para escrever consultas com eficiência, você precisa entender como os tipos de variáveis
em uma operação de consulta completa se relacionam entre si. Se você compreender
esses relacionamentos, entenderá com mais facilidade os exemplos da LINQ e as
amostras de código na documentação. Além disso, você entenderá o que ocorre
quando as variáveis são digitadas implicitamente usando var.

As operações de consulta LINQ são fortemente tipadas na fonte de dados, na própria


consulta e na execução da consulta. O tipo das variáveis na consulta deve ser
compatível com o tipo dos elementos na fonte de dados e com o tipo da variável de
iteração na instrução foreach. Essa tipagem forte garante que erros de tipo sejam
capturados em tempo de compilação, quando podem ser corrigidos antes que os
usuários os encontrem.

Para demonstrar essas relações de tipo, a maioria dos exemplos a seguir usam tipagem
explícita para todas as variáveis. O último exemplo mostra como os mesmos princípios
se aplicam mesmo quando você usa digitação implícita usando var .

Consultas que não transformam os dados de


origem
A ilustração a seguir mostra uma operação de consulta LINQ to Objects que não
executa transformações nos dados. A fonte contém uma sequência de cadeias de
caracteres e a saída da consulta também é uma sequência de cadeias de caracteres.

1. O argumento de tipo da fonte de dados determina o tipo da variável de intervalo.


2. O tipo do objeto selecionado determina o tipo da variável de consulta. Esta é uma
cadeia de caracteres name . Portanto, a variável de consulta é um
IEnumerable<string> .

3. A variável de consulta é iterada na instrução foreach . Como a variável de consulta


é uma sequência de cadeias de caracteres, a variável de iteração também é uma
cadeia de caracteres.

Consultas que transformam os dados de


origem
A ilustração a seguir mostra uma operação de consulta LINQ to SQL que executa uma
transformação simples nos dados. A consulta usa uma sequência de objetos Customer
como entrada e seleciona somente a propriedade Name no resultado. Como Name é uma
cadeia de caracteres, a consulta produz uma sequência de cadeias de caracteres como
saída.

1. O argumento de tipo da fonte de dados determina o tipo da variável de intervalo.


2. A instrução select retorna a propriedade Name em vez do objeto Customer
completo. Como Name é uma cadeia de caracteres, o argumento de tipo de
custNameQuery é string e não Customer .

3. Como custNameQuery é uma sequência de cadeias de caracteres, a variável de


iteração do loop foreach também deve ser um string .

A ilustração a seguir mostra uma transformação um pouco mais complexa. A instrução


select retorna um tipo anônimo que captura apenas dois membros do objeto

Customer original.
1. O argumento de tipo da fonte de dados sempre é o tipo da variável de intervalo
na consulta.
2. Como a instrução select produz um tipo anônimo, a variável de consulta deve ser
tipada implicitamente usando var .
3. Como o tipo da variável de consulta é implícito, a variável de iteração no loop
foreach também deve ser implícito.

Deixando o compilador inferir informações de


tipo
Embora você precise entender as relações de tipo em uma operação de consulta, você
tem a opção de permitir que o compilador fazer todo o trabalho. A palavra-chave var
pode ser usada para qualquer variável local em uma operação de consulta. A ilustração
a seguir é semelhante ao exemplo número 2 que foi discutido anteriormente. No
entanto, o compilador fornece o tipo forte para cada variável na operação de consulta.

LINQ e tipos genéricos (C#)


As consultas LINQ são baseadas em tipos genéricos. Não é necessário um
conhecimento profundo sobre os genéricos antes de começar a escrever consultas. No
entanto, convém entender dois conceitos básicos:

1. Quando você cria uma instância de uma classe de coleção genérica, como List<T>,
substitua o "T" pelo tipo dos objetos que a lista bloqueia. Por exemplo, uma lista
de cadeias de caracteres é expressa como List<string> e uma lista de objetos
Customer é expressa como List<Customer> . Uma lista genérica é fortemente tipada

e oferece muitos benefícios em coleções que armazenam seus elementos como


Object. Se tentar adicionar um Customer em uma List<string> , você obterá um
erro em tempo de compilação. É fácil usar coleções genéricas, porque você não
precisa realizar a conversão de tipo em tempo de execução.
2. A IEnumerable<T> é a interface que permite que as classes de coleção genérica
sejam enumeradas usando a instrução foreach . Classes de coleção genéricas dão
suporte a IEnumerable<T> do mesmo modo que classes de coleção não genéricas,
tais como ArrayList, dão suporte a IEnumerable.

Para obter mais informações sobre os genéricos, consulte Genéricos.

Variáveis IEnumerable<T> em consultas LINQ


As variáveis de consulta LINQ são do tipo IEnumerable<T> ou de um tipo derivado,
como IQueryable<T>. Ao se deparar com uma variável de consulta que é tipada como
IEnumerable<Customer> , significa apenas que a consulta, quando for executada,

produzirá uma sequência de zero ou mais objetos Customer .

C#

IEnumerable<Customer> customerQuery =
from cust in customers
where cust.City == "London"
select cust;

foreach (Customer customer in customerQuery)


{
Console.WriteLine($"{customer.LastName}, {customer.FirstName}");
}

Permitir que o compilador manipule as


declarações de tipo genérico
Se preferir, poderá evitar a sintaxe genérica, usando a palavra-chave var. A palavra-
chave var instrui o compilador a inferir o tipo de uma variável de consulta, examinando
a fonte de dados especificada na cláusula from . O exemplo a seguir produz o mesmo
código compilado que o exemplo anterior:

C#

var customerQuery2 =
from cust in customers
where cust.City == "London"
select cust;

foreach(var customer in customerQuery2)


{
Console.WriteLine($"{customer.LastName}, {customer.FirstName}");
}

A palavra-chave var é útil quando o tipo da variável for óbvio ou quando não é tão
importante especificar explicitamente os tipos genéricos aninhados, como aqueles que
são produzidos por consultas de grupo. É recomendável que você note que o código
poderá se tornar mais difícil de ser lido por outras pessoas, caso você use a var . Para
obter mais informações, consulte Variáveis locais de tipo implícito.

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
funcionalidades do C# que dão suporte
a LINQ
Artigo • 29/04/2024

Expressões de consulta
As expressões de consulta usam uma sintaxe declarativa semelhante a SQL ou XQuery
para consultar coleções System.Collections.Generic.IEnumerable<T>. No tempo de
compilação, a sintaxe de consulta é convertida em chamadas de método para a
implementação de um provedor LINQ dos métodos de consulta padrão. Os aplicativos
controlam os operadores de consulta padrão que estão no escopo, especificando o
namespace apropriado com uma diretiva using. A expressão de consulta a seguir pega
uma matriz de cadeias de caracteres, agrupa-os de acordo com o primeiro caractere da
cadeia de caracteres e ordena os grupos.

C#

var query = from str in stringArray


group str by str[0] into stringGroup
orderby stringGroup.Key
select stringGroup;

Variáveis tipadas implicitamente (var)


Você pode usar o modificador var para instruir o compilador a inferir e atribuir o tipo,
conforme mostrado aqui:

C#

var number = 5;
var name = "Virginia";
var query = from str in stringArray
where str[0] == 'm'
select str;

Variáveis declaradas como var são fortemente tipadas, assim como variáveis cujo tipo
você especifica explicitamente. O uso de var possibilita a criação de tipos anônimos,
mas apenas para variáveis locais. Para obter mais informações, consulte Variáveis locais
de tipo implícito.
Inicializadores de objeto e coleção
Os inicializadores de objeto e de coleção possibilitam a inicialização de objetos sem
chamar explicitamente um construtor para o objeto. Os inicializadores normalmente são
usados em expressões de consulta quando projetam os dados de origem em um novo
tipo de dados. Supondo uma classe chamada Customer com propriedades públicas
Name e Phone , o inicializador de objeto pode ser usado como no código a seguir:

C#

var cust = new Customer { Name = "Mike", Phone = "555-1212" };

Continuando com sua classe Customer , suponha que haja uma fonte de dados chamada
IncomingOrders , e que para cada pedido com um grande OrderSize , você gostaria de

criar um novo Customer com base nessa ordem. Uma consulta LINQ pode ser executada
nessa fonte de dados e usar a inicialização do objeto para preencher uma coleção:

C#

var newLargeOrderCustomers = from o in IncomingOrders


where o.OrderSize > 5
select new Customer { Name = o.Name, Phone =
o.Phone };

A fonte de dados pode ter mais propriedades definidas do que a classe Customer como
OrderSize , mas com a inicialização do objeto, os dados retornados da consulta são

moldados para o tipo de dados desejado; você escolhe os dados relevantes para sua
classe. Como resultado, agora você tem um System.Collections.Generic.IEnumerable<T>
cheio dos novos Customer s que você queria. O exemplo anterior também pode ser
escrito na sintaxe do método LINQ:

C#

var newLargeOrderCustomers = IncomingOrders.Where(x => x.OrderSize >


5).Select(y => new Customer { Name = y.Name, Phone = y.Phone });

A partir do C# 12, você pode usar uma expressão de coleção para inicializar uma
coleção.

Para saber mais, veja:

Inicializadores de objeto e coleção


Sintaxe de expressão da consulta para operadores de consulta padrão
Tipos anônimos
O compilador constrói um tipo anônimo. O nome do tipo só está disponível para o
compilador. Os tipos anônimos fornecem uma maneira conveniente de agrupar um
conjunto de propriedades temporariamente em um resultado de consulta, sem a
necessidade de definir um tipo nomeado separado. Os tipos anônimos são inicializados
com uma nova expressão e um inicializador de objeto, como mostrado aqui:

C#

select new {name = cust.Name, phone = cust.Phone};

A partir do C# 7, você pode usar tuplas para criar tipos sem nome.

Métodos de Extensão
Um método de extensão é um método estático que pode ser associado a um tipo, para
que ele possa ser chamado como se fosse um método de instância no tipo. Esse recurso
permite que você, na verdade, "adicione" novos métodos a tipos existentes sem
realmente modificá-los. Os operadores de consulta padrão são um conjunto de
métodos de extensão que fornecem a funcionalidade de consulta LINQ para qualquer
tipo que implementa IEnumerable<T>.

Expressões lambda
Uma expressão lambda é uma função embutida que usa o operador => para separar os
parâmetros de entrada do corpo da função e pode ser convertida em tempo de
compilação em um representante ou em uma árvore de expressão. Na programação
LINQ, você encontra as expressões lambda ao fazer chamadas de método diretas aos
operadores de consulta padrão.

Expressões como dados


Os objetos de consulta são combináveis, o que significa que você pode retornar uma
consulta de um método. Os objetos que representam consultas não armazenam
a coleção resultante, mas sim as etapas para produzir os resultados quando necessário.
A vantagem de retornar objetos de consulta de métodos é que eles podem ser ainda
mais modificados e combinados. Portanto, qualquer valor retornado ou parâmetro out
de um método que retorna uma consulta também deve ter o tipo. Se um método
materializa uma consulta em um tipo List<T> ou Array concreto, ele retornará os
resultados da consulta em vez da própria consulta. Uma variável de consulta retornada
de um método ainda pode ser combinada ou modificada.

No exemplo a seguir, o primeiro método QueryMethod1 retorna uma consulta como um


valor retornado e o segundo método QueryMethod2 retorna uma consulta como um
parâmetro out ( returnQ , no exemplo). Em ambos os casos, é uma consulta que é
retornada, não os resultados da consulta.

C#

IEnumerable<string> QueryMethod1(int[] ints) =>


from i in ints
where i > 4
select i.ToString();

void QueryMethod2(int[] ints, out IEnumerable<string> returnQ) =>


returnQ =
from i in ints
where i < 4
select i.ToString();

int[] nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

var myQuery1 = QueryMethod1(nums);

A consulta myQuery1 é executada no loop foreach a seguir.

C#

foreach (var s in myQuery1)


{
Console.WriteLine(s);
}

Coloque o ponteiro do mouse sobre myQuery1 para ver o tipo.

Execute também a consulta retornada de QueryMethod1 diretamente, sem usar myQuery1 .

C#

foreach (var s in QueryMethod1(nums))


{
Console.WriteLine(s);
}

Coloque o ponteiro do mouse sobre a chamada a QueryMethod1 para ver o tipo de


retorno.
QueryMethod2 retorna uma consulta como o valor do seu parâmetro out :

C#

QueryMethod2(nums, out IEnumerable<string> myQuery2);

// Execute the returned query.


foreach (var s in myQuery2)
{
Console.WriteLine(s);
}

Você pode modificar uma consulta usando a composição de consultas. Nesse caso, o
objeto de consulta anterior é usado para criar um objeto de consulta. Esse novo objeto
retorna resultados diferentes do objeto de consulta original.

C#

myQuery1 =
from item in myQuery1
orderby item descending
select item;

// Execute the modified query.


Console.WriteLine("\nResults of executing modified myQuery1:");
foreach (var s in myQuery1)
{
Console.WriteLine(s);
}

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
Tutorial: Write queries in C# using
language integrated query (LINQ)
Article • 04/25/2024

In this tutorial, you create a data source and write several LINQ queries. You can
experiment with the query expressions and see the differences in the results. This
walkthrough demonstrates the C# language features that are used to write LINQ query
expressions. You can follow along and build the app and experiment with the queries
yourself. This article assumes you've installed the latest .NET SDK. If not, go to the .NET
Downloads page and install the latest version on your machine.

First, create the application. From the console, type the following command:

.NET CLI

dotnet new console -o WalkthroughWritingLinqQueries

Or, if you prefer Visual Studio, create a new console application named
WalkthroughWritingLinqQueries.

Create an in-memory data source


The first step is to create a data source for your queries. The data source for the queries
is a simple list of Student records. Each Student record has a first name, family name,
and an array of integers that represents their test scores in the class. Add a new file
named students.cs, and copy the following code into that file:

C#

namespace WalkthroughWritingLinqQueries;

public record Student(string First, string Last, int ID, int[] Scores);

Note the following characteristics:

The Student record consists of automatically implemented properties.


Each student in the list is initialized with the primary constructor.
The sequence of scores for each student is initialized with a primary constructor.

Next, create a sequence of Student records that serves as the source of this query. Open
Program.cs, and remove the following boilerplate code:
C#

// See https://aka.ms/new-console-template for more information


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

Replace it with the following code that creates a sequence of Student records:

C#

using WalkthroughWritingLinqQueries;

// Create a data source by using a collection initializer.


IEnumerable<Student> students =
[
new Student(First: "Svetlana", Last: "Omelchenko", ID: 111, Scores: [97,
92, 81, 60]),
new Student(First: "Claire", Last: "O'Donnell", ID: 112, Scores: [75,
84, 91, 39]),
new Student(First: "Sven", Last: "Mortensen", ID: 113, Scores: [88,
94, 65, 91]),
new Student(First: "Cesar", Last: "Garcia", ID: 114, Scores: [97,
89, 85, 82]),
new Student(First: "Debra", Last: "Garcia", ID: 115, Scores: [35,
72, 91, 70]),
new Student(First: "Fadi", Last: "Fakhouri", ID: 116, Scores: [99,
86, 90, 94]),
new Student(First: "Hanying", Last: "Feng", ID: 117, Scores: [93,
92, 80, 87]),
new Student(First: "Hugo", Last: "Garcia", ID: 118, Scores: [92,
90, 83, 78]),

new Student("Lance", "Tucker", 119, [68, 79, 88, 92]),


new Student("Terry", "Adams", 120, [99, 82, 81, 79]),
new Student("Eugene", "Zabokritski", 121, [96, 85, 91, 60]),
new Student("Michael", "Tucker", 122, [94, 92, 91, 91])
];

The sequence of students is initialized with a collection expression.


The Student record type holds the static list of all students.
Some of the constructor calls use named arguments to clarify which argument
matches which constructor parameter.

Try adding a few more students with different test scores to the list of students to get
more familiar with the code so far.

Create the query


Next, you create your first query. Your query, when you execute it, produces a list of all
students whose score on the first test was greater than 90. Because the whole Student
object is selected, the type of the query is IEnumerable<Student> . Although the code
could also use implicit typing by using the var keyword, explicit typing is used to clearly
illustrate results. (For more information about var , see Implicitly Typed Local Variables.)
Add the following code to Program.cs, after the code that creates the sequence of
students:

C#

// Create the query.


// The first line could also be written as "var studentQuery ="
IEnumerable<Student> studentQuery =
from student in students
where student.Scores[0] > 90
select student;

The query's range variable, student , serves as a reference to each Student in the source,
providing member access for each object.

Run the query


Now write the foreach loop that causes the query to execute. Each element in the
returned sequence is accessed through the iteration variable in the foreach loop. The
type of this variable is Student , and the type of the query variable is compatible,
IEnumerable<Student> . After you added the following code, build and run the

application to see the results in the Console window.

C#

// Execute the query.


// var could be used here also.
foreach (Student student in studentQuery)
{
Console.WriteLine($"{student.Last}, {student.First}");
}

// Output:
// Omelchenko, Svetlana
// Garcia, Cesar
// Fakhouri, Fadi
// Feng, Hanying
// Garcia, Hugo
// Adams, Terry
// Zabokritski, Eugene
// Tucker, Michael

To further refine the query, you can combine multiple Boolean conditions in the where
clause. The following code adds a condition so that the query returns those students
whose first score was over 90 and whose last score was less than 80. The where clause
should resemble the following code.

C#

where student.Scores[0] > 90 && student.Scores[3] < 80

Try the preceding where clause, or experiment yourself with other filter conditions. For
more information, see where clause.

Order the query results


It's easier to scan the results if they are in some kind of order. You can order the
returned sequence by any accessible field in the source elements. For example, the
following orderby clause orders the results in alphabetical order from A to Z according
to the family name of each student. Add the following orderby clause to your query,
right after the where statement and before the select statement:

C#

orderby student.Last ascending

Now change the orderby clause so that it orders the results in reverse order according
to the score on the first test, from the highest score to the lowest score.

C#

orderby student.Scores[0] descending

Change the WriteLine format string so that you can see the scores:

C#

Console.WriteLine($"{student.Last}, {student.First} {student.Scores[0]}");

For more information, see orderby clause.


Group the results
Grouping is a powerful capability in query expressions. A query with a group clause
produces a sequence of groups, and each group itself contains a Key and a sequence
that consists of all the members of that group. The following new query groups the
students by using the first letter of their family name as the key.

C#

IEnumerable<IGrouping<char, Student>> studentQuery =


from student in students
group student by student.Last[0];

The type of the query changed. It now produces a sequence of groups that have a char
type as a key, and a sequence of Student objects. The code in the foreach execution
loop also must change:

C#

foreach (IGrouping<char, Student> studentGroup in studentQuery)


{
Console.WriteLine(studentGroup.Key);
foreach (Student student in studentGroup)
{
Console.WriteLine($" {student.Last}, {student.First}");
}
}
// Output:
// O
// Omelchenko, Svetlana
// O'Donnell, Claire
// M
// Mortensen, Sven
// G
// Garcia, Cesar
// Garcia, Debra
// Garcia, Hugo
// F
// Fakhouri, Fadi
// Feng, Hanying
// T
// Tucker, Lance
// Tucker, Michael
// A
// Adams, Terry
// Z
// Zabokritski, Eugene
Run the application and view the results in the Console window. For more information,
see group clause.

Explicitly coding IEnumerables of IGroupings can quickly become tedious. Write the
same query and foreach loop much more conveniently by using var . The var keyword
doesn't change the types of your objects; it just instructs the compiler to infer the types.
Change the type of studentQuery and the iteration variable group to var and rerun the
query. In the inner foreach loop, the iteration variable is still typed as Student , and the
query works as before. Change the student iteration variable to var and run the query
again. You see that you get exactly the same results.

C#

IEnumerable<IGrouping<char, Student>> studentQuery =


from student in students
group student by student.Last[0];

foreach (IGrouping<char, Student> studentGroup in studentQuery)


{
Console.WriteLine(studentGroup.Key);
foreach (Student student in studentGroup)
{
Console.WriteLine($" {student.Last}, {student.First}");
}
}

For more information about var , see Implicitly Typed Local Variables.

Order the groups by their key value


The groups in the previous query aren't in alphabetical order. You can provide an
orderby clause after the group clause. But to use an orderby clause, you first need an
identifier that serves as a reference to the groups created by the group clause. You
provide the identifier by using the into keyword, as follows:

C#

var studentQuery4 =
from student in students
group student by student.Last[0] into studentGroup
orderby studentGroup.Key
select studentGroup;

foreach (var groupOfStudents in studentQuery4)


{
Console.WriteLine(groupOfStudents.Key);
foreach (var student in groupOfStudents)
{
Console.WriteLine($" {student.Last}, {student.First}");
}
}

// Output:
//A
// Adams, Terry
//F
// Fakhouri, Fadi
// Feng, Hanying
//G
// Garcia, Cesar
// Garcia, Debra
// Garcia, Hugo
//M
// Mortensen, Sven
//O
// Omelchenko, Svetlana
// O'Donnell, Claire
//T
// Tucker, Lance
// Tucker, Michael
//Z
// Zabokritski, Eugene

Run this query, and the groups are now sorted in alphabetical order.

You can use the let keyword to introduce an identifier for any expression result in the
query expression. This identifier can be a convenience, as in the following example. It
can also enhance performance by storing the results of an expression so that it doesn't
have to be calculated multiple times.

C#

// This query returns those students whose


// first test score was higher than their
// average score.
var studentQuery5 =
from student in students
let totalScore = student.Scores[0] + student.Scores[1] +
student.Scores[2] + student.Scores[3]
where totalScore / 4 < student.Scores[0]
select $"{student.Last}, {student.First}";

foreach (string s in studentQuery5)


{
Console.WriteLine(s);
}

// Output:
// Omelchenko, Svetlana
// O'Donnell, Claire
// Mortensen, Sven
// Garcia, Cesar
// Fakhouri, Fadi
// Feng, Hanying
// Garcia, Hugo
// Adams, Terry
// Zabokritski, Eugene
// Tucker, Michael

For more information, see the article on the let clause.

Use method syntax in a query expression


As described in Query Syntax and Method Syntax in LINQ, some query operations can
only be expressed by using method syntax. The following code calculates the total score
for each Student in the source sequence, and then calls the Average() method on the
results of that query to calculate the average score of the class.

C#

var studentQuery =
from student in students
let totalScore = student.Scores[0] + student.Scores[1] +
student.Scores[2] + student.Scores[3]
select totalScore;

double averageScore = studentQuery.Average();


Console.WriteLine("Class average score = {0}", averageScore);

// Output:
// Class average score = 334.166666666667

To transform or project in the select clause


It's common for a query to produce a sequence whose elements differ from the
elements in the source sequences. Delete or comment out your previous query and
execution loop, and replace it with the following code. The query returns a sequence of
strings (not Students ), and this fact is reflected in the foreach loop.

C#

IEnumerable<string> studentQuery =
from student in students
where student.Last == "Garcia"
select student.First;

Console.WriteLine("The Garcias in the class are:");


foreach (string s in studentQuery)
{
Console.WriteLine(s);
}

// Output:
// The Garcias in the class are:
// Cesar
// Debra
// Hugo

Code earlier in this walkthrough indicated that the average class score is approximately
334. To produce a sequence of Students whose total score is greater than the class
average, together with their Student ID , you can use an anonymous type in the select
statement:

C#

var aboveAverageQuery =
from student in students
let x = student.Scores[0] + student.Scores[1] +
student.Scores[2] + student.Scores[3]
where x > averageScore
select new { id = student.ID, score = x };

foreach (var item in aboveAverageQuery)


{
Console.WriteLine("Student ID: {0}, Score: {1}", item.id, item.score);
}

// Output:
// Student ID: 113, Score: 338
// Student ID: 114, Score: 353
// Student ID: 116, Score: 369
// Student ID: 117, Score: 352
// Student ID: 118, Score: 343
// Student ID: 120, Score: 341
// Student ID: 122, Score: 368
Standard Query Operators Overview
Article • 05/31/2024

The standard query operators are the keywords and methods that form the LINQ pattern.
The C# language defines LINQ query keywords that you use for the most common
query expression. The compiler translates expressions using these keywords to the
equivalent method calls. The two forms are synonymous. Other methods that are part of
the System.Linq namespace don't have equivalent query keywords. In those cases, you
must use the method syntax. This section covers all the query operator keywords. The
runtime and other NuGet packages add more methods designed to work with LINQ
queries each release. The most common methods, including those that have query
keyword equivalents are covered in this section. For the full list of query methods
supported by the .NET Runtime, see the System.Linq.Enumerable API documentation. In
addition to the methods covered here, this class contains methods for concatenating
data sources, computing a single value from a data source, such as a sum, average, or
other value.

) Important

These samples use an System.Collections.Generic.IEnumerable<T> data source.


Data sources based on System.Linq.IQueryProvider use
System.Linq.IQueryable<T> data sources and expression trees. Expression trees
have limitations on the allowed C# syntax. Furthermore, each IQueryProvider data
source, such as EF Core may impose more restrictions. Check the documentation
for your data source.

Most of these methods operate on sequences, where a sequence is an object whose


type implements the IEnumerable<T> interface or the IQueryable<T> interface. The
standard query operators provide query capabilities including filtering, projection,
aggregation, sorting and more. The methods that make up each set are static members
of the Enumerable and Queryable classes, respectively. They're defined as extension
methods of the type that they operate on.

The distinction between IEnumerable<T> and IQueryable<T> sequences determines


how the query is executed at runtime.

For IEnumerable<T> , the returned enumerable object captures the arguments that were
passed to the method. When that object is enumerated, the logic of the query operator
is employed and the query results are returned.
For IQueryable<T> , the query is translated into an expression tree. The expression tree
can be translated to a native query when the data source can optimize the query.
Libraries such as Entity Framework translate LINQ queries into native SQL queries that
execute at the database.

The following code example demonstrates how the standard query operators can be
used to obtain information about a sequence.

C#

string sentence = "the quick brown fox jumps over the lazy dog";
// Split the string into individual words to create a collection.
string[] words = sentence.Split(' ');

// Using query expression syntax.


var query = from word in words
group word.ToUpper() by word.Length into gr
orderby gr.Key
select new { Length = gr.Key, Words = gr };

// Using method-based query syntax.


var query2 = words.
GroupBy(w => w.Length, w => w.ToUpper()).
Select(g => new { Length = g.Key, Words = g }).
OrderBy(o => o.Length);

foreach (var obj in query)


{
Console.WriteLine("Words of length {0}:", obj.Length);
foreach (string word in obj.Words)
Console.WriteLine(word);
}

// This code example produces the following output:


//
// Words of length 3:
// THE
// FOX
// THE
// DOG
// Words of length 4:
// OVER
// LAZY
// Words of length 5:
// QUICK
// BROWN
// JUMPS

Where possible, the queries in this section use a sequence of words or numbers as the
input source. For queries where more complicated relationships between objects are
used, the following sources that model a school are used:
C#

public enum GradeLevel


{
FirstYear = 1,
SecondYear,
ThirdYear,
FourthYear
};

public class Student


{
public required string FirstName { get; init; }
public required string LastName { get; init; }
public required int ID { get; init; }

public required GradeLevel Year { get; init; }


public required List<int> Scores { get; init; }

public required int DepartmentID { get; init; }


}

public class Teacher


{
public required string First { get; init; }
public required string Last { get; init; }
public required int ID { get; init; }
public required string City { get; init; }
}

public class Department


{
public required string Name { get; init; }
public int ID { get; init; }

public required int TeacherID { get; init; }


}

Each Student has a grade level, a primary department, and a series of scores. A Teacher
also has a City property that identifies the campus where the teacher holds classes. A
Department has a name, and a reference to a Teacher who serves as the department

head.

You can find the data set in the source repo .

Types of query operators


The standard query operators differ in the timing of their execution, depending on
whether they return a singleton value or a sequence of values. Those methods that
return a singleton value (such as Average and Sum) execute immediately. Methods that
return a sequence defer the query execution and return an enumerable object. You can
use the output sequence of one query as the input sequence to another query. Calls to
query methods can be chained together in one query, which enables queries to become
arbitrarily complex.

Query operators
In a LINQ query, the first step is to specify the data source. In a LINQ query, the from
clause comes first in order to introduce the data source ( students ) and the range
variable ( student ).

C#

//queryAllStudents is an IEnumerable<Student>
var queryAllStudents = from student in students
select student;

The range variable is like the iteration variable in a foreach loop except that no actual
iteration occurs in a query expression. When the query is executed, the range variable
serves as a reference to each successive element in students . Because the compiler can
infer the type of student , you don't have to specify it explicitly. You can introduce more
range variables in a let clause. For more information, see let clause.

7 Note

For non-generic data sources such as ArrayList, the range variable must be
explicitly typed. For more information, see How to query an ArrayList with LINQ
(C#) and from clause.

Once you obtain a data source, you can perform any number of operations on that data
source:

Filter data using the where keyword.


Order data using the orderby and optionally descending keywords.
Group data using the group and optionally into keywords.
Join data using the join keyword.
Project data using the select keyword.

Query Expression Syntax Table


The following table lists the standard query operators that have equivalent query
expression clauses.

ノ Expand table

Method C# query
expression syntax

Cast Use an explicitly


typed range
variable:

from int i in
numbers

(For more
information, see
from clause.)

GroupBy group … by

-or-

group … by … into

(For more
information, see
group clause.)

GroupJoin<TOuter,TInner,TKey,TResult>(IEnumerable<TOuter>, join … in … on …
IEnumerable<TInner>, Func<TOuter,TKey>, Func<TInner,TKey>, equals … into …
Func<TOuter,IEnumerable<TInner>, TResult>)
(For more
information, see
join clause.)

Join<TOuter,TInner,TKey,TResult>(IEnumerable<TOuter>, join … in … on …
IEnumerable<TInner>, Func<TOuter,TKey>, Func<TInner,TKey>, equals …
Func<TOuter,TInner,TResult>)
(For more
information, see
join clause.)

OrderBy<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>) orderby

(For more
information, see
orderby clause.)
Method C# query
expression syntax

OrderByDescending<TSource,TKey>(IEnumerable<TSource>, orderby …
Func<TSource,TKey>) descending

(For more
information, see
orderby clause.)

Select select

(For more
information, see
select clause.)

SelectMany Multiple from


clauses.

(For more
information, see
from clause.)

ThenBy<TSource,TKey>(IOrderedEnumerable<TSource>, orderby …, …
Func<TSource,TKey>)
(For more
information, see
orderby clause.)

ThenByDescending<TSource,TKey>(IOrderedEnumerable<TSource>, orderby …, …
Func<TSource,TKey>) descending

(For more
information, see
orderby clause.)

Where where

(For more
information, see
where clause.)

Data Transformations with LINQ


Language-Integrated Query (LINQ) isn't only about retrieving data. It's also a powerful
tool for transforming data. By using a LINQ query, you can use a source sequence as
input and modify it in many ways to create a new output sequence. You can modify the
sequence itself without modifying the elements themselves by sorting and grouping.
But perhaps the most powerful feature of LINQ queries is the ability to create new types.
The select clause creates an output element from an input element. You use it to
transform an input element into an output element:

Merge multiple input sequences into a single output sequence that has a new
type.
Create output sequences whose elements consist of only one or several properties
of each element in the source sequence.
Create output sequences whose elements consist of the results of operations
performed on the source data.
Create output sequences in a different format. For example, you can transform
data from SQL rows or text files into XML.

These transformations can be combined in various ways in the same query. Furthermore,
the output sequence of one query can be used as the input sequence for a new query.
The following example transforms objects in an in-memory data structure into XML
elements.

C#

// Create the query.


var studentsToXML = new XElement("Root",
from student in students
let scores = string.Join(",", student.Scores)
select new XElement("student",
new XElement("First", student.FirstName),
new XElement("Last", student.LastName),
new XElement("Scores", scores)
) // end "student"
); // end "Root"

// Execute the query.


Console.WriteLine(studentsToXML);

The code produces the following XML output:

XML

<Root>
<student>
<First>Svetlana</First>
<Last>Omelchenko</Last>
<Scores>97,90,73,54</Scores>
</student>
<student>
<First>Claire</First>
<Last>O'Donnell</Last>
<Scores>56,78,95,95</Scores>
</student>
...
<student>
<First>Max</First>
<Last>Lindgren</Last>
<Scores>86,88,96,63</Scores>
</student>
<student>
<First>Arina</First>
<Last>Ivanova</Last>
<Scores>93,63,70,80</Scores>
</student>
</Root>

For more information, see Creating XML Trees in C# (LINQ to XML).

You can use the results of one query as the data source for a subsequent query. This
example shows how to order the results of a join operation. This query creates a group
join, and then sorts the groups based on the category element, which is still in scope.
Inside the anonymous type initializer, a subquery orders all the matching elements from
the products sequence.

C#

var orderedQuery = from department in departments


join student in students on department.ID equals
student.DepartmentID into studentGroup
orderby department.Name
select new
{
DepartmentName = department.Name,
Students = from student in studentGroup
orderby student.LastName
select student
};

foreach (var departmentList in orderedQuery)


{
Console.WriteLine(departmentList.DepartmentName);
foreach (var student in departmentList.Students)
{
Console.WriteLine($" {student.LastName,-10}
{student.FirstName,-10}");
}
}
/* Output:
Chemistry
Balzan Josephine
Fakhouri Fadi
Popov Innocenty
Seleznyova Sofiya
Vella Carmen
Economics
Adams Terry
Adaobi Izuchukwu
Berggren Jeanette
Garcia Cesar
Ifeoma Nwanneka
Jamuike Ifeanacho
Larsson Naima
Svensson Noel
Ugomma Ifunanya
Engineering
Axelsson Erik
Berg Veronika
Engström Nancy
Hicks Cassie
Keever Bruce
Micallef Nicholas
Mortensen Sven
Nilsson Erna
Tucker Michael
Yermolayeva Anna
English
Andersson Sarah
Feng Hanying
Ivanova Arina
Jakobsson Jesper
Jensen Christiane
Johansson Mark
Kolpakova Nadezhda
Omelchenko Svetlana
Urquhart Donald
Mathematics
Frost Gaby
Garcia Hugo
Hedlund Anna
Kovaleva Katerina
Lindgren Max
Maslova Evgeniya
Olsson Ruth
Sammut Maria
Sazonova Anastasiya
Physics
Åkesson Sami
Edwards Amy E.
Falzon John
Garcia Debra
Hansson Sanna
Mattsson Martina
Richardson Don
Zabokritski Eugene
*/

The equivalent query using method syntax is shown in the following code:
C#

var orderedQuery = departments


.GroupJoin(students, department => department.ID, student =>
student.DepartmentID,
(department, studentGroup) => new
{
DepartmentName = department.Name,
Students = studentGroup.OrderBy(student => student.LastName)
})
.OrderBy(department => department.DepartmentName);

foreach (var departmentList in orderedQuery)


{
Console.WriteLine(departmentList.DepartmentName);
foreach (var student in departmentList.Students)
{
Console.WriteLine($" {student.LastName,-10}
{student.FirstName,-10}");
}
}

Although you can use an orderby clause with one or more of the source sequences
before the join, generally we don't recommend it. Some LINQ providers might not
preserve that ordering after the join. For more information, see join clause.

See also
Enumerable
Queryable
select clause
Extension Methods
Query Keywords (LINQ)
Anonymous Types
Filtrando dados em C# com LINQ
Artigo • 07/06/2024

A filtragem é a operação de restringir o conjunto de resultados de forma que ele


contenha apenas os elementos correspondentes a uma condição especificada. Também
é conhecido como seleção de elementos que correspondem à condição especificada.

) Importante

Esses exemplos usam uma fonte de dados


System.Collections.Generic.IEnumerable<T>. Fontes de dados baseadas em
System.Linq.IQueryProvider usam as fontes de dados System.Linq.IQueryable<T>
e as árvores de expressão. As árvores de expressão possuem limitações na sintaxe
C# permitida. Além disso, todas as fontes de dados IQueryProvider , como EF Core,
podem impor mais restrições. Verifique a documentação da fonte de dados.

A ilustração a seguir mostra os resultados da filtragem de uma sequência de caracteres.


O predicado para a operação de filtragem especifica que o caractere deve ser "A".

Os métodos de operador de consulta padrão que realizam a seleção estão listados na


tabela a seguir:

ノ Expandir a tabela

Nome do Descrição Sintaxe de Mais informações


método expressão de
consulta C#

OfType Seleciona valores, dependendo da Não aplicável. Enumerable.OfType


capacidade de serem convertidos em
um tipo especificado. Queryable.OfType

Where Seleciona valores com base em uma where Enumerable.Where


função de predicado.
Queryable.Where
O exemplo a seguir usa a cláusula where para filtrar em uma matriz as cadeias de
caracteres com um tamanho específico.

C#

string[] words = ["the", "quick", "brown", "fox", "jumps"];

IEnumerable<string> query = from word in words


where word.Length == 3
select word;

foreach (string str in query)


{
Console.WriteLine(str);
}

/* This code produces the following output:

the
fox
*/

A consulta equivalente usando a sintaxe do método é mostrada no código a seguir:

C#

string[] words = ["the", "quick", "brown", "fox", "jumps"];

IEnumerable<string> query =
words.Where(word => word.Length == 3);

foreach (string str in query)


{
Console.WriteLine(str);
}

/* This code produces the following output:

the
fox
*/

Confira também
System.Linq
Cláusula where
Como consultar metadados de um assembly com Reflexão (LINQ) (C#)
Como consultar arquivos com um atributo ou nome especificado (C#)
Como classificar ou filtrar dados de texto por qualquer palavra ou campo (LINQ)
(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
Operações de projeção (C#)
Artigo • 07/06/2024

Projeção refere-se à operação de transformar um objeto em um novo formulário que


geralmente consiste apenas nas propriedades usadas posteriormente. Usando a
projeção, você pode construir um novo tipo que é criado de cada objeto. É possível
projetar uma propriedade e executar uma função matemática nela. Também é possível
projetar o objeto original sem alterá-lo.

) Importante

Esses exemplos usam uma fonte de dados


System.Collections.Generic.IEnumerable<T>. Fontes de dados baseadas em
System.Linq.IQueryProvider usam as fontes de dados System.Linq.IQueryable<T>
e as árvores de expressão. As árvores de expressão possuem limitações na sintaxe
C# permitida. Além disso, todas as fontes de dados IQueryProvider , como EF Core,
podem impor mais restrições. Verifique a documentação da fonte de dados.

Os métodos de operador de consulta padrão que realizam a projeção estão listados na


seção a seguir.

Métodos
ノ Expandir a tabela

Nomes de Descrição Sintaxe de Mais informações


método expressão de
consulta em C#

Selecionar Projeta valores com base em uma select Enumerable.Select


função de transformação. Queryable.Select

SelectMany Projeta sequências de valores Use várias Enumerable.SelectMany


baseados em uma função de cláusulas from Queryable.SelectMany
transformação e os mescla em
uma sequência.

Zip Produz uma sequência de tuplas Não aplicável. Enumerable.Zip


com elementos das 2 a 3 Queryable.Zip
sequências especificadas.
Select
O exemplo a seguir usa a cláusula select para projetar a primeira letra de cada cadeia
de caracteres em uma lista de cadeias de caracteres.

C#

List<string> words = ["an", "apple", "a", "day"];

var query = from word in words


select word.Substring(0, 1);

foreach (string s in query)


{
Console.WriteLine(s);
}

/* This code produces the following output:

a
a
a
d
*/

A consulta equivalente usando a sintaxe do método é mostrada no código a seguir:

C#

List<string> words = ["an", "apple", "a", "day"];

var query = words.Select(word => word.Substring(0, 1));

foreach (string s in query)


{
Console.WriteLine(s);
}

/* This code produces the following output:

a
a
a
d
*/

SelectMany
O exemplo a seguir usa várias cláusulas from para projetar cada palavra de cada cadeia
de caracteres em uma lista de cadeias de caracteres.

C#

List<string> phrases = ["an apple a day", "the quick brown fox"];

var query = from phrase in phrases


from word in phrase.Split(' ')
select word;

foreach (string s in query)


{
Console.WriteLine(s);
}

/* This code produces the following output:

an
apple
a
day
the
quick
brown
fox
*/

A consulta equivalente usando a sintaxe do método é mostrada no código a seguir:

C#

List<string> phrases = ["an apple a day", "the quick brown fox"];

var query = phrases.SelectMany(phrases => phrases.Split(' '));

foreach (string s in query)


{
Console.WriteLine(s);
}

/* This code produces the following output:

an
apple
a
day
the
quick
brown
fox
*/

O método SelectMany também pode formar a combinação de correspondência de cada


item na primeira sequência com cada item na segunda sequência:

C#

var query = from number in numbers


from letter in letters
select (number, letter);

foreach (var item in query)


{
Console.WriteLine(item);
}

A consulta equivalente usando a sintaxe do método é mostrada no código a seguir:

C#

var method = numbers


.SelectMany(number => letters,
(number, letter) => (number, letter));

foreach (var item in method)


{
Console.WriteLine(item);
}

Zip
Há várias sobrecargas para o operador de projeção Zip . Todos os métodos Zip
funcionam em sequências de dois ou mais tipos possivelmente heterogêneos. As duas
primeiras sobrecargas retornam tuplas com o tipo posicional correspondente das
sequências fornecidas.

Considere as seguintes coleções:

C#

// An int array with 7 elements.


IEnumerable<int> numbers = [1, 2, 3, 4, 5, 6, 7];
// A char array with 6 elements.
IEnumerable<char> letters = ['A', 'B', 'C', 'D', 'E', 'F'];
Para projetar essas sequências em conjunto, use o operador
Enumerable.Zip<TFirst,TSecond>(IEnumerable<TFirst>, IEnumerable<TSecond>):

C#

foreach ((int number, char letter) in numbers.Zip(letters))


{
Console.WriteLine($"Number: {number} zipped with letter: '{letter}'");
}
// This code produces the following output:
// Number: 1 zipped with letter: 'A'
// Number: 2 zipped with letter: 'B'
// Number: 3 zipped with letter: 'C'
// Number: 4 zipped with letter: 'D'
// Number: 5 zipped with letter: 'E'
// Number: 6 zipped with letter: 'F'

) Importante

A sequência resultante de uma operação zip nunca tem mais comprimento do que
a sequência mais curta. As coleções numbers e letters diferem em comprimento e
a sequência resultante omite o último elemento da coleção numbers , pois ela não
possui nada com o que zipar.

A segunda sobrecarga aceita uma sequência third . Vamos criar outra coleção, ou seja,
emoji :

C#

// A string array with 8 elements.


IEnumerable<string> emoji = [ "🤓", "🔥", "🎉", "👀", "⭐", "💜", "✔",
"💯"];

Para projetar essas sequências em conjunto, use o operador


Enumerable.Zip<TFirst,TSecond,TThird>(IEnumerable<TFirst>, IEnumerable<TSecond>,
IEnumerable<TThird>):

C#

foreach ((int number, char letter, string em) in numbers.Zip(letters,


emoji))
{
Console.WriteLine(
$"Number: {number} is zipped with letter: '{letter}' and emoji:
{em}");
}
// This code produces the following output:
// Number: 1 is zipped with letter: 'A' and emoji: 🤓
// Number: 2 is zipped with letter: 'B' and emoji: 🔥
// Number: 3 is zipped with letter: 'C' and emoji: 🎉
// Number: 4 is zipped with letter: 'D' and emoji: 👀
// Number: 5 is zipped with letter: 'E' and emoji: ⭐
// Number: 6 is zipped with letter: 'F' and emoji: 💜

Assim como a sobrecarga anterior, o método Zip projeta uma tupla, mas desta vez com
três elementos.

A terceira sobrecarga aceita um argumento Func<TFirst, TSecond, TResult> que atua


como um seletor de resultados. Você pode projetar uma nova sequência resultante das
sequências que estão sendo compactadas.

C#

foreach (string result in


numbers.Zip(letters, (number, letter) => $"{number} = {letter}
({(int)letter})"))
{
Console.WriteLine(result);
}
// This code produces the following output:
// 1 = A (65)
// 2 = B (66)
// 3 = C (67)
// 4 = D (68)
// 5 = E (69)
// 6 = F (70)

Com a sobrecarga Zip anterior, a função especificada é aplicada aos elementos


correspondentes numbers e letter , produzindo uma sequência dos resultados string .

Select versus SelectMany


O trabalho de Select e SelectMany é produzir um valor (ou valores) de resultado dos
valores de origem. Select produz um valor de resultado para cada valor de origem. O
resultado geral, portanto, é uma coleção que tem o mesmo número de elementos que a
coleção de origem. Por outro lado, SelectMany produz um único resultado geral que
contém subcoleções concatenadas de cada valor de origem. A função de transformação
passada como um argumento para SelectMany deve retornar uma sequência
enumerável de valores para cada valor de origem. SelectMany concatena essas
sequências enumeráveis para criar uma sequência grande.
As duas ilustrações a seguir mostram a diferença conceitual entre as ações desses dois
métodos. Em cada caso, presuma que a função de seletor (transformação) seleciona a
matriz de flores de cada valor de origem.

A ilustração mostra como Select retorna uma coleção que tem o mesmo número de
elementos que a coleção de origem.

Esta ilustração mostra como SelectMany concatena a sequência intermediária de


matrizes em um valor de resultado final que contém cada valor de cada matriz
intermediária.

Exemplo de código
O exemplo a seguir compara o comportamento de Select e de SelectMany . O código
cria um "buquê" de flores usando os itens de cada lista de nomes de flor na coleção de
fonte. No exemplo a seguir, o “valor único” que a função de transformação
Select<TSource,TResult>(IEnumerable<TSource>, Func<TSource,TResult>) usa é uma
coleção de valores. Esse exemplo requer o loop foreach extra para enumerar cada
cadeia de caracteres em cada subsequência.
C#

class Bouquet
{
public required List<string> Flowers { get; init; }
}

static void SelectVsSelectMany()


{
List<Bouquet> bouquets =
[
new Bouquet { Flowers = ["sunflower", "daisy", "daffodil",
"larkspur"] },
new Bouquet { Flowers = ["tulip", "rose", "orchid"] },
new Bouquet { Flowers = ["gladiolis", "lily", "snapdragon", "aster",
"protea"] },
new Bouquet { Flowers = ["larkspur", "lilac", "iris", "dahlia"] }
];

IEnumerable<List<string>> query1 = bouquets.Select(bq => bq.Flowers);

IEnumerable<string> query2 = bouquets.SelectMany(bq => bq.Flowers);

Console.WriteLine("Results by using Select():");


// Note the extra foreach loop here.
foreach (IEnumerable<string> collection in query1)
{
foreach (string item in collection)
{
Console.WriteLine(item);
}
}

Console.WriteLine("\nResults by using SelectMany():");


foreach (string item in query2)
{
Console.WriteLine(item);
}
}

Confira também
System.Linq
Cláusula select
Como preencher coleções de objetos de várias fontes (LINQ) (C#)
Como dividir um arquivo em vários arquivos usando grupos (LINQ) (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
Operações de conjunto (C#)
Artigo • 07/06/2024

Definir operações no LINQ referem-se a operações de consulta que produzem um


conjunto de resultados baseado na presença ou ausência de elementos equivalentes
dentro das mesmas coleções ou coleções separadas.

) Importante

Esses exemplos usam uma fonte de dados


System.Collections.Generic.IEnumerable<T>. Fontes de dados baseadas em
System.Linq.IQueryProvider usam as fontes de dados System.Linq.IQueryable<T>
e as árvores de expressão. As árvores de expressão possuem limitações na sintaxe
C# permitida. Além disso, todas as fontes de dados IQueryProvider , como EF Core,
podem impor mais restrições. Verifique a documentação da fonte de dados.

ノ Expandir a tabela

Nomes de Descrição Sintaxe de Mais informações


método expressão de
consulta em C#

Distinct ou Remove os valores duplicados de Não aplicável. Enumerable.Distinct


DistinctBy uma coleção. Enumerable.DistinctBy
Queryable.Distinct
Queryable.DistinctBy

Except ou Retorna a diferença de conjunto, Não aplicável. Enumerable.Except


ExceptBy que significa os elementos de Enumerable.ExceptBy
uma coleção que não aparecem Queryable.Except
em uma segunda coleção. Queryable.ExceptBy

Intersect ou Retorna a interseção de conjunto, Não aplicável. Enumerable.Intersect


IntersectBy o que significa os elementos que Enumerable.IntersectBy
aparecem em cada uma das duas Queryable.Intersect
coleções. Queryable.IntersectBy

Union ou Retorna a união de conjunto, o Não aplicável. Enumerable.Union


UnionBy que significa os elementos únicos Enumerable.UnionBy
que aparecem em qualquer uma Queryable.Union
das duas coleções. Queryable.UnionBy

Distinct e DistinctBy
O exemplo a seguir descreve o comportamento do método Enumerable.Distinct em
uma sequência de cadeias de caracteres. A sequência retornada contém os elementos
exclusivos da sequência de entrada.

C#

string[] words = ["the", "quick", "brown", "fox", "jumped", "over", "the",


"lazy", "dog"];

IEnumerable<string> query = from word in words.Distinct()


select word;

foreach (var str in query)


{
Console.WriteLine(str);
}

/* This code produces the following output:


*
* the
* quick
* brown
* fox
* jumped
* over
* lazy
* dog
*/

DistinctBy é uma abordagem alternativa a Distinct que usa keySelector . keySelector


é usado como o discriminador comparativo do tipo de origem. No seguinte código, as
palavras são discriminados com base no respectivo Length , e a primeira palavra de cada
tipo é exibida:

C#

string[] words = ["the", "quick", "brown", "fox", "jumped", "over", "the",


"lazy", "dog"];

foreach (string word in words.DistinctBy(p => p.Length))


{
Console.WriteLine(word);
}

// This code produces the following output:


// the
// quick
// jumped
// over

Except e ExceptBy
O exemplo a seguir descreve o comportamento de Enumerable.Except. A sequência
retornada contém apenas os elementos da primeira sequência de entrada que não
estão na segunda sequência de entrada.

Os exemplos a seguir neste artigo usam fontes de dados comuns para esta área:

C#

public enum GradeLevel


{
FirstYear = 1,
SecondYear,
ThirdYear,
FourthYear
};

public class Student


{
public required string FirstName { get; init; }
public required string LastName { get; init; }
public required int ID { get; init; }

public required GradeLevel Year { get; init; }


public required List<int> Scores { get; init; }

public required int DepartmentID { get; init; }


}

public class Teacher


{
public required string First { get; init; }
public required string Last { get; init; }
public required int ID { get; init; }
public required string City { get; init; }
}
public class Department
{
public required string Name { get; init; }
public int ID { get; init; }
public required int TeacherID { get; init; }
}

Cada Student tem um nível de escolaridade, um departamento primário e uma série de


pontuações. Um Teacher também tem uma propriedade City que identifica o campus
onde o docente ministra aulas. Um Department tem um nome, e uma referência a um
Teacher que serve como o chefe do departamento.

C#

string[] words1 = ["the", "quick", "brown", "fox"];


string[] words2 = ["jumped", "over", "the", "lazy", "dog"];

IEnumerable<string> query = from word in words1.Except(words2)


select word;

foreach (var str in query)


{
Console.WriteLine(str);
}

/* This code produces the following output:


*
* quick
* brown
* fox
*/

O método ExceptBy é uma abordagem alternativa a Except que usa duas sequências de
tipos possivelmente heterogêneos e um keySelector . O keySelector é o mesmo tipo
que o tipo da primeira coleção. Considere a matriz Teacher a seguir e IDs de professor a
serem excluídas. Para encontrar professores na primeira coleção que não estão na
segunda coleção, você pode projetar a ID do professor na segunda coleção:

C#

int[] teachersToExclude =
[
901, // English
965, // Mathematics
932, // Engineering
945, // Economics
987, // Physics
901 // Chemistry
];

foreach (Teacher teacher in


teachers.ExceptBy(
teachersToExclude, teacher => teacher.ID))
{
Console.WriteLine($"{teacher.First} {teacher.Last}");
}

No código anterior do C#:

A matriz teachers é filtrada apenas para os professores que não estão na matriz
teachersToExclude .

A matriz teachersToExclude contém o valor ID de todos os chefes de


departamento.
A chamada para ExceptBy resulta em um novo conjunto de valores que são
gravados no console.

O novo conjunto de valores é do tipo Teacher , que é o tipo da primeira coleção. Cada
teacher na matriz teachers que não tem um valor de ID correspondente na matriz

teachersToExclude é gravada no console.

Intersect e IntersectBy
O exemplo a seguir descreve o comportamento de Enumerable.Intersect. A sequência
retornada contém os elementos que são comuns a ambas as sequências de entrada.

C#

string[] words1 = ["the", "quick", "brown", "fox"];


string[] words2 = ["jumped", "over", "the", "lazy", "dog"];

IEnumerable<string> query = from word in words1.Intersect(words2)


select word;

foreach (var str in query)


{
Console.WriteLine(str);
}

/* This code produces the following output:


*
* the
*/
O método IntersectBy é uma abordagem alternativa a Intersect que usa duas
sequências de tipos possivelmente heterogêneos e um keySelector . O keySelector é
usado como o discriminatório comparativo do tipo da segunda coleção. Considere as
matrizes de alunos e professores a seguir. A consulta corresponde os itens em cada
sequência por nome para localizar os alunos que também não são professores:

C#

foreach (Student person in


students.IntersectBy(
teachers.Select(t => (t.First, t.Last)), s => (s.FirstName,
s.LastName)))
{
Console.WriteLine($"{person.FirstName} {person.LastName}");
}

No código anterior do C#:

A consulta produz a interseção do Teacher e Student comparando nomes.


Somente as pessoas encontradas em ambas as matrizes estão presentes na
sequência resultante.
As instâncias resultantes Student são gravadas no console.

Union e UnionBy
O exemplo a seguir descreve uma operação de união em duas sequências de cadeias de
caracteres. A sequência retornada contém os elementos exclusivos das duas sequências
de entrada.

C#

string[] words1 = ["the", "quick", "brown", "fox"];


string[] words2 = ["jumped", "over", "the", "lazy", "dog"];

IEnumerable<string> query = from word in words1.Union(words2)


select word;

foreach (var str in query)


{
Console.WriteLine(str);
}
/* This code produces the following output:
*
* the
* quick
* brown
* fox
* jumped
* over
* lazy
* dog
*/

O método UnionBy é uma abordagem alternativa a Union que usa duas sequências do
mesmo tipo e uma keySelector . keySelector é usado como o discriminador
comparativo do tipo de origem. A consulta a seguir produz a lista de todas as pessoas
que são alunos ou professores. Os alunos que também são professores são adicionados
ao conjunto sindical apenas uma vez:

C#

foreach (var person in


students.Select(s => (s.FirstName, s.LastName)).UnionBy(
teachers.Select(t => (FirstName: t.First, LastName: t.Last)), s =>
(s.FirstName, s.LastName)))
{
Console.WriteLine($"{person.FirstName} {person.LastName}");
}

No código anterior do C#:

As matrizes teachers e students são tecidas usando os nomes delas como o


seletor de chaves.
OS nomes resultantes são gravados no console.

Confira também
System.Linq
Como localizar a diferença de conjunto entre duas listas (LINQ) (C#)

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
Classificando dados (C#)
Artigo • 31/10/2024

Uma operação de classificação ordena os elementos de uma sequência com base em


um ou mais atributos. O primeiro critério de classificação executa uma classificação
primária dos elementos. Especificando um segundo critério de classificação, você pode
classificar os elementos dentro de cada grupo de classificação primário.

) Importante

Esses exemplos usam uma fonte de dados


System.Collections.Generic.IEnumerable<T>. Fontes de dados baseadas em
System.Linq.IQueryProvider usam as fontes de dados System.Linq.IQueryable<T>
e as árvores de expressão. As árvores de expressão possuem limitações na sintaxe
C# permitida. Além disso, todas as fontes de dados IQueryProvider , como EF Core,
podem impor mais restrições. Verifique a documentação da fonte de dados.

A ilustração a seguir mostra os resultados de uma operação de classificação alfabética


em uma sequência de caracteres:

Os métodos de operador de consulta padrão que classificam dados estão listados na


seção a seguir.

Métodos
ノ Expandir a tabela

Nome do método Descrição Sintaxe de Mais informações


expressão de
consulta C#

OrderBy Classifica valores em orderby Enumerable.OrderBy


ordem crescente.
Queryable.OrderBy
Nome do método Descrição Sintaxe de Mais informações
expressão de
consulta C#

OrderByDescending Classifica valores em orderby … Enumerable.OrderByDescending


ordem decrescente. descending
Queryable.OrderByDescending

ThenBy Executa uma orderby …, … Enumerable.ThenBy


classificação
secundária em ordem Queryable.ThenBy
crescente.

ThenByDescending Executa uma orderby …, … Enumerable.ThenByDescending


classificação descending
secundária em ordem Queryable.ThenByDescending
decrescente.

Reverse Inverte a ordem dos Não aplicável. Enumerable.Reverse


elementos em uma
coleção. Queryable.Reverse

7 Observação

Os exemplos a seguir neste artigo usam as fontes de dados comuns para essa área.
Cada Student tem um nível de escolaridade, um departamento primário e uma
série de pontuações. Um Teacher também tem uma propriedade City que
identifica o campus onde o docente ministra aulas. A Department tem um nome e
uma referência a um Teacher que atua como chefe do departamento.
Você pode encontrar o conjunto de dados de exemplo no repositório de origem.

C#

public enum GradeLevel


{
FirstYear = 1,
SecondYear,
ThirdYear,
FourthYear
};

public class Student


{
public required string FirstName { get; init; }
public required string LastName { get; init; }
public required int ID { get; init; }

public required GradeLevel Year { get; init; }


public required List<int> Scores { get; init; }

public required int DepartmentID { get; init; }


}

public class Teacher


{
public required string First { get; init; }
public required string Last { get; init; }
public required int ID { get; init; }
public required string City { get; init; }
}

public class Department


{
public required string Name { get; init; }
public int ID { get; init; }

public required int TeacherID { get; init; }


}

Classificação crescente primária


O exemplo a seguir demonstra como usar a cláusula orderby em uma consulta LINQ
para classificar a matriz de professores por nome de família, em ordem crescente.

C#

IEnumerable<string> query = from teacher in teachers


orderby teacher.Last
select teacher.Last;

foreach (string str in query)


{
Console.WriteLine(str);
}

A consulta equivalente escrita usando a sintaxe do método é mostrada no seguinte


código:

C#

IEnumerable<string> query = teachers


.OrderBy(teacher => teacher.Last)
.Select(teacher => teacher.Last);

foreach (string str in query)


{
Console.WriteLine(str);
}

Classificação decrescente primária


O exemplo a seguir demonstra como usar a cláusula orderby descending em uma
consulta LINQ para classificar os professores pelo nome da família, em ordem
decrescente.

C#

IEnumerable<string> query = from teacher in teachers


orderby teacher.Last descending
select teacher.Last;

foreach (string str in query)


{
Console.WriteLine(str);
}

A consulta equivalente escrita usando a sintaxe do método é mostrada no seguinte


código:

C#

IEnumerable<string> query = teachers


.OrderByDescending(teacher => teacher.Last)
.Select(teacher => teacher.Last);

foreach (string str in query)


{
Console.WriteLine(str);
}

Classificação crescente secundária


O exemplo a seguir demonstra como usar a cláusula orderby em uma consulta LINQ
para executar uma classificação primária e secundária. Os professores são classificados
principalmente pela cidade e, em segundo lugar, pelo nome da família, ambos em
ordem crescente.

C#
IEnumerable<(string, string)> query = from teacher in teachers
orderby teacher.City, teacher.Last
select (teacher.Last, teacher.City);

foreach ((string last, string city) in query)


{
Console.WriteLine($"City: {city}, Last Name: {last}");
}

A consulta equivalente escrita usando a sintaxe do método é mostrada no seguinte


código:

C#

IEnumerable<(string, string)> query = teachers


.OrderBy(teacher => teacher.City)
.ThenBy(teacher => teacher.Last)
.Select(teacher => (teacher.Last, teacher.City));

foreach ((string last, string city) in query)


{
Console.WriteLine($"City: {city}, Last Name: {last}");
}

Classificação decrescente secundária


O exemplo seguinte demonstra como usar a cláusula orderby descending em uma
consulta de LINQ para executar uma classificação primária, em ordem crescente e uma
classificação secundária, em ordem decrescente. Os professores são classificados
principalmente pela cidade e, em segundo lugar, pelo nome da família.

C#

IEnumerable<(string, string)> query = from teacher in teachers


orderby teacher.City, teacher.Last descending
select (teacher.Last, teacher.City);

foreach ((string last, string city) in query)


{
Console.WriteLine($"City: {city}, Last Name: {last}");
}

A consulta equivalente escrita usando a sintaxe do método é mostrada no seguinte


código:

C#
IEnumerable<(string, string)> query = teachers
.OrderBy(teacher => teacher.City)
.ThenByDescending(teacher => teacher.Last)
.Select(teacher => (teacher.Last, teacher.City));

foreach ((string last, string city) in query)


{
Console.WriteLine($"City: {city}, Last Name: {last}");
}

Confira também
System.Linq
Cláusula orderby
Como classificar ou filtrar dados de texto por qualquer palavra ou campo (LINQ)
(C#)
Operações do quantificador no LINQ
(C#)
Artigo • 07/06/2024

As operações de quantificador retornam um valor Boolean que indica se alguns ou


todos os elementos em uma sequência satisfazem uma condição.

) Importante

Esses exemplos usam uma fonte de dados


System.Collections.Generic.IEnumerable<T>. Fontes de dados baseadas em
System.Linq.IQueryProvider usam as fontes de dados System.Linq.IQueryable<T>
e as árvores de expressão. As árvores de expressão possuem limitações na sintaxe
C# permitida. Além disso, todas as fontes de dados IQueryProvider , como EF Core,
podem impor mais restrições. Verifique a documentação da fonte de dados.

A ilustração a seguir mostra duas operações de quantificador diferentes em duas


sequências de origem diferentes. A primeira operação pergunta se algum dos
elementos é o caractere 'A'. A segunda operação pergunta se todos os elementos são o
caractere 'A'. Ambos os métodos retornam true neste exemplo.

ノ Expandir a tabela

Nome do Descrição Sintaxe de Mais informações


método expressão de
consulta C#

Tudo Determina se todos os elementos Não aplicável. Enumerable.All


em uma sequência satisfazem uma Queryable.All
condição.

Qualquer Determina se todos os elementos Não aplicável. Enumerable.Any


em uma sequência satisfazem uma Queryable.Any
condição.
Nome do Descrição Sintaxe de Mais informações
método expressão de
consulta C#

Contém Determina se uma sequência Não aplicável. Enumerable.Contains


contém um elemento especificado. Queryable.Contains

Tudo
O exemplo a seguir usa o All para localizar alunos que obtiveram pontuação acima de
70 em todos os exames.

C#

IEnumerable<string> names = from student in students


where student.Scores.All(score => score > 70)
select $"{student.FirstName} {student.LastName}:
{string.Join(", ", student.Scores.Select(s => s.ToString()))}";

foreach (string name in names)


{
Console.WriteLine($"{name}");
}

// This code produces the following output:


//
// Cesar Garcia: 71, 86, 77, 97
// Nancy Engström: 75, 73, 78, 83
// Ifunanya Ugomma: 84, 82, 96, 80

Qualquer
O exemplo a seguir usa Any para encontrar alunos com pontuação superior a 95 em
qualquer exame.

C#

IEnumerable<string> names = from student in students


where student.Scores.Any(score => score > 95)
select $"{student.FirstName} {student.LastName}:
{student.Scores.Max()}";

foreach (string name in names)


{
Console.WriteLine($"{name}");
}
// This code produces the following output:
//
// Svetlana Omelchenko: 97
// Cesar Garcia: 97
// Debra Garcia: 96
// Ifeanacho Jamuike: 98
// Ifunanya Ugomma: 96
// Michelle Caruana: 97
// Nwanneka Ifeoma: 98
// Martina Mattsson: 96
// Anastasiya Sazonova: 96
// Jesper Jakobsson: 98
// Max Lindgren: 96

Contém
O exemplo a seguir usa o Contains para encontrar alunos que obtiveram exatamente 95
pontos em um exame.

C#

IEnumerable<string> names = from student in students


where student.Scores.Contains(95)
select $"{student.FirstName} {student.LastName}:
{string.Join(", ", student.Scores.Select(s => s.ToString()))}";

foreach (string name in names)


{
Console.WriteLine($"{name}");
}

// This code produces the following output:


//
// Claire O'Donnell: 56, 78, 95, 95
// Donald Urquhart: 92, 90, 95, 57

Confira também
System.Linq
Especificar filtros de predicado dinamicamente em tempo de execução
Como consultar sentenças que contenham um conjunto especificado de palavras
(LINQ) (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
Como particionar dados (C#)
Artigo • 07/06/2024

Particionamento em LINQ refere-se à operação de dividir uma sequência de entrada em


duas seções sem reorganizar os elementos e, depois, retornar uma das seções.

) Importante

Esses exemplos usam uma fonte de dados


System.Collections.Generic.IEnumerable<T>. Fontes de dados baseadas em
System.Linq.IQueryProvider usam as fontes de dados System.Linq.IQueryable<T>
e as árvores de expressão. As árvores de expressão possuem limitações na sintaxe
C# permitida. Além disso, todas as fontes de dados IQueryProvider , como EF Core,
podem impor mais restrições. Verifique a documentação da fonte de dados.

A ilustração a seguir mostra os resultados de três operações de particionamento


diferentes em uma sequência de caracteres. A primeira operação retorna os três
primeiros elementos na sequência. A segunda operação ignora os três primeiros
elementos e retorna os elementos restantes. A terceira operação ignora os dois
primeiros elementos na sequência e retorna os três elementos seguintes.

Os métodos de operador de consulta padrão que particionam sequências estão listados


na seção a seguir.

Operadores
ノ Expandir a tabela
Nomes de Descrição Sintaxe de Mais informações
método expressão de
consulta em C#

Ignorar Ignora elementos até uma posição Não aplicável. Enumerable.Skip


especificada na sequência. Queryable.Skip

SkipWhile Ignora elementos com base em uma Não aplicável. Enumerable.SkipWhile


função de predicado até que um Queryable.SkipWhile
elemento não satisfaça a condição.

Take Aceita elementos até uma posição Não aplicável. Enumerable.Take


especificada na sequência. Queryable.Take

TakeWhile Obtém elementos com base em Não aplicável. Enumerable.TakeWhile


uma função de predicado até que Queryable.TakeWhile
um elemento não satisfaça a
condição.

Chunk Divide os elementos de uma Não aplicável. Enumerable.Chunk


sequência em partes com tamanho Queryable.Chunk
máximo especificado.

Todos os exemplos a seguir usam Enumerable.Range(Int32, Int32) para gerar uma


sequência de números de 0 a 7.

Use o método Take para obter apenas os primeiros elementos de uma sequência:

C#

foreach (int number in Enumerable.Range(0, 8).Take(3))


{
Console.WriteLine(number);
}
// This code produces the following output:
// 0
// 1
// 2

O método Skip é usado para ignorar os primeiros elementos de uma sequência e usar
os elementos restantes:

C#

foreach (int number in Enumerable.Range(0, 8).Skip(3))


{
Console.WriteLine(number);
}
// This code produces the following output:
// 3
// 4
// 5
// 6
// 7

Os métodos TakeWhile e SkipWhile também pegam e ignorar elementos em uma


sequência. No entanto, em vez de um número fixo de elementos, esses métodos
ignoram ou selecionam elementos com base em uma condição. TakeWhile pega os
elementos de uma sequência até que um elemento não corresponda à condição de
correspondência.

C#

foreach (int number in Enumerable.Range(0, 8).TakeWhile(n => n < 5))


{
Console.WriteLine(number);
}
// This code produces the following output:
// 0
// 1
// 2
// 3
// 4

SkipWhile ignora os primeiros elementos, desde que a condição seja verdadeira. O

primeiro elemento que não corresponder à condição e todos os elementos


subsequentes serão retornados.

C#

foreach (int number in Enumerable.Range(0, 8).SkipWhile(n => n < 5))


{
Console.WriteLine(number);
}
// This code produces the following output:
// 5
// 6
// 7

O operador Chunk é usado para dividir elementos de uma sequência com base em
determinado size .

C#

int chunkNumber = 1;
foreach (int[] chunk in Enumerable.Range(0, 8).Chunk(3))
{
Console.WriteLine($"Chunk {chunkNumber++}:");
foreach (int item in chunk)
{
Console.WriteLine($" {item}");
}

Console.WriteLine();
}
// This code produces the following output:
// Chunk 1:
// 0
// 1
// 2
//
//Chunk 2:
// 3
// 4
// 5
//
//Chunk 3:
// 6
// 7

O código anterior do C#:

Depende de Enumerable.Range(Int32, Int32) para gerar uma sequência de


números.
Aplica o operador Chunk , dividindo a sequência em partes com tamanho máximo
igual a três.

Confira também
System.Linq
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
Convertendo Tipos de Dados (C#)
Artigo • 31/10/2024

Os métodos de conversão alteram o tipo dos objetos de entrada.

) Importante

Esses exemplos usam uma fonte de dados


System.Collections.Generic.IEnumerable<T>. Fontes de dados baseadas em
System.Linq.IQueryProvider usam as fontes de dados System.Linq.IQueryable<T>
e as árvores de expressão. As árvores de expressão possuem limitações na sintaxe
C# permitida. Além disso, todas as fontes de dados IQueryProvider , como EF Core,
podem impor mais restrições. Verifique a documentação da fonte de dados.

As operações de conversão em consultas LINQ são úteis em diversos aplicativos. A


seguir estão alguns exemplos:

O método Enumerable.AsEnumerable pode ser usado para ocultar a


implementação personalizada de um tipo de um operador de consulta padrão.
O método Enumerable.OfType pode ser usado para habilitar coleções sem
parâmetros para consulta LINQ.
Os métodos Enumerable.ToArray, Enumerable.ToDictionary, Enumerable.ToList e
Enumerable.ToLookup podem ser usados para forçar a execução de consulta
imediata em vez de adiá-la até que a consulta seja enumerada.

Métodos
A tabela a seguir lista os métodos de operador de consulta padrão que realizam
conversões de tipo de dados.

Os métodos de conversão nesta tabela cujos nomes começam com "As" alteram o tipo
estático da coleção de origem, mas não a enumeram. Os métodos cujos nomes
começam com "To" enumeram a coleção de origem e colocam os itens na coleção de
tipo correspondente.

ノ Expandir a tabela
Nome do Descrição Sintaxe de Mais informações
método expressão de
consulta C#

AsEnumerable Retorna a entrada digitada como Não aplicável. Enumerable.AsEnumerable


IEnumerable<T>.

AsQueryable Converte um IEnumerable Não aplicável. Queryable.AsQueryable


(genérico) em um IQueryable
(genérico).

Conversão Converte os elementos de uma Use uma Enumerable.Cast


coleção em um tipo especificado. variável de
intervalo de Queryable.Cast
tipo explícito.
Por exemplo:

from string
str in words

OfType Filtra valores, dependendo da Não aplicável. Enumerable.OfType


capacidade de serem convertidos
em um tipo especificado. Queryable.OfType

ToArray Converte uma coleção em uma Não aplicável. Enumerable.ToArray


matriz. Esse método força a
execução de consulta.

ToDictionary Coloca os elementos em um Não aplicável. Enumerable.ToDictionary


Dictionary<TKey,TValue> com
base em uma função de seletor de
chave. Esse método força a
execução de consulta.

ToList Converte uma coleção em um Não aplicável. Enumerable.ToList


List<T>. Esse método força a
execução de consulta.

ToLookup Coloca os elementos em um Não aplicável. Enumerable.ToLookup


Lookup<TKey,TElement> (um
dicionário one-to-many) com base
em uma função de seletor de
chave. Esse método força a
execução de consulta.

7 Observação

Os exemplos a seguir neste artigo usam as fontes de dados comuns para essa área.
Cada Student tem um nível de escolaridade, um departamento primário e uma
série de pontuações. Um Teacher também tem uma propriedade City que
identifica o campus onde o docente ministra aulas. A Department tem um nome e
uma referência a um Teacher que atua como chefe do departamento.
Você pode encontrar o conjunto de dados de exemplo no repositório de origem.

C#

public enum GradeLevel


{
FirstYear = 1,
SecondYear,
ThirdYear,
FourthYear
};

public class Student


{
public required string FirstName { get; init; }
public required string LastName { get; init; }
public required int ID { get; init; }

public required GradeLevel Year { get; init; }


public required List<int> Scores { get; init; }

public required int DepartmentID { get; init; }


}

public class Teacher


{
public required string First { get; init; }
public required string Last { get; init; }
public required int ID { get; init; }
public required string City { get; init; }
}

public class Department


{
public required string Name { get; init; }
public int ID { get; init; }

public required int TeacherID { get; init; }


}

Exemplo de sintaxe de expressão de consulta


O exemplo de código a seguir usa uma variável de intervalo de tipo explícito para
converter um tipo em um subtipo antes de acessar um membro que está disponível
somente no subtipo.
C#

IEnumerable people = students;

var query = from Student student in students


where student.Year == GradeLevel.ThirdYear
select student;

foreach (Student student in query)


{
Console.WriteLine(student.FirstName);
}

A consulta equivalente pode ser expressa usando a sintaxe do método, conforme


mostrado no seguinte exemplo:

C#

IEnumerable people = students;

var query = people


.Cast<Student>()
.Where(student => student.Year == GradeLevel.ThirdYear);

foreach (Student student in query)


{
Console.WriteLine(student.FirstName);
}

Confira também
System.Linq
Cláusula From
Join Operações em LINQ
Artigo • 15/10/2024

Uma join de duas fontes de dados é a associação de objetos em uma fonte de dados,
com objetos que compartilham um atributo comum em outra fonte de dados.

) Importante

Esses exemplos usam uma fonte de dados


System.Collections.Generic.IEnumerable<T>. Fontes de dados baseadas em
System.Linq.IQueryProvider usam as fontes de dados System.Linq.IQueryable<T>
e as árvores de expressão. As árvores de expressão possuem limitações na sintaxe
C# permitida. Além disso, todas as fontes de dados IQueryProvider , como EF Core,
podem impor mais restrições. Verifique a documentação da fonte de dados.

A Junção é uma operação importante em consultas que têm como alvo fontes de dados
cujas relações entre si não podem ser seguidas diretamente. Na programação orientada
a objetos, a união pode significar uma correlação entre objetos que não é modelada,
como a direção inversa de um relacionamento unidirecional. Um exemplo de
relacionamento unilateral é uma classe Student que possui uma propriedade do tipo
Department que representa o principal, mas a classe Department não possui uma

propriedade que seja uma coleção de objetos Student . Se você tem uma lista de
objetos Department e você quer encontrar todos os clientes em cada cidade, você pode
usar uma operação join para encontrá-los.

Os métodos join fornecidos na estrutura do LINQ são Join e GroupJoin. Esses métodos
executam junção por igualdade ou junções que correspondem duas fontes de dados
com base na igualdade de suas chaves. Para comparação, o Transact-SQL oferece
suporte a operadores join diferentes de equals , por exemplo, do operador less than .
Em termos de banco de dados relacional, Join implementa um join interno, um tipo de
join no qual apenas os objetos que têm uma correspondência no outro conjunto de
dados são retornados. O método GroupJoin não tem equivalente direto em termos de
banco de dados relacional, mas ele implementa um superconjunto de junções internas e
junções externas esquerdas. Uma join externa esquerda é uma join que retorna cada
elemento da primeira fonte de dados (esquerda), mesmo que ele não tenha elementos
correlacionados na outra fonte de dados.

A ilustração a seguir mostra uma visão conceitual de dois conjuntos e os elementos


dentro desses conjuntos que estão incluídos em uma join interna ou join externa à
esquerda.
Métodos
ノ Expandir a tabela

Nome do Descrição Sintaxe de Mais informações


método expressão de
consulta C#

Join Une duas sequências com base nas join … in … on Enumerable.Join


funções de seletor de chave e extrai … equals …
pares de valores. Queryable.Join

GroupJoin Une duas sequências baseadas em join … in … on Enumerable.GroupJoin


funções de seletor de chave e agrupa … equals … into
as correspondências resultantes para … Queryable.GroupJoin
cada elemento.

Os exemplos a seguir neste artigo usam fontes de dados comuns para esta área:

C#

public enum GradeLevel


{
FirstYear = 1,
SecondYear,
ThirdYear,
FourthYear
};

public class Student


{
public required string FirstName { get; init; }
public required string LastName { get; init; }
public required int ID { get; init; }

public required GradeLevel Year { get; init; }


public required List<int> Scores { get; init; }
public required int DepartmentID { get; init; }
}

public class Teacher


{
public required string First { get; init; }
public required string Last { get; init; }
public required int ID { get; init; }
public required string City { get; init; }
}
public class Department
{
public required string Name { get; init; }
public int ID { get; init; }

public required int TeacherID { get; init; }


}

Cada Student tem um nível de escolaridade, um departamento primário e uma série de


pontuações. Um Teacher também tem uma propriedade City que identifica o campus
onde o docente ministra aulas. A Department tem um nome e uma referência a um
Teacher que atua como chefe do departamento.

O exemplo a seguir usa a cláusula join … in … on … equals … para join duas sequências
com base em um valor específico:

C#

var query = from student in students


join department in departments on student.DepartmentID equals
department.ID
select new { Name = $"{student.FirstName} {student.LastName}",
DepartmentName = department.Name };

foreach (var item in query)


{
Console.WriteLine($"{item.Name} - {item.DepartmentName}");
}

A consulta anterior pode ser expressa usando a sintaxe do método conforme mostrado
no código a seguir:

C#

var query = students.Join(departments,


student => student.DepartmentID, department => department.ID,
(student, department) => new { Name = $"{student.FirstName}
{student.LastName}", DepartmentName = department.Name });
foreach (var item in query)
{
Console.WriteLine($"{item.Name} - {item.DepartmentName}");
}

O exemplo a seguir usa a cláusula join … in … on … equals … into … para join duas
sequências com base em um valor específico e agrupa as correspondências resultantes
para cada elemento:

C#

IEnumerable<IEnumerable<Student>> studentGroups = from department in


departments
join student in students on department.ID equals
student.DepartmentID into studentGroup
select studentGroup;

foreach (IEnumerable<Student> studentGroup in studentGroups)


{
Console.WriteLine("Group");
foreach (Student student in studentGroup)
{
Console.WriteLine($" - {student.FirstName}, {student.LastName}");
}
}

A consulta anterior pode ser expressa usando a sintaxe do método conforme mostrado
no exemplo a seguir:

C#

// Join department and student based on DepartmentId and grouping result


IEnumerable<IEnumerable<Student>> studentGroups =
departments.GroupJoin(students,
department => department.ID, student => student.DepartmentID,
(department, studentGroup) => studentGroup);

foreach (IEnumerable<Student> studentGroup in studentGroups)


{
Console.WriteLine("Group");
foreach (Student student in studentGroup)
{
Console.WriteLine($" - {student.FirstName}, {student.LastName}");
}
}

Executar junções internas


Em termos de banco de dados, uma relacionais join interna produz um conjunto de
resultados no qual cada elemento da primeira coleção aparece uma vez para todo
elemento correspondente na segunda coleção. Se um elemento da primeira coleção
não tiver elementos correspondentes, ele não aparecerá no conjunto de resultados. O
método Join, que é chamado pela cláusula join no C#, implementa uma join interna.
Os exemplos a seguir mostram como executar quatro variações de uma join interna:

Uma join interna simples que correlaciona os elementos de duas fontes de dados
com base em uma chave simples.
Uma join interna simples que correlaciona os elementos de duas fontes de dados
com base em uma chave composta. Uma chave composta, que é uma chave que
consiste em mais de um valor, permite que você correlacione os elementos com
base em mais de uma propriedade.
Uma múltipla join na qual operações join sucessivas são acrescentadas umas às
outras.
Uma join interna que é implementada por meio de uma junção de grupo join.

Chave única join


O exemplo a seguir combina Teacher objetos com Deparment objetos cujo TeacherId
corresponde a esse Teacher . A cláusula select em C# define a aparência dos objetos
resultantes. No exemplo a seguir, os objetos resultantes são tipos anônimos que
consistem no nome do departamento e no nome do professor que lidera o
departamento.

C#

var query = from department in departments


join teacher in teachers on department.TeacherID equals
teacher.ID
select new
{
DepartmentName = department.Name,
TeacherName = $"{teacher.First} {teacher.Last}"
};

foreach (var departmentAndTeacher in query)


{
Console.WriteLine($"{departmentAndTeacher.DepartmentName} is managed by
{departmentAndTeacher.TeacherName}");
}

Você obtém os mesmos resultados usando a sintaxe do método Join:

C#
var query = teachers
.Join(departments, teacher => teacher.ID, department =>
department.TeacherID,
(teacher, department) =>
new { DepartmentName = department.Name, TeacherName = $"
{teacher.First} {teacher.Last}" });

foreach (var departmentAndTeacher in query)


{
Console.WriteLine($"{departmentAndTeacher.DepartmentName} is managed by
{departmentAndTeacher.TeacherName}");
}

Os professores que não são chefes de departamento não aparecem nos resultados
finais.

Chave composta join


Em vez de correlacionar os elementos com base em apenas uma propriedade, você
pode usar uma chave composta para comparar elementos com base em várias
propriedades. Especifique a função de seletor de chave para cada coleção para retornar
um tipo anônimo que consiste nas propriedades que você deseja comparar. Se você
rotular as propriedades, elas devem ter o mesmo rótulo no tipo anônimo cada chave. As
propriedades também devem aparecer na mesma ordem.

O exemplo a seguir usa uma lista de objetos Teacher e uma lista de objetos Student
para determinar quais professores também são alunos. Ambos os tipos possuem
propriedades que representam o nome e o sobrenome de cada pessoa. As funções que
criam as chaves join dos elementos de cada lista retornam um tipo anônimo que
consiste nas propriedades. A operação join compara essas chaves compostas quanto à
igualdade e retorna pares de objetos de cada lista em que o nome e o sobrenome
correspondem.

C#

// Join the two data sources based on a composite key consisting of first
and last name,
// to determine which employees are also students.
IEnumerable<string> query =
from teacher in teachers
join student in students on new
{
FirstName = teacher.First,
LastName = teacher.Last
} equals new
{
student.FirstName,
student.LastName
}
select teacher.First + " " + teacher.Last;

string result = "The following people are both teachers and students:\r\n";
foreach (string name in query)
{
result += $"{name}\r\n";
}
Console.Write(result);

Você pode usar o método Join, conforme mostrado no exemplo a seguir:

C#

IEnumerable<string> query = teachers


.Join(students,
teacher => new { FirstName = teacher.First, LastName = teacher.Last
},
student => new { student.FirstName, student.LastName },
(teacher, student) => $"{teacher.First} {teacher.Last}"
);

Console.WriteLine("The following people are both teachers and students:");


foreach (string name in query)
{
Console.WriteLine(name);
}

Múltiplo join
Qualquer quantidade de operações join pode ser acrescentado uma a outra para
realizar uma join múltipla. Cada cláusula join em C# correlaciona uma fonte de dados
especificada com os resultados da join anterior.

A primeira cláusula join corresponde a alunos e departamentos com base em um


Student objeto DepartmentID correspondente a um Department objeto ID . Ele retorna

uma sequência de tipos anônimos que contêm o objeto Student e o objeto Department .

A segunda cláusula join correlaciona os tipos anônimos retornados pelo primeiro join
com objetos Teacher baseados na ID desse professor que correspondem à ID do chefe
do departamento. Ele retorna uma sequência de tipos anônimos que contém o nome
do aluno, o nome do departamento e o nome do líder do departamento. Como esta é
uma operação interna, join apenas os objetos da primeira fonte de dados que têm uma
correspondência na segunda fonte de dados são retornados.
C#

// The first join matches Department.ID and Student.DepartmentID from the


list of students and
// departments, based on a common ID. The second join matches teachers who
lead departments
// with the students studying in that department.
var query = from student in students
join department in departments on student.DepartmentID equals
department.ID
join teacher in teachers on department.TeacherID equals teacher.ID
select new {
StudentName = $"{student.FirstName} {student.LastName}",
DepartmentName = department.Name,
TeacherName = $"{teacher.First} {teacher.Last}"
};

foreach (var obj in query)


{
Console.WriteLine($"""The student "{obj.StudentName}" studies in the
department run by "{obj.TeacherName}".""");
}

O equivalente usando vários métodos Join usa a mesma abordagem com o tipo
anônimo:

C#

var query = students


.Join(departments, student => student.DepartmentID, department =>
department.ID,
(student, department) => new { student, department })
.Join(teachers, commonDepartment =>
commonDepartment.department.TeacherID, teacher => teacher.ID,
(commonDepartment, teacher) => new
{
StudentName = $"{commonDepartment.student.FirstName}
{commonDepartment.student.LastName}",
DepartmentName = commonDepartment.department.Name,
TeacherName = $"{teacher.First} {teacher.Last}"
});

foreach (var obj in query)


{
Console.WriteLine($"""The student "{obj.StudentName}" studies in the
department run by "{obj.TeacherName}".""");
}

join interna usando join agrupada


O exemplo a seguir mostra como implementar uma join interna usando um grupo join.
A lista de objetos Department é unida ao grupo à lista de objetos Student com base na
Department.ID correspondência com a propriedade Student.DepartmentID . O grupo join

cria uma coleção de grupos intermediários, em que cada grupo é composto por um
objeto Department e uma sequência de objetos Student correspondentes. A segunda
cláusula from combina (ou nivela) esta sequência de sequências em uma sequência
mais longa. A cláusula select especifica o tipo de elementos na sequência final. Esse
tipo é anônimo que consiste no nome do aluno e no nome do departamento
correspondente.

C#

var query1 =
from department in departments
join student in students on department.ID equals student.DepartmentID
into gj
from subStudent in gj
select new
{
DepartmentName = department.Name,
StudentName = $"{subStudent.FirstName} {subStudent.LastName}"
};
Console.WriteLine("Inner join using GroupJoin():");
foreach (var v in query1)
{
Console.WriteLine($"{v.DepartmentName} - {v.StudentName}");
}

Os mesmos resultados podem ser obtidos usando o método GroupJoin da seguinte


forma:

C#

var queryMethod1 = departments


.GroupJoin(students, department => department.ID, student =>
student.DepartmentID,
(department, gj) => new { department, gj })
.SelectMany(departmentAndStudent => departmentAndStudent.gj,
(departmentAndStudent, subStudent) => new
{
DepartmentName = departmentAndStudent.department.Name,
StudentName = $"{subStudent.FirstName} {subStudent.LastName}"
});

Console.WriteLine("Inner join using GroupJoin():");


foreach (var v in queryMethod1)
{
Console.WriteLine($"{v.DepartmentName} - {v.StudentName}");
}

O resultado é equivalente ao conjunto de resultados que seria obtido usando a cláusula


join sem a cláusula into para realizar uma join interna. O código a seguir demonstra

esta consulta equivalente:

C#

var query2 = from department in departments


join student in students on department.ID equals student.DepartmentID
select new
{
DepartmentName = department.Name,
StudentName = $"{student.FirstName} {student.LastName}"
};

Console.WriteLine("The equivalent operation using Join():");


foreach (var v in query2)
{
Console.WriteLine($"{v.DepartmentName} - {v.StudentName}");
}

Para evitar o encadeamento, o método Join único pode ser utilizado conforme
apresentado aqui:

C#

var queryMethod2 = departments.Join(students, departments => departments.ID,


student => student.DepartmentID,
(department, student) => new
{
DepartmentName = department.Name,
StudentName = $"{student.FirstName} {student.LastName}"
});

Console.WriteLine("The equivalent operation using Join():");


foreach (var v in queryMethod2)
{
Console.WriteLine($"{v.DepartmentName} - {v.StudentName}");
}

Executar junções agrupadas


O grupo join é útil para a produção de estruturas de dados hierárquicos. Ela combina
cada elemento da primeira coleção com um conjunto de elementos correlacionados da
segunda coleção.
7 Observação

Cada elemento da primeira coleção aparece no conjunto de resultados de uma


junção de grupo, join independentemente de se os elementos correlacionados
encontram-se na segunda coleção. Caso nenhum elemento correlacionado seja
encontrado, a sequência de elementos correlacionados desse elemento ficará vazia.
O seletor de resultado, portanto, tem acesso a todos os elementos da primeira
coleção. Isso difere do seletor de resultado de um não é de grupo, join que não
pode acessar os elementos da primeira coleção que não têm correspondência na
segunda coleção.

2 Aviso

Enumerable.GroupJoin não tem equivalente direto em termos de banco de dados


relacionais tradicionais. No entanto, esse método implementa um superconjunto
de junções internas e junções externas à esquerda. Ambas as operações podem ser
gravadas em termos de uma junção agrupada join. Para obter mais informações,
veja Entity Framework Core, GroupJoin.

O primeiro exemplo neste artigo mostra como executar uma junção de grupo join. O
segundo exemplo mostra como usar uma junção de grupo join para criar elementos
XML.

Agrupar join
O exemplo a seguir realiza uma junção de grupo join de objetos do tipo Department e
Student com base em Department.ID correspondente à Student.DepartmentID

propriedade. Ao contrário de uma junção que não é de grupo join, que produziria um
par de elementos para cada correspondência, a junção de grupo join produz apenas um
objeto resultante para cada elemento da primeira coleção, que neste exemplo é um
Department objeto. Os elementos correspondentes da segunda coleção, que neste

exemplo são objetos Student , são agrupados em uma coleção. Por fim, a função de
seletor de resultado cria um tipo anônimo para cada correspondência que consiste em
Department.Name e em uma coleção de objetos Student .

C#

var query = from department in departments


join student in students on department.ID equals student.DepartmentID
into studentGroup
select new
{
DepartmentName = department.Name,
Students = studentGroup
};

foreach (var v in query)


{
// Output the department's name.
Console.WriteLine($"{v.DepartmentName}:");

// Output each of the students in that department.


foreach (Student? student in v.Students)
{
Console.WriteLine($" {student.FirstName} {student.LastName}");
}
}

No exemplo acima, a variável query contém a consulta que cria uma lista onde cada
elemento é do tipo anônimo que contém o nome do departamento e uma coleção de
alunos que estudam naquele departamento.

A consulta equivalente usando a sintaxe do método é mostrada no código a seguir:

C#

var query = departments.GroupJoin(students, department => department.ID,


student => student.DepartmentID,
(department, Students) => new { DepartmentName = department.Name,
Students });

foreach (var v in query)


{
// Output the department's name.
Console.WriteLine($"{v.DepartmentName}:");

// Output each of the students in that department.


foreach (Student? student in v.Students)
{
Console.WriteLine($" {student.FirstName} {student.LastName}");
}
}

Grupo join para criar um XML


As junções de grupo são ideais para a criação de XML usando o LINQ to XML. O
exemplo a seguir é semelhante ao exemplo anterior, exceto que em vez de criar tipos
anônimos, a função de seletor de resultado cria elementos XML que representam os
objetos associados.
C#

XElement departmentsAndStudents = new("DepartmentEnrollment",


from department in departments
join student in students on department.ID equals student.DepartmentID
into studentGroup
select new XElement("Department",
new XAttribute("Name", department.Name),
from student in studentGroup
select new XElement("Student",
new XAttribute("FirstName", student.FirstName),
new XAttribute("LastName", student.LastName)
)
)
);

Console.WriteLine(departmentsAndStudents);

A consulta equivalente usando a sintaxe do método é mostrada no código a seguir:

C#

XElement departmentsAndStudents = new("DepartmentEnrollment",


departments.GroupJoin(students, department => department.ID, student =>
student.DepartmentID,
(department, Students) => new XElement("Department",
new XAttribute("Name", department.Name),
from student in Students
select new XElement("Student",
new XAttribute("FirstName", student.FirstName),
new XAttribute("LastName", student.LastName)
)
)
)
);

Console.WriteLine(departmentsAndStudents);

Executar junções externas esquerdas


Uma junção externa esquerda join é uma join junção em que cada elemento da primeira
coleção é retornado, mesmo que ele tenha elementos correlacionados na segunda
coleção. É possível usar o LINQ para executar uma junção join externa esquerda
chamando o método DefaultIfEmpty nos resultados de uma junção de grupo join.

O exemplo a seguir demonstra como usar o método DefaultIfEmpty nos resultados de


uma junção de grupo join para executar uma junção externa esquerda join.
A primeira etapa da produção de uma junção externa esquerda join de duas coleções é
executar uma junção interna join usando uma junção de grupo join. (Consulte Executar
junções internas para obter uma explicação desse processo.) Nesse exemplo, a lista de
Department objetos é unida internamente à lista de Student objetos com base no ID de

um Department objeto que corresponde ao ID do aluno DepartmentID .

A segunda etapa é incluir cada elemento da primeira coleção (esquerda) no conjunto de


resultados, mesmo que esse elemento não tenha nenhuma correspondência na coleção
direita. Isso é feito chamando DefaultIfEmpty em cada sequência de elementos
correspondentes da junção de grupo join. Neste exemplo, DefaultIfEmpty é chamado
em cada sequência de objetos Student correspondentes. O método retorna uma
coleção que contém um único valor padrão se a sequência de objetos Student
correspondentes estiver vazia para qualquer objeto Department , garantindo que cada
objeto Department seja representado na coleção de resultados.

7 Observação

O valor padrão para um tipo de referência é null ; portanto, o exemplo procura


uma referência nula antes de acessar cada elemento de cada coleção Student .

C#

var query =
from student in students
join department in departments on student.DepartmentID equals
department.ID into gj
from subgroup in gj.DefaultIfEmpty()
select new
{
student.FirstName,
student.LastName,
Department = subgroup?.Name ?? string.Empty
};

foreach (var v in query)


{
Console.WriteLine($"{v.FirstName:-15} {v.LastName:-15}:
{v.Department}");
}

A consulta equivalente usando a sintaxe do método é mostrada no código a seguir:

C#
var query = students.GroupJoin(departments, student => student.DepartmentID,
department => department.ID,
(student, departmentList) => new { student, subgroup =
departmentList.AsQueryable() })
.SelectMany(joinedSet => joinedSet.subgroup.DefaultIfEmpty(), (student,
department) => new
{
student.student.FirstName,
student.student.LastName,
Department = department.Name
});

foreach (var v in query)


{
Console.WriteLine($"{v.FirstName:-15} {v.LastName:-15}:
{v.Department}");
}

Confira também
Join
GroupJoin
Tipos anônimos
Formular junções e consultas entre produtos
join cláusula
Cláusula group
Como unir conteúdo de join a partir de arquivos diferentes (LINQ) (C#)
Como preencher coleções de objetos de várias fontes (LINQ) (C#)
Agrupando dados (C#)
Artigo • 07/06/2024

O agrupamento refere-se à operação de colocação de dados em grupos, de modo que


os elementos em cada grupo compartilhem um atributo comum. A ilustração a seguir
mostra os resultados do agrupamento de uma sequência de caracteres. A chave para
cada grupo é o caractere.

) Importante

Esses exemplos usam uma fonte de dados


System.Collections.Generic.IEnumerable<T>. Fontes de dados baseadas em
System.Linq.IQueryProvider usam as fontes de dados System.Linq.IQueryable<T>
e as árvores de expressão. As árvores de expressão possuem limitações na sintaxe
C# permitida. Além disso, todas as fontes de dados IQueryProvider , como EF Core,
podem impor mais restrições. Verifique a documentação da fonte de dados.

Os métodos do operador de consulta padrão que agrupam elementos de dados estão


listados na tabela a seguir.

ノ Expandir a tabela

Nome do Descrição Sintaxe de Mais informações


método expressão de
consulta C#

GroupBy Agrupa elementos que compartilham um group … by Enumerable.GroupBy


atributo comum. Um objeto
IGrouping<TKey,TElement> representa -ou- Queryable.GroupBy
cada grupo.
group … by …
into …
Nome do Descrição Sintaxe de Mais informações
método expressão de
consulta C#

ToLookup Insere os elementos em um Não aplicável. Enumerable.ToLookup


Lookup<TKey,TElement> (um dicionário
one-to-many) com base em uma função
de seletor de chave.

O exemplo de código a seguir usa a cláusula group by para agrupar números inteiros
em uma lista de acordo com o fato de serem pares ou ímpares.

C#

List<int> numbers = [35, 44, 200, 84, 3987, 4, 199, 329, 446, 208];

IEnumerable<IGrouping<int, int>> query = from number in numbers


group number by number % 2;

foreach (var group in query)


{
Console.WriteLine(group.Key == 0 ? "\nEven numbers:" : "\nOdd
numbers:");
foreach (int i in group)
{
Console.WriteLine(i);
}
}

A consulta equivalente usando a sintaxe do método é mostrada no código a seguir:

C#

List<int> numbers = [35, 44, 200, 84, 3987, 4, 199, 329, 446, 208];

IEnumerable<IGrouping<int, int>> query = numbers


.GroupBy(number => number % 2);

foreach (var group in query)


{
Console.WriteLine(group.Key == 0 ? "\nEven numbers:" : "\nOdd
numbers:");
foreach (int i in group)
{
Console.WriteLine(i);
}
}

Os exemplos a seguir neste artigo usam fontes de dados comuns para esta área:
C#

public enum GradeLevel


{
FirstYear = 1,
SecondYear,
ThirdYear,
FourthYear
};

public class Student


{
public required string FirstName { get; init; }
public required string LastName { get; init; }
public required int ID { get; init; }

public required GradeLevel Year { get; init; }


public required List<int> Scores { get; init; }

public required int DepartmentID { get; init; }


}

public class Teacher


{
public required string First { get; init; }
public required string Last { get; init; }
public required int ID { get; init; }
public required string City { get; init; }
}
public class Department
{
public required string Name { get; init; }
public int ID { get; init; }

public required int TeacherID { get; init; }


}

Cada Student tem um nível de escolaridade, um departamento primário e uma série de


pontuações. Um Teacher também tem uma propriedade City que identifica o campus
onde o docente ministra aulas. A Department tem um nome e uma referência a um
Teacher que atua como chefe do departamento.

Agrupar resultados de consultas


O agrupamento é um dos recursos mais poderosos do LINQ. Os exemplos a seguir
mostram como agrupar dados de várias maneiras:

Por uma única propriedade.


Pela primeira letra de uma propriedade de cadeia de caracteres.
Por um intervalo numérico calculado.
Por predicado booliano ou outra expressão.
Por uma chave composta.

Além disso, as duas últimas consultas projetam seus resultados em um novo tipo
anônimo que contém somente o primeiro nome e sobrenome do estudante. Para obter
mais informações, consulte a cláusula group.

Exemplo de agrupamento por propriedade única


O exemplo a seguir mostra como agrupar elementos de origem usando uma única
propriedade do elemento como a chave de grupo. A chave é um enum , que é o ano do
estudante na escola. A operação de agrupamento usa o comparador de igualdade
padrão para o tipo.

C#

var groupByYearQuery =
from student in students
group student by student.Year into newGroup
orderby newGroup.Key
select newGroup;

foreach (var yearGroup in groupByYearQuery)


{
Console.WriteLine($"Key: {yearGroup.Key}");
foreach (var student in yearGroup)
{
Console.WriteLine($"\t{student.LastName}, {student.FirstName}");
}
}

O código equivalente usando a sintaxe do método é mostrada no exemplo a seguir:

C#

// Variable groupByLastNamesQuery is an IEnumerable<IGrouping<string,


// DataClass.Student>>.
var groupByYearQuery = students
.GroupBy(student => student.Year)
.OrderBy(newGroup => newGroup.Key);

foreach (var yearGroup in groupByYearQuery)


{
Console.WriteLine($"Key: {yearGroup.Key}");
foreach (var student in yearGroup)
{
Console.WriteLine($"\t{student.LastName}, {student.FirstName}");
}
}

Exemplo de agrupamento por valor


O exemplo a seguir mostra como agrupar elementos de origem usando algo diferente
de uma propriedade do objeto para a chave de grupo. Neste exemplo, a chave é a
primeira letra do sobrenome do estudante.

C#

var groupByFirstLetterQuery =
from student in students
let firstLetter = student.LastName[0]
group student by firstLetter;

foreach (var studentGroup in groupByFirstLetterQuery)


{
Console.WriteLine($"Key: {studentGroup.Key}");
foreach (var student in studentGroup)
{
Console.WriteLine($"\t{student.LastName}, {student.FirstName}");
}
}

O foreach aninhado é necessário para acessar itens de grupo.

O código equivalente usando a sintaxe do método é mostrada no exemplo a seguir:

C#

var groupByFirstLetterQuery = students


.GroupBy(student => student.LastName[0]);

foreach (var studentGroup in groupByFirstLetterQuery)


{
Console.WriteLine($"Key: {studentGroup.Key}");
foreach (var student in studentGroup)
{
Console.WriteLine($"\t{student.LastName}, {student.FirstName}");
}
}

Exemplo de agrupamento por intervalo


O exemplo a seguir mostra como agrupar elementos de origem usando um intervalo
numérico como a chave de grupo. Em seguida, a consulta, projeta os resultados em um
tipo anônimo que contém apenas o nome e o sobrenome e o intervalo de percentil ao
qual o estudante pertence. Um tipo anônimo é usado porque não é necessário usar o
objeto Student completo para exibir os resultados. GetPercentile é uma função auxiliar
que calcula um percentil com base na pontuação média do aluno. O método retorna um
número inteiro entre 0 e 10.

C#

static int GetPercentile(Student s)


{
double avg = s.Scores.Average();
return avg > 0 ? (int)avg / 10 : 0;
}

var groupByPercentileQuery =
from student in students
let percentile = GetPercentile(student)
group new
{
student.FirstName,
student.LastName
} by percentile into percentGroup
orderby percentGroup.Key
select percentGroup;

foreach (var studentGroup in groupByPercentileQuery)


{
Console.WriteLine($"Key: {studentGroup.Key * 10}");
foreach (var item in studentGroup)
{
Console.WriteLine($"\t{item.LastName}, {item.FirstName}");
}
}

O foreach aninhado é necessário para iterar em grupos e itens de grupo. O código


equivalente usando a sintaxe do método é mostrada no exemplo a seguir:

C#

static int GetPercentile(Student s)


{
double avg = s.Scores.Average();
return avg > 0 ? (int)avg / 10 : 0;
}

var groupByPercentileQuery = students


.Select(student => new { student, percentile = GetPercentile(student) })
.GroupBy(student => student.percentile)
.Select(percentGroup => new
{
percentGroup.Key,
Students = percentGroup.Select(s => new { s.student.FirstName,
s.student.LastName })
})
.OrderBy(percentGroup => percentGroup.Key);

foreach (var studentGroup in groupByPercentileQuery)


{
Console.WriteLine($"Key: {studentGroup.Key * 10}");
foreach (var item in studentGroup.Students)
{
Console.WriteLine($"\t{item.LastName}, {item.FirstName}");
}
}

Exemplo de agrupamento por comparação


O exemplo a seguir mostra como agrupar elementos de origem usando uma expressão
de comparação booliana. Neste exemplo, a expressão booliana testa se a pontuação
média de provas do aluno é maior que 75. Como nos exemplos anteriores, os resultados
são projetados em um tipo anônimo porque o elemento de origem completo não é
necessário. As propriedades no tipo anônimo tornam-se propriedades no membro Key .

C#

var groupByHighAverageQuery =
from student in students
group new
{
student.FirstName,
student.LastName
} by student.Scores.Average() > 75 into studentGroup
select studentGroup;

foreach (var studentGroup in groupByHighAverageQuery)


{
Console.WriteLine($"Key: {studentGroup.Key}");
foreach (var student in studentGroup)
{
Console.WriteLine($"\t{student.FirstName} {student.LastName}");
}
}

A consulta equivalente usando a sintaxe do método é mostrada no código a seguir:

C#

var groupByHighAverageQuery = students


.GroupBy(student => student.Scores.Average() > 75)
.Select(group => new
{
group.Key,
Students = group.AsEnumerable().Select(s => new { s.FirstName,
s.LastName })
});

foreach (var studentGroup in groupByHighAverageQuery)


{
Console.WriteLine($"Key: {studentGroup.Key}");
foreach (var student in studentGroup.Students)
{
Console.WriteLine($"\t{student.FirstName} {student.LastName}");
}
}

Exemplo de agrupamento por tipo anônimo


O exemplo a seguir mostra como usar um tipo anônimo para encapsular uma chave que
contém vários valores. Neste exemplo, o primeiro valor da chave é a primeira letra do
sobrenome do estudante. O segundo valor da chave é um booliano que especifica se o
aluno tirou mais que 85 na primeira prova. Você pode ordenar os grupos por qualquer
propriedade na chave.

C#

var groupByCompoundKey =
from student in students
group student by new
{
FirstLetterOfLastName = student.LastName[0],
IsScoreOver85 = student.Scores[0] > 85
} into studentGroup
orderby studentGroup.Key.FirstLetterOfLastName
select studentGroup;

foreach (var scoreGroup in groupByCompoundKey)


{
var s = scoreGroup.Key.IsScoreOver85 ? "more than 85" : "less than 85";
Console.WriteLine($"Name starts with
{scoreGroup.Key.FirstLetterOfLastName} who scored {s}");
foreach (var item in scoreGroup)
{
Console.WriteLine($"\t{item.FirstName} {item.LastName}");
}
}

A consulta equivalente usando a sintaxe do método é mostrada no código a seguir:

C#
var groupByCompoundKey = students
.GroupBy(student => new
{
FirstLetterOfLastName = student.LastName[0],
IsScoreOver85 = student.Scores[0] > 85
})
.OrderBy(studentGroup => studentGroup.Key.FirstLetterOfLastName);

foreach (var scoreGroup in groupByCompoundKey)


{
var s = scoreGroup.Key.IsScoreOver85 ? "more than 85" : "less than 85";
Console.WriteLine($"Name starts with
{scoreGroup.Key.FirstLetterOfLastName} who scored {s}");
foreach (var item in scoreGroup)
{
Console.WriteLine($"\t{item.FirstName} {item.LastName}");
}
}

Criar um grupo aninhado


O exemplo a seguir mostra como criar grupos aninhados em uma expressão de consulta
LINQ. Cada grupo que é criado de acordo com o ano do aluno ou nível de ensino, é
subdividido em grupos com base nos nomes das pessoas.

C#

var nestedGroupsQuery =
from student in students
group student by student.Year into newGroup1
from newGroup2 in
from student in newGroup1
group student by student.LastName
group newGroup2 by newGroup1.Key;

foreach (var outerGroup in nestedGroupsQuery)


{
Console.WriteLine($"DataClass.Student Level = {outerGroup.Key}");
foreach (var innerGroup in outerGroup)
{
Console.WriteLine($"\tNames that begin with: {innerGroup.Key}");
foreach (var innerGroupElement in innerGroup)
{
Console.WriteLine($"\t\t{innerGroupElement.LastName}
{innerGroupElement.FirstName}");
}
}
}
Os três loops foreach aninhados são necessários para iterar sobre os elementos
internos de um grupo aninhado.
(Passe o cursor do mouse sobre as variáveis de iteração outerGroup , innerGroup e
innerGroupElement para ver o tipo real delas).

A consulta equivalente usando a sintaxe do método é mostrada no código a seguir:

C#

var nestedGroupsQuery =
students
.GroupBy(student => student.Year)
.Select(newGroup1 => new
{
newGroup1.Key,
NestedGroup = newGroup1
.GroupBy(student => student.LastName)
});

foreach (var outerGroup in nestedGroupsQuery)


{
Console.WriteLine($"DataClass.Student Level = {outerGroup.Key}");
foreach (var innerGroup in outerGroup.NestedGroup)
{
Console.WriteLine($"\tNames that begin with: {innerGroup.Key}");
foreach (var innerGroupElement in innerGroup)
{
Console.WriteLine($"\t\t{innerGroupElement.LastName}
{innerGroupElement.FirstName}");
}
}
}

Executar uma subconsulta em uma operação


de agrupamento
Este artigo mostra duas maneiras diferentes de criar uma consulta que ordena os dados
de origem em grupos e, em seguida, realiza uma subconsulta em cada grupo
individualmente. A técnica básica em cada exemplo é agrupar os elementos de origem
usando uma continuação chamada newGroup e, em seguida, gerar uma nova
subconsulta de newGroup . Essa subconsulta é executada em cada novo grupo criado
pela consulta externa. Nesse exemplo específico, a saída final não é um grupo, mas uma
sequência simples de tipos anônimos.

Para obter mais informações sobre como agrupar, consulte Cláusula group. Para obter
mais informações sobre continuações, consulte into. O exemplo a seguir usa uma
estrutura de dados na memória como a fonte de dados, mas os mesmos princípios se
aplicam a qualquer tipo de fonte de dados do LINQ.

C#

var queryGroupMax =
from student in students
group student by student.Year into studentGroup
select new
{
Level = studentGroup.Key,
HighestScore = (
from student2 in studentGroup
select student2.Scores.Average()
).Max()
};

var count = queryGroupMax.Count();


Console.WriteLine($"Number of groups = {count}");

foreach (var item in queryGroupMax)


{
Console.WriteLine($" {item.Level} Highest Score={item.HighestScore}");
}

A consulta no trecho anterior acima também pode ser escrita usando a sintaxe do
método. O snippet de código a seguir tem uma consulta semanticamente equivalente
escrita usando a sintaxe de método.

C#

var queryGroupMax =
students
.GroupBy(student => student.Year)
.Select(studentGroup => new
{
Level = studentGroup.Key,
HighestScore = studentGroup.Max(student2 =>
student2.Scores.Average())
});

var count = queryGroupMax.Count();


Console.WriteLine($"Number of groups = {count}");

foreach (var item in queryGroupMax)


{
Console.WriteLine($" {item.Level} Highest Score={item.HighestScore}");
}
Confira também
System.Linq
GroupBy
IGrouping<TKey,TElement>
Cláusula group
Como dividir um arquivo em vários arquivos usando grupos (LINQ) (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
Como usar LINQ para consultar arquivos
e diretórios
Artigo • 02/05/2024

Muitas operações do sistema de arquivos são essencialmente consultas e, portanto, são


ideais para a abordagem do LINQ. Essas consultas não são indestrutivas. Elas não
alteram o conteúdo dos arquivos ou pastas originais. As consultas não devem causar
efeitos colaterais. Em geral, qualquer código (incluindo consultas que executam
operações criar / atualizar / excluir) que modifique dados de origem deve ser mantido
separado do código que apenas consulta os dados.

Há certa complexidade envolvida na criação de uma fonte de dados que representa o


conteúdo do sistema de arquivos com precisão e trata exceções de maneira elegante.
Os exemplos nesta seção criam uma coleção de instantâneos de objetos FileInfo que
representa todos os arquivos em uma pasta raiz especificada e todas as suas subpastas.
O estado real de cada FileInfo pode ser alterado no tempo entre o momento em que
você começa e termina a execução de uma consulta. Por exemplo, você pode criar uma
lista de objetos FileInfo para usar como uma fonte de dados. Se você tentar acessar a
propriedade Length em uma consulta, o objeto FileInfo tentará acessar o sistema de
arquivos para atualizar o valor de Length . Se o arquivo não existir, você obterá uma
FileNotFoundException em sua consulta, embora não esteja consultando diretamente o
sistema de arquivos.

Como consultar arquivos com um atributo ou


nome especificado
Este exemplo mostra como localizar todos os arquivos que têm uma extensão de nome
de arquivo especificada (por exemplo ".txt") em uma árvore de diretório especificada.
Ele também mostra como retornar tanto os arquivos mais recentes como os mais antigo
na árvore com base na hora de criação. Talvez seja necessário modificar a primeira linha
de muitos dos exemplos, independentemente de você estar executando esse código no
Windows, Mac ou em um sistema Linux.

C#

string startFolder = """C:\Program Files\dotnet\sdk""";


// Or
// string startFolder = "/usr/local/share/dotnet/sdk";

DirectoryInfo dir = new DirectoryInfo(startFolder);


var fileList = dir.GetFiles("*.*", SearchOption.AllDirectories);

var fileQuery = from file in fileList


where file.Extension == ".txt"
orderby file.Name
select file;

// Uncomment this block to see the full query


// foreach (FileInfo fi in fileQuery)
// {
// Console.WriteLine(fi.FullName);
// }

var newestFile = (from file in fileQuery


orderby file.CreationTime
select new { file.FullName, file.CreationTime })
.Last();

Console.WriteLine($"\r\nThe newest .txt file is {newestFile.FullName}.


Creation time: {newestFile.CreationTime}");

Como agrupar arquivos por extensão


Este exemplo mostra como o LINQ pode ser usado para realizar operações avançadas
de classificação e agrupamento em listas de arquivos ou pastas. Ele também mostra
como paginar a saída na janela do console usando os métodos Skip e Take.

A consulta a seguir mostra como agrupar o conteúdo de uma árvore de diretórios


especificada, pela extensão de nome de arquivo.

C#

string startFolder = """C:\Program Files\dotnet\sdk""";


// Or
// string startFolder = "/usr/local/share/dotnet/sdk";

int trimLength = startFolder.Length;

DirectoryInfo dir = new DirectoryInfo(startFolder);

var fileList = dir.GetFiles("*.*", SearchOption.AllDirectories);

var queryGroupByExt = from file in fileList


group file by file.Extension.ToLower() into fileGroup
orderby fileGroup.Count(), fileGroup.Key
select fileGroup;

// Iterate through the outer collection of groups.


foreach (var filegroup in queryGroupByExt.Take(5))
{
Console.WriteLine($"Extension: {filegroup.Key}");
var resultPage = filegroup.Take(20);

//Execute the resultPage query


foreach (var f in resultPage)
{
Console.WriteLine($"\t{f.FullName.Substring(trimLength)}");
}
Console.WriteLine();
}

A saída desse programa pode ser longa, dependendo dos detalhes do sistema de
arquivos local e o que está definido em startFolder . Para habilitar a exibição de todos
os resultados, este exemplo mostra como paginá-los. Um loop de foreach aninhado é
necessário porque cada grupo é enumerado separadamente.

Como consultar para detectar o número total


de bytes em um conjunto de pastas
Este exemplo mostra como recuperar o número total de bytes usado por todos os
arquivos em uma pasta especificada e todas as suas subpastas. O método Sum adiciona
os valores de todos os itens selecionados na cláusula select . Você pode modificar essa
consulta para recuperar o maior ou o menor arquivo na árvore de diretório especificada
chamando o método Min ou Max em vez de Sum.

C#

string startFolder = """C:\Program Files\dotnet\sdk""";


// Or
// string startFolder = "/usr/local/share/dotnet/sdk";

var fileList = Directory.GetFiles(startFolder, "*.*",


SearchOption.AllDirectories);

var fileQuery = from file in fileList


let fileLen = new FileInfo(file).Length
where fileLen > 0
select fileLen;

// Cache the results to avoid multiple trips to the file system.


long[] fileLengths = fileQuery.ToArray();

// Return the size of the largest file


long largestFile = fileLengths.Max();

// Return the total number of bytes in all the files under the specified
folder.
long totalBytes = fileLengths.Sum();
Console.WriteLine($"There are {totalBytes} bytes in {fileList.Count()} files
under {startFolder}");
Console.WriteLine($"The largest file is {largestFile} bytes.");

Este exemplo estende o exemplo anterior para fazer o seguinte:

Como recuperar o tamanho em bytes do maior arquivo.


Como recuperar o tamanho em bytes do menor arquivo.
Como recuperar o maior ou menor arquivo do objeto FileInfo de uma ou mais
pastas em uma pasta raiz especificada.
Como recuperar uma sequência, como os 10 maiores arquivos.
Como ordenar os arquivos em grupos com base no tamanho do arquivo em bytes,
ignorando arquivos menores do que um tamanho especificado.

O exemplo a seguir contém cinco consultas separadas que mostram como consultar e
agrupar arquivos, dependendo do tamanho do arquivo em bytes. Você pode modificar
esses exemplos para basear a consulta em outra propriedade do objeto FileInfo.

C#

// Return the FileInfo object for the largest file


// by sorting and selecting from beginning of list
FileInfo longestFile = (from file in fileList
let fileInfo = new FileInfo(file)
where fileInfo.Length > 0
orderby fileInfo.Length descending
select fileInfo
).First();

Console.WriteLine($"The largest file under {startFolder} is


{longestFile.FullName} with a length of {longestFile.Length} bytes");

//Return the FileInfo of the smallest file


FileInfo smallestFile = (from file in fileList
let fileInfo = new FileInfo(file)
where fileInfo.Length > 0
orderby fileInfo.Length ascending
select fileInfo
).First();

Console.WriteLine($"The smallest file under {startFolder} is


{smallestFile.FullName} with a length of {smallestFile.Length} bytes");

//Return the FileInfos for the 10 largest files


var queryTenLargest = (from file in fileList
let fileInfo = new FileInfo(file)
let len = fileInfo.Length
orderby len descending
select fileInfo
).Take(10);

Console.WriteLine($"The 10 largest files under {startFolder} are:");

foreach (var v in queryTenLargest)


{
Console.WriteLine($"{v.FullName}: {v.Length} bytes");
}

// Group the files according to their size, leaving out


// files that are less than 200000 bytes.
var querySizeGroups = from file in fileList
let fileInfo = new FileInfo(file)
let len = fileInfo.Length
where len > 0
group fileInfo by (len / 100000) into fileGroup
where fileGroup.Key >= 2
orderby fileGroup.Key descending
select fileGroup;

foreach (var filegroup in querySizeGroups)


{
Console.WriteLine($"{filegroup.Key}00000");
foreach (var item in filegroup)
{
Console.WriteLine($"\t{item.Name}: {item.Length}");
}
}

Para retornar um ou mais objetos FileInfo completos, a consulta deve primeiro examinar
cada um dos objetos na fonte de dados e, em seguida, classificá-los segundo o valor de
sua propriedade Length. Em seguida, ela pode retornar um único elemento ou a
sequência com os maiores tamanhos. Use First para retornar o primeiro elemento em
uma lista. Use Take para retornar o primeiro número n de elementos. Especifique uma
ordem de classificação decrescente para colocar os menores elementos no início da
lista.

Como consultar para detectar arquivos


duplicados em uma árvore de diretório
Às vezes, arquivos que têm o mesmo nome podem ser localizados em mais de uma
pasta. Este exemplo mostra como consultar esses nomes de arquivos duplicados sob
uma pasta raiz especificada. O segundo exemplo mostra como consultar arquivos cujo
tamanho e os tempos LastWrite também são semelhantes.

C#
string startFolder = """C:\Program Files\dotnet\sdk""";
// Or
// string startFolder = "/usr/local/share/dotnet/sdk";

DirectoryInfo dir = new DirectoryInfo(startFolder);

IEnumerable<FileInfo> fileList = dir.GetFiles("*.*",


SearchOption.AllDirectories);

// used in WriteLine to keep the lines shorter


int charsToSkip = startFolder.Length;

// var can be used for convenience with groups.


var queryDupNames = from file in fileList
group file.FullName.Substring(charsToSkip) by file.Name
into fileGroup
where fileGroup.Count() > 1
select fileGroup;

foreach (var queryDup in queryDupNames.Take(20))


{
Console.WriteLine($"Filename = {(queryDup.Key.ToString() == string.Empty
? "[none]" : queryDup.Key.ToString())}");

foreach (var fileName in queryDup.Take(10))


{
Console.WriteLine($"\t{fileName}");
}
}

A primeira consulta usa uma chave para determinar uma correspondência. Ela localiza
arquivos que têm o mesmo nome, mas cujo conteúdo pode ser diferente. A segunda
consulta usa uma chave composta para comparar em relação a três propriedades do
objeto FileInfo. É muito mais provável que essa consulta localize arquivos que têm o
mesmo nome e conteúdo semelhante ou idêntico.

C#

string startFolder = """C:\Program Files\dotnet\sdk""";


// Or
// string startFolder = "/usr/local/share/dotnet/sdk";

// Make the lines shorter for the console display


int charsToSkip = startFolder.Length;

// Take a snapshot of the file system.


DirectoryInfo dir = new DirectoryInfo(startFolder);
IEnumerable<FileInfo> fileList = dir.GetFiles("*.*",
SearchOption.AllDirectories);

// Note the use of a compound key. Files that match


// all three properties belong to the same group.
// A named type is used to enable the query to be
// passed to another method. Anonymous types can also be used
// for composite keys but cannot be passed across method boundaries
//
var queryDupFiles = from file in fileList
group file.FullName.Substring(charsToSkip) by
(Name: file.Name, LastWriteTime: file.LastWriteTime,
Length: file.Length )
into fileGroup
where fileGroup.Count() > 1
select fileGroup;

foreach (var queryDup in queryDupFiles.Take(20))


{
Console.WriteLine($"Filename = {(queryDup.Key.ToString() ==
string.Empty ? "[none]" : queryDup.Key.ToString())}");

foreach (var fileName in queryDup)


{
Console.WriteLine($"\t{fileName}");
}
}
}

Como consultar o conteúdo de arquivos de


texto em uma pasta
Este exemplo mostra como consultar todos os arquivos em uma árvore de diretório
especificada, abrir cada arquivo e inspecionar seu conteúdo. Este tipo de técnica pode
ser usado para criar índices ou inverter os índices do conteúdo de uma árvore de
diretório. Uma pesquisa de cadeia de caracteres simples é executada neste exemplo. No
entanto, os tipos de correspondência de padrões mais complexos podem ser
executados com uma expressão regular.

C#

string startFolder = """C:\Program Files\dotnet\sdk""";


// Or
// string startFolder = "/usr/local/share/dotnet/sdk";

DirectoryInfo dir = new DirectoryInfo(startFolder);

var fileList = dir.GetFiles("*.*", SearchOption.AllDirectories);

string searchTerm = "change";

var queryMatchingFiles = from file in fileList


where file.Extension == ".txt"
let fileText = File.ReadAllText(file.FullName)
where fileText.Contains(searchTerm)
select file.FullName;

// Execute the query.


Console.WriteLine($"""The term "{searchTerm}" was found in:""");
foreach (string filename in queryMatchingFiles)
{
Console.WriteLine(filename);
}

Como comparar o conteúdo de duas pastas


Este exemplo demonstra três modos de se comparar duas listagens de arquivo:

Consultando um valor booliano que especifica se as duas listas de arquivos são


idênticas.
Consultando a interseção para recuperar os arquivos que estão em ambas as
pastas.
Consultando a diferença de conjunto para recuperar os arquivos que estão em
uma pasta, mas não na outra.

As técnicas mostradas aqui podem ser adaptadas para comparar sequências de objetos
de qualquer tipo.

A classe FileComparer mostrada aqui demonstra como usar uma classe de comparação
personalizada junto com operadores de consulta padrão. A classe não se destina ao uso
em cenários do mundo real. Ela apenas utiliza o nome e o comprimento em bytes de
cada arquivo para determinar se o conteúdo de cada pasta é idêntico ou não. Em um
cenário do mundo real, você deve modificar esse comparador para executar uma
verificação mais rigorosa de igualdade.

C#

// This implementation defines a very simple comparison


// between two FileInfo objects. It only compares the name
// of the files being compared and their length in bytes.
class FileCompare : IEqualityComparer<FileInfo>
{
public bool Equals(FileInfo? f1, FileInfo? f2)
{
return (f1?.Name == f2?.Name &&
f1?.Length == f2?.Length);
}

// Return a hash that reflects the comparison criteria. According to the


// rules for IEqualityComparer<T>, if Equals is true, then the hash
codes must
// also be equal. Because equality as defined here is a simple value
equality, not
// reference identity, it is possible that two or more objects will
produce the same
// hash code.
public int GetHashCode(FileInfo fi)
{
string s = $"{fi.Name}{fi.Length}";
return s.GetHashCode();
}
}

public static void CompareDirectories()


{
string pathA = """C:\Program Files\dotnet\sdk\8.0.104""";
string pathB = """C:\Program Files\dotnet\sdk\8.0.204""";

DirectoryInfo dir1 = new DirectoryInfo(pathA);


DirectoryInfo dir2 = new DirectoryInfo(pathB);

IEnumerable<FileInfo> list1 = dir1.GetFiles("*.*",


SearchOption.AllDirectories);
IEnumerable<FileInfo> list2 = dir2.GetFiles("*.*",
SearchOption.AllDirectories);

//A custom file comparer defined below


FileCompare myFileCompare = new FileCompare();

// This query determines whether the two folders contain


// identical file lists, based on the custom file comparer
// that is defined in the FileCompare class.
// The query executes immediately because it returns a bool.
bool areIdentical = list1.SequenceEqual(list2, myFileCompare);

if (areIdentical == true)
{
Console.WriteLine("the two folders are the same");
}
else
{
Console.WriteLine("The two folders are not the same");
}

// Find the common files. It produces a sequence and doesn't


// execute until the foreach statement.
var queryCommonFiles = list1.Intersect(list2, myFileCompare);

if (queryCommonFiles.Any())
{
Console.WriteLine($"The following files are in both folders (total
number = {queryCommonFiles.Count()}):");
foreach (var v in queryCommonFiles.Take(10))
{
Console.WriteLine(v.Name); //shows which items end up in result
list
}
}
else
{
Console.WriteLine("There are no common files in the two folders.");
}

// Find the set difference between the two folders.


var queryList1Only = (from file in list1
select file)
.Except(list2, myFileCompare);

Console.WriteLine();
Console.WriteLine($"The following files are in list1 but not list2
(total number = {queryList1Only.Count()}):");
foreach (var v in queryList1Only.Take(10))
{
Console.WriteLine(v.FullName);
}

var queryList2Only = (from file in list2


select file)
.Except(list1, myFileCompare);

Console.WriteLine();
Console.WriteLine($"The following files are in list2 but not list1
(total number = {queryList2Only.Count()}:");
foreach (var v in queryList2Only.Take(10))
{
Console.WriteLine(v.FullName);
}
}

Como reorganizar os campos de um arquivo


delimitado
Um CSV (arquivo de valores separados por vírgula) é um arquivo de texto que é
frequentemente usado para armazenar dados de planilha ou outros dados de tabela
representados por linhas e colunas. Ao usar o método Split para separar os campos, é
fácil consultar e manipular arquivos CSV usando LINQ. Na verdade, a mesma técnica
pode ser usada para reordenar as partes de qualquer linha estruturada de texto. Ela não
é limitada a arquivos CSV.

No exemplo a seguir, suponha que as três colunas representam o “sobrenome”, o


“nome” e a “ID” dos alunos. Os campos estão em ordem alfabética com base nos
sobrenomes dos alunos. A consulta gera uma nova sequência, na qual a coluna ID é
exibida em primeiro, seguida por uma segunda coluna que combina o nome e o
sobrenome do aluno. As linhas são reordenadas acordo com o campo ID. Os resultados
são salvos em um novo arquivo e os dados originais não são modificados. O texto a
seguir mostra o conteúdo do arquivo spreadsheet1.csv usado no exemplo a seguir:

txt

Adams,Terry,120
Fakhouri,Fadi,116
Feng,Hanying,117
Garcia,Cesar,114
Garcia,Debra,115
Garcia,Hugo,118
Mortensen,Sven,113
O'Donnell,Claire,112
Omelchenko,Svetlana,111
Tucker,Lance,119
Tucker,Michael,122
Zabokritski,Eugene,121

O código a seguir lê o arquivo de origem e reorganiza cada coluna no arquivo CSV para
reorganizar a ordem das colunas:

C#

string[] lines = File.ReadAllLines("spreadsheet1.csv");

// Create the query. Put field 2 first, then


// reverse and combine fields 0 and 1 from the old field
IEnumerable<string> query = from line in lines
let fields = line.Split(',')
orderby fields[2]
select $"{fields[2]}, {fields[1]} {fields[0]}";

File.WriteAllLines("spreadsheet2.csv", query.ToArray());

/* Output to spreadsheet2.csv:
111, Svetlana Omelchenko
112, Claire O'Donnell
113, Sven Mortensen
114, Cesar Garcia
115, Debra Garcia
116, Fadi Fakhouri
117, Hanying Feng
118, Hugo Garcia
119, Lance Tucker
120, Terry Adams
121, Eugene Zabokritski
122, Michael Tucker
*/
Como dividir um arquivo em vários arquivos
usando grupos
Este exemplo mostra uma maneira de mesclar o conteúdo de dois arquivos e, em
seguida, criar um conjunto de novos arquivos que organizam os dados em uma nova
forma. A consulta usa o conteúdo de dois arquivos. O texto a seguir mostra o conteúdo
do primeiro arquivo, names1.txt:

txt

Bankov, Peter
Holm, Michael
Garcia, Hugo
Potra, Cristina
Noriega, Fabricio
Aw, Kam Foo
Beebe, Ann
Toyoshima, Tim
Guy, Wey Yuan
Garcia, Debra

O segundo arquivo, names2.txt, contém um conjunto diferente de nomes, alguns dos


quais estão em comum com o primeiro conjunto:

txt

Liu, Jinghao
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Gilchrist, Beth
Myrcha, Jacek
Giakoumakis, Leo
McLin, Nkenge
El Yassir, Mehdi

O código a seguir consulta ambos os arquivos, usa a união de ambos os arquivos e


grava um novo arquivo para cada grupo, definido pela primeira letra do sobrenome:

C#

string[] fileA = File.ReadAllLines("names1.txt");


string[] fileB = File.ReadAllLines("names2.txt");

// Concatenate and remove duplicate names


var mergeQuery = fileA.Union(fileB);
// Group the names by the first letter in the last name.
var groupQuery = from name in mergeQuery
let n = name.Split(',')[0]
group name by n[0] into g
orderby g.Key
select g;

foreach (var g in groupQuery)


{
string fileName = $"testFile_{g.Key}.txt";

Console.WriteLine(g.Key);

using StreamWriter sw = new StreamWriter(fileName);


foreach (var item in g)
{
sw.WriteLine(item);
// Output to console for example purposes.
Console.WriteLine($" {item}");
}
}
/* Output:
A
Aw, Kam Foo
B
Bankov, Peter
Beebe, Ann
E
El Yassir, Mehdi
G
Garcia, Hugo
Guy, Wey Yuan
Garcia, Debra
Gilchrist, Beth
Giakoumakis, Leo
H
Holm, Michael
L
Liu, Jinghao
M
Myrcha, Jacek
McLin, Nkenge
N
Noriega, Fabricio
P
Potra, Cristina
T
Toyoshima, Tim
*/
Como unir conteúdo proveniente de arquivos
diferentes
Este exemplo mostra como unir dados de dois arquivos delimitados por vírgulas que
compartilham um valor comum que é usado como uma chave correspondente. Essa
técnica pode ser útil se você precisa combinar dados de duas planilhas ou de uma
planilha e um arquivo com outro formato, em um novo arquivo. Você pode modificar o
exemplo para funcionar com qualquer tipo de texto estruturado.

O texto a seguir mostra o conteúdo de scores.csv. O arquivo representa dados da


planilha. A coluna 1 é a ID do aluno e as colunas 2 a 5 são resultados de testes.

txt

111, 97, 92, 81, 60


112, 75, 84, 91, 39
113, 88, 94, 65, 91
114, 97, 89, 85, 82
115, 35, 72, 91, 70
116, 99, 86, 90, 94
117, 93, 92, 80, 87
118, 92, 90, 83, 78
119, 68, 79, 88, 92
120, 99, 82, 81, 79
121, 96, 85, 91, 60
122, 94, 92, 91, 91

O texto a seguir mostra o conteúdo de names.csv. O arquivo representa uma planilha


que contém o sobrenome, o nome e a ID do aluno.

txt

Omelchenko,Svetlana,111
O'Donnell,Claire,112
Mortensen,Sven,113
Garcia,Cesar,114
Garcia,Debra,115
Fakhouri,Fadi,116
Feng,Hanying,117
Garcia,Hugo,118
Tucker,Lance,119
Adams,Terry,120
Zabokritski,Eugene,121
Tucker,Michael,122

Junte o conteúdo de arquivos diferentes que contêm informações relacionadas. O


names.csv de arquivo contém o nome do aluno mais um número de ID. O scores.csv de
arquivo contém a ID e um conjunto de quatro pontuações de teste. A consulta a seguir
une as pontuações aos nomes dos alunos usando a ID como uma chave
correspondente. O código é mostrado no exemplo a seguir:

C#

string[] names = File.ReadAllLines(@"names.csv");


string[] scores = File.ReadAllLines(@"scores.csv");

var scoreQuery = from name in names


let nameFields = name.Split(',')
from id in scores
let scoreFields = id.Split(',')
where Convert.ToInt32(nameFields[2]) ==
Convert.ToInt32(scoreFields[0])
select $"{nameFields[0]},{scoreFields[1]},
{scoreFields[2]},{scoreFields[3]},{scoreFields[4]}";

Console.WriteLine("\r\nMerge two spreadsheets:");


foreach (string item in scoreQuery)
{
Console.WriteLine(item);
}
Console.WriteLine("{0} total names in list", scoreQuery.Count());
/* Output:
Merge two spreadsheets:
Omelchenko, 97, 92, 81, 60
O'Donnell, 75, 84, 91, 39
Mortensen, 88, 94, 65, 91
Garcia, 97, 89, 85, 82
Garcia, 35, 72, 91, 70
Fakhouri, 99, 86, 90, 94
Feng, 93, 92, 80, 87
Garcia, 92, 90, 83, 78
Tucker, 68, 79, 88, 92
Adams, 99, 82, 81, 79
Zabokritski, 96, 85, 91, 60
Tucker, 94, 92, 91, 91
12 total names in list
*/

Como computar valores de colunas em um


arquivo de texto CSV
Este exemplo mostra como executar cálculos de agregação, como soma, média, mín. e
máx. nas colunas de um arquivo .csv. Os princípios de exemplo mostrados aqui podem
ser aplicados a outros tipos de texto estruturado.
O texto a seguir mostra o conteúdo de scores.csv. Suponha que a primeira coluna
representa uma ID do aluno e as colunas subsequentes representam as notas de quatro
provas.

txt

111, 97, 92, 81, 60


112, 75, 84, 91, 39
113, 88, 94, 65, 91
114, 97, 89, 85, 82
115, 35, 72, 91, 70
116, 99, 86, 90, 94
117, 93, 92, 80, 87
118, 92, 90, 83, 78
119, 68, 79, 88, 92
120, 99, 82, 81, 79
121, 96, 85, 91, 60
122, 94, 92, 91, 91

O texto a seguir mostra como usar o método Split para converter cada linha de texto
em uma matriz. Cada elemento da matriz representa uma coluna. Por fim, o texto em
cada coluna é convertido em sua representação numérica.

C#

public class SumColumns


{
public static void SumCSVColumns(string fileName)
{
string[] lines = File.ReadAllLines(fileName);

// Specifies the column to compute.


int exam = 3;

// Spreadsheet format:
// Student ID Exam#1 Exam#2 Exam#3 Exam#4
// 111, 97, 92, 81, 60

// Add one to exam to skip over the first column,


// which holds the student ID.
SingleColumn(lines, exam + 1);
Console.WriteLine();
MultiColumns(lines);
}

static void SingleColumn(IEnumerable<string> strs, int examNum)


{
Console.WriteLine("Single Column Query:");

// Parameter examNum specifies the column to


// run the calculations on. This value could be
// passed in dynamically at run time.

// Variable columnQuery is an IEnumerable<int>.


// The following query performs two steps:
// 1) use Split to break each row (a string) into an array
// of strings,
// 2) convert the element at position examNum to an int
// and select it.
var columnQuery = from line in strs
let elements = line.Split(',')
select Convert.ToInt32(elements[examNum]);

// Execute the query and cache the results to improve


// performance. This is helpful only with very large files.
var results = columnQuery.ToList();

// Perform aggregate calculations Average, Max, and


// Min on the column specified by examNum.
double average = results.Average();
int max = results.Max();
int min = results.Min();

Console.WriteLine($"Exam #{examNum}: Average:{average:##.##} High


Score:{max} Low Score:{min}");
}

static void MultiColumns(IEnumerable<string> strs)


{
Console.WriteLine("Multi Column Query:");

// Create a query, multiColQuery. Explicit typing is used


// to make clear that, when executed, multiColQuery produces
// nested sequences. However, you get the same results by
// using 'var'.

// The multiColQuery query performs the following steps:


// 1) use Split to break each row (a string) into an array
// of strings,
// 2) use Skip to skip the "Student ID" column, and store the
// rest of the row in scores.
// 3) convert each score in the current row from a string to
// an int, and select that entire sequence as one row
// in the results.
var multiColQuery = from line in strs
let elements = line.Split(',')
let scores = elements.Skip(1)
select (from str in scores
select Convert.ToInt32(str));

// Execute the query and cache the results to improve


// performance.
// ToArray could be used instead of ToList.
var results = multiColQuery.ToList();

// Find out how many columns you have in results.


int columnCount = results[0].Count();

// Perform aggregate calculations Average, Max, and


// Min on each column.
// Perform one iteration of the loop for each column
// of scores.
// You can use a for loop instead of a foreach loop
// because you already executed the multiColQuery
// query by calling ToList.
for (int column = 0; column < columnCount; column++)
{
var results2 = from row in results
select row.ElementAt(column);
double average = results2.Average();
int max = results2.Max();
int min = results2.Min();

// Add one to column because the first exam is Exam #1,


// not Exam #0.
Console.WriteLine($"Exam #{column + 1} Average: {average:##.##}
High Score: {max} Low Score: {min}");
}
}
}
/* Output:
Single Column Query:
Exam #4: Average:76.92 High Score:94 Low Score:39

Multi Column Query:


Exam #1 Average: 86.08 High Score: 99 Low Score: 35
Exam #2 Average: 86.42 High Score: 94 Low Score: 72
Exam #3 Average: 84.75 High Score: 91 Low Score: 65
Exam #4 Average: 76.92 High Score: 94 Low Score: 39
*/

Se o arquivo for um arquivo separado por tabulações, é só atualizar o argumento no


método Split para \t .

6 Colaborar conosco no
GitHub
A fonte deste conteúdo pode
ser encontrada no GitHub, onde
você também pode criar e
revisar problemas e solicitações
de pull. Para obter mais
informações, confira o nosso
guia para colaboradores.
Comentários do .NET
O .NET é um projeto código aberto.
Selecione um link para fornecer
comentários:

 Abrir um problema de
documentação

 Fornecer comentários sobre o


produto
Como usar LINQ para consultar cadeias
de caracteres
Artigo • 02/05/2024

As cadeias de caracteres são armazenadas como uma sequência de caracteres. Como


uma sequência de caracteres, eles podem ser consultados usando LINQ. Neste artigo,
há várias consultas de exemplo que consultam cadeias de caracteres ou palavras
diferentes, filtram cadeias de caracteres ou misturam consultas com expressões
regulares.

Como consultar para detectar caracteres em


uma cadeia de caracteres
O exemplo a seguir consulta uma cadeia de caracteres para determinar quantos dígitos
numéricos ela contém.

C#

string aString = "ABCDE99F-J74-12-89A";

// Select only those characters that are numbers


var stringQuery = from ch in aString
where Char.IsDigit(ch)
select ch;

// Execute the query


foreach (char c in stringQuery)
Console.Write(c + " ");

// Call the Count method on the existing query.


int count = stringQuery.Count();
Console.WriteLine($"Count = {count}");

// Select all characters before the first '-'


var stringQuery2 = aString.TakeWhile(c => c != '-');

// Execute the second query


foreach (char c in stringQuery2)
Console.Write(c);
/* Output:
Output: 9 9 7 4 1 2 8 9
Count = 8
ABCDE99F
*/
A consulta anterior mostra como você pode tratar uma cadeia de caracteres como uma
sequência de caracteres.

Como contar ocorrências de uma palavra em


uma cadeia de caracteres
Este exemplo a seguir mostra como usar uma consulta LINQ para contar as ocorrências
de uma palavra especificada em uma cadeia de caracteres. Para executar a contagem,
primeiro o método Split é chamado para criar uma matriz de palavras. Há um custo de
desempenho para o método Split. Se for a única operação na cadeia de caracteres for
contar as palavras, considere o uso dos métodos Matches ou IndexOf em vez dele.

C#

string text = """


Historically, the world of data and the world of objects
have not been well integrated. Programmers work in C# or Visual Basic
and also in SQL or XQuery. On the one side are concepts such as classes,
objects, fields, inheritance, and .NET APIs. On the other side
are tables, columns, rows, nodes, and separate languages for dealing
with
them. Data types often require translation between the two worlds; there
are
different standard functions. Because the object world has no notion of
query, a
query can only be represented as a string without compile-time type
checking or
IntelliSense support in the IDE. Transferring data from SQL tables or
XML trees to
objects in memory is often tedious and error-prone.
""";

string searchTerm = "data";

//Convert the string into an array of words


char[] separators = ['.', '?', '!', ' ', ';', ':', ','];
string[] source = text.Split(separators,
StringSplitOptions.RemoveEmptyEntries);

// Create the query. Use the InvariantCultureIgnoreCase comparison to match


"data" and "Data"
var matchQuery = from word in source
where word.Equals(searchTerm,
StringComparison.InvariantCultureIgnoreCase)
select word;

// Count the matches, which executes the query.


int wordCount = matchQuery.Count();
Console.WriteLine($"""{wordCount} occurrences(s) of the search term "
{searchTerm}" were found.""");
/* Output:
3 occurrences(s) of the search term "data" were found.
*/

A consulta anterior mostra como você pode exibir cadeias de caracteres como uma
sequência de palavras, depois de dividir uma cadeia de caracteres em uma sequência de
palavras.

Como classificar ou filtrar dados de texto por


qualquer palavra ou campo
O exemplo a seguir mostra como classificar linhas de texto estruturado, como valores
separados por vírgulas, por qualquer campo na linha. O campo pode ser especificado
dinamicamente em tempo de execução. Suponha que os campos em scores.csv
representam o número de ID do aluno, seguido por uma série de quatro resultados de
teste:

txt

111, 97, 92, 81, 60


112, 75, 84, 91, 39
113, 88, 94, 65, 91
114, 97, 89, 85, 82
115, 35, 72, 91, 70
116, 99, 86, 90, 94
117, 93, 92, 80, 87
118, 92, 90, 83, 78
119, 68, 79, 88, 92
120, 99, 82, 81, 79
121, 96, 85, 91, 60
122, 94, 92, 91, 91

A consulta a seguir classifica as linhas com base na pontuação do primeiro exame,


armazenada na segunda coluna:

C#

// Create an IEnumerable data source


string[] scores = File.ReadAllLines("scores.csv");

// Change this to any value from 0 to 4.


int sortField = 1;

Console.WriteLine($"Sorted highest to lowest by field [{sortField}]:");


// Split the string and sort on field[num]
var scoreQuery = from line in scores
let fields = line.Split(',')
orderby fields[sortField] descending
select line;

foreach (string str in scoreQuery)


{
Console.WriteLine(str);
}
/* Output (if sortField == 1):
Sorted highest to lowest by field [1]:
116, 99, 86, 90, 94
120, 99, 82, 81, 79
111, 97, 92, 81, 60
114, 97, 89, 85, 82
121, 96, 85, 91, 60
122, 94, 92, 91, 91
117, 93, 92, 80, 87
118, 92, 90, 83, 78
113, 88, 94, 65, 91
112, 75, 84, 91, 39
119, 68, 79, 88, 92
115, 35, 72, 91, 70
*/

A consulta anterior mostra como você pode manipular cadeias de caracteres dividindo-
as em campos e consultando os campos individuais.

Como consultar frases que expressões


específicas
O exemplo a seguir mostra como localizar frases em um arquivo de texto que
contenham correspondências para cada conjunto de palavras especificado. Embora a
matriz de termos de pesquisa seja embutida em código, ela também pode ser
preenchida dinamicamente em tempo de execução. A consulta retorna as frases que
contêm as palavras “Historically”, “data" e “integrated”.

C#

string text = """


Historically, the world of data and the world of objects
have not been well integrated. Programmers work in C# or Visual Basic
and also in SQL or XQuery. On the one side are concepts such as classes,
objects, fields, inheritance, and .NET APIs. On the other side
are tables, columns, rows, nodes, and separate languages for dealing with
them. Data types often require translation between the two worlds; there are
different standard functions. Because the object world has no notion of
query, a
query can only be represented as a string without compile-time type checking
or
IntelliSense support in the IDE. Transferring data from SQL tables or XML
trees to
objects in memory is often tedious and error-prone.
""";

// Split the text block into an array of sentences.


string[] sentences = text.Split(['.', '?', '!']);

// Define the search terms. This list could also be dynamically populated at
run time.
string[] wordsToMatch = [ "Historically", "data", "integrated" ];

// Find sentences that contain all the terms in the wordsToMatch array.
// Note that the number of terms to match is not specified at compile time.
char[] separators = ['.', '?', '!', ' ', ';', ':', ','];
var sentenceQuery = from sentence in sentences
let w =
sentence.Split(separators,StringSplitOptions.RemoveEmptyEntries)
where w.Distinct().Intersect(wordsToMatch).Count() ==
wordsToMatch.Count()
select sentence;

foreach (string str in sentenceQuery)


{
Console.WriteLine(str);
}
/* Output:
Historically, the world of data and the world of objects have not been well
integrated
*/

A consulta funciona primeiro dividindo o texto em frases e, em seguida, dividindo as


sentenças em uma matriz de cadeias de caracteres que contêm cada palavra. Para cada
uma dessas matrizes, o método Distinct remove todas as palavras duplicadas e, em
seguida, a consulta executa uma operação Intersect na matriz de palavras e na matriz
wordsToMatch . Se a contagem da interseção for igual à contagem da matriz
wordsToMatch , todas as palavras foram encontradas nas palavras e a frase original será

retornada.

A chamada para Split usa marcas de pontuação como separadores para removê-las da
cadeia de caracteres. Se não remover a pontuação, por exemplo, você poderia ter uma
cadeia de caracteres “Historically” que não corresponderia a “Historically” na matriz
wordsToMatch . Talvez você precise usar separadores extras, dependendo dos tipos de

pontuação encontrados no texto de origem.


Como combinar consultas LINQ com
expressões regulares
O exemplo a seguir mostra como usar a classe Regex para criar uma expressão regular
para correspondências mais complexas em cadeias de texto. A consulta LINQ torna fácil
a aplicação de filtro exatamente nos arquivos que você deseja pesquisar com a
expressão regular e formatar os resultados.

C#

string startFolder = """C:\Program Files\dotnet\sdk""";


// Or
// string startFolder = "/usr/local/share/dotnet/sdk";

// Take a snapshot of the file system.


var fileList = from file in Directory.GetFiles(startFolder, "*.*",
SearchOption.AllDirectories)
let fileInfo = new FileInfo(file)
select fileInfo;

// Create the regular expression to find all things "Visual".


System.Text.RegularExpressions.Regex searchTerm =
new System.Text.RegularExpressions.Regex(@"microsoft.net.
(sdk|workload)");

// Search the contents of each .htm file.


// Remove the where clause to find even more matchedValues!
// This query produces a list of files where a match
// was found, and a list of the matchedValues in that file.
// Note: Explicit typing of "Match" in select clause.
// This is required because MatchCollection is not a
// generic IEnumerable collection.
var queryMatchingFiles =
from file in fileList
where file.Extension == ".txt"
let fileText = File.ReadAllText(file.FullName)
let matches = searchTerm.Matches(fileText)
where matches.Count > 0
select new
{
name = file.FullName,
matchedValues = from System.Text.RegularExpressions.Match match in
matches
select match.Value
};

// Execute the query.


Console.WriteLine($"""The term "{searchTerm}" was found in:""");

foreach (var v in queryMatchingFiles)


{
// Trim the path a bit, then write
// the file name in which a match was found.
string s = v.name.Substring(startFolder.Length - 1);
Console.WriteLine(s);

// For this file, write out all the matching strings


foreach (var v2 in v.matchedValues)
{
Console.WriteLine($" {v2}");
}
}

Você também pode consultar o objeto MatchCollection retornado por uma pesquisa
RegEx . Apenas o valor de cada correspondência é produzido nos resultados. No

entanto, também é possível usar a LINQ para executar todos os tipos de filtragem,
classificação e agrupamento nessa coleção. Como MatchCollection é uma coleção
IEnumerable não genérica, é necessário declarar explicitamente o tipo da variável de
intervalo na consulta.

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
LINQ e coleções
Artigo • 02/05/2024

A maioria das coleções modela uma sequência de elementos. Você pode usar LINQ para
consultar qualquer tipo de coleção. Outros métodos LINQ encontram elementos em
uma coleção, calculam valores dos elementos em uma coleção ou modificam a coleção
ou seus elementos. Esses exemplos ajudam você a aprender sobre os métodos LINQ e
como usá-los com suas coleções ou outras fontes de dados.

Como descobrir a diferença de conjuntos entre


duas listas
Este exemplo mostra como usar o LINQ para comparar duas listas de cadeias de
caracteres e retornar as linhas que estão na primeira coleção, mas não na segunda. A
primeira coleção de nomes é armazenada no arquivo names1.txt:

txt

Bankov, Peter
Holm, Michael
Garcia, Hugo
Potra, Cristina
Noriega, Fabricio
Aw, Kam Foo
Beebe, Ann
Toyoshima, Tim
Guy, Wey Yuan
Garcia, Debra

A segunda coleção de nomes é armazenada no arquivo names2.txt. Alguns nomes


aparecem em ambas as sequências.

txt

Liu, Jinghao
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Gilchrist, Beth
Myrcha, Jacek
Giakoumakis, Leo
McLin, Nkenge
El Yassir, Mehdi
O código a seguir mostra como você pode usar o método Enumerable.Except para
localizar elementos na primeira lista que não estão na segunda lista:

C#

// Create the IEnumerable data sources.


string[] names1 = File.ReadAllLines("names1.txt");
string[] names2 = File.ReadAllLines("names2.txt");

// Create the query. Note that method syntax must be used here.
var differenceQuery = names1.Except(names2);

// Execute the query.


Console.WriteLine("The following lines are in names1.txt but not
names2.txt");
foreach (string s in differenceQuery)
Console.WriteLine(s);
/* Output:
The following lines are in names1.txt but not names2.txt
Potra, Cristina
Noriega, Fabricio
Aw, Kam Foo
Toyoshima, Tim
Guy, Wey Yuan
Garcia, Debra
*/

Alguns tipos de operações de consulta, tais como Except, Distinct, Union e Concat, só
podem ser expressas em sintaxe baseada em método.

Como combinar e comparar coleções de


cadeias de caracteres
Este exemplo mostra como mesclar arquivos que contêm linhas de texto e, em seguida,
classificar os resultados. Especificamente, mostra como executar uma concatenação,
uma união e uma interseção nos dois conjuntos de linhas de texto. Ele usa os mesmos
dois arquivos de texto mostrados no exemplo anterior. O código mostra exemplos dos
Enumerable.Concat, Enumerable.Union e Enumerable.Except.

C#

//Put text files in your solution folder


string[] fileA = File.ReadAllLines("names1.txt");
string[] fileB = File.ReadAllLines("names2.txt");

//Simple concatenation and sort. Duplicates are preserved.


var concatQuery = fileA.Concat(fileB).OrderBy(s => s);
// Pass the query variable to another function for execution.
OutputQueryResults(concatQuery, "Simple concatenate and sort. Duplicates are
preserved:");

// Concatenate and remove duplicate names based on


// default string comparer.
var uniqueNamesQuery = fileA.Union(fileB).OrderBy(s => s);
OutputQueryResults(uniqueNamesQuery, "Union removes duplicate names:");

// Find the names that occur in both files (based on


// default string comparer).
var commonNamesQuery = fileA.Intersect(fileB);
OutputQueryResults(commonNamesQuery, "Merge based on intersect:");

// Find the matching fields in each list. Merge the two


// results by using Concat, and then
// sort using the default string comparer.
string nameMatch = "Garcia";

var tempQuery1 = from name in fileA


let n = name.Split(',')
where n[0] == nameMatch
select name;

var tempQuery2 = from name2 in fileB


let n2 = name2.Split(',')
where n2[0] == nameMatch
select name2;

var nameMatchQuery = tempQuery1.Concat(tempQuery2).OrderBy(s => s);


OutputQueryResults(nameMatchQuery, $"""Concat based on partial name match "
{nameMatch}":""");

static void OutputQueryResults(IEnumerable<string> query, string message)


{
Console.WriteLine(Environment.NewLine + message);
foreach (string item in query)
{
Console.WriteLine(item);
}
Console.WriteLine($"{query.Count()} total names in list");
}
/* Output:
Simple concatenate and sort. Duplicates are preserved:
Aw, Kam Foo
Bankov, Peter
Bankov, Peter
Beebe, Ann
Beebe, Ann
El Yassir, Mehdi
Garcia, Debra
Garcia, Hugo
Garcia, Hugo
Giakoumakis, Leo
Gilchrist, Beth
Guy, Wey Yuan
Holm, Michael
Holm, Michael
Liu, Jinghao
McLin, Nkenge
Myrcha, Jacek
Noriega, Fabricio
Potra, Cristina
Toyoshima, Tim
20 total names in list

Union removes duplicate names:


Aw, Kam Foo
Bankov, Peter
Beebe, Ann
El Yassir, Mehdi
Garcia, Debra
Garcia, Hugo
Giakoumakis, Leo
Gilchrist, Beth
Guy, Wey Yuan
Holm, Michael
Liu, Jinghao
McLin, Nkenge
Myrcha, Jacek
Noriega, Fabricio
Potra, Cristina
Toyoshima, Tim
16 total names in list

Merge based on intersect:


Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
4 total names in list

Concat based on partial name match "Garcia":


Garcia, Debra
Garcia, Hugo
Garcia, Hugo
3 total names in list
*/

Como preencher coleções de objetos com


dados provenientes de diversas fontes
Este exemplo mostra como mesclar dados de diferentes fontes em uma sequência de
novos tipos.
7 Observação

Não tente unir dados na memória ou dados no sistema de arquivos com os dados
que ainda estão em um banco de dados. Essas junções entre domínios podem
gerar resultados indefinidos, devido às diferentes formas em que as operações de
junção podem ser definidas para consultas de banco de dados e outros tipos de
fontes. Além disso, há um risco de que essa operação possa causar uma exceção de
falta de memória, se a quantidade de dados no banco de dados for grande o
suficiente. Para unir dados de um banco de dados com os dados na memória,
primeiro chame ToList ou ToArray na consulta de banco de dados e, em seguida,
realize a junção na coleção retornada.

Este exemplo usa dois arquivos. O primeiro, names.csv, contém nomes de alunos e IDs
de alunos.

txt

Omelchenko,Svetlana,111
O'Donnell,Claire,112
Mortensen,Sven,113
Garcia,Cesar,114
Garcia,Debra,115
Fakhouri,Fadi,116
Feng,Hanying,117
Garcia,Hugo,118
Tucker,Lance,119
Adams,Terry,120
Zabokritski,Eugene,121
Tucker,Michael,122

O segundo, scores.csv, contém IDs de alunos na primeira coluna, seguidas pelas


pontuações do exame.

txt

111, 97, 92, 81, 60


112, 75, 84, 91, 39
113, 88, 94, 65, 91
114, 97, 89, 85, 82
115, 35, 72, 91, 70
116, 99, 86, 90, 94
117, 93, 92, 80, 87
118, 92, 90, 83, 78
119, 68, 79, 88, 92
120, 99, 82, 81, 79
121, 96, 85, 91, 60
122, 94, 92, 91, 91

O exemplo a seguir mostra como usar um registro nomeado Student para armazenar
dados mesclados, de duas coleções na memória de cadeias de caracteres, que simulam
dados de planilha no formato .csv. A ID é usada como a chave para mapear os alunos
para suas pontuações.

C#

// Each line of names.csv consists of a last name, a first name, and an


// ID number, separated by commas. For example, Omelchenko,Svetlana,111
string[] names = File.ReadAllLines("names.csv");

// Each line of scores.csv consists of an ID number and four test


// scores, separated by commas. For example, 111, 97, 92, 81, 60
string[] scores = File.ReadAllLines("scores.csv");

// Merge the data sources using a named type.


// var could be used instead of an explicit type. Note the dynamic
// creation of a list of ints for the ExamScores member. The first item
// is skipped in the split string because it is the student ID,
// not an exam score.
IEnumerable<Student> queryNamesScores = from nameLine in names
let splitName = nameLine.Split(',')
from scoreLine in scores
let splitScoreLine =
scoreLine.Split(',')
where Convert.ToInt32(splitName[2])
== Convert.ToInt32(splitScoreLine[0])
select new Student
(
FirstName: splitName[0],
LastName: splitName[1],
ID:
Convert.ToInt32(splitName[2]),
ExamScores: (from scoreAsText in
splitScoreLine.Skip(1)
select
Convert.ToInt32(scoreAsText)
).ToArray()
);

// Optional. Store the newly created student objects in memory


// for faster access in future queries. This could be useful with
// very large data files.
List<Student> students = queryNamesScores.ToList();

// Display each student's name and exam score average.


foreach (var student in students)
{
Console.WriteLine($"The average score of {student.FirstName}
{student.LastName} is {student.ExamScores.Average()}.");
}
/* Output:
The average score of Omelchenko Svetlana is 82.5.
The average score of O'Donnell Claire is 72.25.
The average score of Mortensen Sven is 84.5.
The average score of Garcia Cesar is 88.25.
The average score of Garcia Debra is 67.
The average score of Fakhouri Fadi is 92.25.
The average score of Feng Hanying is 88.
The average score of Garcia Hugo is 85.75.
The average score of Tucker Lance is 81.75.
The average score of Adams Terry is 85.25.
The average score of Zabokritski Eugene is 83.
The average score of Tucker Michael is 92.
*/

Na cláusula select, cada novo objeto Student é inicializado dos dados nas duas fontes.

Se você não tiver que armazenar os resultados de uma consulta, as tuplas ou os tipos
anônimos poderão ser mais convenientes que os tipos nomeados. O exemplo a seguir
realiza a mesma tarefa do exemplo anterior, mas usa tuplas em vez de tipos nomeados:

C#

// Merge the data sources by using an anonymous type.


// Note the dynamic creation of a list of ints for the
// ExamScores member. We skip 1 because the first string
// in the array is the student ID, not an exam score.
var queryNamesScores2 = from nameLine in names
let splitName = nameLine.Split(',')
from scoreLine in scores
let splitScoreLine = scoreLine.Split(',')
where Convert.ToInt32(splitName[2]) ==
Convert.ToInt32(splitScoreLine[0])
select (FirstName: splitName[0],
LastName: splitName[1],
ExamScores: (from scoreAsText in
splitScoreLine.Skip(1)
select
Convert.ToInt32(scoreAsText))
.ToList()
);

// Display each student's name and exam score average.


foreach (var student in queryNamesScores2)
{
Console.WriteLine($"The average score of {student.FirstName}
{student.LastName} is {student.ExamScores.Average()}.");
}
Como consultar um ArrayList com LINQ
Ao usar a LINQ para consultar coleções IEnumerable não genéricas como ArrayList, você
deve declarar explicitamente o tipo da variável de intervalo para refletir o tipo específico
dos objetos na coleção. Se você tiver um ArrayList de objetos Student , sua cláusula
from deverá ter uma aparência semelhante a esta:

C#

var query = from Student s in arrList


//...

Especificando o tipo da variável de intervalo, você está convertendo cada item na


ArrayList em um Student .

O uso de uma variável de intervalo de tipo explícito em uma expressão de consulta é


equivalente a chamar o método Cast. Cast lança uma exceção se a conversão
especificada não puder ser realizada. Cast e OfType são os dois métodos de operador
de consulta padrão que operam em tipos IEnumerable não genéricos. Para obter mais
informações, consulte Relacionamentos de tipo em operações de consulta LINQ. O
exemplo a seguir mostra uma consulta sobre um ArrayList.

C#

ArrayList arrList = new ArrayList();


arrList.Add(
new Student
(
FirstName: "Svetlana",
LastName: "Omelchenko",
ExamScores: new int[] { 98, 92, 81, 60 }
));
arrList.Add(
new Student
(
FirstName: "Claire",
LastName: "O’Donnell",
ExamScores: new int[] { 75, 84, 91, 39 }
));
arrList.Add(
new Student
(
FirstName: "Sven",
LastName: "Mortensen",
ExamScores: new int[] { 88, 94, 65, 91 }
));
arrList.Add(
new Student
(
FirstName: "Cesar",
LastName: "Garcia",
ExamScores: new int[] { 97, 89, 85, 82 }
));

var query = from Student student in arrList


where student.ExamScores[0] > 95
select student;

foreach (Student s in query)


Console.WriteLine(s.LastName + ": " + s.ExamScores[0]);

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
Como estender o LINQ
Artigo • 02/05/2024

Todos os métodos baseados em LINQ seguem um dos dois padrões semelhantes. Eles
pegam uma sequência enumerável. Eles retornam uma sequência diferente ou um único
valor. A consistência da forma permite que você estenda LINQ escrevendo métodos
com uma forma semelhante. Na verdade, as bibliotecas .NET ganharam novos métodos
em muitas versões do .NET desde que o LINQ foi introduzido pela primeira vez. Neste
artigo, você verá exemplos de extensão do LINQ escrevendo seus próprios métodos que
seguem o mesmo padrão.

Adicionar métodos personalizados para


consultas LINQ
Você pode estender o conjunto de métodos que usa para consultas LINQ adicionando
métodos de extensão à interface IEnumerable<T>. Por exemplo, além do padrão médio
ou máximo de operações, é possível criar um método de agregação personalizado para
calcular um único valor de uma sequência de valores. Também é possível criar um
método que funciona como filtro personalizado ou transformação de dados específicos
para uma sequência de valores e retorna uma nova sequência. Exemplos desses
métodos são Distinct, Skip e Reverse.

Ao estender a interface IEnumerable<T>, você pode aplicar seus métodos


personalizados para qualquer coleção enumerável. Para obter mais informações,
consulte Métodos de extensão.

Um método de agregação calcula um valor único de um conjunto de valores. O LINQ


fornece vários métodos de agregação, incluindo Average, Min e Max. Você pode criar
seu próprio método de agregação, adicionando um método de extensão à interface
IEnumerable<T>.

O exemplo de código a seguir mostra como criar um método de extensão chamado


Median para calcular uma mediana de uma sequência de números do tipo double .

C#

public static class EnumerableExtension


{
public static double Median(this IEnumerable<double>? source)
{
if (source is null || !source.Any())
{
throw new InvalidOperationException("Cannot compute median for a
null or empty set.");
}

var sortedList =
source.OrderBy(number => number).ToList();

int itemIndex = sortedList.Count / 2;

if (sortedList.Count % 2 == 0)
{
// Even number of items.
return (sortedList[itemIndex] + sortedList[itemIndex - 1]) / 2;
}
else
{
// Odd number of items.
return sortedList[itemIndex];
}
}
}

Você chama esse método de extensão para qualquer coleção enumerável da mesma
maneira que chama outros métodos de agregação da interface IEnumerable<T>.

O exemplo de código a seguir mostra como usar o método Median para uma matriz do
tipo double .

C#

double[] numbers = [1.9, 2, 8, 4, 5.7, 6, 7.2, 0];


var query = numbers.Median();

Console.WriteLine($"double: Median = {query}");


// This code produces the following output:
// double: Median = 4.85

Você pode sobrecarregar o método de agregação para que ele aceite sequências de
vários tipos. A abordagem padrão é criar uma sobrecarga para cada tipo. Outra
abordagem é criar uma sobrecarga que pega um tipo genérico e convertê-lo em um
tipo específico, usando um delegado. Você também pode combinar as duas
abordagens.

Você pode criar uma sobrecarga específica para cada tipo que deseja oferecer suporte.
O exemplo de código a seguir mostra uma sobrecarga do método Median para o tipo
int .

C#
// int overload
public static double Median(this IEnumerable<int> source) =>
(from number in source select (double)number).Median();

Agora você pode chamar as sobrecargas Median para os tipos integer e double ,
conforme mostrado no código a seguir:

C#

double[] numbers1 = [1.9, 2, 8, 4, 5.7, 6, 7.2, 0];


var query1 = numbers1.Median();

Console.WriteLine($"double: Median = {query1}");

int[] numbers2 = [1, 2, 3, 4, 5];


var query2 = numbers2.Median();

Console.WriteLine($"int: Median = {query2}");


// This code produces the following output:
// double: Median = 4.85
// int: Median = 3

Você também pode criar uma sobrecarga que aceita uma sequência genérica de objetos.
Essa sobrecarga recebe um delegado como parâmetro e usa-o para converter uma
sequência de objetos de um tipo genérico em um tipo específico.

O código a seguir mostra uma sobrecarga do método Median que recebe o delegado
Func<T,TResult> como um parâmetro. Esse delegado recebe um objeto de tipo
genérico T e retorna um objeto do tipo double .

C#

// generic overload
public static double Median<T>(
this IEnumerable<T> numbers, Func<T, double> selector) =>
(from num in numbers select selector(num)).Median();

Agora você pode chamar o método Median para uma sequência de objetos de qualquer
tipo. Se o tipo não tiver sua própria sobrecarga de método, será necessário passar um
parâmetro delegado. No C# você pode usar uma expressão lambda para essa finalidade.
Além disso, no Visual Basic, se você usar a cláusula Aggregate ou Group By em vez da
chamada de método, você pode passar qualquer valor ou expressão que estiver no
escopo dessa cláusula.
O exemplo de código a seguir mostra como chamar o método Median para uma matriz
de inteiros e para uma matriz de cadeias de caracteres. Será calculada a mediana dos
comprimentos das cadeias de caracteres na matriz. O exemplo também mostra como
passar o parâmetro delegado Func<T,TResult> ao método Median para cada caso.

C#

int[] numbers3 = [1, 2, 3, 4, 5];

/*
You can use the num => num lambda expression as a parameter for the
Median method
so that the compiler will implicitly convert its value to double.
If there is no implicit conversion, the compiler will display an error
message.
*/
var query3 = numbers3.Median(num => num);

Console.WriteLine($"int: Median = {query3}");

string[] numbers4 = ["one", "two", "three", "four", "five"];

// With the generic overload, you can also use numeric properties of
objects.
var query4 = numbers4.Median(str => str.Length);

Console.WriteLine($"string: Median = {query4}");


// This code produces the following output:
// int: Median = 3
// string: Median = 4

Você pode estender a interface IEnumerable<T> com um método de consulta


personalizada que retorna uma sequência de valores. Nesse caso, o método deve
retornar uma coleção do tipo IEnumerable<T>. Esses métodos podem ser usados para
aplicar transformações de dados ou filtros a uma sequência de valores.

O exemplo a seguir mostra como criar um método de extensão chamado


AlternateElements que retorna todos os outros elementos em uma coleção, começando

pelo primeiro elemento.

C#

// Extension method for the IEnumerable<T> interface.


// The method returns every other element of a sequence.
public static IEnumerable<T> AlternateElements<T>(this IEnumerable<T>
source)
{
int index = 0;
foreach (T element in source)
{
if (index % 2 == 0)
{
yield return element;
}

index++;
}
}

Você pode chamar esse método de extensão para qualquer coleção enumerável
exatamente como chamaria outros métodos da interface IEnumerable<T>, conforme
mostrado no código a seguir:

C#

string[] strings = ["a", "b", "c", "d", "e"];

var query5 = strings.AlternateElements();

foreach (var element in query5)


{
Console.WriteLine(element);
}
// This code produces the following output:
// a
// c
// e

Agrupar resultados por chaves contíguas


O exemplo a seguir mostra como agrupar elementos em partes que representam
subsequências de chaves contíguas. Por exemplo, suponha que você receba a seguinte
sequência de pares chave-valor:

ノ Expandir a tabela

Chave Valor

Um We

Um think

Um that

B Linq

C is
Chave Valor

Um really

B cool

B !

Os seguintes grupos são criados nesta ordem:

1. We, think, that


2. Linq
3. is
4. really
5. cool, !

A solução é implementada como um método de extensão thread-safe que retorna os


resultados de uma maneira de streaming. Ela produz seus grupos à medida que
percorre a sequência de origem. Diferentemente dos operadores group ou orderby , ela
pode começar a retornar grupos para o chamador antes de ler toda a sequência. O
exemplo a seguir mostra o método de extensão e o código do cliente que o usa:

C#

public static class ChunkExtensions


{
public static IEnumerable<IGrouping<TKey, TSource>> ChunkBy<TSource,
TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector) =>
source.ChunkBy(keySelector, EqualityComparer<TKey>.Default);

public static IEnumerable<IGrouping<TKey, TSource>> ChunkBy<TSource,


TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> comparer)
{
// Flag to signal end of source sequence.
const bool noMoreSourceElements = true;

// Auto-generated iterator for the source array.


IEnumerator<TSource>? enumerator = source.GetEnumerator();

// Move to the first element in the source sequence.


if (!enumerator.MoveNext())
{
yield break; // source collection is empty
}
while (true)
{
var key = keySelector(enumerator.Current);

Chunk<TKey, TSource> current = new(key, enumerator, value =>


comparer.Equals(key, keySelector(value)));

yield return current;

if (current.CopyAllChunkElements() == noMoreSourceElements)
{
yield break;
}
}
}
}

C#

public static class GroupByContiguousKeys


{
// The source sequence.
static readonly KeyValuePair<string, string>[] list = [
new("A", "We"),
new("A", "think"),
new("A", "that"),
new("B", "LINQ"),
new("C", "is"),
new("A", "really"),
new("B", "cool"),
new("B", "!")
];

// Query variable declared as class member to be available


// on different threads.
static readonly IEnumerable<IGrouping<string, KeyValuePair<string,
string>>> query =
list.ChunkBy(p => p.Key);

public static void GroupByContiguousKeys1()


{
// ChunkBy returns IGrouping objects, therefore a nested
// foreach loop is required to access the elements in each "chunk".
foreach (var item in query)
{
Console.WriteLine($"Group key = {item.Key}");
foreach (var inner in item)
{
Console.WriteLine($"\t{inner.Value}");
}
}
}
}

Classe ChunkExtensions
No código apresentado da implementação da classe ChunkExtensions , o while(true)
faz um loop no método ChunkBy , itera pela sequência de origem e cria uma cópia de
cada Chunk. Em cada passagem, o iterador avança para o primeiro elemento do
próximo “Chunk”, representado por um objeto Chunk, na sequência de origem. Esse
loop corresponde ao loop foreach externo que executa a consulta. Nesse loop, o código
executa as seguintes ações:

1. Obtém a chave para o Chunk atual e a atribui à variável key . O iterador de origem
consome a sequência de origem até encontrar um elemento com uma chave que
não seja correspondente.
2. Crie um novo objeto Chunk (grupo) e armazene-o em current variável. Ele tem
um GroupItem, uma cópia do elemento de origem atual.
3. Retorna esse Chunk. Um Chunk é um IGrouping<TKey,TSource> , que é o valor
retornado do método ChunkBy. O Chunk tem apenas o primeiro elemento na
sequência de origem. Os elementos restantes são retornados somente quando o
foreach do código do cliente estiver nessa parte. Confira Chunk.GetEnumerator para
obter mais informações.
4. Verifique se:

o Chunk tem uma cópia de todos os respectivos elementos de origem ou


o iterador atingiu o fim da sequência de origem.

5. Quando o chamador enumerou todos os itens de parte, o método


Chunk.GetEnumerator copiou todos os itens de chunk. Se o loop de
Chunk.GetEnumerator não enumerar todos os elementos no chunk, faça isso agora

para evitar corromper o iterador para clientes que podem estar chamando-o em
um thread separado.

Classe Chunk
A classe Chunk é um grupo contíguo de um ou mais elementos de origem que têm a
mesma chave. Uma parte tem uma chave e uma lista de objetos ChunkItem, que são
cópias dos elementos na sequência de origem:

C#
class Chunk<TKey, TSource> : IGrouping<TKey, TSource>
{
// INVARIANT: DoneCopyingChunk == true ||
// (predicate != null && predicate(enumerator.Current) &&
current.Value == enumerator.Current)

// A Chunk has a linked list of ChunkItems, which represent the elements


in the current chunk. Each ChunkItem
// has a reference to the next ChunkItem in the list.
class ChunkItem
{
public ChunkItem(TSource value) => Value = value;
public readonly TSource Value;
public ChunkItem? Next;
}

public TKey Key { get; }

// Stores a reference to the enumerator for the source sequence


private IEnumerator<TSource> enumerator;

// A reference to the predicate that is used to compare keys.


private Func<TSource, bool> predicate;

// Stores the contents of the first source element that


// belongs with this chunk.
private readonly ChunkItem head;

// End of the list. It is repositioned each time a new


// ChunkItem is added.
private ChunkItem? tail;

// Flag to indicate the source iterator has reached the end of the
source sequence.
internal bool isLastSourceElement;

// Private object for thread synchronization


private readonly object m_Lock;

// REQUIRES: enumerator != null && predicate != null


public Chunk(TKey key, [DisallowNull] IEnumerator<TSource> enumerator,
[DisallowNull] Func<TSource, bool> predicate)
{
Key = key;
this.enumerator = enumerator;
this.predicate = predicate;

// A Chunk always contains at least one element.


head = new ChunkItem(enumerator.Current);

// The end and beginning are the same until the list contains > 1
elements.
tail = head;
m_Lock = new object();
}

// Indicates that all chunk elements have been copied to the list of
ChunkItems.
private bool DoneCopyingChunk => tail == null;

// Adds one ChunkItem to the current group


// REQUIRES: !DoneCopyingChunk && lock(this)
private void CopyNextChunkElement()
{
// Try to advance the iterator on the source sequence.
isLastSourceElement = !enumerator.MoveNext();

// If we are (a) at the end of the source, or (b) at the end of the
current chunk
// then null out the enumerator and predicate for reuse with the
next chunk.
if (isLastSourceElement || !predicate(enumerator.Current))
{
enumerator = default!;
predicate = default!;
}
else
{
tail!.Next = new ChunkItem(enumerator.Current);
}

// tail will be null if we are at the end of the chunk elements


// This check is made in DoneCopyingChunk.
tail = tail!.Next;
}

// Called after the end of the last chunk was reached.


internal bool CopyAllChunkElements()
{
while (true)
{
lock (m_Lock)
{
if (DoneCopyingChunk)
{
return isLastSourceElement;
}
else
{
CopyNextChunkElement();
}
}
}
}

// Stays just one step ahead of the client requests.


public IEnumerator<TSource> GetEnumerator()
{
// Specify the initial element to enumerate.
ChunkItem? current = head;

// There should always be at least one ChunkItem in a Chunk.


while (current != null)
{
// Yield the current item in the list.
yield return current.Value;

// Copy the next item from the source sequence,


// if we are at the end of our local list.
lock (m_Lock)
{
if (current == tail)
{
CopyNextChunkElement();
}
}

// Move to the next ChunkItem in the list.


current = current.Next;
}
}

System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
}

Cada ChunkItem (representado pela classe ChunkItem ) tem uma referência ao próximo
ChunkItem na lista. A lista consiste no head , que armazena o conteúdo do primeiro

elemento de origem que pertence a esse Chunk, e no tail , que é um fim da lista. O fim
da lista é reposicionado cada vez que um novo ChunkItem é adicionado. A parte final da
lista vinculada é definida como null no método CopyNextChunkElement se a chave do
próximo elemento não corresponde à chave da parte atual ou se não há mais elementos
na origem.

O método CopyNextChunkElement da classe Chunk adiciona um ChunkItem ao grupo atual


de itens. Ele tenta avançar o iterador na sequência de origem. Se o método MoveNext()
retornar false , a iteração está no final e que o isLastSourceElement está definido como
true .

O método CopyAllChunkElements é chamado depois que o final da última parte foi


alcançado. Ele verifica se há mais elementos na sequência de origem. Se houver, ele
retornará true se o enumerador dessa parte tiver sido esgotado. Nesse método,
quando é verificado se o campo privado DoneCopyingChunk é true , caso
isLastSourceElement seja false , ele sinaliza ao iterador externo para continuar iterando.
O loop foreach interno invoca o método GetEnumerator da classe Chunk . Esse método
fica apenas um elemento à frente das solicitações do cliente. Ele adiciona o próximo
elemento da parte somente depois que o cliente solicita o elemento anterior da lista.

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
Consulta com base no estado de tempo
de execução
Artigo • 02/05/2024

Na maioria das consultas LINQ, a forma geral da consulta é definida no código. Você
pode filtrar itens usando uma cláusula where , classificar a coleção de saída usando
orderby , itens de grupo ou executar alguma computação. Seu código pode fornecer

parâmetros para o filtro, a chave de classificação ou outras expressões que fazem parte
da consulta. No entanto, a forma geral da consulta não pode ser alterada. Neste artigo,
você aprenderá técnicas para usar a interface System.Linq.IQueryable<T> e tipos que a
implementam para modificar a forma de uma consulta em tempo de execução.

Você usa essas técnicas para criar consultas em tempo de execução, em que algum
estado de entrada ou tempo de execução do usuário altera os métodos de consulta que
você deseja usar como parte da consulta. Você edita a consulta adicionando,
removendo ou modificando cláusulas de consulta.

7 Observação

Adicione using System.Linq.Expressions; e using static


System.Linq.Expressions.Expression; na parte superior do seu arquivo .cs.

Considere o código que define um IQueryable ou um IQueryable<T> em relação a uma


fonte de dados:

C#

string[] companyNames = [
"Consolidated Messenger", "Alpine Ski House", "Southridge Video",
"City Power & Light", "Coho Winery", "Wide World Importers",
"Graphic Design Institute", "Adventure Works", "Humongous Insurance",
"Woodgrove Bank", "Margie's Travel", "Northwind Traders",
"Blue Yonder Airlines", "Trey Research", "The Phone Company",
"Wingtip Toys", "Lucerne Publishing", "Fourth Coffee"
];

// Use an in-memory array as the data source, but the IQueryable could have
come
// from anywhere -- an ORM backed by a database, a web request, or any other
LINQ provider.
IQueryable<string> companyNamesSource = companyNames.AsQueryable();
var fixedQry = companyNames.OrderBy(x => x);
Toda vez que você executar o código anterior, exatamente a mesma consulta será
executada. Vamos aprender a modificar a consulta para estendê-la ou modificá-la.
Fundamentalmente, uma IQueryable tem dois componentes:

Expression – Uma representação (agnóstica quanto à linguagem e à fonte de


dados) dos componentes da consulta atual, na forma de uma árvore de expressão.
Provider – Uma instância de um provedor LINQ, que sabe como materializar a
consulta atual em um valor ou conjunto de valores.

No contexto de consulta dinâmica, o provedor geralmente permanece o mesmo, ao


passo que a árvore de expressão da consulta varia de uma consulta para a outra.

As árvores de expressão são imutáveis; se você quiser uma árvore de expressão


diferente e, portanto, uma consulta diferente, precisará traduzir a árvore de expressão
existente para uma nova. As seguintes seções descrevem técnicas específicas para
executar consultas diferentes em resposta ao estado de runtime:

Usar o estado de runtime de dentro da árvore de expressão


Chamar mais métodos LINQ
Variar a árvore de expressão passada para os métodos LINQ
Construir uma árvore de expressão Expression<TDelegate> usando os métodos de
fábrica em Expression
Adicionar nós de chamada de método a uma árvore de expressão de IQueryable
Construir cadeias de caracteres e usar a Biblioteca dinâmica do LINQ

Cada uma das técnicas permite mais funcionalidades, mas a um custo de maior
complexidade.

Usar o estado de runtime de dentro da árvore


de expressão
A maneira mais simples de executar consultas dinamicamente é referenciar o estado de
runtime diretamente na consulta por meio de uma variável fechada, como length no
seguinte exemplo de código:

C#

var length = 1;
var qry = companyNamesSource
.Select(x => x.Substring(0, length))
.Distinct();

Console.WriteLine(string.Join(",", qry));
// prints: C, A, S, W, G, H, M, N, B, T, L, F
length = 2;
Console.WriteLine(string.Join(",", qry));
// prints: Co, Al, So, Ci, Wi, Gr, Ad, Hu, Wo, Ma, No, Bl, Tr, Th, Lu, Fo

A árvore de expressão interna e, portanto, a consulta, não é modificada; a consulta


retorna valores diferentes apenas porque o valor de length foi alterado.

Chamar mais métodos LINQ


Em geral, os métodos LINQ internos em Queryable executam duas etapas:

Empacote a árvore de expressão atual em uma MethodCallExpression


representando a chamada de método.
Passar a árvore de expressão empacotada de volta para o provedor, seja para
retornar um valor por meio do método IQueryProvider.Execute do provedor ou
para retornar um objeto de consulta traduzido por meio do método
IQueryProvider.CreateQuery.

Você pode substituir a consulta original pelo resultado do método de retorno


System.Linq.IQueryable<T> a fim de obter uma nova consulta. Você pode usar o estado
de tempo de execução, como no exemplo a seguir:

C#

// bool sortByLength = /* ... */;

var qry = companyNamesSource;


if (sortByLength)
{
qry = qry.OrderBy(x => x.Length);
}

Variar a árvore de expressão passada para os


métodos LINQ
Você pode passar expressões diferentes para os métodos LINQ, dependendo do estado
de runtime:

C#

// string? startsWith = /* ... */;


// string? endsWith = /* ... */;
Expression<Func<string, bool>> expr = (startsWith, endsWith) switch
{
("" or null, "" or null) => x => true,
(_, "" or null) => x => x.StartsWith(startsWith),
("" or null, _) => x => x.EndsWith(endsWith),
(_, _) => x => x.StartsWith(startsWith) || x.EndsWith(endsWith)
};

var qry = companyNamesSource.Where(expr);

Talvez você também queira compor as diversas subexpressões usando outra biblioteca,
como o PredicateBuilder da LinqKit :

C#

// This is functionally equivalent to the previous example.

// using LinqKit;
// string? startsWith = /* ... */;
// string? endsWith = /* ... */;

Expression<Func<string, bool>>? expr = PredicateBuilder.New<string>(false);


var original = expr;
if (!string.IsNullOrEmpty(startsWith))
{
expr = expr.Or(x => x.StartsWith(startsWith));
}
if (!string.IsNullOrEmpty(endsWith))
{
expr = expr.Or(x => x.EndsWith(endsWith));
}
if (expr == original)
{
expr = x => true;
}

var qry = companyNamesSource.Where(expr);

Construir árvores de expressão e consultas


usando métodos de fábrica
Em todos os exemplos até este ponto, você conhece o tipo de elemento no tipo de
compilação string e, portanto, o tipo da consulta IQueryable<string> . Talvez seja
necessário adicionar componentes a uma consulta de qualquer tipo de elemento ou
adicionar componentes diferentes, dependendo do tipo de elemento. Você pode criar
árvores de expressão do zero, usando os métodos de fábrica em
System.Linq.Expressions.Expression, personalizando assim a expressão em tempo de
execução para um tipo de elemento específico.

Construindo uma expressão <TDelegate>


Quando você constrói uma expressão para passar para um dos métodos LINQ, na
verdade você está criando uma instância de
System.Linq.Expressions.Expression<TDelegate>, em que TDelegate é algum tipo de
delegado, como Func<string, bool> , Action ou um tipo de delegado personalizado.

System.Linq.Expressions.Expression<TDelegate> herda de LambdaExpression, que


representa uma expressão lambda completa como o exemplo a seguir:

C#

Expression<Func<string, bool>> expr = x => x.StartsWith("a");

Uma LambdaExpression tem dois componentes:

1. Uma lista de parâmetros – (string x) – representada pela propriedade


Parameters.
2. Um corpo – x.StartsWith("a") – representado pela propriedade Body.

As etapas básicas para construção de um Expression<TDelegate> são as seguintes:

1. Defina objetos ParameterExpression para cada um dos parâmetros (se houver) na


expressão lambda, usando o método de fábrica Parameter.

C#

ParameterExpression x = Parameter(typeof(string), "x");

2. Construa o corpo do seu LambdaExpression, usando ParameterExpression definido


e os métodos de fábrica em Expression. Por exemplo, uma expressão que
representa x.StartsWith("a") poderia ser construída assim:

C#

Expression body = Call(


x,
typeof(string).GetMethod("StartsWith", [typeof(string)])!,
Constant("a")
);
3. Encapsule os parâmetros e o corpo em um Expression<TDelegate> do tipo tempo
de compilação, usando a sobrecarga do método de fábrica Lambda apropriada:

C#

Expression<Func<string, bool>> expr = Lambda<Func<string, bool>>(body,


x);

As seções a seguir descrevem um cenário no qual talvez você queira construir uma
Expression<TDelegate> para passar para um método LINQ. Ele fornece um exemplo
completo de como fazer isso usando os métodos de fábrica.

Construir uma consulta completa em tempo de


execução
Você deseja escrever consultas que funcionam com vários tipos de entidade:

C#

record Person(string LastName, string FirstName, DateTime DateOfBirth);


record Car(string Model, int Year);

Para qualquer um desses tipos de entidade, você deve filtrar e retornar somente as
entidades que têm determinado texto dentro de um dos campos string delas. Para
Person , você deve pesquisar as propriedades FirstName e LastName :

C#

string term = /* ... */;


var personsQry = new List<Person>()
.AsQueryable()
.Where(x => x.FirstName.Contains(term) || x.LastName.Contains(term));

Já para Car , você deve pesquisar apenas a propriedade Model :

C#

string term = /* ... */;


var carsQry = new List<Car>()
.AsQueryable()
.Where(x => x.Model.Contains(term));
Embora você possa gravar uma função personalizada para IQueryable<Person> e outra
para IQueryable<Car> , a função a seguir adiciona essa filtragem a qualquer consulta
existente, independentemente do tipo do elemento.

C#

// using static System.Linq.Expressions.Expression;

IQueryable<T> TextFilter<T>(IQueryable<T> source, string term)


{
if (string.IsNullOrEmpty(term)) { return source; }

// T is a compile-time placeholder for the element type of the query.


Type elementType = typeof(T);

// Get all the string properties on this specific type.


PropertyInfo[] stringProperties = elementType
.GetProperties()
.Where(x => x.PropertyType == typeof(string))
.ToArray();
if (!stringProperties.Any()) { return source; }

// Get the right overload of String.Contains


MethodInfo containsMethod = typeof(string).GetMethod("Contains",
[typeof(string)])!;

// Create a parameter for the expression tree:


// the 'x' in 'x => x.PropertyName.Contains("term")'
// The type of this parameter is the query's element type
ParameterExpression prm = Parameter(elementType);

// Map each property to an expression tree node


IEnumerable<Expression> expressions = stringProperties
.Select(prp =>
// For each property, we have to construct an expression tree
node like x.PropertyName.Contains("term")
Call( // .Contains(...)
Property( // .PropertyName
prm, // x
prp
),
containsMethod,
Constant(term) // "term"
)
);

// Combine all the resultant expression nodes using ||


Expression body = expressions
.Aggregate((prev, current) => Or(prev, current));

// Wrap the expression body in a compile-time-typed lambda expression


Expression<Func<T, bool>> lambda = Lambda<Func<T, bool>>(body, prm);
// Because the lambda is compile-time-typed (albeit with a generic
parameter), we can use it with the Where method
return source.Where(lambda);
}

Como a função TextFilter usa e retorna um IQueryable<T> (e não apenas um


IQueryable), você pode adicionar mais elementos de consulta do tipo tempo de
compilação após o filtro de texto.

C#

var qry = TextFilter(


new List<Person>().AsQueryable(),
"abcd"
)
.Where(x => x.DateOfBirth < new DateTime(2001, 1, 1));

var qry1 = TextFilter(


new List<Car>().AsQueryable(),
"abcd"
)
.Where(x => x.Year == 2010);

Adicionar nós de chamada de método à árvore de


expressão IQueryable<TDelegate>
Se você tiver um IQueryable em vez de IQueryable<T>, não poderá chamar diretamente
os métodos LINQ genéricos. Uma alternativa é criar a árvore de expressão interna
conforme mostrado no exemplo acima e usar a reflexão para invocar o método LINQ
apropriado e passar como parâmetro a árvore de expressão.

Você também pode duplicar a funcionalidade do método LINQ, empacotando toda a


árvore em um MethodCallExpression que representa uma chamada ao método LINQ:

C#

IQueryable TextFilter_Untyped(IQueryable source, string term)


{
if (string.IsNullOrEmpty(term)) { return source; }
Type elementType = source.ElementType;

// The logic for building the ParameterExpression and the


LambdaExpression's body is the same as in the previous example,
// but has been refactored into the constructBody function.
(Expression? body, ParameterExpression? prm) =
constructBody(elementType, term);
if (body is null) { return source; }
Expression filteredTree = Call(
typeof(Queryable),
"Where",
[elementType],
source.Expression,
Lambda(body, prm!)
);

return source.Provider.CreateQuery(filteredTree);
}

Nesse caso, você não tem um espaço reservado genérico T em tempo de compilação,
portanto, você usa a sobrecarga Lambda que não requer informações do tipo tempo de
compilação e que produz um LambdaExpression em vez de um Expression<TDelegate>.

Biblioteca Dinâmica do LINQ


A construção de árvores de expressão usando métodos de fábrica é relativamente
complexa; é mais fácil compor cadeias de caracteres. A Biblioteca Dinâmica do LINQ
expõe um conjunto de métodos de extensão em IQueryable, correspondentes aos
métodos LINQ padrão em Queryable e que aceitam cadeias de caracteres em uma
sintaxe especial em vez de árvores de expressão. A biblioteca gera a árvore de
expressão apropriada com base na cadeia de caracteres e pode retornar o IQueryable
traduzido resultante.

Por exemplo, o exemplo anterior pode ser reescrito da seguinte maneira:

C#

// using System.Linq.Dynamic.Core

IQueryable TextFilter_Strings(IQueryable source, string term)


{
if (string.IsNullOrEmpty(term)) { return source; }

var elementType = source.ElementType;

// Get all the string property names on this specific type.


var stringProperties =
elementType.GetProperties()
.Where(x => x.PropertyType == typeof(string))
.ToArray();
if (!stringProperties.Any()) { return source; }

// Build the string expression


string filterExpr = string.Join(
" || ",
stringProperties.Select(prp => $"{prp.Name}.Contains(@0)")
);

return source.Where(filterExpr, term);


}

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
Asynchronous programming with async
and await
Article • 09/28/2023

The Task asynchronous programming model (TAP) provides an abstraction over


asynchronous code. You write code as a sequence of statements, just like always. You
can read that code as though each statement completes before the next begins. The
compiler performs many transformations because some of those statements may start
work and return a Task that represents the ongoing work.

That's the goal of this syntax: enable code that reads like a sequence of statements, but
executes in a much more complicated order based on external resource allocation and
when tasks are complete. It's analogous to how people give instructions for processes
that include asynchronous tasks. Throughout this article, you'll use an example of
instructions for making breakfast to see how the async and await keywords make it
easier to reason about code that includes a series of asynchronous instructions. You'd
write the instructions something like the following list to explain how to make a
breakfast:

1. Pour a cup of coffee.


2. Heat a pan, then fry two eggs.
3. Fry three slices of bacon.
4. Toast two pieces of bread.
5. Add butter and jam to the toast.
6. Pour a glass of orange juice.

If you have experience with cooking, you'd execute those instructions asynchronously.
You'd start warming the pan for eggs, then start the bacon. You'd put the bread in the
toaster, then start the eggs. At each step of the process, you'd start a task, then turn
your attention to tasks that are ready for your attention.

Cooking breakfast is a good example of asynchronous work that isn't parallel. One
person (or thread) can handle all these tasks. Continuing the breakfast analogy, one
person can make breakfast asynchronously by starting the next task before the first task
completes. The cooking progresses whether or not someone is watching it. As soon as
you start warming the pan for the eggs, you can begin frying the bacon. Once the bacon
starts, you can put the bread into the toaster.

For a parallel algorithm, you'd need multiple cooks (or threads). One would make the
eggs, one the bacon, and so on. Each one would be focused on just that one task. Each
cook (or thread) would be blocked synchronously waiting for the bacon to be ready to
flip, or the toast to pop.

Now, consider those same instructions written as C# statements:

C#

using System;
using System.Threading.Tasks;

namespace AsyncBreakfast
{
// These classes are intentionally empty for the purpose of this
example. They are simply marker classes for the purpose of demonstration,
contain no properties, and serve no other purpose.
internal class Bacon { }
internal class Coffee { }
internal class Egg { }
internal class Juice { }
internal class Toast { }

class Program
{
static void Main(string[] args)
{
Coffee cup = PourCoffee();
Console.WriteLine("coffee is ready");

Egg eggs = FryEggs(2);


Console.WriteLine("eggs are ready");

Bacon bacon = FryBacon(3);


Console.WriteLine("bacon is ready");

Toast toast = ToastBread(2);


ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("toast is ready");

Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
}

private static Juice PourOJ()


{
Console.WriteLine("Pouring orange juice");
return new Juice();
}

private static void ApplyJam(Toast toast) =>


Console.WriteLine("Putting jam on the toast");
private static void ApplyButter(Toast toast) =>
Console.WriteLine("Putting butter on the toast");

private static Toast ToastBread(int slices)


{
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("Putting a slice of bread in the
toaster");
}
Console.WriteLine("Start toasting...");
Task.Delay(3000).Wait();
Console.WriteLine("Remove toast from toaster");

return new Toast();


}

private static Bacon FryBacon(int slices)


{
Console.WriteLine($"putting {slices} slices of bacon in the
pan");
Console.WriteLine("cooking first side of bacon...");
Task.Delay(3000).Wait();
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("flipping a slice of bacon");
}
Console.WriteLine("cooking the second side of bacon...");
Task.Delay(3000).Wait();
Console.WriteLine("Put bacon on plate");

return new Bacon();


}

private static Egg FryEggs(int howMany)


{
Console.WriteLine("Warming the egg pan...");
Task.Delay(3000).Wait();
Console.WriteLine($"cracking {howMany} eggs");
Console.WriteLine("cooking the eggs ...");
Task.Delay(3000).Wait();
Console.WriteLine("Put eggs on plate");

return new Egg();


}

private static Coffee PourCoffee()


{
Console.WriteLine("Pouring coffee");
return new Coffee();
}
}
}
The synchronously prepared breakfast took roughly 30 minutes because the total is the
sum of each task.

Computers don't interpret those instructions the same way people do. The computer
will block on each statement until the work is complete before moving on to the next
statement. That creates an unsatisfying breakfast. The later tasks wouldn't be started
until the earlier tasks had been completed. It would take much longer to create the
breakfast, and some items would have gotten cold before being served.

If you want the computer to execute the above instructions asynchronously, you must
write asynchronous code.

These concerns are important for the programs you write today. When you write client
programs, you want the UI to be responsive to user input. Your application shouldn't
make a phone appear frozen while it's downloading data from the web. When you write
server programs, you don't want threads blocked. Those threads could be serving other
requests. Using synchronous code when asynchronous alternatives exist hurts your
ability to scale out less expensively. You pay for those blocked threads.

Successful modern applications require asynchronous code. Without language support,


writing asynchronous code required callbacks, completion events, or other means that
obscured the original intent of the code. The advantage of the synchronous code is that
its step-by-step actions make it easy to scan and understand. Traditional asynchronous
models forced you to focus on the asynchronous nature of the code, not on the
fundamental actions of the code.

Don't block, await instead


The preceding code demonstrates a bad practice: constructing synchronous code to
perform asynchronous operations. As written, this code blocks the thread executing it
from doing any other work. It won't be interrupted while any of the tasks are in
progress. It would be as though you stared at the toaster after putting the bread in.
You'd ignore anyone talking to you until the toast popped.

Let's start by updating this code so that the thread doesn't block while tasks are
running. The await keyword provides a non-blocking way to start a task, then continue
execution when that task completes. A simple asynchronous version of the make a
breakfast code would look like the following snippet:

C#

static async Task Main(string[] args)


{
Coffee cup = PourCoffee();
Console.WriteLine("coffee is ready");

Egg eggs = await FryEggsAsync(2);


Console.WriteLine("eggs are ready");

Bacon bacon = await FryBaconAsync(3);


Console.WriteLine("bacon is ready");

Toast toast = await ToastBreadAsync(2);


ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("toast is ready");

Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
}

) Important

The total elapsed time is roughly the same as the initial synchronous version. The
code has yet to take advantage of some of the key features of asynchronous
programming.
 Tip

The method bodies of the FryEggsAsync , FryBaconAsync , and ToastBreadAsync have


all been updated to return Task<Egg> , Task<Bacon> , and Task<Toast> respectively.
The methods are renamed from their original version to include the "Async" suffix.
Their implementations are shown as part of the final version later in this article.

7 Note

The Main method returns Task , despite not having a return expression—this is by
design. For more information, see Evaluation of a void-returning async function.

This code doesn't block while the eggs or the bacon are cooking. This code won't start
any other tasks though. You'd still put the toast in the toaster and stare at it until it pops.
But at least, you'd respond to anyone that wanted your attention. In a restaurant where
multiple orders are placed, the cook could start another breakfast while the first is
cooking.

Now, the thread working on the breakfast isn't blocked while awaiting any started task
that hasn't yet finished. For some applications, this change is all that's needed. A GUI
application still responds to the user with just this change. However, for this scenario,
you want more. You don't want each of the component tasks to be executed
sequentially. It's better to start each of the component tasks before awaiting the
previous task's completion.

Start tasks concurrently


In many scenarios, you want to start several independent tasks immediately. Then, as
each task finishes, you can continue other work that's ready. In the breakfast analogy,
that's how you get breakfast done more quickly. You also get everything done close to
the same time. You'll get a hot breakfast.

The System.Threading.Tasks.Task and related types are classes you can use to reason
about tasks that are in progress. That enables you to write code that more closely
resembles the way you'd create breakfast. You'd start cooking the eggs, bacon, and
toast at the same time. As each requires action, you'd turn your attention to that task,
take care of the next action, then wait for something else that requires your attention.

You start a task and hold on to the Task object that represents the work. You'll await
each task before working with its result.
Let's make these changes to the breakfast code. The first step is to store the tasks for
operations when they start, rather than awaiting them:

C#

Coffee cup = PourCoffee();


Console.WriteLine("Coffee is ready");

Task<Egg> eggsTask = FryEggsAsync(2);


Egg eggs = await eggsTask;
Console.WriteLine("Eggs are ready");

Task<Bacon> baconTask = FryBaconAsync(3);


Bacon bacon = await baconTask;
Console.WriteLine("Bacon is ready");

Task<Toast> toastTask = ToastBreadAsync(2);


Toast toast = await toastTask;
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("Toast is ready");

Juice oj = PourOJ();
Console.WriteLine("Oj is ready");
Console.WriteLine("Breakfast is ready!");

The preceding code won't get your breakfast ready any faster. The tasks are all await ed
as soon as they are started. Next, you can move the await statements for the bacon and
eggs to the end of the method, before serving breakfast:

C#

Coffee cup = PourCoffee();


Console.WriteLine("Coffee is ready");

Task<Egg> eggsTask = FryEggsAsync(2);


Task<Bacon> baconTask = FryBaconAsync(3);
Task<Toast> toastTask = ToastBreadAsync(2);

Toast toast = await toastTask;


ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("Toast is ready");
Juice oj = PourOJ();
Console.WriteLine("Oj is ready");

Egg eggs = await eggsTask;


Console.WriteLine("Eggs are ready");
Bacon bacon = await baconTask;
Console.WriteLine("Bacon is ready");
Console.WriteLine("Breakfast is ready!");

The asynchronously prepared breakfast took roughly 20 minutes, this time savings is
because some tasks ran concurrently.

The preceding code works better. You start all the asynchronous tasks at once. You await
each task only when you need the results. The preceding code may be similar to code in
a web application that makes requests to different microservices, then combines the
results into a single page. You'll make all the requests immediately, then await all those
tasks and compose the web page.

Composition with tasks


You have everything ready for breakfast at the same time except the toast. Making the
toast is the composition of an asynchronous operation (toasting the bread), and
synchronous operations (adding the butter and the jam). Updating this code illustrates
an important concept:

) Important

The composition of an asynchronous operation followed by synchronous work is an


asynchronous operation. Stated another way, if any portion of an operation is
asynchronous, the entire operation is asynchronous.
The preceding code showed you that you can use Task or Task<TResult> objects to hold
running tasks. You await each task before using its result. The next step is to create
methods that represent the combination of other work. Before serving breakfast, you
want to await the task that represents toasting the bread before adding butter and jam.
You can represent that work with the following code:

C#

static async Task<Toast> MakeToastWithButterAndJamAsync(int number)


{
var toast = await ToastBreadAsync(number);
ApplyButter(toast);
ApplyJam(toast);

return toast;
}

The preceding method has the async modifier in its signature. That signals to the
compiler that this method contains an await statement; it contains asynchronous
operations. This method represents the task that toasts the bread, then adds butter and
jam. This method returns a Task<TResult> that represents the composition of those
three operations. The main block of code now becomes:

C#

static async Task Main(string[] args)


{
Coffee cup = PourCoffee();
Console.WriteLine("coffee is ready");

var eggsTask = FryEggsAsync(2);


var baconTask = FryBaconAsync(3);
var toastTask = MakeToastWithButterAndJamAsync(2);

var eggs = await eggsTask;


Console.WriteLine("eggs are ready");

var bacon = await baconTask;


Console.WriteLine("bacon is ready");

var toast = await toastTask;


Console.WriteLine("toast is ready");

Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
}
The previous change illustrated an important technique for working with asynchronous
code. You compose tasks by separating the operations into a new method that returns a
task. You can choose when to await that task. You can start other tasks concurrently.

Asynchronous exceptions
Up to this point, you've implicitly assumed that all these tasks complete successfully.
Asynchronous methods throw exceptions, just like their synchronous counterparts.
Asynchronous support for exceptions and error handling strives for the same goals as
asynchronous support in general: You should write code that reads like a series of
synchronous statements. Tasks throw exceptions when they can't complete successfully.
The client code can catch those exceptions when a started task is awaited . For example,
let's assume that the toaster catches fire while making the toast. You can simulate that
by modifying the ToastBreadAsync method to match the following code:

C#

private static async Task<Toast> ToastBreadAsync(int slices)


{
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("Putting a slice of bread in the toaster");
}
Console.WriteLine("Start toasting...");
await Task.Delay(2000);
Console.WriteLine("Fire! Toast is ruined!");
throw new InvalidOperationException("The toaster is on fire");
await Task.Delay(1000);
Console.WriteLine("Remove toast from toaster");

return new Toast();


}

7 Note

You'll get a warning when you compile the preceding code regarding unreachable
code. That's intentional, because once the toaster catches fire, operations won't
proceed normally.

Run the application after making these changes, and you'll output similar to the
following text:

Console
Pouring coffee
Coffee is ready
Warming the egg pan...
putting 3 slices of bacon in the pan
Cooking first side of bacon...
Putting a slice of bread in the toaster
Putting a slice of bread in the toaster
Start toasting...
Fire! Toast is ruined!
Flipping a slice of bacon
Flipping a slice of bacon
Flipping a slice of bacon
Cooking the second side of bacon...
Cracking 2 eggs
Cooking the eggs ...
Put bacon on plate
Put eggs on plate
Eggs are ready
Bacon is ready
Unhandled exception. System.InvalidOperationException: The toaster is on
fire
at AsyncBreakfast.Program.ToastBreadAsync(Int32 slices) in
Program.cs:line 65
at AsyncBreakfast.Program.MakeToastWithButterAndJamAsync(Int32 number) in
Program.cs:line 36
at AsyncBreakfast.Program.Main(String[] args) in Program.cs:line 24
at AsyncBreakfast.Program.<Main>(String[] args)

You'll notice quite a few tasks are completed between when the toaster catches fire and
the exception is observed. When a task that runs asynchronously throws an exception,
that Task is faulted. The Task object holds the exception thrown in the Task.Exception
property. Faulted tasks throw an exception when they're awaited.

There are two important mechanisms to understand: how an exception is stored in a


faulted task, and how an exception is unpackaged and rethrown when code awaits a
faulted task.

When code running asynchronously throws an exception, that exception is stored in the
Task . The Task.Exception property is a System.AggregateException because more than

one exception may be thrown during asynchronous work. Any exception thrown is
added to the AggregateException.InnerExceptions collection. If that Exception property
is null, a new AggregateException is created and the thrown exception is the first item in
the collection.

The most common scenario for a faulted task is that the Exception property contains
exactly one exception. When code awaits a faulted task, the first exception in the
AggregateException.InnerExceptions collection is rethrown. That's why the output from
this example shows an InvalidOperationException instead of an AggregateException .
Extracting the first inner exception makes working with asynchronous methods as
similar as possible to working with their synchronous counterparts. You can examine the
Exception property in your code when your scenario may generate multiple exceptions.

 Tip

We recommend that any argument validation exceptions emerge synchronously


from task-returning methods. For more information and an example of how to do
it, see Exceptions in task-returning methods.

Before going on, comment out these two lines in your ToastBreadAsync method. You
don't want to start another fire:

C#

Console.WriteLine("Fire! Toast is ruined!");


throw new InvalidOperationException("The toaster is on fire");

Await tasks efficiently


The series of await statements at the end of the preceding code can be improved by
using methods of the Task class. One of those APIs is WhenAll, which returns a Task that
completes when all the tasks in its argument list have completed, as shown in the
following code:

C#

await Task.WhenAll(eggsTask, baconTask, toastTask);


Console.WriteLine("Eggs are ready");
Console.WriteLine("Bacon is ready");
Console.WriteLine("Toast is ready");
Console.WriteLine("Breakfast is ready!");

Another option is to use WhenAny, which returns a Task<Task> that completes when
any of its arguments complete. You can await the returned task, knowing that it has
already finished. The following code shows how you could use WhenAny to await the
first task to finish and then process its result. After processing the result from the
completed task, you remove that completed task from the list of tasks passed to
WhenAny .
C#

var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };


while (breakfastTasks.Count > 0)
{
Task finishedTask = await Task.WhenAny(breakfastTasks);
if (finishedTask == eggsTask)
{
Console.WriteLine("Eggs are ready");
}
else if (finishedTask == baconTask)
{
Console.WriteLine("Bacon is ready");
}
else if (finishedTask == toastTask)
{
Console.WriteLine("Toast is ready");
}
await finishedTask;
breakfastTasks.Remove(finishedTask);
}

Near the end, you see the line await finishedTask; . The line await Task.WhenAny
doesn't await the finished task. It await s the Task returned by Task.WhenAny . The result
of Task.WhenAny is the task that has completed (or faulted). You should await that task
again, even though you know it's finished running. That's how you retrieve its result, or
ensure that the exception causing it to fault gets thrown.

After all those changes, the final version of the code looks like this:

C#

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace AsyncBreakfast
{
// These classes are intentionally empty for the purpose of this
example. They are simply marker classes for the purpose of demonstration,
contain no properties, and serve no other purpose.
internal class Bacon { }
internal class Coffee { }
internal class Egg { }
internal class Juice { }
internal class Toast { }

class Program
{
static async Task Main(string[] args)
{
Coffee cup = PourCoffee();
Console.WriteLine("coffee is ready");

var eggsTask = FryEggsAsync(2);


var baconTask = FryBaconAsync(3);
var toastTask = MakeToastWithButterAndJamAsync(2);

var breakfastTasks = new List<Task> { eggsTask, baconTask,


toastTask };
while (breakfastTasks.Count > 0)
{
Task finishedTask = await Task.WhenAny(breakfastTasks);
if (finishedTask == eggsTask)
{
Console.WriteLine("eggs are ready");
}
else if (finishedTask == baconTask)
{
Console.WriteLine("bacon is ready");
}
else if (finishedTask == toastTask)
{
Console.WriteLine("toast is ready");
}
await finishedTask;
breakfastTasks.Remove(finishedTask);
}

Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
}

static async Task<Toast> MakeToastWithButterAndJamAsync(int number)


{
var toast = await ToastBreadAsync(number);
ApplyButter(toast);
ApplyJam(toast);

return toast;
}

private static Juice PourOJ()


{
Console.WriteLine("Pouring orange juice");
return new Juice();
}

private static void ApplyJam(Toast toast) =>


Console.WriteLine("Putting jam on the toast");

private static void ApplyButter(Toast toast) =>


Console.WriteLine("Putting butter on the toast");
private static async Task<Toast> ToastBreadAsync(int slices)
{
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("Putting a slice of bread in the
toaster");
}
Console.WriteLine("Start toasting...");
await Task.Delay(3000);
Console.WriteLine("Remove toast from toaster");

return new Toast();


}

private static async Task<Bacon> FryBaconAsync(int slices)


{
Console.WriteLine($"putting {slices} slices of bacon in the
pan");
Console.WriteLine("cooking first side of bacon...");
await Task.Delay(3000);
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("flipping a slice of bacon");
}
Console.WriteLine("cooking the second side of bacon...");
await Task.Delay(3000);
Console.WriteLine("Put bacon on plate");

return new Bacon();


}

private static async Task<Egg> FryEggsAsync(int howMany)


{
Console.WriteLine("Warming the egg pan...");
await Task.Delay(3000);
Console.WriteLine($"cracking {howMany} eggs");
Console.WriteLine("cooking the eggs ...");
await Task.Delay(3000);
Console.WriteLine("Put eggs on plate");

return new Egg();


}

private static Coffee PourCoffee()


{
Console.WriteLine("Pouring coffee");
return new Coffee();
}
}
}
The final version of the asynchronously prepared breakfast took roughly 6 minutes
because some tasks ran concurrently, and the code monitored multiple tasks at once
and only took action when it was needed.

This final code is asynchronous. It more accurately reflects how a person would cook a
breakfast. Compare the preceding code with the first code sample in this article. The
core actions are still clear from reading the code. You can read this code the same way
you'd read those instructions for making a breakfast at the beginning of this article. The
language features for async and await provide the translation every person makes to
follow those written instructions: start tasks as you can and don't block waiting for tasks
to complete.

Next steps
Explore real world scenarios for asynchronous programs
Programação assíncrona
Artigo • 10/05/2023

Se você tiver qualquer necessidade vinculada à E/S (como a solicitação de dados de


uma rede, o acesso a um banco de dados ou a leitura e gravação em um sistema de
arquivos), você pode querer usar a programação assíncrona. Você também pode ter
código vinculado à CPU, como a execução de um cálculo dispendioso, que também é
um bom cenário para escrever código assíncrono.

O C# tem um modelo de programação assíncrona em nível de linguagem que permite


escrever facilmente código assíncrono sem precisar manipular retornos de chamada ou
estar em conformidade com uma biblioteca que dê suporte à assincronia. Ele segue o
que é conhecido como TAP (Padrão assíncrono baseado em tarefa).

Visão geral do modelo assíncrono


O núcleo da programação assíncrona são os objetos Task e Task<T> , que modelam as
operações assíncronas. Eles têm suporte das palavras-chave async e await . O modelo é
bastante simples na maioria dos casos:

Para código vinculado à E/S, você aguarda uma operação que retorna um Task ou
Task<T> dentro de um método async .

Para o código vinculado à CPU, você aguarda uma operação iniciada em um


thread em segundo plano com o método Task.Run.

É na palavra-chave await que a mágica acontece. Ela cede o controle para o chamador
do método que executou await e, em última instância, permite que uma interface do
usuário tenha capacidade de resposta ou que um serviço seja elástico. Embora existam
maneiras de abordar o código assíncrono diferentes de async e await , este artigo se
concentra nos constructos no nível da linguagem.

Exemplo vinculado à E/S: baixar dados de um serviço


Web
Talvez você queira baixar alguns dados de um serviço Web quando um botão for
pressionado, mas não deseja bloquear o thread da interface do usuário. Isso pode ser
feito assim, usando a classe System.Net.Http.HttpClient:

C#
private readonly HttpClient _httpClient = new HttpClient();

downloadButton.Clicked += async (o, e) =>


{
// This line will yield control to the UI as the request
// from the web service is happening.
//
// The UI thread is now free to perform other work.
var stringData = await _httpClient.GetStringAsync(URL);
DoSomethingWithData(stringData);
};

O código expressa a intenção (baixar alguns dados de forma assíncrona) sem se prender
à interação com objetos Task .

Exemplo vinculado à CPU: executar um cálculo para um


jogo
Digamos que você está escrevendo um jogo para dispositivo móvel em que, ao
pressionar um botão, poderá causar danos a muitos inimigos na tela. A realização do
cálculo de dano pode ser dispendiosa e fazê-lo no thread da interface do usuário faria
com que o jogo parecesse pausar durante a realização do cálculo!

A melhor maneira de lidar com isso é iniciar um thread em segundo plano que faz o
trabalho usando Task.Run , e aguardar o resultado usando await . Isso permitirá que a
interface do usuário funcione de maneira suave enquanto o trabalho está sendo feito.

C#

private DamageResult CalculateDamageDone()


{
// Code omitted:
//
// Does an expensive calculation and returns
// the result of that calculation.
}

calculateButton.Clicked += async (o, e) =>


{
// This line will yield control to the UI while CalculateDamageDone()
// performs its work. The UI thread is free to perform other work.
var damageResult = await Task.Run(() => CalculateDamageDone());
DisplayDamage(damageResult);
};
Esse código expressa claramente a intenção do evento de clique do botão. Ele não
requer o gerenciamento manual de um thread em segundo plano, e faz isso sem
bloqueios.

O que acontece nos bastidores


No lado C# das coisas, o compilador transforma seu código em uma máquina de estado
que mantém o controle de situações como transferir a execução quando uma await é
alcançada e retomar a execução quando um trabalho em segundo plano for concluído.

Para os que gostam da teoria, essa é uma implementação do Modelo Promise de


assincronia .

Informações importantes para entender


O código assíncrono pode ser usado tanto para o código vinculado à E/S quanto
vinculado à CPU, mas de maneira diferente para cada cenário.
O código assíncrono usa Task<T> e Task , que são constructos usados para
modelar o trabalho que está sendo feito em segundo plano.
A palavra-chave async transforma um método em um método assíncrono, o que
permite que você use a palavra-chave await em seu corpo.
Quando a palavra-chave await é aplicada, ela suspende o método de chamada e
transfere o controle de volta ao seu chamador até que a tarefa em espera seja
concluída.
A await só pode ser usada dentro de um método assíncrono.

Reconhecer trabalho vinculado à CPU e


vinculado à E/S
Os primeiros dois exemplos deste guia mostraram como você pode usar async e await
para trabalho vinculado à E/S e vinculado à CPU. É fundamental que você saiba
identificar quando um trabalho que você precisa fazer é vinculado à E/S ou vinculado à
CPU, porque isso pode afetar significativamente o desempenho do seu código e
poderia potencialmente levar ao uso indevido de determinados constructos.

Aqui estão duas perguntas que devem ser feitas antes de escrever qualquer código:

1. Seu código ficará em "espera" por alguma coisa, como dados de um banco de
dados?
Se a resposta é "sim", seu trabalho é vinculado à E/S.

2. Seu código executará uma computação dispendiosa?

Se você respondeu "sim", seu trabalho é vinculado à CPU.

Se o seu trabalho for vinculado à E/S, use async e await sem Task.Run . Você não deve
usar a biblioteca de paralelismo de tarefas.

Se o seu trabalho for vinculado à CPU e você se importa com a capacidade de resposta,
use async e await , mas gere o trabalho em outro thread com Task.Run . Se o trabalho
for adequado para a simultaneidade e paralelismo, você também deverá considerar o
uso da Biblioteca de paralelismo de tarefas.

Além disso, você sempre deve medir a execução do seu código. Por exemplo, talvez
você tenha uma situação em que seu trabalho vinculado à CPU não é caro o suficiente
em comparação com os custos gerais das trocas de contexto ao realizar o
multithreading. Cada opção tem vantagens e desvantagens e você deve escolher o que
é correto para a sua situação.

Mais exemplos
Os exemplos a seguir demonstram várias maneiras para escrever código assíncrono no
C#. Elas abordam alguns cenários diferentes que você pode encontrar.

Extrair dados de uma rede


Este snippet de código baixa o HTML da página inicial em
https://dotnetfoundation.org e conta o número de vezes que a cadeia de caracteres
".NET" ocorre no HTML. Ele usa o ASP.NET para definir um método do controlador da
API Web que realiza essa tarefa, retornando o número.

7 Observação

Se você pretende fazer análise de HTML no código de produção, não use


expressões regulares. Use uma biblioteca de análise.

C#

private readonly HttpClient _httpClient = new HttpClient();

[HttpGet, Route("DotNetCount")]
public async Task<int> GetDotNetCount()
{
// Suspends GetDotNetCount() to allow the caller (the web server)
// to accept another request, rather than blocking on this one.
var html = await
_httpClient.GetStringAsync("https://dotnetfoundation.org");

return Regex.Matches(html, @"\.NET").Count;


}

Aqui está o mesmo cenário escrito para um aplicativo universal do Windows, que
executa a mesma tarefa quando um botão for pressionado:

C#

private readonly HttpClient _httpClient = new HttpClient();

private async void OnSeeTheDotNetsButtonClick(object sender, RoutedEventArgs


e)
{
// Capture the task handle here so we can await the background task
later.
var getDotNetFoundationHtmlTask =
_httpClient.GetStringAsync("https://dotnetfoundation.org");

// Any other work on the UI thread can be done here, such as enabling a
Progress Bar.
// This is important to do here, before the "await" call, so that the
user
// sees the progress bar before execution of this method is yielded.
NetworkProgressBar.IsEnabled = true;
NetworkProgressBar.Visibility = Visibility.Visible;

// The await operator suspends OnSeeTheDotNetsButtonClick(), returning


control to its caller.
// This is what allows the app to be responsive and not block the UI
thread.
var html = await getDotNetFoundationHtmlTask;
int count = Regex.Matches(html, @"\.NET").Count;

DotNetCountLabel.Text = $"Number of .NETs on dotnetfoundation.org:


{count}";

NetworkProgressBar.IsEnabled = false;
NetworkProgressBar.Visibility = Visibility.Collapsed;
}

Aguardar a conclusão de várias tarefas


Você pode encontrar em uma situação em que precisa recuperar várias partes de dados
simultaneamente. A API Task contém dois métodos, Task.WhenAll e Task.WhenAny, que
permitem escrever um código assíncrono que realiza uma espera sem bloqueio em
vários trabalhos em segundo plano.

Este exemplo mostra como você pode obter os dados User para um conjunto de
userId s.

C#

public async Task<User> GetUserAsync(int userId)


{
// Code omitted:
//
// Given a user Id {userId}, retrieves a User object corresponding
// to the entry in the database with {userId} as its Id.
}

public static async Task<IEnumerable<User>> GetUsersAsync(IEnumerable<int>


userIds)
{
var getUserTasks = new List<Task<User>>();
foreach (int userId in userIds)
{
getUserTasks.Add(GetUserAsync(userId));
}

return await Task.WhenAll(getUserTasks);


}

Aqui está outro jeito de escrever isso de forma mais sucinta usando LINQ:

C#

public async Task<User> GetUserAsync(int userId)


{
// Code omitted:
//
// Given a user Id {userId}, retrieves a User object corresponding
// to the entry in the database with {userId} as its Id.
}

public static async Task<User[]> GetUsersAsync(IEnumerable<int> userIds)


{
var getUserTasks = userIds.Select(id => GetUserAsync(id)).ToArray();
return await Task.WhenAll(getUserTasks);
}

Embora seja menos código, tome cuidado ao misturar LINQ com código assíncrono.
Como o LINQ utiliza a execução adiada (lenta), as chamadas assíncronas não
acontecerão imediatamente como em um loop foreach , a menos que você force a
sequência gerada a iterar com uma chamada a .ToList() ou .ToArray() . O exemplo
acima usa Enumerable.ToArray para executar a consulta ansiosamente e armazenar os
resultados em uma matriz. Isso força o código id => GetUserAsync(id) a executar e
iniciar a tarefa.

Conselhos e informações importantes


Com a programação assíncrona, há alguns detalhes para ter em mente que podem
impedir um comportamento inesperado.

async Os métodos precisam ter uma palavra-chave await no corpo ou eles nunca

transferirão!

É importante ter isso em mente. Se await não for usado no corpo de um método
async , o compilador do C# gerará um aviso, mas o código será compilado e
executado como se fosse um método normal. Isso também seria extremamente
ineficiente, pois a máquina de estado gerada pelo compilador do C# para o
método assíncrono não realizaria nada.

Você deve adicionar "Async" como o sufixo de cada nome de método assíncrono
que escrever.

Essa é a convenção usada no .NET para diferenciar mais facilmente os métodos


síncronos e assíncronos. Isso não se aplica, necessariamente, a alguns métodos
que não são explicitamente chamados pelo seu código (como manipuladores de
eventos ou métodos do controlador da Web). Como eles não são chamados
explicitamente pelo seu código, não é tão importante ser explícito em relação à
nomenclatura.

async void O só deve ser usado para manipuladores de eventos.

O async void é a única maneira de permitir que os manipuladores de eventos


assíncronos trabalhem, pois os eventos não têm tipos de retorno (portanto, não
podem fazer uso de Task e Task<T> ). Qualquer outro uso de async void não
segue o modelo TAP e pode ser um desafio utilizá-lo, como:
As exceções lançadas em um método async void não podem ser capturadas
fora desse método.
Métodos async void são difíceis de testar.
Métodos async void poderão causar efeitos colaterais indesejados se o
chamador não estiver esperando que eles sejam assíncronos.

Vá com cuidado ao usar lambdas assíncronas em expressões LINQ


As expressões lambda em LINQ usam a execução adiada, o que significa que o
código poderia acabar executando em um momento que você não está
esperando. A introdução de tarefas de bloqueio no meio disso poderia facilmente
resultar em um deadlock, se não estivessem escritas corretamente. Além disso, o
aninhamento de código assíncrono dessa maneira também pode dificultar a
ponderação a respeito da execução do código. A assíncrona e a LINQ são
poderosas, mas devem ser usadas de uma maneira mais cuidadosa e clara possível.

Escrever código que aguarda tarefas de uma maneira sem bloqueio

Bloquear o thread atual como um meio de aguardar a conclusão de uma Task


pode resultar em deadlocks e threads de contexto bloqueados e pode exigir
tratamento de erros significativamente mais complexo. A tabela a seguir fornece
diretrizes de como lidar com a espera de tarefas de uma forma sem bloqueio:

Use isto... Em vez disto... Quando desejar fazer isso...

await Task.Wait ou Recuperação do resultado de uma tarefa em


Task.Result segundo plano

await Task.WaitAny Aguardar a conclusão de qualquer tarefa


Task.WhenAny

await Task.WaitAll Aguardar a conclusão de todas as tarefas


Task.WhenAll

await Task.Delay Thread.Sleep Aguardar por um período de tempo

Considere usar ValueTask sempre que possível

Retornar um objeto Task de métodos assíncronos pode introduzir gargalos de


desempenho em determinados caminhos. Task é um tipo de referência, portanto,
usá-lo significa alocar um objeto. Em casos em que um método declarado com o
modificador async retorna um resultado armazenado em cache ou é concluído de
forma síncrona, as alocações extras podem se tornar um custo de tempo
significativo em seções críticas de desempenho de código. Isso pode se tornar
caro se essas alocações ocorrem em loops rígidos. Para obter mais informações,
consulte Tipos de retorno assíncronos generalizados.

Considere usar ConfigureAwait(false)

Uma pergunta comum é: "quando devo usar o método


Task.ConfigureAwait(Boolean)?". O método permite que uma instância Task
configure seu awaiter. Essa é uma consideração importante, e defini-la
incorretamente pode potencialmente ter implicações de desempenho e até
deadlocks. Para obter mais informações sobre ConfigureAwait , consulte FAQ do
ConfigureAwait .

Escrever código com menos monitoração de estado

Não depender do estado de objetos globais ou da execução de determinados


métodos. Em vez disso, depender apenas dos valores retornados dos métodos. Por
quê?
Será mais fácil raciocinar sobre o código.
O código será mais fácil de testar.
Misturar código assíncrono e síncrono será muito mais simples.
As condições de corrida poderão, normalmente, ser completamente evitadas.
Dependendo dos valores retornados, a coordenação de código assíncrono se
tornará simples.
(Bônus) funciona muito bem com a injeção de dependência.

Uma meta recomendada é alcançar a Transparência referencial completa ou quase


completa em seu código. Isso resultará em uma base de código previsível, testável e de
fácil manutenção.

Outros recursos
Modelo de programação assíncrono de tarefas (C#).

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.
Modelo de programação assíncrona de
tarefa
Artigo • 28/03/2023

É possível evitar gargalos de desempenho e aprimorar a resposta geral do seu aplicativo


usando a programação assíncrona. No entanto, as técnicas tradicionais para escrever
aplicativos assíncronos podem ser complicadas, dificultando sua escrita, depuração e
manutenção.

O C# apresenta uma abordagem simplificada à programação assíncrona que faz uso do


suporte assíncrono no runtime do .NET. O compilador faz o trabalho difícil que o
desenvolvedor costumava fazer, e seu aplicativo mantém a estrutura lógica que se
assemelha ao código síncrono. Como resultado, você obtém todas as vantagens da
programação assíncrona com uma fração do esforço.

Este tópico oferece uma visão geral de quando e como usar a programação assíncrona
e inclui links para tópicos de suporte que contêm detalhes e exemplos.

A assincronia melhora a capacidade de


resposta
A assincronia é essencial para atividades que são potencialmente de bloqueio, como o
acesso via Web. O acesso a um recurso da Web às vezes é lento ou atrasado. Se tal
atividade for bloqueada em um processo síncrono, todo o aplicativo deverá esperar. Em
um processo assíncrono, o aplicativo poderá prosseguir com outro trabalho que não
dependa do recurso da Web até a tarefa potencialmente causadora do bloqueio
terminar.

A tabela a seguir mostra a áreas típicas onde a programação assíncrona melhora a


resposta. As APIs listadas do .NET e do Windows Runtime contêm métodos que dão
suporte à programação assíncrona.

Área do aplicativo Tipos .NET com métodos Tipos Windows Runtime com métodos
assíncronos assíncronos

Acesso à Web HttpClient Windows.Web.Http.HttpClient


SyndicationClient

Trabalhando com JsonSerializer StorageFile


arquivos StreamReader
StreamWriter
Área do aplicativo Tipos .NET com métodos Tipos Windows Runtime com métodos
assíncronos assíncronos

XmlReader
XmlWriter

Trabalhando com MediaCapture


imagens BitmapEncoder
BitmapDecoder

Programação WCF Operações síncronas e


assíncronas

A assincronia é especialmente importante para aplicativos que acessam o thread de


interface de usuário porque todas as atividades relacionadas à interface do usuário
normalmente compartilham um único thread. Se um processo for bloqueado em um
aplicativo síncrono, todos serão bloqueados. Seu aplicativo para de responder, o que
poderia levar você a concluir que ele falhou quando, na verdade, está apenas
aguardando.

Quando você usa métodos assíncronos, o aplicativo continua a responder à interface do


usuário. Você poderá redimensionar ou minimizar uma janela, por exemplo, ou fechar o
aplicativo se você não desejar aguardar sua conclusão.

A abordagem baseada em assincronia adiciona o equivalente de uma transmissão


automática à lista de opções disponíveis para escolha ao criar operações assíncronas.
Ou seja, você obtém todos os benefícios da programação assíncrona tradicional, mas
com muito menos esforço do desenvolvedor.

Métodos assíncronos são mais fáceis de


escrever
As palavras-chave async e await em C# são a parte central da programação assíncrona.
Ao usar essas duas palavras-chave, você poderá usar recursos do .NET Framework, do
.NET Core ou do Windows Runtime para criar um método assíncrono quase que tão
facilmente quanto criar um método síncrono. Os métodos assíncronos que você define
usando a palavra-chave async são chamados de métodos assíncronos.

O exemplo a seguir mostra um método assíncrono. Você deve estar familiarizado com
quase tudo no código.

Você pode encontrar um exemplo completo de WPF (Windows Presentation


Foundation) disponível para download na Programação assíncrona com async e await
em C#.
C#

public async Task<int> GetUrlContentLengthAsync()


{
var client = new HttpClient();

Task<string> getStringTask =
client.GetStringAsync("https://learn.microsoft.com/dotnet");

DoIndependentWork();

string contents = await getStringTask;

return contents.Length;
}

void DoIndependentWork()
{
Console.WriteLine("Working...");
}

Você pode aprender várias práticas com a amostra anterior. Comece com a assinatura
do método. Ele inclui o modificador async . O tipo de retorno é Task<int> (confira a
seção "Tipos de retorno" para obter mais opções). O nome do método termina com
Async . No corpo do método, GetStringAsync retorna uma Task<string> . Isso significa

que, quando você executar await em uma tarefa, obterá uma string ( contents ). Antes
de aguardar a tarefa, você poderá fazer um trabalho que não dependa da string em
GetStringAsync .

Preste muita atenção no operador await . Ele suspende GetUrlContentLengthAsync :

GetUrlContentLengthAsync não poderá continuar enquanto getStringTask não for

concluída.
Enquanto isso, o controle é retornado ao chamador de GetUrlContentLengthAsync .
O controle será retomado aqui quando a getStringTask for concluída.
Em seguida, o operador await recupera o resultado string de getStringTask .

A instrução de retorno especifica um resultado inteiro. Os métodos que estão


aguardando GetUrlContentLengthAsync recuperar o valor de comprimento.

Se GetUrlContentLengthAsync não tiver nenhum trabalho que possa fazer entre chamar
GetStringAsync e aguardar a conclusão, você poderá simplificar o código ao chamar e

esperar na instrução única a seguir.

C#
string contents = await
client.GetStringAsync("https://learn.microsoft.com/dotnet");

As seguintes características resumem o que transforma o exemplo anterior em um


método assíncrono:

A assinatura do método inclui um modificador async .

O nome de um método assíncrono, por convenção, termina com um sufixo


"Async".

O tipo de retorno é um dos seguintes tipos:


Task<TResult> se o método possui uma instrução de retorno em que o
operando tem o tipo TResult .
Task se o método não possui instrução de retorno alguma ou se ele possui uma
instrução de retorno sem operando.
void se você estiver escrevendo um manipulador de eventos assíncronos.

Qualquer outro tipo que tenha um método GetAwaiter .

Para obter mais informações, confira a seção Tipos e parâmetros de retorno.

O método geralmente inclui, pelo menos, uma expressão await , que marca um
ponto em que o método não poderá continuar enquanto a operação assíncrona
aguardada não for concluída. Enquanto isso, o método é suspenso e o controle
retorna para o chamador do método. A próxima seção deste tópico ilustra o que
acontece no ponto de suspensão.

Em métodos assíncronos, você usa as palavras-chave e os tipos fornecidos para indicar


o que deseja fazer, e o compilador faz o resto, inclusive acompanhar o que deve
acontecer quando o controle retorna a um ponto de espera em um método suspenso.
Alguns processos de rotina, como loops e a manipulação de exceções, podem ser
difíceis de manipular em um código assíncrono tradicional. Em um método assíncrono,
você escreve esses elementos da mesma forma que faria em uma solução síncrona, e o
problema é resolvido.

Para obter mais informações sobre assincronia nas versões anteriores do .NET
Framework, confira Programação assíncrona do .NET Framework tradicional e do TPL.

O que acontece em um método assíncrono


O mais importante que você deve compreender na programação assíncrona é a forma
como o fluxo de controle avança de um método para outro. O diagrama a seguir pode
ser usado para conduzi-lo pelo processo:

Os números do diagrama correspondem às etapas a seguir, iniciadas quando um


método de chamada chama o método assíncrono.

1. Um método de chamada chama e aguarda o método assíncrono


GetUrlContentLengthAsync .

2. GetUrlContentLengthAsync cria uma instância de HttpClient e chama o método


assíncrono GetStringAsync para baixar o conteúdo de um site como uma cadeia de
caracteres.

3. Algo acontece em GetStringAsync que suspende o andamento. Talvez ele deva


aguardar o download de um site ou alguma outra atividade causadora de
bloqueio. Para evitar o bloqueio de recursos, GetStringAsync transfere o controle
para seu chamador, GetUrlContentLengthAsync .

GetStringAsync retorna um Task<TResult>, em que TResult é uma cadeia de

caracteres, e GetUrlContentLengthAsync atribui a tarefa à variável getStringTask . A


tarefa representa o processo contínuo para a chamada a GetStringAsync , com um
compromisso de produzir um valor de cadeia de caracteres real quando o trabalho
estiver concluído.

4. Como o getStringTask ainda não foi esperado, GetUrlContentLengthAsync pode


continuar com outro trabalho que não depende do resultado final de
GetStringAsync . O trabalho é representado por uma chamada ao método síncrono
DoIndependentWork .

5. DoIndependentWork é um método síncrono que faz seu trabalho e retorna ao seu


chamador.

6. GetUrlContentLengthAsync está sem trabalho que ele possa executar sem um


resultado de getStringTask . Em seguida, GetUrlContentLengthAsync deseja calcular
e retornar o comprimento da cadeia de caracteres baixada, mas o método não
poderá calcular o valor enquanto o método tiver a cadeia de caracteres.

Portanto, GetUrlContentLengthAsync usa um operador await para suspender seu


andamento e para transferir o controle para o método que chamou
GetUrlContentLengthAsync . GetUrlContentLengthAsync retorna um Task<int> ao

chamador. A tarefa representa uma promessa de produzir um resultado inteiro


que é o comprimento da cadeia de caracteres baixada.

7 Observação

Se GetStringAsync (e, portanto, getStringTask ) for concluído antes que


GetUrlContentLengthAsync o aguarde, o controle permanecerá em
GetUrlContentLengthAsync . A despesa de suspender e depois retornar para

GetUrlContentLengthAsync seria desperdiçada caso o processo assíncrono

chamado getStringTask já tivesse sido concluído e GetUrlContentLengthAsync


não tivesse que aguardar o resultado final.

Dentro do método de chamada, o padrão de processamento continua. O


chamador pode fazer outro trabalho que não dependa do resultado de
GetUrlContentLengthAsync antes de aguardar o resultado, ou o chamador pode

aguardar imediatamente. O método de chamada está aguardando


GetUrlContentLengthAsync e GetUrlContentLengthAsync está aguardando
GetStringAsync .

7. GetStringAsync completa e produz um resultado de cadeia de caracteres. O


resultado da cadeia de caracteres não é retornado pela chamada para
GetStringAsync da maneira que você poderia esperar. (Lembre-se que o método já

retornou uma tarefa na etapa 3.) Em vez disso, o resultado da cadeia de caracteres
é armazenado na tarefa que representa a conclusão do método, getStringTask . O
operador await recupera o resultado de getStringTask . A instrução de atribuição
atribui o resultado retornado a contents .
8. Quando GetUrlContentLengthAsync tem o resultado da cadeia de caracteres, o
método pode calcular o comprimento da cadeia de caracteres. Em seguida, o
trabalho de GetUrlContentLengthAsync também é concluído e o manipulador de
eventos de espera poderá retomar. No exemplo completo no final do tópico, é
possível confirmar que o manipulador de eventos recuperou e imprimiu o valor do
comprimento do resultado. Se você não tiver experiência em programação
assíncrona, considere por um minuto a diferença entre o comportamento síncrono
e o assíncrono. Um método síncrono retorna quando seu trabalho é concluído
(etapa 5), mas um método assíncrono retorna um valor de tarefa quando seu
trabalho está suspenso (etapas 3 e 6). Quando o método assíncrono
eventualmente concluir seu trabalho, a tarefa será marcada como concluída e o
resultado, se houver, será armazenado na tarefa.

Métodos assíncronos da API


Você pode estar curioso para saber onde encontrar métodos como GetStringAsync que
oferecem suporte à programação assíncrona. O .NET Framework 4.5 ou versão superior
e o .NET Core contêm muitos membros que funcionam com async e com await . É
possível reconhecê-los pelo sufixo “Async” que é acrescentado ao nome do membro e
pelo tipo de retorno de Task ou de Task<TResult>. Por exemplo, a classe
System.IO.Stream contém métodos como CopyToAsync, ReadAsync e WriteAsync,

juntamente com os métodos síncronos CopyTo, Read e Write.

O Windows Runtime também contém vários métodos que você pode usar com async e
await em aplicativos do Windows. Para obter mais informações, veja Threading e

programação assíncrona para o desenvolvimento da UWP e Programação assíncrona


(aplicativos da Windows Store) e Início Rápido: chamando APIs assíncronas em C# ou
Visual Basic se você usa versões anteriores do Windows Runtime.

Threads
Os métodos assíncronos destinam-se a ser operações não causadoras de bloqueios.
Uma expressão await em um método assíncrono não bloqueia o thread atual enquanto
a tarefa aguardada está em execução. Em vez disso, a expressão anterior assina o
restante do método como uma continuação e retorna o controle para o chamador do
método assíncrono.

As palavras-chave async e await não fazem com que threads adicionais sejam criados.
Os métodos assíncronos não exigem multithreading, pois um método assíncrono não
executa em seu próprio thread. O método é executado no contexto de sincronização
atual e usa tempo no thread somente quando o método está ativo. É possível usar
Task.Run para mover o trabalho de CPU associado a um thread em segundo plano, mas
um thread em segundo plano não ajuda com um processo que está apenas aguardando
que os resultados tornem-se disponíveis.

A abordagem baseada em async para a programação assíncrona é preferível às


abordagens existentes em quase todos os casos. Essa abordagem é especialmente mais
eficiente do que a classe BackgroundWorker para operações de entrada e saída, porque
o código é mais simples e você não precisa se proteger contra condições de corrida. Em
combinação com o método Task.Run, a programação assíncrona é melhor que
BackgroundWorker para operações associadas à CPU, porque a programação assíncrona
separa os detalhes de coordenação da execução do código de trabalho que Task.Run
transfere ao pool de threads.

async e await
Se você especificar que um método é assíncrono usando um modificador async, você
habilitará os dois recursos a seguir.

O método assíncrono marcado pode usar await para designar pontos de


suspensão. O operador await informa ao compilador que o método assíncrono
não poderá continuar além daquele ponto até que o processo assíncrono
aguardado seja concluído. Enquanto isso, o controle retorna para o chamador do
método assíncrono.

A suspensão de um método assíncrono em uma expressão await não constitui


uma saída de método e os blocos finally não são executados.

O método assíncrono marcado pode ele próprio ser aguardado por métodos que
o chamam.

Um método assíncrono normalmente contém uma ou mais ocorrências do operador


await , mas a ausência de expressões await não causa erro de compilação. Se um

método assíncrono não usa o operador await para marcar um ponto de suspensão, o
método é executado da mesma forma que um método síncrono, independentemente
do modificador async . O compilador emite um aviso para esses métodos.

async e await são palavras-chave contextuais. Para obter mais informações e exemplos,

consulte os seguintes tópicos:

async
await
Tipos e parâmetros de retorno
Um método assíncrono normalmente retorna Task ou Task<TResult>. Dentro de um
método assíncrono, um operador await é aplicado a uma tarefa que é retornada de
uma chamada para outro método assíncrono.

Você especificará Task<TResult> como o tipo de retorno se o método contiver uma


instrução return que especifica um operando do tipo TResult .

Você usará Task como o tipo de retorno caso o método não possua nenhuma instrução
return ou tenha uma instrução return que não retorna um operando.

Você também pode especificar qualquer outro tipo de retorno, desde que o tipo inclua
um método GetAwaiter . ValueTask<TResult> é um exemplo de tal tipo. Ele está
disponível no pacote NuGet System.Threading.Tasks.Extension .

O seguinte exemplo mostra como você declara e chama um método que retorna
Task<TResult> ou Task:

C#

async Task<int> GetTaskOfTResultAsync()


{
int hours = 0;
await Task.Delay(0);

return hours;
}

Task<int> returnedTaskTResult = GetTaskOfTResultAsync();


int intResult = await returnedTaskTResult;
// Single line
// int intResult = await GetTaskOfTResultAsync();

async Task GetTaskAsync()


{
await Task.Delay(0);
// No return statement needed
}

Task returnedTask = GetTaskAsync();


await returnedTask;
// Single line
await GetTaskAsync();

Cada tarefa retornada representa um trabalho em andamento. Uma tarefa encapsula


informações sobre o estado do processo assíncrono e, consequentemente, o resultado
final do processo ou a exceção que o processo apresenta quando não é bem-sucedido.

Um método assíncrono também pode ter um tipo de retorno void . Esse tipo de retorno
é usado principalmente para definir manipuladores de eventos, onde o tipo de retorno
void é necessário. Os manipuladores de eventos assíncronos geralmente servem como

o ponto de partida para programas assíncronos.

Um método assíncrono que tem o tipo de retorno void não pode ser esperado, e o
chamador de um método que retorna nulo não pode capturar nenhuma exceção
lançada pelo método.

O método não pode declarar nenhum parâmetro in, ref ou out, mas pode chamar
métodos com tais parâmetros. Da mesma forma, um método assíncrono não pode
retornar um valor por referência, embora possa chamar métodos com valores
retornados ref.

Para obter mais informações e exemplos, confira Tipos de retorno assíncronos (C#).

As APIs assíncronas na programação do Windows Runtime têm um dos seguintes tipos


de retorno, que são semelhantes às tarefas:

IAsyncOperation<TResult>, que corresponde a Task<TResult>


IAsyncAction, que corresponde a Task
IAsyncActionWithProgress<TProgress>
IAsyncOperationWithProgress<TResult,TProgress>

Convenção de nomenclatura
Por convenção, os métodos que geralmente retornam tipos esperáveis (por exemplo,
Task , Task<T> , ValueTask e ValueTask<T> ) devem ter nomes que terminam com

"Async". Os métodos que iniciam uma operação assíncrona, mas não retornam um tipo
aguardável não devem ter nomes que terminam com "Async", mas podem começar com
"Begin", "Start" ou algum outro verbo que indique que esse método não retorna nem
gera o resultado da operação.

É possível ignorar a convenção quando um evento, uma classe base ou um contrato de


interface sugere um nome diferente. Por exemplo, você não deve renomear
manipuladores de eventos comuns, como OnButtonClick .

Artigos relacionados (Visual Studio)


Title Descrição

Como fazer várias solicitações da Web em Demonstra como iniciar várias tarefas ao mesmo
paralelo usando async e await (C#) tempo.

Tipos de retorno assíncronos (C#) Ilustra os tipos que os métodos assíncronos


podem retornar e explica quando cada tipo é
apropriado.

Cancelar tarefas com um token de Mostra como adicionar a seguinte funcionalidade à


cancelamento como um mecanismo de sua solução assíncrona:
sinalização.
- Cancelar uma lista de tarefas (C#)
- Cancelar tarefas após um período (C#)
- Processar tarefas assíncronas conforme elas
forem concluídas (C#)

Como usar o Async para acessar arquivos Lista e demonstra as vantagens de usar async e
(C#) await para acessar arquivos.

TAP (padrão assíncrono baseado em Descreve um padrão assíncrono, o padrão é


tarefas) baseado nos tipos Task e Task<TResult>.

Vídeos sobre assincronia no Channel 9 Fornece links para uma variedade de vídeos sobre
programação assíncrona.

Confira também
Programação assíncrona com async e await
async
await

6 Collaborate with us on
GitHub .NET feedback
The source for this content can The .NET documentation is open
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.
Tipos de retorno assíncronos (C#)
Artigo • 10/05/2023

Métodos assíncronos podem conter os seguintes tipos de retorno:

Task, para um método assíncrono que executa uma operação, mas não retorna
nenhum valor.
Task<TResult>, para um método assíncrono que retorna um valor.
void , para um manipulador de eventos.

Qualquer tipo que tenha um método GetAwaiter acessível. O objeto retornado


pelo método GetAwaiter deve implementar a interface
System.Runtime.CompilerServices.ICriticalNotifyCompletion.
IAsyncEnumerable<T>, para um método assíncrono que retorna um fluxo
assíncrono.

Para obter mais informações sobre os métodos assíncronos, confira Programação


assíncrona com async e await (C#).

Também existem vários outros tipos específicos para cargas de trabalho do Windows:

DispatcherOperation, para operações assíncronas limitadas ao Windows.


IAsyncAction, para ações assíncronas na UWP que não retornam um valor.
IAsyncActionWithProgress<TProgress>, para ações assíncronas na UWP que
relatam o progresso, mas não retornam um valor.
IAsyncOperation<TResult>, para operações assíncronas na UWP que retornam um
valor.
IAsyncOperationWithProgress<TResult,TProgress>, para operações assíncronas na
UWP que relatam progresso e retornam um valor.

Tipo de retorno da tarefa


Os métodos assíncronos que não contêm uma instrução return ou que contêm uma
instrução return que não retorna um operando, normalmente têm um tipo de retorno
de Task. Esses métodos retornam void se eles são executados de forma síncrona. Se
você usar um tipo de retorno Task para um método assíncrono, um método de chamada
poderá usar um operador await para suspender a conclusão do chamador até que o
método assíncrono chamado seja concluído.

No exemplo a seguir, o método WaitAndApologizeAsync não contém uma instrução


return , então o método retorna um objeto Task. Retornar Task habilita a espera por
WaitAndApologizeAsync . O tipo Task não inclui uma propriedade Result porque ele não

tem nenhum valor retornado.

C#

public static async Task DisplayCurrentInfoAsync()


{
await WaitAndApologizeAsync();

Console.WriteLine($"Today is {DateTime.Now:D}");
Console.WriteLine($"The current time is {DateTime.Now.TimeOfDay:t}");
Console.WriteLine("The current temperature is 76 degrees.");
}

static async Task WaitAndApologizeAsync()


{
await Task.Delay(2000);

Console.WriteLine("Sorry for the delay...\n");


}
// Example output:
// Sorry for the delay...
//
// Today is Monday, August 17, 2020
// The current time is 12:59:24.2183304
// The current temperature is 76 degrees.

O WaitAndApologizeAsync é aguardado, usando uma instrução await, em vez de uma


expressão await, semelhante à instrução de chamada a um método síncrono de retorno
void. A aplicação de um operador await, nesse caso, não produz um valor. Quando o
operando direito de await é Task<TResult>, a expressão await produz o resultado T .
Quando o operando direito de await é Task, await e o operando dele são uma
instrução.

Você pode separar a chamada a WaitAndApologizeAsync da aplicação de um operador


await, como mostrado no código a seguir. No entanto, lembre-se que uma Task não
tem uma propriedade Result e que nenhum valor será produzido quando um operador
await for aplicado a uma Task .

O código a seguir separa a chamada ao método WaitAndApologizeAsync da espera pela


tarefa que o método retorna.

C#

Task waitAndApologizeTask = WaitAndApologizeAsync();

string output =
$"Today is {DateTime.Now:D}\n" +
$"The current time is {DateTime.Now.TimeOfDay:t}\n" +
"The current temperature is 76 degrees.\n";

await waitAndApologizeTask;
Console.WriteLine(output);

Tipo de retorno Task<TResult>


O tipo de retorno Task<TResult> é usado para um método assíncrono que contém uma
instrução return em que o operando é TResult .

No exemplo a seguir, o método GetLeisureHoursAsync contém uma instrução return


que retorna um número inteiro. A declaração do método deve especificar um tipo de
retorno igual a Task<int> . O método assíncrono FromResult é um espaço reservado
para uma operação que retorna DayOfWeek.

C#

public static async Task ShowTodaysInfoAsync()


{
string message =
$"Today is {DateTime.Today:D}\n" +
"Today's hours of leisure: " +
$"{await GetLeisureHoursAsync()}";

Console.WriteLine(message);
}

static async Task<int> GetLeisureHoursAsync()


{
DayOfWeek today = await Task.FromResult(DateTime.Now.DayOfWeek);

int leisureHours =
today is DayOfWeek.Saturday || today is DayOfWeek.Sunday
? 16 : 5;

return leisureHours;
}
// Example output:
// Today is Wednesday, May 24, 2017
// Today's hours of leisure: 5

Quando GetLeisureHoursAsync é chamado de dentro de uma expressão await no


método ShowTodaysInfo , a expressão await recupera o valor inteiro (o valor de
leisureHours ) que está armazenado na tarefa que é retornada pelo método

GetLeisureHours . Para obter mais informações sobre expressões await, consulte await.
Você pode entender melhor como await recupera o resultado de Task<T> separando a
chamada a GetLeisureHoursAsync da aplicação de await , como mostra o código a
seguir. Uma chamada ao método GetLeisureHoursAsync que não é aguardada
imediatamente, retorna um Task<int> , como você esperaria da declaração do método.
A tarefa é atribuída à variável getLeisureHoursTask no exemplo. Já que
getLeisureHoursTask é um Task<TResult>, ele contém uma propriedade Result do tipo
TResult . Nesse caso, TResult representa um tipo inteiro. Quando await é aplicado à

getLeisureHoursTask , a expressão await é avaliada como o conteúdo da propriedade

Result de getLeisureHoursTask . O valor é atribuído à variável ret .

) Importante

A propriedade Result é uma propriedade de bloqueio. Se você tentar acessá-la


antes que sua tarefa seja concluída, o thread que está ativo no momento será
bloqueado até que a tarefa seja concluída e o valor esteja disponível. Na maioria
dos casos, você deve acessar o valor usando await em vez de acessar a
propriedade diretamente.

O exemplo anterior recuperou o valor da propriedade Result para bloquear o


thread principal, de modo que o método Main pudesse imprimir message no
console antes do encerramento do aplicativo.

C#

var getLeisureHoursTask = GetLeisureHoursAsync();

string message =
$"Today is {DateTime.Today:D}\n" +
"Today's hours of leisure: " +
$"{await getLeisureHoursTask}";

Console.WriteLine(message);

Tipo de retorno nulo


O tipo de retorno void é usado em manipuladores de eventos assíncronos, que exigem
um tipo de retorno void . Para métodos diferentes de manipuladores de eventos que
não retornam um valor, você deve retornar um Task, porque não é possível esperar por
um método assíncrono que retorna void . Qualquer chamador desse método deve
continuar a conclusão sem esperar que o método assíncrono chamado seja concluído. O
chamador deve ser independente de quaisquer valores ou exceções que o método
assíncrono gere.

O chamador de um método assíncrono de retorno nulo não pode capturar exceções


geradas por meio do método. Essas exceções sem tratamento provavelmente causarão
falha no aplicativo. Se um método que retorna Task ou Task<TResult> gera uma
exceção, ela é armazenada na tarefa retornada. A exceção é gerada novamente quando
a tarefa é aguardada. Verifique se qualquer método assíncrono que pode produzir uma
exceção tem o tipo de retorno Task ou Task<TResult> e se as chamadas ao método são
aguardadas.

O exemplo a seguir mostra o comportamento de um manipulador de eventos


assíncrono. No exemplo de código, um manipulador de eventos assíncrono deve
informar o thread principal quando for concluído. Em seguida, o thread principal pode
aguardar um manipulador de eventos assíncronos ser concluído antes de sair do
programa.

C#

public class NaiveButton


{
public event EventHandler? Clicked;

public void Click()


{
Console.WriteLine("Somebody has clicked a button. Let's raise the
event...");
Clicked?.Invoke(this, EventArgs.Empty);
Console.WriteLine("All listeners are notified.");
}
}

public class AsyncVoidExample


{
static readonly TaskCompletionSource<bool> s_tcs = new
TaskCompletionSource<bool>();

public static async Task MultipleEventHandlersAsync()


{
Task<bool> secondHandlerFinished = s_tcs.Task;

var button = new NaiveButton();

button.Clicked += OnButtonClicked1;
button.Clicked += OnButtonClicked2Async;
button.Clicked += OnButtonClicked3;

Console.WriteLine("Before button.Click() is called...");


button.Click();
Console.WriteLine("After button.Click() is called...");
await secondHandlerFinished;
}

private static void OnButtonClicked1(object? sender, EventArgs e)


{
Console.WriteLine(" Handler 1 is starting...");
Task.Delay(100).Wait();
Console.WriteLine(" Handler 1 is done.");
}

private static async void OnButtonClicked2Async(object? sender,


EventArgs e)
{
Console.WriteLine(" Handler 2 is starting...");
Task.Delay(100).Wait();
Console.WriteLine(" Handler 2 is about to go async...");
await Task.Delay(500);
Console.WriteLine(" Handler 2 is done.");
s_tcs.SetResult(true);
}

private static void OnButtonClicked3(object? sender, EventArgs e)


{
Console.WriteLine(" Handler 3 is starting...");
Task.Delay(100).Wait();
Console.WriteLine(" Handler 3 is done.");
}
}
// Example output:
//
// Before button.Click() is called...
// Somebody has clicked a button. Let's raise the event...
// Handler 1 is starting...
// Handler 1 is done.
// Handler 2 is starting...
// Handler 2 is about to go async...
// Handler 3 is starting...
// Handler 3 is done.
// All listeners are notified.
// After button.Click() is called...
// Handler 2 is done.

Tipos de retorno assíncronos generalizados e


ValueTask<TResult>
Um método assíncrono pode retornar qualquer tipo que tenha um método GetAwaiter
acessível que retorne uma instância de um tipo de aguardador. Além disso, o tipo
retornado do método GetAwaiter deve ter o atributo
System.Runtime.CompilerServices.AsyncMethodBuilderAttribute. Saiba mais no artigo
sobre Atributos lidos pelo compilador ou na especificação de C# para o Padrão de
construtor de tipo de tarefa.

Esse recurso é o complemento para expressões aguardáveis, que descreve os requisitos


para o operando await . Tipos de retorno assíncronos generalizados permitem que o
compilador gere métodos async que retornam tipos diferentes. Tipos de retorno
assíncronos generalizados habilitaram melhorias de desempenho nas bibliotecas .NET.
Já que Task e Task<TResult> são tipos de referência, a alocação de memória em
caminhos críticos para o desempenho, especialmente quando alocações ocorrerem em
loops estreitos, podem afetar o desempenho. Suporte para tipos de retorno
generalizados significa que você pode retornar um tipo de valor leve em vez de um tipo
de referência para evitar as alocações de memória adicionais.

O .NET fornece a estrutura System.Threading.Tasks.ValueTask<TResult> como uma


implementação leve de um valor de retorno de tarefa generalizado. O exemplo a seguir
usa a estrutura ValueTask<TResult> para recuperar o valor de dois lançamentos de
dados.

C#

class Program
{
static readonly Random s_rnd = new Random();

static async Task Main() =>


Console.WriteLine($"You rolled {await GetDiceRollAsync()}");

static async ValueTask<int> GetDiceRollAsync()


{
Console.WriteLine("Shaking dice...");

int roll1 = await RollAsync();


int roll2 = await RollAsync();

return roll1 + roll2;


}

static async ValueTask<int> RollAsync()


{
await Task.Delay(500);

int diceRoll = s_rnd.Next(1, 7);


return diceRoll;
}
}
// Example output:
// Shaking dice...
// You rolled 8
Escrever um tipo de retorno assíncrono generalizado é um cenário avançado,
direcionado para uso em ambientes especializados. Considere o uso dos tipos Task ,
Task<T> e ValueTask<T> , que abrangem a maioria dos cenários para código assíncrono.

No C# 10 e versões posteriores, você pode aplicar o atributo AsyncMethodBuilder a um


método assíncrono (em vez da declaração de tipo de retorno assíncrono) para substituir
o construtor daquele tipo. Normalmente, você aplicaria esse atributo para usar um
construtor diferente fornecido no runtime do .NET.

Fluxos assíncronos com IAsyncEnumerable<T>


Um método assíncrono pode retornar um fluxo assíncrono, representado por
IAsyncEnumerable<T>. Um fluxo assíncrono fornece uma forma de enumerar itens lidos
de um fluxo quando os elementos são gerados em partes com chamadas assíncronas
repetidas. O seguinte exemplo mostra um método assíncrono que gera um fluxo
assíncrono:

C#

static async IAsyncEnumerable<string> ReadWordsFromStreamAsync()


{
string data =
@"This is a line of text.
Here is the second line of text.
And there is one more for good measure.
Wait, that was the penultimate line.";

using var readStream = new StringReader(data);

string? line = await readStream.ReadLineAsync();


while (line != null)
{
foreach (string word in line.Split(' ',
StringSplitOptions.RemoveEmptyEntries))
{
yield return word;
}

line = await readStream.ReadLineAsync();


}
}

O exemplo anterior lê linhas de uma cadeia de caracteres de modo assíncrono. Depois


que cada linha é lida, o código enumera cada palavra na cadeia de caracteres. Os
chamadores enumerariam cada palavra usando a instrução await foreach . O método
aguarda quando precisa ler de modo assíncrono a próxima linha da cadeia de caracteres
de origem.

Confira também
FromResult
Processar tarefas assíncronas conforme elas são concluídas
Programação assíncrona com async e await (C#)
async
await

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.
Processar tarefas assíncronas conforme
elas são concluídas (C#)
Artigo • 23/05/2023

Usando Task.WhenAny, você pode iniciar várias tarefas ao mesmo tempo e processá-las
individualmente conforme elas forem concluídas, em vez de processá-las na ordem em
que foram iniciadas.

O exemplo a seguir usa uma consulta para criar uma coleção de tarefas. Cada tarefa
baixa o conteúdo de um site especificado. Em cada iteração de um loop "while", uma
chamada esperada para WhenAny retorna a tarefa na coleção de tarefas que concluir o
download primeiro. Essa tarefa é removida da coleção e processada. O loop é repetido
até que a coleção não contenha mais tarefas.

Pré-requisitos
Você pode seguir este tutorial usando uma das seguintes opções:

Visual Studio 2022 com a carga de trabalho Desenvolvimento de área de


trabalho do .NET instalada. O SDK do .NET é instalado automaticamente quando
você seleciona essa carga de trabalho.
O SDK do .NET com um editor de código de sua escolha, como Visual Studio
Code .

Criar aplicativo de exemplo


Criar um novo aplicativo de console do .NET Core. Você pode criar um usando o
comando dotnet new console ou do Visual Studio.

Abra o arquivo Program.cs no editor de código e substitua o código existente por este:

C#

using System.Diagnostics;

namespace ProcessTasksAsTheyFinish;

class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}

Adicionar campos
Na definição de classe Program , adicione os dois seguintes campos:

C#

static readonly HttpClient s_client = new HttpClient


{
MaxResponseContentBufferSize = 1_000_000
};

static readonly IEnumerable<string> s_urlList = new string[]


{
"https://learn.microsoft.com",
"https://learn.microsoft.com/aspnet/core",
"https://learn.microsoft.com/azure",
"https://learn.microsoft.com/azure/devops",
"https://learn.microsoft.com/dotnet",
"https://learn.microsoft.com/dynamics365",
"https://learn.microsoft.com/education",
"https://learn.microsoft.com/enterprise-mobility-security",
"https://learn.microsoft.com/gaming",
"https://learn.microsoft.com/graph",
"https://learn.microsoft.com/microsoft-365",
"https://learn.microsoft.com/office",
"https://learn.microsoft.com/powershell",
"https://learn.microsoft.com/sql",
"https://learn.microsoft.com/surface",
"https://learn.microsoft.com/system-center",
"https://learn.microsoft.com/visualstudio",
"https://learn.microsoft.com/windows",
"https://learn.microsoft.com/xamarin"
};

O HttpClient expõe a capacidade de enviar solicitações HTTP e receber respostas HTTP.


O s_urlList contém todas as URLs que o aplicativo planeja processar.

Atualize o ponto de entrada do aplicativo


O principal ponto de entrada no aplicativo de console é o método Main . Substitua o
método existente pelo seguinte:

C#
static Task Main() => SumPageSizesAsync();

O método atualizado Main agora é considerado um Async main, que permite um ponto
de entrada assíncrono no executável. Ele é expresso como uma chamada para
SumPageSizesAsync .

Criar o método de tamanhos de página de


soma assíncrona
Abaixo do método Main , adicione o método SumPageSizesAsync :

C#

static async Task SumPageSizesAsync()


{
var stopwatch = Stopwatch.StartNew();

IEnumerable<Task<int>> downloadTasksQuery =
from url in s_urlList
select ProcessUrlAsync(url, s_client);

List<Task<int>> downloadTasks = downloadTasksQuery.ToList();

int total = 0;
while (downloadTasks.Any())
{
Task<int> finishedTask = await Task.WhenAny(downloadTasks);
downloadTasks.Remove(finishedTask);
total += await finishedTask;
}

stopwatch.Stop();

Console.WriteLine($"\nTotal bytes returned: {total:#,#}");


Console.WriteLine($"Elapsed time: {stopwatch.Elapsed}\n");
}

O loop while remove uma das tarefas em cada iteração. Depois que todas as tarefas
forem concluídas, o loop terminará. O método começa instanciando e iniciando um
Stopwatch. Em seguida, ele inclui uma consulta que, quando executada, cria uma
coleção de tarefas. Cada chamada para ProcessUrlAsync no código a seguir retorna um
Task<TResult>, em que TResult é um inteiro:

C#
IEnumerable<Task<int>> downloadTasksQuery =
from url in s_urlList
select ProcessUrlAsync(url, s_client);

Devido à execução adiada com o LINQ, você chama Enumerable.ToList para iniciar cada
tarefa.

C#

List<Task<int>> downloadTasks = downloadTasksQuery.ToList();

O loop while executa as seguintes etapas para cada tarefa na coleção:

1. Espera uma chamada para WhenAny , com o objetivo de identificar a primeira tarefa
na coleção que concluiu o download.

C#

Task<int> finishedTask = await Task.WhenAny(downloadTasks);

2. Remove a tarefa da coleção.

C#

downloadTasks.Remove(finishedTask);

3. Espera finishedTask , que é retornado por uma chamada para ProcessUrlAsync . A


variável finishedTask é uma Task<TResult> em que TResult é um inteiro. A tarefa
já foi concluída, mas você espera para recuperar o tamanho do site baixado, como
mostra o exemplo a seguir. Se a tarefa tiver falha, await gerará a primeira exceção
filho armazenada no AggregateException , ao contrário da leitura da propriedade
Task<TResult>.Result, que lançaria o AggregateException .

C#

total += await finishedTask;

Adicionar método de processo


Adicione o seguinte método ProcessUrlAsync abaixo do método SumPageSizesAsync :
C#

static async Task<int> ProcessUrlAsync(string url, HttpClient client)


{
byte[] content = await client.GetByteArrayAsync(url);
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");

return content.Length;
}

Para qualquer URL fornecida, o método usará a instância client fornecida para obter a
resposta como um byte[] . O comprimento é retornado depois que a URL e o
comprimento são gravados no console.

Execute o programa várias vezes para verificar se os tamanhos baixados não aparecem
sempre na mesma ordem.

U Cuidado

Você pode usar WhenAny em um loop, conforme descrito no exemplo, para resolver
problemas que envolvem um número pequeno de tarefas. No entanto, outras
abordagens são mais eficientes se você tiver um número grande de tarefas para
processar. Para obter mais informações e exemplos, consulte Processando tarefas
quando elas são concluídas .

Exemplo completo
O código a seguir é o texto completo do arquivo Program.cs para o exemplo.

C#

using System.Diagnostics;

HttpClient s_client = new()


{
MaxResponseContentBufferSize = 1_000_000
};

IEnumerable<string> s_urlList = new string[]


{
"https://learn.microsoft.com",
"https://learn.microsoft.com/aspnet/core",
"https://learn.microsoft.com/azure",
"https://learn.microsoft.com/azure/devops",
"https://learn.microsoft.com/dotnet",
"https://learn.microsoft.com/dynamics365",
"https://learn.microsoft.com/education",
"https://learn.microsoft.com/enterprise-mobility-security",
"https://learn.microsoft.com/gaming",
"https://learn.microsoft.com/graph",
"https://learn.microsoft.com/microsoft-365",
"https://learn.microsoft.com/office",
"https://learn.microsoft.com/powershell",
"https://learn.microsoft.com/sql",
"https://learn.microsoft.com/surface",
"https://learn.microsoft.com/system-center",
"https://learn.microsoft.com/visualstudio",
"https://learn.microsoft.com/windows",
"https://learn.microsoft.com/xamarin"
};

await SumPageSizesAsync();

async Task SumPageSizesAsync()


{
var stopwatch = Stopwatch.StartNew();

IEnumerable<Task<int>> downloadTasksQuery =
from url in s_urlList
select ProcessUrlAsync(url, s_client);

List<Task<int>> downloadTasks = downloadTasksQuery.ToList();

int total = 0;
while (downloadTasks.Any())
{
Task<int> finishedTask = await Task.WhenAny(downloadTasks);
downloadTasks.Remove(finishedTask);
total += await finishedTask;
}

stopwatch.Stop();

Console.WriteLine($"\nTotal bytes returned: {total:#,#}");


Console.WriteLine($"Elapsed time: {stopwatch.Elapsed}\n");
}

static async Task<int> ProcessUrlAsync(string url, HttpClient client)


{
byte[] content = await client.GetByteArrayAsync(url);
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");

return content.Length;
}

// Example output:
// https://learn.microsoft.com 132,517
// https://learn.microsoft.com/powershell 57,375
// https://learn.microsoft.com/gaming 33,549
// https://learn.microsoft.com/aspnet/core 88,714
// https://learn.microsoft.com/surface 39,840
// https://learn.microsoft.com/enterprise-mobility-security 30,903
// https://learn.microsoft.com/microsoft-365 67,867
// https://learn.microsoft.com/windows 26,816
// https://learn.microsoft.com/xamarin 57,958
// https://learn.microsoft.com/dotnet 78,706
// https://learn.microsoft.com/graph 48,277
// https://learn.microsoft.com/dynamics365 49,042
// https://learn.microsoft.com/office 67,867
// https://learn.microsoft.com/system-center 42,887
// https://learn.microsoft.com/education 38,636
// https://learn.microsoft.com/azure 421,663
// https://learn.microsoft.com/visualstudio 30,925
// https://learn.microsoft.com/sql 54,608
// https://learn.microsoft.com/azure/devops 86,034

// Total bytes returned: 1,454,184


// Elapsed time: 00:00:01.1290403

Confira também
WhenAny
Programação assíncrona com async e await (C#)

6 Collaborate with us on
GitHub .NET feedback
The .NET documentation is open
The source for this content can
be found on GitHub, where you source. Provide feedback here.
can also create and review
 Open a documentation issue
issues and pull requests. For
more information, see our
 Provide product feedback
contributor guide.
Acesso a arquivos assíncronos (C#)
Artigo • 10/05/2023

Você pode usar o recurso async para acessar arquivos. Usando o recurso async, você
pode chamar os métodos assíncronos sem usar retornos de chamada ou dividir seu
código em vários métodos ou expressões lambda. Para tornar síncrono um código
assíncrono, basta chamar um método assíncrono em vez de um método síncrono e
adicionar algumas palavras-chave ao código.

Você pode considerar seguintes motivos para adicionar a assincronia às chamadas de


acesso ao arquivo:

" A assincronia torna os aplicativos de interface do usuário mais responsivos porque


o thread de interface do usuário que inicia a operação pode executar outro
trabalho. Se o thread de interface do usuário precisar executar o código que leva
muito tempo (por exemplo, mais de 50 milissegundos), a interface do usuário
poderá congelar até que a E/S seja concluída e o thread da interface do usuário
possa processar entradas do mouse e do teclado e outros eventos.
" A assincronia melhora a escalabilidade do ASP.NET e outros aplicativos baseados
em servidor reduzindo a necessidade de threads. Se o aplicativo usar um thread
dedicado por resposta e mil solicitações forem tratadas simultaneamente, serão
necessários mil threads. As operações assíncronas normalmente não precisam usar
um thread durante a espera. Elas podem usar o thread de conclusão de E/S
existente rapidamente no final.
" A latência de uma operação de acesso de arquivo pode ser muito baixa nas
condições atuais, mas a latência pode aumentar consideravelmente no futuro. Por
exemplo, um arquivo pode ser movido para um servidor que está do outro lado do
mundo.
" A sobrecarga adicional de usar o recurso async é pequena.
" As tarefas assíncronas podem facilmente ser executadas em paralelo.

Usar classes apropriadas


Os exemplos simples neste tópico demonstram File.WriteAllTextAsync e
File.ReadAllTextAsync. Para controle fino sobre as operações de E/S do arquivo, use a
classe FileStream, que tem uma opção que faz com que a E/S assíncrona ocorra no nível
do sistema operacional. Usando essa opção, você pode evitar o bloqueio de um thread
de pool de threads em muitos casos. Para habilitar essa opção, você deve especificar o
argumento useAsync=true ou options=FileOptions.Asynchronous na chamada do
construtor.
Você não pode usar essa opção com StreamReader e StreamWriter se você os abrir
diretamente especificando um caminho de arquivo. No entanto, você poderá usar essa
opção se fornecer um Stream que a classe FileStream abriu. Chamadas assíncronas serão
mais rápidas em aplicativos de interface do usuário mesmo se um thread de pool de
threads estiver bloqueado, porque o thread de interface do usuário não é bloqueado
durante a espera.

Gravar texto
Os exemplos a seguir gravam texto em um arquivo. A cada instrução await, o método
sai imediatamente. Quando o arquivo de E/S for concluído, o método continuará na
instrução após a instrução await. O modificador async é a definição de métodos que
usam a instrução await.

Exemplo simples
C#

public async Task SimpleWriteAsync()


{
string filePath = "simple.txt";
string text = $"Hello World";

await File.WriteAllTextAsync(filePath, text);


}

Exemplo de controle finito


C#

public async Task ProcessWriteAsync()


{
string filePath = "temp.txt";
string text = $"Hello World{Environment.NewLine}";

await WriteTextAsync(filePath, text);


}

async Task WriteTextAsync(string filePath, string text)


{
byte[] encodedText = Encoding.Unicode.GetBytes(text);

using var sourceStream =


new FileStream(
filePath,
FileMode.Create, FileAccess.Write, FileShare.None,
bufferSize: 4096, useAsync: true);

await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);


}

O exemplo original tem a instrução await sourceStream.WriteAsync(encodedText, 0,


encodedText.Length); , que é uma contração das duas instruções a seguir:

C#

Task theTask = sourceStream.WriteAsync(encodedText, 0, encodedText.Length);


await theTask;

A primeira instrução retorna uma tarefa e faz com que o processamento do arquivo seja
iniciado. A segunda instrução com o await faz com que o método saia imediatamente e
retorne uma tarefa diferente. Quando o processamento do arquivo é concluído
posteriormente, a execução retorna para a instrução após a await.

Ler texto
Os exemplos a seguir leem texto de um arquivo.

Exemplo simples
C#

public async Task SimpleReadAsync()


{
string filePath = "simple.txt";
string text = await File.ReadAllTextAsync(filePath);

Console.WriteLine(text);
}

Exemplo de controle finito


O texto é armazenado em buffer e, nesse caso, colocado em um StringBuilder.
Diferentemente do exemplo anterior, a avaliação de await produz um valor. O método
ReadAsync retorna um Task<Int32>, portanto, a avaliação do await produz um Int32
valor numRead após a conclusão da operação. Para obter mais informações, consulte
Tipos de retorno assíncronos (C#).
C#

public async Task ProcessReadAsync()


{
try
{
string filePath = "temp.txt";
if (File.Exists(filePath) != false)
{
string text = await ReadTextAsync(filePath);
Console.WriteLine(text);
}
else
{
Console.WriteLine($"file not found: {filePath}");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}

async Task<string> ReadTextAsync(string filePath)


{
using var sourceStream =
new FileStream(
filePath,
FileMode.Open, FileAccess.Read, FileShare.Read,
bufferSize: 4096, useAsync: true);

var sb = new StringBuilder();

byte[] buffer = new byte[0x1000];


int numRead;
while ((numRead = await sourceStream.ReadAsync(buffer, 0,
buffer.Length)) != 0)
{
string text = Encoding.Unicode.GetString(buffer, 0, numRead);
sb.Append(text);
}

return sb.ToString();
}

E/S assíncrona paralela


Os exemplos a seguir demonstram o processamento paralelo gravando dez arquivos de
texto.
Exemplo simples
C#

public async Task SimpleParallelWriteAsync()


{
string folder = Directory.CreateDirectory("tempfolder").Name;
IList<Task> writeTaskList = new List<Task>();

for (int index = 11; index <= 20; ++ index)


{
string fileName = $"file-{index:00}.txt";
string filePath = $"{folder}/{fileName}";
string text = $"In file {index}{Environment.NewLine}";

writeTaskList.Add(File.WriteAllTextAsync(filePath, text));
}

await Task.WhenAll(writeTaskList);
}

Exemplo de controle finito


Para cada arquivo, o método WriteAsync retorna uma tarefa que é então adicionada a
uma lista de tarefas. A instrução await Task.WhenAll(tasks); sai do método e retoma
no método quando o processamento do arquivo é concluído para todas as tarefas.

O exemplo fecha todas as instâncias de FileStream em um bloco finally após as tarefas


serem concluídas. Se cada FileStream foi criado em uma instrução using , o FileStream
pode ter sido descartado antes de a tarefa ter sido concluída.

Qualquer aumento de desempenho é quase que totalmente do processamento paralelo


e não o processamento assíncrono. As vantagens da assincronia são que ela não
bloqueia vários threads e que ela não bloqueia o thread da interface do usuário.

C#

public async Task ProcessMultipleWritesAsync()


{
IList<FileStream> sourceStreams = new List<FileStream>();

try
{
string folder = Directory.CreateDirectory("tempfolder").Name;
IList<Task> writeTaskList = new List<Task>();

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


{
string fileName = $"file-{index:00}.txt";
string filePath = $"{folder}/{fileName}";

string text = $"In file {index}{Environment.NewLine}";


byte[] encodedText = Encoding.Unicode.GetBytes(text);

var sourceStream =
new FileStream(
filePath,
FileMode.Create, FileAccess.Write, FileShare.None,
bufferSize: 4096, useAsync: true);

Task writeTask = sourceStream.WriteAsync(encodedText, 0,


encodedText.Length);
sourceStreams.Add(sourceStream);

writeTaskList.Add(writeTask);
}

await Task.WhenAll(writeTaskList);
}
finally
{
foreach (FileStream sourceStream in sourceStreams)
{
sourceStream.Close();
}
}
}

Ao usar os métodos WriteAsync e ReadAsync, você pode especificar um


CancellationToken, que pode ser usado para cancelar o fluxo intermediário da operação.
Para saber mais, confira Cancelamento em threads gerenciados.

Confira também
Programação assíncrona com async e await (C#)
Tipos de retorno assíncronos (C#)

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
 Provide product feedback
more information, see our
contributor guide.
Cancelar uma lista de tarefas
Artigo • 28/03/2023

Você pode cancelar um aplicativo de console assíncrono se não quiser esperar que ele
seja concluído. Seguindo o exemplo neste tópico, você pode adicionar um
cancelamento a um aplicativo que baixa o conteúdo de uma lista de sites. Você pode
cancelar várias tarefas associando a instância CancellationTokenSource a cada tarefa. Se
você selecionar a tecla Enter , você cancele todas as tarefas que ainda não foram
concluídas.

Este tutorial abrange:

" Criando um aplicativo de console .NET


" Escrever um aplicativo assíncrono que dá suporte a cancelamento
" Demonstrando cancelamento de sinalização

Pré-requisitos
Este tutorial exige o seguinte:

.NET 5 ou SDK posterior


IDE (ambiente de desenvolvimento integrado)
Recomendamos o Visual Studio ou o Visual Studio Code

Criar aplicativo de exemplo


Criar um novo aplicativo de console do .NET Core. Você pode criar um usando o
comando ou do dotnet new consoleVisual Studio. Abra o arquivo Program.cs em seu
editor de código favorito.

Substituir usando instruções


Substitua as instruções de uso existentes por estas declarações:

C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

Adicionar campos
Na definição de classe Program , adicione estes três campos:

C#

static readonly CancellationTokenSource s_cts = new


CancellationTokenSource();

static readonly HttpClient s_client = new HttpClient


{
MaxResponseContentBufferSize = 1_000_000
};

static readonly IEnumerable<string> s_urlList = new string[]


{
"https://learn.microsoft.com",
"https://learn.microsoft.com/aspnet/core",
"https://learn.microsoft.com/azure",
"https://learn.microsoft.com/azure/devops",
"https://learn.microsoft.com/dotnet",
"https://learn.microsoft.com/dynamics365",
"https://learn.microsoft.com/education",
"https://learn.microsoft.com/enterprise-mobility-security",
"https://learn.microsoft.com/gaming",
"https://learn.microsoft.com/graph",
"https://learn.microsoft.com/microsoft-365",
"https://learn.microsoft.com/office",
"https://learn.microsoft.com/powershell",
"https://learn.microsoft.com/sql",
"https://learn.microsoft.com/surface",
"https://learn.microsoft.com/system-center",
"https://learn.microsoft.com/visualstudio",
"https://learn.microsoft.com/windows",
"https://learn.microsoft.com/xamarin"
};

O CancellationTokenSource é usado para sinalizar um cancelamento solicitado para um


CancellationToken. O HttpClient expõe a capacidade de enviar solicitações HTTP e
receber respostas HTTP. O s_urlList contém todas as URLs que o aplicativo planeja
processar.

Atualize o ponto de entrada do aplicativo


O principal ponto de entrada no aplicativo de console é o método Main . Substitua o
método existente pelo seguinte:

C#

static async Task Main()


{
Console.WriteLine("Application started.");
Console.WriteLine("Press the ENTER key to cancel...\n");

Task cancelTask = Task.Run(() =>


{
while (Console.ReadKey().Key != ConsoleKey.Enter)
{
Console.WriteLine("Press the ENTER key to cancel...");
}

Console.WriteLine("\nENTER key pressed: cancelling downloads.\n");


s_cts.Cancel();
});

Task sumPageSizesTask = SumPageSizesAsync();

Task finishedTask = await Task.WhenAny(new[] { cancelTask,


sumPageSizesTask });
if (finishedTask == cancelTask)
{
// wait for the cancellation to take place:
try
{
await sumPageSizesTask;
Console.WriteLine("Download task completed before cancel request
was processed.");
}
catch (TaskCanceledException)
{
Console.WriteLine("Download task has been cancelled.");
}
}

Console.WriteLine("Application ending.");
}

O método atualizado Main agora é considerado um Async main, que permite um ponto
de entrada assíncrono no executável. Ele grava algumas mensagens instrutivas no
console e, em seguida, declara uma instância Task chamada cancelTask , que lerá os
traços de chave do console. Se a tecla Enter for pressionada, será feita uma chamada
CancellationTokenSource.Cancel(). Isso sinalizará o cancelamento. Em seguida, a variável
sumPageSizesTask é atribuída do método SumPageSizesAsync . Ambas as tarefas são
então passadas para Task.WhenAny(Task[]), o que continuará quando qualquer uma das
duas tarefas tiver sido concluída.

O próximo bloco de código garante que o aplicativo não saia até que o cancelamento
seja processado. Se a primeira tarefa a ser concluída for cancelTask , o sumPageSizeTask
será aguardado. Se ele foi cancelado, quando aguardado, ele lança um
System.Threading.Tasks.TaskCanceledException. O bloco captura essa exceção e imprime
uma mensagem.

Criar o método de tamanhos de página de


soma assíncrona
Abaixo do método Main , adicione o método SumPageSizesAsync :

C#

static async Task SumPageSizesAsync()


{
var stopwatch = Stopwatch.StartNew();

int total = 0;
foreach (string url in s_urlList)
{
int contentLength = await ProcessUrlAsync(url, s_client,
s_cts.Token);
total += contentLength;
}

stopwatch.Stop();

Console.WriteLine($"\nTotal bytes returned: {total:#,#}");


Console.WriteLine($"Elapsed time: {stopwatch.Elapsed}\n");
}

O método começa instanciando e iniciando um Stopwatch. Em seguida, ele faz loops


por cada URL no s_urlList e chama ProcessUrlAsync . A cada iteração, o método
s_cts.Token é passado para o método ProcessUrlAsync e o código retorna um

Task<TResult>, que que TResult é um inteiro:

C#

int total = 0;
foreach (string url in s_urlList)
{
int contentLength = await ProcessUrlAsync(url, s_client, s_cts.Token);
total += contentLength;
}

Adicionar método de processo


Adicione o seguinte método ProcessUrlAsync abaixo do método SumPageSizesAsync :

C#

static async Task<int> ProcessUrlAsync(string url, HttpClient client,


CancellationToken token)
{
HttpResponseMessage response = await client.GetAsync(url, token);
byte[] content = await response.Content.ReadAsByteArrayAsync(token);
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");

return content.Length;
}

Para qualquer URL fornecida, o método usará a instância client fornecida para obter a
resposta como um byte[] . A instância CancellationToken é passada para os métodos
HttpClient.GetAsync(String, CancellationToken) e HttpContent.ReadAsByteArrayAsync().
O token é usado para registrar-se para cancelamento solicitado. O comprimento é
retornado depois que a URL e o comprimento são gravados no console.

Exemplo de saída de aplicativo


Console

Application started.
Press the ENTER key to cancel...

https://learn.microsoft.com 37,357
https://learn.microsoft.com/aspnet/core 85,589
https://learn.microsoft.com/azure 398,939
https://learn.microsoft.com/azure/devops 73,663
https://learn.microsoft.com/dotnet 67,452
https://learn.microsoft.com/dynamics365 48,582
https://learn.microsoft.com/education 22,924

ENTER key pressed: cancelling downloads.

Application ending.
Exemplo completo
O código a seguir é o texto completo do arquivo Program.cs para o exemplo.

C#

using System.Diagnostics;

class Program
{
static readonly CancellationTokenSource s_cts = new
CancellationTokenSource();

static readonly HttpClient s_client = new HttpClient


{
MaxResponseContentBufferSize = 1_000_000
};

static readonly IEnumerable<string> s_urlList = new string[]


{
"https://learn.microsoft.com",
"https://learn.microsoft.com/aspnet/core",
"https://learn.microsoft.com/azure",
"https://learn.microsoft.com/azure/devops",
"https://learn.microsoft.com/dotnet",
"https://learn.microsoft.com/dynamics365",
"https://learn.microsoft.com/education",
"https://learn.microsoft.com/enterprise-mobility-security",
"https://learn.microsoft.com/gaming",
"https://learn.microsoft.com/graph",
"https://learn.microsoft.com/microsoft-365",
"https://learn.microsoft.com/office",
"https://learn.microsoft.com/powershell",
"https://learn.microsoft.com/sql",
"https://learn.microsoft.com/surface",
"https://learn.microsoft.com/system-center",
"https://learn.microsoft.com/visualstudio",
"https://learn.microsoft.com/windows",
"https://learn.microsoft.com/xamarin"
};

static async Task Main()


{
Console.WriteLine("Application started.");
Console.WriteLine("Press the ENTER key to cancel...\n");

Task cancelTask = Task.Run(() =>


{
while (Console.ReadKey().Key != ConsoleKey.Enter)
{
Console.WriteLine("Press the ENTER key to cancel...");
}
Console.WriteLine("\nENTER key pressed: cancelling
downloads.\n");
s_cts.Cancel();
});

Task sumPageSizesTask = SumPageSizesAsync();

Task finishedTask = await Task.WhenAny(new[] { cancelTask,


sumPageSizesTask });
if (finishedTask == cancelTask)
{
// wait for the cancellation to take place:
try
{
await sumPageSizesTask;
Console.WriteLine("Download task completed before cancel
request was processed.");
}
catch (TaskCanceledException)
{
Console.WriteLine("Download task has been cancelled.");
}
}

Console.WriteLine("Application ending.");
}

static async Task SumPageSizesAsync()


{
var stopwatch = Stopwatch.StartNew();

int total = 0;
foreach (string url in s_urlList)
{
int contentLength = await ProcessUrlAsync(url, s_client,
s_cts.Token);
total += contentLength;
}

stopwatch.Stop();

Console.WriteLine($"\nTotal bytes returned: {total:#,#}");


Console.WriteLine($"Elapsed time: {stopwatch.Elapsed}\n");
}

static async Task<int> ProcessUrlAsync(string url, HttpClient client,


CancellationToken token)
{
HttpResponseMessage response = await client.GetAsync(url, token);
byte[] content = await response.Content.ReadAsByteArrayAsync(token);
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");

return content.Length;
}
}
Confira também
CancellationToken
CancellationTokenSource
Programação assíncrona com async e await (C#)

Próximas etapas
Cancelar tarefas assíncronas após um período (C#)

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.
Cancelar tarefas assíncronas após um
período
Artigo • 12/09/2023

Você pode cancelar uma operação assíncrona após um período usando o método
CancellationTokenSource.CancelAfter se não quiser aguardar a conclusão da operação.
Esse método agenda o cancelamento de quaisquer tarefas associadas que não são
concluídas dentro do período designado pela expressão CancelAfter .

Este exemplo adiciona o código desenvolvido em Cancelar uma tarefa assíncrona ou


uma lista de tarefas (C#) para baixar uma lista de sites e para exibir o tamanho dos
conteúdos de cada um.

Este tutorial abrange:

" Atualizando um aplicativo de console .NET existente


" Agendando um cancelamento

Pré-requisitos
Este tutorial exige o seguinte:

Espera-se que você tenha criado um aplicativo no tutorial Cancelar uma lista de
tarefas (C#)
.NET 5 ou SDK posterior
IDE (ambiente de desenvolvimento integrado)
Recomendamos o Visual Studio ou o Visual Studio Code

Atualize o ponto de entrada do aplicativo.


Substitua o método Main existente pelo seguinte:

C#

static async Task Main()


{
Console.WriteLine("Application started.");

try
{
s_cts.CancelAfter(3500);
await SumPageSizesAsync();
}
catch (OperationCanceledException)
{
Console.WriteLine("\nTasks cancelled: timed out.\n");
}
finally
{
s_cts.Dispose();
}

Console.WriteLine("Application ending.");
}

O método Main atualizado grava algumas mensagens instrutivas no console. Dentro do


try-catch, uma chamada para CancellationTokenSource.CancelAfter(Int32) agenda um
cancelamento. Isso sinalizará o cancelamento após um período.

Em seguida, o método SumPageSizesAsync é aguardado. Se o processamento de todas


as URLs ocorrer mais rápido do que o cancelamento agendado, o aplicativo é
encerrado. No entanto, se o cancelamento agendado for disparado antes que todas as
URLs sejam processadas, um OperationCanceledException será gerado.

Exemplo de saída de aplicativo


Console

Application started.

https://learn.microsoft.com 37,357
https://learn.microsoft.com/aspnet/core 85,589
https://learn.microsoft.com/azure 398,939
https://learn.microsoft.com/azure/devops 73,663

Tasks cancelled: timed out.

Application ending.

Exemplo completo
O código a seguir é o texto completo do arquivo Program.cs para o exemplo.

C#

using System.Diagnostics;

class Program
{
static readonly CancellationTokenSource s_cts = new
CancellationTokenSource();

static readonly HttpClient s_client = new HttpClient


{
MaxResponseContentBufferSize = 1_000_000
};

static readonly IEnumerable<string> s_urlList = new string[]


{
"https://learn.microsoft.com",
"https://learn.microsoft.com/aspnet/core",
"https://learn.microsoft.com/azure",
"https://learn.microsoft.com/azure/devops",
"https://learn.microsoft.com/dotnet",
"https://learn.microsoft.com/dynamics365",
"https://learn.microsoft.com/education",
"https://learn.microsoft.com/enterprise-mobility-security",
"https://learn.microsoft.com/gaming",
"https://learn.microsoft.com/graph",
"https://learn.microsoft.com/microsoft-365",
"https://learn.microsoft.com/office",
"https://learn.microsoft.com/powershell",
"https://learn.microsoft.com/sql",
"https://learn.microsoft.com/surface",
"https://learn.microsoft.com/system-center",
"https://learn.microsoft.com/visualstudio",
"https://learn.microsoft.com/windows",
"https://learn.microsoft.com/xamarin"
};

static async Task Main()


{
Console.WriteLine("Application started.");

try
{
s_cts.CancelAfter(3500);

await SumPageSizesAsync();
}
catch (OperationCanceledException)
{
Console.WriteLine("\nTasks cancelled: timed out.\n");
}
finally
{
s_cts.Dispose();
}

Console.WriteLine("Application ending.");
}

static async Task SumPageSizesAsync()


{
var stopwatch = Stopwatch.StartNew();

int total = 0;
foreach (string url in s_urlList)
{
int contentLength = await ProcessUrlAsync(url, s_client,
s_cts.Token);
total += contentLength;
}

stopwatch.Stop();

Console.WriteLine($"\nTotal bytes returned: {total:#,#}");


Console.WriteLine($"Elapsed time: {stopwatch.Elapsed}\n");
}

static async Task<int> ProcessUrlAsync(string url, HttpClient client,


CancellationToken token)
{
HttpResponseMessage response = await client.GetAsync(url, token);
byte[] content = await response.Content.ReadAsByteArrayAsync(token);
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");

return content.Length;
}
}

Confira também
CancellationToken
CancellationTokenSource
Programação assíncrona com async e await (C#)
Cancelar uma lista de tarefas (C#)

6 Collaborate with us on
GitHub .NET feedback
The .NET documentation is open
The source for this content can
be found on GitHub, where you source. Provide feedback here.
can also create and review
 Open a documentation issue
issues and pull requests. For
more information, see our
 Provide product feedback
contributor guide.
Tutorial: gerar e consumir fluxos
assíncronos usando o C# e .NET
Artigo • 28/03/2023

Os fluxos assíncronos modelam uma fonte de streaming de dados. Os fluxos de dados


geralmente recuperam ou geram elementos de forma assíncrona. Eles fornecem um
modelo de programação natural para fontes de dados de streaming assíncronas.

Neste tutorial, você aprenderá como:

" Criar uma fonte de dados que gera uma sequência de elementos de dados de
forma assíncrona.
" Consumir essa fonte de dados de forma assíncrona.
" Suporte ao cancelamento e contextos capturados para fluxos assíncronos.
" Reconhecer quando a nova interface e a fonte de dados forem preferenciais para
sequências anteriores de dados síncronos.

Pré-requisitos
Você precisa configurar o computador para executar o .NET, incluindo o compilador C#.
O compilador C# está disponível com o Visual Studio 2022 ou o SDK do .NET .

Você precisará criar um token de acesso do GitHub para poder acessar o ponto de
extremidade GitHub GraphQL. Selecione as seguintes permissões para o Token de
acesso do GitHub:

repo:status
public_repo

Salve o token de acesso em um local seguro, de modo que possa usá-lo para acessar o
ponto de extremidade da API do GitHub.

2 Aviso

Mantenha seu token de acesso pessoal protegido. Qualquer software com seu
token de acesso pessoal pode fazer chamadas da API do GitHub usando seus
direitos de acesso.

Este tutorial pressupõe que você esteja familiarizado com o C# e .NET, incluindo o Visual
Studio ou a CLI do .NET.
Executar o aplicativo inicial
Você pode obter o código para o aplicativo inicial usado neste tutorial em nosso
repositório dotnet/docs na pasta asynchronous-programming/snippets .

O aplicativo inicial é um aplicativo de console que usa a interface GitHub GraphQL


para recuperar os problemas recentes gravados no repositório dotnet/docs . Comece
observando o código a seguir para o método Main do aplicativo inicial:

C#

static async Task Main(string[] args)


{
//Follow these steps to create a GitHub Access Token
// https://help.github.com/articles/creating-a-personal-access-token-
for-the-command-line/#creating-a-token
//Select the following permissions for your GitHub Access Token:
// - repo:status
// - public_repo
// Replace the 3rd parameter to the following code with your GitHub
access token.
var key = GetEnvVariable("GitHubKey",
"You must store your GitHub key in the 'GitHubKey' environment
variable",
"");

var client = new GitHubClient(new


Octokit.ProductHeaderValue("IssueQueryDemo"))
{
Credentials = new Octokit.Credentials(key)
};

var progressReporter = new progressStatus((num) =>


{
Console.WriteLine($"Received {num} issues in total");
});
CancellationTokenSource cancellationSource = new
CancellationTokenSource();

try
{
var results = await RunPagedQueryAsync(client, PagedIssueQuery,
"docs",
cancellationSource.Token, progressReporter);
foreach(var issue in results)
Console.WriteLine(issue);
}
catch (OperationCanceledException)
{
Console.WriteLine("Work has been cancelled");
}
}

Você pode definir uma variável de ambiente GitHubKey para o token de acesso pessoal
ou pode substituir o último argumento na chamada para GetEnvVariable com seu token
de acesso pessoal. Não coloque seu código de acesso no código-fonte se você estiver
compartilhando a origem com outras pessoas. Nunca carregue códigos de acesso em
um repositório de origem compartilhado.

Após criar o cliente do GitHub, o código em Main criará um objeto de relatório de


andamento e um token de cancelamento. Depois que esses objetos forem criados, Main
chamará RunPagedQueryAsync para recuperar os 250 problemas mais recente criados.
Depois que a tarefa for concluída, os resultados serão exibidos.

Ao executar o aplicativo inicial, você poderá realizar algumas observações importantes


sobre como esse aplicativo é executado. Você verá o progresso informado para cada
página retornada do GitHub. É possível observar uma pausa perceptível antes do GitHub
retornar cada nova página de problemas. Por fim, os problemas só serão exibidos
depois que todas as 10 páginas forem recuperadas do GitHub.

Examinar a implementação
A implementação revela por que você observou o comportamento discutido na seção
anterior. Examine o código para RunPagedQueryAsync :

C#

private static async Task<JArray> RunPagedQueryAsync(GitHubClient client,


string queryText, string repoName, CancellationToken cancel, IProgress<int>
progress)
{
var issueAndPRQuery = new GraphQLRequest
{
Query = queryText
};
issueAndPRQuery.Variables["repo_name"] = repoName;

JArray finalResults = new JArray();


bool hasMorePages = true;
int pagesReturned = 0;
int issuesReturned = 0;

// Stop with 10 pages, because these are large repos:


while (hasMorePages && (pagesReturned++ < 10))
{
var postBody = issueAndPRQuery.ToJsonText();
var response = await client.Connection.Post<string>(new
Uri("https://api.github.com/graphql"),
postBody, "application/json", "application/json");

JObject results =
JObject.Parse(response.HttpResponse.Body.ToString()!);

int totalCount = (int)issues(results)["totalCount"]!;


hasMorePages = (bool)pageInfo(results)["hasPreviousPage"]!;
issueAndPRQuery.Variables["start_cursor"] = pageInfo(results)
["startCursor"]!.ToString();
issuesReturned += issues(results)["nodes"]!.Count();
finalResults.Merge(issues(results)["nodes"]!);
progress?.Report(issuesReturned);
cancel.ThrowIfCancellationRequested();
}
return finalResults;

JObject issues(JObject result) => (JObject)result["data"]!


["repository"]!["issues"]!;
JObject pageInfo(JObject result) => (JObject)issues(result)
["pageInfo"]!;
}

Vamos nos concentrar no algoritmo de paginação e na estrutura assíncrona do código


anterior. (Você pode consultar a documentação do GraphQL do GitHub para obter
detalhes sobre a API do GraphQL do GitHub). O método RunPagedQueryAsync enumera
os problemas dos mais recentes aos mais antigos. Ele solicita 25 problemas por página e
examina a estrutura pageInfo da resposta para continuar com a página anterior. Isso
segue o suporte de paginação padrão do GraphQL para respostas com várias páginas. A
resposta inclui um objeto pageInfo que inclui um valor hasPreviousPages e um valor
startCursor usados para solicitar a página anterior. Os problemas estão na matriz

nodes . O método RunPagedQueryAsync anexa esses nós em uma matriz que contém

todos os resultados de todas as páginas.

Após a recuperação e a restauração de uma página de resultados, RunPagedQueryAsync


informa o andamento e verifica o cancelamento. Se o cancelamento tiver sido solicitado,
RunPagedQueryAsync gerará um OperationCanceledException.

Há vários elementos nesse código que podem ser melhorados. Acima de tudo,
RunPagedQueryAsync deve alocar armazenamento para todos os problemas retornados.

Este exemplo é interrompido em 250 problemas porque recuperar todos os problemas


exigiria muito mais memória para armazenar todos os problemas recuperados. Os
protocolos para dar suporte a relatórios de progresso e cancelamento dificultam o
entendimento do algoritmo em sua primeira leitura. Mais tipos e APIs estão envolvidos.
Você também tem que rastrear as comunicações por meio de CancellationTokenSource
e seu CancellationToken associado para entender onde o cancelamento foi solicitado e
onde ele foi concedido.

Os fluxos assíncronos fornecem uma melhor


maneira
Os fluxos assíncronos e o suporte de linguagem associado lidam com todas essas
preocupações. O código que gera a sequência agora pode usar yield return para
retornar os elementos em um método que foi declarado com o modificador async . É
possível consumir um fluxo assíncrono usando um loop await foreach da mesma forma
que é possível consumir qualquer sequência usando um loop foreach .

Esses novos recursos de linguagem dependem das três novas interfaces adicionadas ao
.NET Standard 2.1 e implementadas no .NET Core 3.0:

System.Collections.Generic.IAsyncEnumerable<T>
System.Collections.Generic.IAsyncEnumerator<T>
System.IAsyncDisposable

Essas três interfaces devem ser familiares à maioria dos desenvolvedores C#. Elas se
comportam de maneira semelhante às suas contrapartes síncronas:

System.Collections.Generic.IEnumerable<T>
System.Collections.Generic.IEnumerator<T>
System.IDisposable

Um tipo que pode não ser familiar é System.Threading.Tasks.ValueTask. A estrutura


ValueTask fornece uma API semelhante para a classe System.Threading.Tasks.Task.

ValueTask é usado nas interfaces por motivos de desempenho.

Converter para fluxos assíncronos


Em seguida, converta o método RunPagedQueryAsync para gerar um fluxo assíncrono.
Primeiro, altere a assinatura de RunPagedQueryAsync para retornar um
IAsyncEnumerable<JToken> e remova os objetos de progresso e o token de

cancelamento da lista de parâmetros, conforme mostrado no código a seguir:

C#

private static async IAsyncEnumerable<JToken>


RunPagedQueryAsync(GitHubClient client,
string queryText, string repoName)

O código inicial processa cada página à medida que a página é recuperada, como
mostrado no código a seguir:

C#

finalResults.Merge(issues(results)["nodes"]!);
progress?.Report(issuesReturned);
cancel.ThrowIfCancellationRequested();

Substitua essas três linhas pelo seguinte código:

C#

foreach (JObject issue in issues(results)["nodes"]!)


yield return issue;

Você também pode remover a declaração de finalResults anteriormente nesse


método e a instrução return que segue o loop que você modificou.

Você terminou as alterações para gerar um fluxo assíncrono. O método concluído deve
ser semelhante ao seguinte código:

C#

private static async IAsyncEnumerable<JToken>


RunPagedQueryAsync(GitHubClient client,
string queryText, string repoName)
{
var issueAndPRQuery = new GraphQLRequest
{
Query = queryText
};
issueAndPRQuery.Variables["repo_name"] = repoName;

bool hasMorePages = true;


int pagesReturned = 0;
int issuesReturned = 0;

// Stop with 10 pages, because these are large repos:


while (hasMorePages && (pagesReturned++ < 10))
{
var postBody = issueAndPRQuery.ToJsonText();
var response = await client.Connection.Post<string>(new
Uri("https://api.github.com/graphql"),
postBody, "application/json", "application/json");

JObject results =
JObject.Parse(response.HttpResponse.Body.ToString()!);

int totalCount = (int)issues(results)["totalCount"]!;


hasMorePages = (bool)pageInfo(results)["hasPreviousPage"]!;
issueAndPRQuery.Variables["start_cursor"] = pageInfo(results)
["startCursor"]!.ToString();
issuesReturned += issues(results)["nodes"]!.Count();

foreach (JObject issue in issues(results)["nodes"]!)


yield return issue;
}

JObject issues(JObject result) => (JObject)result["data"]!


["repository"]!["issues"]!;
JObject pageInfo(JObject result) => (JObject)issues(result)
["pageInfo"]!;
}

Em seguida, você pode alterar o código que consome a coleção para consumir o fluxo
assíncrono. Localize o seguinte código em Main que processa a coleção de problemas:

C#

var progressReporter = new progressStatus((num) =>


{
Console.WriteLine($"Received {num} issues in total");
});
CancellationTokenSource cancellationSource = new CancellationTokenSource();

try
{
var results = await RunPagedQueryAsync(client, PagedIssueQuery, "docs",
cancellationSource.Token, progressReporter);
foreach(var issue in results)
Console.WriteLine(issue);
}
catch (OperationCanceledException)
{
Console.WriteLine("Work has been cancelled");
}

Substitua o código pelo seguinte loop await foreach :

C#

int num = 0;
await foreach (var issue in RunPagedQueryAsync(client, PagedIssueQuery,
"docs"))
{
Console.WriteLine(issue);
Console.WriteLine($"Received {++num} issues in total");
}

A nova interface IAsyncEnumerator<T> deriva de IAsyncDisposable. Isso significa que o


loop anterior descartará o fluxo de forma assíncrona quando o loop terminar. Você
pode imaginar que o loop se parece com o seguinte código:

C#

int num = 0;
var enumerator = RunPagedQueryAsync(client, PagedIssueQuery,
"docs").GetAsyncEnumerator();
try
{
while (await enumerator.MoveNextAsync())
{
var issue = enumerator.Current;
Console.WriteLine(issue);
Console.WriteLine($"Received {++num} issues in total");
}
} finally
{
if (enumerator != null)
await enumerator.DisposeAsync();
}

Por padrão, os elementos de fluxo são processados no contexto capturado. Se você


quiser desabilitar a captura do contexto, use o método de extensão
TaskAsyncEnumerableExtensions.ConfigureAwait. Para obter mais informações sobre
contextos de sincronização e captura do contexto atual, consulte o artigo Como
consumir o padrão assíncrono baseado em tarefa.

Os fluxos assíncronos dão suporte ao cancelamento usando o mesmo protocolo que


outros métodos async . Você modificaria a assinatura do método iterador assíncrono da
seguinte forma para dar suporte ao cancelamento:

C#

private static async IAsyncEnumerable<JToken>


RunPagedQueryAsync(GitHubClient client,
string queryText, string repoName, [EnumeratorCancellation]
CancellationToken cancellationToken = default)
{
var issueAndPRQuery = new GraphQLRequest
{
Query = queryText
};
issueAndPRQuery.Variables["repo_name"] = repoName;
bool hasMorePages = true;
int pagesReturned = 0;
int issuesReturned = 0;

// Stop with 10 pages, because these are large repos:


while (hasMorePages && (pagesReturned++ < 10))
{
var postBody = issueAndPRQuery.ToJsonText();
var response = await client.Connection.Post<string>(new
Uri("https://api.github.com/graphql"),
postBody, "application/json", "application/json");

JObject results =
JObject.Parse(response.HttpResponse.Body.ToString()!);

int totalCount = (int)issues(results)["totalCount"]!;


hasMorePages = (bool)pageInfo(results)["hasPreviousPage"]!;
issueAndPRQuery.Variables["start_cursor"] = pageInfo(results)
["startCursor"]!.ToString();
issuesReturned += issues(results)["nodes"]!.Count();

foreach (JObject issue in issues(results)["nodes"]!)


yield return issue;
}

JObject issues(JObject result) => (JObject)result["data"]!


["repository"]!["issues"]!;
JObject pageInfo(JObject result) => (JObject)issues(result)
["pageInfo"]!;
}

O atributo System.Runtime.CompilerServices.EnumeratorCancellationAttribute faz com


que o compilador gere código para o IAsyncEnumerator<T> que torna o token passado
para visível para GetAsyncEnumerator o corpo do iterador assíncrono como esse
argumento. Em runQueryAsync , você pode examinar o estado do token e cancelar mais
trabalhos, se solicitado.

Você usa outro método de extensão, WithCancellation, para passar o token de


cancelamento para o fluxo assíncrono. Você modificaria o loop ao enumerar os
problemas da seguinte forma:

C#

private static async Task EnumerateWithCancellation(GitHubClient client)


{
int num = 0;
var cancellation = new CancellationTokenSource();
await foreach (var issue in RunPagedQueryAsync(client, PagedIssueQuery,
"docs")
.WithCancellation(cancellation.Token))
{
Console.WriteLine(issue);
Console.WriteLine($"Received {++num} issues in total");
}
}

Você pode obter o código para o tutorial concluído do repositório dotnet/docs na


pasta asynchronous-programming/snippets .

Executar o aplicativo finalizado


Execute o aplicativo novamente. Compare esse comportamento com o comportamento
do aplicativo inicial. A primeira página de resultados é enumerada assim que fica
disponível. Há uma pausa observável à medida que cada nova página é solicitada e
recuperada, e os resultados da próxima página são rapidamente enumerados. O bloco
try / catch não é necessário para lidar com o cancelamento: o chamador pode

interromper a enumeração da coleção. O progresso é claramente informado, pois o


fluxo assíncrono gera resultados à medida que cada página é baixada. O status de cada
problema retornado está incluído diretamente no loop await foreach . Você não precisa
de um objeto de retorno de chamada para acompanhar o progresso.

Você pode ver as melhorias no uso da memória examinando o código. Não é mais
necessário alocar uma coleção para armazenar todos os resultados antes de serem
enumerados. O chamador pode determinar como consumir os resultados e se uma
coleção de armazenamento é necessária.

Execute o aplicativos inicial e o acabado, e observe você mesmo as diferenças entre as


implementações. Depois de terminar, você pode excluir o token de acesso de GitHub
criado ao iniciar este tutorial. Se um invasor obtiver acesso a esse token, ele poderá
acessar as APIs do GitHub usando suas credenciais.

Neste tutorial, você usou fluxos assíncronos para ler itens individuais de uma API de
rede que retorna páginas de dados. Fluxos assíncronos também podem ler de "fluxos
sem fim", como um ticker de ações ou dispositivo sensor. A chamada para
MoveNextAsync retorna o próximo item assim que ele estiver disponível.

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.
Tipos de referência anuláveis
Artigo • 01/02/2024

Em um contexto de anulável e alheio, todos os tipos de referência eram nulos. Tipos de


referência anuláveis refere-se a um grupo de recursos habilitados em um contexto com
reconhecimento de anulável que minimiza a probabilidade de que seu código faça com
que o runtime lance System.NullReferenceException. Os tipos de referência anuláveis
incluem três recursos que ajudam você a evitar essas exceções, incluindo a capacidade
de marcar explicitamente um tipo de referência como anulável:

Análise aprimorada do fluxo estático que determina se uma variável pode ser null
antes de desreferenciá-la.
Atributos que anotam APIs para que a análise de fluxo determine o estado nulo.
Anotações variáveis que os desenvolvedores usam para declarar explicitamente o
null-state pretendido para uma variável.

O compilador rastreia o estado nulo de cada expressão em seu código no momento da


compilação. O estado nulo tem um de três valores:

não nulo: a expressão é conhecida como não null .


possivelmente nulo: a expressão pode ser null .
alheio: o compilador não pode determinar o estado nulo da expressão.

As anotações de variáveis determinam a nulidade de uma variável de tipo de referência:

não anulável: se você atribuir um valor null ou uma expressão possivelmente nula
à variável, o compilador emitirá um aviso. As variáveis que são não anuláveis têm
um estado nulo padrão de não nulo.
anulável: você pode atribuir um valor null ou uma expressão possivelmente nula à
variável. Quando o estado nulo da variável é possivelmente nulo, o compilador
emite um aviso se você desreferenciar a variável. O estado nulo padrão da variável
é possivelmente nulo.
alheio: você pode atribuir um valor null ou uma expressão possivelmente nula à
variável. O compilador não emite avisos quando você desreferencia a variável ou
quando atribui uma expressão possivelmente nula à variável.

O estado nulo alheio e a nulidade alheia correspondem ao comportamento anterior à


introdução dos tipos de referência anuláveis. Esses valores são úteis durante a migração
ou quando seu aplicativo usa uma biblioteca que não habilitou tipos de referência
anuláveis.
A análise de estado nulo e as anotações variáveis são desabilitadas por padrão para
projetos existentes, o que significa que todos os tipos de referência continuam
anuláveis. A partir do .NET 6, eles são habilitados por padrão para novos projetos. Para
obter informações sobre como habilitar esses recursos declarando um contexto de
anotação anulável, consulte Contextos anuláveis.

O restante deste artigo descreve como essas três áreas de recursos funcionam para
gerar avisos quando seu código pode estar dereferenciando um valor null .
Desreferenciar uma variável significa acessar um de seus membros usando o operador
. (ponto), conforme mostrado no exemplo a seguir:

C#

string message = "Hello, World!";


int length = message.Length; // dereferencing "message"

Quando você desreferencia uma variável cujo valor é null , o runtime gera um
System.NullReferenceException.

Você saberá mais sobre:

A análise do estado nulo do compilador: como o compilador determina se uma


expressão é não nula ou possivelmente nula.
Atributos que são aplicados às APIs que fornecem mais contexto para a análise de
estado nulo do compilador.
Anotações de variáveis anuláveis que fornecem informações sobre sua intenção
com relação às variáveis. As anotações são úteis para que os campos definam o
estado nulo padrão no início dos métodos membros.
As regras que regem os argumentos de tipo genérico. Novas restrições foram
adicionadas porque os parâmetros de tipo podem ser tipos de referência ou tipos
de valor. O sufixo ? é implementado de forma diferente para tipos de valor
anuláveis e tipos de referência anulável.
Contextos nulos ajudam a migrar grandes projetos. Você pode habilitar contextos
ou avisos anuláveis em partes do seu aplicativo durante a migração. Depois de
resolver mais avisos, você pode habilitar os tipos de referência anuláveis para todo
o projeto.

Por fim, você aprenderá as armadilhas conhecidas da análise de estado nulo em tipos
struct e matrizes.

Você também pode explorar esses conceitos no módulo do Learn sobre Segurança de
anuláveis em C#.
análise de estado nulo
Quando os tipos de referência anuláveis estão habilitados, a Análise de estado nulo
rastreia o estado nulo das referências. Uma expressão é não nulo ou talvez nulo. O
compilador determina que uma variável é not-null de duas maneiras:

1. Foi atribuído à variável um valor que é conhecido como não nulo.


2. A variável foi verificada em relação a null e não foi modificada desde essa
verificação.

Quando os tipos de referência anuláveis não estão habilitados, todas as expressões têm
o estado nulo de alheio. O restante da seção descreve o comportamento quando os
tipos de referência anuláveis estão habilitados.

Qualquer variável que o compilador não tenha determinado como not-null é


considerada maybe-null. A análise fornece avisos em situações em que você pode
acidentalmente desreferenciar um valor null . O compilador produz avisos com base no
estado nulo.

Quando uma variável é não nula, essa variável pode ser desreferenciada com
segurança.
Quando uma variável é maybe-null, essa variável deve ser verificada para garantir
que não seja null antes de ser desreferenciada.

Considere o seguinte exemplo:

C#

string message = null;

// warning: dereference null.


Console.WriteLine($"The length of the message is {message.Length}");

var originalMessage = message;


message = "Hello, World!";

// No warning. Analysis determined "message" is not-null.


Console.WriteLine($"The length of the message is {message.Length}");

// warning!
Console.WriteLine(originalMessage.Length);

No exemplo anterior, o compilador determina que message é maybe-null quando a


primeira mensagem é impressa. Não há nenhum aviso para a segunda mensagem. A
linha final de código produz um aviso, já que originalMessage pode ser nula. O
exemplo a seguir mostra um uso mais prático para percorrer uma árvore de nós até a
raiz, processando cada nó durante a passagem:

C#

void FindRoot(Node node, Action<Node> processNode)


{
for (var current = node; current != null; current = current.Parent)
{
processNode(current);
}
}

O código anterior não gera avisos para desreferenciar a variável current . A análise
estática determina que current nunca é desreferenciada quando é maybe-null. A
variável current é verificada em relação a null antes de current.Parent ser acessada e
antes de passar current para a ação ProcessNode . Os exemplos anteriores mostram
como o compilador determina o null-state para variáveis locais quando inicializado,
atribuído ou comparado a null .

A análise de estado nulo não rastreia os métodos chamados. Como resultado,


os campos inicializados em um método auxiliar comum chamado por todos os
construtores geram um aviso com o seguinte modelo:

A propriedade 'name' não anulável deve conter um valor não nulo ao sair do
construtor.

Você pode resolver esses avisos de duas maneiras: encadeamento de construtores ou


atributos anuláveis no método auxiliar. O código a seguir mostra um exemplo de cada
um desses casos. A classe Person usa um construtor comum chamado por todos os
outros construtores. A classe Student tem um método auxiliar anotado com o atributo
System.Diagnostics.CodeAnalysis.MemberNotNullAttribute:

C#

using System.Diagnostics.CodeAnalysis;

public class Person


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

public Person(string firstName, string lastName)


{
FirstName = firstName;
LastName = lastName;
}

public Person() : this("John", "Doe") { }


}

public class Student : Person


{
public string Major { get; set; }

public Student(string firstName, string lastName, string major)


: base(firstName, lastName)
{
SetMajor(major);
}

public Student(string firstName, string lastName) :


base(firstName, lastName)
{
SetMajor();
}

public Student()
{
SetMajor();
}

[MemberNotNull(nameof(Major))]
private void SetMajor(string? major = default)
{
Major = major ?? "Undeclared";
}
}

7 Observação

Vários aprimoramentos na atribuição definitiva e na análise de estado nulo foram


adicionados ao C# 10. Ao atualizar para o C# 10, você poderá encontrar menos
avisos anuláveis que são falsos positivos. Você pode saber mais sobre as melhorias
na especificação de recursos para melhorias de atribuição definitivas.

Análise de estado anulável e os avisos gerados pelo compilador ajudam você a evitar
erros de programa ao desreferenciar null . O artigo sobre resolução de avisos anuláveis
fornece técnicas para corrigir os avisos mais prováveis de serem vistos em seu código.

Atributos em assinaturas de API


A análise de estado nulo precisa de dicas dos desenvolvedores para entender
a semântica das APIs. Algumas APIs fornecem verificações nulas e devem alterar o null-
state de uma variável de maybe-null para not-null. Outras APIs retornam expressões que
são not-null ou maybe-null, dependendo do null-state dos argumentos de entrada. Por
exemplo, considere o seguinte código que exibe uma mensagem em letras maiúsculas:

C#

void PrintMessageUpper(string? message)


{
if (!IsNull(message))
{
Console.WriteLine($"{DateTime.Now}: {message.ToUpper()}");
}
}

bool IsNull(string? s) => s == null;

Com base na inspeção, qualquer desenvolvedor consideraria esse código seguro e não
deveria gerar avisos. Entretanto, o compilador não sabe que IsNull fornece uma
verificação nula e emite um aviso para a instrução message.ToUpper() , considerando
message como uma variável possivelmente nula. Use o atributo NotNullWhen para

corrigir esse aviso:

C#

bool IsNull([NotNullWhen(false)] string? s) => s == null;

Esse atributo informa ao compilador que, se IsNull retornar false , o parâmetro s não
será nulo. O compilador altera o estado nulo de message para não nulo dentro do bloco
if (!IsNull(message)) {...} . Nenhum aviso é emitido.

Os atributos fornecem informações detalhadas sobre o estado nulo dos argumentos,


valores de retorno e membros da instância do objeto usada para invocar um membro.
Os detalhes sobre cada atributo podem ser encontrados no artigo de referência de
idioma sobre atributos de referência anuláveis. A partir do .NET 5, todas as APIs de
runtime do .NET serão anotadas. Você aprimora a análise estática anotando suas APIs
para fornecer informações semânticas sobre o null-state dos argumentos e os valores
retornados.

Anotações de variáveis anuláveis


A análise de estado nulo fornece uma análise robusta para variáveis locais. O compilador
precisa de mais informações de você para variáveis membro. O compilador precisa de
mais informações para definir o estado nulo de todos os campos no colchete de
abertura de um membro. Qualquer um dos construtores acessíveis pode ser usado para
inicializar o objeto. Se um campo membro for definido como null , o compilador deve
assumir que seu null-state seja maybe-null no início de cada método.

Você usa anotações que podem declarar se uma variável é um tipo de referência
anulável ou um tipo de referência não anulável. Essas anotações fazem instruções
importantes sobre o null-state das variáveis:

Uma referência não deve ser nula. O estado padrão de uma variável de referência
não anulável é não nulo. O compilador impõe regras que garantem que seja
seguro desreferenciar essas variáveis sem verificar primeiro que ela não está nula:
A variável deve ser inicializada para um valor não nulo.
A variável nunca pode receber o valor null . O compilador emite um aviso
quando o código atribui uma expressão maybe-null a uma variável que não
deve ser nula.
Uma referência pode ser nula. O estado padrão de uma variável de referência
anulável é maybe-null. O compilador impõe regras para garantir que você verifique
corretamente se há uma referência null :
A variável só poderá ser desreferenciada quando o compilador puder garantir
que o valor não seja null .
Essas variáveis podem ser inicializadas com o valor padrão null e podem
receber o valor null em outro código.
O compilador não emite avisos quando o código atribui uma expressão
possivelmente nula a uma variável que pode ser nula.

Qualquer variável de referência não anulável tem um estado nulo padrão de não nulo.
Qualquer variável de referência anulável tem o estado inicial nulo de possivelmente nulo.

Um tipo de referência que permite valor nulo é indicado usando a mesma sintaxe que
tipos de valor que permitem valor nulo: um ? é acrescentado ao tipo da variável. Por
exemplo, a seguinte declaração de variável representa uma variável de cadeia de
caracteres que permite valor nulo, name :

C#

string? name;

Quando os tipos de referência anuláveis estiverem habilitados, qualquer variável em que


o ? não esteja anexado ao nome do tipo é um tipo de referência não anulável. Isso
inclui todas as variáveis de tipo de referência no código existente quando você habilitar
esse recurso. No entanto, todas as variáveis locais tipadas implicitamente (declaradas
usando var ) são tipos de referência anuláveis. Como as seções anteriores mostraram, a
análise estática determina o estado nulo das variáveis locais para verificar se elas estão
possivelmente nulas antes de desreferenciá-las.

Às vezes, você deve substituir um aviso quando souber que uma variável não é nula,
mas o compilador determina que seu null-state é maybe-null. Você usa o operador null-
forgiving ! seguindo um nome de variável para forçar o null-state a ser not-null. Por
exemplo, se você sabe que a variável name não é null , mas o compilador emite um
aviso, é possível escrever o seguinte código para substituir a análise do compilador:

C#

name!.Length;

Os tipos de referência anuláveis e os tipos de valor anuláveis fornecem um conceito


semântico semelhante: uma variável pode representar um valor ou objeto, ou essa
variável pode ser null . No entanto, tipos de referência anuláveis e tipos de valor
anulável são implementados de forma diferente: tipos de valor anuláveis são
implementados usando System.Nullable<T> e tipos de referência anuláveis são
implementados por atributos lidos pelo compilador. Por exemplo, string? e string são
ambos representados pelo mesmo tipo: System.String. No entanto, int? e int são
representados por System.Nullable<System.Int32> e System.Int32, respectivamente.

Tipos de referência anuláveis são um recurso de tempo de compilação. Isso significa


que é possível que os chamadores ignorem avisos, usar null intencionalmente como
um argumento para um método que espera uma referência não anulável. Os autores da
biblioteca devem incluir verificações de runtime em relação a valores de argumento
nulos. O ArgumentNullException.ThrowIfNull é a opção preferencial para verificar um
parâmetro em relação ao nulo no tempo de execução.

) Importante

Habilitar anotações anuláveis pode alterar a forma como o Entity Framework Core
determina se um membro de dados é necessário. Você pode conferir mais detalhes
no artigo sobre Conceitos básicos do Entity Framework Core: Trabalhando com
tipos de referência anuláveis.

Genéricos
Os genéricos exigem regras detalhadas para lidar com T? para qualquer parâmetro de
tipo T . As regras são necessariamente detalhadas devido ao histórico e à
implementação diferente para um tipo de valor anulável e um tipo de referência
anulável. Tipos de valor anuláveis são implementados usando o struct
System.Nullable<T>. Tipos de referência anuláveis são implementados como anotações
de tipo que fornecem regras semânticas para o compilador.

Se o argumento de tipo para T for um tipo de referência, T? referencia o tipo de


referência anulável correspondente. Por exemplo, se T for um string , então T?
será um string? .
Se o argumento de tipo for T um tipo de valor, T? faça referência ao mesmo tipo
de valor, T . Por exemplo, se T for um int , o T? também será um int .
Se o argumento de tipo para T for um tipo de referência anulável, T? referencia o
mesmo tipo de referência anulável correspondente. Por exemplo, se T for um
string? , então T? será um string? .

Se o argumento de tipo para T for um tipo de valor anulável, T? referencia o


mesmo tipo de valor anulável correspondente. Por exemplo, se T for um int? ,
então T? será um int? .

Para valores retornados, T? é equivalente a [MaybeNull]T ; para valores de argumento,


T? é equivalente a [AllowNull]T . Para obter mais informações, consulte o artigo sobre

Atributos para análise de null-state na referência da linguagem.

Você pode especificar um comportamento diferente usando restrições:

A restrição class significa que T deve ser um tipo de referência não anulável (por
exemplo, string ). O compilador produz um aviso se você usar um tipo de
referência anulável, como string? para T .
A restrição class? significa que T deve ser um tipo de referência, não anulável
( string ) ou um tipo de referência anulável (por exemplo, string? ). Quando o
parâmetro de tipo é um tipo de referência anulável, como string? , uma expressão
de T? referencia esse mesmo tipo de referência anulável, como string? .
A restrição notnull significa que T deve ser um tipo de referência não anulável ou
um tipo de valor não anulável. Se você usar um tipo de referência anulável ou um
tipo de valor anulável para o parâmetro de tipo, o compilador produzirá um aviso.
Além disso, quando T é um tipo de valor, o valor retornado é esse tipo de valor,
não o tipo de valor anulável correspondente.

Essas restrições ajudam a fornecer mais informações ao compilador sobre como T é


usado. Isso ajuda quando os desenvolvedores escolhem o tipo para T e proporciona
uma melhor análise de estado nulo quando uma instância de tipo genérico é usada.

Contextos que permitem valor nulo


Em projetos pequenos, você pode habilitar tipos de referência anuláveis, corrigir avisos
e continuar. No entanto, para projetos maiores e soluções de vários projetos, isso pode
gerar um grande número de avisos. Você pode usar pragmas para habilitar tipos de
referência anuláveis arquivo por arquivo à medida que começar a usar os tipos de
referência anuláveis. Os novos recursos que protegem contra o lançamento de um
System.NullReferenceException podem ser disruptivos quando ativados em uma base
de código existente:

Todas as variáveis de referência tipadas explicitamente são interpretadas como


tipos de referência não anuláveis.
O significado da restrição class em genéricos foi alterado para significar um tipo
de referência não anulável.
Novos avisos são gerados devido a essas novas regras.

O contexto de anotação anulável determina o comportamento do compilador. Há


quatro valores para o contexto de notação anulável:

desabilitar: o código é alheio anulável. Desabilitar corresponde ao comportamento


anterior à habilitação dos tipos de referência anuláveis, exceto pelo fato de que a
nova sintaxe produz avisos em vez de erros.
Avisos anuláveis estão desabilitados.
Todas as variáveis de tipo de referência são tipos de referência anuláveis.
O uso do sufixo ? para declarar um tipo de referência anulável produz um
aviso.
Você pode usar o operador tolerante a nulo, ! , mas ele não tem efeito.
enable: o compilador habilita toda a análise de referência nula e todos os recursos
de linguagem.
Todos os novos avisos anuláveis estão habilitados.
Você pode usar o sufixo ? para declarar um tipo de referência anulável.
As variáveis de tipo de referência sem o sufixo ? são tipos de referência não
anuláveis.
O operador de tolerância a nulo suprime avisos para uma possível atribuição a
null .

warnings: o compilador executa todas as análises nulas e emite avisos quando o


código pode desreferenciar null .
Todos os novos avisos anuláveis estão habilitados.
O uso do sufixo ? para declarar um tipo de referência anulável produz um
aviso.
Todas as variáveis de tipo de referência têm permissão para serem nulas. No
entanto, os membros têm o null-state de not-null na chave de abertura de
todos os métodos, a menos que declarados com o sufixo ? .
Você pode usar o operador de tolerância a nulo, ! .
anotações: o compilador não emite avisos quando o código pode desreferenciar
null ou quando você atribui uma expressão possivelmente nula a uma variável

não nula.
Todos os avisos anuláveis estão desabilitados.
Você pode usar o sufixo ? para declarar um tipo de referência anulável.
As variáveis de tipo de referência sem o sufixo ? são tipos de referência não
anuláveis.
Você pode usar o operador tolerante a nulo, ! , mas ele não tem efeito.

O contexto de anotação que permite valor nulo e o contexto de aviso que permite valor
nulo podem ser definidos para um projeto que usa o elemento <Nullable> em seu
arquivo .csproj. Esse elemento configura como o compilador interpreta a nulidade de
tipos e quais avisos são gerados. A tabela a seguir mostra os valores permitidos e
resume os contextos que eles especificam.

ノ Expandir a tabela

Contexto Avisos de Avisos de Tipos de referência Sufixo ? Operador !


desreferência atribuição

disable Desabilitado Desabilitado Todos são anuláveis Produz Não tem


um aviso efeito

enable habilitado habilitado Não anulável, a Declara Suprime


menos que tipo avisos para
declarado com ? anulável uma possível
atribuição
null

warnings habilitado Não Todos são anuláveis, Produz Suprime


aplicável mas os membros um aviso avisos para
são considerados uma possível
não nulos na atribuição
abertura dos null
métodos

annotations Desabilitado Desabilitado Não anulável, a Declara Não tem


menos que tipo efeito
declarado com ? anulável
As variáveis de tipo de referência no código compilado em um contexto disabled são
nullable-oblivious. Você pode atribuir um literal null ou uma variável possivelmente nula
para uma variável que seja alheia anulável. No entanto, o estado padrão de uma
variável nullable-oblivious é not-null.

Você pode escolher qual configuração é melhor para seu projeto:

Escolha disable em projetos herdados que você não deseja atualizar com base no
diagnóstico ou em novos recursos.
Escolha avisos para determinar o local em que seu código poderá gerar
System.NullReferenceExceptions. Você pode resolver esses avisos antes de
modificar o código para habilitar tipos de referência não anuláveis.
Escolha annotations para expressar sua intenção de design antes de habilitar
avisos.
Escolha enable para novos projetos e projetos ativos em que você deseja proteger
contra exceções de referência nulas.

Exemplo:

XML

<Nullable>enable</Nullable>

Também é possível usar diretivas para definir esses mesmos contextos em qualquer
lugar no código-fonte: Essas diretivas são mais úteis quando você está migrando uma
grande base de código.

#nullable enable : define o contexto de anotação que permite valor nulo e o

contexto de aviso que permite valor nulo como enable.


#nullable disable : define o contexto de anotação que permite valor nulo e o
contexto de aviso que permite valor nulo como disable.
#nullable restore : restaura o contexto de anotação que permite valor nulo e o

contexto de aviso que permite valor nulo para as configurações do projeto.


#nullable disable warnings : definir o contexto de aviso anulável para disable.
#nullable enable warnings : definir o contexto de aviso anulável para enable.

#nullable restore warnings : restaura o contexto de aviso anulável para as

configurações do projeto.
#nullable disable annotations : definir o contexto de anotação anulável para

disable.
#nullable enable annotations : definir o contexto de anotação anulável para

enable.
#nullable restore annotations : restaura o contexto de aviso de anotação para as

configurações do projeto.

Para qualquer linha de código, você pode definir qualquer uma das seguintes
combinações:

ノ Expandir a tabela

Contexto de aviso Contexto de anotação Use

Padrão do projeto Padrão do projeto Padrão

enable disable Corrigir avisos da análise

enable Padrão do projeto Corrigir avisos da análise

Padrão do projeto enable Adicionar anotações de tipo:

enable enable Código já migrado

disable enable Anotar código antes de corrigir avisos

disable disable Adicionando código herdado ao projeto migrado

Padrão do projeto disable Raramente

disable Padrão do projeto Raramente

Essas nove combinações fornecem controle refinado sobre os diagnósticos que o


compilador emite para seu código. Você pode habilitar mais recursos em qualquer área
que esteja atualizando, sem ver mais avisos que você ainda não está pronto para
resolver.

) Importante

O contexto global anulável não se aplica aos arquivos de código gerados. Em


qualquer das estratégias, o contexto anulável é desabilitado para todo arquivo de
origem marcado como gerado. Isso significa que todas as APIs em arquivos
gerados não são anotadas. Há quatro maneiras de um arquivo ser marcado como
gerado:

1. No .editorconfig, especifique generated_code = true em uma seção que se


aplica a esse arquivo.
2. Coloque <auto-generated> ou <auto-generated/> em um comentário na parte
superior do arquivo. Ele pode estar em qualquer linha nesse comentário, mas
o bloco de comentários deve ser o primeiro elemento do arquivo.
3. Inicie o nome do arquivo com TemporaryGeneratedFile_
4. Termine o nome do arquivo com .designer.cs, .generated.cs, .g.cs ou .g.i.cs.

Os geradores podem aceitar usando a diretiva de pré-processador #nullable.

Por padrão, os contextos de aviso e de anotação de anulável são desabilitados. Isso


significa que seu código existente compila sem alterações e sem gerar nenhum aviso
novo. A partir do .NET 6, novos projetos incluem o elemento
<Nullable>enable</Nullable> em todos os modelos de projeto.

Essas opções fornecem duas estratégias distintas para atualizar uma base de código
existente para usar tipos de referência anuláveis.

Armadilhas conhecidas
Matrizes e structs que contêm tipos de referência são armadilhas conhecidas em
referências anuláveis e na análise estática que determina a segurança nula. Em ambas as
situações, uma referência não anulável pode ser inicializada como null , sem gerar
avisos.

Estruturas
Um struct que contém tipos de referência não anuláveis permite atribuir default para
ele sem avisos. Considere o seguinte exemplo:

C#

using System;

#nullable enable

public struct Student


{
public string FirstName;
public string? MiddleName;
public string LastName;
}

public static class Program


{
public static void PrintStudent(Student student)
{
Console.WriteLine($"First name: {student.FirstName.ToUpper()}");
Console.WriteLine($"Middle name: {student.MiddleName?.ToUpper()}");
Console.WriteLine($"Last name: {student.LastName.ToUpper()}");
}

public static void Main() => PrintStudent(default);


}

No exemplo anterior, não há nenhum aviso em PrintStudent(default) , enquanto os


tipos de referência não anuláveis FirstName e LastName são nulos.

Outro caso mais comum é quando você lida com structs genéricos. Considere o
seguinte exemplo:

C#

#nullable enable

public struct S<T>


{
public T Prop { get; set; }
}

public static class Program


{
public static void Main()
{
string s = default(S<string>).Prop;
}
}

No exemplo anterior, a propriedade Prop é null em tempo de execução. Ele é


atribuído a uma cadeia de caracteres não nula sem nenhum aviso.

matrizes
Matrizes também são uma armadilha conhecida em tipos de referência anuláveis.
Considere o exemplo a seguir que não produz avisos:

C#

using System;

#nullable enable

public static class Program


{
public static void Main()
{
string[] values = new string[10];
string s = values[0];
Console.WriteLine(s.ToUpper());
}
}

No exemplo anterior, a declaração da matriz mostra que ela contém cadeias de


caracteres não anuláveis, enquanto seus elementos são inicializados para null . Em
seguida, a variável s recebe um valor ( null o primeiro elemento da matriz). Por fim, a
variável s é desreferenciada, causando uma exceção de runtime.

Confira também
Proposta de tipos de referência anulável
Especificação de tipos de referência anuláveis de rascunho
Anotações de parâmetro de tipo sem restrição
Introdução ao tutorial de referências que permitem valor nulo
Anulável (Opçõa do compilador 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
Atualizar uma base de código com tipos
de referência anuláveis para melhorar os
avisos de diagnóstico nulos
Artigo • 07/04/2023

Tipos de referência anuláveis permitem declarar se variáveis de um tipo de referência


devem ou não ser atribuídas a um valor null . A análise estática e os avisos do
compilador quando seu código pode desreferenciar null são o benefício mais
importante desse recurso. Depois de habilitado, o compilador gera avisos que ajudam
você a evitar a geração de System.NullReferenceException quando o código é
executado.

Se a base de código for relativamente pequena, você poderá ativar o recurso em seu
projeto, endereçar avisos e aproveitar os benefícios do diagnóstico aprimorado. Bases
de código maiores podem exigir uma abordagem mais estruturada para endereçar
avisos ao longo do tempo, habilitando o recurso para alguns, conforme você aborda
avisos em diferentes tipos ou arquivos. Este artigo descreve diferentes estratégias para
atualizar uma base de código e as compensações associadas a essas estratégias. Antes
de iniciar a migração, leia a visão geral conceitual dos tipos de referência anuláveis. Ele
aborda a análise estática do compilador, valores do estado de nulo iguais a talvez nulo e
não nulo e anotações anuláveis. Depois de conhecer esses conceitos e termos, você
estará pronto para migrar seu código.

Planeje sua migração


Independentemente de como você atualiza sua base de código, a meta é que avisos
anuláveis e anotações anuláveis sejam habilitados em seu projeto. Depois de atingir
essa meta, você terá a configuração <nullable>Enable</nullable> em seu projeto. Você
não precisará de nenhuma das diretivas de pré-processador para ajustar as
configurações em outro lugar.

A primeira opção é definir o padrão do projeto. Suas opções são:

1. Anulável desabilitado como padrão: Disable será o padrão se você não adicionar
um elemento Nullable ao arquivo de projeto. Use esse padrão quando não estiver
ativamente adicionando novos arquivos à base de código. A atividade principal é
atualizar a biblioteca para usar tipos de referência anuláveis. Usar esse padrão
significa que você adiciona uma diretiva de pré-processador anulável a cada
arquivo à medida que atualiza o código deles.
2. Anulável habilitado como padrão: defina esse padrão quando estiver
desenvolvendo ativamente novos recursos. Você deseja que todos os novos
códigos beneficiem tipos de referência anuláveis e análise estática anulável. Usar
esse padrão significa que você deve adicionar #nullable disable à parte superior
de cada arquivo. Você removerá essas diretivas de pré-processador ao resolver os
avisos em cada arquivo.
3. Avisos anuláveis como padrão: escolha esse padrão para uma migração em duas
fases. Na primeira fase, aborde os avisos. Na segunda fase, ative as anotações para
declarar o estado de nulo esperado de uma variável. Usar esse padrão significa que
você deve adicionar #nullable disable à parte superior de cada arquivo.
4. Anotações anuláveis como padrão. Faça anotações no código antes de abordar os
avisos.

Habilitar anulável como padrão cria um trabalho mais inicial para adicionar as diretivas
de pré-processador a cada arquivo. A vantagem é que cada novo arquivo de código
adicionado ao projeto será habilitado para anulável. Qualquer novo trabalho será
habilitado para anulável; somente o código existente deve ser atualizado. Desabilitar
anulável como o padrão funciona melhor se a biblioteca estiver estável e o foco
principal do desenvolvimento for adotar tipos de referência anuláveis. Você ativa tipos
de referência anuláveis à medida que anota as APIs. Ao terminar, você habilita os tipos
de referência anuláveis para o projeto inteiro. Ao criar um arquivo, você deve adicionar
as diretivas de pré-processador e habilitá-lo para anulável. Se algum desenvolvedor em
sua equipe esquecer, esse novo código agora estará no backlog do trabalho para tornar
todo o código habilitado para anulável.

A sua encolha entre essas estratégias depende da quantidade de desenvolvimento ativo


que está ocorrendo em seu projeto. Quanto mais maduro e estável o projeto, melhor
será a segunda estratégia. Quanto mais recursos sendo desenvolvidos, melhor será a
primeira estratégia.

) Importante

O contexto global anulável não se aplica aos arquivos de código gerados. Em


qualquer das estratégias, o contexto anulável é desabilitado para todo arquivo de
origem marcado como gerado. Isso significa que todas as APIs em arquivos
gerados não são anotadas. Há quatro maneiras de um arquivo ser marcado como
gerado:

1. No .editorconfig, especifique generated_code = true em uma seção que se


aplica a esse arquivo.
2. Coloque <auto-generated> ou <auto-generated/> em um comentário na parte
superior do arquivo. Ele pode estar em qualquer linha nesse comentário, mas
o bloco de comentários deve ser o primeiro elemento do arquivo.
3. Inicie o nome do arquivo com TemporaryGeneratedFile_
4. Termine o nome do arquivo com .designer.cs, .generated.cs, .g.cs ou .g.i.cs.

Os geradores podem aceitar usando a diretiva de pré-processador #nullable.

Entender contextos e avisos


Habilitar avisos e anotações controla como o compilador exibe tipos de referência e
nulidade. Cada tipo tem uma entre três nulidades:

oblivious: todos os tipos de referência são nullable oblivious quando o contexto de


anotação está desabilitado.
nonnullable: um tipo de referência não anotado, C é nonnullable quando o
contexto de anotação está habilitado.
nullable: um tipo de referência anotado, C? , é nullable, mas um aviso pode ser
emitido quando o contexto de anotação está desabilitado. As variáveis declaradas
com var são nullable quando o contexto de anotação está habilitado.

O compilador gera avisos com base nessa nulidade:

Tipos nonnullable geram avisos se um valor potencial null é atribuído a eles.


Tipos nullable geram avisos se são desreferenciados quando talvez nulos.
Tipos oblivious geram avisos se são desreferenciados quando talvez nulo e o
contexto de aviso está habilitado.

Cada variável tem um estado anulável padrão que depende de sua nulidade:

As variáveis nullable têm um estado de nulo padrão igual a talvez nulo.


As variáveis non-nullable têm um estado de nulo padrão igual a não nulo.
As variáveis nullable oblivious têm um estado de nulo padrão igual a não nulo.

Antes de habilitar tipos de referência anuláveis, todas as declarações da sua base de


código podem ser nullable oblivious. Isso é importante porque significa que todos os
tipos de referência têm um estado de nulo padrão igual a não nulo.

Abordar avisos
Se o projeto usa o Entity Framework Core, você deve ler as diretrizes dele sobre Como
trabalhar com tipos de referência anuláveis.

Ao iniciar a migração, você deve começar habilitando apenas avisos. Todas as


declarações permanecem nullable oblivious, mas você verá avisos quando desreferenciar
um valor após o estado de nulo dele mudar para talvez nulo. Ao abordar esses avisos,
você verificará nulo em mais locais e sua base de código se tornará mais resiliente. Para
aprender técnicas específicas adequadas a diferentes situações, confira o artigo sobre
Técnicas para resolver avisos anuláveis.

Você pode abordar avisos e habilitar anotações em cada arquivo ou classe antes de
continuar com outro código. No entanto, geralmente é mais eficiente resolver os avisos
gerados enquanto o contexto é avisos, antes de habilitar as anotações de tipo. Assim,
todos os tipos são oblivious até você abordar o primeiro conjunto de avisos.

Habilitar anotações de tipo


Depois de abordar o primeiro conjunto de avisos, você pode habilitar o contexto de
anotação. Isso altera os tipos de referência de oblivious para nonnullable. Todas as
variáveis declaradas com var são anuláveis. Essa alteração geralmente introduz novos
avisos. A primeira etapa para abordar os avisos do compilador é usar anotações ? em
tipos de retorno e parâmetro para indicar quando os argumentos ou os valores
retornados podem ser null . Ao realizar essa tarefa, sua meta não é apenas corrigir
avisos. A meta mais importante é fazer com que o compilador entenda sua intenção
com os possíveis valores nulos.

Atributos estendem anotações de tipo


Vários atributos foram adicionados para expressar informações adicionais sobre o
estado de nulo das variáveis. As regras das suas APIs provavelmente são mais
complicadas do que não nulas ou talvez nulo para todos os parâmetros e valores
retornados. Muitas das suas APIs têm regras mais complexas para quando as variáveis
podem ou não ser null . Nesses casos, você usará atributos para expressar essas regras.
Os atributos que descrevem a semântica da sua API são encontrados no artigo sobre
Atributos que afetam a análise anulável.

Próximas etapas
Depois de resolver todos os avisos após habilitar anotações, você pode definir o
contexto padrão do seu projeto como habilitado. Se você adicionou pragmas ao seu
código para o contexto de anotações ou avisos anuláveis, você pode removê-los. Com o
tempo, você pode ver novos avisos. Você pode escrever um código que introduz avisos.
Uma dependência de biblioteca pode ser atualizada para tipos de referência anuláveis.
Essas atualizações vão alterar os tipos dessa biblioteca de nullable oblivious para
nonnullable ou nullable.

Você também pode explorar esses conceitos em nosso módulo Learn sobre segurança
anulável em C#.
Métodos em C#
Artigo • 17/05/2024

Um método é um bloco de código que contém uma série de instruções. Um programa


faz com que as instruções sejam executadas chamando o método e especificando os
argumentos de método necessários. No C#, todas as instruções executadas são
realizadas no contexto de um método.

7 Observação

Este tópico aborda os métodos nomeados. Para obter mais informações sobre
funções anônimas, consulte Expressões lambda.

Assinaturas de método
Os métodos são declarados em class , record ou struct especificando:

Um nível de acesso opcional, como public ou private . O padrão é private .


Modificadores opcionais como abstract ou sealed .
O valor retornado ou void se o método não tiver nenhum.
O nome do método.
Quaisquer parâmetros de método. Os parâmetros de método estão entre
parênteses e separados por vírgulas. Parênteses vazios indicam que o método não
requer parâmetros.

Essas partes juntas formam a assinatura do método.

) Importante

Um tipo de retorno de um método não faz parte da assinatura do método para fins
de sobrecarga de método. No entanto, ele faz parte da assinatura do método ao
determinar a compatibilidade entre um delegado e o método para o qual ele
aponta.

O exemplo a seguir define uma classe chamada Motorcycle que contém cinco métodos:

C#

namespace MotorCycleExample
{
abstract class Motorcycle
{
// Anyone can call this.
public void StartEngine() {/* Method statements here */ }

// Only derived classes can call this.


protected void AddGas(int gallons) { /* Method statements here */ }

// Derived classes can override the base class implementation.


public virtual int Drive(int miles, int speed) { /* Method
statements here */ return 1; }

// Derived classes can override the base class implementation.


public virtual int Drive(TimeSpan time, int speed) { /* Method
statements here */ return 0; }

// Derived classes must implement this.


public abstract double GetTopSpeed();
}

A classe Motorcycle inclui um método sobrecarregado, Drive . Dois métodos têm o


mesmo nome, mas são diferenciados por seus tipos de parâmetro.

Invocação de método
Os métodos podem ser de instância ou estáticos. Você deve criar uma instância de um
objeto para invocar um método de instância nessa instância. Um método de instância
opera nessa instância e seus dados. Você invoca um método estático referenciando o
nome do tipo ao qual o método pertence; os métodos estáticos não operam nos dados
da instância. Tentar chamar um método estático por meio de uma instância do objeto
gera um erro do compilador.

Chamar um método é como acessar um campo. Após o nome do objeto (se você estiver
chamando um método de instância) ou o nome do tipo (se você estiver chamando um
método static ), adicione um ponto, o nome do método e parênteses. Os argumentos
são listados dentro dos parênteses e são separados por vírgulas.

A definição do método especifica os nomes e tipos de quaisquer parâmetros


obrigatórios. Quando um chamador invoca o método, ele fornece valores concretos,
chamados argumentos, para cada parâmetro. Os argumentos devem ser compatíveis
com o tipo de parâmetro, mas o nome do argumento, se for usado no código de
chamada, não precisa ser o mesmo do parâmetro nomeado definido no método. No
exemplo a seguir, o método Square inclui um único parâmetro do tipo int chamado i.
A primeira chamada do método passa para o método Square uma variável do tipo int
chamada num, a segunda, uma constante numérica e a terceira, uma expressão.
C#

public static class SquareExample


{
public static void Main()
{
// Call with an int variable.
int num = 4;
int productA = Square(num);

// Call with an integer literal.


int productB = Square(12);

// Call with an expression that evaluates to int.


int productC = Square(productA * 3);
}

static int Square(int i)


{
// Store input argument in a local variable.
int input = i;
return input * input;
}
}

A forma mais comum de invocação de método usa argumentos posicionais, ela fornece
os argumentos na mesma ordem que os parâmetros de método. Os métodos da classe
Motorcycle , podem, portanto, ser chamados como no exemplo a seguir. A chamada

para o método Drive , por exemplo, inclui dois argumentos que correspondem aos dois
parâmetros na sintaxe do método. O primeiro se torna o valor do parâmetro miles . O
segundo se torna o valor do parâmetro speed .

C#

class TestMotorcycle : Motorcycle


{
public override double GetTopSpeed() => 108.4;

static void Main()


{
var moto = new TestMotorcycle();

moto.StartEngine();
moto.AddGas(15);
_ = moto.Drive(5, 20);
double speed = moto.GetTopSpeed();
Console.WriteLine("My top speed is {0}", speed);
}
}
Você também pode usar argumentos nomeados em vez de argumentos posicionais ao
invocar um método. Ao usar argumentos nomeados, você especifica o nome do
parâmetro seguido por dois pontos (":") e o argumento. Os argumentos do método
podem aparecer em qualquer ordem, desde que todos os argumentos necessários
estejam presentes. O exemplo a seguir usa argumentos nomeados para invocar o
método TestMotorcycle.Drive . Neste exemplo, os argumentos nomeados são passados
na ordem oposta da lista de parâmetros do método.

C#

namespace NamedMotorCycle;

class TestMotorcycle : Motorcycle


{
public override int Drive(int miles, int speed) =>
(int)Math.Round((double)miles / speed, 0);

public override double GetTopSpeed() => 108.4;

static void Main()


{
var moto = new TestMotorcycle();
moto.StartEngine();
moto.AddGas(15);
int travelTime = moto.Drive(miles: 170, speed: 60);
Console.WriteLine("Travel time: approx. {0} hours", travelTime);
}
}
// The example displays the following output:
// Travel time: approx. 3 hours

Você pode invocar um método usando argumentos posicionais e argumentos


nomeados. No entanto, os argumentos posicionais só podem seguir argumentos
nomeados quando os argumentos nomeados estiverem nas posições corretas. O
exemplo a seguir invoca o método TestMotorcycle.Drive do exemplo anterior usando
um argumento posicional e um argumento nomeado.

C#

int travelTime = moto.Drive(170, speed: 55);

Métodos herdados e substituídos


Além dos membros que são definidos explicitamente em um tipo, um tipo herda
membros definidos em suas classes base. Como todos os tipos no sistema de tipos
gerenciado são herdados direta ou indiretamente da classe Object, todos os tipos
herdam seus membros, como Equals(Object), GetType() e ToString(). O exemplo a seguir
define uma classe Person , instancia dois objetos Person e chama o método
Person.Equals para determinar se os dois objetos são iguais. O método Equals , no

entanto, não é definido na classe Person ; ele é herdado do Object.

C#

public class Person


{
public string FirstName = default!;
}

public static class ClassTypeExample


{
public static void Main()
{
Person p1 = new() { FirstName = "John" };
Person p2 = new() { FirstName = "John" };
Console.WriteLine("p1 = p2: {0}", p1.Equals(p2));
}
}
// The example displays the following output:
// p1 = p2: False

Tipos podem substituir membros herdados usando a palavra-chave override e


fornecendo uma implementação para o método substituído. A assinatura do método
precisa ser igual à do método substituído. O exemplo a seguir é semelhante ao anterior,
exceto que ele substitui o método Equals(Object). (Ele também substitui o método
GetHashCode(), uma vez que os dois métodos destinam-se a fornecer resultados
consistentes.)

C#

namespace methods;

public class Person


{
public string FirstName = default!;

public override bool Equals(object? obj) =>


obj is Person p2 &&
FirstName.Equals(p2.FirstName);

public override int GetHashCode() => FirstName.GetHashCode();


}

public static class Example


{
public static void Main()
{
Person p1 = new() { FirstName = "John" };
Person p2 = new() { FirstName = "John" };
Console.WriteLine("p1 = p2: {0}", p1.Equals(p2));
}
}
// The example displays the following output:
// p1 = p2: True

Passando parâmetros
Os tipos no C# são tipos de valor ou tipos de referência. Para obter uma lista de tipos de
valor internos, consulte Tipos. Por padrão, os tipos de referência e tipos de valor são
passados por valor para um método.

Passando parâmetros por valor


Quando um tipo de valor é passado para um método por valor, uma cópia do objeto,
em vez do próprio objeto, é passada para o método. Portanto, as alterações no objeto
do método chamado não têm efeito no objeto original quando o controle retorna ao
chamador.

O exemplo a seguir passa um tipo de valor para um método por valor e o método
chamado tenta alterar o valor do tipo de valor. Ele define uma variável do tipo int , que
é um tipo de valor, inicializa o valor para 20 e o passa para um método chamado
ModifyValue que altera o valor da variável para 30. No entanto, quando o método

retorna, o valor da variável permanece inalterado.

C#

public static class ByValueExample


{
public static void Main()
{
var value = 20;
Console.WriteLine("In Main, value = {0}", value);
ModifyValue(value);
Console.WriteLine("Back in Main, value = {0}", value);
}

static void ModifyValue(int i)


{
i = 30;
Console.WriteLine("In ModifyValue, parameter value = {0}", i);
return;
}
}
// The example displays the following output:
// In Main, value = 20
// In ModifyValue, parameter value = 30
// Back in Main, value = 20

Quando um objeto do tipo de referência é passado para um método por valor, uma
referência ao objeto é passada por valor. Ou seja, o método recebe não o objeto em si,
mas um argumento que indica o local do objeto. Se você alterar um membro do objeto
usando essa referência, a alteração será refletida no objeto quando o controle retornar
para o método de chamada. No entanto, substituir o objeto passado para o método
não tem efeito no objeto original quando o controle retorna para o chamador.

O exemplo a seguir define uma classe (que é um tipo de referência) chamada


SampleRefType . Ele cria uma instância de um objeto SampleRefType , atribui 44 ao seu

campo value e passa o objeto para o método ModifyObject . Este exemplo faz
essencialmente a mesma coisa que o exemplo anterior: ele passa um argumento por
valor para um método. Mas como um tipo de referência é usado, o resultado é
diferente. A modificação feita em ModifyObject para o campo obj.value também muda
o campo value do argumento, rt , no método Main para 33, como a saída do exemplo
mostra.

C#

public class SampleRefType


{
public int value;
}

public static class ByRefTypeExample


{
public static void Main()
{
var rt = new SampleRefType { value = 44 };
ModifyObject(rt);
Console.WriteLine(rt.value);
}

static void ModifyObject(SampleRefType obj) => obj.value = 33;


}

Passando parâmetros por referência


Você passa um parâmetro por referência quando deseja alterar o valor de um
argumento em um método e deseja refletir essa alteração quando o controle retorna
para o método de chamada. Para passar um parâmetro por referência, use a palavra-
chave ref ou out. Você também pode passar um valor por referência para evitar a cópia
e ainda evitar modificações usando a palavra-chave in.

O exemplo a seguir é idêntico ao anterior, exceto que o valor é passado por referência
para o método ModifyValue . Quando o valor do parâmetro é modificado no método
ModifyValue , a alteração no valor é refletida quando o controle retorna ao chamador.

C#

public static class ByRefExample


{
public static void Main()
{
var value = 20;
Console.WriteLine("In Main, value = {0}", value);
ModifyValue(ref value);
Console.WriteLine("Back in Main, value = {0}", value);
}

private static void ModifyValue(ref int i)


{
i = 30;
Console.WriteLine("In ModifyValue, parameter value = {0}", i);
return;
}
}
// The example displays the following output:
// In Main, value = 20
// In ModifyValue, parameter value = 30
// Back in Main, value = 30

Um padrão comum que usa parâmetros pela referência envolve a troca os valores das
variáveis. Você passa duas variáveis para um método por referência e o método troca
seus conteúdos. O exemplo a seguir troca valores inteiros.

C#

public static class RefSwapExample


{
static void Main()
{
int i = 2, j = 3;
Console.WriteLine("i = {0} j = {1}", i, j);

Swap(ref i, ref j);

Console.WriteLine("i = {0} j = {1}", i, j);


}
static void Swap(ref int x, ref int y) =>
(y, x) = (x, y);
}
// The example displays the following output:
// i = 2 j = 3
// i = 3 j = 2

Passar um parâmetro de tipo de referência permite que você altere o valor da própria
referência, em vez de o valor de seus campos ou elementos individuais.

Coleções de parâmetros
Às vezes, o requisito de que você especifique o número exato de argumentos para o
método é restritivo. Usando a palavra-chave params para indicar que um parâmetro é
uma coleção de parâmetros, você permite que o método seja chamado com um
número variável de argumentos. O parâmetro marcado com a palavra-chave params
deve ser um tipo de coleção e ele deve ser o último parâmetro na lista de parâmetros
do método.

Um chamador pode, então, invocar o método de uma das quatro maneiras para o
parâmetro params :

Passando uma coleção do tipo apropriado que contém o número de elementos


desejado. O exemplo usa uma expressão de coleção para que o compilador crie
um tipo de coleção apropriado.
Passando uma lista separada por vírgulas de argumentos individuais do tipo
apropriado para o método. O compilador cria o tipo de coleção apropriado.
Passando null .
Não fornecendo um argumento para a coleção de parâmetros.

O exemplo a seguir define um método chamado GetVowels que retorna todas as vogais
de uma coleção de parâmetros. O método Main ilustra todas as quatro maneiras de
invocar o método. Os chamadores não precisam fornecer argumentos para parâmetros
que incluem o modificador params . Nesse caso, o parâmetro é uma coleção vazia.

C#

static class ParamsExample


{
static void Main()
{
string fromArray = GetVowels(["apple", "banana", "pear"]);
Console.WriteLine($"Vowels from collection expression:
'{fromArray}'");
string fromMultipleArguments = GetVowels("apple", "banana", "pear");
Console.WriteLine($"Vowels from multiple arguments:
'{fromMultipleArguments}'");

string fromNull = GetVowels(null);


Console.WriteLine($"Vowels from null: '{fromNull}'");

string fromNoValue = GetVowels();


Console.WriteLine($"Vowels from no value: '{fromNoValue}'");
}

static string GetVowels(params IEnumerable<string>? input)


{
if (input == null || !input.Any())
{
return string.Empty;
}

char[] vowels = ['A', 'E', 'I', 'O', 'U'];


return string.Concat(
input.SelectMany(
word => word.Where(letter =>
vowels.Contains(char.ToUpper(letter)))));
}
}

// The example displays the following output:


// Vowels from array: 'aeaaaea'
// Vowels from multiple arguments: 'aeaaaea'
// Vowels from null: ''
// Vowels from no value: ''

Antes do C# 13, o modificador params só pode ser usado com uma única matriz
dimensional.

Parâmetros e argumentos opcionais


Uma definição de método pode especificar que os parâmetros são obrigatórios ou que
são opcionais. Por padrão, os parâmetros são obrigatórios. Os parâmetros opcionais são
especificados incluindo o valor padrão do parâmetro na definição do método. Quando
o método for chamado, se nenhum argumento for fornecido para um parâmetro
opcional, o valor padrão será usado em vez disso.

Você atribuirá o valor padrão do parâmetro com um dos tipos de expressões a seguir:

Uma constante, como um número ou uma cadeia de caracteres literal.


Uma expressão do formulário default(SomeType) , em que SomeType pode ser um
tipo de valor ou um tipo de referência. Se for um tipo de referência, ele será
efetivamente o mesmo que especificar null . Você pode usar o literal default , pois
o compilador pode inferir o tipo a partir da declaração do parâmetro.

Uma expressão da forma new ValType() , em que ValType é um tipo de valor. Essa
expressão invoca o construtor sem parâmetros implícito do tipo de valor, que não
é de fato um membro do tipo.

7 Observação

No C# 10 e posterior, quando uma expressão do formulário new ValType()


invoca o construtor sem parâmetros definido explicitamente de um tipo de
valor, o compilador gera um erro, pois o valor do parâmetro padrão deve ser
uma constante de tempo de compilação. Use a expressão default(ValType)
ou o literal default para fornecer o valor padrão do parâmetro. Para obter
mais informações sobre construtores sem parâmetros, consulte a seção
Inicialização de struct e valores padrão do artigo Tipos de estrutura.

Se um método inclui parâmetros obrigatórios e opcionais, os parâmetros opcionais são


definidos no final da lista de parâmetros, após todos os parâmetros obrigatórios.

O exemplo a seguir define um método, ExampleMethod , que tem um parâmetro


obrigatório e dois opcionais.

C#

public class Options


{
public void ExampleMethod(int required, int optionalInt = default,
string? description = default)
{
var msg = $"{description ?? "N/A"}: {required} + {optionalInt} =
{required + optionalInt}";
Console.WriteLine(msg);
}
}

O chamador deve fornecer um argumento para todos os parâmetros opcionais até o


último parâmetro opcional para o qual um argumento é fornecido. No método
ExampleMethod , por exemplo, se o chamador fornecer um argumento para o parâmetro

description , ele deverá fornecer também um para o parâmetro optionalInt .


opt.ExampleMethod(2, 2, "Addition of 2 and 2"); é uma chamada de método válida,
opt.ExampleMethod(2, , "Addition of 2 and 0"); gera um erro do compilador de

“Argumento ausente”.

Se um método for chamado usando argumentos nomeados ou uma combinação de


argumentos posicionais e nomeados, o chamador poderá omitir todos os argumentos
após o último argumento posicional na chamada do método.

A exemplo a seguir chama o método ExampleMethod três vezes. As duas primeiras


chamadas de método usam argumentos posicionais. O primeiro omite ambos os
argumentos opcionais, enquanto o segundo omite o último argumento. A terceira
chamada de método fornece um argumento posicional para o parâmetro obrigatório,
mas usa um argumento nomeado para fornecer um valor para o parâmetro description
enquanto omite o argumento optionalInt .

C#

public static class OptionsExample


{
public static void Main()
{
var opt = new Options();
opt.ExampleMethod(10);
opt.ExampleMethod(10, 2);
opt.ExampleMethod(12, description: "Addition with zero:");
}
}
// The example displays the following output:
// N/A: 10 + 0 = 10
// N/A: 10 + 2 = 12
// Addition with zero:: 12 + 0 = 12

O uso de parâmetros opcionais afeta a resolução de sobrecarga ou a maneira como o


compilador do C# determina qual sobrecarga deve ser invocada para uma chamada de
método da seguinte maneira:

Um método, indexador ou construtor é um candidato para a execução se cada um


dos parâmetros corresponde, por nome ou posição, a um único argumento e esse
argumento pode ser convertido para o tipo do parâmetro.
Se mais de um candidato for encontrado, as regras de resolução de sobrecarga de
conversões preferenciais serão aplicadas aos argumentos que são especificados
explicitamente. Os argumentos omitidos para parâmetros opcionais são ignorados.
Se dois candidatos são considerados igualmente bons, a preferência vai para um
candidato que não tenha parâmetros opcionais para os quais argumentos foram
omitidos na chamada.
Valores retornados
Os métodos podem retornar um valor para o chamador. Se o tipo de retorno (o tipo
listado antes do nome do método) não for void , o método poderá retornar o valor
usando a palavra-chave return . Uma instrução com a palavra-chave return seguida por
uma variável, constante ou expressão que corresponde ao tipo de retorno retorna esse
valor para o chamador do método. Métodos com um tipo de retorno não nulo devem
usar a palavra-chave return para retornar um valor. A palavra-chave return também
interrompe a execução do método.

Se o tipo de retorno for void , uma instrução return sem um valor ainda será útil para
interromper a execução do método. Sem a palavra-chave return , a execução do
método é interrompida quando chega ao final do bloco de código.

Por exemplo, esses dois métodos usam a palavra-chave return para retornar inteiros:

C#

class SimpleMath
{
public int AddTwoNumbers(int number1, int number2) =>
number1 + number2;

public int SquareANumber(int number) =>


number * number;
}

Para usar um valor retornado de um método, o método de chamada pode usar a


chamada de método em si em qualquer lugar que um valor do mesmo tipo seria
suficiente. Você também pode atribuir o valor retornado a uma variável. Por exemplo, os
dois exemplos de código a seguir obtêm a mesma meta:

C#

int result = obj.AddTwoNumbers(1, 2);


result = obj.SquareANumber(result);
// The result is 9.
Console.WriteLine(result);

C#

result = obj.SquareANumber(obj.AddTwoNumbers(1, 2));


// The result is 9.
Console.WriteLine(result);
Às vezes, você deseja que seu método retorne mais de um único valor. Você usa tipos de
tupla e literais de tupla para retornar vários valores. O tipo de tupla define os tipos de
dados dos elementos da tupla. Os literais de tupla fornecem os valores reais da tupla
retornada. No exemplo a seguir, (string, string, string, int) define o tipo de tupla
retornado pelo método GetPersonalInfo . A expressão (per.FirstName, per.MiddleName,
per.LastName, per.Age) é a tupla literal, o método retorna o nome, o nome do meio e o

sobrenome, juntamente com a idade, de um objeto PersonInfo .

C#

public (string, string, string, int) GetPersonalInfo(string id)


{
PersonInfo per = PersonInfo.RetrieveInfoById(id);
return (per.FirstName, per.MiddleName, per.LastName, per.Age);
}

O chamador pode consumir a tupla retornada usando o seguinte código:

C#

var person = GetPersonalInfo("111111111");


Console.WriteLine($"{person.Item1} {person.Item3}: age = {person.Item4}");

Os nomes também podem ser atribuídos aos elementos da tupla na definição de tipo
de tupla. O exemplo a seguir mostra uma versão alternativa do método
GetPersonalInfo que usa elementos nomeados:

C#

public (string FName, string MName, string LName, int Age)


GetPersonalInfo(string id)
{
PersonInfo per = PersonInfo.RetrieveInfoById(id);
return (per.FirstName, per.MiddleName, per.LastName, per.Age);
}

A chamada anterior para o método GetPersonalInfo pode ser modificada da seguinte


maneira:

C#

var person = GetPersonalInfo("111111111");


Console.WriteLine($"{person.FName} {person.LName}: age = {person.Age}");
Se um método usar uma matriz como parâmetro e modificar o valor de elementos
individuais, não será necessário que o método retorne a matriz. O C# passa todos os
tipos de referência por valor e o valor de uma referência de matriz é o ponteiro para a
matriz. No exemplo a seguir, as alterações no conteúdo da matriz values realizados
pelo método DoubleValues são observáveis por qualquer código que faz referência à
matriz.

C#

public static class ArrayValueExample


{
static void Main()
{
int[] values = [2, 4, 6, 8];
DoubleValues(values);
foreach (var value in values)
{
Console.Write("{0} ", value);
}
}

public static void DoubleValues(int[] arr)


{
for (var ctr = 0; ctr <= arr.GetUpperBound(0); ctr++)
{
arr[ctr] *= 2;
}
}
}
// The example displays the following output:
// 4 8 12 16

Métodos de extensão
Normalmente, há duas maneiras de adicionar um método a um tipo existente:

Modificar o código-fonte para esse tipo. Modificar a fonte criar uma alteração
interruptiva, se você também adicionar campos de dados privados para dar
suporte ao método.
Definir o novo método em uma classe derivada. Não é possível adicionar um
método dessa forma usando a herança para outros tipos, como estruturas e
enumerações. Isso também não pode ser usado para “adicionar” um método a
uma classe selada.
Os métodos de extensão permitem que você “adicione” um método a um tipo existente
sem modificar o tipo em si ou implementar o novo método em um tipo herdado. O
método de extensão também não precisa residir no mesmo assembly do tipo que ele
estende. Você chama um método de extensão como se fosse um membro definido de
um tipo.

Para obter mais informações, consulte Métodos de extensão.

Métodos assíncronos
Usando o recurso async, você pode invocar métodos assíncronos sem usar retornos de
chamada explícitos ou dividir manualmente seu código entre vários métodos ou
expressões lambda.

Se marcar um método com o modificador async, você poderá usar o operador await no
método. Quando o controle atingir uma expressão await no método assíncrono, o
controle retornará para o chamador se a tarefa aguardada não estiver concluída, e o
progresso no método com a palavra-chave await será suspenso até a tarefa aguardada
ser concluída. Quando a tarefa for concluída, a execução poderá ser retomada no
método.

7 Observação

Um método assíncrono retorna para o chamador quando encontra o primeiro


objeto esperado que ainda não está completo ou chega ao final do método
assíncrono, o que ocorrer primeiro.

Um método assíncrono normalmente tem um tipo de retorno Task<TResult>, Task,


IAsyncEnumerable<T> ou void . O tipo de retorno void é usado principalmente para
definir manipuladores de eventos, nos quais o tipo de retorno void é necessário. Um
método assíncrono que retorna void não pode ser aguardado e o chamador de um
método de retorno nulo não pode capturar as exceções que esse método gera. Um
método assíncrono pode ter qualquer tipo de retorno como os de tarefa.

No exemplo a seguir, DelayAsync é um método assíncrono que contém uma instrução


return que retorna um inteiro. Como é um método assíncrono, sua declaração de
método deve ter um tipo de retorno Task<int> . Como o tipo de retorno é Task<int> , a
avaliação da expressão await em DoSomethingAsync produz um inteiro, como a
instrução int result = await delayTask a seguir demonstra.

C#
class Program
{
static Task Main() => DoSomethingAsync();

static async Task DoSomethingAsync()


{
Task<int> delayTask = DelayAsync();
int result = await delayTask;

// The previous two statements may be combined into


// the following statement.
//int result = await DelayAsync();

Console.WriteLine($"Result: {result}");
}

static async Task<int> DelayAsync()


{
await Task.Delay(100);
return 5;
}
}
// Example output:
// Result: 5

Um método assíncrono não pode declarar os parâmetros in, ref nem out, mas pode
chamar métodos que tenham esses parâmetros.

Para obter mais informações sobre os métodos assíncronos, consulte Programação


assíncrona com async e await e Tipos de retorno Async.

Membros aptos para expressão


É comum ter definições de método que retornam imediatamente com o resultado de
uma expressão ou que têm uma única instrução como o corpo do método. Há um
atalho de sintaxe para definir esses métodos usando => :

C#

public Point Move(int dx, int dy) => new Point(x + dx, y + dy);
public void Print() => Console.WriteLine(First + " " + Last);
// Works with operators, properties, and indexers too.
public static Complex operator +(Complex a, Complex b) => a.Add(b);
public string Name => First + " " + Last;
public Customer this[long id] => store.LookupCustomer(id);
Se o método retornar void ou for um método assíncrono, o corpo do método deverá
ser uma expressão de instrução (igual aos lambdas). Para propriedades e indexadores,
eles devem ser somente leitura e você não usa a palavra-chave do acessador get .

Iterators
Um iterador realiza uma iteração personalizada em uma coleção, como uma lista ou
uma matriz. Um iterador usa a instrução yield return para retornar um elemento de cada
vez. Quando uma instrução yield return for atingida, o local atual será lembrado para
que o chamador possa solicitar o próximo elemento na sequência.

O tipo de retorno de um iterador pode ser IEnumerable, IEnumerable<T>,


IAsyncEnumerable<T>, IEnumerator ou IEnumerator<T>.

Para obter mais informações, consulte Iteradores.

Confira também
Modificadores de acesso
Classes static e membros de classes static
Herança
Classes e membros de classes abstract e sealed
params
out
ref
Em
Passando parâmetros

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
Iterators
Artigo • 10/05/2023

Quase todos os programas que você escrever terão alguma necessidade de iterar em
uma coleção. Você escreverá um código que examina cada item em uma coleção.

Você também criará métodos de iterador, que são métodos que produzem um iterator
para os elementos dessa classe. Um iterator é um objeto que percorre contêineres,
particularmente listas. Os iteradores podem ser usados para:

Executar uma ação em cada item em uma coleção.


Enumerar uma coleção personalizada.
Estender LINQ ou outras bibliotecas.
Criar um pipeline de dados em que os dados fluem com eficiência pelos métodos
de iterador.

A linguagem C# fornece recursos para gerar e consumir sequências. Essas sequências


podem ser produzidas e consumidas de maneira síncrona ou assíncrona. Este artigo
fornece uma visão geral desses recursos.

iterando com foreach


Enumerar uma coleção é simples: a palavra-chave foreach enumera uma coleção,
executando a instrução inserida uma vez para cada elemento na coleção:

C#

foreach (var item in collection)


{
Console.WriteLine(item?.ToString());
}

Isso é tudo. Para iterar em todo o conteúdo de uma coleção, a instrução foreach é tudo
o que você precisa. No entanto, a instrução foreach não é mágica. Ele se baseia em
duas interfaces genéricas definidas na biblioteca do .NET Core a fim de gerar o código
necessário para iterar uma coleção: IEnumerable<T> e IEnumerator<T> . Esse mecanismo
é explicado mais detalhadamente abaixo.

Essas duas interfaces também têm contrapartes não genéricas: IEnumerable e


IEnumerator . As versões genéricas são preferenciais para o código moderno.
Quando uma sequência é gerada de maneira assíncrona, você pode usar a instrução
await foreach para consumir essa sequência assincronamente:

C#

await foreach (var item in asyncSequence)


{
Console.WriteLine(item?.ToString());
}

Quando a sequência é uma System.Collections.Generic.IEnumerable<T>, você usa


foreach . Quando a sequência é uma System.Collections.Generic.IAsyncEnumerable<T>,
você usa await foreach . No último caso, a sequência é gerada de maneira assíncrona.

Fontes de enumeração com métodos de


iterador
Outro ótimo recurso da linguagem C# permite que você crie métodos que criam uma
fonte para uma enumeração. Esses métodos são chamados de métodos de iterador. Um
método de iterador define como gerar os objetos em uma sequência quando solicitado.
Você usa as palavras-chave contextuais yield return para definir um método iterador.

Você poderia escrever esse método para produzir a sequência de inteiros de 0 a 9:

C#

public IEnumerable<int> GetSingleDigitNumbers()


{
yield return 0;
yield return 1;
yield return 2;
yield return 3;
yield return 4;
yield return 5;
yield return 6;
yield return 7;
yield return 8;
yield return 9;
}

O código acima mostra instruções yield return distintas para destacar o fato de que
você pode usar várias instruções yield return discretas em um método iterador. Você
pode (e frequentemente o faz) usar outros constructos de linguagem para simplificar o
código de um método iterador. A definição do método abaixo produz a mesma
sequência exata de números:

C#

public IEnumerable<int> GetSingleDigitNumbersLoop()


{
int index = 0;
while (index < 10)
yield return index++;
}

Você não precisa determinar uma ou a outra. Você pode ter quantas instruções yield
return forem necessárias para atender as necessidades do seu método:

C#

public IEnumerable<int> GetSetsOfNumbers()


{
int index = 0;
while (index < 10)
yield return index++;

yield return 50;

index = 100;
while (index < 110)
yield return index++;
}

Todos esses exemplos anteriores teriam um equivalente assíncrono. Em cada caso, você
substituiria o tipo de retorno IEnumerable<T> por IAsyncEnumerable<T> . O exemplo
anterior, por exemplo, teria a seguinte versão assíncrona:

C#

public async IAsyncEnumerable<int> GetSetsOfNumbersAsync()


{
int index = 0;
while (index < 10)
yield return index++;

await Task.Delay(500);

yield return 50;

await Task.Delay(500);

index = 100;
while (index < 110)
yield return index++;
}

Essa é a sintaxe para iteradores síncronos e assíncronos. Vamos considerar um exemplo


do mundo real. Imagine que você está em um projeto de IoT e os sensores de
dispositivo geram um enorme fluxo de dados. Para ter uma noção dos dados, você
pode escrever um método realiza a amostragem a cada N elementos de dados. Esse
pequeno método iterador resolve:

C#

public static IEnumerable<T> Sample<T>(this IEnumerable<T> sourceSequence,


int interval)
{
int index = 0;
foreach (T item in sourceSequence)
{
if (index++ % interval == 0)
yield return item;
}
}

Se a leitura do dispositivo IoT produzir uma sequência assíncrona, você modificará o


método como mostra o seguinte exemplo:

C#

public static async IAsyncEnumerable<T> Sample<T>(this IAsyncEnumerable<T>


sourceSequence, int interval)
{
int index = 0;
await foreach (T item in sourceSequence)
{
if (index++ % interval == 0)
yield return item;
}
}

Há uma restrição importante em métodos de iterador: você não pode ter uma instrução
return e uma instrução yield return no mesmo método. O seguinte código não será

compilado:

C#

public IEnumerable<int> GetSingleDigitNumbers()


{
int index = 0;
while (index < 10)
yield return index++;

yield return 50;

// generates a compile time error:


var items = new int[] {100, 101, 102, 103, 104, 105, 106, 107, 108, 109
};
return items;
}

Essa restrição normalmente não é um problema. Você tem a opção de usar yield
return em todo o método ou separar o método original em vários métodos, alguns

usando return e alguns usando yield return .

Você pode modificar um pouco o último método para usar yield return em todos os
lugares:

C#

public IEnumerable<int> GetFirstDecile()


{
int index = 0;
while (index < 10)
yield return index++;

yield return 50;

var items = new int[] {100, 101, 102, 103, 104, 105, 106, 107, 108, 109
};
foreach (var item in items)
yield return item;
}

Às vezes, a resposta certa é dividir um método iterador em dois métodos diferentes. Um


que usa return e outro que usa yield return . Considere a situação em que você talvez
deseja retornar uma coleção vazia ou os primeiros cinco números ímpares, com base em
um argumento booliano. Você poderia escrever isso como esses dois métodos:

C#

public IEnumerable<int> GetSingleDigitOddNumbers(bool getCollection)


{
if (getCollection == false)
return new int[0];
else
return IteratorMethod();
}
private IEnumerable<int> IteratorMethod()
{
int index = 0;
while (index < 10)
{
if (index % 2 == 1)
yield return index;
index++;
}
}

Observe os métodos acima. O primeiro usa a instrução return padrão para retornar
uma coleção vazia ou o iterador criado pelo segundo método. O segundo método usa a
instrução yield return para criar a sequência solicitada.

Aprofundamento em foreach
A instrução foreach se expande em uma expressão padrão que usa as interfaces
IEnumerable<T> e IEnumerator<T> para iterar em todos os elementos de uma coleção.

Ela também minimiza os erros cometidos pelos desenvolvedores por não gerenciarem
os recursos adequadamente.

O compilador converte o loop foreach mostrado no primeiro exemplo em algo


semelhante a esse constructo:

C#

IEnumerator<int> enumerator = collection.GetEnumerator();


while (enumerator.MoveNext())
{
var item = enumerator.Current;
Console.WriteLine(item.ToString());
}

O código exato gerado pelo compilador é mais complicado e lida com situações em que
o objeto retornado por GetEnumerator() implementa a interface IDisposable . A
expansão completa gera um código mais semelhante a esse:

C#

{
var enumerator = collection.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
var item = enumerator.Current;
Console.WriteLine(item.ToString());
}
}
finally
{
// dispose of enumerator.
}
}

O compilador converte a primeira amostra assíncrona em algo semelhante a este


constructo:

C#

{
var enumerator = collection.GetAsyncEnumerator();
try
{
while (await enumerator.MoveNextAsync())
{
var item = enumerator.Current;
Console.WriteLine(item.ToString());
}
}
finally
{
// dispose of async enumerator.
}
}

A maneira na qual o enumerador é descartado depende das características do tipo de


enumerator . No caso síncrono geral, a cláusula finally se expande para:

C#

finally
{
(enumerator as IDisposable)?.Dispose();
}

O caso assíncrono geral se expande para:

C#

finally
{
if (enumerator is IAsyncDisposable asyncDisposable)
await asyncDisposable.DisposeAsync();
}

No entanto, se enumerator é de um tipo selado e não há nenhuma conversão implícita


do tipo de enumerator para IDisposable ou IAsyncDisposable , a cláusula finally se
expande para um bloco vazio:

C#

finally
{
}

Se houver uma conversão implícita do tipo de enumerator para IDisposable e


enumerator for um tipo de valor não anulável, a cláusula finally se expandirá para:

C#

finally
{
((IDisposable)enumerator).Dispose();
}

Felizmente, você não precisa se lembrar de todos esses detalhes. A instrução foreach
trata todas essas nuances para você. O compilador gerará o código correto para
qualquer um desses constructos.
Introdução a delegados e eventos em
C#
Artigo • 12/03/2024

Os delegados fornecem um mecanismo de associação tardia no .NET. Associação tardia


significa que você cria um algoritmo em que o chamador também fornece pelo menos
um método que implementa a parte do algoritmo.

Por exemplo, considere classificar uma lista de estrelas em um aplicativo de astronomia.


Você pode optar por classificar as estrelas por sua distância da terra ou a magnitude da
estrela ou seu brilho percebido.

Em todos esses casos, o método Sort() faz essencialmente a mesma coisa: organiza os
itens na lista com base em alguma comparação. O código que compara duas estrelas é
diferente para cada uma das ordenações de classificação.

Esses tipos de soluções foram usados no software por meio século. O conceito de
delegado de linguagem C# fornece suporte de linguagem de primeira classe e
segurança de tipos em torno do conceito.

Como você verá mais à frente nesta série, o código C# que você escreve para
algoritmos como esse é de fortemente tipado. O compilador garante que os tipos
correspondam a argumentos e tipos de retorno.

Os ponteiros de função dão suporte a cenários semelhantes, em que você precisa ter
mais controle sobre a convenção de chamada. O código associado a um delegado é
invocado usando um método virtual adicionado a um tipo delegado. Usando ponteiros
de função, você pode especificar convenções diferentes.

Metas de design da linguagem para delegados


Os designers de linguagem enumeraram várias metas para o recurso que
eventualmente se tornaram delegados.

A equipe queria um constructo de linguagem comum que pudesse ser usada qualquer
algoritmo de associação tardia. Delegados permitem aos desenvolvedores aprender um
conceito e usar esse mesmo conceito em muitos problemas de software diferentes.

Em segundo lugar, a equipe queria dar suporte a chamadas de método single ou


multicast. (Representantes multicast são delegados que encadeiam várias chamadas de
método. Você verá exemplos mais adiante nesta série.)
A equipe queria delegados para dar suporte à mesma segurança de tipos que os
desenvolvedores esperam de todos os constructos de C#.

Por fim, a equipe reconheceu que um padrão de eventos é um padrão específico em


que delegados ou qualquer algoritmo de associação tardia é útil. A equipe quis garantir
que o código para delegados pudesse fornecer a base para o padrão de evento .NET.

O resultado de todo esse trabalho era o suporte do delegado e do evento no C# e .NET.

Os demais artigos nessa série abordarão os recursos da linguagem, o suporte da


biblioteca e as expressões comuns que são usadas ao trabalhar com delegados e
eventos. Você saberá mais sobre:

A palavra-chave delegate e qual código ela gera.


Os recursos na classe System.Delegate e como esses recursos são usados.
Como criar delegados fortemente tipados.
Como criar métodos que podem ser invocados por meio de delegados.
Como trabalhar com eventos e delegados usando expressões lambda.
Como os delegados se tornam um dos blocos de construção para LINQ.
Como os delegados são a base para o padrão de evento do .NET e como eles são
diferentes.

Vamos começar.

Próximo

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
System.Delegate e a palavra-chave
delegate
Artigo • 10/05/2023

Anterior

Este artigo abordará as classes do .NET Framework que dão suporte a delegados e
como eles são mapeados para a palavra-chave delegate .

Definir tipos de delegado


Vamos começar com a palavra-chave ‘delegate’, pois ela é basicamente o que você
usará ao trabalhar com delegados. O código que o compilador gera quando você usa a
palavra-chave delegate será mapeado para chamadas de método que invocam
membros das classes Delegate e MulticastDelegate.

Você define um tipo de delegado usando uma sintaxe semelhante à definição de uma
assinatura de método. Basta adicionar a palavra-chave delegate à definição.

Vamos continuar a usar o método List.Sort() como nosso exemplo. A primeira etapa é
criar um tipo para o delegado de comparação:

C#

// From the .NET Core library

// Define the delegate type:


public delegate int Comparison<in T>(T left, T right);

O compilador gera uma classe, derivada de System.Delegate , que corresponde à


assinatura usada (nesse caso, um método que retorna um inteiro e tem dois
argumentos). O tipo do delegado é Comparison . O tipo delegado Comparison é um tipo
genérico. Para obter detalhes sobre os genéricos, consulte aqui.

Observe que a sintaxe pode aparecer como se estivesse declarando uma variável, mas
na verdade está declarando um tipo. Você pode definir tipos de delegado dentro de
classes, diretamente dentro de namespaces ou até mesmo no namespace global.

7 Observação
Declarar tipos de delegado (ou outros tipos) diretamente no namespace global não
é recomendado.

O compilador também gera manipuladores de adição e remoção para esse novo tipo de
forma que os clientes dessa classe podem adicionar e remover métodos de uma lista de
invocação de instância. O compilador imporá que a assinatura do método que está
sendo adicionado ou removido corresponda à assinatura usada ao declarar o método.

Declarar instâncias de delegados


Depois de definir o delegado, você pode criar uma instância desse tipo. Como todas as
variáveis em C#, você não pode declarar instâncias de delegado diretamente em um
namespace ou no namespace global.

C#

// inside a class definition:

// Declare an instance of that type:


public Comparison<T> comparator;

O tipo da variável é Comparison<T> , o tipo de delegado definido anteriormente. O nome


da variável é comparator .

Esse snippet de código acima declarou uma variável de membro dentro de uma classe.
Você também pode declarar variáveis de delegado que são variáveis locais ou
argumentos para métodos.

Invocar delegados
Você invoca os métodos que estão na lista de invocação de um delegado chamando
esse delegado. Dentro do método Sort() , o código chamará o método de comparação
para determinar em qual ordem posicionar objetos:

C#

int result = comparator(left, right);

Na linha acima, o código invoca o método anexado ao delegado. Você trata a variável
como um nome de método e a invoca usando a sintaxe de chamada de método normal.
Essa linha de código faz uma suposição não segura: não há garantia de que um destino
foi adicionado ao delegado. Se nenhum destino tiver sido anexado, a linha acima fará
com que um NullReferenceException seja lançado. As expressões usadas para resolver
esse problema são mais complicadas do que uma simples verificação de null e são
abordadas posteriormente nesta série.

Atribuir, adicionar e remover destinos de


invocação
Essa é a forma como o tipo do delegado é definido e como as instâncias de delegado
são declaradas e invocadas.

Os desenvolvedores que desejam usar o método List.Sort() precisa definir um


método cuja assinatura corresponde à definição de tipo de delegado e atribuí-lo ao
delegado usado pelo método de classificação. Esta atribuição adiciona o método à lista
de invocação do objeto de delegado.

Suponha que você queira classificar uma lista de cadeias de caracteres pelo seu
comprimento. A função de comparação pode ser a seguinte:

C#

private static int CompareLength(string left, string right) =>


left.Length.CompareTo(right.Length);

O método é declarado como um método particular. Tudo bem. Você pode não desejar
que esse método seja parte da sua interface pública. Ele ainda pode ser usado como o
método de comparação ao anexar a um delegado. O código de chamada terá esse
método anexado à lista de destino do objeto de delegado e pode acessá-lo por meio
do delegado.

Você cria essa relação passando esse método para o método List.Sort() :

C#

phrases.Sort(CompareLength);

Observe que o nome do método é usado, sem parênteses. Usar o método como um
argumento informa ao compilador para converter a referência de método em uma
referência que pode ser usada como um destino de invocação do delegado e anexar
esse método como um destino de invocação.
Você também poderia ter sido explícito declarando uma variável do tipo
Comparison<string> e fazendo uma atribuição:

C#

Comparison<string> comparer = CompareLength;


phrases.Sort(comparer);

Em utilizações em que o método que está sendo usado como um destinos de delegado
é um método pequeno, é comum usar a sintaxe da expressão lambda para executar a
atribuição:

C#

Comparison<string> comparer = (left, right) =>


left.Length.CompareTo(right.Length);
phrases.Sort(comparer);

O uso de expressões lambda para destinos de delegado será abordado em uma seção
posterior.

O exemplo de Sort() normalmente anexa um único método de destino ao delegado. No


entanto, objetos delegados dão suporte a listas de invocação que têm vários métodos
de destino anexados a um objeto de delegado.

Classes Delegate e MulticastDelegate


O suporte de linguagem descrito acima fornece os recursos e o suporte que você
normalmente precisará para trabalhar com delegados. Esses recursos são criados com
base em duas classes no .NET Core Framework: Delegate e MulticastDelegate.

A classe System.Delegate e sua única subclasse direta, System.MulticastDelegate ,


fornecem o suporte de estrutura para criar delegados, registrar métodos como destinos
de delegado e invocar todos os métodos que são registrados como um destino de
delegado.

Curiosamente, as classes System.Delegate e System.MulticastDelegate não são em si


tipos de delegado. Elas fornecem a base para todos os tipos de delegado específicos.
Esse mesmo processo de design de linguagem determinou que você não pode declarar
uma classe que deriva de Delegate ou MulticastDelegate . As regras da linguagem C#
proíbem isso.
Em vez disso, o compilador C# cria instâncias de uma classe derivada de
MulticastDelegate quando você usa a palavra-chave da linguagem C# para declarar os
tipos de delegado.

Esse design tem suas raízes na primeira versão do C# e do .NET. Uma meta da equipe
de design era garantir que a linguagem aplicava a segurança de tipos ao usar
delegados. Isso significava garantir que os delegados fossem invocados com o tipo e o
número de argumentos certos. E, que algum tipo de retorno fosse indicado no tempo
de compilação. Os delegados faziam parte da versão 1.0 do .NET, que era anterior aos
genéricos.

A melhor maneira de reforçar essa segurança de tipos foi o compilador criar as classes
de delegado concretas que representavam a assinatura do método sendo usado.

Embora não seja possível criar classes derivadas diretamente, você usará os métodos
definidos nessas classes. Vamos percorrer os métodos mais comuns que você usará ao
trabalhar com delegados.

O primeiro e mais importante fato a se lembrar é que todos os delegados com os quais
você trabalha são derivados de MulticastDelegate . Um delegado multicast significa que
mais de um destino de método pode ser invocado durante a invocação através de um
delegado. O design original considerava fazer uma distinção entre delegados em que
somente um método de destino poderia ser anexado e invocado e delegados em que
vários métodos de destino poderiam ser anexados e invocados. Essa distinção provou
ser menos útil na prática do que pensado originalmente. As duas classes diferentes já
foram criadas e estão na estrutura desde seu lançamento público inicial.

Os métodos que você usará mais com delegados são Invoke() e BeginInvoke() /
EndInvoke() . Invoke() invocará todos os métodos que foram anexados a uma instância
de delegado específica. Como você viu anteriormente, normalmente invoca delegados
usando a sintaxe de chamada de método na variável de delegado. Como você verá
posteriormente nesta série, existem padrões que trabalham diretamente com esses
métodos.

Agora que você viu a sintaxe da linguagem e as classes que dão suporte a delegados,
vamos examinar como os delegados fortemente tipados são usados, criados e
invocados.

Próximo
Delegados Fortemente Tipados
Artigo • 10/05/2023

Anterior

No artigo anterior, você viu como criar tipos de delegado específicos usando a palavra-
chave delegate .

A classe de Delegado abstrata fornece a infraestrutura para a invocação e acoplamento


fraco. Os tipos de delegado concretos se tornam muito mais úteis adotando e impondo
a segurança de tipos para os métodos que são adicionados à lista de invocação para um
objeto delegado. Quando você usa a palavra-chave delegate e define um tipo de
delegado concreto, o compilador gera esses métodos.

Na prática, isso poderia levar à criação de novos tipos de delegado sempre que precisar
de uma assinatura de método diferente. Esse trabalho pode se tornar entediante depois
de um tempo. Cada novo recurso exige novos tipos de delegado.

Felizmente, isso não é necessário. O .NET Core Framework contém vários tipos que
podem ser reutilizados sempre que você precisar de tipos de delegado. Essas são
definições genéricas para que você possa declarar personalizações quando precisar de
novas declarações de método.

O primeiro desses tipos é o tipo Action e diversas variações:

C#

public delegate void Action();


public delegate void Action<in T>(T arg);
public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);
// Other variations removed for brevity.

O modificador in no argumento de tipo genérico é abordado neste artigo sobre


covariância.

Há variações do delegado Action que contêm até 16 argumentos como


Action<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16>. É importante que essas
definições usem argumentos genéricos diferentes para cada um dos argumentos do
delegado: isso proporciona a máxima flexibilidade. Os argumentos de método não
precisam ser, mas podem ser, do mesmo tipo.

Use um dos tipos Action para qualquer tipo de delegado que tenha um tipo de retorno
nulo.
A estrutura também inclui vários tipos de delegado genérico que você pode usar para
tipos de delegado que retornam valores:

C#

public delegate TResult Func<out TResult>();


public delegate TResult Func<in T1, out TResult>(T1 arg);
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
// Other variations removed for brevity

O modificador out no argumento de tipo genérico de resultado é abordado neste


artigo sobre covariância.

Há variações do delegado Func com até 16 argumentos de entrada como


Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,TResult>. O tipo do
resultado é sempre o último parâmetro de tipo em todas as declarações Func , por
convenção.

Use um dos tipos Func para qualquer tipo de delegado que retorna um valor.

Há também um tipo Predicate<T> especializado para um delegado que retorna um


teste em um único valor:

C#

public delegate bool Predicate<in T>(T obj);

Você pode observar que para qualquer tipo Predicate , há um tipo Func
estruturalmente equivalente, por exemplo:

C#

Func<string, bool> TestForString;


Predicate<string> AnotherTestForString;

Você pode pensar que esses dois tipos são equivalentes. Eles não são. Essas duas
variáveis não podem ser usadas alternadamente. Uma variável de um tipo não pode ser
atribuída o outro tipo. O sistema de tipo do C# usa os nomes dos tipos definidos, não a
estrutura.

Todas essas definições de tipo de delegado na Biblioteca do .NET Core devem significar
que você não precisa definir um novo tipo de delegado para qualquer novo recurso
criado que exige delegados. Essas definições genéricas devem fornecer todos os tipos
de delegado necessários na maioria das situações. Você pode simplesmente instanciar
um desses tipos com os parâmetros de tipo necessários. No caso de algoritmos que
podem ser tornados genéricos, esses delegados podem ser usados como tipos
genéricos.

Isso deve economizar tempo e minimizar o número de novos tipos de que você precisa
criar a fim de trabalhar com delegados.

No próximo artigo, você verá vários padrões comuns para trabalhar com delegados na
prática.

Próximo
Padrões comuns para delegados
Artigo • 07/04/2023

Anterior

Os delegados fornecem um mecanismo que permite designs de software que envolvem


acoplamento mínimo entre os componentes.

Um exemplo excelente desse tipo de design é o LINQ. O padrão de expressão de


consulta LINQ se baseia em delegados para todos os seus recursos. Considere este
exemplo simples:

C#

var smallNumbers = numbers.Where(n => n < 10);

Isso filtra a sequência de números para somente aqueles com valor menor que 10. O
método Where usa um delegado que determina quais elementos de uma sequência são
passados no filtro. Quando cria uma consulta LINQ, você fornece a implementação do
delegado para essa finalidade específica.

O protótipo para o método Where é:

C#

public static IEnumerable<TSource> Where<TSource> (this IEnumerable<TSource>


source, Func<TSource, bool> predicate);

Este exemplo é repetido com todos os métodos que fazem parte do LINQ. Todos eles
contam com delegados para o código que gerencia a consulta específica. Esse padrão
de design de API é poderoso para aprender e entender.

Este exemplo simples ilustra como delegados requerem muito pouco acoplamento
entre componentes. Você não precisa criar uma classe que deriva de uma classe base
específica. Você não precisa implementar uma interface específica. O único requisito é
fornecer a implementação de um método que é fundamental para a tarefa em questão.

Criar componentes próprios com delegados


Vamos trabalhar naquele exemplo criando um componente usando um design que se
baseia em delegados.
Vamos definir um componente que poderia ser usado para mensagens de log em um
sistema grande. Os componentes da biblioteca poderiam ser usados em muitos
ambientes diferentes, em várias plataformas diferentes. Há muitos recursos comuns no
componente que gerencia os logs. Ele precisará aceitar mensagens de qualquer
componente do sistema. Essas mensagens terão prioridades diferentes, que o
componente de núcleo pode gerenciar. As mensagens devem ter carimbos de data/hora
em sua forma final arquivada. Para cenários mais avançados, é possível filtrar
mensagens pelo componente de origem.

Há um aspecto do recurso que é alterado com frequência: onde as mensagens são


gravadas. Em alguns ambientes, elas podem ser gravadas no console de erro. Em outros,
em um arquivo. Outras possibilidades incluem o armazenamento em banco de dados,
logs de eventos do sistema operacional ou outro armazenamento de documentos.

Também há combinações de saídas que podem ser usadas em cenários diferentes.


Talvez você queira gravar mensagens no console e em um arquivo.

Um design baseado em delegados fornece muita flexibilidade e facilitam o suporte a


mecanismos de armazenamento que podem ser adicionados no futuro.

Nesse design, o componente de log primário pode ser uma classe não virtual, até
mesmo lacrada. Você pode conectar qualquer conjunto de delegados para gravar as
mensagens em diferentes mídias de armazenamento. O suporte interno para delegados
multicast facilita o suporte a cenários em que as mensagens devem ser gravadas em
vários locais (um arquivo e um console).

Uma primeira implementação


Vamos começar pequeno: a implementação inicial aceitará novas mensagens e as
gravará usando qualquer delegado anexo. Você pode começar com um delegado que
grava mensagens no console.

C#

public static class Logger


{
public static Action<string>? WriteMessage;

public static void LogMessage(string msg)


{
if (WriteMessage is not null)
WriteMessage(msg);
}
}
A classe estática acima é a coisa mais simples que pode funcionar. Precisamos escrever
a única implementação do método que grava mensagens no console:

C#

public static class LoggingMethods


{
public static void LogToConsole(string message)
{
Console.Error.WriteLine(message);
}
}

Por fim, você precisa conectar o delegado anexando-o ao delegado WriteMessage


declarado no agente:

C#

Logger.WriteMessage += LoggingMethods.LogToConsole;

Práticas
Até agora, nossa amostra é bastante simples, mas ainda demonstra algumas das
diretrizes importantes para designs que envolvem delegados.

Usar os tipos de delegados definidos no Core Framework torna mais fácil para os
usuários trabalhem com os delegados. Você não precisa definir novos tipos e os
desenvolvedores que usam sua biblioteca não precisam aprender novos tipos de
delegados especializadas.

As interfaces usadas são tão mínimas e flexíveis quanto possível: para criar um novo
agente de saída, você precisa criar um método. O método pode ser um método estático
ou um método de instância. Ele pode ter qualquer acesso.

Formatar saída
Vamos fazer esta primeira versão um pouco mais robusta e, então, começar a criar
outros mecanismos de registro em log.

Em seguida, vamos adicionar alguns argumentos para o método LogMessage() para que
sua classe de log crie mensagens mais estruturadas:

C#
public enum Severity
{
Verbose,
Trace,
Information,
Warning,
Error,
Critical
}

C#

public static class Logger


{
public static Action<string>? WriteMessage;

public static void LogMessage(Severity s, string component, string msg)


{
var outputMsg = $"{DateTime.Now}\t{s}\t{component}\t{msg}";
if (WriteMessage is not null)
WriteMessage(outputMsg);
}
}

Em seguida, vamos usar aquele argumento Severity para filtrar as mensagens que são
enviadas para o log de saída.

C#

public static class Logger


{
public static Action<string>? WriteMessage;

public static Severity LogLevel { get; set; } = Severity.Warning;

public static void LogMessage(Severity s, string component, string msg)


{
if (s < LogLevel)
return;

var outputMsg = $"{DateTime.Now}\t{s}\t{component}\t{msg}";


if (WriteMessage is not null)
WriteMessage(outputMsg);
}
}

Práticas
Você adicionou novos recursos à infraestrutura de registro em log. Como somente o
componente do agente está acoplado de forma muito flexível a qualquer mecanismo de
saída, esses novos recursos podem ser adicionados sem afetar o código que
implementa o delegado do agente.

Conforme prosseguir com sua criação, você verá mais exemplos de como esse
acoplamento flexível permite maior versatilidade para atualizar partes do site sem
alterar outros locais. De fato, em um aplicativo maior, as classes de saída do agente
podem estar em um assembly diferente e nem mesmo precisar ser recriadas.

Criar um segundo mecanismo de saída


O componente de Log estará indo bem. Vamos adicionar mais um mecanismo de saída
que registra as mensagens em um arquivo. Esse será um mecanismo de saída de um
pouco mais envolvido. Ele será uma classe que encapsula as operações de arquivo e
garante que o arquivo sempre seja fechado após cada gravação. Isso garante que todos
os dados sejam liberados no disco após cada mensagem ser gerada.

Aqui está o agente baseado em arquivo:

C#

public class FileLogger


{
private readonly string logPath;
public FileLogger(string path)
{
logPath = path;
Logger.WriteMessage += LogMessage;
}

public void DetachLog() => Logger.WriteMessage -= LogMessage;


// make sure this can't throw.
private void LogMessage(string msg)
{
try
{
using (var log = File.AppendText(logPath))
{
log.WriteLine(msg);
log.Flush();
}
}
catch (Exception)
{
// Hmm. We caught an exception while
// logging. We can't really log the
// problem (since it's the log that's failing).
// So, while normally, catching an exception
// and doing nothing isn't wise, it's really the
// only reasonable option here.
}
}
}

Após ter criado essa classe, você pode instanciá-la e ela anexa o método LogMessage
ao componente do agente:

C#

var file = new FileLogger("log.txt");

Os dois não são mutuamente exclusivos. Você pode anexar os dois métodos de log e
gerar mensagens para o console e um arquivo:

C#

var fileOutput = new FileLogger("log.txt");


Logger.WriteMessage += LoggingMethods.LogToConsole; // LoggingMethods is the
static class we utilized earlier

Posteriormente, mesmo no mesmo aplicativo, você pode remover um dos delegados


sem problemas para o sistema:

C#

Logger.WriteMessage -= LoggingMethods.LogToConsole;

Práticas
Agora, você adicionou um segundo manipulador de saída para o subsistema de registro
em log. Este precisa de um pouco mais infraestrutura para dar suporte ao sistema de
arquivos corretamente. O delegado um método de instância. Ele também é um método
particular. Não é necessário ter mais acessibilidade porque a infraestrutura de
delegados pode conectar os delegados.

Em segundo lugar, o design baseado em delegados permite vários métodos de saída


sem nenhum código extra. Não é necessário criar nenhuma infraestrutura adicional para
dar suporte a vários métodos de saída. Eles simplesmente se tornam outro método na
lista de invocação.
Dedique atenção especial ao código no método de saída do registro em log de
arquivos. Ele é codificado para garantir que não gere nenhuma exceção. Embora isso
nem sempre seja estritamente necessário, geralmente é uma boa prática. Se um dos
métodos de delegado gerar uma exceção, os delegados restantes que fazem parte da
invocação não serão invocados.

Como uma última observação, o agente de arquivos deve gerenciar seus recursos
abrindo e fechando o arquivo em cada mensagem de log. Você pode optar por manter
o arquivo aberto e implementar IDisposable para fechar o arquivo quando terminar.
Cada método tem suas vantagens e desvantagens. Ambos criam um pouco mais de
acoplamento entre as classes.

Nenhum código na classe Logger precisaria ser atualizado para dar suporte a qualquer
um dos cenários.

Manipular delegados nulos


Por fim, vamos atualizar o método LogMessage para que ele seja robusto para os casos
em que nenhum mecanismo de saída está selecionado. A implementação atual gerará
um NullReferenceException quando o delegado WriteMessage não tiver uma lista de
invocação anexada. Talvez você prefira um design que continua silenciosamente quando
não nenhum método tiver sido anexado. Isso é fácil usando o operador condicional
nulo, combinado com o método Delegate.Invoke() :

C#

public static void LogMessage(string msg)


{
WriteMessage?.Invoke(msg);
}

O operador condicional nulo ( ?. ) entra em curto-circuito quando o operando esquerdo


( WriteMessage nesse caso) for nulo, o que significa que não é feita nenhuma tentativa
de registrar uma mensagem.

Você não encontrará o método Invoke() listado na documentação de System.Delegate


ou System.MulticastDelegate . O compilador gera um método Invoke fortemente tipado
para qualquer tipo de delegado declarado. Neste exemplo, isso significa que Invoke usa
um único argumento string e tem um tipo de retorno nulo.

Resumo das práticas


Você já viu o início de um componente de log que poderia ser expandido com outros
gravadores e outros recursos. Com o uso de delegados no design, esses diferentes
componentes ficam acoplados de maneira flexível. Isso traz vários benefícios. É fácil criar
mecanismos de saída e anexá-los ao sistema. Esses outros mecanismos precisam de
apenas um método: o método que grava a mensagem de log. Trata-se de um design
que é resiliente quando novos recursos são adicionados. O contrato necessário para
qualquer gravador é implementar um método. Esse método pode ser estático ou de
instância. Ele pode ser ter acesso público, privado ou qualquer outro acesso válido.

A classe de agente pode fazer vários aprimoramentos ou alterações sem introduzir


alterações interruptivas. Assim como qualquer classe, você não pode modificar a API
pública sem o risco de fazer alterações interruptivas. Mas, como o acoplamento entre o
agente e qualquer mecanismo de saída ocorre somente por meio do delegado, nenhum
outro tipo (como interfaces ou classes base) é envolvido. O acoplamento é o menor
possível.

Próximo
Introdução a eventos
Artigo • 10/05/2023

Anterior

Eventos são, assim como delegados, um mecanismo de associação tardia. De fato, os


eventos são criados com base no suporte de linguagem para delegados.

Os eventos são uma forma de um objeto difundir (para todos os componentes


interessados do sistema) que algo aconteceu. Qualquer outro componente pode assinar
ao evento e ser notificado quando um evento for gerado.

Provavelmente, você usou eventos em alguma parte de sua programação. Muitos


sistemas gráficos têm um modelo de evento para informar a interação do usuário. Esses
eventos informariam movimentos do mouse, pressionamentos de botão e interações
semelhantes. Esse é um dos cenários mais comuns, mas certamento não o único cenário
em que eventos são usados.

Você pode definir os eventos que devem ser gerados para suas classes. Uma
consideração importante ao trabalhar com eventos é que pode não haver nenhum
objeto registrado para um determinado evento. Você deve escrever seu código de
modo que ele não gere eventos quando nenhum ouvinte estiver configurado.

Assinar um evento também cria um acoplamento entre dois objetos (a origem do


evento e o coletor do evento). Você precisa garantir que o coletor do evento cancele a
assinatura da origem do evento quando não houver mais interesse nos eventos.

Metas de design para o suporte a eventos


O design de linguagem para eventos tem como alvo as seguintes metas:

Habilitar um acoplamento muito mínimo entre uma origem do evento e um


coletor de eventos. Esses dois componentes não podem ter sido escritos pela
mesma organização e podem até mesmo ser atualizados segundo cronogramas
totalmente diferentes.

Deve ser muito simples assinar um evento e cancelar a assinatura desse mesmo
evento.

As origens do evento devem dar suporte a vários assinantes de eventos. Elas


também devem dar suporte a não ter assinantes de evento anexados.
Você pode ver que as metas para os eventos são muito semelhantes às metas para
delegados. É por isso que o suporte à linguagem do evento é baseado no suporte à
linguagem do delegado.

Suporte de linguagem para eventos


A sintaxe para definir eventos e se inscrever ou cancelar a inscrição em eventos é uma
extensão da sintaxe de delegados.

Para definir um evento, você use a palavra-chave event :

C#

public event EventHandler<FileListArgs> Progress;

O tipo de evento ( EventHandler<FileListArgs> neste exemplo) deve ser um tipo


delegado. Há uma série de convenções que você deve seguir ao declarar um evento.
Normalmente, o tipo de delegado do evento tem um retorno nulo. Declarações de
evento devem ser um verbo ou uma frase verbal. Use o tempo passado quando o
evento relatar algo que aconteceu. Use um tempo verbal presente (por exemplo,
Closing ) para informar algo que está prestes a ocorrer. Frequentemente, usar o tempo

presente indica que sua classe dá suporte a algum tipo de comportamento de


personalização. Um dos cenários mais comuns é dar suporte ao cancelamento. Por
exemplo, um evento Closing pode incluir um argumento que indicaria se a operação de
encerramento deve continuar ou não. Outros cenários podem permitir que os
chamadores modifiquem o comportamento atualizando propriedades dos argumentos
do evento. Você pode acionar um evento para indicar uma próxima ação proposta que
um algoritmo usará. O manipulador de eventos pode forçar uma ação diferente
modificando as propriedades do argumento do evento.

Quando quiser acionar o evento, você pode chamar os manipuladores de eventos


usando a sintaxe de invocação de delegado:

C#

Progress?.Invoke(this, new FileListArgs(file));

Conforme discutido na seção sobre delegados, o operador ?. torna fácil garantir que
você não tente acionar o evento quando não houver nenhum assinante do evento.

Assine um evento usando o operador += :


C#

EventHandler<FileListArgs> onProgress = (sender, eventArgs) =>


Console.WriteLine(eventArgs.FoundFile);

fileLister.Progress += onProgress;

O método de manipulador normalmente tem o prefixo "On" seguido pelo nome do


evento, conforme mostrado acima.

Cancele a assinatura usando o operador -= :

C#

fileLister.Progress -= onProgress;

É importante declarar uma variável local para a expressão que representa o manipulador
de eventos. Isso garante que o cancelamento da assinatura remova o manipulador. Se,
em vez disso, você tiver usado o corpo da expressão lambda, você estará tentando
remover um manipulador que nunca foi anexado, o que não faz nada.

No próximo artigo, você aprenderá mais sobre padrões de evento típicos e diferentes
variações deste exemplo.

Próximo
Padrões de evento .NET padrão
Artigo • 10/05/2023

Anterior

Os eventos do .NET geralmente seguem alguns padrões conhecidos. Adotar esses


padrões significa que os desenvolvedores podem aproveitar o conhecimento desses
padrões, que podem ser aplicados a qualquer programa de evento do .NET.

Vamos analisar esses padrões para que você obtenha todo o conhecimento que precisa
a fim de criar origens do evento padrão e também assinar e processar eventos padrão
em seu código.

Assinaturas de delegado de evento


A assinatura padrão de um delegado de evento do .NET é:

C#

void EventRaised(object sender, EventArgs args);

O tipo de retorno é nulo. Os eventos são baseados em delegados e são delegados


multicast. Isso dá suporte a vários assinantes de qualquer origem do evento. O único
valor retornado de um método não ajusta a escala para vários assinantes do evento.
Qual valor retornado a origem do evento vê depois de gerar um evento? Neste artigo,
você verá como criar protocolos de evento que oferecem suporte a assinantes de
evento que relatam informações para a origem do evento.

A lista de argumentos contém dois argumentos: o remetente e os argumentos do


evento. O tipo de tempo de compilação de sender é System.Object , mas é provável que
você conheça um tipo mais derivado que sempre estaria correto. Por convenção, use
object .

O segundo argumento normalmente tem sido um tipo derivado de System.EventArgs .


(Você verá na próxima seção que essa convenção não é mais imposta.) Se o tipo de
evento não precisar de argumentos adicionais, você ainda fornecerá ambos os
argumentos. Há um valor especial, o EventArgs.Empty , que você deve usar para indicar
que o evento não contém nenhuma informação adicional.

Vamos criar uma classe que lista os arquivos em um diretório ou em qualquer um de


seus subdiretórios, que seguem um padrão. Esse componente aciona um evento para
cada arquivo encontrado que corresponde ao padrão.

O uso de um modelo de evento fornece algumas vantagens de design. Você pode criar
vários ouvintes de eventos que realizam ações diferentes quando um arquivo procurado
é encontrado. A combinação de diferentes ouvintes pode criar algoritmos mais
robustos.

Aqui está a declaração de argumento de evento inicial para localizar um arquivo


pesquisado:

C#

public class FileFoundArgs : EventArgs


{
public string FoundFile { get; }

public FileFoundArgs(string fileName) => FoundFile = fileName;


}

Embora esse tipo se pareça com um tipo pequeno de somente dados, você deve seguir
a convenção e torná-lo um tipo de referência ( class ). Isso significa que o objeto de
argumento será passado por referência e todas as atualizações nos dados serão
visualizadas por todos os assinantes. A primeira versão é um objeto imutável. É
preferível tornar as propriedades em seu tipo de argumento de evento imutáveis. Dessa
forma, um assinante não poderá alterar os valores antes que outro assinante os veja.
(Há exceções, como você verá abaixo).

Em seguida, precisamos criar a declaração de evento na classe FileSearcher. O


aproveitamento do tipo EventHandler<T> significa que não é necessário criar outra
definição de tipo. Você simplesmente usa uma especialização genérica.

Vamos preencher a classe FileSearcher para pesquisar arquivos que correspondam a um


padrão e acionar o evento correto quando uma correspondência for descoberta.

C#

public class FileSearcher


{
public event EventHandler<FileFoundArgs>? FileFound;

public void Search(string directory, string searchPattern)


{
foreach (var file in Directory.EnumerateFiles(directory,
searchPattern))
{
RaiseFileFound(file);
}
}

private void RaiseFileFound(string file) =>


FileFound?.Invoke(this, new FileFoundArgs(file));
}

Definir e gerar eventos semelhantes a campos


A maneira mais simples de adicionar um evento à sua classe é declarar esse evento
como um campo público, como no exemplo anterior:

C#

public event EventHandler<FileFoundArgs>? FileFound;

Isso é semelhante à declaração de um campo público, o que parece ser uma prática
ruim orientada a objetos. Você deseja proteger o acesso a dados por meio de
propriedades ou métodos. Embora isso possa parecer uma prática ruim, o código
gerado pelo compilador cria wrappers para que os objetos de evento só possam ser
acessados de maneiras seguras. As únicas operações disponíveis em um evento
semelhante a campo são adicionar manipulador:

C#

var fileLister = new FileSearcher();


int filesFound = 0;

EventHandler<FileFoundArgs> onFileFound = (sender, eventArgs) =>


{
Console.WriteLine(eventArgs.FoundFile);
filesFound++;
};

fileLister.FileFound += onFileFound;

e remover manipulador:

C#

fileLister.FileFound -= onFileFound;

Observe que há uma variável local para o manipulador. Se você usou o corpo de
lambda, a operação remover não funcionará corretamente. Ela seria uma instância
diferente do delegado e silenciosamente não faria nada.
O código fora da classe não pode acionar o evento nem executar outras operações.

Valor retornados de assinantes de evento


Sua versão simples está funcionando bem. Vamos adicionar outro recurso:
cancelamento.

Quando você acionar o evento encontrado, os ouvintes devem conseguir parar o


processamento, se esse arquivo for o procurado.

Os manipuladores de eventos não retornam um valor, por isso você precisa comunicar
isso de outra forma. O padrão de evento usa o objeto EventArgs para incluir campos
que os assinantes de evento podem usar para comunicar o cancelamento.

Há dois padrões diferentes que podem ser usados, com base na semântica do contrato
de cancelamento. Em ambos os casos, você adicionará um campo booliano no
EventArguments para o evento de arquivo encontrado.

Um padrão permitiria a qualquer assinante cancelar a operação. Para esse padrão, o


novo campo é inicializado para false . Qualquer assinante pode alterá-lo para true .
Depois que todos os assinantes viram o evento acionado, o componente FileSearcher
examina o valor booliano e toma uma ação.

O segundo padrão apenas cancelaria a operação se todos os assinantes quisessem que


a operação fosse cancelada. Nesse padrão, o novo campo é inicializado para indicar que
a operação deve ser cancelada e qualquer assinante poderia alterá-lo para indicar que a
operação deve continuar. Depois que todos os assinantes viram o evento acionado, o
componente FileSearcher examina o booliano e toma uma ação. Há uma etapa adicional
nesse padrão: o componente precisa saber se algum assinante viu o evento. Se não
houver nenhum assinante, o campo indicaria incorretamente um cancelamento.

Vamos implementar a primeira versão deste exemplo. Você precisa adicionar um campo
booliano chamado CancelRequested ao tipo FileFoundArgs :

C#

public class FileFoundArgs : EventArgs


{
public string FoundFile { get; }
public bool CancelRequested { get; set; }

public FileFoundArgs(string fileName) => FoundFile = fileName;


}
Este novo campo é inicializado automaticamente para false , o valor padrão para um
campo Boolean , para que você não cancele acidentalmente. A única alteração adicional
no componente é verificar o sinalizador depois de acionar o evento, para ver se
qualquer um dos assinantes solicitou um cancelamento:

C#

private void SearchDirectory(string directory, string searchPattern)


{
foreach (var file in Directory.EnumerateFiles(directory, searchPattern))
{
FileFoundArgs args = RaiseFileFound(file);
if (args.CancelRequested)
{
break;
}
}
}

private FileFoundArgs RaiseFileFound(string file)


{
var args = new FileFoundArgs(file);
FileFound?.Invoke(this, args);
return args;
}

Uma vantagem desse padrão é que ele não é uma alteração significativa. Nenhum dos
assinantes solicitou um cancelamento antes e ainda não fizeram. Nenhuma parte do
código de assinante precisa ser atualizado, a menos que eles queiram dar suporte ao
novo protocolo de cancelamento. Ele é acoplado de forma bem livre.

Vamos atualizar o assinante para que ele solicite um cancelamento, depois de encontrar
o primeiro executável:

C#

EventHandler<FileFoundArgs> onFileFound = (sender, eventArgs) =>


{
Console.WriteLine(eventArgs.FoundFile);
eventArgs.CancelRequested = true;
};

Adicionar outra declaração de evento


Vamos adicionar mais um recurso e demonstrar outras expressões de linguagem para
eventos. Vamos adicionar uma sobrecarga do método Search que percorre todas os
subdiretórios pesquisando arquivos.

Isso poderia se tornar uma operação demorada em um diretório com muitos


subdiretórios. Vamos adicionar um evento que é acionado no início de cada nova
pesquisa de diretório. Isso permite que os assinantes acompanhem o progresso e
atualizem o usuário sobre o progresso. Todos os exemplos que você criou até agora são
públicos. Vamos fazer com que esse seja um evento interno. Isso significa que você
também pode fazer com que os tipos usados para os argumentos sejam internos.

Você começará criando a nova classe derivada EventArgs para relatar o novo diretório e
o andamento.

C#

internal class SearchDirectoryArgs : EventArgs


{
internal string CurrentSearchDirectory { get; }
internal int TotalDirs { get; }
internal int CompletedDirs { get; }

internal SearchDirectoryArgs(string dir, int totalDirs, int


completedDirs)
{
CurrentSearchDirectory = dir;
TotalDirs = totalDirs;
CompletedDirs = completedDirs;
}
}

Novamente, você pode seguir as recomendações para criar um tipo de referência


imutável para os argumentos do evento.

Em seguida, defina o evento. Desta vez, você usará uma sintaxe diferente. Além de usar
a sintaxe de campo, você pode explicitamente criar a propriedade com os
manipuladores adicionar e remover. Nesta amostra, você não precisará de código extra
nos manipuladores, mas será mostrado como criá-los.

C#

internal event EventHandler<SearchDirectoryArgs> DirectoryChanged


{
add { _directoryChanged += value; }
remove { _directoryChanged -= value; }
}
private EventHandler<SearchDirectoryArgs>? _directoryChanged;
De muitas formas, o código que você escreverá aqui é bem parecido com o código que
o compilador gera para as definições de evento de campo vistas anteriormente. Você
cria o evento usando uma sintaxe muito parecida àquela utilizada para propriedades.
Observe que os manipuladores têm nomes diferentes: add e remove . Eles são chamados
para assinar o evento ou cancelar a inscrição do evento. Observe que você também
deve declarar um campo de suporte particular para armazenar a variável de evento. Ele
é inicializado como null.

Em seguida, vamos adicionar a sobrecarga do método Search que percorre os


subdiretórios e aciona os dois eventos. A maneira mais fácil de fazer isso é usar um
argumento padrão para especificar que você deseja pesquisar todas as pastas:

C#

public void Search(string directory, string searchPattern, bool


searchSubDirs = false)
{
if (searchSubDirs)
{
var allDirectories = Directory.GetDirectories(directory, "*.*",
SearchOption.AllDirectories);
var completedDirs = 0;
var totalDirs = allDirectories.Length + 1;
foreach (var dir in allDirectories)
{
RaiseSearchDirectoryChanged(dir, totalDirs, completedDirs++);
// Search 'dir' and its subdirectories for files that match the
search pattern:
SearchDirectory(dir, searchPattern);
}
// Include the Current Directory:
RaiseSearchDirectoryChanged(directory, totalDirs, completedDirs++);

SearchDirectory(directory, searchPattern);
}
else
{
SearchDirectory(directory, searchPattern);
}
}

private void SearchDirectory(string directory, string searchPattern)


{
foreach (var file in Directory.EnumerateFiles(directory, searchPattern))
{
FileFoundArgs args = RaiseFileFound(file);
if (args.CancelRequested)
{
break;
}
}
}

private void RaiseSearchDirectoryChanged(


string directory, int totalDirs, int completedDirs) =>
_directoryChanged?.Invoke(
this,
new SearchDirectoryArgs(directory, totalDirs, completedDirs));

private FileFoundArgs RaiseFileFound(string file)


{
var args = new FileFoundArgs(file);
FileFound?.Invoke(this, args);
return args;
}

Neste momento, você pode executar o aplicativo, chamando a sobrecarga para


pesquisar todos os subdiretórios. Não há nenhum assinante no novo evento
DirectoryChanged , mas o uso da expressão ?.Invoke() garante que isso funcione

corretamente.

Vamos adicionar um manipulador para escrever uma linha que mostra o andamento na
janela do console.

C#

fileLister.DirectoryChanged += (sender, eventArgs) =>


{
Console.Write($"Entering '{eventArgs.CurrentSearchDirectory}'.");
Console.WriteLine($" {eventArgs.CompletedDirs} of {eventArgs.TotalDirs}
completed...");
};

Você viu os padrões que são seguidos em todo o ecossistema do .NET. Ao aprender
esses padrões e convenções, você escreverá expressões idiomáticas de C# e .NET
rapidamente.

Confira também
Introdução a eventos
Design de evento
Manipular e gerar eventos

Em seguida, você verá algumas alterações nesses padrões na versão mais recente do
.NET.

Próximo
O padrão de eventos atualizado do .NET
Core Event
Artigo • 10/05/2023

Anterior

O artigo anterior abordou os padrões de eventos mais comuns. O .NET Core tem um
padrão mais flexível. Nesta versão, a definição EventHandler<TEventArgs> não tem a
restrição de que TEventArgs deve ser uma classe derivada de System.EventArgs .

Isso aumenta a flexibilidade para você e é compatível com versões anteriores. Vamos
começar com a flexibilidade. A classe System.EventArgs introduz um método:
MemberwiseClone() , que cria uma cópia superficial do objeto. Esse método deve usar a

reflexão para implementar sua funcionalidade para qualquer classe derivada de


EventArgs . Essa funcionalidade é mais fácil de criar em uma classe derivada específica.
Na prática, isso significa que a derivação de System.EventArgs é uma restrição que limita
seus designs, mas não oferece nenhum benefício adicional. Na verdade, você pode
alterar as definições de FileFoundArgs e SearchDirectoryArgs para que eles não
derivem de EventArgs . O programa funcionará exatamente da mesma forma.

Você também pode alterar o SearchDirectoryArgs para um struct, se você fizer mais
uma alteração:

C#

internal struct SearchDirectoryArgs


{
internal string CurrentSearchDirectory { get; }
internal int TotalDirs { get; }
internal int CompletedDirs { get; }

internal SearchDirectoryArgs(string dir, int totalDirs, int


completedDirs) : this()
{
CurrentSearchDirectory = dir;
TotalDirs = totalDirs;
CompletedDirs = completedDirs;
}
}

A alteração adicional é chamar o construtor sem parâmetro antes de inserir o construtor


que inicializa todos os campos. Sem esse acréscimo, as regras de C# informariam que as
propriedades estão sendo acessadas antes de terem sido atribuídas.
Você não deve alterar o FileFoundArgs de uma classe (tipo de referência) para um struct
(tipo de valor). Isso ocorre porque o protocolo para manipular cancelamentos exige que
os argumentos do evento sejam passados por referência. Se você fizesse a mesma
alteração, a classe de pesquisa de arquivo nunca observaria as alterações feitas por
qualquer um dos assinantes do evento. Uma nova cópia da estrutura seria usada para
cada assinante e essa cópia seria uma cópia diferente daquela vista pelo objeto de
pesquisa de arquivo.

Em seguida, vamos considerar como essa alteração pode ser compatível com versões
anteriores. A remoção da restrição não afeta nenhum código existente. Qualquer tipo de
argumento de evento existente ainda deriva de System.EventArgs . A compatibilidade
com versões anteriores é um dos principais motivos pelos quais eles vão continuar
derivando de System.EventArgs . Assinantes de eventos existentes serão assinantes de
um evento que seguiu o padrão clássico.

Seguindo uma lógica semelhante, qualquer tipo de argumento de evento criado agora
não teria assinantes nas bases de código existentes. Novos tipos de evento que não
derivam de System.EventArgs não quebram essas bases de código.

Eventos com assinantes assíncronos


Você tem um último padrão para aprender: como escrever corretamente os assinantes
do evento que chamam o código assíncrono. O desafio é descrito no artigo em async e
await. Métodos assíncronos podem ter um tipo de retorno nulo, mas isso não é
recomendável. Quando seu código de assinante de evento chama um método
assíncrono, você não terá outra escolha além de criar um método async void . A
assinatura do manipulador de eventos o exige.

Você precisa conciliar essas diretrizes opostas. De alguma forma, você precisa criar um
método async void seguro. As noções básicas do padrão que você precisa implementar
estão abaixo:

C#

worker.StartWorking += async (sender, eventArgs) =>


{
try
{
await DoWorkAsync();
}
catch (Exception e)
{
//Some form of logging.
Console.WriteLine($"Async task failure: {e.ToString()}");
// Consider gracefully, and quickly exiting.
}
};

Primeiro, observe que o manipulador está marcado como um manipulador assíncrono.


Como está sendo atribuído a um tipo de delegado de manipulador de eventos, ele terá
um tipo de retorno nulo. Isso significa que você deve seguir o padrão mostrado no
manipulador e não permitir que qualquer exceção seja gerada fora do contexto do
manipulador assíncrono. Como ele não retorna uma tarefa, não há nenhuma tarefa que
pode relatar o erro entrando no estado de falha. Como o método é assíncrono, ele não
pode simplesmente gerar a exceção. (O método de chamada continuou a execução
porque é async .) O comportamento de runtime real será definido de forma diferente
para ambientes diferentes. Ele pode encerrar o thread ou o processo que possui o
thread ou deixar o processo em um estado indeterminado. Todos esses resultados
potenciais são altamente indesejáveis.

É por isso que você deve encapsular a instrução await para a tarefa assíncrona em seu
próprio bloco de teste. Se isso causar uma tarefa com falha, você pode registrar o erro
em log. Se for um erro do qual não é possível recuperar o aplicativo, você pode sair do
programa rápida e normalmente

Essas são as principais atualizações do padrão de eventos do .NET. Você verá muitos
exemplos das versões anteriores nas bibliotecas com que trabalhar. No entanto, você
também precisa compreender quais são os padrões mais recentes.

O próximo artigo desta série ajuda a distinguir entre o uso de delegates e de events
em seus designs. Eles são conceitos similares e artigo o ajudará a tomar a melhor
decisão para seus programas.

Próximo
Distinção entre Delegados e Eventos
Artigo • 09/05/2023

Anterior

Desenvolvedores que são novos na plataforma .NET Core geralmente têm dificuldades
para decidir entre um design baseado em delegates e um design baseado em events .
A escolha entre delegados ou eventos geralmente é difícil, pois os dois recursos de
idioma são semelhantes. De fato, os eventos são criados usando o suporte de
linguagem para delegados.

Ambos oferecem um cenário de associação tardia: eles permitem cenários em que um


componente se comunica chamando um método conhecido somente em tempo de
execução. Ambas dão suporte a métodos de assinante único e vários assinantes. Você
pode ver esse suporte ser chamado de singlecast e multicast. Ambas dão suporte a uma
sintaxe semelhante para adicionar e remover manipuladores. Por fim, acionar um evento
e chamar um delegado usam exatamente a mesma sintaxe de chamada de método. As
duas até mesmo dão suporte à mesma sintaxe de método Invoke() para uso com o
operador ?. .

Com todas essas semelhanças, é fácil de ter problemas para determinar quando usar
qual.

Ouvir eventos é opcional


O aspecto mais importante para determinar qual recurso da linguagem usar é se é
necessário ou não que haja um assinante anexado. Se o seu código precisar chamar o
código fornecido pelo assinante, você deverá usar um design baseado em delegados
quando precisar implementar o retorno de chamada. Se seu código puder concluir todo
o seu trabalho sem chamar nenhum assinante, você deverá usar um design baseado em
eventos.

Considere os exemplos criados durante esta seção. O código que você criou usando
List.Sort() deve receber uma função de comparador para classificar corretamente os
elementos. Consultas de LINQ devem receber delegados para determinar quais
elementos retornar. Ambos usaram um design criado com delegados.

Considere o evento Progress . Ele relata o progresso de uma tarefa. A tarefa continua
quer haja ouvintes ou não. O FileSearcher é outro exemplo. Ele ainda pesquisaria e
localizaria todos os arquivos que foram buscados, mesmo que não houvesse assinantes
do evento anexados. Controles de UX ainda funcionam corretamente, mesmo quando
não houver nenhum assinante ouvindo os eventos. Ambos usam os designs baseados
em eventos.

Valores retornados exigem delegados


Outra consideração é o protótipo do método que você gostaria de ter para seu método
de delegado. Como você viu, todos os delegados usados para os eventos têm um tipo
retornado nulo. Você também viu que há expressões para criar manipuladores de
eventos que passam informações para as origens dos eventos modificando
propriedades do objeto de argumento de evento. Embora essas expressões funcionem,
elas não são tão naturais quanto retornar um valor de um método.

Observe que essas duas heurísticas geralmente podem estar presentes: se o método de
delegado retornar um valor, provavelmente ele terá impacto sobre o algoritmo de
alguma forma.

Eventos têm invocação privada


Classes diferentes daquela em que um evento está contido só podem adicionar e
remover ouvintes de eventos; só a classe que contém um evento pode invocá-lo.
Eventos normalmente são membros de classe pública. Em comparação, os delegados
geralmente são passados como parâmetros e armazenados como membros de classe
privada, se forem armazenados.

Ouvintes de evento frequentemente têm vida


útil mais longa
O fato de que os ouvintes de evento têm tempos de vida mais longos é uma justificativa
um pouco mais fraca. No entanto, você pode descobrir que designs baseados em
eventos são mais naturais quando a origem do evento for gerar eventos durante um
longo período de tempo. Você pode ver exemplos de design baseado em evento para
controles de UX em muitos sistemas. Quando você assina um evento, a origem do
evento pode gerar eventos durante o tempo de vida do programa. (Você pode cancelar
a assinatura de eventos quando não precisar mais deles.)

Compare isso com vários designs baseados em delegados, em que um delegado é


usado como um argumento para um método e o delegado não é usado depois que o
método é retornado.
Avalie cuidadosamente
As considerações acima não são regras rígidas e óbvias. Em vez disso, elas são diretrizes
que podem ajudá-lo a decidir qual opção é melhor para seu uso específico. Como elas
são semelhantes, você pode até mesmo fazer protótipos das suas e considerar com qual
seria mais natural trabalhar. Ambas lidam bem com cenários de associação tardia. Use a
que comunica melhor o seu design.
Controle de versão em C#
Artigo • 01/10/2024

Neste tutorial, você aprenderá o que significa o controle de versão no .NET. Você
também aprenderá sobre os fatores a serem considerados ao fazer o controle de versão
de sua biblioteca, bem como ao atualizar para uma nova versão de uma biblioteca.

Versão da linguagem
O compilador C# faz parte do SDK do .NET. Por padrão, o compilador escolhe a versão
da linguagem C# que corresponde ao TFM escolhido para seu projeto. Se a versão do
SDK for maior do que a estrutura escolhida, o compilador poderá usar uma versão de
linguagem maior. Você pode alterar o padrão definindo o elemento LangVersion em
seu projeto. Você pode aprender como em nosso artigo sobre opções do compilador.

2 Aviso

Não é recomendável definir o elemento LangVersion como latest . A configuração


latest significa que o compilador instalado usa sua versão mais recente. Isso pode

mudar conforme o computador, tornando as compilações não confiáveis. Além


disso, ele habilita recursos de linguagem que podem exigir recursos de tempo de
execução ou biblioteca não incluídos no SDK atual.

Criando bibliotecas
Como um desenvolvedor que criou a bibliotecas .NET para uso público, provavelmente
você esteve em situações em que precisa distribuir novas atualizações. Como você
realiza esse processo é muito importante, pois você precisa garantir que haja uma
transição suave do código existente para a nova versão da biblioteca. Aqui estão Vários
aspectos a considerar ao criar uma nova versão:

Controle de Versão Semântico


Controle de versão semântico (SemVer, de forma abreviada) é uma convenção de
nomenclatura aplicada a versões de sua biblioteca para indicar eventos com marcos
específicos. Idealmente, as informações de versão que você fornece a sua biblioteca
devem ajudar os desenvolvedores a determinar a compatibilidade com seus projetos
que usam versões mais antigas da mesma biblioteca.
A abordagem mais básica ao SemVer é o formato de 3 componentes
MAJOR.MINOR.PATCH , em que:

MAJOR é incrementado quando você faz alterações em APIs incompatíveis


MINOR é incrementado quando você adiciona funcionalidades de maneira

compatível com versões anteriores


PATCH é incrementado quando você faz correções de bugs compatíveis com

versões anteriores

Também há maneiras de especificar outros cenários, como versões de pré-lançamento,


ao aplicar informações de versão à biblioteca do .NET.

Compatibilidade com versões anteriores


Conforme você lança novas versões de sua biblioteca, a compatibilidade com versões
anteriores provavelmente será uma de suas principais preocupações. Uma nova versão
da biblioteca será compatível com a origem de uma versão anterior se o código que
depende da versão anterior puder, quando recompilado, trabalhar com a nova versão.
Uma nova versão da biblioteca será compatível de forma binária se um aplicativo que
dependia da versão anterior puder, sem recompilação, trabalhar com a nova versão.

Aqui estão algumas coisas a serem consideradas ao tentar manter a compatibilidade


com versões mais antigas de sua biblioteca:

Métodos virtuais: quando você torna um método em virtual não virtual na nova
versão, significa que projetos que substituem esse método precisarão ser
atualizados. Essa é uma alteração muito grande e significativa que é altamente
desaconselhável.
Assinaturas de método: quando atualizar o comportamento de um método exigir
que você altere também sua assinatura, você deve criar uma sobrecarga para que
o código que chamar esse método ainda funcione. Você sempre pode manipular a
assinatura de método antiga para chamar a nova assinatura de método para que a
implementação permaneça consistente.
Atributo obsoleto: você pode usar esse atributo no seu código para especificar
classes ou membros da classe que foram preteridos e provavelmente serão
removidos em versões futuras. Isso garante que os desenvolvedores que utilizam
sua biblioteca estarão melhor preparados para alterações significativas.
Argumentos de método opcionais: quando você tornar argumentos de método
que antes eram opcionais em compulsórios ou alterar seu valor padrão, todo
código que não fornece esses argumentos precisará ser atualizado.
7 Observação

Tornar argumentos compulsórios em opcionais deve ter muito pouco efeito,


especialmente se não alterar o comportamento do método.

Quanto mais fácil for para os usuários atualizarem para a nova versão da sua biblioteca,
mais provável será que eles atualizem o quanto antes.

Arquivo de Configuração do Aplicativo


Como um desenvolvedor de .NET, há uma chance muito grande de você já ter
encontrado o arquivo o app.config na maioria dos tipos de projeto. Esse arquivo de
configuração simples pode fazer muita diferença para melhorar a distribuição de novas
atualizações. Em geral, você deve projetar suas bibliotecas de forma que as informações
que provavelmente serão alteradas regularmente sejam armazenadas no arquivo
app.config . Dessa forma, quando essas informações forem atualizadas, o arquivo de

configuração de versões mais antigas só precisa ser substituído pelo novo, sem a
necessidade de recompilar a biblioteca.

Consumindo bibliotecas
Como um desenvolvedor que consome bibliotecas .NET criadas por outros
desenvolvedores, vocês provavelmente está ciente de que uma nova versão de uma
biblioteca pode não ser totalmente compatível com seu projeto e pode acabar
precisando atualizar seu código para trabalhar com essas alterações.

Para sua sorte, o ecossistema do C# e do .NET tem recursos e técnicas que permitem
facilmente atualizar nosso aplicativo para trabalhar com novas versões das bibliotecas
que podem introduzir alterações interruptivas.

Redirecionamento de associação de assembly


Você pode usar o arquivo app.config para atualizar a versão de uma biblioteca que seu
aplicativo usa. Adicionando o que é chamado de redirecionamento de associação, você
pode usar a nova versão da biblioteca sem precisar recompilar seu aplicativo. O
exemplo a seguir mostra como atualizar o arquivo app.config de seu aplicativo para uso
com a versão de patch 1.0.1 de ReferencedLibrary em vez da versão 1.0.0 com que
ele foi compilado originalmente.

XML
<dependentAssembly>
<assemblyIdentity name="ReferencedLibrary"
publicKeyToken="32ab4ba45e0a69a1" culture="en-us" />
<bindingRedirect oldVersion="1.0.0" newVersion="1.0.1" />
</dependentAssembly>

7 Observação

Essa abordagem só funcionará se a nova versão do ReferencedLibrary for


compatível de forma binária com seu aplicativo. Consulte a seção Compatibilidade
com versões anteriores acima para ver as alterações importantes ao determinar a
compatibilidade.

novo
Você usa o modificador new para ocultar membros herdados de uma classe base. Essa é
uma maneira das classes derivadas responderem a atualizações em classes base.

Veja o exemplo seguinte:

C#

public class BaseClass


{
public void MyMethod()
{
Console.WriteLine("A base method");
}
}

public class DerivedClass : BaseClass


{
public new void MyMethod()
{
Console.WriteLine("A derived method");
}
}

public static void Main()


{
BaseClass b = new BaseClass();
DerivedClass d = new DerivedClass();

b.MyMethod();
d.MyMethod();
}
Saída

Console

A base method
A derived method

No exemplo acima, você pode ver como DerivedClass oculta o método MyMethod
presente em BaseClass . Isso significa que quando uma classe base na nova versão de
uma biblioteca adiciona um membro que já existe em sua classe derivada, você pode
simplesmente usar o modificador new no membro de sua classe derivada para ocultar o
membro da classe base.

Quando nenhum modificador new é especificado, uma classe derivada ocultará por
padrão membros conflitantes em uma classe base e, embora um aviso do compilador
seja gerado, o código ainda será compilado. Isso significa que simplesmente adicionar
novos membros a uma classe existente torna a nova versão da biblioteca compatível
com a origem e de forma binária com o código que depende dela.

override
O modificador override significa que uma implementação derivada estende a
implementação de um membro da classe base, em vez de ocultá-lo. O membro da
classe base precisa ter o modificador virtual aplicado a ele.

C#

public class MyBaseClass


{
public virtual string MethodOne()
{
return "Method One";
}
}

public class MyDerivedClass : MyBaseClass


{
public override string MethodOne()
{
return "Derived Method One";
}
}

public static void Main()


{
MyBaseClass b = new MyBaseClass();
MyDerivedClass d = new MyDerivedClass();
Console.WriteLine("Base Method One: {0}", b.MethodOne());
Console.WriteLine("Derived Method One: {0}", d.MethodOne());
}

Saída

Console

Base Method One: Method One


Derived Method One: Derived Method One

O modificador override é avaliado em tempo de compilação e o compilador gerará um


erro se não encontrar um membro virtual para substituir.

Seu conhecimento sobre as técnicas discutidas e sua compreensão das situações em


que usá-las, farão muita diferença para facilitar a transição entre versões de uma
biblioteca.
Instruções (C#)
Artigo • 11/04/2024

Na seção de Instruções do Guia de C#, é possível encontrar respostas rápidas para


perguntas comuns. Em alguns casos, os artigos podem ser listados em várias seções.
Queremos facilitar que sejam localizados por vários caminhos de pesquisa.

Conceitos gerais de C#
Há dicas e truques que são práticas comuns do desenvolvedor de C#:

Inicializar objetos usando um inicializador de objeto.


Aprenda as diferenças entre passar um struct e uma classe para um método.
Use a sobrecarga de operador.
Implemente e chame um método de extensão personalizado.
Crie um novo método para um tipo enum usando métodos de extensão.

Membros de classe, registro e struct


Crie classes, registros e structs para implementar seu programa. Essas técnicas são
comumente usadas durante a gravação de classes, registros ou structs.

Declare propriedades implementadas automaticamente.


Declare e use propriedades de leitura/gravação.
Defina constantes.
Substitua o método ToString para fornecer saída de cadeia de caracteres.
Defina propriedades abstract.
Use os recursos de documentação para documentar seu código.
Implemente membros de interface explicitamente para manter a interface pública
concisa.
Implemente membros de duas interfaces explicitamente.

Trabalhando com coleções


Esses artigos ajudam você a trabalhar com coleções de dados.

Inicialize um dicionário com um inicializador de coleção.

Trabalhando com cadeias de caracteres


As cadeias de caracteres são o tipo de dados fundamental usado para exibir ou
manipular texto. Esses artigos demonstram práticas comuns com cadeias de caracteres.

Compare cadeias de caracteres.


Modifique o conteúdo da cadeia de caracteres.
Determine se uma cadeia de caracteres representa um número.
Use String.Split para separar as cadeias de caracteres.
Junte várias cadeias de caracteres em uma.
Pesquise texto em uma cadeia de caracteres.

Conversão entre tipos


Talvez seja necessário converter um objeto em um tipo diferente.

Determine se uma cadeia de caracteres representa um número.


Converta entre cadeias de caracteres que representam números hexadecimais e o
número.
Converta uma cadeia de caracteres para um DateTime.
Converta uma matriz de bytes em um interno.
Converta uma cadeia de caracteres em um número.
Use a correspondência de padrões, os operadores as e is para converter para um
tipo diferente com segurança.
Define as conversões de tipo personalizado.
Determine se um tipo é um tipo de valor anulável.
Converta entre tipos de valor anuláveis e não anuláveis.

Comparações de ordem e igualdade


É possível criar tipos que definem suas próprias regras de igualdade ou definem uma
ordem natural entre os objetos desse tipo.

Testar a igualdade com base em referência.


Defina a igualdade com base em valor para um tipo.

Tratamento de exceções
Programas .NET relatam que os métodos não concluíram seu trabalho com sucesso ao
lançar exceções. Nesses artigos, você aprenderá a trabalhar com exceções.

Trate exceções usando try e catch.


Limpe recursos usando as finally cláusulas.
Recupere com base em exceções não CLS (Common Language Specification).

Representantes e eventos
Representantes e eventos fornecem uma capacidade para estratégias que envolve
blocos de código acoplados livremente.

Declare, crie uma instância e use delegados.


Combine delegados multicast.

Os eventos fornecem um mecanismo para publicar ou assinar notificações.

Assine e cancele a assinatura de eventos.


Implemente eventos declarados nas interfaces.
Esteja em conformidade com as diretrizes do .NET quando seu código publicar
eventos.
Gere eventos definidos nas classes de base de classes derivadas.
Implemente acessadores de eventos personalizados.

Práticas do LINQ
O LINQ permite que você grave códigos para consultar qualquer fonte de dados
compatível com o padrão de expressão de consulta do LINQ. Esses artigos o ajudarão a
entender o padrão e trabalhar com diferentes fontes de dados.

Consulte uma coleção.


Use var nas expressões de consulta.
Retorne subconjuntos de propriedades de elementos em uma consulta.
Grave consultas com filtragem complexa.
Classifique os elementos de uma fonte de dados.
Classifique os elementos em múltiplas chaves.
Controle o tipo de uma projeção.
Conte as ocorrências de um valor em uma sequência de origem.
Calcule valores intermediários.
Mescle dados de várias fontes.
Encontre a diferença de conjunto entre duas sequências.
Depure resultados de consultas vazios.
Adicione métodos personalizados a consultas LINQ.

Threads múltiplos e processamento assíncrono


Programas modernos geralmente usam operações assíncronas. Esses artigos o ajudarão
a aprender a usar essas técnicas.

Melhore o desempenho assíncrono usando System.Threading.Tasks.Task.WhenAll.


Faça várias solicitações da Web paralelamente usando async e await.
Use um pool de thread.

Argumentos da linha de comando para o


programa
Geralmente, os programas de C# têm argumentos da linha de comando. Esses artigos o
ensinam a acessar e processar esses argumentos da linha de comando.

Recupere todos os argumentos da linha de comando com for.

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

Fornecer comentários sobre o produto


Como separar cadeias de caracteres
usando String.Split em C#
Artigo • 07/11/2024

O método String.Split cria uma matriz de subcadeias, dividindo a cadeia de caracteres


de entrada com base em um ou mais delimitadores. Esse método geralmente é a
maneira mais fácil de separar uma cadeia de caracteres em limites de palavra. Ele
também é usado para dividir cadeias de caracteres em outros caracteres específicos ou
cadeias de caracteres.

7 Observação

Os exemplos de C# neste artigo são executados no executador de código


embutido Try.NET e no playground. Clique no botão Executar para executar um
exemplo em uma janela interativa. Ao executar o código, é possível modificá-lo e
executar o código modificado clicando em Executar novamente. O código
modificado será executado na janela interativa ou, se a compilação falhar, a janela
interativa exibirá todos as mensagens de erro do compilador C#.

Exemplos de String.Split
O código a seguir divide uma frase comum em uma matriz de cadeias de caracteres
para cada palavra.

C#

string phrase = "The quick brown fox jumps over the lazy dog.";
string[] words = phrase.Split(' ');

foreach (var word in words)


{
System.Console.WriteLine($"<{word}>");
}

Cada instância de um caractere separador produz um valor na matriz retornada. Como


as matrizes em C# são indexadas a zero, cada cadeia de caracteres na matriz é indexada
de 0 até o valor retornado pela propriedade Array.Length menos 1:

C#
string phrase = "The quick brown fox jumps over the lazy dog.";
string[] words = phrase.Split(' ');

for (int i = 0; i < words.Length; i++)


{
System.Console.WriteLine($"Index {i}: <{words[i]}>");
}

Caracteres separadores consecutivos produzem a cadeia de caracteres vazia como um


valor na matriz retornada. Você pode ver como uma cadeia de caracteres vazia é criada
no exemplo a seguir, que usa o caractere de espaço como separador.

C#

string phrase = "The quick brown fox jumps over the lazy dog.";
string[] words = phrase.Split(' ');

foreach (var word in words)


{
System.Console.WriteLine($"<{word}>");
}

Esse comportamento facilita para formatos como arquivos CSV (valores separados por
vírgula) que representam dados de tabela. Vírgulas consecutivas representam uma
coluna em branco.

Você pode passar um parâmetro StringSplitOptions.RemoveEmptyEntries opcional para


excluir as cadeias de caracteres vazias da matriz retornada. Para um processamento mais
complicado da coleção retornada, você pode usar o LINQ para manipular a sequência
de resultado.

O String.Split pode usar vários caracteres separadores. O exemplo a seguir utiliza


espaços, vírgulas, pontos, dois pontos e tabulações como caracteres de separação, que
são passados para Split em uma matriz. O loop, na parte inferior do código, exibe cada
uma das palavras na matriz retornada.

C#

char[] delimiterChars = { ' ', ',', '.', ':', '\t' };

string text = "one\ttwo three:four,five six seven";


System.Console.WriteLine($"Original text: '{text}'");

string[] words = text.Split(delimiterChars);


System.Console.WriteLine($"{words.Length} words in text:");

foreach (var word in words)


{
System.Console.WriteLine($"<{word}>");
}

As instâncias consecutivas de qualquer separador produzem a cadeia de caracteres


vazia na matriz de saída:

C#

char[] delimiterChars = { ' ', ',', '.', ':', '\t' };

string text = "one\ttwo :,five six seven";


System.Console.WriteLine($"Original text: '{text}'");

string[] words = text.Split(delimiterChars);


System.Console.WriteLine($"{words.Length} words in text:");

foreach (var word in words)


{
System.Console.WriteLine($"<{word}>");
}

O String.Split pode receber uma matriz de cadeias de caracteres (sequências de


caracteres que atuam como separadores para analisar a cadeia de caracteres de destino,
em vez de um único caractere).

C#

string[] separatingStrings = { "<<", "..." };

string text = "one<<two......three<four";


System.Console.WriteLine($"Original text: '{text}'");

string[] words = text.Split(separatingStrings,


System.StringSplitOptions.RemoveEmptyEntries);
System.Console.WriteLine($"{words.Length} substrings in text:");

foreach (var word in words)


{
System.Console.WriteLine(word);
}

Usar o GitHub Copilot para dividir uma string


Você pode usar o GitHub Copilot no IDE para gerar código para dividir cadeias de
caracteres usando String.Split em C#.
Se você estiver usando o Visual Studio 2022 versão 17.8 ou posterior, poderá
experimentar o GitHub Copilot controlado por IA no Visual Studio para gerar código
para dividir uma cadeia de caracteres de entrada em subcadeias de caracteres com base
em um ou mais delimitadores. Envie sua pergunta como um prompt na janela de bate-
papo do Copilot, como no exemplo a seguir. Você também pode enviar prompts
usando o bate-papo em linha na própria janela do editor.

7 Observação

O GitHub Copilot é alimentado por IA, portanto é passível de surpresas e erros.


Verifique qualquer código gerado ou sugestões. Para obter mais informações sobre
o uso geral do GitHub Copilot, o impacto do produto, a supervisão humana e a
privacidade, consulte Perguntas frequentes sobre o GitHub Copilot .

O texto a seguir mostra um exemplo de prompt para o Copilot Chat:

Gere código C# para usar Split.String dividir uma cadeia de caracteres em


subcadeias de caracteres. A string de entrada é "Você ganha alguns. Você perde
alguns." Os delimitadores são espaço e ponto. Forneça um exemplo de saída.

Você pode personalizar o prompt para usar cadeias de caracteres e delimitadores de


acordo com seus requisitos.

Você pode usar recursos de bate-papo, como comandos de barra, referências a


arquivos, métodos ou classes e threads, para definir a intenção e obter melhores
respostas com o contexto com escopo. Para um arquivo existente que está aberto no
IDE, você pode solicitar o GitHub Copilot usando o chat embutido com /generate code
to split string1 in #Filename by delimiter spaces o .

A saída a seguir mostra um exemplo de resposta do Copilot Chat:

Saída

Here is an example of how you can use the Split method in C# to split the
string "You win some. You lose some." using space and period as delimiters:

using System;

class Program
{
static void Main()
{
string input = "You win some. You lose some.";
char[] delimiters = new char[] { ' ', '.' };
string[] substrings = input.Split(delimiters,
StringSplitOptions.RemoveEmptyEntries);

foreach (string substring in substrings)


{
Console.WriteLine(substring);
}
}
}

**Example Output**
You
win
some
You
lose
some

Quando o Copilot retorna um bloco de código, a resposta inclui opções para copiar o
código, inserir o código em um novo arquivo ou visualizar a saída do código.

7 Observação

Seus resultados podem ser diferentes do que é mostrado nas respostas de


exemplo. Os modelos de IA não são determinísticos, ou seja, eles podem retornar
respostas diferentes quando fazem a mesma pergunta. Isso pode ser devido a
aprendizado e adaptação adicionais ao longo do tempo, variação de idioma,
mudanças no contexto, como seu histórico de bate-papo e muito mais.

Para saber mais, veja:

Central de Confiabilidade do GitHub Copilot


GitHub Copilot no Visual Studio
GitHub Copilot no VS Code

Confira também
Extrair elementos de uma cadeia de caracteres
Cadeias de caracteres
Expressões regulares do .NET
Como concatenar várias cadeias de
caracteres (Guia de C#)
Artigo • 21/03/2024

Concatenação é o processo de acrescentar uma cadeia de caracteres ao final de outra


cadeia de caracteres. Você concatena cadeias de caracteres usando o operador + . Para
literais de cadeia de caracteres e constantes de cadeia de caracteres, a concatenação
ocorre em tempo de compilação; não ocorre nenhuma concatenação de tempo de
execução. Para variáveis de cadeia de caracteres, a concatenação ocorre somente em
tempo de execução.

7 Observação

Os exemplos de C# neste artigo são executados no executador de código


embutido Try.NET e no playground. Clique no botão Executar para executar um
exemplo em uma janela interativa. Ao executar o código, é possível modificá-lo e
executar o código modificado clicando em Executar novamente. O código
modificado será executado na janela interativa ou, se a compilação falhar, a janela
interativa exibirá todos as mensagens de erro do compilador C#.

Literais de cadeia de caracteres


O exemplo a seguir divide um literal de cadeia de caracteres longo em cadeias de
caracteres menores para melhorar a legibilidade no código-fonte. O código concatena
as cadeias de caracteres menores para criar o literal de cadeia de caracteres longa. As
partes são concatenadas em uma única cadeia de caracteres em tempo de compilação.
Não há custos de desempenho em tempo de execução, independentemente da
quantidade de cadeias de caracteres envolvidas.

C#

// Concatenation of literals is performed at compile time, not run time.


string text = "Historically, the world of data and the world of objects " +
"have not been well integrated. Programmers work in C# or Visual Basic " +
"and also in SQL or XQuery. On the one side are concepts such as classes, "
+
"objects, fields, inheritance, and .NET Framework APIs. On the other side "
+
"are tables, columns, rows, nodes, and separate languages for dealing with "
+
"them. Data types often require translation between the two worlds; there
are " +
"different standard functions. Because the object world has no notion of
query, a " +
"query can only be represented as a string without compile-time type
checking or " +
"IntelliSense support in the IDE. Transferring data from SQL tables or XML
trees to " +
"objects in memory is often tedious and error-prone.";

System.Console.WriteLine(text);

Operadores + e +=
Para concatenar variáveis de cadeia de caracteres, você pode usar os operadores + ou
+= , a interpolação de cadeia de caracteres ou os métodos String.Format, String.Concat,

String.Join ou StringBuilder.Append. O operador + é fácil de usar e torna o código


intuitivo. Mesmo ao usar vários operadores + em uma instrução, o conteúdo da cadeia
de caracteres será copiado apenas uma vez. O código a seguir mostra dois exemplos de
como usar os operadores + e += para concatenar cadeias de caracteres:

C#

string userName = "<Type your name here>";


string dateString = DateTime.Today.ToShortDateString();

// Use the + and += operators for one-time concatenations.


string str = "Hello " + userName + ". Today is " + dateString + ".";
System.Console.WriteLine(str);

str += " How are you today?";


System.Console.WriteLine(str);

Interpolação de cadeia de caracteres


Em algumas expressões, é mais fácil concatenar cadeias de caracteres usando a
interpolação de cadeia de caracteres, conforme mostra o seguinte código:

C#

string userName = "<Type your name here>";


string date = DateTime.Today.ToShortDateString();

// Use string interpolation to concatenate strings.


string str = $"Hello {userName}. Today is {date}.";
System.Console.WriteLine(str);
str = $"{str} How are you today?";
System.Console.WriteLine(str);

7 Observação

Em operações de concatenação de cadeia de caracteres, o compilador C# trata


uma cadeia de caracteres nula da mesma maneira que uma cadeia de caracteres
vazia.

A partir do C# 10, você pode usar a interpolação de cadeia de caracteres para inicializar
uma cadeia de caracteres constante quando todas as expressões usadas para espaços
reservados também são cadeias de caracteres constantes.

String.Format
Outro método para concatenar cadeias de caracteres é o String.Format. Esse método
funciona bem quando você está criando uma cadeia de caracteres com base em um
pequeno número de cadeias de caracteres de componente.

StringBuilder
Em outros casos, você pode combinar cadeias de caracteres em um loop em que você
não sabe quantas cadeias de caracteres de origem você está combinando, sendo que o
número real de cadeias de caracteres de origem pode ser grande. A classe StringBuilder
foi projetada para esses cenários. O código a seguir usa o método Append da classe
StringBuilder para concatenar cadeias de caracteres.

C#

// Use StringBuilder for concatenation in tight loops.


var sb = new System.Text.StringBuilder();
for (int i = 0; i < 20; i++)
{
sb.AppendLine(i.ToString());
}
System.Console.WriteLine(sb.ToString());

Você pode ler mais sobre os motivos para escolher a concatenação de cadeia de
caracteres ou a classe StringBuilder.

String.Concat ou String.Join
Outra opção para unir cadeias de caracteres de uma coleção é usar o método
String.Concat. Use método String.Join se um delimitador deve separar cadeias de
caracteres de origem. O código a seguir combina uma matriz de palavras usando os
dois métodos:

C#

string[] words = { "The", "quick", "brown", "fox", "jumps", "over", "the",


"lazy", "dog." };

var unreadablePhrase = string.Concat(words);


System.Console.WriteLine(unreadablePhrase);

var readablePhrase = string.Join(" ", words);


System.Console.WriteLine(readablePhrase);

LINQ e Enumerable.Aggregate
Por fim, você pode usar LINQ e o método Enumerable.Aggregate para unir cadeias de
caracteres de uma coleção. Esse método combina as cadeias de caracteres de origem
usando uma expressão lambda. A expressão lambda faz o trabalho de adicionar cada
cadeia de caracteres ao acúmulo existente. O exemplo a seguir combina uma matriz de
palavras, adicionando um espaço entre cada palavra na matriz:

C#

string[] words = { "The", "quick", "brown", "fox", "jumps", "over", "the",


"lazy", "dog." };

var phrase = words.Aggregate((partialPhrase, word) =>$"{partialPhrase}


{word}");
System.Console.WriteLine(phrase);

Essa opção pode causar mais alocações do que outros métodos para concatenar
coleções, pois cria uma cadeia de caracteres intermediária para cada iteração. Se a
otimização do desempenho for crítica, considere a classe StringBuilder ou o
String.Concat ou o método String.Join para concatenar uma coleção, em vez de
Enumerable.Aggregate .

Confira também
String
StringBuilder
Cadeias de caracteres

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
Como pesquisar em cadeias de
caracteres
Artigo • 10/05/2023

Você pode usar duas estratégias principais para pesquisar texto em cadeias de
caracteres. Os métodos da classe String pesquisam por um texto específico. Expressões
regulares pesquisam por padrões no texto.

7 Observação

Os exemplos de C# neste artigo são executados no executador de código


embutido Try.NET e no playground. Clique no botão Executar para executar um
exemplo em uma janela interativa. Ao executar o código, é possível modificá-lo e
executar o código modificado clicando em Executar novamente. O código
modificado será executado na janela interativa ou, se a compilação falhar, a janela
interativa exibirá todos as mensagens de erro do compilador C#.

O tipo string, que é um alias para a classe System.String, fornece uma série de métodos
úteis para pesquisar o conteúdo de uma cadeia de caracteres. Entre elas estão Contains,
StartsWith, EndsWith, IndexOf e LastIndexOf. A classe
System.Text.RegularExpressions.Regex fornece um vocabulário avançado para pesquisar
por padrões de texto. Neste artigo, você aprenderá essas técnicas e como escolher o
melhor método para suas necessidades.

Uma cadeia de caracteres contém texto?


Os métodos String.Contains, String.StartsWith e String.EndsWith pesquisam uma cadeia
de caracteres em busca de um texto específico. O seguinte exemplo mostra cada um
desses métodos e uma variação que usa uma pesquisa que não diferencia maiúsculas
de minúsculas:

C#

string factMessage = "Extension methods have all the capabilities of regular


static methods.";

// Write the string and include the quotation marks.


Console.WriteLine($"\"{factMessage}\"");

// Simple comparisons are always case sensitive!


bool containsSearchResult = factMessage.Contains("extension");
Console.WriteLine($"Contains \"extension\"? {containsSearchResult}");

// For user input and strings that will be displayed to the end user,
// use the StringComparison parameter on methods that have it to specify how
to match strings.
bool ignoreCaseSearchResult = factMessage.StartsWith("extension",
System.StringComparison.CurrentCultureIgnoreCase);
Console.WriteLine($"Starts with \"extension\"? {ignoreCaseSearchResult}
(ignoring case)");

bool endsWithSearchResult = factMessage.EndsWith(".",


System.StringComparison.CurrentCultureIgnoreCase);
Console.WriteLine($"Ends with '.'? {endsWithSearchResult}");

O exemplo anterior demonstra um ponto importante para usar esses métodos. As


pesquisas de texto diferenciam maiúsculas e minúsculas por padrão. Use o valor de
enumeração StringComparison.CurrentCultureIgnoreCase para especificar uma pesquisa
que não diferencia maiúsculas de minúsculas.

Em que local de uma cadeia de caracteres o


texto procurado ocorre?
Os métodos IndexOf e LastIndexOf também pesquisam texto em cadeias de caracteres.
Esses métodos retornam o local do texto que está sendo procurado. Se o texto não for
encontrado, elas retornarão -1 . O exemplo a seguir mostra uma pesquisa para a
primeira e a última ocorrência da palavra "métodos" e exibe o texto entre elas.

C#

string factMessage = "Extension methods have all the capabilities of regular


static methods.";

// Write the string and include the quotation marks.


Console.WriteLine($"\"{factMessage}\"");

// This search returns the substring between two strings, so


// the first index is moved to the character just after the first string.
int first = factMessage.IndexOf("methods") + "methods".Length;
int last = factMessage.LastIndexOf("methods");
string str2 = factMessage.Substring(first, last - first);
Console.WriteLine($"Substring between \"methods\" and \"methods\":
'{str2}'");

Localizar texto específico usando expressões


regulares
A classe System.Text.RegularExpressions.Regex pode ser usada para pesquisar cadeias
de caracteres. Essas pesquisas podem variar em complexidade, de padrões de texto
simples até os complicados.

O exemplo de código a seguir procura a palavra "the" ou "their" em uma oração, sem
diferenciar maiúsculas e minúsculas. O método estático Regex.IsMatch realiza a
pesquisa. Você fornece a ele a cadeia de caracteres a pesquisar e um padrão de
pesquisa. Nesse caso, um terceiro argumento especifica que a pesquisa não diferencia
maiúsculas de minúsculas. Para obter mais informações, consulte
System.Text.RegularExpressions.RegexOptions.

O padrão de pesquisa descreve o texto pelo qual procurar. A tabela a seguir descreve
cada elemento desse padrão de pesquisa. (A tabela abaixo usa a única \ , que deve ser
escapada como \\ em uma cadeia de caracteres C#).

Padrão Significado

the corresponder ao texto "the"

(eir)? corresponder a 0 ou 1 ocorrência de "eir"

\s corresponder a um caractere de espaço em branco

C#

string[] sentences =
{
"Put the water over there.",
"They're quite thirsty.",
"Their water bottles broke."
};

string sPattern = "the(ir)?\\s";

foreach (string s in sentences)


{
Console.Write($"{s,24}");

if (System.Text.RegularExpressions.Regex.IsMatch(s, sPattern,
System.Text.RegularExpressions.RegexOptions.IgnoreCase))
{
Console.WriteLine($" (match for '{sPattern}' found)");
}
else
{
Console.WriteLine();
}
}
 Dica

Os métodos string são geralmente melhores opções quando você está


procurando por uma cadeia de caracteres exata. Expressões regulares são melhores
quando você está procurando por algum padrão em uma cadeia de caracteres de
origem.

Uma cadeia de caracteres segue um padrão?


O código a seguir usa expressões regulares para validar o formato de cada cadeia de
caracteres em uma matriz. A validação requer que cada cadeia de caracteres tenha a
forma de um número de telefone no qual os três grupos de dígitos são separados por
traços, os dois primeiros grupos contêm três dígitos e o terceiro grupo contém quatro
dígitos. O padrão de pesquisa usa a expressão regular ^\\d{3}-\\d{3}-\\d{4}$ . Para
obter mais informações, consulte Linguagem de expressões regulares – referência
rápida.

Padrão Significado

^ corresponde ao início da cadeia de caracteres

\d{3} corresponde a exatamente 3 caracteres de dígitos

- corresponde ao caractere '-'

\d{4} corresponde a exatamente 4 caracteres de dígitos

$ corresponde ao final da cadeia de caracteres

C#

string[] numbers =
{
"123-555-0190",
"444-234-22450",
"690-555-0178",
"146-893-232",
"146-555-0122",
"4007-555-0111",
"407-555-0111",
"407-2-5555",
"407-555-8974",
"407-2ab-5555",
"690-555-8148",
"146-893-232-"
};
string sPattern = "^\\d{3}-\\d{3}-\\d{4}$";

foreach (string s in numbers)


{
Console.Write($"{s,14}");

if (System.Text.RegularExpressions.Regex.IsMatch(s, sPattern))
{
Console.WriteLine(" - valid");
}
else
{
Console.WriteLine(" - invalid");
}
}

Este padrão de pesquisa único corresponde a várias cadeias de caracteres válidas.


Expressões regulares são melhores para pesquisar por ou validar mediante um padrão,
em vez de uma única cadeia de caracteres de texto.

Confira também
Guia de programação em C#
Cadeias de caracteres
LINQ e cadeias de caracteres
System.Text.RegularExpressions.Regex
Expressões regulares do .NET
Linguagem de expressão regular – referência rápida
Práticas recomendadas para o uso de cadeias de caracteres no .NET
Como modificar o conteúdo de uma
cadeia de caracteres em C#
Artigo • 09/05/2023

Este artigo demonstra várias técnicas para produzir um string modificando um string
existente. Todas as técnicas demonstradas retornam o resultado das modificações como
um novo objeto string . Para demonstrar que as cadeias de caracteres originais e
modificadas são instâncias distintas, os exemplos armazenam o resultado em uma nova
variável. Você pode examinar o original string e o string novo e modificado ao
executar cada exemplo.

7 Observação

Os exemplos de C# neste artigo são executados no executador de código


embutido Try.NET e no playground. Clique no botão Executar para executar um
exemplo em uma janela interativa. Ao executar o código, é possível modificá-lo e
executar o código modificado clicando em Executar novamente. O código
modificado será executado na janela interativa ou, se a compilação falhar, a janela
interativa exibirá todos as mensagens de erro do compilador C#.

Há várias técnicas demonstradas neste artigo. Você pode substituir o texto existente.
Você pode procurar padrões e substituir o texto correspondente com outro texto. Você
pode tratar uma cadeia de caracteres como uma sequência de caracteres. Você também
pode usar métodos de conveniência que removem o espaço em branco. Escolha as
técnicas que mais se aproximam do seu cenário.

Substituir texto
O código a seguir cria uma nova cadeia de caracteres, substituindo o texto existente por
um substituto.

C#

string source = "The mountains are behind the clouds today.";

// Replace one substring with another with String.Replace.


// Only exact matches are supported.
var replacement = source.Replace("mountains", "peaks");
Console.WriteLine($"The source string is <{source}>");
Console.WriteLine($"The updated string is <{replacement}>");
O código anterior demonstra essa propriedade immutable de cadeias de caracteres.
Você pode ver no exemplo anterior que a cadeia de caracteres original, source , não é
modificada. O método String.Replace cria uma nova string contendo as modificações.

O método Replace pode substituir cadeias de caracteres ou caracteres únicos. Em


ambos os casos, todas as ocorrências do texto pesquisado são substituídas. O exemplo
a seguir substitui todos os caracteres ' ' com '_':

C#

string source = "The mountains are behind the clouds today.";

// Replace all occurrences of one char with another.


var replacement = source.Replace(' ', '_');
Console.WriteLine(source);
Console.WriteLine(replacement);

A cadeia de caracteres de origem não é alterada e uma nova cadeia de caracteres é


retornada com a substituição.

Cortar espaço em branco


Você pode usar os métodos String.Trim, String.TrimStart e String.TrimEnd para remover
espaços em branco à esquerda ou à direita. O código a seguir mostra um exemplo de
cada um desses casos. A cadeia de caracteres de origem não é alterada; esses métodos
retornam uma nova cadeia de caracteres com o conteúdo modificado.

C#

// Remove trailing and leading white space.


string source = " I'm wider than I need to be. ";
// Store the results in a new string variable.
var trimmedResult = source.Trim();
var trimLeading = source.TrimStart();
var trimTrailing = source.TrimEnd();
Console.WriteLine($"<{source}>");
Console.WriteLine($"<{trimmedResult}>");
Console.WriteLine($"<{trimLeading}>");
Console.WriteLine($"<{trimTrailing}>");

Remover texto
Você pode remover texto de uma cadeia de caracteres usando o método String.Remove.
Esse método remove um número de caracteres começando em um índice específico. O
exemplo a seguir mostra como usar String.IndexOf seguido por Remove para remover
texto de uma cadeia de caracteres:

C#

string source = "Many mountains are behind many clouds today.";


// Remove a substring from the middle of the string.
string toRemove = "many ";
string result = string.Empty;
int i = source.IndexOf(toRemove);
if (i >= 0)
{
result= source.Remove(i, toRemove.Length);
}
Console.WriteLine(source);
Console.WriteLine(result);

Substituir padrões correspondentes


Você pode usar expressões regulares para substituir padrões correspondentes de texto
com um novo texto, possivelmente definido por um padrão. O exemplo a seguir usa a
classe System.Text.RegularExpressions.Regex para localizar um padrão em uma cadeia
de caracteres de origem e substituí-lo pela capitalização correta. O método
Regex.Replace(String, String, MatchEvaluator, RegexOptions) usa uma função que
fornece a lógica de substituição como um de seus argumentos. Neste exemplo, essa
função LocalReplaceMatchCase é uma função local declarada dentro do método de
exemplo. LocalReplaceMatchCase usa a classe System.Text.StringBuilder para criar a
cadeia de caracteres de substituição com a capitalização correta.

Expressões regulares são mais úteis para localizar e substituir texto que segue um
padrão, em vez de texto conhecido. Saiba mais em Como pesquisar cadeias de
caracteres. O padrão de pesquisa "the\s" procura a palavra "the" seguida por um
caractere de espaço em branco. Essa parte do padrão garante que isso não corresponda
a "there" na cadeia de caracteres de origem. Para obter mais informações sobre
elementos de linguagem de expressão regular, consulte Linguagem de expressão
regular – referência rápida.

C#

string source = "The mountains are still there behind the clouds today.";

// Use Regex.Replace for more flexibility.


// Replace "the" or "The" with "many" or "Many".
// using System.Text.RegularExpressions
string replaceWith = "many ";
source = System.Text.RegularExpressions.Regex.Replace(source, "the\\s",
LocalReplaceMatchCase,
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
Console.WriteLine(source);

string LocalReplaceMatchCase(System.Text.RegularExpressions.Match
matchExpression)
{
// Test whether the match is capitalized
if (Char.IsUpper(matchExpression.Value[0]))
{
// Capitalize the replacement string
System.Text.StringBuilder replacementBuilder = new
System.Text.StringBuilder(replaceWith);
replacementBuilder[0] = Char.ToUpper(replacementBuilder[0]);
return replacementBuilder.ToString();
}
else
{
return replaceWith;
}
}

O método StringBuilder.ToString retorna uma cadeia de caracteres imutável com o


conteúdo no objeto StringBuilder.

Modificar caracteres individuais


Você pode produzir uma matriz de caracteres de uma cadeia de caracteres, modificar o
conteúdo da matriz e, em seguida, criar uma nova cadeia de caracteres com base no
conteúdo modificado da matriz.

O exemplo a seguir mostra como substituir um conjunto de caracteres em uma cadeia


de caracteres. Primeiro, ele usa o método String.ToCharArray() para criar uma matriz de
caracteres. Ele usa o método IndexOf para encontrar o índice inicial da palavra "fox". Os
próximos três caracteres são substituídos por uma palavra diferente. Por fim, uma nova
cadeia de caracteres é construída com a matriz de caracteres atualizada.

C#

string phrase = "The quick brown fox jumps over the fence";
Console.WriteLine(phrase);

char[] phraseAsChars = phrase.ToCharArray();


int animalIndex = phrase.IndexOf("fox");
if (animalIndex != -1)
{
phraseAsChars[animalIndex++] = 'c';
phraseAsChars[animalIndex++] = 'a';
phraseAsChars[animalIndex] = 't';
}

string updatedPhrase = new string(phraseAsChars);


Console.WriteLine(updatedPhrase);

Compilar programaticamente o conteúdo de


cadeia de caracteres
Como as cadeias de caracteres são imutáveis, todos os exemplos anteriores criam
cadeias de caracteres temporárias ou matrizes de caracteres. Em cenários de alto
desempenho, pode ser desejável evitar essas alocações de heap. O .NET Core fornece
um método String.Create que permite preencher programaticamente o conteúdo de
caracteres de uma cadeia de caracteres por meio de um retorno de chamada, evitando
as alocações de cadeias de caracteres temporárias intermediárias.

C#

// constructing a string from a char array, prefix it with some additional


characters
char[] chars = { 'a', 'b', 'c', 'd', '\0' };
int length = chars.Length + 2;
string result = string.Create(length, chars, (Span<char> strContent, char[]
charArray) =>
{
strContent[0] = '0';
strContent[1] = '1';
for (int i = 0; i < charArray.Length; i++)
{
strContent[i + 2] = charArray[i];
}
});

Console.WriteLine(result);

Você pode modificar uma cadeia de caracteres em um bloco fixo com código não
seguro, mas é altamente desaconselhável modificar o conteúdo da cadeia de caracteres
depois que uma cadeia de caracteres é criada. Fazer isso pode quebrar as coisas de
maneiras imprevisíveis. Por exemplo, se alguém internalizar uma cadeia de caracteres
com o mesmo conteúdo que o seu, ele receberá a sua cópia e não esperará que você
esteja modificando sua cadeia de caracteres.

Confira também
Expressões regulares do .NET
Linguagem de expressão regular – referência rápida
Como comparar cadeias de caracteres
no C#
Artigo • 20/03/2024

Você compara cadeias de caracteres para responder a uma das duas perguntas: "Essas
duas cadeias de caracteres são iguais?" ou "Em que ordem essas cadeias de caracteres
devem ser colocadas ao classificá-las?"

Essas duas perguntas são complicadas devido a fatores que afetam as comparações de
cadeia de caracteres:

Você pode escolher uma comparação ordinal ou linguística.


Você pode escolher se o uso de maiúsculas faz diferença.
Você pode escolher comparações específicas de cultura.
As comparações linguísticas dependem da plataforma e da cultura.

Os campos de enumeração System.StringComparison representam estas opções:

CurrentCulture: Compare strings usando regras de classificação sensíveis à cultura


e a cultura atual.
CurrentCultureIgnoreCase: Compare strings usando regras de classificação
sensíveis à cultura, a cultura atual e ignorando o caso das strings que estão sendo
comparadas.
InvariantCulture: Compare strings usando regras de classificação sensíveis à
cultura e a cultura invariável.
InvariantCultureIgnoreCase: Compare strings usando regras de classificação
sensíveis à cultura, a cultura invariável e ignorando o caso das strings que estão
sendo comparadas.
Ordinal: Compare strings usando regras de classificação ordinais (binárias).
OrdinalIgnoreCase: Compare strings usando regras de classificação ordinais
(binárias) e ignorando o caso das strings que estão sendo comparadas.

7 Observação

Os exemplos de C# neste artigo são executados no executador de código


embutido Try.NET e no playground. Clique no botão Executar para executar um
exemplo em uma janela interativa. Ao executar o código, é possível modificá-lo e
executar o código modificado clicando em Executar novamente. O código
modificado será executado na janela interativa ou, se a compilação falhar, a janela
interativa exibirá todos as mensagens de erro do compilador C#.
Ao comparar cadeias de caracteres, você pode definir uma ordem entre elas. As
comparações são usadas para classificar uma sequência de cadeias de caracteres. Uma
vez que a sequência esteja em uma ordem conhecida, fica mais fácil pesquisar, tanto
por software quanto por humanos. Outras comparações podem verificar se as strings
são iguais. Essas verificações de semelhança são semelhantes à igualdade, mas algumas
diferenças, como diferenças de maiúsculas e minúsculas, podem ser ignoradas.

Comparações ordinárias padrão


Por padrão, as operações mais comuns:

String.Equals
String.Equality e String.Inequality, ou seja, operadores de igualdade == e !=,
respectivamente, executam uma comparação ordinal que diferencia maiúsculas de
minúsculas. String.Equals tem uma sobrecarga onde um argumento
StringComparison pode ser fornecido para alterar suas regras de classificação. O
exemplo a seguir demonstra que:

C#

string root = @"C:\users";


string root2 = @"C:\Users";

bool result = root.Equals(root2);


Console.WriteLine($"Ordinal comparison: <{root}> and <{root2}> are {(result
? "equal." : "not equal.")}");

result = root.Equals(root2, StringComparison.Ordinal);


Console.WriteLine($"Ordinal comparison: <{root}> and <{root2}> are {(result
? "equal." : "not equal.")}");

Console.WriteLine($"Using == says that <{root}> and <{root2}> are {(root ==


root2 ? "equal" : "not equal")}");

A comparação ordinal padrão não leva em conta regras linguísticas ao comparar cadeias
de caracteres. Ela compara o valor binário de cada objeto Char em duas cadeias de
caracteres. Como resultado, a comparação ordinal padrão também diferencia
maiúsculas de minúsculas.

Observe que o teste de igualdade com String.Equals e os operadores == e != difere da


comparação de cadeias de caracteres usando os métodos String.CompareTo e
Compare(String, String). Todos eles executam uma comparação que diferencia
maiúsculas de minúsculas. No entanto, enquanto os testes de igualdade executam uma
comparação ordinal, os métodos CompareTo e Compare executam uma comparação
linguística com reconhecimento de cultura usando a cultura atual. Deixe clara a intenção
do seu código chamando uma sobrecarga que especifica explicitamente o tipo de
comparação a ser executada.

Comparações ordinais que não diferenciam


maiúsculas de minúsculas
O método String.Equals(String, StringComparison) permite que você especifique um
valor StringComparison de StringComparison.OrdinalIgnoreCase para especificar uma
comparação que não diferencia maiúsculas de minúsculas. Há também um método
estático String.Compare(String, String, StringComparison) que executa uma comparação
ordinal que não diferencia maiúsculas de minúsculas se você especificar um valor
StringComparison.OrdinalIgnoreCase para o argumento StringComparison. Essas
comparações são mostradas no código a seguir:

C#

string root = @"C:\users";


string root2 = @"C:\Users";

bool result = root.Equals(root2, StringComparison.OrdinalIgnoreCase);


bool areEqual = String.Equals(root, root2,
StringComparison.OrdinalIgnoreCase);
int comparison = String.Compare(root, root2, comparisonType:
StringComparison.OrdinalIgnoreCase);

Console.WriteLine($"Ordinal ignore case: <{root}> and <{root2}> are {(result


? "equal." : "not equal.")}");
Console.WriteLine($"Ordinal static ignore case: <{root}> and <{root2}> are
{(areEqual ? "equal." : "not equal.")}");
if (comparison < 0)
Console.WriteLine($"<{root}> is less than <{root2}>");
else if (comparison > 0)
Console.WriteLine($"<{root}> is greater than <{root2}>");
else
Console.WriteLine($"<{root}> and <{root2}> are equivalent in order");

Esses métodos usam as convenções de maiúsculas e minúsculas da cultura invariável ao


realizar uma comparação ordinal sem distinção entre maiúsculas e minúsculas.

Comparações linguísticas
Muitos métodos de comparação de cadeias de caracteres (como String.StartsWith) usam
regras linguísticas para a cultura atual por padrão para ordenar suas entradas. Essa
comparação linguística às vezes é chamada de "ordem de classificação de palavras."
Quando você executa uma comparação linguística, alguns caracteres Unicode não
alfanuméricos podem ter pesos especiais atribuídos. Por exemplo, o hífen "-" pode ter
um peso pequeno atribuído a ele para que "co-op" e "coop" apareçam um ao lado do
outro na ordem de classificação. Alguns caracteres de controle não imprimíveis podem
ser ignorados. Além disso, alguns caracteres Unicode podem ser equivalentes a uma
sequência de instâncias Char. O exemplo a seguir usa a frase "Eles dançam na rua" em
alemão com os "ss" (U+0073 U+0073) em uma cadeia de caracteres e 'ß' (U+00DF) em
outra. Linguisticamente (no Windows), "ss" é igual ao caractere 'ß' Esszet alemão nas
culturas "en-US" e "de-DE".

C#

string first = "Sie tanzen auf der Straße.";


string second = "Sie tanzen auf der Strasse.";

Console.WriteLine($"First sentence is <{first}>");


Console.WriteLine($"Second sentence is <{second}>");

bool equal = String.Equals(first, second,


StringComparison.InvariantCulture);
Console.WriteLine($"The two strings {(equal == true ? "are" : "are not")}
equal.");
showComparison(first, second);

string word = "coop";


string words = "co-op";
string other = "cop";

showComparison(word, words);
showComparison(word, other);
showComparison(words, other);
void showComparison(string one, string two)
{
int compareLinguistic = String.Compare(one, two,
StringComparison.InvariantCulture);
int compareOrdinal = String.Compare(one, two, StringComparison.Ordinal);
if (compareLinguistic < 0)
Console.WriteLine($"<{one}> is less than <{two}> using invariant
culture");
else if (compareLinguistic > 0)
Console.WriteLine($"<{one}> is greater than <{two}> using invariant
culture");
else
Console.WriteLine($"<{one}> and <{two}> are equivalent in order
using invariant culture");
if (compareOrdinal < 0)
Console.WriteLine($"<{one}> is less than <{two}> using ordinal
comparison");
else if (compareOrdinal > 0)
Console.WriteLine($"<{one}> is greater than <{two}> using ordinal
comparison");
else
Console.WriteLine($"<{one}> and <{two}> are equivalent in order
using ordinal comparison");
}

No Windows, antes do .NET 5, a ordem de classificação de "cop", "coop" e "co-op"


muda quando você muda de uma comparação linguística para uma comparação
ordinal. As duas frases em alemão também são comparadas de forma diferente ao usar
os tipos diferentes de comparação. Antes do .NET 5, as APIs de globalização do .NET
usavam bibliotecas National Language Support (NLS). No .NET 5 e versões posteriores,
as APIs de globalização do .NET usam bibliotecas ICU (Componentes Internacionais para
Unicode) , que unificam o comportamento de globalização da NET em todos os
sistemas operacionais com suporte.

Comparações usando culturas específicas


O exemplo a seguir armazena objetos CultureInfo para as culturas en-US e de-DE. As
comparações são feitas usando um objeto CultureInfo para garantir uma comparação
específica da cultura. A cultura usada afeta as comparações linguísticas. O exemplo a
seguir mostra os resultados da comparação das duas frases em alemão usando a cultura
"en-US" e a cultura "de-DE":

C#

string first = "Sie tanzen auf der Straße.";


string second = "Sie tanzen auf der Strasse.";

Console.WriteLine($"First sentence is <{first}>");


Console.WriteLine($"Second sentence is <{second}>");

var en = new System.Globalization.CultureInfo("en-US");

// For culture-sensitive comparisons, use the String.Compare


// overload that takes a StringComparison value.
int i = String.Compare(first, second, en,
System.Globalization.CompareOptions.None);
Console.WriteLine($"Comparing in {en.Name} returns {i}.");

var de = new System.Globalization.CultureInfo("de-DE");


i = String.Compare(first, second, de,
System.Globalization.CompareOptions.None);
Console.WriteLine($"Comparing in {de.Name} returns {i}.");

bool b = String.Equals(first, second, StringComparison.CurrentCulture);


Console.WriteLine($"The two strings {(b ? "are" : "are not")} equal.");

string word = "coop";


string words = "co-op";
string other = "cop";

showComparison(word, words, en);


showComparison(word, other, en);
showComparison(words, other, en);
void showComparison(string one, string two, System.Globalization.CultureInfo
culture)
{
int compareLinguistic = String.Compare(one, two, en,
System.Globalization.CompareOptions.None);
int compareOrdinal = String.Compare(one, two, StringComparison.Ordinal);
if (compareLinguistic < 0)
Console.WriteLine($"<{one}> is less than <{two}> using en-US
culture");
else if (compareLinguistic > 0)
Console.WriteLine($"<{one}> is greater than <{two}> using en-US
culture");
else
Console.WriteLine($"<{one}> and <{two}> are equivalent in order
using en-US culture");
if (compareOrdinal < 0)
Console.WriteLine($"<{one}> is less than <{two}> using ordinal
comparison");
else if (compareOrdinal > 0)
Console.WriteLine($"<{one}> is greater than <{two}> using ordinal
comparison");
else
Console.WriteLine($"<{one}> and <{two}> are equivalent in order
using ordinal comparison");
}

As comparações que diferenciam cultura normalmente são usadas para comparar e


classificar cadeias de caracteres inseridas por usuários com outras cadeias de caracteres
inseridas por usuários. Os caracteres e as convenções de classificação dessas cadeias de
caracteres podem variar de acordo com a localidade do computador do usuário. Até
mesmo cadeias de caracteres que contêm caracteres idênticos podem ser classificadas
de formas diferentes dependendo da cultura do thread atual.

Classificação linguística e cadeias de caracteres


de pesquisa em matrizes
Os exemplos a seguir mostram como classificar e pesquisar cadeias de caracteres em
uma matriz usando uma comparação linguística que depende da cultura atual. Use os
métodos Array estáticos que aceitam um parâmetro System.StringComparer.

O exemplo a seguir mostra como classificar um array de strings usando a cultura atual:
C#

string[] lines = new string[]


{
@"c:\public\textfile.txt",
@"c:\public\textFile.TXT",
@"c:\public\Text.txt",
@"c:\public\testfile2.txt"
};

Console.WriteLine("Non-sorted order:");
foreach (string s in lines)
{
Console.WriteLine($" {s}");
}

Console.WriteLine("\n\rSorted order:");

// Specify Ordinal to demonstrate the different behavior.


Array.Sort(lines, StringComparer.CurrentCulture);

foreach (string s in lines)


{
Console.WriteLine($" {s}");
}

Depois que a matriz é classificada, você pode procurar as entradas usando uma
pesquisa binária. Uma pesquisa binária é iniciada no meio da coleção para determinar
qual metade da coleção contém a cadeia de caracteres procurada. Cada comparação
subsequente subdivide a parte restante da coleção na metade. A matriz é classificada
usando o StringComparer.CurrentCulture. A função local ShowWhere exibe informações
sobre o local em que a cadeia de caracteres foi encontrada. Se a cadeia de caracteres
não for encontrada, o valor retornado indicará onde ela estaria se fosse encontrada.

C#

string[] lines = new string[]


{
@"c:\public\textfile.txt",
@"c:\public\textFile.TXT",
@"c:\public\Text.txt",
@"c:\public\testfile2.txt"
};
Array.Sort(lines, StringComparer.CurrentCulture);

string searchString = @"c:\public\TEXTFILE.TXT";


Console.WriteLine($"Binary search for <{searchString}>");
int result = Array.BinarySearch(lines, searchString,
StringComparer.CurrentCulture);
ShowWhere<string>(lines, result);
Console.WriteLine($"{(result > 0 ? "Found" : "Did not find")}
{searchString}");

void ShowWhere<T>(T[] array, int index)


{
if (index < 0)
{
index = ~index;

Console.Write("Not found. Sorts between: ");

if (index == 0)
Console.Write("beginning of sequence and ");
else
Console.Write($"{array[index - 1]} and ");

if (index == array.Length)
Console.WriteLine("end of sequence.");
else
Console.WriteLine($"{array[index]}.");
}
else
{
Console.WriteLine($"Found at index {index}.");
}
}

Classificação ordinal e pesquisa em coleções


O código a seguir usa a classe de coleção System.Collections.Generic.List<T> para
armazenar cadeias de caracteres. As cadeias de caracteres são classificadas usando o
método List<T>.Sort. Esse método precisa de um delegado que compara e ordena as
duas cadeias de caracteres. O método String.CompareTo fornece essa função de
comparação. Execute o exemplo e observe a ordem. Essa operação de classificação usa
uma classificação ordinal que diferencia maiúsculas e minúsculas. Você usaria os
métodos String.Compare estáticos para especificar regras de comparação diferentes.

C#

List<string> lines = new List<string>


{
@"c:\public\textfile.txt",
@"c:\public\textFile.TXT",
@"c:\public\Text.txt",
@"c:\public\testfile2.txt"
};

Console.WriteLine("Non-sorted order:");
foreach (string s in lines)
{
Console.WriteLine($" {s}");
}

Console.WriteLine("\n\rSorted order:");

lines.Sort((left, right) => left.CompareTo(right));


foreach (string s in lines)
{
Console.WriteLine($" {s}");
}

Uma vez classificada, a lista de cadeias de caracteres pode ser pesquisada usando uma
pesquisa binária. O exemplo a seguir mostra como pesquisar a lista classificada usando
a mesma função de comparação. A função local ShowWhere mostra o local em que o
texto procurado está ou deveria estar:

C#

List<string> lines = new List<string>


{
@"c:\public\textfile.txt",
@"c:\public\textFile.TXT",
@"c:\public\Text.txt",
@"c:\public\testfile2.txt"
};
lines.Sort((left, right) => left.CompareTo(right));

string searchString = @"c:\public\TEXTFILE.TXT";


Console.WriteLine($"Binary search for <{searchString}>");
int result = lines.BinarySearch(searchString);
ShowWhere<string>(lines, result);

Console.WriteLine($"{(result > 0 ? "Found" : "Did not find")}


{searchString}");

void ShowWhere<T>(IList<T> collection, int index)


{
if (index < 0)
{
index = ~index;

Console.Write("Not found. Sorts between: ");

if (index == 0)
Console.Write("beginning of sequence and ");
else
Console.Write($"{collection[index - 1]} and ");

if (index == collection.Count)
Console.WriteLine("end of sequence.");
else
Console.WriteLine($"{collection[index]}.");
}
else
{
Console.WriteLine($"Found at index {index}.");
}
}

Use sempre o mesmo tipo de comparação para classificação e pesquisa. O uso de tipos
diferentes de comparação para classificação e pesquisa produz resultados inesperados.

Classes de coleção como System.Collections.Hashtable,


System.Collections.Generic.Dictionary<TKey,TValue> e
System.Collections.Generic.List<T> têm construtores que usam um parâmetro
System.StringComparer quando o tipo dos elementos ou chaves é string . Em geral,
você deve usar esses construtores sempre que possível e especificar
StringComparer.Ordinal ou StringComparer.OrdinalIgnoreCase.

Confira também
System.Globalization.CultureInfo
System.StringComparer
Cadeias de caracteres
Comparando cadeias de caracteres
Globalizando e localizando aplicativos

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
Como capturar uma exceção não
compatível com CLS
Artigo • 10/05/2023

Algumas linguagens .NET, incluindo o C++/CLI, permite que os objetos lancem exceções
que não derivam de Exception. Essas exceções são chamadas de exceções não CLS ou
não exceções. Em C#, não é possível gerar exceções que não sejam do CLS, mas você
pode capturá-las de duas maneiras:

Em um bloco catch (RuntimeWrappedException e) .

Por padrão, um assembly do Visual C# captura exceções não CLS como exceções
encapsuladas. Use este método se você precisar de acesso à exceção original, que
pode ser acessada por meio da propriedade
RuntimeWrappedException.WrappedException. O procedimento, mais adiante
neste tópico, explica como capturar exceções dessa maneira.

Em um bloco de captura geral (um bloco de captura sem um tipo de exceção


especificado), que é colocado após todos os outros blocos catch .

Use esse método quando desejar realizar alguma ação (como gravar em um
arquivo de log) em resposta a exceções não CLS e você não precisa de acesso às
informações de exceção. Por padrão, o Common Language Runtime encapsula
todas as exceções. Para desabilitar esse comportamento, adicione esse atributo de
nível de assembly em seu código, geralmente no arquivo AssemblyInfo.cs:
[assembly: RuntimeCompatibilityAttribute(WrapNonExceptionThrows = false)] .

Para capturar uma exceção não CLS


Em um bloco catch(RuntimeWrappedException e) , acesse a exceção original por meio da
propriedade RuntimeWrappedException.WrappedException.

Exemplo
O exemplo a seguir mostra como capturar uma exceção que não é do CLS, que foi
gerada por uma biblioteca de classes escrita em C++/CLI. Observe que, neste exemplo,
o código cliente C# sabe com antecedência que o tipo da exceção que está sendo
gerada é um System.String. Você pode converter a propriedade
RuntimeWrappedException.WrappedException de volta a seu tipo original, desde que o
tipo seja acessível por meio do código.
C#

// Class library written in C++/CLI.


var myClass = new ThrowNonCLS.Class1();

try
{
// throws gcnew System::String(
// "I do not derive from System.Exception!");
myClass.TestThrow();
}
catch (RuntimeWrappedException e)
{
String s = e.WrappedException as String;
if (s != null)
{
Console.WriteLine(s);
}
}

Confira também
RuntimeWrappedException
Exceções e manipulação de exceções
Attributes
Article • 03/15/2023

Attributes provide a powerful method of associating metadata, or declarative


information, with code (assemblies, types, methods, properties, and so forth). After an
attribute is associated with a program entity, the attribute can be queried at run time by
using a technique called reflection.

Attributes have the following properties:

Attributes add metadata to your program. Metadata is information about the types
defined in a program. All .NET assemblies contain a specified set of metadata that
describes the types and type members defined in the assembly. You can add
custom attributes to specify any additional information that is required.
You can apply one or more attributes to entire assemblies, modules, or smaller
program elements such as classes and properties.
Attributes can accept arguments in the same way as methods and properties.
Your program can examine its own metadata or the metadata in other programs
by using reflection.

Reflection provides objects (of type Type) that describe assemblies, modules, and types.
You can use reflection to dynamically create an instance of a type, bind the type to an
existing object, or get the type from an existing object and invoke its methods or access
its fields and properties. If you're using attributes in your code, reflection enables you to
access them. For more information, see Attributes.

Here's a simple example of reflection using the GetType() method - inherited by all types
from the Object base class - to obtain the type of a variable:

7 Note

Make sure you add using System; and using System.Reflection; at the top of
your .cs file.

C#

// Using GetType to obtain type information:


int i = 42;
Type type = i.GetType();
Console.WriteLine(type);
The output is: System.Int32 .

The following example uses reflection to obtain the full name of the loaded assembly.

C#

// Using Reflection to get information of an Assembly:


Assembly info = typeof(int).Assembly;
Console.WriteLine(info);

The output is something like: System.Private.CoreLib, Version=7.0.0.0,


Culture=neutral, PublicKeyToken=7cec85d7bea7798e .

7 Note

The C# keywords protected and internal have no meaning in Intermediate


Language (IL) and are not used in the reflection APIs. The corresponding terms in IL
are Family and Assembly. To identify an internal method using reflection, use the
IsAssembly property. To identify a protected internal method, use the
IsFamilyOrAssembly.

Using attributes
Attributes can be placed on almost any declaration, though a specific attribute might
restrict the types of declarations on which it's valid. In C#, you specify an attribute by
placing the name of the attribute enclosed in square brackets ( [] ) above the
declaration of the entity to which it applies.

In this example, the SerializableAttribute attribute is used to apply a specific


characteristic to a class:

C#

[Serializable]
public class SampleClass
{
// Objects of this type can be serialized.
}

A method with the attribute DllImportAttribute is declared like the following example:

C#
[System.Runtime.InteropServices.DllImport("user32.dll")]
extern static void SampleMethod();

More than one attribute can be placed on a declaration as the following example shows:

C#

void MethodA([In][Out] ref double x) { }


void MethodB([Out][In] ref double x) { }
void MethodC([In, Out] ref double x) { }

Some attributes can be specified more than once for a given entity. An example of such
a multiuse attribute is ConditionalAttribute:

C#

[Conditional("DEBUG"), Conditional("TEST1")]
void TraceMethod()
{
// ...
}

7 Note

By convention, all attribute names end with the word "Attribute" to distinguish
them from other items in the .NET libraries. However, you do not need to specify
the attribute suffix when using attributes in code. For example, [DllImport] is
equivalent to [DllImportAttribute] , but DllImportAttribute is the attribute's
actual name in the .NET Class Library.

Attribute parameters
Many attributes have parameters, which can be positional, unnamed, or named. Any
positional parameters must be specified in a certain order and can't be omitted. Named
parameters are optional and can be specified in any order. Positional parameters are
specified first. For example, these three attributes are equivalent:

C#

[DllImport("user32.dll")]
[DllImport("user32.dll", SetLastError=false, ExactSpelling=false)]
[DllImport("user32.dll", ExactSpelling=false, SetLastError=false)]
The first parameter, the DLL name, is positional and always comes first; the others are
named. In this case, both named parameters default to false, so they can be omitted.
Positional parameters correspond to the parameters of the attribute constructor. Named
or optional parameters correspond to either properties or fields of the attribute. Refer to
the individual attribute's documentation for information on default parameter values.

For more information on allowed parameter types, see the Attributes section of the C#
language specification

Attribute targets
The target of an attribute is the entity that the attribute applies to. For example, an
attribute may apply to a class, a particular method, or an entire assembly. By default, an
attribute applies to the element that follows it. But you can also explicitly identify, for
example, whether an attribute is applied to a method, or to its parameter, or to its return
value.

To explicitly identify an attribute target, use the following syntax:

C#

[target : attribute-list]

The list of possible target values is shown in the following table.

ノ Expand table

Target value Applies to

assembly Entire assembly

module Current assembly module

field Field in a class or a struct

event Event

method Method or get and set property accessors

param Method parameters or set property accessor parameters

property Property

return Return value of a method, property indexer, or get property accessor

type Struct, class, interface, enum, or delegate


You would specify the field target value to apply an attribute to the backing field
created for an automatically implemented property.

The following example shows how to apply attributes to assemblies and modules. For
more information, see Common Attributes (C#).

C#

using System;
using System.Reflection;
[assembly: AssemblyTitleAttribute("Production assembly 4")]
[module: CLSCompliant(true)]

The following example shows how to apply attributes to methods, method parameters,
and method return values in C#.

C#

// default: applies to method


[ValidatedContract]
int Method1() { return 0; }

// applies to method
[method: ValidatedContract]
int Method2() { return 0; }

// applies to parameter
int Method3([ValidatedContract] string contract) { return 0; }

// applies to return value


[return: ValidatedContract]
int Method4() { return 0; }

7 Note

Regardless of the targets on which ValidatedContract is defined to be valid, the


return target has to be specified, even if ValidatedContract were defined to apply

only to return values. In other words, the compiler will not use AttributeUsage
information to resolve ambiguous attribute targets. For more information, see
AttributeUsage.

Common uses for attributes


The following list includes a few of the common uses of attributes in code:
Marking methods using the WebMethod attribute in Web services to indicate that
the method should be callable over the SOAP protocol. For more information, see
WebMethodAttribute.
Describing how to marshal method parameters when interoperating with native
code. For more information, see MarshalAsAttribute.
Describing the COM properties for classes, methods, and interfaces.
Calling unmanaged code using the DllImportAttribute class.
Describing your assembly in terms of title, version, description, or trademark.
Describing which members of a class to serialize for persistence.
Describing how to map between class members and XML nodes for XML
serialization.
Describing the security requirements for methods.
Specifying characteristics used to enforce security.
Controlling optimizations by the just-in-time (JIT) compiler so the code remains
easy to debug.
Obtaining information about the caller to a method.

Reflection overview
Reflection is useful in the following situations:

When you have to access attributes in your program's metadata. For more
information, see Retrieving Information Stored in Attributes.
For examining and instantiating types in an assembly.
For building new types at run time. Use classes in System.Reflection.Emit.
For performing late binding, accessing methods on types created at run time. See
the article Dynamically Loading and Using Types.

Related sections
For more information:

Common Attributes (C#)


Caller Information (C#)
Attributes
Reflection
View Type Information
Reflection and Generic Types
System.Reflection.Emit
Retrieving Information Stored in Attributes
Criar atributos personalizados
Artigo • 20/03/2023

Você pode criar seus próprios atributos personalizados definindo uma classe de
atributos, uma classe que deriva direta ou indiretamente de Attribute, o que faz com
que a identificação das definições de atributo nos metadados seja rápida e fácil.
Suponha que você queira marcar tipos com o nome do programador que escreveu o
tipo. Você pode definir uma classe de atributos Author personalizada:

C#

[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Struct)
]
public class AuthorAttribute : System.Attribute
{
private string Name;
public double Version;

public AuthorAttribute(string name)


{
Name = name;
Version = 1.0;
}
}

O nome de classe AuthorAttribute é o nome do atributo, Author , acrescido do sufixo


Attribute . Ela é derivada de System.Attribute , portanto, é uma classe de atributo

personalizado. Os parâmetros do construtor são parâmetros posicionais do atributo


personalizado. Neste exemplo, name é um parâmetro posicional. Quaisquer
propriedades ou campos públicos de leitura/gravação são chamados de parâmetros.
Nesse caso, version é o único parâmetro nomeado. Observe o uso do atributo
AttributeUsage para tornar o atributo Author válido apenas na classe e nas declarações
struct .

Você pode usar esse novo atributo da seguinte maneira:

C#

[Author("P. Ackerman", Version = 1.1)]


class SampleClass
{
// P. Ackerman's code goes here...
}
AttributeUsage tem um parâmetro nomeado, AllowMultiple , com o qual você pode

fazer um atributo personalizado de uso único ou mulituso. No exemplo de código a


seguir, um atributo multiuso é criado.

C#

[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Struct,
AllowMultiple = true) // Multiuse attribute.
]
public class AuthorAttribute : System.Attribute
{
string Name;
public double Version;

public AuthorAttribute(string name)


{
Name = name;

// Default value.
Version = 1.0;
}

public string GetName() => Name;


}

No exemplo de código a seguir, vários atributos do mesmo tipo são aplicados a uma
classe.

C#

[Author("P. Ackerman"), Author("R. Koch", Version = 2.0)]


public class ThirdClass
{
// ...
}

Confira também
System.Reflection
Escrevendo atributos personalizados
AttributeUsage (C#)
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.
Acessar atributos usando reflexão
Artigo • 20/03/2023

O fato de que você pode definir atributos personalizados e colocá-los em seu código-
fonte seria de pouco valor sem alguma maneira de recuperar essas informações e tomar
ação sobre elas. Por meio de reflexão, você pode recuperar as informações que foram
definidas com atributos personalizados. O método principal é o GetCustomAttributes ,
que retorna uma matriz de objetos que são equivalentes, em tempo de execução, aos
atributos do código-fonte. Esse método tem várias versões sobrecarregadas. Para obter
mais informações, consulte Attribute.

Uma especificação de atributo, como:

C#

[Author("P. Ackerman", Version = 1.1)]


class SampleClass { }

é conceitualmente equivalente ao seguinte código:

C#

var anonymousAuthorObject = new Author("P. Ackerman")


{
Version = 1.1
};

No entanto, o código não será executado até que SampleClass tenha os atributos
consultados. Chamar GetCustomAttributes na SampleClass faz com que um objeto
Author seja criado e inicializado. Se a classe tiver outros atributos, outros objetos de

atributo serão construídos de forma semelhante. Então o GetCustomAttributes retornará


o objeto Author e quaisquer outros objetos de atributo em uma matriz. Você poderá
iterar sobre essa matriz, determinar quais atributos foram aplicados com base no tipo
de cada elemento da matriz e extrair informações dos objetos de atributo.

Veja um exemplo completo. Um atributo personalizado é definido, aplicado a várias


entidades e recuperado por meio da reflexão.

C#

// Multiuse attribute.
[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Struct,
AllowMultiple = true) // Multiuse attribute.
]
public class AuthorAttribute : System.Attribute
{
string Name;
public double Version;

public AuthorAttribute(string name)


{
Name = name;

// Default value.
Version = 1.0;
}

public string GetName() => Name;


}

// Class with the Author attribute.


[Author("P. Ackerman")]
public class FirstClass
{
// ...
}

// Class without the Author attribute.


public class SecondClass
{
// ...
}

// Class with multiple Author attributes.


[Author("P. Ackerman"), Author("R. Koch", Version = 2.0)]
public class ThirdClass
{
// ...
}

class TestAuthorAttribute
{
public static void Test()
{
PrintAuthorInfo(typeof(FirstClass));
PrintAuthorInfo(typeof(SecondClass));
PrintAuthorInfo(typeof(ThirdClass));
}

private static void PrintAuthorInfo(System.Type t)


{
System.Console.WriteLine($"Author information for {t}");

// Using reflection.
System.Attribute[] attrs = System.Attribute.GetCustomAttributes(t);
// Reflection.
// Displaying output.
foreach (System.Attribute attr in attrs)
{
if (attr is AuthorAttribute a)
{
System.Console.WriteLine($" {a.GetName()}, version
{a.Version:f}");
}
}
}
}
/* Output:
Author information for FirstClass
P. Ackerman, version 1.00
Author information for SecondClass
Author information for ThirdClass
R. Koch, version 2.00
P. Ackerman, version 1.00
*/

Confira também
System.Reflection
Attribute
Recuperando informações armazenadas em atributos

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.
Como criar uma união do C/C++
usando atributos em C#
Artigo • 03/04/2023

Usando atributos, você pode personalizar como os structs são dispostos na memória.
Por exemplo, você pode criar o que é conhecido como uma união no C/C++ usando os
atributos StructLayout(LayoutKind.Explicit) e FieldOffset .

Neste segmento de código, todos os campos de TestUnion são iniciados no mesmo


local na memória.

C#

[System.Runtime.InteropServices.StructLayout(LayoutKind.Explicit)]
struct TestUnion
{
[System.Runtime.InteropServices.FieldOffset(0)]
public int i;

[System.Runtime.InteropServices.FieldOffset(0)]
public double d;

[System.Runtime.InteropServices.FieldOffset(0)]
public char c;

[System.Runtime.InteropServices.FieldOffset(0)]
public byte b;
}

O código a seguir é outro exemplo em que os campos são iniciados em locais diferentes
definidos explicitamente.

C#

[System.Runtime.InteropServices.StructLayout(LayoutKind.Explicit)]
struct TestExplicit
{
[System.Runtime.InteropServices.FieldOffset(0)]
public long lg;

[System.Runtime.InteropServices.FieldOffset(0)]
public int i1;

[System.Runtime.InteropServices.FieldOffset(4)]
public int i2;

[System.Runtime.InteropServices.FieldOffset(8)]
public double d;

[System.Runtime.InteropServices.FieldOffset(12)]
public char c;

[System.Runtime.InteropServices.FieldOffset(14)]
public byte b;
}

Os dois campos inteiros, i1 e i2 combinados, compartilham os mesmos locais de


memória que lg . lg usa os primeiros oito bytes, ou então i1 usa os quatro primeiros
bytes e i2 usa os próximos quatro bytes. Esse tipo de controle sobre o layout do struct
é útil ao usar a invocação de plataforma.

Confira também
System.Reflection
Attribute
Atributos

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.
Genéricos e atributos
Artigo • 21/11/2024

Atributos podem ser aplicados a tipos genéricos da mesma forma que a tipos não
genéricos. No entanto, você pode aplicar atributos somente em tipos genéricos abertos e
tipos genéricos construídos fechados, não em tipos genéricos parcialmente construídos.
Um tipo genérico aberto é aquele em que nenhum dos argumentos de tipo é
especificado, como Dictionary<TKey, TValue> Um tipo genérico construído fechado
especifica todos os argumentos de tipo, como Dictionary<string, object> . Um tipo
genérico parcialmente construído especifica alguns argumentos de tipo, mas não todos.
Um exemplo é Dictionary<string, TValue> . Um tipo genérico não associado é aquele
em que os argumentos de tipo são omitidos, como Dictionary<,> .

Os exemplos a seguir usam esse atributo personalizado:

C#

class CustomAttribute : Attribute


{
public object? info;
}

Um atributo pode fazer referência a um tipo genérico não associado:

C#

public class GenericClass1<T> { }

[CustomAttribute(info = typeof(GenericClass1<>))]
class ClassA { }

Especifica vários parâmetros de tipo usando a quantidade apropriada de vírgulas. Neste


exemplo, GenericClass2 tem dois parâmetros de tipo:

C#

public class GenericClass2<T, U> { }

[CustomAttribute(info = typeof(GenericClass2<,>))]
class ClassB { }

Um atributo pode referenciar um tipo genérico construído fechado:


C#

public class GenericClass3<T, U, V> { }

[CustomAttribute(info = typeof(GenericClass3<int, double, string>))]


class ClassC { }

Um atributo que referencia um parâmetro de tipo genérico causa um erro em tempo de


compilação:

C#

[CustomAttribute(info = typeof(GenericClass3<int, T, string>))] //Error


CS0416
class ClassD<T> { }

A partir do C# 11, um tipo genérico pode herdar de Attribute:

C#

public class CustomGenericAttribute<T> : Attribute { } //Requires C# 11

Para obter informações sobre um tipo genérico ou um parâmetro de tipo em tempo de


execução, é possível usar os métodos do System.Reflection. Para obter mais
informações, confira Genéricos e Reflexão.

Confira também
Genéricos
Atributos
Como consultar metadados de um
assembly com reflexão (LINQ)
Artigo • 20/03/2023

Você usa as APIs de reflexão do .NET para examinar os metadados no assembly do .NET
e criar coleções de tipos, membros de tipo e parâmetros que estão nesse assembly.
Como essas coleções dão suporte à interface IEnumerable<T> genéricas, elas podem
ser consultadas usando LINQ.

O exemplo a seguir mostra como o LINQ pode ser usado com a reflexão para recuperar
metadados específicos sobre os métodos que correspondem a um critério de pesquisa
especificado. Nesse caso, a consulta localiza os nomes de todos os métodos no
assembly que retornam tipos enumeráveis como matrizes.

C#

Assembly assembly = Assembly.Load("System.Private.CoreLib, Version=7.0.0.0,


Culture=neutral, PublicKeyToken=7cec85d7bea7798e");
var pubTypesQuery = from type in assembly.GetTypes()
where type.IsPublic
from method in type.GetMethods()
where method.ReturnType.IsArray == true
|| (method.ReturnType.GetInterface(

typeof(System.Collections.Generic.IEnumerable<>).FullName!) != null
&& method.ReturnType.FullName != "System.String")
group method.ToString() by type.ToString();

foreach (var groupOfMethods in pubTypesQuery)


{
Console.WriteLine("Type: {0}", groupOfMethods.Key);
foreach (var method in groupOfMethods)
{
Console.WriteLine(" {0}", method);
}
}

O exemplo usa o método Assembly.GetTypes para retornar uma matriz de tipos no


assembly especificado. O filtro where é aplicado para que apenas tipos públicos sejam
retornados. Para cada tipo de público, uma subconsulta é gerada usando a matriz
MethodInfo que é retornada da chamada Type.GetMethods. Esses resultados são
filtrados para retornar apenas os métodos cujo tipo de retorno é uma matriz ou um tipo
que implementa IEnumerable<T>. Por fim, esses resultados são agrupados usando o
nome do tipo como uma chave.
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.
Genéricos e reflexão
Artigo • 20/03/2023

Como o tempo de execução do CLR (Common Language Runtime) tem acesso às


informações de tipo genérico em tempo de execução, você pode usar a reflexão para
obter informações sobre tipos genéricos da mesma forma como para tipos não
genéricos. Para obter mais informações, confira Genéricos no runtime.

O namespace System.Reflection.Emit também contém novos membros que dão suporte


a genéricos. Consulte Como definir um tipo genérico com a emissão de reflexão.

Para obter uma lista das condições invariáveis para termos usados na reflexão genérica,
consulte os comentários da propriedade IsGenericType:

IsGenericType: retornará true se um tipo for genérico.


GetGenericArguments: retorna uma matriz de objetos Type que representa os
argumentos de tipo fornecidos para um tipo construído ou os parâmetros de tipo
de uma definição de tipo genérico.
GetGenericTypeDefinition: retorna a definição de tipo genérico subjacente para o
tipo construído atual.
GetGenericParameterConstraints: retorna uma matriz de objetos Type que
representam as restrições no parâmetro de tipo genérico atual.
ContainsGenericParameters: retorna verdadeiro se o tipo ou qualquer um dos seus
tipos ou métodos de delimitação contêm parâmetros de tipo para os quais não
foram fornecidos tipos específicos.
GenericParameterAttributes: obtém uma combinação de sinalizadores
GenericParameterAttributes que descrevem as restrições especiais do parâmetro

de tipo genérico atual.


GenericParameterPosition: para um objeto Type que representa um parâmetro de
tipo, obtém a posição do parâmetro de tipo na lista de parâmetros de tipo da
definição de tipo genérico ou da definição de método genérico que declarou o
parâmetro de tipo.
IsGenericParameter: obtém um valor que indica se o Type atual representa um
parâmetro de tipo de um tipo genérico ou uma definição de método.
IsGenericTypeDefinition: obtém um valor que indica se o Type atual representa
uma definição de tipo genérico, da qual outros tipos genéricos podem ser
construídos. Retorna verdadeiro se o tipo representa a definição de um tipo
genérico.
DeclaringMethod: retorna o método genérico que definiu o parâmetro de tipo
genérico atual ou nulo, se o parâmetro de tipo não foi definido por um método
genérico.
MakeGenericType: substitui os elementos de uma matriz de tipos pelos parâmetros
de tipo da definição de tipo genérico atual e retorna um objeto Type que
representa o tipo construído resultante.

Além disso, membros da classe MethodInfo habilitam informações em tempo de


execução para métodos genéricos. Consulte os comentários sobre a propriedade
IsGenericMethod para obter uma lista das condições invariáveis para termos usados
para refletir sobre os métodos genéricos:

IsGenericMethod: retorna true se um método é genérico.


GetGenericArguments: retorna uma matriz de objetos Type que representam os
argumentos de tipo de um método genérico construído ou os parâmetros de tipo
de uma definição de método genérico.
GetGenericMethodDefinition: retorna a definição de método genérico subjacente
para o método construído atual.
ContainsGenericParameters: retorna true se o método ou qualquer um dos seus
tipos de delimitação contêm parâmetros de tipo para os quais não foram
fornecidos tipos específicos.
IsGenericMethodDefinition: retorna true se o MethodInfo atual representar a
definição de um método genérico.
MakeGenericMethod: substitui os elementos de uma matriz de tipos pelos
parâmetros de tipo da definição de método genérico atual e retorna um objeto
MethodInfo que representa o método construído resultante.

Confira também
Genéricos
Reflexão e tipos genéricos
Genéricos

6 Collaborate with us on
GitHub .NET feedback
The .NET documentation is open
The source for this content can
be found on GitHub, where you source. Provide feedback here.
can also create and review
 Open a documentation issue
issues and pull requests. For
more information, see our
 Provide product feedback
contributor guide.
Definir e ler atributos personalizados
Artigo • 20/03/2023

Os atributos fornecem uma maneira de associar informações ao código de forma


declarativa. Eles também podem fornecer um elemento reutilizável que pode ser
aplicado a vários destinos. Considere o ObsoleteAttribute. Ele pode ser aplicado a
classes, structs, métodos, construtores e muito mais. Ele declara que o elemento é
obsoleto. Em seguida, cabe ao compilador C# procurar esse atributo e realizar alguma
ação em resposta.

Neste tutorial, você aprenderá a adicionar atributos a seu código, como criar e usar seus
próprios atributos e como usar alguns atributos que são criados no .NET.

Pré-requisitos
Você precisa configurar seu computador para executar o .NET. Você vai encontrar as
instruções de instalação na página de downloads do .NET . Você pode executar esse
aplicativo no Windows, Ubuntu Linux, macOS ou em um contêiner do Docker. Você
precisa instalar o editor de código da sua preferência. As descrições a seguir usam o
Visual Studio Code , que é um editor de software livre entre plataformas. No entanto,
você pode usar ferramentas com que esteja familiarizado.

Criar o aplicativo
Agora que você instalou todas as ferramentas, crie um aplicativo de console .NET. Para
usar o gerador de linha de comando, execute o seguinte comando no shell de sua
preferência:

CLI do .NET

dotnet new console

Esse comando cria arquivos de projeto .NET bare-bones. Execute dotnet restore para
restaurar as dependências necessárias para compilar esse 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.

Para executar o programa, use dotnet run . Você deve ver a saída do "Olá, Mundo" no
console.

Adicionar atributos ao código


No C#, os atributos são classes que herdam da classe base Attribute . Qualquer classe
que herda de Attribute pode ser usada como uma espécie de "marcação" em outras
partes do código. Por exemplo, há um atributo chamado ObsoleteAttribute . Esse
atributo sinaliza que o código está obsoleto e não deve mais ser usado. Coloque este
atributo em uma classe, por exemplo, usando colchetes.

C#

[Obsolete]
public class MyClass
{
}

Embora a classe seja chamada de ObsoleteAttribute , só é necessário usar [Obsolete]


no código. A maioria dos códigos C# segue esta convenção. Você poderá usar o nome
completo [ObsoleteAttribute] se escolher.

Ao marcar uma classe obsoleta, é uma boa ideia fornecer algumas informações como o
motivo de estar obsoleto e/ou o que usar no lugar. Você inclui um parâmetro de cadeia
de caracteres para o atributo Obsolete para fornecer essa explicação.

C#

[Obsolete("ThisClass is obsolete. Use ThisClass2 instead.")]


public class ThisClass
{
}

A cadeia de caracteres está sendo passada como um argumento para um construtor


ObsoleteAttribute , como se você estivesse escrevendo var attr = new
ObsoleteAttribute("some string") .

Os parâmetros para um construtor de atributo são limitados a literais/tipos simples:


bool, int, double, string, Type, enums, etc e matrizes desses tipos. Você não pode

usar uma expressão ou uma variável. Você pode usar parâmetros posicionais ou
nomeados.

Criar seu próprio atributo


Você cria um atributo definindo uma nova classe que herda da classe base Attribute .

C#

public class MySpecialAttribute : Attribute


{
}

Com o código anterior, agora você pode usar [MySpecial] (ou [MySpecialAttribute] )
como um atributo em qualquer lugar na base do código.

C#

[MySpecial]
public class SomeOtherClass
{
}

Os atributos biblioteca de classes base do .NET, como ObsoleteAttribute , disparam


determinados comportamentos no compilador. No entanto, qualquer atributo que você
cria atua como metadados e não resulta em qualquer código dentro da classe de
atributo que está sendo executada. Cabe a você agir nesses metadados no seu código.

Há uma pegadinha aqui. Conforme mencionado anteriormente, somente determinados


tipos podem ser passados como argumentos ao usar atributos. No entanto, ao criar um
tipo de atributo, o compilador C# não impede você de criar esses parâmetros. No
exemplo a seguir, você criou um atributo com um construtor que compila corretamente.

C#

public class GotchaAttribute : Attribute


{
public GotchaAttribute(Foo myClass, string str)
{
}
}

No entanto, não é possível usar esse construtor com a sintaxe de atributo.

C#

[Gotcha(new Foo(), "test")] // does not compile


public class AttributeFail
{
}

O código anterior causa um erro do compilador como Attribute constructor parameter


'myClass' has type 'Foo', which is not a valid attribute parameter type

Como restringir o uso do atributo


Os atributos podem ser usados nos "destinos" a seguir. Os exemplos acima mostram os
atributos em classes, mas eles também podem ser usados em:

Assembly
Classe
Construtor
Delegar
Enumeração
Evento
Campo
GenericParameter
Interface
Método
Módulo
Parâmetro
Propriedade
ReturnValue
Estrutura

Quando você cria uma classe de atributo, por padrão, o C# permite que você use esse
atributo em qualquer um dos destinos possíveis do atributo. Se quiser restringir seu
atributo a determinados destinos, você poderá fazer isso usando o
AttributeUsageAttribute em sua classe de atributo. É isso mesmo, um atributo em um

atributo!
C#

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class MyAttributeForClassAndStructOnly : Attribute
{
}

Se tentar colocar o atributo acima em algo que não é uma classe ou um struct, você
obtém um erro do compilador como Attribute 'MyAttributeForClassAndStructOnly' is
not valid on this declaration type. It is only valid on 'class, struct'
declarations

C#

public class Foo


{
// if the below attribute was uncommented, it would cause a compiler
error
// [MyAttributeForClassAndStructOnly]
public Foo()
{ }
}

Como usar atributos anexados a um elemento


de código
Atributos agem como metadados. Sem nenhuma força, eles não fazem nada.

Para localizar e agir sobre os atributos, uma reflexão é necessária. A reflexão permite
que você escreva código em C# que examine outro código. Por exemplo, você pode
usar a Reflexão para obter informações sobre uma classe (adicione using
System.Reflection; no início do seu código):

C#

TypeInfo typeInfo = typeof(MyClass).GetTypeInfo();


Console.WriteLine("The assembly qualified name of MyClass is " +
typeInfo.AssemblyQualifiedName);

Isso imprime algo como: The assembly qualified name of MyClass is


ConsoleApplication.MyClass, attributes, Version=1.0.0.0, Culture=neutral,

PublicKeyToken=null
Depois de ter um objeto TypeInfo (ou um MemberInfo , FieldInfo ou outro objeto), você
poderá usar o método GetCustomAttributes . Esse método retorna uma coleção de
objetos Attribute . Você também poderá usar GetCustomAttribute e especificar um tipo
de atributo.

Aqui está um exemplo do uso de GetCustomAttributes em uma instância MemberInfo


para MyClass (que vimos, anteriormente, que tem um atributo [Obsolete] nele).

C#

var attrs = typeInfo.GetCustomAttributes();


foreach(var attr in attrs)
Console.WriteLine("Attribute on MyClass: " + attr.GetType().Name);

Isso imprime no console: Attribute on MyClass: ObsoleteAttribute . Tente adicionar


outros atributos a MyClass .

É importante observar que esses objetos Attribute são instanciados lentamente. Ou


seja, eles não serão instanciados até que você use GetCustomAttribute ou
GetCustomAttributes . Eles também são instanciados a cada vez. Chamar
GetCustomAttributes duas vezes em uma linha retornará duas instâncias diferentes do

ObsoleteAttribute .

Atributos comuns no runtime


Os atributos são usados por muitas ferramentas e estruturas. O NUnit usa atributos
como [Test] e [TestFixture] que são usados pelo executor de teste NUnit. O ASP.NET
MVC usa atributos como [Authorize] e fornece uma estrutura de filtro de ação para
executar questões abrangentes sobre as ações do MVC. O PostSharp usa a sintaxe de
atributo para permitir a programação em C# orientada ao aspecto.

Aqui estão alguns atributos importantes incorporados às bibliotecas de classes base do


.NET Core:

[Obsolete] . Este foi usado nos exemplos acima, e reside no namespace System .

Isso é útil para fornecer a documentação declarativa sobre uma base de código de
alteração. Uma mensagem pode ser fornecida na forma de uma cadeia de
caracteres e outro parâmetro booliano pode ser usado para encaminhamento de
um aviso do compilador para um erro do compilador.
[Conditional] . Esse atributo está no namespace System.Diagnostics . Esse atributo

pode ser aplicado aos métodos (ou classes de atributo). Você deve passar uma
cadeia de caracteres para o construtor. Se essa cadeia de caracteres não
corresponder a uma diretiva #define , o compilador C# removerá todas as
chamadas a esse método (mas não o próprio método). Normalmente, você usa
essa técnica para fins de depuração (diagnóstico).
[CallerMemberName] . Esse atributo pode ser usado em parâmetros e reside no

namespace System.Runtime.CompilerServices . CallerMemberName é um atributo


usado para injetar o nome do método que está chamando outro método. É uma
forma de eliminar 'cadeias de caracteres mágicas' ao implementar
INotifyPropertyChanged em diversas estruturas de interface do usuário. Por
exemplo:

C#

public class MyUIClass : INotifyPropertyChanged


{
public event PropertyChangedEventHandler? PropertyChanged;

public void RaisePropertyChanged([CallerMemberName] string propertyName


= default!)
{
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs(propertyName));
}

private string? _name;


public string? Name
{
get { return _name;}
set
{
if (value != _name)
{
_name = value;
RaisePropertyChanged(); // notice that "Name" is not
needed here explicitly
}
}
}
}

No código acima, você não precisa ter uma cadeia de caracteres "Name" literal. Usar
CallerMemberName evita erros relacionados à digitação e também possibilita a

refatoração/renomeação mais suave. Atributos trazem poder declarativo para C#, mas
eles são uma forma de metadados de código e não agem sozinhos.
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.
Tutorial: atualizar interfaces com
métodos de interface padrão
Artigo • 21/03/2023

Você pode definir uma implementação ao declarar um membro de uma interface. O


cenário mais comum é adicionar membros com segurança a uma interface já lançada e
usada por vários clientes.

Neste tutorial, você aprenderá como:

" Estender interfaces com segurança, adicionando métodos com implementações.


" Criar implementações parametrizadas para oferecer maior flexibilidade.
" Permitir que os implementadores forneçam uma implementação mais específica na
forma de uma substituição.

Pré-requisitos
Você precisa configurar o computador para executar o .NET, incluindo o compilador C#.
O compilador C# está disponível com o Visual Studio 2022 ou o SDK do .NET .

Visão geral do cenário


Este tutorial começa com a versão 1 de uma biblioteca de relacionamento com o cliente.
Você pode obter o aplicativo de iniciante em nosso repositório de exemplos no
GitHub . A empresa que criou essa biblioteca pretendia que clientes com aplicativos
existentes adotassem a biblioteca. Eles forneciam definições de interface mínimas para
os usuários da biblioteca implementarem. Aqui está a definição de interface para um
cliente:

C#

public interface ICustomer


{
IEnumerable<IOrder> PreviousOrders { get; }

DateTime DateJoined { get; }


DateTime? LastOrder { get; }
string Name { get; }
IDictionary<DateTime, string> Reminders { get; }
}
Eles definiram uma segunda interface que representava um pedido:

C#

public interface IOrder


{
DateTime Purchased { get; }
decimal Cost { get; }
}

Dessas interfaces, a equipe poderia criar uma biblioteca para os usuários criarem uma
experiência melhor para os clientes. A meta era criar um relacionamento mais profundo
com os clientes existentes e melhorar o relacionamento com clientes novos.

Agora é hora de atualizar a biblioteca para a próxima versão. Um dos recursos


solicitados habilitará um desconto de fidelidade para os clientes que tiverem muitos
pedidos. Esse novo desconto de fidelidade é aplicado sempre que um cliente faz um
pedido. O desconto específico é uma propriedade de cada cliente. Cada implementação
de ICustomer pode definir regras diferentes para o desconto de fidelidade.

A forma mais natural de adicionar essa funcionalidade é melhorar a interface ICustomer


com um método para aplicar qualquer desconto de fidelidade. Essa sugestão de design
causou preocupação entre desenvolvedores experientes: "As interfaces são imutáveis
depois de serem lançadas. Não realize uma alteração interruptiva". Implementações de
interface padrão para atualizar interfaces. Os autores de biblioteca podem adicionar
novos membros à interface e fornecer uma implementação padrão para esses membros.

Implementações de interface padrão permitem que os desenvolvedores atualizem uma


interface, permitindo que qualquer implementador substitua essa implementação. Os
usuários da biblioteca podem aceitar a implementação padrão como uma alteração da
falha. Se as regras de negócio forem diferentes, elas poderão substituir a
implementação.

Atualizar com métodos de interface padrão


A equipe concordou na implementação padrão mais provável: um desconto de
fidelidade para os clientes.

A atualização deve fornecer a funcionalidade para definir duas propriedades: o número


de pedidos necessários para ser qualificado para o desconto e o percentual de
desconto. Esses recursos o tornam um cenário ideal para métodos de interface padrão.
Você pode adicionar um método à interface ICustomer e fornecer a implementação
mais provável. Todas as implementações novas e existentes podem usar a
implementação padrão ou fornecer as suas próprias.

Primeiro, adicione o novo método à interface, incluindo o corpo do método:

C#

// Version 1:
public decimal ComputeLoyaltyDiscount()
{
DateTime TwoYearsAgo = DateTime.Now.AddYears(-2);
if ((DateJoined < TwoYearsAgo) && (PreviousOrders.Count() > 10))
{
return 0.10m;
}
return 0;
}

O autor da biblioteca escreveu um primeiro teste para verificar a implementação:

C#

SampleCustomer c = new SampleCustomer("customer one", new DateTime(2010, 5,


31))
{
Reminders =
{
{ new DateTime(2010, 08, 12), "childs's birthday" },
{ new DateTime(1012, 11, 15), "anniversary" }
}
};

SampleOrder o = new SampleOrder(new DateTime(2012, 6, 1), 5m);


c.AddOrder(o);

o = new SampleOrder(new DateTime(2103, 7, 4), 25m);


c.AddOrder(o);

// Check the discount:


ICustomer theCustomer = c;
Console.WriteLine($"Current discount:
{theCustomer.ComputeLoyaltyDiscount()}");

Observe a seguinte parte do teste:

C#

// Check the discount:


ICustomer theCustomer = c;
Console.WriteLine($"Current discount:
{theCustomer.ComputeLoyaltyDiscount()}");

Essa conversão de SampleCustomer em ICustomer é necessária. A classe SampleCustomer


não precisa fornecer uma implementação de ComputeLoyaltyDiscount ; isso é fornecido
pela interface ICustomer . No entanto, a classe SampleCustomer não herda membros de
suas interfaces. Essa regra não foi alterada. Para chamar qualquer método declarado e
implementado na interface, a variável deve ser o tipo da interface: ICustomer neste
exemplo.

Fornecer parametrização
A implementação padrão é muito restritiva. Muitos consumidores desse sistema podem
escolher limites diferentes para o número de compras, um período diferente de
associação ou um percentual de desconto diferente. Você pode fornecer uma
experiência de atualização melhor para mais clientes, fornecendo uma maneira de
definir esses parâmetros. Vamos adicionar um método estático que defina esses três
parâmetros, controlando a implementação padrão:

C#

// Version 2:
public static void SetLoyaltyThresholds(
TimeSpan ago,
int minimumOrders = 10,
decimal percentageDiscount = 0.10m)
{
length = ago;
orderCount = minimumOrders;
discountPercent = percentageDiscount;
}
private static TimeSpan length = new TimeSpan(365 * 2, 0,0,0); // two years
private static int orderCount = 10;
private static decimal discountPercent = 0.10m;

public decimal ComputeLoyaltyDiscount()


{
DateTime start = DateTime.Now - length;

if ((DateJoined < start) && (PreviousOrders.Count() > orderCount))


{
return discountPercent;
}
return 0;
}
Há muitos novos recursos de linguagem mostrados naquele pequeno fragmento de
código. Agora as interfaces podem incluir membros estáticos, incluindo campos e
métodos. Modificadores de acesso diferentes também estão habilitados. Os outros
campos são privados, mas o novo método é público. Qualquer dos modificadores são
permitidos em membros de interface.

Aplicativos que usam a fórmula geral para calcular o desconto de fidelidade, mas que
usam parâmetros diferentes, não precisam fornecer uma implementação personalizada;
eles podem definir os argumentos por meio de um método estático. Por exemplo, o
código a seguir define um "agradecimento ao cliente" que recompensa qualquer cliente
com mais de um mês de associação:

C#

ICustomer.SetLoyaltyThresholds(new TimeSpan(30, 0, 0, 0), 1, 0.25m);


Console.WriteLine($"Current discount:
{theCustomer.ComputeLoyaltyDiscount()}");

Estender a implementação padrão


O código que você adicionou até agora forneceu uma implementação conveniente para
esses cenários em que os usuários querem algo semelhante à implementação padrão
ou para fornecer um conjunto de regras não relacionado. Como um último recurso,
vamos refatorar o código um pouco para permitir cenários em que os usuários queiram
criar com base na implementação padrão.

Considere uma startup que deseja atrair novos clientes. Eles oferecem um desconto de
50% no primeiro pedido de um novo cliente. Por outro lado, clientes existentes obtém o
desconto padrão. O autor da biblioteca precisa mover a implementação padrão para um
método protected static , de modo que qualquer classe que implemente essa interface
possa reutilizar o código em sua implementação. A implementação padrão do membro
da interface também chama esse método compartilhado:

C#

public decimal ComputeLoyaltyDiscount() => DefaultLoyaltyDiscount(this);


protected static decimal DefaultLoyaltyDiscount(ICustomer c)
{
DateTime start = DateTime.Now - length;

if ((c.DateJoined < start) && (c.PreviousOrders.Count() > orderCount))


{
return discountPercent;
}
return 0;
}

Em uma implementação de uma classe que implementa essa interface, a substituição


pode chamar o método auxiliar estático e estender essa lógica para fornecer o desconto
de "novo cliente":

C#

public decimal ComputeLoyaltyDiscount()


{
if (PreviousOrders.Any() == false)
return 0.50m;
else
return ICustomer.DefaultLoyaltyDiscount(this);
}

Você pode ver todo o código concluído no nosso repositório de amostras no GitHub .
Você pode obter o aplicativo de iniciante em nosso repositório de exemplos no
GitHub .

Esses novos recursos significam que interfaces podem ser atualizadas com segurança
quando há uma implementação padrão razoável para os novos membros. Projete
interfaces cuidadosamente para expressar ideias funcionais únicas que possam ser
implementadas por várias classes. Isso torna mais fácil atualizar essas definições de
interface quando são descobertos novos requisitos para a mesma ideia funcional.

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.
Tutorial: Funcionalidade mix ao criar
classes usando interfaces com métodos
de interface padrão
Artigo • 04/08/2024

Você pode definir uma implementação ao declarar um membro de uma interface. Essa
funcionalidade fornece novos recursos em que você pode definir implementações
padrão para recursos declarados em interfaces. As classes podem escolher quando
substituir a funcionalidade, quando usar a funcionalidade padrão e quando não declarar
suporte a recursos discretos.

Neste tutorial, você aprenderá a:

" Crie interfaces com implementações que descrevem recursos discretos.


" Crie classes que usam as implementações padrão.
" Crie classes que substituam algumas ou todas as implementações padrão.

Pré-requisitos
Você precisa configurar o computador para executar o .NET, incluindo o compilador C#.
O compilador C# está disponível com o Visual Studio 2022 ou o SDK do .NET .

Limitações de métodos de extensão


Uma maneira de implementar o comportamento que aparece como parte de uma
interface é definindo métodos de extensão que fornecem o comportamento padrão. As
interfaces declaram um conjunto mínimo de membros, fornecendo uma área de
superfície maior para qualquer classe que implemente essa interface. Por exemplo, os
métodos de extensão em Enumerable fornecem a implementação para qualquer
sequência ser a fonte de uma consulta LINQ.

Os métodos de extensão são resolvidos no tempo de compilação, usando o tipo


declarado da variável. As classes que implementam a interface podem fornecer uma
implementação melhor para qualquer método de extensão. As declarações de variável
devem corresponder ao tipo de implementação para permitir que o compilador escolha
essa implementação. Quando o tipo de tempo de compilação corresponder à interface,
as chamadas de método são resolvidas para o método de extensão. Outra preocupação
com os métodos de extensão é que eles podem ser acessados pelo mesmo local que a
classe contendo os métodos de extensão. As classes não poderão declarar se devem ou
não fornecer recursos declarados nos métodos de extensão.

Você pode declarar as implementações padrão como métodos de interface. Em seguida,


cada classe usa automaticamente a implementação padrão. Qualquer classe que possa
fornecer uma implementação melhor pode substituir a definição do método de
interface por um algoritmo melhor. De certa forma, essa técnica é semelhante à forma
de utilização dos métodos de extensão.

Neste artigo, você aprende como as implementações de interface padrão habilitam


novos cenários.

Criar o aplicativo
Considere um aplicativo de automação residencial. Você provavelmente tem muitos
tipos de luzes e indicadores que podem ser usados em toda a casa. Cada luz deve dar
suporte a APIs para ativá-las, desativá-las e relatar o estado atual. Algumas luzes e
indicadores podem dar suporte a outros recursos, como:

Ativar a luz e desligá-la após um temporizador.


Piscar a luz por um período de tempo.

Alguns desses recursos estendidos podem ser emulados em dispositivos que dão
suporte ao conjunto mínimo. Isso indica o fornecimento de uma implementação padrão.
Para os dispositivos com mais funcionalidades internas, o software do dispositivo usaria
os recursos nativos. Para outras luzes, eles podem optar por implementar a interface e
usar a implementação padrão.

Os membros de interface padrão são uma solução melhor para esse cenário do que os
métodos de extensão. Os autores de classe podem controlar quais interfaces eles
escolhem implementar. Essas interfaces escolhidas estão disponíveis como métodos.
Além disso, como os métodos de interface padrão são virtuais por padrão, a expedição
de método sempre escolhe a implementação na classe.

Vamos criar o código para demonstrar essas diferenças.

Criar interfaces
Comece criando a interface que define o comportamento de todas as luzes:

C#
public interface ILight
{
void SwitchOn();
void SwitchOff();
bool IsOn();
}

Uma luminária suspensa básica pode implementar essa interface, conforme mostrado
no seguinte código:

C#

public class OverheadLight : ILight


{
private bool isOn;
public bool IsOn() => isOn;
public void SwitchOff() => isOn = false;
public void SwitchOn() => isOn = true;

public override string ToString() => $"The light is {(isOn ? "on" :


"off")}";
}

Neste tutorial, o código não conduz os dispositivos IoT, mas emula essas atividades
gravando mensagens no console. É possível explorar o código sem automatizar sua
casa.

Em seguida, vamos definir a interface para uma luz que pode ser desativada
automaticamente após um tempo limite:

C#

public interface ITimerLight : ILight


{
Task TurnOnFor(int duration);
}

Você pode adicionar uma implementação básica à luz suspensa, mas uma solução
melhor é modificar essa definição de interface para fornecer uma implementação
padrão virtual :

C#

public interface ITimerLight : ILight


{
public async Task TurnOnFor(int duration)
{
Console.WriteLine("Using the default interface method for the
ITimerLight.TurnOnFor.");
SwitchOn();
await Task.Delay(duration);
SwitchOff();
Console.WriteLine("Completed ITimerLight.TurnOnFor sequence.");
}
}

A classe OverheadLight pode implementar a função de temporizador declarando


suporte à interface:

C#

public class OverheadLight : ITimerLight { }

Um tipo de luz diferente pode dar suporte a um protocolo mais sofisticado. Ele pode
fornecer sua própria implementação para TurnOnFor , conforme mostrado no seguinte
código:

C#

public class HalogenLight : ITimerLight


{
private enum HalogenLightState
{
Off,
On,
TimerModeOn
}

private HalogenLightState state;


public void SwitchOn() => state = HalogenLightState.On;
public void SwitchOff() => state = HalogenLightState.Off;
public bool IsOn() => state != HalogenLightState.Off;
public async Task TurnOnFor(int duration)
{
Console.WriteLine("Halogen light starting timer function.");
state = HalogenLightState.TimerModeOn;
await Task.Delay(duration);
state = HalogenLightState.Off;
Console.WriteLine("Halogen light finished custom timer function");
}

public override string ToString() => $"The light is {state}";


}

Ao contrário da substituição de métodos de classe virtual, a declaração de TurnOnFor na


classe HalogenLight não usa a palavra-chave override .
Recursos combinados
As vantagens dos métodos de interface padrão ficam mais claras à medida que você
introduz recursos mais avançados. O uso de interfaces permite combinar recursos.
Também permite que cada autor de classe escolha entre uma implementação padrão ou
uma implementação personalizada. Vamos adicionar uma interface com uma
implementação padrão para uma luz piscando:

C#

public interface IBlinkingLight : ILight


{
public async Task Blink(int duration, int repeatCount)
{
Console.WriteLine("Using the default interface method for
IBlinkingLight.Blink.");
for (int count = 0; count < repeatCount; count++)
{
SwitchOn();
await Task.Delay(duration);
SwitchOff();
await Task.Delay(duration);
}
Console.WriteLine("Done with the default interface method for
IBlinkingLight.Blink.");
}
}

A implementação padrão permite que qualquer luz pisque. A luz suspensa pode
adicionar recursos de temporizador e piscar usando a implementação padrão:

C#

public class OverheadLight : ILight, ITimerLight, IBlinkingLight


{
private bool isOn;
public bool IsOn() => isOn;
public void SwitchOff() => isOn = false;
public void SwitchOn() => isOn = true;

public override string ToString() => $"The light is {(isOn ? "on" :


"off")}";
}

Um novo tipo de luz LEDLight , dá suporte à função de temporizador e à função de


piscar diretamente. Esse estilo de luz implementa as interfaces ITimerLight e
IBlinkingLight e substitui o método Blink :
C#

public class LEDLight : IBlinkingLight, ITimerLight, ILight


{
private bool isOn;
public void SwitchOn() => isOn = true;
public void SwitchOff() => isOn = false;
public bool IsOn() => isOn;
public async Task Blink(int duration, int repeatCount)
{
Console.WriteLine("LED Light starting the Blink function.");
await Task.Delay(duration * repeatCount);
Console.WriteLine("LED Light has finished the Blink function.");
}

public override string ToString() => $"The light is {(isOn ? "on" :


"off")}";
}

Um ExtraFancyLight pode dar suporte diretamente a funções de piscar e temporizador:

C#

public class ExtraFancyLight : IBlinkingLight, ITimerLight, ILight


{
private bool isOn;
public void SwitchOn() => isOn = true;
public void SwitchOff() => isOn = false;
public bool IsOn() => isOn;
public async Task Blink(int duration, int repeatCount)
{
Console.WriteLine("Extra Fancy Light starting the Blink function.");
await Task.Delay(duration * repeatCount);
Console.WriteLine("Extra Fancy Light has finished the Blink
function.");
}
public async Task TurnOnFor(int duration)
{
Console.WriteLine("Extra Fancy light starting timer function.");
await Task.Delay(duration);
Console.WriteLine("Extra Fancy light finished custom timer
function");
}

public override string ToString() => $"The light is {(isOn ? "on" :


"off")}";
}

O HalogenLight criado anteriormente não dá suporte a piscar. Portanto, não adicione


IBlinkingLight à lista de interfaces com suporte.
Detectar os tipos de luz usando a
correspondência de padrões
Em seguida, vamos gravar um código de teste. Você pode usar o recurso de
correspondência de padrões do C# para determinar as funcionalidades de uma luz
examinando quais interfaces ele dá suporte. O método a seguir exercita as
funcionalidades com suporte de cada luz:

C#

private static async Task TestLightCapabilities(ILight light)


{
// Perform basic tests:
light.SwitchOn();
Console.WriteLine($"\tAfter switching on, the light is {(light.IsOn() ?
"on" : "off")}");
light.SwitchOff();
Console.WriteLine($"\tAfter switching off, the light is {(light.IsOn() ?
"on" : "off")}");

if (light is ITimerLight timer)


{
Console.WriteLine("\tTesting timer function");
await timer.TurnOnFor(1000);
Console.WriteLine("\tTimer function completed");
}
else
{
Console.WriteLine("\tTimer function not supported.");
}

if (light is IBlinkingLight blinker)


{
Console.WriteLine("\tTesting blinking function");
await blinker.Blink(500, 5);
Console.WriteLine("\tBlink function completed");
}
else
{
Console.WriteLine("\tBlink function not supported.");
}
}

O código a seguir no método Main cria cada tipo de luz na sequência e testa essa luz:

C#

static async Task Main(string[] args)


{
Console.WriteLine("Testing the overhead light");
var overhead = new OverheadLight();
await TestLightCapabilities(overhead);
Console.WriteLine();

Console.WriteLine("Testing the halogen light");


var halogen = new HalogenLight();
await TestLightCapabilities(halogen);
Console.WriteLine();

Console.WriteLine("Testing the LED light");


var led = new LEDLight();
await TestLightCapabilities(led);
Console.WriteLine();

Console.WriteLine("Testing the fancy light");


var fancy = new ExtraFancyLight();
await TestLightCapabilities(fancy);
Console.WriteLine();
}

Como o compilador determina a melhor


implementação
Esse cenário mostra uma interface base sem implementações. A adição de um método à
interface ILight , introduz novas complexidades. As regras de linguagem que regem os
métodos de interface padrão minimizam o efeito nas classes concretas que
implementam várias interfaces derivadas. Vamos aprimorar a interface original com um
novo método para demonstrar como isso altera seu uso. Cada luz do indicador pode
relatar seu status de energia como um valor enumerado:

C#

public enum PowerStatus


{
NoPower,
ACPower,
FullBattery,
MidBattery,
LowBattery
}

A implementação padrão pressupõe que não há energia:

C#

public interface ILight


{
void SwitchOn();
void SwitchOff();
bool IsOn();
public PowerStatus Power() => PowerStatus.NoPower;
}

Essas alterações são compiladas de forma limpa, embora ExtraFancyLight declare


suporte para a interface ILight e as interfaces derivadas ITimerLight e IBlinkingLight .
Há apenas uma implementação "mais próxima" declarada na interface ILight . Qualquer
classe que declarasse uma substituição se tornaria a única implementação "mais
próxima". Você viu exemplos nas classes anteriores que substituíram os membros de
outras interfaces derivadas.

Evite substituir o mesmo método em várias interfaces derivadas. Isso cria uma chamada
de método ambíguo sempre que uma classe implementa ambas as interfaces derivadas.
O compilador não pode escolher um único método, logo, um erro é gerado. Por
exemplo, se IBlinkingLight e ITimerLight implementaram uma substituição de
PowerStatus , OverheadLight precisaria fornecer uma substituição mais específica. Caso
contrário, o compilador não poderá escolher entre as implementações nas duas
interfaces derivadas. Este cenário é mostrado no seguinte diagrama:
« interface »
ILight

PowerStatus

« interface » « interface »
IBlinkingLight ITimerLight

PowerStatus PowerStatus

OverheadLight

O diagrama anterior ilustra a ambiguidade. OverheadLight não fornece uma


implementação de ILight.PowerStatus . Ambos IBlinkingLight e ITimerLight forneçam
substituições mais específicas. Uma chamada para ILight.PowerStatus em uma
instância de OverheadLight é ambígua. Você precisa adicionar uma nova substituição
em OverheadLight para resolver a ambiguidade.
Normalmente, você pode evitar essa situação mantendo as definições de interface
pequenas e focadas em um recurso. Nesse cenário, cada funcionalidade de uma luz é a
própria interface, e várias interfaces são herdadas apenas por classes.

Este exemplo mostra um cenário em que você pode definir recursos discretos que
podem ser misturados em classes. Você declara qualquer conjunto de funcionalidades
com suporte declarando a quais interfaces uma classe dá suporte. O uso de métodos de
interface padrão virtual permite que as classes usem ou definam uma implementação
diferente para um ou todos os métodos de interface. Essa funcionalidade de linguagem
fornece novas maneiras de modelar os sistemas do mundo real sendo criados. Os
métodos de interface padrão fornecem uma maneira mais clara de expressar classes
relacionadas que podem combinar diferentes recursos usando implementações virtuais
dessas funcionalidades.

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
Expression Trees
Article • 05/29/2024

Expression trees represent code in a tree-like data structure, where each node is an
expression, for example, a method call or a binary operation such as x < y .

If you used LINQ, you have experience with a rich library where the Func types are part
of the API set. (If you aren't familiar with LINQ, you probably want to read the LINQ
tutorial and the article about lambda expressions before this one.) Expression Trees
provide richer interaction with the arguments that are functions.

You write function arguments, typically using Lambda Expressions, when you create
LINQ queries. In a typical LINQ query, those function arguments are transformed into a
delegate the compiler creates.

You already write code that uses Expression trees. Entity Framework's LINQ APIs accept
Expression trees as the arguments for the LINQ Query Expression Pattern. That enables
Entity Framework to translate the query you wrote in C# into SQL that executes in the
database engine. Another example is Moq , which is a popular mocking framework for
.NET.

When you want to have a richer interaction, you need to use Expression Trees. Expression
Trees represent code as a structure that you examine, modify, or execute. These tools
give you the power to manipulate code during run time. You write code that examines
running algorithms, or injects new capabilities. In more advanced scenarios, you modify
running algorithms and even translate C# expressions into another form for execution in
another environment.

You compile and run code represented by expression trees. Building and running
expression trees enables dynamic modification of executable code, the execution of
LINQ queries in various databases, and the creation of dynamic queries. For more
information about expression trees in LINQ, see How to use expression trees to build
dynamic queries.

Expression trees are also used in the dynamic language runtime (DLR) to provide
interoperability between dynamic languages and .NET and to enable compiler writers to
emit expression trees instead of Microsoft intermediate language (CIL). For more
information about the DLR, see Dynamic Language Runtime Overview.

You can have the C# or Visual Basic compiler create an expression tree for you based on
an anonymous lambda expression, or you can create expression trees manually by using
the System.Linq.Expressions namespace.
When a lambda expression is assigned to a variable of type Expression<TDelegate>, the
compiler emits code to build an expression tree that represents the lambda expression.

The following code examples demonstrate how to have the C# compiler create an
expression tree that represents the lambda expression num => num < 5 .

C#

Expression<Func<int, bool>> lambda = num => num < 5;

You create expression trees in your code. You build the tree by creating each node and
attaching the nodes into a tree structure. You learn how to create expressions in the
article on building expression trees.

Expression trees are immutable. If you want to modify an expression tree, you must
construct a new expression tree by copying the existing one and replacing nodes in it.
You use an expression tree visitor to traverse the existing expression tree. For more
information, see the article on translating expression trees.

Once you build an expression tree, you execute the code represented by the expression
tree.

Limitations
The C# compiler generates expression trees only from expression lambdas (or single-
line lambdas). It can't parse statement lambdas (or multi-line lambdas). For more
information about lambda expressions in C#, see Lambda Expressions.

There are some newer C# language elements that don't translate well into expression
trees. Expression trees can't contain await expressions, or async lambda expressions.
Many of the features added in C# 6 and later don't appear exactly as written in
expression trees. Instead, newer features are exposed in expression trees in the
equivalent, earlier syntax, where possible. Other constructs aren't available. It means that
code that interprets expression trees works the same when new language features are
introduced. However, even with these limitations, expression trees do enable you to
create dynamic algorithms that rely on interpreting and modifying code that is
represented as a data structure. It enables rich libraries such as Entity Framework to
accomplish what they do.

Expression trees won't support new expression node types. It would be a breaking
change for all libraries interpreting expression trees to introduce new node types. The
following list includes most C# language elements that can't be used:
Conditional methods removed from the output
base access
Method group expressions, including address-of (&) a method group, and
anonymous method expressions
References to local functions
Statements, including assignment ( = ) and statement bodied expressions
Partial methods with only a defining declaration
Unsafe pointer operations
dynamic operations
Coalescing operators with null or default literal left side, null coalescing
assignment, and the null propagating operator (?.)
Multi-dimensional array initializers, indexed properties, and dictionary initializers
Collection expressions
throw expressions
Accessing static virtual or abstract interface members
Lambda expressions that have attributes
Interpolated strings
UTF-8 string conversions or UTF-8 string literals
Method invocations using variable arguments, named arguments, or optional
arguments
Expressions using System.Index or System.Range, index "from end" (^) operator or
range expressions (..)
async lambda expressions or await expressions, including await foreach and await
using
Tuple literals, tuple conversions, tuple == or !=, or with expressions
Discards (_), deconstructing assignment, pattern matching is operator, or the
pattern matching switch expression
COM call with ref omitted on the arguments
ref, in or out parameters, ref return values, out arguments, or any values of ref
struct type
Árvores de expressão – Dados que
definem o código
Artigo • 16/03/2023

Uma Árvore de expressão é uma estrutura de dados que define o código. As árvores de
expressão se baseiam nas mesmas estruturas que um compilador usa para analisar o
código e gerar a saída compilada. Ao ler este artigo, você notará certa semelhança entre
as árvores de expressão e os tipos usados nas APIs Roslyn para criar Analyzers e
CodeFixes . (Os Analyzers e os CodeFixes são pacotes NuGet que executam análise
estática no código e sugerem possíveis correções para um desenvolvedor.) Os conceitos
são semelhantes e o resultado final é uma estrutura de dados que permite o exame do
código-fonte de maneira significativa. No entanto, as árvores de expressão são
baseadas em um conjunto de classes e APIs totalmente diferentes das APIs Roslyn. Aqui
está uma linha de código:

C#

var sum = 1 + 2;

Ao analisar o código anterior como uma árvore de expressão, é possível perceber que a
árvore contém vários nós. O nó mais externo é uma instrução de declaração de variável
com atribuição ( var sum = 1 + 2; ). Esse nó mais externo contém vários nós filho: uma
declaração de variável, um operador de atribuição e uma expressão que representa o
lado direito do sinal de igual. Essa expressão é ainda subdividida em expressões que
representam a operação de adição e os operandos esquerdo e direito da adição.

Vamos detalhar um pouco mais as expressões que compõem o lado direito do sinal de
igual. A expressão é 1 + 2 , uma expressão binária. Mais especificamente, ela é uma
expressão de adição binária. Uma expressão de adição binária tem dois filhos, que
representam os nós esquerdo e direito da expressão de adição. Aqui, ambos os nós são
expressões constantes: o operando esquerdo é o valor 1 e o operando direito é o valor
2.

Visualmente, a declaração inteira é uma árvore: você pode começar no nó raiz e viajar
até cada nó da árvore para ver o código que constitui a instrução:

Instrução de declaração de variável com atribuição ( var sum = 1 + 2; )


Declaração de tipo de variável implícita ( var sum )
Palavra-chave var implícita ( var )
Declaração de nome de variável ( sum )
Operador de atribuição ( = )
Expressão de adição binária ( 1 + 2 )
Operando esquerdo ( 1 )
Operador de adição ( + )
Operando direito ( 2 )

A árvore anterior pode parecer complicada, mas é muito eficiente. Seguindo o mesmo
processo, é possível decompor expressões muito mais complicadas. Considere esta
expressão:

C#

var finalAnswer = this.SecretSauceFunction(


currentState.createInterimResult(), currentState.createSecondValue(1,
2),
decisionServer.considerFinalOptions("hello")) +
MoreSecretSauce('A', DateTime.Now, true);

A expressão anterior também é uma declaração de variável com uma atribuição. Neste
exemplo, o lado direito da atribuição é uma árvore muito mais complicada. Você não vai
decompor essa expressão, mas considere quais seriam os diferentes nós. Há chamadas
de método usando o objeto atual como receptor, uma com um receptor this explícito
e outra não. Há chamadas de método usando outros objetos receptores, há argumentos
constantes de tipos diferentes. E, por fim, há um operador de adição binário.
Dependendo do tipo de retorno de SecretSauceFunction() ou MoreSecretSauce() , esse
operador de adição binária pode ser uma chamada de método para um operador de
adição substituído, resolvendo em uma chamada de método estático ao operador de
adição binária definido para uma classe.

Apesar da complexidade, a expressão anterior cria uma estrutura de árvore tão fácil de
se navegar quanto a do primeiro exemplo. Você continua percorrendo os nós filho para
encontrar os nós folha na expressão. Os nós pai terão referências aos filhos, sendo que
cada nó tem uma propriedade que descreve o tipo de nó.

A estrutura de uma árvore de expressão é muito consistente. Depois de aprender os


conceitos básicos, você entenderá até mesmo o código mais complexo, quando ele for
representado como uma árvore de expressão. A elegância na estrutura de dados explica
como o compilador C# analisa os programas em C# mais complexos e cria a saída
apropriada desse código-fonte complicado.

Depois de se familiarizar com a estrutura das árvores de expressão, descobrirá que o


conhecimento adquirido permitirá que você trabalhe com muitos outros cenários ainda
mais avançados. O potencial das árvores de expressão é incrível.
Além de converter algoritmos para serem executados em outros ambientes, com as
árvores de expressão, você pode escrever facilmente algoritmos que inspecionam o
código antes de executá-lo. Você escreverá um método cujos argumentos são
expressões e, depois, examinará essas expressões antes de executar o código. A árvore
de expressão é uma representação completa do código: é possível ver os valores de
qualquer subexpressão. Você vê os nomes dos métodos e das propriedades. Você vê o
valor de qualquer expressão de constante. Você converte uma árvore de expressão em
um delegado executável e executa o código.

As APIs para árvores de expressão permitem criar árvores que representam quase todos
os constructos de código válidos. No entanto, para manter as coisas o mais simples
possível, não é possível criar algumas expressões em C# em uma árvore de expressão.
Um exemplo são as expressões assíncronas (usando as palavras-chave async e await ).
Se suas necessidades requerem algoritmos assíncronos, você precisa manipular
diretamente os objetos Task , em vez de contar com o suporte do compilador. Outro
exemplo é na criação de loops. Normalmente, você cria esses loops usando for ,
foreach , while ou do . Como você verá mais adiante nesta série, as APIs para árvores de

expressão dão suporte a uma expressão de loop individual, com expressões break e
continue controlando a repetição do loop.

A única coisa que você não pode fazer é modificar uma árvore de expressão. As árvores
de expressão são estruturas de dados imutáveis. Se quiser modificar (alterar) uma árvore
de expressão, você deverá criar uma nova árvore, que seja uma cópia da original, com
as alterações desejadas.

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.
Suporte ao runtime do .NET para
árvores de expressão
Artigo • 16/03/2023

Há uma grande lista de classes no runtime do .NET que funcionam com árvores de
expressão. Veja a lista completa em System.Linq.Expressions. Em vez de enumerar a lista
completa, vamos entender como as classes de runtime foram projetadas.

No design de linguagem, uma expressão é um corpo de código que calcula e retorna


um valor. As expressões podem ser muito simples: a expressão constante 1 retorna o
valor constante de 1. Elas podem ser mais complicados: a expressão (-B +
Math.Sqrt(B*B - 4 * A * C)) / (2 * A) retorna uma raiz de uma equação quadrática

(no caso em que a equação tem uma solução).

System.Linq.Expression e tipos derivados


Uma das complexidades de se trabalhar com árvores de expressão é que muitos tipos
diferentes de expressões são válidos em muitos locais nos programas. Considere uma
expressão de atribuição. O lado direito de uma atribuição pode ser um valor constante,
uma variável, uma expressão de chamada de método ou outros. Essa flexibilidade de
linguagem significa que você poderá encontrar muitos tipos diferentes de expressão em
qualquer lugar nos nós de uma árvore ao percorrer uma árvore de expressão. Portanto,
a maneira mais simples de se trabalhar é quando você pode trabalhar com o tipo de
expressão de base. No entanto, às vezes você precisa saber mais. A classe Expressão de
base contém uma propriedade NodeType para essa finalidade. Ela retorna um
ExpressionType que é uma enumeração dos tipos de expressão possíveis. Quando você

sabe qual é o tipo do nó, pode convertê-la para esse tipo e executar ações específicas,
conhecendo o tipo do nó de expressão. Você pode pesquisar determinados tipos de nó
e trabalhar com as propriedades específicas desse tipo de expressão.

Por exemplo, esse código imprimirá o nome de uma variável para uma expressão de
acesso variável. O código abaixo mostra a prática de verificar o tipo de nó para
convertê-lo em uma expressão de acesso variável e verificar as propriedades do tipo de
expressão específico:

C#

Expression<Func<int, int>> addFive = (num) => num + 5;

if (addFive is LambdaExpression lambdaExp)


{
var parameter = lambdaExp.Parameters[0]; -- first

Console.WriteLine(parameter.Name);
Console.WriteLine(parameter.Type);
}

Criar árvores de expressão


A classe System.Linq.Expression também contém vários métodos estáticos para criar
expressões. Esses métodos criam um nó de expressão usando os argumentos fornecidos
para seus filhos. Dessa forma, você cria uma expressão com os nós folha. Por exemplo,
esse código cria uma expressão de Adicionar:

C#

// Addition is an add expression for "1 + 2"


var one = Expression.Constant(1, typeof(int));
var two = Expression.Constant(2, typeof(int));
var addition = Expression.Add(one, two);

Você pode ver neste exemplo simples que há muitos tipos envolvidos na criação e no
trabalho com árvores de expressão. Essa complexidade é necessária para fornecer os
recursos do vocabulário avançado fornecido pela linguagem C#.

Navegar pelas APIs


Há tipos de Nó de expressão que mapeiam para quase todos os elementos de sintaxe
da linguagem C#. Cada tipo tem métodos específicos para esse tipo de elemento de
linguagem. É muita coisa para guardar na memória ao mesmo tempo. Em vez de tentar
memorizar tudo, confira técnicas úteis para trabalhar com árvores de expressão:

1. Observar os membros da enumeração ExpressionType para determinar possíveis


nós que devem ser examinados. Essa lista é útil se você deseja percorrer e
entender uma árvore de expressão.
2. Observar os membros estáticos da classe Expression para compilar uma
expressão. Esses métodos podem compilar qualquer tipo de expressão em um
conjunto de seu nós filho.
3. Examinar a classe ExpressionVisitor para compilar uma árvore de expressão
modificada.
Você encontrará mais informações ao examinar cada uma dessas três áreas. Você
sempre encontrará o que precisa ao começar com uma dessas três etapas.

6 Collaborate with us on
GitHub .NET feedback
The source for this content can The .NET documentation is open
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.
Executar árvores de expressão
Artigo • 16/03/2023

Uma árvore de expressão é uma estrutura de dados que representa algum código. Não
se trata de código compilado nem executável. Se você quiser executar o código do .NET
representado por uma árvore de expressão, precisará convertê-lo em instruções IL
executáveis. Executar uma árvore de expressão pode retornar um valor ou apenas
realizar uma ação, como chamar um método.

Somente árvores de expressão que representam expressões lambda podem ser


executadas. Árvores de expressão que representam expressões lambda são do tipo
LambdaExpression ou Expression<TDelegate>. Para executar essas árvores de
expressão, chame o método Compile para criar um delegado executável e, em seguida,
invoque o delegado.

7 Observação

Se o tipo de delegado não for conhecido, ou seja, se a expressão lambda for do


tipo LambdaExpression e não Expression<TDelegate>, chame o método
DynamicInvoke no delegado em vez de invocá-la diretamente.

Se uma árvore de expressão não representa uma expressão lambda, você pode criar
uma expressão lambda que tenha a árvore de expressão original como corpo. Para isso,
chame o método Lambda<TDelegate>(Expression,
IEnumerable<ParameterExpression>). Em seguida, você pode executar a expressão
lambda como descrito anteriormente nesta seção.

Expressões lambda para funções


Você pode converter qualquer LambdaExpression ou qualquer tipo derivado de
LambdaExpression em IL executável. Outros tipos de expressões não podem ser
convertidos diretamente em código. Essa restrição tem pouco efeito na prática. As
expressões lambda são os únicos tipos de expressões que você gostaria de executar
convertendo em IL (linguagem intermediária) executável. (Pense no que significaria
executar diretamente um System.Linq.Expressions.ConstantExpression. Significaria algo
útil?) Qualquer árvore de expressão, que seja um
System.Linq.Expressions.LambdaExpression ou um tipo derivado de LambdaExpression ,
pode ser convertida em IL. O tipo de expressão
System.Linq.Expressions.Expression<TDelegate> é o único exemplo concreto nas
bibliotecas do .NET Core. Ele é usado para representar uma expressão que mapeia para
qualquer tipo delegado. Como esse tipo mapeia para um tipo delegado, o .NET pode
examinar a expressão e gerar a IL para um delegado apropriado que corresponda à
assinatura da expressão lambda. O tipo delegado é baseado no tipo de expressão. Você
deve conhecer o tipo de retorno e a lista de argumentos se quiser usar o objeto
delegado de maneira fortemente tipada. O método LambdaExpression.Compile() retorna
o tipo Delegate . Você precisará convertê-lo no tipo delegado correto para fazer com
que as ferramentas de tempo de compilação verifiquem a lista de argumentos ou o tipo
de retorno.

Na maioria dos casos, existe um mapeamento simples entre uma expressão e o


delegado correspondente. Por exemplo, uma árvore de expressão representada por
Expression<Func<int>> seria convertida em um delegado do tipo Func<int> . Para uma

expressão lambda com qualquer tipo de retorno e lista de argumentos, existe um tipo
delegado que é o tipo de destino para o código executável representado por essa
expressão lambda.

O tipo System.Linq.Expressions.LambdaExpression contém membros


LambdaExpression.Compile e LambdaExpression.CompileToMethod que você usaria
para converter uma árvore de expressão em código executável. O método Compile cria
um delegado. O método CompileToMethod atualiza um objeto
System.Reflection.Emit.MethodBuilder com a IL que representa a saída compilada da
árvore de expressão.

) Importante

CompileToMethod só está disponível no .NET Framework, e não no .NET Core nem

no .NET 5 e posterior.

Como opção, você também pode fornecer um


System.Runtime.CompilerServices.DebugInfoGenerator que receberá as informações de
depuração de símbolo para o objeto delegado gerado. O DebugInfoGenerator fornece
informações completas de depuração sobre o delegado gerado.

Uma expressão seria convertida em um delegado usando o seguinte código:

C#

Expression<Func<int>> add = () => 1 + 2;


var func = add.Compile(); // Create Delegate
var answer = func(); // Invoke Delegate
Console.WriteLine(answer);
O exemplo de código a seguir demonstra os tipos concretos usados ao compilar e
executar uma árvore de expressão.

C#

Expression<Func<int, bool>> expr = num => num < 5;

// Compiling the expression tree into a delegate.


Func<int, bool> result = expr.Compile();

// Invoking the delegate and writing the result to the console.


Console.WriteLine(result(4));

// Prints True.

// You can also use simplified syntax


// to compile and run an expression tree.
// The following line can replace two previous statements.
Console.WriteLine(expr.Compile()(4));

// Also prints True.

O exemplo de código a seguir demonstra como executar uma árvore de expressão que
representa a elevação de um número a uma potência, criando uma expressão lambda e
executando-a. O resultado, representado pelo número elevado à potência, é exibido.

C#

// The expression tree to execute.


BinaryExpression be = Expression.Power(Expression.Constant(2d),
Expression.Constant(3d));

// Create a lambda expression.


Expression<Func<double>> le = Expression.Lambda<Func<double>>(be);

// Compile the lambda expression.


Func<double> compiledExpression = le.Compile();

// Execute the lambda expression.


double result = compiledExpression();

// Display the result.


Console.WriteLine(result);

// This code produces the following output:


// 8

Execução e tempos de vida


O código é executado ao invocar o delegado que foi criado quando você chamou
LambdaExpression.Compile() . O código anterior, add.Compile() , retorna um delegado.

Você invoca esse delegado chamando func() , que executa o código.

Esse delegado representa o código na árvore de expressão. Você pode reter o


identificador para esse delegado e invocá-lo mais tarde. Você não precisa compilar a
árvore de expressão sempre que deseja executar o código que ela representa. (Lembre-
se que as árvores de expressão são imutáveis e compilar a mesma árvore de expressão
mais tarde, criará um delegado que executa o mesmo código.)

U Cuidado

Não crie mecanismos de cache mais sofisticados para aumentar o desempenho


evitando chamadas de compilação desnecessárias. Comparar duas árvores de
expressão arbitrárias para determinar se elas representam o mesmo algoritmo é
uma operação demorada. O tempo de computação que você economiza evitando
chamadas extras a LambdaExpression.Compile() provavelmente é maior que o
consumido pelo tempo de execução do código que determina se as duas árvores
de expressão diferentes resultam no mesmo código executável.

Advertências
A compilação de uma expressão lambda para um delegado e invocar esse delegado é
uma das operações mais simples que você pode realizar com uma árvore de expressão.
No entanto, mesmo com essa operação simples, há limitações que você deve estar
ciente.

As expressões lambda criam fechamentos sobre todas as variáveis locais que são
referenciadas na expressão. Você deve assegurar que todas as variáveis que farão parte
do delegado são utilizáveis no local em que você chamar Compile e no momento em
que você executar o delegado resultante. O compilador garante que as variáveis
estejam no escopo. No entanto, se a sua expressão acessa uma variável que implementa
IDisposable , é possível que o código descarte o objeto enquanto ele ainda é mantido

pela árvore de expressão.

Por exemplo, esse código funciona bem, porque int não implementa IDisposable :

C#

private static Func<int, int> CreateBoundFunc()


{
var constant = 5; // constant is captured by the expression tree
Expression<Func<int, int>> expression = (b) => constant + b;
var rVal = expression.Compile();
return rVal;
}

O delegado capturou uma referência à variável local constant . Essa variável é acessada
a qualquer momento mais tarde, quando a função retornada por CreateBoundFunc for
executada.

No entanto, considere a seguinte classe (bastante artificial) que implementa


System.IDisposable:

C#

public class Resource : IDisposable


{
private bool _isDisposed = false;
public int Argument
{
get
{
if (!_isDisposed)
return 5;
else throw new ObjectDisposedException("Resource");
}
}

public void Dispose()


{
_isDisposed = true;
}
}

Se você a usar em uma expressão, conforme mostrado no seguinte código, obterá uma
System.ObjectDisposedException ao executar o código referenciado pela propriedade
Resource.Argument :

C#

private static Func<int, int> CreateBoundResource()


{
using (var constant = new Resource()) // constant is captured by the
expression tree
{
Expression<Func<int, int>> expression = (b) => constant.Argument +
b;
var rVal = expression.Compile();
return rVal;
}
}

O delegado retornado desse método fechou sobre o objeto constant , que foi
descartado. (Foi descartado, porque foi declarado em uma instrução using ).

Agora, ao executar o delegado retornado por esse método, uma


ObjectDisposedException será gerada no ponto de execução.

Parece estranho ter um erro de runtime representando um constructo de tempo de


compilação, mas é isso que ocorre quando trabalha com árvores de expressão.

Há várias permutações desse problema, portanto é difícil oferecer diretrizes gerais para
evitá-lo. Tenha cuidado ao acessar variáveis locais quando define expressões e ao
acessar o estado no objeto atual (representado por this ) quando cria uma árvore de
expressão retornada por meio de uma API pública.

O código na sua expressão pode referenciar métodos ou propriedades em outros


assemblies. Esse assembly deve estar acessível quando a expressão for definida, quando
ela for compilada e quando o delegado resultante for invocado. Você é recebido com
um ReferencedAssemblyNotFoundException quando ele não está presente.

Resumo
As árvores de expressão que representam expressões lambda podem ser compiladas
para criar um delegado que pode ser executado. As árvores de expressão fornecem um
mecanismo para executar o código representado por uma árvore de expressão.

A árvore de expressão representa o código que seria executado para qualquer


constructo específico que você criar. Contanto que o ambiente em que você compilar e
executar o código corresponda ao ambiente em que você criar a expressão, tudo
funcionará conforme o esperado. Quando isso não acontece, os erros são previsíveis e
capturados nos primeiros testes de qualquer código que usam as árvores de expressão.

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.
Interpretar expressões
Artigo • 16/03/2023

O exemplo de código a seguir demonstra como a árvore de expressão que representa a


expressão lambda num => num < 5 pode ser decomposta em suas partes.

C#

// Add the following using directive to your code file:


// using System.Linq.Expressions;

// Create an expression tree.


Expression<Func<int, bool>> exprTree = num => num < 5;

// Decompose the expression tree.


ParameterExpression param = (ParameterExpression)exprTree.Parameters[0];
BinaryExpression operation = (BinaryExpression)exprTree.Body;
ParameterExpression left = (ParameterExpression)operation.Left;
ConstantExpression right = (ConstantExpression)operation.Right;

Console.WriteLine("Decomposed expression: {0} => {1} {2} {3}",


param.Name, left.Name, operation.NodeType, right.Value);

// This code produces the following output:

// Decomposed expression: num => num LessThan 5

Agora, vamos escrever código para examinar a estrutura de um árvore de expressão.


Cada nó em uma árvore de expressão é um objeto de uma classe derivada de
Expression .

Esse design faz com que visitar todos os nós em uma árvore de expressão seja uma
operação recursiva relativamente simples. A estratégia geral é iniciar no nó raiz e
determine que tipo de nó ele é.

Se o tipo de nó tiver filhos, visite os filhos recursivamente. Em cada nó filho, repita o


processo usado no nó raiz: determine o tipo e, se o tipo tiver filhos, visite cada um dos
filhos.

Examinar uma expressão sem filhos


Vamos começar visitando cada nó em uma árvore de expressão simples. Este é o código
que cria uma expressão constante e, em seguida, examina suas propriedades:

C#
var constant = Expression.Constant(24, typeof(int));

Console.WriteLine($"This is a/an {constant.NodeType} expression type");


Console.WriteLine($"The type of the constant value is {constant.Type}");
Console.WriteLine($"The value of the constant value is {constant.Value}");

O código anterior gera a seguinte saída:

Saída

This is a/an Constant expression type


The type of the constant value is System.Int32
The value of the constant value is 24

Agora, vamos escrever o código que examinaria essa expressão e escrever algumas
propriedades importantes sobre ele.

Expressão de adição
Vamos começar com o exemplo de adição da introdução desta seção.

C#

Expression<Func<int>> sum = () => 1 + 2;

7 Observação

Não use var para declarar essa árvore de expressão, pois o tipo natural do
delegado é Func<int> , e não Expression<Func<int>> .

O nó raiz é um LambdaExpression . Para obter o código interessante no lado direito do


operador => , você precisa encontrar um dos filhos de LambdaExpression . Você fará isso
com todas as expressões nesta seção. O nó pai nos ajudar a localizar o tipo de retorno
do LambdaExpression .

Para examinar cada nó nesta expressão, você precisa visitar de maneira recorrente
muitos nós. Esta é uma primeira implementação simples:

C#

Expression<Func<int, int, int>> addition = (a, b) => a + b;


Console.WriteLine($"This expression is a {addition.NodeType} expression
type");
Console.WriteLine($"The name of the lambda is {((addition.Name == null) ? "
<null>" : addition.Name)}");
Console.WriteLine($"The return type is {addition.ReturnType.ToString()}");
Console.WriteLine($"The expression has {addition.Parameters.Count}
arguments. They are:");
foreach (var argumentExpression in addition.Parameters)
{
Console.WriteLine($"\tParameter Type:
{argumentExpression.Type.ToString()}, Name: {argumentExpression.Name}");
}

var additionBody = (BinaryExpression)addition.Body;


Console.WriteLine($"The body is a {additionBody.NodeType} expression");
Console.WriteLine($"The left side is a {additionBody.Left.NodeType}
expression");
var left = (ParameterExpression)additionBody.Left;
Console.WriteLine($"\tParameter Type: {left.Type.ToString()}, Name:
{left.Name}");
Console.WriteLine($"The right side is a {additionBody.Right.NodeType}
expression");
var right = (ParameterExpression)additionBody.Right;
Console.WriteLine($"\tParameter Type: {right.Type.ToString()}, Name:
{right.Name}");

Este exemplo imprime a seguinte saída:

Saída

This expression is a/an Lambda expression type


The name of the lambda is <null>
The return type is System.Int32
The expression has 2 arguments. They are:
Parameter Type: System.Int32, Name: a
Parameter Type: System.Int32, Name: b
The body is a/an Add expression
The left side is a Parameter expression
Parameter Type: System.Int32, Name: a
The right side is a Parameter expression
Parameter Type: System.Int32, Name: b

É possível perceber muita repetição no exemplo de código anterior. Vamos limpar tudo
isso e criar um visitante de nós de expressão com uma finalidade mais geral. Para isso,
precisaremos escrever um algoritmo recursivo. Qualquer nó poderia ser de um tipo que
pode ter filhos. Qualquer nó que tem filhos exige que nós visitemos esses filhos e
determinemos o que é esse nó. Esta é a versão limpa que utiliza a recursão para visitar
as operações de adição:

C#
using System.Linq.Expressions;

namespace Visitors;
// Base Visitor class:
public abstract class Visitor
{
private readonly Expression node;

protected Visitor(Expression node) => this.node = node;

public abstract void Visit(string prefix);

public ExpressionType NodeType => node.NodeType;


public static Visitor CreateFromExpression(Expression node) =>
node.NodeType switch
{
ExpressionType.Constant => new
ConstantVisitor((ConstantExpression)node),
ExpressionType.Lambda => new
LambdaVisitor((LambdaExpression)node),
ExpressionType.Parameter => new
ParameterVisitor((ParameterExpression)node),
ExpressionType.Add => new BinaryVisitor((BinaryExpression)node),
_ => throw new NotImplementedException($"Node not processed yet:
{node.NodeType}"),
};
}

// Lambda Visitor
public class LambdaVisitor : Visitor
{
private readonly LambdaExpression node;
public LambdaVisitor(LambdaExpression node) : base(node) => this.node =
node;

public override void Visit(string prefix)


{
Console.WriteLine($"{prefix}This expression is a {NodeType}
expression type");
Console.WriteLine($"{prefix}The name of the lambda is {((node.Name
== null) ? "<null>" : node.Name)}");
Console.WriteLine($"{prefix}The return type is {node.ReturnType}");
Console.WriteLine($"{prefix}The expression has
{node.Parameters.Count} argument(s). They are:");
// Visit each parameter:
foreach (var argumentExpression in node.Parameters)
{
var argumentVisitor = CreateFromExpression(argumentExpression);
argumentVisitor.Visit(prefix + "\t");
}
Console.WriteLine($"{prefix}The expression body is:");
// Visit the body:
var bodyVisitor = CreateFromExpression(node.Body);
bodyVisitor.Visit(prefix + "\t");
}
}

// Binary Expression Visitor:


public class BinaryVisitor : Visitor
{
private readonly BinaryExpression node;
public BinaryVisitor(BinaryExpression node) : base(node) => this.node =
node;

public override void Visit(string prefix)


{
Console.WriteLine($"{prefix}This binary expression is a {NodeType}
expression");
var left = CreateFromExpression(node.Left);
Console.WriteLine($"{prefix}The Left argument is:");
left.Visit(prefix + "\t");
var right = CreateFromExpression(node.Right);
Console.WriteLine($"{prefix}The Right argument is:");
right.Visit(prefix + "\t");
}
}

// Parameter visitor:
public class ParameterVisitor : Visitor
{
private readonly ParameterExpression node;
public ParameterVisitor(ParameterExpression node) : base(node)
{
this.node = node;
}

public override void Visit(string prefix)


{
Console.WriteLine($"{prefix}This is an {NodeType} expression type");
Console.WriteLine($"{prefix}Type: {node.Type}, Name: {node.Name},
ByRef: {node.IsByRef}");
}
}

// Constant visitor:
public class ConstantVisitor : Visitor
{
private readonly ConstantExpression node;
public ConstantVisitor(ConstantExpression node) : base(node) =>
this.node = node;

public override void Visit(string prefix)


{
Console.WriteLine($"{prefix}This is an {NodeType} expression type");
Console.WriteLine($"{prefix}The type of the constant value is
{node.Type}");
Console.WriteLine($"{prefix}The value of the constant value is
{node.Value}");
}
}

Esse algoritmo é a base de um algoritmo que visita qualquer LambdaExpression


arbitrário. O código criado procura apenas uma pequena amostra dos possíveis
conjuntos de nós de árvore de expressão que ele pode encontrar. No entanto, ainda é
possível aprender bastante com o que ele produz. (O caso padrão no método
Visitor.CreateFromExpression imprime uma mensagem no console de erro quando um

novo tipo de nó é encontrado. Dessa forma, você sabe adicionar um novo tipo de
expressão.)

Ao executar esse visitante na expressão de adição anterior, você obtém a seguinte saída:

Saída

This expression is a/an Lambda expression type


The name of the lambda is <null>
The return type is System.Int32
The expression has 2 argument(s). They are:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
This is an Parameter expression type
Type: System.Int32, Name: b, ByRef: False
The expression body is:
This binary expression is a Add expression
The Left argument is:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
The Right argument is:
This is an Parameter expression type
Type: System.Int32, Name: b, ByRef: False

Agora que criou uma implementação de visitante mais geral, você pode visitar e
processar muitos tipos diferentes de expressões.

Expressão de adição com mais operandos


Vamos testar um exemplo mais complicado, mas ainda limitar os tipos de nó somente à
adição:

C#

Expression<Func<int>> sum = () => 1 + 2 + 3 + 4;


Antes de executar esses exemplos no algoritmo de visitante, tente pensar qual seria a
saída. Lembre-se de que o operador + é um operador binário: ele deve ter dois filhos,
que representam os operandos esquerdo e direito. Há várias maneiras possíveis de
construir uma árvore que podem ser corretas:

C#

Expression<Func<int>> sum1 = () => 1 + (2 + (3 + 4));


Expression<Func<int>> sum2 = () => ((1 + 2) + 3) + 4;

Expression<Func<int>> sum3 = () => (1 + 2) + (3 + 4);


Expression<Func<int>> sum4 = () => 1 + ((2 + 3) + 4);
Expression<Func<int>> sum5 = () => (1 + (2 + 3)) + 4;

Você pode ver a separação em duas possíveis respostas para realçar a mais promissora.
A primeira representa expressões associativas à direita. A segunda representa
expressões associativas à esquerda. A vantagem desses dois formatos é que o formato
pode ser dimensionado para qualquer número arbitrário de expressões de adição.

Se você executar essa expressão por meio do visitante, verá essa saída, verificando se a
expressão de adição simples é associativa à esquerda.

Para executar esse exemplo e ver a árvore de expressão completa, é preciso fazer uma
alteração na árvore de expressão de origem. Quando a árvore de expressão contém
todas as constantes, a árvore resultante contém apenas o valor constante de 10 . O
compilador executa toda a adição e reduz a expressão a sua forma mais simples.
Simplesmente adicionar uma variável à expressão é suficiente para ver a árvore original:

C#

Expression<Func<int, int>> sum = (a) => 1 + a + 3 + 4;

Crie um visitante para essa soma e execute o visitante. Você verá esta saída:

Saída

This expression is a/an Lambda expression type


The name of the lambda is <null>
The return type is System.Int32
The expression has 1 argument(s). They are:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
The expression body is:
This binary expression is a Add expression
The Left argument is:
This binary expression is a Add expression
The Left argument is:
This binary expression is a Add expression
The Left argument is:
This is an Constant expression type
The type of the constant value is
System.Int32
The value of the constant value is 1
The Right argument is:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
The Right argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 3
The Right argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 4

Você pode executar qualquer um dos outros exemplos pelo código visitante e ver que
árvore ele representa. Veja um exemplo da expressão sum3 anterior (com um parâmetro
adicional para impedir que o compilador calcule a constante):

C#

Expression<Func<int, int, int>> sum3 = (a, b) => (1 + a) + (3 + b);

Esta é a saída do visitante:

Saída

This expression is a/an Lambda expression type


The name of the lambda is <null>
The return type is System.Int32
The expression has 2 argument(s). They are:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
This is an Parameter expression type
Type: System.Int32, Name: b, ByRef: False
The expression body is:
This binary expression is a Add expression
The Left argument is:
This binary expression is a Add expression
The Left argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 1
The Right argument is:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
The Right argument is:
This binary expression is a Add expression
The Left argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 3
The Right argument is:
This is an Parameter expression type
Type: System.Int32, Name: b, ByRef: False

Observe que os parênteses não fazem parte da saída. Não há nenhum nó na árvore de
expressão que representa os parênteses na expressão de entrada. A estrutura de árvore
de expressão contém todas as informações necessárias para comunicar a precedência.

Estendendo este exemplo


O exemplo lida apenas com as árvores de expressão mais rudimentares. O código que
você viu nesta seção só lida com inteiros constantes e com o operador + binário. Como
um exemplo final, vamos atualizar o visitante para lidar com uma expressão mais
complicada. Vamos fazer com que funcione para a seguinte expressão fatorial:

C#

Expression<Func<int, int>> factorial = (n) =>


n == 0 ?
1 :
Enumerable.Range(1, n).Aggregate((product, factor) => product * factor);

Este código representa uma possível implementação da função fatorial matemática. A


maneira como você escreve o código destaca duas limitações da criação de árvores de
expressão atribuindo expressões lambda a Expressões. Primeiro, lambdas de instrução
não são permitidos. Isso significa que eu não posso usar loops, blocos, instruções if/else
nem outras estruturas de controle comuns em C#. Você só pode usar expressões. Em
segundo lugar, você não pode chamar a mesma expressão de maneira recorrente. Você
poderia se ela já fosse um delegado, mas não pode chamá-la em sua forma de árvore
de expressão. Na seção criando árvores de expressão, você aprenderá técnicas para
superar essas limitações.

Nesta expressão, você encontrará todos estes tipos de nós:

1. Igual (expressão binária)


2. Multiplicar (expressão binária)
3. Condicional (a expressão ? : )
4. Expressão de chamada de método (chamar Range() e Aggregate() )
Uma maneira de modificar o algoritmo do visitante é continuar executando-o e escrever
o tipo de nó toda vez que você atingir sua cláusula default . Após algumas iterações,
você verá cada um dos possíveis nós. Então, você tem tudo de que você precisa. O
resultado seria algo semelhante a:

C#

public static Visitor CreateFromExpression(Expression node) =>


node.NodeType switch
{
ExpressionType.Constant => new
ConstantVisitor((ConstantExpression)node),
ExpressionType.Lambda => new
LambdaVisitor((LambdaExpression)node),
ExpressionType.Parameter => new
ParameterVisitor((ParameterExpression)node),
ExpressionType.Add => new
BinaryVisitor((BinaryExpression)node),
ExpressionType.Equal => new
BinaryVisitor((BinaryExpression)node),
ExpressionType.Multiply => new BinaryVisitor((BinaryExpression)
node),
ExpressionType.Conditional => new
ConditionalVisitor((ConditionalExpression) node),
ExpressionType.Call => new
MethodCallVisitor((MethodCallExpression) node),
_ => throw new NotImplementedException($"Node not processed yet:
{node.NodeType}"),
};

O ConditionalVisitor e o MethodCallVisitor processa esses dois nós:

C#

public class ConditionalVisitor : Visitor


{
private readonly ConditionalExpression node;
public ConditionalVisitor(ConditionalExpression node) : base(node)
{
this.node = node;
}

public override void Visit(string prefix)


{
Console.WriteLine($"{prefix}This expression is a {NodeType}
expression");
var testVisitor = Visitor.CreateFromExpression(node.Test);
Console.WriteLine($"{prefix}The Test for this expression is:");
testVisitor.Visit(prefix + "\t");
var trueVisitor = Visitor.CreateFromExpression(node.IfTrue);
Console.WriteLine($"{prefix}The True clause for this expression
is:");
trueVisitor.Visit(prefix + "\t");
var falseVisitor = Visitor.CreateFromExpression(node.IfFalse);
Console.WriteLine($"{prefix}The False clause for this expression
is:");
falseVisitor.Visit(prefix + "\t");
}
}

public class MethodCallVisitor : Visitor


{
private readonly MethodCallExpression node;
public MethodCallVisitor(MethodCallExpression node) : base(node)
{
this.node = node;
}

public override void Visit(string prefix)


{
Console.WriteLine($"{prefix}This expression is a {NodeType}
expression");
if (node.Object == null)
Console.WriteLine($"{prefix}This is a static method call");
else
{
Console.WriteLine($"{prefix}The receiver (this) is:");
var receiverVisitor = Visitor.CreateFromExpression(node.Object);
receiverVisitor.Visit(prefix + "\t");
}

var methodInfo = node.Method;


Console.WriteLine($"{prefix}The method name is
{methodInfo.DeclaringType}.{methodInfo.Name}");
// There is more here, like generic arguments, and so on.
Console.WriteLine($"{prefix}The Arguments are:");
foreach (var arg in node.Arguments)
{
var argVisitor = Visitor.CreateFromExpression(arg);
argVisitor.Visit(prefix + "\t");
}
}
}

E a saída da árvore de expressão seria:

Saída

This expression is a/an Lambda expression type


The name of the lambda is <null>
The return type is System.Int32
The expression has 1 argument(s). They are:
This is an Parameter expression type
Type: System.Int32, Name: n, ByRef: False
The expression body is:
This expression is a Conditional expression
The Test for this expression is:
This binary expression is a Equal expression
The Left argument is:
This is an Parameter expression type
Type: System.Int32, Name: n, ByRef: False
The Right argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 0
The True clause for this expression is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 1
The False clause for this expression is:
This expression is a Call expression
This is a static method call
The method name is System.Linq.Enumerable.Aggregate
The Arguments are:
This expression is a Call expression
This is a static method call
The method name is System.Linq.Enumerable.Range
The Arguments are:
This is an Constant expression type
The type of the constant value is
System.Int32
The value of the constant value is 1
This is an Parameter expression type
Type: System.Int32, Name: n, ByRef: False
This expression is a Lambda expression type
The name of the lambda is <null>
The return type is System.Int32
The expression has 2 arguments. They are:
This is an Parameter expression type
Type: System.Int32, Name: product, ByRef:
False
This is an Parameter expression type
Type: System.Int32, Name: factor, ByRef:
False
The expression body is:
This binary expression is a Multiply
expression
The Left argument is:
This is an Parameter expression type
Type: System.Int32, Name: product,
ByRef: False
The Right argument is:
This is an Parameter expression type
Type: System.Int32, Name: factor,
ByRef: False
Estender a biblioteca de exemplos
Os exemplos nesta seção mostram as principais técnicas para visitar e examinar nós em
uma árvore de expressão. Ela simplifica os tipos de nós que você encontrará para se
concentrar nas tarefas principais de visitar e acessar nós em uma árvore de expressão.

Primeiro, os visitantes lidam somente com constantes que são números inteiros. Os
valores das constantes podem ser de qualquer outro tipo numérico e a linguagem C#
dá suporte a conversões e promoções entre esses tipos. Uma versão mais robusta desse
código espelharia todos esses recursos.

Até o último exemplo reconhece um subconjunto dos tipos de nó possíveis. Você ainda
pode alimentá-lo com muitas expressões que o fariam falhar. Uma implementação
completa está incluída no .NET Standard com o nome ExpressionVisitor e pode lidar
com todos os tipos de nó possíveis.

Por fim, a biblioteca usada neste artigo foi desenvolvida para demonstração e
aprendizado. Ela não está otimizada. Ela deixa as estruturas claras e realça as técnicas
usadas para visitar os nós e analisar o que está neles.

Mesmo com essas limitações, você deve estar bem no processo de escrever algoritmos
que leem e entendem árvores de expressão.

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.
Criar árvores de expressão
Artigo • 16/03/2023

O compilador C# criou todas as árvores de expressão que você viu até agora. Você criou
uma expressão lambda atribuída a uma variável digitada como um Expression<Func<T>>
ou algum tipo semelhante. Para muitos cenários, você cria uma expressão na memória
no tempo de execução.

As árvores de expressão são imutáveis. Ser imutável significa que você precisa criar a
árvore de folhas até a raiz. As APIs que você usa para criar as árvores de expressão
refletem esse fato: os métodos que você usa para criar um nó usam todos os filhos
como argumentos. Vejamos alguns exemplos para mostrar as técnicas a você.

Criar nós
Comece com a expressão de adição com a qual você tem trabalhado nestas seções:

C#

Expression<Func<int>> sum = () => 1 + 2;

Para construir essa árvore de expressão, você precisará criar os nós folha. Os nós folha
são constantes. Use o método Constant para criar os nós:

C#

var one = Expression.Constant(1, typeof(int));


var two = Expression.Constant(2, typeof(int));

Em seguida, crie a expressão de adição:

C#

var addition = Expression.Add(one, two);

Depois de criar a expressão de adição, crie a expressão lambda:

C#

var lambda = Expression.Lambda(addition);


Essa expressão lambda não contém argumentos. Mais adiante nesta seção, você verá
como mapear argumentos para parâmetros e criar expressões mais complicadas.

Para expressões como essa, você pode combinar todas as chamadas em uma só
instrução:

C#

var lambda2 = Expression.Lambda(


Expression.Add(
Expression.Constant(1, typeof(int)),
Expression.Constant(2, typeof(int))
)
);

Criar uma árvore


A seção anterior mostrou os conceitos básicos da criação de uma árvore de expressão
na memória. Árvores mais complexas geralmente significam mais tipos de nó e mais nós
na árvore. Vamos percorrer mais um exemplo e mostrar dois outros tipos de nó que
você normalmente criará quando criar árvores de expressão: os nós de argumento e os
nós de chamada de método. Vamos criar uma árvore de expressão para criar esta
expressão:

C#

Expression<Func<double, double, double>> distanceCalc =


(x, y) => Math.Sqrt(x * x + y * y);

Você começará criando expressões de parâmetro para x e y :

C#

var xParameter = Expression.Parameter(typeof(double), "x");


var yParameter = Expression.Parameter(typeof(double), "y");

A criação de expressões de multiplicação e adição segue o padrão que você já viu:

C#

var xSquared = Expression.Multiply(xParameter, xParameter);


var ySquared = Expression.Multiply(yParameter, yParameter);
var sum = Expression.Add(xSquared, ySquared);
Em seguida, você precisa criar uma expressão de chamada de método para a chamada
para Math.Sqrt .

C#

var sqrtMethod = typeof(Math).GetMethod("Sqrt", new[] { typeof(double) }) ??


throw new InvalidOperationException("Math.Sqrt not found!");
var distance = Expression.Call(sqrtMethod, sum);

A chamada GetMethod poderá retornar null se o método não for encontrado.


Provavelmente, isso ocorre porque você digitou incorretamente o nome do método.
Caso contrário, isso pode significar que o assembly necessário não está carregado. Por
fim, você colocará a chamada de método em uma expressão lambda e definirá os
argumentos para a expressão lambda:

C#

var distanceLambda = Expression.Lambda(


distance,
xParameter,
yParameter);

Neste exemplo mais complicado, é possível ver mais algumas técnicas que
frequentemente serão necessárias para criar árvores de expressão.

Primeiro, você precisa criar os objetos que representam parâmetros ou variáveis locais
antes de usá-los. Após ter criado esses objetos, você pode usá-los em sua árvore de
expressão quando for necessário.

Depois, você precisa usar um subconjunto das APIs de reflexão para criar um objeto
System.Reflection.MethodInfo para que possa criar uma árvore de expressão para
acessar esse método. Você deve se limitar ao subconjunto das APIs de reflexão que
estão disponíveis na plataforma do .NET Core. Mais uma vez, essas técnicas se
estenderão a outras árvores de expressão.

Criar código em profundidade


Você não fica limitado ao que pode criar usando essas APIs. No entanto, quanto mais
complicada for a árvore de expressão que você quer criar, mais difícil ser;a gerenciar e
ler o código.

Vamos criar uma árvore de expressão que é o equivalente a este código:


C#

Func<int, int> factorialFunc = (n) =>


{
var res = 1;
while (n > 1)
{
res = res * n;
n--;
}
return res;
};

O código anterior não compilou a árvore de expressão, mas simplesmente o delegado.


Usando a classe Expression , não é possível criar lambdas de instrução. Este é o código
que é necessário para criar a mesma funcionalidade. Não há uma API para criar um loop
while , em vez disso, você precisa criar um loop que contenha um teste condicional e

um destino de rótulo para sair do loop.

C#

var nArgument = Expression.Parameter(typeof(int), "n");


var result = Expression.Variable(typeof(int), "result");

// Creating a label that represents the return value


LabelTarget label = Expression.Label(typeof(int));

var initializeResult = Expression.Assign(result, Expression.Constant(1));

// This is the inner block that performs the multiplication,


// and decrements the value of 'n'
var block = Expression.Block(
Expression.Assign(result,
Expression.Multiply(result, nArgument)),
Expression.PostDecrementAssign(nArgument)
);

// Creating a method body.


BlockExpression body = Expression.Block(
new[] { result },
initializeResult,
Expression.Loop(
Expression.IfThenElse(
Expression.GreaterThan(nArgument, Expression.Constant(1)),
block,
Expression.Break(label, result)
),
label
)
);
O código para criar a árvore de expressão para a função fatorial é bem mais longo, mais
complicado e está cheio de rótulos e instruções de interrupção, bem como outros
elementos que você gostaria de evitar em nossas tarefas cotidianas de codificação.

Para esta seção, você gravou código para visitar todos os nós nesta árvore de expressão
e gravar informações sobre os nós criados neste exemplo. Você pode exibir ou baixar o
código de exemplo no repositório dotnet/docs do GitHub. Experimente por conta
própria criando e executando os exemplos.

Mapear constructos de código para expressões


O exemplo de código a seguir demonstra uma árvore de expressão que representa a
expressão lambda num => num < 5 usando a API.

C#

// Manually build the expression tree for


// the lambda expression num => num < 5.
ParameterExpression numParam = Expression.Parameter(typeof(int), "num");
ConstantExpression five = Expression.Constant(5, typeof(int));
BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);
Expression<Func<int, bool>> lambda1 =
Expression.Lambda<Func<int, bool>>(
numLessThanFive,
new ParameterExpression[] { numParam });

A API de árvores de expressão também dá suporte a atribuições e expressões de fluxo


de controle, como loops, blocos condicionais e blocos try-catch . Usando a API, você
pode criar árvores de expressão mais complexas do que aquelas que podem ser criadas
por meio de expressões lambda pelos compiladores do C#. O exemplo a seguir
demonstra como criar uma árvore de expressão que calcula o fatorial de um número.

C#

// Creating a parameter expression.


ParameterExpression value = Expression.Parameter(typeof(int), "value");

// Creating an expression to hold a local variable.


ParameterExpression result = Expression.Parameter(typeof(int), "result");

// Creating a label to jump to from a loop.


LabelTarget label = Expression.Label(typeof(int));

// Creating a method body.


BlockExpression block = Expression.Block(
// Adding a local variable.
new[] { result },
// Assigning a constant to a local variable: result = 1
Expression.Assign(result, Expression.Constant(1)),
// Adding a loop.
Expression.Loop(
// Adding a conditional block into the loop.
Expression.IfThenElse(
// Condition: value > 1
Expression.GreaterThan(value, Expression.Constant(1)),
// If true: result *= value --
Expression.MultiplyAssign(result,
Expression.PostDecrementAssign(value)),
// If false, exit the loop and go to the label.
Expression.Break(label, result)
),
// Label to jump to.
label
)
);

// Compile and execute an expression tree.


int factorial = Expression.Lambda<Func<int, int>>(block, value).Compile()
(5);

Console.WriteLine(factorial);
// Prints 120.

Para saber mais, confira Gerar métodos dinâmicos com árvores de expressão no Visual
Studio 2010 , que também se aplica a versões mais recentes do Visual Studio.

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.
Mover árvores de expressão
Artigo • 16/03/2023

Neste artigo, você aprenderá a visitar cada nó em uma árvore de expressão enquanto
estiver criando uma cópia modificada dessa árvore de expressão. Você converterá
árvores de expressão para entender os algoritmos de modo que eles possam ser
convertidos em outro ambiente. Você alterará o algoritmo criado. Você poderá adicionar
registro em log, interceptar chamadas de método e monitorá-las ou realizar outras
ações.

O código que você compila para mover uma árvore de expressão é uma extensão do
que você já viu para visitar todos os nós em uma árvore. Quando você move uma árvore
de expressão, visita todos os nós e ao visitá-los, cria a nova árvore. A nova árvore pode
conter referências aos nós originais ou aos novos nós que você inseriu na árvore.

Vamos acessar uma árvore de expressão e criar uma árvore com alguns nós de
substituição. Neste exemplo, substituiremos qualquer constante por uma constante dez
vezes maior. Caso contrário, deixe a árvore de expressão intacta. Em vez de ler o valor
da constante e substituí-la por uma nova constante, você fará essa substituição
trocando o nó de constante por um novo nó que executa a multiplicação.

Aqui, quando encontrar um nó de constante, você criará um nó de multiplicação cujos


filhos serão a constante original e a constante 10 :

C#

private static Expression ReplaceNodes(Expression original)


{
if (original.NodeType == ExpressionType.Constant)
{
return Expression.Multiply(original, Expression.Constant(10));
}
else if (original.NodeType == ExpressionType.Add)
{
var binaryExpression = (BinaryExpression)original;
return Expression.Add(
ReplaceNodes(binaryExpression.Left),
ReplaceNodes(binaryExpression.Right));
}
return original;
}

Crie uma árvore substituindo o nó original pelo substituto. Você verificará as alterações
compilando e executando a árvore substituída.
C#

var one = Expression.Constant(1, typeof(int));


var two = Expression.Constant(2, typeof(int));
var addition = Expression.Add(one, two);
var sum = ReplaceNodes(addition);
var executableFunc = Expression.Lambda(sum);

var func = (Func<int>)executableFunc.Compile();


var answer = func();
Console.WriteLine(answer);

A criação de uma nova árvore é uma combinação da visita aos nós da árvore existentes
e a criação de novos nós, inserindo-os na árvore. O anterior exemplo mostra a
importância de as árvores de expressão serem imutáveis. Observe que a árvore criada
no código anterior contém uma mistura de nós recém-criados e nós da árvore existente.
Os nós podem ser usados em ambas as árvores porque os nós na árvore existente não
podem ser modificados. Reutilizar nós resulta em eficiências significativas de memória.
Os mesmos nós podem ser usados em toda a árvore ou em várias árvores de expressão.
Como os nós não podem ser modificados, o mesmo nó pode ser reutilizado sempre que
necessário.

Percorrer e executar uma adição


Vamos verificar a nova árvore criando um segundo visitante que percorre a árvore de
nós de adição e calcula o resultado. Faça algumas modificações no visitante que você
viu até agora. Nessa nova versão, o visitante retorna a soma parcial da operação de
adição até este ponto. Para uma expressão de constante, esse é simplesmente o valor
da expressão de constante. Para uma expressão de adição, o resultado será a soma dos
operandos esquerdos e direitos, uma vez que essas árvores forem percorridas.

C#

var one = Expression.Constant(1, typeof(int));


var two = Expression.Constant(2, typeof(int));
var three = Expression.Constant(3, typeof(int));
var four = Expression.Constant(4, typeof(int));
var addition = Expression.Add(one, two);
var add2 = Expression.Add(three, four);
var sum = Expression.Add(addition, add2);

// Declare the delegate, so you can call it


// from itself recursively:
Func<Expression, int> aggregate = null!;
// Aggregate, return constants, or the sum of the left and right operand.
// Major simplification: Assume every binary expression is an addition.
aggregate = (exp) =>
exp.NodeType == ExpressionType.Constant ?
(int)((ConstantExpression)exp).Value :
aggregate(((BinaryExpression)exp).Left) +
aggregate(((BinaryExpression)exp).Right);

var theSum = aggregate(sum);


Console.WriteLine(theSum);

Tem bastante código nisso, mas os conceitos são acessíveis. Esse código visita filhos em
uma pesquisa de profundidade inicial. Ao encontrar um nó constante, o visitante retorna
o valor da constante. Depois de visitar ambos os filhos, o visitante terá a soma calculada
para essa subárvore. Agora o nó de adição poderá computar sua soma. Uma vez que
todos os nós da árvore de expressão forem visitados, a soma é calculada. Você pode
executar o exemplo no depurador e rastrear a execução.

Vamos facilitar o rastreamento de como os nós são analisados e como a soma é


calculada, percorrendo a árvore. Esta é uma versão atualizada do método de agregação
que inclui bastante informação de rastreamento:

C#

private static int Aggregate(Expression exp)


{
if (exp.NodeType == ExpressionType.Constant)
{
var constantExp = (ConstantExpression)exp;
Console.Error.WriteLine($"Found Constant: {constantExp.Value}");
if (constantExp.Value is int value)
{
return value;
}
else
{
return 0;
}
}
else if (exp.NodeType == ExpressionType.Add)
{
var addExp = (BinaryExpression)exp;
Console.Error.WriteLine("Found Addition Expression");
Console.Error.WriteLine("Computing Left node");
var leftOperand = Aggregate(addExp.Left);
Console.Error.WriteLine($"Left is: {leftOperand}");
Console.Error.WriteLine("Computing Right node");
var rightOperand = Aggregate(addExp.Right);
Console.Error.WriteLine($"Right is: {rightOperand}");
var sum = leftOperand + rightOperand;
Console.Error.WriteLine($"Computed sum: {sum}");
return sum;
}
else throw new NotSupportedException("Haven't written this yet");
}

Executá-lo na expressão sum produz o seguinte resultado:

Saída

10
Found Addition Expression
Computing Left node
Found Addition Expression
Computing Left node
Found Constant: 1
Left is: 1
Computing Right node
Found Constant: 2
Right is: 2
Computed sum: 3
Left is: 3
Computing Right node
Found Addition Expression
Computing Left node
Found Constant: 3
Left is: 3
Computing Right node
Found Constant: 4
Right is: 4
Computed sum: 7
Right is: 7
Computed sum: 10
10

Rastreie a saída e acompanhe no código anterior. Você será capaz de entender como o
código visita cada nó e calcula a soma, à medida que percorre a árvore e localiza a
soma.

Agora, vejamos uma execução diferente, com a expressão fornecida por sum1 :

C#

Expression<Func<int>> sum1 = () => 1 + (2 + (3 + 4));

Aqui está a saída ao examinar essa expressão:

Saída

Found Addition Expression


Computing Left node
Found Constant: 1
Left is: 1
Computing Right node
Found Addition Expression
Computing Left node
Found Constant: 2
Left is: 2
Computing Right node
Found Addition Expression
Computing Left node
Found Constant: 3
Left is: 3
Computing Right node
Found Constant: 4
Right is: 4
Computed sum: 7
Right is: 7
Computed sum: 9
Right is: 9
Computed sum: 10
10

Embora a resposta final seja a mesma, a forma de percorrer a árvore é diferente. Os nós
são percorridos em uma ordem diferente, porque a árvore foi construída com operações
diferentes que ocorrem primeiro.

Criar uma cópia modificada


Crie um novo projeto de Aplicativo de Console. Adicione uma diretiva using ao arquivo
para o namespace System.Linq.Expressions . Adicione a classe AndAlsoModifier ao seu
projeto.

C#

public class AndAlsoModifier : ExpressionVisitor


{
public Expression Modify(Expression expression)
{
return Visit(expression);
}

protected override Expression VisitBinary(BinaryExpression b)


{
if (b.NodeType == ExpressionType.AndAlso)
{
Expression left = this.Visit(b.Left);
Expression right = this.Visit(b.Right);

// Make this binary expression an OrElse operation instead of an


AndAlso operation.
return Expression.MakeBinary(ExpressionType.OrElse, left, right,
b.IsLiftedToNull, b.Method);
}

return base.VisitBinary(b);
}
}

Essa classe herda a classe ExpressionVisitor e é especializada para modificar expressões


que representam operações AND condicionais. Ela muda essas operações de uma AND
condicional para uma OR condicional. A classe substitui o método VisitBinary do tipo
base, pois as expressões condicionais AND são representadas como expressões binárias.
No método VisitBinary , se a expressão que é passada a ele representa uma operação
AND condicional, o código cria uma nova expressão que contém o operador OR
condicional em vez do operador AND condicional. Se a expressão passada para o
VisitBinary não representa uma operação AND condicional, o método adia para a

implementação da classe base. Os métodos da classe base constroem nós semelhantes


às árvores de expressão passadas, mas as subárvores dos nós são substituídas pelas
árvores de expressão produzidas de maneira recorrente pelo visitante.

Adicione uma diretiva using ao arquivo para o namespace System.Linq.Expressions .


Adicione código ao método Main no arquivo Program.cs para criar uma árvore de
expressão e passá-la ao método que a modifica.

C#

Expression<Func<string, bool>> expr = name => name.Length > 10 &&


name.StartsWith("G");
Console.WriteLine(expr);

AndAlsoModifier treeModifier = new AndAlsoModifier();


Expression modifiedExpr = treeModifier.Modify((Expression)expr);

Console.WriteLine(modifiedExpr);

/* This code produces the following output:

name => ((name.Length > 10) && name.StartsWith("G"))


name => ((name.Length > 10) || name.StartsWith("G"))
*/

O código cria uma expressão que contém uma operação AND condicional. Em seguida,
ele cria uma instância da classe AndAlsoModifier e passa a expressão ao método Modify
dessa classe. A árvore de expressão original e a modificada são geradas para mostrar a
alteração. Compile e execute o aplicativo.
Saiba mais
Este exemplo mostra um pequeno subconjunto do código que você compilaria para
percorrer e interpretar os algoritmos representados por uma árvore de expressão. Para
obter informações sobre como criar uma biblioteca de uso geral que converte árvores
de expressão em outra linguagem, leia esta série de Matt Warren. Ele entra em detalhes
de como mover qualquer código que você pode encontrar em uma árvore de
expressão.

Espero que você tenha visto o verdadeiro potencial das árvores de expressão. Você
examina um conjunto de códigos, faz as alterações que deseja nesse código e executa a
versão modificada. Como as árvores de expressão são imutáveis, você cria árvores
usando os componentes de árvores existentes. A reutilização de nós minimiza a
quantidade de memória necessária para criar árvores de expressão modificadas.

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.
Depurando árvores de expressão no
Visual Studio
Artigo • 16/03/2023

Ao depurar seus aplicativos, você pode analisar a estrutura e o conteúdo das árvores de
expressão. Para obter uma visão geral da estrutura de árvore de expressão, você pode
usar a propriedade DebugView , que representa as árvores de expressão usando uma
sintaxe especial. DebugView está disponível apenas no modo de depuração.

Uma vez que DebugView é uma cadeia de caracteres, você pode usar o Visualizador de
Texto interno para exibi-lo em várias linhas, selecionando Visualizador de Texto do
ícone de lupa ao lado do rótulo DebugView .

Como alternativa, você pode instalar e usar um visualizador personalizado para árvores
de expressão, como:

As Expressões Legíveis (licença do MIT , disponível no Visual Studio


Marketplace ) renderizam a árvore de expressão como código C# com tema, com
várias opções de renderização:
O Visualizador de Árvore de Expressão (licença MIT ) fornece um modo de
exibição de árvore da árvore de expressão e dos nós individuais dela:

Abrir um visualizador para uma árvore de


expressão
Selecione o ícone de lupa que aparece ao lado da árvore de expressão em DataTips,
uma janela Inspeção, a janela Autos ou a janela Locais. É exibida uma lista dos
visualizadores disponíveis:
Selecione o visualizador que você deseja usar.

Confira também
Depurando no Visual Studio
Criar visualizadores personalizados
DebugView sintaxe

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.
Sintaxe do DebugView
Artigo • 16/03/2023

A propriedade DebugView (disponível apenas durante a depuração) fornece uma


renderização de cadeia de caracteres de árvores de expressão. A maior parte da sintaxe
é bastante simples de entender; os casos especiais são descritos nas seções a seguir.

Cada exemplo é seguido por um comentário de bloco, que contém DebugView.

ParameterExpression
ParameterExpression os nomes de variáveis são exibidos com o símbolo $ no início.

Se um parâmetro não tiver nome, receberá um nome gerado automaticamente, como


$var1 ou $var2 .

C#

ParameterExpression numParam = Expression.Parameter(typeof(int), "num");


/*
$num
*/

ParameterExpression numParam = Expression.Parameter(typeof(int));


/*
$var1
*/

ConstantExpression
Para objetos ConstantExpression que representam valores inteiros, cadeias de caracteres
e null , o valor da constante é exibido.

Para tipos numéricos que têm sufixos padrão como literais de C#, o sufixo é adicionado
ao valor. A tabela a seguir mostra os sufixos associados com vários tipos numéricos.

Tipo Palavra-chave Sufixo

System.UInt32 uint U

System.Int64 longo L

System.UInt64 ulong UL
Tipo Palavra-chave Sufixo

System.Double double D

System.Single float F

System.Decimal decimal M

C#

int num = 10;


ConstantExpression expr = Expression.Constant(num);
/*
10
*/

double num = 10;


ConstantExpression expr = Expression.Constant(num);
/*
10D
*/

BlockExpression
Se o tipo de um objeto BlockExpression difere do tipo da última expressão no bloco, o
tipo será exibido entre colchetes angulares ( < e > ). Caso contrário, o tipo do objeto
BlockExpression não é exibido.

C#

BlockExpression block = Expression.Block(Expression.Constant("test"));


/*
.Block() {
"test"
}
*/

BlockExpression block = Expression.Block(typeof(Object),


Expression.Constant("test"));
/*
.Block<System.Object>() {
"test"
}
*/

LambdaExpression
Objetos LambdaExpression são exibidos junto com seus tipos delegados.

Se uma expressão lambda não tiver nome, receberá um nome gerado automaticamente,
como #Lambda1 ou #Lambda2 .

C#

LambdaExpression lambda = Expression.Lambda<Func<int>>


(Expression.Constant(1));
/*
.Lambda #Lambda1<System.Func'1[System.Int32]>() {
1
}
*/

LambdaExpression lambda = Expression.Lambda<Func<int>>


(Expression.Constant(1), "SampleLambda", null);
/*
.Lambda #SampleLambda<System.Func'1[System.Int32]>() {
1
}
*/

LabelExpression
Se você especificar um valor padrão para o objeto LabelExpression, esse valor será
exibido antes do objeto LabelTarget.

O token .Label indica o início do rótulo. O token .LabelTarget indica o local para o
qual o destino deve saltar.

Se um rótulo não tiver nome, receberá um nome gerado automaticamente, como


#Label1 ou #Label2 .

C#

LabelTarget target = Expression.Label(typeof(int), "SampleLabel");


BlockExpression block = Expression.Block(
Expression.Goto(target, Expression.Constant(0)),
Expression.Label(target, Expression.Constant(-1))
);
/*
.Block() {
.Goto SampleLabel { 0 };
.Label
-1
.LabelTarget SampleLabel:
}
*/

LabelTarget target = Expression.Label();


BlockExpression block = Expression.Block(
Expression.Goto(target),
Expression.Label(target)
);
/*
.Block() {
.Goto #Label1 { };
.Label
.LabelTarget #Label1:
}
*/

Operadores verificados
Os operadores verificados são exibidos com o símbolo # na frente do operador. Por
exemplo, o operador de adição verificado é exibido como #+ .

C#

Expression expr = Expression.AddChecked( Expression.Constant(1),


Expression.Constant(2));
/*
1 #+ 2
*/

Expression expr = Expression.ConvertChecked( Expression.Constant(10.0),


typeof(int));
/*
#(System.Int32)10D
*/

6 Collaborate with us on
GitHub .NET feedback
The .NET documentation is open
The source for this content can
be found on GitHub, where you source. Provide feedback here.
can also create and review
 Open a documentation issue
issues and pull requests. For
more information, see our
 Provide product feedback
contributor guide.
Métodos
System.Linq.Expressions.Expression.Add
Artigo • 31/01/2024

Este artigo fornece observações complementares à documentação de referência para


essa API.

O Add método retorna um BinaryExpression que tem a Method propriedade definida


para o método de implementação. A Type propriedade é definida como o tipo do nó. Se
o nó for levantado, as IsLifted propriedades e IsLiftedToNull serão ambas true . Caso
contrário, são false . A propriedade Conversion é null .

As informações a seguir descrevem o método de implementação, o tipo de nó e se um


nó é suspenso.

Método de execução
As regras a seguir determinam o método de execução selecionado para a operação:

Se a Type propriedade de um ou right representa um left tipo definido pelo


usuário que sobrecarrega o operador de adição, o que representa esse método é o
MethodInfo método de implementação.
Caso contrário, se left . Tipo e right . Tipo são tipos numéricos, o método de
implementação é null .

Tipo de nó e levantado versus não levantado


Se o método de execução não null for:

Se left . Tipo e right . Os tipos são atribuíveis aos tipos de argumento


correspondentes do método de implementação, o nó não é levantado. O tipo do
nó é o tipo de retorno do método de implementação.

Se as duas condições a seguir forem satisfeitas, o nó será suspenso e o tipo do nó


será o tipo anulável que corresponde ao tipo de retorno do método de
implementação:
left . Tipo e right . Tipo são ambos os tipos de valor dos quais pelo menos um

é anulável e os tipos não anuláveis correspondentes são iguais aos tipos de


argumento correspondentes do método de implementação.
O tipo de retorno do método de implementação é um tipo de valor não
anulável.

Se o método de execução for null :

Se left . Tipo e right . Os tipos não são anuláveis, o nó não é levantado. O tipo do
nó é o tipo de resultado do operador de adição predefinido.
Se left . Tipo e right . Os tipos são ambos anuláveis, o nó é levantado. O tipo do
nó é o tipo anulável que corresponde ao tipo de resultado do operador de adição
predefinido.

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
System.Linq.Expressions.BinaryExpressio
n classe
Artigo • 11/01/2024

Este artigo fornece observações complementares à documentação de referência para


essa API.

A BinaryExpression classe representa uma expressão que tem um operador binário.

As tabelas a seguir resumem os métodos de fábrica que podem ser usados para criar
um que tenha um BinaryExpression tipo de nó específico, representado pela NodeType
propriedade. Cada tabela contém informações para uma classe específica de operações,
como aritmética ou bitwise.

Operações aritméticas binárias


ノ Expandir a tabela

Tipo de nó Método de Fábrica

Add Add

AddChecked AddChecked

Divide Divide

Modulo Modulo

Multiply Multiply

MultiplyChecked MultiplyChecked

Power Power

Subtract Subtract

SubtractChecked SubtractChecked

Operações bit a bit


ノ Expandir a tabela
Tipo de nó Método de Fábrica

And And

Or Or

ExclusiveOr ExclusiveOr

Operações de turno
ノ Expandir a tabela

Tipo de nó Método de Fábrica

LeftShift LeftShift

RightShift RightShift

Operações booleanas condicionais


ノ Expandir a tabela

Tipo de nó Método de Fábrica

AndAlso AndAlso

OrElse OrElse

Operadores de comparação
ノ Expandir a tabela

Tipo de nó Método de Fábrica

Equal Equal

NotEqual NotEqual

GreaterThanOrEqual GreaterThanOrEqual

GreaterThan GreaterThan

LessThan LessThan
Tipo de nó Método de Fábrica

LessThanOrEqual LessThanOrEqual

Operações de coalescência
ノ Expandir a tabela

Tipo de nó Método de Fábrica

Coalesce Coalesce

Operações de indexação de matriz


ノ Expandir a tabela

Tipo de nó Método de Fábrica

ArrayIndex ArrayIndex

Além disso, os MakeBinary métodos também podem ser usados para criar um
BinaryExpressionarquivo . Esses métodos de fábrica podem ser usados para criar um
BinaryExpression de qualquer tipo de nó que represente uma operação binária. O
parâmetro desses métodos que é do tipo especifica o tipo NodeType de nó desejado.

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
Interoperability Overview
Article • 02/25/2023

Interoperability enables you to preserve and take advantage of existing investments in


unmanaged code. Code that runs under the control of the common language runtime
(CLR) is managed code, and code that runs outside the CLR is unmanaged code. COM,
COM+, C++ components, ActiveX components, and Microsoft Windows API are
examples of unmanaged code.

.NET enables interoperability with unmanaged code through platform invoke services,
the System.Runtime.InteropServices namespace, C++ interoperability, and COM
interoperability (COM interop).

Platform Invoke
Platform invoke is a service that enables managed code to call unmanaged functions
implemented in dynamic link libraries (DLLs), such as the Microsoft Windows API. It
locates and invokes an exported function and marshals its arguments (integers, strings,
arrays, structures, and so on) across the interoperation boundary as needed.

For more information, see Consuming Unmanaged DLL Functions and How to use
platform invoke to play a WAV file.

7 Note

The Common Language Runtime (CLR) manages access to system resources.


Calling unmanaged code that is outside the CLR bypasses this security mechanism,
and therefore presents a security risk. For example, unmanaged code might call
resources in unmanaged code directly, bypassing CLR security mechanisms. For
more information, see Security in .NET.

C++ Interop
You can use C++ interop, also known as It Just Works (IJW), to wrap a native C++ class.
C++ interop enables code authored in C# or another .NET language to access it. You
write C++ code to wrap a native DLL or COM component. Unlike other .NET languages,
Visual C++ has interoperability support that enables managed and unmanaged code in
the same application and even in the same file. You then build the C++ code by using
the /clr compiler switch to produce a managed assembly. Finally, you add a reference to
the assembly in your C# project and use the wrapped objects just as you would use
other managed classes.

Exposing COM Components to C#


You can consume a COM component from a C# project. The general steps are as
follows:

1. Locate a COM component to use and register it. Use regsvr32.exe to register or
un–register a COM DLL.
2. Add to the project a reference to the COM component or type library. When you
add the reference, Visual Studio uses the Tlbimp.exe (Type Library Importer), which
takes a type library as input, to output a .NET interop assembly. The assembly, also
named a runtime callable wrapper (RCW), contains managed classes and interfaces
that wrap the COM classes and interfaces that are in the type library. Visual Studio
adds to the project a reference to the generated assembly.
3. Create an instance of a class defined in the RCW. Creating an instance of that class
creates an instance of the COM object.
4. Use the object just as you use other managed objects. When the object is
reclaimed by garbage collection, the instance of the COM object is also released
from memory.

For more information, see Exposing COM Components to the .NET Framework.

Exposing C# to COM
COM clients can consume C# types that have been correctly exposed. The basic steps to
expose C# types are as follows:

1. Add interop attributes in the C# project. You can make an assembly COM visible by
modifying C# project properties. For more information, see Assembly Information
Dialog Box.
2. Generate a COM type library and register it for COM usage. You can modify C#
project properties to automatically register the C# assembly for COM interop.
Visual Studio uses the Regasm.exe (Assembly Registration Tool), using the /tlb
command-line switch, which takes a managed assembly as input, to generate a
type library. This type library describes the public types in the assembly and adds
registry entries so that COM clients can create managed classes.

For more information, see Exposing .NET Framework Components to COM and Example
COM Class.
See also
Improving Interop Performance
Introduction to Interoperability between COM and .NET
Introduction to COM Interop in Visual Basic
Marshaling between Managed and Unmanaged Code
Interoperating with Unmanaged Code
Registering Assemblies with COM
Exemplo de classe COM
Artigo • 10/05/2023

O código a seguir é um exemplo de uma classe que você poderia expor como um
objeto COM. Depois de posicionar o código em um arquivo .cs adicionado a seu
projeto, defina a propriedade Registrar para interoperabilidade COM como True. Para
obter mais informações, consulte Como registrar um componente para
interoperabilidade COM.

A exposição de objetos do C# para COM requer a declaração de uma interface de


classe, de uma “interface de eventos” se necessário e da própria classe. Os membros de
classe devem seguir estas regras para ficarem visíveis ao COM:

A classe deve ser pública.


As propriedades, os métodos e os eventos devem ser públicos.
As propriedades e os métodos devem ser declarados na interface de classe.
Os eventos devem ser declarados na interface de eventos.

Outros membros públicos da classe que não são declarados nessas interfaces não
estarão visíveis para COM, mas eles ficarão visíveis para outros objetos .NET. Para expor
propriedades e métodos ao COM, você deve declará-los na interface de classe e marcá-
los com um atributo DispId e implementá-los na classe. A ordem de declaração dos
membros na interface é a ordem usada para a vtable do COM. Para expor eventos de
sua classe, você deve declará-los na interface de eventos e marcá-los com um atributo
DispId . A classe não deve implementar essa interface.

A classe implementa a interface de classe. Ela pode implementar mais de uma interface,
mas a primeira implementação é a interface de classe padrão. Implemente os métodos e
propriedades expostos ao COM aqui. Ela deve ser marcada como pública e deve
corresponder às declarações na interface de classe. Além disso, declare aqui os eventos
acionados pela classe. Ela deve ser pública e deve corresponder às declarações na
interface de eventos.

Exemplo
C#

using System.Runtime.InteropServices;

namespace project_name
{
[Guid("EAA4976A-45C3-4BC5-BC0B-E474F4C3C83F")]
public interface ComClass1Interface
{
}

[Guid("7BD20046-DF8C-44A6-8F6B-687FAA26FA71"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ComClass1Events
{
}

[Guid("0D53A3E8-E51A-49C7-944E-E72A2064F938"),
ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(ComClass1Events))]
public class ComClass1 : ComClass1Interface
{
}
}

Confira também
Interoperabilidade
Página de Build, Designer de Projeto (C#)

6 Collaborate with us on
GitHub .NET feedback
The .NET documentation is open
The source for this content can
be found on GitHub, where you source. Provide feedback here.
can also create and review
 Open a documentation issue
issues and pull requests. For
more information, see our
 Provide product feedback
contributor guide.
Passo a passo: programação do Office
em C#
Artigo • 09/03/2023

O C# oferece recursos que melhoram a programação do Microsoft Office. As


funcionalidades úteis do C# incluem argumentos nomeados e opcionais e valores
retornados do tipo dynamic . Na programação COM, você pode omitir a palavra-chave
ref e obter acesso a propriedades indexadas.

Ambas as linguagens permitem incorporar as informações de tipo, que permitem a


implantação de assemblies que interagem com componentes COM sem implantar
assemblies de interoperabilidade primários (PIAs) no computador do usuário. Para obter
mais informações, consulte Instruções passo a passo: Inserindo tipos de assemblies
gerenciados.

Este passo a passo demonstra essas funcionalidades no contexto de programação do


Office, mas muitos deles também são úteis na programação em geral. No passo a passo,
você usa um aplicativo Suplemento do Excel para criar uma pasta de trabalho do Excel.
Em seguida, você cria um documento do Word que contém um link para a pasta de
trabalho. Por fim, você vê como habilitar e desabilitar a dependência de PIA.

) Importante

O VSTO se baseia no .NET Framework. Os suplementos COM também podem ser


gravados com o .NET Framework. Os suplementos do Office não podem ser criados
com o .NET Core e o .NET 5+, as versões mais recentes do .NET. Isso ocorre porque
o .NET Core/.NET 5+ não pode trabalhar em conjunto com o .NET Framework no
mesmo processo e pode levar a falhas de carga de suplemento. Você pode
continuar a usar o .NET Framework para escrever suplementos VSTO e COM para o
Office. A Microsoft não atualizará o VSTO ou a plataforma de suplemento COM
para usar o .NET Core ou o .NET 5+. Você pode aproveitar o .NET Core e o .NET 5+,
incluindo o ASP.NET Core, para criar o lado do servidor dos Suplementos Web do
Office.

Pré-requisitos
Você deve ter o Microsoft Office Excel e o Microsoft Office Word instalados no
computador para concluir esse passo a passo.
7 Observação

Seu computador pode mostrar diferentes nomes ou locais para alguns dos
elementos de interface do usuário do Visual Studio nas instruções a seguir. A
edição do Visual Studio que você possui e as configurações que você usa
determinam esses elementos. Para obter mais informações, consulte
Personalizando o IDE.

Configurar um aplicativo Suplemento do Excel


1. Inicie o Visual Studio.
2. No menu Arquivo , aponte para Novoe selecione Projeto.
3. No painel Modelos Instalados, expanda C#, expanda Office e, em seguida,
selecione o ano da versão do produto do Office.
4. No painel Modelos, selecione Suplemento do <Excel> versão.
5. Observe a parte superior do painel Modelos para se certificar de que .NET
Framework 4 ou uma versão posterior, é exibido na caixa Estrutura de Destino.
6. Digite um nome para seu projeto na caixa Nome, se desejar.
7. Selecione OK.
8. O novo projeto aparece no Gerenciador de Soluções.

Adicionar referências
1. No Gerenciador de Soluções, clique com o botão direito do mouse no nome do
projeto e, em seguida, selecione Adicionar Referência. A caixa de diálogo
Adicionar Referência é exibida.
2. Na guia Assemblies, selecione Microsoft.Office.Interop.Excel, versão
<version>.0.0.0 (para uma chave para os números de versão de produto do

Office, consulte Versões da Microsoft ), na lista Nome do componente e


mantenha a tecla CTRL pressionada e selecione Microsoft.Office.Interop.Word,
version <version>.0.0.0 . Se você não vir os assemblies, talvez seja necessário

verificar se eles estão instalados (confira Como: Instalar assemblies de


interoperabilidade primária do Office).
3. Selecione OK.

Adicionar instruções Imports necessárias ou


diretivas de uso
No Gerenciador de Soluções, clique com o botão direito do mouse no arquivo
ThisAddIn.cs e, em seguida, selecione Exibir Código. Adicione as seguintes diretivas
using (C#) na parte superior do arquivo de código se elas ainda não estiverem

presentes.

C#

using System.Collections.Generic;
using Excel = Microsoft.Office.Interop.Excel;
using Word = Microsoft.Office.Interop.Word;

Criar uma lista de contas bancárias


No Gerenciador de Soluções, clique com o botão direito do mouse no nome do projeto
e, em seguida, selecione Adicionar e Classe. Nomeie a classe Account.cs. Selecione
Adicionar. Substitua a definição da classe Account pelo código a seguir. As definições
de classe usam propriedades autoimplementadas.

C#

class Account
{
public int ID { get; set; }
public double Balance { get; set; }
}

Para criar uma lista bankAccounts que contém duas contas, adicione o seguinte código
ao método ThisAddIn_Startup em ThisAddIn.cs. As declarações de lista usam
inicializadores de coleção.

C#

var bankAccounts = new List<Account>


{
new Account
{
ID = 345,
Balance = 541.27
},
new Account
{
ID = 123,
Balance = -127.44
}
};
Exportar dados para o Excel
No mesmo arquivo, adicione o método a seguir para a classe ThisAddIn . O método
configura uma planilha do Excel e exporta dados para ela.

C#

void DisplayInExcel(IEnumerable<Account> accounts,


Action<Account, Excel.Range> DisplayFunc)
{
var excelApp = this.Application;
// Add a new Excel workbook.
excelApp.Workbooks.Add();
excelApp.Visible = true;
excelApp.Range["A1"].Value = "ID";
excelApp.Range["B1"].Value = "Balance";
excelApp.Range["A2"].Select();

foreach (var ac in accounts)


{
DisplayFunc(ac, excelApp.ActiveCell);
excelApp.ActiveCell.Offset[1, 0].Select();
}
// Copy the results to the Clipboard.
excelApp.Range["A1:B3"].Copy();
}

O método Add tem um parâmetro opcional para especificar um modelo específico.


Os parâmetros opcionais permitem omitir o argumento para esse parâmetro, se
você deseja usar o valor padrão do parâmetro. Como o exemplo anterior não tem
argumentos, Add usa o modelo padrão e cria uma pasta de trabalho. A instrução
equivalente em versões anteriores do C# requer um argumento de espaço
reservado: excelApp.Workbooks.Add(Type.Missing) . Para obter mais informações,
consulte Argumentos nomeados e opcionais.
As propriedades Range e Offset do objeto Range usam o recurso de propriedades
indexadas. Este recurso permite consumir essas propriedades de tipos COM
usando a sintaxe típica do C# a seguir. Propriedades indexadas também permitem
que você use a propriedade Value do objeto Range , eliminando a necessidade de
usar a propriedade Value2 . A propriedade Value é indexada, mas o índice é
opcional. Argumentos opcionais e propriedades indexadas trabalham juntos no
exemplo a seguir.

C#

// Visual C# 2010 provides indexed properties for COM programming.


excelApp.Range["A1"].Value = "ID";
excelApp.ActiveCell.Offset[1, 0].Select();

Não é possível criar propriedades indexadas de sua preferência. O recurso dá suporte


apenas ao consumo de propriedades indexadas existentes.

Adicione o seguinte código no final de DisplayInExcel para ajustar as larguras das


colunas para adequar o conteúdo.

C#

excelApp.Columns[1].AutoFit();
excelApp.Columns[2].AutoFit();

Essas adições demonstram outro recurso no C#: tratar valores Object retornados de
hosts COM como o Office, como se eles tivessem o tipo dinâmico. Os objetos COM são
tratados como dynamic automaticamente quando os Inserir Tipos de
Interoperabilidade têm seu valor padrão, True , ou, de modo equivalente, quando você
faz referência ao assembly com a opção do compilador EmbedInteropTypes. Para obter
mais informações sobre como inserir tipos de interoperabilidade, consulte os
procedimentos "Localizar a referência de PIA" e "Restaurar a dependência de PIA"
posteriormente neste artigo. Para obter mais informações sobre dynamic , consulte
dynamic ou Usando o tipo dynamic.

Invocar DisplayInExcel
Adicione o código a seguir no final do método ThisAddIn_StartUp . A chamada para
DisplayInExcel contém dois argumentos. O primeiro argumento é o nome da lista de

contas a ser processada. O segundo argumento é uma expressão lambda de várias


linhas que define como processar os dados. Os valores ID e balance de cada conta
serão exibidos em células adjacentes e a linha será exibida em vermelho se o equilíbrio
for menor do que zero. Para obter mais informações, consulte Expressões Lambda.

C#

DisplayInExcel(bankAccounts, (account, cell) =>


// This multiline lambda expression sets custom processing rules
// for the bankAccounts.
{
cell.Value = account.ID;
cell.Offset[0, 1].Value = account.Balance;
if (account.Balance < 0)
{
cell.Interior.Color = 255;
cell.Offset[0, 1].Interior.Color = 255;
}
});

Para executar o programa, pressione F5. Uma planilha do Excel é exibida contendo os
dados das contas.

Adicionar um documento do Word


Adicione o código a seguir ao final do método ThisAddIn_StartUp para criar um
documento do Word que contém um link para a pasta de trabalho do Excel.

C#

var wordApp = new Word.Application();


wordApp.Visible = true;
wordApp.Documents.Add();
wordApp.Selection.PasteSpecial(Link: true, DisplayAsIcon: true);

Esse código demonstra vários dos recursos em C#: a capacidade de omitir a palavra-
chave ref na programação COM, argumentos nomeados e argumentos opcionais. O
método PasteSpecial tem sete parâmetros, todos os quais são parâmetros de referência
opcionais. Argumentos opcionais e nomeados permitem designar os parâmetros que
você deseja acessar pelo nome e enviar argumentos apenas para esses parâmetros.
Neste exemplo, os argumentos indicam que deve ser criado um link para a pasta de
trabalho na área de transferência (parâmetro Link ), e que o link será exibido no
documento do Word como um ícone (parâmetro DisplayAsIcon ). O C# permite também
que você omita a palavra-chave ref nesses argumentos.

Executar o aplicativo
Pressione F5 para executar o aplicativo. O Excel é iniciado e exibe uma tabela que
contém as informações das duas contas em bankAccounts . Em seguida, um documento
do Word é exibido contendo um link para a tabela do Excel.

Limpar o projeto concluído


No Visual Studio, clique em Limpar Solução no menu Compilar. Caso contrário, o
suplemento será executado toda vez que você abrir o Excel no seu computador.

Localizar a referência de PIA


1. Execute o aplicativo novamente, mas não selecione Limpar Solução.
2. Selecione Iniciar. Localize Microsoft Visual Studio <versão>> e abra o prompt de
comando do desenvolvedor.
3. Digite ildasm na janela Prompt de Comando do Desenvolvedor para Visual Studio
e pressione Enter. A janela IL DASM é exibida.
4. No menu Arquivo na janela IL DASM, selecione Arquivo>Abrir. Clique duas vezes
em Visual Studio <versão> e clique duas vezes em Projetos. Abra a pasta do seu
projeto e procure na pasta bin/Debug por nome do projeto.dll. Clique duas vezes
em nome do projeto.dll. Uma nova janela exibe os atributos do projeto, além das
referências a outros módulos e assemblies. O assembly inclui os namespaces
Microsoft.Office.Interop.Excel e Microsoft.Office.Interop.Word . Por padrão, no

Visual Studio, o compilador importa os tipos que você precisa de um PIA


referenciado para o seu assembly. Para obter mais informações, consulte Como
exibir o conteúdo de um assembly.
5. Clique duas vezes no ícone MANIFEST. Uma janela será exibida contendo uma lista
de assemblies que contêm itens referenciados pelo projeto.
Microsoft.Office.Interop.Excel e Microsoft.Office.Interop.Word não estão na

lista. Como você importou os tipos de que seu projeto precisa para o assembly,
não é necessário instalar referências a um PIA. Importar os tipos no assembly
facilita a implantação. Os PIAs não precisam estar presentes no computador do
usuário. Um aplicativo não requer a implantação de uma versão específica de um
PIA. Os aplicativos podem funcionar com várias versões do Office, desde que as
APIs necessárias existam em todas as versões. Como a implantação de PIAs não é
mais necessária, você pode criar um aplicativo em cenários avançados que
funcione com várias versões do Office, incluindo versões anteriores. Seu código
não pode usar nenhuma API que não esteja disponível na versão do Office com a
qual você está trabalhando. Nem sempre fica claro se uma API específica estava
disponível em uma versão anterior. Não é recomendável trabalhar com versões
anteriores do Office.
6. Feche a janela do manifesto e a janela do assembly.

Restaurar a dependência de PIA


1. No Gerenciador de Soluções, selecione o botão Mostrar Todos os Arquivos.
Expanda a pasta Referências e selecione Microsoft.Office.Interop.Excel. Pressione
F4 para exibir a janela Propriedades.
2. Na janela Propriedades, altere a propriedade Inserir Tipos de Interoperabilidade
de True para False.
3. Repita as etapas 1 e 2 deste procedimento para Microsoft.Office.Interop.Word .
4. Em C#, comente as duas chamadas para Autofit no final do método
DisplayInExcel .

5. Pressione F5 para verificar se o projeto ainda é executado corretamente.


6. Repita as etapas de 1 a 3 do procedimento anterior para abrir a janela do
assembly. Observe que Microsoft.Office.Interop.Word e
Microsoft.Office.Interop.Excel não estão mais na lista de assemblies inseridos.

7. Clique duas vezes no ícone MANIFEST e role a lista de assemblies referenciados.


Ambos Microsoft.Office.Interop.Word e Microsoft.Office.Interop.Excel estão
na lista. Como o aplicativo faz referência aos PIAs do Excel e do Word e a
propriedade Inserir Tipos de Interoperabilidade está definida como False, ambos
os assemblies devem existir no computador do usuário final.
8. No Visual Studio, selecione Limpar Solução no menu Compilar para limpar o
projeto concluído.

Confira também
Propriedades autoimplementadas (C#)
Inicializadores de objeto e coleção
Argumentos nomeados e opcionais
dinâmico
Usando o tipo dynamic
Expressões lambda [C#]
Passo a passo: inserindo informações de tipo dos Microsoft Office Assemblies no
Visual Studio
Instruções passo a passo: Inserindo tipos de assemblies gerenciados
Passo a passo: criando o primeiro suplemento do VSTO para Excel
Como usar invocação de plataforma
para executar um arquivo WAV
Artigo • 07/04/2023

O exemplo de código C# a seguir ilustra como usar os serviços de invocação de


plataforma para reproduzir um arquivo de som WAV no sistema operacional Windows.

Exemplo
Esse código de exemplo usa DllImportAttribute para importar o ponto de entrada de
método PlaySound da winmm.dll como Form1 PlaySound() . O exemplo tem um
Windows Form simples com um botão. Ao clicar no botão, abre-se uma caixa de
diálogo padrão OpenFileDialog do Windows para que você possa abrir o arquivo para
reprodução. Quando um arquivo wave é selecionado, ele é executado usando o método
PlaySound() da biblioteca winmm.dll. Para obter mais informações sobre esse método,

consulte Usando a função PlaySound com arquivos de áudio Waveform. Procure e


selecione um arquivo que tenha uma extensão .wav e, em seguida, selecione Abrir para
reproduzir o arquivo wave usando a invocação de plataforma. Uma caixa de texto exibe
o caminho completo do arquivo selecionado.

C#

using System.Runtime.InteropServices;

namespace WinSound;

public partial class Form1 : Form


{
private TextBox textBox1;
private Button button1;

public Form1() // Constructor.


{
InitializeComponent();
}

[DllImport("winmm.DLL", EntryPoint = "PlaySound", SetLastError = true,


CharSet = CharSet.Unicode, ThrowOnUnmappableChar = true)]
private static extern bool PlaySound(string szSound, System.IntPtr hMod,
PlaySoundFlags flags);

[System.Flags]
public enum PlaySoundFlags : int
{
SND_SYNC = 0x0000,
SND_ASYNC = 0x0001,
SND_NODEFAULT = 0x0002,
SND_LOOP = 0x0008,
SND_NOSTOP = 0x0010,
SND_NOWAIT = 0x00002000,
SND_FILENAME = 0x00020000,
SND_RESOURCE = 0x00040004
}

private void button1_Click(object sender, System.EventArgs e)


{
var dialog1 = new OpenFileDialog();

dialog1.Title = "Browse to find sound file to play";


dialog1.InitialDirectory = @"c:\";
//<Snippet5>
dialog1.Filter = "Wav Files (*.wav)|*.wav";
//</Snippet5>
dialog1.FilterIndex = 2;
dialog1.RestoreDirectory = true;

if (dialog1.ShowDialog() == DialogResult.OK)
{
textBox1.Text = dialog1.FileName;
PlaySound(dialog1.FileName, new System.IntPtr(),
PlaySoundFlags.SND_SYNC);
}
}

private void Form1_Load(object sender, EventArgs e)


{
// Including this empty method in the sample because in the IDE,
// when users click on the form, generates code that looks for a
default method
// with this name. We add it here to prevent confusion for those
using the samples.
}
}

A caixa de diálogo Abrir Arquivos é filtrada por meio das configurações de filtro para
mostrar somente os arquivos que têm uma extensão .wav.

Compilando o código
Crie um novo projeto de aplicativos do Windows Forms em C# no Visual Studio e
nomeie-o como WinSound. Copie o código anterior e cole-o sobre o conteúdo do
arquivo Form1.cs. Copie o código a seguir e cole-o no arquivo Form1.Designer.cs, no
método InitializeComponent() , após qualquer código existente.

C#
this.button1 = new System.Windows.Forms.Button();
this.textBox1 = new System.Windows.Forms.TextBox();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(192, 40);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(88, 24);
this.button1.TabIndex = 0;
this.button1.Text = "Browse";
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(8, 40);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(168, 20);
this.textBox1.TabIndex = 1;
this.textBox1.Text = "File path";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(5, 13);
this.ClientSize = new System.Drawing.Size(292, 266);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Platform Invoke WinSound C#";
this.ResumeLayout(false);
this.PerformLayout();

Compile e execute o código.

Confira também
Um olhar detalhado sobre invocação de plataforma
Marshaling de dados com invocação de plataforma

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.
Como usar propriedades indexadas na
programação para interoperabilidade
COM
Artigo • 22/09/2023

As propriedades indexadas trabalham juntamente com outras funcionalidades no C#,


como argumentos nomeados e opcionais, um novo tipo (dinâmico) e informações de
tipo inseridas para melhorar a programação do Microsoft Office.

) Importante

VSTO (Ferramentas do Visual Studio para Office) depende do .NET Framework.


Os suplementos COM também podem ser gravados com o .NET Framework. Os
suplementos do Office não podem ser criados com o .NET Core e o .NET 5+, as
versões mais recentes do .NET. Isso ocorre porque o .NET Core/.NET 5+ não pode
trabalhar em conjunto com o .NET Framework no mesmo processo e pode levar a
falhas de carga de suplemento. Você pode continuar a usar o .NET Framework para
escrever suplementos VSTO e COM para o Office. A Microsoft não atualizará o
VSTO ou a plataforma de suplemento COM para usar o .NET Core ou o .NET 5+.
Você pode aproveitar o .NET Core e o .NET 5+, incluindo o ASP.NET Core, para criar
o lado do servidor dos Suplementos Web do Office.

Nas versões anteriores do C#, os métodos são acessíveis como propriedades apenas se
o método get não tem parâmetros e o método set tem apenas um parâmetro de
valor. No entanto, nem todas as propriedades COM atendem a essas restrições. Por
exemplo, a propriedade Range[] do Excel tem um acessador get que requer um
parâmetro para o nome do intervalo. No passado, como não era possível acessar a
propriedade Range diretamente, era necessário usar o método get_Range em vez disso,
conforme mostrado no exemplo a seguir.

{language}

// Visual C# 2008 and earlier.


var excelApp = new Excel.Application();
// . . .
Excel.Range targetRange = excelApp.get_Range("A1", Type.Missing);

Em vez disso, as propriedades indexadas permitem escrever o seguinte:


{language}

// Visual C# 2010.
var excelApp = new Excel.Application();
// . . .
Excel.Range targetRange = excelApp.Range["A1"];

O exemplo anterior também usa o recurso argumentos opcionais, que permite que você
omita Type.Missing .

As propriedades indexadas permitem escrever o código a seguir.

{language}

// Visual C# 2010.
targetRange.Value = "Name";

Não é possível criar propriedades indexadas de sua preferência. O recurso dá suporte


apenas ao consumo de propriedades indexadas existentes.

Exemplo
O código a seguir mostra um exemplo completo. Para obter mais informações sobre
como configurar um projeto que acessa a API do Office, consulte Como acessar objetos
de interoperabilidade do Office usando recursos do Visual C#.

{language}

// You must add a reference to Microsoft.Office.Interop.Excel to run


// this example.
using System;
using Excel = Microsoft.Office.Interop.Excel;

namespace IndexedProperties
{
class Program
{
static void Main(string[] args)
{
CSharp2010();
}

static void CSharp2010()


{
var excelApp = new Excel.Application();
excelApp.Workbooks.Add();
excelApp.Visible = true;
Excel.Range targetRange = excelApp.Range["A1"];
targetRange.Value = "Name";
}

static void CSharp2008()


{
var excelApp = new Excel.Application();
excelApp.Workbooks.Add(Type.Missing);
excelApp.Visible = true;

Excel.Range targetRange = excelApp.get_Range("A1",


Type.Missing);
targetRange.set_Value(Type.Missing, "Name");
// Or
//targetRange.Value2 = "Name";
}
}
}

Confira também
Argumentos nomeados e opcionais
dinâmico
Usando o tipo dynamic

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.
Como acessar objetos de
interoperabilidade do Office
Artigo • 09/03/2023

O C# tem recursos que simplificam o acesso a objetos de API do Office. Os novos


recursos incluem argumentos nomeados e opcionais, um novo tipo chamado dynamic e
a capacidade de passar argumentos para parâmetros de referência em métodos COM
como se fossem parâmetros de valor.

Neste artigo, você usará os novos recursos para escrever código que cria e exibe uma
planilha do Microsoft Office Excel. Escreva o código para adicionar um documento do
Office Word que contenha um ícone que esteja vinculado à planilha do Excel.

Para concluir este passo a passo, você deve ter o Microsoft Office Excel 2007 e o
Microsoft Office Word 2007 ou versões posteriores, instaladas no computador.

7 Observação

Seu computador pode mostrar diferentes nomes ou locais para alguns dos
elementos de interface do usuário do Visual Studio nas instruções a seguir. A
edição do Visual Studio que você possui e as configurações que você usa
determinam esses elementos. Para obter mais informações, consulte
Personalizando o IDE.

) Importante

O VSTO se baseia no .NET Framework. Os suplementos COM também podem ser


gravados com o .NET Framework. Os suplementos do Office não podem ser criados
com o .NET Core e o .NET 5+, as versões mais recentes do .NET. Isso ocorre porque
o .NET Core/.NET 5+ não pode trabalhar em conjunto com o .NET Framework no
mesmo processo e pode levar a falhas de carga de suplemento. Você pode
continuar a usar o .NET Framework para escrever suplementos VSTO e COM para o
Office. A Microsoft não atualizará o VSTO ou a plataforma de suplemento COM
para usar o .NET Core ou o .NET 5+. Você pode aproveitar o .NET Core e o .NET 5+,
incluindo o ASP.NET Core, para criar o lado do servidor dos Suplementos Web do
Office.

Para criar um novo aplicativo de console


1. Inicie o Visual Studio.
2. No menu Arquivo , aponte para Novoe selecione Projeto. A caixa de diálogo Novo
Projeto aparecerá.
3. No painel Modelos Instalados, expanda C# e selecione Windows.
4. Observe a parte superior da caixa de diálogo Novo Projeto para selecionar .NET
Framework 4 (ou versão posterior) como uma estrutura de destino.
5. No painel Modelos, selecione Aplicativo de Console.
6. Digite um nome para o projeto no campo Nome.
7. Selecione OK.

O novo projeto aparece no Gerenciador de Soluções.

Para adicionar referências


1. No Gerenciador de Soluções, clique com o botão direito do mouse no nome do
projeto e, em seguida, selecione Adicionar Referência. A caixa de diálogo
Adicionar Referência é exibida.
2. Na página Assemblies, selecione Microsoft.Office.Interop.Word na lista Nome do
Componente e, mantendo a tecla CTRL pressionada, selecione
Microsoft.Office.Interop.Excel. Se você não vir os assemblies, talvez seja
necessário instalá-los. Confira Como instalar assemblies de interoperabilidade
primários do Office.
3. Selecione OK.

Para adicionar as diretivas using necessárias


No Gerenciador de Soluções, clique com o botão direito do mouse no arquivo
Program.cs e, em seguida, selecione Exibir Código. Adicione as seguintes diretivas
using na parte superior do arquivo de código:

C#

using Excel = Microsoft.Office.Interop.Excel;


using Word = Microsoft.Office.Interop.Word;

Para criar uma lista de contas bancárias


Cole a seguinte definição de classe em Program.cs, na classe Program .

C#
public class Account
{
public int ID { get; set; }
public double Balance { get; set; }
}

Adicione o seguinte código ao método Main para criar uma lista bankAccounts que
contenha duas contas.

C#

// Create a list of accounts.


var bankAccounts = new List<Account> {
new Account {
ID = 345678,
Balance = 541.27
},
new Account {
ID = 1230221,
Balance = -127.44
}
};

Para declarar um método que exporta as


informações de conta para o Excel
1. Adicione o método a seguir à classe Program para configurar uma planilha do
Excel. O método Add tem um parâmetro opcional para especificar um modelo
específico. Os parâmetros opcionais permitem omitir o argumento para esse
parâmetro, se você deseja usar o valor padrão do parâmetro. Como você não
forneceu um argumento, Add usará o modelo padrão e criará uma nova pasta de
trabalho. A instrução equivalente em versões anteriores do C# requer um
argumento de espaço reservado: ExcelApp.Workbooks.Add(Type.Missing) .

C#

static void DisplayInExcel(IEnumerable<Account> accounts)


{
var excelApp = new Excel.Application();
// Make the object visible.
excelApp.Visible = true;

// Create a new, empty workbook and add it to the collection returned


// by property Workbooks. The new workbook becomes the active workbook.
// Add has an optional parameter for specifying a particular template.
// Because no argument is sent in this example, Add creates a new
workbook.
excelApp.Workbooks.Add();

// This example uses a single workSheet. The explicit type casting is


// removed in a later procedure.
Excel._Worksheet workSheet = (Excel.Worksheet)excelApp.ActiveSheet;
}

Adicione o código a seguir no final de DisplayInExcel . O código insere valores nas duas
primeiras colunas da primeira linha da planilha.

C#

// Establish column headings in cells A1 and B1.


workSheet.Cells[1, "A"] = "ID Number";
workSheet.Cells[1, "B"] = "Current Balance";

Adicione o código a seguir no final de DisplayInExcel . O loop foreach coloca as


informações da lista de contas nas duas primeiras colunas de sucessivas linhas da
planilha.

C#

var row = 1;
foreach (var acct in accounts)
{
row++;
workSheet.Cells[row, "A"] = acct.ID;
workSheet.Cells[row, "B"] = acct.Balance;
}

Adicione o seguinte código no final de DisplayInExcel para ajustar as larguras das


colunas para adequar o conteúdo.

C#

workSheet.Columns[1].AutoFit();
workSheet.Columns[2].AutoFit();

As versões anteriores do C# exigem a conversão explícita para essas operações, porque


ExcelApp.Columns[1] retorna um Object , e AutoFit é um método Range do Excel. As

linhas a seguir mostram a conversão.

C#
((Excel.Range)workSheet.Columns[1]).AutoFit();
((Excel.Range)workSheet.Columns[2]).AutoFit();

O C# converte o Object retornado em dynamic automaticamente, se o assembly for


referenciado pela opção do compilador EmbedInteropTypes ou, de maneira
equivalente, se a propriedade Inserir Tipos de Interoperabilidade do Excel for true. True
é o valor padrão para essa propriedade.

Para executar o projeto


Adicione a seguinte linha no final de Main .

C#

// Display the list in an Excel spreadsheet.


DisplayInExcel(bankAccounts);

Pressione CTRL+F5. Uma planilha do Excel é exibida contendo os dados das duas
contas.

Para adicionar um documento do Word


O código a seguir abre um aplicativo do Word e cria um ícone que é vinculado à
planilha do Excel. Cole o método CreateIconInWordDoc , fornecido posteriormente nesta
etapa, na classe Program . O CreateIconInWordDoc usa argumentos nomeados e
opcionais para reduzir a complexidade das chamadas de método a Add e PasteSpecial.
Essas chamadas incorporam dois recursos que simplificam as chamadas para métodos
COM que possuem parâmetros de referência. Primeiro, você pode enviar argumentos
para os parâmetros de referência como se fossem parâmetros de valor. Ou seja, você
pode enviar valores diretamente, sem criar uma variável para cada parâmetro de
referência. O compilador gera variáveis temporárias para conter os valores de
argumento e descarta as variáveis quando você retornar da chamada. Em segundo
lugar, você pode omitir a palavra-chave ref na lista de argumentos.

O método Add tem quatro parâmetros de referência que são opcionais. Você poderá
omitir argumentos para alguns ou todos os parâmetros se desejar usar os valores
padrão.

O método PasteSpecial insere o conteúdo da área de transferência. O método tem sete


parâmetros de referência que são opcionais. O código a seguir especifica argumentos
para dois deles: Link , para criar um link para a origem do conteúdo da área de
transferência, e DisplayAsIcon , para exibir o link como um ícone. Você pode usar
argumentos nomeados para esses dois argumentos e omitir os outros. Embora esses
argumentos sejam parâmetros de referência, você não precisa usar a palavra-chave ref
ou criar variáveis para os enviar como argumentos. Você pode enviar os valores
diretamente.

C#

static void CreateIconInWordDoc()


{
var wordApp = new Word.Application();
wordApp.Visible = true;

// The Add method has four reference parameters, all of which are
// optional. Visual C# allows you to omit arguments for them if
// the default values are what you want.
wordApp.Documents.Add();

// PasteSpecial has seven reference parameters, all of which are


// optional. This example uses named arguments to specify values
// for two of the parameters. Although these are reference
// parameters, you do not need to use the ref keyword, or to create
// variables to send in as arguments. You can send the values directly.
wordApp.Selection.PasteSpecial( Link: true, DisplayAsIcon: true);
}

Adicione a instrução a seguir no final de Main .

C#

// Create a Word document that contains an icon that links to


// the spreadsheet.
CreateIconInWordDoc();

Adicione a instrução a seguir no final de DisplayInExcel . O método Copy adiciona a


planilha na área de transferência.

C#

// Put the spreadsheet contents on the clipboard. The Copy method has one
// optional parameter for specifying a destination. Because no argument
// is sent, the destination is the Clipboard.
workSheet.Range["A1:B3"].Copy();

Pressione CTRL+F5. Um documento do Word é exibido contendo um ícone. Clique duas


vezes no ícone para colocar a planilha no primeiro plano.
Para definir a propriedade Inserir Tipos Interop
Outras melhorias são possíveis quando você chama um tipo COM que não requer um
assembly de interoperabilidade primário (PIA) no tempo de execução. A remoção da
dependência nos PIAs resulta na independência de versão e em uma implantação mais
fácil. Para obter mais informações sobre as vantagens da programação sem PIAs,
consulte Passo a passo: inserindo tipos de assemblies gerenciados.

Além disso, a programação é mais fácil porque o tipo dynamic representa os tipos
necessários e retornados declarados em métodos COM. Variáveis com o tipo dynamic
não são avaliadas até o tempo de execução, o que elimina a necessidade de conversão
explícita. Para obter mais informações, veja Usando o tipo dynamic.

A inserção de informações de tipo, em vez do uso de PIAs, é o comportamento padrão.


Devido a esse padrão, vários dos exemplos anteriores são simplificados. Você não
precisa de nenhuma conversão explícita. Por exemplo, a declaração de worksheet em
DisplayInExcel é escrita como Excel._Worksheet workSheet = excelApp.ActiveSheet , em

vez de Excel._Worksheet workSheet = (Excel.Worksheet)excelApp.ActiveSheet . As


chamadas para AutoFit no mesmo método também exigem conversão explícita sem o
padrão, pois ExcelApp.Columns[1] retorna um Object , e AutoFit é um método do Excel.
O código a seguir mostra a conversão.

C#

((Excel.Range)workSheet.Columns[1]).AutoFit();
((Excel.Range)workSheet.Columns[2]).AutoFit();

Para alterar o padrão e usar PIAs em vez de inserir informações de tipo, expanda o nó
Referências no Gerenciador de Soluções e, em seguida, selecione
Microsoft.Office.Interop.Excel ou Microsoft.Office.Interop.Word. Se você não
conseguir ver a janela Propriedades, pressione F4. Localize Inserir Tipos Interop na lista
de propriedades e altere seu valor para False. De maneira equivalente, você pode
compilar usando a opção do compilador Referências, em vez de EmbedInteropTypes
em um prompt de comando.

Para adicionar formatação adicional à tabela


Substitua as duas chamadas para AutoFit em DisplayInExcel pela instrução a seguir.

C#
// Call to AutoFormat in Visual C# 2010.
workSheet.Range["A1", "B3"].AutoFormat(
Excel.XlRangeAutoFormat.xlRangeAutoFormatClassic2);

O método AutoFormat tem sete parâmetros de valor, que são opcionais. Argumentos
nomeados e opcionais permitem que você forneça argumentos para nenhum, alguns ou
todos eles. Na instrução anterior, você fornece um argumento para apenas um dos
parâmetros, Format . Como Format é o primeiro parâmetro na lista de parâmetros, você
não precisará fornecer o nome do parâmetro. No entanto, poderá ser mais fácil
entender a instrução se você incluir o nome do parâmetro, conforme mostrado no
código a seguir.

C#

// Call to AutoFormat in Visual C# 2010.


workSheet.Range["A1", "B3"].AutoFormat(Format:
Excel.XlRangeAutoFormat.xlRangeAutoFormatClassic2);

Pressione CTRL + F5 para ver o resultado. Você pode encontrar outros formatos na lista
na enumeração XlRangeAutoFormat.

Exemplo
O código a seguir mostra um exemplo completo.

C#

using System.Collections.Generic;
using Excel = Microsoft.Office.Interop.Excel;
using Word = Microsoft.Office.Interop.Word;

namespace OfficeProgrammingWalkthruComplete
{
class Walkthrough
{
static void Main(string[] args)
{
// Create a list of accounts.
var bankAccounts = new List<Account>
{
new Account {
ID = 345678,
Balance = 541.27
},
new Account {
ID = 1230221,
Balance = -127.44
}
};

// Display the list in an Excel spreadsheet.


DisplayInExcel(bankAccounts);

// Create a Word document that contains an icon that links to


// the spreadsheet.
CreateIconInWordDoc();
}

static void DisplayInExcel(IEnumerable<Account> accounts)


{
var excelApp = new Excel.Application();
// Make the object visible.
excelApp.Visible = true;

// Create a new, empty workbook and add it to the collection


returned
// by property Workbooks. The new workbook becomes the active
workbook.
// Add has an optional parameter for specifying a particular
template.
// Because no argument is sent in this example, Add creates a
new workbook.
excelApp.Workbooks.Add();

// This example uses a single workSheet.


Excel._Worksheet workSheet = excelApp.ActiveSheet;

// Earlier versions of C# require explicit casting.


//Excel._Worksheet workSheet =
(Excel.Worksheet)excelApp.ActiveSheet;

// Establish column headings in cells A1 and B1.


workSheet.Cells[1, "A"] = "ID Number";
workSheet.Cells[1, "B"] = "Current Balance";

var row = 1;
foreach (var acct in accounts)
{
row++;
workSheet.Cells[row, "A"] = acct.ID;
workSheet.Cells[row, "B"] = acct.Balance;
}

workSheet.Columns[1].AutoFit();
workSheet.Columns[2].AutoFit();

// Call to AutoFormat in Visual C#. This statement replaces the


// two calls to AutoFit.
workSheet.Range["A1", "B3"].AutoFormat(
Excel.XlRangeAutoFormat.xlRangeAutoFormatClassic2);
// Put the spreadsheet contents on the clipboard. The Copy
method has one
// optional parameter for specifying a destination. Because no
argument
// is sent, the destination is the Clipboard.
workSheet.Range["A1:B3"].Copy();
}

static void CreateIconInWordDoc()


{
var wordApp = new Word.Application();
wordApp.Visible = true;

// The Add method has four reference parameters, all of which


are
// optional. Visual C# allows you to omit arguments for them if
// the default values are what you want.
wordApp.Documents.Add();

// PasteSpecial has seven reference parameters, all of which are


// optional. This example uses named arguments to specify values
// for two of the parameters. Although these are reference
// parameters, you do not need to use the ref keyword, or to
create
// variables to send in as arguments. You can send the values
directly.
wordApp.Selection.PasteSpecial(Link: true, DisplayAsIcon: true);
}
}

public class Account


{
public int ID { get; set; }
public double Balance { get; set; }
}
}

Confira também
Type.Missing
dinâmico
Argumentos nomeados e opcionais
Como usar argumentos nomeados e opcionais na programação do Office
Como usar argumentos nomeados e
opcionais na programação do Office
Artigo • 07/06/2024

Os argumentos nomeados e opcionais aprimoram a conveniência, a flexibilidade e a


legibilidade na programação em C#. Além disso, esses recursos facilitam bastante o
acesso a interfaces COM, como as APIs de Automação do Microsoft Office.

) Importante

O Visual Studio Tools para Office (VSTO) depende do .NET Framework. Os


suplementos COM também podem ser gravados com o .NET Framework. Os
suplementos do Office não podem ser criados com o .NET Core e o .NET 5+, as
versões mais recentes do .NET. Isso ocorre porque o .NET Core/.NET 5+ não pode
trabalhar em conjunto com o .NET Framework no mesmo processo e pode levar a
falhas de carga de suplemento. Você pode continuar a usar o .NET Framework para
escrever suplementos VSTO e COM para o Office. A Microsoft não atualizará o
VSTO ou a plataforma de suplemento COM para usar o .NET Core ou o .NET 5+.
Você pode aproveitar o .NET Core e o .NET 5+, incluindo o ASP.NET Core, para criar
o lado do servidor dos Suplementos Web do Office.

No exemplo a seguir, o método ConvertToTable tem 16 parâmetros que representam as


características de uma tabela, como o número de colunas e linhas, formatação, bordas,
fontes e cores. Todos os 16 parâmetros são opcionais, pois na maioria das vezes você
não deseja especificar determinados valores para todos eles. No entanto, sem
argumentos nomeados e opcionais, é preciso fornecer um valor ou um valor de espaço
reservado. Com argumentos nomeados e opcionais, você especifica somente os valores
para os parâmetros necessários para seu projeto.

Você deve ter o Microsoft Office Word instalado em seu computador para concluir esses
procedimentos.

7 Observação

Seu computador pode mostrar diferentes nomes ou locais para alguns dos
elementos de interface do usuário do Visual Studio nas instruções a seguir. A
edição do Visual Studio que você possui e as configurações que você usa
determinam esses elementos. Para obter mais informações, consulte
Personalizando o IDE.

Crie um novo aplicativo de console


Inicie o Visual Studio. No menu Arquivo , aponte para Novoe selecione Projeto. No
painel Categorias de Modelos, expanda C# e selecione Windows. Observe a parte
superior do painel Modelos para se certificar de que .NET Framework 4 é exibido na
caixa Estrutura de Destino. No painel Modelos, selecione Aplicativo de Console. Digite
um nome para o projeto no campo Nome. Selecione OK. O novo projeto aparece no
Gerenciador de Soluções.

Adicionar uma referência


No Gerenciador de Soluções, clique com o botão direito do mouse no nome do projeto
e, em seguida, selecione Adicionar Referência. A caixa de diálogo Adicionar Referência
é exibida. Na página .NET, selecione Microsoft.Office.Interop.Word na lista Nome do
Componente. Selecione OK.

Adicionar as diretivas using necessárias


No Gerenciador de Soluções, clique com o botão direito do mouse no arquivo
Program.cs e, em seguida, selecione Exibir Código. Adicione as seguintes diretivas
using na parte superior do arquivo de código:

C#

using Word = Microsoft.Office.Interop.Word;

Exibir texto em um documento do Word


Na classe Program em Program.cs, adicione o seguinte método para criar um aplicativo
do Word e um documento do Word. O método Add tem quatro parâmetros opcionais.
Este exemplo usa os valores padrão. Portanto, nenhum argumento é necessário na
instrução de chamada.

C#
static void DisplayInWord()
{
var wordApp = new Word.Application();
wordApp.Visible = true;
// docs is a collection of all the Document objects currently
// open in Word.
Word.Documents docs = wordApp.Documents;

// Add a document to the collection and name it doc.


Word.Document doc = docs.Add();
}

Adicione o código a seguir ao final do método para definir onde exibir o texto no
documento e o texto a ser exibido:

C#

// Define a range, a contiguous area in the document, by specifying


// a starting and ending character position. Currently, the document
// is empty.
Word.Range range = doc.Range(0, 0);

// Use the InsertAfter method to insert a string at the end of the


// current range.
range.InsertAfter("Testing, testing, testing. . .");

Executar o aplicativo
Adicione a seguinte instrução a Main:

C#

DisplayInWord();

Pressione CTRL + F5 para executar o projeto. É exibido um documento do Word


contendo o texto especificado.

Alterar o texto para uma tabela


Use o método ConvertToTable para colocar o texto em uma tabela. O método tem 16
parâmetros opcionais. O IntelliSense coloca os parâmetros opcionais entre colchetes,
como mostrado na ilustração a seguir. Os valores padrão de Type.Missing são o nome
simples para System.Type.Missing .
Os argumentos nomeados e opcionais permitem que você especifique valores apenas
para os parâmetros que deseja alterar. Adicione o seguinte código ao final do método
DisplayInWord para criar uma tabela. O argumento especifica que as vírgulas na cadeia

de caracteres de texto em range separam as células da tabela.

C#

// Convert to a simple table. The table will have a single row with
// three columns.
range.ConvertToTable(Separator: ",");

Pressione CTRL + F5 para executar o projeto.

Experimentar outros parâmetros


Altere a tabela para que ela tenha uma coluna e três linhas, substitua a última linha em
DisplayInWord pela instrução a seguir e digite CTRL + F5 .

C#

range.ConvertToTable(Separator: ",", AutoFit: true, NumColumns: 1);

Especifique um formato predefinido para a tabela, substitua a última linha em


DisplayInWord pela instrução a seguir e digite CTRL + F5 . O formato pode ser qualquer

uma das constantes WdTableFormat.

C#

range.ConvertToTable(Separator: ",", AutoFit: true, NumColumns: 1,


Format: Word.WdTableFormat.wdTableFormatElegant);

Exemplo
O código a seguir inclui o exemplo completo:
C#

using System;
using Word = Microsoft.Office.Interop.Word;

namespace OfficeHowTo
{
class WordProgram
{
static void Main(string[] args)
{
DisplayInWord();
}

static void DisplayInWord()


{
var wordApp = new Word.Application();
wordApp.Visible = true;
// docs is a collection of all the Document objects currently
// open in Word.
Word.Documents docs = wordApp.Documents;

// Add a document to the collection and name it doc.


Word.Document doc = docs.Add();

// Define a range, a contiguous area in the document, by


specifying
// a starting and ending character position. Currently, the
document
// is empty.
Word.Range range = doc.Range(0, 0);

// Use the InsertAfter method to insert a string at the end of


the
// current range.
range.InsertAfter("Testing, testing, testing. . .");

// You can comment out any or all of the following statements to


// see the effect of each one in the Word document.

// Next, use the ConvertToTable method to put the text into a


table.
// The method has 16 optional parameters. You only have to
specify
// values for those you want to change.

// Convert to a simple table. The table will have a single row


with
// three columns.
range.ConvertToTable(Separator: ",");

// Change to a single column with three rows..


range.ConvertToTable(Separator: ",", AutoFit: true, NumColumns:
1);
// Format the table.
range.ConvertToTable(Separator: ",", AutoFit: true, NumColumns:
1,
Format: Word.WdTableFormat.wdTableFormatElegant);
}
}
}

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
Usar o tipo dinâmico
Artigo • 16/06/2023

O tipo dynamic é estático, mas um objeto do tipo dynamic ignora a verificação de tipo
estático. Na maioria dos casos, ele funciona como se tivesse o tipo object . O
compilador pressupõe que um elemento dynamic dá suporte a qualquer operação.
Portanto, você não precisa determinar se o objeto obtém seu valor de uma API COM, de
uma linguagem dinâmica como o IronPython, do HTML DOM (Modelo de Objeto do
Documento), a reflexão ou de algum outro lugar no programa. No entanto, se o código
não for válido, os erros aparecerão em tempo de execução.

Por exemplo, se método de instância exampleMethod1 no código a seguir tiver apenas


um parâmetro, o compilador reconhecerá que a primeira chamada para o método,
ec.exampleMethod1(10, 4) , não será válido porque ele contém dois argumentos. Essa

chamada causa um erro do compilador. O compilador não verifica a segunda chamada


para o método, dynamic_ec.exampleMethod1(10, 4) , porque o tipo dynamic_ec é
dynamic . Portanto, nenhum erro de compilador é relatado. No entanto, o erro não passa

despercebido indefinidamente. Ele aparece em tempo de execução e causa uma


exceção em tempo de execução.

C#

static void Main(string[] args)


{
ExampleClass ec = new ExampleClass();
// The following call to exampleMethod1 causes a compiler error
// if exampleMethod1 has only one parameter. Uncomment the line
// to see the error.
//ec.exampleMethod1(10, 4);

dynamic dynamic_ec = new ExampleClass();


// The following line is not identified as an error by the
// compiler, but it causes a run-time exception.
dynamic_ec.exampleMethod1(10, 4);

// The following calls also do not cause compiler errors, whether


// appropriate methods exist or not.
dynamic_ec.someMethod("some argument", 7, null);
dynamic_ec.nonexistentMethod();
}

C#

class ExampleClass
{
public ExampleClass() { }
public ExampleClass(int v) { }

public void exampleMethod1(int i) { }

public void exampleMethod2(string str) { }


}

A função do compilador nesses exemplos é reunir informações sobre o que cada


instrução está propondo fazer para o objeto ou expressão que tem o tipo dynamic . O
runtime examina as informações armazenadas e qualquer instrução que não seja válida
causa uma exceção em tempo de execução.

O resultado de operações mais dinâmicas é dynamic . Por exemplo, se você passar o


ponteiro do mouse sobre o uso de testSum no exemplo a seguir, o IntelliSense exibirá o
tipo (variável local) testSum dinâmico.

C#

dynamic d = 1;
var testSum = d + 3;
// Rest the mouse pointer over testSum in the following statement.
System.Console.WriteLine(testSum);

As operações em que o resultado não é dynamic incluem:

Conversões de dynamic em outro tipo.


Chamadas de construtor que incluem argumentos do tipo dynamic .

Por exemplo, o tipo de testInstance na seguinte declaração é ExampleClass e não


dynamic :

C#

var testInstance = new ExampleClass(d);

Conversões
As conversões entre objetos dinâmicos e outros tipos são fáceis. As conversões
permitem que o desenvolvedor alterne entre o comportamento dinâmico e o não
dinâmico.

Você pode converter qualquer um para dynamic implicitamente, conforme mostrado


nos exemplos a seguir.
C#

dynamic d1 = 7;
dynamic d2 = "a string";
dynamic d3 = System.DateTime.Today;
dynamic d4 = System.Diagnostics.Process.GetProcesses();

Por outro lado, você pode aplicar dinamicamente qualquer conversão implícita a
qualquer expressão do tipo dynamic .

C#

int i = d1;
string str = d2;
DateTime dt = d3;
System.Diagnostics.Process[] procs = d4;

Resolução de sobrecarga com argumentos de


tipo dynamic
A resolução de sobrecarga ocorre em tempo de execução em vez de em tempo de
compilação se um ou mais dos argumentos em uma chamada de método tem o tipo
dynamic ou se o receptor da chamada do método é do tipo dynamic . No exemplo a

seguir, se o único método exampleMethod2 acessível for definido para obter um


argumento de cadeia de caracteres, enviar d1 como o argumento não causará um erro
de compilador, mas causará uma exceção de tempo de execução. A resolução de
sobrecarga falha em tempo de execução porque o tipo de tempo de execução de d1 é
int e exampleMethod2 requer uma cadeia de caracteres.

C#

// Valid.
ec.exampleMethod2("a string");

// The following statement does not cause a compiler error, even though ec
is not
// dynamic. A run-time exception is raised because the run-time type of d1
is int.
ec.exampleMethod2(d1);
// The following statement does cause a compiler error.
//ec.exampleMethod2(7);
runtime de linguagem dinâmico
O DLR (tempo de execução de linguagem dinâmica) fornece a infraestrutura que dá
suporte ao tipo dynamic em C# e também à implementação das linguagens de
programação dinâmicas como IronPython e IronRuby. Para obter mais informações
sobre o DLR, consulte Visão geral do Dynamic Language Runtime.

interoperabilidade COM
Muitos métodos COM permitem variação nos tipos de argumento e tipo de retorno,
especificando os tipos como object . A interoperabilidade COM exigiu a conversão
explícita dos valores para coordenar com variáveis fortemente tipadas no C#. Se você
compilar usando a opção EmbedInteropTypes (opções do compilador C#), a introdução
do tipo dynamic permitirá tratar as ocorrências de object em assinaturas COM, como se
fossem do tipo dynamic e, portanto, evitar grande parte da conversão. Para obter mais
informações sobre como usar o tipo dynamic com objetos COM, confira o artigo sobre
Como acessar objetos de interoperabilidade do Office usando recursos C#.

Artigos relacionados
Título Descrição

dinâmico Descreve o uso da palavra-chave dynamic .

Visão geral do Dynamic Fornece uma visão geral do DLR, que é um ambiente de tempo de
Language Runtime execução que adiciona um conjunto de serviços para as linguagens
dinâmicas para o CLR (Common Language Runtime).

Passo a passo: Criando Fornece instruções passo a passo para criar um objeto dinâmico
e usando objetos personalizado e para criar um projeto que acessa uma biblioteca
dinâmicos IronPython .

6 Collaborate with us on
GitHub .NET feedback
The .NET documentation is open
The source for this content can
be found on GitHub, where you source. Provide feedback here.
can also create and review
 Open a documentation issue
issues and pull requests. For
 Provide product feedback
more information, see our
contributor guide.
Passo a passo: criar e usar objetos
dinâmicos em C#
Artigo • 09/05/2023

Os objetos dinâmicos expõem membros como propriedades e métodos em tempo de


execução, em vez de em tempo de compilação. Os objetos dinâmicos permitem que
você crie objetos para trabalhar com estruturas que não correspondem a um formato
ou tipo estático. Por exemplo, você pode usar um objeto dinâmico para fazer referência
ao DOM (Modelo de Objeto do Documento) HTML, que pode conter qualquer
combinação de atributos e elementos de marcação HTML válidos. Como cada
documento HTML é único, os membros de um determinado documento HTML são
determinados em tempo de execução. Um método comum para fazer referência a um
atributo de um elemento HTML é passar o nome do atributo para o método
GetProperty do elemento. Para fazer referência ao atributo id do elemento HTML <div
id="Div1"> , primeiro você obtém uma referência ao elemento <div> e, depois, usa

divElement.GetProperty("id") . Se usar um objeto dinâmico, você poderá fazer

referência ao atributo id como divElement.id .

Objetos dinâmicos também fornecem acesso conveniente a linguagens dinâmicas,


como IronPython e IronRuby. É possível usar um objeto dinâmico para fazer referência a
um script dinâmico interpretado em tempo de execução.

Você faz referência a um objeto dinâmico usando a associação tardia. Especifique o tipo
de um objeto com associação tardia como dynamic . Para obter mais informações,
confira dynamic.

Você pode criar objetos dinâmicos personalizados usando classes no namespace


System.Dynamic. Por exemplo, é possível criar um ExpandoObject e especificar os
membros desse objeto em tempo de execução. Você também pode criar seu próprio
tipo que herda da classe DynamicObject. Em seguida, você pode substituir os membros
da classe DynamicObject para fornecer funcionalidade dinâmica de tempo de execução.

Este artigo contém dois passo a passo independentes:

Criar um objeto personalizado que expõe dinamicamente o conteúdo de um


arquivo de texto como propriedades de um objeto.
Criar um projeto que usa uma biblioteca IronPython .

Pré-requisitos
Visual Studio 2022 versão 17.3 ou uma versão posterior com a carga de trabalho
de desenvolvimento para desktop com o .NET instalada. O SDK do .NET 7 é
incluído quando você seleciona essa carga de trabalho.

7 Observação

Seu computador pode mostrar diferentes nomes ou locais para alguns dos
elementos de interface do usuário do Visual Studio nas instruções a seguir. A
edição do Visual Studio que você possui e as configurações que você usa
determinam esses elementos. Para obter mais informações, consulte
Personalizando o IDE.

Para o segundo passo a passo, instale o IronPython para .NET. Navegue até a
respectiva página de download para obter a versão mais recente.

Criar um objeto dinâmico personalizado


O primeiro passo a passo define um objeto dinâmico personalizado que pesquisa o
conteúdo de um arquivo de texto. Uma propriedade dinâmica especifica o texto a ser
pesquisado. Por exemplo, se o código de chamada especificar dynamicFile.Sample , a
classe dinâmica retornará uma lista genérica de cadeias de caracteres que contém todas
as linhas do arquivo que começam com "Sample". A pesquisa diferencia maiúsculas de
minúsculas. A classe dinâmica também dá suporte a dois argumentos opcionais. O
primeiro argumento é um valor de enum de opção de pesquisa que especifica que a
classe dinâmica deve pesquisar por correspondências no início da linha, no final da linha
ou em qualquer lugar da linha. O segundo argumento especifica que a classe dinâmica
deve cortar espaços iniciais e finais de cada linha antes de pesquisar. Por exemplo, se o
código de chamada especificar dynamicFile.Sample(StringSearchOption.Contains) , a
classe dinâmica pesquisará por "Sample" em qualquer lugar de uma linha. Se o código
de chamada especificar dynamicFile.Sample(StringSearchOption.StartsWith, false) , a
classe dinâmica pesquisará por "Sample" no início de cada linha e não removerá
espaços à direita e à esquerda. O comportamento padrão da classe dinâmica é
pesquisar por uma correspondência no início de cada linha e remover espaços à direita
e à esquerda.

Criar uma classe dinâmica personalizada


Inicie o Visual Studio. Selecione Criar um novo projeto. Na caixa de diálogo Criar um
novo projeto, selecione C#, selecione Aplicativo de Console e, em seguida, Avançar. Na
caixa de diálogo Configurar novo projeto, digite DynamicSample como Nome do
projeto e selecione em Avançar. Na caixa de diálogo Informações adicionais, selecione
.NET 7.0 (atual) como Estrutura de destino e, em seguida, selecione Criar. No
Gerenciador de Soluções, clique com o botão direito do mouse no projeto
DynamicSample e selecione Adicionar>Classe. Na caixa Nome, digite ReadOnlyFile e
selecione Adicionar. Na parte superior do arquivo ReadOnlyFile.cs ou ReadOnlyFile.vb,
adicione o código a seguir para importar os namespaces System.IO e System.Dynamic.

C#

using System.IO;
using System.Dynamic;

O objeto dinâmico personalizado usa um enum para determinar os critérios de


pesquisa. Antes da instrução de classe, adicione a seguinte definição de enum.

C#

public enum StringSearchOption


{
StartsWith,
Contains,
EndsWith
}

Atualize a instrução de classe para herdar a classe DynamicObject , conforme mostrado


no exemplo de código a seguir.

C#

class ReadOnlyFile : DynamicObject

Adicione o código a seguir para a classe ReadOnlyFile para definir um campo particular
para o caminho do arquivo e um construtor para a classe ReadOnlyFile .

C#

// Store the path to the file and the initial line count value.
private string p_filePath;

// Public constructor. Verify that file exists and store the path in
// the private variable.
public ReadOnlyFile(string filePath)
{
if (!File.Exists(filePath))
{
throw new Exception("File path does not exist.");
}

p_filePath = filePath;
}

1. Adicione o seguinte método GetPropertyValue à classe ReadOnlyFile . O método


GetPropertyValue usa, como entrada, critérios de pesquisa e retorna as linhas de

um arquivo de texto que correspondem a esse critério de pesquisa. Os métodos


dinâmicos fornecidos pela classe ReadOnlyFile chamam o método
GetPropertyValue para recuperar seus respectivos resultados.

C#

public List<string> GetPropertyValue(string propertyName,


StringSearchOption StringSearchOption =
StringSearchOption.StartsWith,
bool trimSpaces = true)
{
StreamReader sr = null;
List<string> results = new List<string>();
string line = "";
string testLine = "";

try
{
sr = new StreamReader(p_filePath);

while (!sr.EndOfStream)
{
line = sr.ReadLine();

// Perform a case-insensitive search by using the specified


search options.
testLine = line.ToUpper();
if (trimSpaces) { testLine = testLine.Trim(); }

switch (StringSearchOption)
{
case StringSearchOption.StartsWith:
if (testLine.StartsWith(propertyName.ToUpper())) {
results.Add(line); }
break;
case StringSearchOption.Contains:
if (testLine.Contains(propertyName.ToUpper())) {
results.Add(line); }
break;
case StringSearchOption.EndsWith:
if (testLine.EndsWith(propertyName.ToUpper())) {
results.Add(line); }
break;
}
}
}
catch
{
// Trap any exception that occurs in reading the file and return
null.
results = null;
}
finally
{
if (sr != null) {sr.Close();}
}

return results;
}

Após o método GetPropertyValue , adicione o seguinte código para substituir o método


TryGetMember da classe DynamicObject. O método TryGetMember é chamado quando
um membro de uma classe dinâmica é solicitado e nenhum argumento é especificado.
O argumento binder contém informações sobre o membro referenciado e o argumento
result faz referência ao resultado retornado para o membro especificado. O método

TryGetMember retorna um valor booliano que retorna true se o membro solicitado


existe; caso contrário, ele retorna false .

C#

// Implement the TryGetMember method of the DynamicObject class for dynamic


member calls.
public override bool TryGetMember(GetMemberBinder binder,
out object result)
{
result = GetPropertyValue(binder.Name);
return result == null ? false : true;
}

Após o método TryGetMember , adicione o seguinte código para substituir o método


TryInvokeMember da classe DynamicObject. O método TryInvokeMember é chamado
quando um membro de uma classe dinâmica é solicitado com argumentos. O
argumento binder contém informações sobre o membro referenciado e o argumento
result faz referência ao resultado retornado para o membro especificado. O

argumento args contém uma matriz de argumentos que são passados ao membro. O
método TryInvokeMember retorna um valor booliano que retorna true se o membro
solicitado existe; caso contrário, ele retorna false .

A versão personalizada do método TryInvokeMember espera que o primeiro argumento


seja um valor do enum StringSearchOption que você definiu na etapa anterior. O
método TryInvokeMember espera que o segundo argumento seja um valor booliano. Se
um ou os dois argumentos forem valores válidos, eles serão passados para o método
GetPropertyValue para recuperar os resultados.

C#

// Implement the TryInvokeMember method of the DynamicObject class for


// dynamic member calls that have arguments.
public override bool TryInvokeMember(InvokeMemberBinder binder,
object[] args,
out object result)
{
StringSearchOption StringSearchOption = StringSearchOption.StartsWith;
bool trimSpaces = true;

try
{
if (args.Length > 0) { StringSearchOption =
(StringSearchOption)args[0]; }
}
catch
{
throw new ArgumentException("StringSearchOption argument must be a
StringSearchOption enum value.");
}

try
{
if (args.Length > 1) { trimSpaces = (bool)args[1]; }
}
catch
{
throw new ArgumentException("trimSpaces argument must be a Boolean
value.");
}

result = GetPropertyValue(binder.Name, StringSearchOption, trimSpaces);

return result == null ? false : true;


}

Salve e feche o arquivo.

Criar um arquivo de texto de exemplo


No Gerenciador de Soluções, clique com o botão direito do mouse no projeto
DynamicSample e selecione Adicionar>Novo item. No painel Modelos Instalados,
selecione Geral e, então, selecione o modelo Arquivo de Texto. Deixe o nome padrão
de TextFile1.txt na caixa Nome e, em seguida, selecione Adicionar. Copie o seguinte
texto para o arquivo TextFile1.txt.

text

List of customers and suppliers

Supplier: Lucerne Publishing (https://www.lucernepublishing.com/)


Customer: Preston, Chris
Customer: Hines, Patrick
Customer: Cameron, Maria
Supplier: Graphic Design Institute (https://www.graphicdesigninstitute.com/)
Supplier: Fabrikam, Inc. (https://www.fabrikam.com/)
Customer: Seubert, Roxanne
Supplier: Proseware, Inc. (http://www.proseware.com/)
Customer: Adolphi, Stephan
Customer: Koch, Paul

Salve e feche o arquivo.

Criar um aplicativo de exemplo que usa o objeto


dinâmico personalizado
Em Gerenciador de Soluções, clique duas vezes no arquivo Program.cs. Adicione o
código a seguir ao procedimento Main para criar uma instância da classe ReadOnlyFile
para o arquivo TextFile1.txt. O código usa associação tardia para chamar membros
dinâmicos e recuperar linhas de texto que contêm a cadeia de caracteres "Customer".

C#

dynamic rFile = new ReadOnlyFile(@"..\..\..\TextFile1.txt");


foreach (string line in rFile.Customer)
{
Console.WriteLine(line);
}
Console.WriteLine("----------------------------");
foreach (string line in rFile.Customer(StringSearchOption.Contains, true))
{
Console.WriteLine(line);
}

Salve o arquivo e pressione Ctrl+ F5 para compilar e executar o aplicativo.

Chamar uma biblioteca de linguagem dinâmica


O próximo passo a passo cria um projeto que acessa uma biblioteca escrita na
linguagem dinâmica IronPython.

Para criar uma classe dinâmica personalizada


No Visual Studio, selecione Arquivo>Novo>Projeto. Na caixa de diálogo Criar um novo
projeto, selecione C#, selecione Aplicativo de Console e, em seguida, Avançar. Na caixa
de diálogo Configurar novo projeto, digite DynamicIronPythonSample como Nome do
projeto e selecione em Avançar. Na caixa de diálogo Informações adicionais, selecione
.NET 7.0 (atual) como Estrutura de destino e, em seguida, selecione Criar. Instale o
pacote NuGet do IronPython . Edite o arquivo Program.cs. Na parte superior do
arquivo, adicione o código a seguir para importar os namespaces
Microsoft.Scripting.Hosting e IronPython.Hosting das bibliotecas do IronPython e o

namespace System.Linq .

C#

using System.Linq;
using Microsoft.Scripting.Hosting;
using IronPython.Hosting;

No método Main, adicione o código a seguir para criar um novo objeto


Microsoft.Scripting.Hosting.ScriptRuntime para hospedar as bibliotecas do

IronPython. O objeto ScriptRuntime carrega o módulo random.py da biblioteca do


IronPython.

C#

// Set the current directory to the IronPython libraries.


System.IO.Directory.SetCurrentDirectory(
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) +
@"\IronPython 2.7\Lib");

// Create an instance of the random.py IronPython library.


Console.WriteLine("Loading random.py");
ScriptRuntime py = Python.CreateRuntime();
dynamic random = py.UseFile("random.py");
Console.WriteLine("random.py loaded.");

Após o código carregar o módulo random.py, adicione o seguinte código para criar uma
matriz de inteiros. A matriz é passada para o método shuffle do módulo random.py,
que classifica aleatoriamente os valores na matriz.

C#
// Initialize an enumerable set of integers.
int[] items = Enumerable.Range(1, 7).ToArray();

// Randomly shuffle the array of integers by using IronPython.


for (int i = 0; i < 5; i++)
{
random.shuffle(items);
foreach (int item in items)
{
Console.WriteLine(item);
}
Console.WriteLine("-------------------");
}

Salve o arquivo e pressione Ctrl+ F5 para compilar e executar o aplicativo.

Confira também
System.Dynamic
System.Dynamic.DynamicObject
Usando o tipo dynamic
dinâmico
Implementando interfaces dinâmicas (PDF para download do Microsoft TechNet)

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.
Reduce memory allocations using new
C# features
Article • 10/17/2023

) Important

The techniques described in this section improve performance when applied to hot
paths in your code. Hot paths are those sections of your codebase that are
executed often and repeatedly in normal operations. Applying these techniques to
code that isn't often executed will have minimal impact. Before making any
changes to improve performance, it's critical to measure a baseline. Then, analyze
that baseline to determine where memory bottlenecks occur. You can learn about
many cross platform tools to measure your application's performance in the section
on Diagnostics and instrumentation. You can practice a profiling session in the
tutorial to Measure memory usage in the Visual Studio documentation.

Once you've measured memory usage and have determined that you can reduce
allocations, use the techniques in this section to reduce allocations. After each
successive change, measure memory usage again. Make sure each change has a
positive impact on the memory usage in your application.

Performance work in .NET often means removing allocations from your code. Every
block of memory you allocate must eventually be freed. Fewer allocations reduce time
spent in garbage collection. It allows for more predictable execution time by removing
garbage collections from specific code paths.

A common tactic to reduce allocations is to change critical data structures from class
types to struct types. This change impacts the semantics of using those types.
Parameters and returns are now passed by value instead of by reference. The cost of
copying a value is negligible if the types are small, three words or less (considering one
word being of natural size of one integer). It's measurable and can have real
performance impact for larger types. To combat the effect of copying, developers can
pass these types by ref to get back the intended semantics.

The C# ref features give you the ability to express the desired semantics for struct
types without negatively impacting their overall usability. Prior to these enhancements,
developers needed to resort to unsafe constructs with pointers and raw memory to
achieve the same performance impact. The compiler generates verifiably safe code for
the new ref related features. Verifiably safe code means the compiler detects possible
buffer overruns or accessing unallocated or freed memory. The compiler detects and
prevents some errors.

Pass and return by reference


Variables in C# store values. In struct types, the value is the contents of an instance of
the type. In class types, the value is a reference to a block of memory that stores an
instance of the type. Adding the ref modifier means that the variable stores the
reference to the value. In struct types, the reference points to the storage containing
the value. In class types, the reference points to the storage containing the reference to
the block of memory.

In C#, parameters to methods are passed by value, and return values are return by value.
The value of the argument is passed to the method. The value of the return argument is
the return value.

The ref , in , ref readonly , or out modifier indicates that the argument is passed by
reference. A reference to the storage location is passed to the method. Adding ref to
the method signature means the return value is returned by reference. A reference to the
storage location is the return value.

You can also use ref assignment to have a variable refer to another variable. A typical
assignment copies the value of the right hand side to the variable on the left hand side
of the assignment. A ref assignment copies the memory location of the variable on the
right hand side to the variable on the left hand side. The ref now refers to the original
variable:

C#

int anInteger = 42; // assignment.


ref int location = ref anInteger; // ref assignment.
ref int sameLocation = ref location; // ref assignment

Console.WriteLine(location); // output: 42

sameLocation = 19; // assignment

Console.WriteLine(anInteger); // output: 19

When you assign a variable, you change its value. When you ref assign a variable, you
change what it refers to.
You can work directly with the storage for values using ref variables, pass by reference,
and ref assignment. Scope rules enforced by the compiler ensure safety when working
directly with storage.

The ref readonly and in modifiers both indicate that the argument should be passed
by reference and can't be reassigned in the method. The difference is that ref readonly
indicates that the method uses the parameter as a variable. The method might capture
the parameter, or it might return the parameter by readonly reference. In those cases,
you should use the ref readonly modifier. Otherwise, the in modifier offers more
flexibility. You don't need to add the in modifier to an argument for an in parameter,
so you can update existing API signatures safely using the in modifier. The compiler
issues a warning if you don't add either the ref or in modifier to an argument for a
ref readonly parameter.

Ref safe context


C# includes rules for ref expressions to ensure that a ref expression can't be accessed
where the storage it refers to is no longer valid. Consider the following example:

C#

public ref int CantEscape()


{
int index = 42;
return ref index; // Error: index's ref safe context is the body of
CantEscape
}

The compiler reports an error because you can't return a reference to a local variable
from a method. The caller can't access the storage being referred to. The ref safe context
defines the scope in which a ref expression is safe to access or modify. The following
table lists the ref safe contexts for variable types. ref fields can't be declared in a class
or a non-ref struct , so those rows aren't in the table:

ノ Expand table

Declaration ref safe context

non-ref local block where local is declared

non-ref parameter current method

ref , ref readonly , in parameter calling method


Declaration ref safe context

out parameter current method

class field calling method

non-ref struct field current method

ref field of ref struct calling method

A variable can be ref returned if its ref safe context is the calling method. If its ref safe
context is the current method or a block, ref return is disallowed. The following snippet
shows two examples. A member field can be accessed from the scope calling a method,
so a class or struct field's ref safe context is the calling method. The ref safe context for a
parameter with the ref , or in modifiers is the entire method. Both can be ref returned
from a member method:

C#

private int anIndex;

public ref int RetrieveIndexRef()


{
return ref anIndex;
}

public ref int RefMin(ref int left, ref int right)


{
if (left < right)
return ref left;
else
return ref right;
}

7 Note

When the ref readonly or in modifier is applied to a parameter, that parameter


can be returned by ref readonly , not ref .

The compiler ensures that a reference can't escape its ref safe context. You can use ref
parameters, ref return , and ref local variables safely because the compiler detects if
you've accidentally written code where a ref expression could be accessed when its
storage isn't valid.
Safe context and ref structs
ref struct types require more rules to ensure they can be used safely. A ref struct

type can include ref fields. That requires the introduction of a safe context. For most
types, the safe context is the calling method. In other words, a value that's not a ref
struct can always be returned from a method.

Informally, the safe context for a ref struct is the scope where all of its ref fields can
be accessed. In other words, it's the intersection of the ref safe context of all its ref
fields. The following method returns a ReadOnlySpan<char> to a member field, so its safe
context is the method:

C#

private string longMessage = "This is a long message";

public ReadOnlySpan<char> Safe()


{
var span = longMessage.AsSpan();
return span;
}

In contrast, the following code emits an error because the ref field member of the
Span<int> refers to the stack allocated array of integers. It can't escape the method:

C#

public Span<int> M()


{
int length = 3;
Span<int> numbers = stackalloc int[length];
for (var i = 0; i < length; i++)
{
numbers[i] = i;
}
return numbers; // Error! numbers can't escape this method.
}

Unify memory types


The introduction of System.Span<T> and System.Memory<T> provide a unified model
for working with memory. System.ReadOnlySpan<T> and System.ReadOnlyMemory<T>
provide readonly versions for accessing memory. They all provide an abstraction over a
block of memory storing an array of similar elements. The difference is that Span<T> and
ReadOnlySpan<T> are ref struct types whereas Memory<T> and ReadOnlyMemory<T> are
struct types. Spans contain a ref field . Therefore instances of a span can't leave its

safe context. The safe context of a ref struct is the ref safe context of its ref field . The
implementation of Memory<T> and ReadOnlyMemory<T> remove this restriction. You use
these types to directly access memory buffers.

Improve performance with ref safety


Using these features to improve performance involves these tasks:

Avoid allocations: When you change a type from a class to a struct , you change
how it's stored. Local variables are stored on the stack. Members are stored inline
when the container object is allocated. This change means fewer allocations and
that decreases the work the garbage collector does. It might also decrease
memory pressure so the garbage collector runs less often.
Preserve reference semantics: Changing a type from a class to a struct changes
the semantics of passing a variable to a method. Code that modified the state of
its parameters needs modification. Now that the parameter is a struct , the
method is modifying a copy of the original object. You can restore the original
semantics by passing that parameter as a ref parameter. After that change, the
method modifies the original struct again.
Avoid copying data: Copying larger struct types can impact performance in some
code paths. You can also add the ref modifier to pass larger data structures to
methods by reference instead of by value.
Restrict modifications: When a struct type is passed by reference, the called
method could modify the state of the struct. You can replace the ref modifier with
the ref readonly or in modifiers to indicate that the argument can't be modified.
Prefer ref readonly when the method captures the parameter or returns it by
readonly reference. You can also create readonly struct types or struct types
with readonly members to provide more control over what members of a struct
can be modified.
Directly manipulate memory: Some algorithms are most efficient when treating
data structures as a block of memory containing a sequence of elements. The Span
and Memory types provide safe access to blocks of memory.

None of these techniques require unsafe code. Used wisely, you can get performance
characteristics from safe code that was previously only possible by using unsafe
techniques. You can try the techniques yourself in the tutorial on reducing memory
allocations.
Tutorial: como reduzir alocações de
memória com segurança ref
Artigo • 02/06/2023

Geralmente, o ajuste de desempenho de um aplicativo .NET envolve duas técnicas.


Primeiro, reduza o número e o tamanho das alocações de heap. Segundo, reduza a
frequência em que os dados são copiados. O Visual Studio oferece ótimas ferramentas
que ajudam a analisar como o aplicativo está usando a memória. Depois de determinar
onde o aplicativo faz alocações desnecessárias, você faz alterações para minimizar essas
alocações. Você converte os tipos class em tipos struct . Você usa recursos de
segurança ref para preservar a semântica e minimizar a cópia extra.

Use o Visual Studio 17.5 para obter a melhor experiência possível com este tutorial. A
ferramenta de alocação de objeto .NET usada para analisar o uso de memória faz parte
do Visual Studio. Você pode usar o Visual Studio Code e a linha de comando para
executar o aplicativo e fazer todas as alterações. No entanto, você não poderá ver os
resultados da análise das alterações.

O aplicativo que você usará é uma simulação de um aplicativo de IoT que monitora
vários sensores para determinar se um intruso entrou em uma galeria de segredos com
itens de valor. Os sensores de IoT estão sempre enviando dados que medem a mistura
de O2 (oxigênio) e CO2 (dióxido de carbono) no ar. Eles também relatam a temperatura
e a umidade relativa. Cada um desses valores flutua um pouco o tempo todo. No
entanto, quando uma pessoa entra na sala, a alteração aumenta um pouco e sempre na
mesma direção: o oxigênio diminui, dióxido de carbono aumenta, a temperatura
aumenta, assim como a umidade relativa. Quando os sensores se combinam para
mostrar aumentos, o alarme de intruso é disparado.

Neste tutorial, você vai executar o aplicativo, obter medidas de alocações de memória e,
depois, aprimorar o desempenho reduzindo o número de alocações. O código-fonte
está disponível no navegador de exemplos.

Explorar o aplicativo inicial


Baixe o aplicativo e execute o exemplo inicial. O aplicativo inicial funciona corretamente,
mas como aloca muitos objetos pequenos a cada ciclo de medição, o desempenho
diminui lentamente à medida que é executado ao longo do tempo.

Console
Press <return> to start simulation

Debounced measurements:
Temp: 67.332
Humidity: 41.077%
Oxygen: 21.097%
CO2 (ppm): 404.906
Average measurements:
Temp: 67.332
Humidity: 41.077%
Oxygen: 21.097%
CO2 (ppm): 404.906

Debounced measurements:
Temp: 67.349
Humidity: 46.605%
Oxygen: 20.998%
CO2 (ppm): 408.707
Average measurements:
Temp: 67.349
Humidity: 46.605%
Oxygen: 20.998%
CO2 (ppm): 408.707

Muitas linhas removidas.

Console

Debounced measurements:
Temp: 67.597
Humidity: 46.543%
Oxygen: 19.021%
CO2 (ppm): 429.149
Average measurements:
Temp: 67.568
Humidity: 45.684%
Oxygen: 19.631%
CO2 (ppm): 423.498
Current intruders: 3
Calculated intruder risk: High

Debounced measurements:
Temp: 67.602
Humidity: 46.835%
Oxygen: 19.003%
CO2 (ppm): 429.393
Average measurements:
Temp: 67.568
Humidity: 45.684%
Oxygen: 19.631%
CO2 (ppm): 423.498
Current intruders: 3
Calculated intruder risk: High

Você pode explorar o código para saber como o aplicativo funciona. O programa
principal executa a simulação. Depois de pressionar <Enter> , ele cria uma sala e coleta
alguns dados de linha de base iniciais:

C#

Console.WriteLine("Press <return> to start simulation");


Console.ReadLine();
var room = new Room("gallery");
var r = new Random();

int counter = 0;

room.TakeMeasurements(
m =>
{
Console.WriteLine(room.Debounce);
Console.WriteLine(room.Average);
Console.WriteLine();
counter++;
return counter < 20000;
});

Depois que os dados de linha de base forem estabelecidos, ele executará a simulação
na sala, em que um gerador de número aleatório determinará se um intruso entrou na
sala:

C#

counter = 0;
room.TakeMeasurements(
m =>
{
Console.WriteLine(room.Debounce);
Console.WriteLine(room.Average);
room.Intruders += (room.Intruders, r.Next(5)) switch
{
( > 0, 0) => -1,
( < 3, 1) => 1,
_ => 0
};

Console.WriteLine($"Current intruders: {room.Intruders}");


Console.WriteLine($"Calculated intruder risk: {room.RiskStatus}");
Console.WriteLine();
counter++;
return counter < 200000;
});

Outros tipos contêm as medidas, uma medida esporádica que é a média das últimas 50
medidas e a média de todas as medidas realizadas.

Depois, execute o aplicativo usando a ferramenta de alocação de objeto .NET. Verifique


se você está usando o build Release , não o build Debug . No menu Depurar, abra o
Criador de perfil de desempenho. Marque somente a opção Acompanhamento de
Alocação de Objeto .NET. Execute o aplicativo até a conclusão. O criador de perfil mede
as alocações de objetos e relata as alocações e os ciclos de coleta de lixo. Você verá um
grafo semelhante à seguinte imagem:

O grafo anterior mostra que o trabalho para minimizar as alocações trará benefícios de
desempenho. Você vê um padrão de serrilhado no grafo de objetos dinâmicos. Isso
indica que são criados vários objetos que rapidamente se tornam lixo. Eles são
coletados depois, como é mostrado no grafo delta de objeto. As barras vermelhas para
baixo indicam um ciclo de coleta de lixo.

Depois, examine a guia Alocações abaixo dos grafos. Esta tabela mostra quais tipos são
mais alocados:

O tipo System.String conta para a maioria das alocações. A tarefa mais importante deve
ser minimizar a frequência das alocações de cadeia de caracteres. Este aplicativo
imprime várias saídas formatadas no console constantemente. Para essa simulação,
queremos manter as mensagens, portanto, vamos olhar as duas próximas linhas: o tipo
SensorMeasurement e o tipo IntruderRisk .

Clique duas vezes na linha SensorMeasurement . Veja que todas as alocações ocorrem no
método static SensorMeasurement.TakeMeasurement . Veja o método no seguinte snippet:

C#

public static SensorMeasurement TakeMeasurement(string room, int intruders)


{
return new SensorMeasurement
{
CO2 = (CO2Concentration + intruders * 10) + (20 *
generator.NextDouble() - 10.0),
O2 = (O2Concentration - intruders * 0.01) + (0.005 *
generator.NextDouble() - 0.0025),
Temperature = (TemperatureSetting + intruders * 0.05) + (0.5 *
generator.NextDouble() - 0.25),
Humidity = (HumiditySetting + intruders * 0.005) + (0.20 *
generator.NextDouble() - 0.10),
Room = room,
TimeRecorded = DateTime.Now
};
}

Cada medida aloca um novo objeto SensorMeasurement , que é um tipo class . Cada
SensorMeasurement criado causa uma alocação de heap.

Alterar classes para structs


O seguinte código mostra a declaração inicial de SensorMeasurement :

C#

public class SensorMeasurement


{
private static readonly Random generator = new Random();

public static SensorMeasurement TakeMeasurement(string room, int


intruders)
{
return new SensorMeasurement
{
CO2 = (CO2Concentration + intruders * 10) + (20 *
generator.NextDouble() - 10.0),
O2 = (O2Concentration - intruders * 0.01) + (0.005 *
generator.NextDouble() - 0.0025),
Temperature = (TemperatureSetting + intruders * 0.05) + (0.5 *
generator.NextDouble() - 0.25),
Humidity = (HumiditySetting + intruders * 0.005) + (0.20 *
generator.NextDouble() - 0.10),
Room = room,
TimeRecorded = DateTime.Now
};
}

private const double CO2Concentration = 409.8; // increases with people.


private const double O2Concentration = 0.2100; // decreases
private const double TemperatureSetting = 67.5; // increases
private const double HumiditySetting = 0.4500; // increases

public required double CO2 { get; init; }


public required double O2 { get; init; }
public required double Temperature { get; init; }
public required double Humidity { get; init; }
public required string Room { get; init; }
public required DateTime TimeRecorded { get; init; }

public override string ToString() => $"""


Room: {Room} at {TimeRecorded}:
Temp: {Temperature:F3}
Humidity: {Humidity:P3}
Oxygen: {O2:P3}
CO2 (ppm): {CO2:F3}
""";
}

O tipo foi criado originalmente como um class porque contém várias medidas double .
Ele é maior do que o ideal para ser copiado em caminhos críticos. No entanto, essa
decisão significou um grande número de alocações. Altere o tipo de class para struct .

A alteração de class para struct apresenta alguns erros do compilador porque o


código original usou verificações de referência null em alguns pontos. O primeiro está
na classe DebounceMeasurement , no método AddMeasurement :

C#

public void AddMeasurement(SensorMeasurement datum)


{
int index = totalMeasurements % debounceSize;
recentMeasurements[index] = datum;
totalMeasurements++;
double sumCO2 = 0;
double sumO2 = 0;
double sumTemp = 0;
double sumHumidity = 0;
for (int i = 0; i < debounceSize; i++)
{
if (recentMeasurements[i] is not null)
{
sumCO2 += recentMeasurements[i].CO2;
sumO2+= recentMeasurements[i].O2;
sumTemp+= recentMeasurements[i].Temperature;
sumHumidity += recentMeasurements[i].Humidity;
}
}
O2 = sumO2 / ((totalMeasurements > debounceSize) ? debounceSize :
totalMeasurements);
CO2 = sumCO2 / ((totalMeasurements > debounceSize) ? debounceSize :
totalMeasurements);
Temperature = sumTemp / ((totalMeasurements > debounceSize) ?
debounceSize : totalMeasurements);
Humidity = sumHumidity / ((totalMeasurements > debounceSize) ?
debounceSize : totalMeasurements);
}

O tipo DebounceMeasurement contém uma matriz de 50 medidas. As leituras de um


sensor são relatadas como a média das últimas 50 medidas. Isso reduz o ruído nas
leituras. Antes de serem realizadas 50 leituras completas, esses valores são null . O
código verifica a referência null para relatar a média correta na inicialização do sistema.
Depois de alterar o tipo SensorMeasurement para um struct, você precisa usar um teste
diferente. O tipo SensorMeasurement inclui um string para o identificador de sala, para
que você possa usar esse teste nesse caso:

C#

if (recentMeasurements[i].Room is not null)

Os outros três erros do compilador estão no método que obtém medidas


repetidamente em uma sala:

C#

public void TakeMeasurements(Func<SensorMeasurement, bool>


MeasurementHandler)
{
SensorMeasurement? measure = default;
do {
measure = SensorMeasurement.TakeMeasurement(Name, Intruders);
Average.AddMeasurement(measure);
Debounce.AddMeasurement(measure);
} while (MeasurementHandler(measure));
}

No método inicial, a variável local de SensorMeasurement é uma referência anulável:

C#
SensorMeasurement? measure = default;

Agora que SensorMeasurement é um struct em vez de um class , o anulável é um tipo


de valor anulável. Você pode alterar a declaração para um tipo de valor a fim de corrigir
os erros restantes do compilador:

C#

SensorMeasurement measure = default;

Agora que os erros do compilador foram resolvidos, você deve examinar o código para
garantir que a semântica não tenha sido alterada. Como os tipos struct são passados
por valor, as modificações feitas nos parâmetros de método não são visíveis após o
retorno do método.

) Importante

A alteração de um tipo de class para struct pode alterar a semântica do


programa. Quando um tipo class é passado a um método, as mutações feitas no
método são feitas ao argumento. Quando um tipo struct é passado a um método,
as mutações feitas no método são feitas em uma cópia do argumento. Isso significa
que qualquer método que modifique argumentos por design deve ser atualizado
para usar o modificador ref nos tipos de argumento alterados de class para
struct .

O tipo SensorMeasurement não inclui nenhum método que altere o estado, portanto, isso
não é uma preocupação neste exemplo. Você pode provar isso adicionando o
modificador readonly ao struct SensorMeasurement :

C#

public readonly struct SensorMeasurement

O compilador impõe a natureza readonly do struct SensorMeasurement . Se a inspeção


do código deixar passar algum método que tenha modificado o estado, o compilador
informará isso. O aplicativo ainda é criado sem erros, portanto, esse tipo é readonly . A
adição do modificador readonly ao alterar um tipo de class para struct pode ajudar a
encontrar membros que modificam o estado do struct .
Evite fazer cópias
Você removeu um grande número de alocações desnecessárias do aplicativo. O tipo
SensorMeasurement não aparece na tabela em nenhum lugar.

Agora, ele está fazendo um trabalho extra copiando a estrutura SensorMeasurement


sempre que ela é usada como um parâmetro ou um valor retornado. O struct
SensorMeasurement contém quatro duplas, um DateTime e um string . Essa estrutura é
mensuravelmente maior que uma referência. Vamos adicionar os modificadores ref ou
in a locais em que o tipo SensorMeasurement é usado.

A próxima etapa é localizar métodos que retornam uma medida ou que obtenham uma
medida como argumento e usem referências sempre que possível. Comece no struct
SensorMeasurement . O método TakeMeasurement estático cria e retorna um novo
SensorMeasurement :

C#

public static SensorMeasurement TakeMeasurement(string room, int intruders)


{
return new SensorMeasurement
{
CO2 = (CO2Concentration + intruders * 10) + (20 *
generator.NextDouble() - 10.0),
O2 = (O2Concentration - intruders * 0.01) + (0.005 *
generator.NextDouble() - 0.0025),
Temperature = (TemperatureSetting + intruders * 0.05) + (0.5 *
generator.NextDouble() - 0.25),
Humidity = (HumiditySetting + intruders * 0.005) + (0.20 *
generator.NextDouble() - 0.10),
Room = room,
TimeRecorded = DateTime.Now
};
}

Deixaremos este como está, retornando por valor. Se você tentasse retornar por ref ,
obteria um erro do compilador. Não é possível retornar um ref a uma estrutura criada
localmente no método. O design do struct imutável significa que você só pode definir
os valores da medida na construção. Esse método precisa criar um struct de medida.

Vamos analisar DebounceMeasurement.AddMeasurement novamente. Você deve adicionar o


modificador in ao parâmetro measurement :

C#
public void AddMeasurement(in SensorMeasurement datum)
{
int index = totalMeasurements % debounceSize;
recentMeasurements[index] = datum;
totalMeasurements++;
double sumCO2 = 0;
double sumO2 = 0;
double sumTemp = 0;
double sumHumidity = 0;
for (int i = 0; i < debounceSize; i++)
{
if (recentMeasurements[i].Room is not null)
{
sumCO2 += recentMeasurements[i].CO2;
sumO2+= recentMeasurements[i].O2;
sumTemp+= recentMeasurements[i].Temperature;
sumHumidity += recentMeasurements[i].Humidity;
}
}
O2 = sumO2 / ((totalMeasurements > debounceSize) ? debounceSize :
totalMeasurements);
CO2 = sumCO2 / ((totalMeasurements > debounceSize) ? debounceSize :
totalMeasurements);
Temperature = sumTemp / ((totalMeasurements > debounceSize) ?
debounceSize : totalMeasurements);
Humidity = sumHumidity / ((totalMeasurements > debounceSize) ?
debounceSize : totalMeasurements);
}

Isso economiza uma operação de cópia. O parâmetro in é uma referência à cópia já


criada pelo chamador. Você também pode salvar uma cópia com o método
TakeMeasurement no tipo Room . Este método ilustra como o compilador fornece

segurança quando você passa argumentos por ref . O método TakeMeasurement inicial
no tipo Room usa o argumento Func<SensorMeasurement, bool> . Se você tentar adicionar
o modificador in ou ref a essa declaração, o compilador relatará um erro. Você não
pode passar um argumento ref para uma expressão lambda. O compilador não pode
garantir que a expressão chamada não copie a referência. Se a expressão lambda
capturar a referência, a referência poderá ter um tempo de vida maior do que o valor ao
qual se refere. Se o acesso for feito de fora da referência segura para escapar do escopo,
ocorrerá corrupção de memória. As regras de segurança de ref não permitem isso.
Saiba mais na visão geral de recursos de segurança de referência.

Preservar a semântica
Os conjuntos finais de alterações não terão um grande impacto no desempenho desse
aplicativo porque os tipos não são criados em caminhos críticos. Essas alterações
ilustram algumas das outras técnicas que você usaria no ajuste de desempenho. Vamos
dar uma olhada na classe Room inicial:

C#

public class Room


{
public AverageMeasurement Average { get; } = new ();
public DebounceMeasurement Debounce { get; } = new ();
public string Name { get; }

public IntruderRisk RiskStatus


{
get
{
var CO2Variance = (Debounce.CO2 - Average.CO2) > 10.0 / 4;
var O2Variance = (Average.O2 - Debounce.O2) > 0.005 / 4.0;
var TempVariance = (Debounce.Temperature - Average.Temperature)
> 0.05 / 4.0;
var HumidityVariance = (Debounce.Humidity - Average.Humidity) >
0.20 / 4;
IntruderRisk risk = IntruderRisk.None;
if (CO2Variance) { risk++; }
if (O2Variance) { risk++; }
if (TempVariance) { risk++; }
if (HumidityVariance) { risk++; }
return risk;
}
}

public int Intruders { get; set; }

public Room(string name)


{
Name = name;
}

public void TakeMeasurements(Func<SensorMeasurement, bool>


MeasurementHandler)
{
SensorMeasurement? measure = default;
do {
measure = SensorMeasurement.TakeMeasurement(Name, Intruders);
Average.AddMeasurement(measure);
Debounce.AddMeasurement(measure);
} while (MeasurementHandler(measure));
}
}

Esse tipo contém várias propriedades. Alguns são tipos class . A criação de um objeto
Room envolve várias alocações. Uma para o Room em si e outra para cada um dos
membros de um tipo class que ele contém. Você pode converter duas dessas
propriedades de tipos class em tipos struct : os tipos DebounceMeasurement e
AverageMeasurement . Vamos trabalhar nessa transformação com os dois tipos.

Altere o tipo DebounceMeasurement de class para struct . Isso apresenta um erro do


compilador CS8983: A 'struct' with field initializers must include an explicitly
declared constructor . Você pode corrigir isso adicionando um construtor sem

parâmetros vazio:

C#

public DebounceMeasurement() { }

Saiba mais sobre esse requisito no artigo de referência de linguagem em structs.

A substituição de Object.ToString() não modifica nenhum dos valores do struct. Você


pode adicionar o modificador readonly a essa declaração de método. O tipo
DebounceMeasurement é mutável, portanto, você precisará tomar cuidado para que as

modificações não afetem cópias descartadas. O método AddMeasurement modifica o


estado do objeto. Ele é chamado da classe Room , no método TakeMeasurements . Você
deseja que essas alterações persistam depois de chamar o método. Você pode alterar a
propriedade Room.Debounce para retornar uma referência a uma só instância do tipo
DebounceMeasurement :

C#

private DebounceMeasurement debounce = new();


public ref readonly DebounceMeasurement Debounce { get { return ref
debounce; } }

Há algumas alterações no exemplo anterior. Primeiro, a propriedade é uma propriedade


somente leitura que retorna uma referência somente leitura à instância pertencente a
essa sala. Agora ela é apoiada por um campo declarado que é inicializado quando é
criada uma instância do objeto Room . Depois de fazer essas alterações, você atualizará a
implementação do método AddMeasurement . Ela usa o campo de suporte privado,
debounce , não a propriedade somente leitura Debounce . Dessa forma, as alterações

ocorrem na única instância criada durante a inicialização.

A mesma técnica funciona com a propriedade Average . Primeiro, modifique o tipo


AverageMeasurement de class para struct e adicione o modificador readonly ao

método ToString :
C#

namespace IntruderAlert;

public struct AverageMeasurement


{
private double sumCO2 = 0;
private double sumO2 = 0;
private double sumTemperature = 0;
private double sumHumidity = 0;
private int totalMeasurements = 0;

public AverageMeasurement() { }

public readonly double CO2 => sumCO2 / totalMeasurements;


public readonly double O2 => sumO2 / totalMeasurements;
public readonly double Temperature => sumTemperature /
totalMeasurements;
public readonly double Humidity => sumHumidity / totalMeasurements;

public void AddMeasurement(in SensorMeasurement datum)


{
totalMeasurements++;
sumCO2 += datum.CO2;
sumO2 += datum.O2;
sumTemperature += datum.Temperature;
sumHumidity+= datum.Humidity;
}

public readonly override string ToString() => $"""


Average measurements:
Temp: {Temperature:F3}
Humidity: {Humidity:P3}
Oxygen: {O2:P3}
CO2 (ppm): {CO2:F3}
""";
}

Depois, modifique a classe Room seguindo a mesma técnica usada para a propriedade
Debounce . A propriedade Average retorna um readonly ref ao campo privado para a

medição média. O método AddMeasurement modifica os campos internos.

C#

private AverageMeasurement average = new();


public ref readonly AverageMeasurement Average { get { return ref average;
} }

Evitar a conversão boxing


Há uma última alteração para aprimorar o desempenho. O programa principal está
imprimindo estatísticas para a sala, incluindo a avaliação de risco:

C#

Console.WriteLine($"Current intruders: {room.Intruders}");


Console.WriteLine($"Calculated intruder risk: {room.RiskStatus}");

A chamada ao ToString gerado faz a conversão boxing para o valor de enumeração.


Você pode evitar isso escrevendo uma substituição na classe Room que formata a cadeia
de caracteres com base no valor do risco estimado:

C#

public override string ToString() =>


$"Calculated intruder risk: {RiskStatus switch
{
IntruderRisk.None => "None",
IntruderRisk.Low => "Low",
IntruderRisk.Medium => "Medium",
IntruderRisk.High => "High",
IntruderRisk.Extreme => "Extreme",
_ => "Error!"
}}, Current intruders: {Intruders.ToString()}";

Depois, modifique o código no programa principal para chamar esse novo método
ToString :

C#

Console.WriteLine(room.ToString());

Execute o aplicativo usando o criador de perfil e examine a tabela atualizada em busca


de alocações.
Você removeu várias alocações e proporcionou ao aplicativo um aumento de
desempenho.

Como usar a segurança de referência no


aplicativo
Essas técnicas são um ajuste de desempenho de nível baixo. Elas podem aumentar o
desempenho no aplicativo quando aplicadas a caminhos críticos e quando o impacto é
medido antes e depois das alterações. Na maioria dos casos, o ciclo a ser seguido é:

Alocações de medidas: determine quais tipos estão sendo mais alocados e quando
você pode reduzir as alocações de heap.
Converter classe em struct: muitas vezes, os tipos podem ser convertidos de class
em struct . O aplicativo usa espaço de pilha em vez de fazer alocações de heap.
Preservar a semântica: a conversão de class em struct pode afetar a semântica
dos parâmetros e valores retornados. Agora, qualquer método que modificar os
parâmetros deverá marcar esses parâmetros com o modificador ref . Isso garante
que as modificações sejam feitas no objeto correto. Da mesma forma, se um valor
retornado de uma propriedade ou um método precisar ser modificado pelo
chamador, esse retorno deverá ser marcado com o modificador ref .
Evitar cópias: ao passar um struct grande como um parâmetro, você pode marcar o
parâmetro com o modificador in . Você pode passar uma referência em menos
bytes e garantir que o método não modifique o valor original. Você também pode
retornar valores por readonly ref para retornar uma referência que não possa ser
modificada.

Usando essas técnicas, você pode aprimorar o desempenho em caminhos críticos do


código.
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.
O SDK do .NET Compiler Platform
Artigo • 11/04/2024

Os compiladores criam um modelo detalhado do código do aplicativo conforme


validam a sintaxe e a semântica do código. O uso desse modelo para criar a saída
executável do código-fonte. O SDK do .NET Compiler Platform fornece acesso a esse
modelo. Cada vez mais, contamos com recursos do IDE (ambiente de desenvolvimento
integrado), como IntelliSense, refatoração, renomeação inteligente, "Localizar todas as
referências" e "Ir para definição" para aumentar nossa produtividade. Contamos com
ferramentas de análise de código para melhorar a qualidade e com geradores de código
para ajudar na criação do aplicativo. À medida que essas ferramentas ficam mais
inteligentes, elas precisam de acesso a cada vez mais do modelo que somente os
compiladores podem criar conforme processam o código do aplicativo. Este é o objetivo
principal das APIs do Roslyn: abrir as caixas opacas e permitir que as ferramentas e os
usuários finais compartilhem a riqueza de informações que os compiladores têm sobre
nosso código. Em vez de ser meros conversores de código-fonte e objeto-código-saída,
por meio do Roslyn, os compiladores se tornam plataformas: as APIs que você pode
usar para as tarefas relacionadas ao código em seus aplicativos e ferramentas.

Conceitos do SDK do .NET Compiler Platform


O SDK do .NET Compiler Platform diminui drasticamente a barreira de entrada para a
criação de aplicativos e ferramentas voltadas para o código. Ele cria várias
oportunidades para inovação em áreas como metaprogramação, geração e
transformação de código, uso interativo das linguagens C# e Visual Basic e incorporação
de C# e Visual Basic a linguagens específicas de domínio.

O SDK do .NET Compiler Platform permite que você crie analisadores e correções de
código que encontram e corrigem os erros de codificação. Os analisadores entendem a
sintaxe (estrutura do código) e a semântica para detectar práticas que devem ser
corrigidas. As correções de código fornecem uma ou mais correções sugeridas para
tratar erros de codificação encontrados pelos analisadores ou diagnósticos do
compilador. Normalmente, um analisador e as correções de código associadas são
empacotados em um único projeto.

Os analisadores e as correções de código usam a análise estática para entender o


código. Eles não executam o código ou fornecem outros benefícios de teste. No
entanto, eles podem destacar práticas que frequentemente levam a erros, códigos de
difícil manutenção ou violação de diretrizes padrão.
Além de analisadores e correções de código, o SDK do .NET Compiler Platform também
permite criar refatorações de código. Ele também fornece um único conjunto de APIs
que permitem que você examine e compreenda uma base de código C# ou Visual Basic.
Uma vez que você pode usar essa base de código única, é possível escrever analisadores
e correções de código com mais facilidade aproveitando as APIs de análise de sintaxe e
de semântica fornecidas pelo SDK do .NET Compiler Platform. Liberado da enorme
tarefa de replicar a análise feita pelo compilador, você pode se concentrar na tarefa de
localizar e corrigir os erros de codificação comuns no projeto ou na biblioteca.

Um benefício menor é que os analisadores e as correções de código são menores e


usam muito menos memória quando carregados no Visual Studio do que usariam se
você tivesse escrito sua própria base de código para entender o código em um projeto.
Aproveitando as mesmas classes usadas pelo compilador e o Visual Studio, você pode
criar suas próprias ferramentas de análise estática. Isso significa que sua equipe poderá
usar os analisadores e as correções de código sem um impacto significativo no
desempenho do IDE.

Há três cenários principais para escrever analisadores e correções de código:

1. Impor padrões de codificação à equipe


2. Fornecer diretrizes com pacotes de biblioteca
3. Fornecer diretrizes gerais

Impor padrões de codificação à equipe


Muitas equipes têm padrões de codificação aplicados por meio de revisões de código
feitas com outros membros da equipe. Os analisadores e as correções de código podem
tornar esse processo muito mais eficiente. As revisões de código ocorrem quando um
desenvolvedor compartilha seu trabalho com outras pessoas da equipe. O
desenvolvedor terá investido todo o tempo necessário para concluir um novo recurso
antes de obter algum comentário. Semanas podem passar enquanto o desenvolvedor
reforça hábitos que não correspondem às práticas da equipe.

Os analisadores são executados à medida que um desenvolvedor escreve o código. O


desenvolvedor obtém comentários imediatos que o incentiva a seguir as diretrizes no
mesmo instante. O desenvolvedor cria hábitos para gravar códigos compatíveis assim
que começa a criar protótipos. Quando o recurso está pronto para que outras pessoas o
examinem, todas as diretrizes padrão já terão sido impostas.

As equipes podem criar analisadores e correções de código que procurem as práticas


mais comuns que violem as práticas de codificação em equipe. Eles podem ser
instalados nos computadores de cada desenvolvedor para impor os padrões.
 Dica

Antes de criar seu próprio analisador, confira os analisadores internos. Para mais
informações, confira Regras de estilo de código.

Fornecer diretrizes com pacotes de biblioteca


Há uma grande variedade de bibliotecas para desenvolvedores de .NET no NuGet.
Algumas dessas provenientes da Microsoft, algumas de terceiros e outras de membros e
de voluntários da comunidade. Essas bibliotecas obtêm mais adoção e análises mais
positivas quando os desenvolvedores são bem-sucedidos com elas.

Além de fornecer a documentação, você pode fornecer analisadores e correções de


código que encontram e corrigem os usos inadequados comuns da sua biblioteca. Essas
correções imediatas ajudarão os desenvolvedores a obter êxito mais rapidamente.

Você pode empacotar analisadores e correções de código com sua biblioteca no NuGet.
Nesse cenário, cada desenvolvedor que instalar o pacote do NuGet também instalará o
pacote do analisador. Todos os desenvolvedores que estiverem usando a biblioteca
obterão imediatamente as diretrizes da sua equipe na forma de comentários imediatos
sobre erros e correções sugeridas.

Fornecer diretrizes gerais


A comunidade de desenvolvedores do .NET descobriu, pela experiência, os padrões que
funcionam bem e os padrões que devem ser evitados. Vários membros da comunidade
criaram analisadores que impõem esses padrões recomendados. À medida que
aprendemos mais, sempre haverá espaço para novas ideias.

Esses analisadores podem ser carregados no Visual Studio Marketplace e baixados


por desenvolvedores que usam o Visual Studio. Quem ainda não tem experiência na
linguagem e na plataforma aprende rapidamente as práticas aceitas e se torna
produtivo mais cedo em sua jornada no .NET. Quando as práticas se tornam
amplamente usadas, a comunidade as adota.

Próximas etapas
O SDK do .NET Compiler Platform inclui os modelos de objeto de linguagem mais
recentes para geração de código, análise e refatoração. Esta seção fornece uma visão
geral conceitual do SDK do .NET Compiler Platform. Mais detalhes podem ser
encontrados nas seções de inícios rápidos, de exemplos e de tutoriais.

Você pode saber mais sobre os conceitos no SDK do .NET Compiler Platform nestes
cinco tópicos:

Explorar código com o visualizador de sintaxe


Entender o modelo de API do compilador
Trabalhar com sintaxe
Trabalhar com semântica
Trabalhar com um workspace

Para começar, será necessário instalar o SDK do .NET Compiler Platform:

Instruções de instalação – Instalador do Visual


Studio
Há duas maneiras diferentes de encontrar o SDK da .NET Compiler Platform no
Instalador do Visual Studio:

Instalar usando o Instalador do Visual Studio – exibição


de cargas de trabalho
O SDK da .NET Compiler Platform não é selecionado automaticamente como parte da
carga de trabalho de desenvolvimento da extensão do Visual Studio. É necessário
selecioná-lo como um componente opcional.

1. Execute o Instalador do Visual Studio


2. Escolha Mudar
3. Marque a carga de trabalho de Desenvolvimento de extensão do Visual Studio.
4. Abra o nó Desenvolvimento de extensão do Visual Studio na árvore de resumo.
5. Marque a caixa do SDK da .NET Compiler Platform. Você a encontrará por último
nos componentes opcionais.

Opcionalmente, você também poderá fazer o Editor DGML exibir gráficos no


visualizador:

1. Abra o nó Componentes individuais na árvore de resumo.


2. Marque a caixa do Editor DGML
Instalar usando o Instalador do Visual Studio – guia
Componentes individuais
1. Execute o Instalador do Visual Studio
2. Escolha Mudar
3. Selecione a guia Componentes individuais
4. Marque a caixa do SDK da .NET Compiler Platform. Você a encontrará na parte
superior, na seção Compiladores, ferramentas de compilação e runtimes.

Opcionalmente, você também poderá fazer o Editor DGML exibir gráficos no


visualizador:

1. Marque a caixa do Editor DGML. Você a encontrará na seção Ferramentas de


código.

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

Fornecer comentários sobre o produto


Entender o modelo do SDK do .NET
Compiler Platform
Artigo • 08/11/2023

Os compiladores processam o código escrito seguindo regras estruturadas que


geralmente diferem da forma como os humanos leem e entendem um código. Uma
compreensão básica do modelo usado pelos compiladores é essencial para
compreender as APIs usadas ao criar ferramentas baseadas no Roslyn.

Áreas funcionais do pipeline do compilador


O SDK do .NET Compiler Platform expõe a análise de código dos compiladores C# e
Visual Basic para você como um consumidor, fornecendo uma camada de API que
espelha um pipeline de compilador tradicional.

Cada fase desse pipeline é um componente separado. Primeiro, a fase de análise cria
tokens do texto de origem e o analisa na sintaxe que segue a gramática da linguagem.
Depois, a fase de declaração analisa os metadados de origem e importados para formar
símbolos nomeados. Em seguida, a fase de associação corresponde os identificadores
no código aos símbolos. Por fim, a fase de emissão emite um assembly com todas as
informações criadas pelo compilador.

Correspondente a cada uma dessas fases, o SDK do .NET Compiler Platform expõe um
modelo de objeto que permite o acesso às informações da fase. A fase de análise expõe
uma árvore de sintaxe, a fase de declaração expõe uma tabela de símbolos hierárquica,
a fase de associação expõe o resultado da análise semântica do compilador e a fase de
emissão é uma API que gera códigos de bytes de IL.

Cada compilador combina esses componentes como um único inteiro de ponta a ponta.

Essas APIs são as mesmas usadas pelo Visual Studio. Por exemplo, os recursos de
formatação e estrutura de tópicos do código usam as árvores de sintaxe, o Pesquisador
de Objetos e os recursos de navegação usam a tabela de símbolos, as refatorações e o
recurso Ir para Definição usam o modelo semântico e o recurso Editar e Continuar usa
todos eles, inclusive a API de Emissão.

Camadas de API
O SDK do compilador .NET consiste em várias camadas de APIs, ou seja: APIs de
compilador, de diagnóstico, de script e de espaços de trabalho.

APIs do compilador
A camada do compilador contém os modelos de objeto que correspondem às
informações expostas em cada fase do pipeline do compilador, tanto sintáticas quanto
semânticas. A camada do compilador também contém um instantâneo imutável de uma
única invocação de um compilador, incluindo referências de assembly, opções do
compilador e arquivos de código-fonte. Há duas APIs distintas que representam a
linguagem C# e a linguagem Visual Basic. Essas duas APIs são semelhantes na forma,
mas adaptadas para alta fidelidade a cada linguagem individual. Essa camada não tem
dependências em componentes do Visual Studio.
APIs de diagnóstico
Como parte da análise, o compilador pode produzir um conjunto de diagnósticos que
abrangem tudo, desde sintaxe, semântica e erros de atribuição definida a vários
diagnósticos de avisos e informativos. A camada de API do Compilador expõe o
diagnóstico por meio de uma API extensível que permite que os analisadores definidos
pelo usuário sejam conectados ao processo de compilação. Ela possibilita que o
diagnóstico definido pelo usuário, como aqueles gerados por ferramentas como o
StyleCop, seja produzido junto com o diagnóstico definido pelo compilador. Essa forma
de produção de diagnóstico tem o benefício da integração natural a ferramentas como
o MSBuild e o Visual Studio, que dependem do diagnóstico para experiências como
interrupção de um build com base na política, exibição de textos sublinhados em tempo
real no editor e sugestão de correções de código.

APIs de script
As APIs de hospedagem e de script foram criadas com base na camada do compilador.
Você pode usar as APIs de script para executar snippets de código e acumular um
contexto de execução de runtime. O REPL (Loop de Leitura-Avaliação-Impressão)
interativo do C# usa essas APIs. O REPL permite que você use o C# como a linguagem
de script, executando o código de maneira interativa enquanto o escreve.

APIs dos workspaces


A camada Workspaces contém a API de Workspace, que é o ponto de partida para fazer
a análise de código e refatoração em soluções inteiras. Ela ajuda a organizar todas as
informações sobre os projetos de uma solução em um único modelo de objeto,
oferecendo acesso direto aos modelos de objeto da camada do compilador, sem a
necessidade de analisar arquivos, configurar opções ou gerenciar dependências projeto
a projeto.

Além disso, a camada Workspaces expõe um conjunto de APIs usado ao implementar


ferramentas de análise de código e refatoração que funcionam em um ambiente de
host como o IDE do Visual Studio. Exemplos incluem as APIs Localizar Todas as
Referências, Formatação e Geração de Código.

Essa camada não tem dependências em componentes do Visual Studio.


6 Colaborar conosco no .NET feedback
GitHub The .NET documentation is open
A fonte deste conteúdo pode source. Provide feedback here.
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
Trabalhar com sintaxe
Artigo • 10/05/2023

A árvore de sintaxe é uma estrutura de dados imutável e fundamental exposta pelas APIs
do compilador. Essas árvores representam a estrutura lexical e sintática do código-fonte.
Elas servem duas finalidades importantes:

Permitir que ferramentas – como um IDE, suplementos, ferramentas de análise de


código e refatorações – vejam e processem a estrutura sintática do código-fonte
no projeto do usuário.
Permitir que ferramentas – como refatorações e um IDE – criem, modifiquem e
reorganizem o código-fonte de uma maneira natural sem a necessidade de uso de
edições de texto diretas. Criando e manipulando árvores, as ferramentas podem
criar e reorganizar o código-fonte com facilidade.

Árvores de sintaxe
Árvores de sintaxe são a estrutura principal usada para compilação, análise de código,
associação, refatoração, recursos de IDE e geração de código. Nenhuma parte do
código-fonte é entendida sem primeiro ser identificada e categorizada em um dos
muitos elementos de linguagem estrutural conhecidos.

As árvores de sintaxe têm três atributos-chave:

Elas têm todas as informações de origem com fidelidade total. A fidelidade total
significa que a árvore de sintaxe contém cada informação encontrada no texto de
origem, cada constructo gramatical, cada token lexical e todo o resto, incluindo
espaço em branco, comentários e diretivas do pré-processador. Por exemplo, cada
literal mencionado na fonte é representado exatamente como foi digitado. Através
da representação de tokens ignorados ou ausentes, as árvores de sintaxe também
capturam erros no código-fonte quando o programa está incompleto ou mal-
formado.
Elas podem produzir o texto exato do qual foram analisados. Em qualquer nó de
sintaxe, é possível obter a representação de texto da subárvore com raiz nesse nó.
Essa habilidade significa que as árvores de sintaxe podem ser usadas como uma
maneira de construir e editar o texto de origem. Ao criar uma árvore, por
implicação, você criou o texto equivalente e, ao criar uma nova árvore com base
nas alterações de uma árvore existente, você editou o texto efetivamente.
Eles são imutáveis e thread-safe. Depois que uma árvore é obtida, ela é um
instantâneo do estado atual do código e nunca é alterada. Isso permite que vários
usuários interajam com a mesma árvore de sintaxe ao mesmo tempo em threads
diferentes sem bloqueio nem duplicação. Como as árvores são imutáveis e
nenhuma modificação pode ser feita diretamente em uma árvore, os métodos de
fábrica ajudam a criar e modificar árvores de sintaxe criando instantâneos
adicionais da árvore. As árvores são eficientes no modo como reutilizam os nós
subjacentes, de forma que uma nova versão possa ser recompilada rapidamente e
com pouca memória extra.

Uma árvore de sintaxe é literalmente uma estrutura de dados de árvore, em que os


elementos estruturais não terminais são pais de outros elementos. Cada árvore de
sintaxe é composta por nós, tokens e desafios.

Nós de sintaxe
Nós de sintaxe são um dos elementos principais das árvores de sintaxe. Esses nós
representam os constructos sintáticos como declarações, instruções, cláusulas e
expressões. Cada categoria de nós de sintaxe é representada por uma classe separada
derivada de Microsoft.CodeAnalysis.SyntaxNode. O conjunto de classes de nó não é
extensível.

Todos os nós de sintaxe são nós não terminais na árvore de sintaxe, o que significa que
eles sempre têm outros nós e tokens como filhos. Como filho de outro nó, cada nó tem
um nó pai que pode ser acessado por meio da propriedade SyntaxNode.Parent. Como
os nós e as árvores são imutáveis, o pai de um nó nunca é alterado. A raiz da árvore tem
um pai nulo.

Cada nó tem um método SyntaxNode.ChildNodes(), que retorna uma lista de nós filho
em ordem sequencial com base em sua posição no texto de origem. Essa lista não
contém tokens. Cada nó também tem métodos para examinar os Descendentes, como
DescendantNodes, DescendantTokens ou DescendantTrivia – que representam uma lista
de todos os nós, tokens ou desafios, que existem na subárvore com raiz nesse nó.

Além disso, cada subclasse de nó de sintaxe expõe os mesmos filhos por meio de
propriedades fortemente tipadas. Por exemplo, uma classe de nó
BinaryExpressionSyntax tem três propriedades adicionais específicas aos operadores
binários: Left, OperatorToken e Right. O tipo de Left e Right é ExpressionSyntax e o tipo
de OperatorToken é SyntaxToken.

Alguns nós de sintaxe têm filhos opcionais. Por exemplo, um IfStatementSyntax tem um
ElseClauseSyntax opcional. Se o filho não estiver presente, a propriedade retornará nulo.
Tokens de sintaxe
Os tokens de sintaxe são os terminais da gramática da linguagem, que representam os
menores fragmentos sintáticos do código. Eles nunca são os pais de outros nós ou
tokens. Os tokens de sintaxe consistem em palavras-chave, identificadores, literais e
pontuação.

Para fins de eficiência, o tipo SyntaxToken é um tipo de valor CLR. Portanto, ao contrário
dos nós de sintaxe, há apenas uma estrutura para todos os tipos de tokens com uma
combinação de propriedades que têm significado, dependendo do tipo de token que
está sendo representado.

Por exemplo, um token literal inteiro representa um valor numérico. Além do texto de
origem não processado abrangido pelo token, o token literal tem uma propriedade
Value que informa o valor inteiro decodificado exato. Essa propriedade é tipada como
Object porque pode ser um dos muitos tipos primitivos.

A propriedade ValueText indica as mesmas informações que a propriedade Value; no


entanto, essa propriedade sempre é tipada como String. Um identificador no texto de
origem C# pode incluir caracteres de escape Unicode, embora a sintaxe da sequência de
escape em si não seja considerada parte do nome do identificador. Portanto, embora o
texto não processado abrangido pelo token inclua a sequência de escape, isso não
ocorre com a propriedade ValueText. Em vez disso, ela inclui os caracteres Unicode
identificados pelo escape. Por exemplo, se o texto de origem contiver um identificador
gravado como \u03C0 , a propriedade ValueText desse token retornará π .

Desafios de sintaxe
Os desafios de sintaxe representam as partes do texto de origem que são amplamente
insignificantes para o reconhecimento normal do código, como espaço em branco,
comentários e diretivas do pré-processador. Assim como os tokens de sintaxe, os
desafios são tipos de valor. O único tipo Microsoft.CodeAnalysis.SyntaxTrivia é usado
para descrever todos os tipos de desafios.

Como os desafios não fazem parte da sintaxe de linguagem normal e podem aparecer
em qualquer lugar entre dois tokens quaisquer, eles não são incluídos na árvore de
sintaxe como um filho de um nó. Apesar disso, como eles são importantes ao
implementar um recurso como refatoração e para manter fidelidade total com o texto
de origem, eles existem como parte da árvore de sintaxe.

Acesse os desafios inspecionando as coleções SyntaxToken.LeadingTrivia ou


SyntaxToken.TrailingTrivia de um token. Quando o texto de origem é analisado,
sequências de desafios são associadas aos tokens. Em geral, um token possui qualquer
desafio após ele na mesma linha até o próximo token. Qualquer desafio após essa linha
é associado ao próximo token. O primeiro token no arquivo de origem obtém todos as
desafios iniciais e a última sequência de desafios no arquivo é anexada ao token de fim
do arquivo, que, de outro modo, tem largura zero.

Ao contrário dos nós e tokens de sintaxe, os desafios de sintaxe não têm pais. Apesar
disso, como eles fazem parte da árvore e cada um deles é associado um único token,
você poderá acessar o token ao qual ele está associado usando a propriedade
SyntaxTrivia.Token.

Intervalos
Cada nó, token ou desafio conhece sua posição dentro do texto de origem e o número
de caracteres no qual ele consiste. Uma posição de texto é representada como um
inteiro de 32 bits, que é um índice char baseado em zero. Um objeto TextSpan é a
posição inicial e uma contagem de caracteres, ambas representadas como inteiros. Se
TextSpan tem comprimento zero, ele se refere a um local entre dois caracteres.

Cada nó tem duas propriedades TextSpan: Span e FullSpan.

A propriedade Span é o intervalo de texto do início do primeiro token na subárvore do


nó ao final do último token. Esse intervalo não inclui nenhum desafio à esquerda ou à
direita.

A propriedade FullSpan é o intervalo de texto que inclui o intervalo normal do nó mais o


intervalo de qualquer desafio à esquerda ou à direita.

Por exemplo:

C#

if (x > 3)
{
|| // this is bad
|throw new Exception("Not right.");| // better exception?||
}

O nó de instrução dentro do bloco tem um intervalo indicado pelas barras verticais


simples (|). Ele inclui os caracteres throw new Exception("Not right."); . O intervalo total
é indicado pelas barras verticais duplas (||). Ele inclui os mesmos caracteres do intervalo
e os caracteres associados ao desafio à esquerda e à direita.
Variantes
Cada nó, token ou desafio tem uma propriedade SyntaxNode.RawKind, do tipo
System.Int32, que identifica o elemento de sintaxe exato representado. Esse valor pode
ser convertido em uma enumeração específica a idioma. Cada linguagem, C# ou Visual
Basic, tem uma única enumeração SyntaxKind
(Microsoft.CodeAnalysis.CSharp.SyntaxKind e
Microsoft.CodeAnalysis.VisualBasic.SyntaxKind, respectivamente), que lista todos os
possíveis nós, tokens e elementos de trívia na gramática. Esta conversão pode ser feita
automaticamente acessando os métodos de extensão CSharpExtensions.Kind ou
VisualBasicExtensions.Kind.

A propriedade RawKind permite a desambiguidade fácil de tipos de nó de sintaxe que


compartilham a mesma classe de nó. Para tokens e desafios, essa propriedade é a única
maneira de diferenciar um tipo de elemento de outro.

Por exemplo, uma única classe BinaryExpressionSyntax tem Left, OperatorToken e Right
como filhos. A propriedade Kind distingue se ela é um tipo AddExpression,
SubtractExpression ou MultiplyExpression de nó de sintaxe.

 Dica

É recomendado verificar os tipos usando os métodos de extensão IsKind (para C#)


ou IsKind (para VB).

Errors
Mesmo quando o texto de origem contém erros de sintaxe, uma árvore de sintaxe
completa com ida e volta para a origem é exposta. Quando o analisador encontra um
código que não está em conformidade com a sintaxe definida da linguagem, ele usa
uma das duas técnicas para criar uma árvore de sintaxe:

Se o analisador espera determinado tipo de token, mas não o encontra, ele pode
inserir um token ausente na árvore de sintaxe no local em que o token era
esperado. Um token ausente representa o token real que era esperado, mas tem
um intervalo vazio e sua propriedade SyntaxNode.IsMissing retorna true .

O analisador pode ignorar tokens até encontrar um no qual pode continuar a


análise. Nesse caso, os tokens ignorados são anexados como um nó de desafio
com o tipo SkippedTokensTrivia.
Trabalhar com semântica
Artigo • 10/05/2023

As árvores de sintaxe representam a estrutura lexical e sintática do código-fonte.


Embora essas informações apenas sejam suficientes para descrever todas as declarações
e a lógica na fonte, não são informações suficientes para identificar o que está sendo
referenciado. Um nome pode representar:

um tipo
um campo
um método
uma variável local

Embora cada um deles seja exclusivamente diferente, determinar a qual deles um


identificador, de fato, se refere exige uma compreensão profunda das regras da
linguagem.

Há elementos do programa representados no código-fonte e os programas também


podem se referir a bibliotecas compiladas anteriormente, empacotadas em arquivos do
assembly. Embora nenhum código-fonte e, portanto, nenhum nó ou árvore de sintaxe,
esteja disponível para assemblies, os programas ainda podem se referir a elementos
contidos neles.

Para essas tarefas, é necessário usar o Modelo semântico.

Além de um modelo sintático do código-fonte, um modelo semântico encapsula as


regras da linguagem, fornecendo uma maneira fácil para fazer a correspondência
correta de identificadores com o elemento de programa correto que está sendo
referenciado.

Compilação
Uma compilação é uma representação de tudo o que é necessário para compilar um
programa do C# ou Visual Basic, que inclui todas as referências de assembly, opções do
compilador e arquivos de origem.

Como todas essas informações estão em um só lugar, os elementos contidos no


código-fonte podem ser descritos mais detalhadamente. A compilação representa cada
tipo, membro ou variável declarada como um símbolo. A compilação contém uma
variedade de métodos que ajudam você encontrar e identificar os símbolos que foram
declarados no código-fonte ou importados como metadados de um assembly.
Semelhantes às árvores de sintaxe, as compilações são imutáveis. Depois de criar uma
compilação, ela não pode ser alterada por você ou por outra pessoa com quem você
pode está compartilhando. No entanto, é possível criar uma nova compilação com base
em uma compilação existente, especificando uma alteração conforme ela é feita. Por
exemplo, é possível criar uma compilação que é igual em todos os aspectos a uma
compilação existente, com exceção de que ela pode incluir um arquivo de origem ou
uma referência de assembly adicional.

Símbolos
Um símbolo representa um elemento distinto declarado pelo código-fonte ou
importado de um assembly como metadados. Cada namespace, tipo, método,
propriedade, campo, evento, parâmetro ou variável local é representado por um
símbolo.

Uma variedade de métodos e propriedades no tipo Compilation ajudam você a


encontrar símbolos. Por exemplo, encontre um símbolo para um tipo declarado pelo seu
nome comum de metadados. Também acesse a tabela inteira de símbolos como uma
árvore de símbolos com raiz no namespace global.

Os símbolos também contêm informações adicionais que o compilador determina da


fonte ou dos metadados, como outros símbolos referenciados. Cada tipo de símbolo é
representado por uma interface separada derivada de ISymbol, cada um com seus
próprios métodos e propriedades que fornecem detalhes das informações reunidas pelo
compilador. Muitas dessas propriedades referenciam outros símbolos diretamente. Por
exemplo, a propriedade IMethodSymbol.ReturnType indica o símbolo de tipo real que o
método retorna.

Os símbolos apresentam uma representação comum de namespaces, tipos e membros,


entre o código-fonte e os metadados. Por exemplo, um método que foi declarado no
código-fonte e um método que foi importado dos metadados são representados por
um IMethodSymbol com as mesmas propriedades.

Símbolos são semelhantes em conceito ao sistema de tipos CLR, conforme representado


pela API System.Reflection, mas são mais sofisticados pois modelam mais do que
apenas tipos. Namespaces, variáveis locais e rótulos são todos símbolos. Além disso, os
símbolos são uma representação dos conceitos da linguagem, não dos conceitos do
CLR. Há muita sobreposição, mas há muitas diferenças significativas também. Por
exemplo, um método iterador no C# ou Visual Basic é um único símbolo. No entanto,
quando o método iterador é convertido em metadados CLR, ele é um tipo e vários
métodos.
Modelo semântico
Um modelo semântico representa todas as informações semânticas de um único
arquivo de origem. Use-o para descobrir o seguinte:

Os símbolos referenciados em um local específico na fonte.


O tipo resultante de qualquer expressão.
Todo o diagnóstico, que são erros e avisos.
Como as variáveis fluem bidirecionalmente entre as regiões de origem.
As respostas a perguntas mais especulativas.
Trabalhar com um workspace
Artigo • 10/05/2023

A camada Workspaces é o ponto inicial para fazer a análise de código e refatoração em


soluções inteiras. Nessa camada, a API do Workspace ajudará você a organizar todas as
informações sobre os projetos de uma solução em um único modelo de objeto,
oferecendo acesso direto aos modelos de objeto da camada do compilador como texto
de origem, árvores de sintaxe, modelos semânticos e compilações, sem a necessidade
de analisar arquivos, configurar opções ou gerenciar dependências entre projetos.

Ambientes de host, como um IDE, fornecem um workspace para você correspondente à


solução aberta. Também é possível usar esse modelo fora de um IDE apenas carregando
um arquivo de solução.

Workspace
Um workspace é uma representação ativa da solução como uma coleção de projetos,
cada uma com uma coleção de documentos. Um workspace normalmente é vinculado a
um ambiente de host que está sendo modificado constantemente conforme um usuário
digita ou manipula propriedades.

O Workspace fornece acesso ao modelo atual da solução. Quando ocorre uma alteração
no ambiente de host, o workspace aciona eventos correspondentes e a propriedade
Workspace.CurrentSolution é atualizada. Por exemplo, quando o usuário digita em um
editor de texto algo correspondente a um dos documentos de origem, o workspace usa
um evento para sinalizar que o modelo geral da solução foi alterado e qual documento
foi modificado. Em seguida, você pode reagir a essas alterações analisando o novo
modelo quanto à exatidão, realçando áreas de significância ou fazendo uma sugestão
de alteração de código.

Também pode criar workspaces independentes desconectados do ambiente de host ou


usados em um aplicativo que não tem nenhum ambiente de host.

Soluções, projetos e documentos


Embora um workspace possa ser alterado sempre que uma tecla é pressionada, você
pode trabalhar com o modelo da solução de forma isolada.

Uma solução é um modelo imutável dos projetos e documentos. Isso significa que o
modelo pode ser compartilhado sem bloqueio nem duplicação. Depois de obter uma
instância da solução da propriedade Workspace.CurrentSolution, essa instância nunca
será alterada. No entanto, assim como árvores de sintaxe e compilações, você pode
modificar soluções construindo novas instâncias com base em soluções existentes e
alterações específicas. Para fazer com que o workspace reflita as alterações, é necessário
aplicar explicitamente a solução alterada novamente ao workspace.

Um projeto é uma parte do modelo de solução imutável geral. Ele representa todos os
documentos do código-fonte, opções de análise e compilação e referências de
assembly e projeto a projeto. Em um projeto, você pode acessar a compilação
correspondente sem a necessidade de determinar as dependências de projeto nem
analisar arquivos de origem.

Um documento também é uma parte do modelo de solução imutável geral. Um


documento representa um único arquivo de origem do qual você pode acessar o texto
do arquivo, a árvore de sintaxe e o modelo semântico.

O diagrama a seguir é uma representação de como o Workspace se relaciona ao


ambiente de host, às ferramentas e ao modo como as edições são feitas.

Resumo
O Roslyn expõe um conjunto de APIs do compilador e APIs dos Workspaces que
fornecem informações detalhadas sobre o código-fonte e que têm fidelidade total com
as linguagens C# e Visual Basic. O SDK do .NET Compiler Platform diminui
drasticamente a barreira de entrada para a criação de aplicativos e ferramentas voltadas
para o código. Ele cria várias oportunidades para inovação em áreas como
metaprogramação, geração e transformação de código, uso interativo das linguagens
C# e Visual Basic e incorporação de C# e Visual Basic em linguagens específicas de
domínio.
Explorar código com o visualizador de
sintaxe Roslyn no Visual Studio
Artigo • 10/05/2023

Este artigo oferece uma visão geral sobre a ferramenta de Visualizador de sintaxe que é
fornecida como parte do SDK do .NET Compiler Platform ("Roslyn"). O Visualizador de
sintaxe é uma janela de ferramentas que ajuda a inspecionar e explorar árvores de
sintaxe. É uma ferramenta essencial para compreender os modelos do código que você
deseja analisar. Também é um auxílio para depuração ao desenvolver seus próprios
aplicativos usando o SDK do.NET Compiler Platform ("Roslyn"). Abra essa ferramenta ao
criar seus primeiros analisadores. O visualizador ajuda você a entender os modelos
usados pelas APIs. Você também pode usar ferramentas como SharpLab ou
LINQPad para inspecionar o código e entender as árvores de sintaxe.

Instruções de instalação – Instalador do Visual


Studio
Há duas maneiras diferentes de encontrar o SDK da .NET Compiler Platform no
Instalador do Visual Studio:

Instalar usando o Instalador do Visual Studio – exibição


de cargas de trabalho
O SDK da .NET Compiler Platform não é selecionado automaticamente como parte da
carga de trabalho de desenvolvimento da extensão do Visual Studio. É necessário
selecioná-lo como um componente opcional.

1. Execute o Instalador do Visual Studio


2. Escolha Mudar
3. Marque a carga de trabalho de Desenvolvimento de extensão do Visual Studio.
4. Abra o nó Desenvolvimento de extensão do Visual Studio na árvore de resumo.
5. Marque a caixa do SDK da .NET Compiler Platform. Você a encontrará por último
nos componentes opcionais.

Opcionalmente, você também poderá fazer o Editor DGML exibir gráficos no


visualizador:

1. Abra o nó Componentes individuais na árvore de resumo.


2. Marque a caixa do Editor DGML
Instalar usando o Instalador do Visual Studio – guia
Componentes individuais
1. Execute o Instalador do Visual Studio
2. Escolha Mudar
3. Selecione a guia Componentes individuais
4. Marque a caixa do SDK da .NET Compiler Platform. Você a encontrará na parte
superior, na seção Compiladores, ferramentas de compilação e runtimes.

Opcionalmente, você também poderá fazer o Editor DGML exibir gráficos no


visualizador:

1. Marque a caixa do Editor DGML. Você a encontrará na seção Ferramentas de


código.

Familiarize-se com os conceitos usados no SDK do.NET Compiler Platform lendo o


artigo de visão geral. Ele fornece uma introdução a árvores de sintaxe, nós, tokens e
trívia.

Visualizador de sintaxe
O Visualizador de sintaxe permite a inspeção da árvore de sintaxe de arquivo de código
C# ou Visual Basic na janela do editor atualmente ativa dentro do IDE do Visual Studio.
O visualizador pode ser iniciado ao clicar em Exibir>Outras Janelas>Visualizador de
sintaxe. Você também pode usar a barra de ferramentas de Início Rápido no canto
superior direito. Digite "sintaxe" e o comando para abrir o Visualizador de sintaxe
deverá aparecer.

Este comando abre o Visualizador de sintaxe como uma janela de ferramentas flutuante.
Se você não tiver uma janela de editor de código aberta, a exibição ficará em branco,
conforme mostrado na figura a seguir.
Encaixe esta janela de ferramentas em um local conveniente dentro do Visual Studio,
como o lado esquerdo. O Visualizador mostra informações sobre o arquivo de código
atual.

Crie um novo projeto usando o comando Arquivo>Novo Projeto. Você pode criar um
projeto Visual Basic ou C#. Quando o Visual Studio abre o arquivo de código principal
deste projeto, o visualizador exibe a árvore de sintaxe dele. Você pode abrir qualquer
arquivo de C# ou Visual Basic existente nesta instância do Visual Studio e o visualizador
exibirá a árvore de sintaxe do arquivo correspondente. Se você tiver vários arquivos de
código abertos no Visual Studio, o visualizador exibirá a árvore de sintaxe do arquivo de
código atualmente ativo, (o arquivo de código que tem o foco do teclado).

C#
Conforme mostrado nas imagens acima, a janela de ferramentas do visualizador exibe a
árvore de sintaxe na parte superior e uma grade de propriedade na parte inferior. A
grade de propriedade exibe as propriedades do item atualmente selecionado na árvore,
incluindo Type e Kind (SyntaxKind) do .NET do item.

As árvores de sintaxe incluem três tipos de itens: nós, tokens e trívia. Você pode ler mais
sobre esses tipos no artigo Trabalhar com sintaxe. Os itens de cada tipo estão
representados com cores diferentes. Clique no botão "Legenda" para uma visão geral
das cores usadas.

Cada item da árvore também exibe sua própria extensão. A extensão é composta pelos
índices (a posição inicial e final) do nó no arquivo de texto. No exemplo de C# anterior,
o token “UsingKeyword [0..5)” selecionado tem uma abrangência de cinco caracteres de
largura, [0..5). A notação "[..)" significa que o índice inicial faz parte da extensão, mas o
índice final não.

Há duas maneiras de navegar na árvore:

Expandir ou clicar em itens na árvore. O visualizador seleciona automaticamente o


texto correspondente à extensão do item no editor de código.
Clicar ou selecionar texto no editor de código. No exemplo anterior do Visual
Basic, se você seleciona a linha que contém "Module Module1" no editor de
código, o visualizador navega automaticamente até o nó ModuleStatement
correspondente na árvore.
O visualizador realça o item da árvore cuja extensão melhor corresponda com a
extensão do texto selecionado no editor.

O visualizador atualiza a árvore para corresponder às modificações no arquivo de


código ativo. Adicione uma chamada a Console.WriteLine() dentro de Main() .
Conforme você digita, o visualizador atualiza a árvore.

Pare de digitar quando já tiver digitado Console. . A árvore tem alguns itens coloridos
em rosa. Neste ponto, há erros (também conhecidos como "Diagnóstico") no código
digitado. Esses erros são anexados a nós, tokens e trívia na árvore de sintaxe. O
visualizador mostra quais itens têm erros anexados a eles, realçando a tela de fundo em
rosa. Você pode inspecionar os erros em qualquer item colorido em rosa passando o
mouse sobre o item. O visualizador exibe somente erros sintáticos (os erros
relacionados à sintaxe do código digitado); erros semânticos não são exibidos.

Gráficos de sintaxe
Clique com o botão direito do mouse em qualquer item da árvore e clique em Exibir
gráfico de sintaxe direcionado.

C#

O visualizador exibe uma representação gráfica da subárvore com raiz no item


selecionado. Repita essas etapas para o nó MethodDeclaration correspondente ao
método Main() no exemplo de C#. O visualizador exibe um gráfico de sintaxe que
tem a seguinte aparência:
O visualizador de gráfico de sintaxe tem uma opção para exibir uma legenda para seu
esquema de cores. Você também pode passar o mouse sobre itens específicos no
gráfico de sintaxe para exibir as respectivas propriedades.

Você pode exibir gráficos de sintaxe de itens diferentes da árvore repetidamente e os


gráficos serão sempre exibidos na mesma janela no Visual Studio. Você pode encaixar
essa janela em um local conveniente no Visual Studio para não precisar alternar entre as
guias para exibir um novo gráfico de sintaxe. A parte inferior, abaixo das janelas do
editor de código, geralmente é conveniente.

Veja o layout de encaixe para usar com a janela de ferramentas do visualizador e a


janela do gráfico de sintaxe:

Outra opção é colocar a janela de gráfico de sintaxe em um segundo monitor, em uma


configuração de dois monitores.

Inspecionando semântica
O Visualizador de sintaxe possibilita uma inspeção rudimentar de símbolos e
informações semânticas. Digite double x = 1 + 1; dentro de Main() no exemplo de C#.
Em seguida, selecione a expressão 1 + 1 na janela do editor de código. O visualizador
realça o nó AddExpression no visualizador. Clique com o botão direito do mouse nesse
AddExpression e clique em Exibir Symbol (se houver). Observe que a maioria dos itens
de menu tem o qualificador "se houver". O Visualizador de sintaxe inspeciona as
propriedades de um Nó, incluindo propriedades que podem não estar presentes em
todos os nós.

A grade de propriedade do visualizador é atualizada conforme mostrado na figura a


seguir: o símbolo da expressão é um SynthesizedIntrinsicOperatorSymbol com Kind =
Method.
Experimente Exibir TypeSymbol (se houver) para o mesmo nó AddExpression. A grade
de propriedade no visualizador é atualizada, conforme mostrado na figura a seguir,
indicando que o tipo da expressão selecionada é Int32 .

Experimente Exibir TypeSymbol Convertido (se houver) para o mesmo nó


AddExpression. A grade de propriedade é atualizada, indicando que, embora o tipo da
expressão seja Int32 , o tipo convertido da expressão é Double , conforme mostrado na
figura a seguir. Esse nó inclui informações de símbolo de tipo convertido porque a
expressão Int32 ocorre em um contexto em que deve ser convertida em um Double .
Essa conversão satisfaz o tipo Double especificado para a variável x no lado esquerdo
do operador de atribuição.

Por fim, experimente Exibir Valor Constante (se houver) para o mesmo nó
AddExpression. A grade de propriedade mostra que o valor da expressão é uma
constante de tempo de compilação com o valor 2 .
O exemplo anterior também pode ser replicado no Visual Basic. Digite Dim x As Double
= 1 + 1 em um arquivo do Visual Basic. Selecione a expressão 1 + 1 na janela do editor
de código. O visualizador realça o nó AddExpression correspondente no visualizador.
Repita as etapas anteriores para esta AddExpression e você verá resultados idênticos.

Examine mais código no Visual Basic. Atualize seu arquivo principal do Visual Basic com
o seguinte código:

VB

Imports C = System.Console

Module Program
Sub Main(args As String())
C.WriteLine()
End Sub
End Module

Esse código introduz um alias chamado C que mapeia para o tipo System.Console na
parte superior do arquivo e usa esse alias em Main() . Selecione o uso desse alias, o C
em C.WriteLine() , dentro do método Main() . O visualizador seleciona o nó
IdentifierName correspondente no visualizador. Clique com botão direito do mouse
nesse nó e clique em Exibir Symbol (se houver). A grade de propriedade mostra que
esse identificador é associado com o tipo System.Console , conforme mostrado na figura
a seguir:
Experimente Exibir AliasSymbol (se houver) para o mesmo nó IdentifierName. A grade
de propriedade mostra que o identificador é um alias com nome C , que é associado
com o destino System.Console . Em outras palavras, a grade de propriedade fornece
informações sobre o AliasSymbol correspondente ao identificador C .

Inspecione o símbolo correspondente a qualquer tipo, método e propriedade


declarados. Selecione o nó correspondente no visualizador e clique em Exibir Symbol
(se houver). Selecione o método Sub Main() , incluindo o corpo do método. Clique em
Exibir Symbol (se houver) para o nó SubBlock correspondente no visualizador. A grade
de propriedade mostra que o MethodSymbol deste SubBlock tem nome Main com o
tipo de retorno Void .

Os exemplos de Visual Basic acima podem ser facilmente replicados em C#. Digite using
C = System.Console; no lugar de Imports C = System.Console para o alias. As etapas

anteriores em C# geram resultados idênticos na janela do visualizador.

As operações de inspeção semântica estão disponíveis somente em nós. Elas não estão
disponíveis em tokens nem trívia. Nem todos os nós têm informações semânticas
interessantes para inspecionar. Quando um nó não tem informações semânticas
interessantes, clicar em Exibir * Symbol (se houver) mostrará uma grade de propriedade
em branco.

Você pode ler mais sobre as APIs para executar análise semântica no documento de
visão geral Trabalhar com semântica.

Fechando o visualizador de sintaxe


Você pode fechar a janela do visualizador quando não a estiver usando para examinar o
código-fonte. O visualizador de sintaxe atualiza a exibição enquanto você navega pelo
código, editando e alterando a origem. Ele poderá se tornar uma distração quando você
não estiver usando.
Escolher IDs de diagnóstico
Artigo • 23/12/2023

Uma ID de diagnóstico é a cadeia de caracteres associada a um determinado


diagnóstico, como um erro do compilador ou um diagnóstico produzido por um
analisador.

As IDs são exibidas de várias APIs, como:

DiagnosticDescriptor.Id
ObsoleteAttribute.DiagnosticId
ExperimentalAttribute.DiagnosticId

As IDs de diagnóstico também são usadas como identificadores na origem, por


exemplo, de arquivos de desativação de aviso #pragma ou .editorconfig.

Considerações
As IDs de diagnóstico devem ser exclusivas
As IDs de diagnóstico devem ser identificadores legais em C#
As IDs de diagnóstico devem ter menos de 15 caracteres
As IDs de diagnóstico devem ter o formato <PREFIX><number>
O prefixo é específico do seu projeto
O número representa o diagnóstico específico

7 Observação

É uma alteração de falha de origem para alterar as IDs de diagnóstico, pois as


supressões existentes seriam ignoradas se a ID fosse alterada.

Não limite o seu prefixo a dois caracteres (como CSXXX e CAXXXX ). Em vez disso, use um
prefixo mais longo para evitar conflitos. Por exemplo, o diagnóstico System.* usa
SYSLIB como prefixo.

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  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
Introdução à análise de sintaxe
Artigo • 09/05/2023

Neste tutorial, você explorará a API de sintaxe. A API de sintaxe fornece acesso às
estruturas de dados que descrevem um programa C# ou Visual Basic. Essas estruturas
de dados têm detalhes suficientes para que possam representar qualquer programa, de
qualquer tamanho. Essas estruturas podem descrever programas completos que
compilam e executam corretamente. Elas também podem descrever programas
incompletos, enquanto você os escreve no editor.

Para habilitar essa expressão avançada, as estruturas de dados e as APIs que compõem
a API de sintaxe são necessariamente complexas. Começaremos com a aparência da
estrutura de dados para o programa "Olá, Mundo" típico:

C#

using System;
using System.Collections.Generic;
using System.Linq;

namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}

Veja o texto do programa anterior. Você reconhece elementos familiares. O texto inteiro
representa um único arquivo de origem, ou uma unidade de compilação. As três
primeiras linhas do arquivo de origem são diretivas de uso. A origem restante está
contida em uma declaração de namespace. A declaração de namespace contém uma
declaração de classe filha. A declaração de classe contém uma declaração de método.

A API de sintaxe cria uma estrutura de árvore com a raiz que representa a unidade de
compilação. Nós da árvore representam as diretivas using, a declaração de namespace e
todos os outros elementos do programa. A estrutura da árvore continua até os níveis
mais baixos: a cadeia de caracteres "Olá, Mundo!" é um token literal de cadeia de
caracteres descendente de um argumento. A API de sintaxe fornece acesso à estrutura
do programa. Você pode consultar as práticas recomendadas de código específico,
percorrer a árvore inteira para entender o código e criar novas árvores ao modificar a
árvore existente.

Essa breve descrição fornece uma visão geral do tipo de informações acessíveis usando
a API de sintaxe. A API de sintaxe não é nada mais de uma API formal que descreve os
constructos de código familiares que você conhece do C#. As funcionalidades
completas incluem informações sobre como o código é formatado, incluindo quebras
de linha, espaço em branco e recuo. Usando essas informações, você pode representar
totalmente o código como escrito e lido por programadores humanos ou pelo
compilador. Usar essa estrutura permite que você interaja com o código-fonte em um
nível muito significativo. Não se trata mais de cadeias de caracteres de texto, mas de
dados que representam a estrutura de um programa C#.

Para começar, será necessário instalar o SDK do .NET Compiler Platform:

Instruções de instalação – Instalador do Visual


Studio
Há duas maneiras diferentes de encontrar o SDK da .NET Compiler Platform no
Instalador do Visual Studio:

Instalar usando o Instalador do Visual Studio – exibição


de cargas de trabalho
O SDK da .NET Compiler Platform não é selecionado automaticamente como parte da
carga de trabalho de desenvolvimento da extensão do Visual Studio. É necessário
selecioná-lo como um componente opcional.

1. Execute o Instalador do Visual Studio


2. Escolha Mudar
3. Marque a carga de trabalho de Desenvolvimento de extensão do Visual Studio.
4. Abra o nó Desenvolvimento de extensão do Visual Studio na árvore de resumo.
5. Marque a caixa do SDK da .NET Compiler Platform. Você a encontrará por último
nos componentes opcionais.

Opcionalmente, você também poderá fazer o Editor DGML exibir gráficos no


visualizador:

1. Abra o nó Componentes individuais na árvore de resumo.


2. Marque a caixa do Editor DGML
Instalar usando o Instalador do Visual Studio – guia
Componentes individuais
1. Execute o Instalador do Visual Studio
2. Escolha Mudar
3. Selecione a guia Componentes individuais
4. Marque a caixa do SDK da .NET Compiler Platform. Você a encontrará na parte
superior, na seção Compiladores, ferramentas de compilação e runtimes.

Opcionalmente, você também poderá fazer o Editor DGML exibir gráficos no


visualizador:

1. Marque a caixa do Editor DGML. Você a encontrará na seção Ferramentas de


código.

Noções básicas sobre árvores de sintaxe


Você pode usar a API de sintaxe para uma análise da estrutura do código C#. A API de
sintaxe expõe os analisadores, as árvores de sintaxe e os utilitários para analisar e criar
árvores de sintaxe. Trata-se do modo como você pesquisa o código em busca de
elementos de sintaxe específicos ou lê o código para um programa.

Uma árvore de sintaxe é uma estrutura de dados usada pelos compiladores C# e Visual
Basic para entender programas nessas linguagens. Árvores de sintaxe são produzidas
pelo mesmo analisador que é executado quando um projeto é compilado ou quando
um desenvolvedor pressiona F5. As árvores de sintaxe têm fidelidade total com à
linguagem de programação; cada bit de informações em um arquivo de código é
representado na árvore. Gravar uma árvore de sintaxe em texto reproduz o texto
original exato que foi analisado. As árvores de sintaxe também são imutáveis; uma vez
criada, uma árvore de sintaxe nunca pode ser alterada. Os consumidores de árvores
podem analisar as árvores de vários threads, sem bloqueios ou outras medidas de
simultaneidade, sabendo que os dados nunca são alterados. Você pode usar APIs para
criar novas árvores que são o resultado da modificação de uma árvore existente.

Os quatro principais blocos de construção de árvores de sintaxe são:

A classe Microsoft.CodeAnalysis.SyntaxTree, uma instância da qual representa uma


árvore de análise inteira. SyntaxTree é uma classe abstrata que tem derivativos
específicos a um idioma. Você usa os métodos de análise da classe
Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree (ou
Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxTree) para analisar o texto em
C# (ou em Visual Basic).
A classe Microsoft.CodeAnalysis.SyntaxNode, instâncias da qual representam
constructos sintáticos como declarações, instruções, cláusulas e expressões.
A estrutura Microsoft.CodeAnalysis.SyntaxToken, que representa uma pontuação,
operador, identificador ou palavra-chave individual.
Finalmente, a estrutura Microsoft.CodeAnalysis.SyntaxTrivia, que representa os bits
de informação sem significância sintática, tais como o espaço em branco entre
tokens, diretivas de pré-processamento e comentários.

Trívia, tokens e nós são compostos hierarquicamente para formar uma árvore que
representa completamente tudo em um fragmento de código do Visual Basic ou do C#.
Você pode ver essa estrutura usando a janela Visualizador de Sintaxe. No Visual Studio,
escolha Exibição>Outras Janelas>Visualizador de Sintaxe. Por exemplo, o arquivo de
origem C# anterior examinado usando o Visualizador de Sintaxe se parecerá com a
figura a seguir:
SyntaxNode: Azul | SyntaxToken: Verde | SyntaxTrivia: Vermelho

Ao navegar nessa estrutura de árvore, você pode encontrar qualquer instrução,


expressão, token ou bit de espaço em branco em um arquivo de código.

Embora você possa encontrar tudo em um arquivo de código usando as APIs de sintaxe,
a maioria dos cenários envolvem o exame de pequenos snippets de código ou a
pesquisa por instruções ou fragmentos específicos. Os dois exemplos a seguir mostram
usos típicos para navegar pela estrutura de códigos ou pesquisar por instruções
individuais.

Percorrendo árvores
Você pode examinar os nós em uma árvore de sintaxe de duas maneiras. Você pode
percorrer a árvore para examinar cada nó, ou então consultar elementos ou nós
específicos.

Passagem manual
Você pode ver o código concluído para essa amostra no nosso repositório do GitHub .

7 Observação

Os tipos de árvore de sintaxe usam a herança para descrever os elementos de


sintaxe diferentes que são válidos em locais diferentes no programa. Usar essas
APIs geralmente significa converter propriedades ou membros da coleção em tipos
derivados específicos. Nos exemplos a seguir, a atribuição e as conversões são
instruções separadas, usando variáveis explicitamente tipadas. Você pode ler o
código para ver os tipos de retorno da API e o tipo de runtime dos objetos
retornados. Na prática, é mais comum usar variáveis implicitamente tipadas e
depender de nomes de API para descrever o tipo de objeto que está sendo
examinado.

Criar um novo projeto de Ferramenta de Análise de Código Autônoma do C#:

No Visual Studio, escolha Arquivo>Novo>Projeto para exibir a caixa de diálogo


Novo Projeto.
Em Visual C#>Extensibilidade, escolha Ferramenta de Análise de Código
Autônoma.
Nomeie o projeto "SyntaxTreeManualTraversal" e clique em OK.

Você vai analisar o programa básico "Olá, Mundo!" mostrado anteriormente. Adicione o
texto ao programa Olá, Mundo como uma constante em sua classe Program :

C#

const string programText =


@"using System;
using System.Collections;
using System.Linq;
using System.Text;

namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(""Hello, World!"");
}
}
}";

Em seguida, adicione o código a seguir para criar a árvore de sintaxe para o texto do
código na constante programText . Adicione a seguinte linha ao seu método Main :

C#

SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);


CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

Essas duas linhas criam a árvore e recuperam o nó raiz dessa árvore. Agora você pode
examinar os nós na árvore. Adicione essas linhas ao seu método Main para exibir
algumas das propriedades do nó raiz na árvore:

C#

WriteLine($"The tree is a {root.Kind()} node.");


WriteLine($"The tree has {root.Members.Count} elements in it.");
WriteLine($"The tree has {root.Usings.Count} using statements. They are:");
foreach (UsingDirectiveSyntax element in root.Usings)
WriteLine($"\t{element.Name}");

Execute o aplicativo para ver o que seu código descobriu sobre o nó raiz nessa árvore.

Normalmente, percorreria a árvore para saber mais sobre o código. Neste exemplo,
você está analisando código que você conhece para explorar as APIs. Adicione o código
a seguir para examinar o primeiro membro do nó root :

C#

MemberDeclarationSyntax firstMember = root.Members[0];


WriteLine($"The first member is a {firstMember.Kind()}.");
var helloWorldDeclaration = (NamespaceDeclarationSyntax)firstMember;

Esse membro é um Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax.


Ele representa tudo no escopo da declaração namespace HelloWorld . Adicione o
seguinte código para examinar quais nós são declarados dentro do namespace
HelloWorld :

C#
WriteLine($"There are {helloWorldDeclaration.Members.Count} members declared
in this namespace.");
WriteLine($"The first member is a
{helloWorldDeclaration.Members[0].Kind()}.");

Execute o programa para ver o que você aprendeu.

Agora que você sabe que a declaração é um


Microsoft.CodeAnalysis.CSharp.Syntax.ClassDeclarationSyntax, declare uma nova variável
de tipo para examinar a declaração de classe. Essa classe contém somente um membro:
o método Main . Adicione o código a seguir para localizar o método Main e convertê-lo
em um Microsoft.CodeAnalysis.CSharp.Syntax.MethodDeclarationSyntax.

C#

var programDeclaration =
(ClassDeclarationSyntax)helloWorldDeclaration.Members[0];
WriteLine($"There are {programDeclaration.Members.Count} members declared in
the {programDeclaration.Identifier} class.");
WriteLine($"The first member is a {programDeclaration.Members[0].Kind()}.");
var mainDeclaration =
(MethodDeclarationSyntax)programDeclaration.Members[0];

O nó de declaração do método contém todas as informações de sintaxe sobre o


método. Permite exibir o tipo de retorno do método Main , o número e os tipos dos
argumentos e o texto do corpo do método. Adicione os códigos a seguir:

C#

WriteLine($"The return type of the {mainDeclaration.Identifier} method is


{mainDeclaration.ReturnType}.");
WriteLine($"The method has {mainDeclaration.ParameterList.Parameters.Count}
parameters.");
foreach (ParameterSyntax item in mainDeclaration.ParameterList.Parameters)
WriteLine($"The type of the {item.Identifier} parameter is
{item.Type}.");
WriteLine($"The body text of the {mainDeclaration.Identifier} method
follows:");
WriteLine(mainDeclaration.Body?.ToFullString());

var argsParameter = mainDeclaration.ParameterList.Parameters[0];

Execute o programa para ver todas as informações que você descobriu sobre este
programa:

text
The tree is a CompilationUnit node.
The tree has 1 elements in it.
The tree has 4 using statements. They are:
System
System.Collections
System.Linq
System.Text
The first member is a NamespaceDeclaration.
There are 1 members declared in this namespace.
The first member is a ClassDeclaration.
There are 1 members declared in the Program class.
The first member is a MethodDeclaration.
The return type of the Main method is void.
The method has 1 parameters.
The type of the args parameter is string[].
The body text of the Main method follows:
{
Console.WriteLine("Hello, World!");
}

Métodos de consulta
Além de percorrer árvores, você também pode explorar a árvore de sintaxe usando os
métodos de consulta definidos em Microsoft.CodeAnalysis.SyntaxNode. Esses métodos
devem ser imediatamente familiares a qualquer pessoa familiarizada com o XPath. Você
pode usar esses métodos com o LINQ para localizar itens rapidamente em uma árvore.
O SyntaxNode tem métodos de consulta como DescendantNodes, AncestorsAndSelf e
ChildNodes.

Você pode usar esses métodos de consulta para localizar o argumento para o método
Main como uma alternativa a navegar pela árvore. Adicione o seguinte código à parte

inferior do método Main :

C#

var firstParameters = from methodDeclaration in root.DescendantNodes()


.OfType<MethodDeclarationSyntax>()
where methodDeclaration.Identifier.ValueText == "Main"
select
methodDeclaration.ParameterList.Parameters.First();

var argsParameter2 = firstParameters.Single();

WriteLine(argsParameter == argsParameter2);

A primeira instrução usa uma expressão LINQ e o método DescendantNodes para


localizar o mesmo parâmetro do exemplo anterior.
Execute o programa e você poderá ver que a expressão LINQ encontrou o mesmo
parâmetro encontrado ao navegar manualmente pela árvore.

O exemplo usa instruções WriteLine para exibir informações sobre as árvores de sintaxe
conforme elas são percorridas. Você também pode aprender mais executando o
programa concluído no depurador. Você pode examinar mais das propriedades e
métodos que fazem parte da árvore de sintaxe criada para o programa Olá, Mundo.

Caminhadores de sintaxe
Muitas vezes, você deseja localizar todos os nós de um tipo específico em uma árvore
de sintaxe, por exemplo, cada declaração de propriedade em um arquivo. Ao estender a
classe Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker e substituir o método
VisitPropertyDeclaration(PropertyDeclarationSyntax), você processa cada declaração de
propriedade em uma árvore de sintaxe sem conhecer a estrutura dele com
antecedência. CSharpSyntaxWalker é um tipo específico de CSharpSyntaxVisitor, que
visita recursivamente um nó e cada um dos filhos desse nó.

Este exemplo implementa um CSharpSyntaxWalker que examina uma árvore de sintaxe.


Ele coleta diretivas using que ele constata que não estão importando um namespace
System .

Crie um novo projeto de Ferramenta de Análise de Código Autônoma do C#; nomeie-o


"SyntaxWalker".

Você pode ver o código concluído para essa amostra no nosso repositório do GitHub .
A amostra no GitHub contém os dois projetos descritos neste tutorial.

Assim como no exemplo anterior, você pode definir uma constante de cadeia de
caracteres para conter o texto do programa que você pretende analisar:

C#

const string programText =


@"using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace TopLevel
{
using Microsoft;
using System.ComponentModel;
namespace Child1
{
using Microsoft.Win32;
using System.Runtime.InteropServices;

class Foo { }
}

namespace Child2
{
using System.CodeDom;
using Microsoft.CSharp;

class Bar { }
}
}";

Este texto de origem contém diretivas using espalhadas em quatro locais diferentes: o
nível de arquivo, no namespace de nível superior e nos dois namespaces aninhados.
Este exemplo destaca um cenário principal para usar a classe CSharpSyntaxWalker para
consultar código. Seria complicado visitar cada nó na árvore de sintaxe de raiz para
encontrar declarações using. Em vez disso, você pode criar uma classe derivada e
substituir o método chamado apenas quando o nó atual na árvore é uma diretiva using.
O visitante não realiza nenhum trabalho em nenhum outro tipo de nó. Esse método
único examina cada uma das instruções using e compila uma coleção de namespaces
que não estão no namespace System . Você compila um CSharpSyntaxWalker que
examina todas as instruções using , mas apenas as instruções using .

Agora que você definiu o texto do programa, você precisa criar um SyntaxTree e obter
a raiz dessa árvore:

C#

SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);


CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

Em seguida, crie uma nova classe. No Visual Studio, escolha Projeto>Adicionar Novo
Item. Na caixa de diálogo Adicionar Novo Item, digite UsingCollector.cs como o nome
do arquivo.

Você implementa a funcionalidade de visitante using na classe UsingCollector . Para


começar, crie a classe UsingCollector derivada de CSharpSyntaxWalker.

C#

class UsingCollector : CSharpSyntaxWalker


Você precisa de armazenamento para conter os nós de namespace que você está
coletando. Declare uma propriedade pública somente leitura na classe UsingCollector ;
use essa variável para armazenar os nós UsingDirectiveSyntax que você encontrar:

C#

public ICollection<UsingDirectiveSyntax> Usings { get; } = new


List<UsingDirectiveSyntax>();

A classe base CSharpSyntaxWalker implementa a lógica para visitar cada nó na árvore de


sintaxe. A classe derivada substitui os métodos chamados para os nós específicos nos
quais você está interessado. Nesse caso, você está interessado em qualquer diretiva
using . Isso significa que você deve substituir o método

VisitUsingDirective(UsingDirectiveSyntax). Um argumento para esse método é um


objeto Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax. Essa é uma
importante vantagem de se usar os visitantes: eles chamam os métodos substituídos
com argumentos que já foram convertidos para o tipo de nó específico. A classe
Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax tem uma propriedade Name
que armazena o nome do namespace que está sendo importado. É um
Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax. Adicione o código a seguir na
substituição VisitUsingDirective(UsingDirectiveSyntax):

C#

public override void VisitUsingDirective(UsingDirectiveSyntax node)


{
WriteLine($"\tVisitUsingDirective called with {node.Name}.");
if (node.Name.ToString() != "System" &&
!node.Name.ToString().StartsWith("System."))
{
WriteLine($"\t\tSuccess. Adding {node.Name}.");
this.Usings.Add(node);
}
}

Assim como no exemplo anterior, você adicionou uma variedade de instruções


WriteLine para ajudar na compreensão do método. Você pode ver quando ele é

chamado e quais argumentos são passados para ele a cada vez.

Por fim, você precisa adicionar duas linhas de código para criar o UsingCollector e fazer
com que ele acesse o nó raiz, coletando todas as instruções using . Em seguida, adicione
um loop foreach para exibir todas as instruções using encontradas pelo seu coletor:
C#

var collector = new UsingCollector();


collector.Visit(root);
foreach (var directive in collector.Usings)
{
WriteLine(directive.Name);
}

Compile e execute o programa. Você deve ver o seguinte resultado:

Console

VisitUsingDirective called with System.


VisitUsingDirective called with System.Collections.Generic.
VisitUsingDirective called with System.Linq.
VisitUsingDirective called with System.Text.
VisitUsingDirective called with Microsoft.CodeAnalysis.
Success. Adding Microsoft.CodeAnalysis.
VisitUsingDirective called with Microsoft.CodeAnalysis.CSharp.
Success. Adding Microsoft.CodeAnalysis.CSharp.
VisitUsingDirective called with Microsoft.
Success. Adding Microsoft.
VisitUsingDirective called with System.ComponentModel.
VisitUsingDirective called with Microsoft.Win32.
Success. Adding Microsoft.Win32.
VisitUsingDirective called with System.Runtime.InteropServices.
VisitUsingDirective called with System.CodeDom.
VisitUsingDirective called with Microsoft.CSharp.
Success. Adding Microsoft.CSharp.
Microsoft.CodeAnalysis
Microsoft.CodeAnalysis.CSharp
Microsoft
Microsoft.Win32
Microsoft.CSharp
Press any key to continue . . .

Parabéns! Você usou a API de sintaxe para localizar tipos específicos de instruções C# e
declarações em código-fonte C#.
Introdução à análise semântica
Artigo • 10/05/2023

Este tutorial presume que você está familiarizado com a API de sintaxe. O artigo
Introdução à a análise de sintaxe fornece uma introdução suficiente.

Neste tutorial, você explora as APIs de Símbolo e de Associação. Essas APIs fornecem
informações sobre o significado semântico de um programa. Elas permitem fazer e
responder perguntas sobre os tipos representado por qualquer símbolo em seu
programa.

Você deverá instalar o SDK do .NET Compiler Platform:

Instruções de instalação – Instalador do Visual


Studio
Há duas maneiras diferentes de encontrar o SDK da .NET Compiler Platform no
Instalador do Visual Studio:

Instalar usando o Instalador do Visual Studio – exibição


de cargas de trabalho
O SDK da .NET Compiler Platform não é selecionado automaticamente como parte da
carga de trabalho de desenvolvimento da extensão do Visual Studio. É necessário
selecioná-lo como um componente opcional.

1. Execute o Instalador do Visual Studio


2. Escolha Mudar
3. Marque a carga de trabalho de Desenvolvimento de extensão do Visual Studio.
4. Abra o nó Desenvolvimento de extensão do Visual Studio na árvore de resumo.
5. Marque a caixa do SDK da .NET Compiler Platform. Você a encontrará por último
nos componentes opcionais.

Opcionalmente, você também poderá fazer o Editor DGML exibir gráficos no


visualizador:

1. Abra o nó Componentes individuais na árvore de resumo.


2. Marque a caixa do Editor DGML
Instalar usando o Instalador do Visual Studio – guia
Componentes individuais
1. Execute o Instalador do Visual Studio
2. Escolha Mudar
3. Selecione a guia Componentes individuais
4. Marque a caixa do SDK da .NET Compiler Platform. Você a encontrará na parte
superior, na seção Compiladores, ferramentas de compilação e runtimes.

Opcionalmente, você também poderá fazer o Editor DGML exibir gráficos no


visualizador:

1. Marque a caixa do Editor DGML. Você a encontrará na seção Ferramentas de


código.

Noções básicas sobre compilações e símbolos


Conforme você trabalha mais com o SDK do .NET Compiler, você se familiariza com as
distinções entre a API de Sintaxe e a API de Semântica. A API de Sintaxe permite que
você examine a estrutura de um programa. Muitas vezes, no entanto, você deseja as
informações sobre a semântica ou significado de um programa. Embora um snippet ou
arquivo de código livre do Visual Basic ou C# possa ser analisado sintaticamente de
modo isolado, não faz sentido fazer a esmo perguntas como "qual é o tipo dessa
variável". O significado de um nome de tipo pode ser dependente de referências de
assembly, importações de namespace ou outros arquivos de código. Essas perguntas
são respondidas usando-se a API de Semântica, especificamente a classe
Microsoft.CodeAnalysis.Compilation.

Uma instância de Compilation é análoga a um único projeto conforme visto pelo


compilador e representa tudo o que é necessário para compilar um programa Visual
Basic ou C#. A compilação inclui o conjunto de arquivos de origem a serem compilados,
referências de assembly e opções de compilador. Você pode avaliar o significado do
código usando todas as outras informações neste contexto. Um Compilation permite
que você encontre símbolos – entidades como tipos, namespaces, membros e variáveis
aos quais os nomes e outras expressões se referem. O processo de associar nomes e
expressões com símbolos é chamado de associação.

Assim como Microsoft.CodeAnalysis.SyntaxTree, Compilation é uma classe abstrata com


derivativos específicos a um idioma. Ao criar uma instância de compilação, você deve
invocar um método de fábrica na classe
Microsoft.CodeAnalysis.CSharp.CSharpCompilation (ou
Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation).
Consultar símbolos
Neste tutorial, você analisa novamente o programa "Olá, Mundo". Dessa vez, você
consulta os símbolos no programa para compreender quais tipos esses símbolos
representam. Você consulta os tipos em um namespace e aprende a localizar os
métodos disponíveis em um tipo.

Você pode ver o código concluído para essa amostra no nosso repositório do GitHub .

7 Observação

Os tipos de árvore de sintaxe usam a herança para descrever os elementos de


sintaxe diferentes que são válidos em locais diferentes no programa. Usar essas
APIs geralmente significa converter propriedades ou membros da coleção em tipos
derivados específicos. Nos exemplos a seguir, a atribuição e as conversões são
instruções separadas, usando variáveis explicitamente tipadas. Você pode ler o
código para ver os tipos de retorno da API e o tipo de runtime dos objetos
retornados. Na prática, é mais comum usar variáveis implicitamente tipadas e
depender de nomes de API para descrever o tipo de objeto que está sendo
examinado.

Criar um novo projeto de Ferramenta de Análise de Código Autônoma do C#:

No Visual Studio, escolha Arquivo>Novo>Projeto para exibir a caixa de diálogo


Novo Projeto.
Em Visual C#>Extensibilidade, escolha Ferramenta de Análise de Código
Autônoma.
Nomeie o projeto "SemanticQuickStart" e clique em OK.

Você vai analisar o programa básico "Olá, Mundo!" mostrado anteriormente. Adicione o
texto ao programa Olá, Mundo como uma constante em sua classe Program :

C#

const string programText =


@"using System;
using System.Collections.Generic;
using System.Text;

namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(""Hello, World!"");
}
}
}";

Em seguida, adicione o código a seguir para criar a árvore de sintaxe para o texto do
código na constante programText . Adicione a seguinte linha ao seu método Main :

C#

SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);

CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

Em seguida, compile uma CSharpCompilation da árvore que você já criou. A amostra


"Olá, Mundo" depende dos tipos String e Console. Você precisa fazer referência ao
assembly que declara esses dois tipos em sua compilação. Adicione a seguinte linha ao
seu método Main para criar uma compilação de sua árvore de sintaxe, incluindo a
referência ao assembly apropriado:

C#

var compilation = CSharpCompilation.Create("HelloWorld")


.AddReferences(MetadataReference.CreateFromFile(
typeof(string).Assembly.Location))
.AddSyntaxTrees(tree);

O método CSharpCompilation.AddReferences adiciona referências à compilação. O


método MetadataReference.CreateFromFile carrega um assembly como uma referência.

Consultar o modelo semântico


Assim que você tiver uma Compilation, você poderá solicitar a ela um SemanticModel
para qualquer SyntaxTree contida nessa Compilation. Você pode pensar no modelo
semântico como a origem de todas as informações normalmente obtidas do
IntelliSense. Um SemanticModel pode responder a perguntas como "O que são nomes
no escopo nesse local?", "Quais membros são acessíveis deste método?", "Quais
variáveis são usadas neste bloco de texto?" e "A que este nome/expressão se refere?"
Adicione esta instrução para criar o modelo semântico:

C#

SemanticModel model = compilation.GetSemanticModel(tree);


Associar um nome
A Compilation cria o SemanticModel da SyntaxTree. Depois de criar o modelo, você
pode consultar para localizar a primeira diretiva using e recuperar as informações de
símbolo para o namespace System . Adicione estas duas linhas a seu método Main para
criar o modelo semântico e recuperar o símbolo para a primeira instrução using:

C#

// Use the syntax tree to find "using System;"


UsingDirectiveSyntax usingSystem = root.Usings[0];
NameSyntax systemName = usingSystem.Name;

// Use the semantic model for symbol information:


SymbolInfo nameInfo = model.GetSymbolInfo(systemName);

O código anterior mostra como associar o nome na primeira diretiva using para
recuperar um Microsoft.CodeAnalysis.SymbolInfo para o namespace System . O código
anterior também ilustra o uso da sintaxe de modelo para localizar a estrutura do
código; você usa o modelo semântico para entender seu significado. A sintaxe de
modelo localiza a cadeia de caracteres System na instrução using. O modelo semântico
tem todas as informações sobre os tipos definidos no namespace System .

Do objeto SymbolInfo, você pode obter o Microsoft.CodeAnalysis.ISymbol usando a


propriedade SymbolInfo.Symbol. Essa propriedade retorna o símbolo a que essa
expressão se refere. Para expressões que não se referem a nada (como literais
numéricos), essa propriedade é null . Quando o SymbolInfo.Symbol não for null, o
ISymbol.Kind denotará o tipo do símbolo. Nesse exemplo, a propriedade ISymbol.Kind é
um SymbolKind.Namespace. Adicione o código a seguir ao método Main . Ele recupera o
símbolo para o namespace System e, em seguida, exibe todos os namespaces filho
declarados no namespace System :

C#

var systemSymbol = (INamespaceSymbol?)nameInfo.Symbol;


if (systemSymbol?.GetNamespaceMembers() is not null)
{
foreach (INamespaceSymbol ns in systemSymbol?.GetNamespaceMembers()!)
{
Console.WriteLine(ns);
}
}

Execute o programa e você deverá ver a seguinte saída:


Saída

System.Collections
System.Configuration
System.Deployment
System.Diagnostics
System.Globalization
System.IO
System.Numerics
System.Reflection
System.Resources
System.Runtime
System.Security
System.StubHelpers
System.Text
System.Threading
Press any key to continue . . .

7 Observação

A saída não inclui todos os namespaces que são namespaces filhos do namespace
System . El exibe cada namespace presente nessa compilação, que só referencia o

assembly em que System.String é declarada. Quaisquer outros namespaces


declarados em outros assemblies não são conhecidos desta compilação

Associar uma expressão


O código anterior mostra como encontrar um símbolo associando-o a um nome. Há
outras expressões em um programa C# que podem ser associadas que não são nomes.
Para demonstrar essa capacidade, acessaremos a associação a um único literal de cadeia
de caracteres.

O programa "Olá, Mundo" contém


Microsoft.CodeAnalysis.CSharp.Syntax.LiteralExpressionSyntax, uma cadeia de caracteres
"Olá, Mundo!" exibida no console.

Você encontra a cadeia de caracteres "Olá, Mundo!" localizando o único literal de cadeia
de caracteres no programa. Em seguida, depois de localizar o nó de sintaxe, você obtém
as informações de tipo para esse nó do modelo semântico. Adicione o código a seguir
ao método Main :

C#

// Use the syntax model to find the literal string:


LiteralExpressionSyntax helloWorldString = root.DescendantNodes()
.OfType<LiteralExpressionSyntax>()
.Single();

// Use the semantic model for type information:


TypeInfo literalInfo = model.GetTypeInfo(helloWorldString);

O struct Microsoft.CodeAnalysis.TypeInfo inclui uma propriedade TypeInfo.Type que


permite o acesso às informações semânticas sobre o tipo do literal. Neste exemplo, ele é
do tipo string . Adicione uma declaração que atribui essa propriedade a uma variável
local:

C#

var stringTypeSymbol = (INamedTypeSymbol?)literalInfo.Type;

Para concluir este tutorial, criaremos uma consulta LINQ que criará uma sequência de
todos os métodos públicos declarados no tipo string que retorna um string . Essa
consulta torna-se complexa, então a compilaremos linha a linha e então a
reconstruiremos como uma única consulta. A ordem desta consulta é a sequência de
todos os membros declarados no tipo string :

C#

var allMembers = stringTypeSymbol?.GetMembers();

Essa sequência de origem contém todos os membros, incluindo propriedades e campos,


portanto, filtre-a usando o método ImmutableArray<T>.OfType para localizar elementos
que são objetos Microsoft.CodeAnalysis.IMethodSymbol:

C#

var methods = allMembers?.OfType<IMethodSymbol>();

Em seguida, adicione outro filtro para retornar somente os métodos que são públicos e
retornam um string :

C#

var publicStringReturningMethods = methods?


.Where(m => SymbolEqualityComparer.Default.Equals(m.ReturnType,
stringTypeSymbol) &&
m.DeclaredAccessibility == Accessibility.Public);
Selecione apenas a propriedade de nome e somente os nomes distintos, removendo
quaisquer sobrecargas:

C#

var distinctMethods = publicStringReturningMethods?.Select(m =>


m.Name).Distinct();

Você pode também compilar a consulta completa usando a sintaxe de consulta LINQ e,
em seguida, exibir todos os nomes de método no console:

C#

foreach (string name in (from method in stringTypeSymbol?


.GetMembers().OfType<IMethodSymbol>()
where
SymbolEqualityComparer.Default.Equals(method.ReturnType, stringTypeSymbol)
&&
method.DeclaredAccessibility ==
Accessibility.Public
select method.Name).Distinct())
{
Console.WriteLine(name);
}

Compile e execute o programa. Você deve ver o seguinte resultado:

Saída

Join
Substring
Trim
TrimStart
TrimEnd
Normalize
PadLeft
PadRight
ToLower
ToLowerInvariant
ToUpper
ToUpperInvariant
ToString
Insert
Replace
Remove
Format
Copy
Concat
Intern
IsInterned
Press any key to continue . . .

Você usou a API de semântica para localizar e exibir informações sobre os símbolos que
fazem parte deste programa.
Introdução à transformação de sintaxe
Artigo • 09/05/2023

Este tutorial baseia-se nos conceitos e técnicas explorados nos guias de início rápido
Introdução à análise de sintaxe e Introdução à análise semântica. Se ainda não o fez,
você deve concluir as etapas rápidas antes de começar esta.

Neste início rápido, você explora técnicas para criar e transformar árvores de sintaxe. Em
combinação com as técnicas que você aprendeu em guias de início rápido anteriores,
você cria sua primeira refatoração de linha de comando!

Instruções de instalação – Instalador do Visual


Studio
Há duas maneiras diferentes de encontrar o SDK da .NET Compiler Platform no
Instalador do Visual Studio:

Instalar usando o Instalador do Visual Studio – exibição


de cargas de trabalho
O SDK da .NET Compiler Platform não é selecionado automaticamente como parte da
carga de trabalho de desenvolvimento da extensão do Visual Studio. É necessário
selecioná-lo como um componente opcional.

1. Execute o Instalador do Visual Studio


2. Escolha Mudar
3. Marque a carga de trabalho de Desenvolvimento de extensão do Visual Studio.
4. Abra o nó Desenvolvimento de extensão do Visual Studio na árvore de resumo.
5. Marque a caixa do SDK da .NET Compiler Platform. Você a encontrará por último
nos componentes opcionais.

Opcionalmente, você também poderá fazer o Editor DGML exibir gráficos no


visualizador:

1. Abra o nó Componentes individuais na árvore de resumo.


2. Marque a caixa do Editor DGML

Instalar usando o Instalador do Visual Studio – guia


Componentes individuais
1. Execute o Instalador do Visual Studio
2. Escolha Mudar
3. Selecione a guia Componentes individuais
4. Marque a caixa do SDK da .NET Compiler Platform. Você a encontrará na parte
superior, na seção Compiladores, ferramentas de compilação e runtimes.

Opcionalmente, você também poderá fazer o Editor DGML exibir gráficos no


visualizador:

1. Marque a caixa do Editor DGML. Você a encontrará na seção Ferramentas de


código.

Imutabilidade e a plataforma de compiladores


.NET
Imutabilidade é um princípio fundamental da plataforma de compiladores .NET.
Estruturas de dados imutáveis ​não podem ser alteradas depois de criadas. Estruturas de
dados imutáveis ​podem ser compartilhadas com segurança e analisadas por vários
consumidores simultaneamente. Não há perigo de que um consumidor afete o outro de
maneiras imprevisíveis. Seu analisador não precisa de bloqueios ou outras medidas de
simultaneidade. Essa regra se aplica a árvores de sintaxe, compilações, símbolos,
modelos semânticos e todas as outras estruturas de dados que você encontrar. Em vez
de modificar as estruturas existentes, as APIs criam novos objetos com base nas
diferenças especificadas para os antigos. Você aplica esse conceito a árvores de sintaxe
para criar novas árvores usando transformações.

Criar e transformar árvores


Você escolhe uma das duas estratégias para transformações de sintaxe. Os métodos de
fábrica são melhor usados ​quando você está procurando por nós específicos para
substituir ou locais específicos onde deseja inserir um novo código. Regravadores são a
melhor opção quando você deseja examinar um projeto inteiro em busca dos padrões
de código que deseja substituir.

Criar nós com métodos de fábrica


A primeira transformação de sintaxe demonstra os métodos de fábrica. Substitua uma
instrução using System.Collections; por uma instrução using
System.Collections.Generic; . Este exemplo demonstra como você cria objetos
Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode usando os métodos de fábrica
Microsoft.CodeAnalysis.CSharp.SyntaxFactory. Para cada tipo de nó, tokenou trívia, há
um método de fábrica que cria uma instância desse tipo. Você cria árvores de sintaxe
compondo os nós hierarquicamente de baixa para cima. Em seguida, você transformará
o programa existente substituindo nós existentes pela nova árvore criada.

Inicie o Visual Studio e crie um novo projeto de Ferramenta de Análise de Código


Autônoma do C#. No Visual Studio, escolha Arquivo>Novo>Projeto para exibir a caixa
de diálogo Novo Projeto. Em Visual C#>Extensibilidade, escolha uma Ferramenta de
Análise de Código Autônoma. Este guia de início rápido tem dois projetos de exemplo,
portanto, nomeie a solução SyntaxTransformationQuickStart e nomeie o projeto
ConstructionCS. Clique em OK.

Este projeto usa os métodos de classe Microsoft.CodeAnalysis.CSharp.SyntaxFactory


para construir um Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax representando o
namespace System.Collections.Generic .

Adicione a seguinte diretiva de uso à parte superior do Program.cs .

C#

using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;


using static System.Console;

Você criará nós de sintaxe de nome para construir a árvore que representa a instrução
using System.Collections.Generic; . NameSyntax é a classe base para quatro tipos de
nomes que aparecem no C#. Você compõe esses quatro tipos de nomes para criar
qualquer nome que possa aparecer na linguagem C#:

Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax, que representa nomes simples


de identificadores únicos como System e Microsoft .
Microsoft.CodeAnalysis.CSharp.Syntax.GenericNameSyntax, que representa um
tipo genérico ou nome de método, como List<int> .
Microsoft.CodeAnalysis.CSharp.Syntax.QualifiedNameSyntax,que representa um
nome qualificado do formulário <left-name>.<right-identifier-or-generic-name> ,
como System.IO .
Microsoft.CodeAnalysis.CSharp.Syntax.AliasQualifiedNameSyntax, que representa
um nome usando um alias externo de assembly como LibraryV2::Foo .

Você usa o método IdentifierName(String) para criar um nó NameSyntax. Adicione o


seguinte código no seu método Main no Program.cs :

C#
NameSyntax name = IdentifierName("System");
WriteLine($"\tCreated the identifier {name}");

O código anterior cria um objeto IdentifierNameSyntax e o atribui à variável name .


Muitas das APIs Roslyn retornam classes básicas para facilitar o trabalho com tipos
relacionados. A variável name , um NameSyntax, pode ser reutilizada conforme você
constrói o QualifiedNameSyntax. Não use inferência de tipo ao criar a amostra. Você
automatizará essa etapa neste projeto.

Você criou o nome. Agora, é hora de criar mais nós na árvore criando um
QualifiedNameSyntax. A nova árvore usa name como a esquerda do nome e um novo
IdentifierNameSyntax para o namespace Collections como o lado direito do
QualifiedNameSyntax. Adicione o seguinte código a program.cs :

C#

name = QualifiedName(name, IdentifierName("Collections"));


WriteLine(name.ToString());

Execute o código novamente e confira os resultados. Você está construindo uma árvore
de nós que representa o código. Você continuará este padrão para construir o
QualifiedNameSyntax para o namespace System.Collections.Generic . Adicione o
seguinte código a Program.cs :

C#

name = QualifiedName(name, IdentifierName("Generic"));


WriteLine(name.ToString());

Execute o programa novamente para ver se você construiu a árvore para o código a ser
adicionado.

Criar uma árvore modificada


Você criou uma pequena árvore de sintaxe que contém uma instrução. As APIs para criar
novos nós são a escolha certa para criar instruções únicas ou outros pequenos blocos
de código. No entanto, para construir blocos maiores de código, você deve usar
métodos que substituem nós ou inserem nós em uma árvore existente. Lembre-se de
que as árvores de sintaxe são imutáveis. A API de Sintaxe não fornece nenhum
mecanismo para modificar uma árvore de sintaxe existente após a construção. Em vez
disso, fornece métodos que produzem novas árvores com base nas alterações
existentes. Os métodos With* são definidos em classes concretas que derivam de
SyntaxNode ou em métodos de extensão declarados na classe SyntaxNodeExtensions.
Esses métodos criam um novo nó aplicando alterações nas propriedades filho de um nó
existente. Além disso, o método de extensão ReplaceNode pode ser usado para
substituir um nó descendente em uma subárvore. Esse método também atualiza o pai
para apontar para o filho recém-criado e repete esse processo até a árvore inteira — um
processo conhecido como re-spinning da árvore.

O próximo passo é criar uma árvore que represente um programa inteiro (pequeno) e
depois modificá-la. Adicione o seguinte código ao início da classe Program :

C#

private const string sampleCode =


@"using System;
using System.Collections;
using System.Linq;
using System.Text;

namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(""Hello, World!"");
}
}
}";

7 Observação

O código de exemplo usa o namespace System.Collections e não o namespace


System.Collections.Generic .

Em seguida, adicione o seguinte código à parte inferior do método Main para analisar o
texto e criar uma árvore:

C#

SyntaxTree tree = CSharpSyntaxTree.ParseText(sampleCode);


var root = (CompilationUnitSyntax)tree.GetRoot();

Este exemplo usa o método WithName(NameSyntax) para substituir o nome em um nó


UsingDirectiveSyntax pelo que foi construído no código anterior.
Crie um novo nó UsingDirectiveSyntax usando o método WithName(NameSyntax) para
atualizar o nome System.Collections com o nome criado no código anterior. Adicione o
seguinte código à parte inferior do método Main :

C#

var oldUsing = root.Usings[1];


var newUsing = oldUsing.WithName(name);
WriteLine(root.ToString());

Execute o programa e observe atentamente a saída. O newUsing não foi colocado na


árvore raiz. A árvore original não foi alterada.

Adicione o seguinte código usando o método de extensão ReplaceNode para criar uma
nova árvore. A nova árvore é o resultado da substituição da importação existente pelo
nó newUsing atualizado. Você atribui essa nova árvore à variável root ​existente:

C#

root = root.ReplaceNode(oldUsing, newUsing);


WriteLine(root.ToString());

Execute o programa novamente. Desta vez, a árvore importa corretamente o namespace


System.Collections.Generic .

Transformar árvores usando SyntaxRewriters


Os métodos With* e ReplaceNode fornecem meios convenientes para transformar
ramos individuais de uma árvore de sintaxe. A classe
Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter realiza várias transformações em
uma árvore de sintaxe. A classe Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter é
uma subclasse de Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor<TResult>. O
CSharpSyntaxRewriter aplica uma transformação a um tipo específico de SyntaxNode.
Você pode aplicar transformações a vários tipos de objetos SyntaxNode sempre que
eles aparecerem em uma árvore de sintaxe. O segundo projeto neste guia de início
rápido cria uma refatoração de linha de comando que remove tipos explícitos em
declarações de variáveis ​locais em qualquer lugar em que a inferência de tipo possa ser
usada.

Crie um novo projeto de Ferramenta de Análise de Código Autônoma do C#. No Visual


Studio, clique com o botão direito do mouse no nó da solução
SyntaxTransformationQuickStart . Escolha Adicionar>Novo Projeto para exibir o diálogo
Novo Projeto. Em Visual C#>Extensibilidade, escolha Ferramenta de Análise de
Código Autônoma. Nomeie seu projeto como TransformationCS e clique em OK.

A primeira etapa é criar uma classe que deriva de CSharpSyntaxRewriter para executar
as transformações. Adicione um novo arquivo de classe ao projeto. No Visual Studio,
escolha Projeto>Adicionar Classes.... Na caixa de diálogo Adicionar Novo Item, digite
TypeInferenceRewriter.cs como nome do arquivo.

Adicione o seguinte usando diretivas ao arquivo TypeInferenceRewriter.cs :

C#

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

Em seguida, faça a classe TypeInferenceRewriter se estender à classe


CSharpSyntaxRewriter:

C#

public class TypeInferenceRewriter : CSharpSyntaxRewriter

Adicione o seguinte código para declarar um campo somente leitura privado para
conter um SemanticModel e inicializá-lo no construtor. Você precisará deste campo
posteriormente para determinar onde a inferência de tipos pode ser usada:

C#

private readonly SemanticModel SemanticModel;

public TypeInferenceRewriter(SemanticModel semanticModel) => SemanticModel =


semanticModel;

Substitua o método VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax):

C#

public override SyntaxNode


VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax node)
{

}
7 Observação

Muitas das APIs Roslyn declaram os tipos de retorno que são classes base dos tipos
de runtime reais retornados. Em muitos cenários, um tipo de nó pode ser
substituído inteiramente por outro tipo de nó, ou até mesmo removido. Neste
exemplo, o método
VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax) retorna
SyntaxNode, em vez do tipo derivado de LocalDeclarationStatementSyntax. Este
regravador retorna um novo nó LocalDeclarationStatementSyntax baseado no
existente.

Este guia de início rápido lida com declarações de variáveis ​locais. Você poderia
estendê-lo para outras declarações, como loops foreach , loops for , expressões LINQ e
expressões lambda. Além disso, este regravador só irá transformar as declarações da
forma mais simples:

C#

Type variable = expression;

Se você quiser explorar por conta própria, considere estender a amostra finalizada para
esses tipos de declarações de variáveis:

C#

// Multiple variables in a single declaration.


Type variable1 = expression1,
variable2 = expression2;
// No initializer.
Type variable;

Adicione o seguinte código ao corpo do método VisitLocalDeclarationStatement para


ignorar a reescrita dessas formas de declaração:

C#

if (node.Declaration.Variables.Count > 1)
{
return node;
}
if (node.Declaration.Variables[0].Initializer == null)
{
return node;
}
O método indica que nenhuma reescrita ocorre retornando o parâmetro node não
modificado. Se nenhuma dessas expressões if for verdadeira, o nó representa uma
possível declaração com inicialização. Adicione estas instruções para extrair o nome do
tipo especificado na declaração e vinculá-lo usando o campo SemanticModel para obter
um símbolo de tipo:

C#

var declarator = node.Declaration.Variables.First();


var variableTypeName = node.Declaration.Type;

var variableType = (ITypeSymbol)SemanticModel


.GetSymbolInfo(variableTypeName)
.Symbol;

Agora, adicione esta instrução para associar a expressão inicializadora:

C#

var initializerInfo =
SemanticModel.GetTypeInfo(declarator.Initializer.Value);

Por fim, inclua a seguinte instrução if para substituir o nome do tipo existente pela
palavra-chave var , se o tipo de expressão do inicializador corresponder ao tipo
especificado:

C#

if (SymbolEqualityComparer.Default.Equals(variableType,
initializerInfo.Type))
{
TypeSyntax varTypeName = SyntaxFactory.IdentifierName("var")
.WithLeadingTrivia(variableTypeName.GetLeadingTrivia())
.WithTrailingTrivia(variableTypeName.GetTrailingTrivia());

return node.ReplaceNode(variableTypeName, varTypeName);


}
else
{
return node;
}

A condicional é necessária porque a declaração pode converter a expressão


inicializadora em uma classe ou interface base. Se este for o caso, os tipos à esquerda e
à direita da atribuição não coincidem. Remover o tipo explícito nesses casos alteraria a
semântica de um programa. var é especificado como um identificador em vez de uma
palavra-chave porque var é uma palavra-chave contextual. As trivialidades inicial e final
(espaço em branco) são transferidas do nome do tipo antigo para a palavra-chave var
para manter espaço em branco vertical e recuo. É mais simples usar ReplaceNode em vez
de With* para transformar o LocalDeclarationStatementSyntax, pois o nome do tipo é
na verdade o neto da declaração.

Você concluiu o TypeInferenceRewriter . Agora, retorne ao arquivo Program.cs para


finalizar o exemplo. Crie um teste Compilation e obtenha o SemanticModel dele. Use
esse SemanticModel para testar seu TypeInferenceRewriter . Você realizará esta etapa
por último. Enquanto isso, declare uma variável de espaço reservado representando sua
compilação de teste:

C#

Compilation test = CreateTestCompilation();

Após uma pausa, um erro será exibido informando que não existe método
CreateTestCompilation . Pressione Ctrl + Ponto para abrir a lâmpada e pressione Enter

para invocar o comando Gerar Stub de Método. Este comando irá gerar um stub de
método para o método CreateTestCompilation na classe Program . Você voltará a
preencher este método mais tarde:

Grave o seguinte código para iterar sobre cada SyntaxTree no teste Compilation. Para
cada um, inicialize um novo TypeInferenceRewriter com o SemanticModel para essa
árvore:
C#

foreach (SyntaxTree sourceTree in test.SyntaxTrees)


{
SemanticModel model = test.GetSemanticModel(sourceTree);

TypeInferenceRewriter rewriter = new TypeInferenceRewriter(model);

SyntaxNode newSource = rewriter.Visit(sourceTree.GetRoot());

if (newSource != sourceTree.GetRoot())
{
File.WriteAllText(sourceTree.FilePath, newSource.ToFullString());
}
}

Dentro da instrução foreach criada, adicione o seguinte código para executar a


transformação em cada árvore de origem. Este código registra condicionalmente a nova
árvore transformada se alguma edição tiver sido feita. Seu regravador só deve modificar
uma árvore se encontrar uma ou mais declarações de variáveis ​locais que poderiam ser
simplificadas usando a inferência de tipos:

C#

SyntaxNode newSource = rewriter.Visit(sourceTree.GetRoot());

if (newSource != sourceTree.GetRoot())
{
File.WriteAllText(sourceTree.FilePath, newSource.ToFullString());
}

Você deve ver rabiscos abaixo do código File.WriteAllText . Selecione a lâmpada e


adicione a instrução using System.IO; necessária.

Você está quase lá! Resta somente uma etapa: criar um teste Compilation. Como você
não usou a inferência de tipos durante este guia de início rápido, este seria um caso de
teste perfeito. Infelizmente, criar uma compilação a partir de um arquivo de projeto C#
está além do escopo desta explicação passo a passo. Mas, felizmente, se você está
seguindo as instruções cuidadosamente, há esperança. Substitua o conteúdo do
método CreateTestCompilation pelo código a seguir. Ele cria uma compilação de teste
que combina coincidentemente com o projeto descrito neste guia de início rápido:

C#

String programPath = @"..\..\..\Program.cs";


String programText = File.ReadAllText(programPath);
SyntaxTree programTree =
CSharpSyntaxTree.ParseText(programText)
.WithFilePath(programPath);

String rewriterPath = @"..\..\..\TypeInferenceRewriter.cs";


String rewriterText = File.ReadAllText(rewriterPath);
SyntaxTree rewriterTree =
CSharpSyntaxTree.ParseText(rewriterText)
.WithFilePath(rewriterPath);

SyntaxTree[] sourceTrees = { programTree, rewriterTree };

MetadataReference mscorlib =
MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
MetadataReference codeAnalysis =

MetadataReference.CreateFromFile(typeof(SyntaxTree).Assembly.Location);
MetadataReference csharpCodeAnalysis =

MetadataReference.CreateFromFile(typeof(CSharpSyntaxTree).Assembly.Location)
;

MetadataReference[] references = { mscorlib, codeAnalysis,


csharpCodeAnalysis };

return CSharpCompilation.Create("TransformationCS",
sourceTrees,
references,
new CSharpCompilationOptions(OutputKind.ConsoleApplication));

Cruze os dedos e execute o projeto. No Visual Studio, escolha Depurar>Iniciar


Depuração. O Visual Studio exibirá um aviso dizendo que os arquivos em seu projeto
foram alterados. Clique em "Sim para Todos" para recarregar os arquivos modificados.
Examine-os para observar sua grandiosidade. Observe como o código parece mais
limpo sem todos os especificadores de tipo explícitos e redundantes.

Parabéns! Você usou as APIs do compilador para criar sua própria refatoração que
pesquisa todos os arquivos em um projeto C# para determinados padrões sintáticos,
analisa a semântica do código-fonte que corresponde a esses padrões e os transforma.
Agora você é oficialmente um autor de refatoração!
Tutorial: escrever seu primeiro
analisador e correção de código
Artigo • 10/05/2023

O SDK do .NET Compiler Platform fornece as ferramentas necessárias para criar


diagnósticos personalizados (analisadores), correções de código, refatoração de código
e supressores de diagnóstico destinados a códigos C# ou Visual Basic. Um analisador
contém código que reconhece violações às suas regras. Sua correção de código contém
o código que corrige a violação. As regras que você implementar podem ser qualquer
coisa, incluindo estrutura do código, estilo de codificação, convenções de nomenclatura
e muito mais. O .NET Compiler Platform fornece a estrutura para executar análise
conforme os desenvolvedores escrevem o código, bem como todos os recursos de
interface do usuário do Visual Studio para corrigir o código: mostrar rabiscos no editor,
popular a Lista de Erros do Visual Studio, criar as sugestões da "lâmpada" e mostrar a
visualização avançada das correções sugeridas.

Neste tutorial, você explorará a criação de um analisador e uma correção de código


que o acompanha, usando as APIs do Roslyn. Um analisador é uma maneira de executar
a análise de código-fonte e relatar um problema para o usuário. Opcionalmente, uma
correção de código pode ser associada ao analisador para representar uma modificação
no código-fonte do usuário. Este tutorial cria um analisador que localiza as declarações
de variável local que poderiam ser declaradas usando o modificador const , mas não o
são. A correção de código anexa modifica essas declarações para adicionar o
modificador const .

Pré-requisitos
Visual Studio 2019 , versão 16.8 ou posterior

Será necessário instalar o SDK do .NET Compiler Platform por meio do Instalador do
Visual Studio:

Instruções de instalação – Instalador do Visual


Studio
Há duas maneiras diferentes de encontrar o SDK da .NET Compiler Platform no
Instalador do Visual Studio:
Instalar usando o Instalador do Visual Studio – exibição
de cargas de trabalho
O SDK da .NET Compiler Platform não é selecionado automaticamente como parte da
carga de trabalho de desenvolvimento da extensão do Visual Studio. É necessário
selecioná-lo como um componente opcional.

1. Execute o Instalador do Visual Studio


2. Escolha Mudar
3. Marque a carga de trabalho de Desenvolvimento de extensão do Visual Studio.
4. Abra o nó Desenvolvimento de extensão do Visual Studio na árvore de resumo.
5. Marque a caixa do SDK da .NET Compiler Platform. Você a encontrará por último
nos componentes opcionais.

Opcionalmente, você também poderá fazer o Editor DGML exibir gráficos no


visualizador:

1. Abra o nó Componentes individuais na árvore de resumo.


2. Marque a caixa do Editor DGML

Instalar usando o Instalador do Visual Studio – guia


Componentes individuais
1. Execute o Instalador do Visual Studio
2. Escolha Mudar
3. Selecione a guia Componentes individuais
4. Marque a caixa do SDK da .NET Compiler Platform. Você a encontrará na parte
superior, na seção Compiladores, ferramentas de compilação e runtimes.

Opcionalmente, você também poderá fazer o Editor DGML exibir gráficos no


visualizador:

1. Marque a caixa do Editor DGML. Você a encontrará na seção Ferramentas de


código.

Há várias etapas para criar e validar o analisador:

1. Crie a solução.
2. Registre o nome e a descrição do analisador.
3. Relate os avisos e recomendações do analisador.
4. Implemente a correção de código para aceitar as recomendações.
5. Melhore a análise por meio de testes de unidade.
Criar a solução
No Visual Studio, escolha Arquivo > Novo > Projeto... para exibir o diálogo Novo
Projeto.
Em Visual C# > Extensibilidade, escolha Analisador com correção de código
(.NET Standard).
Nomeie seu projeto como "MakeConst" e clique em OK.

7 Observação

Você pode receber um erro de compilação (MSB4062: não foi possível carregar a
tarefa "CompareBuildTaskVersion"). Para corrigir isso, atualize os pacotes NuGet na
solução com o Gerenciador de Pacotes NuGet ou use Update-Package na janela do
console do Gerenciador de Pacotes.

Explorar o modelo do analisador


O analisador com o modelo de correção de código cria cinco projetos:

MakeConst, que contém o analisador.


MakeConst.CodeFixes, que contém a correção de código.
MakeConst.Package, que é usado para produzir o pacote NuGet para o analisador
e a correção de código.
MakeConst.Test, que é um projeto de teste de unidade.
MakeConst.Vsix, que é o projeto de inicialização padrão que carrega uma segunda
instância do Visual Studio com seu novo analisador. Pressione F5 para iniciar o
projeto VSIX.

7 Observação

Os analisadores devem ter como destino o .NET Standard 2.0 porque podem ser
executados nos ambientes do .NET Core (builds de linha de comando) e do .NET
Framework (Visual Studio).

 Dica

Quando você executa seu analisador, você pode iniciar uma segunda cópia do
Visual Studio. Essa segunda cópia usa um hive do Registro diferente para
armazenar configurações. Isso lhe permite diferenciar as configurações visuais em
duas cópias do Visual Studio. Você pode escolher um tema diferente para a
execução experimental do Visual Studio. Além disso, não use perfil móvel de suas
configurações nem faça logon na conta do Visual Studio usando a execução
experimental do Visual Studio. Isso mantém as diferenças entre as configurações.

O hive inclui não apenas o analisador em desenvolvimento, mas também todos os


analisadores anteriores abertos. Para redefinir o hive da Roslyn, você precisa excluí-
lo manualmente de %LocalAppData%\Microsoft\VisualStudio. O nome da pasta do
hive da Roslyn terminará em Roslyn , por exemplo, 16.0_9ae182f9Roslyn . Observe
que talvez seja necessário limpar a solução e compilar novamente após excluir o
hive.

Na segunda instância do Visual Studio que você acabou de iniciar, crie um projeto de
Aplicativo de Console do C# (qualquer estrutura de destino funcionará – os analisadores
funcionam no nível de origem). Passe o mouse sobre o token com um sublinhado
ondulado e o texto de aviso fornecido por um analisador será exibido.

O modelo cria um analisador que relata um aviso em cada declaração de tipo em que o
nome do tipo contém letras minúsculas, conforme mostrado na figura a seguir:

O modelo também fornece uma correção de código que altera qualquer nome de tipo
que contenha caracteres de letras minúsculas, deixando-o com todas as letras
maiúsculas. Você pode clicar na lâmpada exibida com o aviso para ver as alterações
sugeridas. Aceitar as alterações sugeridas atualiza o nome do tipo e todas as referências
para esse tipo na solução. Agora que você já viu o analisador inicial em ação, feche a
segunda instância do Visual Studio e retorne ao projeto do analisador.

Você não precisa iniciar uma segunda cópia do Visual Studio e criar um novo código
para testar cada alteração em seu analisador. O modelo também cria um projeto de
teste de unidade para você. Esse projeto contém dois testes. TestMethod1 mostra o
formato típico de um teste que analisa o código sem disparar um diagnóstico.
TestMethod2 mostra o formato de um teste que dispara um diagnóstico e, em seguida,
aplica uma correção de código sugerida. Conforme você cria o analisador e a correção
de código, você escreve testes para estruturas de código diferentes para verificar seu
trabalho. Testes de unidade para os analisadores são muito mais rápidos do que testá-
los de forma interativa com o Visual Studio.

 Dica

Testes de unidade de analisador são uma excelente ferramenta quando você sabe
quais constructos de código devem e não devem disparar seu analisador. Carregar
o analisador em outra cópia do Visual Studio é uma excelente ferramenta para
explorar e encontrar constructos nos quais você talvez não tenha pensado ainda.

Neste tutorial, você grava um analisador que relata ao usuário qualquer declaração de
variável local que possa ser convertida em constante local. Por exemplo, considere o
seguinte código:

C#

int x = 0;
Console.WriteLine(x);

No código acima, um valor constante é atribuído a x , que nunca é modificado. Ele pode
ser declarado usando o modificador const :

C#

const int x = 0;
Console.WriteLine(x);

A análise para determinar se uma variável pode ser tornada constante está envolvida,
exigindo análise sintática, análise constante da expressão de inicializador e também
análise de fluxo de dados, para garantir que nunca ocorram gravações na variável. O
.NET Compiler Platform fornece APIs que facilitam essa análise.

Criar registros do analisador


O modelo cria a classe inicial DiagnosticAnalyzer , no arquivo MakeConstAnalyzer.cs.
Esse analisador inicial mostra duas propriedades importantes de cada analisador.

Cada analisador de diagnóstico deve fornecer um atributo [DiagnosticAnalyzer]


que descreve a linguagem em que opera.
Cada analisador de diagnóstico deve derivar (direta ou indiretamente) da classe
DiagnosticAnalyzer.

O modelo também mostra os recursos básicos que fazem parte de qualquer analisador:

1. Registrar ações. As ações representam alterações de código que devem disparar o


analisador para examinar se há violações de código. Quando o Visual Studio
detecta as edições de código que correspondem a uma ação registrada, ele chama
o método registrado do analisador.
2. Criar diagnósticos. Quando o analisador detecta uma violação, ele cria um objeto
de diagnóstico que o Visual Studio usa para notificar o usuário sobre a violação.

Registrar ações na substituição do método


DiagnosticAnalyzer.Initialize(AnalysisContext). Neste tutorial, você visitará nós de sintaxe
em busca de declarações locais e verá quais delas têm valores constantes. Se houver
possibilidade de uma declaração ser constante, seu analisador criará e relatará um
diagnóstico.

A primeira etapa é atualizar as constantes de registro e o método Initialize , de modo


que essas constantes indiquem seu analisador "Make Const". A maioria das constantes
de cadeia de caracteres é definida no arquivo de recurso de cadeia de caracteres. Você
deve seguir essa prática para uma localização mais fácil. Abra o arquivo Resources.resx
para o projeto do analisador MakeConst. Isso exibe o editor de recursos. Atualize os
recursos de cadeia de caracteres da seguinte maneira:

Altere AnalyzerDescription para "Variables that are not modified should be made
constants.".
Altere AnalyzerMessageFormat para "Variable '{0}' can be made constant".
Altere AnalyzerTitle para "Variable can be made constant".

Quando você terminar, o editor de recursos deverá aparecer conforme mostrado na


seguinte figura:

As alterações restantes estão no arquivo do analisador. Abra MakeConstAnalyzer.cs no


Visual Studio. Altere a ação registrada de uma que age em símbolos para uma que age
sobre a sintaxe. No método MakeConstAnalyzerAnalyzer.Initialize , localize a linha que
registra a ação em símbolos:

C#
context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);

Substitua-a com a seguinte linha:

C#

context.RegisterSyntaxNodeAction(AnalyzeNode,
SyntaxKind.LocalDeclarationStatement);

Após essa alteração, você poderá excluir o método AnalyzeSymbol . Este analisador
examina SyntaxKind.LocalDeclarationStatement, e não instruções
SymbolKind.NamedType. Observe que AnalyzeNode tem rabiscos vermelhos sob ele. O
código apenas que você acaba de adicionar referencia um método AnalyzeNode que
não foi declarado. Declare esse método usando o seguinte código:

C#

private void AnalyzeNode(SyntaxNodeAnalysisContext context)


{
}

Altere Category para "Usage" em MakeConstAnalyzer.cs, conforme mostrado no


seguinte código:

C#

private const string Category = "Usage";

Localize as declarações locais que podem ser


constantes
É hora de escrever a primeira versão do método AnalyzeNode . Ele deve procurar uma
única declaração local que poderia ser const mas não é, algo semelhante ao seguinte
código:

C#

int x = 0;
Console.WriteLine(x);
A primeira etapa é encontrar declarações locais. Adicione o seguinte código a
AnalyzeNode em MakeConstAnalyzer.cs:

C#

var localDeclaration = (LocalDeclarationStatementSyntax)context.Node;

Essa conversão sempre terá êxito porque seu analisador fez o registro para alterações
unicamente a declarações locais. Nenhum outro tipo de nó dispara uma chamada para
seu método AnalyzeNode . Em seguida, verifique a declaração para quaisquer
modificadores const . Se você encontrá-los, retorne imediatamente. O código a seguir
procura por quaisquer modificadores const na declaração local:

C#

// make sure the declaration isn't already const:


if (localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
{
return;
}

Por fim, você precisa verificar que a variável pode ser const . Isso significa assegurar que
ela nunca seja atribuída após ser inicializada.

Você executará alguma análise semântica usando o SyntaxNodeAnalysisContext. Você


usa o argumento context para determinar se a declaração de variável local pode ser
tornada const . Um Microsoft.CodeAnalysis.SemanticModel representa todas as
informações semânticas em apenas um arquivo de origem. Você pode aprender mais no
artigo que aborda modelos semânticos. Você usará o
Microsoft.CodeAnalysis.SemanticModel para realizar a análise de fluxo de dados na
instrução de declaração local. Em seguida, você usa os resultados dessa análise de fluxo
de dados para garantir que a variável local não seja escrita com um novo valor em
nenhum outro lugar. Chame o método de extensão GetDeclaredSymbol para recuperar
o ILocalSymbol para a variável e verifique se ele não está contido na coleção
DataFlowAnalysis.WrittenOutside da análise de fluxo de dados. Adicione o seguinte
código ao final do método AnalyzeNode :

C#

// Perform data flow analysis on the local declaration.


DataFlowAnalysis dataFlowAnalysis =
context.SemanticModel.AnalyzeDataFlow(localDeclaration);

// Retrieve the local symbol for each variable in the local declaration
// and ensure that it is not written outside of the data flow analysis
region.
VariableDeclaratorSyntax variable =
localDeclaration.Declaration.Variables.Single();
ISymbol variableSymbol = context.SemanticModel.GetDeclaredSymbol(variable,
context.CancellationToken);
if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
{
return;
}

O código recém-adicionado garante que a variável não seja modificada e pode,


portanto, ser tornada const . É hora de gerar o diagnóstico. Adicione o código a seguir
como a última linha em AnalyzeNode :

C#

context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation(),
localDeclaration.Declaration.Variables.First().Identifier.ValueText));

Você pode verificar seu andamento pressionando F5 para executar o analisador. Você
pode carregar o aplicativo de console que você criou anteriormente e, em seguida,
adicionar o seguinte código de teste:

C#

int x = 0;
Console.WriteLine(x);

A lâmpada deve aparecer e o analisador deve relatar um diagnóstico. No entanto,


dependendo da sua versão do Visual Studio, você verá:

A lâmpada, que ainda usa a correção de código gerada por modelo e dirá a você
que ela pode ser colocada em letras maiúsculas.
Uma mensagem na barra de notificações, na parte superior do editor, informando
que o "MakeConstCodeFixProvider" encontrou um erro e foi desabilitado. Isso
ocorre porque o provedor de correção de código ainda não foi alterado e espera
encontrar TypeDeclarationSyntax elementos em vez de
LocalDeclarationStatementSyntax .

A próxima seção explica como escrever a correção de código.

Escrever a correção de código


Um analisador pode fornecer uma ou mais correções de código. Uma correção de
código define uma edição que resolve o problema relatado. Para o analisador que você
criou, você pode fornecer uma correção de código que insere a palavra-chave const:

diff

- int x = 0;
+ const int x = 0;
Console.WriteLine(x);

O usuário escolhe-a da lâmpada da interface do usuário no editor e do Visual Studio


altera o código.

Abra o arquivo CodeFixResources.resx e altere CodeFixTitle para "Make constant".

Abra o arquivo MakeConstCodeFixProvider.cs adicionado pelo modelo. Essa correção de


código já está conectada à ID de Diagnóstico produzida pelo analisador de diagnóstico,
mas ela ainda não implementa a transformação de código correta.

Em seguida, exclua o método MakeUppercaseAsync . Ele não se aplica mais.

Todos os provedores de correção de código derivam de CodeFixProvider. Todos eles


substituem CodeFixProvider.RegisterCodeFixesAsync(CodeFixContext) para relatar as
correções de código disponíveis. Em RegisterCodeFixesAsync , altere o tipo de nó
ancestral pelo qual você está pesquisando para um LocalDeclarationStatementSyntax
para corresponder ao diagnóstico:

C#

var declaration =
root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<LocalD
eclarationStatementSyntax>().First();

Em seguida, altere a última linha para registrar uma correção de código. A correção
criará um novo documento resultante da adição do modificador const para uma
declaração existente:

C#

// Register a code action that will invoke the fix.


context.RegisterCodeFix(
CodeAction.Create(
title: CodeFixResources.CodeFixTitle,
createChangedDocument: c => MakeConstAsync(context.Document,
declaration, c),
equivalenceKey: nameof(CodeFixResources.CodeFixTitle)),
diagnostic);

Você observará rabiscos vermelhos no código que você acabou de adicionar no símbolo
MakeConstAsync . Adicione uma declaração para MakeConstAsync semelhante ao seguinte
código:

C#

private static async Task<Document> MakeConstAsync(Document document,


LocalDeclarationStatementSyntax localDeclaration,
CancellationToken cancellationToken)
{
}

Seu novo método MakeConstAsync transformará o Document que representa o arquivo


de origem do usuário em um novo Document que agora contém uma declaração const .

Você cria um novo token de palavra-chave const a ser inserido no início da instrução de
declaração. Tenha cuidado para remover qualquer desafio à esquerda do primeiro token
de instrução de declaração e anexe-o ao token const . Adicione o seguinte código ao
método MakeConstAsync :

C#

// Remove the leading trivia from the local declaration.


SyntaxToken firstToken = localDeclaration.GetFirstToken();
SyntaxTriviaList leadingTrivia = firstToken.LeadingTrivia;
LocalDeclarationStatementSyntax trimmedLocal =
localDeclaration.ReplaceToken(
firstToken, firstToken.WithLeadingTrivia(SyntaxTriviaList.Empty));

// Create a const token with the leading trivia.


SyntaxToken constToken = SyntaxFactory.Token(leadingTrivia,
SyntaxKind.ConstKeyword,
SyntaxFactory.TriviaList(SyntaxFactory.ElasticMarker));

Em seguida, adicione o token const à declaração usando o seguinte código:

C#

// Insert the const token into the modifiers list, creating a new modifiers
list.
SyntaxTokenList newModifiers = trimmedLocal.Modifiers.Insert(0, constToken);
// Produce the new local declaration.
LocalDeclarationStatementSyntax newLocal = trimmedLocal
.WithModifiers(newModifiers)
.WithDeclaration(localDeclaration.Declaration);

Em seguida, formate a nova declaração de acordo com regras de formatação de C#.


Formatar de suas alterações para corresponderem ao código existente cria uma
experiência melhor. Adicione a instrução a seguir imediatamente após o código
existente:

C#

// Add an annotation to format the new local declaration.


LocalDeclarationStatementSyntax formattedLocal =
newLocal.WithAdditionalAnnotations(Formatter.Annotation);

Um novo namespace é necessário para esse código. Adicione a seguinte diretiva using
para a parte superior do arquivo:

C#

using Microsoft.CodeAnalysis.Formatting;

A etapa final é fazer a edição. Há três etapas para esse processo:

1. Obter um identificador para o documento existente.


2. Criar um novo documento, substituindo a declaração existente pela nova
declaração.
3. Retornar o novo documento.

Adicione o seguinte código ao final do método MakeConstAsync :

C#

// Replace the old local declaration with the new local declaration.
SyntaxNode oldRoot = await
document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
SyntaxNode newRoot = oldRoot.ReplaceNode(localDeclaration, formattedLocal);

// Return document with transformed tree.


return document.WithSyntaxRoot(newRoot);

A correção de código está pronta para ser experimentada. Pressione F5 para executar o
projeto do analisador em uma segunda instância do Visual Studio. Na segunda instância
do Visual Studio, crie um novo projeto de Aplicativo de Console em C# e adicione
algumas declarações de variável local inicializadas com valores constantes para o
método Main. Você verá que elas são relatadas como avisos, conforme mostrado a
seguir.

Você fez muito progresso. Há rabiscos sob as declarações que podem ser tornados
const . Mas ainda há trabalho a fazer. Isso funciona bem se você adicionar const às

declarações começando com i , depois j e, por fim, k . Mas se você adicionar o


modificador const em uma ordem diferente, começando com k , seu analisador criará
erros: k não pode ser declarado como const , a menos que i e j já sejam ambos
const . Você tem que fazer mais análise para assegurar que lida com as diferentes
maneiras em que variáveis podem ser declaradas e inicializadas.

Criar testes de unidade


Seu analisador e correção de código trabalham em um caso simples de uma única
declaração que pode ser tornada const. Há várias instruções de declaração possíveis em
que essa implementação comete erros. Você tratará desses casos trabalhando com a
biblioteca de teste de unidade gravada pelo modelo. Isso é muito mais rápido do que
abrir repetidamente uma segunda cópia do Visual Studio.

Abra o arquivo MakeConstUnitTests.cs no projeto de teste de unidade. O modelo criou


dois testes que seguem os dois padrões comuns para um analisador e o teste de
unidade de correção de código. TestMethod1 mostra o padrão para um teste que
garante que o analisador não relata um diagnóstico quando não deve fazê-lo.
TestMethod2 mostra o padrão para relatar um diagnóstico e executar a correção de
código.

O modelo usa pacotes Microsoft.CodeAnalysis.Testing para testes de unidade.

 Dica

A biblioteca de testes dá suporte a uma sintaxe de marcação especial, incluindo o


seguinte:

[|text|] : indica que um diagnóstico é relatado para text . Por padrão, esse

formulário só pode ser usado para testar analisadores com exatamente um


DiagnosticDescriptor fornecido por

DiagnosticAnalyzer.SupportedDiagnostics .

{|ExpectedDiagnosticId:text|} : indica que um diagnóstico com

Id ExpectedDiagnosticId é relatado para text .

Substitua os testes de modelo na classe MakeConstUnitTest pelo seguinte método de


teste:

C#

[TestMethod]
public async Task LocalIntCouldBeConstant_Diagnostic()
{
await VerifyCS.VerifyCodeFixAsync(@"
using System;

class Program
{
static void Main()
{
[|int i = 0;|]
Console.WriteLine(i);
}
}
", @"
using System;

class Program
{
static void Main()
{
const int i = 0;
Console.WriteLine(i);
}
}
");
}

Execute este teste para garantir que ele passa. No Visual Studio, abra o Gerenciador de
Testes selecionando Teste>Windows>Gerenciador de Testes. Então selecione Executar
tudo.

Criar testes para declarações válidas


Como regra geral, os analisadores devem sair assim que possível, fazendo o mínimo de
trabalho. O Visual Studio chama analisadores registrados conforme o usuário edita o
código. A capacidade de resposta é um requisito fundamental. Há vários casos de teste
para o código que não deverão gerar o diagnóstico. O analisador já gerencia um desses
testes, o caso em que uma variável é atribuída após ser inicializada. Adicione o seguinte
método de teste para representar esse caso:

C#

[TestMethod]
public async Task VariableIsAssigned_NoDiagnostic()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using System;

class Program
{
static void Main()
{
int i = 0;
Console.WriteLine(i++);
}
}
");
}

Esse teste é aprovado também. Em seguida, adicione métodos de teste para condições
que você ainda não gerenciou:

Declarações que já são const , porque elas já são const:

C#

[TestMethod]
public async Task VariableIsAlreadyConst_NoDiagnostic()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using System;

class Program
{
static void Main()
{
const int i = 0;
Console.WriteLine(i);
}
}
");
}
Declarações que não têm nenhum inicializador, porque não há nenhum valor a ser
usado:

C#

[TestMethod]
public async Task NoInitializer_NoDiagnostic()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using System;

class Program
{
static void Main()
{
int i;
i = 0;
Console.WriteLine(i);
}
}
");
}

Declarações em que o inicializador não é uma constante, porque elas não podem
ser constantes de tempo de compilação:

C#

[TestMethod]
public async Task InitializerIsNotConstant_NoDiagnostic()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using System;

class Program
{
static void Main()
{
int i = DateTime.Now.DayOfYear;
Console.WriteLine(i);
}
}
");
}

Isso pode ser ainda mais complicado, porque o C# permite várias declarações como
uma instrução. Considere a seguinte constante de cadeia de caracteres de caso de teste:

C#
[TestMethod]
public async Task MultipleInitializers_NoDiagnostic()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using System;

class Program
{
static void Main()
{
int i = 0, j = DateTime.Now.DayOfYear;
Console.WriteLine(i);
Console.WriteLine(j);
}
}
");
}

A variável i pode ser tornada constante, mas o mesmo não se aplica à variável j .
Portanto, essa instrução não pode ser tornada uma declaração const.

Execute os testes novamente e você verá esses novos casos de teste falharem.

Atualize seu analisador para ignorar as


declarações corretas
Você precisa de algumas melhorias no método AnalyzeNode do analisador para filtrar o
código que corresponde a essas condições. Elas são todas condições relacionadas,
portanto, alterações semelhantes corrigirão todas essas condições. Faça as alterações a
seguir em AnalyzeNode :

A análise semântica examinou uma única declaração de variável. Esse código deve
estar em um loop de foreach que examina todas as variáveis declaradas na
mesma instrução.
Cada variável declarada precisa ter um inicializador.
O inicializador de cada variável declarada precisa ser uma constante de tempo de
compilação.

No seu método AnalyzeNode , substitua a análise semântica original:

C#

// Perform data flow analysis on the local declaration.


DataFlowAnalysis dataFlowAnalysis =
context.SemanticModel.AnalyzeDataFlow(localDeclaration);
// Retrieve the local symbol for each variable in the local declaration
// and ensure that it is not written outside of the data flow analysis
region.
VariableDeclaratorSyntax variable =
localDeclaration.Declaration.Variables.Single();
ISymbol variableSymbol = context.SemanticModel.GetDeclaredSymbol(variable,
context.CancellationToken);
if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
{
return;
}

com o snippet de código a seguir:

C#

// Ensure that all variables in the local declaration have initializers that
// are assigned with constant values.
foreach (VariableDeclaratorSyntax variable in
localDeclaration.Declaration.Variables)
{
EqualsValueClauseSyntax initializer = variable.Initializer;
if (initializer == null)
{
return;
}

Optional<object> constantValue =
context.SemanticModel.GetConstantValue(initializer.Value,
context.CancellationToken);
if (!constantValue.HasValue)
{
return;
}
}

// Perform data flow analysis on the local declaration.


DataFlowAnalysis dataFlowAnalysis =
context.SemanticModel.AnalyzeDataFlow(localDeclaration);

foreach (VariableDeclaratorSyntax variable in


localDeclaration.Declaration.Variables)
{
// Retrieve the local symbol for each variable in the local declaration
// and ensure that it is not written outside of the data flow analysis
region.
ISymbol variableSymbol =
context.SemanticModel.GetDeclaredSymbol(variable,
context.CancellationToken);
if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
{
return;
}
}

O primeiro loop de foreach examina cada declaração de variável usando a análise


sintática. A primeira verificação garante que a variável tenha um inicializador. A segunda
verificação garante que o inicializador seja uma constante. O segundo loop tem a
análise semântica original. As verificações semânticas estão em um loop separado
porque ele tem um impacto maior no desempenho. Execute os testes novamente e você
deverá ver todos eles serem aprovados.

Adicionar o final polonês


Você está quase lá. Há mais algumas condições com as quais o seu analisador deve
lidar. Enquanto o usuário está escrevendo código, o Visual Studio chama os
analisadores. Muitas vezes o analisador será chamado para código que não é
compilado. O método AnalyzeNode do analisador de diagnóstico não verifica para ver se
o valor da constante é conversível para o tipo de variável. Assim, a implementação atual
converterá facilmente uma declaração incorreta, tal como int i = "abc" , em uma
constante local. Adicione um método de teste para este caso:

C#

[TestMethod]
public async Task DeclarationIsInvalid_NoDiagnostic()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using System;

class Program
{
static void Main()
{
int x = {|CS0029:""abc""|};
}
}
");
}

Além disso, os tipos de referência não são tratados corretamente. O único valor de
constante permitido para um tipo de referência é null , exceto no caso de
System.String, que permite literais de cadeia de caracteres. Em outras palavras, const
string s = "abc" é legal, mas const object s = "abc" não é. Este snippet de código
verifica essa condição:
C#

[TestMethod]
public async Task DeclarationIsNotString_NoDiagnostic()
{
await VerifyCS.VerifyAnalyzerAsync(@"
using System;

class Program
{
static void Main()
{
object s = ""abc"";
}
}
");
}

Para ser criterioso, você precisará adicionar outro teste para verificar se pode criar uma
declaração de constante para uma cadeia de caracteres. O snippet de código a seguir
define o código que gera o diagnóstico e o código após a aplicação da correção:

C#

[TestMethod]
public async Task StringCouldBeConstant_Diagnostic()
{
await VerifyCS.VerifyCodeFixAsync(@"
using System;

class Program
{
static void Main()
{
[|string s = ""abc"";|]
}
}
", @"
using System;

class Program
{
static void Main()
{
const string s = ""abc"";
}
}
");
}
Por fim, se uma variável é declarada com a palavra-chave var , a correção de código faz
a coisa errada e gera uma declaração const var , que não é compatível com a
linguagem C#. Para corrigir esse bug, a correção de código deve substituir a palavra-
chave var pelo nome do tipo inferido:

C#

[TestMethod]
public async Task VarIntDeclarationCouldBeConstant_Diagnostic()
{
await VerifyCS.VerifyCodeFixAsync(@"
using System;

class Program
{
static void Main()
{
[|var item = 4;|]
}
}
", @"
using System;

class Program
{
static void Main()
{
const int item = 4;
}
}
");
}

[TestMethod]
public async Task VarStringDeclarationCouldBeConstant_Diagnostic()
{
await VerifyCS.VerifyCodeFixAsync(@"
using System;

class Program
{
static void Main()
{
[|var item = ""abc"";|]
}
}
", @"
using System;

class Program
{
static void Main()
{
const string item = ""abc"";
}
}
");
}

Felizmente, todos os erros acima podem ser resolvidos usando as mesmas técnicas que
você acabou de aprender.

Para corrigir o primeiro bug, primeiro abra DiagnosticAnalyzer.cs e localize o loop


foreach em que cada um dos inicializadores de declaração local é verificado para
garantir que valores constantes sejam atribuídos a eles. Imediatamente antes do
primeiro loop foreach, chame context.SemanticModel.GetTypeInfo() para recuperar
informações detalhadas sobre o tipo declarado da declaração local:

C#

TypeSyntax variableTypeName = localDeclaration.Declaration.Type;


ITypeSymbol variableType =
context.SemanticModel.GetTypeInfo(variableTypeName,
context.CancellationToken).ConvertedType;

Em seguida, dentro do loop foreach , verifique cada inicializador para garantir que ele
pode ser convertido no tipo de variável. Adicione a seguinte verificação depois de
garantir que o inicializador é uma constante:

C#

// Ensure that the initializer value can be converted to the type of the
// local declaration without a user-defined conversion.
Conversion conversion =
context.SemanticModel.ClassifyConversion(initializer.Value, variableType);
if (!conversion.Exists || conversion.IsUserDefined)
{
return;
}

A próxima alteração é realizada com base na última. Antes da chave de fechamento do


primeiro loop foreach, adicione o código a seguir para verificar o tipo da declaração de
local quando a constante é uma cadeia de caracteres ou valor nulo.

C#

// Special cases:
// * If the constant value is a string, the type of the local declaration
// must be System.String.
// * If the constant value is null, the type of the local declaration must
// be a reference type.
if (constantValue.Value is string)
{
if (variableType.SpecialType != SpecialType.System_String)
{
return;
}
}
else if (variableType.IsReferenceType && constantValue.Value != null)
{
return;
}

Você precisa escrever um pouco mais de código no seu provedor de correção de código
para substituir a palavra-chave var pelo nome do tipo correto. Retorne para
MakeConstCodeFixProvider.cs. O código que você adicionará realizará as seguintes
etapas:

Verifique se a declaração é uma declaração var e se afirmativo:


Crie um novo tipo para o tipo inferido.
Certifique-se de que a declaração de tipo não é um alias. Em caso afirmativo, é
legal declarar const var .
Certifique-se de que var não é um nome de tipo neste programa. (Em caso
afirmativo, const var é legal).
Simplificar o nome completo do tipo

Isso soa como muito código. Mas não é. Substitua a linha que declara e inicializa
newLocal com o código a seguir. Ele é colocado imediatamente após a inicialização de

newModifiers :

C#

// If the type of the declaration is 'var', create a new type name


// for the inferred type.
VariableDeclarationSyntax variableDeclaration =
localDeclaration.Declaration;
TypeSyntax variableTypeName = variableDeclaration.Type;
if (variableTypeName.IsVar)
{
SemanticModel semanticModel = await
document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

// Special case: Ensure that 'var' isn't actually an alias to another


type
// (e.g. using var = System.String).
IAliasSymbol aliasInfo = semanticModel.GetAliasInfo(variableTypeName,
cancellationToken);
if (aliasInfo == null)
{
// Retrieve the type inferred for var.
ITypeSymbol type = semanticModel.GetTypeInfo(variableTypeName,
cancellationToken).ConvertedType;

// Special case: Ensure that 'var' isn't actually a type named


'var'.
if (type.Name != "var")
{
// Create a new TypeSyntax for the inferred type. Be careful
// to keep any leading and trailing trivia from the var keyword.
TypeSyntax typeName =
SyntaxFactory.ParseTypeName(type.ToDisplayString())
.WithLeadingTrivia(variableTypeName.GetLeadingTrivia())
.WithTrailingTrivia(variableTypeName.GetTrailingTrivia());

// Add an annotation to simplify the type name.


TypeSyntax simplifiedTypeName =
typeName.WithAdditionalAnnotations(Simplifier.Annotation);

// Replace the type in the variable declaration.


variableDeclaration =
variableDeclaration.WithType(simplifiedTypeName);
}
}
}
// Produce the new local declaration.
LocalDeclarationStatementSyntax newLocal =
trimmedLocal.WithModifiers(newModifiers)
.WithDeclaration(variableDeclaration);

Você precisará adicionar uma diretiva using para usar o tipo Simplifier:

C#

using Microsoft.CodeAnalysis.Simplification;

Execute seus testes, que devem todos ser aprovados. Dê parabéns a si mesmo,
executando seu analisador concluído. Pressione Ctrl + F5 para executar o projeto do
analisador em uma segunda instância do Visual Studio com a extensão de versão prévia
da Roslyn carregada.

Na segunda instância do Visual Studio, crie um novo projeto de Aplicativo de


Console de C# e adicione int x = "abc"; ao método Main. Graças à primeira
correção de bug, nenhum aviso deve ser relatado para esta declaração de variável
local (embora haja um erro do compilador, conforme esperado).
Em seguida, adicione object s = "abc"; ao método Main. Devido à segunda
correção de bug, nenhum aviso deve ser relatado.
Por fim, adicione outra variável local que usa a palavra-chave var . Você verá que
um aviso é relatado e uma sugestão é exibida abaixo e a esquerda.
Mova o cursor do editor sobre o sublinhado ondulado e pressione Ctrl + . . para
exibir a correção de código sugerida. Ao selecionar a correção de código, observe
que a palavra-chave var agora é gerenciada corretamente.

Por fim, adicione o seguinte código:

C#

int i = 2;
int j = 32;
int k = i + j;

Após essas alterações, você obtém linhas onduladas vermelhas apenas nas duas
primeiras variáveis. Adicione const para ambos i e j , e você receberá um novo aviso
em k porque ele agora pode ser const .

Parabéns! Você criou sua primeira extensão do .NET Compiler Platform que executa
análise de código com o sistema em funcionamento para detectar um problema e
fornece uma correção rápida para corrigi-lo. Ao longo do caminho, você aprendeu
muitas das APIs de código que fazem parte do SDK do .NET Compiler Platform (APIs do
Roslyn). Você pode verificar seu trabalho comparando-o à amostra concluída em
nosso repositório GitHub de exemplos.

Outros recursos
Introdução à análise de sintaxe
Introdução à análise semântica
Conceitos de programação (C#)
Artigo • 11/04/2024

Esta seção explica conceitos de programação na linguagem C#.

Nesta seção
ノ Expandir a tabela

Title Descrição

Assemblies no .NET Descreve como criar e usar um assemblies.

Programação Descreve como criar soluções assíncronas usando as palavras-chave


assíncrona com async e async e await no C#. Inclui um passo a passo.
await (C#)

Atributos (C#) Discute como fornecer informações adicionais sobre como programar
elementos como tipos, campos, métodos e propriedades por meio de
atributos.

Coleções (C#) Descreve alguns dos tipos de coleções fornecidos pelo .NET.
Demonstra como usar coleções simples e coleções de pares
chave/valor.

Covariância e Mostra como habilitar a conversão implícita de parâmetros de tipo


contravariância (C#) genérico em interfaces e delegados.

Árvores de expressão Explica como você pode usar árvores de expressão para habilitar a
(C#) modificação dinâmica de código executável.

Iteradores (C#) Descreve os iteradores, que são usados para percorrer coleções e
retornar elementos um por vez.

LINQ (Consulta Discute os recursos avançados de consulta na sintaxe de linguagem do


Integrada à Linguagem) C# e o modelo para consultar bancos de dados relacionais,
(C#) documentos XML, conjuntos de dados e coleções na memória.

Reflexão (C#) Explica como usar a reflexão para criar dinamicamente uma instância
de um tipo, associar o tipo a um objeto existente ou obter o tipo de
um objeto existente e invocar seus métodos ou acessar suas
propriedades e campos.

Serialização (C#) Descreve os principais conceitos em binário, XML e serialização SOAP.

Seções relacionadas
Dicas de desempenho

Discute várias regras básicas que podem ajudá-lo a aumentar o desempenho do


seu aplicativo.

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

Fornecer comentários sobre o produto


Covariância e contravariância (C#)
Artigo • 11/04/2024

No C#, a covariância e a contravariância habilitam a conversão de referência implícita


para tipos de matriz, tipos de delegados e argumentos de tipo genérico. A covariância
preserva a compatibilidade de atribuição, e a contravariância reverte.

O código a seguir demonstra a diferença entre a compatibilidade da atribuição, a


covariância e a contravariância.

C#

// Assignment compatibility.
string str = "test";
// An object of a more derived type is assigned to an object of a less
derived type.
object obj = str;

// Covariance.
IEnumerable<string> strings = new List<string>();
// An object that is instantiated with a more derived type argument
// is assigned to an object instantiated with a less derived type argument.
// Assignment compatibility is preserved.
IEnumerable<object> objects = strings;

// Contravariance.
// Assume that the following method is in the class:
static void SetObject(object o) { }
Action<object> actObject = SetObject;
// An object that is instantiated with a less derived type argument
// is assigned to an object instantiated with a more derived type argument.
// Assignment compatibility is reversed.
Action<string> actString = actObject;

A covariância para matrizes permite a conversão implícita de uma matriz de um tipo


mais derivado para uma matriz de um tipo menos derivado. Mas essa operação não é
fortemente tipada, conforme mostrado no exemplo de código a seguir.

C#

object[] array = new String[10];


// The following statement produces a run-time exception.
// array[0] = 10;

O suporte de covariância e contravariância aos grupos de método permite a


correspondência de assinaturas de método com tipos de delegados. Isso permite
atribuir a delegados não apenas os métodos que têm correspondência de assinaturas,
mas também métodos que retornam tipos mais derivados (covariância) ou que aceitam
parâmetros que têm tipos menos derivados (contravariância) do que o especificado pelo
tipo delegado. Para obter mais informações, consulte Variância em delegados (C#) e
Usando variância em delegados (C#).

O exemplo de código a seguir mostra o suporte da covariância e da contravariância


para grupos de método.

C#

static object GetObject() { return null; }


static void SetObject(object obj) { }

static string GetString() { return ""; }


static void SetString(string str) { }

static void Test()


{
// Covariance. A delegate specifies a return type as object,
// but you can assign a method that returns a string.
Func<object> del = GetString;

// Contravariance. A delegate specifies a parameter type as string,


// but you can assign a method that takes an object.
Action<string> del2 = SetObject;
}

No .NET Framework 4 e versões mais recentes, o C# oferece suporte à covariância e á


contravariância em interfaces e delegados genéricos, e permite a conversão implícita de
parâmetros de tipo genérico. Para obter mais informações, consulte Variação em
interfaces genéricas (C#) e Variação em delegados (C#).

O exemplo de código a seguir mostra a conversão de referência implícita para interfaces


genéricas.

C#

IEnumerable<String> strings = new List<String>();


IEnumerable<Object> objects = strings;

Uma interface ou delegado genérico será chamado variante se seus parâmetros


genéricos forem declarados covariantes ou contravariantes. O C# permite que você crie
suas próprias interfaces variantes e delegados. Para obter mais informações, consulte
Criando interfaces genéricas variantes (C#) e Variação em delegados (C#).
Tópicos Relacionados
ノ Expandir a tabela

Título Descrição

Variância em interfaces Discute a covariância e a contravariância em interfaces genéricas e


genéricas (C#) fornece uma lista de interfaces genéricas variáveis no .NET
Framework.

Criando interfaces genéricas Mostra como criar interfaces variantes personalizadas.


variáveis (C#)

Usando variação em Mostra como o suporte de covariância e contravariância nas


interfaces para Coleções interfaces IEnumerable<T> e IComparable<T> pode ajudar na
Genéricas (C#) reutilização do código.

Variação em delegados (C#) Discute a covariância e a contravariância em interfaces genéricas e


não genéricas, e fornece uma lista de delegados genéricos
variáveis no .NET.

Usando variação em Mostra como usar o suporte de covariância e contravariância em


delegados (C#) delegados não genéricos para corresponder às assinaturas de
método com tipos delegados.

Usando variação para Mostra como o suporte de covariância e contravariância nos


delegados genéricos Func e delegados Func e Action pode ajudar na reutilização do código.
Action (C#)

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

Fornecer comentários sobre o produto


Variância em interfaces genéricas (C#)
Artigo • 10/05/2023

O .NET Framework 4 introduziu o suporte à variação para diversas interfaces genéricas


existentes. O suporte à variação possibilita a conversão implícita de classes que
implementam essas interfaces.

Do .NET Framework 4 em diante, as seguintes interfaces são variantes:

IEnumerable<T> (T é covariante)

IEnumerator<T> (T é covariante)

IQueryable<T> (T é covariante)

IGrouping<TKey,TElement> ( TKey e TElement são covariantes)

IComparer<T> (T é contravariante)

IEqualityComparer<T> (T é contravariante)

IComparable<T> (T é contravariante)

A partir do .NET Framework 4.5, as seguintes interfaces são variantes:

IReadOnlyList<T> (T é covariante)

IReadOnlyCollection<T> (T é covariante)

A covariância permite que um método tenha um tipo de retorno mais derivados que
aquele definidos pelo parâmetro de tipo genérico da interface. Para ilustrar o recurso de
covariância, considere estas interfaces genéricas: IEnumerable<Object> e
IEnumerable<String> . A interface IEnumerable<String> não herda a interface
IEnumerable<Object> . No entanto, o tipo String herda o tipo Object e, em alguns

casos, talvez você queira atribuir objetos dessas interfaces uns aos outros. Isso é
mostrado no exemplo de código a seguir.

C#

IEnumerable<String> strings = new List<String>();


IEnumerable<Object> objects = strings;

Em versões anteriores do .NET Framework, esse código gera um erro de compilação no


C# e, se Option Strict estiver ativado, no Visual Basic. Mas agora você pode usar
strings em vez de objects , conforme mostrado no exemplo anterior, porque a

interface IEnumerable<T> é covariante.

A contravariância permite que um método tenha tipos de argumentos menos derivados


que aquele especificado pelo parâmetro genérico da interface. Para ilustrar a
contravariância, suponha que você tenha criado uma classe BaseComparer para
comparar instâncias da classe BaseClass . A classe BaseComparer implementa a interface
IEqualityComparer<BaseClass> . Como a interface IEqualityComparer<T> agora é
contravariante, você pode usar BaseComparer para comparar instâncias de classes que
herdam a classe BaseClass . Isso é mostrado no exemplo de código a seguir.

C#

// Simple hierarchy of classes.


class BaseClass { }
class DerivedClass : BaseClass { }

// Comparer class.
class BaseComparer : IEqualityComparer<BaseClass>
{
public int GetHashCode(BaseClass baseInstance)
{
return baseInstance.GetHashCode();
}
public bool Equals(BaseClass x, BaseClass y)
{
return x == y;
}
}
class Program
{
static void Test()
{
IEqualityComparer<BaseClass> baseComparer = new BaseComparer();

// Implicit conversion of IEqualityComparer<BaseClass> to


// IEqualityComparer<DerivedClass>.
IEqualityComparer<DerivedClass> childComparer = baseComparer;
}
}

Para ver mais exemplos, consulte Usando variação em interfaces para coleções
genéricas (C#).

A variação em interfaces genéricas tem suporte somente para tipos de referência. Tipos
de valor não dão suporte à variação. Por exemplo, IEnumerable<int> não pode ser
convertido implicitamente em IEnumerable<object> , porque inteiros são representados
por um tipo de valor.
C#

IEnumerable<int> integers = new List<int>();


// The following statement generates a compiler error,
// because int is a value type.
// IEnumerable<Object> objects = integers;

Também é importante lembrar que as classes que implementam interfaces variantes


ainda são invariantes. Por exemplo, embora List<T> implemente a interface covariante
IEnumerable<T>, você não pode converter implicitamente List<String> para
List<Object> . Isso é ilustrado no exemplo de código a seguir.

C#

// The following line generates a compiler error


// because classes are invariant.
// List<Object> list = new List<String>();

// You can use the interface object instead.


IEnumerable<Object> listObjects = new List<String>();

Confira também
Usando variação em interfaces para Coleções Genéricas (C#)
Criando interfaces genéricas variáveis (C#)
Interfaces genéricas
Variação em delegados (C#)
Criando interfaces genéricas variantes
(C#)
Artigo • 10/05/2023

Você pode declarar parâmetros de tipo genérico em interfaces como covariantes ou


contravariantes. A Covariância permite que os métodos de interface tenham tipos de
retorno mais derivados que aqueles definidos pelos parâmetros de tipo genérico. A
Contravariância permite que os métodos de interface tenham tipos de argumentos que
são menos derivados que aqueles especificados pelos parâmetros genéricos. Uma
interface genérica que tenha parâmetros de tipo genérico covariantes ou
contravariantes é chamada de variante.

7 Observação

O .NET Framework 4 introduziu o suporte à variação para diversas interfaces


genéricas existentes. Para obter a lista das interfaces variantes no .NET, consulte
Variância em interfaces genéricas (C#).

Declarando interfaces genéricas variantes


Você pode declarar interfaces genéricas variantes usando as palavras-chave in e out
para parâmetros de tipo genérico.

) Importante

Os parâmetros ref , in e out no C# não podem ser variantes. Os tipos de valor


também não dão suporte à variância.

Você pode declarar um parâmetro de tipo genérico como covariante usando a palavra-
chave out . O tipo de covariante deve satisfazer as condições a seguir:

O tipo é usado apenas como um tipo de retorno dos métodos de interface e não é
usado como um tipo de argumentos de método. Isso é ilustrado no exemplo a
seguir, no qual o tipo R é declarado covariante.

C#

interface ICovariant<out R>


{
R GetSomething();
// The following statement generates a compiler error.
// void SetSomething(R sampleArg);

Há uma exceção a essa regra. Se você tiver um delegado genérico contravariante


como um parâmetro de método, você poderá usar o tipo como um parâmetro de
tipo genérico para o delegado. Isso é ilustrado pelo tipo R no exemplo a seguir.
Para obter mais informações, consulte Variância em delegados (C#) e Usando
variância para delegados genéricos Func e Action (C#).

C#

interface ICovariant<out R>


{
void DoSomething(Action<R> callback);
}

O tipo não é usado como uma restrição genérica para os métodos de interface. O
código a seguir ilustra isso.

C#

interface ICovariant<out R>


{
// The following statement generates a compiler error
// because you can use only contravariant or invariant types
// in generic constraints.
// void DoSomething<T>() where T : R;
}

Você pode declarar um parâmetro de tipo genérico como contravariante usando a


palavra-chave in . O tipo contravariante pode ser usado apenas como um tipo de
argumentos de método e não como um tipo de retorno dos métodos de interface. O
tipo contravariante também pode ser usado para restrições genéricas. O código a seguir
mostra como declarar uma interface contravariante e usar uma restrição genérica para
um de seus métodos.

C#

interface IContravariant<in A>


{
void SetSomething(A sampleArg);
void DoSomething<T>() where T : A;
// The following statement generates a compiler error.
// A GetSomething();
}

Também é possível oferecer suporte à covariância e contravariância na mesma interface,


mas para parâmetros de tipo diferentes, conforme mostrado no exemplo de código a
seguir.

C#

interface IVariant<out R, in A>


{
R GetSomething();
void SetSomething(A sampleArg);
R GetSetSomethings(A sampleArg);
}

Implementando interfaces genéricas variantes


Você pode implementar interfaces genéricas variantes em classes, através da mesma
sintaxe que é usada para interfaces invariantes. O exemplo de código a seguir mostra
como implementar uma interface covariante em uma classe genérica.

C#

interface ICovariant<out R>


{
R GetSomething();
}
class SampleImplementation<R> : ICovariant<R>
{
public R GetSomething()
{
// Some code.
return default(R);
}
}

As classes que implementam interfaces variantes são invariantes. Por exemplo, considere
o código a seguir.

C#

// The interface is covariant.


ICovariant<Button> ibutton = new SampleImplementation<Button>();
ICovariant<Object> iobj = ibutton;

// The class is invariant.


SampleImplementation<Button> button = new SampleImplementation<Button>();
// The following statement generates a compiler error
// because classes are invariant.
// SampleImplementation<Object> obj = button;

Estendendo interfaces genéricas variantes


Quando você estende uma interface genérica variante, é necessário usar as palavras-
chave in e out para especificar explicitamente se a interface derivada dará suporte à
variância. O compilador não deduz a variância da interface que está sendo estendida.
Por exemplo, considere as seguintes interfaces.

C#

interface ICovariant<out T> { }


interface IInvariant<T> : ICovariant<T> { }
interface IExtCovariant<out T> : ICovariant<T> { }

Na interface IInvariant<T> , o parâmetro de tipo genérico T é invariante, enquanto que


no IExtCovariant<out T> o parâmetro de tipo é covariante, embora as duas interfaces
estendam a mesma interface. A mesma regra é aplicada aos parâmetros de tipo
genérico contravariantes.

Você pode criar uma interface que estende tanto a interface em que o parâmetro de
tipo genérico T é covariante, quanto a interface em que ele é contravariante, caso na
interface de extensão o parâmetro de tipo genérico T seja invariante. Isso é ilustrado no
exemplo de código a seguir.

C#

interface ICovariant<out T> { }


interface IContravariant<in T> { }
interface IInvariant<T> : ICovariant<T>, IContravariant<T> { }

No entanto, se um parâmetro de tipo genérico T for declarado covariante em uma


interface, você não poderá declará-lo contravariante na interface de extensão ou vice-
versa. Isso é ilustrado no exemplo de código a seguir.

C#

interface ICovariant<out T> { }


// The following statement generates a compiler error.
// interface ICoContraVariant<in T> : ICovariant<T> { }
Evitando ambiguidade
Ao implementar interfaces genéricas variantes, a variância, às vezes, pode levar à
ambiguidade. Essa ambiguidade deve ser evitada.

Por exemplo, se você implementar explicitamente a mesma interface genérica variante


com parâmetros de tipo genérico diferentes em uma classe, isso poderá criar
ambiguidade. O compilador não produzirá um erro nesse caso, mas não está
especificada qual implementação de interface será escolhida em runtime. Essa
ambiguidade pode causar bugs sutis no seu código. Considere o exemplo de código a
seguir.

C#

// Simple class hierarchy.


class Animal { }
class Cat : Animal { }
class Dog : Animal { }

// This class introduces ambiguity


// because IEnumerable<out T> is covariant.
class Pets : IEnumerable<Cat>, IEnumerable<Dog>
{
IEnumerator<Cat> IEnumerable<Cat>.GetEnumerator()
{
Console.WriteLine("Cat");
// Some code.
return null;
}

IEnumerator IEnumerable.GetEnumerator()
{
// Some code.
return null;
}

IEnumerator<Dog> IEnumerable<Dog>.GetEnumerator()
{
Console.WriteLine("Dog");
// Some code.
return null;
}
}
class Program
{
public static void Test()
{
IEnumerable<Animal> pets = new Pets();
pets.GetEnumerator();
}
}
Neste exemplo, não está especificado como o método pets.GetEnumerator escolherá
entre Cat e Dog . Isso poderá causar problemas em seu código.

Confira também
Variância em interfaces genéricas (C#)
Usando variação para delegados genéricos Func e Action (C#)
Usando variação em interfaces para
Coleções Genéricas (C#)
Artigo • 10/05/2023

Uma interface de covariante permite que seus métodos retornem mais tipos derivados
daquelas especificadas na interface. Uma interface de contravariante permite que seus
métodos aceitem parâmetros de tipos menos derivados do que os especificados na
interface.

No .NET Framework 4, várias interfaces existentes se tornaram covariantes e


contravariantes. Eles incluem IEnumerable<T> e IComparable<T>. Isso permite que
você reutilize métodos que operam com coleções genéricas de tipos base para coleções
de tipos derivados.

Para obter uma lista de interfaces variantes no .NET, confira Variância em interfaces
genéricas (C#).

Convertendo coleções genéricas


O exemplo a seguir ilustra os benefícios do suporte à covariância na interface
IEnumerable<T>. O método PrintFullName aceita uma coleção do tipo
IEnumerable<Person> como um parâmetro. No entanto, você pode reutilizá-lo para uma

coleção do tipo IEnumerable<Employee> porque Employee herda Person .

C#

// Simple hierarchy of classes.


public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

public class Employee : Person { }

class Program
{
// The method has a parameter of the IEnumerable<Person> type.
public static void PrintFullName(IEnumerable<Person> persons)
{
foreach (Person person in persons)
{
Console.WriteLine("Name: {0} {1}",
person.FirstName, person.LastName);
}
}

public static void Test()


{
IEnumerable<Employee> employees = new List<Employee>();

// You can pass IEnumerable<Employee>,


// although the method expects IEnumerable<Person>.

PrintFullName(employees);

}
}

Comparando coleções genéricas


O exemplo a seguir ilustra os benefícios do suporte à contravariância na interface
IEqualityComparer<T>. A classe PersonComparer implementa a interface
IEqualityComparer<Person> . No entanto, você pode reutilizar essa classe para comparar

uma sequência de objetos do tipo Employee porque Employee herda Person .

C#

// Simple hierarchy of classes.


public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

public class Employee : Person { }

// The custom comparer for the Person type


// with standard implementations of Equals()
// and GetHashCode() methods.
class PersonComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
if (Object.ReferenceEquals(x, y)) return true;
if (Object.ReferenceEquals(x, null) ||
Object.ReferenceEquals(y, null))
return false;
return x.FirstName == y.FirstName && x.LastName == y.LastName;
}
public int GetHashCode(Person person)
{
if (Object.ReferenceEquals(person, null)) return 0;
int hashFirstName = person.FirstName == null
? 0 : person.FirstName.GetHashCode();
int hashLastName = person.LastName.GetHashCode();
return hashFirstName ^ hashLastName;
}
}

class Program
{

public static void Test()


{
List<Employee> employees = new List<Employee> {
new Employee() {FirstName = "Michael", LastName =
"Alexander"},
new Employee() {FirstName = "Jeff", LastName = "Price"}
};

// You can pass PersonComparer,


// which implements IEqualityComparer<Person>,
// although the method expects IEqualityComparer<Employee>.

IEnumerable<Employee> noduplicates =
employees.Distinct<Employee>(new PersonComparer());

foreach (var employee in noduplicates)


Console.WriteLine(employee.FirstName + " " + employee.LastName);
}
}

Confira também
Variância em interfaces genéricas (C#)
Variação em delegados (C#)
Artigo • 09/05/2023

O .NET Framework 3.5 introduziu o suporte a variação para assinaturas de método


correspondentes com tipos de delegados em todos os delegados do C#. Isso significa
que você pode atribuir a delegados não apenas os métodos que têm assinaturas
correspondentes, mas também métodos que retornam tipos mais derivados
(covariância) ou que aceitam parâmetros que têm tipos menos derivados
(contravariância) do que o especificado pelo tipo de delegado. Isso inclui delegados
genéricos e não genéricos.

Por exemplo, considere o código a seguir, que tem duas classes e dois delegados:
genérico e não genérico.

C#

public class First { }


public class Second : First { }
public delegate First SampleDelegate(Second a);
public delegate R SampleGenericDelegate<A, R>(A a);

Quando cria delegados dos tipos SampleDelegate ou SampleGenericDelegate<A, R> , você


pode atribuir qualquer um dos seguintes métodos a esses delegados.

C#

// Matching signature.
public static First ASecondRFirst(Second second)
{ return new First(); }

// The return type is more derived.


public static Second ASecondRSecond(Second second)
{ return new Second(); }

// The argument type is less derived.


public static First AFirstRFirst(First first)
{ return new First(); }

// The return type is more derived


// and the argument type is less derived.
public static Second AFirstRSecond(First first)
{ return new Second(); }

O exemplo de código a seguir ilustra a conversão implícita entre a assinatura do


método e o tipo de delegado.
C#

// Assigning a method with a matching signature


// to a non-generic delegate. No conversion is necessary.
SampleDelegate dNonGeneric = ASecondRFirst;
// Assigning a method with a more derived return type
// and less derived argument type to a non-generic delegate.
// The implicit conversion is used.
SampleDelegate dNonGenericConversion = AFirstRSecond;

// Assigning a method with a matching signature to a generic delegate.


// No conversion is necessary.
SampleGenericDelegate<Second, First> dGeneric = ASecondRFirst;
// Assigning a method with a more derived return type
// and less derived argument type to a generic delegate.
// The implicit conversion is used.
SampleGenericDelegate<Second, First> dGenericConversion = AFirstRSecond;

Para obter mais informações, consulte Usando variação em delegados (C#) e Usando
variação para os delegados genéricos Func e Action (C#).

Variação em parâmetros de tipo genérico


No .NET Framework 4 ou posterior, agora você pode habilitar a conversão implícita
entre delegados, de modo que delegados genéricos que têm tipos diferentes
especificados por parâmetros de tipo genérico podem ser atribuídos uns aos outros, se
os tipos forem herdados uns dos outros como é obrigatório de acordo com a variação.

Para habilitar a conversão implícita, você precisa declarar explicitamente os parâmetros


genéricos em um delegado como covariante ou contravariante usando a palavra-chave
in ou out .

O exemplo de código a seguir mostra como você pode criar um delegado que tem um
parâmetro de tipo genérico covariante.

C#

// Type T is declared covariant by using the out keyword.


public delegate T SampleGenericDelegate <out T>();

public static void Test()


{
SampleGenericDelegate <String> dString = () => " ";

// You can assign delegates to each other,


// because the type T is declared covariant.
SampleGenericDelegate <Object> dObject = dString;
}
Se você usar somente o suporte à para fazer a correspondência de assinaturas de
método com tipos de delegados e não usar as palavras-chave in e out , você poderá
perceber que, às vezes, é possível instanciar delegados com métodos ou expressões
lambda idênticas, mas não é possível atribuir um delegado a outro.

No exemplo de código a seguir, SampleGenericDelegate<String> não pode ser


convertido explicitamente em SampleGenericDelegate<Object> , embora String herde
Object . Você pode corrigir esse problema marcando o parâmetro genérico T com a

palavra-chave out .

C#

public delegate T SampleGenericDelegate<T>();

public static void Test()


{
SampleGenericDelegate<String> dString = () => " ";

// You can assign the dObject delegate


// to the same lambda expression as dString delegate
// because of the variance support for
// matching method signatures with delegate types.
SampleGenericDelegate<Object> dObject = () => " ";

// The following statement generates a compiler error


// because the generic type T is not marked as covariant.
// SampleGenericDelegate <Object> dObject = dString;

Delegados genéricos que têm parâmetros de tipo


variante no .NET
O .NET Framework 4 introduziu o suporte à variação para parâmetros de tipo genérico
em diversos delegados genéricos existentes:

Action delega do namespace System, por exemplo, Action<T> e Action<T1,T2>

Func delega do namespace System, por exemplo, Func<TResult> e


Func<T,TResult>

O delegado Predicate<T>

O delegado Comparison<T>
O delegado Converter<TInput,TOutput>

Para obter mais informações e exemplos, consulte Usando variação para delegados
genéricos Func e Action (C#).

Declarando parâmetros de tipo variante em delegados


genéricos
Se um delegado genérico tiver parâmetros de tipo genérico covariantes ou
contravariantes, ele poderá ser considerado um delegado genérico variante.

Você pode declarar um parâmetro de tipo genérico covariante em um delegado


genérico usando a palavra-chave out . O tipo covariante pode ser usado apenas como
um tipo de retorno de método e não como um tipo de argumentos de método. O
exemplo de código a seguir mostra como declarar um delegado genérico covariante.

C#

public delegate R DCovariant<out R>();

Você pode declarar um parâmetro de tipo genérico contravariante em um delegado


genérico usando a palavra-chave in . O tipo contravariante pode ser usado apenas
como um tipo de argumentos de método e não como um tipo de retorno de método. O
exemplo de código a seguir mostra como declarar um delegado genérico
contravariante.

C#

public delegate void DContravariant<in A>(A a);

) Importante

Os parâmetros ref , in e out em C# não podem ser marcados como variantes.

Também é possível dar suporte à variância e à covariância no mesmo delegado, mas


para parâmetros de tipo diferente. Isso é mostrado no exemplo a seguir.

C#

public delegate R DVariant<in A, out R>(A a);


Instanciando e invocando delegados genéricos variantes
Você pode instanciar e invocar delegados variantes da mesma forma como instancia e
invoca delegados invariantes. No exemplo a seguir, um delegado é instanciado por uma
expressão lambda.

C#

DVariant<String, String> dvariant = (String str) => str + " ";


dvariant("test");

Combinando delegados genéricos variantes


Não combine delegados variantes. O método Combine não dá suporte à conversão de
delegados variantes e espera que os delegados sejam exatamente do mesmo tipo. Isso
pode levar a uma exceção de tempo de execução quando você combina delegados
usando o método Combine ou o operador + , conforme mostrado no exemplo de
código a seguir.

C#

Action<object> actObj = x => Console.WriteLine("object: {0}", x);


Action<string> actStr = x => Console.WriteLine("string: {0}", x);
// All of the following statements throw exceptions at run time.
// Action<string> actCombine = actStr + actObj;
// actStr += actObj;
// Delegate.Combine(actStr, actObj);

Variação em parâmetros de tipo genérico para


tipos de referência e valor
A variação para parâmetros de tipo genérico tem suporte apenas para tipos de
referência. Por exemplo, DVariant<int> não pode ser convertido implicitamente em
DVariant<Object> ou DVariant<long> , pois inteiro é um tipo de valor.

O exemplo a seguir demonstra que a variação em parâmetros de tipo genérico não tem
suporte para tipos de valor.

C#

// The type T is covariant.


public delegate T DVariant<out T>();
// The type T is invariant.
public delegate T DInvariant<T>();

public static void Test()


{
int i = 0;
DInvariant<int> dInt = () => i;
DVariant<int> dVariantInt = () => i;

// All of the following statements generate a compiler error


// because type variance in generic parameters is not supported
// for value types, even if generic type parameters are declared
variant.
// DInvariant<Object> dObject = dInt;
// DInvariant<long> dLong = dInt;
// DVariant<Object> dVariantObject = dVariantInt;
// DVariant<long> dVariantLong = dVariantInt;
}

Confira também
Genéricos
Usando variação para delegados genéricos Func e Action (C#)
Como combinar delegados (delegados multicast)
Usando variação em delegações (C#)
Artigo • 07/04/2023

Quando você atribui um método a um delegado, a covariância e a contravariância


fornece flexibilidade para corresponder um tipo de delegado a uma assinatura de
método. A covariância permite que um método tenha o tipo de retorno mais derivado
do que o definido no delegado. A contravariância permite que um método que tem
tipos de parâmetro menos derivados do que no tipo delegado.

Exemplo 1: covariância

Descrição
Este exemplo demonstra como delegados podem ser usados com métodos que têm
tipos de retorno que são derivados do tipo de retorno na assinatura do delegado. O
tipo de dados retornado por DogsHandler é do tipo Dogs , que deriva do tipo Mammals
definido no delegado.

Código
C#

class Mammals {}
class Dogs : Mammals {}

class Program
{
// Define the delegate.
public delegate Mammals HandlerMethod();

public static Mammals MammalsHandler()


{
return null;
}

public static Dogs DogsHandler()


{
return null;
}

static void Test()


{
HandlerMethod handlerMammals = MammalsHandler;

// Covariance enables this assignment.


HandlerMethod handlerDogs = DogsHandler;
}
}

Exemplo 2: contravariância

Descrição
Este exemplo demonstra como representantes podem ser usados com métodos que
têm parâmetros cujos tipos são tipos base do tipo de parâmetro de assinatura do
representante. Com a contravariância, você pode usar um manipulador de eventos em
vez de manipuladores separados. O seguinte exemplo usa dois representantes:

Um representante KeyEventHandler que define a assinatura do evento


Button.KeyDown. Sua assinatura é:

C#

public delegate void KeyEventHandler(object sender, KeyEventArgs e)

Um representante MouseEventHandler que define a assinatura do evento


Button.MouseClick. Sua assinatura é:

C#

public delegate void MouseEventHandler(object sender, MouseEventArgs e)

O exemplo define um manipulador de eventos com um parâmetro EventArgs e o usa


para manipular os eventos Button.KeyDown e Button.MouseClick . Ele pode fazer isso
porque EventArgs é um tipo base de KeyEventArgs e MouseEventArgs.

Código
C#

// Event handler that accepts a parameter of the EventArgs type.


private void MultiHandler(object sender, System.EventArgs e)
{
label1.Text = System.DateTime.Now.ToString();
}

public Form1()
{
InitializeComponent();

// You can use a method that has an EventArgs parameter,


// although the event expects the KeyEventArgs parameter.
this.button1.KeyDown += this.MultiHandler;

// You can use the same method


// for an event that expects the MouseEventArgs parameter.
this.button1.MouseClick += this.MultiHandler;

Confira também
Variação em delegados (C#)
Usando variação para delegados genéricos Func e Action (C#)
Usando variância para delegados
genéricos Func e Action (C#)
Artigo • 07/04/2023

Esses exemplos demonstram como usar covariância e contravariância nos delegados


genéricos Func e Action para permitir a reutilização dos métodos e fornecer mais
flexibilidade em seu código.

Para obter mais informações sobre covariância e contravariância, consulte Variação em


delegações (C#).

Usando delegados com parâmetros de tipo


covariantes
O exemplo a seguir ilustra os benefícios do suporte à covariância nos delegados
genéricos Func . O método FindByTitle assume um parâmetro do tipo String e retorna
um objeto do tipo Employee . No entanto, você pode atribuir esse método ao delegado
Func<String, Person> porque Employee herda Person .

C#

// Simple hierarchy of classes.


public class Person { }
public class Employee : Person { }
class Program
{
static Employee FindByTitle(String title)
{
// This is a stub for a method that returns
// an employee that has the specified title.
return new Employee();
}

static void Test()


{
// Create an instance of the delegate without using variance.
Func<String, Employee> findEmployee = FindByTitle;

// The delegate expects a method to return Person,


// but you can assign it a method that returns Employee.
Func<String, Person> findPerson = FindByTitle;

// You can also assign a delegate


// that returns a more derived type
// to a delegate that returns a less derived type.
findPerson = findEmployee;

}
}

Usando delegados com parâmetros de tipo


contravariantes
O exemplo a seguir ilustra os benefícios do suporte à contravariância nos delegados
genéricos Action . O método AddToContacts assume um parâmetro do tipo Person . No
entanto, você pode atribuir esse método ao delegado Action<Employee> porque
Employee herda Person .

C#

public class Person { }


public class Employee : Person { }
class Program
{
static void AddToContacts(Person person)
{
// This method adds a Person object
// to a contact list.
}

static void Test()


{
// Create an instance of the delegate without using variance.
Action<Person> addPersonToContacts = AddToContacts;

// The Action delegate expects


// a method that has an Employee parameter,
// but you can assign it a method that has a Person parameter
// because Employee derives from Person.
Action<Employee> addEmployeeToContacts = AddToContacts;

// You can also assign a delegate


// that accepts a less derived parameter to a delegate
// that accepts a more derived parameter.
addEmployeeToContacts = addPersonToContacts;
}
}

Confira também
Covariância e contravariância (C#)
Genéricos
Iteradores (C#)
Artigo • 09/05/2023

Um iterador pode ser usado para percorrer coleções, como listas e matrizes.

Um método iterador ou um acessador get realiza uma iteração personalizada em uma


coleção. Um método iterador usa a instrução yield return para retornar um elemento de
cada vez. Quando uma instrução yield return for atingida, o local atual no código será
lembrado. A execução será reiniciada desse local na próxima vez que a função iteradora
for chamada.

Um iterador é consumido no código cliente, usando uma instrução foreach ou usando


uma consulta LINQ.

No exemplo a seguir, a primeira iteração do loop foreach faz que a execução continue
no método iterador SomeNumbers até que a primeira instrução yield return seja
alcançada. Essa iteração retorna um valor de 3 e o local atual no método iterador é
mantido. Na próxima iteração do loop, a execução no método iterador continuará de
onde parou, parando novamente quando alcançar uma instrução yield return . Essa
iteração retorna um valor de 5 e o local atual no método iterador é mantido novamente.
O loop terminará quando o final do método iterador for alcançado.

C#

static void Main()


{
foreach (int number in SomeNumbers())
{
Console.Write(number.ToString() + " ");
}
// Output: 3 5 8
Console.ReadKey();
}

public static System.Collections.IEnumerable SomeNumbers()


{
yield return 3;
yield return 5;
yield return 8;
}

O tipo de retorno de um método iterador ou acessador get pode ser IEnumerable,


IEnumerable<T>, IEnumerator ou IEnumerator<T>.

Você pode usar uma instrução yield break para terminar a iteração.
7 Observação

Todos os exemplos neste tópico, exceto o exemplo Iterador Simples, incluem


diretivas using para os namespaces System.Collections e
System.Collections.Generic .

Iterador simples
O exemplo a seguir contém uma única instrução yield return que está dentro de um
loop for. Em Main , cada iteração do corpo da instrução foreach cria uma chamada à
função iteradora, que avança para a próxima instrução yield return .

C#

static void Main()


{
foreach (int number in EvenSequence(5, 18))
{
Console.Write(number.ToString() + " ");
}
// Output: 6 8 10 12 14 16 18
Console.ReadKey();
}

public static System.Collections.Generic.IEnumerable<int>


EvenSequence(int firstNumber, int lastNumber)
{
// Yield even numbers in the range.
for (int number = firstNumber; number <= lastNumber; number++)
{
if (number % 2 == 0)
{
yield return number;
}
}
}

Criando uma classe de coleção


No exemplo a seguir, a classe DaysOfTheWeek implementa a interface IEnumerable, que
requer um método GetEnumerator. O compilador chama implicitamente o método
GetEnumerator , que retorna um IEnumerator.
O método GetEnumerator retorna cada cadeia de caracteres, uma de cada vez, usando a
instrução yield return .

C#

static void Main()


{
DaysOfTheWeek days = new DaysOfTheWeek();

foreach (string day in days)


{
Console.Write(day + " ");
}
// Output: Sun Mon Tue Wed Thu Fri Sat
Console.ReadKey();
}

public class DaysOfTheWeek : IEnumerable


{
private string[] days = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri",
"Sat" };

public IEnumerator GetEnumerator()


{
for (int index = 0; index < days.Length; index++)
{
// Yield each day of the week.
yield return days[index];
}
}
}

O exemplo a seguir cria uma classe Zoo que contém uma coleção de animais.

A instrução foreach , que faz referência à instância de classe ( theZoo ), chama


implicitamente o método GetEnumerator . As instruções foreach , que fazem referência às
propriedades Birds e Mammals , usam o método iterador nomeado AnimalsForType .

C#

static void Main()


{
Zoo theZoo = new Zoo();

theZoo.AddMammal("Whale");
theZoo.AddMammal("Rhinoceros");
theZoo.AddBird("Penguin");
theZoo.AddBird("Warbler");

foreach (string name in theZoo)


{
Console.Write(name + " ");
}
Console.WriteLine();
// Output: Whale Rhinoceros Penguin Warbler

foreach (string name in theZoo.Birds)


{
Console.Write(name + " ");
}
Console.WriteLine();
// Output: Penguin Warbler

foreach (string name in theZoo.Mammals)


{
Console.Write(name + " ");
}
Console.WriteLine();
// Output: Whale Rhinoceros

Console.ReadKey();
}

public class Zoo : IEnumerable


{
// Private members.
private List<Animal> animals = new List<Animal>();

// Public methods.
public void AddMammal(string name)
{
animals.Add(new Animal { Name = name, Type = Animal.TypeEnum.Mammal
});
}

public void AddBird(string name)


{
animals.Add(new Animal { Name = name, Type = Animal.TypeEnum.Bird
});
}

public IEnumerator GetEnumerator()


{
foreach (Animal theAnimal in animals)
{
yield return theAnimal.Name;
}
}

// Public members.
public IEnumerable Mammals
{
get { return AnimalsForType(Animal.TypeEnum.Mammal); }
}

public IEnumerable Birds


{
get { return AnimalsForType(Animal.TypeEnum.Bird); }
}

// Private methods.
private IEnumerable AnimalsForType(Animal.TypeEnum type)
{
foreach (Animal theAnimal in animals)
{
if (theAnimal.Type == type)
{
yield return theAnimal.Name;
}
}
}

// Private class.
private class Animal
{
public enum TypeEnum { Bird, Mammal }

public string Name { get; set; }


public TypeEnum Type { get; set; }
}
}

Usando iteradores com uma lista genérica


No exemplo a seguir, a classe Stack<T> genérica implementa a interface genérica
IEnumerable<T>. O método Push atribui valores a uma matriz do tipo T . O método
GetEnumerator retorna os valores da matriz usando a instrução yield return .

Além do método GetEnumerator genérico, o método GetEnumerator não genérico


também deve ser implementado. Isso ocorre porque IEnumerable<T> herda de
IEnumerable. A implementação não genérica adia a implementação genérica.

O exemplo usa iteradores nomeados para dar suporte a várias maneiras de iterar na
mesma coleção de dados. Esses iteradores nomeados são as propriedades TopToBottom
e BottomToTop e o método TopN .

A propriedade BottomToTop usa um iterador em um acessador get .

C#

static void Main()


{
Stack<int> theStack = new Stack<int>();
// Add items to the stack.
for (int number = 0; number <= 9; number++)
{
theStack.Push(number);
}

// Retrieve items from the stack.


// foreach is allowed because theStack implements IEnumerable<int>.
foreach (int number in theStack)
{
Console.Write("{0} ", number);
}
Console.WriteLine();
// Output: 9 8 7 6 5 4 3 2 1 0

// foreach is allowed, because theStack.TopToBottom returns


IEnumerable(Of Integer).
foreach (int number in theStack.TopToBottom)
{
Console.Write("{0} ", number);
}
Console.WriteLine();
// Output: 9 8 7 6 5 4 3 2 1 0

foreach (int number in theStack.BottomToTop)


{
Console.Write("{0} ", number);
}
Console.WriteLine();
// Output: 0 1 2 3 4 5 6 7 8 9

foreach (int number in theStack.TopN(7))


{
Console.Write("{0} ", number);
}
Console.WriteLine();
// Output: 9 8 7 6 5 4 3

Console.ReadKey();
}

public class Stack<T> : IEnumerable<T>


{
private T[] values = new T[100];
private int top = 0;

public void Push(T t)


{
values[top] = t;
top++;
}
public T Pop()
{
top--;
return values[top];
}

// This method implements the GetEnumerator method. It allows


// an instance of the class to be used in a foreach statement.
public IEnumerator<T> GetEnumerator()
{
for (int index = top - 1; index >= 0; index--)
{
yield return values[index];
}
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

public IEnumerable<T> TopToBottom


{
get { return this; }
}

public IEnumerable<T> BottomToTop


{
get
{
for (int index = 0; index <= top - 1; index++)
{
yield return values[index];
}
}
}

public IEnumerable<T> TopN(int itemsFromTop)


{
// Return less than itemsFromTop if necessary.
int startIndex = itemsFromTop >= top ? 0 : top - itemsFromTop;

for (int index = top - 1; index >= startIndex; index--)


{
yield return values[index];
}
}

Informações de sintaxe
Um iterador pode ocorrer como um método ou como um acessador get . Um iterador
não pode ocorrer em um evento, um construtor de instância, um construtor estático ou
um finalizador estático.
Deve existir uma conversão implícita do tipo de expressão na instrução yield return ,
para o argumento de tipo do IEnumerable<T> retornado pelo iterador.

Em C#, um método iterador não pode ter os parâmetros in , ref nem out .

No C#, yield não é uma palavra reservada e só terá significado especial quando for
usada antes das palavras-chave return ou break .

Implementação Técnica
Embora você escreva um iterador como um método, o compilador o traduz em uma
classe aninhada que é, na verdade, uma máquina de estado. Essa classe mantém o
controle da posição do iterador enquanto o loop foreach no código cliente continuar.

Para ver o que o compilador faz, você pode usar a ferramenta Ildasm.exe para exibir o
código Microsoft Intermediate Language que é gerado para um método iterador.

Quando você cria um iterador para uma classe ou struct, não é necessário implementar
toda a interface IEnumerator. Quando o compilador detecta o iterador, ele gera
automaticamente os métodos Current , MoveNext e Dispose da interface IEnumerator ou
IEnumerator<T>.

A cada iteração sucessiva do loop foreach (ou a chamada direta ao


IEnumerator.MoveNext ), o próximo corpo de código do iterador continua, depois da

instrução yield return anterior. Em seguida, ele continuará até a próxima instrução
yield return , até que o final do corpo do iterador seja alcançado ou até que uma
instrução yield break seja encontrada.

Iteradores não dão suporte ao método IEnumerator.Reset. Para iterar novamente desde
o início, você deve obter um novo iterador. Chamar Reset no iterador retornado por um
método iterador lança um NotSupportedException.

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

Uso de iteradores
Os iteradores permitem que você mantenha a simplicidade de um loop foreach quando
for necessário usar um código complexo para preencher uma sequência de lista. Isso
pode ser útil quando você quiser fazer o seguinte:

Modificar a sequência de lista após a primeira iteração de loop foreach .


Evitar o carregamento completo de uma grande lista antes da primeira iteração de
um loop foreach . Um exemplo é uma busca paginada para carregar um lote de
linhas da tabela. Outro exemplo é o método EnumerateFiles, que implementa os
iteradores no .NET.

Encapsular a criação da lista no iterador. No método iterador, você pode criar a


lista e, em seguida, gerar cada resultado em um loop.

Confira também
System.Collections.Generic
IEnumerable<T>
foreach, in
Usar foreach com matrizes
Genéricos
Instruções (Guia de Programação em
C#)
Artigo • 07/04/2023

As ações que usa um programa executa são expressas em instruções. Ações comuns
incluem declarar variáveis, atribuir valores, chamar métodos, fazer loops pelas coleções
e ramificar para um ou para outro bloco de código, dependendo de uma determinada
condição. A ordem na qual as instruções são executadas em um programa é chamada
de fluxo de controle ou fluxo de execução. O fluxo de controle pode variar sempre que
um programa é executado, dependendo de como o programa reage às entradas que
recebe em tempo de execução.

Uma instrução pode consistir em uma única linha de código que termina em um ponto
e vírgula ou uma série de instruções de uma linha em um bloco. Um bloco de instrução
é colocado entre colchetes {} e pode conter blocos aninhados. O código a seguir mostra
dois exemplos de instruções de linha única, bem como um bloco de instrução de várias
linhas:

C#

static void Main()


{
// Declaration statement.
int counter;

// Assignment statement.
counter = 1;

// Error! This is an expression, not an expression statement.


// counter + 1;

// Declaration statements with initializers are functionally


// equivalent to declaration statement followed by assignment
statement:
int[] radii = { 15, 32, 108, 74, 9 }; // Declare and initialize an
array.
const double pi = 3.14159; // Declare and initialize constant.

// foreach statement block that contains multiple statements.


foreach (int radius in radii)
{
// Declaration statement with initializer.
double circumference = pi * (2 * radius);

// Expression statement (method invocation). A single-line


// statement can span multiple text lines because line breaks
// are treated as white space, which is ignored by the compiler.
System.Console.WriteLine("Radius of circle #{0} is {1}.
Circumference = {2:N2}",
counter, radius, circumference);

// Expression statement (postfix increment).


counter++;
} // End of foreach statement block
} // End of Main method body.
} // End of SimpleStatements class.
/*
Output:
Radius of circle #1 = 15. Circumference = 94.25
Radius of circle #2 = 32. Circumference = 201.06
Radius of circle #3 = 108. Circumference = 678.58
Radius of circle #4 = 74. Circumference = 464.96
Radius of circle #5 = 9. Circumference = 56.55
*/

Tipos de instruções
A tabela a seguir lista os diferentes tipos de instruções em C# e as palavras-chave
associadas a elas, com links para tópicos que contêm mais informações:

Categoria Palavras-chave do C#/observações

Instruções Uma declaração de instrução introduz uma nova variável ou constante. Uma
de declaração variável pode, opcionalmente, atribuir um valor à variável. Uma
declaração declaração constante, a atribuição é obrigatória.

Instruções Instruções de expressão que calculam um valor devem armazenar o valor em uma
de variável.
expressão

Instruções Instruções de seleção permitem que você ramifique para diferentes seções de
de seleção código, dependendo de uma ou mais condições especificadas. Para obter mais
informações, consulte estes tópicos:
if
switch

Instruções Instruções de iteração permitem que você percorra coleções como matrizes ou
de iteração execute o mesmo conjunto de instruções repetidamente até que uma determinada
condição seja atendida. Para obter mais informações, consulte estes tópicos:
do
for
foreach
while
Categoria Palavras-chave do C#/observações

Instruções Instruções de hiperlink transferem o controle para outra seção de código. Para obter
de atalho mais informações, consulte estes tópicos:
break
continue
goto
return
yield

Instruções Instruções para tratamento de exceções permitem que você se recupere


para normalmente de condições excepcionais que ocorrem em tempo de execução. Para
tratamento obter mais informações, consulte estes tópicos:
de throw
exceções try-catch
try-finally
try-catch-finally

checked e As instruções checked e unchecked permitem especificar se as operações numéricas


unchecked de tipo integral têm permissão para causar um estouro quando o resultado é
armazenado em uma variável muito pequena para manter o valor resultante.

A Se marcar um método com o modificador async, você poderá usar o operador await
instrução no método. Quando o controle atinge uma expressão await no método assíncrono,
await ele retorna para o chamador e o progresso no método é suspenso até a tarefa
aguardada ser concluída. Quando a tarefa for concluída, a execução poderá ser
retomada no método.

Para obter um exemplo simples, consulte a seção "Métodos assíncronos" em


Métodos. Para obter mais informações, consulte Programação assíncrona com async
e await.

A Um iterador realiza uma iteração personalizada em uma coleção, como uma lista ou
instrução uma matriz. Um iterador usa a instrução yield return para retornar um elemento de
yield cada vez. Quando uma instrução yield return for atingida, o local atual no código
return será lembrado. A execução será reiniciada desse local quando o iterador for
chamado na próxima vez.

Para obter mais informações, consulte Iteradores.

A A instrução fixed impede que o coletor de lixo faça a realocação de uma variável
instrução móvel. Para obter mais informações, consulte fixed.
fixed

A A instrução lock permite limitar o acesso a blocos de código a apenas um thread


instrução por vez. Para obter mais informações, consulte lock.
lock
Categoria Palavras-chave do C#/observações

Instruções Você pode atribuir um rótulo a uma instrução e, em seguida, usar a palavra-chave
rotuladas goto para ir diretamente para a instrução rotulada. (Veja o exemplo na linha a
seguir.)

A A instrução vazia consiste em um único ponto e vírgula. Ela não faz nada e pode ser
instrução usada em locais em que uma instrução é necessária, mas nenhuma ação precisa ser
vazia executada.

Instruções de declaração
O código a seguir mostra exemplos de declarações de variável com e sem uma
atribuição inicial e uma declaração de constante com a inicialização necessária.

C#

// Variable declaration statements.


double area;
double radius = 2;

// Constant declaration statement.


const double pi = 3.14159;

Instruções de expressão
O código a seguir mostra exemplos de instruções de expressão, incluindo a atribuição, a
criação de objeto com a atribuição e a invocação de método.

C#

// Expression statement (assignment).


area = 3.14 * (radius * radius);

// Error. Not statement because no assignment:


//circ * 2;

// Expression statement (method invocation).


System.Console.WriteLine();

// Expression statement (new object creation).


System.Collections.Generic.List<string> strings =
new System.Collections.Generic.List<string>();
A instrução vazia
Os exemplos a seguir mostram dois usos de uma instrução vazia:

C#

void ProcessMessages()
{
while (ProcessMessage())
; // Statement needed here.
}

void F()
{
//...
if (done) goto exit;
//...
exit:
; // Statement needed here.
}

Instruções inseridas
Algumas instruções, por exemplo, instruções de iteração, sempre têm uma instrução
inserida que as segue. Essa instrução inserida pode ser uma instrução única ou várias
instruções colocadas entre colchetes {} em um bloco de instrução. Até mesmo
instruções inseridas de uma única linha podem ser colocadas entre colchetes {},
conforme mostrado no seguinte exemplo:

C#

// Recommended style. Embedded statement in block.


foreach (string s in System.IO.Directory.GetDirectories(
System.Environment.CurrentDirectory))
{
System.Console.WriteLine(s);
}

// Not recommended.
foreach (string s in System.IO.Directory.GetDirectories(
System.Environment.CurrentDirectory))
System.Console.WriteLine(s);

Uma instrução inserida que não está entre colchetes {} não pode ser uma instrução de
declaração ou uma instrução rotulada. Isso é mostrado no exemplo a seguir:

C#
if(pointB == true)
//Error CS1023:
int radius = 5;

Coloque a instrução inserida em um bloco para corrigir o erro:

C#

if (b == true)
{
// OK:
System.DateTime d = System.DateTime.Now;
System.Console.WriteLine(d.ToLongDateString());
}

Blocos de instrução aninhados


Blocos de instrução podem ser aninhados, conforme mostrado no código a seguir:

C#

foreach (string s in System.IO.Directory.GetDirectories(


System.Environment.CurrentDirectory))
{
if (s.StartsWith("CSharp"))
{
if (s.EndsWith("TempFolder"))
{
return s;
}
}
}
return "Not found.";

Instruções inacessíveis
Se o compilador determinar que o fluxo de controle nunca pode atingir uma
determinada instrução em nenhuma circunstância, ele produzirá o aviso CS0162,
conforme mostrado no exemplo a seguir:

C#

// An over-simplified example of unreachable code.


const int val = 5;
if (val < 4)
{
System.Console.WriteLine("I'll never write anything."); //CS0162
}

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

Confira também
Guia de Programação em C#
Palavras-chave de instrução
Operadores e expressões C#
Membros aptos para expressão (Guia de
Programação em C#)
Artigo • 10/05/2023

As definições de corpo da expressão permitem que você forneça uma implementação


de um membro em uma forma bastante concisa e legível. Você pode usar uma definição
de corpo da expressão sempre que a lógica para qualquer membro com suporte, como
um método ou propriedade, consiste em uma única expressão. Uma definição de corpo
da expressão tem a seguinte sintaxe geral:

C#

member => expression;

em que expression é uma expressão válida.

As definições do corpo da expressão podem ser usadas com os seguintes membros de


tipo:

Método
Propriedade somente leitura
Propriedade
Construtor
Finalizer
Indexador

Métodos
Um método apto para expressão consiste em uma única expressão que retorna um
valor cujo tipo corresponde ao tipo de retorno do método, ou, para métodos que
retornam void , que executam uma operação. Por exemplo, os tipos que substituem o
método ToString normalmente incluem uma única expressão que retorna a
representação da cadeia de caracteres do objeto atual.

O exemplo a seguir define uma classe Person que substitui o método ToString por uma
definição de corpo da expressão. Ele também define um método DisplayName que exibe
um nome para o console. Observe que a palavra-chave return não é usada na definição
de corpo da expressão ToString .

C#
using System;

public class Person


{
public Person(string firstName, string lastName)
{
fname = firstName;
lname = lastName;
}

private string fname;


private string lname;

public override string ToString() => $"{fname} {lname}".Trim();


public void DisplayName() => Console.WriteLine(ToString());
}

class Example
{
static void Main()
{
Person p = new Person("Mandy", "Dejesus");
Console.WriteLine(p);
p.DisplayName();
}
}

Para obter mais informações, consulte Métodos (Guia de Programação em C#).

Propriedades somente leitura


Você pode usar a definição de corpo da expressão para implementar uma propriedade
somente leitura. Para isso, use a seguinte sintaxe:

C#

PropertyType PropertyName => expression;

O exemplo a seguir define uma classe Location cuja propriedade somente leitura Name
é implementada como uma definição de corpo da expressão que retorna o valor do
campo locationName particular:

C#

public class Location


{
private string locationName;
public Location(string name)
{
locationName = name;
}

public string Name => locationName;


}

Para obter mais informações sobre as propriedades, confira Propriedades (Guia de


Programação em C#).

Propriedades
Você pode usar as definições de corpo da expressão para implementar a propriedade
get e os acessadores set . O exemplo a seguir demonstra como fazer isso:

C#

public class Location


{
private string locationName;

public Location(string name) => Name = name;

public string Name


{
get => locationName;
set => locationName = value;
}
}

Para obter mais informações sobre as propriedades, confira Propriedades (Guia de


Programação em C#).

Construtores
Uma definição de corpo da expressão para um construtor normalmente consiste em
uma expressão de atribuição simples ou uma chamada de método que manipula os
argumentos do construtor ou inicializa o estado da instância.

O exemplo a seguir define uma classe Location cujo construtor tem um único
parâmetro de cadeia de caracteres chamado nome. A definição de corpo da expressão
atribui o argumento à propriedade Name .

C#
public class Location
{
private string locationName;

public Location(string name) => Name = name;

public string Name


{
get => locationName;
set => locationName = value;
}
}

Para obter mais informações, consulte Construtores (Guia de Programação em C#).

Finalizadores
Uma definição de corpo da expressão para um finalizador normalmente contém
instruções de limpeza, como instruções que liberam recursos não gerenciados.

O exemplo a seguir define um finalizador que usa uma definição de corpo da expressão
para indicar que o finalizador foi chamado.

C#

public class Destroyer


{
public override string ToString() => GetType().Name;

~Destroyer() => Console.WriteLine($"The {ToString()} finalizer is


executing.");
}

Para obter mais informações, consulte Finalizadores (Guia de Programação em C#).

Indexadores
Como as propriedades, os acessadores get e set do indexador consistirão em
definições de corpo da expressão se o acessador get consistir em uma única expressão
que retorna um valor ou o acessador set executar uma atribuição simples.

O exemplo a seguir define uma classe chamada Sports que inclui uma matriz String
interna que contém os nomes de vários esportes. Os acessadores get e set do
indexador são implementados como definições de corpo da expressão.
C#

using System;
using System.Collections.Generic;

public class Sports


{
private string[] types = { "Baseball", "Basketball", "Football",
"Hockey", "Soccer", "Tennis",
"Volleyball" };

public string this[int i]


{
get => types[i];
set => types[i] = value;
}
}

Para obter mais informações, consulte Indexadores (Guia de Programação em C#).

Confira também
Regras de estilo de código do .NET para membros do corpo da expressão
Comparações de igualdade (Guia de
Programação em C#)
Artigo • 13/03/2024

Às vezes, é necessário comparar dois valores em relação à igualdade. Em alguns casos,


testa-se a igualdade de valor, também conhecida como equivalência, o que significa que
os valores contidos pelas duas variáveis são iguais. Em outros casos, é necessário
determinar se duas variáveis se referem ao mesmo objeto subjacente na memória. Esse
tipo de igualdade é chamado igualdade de referência ou identidade. Este tópico
descreve esses dois tipos de igualdade e fornece links para outros tópicos que fornecem
mais informações.

Igualdade de referência
Igualdade de referência significa que as duas referências de objeto se referem ao
mesmo objeto subjacente. Isso pode ocorrer por meio de uma atribuição simples,
conforme mostrado no exemplo a seguir.

C#

using System;
class Test
{
public int Num { get; set; }
public string Str { get; set; }

public static void Main()


{
Test a = new Test() { Num = 1, Str = "Hi" };
Test b = new Test() { Num = 1, Str = "Hi" };

bool areEqual = System.Object.ReferenceEquals(a, b);


// False:
System.Console.WriteLine("ReferenceEquals(a, b) = {0}", areEqual);

// Assign b to a.
b = a;

// Repeat calls with different results.


areEqual = System.Object.ReferenceEquals(a, b);
// True:
System.Console.WriteLine("ReferenceEquals(a, b) = {0}", areEqual);
}
}
Nesse código, dois objetos são criados, mas após a instrução de atribuição, ambas as
referências se referem ao mesmo objeto. Portanto, eles têm igualdade de referência.
Use o método ReferenceEquals para determinar se duas referências referenciam o
mesmo objeto.

O conceito de igualdade de referência se aplica apenas a tipos de referência. Objetos de


tipo de valor não podem ter igualdade de referência, pois quando uma instância de um
tipo de valor é atribuída a uma variável, uma cópia do valor é gerada. Portanto, não é
possível ter dois structs desconvertidos que referenciam o mesmo local na memória.
Além disso, se ReferenceEquals for usado para comparar dois tipos de valor, o resultado
sempre será false , mesmo se os valores contidos nos objetos forem idênticos. Isso
ocorre porque cada variável é convertido em uma instância de objeto separada. Para
obter mais informações, consulte Como testar a igualdade de referência (Identidade).

Igualdade de valor
Igualdade de valor significa que dois objetos contêm o mesmo valor ou valores. Para
tipos de valor primitivos, como int ou bool, os testes de igualdade de valor são simples.
É possível usar o operador ==, conforme mostrado no exemplo a seguir.

C#

int a = GetOriginalValue();
int b = GetCurrentValue();

// Test for value equality.


if (b == a)
{
// The two integers are equal.
}

Para a maioria dos outros tipos, o teste de igualdade de valor é mais complexo, pois é
necessário entender como o tipo o define. Para classes e structs que têm vários campos
ou propriedades, a igualdade de valor geralmente é definida para determinar que todos
os campos ou propriedades tenham o mesmo valor. Por exemplo, dois objetos Point
podem ser definidos para serem equivalentes se pointA.X for igual a pointB.X e pointA.Y
for igual a pointB.Y. Para registros, a igualdade de valor significa que duas variáveis de
um tipo de registro são iguais se os tipos corresponderem e todos os valores de
propriedade e de campo corresponderem.

No entanto, não há nenhuma exigência de que a equivalência seja baseada em todos os


campos em um tipo. Ela pode ser baseada em um subconjunto. Ao comparar tipos que
não são de sua propriedade, certifique-se de que a forma como a equivalência é
definida especificamente para esse tipo foi entendida. Para obter mais informações
sobre como definir a igualdade de valor em suas próprias classes e structs, consulte
Como definir a igualdade de valor para um tipo.

Igualdade de valor para valores de ponto flutuante


As comparações de igualdade de valores de ponto flutuante (double e float) são
problemáticas devido à imprecisão da aritmética de ponto flutuante em computadores
binários. Para obter mais informações, consulte os comentários no tópico
System.Double.

Tópicos relacionados
ノ Expandir a tabela

Título Descrição

Como testar a igualdade de Descreve como determinar se duas variáveis têm igualdade
referência (Identidade) de referência.

Como definir a igualdade de valor Descreve como fornecer uma definição personalizada de
para um tipo igualdade de valor a um tipo.

Types Fornece informações sobre o sistema de tipos do C# e links


para mais informações.

Registros Fornece informações sobre tipos de registro que testam a


igualdade de valor por padrã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
Como definir a igualdade de valor para
uma classe ou struct (Guia de
programação em C#)
Artigo • 13/03/2024

Os registros implementam automaticamente a igualdade de valor. Considere definir um


record em vez de um class quando seu tipo modelar dados e tiver que implementar a

igualdade de valor.

Quando você define uma classe ou struct, decide se faz sentido criar uma definição
personalizada de igualdade de valor (ou equivalência) para o tipo. Normalmente, você
implementa igualdade de valor quando espera adicionar objetos do tipo a uma coleção,
ou quando seu objetivo principal for armazenar um conjunto de campos ou
propriedades. Você pode basear sua definição de igualdade de valor em uma
comparação de todos os campos e propriedades no tipo ou pode basear a definição em
um subconjunto.

Em ambos os casos e em classes e structs, sua implementação deve seguir as cinco


garantias de equivalência (para as seguintes regras, suponha que x , y e z não sejam
nulos):

1. Propriedade reflexiva: x.Equals(x) retorna true .

2. Propriedade simétrica: x.Equals(y) retorna o mesmo valor que y.Equals(x) .

3. Propriedade transitiva: se (x.Equals(y) && y.Equals(z)) retorna true , então


x.Equals(z) retorna true .

4. Invocações sucessivas de x.Equals(y) retornam o mesmo valor, contanto que os


objetos referenciados por x e y não sejam modificados.

5. Qualquer valor não nulo não é igual a nulo. No entanto, x.Equals(y) gera uma
exceção quando x é nulo. Isso quebra as regras 1 ou 2, a depender do argumento
para Equals .

Qualquer struct que você define já tem uma implementação padrão de igualdade de
valor que ele herda da substituição System.ValueType do método Object.Equals(Object).
Essa implementação usa a reflexão para examinar todos os campos e propriedades no
tipo. Embora essa implementação produza resultados corretos, ela é relativamente lenta
em comparação com uma implementação personalizada escrita especificamente para o
tipo.

Os detalhes de implementação para a igualdade de valor são diferentes para classes e


struct. No entanto, as classes e structs exigem as mesmas etapas básicas para
implementar a igualdade:

1. Substitua o método virtualObject.Equals(Object). Na maioria dos casos, sua


implementação de bool Equals( object obj ) deve apenas chamar o método
Equals específico do tipo que é a implementação da interface

System.IEquatable<T>. (Consulte a etapa 2.)

2. Implemente a interface System.IEquatable<T> fornecendo um método Equals


específico do tipo. Isso é o local em que a comparação de equivalência de fato é
realizada. Por exemplo, você pode decidir definir a igualdade comparando apenas
um ou dois campos em seu tipo. Não lance exceções a partir de Equals . Para
classes relacionadas por herança:

esse método deve examinar somente os campos que são declarados na


classe. Ele deve chamar base.Equals para examinar os campos que estão na
classe base. (Não chame base.Equals se o tipo herdar diretamente de Object,
pois a implementação Object de Object.Equals(Object) executa uma
verificação de igualdade de referência.)

Duas variáveis devem ser consideradas iguais somente se os tipos de tempo


de execução das variáveis que estão sendo comparadas forem os mesmos.
Além disso, verifique se a implementação IEquatable do método Equals
para o tipo de tempo de execução será usada se os tipos de tempo de
execução e tempo de compilação de uma variável forem diferentes. Uma
estratégia para garantir que os tipos de tempo de execução sejam sempre
comparados corretamente é implementar IEquatable somente em classes
sealed . Para obter mais informações, consulte o exemplo de classe mais

adiante neste artigo.

3. Opcional, mas recomendado: sobrecarregue os operadores == e !=.

4. Substitua Object.GetHashCode para que os dois objetos que têm a igualdade de


valor produzam o mesmo código hash.

5. Opcional: para dar suporte às definições para “maior que” ou “menor que”,
implemente a interface IComparable<T> para seu tipo e também sobrecarregue
os operadores <= e >=.
7 Observação

Use registros para obter a semântica de igualdade de valor sem nenhum código
clichê desnecessário.

Exemplo de classe
O exemplo a seguir mostra como implementar a igualdade de valor em uma classe (tipo
de referência).

C#

namespace ValueEqualityClass;

class TwoDPoint : IEquatable<TwoDPoint>


{
public int X { get; private set; }
public int Y { get; private set; }

public TwoDPoint(int x, int y)


{
if (x is (< 1 or > 2000) || y is (< 1 or > 2000))
{
throw new ArgumentException("Point must be in range 1 - 2000");
}
this.X = x;
this.Y = y;
}

public override bool Equals(object obj) => this.Equals(obj as


TwoDPoint);

public bool Equals(TwoDPoint p)


{
if (p is null)
{
return false;
}

// Optimization for a common success case.


if (Object.ReferenceEquals(this, p))
{
return true;
}

// If run-time types are not exactly the same, return false.


if (this.GetType() != p.GetType())
{
return false;
}

// Return true if the fields match.


// Note that the base class is not invoked because it is
// System.Object, which defines Equals as reference equality.
return (X == p.X) && (Y == p.Y);
}

public override int GetHashCode() => (X, Y).GetHashCode();

public static bool operator ==(TwoDPoint lhs, TwoDPoint rhs)


{
if (lhs is null)
{
if (rhs is null)
{
return true;
}

// Only the left side is null.


return false;
}
// Equals handles case of null on right side.
return lhs.Equals(rhs);
}

public static bool operator !=(TwoDPoint lhs, TwoDPoint rhs) => !(lhs ==
rhs);
}

// For the sake of simplicity, assume a ThreeDPoint IS a TwoDPoint.


class ThreeDPoint : TwoDPoint, IEquatable<ThreeDPoint>
{
public int Z { get; private set; }

public ThreeDPoint(int x, int y, int z)


: base(x, y)
{
if ((z < 1) || (z > 2000))
{
throw new ArgumentException("Point must be in range 1 - 2000");
}
this.Z = z;
}

public override bool Equals(object obj) => this.Equals(obj as


ThreeDPoint);

public bool Equals(ThreeDPoint p)


{
if (p is null)
{
return false;
}
// Optimization for a common success case.
if (Object.ReferenceEquals(this, p))
{
return true;
}

// Check properties that this class declares.


if (Z == p.Z)
{
// Let base class check its own fields
// and do the run-time type comparison.
return base.Equals((TwoDPoint)p);
}
else
{
return false;
}
}

public override int GetHashCode() => (X, Y, Z).GetHashCode();

public static bool operator ==(ThreeDPoint lhs, ThreeDPoint rhs)


{
if (lhs is null)
{
if (rhs is null)
{
// null == null = true.
return true;
}

// Only the left side is null.


return false;
}
// Equals handles the case of null on right side.
return lhs.Equals(rhs);
}

public static bool operator !=(ThreeDPoint lhs, ThreeDPoint rhs) => !


(lhs == rhs);
}

class Program
{
static void Main(string[] args)
{
ThreeDPoint pointA = new ThreeDPoint(3, 4, 5);
ThreeDPoint pointB = new ThreeDPoint(3, 4, 5);
ThreeDPoint pointC = null;
int i = 5;

Console.WriteLine("pointA.Equals(pointB) = {0}",
pointA.Equals(pointB));
Console.WriteLine("pointA == pointB = {0}", pointA == pointB);
Console.WriteLine("null comparison = {0}", pointA.Equals(pointC));
Console.WriteLine("Compare to some other type = {0}",
pointA.Equals(i));

TwoDPoint pointD = null;


TwoDPoint pointE = null;

Console.WriteLine("Two null TwoDPoints are equal: {0}", pointD ==


pointE);

pointE = new TwoDPoint(3, 4);


Console.WriteLine("(pointE == pointA) = {0}", pointE == pointA);
Console.WriteLine("(pointA == pointE) = {0}", pointA == pointE);
Console.WriteLine("(pointA != pointE) = {0}", pointA != pointE);

System.Collections.ArrayList list = new


System.Collections.ArrayList();
list.Add(new ThreeDPoint(3, 4, 5));
Console.WriteLine("pointE.Equals(list[0]): {0}",
pointE.Equals(list[0]));

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}

/* Output:
pointA.Equals(pointB) = True
pointA == pointB = True
null comparison = False
Compare to some other type = False
Two null TwoDPoints are equal: True
(pointE == pointA) = False
(pointA == pointE) = False
(pointA != pointE) = True
pointE.Equals(list[0]): False
*/

Em classes (tipo de referência), a implementação padrão de ambos os métodos


Object.Equals(Object) executa uma comparação de igualdade de referência, não uma
verificação de igualdade de valor. Quando um implementador substitui o método
virtual, o objetivo é fornecer semântica de igualdade de valor.

Os operadores == e != podem ser usados com classes, mesmo se a classe não


sobrecarregá-los. No entanto, o comportamento padrão é executar uma verificação de
igualdade de referência. Em uma classe, se você sobrecarregar o método Equals , você
deverá sobrecarregar os operadores == e != , mas isso não é necessário.

) Importante
O código de exemplo anterior pode não lidar com todos os cenários de herança da
maneira esperada. Considere o seguinte código:

C#

TwoDPoint p1 = new ThreeDPoint(1, 2, 3);


TwoDPoint p2 = new ThreeDPoint(1, 2, 4);
Console.WriteLine(p1.Equals(p2)); // output: True

Esse código relata que p1 é igual a p2 apesar da diferença de valores z . A


diferença é ignorada porque o compilador escolhe a implementação TwoDPoint de
IEquatable com base no tipo de tempo de compilação.

A igualdade de valores interna de tipos record lida com cenários como este
corretamente. Se TwoDPoint e ThreeDPoint fossem tipos record , o resultado de
p1.Equals(p2) seria False . Para obter mais informações, consulte Igualdade nas

hierarquias de herança de tipo record.

Exemplo de struct
O exemplo a seguir mostra como implementar a igualdade de valor em um struct (tipo
de valor):

C#

namespace ValueEqualityStruct
{
struct TwoDPoint : IEquatable<TwoDPoint>
{
public int X { get; private set; }
public int Y { get; private set; }

public TwoDPoint(int x, int y)


: this()
{
if (x is (< 1 or > 2000) || y is (< 1 or > 2000))
{
throw new ArgumentException("Point must be in range 1 -
2000");
}
X = x;
Y = y;
}

public override bool Equals(object? obj) => obj is TwoDPoint other


&& this.Equals(other);
public bool Equals(TwoDPoint p) => X == p.X && Y == p.Y;

public override int GetHashCode() => (X, Y).GetHashCode();

public static bool operator ==(TwoDPoint lhs, TwoDPoint rhs) =>


lhs.Equals(rhs);

public static bool operator !=(TwoDPoint lhs, TwoDPoint rhs) => !


(lhs == rhs);
}

class Program
{
static void Main(string[] args)
{
TwoDPoint pointA = new TwoDPoint(3, 4);
TwoDPoint pointB = new TwoDPoint(3, 4);
int i = 5;

// True:
Console.WriteLine("pointA.Equals(pointB) = {0}",
pointA.Equals(pointB));
// True:
Console.WriteLine("pointA == pointB = {0}", pointA == pointB);
// True:
Console.WriteLine("object.Equals(pointA, pointB) = {0}",
object.Equals(pointA, pointB));
// False:
Console.WriteLine("pointA.Equals(null) = {0}",
pointA.Equals(null));
// False:
Console.WriteLine("(pointA == null) = {0}", pointA == null);
// True:
Console.WriteLine("(pointA != null) = {0}", pointA != null);
// False:
Console.WriteLine("pointA.Equals(i) = {0}", pointA.Equals(i));
// CS0019:
// Console.WriteLine("pointA == i = {0}", pointA == i);

// Compare unboxed to boxed.


System.Collections.ArrayList list = new
System.Collections.ArrayList();
list.Add(new TwoDPoint(3, 4));
// True:
Console.WriteLine("pointA.Equals(list[0]): {0}",
pointA.Equals(list[0]));

// Compare nullable to nullable and to non-nullable.


TwoDPoint? pointC = null;
TwoDPoint? pointD = null;
// False:
Console.WriteLine("pointA == (pointC = null) = {0}", pointA ==
pointC);
// True:
Console.WriteLine("pointC == pointD = {0}", pointC == pointD);

TwoDPoint temp = new TwoDPoint(3, 4);


pointC = temp;
// True:
Console.WriteLine("pointA == (pointC = 3,4) = {0}", pointA ==
pointC);

pointD = temp;
// True:
Console.WriteLine("pointD == (pointC = 3,4) = {0}", pointD ==
pointC);

Console.WriteLine("Press any key to exit.");


Console.ReadKey();
}
}

/* Output:
pointA.Equals(pointB) = True
pointA == pointB = True
Object.Equals(pointA, pointB) = True
pointA.Equals(null) = False
(pointA == null) = False
(pointA != null) = True
pointA.Equals(i) = False
pointE.Equals(list[0]): True
pointA == (pointC = null) = False
pointC == pointD = True
pointA == (pointC = 3,4) = True
pointD == (pointC = 3,4) = True
*/
}

Para estruturas, a implementação padrão de Object.Equals(Object) (que é a versão


substituída em System.ValueType) executa uma verificação de igualdade de valor por
meio de reflexão para comparar os valores de cada campo no tipo. Quando um
implementador substitui o método virtual Equals em um struct, a finalidade é fornecer
uma maneira mais eficiente de executar a verificação de igualdade de valor e,
opcionalmente, basear a comparação em algum subconjunto dos campos ou
propriedades do struct.

Os operadores == e != não podem operar em um struct a menos que o struct os


sobrecarregue explicitamente.

Confira também
Comparações de igualdade
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
Como testar a igualdade de referência
(identidade) (Guia de Programação em
C#)
Artigo • 07/04/2023

Não é necessário implementar qualquer lógica personalizada para dar suporte a


comparações de igualdade de referência em seus tipos. Essa funcionalidade é fornecida
para todos os tipos pelo método estático Object.ReferenceEquals.

O exemplo a seguir mostra como determinar se duas variáveis têm igualdade de


referência, que significa que elas se referem ao mesmo objeto na memória.

O exemplo também mostra por que Object.ReferenceEquals sempre retorna false para
tipos de valor e por que você não deve usar ReferenceEquals para determinar igualdade
de cadeia de caracteres.

Exemplo
C#

using System.Text;

namespace TestReferenceEquality
{
struct TestStruct
{
public int Num { get; private set; }
public string Name { get; private set; }

public TestStruct(int i, string s) : this()


{
Num = i;
Name = s;
}
}

class TestClass
{
public int Num { get; set; }
public string? Name { get; set; }
}

class Program
{
static void Main()
{
// Demonstrate reference equality with reference types.
#region ReferenceTypes

// Create two reference type instances that have identical


values.
TestClass tcA = new TestClass() { Num = 1, Name = "New
TestClass" };
TestClass tcB = new TestClass() { Num = 1, Name = "New
TestClass" };

Console.WriteLine("ReferenceEquals(tcA, tcB) = {0}",


Object.ReferenceEquals(tcA, tcB)); // false

// After assignment, tcB and tcA refer to the same object.


// They now have reference equality.
tcB = tcA;
Console.WriteLine("After assignment: ReferenceEquals(tcA, tcB) =
{0}",
Object.ReferenceEquals(tcA, tcB)); // true

// Changes made to tcA are reflected in tcB. Therefore, objects


// that have reference equality also have value equality.
tcA.Num = 42;
tcA.Name = "TestClass 42";
Console.WriteLine("tcB.Name = {0} tcB.Num: {1}", tcB.Name,
tcB.Num);
#endregion

// Demonstrate that two value type instances never have


reference equality.
#region ValueTypes

TestStruct tsC = new TestStruct( 1, "TestStruct 1");

// Value types are copied on assignment. tsD and tsC have


// the same values but are not the same object.
TestStruct tsD = tsC;
Console.WriteLine("After assignment: ReferenceEquals(tsC, tsD) =
{0}",
Object.ReferenceEquals(tsC, tsD)); // false
#endregion

#region stringRefEquality
// Constant strings within the same assembly are always interned
by the runtime.
// This means they are stored in the same location in memory.
Therefore,
// the two strings have reference equality although no
assignment takes place.
string strA = "Hello world!";
string strB = "Hello world!";
Console.WriteLine("ReferenceEquals(strA, strB) = {0}",
Object.ReferenceEquals(strA, strB)); // true
// After a new string is assigned to strA, strA and strB
// are no longer interned and no longer have reference equality.
strA = "Goodbye world!";
Console.WriteLine("strA = \"{0}\" strB = \"{1}\"", strA, strB);

Console.WriteLine("After strA changes, ReferenceEquals(strA,


strB) = {0}",
Object.ReferenceEquals(strA, strB)); // false

// A string that is created at runtime cannot be interned.


StringBuilder sb = new StringBuilder("Hello world!");
string stringC = sb.ToString();
// False:
Console.WriteLine("ReferenceEquals(stringC, strB) = {0}",
Object.ReferenceEquals(stringC, strB));

// The string class overloads the == operator to perform an


equality comparison.
Console.WriteLine("stringC == strB = {0}", stringC == strB); //
true

#endregion

// Keep the console open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
}

/* Output:
ReferenceEquals(tcA, tcB) = False
After assignment: ReferenceEquals(tcA, tcB) = True
tcB.Name = TestClass 42 tcB.Num: 42
After assignment: ReferenceEquals(tsC, tsD) = False
ReferenceEquals(strA, strB) = True
strA = "Goodbye world!" strB = "Hello world!"
After strA changes, ReferenceEquals(strA, strB) = False
ReferenceEquals(stringC, strB) = False
stringC == strB = True
*/

A implementação de Equals na classe base universal System.Object também realiza


uma verificação de igualdade de referência, mas é melhor não usar isso, porque, se uma
classe substituir o método, os resultados poderão não ser o que você espera. O mesmo
é verdadeiro para os operadores == e != . Quando eles estiverem operando em tipos de
referência, o comportamento padrão de == e != é realizar uma verificação de igualdade
de referência. No entanto, as classes derivadas podem sobrecarregar o operador para
executar uma verificação de igualdade de valor. Para minimizar o potencial de erro, será
melhor usar sempre ReferenceEquals quando for necessário determinar se os dois
objetos têm igualdade de referência.
Cadeias de caracteres constantes dentro do mesmo assembly sempre são internalizadas
pelo runtime. Ou seja, apenas uma instância de cada cadeia de caracteres literal única é
mantida. No entanto, o runtime não garante que cadeias de caracteres criadas em
runtime sejam internalizadas nem garante que duas de cadeias de caracteres constantes
iguais em diferentes assemblies sejam internalizadas.

Confira também
Comparações de igualdade
Coerções e conversões de tipo (Guia de
Programação em C#)
Artigo • 15/03/2024

Como o C# é tipado estaticamente no tempo de compilação, depois que uma variável é


declarada, ela não pode ser declarada novamente ou atribuída a um valor de outro tipo,
a menos que esse tipo possa ser convertido implicitamente no tipo da variável. Por
exemplo, a string não pode ser convertida implicitamente em int . Portanto, depois de
declarar i como um int , não é possível atribuir a cadeia de caracteres "Hello" a ele,
como mostra o código a seguir:

C#

int i;

// error CS0029: can't implicitly convert type 'string' to 'int'


i = "Hello";

No entanto, às vezes é necessário copiar um valor para uma variável ou um parâmetro


de método de outro tipo. Por exemplo, você pode ter que passar uma variável de inteiro
para um método cujo parâmetro é digitado como double . Ou talvez precise atribuir
uma variável de classe a uma variável de um tipo de interface. Esses tipos de operações
são chamados de conversões de tipo. No C#, você pode realizar os seguintes tipos de
conversões:

Conversões implícitas: nenhuma sintaxe especial é necessária porque a conversão


sempre é bem sucedida e nenhum dado é perdido. Exemplos incluem conversões
de tipos integrais menores para maiores e conversões de classes derivadas para
classes base.

Conversões explícitas (casts): as conversões explícitas exigem uma expressão cast.


A conversão é necessária quando as informações podem ser perdidas na
conversão ou quando a conversão pode não funcionar por outros motivos.
Exemplos típicos incluem a conversão numérica para um tipo que tem menos
precisão ou um intervalo menor e a conversão de uma instância de classe base
para uma classe derivada.

Conversões definidas pelo usuário: as conversões definidas pelo usuário usam


métodos especiais que podem ser definidos para habilitar conversões explícitas e
implícitas entre tipos personalizados que não têm uma relação de classe
base/classe derivada. Para saber mais, confira Operadores de conversão definidos
pelo usuário.

Conversões com classes auxiliares: para converter entre tipos não compatíveis,
assim como inteiros e objetos System.DateTime, ou cadeias de caracteres
hexadecimais e matrizes de bytes, você pode usar a classe System.BitConverter, a
classe System.Convert e os métodos Parse dos tipos numéricos internos, tais
como Int32.Parse. Para obter mais informações, consulte Como converter uma
matriz de bytes em um int, Como converter uma cadeia de caracteres em um
número e Como converter entre cadeias de caracteres hexadecimais e tipos
numéricos.

Conversões implícitas
Para tipos numéricos internos, uma conversão implícita poderá ser feita quando o valor
a ser armazenado puder se ajustar à variável sem ser truncado ou arredondado. Para
tipos integrais, isso significa que o intervalo do tipo de origem é um subconjunto
apropriado do intervalo para o tipo de destino. Por exemplo, uma variável do tipo long
(inteiro de 64 bits) pode armazenar qualquer valor que um int (inteiro de 32 bits) pode
armazenar. No exemplo a seguir, o compilador converte implicitamente o valor de num
à direita em um tipo long antes de atribuí-lo a bigNum .

C#

// Implicit conversion. A long can


// hold any value an int can hold, and more!
int num = 2147483647;
long bigNum = num;

Para obter uma lista completa de todas as conversões numéricas implícitas, consulte a
seção Conversões numéricas implícitas do artigo Conversões numéricas internas.

Para tipos de referência, uma conversão implícita sempre existe de uma classe para
qualquer uma das suas interfaces ou classes base diretas ou indiretas. Nenhuma sintaxe
especial é necessária porque uma classe derivada sempre contém todos os membros de
uma classe base.

C#

Derived d = new Derived();

// Always OK.
Base b = d;
Conversões explícitas
No entanto, se uma conversão não puder ser realizada sem o risco de perda de
informações, o compilador exigirá que você execute uma conversão explícita, que é
chamada de cast. Uma conversão é uma maneira de informar explicitamente ao
compilador que você pretende fazer a conversão e que você está ciente de que poderá
ocorrer perda de dados ou a conversão poderá falhar em tempo de execução. Para
executar uma conversão, especifique entre parênteses o tipo para o qual você está
convertendo, na frente do valor ou da variável a ser convertida. O seguinte programa
converte um double em um int. O programa não será compilado sem a conversão.

C#

class Test
{
static void Main()
{
double x = 1234.7;
int a;
// Cast double to int.
a = (int)x;
System.Console.WriteLine(a);
}
}
// Output: 1234

Para obter uma lista completa de conversões numéricas explícitas com suporte, consulte
a seção Conversões numéricas explícitas do artigo Conversões numéricas internas.

Para tipos de referência, uma conversão explícita será necessária se você precisar
converter de um tipo base para um tipo derivado:

C#

// Create a new derived type.


Giraffe g = new Giraffe();

// Implicit conversion to base type is safe.


Animal a = g;

// Explicit conversion is required to cast back


// to derived type. Note: This will compile but will
// throw an exception at run time if the right-side
// object is not in fact a Giraffe.
Giraffe g2 = (Giraffe)a;
Uma operação de conversão entre tipos de referência não altera o tipo de tempo de
execução do objeto subjacente. Ela apenas altera o tipo do valor que está sendo usado
como uma referência a esse objeto. Para obter mais informações, consulte Polimorfismo.

Exceções de conversão de tipo em tempo de


execução
Em algumas conversões de tipo de referência, o compilador não pode determinar se
uma conversão é válida. É possível que uma operação de conversão que é compilada
corretamente falhe em tempo de execução. Conforme mostrado no exemplo a seguir,
um tipo de conversão que falha em tempo de execução faz com que uma
InvalidCastException seja lançada.

C#

class Animal
{
public void Eat() => System.Console.WriteLine("Eating.");

public override string ToString() => "I am an animal.";


}

class Reptile : Animal { }


class Mammal : Animal { }

class UnSafeCast
{
static void Main()
{
Test(new Mammal());

// Keep the console window open in debug mode.


System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}

static void Test(Animal a)


{
// System.InvalidCastException at run time
// Unable to cast object of type 'Mammal' to type 'Reptile'
Reptile r = (Reptile)a;
}
}

O método Test tem um parâmetro Animal , assim, converter explicitamente o


argumento a em um Reptile é fazer uma suposição perigosa. É mais seguro não fazer
suposições, mas sim verificar o tipo. O C# fornece o operador is para habilitar o teste de
compatibilidade antes de realmente executar uma conversão. Para saber mais, confira
Como converter com segurança usando a correspondência de padrões e os operadores
“is” e “as”.

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

Confira também
Types
Expressão de conversão
Operadores de conversões definidas pelo usuário
Conversão de tipos generalizada
Como converter uma cadeia de caracteres em um número

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
Conversões boxing e unboxing (Guia de
Programação em C#)
Artigo • 07/04/2023

Conversão boxing é o processo de conversão de um tipo de valor para o tipo object ou


para qualquer tipo de interface implementada por esse tipo de valor. Quando o CLR
(Common Language Runtime) realiza a conversão de um tipo de valor, ele encapsula o
valor dentro de uma instância System.Object e a armazena no heap gerenciado. A
conversão unboxing extrai o tipo de valor do objeto. A conversão boxing é implícita, a
conversão unboxing é explícita. O conceito de conversões boxing e unboxing serve
como base para a exibição unificada de C# do sistema de tipos em que um valor de
qualquer tipo pode ser tratado como um objeto.

No exemplo a seguir, a variável de inteiro i é submetida à conversão boxing e atribuída


ao objeto o .

C#

int i = 123;
// The following line boxes i.
object o = i;

O objeto o pode ser submetido à conversão unboxing e atribuído à variável de inteiro


i:

C#

o = 123;
i = (int)o; // unboxing

Os exemplos a seguir ilustram como a conversão boxing é usada em C#.

C#

// String.Concat example.
// String.Concat has many versions. Rest the mouse pointer on
// Concat in the following statement to verify that the version
// that is used here takes three object arguments. Both 42 and
// true must be boxed.
Console.WriteLine(String.Concat("Answer", 42, true));

// List example.
// Create a list of objects to hold a heterogeneous collection
// of elements.
List<object> mixedList = new List<object>();

// Add a string element to the list.


mixedList.Add("First Group:");

// Add some integers to the list.


for (int j = 1; j < 5; j++)
{
// Rest the mouse pointer over j to verify that you are adding
// an int to a list of objects. Each element j is boxed when
// you add j to mixedList.
mixedList.Add(j);
}

// Add another string and more integers.


mixedList.Add("Second Group:");
for (int j = 5; j < 10; j++)
{
mixedList.Add(j);
}

// Display the elements in the list. Declare the loop variable by


// using var, so that the compiler assigns its type.
foreach (var item in mixedList)
{
// Rest the mouse pointer over item to verify that the elements
// of mixedList are objects.
Console.WriteLine(item);
}

// The following loop sums the squares of the first group of boxed
// integers in mixedList. The list elements are objects, and cannot
// be multiplied or added to the sum until they are unboxed. The
// unboxing must be done explicitly.
var sum = 0;
for (var j = 1; j < 5; j++)
{
// The following statement causes a compiler error: Operator
// '*' cannot be applied to operands of type 'object' and
// 'object'.
//sum += mixedList[j] * mixedList[j]);

// After the list elements are unboxed, the computation does


// not cause a compiler error.
sum += (int)mixedList[j] * (int)mixedList[j];
}

// The sum displayed is 30, the sum of 1 + 4 + 9 + 16.


Console.WriteLine("Sum: " + sum);

// Output:
// Answer42True
// First Group:
// 1
// 2
// 3
// 4
// Second Group:
// 5
// 6
// 7
// 8
// 9
// Sum: 30

Desempenho
Em relação às atribuições simples, as conversões boxing e unboxing são processos
computacionalmente dispendiosos. Quando um tipo de valor é submetido à conversão
boxing, um novo objeto deve ser alocado e construído. A um grau menor, a conversão
necessária para a conversão unboxing também é computacionalmente dispendiosa.
Para obter mais informações, consulte Desempenho.

Conversão boxing
A conversão boxing é usada para armazenar tipos de valor no heap coletado como lixo.
A conversão boxing é uma conversão implícita de um tipo de valor para o tipo object
ou para qualquer tipo de interface implementada por esse tipo de valor. A conversão
boxing de um tipo de valor aloca uma instância de objeto no heap e copia o valor no
novo objeto.

Considere a seguinte declaração de uma variável de tipo de valor:

C#

int i = 123;

A instrução a seguir aplica implicitamente a operação de conversão boxing na variável


i:

C#

// Boxing copies the value of i into object o.


object o = i;

O resultado dessa instrução é a criação de uma referência de objeto o , na pilha, que faz
referência a um valor do tipo int , no heap. Esse valor é uma cópia do valor do tipo de
valor atribuído à variável i . A diferença entre as duas variáveis, i e o , é ilustrada na
figura de conversão boxing a seguir:

Também é possível executar a conversão boxing explicitamente como no exemplo a


seguir, mas a conversão boxing explícita nunca é necessária:

C#

int i = 123;
object o = (object)i; // explicit boxing

Exemplo
Este exemplo converte uma variável de inteiro i em um objeto o usando a conversão
boxing. Em seguida, o valor armazenado na variável i é alterado de 123 para 456 . O
exemplo mostra que o tipo do valor original e o objeto submetido à conversão boxing
usa locais de memória separados e, portanto, pode armazenar valores diferentes.

C#

class TestBoxing
{
static void Main()
{
int i = 123;

// Boxing copies the value of i into object o.


object o = i;

// Change the value of i.


i = 456;

// The change in i doesn't affect the value stored in o.


System.Console.WriteLine("The value-type value = {0}", i);
System.Console.WriteLine("The object-type value = {0}", o);
}
}
/* Output:
The value-type value = 456
The object-type value = 123
*/

Conversão unboxing
A conversão unboxing é uma conversão explícita do tipo object para um tipo de valor
ou de um tipo de interface para um tipo de valor que implementa a interface. Uma
operação de conversão unboxing consiste em:

Verificar a instância do objeto para garantir que ele é um valor da conversão


boxing de um determinado tipo de valor.

Copiar o valor da instância para a variável de tipo de valor.

As instruções a seguir demonstram operações conversão boxing e unboxing:

C#

int i = 123; // a value type


object o = i; // boxing
int j = (int)o; // unboxing

A figura a seguir demonstra o resultado das instruções anteriores:

Para a conversão unboxing de tipos de valor ter êxito em tempo de execução, o item
sendo submetido à conversão unboxing deve ser uma referência para um objeto que foi
criado anteriormente ao realizar a conversão boxing de uma instância desse tipo de
valor. Tentar realizar a conversão unboxing de null causa uma NullReferenceException.
Tentar realizar a conversão unboxing de uma referência para um tipo de valor
incompatível causa uma InvalidCastException.

Exemplo
O exemplo a seguir demonstra um caso de conversão unboxing inválida e o
InvalidCastException resultante. Usando try e catch , uma mensagem de erro é
exibida quando o erro ocorre.

C#

class TestUnboxing
{
static void Main()
{
int i = 123;
object o = i; // implicit boxing

try
{
int j = (short)o; // attempt to unbox

System.Console.WriteLine("Unboxing OK.");
}
catch (System.InvalidCastException e)
{
System.Console.WriteLine("{0} Error: Incorrect unboxing.",
e.Message);
}
}
}

Este programa produz:

Specified cast is not valid. Error: Incorrect unboxing.

Se você alterar a instrução:

C#

int j = (short)o;

para:

C#

int j = (int)o;

a conversão será executada e você receberá a saída:

Unboxing OK.
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
Guia de programação em C#
Tipos de referência
Tipos de valor
Como converter uma matriz de bytes
em um int (Guia de Programação em
C#)
Artigo • 07/04/2023

Este exemplo mostra como usar a classe BitConverter para converter uma matriz de
bytes em um int e de volta em uma matriz de bytes. Talvez você precise converter bytes
em um tipo de dados interno depois de ler bytes da rede, por exemplo. Além do
método ToInt32(Byte[], Int32) no exemplo, a tabela a seguir lista métodos na classe
BitConverter que convertem bytes (de uma matriz de bytes) em outros tipos internos.

Tipo retornado Método

bool ToBoolean(Byte[], Int32)

char ToChar(Byte[], Int32)

double ToDouble(Byte[], Int32)

short ToInt16(Byte[], Int32)

int ToInt32(Byte[], Int32)

long ToInt64(Byte[], Int32)

float ToSingle(Byte[], Int32)

ushort ToUInt16(Byte[], Int32)

uint ToUInt32(Byte[], Int32)

ulong ToUInt64(Byte[], Int32)

Exemplos
Este exemplo inicializa uma matriz de bytes, reverte a matriz se a arquitetura do
computador for little-endian (ou seja, se o byte menos significativo for armazenado
primeiro) e, em seguida, chama o método ToInt32(Byte[], Int32) para converter quatro
bytes da matriz em um int . O segundo argumento para ToInt32(Byte[], Int32) especifica
o índice de início da matriz de bytes.

7 Observação
A saída pode ser diferente dependendo da extremidade (ordenação dos bytes) da
arquitetura do computador.

C#

byte[] bytes = { 0, 0, 0, 25 };

// If the system architecture is little-endian (that is, little end first),


// reverse the byte array.
if (BitConverter.IsLittleEndian)
Array.Reverse(bytes);

int i = BitConverter.ToInt32(bytes, 0);


Console.WriteLine("int: {0}", i);
// Output: int: 25

Neste exemplo, o método GetBytes(Int32) da classe BitConverter é chamado para


converter um int em uma matriz de bytes.

7 Observação

A saída pode ser diferente dependendo da extremidade (ordenação dos bytes) da


arquitetura do computador.

C#

byte[] bytes = BitConverter.GetBytes(201805978);


Console.WriteLine("byte array: " + BitConverter.ToString(bytes));
// Output: byte array: 9A-50-07-0C

Confira também
BitConverter
IsLittleEndian
Types
Como converter uma cadeia de
caracteres em um número (Guia de
Programação em C#)
Artigo • 07/11/2024

Converta um string em um número chamando o método Parse ou TryParse


encontrado em tipos numéricos ( int , long , double e assim por diante) ou usando
métodos na classe System.Convert.

Será um pouco mais eficiente e simples chamar um método TryParse (por exemplo,
int.TryParse("11", out number)) ou o método Parse (por exemplo, var number =
int.Parse("11")). Usar um método Convert é mais útil para objetos gerais que
implementam IConvertible.

É possível usar métodos Parse ou TryParse no tipo numérico que se espera que a
cadeia de caracteres contenha, como o tipo System.Int32. O método Convert.ToInt32
usa Parse internamente. O método Parse retorna o número convertido; o método
TryParse retorna um valor booliano que indica se a conversão foi bem-sucedida e

retorna o número convertido em um parâmetro out . Se a cadeia de caracteres não


estiver em um formato válido, Parse lançará uma exceção, mas TryParse retornará
false . Ao chamar um método Parse , você sempre deve usar o tratamento de exceções

para capturar um FormatException quando a operação de análise falhar.

Chamar métodos Parse ou TryParse


Os métodos Parse e TryParse ignoram o espaço em branco no início e no final da
cadeia de caracteres; porém, todos os outros caracteres devem formar o tipo numérico
correto ( int , long , ulong , float , decimal e assim por diante). Qualquer espaço em
branco na cadeia de caracteres que forma o número causa um erro. Por exemplo, é
possível usar decimal.TryParse para analisar "10", "10,3" ou " 10 ", mas não é possível
usar esse método para analisar 10 de "10X", "1 0" (observe o espaço inserido), "10 .3"
(observe o espaço inserido), "10e1" ( float.TryParse funcionará neste caso) e assim por
diante. Uma cadeia de caracteres cujo valor seja null ou String.Empty falhará ao ser
analisada com êxito. Você pode verificar uma cadeia de caracteres nula ou vazia antes
de tentar analisá-la chamando o método String.IsNullOrEmpty.

O exemplo a seguir demonstra chamadas com e sem êxito para Parse e TryParse .
C#

using System;

public static class StringConversion


{
public static void Main()
{
string input = String.Empty;
try
{
int result = Int32.Parse(input);
Console.WriteLine(result);
}
catch (FormatException)
{
Console.WriteLine($"Unable to parse '{input}'");
}
// Output: Unable to parse ''

try
{
int numVal = Int32.Parse("-105");
Console.WriteLine(numVal);
}
catch (FormatException e)
{
Console.WriteLine(e.Message);
}
// Output: -105

if (Int32.TryParse("-105", out int j))


{
Console.WriteLine(j);
}
else
{
Console.WriteLine("String could not be parsed.");
}
// Output: -105

try
{
int m = Int32.Parse("abc");
}
catch (FormatException e)
{
Console.WriteLine(e.Message);
}
// Output: Input string was not in a correct format.

const string inputString = "abc";


if (Int32.TryParse(inputString, out int numValue))
{
Console.WriteLine(numValue);
}
else
{
Console.WriteLine($"Int32.TryParse could not parse
'{inputString}' to an int.");
}
// Output: Int32.TryParse could not parse 'abc' to an int.
}
}

O exemplo a seguir ilustra uma abordagem para analisar uma cadeia de caracteres que
deve incluir caracteres numéricos à esquerda (incluindo caracteres hexadecimais) e
caracteres não numéricos à direita. Ele atribui caracteres válidos do início de uma cadeia
de caracteres até uma nova cadeia de caracteres antes de chamar o método TryParse.
Como as cadeias de caracteres a ser analisadas contêm poucos caracteres, o exemplo
chama o método String.Concat para atribuir os caracteres válidos a uma nova cadeia de
caracteres. Para cadeias de caracteres maiores, pode ser usada a classe StringBuilder.

C#

using System;

public static class StringConversion


{
public static void Main()
{
var str = " 10FFxxx";
string numericString = string.Empty;
foreach (var c in str)
{
// Check for numeric characters (hex in this case) or leading or
trailing spaces.
if ((c >= '0' && c <= '9') || (char.ToUpperInvariant(c) >= 'A'
&& char.ToUpperInvariant(c) <= 'F') || c == ' ')
{
numericString = string.Concat(numericString, c.ToString());
}
else
{
break;
}
}

if (int.TryParse(numericString,
System.Globalization.NumberStyles.HexNumber, null, out int i))
{
Console.WriteLine($"'{str}' --> '{numericString}' --> {i}");
}
// Output: ' 10FFxxx' --> ' 10FF' --> 4351
str = " -10FFXXX";
numericString = "";
foreach (char c in str)
{
// Check for numeric characters (0-9), a negative sign, or
leading or trailing spaces.
if ((c >= '0' && c <= '9') || c == ' ' || c == '-')
{
numericString = string.Concat(numericString, c);
}
else
{
break;
}
}

if (int.TryParse(numericString, out int j))


{
Console.WriteLine($"'{str}' --> '{numericString}' --> {j}");
}
// Output: ' -10FFXXX' --> ' -10' --> -10
}
}

Chamar métodos Convert


A tabela a seguir lista alguns dos métodos da classe Convert que podem ser usados
para converter uma cadeia de caracteres em um número.

ノ Expandir a tabela

Tipo numérico Método

decimal ToDecimal(String)

float ToSingle(String)

double ToDouble(String)

short ToInt16(String)

int ToInt32(String)

long ToInt64(String)

ushort ToUInt16(String)

uint ToUInt32(String)

ulong ToUInt64(String)
O exemplo a seguir chama o método Convert.ToInt32(String) para converter uma cadeia
de caracteres de entrada em um int. O exemplo captura as duas exceções mais comuns
geradas por esse método: FormatException e OverflowException. Se o número
resultante puder ser incrementado sem exceder Int32.MaxValue, o exemplo adicionará 1
ao resultado e exibirá a saída.

C#

using System;

public class ConvertStringExample1


{
static void Main(string[] args)
{
int numVal = -1;
bool repeat = true;

while (repeat)
{
Console.Write("Enter a number between −2,147,483,648 and
+2,147,483,647 (inclusive): ");

string? input = Console.ReadLine();

// ToInt32 can throw FormatException or OverflowException.


try
{
numVal = Convert.ToInt32(input);
if (numVal < Int32.MaxValue)
{
Console.WriteLine("The new value is {0}", ++numVal);
}
else
{
Console.WriteLine("numVal cannot be incremented beyond
its current value");
}
}
catch (FormatException)
{
Console.WriteLine("Input string is not a sequence of
digits.");
}
catch (OverflowException)
{
Console.WriteLine("The number cannot fit in an Int32.");
}

Console.Write("Go again? Y/N: ");


string? go = Console.ReadLine();
if (go?.ToUpper() != "Y")
{
repeat = false;
}
}
}
}
// Sample Output:
// Enter a number between -2,147,483,648 and +2,147,483,647 (inclusive):
473
// The new value is 474
// Go again? Y/N: y
// Enter a number between -2,147,483,648 and +2,147,483,647 (inclusive):
2147483647
// numVal cannot be incremented beyond its current value
// Go again? Y/N: y
// Enter a number between -2,147,483,648 and +2,147,483,647 (inclusive):
-1000
// The new value is -999
// Go again? Y/N: n

Usar o GitHub Copilot para converter uma


string em um número
Você pode usar o GitHub Copilot no IDE para gerar código para converter uma string
em um número em C#.

Se você estiver usando o Visual Studio 2022 versão 17.8 ou posterior, poderá
experimentar o GitHub Copilot controlado por IA no Visual Studio para gerar código
para converter uma cadeia de caracteres em um número. Envie sua pergunta como um
prompt na janela de bate-papo do Copilot, como no exemplo a seguir. Você também
pode enviar prompts usando o bate-papo em linha na própria janela do editor.

7 Observação

O GitHub Copilot é alimentado por IA, portanto é passível de surpresas e erros.


Verifique qualquer código gerado ou sugestões. Para obter mais informações sobre
o uso geral do GitHub Copilot, o impacto do produto, a supervisão humana e a
privacidade, consulte Perguntas frequentes sobre o GitHub Copilot .

O texto a seguir mostra um exemplo de prompt para o Copilot Chat:

Gere código C# para converter a cadeia de caracteres "123" em um número usando


TryParse. Forneça um exemplo de saída.

Você pode personalizar o prompt para usar uma cadeia de caracteres de acordo com
seus requisitos.
Você pode usar recursos de bate-papo, como comandos de barra, referências a
arquivos, métodos ou classes e threads, para definir a intenção e obter melhores
respostas com o contexto com escopo. Para um arquivo existente que está aberto no
IDE, você pode solicitar o GitHub Copilot usando o chat embutido com /generate code
to convert the string string1 in #Filename to a number o .

A saída a seguir mostra um exemplo de resposta do Copilot Chat:

Saída

Here is a C# code snippet to convert the string "123" to a number using


TryParse:

using System;

class Program
{
static void Main()
{
string numberString = "123";
int number;
bool success = int.TryParse(numberString, out number);

if (success)
{
// Example output
Console.WriteLine("The converted number is: " + number);
}
else
{
Console.WriteLine("Conversion failed.");
}
}
}

When you run this code, the output will be:


The converted number is: 123

Quando o Copilot retorna um bloco de código, a resposta inclui opções para copiar o
código, inserir o código em um novo arquivo ou visualizar a saída do código.

7 Observação

Seus resultados podem ser diferentes do que é mostrado nas respostas de


exemplo. Os modelos de IA não são determinísticos, ou seja, eles podem retornar
respostas diferentes quando fazem a mesma pergunta. Isso pode ser devido a
aprendizado e adaptação adicionais ao longo do tempo, variação de idioma,
mudanças no contexto, como seu histórico de bate-papo e muito mais.

Para saber mais, veja:

Central de Confiabilidade do GitHub Copilot


GitHub Copilot no Visual Studio
GitHub Copilot no VS Code
Como converter entre cadeias de
caracteres hexadecimais e tipos
numéricos (Guia de Programação em
C#)
Artigo • 10/05/2023

Estes exemplos mostram como realizar as seguintes tarefas:

Obter o valor hexadecimal de cada caractere em uma cadeia de caracteres.

Obter o char que corresponde a cada valor em uma cadeia de caracteres


hexadecimal.

Converter um string hexadecimal em um int.

Converter um string hexadecimal em um float.

Como converter uma matriz de bytes em um string hexadecimal.

Exemplos
Este exemplo gera o valor hexadecimal de cada caractere em um string . Primeiro, ele
analisa o string como uma matriz de caracteres. Em seguida, ele chama ToInt32(Char)
em cada caractere para obter seu valor numérico. Por fim, ele formata o número como
sua representação hexadecimal em um string .

C#

string input = "Hello World!";


char[] values = input.ToCharArray();
foreach (char letter in values)
{
// Get the integral value of the character.
int value = Convert.ToInt32(letter);
// Convert the integer value to a hexadecimal value in string form.
Console.WriteLine($"Hexadecimal value of {letter} is {value:X}");
}
/* Output:
Hexadecimal value of H is 48
Hexadecimal value of e is 65
Hexadecimal value of l is 6C
Hexadecimal value of l is 6C
Hexadecimal value of o is 6F
Hexadecimal value of is 20
Hexadecimal value of W is 57
Hexadecimal value of o is 6F
Hexadecimal value of r is 72
Hexadecimal value of l is 6C
Hexadecimal value of d is 64
Hexadecimal value of ! is 21
*/

Este exemplo analisa um string de valores hexadecimais e gera o caractere


correspondente a cada valor hexadecimal. Primeiro, ele chama o método Split(Char[])
para obter cada valor hexadecimal como um string individual em uma matriz. Em
seguida, ele chama ToInt32(String, Int32) para converter o valor hexadecimal em um
valor decimal representado como um int. Ele mostra duas maneiras diferentes de obter
o caractere correspondente a esse código de caractere. A primeira técnica usa
ConvertFromUtf32(Int32), que retorna o caractere correspondente ao argumento de
inteiro como um string . A segunda técnica converte explicitamente o int em um char.

C#

string hexValues = "48 65 6C 6C 6F 20 57 6F 72 6C 64 21";


string[] hexValuesSplit = hexValues.Split(' ');
foreach (string hex in hexValuesSplit)
{
// Convert the number expressed in base-16 to an integer.
int value = Convert.ToInt32(hex, 16);
// Get the character corresponding to the integral value.
string stringValue = Char.ConvertFromUtf32(value);
char charValue = (char)value;
Console.WriteLine("hexadecimal value = {0}, int value = {1}, char value
= {2} or {3}",
hex, value, stringValue, charValue);
}
/* Output:
hexadecimal value = 48, int value = 72, char value = H or H
hexadecimal value = 65, int value = 101, char value = e or e
hexadecimal value = 6C, int value = 108, char value = l or l
hexadecimal value = 6C, int value = 108, char value = l or l
hexadecimal value = 6F, int value = 111, char value = o or o
hexadecimal value = 20, int value = 32, char value = or
hexadecimal value = 57, int value = 87, char value = W or W
hexadecimal value = 6F, int value = 111, char value = o or o
hexadecimal value = 72, int value = 114, char value = r or r
hexadecimal value = 6C, int value = 108, char value = l or l
hexadecimal value = 64, int value = 100, char value = d or d
hexadecimal value = 21, int value = 33, char value = ! or !
*/
Este exemplo mostra outra maneira de converter um hexadecimal string em um
inteiro, chamando o método Parse(String, NumberStyles).

C#

string hexString = "8E2";


int num = Int32.Parse(hexString,
System.Globalization.NumberStyles.HexNumber);
Console.WriteLine(num);
//Output: 2274

O exemplo a seguir mostra como converter um hexadecimal string para um float


usando a classe System.BitConverter e o método UInt32.Parse.

C#

string hexString = "43480170";


uint num = uint.Parse(hexString,
System.Globalization.NumberStyles.AllowHexSpecifier);

byte[] floatVals = BitConverter.GetBytes(num);


float f = BitConverter.ToSingle(floatVals, 0);
Console.WriteLine("float convert = {0}", f);

// Output: 200.0056

O exemplo a seguir mostra como converter uma matriz de bytes em uma cadeia de
caracteres hexadecimal usando a classe System.BitConverter.

C#

byte[] vals = { 0x01, 0xAA, 0xB1, 0xDC, 0x10, 0xDD };

string str = BitConverter.ToString(vals);


Console.WriteLine(str);

str = BitConverter.ToString(vals).Replace("-", "");


Console.WriteLine(str);

/*Output:
01-AA-B1-DC-10-DD
01AAB1DC10DD
*/

O exemplo a seguir mostra como converter uma matriz de byte em uma cadeia de
caracteres hexadecimal chamando o método Convert.ToHexString, introduzido no .NET
5.0.
C#

byte[] array = { 0x64, 0x6f, 0x74, 0x63, 0x65, 0x74 };

string hexValue = Convert.ToHexString(array);


Console.WriteLine(hexValue);

/*Output:
646F74636574
*/

Confira também
Cadeias de Caracteres de Formato Numérico Padrão
Types
Como determinar se uma cadeia de caracteres representa um valor numérico
Controle de versão com as palavras-
chave override e new (Guia de
Programação em C#)
Artigo • 07/04/2023

A linguagem C# foi projetada para que o controle de versão entre classes derivadas e
base em diferentes bibliotecas possa evoluir e manter a compatibilidade com versões
anteriores. Isso significa, por exemplo, que a introdução de um novo membro em uma
classe base com o mesmo nome que um membro em uma classe derivada tem suporte
completo pelo C# e não leva a comportamento inesperado. Isso também significa que
uma classe deve declarar explicitamente se um método destina-se a substituir um
método herdado ou se um método é um novo método que oculta um método herdado
de nome semelhante.

No C#, as classes derivadas podem conter métodos com o mesmo nome que os
métodos da classe base.

Se o método na classe derivada não for precedido pelas palavras-chave new ou


override, o compilador emitirá um aviso e o método se comportará como se a
palavra-chave new estivesse presente.

Se o método na classe derivada for precedido pela palavra-chave new , o método


será definido como sendo independente do método na classe base.

Se o método na classe derivada for precedido pela palavra-chave override , os


objetos da classe derivada chamarão esse método em vez do método da classe
base.

Para aplicar a palavra-chave override ao método na classe derivada, o método da


classe base deve ser definido como virtual.

O método da classe base pode ser chamado de dentro da classe derivada usando
a palavra-chave base .

As palavras-chave override , virtual e new também podem ser aplicadas a


propriedades, indexadores e eventos.

Por padrão, os métodos C# não são virtuais. Se um método for declarado como virtual,
qualquer classe que herdar o método pode implementar sua própria versão. Para tornar
um método virtual, o modificador virtual é usado na declaração de método da classe
base. A classe derivada pode, em seguida, substituir o método virtual base usando a
palavra-chave override ou ocultar o método virtual na classe base usando a palavra-
chave new . Se nem a palavra-chave override nem a new for especificada, o compilador
emitirá um aviso e o método na classe derivada ocultará o método na classe base.

Para demonstrar isso na prática, suponha por um momento que a Empresa A tenha
criado uma classe chamada GraphicsClass , que seu programa usa. A seguir está
GraphicsClass :

C#

class GraphicsClass
{
public virtual void DrawLine() { }
public virtual void DrawPoint() { }
}

Sua empresa usa essa classe e você a usa para derivar sua própria classe, adicionando
um novo método:

C#

class YourDerivedGraphicsClass : GraphicsClass


{
public void DrawRectangle() { }
}

Seu aplicativo é usado sem problemas, até a Empresa A lançar uma nova versão de
GraphicsClass , que se parece com o seguinte código:

C#

class GraphicsClass
{
public virtual void DrawLine() { }
public virtual void DrawPoint() { }
public virtual void DrawRectangle() { }
}

A nova versão de GraphicsClass agora contém um método chamado DrawRectangle .


Inicialmente, nada ocorre. A nova versão ainda é compatível em relação ao binário com
a versão antiga. Qualquer software que você implantou continuará a funcionar, mesmo
se a nova classe for instalada nesses sistemas de computador. Todas as chamadas
existentes para o método DrawRectangle continuarão a fazer referência à sua versão, em
sua classe derivada.
No entanto, assim que você recompilar seu aplicativo usando a nova versão do
GraphicsClass , você receberá um aviso do compilador, CS0108. Este aviso informa que
você deve considerar como deseja que seu método DrawRectangle se comporte em seu
aplicativo.

Se desejar que seu método substitua o novo método de classe base, use a palavra-
chave override :

C#

class YourDerivedGraphicsClass : GraphicsClass


{
public override void DrawRectangle() { }
}

A palavra-chave override garante que todos os objetos derivados de


YourDerivedGraphicsClass usarão a versão da classe derivada de DrawRectangle . Os

objetos derivados de YourDerivedGraphicsClass ainda poderão acessar a versão da


classe base de DrawRectangle usando a palavra-chave base:

C#

base.DrawRectangle();

Se você não quiser que seu método substitua o novo método de classe base, as
seguintes considerações se aplicam. Para evitar confusão entre os dois métodos, você
pode renomear seu método. Isso pode ser demorado e propenso a erros e
simplesmente não ser prático em alguns casos. No entanto, se seu projeto for
relativamente pequeno, você poderá usar opções de Refatoração do Visual Studio para
renomear o método. Para obter mais informações, consulte Refatorando classes e tipos
(Designer de Classe).

Como alternativa, você pode evitar o aviso usando a palavra-chave new na definição da
classe derivada:

C#

class YourDerivedGraphicsClass : GraphicsClass


{
public new void DrawRectangle() { }
}
Usando a palavra-chave new informa ao compilador que sua definição oculta a
definição que está contida na classe base. Esse é o comportamento padrão.

Seleção de método e substituição


Quando um método é chamado em uma classe, o compilador C# seleciona o melhor
método a ser chamado se mais de um método for compatível com a chamada, como
quando há dois métodos com o mesmo nome e parâmetros que são compatíveis com o
parâmetro passado. Os métodos a seguir seriam compatíveis:

C#

public class Derived : Base


{
public override void DoWork(int param) { }
public void DoWork(double param) { }
}

Quando DoWork é chamado em uma instância do Derived , o compilador C# tenta


primeiro tornar a chamada compatível com as versões do DoWork originalmente
declarado em Derived . Os métodos de substituição não são considerados como
declarados em uma classe, eles são novas implementações de um método declarado em
uma classe base. Somente se o compilador C# não puder corresponder a chamada de
método a um método original em Derived é que tentará corresponder a chamada a um
método substituído com o mesmo nome e parâmetros compatíveis. Por exemplo:

C#

int val = 5;
Derived d = new Derived();
d.DoWork(val); // Calls DoWork(double).

Como a variável val pode ser convertida para um duplo implicitamente, o compilador
C# chama DoWork(double) em vez de DoWork(int) . Há duas formas de evitar isso.
Primeiro, evite declarar novos métodos com o mesmo nome que os métodos virtuais.
Segundo, você pode instruir o compilador C# para chamar o método virtual fazendo-o
pesquisar a lista do método de classe base convertendo a instância do Derived para
Base . Como o método é virtual, a implementação de DoWork(int) em Derived será

chamada. Por exemplo:

C#
((Base)d).DoWork(val); // Calls DoWork(int) on Derived.

Para obter mais exemplos de new e override , consulte Quando usar as palavras-chave
override e new.

Confira também
Guia de Programação em C#
Sistema de tipos do C#
Métodos
Herança
Quando usar as palavras-chave override
e new (Guia de Programação em C#)
Artigo • 07/04/2023

No C#, um método em uma classe derivada pode ter o mesmo nome que um método
na classe base. É possível especificar a maneira como os métodos interagem usando as
palavras-chave new e override. O modificador override estende o método virtual da
classe base e o modificador new oculta um método de classe base acessível. A diferença é
ilustrada nos exemplos deste tópico.

Em um aplicativo de console, declare as duas classes a seguir, BaseClass e


DerivedClass . DerivedClass herda de BaseClass .

C#

class BaseClass
{
public void Method1()
{
Console.WriteLine("Base - Method1");
}
}

class DerivedClass : BaseClass


{
public void Method2()
{
Console.WriteLine("Derived - Method2");
}
}

No método Main , declare as variáveis bc , dc e bcdc .

bc é do tipo BaseClass e seu valor é do tipo BaseClass .

dc é do tipo DerivedClass e seu valor é do tipo DerivedClass .

bcdc é do tipo BaseClass e seu valor é do tipo DerivedClass . Essa é a variável à


qual você deve prestar atenção.

Como bc e bcdc têm o tipo BaseClass , eles podem ter acesso direto a Method1 , a
menos que você usa a conversão. A variável dc pode acessar Method1 e Method2 . Essas
relações são mostradas no código a seguir.
C#

class Program
{
static void Main(string[] args)
{
BaseClass bc = new BaseClass();
DerivedClass dc = new DerivedClass();
BaseClass bcdc = new DerivedClass();

bc.Method1();
dc.Method1();
dc.Method2();
bcdc.Method1();
}
// Output:
// Base - Method1
// Base - Method1
// Derived - Method2
// Base - Method1
}

Em seguida, adicione o seguinte método Method2 a BaseClass . A assinatura desse


método corresponde à assinatura do método Method2 em DerivedClass .

C#

public void Method2()


{
Console.WriteLine("Base - Method2");
}

Como BaseClass agora tem um método Method2 , uma segunda instrução de chamada
pode ser adicionada para variáveis de BaseClass bc e bcdc , conforme mostrado no
código a seguir.

C#

bc.Method1();
bc.Method2();
dc.Method1();
dc.Method2();
bcdc.Method1();
bcdc.Method2();

Quando você compilar o projeto, verá que a adição do método Method2 gera um aviso
BaseClass . O aviso informa que o método Method2 em DerivedClass oculta o método
Method2 em BaseClass . É recomendável usar a palavra-chave new na definição Method2
se você pretende gerar esse resultado. Como alternativa, seria possível renomear um
dos métodos Method2 para resolver o aviso, mas isso nem sempre é prático.

Antes de adicionar new , execute o programa para ver a saída produzida pelas outras
instruções de chamada. Os seguintes resultados são exibidos.

C#

// Output:
// Base - Method1
// Base - Method2
// Base - Method1
// Derived - Method2
// Base - Method1
// Base - Method2

A palavra-chave new preserva as relações que produzem essa saída, mas suprime o
aviso. As variáveis que têm o tipo BaseClass continuam acessando os membros de
BaseClass e a variável que tem o tipo DerivedClass continua acessando os membros

em DerivedClass primeiro e, em seguida, considera os membros herdados de


BaseClass .

Para suprimir o aviso, adicione o modificador new para a definição de Method2 em


DerivedClass , conforme mostrado no código a seguir. O modificador pode ser
adicionado antes ou depois de public .

C#

public new void Method2()


{
Console.WriteLine("Derived - Method2");
}

Execute o programa novamente para verificar se a saída não foi alterada. Verifique
também se o aviso não é mais exibido. Usando new , você está declarando que está
ciente de que o membro que ele modifica oculta um membro herdado da classe base.
Para obter mais informações sobre a ocultação de nome por meio de herança, consulte
Novo modificador.

Para comparar esse comportamento com os efeitos de usar override , adicione o


seguinte método a DerivedClass . O modificador override pode ser adicionado antes
ou depois de public .

C#
public override void Method1()
{
Console.WriteLine("Derived - Method1");
}

Adicione o modificador virtual à definição de Method1 em BaseClass . O modificador


virtual pode ser adicionado antes ou depois de public .

C#

public virtual void Method1()


{
Console.WriteLine("Base - Method1");
}

Execute o projeto novamente. Observe principalmente as duas últimas linhas da saída a


seguir.

C#

// Output:
// Base - Method1
// Base - Method2
// Derived - Method1
// Derived - Method2
// Derived - Method1
// Base - Method2

O uso do modificador override permite que bcdc acesse o método Method1 definido
em DerivedClass . Normalmente, esse é o comportamento desejado em hierarquias de
herança. Você quer objetos com valores criados da classe derivada para usar os
métodos definidos na classe derivada. Obtenha esse comportamento usando override
para estender o método da classe base.

O código a seguir contem o exemplo completo.

C#

using System;
using System.Text;

namespace OverrideAndNew
{
class Program
{
static void Main(string[] args)
{
BaseClass bc = new BaseClass();
DerivedClass dc = new DerivedClass();
BaseClass bcdc = new DerivedClass();

// The following two calls do what you would expect. They call
// the methods that are defined in BaseClass.
bc.Method1();
bc.Method2();
// Output:
// Base - Method1
// Base - Method2

// The following two calls do what you would expect. They call
// the methods that are defined in DerivedClass.
dc.Method1();
dc.Method2();
// Output:
// Derived - Method1
// Derived - Method2

// The following two calls produce different results, depending


// on whether override (Method1) or new (Method2) is used.
bcdc.Method1();
bcdc.Method2();
// Output:
// Derived - Method1
// Base - Method2
}
}

class BaseClass
{
public virtual void Method1()
{
Console.WriteLine("Base - Method1");
}

public virtual void Method2()


{
Console.WriteLine("Base - Method2");
}
}

class DerivedClass : BaseClass


{
public override void Method1()
{
Console.WriteLine("Derived - Method1");
}

public new void Method2()


{
Console.WriteLine("Derived - Method2");
}
}
}

O exemplo a seguir ilustra um comportamento semelhante em um contexto diferente. O


exemplo define três classes: uma classe base chamada Car e duas classes derivadas
dela, ConvertibleCar e Minivan . A classe base contém um método DescribeCar . O
método exibe uma descrição básica de um carro e, em seguida, chama ShowDetails
para fornecer mais informações. Cada uma das três classes define um método
ShowDetails . O modificador new é usado para definir ShowDetails na classe

ConvertibleCar . O modificador override é usado para definir ShowDetails na classe


Minivan .

C#

// Define the base class, Car. The class defines two methods,
// DescribeCar and ShowDetails. DescribeCar calls ShowDetails, and each
derived
// class also defines a ShowDetails method. The example tests which version
of
// ShowDetails is selected, the base class method or the derived class
method.
class Car
{
public void DescribeCar()
{
System.Console.WriteLine("Four wheels and an engine.");
ShowDetails();
}

public virtual void ShowDetails()


{
System.Console.WriteLine("Standard transportation.");
}
}

// Define the derived classes.

// Class ConvertibleCar uses the new modifier to acknowledge that


ShowDetails
// hides the base class method.
class ConvertibleCar : Car
{
public new void ShowDetails()
{
System.Console.WriteLine("A roof that opens up.");
}
}

// Class Minivan uses the override modifier to specify that ShowDetails


// extends the base class method.
class Minivan : Car
{
public override void ShowDetails()
{
System.Console.WriteLine("Carries seven people.");
}
}

O exemplo testa qual versão do ShowDetails é chamada. O método a seguir, TestCars1 ,


declara uma instância de cada classe e, em seguida, chama DescribeCar em cada
instância.

C#

public static void TestCars1()


{
System.Console.WriteLine("\nTestCars1");
System.Console.WriteLine("----------");

Car car1 = new Car();


car1.DescribeCar();
System.Console.WriteLine("----------");

// Notice the output from this test case. The new modifier is
// used in the definition of ShowDetails in the ConvertibleCar
// class.

ConvertibleCar car2 = new ConvertibleCar();


car2.DescribeCar();
System.Console.WriteLine("----------");

Minivan car3 = new Minivan();


car3.DescribeCar();
System.Console.WriteLine("----------");
}

TestCars1 produz a saída a seguir. Observe principalmente os resultados para car2 ,

que provavelmente não são o que você espera. O tipo do objeto é ConvertibleCar , mas
DescribeCar não acessa a versão de ShowDetails definida na classe ConvertibleCar ,
porque esse método é declarado com o modificador new , e não com o modificador
override . Em decorrência disso, um objeto ConvertibleCar exibe a mesma descrição
que um objeto Car . Compare os resultados de car3 , que é um objeto Minivan . Nesse
caso, o método ShowDetails declarado na classe Minivan substitui o método
ShowDetails declarado na classe Car e a descrição exibida descreve uma minivan.

C#
// TestCars1
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Carries seven people.
// ----------

TestCars2 cria uma lista de objetos que têm tipo Car . Os valores dos objetos são
instanciados com base nas classes Car , ConvertibleCar e Minivan . DescribeCar é
chamado em cada elemento da lista. O código a seguir mostra a definição de
TestCars2 .

C#

public static void TestCars2()


{
System.Console.WriteLine("\nTestCars2");
System.Console.WriteLine("----------");

var cars = new List<Car> { new Car(), new ConvertibleCar(),


new Minivan() };

foreach (var car in cars)


{
car.DescribeCar();
System.Console.WriteLine("----------");
}
}

É exibida a saída a seguir. Observe que é a mesma que a saída exibida por TestCars1 . O
método ShowDetails da classe ConvertibleCar não é chamado, independentemente se
o tipo do objeto é ConvertibleCar , como em TestCars1 ou Car , como em TestCars2 .
Por outro lado, car3 chama o método ShowDetails com base na classe Minivan nos
dois casos, tendo ele o tipo Minivan ou o tipo Car .

C#

// TestCars2
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Carries seven people.
// ----------

Os métodos TestCars3 e TestCars4 completam o exemplo. Esses métodos chamam


ShowDetails diretamente, primeiro com base nos objetos declarados para ter o tipo
ConvertibleCar e Minivan ( TestCars3 ), em seguida, com base nos objetos declarados

para ter o tipo Car ( TestCars4 ). O código a seguir define esses dois métodos.

C#

public static void TestCars3()


{
System.Console.WriteLine("\nTestCars3");
System.Console.WriteLine("----------");
ConvertibleCar car2 = new ConvertibleCar();
Minivan car3 = new Minivan();
car2.ShowDetails();
car3.ShowDetails();
}

public static void TestCars4()


{
System.Console.WriteLine("\nTestCars4");
System.Console.WriteLine("----------");
Car car2 = new ConvertibleCar();
Car car3 = new Minivan();
car2.ShowDetails();
car3.ShowDetails();
}

Os métodos produzem a saída a seguir, que corresponde aos resultados do primeiro


exemplo neste tópico.

C#

// TestCars3
// ----------
// A roof that opens up.
// Carries seven people.

// TestCars4
// ----------
// Standard transportation.
// Carries seven people.
O código a seguir mostra o projeto completo e sua saída.

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace OverrideAndNew2
{
class Program
{
static void Main(string[] args)
{
// Declare objects of the derived classes and test which version
// of ShowDetails is run, base or derived.
TestCars1();

// Declare objects of the base class, instantiated with the


// derived classes, and repeat the tests.
TestCars2();

// Declare objects of the derived classes and call ShowDetails


// directly.
TestCars3();

// Declare objects of the base class, instantiated with the


// derived classes, and repeat the tests.
TestCars4();
}

public static void TestCars1()


{
System.Console.WriteLine("\nTestCars1");
System.Console.WriteLine("----------");

Car car1 = new Car();


car1.DescribeCar();
System.Console.WriteLine("----------");

// Notice the output from this test case. The new modifier is
// used in the definition of ShowDetails in the ConvertibleCar
// class.
ConvertibleCar car2 = new ConvertibleCar();
car2.DescribeCar();
System.Console.WriteLine("----------");

Minivan car3 = new Minivan();


car3.DescribeCar();
System.Console.WriteLine("----------");
}
// Output:
// TestCars1
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Carries seven people.
// ----------

public static void TestCars2()


{
System.Console.WriteLine("\nTestCars2");
System.Console.WriteLine("----------");

var cars = new List<Car> { new Car(), new ConvertibleCar(),


new Minivan() };

foreach (var car in cars)


{
car.DescribeCar();
System.Console.WriteLine("----------");
}
}
// Output:
// TestCars2
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Carries seven people.
// ----------

public static void TestCars3()


{
System.Console.WriteLine("\nTestCars3");
System.Console.WriteLine("----------");
ConvertibleCar car2 = new ConvertibleCar();
Minivan car3 = new Minivan();
car2.ShowDetails();
car3.ShowDetails();
}
// Output:
// TestCars3
// ----------
// A roof that opens up.
// Carries seven people.

public static void TestCars4()


{
System.Console.WriteLine("\nTestCars4");
System.Console.WriteLine("----------");
Car car2 = new ConvertibleCar();
Car car3 = new Minivan();
car2.ShowDetails();
car3.ShowDetails();
}
// Output:
// TestCars4
// ----------
// Standard transportation.
// Carries seven people.
}

// Define the base class, Car. The class defines two virtual methods,
// DescribeCar and ShowDetails. DescribeCar calls ShowDetails, and each
derived
// class also defines a ShowDetails method. The example tests which
version of
// ShowDetails is used, the base class method or the derived class
method.
class Car
{
public virtual void DescribeCar()
{
System.Console.WriteLine("Four wheels and an engine.");
ShowDetails();
}

public virtual void ShowDetails()


{
System.Console.WriteLine("Standard transportation.");
}
}

// Define the derived classes.

// Class ConvertibleCar uses the new modifier to acknowledge that


ShowDetails
// hides the base class method.
class ConvertibleCar : Car
{
public new void ShowDetails()
{
System.Console.WriteLine("A roof that opens up.");
}
}

// Class Minivan uses the override modifier to specify that ShowDetails


// extends the base class method.
class Minivan : Car
{
public override void ShowDetails()
{
System.Console.WriteLine("Carries seven people.");
}
}

Confira também
Guia de Programação em C#
Sistema de tipos do C#
Controle de versão com as palavras-chave override e new
base
abstract
Como substituir o método ToString
(Guia de Programação em C#)
Artigo • 07/04/2023

Cada classe ou struct no C# herda implicitamente a classe Object. Portanto, cada objeto
no C# obtém o método ToString, que retorna uma representação de cadeia de
caracteres desse objeto. Por exemplo, todas as variáveis do tipo int tem um método
ToString , que permite retornar seus conteúdos como uma cadeia de caracteres:

C#

int x = 42;
string strx = x.ToString();
Console.WriteLine(strx);
// Output:
// 42

Ao criar uma classe ou struct personalizada, é necessário substituir o método ToString a


fim de fornecer informações sobre o tipo ao código cliente.

Para obter informações sobre como usar cadeias de caracteres de formato e outros
tipos de formatação personalizada com o método ToString , consulte Tipos de
Formatação.

) Importante

Ao decidir quais informações devem ser fornecidas por meio desse método,
considere se a classe ou struct será utilizado por código não confiável. Assegure-se
de que nenhuma informação que possa ser explorada por código mal-
intencionado seja fornecida.

Substituir o método ToString na classe ou struct:

1. Declare um método ToString com os seguintes modificadores e tipo retornado:

C#

public override string ToString(){}

2. Implemente o método para que ele retorne uma cadeia de caracteres.


O exemplo a seguir retorna o nome da classe, além dos dados específicos de uma
instância particular da classe.

C#

class Person
{
public string Name { get; set; }
public int Age { get; set; }

public override string ToString()


{
return "Person: " + Name + " " + Age;
}
}

É possível testar o método ToString , conforme mostrado no exemplo de código a


seguir:

C#

Person person = new Person { Name = "John", Age = 12 };


Console.WriteLine(person);
// Output:
// Person: John 12

Confira também
IFormattable
Guia de Programação em C#
O sistema de tipos C#
Cadeias de caracteres
cadeia de caracteres
override
virtual
Formatar tipos
Membros (Guia de Programação em C#)
Artigo • 13/03/2024

Classes e structs têm membros que representam seus dados e comportamento. Os


membros de uma classe incluem todos os membros declarados na classe, juntamente
com todos os membros (exceto construtores e finalizadores) declarados em todas as
classes em sua hierarquia de herança. Os membros privados em classes base são
herdados, mas não podem ser acessados de classes derivadas.

A tabela a seguir lista os tipos de membros que uma classe ou struct pode conter:

ノ Expandir a tabela

Membro DESCRIÇÃO

Fields Os campos são variáveis declaradas no escopo da classe. Um campo pode ser um
tipo numérico interno ou uma instância de outra classe. Por exemplo, uma classe
de calendário pode ter um campo que contém a data atual.

Constantes Constantes são campos cujo valor é definido em tempo de compilação e não
pode ser alterado.

Propriedades As propriedades são métodos de uma classe acessados como se fossem campos
dessa classe. Uma propriedade pode fornecer proteção para um campo de classe
para evitar que ele seja alterado sem o conhecimento do objeto.

Métodos Os métodos definem as ações que uma classe pode executar. Métodos podem
usar parâmetros que fornecem dados de entrada e retornar dados de saída por
meio de parâmetros. Os métodos também podem retornar um valor diretamente,
sem usar um parâmetro.

Eventos Os eventos fornecem notificações sobre ocorrências a outros objetos, como


cliques de botão ou a conclusão bem-sucedida de um método. Eventos são
definidos e disparados pelos delegados.

Operadores Os operadores sobrecarregados são considerados membros de tipo. Ao


sobrecarregar um operador, ele é definido como um método estático público em
um tipo. Para obter mais informações, consulte Sobrecarga de operador.

Indexadores Os indexadores permitem que um objeto seja indexado de maneira semelhante


às matrizes.

Construtores Os construtores são os métodos chamados quando o objeto é criado pela


primeira vez. Geralmente, eles são usados para inicializar os dados de um objeto.

Finalizadores Os finalizadores raramente são usados no C#. Eles são métodos chamados pelo
mecanismo de runtime quando o objeto está prestes a ser removido da memória.
Membro DESCRIÇÃO

Geralmente, eles são usados para garantir que recursos que devem ser liberados
sejam manipulados corretamente.

Tipos Os tipos aninhados são tipos declarados dentro de outro tipo. Geralmente, eles
aninhados são usados para descrever objetos utilizados somente pelos tipos que os contêm.

Confira também
Classes

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
Classes e membros de classes abstract e
sealed (Guia de Programação em C#)
Artigo • 07/04/2023

A palavra-chave abstract permite que você crie classes e membros de classe que estão
incompletos e devem ser implementados em uma classe derivada.

A palavra-chave sealed permite evitar a herança de uma classe ou de determinados


membros de classe que foram marcados anteriormente com virtual.

Classes abstratas e membros de classe


As classes podem ser declaradas como abstratas, colocando a palavra-chave abstract
antes da definição de classe. Por exemplo:

C#

public abstract class A


{
// Class members here.
}

Uma classe abstrata não pode ser instanciada. A finalidade de uma classe abstrata é
fornecer uma definição comum de uma classe base que pode ser compartilhada por
várias classes derivadas. Por exemplo, uma biblioteca de classes pode definir uma classe
abstrata que serve como um parâmetro para muitas de suas funções e exige que os
programadores que usam essa biblioteca forneçam sua própria implementação da
classe, criando uma classe derivada.

As classes abstratas também podem definir métodos abstratos. Isso é realizado através
da adição da palavra-chave abstract antes do tipo de retorno do método. Por exemplo:

C#

public abstract class A


{
public abstract void DoWork(int i);
}

Os métodos abstratos não têm implementação, portanto, a definição do método é


seguida por um ponto e vírgula, em vez de um bloco de método normal. As classes
derivadas da classe abstrata devem implementar todos os métodos abstratos. Quando
uma classe abstrata herda um método virtual de uma classe base, a classe abstrata pode
substituir o método virtual por um método abstrato. Por exemplo:

C#

// compile with: -target:library


public class D
{
public virtual void DoWork(int i)
{
// Original implementation.
}
}

public abstract class E : D


{
public abstract override void DoWork(int i);
}

public class F : E
{
public override void DoWork(int i)
{
// New implementation.
}
}

Se um método virtual for declarado abstract , ele ainda será virtual para qualquer
classe que herdar da classe abstrata. Uma classe que herda um método abstrato não
pode acessar a implementação original do método. No exemplo anterior, DoWork na
classe F não pode chamar DoWork na classe D. Dessa forma, uma classe abstrata pode
forçar classes derivadas a fornecerem novas implementações de método para métodos
virtuais.

Classes e membros de classes sealed


As classes podem ser declaradas como sealed, colocando a palavra-chave sealed antes
da definição de classe. Por exemplo:

C#

public sealed class D


{
// Class members here.
}
Uma classe sealed não pode ser usada como uma classe base. Por esse motivo, também
não pode ser uma classe abstrata. As classes sealed impedem a derivação. Como elas
nunca podem ser usadas como uma classe base, algumas otimizações em tempo de
execução podem tornar a chamada a membros de classe sealed ligeiramente mais
rápida.

Um método, um indexador, uma propriedade ou um evento em uma classe derivada


que está substituindo um membro virtual da classe base, pode declarar esse membro
como sealed. Isso anula o aspecto virtual do membro para qualquer outra classe
derivada. Isso é realizado através da colocação da palavra-chave sealed antes da
palavra-chave override na declaração de membro de classe. Por exemplo:

C#

public class D : C
{
public sealed override void DoWork() { }
}

Confira também
Guia de Programação em C#
O sistema de tipos C#
Herança
Métodos
Fields
Como definir propriedades abstract
Classes static e membros de classes
static (Guia de Programação em C#)
Artigo • 22/03/2024

Uma classe estática é basicamente a mesma coisa que uma classe não estática, mas há
uma diferença: uma classe estática não pode ser instanciada. Em outras palavras, não é
possível usar o operador novo para criar uma variável do tipo de classe. Como não há
variável de instância, acesse os membros de uma classe estática usando o próprio nome
da classe. Por exemplo, se houver uma classe estática chamada UtilityClass com um
método público chamado MethodA , chame o método, como mostra o exemplo a seguir:

C#

UtilityClass.MethodA();

Uma classe estática pode ser usada como um contêiner conveniente para conjuntos de
métodos que operam apenas em parâmetros de entrada e não precisam obter ou
definir nenhum campo de instância interno. Por exemplo, na biblioteca de classes .NET,
a classe estática System.Math contém métodos que executam operações matemáticas,
sem a necessidade de armazenar ou recuperar dados que são exclusivos de uma
determinada instância da classe Math. Ou seja, você aplica os membros da classe
especificando o nome de classe e o nome do método, conforme mostrado no exemplo
a seguir.

C#

double dub = -3.14;


Console.WriteLine(Math.Abs(dub));
Console.WriteLine(Math.Floor(dub));
Console.WriteLine(Math.Round(Math.Abs(dub)));

// Output:
// 3.14
// -4
// 3

Como ocorre com todos os tipos de classe, o runtime do .NET carrega as informações
de tipo de uma classe estática quando o programa que faz referência à classe é
carregado. O programa não pode especificar exatamente quando a classe será
carregada. Entretanto, é garantido que ela seja carregada e que seus campos sejam
inicializados e que seu construtor estático seja chamado antes que a classe seja
referenciada pela primeira vez em seu programa. Um construtor estático é chamado
apenas uma vez e uma classe estática permanece na memória pelo tempo de vida do
domínio do aplicativo em que seu programa reside.

7 Observação

Para criar uma classe não estática que permite que apenas uma instância de si
mesma seja criada, consulte Implementando singleton no C#.

A lista a seguir fornece os principais recursos de uma classe estática:

Contém apenas membros estáticos.

Não pode ser instanciada.

É lacrada.

Não pode conter construtores de instâncias.

Criar uma classe estática é, portanto, basicamente o mesmo que criar uma classe que
contém apenas membros estáticos e um construtor particular. Um construtor particular
impede que a classe seja instanciada. A vantagem de usar uma classe estática é que o
compilador pode verificar se nenhum membro de instância foi adicionado
acidentalmente. O compilador garante que as instâncias dessa classe não podem ser
criadas.

As classes estáticas são seladas e, portanto, não podem ser herdadas. Elas não podem
herdar de nenhuma classe ou interface, exceto Object. As classes estáticas não podem
conter um construtor de instâncias. No entanto, eles podem conter um construtor
estático. Classes não estáticas também devem definir um construtor estático se a classe
contiver membros estáticos que exigem inicialização não trivial. Para obter mais
informações, consulte Construtores estáticos.

Exemplo
Aqui está um exemplo de uma classe estática que contém dois métodos que convertem
a temperatura de Celsius para Fahrenheit e de Fahrenheit para Celsius:

C#

public static class TemperatureConverter


{
public static double CelsiusToFahrenheit(string temperatureCelsius)
{
// Convert argument to double for calculations.
double celsius = Double.Parse(temperatureCelsius);

// Convert Celsius to Fahrenheit.


double fahrenheit = (celsius * 9 / 5) + 32;

return fahrenheit;
}

public static double FahrenheitToCelsius(string temperatureFahrenheit)


{
// Convert argument to double for calculations.
double fahrenheit = Double.Parse(temperatureFahrenheit);

// Convert Fahrenheit to Celsius.


double celsius = (fahrenheit - 32) * 5 / 9;

return celsius;
}
}

class TestTemperatureConverter
{
static void Main()
{
Console.WriteLine("Please select the convertor direction");
Console.WriteLine("1. From Celsius to Fahrenheit.");
Console.WriteLine("2. From Fahrenheit to Celsius.");
Console.Write(":");

string? selection = Console.ReadLine();


double F, C = 0;

switch (selection)
{
case "1":
Console.Write("Please enter the Celsius temperature: ");
F =
TemperatureConverter.CelsiusToFahrenheit(Console.ReadLine() ?? "0");
Console.WriteLine("Temperature in Fahrenheit: {0:F2}", F);
break;

case "2":
Console.Write("Please enter the Fahrenheit temperature: ");
C =
TemperatureConverter.FahrenheitToCelsius(Console.ReadLine() ?? "0");
Console.WriteLine("Temperature in Celsius: {0:F2}", C);
break;

default:
Console.WriteLine("Please select a convertor.");
break;
}

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Example Output:
Please select the convertor direction
1. From Celsius to Fahrenheit.
2. From Fahrenheit to Celsius.
:2
Please enter the Fahrenheit temperature: 20
Temperature in Celsius: -6.67
Press any key to exit.
*/

Membros Estáticos
Uma classe não estática não pode conter métodos, campos, propriedades ou eventos
estáticos. O membro estático pode ser chamado em uma classe mesmo que não exista
nenhuma instância da classe. O membro estático sempre é acessado pelo nome de
classe, não pelo nome da instância. Existe apenas uma cópia de um membro estático,
independentemente de quantas instâncias da classe forem criadas. Os métodos e
propriedades estáticos não podem acessar campos e eventos não estáticos no tipo que
os contém e não podem acessar uma variável de instância de qualquer objeto, a menos
que ela seja explicitamente passada em um parâmetro de método.

É mais comum declarar uma classe não estática com alguns membros estáticos do que
declarar uma classe inteira como estática. Dois usos comuns de campos estáticos são
manter uma contagem do número de objetos que são instanciados ou armazenar um
valor que deve ser compartilhado entre todas as instâncias.

Métodos estáticos podem ser sobrecarregados, mas não substituídos, porque


pertencem à classe e não a qualquer instância da classe.

Embora um campo não possa ser declarado como static const , um campo const
é essencialmente estático em seu comportamento. Ele pertence ao tipo e não a
instâncias do tipo. Portanto, os campos const podem ser acessados usando a mesma
notação ClassName.MemberName usada para campos estáticos. Nenhuma instância de
objeto é necessária.

O C# não dá suporte a variáveis locais estáticas (ou seja, variáveis que são declaradas no
escopo do método).

Você declara membros de classe estática usando a palavra-chave static antes do tipo
de retorno do membro, conforme mostrado no exemplo a seguir:
C#

public class Automobile


{
public static int NumberOfWheels = 4;

public static int SizeOfGasTank


{
get
{
return 15;
}
}

public static void Drive() { }

public static event EventType? RunOutOfGas;

// Other non-static fields and properties...


}

Os membros estáticos são inicializados antes que o membro estático seja acessado pela
primeira vez e antes que o construtor estático, se houver um, seja chamado. Para
acessar um membro de classe estática, use o nome da classe em vez de um nome de
variável para especificar o local do membro, conforme mostrado no exemplo a seguir:

C#

Automobile.Drive();
int i = Automobile.NumberOfWheels;

Se sua classe contiver campos estáticos, forneça um construtor estático que os inicializa
quando a classe é carregada.

Uma chamada para um método estático gera uma instrução de chamada em MSIL
(Microsoft Intermediate Language), enquanto uma chamada para um método de
instância gera uma instrução callvirt , que também verifica se há referências de objeto
nulas. Entretanto, na maioria das vezes, a diferença de desempenho entre os dois não é
significativa.

Especificação da Linguagem C#
Para saber mais, confira Classes estáticas, Membros estáticos e de instância e
Construtores estáticos 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
static
Classes
class
Construtores estáticos
Construtores de instância

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
Modificadores de acesso (Guia de
Programação em C#)
Artigo • 20/08/2024

Todos os tipos e membros de tipo têm um nível de acessibilidade. O nível de


acessibilidade controla se eles podem ser usados em outro código no assembly ou em
outros assemblies. Um assembly é um .dll ou .exe criado ao selecionar um ou mais
arquivos .cs em uma única compilação. Use os modificadores de acesso a seguir para
especificar a acessibilidade de um tipo ou membro quando você o declarar:

público: O código em qualquer assembly pode acessar esse tipo ou membro. O


nível de acessibilidade do tipo que contém controla o nível de acessibilidade dos
membros públicos do tipo.
privado: Somente o código declarado no mesmo class ou struct pode acessar
este membro.
protegido: Somente código no mesmo class ou em um class derivado pode
acessar esse tipo ou membro.
interno: Somente código no mesmo assembly pode acessar esse tipo ou membro.
interno protegido: Somente código no mesmo assembly ou em uma classe
derivada em outro assembly pode acessar esse tipo ou membro.
privado protegido: Somente código no mesmo assembly e na mesma classe ou em
uma classe derivada pode acessar o tipo ou membro.
arquivo: somente o código no mesmo arquivo pode acessar o tipo ou membro.

O modificador record em um tipo faz com que o compilador sintetize membros extras.
O modificador record não afeta a acessibilidade padrão para um record class ou um
record struct .

Tabela de resumo
ノ Expandir a tabela

Local do public protected protected internal private private file


chamador internal protected

Dentro do ✔️️ ✔️ ✔️ ✔️ ✔️ ✔️ ✔️
arquivo

Dentro da classe ✔️️ ✔️ ✔️ ✔️ ✔️ ✔️ ❌


Local do public protected protected internal private private file
chamador internal protected

Classe derivada ✔️ ✔️ ✔️ ✔️ ✔️ ❌ ❌
(mesmo
assembly)

Classe não ✔️ ✔️ ❌ ✔️ ❌ ❌ ❌
derivada
(mesmo
assembly)

Classe derivada ✔️ ✔️ ✔️ ❌ ❌ ❌ ❌
(assembly
diferente)

Classe não ✔️ ❌ ❌ ❌ ❌ ❌ ❌
derivada
(assembly
diferente)

Os exemplos a seguir demonstram como especificar modificadores de acesso em um


tipo e membro:

C#

public class Bicycle


{
public void Pedal() { }
}

Nem todos os modificadores de acesso são válidos para todos os tipos ou membros em
todos os contextos. Em alguns casos, a acessibilidade do tipo que contém restringe a
acessibilidade dos seus membros.

Várias declarações de uma classe parcial ou membro parcial devem ter a mesma
acessibilidade. Se uma declaração da classe ou membro parcial não incluir um
modificador de acesso, as outras declarações não poderão declarar um modificador de
acesso. O compilador gera um erro se múltiplas declarações para a classe ou método
parcial declararem acessibilidades diferentes.

Acessibilidade de classe e estrutura


Classes e estruturas declaradas diretamente em um namespace (não estão aninhadas
em outras classes ou structs) podem ter acesso public , internal ou file . internal é o
padrão, se nenhum modificador de acesso for especificado.

Os membros de struct, incluindo classes e structs aninhados, podem ser declarados


como public , internal ou private . Os membros de classe, incluindo classes e structs
aninhados, podem ser public , protected internal , protected , internal , private
protected ou private . Os membros de classe e struct, incluindo classes e structs

aninhados, têm acesso private por padrão.

As classes derivadas não podem ter maior acessibilidade do que seus tipos base. Você
não pode declarar uma classe pública B derivada de uma classe interna A . Se permitido,
teria o efeito de tornar A público, pois todos os membros protected ou internal de A
são acessíveis na classe derivada.

Você pode permitir que outros assemblies específicos acessem os tipos internos usando
o InternalsVisibleToAttribute . Para obter mais informações, consulte Assemblies
amigáveis.

Outros tipos
As interfaces declaradas diretamente em um namespace podem ser public ou internal
e, assim como classes e structs, o padrão das interfaces é o acesso internal . Membros
de interface são public por padrão, pois a finalidade de uma interface é permitir que
outros tipos acessem uma classe ou um struct. As declarações de membros da interface
podem incluir qualquer modificador de acesso. Você usa modificadores de acesso em
interface membros para fornecer uma implementação comum necessária para todos

os implementadores de uma interface.

Um tipo delegate declarado diretamente em um namespace tem acesso internal por


padrão.

Para obter mais informações sobre modificadores de acesso, consulte a página Níveis
de acessibilidade.

Acessibilidade de membro
Membros de um class ou struct (incluindo classes e estruturas aninhadas) podem ser
declarados com qualquer um dos seis tipos de acesso. Os membros de struct não
podem ser declarados como protected , protected internal ou private protected , pois
os structs não permitem herança.
Normalmente, a acessibilidade de um membro não é maior que a acessibilidade do tipo
que o contém. No entanto, um membro public de uma classe internal poderá ser
acessível de fora do assembly se o membro implementar métodos de interface ou
substituir métodos virtuais definidos em uma classe base pública.

O tipo de qualquer campo, propriedade ou evento do membro deve ser pelo menos tão
acessível quanto o próprio membro. Da mesma forma, o tipo de retorno e os tipos de
parâmetro de qualquer método, indexador ou delegado devem ser pelo menos tão
acessíveis quanto o próprio membro. Por exemplo, você não pode ter um método
public , M , que retorna uma classe C , a menos que C também seja public . Da mesma

forma, você não pode ter uma propriedade protected do tipo A , se A for declarada
como private .

Os operadores definidos pelo usuário sempre devem ser declarados como public e
static . Para obter mais informações, consulte Sobrecarga de operador.

Para definir o nível de acesso para um membro class ou struct , adicione a palavra-
chave apropriada à declaração do membro, conforme mostrado no exemplo a seguir.

C#

// public class:
public class Tricycle
{
// protected method:
protected void Pedal() { }

// private field:
private int _wheels = 3;

// protected internal property:


protected internal int Wheels
{
get { return _wheels; }
}
}

Os finalizadores não podem ter modificadores de acessibilidade. Os membros de um


enum tipo são sempre public e nenhum modificador de acesso pode ser aplicado.

O modificador de acesso file é permitido apenas em declarações de tipo de nível


superior (não aninhados).

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
Especificar a ordem do modificador (regra de estilo IDE0036)
Sistema de tipos do C#
Interfaces
Níveis de acessibilidade
private
público
interno
protected
internos protegidos
privado protegido
sealed
class
struct
interface
Tipos anônimos
Campos (Guia de Programação em C#)
Artigo • 02/06/2023

Um campo é uma variável de qualquer tipo que é declarada diretamente em uma classe
ou struct. Os campos são membros do tipo que os contém.

Uma classe ou um struct podem ter campos de instância, estáticos ou ambos. Os


campos de instância são específicos a uma instância de um tipo. Se você tem uma classe
T , com um campo de instância F , você pode criar dois objetos do tipo T e modificar o

valor de F em cada objeto sem afetar o valor no outro objeto. Por outro lado, um
campo estático pertence ao próprio tipo e é compartilhado entre todas as instâncias
desse tipo. Você só pode acessar o campo estático usando o nome do tipo. Se você
acessar o campo estático por meio de um nome de instância, receberá o erro em tempo
de compilação CS0176.

Em geral, você deve declarar a acessibilidade private ou protected para campos. Os


dados que o tipo expõe para o código de cliente devem ser fornecidos por meio de
métodos, propriedades e indexadores. Usando esses constructos para acesso indireto
aos campos internos, você pode proteger contra valores de entrada inválidos. Um
campo particular que armazena os dados expostos por uma propriedade pública é
chamado de repositório de backup ou de campo de suporte. Você pode declarar campos
public , mas não pode impedir que o código que usa seu tipo defina esse campo como

um valor inválido ou altere os dados de um objeto.

Os campos normalmente armazenam os dados que devem estar acessíveis a mais de


um método de tipo e devem ser armazenados por mais tempo que o tempo de vida de
qualquer método único. Por exemplo, um tipo que representa uma data do calendário
pode ter três campos de inteiros: um para o mês, um para o dia e outro para o ano. As
variáveis que não são usadas fora do escopo de um método único devem ser declaradas
como variáveis locais dentro do próprio corpo do método.

Os campos são declarados no bloco da classe ou do struct, especificando o nível de


acesso, seguido pelo tipo e pelo nome do campo. Por exemplo:

C#

public class CalendarEntry


{

// private field (Located near wrapping "Date" property).


private DateTime _date;

// Public property exposes _date field safely.


public DateTime Date
{
get
{
return _date;
}
set
{
// Set some reasonable boundaries for likely birth dates.
if (value.Year > 1900 && value.Year <= DateTime.Today.Year)
{
_date = value;
}
else
{
throw new ArgumentOutOfRangeException("Date");
}
}
}

// public field (Generally not recommended).


public string? Day;

// Public method also exposes _date field safely.


// Example call: birthday.SetDate("1975, 6, 30");
public void SetDate(string dateString)
{
DateTime dt = Convert.ToDateTime(dateString);

// Set some reasonable boundaries for likely birth dates.


if (dt.Year > 1900 && dt.Year <= DateTime.Today.Year)
{
_date = dt;
}
else
{
throw new ArgumentOutOfRangeException("dateString");
}
}

public TimeSpan GetTimeSpan(string dateString)


{
DateTime dt = Convert.ToDateTime(dateString);

if (dt.Ticks < _date.Ticks)


{
return _date - dt;
}
else
{
throw new ArgumentOutOfRangeException("dateString");
}
}
}
Para acessar um campo em uma instância, adicione um ponto após o nome da instância,
seguido pelo nome do campo, como em instancename._fieldName . Por exemplo:

C#

CalendarEntry birthday = new CalendarEntry();


birthday.Day = "Saturday";

Um campo pode receber um valor inicial, usando o operador de atribuição quando o


campo é declarado. Para atribuir automaticamente o campo Day ao "Monday" , por
exemplo, você poderia declarar Day como no exemplo a seguir:

C#

public class CalendarDateWithInitialization


{
public string Day = "Monday";
//...
}

Os campos são inicializados imediatamente antes do construtor para a instância do


objeto ser chamado. Se o construtor atribuir o valor de um campo, ele substituirá
qualquer valor fornecido durante a declaração do campo. Para obter mais informações,
veja Usando construtores.

7 Observação

Um inicializador de campo não pode fazer referência a outros campos de instância.

Os campos podem ser marcados como public, private, protected, internal, protected
internal ou private protected. Esses modificadores de acesso definem como os usuários
do tipo podem acessar os campos. Para obter mais informações, consulte Modificadores
de Acesso.

Opcionalmente, um campo pode ser declarado static. Os campos estáticos estão


disponíveis para chamadores a qualquer momento, mesmo se não existir nenhuma
instância do tipo. Para obter mais informações, consulte Classes estáticas e membros de
classes estáticas.

Um campo pode ser declarado readonly. Um valor só pode ser atribuído a um campo
somente leitura durante a inicialização ou em um construtor. Um campo static
readonly é semelhante a uma constante, exceto que o compilador C# não tem acesso
ao valor de um campo somente leitura estático em tempo de compilação, mas somente
em tempo de execução. Para obter mais informações, consulte Constantes.

Um campo pode ser declarado required. Um campo obrigatório precisa ser inicializado
pelo construtor ou por um inicializador de objeto quando um objeto é criado. Adicione
o atributo System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute a qualquer
declaração de construtor que inicialize todos os membros necessários.

O modificador required não pode ser combinado com o modificador readonly no


mesmo campo. No entanto, a propriedade pode ser required e init somente.

A partir do C# 12, os parâmetros do construtor primário são uma alternativa para


declarar campos. Quando seu tipo tem dependências que devem ser fornecidas na
inicialização, você pode criar um construtor primário que fornece essas dependências.
Esses parâmetros podem ser capturados e usados no lugar de campos declarados em
seus tipos. No caso de tipos record, os parâmetros do construtor primário são exibidos
como propriedades públicas.

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
Guia de Programação em C#
Sistema de tipos do C#
Usando construtores
Herança
Modificadores de acesso
Classes e membros de classes abstract e sealed
Constantes (Guia de Programação em
C#)
Artigo • 07/04/2023

As constantes são valores imutáveis que são conhecidos no tempo de compilação e não
são alterados durante a vida útil do programa. Constantes são declaradas com o
modificador const. Apenas os tipos internos do C# (excluindo System.Object) podem ser
declarados como const . Tipos definidos pelo usuário, incluindo classes, struct e
matrizes, não podem ser const . Use o modificador readonly para criar uma classe, um
struct ou uma matriz que sejam inicializados uma vez em tempo de execução (por
exemplo, em um construtor) e não possam mais ser alterados depois disso.

O C# não dá suporte aos métodos const , propriedades ou eventos.

O tipo de enumeração permite que você defina constantes nomeadas para tipos
internos integrais (por exemplo int , uint , long e assim por diante). Para obter mais
informações, consulte enum.

As constantes devem ser inicializadas conforme elas são declaradas. Por exemplo:

C#

class Calendar1
{
public const int Months = 12;
}

Neste exemplo, a constante Months sempre é 12 e não pode ser alterada até mesmo
pela própria classe. Na verdade, quando o compilador encontra um identificador
constante no código-fonte C# (por exemplo, Months ), ele substitui o valor literal
diretamente no código de IL (linguagem intermediária) que ele produz. Como não há
nenhum endereço variável associado a uma constante em tempo de execução, os
campos const não podem ser passados por referência e não podem aparecer como um
l-value em uma expressão.

7 Observação

Tenha cuidado ao fazer referência a valores constantes definidos em outro código


como DLLs. Se uma nova versão da DLL definir um novo valor para a constante, seu
programa ainda conterá o valor literal antigo até que ele seja recompilado com a
nova versão.
Várias constantes do mesmo tipo podem ser declaradas ao mesmo tempo, por exemplo:

C#

class Calendar2
{
public const int Months = 12, Weeks = 52, Days = 365;
}

A expressão que é usada para inicializar uma constante poderá fazer referência a outra
constante se ela não criar uma referência circular. Por exemplo:

C#

class Calendar3
{
public const int Months = 12;
public const int Weeks = 52;
public const int Days = 365;

public const double DaysPerWeek = (double) Days / (double) Weeks;


public const double DaysPerMonth = (double) Days / (double) Months;
}

As constantes podem ser marcadas como public, private, protected, internal, protected
internal ou private protected. Esses modificadores de acesso definem como os usuários
da classe podem acessar a constante. Para obter mais informações, consulte
Modificadores de Acesso.

As constantes são acessadas como se fossem campos static porque o valor da constante
é o mesmo para todas as instâncias do tipo. Você não usa a palavra-chave static para
declará-las. As expressões que não estão na classe que define a constante devem usar o
nome de classe, um período e o nome da constante para acessar a constante. Por
exemplo:

C#

int birthstones = Calendar.Months;

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
Guia de Programação em C#
Propriedades
Types
readonly
Immutability in C# Part One: Kinds of Immutability (Imutabilidade no C#, parte um:
tipos de imutabilidade)
Como definir propriedades abstract
(Guia de programação em C#)
Artigo • 07/04/2023

O exemplo a seguir mostra como definir propriedades abstract. Uma declaração de


propriedade abstract não fornece uma implementação dos acessadores da propriedade
– ela declara que a classe dá suporte às propriedades, mas deixa a implementação do
acessador para classes derivadas. O exemplo a seguir demonstra como implementar as
propriedades abstract herdadas de uma classe base.

Esse exemplo consiste em três arquivos, cada um deles é compilado individualmente e


seu assembly resultante é referenciado pela próxima compilação:

abstractshape.cs: a classe Shape que contém uma propriedade abstract Area .

shapes.cs: as subclasses da classe Shape .

shapetest.cs: um programa de teste para exibir as áreas de alguns objetos


derivados de Shape .

Para compilar o exemplo, use o comando a seguir:

csc abstractshape.cs shapes.cs shapetest.cs

Isso criará o arquivo executável shapetest.exe.

Exemplos
Esse arquivo declara a classe Shape que contém a propriedade Area do tipo double .

C#

// compile with: csc -target:library abstractshape.cs


public abstract class Shape
{
private string name;

public Shape(string s)
{
// calling the set accessor of the Id property.
Id = s;
}

public string Id
{
get
{
return name;
}

set
{
name = value;
}
}

// Area is a read-only property - only a get accessor is needed:


public abstract double Area
{
get;
}

public override string ToString()


{
return $"{Id} Area = {Area:F2}";
}
}

Os modificadores da propriedade são colocados na própria declaração de


propriedade. Por exemplo:

C#

public abstract double Area

Ao declarar uma propriedade abstract (como Area neste exemplo), você


simplesmente indica quais acessadores de propriedade estão disponíveis, mas não
os implementa. Neste exemplo, apenas um acessador get está disponível, assim, a
propriedade é somente leitura.

O código a seguir mostra três subclasses de Shape e como elas substituem a


propriedade Area para fornecer sua própria implementação.

C#

// compile with: csc -target:library -reference:abstractshape.dll shapes.cs


public class Square : Shape
{
private int side;

public Square(int side, string id)


: base(id)
{
this.side = side;
}

public override double Area


{
get
{
// Given the side, return the area of a square:
return side * side;
}
}
}

public class Circle : Shape


{
private int radius;

public Circle(int radius, string id)


: base(id)
{
this.radius = radius;
}

public override double Area


{
get
{
// Given the radius, return the area of a circle:
return radius * radius * System.Math.PI;
}
}
}

public class Rectangle : Shape


{
private int width;
private int height;

public Rectangle(int width, int height, string id)


: base(id)
{
this.width = width;
this.height = height;
}

public override double Area


{
get
{
// Given the width and height, return the area of a rectangle:
return width * height;
}
}
}
O código a seguir mostra um programa de teste que cria uma quantidade de objetos
derivados de Shape e imprime suas áreas.

C#

// compile with: csc -reference:abstractshape.dll;shapes.dll shapetest.cs


class TestClass
{
static void Main()
{
Shape[] shapes =
{
new Square(5, "Square #1"),
new Circle(3, "Circle #1"),
new Rectangle( 4, 5, "Rectangle #1")
};

System.Console.WriteLine("Shapes Collection");
foreach (Shape s in shapes)
{
System.Console.WriteLine(s);
}
}
}
/* Output:
Shapes Collection
Square #1 Area = 25.00
Circle #1 Area = 28.27
Rectangle #1 Area = 20.00
*/

Confira também
Guia de Programação em C#
O sistema do tipo C#
Classes e membros de classes abstract e sealed
Propriedades
Como definir constantes em C#
Artigo • 07/04/2023

As constantes são campos cujos valores são definidos em tempo de compilação e nunca
podem ser alterados. Use constantes para fornecer nomes significativos em vez de
literais numéricos ("números mágicos") a valores especiais.

7 Observação

No C#, a diretiva de pré-processador #define não pode ser utilizada para definir
constantes da mesma maneira que é normalmente usada no C e no C++.

Para definir valores de constantes de tipos integrais ( int , byte e assim por diante), use
um tipo enumerado. Para obter mais informações, consulte enum.

Para definir constantes não integrais, uma abordagem é agrupá-las em uma única classe
estática de nome Constants . Isso exigirá que todas as referências às constantes sejam
precedidas com o nome de classe, conforme mostrado no exemplo a seguir.

Exemplo
C#

static class Constants


{
public const double Pi = 3.14159;
public const int SpeedOfLight = 300000; // km per sec.
}

class Program
{
static void Main()
{
double radius = 5.3;
double area = Constants.Pi * (radius * radius);
int secsFromSun = 149476000 / Constants.SpeedOfLight; // in km
Console.WriteLine(secsFromSun);
}
}

O uso do qualificador de nome de classe ajuda a garantir que você e outras pessoas que
usam a constante entendam que ele é constante e não pode ser modificado.
Confira também
Sistema de tipos do C#
Propriedades (Guia de Programação em
C#)
Artigo • 14/11/2024

Uma propriedade é um membro que oferece um mecanismo flexível para ler, gravar ou
calcular o valor de um campo de dados. As propriedades aparecem como membros de
dados públicos, mas são implementadas como métodos especiais chamados
acessadores. Esse recurso permite que os chamadores acessem dados facilmente e ainda
ajuda a promover a segurança e a flexibilidade dos dados. A sintaxe para propriedades é
uma extensão natural para os campos. Um campo define um local de armazenamento:

C#

public class Person


{
public string? FirstName;

// Omitted for brevity.


}

Propriedades implementadas automaticamente


Uma definição de propriedade contém declarações para um acessador get e set que
recupera e atribui o valor dessa propriedade:

C#

public class Person


{
public string? FirstName { get; set; }

// Omitted for brevity.


}

O exemplo anterior mostra uma propriedade implementada automaticamente. O


compilador gera um campo de suporte oculto para a propriedade. O compilador
também implementa o corpo dos acessadores get e set . Todos os atributos são
aplicados à propriedade implementada automaticamente. Você pode aplicar o atributo
ao campo de suporte gerado pelo compilador especificando a tag field: no atributo.

Você pode inicializar uma propriedade para um valor diferente do padrão definindo um
valor após a chave de fechamento da propriedade. Talvez você prefira que o valor inicial
para a propriedade FirstName seja a cadeia de caracteres vazia em vez de null . Você
especificaria isso conforme mostrado no código a seguir:

C#

public class Person


{
public string FirstName { get; set; } = string.Empty;

// Omitted for brevity.


}

Propriedades com suporte de campo


No C# 13, você pode adicionar validação ou outra lógica no acessador de uma
propriedade usando o recurso de visualização de field palavra-chave. A field palavra-
chave acessa o campo de suporte sintetizado do compilador para uma propriedade. Ele
permite que você escreva um acessador de propriedade sem declarar explicitamente um
campo de suporte separado.

C#

public class Person


{
public string? FirstName
{
get;
set => field = value.Trim();
}

// Omitted for brevity.


}

) Importante

A field palavra-chave é um recurso de visualização no C# 13. Você deve estar


usando o .NET 9 e definir seu <LangVersion> elemento como preview no arquivo
de projeto para usar a field palavra-chave contextual.

Você deve ter cuidado ao usar o recurso de field palavra-chave em uma classe
que tenha um campo chamado field . A nova field palavra-chave sombreia um
campo nomeado field no escopo de um acessador de propriedade. Você pode
alterar o nome da field variável ou usar o @ token para fazer referência ao field
identificador como @field . Você pode saber mais lendo a especificação do recurso
para a field palavra-chave.

Propriedades obrigatórias
O exemplo anterior permite que um chamador crie um Person usando o construtor
padrão, sem definir a propriedade FirstName . A propriedade alterou o tipo para uma
cadeia de caracteres que permite valor nulo. A partir do C# 11, você pode exigir que os
chamadores definam uma propriedade:

C#

public class Person


{
public Person() { }

[SetsRequiredMembers]
public Person(string firstName) => FirstName = firstName;

public required string FirstName { get; init; }

// Omitted for brevity.


}

O código anterior faz duas alterações na classe Person . Primeiro, a declaração de


propriedade FirstName inclui o modificador required . Isso significa que qualquer
código que crie um novo Person deve definir essa propriedade usando um inicializador
de objeto. Em segundo lugar, o construtor que usa um parâmetro firstName tem o
atributo System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute. Esse atributo
informa ao compilador que esse construtor define todos required os membros. Os
chamadores que usam esse construtor não precisam definir propriedades required com
um inicializador de objeto.

) Importante

Não confunda required com não anulável. É válido definir uma propriedade
required como null ou default . Se o tipo for não anulável, como string nesses

exemplos, o compilador emitirá um aviso.

C#
var aPerson = new Person("John");
aPerson = new Person{ FirstName = "John"};
// Error CS9035: Required member `Person.FirstName` must be set:
//aPerson2 = new Person();

Definições de corpo de expressão


Os acessadores de propriedade geralmente consistem em instruções de linha única. Os
acessadores atribuem ou retornam o resultado de uma expressão. Você pode
implementar essas propriedades como membros aptos para expressão. As definições de
corpo da expressão consistem no token => seguido pela expressão à qual atribuir ou
recuperar da propriedade.

Propriedades somente leitura podem implementar o acessador get como um membro


apto para expressão. O exemplo a seguir implementa a propriedade Name somente
leitura como um membro apto para expressão:

C#

public class Person


{
public Person() { }

[SetsRequiredMembers]
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}

public required string FirstName { get; init; }


public required string LastName { get; init; }

public string Name => $"{FirstName} {LastName}";

// Omitted for brevity.


}

A propriedade Name é uma propriedade computada. Não há campo de apoio para Name .
A propriedade calcula isso toda vez.

Controle de acesso
Os exemplos anteriores mostraram propriedades de leitura/gravação. Você também
pode criar propriedades somente leitura ou dar acessibilidade diferente aos acessadores
get e set. Suponha que sua Person classe só deva FirstName habilitar a alteração do
valor da propriedade de outros métodos na classe. Você pode dar acessibilidade ao
acessador private definido em vez de internal ou public :

C#

public class Person


{
public string? FirstName { get; private set; }

// Omitted for brevity.


}

A propriedade FirstName pode ser lida em qualquer código, mas só pode ser atribuída
do código na classe Person .

Você pode adicionar qualquer modificador de acesso restritivo aos acessadores get ou
set. Um modificador de acesso em um acessador individual deve ser mais restritivo do
que o acesso da propriedade. O código anterior é legal porque a propriedade
FirstName é public , mas o acessador set é private . Você não poderia declarar uma

propriedade private com um acessador public . As declarações de propriedade


também podem ser declaradas protected , internal , protected internal ou até mesmo
private .

Há dois modificadores de acesso especiais para acessadores set :

Um acessador set pode ter init como seu modificador de acesso. Esse acessador
set pode ser chamado somente de um inicializador de objeto ou dos construtores

do tipo. É mais restritivo do que private no acessador set .


Uma propriedade implementada automaticamente pode declarar um get
acessador sem um set acessador. Nesse caso, o compilador permite que o
acessador set seja chamado somente dos construtores do tipo. É mais restritivo
do que o acessador init no acessador set .

Modifique a classe Person da seguinte maneira:

C#

public class Person


{
public Person(string firstName) => FirstName = firstName;
public string FirstName { get; }

// Omitted for brevity.


}

O exemplo anterior requer que os chamadores usem o construtor que inclui o


parâmetro FirstName . Os chamadores não podem usar inicializadores de objeto para
atribuir um valor à propriedade. Para dar suporte a inicializadores, você pode
transformar o set em um init , conforme mostrado no seguinte código:

C#

public class Person


{
public Person() { }
public Person(string firstName) => FirstName = firstName;

public string? FirstName { get; init; }

// Omitted for brevity.


}

Esses modificadores são frequentemente usados com o modificador required para


forçar a inicialização adequada.

Propriedades com campos de suporte


Combine o conceito de uma propriedade computada com um campo privado e crie
uma propriedade avaliada armazenada em cache. Por exemplo, atualize a propriedade
FullName para que a formatação da string ocorra no primeiro acesso:

C#

public class Person


{
public Person() { }

[SetsRequiredMembers]
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}

public required string FirstName { get; init; }


public required string LastName { get; init; }
private string? _fullName;
public string FullName
{
get
{
if (_fullName is null)
_fullName = $"{FirstName} {LastName}";
return _fullName;
}
}
}

Essa implementação funciona porque as propriedades FirstName e LastName são


somente leitura. As pessoas podem mudar o nome. A atualização das propriedades
FirstName e LastName para permitir acessadores set exige que você invalide qualquer

valor armazenado em cache para fullName . Modifique os acessadores set das


propriedades FirstName e LastName para que o campo fullName seja calculado
novamente:

C#

public class Person


{
private string? _firstName;
public string? FirstName
{
get => _firstName;
set
{
_firstName = value;
_fullName = null;
}
}

private string? _lastName;


public string? LastName
{
get => _lastName;
set
{
_lastName = value;
_fullName = null;
}
}

private string? _fullName;


public string FullName
{
get
{
if (_fullName is null)
_fullName = $"{FirstName} {LastName}";
return _fullName;
}
}
}

Esta versão final avalia a propriedade FullName apenas quando necessário. Se a versão
calculada anteriormente é válida, ela é usada. Caso contrário, o cálculo atualizará o valor
armazenado em cache. Os desenvolvedores que usam essa classe não precisam saber
dos detalhes da implementação. Nenhuma dessas alterações internas afetam o uso do
objeto Person.

A partir do C# 13, você pode criar partial propriedades em classes partial . A


declaração de implementação de uma partial propriedade não pode ser uma
propriedade implementada automaticamente. Uma propriedade implementada
automaticamente usa a mesma sintaxe que uma declaração de propriedade parcial de
declaração.

Propriedades
As propriedades são uma forma de campos inteligentes em uma classe ou objeto. De
fora do objeto, elas parecem como campos no objeto. No entanto, as propriedades
podem ser implementadas usando a paleta completa de funcionalidades do C#. Você
pode fornecer validação, acessibilidade diferente, avaliação lenta ou quaisquer
requisitos necessários aos seus cenários.

Propriedades simples que não exigem nenhum código de acesso personalizado


podem ser implementadas como definições de corpo de expressão ou como
propriedades implementadas automaticamente.
As propriedades permitem que uma classe exponha uma forma pública de obter e
definir valores, enquanto oculta o código de implementação ou de verificação.
Um acessador de propriedade get é usado para retornar o valor da propriedade e
um acessador de propriedade set é usado para atribuir um novo valor. Um
acessador de propriedade init é usado para atribuir um novo valor somente
durante a construção de objeto. Esses acessadores podem ter diferentes níveis de
acesso. Para obter mais informações, consulte Restringindo a acessibilidade aos
acessadores.
A palavra-chave value é usada para definir o valor que o acessor set ou init está
atribuindo.
As propriedades podem ser de leitura/gravação (elas têm um acessador get e
set ), somente leitura (elas têm um acessador get , mas nenhum set ) ou somente
gravação (elas têm um acessador set , mas nenhum get ). As propriedades
somente gravação são raras.

Especificação da Linguagem C#
Para obter mais informações, veja Propriedades 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
Indexadores
Palavra-chave init
Palavra-chave get
Palavra-chave set
Usando propriedades (Guia de
Programação em C#)
Artigo • 14/11/2024

As propriedades combinam aspectos de métodos e campos. Para o usuário de um


objeto, uma propriedade parece como um campo; o acesso à propriedade exige a
mesma sintaxe. Para o implementador de uma classe, uma propriedade é um ou dois
blocos de código, representando um acessador get e/ou um ou acessador set OU init. O
bloco de código do acessador get é executado quando a propriedade é lida; o bloco de
código do acessador set ou init é executado quando a propriedade recebe um valor.
Uma propriedade sem um acessador set é considerada como somente leitura. Uma
propriedade sem um acessador get é considerada como somente gravação. Uma
propriedade que tem os dois acessadores é leitura/gravação. Você pode usar um
acessador init em vez de um acessador set para permitir que a propriedade seja
definida como parte da inicialização do objeto, mas de outra forma torná-la somente
leitura.

Diferentemente dos campos, as propriedades não são classificadas como variáveis.


Portanto, você não pode passar uma propriedade como um parâmetro ref ou out.

As propriedades têm muitos usos:

Elas podem validar dados antes de permitir uma alteração.


Elas podem expor dados de forma transparente em uma classe em que esses
dados são recuperados de alguma outra fonte, como um banco de dados.
Elas podem executar uma ação quando os dados são alterados, como gerar um
evento ou alterar o valor de outros campos.

As propriedades são declaradas no bloco de classe, especificando o nível de acesso do


campo, seguido pelo tipo da propriedade, pelo nome da propriedade e por um bloco
de código que declara um acessador get e/ou um acessador set . Por exemplo:

C#

public class Date


{
private int _month = 7; // Backing store

public int Month


{
get => _month;
set
{
if ((value > 0) && (value < 13))
{
_month = value;
}
}
}
}

Neste exemplo, Month é declarado como uma propriedade de maneira que o acessador
set possa garantir que o valor Month esteja definido entre 1 e 12. A propriedade Month

usa um campo particular para rastrear o valor real. O local real dos dados de uma
propriedade é frequentemente chamado de "repositório de backup" da propriedade. É
comum que as propriedades usem campos privados como repositório de backup. O
campo é marcado como particular para garantir que ele só pode ser alterado ao chamar
a propriedade. Para obter mais informações sobre as restrições de acesso público e
particular, consulte Modificadores de acesso. As propriedades implementadas
automaticamente fornecem sintaxe simplificada para declarações de propriedade
simples. Para obter mais informações, consulte Propriedades implementadas
automaticamente.

A partir do C# 13, você pode usar propriedades com suporte de campo para adicionar
validação ao set acessador de uma propriedade implementada automaticamente,
conforme mostrado no exemplo a seguir:

C#

public class DateExample


{
public int Month
{
get;
set
{
if ((value > 0) && (value < 13))
{
field = value;
}
}
}
}

) Importante

A field palavra-chave é um recurso de visualização no C# 13. Você deve estar


usando o .NET 9 e definir seu <LangVersion> elemento como preview no arquivo
de projeto para usar a field palavra-chave contextual.

Você deve ter cuidado ao usar o recurso de field palavra-chave em uma classe
que tenha um campo chamado field . A nova field palavra-chave sombreia um
campo nomeado field no escopo de um acessador de propriedade. Você pode
alterar o nome da field variável ou usar o @ token para fazer referência ao field
identificador como @field . Você pode saber mais lendo a especificação do recurso
para a field palavra-chave.

O acessador get
O corpo do acessador get assemelha-se ao de um método. Ele deve retornar um valor
do tipo de propriedade. O compilador C# e o compilador just-in-time (JIT) detectam
padrões comuns para implementar o acessador get , e otimiza esses padrões. Por
exemplo, um acessador get que retorna um campo sem executar qualquer computação
provavelmente é otimizado para uma leitura de memória desse campo. As propriedades
implementadas automaticamente seguem esse padrão e se beneficiam dessas
otimizações. No entanto, um método acessador virtual get não pode ser embutido
porque o compilador não sabe em tempo de compilação qual método pode realmente
ser chamado em tempo de execução. O seguinte exemplo mostra um acessador get
que retorna o valor de um campo particular _name :

C#

class Employee
{
private string _name; // the name field
public string Name => _name; // the Name property
}

Quando você referencia a propriedade, exceto como o destino de uma atribuição, o


acessador get é invocado para ler o valor da propriedade. Por exemplo:

C#

var employee= new Employee();


//...

System.Console.Write(employee.Name); // the get accessor is invoked here


O acessador get deve ser um membro com corpo de expressão ou terminar em uma
instrução return ou throw, e o controle não pode fluir para fora do corpo do acessador.

2 Aviso

Geralmente, é um estilo de programação ruim alterar o estado do objeto usando o


get acessador. Uma exceção a essa regra é uma propriedade avaliada lentamente,

em que o valor de uma propriedade é calculado somente quando ela é acessada


pela primeira vez.

O acessador get pode ser usado para retornar o valor do campo ou para calculá-lo e
retorná-lo. Por exemplo:

C#

class Manager
{
private string _name;
public string Name => _name != null ? _name : "NA";
}

No exemplo anterior, se você não atribuir um valor à propriedade Name , ela retornará o
valor NA .

O acessador set
O acessador set é semelhante a um método cujo tipo de retorno é void. Ele usa uma
parâmetro implícito chamado value , cujo tipo é o tipo da propriedade. O compilador e
o compilador JIT também reconhecem padrões comuns para um acessador set ou
init . Esses padrões comuns são otimizados, gravando diretamente a memória para o

campo de backup. No exemplo a seguir, uma acessador set é adicionado à


propriedade Name :

C#

class Student
{
private string _name; // the name field
public string Name // the Name property
{
get => _name;
set => _name = value;
}
}

Quando você atribui um valor à propriedade, o acessador set é invocado por meio do
uso de um argumento que fornece o novo valor. Por exemplo:

C#

var student = new Student();


student.Name = "Joe"; // the set accessor is invoked here

System.Console.Write(student.Name); // the get accessor is invoked here

É um erro usar o nome de parâmetro implícito value , para uma declaração de variável
local em um acessador set .

O acessador init
O código para criar um acessador init é o mesmo que o código para criar um
acessador set , exceto que você usa a palavra-chave init em vez de set . A diferença é
que o acessador init só pode ser empregado no construtor ou usando um inicializador
de objeto.

Comentários
As propriedades podem ser marcadas como public , private , protected , internal ,
protected internal ou private protected . Esses modificadores de acesso definem

como os usuários da classe podem acessar a propriedade. Os acessadores get e set da


mesma propriedade podem ter modificadores de acesso diferentes. Por exemplo, get
pode ser public para permitir o acesso somente leitura de fora do tipo, e set pode ser
private ou protected . Para obter mais informações, consulte Modificadores de Acesso.

Uma propriedade pode ser declarada como uma propriedade estática usando a palavra-
chave static . As propriedades estáticas estão disponíveis para chamadores a qualquer
momento, mesmo se não existir nenhuma instância da classe. Para obter mais
informações, consulte Classes estáticas e membros de classes estáticas.

Uma propriedade pode ser marcada como uma propriedade virtual usando a palavra-
chave virtual. As propriedades virtuais permitem que classes derivadas substituam o
comportamento da propriedade, usando a palavra-chave override. Para obter mais
informações sobre essas opções, consulte Herança.
Uma propriedade que substitui uma propriedade virtual também pode ser sealed,
especificando que ela não é mais virtual para classes derivadas. Por fim, uma
propriedade pode ser declarada abstract. As propriedades abstratas não definem
nenhuma implementação na classe e as classes derivadas devem escrever sua própria
implementação. Para obter mais informações sobre essas opções, consulte Classes e
membros de classes abstract e sealed.

7 Observação

É um erro usar um modificador virtual, abstract ou override em um acessador de


uma propriedade static.

Exemplos
Este exemplo demonstra as propriedades instância, estática e somente leitura. Ele aceita
o nome do funcionário digitado no teclado, incrementa NumberOfEmployees em 1 e exibe
o nome e o número do funcionário.

C#

public class Employee


{
public static int NumberOfEmployees;
private static int _counter;
private string _name;

// A read-write instance property:


public string Name
{
get => _name;
set => _name = value;
}

// A read-only static property:


public static int Counter => _counter;

// A Constructor:
public Employee() => _counter = ++NumberOfEmployees; // Calculate the
employee's number:
}

Exemplo de propriedade oculta


Este exemplo demonstra como acessar uma propriedade em uma classe base que está
oculta por outra propriedade que tem o mesmo nome em uma classe derivada:

C#

public class Employee


{
private string _name;
public string Name
{
get => _name;
set => _name = value;
}
}

public class Manager : Employee


{
private string _name;

// Notice the use of the new modifier:


public new string Name
{
get => _name;
set => _name = value + ", Manager";
}
}

class TestHiding
{
public static void Test()
{
Manager m1 = new Manager();

// Derived class property.


m1.Name = "John";

// Base class property.


((Employee)m1).Name = "Mary";

System.Console.WriteLine("Name in the derived class is: {0}",


m1.Name);
System.Console.WriteLine("Name in the base class is: {0}",
((Employee)m1).Name);
}
}
/* Output:
Name in the derived class is: John, Manager
Name in the base class is: Mary
*/

A seguir estão os pontos importantes do exemplo anterior:


A propriedade Name na classe derivada oculta a propriedade Name na classe base.
Nesse caso, o modificador new é usado na declaração da propriedade na classe
derivada:

C#

public new string Name

A conversão (Employee) é usada para acessar a propriedade oculta na classe base:

C#

((Employee)m1).Name = "Mary";

Para obter mais informações sobre como ocultar membros, consulte o Modificador new.

Exemplo de substituição de propriedade


Neste exemplo, duas classes, Cube e Square , implementam uma classe abstrata Shape e
substituem sua propriedade Area abstrata. Observe o uso do modificador override nas
propriedades. O programa aceita o lado como uma entrada e calcula as áreas para o
cubo e o quadrado. Ele também aceita a área como uma entrada e calcula o lado
correspondente para o cubo e o quadrado.

C#

abstract class Shape


{
public abstract double Area
{
get;
set;
}
}

class Square : Shape


{
public double side;

//constructor
public Square(double s) => side = s;

public override double Area


{
get => side * side;
set => side = System.Math.Sqrt(value);
}
}

class Cube : Shape


{
public double side;

//constructor
public Cube(double s) => side = s;

public override double Area


{
get => 6 * side * side;
set => side = System.Math.Sqrt(value / 6);
}
}

class TestShapes
{
static void Main()
{
// Input the side:
System.Console.Write("Enter the side: ");
double side = double.Parse(System.Console.ReadLine());

// Compute the areas:


Square s = new Square(side);
Cube c = new Cube(side);

// Display the results:


System.Console.WriteLine("Area of the square = {0:F2}", s.Area);
System.Console.WriteLine("Area of the cube = {0:F2}", c.Area);
System.Console.WriteLine();

// Input the area:


System.Console.Write("Enter the area: ");
double area = double.Parse(System.Console.ReadLine());

// Compute the sides:


s.Area = area;
c.Area = area;

// Display the results:


System.Console.WriteLine("Side of the square = {0:F2}", s.side);
System.Console.WriteLine("Side of the cube = {0:F2}", c.side);
}
}
/* Example Output:
Enter the side: 4
Area of the square = 16.00
Area of the cube = 96.00

Enter the area: 24


Side of the square = 4.90
Side of the cube = 2.00
*/

Confira também
Propriedades
Propriedades de interface
Propriedades implementadas automaticamente
Propriedades parciais
Propriedades de interface (Guia de
Programação em C#)
Artigo • 15/10/2024

As propriedades podem ser declaradas em uma interface. O exemplo a seguir declara


um acessador de propriedade de interface:

C#

public interface ISampleInterface


{
// Property declaration:
string Name
{
get;
set;
}
}

As propriedades da interface normalmente não têm um corpo. Os acessadores indicam


se a propriedade é leitura/gravação, somente leitura ou somente gravação. Ao contrário
de classes e structs, declarar os acessadores sem um corpo não declara uma
propriedade implementada automaticamente. Uma interface pode definir uma
implementação padrão para membros, incluindo propriedades. Definir uma
implementação padrão para uma propriedade em uma interface é raro porque as
interfaces não podem definir campos de dados de instância.

Exemplo
Neste exemplo, a interface IEmployee tem uma propriedade de leitura/gravação, Name e
uma propriedade somente leitura, Counter . A classe Employee implementa a interface
IEmployee e usa essas duas propriedades. O programa lê o nome de um novo

funcionário e o número atual de funcionários e exibe o nome do funcionário e o


número do funcionário computado.

Seria possível usar o nome totalmente qualificado da propriedade, que referencia a


interface na qual o membro é declarado. Por exemplo:

C#

string IEmployee.Name
{
get { return "Employee Name"; }
set { }
}

O exemplo anterior demonstra a Implementação explícita da interface. Por exemplo, se


a classe Employee está implementando duas interfaces ICitizen e IEmployee e as duas
interfaces têm a propriedade Name , é preciso haver a implementação explícita de
membro da interface. Ou seja, a seguinte declaração de propriedade:

C#

string IEmployee.Name
{
get { return "Employee Name"; }
set { }
}

Implementa a propriedade Name na interface IEmployee , enquanto a seguinte


declaração:

C#

string ICitizen.Name
{
get { return "Citizen Name"; }
set { }
}

Implementa a propriedade Name na interface ICitizen .

C#

interface IEmployee
{
string Name
{
get;
set;
}

int Counter
{
get;
}
}

public class Employee : IEmployee


{
public static int numberOfEmployees;

private string _name;


public string Name // read-write instance property
{
get => _name;
set => _name = value;
}

private int _counter;


public int Counter // read-only instance property
{
get => _counter;
}

// constructor
public Employee() => _counter = ++numberOfEmployees;
}

C#

System.Console.Write("Enter number of employees: ");


Employee.numberOfEmployees = int.Parse(System.Console.ReadLine());

Employee e1 = new Employee();


System.Console.Write("Enter the name of the new employee: ");
e1.Name = System.Console.ReadLine();

System.Console.WriteLine("The employee information:");


System.Console.WriteLine("Employee number: {0}", e1.Counter);
System.Console.WriteLine("Employee name: {0}", e1.Name);

Saída de exemplo
Console

Enter number of employees: 210


Enter the name of the new employee: Hazem Abolrous
The employee information:
Employee number: 211
Employee name: Hazem Abolrous

Confira também
Propriedades
Usando propriedades
Comparação entre propriedades e indexadores
Indexadores
Interfaces
Restringindo a acessibilidade ao
acessador (Guia de Programação em C#)
Artigo • 30/10/2024

As partes get e set de uma propriedade ou de um indexador são chamadas acessadores.


Por padrão, esses acessadores têm a mesma visibilidade ou nível de acesso da
propriedade ou do indexador aos quais pertencem. Para obter mais informações,
consulte níveis de acessibilidade. No entanto, às vezes é útil restringir o acesso a um
desses acessadores. Normalmente, você restringe a acessibilidade do acessador set ,
mantendo o acessador get publicamente acessível. Por exemplo:

C#

private string _name = "Hello";

public string Name


{
get
{
return _name;
}
protected set
{
_name = value;
}
}

Neste exemplo, uma propriedade chamada Name define um acessador get e set . O
acessador get recebe o nível de acessibilidade da propriedade em si, public nesse
caso, embora o set acessador esteja restrito explicitamente ao aplicar o modificador de
acesso protegido ao acessador em si.

7 Observação

Os exemplos neste artigo não usam propriedades implementadas


automaticamente. As propriedades implementadas automaticamente fornecem uma
sintaxe concisa para declarar propriedades quando um campo de suporte
personalizado não é necessário.
Restrições em modificadores de acesso nos
acessadores
O uso dos modificadores de acesso em propriedades ou indexadores está sujeito a estas
condições:

Não é possível usar modificadores de acessador em uma interface nem uma


implementação explícita de membro de interface.
É possível usar os modificadores de acessador somente se a propriedade ou o
indexador tiver os acessadores set e get . Nesse caso, o modificador é permitido
em apenas um dos dois acessadores.
Se a propriedade ou o indexador tiver um modificador substituir, o modificador de
acessador deverá corresponder o acessador do acessador substituído, se houver.
O nível de acessibilidade do acessador deve ser mais restritivo do que o nível de
acessibilidade na propriedade ou no indexador em si.

Modificadores de acesso em acessadores de


substituição
Quando você substitui uma propriedade ou indexador, os acessadores substituídos
devem estar acessíveis ao código de substituição. Além disso, a acessibilidade da
propriedade/indexador, e seus acessadores, devem corresponder à
propriedade/indexador substituído e seus acessadores. Por exemplo:

C#

public class Parent


{
public virtual int TestProperty
{
// Notice the accessor accessibility level.
protected set { }

// No access modifier is used here.


get { return 0; }
}
}
public class Kid : Parent
{
public override int TestProperty
{
// Use the same accessibility level as in the overridden accessor.
protected set { }

// Cannot use access modifier here.


get { return 0; }
}
}

Implementando interfaces
Quando você usa um acessador para implementar uma interface, o acessador pode não
ter um modificador de acesso. No entanto, se você implementar a interface usando um
acessador, como get , o outro acessador poderá ter um modificador de acesso, como no
exemplo a seguir:

C#

public interface ISomeInterface


{
int TestProperty
{
// No access modifier allowed here
// because this is an interface.
get;
}
}

public class TestClass : ISomeInterface


{
public int TestProperty
{
// Cannot use access modifier here because
// this is an interface implementation.
get { return 10; }

// Interface property does not have set accessor,


// so access modifier is allowed.
protected set { }
}
}

Domínio de acessibilidade do acessador


Se você usar um modificador de acesso no acessador, o domínio de acessibilidade do
acessador será determinado por esse modificador.

Se você não usou um modificador de acesso no acessador, o domínio de acessibilidade


do acessador será determinado pelo nível de acessibilidade da propriedade ou do
indexador.
Exemplo
O exemplo a seguir contém três classes, BaseClass , DerivedClass e MainClass . Há duas
propriedades no BaseClass , Name e Id em ambas as classes. O exemplo demonstra
como a propriedade Id no DerivedClass pode ser oculta pela propriedade Id no
BaseClass quando você usa um modificador de acesso restritivo como protegido ou

privado. Portanto, em vez disso, quando você atribui valores a essa propriedade, a
propriedade na classe BaseClass é chamada. Substituindo o modificador de acesso por
público tornará a propriedade acessível.

O exemplo também demonstra que um modificador de acesso restritivo, como private


ou protected , no acessador set da propriedade Name em DerivedClass impede o
acesso ao acessador na classe derivada. Ele gera um erro quando você faz uma
atribuição a ele ou acessa a propriedade de classe base com o mesmo nome, quando
acessível.

C#

public class BaseClass


{
private string _name = "Name-BaseClass";
private string _id = "ID-BaseClass";

public string Name


{
get { return _name; }
set { }
}

public string Id
{
get { return _id; }
set { }
}
}

public class DerivedClass : BaseClass


{
private string _name = "Name-DerivedClass";
private string _id = "ID-DerivedClass";

new public string Name


{
get
{
return _name;
}

// Using "protected" would make the set accessor not accessible.


set
{
_name = value;
}
}

// Using private on the following property hides it in the Main Class.


// Any assignment to the property will use Id in BaseClass.
new private string Id
{
get
{
return _id;
}
set
{
_id = value;
}
}
}

class MainClass
{
static void Main()
{
BaseClass b1 = new BaseClass();
DerivedClass d1 = new DerivedClass();

b1.Name = "Mary";
d1.Name = "John";

b1.Id = "Mary123";
d1.Id = "John123"; // The BaseClass.Id property is called.

System.Console.WriteLine("Base: {0}, {1}", b1.Name, b1.Id);


System.Console.WriteLine("Derived: {0}, {1}", d1.Name, d1.Id);

// Keep the console window open in debug mode.


System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
}
/* Output:
Base: Name-BaseClass, ID-BaseClass
Derived: John, ID-BaseClass
*/

Comentários
Observe que, se você substituir a declaração new private string Id por new public
string Id , você obterá a saída:
Name and ID in the base class: Name-BaseClass, ID-BaseClass Name and ID in the
derived class: John, John123

Confira também
Propriedades
Indexadores
Modificadores de acesso
Somente propriedades de inicialização
Propriedades obrigatórias
Como declarar e usar propriedades de
leitura e gravação (Guia de
Programação em C#)
Artigo • 07/04/2023

As propriedades oferecem a conveniência de membros de dados públicos sem os riscos


associados ao acesso sem proteção, sem controle e não verificado aos dados de um
objeto. Propriedades declaram acessadores: métodos especiais que atribuem e
recuperam valores do membro de dados subjacente. O acessador set habilita a
atribuição de membros de dados e o acessador get recupera valores do membro de
dados.

Este exemplo mostra uma classe Person que tem duas propriedades: Name (string) e
Age (int). Ambas as propriedades fornecem acessadores get e set , portanto, são

consideradas propriedades de leitura/gravação.

Exemplo
C#

class Person
{
private string _name = "N/A";
private int _age = 0;

// Declare a Name property of type string:


public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}

// Declare an Age property of type int:


public int Age
{
get
{
return _age;
}

set
{
_age = value;
}
}

public override string ToString()


{
return "Name = " + Name + ", Age = " + Age;
}
}

public class Wrapper


{
private string _name = "N/A";
public string Name
{
get
{
return _name;
}
private set
{
_name = value;
}
}

class TestPerson
{
static void Main()
{
// Create a new Person object:
Person person = new Person();

// Print out the name and the age associated with the person:
Console.WriteLine("Person details - {0}", person);

// Set some values on the person object:


person.Name = "Joe";
person.Age = 99;
Console.WriteLine("Person details - {0}", person);

// Increment the Age property:


person.Age += 1;
Console.WriteLine("Person details - {0}", person);

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Person details - Name = N/A, Age = 0
Person details - Name = Joe, Age = 99
Person details - Name = Joe, Age = 100
*/

Programação robusta
No exemplo anterior, as propriedades Name e Age são públicas e incluem os
acessadores get e set . Os acessadores públicos permitem que qualquer objeto leia e
grave essas propriedades. No entanto, às vezes é desejável excluir um os acessadores.
Você pode omitir o acessador set para tornar a propriedade somente leitura:

C#

public string Name


{
get
{
return _name;
}
private set
{
_name = value;
}
}

Como alternativa, é possível expor um acessador publicamente, porém, tornando o


outro privado ou protegido. Para obter mais informações, consulte Acessibilidade do
Acessador Assimétrico.

Depois de serem declaradas, as propriedades podem ser usadas como campos da


classe. As propriedades permitem uma sintaxe natural na obtenção e configuração do
valor de uma propriedade, conforme as instruções a seguir:

C#

person.Name = "Joe";
person.Age = 99;

Em um método de propriedade set , uma variável especial value está disponível. Essa
variável contém o valor que o usuário especificou, por exemplo:

C#
_name = value;

Observe a sintaxe normal para incrementar a propriedade Age em um objeto Person :

C#

person.Age += 1;

Se métodos set e get separados fossem usados para modelar propriedades, o código
equivalente se pareceria com isto:

C#

person.SetAge(person.GetAge() + 1);

O método ToString é substituído neste exemplo:

C#

public override string ToString()


{
return "Name = " + Name + ", Age = " + Age;
}

Observe que ToString não é usado explicitamente no programa. Ele é invocado por
padrão pelas chamadas WriteLine .

Confira também
Propriedades
Sistema de tipos do C#
Propriedades implementadas
automaticamente
Artigo • 14/11/2024

As propriedades implementadas automaticamente tornam a declaração de propriedade


mais concisa quando nenhuma outra lógica é necessária nos acessadores de
propriedade. Elas também habilitam o código do cliente a criar objetos. Ao declarar uma
propriedade, como mostrado no exemplo a seguir, o compilador cria um campo de
suporte privado e anônimo que pode ser acessado somente por meio dos acessadores
get e set da propriedade. init Os acessadores também podem ser declarados como

propriedades implementadas automaticamente.

O exemplo a seguir mostra uma classe simples que tem algumas propriedades
implementadas automaticamente:

C#

// This class is mutable. Its data can be modified from


// outside the class.
public class Customer
{
// Auto-implemented properties for trivial get and set
public double TotalPurchases { get; set; }
public string Name { get; set; }
public int CustomerId { get; set; }

// Constructor
public Customer(double purchases, string name, int id)
{
TotalPurchases = purchases;
Name = name;
CustomerId = id;
}

// Methods
public string GetContactInfo() { return "ContactInfo"; }
public string GetTransactionHistory() { return "History"; }

// .. Additional methods, events, etc.


}

class Program
{
static void Main()
{
// Initialize a new object.
Customer cust1 = new Customer(4987.63, "Northwind", 90108);
// Modify a property.
cust1.TotalPurchases += 499.99;
}
}

Você não pode declarar propriedades implementadas automaticamente em interfaces.


As propriedades implementadas automaticamente e com suporte de campo declaram
um campo de suporte de instância privada e as interfaces não podem declarar campos
de instância. Declarar uma propriedade em uma interface sem definir um corpo declara
uma propriedade com acessadores. Cada tipo que implementa essa interface deve
implementar essa propriedade.

Você pode inicializar propriedades implementadas automaticamente de forma


semelhante aos campos:

C#

public string FirstName { get; set; } = "Jane";

A classe mostrada no exemplo anterior é mutável. O código cliente pode alterar os


valores nos objetos após a criação. Em classes complexas que contêm comportamento
significativo (métodos) e dados, geralmente é necessário ter propriedades públicas. No
entanto, para classes pequenas ou structs que encapsulam apenas um conjunto de
valores (dados) e têm pouco ou nenhum comportamento, você deve usar uma das
seguintes opções para tornar os objetos imutáveis:

Declare apenas um acessador get (imutável em todos os lugares, exceto o


construtor).
Declare um acessador get e um acessador init (imutável em todos os lugares,
exceto durante a construção do objeto).
Declare o acessador set como privado (imutável para os consumidores).

Para obter mais informações, consulte Como implementar uma classe leve com
propriedades implementadas automaticamente.

Talvez seja necessário adicionar validação a uma propriedade implementada


automaticamente. O C# 13 adiciona propriedades com suporte de campo como um
recurso de visualização. Use a field palavra-chave para acessar o campo de suporte
sintetizado do compilador de uma propriedade implementada automaticamente. Por
exemplo, você pode garantir que a FirstName propriedade no exemplo anterior não
possa ser definida como null ou a cadeia de caracteres vazia:

C#
public string FirstName
{
get;
set
{
field = (string.IsNullOrWhiteSpace(value) is false
? value
: throw new ArgumentException(nameof(value), "First name can't
be whitespace or null"));
}
} = "Jane";

Esse recurso permite que você adicione lógica aos acessadores sem exigir que você
declare explicitamente o campo de suporte. Use a field palavra-chave para acessar o
campo de suporte gerado pelo compilador.

) Importante

A field palavra-chave é um recurso de visualização no C# 13. Você deve estar


usando o .NET 9 e definir seu <LangVersion> elemento como preview no arquivo
de projeto para usar a field palavra-chave contextual.

Você deve ter cuidado ao usar o recurso de field palavra-chave em uma classe
que tenha um campo chamado field . A nova field palavra-chave sombreia um
campo nomeado field no escopo de um acessador de propriedade. Você pode
alterar o nome da field variável ou usar o @ token para fazer referência ao field
identificador como @field . Você pode saber mais lendo a especificação do recurso
para a field palavra-chave.

Confira também
Usar propriedades implementadas automaticamente (regra de estilo IDE0032)
Propriedades
Modificadores
Como implementar uma classe leve com
propriedades implementadas
automaticamente
Artigo • 26/10/2024

Este exemplo mostra como criar uma classe leve imutável que serve apenas para
encapsular um conjunto de propriedades implementadas automaticamente. Use esse
tipo de constructo em vez de um struct quando for necessário usar a semântica do tipo
de referência.

Você pode tornar uma propriedade imutável das seguintes maneiras:

Declare somente o acessador get, o que torna a propriedade imutável em todos os


lugares, exceto no construtor do tipo.
Declare um acessador init em vez de set , o que torna a propriedade configurável
somente no construtor ou com o uso de um inicializador de objeto.
Declare o acessador set como private. A propriedade será configurável somente
dentro do tipo, mas imutável para os consumidores.

Você pode adicionar o modificador required à declaração de propriedade para forçar os


chamadores a definir a propriedade como parte da inicialização de um novo objeto.

O exemplo a seguir mostra como uma propriedade somente com o acessador get difere
de outra com get e conjunto privado.

C#

class Contact
{
public string Name { get; }
public string Address { get; private set; }

public Contact(string contactName, string contactAddress)


{
// Both properties are accessible in the constructor.
Name = contactName;
Address = contactAddress;
}

// Name isn't assignable here. This will generate a compile error.


//public void ChangeName(string newName) => Name = newName;

// Address is assignable here.


public void ChangeAddress(string newAddress) => Address = newAddress;
}

Exemplo
O exemplo a seguir mostra duas maneiras de implementar uma classe imutável que
implementou propriedades automaticamente. Entre essas maneiras, uma declara uma
das propriedades com um set privado e outra declara uma das propriedades somente
com um get . A primeira classe usa um construtor somente para inicializar as
propriedades e a segunda classe usa um método de fábrica estático que chama um
construtor.

C#

// This class is immutable. After an object is created,


// it cannot be modified from outside the class. It uses a
// constructor to initialize its properties.
class Contact
{
// Read-only property.
public string Name { get; }

// Read-write property with a private set accessor.


public string Address { get; private set; }

// Public constructor.
public Contact(string contactName, string contactAddress)
{
Name = contactName;
Address = contactAddress;
}
}

// This class is immutable. After an object is created,


// it cannot be modified from outside the class. It uses a
// static method and private constructor to initialize its properties.
public class Contact2
{
// Read-write property with a private set accessor.
public string Name { get; private set; }

// Read-only property.
public string Address { get; }

// Private constructor.
private Contact2(string contactName, string contactAddress)
{
Name = contactName;
Address = contactAddress;
}
// Public factory method.
public static Contact2 CreateContact(string name, string address)
{
return new Contact2(name, address);
}
}

public class Program


{
static void Main()
{
// Some simple data sources.
string[] names = ["Terry Adams","Fadi Fakhouri", "Hanying Feng",
"Cesar Garcia", "Debra Garcia"];
string[] addresses = ["123 Main St.", "345 Cypress Ave.", "678 1st
Ave",
"12 108th St.", "89 E. 42nd St."];

// Simple query to demonstrate object creation in select clause.


// Create Contact objects by using a constructor.
var query1 = from i in Enumerable.Range(0, 5)
select new Contact(names[i], addresses[i]);

// List elements cannot be modified by client code.


var list = query1.ToList();
foreach (var contact in list)
{
Console.WriteLine("{0}, {1}", contact.Name, contact.Address);
}

// Create Contact2 objects by using a static factory method.


var query2 = from i in Enumerable.Range(0, 5)
select Contact2.CreateContact(names[i],
addresses[i]);

// Console output is identical to query1.


var list2 = query2.ToList();

// List elements cannot be modified by client code.


// CS0272:
// list2[0].Name = "Eugene Zabokritski";
}
}

/* Output:
Terry Adams, 123 Main St.
Fadi Fakhouri, 345 Cypress Ave.
Hanying Feng, 678 1st Ave
Cesar Garcia, 12 108th St.
Debra Garcia, 89 E. 42nd St.
*/
O compilador cria campos de suporte para cada propriedade implementada
automaticamente. Os campos não são acessíveis diretamente do código-fonte.

Confira também
Propriedades
struct
Inicializadores de objeto e coleção
Métodos (Guia de Programação em C#)
Artigo • 07/04/2023

Um método é um bloco de código que contém uma série de instruções. Um programa


faz com que as instruções sejam executadas chamando o método e especificando os
argumentos de método necessários. No C#, todas as instruções executadas são
realizadas no contexto de um método.

O método Main é o ponto de entrada para todos os aplicativos C# e é chamado pelo


CLR (Common Language Runtime) quando o programa é iniciado. Em um aplicativo que
usa instruções de nível superior, o Main método é gerado pelo compilador e contém
todas as instruções de nível superior.

7 Observação

Este artigo discute métodos nomeados. Para obter mais informações sobre funções
anônimas, consulte Expressões lambda.

Assinaturas de método
Os métodos são declarados em uma classe, struct ou interface especificando o nível de
acesso, como public ou private , modificadores opcionais, como abstract ou sealed ,
o valor retornado, o nome do método e os parâmetros de método. Juntas, essas partes
são a assinatura do método.

) Importante

Um tipo de retorno de um método não faz parte da assinatura do método para fins
de sobrecarga de método. No entanto, ele faz parte da assinatura do método ao
determinar a compatibilidade entre um delegado e o método para o qual ele
aponta.

Os parâmetros de método estão entre parênteses e separados por vírgulas. Parênteses


vazios indicam que o método não requer parâmetros. Essa classe contém quatro
métodos:

C#

abstract class Motorcycle


{
// Anyone can call this.
public void StartEngine() {/* Method statements here */ }

// Only derived classes can call this.


protected void AddGas(int gallons) { /* Method statements here */ }

// Derived classes can override the base class implementation.


public virtual int Drive(int miles, int speed) { /* Method statements
here */ return 1; }

// Derived classes must implement this.


public abstract double GetTopSpeed();
}

Acesso a método
Chamar um método em um objeto é como acessar um campo. Após o nome do objeto,
adicione um ponto final, o nome do método e parênteses. Os argumentos são listados
dentro dos parênteses e são separados por vírgulas. Os métodos da classe Motorcycle
podem, portanto, ser chamados como no exemplo a seguir:

C#

class TestMotorcycle : Motorcycle


{

public override double GetTopSpeed()


{
return 108.4;
}

static void Main()


{

TestMotorcycle moto = new TestMotorcycle();

moto.StartEngine();
moto.AddGas(15);
moto.Drive(5, 20);
double speed = moto.GetTopSpeed();
Console.WriteLine("My top speed is {0}", speed);
}
}

Parâmetros de método versus argumentos


A definição do método especifica os nomes e tipos de quaisquer parâmetros
obrigatórios. Quando o código de chamada chama o método, ele fornece valores
concretos, chamados argumentos, para cada parâmetro. Os argumentos devem ser
compatíveis com o tipo de parâmetro, mas o nome do argumento (se algum) usado no
código de chamada não precisa ser o mesmo que o parâmetro chamado definido no
método. Por exemplo:

C#

public void Caller()


{
int numA = 4;
// Call with an int variable.
int productA = Square(numA);

int numB = 32;


// Call with another int variable.
int productB = Square(numB);

// Call with an integer literal.


int productC = Square(12);

// Call with an expression that evaluates to int.


productC = Square(productA * 3);
}

int Square(int i)
{
// Store input argument in a local variable.
int input = i;
return input * input;
}

Passando por referência versus passando por


valor
Por padrão, quando uma instância de um tipo de valor é passada para um método, sua
cópia é passada em vez da própria instância. Portanto, as alterações no argumento não
têm nenhum efeito sobre a instância original no método de chamada. Para passar uma
instância de tipo de valor por referência, use a palavra-chave ref . Para obter mais
informações, consulte Passando parâmetros de tipo de valor.

Quando um objeto de tipo de referência é passado para um método, uma referência ao


objeto é passada. Ou seja, o método recebe não o objeto em si, mas um argumento que
indica o local do objeto. Se você alterar um membro do objeto usando essa referência, a
alteração será refletida no argumento no método de chamada, ainda que você passe o
objeto por valor.

Crie um tipo de referência usando a palavra-chave class , como mostra o exemplo a


seguir:

C#

public class SampleRefType


{
public int value;
}

Agora, se você passar um objeto com base nesse tipo para um método, uma referência
ao objeto será passada. O exemplo a seguir passa um objeto do tipo SampleRefType ao
método ModifyObject :

C#

public static void TestRefType()


{
SampleRefType rt = new SampleRefType();
rt.value = 44;
ModifyObject(rt);
Console.WriteLine(rt.value);
}

static void ModifyObject(SampleRefType obj)


{
obj.value = 33;
}

O exemplo faz essencialmente a mesma coisa que o exemplo anterior, pois ele passa
um argumento por valor para um método. No entanto, como um tipo de referência é
usado, o resultado é diferente. A modificação feita em ModifyObject para o campo
value do parâmetro, obj , também altera o campo value do argumento, rt , no
método TestRefType . O método TestRefType exibe 33 como a saída.

Para obter mais informações sobre como passar tipos de referência por referência e por
valor, consulte Passando parâmetros de tipo de referência e Tipos de referência.

Valores retornados
Os métodos podem retornar um valor para o chamador. Se o tipo de retorno (o tipo
listado antes do nome do método) não for void , o método poderá retornar o valor
usando a instrução return. Uma instrução com a palavra-chave return seguida por uma
variável que corresponde ao tipo de retorno retornará esse valor ao chamador do
método.

O valor pode ser retornado ao chamador por valor ou por referência. Valores são
retornados ao chamador por referência se a ref palavra-chave é usada na assinatura do
método e segue cada palavra-chave return . Por exemplo, a instrução de retorno e a
assinatura de método a seguir indicam que o método retorna uma variável chamada
estDistance por referência para o chamador.

C#

public ref double GetEstimatedDistance()


{
return ref estDistance;
}

A palavra-chave return também interrompe a execução do método. Se o tipo de


retorno for void , uma instrução return sem um valor ainda será útil para interromper a
execução do método. Sem a palavra-chave return , a execução do método será
interrompida quando chegar ao final do bloco de código. Métodos com um tipo de
retorno não nulo devem usar a palavra-chave return para retornar um valor. Por
exemplo, esses dois métodos usam a palavra-chave return para retornar inteiros:

C#

class SimpleMath
{
public int AddTwoNumbers(int number1, int number2)
{
return number1 + number2;
}

public int SquareANumber(int number)


{
return number * number;
}
}

Para usar um valor retornado de um método, o método de chamada pode usar a


chamada de método em si em qualquer lugar que um valor do mesmo tipo seria
suficiente. Você também pode atribuir o valor retornado a uma variável. Por exemplo, os
dois exemplos de código a seguir obtêm a mesma meta:

C#
int result = obj.AddTwoNumbers(1, 2);
result = obj.SquareANumber(result);
// The result is 9.
Console.WriteLine(result);

C#

result = obj.SquareANumber(obj.AddTwoNumbers(1, 2));


// The result is 9.
Console.WriteLine(result);

Usar uma variável local, nesse caso, result , para armazenar um valor é opcional. Isso
pode ajudar a legibilidade do código ou pode ser necessário se você precisar armazenar
o valor original do argumento para todo o escopo do método.

Para usar o valor retornado de um método por referência, você deve declarar uma
variável ref local se você pretende modificar seu valor. Por exemplo, se o método
Planet.GetEstimatedDistance retorna um valor Double por referência, você pode defini-

lo como uma variável ref local com código semelhante ao seguinte:

C#

ref double distance = ref Planet.GetEstimatedDistance();

Retornar uma matriz multidimensional de um método, M , que modifica o conteúdo da


matriz, não é necessário se a função de chamada passou a matriz para M . Você pode
retornar a matriz resultante de M para um bom estilo ou fluxo funcional de valores, mas
isso não é necessário porque o C# passa todos os tipos de referência por valor e o valor
de uma referência de matriz é o ponteiro para a matriz. No método M , as alterações do
conteúdo da matriz podem ser observadas por qualquer código que tiver uma
referência à matriz, conforme mostrado no exemplo a seguir:

C#

static void Main(string[] args)


{
int[,] matrix = new int[2, 2];
FillMatrix(matrix);
// matrix is now full of -1
}

public static void FillMatrix(int[,] matrix)


{
for (int i = 0; i < matrix.GetLength(0); i++)
{
for (int j = 0; j < matrix.GetLength(1); j++)
{
matrix[i, j] = -1;
}
}
}

Métodos assíncronos
Usando o recurso async, você pode invocar métodos assíncronos sem usar retornos de
chamada explícitos ou dividir manualmente seu código entre vários métodos ou
expressões lambda.

Se marcar um método com o modificador async, você poderá usar o operador await no
método. Quando o controle atinge uma expressão await no método assíncrono, ele
retorna para o chamador e o progresso no método é suspenso até a tarefa aguardada
ser concluída. Quando a tarefa for concluída, a execução poderá ser retomada no
método.

7 Observação

Um método assíncrono retorna para o chamador quando encontra o primeiro


objeto esperado que ainda não está completo ou chega ao final do método
assíncrono, o que ocorrer primeiro.

Um método assíncrono normalmente tem um tipo de retornoTask<TResult>, Task,


IAsyncEnumerable<T> ou void . O tipo de retorno void é usado principalmente para
definir manipuladores de eventos, nos quais o tipo de retorno void é necessário. Um
método assíncrono que retorna void não pode ser aguardado e o chamador de um
método de retorno nulo não pode capturar as exceções que esse método gera. Um
método assíncrono pode ter qualquer tipo de retorno semelhante a tarefas.

No exemplo a seguir, DelayAsync é um método assíncrono que tem um tipo de retorno


de Task<TResult>. DelayAsync tem uma instrução return que retorna um número
inteiro. Portanto, a declaração do método de Task<int> deve ter um tipo de retorno de
DelayAsync . Como o tipo de retorno é Task<int> , a avaliação da expressão await em
DoSomethingAsync produz um inteiro, como a instrução a seguir demonstra: int result

= await delayTask .

O método Main é um exemplo de método assíncrono que tem um tipo de retorno Task.
Ele vai para o método DoSomethingAsync e, como é expresso com uma única linha, ele
pode omitir as palavras-chave async e await . Como DoSomethingAsync é um método
assíncrono, a tarefa para a chamada para DoSomethingAsync deve ser colocada em
espera, como mostra a seguinte instrução: await DoSomethingAsync(); .

C#

class Program
{
static Task Main() => DoSomethingAsync();

static async Task DoSomethingAsync()


{
Task<int> delayTask = DelayAsync();
int result = await delayTask;

// The previous two statements may be combined into


// the following statement.
//int result = await DelayAsync();

Console.WriteLine($"Result: {result}");
}

static async Task<int> DelayAsync()


{
await Task.Delay(100);
return 5;
}
}
// Example output:
// Result: 5

Um método assíncrono não pode declarar nenhum parâmetro ref ou out, mas pode
chamar métodos com tais parâmetros.

Para obter mais informações sobre os métodos assíncronos, consulte Programação


assíncrona com async e await e Tipos de retorno Async.

Definições de corpo de expressão


É comum ter definições de método que simplesmente retornam imediatamente com o
resultado de uma expressão ou que têm uma única instrução como o corpo do método.
Há um atalho de sintaxe para definir esses métodos usando => :

C#

public Point Move(int dx, int dy) => new Point(x + dx, y + dy);
public void Print() => Console.WriteLine(First + " " + Last);
// Works with operators, properties, and indexers too.
public static Complex operator +(Complex a, Complex b) => a.Add(b);
public string Name => First + " " + Last;
public Customer this[long id] => store.LookupCustomer(id);

Se o método retornar void ou for um método assíncrono, o corpo do método deverá


ser uma expressão de instrução (igual às lambdas). Para propriedades e indexadores,
eles devem ser somente leitura e você não usa a palavra-chave do acessador get .

Iterators
Um iterador realiza uma iteração personalizada em uma coleção, como uma lista ou
uma matriz. Um iterador usa a instrução yield return para retornar um elemento de cada
vez. Quando uma instrução yield return for atingida, o local atual no código será
lembrado. A execução será reiniciada desse local quando o iterador for chamado na
próxima vez.

Você chama um iterador de um código de cliente usando uma instrução foreach.

O tipo de retorno de um iterador pode ser IEnumerable, IEnumerable<T>,


IAsyncEnumerable<T>, IEnumerator ou IEnumerator<T>.

Para obter mais informações, consulte Iteradores.

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
Guia de Programação em C#
Sistema de tipos do C#
Modificadores de acesso
Classes static e membros de classes static
Herança
Classes e membros de classes abstract e sealed
params
out
ref
Parâmetros de método
Funções locais (Guia de Programação
em C#)
Artigo • 27/04/2023

Funções locais são métodos de um tipo que estão aninhados em outro membro. Eles só
podem ser chamados do membro que os contém. Funções locais podem ser declaradas
em e chamadas de:

Métodos, especialmente os métodos iteradores e os métodos assíncronos


Construtores
Acessadores de propriedades
Acessadores de eventos
Métodos anônimos
Expressões lambda
Finalizadores
Outras funções locais

No entanto, as funções locais não podem ser declaradas dentro de um membro apto
para expressão.

7 Observação

Em alguns casos, você pode usar uma expressão lambda para implementar uma
funcionalidade que também tem suporte por uma função local. Para obter uma
comparação, confira Funções locais em comparação a expressões Lambda.

Funções locais tornam a intenção do seu código clara. Qualquer pessoa que leia o
código poderá ver que o método não pode ser chamado, exceto pelo método que o
contém. Para projetos de equipe, elas também impossibilitam que outro desenvolvedor
chame o método por engano diretamente de qualquer outro lugar na classe ou no
struct.

Sintaxe de função local


Uma função local é definida como um método aninhado dentro de um membro
recipiente. Sua definição tem a seguinte sintaxe:

C#

<modifiers> <return-type> <method-name> <parameter-list>


Você pode usar os seguintes modificadores com uma função local:

async
unsafe
static Uma função local estática não pode capturar variáveis locais nem o estado
da instância.
extern Uma função local externa deve ser static .

Todas as variáveis locais definidas no membro relativo, incluindo os parâmetros do


método, são acessíveis em uma função local não estática.

Ao contrário de uma definição de método, uma definição de função local não pode
incluir o modificador de acesso de membro. Já que todas as funções locais são privadas,
incluir um modificador de acesso como a palavra-chave private gera o erro do
compilador CS0106, "O modificador 'private' não é válido para este item".

O exemplo a seguir define uma função local chamada AppendPathSeparator que é


privada para um método chamado GetText :

C#

private static string GetText(string path, string filename)


{
var reader = File.OpenText($"{AppendPathSeparator(path)}{filename}");
var text = reader.ReadToEnd();
return text;

string AppendPathSeparator(string filepath)


{
return filepath.EndsWith(@"\") ? filepath : filepath + @"\";
}
}

A partir do C# 9.0, você pode aplicar atributos a uma função local, seus parâmetros e
parâmetros de tipo, como mostra o exemplo a seguir:

C#

#nullable enable
private static void Process(string?[] lines, string mark)
{
foreach (var line in lines)
{
if (IsValid(line))
{
// Processing logic...
}
}

bool IsValid([NotNullWhen(true)] string? line)


{
return !string.IsNullOrEmpty(line) && line.Length >= mark.Length;
}
}

O exemplo anterior usa um atributo especial para ajudar o compilador na análise


estática em um contexto anulável.

Funções locais e exceções


Um dos recursos úteis de funções locais é que elas podem permitir que exceções sejam
apresentadas imediatamente. Para métodos de iteradores, as exceções são apresentadas
somente quando a sequência retornada é enumerada e não quando o iterador é
recuperado. Para métodos assíncronos, as exceções geradas em um método assíncrono
são observadas quando a tarefa retornada é esperada.

O exemplo a seguir define um método OddSequence que enumera números ímpares em


um intervalo especificado. Já que ele passa um número maior que 100 para o método
enumerador OddSequence , o método gera uma ArgumentOutOfRangeException. Assim
como demonstrado pela saída do exemplo, a exceção é apresentada somente quando
você itera os números e não quando você recupera o enumerador.

C#

public class IteratorWithoutLocalExample


{
public static void Main()
{
IEnumerable<int> xs = OddSequence(50, 110);
Console.WriteLine("Retrieved enumerator...");

foreach (var x in xs) // line 11


{
Console.Write($"{x} ");
}
}

public static IEnumerable<int> OddSequence(int start, int end)


{
if (start < 0 || start > 99)
throw new ArgumentOutOfRangeException(nameof(start), "start must be
between 0 and 99.");
if (end > 100)
throw new ArgumentOutOfRangeException(nameof(end), "end must be
less than or equal to 100.");
if (start >= end)
throw new ArgumentException("start must be less than end.");

for (int i = start; i <= end; i++)


{
if (i % 2 == 1)
yield return i;
}
}
}
// The example displays the output like this:
//
// Retrieved enumerator...
// Unhandled exception. System.ArgumentOutOfRangeException: end must be
less than or equal to 100. (Parameter 'end')
// at IteratorWithoutLocalExample.OddSequence(Int32 start, Int32
end)+MoveNext() in IteratorWithoutLocal.cs:line 22
// at IteratorWithoutLocalExample.Main() in IteratorWithoutLocal.cs:line
11

Se você colocar a lógica do iterador em uma função local, as exceções de validação de


argumento serão geradas quando você recuperar o enumerador, como mostra o
exemplo a seguir:

C#

public class IteratorWithLocalExample


{
public static void Main()
{
IEnumerable<int> xs = OddSequence(50, 110); // line 8
Console.WriteLine("Retrieved enumerator...");

foreach (var x in xs)


{
Console.Write($"{x} ");
}
}

public static IEnumerable<int> OddSequence(int start, int end)


{
if (start < 0 || start > 99)
throw new ArgumentOutOfRangeException(nameof(start), "start must be
between 0 and 99.");
if (end > 100)
throw new ArgumentOutOfRangeException(nameof(end), "end must be
less than or equal to 100.");
if (start >= end)
throw new ArgumentException("start must be less than end.");

return GetOddSequenceEnumerator();
IEnumerable<int> GetOddSequenceEnumerator()
{
for (int i = start; i <= end; i++)
{
if (i % 2 == 1)
yield return i;
}
}
}
}
// The example displays the output like this:
//
// Unhandled exception. System.ArgumentOutOfRangeException: end must be
less than or equal to 100. (Parameter 'end')
// at IteratorWithLocalExample.OddSequence(Int32 start, Int32 end) in
IteratorWithLocal.cs:line 22
// at IteratorWithLocalExample.Main() in IteratorWithLocal.cs:line 8

Funções locais vs. expressões lambda


À primeira vista, funções locais e expressões lambda são muito semelhantes. Em muitos
casos, a escolha entre usar expressões lambda e funções locais é uma questão de estilo
e preferência pessoal. No entanto, há diferenças reais nos casos em que você pode usar
uma ou outra, e é importante conhecer essas diferenças.

Examinaremos as diferenças entre a função local e as implementações de expressão


lambda do algoritmo fatorial. Esta é a versão que usa uma função local:

C#

public static int LocalFunctionFactorial(int n)


{
return nthFactorial(n);

int nthFactorial(int number) => number < 2


? 1
: number * nthFactorial(number - 1);
}

Esta versão usa expressões lambda:

C#

public static int LambdaFactorial(int n)


{
Func<int, int> nthFactorial = default(Func<int, int>);
nthFactorial = number => number < 2
? 1
: number * nthFactorial(number - 1);

return nthFactorial(n);
}

Nomenclatura
As funções locais são explicitamente nomeadas como métodos. As expressões lambda
são métodos anônimos e precisam ser atribuídas a variáveis de um tipo delegate ,
normalmente os tipos Action ou Func . Quando você declara uma função local, o
processo é como gravar um método normal. Você declara um tipo de retorno e uma
assinatura de função.

Assinaturas de função e tipos de expressão lambda


As expressões lambda dependem do tipo da variável Action / Func atribuída para
determinar o argumento e os tipos de retorno. Em funções locais, como a sintaxe é
muito parecida com gravar um método normal, os tipos de argumento e o tipo de
retorno já fazem parte da declaração de função.

A partir do C# 10, algumas expressões lambda têm um tipo natural, o que permite que
o compilador infira o tipo de retorno e os tipos de parâmetro da expressão lambda.

Atribuição definida
As expressões lambda são objetos declarados e atribuídos em tempo de execução. Para
que uma expressão lambda seja usada, ela precisa ser atribuída de maneira definitiva: a
variável Action / Func à qual ela será atribuída deve ser declarada e a expressão lambda
atribuída a ela. Observe que LambdaFactorial deve declarar e inicializar a expressão
lambda nthFactorial antes de defini-la. Não fazer isso resulta em um erro em tempo
de compilação para referenciar nthFactorial antes de atribuí-lo.

As funções locais são definidas em tempo de compilação. Como elas não são atribuídas
a variáveis, podem ser referenciadas em qualquer local de código em que esteja no
escopo. Em nosso primeiro exemplo LocalFunctionFactorial , podemos declarar nossa
função local acima ou abaixo da instrução return e não disparar erros do compilador.

Essas diferenças significam que os algoritmos recursivos são mais fáceis de criar usando
funções locais. Você pode declarar e definir uma função local que chame a si mesma. As
expressões lambda devem ser declaradas e atribuídas a um valor padrão antes que
possam ser reatribuídas a um corpo que referencie a mesma expressão lambda.

Implementação como delegado


As expressões lambda são convertidas em delegados quando declaradas. As funções
locais são mais flexíveis, pois podem ser gravadas como método tradicional ou como
delegado. As funções locais só são convertidas em delegados quando usadas como
delegado.

Se você declarar uma função local e só referenciá-la ao chamá-la como um método, ela
não será convertida em um delegado.

Captura de variável
As regras de atribuição definitiva também afetam as variáveis capturadas pela função
local ou pela expressão lambda. O compilador pode executar uma análise estática, que
permite que as funções locais atribuam de maneira definitiva as variáveis capturadas no
escopo delimitador. Considere este exemplo:

C#

int M()
{
int y;
LocalFunction();
return y;

void LocalFunction() => y = 0;


}

O compilador pode determinar que LocalFunction definitivamente atribua y quando


chamada. Como a LocalFunction é chamada antes da instrução return , y é atribuído
definitivamente na instrução return .

Observe que quando uma função local captura variáveis no escopo delimitador, a
função local é implementada como tipo de delegado.

Alocações de heap
Dependendo do uso, as funções locais podem evitar as alocações de heap que são
sempre necessárias nas expressões lambda. Se uma função local nunca é convertida em
um delegado, e nenhuma das variáveis capturadas pela função local é capturada por
outras lambdas ou funções locais convertidas em delegados, o compilador pode evitar
alocações de heap.

Considere este exemplo assíncrono:

C#

public async Task<string> PerformLongRunningWorkLambda(string address, int


index, string name)
{
if (string.IsNullOrWhiteSpace(address))
throw new ArgumentException(message: "An address is required",
paramName: nameof(address));
if (index < 0)
throw new ArgumentOutOfRangeException(paramName: nameof(index),
message: "The index must be non-negative");
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException(message: "You must supply a name",
paramName: nameof(name));

Func<Task<string>> longRunningWorkImplementation = async () =>


{
var interimResult = await FirstWork(address);
var secondResult = await SecondStep(index, name);
return $"The results are {interimResult} and {secondResult}.
Enjoy.";
};

return await longRunningWorkImplementation();


}

O fechamento desta expressão lambda contém as variáveis address , index e name . No


caso de funções locais, o objeto que implementa o encerramento pode ser um tipo
struct . Esse tipo de struct seria passado por referência à função local. Essa diferença na
implementação poderia economizar em uma alocação.

A instanciação necessária para expressões lambda ocasiona alocações adicionais de


memória, tornando-se um fator de desempenho em caminhos de código com tempo
crítico. As funções locais não incorrem nessa sobrecarga. No exemplo acima, a versão
das funções locais tem duas alocações a menos que a versão da expressão lambda.

Se você sabe que a função local não será convertida em um delegado e nenhuma das
variáveis capturadas por ela será capturada por outras lambdas ou funções locais
convertidas em delegados, você pode garantir que a função local evite ser alocada no
heap, declarando-a como função local static .

 Dica
Habilite a regra de estilo de código do .NETIDE0062, para garantir que as funções
locais sejam sempre marcadas como static .

7 Observação

A função local equivalente desse método também usa uma classe para o
fechamento. O fechamento de uma função local ser implementado como um
class ou como um struct , trata-se de um detalhe de implementação. Uma função

local pode usar um struct , enquanto uma lambda sempre usará um class .

C#

public async Task<string> PerformLongRunningWork(string address, int index,


string name)
{
if (string.IsNullOrWhiteSpace(address))
throw new ArgumentException(message: "An address is required",
paramName: nameof(address));
if (index < 0)
throw new ArgumentOutOfRangeException(paramName: nameof(index),
message: "The index must be non-negative");
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException(message: "You must supply a name",
paramName: nameof(name));

return await longRunningWorkImplementation();

async Task<string> longRunningWorkImplementation()


{
var interimResult = await FirstWork(address);
var secondResult = await SecondStep(index, name);
return $"The results are {interimResult} and {secondResult}.
Enjoy.";
}
}

Uso da palavra-chave yield


Uma vantagem final não demonstrada neste exemplo é que as funções locais podem
ser implementadas como iteradores, usando a sintaxe yield return para produzir uma
sequência de valores.

C#

public IEnumerable<string> SequenceToLowercase(IEnumerable<string> input)


{
if (!input.Any())
{
throw new ArgumentException("There are no items to convert to
lowercase.");
}

return LowercaseIterator();

IEnumerable<string> LowercaseIterator()
{
foreach (var output in input.Select(item => item.ToLower()))
{
yield return output;
}
}
}

A instrução yield return não é permitida em expressões lambda, confira erro do


compilador CS1621.

Embora as funções locais possam parecer redundantes para expressões lambda, elas
realmente têm finalidades e usos diferentes. As funções locais são mais eficientes para
quando você deseja escrever uma função que é chamada apenas do contexto de outro
método.

Confira também
Usar função local em vez do lambda (regra de estilo IDE0039)
Métodos
Variáveis locais de tipo implícito (Guia
de Programação em C#)
Artigo • 14/03/2023

Variáveis locais podem ser declaradas sem fornecer um tipo explícito. A palavra-chave
var instrui o compilador a inferir o tipo da variável da expressão no lado direito da

instrução de inicialização. O tipo inferido pode ser um tipo interno, um tipo anônimo,
um tipo definido pelo usuário ou um tipo definido na biblioteca de classes .NET. Para
obter mais informações sobre como inicializar matrizes com var , consulte Matrizes de
tipo implícito.

Os exemplos a seguir mostram várias maneiras em que as variáveis locais podem ser
declaradas com var :

C#

// i is compiled as an int
var i = 5;

// s is compiled as a string
var s = "Hello";

// a is compiled as int[]
var a = new[] { 0, 1, 2 };

// expr is compiled as IEnumerable<Customer>


// or perhaps IQueryable<Customer>
var expr =
from c in customers
where c.City == "London"
select c;

// anon is compiled as an anonymous type


var anon = new { Name = "Terry", Age = 34 };

// list is compiled as List<int>


var list = new List<int>();

É importante entender que a palavra-chave var não significa "variante" e não indica
que a variável é vagamente tipada ou de associação tardia. Isso apenas significa que o
compilador determina e atribui o tipo mais apropriado.

A palavra-chave var pode ser usada nos seguintes contextos:


Em variáveis locais (variáveis declaradas no escopo do método) conforme
mostrado no exemplo anterior.

Em uma instrução de inicialização for.

C#

for (var x = 1; x < 10; x++)

Em uma instrução de inicialização foreach.

C#

foreach (var item in list) {...}

Em uma instrução using.

C#

using (var file = new StreamReader("C:\\myfile.txt")) {...}

Para obter mais informações, consulte Como usar matrizes e variáveis locais de tipo
implícito em uma expressão de consulta.

Tipos var e anônimos


Em muitos casos, o uso de var é opcional e é apenas uma conveniência sintática. No
entanto, quando uma variável é inicializada com um tipo anônimo você deve declarar a
variável como var se precisar acessar as propriedades do objeto em um momento
posterior. Esse é um cenário comum em expressões de consulta LINQ. Para obter mais
informações, consulte Tipos Anônimos.

Da perspectiva do código-fonte, um tipo anônimo não tem nome. Portanto, se uma


variável de consulta tiver sido inicializada com var , a única maneira de acessar as
propriedades na sequência retornada será usar var como o tipo da variável de iteração
na instrução foreach .

C#

class ImplicitlyTypedLocals2
{
static void Main()
{
string[] words = { "aPPLE", "BlUeBeRrY", "cHeRry" };

// If a query produces a sequence of anonymous types,


// then use var in the foreach statement to access the properties.
var upperLowerWords =
from w in words
select new { Upper = w.ToUpper(), Lower = w.ToLower() };

// Execute the query


foreach (var ul in upperLowerWords)
{
Console.WriteLine("Uppercase: {0}, Lowercase: {1}", ul.Upper,
ul.Lower);
}
}
}
/* Outputs:
Uppercase: APPLE, Lowercase: apple
Uppercase: BLUEBERRY, Lowercase: blueberry
Uppercase: CHERRY, Lowercase: cherry
*/

Comentários
As seguintes restrições se aplicam às declarações de variável de tipo implícito:

var pode ser usado apenas quando uma variável local é declarada e inicializada na
mesma instrução, a variável não pode ser inicializada como nula, um grupo de
métodos ou uma função anônima.

var não pode ser usado em campos no escopo da classe.

Variáveis declaradas usando var não podem ser usadas na expressão de


inicialização. Em outras palavras, essa expressão é válida: int i = (i = 20); , mas
essa expressão gera um erro em tempo de compilação: var i = (i = 20);

Diversas variáveis de tipo implícito não podem ser inicializadas na mesma


instrução.

Se um tipo nomeado var estiver no escopo, a palavra-chave var será resolvida


para esse nome de tipo e não será tratada como parte de uma declaração de
variável local de tipo implícito.

Tipagem implícita com a palavra-chave var só pode ser aplicada às variáveis no escopo
do método local. Digitação implícita não está disponível para os campos de classe, uma
vez que o compilador C# encontraria um paradoxo lógico ao processar o código: o
compilador precisa saber o tipo do campo, mas não é possível determinar o tipo até
que a expressão de atribuição seja analisada. A expressão não pode ser avaliada sem
saber o tipo. Considere o seguinte código:

C#

private var bookTitles;

bookTitles é um campo de classe dado o tipo var . Como o campo não tem nenhuma

expressão para avaliar, é impossível para o compilador inferir que tipo bookTitles
deveria ser. Além disso, também é insuficiente adicionar uma expressão ao campo
(como você faria para uma variável local):

C#

private var bookTitles = new List<string>();

Quando o compilador encontra campos durante a compilação de código, ele registra


cada tipo de campo antes de processar quaisquer expressões associadas. O compilador
encontra o mesmo paradoxo ao tentar analisar bookTitles : ele precisa saber o tipo do
campo, mas o compilador normalmente determinaria o tipo de var analisando a
expressão, o que não possível sem saber o tipo com antecedência.

Você pode descobrir que var também pode ser útil com expressões de consulta em
que o tipo construído exato da variável de consulta é difícil de ser determinado. Isso
pode ocorrer com operações de agrupamento e classificação.

A palavra-chave var também pode ser útil quando o tipo específico da variável é
enfadonho de digitar no teclado, é óbvio ou não acrescenta à legibilidade do código.
Um exemplo em que var é útil dessa maneira é com os tipos genéricos aninhados,
como os usados com operações de grupo. Na consulta a seguir, o tipo da variável de
consulta é IEnumerable<IGrouping<string, Student>> . Contanto que você e as outras
pessoas que devem manter o código entendam isso, não há problema em usar a
tipagem implícita por questões de conveniência e brevidade.

C#

// Same as previous example except we use the entire last name as a key.
// Query variable is an IEnumerable<IGrouping<string, Student>>
var studentQuery3 =
from student in students
group student by student.Last;
O uso de var ajuda a simplificar o código, mas seu uso deve ser restrito a casos em que
ele é necessário ou facilita a leitura do código. Para obter mais informações sobre
quando usar var corretamente, confira a seção Variáveis locais digitados implicitamente
no artigo Diretrizes de Codificação de C#.

Confira também
Referência de C#
Matrizes de tipo implícito
Como usar matrizes e variáveis locais de tipo implícito em uma expressão de
consulta
Tipos anônimos
Inicializadores de objeto e coleção
var
LINQ em C#
LINQ (Consulta Integrada à Linguagem)
Instruções de iteração
Instrução using
Como usar variáveis locais de tipo
implícito e matrizes em uma expressão
de consulta (Guia de Programação em
C#)
Artigo • 07/04/2023

Será possível usar variáveis locais de tipo implícito sempre que você desejar que o
compilador determine o tipo de uma variável local. É necessário usar variáveis locais de
tipo implícito para armazenar tipos anônimos, usados frequentemente em expressões
de consulta. Os exemplos a seguir ilustram usos obrigatórios e opcionais de variáveis
locais de tipo implícito em consultas.

As variáveis locais de tipo implícito são declaradas usando a palavra-chave contextual


var. Para obter mais informações, consulte Variáveis locais de tipo implícito e Matrizes
de tipo implícito.

Exemplos
O exemplo a seguir mostra um cenário comum em que a palavra-chave var é
necessária: uma expressão de consulta que produz uma sequência de tipos anônimos.
Nesse cenário, a variável de consulta e a variável de iteração na instrução foreach
devem ser tipadas implicitamente usando var , porque você não tem acesso a um nome
de tipo para o tipo anônimo. Para obter mais informações sobre tipos anônimos,
consulte Tipos anônimos.

C#

private static void QueryNames(char firstLetter)


{
// Create the query. Use of var is required because
// the query produces a sequence of anonymous types:
// System.Collections.Generic.IEnumerable<????>.
var studentQuery =
from student in students
where student.FirstName[0] == firstLetter
select new { student.FirstName, student.LastName };

// Execute the query and display the results.


foreach (var anonType in studentQuery)
{
Console.WriteLine("First = {0}, Last = {1}", anonType.FirstName,
anonType.LastName);
}
}

O exemplo a seguir usa a palavra-chave var em uma situação semelhante, mas na qual
o uso de var é opcional. Como student.LastName é uma cadeia de caracteres, a
execução da consulta retorna uma sequência de cadeias de caracteres. Portanto, o tipo
de queryId poderia ser declarado como
System.Collections.Generic.IEnumerable<string> em vez de var . A palavra-chave var é

usada por conveniência. No exemplo, a variável de iteração na instrução foreach tem


tipo explícito como uma cadeia de caracteres, mas, em vez disso, poderia ser declarada
usando var . Como o tipo da variável de iteração não é um tipo anônimo, o uso de var
é opcional, não obrigatório. Lembre-se de que var por si só não é um tipo, mas uma
instrução para o compilador inferir e atribuir o tipo.

C#

// Variable queryId could be declared by using


// System.Collections.Generic.IEnumerable<string>
// instead of var.
var queryId =
from student in students
where student.Id > 111
select student.LastName;

// Variable str could be declared by using var instead of string.


foreach (string str in queryId)
{
Console.WriteLine("Last name: {0}", str);
}

Confira também
Guia de Programação em C#
Métodos de Extensão
LINQ (Consulta Integrada à Linguagem)
LINQ em C#
Métodos de extensão (Guia de
Programação em C#)
Artigo • 15/03/2024

Os métodos de extensão permitem que você "adicione" tipos existentes sem criar um
novo tipo derivado, recompilar ou, caso contrário, modificar o tipo original. Os métodos
de extensão são métodos estáticos, mas são chamados como se fossem métodos de
instância no tipo estendido. No caso do código cliente gravado em C#, F# e Visual Basic,
não há nenhuma diferença aparente entre chamar um método de extensão e os
métodos definidos em um tipo.

Os métodos de extensão mais comuns são os operadores de consulta padrão LINQ que
adicionam funcionalidade de consulta aos tipos System.Collections.IEnumerable e
System.Collections.Generic.IEnumerable<T> existentes. Para usar os operadores de
consulta padrão, traga-os primeiro ao escopo com uma diretiva using System.Linq . Em
seguida, qualquer tipo que implemente IEnumerable<T> parece ter métodos de
instância como GroupBy, OrderBy, Average e assim por diante. Você pode exibir esses
métodos adicionais no preenchimento de declaração do IntelliSense ao digitar "ponto"
após uma instância de um tipo IEnumerable<T> como List<T> ou Array.

Exemplo de OrderBy
O exemplo a seguir mostra como chamar o método de consulta padrão OrderBy em
qualquer matriz de inteiros. A expressão entre parênteses é uma expressão lambda.
Vários operadores de consulta padrão obtêm expressões lambda como parâmetros, mas
isso não é um requisito para métodos de extensão. Para obter mais informações,
consulte Expressões Lambda.

C#

class ExtensionMethods2
{

static void Main()


{
int[] ints = [10, 45, 15, 39, 21, 26];
var result = ints.OrderBy(g => g);
foreach (var i in result)
{
System.Console.Write(i + " ");
}
}
}
//Output: 10 15 21 26 39 45

Os métodos de extensão são definidos como estáticos, mas são chamados usando a
sintaxe do método de instância. Seu primeiro parâmetro especifica em qual tipo o
método opera. O parâmetro segue o modificador this. Os métodos de extensão só
estarão no escopo quando você importar explicitamente o namespace para seu código-
fonte com uma diretiva using .

O exemplo a seguir mostra um método de extensão definido para a classe


System.String. Isso é definido em uma classe estática não aninhada e não genérica:

C#

namespace ExtensionMethods
{
public static class MyExtensions
{
public static int WordCount(this string str)
{
return str.Split(new char[] { ' ', '.', '?' },
StringSplitOptions.RemoveEmptyEntries).Length;
}
}
}

O método de extensão WordCount pode ser colocado no escopo com esta diretiva
using :

C#

using ExtensionMethods;

E pode ser chamado a partir de um aplicativo usando esta sintaxe:

C#

string s = "Hello Extension Methods";


int i = s.WordCount();

Você chama o método de extensão em seu código com a sintaxe do método de


instância. A IL (linguagem intermediária) gerada pelo compilador converte seu código
em uma chamada no método estático. O princípio do encapsulamento não está
realmente sendo violado. Os métodos de extensão não podem acessar variáveis
privadas no tipo que estão estendendo.
Tanto a classe MyExtensions quanto o método WordCount são static , e podem ser
acessados como todos os outros membros static . O método WordCount pode ser
invocado como outros métodos static da seguinte maneira:

C#

string s = "Hello Extension Methods";


int i = MyExtensions.WordCount(s);

O código anterior do C#:

Declara e atribui um novo string chamado s com um valor de "Hello Extension


Methods" .

Chama MyExtensions.WordCount dado argumento s .

Para obter mais informações, confira Como implementar e chamar um método de


extensão personalizado.

Em geral, provavelmente você chamará métodos de extensão com muito mais


frequência do que implementará os seus próprios. Como os métodos de extensão são
chamados com a sintaxe do método de instância, nenhum conhecimento especial é
necessário para usá-los no código do cliente. Para habilitar métodos de extensão para
um tipo específico, apenas adicione uma diretiva using para o namespace no qual os
métodos estão definidos. Por exemplo, para usar os operadores de consulta padrão,
adicione esta diretiva using ao seu código:

C#

using System.Linq;

(Talvez você também precise adicionar uma referência a System.Core.dll.) Você


observará que os operadores de consulta padrão agora aparecem no IntelliSense como
métodos adicionais disponíveis para a maioria dos tipos IEnumerable<T>.

Associando Métodos de Extensão no Momento


da Compilação
Você pode usar métodos de extensão para estender uma classe ou interface, mas não
os substituir. Um método de extensão com o mesmo nome e assinatura que um
método de interface ou classe nunca será chamado. No tempo de compilação, os
métodos de extensão sempre têm menos prioridade que os métodos de instância
definidos no próprio tipo. Em outras palavras, se um tipo possuir um método chamado
Process(int i) e se você tiver um método de extensão com a mesma assinatura, o

compilador sempre se associará ao método de instância. Quando o compilador


encontra uma invocação de método, primeiro ele procura uma correspondência nos
métodos de instância do tipo. Se nenhuma correspondência for encontrada, ele procura
quaisquer métodos de extensão definidos para o tipo e vincula-se ao primeiro método
de extensão encontrado.

Exemplo
O exemplo a seguir demonstra as regras que o compilador C# segue ao determinar se
deve associar uma chamada de método a um método de instância no tipo ou a um
método de extensão. A classe estática Extensions contém métodos de extensão
definidos para qualquer tipo que implementa IMyInterface . As classes A , B e C
implementam a interface.

O método de extensão MethodB nunca é chamado porque seu nome e assinatura são
exatamente iguais aos métodos já implementados pelas classes.

Quando o compilador não consegue localizar um método de instância com uma


assinatura compatível, ele se associa a um método de extensão correspondente se
houver.

C#

// Define an interface named IMyInterface.


namespace DefineIMyInterface
{
public interface IMyInterface
{
// Any class that implements IMyInterface must define a method
// that matches the following signature.
void MethodB();
}
}

// Define extension methods for IMyInterface.


namespace Extensions
{
using System;
using DefineIMyInterface;

// The following extension methods can be accessed by instances of any


// class that implements IMyInterface.
public static class Extension
{
public static void MethodA(this IMyInterface myInterface, int i)
{
Console.WriteLine
("Extension.MethodA(this IMyInterface myInterface, int i)");
}

public static void MethodA(this IMyInterface myInterface, string s)


{
Console.WriteLine
("Extension.MethodA(this IMyInterface myInterface, string
s)");
}

// This method is never called in ExtensionMethodsDemo1, because


each
// of the three classes A, B, and C implements a method named
MethodB
// that has a matching signature.
public static void MethodB(this IMyInterface myInterface)
{
Console.WriteLine
("Extension.MethodB(this IMyInterface myInterface)");
}
}
}

// Define three classes that implement IMyInterface, and then use them to
test
// the extension methods.
namespace ExtensionMethodsDemo1
{
using System;
using Extensions;
using DefineIMyInterface;

class A : IMyInterface
{
public void MethodB() { Console.WriteLine("A.MethodB()"); }
}

class B : IMyInterface
{
public void MethodB() { Console.WriteLine("B.MethodB()"); }
public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)");
}
}

class C : IMyInterface
{
public void MethodB() { Console.WriteLine("C.MethodB()"); }
public void MethodA(object obj)
{
Console.WriteLine("C.MethodA(object obj)");
}
}
class ExtMethodDemo
{
static void Main(string[] args)
{
// Declare an instance of class A, class B, and class C.
A a = new A();
B b = new B();
C c = new C();

// For a, b, and c, call the following methods:


// -- MethodA with an int argument
// -- MethodA with a string argument
// -- MethodB with no argument.

// A contains no MethodA, so each call to MethodA resolves to


// the extension method that has a matching signature.
a.MethodA(1); // Extension.MethodA(IMyInterface, int)
a.MethodA("hello"); // Extension.MethodA(IMyInterface,
string)

// A has a method that matches the signature of the following


call
// to MethodB.
a.MethodB(); // A.MethodB()

// B has methods that match the signatures of the following


// method calls.
b.MethodA(1); // B.MethodA(int)
b.MethodB(); // B.MethodB()

// B has no matching method for the following call, but


// class Extension does.
b.MethodA("hello"); // Extension.MethodA(IMyInterface,
string)

// C contains an instance method that matches each of the


following
// method calls.
c.MethodA(1); // C.MethodA(object)
c.MethodA("hello"); // C.MethodA(object)
c.MethodB(); // C.MethodB()
}
}
}
/* Output:
Extension.MethodA(this IMyInterface myInterface, int i)
Extension.MethodA(this IMyInterface myInterface, string s)
A.MethodB()
B.MethodA(int i)
B.MethodB()
Extension.MethodA(this IMyInterface myInterface, string s)
C.MethodA(object obj)
C.MethodA(object obj)
C.MethodB()
*/
Padrões comuns de uso

Funcionalidade de coleção
No passado, era comum criar "Classes de Coleção" que implementavam a interface
System.Collections.Generic.IEnumerable<T> para determinado tipo e continham
funcionalidades que atuavam em coleções desse tipo. Embora não haja nada de errado
em criar esse tipo de objeto de coleção, a mesma funcionalidade pode ser obtida
usando uma extensão em System.Collections.Generic.IEnumerable<T>. As extensões
têm a vantagem de permitir que a funcionalidade seja chamada de qualquer coleção,
como um System.Array ou System.Collections.Generic.List<T> que implementa
System.Collections.Generic.IEnumerable<T> nesse tipo. Um exemplo disso usando uma
Matriz de Int32 pode ser encontrado anteriormente neste artigo.

Funcionalidade específica da camada


Ao usar uma Arquitetura de camadas tipo cebola ou outro design de aplicativo em
camadas, é comum ter um conjunto de entidades de domínio ou objetos de
transferência de dados que podem ser usados para se comunicar entre os limites do
aplicativo. Esses objetos geralmente não contêm funcionalidade ou têm apenas
funcionalidades mínimas que se aplica a todas as camadas do aplicativo. Os métodos de
extensão podem ser usados para adicionar funcionalidades específicas a cada camada
de aplicativo, sem carregar o objeto com métodos não necessários ou desejados em
outras camadas.

C#

public class DomainEntity


{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}

static class DomainEntityExtensions


{
static string FullName(this DomainEntity value)
=> $"{value.FirstName} {value.LastName}";
}
Estendendo tipos predefinidos
Em vez de criar objetos quando a funcionalidade reutilizável precisa ser criada, muitas
vezes podemos estender um tipo existente, como um tipo .NET ou CLR. Por exemplo, se
não usarmos métodos de extensão, podemos criar uma classe Engine ou Query para
realizar o trabalho de executar uma consulta em um SQL Server que pode ser chamado
de vários lugares do código. No entanto, podemos estender a classe
System.Data.SqlClient.SqlConnection usando métodos de extensão para executar essa
consulta de qualquer lugar que tenhamos uma conexão com um SQL Server. Outros
exemplos podem ser adicionar funcionalidades comuns à classe System.String, estender
os recursos de processamento de dados dos objetos System.IO.Stream e objetos
System.Exception para funcionalidades específicas de tratamento de erros. Esses tipos
de casos de uso são limitados apenas por sua imaginação e bom senso.

Estender tipos predefinidos pode ser difícil com tipos struct porque eles são passados
por valor para os métodos. Isso significa que todas as alterações no struct são feitas em
uma cópia do struct. Essas alterações não ficam visíveis quando o método de extensão é
encerrado. Você pode adicionar o modificador ref ao primeiro argumento, tornando-o
um método de extensão ref . A palavra-chave ref pode aparecer antes ou após a
palavra-chave this sem nenhuma diferença semântica. A adição do modificador ref
indica que o primeiro argumento é passado por referência. Isso permite escrever
métodos de extensão que alteram o estado da estrutura que está sendo estendida
(observe que os membros privados não são acessíveis). Somente tipos de valor ou tipos
genéricos restritos ao struct (consulte struct restrição para obter mais informações) são
permitidos como o primeiro parâmetro de um método de extensão ref . O exemplo a
seguir mostra como utilizar um método de extensão ref para modificar diretamente
um tipo interno sem a necessidade de reatribuir o resultado ou passá-lo por uma
função com a palavra-chave ref :

C#

public static class IntExtensions


{
public static void Increment(this int number)
=> number++;

// Take note of the extra ref keyword here


public static void RefIncrement(this ref int number)
=> number++;
}

public static class IntProgram


{
public static void Test()
{
int x = 1;

// Takes x by value leading to the extension method


// Increment modifying its own copy, leaving x unchanged
x.Increment();
Console.WriteLine($"x is now {x}"); // x is now 1

// Takes x by reference leading to the extension method


// RefIncrement changing the value of x directly
x.RefIncrement();
Console.WriteLine($"x is now {x}"); // x is now 2
}
}

O próximo exemplo demonstra os métodos de extensão ref para os tipos struct


definidos pelo usuário:

C#

public struct Account


{
public uint id;
public float balance;

private int secret;


}

public static class AccountExtensions


{
// ref keyword can also appear before the this keyword
public static void Deposit(ref this Account account, float amount)
{
account.balance += amount;

// The following line results in an error as an extension


// method is not allowed to access private members
// account.secret = 1; // CS0122
}
}

public static class AccountProgram


{
public static void Test()
{
Account account = new()
{
id = 1,
balance = 100f
};

Console.WriteLine($"I have ${account.balance}"); // I have $100


account.Deposit(50f);
Console.WriteLine($"I have ${account.balance}"); // I have $150
}
}

Diretrizes gerais
Embora ainda seja considerado preferível adicionar funcionalidade modificando o
código de um objeto ou derivando um novo tipo sempre que for razoável e possível
fazer isso, os métodos de extensão se tornaram uma opção crucial para criar
funcionalidades reutilizáveis em todo o ecossistema do .NET. Para aquelas ocasiões em
que a origem não está sob seu controle, quando um objeto derivado é inadequado ou
impossível de usar, ou quando a funcionalidade não deve ser exposta além do escopo
aplicável, os métodos de extensão são uma excelente opção.

Para obter mais informações sobre tipos derivados, confira Herança.

Ao usar um método de extensão para estender um tipo cujo código-fonte você não
pode alterar, há o risco de uma alteração na implementação do tipo interromper o
funcionamento do método de extensão.

Se você implementar métodos de extensão para um determinado tipo, lembre-se das


seguintes considerações:

Um método de extensão não é chamado se tiver a mesma assinatura de um


método definido no tipo.
Os métodos de extensão são trazidos para o escopo no nível do namespace. Por
exemplo, se você tiver várias classes estáticas que contenham métodos de
extensão em um só namespace chamado Extensions , todos eles serão trazidos
para o escopo pela diretiva using Extensions; .

Para uma biblioteca de classes que você implemente, não use métodos de extensão
para evitar incrementar o número de versão de um assembly. Se desejar adicionar
funcionalidade significativa a uma biblioteca da qual você tenha o código-fonte, siga as
diretrizes do .NET para controle de versão do assembly. Para obter mais informações,
consulte Controle de versão do assembly.

Confira também
Exemplos de programação paralela (incluem vários métodos de extensão de
exemplo)
Expressões Lambda
Visão geral de operadores de consulta padrão
Regras de conversão para parâmetros de instância e seu impacto
Interoperabilidade de métodos de extensão entre linguagens
Métodos de extensão e representantes via currying
Associação do método de extensão e relatório de erros

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
Como implementar e chamar um
método de extensão personalizado
(Guia de Programação em C#)
Artigo • 04/10/2024

Este artigo mostra como implementar seus próprios métodos de extensão para
qualquer tipo do .NET. O código do cliente pode usar seus métodos de extensão. Os
projetos de cliente devem fazer referência ao assembly (montagem) nos quais estão
contidos. Os projetos cliente devem adicionar uma diretriz de using (utilização) que
especifica o namespace no qual os métodos de extensão são definidos.

Para definir e chamar o método de extensão;

1. Defina uma classe estática para conter o método de extensão. A classe não pode
ser aninhada dentro de outro tipo e deve estar visível para o código do cliente.
Para obter mais informações sobre regras de acessibilidade, consulte
Modificadores de acesso.
2. Implemente o método de extensão como um método estático com, pelo menos, a
mesma visibilidade da classe que a contém.
3. O primeiro parâmetro do método especifica o tipo no qual o método opera. Ele
deve ser precedido pelo modificador this.
4. No código de chamada, adicione uma diretiva using para especificar o namespace
que contém a classe do método de extensão.
5. Chame os métodos como métodos de instância no tipo.

7 Observação

O primeiro parâmetro não é especificado pelo código de chamada porque ele


representa o tipo no qual o operador está sendo aplicado e o compilador já
conhece o tipo do objeto. Você só precisa fornecer argumentos para os parâmetros
de 2 até o n .

O exemplo a seguir implementa um método de extensão chamado WordCount na classe


CustomExtensions.StringExtension . O método funciona na classe String, que é

especificada como o primeiro parâmetro do método. O namespace CustomExtensions é


importado para o namespace do aplicativo e o método é chamado dentro do método
Main .
C#

using CustomExtensions;

string s = "The quick brown fox jumped over the lazy dog.";
// Call the method as if it were an
// instance method on the type. Note that the first
// parameter is not specified by the calling code.
int i = s.WordCount();
System.Console.WriteLine("Word count of s is {0}", i);

namespace CustomExtensions
{
// Extension methods must be defined in a static class.
public static class StringExtension
{
// This is the extension method.
// The first parameter takes the "this" modifier
// and specifies the type for which the method is defined.
public static int WordCount(this string str)
{
return str.Split(new char[] {' ', '.','?'},
StringSplitOptions.RemoveEmptyEntries).Length;
}
}
}

A resolução de sobrecarga dá preferência ao método de instância ou estático definido


pelo próprio tipo antes que métodos de extensão. Os métodos de extensão não podem
acessar nenhum dado particular na classe estendida.

Confira também
Métodos de Extensão
LINQ (Consulta Integrada à Linguagem)
Classes static e membros de classes static
protected
interno
público
this
namespace
Como criar um novo método para uma
enumeração (Guia de Programação em
C#)
Artigo • 13/03/2024

Você pode usar métodos de extensão para adicionar funcionalidades específica para um
tipo de enumeração específico.

Exemplo
No exemplo a seguir, a enumeração Grades representa as letras possíveis que um aluno
pode receber em uma classe. Um método de extensão chamado Passing é adicionado
ao tipo Grades de forma que cada instância desse tipo agora "sabe" se ele representa
uma nota de aprovação ou não.

C#

using System;

namespace EnumExtension
{
// Define an extension method in a non-nested static class.
public static class Extensions
{
public static Grades minPassing = Grades.D;
public static bool Passing(this Grades grade)
{
return grade >= minPassing;
}
}

public enum Grades { F = 0, D=1, C=2, B=3, A=4 };


class Program
{
static void Main(string[] args)
{
Grades g1 = Grades.D;
Grades g2 = Grades.F;
Console.WriteLine("First {0} a passing grade.", g1.Passing() ?
"is" : "is not");
Console.WriteLine("Second {0} a passing grade.", g2.Passing() ?
"is" : "is not");

Extensions.minPassing = Grades.C;
Console.WriteLine("\r\nRaising the bar!\r\n");
Console.WriteLine("First {0} a passing grade.", g1.Passing() ?
"is" : "is not");
Console.WriteLine("Second {0} a passing grade.", g2.Passing() ?
"is" : "is not");
}
}
}
/* Output:
First is a passing grade.
Second is not a passing grade.

Raising the bar!

First is not a passing grade.


Second is not a passing grade.
*/

Observe que a classe Extensions também contém uma variável estática atualizada
dinamicamente e que o valor retornado do método de extensão reflete o valor atual
dessa variável. Isso demonstra que, nos bastidores, os métodos de extensão são
chamados diretamente na classe estática na qual eles são definidos.

Confira também
Métodos de Extensã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
Argumentos nomeados e opcionais
(Guia de Programação em C#)
Artigo • 23/03/2024

Os argumentos nomeados permitem que você especifique um argumento para um


parâmetro correspondendo o argumento com seu nome em vez de com sua posição na
lista de parâmetros. Argumentos opcionais permitem omitir argumentos para alguns
parâmetros. Ambas as técnicas podem ser usadas com os métodos, indexadores,
construtores e delegados.

Quando você usa argumentos nomeados e opcionais, os argumentos são avaliados na


ordem em que aparecem na lista de argumentos e não na lista de parâmetros.

Parâmetros nomeados e opcionais permitem que você forneça argumentos para


parâmetros selecionados. Essa capacidade facilita bastante chamadas para interfaces
COM como as APIs de Automação do Microsoft Office.

Argumentos nomeados
Os argumentos nomeados liberam você da necessidade de combinar a ordem dos
argumentos com a ordem dos parâmetros nas listas de parâmetros de métodos
chamados. O argumento para cada parâmetro pode ser especificado pelo nome do
parâmetro. Por exemplo, uma função que imprime detalhes de pedidos (como o nome
do vendedor, nome do produto e número do pedido) pode ser chamada por meio do
envio de argumentos por posição, na ordem definida pela função.

C#

PrintOrderDetails("Gift Shop", 31, "Red Mug");

Se não se lembrar da ordem dos parâmetros, mas souber os nomes, você poderá enviar
os argumentos em qualquer ordem.

C#

PrintOrderDetails(orderNum: 31, productName: "Red Mug", sellerName: "Gift


Shop");
PrintOrderDetails(productName: "Red Mug", sellerName: "Gift Shop", orderNum:
31);
Os argumentos nomeados também melhoram a legibilidade do código identificando o
que cada argumento representa. No método de exemplo abaixo, o sellerName não
pode ser nulo ou um espaço em branco. Como sellerName e productName são tipos de
cadeia de caracteres, em vez de enviar argumentos por posição, é melhor usar
argumentos nomeados para remover a ambiguidade dos dois e reduzir a confusão para
qualquer pessoa que leia o código.

Os argumentos nomeados, quando usados com argumentos posicionais, são válidos,


desde que

não sejam seguidos por argumentos posicionais ou,

C#

PrintOrderDetails("Gift Shop", 31, productName: "Red Mug");

são usados na posição correta. No exemplo a seguir, o parâmetro orderNum está


na posição correta, mas não está explicitamente nomeado.

C#

PrintOrderDetails(sellerName: "Gift Shop", 31, productName: "Red Mug");

Argumentos posicionais que seguem argumentos nomeados fora de ordem são


inválidos.

C#

// This generates CS1738: Named argument specifications must appear after


all fixed arguments have been specified.
PrintOrderDetails(productName: "Red Mug", 31, "Gift Shop");

Exemplo
O código a seguir implementa os exemplos desta seção, juntamente com outros
exemplos.

C#

class NamedExample
{
static void Main(string[] args)
{
// The method can be called in the normal way, by using positional
arguments.
PrintOrderDetails("Gift Shop", 31, "Red Mug");

// Named arguments can be supplied for the parameters in any order.


PrintOrderDetails(orderNum: 31, productName: "Red Mug", sellerName:
"Gift Shop");
PrintOrderDetails(productName: "Red Mug", sellerName: "Gift Shop",
orderNum: 31);

// Named arguments mixed with positional arguments are valid


// as long as they are used in their correct position.
PrintOrderDetails("Gift Shop", 31, productName: "Red Mug");
PrintOrderDetails(sellerName: "Gift Shop", 31, productName: "Red
Mug");
PrintOrderDetails("Gift Shop", orderNum: 31, "Red Mug");

// However, mixed arguments are invalid if used out-of-order.


// The following statements will cause a compiler error.
// PrintOrderDetails(productName: "Red Mug", 31, "Gift Shop");
// PrintOrderDetails(31, sellerName: "Gift Shop", "Red Mug");
// PrintOrderDetails(31, "Red Mug", sellerName: "Gift Shop");
}

static void PrintOrderDetails(string sellerName, int orderNum, string


productName)
{
if (string.IsNullOrWhiteSpace(sellerName))
{
throw new ArgumentException(message: "Seller name cannot be null
or empty.", paramName: nameof(sellerName));
}

Console.WriteLine($"Seller: {sellerName}, Order #: {orderNum},


Product: {productName}");
}
}

Argumentos opcionais
A definição de um método, construtor, indexador ou delegado pode especificar que
seus parâmetros são obrigatórios ou opcionais. Qualquer chamada deve fornecer
argumentos para todos os parâmetros necessários, mas pode omitir argumentos para
parâmetros opcionais.

Cada parâmetro opcional tem um valor padrão como parte de sua definição. Se nenhum
argumento é enviado para esse parâmetro, o valor padrão é usado. Um valor padrão
deve ser um dos seguintes tipos de expressões:

uma expressão de constante;


uma expressão da forma new ValType() , em que ValType é um tipo de valor, como
um enum ou um struct;
uma expressão da forma default(ValType), em que ValType é um tipo de valor.

Os parâmetros opcionais são definidos no final da lista de parâmetros, depois de todos


os parâmetros obrigatórios. Se o chamador fornecer um argumento para qualquer um
de uma sucessão de parâmetros opcionais, ele deverá fornecer argumentos para todos
os parâmetros opcionais anteriores. Não há suporte para intervalos separados por
vírgula na lista de argumentos. Por exemplo, no código a seguir, método de instância
ExampleMethod está definido com um parâmetro obrigatório e dois opcionais.

C#

public void ExampleMethod(int required, string optionalstr = "default


string",
int optionalint = 10)

A chamada para ExampleMethod a seguir causa um erro do compilador, porque um


argumento é fornecido para o terceiro parâmetro, mas não para o segundo.

C#

//anExample.ExampleMethod(3, ,4);

No entanto, se você souber o nome do terceiro parâmetro, poderá usar um argumento


nomeado para realizar a tarefa.

C#

anExample.ExampleMethod(3, optionalint: 4);

O IntelliSense usa colchetes para indicar parâmetros opcionais, conforme mostrado na


seguinte ilustração:

7 Observação

Você também pode declarar parâmetros opcionais usando a classe


OptionalAttribute do .NET. Os parâmetros OptionalAttribute não exigem um valor
padrão. No entanto, se um valor padrão for desejado, dê uma olhada na classe
DefaultParameterValueAttribute.

Exemplo
No exemplo a seguir, o construtor para ExampleClass tem um parâmetro, que é
opcional. O método de instância ExampleMethod tem um parâmetro obrigatório,
required e dois parâmetros opcionais, optionalstr e optionalint . O código em Main

mostra as diferentes maneiras em que o construtor e o método podem ser invocados.

C#

namespace OptionalNamespace
{
class OptionalExample
{
static void Main(string[] args)
{
// Instance anExample does not send an argument for the
constructor's
// optional parameter.
ExampleClass anExample = new ExampleClass();
anExample.ExampleMethod(1, "One", 1);
anExample.ExampleMethod(2, "Two");
anExample.ExampleMethod(3);

// Instance anotherExample sends an argument for the


constructor's
// optional parameter.
ExampleClass anotherExample = new ExampleClass("Provided name");
anotherExample.ExampleMethod(1, "One", 1);
anotherExample.ExampleMethod(2, "Two");
anotherExample.ExampleMethod(3);

// The following statements produce compiler errors.

// An argument must be supplied for the first parameter, and it


// must be an integer.
//anExample.ExampleMethod("One", 1);
//anExample.ExampleMethod();

// You cannot leave a gap in the provided arguments.


//anExample.ExampleMethod(3, ,4);
//anExample.ExampleMethod(3, 4);

// You can use a named parameter to make the previous


// statement work.
anExample.ExampleMethod(3, optionalint: 4);
}
}
class ExampleClass
{
private string _name;

// Because the parameter for the constructor, name, has a default


// value assigned to it, it is optional.
public ExampleClass(string name = "Default name")
{
_name = name;
}

// The first parameter, required, has no default value assigned


// to it. Therefore, it is not optional. Both optionalstr and
// optionalint have default values assigned to them. They are
optional.
public void ExampleMethod(int required, string optionalstr =
"default string",
int optionalint = 10)
{
Console.WriteLine(
$"{_name}: {required}, {optionalstr}, and {optionalint}.");
}
}

// The output from this example is the following:


// Default name: 1, One, and 1.
// Default name: 2, Two, and 10.
// Default name: 3, default string, and 10.
// Provided name: 1, One, and 1.
// Provided name: 2, Two, and 10.
// Provided name: 3, default string, and 10.
// Default name: 3, default string, and 4.
}

O código anterior mostra vários exemplos em que os parâmetros opcionais não estão
aplicados corretamente. O primeiro ilustra que um argumento deve ser fornecido para o
primeiro parâmetro, o que é necessário.

Atributos de informações do chamador


Atributosde informações do chamador, como CallerFilePathAttribute,
CallerLineNumberAttribute, CallerMemberNameAttribute e
CallerArgumentExpressionAttribute, são usados para obter informações sobre o
chamador de um método. Esses atributos são especialmente úteis quando você está
depurando ou quando precisa registrar informações sobre chamadas de método.

Esses atributos são parâmetros opcionais com valores padrão fornecidos pelo
compilador. O chamador não deve fornecer explicitamente um valor para esses
parâmetros.

Interfaces COM
Os argumentos nomeados e opcionais, juntamente com suporte para objetos dinâmicos
e outros aprimoramentos, aprimoram enormemente a interoperabilidade com APIs
COM, como APIs de Automação do Office.

Por exemplo, o método AutoFormat na interface Range do Microsoft Office Excel tem
sete parâmetros, todos opcionais. Esses parâmetros são mostrados na seguinte
ilustração:

No entanto, você pode simplificar muito a chamada para AutoFormat usando


argumentos nomeados e opcionais. Os argumentos nomeados e opcionais permitem
que você omita o argumento para um parâmetro opcional se não desejar alterar o valor
padrão do parâmetro. Na chamada a seguir, um valor é especificado para apenas um
dos sete parâmetros.

C#

var excelApp = new Microsoft.Office.Interop.Excel.Application();


excelApp.Workbooks.Add();
excelApp.Visible = true;

var myFormat =

Microsoft.Office.Interop.Excel.XlRangeAutoFormat.xlRangeAutoFormatAccounting
1;

excelApp.Range["A1", "B4"].AutoFormat( Format: myFormat );

Para obter mais informações e exemplos, consulte Como usar argumentos nomeados e
opcionais na programação do Office e Como acessar objetos de interoperabilidade do
Office usando recursos do Visual C#.

Resolução de sobrecarga
O uso de argumentos nomeados e opcionais afeta a resolução de sobrecarga das
seguintes maneiras:
Um método, indexador ou construtor é um candidato para a execução se cada um
dos parâmetros é opcional ou corresponde, por nome ou posição, a um único
argumento na instrução de chamada e esse argumento pode ser convertido para o
tipo do parâmetro.
Se mais de um candidato for encontrado, as regras de resolução de sobrecarga de
conversões preferenciais serão aplicadas aos argumentos que são especificados
explicitamente. Os argumentos omitidos para parâmetros opcionais são ignorados.
Se dois candidatos são considerados igualmente bons, a preferência vai para um
candidato que não tenha parâmetros opcionais para os quais argumentos foram
omitidos na chamada. A resolução de sobrecarga geralmente prefere candidatos
com menos parâmetros.

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
Construtores (guia de programação em
C#)
Artigo • 13/03/2024

Sempre que uma instância de uma classe ou um struct é criada, seu construtor é
chamado. Uma classe ou struct pode ter vários construtores que usam argumentos
diferentes. Os construtores permitem que o programador defina valores padrão, limite a
instanciação e grave códigos flexíveis e fáceis de ler. Para obter mais informações e
exemplos, consulte Construtores de Instância e Usando Construtores.

Há várias ações que fazem parte da inicialização de uma nova instância. Essas ações
ocorrem na seguinte ordem:

1. Os campos de instância são definidos como 0. Normalmente, isso é feito pelo


runtime.
2. Inicializadores de campo são executados. Os inicializadores de campo na execução
de tipo mais derivada.
3. Inicializadores de campo de tipo base são executados. Inicializadores de campo
começando com a base direta por meio de cada tipo base para System.Object.
4. Construtores de instância base são executados. Todos os construtores de instância,
começando por Object.Object, em cada classe base para a classe base direta.
5. O construtor de instância é executado. O construtor de instância para o tipo é
executado.
6. Inicializadores de objeto são executados. Se a expressão incluir os inicializadores de
objeto, eles são executados após a execução do construtor de instância.
Inicializadores de objeto são executados na ordem textual.

As ações anteriores ocorrem quando uma nova instância é inicializada. Se uma nova
instância de um struct for definida como seu valor default , todos os campos de
instância serão definidos como 0.

Se o construtor estático não tiver sido executado, ele o será antes que qualquer uma
das ações do construtor de instância ocorra.

Sintaxe do construtor
Um construtor é um método cujo nome é igual ao nome de seu tipo. Sua assinatura do
método inclui apenas um modificador de acesso opcional, o nome do método e sua
lista de parâmetros; ela não inclui tipo de retorno. O exemplo a seguir mostra o
construtor para uma classe denominada Person .
C#

public class Person


{
private string last;
private string first;

public Person(string lastName, string firstName)


{
last = lastName;
first = firstName;
}

// Remaining implementation of Person class.


}

Se um construtor puder ser implementado como uma única instrução, você poderá usar
uma definição de corpo da expressão. O exemplo a seguir define uma classe Location
cujo construtor tem um único parâmetro de cadeia de caracteres chamado nome. A
definição de corpo da expressão atribui o argumento ao campo locationName .

C#

public class Location


{
private string locationName;

public Location(string name) => Name = name;

public string Name


{
get => locationName;
set => locationName = value;
}
}

Construtores estáticos
Os exemplos anteriores têm todos os construtores de instância mostrado, que criam um
novo objeto. Uma classe ou struct também pode ter um construtor estático, que
inicializa membros estáticos do tipo. Construtores estáticos não têm parâmetros. Se
você não fornecer um construtor estático para inicializar campos estáticos, o compilador
C# inicializará campos estáticos com seu valor padrão, conforme listado no artigo
Valores padrão de tipos C#.

O exemplo a seguir usa um construtor estático para inicializar um campo estático.


C#

public class Adult : Person


{
private static int minimumAge;

public Adult(string lastName, string firstName) : base(lastName,


firstName)
{ }

static Adult()
{
minimumAge = 18;
}

// Remaining implementation of Adult class.


}

Você também pode definir um construtor estático com uma definição de corpo da
expressão, como mostra o exemplo a seguir.

C#

public class Child : Person


{
private static int maximumAge;

public Child(string lastName, string firstName) : base(lastName,


firstName)
{ }

static Child() => maximumAge = 18;

// Remaining implementation of Child class.


}

Para obter mais informações e exemplos, consulte Construtores Estáticos.

Nesta seção
Como usar construtores
Construtores de instância
Construtores particulares
Construtores estáticos
Como escrever um construtor de cópia
Confira também
Sistema de tipos do C#
Finalizadores
static
Por que os inicializadores são executados na ordem oposta, como construtores?
Parte 1

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
Usando construtores (Guia de
Programação em C#)
Artigo • 02/06/2023

Quando uma classe ou struct é instanciada, seu construtor é chamado. Os construtores


têm o mesmo nome que a classe ou struct e eles geralmente inicializam os membros de
dados do novo objeto.

No exemplo a seguir, uma classe chamada Taxi é definida usando um construtor


simples. A classe é então instanciada com o operador new. O construtor Taxi é
invocado pelo operador new imediatamente após a memória ser alocada para o novo
objeto.

C#

public class Taxi


{
public bool IsInitialized;

public Taxi()
{
IsInitialized = true;
}
}

class TestTaxi
{
static void Main()
{
Taxi t = new Taxi();
Console.WriteLine(t.IsInitialized);
}
}

Um construtor que não tem nenhum parâmetro é chamado de construtor sem


parâmetros. Os construtores sem parâmetros são invocados sempre que um objeto é
instanciado usando o operador new e nenhum argumento é fornecido para new . O C#
12 apresenta construtores primários. Um construtor primário especifica parâmetros que
devem ser fornecidos para inicializar um novo objeto. Para obter mais informações,
consulte Construtores de instâncias.

A menos que a classe seja static, as classes sem construtores recebem um construtor
sem parâmetros público pelo compilador C# para habilitar a instanciação de classe. Para
obter mais informações, consulte Classes estáticas e membros de classes estáticas.
Você pode impedir que uma classe seja instanciada tornando o construtor privado, da
seguinte maneira:

C#

class NLog
{
// Private Constructor:
private NLog() { }

public static double e = Math.E; //2.71828...


}

Para obter mais informações, consulte Construtores particulares.

Construtores para tipos struct se assemelham a construtores de classe. Quando um tipo


struct é instanciado com new , um construtor é invocado. Quando um struct é definido
como seu valor default , o runtime inicializa toda a memória no struct como 0. Antes do
C# 10, o structs não podia conter um construtor explícito sem parâmetros porque um
é fornecido automaticamente pelo compilador. Para obter mais informações, consulte a
seção Inicialização de struct e valores padrão do artigo Tipos de estrutura.

O código a seguir usa o construtor sem parâmetros para Int32, para que você tenha
certeza de que o inteiro é inicializado:

C#

int i = new int();


Console.WriteLine(i);

O código a seguir, no entanto, causa um erro do compilador, pois não usa new e tenta
usar um objeto não inicializado:

C#

int i;
Console.WriteLine(i);

Como alternativa, os objetos com base em structs (incluindo todos os tipos numéricos
internos) podem ser inicializados ou atribuídos e, em seguida, usados como no exemplo
a seguir:

C#
int a = 44; // Initialize the value type...
int b;
b = 33; // Or assign it before using it.
Console.WriteLine("{0}, {1}", a, b);

Classes e structs podem definir construtores que recebem parâmetros, incluindo


construtores primários. Os construtores que usam parâmetros devem ser chamados por
meio de uma instrução new ou uma instrução base. As classes e structs também podem
definir vários construtores e nenhum deles precisa definir um construtor sem
parâmetros. Por exemplo:

C#

public class Employee


{
public int Salary;

public Employee() { }

public Employee(int annualSalary)


{
Salary = annualSalary;
}

public Employee(int weeklySalary, int numberOfWeeks)


{
Salary = weeklySalary * numberOfWeeks;
}
}

Essa classe pode ser criada usando qualquer uma das instruções a seguir:

C#

Employee e1 = new Employee(30000);


Employee e2 = new Employee(500, 52);

Um construtor pode usar a palavra-chave base para chamar o construtor de uma classe
base. Por exemplo:

C#

public class Manager : Employee


{
public Manager(int annualSalary)
: base(annualSalary)
{
//Add further instructions here.
}
}

Neste exemplo, o construtor da classe base é chamado antes de o bloco do construtor


ser executado. A palavra-chave base pode ser usada com ou sem parâmetros. Os
parâmetros para o construtor podem ser usados como parâmetros para base ou como
parte de uma expressão. Para obter mais informações, consulte base.

Em uma classe derivada, se um construtor de classe base não for chamado


explicitamente usando a palavra-chave base , o construtor sem parâmetros, se houver,
será chamado implicitamente. As seguintes declarações de construtor são efetivamente
iguais:

C#

public Manager(int initialData)


{
//Add further instructions here.
}

C#

public Manager(int initialData)


: base()
{
//Add further instructions here.
}

Se uma classe base não oferecer um construtor sem parâmetros, a classe derivada
deverá fazer uma chamada explícita para um construtor base usando base .

Um construtor pode invocar outro construtor no mesmo objeto usando a palavra-chave


this. Como base , this pode ser usado com ou sem parâmetros e todos os parâmetros
no construtor estão disponíveis como parâmetros para this ou como parte de uma
expressão. Por exemplo, o segundo construtor no exemplo anterior pode ser reescrito
usando this :

C#

public Employee(int weeklySalary, int numberOfWeeks)


: this(weeklySalary * numberOfWeeks)
{
}
O uso da palavra-chave this no exemplo anterior faz com que esse construtor seja
chamado:

C#

public Employee(int annualSalary)


{
Salary = annualSalary;
}

Os construtores podem ser marcados como public, private, protected, internal,


protected internal ou private protected. Esses modificadores de acesso definem como
os usuários da classe podem construir a classe. Para obter mais informações, consulte
Modificadores de Acesso.

Um construtor pode ser declarado estático usando a palavra-chave static. Os


construtores estáticos são chamados automaticamente, imediatamente antes de
qualquer campo estático ser acessado e são usados para inicializar membros da classe
estática. Para obter mais informações, consulte Construtores estáticos.

Especificação da Linguagem C#
Para obter mais informações, veja Construtores de instância e Construtores estáticos na
Especificação de Linguagem C#. A especificação da linguagem é a fonte definitiva para a
sintaxe e o uso de C#.

Confira também
Guia de Programação em C#
O sistema de tipos C#
Construtores
Finalizadores
Construtores de instâncias (Guia de
Programação em C#)
Artigo • 07/06/2024

Você declara um construtor de instância para especificar o código executado ao criar


uma nova instância de um tipo com a expressão new. Para inicializar uma classe estática
ou variáveis estáticas em uma classe não estática, é possível definir um construtor
estático.

Como mostra o exemplo a seguir, você pode declarar vários construtores de instância
em um tipo:

C#

class Coords
{
public Coords()
: this(0, 0)
{ }

public Coords(int x, int y)


{
X = x;
Y = y;
}

public int X { get; set; }


public int Y { get; set; }

public override string ToString() => $"({X},{Y})";


}

class Example
{
static void Main()
{
var p1 = new Coords();
Console.WriteLine($"Coords #1 at {p1}");
// Output: Coords #1 at (0,0)

var p2 = new Coords(5, 3);


Console.WriteLine($"Coords #2 at {p2}");
// Output: Coords #2 at (5,3)
}
}
No exemplo anterior, o primeiro construtor, sem parâmetros, chama o segundo
construtor com ambos os argumentos iguais a 0 . Para fazer isso, use a palavra-chave
this .

Ao declarar um construtor de instância em uma classe derivada, você pode chamar um


construtor de uma classe base. Para fazer isso, use a palavra-chave base , como mostra o
exemplo a seguir:

C#

abstract class Shape


{
public const double pi = Math.PI;
protected double x, y;

public Shape(double x, double y)


{
this.x = x;
this.y = y;
}

public abstract double Area();


}

class Circle : Shape


{
public Circle(double radius)
: base(radius, 0)
{ }

public override double Area() => pi * x * x;


}

class Cylinder : Circle


{
public Cylinder(double radius, double height)
: base(radius)
{
y = height;
}

public override double Area() => (2 * base.Area()) + (2 * pi * x * y);


}

class Example
{
static void Main()
{
double radius = 2.5;
double height = 3.0;

var ring = new Circle(radius);


Console.WriteLine($"Area of the circle = {ring.Area():F2}");
// Output: Area of the circle = 19.63

var tube = new Cylinder(radius, height);


Console.WriteLine($"Area of the cylinder = {tube.Area():F2}");
// Output: Area of the cylinder = 86.39
}
}

Construtores sem parâmetros


Se uma classe não tiver construtores de instância explícita, o C# fornecerá um construtor
sem parâmetros que você pode usar para instanciar uma instância dessa classe, como
mostra o exemplo a seguir:

C#

public class Person


{
public int age;
public string name = "unknown";
}

class Example
{
static void Main()
{
var person = new Person();
Console.WriteLine($"Name: {person.name}, Age: {person.age}");
// Output: Name: unknown, Age: 0
}
}

Esse construtor inicializa os campos e as propriedades da instância de acordo com os


inicializadores correspondentes. Se um campo ou propriedade não tiver um
inicializador, seu valor será definido como o valor padrão do tipo do campo ou da
propriedade. Se você declarar pelo menos um construtor de instância em uma classe, o
C# não fornecerá um construtor sem parâmetros.

Um tipo de estrutura sempre fornece um construtor sem parâmetros. O construtor sem


parâmetros é um construtor sem parâmetros implícito que produz o valor padrão de um
tipo ou um construtor sem parâmetros explicitamente declarado. Para obter mais
informações, consulte a seção Inicialização de struct e valores padrão do artigo Tipos de
estrutura.
Construtores primários
A partir do C# 12, você pode declarar um construtor primário em classes e structs. Você
coloca todos os parâmetros entre parênteses seguindo o nome do tipo:

C#

public class NamedItem(string name)


{
public string Name => name;
}

Os parâmetros para um construtor primário estão no escopo em todo o corpo do tipo


de declaração. Eles podem inicializar propriedades ou campos. Eles podem ser usados
como variáveis em métodos ou funções locais. Eles podem ser passados para um
construtor base.

Um construtor primário indica que esses parâmetros são necessários para qualquer
instância do tipo. Qualquer construtor escrito explicitamente deve usar a sintaxe do
inicializador this(...) para invocar o construtor primário. Isso garante que os
parâmetros do construtor primário sejam definitivamente atribuídos por todos os
construtores. Para qualquer tipo class , incluindo tipos record class , o construtor
implícito sem parâmetros não é emitido quando um construtor primário está presente.
Para qualquer tipo struct , incluindo tipos record struct , o construtor implícito sem
parâmetros sempre é emitido e sempre inicializa todos os campos, incluindo parâmetros
de construtor primário, para o padrão de 0 bit. Se você escrever um construtor explícito
sem parâmetros, ele deverá invocar o construtor primário. Nesse caso, você pode
especificar um valor diferente para os parâmetros do construtor primário. O código a
seguir mostra exemplos de construtores primários.

C#

// name isn't captured in Widget.


// width, height, and depth are captured as private fields
public class Widget(string name, int width, int height, int depth) :
NamedItem(name)
{
public Widget() : this("N/A", 1,1,1) {} // unnamed unit cube

public int WidthInCM => width;


public int HeightInCM => height;
public int DepthInCM => depth;

public int Volume => width * height * depth;


}
Você pode adicionar atributos ao método de construtor primário sintetizado
especificando o destino method: no atributo:

C#

[method: MyAttribute]
public class TaggedWidget(string name)
{
// details elided
}

Se você não especificar o destino method , o atributo será colocado na classe em vez do
método.

Nos tipos class e struct , os parâmetros do construtor primário estão disponíveis em


qualquer lugar no corpo do tipo. O parâmetro pode ser implementado como um campo
privado capturado. Se as únicas referências a um parâmetro forem inicializadores e
chamadas de construtor, esse parâmetro não será capturado em um campo privado. Os
usos em outros membros do tipo fazem com que o compilador capture o parâmetro em
um campo privado.

Se o tipo incluir o modificador record , o compilador sintetizará uma propriedade


pública com o mesmo nome que o parâmetro do construtor primário. Para tipos record
class , se um parâmetro de construtor primário usar o mesmo nome que um construtor

primário base, essa propriedade será uma propriedade pública do tipo base record
class . Ele não é duplicado no tipo derivado record class . Essas propriedades não são

geradas para tipos não record .

Confira também
Classes, structs e registros
Construtores
Finalizadores
base
this
Especificação de recurso de construtores primários

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
Construtores particulares (Guia de
Programação em C#)
Artigo • 13/03/2024

Um construtor particular é um construtor de instância especial. Normalmente, ele é


usado em classes que contêm apenas membros estáticos. Se uma classe tiver um ou
mais construtores particulares e nenhum construtor público, outras classes (exceto as
classes aninhadas) não poderão criar instâncias dessa classe. Por exemplo:

C#

class NLog
{
// Private Constructor:
private NLog() { }

public static double e = Math.E; //2.71828...


}

A declaração do construtor vazio impede a geração automática de um construtor sem


parâmetro. Observe que, se você não usar um modificador de acesso com o construtor,
ele ainda será privado por padrão. No entanto, o modificador private geralmente é
usado explicitamente para deixar claro que a classe não pode ser instanciada.

Construtores particulares são usados para impedir a criação de instâncias de uma classe
quando não há métodos ou campos de instância, como a classe Math ou quando um
método é chamado para obter uma instância de uma classe. Se todos os métodos na
classe forem estáticos, considere deixar toda a classe estática. Para obter mais
informações, consulte Classes Estáticas e Membros de Classes Estáticas.

Exemplo
A seguir, temos um exemplo de uma classe usando um construtor particular.

C#

public class Counter


{
private Counter() { }

public static int currentCount;

public static int IncrementCount()


{
return ++currentCount;
}
}

class TestCounter
{
static void Main()
{
// If you uncomment the following statement, it will generate
// an error because the constructor is inaccessible:
// Counter aCounter = new Counter(); // Error

Counter.currentCount = 100;
Counter.IncrementCount();
Console.WriteLine("New count: {0}", Counter.currentCount);

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
// Output: New count: 101

Observe que se você remover a marca de comentário da seguinte instrução do exemplo,


ela gerará um erro porque o construtor está inacessível devido a seu nível de proteção:

C#

// Counter aCounter = new Counter(); // Error

Confira também
Sistema de tipos do C#
Construtores
Finalizadores
private
público

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
Construtores estáticos (Guia de
Programação em C#)
Artigo • 03/08/2024

Um construtor estático é usado para inicializar quaisquer dados estáticos ou para


executar uma ação específica que precisa ser executada apenas uma vez. Ele é chamado
automaticamente antes que a primeira instância seja criada ou os membros estáticos
sejam referenciados. Um construtor estático é chamado no máximo uma vez.

C#

class SimpleClass
{
// Static variable that must be initialized at run time.
static readonly long baseline;

// Static constructor is called at most one time, before any


// instance constructor is invoked or member is accessed.
static SimpleClass()
{
baseline = DateTime.Now.Ticks;
}
}

Há várias ações que fazem parte da inicialização estática. Essas ações ocorrem na
seguinte ordem:

1. Os campos estáticos são definidos como 0. O runtime normalmente faz essa


inicialização.
2. Inicializadores de campo estático são executados. Os inicializadores de campo
estático na execução de tipo mais derivada.
3. Inicializadores de campo estático do tipo base são executados. Inicializadores de
campo estático começando com a base direta por meio de cada tipo base para
System.Object.
4. Qualquer construtor estático é executado. Todos os construtores estáticos, desde a
classe base final de Object.Object até cada classe base até a execução de tipo. A
ordem de execução do construtor estático não é especificada. No entanto, todos
os construtores estáticos na hierarquia são executados antes de qualquer instância
ser criada.

) Importante
Há uma exceção importante à regra em que um construtor estático é executado
antes de qualquer instância ser criada. Se um inicializador de campo estático criar
uma instância do tipo, esse inicializador será executado (incluindo qualquer
chamada para um construtor de instância) antes da execução do construtor
estático. Isso é mais comum no padrão singleton, conforme mostrado no exemplo a
seguir:

C#

public class Singleton


{
// Static field initializer calls instance constructor.
private static Singleton instance = new Singleton();

private Singleton()
{
Console.WriteLine("Executes before static constructor.");
}

static Singleton()
{
Console.WriteLine("Executes after instance constructor.");
}

public static Singleton Instance => instance;


}

Um inicializador de módulo pode ser uma alternativa a um construtor estático. Para


obter mais informações, consulte a especificação para inicializadores de módulo.

Comentários
Construtores estáticos têm as seguintes propriedades:

Um construtor estático não usa modificadores de acesso nem tem parâmetros.


Uma classe ou struct só pode ter um construtor estático.
Construtores estáticos não podem ser herdados ou sobrecarregados.
Um construtor estático não pode ser chamado diretamente e deve ser chamado
apenas pelo CLR (Common Language Runtime). Ele é invocado automaticamente.
O usuário não tem controle sobre quando o construtor estático é executado no
programa.
Um construtor estático é chamado automaticamente. Ele inicializa a classe antes
de a primeira instância ser criada ou de os membros estáticos declarados nessa
classe (não nas classes base) serem referenciados. Um construtor estático será
executado antes de um construtor de instância. Se os inicializadores de variável de
campo estático estiverem presentes na classe do construtor estático, eles serão
executados na ordem textual na qual aparecem na declaração de classe. Os
inicializadores são executados imediatamente antes do construtor estático.
Se você não fornecer um construtor estático para inicializar campos estáticos,
todos os campos estáticos serão inicializados com seu valor padrão, conforme
listado nos Valores padrão de tipos C#.
Se um construtor estático gerar uma exceção, o runtime não a invocará uma
segunda vez e o tipo permanecerá não inicializado durante o tempo de vida do
domínio do aplicativo. Normalmente, uma exceção TypeInitializationException é
lançada quando um construtor estático não consegue instanciar um tipo ou uma
exceção sem tratamento que ocorre em um construtor estático. Para construtores
estáticos que não estão explicitamente definidos no código-fonte, a solução de
problemas pode exigir a inspeção do código IL (linguagem intermediária).
A presença de um construtor estático impede a adição do atributo do tipo
BeforeFieldInit. Isso limita a otimização do runtime.
Um campo declarado como static readonly só pode ser atribuído como parte de
sua declaração ou em um construtor estático. Quando um construtor estático
explícito não for necessário, inicialize os campos estáticos na declaração, em vez
de usar um construtor estático para melhorar a otimização do runtime.
O runtime chama um construtor estático não mais do que uma vez em um único
domínio do aplicativo. Essa chamada é feita em uma região bloqueada com base
no tipo específico da classe. Nenhum mecanismo de bloqueio extra é necessário
no corpo de um construtor estático. Para evitar o risco de deadlocks, não bloqueie
o thread atual em inicializadores e construtores estáticos. Por exemplo, não
aguarde tarefas, threads, identificadores de espera ou eventos, não adquira
bloqueios e não execute o bloqueio de operações paralelas, como loops paralelos
Parallel.Invoke e consultas LINQ paralelas.

7 Observação

Embora não seja diretamente acessível, a presença de um construtor estático


explícito deve ser documentada para auxiliar na solução de problemas de exceções
de inicialização.

Uso
Um uso típico de construtores estáticos é quando a classe está usando um arquivo
de log e o construtor é usado para gravar entradas nesse arquivo.
Construtores estáticos também são úteis ao criar classes wrapper para código não
gerenciado quando o construtor pode chamar o método LoadLibrary .
Construtores estáticos também são um local conveniente para impor verificações
em tempo de execução no parâmetro de tipo que não podem ser verificadas no
tempo de compilação por meio de restrições de parâmetro de tipo.

Exemplo
Nesse exemplo, a classe Bus tem um construtor estático. Quando a primeira instância
do Bus for criada ( bus1 ), o construtor estático será invocado para inicializar a classe. O
exemplo de saída verifica se o construtor estático é executado somente uma vez,
mesmo se duas instâncias de Bus forem criadas e se é executado antes que o construtor
da instância seja executado.

C#

public class Bus


{
// Static variable used by all Bus instances.
// Represents the time the first bus of the day starts its route.
protected static readonly DateTime globalStartTime;

// Property for the number of each bus.


protected int RouteNumber { get; set; }

// Static constructor to initialize the static variable.


// It is invoked before the first instance constructor is run.
static Bus()
{
globalStartTime = DateTime.Now;

// The following statement produces the first line of output,


// and the line occurs only once.
Console.WriteLine("Static constructor sets global start time to
{0}",
globalStartTime.ToLongTimeString());
}

// Instance constructor.
public Bus(int routeNum)
{
RouteNumber = routeNum;
Console.WriteLine("Bus #{0} is created.", RouteNumber);
}

// Instance method.
public void Drive()
{
TimeSpan elapsedTime = DateTime.Now - globalStartTime;
// For demonstration purposes we treat milliseconds as minutes to
simulate
// actual bus times. Do not do this in your actual bus schedule
program!
Console.WriteLine("{0} is starting its route {1:N2} minutes after
global start time {2}.",
this.RouteNumber,
elapsedTime.Milliseconds,
globalStartTime.ToShortTimeString());
}
}

class TestBus
{
static void Main()
{
// The creation of this instance activates the static constructor.
Bus bus1 = new Bus(71);

// Create a second bus.


Bus bus2 = new Bus(72);

// Send bus1 on its way.


bus1.Drive();

// Wait for bus2 to warm up.


System.Threading.Thread.Sleep(25);

// Send bus2 on its way.


bus2.Drive();

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Sample output:
Static constructor sets global start time to 3:57:08 PM.
Bus #71 is created.
Bus #72 is created.
71 is starting its route 6.00 minutes after global start time 3:57 PM.
72 is starting its route 31.00 minutes after global start time 3:57 PM.
*/

Especificação da linguagem C#
Para saber mais, confira a seção Construtores estáticos da Especificação da linguagem
C#.
Confira também
Sistema de tipos do C#
Construtores
Classes static e membros de classes static
Finalizadores
Diretrizes de design de construtor
Aviso de segurança – CA2121: os construtores estáticos devem ser privados
Inicializadores de módulo

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
Como escrever um construtor de cópia
(Guia de Programação em C#)
Artigo • 04/07/2024

Os registros em C# fornecem um construtor de cópia para objetos, mas para classes


você precisa escrever um por conta própria.

) Importante

Escrever construtores de cópia que funcionam para todos os tipos derivados em


uma hierarquia de classe pode ser difícil. Se a sua classe não for sealed , você
deverá criar uma hierarquia de tipos record class para usar o construtor de cópia
sintetizado pelo compilador.

Exemplo
No exemplo a seguir, a classe Person define um construtor de cópia que usa, como seu
argumento, uma instância de Person . Os valores das propriedades do argumento são
atribuídos às propriedades da nova instância de Person . O código contém um
construtor de cópia alternativa que envia as propriedades Name e Age da instância que
você deseja copiar para o construtor de instância da classe. A classe Person é sealed ,
portanto, nenhum tipo derivado pode ser declarado que possa introduzir erros
copiando apenas a classe base.

C#

public sealed class Person


{
// Copy constructor.
public Person(Person previousPerson)
{
Name = previousPerson.Name;
Age = previousPerson.Age;
}

//// Alternate copy constructor calls the instance constructor.


//public Person(Person previousPerson)
// : this(previousPerson.Name, previousPerson.Age)
//{
//}

// Instance constructor.
public Person(string name, int age)
{
Name = name;
Age = age;
}

public int Age { get; set; }

public string Name { get; set; }

public string Details()


{
return Name + " is " + Age.ToString();
}
}

class TestPerson
{
static void Main()
{
// Create a Person object by using the instance constructor.
Person person1 = new Person("George", 40);

// Create another Person object, copying person1.


Person person2 = new Person(person1);

// Change each person's age.


person1.Age = 39;
person2.Age = 41;

// Change person2's name.


person2.Name = "Charles";

// Show details to verify that the name and age fields are distinct.
Console.WriteLine(person1.Details());
Console.WriteLine(person2.Details());

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
// Output:
// George is 39
// Charles is 41

Confira também
ICloneable
Registros
O sistema de tipos C#
Construtores
Finalizadores

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
Finalizadores (Guia de Programação em
C#)
Artigo • 14/03/2023

Os finalizadores (historicamente chamados de destruidores) são usados para executar a


limpeza final necessária quando uma instância da classe está sendo coletada pelo
coletor de lixo. Na maioria dos casos, você pode evitar escrever um finalizador usando
System.Runtime.InteropServices.SafeHandle ou as classes derivadas para encapsular
qualquer identificador não gerenciado.

Comentários
Os finalizadores não podem ser definidos em structs. Eles são usados somente
com classes.
Uma classe pode ter somente um finalizador.
Os finalizadores não podem ser herdados ou sobrecarregados.
Os finalizadores não podem ser chamados. Eles são invocados automaticamente.
Um finalizador não usa modificadores ou não tem parâmetros.

Por exemplo, o seguinte é uma declaração de um finalizador para a classe Car .

C#

class Car
{
~Car() // finalizer
{
// cleanup statements...
}
}

Um finalizador também pode ser implementado como uma definição do corpo da


expressão, como mostra o exemplo a seguir.

C#

public class Destroyer


{
public override string ToString() => GetType().Name;

~Destroyer() => Console.WriteLine($"The {ToString()} finalizer is


executing.");
}
O finalizador chama implicitamente Finalize na classe base do objeto. Portanto, uma
chamada para um finalizador é convertida implicitamente para o código a seguir:

C#

protected override void Finalize()


{
try
{
// Cleanup statements...
}
finally
{
base.Finalize();
}
}

Esse arranjo significa que o método Finalize é chamado de forma recursiva para todas
as instâncias da cadeia de herança, da mais derivada à menos derivada.

7 Observação

Finalizadores vazios não devem ser usados. Quando uma classe contém um
finalizador, uma entrada é criada na fila Finalize . Essa fila é processada pelo
coletor de lixo. Quando o coletor de lixo processa a fila, ele chama cada finalizador.
Finalizadores desnecessários, incluindo finalizadores vazios, finalizadores que
chamam apenas o finalizador de classe base ou finalizadores que chamam apenas
métodos emitidos condicionalmente, causam uma perda desnecessária de
desempenho.

O programador não tem controle sobre quando o finalizador é chamado porque isso é
determinado pelo coletor de lixo. O coletor de lixo procura objetos que não estão mais
sendo usados pelo aplicativo. Se considerar um objeto qualificado para finalização, ele
chamará o finalizador (se houver) e recuperará a memória usada para armazenar o
objeto. É possível forçar a coleta de lixo chamando Collect, mas na maioria das vezes
essa chamada deve ser evitada, porque pode criar problemas de desempenho.

7 Observação

Executar ou não os finalizadores como parte do encerramento do aplicativo é uma


situação específica para cada implementação do .NET. Quando um aplicativo é
encerrado, o .NET Framework faz todos os esforços razoáveis para chamar
finalizadores para objetos que ainda não foram coletados, a menos que essa
limpeza tenha sido suprimida (por uma chamada ao método de biblioteca
GC.SuppressFinalize , por exemplo). O .NET 5 (incluindo o .NET Core) e versões

posteriores não chamam finalizadores como parte do encerramento do aplicativo.


Para obter mais informações, consulte o problema do GitHub
dotnet/csharpstandard #291 .

Se você precisar executar a limpeza de forma confiável quando um aplicativo for


encerrado, registre um manipulador para o evento System.AppDomain.ProcessExit. Esse
manipulador garantiria que IDisposable.Dispose() (ou) IAsyncDisposable.DisposeAsync()
fosse chamado para todos os objetos que exigem limpeza antes da saída do aplicativo.
Como você não pode chamar Finalizar diretamente e não pode garantir que o coletor
de lixo chame todos os finalizadores antes de sair, você deve usar Dispose ou
DisposeAsync garantir que os recursos sejam liberados.

Usar finalizadores para liberar recursos


Em geral, o C# não requer tanto gerenciamento de memória por parte do
desenvolvedor quanto linguagens que não têm como destino um runtime com coleta
de lixo. Isso ocorre porque o coletor de lixo do .NET gerencia implicitamente a alocação
e a liberação de memória para seus objetos. No entanto, quando seu aplicativo
encapsula recursos não gerenciados, como janelas, arquivos e conexões de rede, você
deve usar finalizadores para liberar esses recursos. Quando o objeto está qualificado
para finalização, o coletor de lixo executa o método Finalize do objeto.

Liberação explícita de recursos


Se seu aplicativo estiver usando um recurso externo caro, também será recomendável
fornecer uma maneira de liberar explicitamente o recurso antes que o coletor de lixo
libere o objeto. Para liberar o recurso, implemente um método Dispose da interface
IDisposable que executa a limpeza necessária para o objeto. Isso pode melhorar
consideravelmente o desempenho do aplicativo. Mesmo com esse controle explícito
sobre os recursos, o finalizador se tornará uma proteção usada para limpar os recursos
se a chamada para o método Dispose falhar.

Para obter mais informações sobre como limpar recursos, consulte os seguintes artigos:

Limpando recursos não gerenciados


Implementando um método Dispose
Implementar um método DisposeAsync
Instrução using

Exemplo
O exemplo a seguir cria três classes que compõem uma cadeia de herança. A classe
First é a classe base, Second é derivado de First e Third é derivado de Second . Todas
as três têm finalizadores. Em Main , uma instância da classe mais derivada é criada. A
saída desse código depende de qual implementação do .NET o aplicativo tem como
destino:

.NET Framework: a saída mostra que os finalizadores das três classes são
chamados automaticamente quando o aplicativo é encerrado, na ordem do mais
derivado para o menos derivado.
.NET 5 (incluindo o .NET Core) ou uma versão posterior: não há saída, porque essa
implementação do .NET não chama finalizadores quando o aplicativo termina.

C#

class First
{
~First()
{
System.Diagnostics.Trace.WriteLine("First's finalizer is called.");
}
}

class Second : First


{
~Second()
{
System.Diagnostics.Trace.WriteLine("Second's finalizer is called.");
}
}

class Third : Second


{
~Third()
{
System.Diagnostics.Trace.WriteLine("Third's finalizer is called.");
}
}

/*
Test with code like the following:
Third t = new Third();
t = null;

When objects are finalized, the output would be:


Third's finalizer is called.
Second's finalizer is called.
First's finalizer is called.
*/

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

Confira também
IDisposable
Guia de Programação em C#
Construtores
Coleta de lixo
Inicializadores de objeto e coleção (Guia
de Programação em C#)
Artigo • 27/05/2024

O C# permite criar uma instância de um objeto ou uma coleção e executar as


atribuições de membro em uma única instrução.

Inicializadores de objeto
Os inicializadores de objeto permitem atribuir valores a quaisquer campos ou
propriedades acessíveis de um objeto na hora de criação sem que seja necessário
invocar um construtor seguido por linhas de instruções de atribuição. A sintaxe do
inicializador de objeto permite especificar argumentos para um construtor ou omitir os
argumentos (e a sintaxe de parênteses). O exemplo a seguir mostra como usar um
inicializador de objeto com um tipo nomeado, Cat , e como invocar o construtor sem
parâmetros. Observe o uso de propriedades autoimplementadas na classe Cat . Para
obter mais informações, consulte Propriedades autoimplementadas.

C#

public class Cat


{
// Auto-implemented properties.
public int Age { get; set; }
public string? Name { get; set; }

public Cat()
{
}

public Cat(string name)


{
this.Name = name;
}
}

C#

Cat cat = new Cat { Age = 10, Name = "Fluffy" };


Cat sameCat = new Cat("Fluffy"){ Age = 10 };

A sintaxe dos inicializadores de objetos permite que você crie uma instância, e depois
atribui o objeto recém-criado, com suas propriedades atribuídas, à variável na
atribuição.

Os inicializadores de objeto podem definir indexadores, além de atribuir campos e


propriedades. Considere esta classe Matrix básica:

C#

public class Matrix


{
private double[,] storage = new double[3, 3];

public double this[int row, int column]


{
// The embedded array will throw out of range exceptions as
appropriate.
get { return storage[row, column]; }
set { storage[row, column] = value; }
}
}

Você poderia inicializar a matriz de identidade com o código a seguir:

C#

var identity = new Matrix


{
[0, 0] = 1.0,
[0, 1] = 0.0,
[0, 2] = 0.0,

[1, 0] = 0.0,
[1, 1] = 1.0,
[1, 2] = 0.0,

[2, 0] = 0.0,
[2, 1] = 0.0,
[2, 2] = 1.0,
};

Nenhum indexador acessível que contenha um setter acessível pode ser usado como
uma das expressões no inicializador de objeto, independentemente do número ou dos
tipos de argumentos. Os argumentos de índice formam o lado esquerdo da atribuição e
o valor é o lado direito da expressão. Por exemplo, todos os seguintes inicializadores
são válidos se IndexersExample tiver os indexadores apropriados:

C#

var thing = new IndexersExample


{
name = "object one",
[1] = '1',
[2] = '4',
[3] = '9',
Size = Math.PI,
['C',4] = "Middle C"
}

Para que o código anterior seja compilado, o tipo IndexersExample precisará ter os
seguintes membros:

C#

public string name;


public double Size { set { ... }; }
public char this[int i] { set { ... }; }
public string this[char c, int i] { set { ... }; }

Inicializadores de objeto com tipos anônimos


Embora inicializadores de objetos possam ser usados em qualquer contexto, eles são
especialmente úteis em expressões de consulta LINQ. Expressões de consulta fazem uso
frequente de tipos anônimos, que podem ser inicializados somente usando um
inicializador de objeto, como mostrado na declaração a seguir.

C#

var pet = new { Age = 10, Name = "Fluffy" };

Os tipos anônimos habilitam a cláusula select em uma expressão de consulta LINQ a


transformar objetos da sequência original em objetos cujo valor e forma podem diferir
dos originais. Talvez convenha armazenar apenas uma parte das informações de cada
objeto em uma sequência. No exemplo a seguir, suponha que um objeto de produto
( p ) contenha muitos campos e métodos e que você só está interessado em criar uma
sequência de objetos que contêm o nome do produto e o preço unitário.

C#

var productInfos =
from p in products
select new { p.ProductName, p.UnitPrice };
Quando essa consulta é executada, a variável productInfos contém uma sequência de
objetos que podem ser acessados em uma instrução foreach como mostrado neste
exemplo:

C#

foreach(var p in productInfos){...}

Cada objeto no novo tipo anônimo tem duas propriedades públicas que recebem os
mesmos nomes que as propriedades ou os campos no objeto original. Você também
poderá renomear um campo quando estiver criando um tipo anônimo; o exemplo a
seguir renomeia o campo UnitPrice como Price .

C#

select new {p.ProductName, Price = p.UnitPrice};

Inicializadores de objeto com o modificador


required
Use a palavra-chave required para forçar os chamadores a definir o valor de uma
propriedade ou campo usando um inicializador de objeto. As propriedades necessárias
não precisam ser definidas como parâmetros de construtor. O compilador garante que
todos os chamadores inicializem esses valores.

C#

public class Pet


{
public required int Age;
public string Name;
}

// `Age` field is necessary to be initialized.


// You don't need to initialize `Name` property
var pet = new Pet() { Age = 10};

// Compiler error:
// Error CS9035 Required member 'Pet.Age' must be set in the object
initializer or attribute constructor.
// var pet = new Pet();

É uma prática típica garantir que seu objeto seja inicializado corretamente,
especialmente quando você tem vários campos ou propriedades para gerenciar e não
deseja incluir todos no construtor.

Inicializadores de objeto com o acessador init


Garantir que ninguém altere o objeto projetado pode ser limitado usando um acessador
init . Ele ajuda a restringir a configuração do valor da propriedade.

C#

public class Person


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

// The `LastName` property can be set only during initialization. It CAN'T


be modified afterwards.
// The `FirstName` property can be modified after initialization.
var pet = new Person() { FirstName = "Joe", LastName = "Doe"};

// You can assign the FirstName property to a different value.


pet.FirstName = "Jane";

// Compiler error:
// Error CS8852 Init - only property or indexer 'Person.LastName' can only
be assigned in an object initializer,
// or on 'this' or 'base' in an instance constructor or an
'init' accessor.
// pet.LastName = "Kowalski";

As propriedades somente inicialização necessárias dão suporte a estruturas imutáveis,


permitindo a sintaxe natural para os usuários do tipo.

Inicializadores de objeto com propriedades


tipadas por classe
É fundamental considerar as implicações para propriedades de tipo de classe ao
inicializar um objeto:

C#

public class HowToClassTypedInitializer


{
public class EmbeddedClassTypeA
{
public int I { get; set; }
public bool B { get; set; }
public string S { get; set; }
public EmbeddedClassTypeB ClassB { get; set; }

public override string ToString() => $"{I}|{B}|{S}|||{ClassB}";

public EmbeddedClassTypeA()
{
Console.WriteLine($"Entering EmbeddedClassTypeA constructor.
Values are: {this}");
I = 3;
B = true;
S = "abc";
ClassB = new() { BB = true, BI = 43 };
Console.WriteLine($"Exiting EmbeddedClassTypeA constructor.
Values are: {this})");
}
}

public class EmbeddedClassTypeB


{
public int BI { get; set; }
public bool BB { get; set; }
public string BS { get; set; }

public override string ToString() => $"{BI}|{BB}|{BS}";

public EmbeddedClassTypeB()
{
Console.WriteLine($"Entering EmbeddedClassTypeB constructor.
Values are: {this}");
BI = 23;
BB = false;
BS = "BBBabc";
Console.WriteLine($"Exiting EmbeddedClassTypeB constructor.
Values are: {this})");
}
}

public static void Main()


{
var a = new EmbeddedClassTypeA
{
I = 103,
B = false,
ClassB = { BI = 100003 }
};
Console.WriteLine($"After initializing EmbeddedClassTypeA: {a}");

var a2 = new EmbeddedClassTypeA


{
I = 103,
B = false,
ClassB = new() { BI = 100003 } //New instance
};
Console.WriteLine($"After initializing EmbeddedClassTypeA a2:
{a2}");
}

// Output:
//Entering EmbeddedClassTypeA constructor Values are: 0|False||||
//Entering EmbeddedClassTypeB constructor Values are: 0|False|
//Exiting EmbeddedClassTypeB constructor Values are: 23|False|BBBabc)
//Exiting EmbeddedClassTypeA constructor Values are:
3|True|abc|||43|True|BBBabc)
//After initializing EmbeddedClassTypeA:
103|False|abc|||100003|True|BBBabc
//Entering EmbeddedClassTypeA constructor Values are: 0|False||||
//Entering EmbeddedClassTypeB constructor Values are: 0|False|
//Exiting EmbeddedClassTypeB constructor Values are: 23|False|BBBabc)
//Exiting EmbeddedClassTypeA constructor Values are:
3|True|abc|||43|True|BBBabc)
//Entering EmbeddedClassTypeB constructor Values are: 0|False|
//Exiting EmbeddedClassTypeB constructor Values are: 23|False|BBBabc)
//After initializing EmbeddedClassTypeA a2:
103|False|abc|||100003|False|BBBabc
}

O exemplo a seguir mostra como, para o ClassB, o processo de inicialização envolve a


atualização de valores específicos, mantendo outros da instância original. O inicializador
reutiliza a instância atual. Os valores de ClassB são: 100003 (novo valor que atribuímos
aqui), true (mantido da inicialização de EmbeddedClassTypeA), BBBabc (padrão
inalterado de EmbeddedClassTypeB).

Inicializadores de coleção
Os inicializadores de coleção permitem especificar um ou mais inicializadores de
elemento quando você inicializa um tipo de coleção que implementa IEnumerable e tem
Add com a assinatura apropriada como um método de instância ou um método de

extensão. Os inicializadores de elemento podem ser um valor, uma expressão ou um


inicializador de objeto. Ao usar um inicializador de coleção, você não precisa especificar
várias chamadas. O compilador adiciona as chamadas automaticamente.

O exemplo a seguir mostra dois inicializadores de coleção simples:

C#

List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };


List<int> digits2 = new List<int> { 0 + 1, 12 % 3, MakeInt() };

O inicializador de coleção a seguir usa inicializadores de objeto para inicializar objetos


da classe Cat definida em um exemplo anterior. Os inicializadores de objeto individuais
são envolvidos por chaves e separados por vírgulas.

C#

List<Cat> cats = new List<Cat>


{
new Cat{ Name = "Sylvester", Age=8 },
new Cat{ Name = "Whiskers", Age=2 },
new Cat{ Name = "Sasha", Age=14 }
};

Você poderá especificar nulo como um elemento em um inicializador de coleção se o


método Add da coleção permitir.

C#

List<Cat?> moreCats = new List<Cat?>


{
new Cat{ Name = "Furrytail", Age=5 },
new Cat{ Name = "Peaches", Age=4 },
null
};

É possível especificar elementos indexados quando a coleção é compatível com


indexação de leitura/gravação.

C#

var numbers = new Dictionary<int, string>


{
[7] = "seven",
[9] = "nine",
[13] = "thirteen"
};

O exemplo anterior gera o código que chama o Item[TKey] para definir os valores. Você
também poderia inicializar dicionários e outros contêineres associativos usando a
seguinte sintaxe. Observe que, em vez da sintaxe do indexador, com parênteses e uma
atribuição, ele usa um objeto com vários valores:

C#

var moreNumbers = new Dictionary<int, string>


{
{19, "nineteen" },
{23, "twenty-three" },
{42, "forty-two" }
};

Este exemplo de inicializador chama Add(TKey, TValue) para adicionar os três itens no
dicionário. Essas duas maneiras diferentes para inicializar coleções associativas tem um
comportamento um pouco diferente devido às chamadas de método que o compilador
gera. As duas variantes trabalham com a classe Dictionary . Outros tipos podem ser
compatíveis apenas com uma ou com outra, dependendo da API pública deles.

Inicializadores de objeto com inicialização de


propriedade somente leitura da coleção
Algumas classes podem ter propriedades de coleção em que a propriedade é somente
leitura, como a propriedade Cats de CatOwner no seguinte caso:

C#

public class CatOwner


{
public IList<Cat> Cats { get; } = new List<Cat>();
}

Você não pode usar a sintaxe do inicializador de coleção discutida até o momento, pois
a propriedade não pode ser atribuída a uma nova lista:

C#

CatOwner owner = new CatOwner


{
Cats = new List<Cat>
{
new Cat{ Name = "Sylvester", Age=8 },
new Cat{ Name = "Whiskers", Age=2 },
new Cat{ Name = "Sasha", Age=14 }
}
};

No entanto, novas entradas podem ser adicionadas a Cats usando a sintaxe de


inicialização omitindo a criação da lista ( new List<Cat> ), conforme mostrado a seguir:

C#

CatOwner owner = new CatOwner


{
Cats =
{
new Cat{ Name = "Sylvester", Age=8 },
new Cat{ Name = "Whiskers", Age=2 },
new Cat{ Name = "Sasha", Age=14 }
}
};

O conjunto de entradas a serem adicionadas é exibido entre parênteses. O código


anterior é idêntico à gravação:

C#

CatOwner owner = new ();


owner.Cats.Add(new Cat{ Name = "Sylvester", Age=8 });
owner.Cats.Add(new Cat{ Name = "Whiskers", Age=2 });
owner.Cats.Add(new Cat{ Name = "Sasha", Age=14 });

Exemplos
O exemplo a seguir combina os conceitos de inicializadores de coleção e objeto.

C#

public class InitializationSample


{
public class Cat
{
// Auto-implemented properties.
public int Age { get; set; }
public string? Name { get; set; }

public Cat() { }

public Cat(string name)


{
Name = name;
}
}

public static void Main()


{
Cat cat = new Cat { Age = 10, Name = "Fluffy" };
Cat sameCat = new Cat("Fluffy"){ Age = 10 };

List<Cat> cats = new List<Cat>


{
new Cat { Name = "Sylvester", Age = 8 },
new Cat { Name = "Whiskers", Age = 2 },
new Cat { Name = "Sasha", Age = 14 }
};

List<Cat?> moreCats = new List<Cat?>


{
new Cat { Name = "Furrytail", Age = 5 },
new Cat { Name = "Peaches", Age = 4 },
null
};

// Display results.
System.Console.WriteLine(cat.Name);

foreach (Cat c in cats)


{
System.Console.WriteLine(c.Name);
}

foreach (Cat? c in moreCats)


{
if (c != null)
{
System.Console.WriteLine(c.Name);
}
else
{
System.Console.WriteLine("List element has null value.");
}
}
}
// Output:
//Fluffy
//Sylvester
//Whiskers
//Sasha
//Furrytail
//Peaches
//List element has null value.
}

O exemplo a seguir mostra um objeto que implementa IEnumerable e contém um


método Add com vários parâmetros. Ele usa um inicializador de coleção com vários
elementos por item na lista que correspondem à assinatura do método Add .

C#

public class FullExample


{
class FormattedAddresses : IEnumerable<string>
{
private List<string> internalList = new List<string>();
public IEnumerator<string> GetEnumerator() =>
internalList.GetEnumerator();
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator() =>
internalList.GetEnumerator();

public void Add(string firstname, string lastname,


string street, string city,
string state, string zipcode) => internalList.Add($"""
{firstname} {lastname}
{street}
{city}, {state} {zipcode}
"""
);
}

public static void Main()


{
FormattedAddresses addresses = new FormattedAddresses()
{
{"John", "Doe", "123 Street", "Topeka", "KS", "00000" },
{"Jane", "Smith", "456 Street", "Topeka", "KS", "00000" }
};

Console.WriteLine("Address Entries:");

foreach (string addressEntry in addresses)


{
Console.WriteLine("\r\n" + addressEntry);
}
}

/*
* Prints:

Address Entries:

John Doe
123 Street
Topeka, KS 00000

Jane Smith
456 Street
Topeka, KS 00000
*/
}

Os métodos Add podem usar a palavra-chave params para obter um número variável de
argumentos, como mostrado no seguinte exemplo. Este exemplo também demonstra a
implementação personalizada de um indexador para inicializar uma coleção usando
índices. A partir do C# 13, o parâmetro params não é restrito a uma matriz. Pode ser um
tipo de coleção ou interface.
C#

public class DictionaryExample


{
class RudimentaryMultiValuedDictionary<TKey, TValue> :
IEnumerable<KeyValuePair<TKey, List<TValue>>> where TKey : notnull
{
private Dictionary<TKey, List<TValue>> internalDictionary = new
Dictionary<TKey, List<TValue>>();

public IEnumerator<KeyValuePair<TKey, List<TValue>>> GetEnumerator()


=> internalDictionary.GetEnumerator();

System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator() =>
internalDictionary.GetEnumerator();

public List<TValue> this[TKey key]


{
get => internalDictionary[key];
set => Add(key, value);
}

public void Add(TKey key, params TValue[] values) => Add(key,


(IEnumerable<TValue>)values);

public void Add(TKey key, IEnumerable<TValue> values)


{
if (!internalDictionary.TryGetValue(key, out List<TValue>?
storedValues))
{
internalDictionary.Add(key, storedValues = new List<TValue>
());
}
storedValues.AddRange(values);
}
}

public static void Main()


{
RudimentaryMultiValuedDictionary<string, string>
rudimentaryMultiValuedDictionary1
= new RudimentaryMultiValuedDictionary<string, string>()
{
{"Group1", "Bob", "John", "Mary" },
{"Group2", "Eric", "Emily", "Debbie", "Jesse" }
};
RudimentaryMultiValuedDictionary<string, string>
rudimentaryMultiValuedDictionary2
= new RudimentaryMultiValuedDictionary<string, string>()
{
["Group1"] = new List<string>() { "Bob", "John", "Mary" },
["Group2"] = new List<string>() { "Eric", "Emily", "Debbie",
"Jesse" }
};
RudimentaryMultiValuedDictionary<string, string>
rudimentaryMultiValuedDictionary3
= new RudimentaryMultiValuedDictionary<string, string>()
{
{"Group1", new string []{ "Bob", "John", "Mary" } },
{ "Group2", new string[]{ "Eric", "Emily", "Debbie", "Jesse"
} }
};

Console.WriteLine("Using first multi-valued dictionary created with


a collection initializer:");

foreach (KeyValuePair<string, List<string>> group in


rudimentaryMultiValuedDictionary1)
{
Console.WriteLine($"\r\nMembers of group {group.Key}: ");

foreach (string member in group.Value)


{
Console.WriteLine(member);
}
}

Console.WriteLine("\r\nUsing second multi-valued dictionary created


with a collection initializer using indexing:");

foreach (KeyValuePair<string, List<string>> group in


rudimentaryMultiValuedDictionary2)
{
Console.WriteLine($"\r\nMembers of group {group.Key}: ");

foreach (string member in group.Value)


{
Console.WriteLine(member);
}
}
Console.WriteLine("\r\nUsing third multi-valued dictionary created
with a collection initializer using indexing:");

foreach (KeyValuePair<string, List<string>> group in


rudimentaryMultiValuedDictionary3)
{
Console.WriteLine($"\r\nMembers of group {group.Key}: ");

foreach (string member in group.Value)


{
Console.WriteLine(member);
}
}
}

/*
* Prints:
Using first multi-valued dictionary created with a collection
initializer:

Members of group Group1:


Bob
John
Mary

Members of group Group2:


Eric
Emily
Debbie
Jesse

Using second multi-valued dictionary created with a collection


initializer using indexing:

Members of group Group1:


Bob
John
Mary

Members of group Group2:


Eric
Emily
Debbie
Jesse

Using third multi-valued dictionary created with a collection


initializer using indexing:

Members of group Group1:


Bob
John
Mary

Members of group Group2:


Eric
Emily
Debbie
Jesse
*/
}

Confira também
Usar inicializadores de objeto (regra de estilo IDE0017)
Usar inicializadores de coleção (regra de estilo IDE0028)
LINQ em C#
Tipos anônimos
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
Como inicializar objetos usando um
inicializador de objeto (Guia de
Programação em C#)
Artigo • 15/05/2024

Você pode usar os inicializadores de objeto para inicializar objetos de tipo de maneira
declarativa, sem invocar explicitamente um construtor para o tipo.

Os exemplos a seguir mostram como usar os inicializadores de objeto com objetos


nomeados. O compilador processa os inicializadores de objetos ao acessar, primeiro, o
construtor de instância sem parâmetro e, em seguida, processar as inicializações de
membro. Portanto, se o construtor sem parâmetros for declarado como private na
classe, os inicializadores de objeto que exigem acesso público falharão.

Se você estiver definindo um tipo anônimo, é necessário usar um inicializador de objeto.


Para obter mais informações, consulte Como retornar subconjuntos de propriedades de
elementos em uma consulta.

Exemplo
O exemplo a seguir mostra como inicializar um novo tipo StudentName , usando
inicializadores de objeto. Este exemplo define as propriedades de StudentName tipo:

C#

public class HowToObjectInitializers


{
public static void Main()
{
// Declare a StudentName by using the constructor that has two
parameters.
StudentName student1 = new StudentName("Craig", "Playstead");

// Make the same declaration by using an object initializer and


sending
// arguments for the first and last names. The parameterless
constructor is
// invoked in processing this declaration, not the constructor that
has
// two parameters.
StudentName student2 = new StudentName
{
FirstName = "Craig",
LastName = "Playstead"
};

// Declare a StudentName by using an object initializer and sending


// an argument for only the ID property. No corresponding
constructor is
// necessary. Only the parameterless constructor is used to process
object
// initializers.
StudentName student3 = new StudentName
{
ID = 183
};

// Declare a StudentName by using an object initializer and sending


// arguments for all three properties. No corresponding constructor
is
// defined in the class.
StudentName student4 = new StudentName
{
FirstName = "Craig",
LastName = "Playstead",
ID = 116
};

Console.WriteLine(student1.ToString());
Console.WriteLine(student2.ToString());
Console.WriteLine(student3.ToString());
Console.WriteLine(student4.ToString());
}
// Output:
// Craig 0
// Craig 0
// 183
// Craig 116

public class StudentName


{
// This constructor has no parameters. The parameterless constructor
// is invoked in the processing of object initializers.
// You can test this by changing the access modifier from public to
// private. The declarations in Main that use object initializers
will
// fail.
public StudentName() { }

// The following constructor has parameters for two of the three


// properties.
public StudentName(string first, string last)
{
FirstName = first;
LastName = last;
}

// Properties.
public string? FirstName { get; set; }
public string? LastName { get; set; }
public int ID { get; set; }

public override string ToString() => FirstName + " " + ID;


}
}

Os inicializadores de objeto podem ser usados para definir indexadores em um objeto.


O exemplo a seguir define uma classe BaseballTeam que usa um indexador para obter e
definir jogadores em posições diferentes. O inicializador pode atribuir jogadores com
base na abreviação da posição ou no número usado para cada scorecard de beisebol de
posição:

C#

public class HowToIndexInitializer


{
public class BaseballTeam
{
private string[] players = new string[9];
private readonly List<string> positionAbbreviations = new
List<string>
{
"P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF"
};

public string this[int position]


{
// Baseball positions are 1 - 9.
get { return players[position-1]; }
set { players[position-1] = value; }
}
public string this[string position]
{
get { return players[positionAbbreviations.IndexOf(position)]; }
set { players[positionAbbreviations.IndexOf(position)] = value;
}
}
}

public static void Main()


{
var team = new BaseballTeam
{
["RF"] = "Mookie Betts",
[4] = "Jose Altuve",
["CF"] = "Mike Trout"
};

Console.WriteLine(team["2B"]);
}
}

O próximo exemplo mostra a ordem de execução das inicializações de construtores e


membros usando construtor com e sem parâmetro:

C#

Confira também
Inicializadores de objeto e coleçã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
Como inicializar um dicionário com um
inicializador de coleção (Guia de
Programação em C#)
Artigo • 21/03/2024

Um Dictionary<TKey,TValue> contém uma coleção de pares de chave-valor. Seu


método Add recebe dois parâmetros, um para a chave e outro para o valor. Uma
maneira de inicializar um Dictionary<TKey,TValue> ou qualquer coleção cujo método
Add use vários parâmetros, é colocar cada conjunto de parâmetros entre chaves,

conforme mostrado no exemplo a seguir. Outra opção é usar um inicializador de índice,


também mostrado no exemplo a seguir.

7 Observação

A principal diferença entre essas duas maneiras de inicializar a coleção é que, no


caso de ter chaves duplicadas, por exemplo:

C#

{ 111, new StudentName { FirstName="Sachin", LastName="Karnik", ID=211


} },
{ 111, new StudentName { FirstName="Dina", LastName="Salimzianova",
ID=317 } },

O método Add gerará ArgumentException: 'An item with the same key has
already been added. Key: 111' , enquanto a segunda parte do exemplo, o método

de indexador público de leitura/gravação, substituirá silenciosamente a entrada já


existente com a mesma chave.

Exemplo
No exemplo de código a seguir, um Dictionary<TKey,TValue> é inicializado com
instâncias do tipo StudentName . A primeira inicialização usa o método Add com dois
argumentos. O compilador gera uma chamada para Add para cada um dos pares de
chaves int e valores StudentName . A segunda usa um método de indexador público de
leitura/gravação da classe Dictionary :

C#
public class HowToDictionaryInitializer
{
class StudentName
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public int ID { get; set; }
}

public static void Main()


{
var students = new Dictionary<int, StudentName>()
{
{ 111, new StudentName { FirstName="Sachin", LastName="Karnik",
ID=211 } },
{ 112, new StudentName { FirstName="Dina",
LastName="Salimzianova", ID=317 } },
{ 113, new StudentName { FirstName="Andy", LastName="Ruth",
ID=198 } }
};

foreach(var index in Enumerable.Range(111, 3))


{
Console.WriteLine($"Student {index} is
{students[index].FirstName} {students[index].LastName}");
}
Console.WriteLine();

var students2 = new Dictionary<int, StudentName>()


{
[111] = new StudentName { FirstName="Sachin", LastName="Karnik",
ID=211 },
[112] = new StudentName { FirstName="Dina",
LastName="Salimzianova", ID=317 } ,
[113] = new StudentName { FirstName="Andy", LastName="Ruth",
ID=198 }
};

foreach (var index in Enumerable.Range(111, 3))


{
Console.WriteLine($"Student {index} is
{students2[index].FirstName} {students2[index].LastName}");
}
}
}

Observe os dois pares de chaves em cada elemento da coleção na primeira declaração.


As chaves mais internas incluem o inicializador do objeto para o StudentName , e as
chaves mais externas incluem o inicializador do par chave/valor a ser adicionado ao
students Dictionary<TKey,TValue>. Por fim, todo o inicializador de coleção do dicionário
é colocado entre chaves. Na segunda inicialização, o lado esquerdo da atribuição é a
chave e o lado direito é o valor, usando um inicializador de objeto para StudentName .

Confira também
Inicializadores de objeto e coleçã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
Tipos aninhados (Guia de Programação
em C#)
Artigo • 07/04/2023

Um tipo definido em uma classe, um struct ou uma interface é chamado de tipo


aninhado. Por exemplo

C#

public class Container


{
class Nested
{
Nested() { }
}
}

Independentemente de o tipo externo ser uma classe, uma interface ou uma struct, os
tipos aninhados são privados por padrão, acessíveis somente por meio do tipo que
contêm. No exemplo anterior, a classe Nested é inacessível para tipos externos.

Você também pode especificar um modificador de acesso para definir a acessibilidade


de um tipo aninhado, da seguinte maneira:

Os tipos aninhados de uma classe podem ser public, protected, internal, protected
internal, private ou private protected.

No entanto, a definição de uma classe aninhada protected , protected internal ou


private protected dentro de uma classe selada gera um aviso do compilador
CS0628, "novo membro protegido declarado na classe selada".

Lembre-se também de que tornar um tipo aninhado visível externamente viola a


regra de qualidade de código CA1034 – "Tipos aninhados não devem ser visíveis".

Tipos aninhados de um struct podem ser públicos, internos ou particulares.

O exemplo a seguir torna a classe Nested pública:

C#

public class Container


{
public class Nested
{
Nested() { }
}
}

O tipo aninhado ou interno pode acessar o tipo recipiente ou externo. Para acessar o
tipo recipiente, passe-o como um argumento ao construtor do tipo aninhado. Por
exemplo:

C#

public class Container


{
public class Nested
{
private Container? parent;

public Nested()
{
}
public Nested(Container parent)
{
this.parent = parent;
}
}
}

Um tipo aninhado tem acesso a todos os membros acessíveis ao seu tipo recipiente. Ele
pode acessar membros privados e protegidos do tipo recipiente, incluindo quaisquer
membros protegidos herdados.

Na declaração anterior, o nome completo da classe Nested é Container.Nested . Este é o


nome usado para criar uma nova instância da classe aninhada, da seguinte maneira:

C#

Container.Nested nest = new Container.Nested();

Confira também
Guia de Programação em C#
Sistema de tipos do C#
Modificadores de acesso
Construtores
Regra CA1034
Classes e métodos partial (Guia de
Programação em C#)
Artigo • 14/11/2024

É possível dividir a definição de uma classe, um struct, uma interface ou um método em


dois ou mais arquivos de origem. Cada arquivo de origem contém uma seção da
definição de tipo ou método e todas as partes são combinadas quando o aplicativo é
compilado.

Classes parciais
Há várias situações em que a divisão de uma definição de classe é desejável:

Declarar uma classe em arquivos separados permite que vários programadores


trabalhem nela ao mesmo tempo.
Você pode adicionar código à classe sem precisar recriar o arquivo de origem que
inclui a origem gerada automaticamente. O Visual Studio usa essa abordagem
quando cria Windows Forms, código de wrapper de serviço Web e assim por
diante. Você pode criar código que usa essas classes sem precisar modificar o
arquivo que o Visual Studio cria.
Os geradores de origem podem gerar funcionalidade extra em uma classe.

Para dividir uma definição de classe, use o modificador de palavra-chave parcial . Na


prática, cada classe parcial é normalmente definida em um arquivo separado, facilitando
o gerenciamento e a expansão da classe ao longo do tempo.

O exemplo a seguir Employee demonstra como a classe pode ser dividida em dois
arquivos: Employee_Part1.cs e Employee_Part2.cs.

C#

// This is in Employee_Part1.cs
public partial class Employee
{
public void DoWork()
{
}
}

// This is in Employee_Part2.cs
public partial class Employee
{
public void GoToLunch()
{
}
}

//Main program demonstrating the Employee class usage


public class Program
{
public static void Main()
{
Employee emp = new Employee();
emp.DoWork();
emp.GoToLunch();
}
}

// Expected Output:
// Employee is working.
// Employee is at lunch.

A palavra-chave partial indica que outras partes da classe, struct ou interface podem
ser definidas no namespace. Todas as partes devem usar a palavra-chave partial . Todas
as partes devem estar disponíveis em tempo de compilação para formar o tipo final.
Todas as partes devem ter a mesma acessibilidade, tais como public , private e assim
por diante.

Se alguma parte for declarada como abstrata, o tipo inteiro será considerado abstrato.
Se alguma parte for declarada como lacrada, o tipo inteiro será considerado lacrado. Se
alguma parte declarar um tipo base, o tipo inteiro herda dessa classe.

Todas as partes que especificam uma classe base devem concordar, mas partes que
omitem uma classe base ainda herdam o tipo base. As partes podem especificar
diferentes interfaces base e o tipo final implementa todas as interfaces listadas por
todas as declarações parciais. Qualquer membro de classe, struct ou interface declarado
em uma definição parcial está disponível para todas as outras partes. O tipo final é a
combinação de todas as partes em tempo de compilação.

7 Observação

O modificador partial não está disponível em declarações de enumeração ou


delegados.

O exemplo a seguir mostra que os tipos aninhados podem ser parciais, mesmo que o
tipo em que estão aninhados não seja parcial.

C#
class Container
{
partial class Nested
{
void Test() { }
}

partial class Nested


{
void Test2() { }
}
}

Em tempo de compilação, atributos de definições de tipo parcial são mesclados. Por


exemplo, considere as declarações a seguir:

C#

[SerializableAttribute]
partial class Moon { }

[ObsoleteAttribute]
partial class Moon { }

Elas são equivalentes às seguintes declarações:

C#

[SerializableAttribute]
[ObsoleteAttribute]
class Moon { }

Os itens a seguir são mesclados de todas as definições de tipo parcial:

Comentários XML. No entanto, se ambas as declarações de um membro parcial


incluírem comentários, apenas os comentários do membro de implementação
serão incluídos.
interfaces
atributos de parâmetro de tipo genérico
atributos class
membros

Por exemplo, considere as declarações a seguir:

C#
partial class Earth : Planet, IRotate { }
partial class Earth : IRevolve { }

Elas são equivalentes às seguintes declarações:

C#

class Earth : Planet, IRotate, IRevolve { }

Restrições
Há várias regras a seguir quando você está trabalhando com definições parciais de
classe:

Todas as definições de tipo parcial que devem ser partes do mesmo tipo devem
ser modificadas com partial . Por exemplo, as seguintes declarações de classe
geram um erro:

C#

public partial class A { }


//public class A { } // Error, must also be marked partial

O modificador partial só pode aparecer imediatamente antes da palavra-chave


class , struct ou interface .

Tipos parciais aninhados são permitidos em definições de tipo parcial, conforme


ilustrado no exemplo a seguir:

C#

partial class ClassWithNestedClass


{
partial class NestedClass { }
}

partial class ClassWithNestedClass


{
partial class NestedClass { }
}

Todas as definições de tipo parcial que devem ser partes do mesmo tipo devem
ser definidas no mesmo assembly e no mesmo módulo (arquivo .dll ou .exe).
Definições parciais não podem abranger vários módulos.
O nome de classe e os parâmetros de tipo genérico devem corresponder em todas
as definições de tipo parcial. Tipos genéricos podem ser parciais. Cada declaração
parcial deve usar os mesmos nomes de parâmetro na mesma ordem.
As seguintes palavras-chave em uma definição de tipo parcial são opcionais, mas,
se estiverem presentes em uma definição de tipo parcial, é preciso que esteja
especificada em outra definição parcial para o mesmo tipo:
public
private
protected
interno
abstract
sealed
classe base
modificador new (partes aninhadas)
restrições genéricas

Para obter mais informações, consulte Restrições a parâmetros de tipo.

Exemplos
No exemplo a seguir, os campos e o Coords construtor da classe são declarados em
uma definição de classe parcial ( Coords_Part1.cs ) e o método é declarado PrintCoords
em outra definição de classe parcial ( Coords_Part2.cs ). Essa separação demonstra como
as classes parciais podem ser divididas em vários arquivos para facilitar a manutenção.

C#

// This is in Coords_Part1.cs
public partial class Coords
{
private int x;
private int y;

public Coords(int x, int y)


{
this.x = x;
this.y = y;
}
}

// This is in Coords_Part2.cs
public partial class Coords
{
public void PrintCoords()
{
Console.WriteLine("Coords: {0},{1}", x, y);
}
}

// Main program demonstrating the Coords class usage


class TestCoords
{
static void Main()
{
Coords myCoords = new Coords(10, 15);
myCoords.PrintCoords();

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
// Output: Coords: 10,15

O exemplo a seguir mostra que você também pode desenvolver interfaces e structs
parciais.

C#

partial interface ITest


{
void Interface_Test();
}

partial interface ITest


{
void Interface_Test2();
}

partial struct S1
{
void Struct_Test() { }
}

partial struct S1
{
void Struct_Test2() { }
}

Membros parciais
Uma classe ou struct parcial pode conter um membro parcial. Uma parte da classe
contém a assinatura do membro. Uma implementação pode ser definida na mesma
parte ou em outra parte.
Uma implementação não é necessária para um método parcial quando a assinatura
obedece às seguintes regras:

A declaração não inclui modificadores de acesso. O método tem acesso private por
padrão.
O tipo de retorno é void.
Nenhum dos parâmetros tem o modificador out.
A declaração do método não pode incluir nenhum dos seguintes modificadores:
virtual
override
sealed
new
extern

O método e todas as chamadas para o método são removidos no momento da


compilação quando não há nenhuma implementação.

Qualquer método que não esteja em conformidade com todas essas restrições,
incluindo propriedades e indexadores, deve fornecer uma implementação. Essa
implementação pode ser fornecida por um gerador de origem. Propriedades parciais não
podem ser implementadas usando propriedades implementadas automaticamente. O
compilador não pode distinguir entre uma propriedade implementada
automaticamente e a declaração de declaração de uma propriedade parcial.

A partir do C# 13, a declaração de implementação de uma propriedade parcial pode


usar propriedades com suporte de campo para definir a declaração de implementação.
Uma propriedade com suporte de campo fornece uma sintaxe concisa em que a
palavra-chave acessa field o campo de suporte sintetizado do compilador para a
propriedade. Por exemplo, você pode escrever o seguinte:

C#

// in file1.cs
public partial class PropertyBag
{
// Defining declaration
public partial int MyProperty { get; set; }
}

// In file2.cs
public partial class PropertyBag
{
// Defining declaration
public partial int MyProperty { get => field; set; }
}
Você pode usar field no get acessador ou set em ambos.

) Importante

A field palavra-chave é um recurso de visualização no C# 13. Você deve estar


usando o .NET 9 e definir seu <LangVersion> elemento como preview no arquivo
de projeto para usar a field palavra-chave contextual.

Você deve ter cuidado ao usar o recurso de field palavra-chave em uma classe
que tenha um campo chamado field . A nova field palavra-chave sombreia um
campo nomeado field no escopo de um acessador de propriedade. Você pode
alterar o nome da field variável ou usar o @ token para fazer referência ao field
identificador como @field . Você pode saber mais lendo a especificação do recurso
para a field palavra-chave.

Métodos parciais permitem que o implementador de uma parte de uma classe declare
um membro. O implementador de outra parte da classe pode definir esse membro. Há
dois cenários em que essa separação é útil: modelos que geram código clichê e
geradores de origem.

Código do modelo: o modelo reserva um nome de método e uma assinatura para


que o código gerado possa chamar o método. Esses métodos seguem as
restrições que permitem a um desenvolvedor decidir se quer implementar o
método. Se o método não for implementado, o compilador removerá a assinatura
do método e todas as chamadas para o método. As chamadas para o método,
incluindo qualquer resultado que ocorreria da avaliação de argumentos nas
chamadas, não têm efeito em tempo de execução. Portanto, qualquer código na
classe parcial pode usar livremente um método parcial, mesmo que a
implementação não seja fornecida. Nenhum erro de tempo de compilação ou em
tempo de execução resulta se o método for chamado, mas não implementado.
Geradores de origem: os geradores de origem fornecem uma implementação para
membros. O desenvolvedor humano pode adicionar a declaração de membro
(geralmente com atributos lidos pelo gerador de origem). O desenvolvedor pode
gravar um código que chama esses membros. O gerador de origem é executado
durante a compilação e fornece a implementação. Nesse cenário, as restrições para
membros parciais que nem sempre são implementadas geralmente não são
seguidas.

C#
// Definition in file1.cs
partial void OnNameChanged();

// Implementation in file2.cs
partial void OnNameChanged()
{
// method body
}

As declarações de membro parcial devem começar com a palavra-chave contextual


partial.
As assinaturas de membro parcial em ambas as partes do tipo parcial devem ser
correspondentes.
Métodos de membros podem ter modificadores static e unsafe.
Membros parciais podem ser genéricos. As restrições devem ser as mesmas na
declaração de método de definição e implementação. Os nomes de parâmetro e
de parâmetro de tipo não precisam ser os mesmos na declaração de
implementação e de definição.
Você pode criar um delegado para um método parcial definido e implementado,
mas não para um método parcial que não tenha uma implementação.

Especificação da Linguagem C#
Para obter mais informações, confira Tipos parciais e Métodos parciais na Especificação
da Linguagem C#. A especificação da linguagem é a fonte definitiva para a sintaxe e o
uso de C#. Os novos recursos para métodos parciais são definidos na especificação do
recurso.

Confira também
Classes
Tipos de estrutura
Interfaces
parcial (tipo)
Como retornar subconjuntos de
propriedades de elementos em uma
consulta (Guia de Programação em C#)
Artigo • 13/03/2024

Use um tipo anônimo em uma expressão de consulta quando essas duas condições se
aplicarem:

Você deseja retornar apenas algumas das propriedades de cada elemento de


origem.

Você não precisa armazenar os resultados da consulta fora do escopo do método


em que a consulta é executada.

Se você deseja retornar apenas uma propriedade ou campo de cada elemento de


origem, use somente o operador de ponto na cláusula select . Por exemplo, para
retornar somente a ID de cada student , escreva a cláusula select da seguinte maneira:

C#

select student.ID;

Exemplo
O exemplo a seguir mostra como usar um tipo anônimo para retornar apenas um
subconjunto das propriedades de cada elemento de origem que corresponda à
condição especificada.

C#

private static void QueryByScore()


{
// Create the query. var is required because
// the query produces a sequence of anonymous types.
var queryHighScores =
from student in students
where student.ExamScores[0] > 95
select new { student.FirstName, student.LastName };

// Execute the query.


foreach (var obj in queryHighScores)
{
// The anonymous type's properties were not named. Therefore
// they have the same names as the Student properties.
Console.WriteLine(obj.FirstName + ", " + obj.LastName);
}
}
/* Output:
Adams, Terry
Fakhouri, Fadi
Garcia, Cesar
Omelchenko, Svetlana
Zabokritski, Eugene
*/

Observe que o tipo anônimo usa nomes do elemento de origem para suas
propriedades, se nenhum nome for especificado. Para fornecer novos nomes para as
propriedades no tipo anônimo, escreva a instrução select da seguinte maneira:

C#

select new { First = student.FirstName, Last = student.LastName };

Se você tentar fazer isso no exemplo anterior, a instrução Console.WriteLine também


deve ser alterada:

C#

Console.WriteLine(student.First + " " + student.Last);

Compilando o código
Para executar esse código, copie e cole a classe em um aplicativo de console em C#
com uma diretiva using para System.Linq.

Confira também
Tipos anônimos
LINQ em 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
 Abrir um problema de
revisar problemas e solicitações
de pull. Para obter mais documentação
informações, confira o nosso
guia para colaboradores.  Fornecer comentários sobre o
produto
Implementação de interface explícita
(Guia de Programação em C#)
Artigo • 07/04/2023

Caso uma classe implemente duas interfaces que contêm um membro com a mesma
assinatura, logo, implementar esse membro na classe fará com que as duas interfaces
usem tal membro como sua implementação. No exemplo a seguir, todas as chamadas
para Paint invocam o mesmo método. Esse primeiro exemplo define os tipos:

C#

public interface IControl


{
void Paint();
}
public interface ISurface
{
void Paint();
}
public class SampleClass : IControl, ISurface
{
// Both ISurface.Paint and IControl.Paint call this method.
public void Paint()
{
Console.WriteLine("Paint method in SampleClass");
}
}

O exemplo a seguir chama os métodos:

C#

SampleClass sample = new SampleClass();


IControl control = sample;
ISurface surface = sample;

// The following lines all call the same method.


sample.Paint();
control.Paint();
surface.Paint();

// Output:
// Paint method in SampleClass
// Paint method in SampleClass
// Paint method in SampleClass
Mas talvez você não queira que a mesma implementação seja chamada para ambas as
interfaces. Para chamar uma implementação diferente dependendo de qual interface
está em uso, você pode implementar um membro de interface explicitamente. Uma
implementação de interface explícita é um membro de classe que só é chamado por
meio da interface especificada. Nomeia o membro de classe prefixando-o com o nome
da interface e um ponto. Por exemplo:

C#

public class SampleClass : IControl, ISurface


{
void IControl.Paint()
{
System.Console.WriteLine("IControl.Paint");
}
void ISurface.Paint()
{
System.Console.WriteLine("ISurface.Paint");
}
}

O membro da classe IControl.Paint está disponível somente por meio da interface


IControl e ISurface.Paint está disponível apenas por meio do ISurface . Ambas as

implementações de método são separadas e nenhuma delas está diretamente


disponível na classe. Por exemplo:

C#

SampleClass sample = new SampleClass();


IControl control = sample;
ISurface surface = sample;

// The following lines all call the same method.


//sample.Paint(); // Compiler error.
control.Paint(); // Calls IControl.Paint on SampleClass.
surface.Paint(); // Calls ISurface.Paint on SampleClass.

// Output:
// IControl.Paint
// ISurface.Paint

A implementação explícita também é utilizada para resolver casos em que duas


interfaces declaram membros diferentes com o mesmo nome, como uma propriedade e
um método. Para implementar as duas interfaces, é necessário que uma classe use a
implementação explícita para a propriedade P , o método P ou ambos, a fim de evitar
um erro do compilador. Por exemplo:
C#

interface ILeft
{
int P { get;}
}
interface IRight
{
int P();
}

class Middle : ILeft, IRight


{
public int P() { return 0; }
int ILeft.P { get { return 0; } }
}

Uma implementação de interface explícita não tem um modificador de acesso, pois não
está acessível como membro do tipo em que está definido. Em vez disso, ele só pode
ser acessado quando chamado por meio de uma instância da interface. Se você
especificar um modificador de acesso para uma implementação de interface explícita,
receberá o erro do compilador CS0106. Para obter mais informações, confira interface
(referência C#).

Você pode definir uma implementação para membros declarados em uma interface. Se
uma classe herdar uma implementação de método de uma interface, esse método só
será acessível por meio de uma referência do tipo de interface. O membro herdado não
aparece como parte da interface pública. O exemplo a seguir define uma
implementação padrão para um método de interface:

C#

public interface IControl


{
void Paint() => Console.WriteLine("Default Paint method");
}
public class SampleClass : IControl
{
// Paint() is inherited from IControl.
}

O exemplo a seguir invoca a implementação padrão:

C#

var sample = new SampleClass();


//sample.Paint();// "Paint" isn't accessible.
var control = sample as IControl;
control.Paint();

Qualquer classe que implementa a interface IControl pode substituir o método padrão
Paint , seja como um método público, seja como uma implementação de interface
explícita.

Confira também
Guia de Programação em C#
Programação orientada a objetos
Interfaces
Herança
Como implementar os membros da
interface explicitamente (Guia de
Programação em C#)
Artigo • 13/03/2024

Este exemplo declara uma interface, IDimensions e uma classe, Box , que implementa
explicitamente os membros de interface GetLength e GetWidth . Os membros são
acessados por meio da instância dimensions da interface.

Exemplo
C#

interface IDimensions
{
float GetLength();
float GetWidth();
}

class Box : IDimensions


{
float lengthInches;
float widthInches;

Box(float length, float width)


{
lengthInches = length;
widthInches = width;
}
// Explicit interface member implementation:
float IDimensions.GetLength()
{
return lengthInches;
}
// Explicit interface member implementation:
float IDimensions.GetWidth()
{
return widthInches;
}

static void Main()


{
// Declare a class instance box1:
Box box1 = new Box(30.0f, 20.0f);

// Declare an interface instance dimensions:


IDimensions dimensions = box1;

// The following commented lines would produce compilation


// errors because they try to access an explicitly implemented
// interface member from a class instance:
//System.Console.WriteLine("Length: {0}", box1.GetLength());
//System.Console.WriteLine("Width: {0}", box1.GetWidth());

// Print out the dimensions of the box by calling the methods


// from an instance of the interface:
System.Console.WriteLine("Length: {0}", dimensions.GetLength());
System.Console.WriteLine("Width: {0}", dimensions.GetWidth());
}
}
/* Output:
Length: 30
Width: 20
*/

Programação robusta
Observe que as seguintes linhas, no método Main , foram comentadas, pois
produziriam erros de compilação. Um membro de interface implementado
explicitamente não pode ser acessado de uma instância de classe:

C#

//System.Console.WriteLine("Length: {0}", box1.GetLength());


//System.Console.WriteLine("Width: {0}", box1.GetWidth());

Observe também que as linhas a seguir, no método Main , imprimem com êxito as
dimensões da caixa, pois os métodos estão sendo chamados de uma instância da
interface:

C#

System.Console.WriteLine("Length: {0}", dimensions.GetLength());


System.Console.WriteLine("Width: {0}", dimensions.GetWidth());

Confira também
Programação orientada a objetos
Interfaces
Como implementar membros de duas interfaces explicitamente
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
Como implementar explicitamente
membros de duas interfaces (Guia de
Programação em C#)
Artigo • 13/03/2024

A implementação explícita da interface também permite ao programador implementar


duas interfaces que têm os mesmos nomes de membro e implementar separadamente
cada membro de interface. Este exemplo exibe as dimensões de uma caixa em unidades
inglesas e no sistema métrico. A Caixa classe implementa duas interfaces,
IEnglishDimensions e IMetricDimensions, que representam os diferentes sistemas de
medida. As duas interfaces têm nomes de membro idênticos, Comprimento e Largura.

Exemplo
C#

// Declare the English units interface:


interface IEnglishDimensions
{
float Length();
float Width();
}

// Declare the metric units interface:


interface IMetricDimensions
{
float Length();
float Width();
}

// Declare the Box class that implements the two interfaces:


// IEnglishDimensions and IMetricDimensions:
class Box : IEnglishDimensions, IMetricDimensions
{
float lengthInches;
float widthInches;

public Box(float lengthInches, float widthInches)


{
this.lengthInches = lengthInches;
this.widthInches = widthInches;
}

// Explicitly implement the members of IEnglishDimensions:


float IEnglishDimensions.Length() => lengthInches;
float IEnglishDimensions.Width() => widthInches;

// Explicitly implement the members of IMetricDimensions:


float IMetricDimensions.Length() => lengthInches * 2.54f;

float IMetricDimensions.Width() => widthInches * 2.54f;

static void Main()


{
// Declare a class instance box1:
Box box1 = new Box(30.0f, 20.0f);

// Declare an instance of the English units interface:


IEnglishDimensions eDimensions = box1;

// Declare an instance of the metric units interface:


IMetricDimensions mDimensions = box1;

// Print dimensions in English units:


System.Console.WriteLine("Length(in): {0}", eDimensions.Length());
System.Console.WriteLine("Width (in): {0}", eDimensions.Width());

// Print dimensions in metric units:


System.Console.WriteLine("Length(cm): {0}", mDimensions.Length());
System.Console.WriteLine("Width (cm): {0}", mDimensions.Width());
}
}
/* Output:
Length(in): 30
Width (in): 20
Length(cm): 76.2
Width (cm): 50.8
*/

Programação robusta
Caso deseje que as medidas padrão estejam unidades inglesas, implemente os métodos
Comprimento e Largura normalmente e implemente explicitamente os métodos
Comprimento e Largura da interface IMetricDimensions:

C#

// Normal implementation:
public float Length() => lengthInches;
public float Width() => widthInches;

// Explicit implementation:
float IMetricDimensions.Length() => lengthInches * 2.54f;
float IMetricDimensions.Width() => widthInches * 2.54f;
Nesse caso, é possível acessar as unidades inglesas da instância de classe e acessar as
unidades métricas da instância da interface:

C#

public static void Test()


{
Box box1 = new Box(30.0f, 20.0f);
IMetricDimensions mDimensions = box1;

System.Console.WriteLine("Length(in): {0}", box1.Length());


System.Console.WriteLine("Width (in): {0}", box1.Width());
System.Console.WriteLine("Length(cm): {0}", mDimensions.Length());
System.Console.WriteLine("Width (cm): {0}", mDimensions.Width());
}

Confira também
Programação orientada a objetos
Interfaces
Como implementar membros de interface explicitamente

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
Delegados (Guia de Programação em
C#)
Artigo • 11/04/2024

Um delegado é um tipo que representa referências aos métodos com lista de


parâmetros e tipo de retorno específicos. Ao instanciar um delegado, você pode
associar sua instância a qualquer método com assinatura e tipo de retorno compatíveis.
Você pode invocar (ou chamar) o método através da instância de delegado.

Delegados são usados para passar métodos como argumentos a outros métodos. Os
manipuladores de eventos nada mais são do que métodos chamados por meio de
delegados. Ao criar um método personalizado, uma classe como um controle do
Windows poderá chamá-lo quando um determinado evento ocorrer. O seguinte
exemplo mostra uma declaração de delegado:

C#

public delegate int PerformCalculation(int x, int y);

Qualquer método de qualquer classe ou struct acessível que corresponda ao tipo


delegado pode ser atribuído ao delegado. O método pode ser estático ou de instância.
Essa flexibilidade significa que você pode alterar programaticamente as chamadas de
método ou conectar um novo código às classes existentes.

7 Observação

No contexto da sobrecarga de método, a assinatura de um método não inclui o


valor retornado. No entanto, no contexto de delegados, a assinatura inclui o valor
retornado. Em outras palavras, um método deve ter o mesmo tipo de retorno que
o delegado.

Essa capacidade de se referir a um método como um parâmetro torna delegados ideais


para definir métodos de retorno de chamada. Você pode escrever um método que
compara dois objetos em seu aplicativo. Esse método pode ser usado em um delegado
para um algoritmo de classificação. Como o código de comparação é separado da
biblioteca, o método de classificação pode ser mais geral.

Ponteiros de função foram adicionados ao C# 9 para cenários semelhantes em que você


precisa de mais controle sobre a convenção de chamada. O código associado a um
delegado é invocado usando um método virtual adicionado a um tipo delegado.
Usando ponteiros de função, você pode especificar convenções diferentes.

Visão geral de delegados


Os delegados possuem as seguintes propriedades:

Representantes são semelhantes a ponteiros de função do C++, mas delegados


são totalmente orientados a objeto e, ao contrário dos ponteiros de C++ para
funções de membro, os delegados encapsulam uma instância do objeto e um
método.
Os delegados permitem que métodos sejam passados como parâmetros.
Os delegados podem ser usados para definir métodos de retorno de chamada.
Os delegados podem ser encadeados juntos; por exemplo, vários métodos podem
ser chamados um único evento.
Os métodos não precisam corresponder exatamente ao tipo delegado. Para obter
mais informações, confira Usando a variação delegados.
Expressões lambda são uma forma mais concisa de escrever blocos de código
embutidos. As expressões lambda (em determinados contextos) são compiladas
para tipos delegados. Para saber mais sobre expressões lambda, confira o artigo
sobre expressões lambda.

Nesta seção
Usando delegados
Quando usar delegados em vez de interfaces (Guia de Programação em C#)
Delegados com Métodos Nomeados vs. Métodos anônimos
Usando variação em delegados
Como combinar delegados (delegados multicast)
Como declarar e usar um delegado e criar uma instância dele

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

Capítulos do Livro em Destaque


Expressões lambda, eventos e delegados em C# 3.0 Cookbook, Third Edition: More
than 250 solutions for C# 3.0 programmers
Delegados e eventos em Aprendendo sobre C# 3.0: conceitos básicos do C# 3.0

Confira também
Delegate
Guia de Programação em C#
Eventos

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

Fornecer comentários sobre o produto


Usando delegados (Guia de
Programação em C#)
Artigo • 31/07/2023

Um delegado é um tipo que encapsula com segurança um método, semelhante a um


ponteiro de função em C e C++. No entanto, ao contrário dos ponteiros de função de C,
delegados são orientados a objeto, fortemente tipados e seguros. O tipo de um
delegado é definido pelo nome do delegado. O exemplo a seguir declara um delegado
chamado Callback que pode encapsular um método que usa uma cadeia de caracteres
como um argumento e retorna nulo:

C#

public delegate void Callback(string message);

Normalmente, um objeto delegado é construído fornecendo-se o nome do método que


o delegado encapsulará ou com uma expressão lambda. Quando um delegado é
instanciado, uma chamada de método feita ao delegado será passada pelo delegado
para esse método. Os parâmetros passados para o delegado pelo chamador são
passados para o método e o valor de retorno, se houver, do método é retornado ao
chamador pelo delegado. Isso é conhecido como invocar o delegado. Um delegado
instanciado pode ser invocado como se fosse o método encapsulado em si. Por
exemplo:

C#

// Create a method for a delegate.


public static void DelegateMethod(string message)
{
Console.WriteLine(message);
}

C#

// Instantiate the delegate.


Callback handler = DelegateMethod;

// Call the delegate.


handler("Hello World");

Tipos de delegado são derivados da classe Delegate no .NET. Tipos de delegado são
lacrados – não podem ser derivados de – e não é possível derivar classes personalizadas
de Delegate. Como o delegado instanciado é um objeto, ele pode ser passado como um
argumento ou atribuído a uma propriedade. Isso permite que um método aceite um
delegado como um parâmetro e chame o delegado posteriormente. Isso é conhecido
como um retorno de chamada assíncrono e é um método comum de notificação de um
chamador quando um processo longo for concluído. Quando um delegado é usado
dessa maneira, o código que usa o delegado não precisa de conhecimento algum da
implementação do método que está sendo usado. A funcionalidade é semelhante ao
encapsulamento que as interfaces fornecem.

Outro uso comum de chamadas de retorno é definir um método de comparação


personalizada e passar esse delegado para um método de classificação. Ele permite que
o código do chamador se torne parte do algoritmo de classificação. O método de
exemplo a seguir usa o tipo Del como um parâmetro:

C#

public static void MethodWithCallback(int param1, int param2, Callback


callback)
{
callback("The number is: " + (param1 + param2).ToString());
}

Em seguida, você pode passar o delegado criado acima para esse método:

C#

MethodWithCallback(1, 2, handler);

e receber a seguinte saída para o console:

Console

The number is: 3

Usando o delegado como uma abstração, MethodWithCallback não precisa chamar o


console diretamente — ele não precisa ser criado com um console em mente. O que
MethodWithCallback faz é simplesmente preparar uma cadeia de caracteres e passá-la

para outro método. Isso é especialmente poderoso, uma vez que um método delegado
pode usar qualquer número de parâmetros.

Quando um delegado é construído para encapsular um método de instância, o


delegado faz referência à instância e ao método. Um delegado não tem conhecimento
do tipo de instância além do método que ele encapsula, de modo que um delegado
pode se referir a qualquer tipo de objeto desde que haja um método nesse objeto que
corresponda à assinatura do delegado. Quando um delegado é construído para
encapsular um método estático, ele só faz referência ao método. Considere as seguintes
declarações:

C#

public class MethodClass


{
public void Method1(string message) { }
public void Method2(string message) { }
}

Além do DelegateMethod estático mostrado anteriormente, agora temos três métodos


que podem ser encapsulados por uma instância Del .

Um delegado pode chamar mais de um método quando invocado. Isso é chamado de


multicast. Para adicionar um método extra à lista de métodos do delegado — a lista de
invocação — basta adicionar dois delegados usando os operadores de adição ou de
atribuição de adição ('+' ou '+ ='). Por exemplo:

C#

var obj = new MethodClass();


Callback d1 = obj.Method1;
Callback d2 = obj.Method2;
Callback d3 = DelegateMethod;

//Both types of assignment are valid.


Callback allMethodsDelegate = d1 + d2;
allMethodsDelegate += d3;

Nesse ponto, allMethodsDelegate contém três métodos em sua lista de invocação —


Method1 , Method2 e DelegateMethod . Os três delegados originais, d1 , d2 e d3 ,

permanecem inalterados. Quando allMethodsDelegate é invocado, os três métodos são


chamados na ordem. Se o delegado usar parâmetros de referência, a referência será
passada em sequência para cada um dos três métodos por vez, e quaisquer alterações
em um método serão visíveis no próximo método. Quando algum dos métodos gerar
uma exceção que não foi detectada dentro do método, essa exceção será passada ao
chamador do delegado e nenhum método subsequente na lista de invocação será
chamado. Se o delegado tiver um valor de retorno e/ou parâmetros de saída, ele
retornará o valor de retorno e os parâmetros do último método invocado. Para remover
um método da lista de invocação, use os operadores de atribuição de subtração ou
subtração ( - ou -= ). Por exemplo:
C#

//remove Method1
allMethodsDelegate -= d1;

// copy AllMethodsDelegate while removing d2


Callback oneMethodDelegate = allMethodsDelegate - d2;

Como os tipos de delegados são derivados de System.Delegate , os métodos e as


propriedades definidos por essa classe podem ser chamados no delegado. Por exemplo,
para localizar o número de métodos na lista de invocação do delegado, é possível
escrever:

C#

int invocationCount = d1.GetInvocationList().GetLength(0);

Delegados com mais de um método em sua lista de invocação derivam de


MulticastDelegate, que é uma subclasse de System.Delegate . O código acima funciona
em ambos os casos, pois as classes oferecem suporte à GetInvocationList .

Delegados multicast são amplamente usados na manipulação de eventos. Objetos de


origem do evento enviam notificações de eventos aos objetos de destinatário que se
registraram para receber esse evento. Para se registrar para um evento, o destinatário
cria um método projetado para lidar com o evento, em seguida, cria um delegado para
esse método e passa o delegado para a origem do evento. A origem chama o delegado
quando o evento ocorre. O delegado chama então o método de manipulação de
eventos no destinatário, fornecendo os dados do evento. O tipo de delegado de um
determinado evento é definido pela origem do evento. Para saber mais, consulte
Eventos.

A comparação de delegados de dois tipos diferentes atribuídos no tempo de


compilação resultará em um erro de compilação. Se as instâncias de delegado forem
estaticamente do tipo System.Delegate , então a comparação será permitida, mas
retornará false no tempo de execução. Por exemplo:

C#

delegate void Callback1();


delegate void Callback2();

static void method(Callback1 d, Callback2 e, System.Delegate f)


{
// Compile-time error.
//Console.WriteLine(d == e);
// OK at compile-time. False if the run-time type of f
// is not the same as that of d.
Console.WriteLine(d == f);
}

Confira também
Guia de Programação em C#
Representantes
Usando variação em delegados
Variação em delegações
Usando Variação para Delegações Genéricas Func e Action
Eventos
Delegados com métodos nomeados
versus anônimos (Guia de Programação
em C#)
Artigo • 13/03/2024

Um delegado pode ser associado a um método nomeado. Ao instanciar um delegado


usando um método nomeado, o método é passado como um parâmetro, por exemplo:

C#

// Declare a delegate.
delegate void WorkCallback(int x);

// Define a named method.


void DoWork(int k) { /* ... */ }

// Instantiate the delegate using the method as a parameter.


WorkCallback d = obj.DoWork;

Isso é chamado usando um método nomeado. Os delegados construídos com um


método nomeado podem encapsular um método estático ou um método de instância.
Métodos nomeados são a única maneira de instanciar um delegado nas versões
anteriores do C#. No entanto, em uma situação em que a criação de um novo método
for uma sobrecarga indesejada, o C# permite instanciar um delegado e especificar
imediatamente um bloco de código que esse delegado processará quando for
chamado. O bloco pode conter uma expressão lambda ou um método anônimo.

O método passado como parâmetro delegado deve ter a mesma assinatura da


declaração delegada. Uma instância de delegado pode encapsular o método estático ou
de instância.

7 Observação

Embora o delegado possa usar um parâmetro out, não é recomendável utilizá-lo


com delegados de evento multicast, pois não é possível saber qual delegado será
chamado.

Do C# 10 em diante, os grupos de métodos com uma só sobrecarga têm um tipo


natural. Isso significa que o compilador pode inferir o tipo de retorno e os tipos de
parâmetro para o tipo de delegado:
C#

var read = Console.Read; // Just one overload; Func<int> inferred


var write = Console.Write; // ERROR: Multiple overloads, can't choose

Exemplos
Este é um exemplo simples de declaração usando um delegado. Observe que tanto o
delegado, Del e o método associado, MultiplyNumbers , têm a mesma assinatura

C#

// Declare a delegate
delegate void MultiplyCallback(int i, double j);

class MathClass
{
static void Main()
{
MathClass m = new MathClass();

// Delegate instantiation using "MultiplyNumbers"


MultiplyCallback d = m.MultiplyNumbers;

// Invoke the delegate object.


Console.WriteLine("Invoking the delegate using 'MultiplyNumbers':");
for (int i = 1; i <= 5; i++)
{
d(i, 2);
}

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}

// Declare the associated method.


void MultiplyNumbers(int m, double n)
{
Console.Write(m * n + " ");
}
}
/* Output:
Invoking the delegate using 'MultiplyNumbers':
2 4 6 8 10
*/

No exemplo a seguir, um delegado é mapeado para métodos estáticos e de instância e


retorna informações específicas sobre cada um.
C#

// Declare a delegate
delegate void Callback();

class SampleClass
{
public void InstanceMethod()
{
Console.WriteLine("A message from the instance method.");
}

static public void StaticMethod()


{
Console.WriteLine("A message from the static method.");
}
}

class TestSampleClass
{
static void Main()
{
var sc = new SampleClass();

// Map the delegate to the instance method:


Callback d = sc.InstanceMethod;
d();

// Map to the static method:


d = SampleClass.StaticMethod;
d();
}
}
/* Output:
A message from the instance method.
A message from the static method.
*/

Confira também
Representantes
Como combinar delegados (delegados multicast)
Eventos

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
Como combinar delegados (delegados
multicast) (Guia de Programação em C#)
Artigo • 13/03/2024

Este exemplo demonstra como criar delegados multicast. Uma propriedade útil de
objetos delegados é que vários objetos podem ser atribuídos a uma instância delegada
usando o operador + . O delegado multicast contém uma lista dos delegados atribuídos.
Quando o delegado multicast é chamado, ele invoca os delegados da lista, em ordem.
Apenas os delegados do mesmo tipo podem ser combinados.

O operador - pode ser usado para remover um delegado de componente de um


delegado multicast.

Exemplo
C#

using System;

// Define a custom delegate that has a string parameter and returns void.
delegate void CustomCallback(string s);

class TestClass
{
// Define two methods that have the same signature as CustomCallback.
static void Hello(string s)
{
Console.WriteLine($" Hello, {s}!");
}

static void Goodbye(string s)


{
Console.WriteLine($" Goodbye, {s}!");
}

static void Main()


{
// Declare instances of the custom delegate.
CustomCallback hiDel, byeDel, multiDel, multiMinusHiDel;

// In this example, you can omit the custom delegate if you


// want to and use Action<string> instead.
//Action<string> hiDel, byeDel, multiDel, multiMinusHiDel;

// Initialize the delegate object hiDel that references the


// method Hello.
hiDel = Hello;

// Initialize the delegate object byeDel that references the


// method Goodbye.
byeDel = Goodbye;

// The two delegates, hiDel and byeDel, are combined to


// form multiDel.
multiDel = hiDel + byeDel;

// Remove hiDel from the multicast delegate, leaving byeDel,


// which calls only the method Goodbye.
multiMinusHiDel = multiDel - hiDel;

Console.WriteLine("Invoking delegate hiDel:");


hiDel("A");
Console.WriteLine("Invoking delegate byeDel:");
byeDel("B");
Console.WriteLine("Invoking delegate multiDel:");
multiDel("C");
Console.WriteLine("Invoking delegate multiMinusHiDel:");
multiMinusHiDel("D");
}
}
/* Output:
Invoking delegate hiDel:
Hello, A!
Invoking delegate byeDel:
Goodbye, B!
Invoking delegate multiDel:
Hello, C!
Goodbye, C!
Invoking delegate multiMinusHiDel:
Goodbye, D!
*/

Confira também
MulticastDelegate
Eventos

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
Como declarar, instanciar e usar um
delegado (Guia de Programação em C#)
Artigo • 13/03/2024

Você pode declarar delegados usando qualquer um dos seguintes métodos:

Declarar um tipo de delegado e declarar um método com uma assinatura


correspondente:

C#

// Declare a delegate.
delegate void NotifyCallback(string str);

// Declare a method with the same signature as the delegate.


static void Notify(string name)
{
Console.WriteLine($"Notification received for: {name}");
}

C#

// Create an instance of the delegate.


NotifyCallback del1 = new NotifyCallback(Notify);

Atribuir um grupo de métodos a um tipo de delegado:

C#

// C# 2.0 provides a simpler way to declare an instance of NotifyCallback.


NotifyCallback del2 = Notify;

Declarar um método anônimo:

C#

// Instantiate NotifyCallback by using an anonymous method.


NotifyCallback del3 = delegate(string name)
{ Console.WriteLine($"Notification received for: {name}"); };

Usar uma expressão lambda:

C#
// Instantiate NotifyCallback by using a lambda expression.
NotifyCallback del4 = name => { Console.WriteLine($"Notification received
for: {name}"); };

Para obter mais informações, consulte Expressões Lambda.

O exemplo a seguir ilustra a declaração, instanciação e o uso de um delegado. A classe


BookDB encapsula um banco de dados de uma livraria que mantém um banco de dados

de livros. Ela expõe um método, ProcessPaperbackBooks , que localiza todos os livros de


bolso no banco de dados e chama um delegado para cada um. O tipo delegate usado
tem o nome ProcessBookCallback . A classe Test usa essa classe para imprimir os títulos
e o preço médio dos livros de bolso.

O uso de delegados promove uma boa separação de funcionalidade entre o banco de


dados da livraria e o código de cliente. O código de cliente não tem conhecimento de
como os livros são armazenados ou como o código da livraria localiza os livros de bolso.
O código da livraria não tem conhecimento do processamento executado nos livros de
bolso após a localização.

Exemplo
C#

// A set of classes for handling a bookstore:


namespace Bookstore
{
using System.Collections;

// Describes a book in the book list:


public struct Book
{
public string Title; // Title of the book.
public string Author; // Author of the book.
public decimal Price; // Price of the book.
public bool Paperback; // Is it paperback?

public Book(string title, string author, decimal price, bool


paperBack)
{
Title = title;
Author = author;
Price = price;
Paperback = paperBack;
}
}

// Declare a delegate type for processing a book:


public delegate void ProcessBookCallback(Book book);

// Maintains a book database.


public class BookDB
{
// List of all books in the database:
ArrayList list = new ArrayList();

// Add a book to the database:


public void AddBook(string title, string author, decimal price, bool
paperBack)
{
list.Add(new Book(title, author, price, paperBack));
}

// Call a passed-in delegate on each paperback book to process it:


public void ProcessPaperbackBooks(ProcessBookCallback processBook)
{
foreach (Book b in list)
{
if (b.Paperback)
// Calling the delegate:
processBook(b);
}
}
}
}

// Using the Bookstore classes:


namespace BookTestClient
{
using Bookstore;

// Class to total and average prices of books:


class PriceTotaller
{
int countBooks = 0;
decimal priceBooks = 0.0m;

internal void AddBookToTotal(Book book)


{
countBooks += 1;
priceBooks += book.Price;
}

internal decimal AveragePrice()


{
return priceBooks / countBooks;
}
}

// Class to test the book database:


class Test
{
// Print the title of the book.
static void PrintTitle(Book b)
{
Console.WriteLine($" {b.Title}");
}

// Execution starts here.


static void Main()
{
BookDB bookDB = new BookDB();

// Initialize the database with some books:


AddBooks(bookDB);

// Print all the titles of paperbacks:


Console.WriteLine("Paperback Book Titles:");

// Create a new delegate object associated with the static


// method Test.PrintTitle:
bookDB.ProcessPaperbackBooks(PrintTitle);

// Get the average price of a paperback by using


// a PriceTotaller object:
PriceTotaller totaller = new PriceTotaller();

// Create a new delegate object associated with the nonstatic


// method AddBookToTotal on the object totaller:
bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);

Console.WriteLine("Average Paperback Book Price: ${0:#.##}",


totaller.AveragePrice());
}

// Initialize the book database with some test books:


static void AddBooks(BookDB bookDB)
{
bookDB.AddBook("The C Programming Language", "Brian W. Kernighan
and Dennis M. Ritchie", 19.95m, true);
bookDB.AddBook("The Unicode Standard 2.0", "The Unicode
Consortium", 39.95m, true);
bookDB.AddBook("The MS-DOS Encyclopedia", "Ray Duncan", 129.95m,
false);
bookDB.AddBook("Dogbert's Clues for the Clueless", "Scott
Adams", 12.00m, true);
}
}
}
/* Output:
Paperback Book Titles:
The C Programming Language
The Unicode Standard 2.0
Dogbert's Clues for the Clueless
Average Paperback Book Price: $23.97
*/
Programação robusta
Declarando um delegado.

A instrução a seguir declara um novo tipo de delegado.

C#

public delegate void ProcessBookCallback(Book book);

Cada tipo de delegado descreve o número e os tipos dos argumentos e o tipo do


valor retornado dos métodos que pode encapsular. Sempre que um novo conjunto
de tipos de argumento ou tipo de valor retornado for necessário, um novo tipo de
delegado deverá ser declarado.

Instanciando um delegado.

Após a declaração do tipo de delegado, um objeto delegado deve ser criado e


associado a um método específico. No exemplo anterior, faça isso passando o
método PrintTitle para o método ProcessPaperbackBooks , como no exemplo a
seguir:

C#

bookDB.ProcessPaperbackBooks(PrintTitle);

Isso cria um novo objeto delegado associado ao método estático Test.PrintTitle .


Da mesma forma, o método não estático AddBookToTotal no objeto totaller é
passado como no exemplo a seguir:

C#

bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);

Em ambos os casos, um novo objeto delegado é passado para o método


ProcessPaperbackBooks .

Após a criação de um delegado, o método ao qual ele está associado nunca se


altera; objetos delegados são imutáveis.

Chamando um delegado.

Normalmente, o objeto delegado, após sua criação, é passado para outro código
que chamará o delegado. Um objeto delegado é chamado usando seu nome
seguido dos argumentos entre parênteses a serem passados para o delegado. A
seguir, veja um exemplo de uma chamada de delegado:

C#

processBook(b);

Um delegado pode ser chamado de forma síncrona, como neste exemplo ou de


forma assíncrona, usando os métodos BeginInvoke e EndInvoke .

Confira também
Eventos
Representantes

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
Cadeias de caracteres e literais de
cadeia de caracteres
Artigo • 11/04/2024

Uma cadeia de caracteres é um objeto do tipo String cujo valor é texto. Internamente, o
texto é armazenado como uma coleção sequencial somente leitura de objetos Char.
Não há um caractere de finalização null ao fim de uma cadeia em C#. Portanto, uma
cadeia de caracteres em C# pode ter qualquer número de caracteres nulos inseridos
('\0'). A propriedade Char de uma cadeia de caracteres representa o número de objetos
Length que ela contém e não o número de caracteres Unicode. Para acessar os pontos
de código Unicode individuais em uma cadeia de caracteres, use o objeto StringInfo.

Cadeia de caracteres versus System.String


Em C#, a palavra-chave string é um alias para String. Portanto, String e string são
equivalentes, independentemente de ser recomendável usar o alias string fornecido,
pois ele funciona mesmo sem using System; . A classe String fornece vários métodos
para criar, manipular e comparar cadeias de caracteres com segurança. Além disso, a
linguagem C# sobrecarrega alguns operadores para simplificar operações comuns de
cadeia de caracteres. Para saber mais sobre a palavra-chave, confira cadeia de
caracteres. Para obter mais informações sobre o tipo e seus métodos, consulte String.

Declaração e inicialização de cadeias de


caracteres
Você pode declarar e inicializar cadeias de caracteres de várias maneiras, conforme
mostrado no seguinte exemplo:

C#

// Declare without initializing.


string message1;

// Initialize to null.
string message2 = null;

// Initialize as an empty string.


// Use the Empty constant instead of the literal "".
string message3 = System.String.Empty;

// Initialize with a regular string literal.


string oldPath = "c:\\Program Files\\Microsoft Visual Studio 8.0";

// Initialize with a verbatim string literal.


string newPath = @"c:\Program Files\Microsoft Visual Studio 9.0";

// Use System.String if you prefer.


System.String greeting = "Hello World!";

// In local variables (i.e. within a method body)


// you can use implicit typing.
var temp = "I'm still a strongly-typed System.String!";

// Use a const string to prevent 'message4' from


// being used to store another string value.
const string message4 = "You can't get rid of me!";

// Use the String constructor only when creating


// a string from a char*, char[], or sbyte*. See
// System.String documentation for details.
char[] letters = { 'A', 'B', 'C' };
string alphabet = new string(letters);

Você não usa o operador new para criar um objeto de cadeia de caracteres, exceto ao
inicializar a cadeia de caracteres com uma matriz de caracteres.

Inicialize uma cadeia de caracteres com o valor constante Empty para criar um novo
objeto String cuja cadeia de caracteres tem comprimento zero. A representação de
cadeia de caracteres literal de uma cadeia de caracteres de comprimento zero é "". Ao
inicializar cadeias de caracteres com o valor Empty em vez de nulo, você poderá reduzir
as chances de uma NullReferenceException ocorrer. Use o método estático
IsNullOrEmpty(String) para verificar o valor de uma cadeia de caracteres antes de tentar
acessá-la.

Imutabilidade das cadeias de caracteres


Objetos de cadeia de caracteres são imutáveis: não pode ser alterados após serem
criados. Todos os métodos String e operadores C# que aparecem para modificar uma
cadeia de caracteres retornam, na verdade, os resultados em um novo objeto de cadeia
de caracteres. No exemplo a seguir, quando o conteúdo de s1 e s2 é concatenado para
formar uma única cadeia de caracteres, as duas cadeias de caracteres originais são
modificadas. O operador += cria uma nova cadeia de caracteres que tem o conteúdo
combinado. Esse novo objeto é atribuído à variável s1 , e o objeto original que foi
atribuído a s1 é liberado para coleta de lixo, pois nenhuma outra variável contém uma
referência a ele.

C#
string s1 = "A string is more ";
string s2 = "than the sum of its chars.";

// Concatenate s1 and s2. This actually creates a new


// string object and stores it in s1, releasing the
// reference to the original object.
s1 += s2;

System.Console.WriteLine(s1);
// Output: A string is more than the sum of its chars.

Como uma cadeia de caracteres de "modificação" na verdade é uma nova criação de


cadeia de caracteres, você deve ter cuidado ao criar referências em cadeias de
caracteres. Se você criar uma referência a uma cadeia de caracteres e "modificar" a
cadeia de caracteres original, a referência continuará apontar para o objeto original em
vez do novo objeto que foi criado quando a cadeia de caracteres foi modificada. O
código a seguir ilustra esse comportamento:

C#

string str1 = "Hello ";


string str2 = str1;
str1 += "World";

System.Console.WriteLine(str2);
//Output: Hello

Para saber mais sobre como criar novas cadeias de caracteres que se baseiam em
modificações, como operações de pesquisa e substituição na cadeia de caracteres
original, confira Como modificar o conteúdo da cadeia de caracteres.

Literais de cadeia de caracteres entre aspas


Os literais de cadeia de caracteres entre aspas são iniciar e terminar com um único
caractere de aspas duplas ( " ) na mesma linha. Literais de cadeia de caracteres entre
aspas são mais adequados para cadeias de caracteres que se encaixam em uma única
linha e não incluem sequências de escape. Um literal de cadeia de caracteres entre
aspas deve inserir caracteres de escape, conforme demonstrado no exemplo a seguir:

C#

string columns = "Column 1\tColumn 2\tColumn 3";


//Output: Column 1 Column 2 Column 3

string rows = "Row 1\r\nRow 2\r\nRow 3";


/* Output:
Row 1
Row 2
Row 3
*/

string title = "\"The \u00C6olean Harp\", by Samuel Taylor Coleridge";


//Output: "The Æolean Harp", by Samuel Taylor Coleridge

Literais de cadeia de caracteres textuais


Literais de cadeia de caracteres verbatim são mais convenientes para cadeias de
caracteres de várias linhas, cadeias de caracteres que contêm caracteres de barra
invertida ou aspas duplas inseridas. Cadeias de caracteres verbatim preservam
caracteres de nova linha como parte do texto da cadeia de caracteres. Use aspas duplas
para inserir uma marca de aspas simples dentro de uma cadeia de caracteres textual. O
exemplo a seguir mostra alguns usos comuns para cadeias de caracteres textuais:

C#

string filePath = @"C:\Users\scoleridge\Documents\";


//Output: C:\Users\scoleridge\Documents\

string text = @"My pensive SARA ! thy soft cheek reclined


Thus on mine arm, most soothing sweet it is
To sit beside our Cot,...";
/* Output:
My pensive SARA ! thy soft cheek reclined
Thus on mine arm, most soothing sweet it is
To sit beside our Cot,...
*/

string quote = @"Her name was ""Sara.""";


//Output: Her name was "Sara."

Literais de cadeia de caracteres bruta


A partir do C# 11, é possível usar literais de cadeia de caracteres bruta para criar cadeias
de caracteres multilinha com mais facilidade ou usar quaisquer caracteres que exijam
sequências de escape. Literais de cadeia de caracteres bruta removem a necessidade de
usar sequências de escape. É possível gravar a cadeia de caracteres, incluindo a
formatação de espaço em branco, da forma que ela deve aparecer na saída. Um literal
de cadeia de caracteres bruta:
Inicia e termina com uma sequência de pelo menos três caracteres de aspas duplas
( """ ). É permitido que mais de três caracteres consecutivos iniciem e terminem a
sequência para dar suporte a literais de cadeia de caracteres que contêm três (ou
mais) caracteres de aspas repetidos.
Literais de cadeia de caracteres bruta de linha única exigem os caracteres de aspas
de abertura e fechamento na mesma linha.
Literais de cadeia de caracteres bruta multilinha exigem caracteres de aspas de
abertura e fechamento em suas próprias linhas.
Nos literais de cadeia de caracteres bruta multilinha, qualquer espaço em branco à
esquerda das aspas de fechamento é removido.

Os exemplos a seguir demonstram essas regras:

C#

string singleLine = """Friends say "hello" as they pass by.""";


string multiLine = """
"Hello World!" is typically the first program someone writes.
""";
string embeddedXML = """
<element attr = "content">
<body style="normal">
Here is the main text
</body>
<footer>
Excerpts from "An amazing story"
</footer>
</element >
""";
// The line "<element attr = "content">" starts in the first column.
// All whitespace left of that column is removed from the string.

string rawStringLiteralDelimiter = """"


Raw string literals are delimited
by a string of at least three double quotes,
like this: """
"""";

Os exemplos a seguir demonstram os erros do compilador relatados com base nessas


regras:

C#

// CS8997: Unterminated raw string literal.


var multiLineStart = """This
is the beginning of a string
""";

// CS9000: Raw string literal delimiter must be on its own line.


var multiLineEnd = """
This is the beginning of a string """;

// CS8999: Line does not start with the same whitespace as the closing line
// of the raw string literal
var noOutdenting = """
A line of text.
Trying to outdent the second line.
""";

Os dois primeiros exemplos são inválidos porque literais de cadeia de caracteres bruta
multilinha exigem a sequência de aspas de abertura e fechamento em sua própria linha.
O terceiro exemplo é inválido porque o texto é recuado para a esquerda da sequência
das aspas de fechamento.

Você deve considerar literais de cadeia de caracteres bruta ao gerar texto que inclua
caracteres que exigem sequências de escape ao usar literais de cadeia de caracteres
entre aspas ou literais de cadeia de caracteres verbatim. Literais de cadeia de caracteres
bruta podem ser lidos com mais facilidade por você e outras pessoas, pois serão mais
parecidos com o texto de saída. Por exemplo, considere o seguinte código que inclui
uma cadeia de caracteres de JSON formatado:

C#

string jsonString = """


{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot",
"DatesAvailable": [
"2019-08-01T00:00:00-07:00",
"2019-08-02T00:00:00-07:00"
],
"TemperatureRanges": {
"Cold": {
"High": 20,
"Low": -10
},
"Hot": {
"High": 60,
"Low": 20
}
},
"SummaryWords": [
"Cool",
"Windy",
"Humid"
]
}
""";

Compare esse texto com o texto equivalente em nosso exemplo de serialização JSON,
que não usa esse novo recurso.

Sequências de escape de cadeia de caracteres

ノ Expandir a tabela

Sequência de Nome do caractere Codificação Unicode


escape

\' Aspas simples 0x0027

\" Aspas duplas 0x0022

\ Barra invertida 0x005C

\0 Nulo 0x0000

\a Alerta 0x0007

\b Backspace 0x0008

\f Avanço de formulário 0x000C

\n Nova linha 0x000A

\r Retorno de carro 0x000D

\t Guia horizontal 0x0009

\v Guia vertical 0x000B

\u Sequência de escape Unicode (UTF-16) \uHHHH (intervalo: 0000 - FFFF;


exemplo: \u00E7 = "ç")

\U Sequência de escape Unicode (UTF-32) \U00HHHHHH (intervalo: 000000 -


10FFFF; exemplo: \U0001F47D =
"👽")

\x Sequência de escape Unicode \xH[H][H][H] (intervalo: 0 - FFFF;


semelhante a "\u", exceto pelo exemplo: \x00E7 ou \x0E7 ou \xE7
comprimento variável = "ç")

2 Aviso
Ao usar a sequência de escape \x e especificar menos de quatro dígitos
hexadecimais, se os caracteres que seguem imediatamente a sequência de escape
são dígitos hexadecimais válidos (ou seja, 0 a 9, A-F e a-f), eles serão interpretados
como sendo parte da sequência de escape. Por exemplo, \xA1 produz "¡", que é o
ponto de código U+00A1. No entanto, se o próximo caractere é "A" ou "a", então a
sequência de escape será, em vez disso, interpretada como sendo \xA1A e
produzirá "ਚ", que é o ponto de código U+0A1A. Nesses casos, especificar todos
os quatro dígitos hexadecimais (por exemplo, \x00A1 ) impedirá qualquer
interpretação errônea possível.

7 Observação

Em tempo de compilação, cadeias de caracteres textuais são convertidas em


cadeias de caracteres comuns com as mesmas sequências de escape. Portanto, se
exibir uma cadeia de caracteres textual na janela de observação do depurador, você
verá os caracteres de escape que foram adicionados pelo compilador, não a versão
textual do código-fonte. Por exemplo, a cadeia de caracteres textual
@"C:\files.txt" será exibida na janela de inspeção como "C:\files.txt".

Cadeias de caracteres de formato


Uma cadeia de caracteres de formato é aquela cujo conteúdo pode é determinado
dinamicamente no runtime. Cadeias de caracteres de formato são criadas incorporando
expressões interpoladas ou espaços reservados dentro de chaves dentro em uma cadeia
de caracteres. Tudo dentro das chaves ( {...} ) será resolvido para um valor e uma saída
como uma cadeia de caracteres formatada no runtime. Há dois métodos para criar
cadeias de caracteres de formato: cadeia de caracteres de interpolação e formatação de
composição.

Interpolação de cadeia de caracteres


Disponíveis no C# 6.0 e posterior, as cadeias de caracteres interpoladas são identificadas
pelo caractere especial $ e incluem expressões interpoladas entre chaves. Se você não
estiver familiarizado com a interpolação de cadeia de caracteres, confira o tutorial
Interpolação de cadeia de caracteres – tutorial interativo do C#.

Use a interpolação de cadeia de caracteres para melhorar a legibilidade e a facilidade de


manutenção do seu código. A interpolação de cadeia de caracteres alcança os mesmos
resultados que o método String.Format , mas aumenta a facilidade de uso e a clareza
embutida.

C#

var jh = (firstName: "Jupiter", lastName: "Hammon", born: 1711, published:


1761);
Console.WriteLine($"{jh.firstName} {jh.lastName} was an African American
poet born in {jh.born}.");
Console.WriteLine($"He was first published in {jh.published} at the age of
{jh.published - jh.born}.");
Console.WriteLine($"He'd be over {Math.Round((2018d - jh.born) / 100d) *
100d} years old today.");

// Output:
// Jupiter Hammon was an African American poet born in 1711.
// He was first published in 1761 at the age of 50.
// He'd be over 300 years old today.

A partir do C# 10, você pode usar a interpolação de cadeia de caracteres para inicializar
uma cadeia de caracteres constante quando todas as expressões usadas para espaços
reservados também são cadeias de caracteres constantes.

A partir do C# 11, você pode combinar literais de cadeia de caracteres bruta com
interpolações de cadeia de caracteres. Você inicia e termina a cadeia de caracteres de
formato com três ou mais aspas duplas sucessivas. Se a cadeia de caracteres de saída
precisar conter o caractere { ou } , você poderá usar caracteres extras $ para
especificar quantos caracteres { e } iniciam e encerram uma interpolação. Qualquer
sequência com menos de { ou } caracteres é incluída na saída. O exemplo a seguir
mostra como você pode usar esse recurso para exibir a distância de um ponto da
origem e colocar o ponto dentro de chaves:

C#

int X = 2;
int Y = 3;

var pointMessage = $$"""The point {{{X}}, {{Y}}} is {{Math.Sqrt(X * X + Y *


Y)}} from the origin.""";

Console.WriteLine(pointMessage);
// Output:
// The point {2, 3} is 3.605551275463989 from the origin.

Formatação de composição
O String.Format utiliza os espaços reservados entre chaves para criar uma cadeia de
caracteres de formato. Este exemplo resulta em uma saída semelhante para o método
de interpolação de cadeia de caracteres usado acima.

C#

var pw = (firstName: "Phillis", lastName: "Wheatley", born: 1753, published:


1773);
Console.WriteLine("{0} {1} was an African American poet born in {2}.",
pw.firstName, pw.lastName, pw.born);
Console.WriteLine("She was first published in {0} at the age of {1}.",
pw.published, pw.published - pw.born);
Console.WriteLine("She'd be over {0} years old today.", Math.Round((2018d -
pw.born) / 100d) * 100d);

// Output:
// Phillis Wheatley was an African American poet born in 1753.
// She was first published in 1773 at the age of 20.
// She'd be over 300 years old today.

Para mais informações sobre formatação de tipos .NET, confira Tipos de formatação em
.NET.

Subcadeias de caracteres
Uma subcadeia de caracteres é qualquer sequência de caracteres contida em uma
cadeia de caracteres. Use o método Substring para criar uma nova cadeia de caracteres
com base em uma parte da cadeia de caracteres original. Você pode pesquisar uma ou
mais ocorrências de uma subcadeia de caracteres usando o método IndexOf. Use o
método Replace para substituir todas as ocorrências de uma subcadeia de caracteres
especificada por uma nova cadeia de caracteres. Como o método Substring, Replace
retorna, na verdade, uma nova cadeia de caracteres e não a modifica a cadeia de
caracteres original. Saiba mais em Como pesquisar cadeias de caracteres e Como
modificar o conteúdo da cadeia de caracteres.

C#

string s3 = "Visual C# Express";


System.Console.WriteLine(s3.Substring(7, 2));
// Output: "C#"

System.Console.WriteLine(s3.Replace("C#", "Basic"));
// Output: "Visual Basic Express"

// Index values are zero-based


int index = s3.IndexOf("C");
// index = 7

Acesso a caracteres individuais


Você pode usar a notação de matriz com um valor de índice para adquirir acesso
somente leitura a caracteres individuais, como no seguinte exemplo:

C#

string s5 = "Printing backwards";

for (int i = 0; i < s5.Length; i++)


{
System.Console.Write(s5[s5.Length - i - 1]);
}
// Output: "sdrawkcab gnitnirP"

Se os métodos String não fornecerem a funcionalidade necessária para modificar


caracteres individuais em uma cadeia de caracteres, você poderá usar um objeto
StringBuilder para modificar os caracteres individuais "in-loco" e criar uma nova cadeia
de caracteres para armazenar os resultados usando os métodos StringBuilder. No
exemplo a seguir, suponha que você deva modificar a cadeia de caracteres original de
uma maneira específica e armazenar os resultados para uso futuro:

C#

string question = "hOW DOES mICROSOFT wORD DEAL WITH THE cAPS lOCK KEY?";
System.Text.StringBuilder sb = new System.Text.StringBuilder(question);

for (int j = 0; j < sb.Length; j++)


{
if (System.Char.IsLower(sb[j]) == true)
sb[j] = System.Char.ToUpper(sb[j]);
else if (System.Char.IsUpper(sb[j]) == true)
sb[j] = System.Char.ToLower(sb[j]);
}
// Store the new string.
string corrected = sb.ToString();
System.Console.WriteLine(corrected);
// Output: How does Microsoft Word deal with the Caps Lock key?

Cadeias de caracteres nulas e cadeias de


caracteres vazias
Uma cadeia de caracteres vazia é uma instância de um objeto System.String que contém
zero caractere. As cadeias de caracteres vazias geralmente são usadas em vários
cenários de programação para representar um campo de texto em branco. Você pode
chamar métodos em cadeias de caracteres vazias porque eles são objetos System.String
válidos. As cadeias de caracteres vazias são inicializadas da seguinte maneira:

C#

string s = String.Empty;

Por outro lado, uma cadeia de caracteres nula não se refere a uma instância de um
objeto System.String e qualquer tentativa de chamar um método em uma cadeia de
caracteres nula provocará uma NullReferenceException. No entanto, você pode usar
cadeias de caracteres nulas em operações de comparação e concatenação com outras
cadeias de caracteres. Os exemplos a seguir ilustram alguns casos em que uma
referência a uma cadeia de caracteres nula faz e não faz com que uma exceção seja
lançada:

C#

string str = "hello";


string nullStr = null;
string emptyStr = String.Empty;

string tempStr = str + nullStr;


// Output of the following line: hello
Console.WriteLine(tempStr);

bool b = (emptyStr == nullStr);


// Output of the following line: False
Console.WriteLine(b);

// The following line creates a new empty string.


string newStr = emptyStr + nullStr;

// Null strings and empty strings behave differently. The following


// two lines display 0.
Console.WriteLine(emptyStr.Length);
Console.WriteLine(newStr.Length);
// The following line raises a NullReferenceException.
//Console.WriteLine(nullStr.Length);

// The null character can be displayed and counted, like other chars.
string s1 = "\x0" + "abc";
string s2 = "abc" + "\x0";
// Output of the following line: * abc*
Console.WriteLine("*" + s1 + "*");
// Output of the following line: *abc *
Console.WriteLine("*" + s2 + "*");
// Output of the following line: 4
Console.WriteLine(s2.Length);

Uso do stringBuilder para a criação rápida de


cadeias de caracteres
As operações de cadeia de caracteres no .NET são altamente otimizadas e, na maioria
dos casos, não afetam o desempenho de forma significativa. No entanto, em alguns
cenários, como loops rígidos que são executados centenas ou milhares de vezes, as
operações de cadeia de caracteres podem afetar o desempenho. A classe StringBuilder
cria um buffer de cadeia de caracteres que oferece desempenho melhor se o programa
executa várias manipulações de cadeia de caracteres. A cadeia de caracteres
StringBuilder também permite reatribuir caracteres individuais, o que o tipo de dados
String interno não dá suporte. Esse código, por exemplo, altera o conteúdo de uma
cadeia de caracteres sem criar uma nova cadeia de caracteres:

C#

System.Text.StringBuilder sb = new System.Text.StringBuilder("Rat: the ideal


pet");
sb[0] = 'C';
System.Console.WriteLine(sb.ToString());
//Outputs Cat: the ideal pet

Neste exemplo, um objeto StringBuilder é usado para criar uma cadeia de caracteres
com base em um conjunto de tipos numéricos:

C#

var sb = new StringBuilder();

// Create a string composed of numbers 0 - 9


for (int i = 0; i < 10; i++)
{
sb.Append(i.ToString());
}
Console.WriteLine(sb); // displays 0123456789

// Copy one character of the string (not possible with a System.String)


sb[0] = sb[9];

Console.WriteLine(sb); // displays 9123456789


Cadeias de caracteres, métodos de extensão e
LINQ
Uma vez que o tipo String implementa IEnumerable<T>, você pode usar os métodos de
extensão definidos na classe Enumerable em cadeias de caracteres. Para evitar a
desordem visual, esses métodos são excluídos do IntelliSense para o tipo String, mas
estão disponíveis mesmo assim. Você também pode usar a expressão de consulta LINQ
em cadeias de caracteres. Para saber mais, confira LINQ e cadeias de caracteres.

Artigos relacionados
Como modificar o conteúdo de uma cadeia de caracteres: ilustra as técnicas para
transformar cadeias de caracteres e modificar o conteúdo delas.
Como comparar cadeias de caracteres: mostra como executar comparações
ordinais e específicas da cultura de cadeias de caracteres.
Como concatenar várias cadeias de caracteres: demonstra várias maneiras de unir
diversas cadeias de caracteres em uma só.
Como analisar cadeias de caracteres usando String.Split: contém exemplos de
código que descrevem como usar o método String.Split para analisar cadeias de
caracteres.
Como pesquisar cadeias de caracteres: explica como usar a pesquisa para texto
específico ou padrões em cadeias de caracteres.
Como determinar se uma cadeia de caracteres representa um valor numérico:
mostra como analisar com segurança uma cadeia de caracteres para ver se ela tem
um valor numérico válido.
Interpolação de cadeias de caracteres: descreve o recurso de interpolação de
cadeia de caracteres que fornece uma sintaxe prática para cadeias de caracteres de
formato.
Operações básicas de cadeias de caracteres: fornece links para artigos que usam
os métodos System.String e System.Text.StringBuilder para executar operações
básicas de cadeia de caracteres.
Analisando cadeias de caracteres: descreve como converter representações de
cadeia de caracteres de tipos base do .NET em instâncias de tipos
correspondentes.
Como analisar cadeias de caracteres de data e hora no .NET: mostra como
converter uma cadeia de caracteres como "24/01/2008" em um objeto
System.DateTime.
Comparando cadeias de caracteres: inclui informações sobre como comparar
cadeias de caracteres e fornece exemplos em C# e Visual Basic.
Uso da classe StringBuilder: descreve como criar e modificar objetos de cadeias de
caracteres dinâmicas usando a classe StringBuilder.
LINQ e Strings: fornece informações sobre como executar várias operações de
cadeia de caracteres usando consultas LINQ.

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

Fornecer comentários sobre o produto


Como determinar se uma cadeia de
caracteres representa um valor
numérico (Guia de Programação em C#)
Artigo • 08/04/2023

Para determinar se uma cadeia de caracteres é uma representação válida de um tipo


numérico especificado, use o método estático TryParse implementado por todos os
tipos numéricos primitivos e também por tipos como DateTime e IPAddress. O exemplo
a seguir mostra como determinar se "108" é um int válido.

C#

int i = 0;
string s = "108";
bool result = int.TryParse(s, out i); //i now = 108

Se a cadeia de caracteres contiver caracteres não numéricos ou o valor numérico for


muito grande ou muito pequeno para o tipo especificado, TryParse retornará false e
definirá o parâmetro de saída como zero. Caso contrário, ele retornará true e definirá o
parâmetro de saída como o valor numérico da cadeia de caracteres.

7 Observação

Uma cadeia de caracteres pode conter apenas caracteres numéricos e ainda não
ser válida para o método TryParse do tipo usado. Por exemplo, "256" não é um
valor válido para byte , mas é válido para int . “98,6” não é um valor válido para
int , mas é válido para decimal .

Exemplo
Os exemplos a seguir mostram como usar TryParse com representações de cadeia de
caracteres dos valores long , byte e decimal .

C#

string numString = "1287543"; //"1287543.0" will return false for a long


long number1 = 0;
bool canConvert = long.TryParse(numString, out number1);
if (canConvert == true)
Console.WriteLine("number1 now = {0}", number1);
else
Console.WriteLine("numString is not a valid long");

byte number2 = 0;
numString = "255"; // A value of 256 will return false
canConvert = byte.TryParse(numString, out number2);
if (canConvert == true)
Console.WriteLine("number2 now = {0}", number2);
else
Console.WriteLine("numString is not a valid byte");

decimal number3 = 0;
numString = "27.3"; //"27" is also a valid decimal
canConvert = decimal.TryParse(numString, out number3);
if (canConvert == true)
Console.WriteLine("number3 now = {0}", number3);
else
Console.WriteLine("number3 is not a valid decimal");

Programação robusta
Os tipos numéricos primitivos também implementam o método estático Parse , que
lançará uma exceção se a cadeia de caracteres não for um número válido. Geralmente,
TryParse é mais eficiente, pois retornará false apenas se o número não for válido.

Segurança do .NET
Sempre use os métodos TryParse ou Parse para validar entradas de usuário em
controles como caixas de texto e caixas de combinação.

Confira também
Como converter uma matriz de bytes em um int
Como converter uma cadeia de caracteres em um número
Como converter entre cadeias de caracteres hexadecimais e tipos numéricos
Analisando cadeias de caracteres numéricas
Formatar tipos
Indexadores (Guia de Programação em
C#)
Artigo • 11/04/2024

Os indexadores permitem que instâncias de uma classe ou struct sejam indexados como
matrizes. O valor indexado pode ser definido ou recuperado sem especificar
explicitamente um membro de instância ou tipo. Os indexadores parecem com
propriedades, a diferença é que seus acessadores usam parâmetros.

O exemplo a seguir define uma classe genérica com métodos de acesso get e set
simples para atribuir e recuperar valores. A classe Program cria uma instância dessa
classe para armazenar cadeias de caracteres.

C#

using System;

class SampleCollection<T>
{
// Declare an array to store the data elements.
private T[] arr = new T[100];

// Define the indexer to allow client code to use [] notation.


public T this[int i]
{
get { return arr[i]; }
set { arr[i] = value; }
}
}

class Program
{
static void Main()
{
var stringCollection = new SampleCollection<string>();
stringCollection[0] = "Hello, World";
Console.WriteLine(stringCollection[0]);
}
}
// The example displays the following output:
// Hello, World.

7 Observação

Para mais exemplos, consulte as seções relacionadas.


Definições de corpo de expressão
É comum para um acessador get ou set de um indexador ser constituído de uma única
instrução que retorna ou define um valor. Os membros de expressão fornecem uma
sintaxe simplificada para dar suporte a esse cenário. Começando do C# 6, um indexador
somente leitura pode ser implementado como um membro de expressão, como mostra
o exemplo a seguir.

C#

using System;

class SampleCollection<T>
{
// Declare an array to store the data elements.
private T[] arr = new T[100];
int nextIndex = 0;

// Define the indexer to allow client code to use [] notation.


public T this[int i] => arr[i];

public void Add(T value)


{
if (nextIndex >= arr.Length)
throw new IndexOutOfRangeException($"The collection can hold only
{arr.Length} elements.");
arr[nextIndex++] = value;
}
}

class Program
{
static void Main()
{
var stringCollection = new SampleCollection<string>();
stringCollection.Add("Hello, World");
System.Console.WriteLine(stringCollection[0]);
}
}
// The example displays the following output:
// Hello, World.

Observe que => apresenta o corpo da expressão e que a palavra-chave get não é
usada.

Começando do C# 7.0, os acessadores get e set podem ser implementados como


membros aptos para expressão. Nesse caso, as palavras-chave get e set devem ser
usadas. Por exemplo:
C#

using System;

class SampleCollection<T>
{
// Declare an array to store the data elements.
private T[] arr = new T[100];

// Define the indexer to allow client code to use [] notation.


public T this[int i]
{
get => arr[i];
set => arr[i] = value;
}
}

class Program
{
static void Main()
{
var stringCollection = new SampleCollection<string>();
stringCollection[0] = "Hello, World.";
Console.WriteLine(stringCollection[0]);
}
}
// The example displays the following output:
// Hello, World.

Visão Geral dos Indexadores


Os indexadores permitem que objetos sejam indexados de maneira semelhante às
matrizes.

Um acessador get retorna um valor. Um acessador set atribui um valor.

A palavra-chave this é usada para definir o indexador.

A palavra-chave value é usada para definir o valor que está sendo atribuído pelo
acessador set .

Os indexadores não precisam ser indexados por um valor inteiro. Você deve definir
o mecanismo de pesquisa específico.

Os indexadores podem ser sobrecarregados.

Os indexadores podem ter mais de um parâmetro formal, por exemplo, ao acessar


uma matriz bidimensional.
Seções relacionadas
Usando indexadores

Indexadores em interfaces

Comparação entre propriedades e indexadores

Restringindo a acessibilidade ao acessador

Especificação da Linguagem C#
Para obter mais informações, veja Indexadores 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
Guia de Programação em C#
Propriedades

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

Fornecer comentários sobre o produto


Usando indexadores (Guia de
Programação em C#)
Artigo • 20/08/2024

Os indexadores são uma conveniência sintática que permite criar uma classe, um struct
ou uma interface que os aplicativos clientes podem acessar como uma matriz. O
compilador gera uma propriedade Item (ou uma propriedade de nome alternativo, se
IndexerNameAttribute estiver presente) e os métodos acessadores apropriados. Os
indexadores são implementados em tipos cuja principal finalidade é encapsular uma
coleção ou matriz interna. Por exemplo, suponha que você tenha uma classe TempRecord
que representa a temperatura em Fahrenheit, conforme registrada em 10 momentos
diferentes durante um período de 24 horas. A classe contém uma matriz temps do tipo
float[] para armazenar os valores de temperatura. Ao implementar um indexador

nessa classe, os clientes podem acessar as temperaturas em uma instância TempRecord


como float temp = tempRecord[4] , e não como float temp = tempRecord.temps[4] . A
notação do indexador não apenas simplifica a sintaxe para aplicativos clientes, mas
também torna a classe e a finalidade dela mais intuitivas para que os outros
desenvolvedores entendam.

Para declarar um indexador em uma classe ou struct, use a palavra-chave this, como
mostra o seguinte exemplo:

C#

// Indexer declaration
public int this[int index]
{
// get and set accessors
}

) Importante

Declarar um indexador vai gerar automaticamente uma propriedade chamada Item


no objeto. A propriedade Item não pode ser acessada diretamente por meio da
expressão de acesso a membro da instância. Além disso, se você adicionar a sua
propriedade Item a um objeto com indexador, será gerado um erro do compilador
CS0102. Para evitar esse erro, use a opção de IndexerNameAttribute renomear o
indexador, conforme detalhado mais para frente neste artigo.
Comentários
O tipo de um indexador e o tipo dos seus parâmetros devem ser pelo menos tão
acessíveis quanto o próprio indexador. Para obter mais informações sobre níveis de
acessibilidade, consulte Modificadores de acesso.

Para obter mais informações sobre como usar indexadores com uma interface, consulte
Indexadores de Interface.

A assinatura de um indexador consiste do número e dos tipos de seus parâmetros


formais. Ela não inclui o tipo de indexador nem os nomes dos parâmetros formais. Se
você declarar mais de um indexador na mesma classe, eles terão diferentes assinaturas.

Um indexador não é classificado como uma variável; portanto, um valor de indexador


não pode ser passado por referência (como um parâmetro ref ou out), a menos que seu
valor seja uma referência (ou seja, ele retorna por referência).

Para fornecer o indexador com um nome que outras linguagens possam usar, use
System.Runtime.CompilerServices.IndexerNameAttribute, como mostra o seguinte
exemplo:

C#

// Indexer declaration
[System.Runtime.CompilerServices.IndexerName("TheItem")]
public int this[int index]
{
// get and set accessors
}

Esse indexador tem o nome TheItem , pois ele é substituído pelo atributo de nome do
indexador. Por padrão, o nome do indexador é Item .

Exemplo 1
O exemplo a seguir mostra como declarar um campo de matriz privada, temps e um
indexador. O indexador permite acesso direto à instância tempRecord[i] . A alternativa
ao uso do indexador é declarar a matriz como um membro público e acessar seus
membros, tempRecord.temps[i] , diretamente.

C#

public class TempRecord


{
// Array of temperature values
float[] temps =
[
56.2F, 56.7F, 56.5F, 56.9F, 58.8F,
61.3F, 65.9F, 62.1F, 59.2F, 57.5F
];

// To enable client code to validate input


// when accessing your indexer.
public int Length => temps.Length;

// Indexer declaration.
// If index is out of range, the temps array will throw the exception.
public float this[int index]
{
get => temps[index];
set => temps[index] = value;
}
}

Observe que, quando o acesso de um indexador é avaliado, por exemplo, em uma


instrução Console.Write , o acessador get é invocado. Portanto, se não existir nenhum
acessador get , ocorrerá um erro em tempo de compilação.

C#

var tempRecord = new TempRecord();

// Use the indexer's set accessor


tempRecord[3] = 58.3F;
tempRecord[5] = 60.1F;

// Use the indexer's get accessor


for (int i = 0; i < 10; i++)
{
Console.WriteLine($"Element #{i} = {tempRecord[i]}");
}

Indexando usando outros valores


O C# não limita o tipo de parâmetro do indexador ao inteiro. Por exemplo, pode ser útil
usar uma cadeia de caracteres com um indexador. Esse indexador pode ser
implementado pesquisando a cadeia de caracteres na coleção e retornando o valor
adequado. Como os acessadores podem ser sobrecarregados, as versões do inteiro e da
cadeia de caracteres podem coexistir.
Exemplo 2
O exemplo a seguir declara uma classe que armazena os dias da semana. Um acessador
get aceita uma cadeia de caracteres, o nome de um dia e retorna o inteiro

correspondente. Por exemplo, "Sunday" retorna 0, "Monday" retorna 1 e assim por


diante.

C#

// Using a string as an indexer value


class DayCollection
{
string[] days = ["Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"];

// Indexer with only a get accessor with the expression-bodied


definition:
public int this[string day] => FindDayIndex(day);

private int FindDayIndex(string day)


{
for (int j = 0; j < days.Length; j++)
{
if (days[j] == day)
{
return j;
}
}

throw new ArgumentOutOfRangeException(


nameof(day),
$"Day {day} is not supported.\nDay input must be in the form
\"Sun\", \"Mon\", etc");
}
}

Exemplo de consumo 2
C#

var week = new DayCollection();


Console.WriteLine(week["Fri"]);

try
{
Console.WriteLine(week["Made-up day"]);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine($"Not supported input: {e.Message}");
}

Exemplo 3
O exemplo a seguir declara uma classe que armazena os dias da semana usando a
enumeração System.DayOfWeek. Um acessador get aceita DayOfWeek , o valor de um
dia, e retorna o inteiro correspondente. Por exemplo, DayOfWeek.Sunday retorna 0,
DayOfWeek.Monday retorna 1 e assim por diante.

C#

using Day = System.DayOfWeek;

class DayOfWeekCollection
{
Day[] days =
[
Day.Sunday, Day.Monday, Day.Tuesday, Day.Wednesday,
Day.Thursday, Day.Friday, Day.Saturday
];

// Indexer with only a get accessor with the expression-bodied


definition:
public int this[Day day] => FindDayIndex(day);

private int FindDayIndex(Day day)


{
for (int j = 0; j < days.Length; j++)
{
if (days[j] == day)
{
return j;
}
}
throw new ArgumentOutOfRangeException(
nameof(day),
$"Day {day} is not supported.\nDay input must be a defined
System.DayOfWeek value.");
}
}

Exemplo de consumo 3
C#

var week = new DayOfWeekCollection();


Console.WriteLine(week[DayOfWeek.Friday]);
try
{
Console.WriteLine(week[(DayOfWeek)43]);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine($"Not supported input: {e.Message}");
}

Programação robusta
Há duas maneiras principais nas quais a segurança e a confiabilidade de indexadores
podem ser melhoradas:

Certifique-se de incorporar algum tipo de estratégia de tratamento de erros para


manipular a chance de passagem de código cliente em um valor de índice inválido.
Anteriormente, no primeiro exemplo neste artigo, a classe TempRecord oferece
uma propriedade Length que permite que o código cliente verifique a saída antes
de passá-la para o indexador. Também é possível colocador o código de
tratamento de erro dentro do próprio indexador. Certifique-se documentar para os
usuários as exceções que você gera dentro de um acessador do indexador.

Defina a acessibilidade dos acessadores get e set para que ela seja mais restritiva
possível. Isso é importante para o acessador set em particular. Para obter mais
informações, consulte Restringindo a acessibilidade aos acessadores.

Confira também
Indexadores
Propriedades
Indexadores em interfaces (Guia de
Programação em C#)
Artigo • 20/08/2024

Os indexadores podem ser declarados em uma interface. Acessadores de indexadores


de interface diferem dos acessadores de indexadores de classe das seguintes maneiras:

Os acessadores de interface não usam modificadores.


Um acessador de interface normalmente não tem corpo.

Portanto, a finalidade do acessador é indicar se o indexador é do tipo leitura/gravação,


somente leitura ou somente gravação. Você pode fornecer uma implementação para um
indexador definido em uma interface, mas isso é raro. Normalmente, os indexadores
definem uma API para acessar campos de dados, que não podem ser definidos em uma
interface.

Este é um exemplo de um acessador de indexador de interface:

C#

public interface ISomeInterface


{
//...

// Indexer declaration:
string this[int index]
{
get;
set;
}
}

A assinatura de um indexador deve ser diferente das assinaturas de todos os outros


indexadores declarados na mesma interface.

Exemplo
O exemplo a seguir mostra como implementar indexadores de interface.

C#

// Indexer on an interface:
public interface IIndexInterface
{
// Indexer declaration:
int this[int index]
{
get;
set;
}
}

// Implementing the interface.


class IndexerClass : IIndexInterface
{
private int[] arr = new int[100];
public int this[int index] // indexer declaration
{
// The arr object will throw IndexOutOfRange exception.
get => arr[index];
set => arr[index] = value;
}
}

C#

IndexerClass test = new IndexerClass();


System.Random rand = System.Random.Shared;
// Call the indexer to initialize its elements.
for (int i = 0; i < 10; i++)
{
test[i] = rand.Next();
}
for (int i = 0; i < 10; i++)
{
System.Console.WriteLine($"Element #{i} = {test[i]}");
}

/* Sample output:
Element #0 = 360877544
Element #1 = 327058047
Element #2 = 1913480832
Element #3 = 1519039937
Element #4 = 601472233
Element #5 = 323352310
Element #6 = 1422639981
Element #7 = 1797892494
Element #8 = 875761049
Element #9 = 393083859
*/

No exemplo anterior, é possível usar a implementação de membro de interface explícita


usando o nome totalmente qualificado do membro de interface. Por exemplo

C#
string IIndexInterface.this[int index]
{
}

No entanto, o nome totalmente qualificado só será necessário para evitar ambiguidade


quando a classe estiver implementando mais de uma interface com a mesma assinatura
do indexador. Por exemplo, se uma classe Employee estiver implementando dois
interfaces, ICitizen e IEmployee , e as duas interfaces tiverem a mesma assinatura de
indexador, a implementação de membro de interface explícita é necessária. Ou seja, a
seguinte declaração de indexador:

C#

string IEmployee.this[int index]


{
}

implementa o indexador na interface IEmployee , enquanto a seguinte declaração:

C#

string ICitizen.this[int index]


{
}

implementa o indexador na interface ICitizen .

Confira também
Indexadores
Propriedades
Interfaces
Comparação entre propriedades e
indexadores (Guia de Programação em
C#)
Artigo • 26/10/2024

Os indexadores são como propriedades. Com exceção das diferenças mostradas na


tabela a seguir, todas as regras definidas para acessadores de propriedade também se
aplicam a acessadores de indexador.

ノ Expandir a tabela

Propriedade Indexador

Permite que os métodos sejam Permite que elementos de uma coleção interna de
chamados como se fossem membros de um objeto sejam acessados usando uma notação de
dados públicos. matriz no próprio objeto.

Acessado por meio de um nome simples. Acessado por meio de um índice.

Pode ser estático ou um membro de Deve ser um membro da instância.


instância.

Um acessador get de uma propriedade Um acessador get de um indexador tem a mesma


não tem parâmetros. lista de parâmetro formal que o indexador.

Um acessador set de uma propriedade Um acessador set de um indexador tem a mesma


contém o parâmetro implícito value . lista de parâmetro formal que o indexador, bem
como o mesmo parâmetro de valor.

Suporta sintaxe abreviada com Dá suporte a membros aptos para expressão a fim de
propriedades implementadas obter somente indexadores.
automaticamente.

Confira também
Indexadores
Propriedades
Eventos (Guia de Programação em C#)
Artigo • 11/04/2024

Eventos permitem que uma classe ou objeto notifique outras classes ou objetos quando
algo interessante ocorre. A classe que envia (ou aciona) o evento é chamada de editor e
as classes que recebem (ou manipulam) os eventos são chamadas assinantes.

Em um aplicativo Windows Forms em C# ou Web típico, você assina eventos acionados


pelos controles, como botões e caixas de listagem. Você pode usar o IDE (ambiente de
desenvolvimento integrado) do Visual C# para procurar os eventos que um controle
publica e selecionar aqueles que você deseja manipular. O IDE oferece uma maneira
fácil de adicionar automaticamente um método de manipulador de eventos vazio e o
código para assinar o evento. Para obter mais informações, confira Como assinar e
cancelar a assinatura de eventos.

Visão geral sobre eventos


Os eventos têm as seguintes propriedades:

O editor determina quando um evento é acionado. Os assinantes determinam a


ação que é executada em resposta ao evento.

Um evento pode ter vários assinantes. Um assinante pode manipular vários


eventos de vários publicadores.

Eventos que não têm assinantes nunca são acionados.

Normalmente, os eventos são usados para sinalizar ações do usuário, como cliques
de botão ou seleções de menu em interfaces gráficas do usuário.

Quando um evento tem vários assinantes, os manipuladores de eventos são


invocados sincronicamente quando um evento é acionado. Para invocar eventos
de forma assíncrona, consulte Chamando métodos síncronos assincronamente.

Na biblioteca de classes do .NET, os eventos são baseados no delegado


EventHandler e na classe base EventArgs.

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

Como realizar e cancelar a assinatura de eventos


Como publicar eventos em conformidade com as diretrizes do .NET

Como acionar eventos de classe base em classes derivadas

Como implementar eventos de interface

Como implementar acessadores de eventos personalizados

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

Capítulos do Livro em Destaque


Expressões lambda, eventos e delegados em C# 3.0 Cookbook, Third Edition: More than
250 solutions for C# 3.0 programmers

Delegados e eventos em Aprendendo sobre C# 3.0: conceitos básicos do C# 3.0

Confira também
EventHandler
Guia de Programação em C#
Representantes
Criando manipuladores de eventos no Windows Forms

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

Fornecer comentários sobre o produto


Como realizar e cancelar a assinatura de
eventos (Guia de Programação em C#)
Artigo • 10/03/2023

Você assina um evento publicado por outra classe quando quer escrever um código
personalizado que é chamado quando esse evento é gerado. Por exemplo, você pode
assinar o evento click de um botão para fazer com que seu aplicativo faça algo útil
quando o usuário clicar no botão.

Para assinar eventos usando o IDE do Visual Studio


1. Se você não vir a janela Propriedades, no modo de exibição de Design, clique com
o botão direito do mouse no formulário ou controle para o qual deseja criar um
manipulador de eventos e selecione Propriedades.

2. Na parte superior da janela Propriedades, clique no ícone Eventos.

3. Clique duas vezes no evento que deseja criar, por exemplo, o evento Load .

O Visual C# cria um método de manipulador de eventos vazio e adiciona-o ao


código. Como alternativa, você pode adicionar o código manualmente no modo
de exibição Código. Por exemplo, as linhas de código a seguir declaram um
método de manipulador de eventos que será chamado quando a classe Form
gerar o evento Load .

C#

private void Form1_Load(object sender, System.EventArgs e)


{
// Add your form load event handling code here.
}

A linha de código que é necessária para assinar o evento também é gerada


automaticamente no método InitializeComponent no arquivo Form1.Designer.cs
em seu projeto. Ele é semelhante a isto:

C#

this.Load += new System.EventHandler(this.Form1_Load);


Para assinar eventos de forma programática
1. Defina um método de manipulador de eventos cuja assinatura corresponda à
assinatura do delegado do evento. Por exemplo, se o evento se basear no tipo de
delegado EventHandler, o código a seguir representará o stub do método:

C#

void HandleCustomEvent(object sender, CustomEventArgs a)


{
// Do something useful here.
}

2. Use o operador de atribuição de adição ( += ) para anexar um manipulador de


eventos ao evento. No exemplo a seguir, suponha que um objeto chamado
publisher tem um evento chamado RaiseCustomEvent . Observe que a classe do
assinante precisa de uma referência à classe do editor para assinar seus eventos.

C#

publisher.RaiseCustomEvent += HandleCustomEvent;

Você também pode usar uma expressão lambda para especificar um manipulador
de eventos:

C#

public Form1()
{
InitializeComponent();
this.Click += (s,e) =>
{
MessageBox.Show(((MouseEventArgs)e).Location.ToString());
};
}

Para assinar eventos usando uma função anônima


Se não tiver que cancelar a assinatura de um evento posteriormente, você poderá usar o
operador de atribuição de adição ( += ) para anexar uma função anônima como um
manipulador de eventos. No exemplo a seguir, suponha que um objeto chamado
publisher tenha um evento chamado RaiseCustomEvent e que uma classe
CustomEventArgs também tenha sido definida para conter algum tipo de informação de
evento específico. Observe que a classe do assinante precisa de uma referência a
publisher para assinar seus eventos.

C#

publisher.RaiseCustomEvent += (object o, CustomEventArgs e) =>


{
string s = o.ToString() + " " + e.ToString();
Console.WriteLine(s);
};

Você não poderá cancelar facilmente a assinatura de um evento se tiver usado uma
função anônima para assiná-lo. Para cancelar a assinatura nesse cenário, volte para o
código em que você assina o evento, armazene a função anônima em uma variável de
delegado e adicione o delegado ao evento. Recomendamos que você não use funções
anônimas para assinar eventos se tiver que cancelar a assinatura do evento em algum
momento posterior em seu código. Para obter mais informações sobre expressões
lambda, consulte Expressão lambda.

Cancelando a assinatura
Para impedir que o manipulador de eventos seja invocado quando o evento for gerado,
cancele a assinatura do evento. Para evitar perda de recursos, cancele a assinatura de
eventos antes de descartar um objeto de assinante. Até que você cancele a assinatura
de um evento, o delegado multicast subjacente ao evento no objeto de publicação terá
uma referência ao delegado que encapsula o manipulador de eventos do assinante.
Desde que o objeto de publicação contenha essa referência, a coleta de lixo não excluirá
seu objeto de assinante.

Para cancelar a assinatura de um evento


Use o operador de atribuição de subtração ( -= ) para cancelar a assinatura de um
evento:

C#

publisher.RaiseCustomEvent -= HandleCustomEvent;

Quando todos os assinantes tiverem cancelado a assinatura de um evento, a


instância do evento na classe do publicador será definida como null .
Confira também
Eventos
event
Como publicar eventos em conformidade com as diretrizes do .NET
Operadores - e -=
Operadores + e +=
Como acionar eventos de classe base
em classes derivadas (Guia de
Programação em C#)
Artigo • 10/05/2023

O exemplo simples a seguir mostra o modo padrão para declarar os eventos em uma
classe base para que eles também possam ser gerados das classes derivadas. Esse
padrão é amplamente usado em classes do Windows Forms nas bibliotecas de classes
do .NET.

Quando você cria uma classe que pode ser usada como uma classe base para outras
classes, deve considerar o fato de que os eventos são um tipo especial de delegado que
pode ser invocado apenas de dentro da classe que os declarou. As classes derivadas não
podem invocar diretamente eventos declarados dentro da classe base. Embora, às vezes,
você possa desejar um evento que possa ser gerado apenas pela classe base, na maioria
das vezes você deve habilitar a classe derivada para invocar os eventos de classe base.
Para fazer isso, você pode criar um método de invocação protegido na classe base que
encapsula o evento. Chamando ou substituindo esse método de invocação, as classes
derivadas podem invocar o evento diretamente.

7 Observação

Não declare eventos virtuais em uma classe base e substitua-os em uma classe
derivada. O compilador C# não lida com eles corretamente e é imprevisível se um
assinante do evento derivado realmente estará assinando o evento de classe base.

Exemplo
C#

namespace BaseClassEvents
{
// Special EventArgs class to hold info about Shapes.
public class ShapeEventArgs : EventArgs
{
public ShapeEventArgs(double area)
{
NewArea = area;
}
public double NewArea { get; }
}

// Base class event publisher


public abstract class Shape
{
protected double _area;

public double Area


{
get => _area;
set => _area = value;
}

// The event. Note that by using the generic EventHandler<T> event


type
// we do not need to declare a separate delegate type.
public event EventHandler<ShapeEventArgs> ShapeChanged;

public abstract void Draw();

//The event-invoking method that derived classes can override.


protected virtual void OnShapeChanged(ShapeEventArgs e)
{
// Safely raise the event for all subscribers
ShapeChanged?.Invoke(this, e);
}
}

public class Circle : Shape


{
private double _radius;

public Circle(double radius)


{
_radius = radius;
_area = 3.14 * _radius * _radius;
}

public void Update(double d)


{
_radius = d;
_area = 3.14 * _radius * _radius;
OnShapeChanged(new ShapeEventArgs(_area));
}

protected override void OnShapeChanged(ShapeEventArgs e)


{
// Do any circle-specific processing here.

// Call the base class event invocation method.


base.OnShapeChanged(e);
}
public override void Draw()
{
Console.WriteLine("Drawing a circle");
}
}

public class Rectangle : Shape


{
private double _length;
private double _width;

public Rectangle(double length, double width)


{
_length = length;
_width = width;
_area = _length * _width;
}

public void Update(double length, double width)


{
_length = length;
_width = width;
_area = _length * _width;
OnShapeChanged(new ShapeEventArgs(_area));
}

protected override void OnShapeChanged(ShapeEventArgs e)


{
// Do any rectangle-specific processing here.

// Call the base class event invocation method.


base.OnShapeChanged(e);
}

public override void Draw()


{
Console.WriteLine("Drawing a rectangle");
}
}

// Represents the surface on which the shapes are drawn


// Subscribes to shape events so that it knows
// when to redraw a shape.
public class ShapeContainer
{
private readonly List<Shape> _list;

public ShapeContainer()
{
_list = new List<Shape>();
}

public void AddShape(Shape shape)


{
_list.Add(shape);
// Subscribe to the base class event.
shape.ShapeChanged += HandleShapeChanged;
}

// ...Other methods to draw, resize, etc.

private void HandleShapeChanged(object sender, ShapeEventArgs e)


{
if (sender is Shape shape)
{
// Diagnostic message for demonstration purposes.
Console.WriteLine($"Received event. Shape area is now
{e.NewArea}");

// Redraw the shape here.


shape.Draw();
}
}
}

class Test
{
static void Main()
{
//Create the event publishers and subscriber
var circle = new Circle(54);
var rectangle = new Rectangle(12, 9);
var container = new ShapeContainer();

// Add the shapes to the container.


container.AddShape(circle);
container.AddShape(rectangle);

// Cause some events to be raised.


circle.Update(57);
rectangle.Update(7, 7);

// Keep the console window open in debug mode.


Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
}
}
/* Output:
Received event. Shape area is now 10201.86
Drawing a circle
Received event. Shape area is now 49
Drawing a rectangle
*/

Confira também
Guia de Programação em C#
Eventos
Representantes
Modificadores de acesso
Criando manipuladores de eventos no Windows Forms
Como implementar eventos de interface
(Guia de Programação em C#)
Artigo • 13/03/2024

Um interface pode declarar uma evento. O exemplo a seguir mostra como implementar
eventos de interface em uma classe. Basicamente, as regras são as mesmas aplicadas à
implementação de qualquer método ou propriedade de interface.

Implementar eventos de interface em uma


classe
Declare o evento na classe e, em seguida, invoque-o nas áreas apropriadas.

C#

namespace ImplementInterfaceEvents
{
public interface IDrawingObject
{
event EventHandler ShapeChanged;
}
public class MyEventArgs : EventArgs
{
// class members
}
public class Shape : IDrawingObject
{
public event EventHandler ShapeChanged;
void ChangeShape()
{
// Do something here before the event…

OnShapeChanged(new MyEventArgs(/*arguments*/));

// or do something here after the event.


}
protected virtual void OnShapeChanged(MyEventArgs e)
{
ShapeChanged?.Invoke(this, e);
}
}

}
Exemplo
O exemplo a seguir mostra como lidar com a situação menos comum, na qual a classe
herda de duas ou mais interfaces e cada interface tem um evento com o mesmo nome.
Nessa situação, é necessário fornecer uma implementação explícita da interface para
pelo menos um dos eventos. Ao gravar uma implementação explícita da interface de um
evento, também é necessário gravar os acessadores de evento add e remove .
Normalmente, eles são fornecidos pelo compilador, mas nesse caso o compilador não
pode fornecê-los.

Ao fornecer acessadores próprios, é possível especificar se os dois eventos são


representados pelo mesmo evento na classe ou por eventos diferentes. Por exemplo, se
os eventos forem gerados em horários diferentes, de acordo com as especificações da
interface, será possível associar cada evento a uma implementação separada na classe.
No exemplo a seguir, os assinantes determinam qual evento OnDraw receberão ao
converter a referência de forma para um IShape ou um IDrawingObject .

C#

namespace WrapTwoInterfaceEvents
{
using System;

public interface IDrawingObject


{
// Raise this event before drawing
// the object.
event EventHandler OnDraw;
}
public interface IShape
{
// Raise this event after drawing
// the shape.
event EventHandler OnDraw;
}

// Base class event publisher inherits two


// interfaces, each with an OnDraw event
public class Shape : IDrawingObject, IShape
{
// Create an event for each interface event
event EventHandler PreDrawEvent;
event EventHandler PostDrawEvent;

object objectLock = new Object();

// Explicit interface implementation required.


// Associate IDrawingObject's event with
// PreDrawEvent
#region IDrawingObjectOnDraw
event EventHandler IDrawingObject.OnDraw
{
add
{
lock (objectLock)
{
PreDrawEvent += value;
}
}
remove
{
lock (objectLock)
{
PreDrawEvent -= value;
}
}
}
#endregion
// Explicit interface implementation required.
// Associate IShape's event with
// PostDrawEvent
event EventHandler IShape.OnDraw
{
add
{
lock (objectLock)
{
PostDrawEvent += value;
}
}
remove
{
lock (objectLock)
{
PostDrawEvent -= value;
}
}
}

// For the sake of simplicity this one method


// implements both interfaces.
public void Draw()
{
// Raise IDrawingObject's event before the object is drawn.
PreDrawEvent?.Invoke(this, EventArgs.Empty);

Console.WriteLine("Drawing a shape.");

// Raise IShape's event after the object is drawn.


PostDrawEvent?.Invoke(this, EventArgs.Empty);
}
}
public class Subscriber1
{
// References the shape object as an IDrawingObject
public Subscriber1(Shape shape)
{
IDrawingObject d = (IDrawingObject)shape;
d.OnDraw += d_OnDraw;
}

void d_OnDraw(object sender, EventArgs e)


{
Console.WriteLine("Sub1 receives the IDrawingObject event.");
}
}
// References the shape object as an IShape
public class Subscriber2
{
public Subscriber2(Shape shape)
{
IShape d = (IShape)shape;
d.OnDraw += d_OnDraw;
}

void d_OnDraw(object sender, EventArgs e)


{
Console.WriteLine("Sub2 receives the IShape event.");
}
}

public class Program


{
static void Main(string[] args)
{
Shape shape = new Shape();
Subscriber1 sub = new Subscriber1(shape);
Subscriber2 sub2 = new Subscriber2(shape);
shape.Draw();

// Keep the console window open in debug mode.


System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
}
}
/* Output:
Sub1 receives the IDrawingObject event.
Drawing a shape.
Sub2 receives the IShape event.
*/

Confira também
Eventos
Representantes
Implementação de interface explícita
Como acionar eventos de classe base em classes derivadas

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
Como implementar acessadores de
eventos personalizados (Guia de
Programação em C#)
Artigo • 10/03/2023

Um evento é um tipo especial de delegado multicast que só pode ser invocado de


dentro da classe em que ele está declarado. O código cliente assina o evento ao
fornecer uma referência a um método que deve ser invocado quando o evento for
disparado. Esses métodos são adicionados à lista de invocação do delegado por meio
de acessadores de evento, que se assemelham aos acessadores de propriedade, com a
exceção de que os acessadores de eventos são nomeados add e remove . Na maioria dos
casos, não é necessário fornecer acessadores de eventos personalizados. Quando
nenhum acessador de evento personalizado for fornecido no código, o compilador o
adicionará automaticamente. No entanto, em alguns casos será necessário fornecer um
comportamento personalizado. Um caso desse tipo é mostrado no tópico Como
implementar eventos de interface.

Exemplo
O exemplo a seguir mostra como implementar os acessadores de eventos
personalizados adicionar e remover. Embora seja possível substituir qualquer código
dentro dos acessadores, é recomendável que você bloqueie o evento antes de adicionar
ou remover um novo método de manipulador de eventos.

C#

event EventHandler IDrawingObject.OnDraw


{
add
{
lock (objectLock)
{
PreDrawEvent += value;
}
}
remove
{
lock (objectLock)
{
PreDrawEvent -= value;
}
}
}

Confira também
Eventos
event
Parâmetros de tipo genérico (Guia de
Programação em C#)
Artigo • 13/03/2024

Na definição de um tipo genérico ou método, parâmetros de tipo são um espaço


reservado para um tipo específico que o um cliente especifica ao criar uma instância do
tipo genérico. Uma classe genérica, como GenericList<T> , listada em Introdução aos
Genéricos, não pode ser usada no estado em que se encontra porque não é realmente
um tipo, mas um plano gráfico de um tipo. Para usar GenericList<T> , o código cliente
deve declarar e instanciar um tipo construído, especificando um argumento de tipo
entre colchetes. O argumento de tipo para essa classe específica pode ser qualquer tipo
reconhecido pelo compilador. É possível criar qualquer quantidade de instâncias do tipo
construído, cada uma usando um argumento de tipo diferente, da seguinte maneira:

C#

GenericList<float> list1 = new GenericList<float>();


GenericList<ExampleClass> list2 = new GenericList<ExampleClass>();
GenericList<ExampleStruct> list3 = new GenericList<ExampleStruct>();

Em cada uma dessas instâncias de GenericList<T> , todas as ocorrências de T na classe


são substituídas em tempo de execução com o argumento de tipo. Por meio dessa
substituição, cria-se três objetos separados eficientes e fortemente tipados usando uma
única definição de classe. Para obter mais informações sobre como essa substituição é
executada pelo CLR, confira Genéricos no Runtime.

Você pode aprender as convenções de nomenclatura para parâmetros de tipo genérico


no artigo sobre convenções de nomenclatura.

Confira também
System.Collections.Generic
Genéricos
Diferenças entre modelos C++ e genéricos C#

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
Restrições a parâmetros de tipo (Guia
de Programação em C#)
Artigo • 05/08/2024

Restrições informam o compilador sobre as funcionalidades que um argumento de tipo


deve ter. Sem nenhuma restrição, o argumento de tipo poderia ser qualquer tipo. O
compilador pode assumir somente os membros de System.Object, que é a classe base
definitiva para qualquer tipo .NET. Para obter mais informações, consulte Por que usar
restrições. Se o código do cliente usa um tipo que não satisfaz uma restrição, o
compilador emite um erro. Restrições são especificadas usando a palavra-chave
contextual where . A tabela a seguir lista os sete tipos de restrições:

ノ Expandir a tabela

Constraint Descrição

where T : O argumento de tipo deve ser um tipo de valor não anulável, que inclui os tipos
struct record struct . Para obter informações sobre tipos que permitem valor nulo,
consulte Tipos que permitem valor nulo. Como todos os tipos de valor têm um
construtor sem parâmetros acessível, declarado ou implícito, a restrição struct
implica a restrição new() e não pode ser combinada com a restrição new() . Você
não pode combinar a restrição struct com a restrição unmanaged .

where T : O argumento de tipo deve ser um tipo de referência. Essa restrição se aplica
class também a qualquer classe, interface, delegado ou tipo de matriz. Em um
contexto anulável, T deve ser um tipo de referência não anulável.

where T : O argumento de tipo deve ser um tipo de referência, anulável ou não anulável.
class? Essa restrição se aplica também a qualquer classe, interface, delegado ou tipo de
matriz, incluindo registros.

where T : O argumento de tipo deve ser um tipo não anulável. O argumento pode ser um
notnull tipo de referência não anulável ou um tipo de valor não anulável.

where T : O argumento de tipo deve ser um tipo não gerenciado não anulável. A restrição
unmanaged unmanaged implica a restrição struct e não pode ser combinada com as
restrições struct ou new() .

where T : O argumento de tipo deve ter um construtor público sem parâmetros. Quando
new() usado em conjunto com outras restrições, a restrição new() deve ser a última a
ser especificada. A restrição new() não pode ser combinada com as restrições
restrições struct e unmanaged .

where T : O argumento de tipo deve ser ou derivar da classe base especificada. Em um


<nome da contexto anulável, T deve ser um tipo de referência não anulável derivado da
Constraint Descrição

classe base> classe base especificada.

where T : O argumento de tipo deve ser ou derivar da classe base especificada. Em um


<nome da contexto anulável, T pode ser um tipo anulável ou não anulável derivado da
classe base>? classe base especificada.

where T : O argumento de tipo deve ser ou implementar a interface especificada. Várias


<nome da restrições de interface podem ser especificadas. A interface de restrição também
interface> pode ser genérica. Em um contexto anulável, T deve ser um tipo não anulável
que implementa a interface especificada.

where T : O argumento de tipo deve ser ou implementar a interface especificada. Várias


<nome da restrições de interface podem ser especificadas. A interface de restrição também
interface>? pode ser genérica. Em um contexto anulável, T pode ser um tipo de referência
anulável, um tipo de referência não anulável ou um tipo de valor. T não pode
ser um tipo de valor anulável.

where T : U O argumento de tipo fornecido para T deve ser ou derivar do argumento


fornecido para U . Em um contexto anulável, se U for um tipo de referência não
anulável, T deve ser um tipo de referência não anulável. Se U for um tipo de
referência anulável, T pode ser anulável ou não anulável.

where T : Essa restrição resolve a ambiguidade quando você precisa especificar um


default parâmetro de tipo não treinado ao substituir um método ou fornecer uma
implementação de interface explícita. A restrição default implica o método base
sem a restrição class a struct . Para obter mais informações, consulte a
proposta de especificação de restrição default.

where T : Essa anti-restrição declara que o argumento de tipo para T pode ser um tipo de
allows ref ref struct . O tipo ou método genérico deve obedecer às regras de segurança
struct de ref para qualquer instância de T porque pode ser um ref struct .

Algumas restrições são mutuamente exclusivas e algumas restrições devem estar em


uma ordem especificada:

Você pode aplicar no máximo uma das restrições struct , class , class? , notnull
e unmanaged . Se você fornecer qualquer uma dessas restrições, ela deverá ser a
primeira restrição especificada para esse parâmetro de tipo.
A restrição de classe base ( where T : Base ou where T : Base? ) não pode ser
combinada com nenhuma das restrições struct , class , class? , notnull ou
unmanaged .

Você pode aplicar no máximo uma restrição de classe base, em qualquer um dos
formulários. Se você quiser dar suporte ao tipo base anulável, use Base? .
Você não pode nomear a forma não anulável e anulável de uma interface como
uma restrição.
A restrição new() não pode ser combinada à restrição struct ou unmanaged . Se
você especificar a restrição new() , ela deverá ser a última restrição para esse
parâmetro de tipo. As antirrestrições, se aplicáveis, podem seguir a restrição new() .
A restrição default pode ser aplicada somente em implementações de interface
explícitas ou de substituição. Ela não pode ser combinada com as restrições
struct ou class .

A antirrestrição allows ref struct não pode ser combinada à restrição class ou
class? .

A antirrestrição allows ref struct deve seguir todas as restrições para esse
argumento de tipo.

Por que usar restrições


As restrições especificam os recursos e as expectativas de um parâmetro de tipo.
Declarar essas restrições significa que você pode usar as operações e as chamadas de
método do tipo de restrição. Você aplica restrições ao parâmetro de tipo quando sua
classe ou método genérico usa qualquer operação nos membros genéricos além da
atribuição simples, o que inclui chamar quaisquer métodos sem suporte por
System.Object. Por exemplo, a restrição de classe base informa ao compilador que
somente os objetos desse tipo ou derivados desse tipo podem substituir este
argumento de tipo. Uma vez que o compilador tiver essa garantia, ele poderá permitir
que métodos desse tipo sejam chamados na classe genérica. O exemplo de código a
seguir demonstra a funcionalidade que pode ser adicionada à classe GenericList<T>
(em Introdução aos Genéricos) ao aplicar uma restrição de classe base.

C#

public class Employee


{
public Employee(string name, int id) => (Name, ID) = (name, id);
public string Name { get; set; }
public int ID { get; set; }
}

public class GenericList<T> where T : Employee


{
private class Node
{
public Node(T t) => (Next, Data) = (null, t);

public Node? Next { get; set; }


public T Data { get; set; }
}

private Node? head;


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

public IEnumerator<T> GetEnumerator()


{
Node? current = head;

while (current != null)


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

public T? FindFirstOccurrence(string s)
{
Node? current = head;
T? t = null;

while (current != null)


{
//The constraint enables access to the Name property.
if (current.Data.Name == s)
{
t = current.Data;
break;
}
else
{
current = current.Next;
}
}
return t;
}
}

A restrição permite que a classe genérica use a propriedade Employee.Name . A restrição


especifica que todos os itens do tipo T são um objeto Employee ou um objeto que
herda de Employee .

Várias restrições podem ser aplicadas ao mesmo parâmetro de tipo e as restrições em si


podem ser tipos genéricos, da seguinte maneira:

C#

class EmployeeList<T> where T : notnull, Employee, IComparable<T>, new()


{
// ...
public void AddDefault()
{
T t = new T();
// ...
}
}

Ao aplicar a restrição where T : class , evite os operadores == e != no parâmetro de


tipo, pois esses operadores testam somente a identidade de referência e não a
igualdade de valor. Esse comportamento ocorrerá mesmo se esses operadores forem
sobrecarregados em um tipo usado como argumento. O código a seguir ilustra esse
ponto; a saída é false, muito embora a classe String sobrecarregue o operador == .

C#

public static void OpEqualsTest<T>(T s, T t) where T : class


{
System.Console.WriteLine(s == t);
}

private static void TestStringEquality()


{
string s1 = "target";
System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
string s2 = sb.ToString();
OpEqualsTest<string>(s1, s2);
}

O compilador sabe apenas que T é um tipo de referência no tempo de compilação e


deve usar os operadores padrão válidos para todos os tipos de referência. Caso seja
necessário testar a igualdade de valor, aplique a restrição where T : IEquatable<T> ou
where T : IComparable<T> e implemente a interface em qualquer classe usada para

construir a classe genérica.

Restringindo vários parâmetros


É possível aplicar restrições a vários parâmetros e várias restrições a um único
parâmetro, conforme mostrado no exemplo a seguir:

C#

class Base { }
class Test<T, U>
where U : struct
where T : Base, new()
{ }

Parâmetros de tipo não associado


Os parâmetros de tipo que não têm restrições, como o T na classe pública
SampleClass<T>{} , são denominados “parâmetros de tipo não associado”. Os

parâmetros de tipo não associado têm as seguintes regras:

Os operadores != e == não podem ser usados, pois não há garantia de que o


argumento de tipo concreto oferecerá suporte a eles.
Eles podem ser convertidos para e de System.Object ou explicitamente
convertidos para qualquer tipo de interface.
Você pode compará-los com nulo. Se um parâmetro não associado for comparado
a null , a comparação sempre retornará false se o argumento de tipo for um tipo
de valor.

Parâmetros de tipo como restrições


O uso de um parâmetro de tipo genérico como uma restrição será útil quando uma
função membro com parâmetro de tipo próprio tiver que restringir esse parâmetro para
o parâmetro de tipo do tipo recipiente, conforme mostrado no exemplo a seguir:

C#

public class List<T>


{
public void Add<U>(List<U> items) where U : T {/*...*/}
}

No exemplo anterior, T é uma restrição de tipo no contexto do método Add e um


parâmetro de tipo não associado no contexto da classe List .

Parâmetros de tipo também podem ser usados como restrições em definições de classe
genérica. O parâmetro de tipo deve ser declarado entre colchetes angulares junto com
quaisquer outros parâmetros de tipo:

C#

//Type parameter V is used as a type constraint.


public class SampleClass<T, U, V> where T : V { }
A utilidade dos parâmetros de tipo como restrições com classes genéricas é limitada,
pois o compilador não pode presumir nada sobre o parâmetro de tipo, exceto que ele
deriva de System.Object . Use parâmetros de tipo como restrições em classes genéricas
em cenários nos quais deseja impor uma relação de herança entre dois parâmetros de
tipo.

restrição de notnull
Você pode usar a restrição notnull para especificar que o argumento de tipo deve ser
um tipo de valor não anulável ou um tipo de referência não anulável. Ao contrário da
maioria das outras restrições, se um argumento de tipo violar a restrição notnull , o
compilador gerará um aviso em vez de um erro.

A restrição notnull só tem efeito quando usada em um contexto anulável. Se você


adicionar a restrição notnull em um contexto alheio anulável, o compilador não gerará
avisos ou erros para violações da restrição.

restrição de class
A restrição class em um contexto anulável especifica que o argumento de tipo deve ser
um tipo de referência não anulável. Em um contexto anulável, quando um argumento
de tipo é um tipo de referência anulável, o compilador gera um aviso.

restrição de default
A adição de tipos de referência anuláveis complica o uso de T? em um tipo ou método
genérico. T? pode ser usado com a restrição struct ou class , mas uma deve estar
presente. Quando a restrição class era usada, T? referia-se ao tipo de referência
anulável para T . T? pode ser usado quando nenhuma das restrições é aplicada. Nesse
caso, T? é interpretado como T? para tipos de valor e tipos de referência. No entanto,
se T for uma instância de Nullable<T>, T? será o mesmo que T . Em outras palavras, ele
não se torna T?? .

Como T? agora pode ser usado sem a restrição class ou struct , as ambiguidades
podem surgir em substituições ou implementações de interface explícitas. Em ambos os
casos, a substituição não inclui as restrições, mas as herda da classe base. Quando a
classe base não aplica a restrição class ou struct , as classes derivadas precisam
especificar de alguma forma uma substituição aplicada ao método base sem qualquer
restrição. O método derivado aplica a restrição default . A restrição default não
esclarece a restrição nem a restrição class nem struct .

Restrição não gerenciada


Você pode usar a restrição unmanaged para especificar que o parâmetro de tipo deve ser
um tipo não gerenciado não anulável. A restrição unmanaged permite que você escreva
rotinas reutilizáveis para trabalhar com tipos que podem ser manipulados como blocos
de memória, conforme mostrado no exemplo a seguir:

C#

unsafe public static byte[] ToByteArray<T>(this T argument) where T :


unmanaged
{
var size = sizeof(T);
var result = new Byte[size];
Byte* p = (byte*)&argument;
for (var i = 0; i < size; i++)
result[i] = *p++;
return result;
}

O método anterior deve ser compilado em um contexto unsafe porque ele usa o
operador sizeof em um tipo não conhecido como um tipo interno. Sem a restrição
unmanaged , o operador sizeof não está disponível.

A restrição unmanaged implica a restrição struct e não pode ser combinada ela. Como a
restrição struct implica a restrição new() , a restrição unmanaged não pode ser
combinada com a restrição new() também.

Restrições de delegado
Você pode usar System.Delegate ou System.MulticastDelegate como uma restrição de
classe base. O CLR sempre permitia essa restrição, mas a linguagem C# não a permite. A
restrição System.Delegate permite que você escreva código que funcione com
delegados de uma maneira fortemente tipada. O código a seguir define um método de
extensão que combina dois delegados fornecidos que são do mesmo tipo:

C#

public static TDelegate? TypeSafeCombine<TDelegate>(this TDelegate source,


TDelegate target)
where TDelegate : System.Delegate
=> Delegate.Combine(source, target) as TDelegate;

Você pode usar o método anterior para combinar delegados que são do mesmo tipo:

C#

Action first = () => Console.WriteLine("this");


Action second = () => Console.WriteLine("that");

var combined = first.TypeSafeCombine(second);


combined!();

Func<bool> test = () => true;


// Combine signature ensures combined delegates must
// have the same type.
//var badCombined = first.TypeSafeCombine(test);

Se você remover a marca de comentário na última linha, ela não será compilada. Tanto
first quanto test são tipos de representante, mas são tipos diferentes de

representantes.

Restrições de enum
Você também pode especificar o tipo System.Enum como uma restrição de classe base.
O CLR sempre permitia essa restrição, mas a linguagem C# não a permite. Genéricos
usando System.Enum fornecem programação fortemente tipada para armazenar em
cache os resultados do uso de métodos estáticos em System.Enum . O exemplo a seguir
localiza todos os valores válidos para um tipo enum e, em seguida, cria um dicionário
que mapeia esses valores para sua representação de cadeia de caracteres.

C#

public static Dictionary<int, string> EnumNamedValues<T>() where T :


System.Enum
{
var result = new Dictionary<int, string>();
var values = Enum.GetValues(typeof(T));

foreach (int item in values)


result.Add(item, Enum.GetName(typeof(T), item)!);
return result;
}
Enum.GetValues e Enum.GetName usam reflexão, que tem implicações de desempenho.

Você pode chamar EnumNamedValues para criar uma coleção que é armazenada em cache
e reutilizada, em vez de repetir as chamadas que exigem reflexão.

Você pode usá-lo conforme mostrado no exemplo a seguir para criar uma enum e
compilar um dicionário de seus nomes e valores:

C#

enum Rainbow
{
Red,
Orange,
Yellow,
Green,
Blue,
Indigo,
Violet
}

C#

var map = EnumNamedValues<Rainbow>();

foreach (var pair in map)


Console.WriteLine($"{pair.Key}:\t{pair.Value}");

Argumentos de tipo implementam interface


declarada
Alguns cenários exigem que um argumento fornecido para um parâmetro de tipo
implemente essa interface. Por exemplo:

C#

public interface IAdditionSubtraction<T> where T : IAdditionSubtraction<T>


{
static abstract T operator +(T left, T right);
static abstract T operator -(T left, T right);
}

Esse padrão permite que o compilador C# determine o tipo que contém os operadores
sobrecarregados ou qualquer método static virtual ou static abstract . Ele fornece
a sintaxe para que os operadores de adição e subtração possam ser definidos em um
tipo que contém. Sem essa restrição, os parâmetros e argumentos seriam necessários
para serem declarados como a interface, em vez do parâmetro de tipo:

C#

public interface IAdditionSubtraction<T> where T : IAdditionSubtraction<T>


{
static abstract IAdditionSubtraction<T> operator +(
IAdditionSubtraction<T> left,
IAdditionSubtraction<T> right);

static abstract IAdditionSubtraction<T> operator -(


IAdditionSubtraction<T> left,
IAdditionSubtraction<T> right);
}

A sintaxe anterior exigiria que os implementadores usassem a implementação de


interface explícita para esses métodos. Fornecer a restrição extra permite que a interface
defina os operadores em termos dos parâmetros de tipo. Os tipos que implementam a
interface podem implementar implicitamente os métodos de interface.

Permite ref struct


A antirrestrição allows ref struct declara que o argumento de tipo correspondente
para pode ser um tipo de ref struct. As instâncias desse parâmetro de tipo devem
obedecer às seguintes regras:

Não pode ser demarcado.


Ele participa regras de segurança de ref.
Não é possível usar instâncias em que um tipo de ref struct não é permitido,
como campos static .
As instâncias podem ser marcadas com o modificador scoped .

A cláusula allows ref struct não é herdada. No seguinte código:

C#

class SomeClass<T, S>


where T : allows ref struct
where S : T
{
// etc
}
O argumento para S não pode ser um ref struct porque S não tem a cláusula allows
ref struct .

Um parâmetro de tipo que tem a cláusula allows ref struct não pode ser usado como
um argumento de tipo, a menos que o parâmetro de tipo correspondente também
tenha a cláusula allows ref struct . Essa regra é demonstrada no exemplo a seguir:

C#

public class Allow<T> where T : allows ref struct


{

public class Disallow<T>


{
}

public class Example<T> where T : allows ref struct


{
private Allow<T> fieldOne; // Allowed. T is allowed to be a ref struct

private Disallow<T> fieldTwo; // Error. T is not allowed to be a ref


struct
}

O exemplo anterior mostra que um argumento de tipo que pode ser um tipo de ref
struct não pode ser substituído por um parâmetro de tipo que não pode ser um tipo

de ref struct .

Confira também
System.Collections.Generic
Introdução aos genéricos
Classes genéricas
Restrição new

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
Classes genéricas (Guia de Programação
em C#)
Artigo • 10/05/2023

As classes genéricas encapsulam operações que não são específicas de um determinado


tipo de dados. O uso mais comum das classes genéricas é com coleções, como listas
vinculadas, tabelas de hash, pilhas, filas, árvores e assim por diante. As operações como
adicionar e remover itens da coleção são realizadas basicamente da mesma maneira,
independentemente do tipo de dados que estão sendo armazenados.

Na maioria dos cenários que exigem classes de coleção, a abordagem recomendada é


usar as que são fornecidas na biblioteca de classes do .NET. Para obter mais
informações sobre o uso dessas classes, consulte Coleções genéricas no .NET.

Em geral, você cria classes genéricas iniciando com uma classe concreta existente e
alterando os tipos para parâmetros de tipo, um por vez, até alcançar o equilíbrio ideal
de generalização e usabilidade. Ao criar suas próprias classes genéricas, observe as
seguintes considerações importantes:

Quais tipos generalizar em parâmetros de tipo.

Como uma regra, quanto mais tipos você puder parametrizar, mais flexível e
reutilizável seu código se tornará. No entanto, generalização em excesso poderá
criar um código que seja difícil de ser lido ou entendido por outros
desenvolvedores.

Quais restrições, se houver, aplicar aos parâmetros de tipo (consulte Restrições a


parâmetros de tipo).

Uma boa regra é aplicar o máximo de restrições, de maneira que ainda seja
possível manipular os tipos que você precisa manipular. Por exemplo, se você
souber que a classe genérica é destinada a ser usada apenas com tipos de
referência, aplique a restrição da classe. Isso impedirá o uso não intencional de sua
classe com tipos de valor e permitirá que você use o operador as em T e verificar
se há valores nulos.

Se deve-se levar em consideração o comportamento genérico em subclasses e


classes base.

Como as classes genéricas podem servir como classes base, as mesmas


considerações de design aplicam-se nesse caso, como com as classes não
genéricas. Consulte as regras sobre heranças de classes base genéricas mais
adiante neste tópico.

Se implementar uma ou mais interfaces genéricas.

Por exemplo, se você estiver projetando uma classe que será usada para criar itens
em uma coleção com base em classes genéricas, poderá ser necessário
implementar uma interface como a IComparable<T>, em que T é o tipo de sua
classe.

Para obter um exemplo de uma classe genérica simples, consulte Introdução aos
genéricos.

As regras para parâmetros de tipo e restrições têm várias implicações para o


comportamento de classes genéricas, especialmente em relação à acessibilidade de
membro e herança. Antes de prosseguir, você deve compreender alguns termos. Para
um Node<T>, que é de classe genérica, o código cliente pode fazer referência à classe
especificando um argumento de tipo – para criar um tipo construído fechado
( Node<int> ); ou deixando o parâmetro de tipo não especificado – por exemplo, quando
você especifica uma classe base genérica, para criar um tipo construído aberto
( Node<T> ). As classes genéricas podem herdar de classes base construídas concretas,
fechadas ou abertas:

C#

class BaseNode { }
class BaseNodeGeneric<T> { }

// concrete type
class NodeConcrete<T> : BaseNode { }

//closed constructed type


class NodeClosed<T> : BaseNodeGeneric<int> { }

//open constructed type


class NodeOpen<T> : BaseNodeGeneric<T> { }

As classes não genéricas ou em outras palavras, classes concretas, podem herdar de


classes base construídas fechadas, mas não de classes construídas abertas ou de
parâmetros de tipo, porque não há maneiras de o código cliente fornecer o argumento
de tipo necessário para instanciar a classe base em tempo de execução.

C#

//No error
class Node1 : BaseNodeGeneric<int> { }
//Generates an error
//class Node2 : BaseNodeGeneric<T> {}

//Generates an error
//class Node3 : T {}

As classes genéricas que herdam de tipos construídos abertos devem fornecer


argumentos de tipo para qualquer parâmetro de tipo de classe base que não é
compartilhado pela classe herdeira, conforme demonstrado no código a seguir:

C#

class BaseNodeMultiple<T, U> { }

//No error
class Node4<T> : BaseNodeMultiple<T, int> { }

//No error
class Node5<T, U> : BaseNodeMultiple<T, U> { }

//Generates an error
//class Node6<T> : BaseNodeMultiple<T, U> {}

As classes genéricas que herdam de tipos construídos abertos devem especificar


restrições que são um superconjunto ou sugerem, as restrições no tipo base:

C#

class NodeItem<T> where T : System.IComparable<T>, new() { }


class SpecialNodeItem<T> : NodeItem<T> where T : System.IComparable<T>,
new() { }

Os tipos genéricos podem usar vários parâmetros de tipo e restrições, da seguinte


maneira:

C#

class SuperKeyType<K, V, U>


where U : System.IComparable<U>
where V : new()
{ }

Tipos construídos abertos e construídos fechados podem ser usados como parâmetros
de método:

C#
void Swap<T>(List<T> list1, List<T> list2)
{
//code to swap items
}

void Swap(List<int> list1, List<int> list2)


{
//code to swap items
}

Se uma classe genérica implementa uma interface, todas as instâncias dessa classe
podem ser convertidas nessa interface.

As classes genéricas são invariáveis. Em outras palavras, se um parâmetro de entrada


especifica um List<BaseClass> , você receberá um erro em tempo de compilação se
tentar fornecer um List<DerivedClass> .

Confira também
System.Collections.Generic
Guia de Programação em C#
Genéricos
Salvar o estado de enumeradores
Um enigma de herança, parte 1
Interfaces genéricas (Guia de
Programação em C#)
Artigo • 08/06/2023

Muitas vezes, é útil definir interfaces para classes de coleção genéricas ou para as
classes genéricas que representam itens na coleção. Para evitar operações de conversão
boxe e unboxing em tipos de valor, é melhor usar interfaces genéricas, como
IComparable<T>, em classes genéricas. A biblioteca de classes .NET define várias
interfaces genéricas para uso com as classes de coleção no namespace
System.Collections.Generic. Para obter mais informações sobre essas interfaces, consulte
Interfaces genéricas.

Quando uma interface é especificada como uma restrição em um parâmetro de tipo,


somente os tipos que implementam a interface podem ser usados. O exemplo de
código a seguir mostra uma classe SortedList<T> que deriva da classe GenericList<T> .
Para obter mais informações, consulte Introdução aos Genéricos. SortedList<T>
adiciona a restrição where T : IComparable<T> . Essa restrição habilita o método
BubbleSort em SortedList<T> a usar o método genérico CompareTo em elementos de

lista. Neste exemplo, os elementos de lista são uma classe simples, Person , que
implementa IComparable<Person> .

C#

//Type parameter T in angle brackets.


public class GenericList<T> : System.Collections.Generic.IEnumerable<T>
{
protected Node head;
protected Node current = null;

// Nested class is also generic on T


protected class Node
{
public Node next;
private T data; //T as private member datatype

public Node(T t) //T used in non-generic constructor


{
next = null;
data = t;
}

public Node Next


{
get { return next; }
set { next = value; }
}

public T Data //T as return type of property


{
get { return data; }
set { data = value; }
}
}

public GenericList() //constructor


{
head = null;
}

public void AddHead(T t) //T as method parameter type


{
Node n = new Node(t);
n.Next = head;
head = n;
}

// Implementation of the iterator


public System.Collections.Generic.IEnumerator<T> GetEnumerator()
{
Node current = head;
while (current != null)
{
yield return current.Data;
current = current.Next;
}
}

// IEnumerable<T> inherits from IEnumerable, therefore this class


// must implement both the generic and non-generic versions of
// GetEnumerator. In most cases, the non-generic method can
// simply call the generic method.
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}

public class SortedList<T> : GenericList<T> where T : System.IComparable<T>


{
// A simple, unoptimized sort algorithm that
// orders list elements from lowest to highest:

public void BubbleSort()


{
if (null == head || null == head.Next)
{
return;
}
bool swapped;
do
{
Node previous = null;
Node current = head;
swapped = false;

while (current.next != null)


{
// Because we need to call this method, the SortedList
// class is constrained on IComparable<T>
if (current.Data.CompareTo(current.next.Data) > 0)
{
Node tmp = current.next;
current.next = current.next.next;
tmp.next = current;

if (previous == null)
{
head = tmp;
}
else
{
previous.next = tmp;
}
previous = tmp;
swapped = true;
}
else
{
previous = current;
current = current.next;
}
}
} while (swapped);
}
}

// A simple class that implements IComparable<T> using itself as the


// type argument. This is a common design pattern in objects that
// are stored in generic lists.
public class Person : System.IComparable<Person>
{
string name;
int age;

public Person(string s, int i)


{
name = s;
age = i;
}

// This will cause list elements to be sorted on age values.


public int CompareTo(Person p)
{
return age - p.age;
}

public override string ToString()


{
return name + ":" + age;
}

// Must implement Equals.


public bool Equals(Person p)
{
return (this.age == p.age);
}
}

public class Program


{
public static void Main()
{
//Declare and instantiate a new generic SortedList class.
//Person is the type argument.
SortedList<Person> list = new SortedList<Person>();

//Create name and age values to initialize Person objects.


string[] names = new string[]
{
"Franscoise",
"Bill",
"Li",
"Sandra",
"Gunnar",
"Alok",
"Hiroyuki",
"Maria",
"Alessandro",
"Raul"
};

int[] ages = new int[] { 45, 19, 28, 23, 18, 9, 108, 72, 30, 35 };

//Populate the list.


for (int x = 0; x < 10; x++)
{
list.AddHead(new Person(names[x], ages[x]));
}

//Print out unsorted list.


foreach (Person p in list)
{
System.Console.WriteLine(p.ToString());
}
System.Console.WriteLine("Done with unsorted list");

//Sort the list.


list.BubbleSort();
//Print out sorted list.
foreach (Person p in list)
{
System.Console.WriteLine(p.ToString());
}
System.Console.WriteLine("Done with sorted list");
}
}

Várias interfaces podem ser especificadas como restrições em um único tipo, da


seguinte maneira:

C#

class Stack<T> where T : System.IComparable<T>, IEnumerable<T>


{
}

Uma interface pode definir mais de um parâmetro de tipo, da seguinte maneira:

C#

interface IDictionary<K, V>


{
}

As regras de herança que se aplicam às classes também se aplicam às interfaces:

C#

interface IMonth<T> { }

interface IJanuary : IMonth<int> { } //No error


interface IFebruary<T> : IMonth<int> { } //No error
interface IMarch<T> : IMonth<T> { } //No error
//interface IApril<T> : IMonth<T, U>
{} //Error

Interfaces genéricas poderão herdar de interfaces não genéricas se a interface genérica


for covariante, o que significa que ela usa apenas seu parâmetro de tipo como um valor
retornado. Na biblioteca de classes do .NET, IEnumerable<T> herda de IEnumerable
porque IEnumerable<T> usa apenas T no valor retornado de GetEnumerator e no
getter de propriedade Current.

Classes concretas podem implementar interfaces construídas fechadas, da seguinte


maneira:
C#

interface IBaseInterface<T> { }

class SampleClass : IBaseInterface<string> { }

Classes genéricas podem implementar interfaces genéricas ou interfaces construídas


fechadas, contanto que a lista de parâmetros de classe forneça todos os argumentos
exigidos pela interface, da seguinte maneira:

C#

interface IBaseInterface1<T> { }
interface IBaseInterface2<T, U> { }

class SampleClass1<T> : IBaseInterface1<T> { } //No error


class SampleClass2<T> : IBaseInterface2<T, string> { } //No error

As regras que controlam a sobrecarga de método são as mesmas para métodos em


classes genéricas, structs genéricos ou interfaces genéricas. Para obter mais
informações, consulte Métodos Genéricos.

A partir do C# 11, as interfaces podem declarar membros static abstract ou static


virtual . As interfaces que declaram membros static abstract ou static virtual são

quase sempre interfaces genéricas. O compilador deve resolver chamadas para métodos
static virtual e static abstract em tempo de compilação. Os métodos static

virtual e static abstract declarados em interfaces não têm um mecanismo de


expedição de runtime análogo a métodos virtual ou abstract declarados em classes.
Em vez disso, o compilador usa informações de tipo disponíveis em tempo de
compilação. Normalmente, esses membros são declarados em interfaces genéricas.
Além disso, a maioria das interfaces que declaram os métodos static virtual ou
static abstract declaram que um dos parâmetros de tipo deve implementar a
interface declarada. Em seguida, o compilador usa os argumentos de tipo fornecidos
para resolver o tipo do membro declarado.

Confira também
Guia de Programação em C#
Introdução aos genéricos
interface
Genéricos
Métodos genéricos (Guia de
Programação em C#)
Artigo • 31/03/2024

Um método genérico é um método declarado com parâmetros de tipo, da seguinte


maneira:

C#

static void Swap<T>(ref T lhs, ref T rhs)


{
T temp;
temp = lhs;
lhs = rhs;
rhs = temp;
}

O exemplo de código a seguir mostra uma maneira de chamar o método usando int
para o argumento de tipo:

C#

public static void TestSwap()


{
int a = 1;
int b = 2;

Swap<int>(ref a, ref b);


System.Console.WriteLine(a + " " + b);
}

Também é possível omitir o argumento de tipo e o compilador o inferirá. Esta chamada


para Swap é equivalente à chamada anterior:

C#

Swap(ref a, ref b);

As mesmas regras de inferência de tipos se aplicam a métodos estáticos e métodos de


instância. O compilador pode inferir os parâmetros de tipo com base nos argumentos
de método passados; não é possível inferir os parâmetros de tipo somente de uma
restrição ou valor retornado. Portanto, a inferência de tipos não funciona com métodos
que não têm parâmetros. A inferência de tipos ocorre em tempo de compilação, antes
de o compilador tentar resolver assinaturas de método sobrecarregadas. O compilador
aplica a lógica da inferência de tipos a todos os métodos genéricos que compartilham o
mesmo nome. Na etapa de resolução de sobrecarga, o compilador incluirá somente os
métodos genéricos em que a inferência de tipos foi bem-sucedida.

Em uma classe genérica, métodos não genéricos podem acessar os parâmetros de tipo
de nível de classe, da seguinte maneira:

C#

class SampleClass<T>
{
void Swap(ref T lhs, ref T rhs) { }
}

Se um método genérico que usa os mesmos parâmetros de tipo da classe que o contém
for definido, o compilador gerará um aviso CS0693, pois, dentro do escopo do método,
o argumento fornecido para o T interno oculta o argumento fornecido para o T
externo. Caso seja necessária a flexibilidade de chamar um método de classe genérica
com argumentos de tipo diferentes dos fornecidos quando a instância da classe foi
criada, considere fornecer outro identificador ao parâmetro de tipo do método,
conforme mostrado no GenericList2<T> do exemplo a seguir.

C#

class GenericList<T>
{
// CS0693.
void SampleMethod<T>() { }
}

class GenericList2<T>
{
// No warning.
void SampleMethod<U>() { }
}

Use restrições para permitir operações mais especializadas em parâmetros de tipo de


métodos. Essa versão do Swap<T> , agora denominada SwapIfGreater<T> , pode ser usada
somente com argumentos de tipo que implementam IComparable<T>.

C#

void SwapIfGreater<T>(ref T lhs, ref T rhs) where T : System.IComparable<T>


{
T temp;
if (lhs.CompareTo(rhs) > 0)
{
temp = lhs;
lhs = rhs;
rhs = temp;
}
}

Métodos genéricos podem ser sobrecarregados vários parâmetros de tipo. Por exemplo,
todos os seguintes métodos podem ser localizados na mesma classe:

C#

void DoWork() { }
void DoWork<T>() { }
void DoWork<T, U>() { }

Você também pode usar o parâmetro de tipo como o tipo de retorno de um método. O
exemplo de código a seguir mostra um método que retorna uma matriz de tipo T :

C#

T[] Swap<T>(T a, T b)
{
return [b, a];
}

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

Confira também
System.Collections.Generic
Introdução aos genéricos
Métodos

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  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
Genéricos e matrizes (Guia de
Programação em C#)
Artigo • 13/03/2024

As matrizes unidimensionais que têm um limite inferior a zero implementam IList<T>


automaticamente. Isso permite a criação de métodos genéricos que podem usar o
mesmo código para iterar por meio de matrizes e outros tipos de coleção. Essa técnica é
útil principalmente para ler dados em coleções. A interface IList<T> não pode ser usada
para adicionar ou remover elementos de uma matriz. Uma exceção será lançada se você
tentar chamar um método IList<T> tal como RemoveAt em uma matriz neste contexto.

O exemplo de código a seguir demonstra como um único método genérico que usa um
parâmetro de entrada IList<T> pode iterar por meio de uma lista e uma matriz, nesse
caso, uma matriz de inteiros.

C#

class Program
{
static void Main()
{
int[] arr = [0, 1, 2, 3, 4];
List<int> list = new List<int>();

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


{
list.Add(x);
}

ProcessItems<int>(arr);
ProcessItems<int>(list);
}

static void ProcessItems<T>(IList<T> coll)


{
// IsReadOnly returns True for the array and False for the List.
System.Console.WriteLine
("IsReadOnly returns {0} for this collection.",
coll.IsReadOnly);

// The following statement causes a run-time exception for the


// array, but not for the List.
//coll.RemoveAt(4);

foreach (T item in coll)


{
System.Console.Write(item?.ToString() + " ");
}
System.Console.WriteLine();
}
}

Confira também
System.Collections.Generic
Genéricos
matrizes
Genéricos

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
Delegados genéricos (Guia de
Programação em C#)
Artigo • 13/03/2024

Um delegado pode definir seus próprios parâmetros de tipo. O código que referencia o
delegado genérico pode especificar o argumento de tipo para criar um tipo construído
fechado, assim como quando uma classe genérica é instanciada ou quando um método
genérico é chamado, conforme mostrado no exemplo a seguir:

C#

public delegate void Del<T>(T item);


public static void Notify(int i) { }

Del<int> m1 = new Del<int>(Notify);

A versão 2.0 do C# tem um novo recurso chamado conversão de grupo de método, que
pode ser aplicada a tipos concretos e de delegado genérico e habilita a gravação da
linha anterior com esta sintaxe simplificada:

C#

Del<int> m2 = Notify;

Os delegados definidos em uma classe genérica podem usar os parâmetros de tipo da


classe genérica da mesma forma que os métodos da classe.

C#

class Stack<T>
{
public delegate void StackDelegate(T[] items);
}

O código que referencia o delegado deve especificar o argumento de tipo da classe


recipiente, da seguinte maneira:

C#

private static void DoWork(float[] items) { }

public static void TestStack()


{
Stack<float> s = new Stack<float>();
Stack<float>.StackDelegate d = DoWork;
}

Os delegados genéricos são especialmente úteis na definição de eventos com base no


padrão de design comum, pois o argumento do remetente pode ser fortemente tipado
e não precisa ser convertido de e para Object.

C#

delegate void StackEventHandler<T, U>(T sender, U eventArgs);

class Stack<T>
{
public class StackEventArgs : System.EventArgs { }
public event StackEventHandler<Stack<T>, StackEventArgs>? StackEvent;

protected virtual void OnStackChanged(StackEventArgs a)


{
if (StackEvent is not null)
StackEvent(this, a);
}
}

class SampleClass
{
public void HandleStackChange<T>(Stack<T> stack, Stack<T>.StackEventArgs
args) { }
}

public static void Test()


{
Stack<double> s = new Stack<double>();
SampleClass o = new SampleClass();
s.StackEvent += o.HandleStackChange;
}

Confira também
System.Collections.Generic
Introdução aos genéricos
Métodos genéricos
Classes genéricas
Interfaces genéricas
Representantes
Genéricos
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
Diferenças entre modelos C++ e
genéricos C# (Guia de Programação em
C#)
Artigo • 02/06/2023

Os modelos C++ e genéricos C# são recursos de linguagem que fornecem o suporte


aos tipos parametrizados. No entanto, há várias diferenças entre os dois. No nível de
sintaxe, os genéricos C# são uma abordagem mais simples para os tipos parametrizados
sem a complexidade de modelos C++. Além disso, o C# não tenta fornecer toda a
funcionalidade que os modelos C++ fornecem. No nível da implementação, a principal
diferença é que as substituições do tipo genérico do C# são realizadas em runtime e as
informações do tipo genérico são preservadas para objetos instanciados. Para obter
mais informações, confira Genéricos no runtime.

A seguir estão as principais diferenças entre modelos C++ e genéricos C#:

Os genéricos C# não oferecem a mesma flexibilidade que os modelos C++. Por


exemplo, não é possível chamar os operadores aritméticos em uma classe genérica
C#, embora seja possível chamar operadores definidos pelo usuário.

O C# não permite parâmetros de modelo sem tipo, como template C<int i> {} .

O C# não dá suporte à especialização explícita ou seja, uma implementação


personalizada de um modelo para um tipo específico.

O C# não dá suporte à especialização parcial: uma implementação personalizada


para um subconjunto dos argumentos de tipo.

O C# não permite que o parâmetro de tipo a ser usado como a classe base para o
tipo genérico.

O C# não permite que os parâmetros de tipo tenham tipos padrão.

No C#, um parâmetro de tipo genérico não pode ser genérico, embora os tipos
construídos possam ser usados como genéricos. O C++ permite parâmetros de
modelo.

O C++ permite o código que pode não ser válido para todos os parâmetros de
tipo no modelo, que é então verificado para o tipo específico usado como o
parâmetro de tipo. O C# requer código em uma classe a ser gravada de forma que
ele funcionará com qualquer tipo que satisfaça as restrições. Por exemplo, em C++
é possível escrever uma função que usa os operadores aritméticos + e - em
objetos do parâmetro de tipo, que produzirá um erro no momento da instanciação
do modelo com um tipo que não dá suporte a esses operadores. O C# não
permite isso. Os únicos constructos da linguagem permitidos são os que podem
ser deduzidos das restrições.

Confira também
Guia de Programação em C#
Introdução aos genéricos
Modelos
Genéricos no runtime (Guia de
programação em C#)
Artigo • 24/04/2024

Quando um tipo ou método genérico é compilado na linguagem intermediária comum


(CIL), ele contém metadados que o identificam como tendo parâmetros de tipo. O
modo como a CIL de um tipo genérico é usada difere com base no fato de o parâmetro
de tipo fornecido ser um tipo de valor ou um tipo de referência.

Quando um tipo genérico é construído pela primeira vez com um tipo de valor como
parâmetro, o tempo de execução cria um tipo genérico especializado com o parâmetro
ou os parâmetros fornecidos substituídos nos locais apropriados na CIL. Os tipos
genéricos especializados são criados uma vez para cada tipo de valor único usado como
parâmetro.

Por exemplo, caso o código do programa declare uma pilha construída de inteiros:

C#

Stack<int>? stack;

Neste ponto, o runtime gerará uma versão especializada da classe Stack<T> com o
inteiro substituído corretamente, de acordo com seu parâmetro. Agora, sempre que o
código do programa utilizar uma pilha de inteiros, o runtime reutilizará a classe
especializada Stack<T> gerada. No exemplo a seguir, são criadas duas instâncias de
uma pilha de inteiros e eles compartilham uma única instância do código Stack<int> :

C#

Stack<int> stackOne = new Stack<int>();


Stack<int> stackTwo = new Stack<int>();

No entanto, suponha que outra classe Stack<T> com um tipo de valor diferente – como
long ou uma estrutura definida pelo usuário como parâmetro – foi criada em outro

ponto do código. Como resultado, o runtime gerará outra versão do tipo genérico e
substituirá um long nos locais apropriados na CIL. Conversões não são mais necessárias,
pois cada classe genérica especializada contém o tipo de valor nativamente.

Os genéricos funcionam de outro modo nos tipos de referência. Na primeira vez que
um tipo genérico for construído com qualquer tipo de referência, o runtime criará um
tipo genérico especializado com referências de objeto substituídas pelos parâmetros na
CIL. Em seguida, sempre que um tipo construído for instanciado com um tipo de
referência como parâmetro, independentemente do tipo, o runtime reutilizará a versão
especializada do tipo genérico criada anteriormente. Isso é possível porque todas as
referências são do mesmo tamanho.

Por exemplo, suponha que há dois tipos de referência, uma classe Customer e uma
classe Order e que uma pilha de tipos Customer foi criada:

C#

class Customer { }
class Order { }

C#

Stack<Customer> customers;

Neste ponto, o runtime gerará uma versão especializada da classe Stack<T> que
armazenará referências de objeto que serão preenchidas posteriormente, em vez de
armazenar dados. Suponha que a próxima linha de código crie uma pilha de outro tipo
de referência, com o nome Order :

C#

Stack<Order> orders = new Stack<Order>();

Ao contrário dos tipos de valor, outra versão especializada da classe Stack<T> não será
criada para o tipo Order . Em vez disso, uma instância da versão especializada da classe
Stack<T> será criada e a variável orders será definida para referenciá-la. Imagine que
uma linha de código foi encontrada para criar uma pilha de um tipo Customer :

C#

customers = new Stack<Customer>();

Assim como acontece com o uso anterior da classe Stack<T> criada usando o tipo
Order , outra instância da classe especializada Stack<T> é criada. Os ponteiros contidos

nela são definidos para referenciar uma área de memória do tamanho de um tipo
Customer . Como a quantidade de tipos de referência pode variar muito entre os

programas, a implementação de genéricos no C# reduz significativamente a quantidade


de código ao diminuir para um o número de classes especializadas criadas pelo
compilador para classes genéricas ou tipos de referência.
Além disso, quando uma classe genérica do C# é instanciada usando um tipo de valor
ou parâmetro de tipo de referência, a reflexão pode consultá-la em runtime e o seu tipo
real e parâmetro de tipo podem ser determinados.

Confira também
System.Collections.Generic
Introdução aos genéricos
Genéricos

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
Referência de linguagem C#
A referência de linguagem fornece uma referência informal à sintaxe e expressões
idiomáticas do C# para iniciantes e desenvolvedores experientes de C# e .NET.

Referência de linguagem C#

e VISÃO GERAL

Estratégia de linguagem C#

i REFERÊNCIA

Palavras-chave de C#

Operadores e expressões C#

Configurar versão da linguagem

Especificação da linguagem C# – rascunho do C# 8 em andamento

Novidades

h NOVIDADES

O que há de novo no C# 13

Novidades do C# 12

Novidades do C# 11

Novidades do C# 10

i REFERÊNCIA

Alterações de falha no compilador C#

Compatibilidade de versões

Mantenha contato

i REFERÊNCIA
.NET Developer Community

YouTube

Twitter
Especificações
Leia as especificações detalhadas da linguagem C# e as especificações detalhadas dos
recursos mais recentes.

Especificações ECMA e recursos mais recentes

e VISÃO GERAL

Processo de especificação

Conteúdo detalhado da especificação ECMA

i REFERÊNCIA

Especificações de recursos mais recentes

Rascunho da especificação C# ECMA – material introdutório

i REFERÊNCIA

Prefácio

Introdução

i REFERÊNCIA

Escopo

Referências normativas

Termos e definições

Descrição geral

Compatibilidade

Rascunho da especificação C# ECMA – Especificação da linguagem

i REFERÊNCIA

Estrutura lexical
Conceitos básicos

Tipos

Variáveis

Conversões

Padrões

Expressões

Instruções

i REFERÊNCIA

Namespaces

Classes

Estruturas

matrizes

Interfaces

Enums

Delegados

Exceções

Atributos

Código não seguro

Rascunho da especificação C# ECMA - Anexos

i REFERÊNCIA

Gramática

Problemas de portabilidade

Biblioteca padrão

Comentários de documentação

Bibliografia

Você também pode gostar