dotnet-csharp
dotnet-csharp
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
q VIDEO
g TUTORIAL
Tutoriais autoguiados
Tutorial no navegador
i REFERÊNCIA
C# no Q&A
C# no Stack Overflow
C# no Discord
Princípios básicos do C#
e VISÃO GERAL
Um tour pelo C#
p CONCEITO
Sistema de tipos
Programação orientada a objeto
Técnicas funcionais
Exceções
Estilo de codificação
g TUTORIAL
Introdução às classes
C# orientado a objeto
Converter tipos
Correspondência de padrões
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
i REFERÊNCIA
Compatibilidade de versões
Conceitos principais
e VISÃO GERAL
Estratégia de linguagem C#
Conceitos de programação
p CONCEITO
Programação assíncrona
Conceitos avançados
i REFERÊNCIA
Reflexão e atributos
Árvores de expressão
Interoperabilidade nativa
Engenharia de desempenho
Mantenha contato
i REFERÊNCIA
YouTube
Twitter
Um tour pela linguagem C#
Artigo • 09/05/2024
Hello world
O programa "Hello, World" é usado tradicionalmente para introduzir uma linguagem de
programação. Este é para C#:
C#
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#.
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.
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.
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#
C#
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#
C#
C#
C#
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:
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:
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:
Por fim, há recursos de linguagem Java que não estão disponíveis em C#:
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.
À 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:
À medida que você aprende mais, outras diferenças se tornam aparentes, mas muitas
dessas diferenças são menores no escopo.
Além disso, mais alguns recursos do TypeScript não estão disponíveis em C#:
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.
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.
"mantendo a administraçã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.
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
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:
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
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.
Comentários
Esta página foi útil? Yes No
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.
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:
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.
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.
CLI do .NET
) Importante
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);
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);
Dica
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);
}
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.
C#
int a = 5;
int b = 4;
int c = 2;
int d = a + b * c;
Console.WriteLine(d);
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.
C#
int e = 7;
int f = 4;
int g = 3;
int h = (e + f) / g;
Console.WriteLine(h);
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);
}
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#
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
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 .
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);
C#
C#
Você sabe que 0.3 repetido um número finito de vezes não é exatamente o mesmo que
1/3 .
Desafio
C#
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 .
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.
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.
CLI do .NET
) Importante
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
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.
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");
) Importante
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
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
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 && 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");
}
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");
}
}
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.
C#
int counter = 0;
while (counter < 10)
{
Console.WriteLine($"Hello World! The counter is {counter}");
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 é
C#
int counter = 0;
do
{
Console.WriteLine($"Hello World! The counter is {counter}");
counter++;
} while (counter < 10);
C#
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.
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.
C#
C#
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() .
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 .
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
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.
) Importante
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#
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.
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#
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#
C#
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");
names.Sort();
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}
}
C#
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#
fibonacciNumbers.Add(previous + previous2);
Dica
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
C#
// A skeleton of a C# program
using System;
namespace YourNamespace
{
class YourClass
{
}
struct YourStruct
{
}
interface IYourInterface
{
}
enum YourEnum
{
}
namespace YourNestedNamespace
{
struct YourStruct
{
}
}
}
// A skeleton of a C# program
using System;
namespace YourNamespace
{
class YourClass
{
}
struct YourStruct
{
}
interface IYourInterface
{
}
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
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;
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
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().
C#
ノ Expandir a tabela
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
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#
Você pode criar o aplicativo usando o comando dotnet build da CLI do dotnet.
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
C#
class AsyncMainReturnValTest
{
public static int Main()
{
return AsyncConsoleWork().GetAwaiter().GetResult();
}
C#
class Program
{
static async Task<int> Main(string[] args)
{
return await AsyncConsoleWork();
}
Main(args).GetAwaiter().GetResult();
static Task<int> Main() resulta no compilador emitindo o equivalente a private
Main(args).GetAwaiter().GetResult();
7 Observação
ノ Expandir a tabela
static void Main(string[] args) Nenhum valor retornado, nenhum uso de await
static async Task Main(string[] args) Nenhum valor retornado, usa await
ノ Expandir a tabela
7 Observação
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#
Também é possível usar o tipo long de C#, que funciona como alias de Int64 :
C#
Você também pode usar o método da classe Convert , o ToInt64 , para fazer a mesma
coisa:
C#
Dica
C#
public class Functions
{
public static long Factorial(int n)
{
// Test for invalid input.
if ((n < 0) || (n > 20))
{
return -1;
}
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;
}
// 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.
dotnet build
dotnet run -- 3
7 Observação
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
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
diretamente.
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.
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;
Console.WriteLine(builder.ToString());
Namespace global
As instruções de nível superior estão implicitamente no namespace global.
C#
MyClass.TestMethod();
MyNamespace.MyClass.MyMethod();
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#
string? s = Console.ReadLine();
ノ Expandir a tabela
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#.
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
7 Observação
Desenvolvedores de C e C++, observem que, em C#, bool não é conversível em
int .
C#
// Declaration only:
float temperature;
string name;
MyClass myClass;
C#
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.
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
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 ).
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#
Mas você declara e atribui valores a eles como se fossem tipos de não agregação
simples:
C#
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#
Para obter mais informações sobre structs, consulte Tipos de estrutura. Para saber mais
sobre os tipos de valor, confira Tipos de valor.
C#
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#
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#
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.
C#
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#
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.
C#
C#
object anotherMessage = "This is another string of characters";
IEnumerable<char> someCharacters = "abcdefghijklmnopqrstuvwxyz";
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
C#
System.Console.WriteLine("Hello World!");
exemplo a seguir:
C#
using System;
C#
Console.WriteLine("Hello World!");
) Importante
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");
}
}
}
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.
Especificação da linguagem C#
Para saber mais, confira a seção Namespaces da Especificação da linguagem C#.
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 another object of the same type, assigning it the value of the
first object.
MyClass mc2 = mc;
Declarando classes
As classes são declaradas usando a palavra-chave class , seguida por um identificador
exclusivo, conforme mostrado no exemplo a seguir:
C#
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#
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;
C#
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:
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#
Você pode exigir que os chamadores forneçam um valor inicial definindo um construtor
responsável por definir esse valor inicial:
C#
C#
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#
C#
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#
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
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.
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#
C#
person1.PhoneNumbers[0] = "555-1234";
Console.WriteLine(person1 == person2); // output: True
O seguinte exemplo demonstra o uso de uma expressão with para copiar um objeto
imutável e alterar uma das propriedades:
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#.
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.
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.
C#
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
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
C#
C#
// constructor
public GenericList()
{
head = null;
}
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>();
7 Observaçã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
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#
// 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);
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
C#
var productQuery =
from prod in products
select new { prod.Color, prod.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#
C#
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.
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#
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.
7 Observação
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
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.
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.
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.
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:
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
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 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#
class Program
{
static void Main()
{
Person person1 = new Person("Leopold", 6);
Console.WriteLine("person1 Name = {0} Age = {1}", person1.Name,
person1.Age);
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;
7 Observação
C#
if (p2.Equals(p1))
Console.WriteLine("p2 and p1 have the same values.");
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
7 Observação
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.
C#
//Properties.
protected int ID { get; set; }
protected string Title { get; set; }
protected string Description { get; set; }
protected TimeSpan jobLength { get; set; }
// 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;
}
C#
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.
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");
}
}
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()
};
Em C#, cada tipo é polimórfico porque todos os tipos, incluindo tipos definidos pelo
usuário, herdam de Object.
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:
C#
C#
BaseClass A = B;
A.DoWork(); // Also calls the new method.
C#
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#
BaseClass A = (BaseClass)B;
A.DoWork(); // Calls the old method.
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 .
C#
7 Observação
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#
C#
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#
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.
C#
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#
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#
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#
C#
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#
C#
C#
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.
Saída
É 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#
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.
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
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#
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.
C#
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; }
Para obter mais informações sobre desconstruir tipos definidos pelo usuário com
descartes, consulte Desconstruindo tuplas e outros tipos.
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#
C#
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#
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#
C#
7 Observação
_ 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:
C#
C#
C#
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
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#
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#
C#
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#
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");
Por fim, você pode desconstruir a tupla em variáveis que já foram declaradas.
C#
C#
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, _ .
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;
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#
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 void Deconstruct(out string fname, out string mname, out string
lname)
{
fname = FirstName;
mname = MiddleName;
lname = LastName;
}
C#
C#
using System;
using System.Collections.Generic;
using System.Reflection;
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";
}
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
C#
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#
C#
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
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#
try
{
result = SafeDivision(a, b);
Console.WriteLine("{0} divided by {1} = {2}", a, b, result);
}
catch (DivideByZeroException)
{
Console.WriteLine("Attempted divide by zero.");
}
}
}
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
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#
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
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");
}
}
}
C#
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:
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.
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.
}
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.
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#
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#
C#
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
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#
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.
C#
C#
C#
7 Observação
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.
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.
if (toastTime < 1)
{
throw new ArgumentException(
"Toast time is too short.", nameof(toastTime));
}
Console.WriteLine("Toast is ready!");
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
Exceção Descrição
ArithmeticException Uma classe base para exceções que ocorrem durante operações
aritméticas, tais como DivideByZeroException e
OverflowException.
Confira também
Instruções para manipulação de exceções
Regras e convenções de nomenclatura
do identificador C#
Artigo • 04/02/2024
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:
) Importante
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.
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
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#
C#
C#
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#
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#
// 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#
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#
Dica
Ao trabalhar com os campos static que são private ou internal , use o prefixo s_ e,
para thread estático, use t_ .
C#
[ThreadStatic]
private static TimeSpan t_timeSpan;
}
C#
Para obter mais informações sobre convenções de nomenclatura em C#, confira o estilo
de codificação da equipe do .NET Runtime .
./snippets/coding-conventions
./snippets/coding-conventions
./snippets/coding-conventions
C#
) Importante
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.
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:
C#
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#
C#
Delegados
Use Func<> e Action<>, em vez de definir tipos delegados. Em uma classe, defina
o método delegado.
C#
C#
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#
C#
C#
C#
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 .
C#
C#
C#
Operadores && e ||
Use && em vez de & e || em vez de | quando executar comparações, conforme
mostrado no exemplo a seguir.
C#
Operador new
Use uma das formas concisas de instanciação de objeto, conforme mostrado nas
declarações a seguir.
C#
C#
C#
C#
var thirdExample = new ExampleClass { Name = "Desktop", ID = 37414,
Location = "Redmond", Age = 2.3 };
C#
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());
};
}
C#
public Form1()
{
this.Click += new EventHandler(Form1_Click);
}
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#
C#
var localDistributors =
from customer in customers
join distributor in distributors on customer.City equals
distributor.City
select new { Customer = customer, Distributor = distributor };
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#
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#
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#
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#
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#
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#).
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.
C#
7 Observação
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.
C#
using Azure;
namespace CoolStuff.AwesomeFeature
{
public class Awesome
{
public void Stuff()
{
WaitUntil wait = WaitUntil.Completed;
// ...
}
}
}
C#
namespace CoolStuff.AwesomeFeature
{
using Azure;
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; }
}
}
CoolStuff.AwesomeFeature.Azure
CoolStuff.Azure
Azure
C#
namespace CoolStuff.AwesomeFeature
{
using global::Azure;
Diretrizes de estilo
Em geral, use o seguinte formato para exemplos de código:
Estilo de comentário
Use comentários de linha única ( // ) para breves explicações.
C#
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:
C#
Segurança
Siga as diretrizes em Diretrizes de codificação segura.
Como exibir argumentos de linha de
comando
Artigo • 08/06/2023
executável.exe a b c "a"
"b"
"c"
"dois"
"three"
7 Observação
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#
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#
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.
C#
namespace Classes;
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 é
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.
C#
C#
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
using Classes;
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.
C#
C#
this.Number = accountNumberSeed.ToString();
accountNumberSeed++;
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;
C#
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#
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.
C#
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#
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#
C#
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#
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.
C#
Console.WriteLine(account.GetAccountHistory());
Próximas etapas
Se você não conseguir avançar, veja a origem deste tutorial em nosso repositório
GitHub .
Instruções de seleção
Instruções de iteração
Programação orientada a objeto (C#)
Artigo • 08/06/2023
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.
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#
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.
C#
C#
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#
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
C#
C#
C#
C#
C#
C#
Console
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#
Owner = name;
_minimumBalance = minimumBalance;
if (initialBalance > 0)
MakeDeposit(initialBalance, DateTime.Now, "Initial balance");
}
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)
C#
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#
C#
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#
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#
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
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.
2. Insira o comando dotnet new console no prompt de comando para criar um novo
projeto do .NET Core.
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 .
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 .
Nem todos os membros de uma classe base são herdados por classes derivadas. Os
membros a seguir não são herdados:
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;
// }
}
C#
public class A
{
public void Method1()
{
// Method implementation.
}
}
public class B : A
{ }
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#
C#
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#
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;
A herança implícita da classe Object torna esses métodos disponíveis para a classe
SimpleClass :
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 .
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
C#
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.
classe Object
7 Observação
C#
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;
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;
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 é
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
C#
Type = type;
}
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#
Title é uma propriedade String somente leitura cujo valor é fornecido pela
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 .
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;
Author = author;
}
if (currency.Length != 3)
throw new ArgumentException("The ISO currency symbol is a 3-
character string.");
Currency = currency;
return oldValue;
}
Dois construtores
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#
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#
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;
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;
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.
C#
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 { }
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());
}
}
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#
double d = 9.78654;
UseAsWithNullable(d);
class SuperNova { }
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.
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; }
}
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.
using System;
using CommercialRegistration;
using ConsumerVehicleRegistration;
using LiveryRegistration;
namespace Calculators;
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.
C#
using System;
using CommercialRegistration;
using ConsumerVehicleRegistration;
using LiveryRegistration;
using toll_calculator;
var tollCalc = new TollCalculator();
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.
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.
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.
C#
vehicle switch
{
// ...
// ...
};
vehicle switch
{
// ...
// ...
};
C#
vehicle switch
{
// ...
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,
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#
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.
C#
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 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#
Esse método está correto, mas é redundante. Para simplificar, faça conforme mostrado
no código a seguir:
C#
Depois, adicione uma função semelhante para categorizar o tempo nos blocos:
C#
C#
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#
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#
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#
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
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#
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
C#
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#
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
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 .
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
C#
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 .
Matrizes embutidas: introduzidas no Visual Studio 2022 versão 17.7 versão prévia
3.
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.
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
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.
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:
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]];
C#
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.
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
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.
Saiba mais sobre parâmetros padrão em expressões lambda no artigo sobre expressões
lambda.
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;
}
C#
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.
Por exemplo:
<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);Microsoft.AspN
etCore.Http.Generated;MyLibrary.Generated</InterceptorsPreviewNamespaces>
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.
Confira também
Novidades do .NET 8
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
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;
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 { }
C#
[GenericAttribute<string>()]
public string Method() => default;
C#
dynamic
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:
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
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.
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.
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).
C#
C#
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.
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.
Você pode adicionar o modificador scoped a qualquer declaração ref . Isso limita o
escopo para o qual a referência pode escapar.
Confira também
Novidades do .NET 7
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
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
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:
C#
namespace MyNamespace;
Essa nova sintaxe salva espaço horizontal e vertical para declarações namespace .
C#
{ Prop1.Prop2: pattern }
C#
C#
// Initialization:
(int x, int y) = point;
// assignment:
int x1 = 0;
int y1 = 0;
(x1, y1) = point;
C#
int x = 0;
(x, int y) = point;
C#
// 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.
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#
Você pode saber mais sobre esse recurso no artigo sobre Atributos de informações do
chamador na seção de referência de linguagem.
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
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
C# versão 11
Lançado em novembro de 2022
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
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:
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.
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
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.
C#
Com parênteses opcionais para deixar claro que and tem precedência maior que or :
C#
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 .
Desempenho e interoperabilidade
Outro conjunto de aprimoramentos dá suporte a cenários em que os geradores de
código adicionam funcionalidade:
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#:
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.
C# versão 7.2
Lançado em novembro de 2017
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.
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
Descartes
Literais binários e os separadores de dígito
Expressões throw
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
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)
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
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
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
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.
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
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.
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.
7 Observação
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#
C#
Uma alteração interruptiva de origem exige que os usuários alterem o código antes de
recompilarem. Por exemplo, pense neste tipo:
C#
C#
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.
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.
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
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:
Essas regras são iguais aos parâmetros de qualquer método, incluindo outras
declarações de construtor.
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#
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#
C#
C#
É 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#
C#
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#
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
C#
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#
C#
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
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 .
C#
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#
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#
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() {}
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#
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.
C#
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#
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#
C#
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
C#
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,
C#
using System.Numerics;
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#
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
C#
Agora que você adicionou essa restrição em Translation<T> , precisa adicionar a mesma
restrição para Point<T> :
C#
using System.Numerics;
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
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 .
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#
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#
canalGate.SetLowGate(open: true);
Console.WriteLine($"Open the lower gate: {canalGate}");
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}");
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}");
canalGate.SetLowGate(open: false);
Console.WriteLine($"Close the lower gate: {canalGate}");
C#
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.
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
};
último braço da expressão. Essa condição gera uma exceção, pois indica uma entrada
inválida:
C#
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#
Execute os testes novamente e eles serão aprovados. Esta é a versão final do método
SetHighGate :
C#
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#
C#
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
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.
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.
Implementação inicial
Vamos começar de uma classe básica Logger que dá suporte a diferentes níveis:
C#
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.
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) .
C#
[InterpolatedStringHandler]
public ref struct LogInterpolatedStringHandler
{
// Storage for the built-up string
StringBuilder builder;
builder.Append(s);
Console.WriteLine($"\tAppended the literal string");
}
builder.Append(t?.ToString());
Console.WriteLine($"\tAppended the formatted object");
}
Agora você pode adicionar uma sobrecarga a LogMessage na classe Logger para
experimentar o novo manipulador de cadeia de caracteres interpolado:
C#
Você pode verificar se o novo manipulador é invocado usando o seguinte código como
o programa principal:
C#
PowerShell
Rastreando a saída, você pode ver como o compilador adiciona código para chamar o
manipulador e criar a cadeia de caracteres:
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.
C#
C#
builder.Append(s);
Console.WriteLine($"\tAppended the literal string");
}
builder.Append(t?.ToString());
Console.WriteLine($"\tAppended the formatted object");
}
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
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.
C#
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#
Execute o exemplo agora e você verá que, para a mensagem Trace , apenas o primeiro
AppendLiteral é chamado:
PowerShell
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#
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
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
tipos.
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.
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:
C#
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#
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#
C#
Execute seu aplicativo e você verá uma saída semelhante à exibição a seguir (várias
linhas removidas por espaço):
.NET CLI
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#
C#
.NET CLI
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 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);
C#
Você declara um PrintMembers método no DegreeDays registro que não imprime o tipo
da coleção:
C#
exemplo anterior, você perderia as informações sobre onde o registro estava medindo
graus-dias de aquecimento ou resfriamento.
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#
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#
C#
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.
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#
) Importante
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.
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#.
CLI do .NET
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[]
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.
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#
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?
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#
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.
C#
Console.WriteLine();
foreach(var s in args)
{
Console.Write(s);
Console.Write(' ');
}
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.",
];
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();
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#
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.",
];
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.
Intervalos e índices 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:
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#
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#
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#
C#
Você também pode declarar intervalos ou índices como variáveis. A variável então pode
ser usada dentro dos caracteres [ e ] :
C#
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#
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>.
C#
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
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
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],
];
Em todos os casos, o operador de intervalo para Array aloca uma matriz para armazenar
os elementos retornados.
C#
int[] sequence = Sequence(1000);
Por exemplo:
C#
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
" 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.
O código que você gravará para este exemplo expressa essa intenção e o compilador a
aplica.
XML
<Nullable>enable</Nullable>
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:
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.
C#
namespace NullableIntroduction
{
public class SurveyQuestion
{
}
}
C#
namespace NullableIntroduction
{
public enum QuestionType
{
YesNo,
Number,
Text
}
C#
namespace NullableIntroduction;
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>();
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#
C#
surveyRun.AddQuestion(QuestionType.Text, default);
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; }
Em seguida, adicione um método static para criar novos participantes ao gerar uma ID
aleatória:
C#
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).
C#
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 .
C#
A última etapa para executar a pesquisa é adicionar uma chamada para executar a
pesquisa no final do método Main :
C#
surveyRun.PerformSurvey(50);
C#
Em seguida, adicione esses três membros com corpo de expressão à classe SurveyRun :
C#
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.
C#
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 .
Próximas etapas
Saiba como usar o tipo de referência anulável ao trabalhar com o Entity Framework:
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.
CLI do .NET
Esse comando cria um novo aplicativo de console .NET Core no diretório atual.
C#
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:
C#
C#
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;
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.
C#
C#
using System;
using System.Collections.Generic;
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.
C#
Console.WriteLine($"|{"Author",25}|{"Title",30}|");
foreach (var title in titles)
Console.WriteLine($"|{title.Key,25}|{title.Value,30}|");
C#
Console
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.
C#
{<interpolationExpression>:<formatString>}
C#
C#
{<interpolationExpression>,<alignment>}
C#
{<interpolationExpression>,<alignment>:<formatString>}
C#
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.
C#
A partir do C# 11, você pode usar literais de cadeia de caracteres bruta e interpolada.
C#
var rand = new Random();
for (int i = 0; i < 7; i++)
{
Console.WriteLine($"Coin flip: {(rand.NextDouble() < 0.5 ? "heads" :
"tails")}");
}
C#
C#
Como mostra o exemplo, você pode usar uma instância FormattableString para gerar
várias cadeias de caracteres de resultado para várias culturas.
C#
C#
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á:
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.
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.
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;
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 .
C#
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.
C#
Execute o programa (usando dotnet run , e você poderá ver todas as linhas impressa no
console).
C#
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#
C#
await ShowTeleprompter();
C#
Saiba mais sobre o método async Main em nossa seção de conceitos básicos.
C#
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;
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#
C#
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.
C#
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.
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.
CLI do .NET
CLI do .NET
cd WebAPIClient
CLI do .NET
dotnet run
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.
C#
await ProcessRepositoriesAsync();
Esse código:
C#
using System.Net.Http.Headers;
await ProcessRepositoriesAsync(client);
Esse código:
C#
Console.Write(json);
}
Esse código:
CLI do .NET
dotnet run
C#
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.
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#
3. Adicione código para exibir o nome de cada repositório. Substitua as linhas que
mostram:
C#
Console.Write(json);
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;
Esse código:
C#
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.
C#
C#
O compilador gera o objeto Task<T> para o calor de retorno porque você marcou
esse método como async .
C#
4. Execute o aplicativo.
A saída é a mesma.
C#
using System.Text.Json.Serialization;
C#
3. Execute o aplicativo.
2016-02-08T21:27:00Z
Para obter uma data e hora representadas em seu fuso horário, você precisa escrever
um método de conversão personalizado.
C#
using System.Text.Json.Serialization;
C#
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");
4. Execute o aplicativo.
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:
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.
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.
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
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 };
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#
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 };
// 52 cards in a deck, so 52 / 2 = 26
var top = startingDeck.Take(26);
var bottom = startingDeck.Skip(26);
}
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
}
}
}
C#
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.
C#
public static IEnumerable<T> InterleaveSequenceWith<T>
(this IEnumerable<T> first, IEnumerable<T> second)
{
var firstIter = first.GetEnumerator();
var secondIter = second.GetEnumerator();
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 };
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#
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
C#
// Program.cs
static void Main(string[] args)
{
// Query for building the deck
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));
} 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.
C#
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;
// 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");
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");
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.
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();
times++;
Console.WriteLine(times);
} while (!startingDeck.SequenceEquals(shuffle));
Console.WriteLine(times);
}
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.
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!
C#
// 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.
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.
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.
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:
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
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.
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#
// 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 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#
C#
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.
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.
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;
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#
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.
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
ノ Expandir a tabela
Aggregate TSource X
All Boolean X
Any Boolean X
AsEnumerable IEnumerable<T> 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
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
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
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.
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.
ToList
ToArray
ToDictionary
ToLookup
C#
IEnumerable<int> queryFactorsOfFour =
from num in numbers
where num % 4 == 0
select num;
// 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)
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.
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;
C#
IEnumerable<string> highScoresQuery2 =
from score in scores
where score > 80
orderby score descending
select $"The score is {score}";
C#
var highScoreCount = (
from score in scores
where score > 80
select score
).Count();
C#
IEnumerable<int> highScoresQuery3 =
from score in scores
where score > 80
select score;
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
C#
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
// Output: 93 90 82 82
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;
// 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();
C#
var largeCitiesList = (
from country in countries
from city in country.Cities
where city.Population > 10000
select city
).ToList();
C#
var queryCities =
from city in cities
where city.Population > 100000
select city;
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;
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];
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
};
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.
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#
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;
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;
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
};
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#
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
C#
//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);
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>.
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.
7 Observação
C#
// 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:
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.
C#
// Query #4.
double average = numbers1.Average();
// Query #5.
IEnumerable<int> concatenationQuery = numbers1.Concat(numbers2);
C#
// Query #6.
IEnumerable<int> largeNumbersQuery = numbers2.Where(c => c > 15);
Cada uma das consultas anteriores pode ser escrita usando digitação implícita com
`var``, conforme mostrado no exemplo a seguir:
C#
C#
// Query #7.
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#
C#
C#
var queryNames =
from student in students
where ids.Contains(student.ID)
select new
{
student.LastName,
student.ID
};
/* Output:
Garcia: 114
O'Donnell: 112
Omelchenko: 111
*/
/* 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#
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
*/
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
};
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 };
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#
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#
// Data source.
string[] files = ["fileA.txt", "fileB.txt", "fileC.txt"];
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
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.
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 .
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.
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
C#
IEnumerable<Customer> customerQuery =
from cust in customers
where cust.City == "London"
select cust;
C#
var customerQuery2 =
from cust in customers
where cust.City == "London"
select cust;
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.
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#
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#
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#
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#
A partir do C# 12, você pode usar uma expressão de coleção para inicializar uma
coleção.
C#
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.
C#
C#
C#
C#
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;
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
Or, if you prefer Visual Studio, create a new console application named
WalkthroughWritingLinqQueries.
C#
namespace WalkthroughWritingLinqQueries;
public record Student(string First, string Last, int ID, int[] Scores);
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#
Replace it with the following code that creates a sequence of Student records:
C#
using WalkthroughWritingLinqQueries;
Try adding a few more students with different test scores to the list of students to get
more familiar with the code so far.
C#
The query's range variable, student , serves as a reference to each Student in the source,
providing member access for each object.
C#
// 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#
Try the preceding where clause, or experiment yourself with other filter conditions. For
more information, see where clause.
C#
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#
Change the WriteLine format string so that you can see the scores:
C#
C#
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#
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#
For more information about var , see Implicitly Typed Local Variables.
C#
var studentQuery4 =
from student in students
group student by student.Last[0] into studentGroup
orderby studentGroup.Key
select studentGroup;
// 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#
// Output:
// Omelchenko, Svetlana
// O'Donnell, Claire
// Mortensen, Sven
// Garcia, Cesar
// Fakhouri, Fadi
// Feng, Hanying
// Garcia, Hugo
// Adams, Terry
// Zabokritski, Eugene
// Tucker, Michael
C#
var studentQuery =
from student in students
let totalScore = student.Scores[0] + student.Scores[1] +
student.Scores[2] + student.Scores[3]
select totalScore;
// Output:
// Class average score = 334.166666666667
C#
IEnumerable<string> studentQuery =
from student in students
where student.Last == "Garcia"
select student.First;
// 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 };
// 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
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(' ');
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#
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.
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:
ノ Expand table
Method C# query
expression syntax
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.)
(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.)
(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.)
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#
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>
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#
The equivalent query using method syntax is shown in the following code:
C#
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
) Importante
ノ Expandir a tabela
C#
the
fox
*/
C#
IEnumerable<string> query =
words.Where(word => word.Length == 3);
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#)
) Importante
Métodos
ノ Expandir a tabela
C#
a
a
a
d
*/
C#
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#
an
apple
a
day
the
quick
brown
fox
*/
C#
an
apple
a
day
the
quick
brown
fox
*/
C#
C#
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.
C#
C#
) 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#
C#
Assim como a sobrecarga anterior, o método Zip projeta uma tupla, mas desta vez com
três elementos.
C#
A ilustração mostra como Select retorna uma coleção que tem o mesmo número de
elementos que a coleção de origem.
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; }
}
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
) Importante
ノ Expandir a tabela
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#
C#
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#
C#
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
];
A matriz teachers é filtrada apenas para os professores que não estão na matriz
teachersToExclude .
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
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#
C#
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#
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#
Confira também
System.Linq
Como localizar a diferença de conjunto entre duas listas (LINQ) (C#)
) Importante
Métodos
ノ Expandir a tabela
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#
C#
C#
C#
C#
C#
IEnumerable<(string, string)> query = from teacher in teachers
orderby teacher.City, teacher.Last
select (teacher.Last, teacher.City);
C#
C#
C#
IEnumerable<(string, string)> query = teachers
.OrderBy(teacher => teacher.City)
.ThenByDescending(teacher => teacher.Last)
.Select(teacher => (teacher.Last, teacher.City));
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
) Importante
ノ Expandir a tabela
Tudo
O exemplo a seguir usa o All para localizar alunos que obtiveram pontuação acima de
70 em todos os exames.
C#
Qualquer
O exemplo a seguir usa Any para encontrar alunos com pontuação superior a 95 em
qualquer exame.
C#
Contém
O exemplo a seguir usa o Contains para encontrar alunos que obtiveram exatamente 95
pontos em um exame.
C#
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
) Importante
Operadores
ノ Expandir a tabela
Nomes de Descrição Sintaxe de Mais informações
método expressão de
consulta em C#
Use o método Take para obter apenas os primeiros elementos de uma sequência:
C#
O método Skip é usado para ignorar os primeiros elementos de uma sequência e usar
os elementos restantes:
C#
C#
C#
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
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
) Importante
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#
from string
str in words
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#
C#
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
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.
Os exemplos a seguir neste artigo usam fontes de dados comuns para esta área:
C#
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#
A consulta anterior pode ser expressa usando a sintaxe do método conforme mostrado
no código a seguir:
C#
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#
A consulta anterior pode ser expressa usando a sintaxe do método conforme mostrado
no exemplo a seguir:
C#
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.
C#
C#
var query = teachers
.Join(departments, teacher => teacher.ID, department =>
department.TeacherID,
(teacher, department) =>
new { DepartmentName = department.Name, TeacherName = $"
{teacher.First} {teacher.Last}" });
Os professores que não são chefes de departamento não aparecem nos resultados
finais.
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);
C#
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.
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#
O equivalente usando vários métodos Join usa a mesma abordagem com o tipo
anônimo:
C#
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}");
}
C#
C#
Para evitar o encadeamento, o método Join único pode ser utilizado conforme
apresentado aqui:
C#
2 Aviso
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#
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.
C#
Console.WriteLine(departmentsAndStudents);
C#
Console.WriteLine(departmentsAndStudents);
7 Observação
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
};
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
});
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
) Importante
ノ Expandir a tabela
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];
C#
List<int> numbers = [35, 44, 200, 84, 3987, 4, 199, 329, 446, 208];
Os exemplos a seguir neste artigo usam fontes de dados comuns para esta área:
C#
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.
C#
var groupByYearQuery =
from student in students
group student by student.Year into newGroup
orderby newGroup.Key
select newGroup;
C#
C#
var groupByFirstLetterQuery =
from student in students
let firstLetter = student.LastName[0]
group student by firstLetter;
C#
C#
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;
C#
C#
var groupByHighAverageQuery =
from student in students
group new
{
student.FirstName,
student.LastName
} by student.Scores.Average() > 75 into studentGroup
select studentGroup;
C#
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;
C#
var groupByCompoundKey = students
.GroupBy(student => new
{
FirstLetterOfLastName = student.LastName[0],
IsScoreOver85 = student.Scores[0] > 85
})
.OrderBy(studentGroup => studentGroup.Key.FirstLetterOfLastName);
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;
C#
var nestedGroupsQuery =
students
.GroupBy(student => student.Year)
.Select(newGroup1 => new
{
newGroup1.Key,
NestedGroup = newGroup1
.GroupBy(student => student.LastName)
});
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()
};
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())
});
C#
C#
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.
C#
// 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.");
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#
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.
C#
string startFolder = """C:\Program Files\dotnet\sdk""";
// Or
// string startFolder = "/usr/local/share/dotnet/sdk";
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#
C#
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#
if (areIdentical == true)
{
Console.WriteLine("the two folders are the same");
}
else
{
Console.WriteLine("The two folders are not the same");
}
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.");
}
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);
}
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);
}
}
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#
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
txt
Liu, Jinghao
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Gilchrist, Beth
Myrcha, Jacek
Giakoumakis, Leo
McLin, Nkenge
El Yassir, Mehdi
C#
Console.WriteLine(g.Key);
txt
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
C#
txt
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#
// Spreadsheet format:
// Student ID Exam#1 Exam#2 Exam#3 Exam#4
// 111, 97, 92, 81, 60
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
C#
C#
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.
txt
C#
A consulta anterior mostra como você pode manipular cadeias de caracteres dividindo-
as em campos e consultando os campos individuais.
C#
// 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;
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
C#
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.
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.
txt
Bankov, Peter
Holm, Michael
Garcia, Hugo
Potra, Cristina
Noriega, Fabricio
Aw, Kam Foo
Beebe, Ann
Toyoshima, Tim
Guy, Wey Yuan
Garcia, Debra
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 query. Note that method syntax must be used here.
var differenceQuery = names1.Except(names2);
Alguns tipos de operações de consulta, tais como Except, Distinct, Union e Concat, só
podem ser expressas em sintaxe baseada em método.
C#
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
txt
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#
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#
C#
C#
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.
C#
var sortedList =
source.OrderBy(number => number).ToList();
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#
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#
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#
/*
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);
// With the generic overload, you can also use numeric properties of
objects.
var query4 = numbers4.Median(str => str.Length);
C#
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#
ノ Expandir a tabela
Chave Valor
Um We
Um think
Um that
B Linq
C is
Chave Valor
Um really
B cool
B !
C#
if (current.CopyAllChunkElements() == noMoreSourceElements)
{
yield break;
}
}
}
}
C#
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:
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)
// Flag to indicate the source iterator has reached the end of the
source sequence.
internal bool isLastSourceElement;
// 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;
// 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);
}
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.
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
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:
Cada uma das técnicas permite mais funcionalidades, mas a um custo de maior
complexidade.
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
C#
C#
Talvez você também queira compor as diversas subexpressões usando outra biblioteca,
como o PredicateBuilder da LinqKit :
C#
// using LinqKit;
// string? startsWith = /* ... */;
// string? endsWith = /* ... */;
C#
C#
C#
C#
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.
C#
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#
C#
C#
C#
C#
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>.
C#
// using System.Linq.Dynamic.Core
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:
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.
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");
Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
}
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.
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#
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
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.
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#
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#
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.
) Important
C#
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#
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#
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.
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
Before going on, comment out these two lines in your ToastBreadAsync method. You
don't want to start another fire:
C#
C#
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#
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");
Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
}
return toast;
}
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
Para código vinculado à E/S, você aguarda uma operação que retorna um Task ou
Task<T> dentro de um método async .
É 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.
C#
private readonly HttpClient _httpClient = new HttpClient();
O código expressa a intenção (baixar alguns dados de forma assíncrona) sem se prender
à interação com objetos Task .
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#
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.
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.
7 Observação
C#
[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");
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#
// 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;
NetworkProgressBar.IsEnabled = false;
NetworkProgressBar.Visibility = Visibility.Collapsed;
}
Este exemplo mostra como você pode obter os dados User para um conjunto de
userId s.
C#
Aqui está outro jeito de escrever isso de forma mais sucinta usando LINQ:
C#
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.
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.
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
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.
Área do aplicativo Tipos .NET com métodos Tipos Windows Runtime com métodos
assíncronos assíncronos
XmlReader
XmlWriter
O exemplo a seguir mostra um método assíncrono. Você deve estar familiarizado com
quase tudo no código.
Task<string> getStringTask =
client.GetStringAsync("https://learn.microsoft.com/dotnet");
DoIndependentWork();
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 .
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 .
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
C#
string contents = await
client.GetStringAsync("https://learn.microsoft.com/dotnet");
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.
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.
7 Observação
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.
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
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.
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 ele próprio ser aguardado por métodos que
o chamam.
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,
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ê 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#
return hours;
}
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
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#).
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.
Como fazer várias solicitações da Web em Demonstra como iniciar várias tarefas ao mesmo
paralelo usando async e await (C#) tempo.
Como usar o Async para acessar arquivos Lista e demonstra as vantagens de usar async e
(C#) await para acessar arquivos.
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
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.
Também existem vários outros tipos específicos para cargas de trabalho do Windows:
C#
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.");
}
C#
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);
C#
Console.WriteLine(message);
}
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
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 à
) Importante
C#
string message =
$"Today is {DateTime.Today:D}\n" +
"Today's hours of leisure: " +
$"{await getLeisureHoursTask}";
Console.WriteLine(message);
C#
button.Clicked += OnButtonClicked1;
button.Clicked += OnButtonClicked2Async;
button.Clicked += OnButtonClicked3;
C#
class Program
{
static readonly Random s_rnd = new Random();
C#
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:
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#
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 .
C#
IEnumerable<Task<int>> downloadTasksQuery =
from url in s_urlList
select ProcessUrlAsync(url, s_client);
int total = 0;
while (downloadTasks.Any())
{
Task<int> finishedTask = await Task.WhenAny(downloadTasks);
downloadTasks.Remove(finishedTask);
total += await finishedTask;
}
stopwatch.Stop();
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#
1. Espera uma chamada para WhenAny , com o objetivo de identificar a primeira tarefa
na coleção que concluiu o download.
C#
C#
downloadTasks.Remove(finishedTask);
C#
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;
await SumPageSizesAsync();
IEnumerable<Task<int>> downloadTasksQuery =
from url in s_urlList
select ProcessUrlAsync(url, s_client);
int total = 0;
while (downloadTasks.Any())
{
Task<int> finishedTask = await Task.WhenAny(downloadTasks);
downloadTasks.Remove(finishedTask);
total += await finishedTask;
}
stopwatch.Stop();
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
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.
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#
C#
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#
Console.WriteLine(text);
}
return sb.ToString();
}
writeTaskList.Add(File.WriteAllTextAsync(filePath, text));
}
await Task.WhenAll(writeTaskList);
}
C#
try
{
string folder = Directory.CreateDirectory("tempfolder").Name;
IList<Task> writeTaskList = new List<Task>();
var sourceStream =
new FileStream(
filePath,
FileMode.Create, FileAccess.Write, FileShare.None,
bufferSize: 4096, useAsync: true);
writeTaskList.Add(writeTask);
}
await Task.WhenAll(writeTaskList);
}
finally
{
foreach (FileStream sourceStream in sourceStreams)
{
sourceStream.Close();
}
}
}
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.
Pré-requisitos
Este tutorial exige o seguinte:
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#
C#
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.
C#
int total = 0;
foreach (string url in s_urlList)
{
int contentLength = await ProcessUrlAsync(url, s_client,
s_cts.Token);
total += contentLength;
}
stopwatch.Stop();
C#
int total = 0;
foreach (string url in s_urlList)
{
int contentLength = await ProcessUrlAsync(url, s_client, s_cts.Token);
total += contentLength;
}
C#
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.
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
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();
Console.WriteLine("Application ending.");
}
int total = 0;
foreach (string url in s_urlList)
{
int contentLength = await ProcessUrlAsync(url, s_client,
s_cts.Token);
total += contentLength;
}
stopwatch.Stop();
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 .
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
C#
try
{
s_cts.CancelAfter(3500);
await SumPageSizesAsync();
}
catch (OperationCanceledException)
{
Console.WriteLine("\nTasks cancelled: timed out.\n");
}
finally
{
s_cts.Dispose();
}
Console.WriteLine("Application ending.");
}
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
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();
try
{
s_cts.CancelAfter(3500);
await SumPageSizesAsync();
}
catch (OperationCanceledException)
{
Console.WriteLine("\nTasks cancelled: timed out.\n");
}
finally
{
s_cts.Dispose();
}
Console.WriteLine("Application ending.");
}
int total = 0;
foreach (string url in s_urlList)
{
int contentLength = await ProcessUrlAsync(url, s_client,
s_cts.Token);
total += contentLength;
}
stopwatch.Stop();
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
" 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 .
C#
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.
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#
JObject results =
JObject.Parse(response.HttpResponse.Body.ToString()!);
nodes . O método RunPagedQueryAsync anexa esses nós em uma matriz que contém
Há vários elementos nesse código que podem ser melhorados. Acima de tudo,
RunPagedQueryAsync deve alocar armazenamento para todos os problemas retornados.
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
C#
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();
C#
Você terminou as alterações para gerar um fluxo assíncrono. O método concluído deve
ser semelhante ao seguinte código:
C#
JObject results =
JObject.Parse(response.HttpResponse.Body.ToString()!);
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#
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");
}
C#
int num = 0;
await foreach (var issue in RunPagedQueryAsync(client, PagedIssueQuery,
"docs"))
{
Console.WriteLine(issue);
Console.WriteLine($"Received {++num} issues in total");
}
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();
}
C#
JObject results =
JObject.Parse(response.HttpResponse.Body.ToString()!);
C#
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.
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
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.
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 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#
Quando você desreferencia uma variável cujo valor é null , o runtime gera um
System.NullReferenceException.
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:
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.
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.
C#
// warning!
Console.WriteLine(originalMessage.Length);
C#
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 propriedade 'name' não anulável deve conter um valor não nulo ao sair do
construtor.
C#
using System.Diagnostics.CodeAnalysis;
public Student()
{
SetMajor();
}
[MemberNotNull(nameof(Major))]
private void SetMajor(string? major = default)
{
Major = major ?? "Undeclared";
}
}
7 Observação
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.
C#
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
C#
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.
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;
À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;
) 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.
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.
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
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.
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
) Importante
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
Outro caso mais comum é quando você lida com structs genéricos. Considere o
seguinte exemplo:
C#
#nullable enable
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
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#)
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.
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.
) Importante
Cada variável tem um estado anulável padrão que depende de sua nulidade:
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.
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.
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
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:
) 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 */ }
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 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#
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;
C#
C#
C#
namespace methods;
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.
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
C#
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.
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#
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#
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#
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 :
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#
Antes do C# 13, o modificador params só pode ser usado com uma única matriz
dimensional.
Você atribuirá o valor padrão do parâmetro com um dos tipos de expressões a seguir:
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
C#
“Argumento ausente”.
C#
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;
C#
C#
C#
C#
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#
C#
C#
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.
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
C#
class Program
{
static Task Main() => DoSomethingAsync();
Console.WriteLine($"Result: {result}");
}
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.
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.
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
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:
C#
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.
C#
C#
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#
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#
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#
await Task.Delay(500);
await Task.Delay(500);
index = 100;
while (index < 110)
yield return index++;
}
C#
C#
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#
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
Você pode modificar um pouco o último método para usar yield return em todos os
lugares:
C#
var items = new int[] {100, 101, 102, 103, 104, 105, 106, 107, 108, 109
};
foreach (var item in items)
yield return item;
}
C#
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.
C#
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.
}
}
C#
{
var enumerator = collection.GetAsyncEnumerator();
try
{
while (await enumerator.MoveNextAsync())
{
var item = enumerator.Current;
Console.WriteLine(item.ToString());
}
}
finally
{
// dispose of async enumerator.
}
}
C#
finally
{
(enumerator as IDisposable)?.Dispose();
}
C#
finally
{
if (enumerator is IAsyncDisposable asyncDisposable)
await asyncDisposable.DisposeAsync();
}
C#
finally
{
}
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
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.
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.
Vamos começar.
Próximo
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 .
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#
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.
C#
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#
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.
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#
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#
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#
O uso de expressões lambda para destinos de delegado será abordado em uma seção
posterior.
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 .
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.
C#
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#
Use um dos tipos Func para qualquer tipo de delegado que retorna um valor.
C#
Você pode observar que para qualquer tipo Predicate , há um tipo Func
estruturalmente equivalente, por exemplo:
C#
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
C#
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.
C#
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.
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).
C#
C#
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#
Em seguida, vamos usar aquele argumento Severity para filtrar as mensagens que são
enviadas para o log de saída.
C#
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.
C#
Após ter criado essa classe, você pode instanciá-la e ela anexa o método LogMessage
ao componente do agente:
C#
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#
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.
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.
C#
Próximo
Introdução a eventos
Artigo • 10/05/2023
Anterior
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.
Deve ser muito simples assinar um evento e cancelar a assinatura desse mesmo
evento.
C#
C#
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.
fileLister.Progress += onProgress;
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
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.
C#
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.
C#
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).
C#
C#
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#
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.
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.
Vamos implementar a primeira versão deste exemplo. Você precisa adicionar um campo
booliano chamado CancelRequested ao tipo FileFoundArgs :
C#
C#
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#
Você começará criando a nova classe derivada EventArgs para relatar o novo diretório e
o andamento.
C#
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#
C#
SearchDirectory(directory, searchPattern);
}
else
{
SearchDirectory(directory, searchPattern);
}
}
corretamente.
Vamos adicionar um manipulador para escrever uma linha que mostra o andamento na
janela do console.
C#
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
Você também pode alterar o SearchDirectoryArgs para um struct, se você fizer mais
uma alteração:
C#
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.
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#
É 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.
Com todas essas semelhanças, é fácil de ter problemas para determinar quando usar
qual.
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.
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.
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
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:
versões anteriores
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
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.
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.
XML
<dependentAssembly>
<assemblyIdentity name="ReferencedLibrary"
publicKeyToken="32ab4ba45e0a69a1" culture="en-us" />
<bindingRedirect oldVersion="1.0.0" newVersion="1.0.1" />
</dependentAssembly>
7 Observação
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.
C#
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#
Saída
Console
Conceitos gerais de C#
Há dicas e truques que são práticas comuns do desenvolvedor de C#:
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.
Representantes e eventos
Representantes e eventos fornecem uma capacidade para estratégias que envolve
blocos de código acoplados livremente.
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.
Comentários
Esta página foi útil? Yes No
7 Observação
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(' ');
C#
string phrase = "The quick brown fox jumps over the lazy dog.";
string[] words = phrase.Split(' ');
C#
string phrase = "The quick brown fox jumps over the lazy dog.";
string[] words = phrase.Split(' ');
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.
C#
C#
C#
7 Observação
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);
**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
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
7 Observação
C#
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,
C#
C#
7 Observação
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#
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#
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#
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
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
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.
C#
// 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)");
C#
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
C#
string[] sentences =
{
"Put the water over there.",
"They're quite thirsty.",
"Their water bottles broke."
};
if (System.Text.RegularExpressions.Regex.IsMatch(s, sPattern,
System.Text.RegularExpressions.RegexOptions.IgnoreCase))
{
Console.WriteLine($" (match for '{sPattern}' found)");
}
else
{
Console.WriteLine();
}
}
Dica
Padrão Significado
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}$";
if (System.Text.RegularExpressions.Regex.IsMatch(s, sPattern))
{
Console.WriteLine(" - valid");
}
else
{
Console.WriteLine(" - invalid");
}
}
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
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#
C#
C#
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#
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.";
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;
}
}
C#
string phrase = "The quick brown fox jumps over the fence";
Console.WriteLine(phrase);
C#
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:
7 Observação
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#
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.
C#
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#
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");
}
C#
O exemplo a seguir mostra como classificar um array de strings usando a cultura atual:
C#
Console.WriteLine("Non-sorted order:");
foreach (string s in lines)
{
Console.WriteLine($" {s}");
}
Console.WriteLine("\n\rSorted order:");
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#
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}.");
}
}
C#
Console.WriteLine("Non-sorted order:");
foreach (string s in lines)
{
Console.WriteLine($" {s}");
}
Console.WriteLine("\n\rSorted order:");
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#
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.
Confira também
System.Globalization.CultureInfo
System.StringComparer
Cadeias de caracteres
Comparando cadeias de caracteres
Globalizando e localizando aplicativos
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:
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.
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)] .
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#
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 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#
The following example uses reflection to obtain the full name of the loaded assembly.
C#
7 Note
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.
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#
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.
C#
[target : attribute-list]
ノ Expand table
event Event
property 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#
// applies to method
[method: ValidatedContract]
int Method2() { return 0; }
// applies to parameter
int Method3([ValidatedContract] string contract) { return 0; }
7 Note
only to return values. In other words, the compiler will not use AttributeUsage
information to resolve ambiguous attribute targets. For more information, see
AttributeUsage.
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:
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;
C#
C#
[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Struct,
AllowMultiple = true) // Multiuse attribute.
]
public class AuthorAttribute : System.Attribute
{
string Name;
public double Version;
// Default value.
Version = 1.0;
}
No exemplo de código a seguir, vários atributos do mesmo tipo são aplicados a uma
classe.
C#
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.
C#
C#
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
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;
// Default value.
Version = 1.0;
}
class TestAuthorAttribute
{
public static void Test()
{
PrintAuthorInfo(typeof(FirstClass));
PrintAuthorInfo(typeof(SecondClass));
PrintAuthorInfo(typeof(ThirdClass));
}
// 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 .
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;
}
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<,> .
C#
C#
[CustomAttribute(info = typeof(GenericClass1<>))]
class ClassA { }
C#
[CustomAttribute(info = typeof(GenericClass2<,>))]
class ClassB { }
C#
C#
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#
typeof(System.Collections.Generic.IEnumerable<>).FullName!) != null
&& method.ReturnType.FullName != "System.String")
group method.ToString() by type.ToString();
Para obter uma lista das condições invariáveis para termos usados na reflexão genérica,
consulte os comentários da propriedade IsGenericType:
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
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
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
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.
C#
[Obsolete]
public class MyClass
{
}
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#
usar uma expressão ou uma variável. Você pode usar parâmetros posicionais ou
nomeados.
C#
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
{
}
C#
C#
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#
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#
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.
C#
ObsoleteAttribute .
[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
C#
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
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 .
C#
C#
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.
C#
// Version 1:
public decimal ComputeLoyaltyDiscount()
{
DateTime TwoYearsAgo = DateTime.Now.AddYears(-2);
if ((DateJoined < TwoYearsAgo) && (PreviousOrders.Count() > 10))
{
return 0.10m;
}
return 0;
}
C#
C#
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;
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#
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#
C#
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.
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 .
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:
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.
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#
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#
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#
C#
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#
C#
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#
C#
C#
O código a seguir no método Main cria cada tipo de luz na sequência e testa essa luz:
C#
C#
C#
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
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.
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#
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:
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#
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ó.
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.
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#
Console.WriteLine(parameter.Name);
Console.WriteLine(parameter.Type);
}
C#
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#.
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.
7 Observação
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ã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.
) Importante
no .NET 5 e posterior.
C#
C#
// 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#
U Cuidado
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
Por exemplo, esse código funciona bem, porque int não implementa IDisposable :
C#
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.
C#
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#
O delegado retornado desse método fechou sobre o objeto constant , que foi
descartado. (Foi descartado, porque foi declarado em uma instrução using ).
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.
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.
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
C#
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 é.
C#
var constant = Expression.Constant(24, typeof(int));
Saída
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#
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>> .
Para examinar cada nó nesta expressão, você precisa visitar de maneira recorrente
muitos nós. Esta é uma primeira implementação simples:
C#
Saída
É 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;
// Lambda Visitor
public class LambdaVisitor : Visitor
{
private readonly LambdaExpression node;
public LambdaVisitor(LambdaExpression node) : base(node) => this.node =
node;
// Parameter visitor:
public class ParameterVisitor : Visitor
{
private readonly ParameterExpression node;
public ParameterVisitor(ParameterExpression node) : base(node)
{
this.node = node;
}
// Constant visitor:
public class ConstantVisitor : Visitor
{
private readonly ConstantExpression node;
public ConstantVisitor(ConstantExpression node) : base(node) =>
this.node = node;
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
Agora que criou uma implementação de visitante mais geral, você pode visitar e
processar muitos tipos diferentes de expressões.
C#
C#
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#
Crie um visitante para essa soma e execute o visitante. Você verá esta saída:
Saída
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#
Saída
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.
C#
C#
C#
Saída
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#
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#
C#
C#
Para expressões como essa, você pode combinar todas as chamadas em uma só
instrução:
C#
C#
C#
C#
C#
C#
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.
C#
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.
C#
C#
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.
C#
Crie uma árvore substituindo o nó original pelo substituto. Você verificará as alterações
compilando e executando a árvore substituída.
C#
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.
C#
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.
C#
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#
Saída
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.
C#
return base.VisitBinary(b);
}
}
C#
Console.WriteLine(modifiedExpr);
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:
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
ParameterExpression
ParameterExpression os nomes de variáveis são exibidos com o símbolo $ no início.
C#
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.
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#
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#
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#
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.
C#
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#
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
Método de execução
As regras a seguir determinam o método de execução selecionado para a operação:
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.
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.
Add Add
AddChecked AddChecked
Divide Divide
Modulo Modulo
Multiply Multiply
MultiplyChecked MultiplyChecked
Power Power
Subtract Subtract
SubtractChecked SubtractChecked
And And
Or Or
ExclusiveOr ExclusiveOr
Operações de turno
ノ Expandir a tabela
LeftShift LeftShift
RightShift RightShift
AndAlso AndAlso
OrElse OrElse
Operadores de comparação
ノ Expandir a tabela
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
Coalesce Coalesce
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.
.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
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.
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.
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
) Importante
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.
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
presentes.
C#
using System.Collections.Generic;
using Excel = Microsoft.Office.Interop.Excel;
using Word = Microsoft.Office.Interop.Word;
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#
C#
C#
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
C#
Para executar o programa, pressione F5. Uma planilha do Excel é exibida contendo os
dados das contas.
C#
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.
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.
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
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,
C#
using System.Runtime.InteropServices;
namespace WinSound;
[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
}
if (dialog1.ShowDialog() == DialogResult.OK)
{
textBox1.Text = dialog1.FileName;
PlaySound(dialog1.FileName, new System.IntPtr(),
PlaySoundFlags.SND_SYNC);
}
}
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();
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
) Importante
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# 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 .
{language}
// Visual C# 2010.
targetRange.Value = "Name";
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}
namespace IndexedProperties
{
class Program
{
static void Main(string[] args)
{
CSharp2010();
}
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
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
C#
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#
C#
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#
C#
var row = 1;
foreach (var acct in accounts)
{
row++;
workSheet.Cells[row, "A"] = acct.ID;
workSheet.Cells[row, "B"] = acct.Balance;
}
C#
workSheet.Columns[1].AutoFit();
workSheet.Columns[2].AutoFit();
C#
((Excel.Range)workSheet.Columns[1]).AutoFit();
((Excel.Range)workSheet.Columns[2]).AutoFit();
C#
Pressione CTRL+F5. Uma planilha do Excel é exibida contendo os dados das duas
contas.
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.
C#
// 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();
C#
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();
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.
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.
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#
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
}
};
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();
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
) Importante
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.
C#
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;
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#
Executar o aplicativo
Adicione a seguinte instrução a Main:
C#
DisplayInWord();
C#
// Convert to a simple table. The table will have a single row with
// three columns.
range.ConvertToTable(Separator: ",");
C#
C#
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();
}
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.
C#
C#
class ExampleClass
{
public ExampleClass() { }
public ExampleClass(int v) { }
C#
dynamic d = 1;
var testSum = d + 3;
// Rest the mouse pointer over testSum in the following statement.
System.Console.WriteLine(testSum);
C#
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.
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;
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
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
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.
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.
C#
using System.IO;
using System.Dynamic;
C#
C#
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;
}
C#
try
{
sr = new StreamReader(p_filePath);
while (!sr.EndOfStream)
{
line = sr.ReadLine();
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;
}
C#
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 .
C#
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.");
}
text
C#
namespace System.Linq .
C#
using System.Linq;
using Microsoft.Scripting.Hosting;
using IronPython.Hosting;
C#
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();
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.
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#
Console.WriteLine(location); // output: 42
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.
C#
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
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#
7 Note
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#
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#
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.
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
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.
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
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#
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
};
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.
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#
Cada medida aloca um novo objeto SensorMeasurement , que é um tipo class . Cada
SensorMeasurement criado causa uma alocação de heap.
C#
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 .
C#
C#
C#
C#
SensorMeasurement? measure = default;
C#
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
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#
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#
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.
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);
}
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#
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.
parâmetros vazio:
C#
public DebounceMeasurement() { }
C#
método ToString :
C#
namespace IntruderAlert;
public AverageMeasurement() { }
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
C#
C#
C#
Depois, modifique o código no programa principal para chamar esse novo método
ToString :
C#
Console.WriteLine(room.ToString());
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.
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.
Antes de criar seu próprio analisador, confira os analisadores internos. Para mais
informações, confira Regras de estilo de código.
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.
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:
Comentários
Esta página foi útil? Yes No
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.
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:
Á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.
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.
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.
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.
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.
Por exemplo:
C#
if (x > 3)
{
|| // this is bad
|throw new Exception("Not right.");| // better exception?||
}
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
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 .
um tipo
um campo
um método
uma variável local
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.
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.
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.
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.
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.
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.
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#
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.
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 .
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
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.
DiagnosticDescriptor.Id
ObsoleteAttribute.DiagnosticId
ExperimentalAttribute.DiagnosticId
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
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.
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#.
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.
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
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
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#
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#
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#
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#
C#
WriteLine($"There are {helloWorldDeclaration.Members.Count} members declared
in this namespace.");
WriteLine($"The first member is a
{helloWorldDeclaration.Members[0].Kind()}.");
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];
C#
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
C#
WriteLine(argsParameter == argsParameter2);
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ó.
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#
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#
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.
C#
C#
C#
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#
Console
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ê pode ver o código concluído para essa amostra no nosso repositório do GitHub .
7 Observação
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#
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#
C#
C#
C#
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 .
C#
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
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#
C#
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#
C#
Em seguida, adicione outro filtro para retornar somente os métodos que são públicos e
retornam um string :
C#
C#
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#
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!
C#
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#:
C#
NameSyntax name = IdentifierName("System");
WriteLine($"\tCreated the identifier {name}");
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#
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#
Execute o programa novamente para ver se você construiu a árvore para o código a ser
adicionado.
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#
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(""Hello, World!"");
}
}
}";
7 Observação
Em seguida, adicione o seguinte código à parte inferior do método Main para analisar o
texto e criar uma árvore:
C#
C#
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#
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.
C#
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
C#
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#
C#
}
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#
Se você quiser explorar por conta própria, considere estender a amostra finalizada para
esses tipos de declarações de variáveis:
C#
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#
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());
C#
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#
if (newSource != sourceTree.GetRoot())
{
File.WriteAllText(sourceTree.FilePath, newSource.ToFullString());
}
}
C#
if (newSource != sourceTree.GetRoot())
{
File.WriteAllText(sourceTree.FilePath, newSource.ToFullString());
}
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#
MetadataReference mscorlib =
MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
MetadataReference codeAnalysis =
MetadataReference.CreateFromFile(typeof(SyntaxTree).Assembly.Location);
MetadataReference csharpCodeAnalysis =
MetadataReference.CreateFromFile(typeof(CSharpSyntaxTree).Assembly.Location)
;
return CSharpCompilation.Create("TransformationCS",
sourceTrees,
references,
new CSharpCompilationOptions(OutputKind.ConsoleApplication));
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
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:
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.
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.
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.
O modelo também mostra os recursos básicos que fazem parte de qualquer analisador:
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".
C#
context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);
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#
C#
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#
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#
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.
C#
// 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;
}
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, 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 .
diff
- int x = 0;
+ const int x = 0;
Console.WriteLine(x);
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#
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#
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#
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);
C#
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;
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);
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
Dica
[|text|] : indica que um diagnóstico é relatado para text . Por padrão, esse
DiagnosticAnalyzer.SupportedDiagnostics .
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.
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:
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.
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.
C#
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;
}
}
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.
C#
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;
}
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:
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#
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.
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
Nesta seção
ノ Expandir a tabela
Title Descrição
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.
Á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.
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.
Seções relacionadas
Dicas de desempenho
Comentários
Esta página foi útil? Yes No
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;
C#
C#
C#
Título Descrição
Comentários
Esta página foi útil? Yes No
IEnumerable<T> (T é covariante)
IEnumerator<T> (T é covariante)
IQueryable<T> (T é covariante)
IComparer<T> (T é contravariante)
IEqualityComparer<T> (T é contravariante)
IComparable<T> (T é contravariante)
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#
C#
// 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();
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#
C#
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
7 Observação
) Importante
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#
C#
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#
C#
C#
C#
As classes que implementam interfaces variantes são invariantes. Por exemplo, considere
o código a seguir.
C#
C#
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#
C#
C#
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.
Para obter uma lista de interfaces variantes no .NET, confira Variância em interfaces
genéricas (C#).
C#
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);
}
}
PrintFullName(employees);
}
}
C#
class Program
{
IEnumerable<Employee> noduplicates =
employees.Distinct<Employee>(new PersonComparer());
Confira também
Variância em interfaces genéricas (C#)
Variação em delegados (C#)
Artigo • 09/05/2023
Por exemplo, considere o código a seguir, que tem duas classes e dois delegados:
genérico e não genérico.
C#
C#
// Matching signature.
public static First ASecondRFirst(Second second)
{ return new First(); }
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#).
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#
palavra-chave out .
C#
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#).
C#
C#
) Importante
C#
C#
C#
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#
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
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();
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:
C#
C#
Código
C#
public Form1()
{
InitializeComponent();
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
C#
}
}
C#
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.
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#
Você pode usar uma instrução yield break para terminar a iteração.
7 Observação
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#
C#
O exemplo a seguir cria uma classe Zoo que contém uma coleção de animais.
C#
theZoo.AddMammal("Whale");
theZoo.AddMammal("Rhinoceros");
theZoo.AddBird("Penguin");
theZoo.AddBird("Warbler");
Console.ReadKey();
}
// Public methods.
public void AddMammal(string name)
{
animals.Add(new Animal { Name = name, Type = Animal.TypeEnum.Mammal
});
}
// Public members.
public IEnumerable Mammals
{
get { return AnimalsForType(Animal.TypeEnum.Mammal); }
}
// 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 }
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 .
C#
Console.ReadKey();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
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>.
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.
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:
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#
// Assignment statement.
counter = 1;
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:
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
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.
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.
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
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#
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#
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#
// 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;
C#
if (b == true)
{
// OK:
System.DateTime d = System.DateTime.Now;
System.Console.WriteLine(d.ToLongDateString());
}
C#
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#
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
C#
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;
class Example
{
static void Main()
{
Person p = new Person("Mandy", "Dejesus");
Console.WriteLine(p);
p.DisplayName();
}
}
C#
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#
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#
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;
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#
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;
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
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; }
// Assign b to a.
b = a;
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();
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.
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.
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.
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.
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;
public static bool operator !=(TwoDPoint lhs, TwoDPoint 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));
/* 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
*/
) 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#
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
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; }
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);
pointD = temp;
// True:
Console.WriteLine("pointD == (pointC = 3,4) = {0}", pointD ==
pointC);
/* 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
*/
}
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
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; }
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
#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);
#endregion
/* 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
*/
Confira também
Comparações de igualdade
Coerções e conversões de tipo (Guia de
Programação em C#)
Artigo • 15/03/2024
C#
int i;
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#
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#
// 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#
C#
class Animal
{
public void Eat() => System.Console.WriteLine("Eating.");
class UnSafeCast
{
static void Main()
{
Test(new Mammal());
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
C#
int i = 123;
// The following line boxes i.
object o = i;
C#
o = 123;
i = (int)o; // unboxing
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>();
// 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]);
// 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.
C#
int i = 123;
C#
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:
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;
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:
C#
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);
}
}
}
C#
int j = (short)o;
para:
C#
int j = (int)o;
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.
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 };
7 Observação
C#
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
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
O exemplo a seguir demonstra chamadas com e sem êxito para Parse e TryParse .
C#
using System;
try
{
int numVal = Int32.Parse("-105");
Console.WriteLine(numVal);
}
catch (FormatException e)
{
Console.WriteLine(e.Message);
}
// Output: -105
try
{
int m = Int32.Parse("abc");
}
catch (FormatException e)
{
Console.WriteLine(e.Message);
}
// Output: Input string was not in a correct format.
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;
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;
}
}
ノ Expandir a tabela
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;
while (repeat)
{
Console.Write("Enter a number between −2,147,483,648 and
+2,147,483,647 (inclusive): ");
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
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 .
Saída
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.");
}
}
}
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
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#
C#
C#
C#
// 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#
/*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#
/*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.
O método da classe base pode ser chamado de dentro da classe derivada usando
a palavra-chave base .
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#
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() { }
}
Se desejar que seu método substitua o novo método de classe base, use a palavra-
chave override :
C#
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#
C#
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á
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.
C#
class BaseClass
{
public void Method1()
{
Console.WriteLine("Base - Method1");
}
}
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
}
C#
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
C#
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.
C#
public override void Method1()
{
Console.WriteLine("Derived - Method1");
}
C#
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.
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
class BaseClass
{
public virtual void Method1()
{
Console.WriteLine("Base - Method1");
}
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();
}
C#
// Notice the output from this test case. The new modifier is
// used in the definition of ShowDetails in the ConvertibleCar
// class.
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#
É 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.
// ----------
para ter o tipo Car ( TestCars4 ). O código a seguir define esses dois métodos.
C#
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();
// 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("----------");
// 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();
}
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
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.
C#
C#
class Person
{
public string Name { get; set; }
public int Age { get; set; }
C#
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
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.
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
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.
C#
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#
C#
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.
C#
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#
// 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#.
É lacrada.
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#
return fahrenheit;
}
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(":");
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;
}
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.
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#
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
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
Dentro do ✔️️ ✔️ ✔️ ✔️ ✔️ ✔️ ✔️
arquivo
Classe derivada ✔️ ✔️ ✔️ ✔️ ✔️ ❌ ❌
(mesmo
assembly)
Classe não ✔️ ✔️ ❌ ✔️ ❌ ❌ ❌
derivada
(mesmo
assembly)
Classe derivada ✔️ ✔️ ✔️ ❌ ❌ ❌ ❌
(assembly
diferente)
Classe não ✔️ ❌ ❌ ❌ ❌ ❌ ❌
derivada
(assembly
diferente)
C#
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.
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
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;
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.
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.
C#
C#
C#
7 Observação
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.
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.
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 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
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;
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#
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
Exemplos
Esse arquivo declara a classe Shape que contém a propriedade Area do tipo double .
C#
public Shape(string s)
{
// calling the set accessor of the Id property.
Id = s;
}
public string Id
{
get
{
return name;
}
set
{
name = value;
}
}
C#
C#
C#
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#
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#
C#
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#
C#
) Importante
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#
[SetsRequiredMembers]
public Person(string firstName) => FirstName = firstName;
) 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
C#
var aPerson = new Person("John");
aPerson = new Person{ FirstName = "John"};
// Error CS9035: Required member `Person.FirstName` must be set:
//aPerson2 = new Person();
C#
[SetsRequiredMembers]
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
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#
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
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
C#
C#
C#
[SetsRequiredMembers]
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
C#
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.
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.
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
C#
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#
) Importante
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
}
C#
2 Aviso
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
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#
É 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
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
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#
// A Constructor:
public Employee() => _counter = ++NumberOfEmployees; // Calculate the
employee's number:
}
C#
class TestHiding
{
public static void Test()
{
Manager m1 = new Manager();
C#
C#
((Employee)m1).Name = "Mary";
Para obter mais informações sobre como ocultar membros, consulte o Modificador new.
C#
//constructor
public Square(double s) => side = s;
//constructor
public Cube(double s) => side = s;
class TestShapes
{
static void Main()
{
// Input the side:
System.Console.Write("Enter the side: ");
double side = double.Parse(System.Console.ReadLine());
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
C#
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
C#
string IEmployee.Name
{
get { return "Employee Name"; }
set { }
}
C#
string IEmployee.Name
{
get { return "Employee Name"; }
set { }
}
C#
string ICitizen.Name
{
get { return "Citizen Name"; }
set { }
}
C#
interface IEmployee
{
string Name
{
get;
set;
}
int Counter
{
get;
}
}
// constructor
public Employee() => _counter = ++numberOfEmployees;
}
C#
Saída de exemplo
Console
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
C#
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
C#
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#
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.
C#
public string Id
{
get { return _id; }
set { }
}
}
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.
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
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
Exemplo
C#
class Person
{
private string _name = "N/A";
private int _age = 0;
set
{
_age = 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);
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#
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;
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);
C#
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
O exemplo a seguir mostra uma classe simples que tem algumas propriedades
implementadas automaticamente:
C#
// 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"; }
class Program
{
static void Main()
{
// Initialize a new object.
Customer cust1 = new Customer(4987.63, "Northwind", 90108);
// Modify a property.
cust1.TotalPurchases += 499.99;
}
}
C#
Para obter mais informações, consulte Como implementar uma classe leve com
propriedades implementadas automaticamente.
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
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.
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; }
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#
// Public constructor.
public Contact(string contactName, string contactAddress)
{
Name = contactName;
Address = contactAddress;
}
}
// 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);
}
}
/* 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
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.
C#
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#
moto.StartEngine();
moto.AddGas(15);
moto.Drive(5, 20);
double speed = moto.GetTopSpeed();
Console.WriteLine("My top speed is {0}", speed);
}
}
C#
int Square(int i)
{
// Store input argument in a local variable.
int input = i;
return input * input;
}
C#
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#
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#
C#
class SimpleMath
{
public int AddTwoNumbers(int number1, int number2)
{
return number1 + number2;
}
C#
int result = obj.AddTwoNumbers(1, 2);
result = obj.SquareANumber(result);
// The result is 9.
Console.WriteLine(result);
C#
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-
C#
C#
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
= 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();
Console.WriteLine($"Result: {result}");
}
Um método assíncrono não pode declarar nenhum parâmetro ref ou out, mas pode
chamar métodos com tais parâmetros.
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);
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.
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:
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.
C#
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 .
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".
C#
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...
}
}
C#
C#
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
C#
C#
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.
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.
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;
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.
C#
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#
C#
return LowercaseIterator();
IEnumerable<string> LowercaseIterator()
{
foreach (var output in input.Select(item => item.ToLower()))
{
yield return output;
}
}
}
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 };
É 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.
C#
C#
C#
Para obter mais informações, consulte Como usar matrizes e variáveis locais de tipo
implícito em uma expressão de consulta.
C#
class ImplicitlyTypedLocals2
{
static void Main()
{
string[] words = { "aPPLE", "BlUeBeRrY", "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.
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#
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#
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.
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#
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 é
C#
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
{
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 .
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;
C#
C#
C#
using System.Linq;
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.
C#
// 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();
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.
C#
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#
C#
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.
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.
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
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.
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
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;
}
}
}
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;
}
}
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.
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
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#
Se não se lembrar da ordem dos parâmetros, mas souber os nomes, você poderá enviar
os argumentos em qualquer ordem.
C#
C#
C#
C#
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");
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:
C#
C#
//anExample.ExampleMethod(3, ,4);
C#
7 Observação
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
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);
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.
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:
C#
var myFormat =
Microsoft.Office.Interop.Excel.XlRangeAutoFormat.xlRangeAutoFormatAccounting
1;
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#.
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:
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#
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#
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#.
static Adult()
{
minimumAge = 18;
}
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#
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
C#
public Taxi()
{
IsInitialized = true;
}
}
class TestTaxi
{
static void Main()
{
Taxi t = new Taxi();
Console.WriteLine(t.IsInitialized);
}
}
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() { }
O código a seguir usa o construtor sem parâmetros para Int32, para que você tenha
certeza de que o inteiro é inicializado:
C#
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);
C#
public Employee() { }
Essa classe pode ser criada usando qualquer uma das instruções a seguir:
C#
Um construtor pode usar a palavra-chave base para chamar o construtor de uma classe
base. Por exemplo:
C#
C#
C#
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 .
C#
C#
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
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)
{ }
class Example
{
static void Main()
{
var p1 = new Coords();
Console.WriteLine($"Coords #1 at {p1}");
// Output: Coords #1 at (0,0)
C#
class Example
{
static void Main()
{
double radius = 2.5;
double height = 3.0;
C#
class Example
{
static void Main()
{
var person = new Person();
Console.WriteLine($"Name: {person.name}, Age: {person.age}");
// Output: Name: unknown, Age: 0
}
}
C#
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#
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.
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
Confira também
Classes, structs e registros
Construtores
Finalizadores
base
this
Especificação de recurso de construtores primários
C#
class NLog
{
// Private Constructor:
private NLog() { }
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#
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);
C#
Confira também
Sistema de tipos do C#
Construtores
Finalizadores
private
público
C#
class SimpleClass
{
// Static variable that must be initialized at run time.
static readonly long baseline;
Há várias ações que fazem parte da inicialização estática. Essas ações ocorrem na
seguinte ordem:
) 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#
private Singleton()
{
Console.WriteLine("Executes before static constructor.");
}
static Singleton()
{
Console.WriteLine("Executes after instance constructor.");
}
Comentários
Construtores estáticos têm as seguintes propriedades:
7 Observaçã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#
// 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);
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
) Importante
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#
// Instance constructor.
public Person(string name, int age)
{
Name = name;
Age = age;
}
class TestPerson
{
static void Main()
{
// Create a Person object by using the instance constructor.
Person person1 = new Person("George", 40);
// Show details to verify that the name and age fields are distinct.
Console.WriteLine(person1.Details());
Console.WriteLine(person2.Details());
Confira também
ICloneable
Registros
O sistema de tipos C#
Construtores
Finalizadores
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.
C#
class Car
{
~Car() // finalizer
{
// cleanup statements...
}
}
C#
C#
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
Para obter mais informações sobre como limpar recursos, consulte os seguintes artigos:
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.");
}
}
/*
Test with code like the following:
Third t = new Third();
t = null;
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
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 Cat()
{
}
C#
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.
C#
C#
[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#
Para que o código anterior seja compilado, o tipo IndexersExample precisará ter os
seguintes membros:
C#
C#
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#
C#
// 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.
C#
// 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";
C#
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 EmbeddedClassTypeB()
{
Console.WriteLine($"Entering EmbeddedClassTypeB constructor.
Values are: {this}");
BI = 23;
BB = false;
BS = "BBBabc";
Console.WriteLine($"Exiting EmbeddedClassTypeB constructor.
Values are: {this})");
}
}
// 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
}
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
C#
C#
C#
C#
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#
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.
C#
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#
C#
C#
Exemplos
O exemplo a seguir combina os conceitos de inicializadores de coleção e objeto.
C#
public Cat() { }
// Display results.
System.Console.WriteLine(cat.Name);
C#
Console.WriteLine("Address Entries:");
/*
* 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#
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator() =>
internalDictionary.GetEnumerator();
/*
* Prints:
Using first multi-valued dictionary created with a collection
initializer:
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.
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#
Console.WriteLine(student1.ToString());
Console.WriteLine(student2.ToString());
Console.WriteLine(student3.ToString());
Console.WriteLine(student4.ToString());
}
// Output:
// Craig 0
// Craig 0
// 183
// Craig 116
// Properties.
public string? FirstName { get; set; }
public string? LastName { get; set; }
public int ID { get; set; }
C#
Console.WriteLine(team["2B"]);
}
}
C#
Confira também
Inicializadores de objeto e coleção
7 Observação
C#
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
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; }
}
Confira também
Inicializadores de objeto e coleção
C#
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.
Os tipos aninhados de uma classe podem ser public, protected, internal, protected
internal, private ou private protected.
C#
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 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.
C#
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
Classes parciais
Há várias situações em que a divisão de uma definição de classe é desejável:
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()
{
}
}
// 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 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() { }
}
C#
[SerializableAttribute]
partial class Moon { }
[ObsoleteAttribute]
partial class Moon { }
C#
[SerializableAttribute]
[ObsoleteAttribute]
class Moon { }
C#
partial class Earth : Planet, IRotate { }
partial class Earth : IRevolve { }
C#
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#
C#
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
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;
// This is in Coords_Part2.cs
public partial class Coords
{
public void PrintCoords()
{
Console.WriteLine("Coords: {0},{1}", x, y);
}
}
O exemplo a seguir mostra que você também pode desenvolver interfaces e structs
parciais.
C#
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
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.
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
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#
// Definition in file1.cs
partial void OnNameChanged();
// Implementation in file2.cs
partial void OnNameChanged()
{
// method body
}
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:
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#
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#
C#
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#
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#
C#
// 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#
C#
// Output:
// IControl.Paint
// ISurface.Paint
interface ILeft
{
int P { get;}
}
interface IRight
{
int P();
}
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#
C#
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();
}
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#
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#
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
Exemplo
C#
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#
Confira também
Programação orientada a objetos
Interfaces
Como implementar membros de interface explicitamente
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#
7 Observação
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#.
Confira também
Delegate
Guia de Programação em C#
Eventos
Comentários
Esta página foi útil? Yes No
C#
C#
C#
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.
C#
Em seguida, você pode passar o delegado criado acima para esse método:
C#
MethodWithCallback(1, 2, handler);
Console
para outro método. Isso é especialmente poderoso, uma vez que um método delegado
pode usar qualquer número de parâmetros.
C#
C#
//remove Method1
allMethodsDelegate -= d1;
C#
C#
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
C#
// Declare a delegate.
delegate void WorkCallback(int x);
7 Observação
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();
// Declare a delegate
delegate void Callback();
class SampleClass
{
public void InstanceMethod()
{
Console.WriteLine("A message from the instance method.");
}
class TestSampleClass
{
static void Main()
{
var sc = new SampleClass();
Confira também
Representantes
Como combinar delegados (delegados multicast)
Eventos
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.
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}!");
}
Confira também
MulticastDelegate
Eventos
C#
// Declare a delegate.
delegate void NotifyCallback(string str);
C#
C#
C#
C#
// Instantiate NotifyCallback by using a lambda expression.
NotifyCallback del4 = name => { Console.WriteLine($"Notification received
for: {name}"); };
Exemplo
C#
C#
Instanciando um delegado.
C#
bookDB.ProcessPaperbackBooks(PrintTitle);
C#
bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);
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);
Confira também
Eventos
Representantes
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.
C#
// Initialize to null.
string message2 = null;
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.
C#
string s1 = "A string is more ";
string s2 = "than the sum of its chars.";
System.Console.WriteLine(s1);
// Output: A string is more than the sum of its chars.
C#
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.
C#
C#
C#
C#
// 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#
Compare esse texto com o texto equivalente em nosso exemplo de serialização JSON,
que não usa esse novo recurso.
ノ Expandir a tabela
\0 Nulo 0x0000
\a Alerta 0x0007
\b Backspace 0x0008
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
C#
// 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;
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#
// 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#
System.Console.WriteLine(s3.Replace("C#", "Basic"));
// Output: "Visual Basic Express"
C#
C#
string question = "hOW DOES mICROSOFT wORD DEAL WITH THE cAPS lOCK KEY?";
System.Text.StringBuilder sb = new System.Text.StringBuilder(question);
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#
// 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);
C#
Neste exemplo, um objeto StringBuilder é usado para criar uma cadeia de caracteres
com base em um conjunto de tipos numéricos:
C#
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
C#
int i = 0;
string s = "108";
bool result = int.TryParse(s, out i); //i now = 108
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#
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];
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
C#
using System;
class SampleCollection<T>
{
// Declare an array to store the data elements.
private T[] arr = new T[100];
int nextIndex = 0;
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.
using System;
class SampleCollection<T>
{
// Declare an array to store the data elements.
private T[] arr = new T[100];
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.
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.
Indexadores em interfaces
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
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
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
Para obter mais informações sobre como usar indexadores com uma interface, consulte
Indexadores de Interface.
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#
// 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;
}
}
C#
C#
Exemplo de consumo 2
C#
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#
class DayOfWeekCollection
{
Day[] days =
[
Day.Sunday, Day.Monday, Day.Tuesday, Day.Wednesday,
Day.Thursday, Day.Friday, Day.Saturday
];
Exemplo de consumo 3
C#
Programação robusta
Há duas maneiras principais nas quais a segurança e a confiabilidade de indexadores
podem ser melhoradas:
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
C#
// Indexer declaration:
string this[int index]
{
get;
set;
}
}
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;
}
}
C#
/* 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
*/
C#
string IIndexInterface.this[int index]
{
}
C#
C#
Confira também
Indexadores
Propriedades
Interfaces
Comparação entre propriedades e
indexadores (Guia de Programação em
C#)
Artigo • 26/10/2024
ノ 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.
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.
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.
Seções relacionadas
Para obter mais informações, consulte:
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#.
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
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.
3. Clique duas vezes no evento que deseja criar, por exemplo, o evento Load .
C#
C#
C#
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());
};
}
C#
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.
C#
publisher.RaiseCustomEvent -= HandleCustomEvent;
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; }
}
public ShapeContainer()
{
_list = new List<Shape>();
}
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();
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.
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*/));
}
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.
C#
namespace WrapTwoInterfaceEvents
{
using System;
Console.WriteLine("Drawing a shape.");
Confira também
Eventos
Representantes
Implementação de interface explícita
Como acionar eventos de classe base em classes derivadas
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#
Confira também
Eventos
event
Parâmetros de tipo genérico (Guia de
Programação em C#)
Artigo • 13/03/2024
C#
Confira também
System.Collections.Generic
Genéricos
Diferenças entre modelos C++ e genéricos C#
ノ 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 : 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 .
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.
C#
public T? FindFirstOccurrence(string s)
{
Node? current = head;
T? t = null;
C#
C#
C#
class Base { }
class Test<T, U>
where U : struct
where T : Base, new()
{ }
C#
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#
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.
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 .
C#
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#
Você pode usar o método anterior para combinar delegados que são do mesmo tipo:
C#
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#
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#
C#
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#
C#
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#
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
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:
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.
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.
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.
C#
class BaseNode { }
class BaseNodeGeneric<T> { }
// concrete type
class NodeConcrete<T> : BaseNode { }
C#
//No error
class Node1 : BaseNodeGeneric<int> { }
//Generates an error
//class Node2 : BaseNodeGeneric<T> {}
//Generates an error
//class Node3 : T {}
C#
//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> {}
C#
C#
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
}
Se uma classe genérica implementa uma interface, todas as instâncias dessa classe
podem ser convertidas nessa interface.
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.
lista. Neste exemplo, os elementos de lista são uma classe simples, Person , que
implementa IComparable<Person> .
C#
if (previous == null)
{
head = tmp;
}
else
{
previous.next = tmp;
}
previous = tmp;
swapped = true;
}
else
{
previous = current;
current = current.next;
}
}
} while (swapped);
}
}
int[] ages = new int[] { 45, 19, 28, 23, 18, 9, 108, 72, 30, 35 };
C#
C#
C#
interface IMonth<T> { }
interface IBaseInterface<T> { }
C#
interface IBaseInterface1<T> { }
interface IBaseInterface2<T, U> { }
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
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
C#
O exemplo de código a seguir mostra uma maneira de chamar o método usando int
para o argumento de tipo:
C#
C#
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>() { }
}
C#
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
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>();
ProcessItems<int>(arr);
ProcessItems<int>(list);
}
Confira também
System.Collections.Generic
Genéricos
matrizes
Genéricos
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#
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;
C#
class Stack<T>
{
public delegate void StackDelegate(T[] items);
}
C#
C#
class Stack<T>
{
public class StackEventArgs : System.EventArgs { }
public event StackEventHandler<Stack<T>, StackEventArgs>? StackEvent;
class SampleClass
{
public void HandleStackChange<T>(Stack<T> stack, Stack<T>.StackEventArgs
args) { }
}
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
O C# não permite parâmetros de modelo sem tipo, como template C<int i> {} .
O C# não permite que o parâmetro de tipo a ser usado como a classe base para o
tipo genérico.
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 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#
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#
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#
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
Confira também
System.Collections.Generic
Introdução aos genéricos
Genéricos
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#
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
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.
e VISÃO GERAL
Processo de especificação
i REFERÊNCIA
i REFERÊNCIA
Prefácio
Introdução
i REFERÊNCIA
Escopo
Referências normativas
Termos e definições
Descrição geral
Compatibilidade
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
i REFERÊNCIA
Gramática
Problemas de portabilidade
Biblioteca padrão
Comentários de documentação
Bibliografia