Dê a sua opinião sobre a experiência de download do PDF.
Documentação do C#
Saiba como escrever qualquer aplicativo usando a linguagem de programação C# na
plataforma .NET.
Aprender a programar em C#
b COMEÇAR AGORA
Aprenda C# | Tutoriais, cursos, vídeos e muito mais
q VIDEO
Série de vídeos para iniciantes no C#
Fluxo para iniciantes no C#
Série de vídeos para usuários intermediários do C#
g TUTORIAL
Tutoriais autoguiados
Tutorial no navegador
i REFERÊNCIA
C# no Q&A
Linguagens em fóruns da comunidade de tecnologia do .NET
C# no Stack Overflow
C# no Discord
Princípios básicos do C#
e VISÃO GERAL
Um tour pelo C#
Por dentro de um programa em C#
Série de vídeos sobre os destaques do C#
p CONCEITO
Sistema de tipos
Programação orientada a objeto
Técnicas funcionais
Exceções
Estilo de codificação
g TUTORIAL
Exibir linha de comando
Introdução às classes
C# orientado a objeto
Converter tipos
Correspondência de padrões
Usar o LINQ para consultar dados
Principais conceitos
e VISÃO GERAL
Conceitos de programação
f INÍCIO RÁPIDO
Métodos
Propriedades
Indexadores
Iterators
Delegados
Eventos
p CONCEITO
Tipos de referência anuláveis
Migrações de referência anuláveis
Resolver avisos anuláveis
LINQ (Consulta Integrada à Linguagem)
Controle de versão
Novidades
h NOVIDADES
Novidades do C# 11
Novidades do C# 10
Novidades do C# 9.0
Novidades no C# 8.0
g TUTORIAL
Explorar tipos de registro
Explorar instruções de nível superior
Explorar novos padrões
Atualizar interfaces com segurança
Criar mixins com interfaces
Explore os índices e os intervalos
Tipos de referência anuláveis
Explorar fluxos assíncronos
Escrever um manipulador de interpolação de cadeia de caracteres personalizado
i REFERÊNCIA
Alterações de falha no compilador C#
Compatibilidade de versões
Referência da linguagem C#
i REFERÊNCIA
Referência de linguagem
g g
Palavras-chave de C#
Operadores C#
Configurar versão da linguagem
Especificação da linguagem C# – rascunho do C# 7 em andamento
Mantenha contato
i REFERÊNCIA
.NET Developer Community
YouTube
Twitter
Um tour pela linguagem C#
Artigo • 15/02/2023
O C# (pronuncia-se "C Sharp") é uma linguagem de programação moderna, orientada a
objeto e fortemente tipada. O C# permite que os desenvolvedores criem muitos tipos
de aplicativos seguros e robustos que são executados no .NET. O C# tem suas raízes na
família de linguagens C e os programadores em C, C++, Java e JavaScript a
reconhecerão imediatamente. Este tour dá uma visão geral dos principais componentes
da linguagem em C# 8 e anterioe. Se quiser explorar a linguagem por meio de exemplos
interativos, experimente os tutoriais de Introdução à linguagem C#.
C# é uma linguagem de programação orientada a objetos e orientada a componentes.
C# fornece construções de linguagem para dar suporte diretamente a esses conceitos,
tornando C# uma linguagem natural para criação e uso de componentes de software.
Desde sua origem, o C# adicionou recursos para dar suporte a novas cargas de trabalho
e práticas emergentes de design de software. Em sua essência, o C# é uma linguagem
orientada a objeto. Você define os tipos e o comportamento deles.
Vários recursos do C# ajudam a criar aplicativos robustos e duráveis. A coleta de lixo
recupera automaticamente a memória ocupada por objetos não utilizados inacessíveis.
Tipos anuláveis são protegidos contra variáveis que não se referem a objetos alocados.
O tratamento de exceções fornece uma abordagem estruturada e extensível para
detecção e recuperação de erros. As expressões Lambda dão suporte a técnicas de
programação funcional. Consulta Integrada à Linguagem (LINQ) a sintaxe cria um
padrão comum para trabalhar com dados de qualquer fonte. O suporte à linguagem
para operações assíncronas fornece sintaxe para a criação de sistemas distribuídos. C#
tem um sistema de tipo unificado. Todos os tipos do C#, incluindo tipos primitivos,
como int e double , herdam de um único tipo de object raiz. Todos os tipos
compartilham um conjunto de operações comuns. Valores de qualquer tipo podem ser
armazenados, transportados e operados de maneira consistente. Além disso, o C# dá
suporte a tipos de referência e tipos de valor definidos pelo usuário. O C# permite a
alocação dinâmica de objetos e o armazenamento em linha de estruturas leves. O C# dá
suporte a métodos e tipos genéricos, que fornecem maior segurança e desempenho do
tipo. O C# fornece iteradores, que permitem que os implementadores de classes de
coleção definam comportamentos personalizados para o código do cliente.
O C# enfatiza o controle de versão para garantir que programas e bibliotecas possam
evoluir ao longo do tempo de maneira compatível. Aspectos do design do C# que
foram diretamente influenciados pelas considerações de controle de versão incluem os
modificadores separados virtual e override , as regras de resolução de sobrecarga de
método e suporte para declarações explícitas de membro de interface.
Arquitetura do .NET
Programas C# são executados no .NET, um sistema de execução virtual chamado CLR
(Common Language Runtime) e um conjunto de bibliotecas de classes. O CLR é a
implementação pela Microsoft da CLI (Common Language Infrastructure), um padrão
internacional. A CLI é a base para criar ambientes de execução e desenvolvimento nos
quais as linguagens e bibliotecas funcionam em conjunto perfeitamente.
O código-fonte escrito em C# é compilado em uma IL (linguagem intermediária) que
está em conformidade com a especificação da CLI. O código IL e os recursos, como
bitmaps e cadeias de caracteres, são armazenados em um assembly, normalmente com
uma extensão de .dll. Um assembly contém um manifesto que fornece informações
sobre os tipos, a versão e a cultura.
Quando o programa C# é executado, o assembly é carregado no CLR. Em seguida, o
CLR executará a compilação JIT (Just-In-Time) para converter o código de IL em
instruções nativas da máquina. O CLR também oferece outros serviços relacionados à
coleta automática de lixo, tratamento de exceções e gerenciamento de recursos. O
código que é executado pelo CLR é, às vezes, chamado de "código gerenciado".
"Código não gerenciado" é compilado em linguagem de máquina nativa e visa uma
plataforma específica.
Interoperabilidade de linguagem é um recurso importante do .NET. O código IL
produzido pelo compilador C# está em conformidade com a CTS (Common Type
Specification). O código IL gerado a partir do C# pode interagir com o código que foi
gerado a partir das versões do .NET do F#, do Visual Basic, do C++. Há mais de 20
outras linguagens compatíveis com CTS. Um único assembly pode conter vários
módulos gravados em diferentes idiomas do .NET. Os tipos podem fazer referência uns
aos outros como se fossem escritos na mesma linguagem.
Além dos serviços de tempo de execução, o .NET também inclui bibliotecas extensas.
Essas bibliotecas dão suporte a muitas cargas de trabalho diferentes. Eles são
organizados em namespaces que fornecem uma ampla variedade de funcionalidades
úteis. As bibliotecas incluem desde a entrada e a saída do arquivo até a manipulação de
cadeia de caracteres até a análise de XML até estruturas de aplicativos Web para
controles de Windows Forms. O aplicativo típico em C# usa bastante a biblioteca de
classes para lidar com tarefas comuns de "conexão".
Para obter mais informações sobre o .NET, consulte Visão geral do .NET.
Hello world
O programa "Hello, World" é usado tradicionalmente para introduzir uma linguagem de
programação. Este é para C#:
C#
using System;
class Hello
static void Main()
Console.WriteLine("Hello, World");
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#. Os namespaces contêm tipos e outros namespaces —
por exemplo, o namespace System contém uma quantidade de tipos, como a classe
Console referenciada no programa e diversos 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 .
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, um método estático denominado
Main serve como ponto de entrada de um programa C#.
A saída do programa é produzida pelo método WriteLine da classe Console no
namespace System . Essa classe é fornecida pelas bibliotecas de classe padrão, que, por
padrão, são referenciadas automaticamente pelo compilador.
Tipos e variáveis
Um tipo define a estrutura e o comportamento de qualquer dado em C#. A declaração
de um tipo pode incluir seus membros, tipo base, interfaces implementadas e operações
permitidas para esse tipo. Uma variável é um rótulo que se refere a uma instância de um
tipo específico.
Há dois tipos em C#: tipos de referência e tipos de valor. Variáveis de tipos de valor
contêm diretamente seus dados. Variáveis de tipos de referência armazenam referências
a seus dados, o último sendo conhecido como objetos. Com tipos de referência, é
possível que duas variáveis referenciem o mesmo objeto e, portanto, é possível que
operações em uma variável afetem o objeto referenciado por outra variável. Com tipos
de valor, cada variável tem sua própria cópia dos dados e não é possível que operações
em uma variável afetem a outra (exceto em variáveis de parâmetros ref e out ).
Um identificador é um nome de variável. Um identificador é uma sequência de
caracteres unicode sem nenhum espaço em branco. Um identificador pode ser uma
palavra reservada em C#, se for prefixado por @ . Usar uma palavra reservada como
identificador pode ser útil ao interagir com outros idiomas.
Os tipos de valor do C# são divididos em tipos simples, tipos de enumeração, tipos struct
e tipos de valor anulável e tipos de valor de tupla. Os tipos de referência do C# são
divididos em tipos de classe, tipos de interface, tipos de matriz e tipos delegados.
A estrutura de tópicos a seguir fornece uma visão geral do sistema de tipos do C#.
Tipos de valor
Tipos simples
Integral com sinal:: sbyte , short , int , long
Integral sem sinal:: byte , ushort , uint , ulong
Caracteres Unicode: char , que representa uma unidade de código UTF-16
Ponto flutuante binário de IEEE: float , double
Ponto flutuante decimal de alta precisão: decimal
Booliano: bool , que representa valores boolianos — valores que são true ou
false
Tipos enum
Tipos definidos pelo usuário do formulário enum E {...} . Um tipo enum é um
tipo distinto com constantes nomeadas. Cada tipo enum tem um tipo
subjacente, que deve ser um dos oito tipos integrais. O conjunto de valores
de um tipo enum é o mesmo que o conjunto de valores do tipo subjacente.
Tipos struct
Tipos definidos pelo usuário do formulário struct S {...}
Tipos de valor anuláveis
Extensões de todos os outros tipos de valor com um valor null
Tipos de valor de tupla
Tipos definidos pelo usuário do formulário (T1, T2, ...)
Tipos de referência
Tipos de aula
Classe base definitiva de todos os outros tipos: object
Cadeias de caracteres Unicode: string , que representa uma sequência de
unidades de código UTF-16
Tipos definidos pelo usuário do formulário class C {...}
Tipos de interface
Tipos definidos pelo usuário do formulário interface I {...}
Tipos de matriz
Unidimensional, multidimensional e irregular. Por exemplo: int[] , int[,] e
int[][]
Tipos delegados
Tipos definidos pelo usuário do formulário delegate int D(...)
Os programas em C# usam declarações de tipos para criar novos tipos. Uma declaração
de tipo especifica o nome e os membros do novo tipo. Seis das categorias do C# de
tipos são tipos definidos pelo usuário: tipos de classe, tipos struct, tipos de interface,
tipos enum, tipos delegados e tipos de valor de tupla. Você também pode declarar tipos
record , record struct ou record class . Os tipos de registro têm membros sintetizados
pelo compilador. Você usa registros principalmente para armazenar valores, com o
mínimo de comportamento associado.
Um tipo class define uma estrutura de dados que contém membros de dados
(campos) e membros de função (métodos, propriedades e outros). Os tipos de
classe dão suporte à herança única e ao polimorfismo, mecanismos nos quais as
classes derivadas podem estender e especializar as classes base.
Um tipo struct é semelhante a um tipo de classe que representa uma estrutura
com membros de dados e membros da função. No entanto, diferentemente das
classes, structs são tipos de valor e, normalmente, não precisam de alocação de
heap. Os tipos de estrutura não dão suporte à herança especificada pelo usuário, e
todos os tipos de structs são herdados implicitamente do tipo object .
Um tipo interface define um contrato como um conjunto nomeado de membros
públicos. Um class ou struct que implementa um interface deve fornecer
implementações de membros da interface. Um interface pode herdar de várias
interfaces base e um class ou struct pode implementar várias interfaces.
Um tipo delegate representa referências aos métodos com uma lista de
parâmetros e tipo de retorno específicos. Delegados possibilitam o tratamento de
métodos como entidades que podem ser atribuídos a variáveis e passadas como
parâmetros. Os delegados são análogos aos tipos de função fornecidos pelas
linguagens funcionais. Eles também são semelhantes ao conceito de ponteiros de
função encontrado em algumas outras linguagens. Ao contrário dos ponteiros de
função, os delegados são orientados a objetos e fortemente tipados.
Os tipos class , struct , interface e delegate dão suporte a genéricos e podem ser
parametrizados com outros tipos.
O C# dá suporte a matrizes unidimensionais e multidimensionais de qualquer tipo. Ao
contrário dos tipos listados acima, os tipos de matriz não precisam ser declarados antes
de serem usados. Em vez disso, os tipos de matriz são construídos seguindo um nome
de tipo entre colchetes. Por exemplo, int[] é uma matriz unidimensional de int ,
int[,] é uma matriz bidimensional de int , e int[][] é uma matriz unidimensional da
matriz unidimensional, ou uma matriz "denteada", de int .
Tipos anuláveis não exigem uma definição separada. Para cada tipo não nulo T há um
tipo anulável correspondente T? , que pode conter um valor adicional, null . Por
exemplo, int? é um tipo que pode conter qualquer inteiro de 32 bits ou o valor null e
string? é um tipo que pode conter qualquer string ou o valor null .
O sistema de tipos do C# é unificado, de modo que um valor de qualquer tipo pode ser
tratado como um object . Cada tipo no C#, direta ou indiretamente, deriva do tipo de
classe object , e object é a classe base definitiva de todos os tipos. Os valores de tipos
de referência são tratados como objetos simplesmente exibindo os valores como tipo
object . Os valores de tipos de valor são tratados como objetos, executando conversão
boxing e operações de conversão unboxing. No exemplo a seguir, um valor int é
convertido em object e volta novamente ao int .
C#
int i = 123;
object o = i; // Boxing
int j = (int)o; // Unboxing
Quando um valor de um tipo de valor é atribuído a uma referência object , uma "caixa"
é alocada para conter o valor. Essa caixa é uma instância de um tipo de referência e o
valor é copiado nessa caixa. Por outro lado, quando uma referência object é lançada
em um tipo de valor, object é feita uma verificação de que o referenciado é uma caixa
do tipo de valor correto. Se a verificação for bem-sucedida, o valor na caixa será
copiado para o tipo de valor.
O sistema de tipo unificado do C# significa efetivamente que os tipos de valor são
tratados como referências object "sob demanda". Devido à unificação, as bibliotecas de
uso geral que usam o tipo object podem ser usadas com todos os tipos derivados de
object , incluindo tipos de referência e tipos de valor.
Existem vários tipos de variáveis no C#, incluindo campos, elementos de matriz,
variáveis locais e parâmetros. As variáveis representam locais de armazenamento. Cada
variável tem um tipo que determina quais valores podem ser armazenados na variável,
conforme mostrado abaixo.
Tipo de valor não nulo
Um valor de tipo exato
Tipos de valor anulável
Um valor null ou um valor do tipo exato
objeto
Uma referência null , uma referência a um objeto de qualquer tipo de
referência ou uma referência a um valor de qualquer tipo de valor demarcado
Tipo de classe
Uma referência null , uma referência a uma instância desse tipo de classe ou
uma referência a uma instância de uma classe derivada desse tipo de classe
Tipo de interface
Uma referência null , uma referência a uma instância de um tipo de classe que
implementa esse tipo de interface ou uma referência a um valor demarcado de
um tipo de valor que implementa esse tipo de interface
Tipo de matriz
Uma referência null , uma referência a uma instância desse tipo de matriz ou
uma referência a uma instância de um tipo de matriz compatível
Tipo delegado
Uma referência null ou uma referência a uma instância de um tipo de
delegado compatível
Estrutura do programa
Os principais conceitos organizacionais em C# são programas, namespaces, tipos,
membros e assemblies. Os programas declaram tipos que contêm membros e podem
ser organizados em namespaces. Classes, structs e interfaces são exemplos de tipos.
Campos, métodos, propriedades e eventos são exemplos de membros. Quando os
programas em C# são compilados, eles são empacotados fisicamente em assemblies. Os
assemblies normalmente têm a extensão de arquivo .exe ou .dll , dependendo se eles
implementam aplicativos ou bibliotecas, respectivamente.
Como um pequeno exemplo, considere um assembly que contém o seguinte código:
C#
namespace Acme.Collections;
public class Stack<T>
Entry _top;
public void Push(T data)
_top = new Entry(_top, data);
public T Pop()
if (_top == null)
throw new InvalidOperationException();
T result = _top.Data;
_top = _top.Next;
return result;
class Entry
public Entry Next { get; set; }
public T Data { get; set; }
public Entry(Entry next, T data)
Next = next;
Data = data;
O nome totalmente qualificado dessa classe é Acme.Collections.Stack . A classe contém
vários membros: um campo chamado _top , dois métodos chamados Push e Pop e uma
classe aninhada chamada Entry . A classe Entry ainda contém três membros: um campo
chamado Next , um campo chamado Data e um construtor. Stack é uma classe
genérica. Ele tem um parâmetro de tipo, T que é substituído por um tipo concreto
quando é usado.
Uma pilha é uma coleção "primeiro a entrar - último a sair" (FILO). O elemento
adicionado à parte superior da stack. Quando um elemento é removido, ele é removido
da parte superior da pilha. O exemplo anterior declara o tipo Stack que define o
armazenamento e o comportamento de uma pilha. Você pode declarar uma variável que
se refere a uma instância do tipo Stack para usar essa funcionalidade.
Os assemblies contêm código executável na forma de instruções de IL (Linguagem
Intermediária) e informações simbólicas na forma de metadados. Antes de ser
executado, o compilador Just-In-Time (JIT) do .NET Common Language Runtime
converte o código IL em um assembly em código específico do processador.
Como um assembly é uma unidade autodescritiva da funcionalidade que contém o
código e os metadados, não é necessário de diretivas #include e arquivos de cabeçalho
no C#. Os tipos públicos e os membros contidos em um assembly específico são
disponibilizados em um programa C# simplesmente fazendo referência a esse assembly
ao compilar o programa. Por exemplo, esse programa usa a classe
Acme.Collections.Stack do assembly acme.dll :
C#
class Example
public static void Main()
var s = new Acme.Collections.Stack<int>();
s.Push(1); // stack contains 1
s.Push(10); // stack contains 1, 10
s.Push(100); // stack contains 1, 10, 100
Console.WriteLine(s.Pop()); // stack contains 1, 10
Console.WriteLine(s.Pop()); // stack contains 1
Console.WriteLine(s.Pop()); // stack is empty
Para compilar este programa, você precisaria fazer referência ao assembly que contém a
classe de pilha definida no exemplo anterior.
Os programas C# podem ser armazenados em vários arquivos de origem. Quando um
programa C# é compilado, todos os arquivos de origem são processados juntos e os
arquivos de origem podem se referenciar livremente. Conceitualmente, é como se todos
os arquivos de origem fossem concatenados em um arquivo grande antes de serem
processados. Declarações de encaminhamento nunca são necessárias em C#, porque,
com poucas exceções, a ordem de declaração é insignificante. O C# não limita um
arquivo de origem para declarar somente um tipo público nem requer o nome do
arquivo de origem para corresponder a um tipo declarado no arquivo de origem.
Outros artigos neste tour explicam esses blocos organizacionais.
Próximo
Tipos e membros de C#
Artigo • 02/06/2023
Por ser uma linguagem orientada a objeto, o C# oferece suporte aos conceitos de
encapsulamento, herança e polimorfismo. Uma classe pode herdar diretamente de uma
classe pai e pode implementar qualquer quantidade de interfaces. Métodos que
substituem métodos virtuais em uma classe pai exigem a palavra-chave override como
uma forma de evitar uma redefinição acidental. Em C#, um struct é como uma classe
simplificada. É um tipo alocado na pilha que pode implementar interfaces, mas não
oferece suporte à herança. O C# fornece os tipos record class e record struct , cuja
finalidade é armazenar, principalmente, valores de dados.
Todos os tipos são inicializados por meio de um construtor, um método responsável por
inicializar uma instância. Duas declarações de construtor têm um comportamento
exclusivo:
Um construtor sem parâmetros, que inicializa todos os campos para seu valor
padrão.
Um construtor primário, que declara os parâmetros necessários para uma instância
desse tipo.
Classes e objetos
As classes são os tipos do C# mais fundamentais. Uma classe é uma estrutura de dados
que combina ações (métodos e outros membros da função) e estado (campos) em uma
única unidade. Uma classe fornece uma definição das instâncias da classe, também
conhecidas como objetos. As classes dão suporte à herança e polimorfismo, mecanismos
nos quais classes derivadas podem estender e especializar classes base.
Novas classes são criadas usando declarações de classe. Uma declaração de classe
começa com um cabeçalho. O cabeçalho especifica:
Os atributos e modificadores da classe.
O nome da classe.
A classe base (ao herdar de uma classe base).
As interfaces implementadas pela classe.
O cabeçalho é seguido pelo corpo da classe, que consiste em uma lista de declarações
de membro escrita entre os delimitadores { e } .
O código a seguir mostra uma declaração de uma classe simples chamada Point :
C#
public class Point
public int X { get; }
public int Y { get; }
public Point(int x, int y) => (X, Y) = (x, y);
Instâncias de classes são criadas usando o operador new , que aloca memória para uma
nova instância, chama um construtor para inicializar a instância e retorna uma referência
à instância. As instruções a seguir criam dois objetos Point e armazenam referências a
esses objetos em duas variáveis:
C#
var p1 = new Point(0, 0);
var p2 = new Point(10, 20);
A memória ocupada por um objeto é recuperada automaticamente quando o objeto
não está mais acessível. Não é necessário nem possível desalocar explicitamente os
objetos em C#.
C#
var p1 = new Point(0, 0);
var p2 = new Point(10, 20);
Aplicativos ou testes para algoritmos podem precisar criar vários objetos Point . A classe
a seguir gera uma sequência de pontos aleatórios. O número de pontos é definido pelo
parâmetro do construtor primário. O parâmetro do construtor primário numPoints está
no escopo de todos os membros da classe:
C#
public class PointFactory(int numberOfPoints)
public IEnumerable<Point> CreatePoints()
var generator = new Random();
for (int i = 0; i < numberOfPoints; i++)
yield return new Point(generator.Next(), generator.Next());
Use a classe conforme mostrado no código a seguir:
C#
var factory = new PointFactory(10);
foreach (var point in factory.CreatePoints())
Console.WriteLine($"({point.X}, {point.Y})");
Parâmetros de tipo
Classes genérico definem parâmetros de tipo. Os parâmetros de tipo são uma lista de
nomes de parâmetros de tipo entre colchetes angulares. Os parâmetros de tipo seguem
o nome da classe. Em seguida, os parâmetros de tipo podem ser usados no corpo das
declarações de classe para definir os membros da classe. No exemplo a seguir, os
parâmetros de tipo de Pair são TFirst e TSecond :
C#
public class Pair<TFirst, TSecond>
public TFirst First { get; }
public TSecond Second { get; }
public Pair(TFirst first, TSecond second) =>
(First, Second) = (first, second);
Um tipo de classe que é declarado para pegar parâmetros de tipo é chamado de tipo de
classe genérica. Tipos struct, interface e delegate também podem ser genéricos.
Quando
a classe genérica é usada, os argumentos de tipo devem ser fornecidos para cada um
dos parâmetros de tipo:
C#
var pair = new Pair<int, string>(1, "two");
int i = pair.First; //TFirst int
string s = pair.Second; //TSecond string
Um tipo genérico com argumentos de tipo fornecidos, como Pair<int,string> acima, é
chamado de tipo construído.
Classes base
Uma declaração de classe pode especificar uma classe base. Após o nome da classe e os
parâmetros de tipo, insira dois-pontos e o nome da classe base. Omitir uma
especificação de classe base é o mesmo que derivar do object de tipo. No exemplo a
seguir, a classe base de Point3D é Point . No primeiro exemplo, a classe base de Point
é object :
C#
public class Point3D : Point
public int Z { get; set; }
public Point3D(int x, int y, int z) : base(x, y)
Z = z;
Uma classe herda os membros de sua classe base. Herança significa que uma classe
contém implicitamente quase todos os membros da classe base. Uma classe não herda
a instância, os construtores estáticos e o finalizador. Uma classe derivada pode adicionar
novos membros aos que ela herda, mas não pode remover a definição de um membro
herdado. No exemplo anterior, Point3D herda os membros X e Y de Point f e cada
instância Point3D contém três propriedades: X , Y e Z .
Existe uma conversão implícita de um tipo de classe para qualquer um de seus tipos de
classe base. Uma variável de um tipo de classe pode referenciar uma instância dessa
classe ou uma instância de qualquer classe derivada. Por exemplo, dadas as declarações
de classe anteriores, uma variável do tipo Point podem referenciar um Point ou um
Point3D :
C#
Point a = new(10, 20);
Point b = new Point3D(10, 20, 30);
Estruturas
As classes definem tipos que dão suporte à herança e ao polimorfismo. Elas permitem
que você crie comportamentos sofisticados com base nas hierarquias das classes
derivadas. Por outro lado, os tipos struct são mais simples e a finalidade principal deles
é armazenar valores de dados. Structs não podem declarar um tipo base; eles derivam
implicitamente de System.ValueType. Você não pode derivar outros tipos struct de um
tipo struct . Eles estão implicitamente selados.
C#
public struct Point
public double X { get; }
public double Y { get; }
public Point(double x, double y) => (X, Y) = (x, y);
Interfaces
Uma interface define um contrato que pode ser implementado por classes e structs.
Você define uma interface para declarar recursos compartilhados entre tipos distintos.
Por exemplo, a interface System.Collections.Generic.IEnumerable<T> define uma
maneira consistente de percorrer todos os itens de uma coleção, como uma matriz.
Uma interface pode conter métodos, propriedades, eventos e indexadores. Geralmente,
uma interface não fornece implementações dos membros que define. Ela simplesmente
especifica os membros que devem ser fornecidos por classes ou structs que
implementam a interface.
As interfaces podem empregar a herança múltipla. No exemplo a seguir, a interface
IComboBox herda de ITextBox e IListBox .
C#
interface IControl
void Paint();
interface ITextBox : IControl
void SetText(string text);
interface IListBox : IControl
void SetItems(string[] items);
interface IComboBox : ITextBox, IListBox { }
Classes e structs podem implementar várias interfaces. No exemplo a seguir, a classe
EditBox implementa IControl e IDataBound .
C#
interface IDataBound
void Bind(Binder b);
public class EditBox : IControl, IDataBound
public void Paint() { }
public void Bind(Binder b) { }
Quando uma classe ou struct implementa uma interface específica, as instâncias dessa
classe ou struct podem ser convertidas implicitamente para esse tipo de interface. Por
exemplo
C#
EditBox editBox = new();
IControl control = editBox;
IDataBound dataBound = editBox;
Enumerações
Um tipo Enum define um conjunto de valores constantes. O enum a seguir declara
constantes que definem diversos vegetais de raiz:
C#
public enum SomeRootVegetable
HorseRadish,
Radish,
Turnip
Você também pode definir enum a ser usado na combinação como sinalizadores. A
declaração a seguir define um conjunto de sinalizadores das quatro estações. Qualquer
combinação das estações pode ser aplicada, incluindo um valor All , que inclui todas as
estações:
C#
[Flags]
public enum Seasons
None = 0,
Summer = 1,
Autumn = 2,
Winter = 4,
Spring = 8,
All = Summer | Autumn | Winter | Spring
O exemplo a seguir mostra declarações de ambas as enumerações anteriores:
C#
var turnip = SomeRootVegetable.Turnip;
var spring = Seasons.Spring;
var startingOnEquinox = Seasons.Spring | Seasons.Autumn;
var theYear = Seasons.All;
Tipos anuláveis
Variáveis de qualquer tipo podem ser declaradas como não anuláveis ou anuláveis.
Uma variável anulável pode conter um valor adicional null , indicando nenhum valor.
Tipos de valor anulável (structs ou enums) são representados por System.Nullable<T>.
Tipos de referência não anuláveis e anuláveis são representados pelo tipo de referência
subjacente. A distinção é representada por metadados lidos pelo compilador e algumas
bibliotecas. O compilador fornece avisos quando as referências anuláveis são
desrreferenciadas sem antes verificar o valor delas em null . O compilador também
fornece avisos quando referências não anuláveis são atribuídas a um valor que pode ser
null . O exemplo a seguir declara um inteiro anulável, inicializando-o como null . Em
seguida, ele define o valor como 5 . Ele demonstra o mesmo conceito com uma cadeia
de caracteres anulável. Para saber mais, confira tipos de valor anulável e tipos de
referência nula.
C#
int? optionalInt = default;
optionalInt = 5;
string? optionalText = default;
optionalText = "Hello World.";
Tuplas
O C# dá suporte a tuplas, que fornecem sintaxe concisa para agrupar vários elementos
de dados em uma estrutura de dados leve. Você cria uma instância de uma tupla
declarando os tipos e nomes dos membros entre ( e ) , conforme mostrado no
exemplo a seguir:
C#
(double Sum, int Count) t2 = (4.5, 3);
Console.WriteLine($"Sum of {t2.Count} elements is {t2.Sum}.");
//Output:
//Sum of 3 elements is 4.5.
As tuplas são uma alternativa para a estrutura de dados com vários membros, sem usar
os blocos de construção descritos no próximo artigo.
Anterior Avançar
Blocos de construção de programas C#
Artigo • 07/04/2023
Os tipos descritos no artigo anterior desta série de Tour pelo C# são criados usando
estes blocos de construção:
Membros, como propriedades, campos, métodos e eventos.
Expressões
Instruções
Membros
Os membros de um class são membros estáticos ou membros de instância. Os
membros estáticos pertencem às classes e os membros de instância pertencem aos
objetos (instâncias de classes).
A lista a seguir fornece uma visão geral dos tipos de membros que uma classe pode
conter.
Constantes: valores constantes associados à classe
Campos: variáveis associadas à classe
Métodos: ações que podem ser executadas pela classe
Propriedades: ações associadas à leitura e à gravação de propriedades nomeadas
da classe
Indexadores: ações associadas à indexação de instâncias da classe como uma
matriz
Eventos: notificações que podem ser geradas pela classe
Operadores: conversões e operadores de expressão com suporte na classe
Construtores: ações necessárias para inicializar instâncias da classe ou a própria
classe
Finalizadores: ações feitas antes das instâncias da classe serem descartadas
permanentemente
Tipos: Tipos aninhados declarados pela classe
Acessibilidade
Cada membro de uma classe tem uma acessibilidade associada, que controla as regiões
do texto do programa que podem acessar o membro. Existem seis formas possíveis de
acessibilidade. Os modificadores de acesso são resumidos abaixo.
public : acesso ilimitado.
private : acesso limitado a essa classe.
protected : acesso limitado a essa classe ou a classes derivadas dessa classe.
internal : acesso limitado ao assembly atual ( .exe ou .dll ).
protected internal : acesso limitado a essa classe, a classes derivadas dessa classe
ou a classes dentro do mesmo assembly.
private protected : acesso limitado a essa classe ou a classes derivadas desse tipo
dentro do mesmo assembly.
Campos
Um campo é uma variável que está associada a uma classe ou a uma instância de uma
classe.
Um campo declarado com o modificador estático define um campo estático. Um campo
estático identifica exatamente um local de armazenamento. Não importa quantas
instâncias de uma classe são criadas, há sempre apenas uma cópia de um campo
estático.
Um campo declarado sem o modificador estático define um campo de instância. Cada
instância de uma classe contém uma cópia separada de todos os campos de instância
dessa classe.
No seguinte exemplo, cada instância da classe Color tem uma cópia separada dos
campos de instância R , G e B , mas há apenas uma cópia dos campos estáticos Black ,
White , Red , Green e Blue :
C#
public class Color
public static readonly Color Black = new(0, 0, 0);
public static readonly Color White = new(255, 255, 255);
public static readonly Color Red = new(255, 0, 0);
public static readonly Color Green = new(0, 255, 0);
public static readonly Color Blue = new(0, 0, 255);
public byte R;
public byte G;
public byte B;
public Color(byte r, byte g, byte b)
R = r;
G = g;
B = b;
Conforme mostrado no exemplo anterior, os campos somente leitura podem ser
declarados com um modificador readonly . A atribuição a um campo somente leitura só
pode ocorrer como parte da declaração do campo ou em um construtor da mesma
classe.
Métodos
Um método é um membro que implementa um cálculo ou uma ação que pode ser
executada por um objeto ou classe. Os métodos estáticos são acessados pela classe. Os
métodos de instância são acessados pelas instâncias da classe.
Os métodos podem ter uma lista de parâmetros, que representam valores ou referências
de variáveis passadas para o método. Os métodos têm um tipo de retorno, que
especifica o tipo do valor calculado e retornado pelo método. O tipo de retorno do
método será void se ele não retornar um valor.
Como os tipos, os métodos também podem ter um conjunto de parâmetros de tipo,
para que os quais os argumentos de tipo devem ser especificados quando o método é
chamado. Ao contrário dos tipos, os argumentos de tipo geralmente podem ser
inferidos de argumentos de uma chamada de método e não precisam ser fornecidos
explicitamente.
A assinatura de um método deve ser exclusiva na classe na qual o método é declarado.
A assinatura de um método consiste no nome do método, número de parâmetros de
tipo e número, modificadores e tipos de parâmetros. A assinatura de um método não
inclui o tipo de retorno.
Quando um corpo do método é apenas uma expressão, o método pode ser definido
usando um formato de expressão compacta, conforme mostrado no seguinte exemplo:
C#
public override string ToString() => "This is an object";
Parâmetros
Os parâmetros são usados para passar valores ou referências de variável aos métodos.
Os parâmetros de um método obtêm seus valores reais de argumentos que são
especificados quando o método é invocado. Há quatro tipos de parâmetros: parâmetros
de valor, parâmetros de referência, parâmetros de saída e matrizes de parâmetros.
Um parâmetro de valor é usado para passar argumentos de entrada. Um parâmetro de
valor corresponde a uma variável local que obtém seu valor inicial do argumento
passado para o parâmetro. As modificações em um parâmetro de valor não afetam o
argumento passado para o parâmetro.
Os parâmetros de valor podem ser opcionais, especificando um valor padrão para que
os argumentos correspondentes possam ser omitidos.
Um parâmetro de referência é usado para passar argumentos por referência. O
argumento passado para um parâmetro de referência deve ser uma variável com um
valor definido. Durante a execução do método, o parâmetro de referência representa o
mesmo local de armazenamento que a variável de argumento. Um parâmetro de
referência é declarado com o modificador ref . O exemplo a seguir mostra o uso de
parâmetros ref .
C#
static void Swap(ref int x, ref int y)
int temp = x;
x = y;
y = temp;
public static void SwapExample()
int i = 1, j = 2;
Swap(ref i, ref j);
Console.WriteLine($"{i} {j}"); // "2 1"
}
Um parâmetro de saída é usado para passar argumentos por referência. Ele é
semelhante a um parâmetro de referência, exceto que ele não requer que você atribua
explicitamente um valor ao argumento fornecido pelo chamador. Um parâmetro de
saída é declarado com o modificador out . O exemplo a seguir mostra o uso de
parâmetros out .
C#
static void Divide(int x, int y, out int quotient, out int remainder)
quotient = x / y;
remainder = x % y;
public static void OutUsage()
Divide(10, 3, out int quo, out int rem);
Console.WriteLine($"{quo} {rem}"); // "3 1"
Uma matriz de parâmetros permite que um número variável de argumentos sejam
passados para um método. Uma matriz de parâmetro é declarada com o modificador
params . Somente o último parâmetro de um método pode ser uma matriz de
parâmetros e o tipo de uma matriz de parâmetros deve ser um tipo de matriz
unidimensional. Os métodos Write e WriteLine da classe System.Console são bons
exemplos de uso da matriz de parâmetros. Eles são declarados conforme a seguir.
C#
public class Console
public static void Write(string fmt, params object[] args) { }
public static void WriteLine(string fmt, params object[] args) { }
// ...
Dentro de um método que usa uma matriz de parâmetros, a matriz de parâmetros se
comporta exatamente como um parâmetro regular de um tipo de matriz. No entanto,
em uma invocação de um método com uma matriz de parâmetros, é possível passar
apenas um argumento do tipo da matriz de parâmetro ou qualquer número de
argumentos do tipo de elemento da matriz de parâmetros. No último caso, uma
instância de matriz é automaticamente criada e inicializada com os argumentos
determinados. Esse exemplo
C#
int x, y, z;
x = 3;
y = 4;
z = 5;
Console.WriteLine("x={0} y={1} z={2}", x, y, z);
é equivalente ao escrito a seguir.
C#
int x = 3, y = 4, z = 5;
string s = "x={0} y={1} z={2}";
object[] args = new object[3];
args[0] = x;
args[1] = y;
args[2] = z;
Console.WriteLine(s, args);
Corpo do método e variáveis locais
Um corpo do método especifica as instruções a executar quando o método é invocado.
Um corpo de método pode declarar variáveis que são específicas para a invocação do
método. Essas variáveis são chamadas de variáveis locais. Uma declaração de variável
local especifica um nome de tipo, um nome de variável e, possivelmente, um valor
inicial. O exemplo a seguir declara uma variável local i com um valor inicial de zero e
uma variável local j sem valor inicial.
C#
class Squares
public static void WriteSquares()
int i = 0;
int j;
while (i < 10)
j = i * i;
Console.WriteLine($"{i} x {i} = {j}");
i++;
O C# requer que uma variável local seja atribuída definitivamente antes de seu valor
poder ser obtido. Por exemplo, se a declaração do anterior i não incluísse um valor
inicial, o compilador relataria um erro para usos subsequentes de i porque i não seria
definitivamente atribuído a esses pontos do programa.
Um método pode usar instruções return para retornar o controle é pelo chamador. Em
um método que retorna void , as instruções return não podem especificar uma
expressão. Em um método que retorna não nulo, as instruções return devem incluir
uma expressão que calcula o valor retornado.
Métodos estáticos e de instância
Um método declarado com um modificador static é um método estático. Um método
estático não funciona em uma instância específica e pode acessar diretamente apenas
membros estáticos.
Um método declarado sem o modificador static é um método de instância. Um
método de instância opera em uma instância específica e pode acessar membros
estáticos e de instância. A instância em que um método de instância foi invocado pode
ser explicitamente acessada como this . É um erro fazer referência a this um método
estático.
A seguinte classe Entity tem membros estáticos e de instância.
C#
class Entity
static int s_nextSerialNo;
int _serialNo;
public Entity()
_serialNo = s_nextSerialNo++;
public int GetSerialNo()
return _serialNo;
public static int GetNextSerialNo()
return s_nextSerialNo;
public static void SetNextSerialNo(int value)
s_nextSerialNo = value;
Cada instância Entity contém um número de série (e, presumivelmente, algumas outras
informações que não são mostradas aqui). O construtor Entity (que é como um
método de instância) inicializa a nova instância com o próximo número de série
disponível. Como o construtor é um membro de instância, ele tem permissão para
acessar tanto o campo de instância _serialNo quanto o campo estático s_nextSerialNo .
Os métodos estáticos GetNextSerialNo e SetNextSerialNo podem acessar o campo
estático s_nextSerialNo , mas seria um erro para eles acessar diretamente o campo de
instância _serialNo .
O exemplo a seguir mostra o uso da classe Entity .
C#
Entity.SetNextSerialNo(1000);
Entity e1 = new();
Entity e2 = new();
Console.WriteLine(e1.GetSerialNo()); // Outputs "1000"
Console.WriteLine(e2.GetSerialNo()); // Outputs "1001"
Console.WriteLine(Entity.GetNextSerialNo()); // Outputs "1002"
Os métodos estáticos SetNextSerialNo e GetNextSerialNo são invocados na classe,
enquanto o método de instância GetSerialNo é invocado em instâncias da classe.
Métodos abstratos, virtuais e de substituição
Você usa métodos virtuais, de substituição e abstratos para definir o comportamento de
uma hierarquia de tipos de classe. Como uma classe pode derivar de uma classe base,
essas classes derivadas podem precisar modificar o comportamento implementado na
classe base. Um método virtual é um declarado e implementado em uma classe base
em que qualquer classe derivada pode fornecer uma implementação mais específica.
Um método override é um método implementado em uma classe derivada que
modifica o comportamento da implementação da classe base. Um método abstract é
um método declarado em uma classe base que deve ser substituído em todas as classes
derivadas. Na verdade, métodos abstratos não definem uma implementação na classe
base.
As chamadas de método para métodos de instância podem ser resolvidas para
implementações de classe base ou de classe derivada. O tipo de uma variável determina
seu tipo em tempo de compilação. O tipo em tempo de compilação é o tipo que o
compilador usa para determinar os membros do item. No entanto, uma variável pode
ser atribuída a uma instância de qualquer tipo derivado do tipo em tempo de compilação
dela. O tipo de tempo de execução é o tipo da instância real à qual uma variável se
refere.
Quando um método virtual é invocado, o tipo de tempo de execução da instância para o
qual essa invocação ocorre determina a implementação real do método para invocar.
Em uma invocação de método não virtual, o tipo de tempo de compilação da instância é
o fator determinante.
Um método virtual pode ser substituído em uma classe derivada. Quando uma
declaração de método de instância inclui um modificador de substituição, o método
substitui um método virtual herdado com a mesma assinatura. Uma declaração de
método virtual introduz um novo método. Uma declaração de método de substituição
especializa um método virtual herdado existente fornecendo uma nova implementação
desse método.
Um método abstrato é um método virtual sem implementação. Um método abstract é
declarado com o modificador abstract e é permitido somente em classes abstratas. Um
método abstrato deve ser substituído em cada classe derivada não abstrata.
O exemplo a seguir declara uma classe abstrata, Expression , que representa um nó de
árvore de expressão e três classes derivadas, Constant , VariableReference e Operation ,
que implementam nós de árvore de expressão para operações aritméticas, referências
de variável e constantes. (Este exemplo é semelhante aos tipos de árvore de expressão,
mas não está relacionado a eles).
C#
public abstract class Expression
public abstract double Evaluate(Dictionary<string, object> vars);
public class Constant : Expression
double _value;
public Constant(double value)
_value = value;
public override double Evaluate(Dictionary<string, object> vars)
return _value;
public class VariableReference : Expression
string _name;
public VariableReference(string name)
_name = name;
public override double Evaluate(Dictionary<string, object> vars)
object value = vars[_name] ?? throw new Exception($"Unknown
variable: {_name}");
return Convert.ToDouble(value);
public class Operation : Expression
Expression _left;
char _op;
Expression _right;
public Operation(Expression left, char op, Expression right)
_left = left;
_op = op;
_right = right;
public override double Evaluate(Dictionary<string, object> vars)
double x = _left.Evaluate(vars);
double y = _right.Evaluate(vars);
switch (_op)
case '+': return x + y;
case '-': return x - y;
case '*': return x * y;
case '/': return x / y;
default: throw new Exception("Unknown operator");
As quatro classes anteriores podem ser usadas para modelar expressões aritméticas. Por
exemplo, usando instâncias dessas classes, a expressão x + 3 pode ser representada da
seguinte maneira.
C#
Expression e = new Operation(
new VariableReference("x"),
'+',
new Constant(3));
O método Evaluate de uma instância Expression é chamado para avaliar a expressão
especificada e produzir um valor double . O método recebe um argumento Dictionary
que contém nomes de variáveis (como chaves das entradas) e valores (como valores das
entradas). Como Evaluate é um método abstrato, classes não abstratas derivadas de
Expression devem substituir Evaluate .
Uma implementação de Evaluate do Constant retorna apenas a constante armazenada.
Uma implementação de VariableReference consulta o nome de variável no dicionário e
retorna o valor resultante. Uma implementação de Operation primeiro avalia os
operandos esquerdo e direito (chamando recursivamente seus métodos Evaluate ) e,
em seguida, executa a operação aritmética determinada.
O seguinte programa usa as classes Expression para avaliar a expressão x * (y + 2)
para valores diferentes de x e y .
C#
Expression e = new Operation(
new VariableReference("x"),
'*',
new Operation(
new VariableReference("y"),
'+',
new Constant(2)
)
);
Dictionary<string, object> vars = new();
vars["x"] = 3;
vars["y"] = 5;
Console.WriteLine(e.Evaluate(vars)); // "21"
vars["x"] = 1.5;
vars["y"] = 9;
Console.WriteLine(e.Evaluate(vars)); // "16.5"
Sobrecarga de método
A sobrecarga de método permite que vários métodos na mesma classe tenham o
mesmo nome, contanto que tenham assinaturas exclusivas. Ao compilar uma invocação
de um método sobrecarregado, o compilador usa a resolução de sobrecarga para
determinar o método específico para invocar. A resolução de sobrecarga encontra um
método que melhor corresponde aos argumentos. Se nenhuma correspondência melhor
puder ser encontrada, um erro será relatado. O exemplo a seguir mostra a resolução de
sobrecarga em vigor. O comentário para cada invocação no método UsageExample
mostra qual método é invocado.
C#
class OverloadingExample
static void F() => Console.WriteLine("F()");
static void F(object x) => Console.WriteLine("F(object)");
static void F(int x) => Console.WriteLine("F(int)");
static void F(double x) => Console.WriteLine("F(double)");
static void F<T>(T x) => Console.WriteLine($"F<T>(T), T is
{typeof(T)}");
static void F(double x, double y) => Console.WriteLine("F(double,
double)");
public static void UsageExample()
F(); // Invokes F()
F(1); // Invokes F(int)
F(1.0); // Invokes F(double)
F("abc"); // Invokes F<T>(T), T is System.String
F((double)1); // Invokes F(double)
F((object)1); // Invokes F(object)
F<int>(1); // Invokes F<T>(T), T is System.Int32
F(1, 1); // Invokes F(double, double)
Conforme mostrado pelo exemplo, um método específico sempre pode ser selecionado
por meio da conversão explícita dos argumentos para os tipos de parâmetro e
argumentos de tipo exatos.
Outros membros da função
Os membros que contêm código executável são conhecidos coletivamente como
membros de função de uma classe. A seção anterior descreve os métodos, que são os
principais tipos de membros de função. Esta seção descreve os outros tipos de
membros da função com suporte do C#: construtores, propriedades, indexadores,
eventos, operadores e finalizadores.
O exemplo a seguir mostra uma classe genérica chamada MyList<T> , que implementa
uma lista de objetos crescente. A classe contém vários exemplos dos tipos mais comuns
de membros da função.
C#
public class MyList<T>
const int DefaultCapacity = 4;
T[] _items;
int _count;
public MyList(int capacity = DefaultCapacity)
_items = new T[capacity];
public int Count => _count;
public int Capacity
get => _items.Length;
set
if (value < _count) value = _count;
if (value != _items.Length)
T[] newItems = new T[value];
Array.Copy(_items, 0, newItems, 0, _count);
_items = newItems;
public T this[int index]
get => _items[index];
set
if (!object.Equals(_items[index], value)) {
_items[index] = value;
OnChanged();
public void Add(T item)
if (_count == Capacity) Capacity = _count * 2;
_items[_count] = item;
_count++;
OnChanged();
protected virtual void OnChanged() =>
Changed?.Invoke(this, EventArgs.Empty);
public override bool Equals(object other) =>
Equals(this, other as MyList<T>);
static bool Equals(MyList<T> a, MyList<T> b)
if (Object.ReferenceEquals(a, null)) return
Object.ReferenceEquals(b, null);
if (Object.ReferenceEquals(b, null) || a._count != b._count)
return false;
for (int i = 0; i < a._count; i++)
if (!object.Equals(a._items[i], b._items[i]))
return false;
return true;
public event EventHandler Changed;
public static bool operator ==(MyList<T> a, MyList<T> b) =>
Equals(a, b);
public static bool operator !=(MyList<T> a, MyList<T> b) =>
!Equals(a, b);
Construtores
O C# dá suporte aos construtores estáticos e de instância. Um construtor de instância é
um membro que implementa as ações necessárias para inicializar uma instância de uma
classe. Um construtor estático é um membro que implementa as ações necessárias para
inicializar uma classe quando ele for carregado pela primeira vez.
Um construtor é declarado como um método sem nenhum tipo de retorno e o mesmo
nome que a classe continente. Se uma declaração de Construtor incluir o modificador
static , ele declara um construtor estático. Caso contrário, ela declara um construtor de
instância.
Construtores de instância podem ser sobrecarregados e ter parâmetros opcionais. Por
exemplo, a classe MyList<T> declara um construtor de instância com um único
parâmetro int opcional. Os construtores de instância são invocados usando o operador
new . As seguintes instruções alocam duas instâncias MyList<string> usando o
construtor da classe MyList com e sem o argumento opcional.
C#
MyList<string> list1 = new();
MyList<string> list2 = new(10);
Ao contrário de outros membros, os construtores de instância não são herdados. Uma
classe não tem construtores de instância diferentes dos construtores realmente
declarados na classe. Se nenhum construtor de instância for fornecido para uma classe,
então um construtor vazio sem parâmetros será fornecido automaticamente.
Propriedades
As propriedades são uma extensão natural dos campos. Elas são denominadas membros
com tipos associados, e a sintaxe para acessar os campos e as propriedades é a mesma.
No entanto, ao contrário dos campos, as propriedades não denotam locais de
armazenamento. Em vez disso, as propriedades têm acessadores que especificam as
instruções executadas quando seus valores são lidos ou gravados. Um acessador get lê o
valor. Um acessador set grava o valor.
Uma propriedade é declarada como um campo, exceto que a declaração termina com
um acessador get ou um acessador set escrito entre os delimitadores { e } em vez de
terminar com um ponto e vírgula. Uma propriedade que tem um acessador get e um
acessador set é uma propriedade de leitura/gravação. Uma propriedade que tem apenas
um acessador get é uma propriedade somente leitura. Uma propriedade que tem apenas
um acessador set é uma propriedade somente gravação.
Um acessador get corresponde a um método sem parâmetros com um valor retornado
do tipo de propriedade. Um acessador set corresponde a um método com um
parâmetro único chamado valor e nenhum tipo de retorno. O acessador get obtém o
valor da propriedade. O acessador set fornece um novo valor para a propriedade.
Quando a propriedade é o destino de uma atribuição, ou usa os operandos ++ ou -- , o
acessador set é invocado. Em outros casos em que a propriedade é referenciada, o
acessador get é invocado.
A classe MyList<T> declara duas propriedades, Count e Capacity , que são somente
leitura e leitura/gravação, respectivamente. O seguinte código é um exemplo de uso
dessas propriedades:
C#
MyList<string> names = new();
names.Capacity = 100; // Invokes set accessor
int i = names.Count; // Invokes get accessor
int j = names.Capacity; // Invokes get accessor
Como nos campos e métodos, o C# dá suporte a propriedades de instância e a
propriedades estáticas. As propriedades estáticas são declaradas com o modificador
estático e as propriedades de instância são declaradas sem ele.
Os acessadores de uma propriedade podem ser virtuais. Quando uma declaração de
propriedade inclui um modificador virtual , abstract ou override , ela se aplica aos
acessadores da propriedade.
Indexadores
Um indexador é um membro que permite que objetos sejam indexados da mesma
forma que uma matriz. Um indexador é declarado como uma propriedade, exceto se o
nome do membro for this seguido por uma lista de parâmetros escrita entre os
delimitadores [ e ] . Os parâmetros estão disponíveis nos acessadores do indexador.
Semelhante às propriedades, os indexadores podem ser de leitura-gravação, somente
leitura e somente gravação, e os acessadores de um indexador pode ser virtuais.
A classe MyList<T> declara um indexador único de leitura-gravação que usa um
parâmetro int . O indexador possibilita indexar instâncias MyList<T> com valores int .
Por exemplo:
C#
MyList<string> names = new();
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
for (int i = 0; i < names.Count; i++)
string s = names[i];
names[i] = s.ToUpper();
Os indexadores podem ser sobrecarregados. Uma classe pode declarar vários
indexadores, desde que o número ou os tipos de parâmetros deles sejam diferentes.
Eventos
Um evento é um membro que permite que uma classe ou objeto forneça notificações.
Um evento é declarado como um campo, exceto que a declaração inclui uma palavra-
chave event e o tipo deve ser um tipo delegado.
Dentro de uma classe que declara um membro de evento, o evento se comporta
exatamente como um campo de um tipo delegado (desde que o evento não seja
abstrato e não declare acessadores). O campo armazena uma referência a um delegado
que representa os manipuladores de eventos que foram adicionados ao evento. Se
nenhum manipulador de evento estiver presente, o campo será null .
A MyList<T> classe declara um único membro de evento chamado Changed , que indica
que um novo item foi adicionado à lista ou um item de lista foi alterado usando o
acessador do conjunto de indexadores. O evento Alterado é gerado pelo método virtual
OnChanged , que primeiro verifica se o evento é null (o que significa que nenhum
manipulador está presente). A noção de gerar um evento é precisamente equivalente a
invocar o delegado representado pelo evento. Não há construções de linguagem
especiais para gerar eventos.
Os clientes reagem a eventos por meio de manipuladores de eventos. Os manipuladores
de eventos são conectados usando o operador += e removidos usando o operador -= .
O exemplo a seguir anexa um manipulador de eventos para o evento Changed de um
MyList<string> .
C#
class EventExample
static int s_changeCount;
static void ListChanged(object sender, EventArgs e)
s_changeCount++;
public static void Usage()
var names = new MyList<string>();
names.Changed += new EventHandler(ListChanged);
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
Console.WriteLine(s_changeCount); // "3"
Para cenários avançados em que o controle do armazenamento subjacente de um
evento é desejado, uma declaração de evento pode fornecer explicitamente os
acessadores add e remove , que são semelhantes ao acessador set de uma propriedade.
Operadores
Um operador é um membro que define o significado da aplicação de um operador de
expressão específico para instâncias de uma classe. Três tipos de operadores podem ser
definidos: operadores unários, operadores binários e operadores de conversão. Todos
os operadores devem ser declarados como public e static .
A classe MyList<T> declara dois operadores: operator == e operator != . Esses
operadores substituídos dão um novo significado a expressões que aplicam esses
operadores a instâncias MyList . Especificamente, os operadores definem a igualdade de
duas instâncias MyList<T> ao comparar cada um dos objetos contidos usando os
métodos Equals deles. O exemplo a seguir usa o operador == para comparar duas
instâncias MyList<int> .
C#
MyList<int> a = new();
a.Add(1);
a.Add(2);
MyList<int> b = new();
b.Add(1);
b.Add(2);
Console.WriteLine(a == b); // Outputs "True"
b.Add(3);
Console.WriteLine(a == b); // Outputs "False"
O primeiro Console.WriteLine gera True porque as duas listas contêm o mesmo
número de objetos com os mesmos valores na mesma ordem. Como MyList<T> não
definiu operator == , o primeiro Console.WriteLine geraria False porque a e b
referenciam diferentes instâncias MyList<int> .
Finalizadores
Um finalizador é um membro que implementa as ações necessárias para finalizar uma
instância de uma classe. Normalmente, um finalizador é necessário para liberar recursos
não gerenciados. Os finalizadores não podem ter parâmetros, eles não podem ter
modificadores de acessibilidade e não podem ser invocados explicitamente. O
finalizador de uma instância é invocado automaticamente durante a coleta de lixo. Para
obter mais informações, confira o artigo sobre finalizadores.
O coletor de lixo tem latitude ampla ao decidir quando coletar objetos e executar os
finalizadores. Especificamente, o tempo das invocações de finalizadores não é
determinístico e eles podem ser executados em qualquer thread. Para esses e outros
motivos, as classes devem implementar os finalizadores apenas quando não houver
outras soluções viáveis.
A instrução using fornece uma abordagem melhor para a destruição de objetos.
Expressões
Expressões são construídas a partir de operandos e operadores. Os operadores de uma
expressão indicam quais operações devem ser aplicadas aos operandos. Exemplos de
operadores incluem + , - , * , / e new . Exemplos de operandos incluem literais, campos,
variáveis locais e expressões.
Quando uma expressão contém vários operadores, a precedência dos operadores
controla a ordem na qual os operadores individuais são avaliados. Por exemplo, a
expressão x + y * z é avaliada como x + (y * z) porque o operador * tem
precedência maior do que o operador + .
Quando ocorre um operando entre dois operadores com a mesma precedência, a
associatividade dos operadores controla a ordem na qual as operações são executadas:
Exceto para os operadores de atribuição e os operadores de avaliação de nulo,
todos os operadores binários são associativos à esquerda, o que significa que as
operações são executadas da esquerda para a direita. Por exemplo, x + y + z é
avaliado como (x + y) + z .
Os operadores de atribuição, os operadores de avaliação de nulo ?? e os
operadores ??= , bem como o operador condicional ?: , são associativo à direita, o
que significa que as operações são executadas da direita para a esquerda. Por
exemplo, x = y = z é avaliado como x = (y = z) .
Precedência e associatividade podem ser controladas usando parênteses. Por exemplo,
x + y * z primeiro multiplica y por z e, em seguida, adiciona o resultado a x , mas (x
+ y) * z primeiro adiciona x e y e, em seguida, multiplica o resultado por z .
A maioria dos operadores pode ser sobrecarregada. A sobrecarga de operador permite
que implementações de operador definidas pelo usuário sejam especificadas para
operações em que um ou ambos os operandos são de um tipo struct ou de classe
definida pelo usuário.
O C# fornece operadores para executar operações aritméticas, lógicas, de bits e
deslocamentos e comparações de igualdade e de ordem.
Para obter a lista completa de operadores do C# ordenada pelo nível de precedência,
confira Operadores do C#.
Instruções
As ações de um programa são expressas usando instruções. O C# oferece suporte a
vários tipos diferentes de instruções, algumas delas definidas em termos de instruções
inseridas.
Um bloco permite a produção de várias instruções em contextos nos quais uma
única instrução é permitida. Um bloco é composto por uma lista de instruções
escritas entre os delimitadores { e } .
Instruções de declaração são usadas para declarar constantes e variáveis locais.
Instruções de expressão são usadas para avaliar expressões. As expressões que
podem ser usadas como instruções incluem chamadas de método, alocações de
objeto usando o operador new , atribuições usando = e os operadores de
atribuição compostos, operações de incremento e decremento usando os
operadores ++ e -- e as expressões await .
Instruções de seleção são usadas para selecionar uma dentre várias instruções
possíveis para execução com base no valor de alguma expressão. Esse grupo
contém as instruções if e switch .
Instruções de iteração são usadas para executar repetidamente uma instrução
inserida. Esse grupo contém as instruções while , do , for e foreach .
Instruções de salto são usadas para transferir o controle. Esse grupo contém as
instruções break , continue , goto , throw , return e yield .
A instrução try ... catch é usada para capturar exceções que ocorrem durante a
execução de um bloco, e a instrução try ... finally é usada para especificar o
código de finalização que é executado sempre, se uma exceção ocorrer ou não.
As instruções checked e unchecked são usadas para controlar o contexto de
verificação de estouro para operações e conversões aritméticas do tipo integral.
A instrução lock é usada para obter o bloqueio de exclusão mútua para um
determinado objeto, executar uma instrução e, em seguida, liberar o bloqueio.
A instrução using é usada para obter um recurso, executar uma instrução e, em
seguida, descartar esse recurso.
A seguinte lista mostra os tipos de instruções que podem ser usados:
Declaração de variável local.
Declaração constante local.
Instrução de expressão.
Instrução if .
Instrução switch .
Instrução while .
Instrução do .
Instrução for .
Instrução foreach .
Instrução break .
Instrução continue .
Instrução goto .
Instrução return .
Instrução yield .
Instruções throw e instruções try .
Instruções checked e unchecked .
Instrução lock .
Instrução using .
Anterior Avançar
Principais áreas da linguagem C#
Artigo • 20/03/2023
Este artigo apresenta os principais recursos da linguagem C#.
Matrizes, coleções e LINQ
C# e .NET fornecem muitos tipos de coleção diferentes. As matrizes têm a sintaxe
definida pela linguagem. Tipos de coleção genéricos são listados no namespace
System.Collections.Generic. As coleções especializadas incluem System.Span<T> para
acessar a memória contínua no registro de ativação e System.Memory<T> para acessar
a memória contínua no heap gerenciado. Todas as coleções, incluindo matrizes,
Span<T>e Memory<T> compartilham um princípio unificador para iteração. Você usa a
interface System.Collections.Generic.IEnumerable<T>. Esse princípio unificador significa
que qualquer um dos tipos de coleção pode ser usado com consultas LINQ ou outros
algoritmos. Você escreve métodos usando IEnumerable<T> e esses algoritmos
funcionam com qualquer coleção.
Matrizes
Uma matriz é uma estrutura de dados que contém algumas variáveis acessadas por
meio de índices calculados. As variáveis contidas em uma matriz, também chamadas de
elementos da matriz, são do mesmo tipo. Esse tipo é chamado de tipo do elemento da
matriz.
Os tipos de matriz são tipos de referência, e a declaração de uma variável de matriz
simplesmente reserva espaço para uma referência a uma instância de matriz. As
instâncias de matriz reais são criadas dinamicamente em tempo de operação usando o
operador new . A operação new especifica o comprimento da nova instância de matriz,
que é fixo durante todo o tempo de vida da instância. Os índices dos elementos de uma
matriz variam de 0 a Length - 1 . O operador new inicializa automaticamente os
elementos de uma matriz usando o valor padrão, que, por exemplo, é zero para todos
os tipos numéricos e null para todos os tipos de referência.
O exemplo a seguir cria uma matriz de elementos int , inicializa a matriz e imprime o
conteúdo dela.
C#
int[] a = new int[10];
for (int i = 0; i < a.Length; i++)
a[i] = i * i;
for (int i = 0; i < a.Length; i++)
Console.WriteLine($"a[{i}] = {a[i]}");
Este exemplo cria e opera em uma matriz unidimensional. O C# também oferece
suporte a matrizes multidimensionais. O número de dimensões de um tipo de matriz,
também conhecido como Ordem do tipo de matriz, é uma unidade a mais que o
número de vírgulas entre os colchetes do tipo de matriz. O exemplo a seguir aloca uma
matriz unidimensional, bidimensional e tridimensional, respectivamente.
C#
int[] a1 = new int[10];
int[,] a2 = new int[10, 5];
int[,,] a3 = new int[10, 5, 2];
A matriz a1 contém 10 elementos, a matriz a2 contém 50 (10 × 5) elementos e a matriz
a3 contém 100 (10 × 5 × 2) elementos.
O tipo do elemento de uma matriz pode ser
qualquer tipo, incluindo um tipo de matriz. Uma matriz com elementos do tipo matriz
geralmente é chamada de matriz denteada, pois os comprimentos das matrizes dos
elementos variam. O exemplo a seguir aloca uma matriz de matrizes de int :
C#
int[][] a = new int[3][];
a[0] = new int[10];
a[1] = new int[5];
a[2] = new int[20];
A primeira linha cria uma matriz com três elementos, cada um do tipo int[] , e cada um
com um valor inicial de null . Em seguida, as próximas linhas inicializam os três
elementos com referências a instâncias de matriz individuais de comprimentos variados.
O operador new permite que os valores iniciais dos elementos da matriz sejam
especificados usando um inicializador de matriz, que é uma lista de expressões escritas
entre os delimitadores { e } . O exemplo a seguir aloca e inicializa um int[] com três
elementos.
C#
int[] a = new int[] { 1, 2, 3 };
O comprimento da matriz é inferido do número de expressões entre { e } . A
inicialização da matriz pode ser reduzida ainda mais, de modo que o tipo de matriz não
precisa ser reafirmado.
C#
int[] a = { 1, 2, 3 };
Ambos os exemplos anteriores são equivalentes ao seguinte código:
C#
int[] t = new int[3];
t[0] = 1;
t[1] = 2;
t[2] = 3;
int[] a = t;
A instrução foreach pode ser usada para enumerar os elementos de qualquer coleção.
O seguinte código enumera a matriz do exemplo anterior:
C#
foreach (int item in a)
Console.WriteLine(item);
A instrução foreach usa a interface IEnumerable<T>, para que possa funcionar com
qualquer coleção.
Interpolação de cadeia de caracteres
A interpolação de cadeia de caracteres do C# permite formatar cadeias de caracteres
definindo expressões cujos resultados são colocados em uma cadeia de caracteres de
formato. Por exemplo, a seguinte demonstração imprime a temperatura em
determinado dia de um conjunto de dados meteorológicos:
C#
Console.WriteLine($"The low and high temperature on {weatherData.Date:MM-dd-
yyyy}");
Console.WriteLine($" was {weatherData.LowTemp} and
{weatherData.HighTemp}.");
// Output (similar to):
// The low and high temperature on 08-11-2020
// was 5 and 30.
Uma cadeia de caracteres interpolada é declarada usando o token $ . A interpolação de
cadeia de caracteres avalia as expressões entre { e } , em seguida, converte o resultado
em um string e substitui o texto entre os colchetes pelo resultado da cadeia de
caracteres da expressão. O : na primeira expressão, {weatherData.Date:MM-dd-yyyy}
especifica a cadeia de caracteres de formato. No exemplo anterior, ele especifica que a
data deve ser impressa no formato "MM-dd-aaaa".
Correspondência de padrões
A linguagem C# fornece expressões de padrões correspondentes para consultar o
estado de um objeto e executar o código com base nesse estado. Você pode
inspecionar tipos e valores de propriedades e campos para determinar qual ação deve
ser tomada. Você também pode inspecionar os elementos de uma lista ou matriz. A
expressão switch é a expressão primária para correspondência de padrões.
Delegados e expressões lambda
Um tipo delegado representa referências aos métodos com uma lista de parâmetros e
tipo de retorno específicos. Delegados possibilitam o tratamento de métodos como
entidades que podem ser atribuídos a variáveis e passadas como parâmetros.
Delegados são semelhantes ao conceito de ponteiros de função encontrado em
algumas outras linguagens. Ao contrário dos ponteiros de função, os delegados são
orientados a objetos e fortemente tipados.
O exemplo a seguir declara e usa um tipo delegado chamado Function .
C#
delegate double Function(double x);
class Multiplier
double _factor;
public Multiplier(double factor) => _factor = factor;
public double Multiply(double x) => x * _factor;
class DelegateExample
static double[] Apply(double[] a, Function f)
var result = new double[a.Length];
for (int i = 0; i < a.Length; i++) result[i] = f(a[i]);
return result;
public static void Main()
double[] a = { 0.0, 0.5, 1.0 };
double[] squares = Apply(a, (x) => x * x);
double[] sines = Apply(a, Math.Sin);
Multiplier m = new(2.0);
double[] doubles = Apply(a, m.Multiply);
Uma instância do tipo delegado Function pode fazer referência a qualquer método que
usa um argumento double e retorna um valor double . O método Apply aplica um
determinado Function aos elementos de um double[] , retornando um double[] com
os resultados. No método Main , Apply é usado para aplicar três funções diferentes para
um double[] .
Um delegado pode referenciar uma expressão lambda para criar uma função anônima
(como (x) => x * x no exemplo anterior), um método estático (como Math.Sin no
exemplo anterior) ou um método de instância (como m.Multiply no exemplo anterior).
Um delegado que referencia um método de instância também referencia um objeto
específico, e quando o método de instância é invocado por meio do delegado, esse
objeto se torna this na invocação.
Os delegados também podem ser criados usando funções anônimas ou expressões
lambda, que são "métodos em linha" criados quando declarados. As funções anônimas
podem ver as variáveis locais dos métodos ao redor. O seguinte exemplo não cria uma
classe:
C#
double[] doubles = Apply(a, (double x) => x * 2.0);
Um delegado não sabe nem se importa com a classe do método a que ele faz
referência. O método referenciado deve ter os mesmos parâmetros e tipo de retorno
que o delegado.
async / await
O C# dá suporte a programas assíncronos com duas palavras-chave: async e await .
Você adiciona o modificador async a uma declaração de método para declarar que o
método é assíncrono. O operador await informa ao compilador para aguardar de
maneira assíncrona um resultado para concluir. O controle é retornado ao chamador e o
método retorna uma estrutura que gerencia o estado do trabalho assíncrono. A
estrutura normalmente é um System.Threading.Tasks.Task<TResult>, mas pode ser
qualquer tipo que dê suporte ao padrão awaiter. Esses recursos permitem que você
escreva código que lê como seu equivalente síncrono, mas é executado de maneira
assíncrona. Por exemplo, o seguinte código baixa a home page para documentos da
Microsoft:
C#
public async Task<int> RetrieveDocsHomePage()
var client = new HttpClient();
byte[] content = await
client.GetByteArrayAsync("https://learn.microsoft.com/");
Console.WriteLine($"{nameof(RetrieveDocsHomePage)}: Finished
downloading.");
return content.Length;
Este pequeno exemplo mostra os principais recursos para programação assíncrona:
A declaração do método inclui o modificador async .
O corpo do método await é o retorno do método GetByteArrayAsync .
O tipo especificado na instrução return corresponde ao argumento de tipo da
declaração Task<T> do método. (Um método que retorna Task usaria instruções
return sem nenhum argumento).
Atributos
Tipos, membros e outras entidades em um programa C# dão suporte a modificadores
que controlam determinados aspectos de seu comportamento. Por exemplo, a
acessibilidade de um método é controlada usando os modificadores public , protected ,
internal e private . O C# generaliza essa funcionalidade, de modo que os tipos
definidos pelo usuário de informações declarativas podem ser anexados a entidades de
programa e recuperados no tempo de execução. Os programas especificam essas
informações declarativas definindo e usando atributos.
O exemplo a seguir declara um atributo HelpAttribute que pode ser colocado em
entidades de programa para fornecem links para a documentação associada.
C#
public class HelpAttribute : Attribute
string _url;
string _topic;
public HelpAttribute(string url) => _url = url;
public string Url => _url;
public string Topic
get => _topic;
set => _topic = value;
Todas as classes de atributo derivam da classe base Attribute, fornecida pela biblioteca
.NET. Os atributos podem ser aplicados, fornecendo seu nome, junto com quaisquer
argumentos, dentro dos colchetes pouco antes da declaração associada. Se o nome de
um atributo termina em Attribute , essa parte do nome pode ser omitida quando o
atributo é referenciado. Por exemplo, o atributo HelpAttribute pode ser usado da
seguinte maneira.
C#
[Help("https://learn.microsoft.com/dotnet/csharp/tour-of-csharp/features")]
public class Widget
[Help("https://learn.microsoft.com/dotnet/csharp/tour-of-
csharp/features",
Topic = "Display")]
public void Display(string text) { }
Este exemplo anexa um HelpAttribute à classe Widget . Ele adiciona outro
HelpAttribute ao método Display na classe. Os construtores públicos de uma classe de
atributo controlam as informações que devem ser fornecidas quando o atributo é
anexado a uma entidade de programa. As informações adicionais podem ser fornecidas
ao referenciar propriedades públicas de leitura-gravação da classe de atributo (como a
referência anterior à propriedade Topic ).
Os metadados definidos por atributos podem ser lidos e manipulados em tempo de
execução usando reflexão. Quando um atributo específico é solicitado usando essa
técnica, o construtor da classe de atributo é invocado com as informações fornecidas na
origem do programa. A instância de atributo resultante é retornada. Se forem fornecidas
informações adicionais por meio de propriedades, essas propriedades serão definidas
para os valores fornecidos antes que a instância do atributo seja retornada.
O exemplo de código a seguir demonstra como obter as instâncias HelpAttribute
associadas à classe Widget e seu método Display .
C#
Type widgetType = typeof(Widget);
object[] widgetClassAttributes =
widgetType.GetCustomAttributes(typeof(HelpAttribute), false);
if (widgetClassAttributes.Length > 0)
HelpAttribute attr = (HelpAttribute)widgetClassAttributes[0];
Console.WriteLine($"Widget class help URL : {attr.Url} - Related topic :
{attr.Topic}");
System.Reflection.MethodInfo displayMethod =
widgetType.GetMethod(nameof(Widget.Display));
object[] displayMethodAttributes =
displayMethod.GetCustomAttributes(typeof(HelpAttribute), false);
if (displayMethodAttributes.Length > 0)
HelpAttribute attr = (HelpAttribute)displayMethodAttributes[0];
Console.WriteLine($"Display method help URL : {attr.Url} - Related topic
: {attr.Topic}");
Saiba mais
Você pode explorar mais sobre o C# experimentando um de nossos tutoriais.
Anterior
Introdução ao C#
Artigo • 15/02/2023
Bem-vindo aos tutoriais de introdução ao C#. Essas lições começam com código
interativo que pode ser executado em seu navegador. Você pode aprender as noções
básicas do C# na série de vídeos de introdução ao C# antes de iniciar essas lições
interativas.
https://docs.microsoft.com/shows/CSharp-101/What-is-C/player
As primeiras lições explicam os conceitos de C# usando pequenos snippets de código.
Você aprenderá os conceitos básicos da sintaxe de C# e como trabalhar com tipos de
dados como cadeias de caracteres, números e valores boolianos. É tudo interativo e
você começará a gravar e executar o código em questão de minutos. Estas primeiras
lições não exigem conhecimento prévio de programação ou da linguagem C#.
Você pode experimentar esses tutoriais em ambientes diferentes. Os conceitos que você
aprenderá são os mesmos. A diferença é qual experiência você prefere:
No navegador, na plataforma de documentos: essa experiência incorpora uma
janela de código C# executável em páginas de documentos. Você escreve e
executa o código C# no navegador.
Na experiência do Microsoft Learn. Este roteiro de aprendizagem contém vários
módulos que ensinam as noções básicas de C#.
No Jupyter no Binder . Você pode experimentar o código C# em um notebook
Jupyter no Binder.
Em seu computador local. Depois de explorar online, você pode baixar o SDK do
.NET e criar programas em seu computador.
Todos os tutoriais de introdução posteriores à lição Olá, Mundo estão disponíveis por
meio da experiência de navegador online ou em seu próprio ambiente de
desenvolvimento local. No final de cada tutorial, você decidirá se deseja continuar com
a próxima lição online ou no próprio computador. Há links para ajudar você a configurar
seu ambiente e continuar com o próximo tutorial no computador.
Olá, Mundo
No tutorial Olá, Mundo, você criará o programa C# mais básico. Você explorará o tipo
string e como trabalhar com texto. Você também pode usar o caminho no Microsoft
Learn ou o Jupyter no Binder .
Números em C#
No tutorial Números em C#, você aprenderá como os computadores armazenam
números e como executar cálculos com diferentes tipos de número. Você aprenderá os
conceitos básicos de arredondamento e como executar cálculos matemáticos usando
C#. Este tutorial também está disponível para execução local no seu computador.
Esse tutorial pressupõe que você já tenha concluído a lição Olá, Mundo.
Loops e branches
O tutorial Branches e loops ensina os conceitos básicos da seleção de diferentes
caminhos de execução de código com base nos valores armazenados em variáveis. Você
aprenderá os conceitos básicos do fluxo de controle, que são os fundamentos de como
os programas tomam decisões e escolhem ações diferentes. Este tutorial também está
disponível para execução local no seu computador.
Esse tutorial pressupõe que você já tenha concluído as lições Olá, Mundo e Números
em C#.
Coleções de lista
A lição Coleções de lista fornece um tour pelo tipo Coleções de lista que armazena as
sequências de dados. Você aprenderá a adicionar e remover itens, pesquisar itens e
classificar listas. Você explorará os diferentes tipos de listas. Este tutorial também está
disponível para execução local no seu computador.
Esse tutorial pressupõe que você já tenha concluído as lições listadas acima.
Exemplos básicos do LINQ
Este exemplo requer a ferramenta global dotnet-try . Depois de instalar a ferramenta e
clonar o repositório try-samples , você poderá aprender LINQ (Consulta Integrada à
Linguagem) por meio de um conjunto de amostras básicas que você pode executar
interativamente. Você pode explorar diferentes maneiras de consultar, explorar e
transformar sequências de dados.
Configurar o ambiente local
Artigo • 10/05/2023
A primeira etapa para executar um tutorial em seu computador é configurar um
ambiente de desenvolvimento.
Recomendamos o Visual Studio para Windows ou Mac. Você pode baixar uma
versão gratuita na página de downloads do Visual Studio . O Visual Studio inclui
o SDK do .NET.
Você também pode usar o editor do Visual Studio Code . Será preciso instalar o
SDK do .NET mais recente separadamente.
Se preferir outro editor, você precisará instalar o SDK do .NET mais recente.
Fluxo de desenvolvimento de aplicativos básico
As instruções nesses tutoriais pressupõem que você esteja usando a CLI do .NET para
criar, compilar e executar aplicativos. Você usará os comandos a seguir:
dotnet new cria um aplicativo. Este comando gera os arquivos e ativos necessários
para o seu aplicativo. Todos os tutoriais de introdução ao C# usam o tipo de
aplicativo console . Depois de conhecer as noções básicas, você poderá expandir
para outros tipos de aplicativo.
dotnet build cria o executável.
dotnet run executa o executável.
Se você usar o Visual Studio 2019 para estes tutoriais, escolherá uma seleção de menu
do Visual Studio quando um tutorial o orientar a executar um destes comandos da CLI:
File>New>Project cria um aplicativo.
O modelo de projeto Console Application é recomendado.
Você terá a opção de especificar uma estrutura de destino. Os tutoriais abaixo
funcionam melhor ao direcionar o .NET 5 ou superior.
Build>Build Solution cria o executável.
Debug>Start Without Debugging executa o executável.
Escolha seu tutorial
Você pode iniciar com qualquer um dos seguintes tutoriais:
Números em C#
No tutorial Números em C#, você aprenderá como os computadores armazenam
números e como executar cálculos com diferentes tipos de número. Você aprenderá os
conceitos básicos de arredondamento e como executar cálculos matemáticos usando
C#.
Esse tutorial pressupõe a conclusão da lição Olá, Mundo.
Loops e branches
O tutorial Branches e loops ensina os conceitos básicos da seleção de diferentes
caminhos de execução de código com base nos valores armazenados em variáveis. Você
aprenderá os conceitos básicos do fluxo de controle, que são os fundamentos de como
os programas tomam decisões e escolhem ações diferentes.
Esse tutorial pressupõe a conclusão das lições Olá, Mundo e Números em C#.
Coleções de lista
A lição Coleções de lista fornece um tour pelo tipo Coleções de lista que armazena as
sequências de dados. Você aprenderá a adicionar e remover itens, pesquisar itens e
classificar listas. Você explorará os diferentes tipos de listas.
Esse tutorial pressupõe a conclusão das lições listadas acima.
Como usar números inteiros e de ponto
flutuante em C#
Artigo • 10/05/2023
Este tutorial ensina tipos numéricos em C#. Você escreverá pequenas quantidades de
código, depois compilará e executará esse código. O tutorial contém uma série de lições
que exploram números e operações matemáticas em C#. Estas lições ensinam os
princípios básicos da linguagem C#.
Dica
Para colar um snippet de código dentro do modo de foco, você deve usar o atalho
de teclado ( Ctrl + v ou cmd + v ).
Pré-requisitos
Este tutorial espera que você tenha um computador configurado para desenvolvimento
local. Consulte Configurar seu ambiente local para obter instruções de instalação e uma
visão geral do desenvolvimento de aplicativos no .NET.
Se você não quiser configurar um ambiente local, consulte a versão interativa no
navegador deste tutorial.
Explorar a matemática de inteiros
Crie um diretório chamado numbers-quickstart. Torne esse o diretório atual e execute o
seguinte comando:
CLI do .NET
dotnet new console -n NumbersInCSharp -o .
) Importante
Os modelos C# para o .NET 6 usam instruções de nível superior. Se você já tiver
atualizado para o .NET 6, talvez seu aplicativo não corresponda ao código descrito
neste artigo. Para obter mais informações, consulte o artigo sobre Novos modelos
C# geram instruções de nível superior
O SDK do .NET 6 também adiciona um conjunto de diretivas implícitas global
using para projetos que usam os seguintes SDKs:
Microsoft.NET.Sdk
Microsoft.NET.Sdk.Web
Microsoft.NET.Sdk.Worker
Essas diretivas implícitas global using incluem os namespaces mais comuns para o
tipo de projeto.
Abra Program.cs em seu editor favorito e substitua o conteúdo do arquivo pelo seguinte
código:
C#
int a = 18;
int b = 6;
int c = a + b;
Console.WriteLine(c);
Execute este código digitando dotnet run na janela de comando.
Você viu apenas uma das operações matemáticas fundamentais com números inteiros.
O tipo int representa um inteiro, zero, um número inteiro positivo ou negativo. Você
usa o símbolo + para adição. Outras operações matemáticas comuns para inteiros
incluem:
- para subtração
* para multiplicação
/ para divisão
Comece explorando essas diferentes operações. Adicione estas linhas após a linha que
grava o valor de c :
C#
// subtraction
c = a - b;
Console.WriteLine(c);
// multiplication
c = a * b;
Console.WriteLine(c);
// division
c = a / b;
Console.WriteLine(c);
Execute este código digitando dotnet run na janela de comando.
Você também pode experimentar executar várias operações matemáticas na mesma
linha, se quiser. Experimente c = a + b - 12 * 17; , por exemplo. É permitido misturar
variáveis e números constantes.
Dica
À medida que explora C# (ou qualquer linguagem de programação), você
cometerá erros ao escrever o código. O compilador encontrará esses erros e os
reportará a você. Quando a saída contiver mensagens de erro, analise atentamente
o código de exemplo e o código em sua janela para ver o que deve ser corrigido.
Esse exercício ajudará você a conhecer a estrutura do código C#.
Você terminou a primeira etapa. Antes de iniciar a próxima seção, vamos passar o
código atual para um método separado. Um método é uma série de instruções
agrupadas que receberam um nome. Você nomeia um método escrevendo o nome do
método seguido por () . Organizar seu código em métodos torna mais fácil começar a
trabalhar com um novo exemplo. Quando você terminar, seu código deverá ter a
seguinte aparência:
C#
WorkWithIntegers();
void WorkWithIntegers()
{
int a = 18;
int b = 6;
int c = a + b;
Console.WriteLine(c);
// subtraction
c = a - b;
Console.WriteLine(c);
// multiplication
c = a * b;
Console.WriteLine(c);
// division
c = a / b;
Console.WriteLine(c);
A linha WorkWithIntegers(); invoca o método. O código a seguir declara o método e o
define.
Explorar a ordem das operações
Comente a chamada para WorkingWithIntegers() . Isso tornará a saída menos
congestionada enquanto você trabalha nesta seção:
C#
//WorkWithIntegers();
O // inicia um comentário em C#. Os comentários são qualquer texto que você queira
manter em seu código-fonte, mas não queria executar como código. O compilador não
gera nenhum código executável a partir dos comentários. Como WorkWithIntegers() é
um método, você precisa apenas comentar uma linha.
A linguagem C# define a precedência de operações matemáticas diferentes com regras
consistentes às regras que você aprendeu em matemática. Multiplicação e divisão têm
precedência sobre adição e subtração. Explore isso adicionando o seguinte código a
WorkWithIntegers() após a chamada e executando dotnet run :
C#
int a = 5;
int b = 4;
int c = 2;
int d = a + b * c;
Console.WriteLine(d);
A saída demonstra que a multiplicação é executada antes da adição.
Você pode forçar uma ordem diferente de operações, adicionando parênteses para
delimitar a operação, ou operações, que você quer realizar primeiro. Adicione as
seguintes linhas e execute novamente:
C#
d = (a + b) * c;
Console.WriteLine(d);
Explore mais, combinando várias operações diferentes. Adicione algo semelhante às
linhas a seguir. Tente dotnet run novamente.
C#
d = (a + b) - 6 * c + (12 * 4) / 3 + 12;
Console.WriteLine(d);
Talvez você tenha observado um comportamento interessante com relação aos números
inteiros. A divisão de inteiros sempre produz um resultado inteiro, mesmo quando você
espera que o resultado inclua uma parte decimal ou fracionária.
Se você ainda não viu esse comportamento, tente o seguinte:
C#
int e = 7;
int f = 4;
int g = 3;
int h = (e + f) / g;
Console.WriteLine(h);
Digite dotnet run novamente para ver os resultados.
Antes de avançarmos, pegue todo código que você escreveu nesta seção e coloque-o
em um novo método. Chame esse novo método de OrderPrecedence . Seu código deve
ter a seguinte aparência:
C#
// WorkWithIntegers();
OrderPrecedence();
void WorkWithIntegers()
{
int a = 18;
int b = 6;
int c = a + b;
Console.WriteLine(c);
// subtraction
c = a - b;
Console.WriteLine(c);
// multiplication
c = a * b;
Console.WriteLine(c);
// division
c = a / b;
Console.WriteLine(c);
void OrderPrecedence()
int a = 5;
int b = 4;
int c = 2;
int d = a + b * c;
Console.WriteLine(d);
d = (a + b) * c;
Console.WriteLine(d);
d = (a + b) - 6 * c + (12 * 4) / 3 + 12;
Console.WriteLine(d);
int e = 7;
int f = 4;
int g = 3;
int h = (e + f) / g;
Console.WriteLine(h);
Explorar a precisão de inteiros e limites
Esse último exemplo mostrou que uma divisão de inteiros trunca o resultado. Você pode
obter o restante usando o operador module, o caractere % . Tente o seguinte código
após a chamada de método para OrderPrecedence() :
C#
int a = 7;
int b = 4;
int c = 3;
int d = (a + b) / c;
int e = (a + b) % c;
Console.WriteLine($"quotient: {d}");
Console.WriteLine($"remainder: {e}");
O tipo de inteiro C# difere do inteiros matemáticos de outra forma: o tipo int tem
limites mínimo e máximo. Adicione este código para ver esses limites:
C#
int max = int.MaxValue;
int min = int.MinValue;
Console.WriteLine($"The range of integers is {min} to {max}");
Se um cálculo produzir um valor que excede esses limites, você terá uma condição de
estouro negativo ou estouro. A resposta parece quebrar de um limite para o outro.
Adicione estas duas linhas para ver um exemplo:
C#
int what = max + 3;
Console.WriteLine($"An example of overflow: {what}");
Observe que a resposta é muito próxima do mínimo inteiro (negativo). É o mesmo que
min + 2 . A operação de adição estourou os valores permitidos para números inteiros. A
resposta é um número negativo muito grande, pois um estouro "envolve" do maior
valor de inteiro possível para o menor.
Há outros tipos numéricos com limites e precisão diferentes que você usaria quando o
tipo int não atendesse às suas necessidades. Vamos explorar esses outros tipos em
seguida. Antes de iniciar a próxima seção, mova o código que você escreveu nesta
seção para um método separado. Nomeie-o como TestLimits .
Trabalhar com o tipo Double
O tipo numérico double representa um número de ponto flutuante de precisão dupla.
Esses termos podem ser novidade para você. Um número de ponto flutuante é útil para
representar números não integrais que podem ser muito grandes ou pequenos em
magnitude. Precisão dupla é um termo relativo que descreve o número de dígitos
binários usados para armazenar o valor. Os números de precisão dupla têm o dobro do
número de dígitos binários do que os de precisão simples. Em computadores
modernos, é mais comum usar números de precisão dupla do que de precisão simples.
Números de precisão simples são declarados usando a palavra-chave float . Vamos
explorar. Adicione o seguinte código e veja o resultado:
C#
double a = 5;
double b = 4;
double c = 2;
double d = (a + b) / c;
Console.WriteLine(d);
Observe que a resposta inclui a parte decimal do quociente. Experimente uma expressão
ligeiramente mais complicada com duplos:
C#
double e = 19;
double f = 23;
double g = 8;
double h = (e + f) / g;
Console.WriteLine(h);
O intervalo de um valor duplo é muito maior do que valores inteiros. Experimente o
código a seguir abaixo do código que você escreveu até o momento:
C#
double max = double.MaxValue;
double min = double.MinValue;
Console.WriteLine($"The range of double is {min} to {max}");
Esses valores são impressos em notação científica. O número à esquerda do E é o
significando. O número à direita é o expoente, como uma potência de 10. Assim como
os números decimais em matemática, os duplos em C# podem ter erros de
arredondamento. Experimente esse código:
C#
double third = 1.0 / 3.0;
Console.WriteLine(third);
Você sabe que 0.3 repetido um número finito de vezes não é exatamente o mesmo que
1/3 .
Desafio
Experimente outros cálculos com números grandes, números pequenos, multiplicação e
divisão usando o tipo double . Experimente cálculos mais complicados. Após algum
tempo no desafio, pegue o código que você escreveu e coloque-o em um novo
método. Chame esse novo método de WorkWithDoubles .
Trabalhar com tipos decimais
Você viu os tipos numéricos básicos em C#: inteiros e duplos. Ainda há outro tipo: o tipo
decimal . O tipo decimal tem um intervalo menor, mas precisão maior do que double .
Vamos dar uma olhada:
C#
decimal min = decimal.MinValue;
decimal max = decimal.MaxValue;
Console.WriteLine($"The range of the decimal type is {min} to {max}");
Observe que o intervalo é menor do que o tipo double . Veja a precisão maior com o
tipo decimal experimentando o código a seguir:
C#
double a = 1.0;
double b = 3.0;
Console.WriteLine(a / b);
decimal c = 1.0M;
decimal d = 3.0M;
Console.WriteLine(c / d);
O sufixo M nos números é o modo como você indica que uma constante deve usar o
tipo decimal . Caso contrário, o compilador assumirá o tipo double .
7 Observação
A letra M foi escolhida como a letra mais visualmente distinta entre as palavras-
chave double e decimal .
Observe que o cálculo usando o tipo decimal tem mais dígitos à direita da vírgula
decimal.
Desafio
Agora que você viu os diferentes tipos numéricos, escreva um código que calcula a área
de um círculo cujo raio é de 2,50 centímetros. Lembre-se de que a área de um círculo é
o quadrado do raio multiplicado por PI. Uma dica: o .NET contém uma constante para
PI, Math.PI, que você pode usar para esse valor. Math.PI, como todas as constantes
declaradas no namespace System.Math , é um valor double . Por esse motivo, você deve
usar valores double em vez de decimal para esse desafio.
Você deve obter uma resposta entre 19 e 20. Confira sua resposta analisando o código
de exemplo finalizado no GitHub .
Experimente outras fórmulas, se quiser.
Você concluiu o início rápido "Números em C#". Continue com o início rápido Branches
e loops em seu próprio ambiente de desenvolvimento.
Saiba mais sobre os números em C# nos artigos a seguir:
Tipos numéricos integrais
Tipos numéricos de ponto flutuante
Conversões numéricas internas
Instruções e loops em C# if – tutorial
de lógica condicional
Artigo • 10/05/2023
Este tutorial ensina a escrever código C# que examina variáveis e muda o caminho de
execução com base nessas variáveis. Escreva o código em C# e veja os resultados da
compilação e da execução. O tutorial contém uma série de lições que exploram
construções de branches e loops em C#. Estas lições ensinam os princípios básicos da
linguagem C#.
Dica
Para colar um snippet de código dentro do modo de foco, você deve usar o atalho
de teclado ( Ctrl + v ou cmd + v ).
Pré-requisitos
Este tutorial espera que você tenha um computador configurado para desenvolvimento
local. Consulte Configurar seu ambiente local para obter instruções de instalação e uma
visão geral do desenvolvimento de aplicativos no .NET.
Se você preferir executar o código sem precisar configurar um ambiente local, consulte
a versão interativa no navegador deste tutorial.
Tome decisões usando a instrução if
Crie um diretório chamado branches-tutorial. Torne esse o diretório atual e execute o
seguinte comando:
CLI do .NET
dotnet new console -n BranchesAndLoops -o .
) Importante
Os modelos C# para o .NET 6 usam instruções de nível superior. Se você já tiver
atualizado para o .NET 6, talvez seu aplicativo não corresponda ao código descrito
neste artigo. Para obter mais informações, consulte o artigo sobre Novos modelos
C# geram instruções de nível superior
O SDK do .NET 6 também adiciona um conjunto de diretivas implícitas global
using para projetos que usam os seguintes SDKs:
Microsoft.NET.Sdk
Microsoft.NET.Sdk.Web
Microsoft.NET.Sdk.Worker
Essas diretivas implícitas global using incluem os namespaces mais comuns para o
tipo de projeto.
Esse comando cria um novo aplicativo de console .NET no diretório atual. Abra
Program.cs em seu editor favorito e substitua o conteúdo pelo seguinte código:
C#
int a = 5;
int b = 6;
if (a + b > 10)
Console.WriteLine("The answer is greater than 10.");
Experimente este código digitando dotnet run na sua janela do console. Você deverá
ver a mensagem "A resposta é maior que 10" impressa no console. Modifique a
declaração de b para que a soma seja inferior a 10:
C#
int b = 3;
Digite dotnet run novamente. Como a resposta é inferior a 10, nada é impresso. A
condição que você está testando é falsa. Não há qualquer código para execução,
porque você escreveu apenas uma das ramificações possíveis para uma instrução if : a
ramificação verdadeira.
Dica
À medida que explora C# (ou qualquer linguagem de programação), você
cometerá erros ao escrever o código. O compilador encontrará e reportará esses
erros. Verifique atentamente a saída do erro e o código que gerou o erro. O erro
do compilador geralmente pode ajudá-lo a localizar o problema.
Este primeiro exemplo mostra o poder dos tipos if e Booliano. Um Booliano é uma
variável que pode ter um dos dois valores: true ou false . C# define um tipo especial,
bool para variáveis Boolianas. A instrução if verifica o valor de um bool . Quando o
valor é true , a instrução após if é executada. Caso contrário, ela é ignorada. Esse
processo de verificação de condições e execução de instruções com base nessas
condições é eficiente.
Faça if e else funcionam juntas
Para executar um código diferente nos branches true e false, crie um branch else que
será executado quando a condição for false. Experimente uma ramificação else .
Adicione as duas últimas linhas do código abaixo (você já deve ter as quatro primeiras):
C#
int a = 5;
int b = 3;
if (a + b > 10)
Console.WriteLine("The answer is greater than 10");
else
Console.WriteLine("The answer is not greater than 10");
A instrução após a palavra-chave else é executada somente quando a condição que
estiver sendo testada for false . A combinação de if e else com condições Boolianas
fornece todos os recursos que você precisa para lidar com uma condição true e false .
) Importante
O recuo sob as instruções if e else é para leitores humanos. A linguagem C# não
considera recuos ou espaços em branco como significativos. A instrução após a
palavra-chave if ou else será executada com base na condição. Todos os
exemplos neste tutorial seguem uma prática comum para recuar linhas com base
no fluxo de controle de instruções.
Como o recuo não é significativo, você precisa usar { e } para indicar quando você
quer que mais de uma instrução faça parte do bloco executado condicionalmente. Os
programadores em C# normalmente usam essas chaves em todas as cláusulas if e
else . O exemplo a seguir é igual ao que você acabou de criar. Modifique o código
acima para coincidir com o código a seguir:
C#
int a = 5;
int b = 3;
if (a + b > 10)
Console.WriteLine("The answer is greater than 10");
else
Console.WriteLine("The answer is not greater than 10");
Dica
No restante deste tutorial, todos os exemplos de código incluem as chaves,
seguindo as práticas aceitas.
Você pode testar condições mais complicadas. Adicione o código a seguir após o que
você escreveu até o momento:
C#
int c = 4;
if ((a + b + c > 10) && (a == b))
Console.WriteLine("The answer is greater than 10");
Console.WriteLine("And the first number is equal to the second");
else
Console.WriteLine("The answer is not greater than 10");
Console.WriteLine("Or the first number is not equal to the second");
O símbolo == testa a igualdade. Usar == distingue o teste de igualdade de atribuição,
que você viu em a = 5 .
O && representa "e". Isso significa que as duas condições devem ser verdadeiras para
executar a instrução no branch verdadeiro. Estes exemplos também mostram que você
pode ter várias instruções em cada branch condicional, desde que você coloque-as
entre { e } . Você também pode usar || para representar "ou". Adicione o código a
seguir após o que você escreveu até o momento:
C#
if ((a + b + c > 10) || (a == b))
Console.WriteLine("The answer is greater than 10");
Console.WriteLine("Or the first number is equal to the second");
else
Console.WriteLine("The answer is not greater than 10");
Console.WriteLine("And the first number is not equal to the second");
Modifique os valores de a , b e c e alterne entre && e || para explorar. Você obterá
mais compreensão de como os operadores && e || funcionam.
Você terminou a primeira etapa. Antes de iniciar a próxima seção, vamos passar o
código atual para um método separado. Isso facilita o começo do trabalho com um
exemplo novo. Coloque o código existente em um método chamado ExploreIf() .
Chame-o pela parte superior do programa. Quando você terminar essas alterações, seu
código deverá estar parecido com o seguinte:
C#
ExploreIf();
void ExploreIf()
int a = 5;
int b = 3;
if (a + b > 10)
Console.WriteLine("The answer is greater than 10");
else
Console.WriteLine("The answer is not greater than 10");
int c = 4;
if ((a + b + c > 10) && (a > b))
Console.WriteLine("The answer is greater than 10");
Console.WriteLine("And the first number is greater than the
second");
else
Console.WriteLine("The answer is not greater than 10");
Console.WriteLine("Or the first number is not greater than the
second");
if ((a + b + c > 10) || (a > b))
Console.WriteLine("The answer is greater than 10");
Console.WriteLine("Or the first number is greater than the second");
else
Console.WriteLine("The answer is not greater than 10");
Console.WriteLine("And the first number is not greater than the
second");
Comente a chamada para ExploreIf() . Isso tornará a saída menos congestionada
enquanto você trabalha nesta seção:
C#
//ExploreIf();
O // inicia um comentário em C#. Os comentários são qualquer texto que você queira
manter em seu código-fonte, mas não queria executar como código. O compilador não
gera qualquer código executável a partir dos comentários.
Use loops para repetir operações
Nesta seção, você usa loops repetir as instruções. Adicione esse código após a chamada
para ExploreIf :
C#
int counter = 0;
while (counter < 10)
Console.WriteLine($"Hello World! The counter is {counter}");
counter++;
A instrução while verifica uma condição e executa a instrução, ou bloco de instruções,
após o while . Ela verifica repetidamente a condição e executa essas instruções até que
a condição seja falsa.
Há outro operador novo neste exemplo. O ++ após a variável counter é o operador
increment. Ele adiciona 1 ao valor de counter e armazena esse valor na variável
counter .
) Importante
Verifique se a condição de loop while muda para false ao executar o código. Caso
contrário, crie um loop infinito, para que seu programa nunca termine. Isso não é
demonstrado neste exemplo, porque você tem que forçar o programa a encerrar
usando CTRL-C ou outros meios.
O loop while testa a condição antes de executar o código seguindo while . O loop do ...
while executa o código primeiro e, em seguida, verifica a condição. O loop do while é
mostrado no código a seguir:
C#
int counter = 0;
do
Console.WriteLine($"Hello World! The counter is {counter}");
counter++;
} while (counter < 10);
Esse loop do e o loop while anterior produzem a mesma saída.
Trabalhar com o loop for
O loop for é usado normalmente em C#. Experimente esse código:
C#
for (int index = 0; index < 10; index++)
Console.WriteLine($"Hello World! The index is {index}");
Ele faz o mesmo trabalho do loop while e do loop do que você já usou. A instrução
for tem três partes que controlam o modo como ela funciona.
A primeira parte é o inicializador for: int index = 0; declara que index é a variável do
loop, e define seu valor inicial como 0 .
A parte central é a condição for: index < 10 declara que este loop for continuará
sendo executado desde que o valor do contador seja inferior a 10.
A parte final é o iterador for: index++ especifica como modificar a variável de loop
depois de executar o bloco após a instrução for . Aqui, ela especifica que index deve
ser incrementado com 1 sempre que o bloco for executado.
Experimente você mesmo. Experimente cada uma das seguintes variações:
Altere o inicializador para iniciar em um valor diferente.
Altere a condição para parar em um valor diferente.
Quando terminar, vamos escrever um código para usar o que você aprendeu.
Há uma outra instrução de loop que não é abordada neste tutorial: a instrução foreach .
A instrução foreach repete sua instrução para cada item em uma sequência de itens. Ela
é usada com mais frequência com coleções, portanto, será abordada no próximo tutorial.
Loops aninhados criados
Um loop while , do ou for pode ser aninhado dentro de outro loop para criar uma
matriz usando a combinação de cada item no loop externo com cada item no loop
interno. Vamos fazer isso para criar um conjunto de pares alfanuméricos para
representar linhas e colunas.
Um loop for pode gerar as linhas:
C#
for (int row = 1; row < 11; row++)
Console.WriteLine($"The row is {row}");
Outro loop pode gerar as colunas:
C#
for (char column = 'a'; column < 'k'; column++)
Console.WriteLine($"The column is {column}");
Você pode aninhar um loop dentro do outro para formar pares:
C#
for (int row = 1; row < 11; row++)
for (char column = 'a'; column < 'k'; column++)
Console.WriteLine($"The cell is ({row}, {column})");
Você pode ver que o loop externo incrementa uma vez para cada execução completa do
loop interno. Inverta o aninhamento de linha e da coluna e confira as alterações.
Quando terminar, coloque o código desta seção em um método chamado
ExploreLoops() .
Combinar branches e loops
Agora que você viu a instrução if e as construções de loop na linguagem C#, verifique
se você pode escrever o código C# para encontrar a soma de todos os inteiros de 1 a 20
divisíveis por 3. Veja algumas dicas:
O operador % retorna o restante de uma operação de divisão.
A instrução if retorna a condição para ver se um número deve ser parte da soma.
O loop for pode ajudar você a repetir uma série de etapas para todos os números
de 1 a 20.
Tente você mesmo. Depois verifique como você fez. Você deve obter 63 como resposta.
Veja uma resposta possível exibindo o código completo no GitHub .
Você concluiu o tutorial "branches e loops".
Continue com o tutorial Matrizes e coleções em seu próprio ambiente de
desenvolvimento.
Saiba mais sobre esses conceitos nesses artigos:
Instruções de seleção
Instruções de iteração
Saiba como gerenciar coletas de dados
usando List<T> em C #
Artigo • 10/05/2023
Este tutorial de introdução fornece uma introdução à linguagem C# e os conceitos
básicos da classe List<T>.
Pré-requisitos
Este tutorial espera que você tenha um computador configurado para desenvolvimento
local. Consulte Configurar seu ambiente local para obter instruções de instalação e uma
visão geral do desenvolvimento de aplicativos no .NET.
Se você preferir executar o código sem precisar configurar um ambiente local, consulte
a versão interativa no navegador deste tutorial.
Um exemplo de lista básica
Crie um diretório chamado list-tutorial. Torne-o o diretório atual e execute dotnet new
console .
) Importante
Os modelos C# para o .NET 6 usam instruções de nível superior. Se você já tiver
atualizado para o .NET 6, talvez seu aplicativo não corresponda ao código descrito
neste artigo. Para obter mais informações, consulte o artigo sobre Novos modelos
C# geram instruções de nível superior
O SDK do .NET 6 também adiciona um conjunto de diretivas implícitas global
using para projetos que usam os seguintes SDKs:
Microsoft.NET.Sdk
Microsoft.NET.Sdk.Web
Microsoft.NET.Sdk.Worker
Essas diretivas global using implícitas incluem os namespaces mais comuns para o
tipo de projeto.
Abra Program.cs em seu editor favorito e substitua o código existente pelo seguinte:
C#
var names = new List<string> { "<name>", "Ana", "Felipe" };
foreach (var name in names)
Console.WriteLine($"Hello {name.ToUpper()}!");
Substitua <name> pelo seu nome. Salve o Program.cs. Digite dotnet run na janela de
console para testá-lo.
Você criou uma lista de cadeias de caracteres, adicionou três nomes a essa lista e
imprimiu os nomes em MAIÚSCULAS. Você está usando conceitos que aprendeu em
tutoriais anteriores para executar um loop pela lista.
O código para exibir nomes utiliza o recurso de interpolação de cadeia de caracteres.
Quando você precede um string com o caractere $ , pode inserir o código C# na
declaração da cadeia de caracteres. A cadeia de caracteres real substitui esse código C#
pelo valor gerado. Neste exemplo, ela substitui o {name.ToUpper()} por cada nome,
convertido em letras maiúsculas, pois você chamou o método ToUpper.
Vamos continuar explorando.
Modificar conteúdo da lista
A coleção que você criou usa o tipo List<T>. Esse tipo armazena sequências de
elementos. Especifique o tipo dos elementos entre os colchetes.
Um aspecto importante desse tipo List<T> é que ele pode aumentar ou diminuir,
permitindo que você adicione ou remova elementos. Adicione este código ao final do
programa:
C#
Console.WriteLine();
names.Add("Maria");
names.Add("Bill");
names.Remove("Ana");
foreach (var name in names)
Console.WriteLine($"Hello {name.ToUpper()}!");
Você adicionou mais dois nomes ao final da lista. Também removeu um. Salve o arquivo
e digite dotnet run para testá-lo.
O List<T> também permite fazer referência a itens individuais por índice. Coloque o
índice entre os tokens [ e ] após o nome da lista. C# usa 0 para o primeiro índice.
Adicione este código diretamente abaixo do código que você acabou de adicionar e
teste-o:
C#
Console.WriteLine($"My name is {names[0]}");
Console.WriteLine($"I've added {names[2]} and {names[3]} to the list");
Você não pode acessar um índice além do fim da lista. Lembre-se de que os índices
começam com 0, portanto, o maior índice válido é uma unidade a menos do que o
número de itens na lista. Você pode verificar há quanto tempo a lista está usando a
propriedade Count. Adicione o código a seguir ao final de seu programa:
C#
Console.WriteLine($"The list has {names.Count} people in it");
Salve o arquivo e digite dotnet run novamente para ver os resultados.
Pesquisar e classificar listas
Nossos exemplos usam listas relativamente pequenas, mas seus aplicativos podem criar
listas com muitos outros elementos, chegando, às vezes, a milhares. Para localizar
elementos nessas coleções maiores, pesquise por itens diferentes na lista. O método
IndexOf procura um item e retorna o índice do item. Se o item não estiver na lista,
IndexOf retornará -1 . Adicione este código à parte inferior de seu programa:
C#
var index = names.IndexOf("Felipe");
if (index == -1)
Console.WriteLine($"When an item is not found, IndexOf returns
{index}");
else
Console.WriteLine($"The name {names[index]} is at index {index}");
index = names.IndexOf("Not Found");
if (index == -1)
Console.WriteLine($"When an item is not found, IndexOf returns
{index}");
else
Console.WriteLine($"The name {names[index]} is at index {index}");
Os itens em sua lista também podem ser classificados. O método Sort classifica todos os
itens na lista na ordem normal (em ordem alfabética para cadeias de caracteres).
Adicione este código à parte inferior de seu programa:
C#
names.Sort();
foreach (var name in names)
Console.WriteLine($"Hello {name.ToUpper()}!");
Salve o arquivo e digite dotnet run para experimentar a versão mais recente.
Antes de iniciar a próxima seção, vamos passar o código atual para um método
separado. Isso facilita o começo do trabalho com um exemplo novo. Coloque todo o
código que você escreveu em um novo método chamado WorkWithStrings() . Chame
esse método na parte superior do programa. Quando você terminar, seu código deverá
ter a seguinte aparência:
C#
WorkWithStrings();
void WorkWithStrings()
var names = new List<string> { "<name>", "Ana", "Felipe" };
foreach (var name in names)
Console.WriteLine($"Hello {name.ToUpper()}!");
Console.WriteLine();
names.Add("Maria");
names.Add("Bill");
names.Remove("Ana");
foreach (var name in names)
Console.WriteLine($"Hello {name.ToUpper()}!");
Console.WriteLine($"My name is {names[0]}");
Console.WriteLine($"I've added {names[2]} and {names[3]} to the list");
Console.WriteLine($"The list has {names.Count} people in it");
var index = names.IndexOf("Felipe");
if (index == -1)
Console.WriteLine($"When an item is not found, IndexOf returns
{index}");
else
Console.WriteLine($"The name {names[index]} is at index {index}");
index = names.IndexOf("Not Found");
if (index == -1)
Console.WriteLine($"When an item is not found, IndexOf returns
{index}");
else
Console.WriteLine($"The name {names[index]} is at index {index}");
names.Sort();
foreach (var name in names)
Console.WriteLine($"Hello {name.ToUpper()}!");
Listas de outros tipos
Você usou o tipo string nas listas até o momento. Vamos fazer List<T> usar um tipo
diferente. Vamos compilar um conjunto de números.
Adicione o seguinte ao seu programa depois de chamar WorkWithStrings() :
C#
var fibonacciNumbers = new List<int> {1, 1};
Isso cria uma lista de números inteiros e define os primeiros dois inteiros como o valor
1. Estes são os dois primeiros valores de uma sequência Fibonacci, uma sequência de
números. Cada número Fibonacci seguinte é encontrado considerando a soma dos dois
números anteriores. Adicione este código:
C#
var previous = fibonacciNumbers[fibonacciNumbers.Count - 1];
var previous2 = fibonacciNumbers[fibonacciNumbers.Count - 2];
fibonacciNumbers.Add(previous + previous2);
foreach (var item in fibonacciNumbers)
Console.WriteLine(item);
Salve o arquivo e digite dotnet run para ver os resultados.
Dica
Para se concentrar apenas nesta seção, comente o código que chama
WorkWithStrings(); . Coloque apenas dois caracteres / na frente da chamada,
desta forma: // WorkWithStrings(); .
Desafio
Veja se você consegue combinar alguns dos conceitos desta lição e de lições anteriores.
Expanda o que você compilou até o momento com números Fibonacci. Tente escrever o
código para gerar os 20 primeiros números na sequência. (Como uma dica, o vigésimo
número Fibonacci é 6765.)
Desafio concluído
Veja um exemplo de solução analisando o código de exemplo finalizado no GitHub .
Com cada iteração do loop, você está pegando os últimos dois inteiros na lista,
somando-os e adicionando esse valor à lista. O loop será repetido até que você tenha
adicionado 20 itens à lista.
Parabéns, você concluiu o tutorial de lista. Você pode continuar com tutoriais adicionais
em seu próprio ambiente de desenvolvimento.
Saiba mais sobre como trabalhar com o tipo List no artigo Noções básicas do .NET em
coleções. Você também aprenderá muitos outros tipos de coleção.
Estrutura geral de um programa em C#
Artigo • 07/04/2023
Os programas C# podem consistir em um ou mais arquivos. Cada arquivo pode conter
zero ou mais namespaces. Um namespace pode conter tipos como classes, estruturas,
interfaces, enumerações e delegados, além de outros namespaces. Veja a seguir o
esqueleto de um programa em C# que contém todos esses elementos.
C#
// A skeleton of a C# program
using System;
// Your program starts here:
Console.WriteLine("Hello world!");
namespace YourNamespace
class YourClass
struct YourStruct
interface IYourInterface
delegate int YourDelegate();
enum YourEnum
namespace YourNestedNamespace
struct YourStruct
O exemplo anterior usa instruções de nível superior para o ponto de entrada do
programa. Esse recurso foi adicionado no C# 9. Antes do C# 9, o ponto de entrada era
um método estático chamado Main , como mostra o exemplo a seguir:
C#
// A skeleton of a C# program
using System;
namespace YourNamespace
class YourClass
struct YourStruct
interface IYourInterface
delegate int YourDelegate();
enum YourEnum
namespace YourNestedNamespace
struct YourStruct
class Program
static void Main(string[] args)
//Your program starts here...
Console.WriteLine("Hello world!");
Seções relacionadas
Você aprenderá sobre esses elementos de programa na seção sobre tipos do guia de
conceitos básicos:
Classes
Estruturas
Namespaces
Interfaces
Enumerações
Representantes
Especificação da Linguagem C#
Para obter mais informações, veja Noções básicas na Especificação da linguagem C#. A
especificação da linguagem é a fonte definitiva para a sintaxe e o uso de C#.
Main() e argumentos de linha de
comando
Artigo • 10/05/2023
O método Main é o ponto de entrada de um aplicativo C#. (Bibliotecas e serviços não
exigem um método Main como ponto de entrada). Quando o aplicativo é iniciado, o
método Main é o primeiro método invocado.
Pode haver apenas um ponto de entrada em um programa C#. Se tiver mais de uma
classe que tenha um método Main , você deverá compilar seu programa com a opção do
compilador StartupObject para especificar qual método Main será usado como ponto
de entrada. Para obter mais informações, consulte StartupObject (opções de
compilador do C#).
C#
class TestClass
static void Main(string[] args)
// Display the number of command line arguments.
Console.WriteLine(args.Length);
A partir do C# 9, você pode omitir o método Main e gravar instruções C# como se
estivessem no método Main , como no exemplo a seguir:
C#
using System.Text;
StringBuilder builder = new();
builder.AppendLine("Hello");
builder.AppendLine("World!");
Console.WriteLine(builder.ToString());
Para obter informações sobre como escrever o código do aplicativo com um método de
ponto de entrada implícito, consulte Instruções de nível superior.
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 é declarado dentro de uma classe ou struct. Main deve ser static e não
precisa ser public. (No exemplo anterior, ele recebe o acesso padrão de private). A
classe ou o struct de integração não é necessário para ser estático.
Main pode ter o tipo de retorno void , int , Task ou Task<int> .
Se e somente se Main retornar um Task ou Task<int> , a declaração de Main pode
incluir o modificador async. Isso exclui especificamente um método async void
Main .
O método Main pode ser declarado com ou sem um parâmetro string[] que
contém os argumentos de linha de comando. Ao usar o Visual Studio para criar
aplicativos do Windows, você pode adicionar o parâmetro manualmente ou usar o
método GetCommandLineArgs() para obter os argumentos de linha de comando.
Os parâmetros são lidos como argumentos de linha de comando indexados por
zero. Ao contrário do C e C++, o nome do programa não é tratado como o
primeiro argumento de linha de comando na matriz args , mas é o primeiro
elemento do método GetCommandLineArgs().
A lista a seguir mostra as assinaturas de Main válidas:
C#
public static void Main() { }
public static int Main() { }
public static void Main(string[] args) { }
public static int Main(string[] args) { }
public static async Task Main() { }
public static async Task<int> Main() { }
public static async Task Main(string[] args) { }
public static async Task<int> Main(string[] args) { }
Todos os exemplos anteriores usam o modificador do acessador public . Isso é típico,
mas não é necessário.
A adição dos tipos de retorno async , Task e Task<int> simplifica o código do
programa quando os aplicativos do console precisam iniciar e realizar operações
assíncronas await no Main .
Valores de retorno de Main()
Você pode retornar um int do método Main ao definir o método de uma das seguintes
maneiras:
Código do método Main Assinatura Main
Nenhum uso de args ou await static int Main()
Usa args , nenhum uso de await static int Main(string[] args)
Nenhum uso de args , usa await static async Task<int> Main()
Usa args e await static async Task<int> Main(string[] args)
Se o valor retornado de Main não for usado, o retorno de void ou Task permite um
código um pouco mais simples.
Código do método Main Assinatura Main
Nenhum uso de args ou await static void Main()
Usa args , nenhum uso de await static void Main(string[] args)
Nenhum uso de args , usa await static async Task Main()
Usa args e await static async Task Main(string[] args)
No entanto, o retorno de int ou Task<int> habilita o programa a comunicar
informações de status para outros programas ou scripts, que invocam o arquivo
executável.
O exemplo a seguir mostra como o código de saída para o processo pode ser acessado.
Este exemplo usa ferramentas de linha de comando do .NET Core. Se você não estiver
familiarizado com as ferramentas de linha de comando do .NET Core, poderá aprender
sobre elas neste artigo de introdução.
Crie um novo aplicativo ao executar dotnet new console . Modifique o método Main em
Program.cs da seguinte maneira:
C#
// Save this program as MainReturnValTest.cs.
class MainReturnValTest
static int Main()
//...
return 0;
Quando um programa é executado no Windows, qualquer valor retornado da função
Main é armazenado em uma variável de ambiente. Essa variável de ambiente pode ser
recuperada usando ERRORLEVEL de um arquivo em lotes ou $LastExitCode do
PowerShell.
Você pode criar o aplicativo usando o comando dotnet build da CLI do dotnet.
Em seguida, crie um script do PowerShell para executar o aplicativo e exibir o resultado.
Cole o código a seguir em um arquivo de texto e salve-o como test.ps1 na pasta que
contém o projeto. Execute o script do PowerShell ao digitar test.ps1 no prompt do
PowerShell.
Como o código retorna zero, o arquivo em lotes relatará êxito. No entanto, se você
alterar o MainReturnValTest.cs para retornar um valor diferente de zero e recompilar o
programa, a execução subsequente do script do PowerShell reportará falha.
PowerShell
dotnet run
if ($LastExitCode -eq 0) {
Write-Host "Execution succeeded"
} else
Write-Host "Execution Failed"
Write-Host "Return value = " $LastExitCode
Saída
Execution succeeded
Return value = 0
Valores retornados de Async Main
Quando você declara um valor retornado async para Main , o compilador gera o código
clichê para chamar métodos assíncronos em Main . Se você não especificar a palavra-
chave async , precisará escrever esse código por conta própria, conforme mostrado no
exemplo a seguir. O código no exemplo garante que o programa seja executado até
que a operação assíncrona seja concluída:
C#
public static void Main()
AsyncConsoleWork().GetAwaiter().GetResult();
private static async Task<int> AsyncConsoleWork()
// Main body here
return 0;
Este código clichê pode ser substituído por:
C#
static async Task<int> Main(string[] args)
return await AsyncConsoleWork();
A vantagem de declarar Main como async é que o compilador sempre gera o código
correto.
Quando o ponto de entrada do aplicativo retorna um Task ou Task<int> , o compilador
gera um novo ponto de entrada que chama o método de ponto de entrada declarado
no código do aplicativo. Supondo que esse ponto de entrada é chamado
$GeneratedMain , o compilador gera o código a seguir para esses pontos de entrada:
static Task Main() resulta no compilador emitindo o equivalente a private
static void $GeneratedMain() => Main().GetAwaiter().GetResult();
static Task Main(string[]) resulta no compilador emitindo o equivalente a
private static void $GeneratedMain(string[] args) =>
Main(args).GetAwaiter().GetResult();
static Task<int> Main() resulta no compilador emitindo o equivalente a private
static int $GeneratedMain() => Main().GetAwaiter().GetResult();
static Task<int> Main(string[]) resulta no compilador emitindo o equivalente a
private static int $GeneratedMain(string[] args) =>
Main(args).GetAwaiter().GetResult();
7 Observação
Se os exemplos usassem o modificador async no método Main , o compilador
geraria o mesmo código.
Argumentos de linha de comando
Você pode enviar argumentos para o método Main definindo o método de uma das
seguintes maneiras:
Código do método Main Assinatura Main
Nenhum valor retornado, nenhum uso de await static void Main(string[] args)
Valor retornado, nenhum uso de await static int Main(string[] args)
Nenhum valor retornado, usa await static async Task Main(string[] args)
Valor retornado, usa await static async Task<int> Main(string[] args)
Se os argumentos não forem usados, você poderá omitir args a assinatura do método
para um código ligeiramente mais simples:
Código do método Main Assinatura Main
Nenhum valor retornado, nenhum uso de await static void Main()
Valor retornado, nenhum uso de await static int Main()
Nenhum valor retornado, usa await static async Task Main()
Valor retornado, usa await static async Task<int> Main()
7 Observação
Você também pode usar Environment.CommandLine ou
Environment.GetCommandLineArgs para acessar os argumentos de linha de
comando de qualquer ponto em um console ou um aplicativo do Windows Forms.
Para habilitar os argumentos de linha de comando na assinatura do método Main
em um aplicativo do Windows Forms, você deve modificar manualmente a
assinatura de Main . O código gerado pelo designer do Windows Forms cria um
Main sem um parâmetro de entrada.
O parâmetro do método Main é uma matriz String que representa os argumentos de
linha de comando. Geralmente você determina se os argumentos existem testando a
propriedade Length , por exemplo:
C#
if (args.Length == 0)
System.Console.WriteLine("Please enter a numeric argument.");
return 1;
Dica
A matriz args não pode ser nula. Portanto, é seguro acessar a propriedade Length
sem verificação de nulos.
Você também pode converter os argumentos de cadeia de caracteres em tipos
numéricos, usando a classe Convert ou o método Parse . Por exemplo, a instrução a
seguir converte o string em um número long usando o método Parse:
C#
long num = Int64.Parse(args[0]);
Também é possível usar o tipo long de C#, que funciona como alias de Int64 :
C#
long num = long.Parse(args[0]);
Você também pode usar o método da classe Convert , o ToInt64 , para fazer a mesma
coisa:
C#
long num = Convert.ToInt64(s);
Para obter mais informações, consulte Parse e Convert.
O exemplo a seguir mostra como usar argumentos de linha de comando em um
aplicativo de console. O aplicativo recebe um argumento em tempo de execução,
converte o argumento em um número inteiro e calcula o fatorial do número. Se nenhum
argumento for fornecido, o aplicativo emitirá uma mensagem que explica o uso correto
do programa.
Para compilar e executar o aplicativo em um prompt de comando, siga estas etapas:
1. Cole o código a seguir em qualquer editor de texto e, em seguida, salve o arquivo
como um arquivo de texto com o nome Factorial.cs.
C#
public class Functions
public static long Factorial(int n)
// Test for invalid input.
if ((n < 0) || (n > 20))
return -1;
// Calculate the factorial iteratively rather than recursively.
long tempResult = 1;
for (int i = 1; i <= n; i++)
tempResult *= i;
return tempResult;
class MainClass
static int Main(string[] args)
// Test if input arguments were supplied.
if (args.Length == 0)
Console.WriteLine("Please enter a numeric argument.");
Console.WriteLine("Usage: Factorial <num>");
return 1;
// Try to convert the input arguments to numbers. This will
throw
// an exception if the argument is not a number.
// num = int.Parse(args[0]);
int num;
bool test = int.TryParse(args[0], out num);
if (!test)
Console.WriteLine("Please enter a numeric argument.");
Console.WriteLine("Usage: Factorial <num>");
return 1;
// Calculate factorial.
long result = Functions.Factorial(num);
// Print result.
if (result == -1)
Console.WriteLine("Input must be >= 0 and <= 20.");
else
Console.WriteLine($"The Factorial of {num} is {result}.");
return 0;
// If 3 is entered on command line, the
// output reads: The factorial of 3 is 6.
2. Na tela Inicial ou no menu Iniciar, abra uma janela Prompt de Comando do
Desenvolvedor do Visual Studio e, em seguida, navegue até a pasta que contém o
arquivo que você acabou de criar.
3. Digite o seguinte comando para compilar o aplicativo.
dotnet build
Se seu aplicativo não tiver erros de compilação, um arquivo executável chamado
Factorial.exe será criado.
4. Digite o seguinte comando para calcular o fatorial de 3:
dotnet run -- 3
5. O comando produz esta saída: The factorial of 3 is 6.
7 Observação
Ao executar um aplicativo no Visual Studio, você pode especificar argumentos de
linha de comando na Página de depuração, Designer de Projeto.
Especificação da linguagem C#
Para obter mais informações, consulte a Especificação da linguagem C#. A especificação
da linguagem é a fonte definitiva para a sintaxe e o uso de C#.
Confira também
System.Environment
Como exibir argumentos de linha de comando
Instruções de nível superior –
Programas sem métodos Main
Artigo • 07/04/2023
Do C# 9 em diante, você não precisa incluir explicitamente um método Main em
projetos de aplicativo de console. Em vez disso, você pode usar o recurso de instruções
de nível superior para minimizar o volume de código que precisa escrever. Nesse caso, o
compilador gera uma classe e um ponto de entrada do método Main para o aplicativo.
Aqui está um arquivo Program.cs que corresponde a um programa C# completo no C#
10:
C#
Console.WriteLine("Hello World!");
Instruções de nível superior permitem que você escreva programas simples para
utilitários pequenos, como o Azure Functions e o GitHub Actions. Elas também
simplificam para novos programadores C# começar a aprender e escrever código.
As seções a seguir explicam as regras sobre o que você pode ou não fazer com
instruções de nível superior.
Apenas um arquivo de nível superior
Cada aplicativo deve ter apenas um ponto de entrada. Cada projeto pode ter apenas um
arquivo com instruções de nível superior. Colocar instruções de nível superior em mais
de um arquivo de um projeto resulta no seguinte erro do compilador:
CS8802 Somente uma unidade de compilação pode conter instruções de nível
superior.
Os projetos podem ter um número indefinido de arquivos de código-fonte adicionais
que não têm instruções de nível superior.
Nenhum outro ponto de entrada
Você pode escrever um método Main explicitamente, mas ele não pode funcionar como
um ponto de entrada. O compilador emite o seguinte aviso:
CS7022 O ponto de entrada do programa é o código global, ignorando o ponto de
entrada "Main()".
Em um projeto com instruções de nível superior, você não pode usar a opção do
compilador -main para selecionar o ponto de entrada, mesmo que o projeto tenha um
ou mais métodos Main .
using diretivas
Se você incluir o uso de diretivas, elas deverão vir primeiro no arquivo, como neste
exemplo:
C#
using System.Text;
StringBuilder builder = new();
builder.AppendLine("Hello");
builder.AppendLine("World!");
Console.WriteLine(builder.ToString());
Namespace global
As instruções de nível superior estão implicitamente no namespace global.
Namespaces e definições de tipo
Um arquivo com instruções de nível superior também pode conter namespaces e
definições de tipo, mas devem vir após as instruções de nível superior. Por exemplo:
C#
MyClass.TestMethod();
MyNamespace.MyClass.MyMethod();
public class MyClass
public static void TestMethod()
Console.WriteLine("Hello World!");
namespace MyNamespace
class MyClass
public static void MyMethod()
Console.WriteLine("Hello World from
MyNamespace.MyClass.MyMethod!");
args
Instruções de nível superior podem fazer referência à variável args para acessar
quaisquer argumentos de linha de comando que foram inseridos. A variável args nunca
será nula, mas o Length dela será zero se nenhum argumento de linha de comando
tiver sido fornecido. Por exemplo:
C#
if (args.Length > 0)
foreach (var arg in args)
Console.WriteLine($"Argument={arg}");
else
Console.WriteLine("No arguments");
await
Você pode chamar um método assíncrono usando await . Por exemplo:
C#
Console.Write("Hello ");
await Task.Delay(5000);
Console.WriteLine("World!");
Código de saída do processo
Para retornar um valor int quando o aplicativo terminar, use a instrução return como
faria em um método Main que retorna um int . Por exemplo:
C#
string? s = Console.ReadLine();
int returnValue = int.Parse(s ?? "-1");
return returnValue;
Método de ponto de entrada implícito
O compilador gera um método para servir como o ponto de entrada do programa para
um projeto com instruções de nível superior. O nome desse método, na verdade, não
é Main , é um detalhe de implementação que seu código não pode referenciar
diretamente. A assinatura do método depende se as instruções de nível superior contêm
a palavra-chave await ou a instrução return . A tabela a seguir mostra como seria a
assinatura do método, usando o nome de método Main na tabela para fins de
conveniência.
O código de nível superior contém Assinatura Main implícita
await e return static async Task<int> Main(string[] args)
await static async Task Main(string[] args)
return static int Main(string[] args)
Não await nem return static void Main(string[] args)
Especificação da linguagem C#
Para obter mais informações, consulte a Especificação da linguagem C#. A especificação
da linguagem é a fonte definitiva para a sintaxe e o uso de C#.
Especificação de recurso – Instruções de nível superior
O sistema do tipo C#
Artigo • 15/02/2023
C# é uma linguagem fortemente tipada. Todas as variáveis e constantes têm um tipo,
assim como cada expressão que é avaliada como um valor. Cada declaração de método
especifica um nome, o tipo e a variante (valor, referência ou saída) de cada parâmetro
de entrada e do valor retornado. A biblioteca de classes do .NET define tipos numéricos
internos e tipos complexos que representam uma grande variedade de constructos. Isso
inclui o sistema de arquivos, conexões de rede, coleções e matrizes de objetos e datas.
Um programa em C# típico usa tipos da biblioteca de classes e tipos definidos pelo
usuário que modelam os conceitos que são específicos para o domínio do problema do
programa.
As informações armazenadas em um tipo podem incluir os seguintes itens:
O espaço de armazenamento que uma variável do tipo requer.
Os valores mínimo e máximo que ele pode representar.
Os membros (métodos, campos, eventos e etc.) que ele contém.
O tipo base do qual ele herda.
A interface implementada.
Os tipos de operações que são permitidos.
O compilador usa as informações de tipo para garantir que todas as operações que são
realizadas em seu código sejam fortemente tipadas. Por exemplo, se você declarar uma
variável do tipo int, o compilador permitirá que você use a variável nas operações de
adição e subtração. Se você tentar executar as mesmas operações em uma variável do
tipo bool, o compilador gerará um erro, como mostrado no exemplo a seguir:
C#
int a = 5;
int b = a + 2; //OK
bool test = true;
// Error. Operator '+' cannot be applied to operands of type 'int' and
'bool'.
int c = a + test;
7 Observação
Desenvolvedores de C e C++, observem que, em C#, bool não é conversível em
int .
O compilador insere as informações de tipo no arquivo executável como metadados. O
CLR (Common Language Runtime) usa esses metadados em tempo de execução para
assegurar mais segurança de tipos ao alocar e recuperar a memória.
Especificando tipos em declarações de variável
Quando declara uma variável ou constante em um programa, você deve especificar seu
tipo ou usar a palavra-chave var para permitir que o compilador infira o tipo. O exemplo
a seguir mostra algumas declarações de variáveis que usam tipos numéricos internos e
tipos complexos definidos pelo usuário:
C#
// Declaration only:
float temperature;
string name;
MyClass myClass;
// Declaration with initializers (four examples):
char firstLetter = 'C';
var limit = 3;
int[] source = { 0, 1, 2, 3, 4, 5 };
var query = from item in source
where item <= limit
select item;
Os tipos de parâmetros de método e valores de retorno são especificados na declaração
do método. A assinatura a seguir mostra um método que requer um int como um
argumento de entrada e retorna uma cadeia de caracteres:
C#
public string GetName(int ID)
if (ID < names.Length)
return names[ID];
else
return String.Empty;
private string[] names = { "Spencer", "Sally", "Doug" };
Depois de declarar uma variável, não será possível reenviá-la com um novo tipo, nem
atribuir um valor incompatível com seu tipo declarado. Por exemplo, você não pode
declarar um int e, em seguida, atribuir a ele um valor booliano de true . No entanto, os
valores podem ser convertidos em outros tipos, por exemplo, quando são passados
como argumentos de método ou atribuídos a novas variáveis. Uma conversão de tipo
que não causa a perda de dados é executada automaticamente pelo compilador. Uma
conversão que pode causar perda de dados requer um cast no código-fonte.
Para obter mais informações, consulte Conversões Cast e Conversões de Tipo.
Tipos internos
O C# fornece um conjunto padrão de tipos internos. Eles representam números inteiros,
valores de ponto flutuante, expressões boolianas, caracteres de texto, valores decimais e
outros tipos de dados. Também há tipos string e object internos. Esses tipos estão
disponíveis para uso em qualquer programa em C#. Para obter a lista completa tipos
internos, consulte Tipos internos.
Tipos personalizados
Você usa os constructos struct, class, interface, enum e record para criar seus próprios
tipos personalizados. A biblioteca de classes do .NET em si é uma coleção de tipos
personalizados que você pode usar em seus próprios aplicativos. Por padrão, os tipos
usados com mais frequência na biblioteca de classes estão disponíveis em qualquer
programa em C#. Outros ficam disponíveis somente quando você adiciona
explicitamente uma referência de projeto ao assembly que os define. Depois que o
compilador tiver uma referência ao assembly, você pode declarar variáveis (e
constantes) dos tipos declarados nesse assembly no código-fonte. Para saber mais,
confira Biblioteca de classes do .NET.
O Common Type System
É importante entender os dois pontos fundamentais sobre o sistema de tipos do .NET:
Ele dá suporte ao conceito de herança. Os tipos podem derivar de outros tipos,
chamados tipos base. O tipo derivado herda (com algumas restrições) os métodos,
as propriedades e outros membros do tipo base. O tipo base, por sua vez, pode
derivar de algum outro tipo, nesse caso, o tipo derivado herda os membros de
ambos os tipos base na sua hierarquia de herança. Todos os tipos, incluindo tipos
numéricos internos, como o System.Int32 (palavra-chave do C#: int ), derivam, em
última análise, de um único tipo base, que é o System.Object (palavra-chave do C#:
object). Essa hierarquia unificada de tipos é chamada de CTS (Common Type
System). Para obter mais informações sobre herança em C#, consulte Herança.
Cada tipo no CTS é definido como um tipo de valor ou um tipo de referência. Esses
tipos incluem todos os tipos personalizados na biblioteca de classes do .NET, além
de tipos personalizados definidos pelo usuário. Os tipos que você define usando a
palavra-chave struct são tipos de valor. Todos os tipos numéricos internos são
structs . Os tipos que você define usando a palavra-chave class ou record são
tipos de referência. Os tipos de referência e os tipos de valor têm diferentes regras
de tempo de compilação e comportamento de tempo de execução diferente.
A ilustração a seguir mostra a relação entre tipos de referência e tipos de valor no CTS.
7 Observação
Você pode ver que os tipos mais usados normalmente são todos organizados no
namespace System. No entanto, o namespace no qual um tipo está contido não
tem relação com a possibilidade de ele ser um tipo de valor ou um tipo de
referência.
Classes e structs são duas das construções básicas do Common Type System no .NET. O
C# 9 adiciona registros, que são um tipo de classe. Cada um é, essencialmente, uma
estrutura de dados que encapsula um conjunto de dados e os comportamentos que são
uma unidade lógica. Os dados e comportamentos são os membros da classe, struct ou
registro. Os membros incluem seus métodos, propriedades, eventos e assim por diante,
conforme listado posteriormente neste artigo.
Uma declaração de classe, struct ou registro é como um plano que é usado para criar
instâncias ou objetos em tempo de execução. Se você definir uma classe, struct ou
registro denominado Person , Person será o nome do tipo. Se você declarar e inicializar
um p variável do tipo Person , p será considerado um objeto ou uma instância de
Person . Várias instâncias do mesmo tipo Person podem ser criadas, e cada instância
pode ter valores diferentes em suas propriedades e campos.
Uma classe é um tipo de referência. Quando um objeto do tipo é criado, a variável à
qual o objeto é atribuído armazena apenas uma referência na memória. Quando a
referência de objeto é atribuída a uma nova variável, a nova variável refere-se ao objeto
original. As alterações feitas por meio de uma variável são refletidas na outra variável
porque ambas se referem aos mesmos dados.
Um struct é um tipo de valor. Quando um struct é criado, a variável à qual o struct está
atribuído contém os dados reais do struct. Quando o struct é atribuído a uma nova
variável, ele é copiado. A nova variável e a variável original, portanto, contêm duas
cópias separadas dos mesmos dados. As alterações feitas em uma cópia não afetam a
outra cópia.
Os tipos de registro podem ser tipos de referência ( record class ) ou tipos de valor
( record struct ).
Em geral, as classes são usadas para modelar um comportamento mais complexo. As
classes normalmente armazenam dados que devem ser modificados depois que um
objeto de classe é criado. Structs são mais adequados para estruturas de dados
pequenas. Os structs normalmente armazenam dados que não se destinam a serem
modificados após a criação do struct. Os tipos de registro são estruturas de dados com
membros sintetizados de compilador adicionais. Os registros normalmente armazenam
dados que não se destinam a serem modificados após a criação do objeto.
Tipos de valor
Os tipos de valor derivam de System.ValueType, que deriva de System.Object. Os tipos
que derivam de System.ValueType apresentam um comportamento especial no CLR. As
variáveis de tipo de valor contêm diretamente seus valores. A memória de um struct é
embutida em qualquer contexto em que a variável seja declarada. Não há nenhuma
alocação de heap separada ou sobrecarga de coleta de lixo para variáveis do tipo de
valor. É possível declarar tipos record struct que são tipos de valor e incluir os
membros sintetizados para registros.
Há duas categorias de tipos de valor: struct e enum .
Os tipos numéricos internos são structs e têm campos e métodos que você pode
acessar:
C#
// constant field on type byte.
byte b = byte.MaxValue;
Mas você declara e atribui valores a eles como se fossem tipos de não agregação
simples:
C#
byte num = 0xA;
int i = 5;
char c = 'Z';
Os tipos de valor são selados. Não é possível derivar um tipo de qualquer tipo de valor,
por exemplo System.Int32. Você não pode definir um struct a ser herdado de qualquer
classe definida pelo usuário ou struct, porque um struct só pode ser herdado de
System.ValueType. No entanto, um struct pode implementar uma ou mais interfaces. É
possível converter um tipo struct em qualquer tipo de interface que ele implementa.
Essa conversão faz com que uma operação de conversão boxing encapsule o struct
dentro de um objeto de tipo de referência no heap gerenciado. As operações de
conversão boxing ocorrem quando você passa um tipo de valor para um método que
usa um System.Object ou qualquer tipo de interface como parâmetro de entrada. Para
obter mais informações, consulte Conversões boxing e unboxing.
Você usa a palavra-chave struct para criar seus próprios tipos de valor personalizados.
Normalmente, um struct é usado como um contêiner para um pequeno conjunto de
variáveis relacionadas, conforme mostrado no exemplo a seguir:
C#
public struct Coords
public int x, y;
public Coords(int p1, int p2)
x = p1;
y = p2;
Para obter mais informações sobre structs, consulte Tipos de estrutura. Para saber mais
sobre os tipos de valor, confira Tipos de valor.
A outra categoria de tipos de valor é enum . Uma enum define um conjunto de
constantes integrais nomeadas. Por exemplo, a enumeração System.IO.FileMode na
biblioteca de classes do .NET contém um conjunto de números inteiros constantes
nomeados que especificam como um arquivo deve ser aberto. Ela é definida conforme
mostrado no exemplo abaixo:
C#
public enum FileMode
CreateNew = 1,
Create = 2,
Open = 3,
OpenOrCreate = 4,
Truncate = 5,
Append = 6,
A constante System.IO.FileMode.Create tem um valor de 2. No entanto, o nome é muito
mais significativo para a leitura do código-fonte por humanos e, por esse motivo, é
melhor usar enumerações em vez de números literais constantes. Para obter mais
informações, consulte System.IO.FileMode.
Todas as enumerações herdam de System.Enum, que herda de System.ValueType. Todas
as regras que se aplicam a structs também se aplicam a enums. Para obter mais
informações sobre enums, consulte Tipos de enumeração.
Tipos de referência
Um tipo que é definido como class , record , delegate, matriz ou interface é um
reference type.
Ao declarar uma variável de um reference type, ele contém o valor null até que você o
atribua com uma instância desse tipo ou crie uma usando o operador new. A criação e a
atribuição de uma classe são demonstradas no exemplo a seguir:
C#
MyClass myClass = new MyClass();
MyClass myClass2 = myClass;
Não é possível criar uma instância direta de interface usando o operador new. Em vez
disso, crie e atribua uma instância de uma classe que implemente a interface. Considere
o seguinte exemplo:
C#
MyClass myClass = new MyClass();
// Declare and assign using an existing value.
IMyInterface myInterface = myClass;
// Or create and assign a value in a single statement.
IMyInterface myInterface2 = new MyClass();
Quando o objeto é criado, a memória é alocada no heap gerenciado. A variável contém
apenas uma referência ao local do objeto. Os tipos no heap gerenciado exigem
sobrecarga quando são alocados e recuperados. A coleta de lixo é a funcionalidade de
gerenciamento automático de memória do CLR, que executa a recuperação. No entanto,
a coleta de lixo também é altamente otimizada e, na maioria dos cenários, não cria um
problema de desempenho. Para obter mais informações sobre a coleta de lixo, consulte
Gerenciamento automático de memória.
Todas as matrizes são tipos de referência, mesmo se seus elementos forem tipos de
valor. As matrizes derivam implicitamente da classe System.Array. Você declara e usa as
matrizes com a sintaxe simplificada fornecida pelo C#, conforme mostrado no exemplo
a seguir:
C#
// Declare and initialize an array of integers.
int[] nums = { 1, 2, 3, 4, 5 };
// Access an instance property of System.Array.
int len = nums.Length;
Os tipos de referência dão suporte completo à herança. Quando você cria uma classe, é
possível herdar de qualquer outra interface ou classe que não esteja definida como
selada. Outras classes podem herdar de sua classe e substituir seus métodos virtuais.
Para obter mais informações sobre como criar suas próprias classes, consulte Classes,
structs e registros. Para obter mais informações sobre herança e métodos virtuais,
consulte Herança.
Tipos de valores literais
No C#, valores literais recebem um tipo do compilador. Você pode especificar como um
literal numérico deve ser digitado anexando uma letra ao final do número. Por exemplo,
para especificar que o valor 4.56 deve ser tratado como um float , acrescente um "f"
ou "F" após o número: 4.56f . Se nenhuma letra for anexada, o compilador inferirá um
tipo para o literal. Para obter mais informações sobre quais tipos podem ser
especificados com sufixos de letra, consulte Tipos numéricos integrais e Tipos
numéricos de ponto flutuante.
Como os literais são tipados e todos os tipos derivam basicamente de System.Object,
você pode escrever e compilar o código como o seguinte:
C#
string s = "The answer is " + 5.ToString();
// Outputs: "The answer is 5"
Console.WriteLine(s);
Type type = 12345.GetType();
// Outputs: "System.Int32"
Console.WriteLine(type);
Tipos genéricos
Um tipo pode ser declarado com um ou mais parâmetros de tipo que servem como um
espaço reservado para o tipo real (o tipo concreto). O código do cliente fornece o tipo
concreto quando ele cria uma instância do tipo. Esses tipos são chamados de tipos
genéricos. Por exemplo, o tipo do .NET System.Collections.Generic.List<T> tem um
parâmetro de tipo que, por convenção, recebe o nome T . Ao criar uma instância do
tipo, você pode especificar o tipo dos objetos que a lista conterá, por exemplo, string :
C#
List<string> stringList = new List<string>();
stringList.Add("String example");
// compile time error adding a type other than a string:
stringList.Add(4);
O uso do parâmetro de tipo possibilita a reutilização da mesma classe para conter
qualquer tipo de elemento sem precisar converter cada elemento em objeto. As classes
de coleção genéricas são chamadas de coleções fortemente tipadas porque o compilador
sabe o tipo específico dos elementos da coleção e pode gerar um erro em tempo de
compilação se, por exemplo, você tentar adicionar um inteiro ao objeto stringList no
exemplo anterior. Para obter mais informações, consulte Genéricos.
Tipos implícitos, tipos anônimos e tipos que
permitem valor nulo
Você pode digitar implicitamente uma variável local (mas não os membros de classe)
usando a palavra-chave var. A variável ainda recebe um tipo em tempo de compilação,
mas o tipo é fornecido pelo compilador. Para obter mais informações, consulte Variáveis
locais de tipo implícito.
Pode ser inconveniente criar um tipo nomeado para conjuntos simples de valores
relacionados que você não pretende armazenar ou transmitir fora dos limites de
método. Você pode criar tipos anônimos para essa finalidade. Para obter mais
informações, consulte Tipos Anônimos.
Os tipos comuns de valor não podem ter um valor null. No entanto, você pode criar
tipos de valor anulável acrescentando uma ? após o tipo. Por exemplo, int? é um tipo
int que também pode ter o valor null. Os tipos que permitem valor nulo são instâncias
do tipo struct genérico System.Nullable<T>. Os tipos que permitem valor nulo são
especialmente úteis quando você está passando dados entre bancos de dados nos quais
os valores numéricos podem ser null . Para obter mais informações, consulte Tipos que
permitem valor nulo.
Tipo de tempo de compilação e tipo de tempo
de execução
Uma variável pode ter diferentes tipos de tempo de compilação e tempo de execução.
O tipo de tempo de compilação é o tipo declarado ou inferido da variável no código-
fonte. O tipo de tempo de execução é o tipo da instância referenciada por essa variável.
Geralmente, esses dois tipos são iguais, como no exemplo a seguir:
C#
string message = "This is a string of characters";
Em outros casos, o tipo de tempo de compilação é diferente, conforme mostrado nos
dois exemplos a seguir:
C#
object anotherMessage = "This is another string of characters";
IEnumerable<char> someCharacters = "abcdefghijklmnopqrstuvwxyz";
Em ambos os exemplos acima, o tipo de tempo de execução é um string . O tipo de
tempo de compilação é object na primeira linha e IEnumerable<char> na segunda.
Se os dois tipos forem diferentes para uma variável, é importante entender quando o
tipo de tempo de compilação e o tipo de tempo de execução se aplicam. O tipo de
tempo de compilação determina todas as ações executadas pelo compilador. Essas
ações do compilador incluem resolução de chamada de método, resolução de
sobrecarga e conversões implícitas e explícitas disponíveis. O tipo de tempo de
execução determina todas as ações que são resolvidas em tempo de execução. Essas
ações de tempo de execução incluem a expedição de chamadas de método virtual,
avaliação das expressões is e switch e outras APIs de teste de tipo. Para entender
melhor como seu código interage com tipos, reconheça qual ação se aplica a qual tipo.
Seções relacionadas
Para obter mais informações, consulte os seguintes artigos:
Tipos internos
Tipos de valor
Tipos de referência
Especificação da linguagem C#
Para obter mais informações, consulte a Especificação da linguagem C#. A especificação
da linguagem é a fonte definitiva para a sintaxe e o uso de C#.
Declarar namespaces para organizar
tipos
Artigo • 10/05/2023
Os namespaces são usados intensamente em programações de C# de duas maneiras.
Em primeiro lugar, o .NET usa namespaces para organizar suas muitas classes, da
seguinte maneira:
C#
System.Console.WriteLine("Hello World!");
System é um namespace e Console é uma classe nesse namespace. A palavra-chave
using pode ser usada para que o nome completo não seja necessário, como no
exemplo a seguir:
C#
using System;
C#
Console.WriteLine("Hello World!");
Para saber mais, confira Diretiva using.
) Importante
Os modelos C# para o .NET 6 usam instruções de nível superior. Se você já tiver
atualizado para o .NET 6, talvez seu aplicativo não corresponda ao código descrito
neste artigo. Para obter mais informações, consulte o artigo sobre Novos modelos
C# geram instruções de nível superior
O SDK do .NET 6 também adiciona um conjunto de diretivas implícitas global
using para projetos que usam os seguintes SDKs:
Microsoft.NET.Sdk
Microsoft.NET.Sdk.Web
Microsoft.NET.Sdk.Worker
Essas diretivas implícitas global using incluem os namespaces mais comuns para o
tipo de projeto.
Em segundo lugar, declarar seus próprios namespaces pode ajudar a controlar o escopo
dos nomes de classe e de método em projetos de programação maiores. Use a palavra-
chave namespace para declarar um namespace, como no exemplo a seguir:
C#
namespace SampleNamespace
class SampleClass
public void SampleMethod()
System.Console.WriteLine(
"SampleMethod inside SampleNamespace");
O nome do namespace deve ser um nome do identificador válido em C#.
A partir do C# 10, você pode declarar um namespace para todos os tipos definidos
nesse arquivo, como mostra o exemplo a seguir:
C#
namespace SampleNamespace;
class AnotherSampleClass
public void AnotherSampleMethod()
System.Console.WriteLine(
"SampleMethod inside SampleNamespace");
A vantagem dessa nova sintaxe é que ela é mais simples, economizando espaço
horizontal e chaves. Isso facilita a leitura do seu código.
Visão geral dos namespaces
Os namespaces têm as seguintes propriedades:
Eles organizam projetos de códigos grandes.
Eles são delimitados com o uso do operador . .
A diretiva using elimina a necessidade de especificar o nome do namespace para
cada classe.
O namespace global é o namespace "raiz": global::System sempre fará referência
ao namespace do .NET System.
Especificação da linguagem C#
Para saber mais, confira a seção Namespaces da Especificação da linguagem C#.
Introdução às classes
Artigo • 02/06/2023
Tipos de referência
Um tipo que é definido como uma class é um tipo de referência. No tempo de execução,
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 que foi criado em outro lugar, conforme
mostrado no exemplo a seguir:
C#
//Declaring an object of type MyClass.
MyClass mc = new MyClass();
//Declaring another object of the same type, assigning it the value of the
first object.
MyClass mc2 = mc;
Quando o objeto é criado, memória suficiente é alocada no heap gerenciado para o
objeto específico, e a variável contém apenas uma referência para o local do objeto. A
memória usada por um objeto é recuperada pela funcionalidade de gerenciamento
automático de memória do CLR, que é conhecida como coleta de lixo. Para obter mais
informações sobre a coleta de lixo, consulte Gerenciamento automático de memória e
coleta de lixo.
Declarando classes
As classes são declaradas usando a palavra-chave class , seguida por um identificador
exclusivo, conforme mostrado no exemplo a seguir:
C#
//[access modifier] - [class] - [identifier]
public class Customer
// Fields, properties, methods and events go here...
Um modificador de acesso opcional precede a palavra-chave class . Como public é
usado nesse caso, qualquer pessoa pode criar instâncias dessa classe. O nome da classe
segue a palavra-chave class . O nome da classe deve ser um nome do identificador
válido em C#. O restante da definição é o corpo da classe, em que o comportamento e
os dados são definidos. Campos, propriedades, métodos e eventos em uma classe são
coletivamente denominados de membros de classe.
Criando objetos
Embora eles sejam usados algumas vezes de maneira intercambiável, uma classe e um
objeto são coisas diferentes. Uma classe define um tipo de objeto, mas não é um objeto
em si. Um objeto é uma entidade concreta com base em uma classe e, às vezes, é
conhecido como uma instância de uma classe.
Os objetos podem ser criados usando a palavra-chave new seguida pelo nome da
classe, dessa maneira:
C#
Customer object1 = new Customer();
Quando uma instância de uma classe é criada, uma referência ao objeto é passada de
volta para o programador. No exemplo anterior, object1 é uma referência a um objeto
que é baseado em Customer . Esta referência refere-se ao novo objeto, mas não contém
os dados de objeto. Na verdade, você pode criar uma referência de objeto sem criar um
objeto:
C#
Customer object2;
Não recomendamos a criação de referências de objeto, que não faz referência a um
objeto, porque tentar acessar um objeto por meio de uma referência desse tipo falhará
em tempo de execução. Uma referência pode ser feita para se referir a um objeto,
criando um novo objeto ou atribuindo-a a um objeto existente, como abaixo:
C#
Customer object3 = new Customer();
Customer object4 = object3;
Esse código cria duas referências de objeto que fazem referência ao mesmo objeto.
Portanto, qualquer alteração no objeto feita por meio de object3 será refletida no usos
posteriores de object4 . Como os objetos que são baseados em classes são
referenciados por referência, as classes são conhecidas como tipos de referência.
Construtores e inicialização
As seções anteriores introduziram a sintaxe para declarar um tipo de classe e criar uma
instância desse tipo. Ao criar uma instância de um tipo, você deseja garantir que seus
campos e propriedades sejam inicializados para valores úteis. Há várias maneiras de
inicializar valores:
Aceitar valores padrão
Inicializadores de campo
Parâmetros do construtor
Inicializadores de objeto
Cada tipo .NET tem um valor padrão. Normalmente, esse valor é 0 para tipos de número
e null para todos os tipos de referência. Você pode contar com esse valor padrão
quando for razoável em seu aplicativo.
Quando o padrão .NET não é o valor certo, você pode definir um valor inicial usando um
inicializador de campo:
C#
public class Container
// Initialize capacity field to a default value of 10:
private int _capacity = 10;
Você pode exigir que os chamadores forneçam um valor inicial definindo um construtor
responsável por definir esse valor inicial:
C#
public class Container
private int _capacity;
public Container(int capacity) => _capacity = capacity;
A partir do C# 12, você pode definir um construtor primário como parte da declaração
de classe:
C#
public class Container(int capacity)
private int _capacity = capacity;
Adicionar parâmetros ao nome da classe define o construtor primário. Esses parâmetros
estão disponíveis no corpo da classe, que inclui seus membros. Você pode usá-los para
inicializar campos ou em qualquer outro lugar em que eles sejam necessários.
Você também pode usar o modificador required em uma propriedade e permitir que os
chamadores usem um inicializador de objeto para definir o valor inicial da propriedade:
C#
public class Person
public required string LastName { get; set; }
public required string FirstName { get; set; }
A adição da palavra-chave required determina que os chamadores devem definir essas
propriedades como parte de uma expressão new :
C#
var p1 = new Person(); // Error! Required properties not set
var p2 = new Person() { FirstName = "Grace", LastName = "Hopper" };
Herança de classe
As classes dão suporte completo à herança, uma característica fundamental da
programação orientada a objetos. Quando você cria uma classe, é possível herdar de
qualquer outra classe que não esteja definida como sealed. Outras classes podem
herdar de sua classe e substituir métodos virtuais de classe. Além disso, você pode
implementar uma ou mais interfaces.
A herança é realizada usando uma derivação, o que significa que uma classe é declarada
usando uma classe base, da qual ela herda o comportamento e os dados. Uma classe
base é especificada ao acrescentar dois-pontos e o nome de classe base depois do
nome de classe derivada, dessa maneira:
C#
public class Manager : Employee
// Employee fields, properties, methods and events are inherited
// New Manager fields, properties, methods and events go here...
Quando uma declaração de classe inclui uma classe base, ela herda todos os membros
da classe base, exceto os construtores. Para obter mais informações, consulte Herança.
Uma classe no C# só pode herdar diretamente de uma classe base. No entanto, como
uma classe base pode herdar de outra classe, uma classe pode 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 • 02/06/2023
Um registro em C# é uma classe ou um struct que fornece sintaxe e comportamento
especiais para trabalhar com modelos de dados. O modificador record instrui o
compilador a sintetizar membros que são úteis para tipos cuja função primária é
armazenar dados. Esses membros incluem uma sobrecarga de ToString() e membros
que dão suporte à igualdade de valor.
Quando usar registros
Considere usar um registro no lugar de uma classe ou struct nos seguintes cenários:
Você deseja definir um modelo de dados que depende da igualdade de valor.
Você deseja definir um tipo para o qual os objetos são imutáveis.
Igualdade de valor
Para registros, a igualdade de 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. Para outros tipos de referência, como classes, igualdade significa
igualdade de referência. Ou seja, duas variáveis de um tipo de classe são iguais quando
se referem ao mesmo objeto. Métodos e operadores que determinam a igualdade de
duas instâncias de registro usam igualdade de valor.
Nem todos os modelos de dados funcionam bem com igualdade de valor. Por exemplo,
o Entity Framework Core depende da igualdade de referência para garantir que ele use
apenas uma instância de um tipo de entidade para o que é conceitualmente uma
entidade. Por esse motivo, os tipos de registro não são apropriados para uso como tipos
de entidade no Entity Framework Core.
Imutabilidade
Um tipo imutável é aquele que impede que você altere qualquer propriedade ou valor
de campo de um objeto após ser instanciado. A imutabilidade pode ser útil quando
você precisa que um tipo seja thread-safe ou que um código hash permaneça o mesmo
em uma tabela de hash. Os registros fornecem sintaxe concisa para criar e trabalhar
com tipos imutáveis.
A imutabilidade não é apropriada para todos os cenários de dados. O Entity Framework
Core, por exemplo, não dá suporte à atualização com tipos de entidade imutáveis.
Como os registros diferem das classes e dos
structs
A mesma sintaxe que declara e instancia classes ou structs pode ser usada com
registros. Basta substituir a palavra-chave class por record , ou usar record struct em
vez de struct . Da mesma forma, as classes de registro dão suporte à mesma sintaxe
para expressar relações de herança. Os registros diferem das classes das seguintes
maneiras:
Você pode usar parâmetros posicionais em um construtor primário para criar e
instanciar um tipo com propriedades imutáveis.
Os mesmos métodos e operadores que indicam igualdade de referência ou
desigualdade em classes (como Object.Equals(Object) e == ), indicam igualdade de
valor ou desigualdade nos registros.
Você pode usar uma expressão with para criar uma cópia de um objeto imutável
com novos valores em propriedades selecionadas.
O método ToString de um registro cria uma cadeia de caracteres formatada que
mostra o nome do tipo de um objeto e os nomes e valores de todas as
propriedades públicas dele.
Um registro pode herdar de outro registro. Um registro não pode herdar de uma
classe, e uma classe não pode herdar de um registro.
Os structs de registro diferem dos structs, pois o compilador sintetiza os métodos de
igualdade e ToString . O compilador sintetiza o método Deconstruct para structs de
registro posicional.
O compilador sintetiza uma propriedade public init-only para cada parâmetro de
construtor primário em um record class . Em um record struct , o compilador sintetiza
uma propriedade pública de leitura/gravação. O compilador não cria propriedades para
parâmetros de construtor primário em tipos class e struct que não incluem
modificador record .
Exemplos
O exemplo a seguir define um registro público que usa parâmetros posicionais para
declarar e instanciar um registro. Em seguida, ele imprime o nome do tipo e os valores
de propriedade:
C#
public record Person(string FirstName, string LastName);
public static void Main()
Person person = new("Nancy", "Davolio");
Console.WriteLine(person);
// output: Person { FirstName = Nancy, LastName = Davolio }
O seguinte exemplo demonstra a igualdade de valor em registros:
C#
public record Person(string FirstName, string LastName, string[]
PhoneNumbers);
public static void Main()
var phoneNumbers = new string[2];
Person person1 = new("Nancy", "Davolio", phoneNumbers);
Person person2 = new("Nancy", "Davolio", phoneNumbers);
Console.WriteLine(person1 == person2); // output: True
person1.PhoneNumbers[0] = "555-1234";
Console.WriteLine(person1 == person2); // output: True
Console.WriteLine(ReferenceEquals(person1, person2)); // output: False
O seguinte exemplo demonstra o uso de uma expressão with para copiar um objeto
imutável e alterar uma das propriedades:
C#
public record Person(string FirstName, string LastName)
public string[] PhoneNumbers { get; init; }
public static void Main()
Person person1 = new("Nancy", "Davolio") { PhoneNumbers = new string[1]
};
Console.WriteLine(person1);
// output: Person { FirstName = Nancy, LastName = Davolio, PhoneNumbers
= System.String[] }
Person person2 = person1 with { FirstName = "John" };
Console.WriteLine(person2);
// output: Person { FirstName = John, LastName = Davolio, PhoneNumbers =
System.String[] }
Console.WriteLine(person1 == person2); // output: False
person2 = person1 with { PhoneNumbers = new string[1] };
Console.WriteLine(person2);
// output: Person { FirstName = Nancy, LastName = Davolio, PhoneNumbers
= System.String[] }
Console.WriteLine(person1 == person2); // output: False
person2 = person1 with { };
Console.WriteLine(person1 == person2); // output: True
Para obter mais informações, confira Registros (referência de C#).
Especificação da Linguagem C#
Para obter mais informações, consulte a Especificação da linguagem C#. A especificação
da linguagem é a fonte definitiva para a sintaxe e o uso de C#.
Interfaces – definir comportamento
para vários tipos
Artigo • 18/03/2023
Uma interface contém definições para um grupo de funcionalidades relacionadas que
uma class ou um struct podem implementar. Uma interface pode definir métodos
static , que devem ter uma implementação. Uma interface pode definir uma
implementação padrão para membros. Uma interface pode não declarar dados de
instância, como campos, propriedades implementadas automaticamente ou eventos
semelhantes a propriedades.
Usando interfaces, você pode, por exemplo, incluir o comportamento de várias fontes
em uma classe. Essa funcionalidade é importante em C# porque a linguagem não dá
suporte a várias heranças de classes. Além disso, use uma interface se você deseja
simular a herança para structs, pois eles não podem herdar de outro struct ou classe.
Você define uma interface usando a palavra-chave interface, como mostra o exemplo a
seguir.
C#
interface IEquatable<T>
bool Equals(T obj);
O nome da interface deve ser um nome do identificador válido em C#. Por convenção,
os nomes de interface começam com uma letra maiúscula I .
Qualquer classe ou struct que implemente a interface IEquatable<T> deve conter uma
definição para um método Equals que corresponda à assinatura que a interface
especifica. Como resultado, você pode contar com uma classe que implementa
IEquatable<T> para conter um método Equals com o qual uma instância da classe pode
determinar se é igual a outra instância da mesma classe.
A definição de IEquatable<T> não fornece uma implementação para Equals . Uma classe
ou estrutura pode implementar várias interfaces, mas uma classe só pode herdar de
uma única classe.
Para obter mais informações sobre classes abstratas, consulte Classes e membros de
classes abstratos e lacrados.
As interfaces podem conter métodos, propriedades, eventos, indexadores ou qualquer
combinação desses quatro tipos de membros. As interfaces podem conter construtores
estáticos, campos, constantes ou operadores. A partir do C# 11, os membros da
interface que não são campos podem ser static abstract . Uma interface não pode
conter campos de instância, construtores de instância ou finalizadores. Os membros da
interface são públicos por padrão, e você pode especificar explicitamente modificadores
de acessibilidade, como public , protected , internal , private , protected internal ou
private protected . Um membro private deve ter uma implementação padrão.
Para implementar um membro de interface, o membro correspondente da classe de
implementação deve ser público, não estático e ter o mesmo nome e assinatura do
membro de interface.
7 Observação
Quando uma interface declara membros estáticos, um tipo que implementa essa
interface também pode declarar membros estáticos com a mesma assinatura. Eles
são distintos e identificados exclusivamente pelo tipo que declara o membro. O
membro estático declarado em um tipo não substitui o membro estático declarado
na interface.
Uma classe ou struct que implementa uma interface deve fornecer uma implementação
para todos os membros declarados sem uma implementação padrão fornecida pela
interface. No entanto, se uma classe base implementa uma interface, qualquer classe
que é derivada da classe base herda essa implementação.
O exemplo a seguir mostra uma implementação da interface IEquatable<T>. A classe de
implementação, Car , deverá fornecer uma implementação do método Equals.
C#
public class Car : IEquatable<Car>
public string? Make { get; set; }
public string? Model { get; set; }
public string? Year { get; set; }
// Implementation of IEquatable<T> interface
public bool Equals(Car? car)
return (this.Make, this.Model, this.Year) ==
(car?.Make, car?.Model, car?.Year);
As propriedades e os indexadores de uma classe podem definir acessadores extras para
uma propriedade ou o indexador que é definido em uma interface. Por exemplo, uma
interface pode declarar uma propriedade que tem um acessador get. A classe que
implementa a interface pode declarar a mesma propriedade tanto com um acessador
get quanto com um set. No entanto, se a propriedade ou o indexador usa a
implementação explícita, os acessadores devem corresponder. Para obter mais
informações sobre a implementação explícita, consulte Implementação de interface
explícita e Propriedades da interface.
Uma interface pode herdar de uma ou mais interfaces. A interface derivada herda os
membros de suas interfaces base. Uma classe que implementa uma interface derivada
deve implementar todos os membros na interface derivada, incluindo todos os
membros das interfaces base da interface derivada. Essa classe pode ser convertida
implicitamente para a interface derivada ou para qualquer uma de suas interfaces base.
Uma classe pode incluir uma interface várias vezes por meio das classes base que ela
herda ou por meio de interfaces que outras interfaces herdam. No entanto, a classe
poderá fornecer uma implementação de uma interface apenas uma vez e somente se a
classe declarar a interface como parte da definição de classe ( class ClassName :
InterfaceName ). Se a interface é herdada porque é herdada de uma classe base que
implementa a interface, a classe base fornece a implementação dos membros da
interface. No entanto, a classe derivada pode reimplementar qualquer membro de
interface virtual em vez de usar a implementação herdada. Quando as interfaces
declaram uma implementação padrão de um método, qualquer classe que implemente
essa interface herda essa implementação (você precisa converter a instância da classe
para o tipo de interface para acessar a implementação padrão no membro da interface).
Uma classe base também pode implementar membros de interface usando membros
virtuais. Nesse caso, uma classe derivada pode alterar o comportamento da interface
substituindo os membros virtuais. Para obter mais informações sobre membros virtuais,
consulte Polimorfismo.
Resumo de interfaces
Uma interface tem as propriedades a seguir:
Nas versões do C# anteriores à 8.0, uma interface é como uma classe base abstrata
que contém apenas membros abstratos. Qualquer classe ou struct que implemente
uma interface deve implementar todos os seus membros.
A partir do C# 8.0, uma interface pode definir implementações padrão para alguns
ou todos os seus membros. Uma classe ou struct que implementa a interface não
precisa implementar membros que tenham implementações padrão. Para obter
mais informações, consulte método de interface padrão.
Uma interface não pode ser instanciada diretamente. Seus membros são
implementados por qualquer classe ou struct que implemente a interface.
Uma classe ou struct pode implementar várias interfaces. Uma classe pode herdar
uma classe base e também implementar uma ou mais interfaces.
Classes e métodos genéricos
Artigo • 09/05/2023
Genéricos apresentam o conceito de parâmetros de tipo ao .NET, que possibilitam a
criação de classes e métodos que adiam a especificação de um ou mais tipos até que a
classe ou método seja declarado e instanciado pelo código do cliente. Por exemplo, ao
usar um parâmetro de tipo genérico T , você pode escrever uma única classe que outro
código de cliente pode usar sem incorrer o custo ou risco de conversões de runtime ou
operações de conversão boxing, conforme mostrado aqui:
C#
// Declare the generic class.
public class GenericList<T>
public void Add(T input) { }
class TestGenericList
private class ExampleClass { }
static void Main()
// Declare a list of type int.
GenericList<int> list1 = new GenericList<int>();
list1.Add(1);
// Declare a list of type string.
GenericList<string> list2 = new GenericList<string>();
list2.Add("");
// Declare a list of type ExampleClass.
GenericList<ExampleClass> list3 = new GenericList<ExampleClass>();
list3.Add(new ExampleClass());
As classes e métodos genéricos combinam a capacidade de reutilização, a segurança de
tipos e a eficiência de uma maneira que suas contrapartes não genéricas não
conseguem. Os genéricos são usados com mais frequência com coleções e com os
métodos que operam nelas. O namespace System.Collections.Generic contém várias
classes de coleção de base genérica. As coleções não-genéricas, como ArrayList não são
recomendadas e são mantidas para fins de compatibilidade. Para saber mais, confira
Genéricos no .NET.
Você também pode criar tipos e métodos genéricos personalizados para fornecer suas
próprias soluções e padrões de design generalizados que sejam fortemente tipados e
eficientes. O exemplo de código a seguir mostra uma classe de lista vinculada genérica
simples para fins de demonstração. (Na maioria dos casos, você deve usar a classe
List<T> fornecida pelo .NET em vez de criar sua própria.) O parâmetro de tipo T é
usado em vários locais em que um tipo concreto normalmente seria usado para indicar
o tipo do item armazenado na lista. Ele é usado das seguintes maneiras:
Como o tipo de um parâmetro de método no método AddHead .
Como o tipo de retorno da propriedade Data na classe Node aninhada.
Como o tipo de data do membro particular na classe aninhada.
T está disponível para a classe aninhada Node . Quando GenericList<T> é instanciada
com um tipo concreto, por exemplo como um GenericList<int> , cada ocorrência de T
será substituída por int .
C#
// type parameter T in angle brackets
public class GenericList<T>
// The nested class is also generic on T.
private class Node
// T used in non-generic constructor.
public Node(T t)
next = null;
data = t;
private Node? next;
public Node? Next
get { return next; }
set { next = value; }
// T as private member data type.
private T data;
// T as return type of property.
public T Data
get { return data; }
set { data = value; }
private Node? head;
// constructor
public GenericList()
head = null;
// T as method parameter type:
public void AddHead(T t)
Node n = new Node(t);
n.Next = head;
head = n;
public IEnumerator<T> GetEnumerator()
Node? current = head;
while (current != null)
yield return current.Data;
current = current.Next;
O exemplo de código a seguir mostra como o código cliente usa a classe
GenericList<T> genérica para criar uma lista de inteiros. Ao simplesmente alterar o
argumento de tipo, o código a seguir poderia facilmente ser modificado para criar listas
de cadeias de caracteres ou qualquer outro tipo personalizado:
C#
class TestGenericList
static void Main()
// int is the type argument
GenericList<int> list = new GenericList<int>();
for (int x = 0; x < 10; x++)
list.AddHead(x);
foreach (int i in list)
System.Console.Write(i + " ");
System.Console.WriteLine("\nDone");
Visão geral de genéricos
Use tipos genéricos para maximizar a reutilização de código, o desempenho e a
segurança de tipo.
O uso mais comum de genéricos é para criar classes de coleção.
A biblioteca de classes do .NET contém várias classes de coleção de genéricos no
namespace System.Collections.Generic. As coleções genéricas devem ser usadas
sempre que possível, em vez de classes como ArrayList no namespace
System.Collections.
Você pode criar suas próprias interfaces genéricas, classes, métodos, eventos e
delegados.
Classes genéricas podem ser restringidas para habilitar o acesso aos métodos em
tipos de dados específicos.
Informações sobre os tipos que são usados em um tipo de dados genérico podem
ser obtidas no tempo de execução por meio de reflexão.
Especificação da linguagem C#
Para obter mais informações, consulte a Especificação da linguagem C#.
Confira também
System.Collections.Generic
Generics in .NET (Genéricos no .NET)
Tipos anônimos
Artigo • 10/05/2023
Os tipos anônimos fornecem um modo conveniente de encapsular um conjunto de
propriedades somente leitura em um único objeto sem a necessidade de primeiro
definir explicitamente um tipo. O nome do tipo é gerado pelo compilador e não está
disponível no nível do código-fonte. O tipo de cada propriedade é inferido pelo
compilador.
Crie tipos anônimos usando o operador new com um inicializador de objeto. Para obter
mais informações sobre inicializadores de objeto, consulte Inicializadores de Objeto e
Coleção.
O exemplo a seguir mostra um tipo anônimo que é inicializado com duas propriedades
chamadas Amount e Message .
C#
var v = new { Amount = 108, Message = "Hello" };
// Rest the mouse pointer over v.Amount and v.Message in the following
// statement to verify that their inferred types are int and string.
Console.WriteLine(v.Amount + v.Message);
Os tipos anônimos são normalmente usados na cláusula select de uma expressão de
consulta para retornar um subconjunto das propriedades de cada objeto na sequência
de origem. Para obter mais informações sobre consultas, consulte LINQ no C#.
Os tipos anônimos contêm uma ou mais propriedades públicas somente leitura.
Nenhum outro tipo de membros da classe, como métodos ou eventos, é válido. A
expressão que é usada para inicializar uma propriedade não pode ser null , uma função
anônima ou um tipo de ponteiro.
O cenário mais comum é inicializar um tipo anônimo com propriedades de outro tipo.
No exemplo a seguir, suponha que existe uma classe com o nome Product . A classe
Product inclui as propriedades Color e Price , além de outras propriedades que não lhe
interessam. A variável products é uma coleção de objetos do Product . A declaração do
tipo anônimo começa com a palavra-chave new . A declaração inicializa um novo tipo
que usa apenas duas propriedades de Product . O uso de tipos anônimos faz com que
uma menor quantidade de dados seja retornada na consulta.
Quando você não especifica os nomes de membros no tipo anônimo, o compilador dá
aos membros de tipo anônimo o mesmo nome da propriedade que está sendo usada
para inicializá-los. Forneça um nome para a propriedade que está sendo inicializada
com uma expressão, como mostrado no exemplo anterior. No exemplo a seguir, os
nomes das propriedades do tipo anônimo são Color e Price .
C#
var productQuery =
from prod in products
select new { prod.Color, prod.Price };
foreach (var v in productQuery)
Console.WriteLine("Color={0}, Price={1}", v.Color, v.Price);
Dica
Você pode usar a regra de estilo .NET IDE0037 para impor se os nomes de
membros inferidos ou explícitos são preferenciais.
Também é possível definir um campo por objeto de outro tipo: classe, struct ou até
mesmo outro tipo anônimo. Isso é feito usando a variável que contém esse objeto
exatamente como no exemplo a seguir, onde dois tipos anônimos são criados usando
tipos definidos pelo usuário já instanciados. Em ambos os casos o campo product nos
tipos anônimos shipment e shipmentWithBonus serão do tipo Product contendo seus
valores padrão de cada campo. E o campo bonus será do tipo anônimo criado pelo
compilador.
C#
var product = new Product();
var bonus = new { note = "You won!" };
var shipment = new { address = "Nowhere St.", product };
var shipmentWithBonus = new { address = "Somewhere St.", product, bonus };
Normalmente, ao usar um tipo anônimo para inicializar uma variável, a variável é
declarada como uma variável local de tipo implícito usando var. O nome do tipo não
pode ser especificado na declaração da variável, porque apenas o compilador tem
acesso ao nome subjacente do tipo anônimo. Para obter mais informações sobre var ,
consulte Variáveis de local digitadas implicitamente.
Você pode criar uma matriz de elementos de tipo anônimo combinando uma variável
local de tipo implícito e uma matriz de tipo implícito, como mostrado no exemplo a
seguir.
C#
var anonArray = new[] { new { name = "apple", diam = 4 }, new { name =
"grape", diam = 1 }};
Os tipos anônimos são tipos class que derivam diretamente de object e que não podem
ser convertidos para qualquer tipo, exceto para object. O compilador fornece um nome
para cada tipo anônimo, embora o seu aplicativo não possa acessá-lo. Do ponto de vista
do Common Language Runtime, um tipo anônimo não é diferente de qualquer outro
tipo de referência.
Se dois ou mais inicializadores de objeto anônimos em um assembly especificarem uma
sequência de propriedades que estão na mesma ordem e que têm os mesmos nomes e
tipos, o compilador tratará os objetos como instâncias do mesmo tipo. Eles
compartilham o mesmo tipo de informação gerado pelo compilador.
Tipos anônimos dão suporte a mutações não destrutivas na forma de expressões with.
Isso permite que você crie uma nova instância de um tipo anônimo em que uma ou
mais propriedades têm novos valores:
C#
var apple = new { Item = "apples", Price = 1.35 };
var onSale = apple with { Price = 0.79 };
Console.WriteLine(apple);
Console.WriteLine(onSale);
Você não pode declarar que um campo, uma propriedade, um evento ou um tipo de
retorno de um método tem um tipo anônimo. Da mesma forma, não pode declarar que
um parâmetro formal de um método, propriedade, construtor ou indexador tem um
tipo anônimo. Para passar um tipo anônimo ou uma coleção que contenha tipos
anônimos como um argumento para um método, você pode declarar o parâmetro
como tipo object . No entanto, usar object para tipos anônimos anula o propósito da
tipagem forte. Se você precisa armazenar os resultados da consulta ou passá-los fora do
limite do método, considere o uso de uma estrutura ou classe com denominação
comum em vez de um tipo anônimo.
Como os métodos Equals e GetHashCode em tipos anônimos são definidos em termos
dos métodos das propriedades Equals e GetHashCode , duas instâncias do mesmo tipo
anônimo são iguais somente se todas as suas propriedades forem iguais.
Tipos anônimos substituem o método ToString, concatenando o nome e a saída
ToString de cada propriedade cercada por chaves.
var v = new { Title = "Hello", Age = 24 };
Console.WriteLine(v.ToString()); // "{ Title = Hello, Age = 24 }"
Visão geral de classes, structs e registros
em C#
Artigo • 15/02/2023
Em C#, a definição de um tipo — uma classe, um struct ou um registro — é como um
blueprint que especifica o que o tipo pode fazer. Um objeto é basicamente um bloco de
memória que foi alocado e configurado de acordo com o esquema. Este artigo fornece
uma visão geral desses blueprints e os respectivos recursos. O próximo artigo desta
série apresenta objetos.
Encapsulamento
Encapsulamento é chamado, ocasionalmente, de primeiro pilar ou princípio da
programação orientada a objeto. Uma classe ou um struct pode especificar qual
membro será codificado fora da classe ou do struct. Os métodos e as variáveis que não
serão usados fora da classe ou assembly poderão ser ocultados para limitar erros de
codificação potenciais ou explorações maliciosas. Para obter mais informações, confira o
tutorial de Programação orientada a objeto.
Membros
Os membros de um tipo incluem todos os métodos, campos, constantes, propriedades e
eventos. No C#, não existem variáveis globais ou métodos como em algumas das outras
linguagens. Mesmo o ponto de entrada de um programa, o método Main , deve ser
declarado dentro de uma classe ou de um struct (implicitamente quando você usa
instruções de nível superior).
A lista a seguir inclui todos os vários tipos de membros que podem ser declarados em
uma classe, um struct ou um registro.
Campos
Constantes
Propriedades
Métodos
Construtores
Eventos
Finalizadores
Indexadores
Operadores
Tipos aninhados
Para obter mais informações, confira Membros.
Acessibilidade
Alguns métodos e propriedades devem ser chamados ou acessado pelo código fora da
classe ou do struct, também conhecido como código de cliente. Outros métodos e
propriedades podem ser usados apenas na classe ou struct em si. É importante limitar o
acessibilidade do código para que somente o código do cliente desejado possa fazer
contato. Você especifica o grau de acessibilidade dos tipos e os respectivos membros ao
código do cliente usando os seguintes modificadores de acesso:
público
protected
interno
internos protegidos
private
protegido de forma particular.
A acessibilidade padrão é private .
Herança
Classes (mas não structs) dão suporte ao conceito de herança. Uma classe que deriva de
outra classe, chamada classe base, contém automaticamente todos os membros
públicos, protegidos e internos da classe base, exceto seus construtores e finalizadores.
As classes podem ser declaradas como abstratas, o que significa que um ou mais dos
seus métodos não têm nenhuma implementação. Embora as classes abstratas não
possam ser instanciadas diretamente, elas servem como classes base para outras classes
que fornecem a implementação ausente. As classes também podem ser declaradas
como lacradas para impedir que outras classes herdem delas.
Para obter mais informações, consulte Herança e Polimorfismo.
Interfaces
Classes, structs e registros podem implementar várias interfaces. Implementar a partir de
uma interface significa que o tipo implementa todos os métodos definidos na interface.
Para obter mais informações, consulte Interfaces.
Tipos genéricos
Classes, structs e registros podem ser definidos com um ou mais parâmetros de tipo. O
código do cliente fornece o tipo quando ele cria uma instância do tipo. Por exemplo a
classe List<T> no namespace System.Collections.Generic é definida com um parâmetro
de tipo. O código do cliente cria uma instância de um List<string> ou List<int> para
especificar o tipo que a lista conterá. Para obter mais informações, consulte Genéricos.
Tipos estáticos
Classes (mas não structs ou registros) podem ser declaradas como static . Uma classe
estática pode conter apenas membros estáticos e não pode ser instanciada com a
palavra-chave new . Uma cópia da classe é carregada na memória quando o programa é
carregado e seus membros são acessados pelo nome da classe. Classes, structs e
registros podem conter membros estáticos. Para obter mais informações, consulte
Classes estáticas e membros de classes estáticas.
Tipos aninhados
Uma classe, struct ou registro pode ser aninhado dentro de outra classe, struct ou
registro. Para obter mais informações, consulte Tipos aninhados.
Tipos parciais
Você pode definir parte de uma classe, struct ou método em um arquivo de código e
outra parte em um arquivo de código separado. Para obter mais informações, consulte
Classes parciais e métodos.
Inicializadores de objeto
Você pode criar uma instância e inicializar objetos de classe ou struct e coleções de
objetos, atribuindo valores às respectivas propriedades. Para mais informações, consulte
Como inicializar objetos usando um inicializador de objeto.
Tipos anônimos
Em situações em que não é conveniente ou necessário criar uma classe nomeada, você
usa tipos anônimos. Tipos anônimos são definidos pelos membros de dados nomeados.
Para obter mais informações, consulte Tipos anônimos.
Métodos de Extensão
Você pode "estender" uma classe sem criar uma classe derivada criando um tipo
separado. Esse tipo contém métodos que podem ser chamados como se pertencessem
ao tipo original. Para obter mais informações, consulte Métodos de extensão.
Variáveis Locais Tipadas Implicitamente
Dentro de um método de classe ou struct, você pode usar digitação implícita para
instruir o compilador para determinar o tipo de variável no tempo de compilação. Para
obter mais informações, confira var (referência C#).
Registros
O C# 9 introduz o tipo record , um tipo de referência que você pode criar em vez de
uma classe ou um struct. Os registros são classes com comportamento interno para
encapsular dados em tipos imutáveis. O C# 10 introduz o tipo de valor record struct .
Um registro ( record class ou record struct ) fornece os seguintes recursos:
Sintaxe concisa para criar um tipo de referência com propriedades imutáveis.
Igualdade de valor.
Duas variáveis de um tipo de registro são iguais se tiverem o
mesmo tipo e se, para cada campo, os valores em ambos os registros forem iguais.
As classes usam igualdade de referência: duas variáveis de um tipo de classe são
iguais se elas se referirem ao mesmo objeto.
Sintaxe concisa para mutação não destrutiva.
Uma expressão with permite criar
uma nova instância de registro que seja uma cópia de uma instância existente, mas
com valores de propriedade especificados alterados.
Formatação interna para exibição.
O método ToString imprime o nome do tipo de
registro e os nomes e valores das propriedades públicas.
Suporte para hierarquias de herança em classes de registro.
Classes de registro
dão suporte à herança. Os structs de registro não dão suporte à herança.
Para obter mais informações, confira Registros.
Especificação da Linguagem C#
Para obter mais informações, consulte a Especificação da linguagem C#. A especificação
da linguagem é a fonte definitiva para a sintaxe e o uso de C#.
Objetos – criar instâncias de tipos
Artigo • 10/05/2023
Uma definição de classe ou struct é como um esquema que especifica o que o tipo
pode fazer. Um objeto é basicamente um bloco de memória que foi alocado e
configurado de acordo com o esquema. Um programa pode criar vários objetos da
mesma classe. Objetos também são chamados de instâncias e podem ser armazenados
em uma variável nomeada ou em uma matriz ou coleção. O código de cliente é o
código que usa essas variáveis para chamar os métodos e acessar as propriedades
públicas do objeto. Em uma linguagem orientada a objetos, como o C#, um programa
típico consiste em vários objetos que interagem dinamicamente.
7 Observação
Tipos estáticos se comportam de modo diferente do que está descrito aqui. Para
obter mais informações, consulte Classes estáticas e membros de classes estáticas.
Instâncias Struct vs. Instâncias de classe
Como as classes são tipos de referência, uma variável de um objeto de classe contém
uma referência ao endereço do objeto no heap gerenciado. Se uma segunda variável do
mesmo tipo for atribuída à primeira variável, as duas variáveis farão referência ao objeto
nesse endereço. Esse ponto é abordado com mais detalhes posteriormente neste artigo.
Instâncias de classes são criadas usando o new operador. No exemplo a seguir, Person é
o tipo e person1 e person2 são instâncias ou objetos desse tipo.
C#
public class Person
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
Name = name;
Age = age;
// Other properties, methods, events...
class Program
static void Main()
Person person1 = new Person("Leopold", 6);
Console.WriteLine("person1 Name = {0} Age = {1}", person1.Name,
person1.Age);
// Declare new person, assign person1 to it.
Person person2 = person1;
// Change the name of person2, and person1 also changes.
person2.Name = "Molly";
person2.Age = 16;
Console.WriteLine("person2 Name = {0} Age = {1}", person2.Name,
person2.Age);
Console.WriteLine("person1 Name = {0} Age = {1}", person1.Name,
person1.Age);
/*
Output:
person1 Name = Leopold Age = 6
person2 Name = Molly Age = 16
person1 Name = Molly Age = 16
*/
Como structs são tipos de valor, uma variável de um objeto de struct mantém uma
cópia do objeto inteiro. Instâncias de structs também podem ser criadas usando o
operador new , mas isso não é obrigatório, conforme mostrado no exemplo a seguir:
C#
namespace Example;
public struct Person
public string Name;
public int Age;
public Person(string name, int age)
Name = name;
Age = age;
public class Application
static void Main()
// Create struct instance and initialize by using "new".
// Memory is allocated on thread stack.
Person p1 = new Person("Alex", 9);
Console.WriteLine("p1 Name = {0} Age = {1}", p1.Name, p1.Age);
// Create new struct object. Note that struct can be initialized
// without using "new".
Person p2 = p1;
// Assign values to p2 members.
p2.Name = "Spencer";
p2.Age = 7;
Console.WriteLine("p2 Name = {0} Age = {1}", p2.Name, p2.Age);
// p1 values remain unchanged because p2 is copy.
Console.WriteLine("p1 Name = {0} Age = {1}", p1.Name, p1.Age);
/*
Output:
p1 Name = Alex Age = 9
p2 Name = Spencer Age = 7
p1 Name = Alex Age = 9
*/
A memória de p1 e p2 é alocada na pilha de thread. Essa memória é recuperada em
conjunto com o tipo ou método em que ela é declarada. Esse é um dos motivos pelos
quais os structs são copiados na atribuição. Por outro lado, a memória alocada a uma
instância de classe é recuperada automaticamente (o lixo é coletado) pelo Common
Language Runtime quando todas as referências ao objeto tiveram saído do escopo. Não
é possível destruir de forma determinista um objeto de classe, como é possível no C++.
Para obter mais informações sobre a coleta de lixo no , consulte Coleta de lixo.
7 Observação
A alocação e a desalocação de memória no heap gerenciado é altamente otimizada
no Common Language Runtime. Na maioria dos casos, não há uma diferença
significativa quanto ao custo do desempenho de alocar uma instância da classe no
heap em vez de alocar uma instância de struct na pilha.
Identidade do Objeto vs. Igualdade de Valor
Quando compara dois objetos quanto à igualdade, primeiro você precisa distinguir se
quer saber se as duas variáveis representam o mesmo objeto na memória ou se os
valores de um ou mais de seus campos são equivalentes. Se quiser comparar valores,
você precisa considerar se os objetos são instâncias de tipos de valor (structs) ou tipos
de referência (classes, delegados, matrizes).
Para determinar se duas instâncias de classe se referem ao mesmo local na
memória (o que significa que elas têm a mesma identidade), use o método
Object.Equals estático. (System.Object é a classe base implícita para todos os tipos
de valor e tipos de referência, incluindo classes e structs definidos pelo usuário.)
Para determinar se os campos de instância em duas instâncias de struct têm os
mesmos valores, use o método ValueType.Equals. Como todos os structs herdam
implicitamente de System.ValueType, você chama o método diretamente em seu
objeto, conforme mostrado no exemplo a seguir:
C#
// Person is defined in the previous example.
//public struct Person
//{
// public string Name;
// public int Age;
// public Person(string name, int age)
// {
// Name = name;
// Age = age;
// }
//}
Person p1 = new Person("Wallace", 75);
Person p2 = new Person("", 42);
p2.Name = "Wallace";
p2.Age = 75;
if (p2.Equals(p1))
Console.WriteLine("p2 and p1 have the same values.");
// Output: p2 and p1 have the same values.
A implementaçãoSystem.ValueType de Equals usa conversão boxing e reflexão em
alguns casos. Para obter informações sobre como fornecer um algoritmo de
igualdade eficiente específico ao seu tipo, consulte Como definir a igualdade de
valor para um tipo. Os registros são tipos de referência que usam semântica de
valor para igualdade.
Para determinar se os valores dos campos em duas instâncias de classe são iguais,
você pode usar o método Equals ou o Operador ==. No entanto, use-os apenas se
a classe os tiver substituído ou sobrecarregado para fornecer uma definição
personalizada do que "igualdade" significa para objetos desse tipo. A classe
também pode implementar a interface IEquatable<T> ou a interface
IEqualityComparer<T>. As duas interfaces fornecem métodos que podem ser
usados para testar a igualdade de valores. Ao criar suas próprias classes que
substituem Equals , certifique-se de seguir as diretrizes informadas em Como
definir a igualdade de valor para um tipo e Object.Equals(Object).
Seções relacionadas
Para mais informações:
Classes
Construtores
Finalizadores
Eventos
object
Herança
class
Tipos de estrutura
Operador new
Common Type System
Herança – derivar tipos para criar um
comportamento mais especializado
Artigo • 07/04/2023
A herança, assim como o encapsulamento e o polimorfismo, é uma das três principais
características da programação orientada ao objeto. A herança permite que você crie
novas classes que reutilizam, estendem e modificam o comportamento definido em
outras classes. A classe cujos membros são herdados é chamada classe base e a classe
que herda esses membros é chamada classe derivada. Uma classe derivada pode ter
apenas uma classe base direta. No entanto, a herança é transitiva. Se ClassC for
derivado de ClassB e ClassB for derivado de ClassA , ClassC herdará os membros
declarados em ClassB e ClassA .
7 Observação
Structs não dão suporte a herança, mas podem implementar interfaces.
Conceitualmente, uma classe derivada é uma especialização da classe base. Por
exemplo, se tiver uma classe base Animal , você pode ter uma classe derivada chamada
Mammal e outra classe derivada chamada Reptile . Um Mammal é um Animal e um
Reptile é um Animal , mas cada classe derivada representa especializações diferentes
da classe base.
As declarações de interface podem definir uma implementação padrão para seus
membros. Essas implementações são herdadas por interfaces derivadas e por classes
que implementam essas interfaces. Para obter mais informações sobre métodos de
interface padrão, confira o artigo sobre interfaces.
Quando você define uma classe para derivar de outra classe, a classe derivada obtém
implicitamente todos os membros da classe base, exceto por seus construtores e
finalizadores. A classe derivada reutiliza o código na classe base sem precisar
implementá-lo novamente. Na classe derivada, você pode adicionar mais membros. A
classe derivada estende a funcionalidade da classe base.
A ilustração a seguir mostra uma classe WorkItem que representa um item de trabalho
em um processo comercial. Como todas as classes, ela deriva de System.Object e herda
todos os seus métodos. WorkItem adiciona seis membros próprios. Esses membros
incluem um construtor, porque os construtores não são herdados. A classe
ChangeRequest herda de WorkItem e representa um tipo específico de item de trabalho.
ChangeRequest adiciona mais dois membros aos membros que herda de WorkItem e de
Object. Ele deve adicionar seu próprio construtor e também adiciona originalItemID . A
propriedade originalItemID permite que a instância ChangeRequest seja associada ao
WorkItem original a que a solicitação de alteração se aplica.
O exemplo a seguir mostra como as relações entre as classes demonstradas na
ilustração anterior são expressos em C#. O exemplo também mostra como WorkItem
substitui o método virtual Object.ToString e como a classe ChangeRequest herda a
implementação de WorkItem do método. O primeiro bloco define as classes:
C#
// WorkItem implicitly inherits from the Object class.
public class WorkItem
// Static field currentID stores the job ID of the last WorkItem that
// has been created.
private static int currentID;
//Properties.
protected int ID { get; set; }
protected string Title { get; set; }
protected string Description { get; set; }
protected TimeSpan jobLength { get; set; }
// Default constructor. If a derived class does not invoke a base-
// class constructor explicitly, the default constructor is called
// implicitly.
public WorkItem()
ID = 0;
Title = "Default title";
Description = "Default description.";
jobLength = new TimeSpan();
// Instance constructor that has three parameters.
public WorkItem(string title, string desc, TimeSpan joblen)
this.ID = GetNextID();
this.Title = title;
this.Description = desc;
this.jobLength = joblen;
// Static constructor to initialize the static member, currentID. This
// constructor is called one time, automatically, before any instance
// of WorkItem or ChangeRequest is created, or currentID is referenced.
static WorkItem() => currentID = 0;
// currentID is a static field. It is incremented each time a new
// instance of WorkItem is created.
protected int GetNextID() => ++currentID;
// Method Update enables you to update the title and job length of an
// existing WorkItem object.
public void Update(string title, TimeSpan joblen)
this.Title = title;
this.jobLength = joblen;
// Virtual method override of the ToString method that is inherited
// from System.Object.
public override string ToString() =>
$"{this.ID} - {this.Title}";
// ChangeRequest derives from WorkItem and adds a property (originalItemID)
// and two constructors.
public class ChangeRequest : WorkItem
protected int originalItemID { get; set; }
// Constructors. Because neither constructor calls a base-class
// constructor explicitly, the default constructor in the base class
// is called implicitly. The base class must contain a default
// constructor.
// Default constructor for the derived class.
public ChangeRequest() { }
// Instance constructor that has four parameters.
public ChangeRequest(string title, string desc, TimeSpan jobLen,
int originalID)
// The following properties and the GetNexID method are inherited
// from WorkItem.
this.ID = GetNextID();
this.Title = title;
this.Description = desc;
this.jobLength = jobLen;
// Property originalItemID is a member of ChangeRequest, but not
// of WorkItem.
this.originalItemID = originalID;
Este próximo bloco mostra como usar as classes base e derivadas:
C#
// Create an instance of WorkItem by using the constructor in the
// base class that takes three arguments.
WorkItem item = new WorkItem("Fix Bugs",
"Fix all bugs in my code branch",
new TimeSpan(3, 4, 0, 0));
// Create an instance of ChangeRequest by using the constructor in
// the derived class that takes four arguments.
ChangeRequest change = new ChangeRequest("Change Base Class Design",
"Add members to the class",
new TimeSpan(4, 0, 0),
1);
// Use the ToString method defined in WorkItem.
Console.WriteLine(item.ToString());
// Use the inherited Update method to change the title of the
// ChangeRequest object.
change.Update("Change the Design of the Base Class",
new TimeSpan(4, 0, 0));
// ChangeRequest inherits WorkItem's override of ToString.
Console.WriteLine(change.ToString());
/* Output:
1 - Fix Bugs
2 - Change the Design of the Base Class
*/
Métodos abstratos e virtuais
Quando uma classe base declara um método como virtual, uma classe derivada poderá
override o método com a própria implementação. Se uma classe base declarar um
membro como abstract, esse método deve ser substituído em qualquer classe não
abstrata que herdar diretamente da classe. Se uma classe derivada for abstrato, ele
herdará membros abstratos sem implementá-los. Membros abstratos e virtuais são a
base do polimorfismo, que é a segunda característica principal da programação
orientada a objetos. Para obter mais informações, consulte Polimorfismo.
Classes base abstratas
Você poderá declarar uma classe como abstrata se quiser impedir a instanciação direta
usando o operador new. Uma classe abstrata poderá ser usada somente se uma nova
classe for derivada dela. Uma classe abstrata pode conter uma ou mais assinaturas de
método que também são declaradas como abstratas. Essas assinaturas especificam os
parâmetros e o valor retornado, mas não têm nenhuma implementação (corpo do
método). Uma classe abstrata não precisa conter membros abstratos. No entanto, se
uma classe contiver um membro abstrato, a própria classe deverá ser declarada como
abstrata. Classes derivadas que não são abstratas devem fornecer a implementação para
qualquer método abstrato de uma classe base abstrata.
Interfaces
Uma interface é um tipo de referência que define um conjunto de membros. Todas as
classes e structs que implementam essa interface devem implementar esse conjunto de
membros. Uma interface pode definir uma implementação padrão para qualquer um
desses membros. Uma classe pode implementar várias interfaces, mesmo que ela possa
derivar de apenas uma classe base direta.
Interfaces são usadas para definir recursos específicos para classes que não têm
necessariamente uma relação do tipo “é um”. Por exemplo, a interface
System.IEquatable<T> pode ser implementada por qualquer classe ou struct para
determinar se dois objetos do tipo são equivalentes (como quer que o tipo defina a
equivalência). IEquatable<T> não implica o mesmo tipo de relação "é um" existente
entre uma classe base e uma classe derivada (por exemplo, um Mammal é um Animal ).
Para obter mais informações, consulte Interfaces.
Impedindo derivações adicionais
Uma classe pode impedir que outras classes herdem dela ou de qualquer um de seus
membros ao declarar a si mesma ou o membro como sealed.
Ocultação de membros da classe base pela
classe derivada
Uma classe derivada pode ocultar membros da classe base declarando membros com
mesmo nome e assinatura. O modificador new pode ser usado para indicar
explicitamente que o membro não pretende ser uma substituição do membro base. O
uso de new não é necessário, mas um aviso do compilador será gerado se new não for
usado. Para obter mais informações, consulte Controle de versão com as palavras-chave
override e new e Quando usar as palavras-chave override e new.
Polimorfismo
Artigo • 07/04/2023
O polimorfismo costuma ser chamado de o terceiro pilar da programação orientada a
objetos, depois do encapsulamento e a herança. O polimorfismo é uma palavra grega
que significa "de muitas formas" e tem dois aspectos distintos:
Em tempo de execução, os objetos de uma classe derivada podem ser tratados
como objetos de uma classe base, em locais como parâmetros de método,
coleções e matrizes. Quando esse polimorfismo ocorre, o tipo declarado do objeto
não é mais idêntico ao seu tipo de tempo de runtime.
As classes base podem definir e implementar métodosvirtuais e as classes
derivadas podem substituí-los, o que significa que elas fornecem sua própria
definição e implementação. Em tempo de execução, quando o código do cliente
chama o método, o CLR procura o tipo de tempo de execução do objeto e invoca
a substituição do método virtual. No código-fonte, você pode chamar um método
em uma classe base e fazer com que a versão de uma classe derivada do método
seja executada.
Os métodos virtuais permitem que você trabalhe com grupos de objetos relacionados
de maneira uniforme. Por exemplo, suponha que você tem um aplicativo de desenho
que permite que um usuário crie vários tipos de formas sobre uma superfície de
desenho. Você não sabe no tempo de compilação quais tipos específicos de formas o
usuário criará. No entanto, o aplicativo precisa manter controle de todos os diferentes
tipos de formas que são criados e atualizá-los em resposta às ações do mouse do
usuário. Você pode usar o polimorfismo para resolver esse problema em duas etapas
básicas:
1. Crie uma hierarquia de classes em que cada classe de forma específica derive de
uma classe base comum.
2. Use um método virtual para invocar o método adequado em qualquer classe
derivada por meio de uma única chamada para o método da classe base.
Primeiro, crie uma classe base chamada Shape e as classes derivadas como Rectangle ,
Circle e Triangle . Atribua à classe Shape um método virtual chamado Draw e
substitua-o em cada classe derivada para desenhar a forma especial que a classe
representa. Crie um objeto List<Shape> e adicione um Circle , Triangle e Rectangle a
ele.
C#
public class Shape
// A few example members
public int X { get; private set; }
public int Y { get; private set; }
public int Height { get; set; }
public int Width { get; set; }
// Virtual method
public virtual void Draw()
Console.WriteLine("Performing base class drawing tasks");
public class Circle : Shape
public override void Draw()
// Code to draw a circle...
Console.WriteLine("Drawing a circle");
base.Draw();
public class Rectangle : Shape
public override void Draw()
// Code to draw a rectangle...
Console.WriteLine("Drawing a rectangle");
base.Draw();
public class Triangle : Shape
public override void Draw()
// Code to draw a triangle...
Console.WriteLine("Drawing a triangle");
base.Draw();
Para atualizar a superfície de desenho, use um loop foreach para iterar na lista e chamar
o método Draw em cada objeto Shape na lista. Mesmo que cada objeto na lista tenha
um tipo declarado Shape , é o tipo de runtime (a versão de substituição do método em
cada classe derivada) que será invocado.
C#
// Polymorphism at work #1: a Rectangle, Triangle and Circle
// can all be used wherever a Shape is expected. No cast is
// required because an implicit conversion exists from a derived
// class to its base class.
var shapes = new List<Shape>
new Rectangle(),
new Triangle(),
new Circle()
};
// Polymorphism at work #2: the virtual method Draw is
// invoked on each of the derived classes, not the base class.
foreach (var shape in shapes)
shape.Draw();
/* Output:
Drawing a rectangle
Performing base class drawing tasks
Drawing a triangle
Performing base class drawing tasks
Drawing a circle
Performing base class drawing tasks
*/
Em C#, cada tipo é polimórfico porque todos os tipos, incluindo tipos definidos pelo
usuário, herdam de Object.
Visão geral sobre o polimorfismo
Membros virtuais
Quando uma classe derivada herda de uma classe base, ela inclui todos os membros da
classe base. Todo o comportamento declarado na classe base faz parte da classe
derivada. Isso permite que objetos da classe derivada sejam tratados como objetos da
classe base. Os modificadores de acesso ( public , protected private e assim por diante)
determinam se esses membros estão acessíveis a partir da implementação de classe
derivada. Os métodos virtuais dão ao designer opções diferentes para o
comportamento da classe derivada:
A classe derivada pode substituir membros virtuais na classe base, definindo o
novo comportamento.
A classe derivada pode herdar o método de classe base mais próximo sem
substituí-lo, preservando o comportamento existente, mas permitindo que outras
classes derivadas substituam o método.
A classe derivada pode definir uma nova implementação não virtual desses
membros que ocultam as implementações de classe base.
Uma classe derivada poderá substituir um membro de classe base somente se o
membro da classe base tiver sido declarado como virtual ou abstrato. O membro
derivado deve usar a palavra-chave override para indicar explicitamente que o método
destina-se a participar da invocação virtual. O código a seguir mostra um exemplo:
C#
public class BaseClass
public virtual void DoWork() { }
public virtual int WorkProperty
get { return 0; }
public class DerivedClass : BaseClass
public override void DoWork() { }
public override int WorkProperty
get { return 0; }
Os campos não podem ser virtuais, apenas os métodos, as propriedades, os eventos e
os indexadores podem ser virtuais. Quando uma classe derivada substitui um membro
virtual, esse membro é chamado, mesmo quando uma instância dessa classe está sendo
acessada como uma instância da classe base. O código a seguir mostra um exemplo:
C#
DerivedClass B = new DerivedClass();
B.DoWork(); // Calls the new method.
BaseClass A = B;
A.DoWork(); // Also calls the new method.
Os métodos e propriedades virtuais permitem que classes derivadas estendam uma
classe base sem a necessidade de usar a implementação da classe base de um método.
Para obter mais informações, consulte Controle de versão com as palavras-chave
override e new. Uma interface fornece uma outra maneira de definir um método ou
conjunto de métodos cuja implementação é deixada para classes derivadas.
Ocultar membros da classe base com novos membros
Se quiser que sua classe derivada tenha um membro com o mesmo nome que um
membro em uma classe base, você poderá usar a palavra-chave new para ocultar o
membro da classe base. A palavra-chave new é colocada antes do tipo de retorno de
um membro de classe que está sendo substituído. O código a seguir mostra um
exemplo:
C#
public class BaseClass
public void DoWork() { WorkField++; }
public int WorkField;
public int WorkProperty
get { return 0; }
public class DerivedClass : BaseClass
public new void DoWork() { WorkField++; }
public new int WorkField;
public new int WorkProperty
get { return 0; }
Os membros da classe base oculta podem ser acessados do código do cliente, por meio
da seleção da instância da classe derivada em uma instância da classe base. Por
exemplo:
C#
DerivedClass B = new DerivedClass();
B.DoWork(); // Calls the new method.
BaseClass A = (BaseClass)B;
A.DoWork(); // Calls the old method.
Impedir que classes derivadas substituam membros
virtuais
Os membros virtuais permanecem virtuais, independentemente de quantas classes
foram declaradas entre o membro virtual e a classe que o declarou originalmente. Se a
classe A declara um membro virtual, a classe B deriva de A e a classe C deriva de B , a
classe C herda o membro virtual e tem a opção de substituí-lo, independentemente de
a classe B ter declarado uma substituição para esse membro. O código a seguir mostra
um exemplo:
C#
public class A
public virtual void DoWork() { }
}
public class B : A
public override void DoWork() { }
Uma classe derivada pode interromper a herança virtual, declarando uma substituição
como sealed. Isso exige a colocação da palavra-chave sealed antes da palavra-chave
override na declaração de membro de classe. O código a seguir mostra um exemplo:
C#
public class C : B
public sealed override void DoWork() { }
No exemplo anterior, o método DoWork não é mais virtual para nenhuma classe derivada
de C . Ele ainda é virtual para as instâncias de C , mesmo se elas foram convertidas para
o tipo B ou tipo A . Os métodos lacrados podem ser substituídos por classes derivadas
usando a palavra-chave new , como mostra o exemplo a seguir:
C#
public class D : C
public new void DoWork() { }
Neste caso, se DoWork é chamado em D usando uma variável do tipo D , o novo DoWork
é chamado. Se uma variável do tipo C , B ou A é usada para acessar uma instância de D ,
uma chamada de DoWork seguirá as regras de herança virtual, encaminhando as
chamadas para a implementação de DoWork na classe C .
Acessar membros virtuais da classe base de classes
derivadas
A classe derivada que substituiu um método ou propriedade ainda pode acessar o
método ou propriedade na classe base usando a palavra-chave base . O código a seguir
mostra um exemplo:
C#
public class Base
public virtual void DoWork() {/*...*/ }
public class Derived : Base
public override void DoWork()
//Perform Derived's work here
//...
// Call DoWork on base class
base.DoWork();
Para obter mais informações, consulte base.
7 Observação
Recomendamos que os membros virtuais usem base para chamar a
implementação da classe base do membro em sua própria implementação. Deixar
o comportamento da classe base ocorrer permite que a classe derivada se
concentre na implementação de comportamento específico para a classe derivada.
Se a implementação da classe base não é chamado, cabe à classe derivada tornar
seu comportamento compatível com o comportamento da classe base.
Visão geral dos padrões
correspondentes
Artigo • 16/06/2023
A correspondência de padrões é uma técnica em que você testa uma expressão para
determinar se ela tem determinadas características. A correspondência de padrões C#
fornece uma sintaxe mais concisa para testar expressões e tomar medidas quando uma
expressão corresponde. A "expressão is" dá suporte à correspondência de padrões para
testar uma expressão e declarar condicionalmente uma nova variável para o resultado
dessa expressão. A "expressão switch" permite que você execute ações com base no
padrão de primeira correspondência de uma expressão. Essas duas expressões dão
suporte a um vocabulário avançado de padrões.
Este artigo fornece uma visão geral dos cenários em que você pode usar a
correspondência de padrões. Essas técnicas podem melhorar a legibilidade e a correção
do código. Para obter uma discussão completa sobre todos os padrões que você pode
aplicar, consulte o artigo sobre padrões na referência de linguagem.
Verificações nulas
Um dos cenários mais comuns para correspondência de padrões é garantir que os
valores não sejam null . Você pode testar e converter um tipo de valor anulável em seu
tipo subjacente durante o teste de null usando o seguinte exemplo:
C#
int? maybe = 12;
if (maybe is int number)
Console.WriteLine($"The nullable int 'maybe' has the value {number}");
else
Console.WriteLine("The nullable int 'maybe' doesn't hold a value");
O código anterior é um padrão de declaração para testar o tipo da variável e atribuí-lo a
uma nova variável. As regras de idioma tornam essa técnica mais segura do que muitas
outras. A variável number só é acessível e atribuída na parte verdadeira da cláusula if .
Se você tentar acessá-la em outro lugar, na cláusula else ou após o bloco if , o
compilador emitirá um erro. Em segundo lugar, como você não está usando o operador
== , esse padrão funciona quando um tipo sobrecarrega o operador == . Isso o torna
uma maneira ideal de verificar os valores de referência nulos, adicionando o padrão
not :
C#
string? message = "This is not the null string";
if (message is not null)
Console.WriteLine(message);
O exemplo anterior usou um padrão constante para comparar a variável com null . not
é um padrão lógico que corresponde quando o padrão negado não corresponde.
Testes de tipo
Outro uso comum para correspondência de padrões é testar uma variável para ver se
ela corresponde a um determinado tipo. Por exemplo, o código a seguir testa se uma
variável não é nula e implementa a interface System.Collections.Generic.IList<T>. Se isso
acontecer, ele usará a propriedade ICollection<T>.Count nessa lista para localizar o
índice do meio. O padrão de declaração não corresponde a um valor null ,
independentemente do tipo de tempo de compilação da variável. O código abaixo
protege contra null e um tipo que não implementa IList .
C#
public static T MidPoint<T>(IEnumerable<T> sequence)
if (sequence is IList<T> list)
return list[list.Count / 2];
else if (sequence is null)
throw new ArgumentNullException(nameof(sequence), "Sequence can't be
null.");
else
int halfLength = sequence.Count() / 2 - 1;
if (halfLength < 0) halfLength = 0;
return sequence.Skip(halfLength).First();
Os mesmos testes podem ser aplicados em uma expressão switch para testar uma
variável em vários tipos diferentes. Você pode usar essas informações para criar
algoritmos melhores com base no tipo em tempo de execução específico.
Comparar valores discretos
Você também pode testar uma variável para encontrar uma correspondência em valores
específicos. O código a seguir mostra um exemplo em que você testa um valor em
relação a todos os valores possíveis declarados em uma enumeração:
C#
public State PerformOperation(Operation command) =>
command switch
Operation.SystemTest => RunDiagnostics(),
Operation.Start => StartSystem(),
Operation.Stop => StopSystem(),
Operation.Reset => ResetToReady(),
_ => throw new ArgumentException("Invalid enum value for command",
nameof(command)),
};
O exemplo anterior demonstra uma expedição de método com base no valor de uma
enumeração. O caso _ final é um padrão de descarte que corresponde a todos os
valores. Ele identifica quaisquer condições de erro em que o valor não corresponda a
um dos valores enum definidos. Se você omitir esse braço switch, o compilador avisará
que você não lidou com todos os valores de entrada possíveis. Em tempo de execução,
a expressão switch lançará uma exceção se o objeto que está sendo examinado não
corresponder a nenhum braço switch. Você pode usar constantes numéricas em vez de
um conjunto de valores de enumeração. Você também pode usar essa técnica
semelhante para valores de cadeia de caracteres constantes que representam os
comandos:
C#
public State PerformOperation(string command) =>
command switch
"SystemTest" => RunDiagnostics(),
"Start" => StartSystem(),
"Stop" => StopSystem(),
"Reset" => ResetToReady(),
_ => throw new ArgumentException("Invalid string value for command",
nameof(command)),
};
O exemplo anterior mostra o mesmo algoritmo, mas usa valores de cadeia de caracteres
em vez de uma enumeração. Você usaria esse cenário se seu aplicativo respondesse a
comandos de texto em vez de um formato de dados regular. A partir do C# 11, você
também pode usar um Span<char> ou ReadOnlySpan<char> para testar valores de cadeia
de caracteres constantes, conforme mostrado no exemplo a seguir:
C#
public State PerformOperation(ReadOnlySpan<char> command) =>
command switch
"SystemTest" => RunDiagnostics(),
"Start" => StartSystem(),
"Stop" => StopSystem(),
"Reset" => ResetToReady(),
_ => throw new ArgumentException("Invalid string value for command",
nameof(command)),
};
Em todos esses exemplos, o padrão de descarte garante que você identifique todas as
entradas. O compilador ajuda você a garantir que todos os valores de entrada possíveis
sejam identificados.
Padrões relacionais
Os padrões relacionais podem ser usados para testar como um valor se compara às
constantes. Por exemplo, o código a seguir retorna o estado da água com base na
temperatura em Fahrenheit:
C#
string WaterState(int tempInFahrenheit) =>
tempInFahrenheit switch
(> 32) and (< 212) => "liquid",
< 32 => "solid",
> 212 => "gas",
32 => "solid/liquid transition",
212 => "liquid / gas transition",
};
O código anterior também demonstra o and padrão lógico conjuntivo para verificar se
ambos os padrões relacionais correspondem. Você também pode usar um padrão
disjuntivo or para verificar se um dos padrões corresponde. Os dois padrões relacionais
ficam entre parênteses, que podem ser usados em qualquer padrão para maior clareza.
Os dois últimos braços switch identificam os casos para o ponto de fusão e o ponto de
ebulição. Sem esses dois braços, o compilador avisa que sua lógica não cobre todas as
entradas possíveis.
O código anterior também demonstra outro recurso importante que o compilador
fornece para expressões de correspondência de padrões: o compilador avisa se você
não identifica todos os valores de entrada. O compilador também emitirá um aviso se
um braço switch já tiver sido identificado por um braço switch anterior. Isso lhe dá
liberdade para refatorar e reordenar as expressões de alternância. Outra maneira de
gravar a mesma expressão pode ser:
C#
string WaterState2(int tempInFahrenheit) =>
tempInFahrenheit switch
< 32 => "solid",
32 => "solid/liquid transition",
< 212 => "liquid",
212 => "liquid / gas transition",
_ => "gas",
};
A principal lição nesse caso, e em qualquer outra refatoração ou reordenação, é que o
compilador valida que todas as entradas foram abordadas.
Várias entradas
Todos os padrões vistos até agora foram para verificar uma entrada. Você pode gravar
padrões que examinam várias propriedades de um objeto. Considere o registro Order a
seguir:
C#
public record Order(int Items, decimal Cost);
O tipo de registro posicional anterior declara dois membros em posições explícitas.
Primeiro, Items aparece e, em seguida, o Cost da ordem. Para saber mais, confira
Registros.
O código a seguir examina o número de itens e o valor de uma ordem para calcular um
preço com desconto:
C#
public decimal CalculateDiscount(Order order) =>
order switch
{ Items: > 10, Cost: > 1000.00m } => 0.10m,
{ Items: > 5, Cost: > 500.00m } => 0.05m,
{ Cost: > 250.00m } => 0.02m,
null => throw new ArgumentNullException(nameof(order), "Can't
calculate discount on null order"),
var someObject => 0m,
};
Os dois primeiros braços examinam duas propriedades do Order . O terceiro examina
apenas o custo. O próximo verifica null e o final corresponde a qualquer outro valor. Se
o tipo Order definir um método Deconstruct adequado, você poderá omitir os nomes
de propriedade do padrão e usar a desconstrução para examinar as propriedades:
C#
public decimal CalculateDiscount(Order order) =>
order switch
( > 10, > 1000.00m) => 0.10m,
( > 5, > 50.00m) => 0.05m,
{ Cost: > 250.00m } => 0.02m,
null => throw new ArgumentNullException(nameof(order), "Can't
calculate discount on null order"),
var someObject => 0m,
};
O código anterior demonstra o padrão posicional em que as propriedades são
desconstruídas para a expressão.
Padrões de lista
Você pode verificar os elementos em uma lista ou uma matriz usando um padrão de
lista. Um padrão de lista fornece um meio para aplicar um padrão a qualquer elemento
de uma sequência. Além disso, você pode aplicar o padrão de descarte ( _ ) para
corresponder a qualquer elemento ou aplicar um padrão de fatia para corresponder a
zero ou mais elementos.
Os padrões de lista são uma ferramenta valiosa quando os dados não seguem uma
estrutura regular. Você pode usar a correspondência de padrões para testar a forma e os
valores dos dados em vez de transformá-los em um conjunto de objetos.
Considere o seguinte trecho de um arquivo de texto que contém transações bancárias:
Saída
04-01-2020, DEPOSIT, Initial deposit, 2250.00
04-15-2020, DEPOSIT, Refund, 125.65
04-18-2020, DEPOSIT, Paycheck, 825.65
04-22-2020, WITHDRAWAL, Debit, Groceries, 255.73
05-01-2020, WITHDRAWAL, #1102, Rent, apt, 2100.00
05-02-2020, INTEREST, 0.65
05-07-2020, WITHDRAWAL, Debit, Movies, 12.57
04-15-2020, FEE, 5.55
É um formato CSV, mas algumas das linhas têm mais colunas do que outras. Pior ainda
para o processamento, uma coluna do tipo WITHDRAWAL tem texto gerado pelo usuário e
pode conter uma vírgula no texto. Um padrão de lista que inclui o padrão de descarte, o
padrão constante e o padrão var para capturar os dados de processos de valor neste
formato:
C#
decimal balance = 0m;
foreach (string[] transaction in ReadRecords())
balance += transaction switch
[_, "DEPOSIT", _, var amount] => decimal.Parse(amount),
[_, "WITHDRAWAL", .., var amount] => -decimal.Parse(amount),
[_, "INTEREST", var amount] => decimal.Parse(amount),
[_, "FEE", var fee] => -decimal.Parse(fee),
_ => throw new
InvalidOperationException($"Record {string.Join(", ", transaction)} is not
in the expected format!"),
};
Console.WriteLine($"Record: {string.Join(", ", transaction)}, New
balance: {balance:C}");
}
O exemplo anterior usa uma matriz de cadeia de caracteres, em que cada elemento é
um campo na linha. As teclas de expressão switch no segundo campo, que determina o
tipo de transação, e o número de colunas restantes. Cada linha garante que os dados
estão no formato correto. O padrão de descarte ( _ ) ignora o primeiro campo, com a
data da transação. O segundo campo corresponde ao tipo de transação. As
correspondências de elemento restantes pulam para o campo com a quantidade. A
partida final usa o padrão var para capturar a representação de cadeia de caracteres do
valor. A expressão calcula o valor a ser adicionado ou subtraído do saldo.
Os padrões de lista permitem que você corresponda na forma de uma sequência de
elementos de dados. Você usa os padrões de descarte e fatia para corresponder ao local
dos elementos. Você usa outros padrões para corresponder às características sobre
elementos individuais.
Este artigo fez um tour pelos tipos de código que você pode gravar com
correspondência de padrões em C#. Os artigos a seguir mostram mais exemplos de uso
dos padrões em cenários e o vocabulário completo dos padrões disponíveis para uso.
Confira também
Usar padrões correspondentes para evitar a verificação 'is' seguida por uma
conversão (regras de estilo IDE0020 e IDE0038)
Exploração: usar a correspondência de padrões para criar seu comportamento de
classe para obter um código melhor
Tutorial: Usar padrões correspondentes para criar algoritmos controlados por tipo
e controlados por dados
Referência: Correspondência de padrões
Descartes – conceitos básicos do C#
Artigo • 07/04/2023
Descartes são variáveis de espaço reservado intencionalmente não utilizadas no código
do aplicativo. Descartes são equivalentes a variáveis não atribuídas; eles não têm um
valor. Um descarte comunica uma intenção para o compilador e outras pessoas que
leem seu código: você pretendia ignorar o resultado de uma expressão. Talvez você
queira ignorar o resultado de uma expressão, um ou mais membros de uma expressão
de tupla, um parâmetro out para um método ou o destino de uma expressão de
correspondência de padrões.
Os descartes tornam a intenção do seu código clara. Um descarte indica que nosso
código nunca usa a variável. Eles aprimoram sua legibilidade e manutenção.
Você indica que uma variável é um descarte atribuindo a ela o sublinhado ( _ ) como seu
nome. Por exemplo, a chamada de método a seguir retorna uma tupla na qual o
primeiro e o segundo valores são descartes. area é uma variável declarada
anteriormente definida como o terceiro componente retornado por GetCityInformation :
C#
(_, _, area) = city.GetCityInformation(cityName);
A partir do C# 9.0, você pode usar descartes para especificar parâmetros de entrada não
utilizados de uma expressão lambda. Para obter mais informações, consulte a seção
Parâmetros de entrada de uma expressão lambda do artigo Expressões lambda.
Quando _ é um descarte válido, tentar recuperar seu valor ou usá-lo em uma operação
de atribuição gerará o erro do compilador CS0103, "O nome '_' não existe no contexto
atual". Esse erro ocorre porque não há um valor atribuído a _ , e pode não haver nem
mesmo um local de armazenamento atribuído a ela. Se ela fosse uma variável real, você
não poderia descartar mais de um valor, tal como ocorreu no exemplo anterior.
Desconstrução de objeto e de tupla
Descartes são úteis para trabalhar com tuplas quando seu código de aplicativo usa
alguns elementos da tupla, mas ignora outros. Por exemplo, o método
QueryCityDataForYears a seguir retorna uma tupla de com o nome de uma cidade, sua
área, um ano, a população da cidade nesse ano, um segundo ano e população da
cidade nesse segundo ano. O exemplo mostra a alteração na população entre esses dois
anos. Entre os dados disponíveis da tupla, não estamos preocupados com a área da
cidade e sabemos o nome da cidade e as duas datas em tempo de design. Como
resultado, estamos interessados apenas nos dois valores de população armazenados na
tupla e podemos lidar com seus valores restantes como descartes.
C#
var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960,
2010);
Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");
static (string, double, int, int, int, int) QueryCityDataForYears(string
name, int year1, int year2)
int population1 = 0, population2 = 0;
double area = 0;
if (name == "New York City")
area = 468.48;
if (year1 == 1960)
population1 = 7781984;
if (year2 == 2010)
population2 = 8175133;
return (name, area, year1, population1, year2, population2);
return ("", 0, 0, 0, 0, 0);
// The example displays the following output:
// Population change, 1960 to 2010: 393,149
Para obter mais informações sobre desconstruir tuplas com descartes, consulte
Desconstruindo tuplas e outros tipos.
O método Deconstruct de uma classe, estrutura ou interface também permite que você
recupere e decomponha um conjunto específico de dados de um objeto. Você poderá
usar descartes quando estiver interessado em trabalhar com apenas um subconjunto
dos valores desconstruídos. O exemplo a seguir desconstrói um objeto Person em
quatro cadeias de caracteres (os nomes e sobrenomes, a cidade e o estado), mas
descarta o sobrenome e o estado.
C#
using System;
namespace Discards
public class Person
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
public string State { get; set; }
public Person(string fname, string mname, string lname,
string cityName, string stateName)
FirstName = fname;
MiddleName = mname;
LastName = lname;
City = cityName;
State = stateName;
// Return the first and last name.
public void Deconstruct(out string fname, out string lname)
fname = FirstName;
lname = LastName;
public void Deconstruct(out string fname, out string mname, out
string lname)
fname = FirstName;
mname = MiddleName;
lname = LastName;
public void Deconstruct(out string fname, out string lname,
out string city, out string state)
fname = FirstName;
lname = LastName;
city = City;
state = State;
class Example
public static void Main()
var p = new Person("John", "Quincy", "Adams", "Boston", "MA");
// Deconstruct the person object.
var (fName, _, city, _) = p;
Console.WriteLine($"Hello {fName} of {city}!");
// The example displays the following output:
// Hello John of Boston!
Para obter mais informações sobre desconstruir tipos definidos pelo usuário com
descartes, consulte Desconstruindo tuplas e outros tipos.
Correspondência de padrões com switch
O padrão de descarte pode ser usado na correspondência de padrões com a expressão
switch. Toda expressão, incluindo null , sempre corresponde ao padrão de descarte.
O exemplo a seguir define um método ProvidesFormatInfo que usa uma expressão
switch para determinar se um objeto fornece uma implementação de IFormatProvider e
testa se o objeto é null . Ele também usa o padrão de descarte para manipular objetos
não nulos de qualquer outro tipo.
C#
object?[] objects = { CultureInfo.CurrentCulture,
CultureInfo.CurrentCulture.DateTimeFormat,
CultureInfo.CurrentCulture.NumberFormat,
new ArgumentException(), null };
foreach (var obj in objects)
ProvidesFormatInfo(obj);
static void ProvidesFormatInfo(object? obj) =>
Console.WriteLine(obj switch
IFormatProvider fmt => $"{fmt.GetType()} object",
null => "A null object reference: Its use could result in a
NullReferenceException",
_ => "Some object type without format information"
});
// The example displays the following output:
// System.Globalization.CultureInfo object
// System.Globalization.DateTimeFormatInfo object
// System.Globalization.NumberFormatInfo object
// Some object type without format information
// A null object reference: Its use could result in a
NullReferenceException
Chamadas para métodos com parâmetros out
Ao chamar o método Deconstruct para desconstruir um tipo definido pelo usuário (uma
instância de uma classe, estrutura ou interface), você pode descartar os valores de
argumentos out individuais. Mas você também pode descartar o valor de argumentos
out ao chamar qualquer método com um parâmetro out .
A exemplo a seguir chama o método DateTime.TryParse(String, out DateTime) para
determinar se a representação de cadeia de caracteres de uma data é válida na cultura
atual. Já que o exemplo está preocupado apenas em validar a cadeia de caracteres de
data e não em analisá-lo para extrair a data, o argumento out para o método é um
descarte.
C#
string[] dateStrings = {"05/01/2018 14:57:32.8", "2018-05-01 14:57:32.8",
"2018-05-01T14:57:32.8375298-04:00", "5/01/2018",
"5/01/2018 14:57:32.80 -07:00",
"1 May 2018 2:57:32.8 PM", "16-05-2018 1:00:32 PM",
"Fri, 15 May 2018 20:10:57 GMT" };
foreach (string dateString in dateStrings)
if (DateTime.TryParse(dateString, out _))
Console.WriteLine($"'{dateString}': valid");
else
Console.WriteLine($"'{dateString}': invalid");
// The example displays output like the following:
// '05/01/2018 14:57:32.8': valid
// '2018-05-01 14:57:32.8': valid
// '2018-05-01T14:57:32.8375298-04:00': valid
// '5/01/2018': valid
// '5/01/2018 14:57:32.80 -07:00': valid
// '1 May 2018 2:57:32.8 PM': valid
// '16-05-2018 1:00:32 PM': invalid
// 'Fri, 15 May 2018 20:10:57 GMT': invalid
Um descarte autônomo
Você pode usar um descarte autônomo para indicar qualquer variável que você opte
por ignorar. Um uso típico é usar uma atribuição para garantir que um argumento não
seja nulo. O código a seguir usa um descarte para forçar uma atribuição. O lado direito
da atribuição usa o operador de avaliação de nulo para lançar um
System.ArgumentNullException quando o argumento é null . O código não precisa do
resultado da atribuição; portanto, ele é descartado. A expressão força uma verificação
de nulo. O descarte esclarece sua intenção: o resultado da atribuição não é necessário
ou usado.
C#
public static void Method(string arg)
_ = arg ?? throw new ArgumentNullException(paramName: nameof(arg),
message: "arg can't be null");
// Do work with arg.
O exemplo a seguir usa um descarte autônomo para ignorar o objeto Task retornado
por uma operação assíncrona. A atribuição da tarefa tem o efeito de suprimir a exceção
que a operação gera quando está prestes a ser concluída. Isso deixa clara sua intenção:
você deseja descartar Task e ignorar todos os erros gerados a partir dessa operação
assíncrona.
C#
private static async Task ExecuteAsyncMethods()
Console.WriteLine("About to launch a task...");
_ = Task.Run(() =>
var iterations = 0;
for (int ctr = 0; ctr < int.MaxValue; ctr++)
iterations++;
Console.WriteLine("Completed looping operation...");
throw new InvalidOperationException();
});
await Task.Delay(5000);
Console.WriteLine("Exiting after 5 second delay");
// The example displays output like the following:
// About to launch a task...
// Completed looping operation...
// Exiting after 5 second delay
Sem atribuir a tarefa a um descarte, o código a seguir gera um aviso do compilador:
C#
private static async Task ExecuteAsyncMethods()
Console.WriteLine("About to launch a task...");
// CS4014: Because this call is not awaited, execution of the current
method continues before the call is completed.
// Consider applying the 'await' operator to the result of the call.
Task.Run(() =>
var iterations = 0;
for (int ctr = 0; ctr < int.MaxValue; ctr++)
iterations++;
Console.WriteLine("Completed looping operation...");
throw new InvalidOperationException();
});
await Task.Delay(5000);
Console.WriteLine("Exiting after 5 second delay");
7 Observação
Se você executar um dos dois exemplos anteriores usando um depurador, o
depurador interromperá o programa quando a exceção for lançada. Sem um
depurador anexado, a exceção é silenciosamente ignorada em ambos os casos.
_ também é um identificador válido. Quando usado fora de um contexto com suporte,
_ não é tratado como um descarte, mas como uma variável válida. Se um identificador
chamado _ já está no escopo, o uso de _ como um descarte autônomo pode resultar
em:
A modificação acidental do valor da variável _ no escopo atribuindo a ela o valor
do descarte pretendido. Por exemplo:
C#
private static void ShowValue(int _)
{
byte[] arr = { 0, 0, 1, 2 };
_ = BitConverter.ToInt32(arr, 0);
Console.WriteLine(_);
// The example displays the following output:
// 33619968
Um erro do compilador por violação de segurança de tipo. Por exemplo:
C#
private static bool RoundTrips(int _)
string value = _.ToString();
int newValue = 0;
_ = Int32.TryParse(value, out newValue);
return _ == newValue;
// The example displays the following compiler error:
// error CS0029: Cannot implicitly convert type 'bool' to 'int'
Erro do compilador CS0136, "Um local ou um parâmetro denominado '_' não pode
ser declarado neste escopo porque esse nome é usado em um escopo delimitador
de local para definir um local ou parâmetro." Por exemplo:
C#
public void DoSomething(int _)
var _ = GetValue(); // Error: cannot declare local _ when one is
already in scope
// The example displays the following compiler error:
// error CS0136:
// A local or parameter named '_' cannot be declared in this
scope
// because that name is used in an enclosing local scope
// to define a local or parameter
Confira também
Remover valor de expressão desnecessária (regra de estilo IDE0058)
Remover atribuição de valor desnecessária (regra de estilo IDE0059)
Remover parâmetro não usado (regra de estilo IDE0060)
Desconstruindo tuplas e outros tipos
Operador is
Expressão switch
Desconstruindo tuplas e outros tipos
Artigo • 07/04/2023
Uma tupla fornece uma maneira leve de recuperar vários valores de uma chamada de
método. Mas depois de recuperar a tupla, você precisa lidar com seus elementos
individuais. Trabalhar elemento por elemento é incômodo, conforme mostra o exemplo
a seguir. O método QueryCityData retorna uma tupla de três e cada um de seus
elementos é atribuído a uma variável em uma operação separada.
C#
public class Example
public static void Main()
var result = QueryCityData("New York City");
var city = result.Item1;
var pop = result.Item2;
var size = result.Item3;
// Do something with the data.
private static (string, int, double) QueryCityData(string name)
if (name == "New York City")
return (name, 8175133, 468.48);
return ("", 0, 0);
Recuperar vários valores de propriedade e de campo de um objeto pode ser igualmente
complicado: é preciso atribuir um valor de campo ou de propriedade a uma variável,
membro por membro.
Você pode recuperar vários elementos de uma tupla ou recuperar vários valores de
campo, propriedade e computação de um objeto em uma única operação de
desconstrução . Para desconstruir uma tupla, você atribui os elementos dela a variáveis
individuais. Quando você desconstrói um objeto, você atribui os elementos dela a
variáveis individuais.
Tuplas
O C# conta com suporte interno à desconstrução de tuplas, que permite que você
descompacte todos os itens em uma tupla em uma única operação. A sintaxe geral para
desconstruir uma tupla é semelhante à sintaxe para definir uma: coloque as variáveis
para as quais cada elemento deve ser atribuído entre parênteses no lado esquerdo de
uma instrução de atribuição. Por exemplo, a instrução a seguir atribui os elementos de
uma tupla de quatro a quatro variáveis separadas:
C#
var (name, address, city, zip) = contact.GetAddressInfo();
Há três maneiras de desconstruir uma tupla:
Você pode declarar explicitamente o tipo de cada campo dentro de parênteses. O
exemplo a seguir usa essa abordagem para desconstruir a tupla de três retornada
pelo método QueryCityData .
C#
public static void Main()
(string city, int population, double area) = QueryCityData("New
York City");
// Do something with the data.
Você pode usar a palavra-chave var de modo que o C# infira o tipo de cada
variável. Você coloca a palavra-chave var fora dos parênteses. O exemplo a seguir
usa a inferência de tipos ao desconstruir a tupla de três retornada pelo método
QueryCityData .
C#
public static void Main()
var (city, population, area) = QueryCityData("New York City");
// Do something with the data.
Você também pode usar a palavra-chave var individualmente com qualquer uma
ou todas as declarações de variável dentro dos parênteses.
C#
public static void Main()
(string city, var population, var area) = QueryCityData("New York
City");
// Do something with the data.
Isso é difícil e não é recomendado.
Por fim, você pode desconstruir a tupla em variáveis que já foram declaradas.
C#
public static void Main()
string city = "Raleigh";
int population = 458880;
double area = 144.8;
(city, population, area) = QueryCityData("New York City");
// Do something with the data.
A partir do C# 10, você pode misturar declaração de variável e atribuição em uma
desconstrução.
C#
public static void Main()
string city = "Raleigh";
int population = 458880;
(city, population, double area) = QueryCityData("New York City");
// Do something with the data.
Você não pode especificar um tipo específico fora dos parênteses, mesmo se todos os
campos na tupla tiverem o mesmo tipo. Isso gera o erro do compilador CS8136, "O
formulário de desconstrução 'var (...)' não permite um tipo específico para 'var'.".
Você deve atribuir cada elemento da tupla a uma variável. Se você omitir qualquer
elemento, o compilador gerará o erro CS8132, "Não é possível desconstruir uma tupla
de 'x' elementos em 'y' variáveis".
Elementos tupla com descartes
Geralmente, ao desconstruir uma tupla, você está interessado nos valores de apenas
alguns elementos. Você pode aproveitar o suporte do C#para descartes, que são
variáveis somente gravação cujos valores você escolheu ignorar. Um descarte é
escolhido por um caractere de sublinhado ("_") em uma atribuição. Você pode descartar
tantos valores quantos desejar; todos são representados pelo descarte único, _ .
O exemplo a seguir ilustra o uso de tuplas com descartes. O método
QueryCityDataForYears a seguir retorna uma tupla de seis com o nome de uma cidade,
sua área, um ano, a população da cidade nesse ano, um segundo ano e população da
cidade nesse segundo ano. O exemplo mostra a alteração na população entre esses dois
anos. Entre os dados disponíveis da tupla, não estamos preocupados com a área da
cidade e sabemos o nome da cidade e as duas datas em tempo de design. Como
resultado, estamos interessados apenas nos dois valores de população armazenados na
tupla e podemos lidar com seus valores restantes como descartes.
C#
using System;
public class ExampleDiscard
public static void Main()
var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York
City", 1960, 2010);
Console.WriteLine($"Population change, 1960 to 2010: {pop2 -
pop1:N0}");
private static (string, double, int, int, int, int)
QueryCityDataForYears(string name, int year1, int year2)
int population1 = 0, population2 = 0;
double area = 0;
if (name == "New York City")
area = 468.48;
if (year1 == 1960)
population1 = 7781984;
if (year2 == 2010)
population2 = 8175133;
return (name, area, year1, population1, year2, population2);
return ("", 0, 0, 0, 0, 0);
// The example displays the following output:
// Population change, 1960 to 2010: 393,149
Tipos definidos pelo usuário
O C# não oferece suporte interno para desconstruir tipos não tupla diferentes dos
record tipos e DictionaryEntry . No entanto, como o autor de uma classe, um struct ou
uma interface, você pode permitir instâncias do tipo a ser desconstruído
implementando um ou mais métodos Deconstruct . O método retorna void e cada valor
a ser desconstruído é indicado por um parâmetro out na assinatura do método. Por
exemplo, o método Deconstruct a seguir de uma classe Person retorna o nome, o
segundo nome e o sobrenome:
C#
public void Deconstruct(out string fname, out string mname, out string
lname)
Em seguida, você pode desconstruir uma instância da classe Person denominada p com
uma atribuição semelhante à seguinte:
C#
var (fName, mName, lName) = p;
O exemplo a seguir sobrecarrega o método Deconstruct para retornar várias
combinações de propriedades de um objeto Person . As sobrecargas individuais
retornam:
Um nome e um sobrenome.
Um nome, nome do meio e sobrenome.
Um nome, um sobrenome, um nome de cidade e um nome de estado.
C#
using System;
public class Person
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
public string State { get; set; }
public Person(string fname, string mname, string lname,
string cityName, string stateName)
FirstName = fname;
MiddleName = mname;
LastName = lname;
City = cityName;
State = stateName;
// Return the first and last name.
public void Deconstruct(out string fname, out string lname)
fname = FirstName;
lname = LastName;
public void Deconstruct(out string fname, out string mname, out string
lname)
fname = FirstName;
mname = MiddleName;
lname = LastName;
public void Deconstruct(out string fname, out string lname,
out string city, out string state)
fname = FirstName;
lname = LastName;
city = City;
state = State;
public class ExampleClassDeconstruction
public static void Main()
var p = new Person("John", "Quincy", "Adams", "Boston", "MA");
// Deconstruct the person object.
var (fName, lName, city, state) = p;
Console.WriteLine($"Hello {fName} {lName} of {city}, {state}!");
// The example displays the following output:
// Hello John Adams of Boston, MA!
Vários métodos Deconstruct com o mesmo número de parâmetros são ambíguos. Você
deve ter cuidado ao definir métodos Deconstruct com diferentes números de
parâmetros ou "aridade". Métodos Deconstruct com o mesmo número de parâmetros
não podem ser distinguidos durante a resolução de sobrecarga.
Tipo definido pelo usuário com descartes
Assim como você faria com tuplas, você pode usar descartes para ignorar os itens
selecionados retornados por um método Deconstruct . Cada descarte é definido por
uma variável chamada "_", sendo que uma única operação de desconstrução pode
incluir vários descartes.
O exemplo a seguir desconstrói um objeto Person em quatro cadeias de caracteres (os
nomes e sobrenomes, a cidade e o estado), mas descarta o sobrenome e o estado.
C#
// Deconstruct the person object.
var (fName, _, city, _) = p;
Console.WriteLine($"Hello {fName} of {city}!");
// The example displays the following output:
// Hello John of Boston!
Métodos de extensão para tipos definidos pelo
usuário
Se você não criar uma classe, struct ou interface, você ainda poderá decompor objetos
desse tipo implementando um ou mais Deconstruct métodos de extensão para retornar
os valores nos quais você estiver interessado.
O exemplo a seguir define dois métodos de extensão Deconstruct para a classe
System.Reflection.PropertyInfo. O primeiro retorna um conjunto de valores que indicam
as características da propriedade, incluindo seu tipo, se ela é estática ou instância, se ela
é somente leitura e se é indexada. O segundo indica a acessibilidade da propriedade. Já
que a acessibilidade dos acessadores get e set pode ser diferente, valores boolianos
indicam se a propriedade acessadores get e set separados e, em caso afirmativo, se eles
têm a mesma acessibilidade. Se houver apenas um acessador ou ambos os acessadores
get e set têm a mesma acessibilidade, a variável access indica a acessibilidade da
propriedade como um todo. Caso contrário, a acessibilidade dos acessadores get e set é
indicada pelas variáveis getAccess e setAccess .
C#
using System;
using System.Collections.Generic;
using System.Reflection;
public static class ReflectionExtensions
public static void Deconstruct(this PropertyInfo p, out bool isStatic,
out bool isReadOnly, out bool isIndexed,
out Type propertyType)
var getter = p.GetMethod;
// Is the property read-only?
isReadOnly = ! p.CanWrite;
// Is the property instance or static?
isStatic = getter.IsStatic;
// Is the property indexed?
isIndexed = p.GetIndexParameters().Length > 0;
// Get the property type.
propertyType = p.PropertyType;
public static void Deconstruct(this PropertyInfo p, out bool
hasGetAndSet,
out bool sameAccess, out string access,
out string getAccess, out string
setAccess)
hasGetAndSet = sameAccess = false;
string getAccessTemp = null;
string setAccessTemp = null;
MethodInfo getter = null;
if (p.CanRead)
getter = p.GetMethod;
MethodInfo setter = null;
if (p.CanWrite)
setter = p.SetMethod;
if (setter != null && getter != null)
hasGetAndSet = true;
if (getter != null)
if (getter.IsPublic)
getAccessTemp = "public";
else if (getter.IsPrivate)
getAccessTemp = "private";
else if (getter.IsAssembly)
getAccessTemp = "internal";
else if (getter.IsFamily)
getAccessTemp = "protected";
else if (getter.IsFamilyOrAssembly)
getAccessTemp = "protected internal";
if (setter != null)
if (setter.IsPublic)
setAccessTemp = "public";
else if (setter.IsPrivate)
setAccessTemp = "private";
else if (setter.IsAssembly)
setAccessTemp = "internal";
else if (setter.IsFamily)
setAccessTemp = "protected";
else if (setter.IsFamilyOrAssembly)
setAccessTemp = "protected internal";
// Are the accessibility of the getter and setter the same?
if (setAccessTemp == getAccessTemp)
sameAccess = true;
access = getAccessTemp;
getAccess = setAccess = String.Empty;
else
access = null;
getAccess = getAccessTemp;
setAccess = setAccessTemp;
public class ExampleExtension
public static void Main()
Type dateType = typeof(DateTime);
PropertyInfo prop = dateType.GetProperty("Now");
var (isStatic, isRO, isIndexed, propType) = prop;
Console.WriteLine($"\nThe {dateType.FullName}.{prop.Name}
property:");
Console.WriteLine($" PropertyType: {propType.Name}");
Console.WriteLine($" Static: {isStatic}");
Console.WriteLine($" Read-only: {isRO}");
Console.WriteLine($" Indexed: {isIndexed}");
Type listType = typeof(List<>);
prop = listType.GetProperty("Item",
BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
var (hasGetAndSet, sameAccess, accessibility, getAccessibility,
setAccessibility) = prop;
Console.Write($"\nAccessibility of the {listType.FullName}.
{prop.Name} property: ");
if (!hasGetAndSet | sameAccess)
Console.WriteLine(accessibility);
else
Console.WriteLine($"\n The get accessor: {getAccessibility}");
Console.WriteLine($" The set accessor: {setAccessibility}");
// The example displays the following output:
// The System.DateTime.Now property:
// PropertyType: DateTime
// Static: True
// Read-only: True
// Indexed: False
//
// Accessibility of the System.Collections.Generic.List`1.Item
property: public
Método de extensão para tipos de sistema
Alguns tipos de sistema fornecem o método Deconstruct como uma conveniência. Por
exemplo, o tipo System.Collections.Generic.KeyValuePair<TKey,TValue> fornece essa
funcionalidade. Quando você está iterando em
System.Collections.Generic.Dictionary<TKey,TValue>, cada elemento é um
KeyValuePair<TKey, TValue> e pode ser desconstruído. Considere o seguinte exemplo:
C#
Dictionary<string, int> snapshotCommitMap =
new(StringComparer.OrdinalIgnoreCase)
["https://github.com/dotnet/docs"] = 16_465,
["https://github.com/dotnet/runtime"] = 114_223,
["https://github.com/dotnet/installer"] = 22_436,
["https://github.com/dotnet/roslyn"] = 79_484,
["https://github.com/dotnet/aspnetcore"] = 48_386
};
foreach (var (repo, commitCount) in snapshotCommitMap)
Console.WriteLine(
$"The {repo} repository had {commitCount:N0} commits as of November
10th, 2021.");
Você pode adicionar um método Deconstruct aos tipos de sistema que não têm um.
Considere o seguinte método de extensão:
C#
public static class NullableExtensions
public static void Deconstruct<T>(
this T? nullable,
out bool hasValue,
out T value) where T : struct
hasValue = nullable.HasValue;
value = nullable.GetValueOrDefault();
Esse método de extensão permite que todos os tipos Nullable<T> sejam
desconstruídos em uma tupla de (bool hasValue, T value) . O exemplo a seguir mostra
o código que usa este método de extensão:
C#
DateTime? questionableDateTime = default;
var (hasValue, value) = questionableDateTime;
Console.WriteLine(
$"{{ HasValue = {hasValue}, Value = {value} }}");
questionableDateTime = DateTime.Now;
(hasValue, value) = questionableDateTime;
Console.WriteLine(
$"{{ HasValue = {hasValue}, Value = {value} }}");
// Example outputs:
// { HasValue = False, Value = 1/1/0001 12:00:00 AM }
// { HasValue = True, Value = 11/10/2021 6:11:45 PM }
Tipos record
Quando você declara um tipo de registro usando dois ou mais parâmetros posicionais,
o compilador cria um método Deconstruct com um parâmetro out para cada
parâmetro posicional na declaração record . Para obter mais informações, consulte
Sintaxe posicional para definição de propriedade e Comportamento de desconstrutor
em registros derivados.
Confira também
Declaração de variável de desconstrução (regra de estilo IDE0042)
Descartes
Tipos de tupla
Exceções e manipulação de exceções
Artigo • 15/02/2023
Os recursos de manipulação de exceção da linguagem C# ajudam você a lidar com
quaisquer situações excepcionais ou inesperadas que ocorram quando um programa for
executado. A manipulação de exceções usa as palavras-chave try , catch e finally
para executar ações que talvez não sejam bem-sucedidas, lidar com falhas quando você
decidir que é razoável fazê-lo e limpar recursos depois disso. Podem ser geradas
exceções pelo CLR (Common Language Runtime), pelo .NET, por quaisquer bibliotecas
de terceiros ou pelo código do aplicativo. As exceções são criadas usando a palavra-
chave throw .
Em muitos casos, uma exceção pode ser lançada não por um método que seu código
chamou diretamente, mas por outro método mais abaixo na pilha de chamadas.
Quando isso acontecer, o CLR desenrolará a pilha, em busca de um método com um
bloco catch para o tipo de exceção específico e executará o primeiro o bloco catch
desse tipo que encontrar. Se ele não encontrar um bloco catch apropriado na pilha de
chamadas, ele encerrará o processo e exibirá uma mensagem para o usuário.
Neste exemplo, um método testa a divisão por zero e captura o erro. Sem a
manipulação de exceção, esse programa encerraria com um DivideByZeroException
não resolvido.
C#
public class ExceptionTest
static double SafeDivision(double x, double y)
if (y == 0)
throw new DivideByZeroException();
return x / y;
public static void Main()
// Input for test purposes. Change the values to see
// exception handling behavior.
double a = 98, b = 0;
double result;
try
result = SafeDivision(a, b);
Console.WriteLine("{0} divided by {1} = {2}", a, b, result);
catch (DivideByZeroException)
Console.WriteLine("Attempted divide by zero.");
Visão geral sobre exceções
As exceções têm as seguintes propriedades:
As exceções são tipos que derivam, por fim, de System.Exception .
Use um bloco try nas instruções que podem lançar exceções.
Quando ocorre uma exceção no bloco try , o fluxo de controle vai para o primeiro
manipulador de exceção associada que está presente em qualquer lugar na pilha
de chamadas. No C#, a palavra-chave catch é usada para definir um manipulador
de exceção.
Se nenhum manipulador de exceção para uma determinada exceção estiver
presente, o programa interromperá a execução com uma mensagem de erro.
Não capture uma exceção, a menos que você possa manipulá-la e deixar o
aplicativo em um estado conhecido. Se você capturar System.Exception , relance-o
usando a palavra-chave throw no final do bloco catch .
Se um bloco catch define uma variável de exceção, você pode usá-lo para obter
mais informações sobre o tipo de exceção que ocorreu.
As exceções podem ser geradas explicitamente por um programa usando a
palavra-chave throw .
Os objetos de exceção contêm informações detalhadas sobre o erro, como o
estado da pilha de chamadas e uma descrição de texto do erro.
O código em um bloco finally será executado mesmo que seja lançada uma
exceção. Use um bloco finally para liberar recursos, por exemplo, para fechar
todos os fluxos ou arquivos que foram abertos no bloco try .
As exceções gerenciadas no .NET são implementadas sobre o mecanismo de
manipulação de exceções estruturadas do Win32. Para obter mais informações,
consulte Manipulação de exceções estruturadas (C/C++) e Curso rápido sobre a
manipulação de exceções estruturadas do Win32 .
Especificação da Linguagem C#
Para obter mais informações, veja Exceções na Especificação da linguagem C#. A
especificação da linguagem é a fonte definitiva para a sintaxe e o uso de C#.
Confira também
SystemException
Palavras-chave do C#
throw
try-catch
try-finally
try-catch-finally
Exceções
Usar exceções
Artigo • 10/05/2023
No C#, os erros no programa em tempo de execução são propagados pelo programa
usando um mecanismo chamado exceções. As exceções são geradas pelo código que
encontra um erro e capturadas pelo código que pode corrigir o erro. As exceções
podem ser geradas pelo tempo de execução do .NET ou pelo código em um programa.
Uma vez que uma exceção é gerada, ela é propagada acima na pilha de chamadas até
uma instrução catch para a exceção ser encontrada. As exceções não capturadas são
tratadas por um manipulador de exceção genérico fornecido pelo sistema que exibe
uma caixa de diálogo.
As exceções são representadas por classes derivadas de Exception. Essa classe identifica
o tipo de exceção e contém propriedades que têm detalhes sobre a exceção. Gerar uma
exceção envolve criar uma instância de uma classe derivada de exceção, opcionalmente
configurar propriedades da exceção e, em seguida, gerar o objeto usando a palavra-
chave throw . Por exemplo:
C#
class CustomException : Exception
public CustomException(string message)
private static void TestThrow()
throw new CustomException("Custom exception in TestThrow()");
Depois que uma exceção é gerada, o runtime verifica a instrução atual para ver se ela
está dentro de um bloco try . Se estiver, todos os blocos catch associados ao bloco
try serão verificados para ver se eles podem capturar a exceção. Os blocos Catch
normalmente especificam os tipos de exceção. Se o tipo do bloco catch for do mesmo
tipo que a exceção ou uma classe base da exceção, o bloco catch poderá manipular o
método. Por exemplo:
C#
try
TestThrow();
catch (CustomException ex)
System.Console.WriteLine(ex.ToString());
Se a instrução que gera uma exceção não estiver dentro de um bloco try ou se o bloco
try que o contém não tiver um bloco catch correspondente, o runtime verificará o
método de chamada quanto a uma instrução try e blocos catch . O runtime continuará
acima na pilha de chamada, pesquisando um bloco catch compatível. Depois que o
bloco catch for localizado e executado, o controle será passado para a próxima
instrução após aquele bloco catch .
Uma instrução try pode conter mais de um bloco catch . A primeira instrução catch
que pode manipular a exceção é executado, todas as instruções catch posteriores,
mesmo se forem compatíveis, são ignoradas. Ordenar blocos catch do mais específico
(ou mais derivado) para o menos específico. Por exemplo:
C#
using System;
using System.IO;
namespace Exceptions
public class CatchOrder
public static void Main()
try
using (var sw = new StreamWriter("./test.txt"))
sw.WriteLine("Hello");
// Put the more specific exceptions first.
catch (DirectoryNotFoundException ex)
Console.WriteLine(ex);
catch (FileNotFoundException ex)
Console.WriteLine(ex);
// Put the least specific exception last.
catch (IOException ex)
Console.WriteLine(ex);
Console.WriteLine("Done");
Antes de o bloco catch ser executado, o runtime verifica se há blocos finally . Os
blocos Finally permitem que o programador limpe qualquer estado ambíguo que
pode ser deixado de um bloco try cancelado ou libere quaisquer recursos externos
(como identificadores de gráfico, conexões de banco de dados ou fluxos de arquivo)
sem esperar o coletor de lixo no runtime finalizar os objetos. Por exemplo:
C#
static void TestFinally()
FileStream? file = null;
//Change the path to something that works on your machine.
FileInfo fileInfo = new System.IO.FileInfo("./file.txt");
try
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
finally
// Closing the file allows you to reopen it immediately - otherwise
IOException is thrown.
file?.Close();
try
file = fileInfo.OpenWrite();
Console.WriteLine("OpenWrite() succeeded");
catch (IOException)
Console.WriteLine("OpenWrite() failed");
Se WriteByte() gerou uma exceção, o código no segundo bloco try que tentar reabrir
o arquivo falhará se file.Close() não for chamado e o arquivo permanecerá
bloqueado. Como os blocos finally são executados mesmo se uma exceção for
gerada, o bloco finally no exemplo anterior permite que o arquivo seja fechado
corretamente e ajuda a evitar um erro.
Se não for encontrado nenhum bloco catch compatível na pilha de chamadas após
uma exceção ser gerada, ocorrerá uma das três coisas:
Se a exceção estiver em um finalizador, o finalizador será anulado e o finalizador
base, se houver, será chamado.
Se a pilha de chamadas contiver um construtor estático ou um inicializador de
campo estático, uma TypeInitializationException será gerada, com a exceção
original atribuída à propriedade InnerException da nova exceção.
Se o início do thread for atingido, o thread será encerrado.
Manipulação de exceções (Guia de
Programação em C#)
Artigo • 20/03/2023
Um bloco try é usado por programadores de C# para particionar o código que pode ser
afetado por uma exceção. Os blocos catch associados são usados para tratar qualquer
exceção resultante. Um bloco finally contém código que será executado de uma
exceção ser ou não ser lançada no bloco try , como a liberação de recursos que estão
alocados no bloco try . Um bloco try exige um ou mais blocos catch associados ou
um bloco finally ou ambos.
Os exemplos a seguir mostram uma instrução try-catch , uma instrução try-finally e
um instrução try-catch-finally .
C#
try
// Code to try goes here.
catch (SomeSpecificException ex)
// Code to handle the exception goes here.
// Only catch exceptions that you know how to handle.
// Never catch base class System.Exception without
// rethrowing it at the end of the catch block.
C#
try
// Code to try goes here.
finally
// Code to execute after the try block goes here.
C#
try
// Code to try goes here.
catch (SomeSpecificException ex)
// Code to handle the exception goes here.
finally
// Code to execute after the try (and possibly catch) blocks
// goes here.
Um bloco try sem um bloco catch ou finally causa um erro do compilador.
Blocos catch
Um bloco catch pode especificar o tipo de exceção a ser capturado. A especificação de
tipo é chamada de filtro de exceção. O tipo de exceção deve ser derivado de Exception.
Em geral, não especifique Exception como o filtro de exceção, a menos que você saiba
como tratar todas as exceções que podem ser lançadas no bloco try ou incluiu uma
instrução throw no final do seu bloco catch .
Vários blocos catch com filtros de exceção diferentes podem ser encadeados. Os blocos
catch são avaliados de cima para baixo no seu código, mas somente um bloco catch
será executado para cada exceção que é lançada. O primeiro bloco catch que especifica
o tipo exato ou uma classe base da exceção lançada será executado. Se nenhum bloco
catch especificar uma classe de exceção correspondente, um bloco catch que não tem
nenhum tipo será selecionado, caso haja algum na instrução. É importante posicionar os
blocos catch com as classes de exceção mais específicas (ou seja, os mais derivados)
primeiro.
Capture exceções quando as seguintes condições forem verdadeiras:
Você tem uma boa compreensão de porque a exceção seria lançada e pode
implementar uma recuperação específica, como solicitar que o usuário insira um
novo nome de arquivo, quando você capturar um objeto FileNotFoundException.
Você pode criar e lançar uma exceção nova e mais específica.
C#
int GetInt(int[] array, int index)
try
return array[index];
catch (IndexOutOfRangeException e)
throw new ArgumentOutOfRangeException(
"Parameter index is out of range.", e);
Você deseja tratar parcialmente uma exceção antes de passá-la para mais
tratamento. No exemplo a seguir, um bloco catch é usado para adicionar uma
entrada a um log de erros antes de lançar novamente a exceção.
C#
try
// Try to access a resource.
catch (UnauthorizedAccessException e)
// Call a custom error logging procedure.
LogError(e);
// Re-throw the error.
throw;
Você também pode especificar filtros de exceção para adicionar uma expressão booliana
a uma cláusula catch. Filtros de exceção indicam que uma cláusula catch específica
corresponde somente quando essa condição é verdadeira. No exemplo a seguir, ambas
as cláusulas catch usam a mesma classe de exceção, mas uma condição extra é
verificada para criar uma mensagem de erro diferente:
C#
int GetInt(int[] array, int index)
try
return array[index];
catch (IndexOutOfRangeException e) when (index < 0)
throw new ArgumentOutOfRangeException(
"Parameter index cannot be negative.", e);
catch (IndexOutOfRangeException e)
throw new ArgumentOutOfRangeException(
"Parameter index cannot be greater than the array size.", e);
Um filtro de exceção que sempre retorna false pode ser usado para examinar todas as
exceções, mas não processá-las. Um uso típico é registrar exceções:
C#
public static void Main()
try
string? s = null;
Console.WriteLine(s.Length);
catch (Exception e) when (LogException(e))
Console.WriteLine("Exception must have been handled");
private static bool LogException(Exception e)
Console.WriteLine($"\tIn the log routine. Caught {e.GetType()}");
Console.WriteLine($"\tMessage: {e.Message}");
return false;
O método LogException sempre retorna false , nenhuma cláusula catch que usa esse
filtro de exceção corresponde. A cláusula catch pode ser geral, usando
System.Exception, e cláusulas posteriores podem processar classes de exceção mais
específicas.
Blocos Finally
Um bloco finally permite que você limpe as ações que são realizadas em um bloco
try . Se estiver presente, o bloco finally será executado por último, depois do bloco
try e de qualquer bloco catch de correspondência. Um bloco finally sempre é
executado, independentemente de uma exceção ser lançada ou de um bloco catch
correspondente ao tipo de exceção ser encontrado.
O bloco finally pode ser usado para liberar recursos, como fluxos de arquivos,
conexões de banco de dados e identificadores de gráficos, sem esperar que o coletor de
lixo no runtime finalize os objetos.
No exemplo a seguir, o bloco finally é usado para fechar um arquivo está aberto no
bloco try . Observe que o estado do identificador de arquivo é selecionado antes do
arquivo ser fechado. Se o bloco try não puder abrir o arquivo, o identificador de
arquivo ainda terá o valor null e o bloco finally não tentará fechá-lo. Em vez disso, se
o arquivo for aberto com êxito no bloco try , o bloco finally fechará o arquivo aberto.
C#
FileStream? file = null;
FileInfo fileinfo = new System.IO.FileInfo("./file.txt");
try
file = fileinfo.OpenWrite();
file.WriteByte(0xF);
finally
// Check for null because OpenWrite might have failed.
file?.Close();
Especificação da Linguagem C#
Para obter mais informações, veja Exceções e A declaração try na Especificação da
Linguagem C#. A especificação da linguagem é a fonte definitiva para a sintaxe e o uso
de C#.
Confira também
Referência de C#
try-catch
try-finally
try-catch-finally
Instrução using
Criar e lançar exceções
Artigo • 07/04/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 palavra-chave throw. Então, o runtime procura o manipulador de
exceção mais compatível.
Os programadores devem lançar exceções quando uma ou mais das seguintes
condições forem verdadeiras:
O método não pode concluir sua funcionalidade definida. Por exemplo, se um
parâmetro para um método tem um valor inválido:
C#
static void CopyObject(SampleClass original)
_ = original ?? throw new ArgumentException("Parameter cannot be
null", nameof(original));
É feita uma chamada inadequada a um objeto, com base no estado do objeto. Um
exemplo pode ser a tentativa de gravar em um arquivo somente leitura. Em casos
em que um estado do objeto não permita uma operação, lance uma instância de
InvalidOperationException ou um objeto com base em uma derivação dessa classe.
O código a seguir é um exemplo de um método que gera um objeto
InvalidOperationException:
C#
public class ProgramLog
{
FileStream logFile = null!;
public void OpenLog(FileInfo fileName, FileMode mode) { }
public void WriteLog()
if (!logFile.CanWrite)
throw new InvalidOperationException("Logfile cannot be
read-only");
// Else write data to the log and return.
Quando um argumento para um método causa uma exceção. Nesse caso, a
exceção original deve ser capturada e uma instância de ArgumentException deve
ser criada. A exceção original deve ser passada para o construtor do
ArgumentException como o parâmetro InnerException:
C#
static int GetValueFromArray(int[] array, int index)
try
return array[index];
catch (IndexOutOfRangeException e)
throw new ArgumentOutOfRangeException(
"Parameter index is out of range.", e);
7 Observação
O exemplo acima tem fins ilustrativos. A validação de índice por meio de exceções
é, na maioria dos casos, não é uma boa prática. As exceções devem ser reservadas
para proteger contra condições excepcionais do programa, não para verificação de
argumentos, como acima.
As exceções contêm uma propriedade chamada StackTrace. Essa cadeia de caracteres
contém o nome dos métodos na pilha de chamadas atual, junto com o nome de arquivo
e o número de linha em que a exceção foi lançada para cada método. Um objeto
StackTrace é criado automaticamente pelo CLR (Common Language Runtime) no ponto
da instrução throw , de modo que as exceções devem ser lançadas do ponto em que o
rastreamento de pilha deve começar.
Todas as exceções contêm uma propriedade chamada Message. Essa cadeia de
caracteres deve ser definida para explicar o motivo da exceção. As informações que são
sensíveis à segurança não devem ser colocadas no texto da mensagem. Além Message,
ArgumentException contém uma propriedade chamada ParamName que deve ser
definida como o nome do argumento que causou a exceção a ser lançada. Em um setter
de propriedade, ParamName deve ser definido como value .
Os métodos públicos e protegidos lançam exceções sempre que não puderem concluir
suas funções pretendidas. A classe de exceção lançada é a exceção mais específica
disponível que se adapta às condições do erro. Essas exceções devem ser
documentadas como parte da funcionalidade de classe e as classes derivadas ou as
atualizações da classe original devem manter o mesmo comportamento para
compatibilidade com versões anteriores.
Coisas a serem evitadas ao lançar exceções
A lista a seguir identifica as práticas a serem evitadas ao lançar exceções:
Não use exceções para alterar o fluxo de um programa como parte da execução
normal. Use exceções para relatar e lidar com condições de erro.
As exceções não devem ser retornadas como um valor retornado ou um
parâmetro em vez de serem lançadas.
Não lance System.Exception, System.SystemException,
System.NullReferenceException ou System.IndexOutOfRangeException
intencionalmente de seu próprio código-fonte.
Não crie exceções que podem ser lançadas no modo de depuração, mas não no
modo de versão. Em vez disso, use o Debug Assert para identificar erros em tempo
de execução durante a fase de desenvolvimento.
Definindo classes de exceção
Os programas podem lançar uma classe de exceção predefinida no namespace System
(exceto quando observado anteriormente) ou criar suas próprias classes de exceção,
derivando de Exception. As classes derivadas devem definir pelo menos três
construtores: um construtor sem parâmetros, um que define a propriedade de
mensagem e um que define as propriedades Message e InnerException. Por exemplo:
C#
[Serializable]
public class InvalidDepartmentException : Exception
public InvalidDepartmentException() : base() { }
public InvalidDepartmentException(string message) : base(message) { }
public InvalidDepartmentException(string message, Exception inner) :
base(message, inner) { }
Adicione novas propriedades à classe de exceção quando os dados que elas fornecem
forem úteis para resolver a exceção. Se forem adicionadas novas propriedades à classe
de exceção derivada, ToString() deverá ser substituído para retornar as informações
adicionadas.
Especificação da Linguagem C#
Para obter mais informações, veja Exceções e A declaração throw na Especificação da
Linguagem C#. A especificação da linguagem é a fonte definitiva para a sintaxe e o uso
de C#.
Confira também
Hierarquia de exceções
Exceções geradas pelo compilador
Artigo • 01/06/2023
Algumas exceções são geradas automaticamente pelo runtime do .NET quando
operações básicas falham. Essas exceções e suas condições de erro são listadas na
tabela a seguir.
Exceção Descrição
ArithmeticException Uma classe base para exceções que ocorrem durante operações
aritméticas, tais como DivideByZeroException e
OverflowException.
ArrayTypeMismatchException Gerada quando uma matriz não pode armazenar determinado
elemento porque o tipo real do elemento é incompatível com o
tipo real da matriz.
DivideByZeroException Lançada quando é feita uma tentativa de dividir um valor inteiro
por zero.
IndexOutOfRangeException Lançada quando é feita uma tentativa de indexar uma matriz
quando o índice é menor que zero ou fora dos limites da matriz.
InvalidCastException Gerada quando uma conversão explícita de um tipo base para
uma interface ou um tipo derivado falha em tempo de execução.
NullReferenceException Gerada quando há uma tentativa de fazer referência a um objeto
cujo valor é null.
OutOfMemoryException Lançada quando uma tentativa de alocar memória usando o
operador new falha. Essa exceção indica que a memória
disponível para o Common Language Runtime está esgotada.
OverflowException Lançada quando uma operação aritmética em um contexto
checked estoura.
StackOverflowException Lançada quando a pilha de execução acaba tendo muitas
chamadas de método pendentes, normalmente indica uma
recursão muito profunda ou infinita.
TypeInitializationException Lançada quando um construtor estático lança uma exceção e não
há nenhuma cláusula catch compatível para capturá-la.
Confira também
Instruções para manipulação de exceções
Regras e convenções de nomenclatura
do identificador C#
Artigo • 10/05/2023
Um identificador é o nome que você atribui a um tipo (classe, interface, struct, registro,
delegado ou enumerado), membro, variável ou namespace.
Regras de nomenclatura
Os identificadores válidos devem seguir estas regras:
Os identificadores devem começar com letra ou sublinhado ( _ ).
Os identificadores podem conter caracteres de letra Unicode, caracteres de dígito
decimal, caracteres de conexão Unicode, caracteres de combinação Unicode ou
caracteres de formatação Unicode. Para obter mais informações sobre as
categorias Unicode, consulte o Banco de dados da categoria Unicode .
É possível
declarar identificadores que correspondem às palavras-chave em C# usando o
prefixo @ no identificador. O @ não faz parte do nome do identificador. Por
exemplo, @if declara um identificador chamado if . Esses identificadores textuais
são destinados principalmente para interoperabilidade com os identificadores
declarados em outras linguagens.
Para obter uma definição completa de identificadores válidos, consulte o tópico de
identificadores na especificação da linguagem C#.
Convenções de nomenclatura
Além das regras, há muitas convenções de nomenclatura de identificador usadas em
toda as APIs do .NET. Por convenção, os programas C# usam PascalCase para nomes de
tipo, namespaces e todos os membros públicos. Além disso, as seguintes convenções
são comuns:
Os nomes de interface começam com I maiúsculo.
Os tipos de atributo terminam com a palavra Attribute .
Os tipos enumerados usam um substantivo singular para não sinalizadores e um
substantivo plural para sinalizadores.
Os identificadores não devem conter dois caracteres de sublinhado ( _ )
consecutivos. Esses nomes estão reservados para identificadores gerados por
compilador.
Para saber mais, confira Convenções de nomenclatura.
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#
Referência de C#
Classes
Tipos de estrutura
Namespaces
Interfaces
Representantes
Convenções de codificação em C#
Artigo • 14/03/2023
As convenções de codificação atendem às seguintes finalidades:
" Criam uma aparência consistente para o código, para que os leitores possam se
concentrar no conteúdo e não no layout.
" Permitem que os leitores entendam o código com mais rapidez, fazendo
suposições com base na experiência anterior.
" Facilitam a cópia, a alteração e a manutenção do código.
" Demonstram as práticas recomendadas do C#.
) Importante
As diretrizes neste artigo são usadas pela Microsoft para desenvolver amostras e
documentação. Elas foram adotadas das diretrizes de Estilo de Codificação do C#
do .NET Runtime . Você pode usá-las ou adaptá-las às suas necessidades. Os
principais objetivos são consistência e legibilidade no código-fonte do projeto, da
equipe, da organização ou da empresa.
Convenções de nomenclatura
Há várias convenções de nomenclatura a serem consideradas ao gravar o código do C#.
Nos exemplos a seguir, qualquer uma das diretrizes relativas aos elementos marcados
public também é aplicável ao trabalhar com os elementos protected e protected
internal . Todos eles se destinam a ficar visíveis para chamadores externos.
Pascal Case
Use Pascal Case ("PascalCasing") ao nomear um class , record ou struct .
C#
public class DataService
C#
public record PhysicalAddress(
string Street,
string City,
string StateOrProvince,
string ZipCode);
C#
public struct ValueCoordinate
Ao nomear um interface , use Pascal Case, além de aplicar ao nome um prefixo I . Isso
indica claramente aos consumidores que é um interface .
C#
public interface IWorkerQueue
Ao nomear membros de tipos public , como campos, propriedades, eventos, métodos e
funções locais, use Pascal Case.
C#
public class ExampleEvents
// A public field, these should be used sparingly
public bool IsValid;
// An init-only property
public IWorkerQueue WorkerQueue { get; init; }
// An event
public event Action EventProcessing;
// Method
public void StartEventProcessing()
// Local function
static int CountQueueItems() => WorkerQueue.Count;
// ...
Ao gravar registros posicionais, use Pascal Case para parâmetros, pois são as
propriedades públicas do registro.
C#
public record PhysicalAddress(
string Street,
string City,
string StateOrProvince,
string ZipCode);
Para obter mais informações sobre registros posicionais, confira Sintaxe posicional para
definição de propriedade.
Camel Case
Use Camel Case ("camelCasing") ao nomear os campos private ou internal e aplique
a eles o prefixo _ .
C#
public class DataService
private IWorkerQueue _workerQueue;
Dica
Ao editar o código do C# que segue essas convenções de nomenclatura em um
IDE que permite a conclusão da instrução, digitar _ mostrará todos os membros no
escopo do objeto.
Ao trabalhar com os campos static que são private ou internal , use o prefixo s_ e,
para thread estático, use t_ .
C#
public class DataService
private static IWorkerQueue s_workerQueue;
[ThreadStatic]
private static TimeSpan t_timeSpan;
Ao gravar os parâmetros de método, use Camel Case.
C#
public T SomeMethod<T>(int someNumber, bool isValid)
Para obter mais informações sobre as convenções de nomenclatura do C#, confira Estilo
de Codificação em C# .
Outras convenções de nomenclatura
Em exemplos que não incluem o uso de diretivas, use as qualificações do
namespace. Se você souber que um namespace é importado por padrão em um
projeto, não precisará qualificar totalmente os nomes desse namespace. Nomes
qualificados podem ser interrompidos após um ponto (.) se forem muito longos
para uma única linha, conforme mostrado no exemplo a seguir.
C#
var currentPerformanceCounterCategory = new System.Diagnostics.
PerformanceCounterCategory();
Não é necessário alterar os nomes de objetos que foram criados usando as
ferramentas de designer do Visual Studio para adequá-los a outras diretrizes.
Convenções de layout
Um bom layout usa formatação para enfatizar a estrutura do código e para facilitar a
leitura de código. Exemplos e amostras Microsoft estão em conformidade com as
seguintes convenções:
Use as configurações padrão do Editor de códigos (recuo inteligente, recuos de
quatro caracteres, guias salvas como espaços). Para obter mais informações,
consulte Opções, Editor de Texto, C#, Formatação.
Gravar apenas uma instrução por linha.
Gravar apenas uma declaração por linha.
Se as linhas de continuação não devem recuar automaticamente, recue-as uma
tabulação (quatro espaços).
Adicione pelo menos uma linha em branco entre as definições de método e de
propriedade.
Use parênteses para criar cláusulas em uma expressão aparente, conforme
mostrado no código a seguir.
C#
if ((val1 > val2) && (val1 > val3))
// Take appropriate action.
Coloque as diretivas “using” fora da declaração
de namespace
Quando uma diretiva using está fora de uma declaração de namespace, esse
namespace importado é seu nome totalmente qualificado. Assim fica mais claro.
Quando a diretiva using está dentro do namespace, ela pode ser referente a esse
namespace ou ao nome totalmente qualificado. Assim fica ambíguo.
C#
using Azure;
namespace CoolStuff.AwesomeFeature
public class Awesome
public void Stuff()
WaitUntil wait = WaitUntil.Completed;
Supondo que haja uma referência (direta ou indireta) à classe WaitUntil.
Agora, vamos fazer uma pequena alteração:
C#
namespace CoolStuff.AwesomeFeature
using Azure;
public class Awesome
public void Stuff()
WaitUntil wait = WaitUntil.Completed;
E ela será compilada hoje. E amanhã. Porém, em algum momento da próxima semana,
esse código (intocado) falhará com dois erros:
Console
- error CS0246: The type or namespace name 'WaitUntil' could not be found
(are you missing a using directive or an assembly reference?)
- error CS0103: The name 'WaitUntil' does not exist in the current context
Uma das dependências introduziu esta classe em um namespace e termina com .Azure :
C#
namespace CoolStuff.Azure
public class SecretsManagement
public string FetchFromKeyVault(string vaultId, string secretId) {
return null; }
Uma diretiva using colocada dentro de um namespace diferencia contexto e complica a
resolução do nome. Neste exemplo, é o primeiro namespace que ela encontra.
CoolStuff.AwesomeFeature.Azure
CoolStuff.Azure
Azure
Adicionar um novo namespace que corresponda a CoolStuff.Azure ou
CoolStuff.AwesomeFeature.Azure seria combinado antes do namespace Azure global.
Você poderia resolver isso adicionando o modificador global:: à declaração using . No
entanto, é mais fácil colocar declarações using fora do namespace.
C#
namespace CoolStuff.AwesomeFeature
using global::Azure;
public class Awesome
public void Stuff()
WaitUntil wait = WaitUntil.Completed;
Convenções de comentário
Coloque o comentário em uma linha separada, não no final de uma linha de
código.
Comece o texto do comentário com uma letra maiúscula.
Termine o texto do comentário com um ponto final.
Insira um espaço entre o delimitador de comentário (/ /) e o texto do comentário,
conforme mostrado no exemplo a seguir.
C#
// The following declaration creates a query. It does not run
// the query.
Não crie blocos de asteriscos formatados em torno dos comentários.
Verifique se todos os membros públicos têm os comentários XML necessários,
fornecendo as devidas descrições sobre o comportamento.
Diretrizes de linguagem
As seções a seguir descrevem práticas que a equipe de C# segue para preparar
exemplos e amostras do código.
Tipos de dados de cadeia de caracteres
Use a interpolação de cadeia de caracteres para concatenar cadeias de caracteres
curtas, como é mostrado no código a seguir.
C#
string displayName = $"{nameList[n].LastName},
{nameList[n].FirstName}";
Para acrescentar cadeias de caracteres em loops, especialmente quando você
estiver trabalhando com grandes quantidades de texto, use um objeto
StringBuilder.
C#
var phrase =
"lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala";
var manyPhrases = new StringBuilder();
for (var i = 0; i < 10000; i++)
manyPhrases.Append(phrase);
//Console.WriteLine("tra" + manyPhrases);
Variáveis locais de tipo implícito
Use a digitação implícita para variáveis locais quando o tipo da variável for óbvio
do lado direito da atribuição ou quando o tipo exato não for importante.
C#
var var1 = "This is clearly a string.";
var var2 = 27;
Não use var quando o tipo não estiver aparente no 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 ou uma conversão explícita.
C#
int var3 = Convert.ToInt32(Console.ReadLine());
int var4 = ExampleClass.ResultSoFar();
Não se baseie no nome da variável para especificar o tipo dela. Ele pode não estar
correto. No exemplo a seguir, o nome da variável inputInt está equivocado. É
uma cadeia de caracteres.
C#
var inputInt = Console.ReadLine();
Console.WriteLine(inputInt);
Evite o uso de var em vez de dynamic. Use dynamic quando quiser uma inferência
de tipo em tempo de execução. Para obter mais informações, confira Como usar o
tipo dinâmico (Guia de Programação do C#).
Use a digitação implícita para determinar o tipo da variável em loop nos loops for.
O exemplo a seguir usa digitação implícita em uma instrução for .
C#
var phrase =
"lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala";
var manyPhrases = new StringBuilder();
for (var i = 0; i < 10000; i++)
manyPhrases.Append(phrase);
//Console.WriteLine("tra" + manyPhrases);
Não use a digitação implícita para determinar o tipo da variável em loop nos loops
foreach. Na maioria dos casos, o tipo de elementos na coleção não é
imediatamente evidente. O nome da coleção não deve ser usado apenas para
inferir o tipo dos elementos.
O exemplo a seguir usa digitação explícita em uma instrução foreach .
C#
foreach (char ch in laugh)
if (ch == 'h')
Console.Write("H");
else
Console.Write(ch);
Console.WriteLine();
7 Observação
Tenha cuidado para não alterar acidentalmente um tipo de elemento da
coleção iterável. Por exemplo, é fácil alternar de System.Linq.IQueryable para
System.Collections.IEnumerable em uma instrução foreach , o que altera a
execução de uma consulta.
Tipos de dados não assinados
Em geral, use int em vez de tipos sem assinatura. O uso de int é comum em todo o
C#, e é mais fácil interagir com outras bibliotecas ao usar int .
Matrizes
Use a sintaxe concisa ao inicializar matrizes na linha da declaração. No exemplo a seguir,
observe que você não pode usar var em vez de string[] .
C#
string[] vowels1 = { "a", "e", "i", "o", "u" };
Se você usar uma instanciação explícita, poderá usar var .
C#
var vowels2 = new string[] { "a", "e", "i", "o", "u" };
Delegados
Use Func<> e Action<>, em vez de definir tipos delegados. Em uma classe, defina o
método delegado.
C#
public static Action<string> ActionExample1 = x => Console.WriteLine($"x is:
{x}");
public static Action<string, string> ActionExample2 = (x, y) =>
Console.WriteLine($"x is: {x}, y is {y}");
public static Func<string, int> FuncExample1 = x => Convert.ToInt32(x);
public static Func<int, int, int> FuncExample2 = (x, y) => x + y;
Chame o método usando a assinatura definida pelo delegado Func<> ou Action<> .
C#
ActionExample1("string for x");
ActionExample2("string for x", "string for y");
Console.WriteLine($"The value is {FuncExample1("1")}");
Console.WriteLine($"The sum is {FuncExample2(1, 2)}");
Se você criar instâncias de um tipo delegado, use a sintaxe concisa. Em uma classe,
defina o tipo delegado e um método que tenha uma assinatura correspondente.
C#
public delegate void Del(string message);
public static void DelMethod(string str)
Console.WriteLine("DelMethod argument: {0}", str);
Crie uma instância do tipo delegado e a chame. A declaração a seguir mostra a sintaxe
condensada.
C#
Del exampleDel2 = DelMethod;
exampleDel2("Hey");
A declaração a seguir usa a sintaxe completa.
C#
Del exampleDel1 = new Del(DelMethod);
exampleDel1("Hey");
try-catch
Instruções e using no tratamento de exceções
Use uma instrução try-catch para a maioria da manipulação de exceções.
C#
static string GetValueFromArray(string[] array, int index)
try
return array[index];
catch (System.IndexOutOfRangeException ex)
Console.WriteLine("Index is out of range: {0}", index);
throw;
Simplifique o código usando a instrução using do #C. Se você tiver uma instrução
try-finally na qual o único código do bloco finally é uma chamada para o
método Dispose, use, em vez disso, uma instrução using .
No exemplo a seguir, a instrução try-finally chama apenas Dispose no bloco
finally .
C#
Font font1 = new Font("Arial", 10.0f);
try
byte charset = font1.GdiCharSet;
finally
if (font1 != null)
((IDisposable)font1).Dispose();
Você pode fazer a mesma coisa com uma instrução using .
C#
using (Font font2 = new Font("Arial", 10.0f))
byte charset2 = font2.GdiCharSet;
Use a nova sintaxe using que não requer chaves:
C#
using Font font3 = new Font("Arial", 10.0f);
byte charset3 = font3.GdiCharSet;
Operadores && e ||
Para evitar exceções e melhorar o desempenho ignorando comparações desnecessárias,
use &&, em vez de &, e ||, em vez de |, ao executar comparações, conforme mostrado
no exemplo a seguir.
C#
Console.Write("Enter a dividend: ");
int dividend = Convert.ToInt32(Console.ReadLine());
Console.Write("Enter a divisor: ");
int divisor = Convert.ToInt32(Console.ReadLine());
if ((divisor != 0) && (dividend / divisor > 0))
Console.WriteLine("Quotient: {0}", dividend / divisor);
else
Console.WriteLine("Attempted division by 0 ends up here.");
Se o divisor for 0, a segunda cláusula na instrução if causará um erro em tempo de
execução. Mas o operador && entra em curto-circuito quando a primeira expressão é
falsa. Ou seja, ele não avalia a segunda expressão. O operador & avalia ambas,
resultando em um erro em tempo de execução quando divisor é 0.
Operador new
Use uma das formas concisas de instanciação de objeto, conforme mostrado nas
declarações a seguir. O segundo exemplo mostra a sintaxe disponível a partir do
C# 9.
C#
var instance1 = new ExampleClass();
C#
ExampleClass instance2 = new();
As declarações anteriores são equivalentes à declaração a seguir.
C#
ExampleClass instance2 = new ExampleClass();
Use inicializadores de objeto para simplificar a criação de objeto, conforme
mostrado no exemplo a seguir.
C#
var instance3 = new ExampleClass { Name = "Desktop", ID = 37414,
Location = "Redmond", Age = 2.3 };
O exemplo a seguir define as mesmas propriedades do exemplo anterior, mas não
usa inicializadores.
C#
var instance4 = new ExampleClass();
instance4.Name = "Desktop";
instance4.ID = 37414;
instance4.Location = "Redmond";
instance4.Age = 2.3;
Manipulação de eventos
Se você estiver definindo um manipulador de eventos que não necessita ser removido
posteriormente, use uma expressão lambda.
C#
public Form2()
this.Click += (s, e) =>
MessageBox.Show(
((MouseEventArgs)e).Location.ToString());
};
A expressão lambda reduz a definição convencional a seguir.
C#
public Form1()
this.Click += new EventHandler(Form1_Click);
void Form1_Click(object? sender, EventArgs e)
MessageBox.Show(((MouseEventArgs)e).Location.ToString());
Membros estáticos
Chame membros estáticos usando o nome de classe: ClassName.StaticMember. Essa
prática torna o código mais legível, tornando o acesso estático limpo. Não qualifique
um membro estático definido em uma classe base com o nome de uma classe derivada.
Enquanto esse código é compilado, a leitura do código fica incorreta e o código poderá
ser danificado no futuro se você adicionar um membro estático com o mesmo nome da
classe derivada.
Consultas LINQ
Use nomes significativos para variáveis de consulta. O exemplo a seguir usa
seattleCustomers para os clientes que estão localizados em Seattle.
C#
var seattleCustomers = from customer in customers
where customer.City == "Seattle"
select customer.Name;
Use aliases para se certificar de que os nomes de propriedades de tipos anônimos
sejam colocados corretamente em maiúsculas, usando o padrão Pascal-Case.
C#
var localDistributors =
from customer in customers
join distributor in distributors on customer.City equals
distributor.City
select new { Customer = customer, Distributor = distributor };
Renomeie propriedades quando os nomes de propriedades no resultado forem
ambíguos. Por exemplo, se a sua consulta retornar um nome de cliente e uma ID
de distribuidor, em vez de deixá-los como Name e ID no resultado, renomeie-os
para esclarecer que Name é o nome de um cliente, e ID é a identificação de um
distribuidor.
C#
var localDistributors2 =
from customer in customers
join distributor in distributors on customer.City equals
distributor.City
select new { CustomerName = customer.Name, DistributorID =
distributor.ID };
Usa a digitação implícita na declaração de variáveis de consulta e de intervalo.
C#
var seattleCustomers = from customer in customers
where customer.City == "Seattle"
select customer.Name;
Alinhe as cláusulas de consulta na cláusula from, conforme mostrado nos
exemplos anteriores.
Use as cláusulas where antes de outras cláusulas de consulta, para garantir que as
cláusulas de consulta posteriores operem no conjunto de dados filtrado e
reduzido.
C#
var seattleCustomers2 = from customer in customers
where customer.City == "Seattle"
orderby customer.Name
select customer;
Use várias cláusulas from , em vez de uma cláusula join, para acessar as coleções
internas. Por exemplo, cada coleção de objetos Student pode conter um conjunto
de pontuações no teste. Quando a próxima consulta for executada, ela retorna
cada pontuação que seja acima de 90, juntamente com o sobrenome do estudante
que recebeu a pontuação.
C#
var scoreQuery = from student in students
from score in student.Scores!
where score > 90
select new { Last = student.LastName, score };
Segurança
Siga as diretrizes em Diretrizes de codificação segura.
Confira também
Diretrizes de codificação de runtime do .NET
Convenções de codificação do Visual Basic
Diretrizes de codificação segura
Como exibir argumentos de linha de
comando
Artigo • 08/06/2023
Os argumentos fornecidos a um executável na linha de comando são acessíveis em
instruções de nível superior ou por meio de um parâmetro opcional para Main . Os
argumentos são fornecidos na forma de uma matriz de cadeias de caracteres. Cada
elemento da matriz contém um argumento. O espaço em branco entre os argumentos é
removido. Por exemplo, considere essas invocações de linha de comando de um
executável fictício:
Entrada na linha de comando Matriz de cadeias de caracteres passada a Main
executável.exe a b c "a"
"b"
"c"
executável.exe um dois "um"
"dois"
executável.exe "um dois" três "one two"
"three"
7 Observação
Quando estiver executando um aplicativo no Visual Studio, você pode especificar
argumentos de linha de comando na Página de depuração, Designer de Projeto.
Exemplo
Este exemplo exibe os argumentos de linha de comando passados a um aplicativo de
linha de comando. A saída mostrada é para a primeira entrada da tabela acima.
C#
// The Length property provides the number of array elements.
Console.WriteLine($"parameter count = {args.Length}");
for (int i = 0; i < args.Length; i++)
Console.WriteLine($"Arg[{i}] = [{args[i]}]");
/* Output (assumes 3 cmd line args):
parameter count = 3
Arg[0] = [a]
Arg[1] = [b]
Arg[2] = [c]
*/
Confira também
Visão geral de System.CommandLine
Tutorial: introdução a System.CommandLine
Explorar programação orientada a
objeto com classes e objetos
Artigo • 02/06/2023
Neste tutorial, você vai compilar um aplicativo de console e ver os recursos básicos
orientados a objeto que fazem parte da linguagem C#.
Pré-requisitos
Recomendamos o Visual Studio para Windows ou Mac. Você pode baixar uma
versão gratuita na página de downloads do Visual Studio . O Visual Studio inclui
o SDK do .NET.
Você também pode usar o editor do Visual Studio Code . Será preciso instalar o
SDK do .NET mais recente separadamente.
Se preferir outro editor, você precisará instalar o SDK do .NET mais recente.
Criar o aplicativo
Usando uma janela de terminal, crie um diretório chamado classes. Você compilará o
aplicativo nesse diretório. Altere para esse diretório e digite dotnet new console na
janela do console. Esse comando cria o aplicativo. Abra Program.cs. Ele deverá ser
parecido com:
C#
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
Neste tutorial, você criará novos tipos que representam uma conta bancária.
Normalmente, os desenvolvedores definem cada classe em um arquivo de texto
diferente. Isso facilita o gerenciamento à medida que o tamanho do programa aumenta.
Crie um novo arquivo chamado BankAccount.cs no diretório Classes.
Esse arquivo conterá a definição de uma conta bancária. A programação Orientada a
Objetos organiza o código por meio da criação de tipos na forma de classes. Essas
classes contêm o código que representa uma entidade específica. A classe BankAccount
representa uma conta bancária. O código implementa operações específicas por meio
de métodos e propriedades. Neste tutorial, a conta bancária dá suporte a este
comportamento:
1. Ela tem um número com 10 dígitos que identifica exclusivamente a conta bancária.
2. Ela tem uma cadeia de caracteres que armazena o nome ou os nomes dos
proprietários.
3. O saldo pode ser recuperado.
4. Ela aceita depósitos.
5. Ele aceita saques.
6. O saldo inicial deve ser positivo.
7. Os saques não podem resultar em um saldo negativo.
Definir o tipo de conta bancária
Você pode começar criando as noções básicas de uma classe que define esse
comportamento. Crie um novo arquivo usando o comando File:New. Nomeie-o
bankAccount.cs. Adicione o seguinte código ao arquivo BankAccount.cs:
C#
namespace Classes;
public class BankAccount
public string Number { get; }
public string Owner { get; set; }
public decimal Balance { get; }
public void MakeDeposit(decimal amount, DateTime date, string note)
public void MakeWithdrawal(decimal amount, DateTime date, string note)
Antes de continuar, vamos dar uma olhada no que você compilou. A declaração
namespace fornece uma maneira de organizar logicamente seu código. Este tutorial é
relativamente pequeno, portanto, você colocará todo o código em um namespace.
public class BankAccount define a classe ou o tipo que você está criando. Tudo que
vem dentro de { e } logo após a declaração da classe define o estado e o
comportamento da classe. Há cinco membros na classe BankAccount . Os três primeiros
são propriedades. Propriedades são elementos de dados que podem ter um código que
impõe a validação ou outras regras. Os dois últimos são métodos. Os métodos são
blocos de código que executam uma única função. A leitura dos nomes de cada um dos
membros deve fornecer informações suficientes para você, ou outro desenvolvedor,
entender o que a classe faz.
Abrir uma nova conta
O primeiro recurso a ser implementado serve para abrir uma conta bancária. Quando
um cliente abre uma conta, ele deve fornecer um saldo inicial e informações sobre o
proprietário, ou proprietários, dessa conta.
A criação de novo objeto do tipo BankAccount significa a definição de um construtor
que atribui esses valores. Um construtor é um membro que tem o mesmo nome da
classe. Ele é usado para inicializar objetos desse tipo de classe. Adicione o seguinte
construtor ao tipo BankAccount . Insira o código a seguir acima da declaração de
MakeDeposit :
C#
public BankAccount(string name, decimal initialBalance)
this.Owner = name;
this.Balance = initialBalance;
O código anterior identifica as propriedades do objeto que está sendo construído,
incluindo o qualificador this . Esse qualificador geralmente é opcional e omitido. Você
também poderia ter escrito:
C#
public BankAccount(string name, decimal initialBalance)
Owner = name;
Balance = initialBalance;
O qualificador this só é necessário quando uma variável ou parâmetro locais têm o
mesmo nome desse campo ou propriedade. O qualificador this é omitido durante
todo o restante deste artigo, a menos que seja necessário.
Construtores são chamados quando você cria um objeto usando new. Substitua a linha
Console.WriteLine("Hello World!"); no arquivo Program.cs pelo seguinte código
(substitua <name> pelo seu nome):
C#
using Classes;
var account = new BankAccount("<name>", 1000);
Console.WriteLine($"Account {account.Number} was created for {account.Owner}
with {account.Balance} initial balance.");
Vamos executar o que você construiu até agora. Se você estiver usando o Visual Studio,
selecione Iniciar sem depuração no menu Depurar. Se estiver usando uma linha de
comando, digite dotnet run no diretório em que criou seu projeto.
Você notou que o número da conta está em branco? É hora de corrigir isso. O número
da conta deve ser atribuído na construção do objeto. Mas não é responsabilidade do
chamador criá-lo. O código da classe BankAccount deve saber como atribuir novos
números de conta. Uma maneira simples de fazer isso é começar com um número de 10
dígitos. Incremente-o à medida que novas contas são criadas. Por fim, armazene o
número da conta atual quando um objeto for construído.
Adicione uma declaração de membro à classe BankAccount . Coloque a seguinte linha de
código após a chave de abertura { no início da classe BankAccount :
C#
private static int accountNumberSeed = 1234567890;
O accountNumberSeed é um membro de dados. Ele é private , o que significa que ele só
pode ser acessado pelo código dentro da classe BankAccount . É uma maneira de separar
as responsabilidades públicas (como ter um número de conta) da implementação
privada (como os números de conta são gerados). Ele também é static , o que significa
que é compartilhado por todos os objetos BankAccount . O valor de uma variável não
estática é exclusivo para cada instância do objeto BankAccount . Adicione as duas linhas a
seguir ao construtor para atribuir o número da conta. Coloque-as depois da linha que
diz this.Balance = initialBalance :
C#
this.Number = accountNumberSeed.ToString();
accountNumberSeed++;
Digite dotnet run para ver os resultados.
Criar depósitos e saques
A classe da conta bancária precisa aceitar depósitos e saques para funcionar
corretamente. Vamos implementar depósitos e saques criando um diário de todas as
transações da conta. Rastrear todas as transações tem algumas vantagens em
comparação à simples atualização do saldo em cada transação. O histórico pode ser
usado para auditar todas as transações e gerenciar os saldos diários. Calcular o saldo do
histórico de todas as transações quando necessário assegura que todos os erros
corrigidos em uma única transação serão refletidos corretamente no saldo no próximo
cálculo.
Vamos começar criando um novo tipo para representar uma transação. Essa transação é
um tipo simples que não tem qualquer responsabilidade. Ele precisa de algumas
propriedades. Crie um novo arquivo chamado Transaction.cs. Adicione os seguintes
códigos a ela:
C#
namespace Classes;
public class Transaction
public decimal Amount { get; }
public DateTime Date { get; }
public string Notes { get; }
public Transaction(decimal amount, DateTime date, string note)
Amount = amount;
Date = date;
Notes = note;
Agora, vamos adicionar um List<T> de Transaction objetos à classe BankAccount .
Adicione a seguinte declaração após o construtor no arquivo BankAccount.cs:
C#
private List<Transaction> allTransactions = new List<Transaction>();
Agora, vamos calcular corretamente o Balance . O saldo atual pode ser encontrado
somando os valores de todas as transações. De acordo com a forma atual do código,
você só pode obter o saldo inicial da conta. Portanto, você terá que atualizar a
propriedade Balance . Substitua a linha public decimal Balance { get; } em
BankAccount.cs pelo seguinte código:
C#
public decimal Balance
get
decimal balance = 0;
foreach (var item in allTransactions)
balance += item.Amount;
return balance;
Este exemplo mostra um aspecto importante das propriedades. Agora, você está
calculando o saldo quando outro programador solicita o valor. Seu cálculo enumera
todas as transações e fornece a soma como o saldo atual.
Depois, implemente os métodos MakeDeposit e MakeWithdrawal . Esses métodos vão
impor as duas últimas regras: que o saldo inicial deve ser positivo, e que qualquer saque
não pode criar um saldo negativo.
Essas regras introduzem o conceito de exceções. A forma padrão de indicar que um
método não pode concluir seu trabalho com êxito é lançar uma exceção. O tipo de
exceção e a mensagem associada a ele descrevem o erro. Aqui, o método MakeDeposit
lançará uma exceção se o valor do depósito for menor ou igual a 0. O método
MakeWithdrawal lançará uma exceção se o valor do saque for menor ou igual a 0 ou se a
aplicação do saque resultar em um saldo negativo. Adicione o seguinte código depois
da declaração da lista allTransactions :
C#
public void MakeDeposit(decimal amount, DateTime date, string note)
if (amount <= 0)
throw new ArgumentOutOfRangeException(nameof(amount), "Amount of
deposit must be positive");
var deposit = new Transaction(amount, date, note);
allTransactions.Add(deposit);
}
public void MakeWithdrawal(decimal amount, DateTime date, string note)
if (amount <= 0)
throw new ArgumentOutOfRangeException(nameof(amount), "Amount of
withdrawal must be positive");
if (Balance - amount < 0)
throw new InvalidOperationException("Not sufficient funds for this
withdrawal");
var withdrawal = new Transaction(-amount, date, note);
allTransactions.Add(withdrawal);
A instrução throwgera uma exceção. A execução do bloco atual é encerrada e o controle
transferido para o bloco catch da primeira correspondência encontrado na pilha de
chamadas. Você adicionará um bloco catch para testar esse código um pouco mais
tarde.
O construtor deve receber uma alteração para que adicione uma transação inicial, em
vez de atualizar o saldo diretamente. Como você já escreveu o método MakeDeposit ,
chame-o de seu construtor. O construtor concluído deve ter esta aparência:
C#
public BankAccount(string name, decimal initialBalance)
Number = accountNumberSeed.ToString();
accountNumberSeed++;
Owner = name;
MakeDeposit(initialBalance, DateTime.Now, "Initial balance");
DateTime.Now é uma propriedade que retorna a data e a hora atuais. Teste esse código
adicionando alguns depósitos e saques em seu método Main , seguindo o código que
cria um novo BankAccount :
C#
account.MakeWithdrawal(500, DateTime.Now, "Rent payment");
Console.WriteLine(account.Balance);
account.MakeDeposit(100, DateTime.Now, "Friend paid me back");
Console.WriteLine(account.Balance);
Em seguida, teste se você está recebendo condições de erro ao tentar criar uma conta
com um saldo negativo. Adicione o seguinte código após o código anterior que você
acabou de adicionar:
C#
// Test that the initial balances must be positive.
BankAccount invalidAccount;
try
invalidAccount = new BankAccount("invalid", -55);
catch (ArgumentOutOfRangeException e)
Console.WriteLine("Exception caught creating account with negative
balance");
Console.WriteLine(e.ToString());
return;
Use a instrução try-catch para marcar um bloco de código que possa gerar exceções e
detectar esses erros esperados. Use a mesma técnica a fim de testar o código que gera
uma exceção para um saldo negativo. Adicione o seguinte código antes da declaração
de invalidAccount no seu método Main :
C#
// Test for a negative balance.
try
account.MakeWithdrawal(750, DateTime.Now, "Attempt to overdraw");
catch (InvalidOperationException e)
Console.WriteLine("Exception caught trying to overdraw");
Console.WriteLine(e.ToString());
Salve o arquivo e digite dotnet run para testá-lo.
Desafio – registrar em log todas as transações
Para concluir este tutorial, escreva o método GetAccountHistory que cria um string
para o histórico de transações. Adicione esse método ao tipo BankAccount :
C#
public string GetAccountHistory()
var report = new System.Text.StringBuilder();
decimal balance = 0;
report.AppendLine("Date\t\tAmount\tBalance\tNote");
foreach (var item in allTransactions)
balance += item.Amount;
report.AppendLine($"
{item.Date.ToShortDateString()}\t{item.Amount}\t{balance}\t{item.Notes}");
return report.ToString();
O histórico usa a classe StringBuilder para formatar uma cadeia de caracteres que
contém uma linha para cada transação. Você viu o código de formatação da cadeia de
caracteres anteriormente nesses tutoriais. Um caractere novo é \t . Ele insere uma guia
para formatar a saída.
Adicione esta linha para testá-la no Program.cs:
C#
Console.WriteLine(account.GetAccountHistory());
Execute o programa para ver os resultados.
Próximas etapas
Se você não conseguir avançar, veja a origem deste tutorial em nosso repositório
GitHub .
Você pode continuar com o tutorial de programação orientada a objeto.
Saiba mais sobre esses conceitos nestes artigos:
Instruções de seleção
Instruções de iteração
Programação orientada a objeto (C#)
Artigo • 08/06/2023
O C# é uma linguagem de programação orientada a objeto. Os quatro princípios
básicos da programação orientada a objetos são:
Abstração Modelando os atributos e interações relevantes de entidades como
classes para definir uma representação abstrata de um sistema.
Encapsulamento Ocultando o estado interno e a funcionalidade de um objeto e
permitindo apenas o acesso por meio de um conjunto público de funções.
Herança Capacidade de criar novas abstrações com base em abstrações existentes.
Polimorfismo Capacidade de implementar propriedades ou métodos herdados de
diferentes maneiras em várias abstrações.
No tutorial anterior, introdução às classes que você viu abstração e encapsulamento. A
classe BankAccount forneceu uma abstração para o conceito de conta bancária. Você
pode modificar sua implementação sem afetar nenhum dos códigos que usaram a
classe BankAccount . As classes BankAccount e Transaction classes fornecem
encapsulamento dos componentes necessários para descrever esses conceitos no
código.
Neste tutorial, você estenderá esse aplicativo para usar herança e polimorfismo para
adicionar novos recursos. Você também adicionará recursos à BankAccount classe,
aproveitando as técnicas de abstração e encapsulamento que aprendeu no tutorial
anterior.
Criar diferentes tipos de contas
Depois de criar este programa, você recebe solicitações para adicionar recursos a ele.
Funciona muito bem na situação em que há apenas um tipo de conta bancária. Ao
longo do tempo, as necessidades são alteradas e os tipos de conta relacionados são
solicitados:
Uma conta de ganho de juros que acumula juros no final de cada mês.
Uma linha de crédito que pode ter saldo negativo, mas quando há saldo, há
cobrança de juros a cada mês.
Uma conta de cartão presente pré-paga que começa com um único depósito, e só
pode ser paga. Ela pode ser preenchida novamente uma vez no início de cada mês.
Todas essas contas diferentes são semelhantes à classe BankAccount definida no tutorial
anterior. Você pode copiar esse código, renomear as classes e fazer modificações. Essa
técnica funcionaria a curto prazo, mas seria mais trabalho ao longo do tempo. Todas as
alterações seriam copiadas em todas as classes afetadas.
Em vez disso, você pode criar novos tipos de conta bancária que herdam métodos e
dados da classe BankAccount criada no tutorial anterior. Essas novas classes podem
estender a classe BankAccount com o comportamento específico necessário para cada
tipo:
C#
public class InterestEarningAccount : BankAccount
public class LineOfCreditAccount : BankAccount
{
public class GiftCardAccount : BankAccount
Cada uma dessas classes herda o comportamento compartilhado de sua classe base
compartilhada, a classe BankAccount . Escreva as implementações para funcionalidades
novas e diferentes em cada uma das classes derivadas. Essas classes derivadas já têm
todo o comportamento definido na classe BankAccount .
É uma boa prática criar cada nova classe em um arquivo de origem diferente. No Visual
Studio , você pode clicar com o botão direito do mouse no projeto e selecionar
adicionar classe para adicionar uma nova classe em um novo arquivo. No Visual Studio
Code , selecione Arquivo e Novo para criar um novo arquivo de origem. Em qualquer
ferramenta, nomeie o arquivo para corresponder à classe: InterestEarningAccount.cs,
LineOfCreditAccount.cs e GiftCardAccount.cs.
Ao criar as classes, conforme mostrado no exemplo anterior, você descobrirá que
nenhuma das suas classes derivadas é compilada. Um construtor é responsável por
inicializar um objeto. Um construtor de classe derivada deve inicializar a classe derivada
e fornecer instruções sobre como inicializar o objeto de classe base incluído na classe
derivada. A inicialização adequada normalmente ocorre sem nenhum código extra. A
classe BankAccount declara um construtor público com a seguinte assinatura:
C#
public BankAccount(string name, decimal initialBalance)
O compilador não gera um construtor padrão quando você define um construtor por
conta própria. Isso significa que cada classe derivada deve chamar explicitamente esse
construtor. Você declara um construtor que pode passar argumentos para o construtor
de classe base. O código a seguir mostra o construtor para o InterestEarningAccount :
C#
public InterestEarningAccount(string name, decimal initialBalance) :
base(name, initialBalance)
Os parâmetros para esse novo construtor correspondem ao tipo de parâmetro e aos
nomes do construtor de classe base. Use a sintaxe : base() para indicar uma chamada
para um construtor de classe base. Algumas classes definem vários construtores e essa
sintaxe permite que você escolha qual construtor de classe base você chama. Depois de
atualizar os construtores, você pode desenvolver o código para cada uma das classes
derivadas. Os requisitos para as novas classes podem ser declarados da seguinte
maneira:
Uma conta de ganho de juros:
Obterá um crédito de 2% do saldo final do mês.
Uma linha de crédito:
Pode ter um saldo negativo, mas não ser maior em valor absoluto do que o
limite de crédito.
Incorrerá em uma cobrança de juros a cada mês em que o saldo de fim de mês
não seja 0.
Incorrerá em uma taxa em cada saque que ultrapassa o limite de crédito.
Uma conta de cartão presente:
Pode ser preenchido novamente com um valor especificado uma vez por mês,
no último dia do mês.
Você pode ver que todos os três tipos de conta têm uma ação que ocorre no final de
cada mês. No entanto, cada tipo de conta faz tarefas diferentes. Você usa polimorfismo
para implementar esse código. Crie um único método virtual na classe BankAccount :
C#
public virtual void PerformMonthEndTransactions() { }
O código anterior mostra como você usa a palavra-chave virtual para declarar um
método na classe base para o qual uma classe derivada pode fornecer uma
implementação diferente. Um método virtual é um método em que qualquer classe
derivada pode optar por reimplementar. As classes derivadas usam a palavra-chave
override para definir a nova implementação. Normalmente, você se refere a isso como
"substituindo a implementação da classe base". A palavra-chave virtual especifica que
as classes derivadas podem substituir o comportamento. Você também pode declarar
métodos abstract em que classes derivadas devem substituir o comportamento. A
classe base não fornece uma implementação para um método abstract . Em seguida,
você precisa definir a implementação para duas das novas classes que você criou. Inicie
com um InterestEarningAccount :
C#
public override void PerformMonthEndTransactions()
if (Balance > 500m)
{
decimal interest = Balance * 0.05m;
MakeDeposit(interest, DateTime.Now, "apply monthly interest");
Adicione o seguinte código ao LineOfCreditAccount . O código nega o saldo para
calcular uma taxa de juros positiva que é retirada da conta:
C#
public override void PerformMonthEndTransactions()
if (Balance < 0)
// Negate the balance to get a positive interest charge:
decimal interest = -Balance * 0.07m;
MakeWithdrawal(interest, DateTime.Now, "Charge monthly interest");
A classe GiftCardAccount precisa de duas alterações para implementar sua
funcionalidade de fim de mês. Primeiro, modifique o construtor para incluir um valor
opcional a ser adicionado a cada mês:
C#
private readonly decimal _monthlyDeposit = 0m;
public GiftCardAccount(string name, decimal initialBalance, decimal
monthlyDeposit = 0) : base(name, initialBalance)
=> _monthlyDeposit = monthlyDeposit;
O construtor fornece um valor padrão para o valor monthlyDeposit para que os
chamadores possam omitir um 0 sem depósito mensal. Em seguida, substitua o
método PerformMonthEndTransactions para adicionar o depósito mensal, se ele foi
definido como um valor diferente de zero no construtor:
C#
public override void PerformMonthEndTransactions()
if (_monthlyDeposit != 0)
MakeDeposit(_monthlyDeposit, DateTime.Now, "Add monthly deposit");
A substituição aplica o conjunto de depósito mensal no construtor. Adicione o seguinte
código ao método Main para testar essas alterações para GiftCardAccount e
InterestEarningAccount :
C#
var giftCard = new GiftCardAccount("gift card", 100, 50);
giftCard.MakeWithdrawal(20, DateTime.Now, "get expensive coffee");
giftCard.MakeWithdrawal(50, DateTime.Now, "buy groceries");
giftCard.PerformMonthEndTransactions();
// can make additional deposits:
giftCard.MakeDeposit(27.50m, DateTime.Now, "add some additional spending
money");
Console.WriteLine(giftCard.GetAccountHistory());
var savings = new InterestEarningAccount("savings account", 10000);
savings.MakeDeposit(750, DateTime.Now, "save some money");
savings.MakeDeposit(1250, DateTime.Now, "Add more savings");
savings.MakeWithdrawal(250, DateTime.Now, "Needed to pay monthly bills");
savings.PerformMonthEndTransactions();
Console.WriteLine(savings.GetAccountHistory());
Verifique os resultados. Agora, adicione um conjunto semelhante de código de teste
para LineOfCreditAccount :
C#
var lineOfCredit = new LineOfCreditAccount("line of credit", 0);
// How much is too much to borrow?
lineOfCredit.MakeWithdrawal(1000m, DateTime.Now, "Take out monthly
advance");
lineOfCredit.MakeDeposit(50m, DateTime.Now, "Pay back small amount");
lineOfCredit.MakeWithdrawal(5000m, DateTime.Now, "Emergency funds for
repairs");
lineOfCredit.MakeDeposit(150m, DateTime.Now, "Partial restoration on
repairs");
lineOfCredit.PerformMonthEndTransactions();
Console.WriteLine(lineOfCredit.GetAccountHistory());
Ao adicionar o código anterior e executar o programa, você verá algo semelhante ao
seguinte erro:
Console
Unhandled exception. System.ArgumentOutOfRangeException: Amount of deposit
must be positive (Parameter 'amount')
at OOProgramming.BankAccount.MakeDeposit(Decimal amount, DateTime date,
String note) in BankAccount.cs:line 42
at OOProgramming.BankAccount..ctor(String name, Decimal initialBalance)
in BankAccount.cs:line 31
at OOProgramming.LineOfCreditAccount..ctor(String name, Decimal
initialBalance) in LineOfCreditAccount.cs:line 9
at OOProgramming.Program.Main(String[] args) in Program.cs:line 29
7 Observação
A saída real inclui o caminho completo para a pasta com o projeto. Os nomes das
pastas foram omitidos para brevidade. Além disso, dependendo do formato de
código, os números de linha podem ser ligeiramente diferentes.
Esse código falha porque BankAccount pressupõe que o saldo inicial deve ser maior que
0. Outra suposição feita na classe BankAccount é que o saldo não pode ficar negativo.
Em vez disso, qualquer retirada que sobrecarrega a conta é rejeitada. Ambas as
suposições precisam mudar. A linha de conta de crédito começa em 0 e geralmente terá
um saldo negativo. Além disso, se um cliente empresta muito dinheiro, ele incorre em
uma taxa. A transação é aceita, só custa mais. A primeira regra pode ser implementada
adicionando um argumento opcional ao construtor BankAccount que especifica o saldo
mínimo. O padrão é 0 . A segunda regra requer um mecanismo que permite que classes
derivadas modifiquem o algoritmo padrão. De certa forma, a classe base "pergunta" ao
tipo derivado o que deve acontecer quando há um cheque especial. O comportamento
padrão é rejeitar a transação lançando uma exceção.
Vamos começar adicionando um segundo construtor que inclui um parâmetro
minimumBalance opcional. Esse novo construtor faz todas as ações feitas pelo construtor
existente. Além disso, ele define a propriedade de saldo mínimo. Você pode copiar o
corpo do construtor existente, mas isso significa dois locais a serem alterados no futuro.
Em vez disso, você pode usar o encadeamento de construtor para que um construtor
chame outro. O código a seguir mostra os dois construtores e o novo campo adicional:
C#
private readonly decimal _minimumBalance;
public BankAccount(string name, decimal initialBalance) : this(name,
initialBalance, 0) { }
public BankAccount(string name, decimal initialBalance, decimal
minimumBalance)
Number = s_accountNumberSeed.ToString();
s_accountNumberSeed++;
Owner = name;
_minimumBalance = minimumBalance;
if (initialBalance > 0)
MakeDeposit(initialBalance, DateTime.Now, "Initial balance");
O código anterior mostra duas novas técnicas. Primeiro, o campo minimumBalance é
marcado como readonly . Isso significa que o valor não pode ser alterado depois que o
objeto é construído. Depois que BankAccount é criado, minimumBalance não pode alterar.
Em segundo lugar, o construtor que usa dois parâmetros : this(name, initialBalance,
0) { } como implementação. A expressão : this() chama o outro construtor, aquele
com três parâmetros. Essa técnica permite que você tenha uma única implementação
para inicializar um objeto, embora o código do cliente possa escolher um dos muitos
construtores.
Essa implementação chamará MakeDeposit somente se o saldo inicial for maior que 0 .
Isso preserva a regra de que os depósitos devem ser positivos, mas permite que a conta
de crédito abra com um saldo 0 .
Agora que a classe BankAccount tem um campo somente leitura para o saldo mínimo, a
alteração final é alterar o código físico 0 para minimumBalance no método
MakeWithdrawal :
C#
if (Balance - amount < minimumBalance)
Depois de estender a classe BankAccount , você pode modificar o construtor
LineOfCreditAccount para chamar o novo construtor base, conforme mostrado no
seguinte código:
C#
public LineOfCreditAccount(string name, decimal initialBalance, decimal
creditLimit) : base(name, initialBalance, -creditLimit)
Observe que o construtor LineOfCreditAccount altera o sinal do parâmetro creditLimit
para que corresponda ao significado do parâmetro minimumBalance .
Regras de cheque especial diferentes
O último recurso a ser adicionado permite que LineOfCreditAccount cobre uma taxa por
ultrapassar o limite de crédito em vez de recusar a transação.
Uma técnica é definir uma função virtual na qual você implementa o comportamento
necessário. A classe BankAccount refatora o método MakeWithdrawal em dois métodos.
O novo método faz a ação especificada quando o saque leva o saldo abaixo do mínimo.
O método existente MakeWithdrawal tem o seguinte código:
C#
public void MakeWithdrawal(decimal amount, DateTime date, string note)
if (amount <= 0)
throw new ArgumentOutOfRangeException(nameof(amount), "Amount of
withdrawal must be positive");
if (Balance - amount < _minimumBalance)
throw new InvalidOperationException("Not sufficient funds for this
withdrawal");
var withdrawal = new Transaction(-amount, date, note);
allTransactions.Add(withdrawal);
Substitua-o pelo seguinte código:
C#
public void MakeWithdrawal(decimal amount, DateTime date, string note)
if (amount <= 0)
throw new ArgumentOutOfRangeException(nameof(amount), "Amount of
withdrawal must be positive");
Transaction? overdraftTransaction = CheckWithdrawalLimit(Balance -
amount < _minimumBalance);
Transaction? withdrawal = new(-amount, date, note);
_allTransactions.Add(withdrawal);
if (overdraftTransaction != null)
_allTransactions.Add(overdraftTransaction);
protected virtual Transaction? CheckWithdrawalLimit(bool isOverdrawn)
if (isOverdrawn)
throw new InvalidOperationException("Not sufficient funds for this
withdrawal");
else
return default;
}
O método adicionado é protected , o que significa que ele pode ser chamado apenas de
classes derivadas. Essa declaração impede que outros clientes chamem o método. É
também virtual para que classes derivadas possam alterar o comportamento. O tipo
de retorno é Transaction? . A anotação ? indica que o método pode retornar null .
Adicione a seguinte implementação para LineOfCreditAccount cobrar uma taxa quando
o limite de saque for excedido:
C#
protected override Transaction? CheckWithdrawalLimit(bool isOverdrawn) =>
isOverdrawn
? new Transaction(-20, DateTime.Now, "Apply overdraft fee")
: default;
A substituição retorna uma transação de taxa quando a conta é sacada. Se o saque não
ultrapassar o limite, o método retornará uma transação null . Isso indica que não há
nenhuma taxa. Teste essas alterações adicionando o seguinte código ao seu método
Main na classe Program :
C#
var lineOfCredit = new LineOfCreditAccount("line of credit", 0, 2000);
// How much is too much to borrow?
lineOfCredit.MakeWithdrawal(1000m, DateTime.Now, "Take out monthly
advance");
lineOfCredit.MakeDeposit(50m, DateTime.Now, "Pay back small amount");
lineOfCredit.MakeWithdrawal(5000m, DateTime.Now, "Emergency funds for
repairs");
lineOfCredit.MakeDeposit(150m, DateTime.Now, "Partial restoration on
repairs");
lineOfCredit.PerformMonthEndTransactions();
Console.WriteLine(lineOfCredit.GetAccountHistory());
Execute o programa e verifique os resultados.
Resumo
Se você não conseguir avançar, veja a origem deste tutorial em nosso repositório
GitHub .
Este tutorial demonstrou muitas das técnicas usadas na programação Orientada por
objeto:
Você usou Abstração quando definiu classes para cada um dos diferentes tipos de
conta. Essas classes descreveram o comportamento desse tipo de conta.
Você usou Encapsulamento quando manteve muitos detalhes private em cada
classe.
Você usou Herança quando aproveitou a implementação já criada na classe para
salvar o código BankAccount .
Você usou Polimorfismo ao criar métodos virtual que as classes derivadas
poderiam substituir para criar um comportamento específico para esse tipo de
conta.
Herança em C# e .NET
Artigo • 02/06/2023
Este tutorial apresenta a herança em C#. Herança é um recurso das linguagens de
programação orientadas a objeto que permite a definição de uma classe base que, por
sua vez, fornece uma funcionalidade específica (dados e comportamento), e a definição
de classes derivadas que herdam ou substituem essa funcionalidade.
Pré-requisitos
Recomendamos o Visual Studio para Windows ou Mac. Você pode baixar uma
versão gratuita na página de downloads do Visual Studio . O Visual Studio inclui
o SDK do .NET.
Você também pode usar o editor do Visual Studio Code . Será preciso instalar o
SDK do .NET mais recente separadamente.
Se preferir outro editor, você precisará instalar o SDK do .NET mais recente.
Como executar os exemplos
Para criar e executar os exemplos neste tutorial, use o utilitário dotnet na linha de
comando. Execute estas etapas para cada exemplo:
1. Crie um diretório para armazenar o exemplo.
2. Insira o comando dotnet new console no prompt de comando para criar um novo
projeto do .NET Core.
3. Copie e cole o código do exemplo em seu editor de código.
4. Insira o comando dotnet restore na linha de comando para carregar ou restaurar
as dependências do projeto.
Não é necessário executar dotnet restore, pois ele é executado implicitamente por
todos os comandos que exigem uma restauração, como dotnet new , dotnet build ,
dotnet run , dotnet test , dotnet publish e dotnet pack . Para desabilitar a
restauração implícita, use a opção --no-restore .
O comando dotnet restore ainda é útil em determinados cenários em que realizar
uma restauração explícita faz sentido, como compilações de integração contínua
no Azure DevOps Services ou em sistemas de compilação que precisam controlar
explicitamente quando a restauração ocorrerá.
Para obter informações sobre como gerenciar feeds do NuGet, confira a
documentação do dotnet restore.
5. Insira o comando dotnet run para compilar e executar o exemplo.
Informações: O que é a herança?
Herança é um dos atributos fundamentais da programação orientada a objeto. Ela
permite que você defina uma classe filha que reutiliza (herda), estende ou modifica o
comportamento de uma classe pai. A classe cujos membros são herdados é chamada de
classe base. A classe que herda os membros da classe base é chamada de classe
derivada.
C# e .NET oferecem suporte apenas à herança única. Ou seja, uma classe pode herdar
apenas de uma única classe. No entanto, a herança é transitiva, o que permite que você
defina uma hierarquia de herança para um conjunto de tipos. Em outras palavras, o tipo
D pode herdar do tipo C , que herda do tipo B , que herda do tipo de classe base A .
Como a herança é transitiva, os membros do tipo A estão disponíveis ao tipo D .
Nem todos os membros de uma classe base são herdados por classes derivadas. Os
membros a seguir não são herdados:
Construtores estáticos, que inicializam os dados estáticos de uma classe.
Construtores de instância, que você chama para criar uma nova instância da classe.
Cada classe deve definir seus próprios construtores.
Finalizadores, que são chamados pelo coletor de lixo do runtime para destruir as
instâncias de uma classe.
Enquanto todos os outros membros de uma classe base são herdados por classes
derivadas, o fato de serem visíveis ou não depende de sua acessibilidade. A
acessibilidade de um membro afeta sua visibilidade para classes derivadas da seguinte
maneira:
Membros Privados são visíveis apenas em classes derivadas que estão aninhadas
em sua classe base. Caso contrário, eles não são visíveis em classes derivadas. No
exemplo a seguir, A.B é uma classe aninhada derivada de A , e C deriva de A . O
campo particular A._value é visível em A.B. No entanto, se você remover os
comentários do método C.GetValue e tentar compilar o exemplo, ele produzirá
um erro do compilador CS0122: "'A._value' está inacessível em virtude do nível de
proteção dele".
C#
public class A
private int _value = 10;
public class B : A
public int GetValue()
return _value;
public class C : A
// public int GetValue()
// {
// return _value;
// }
public class AccessExample
public static void Main(string[] args)
var b = new A.B();
Console.WriteLine(b.GetValue());
// The example displays the following output:
// 10
Membros Protegidos são visíveis apenas em classes derivadas.
Membros Internos são visíveis apenas em classes derivadas localizadas no mesmo
assembly que a classe base. Eles não são visíveis em classes derivadas localizadas
em um assembly diferente da classe base.
Membros Públicos são visíveis em classes derivadas e fazem parte da interface
pública da classe derivada. Os membros públicos herdados podem ser chamados
como se estivessem definidos na classe derivada. No exemplo a seguir, a classe A
define um método chamado Method1 , e a classe B herda da classe A . Depois, o
exemplo chama Method1 como se fosse um método de instância em B .
C#
public class A
public void Method1()
// Method implementation.
}
public class B : A
{ }
public class Example
public static void Main()
B b = new ();
b.Method1();
Classes derivadas também podem substituir membros herdados fornecendo uma
implementação alternativa. Para poder substituir um membro, o membro na classe base
deve ser marcado com a palavra-chave virtual. Por padrão, os membros da classe base
não são marcados como virtual e não podem ser substituídos. A tentativa de substituir
um membro não virtual, como o seguinte exemplo faz, gera o erro do compilador
CS0506: "O <membro> não pode substituir o membro herdado <membro>, pois não
está marcado como virtual, abstrato ou de substituição."
C#
public class A
public void Method1()
// Do something.
public class B : A
public override void Method1() // Generates CS0506.
// Do something else.
Em alguns casos, uma classe derivada deve substituir a implementação da classe base.
Membros de classe base marcados com a palavra-chave abstrato exigem que as classes
derivadas os substituam. A tentativa de compilar o exemplo a seguir gera um erro do
compilador CS0534, a "<classe> não implementa o membro abstrato herdado
<membro>", pois a classe B não fornece uma implementação para A.Method1 .
C#
public abstract class A
public abstract void Method1();
public class B : A // Generates CS0534.
public void Method3()
// Do something.
A herança se aplica apenas a classes e interfaces. Outras categorias de tipo (structs,
delegados e enumerações) não dão suporte à herança. Devido a essas regras, tentar
compilar código como o seguinte exemplo, produz o erro do compilador CS0527: "Tipo
'ValueType' na lista de interfaces não é uma interface". A mensagem de erro indica que,
embora você possa definir as interfaces que um struct implementa, não há suporte para
a herança.
C#
public struct ValueStructure : ValueType // Generates CS0527.
Herança implícita
Apesar dos tipos possíveis de herança por meio de herança única, todos os tipos no
sistema de tipo .NET herdam implicitamente de Object ou de um tipo derivado dele. A
funcionalidade comum de Object está disponível para qualquer tipo.
Para ver o que significa herança implícita, vamos definir uma nova classe, SimpleClass ,
que é simplesmente uma definição de classe vazia:
C#
public class SimpleClass
{ }
É possível usar reflexão (o que permite inspecionar os metadados de um tipo para obter
informações sobre esse tipo) para obter uma lista dos membros que pertencem ao tipo
SimpleClass . Embora você ainda não tenha definido membros na classe SimpleClass , a
saída do exemplo indica que, na verdade, ela tem nove membros. Um desses membros
é um construtor sem parâmetros (ou padrão) que é fornecido automaticamente para o
tipo SimpleClass pelo compilador de C#. Os oito são membros do Object, o tipo do
qual todas as classes e interfaces no sistema do tipo .NET herdam implicitamente.
C#
using System.Reflection;
public class SimpleClassExample
public static void Main()
Type t = typeof(SimpleClass);
BindingFlags flags = BindingFlags.Instance | BindingFlags.Static |
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.FlattenHierarchy;
MemberInfo[] members = t.GetMembers(flags);
Console.WriteLine($"Type {t.Name} has {members.Length} members: ");
foreach (MemberInfo member in members)
string access = "";
string stat = "";
var method = member as MethodBase;
if (method != null)
if (method.IsPublic)
access = " Public";
else if (method.IsPrivate)
access = " Private";
else if (method.IsFamily)
access = " Protected";
else if (method.IsAssembly)
access = " Internal";
else if (method.IsFamilyOrAssembly)
access = " Protected Internal ";
if (method.IsStatic)
stat = " Static";
string output = $"{member.Name} ({member.MemberType}): {access}
{stat}, Declared by {member.DeclaringType}";
Console.WriteLine(output);
// The example displays the following output:
// Type SimpleClass has 9 members:
// ToString (Method): Public, Declared by System.Object
// Equals (Method): Public, Declared by System.Object
// Equals (Method): Public Static, Declared by System.Object
// ReferenceEquals (Method): Public Static, Declared by System.Object
// GetHashCode (Method): Public, Declared by System.Object
// GetType (Method): Public, Declared by System.Object
// Finalize (Method): Internal, Declared by System.Object
// MemberwiseClone (Method): Internal, Declared by System.Object
// .ctor (Constructor): Public, Declared by SimpleClass
A herança implícita da classe Object torna esses métodos disponíveis para a classe
SimpleClass :
O método ToString público, que converte um objeto SimpleClass em sua
representação de cadeia de caracteres, retorna o nome de tipo totalmente
qualificado. Nesse caso, o método ToString retorna a cadeia de caracteres
"SimpleClass".
Três métodos de teste de igualdade de dois objetos: o método Equals(Object) da
instância pública, o método Equals(Object, Object) do público estático e o
método ReferenceEquals(Object, Object) de público estático. Por padrão, esses
métodos testam a igualdade de referência; ou seja, para que seja iguais, duas
variáveis de objeto devem fazer referência ao mesmo objeto.
O método público GetHashCode , que calcula um valor que permite o uso de uma
instância do tipo em conjuntos de hash.
O método público GetType , que retorna um objeto Type que representa o tipo
SimpleClass .
O método protegido Finalize, que é projetado para liberar recursos não
gerenciados antes que a memória de um objeto seja reivindicada pelo coletor de
lixo.
O método protegido MemberwiseClone, que cria um clone superficial do objeto
atual.
Devido à herança implícita, você pode chamar qualquer membro herdado de um objeto
SimpleClass como se ele fosse, na verdade, um membro definido na classe
SimpleClass . Por exemplo, o exemplo a seguir chama o método SimpleClass.ToString ,
que SimpleClass herda de Object.
C#
public class EmptyClass
{ }
public class ClassNameExample
public static void Main()
EmptyClass sc = new();
Console.WriteLine(sc.ToString());
// The example displays the following output:
// EmptyClass
A tabela a seguir lista as categorias de tipos que você pode criar em C#, e os tipos de
onde eles herdam implicitamente. Cada tipo base disponibiliza um conjunto diferente
de membros por meio de herança para tipos derivados implicitamente.
Categoria do tipo Herda implicitamente de
classe Object
struct ValueType, Object
enum Enum, ValueType, Object
delegado MulticastDelegate, Delegate, Object
Herança e um relacionamento "é um(a)"
Normalmente, a herança é usada para expressar um relacionamento "é um(a)" entre
uma classe base e uma ou mais classes derivadas, em que as classes derivadas são
versões especializadas da classe base; a classe derivada é um tipo de classe base. Por
exemplo, a classe Publication representa uma publicação de qualquer tipo e as classes
Book e Magazine representam tipos específicos de publicações.
7 Observação
Uma classe ou struct pode implementar uma ou mais interfaces. Embora a
implementação da interface seja apresentada geralmente como uma alternativa
para herança única, ou como uma forma de usar a herança com structs, ela tem
como objetivo expressar um relacionamento diferente (um relacionamento "pode
fazer") entre uma interface e seu tipo de implementação em comparação com a
herança. Uma interface define um subconjunto de funcionalidades (como a
capacidade de testar a igualdade, comparar ou classificar objetos ou dar suporte à
formatação e análise sensível à cultura) que disponibiliza para seus tipos de
implementação.
Observe que "é um(a)" também expressa o relacionamento entre um tipo e uma
instanciação específica desse tipo. No exemplo a seguir, Automobile é uma classe que
tem três propriedades somente leitura exclusivas: Make , o fabricante do automóvel;
Model , o tipo de automóvel; e Year , o ano de fabricação. A classe Automobile também
tem um construtor cujos argumentos são atribuídos aos valores de propriedade. Ela
também substitui o método Object.ToString para produzir uma cadeia de caracteres que
identifica exclusivamente a instância Automobile em vez da classe Automobile .
C#
public class Automobile
{
public Automobile(string make, string model, int year)
if (make == null)
throw new ArgumentNullException(nameof(make), "The make cannot
be null.");
else if (string.IsNullOrWhiteSpace(make))
throw new ArgumentException("make cannot be an empty string or
have space characters only.");
Make = make;
if (model == null)
throw new ArgumentNullException(nameof(model), "The model cannot
be null.");
else if (string.IsNullOrWhiteSpace(model))
throw new ArgumentException("model cannot be an empty string or
have space characters only.");
Model = model;
if (year < 1857 || year > DateTime.Now.Year + 2)
throw new ArgumentException("The year is out of range.");
Year = year;
public string Make { get; }
public string Model { get; }
public int Year { get; }
public override string ToString() => $"{Year} {Make} {Model}";
Nesse caso, você não deve depender da herança para representar marcas e modelos de
carro específicos. Por exemplo, você não precisa definir um tipo Packard para
representar automóveis fabricados pela empresa Packard Motor Car. Nesse caso, é
possível representá-los criando um objeto Automobile com os valores apropriados
passados ao construtor de classe, como no exemplo a seguir.
C#
using System;
public class Example
public static void Main()
var packard = new Automobile("Packard", "Custom Eight", 1948);
Console.WriteLine(packard);
// The example displays the following output:
// 1948 Packard Custom Eight
Um relacionamento é-um(a) baseado na herança é mais bem aplicado a uma classe
base e em classes derivadas que adicionam outros membros à classe base, ou que
exigem funcionalidades adicionais não incluídas na classe base.
Criação da classe base e das classes derivadas
Vamos examinar o processo de criação de uma classe base e de suas classes derivadas.
Nesta seção, você definirá uma classe base, Publication , que representa uma
publicação de qualquer natureza, como um livro, uma revista, um jornal, um diário, um
artigo etc. Você também definirá uma classe Book que deriva de Publication . É possível
estender facilmente o exemplo para definir outras classes derivadas, como Magazine ,
Journal , Newspaper e Article .
A classe base de Publicação
Em projetar a classe Publication , você precisa tomar várias decisões de design:
Quais membros devem ser incluídos na classe base Publication e se os membros
de Publication fornecem implementações de método, ou se Publication é uma
classe base abstrata que funciona como um modelo para suas classes derivadas.
Nesse caso, a classe Publication fornecerá implementações de método. A seção
Criação de classes base abstratas e de suas classes derivadas contém um exemplo
que usa uma classe base abstrata para definir os métodos que as classes derivadas
devem substituir. As classes derivadas são livres para fornecer qualquer
implementação adequada ao tipo derivado.
A capacidade de reutilizar o código (ou seja, várias classes derivadas compartilham
a declaração e a implementação dos métodos de classe base, e não é necessário
substituí-las) é uma vantagem das classes base não abstratas. Portanto, você
deverá adicionar membros à Publication se o código precisar ser compartilhado
por um ou mais tipos Publication especializados. Se você não conseguir fornecer
implementações da classe base de forma eficiente, será necessário fornecer
implementações de membro praticamente idênticas em classes derivadas em vez
de uma única implementação na classe base. A necessidade de manter o código
duplicado em vários locais é uma possível fonte de bugs.
Para maximizar a reutilização do código e criar uma hierarquia de herança lógica e
intuitiva, inclua na classe Publication apenas dos dados e a funcionalidade
comuns a todas as publicações ou à maioria delas. Depois, as classes derivadas
implementam os membros exclusivos a determinados tipos de publicação que eles
representam.
O quanto devemos ampliar a hierarquia de classe. Você deseja desenvolver uma
hierarquia de três ou mais classes, em vez de simplesmente uma classe base e uma
ou mais classes derivadas? Por exemplo, Publication poderia ser uma classe base
de Periodical , que por sua vez é uma classe base de Magazine , Journal e
Newspaper .
Para o seu exemplo, você usará a hierarquia pequena de uma classe Publication e
uma única classe derivada, a Book . É possível ampliar o exemplo facilmente para
criar várias classes adicionais derivadas de Publication , como Magazine e Article .
Se faz sentido instanciar a classe base. Se não fizer, você deverá aplicar a palavra-
chave abstract à classe. Caso contrário, a instância da classe Publication poderá
ser criada chamando seu construtor de classe. Se for feita uma tentativa de criar
uma instância de classe marcada com a palavra-chave abstract por uma chamada
direta ao construtor dela, o compilador C# vai gerar o erro CS0144, "Impossível
criar uma instância da interface ou da classe abstrata". Se for feita uma tentativa de
instanciar a classe usando reflexão, o método de reflexão vai gerar o erro
MemberAccessException.
Por padrão, uma classe base pode ser instanciada chamando seu construtor de
classe. Você não precisa definir um construtor de classe explicitamente. Se não
houver um presente no código-fonte da classe base, o compilador de C# fornecerá
automaticamente um construtor (sem parâmetros) padrão.
No seu exemplo, você marcará a classe Publication como abstract, para que não
seja possível criar uma instância dela. Uma classe abstract sem nenhum método
abstract indica que essa classe representa um conceito abstrato que é
compartilhado entre várias classes concretas (como Book , Journal ).
Se as classes derivadas precisam herdar a implementação de membros específicos
da classe base, se elas têm a opção de substituir a implementação da classe base
ou se precisam fornecer uma implementação. Use a palavra-chave abstract para
forçar as classes derivadas a fornecer uma implementação. Use a palavra-chave
virtual para permitir que as classes derivadas substituam um método de classe
base. Por padrão, os métodos definidos na classe base não são substituíveis.
A classe Publication não tem nenhum método abstract , mas a classe em si é
abstract .
Se uma classe derivada representa a classe final na hierarquia de herança e não
pode se ser usada como uma classe base para outras classes derivadas. Por
padrão, qualquer classe pode servir como classe base. Você pode aplicar a palavra-
chave sealed para indicar que uma classe não pode funcionar como uma classe
base para quaisquer classes adicionais. A tentativa de derivar de uma classe selada
gerou o erro do compilador CS0509, "Não é possível derivar do tipo selado
<typeName>."
No seu exemplo, você marcará a classe derivada como sealed .
O exemplo a seguir mostra o código-fonte para a classe Publication , bem como uma
enumeração PublicationType que é retornada pela propriedade
Publication.PublicationType . Além dos membros herdados de Object, a classe
Publication define os seguintes membros exclusivos e substituições de membro:
C#
public enum PublicationType { Misc, Book, Magazine, Article };
public abstract class Publication
private bool _published = false;
private DateTime _datePublished;
private int _totalPages;
public Publication(string title, string publisher, PublicationType type)
if (string.IsNullOrWhiteSpace(publisher))
throw new ArgumentException("The publisher is required.");
Publisher = publisher;
if (string.IsNullOrWhiteSpace(title))
throw new ArgumentException("The title is required.");
Title = title;
Type = type;
public string Publisher { get; }
public string Title { get; }
public PublicationType Type { get; }
public string? CopyrightName { get; private set; }
public int CopyrightDate { get; private set; }
public int Pages
get { return _totalPages; }
set
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value), "The
number of pages cannot be zero or negative.");
_totalPages = value;
public string GetPublicationDate()
if (!_published)
return "NYP";
else
return _datePublished.ToString("d");
public void Publish(DateTime datePublished)
_published = true;
_datePublished = datePublished;
public void Copyright(string copyrightName, int copyrightDate)
if (string.IsNullOrWhiteSpace(copyrightName))
throw new ArgumentException("The name of the copyright holder is
required.");
CopyrightName = copyrightName;
int currentYear = DateTime.Now.Year;
if (copyrightDate < currentYear - 10 || copyrightDate > currentYear
+ 2)
throw new ArgumentOutOfRangeException($"The copyright year must
be between {currentYear - 10} and {currentYear + 1}");
CopyrightDate = copyrightDate;
public override string ToString() => Title;
Um construtor
Como a classe Publication é abstract , sua instância não pode ser criada
diretamente no código, com no exemplo a seguir:
C#
var publication = new Publication("Tiddlywinks for Experts", "Fun and
Games",
PublicationType.Book);
No entanto, o construtor de instância pode ser chamado diretamente dos
construtores de classe derivada, como mostra o código-fonte para a classe Book .
Duas propriedades relacionadas à publicação
Title é uma propriedade String somente leitura cujo valor é fornecido pela
chamada do construtor Publication .
Pages é uma propriedade Int32 de leitura-gravação que indica o número total de
páginas da publicação. O valor é armazenado em um campo privado chamado
totalPages . O lançamento deve ser de um número positivo ou de um
ArgumentOutOfRangeException.
Membros relacionados ao publicador
Duas propriedades somente leitura, Publisher e Type . Originalmente, os valores
eram fornecidos pela chamada para o construtor da classe Publication .
Membros relacionados à publicação
Dois métodos, Publish e GetPublicationDate , definem e retornam a data de
publicação. O método Publish define um sinalizador particular published como
true quando é chamado e atribui a data passada para ele como argumento ao
campo particular datePublished . O método GetPublicationDate retorna a cadeia
de caracteres "NYP" se o sinalizador published for false , e o valor do campo
datePublished for true .
Membros relacionados a direitos autorais
O método Copyright usa o nome do proprietário dos direitos autorais e o ano dos
direitos autorais como argumentos e os atribui às propriedades CopyrightName e
CopyrightDate .
Uma substituição do método ToString
Se um tipo não substituir o método Object.ToString, ele retornará o nome
totalmente qualificado do tipo, que é de pouca utilidade na diferenciação de uma
instância para outra. A classe Publication substitui Object.ToString para retornar o
valor da propriedade Title .
A figura a seguir ilustra o relacionamento entre a classe base Publication e sua classe
herdada implicitamente Object.
A classe Book
A classe Book representa um livro como tipo especializado de publicação. O exemplo a
seguir mostra o código-fonte para a classe Book .
C#
using System;
public sealed class Book : Publication
public Book(string title, string author, string publisher) :
this(title, string.Empty, author, publisher)
{ }
public Book(string title, string isbn, string author, string publisher)
: base(title, publisher, PublicationType.Book)
// isbn argument must be a 10- or 13-character numeric string
without "-" characters.
// We could also determine whether the ISBN is valid by comparing
its checksum digit
// with a computed checksum.
//
if (!string.IsNullOrEmpty(isbn))
// Determine if ISBN length is correct.
if (!(isbn.Length == 10 | isbn.Length == 13))
throw new ArgumentException("The ISBN must be a 10- or 13-
character numeric string.");
if (!ulong.TryParse(isbn, out _))
throw new ArgumentException("The ISBN can consist of numeric
characters only.");
ISBN = isbn;
Author = author;
public string ISBN { get; }
public string Author { get; }
public decimal Price { get; private set; }
// A three-digit ISO currency symbol.
public string? Currency { get; private set; }
// Returns the old price, and sets a new price.
public decimal SetPrice(decimal price, string currency)
if (price < 0)
throw new ArgumentOutOfRangeException(nameof(price), "The price
cannot be negative.");
decimal oldValue = Price;
Price = price;
if (currency.Length != 3)
throw new ArgumentException("The ISO currency symbol is a 3-
character string.");
Currency = currency;
return oldValue;
public override bool Equals(object? obj)
if (obj is not Book book)
return false;
else
return ISBN == book.ISBN;
public override int GetHashCode() => ISBN.GetHashCode();
public override string ToString() => $"{(string.IsNullOrEmpty(Author) ?
"" : Author + ", ")}{Title}";
Além dos membros herdados de Publication , a classe Book define os seguintes
membros exclusivos e substituições de membro:
Dois construtores
Os dois construtores Book compartilham três parâmetros comuns. Dois, title e
publisher, correspondem aos parâmetros do construtor Publication . O terceiro é
author, que é armazenado em uma propriedade pública Author imutável. Um
construtor inclui um parâmetro isbn, que é armazenado na propriedade
automática ISBN .
O primeiro construtor usa a palavra-chave this para chamar o outro construtor. O
encadeamento do construtor é um padrão comum na definição de construtores.
Construtores com menos parâmetros fornecem valores padrão ao chamar o
construtor com o maior número de parâmetros.
O segundo construtor usa a palavra-chave base para passar o título e o nome do
publicador para o construtor da classe base. Se você não fizer uma chamada
explícita para um construtor de classe base em seu código-fonte, o compilador de
C# fornecerá automaticamente uma chamada para a classe base padrão ou para o
construtor sem parâmetros.
Uma propriedade ISBN somente leitura, que retorna o ISBN (International
Standard Book Number) do objeto Book , um número exclusivo com 10 ou 13
dígitos. O ISBN é fornecido como um argumento para um dos construtores Book .
O ISBN é armazenado em um campo de suporte particular, gerado
automaticamente pelo compilador.
Uma propriedade Author somente leitura. O nome do autor é fornecido como um
argumento para os dois construtores Book e é armazenado na propriedade.
Duas propriedades somente leitura relacionadas ao preço, Price e Currency . Seus
valores são fornecidos como argumentos em uma chamada do método SetPrice .
A propriedade Currency é o símbolo de moeda ISO de três dígitos (por exemplo,
USD para dólar americano). Símbolos de moeda ISO podem ser obtidos da
propriedade ISOCurrencySymbol. Ambas as propriedades são somente leitura
externamente, mas podem ser definidas por código na classe Book .
Um método SetPrice , que define os valores das propriedades Price e Currency .
Esses valores são retornados por essas mesmas propriedades.
Substitui o método ToString (herdado de Publication ) e os métodos
Object.Equals(Object) e GetHashCode (herdados de Object).
A menos que seja substituído, o método Object.Equals(Object) testa a igualdade
de referência. Ou seja, duas variáveis de objeto são consideradas iguais se fizerem
referência ao mesmo objeto. Na classe Book , por outro lado, dois objetos Book
devem ser iguais quando têm o mesmo ISBN.
Ao substituir o método Object.Equals(Object), substitua também o método
GetHashCode, que retorna um valor usado pelo runtime para armazenar itens em
coleções de hash para uma recuperação eficiente. O código de hash deve retornar
um valor que é consistente com o teste de igualdade. Como você substituiu
Object.Equals(Object) para retornar true , se as propriedades de ISBN de dois
objetos Book forem iguais, retorne o código hash computado chamando o
método GetHashCode da cadeia de caracteres retornada pela propriedade ISBN .
A figura a seguir ilustra o relacionamento entre a classe Book e Publication , sua classe
base.
Agora você pode criar a instância de um objeto Book , invocar seus membros exclusivos
e herdados e passá-lo como um argumento a um método que espera um parâmetro do
tipo Publication ou do tipo Book , como mostra o exemplo a seguir.
C#
public class ClassExample
public static void Main()
var book = new Book("The Tempest", "0971655819", "Shakespeare,
William",
"Public Domain Press");
ShowPublicationInfo(book);
book.Publish(new DateTime(2016, 8, 18));
ShowPublicationInfo(book);
var book2 = new Book("The Tempest", "Classic Works Press",
"Shakespeare, William");
Console.Write($"{book.Title} and {book2.Title} are the same
publication: " +
$"{((Publication)book).Equals(book2)}");
public static void ShowPublicationInfo(Publication pub)
string pubDate = pub.GetPublicationDate();
Console.WriteLine($"{pub.Title}, " +
$"{(pubDate == "NYP" ? "Not Yet Published" : "published on
" + pubDate):d} by {pub.Publisher}");
// The example displays the following output:
// The Tempest, Not Yet Published by Public Domain Press
// The Tempest, published on 8/18/2016 by Public Domain Press
// The Tempest and The Tempest are the same publication: False
Criando classes base abstratas e suas classes
derivadas
No exemplo anterior, você definiu uma classe base que forneceu uma implementação
de diversos métodos para permitir que classes derivadas compartilhem código. Em
muitos casos, no entanto, não espera-se que a classe base forneça uma implementação.
Nesse caso, a classe base é uma classe abstrata que declara métodos abstratos. Ela
funciona como um modelo que define os membros que cada classe derivada precisa
implementar. Normalmente em uma classe base abstrata, a implementação de cada tipo
derivado é exclusiva para esse tipo. Você marcou a classe com a palavra-chave abstract
porque não fazia sentido criar uma instância de um objeto Publication , embora a
classe fornecesse implementações de funcionalidades comuns para publicações.
Por exemplo, cada forma geométrica bidimensional fechada inclui duas propriedades:
área, a extensão interna da forma; e perímetro, ou a distância entre as bordas da forma.
A maneira com a qual essas propriedades são calculadas, no entanto, depende
completamente da forma específica. A fórmula para calcular o perímetro (ou a
circunferência) de um círculo, por exemplo, é diferente do quadrado. A classe Shape é
uma classe abstract com métodos abstract . Isso indica que as classes derivadas
compartilham a mesma funcionalidade, mas essas classes derivadas implementam essa
funcionalidade de forma diferente.
O exemplo a seguir define uma classe base abstrata denominada Shape que define duas
propriedades: Area e Perimeter . Além da classe ser marcada com a palavra-chave
abstract, cada membro da instância também é marcado com a palavra-chave abstract.
Nesse caso, o Shape também substitui o método Object.ToString para retornar o nome
do tipo, em vez de seu nome totalmente qualificado. E define dois membros estáticos,
GetArea e GetPerimeter , que permitem a recuperação fácil da área e do perímetro de
uma instância de qualquer classe derivada. Quando você passa uma instância de uma
classe derivada para um desses métodos, o runtime chama a substituição do método da
classe derivada.
C#
public abstract class Shape
public abstract double Area { get; }
public abstract double Perimeter { get; }
public override string ToString() => GetType().Name;
public static double GetArea(Shape shape) => shape.Area;
public static double GetPerimeter(Shape shape) => shape.Perimeter;
Em seguida, você pode derivar algumas classes de Shape que representam formas
específicas. O exemplo a seguir define três classes, Square , Rectangle e Circle . Cada
uma usa uma fórmula exclusiva para essa forma específica para calcular a área e o
perímetro. Algumas das classes derivadas também definem propriedades, como
Rectangle.Diagonal e Circle.Diameter , que são exclusivas para a forma que
representam.
C#
using System;
public class Square : Shape
public Square(double length)
Side = length;
public double Side { get; }
public override double Area => Math.Pow(Side, 2);
public override double Perimeter => Side * 4;
public double Diagonal => Math.Round(Math.Sqrt(2) * Side, 2);
public class Rectangle : Shape
public Rectangle(double length, double width)
Length = length;
Width = width;
public double Length { get; }
public double Width { get; }
public override double Area => Length * Width;
public override double Perimeter => 2 * Length + 2 * Width;
public bool IsSquare() => Length == Width;
public double Diagonal => Math.Round(Math.Sqrt(Math.Pow(Length, 2) +
Math.Pow(Width, 2)), 2);
public class Circle : Shape
public Circle(double radius)
Radius = radius;
public override double Area => Math.Round(Math.PI * Math.Pow(Radius, 2),
2);
public override double Perimeter => Math.Round(Math.PI * 2 * Radius, 2);
// Define a circumference, since it's the more familiar term.
public double Circumference => Perimeter;
public double Radius { get; }
public double Diameter => Radius * 2;
O exemplo a seguir usa objetos derivados de Shape . Ele cria uma matriz de objetos
derivados de Shape e chama os métodos estáticos da classe Shape , que retorna valores
de propriedade Shape . O runtime recupera os valores das propriedades substituídas dos
tipos derivados. O exemplo também converte cada objeto Shape na matriz ao seu tipo
derivado e, se a conversão for bem-sucedida, recupera as propriedades dessa subclasse
específica de Shape .
C#
using System;
public class Example
public static void Main()
Shape[] shapes = { new Rectangle(10, 12), new Square(5),
new Circle(3) };
foreach (Shape shape in shapes)
Console.WriteLine($"{shape}: area, {Shape.GetArea(shape)}; " +
$"perimeter, {Shape.GetPerimeter(shape)}");
if (shape is Rectangle rect)
Console.WriteLine($" Is Square: {rect.IsSquare()},
Diagonal: {rect.Diagonal}");
continue;
if (shape is Square sq)
Console.WriteLine($" Diagonal: {sq.Diagonal}");
continue;
// The example displays the following output:
// Rectangle: area, 120; perimeter, 44
// Is Square: False, Diagonal: 15.62
// Square: area, 25; perimeter, 20
// Diagonal: 7.07
// Circle: area, 28.27; perimeter, 18.85
Como converter com segurança usando
a correspondência de padrões e os
operadores is e as
Artigo • 07/04/2023
Como os objetos são polimórficos, é possível que uma variável de tipo de classe base
tenha um tipo derivado. Para acessar os métodos de instância do tipo derivado, é
necessário converter o valor de volta no tipo derivado. No entanto, uma conversão cria
o risco de lançar um InvalidCastException. O C# fornece instruções de correspondência
de padrões que executarão uma conversão condicionalmente somente quando ela tiver
êxito. O C# também oferece os operadores is e as para testar se um valor é de um
determinado tipo.
O exemplo a seguir mostra como usar a instrução is de correspondência de padrões:
C#
var g = new Giraffe();
var a = new Animal();
FeedMammals(g);
FeedMammals(a);
// Output:
// Eating.
// Animal is not a Mammal
SuperNova sn = new SuperNova();
TestForMammals(g);
TestForMammals(sn);
static void FeedMammals(Animal a)
if (a is Mammal m)
m.Eat();
else
// variable 'm' is not in scope here, and can't be used.
Console.WriteLine($"{a.GetType().Name} is not a Mammal");
static void TestForMammals(object o)
// You also can use the as operator and test for null
// before referencing the variable.
var m = o as Mammal;
if (m != null)
Console.WriteLine(m.ToString());
else
Console.WriteLine($"{o.GetType().Name} is not a Mammal");
// Output:
// I am an animal.
// SuperNova is not a Mammal
class Animal
public void Eat() { Console.WriteLine("Eating."); }
public override string ToString()
return "I am an animal.";
class Mammal : Animal { }
class Giraffe : Mammal { }
class SuperNova { }
O exemplo anterior demonstra alguns recursos da sintaxe de correspondência de
padrões. A instrução if (a is Mammal m) combina o teste com uma atribuição de
inicialização. A atribuição ocorre apenas quando o teste é bem-sucedido. A variável m
está somente no escopo na instrução if inserida em ela foi atribuída. Não é possível
acessar m posteriormente no mesmo método. O exemplo anterior também mostra
como usar o as operador para converter um objeto em um tipo especificado.
Também é possível usar a mesma sintaxe para testar se um tipo que permite valor nulo
tem um valor, como mostra o código de exemplo a seguir:
C#
int i = 5;
PatternMatchingNullable(i);
int? j = null;
PatternMatchingNullable(j);
double d = 9.78654;
PatternMatchingNullable(d);
PatternMatchingSwitch(i);
PatternMatchingSwitch(j);
PatternMatchingSwitch(d);
static void PatternMatchingNullable(ValueType? val)
if (val is int j) // Nullable types are not allowed in patterns
Console.WriteLine(j);
else if (val is null) // If val is a nullable type with no value, this
expression is true
Console.WriteLine("val is a nullable type with the null value");
else
Console.WriteLine("Could not convert " + val.ToString());
static void PatternMatchingSwitch(ValueType? val)
switch (val)
case int number:
Console.WriteLine(number);
break;
case long number:
Console.WriteLine(number);
break;
case decimal number:
Console.WriteLine(number);
break;
case float number:
Console.WriteLine(number);
break;
case double number:
Console.WriteLine(number);
break;
case null:
Console.WriteLine("val is a nullable type with the null value");
break;
default:
Console.WriteLine("Could not convert " + val.ToString());
break;
O exemplo anterior demonstra outros recursos da correspondência de padrões a serem
usados com conversões. É possível testar se uma variável tem padrão nulo verificando
especificamente o valor null . Quando o valor de runtime da variável é null , uma
instrução is que verifica um tipo retorna sempre false . A instrução is da
correspondência de padrões não permite um tipo de valor anulável, como int? ou
Nullable<int> , mas é possível testar qualquer outro tipo de valor. Os padrões is do
exemplo anterior não se limitam aos tipos que permitem valor nulo. Você também pode
usar esses padrões para testar se uma variável de um tipo de referência tem um valor ou
é null .
O exemplo anterior também mostra como usar o padrão de tipo em uma expressão
switch em que a variável pode ser um de muitos tipos diferentes.
Se você quiser testar se uma variável é um determinado tipo sem atribuição a uma nova
variável, poderá usar os operadores is e as para tipos de referência e tipos que
permitem valor nulo. O seguinte código mostra como usar as instruções is e as que
faziam parte da linguagem C# antes que a correspondência de padrões fosse
introduzida para testar se uma variável é de um determinado tipo:
C#
// Use the is operator to verify the type.
// before performing a cast.
Giraffe g = new();
UseIsOperator(g);
// Use the as operator and test for null
// before referencing the variable.
UseAsOperator(g);
// Use pattern matching to test for null
// before referencing the variable
UsePatternMatchingIs(g);
// Use the as operator to test
// an incompatible type.
SuperNova sn = new();
UseAsOperator(sn);
// Use the as operator with a value type.
// Note the implicit conversion to int? in
// the method body.
int i = 5;
UseAsWithNullable(i);
double d = 9.78654;
UseAsWithNullable(d);
static void UseIsOperator(Animal a)
if (a is Mammal)
Mammal m = (Mammal)a;
m.Eat();
static void UsePatternMatchingIs(Animal a)
if (a is Mammal m)
m.Eat();
static void UseAsOperator(object o)
Mammal? m = o as Mammal;
if (m is not null)
Console.WriteLine(m.ToString());
else
Console.WriteLine($"{o.GetType().Name} is not a Mammal");
static void UseAsWithNullable(System.ValueType val)
int? j = val as int?;
if (j is not null)
Console.WriteLine(j);
else
Console.WriteLine("Could not convert " + val.ToString());
class Animal
public void Eat() => Console.WriteLine("Eating.");
public override string ToString() => "I am an animal.";
class Mammal : Animal { }
class Giraffe : Mammal { }
class SuperNova { }
Como você pode ver na comparação desse código com o de correspondência de
padrões, a sintaxe de correspondência de padrões oferece recursos mais robustos
combinando o teste e a atribuição em uma única instrução. Use a sintaxe de
correspondência de padrões sempre que possível.
Tutorial: Usar padrões correspondentes
para criar algoritmos controlados por
tipo e controlados por dados
Artigo • 10/05/2023
É possível escrever uma funcionalidade que se comporte como se você tivesse
estendido tipos que poderiam estar em outras bibliotecas. Outro uso dos padrões é
criar a funcionalidade de que seu aplicativo precisa, mas que não é um recurso
fundamental do tipo que está sendo estendido.
Neste tutorial, você aprenderá como:
" Reconhecer situações em que a correspondência de padrões deverá ser usada.
" Usar expressões de correspondência de padrões para implementar o
comportamento com base em tipos e valores de propriedade.
" Combinar a correspondência de padrões com outras técnicas para criar algoritmos
completos.
Pré-requisitos
Recomendamos o Visual Studio para Windows ou Mac. Você pode baixar uma
versão gratuita na página de downloads do Visual Studio . O Visual Studio inclui
o SDK do .NET.
Você também pode usar o editor do Visual Studio Code . Será preciso instalar o
SDK do .NET mais recente separadamente.
Se preferir outro editor, você precisará instalar o SDK do .NET mais recente.
Este tutorial pressupõe que você esteja familiarizado com o C# e .NET, incluindo o Visual
Studio ou a CLI do .NET.
Cenários para a correspondência de padrões
O desenvolvimento moderno geralmente inclui a integração de dados de várias fontes e
a apresentação de informações e insights de dados em um único aplicativo coeso. Você
e sua equipe não terão controle ou acesso a todos os tipos que representam os dados
de entrada.
O design orientado a objeto clássico exigiria a criação de tipos em seu aplicativo que
representassem cada tipo de dados das várias fontes de dados. O aplicativo, então,
trabalharia com esses novos tipos, criaria hierarquias de herança, métodos virtuais e
implementaria abstrações. Essas técnicas funcionam e, às vezes, são as melhores
ferramentas. Outras vezes, é possível escrever menos código. Você pode escrever um
código mais claro usando técnicas que separam os dados das operações que
manipulam esses dados.
Neste tutorial, você vai criar e explorar um aplicativo que usa dados recebidos de várias
fontes externas para um único cenário. Você verá como a correspondência de padrões
fornece uma maneira eficiente para consumir e processar esses dados de maneiras que
não eram parte do sistema original.
Imagine, por exemplo, uma área metropolitana principal que está implantando pedágios
e preços diferenciados em horário de pico para gerenciar o tráfego. Você escreve um
aplicativo que calcula o pedágio de um veículo com base em seu tipo. Posteriormente,
as melhorias vão incorporar preços com base no número de ocupantes do veículo.
Outros aprimoramentos vão adicionar o preço com base na hora e no dia da semana.
Com base nessa breve descrição, você pode ter elaborado rapidamente uma hierarquia
de objetos para modelar esse sistema. No entanto, seus dados são provenientes de
várias fontes, como outros sistemas de gerenciamento de registro do veículo. Esses
sistemas fornecem classes diferentes para modelar aqueles dados, e você não tem um
modelo de objeto único o qual seja possível usar. Neste tutorial, você usará essas
classes simplificadas para criar o modelo para os dados do veículo, a partir desses
sistemas externos, conforme mostrado no código a seguir:
C#
namespace ConsumerVehicleRegistration
public class Car
public int Passengers { get; set; }
namespace CommercialRegistration
public class DeliveryTruck
public int GrossWeightClass { get; set; }
namespace LiveryRegistration
public class Taxi
public int Fares { get; set; }
public class Bus
public int Capacity { get; set; }
public int Riders { get; set; }
Faça o download do código inicial no repositório dotnet/samples do GitHub. É
possível ver que as classes de veículos são de sistemas diferentes, e estão em
namespaces diferentes. Nenhuma base comum de classe, além da System.Object pode
ser aproveitada.
Designs de correspondência de padrões
O cenário usado neste tutorial destaca os tipos de problemas que a correspondência de
padrões pode resolver de forma adequada:
Os objetos com os quais você precisa trabalhar não estão em uma hierarquia de
objetos que corresponde aos seus objetivos. É possível que você esteja
trabalhando com classes que fazem parte dos sistemas não relacionados.
A funcionalidade que você está adicionando não faz parte da abstração central
dessas classes. A tarifa paga por um veículo muda de acordo com diferentes tipos
de veículos, mas o pedágio não é uma função principal do veículo.
Quando a forma dos dados e as operações nos dados não são descritas em conjunto, o
recurso de correspondência padrões no C# facilita o trabalho.
Implementar os cálculos básicos de pedágio
O cálculo mais básico do pedágio dependerá apenas do tipo do veículo:
Um Car será R$2,00.
Um Taxi será R$3,50.
Um Bus será R$5,00.
Um DeliveryTruck será R$10,00.
Crie uma nova classe TollCalculator e implemente a correspondência de padrões no
tipo de veículo para obter a quantidade do pedágio. O código a seguir mostra a
implementação inicial do TollCalculator .
C#
using System;
using CommercialRegistration;
using ConsumerVehicleRegistration;
using LiveryRegistration;
namespace Calculators;
public class TollCalculator
public decimal CalculateToll(object vehicle) =>
vehicle switch
Car c => 2.00m,
Taxi t => 3.50m,
Bus b => 5.00m,
DeliveryTruck t => 10.00m,
{ } => throw new ArgumentException(message: "Not a known
vehicle type", paramName: nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))
};
O código anterior usa uma expressão switch (não igual a uma instrução switch) que
testa o padrão de instrução. A expressão switch inicia-se com a variável, vehicle no
código anterior, seguida pela palavra-chave switch . Em seguida, estão os braços switch
dentro de chaves. A expressão switch faz outros refinamentos na sintaxe que circunda a
instrução switch . A palavra-chave case é omitida, e o resultado de cada braço é uma
expressão. Os dois últimos braços apresentam um novo recurso de linguagem. O caso {
} corresponde a qualquer objeto não nulo que não correspondia a um braço anterior.
Este braço captura qualquer tipo incorreto passado para esse método. O caso { }
precisa seguir os casos para cada tipo de veículo. Se a ordem for revertida, o caso { }
terá precedência. Por fim, o padrão constante null detecta quando null é passado
para esse método. O padrão null pode ser o último, porque os outros padrões
correspondem apenas a um objeto não nulo do tipo correto.
Você pode testar esse código usando o seguinte código no Program.cs :
C#
using System;
using CommercialRegistration;
using ConsumerVehicleRegistration;
using LiveryRegistration;
using toll_calculator;
var tollCalc = new TollCalculator();
var car = new Car();
var taxi = new Taxi();
var bus = new Bus();
var truck = new DeliveryTruck();
Console.WriteLine($"The toll for a car is {tollCalc.CalculateToll(car)}");
Console.WriteLine($"The toll for a taxi is {tollCalc.CalculateToll(taxi)}");
Console.WriteLine($"The toll for a bus is {tollCalc.CalculateToll(bus)}");
Console.WriteLine($"The toll for a truck is
{tollCalc.CalculateToll(truck)}");
try
tollCalc.CalculateToll("this will fail");
catch (ArgumentException e)
Console.WriteLine("Caught an argument exception when using the wrong
type");
try
tollCalc.CalculateToll(null!);
catch (ArgumentNullException e)
Console.WriteLine("Caught an argument exception when using null");
Esse código está incluído no projeto inicial, mas é comentado. Remova os comentários e
você pode testar o que escreveu.
Você está começando a ver como os padrões podem ajudar a criar algoritmos em que o
código e os dados estão separados. A expressão switch testa o tipo e produz valores
diferentes com base nos resultados. Mas isso é somente o começo.
Adicionar preços de acordo com a ocupação do
veículo
A autoridade de pedágio deseja incentivar que os veículos viagem com a capacidade
máxima de pessoas. Eles decidiram cobrar valores mais altos quando os veículos tiverem
poucos passageiros e oferecer redução da tarifa para veículos com a capacidade total
ocupada:
Os carros e táxis com nenhum passageiro pagam uma taxa adicional de R$ 0,50.
Os carros e táxis com dois passageiros obtêm um desconto de R$ 0,50.
Os carros e táxis com três ou mais passageiros obtêm um desconto de R$ 1,00.
Os ônibus com menos de 50% da capacidade completa pagam uma taxa adicional
de R$ 2,00.
Os ônibus com 90% da capacidade de passageiros completa, ganham um
desconto de R$ 1,00.
Essas regras podem ser implementadas usando o padrão de propriedade na mesma
expressão switch. Um padrão de propriedade compara um valor de propriedade com
um valor constante. O padrão de propriedade examina as propriedades do objeto
depois que o tipo foi determinado. O único caso de um Car se expande para quatro
casos diferentes:
C#
vehicle switch
Car {Passengers: 0} => 2.00m + 0.50m,
Car {Passengers: 1} => 2.0m,
Car {Passengers: 2} => 2.0m - 0.50m,
Car => 2.00m - 1.0m,
// ...
};
Os três primeiros casos testam o tipo como um Car , em seguida, verificam o valor da
propriedade Passengers . Se ambos corresponderem, essa expressão é avaliada e
retornada.
Você também expande os casos para táxis de maneira semelhante:
C#
vehicle switch
// ...
Taxi {Fares: 0} => 3.50m + 1.00m,
Taxi {Fares: 1} => 3.50m,
Taxi {Fares: 2} => 3.50m - 0.50m,
Taxi => 3.50m - 1.00m,
// ...
};
Em seguida, implemente as regras de ocupação expandindo os casos para os ônibus,
conforme mostrado no exemplo a seguir:
C#
vehicle switch
// ...
Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m +
2.00m,
Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m -
1.00m,
Bus => 5.00m,
// ...
};
A autoridade de pedágio não está preocupada com o número de passageiros nos
caminhões de carga. Em vez disso, ela ajusta a quantidade de pedágios com base na
classe de peso dos caminhões da seguinte maneira:
Os caminhões mais de 5000 quilos pagam uma taxa adicional de R$ 5,00.
Os caminhões leves abaixo de 3.000 lb recebem um desconto de US$ 2,00.
Essa regra é implementada com o código a seguir:
C#
vehicle switch
// ...
DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m,
DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m,
DeliveryTruck => 10.00m,
};
O código anterior mostra a cláusula when de um braço switch. Você usa a cláusula when
para testar as condições, com exceção da igualdade, em uma propriedade. Ao terminar,
o método será muito semelhante ao seguinte código:
C#
vehicle switch
Car {Passengers: 0} => 2.00m + 0.50m,
Car {Passengers: 1} => 2.0m,
Car {Passengers: 2} => 2.0m - 0.50m,
Car => 2.00m - 1.0m,
Taxi {Fares: 0} => 3.50m + 1.00m,
Taxi {Fares: 1} => 3.50m,
Taxi {Fares: 2} => 3.50m - 0.50m,
Taxi => 3.50m - 1.00m,
Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m +
2.00m,
Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m -
1.00m,
Bus => 5.00m,
DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m,
DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m,
DeliveryTruck => 10.00m,
{ } => throw new ArgumentException(message: "Not a known vehicle
type", paramName: nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))
};
Muitos desses braços switch são exemplos de padrões recursivos. Por exemplo, Car {
Passengers: 1} mostra um padrão constante dentro de um padrão de propriedade.
É possível fazer esse código menos repetitivo, usando switches aninhados. O Car e Taxi
têm quatro braços diferentes nos exemplos anteriores. Em ambos os casos, você pode
criar um padrão de declaração que alimenta um padrão constante. Essa técnica é
mostrada no código a seguir:
C#
public decimal CalculateToll(object vehicle) =>
vehicle switch
Car c => c.Passengers switch
0 => 2.00m + 0.5m,
1 => 2.0m,
2 => 2.0m - 0.5m,
_ => 2.00m - 1.0m
},
Taxi t => t.Fares switch
0 => 3.50m + 1.00m,
1 => 3.50m,
2 => 3.50m - 0.50m,
_ => 3.50m - 1.00m
},
Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m +
2.00m,
Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m -
1.00m,
Bus b => 5.00m,
DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m,
DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m,
DeliveryTruck t => 10.00m,
{ } => throw new ArgumentException(message: "Not a known vehicle
type", paramName: nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))
};
Na amostra anterior, o uso de uma expressão recursiva significa não repetir os braços
Car e Taxi contendo braços filho que testam o valor da propriedade. Essa técnica não é
usada para os braços Bus e DeliveryTruck porque esses estão testando intervalos da
propriedade, e não valores discretos.
Adicionar preço de horário de pico
Para um último recurso, a autoridade de pedágio quer adicionar um preço os horários
de pico. Durante os horários de pico da manhã e do final da tarde, os pedágios serão
dobrados. Essa regra afetará apenas o tráfego em uma direção: entrada para a cidade,
no período da manhã, e de saída da cidade, no período da tarde. Em outros períodos
durante o dia útil, os pedágios aumentam 50%. Nos períodos da noite e madrugada e
de manhã cedo, as tarifas são 25% mais baratas. Durante o fim de semana, a taxa é
normal, independentemente da hora. Você pode usar uma série de instruções if e
else para expressar isso usando o seguinte código:
C#
public decimal PeakTimePremiumIfElse(DateTime timeOfToll, bool inbound)
if ((timeOfToll.DayOfWeek == DayOfWeek.Saturday) ||
(timeOfToll.DayOfWeek == DayOfWeek.Sunday))
return 1.0m;
else
int hour = timeOfToll.Hour;
if (hour < 6)
return 0.75m;
else if (hour < 10)
if (inbound)
return 2.0m;
else
return 1.0m;
else if (hour < 16)
return 1.5m;
else if (hour < 20)
if (inbound)
return 1.0m;
else
return 2.0m;
else // Overnight
return 0.75m;
O código anterior funciona corretamente, mas não é legível. Você precisa encadear
todos os casos de entrada e as instruções aninhadas if para raciocinar sobre o código.
Em vez disso, você usará a correspondência de padrões para esse recurso, mas poderá
integrá-lo a outras técnicas. É possível criar uma única expressão de correspondência de
padrões que leva em conta todas as combinações de direção, dia da semana e hora. O
resultado seria uma expressão complicada. Seria difícil de ler e entender. O que dificulta
garantir a exatidão. Em vez disso, combine esses métodos para criar uma tupla de
valores que descreve de forma concisa todos os estados. Em seguida, use a
correspondência de padrões para calcular um multiplicador para o pedágio. A tupla
contém três condições distintas:
O dia é um dia da semana ou do fim de semana.
A faixa de tempo é quando o pedágio é coletado.
A direção é para a cidade ou da cidade
A tabela a seguir mostra as combinações de valores de entrada e multiplicador de
preços para os horários de pico:
Dia Hora Direção Premium
Weekday horário de pico da manhã entrada x 2,00
Weekday horário de pico da manhã saída x 1,00
Weekday hora do dia entrada x 1,50
Weekday hora do dia saída x 1,50
Weekday horário de pico do fim da tarde entrada x 1,00
Weekday horário de pico do fim da tarde saída x 2,00
Weekday noite e madrugada entrada x 0,75
Weekday noite e madrugada saída x 0,75
Fim de Semana horário de pico da manhã entrada x 1,00
Fim de Semana horário de pico da manhã saída x 1,00
Fim de Semana hora do dia entrada x 1,00
Fim de Semana hora do dia saída x 1,00
Fim de Semana horário de pico do fim da tarde entrada x 1,00
Fim de Semana horário de pico do fim da tarde saída x 1,00
Fim de Semana noite e madrugada entrada x 1,00
Fim de Semana noite e madrugada saída x 1,00
Há 16 combinações diferentes das três variáveis. Ao combinar algumas das condições,
você simplificará a expressão switch.
O sistema que coleta os pedágios usa uma estrutura DateTime para a hora em que o
pedágio foi cobrado. Construa métodos de membro que criam as variáveis da tabela
anterior. A seguinte função usa como correspondência de padrões a expressão switch
para expressar se um DateTime representa um fim de semana ou um dia útil:
C#
private static bool IsWeekDay(DateTime timeOfToll) =>
timeOfToll.DayOfWeek switch
DayOfWeek.Monday => true,
DayOfWeek.Tuesday => true,
DayOfWeek.Wednesday => true,
DayOfWeek.Thursday => true,
DayOfWeek.Friday => true,
DayOfWeek.Saturday => false,
DayOfWeek.Sunday => false
};
Esse método está correto, mas é redundante. Para simplificar, faça conforme mostrado
no código a seguir:
C#
private static bool IsWeekDay(DateTime timeOfToll) =>
timeOfToll.DayOfWeek switch
DayOfWeek.Saturday => false,
DayOfWeek.Sunday => false,
_ => true
};
Depois, adicione uma função semelhante para categorizar o tempo nos blocos:
C#
private enum TimeBand
MorningRush,
Daytime,
EveningRush,
Overnight
private static TimeBand GetTimeBand(DateTime timeOfToll) =>
timeOfToll.Hour switch
< 6 or > 19 => TimeBand.Overnight,
< 10 => TimeBand.MorningRush,
< 16 => TimeBand.Daytime,
_ => TimeBand.EveningRush,
};
Adicione um enum privado para converter cada intervalo de tempo em um valor
discreto. Em seguida, o método GetTimeBand usa padrões relacionais e padrões or
conjuntivos, ambos adicionados no C# 9.0. Um padrão relacional permite testar um
valor numérico usando < , > , <= ou >= . O padrão or testa se uma expressão
corresponde a um ou mais padrões. Você também pode usar um padrão and para
garantir que uma expressão corresponda a dois padrões distintos e um padrão not para
testar se uma expressão não corresponde a um padrão.
Depois de criar esses métodos, é possível usar outra expressão switch com o padrão de
tupla para calcular o preço premium. Você pode construir uma expressão switch com
todos os 16 braços:
C#
public decimal PeakTimePremiumFull(DateTime timeOfToll, bool inbound) =>
(IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
(true, TimeBand.MorningRush, true) => 2.00m,
(true, TimeBand.MorningRush, false) => 1.00m,
(true, TimeBand.Daytime, true) => 1.50m,
(true, TimeBand.Daytime, false) => 1.50m,
(true, TimeBand.EveningRush, true) => 1.00m,
(true, TimeBand.EveningRush, false) => 2.00m,
(true, TimeBand.Overnight, true) => 0.75m,
(true, TimeBand.Overnight, false) => 0.75m,
(false, TimeBand.MorningRush, true) => 1.00m,
(false, TimeBand.MorningRush, false) => 1.00m,
(false, TimeBand.Daytime, true) => 1.00m,
(false, TimeBand.Daytime, false) => 1.00m,
(false, TimeBand.EveningRush, true) => 1.00m,
(false, TimeBand.EveningRush, false) => 1.00m,
(false, TimeBand.Overnight, true) => 1.00m,
(false, TimeBand.Overnight, false) => 1.00m,
};
O código acima funciona, mas pode ser simplificado. Todas as oito combinações para o
fim de semana têm o mesmo pedágio. É possível substituir todas as oito pela seguinte
linha:
C#
(false, _, _) => 1.0m,
Tanto o tráfego de entrada quanto o de saída têm o mesmo multiplicador durante o dia
e a noite, nos dias úteis. Esses quatro braços switch podem ser substituídos por estas
duas linhas:
C#
(true, TimeBand.Overnight, _) => 0.75m,
(true, TimeBand.Daytime, _) => 1.5m,
O código deverá ser semelhante ao seguinte após essas duas alterações:
C#
public decimal PeakTimePremium(DateTime timeOfToll, bool inbound) =>
(IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
(true, TimeBand.MorningRush, true) => 2.00m,
(true, TimeBand.MorningRush, false) => 1.00m,
(true, TimeBand.Daytime, _) => 1.50m,
(true, TimeBand.EveningRush, true) => 1.00m,
(true, TimeBand.EveningRush, false) => 2.00m,
(true, TimeBand.Overnight, _) => 0.75m,
(false, _, _) => 1.00m,
};
Por fim, você pode remover os dois horários de pico em que é pago o preço normal.
Quando remover essas braços, substitua o false por um descarte ( _ ) no braço switch
final. O método concluído será o seguinte:
C#
public decimal PeakTimePremium(DateTime timeOfToll, bool inbound) =>
(IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
(true, TimeBand.Overnight, _) => 0.75m,
(true, TimeBand.Daytime, _) => 1.5m,
(true, TimeBand.MorningRush, true) => 2.0m,
(true, TimeBand.EveningRush, false) => 2.0m,
_ => 1.0m,
};
Este exemplo destaca uma das vantagens da correspondência de padrões: os branches
de padrões são avaliados na ordem. Se você os reorganizar para que um branch
anterior trate um dos casos posteriores, o compilador emitirá um aviso sobre o código
inacessível. Essas regras de linguagem tornam as simplificações anteriores mais fáceis
com a certeza de que o código não foi alterado.
A correspondência de padrões torna alguns tipos de código mais legíveis e oferece uma
alternativa às técnicas orientadas a objeto quando não é possível adicionar o código às
classes. A nuvem está fazendo com que os dados e a funcionalidade existam
separadamente. A forma dos dados e as operações nela não são necessariamente
descritas juntas. Neste tutorial, você utilizou os dados existentes de maneiras
completamente diferentes de sua função original. A correspondência de padrões
proporcionou a capacidade de escrever a funcionalidade que substituiu esses tipos,
ainda que não tenha sido possível estendê-los.
Próximas etapas
Baixe o código concluído no repositório dotnet/samples do GitHub. Explore os
padrões por conta própria e adicione essa técnica em suas atividades regulares de
codificação. Aprender essas técnicas lhe oferece outra maneira de abordar problemas e
criar novas funcionalidades.
Confira também
Padrões
Expressão switch
Como manipular uma exceção usando
try/catch
Artigo • 07/04/2023
A finalidade de um bloco try-catch é capturar e manipular uma exceção gerada pelo
código de trabalho. Algumas exceções podem ser manipuladas em um bloco catch e o
problema pode ser resolvido sem que a exceção seja gerada novamente. No entanto,
com mais frequência, a única coisa que você pode fazer é certificar-se de que a exceção
apropriada seja gerada.
Exemplo
Neste exemplo, IndexOutOfRangeException não é a exceção mais apropriada:
ArgumentOutOfRangeException faz mais sentido para o método porque o erro é
causado pelo argumento index passado pelo chamador.
C#
static int GetInt(int[] array, int index)
try
return array[index];
catch (IndexOutOfRangeException e) // CS0168
Console.WriteLine(e.Message);
// Set IndexOutOfRangeException to the new exception's
InnerException.
throw new ArgumentOutOfRangeException("index parameter is out of
range.", e);
Comentários
O código que causa uma exceção fica dentro do bloco try . Uma instrução catch é
adicionada imediatamente após para lidar com IndexOutOfRangeException , caso ocorra.
O bloco catch manipula o IndexOutOfRangeException e gera o
ArgumentOutOfRangeException mais apropriado. Para fornecer ao chamador tantas
informações quanto possível, considere especificar a exceção original como o
InnerException da nova exceção. Uma vez que a propriedade InnerException é somente
leitura, você precisa atribuí-la no construtor da nova exceção.
Como executar código de limpeza
usando finally
Artigo • 14/03/2023
O propósito de uma instrução finally é garantir que a limpeza necessária de objetos,
normalmente objetos que estão mantendo recursos externos, ocorra imediatamente,
mesmo que uma exceção seja lançada. Um exemplo dessa limpeza é chamar Close em
um FileStream imediatamente após o uso, em vez de esperar que o objeto passe pela
coleta de lixo feita pelo Common Language Runtime, da seguinte maneira:
C#
static void CodeWithoutCleanup()
FileStream? file = null;
FileInfo fileInfo = new FileInfo("./file.txt");
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
file.Close();
Exemplo
Para transformar o código anterior em uma instrução try-catch-finally , o código de
limpeza é separado do código funcional, da seguinte maneira.
C#
static void CodeWithCleanup()
FileStream? file = null;
FileInfo? fileInfo = null;
try
fileInfo = new FileInfo("./file.txt");
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
catch (UnauthorizedAccessException e)
Console.WriteLine(e.Message);
finally
file?.Close();
Como uma exceção pode ocorrer a qualquer momento no bloco try antes da chamada
OpenWrite() ou a própria chamada OpenWrite() poderia falhar, não há garantia de que
o arquivo esteja aberto quanto tentarmos fechá-lo. O bloco finally adiciona uma
verificação para checar se o objeto FileStream não é null antes de chamar o método
Close. Sem a verificação de null , o bloco finally poderia gerar uma
NullReferenceException própria, mas a geração de exceções em blocos finally deve
ser evitada, se possível.
Uma conexão de banco de dados é outra boa candidata a ser fechada em um bloco
finally . Como o número de conexões permitidas para um servidor de banco de dados
é, às vezes, limitado, você deve fechar conexões de banco de dados assim que possível.
Se uma exceção for gerada antes que você possa fechar a conexão, usar o bloco
finally será melhor do que aguardar a coleta de lixo.
Confira também
Instrução using
try-catch
try-finally
try-catch-finally
Novidades do C# 11
Artigo • 20/03/2023
Os seguintes recursos foram adicionados em C# 11:
Literais brutos de cadeia de caracteres
Suporte matemático genérico
Atributos genéricos
Cadeia de caracteres UTF-8 literais
Linhas novas em expressões de interpolação de cadeia de caracteres
Padrões de lista
Tipos de locais de arquivos
Membros necessários
Structs de padrão automático
Correspondência de padrão Span<char> em uma constante string
Escopo estendido nameof
IntPtr numérico
Campos ref e scoped ref
Conversão aprimorada do grupo de métodos para delegado
Ciclo de aviso 7
Baixe a versão mais recente do Visual Studio 2022 . Você também pode experimentar
todos esses recursos com o SDK do .NET 7, que pode ser baixado na página de
downloads do .NET .
7 Observação
Estamos interessados em seus comentários sobre esses recursos. Se você encontrar
problemas com qualquer um desses novos recursos, crie um problema no
repositório dotnet/roslyn .
Atributos genéricos
Você pode declarar uma classe genérica cuja classe base é System.Attribute. Este recurso
fornece uma sintaxe mais conveniente para atributos que exigem um parâmetro
System.Type. Anteriormente, você precisaria criar um atributo com um Type como
parâmetro de construtor:
C#
// Before C# 11:
public class TypeAttribute : Attribute
public TypeAttribute(Type t) => ParamType = t;
public Type ParamType { get; }
E para aplicar o atributo, você usa o operador typeof:
C#
[TypeAttribute(typeof(string))]
public string Method() => default;
Usando esse novo recurso, você pode criar um atributo genérico em vez disso:
C#
// C# 11 feature:
public class GenericAttribute<T> : Attribute { }
Em seguida, especifique o tipo de parâmetro para usar o atributo:
C#
[GenericAttribute<string>()]
public string Method() => default;
Você deve fornecer todos os parâmetros de tipo ao aplicar o atributo. Em outras
palavras, o tipo genérico deve ser totalmente construído.
No exemplo acima, os
parênteses vazios ( ( e ) ) podem ser omitidos, pois o atributo não tem argumentos.
C#
public class GenericType<T>
[GenericAttribute<T>()] // Not allowed! generic attributes must be fully
constructed types.
public string Method() => default;
Os argumentos de tipo devem atender às mesmas restrições do operador typeof. Tipos
que exigem anotações de metadados não são permitidos. Por exemplo, os seguintes
tipos não são permitidos como o parâmetro de tipo:
dynamic
string? (ou qualquer tipo de referência anulável)
(int X, int Y) (ou qualquer outro tipo de tupla usando a sintaxe de tupla C#).
Esses tipos não são representados diretamente em metadados. Elas incluem anotações
que descrevem o tipo. Em todos os casos, você pode usar o tipo subjacente em vez
disso:
object para dynamic .
string em vez de string? .
ValueTuple<int, int> em vez de (int X, int Y) .
Suporte matemático genérico
Há vários recursos de linguagem que permitem suporte matemático genérico:
Membros static virtual em interfaces
operadores verificados definidos pelo usuário
operadores de deslocamento flexíveis
operador de deslocamento para a direita sem sinal
Você pode adicionar membros static abstract ou static virtual em interfaces para
definir interfaces que incluam operadores sobrecarregados, outros membros estáticos e
propriedades estáticas. O cenário principal para esse recurso é usar operadores
matemáticos em tipos genéricos. Por exemplo, você pode implementar a interface
System.IAdditionOperators<TSelf, TOther, TResult> em um tipo que implementa o
operator + . Outras interfaces definem outras operações matemáticas ou valores bem
definidos. Você pode aprender sobre a nova sintaxe no artigo sobre interfaces.
Interfaces que incluem métodos static virtual normalmente são interfaces genéricas.
Além disso, a maioria declarará uma restrição de que o parâmetro de tipo implemente a
interface declarada.
Você pode saber mais e experimentar o recurso no tutorial Explorar membros da
interface abstrata estática ou na postagem no blog sobre Versão prévia do recurso no
.NET 6 – matemática genérica .
A matemática genérica criou outros requisitos na linguagem.
Operador de deslocamento para a direita sem sinal: antes do C# 11, para forçar um
deslocamento para a direita sem sinal, você precisaria converter qualquer tipo
inteiro com sinal em um tipo sem sinal, executar a mudança e, em seguida,
converter o resultado de volta para um tipo com sinal. A partir do C# 11, você
pode usar o >>> , o operador de deslocamento sem sinal.
Requisitos de operador de deslocamento para a direita sem sinal: o C# 11 remove o
requisito de que o segundo operando deve ser um int ou implicitamente
conversível para int . Essa alteração permite que os tipos que implementam
interfaces matemáticas genéricas sejam usados nesses locais.
operadores marcados e desmarcados definidos pelo usuário: os desenvolvedores
agora podem definir operadores aritméticos checked e unchecked . O compilador
gera chamadas para a variante correta com base no contexto atual. Você pode ler
mais sobre operadores checked no artigo sobre operadores aritméticos.
Numérico IntPtr e UIntPtr
Os tipos nint e nuint agora têm como alias System.IntPtr e System.UIntPtr,
respectivamente.
Novas linhas em interpolações de cadeia de
caracteres
O texto dentro dos caracteres { e } para uma interpolação de cadeia de caracteres
agora pode abranger várias linhas. O texto entre os marcadores { e } é analisado como
C#. Qualquer C# legal, inclusive novas linhas, é permitido. Esse recurso facilita a leitura
de interpolações de cadeia de caracteres que usam expressões C# mais longas, como
expressões switch de padrões correspondentes ou consultas LINQ.
Você pode saber mais sobre o recurso de novas linhas no artigo de interpolações de
cadeia de caracteres na referência de linguagem.
Padrões de lista
Padrões de lista estendem a correspondência de padrões para corresponder a
sequências de elementos em uma lista ou em uma matriz. Por exemplo, sequence is [1,
2, 3] é true quando a sequence é um matriz ou uma lista de três inteiros (1, 2 e 3).
Você pode fazer a correspondência de elementos usando qualquer padrão, inclusive
uma constante, tipo, propriedade e padrões relacionais. O padrão de descarte ( _ )
corresponde a qualquer elemento único e o novo padrão de intervalo ( .. ) corresponde
a qualquer sequência de zero ou mais elementos.
Você pode saber mais detalhes sobre padrões de lista no artigo de padrões
correspondentes na referência de linguagem.
Conversão aprimorada do grupo de métodos
para delegado
O padrão C# em conversões de grupo de métodos agora inclui o seguinte item:
A conversão é permitida (mas não é necessária) para usar uma instância
delegada existente que já contenha essas referências.
As versões anteriores do padrão proibiam o compilador de reutilizar o objeto delegado
criado para uma conversão de grupo de métodos. O compilador C# 11 armazena em
cache o objeto delegado criado de uma conversão de grupo de métodos e reutiliza esse
único objeto delegado. Esse recurso foi disponibilizado pela primeira vez no Visual
Studio 2022 versão 17.2 como uma versão prévia do recurso e no .NET 7 Versão Prévia
2.
Literais de cadeia de caracteres bruta
Literais de cadeia de caracteres bruta são um novo formato para literais de cadeia de
caracteres. Literais de cadeia de caracteres bruta podem conter texto arbitrário,
incluindo espaços em branco, novas linhas, aspas inseridas e outros caracteres especiais
sem a necessidade de sequências de escape. Um literal de cadeia de caracteres bruta
começa com pelo menos três caracteres de aspas duplas ("""). Ele termina com o
mesmo número de caracteres de aspas duplas. Normalmente, um literal de cadeia de
caracteres bruta usa três aspas duplas em uma única linha para iniciar a cadeia de
caracteres e três aspas duplas em uma linha separada para terminar a cadeia de
caracteres. As novas linhas que seguem as aspas de abertura e precedem as aspas de
fechamento não estão incluídas no conteúdo final:
C#
string longMessage = """
This is a long message.
It has several lines.
Some are indented
more than others.
Some should start at the first column.
Some have "quoted text" in them.
""";
Qualquer espaço em branco à esquerda das aspas duplas de fechamento será removido
do literal da cadeia de caracteres. Os literais da cadeia de caracteres bruta podem ser
combinados com a interpolação de cadeia de caracteres para incluir chaves no texto de
saída. Vários caracteres $ indicam quantas chaves consecutivas iniciam e terminam a
interpolação:
C#
var location = $$"""
You are at {{{Longitude}}, {{Latitude}}}
""";
O exemplo anterior especifica que duas chaves iniciam e terminam uma interpolação. A
terceira chave de abertura e de fechamento repetidas são incluídas na cadeia de
caracteres de saída.
Você pode saber mais sobre literais de cadeia de caracteres bruta no artigo sobre
cadeias de caracteres no guia de programação e nos artigos de referência de linguagem
sobre literais de cadeia de caracteres e cadeias de caracteres interpoladas.
Structs de padrão automático
O compilador C# 11 garante que todos os campos de um tipo struct sejam
inicializados para seu valor padrão como parte da execução de um construtor. Essa
alteração significa que qualquer campo ou propriedade automática não inicializados por
um construtor são inicializados automaticamente pelo compilador. Os structs em que o
construtor não atribui definitivamente todos os campos agora são compilados, e todos
os campos não inicializados explicitamente são definidos para o valor padrão. Você
pode ler mais sobre como essa alteração afeta a inicialização de structs no artigo sobre
structs.
Correspondência de padrão Span<char> e
ReadOnlySpan<char> em uma constante string
Você conseguiu testar se um string tinha um valor constante específico usando
padrões correspondentes para várias versões. Agora você pode usar a mesma lógica de
padrões correspondentes com variáveis que são Span<char> ou ReadOnlySpan<char> .
Escopo nameof estendido
Nomes de parâmetros de tipo e nomes de parâmetro agora estão no escopo quando
usados em uma expressão nameof em uma declaração de atributo nesse método. Essa
funcionalidade significa que você pode usar o operador nameof para especificar o nome
de um parâmetro de método em um atributo na declaração de método ou parâmetro.
Esse recurso costuma ser útil para adicionar atributos para análise anulável.
Cadeia de caracteres UTF-8 literais
Você pode especificar o sufixo u8 em um literal de cadeia de caracteres para especificar
a codificação de caracteres UTF-8. Se o aplicativo precisar de cadeias de caracteres UTF-
8, para constantes de cadeia de caracteres HTTP ou protocolos de texto semelhantes,
você pode usar esse recurso para simplificar a criação de cadeias de caracteres UTF-8.
Você pode saber mais sobre literais de cadeia de caracteres UTF-8 na seção literal de
cadeia de caracteres do artigo sobre tipos de referência internos.
Membros necessários
Você pode adicionar o modificador required a propriedades e campos para impor
construtores e chamadores para inicializar esses valores. O
System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute pode ser adicionado
aos construtores para informar ao compilador que um construtor inicializa todos os
membros necessários.
Para obter mais informações sobre os membros necessários, consulte a seção somente
init do artigo de propriedades.
Campos ref e variáveis ref scoped
Você pode declarar campos ref dentro de uma classe ref struct. Isso dá suporte a tipos
como System.Span<T> sem atributos especiais ou tipos internos ocultos.
Você pode adicionar o modificador scoped a qualquer declaração ref . Isso limita o
escopo para o qual a referência pode escapar.
Tipos de locais de arquivo
A partir do C# 11, você pode usar o modificador de acesso file para criar um tipo cuja
visibilidade está no escopo do arquivo de origem no qual ele é declarado. Esse recurso
ajuda os autores do gerador de origem a evitar colisões de nomenclatura. Você pode
saber mais sobre esse recurso no artigo sobre tipos com escopo de arquivo na
referência de linguagem.
Confira também
Novidades do .NET 7
Novidades do C# 10
Artigo • 10/05/2023
O C# 10 adiciona os seguintes recursos e aprimoramentos à linguagem C#:
Estruturas de registro
Aprimoramentos de tipos de estrutura
Manipuladores de cadeia de caracteres interpolada
Diretivas global using
Declaração de namespace com escopo de arquivo
Padrões de propriedade estendida
Aprimoramentos nas expressões lambda
Permissão de cadeias de caracteres interpoladas const
Tipos de registro podem selar ToString()
Atribuição definida aprimorada
Permissão de atribuição e declaração na mesma desconstrução
Permissão do atributo AsyncMethodBuilder em métodos
Atributo CallerArgumentExpression
Pragma #line aprimorado
Ciclo de aviso 6
O C# 10 tem suporte no .NET 6. Para obter mais informações, confira Controle de
versão da linguagem C#.
Você pode baixar o SDK mais recente do .NET 6 na página de downloads do .NET .
Você também pode baixar o Visual Studio 2022 , que inclui o SDK do .NET 6.
7 Observação
Estamos interessados em seus comentários sobre esses recursos. Se você encontrar
problemas com qualquer um desses novos recursos, crie um problema no
repositório dotnet/roslyn .
Structs de registro
Você pode declarar registros de tipo de valor usando as declarações record struct ou
readonly record struct. Agora você pode esclarecer que record é um tipo de referência
com a declaração record class .
Aprimoramentos de tipos de estrutura
O C# 10 apresenta as seguintes melhorias relacionadas aos tipos de estrutura:
Você pode declarar um construtor sem parâmetros de instância em um tipo de
estrutura e inicializar um campo ou propriedade de instância na declaração. Para
obter mais informações, consulte a seção Inicialização de struct e valores padrão
do artigo Tipos de estrutura.
Um operando à esquerda da expressão with pode ser de qualquer tipo de
estrutura ou um tipo anônimo (referência).
Manipuladores de cadeia de caracteres
interpolada
Você pode criar um tipo que compila a cadeia de caracteres resultante de uma
expressão de cadeia de caracteres interpolada. As bibliotecas .NET usam esse recurso
em muitas APIs. Você pode criar um seguindo este tutorial.
Diretivas using globais
Você pode adicionar o modificador global a qualquer diretiva de uso para instruir o
compilador ao qual a diretiva se aplica a todos os arquivos de origem na compilação.
Normalmente, são todos os arquivos de origem em um projeto.
Declaração de namespace com escopo de
arquivo
Você pode usar uma nova forma da declaração namespace para declarar que todas as
seguintes declarações são membros do namespace declarado:
C#
namespace MyNamespace;
Essa nova sintaxe salva espaço horizontal e vertical para declarações namespace .
Padrões de propriedade estendida
A partir do C# 10, você pode referenciar propriedades aninhadas ou campos dentro de
um padrão de propriedade. Por exemplo, um padrão do formulário
C#
{ Prop1.Prop2: pattern }
é válido em C# 10 e posterior e equivalente a
C#
{ Prop1: { Prop2: pattern } }
válido em C# 8.0 e posterior.
Para obter mais informações, confira a nota de proposta de recursos de Padrões de
propriedade estendida. Para obter mais informações sobre um padrão de propriedade,
confira a seção Padrão de propriedade do artigo Padrões.
Aprimoramentos da expressão lambda
O C# 10 inclui muitas melhorias na forma como as expressões lambda são tratadas:
As expressões Lambda podem ter um tipo natural, em que o compilador pode
inferir um tipo delegado da expressão lambda ou do grupo de métodos.
As expressões Lambda podem declarar um tipo de retorno quando o compilador
não pode inferi-lo.
Os atributos podem ser aplicados a expressões lambda.
Esses recursos tornam as expressões lambda mais semelhantes a métodos e funções
locais. Elas facilitam o uso de expressões lambda sem declarar uma variável de um tipo
delegado e funcionam mais perfeitamente com as novas APIs mínimas de ASP.NET
Core.
Cadeias de caracteres interpoladas constantes
No C# 10, as cadeias de caracteres const poderão ser inicializadas usando a
interpolação de cadeia de caracteres se todos os espaços reservados forem cadeias de
caracteres constantes. A interpolação de cadeia de caracteres pode criar cadeias de
caracteres constantes mais legíveis à medida que elas são criadas e usadas no aplicativo.
As expressões de espaço reservado não podem ser constantes numéricas porque essas
constantes são convertidas em cadeias de caracteres em tempo de execução. A cultura
atual pode afetar a representação de cadeia de caracteres. Saiba mais na referência de
linguagem sobre expressõesconst.
Tipos de registro podem selar ToString
No C# 10, você pode adicionar o modificador sealed ao substituir ToString em um tipo
de registro. Selar o método ToString impede que o compilador sintetize um método
ToString para qualquer tipo de registro derivado. Um sealed ToString garante que
todos os tipos de registro derivados usem o método ToString definido em um tipo de
registro base comum. Você pode saber mais sobre esse recurso no artigo sobre
registros.
Atribuição e declaração na mesma
desconstrução
Essa alteração remove uma restrição de versões anteriores do C#. Anteriormente, uma
desconstrução poderia atribuir todos os valores a variáveis existentes ou inicializar
variáveis recém-declaradas:
C#
// Initialization:
(int x, int y) = point;
// assignment:
int x1 = 0;
int y1 = 0;
(x1, y1) = point;
O C# 10 remove essa restrição:
C#
int x = 0;
(x, int y) = point;
Atribuição definitiva aprimorada
Antes do C# 10, havia muitos cenários em que a atribuição definitiva e a análise de
estado nulo produziam avisos que eram falsos positivos. Geralmente, elas envolviam
comparações com constantes boolianas, acesso a uma variável somente nas instruções
true ou false em uma instrução if e expressões de associação nulas. Esses exemplos
geraram avisos em versões anteriores do C#, mas não no C# 10:
C#
string representation = "N/A";
if ((c != null && c.GetDependentValue(out object obj)) == true)
representation = obj.ToString(); // undesired error
// Or, using ?.
if (c?.GetDependentValue(out object obj) == true)
representation = obj.ToString(); // undesired error
// Or, using ??
if (c?.GetDependentValue(out object obj) ?? false)
representation = obj.ToString(); // undesired error
O principal impacto dessa melhoria é que os avisos para atribuição definitiva e análise
de estado nulo são mais precisos.
Permitir atributo AsyncMethodBuilder em
métodos
No C# 10 e posterior, você pode especificar um construtor de métodos assíncrono
diferente para um método único, além de especificar o tipo de construtor de métodos
para todos os métodos que retornam um determinado tipo de tarefa. Um construtor de
métodos assíncrono personalizado permite cenários avançados de ajuste de
desempenho em que um determinado método pode se beneficiar de um construtor
personalizado.
Para saber mais, confira a seção sobre AsyncMethodBuilder no artigo que aborda
atributos lidos pelo compilador.
Diagnóstico de atributo
CallerArgumentExpression
Você pode usar o parâmetro
System.Runtime.CompilerServices.CallerArgumentExpressionAttribute para especificar
um parâmetro que o compilador substitui pela representação de texto de outro
argumento. Esse recurso permite que as bibliotecas criem diagnósticos mais específicos.
O código a seguir testa uma condição. Se a condição for falsa, a mensagem de exceção
conterá a representação de texto do argumento passado para condition :
C#
public static void Validate(bool condition,
[CallerArgumentExpression("condition")] string? message=null)
if (!condition)
throw new InvalidOperationException($"Argument failed validation:
<{message}>");
Você pode saber mais sobre esse recurso no artigo sobre Atributos de informações do
chamador na seção de referência de linguagem.
Pragma de #line avançado
O C# 10 dá suporte a um novo formato para o pragma #line . Você provavelmente não
usará o novo formato, mas verá os respectivos. Os aprimoramentos permitem uma saída
mais refinada em DSLs (linguagens específicas do domínio), como Razor. O mecanismo
Razor usa essas melhorias para aprimorar a experiência de depuração. Você encontrará
depuradores que podem realçar a fonte Razor com mais precisão. Para saber mais sobre
a nova sintaxe, confira o artigo sobre Diretivas de pré-processador na referência de
linguagem. Você também pode ler a especificação de recurso para exemplos baseados
em Razor.
Novidades do C# 9.0
Artigo • 10/05/2023
O C# 9.0 adiciona os seguintes recursos e aprimoramentos à linguagem C#:
Registros
Setters somente init
Instruções de nível superior
Melhorias na correspondência de padrões
Desempenho e interoperabilidade
Inteiros de tamanho nativo
Ponteiros de função
Suprimir a emissão do sinalizador localsinit
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
Suporte para geradores de código
Inicializadores de módulo
Novos recursos para métodos parciais
Ciclo de aviso 5
O C# 9.0 tem suporte no .NET 5. Para obter mais informações, consulte Controle de
versão da linguagem C#.
Você pode baixar o SDK mais recente do .NET na página de downloads do .NET .
7 Observação
Estamos interessados em seus comentários sobre esses recursos. Se você encontrar
problemas com qualquer um desses novos recursos, crie um problema no
repositório dotnet/roslyn .
Tipos de registro
O C# 9.0 introduz tipos de registro. Você usa a palavra-chave record para definir um
tipo de referência que fornece funcionalidade interna para encapsular dados. Você pode
criar tipos de registro com propriedades imutáveis usando parâmetros posicionais ou
sintaxe de propriedade padrão:
C#
public record Person(string FirstName, string LastName);
C#
public record Person
public required string FirstName { get; init; }
public required string LastName { get; init; }
};
Você também pode criar tipos de registros com propriedades e campos mutáveis:
C#
public record Person
public required string FirstName { get; set; }
public required string LastName { get; set; }
};
Embora os registros possam ser mutáveis, eles se destinam principalmente a dar
suporte a modelos de dados imutáveis. O tipo de registro oferece os seguintes recursos:
Sintaxe concisa para criar um tipo de referência com propriedades imutáveis
Comportamento útil para um tipo de referência centrado em dados:
Igualdade de valor
Sintaxe concisa para mutação não destrutiva
Formatação interna para exibição
Suporte para hierarquias de herança
Você pode usar tipos de estrutura para criar tipos centrados em dados que fornecem
igualdade de valor e pouco ou nenhum comportamento. Mas para modelos de dados
relativamente grandes, os tipos de estrutura têm algumas desvantagens:
Eles não dão suporte à herança.
Eles são menos eficientes em determinar a igualdade de valor. Para tipos de valor,
o método ValueType.Equals usa reflexão para localizar todos os campos. Para
registros, o compilador gera o método Equals . Na prática, a implementação da
igualdade de valor nos registros é mensuravelmente mais rápida.
Eles usam mais memória em alguns cenários, pois cada instância tem uma cópia
completa de todos os dados. Os tipos de registro são tipos de referência, portanto,
uma instância de registro contém apenas uma referência aos dados.
Sintaxe posicional para definição de propriedade
Você pode usar parâmetros posicionais para declarar propriedades de um registro e
inicializar os valores de propriedade ao criar uma instância:
C#
public record Person(string FirstName, string LastName);
public static void Main()
Person person = new("Nancy", "Davolio");
Console.WriteLine(person);
// output: Person { FirstName = Nancy, LastName = Davolio }
Quando você usa a sintaxe posicional para definição de propriedade, o compilador cria:
Uma propriedade pública init-only implementada automaticamente para cada
parâmetro posicional fornecido na declaração de registro. Uma propriedade init-
only só pode ser definida no construtor ou usando um inicializador de
propriedade.
Um construtor primário cujos parâmetros correspondem aos parâmetros
posicionais na declaração de registro.
Um método Deconstruct com um parâmetro out para cada parâmetro posicional
fornecido na declaração de registro.
Para obter mais informações, consulte Sintaxe posicional no artigo de referência de
linguagem C# sobre registros.
Imutabilidade
Um tipo de registro não é necessariamente imutável. Você pode declarar propriedades
com acessadores set e campos que não são readonly . Mas, embora os registros
possam ser mutáveis, eles facilitam a criação de modelos de dados imutáveis. As
propriedades criadas usando a sintaxe posicional são imutáveis.
A imutabilidade pode ser útil quando você deseja que um tipo centrado em dados seja
thread-safe ou um código hash permaneça o mesmo em uma tabela de hash. Ela pode
evitar bugs que ocorrem quando você passa um argumento por referência a um
método e o método altera inesperadamente o valor do argumento.
Os recursos exclusivos para tipos de registro são implementados por métodos
sintetizados pelo compilador e nenhum desses métodos compromete a imutabilidade
por meio da modificação do estado do objeto.
Igualdade de valor
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 campo corresponderem.
Para outros tipos de referência, igualdade significa identidade. Ou seja, duas variáveis de
um tipo de referência são iguais se referirem ao mesmo objeto.
O exemplo a seguir ilustra a igualdade de valores dos tipos de registro:
C#
public record Person(string FirstName, string LastName, string[]
PhoneNumbers);
public static void Main()
var phoneNumbers = new string[2];
Person person1 = new("Nancy", "Davolio", phoneNumbers);
Person person2 = new("Nancy", "Davolio", phoneNumbers);
Console.WriteLine(person1 == person2); // output: True
person1.PhoneNumbers[0] = "555-1234";
Console.WriteLine(person1 == person2); // output: True
Console.WriteLine(ReferenceEquals(person1, person2)); // output: False
Em tipos class , você pode substituir manualmente métodos e operadores de igualdade
para obter igualdade de valor, mas desenvolver e testar esse código seria demorado e
propenso a erros. Ter essa funcionalidade interna impede que bugs resultantes do
esquecimento atualizem o código de substituição personalizado quando propriedades
ou campos forem adicionados ou alterados.
Para obter mais informações, consulte Sintaxe posicional no artigo de referência de
linguagem C# sobre registros.
Mutação não destrutiva
Se você precisar modificar propriedades imutáveis de uma instância de registro, poderá
usar uma expressão with para obter mutação não estruturativa. Uma expressão with
faz uma nova instância de registro que é uma cópia de uma instância de registro
existente, com propriedades e campos especificados modificados. Use a sintaxe do
inicializador de objetos para especificar os valores a serem alterados, conforme
mostrado no exemplo a seguir:
C#
public record Person(string FirstName, string LastName)
public string[] PhoneNumbers { get; init; }
public static void Main()
Person person1 = new("Nancy", "Davolio") { PhoneNumbers = new string[1]
};
Console.WriteLine(person1);
// output: Person { FirstName = Nancy, LastName = Davolio, PhoneNumbers
= System.String[] }
Person person2 = person1 with { FirstName = "John" };
Console.WriteLine(person2);
// output: Person { FirstName = John, LastName = Davolio, PhoneNumbers =
System.String[] }
Console.WriteLine(person1 == person2); // output: False
person2 = person1 with { PhoneNumbers = new string[1] };
Console.WriteLine(person2);
// output: Person { FirstName = Nancy, LastName = Davolio, PhoneNumbers
= System.String[] }
Console.WriteLine(person1 == person2); // output: False
person2 = person1 with { };
Console.WriteLine(person1 == person2); // output: True
Para obter mais informações, consulte a mutação não estruturativa no artigo de
referência da linguagem C# sobre registros.
Formatação interna para exibição
Os tipos de registro têm um método ToString gerado pelo compilador que exibe os
nomes e valores de propriedades e campos públicos. O método ToString retorna uma
cadeia de caracteres do seguinte formato:
<nome do tipo de registro> { <nome da propriedade> = <valor>, <nome da
propriedade> = <valor>, ...}
Para tipos de referência, o nome do tipo do objeto ao qual a propriedade se refere é
exibido em vez do valor da propriedade. No exemplo a seguir, a matriz é um tipo de
referência, portanto System.String[] é exibida em vez dos valores reais do elemento de
matriz:
Person { FirstName = Nancy, LastName = Davolio, ChildNames = System.String[]
}
Para obter mais informações, consulte Sintaxe posicional no artigo de referência de
linguagem C# sobre registros.
Herança
Um registro pode herdar de outro registro. No entanto, um registro não pode herdar de
uma classe, e uma classe não pode herdar de um registro.
O exemplo a seguir ilustra a herança com sintaxe de propriedade posicional:
C#
public abstract record Person(string FirstName, string LastName);
public record Teacher(string FirstName, string LastName, int Grade)
: Person(FirstName, LastName);
public static void Main()
Person teacher = new Teacher("Nancy", "Davolio", 3);
Console.WriteLine(teacher);
// output: Teacher { FirstName = Nancy, LastName = Davolio, Grade = 3 }
Para que duas variáveis de registro sejam iguais, o tipo de tempo de execução deve ser
igual. Os tipos das variáveis de conteúdo podem ser diferentes. Isso é ilustrado no
exemplo de código a seguir:
C#
public abstract record Person(string FirstName, string LastName);
public record Teacher(string FirstName, string LastName, int Grade)
: Person(FirstName, LastName);
public record Student(string FirstName, string LastName, int Grade)
: Person(FirstName, LastName);
public static void Main()
Person teacher = new Teacher("Nancy", "Davolio", 3);
Person student = new Student("Nancy", "Davolio", 3);
Console.WriteLine(teacher == student); // output: False
Student student2 = new Student("Nancy", "Davolio", 3);
Console.WriteLine(student2 == student); // output: True
No exemplo, todas as instâncias têm as mesmas propriedades e os mesmos valores de
propriedade. Mas student == teacher retorna False , embora ambas sejam variáveis de
tipo Person . E student == student2 retorna True , embora uma seja uma variável Person
e outra seja uma variável Student .
Todas as propriedades públicas e campos de tipos derivados e base estão incluídos na
saída ToString , conforme mostrado no exemplo a seguir:
C#
public abstract record Person(string FirstName, string LastName);
public record Teacher(string FirstName, string LastName, int Grade)
: Person(FirstName, LastName);
public record Student(string FirstName, string LastName, int Grade)
: Person(FirstName, LastName);
public static void Main()
Person teacher = new Teacher("Nancy", "Davolio", 3);
Console.WriteLine(teacher);
// output: Teacher { FirstName = Nancy, LastName = Davolio, Grade = 3 }
Para obter mais informações, consulte Herança no artigo de referência de linguagem C#
sobre registros.
Setters somente init
Os setters init apenas fornecem sintaxe consistente para inicializar membros de um
objeto. Inicializadores de propriedade deixam claro qual valor está definindo qual
propriedade. A desvantagem é que essas propriedades devem ser configuráveis. A partir
do C# 9.0, você pode criar acessadores init m vez de set para propriedades e
indexadores. Os chamadores podem usar a sintaxe do inicializador de propriedades
para definir esses valores em expressões de criação, mas essas propriedades são lidas
somente depois que a construção for concluída. Os setters init apenas fornecem uma
janela para alterar o estado. Essa janela fecha quando a fase de construção termina. A
fase de construção termina efetivamente após toda a inicialização, incluindo
inicializadores de propriedades e com expressões concluídas.
Você pode declarar setters somente init em qualquer tipo que você escrever. Por
exemplo, o struct a seguir define uma estrutura de observação meteorológica:
C#
public struct WeatherObservation
public DateTime RecordedAt { get; init; }
public decimal TemperatureInCelsius { get; init; }
public decimal PressureInMillibars { get; init; }
public override string ToString() =>
$"At {RecordedAt:h:mm tt} on {RecordedAt:M/d/yyyy}: " +
$"Temp = {TemperatureInCelsius}, with {PressureInMillibars}
pressure";
Os chamadores podem usar a sintaxe do inicializador de propriedades para definir os
valores, preservando ainda a imutabilidade:
C#
var now = new WeatherObservation
RecordedAt = DateTime.Now,
TemperatureInCelsius = 20,
PressureInMillibars = 998.0m
};
Uma tentativa de alterar uma observação após a inicialização resulta em um erro do
compilador:
C#
// Error! CS8852.
now.TemperatureInCelsius = 18;
Somente setters init podem ser úteis para definir propriedades de classe base de classes
derivadas. Eles também podem definir propriedades derivadas por meio de auxiliares
em uma classe base. Registros posicionais declaram propriedades usando apenas setters
init. Esses setters são usados em expressões with. Você pode declarar setters somente
init para qualquer class , struct ou record que você define.
Para obter mais informações, confira init (Referência C#).
Instruções de nível superior
Instruções de nível superior removem a cerimônia desnecessária de muitos aplicativos.
Considere o programa canônico "Olá, Mundo!":
C#
using System;
namespace HelloWorld
class Program
static void Main(string[] args)
Console.WriteLine("Hello World!");
Só há uma linha de código que faz qualquer coisa. Com instruções de nível superior,
você pode substituir todo esse código clichê pela diretiva using e pela única linha que
faz o trabalho:
C#
using System;
Console.WriteLine("Hello World!");
Se você quisesse um programa de uma linha, poderia remover a diretiva using e usar o
nome de tipo totalmente qualificado:
C#
System.Console.WriteLine("Hello World!");
Somente um arquivo em seu aplicativo pode usar instruções de nível superior. Se o
compilador encontrar instruções de nível superior em vários arquivos de origem, será
um erro. Também será um erro se você combinar instruções de nível superior com um
método de ponto de entrada de programa declarado, normalmente um método Main .
De certa forma, você pode pensar que um arquivo contém as instruções que
normalmente estariam no método Main de uma classe Program .
Um dos usos mais comuns para esse recurso é a criação de materiais didáticos. Os
desenvolvedores iniciantes do C# podem escrever o "Olá, Mundo!" canônico em uma ou
duas linhas de código. Nenhuma cerimônia extra é necessária. No entanto, os
desenvolvedores experientes também encontrarão muitos usos para esse recurso.
Instruções de nível superior permitem uma experiência semelhante a script para
experimentação semelhante ao que os notebooks Jupyter fornecem. Instruções de nível
superior são ótimas para pequenos programas de console e utilitários. Azure Functions
é um caso de uso ideal para instruções de nível superior.
Mais importante, as instruções de nível superior não limitam o escopo ou a
complexidade do aplicativo. Essas instruções podem acessar ou usar qualquer classe
.NET. Eles também não limitam o uso de argumentos de linha de comando nem
retornam valores. Instruções de nível superior podem acessar uma matriz de cadeias de
caracteres nomeadas args . Se as instruções de nível superior retornarem um valor
inteiro, esse valor se tornará o código de retorno inteiro de um método sintetizado
Main . As instruções de nível superior podem conter expressões assíncronas. Nesse caso,
o ponto de entrada sintetizado retorna um Task ou Task<int> .
Para obter mais informações, consulte instruções de nível superior no Guia de
Programação em C#.
Melhorias na correspondência de padrões
O C# 9 inclui novas melhorias de correspondência de padrões:
Padrões de tipo correspondem a um objeto correspondente a um tipo específico
Padrões entre parênteses impõem ou enfatizam a precedência de combinações de
padrões
Padrões conjuntivos and exigem que ambos os padrões correspondam
Padrões conjuntivos or exigem que ambos os padrões correspondam
Padrões conjuntivos not exigem que ambos os padrões correspondam
Os padrões relacionais exigem que a entrada seja menor que, maior que, menor
ou igual ou maior que ou igual a uma determinada constante.
Esses padrões enriquecem a sintaxe para padrões. Considere estes exemplos:
C#
public static bool IsLetter(this char c) =>
c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';
Com parênteses opcionais para deixar claro que and tem precedência maior que or :
C#
public static bool IsLetterOrSeparator(this char c) =>
c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ',';
Um dos usos mais comuns é uma nova sintaxe para uma verificação nula:
C#
if (e is not null)
// ...
Qualquer um desses padrões pode ser usado em qualquer contexto em que os padrões
são permitidos: expressões de padrão is , expressões switch , padrões aninhados e o
padrão de uma instrução switch do rótulo case .
Para obter mais informações, confira Padrões (referência de C#).
Para obter mais informações, consulte as seções padrões relacionais e padrões lógicos
do artigo Padrões.
Desempenho e interoperabilidade
Três novos recursos melhoram o suporte para bibliotecas nativas de interoperabilidade
e de baixo nível que exigem alto desempenho: inteiros de tamanho nativo, ponteiros de
função e omitindo o sinalizador localsinit .
Inteiros de tamanho nativo, nint e nuint , são tipos inteiros. Eles são expressos pelos
tipos subjacentes System.IntPtr e System.UIntPtr. O compilador apresenta conversões e
operações adicionais para esses tipos como ints nativos. Inteiros de tamanho nativo
definem propriedades para MaxValue ou MinValue . Esses valores não podem ser
expressos como constantes de tempo de compilação porque dependem do tamanho
nativo de um inteiro no computador de destino. Esses valores são lidos somente em
tempo de execução. Você pode usar valores constantes no intervalo nint [ int.MinValue
.. int.MaxValue ]. Você pode usar valores constantes no intervalo nuint [ uint.MinValue ..
uint.MaxValue ]. O compilador executa a dobra constante para todos os operadores
unários e binários usando e tipos System.Int32 e System.UInt32. Se o resultado não se
encaixar em 32 bits, a operação será executada em tempo de execução e não será
considerada uma constante. Inteiros de tamanho nativo podem aumentar o
desempenho em cenários em que a matemática inteiro é usada extensivamente e
precisa ter o desempenho mais rápido possível. Para obter mais informações, confira os
tipos nint e nuint.
Os ponteiros de função fornecem uma sintaxe fácil para acessar os opcodes de IL ldftn
e calli . Você pode declarar ponteiros de função usando uma nova sintaxe delegate* .
Um tipo delegate* é um tipo de ponteiro. Invocar o tipo delegate* usa calli , em
contraste com um delegado que usa callvirt no método Invoke() . Sintaticamente, as
invocações são idênticas. A invocação do ponteiro de função usa a convenção de
chamada managed . Você adiciona a palavra-chave unmanaged após a sintaxe delegate*
para declarar que deseja a convenção de chamada unmanaged . Outras convenções de
chamada podem ser especificadas usando atributos na declaração delegate* . Para
obter mais informações, consulte Códigos não seguros e tipos de ponteiro.
Por fim, você pode adicionar o System.Runtime.CompilerServices.SkipLocalsInitAttribute
para instruir o compilador a não emitir o sinalizador localsinit . Esse sinalizador instrui
o CLR a inicializar zero todas as variáveis locais. O sinalizador localsinit tem sido o
comportamento padrão para C# desde 1.0. No entanto, a inicialização extra zero pode
ter impacto mensurável no desempenho em alguns cenários. Em particular, quando
você usa o stackalloc . Nesses casos, você pode adicionar o atributo
SkipLocalsInitAttribute. Você pode adicioná-lo a um único método ou propriedade, ou a
um class , struct , interface ou até mesmo a um módulo. Esse atributo não afeta
métodos abstract ; ele afeta o código gerado para a implementação. Para obter mais
informações, consulte atributo SkipLocalsInit.
Esses recursos podem melhorar o desempenho em alguns cenários. Elas devem ser
usadas somente após um benchmarking cuidadoso antes e depois da adoção. O código
que envolve inteiros de tamanho nativo deve ser testado em várias plataformas de
destino com tamanhos inteiros diferentes. Os outros recursos exigem código não
seguro.
Ajustar e concluir recursos
Muitos dos outros recursos ajudam você a escrever código com mais eficiência. No C#
9.0, você pode omitir o tipo em uma newexpressão quando o tipo do objeto criado já
for conhecido. O uso mais comum está em declarações de campo:
C#
private List<WeatherObservation> _observations = new();
O tipo de destino new também pode ser usado quando você precisa criar um novo
objeto para passar como um argumento para um método. Considere um método
ForecastFor() com a seguinte assinatura:
C#
public WeatherForecast ForecastFor(DateTime forecastDate,
WeatherForecastOptions options)
Você pode chamá-lo da seguinte maneira:
C#
var forecast = station.ForecastFor(DateTime.Now.AddDays(2), new());
Outro bom uso para esse recurso é combiná-lo com propriedades somente init para
inicializar um novo objeto:
C#
WeatherStation station = new() { Location = "Seattle, WA" };
Você pode retornar uma instância criada pelo construtor padrão usando uma instrução
return new(); .
Um recurso semelhante melhora a resolução de tipo de destino de expressões
condicionais. Com essa alteração, as duas expressões não precisam ter uma conversão
implícita de uma para outra, mas podem ter conversões implícitas em um tipo de
destino. Você provavelmente não observará essa alteração. O que você observará é que
algumas expressões condicionais que anteriormente exigiam conversões ou não seriam
compiladas agora apenas funcionam.
A partir do C# 9.0, você pode adicionar o modificador static a expressões lambda ou
métodos anônimos. Expressões lambda estáticas são análogas às funções locais static :
um método lambda estático ou anônimo não pode capturar variáveis locais ou o estado
da instância. O modificador static impede a captura acidental de outras variáveis.
Tipos de retorno covariantes fornecem flexibilidade para os tipos de retorno de
métodos de substituição. Um método de substituição pode retornar um tipo derivado
do tipo de retorno do método base substituído. Isso pode ser útil para registros e para
outros tipos que dão suporte a clones virtuais ou métodos de fábrica.
Além disso, o loop foreach reconhecerá e usará um método GetEnumerator de extensão
que, de outra forma, satisfaz o padrão foreach . Essa alteração significa que foreach é
consistente com outras construções baseadas em padrão, como o padrão assíncrono e
a desconstrução baseada em padrão. Na prática, essa alteração significa que você pode
adicionar suporte foreach a qualquer tipo. Você deve limitar seu uso ao enumerar um
objeto faz sentido em seu design.
Em seguida, você pode usar descartes como parâmetros para expressões lambda. Essa
conveniência permite que você evite nomear o argumento e o compilador pode evitar
usá-lo. Você usa o _ para qualquer argumento. Para obter mais informações, consulte a
seção Parâmetros de entrada de uma expressão lambda do artigo Expressões lambda.
Por fim, agora você pode aplicar atributos a funções locais. Por exemplo, você pode
aplicar anotações de atributo anuláveis a funções locais.
Suporte para geradores de código
Dois recursos finais dão suporte a geradores de código C#. Geradores de código C# são
um componente que você pode escrever que é semelhante a um analisador roslyn ou
correção de código. A diferença é que os geradores de código analisam o código e
gravam novos arquivos de código-fonte como parte do processo de compilação. Um
gerador de código típico pesquisa código para atributos ou outras convenções.
Um gerador de código lê atributos ou outros elementos de código usando as APIs de
análise Roslyn. Com base nessa informação, ele adiciona um novo código à compilação.
Geradores de origem só podem adicionar código; eles não têm permissão para
modificar nenhum código existente na compilação.
Os dois recursos adicionados para geradores de código são extensões à sintaxe parcial
do método e inicializadores de módulo. Primeiro, as alterações em métodos parciais.
Antes do C# 9.0, os métodos parciais são private , mas não podem especificar um
modificador de acesso, ter um retorno void e não podem ter parâmetros out . Essas
restrições significaram que, se nenhuma implementação de método for fornecida, o
compilador removerá todas as chamadas para o método parcial. O C# 9.0 remove essas
restrições, mas exige que as declarações de método parcial tenham uma
implementação. Os geradores de código podem fornecer essa implementação. Para
evitar a introdução de uma alteração significativa, o compilador considera qualquer
método parcial sem um modificador de acesso para seguir as regras antigas. Se o
método parcial incluir o modificador de acesso private , as novas regras regem esse
método parcial. Para obter mais informações, consulte o método parcial (Referência de
C#).
O segundo novo recurso para geradores de código são inicializadores de módulo.
Inicializadores de módulo são métodos que têm o atributo ModuleInitializerAttribute
anexado a eles. Esses métodos serão chamados pelo runtime antes de qualquer outro
acesso de campo ou invocação de método dentro de todo o módulo. Um método
inicializador de módulo:
Deve ser estático
Deve ser sem parâmetros
Deve retornar nulo
Não deve ser um método genérico
Não deve estar contido em uma classe genérica
Deve ser acessível por meio do módulo que o contém
Esse último ponto de marcador efetivamente significa que o método e sua classe de
contenção devem ser internos ou públicos. O método não pode ser uma função local.
Para obter mais informações, consulte o ModuleInitializeratributo .
O histórico da linguagem C#
Artigo • 09/03/2023
Este artigo fornece um histórico de cada versão principal da linguagem C#. A equipe C#
continua a inovar e a adicionar novos recursos. Os status detalhados do recurso de
linguagem, incluindo os recursos considerados para as versões futuras, podem ser
encontrados no repositório dotnet/roslyn no GitHub.
) Importante
A linguagem C# depende de tipos e métodos nos quais a especificação C# é
definida como uma biblioteca padrão para alguns dos recursos. A plataforma .NET
fornece esses tipos e métodos em alguns pacotes. Um exemplo é o processamento
de exceção. Cada instrução ou expressão throw é verificada para garantir que o
objeto que está sendo gerado é derivado de Exception. Da mesma forma, cada
catch é verificado para garantir que o tipo que está sendo capturado é derivado
de Exception. Cada versão pode adicionar novos requisitos. Para usar os recursos
de linguagem mais recentes em ambientes mais antigos, talvez seja necessário
instalar bibliotecas específicas. Essas dependências estão documentadas na página
de cada versão específica. Saiba mais sobre as relações entre linguagem e
biblioteca para obter informações sobre essa dependência.
C# versão 11
Lançado em novembro de 2022
Os seguintes recursos foram adicionados em C# 11:
Literais brutos de cadeia de caracteres
Suporte matemático genérico
Atributos genéricos
Cadeia de caracteres UTF-8 literais
Linhas novas em expressões de interpolação de cadeia de caracteres
Padrões de lista
Tipos de locais de arquivos
Membros necessários
Structs de padrão automático
Correspondência de padrão Span<char> em uma constante string
Escopo estendido nameof
IntPtr numérico
Campos ref e scoped ref
Conversão aprimorada do grupo de métodos para delegado
Ciclo de aviso 7
O C# 11 apresenta matemática genérica e vários recursos que dão suporte a essa meta.
Você pode escrever algoritmos numéricos uma vez para todos os tipos de número. Há
mais recursos para facilitar o trabalho com os tipos de struct , como membros
necessários e structs de padrão automático. O trabalho com cadeias de caracteres fica
mais fácil com literais de cadeia de caracteres brutas, nova linha em interpolações de
cadeia de caracteres e literais de cadeia de caracteres UTF-8. Recursos como os tipos de
locais de arquivo permitem que os geradores de origem sejam mais simples. Por fim, os
padrões de lista adicionam mais suporte para correspondência de padrões.
C# versão 10
Lançado em novembro de 2021
C# 10 adiciona os seguintes recursos e aprimoramentos à linguagem C#:
Estruturas de registro
Aprimoramentos de tipos de estrutura
Manipuladores de cadeia de caracteres interpolada
Diretivas global using
Declaração de namespace com escopo de arquivo
Padrões de propriedade estendida
Aprimoramentos nas expressões lambda
Permissão de cadeias de caracteres interpoladas const
Tipos de registro podem selar ToString()
Atribuição definida aprimorada
Permissão de atribuição e declaração na mesma desconstrução
Permissão do atributo AsyncMethodBuilder em métodos
Atributo CallerArgumentExpression
Pragma #line aprimorado
Mais recursos estavam disponíveis no modo de visualização. Para usar esses recursos,
você deve definir <LangVersion> como Preview em seu projeto:
Atributos genéricos, posteriormente neste artigo.
membros abstratos estáticos em interfaces
C# 10 continua trabalhando em temas de remoção de cerimônia, separação de dados
de algoritmos e aprimoramento do desempenho para o Runtime do .NET.
Muitos dos recursos significam que você digitará menos código para expressar os
mesmos conceitos. Structs de registro sintetizam muitos dos mesmos métodos que as
classes de registro. Structs e tipos anônimos dão suporte a expressões. Diretivas de uso
global e declarações de namespace com escopo de arquivo significam expressar
dependências e organização de namespace com mais clareza. Melhorias lambda
facilitam a declaração de expressões lambda onde são usadas. Novos padrões de
propriedade e melhorias de desconstrução criam um código mais conciso.
Os novos manipuladores de cadeia de caracteres interpolados e o comportamento
AsyncMethodBuilder podem melhorar o desempenho. Esses recursos de linguagem
foram aproveitados no .NET Runtime para obter aprimoramentos de desempenho no
.NET 6.
C# 10 também marca uma mudança para a cadência anual de versões do .NET. Como
nem todos os recursos podem ser concluídos em um período anual, você pode
experimentar alguns recursos de "versão prévia" no C# 10. Os atributos genéricos e os
membros abstratos estáticos em interfaces podem ser usados, mas eles são versões
prévias do recurso e podem ser alterados antes da versão final.
C# versão 9
Lançado em novembro de 2020
C# 9 foi lançada com o .NET 5. É a versão de linguagem padrão para qualquer assembly
direcionado à versão do .NET 5. Ela contém os seguintes recursos novos e aprimorados:
Registros
Setters somente init
Instruções de nível superior
Melhorias na correspondência de padrões
Desempenho e interoperabilidade
Inteiros de tamanho nativo
Ponteiros de função
Suprimir a emissão do sinalizador localsinit
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
Suporte para geradores de código
Inicializadores de módulo
Novos recursos para métodos parciais
C# 9 continua três dos temas de versões anteriores: remoção da cerimônia, separação