Você está na página 1de 286

Página 1

SolisIlustrado C # 2010THE EXPERT'S VOICE ® IN .NETIlustradoC # 2010Escrito e ilustrado porDaniel M.


SolisâniondisponívelC # ilustrado de forma clara, concisa e visual

Página 2

Baixe a partir de Wow! e-book <www.wowebook.com>

Página 3

Ilustrado C # 2010■ ■ ■Daniel M. Solis

Página 4

Ilustrado C # 2010Copyright © 2010 por Daniel M. SolisTodos os direitos reservados. Nenhuma parte
deste trabalho pode ser reproduzida ou transmitida de qualquer forma ou porqualquer meio, eletrônico
ou mecânico, incluindo fotocópia, gravação ou por qualquer informaçãosistema de armazenamento ou
recuperação, sem a permissão prévia por escrito do proprietário dos direitos autorais eO editor.ISBN-13
(pbk): 978-1-4302-3282-7ISBN-13 (eletrônico): 978-1-4302-3283-4Impresso e encadernado nos Estados
Unidos da América 9 8 7 6 5 4 3 2 1Presidente e editor: Paul ManningEditor principal: Ewan
BuckinghamEditor de Desenvolvimento: Matthew MoodieConselho Editorial: Steve Anglin, Mark
Beckner, Ewan Buckingham, Gary Cornell, JonathanGennick, Jonathan Hassell, Michelle Lowman,
Matthew Moodie, Duncan Parkes, JeffreyPepper, Frank Pohlmann, Douglas Pundick, Ben Renow-Clarke,
Dominic Shakeshaft, MattWade, Tom WelshEditor Coordenador: Jennifer L. BlackwellEditor de texto: Kim
WimpsettCompositor: Mary SudulIndexador: Serviços de indexação e revisão de BIMArtista: Daniel
SolisDesigner da capa: Anna IshchenkoDistribuído para o mercado de livros em todo o mundo pela
Springer Science + Business Media, LLC.,233 Spring Street, 6th Floor, New York, NY 10013. Telefone 1-
800-SPRINGER, fax (201) 348-4505,envie um e-mail para orders- ny@springer-sbm.com ou visite
www.springeronline.com.Para obter informações sobre traduções, envie um e-mail para
rights@apress.com ou visite www.apress.com.Os livros da Apress e amigos de ED podem ser adquiridos
a granel para fins acadêmicos, corporativos ou promocionaisusar. Versões e licenças de e-books também
estão disponíveis para a maioria dos títulos. Para obter mais informações, consultenossa página da web
Special Bulk Sales - eBook Licensing em www.apress.com/info/bulksales.As informações neste livro são
distribuídas “como estão”, sem garantia. Embora todoprecaução foi tomada na preparação deste
trabalho, nem o (s) autor (es) nem a Apress deverãotem qualquer responsabilidade para com qualquer
pessoa ou entidade no que diz respeito a qualquer perda ou dano causado ou alegadamentecausados
direta ou indiretamente pelas informações contidas neste trabalho.O código-fonte deste livro está
disponível para leitores em www.apress.com.

Página 5

Eu gostaria de dedicar este livro aSian; aos meus pais, Sal e Amy;e para Sue.
Página 6

ivConteúdo
resumidoConteúdo................................................. .................................................. ............... .. vSobre o
autor ............................................... .................................................. . . xx
iAgradecimentos ................................................. ............................................ .. xxv
iIntrodução ................................................. .................................................. ... .. xxvi i■ Capítulo 1: C # e o
.NET Framework ........................................ ...................... . 1■ Capítulo 2: Visão geral da programação C #
.......................................... ............... ..1 5■ Capítulo 3: Tipos, armazenamento e variáveis
........................................ ................ .. 3 1■ Capítulo 4: Classes: O
básico .......................................... .............................. .. 4 9■ Capítulo 5:
Métodos ............................................. .............................................. 6 7■ Capítulo 6: Mais sobre as
aulas ........................................... .......................... ... 10 9■ Capítulo 7: Classes e
herança ........................................... .................... ..16 1■ Capítulo 8: Expressões e
operadores ........................................... ............... ... 20 1■ Capítulo 9:
Declarações ............................................. ..................................... ... 23 9■ Capítulo 10: Namespaces e
Assemblies ........................................... .......... ..26 9■ Capítulo 11:
Exceções ............................................. .................................... ... 29 7■ Capítulo 12:
Structs ............................................. .......................................... ... 31 7■ Capítulo 13:
Enumerações ............................................. ............................... ... 32 7■ Capítulo 14:
Arrays ............................................. ........................................... ... 34 1■ Capítulo 15:
Delegados ............................................. ...................................... ... 36 9■ Capítulo 16:
Eventos ............................................. ........................................... ... 39 1■ Capítulo 17:
Interfaces ............................................. ..................................... ... 40 9■ Capítulo 18:
Conversões ............................................. .................................. ... 43 5■ Capítulo 19:
Genéricos ............................................. ........................................ ... 46 5■ Capítulo 20: Enumeradores e
Iteradores ........................................... .............. .. 50 5■ Capítulo 21: Introdução ao
LINQ ........................................... ....................... ... 53 7■ Capítulo 22: Introdução à Programação
Assíncrona ............................. .. 59 5■ Capítulo 23: Diretivas de pré-
processador ............................................ ................. .. 62 7■ Capítulo 24: Reflexão e
Atributos ........................................... ................. .. 63 9■ Capítulo 25: Outros
tópicos ............................................ .................................. ... 66 3■
Índice ................................................ .................................................. ........... ... 693

Página 7

■ CONTEÚDOvConteúdoResumo do
conteúdo .............................................. ................................................. . eu vSobre o
autor ............................................... .................................................. . . xx
iAgradecimentos ................................................. ............................................ .. xxv
iIntrodução ................................................. .................................................. ... .. xxvi i■ Capítulo 1: C # e o
.NET Framework ........................................ ...................... . 1Antes do
.NET ............................................... .................................................. .................... .2Programação do
Windows no final da década de 1990 ............................................ ................................................. .
2Metas para os serviços da plataforma de última
geração .......................................... ........................................ .2Digite Microsoft
.NET .............................................. .................................................. ....... .2Componentes do .NET
Framework ............................................ .................................................. ....... .3Um ambiente de
programação aprimorado .............................................. ................................................. . 4Compilando
para a linguagem intermediária comum ............................................ ............. 7Compilando para código
nativo e execução ............................................ ............................ .8Visão geral da compilação e
execução ............................................. .................................................. . .9O Common Language
Runtime .............................................. .................................... .10A infraestrutura de linguagem
comum .............................................. ........................... .11Partes importantes da
CLI ............................................. .................................................. .................... ..12Revisão dos
acrônimos .............................................. ................................................ .. 13■ Capítulo 2: Visão geral da
programação C # .......................................... ............... ..1 5Um programa simples em C
# .............................................. .................................................. ... ..16Mais sobre
SimpleProgram ............................................... ............................................ 17Identificadores e palavras-
chave ............................................... .............................................. ..18

Página 8

■ CONTEÚDOviConvenções de
nomenclatura ................................................ .................................................. .. ..19Palavras-
chave................................................. .................................................. .................. ..20Principal: O ponto
de partida de um programa .......................................... ................................ .21Espaço em
branco ................................................. .................................................. ............... ..21Afirmações ..............
................................... .................................................. ............... ..22Declarações
simples ................................................ .................................................. .... ..22Blocos .................................
................ .................................................. ....................... ..22Saída de texto de um
programa ............................................. ............................................ .
23Escrever ................................................. .................................................. ......................... ..23WriteLine
................................................. .................................................. ................... ..24The Format
String ............................................... .................................................. ....... ..25Vários marcadores e valores
.............................................. .......................................... .26Comentários: Anotando o
Código ............................................. ...................................... 27Mais sobre
comentários ............................................... ................................................. . 28Comentários da
documentação ................................................ ............................................ 28Resumo dos tipos de
comentários .............................................. ......................................... .29■ Capítulo 3: Tipos,
armazenamento e variáveis ........................................ ................ .. 3 1Programa AC # é um conjunto de
declarações de tipo ......................................... ........................ .32Um tipo é um
modelo ............................................. .................................................. .... ..33Instanciando um
tipo ............................................... .................................................. .... ..33Membros de dados e membros
de função ............................................. ............................ .34Tipos de
membros ............................................... .................................................. ............................. .. 34Tipos
predefinidos ................................................ .................................................. ....... ..35Mais sobre os tipos
predefinidos ............................................. .................................................. ........ 0,36Tipos definidos pelo
usuário .............................................. .................................................. ..... ..38A pilha e a
pilha ............................................. .................................................. ..39A
pilha................................................ .................................................. .......................................... ..39O Heap
................................................ .................................................. .......................................... ..40

Página 9

■ CONTEÚDOvi euTipos de valor e tipos de referência ............................................. ..............................


41Armazenando Membros de um Objeto de Tipo de
Referência ........................................... .......................................... 41Categorizando os tipos C
# .............................................. .................................................. .................. ..42Variáveis
................................................. .................................................. ................... ..43Declarações de variáveis
................................................ .................................................. ........................ ..43Declarações de
múltiplas variáveis .............................................. .................................................. ........... ..45Usando o
valor de uma
variável ............................................ .................................................. ................ ..45Digitação estática e a
palavra-chave dinâmica ............................................ .......................... 0,45Tipos anuláveis
................................................ .................................................. ........... .. 46Criando um tipo
anulável .............................................. .................................................. .................... .. 46Atribuindo a um
tipo anulável ............................................. .................................................. ............... ..48■ Capítulo 4:
Classes: O básico .......................................... .............................. .. 4 9Visão geral das
aulas ............................................... .................................................. ... ..50Uma classe é uma estrutura de
dados ativa ........................................... .................................................. ........ 0,50Programas e aulas: um
exemplo rápido ........................................... .......................... 0,51Declarando uma
classe ............................................... .................................................. ........ ..52Membros da
classe ................................................ .................................................. .......... ..53Campos ...........................
...................... .................................................. ............................................... ..53Métodos..........................
....................... .................................................. ........................................... ..55Criando Variáveis e
Instâncias de uma Classe ........................................... ...................... 0,56Alocando memória para os dados
............................................. ....................................... .57Combinando as
etapas ............................................... .................................................. ......................... ..58Membros da
instância ................................................ .................................................. ..... ..59Modificadores de
acesso ................................................ .................................................. ....... ..60Acesso privado e
público .............................................. .................................................. .................. ..60Acessando
membros de dentro da classe ............................................ ....................... 0,63Acessando membros de
fora da classe ............................................ .................... 64Juntando
tudo .............................................. .................................................. .. ..65

Página 10

■ CONTEÚDOviii■ Capítulo 5: Métodos ............................................. .............................................. 6 7A


Estrutura de um Método ............................................. .............................................. ..68Execução de
código no corpo do método ............................................ .................................................. ...... 69Variáveis
locais ................................................ .................................................. .......... ..70Digite Inference e a
palavra-chave var ............................................ .................................................. ..... 0,71Variáveis locais
dentro de blocos aninhados ............................................. .................................................. ...
0,72Constantes locais ................................................ .................................................. ......... ..73Fluxo de
controle ............................................... .................................................. .................................. ..74Invocaçõ
es de método ................................................ .................................................. ... ..75Valores de
retorno ................................................ .................................................. ............ ..76A Declaração de
Retorno e Métodos de Vazio ............................................ ................................................ .
78Parâmetros ................................................. .................................................. ............... ..80Parâmetros
formais ................................................ .................................................. ........................... ..80Parâmetros
reais ................................................ .................................................. ............................ ..81Parâmetros de
valor ................................................ .................................................. ...... ..83Parâmetros de
referência ................................................ ................................................. . 0,86Parâmetros de
saída ................................................ .................................................. ..... ..89Matrizes de
parâmetros ................................................ .................................................. ....... ..92Invocação de Método
................................................ .................................................. ............................ ..93Matrizes como
parâmetros reais .............................................. .................................................. .............. ..96Resumo dos
tipos de parâmetros .............................................. ........................................ 0,96Sobrecarga de
método ................................................ .................................................. ... ..97Parâmetros
nomeados ................................................ .................................................. .... ..98Parâmetros
opcionais ................................................ .................................................. ..100Stack
Frames ................................................ .................................................. ........... ..104Recursão.....................
............................ .................................................. ................ ..106■ Capítulo 6: Mais sobre as
aulas ........................................... .......................... ... 10 9Membros da
classe ................................................ .................................................. ........ ..110Ordem dos
modificadores de membro .............................................. .................................................. .............. ..110

Página 11

■ CONTEÚDOixMembros da classe de
instância ............................................... ............................................ ..112Campos
estáticos ................................................ .................................................. ............ ... 113Acessando
membros estáticos de fora da classe ........................................... .......... 114Exemplo de um campo
estático ............................................. .................................................. ................... .. 114Vida útil dos
membros estáticos .............................................. .................................................. ............ ..115Membros
de função estática ............................................... ............................................ ..116Outros tipos de
membros de classe estática ............................................. ................................. ..117Constantes de
membros ................................................ .................................................. ... ..118Constantes são como
estática .............................................. .................................................. ................ ..119Propriedades ..........
....................................... .................................................. .............. ... 121Declarações de propriedade e
acessores .............................................. ................................................. . 0,122Um exemplo de
propriedade ............................................... .................................................. ......................... ..123Usando
uma propriedade ............................................... .................................................. ............................. ...
124Propriedades e campos associados .............................................. .................................................. ..... ..
125Realizando outros
cálculos ............................................... .................................................. ......... ..127Propriedades somente
leitura e somente gravação .......................................... .................................................. ... ..128Um
exemplo de uma propriedade computada, somente
leitura ........................................ ..................................... ..129Exemplo de propriedades e bancos de
dados ............................................. ................................................. . 0,130Propriedades vs. Campos
Públicos ............................................. .................................................. ............... ..130Propriedades
implementadas
automaticamente ............................................... ............................................. ..131Propriedades
estáticas ................................................ .................................................. ............................ ...
132Construtores de instância ................................................ ................................................ ..
133Construtores com
parâmetros ............................................... .................................................. ......... ..134Construtores
padrão ................................................ .................................................. ....................... ..135Construtores
estáticos ................................................ .................................................. .. ..136Exemplo de um construtor
estático ............................................. .................................................. ........ ..137Acessibilidade de
Construtores ............................................... .................................................. ........... ..137Inicializadores de
objeto ................................................ .................................................. ..... ..138Destruidores ......................
........................... .................................................. ............. ..140Chamando o
Destruidor ............................................... .................................................. ...................... ..141O padrão de
descarte padrão .............................................. .................................................. .......... ..143

Página 12

CONTEÚDOxComparando construtores e destruidores. .................................................. ............. ..144O


modificador somente leitura. .................................................. ............................................ ..145A esta
palavra-
chave. .................................................. .................................................. . ..147Indexadores .........................
........................ .................................................. ................. ... 148O que é um
indexador? ............................................. .................................................. ............................. ..149Indexado
res e
propriedades ............................................... .................................................. ..................... ..149Declarand
o um indexador ............................................... .................................................. ......................... ..150O
indexador set Accessor .............................................. .................................................. ................... ..151O
indexador obtém o
acessador .............................................. .................................................. ................... ..152Mais sobre
indexadores ............................................... .................................................. ......................... ..152Declaran
do o indexador para o exemplo do
funcionário ........................................... ................................... ..153Outro exemplo de
indexador ............................................... .................................................. .................. ..154Sobrecarga do
indexador ................................................ .................................................. .......................... ..155Modificad
ores de acesso em acessores. .................................................. ............................. ..156Classes parciais e
tipos parciais. .................................................. ........................... ..157Métodos
Parciais ................................................ .................................................. ............................... ... 159Capítulo
7: Classes e herança ............................................ ................... ..16 1Herança de
classe. .................................................. .................................................. . ..162Acessando os membros
herdados. .................................................. ......................... ..163Todas as classes são derivadas do objeto
Class ........................................... .............................................. ..164Ocultando membros de uma classe
base. .................................................. ........................... ..165Acesso à
base. .................................................. .................................................. ........ ..167Usando referências a
uma classe base. .................................................. ......................... ..168Métodos virtuais e de
substituição. .................................................. .................................................. ....... ..170Substituindo uma
substituição marcada pelo
método. .................................................. ............................................ ..172Substituindo outros tipos de
membros. .................................................. .................................................. ... ..175Execução do
construtor. .................................................. ........................................... ..176Inicializadores de
construtor ................................................ .................................................. ..................... ..178Modificador
es de acesso de
classe ............................................... .................................................. ...................... ..181Baixe a partir de
Wow! e-book <www.wowebook.com>

Página 13

■ CONTEÚDOXIHerança entre
Assemblies ............................................... ............................... ..182Modificadores de acesso de
membro ............................................... .......................................... ..184Regiões que acessam um membro
.............................................. .................................................. .......... ..185Acessibilidade de membro
público ............................................... .................................................. ........... ..186Acessibilidade de
membro privado ............................................... .................................................. .......... ..186Acessibilidade
de membro
protegido ............................................... .................................................. ...... ..187Acessibilidade de
membro interno ............................................... .................................................. ......... ..187Acessibilidade
de membro interno protegido .............................................. ............................................ ..188Resumo dos
modificadores de acesso de
membro ............................................. .............................................. ..188Membros
abstratos ................................................ .................................................. ... .190Classes
abstratas ................................................ .................................................. ...... ..192Exemplo de uma classe
abstrata e um método abstrato ......................................... ........................... ..193Outro exemplo de uma
classe abstrata ............................................ ................................................. . 0,194Classes
seladas ................................................ .................................................. ......... ..195Classes
estáticas ................................................ .................................................. .......... ..196Métodos de extensão
................................................ .................................................. .. ..197■ Capítulo 8: Expressões e
operadores ........................................... ............... ... 20
1Expressões ................................................. .................................................. ............ ..202Literais .............
.................................... .................................................. ................... ... 203Literais
inteiros ................................................ .................................................. ......... ..204Literais
reais ................................................ .................................................. ................................... ... 205Literais
de caracteres ................................................ .................................................. .... ..206Literais de
string ................................................ .................................................. .......... ..207Ordem de
Avaliação ............................................... .................................................. ... ..209Precedência......................
........................... .................................................. ................................... ...
209Associatividade ................................................. .................................................. ................................. ..
. 210Operadores aritméticos simples ............................................... ...................................... ..212O
operador restante ............................................... ............................................. ..213

Página 14

■ CONTEÚDOxi euOperadores de comparação relacional e de


igualdade ............................................. ............ ..214Operações de comparação e
igualdade .............................................. ................................................. . 0,215Operadores de incremento
e decremento .............................................. .......................... ..217Operadores lógicos
condicionais ............................................... ..................................... ..219Operadores
lógicos................................................ .................................................. ..... ..221Operadores de
turno ................................................ .................................................. ......... ..223Operadores de atribuição
................................................ ............................................... ..225Atribuição
composta ................................................ .................................................. ................... ..226O operador
condicional ............................................... ............................................ ..227Operadores aritméticos
unários ............................................... ........................................ ..229Conversões de tipo definidas pelo
usuário ............................................. .................................... ..230Conversão explícita e o operador de
elenco ............................................ ........................................... ..232Sobrecarga do
operador ................................................ ................................................. . 0,233Restrições à sobrecarga do
operador .............................................. ................................................. . .234Exemplo de sobrecarga do
operador .............................................. .................................................. ..... ..235O operador
typeof ............................................... .................................................. .. ..236Outros
operadores ................................................ .................................................. ....... ..238■ Capítulo 9:
Declarações ............................................. ..................................... ... 23 9O que são
declarações? .............................................. ................................................. . 0,240Declarações de
expressão ................................................ .............................................. ..241Declarações de fluxo de
controle ............................................ .......................................... ..242A declaração
if ............................................... .................................................. ....... ..243O se. . . declaração
else ................................................ ........................................... ..244A declaração
switch ............................................... ................................................. . 0,245Um exemplo de
switch ............................................... .................................................. ............................ ..247Mais sobre a
declaração switch ............................................. .................................................. .......... ..248Mudar de
etiquetas ................................................ .................................................. ................................ ... 249The
while Loop ............................................... .................................................. ......... ..250

Página 15

■ CONTEÚDOxii euThe do
Loop ............................................... .................................................. .............. ..251The for
Loop ............................................... .................................................. .............. ..253O escopo das variáveis
em uma instrução for .......................................... ............................................. ..255Expressões múltiplas
no inicializador e expressão de iteração .......................................... ............. ..256Declarações de
salto ................................................ .................................................. .... ..257A declaração
break ............................................... .................................................. ..257A declaração
continue ............................................... ............................................. ..258Declarações
etiquetadas ................................................ .................................................. . ..259Etiquetas .......................
.......................... .................................................. ........................................... ... 259O escopo das
declarações rotuladas ............................................. .................................................. ..... ..260A instrução
goto ............................................... .................................................. .. ..261A instrução goto dentro de
uma instrução switch ........................................... ..................................... ..261A declaração
using ............................................... .................................................. . ..262Empacotando o Uso do
Recurso ............................................. .................................................. ......... ..263Exemplo de instrução
using ............................................. .................................................. ....... ..264Vários recursos e
aninhamento .............................................. .................................................. ....... ..265Outra forma de
declaração de uso ............................................ .................................................. ..266Outras
Declarações ................................................ .................................................. ..... ..267■ Capítulo 10:
Namespaces e Assemblies ........................................... .......... ..26 9Referenciando outras
montagens ............................................... ................................... ..270A Biblioteca
mscorlib ............................................... .................................................. ........................ ..273Namespaces .
................................................ .................................................. ........... ..275Nomes de
namespace ................................................ .................................................. ......................... ..279Mais
sobre namespaces ............................................... .................................................. ................ ..280Espaços
de nomes espalhados pelos
arquivos .............................................. .................................................. .... ..281Namespaces
aninhados ................................................ .................................................. ...................... ..282O uso de
Diretivas ............................................... .................................................. . ..283O uso da Diretiva de
Namespace .............................................. .................................................. ....... ..283O uso da Diretiva de
Alias .............................................. .................................................. .................. ..284
Página 16

■ CONTEÚDOxivA Estrutura de uma


Montagem ............................................. ...................................... ..285A identidade de uma
assembléia ............................................. ......................................... ..287Assemblies fortemente
nomeados ............................................... ....................................... ..289Criando uma montagem com
nome forte ............................................. ................................................. . 0,290Implantação privada de
uma montagem ............................................. ............................. ..291Assemblies compartilhados e o GAC
............................................. .................................. ..292Instalando Assemblies no
GAC ............................................. .................................................. .... ..292Execução lado a lado no
GAC ......................................... .................................................. ........ ..293Arquivos de
configuração ................................................ .................................................. ... ..294Assinatura
atrasada ................................................ .................................................. ....... ..295■ Capítulo 11:
Exceções ............................................. .................................... ... 29 7O que são
exceções? .............................................. .................................................. ..298A declaração
try ............................................... .................................................. ...... 299Lidando com a
exceção ............................................... .................................................. .................... .. 300As classes de
exceção ............................................... ................................................ .. 301A cláusula
catch ............................................... .................................................. ...... ..303Exemplos usando cláusulas
catch específicas ............................................. ....................... ..304A seção de cláusulas
catch .............................................. ........................................... .. 305O bloco
finalmente ............................................... .................................................. ....... ..306Encontrando um
manipulador para uma exceção ............................................ ................................ ..307Pesquisando
mais ................................................ .................................................. .... ..308Algoritmo
Geral ................................................ .................................................. ........................... .. 309Exemplo de
pesquisa na pilha de chamadas ........................................... .......................................... ..310Lançando
exceções ................................................ ................................................. . 0,313Lançando sem um objeto
de exceção ............................................. ......................... ..314■ Capítulo 12:
Structs ............................................. .......................................... ... 31 7O que são
Structs? .............................................. .................................................. ...... ..318Estruturas são tipos de
valor .............................................. ............................................... ..319

Página 17

■ CONTEÚDOxvAtribuindo a um Struct .............................................. .................................................. . ..


320Construtores e Destruidores ............................................... ..................................... ..321Construtores
de
instância ................................................ .................................................. ..................... ..321Construtores
estáticos ................................................ .................................................. ......................... ..323Resumo de
construtores e destruidores ............................................. ......................................... ..323Inicializadores
de campo não são permitidos ............................................. .................................. ..324As estruturas são
seladas ............................................... .................................................. .... ..324Boxing e
Unboxing ............................................... .................................................. ..324Estruturas como valores e
parâmetros de retorno ............................................ ..................... ..325Informações adicionais sobre
structs .............................................. ........................... ..325■ Capítulo 13:
Enumerações ............................................. ............................... ... 32
7Enumerações ................................................. .................................................. .......... ..328Definindo o
tipo subjacente e os valores explícitos ........................................... ..................................... 330Numeração
implícita de
membros ............................................... .................................................. ............ ..331Sinalizadores de bits
................................................ .................................................. .................. ... 332O atributo
sinalizadores ............................................... .................................................. .......................... ..335Exempl
o de uso de sinalizadores de
bits .............................................. .................................................. ................... ..337Mais sobre
Enums ............................................... .................................................. ... ..339■ Capítulo 14:
Arrays ............................................. ........................................... ... 34
1Matrizes ................................................. .................................................. .................... ...
342Definições ................................................. .................................................. .................................... ...
342Detalhes
importantes ................................................ .................................................. ............................ ..342Tipos
de matrizes ............................................... .................................................. ......... ..343Uma matriz como
um objeto ............................................. .................................................. . .. 344Matrizes Unidimensionais
e Retangulares ............................................ ..................... ..345Declarando um Array Unidimensional ou
um Array Retangular ........................................ .................... ..345Instanciando um Array Unidimensional
ou Retangular .......................................... .... 0,346Acessando Elementos de
Matriz ............................................... .......................................... ..347

Página 18

■ CONTEÚDOxviInicializando um
Array ............................................... .................................................. ... ..348Inicialização explícita de
matrizes unidimensionais ........................................... ..................................... 348Inicialização explícita de
matrizes retangulares ............................................. ......................................... ..349Pontos de sintaxe para
inicializar matrizes retangulares ............................................ .................................. ..349Sintaxe de
atalho ................................................ .................................................. .............................. ..350Matrizes
com tipagem
implícita ............................................... .................................................. .................... ..351Juntando
tudo .............................................. .................................................. ....................... ..352Matrizes
denteadas ................................................ .................................................. .......... ..353Declarando uma
Matriz Jagged .............................................. .................................................. ................. ..354Instanciação
de atalho ................................................ .................................................. ..................... ..354Instanciando
uma Matriz Jagged .............................................. .................................................. ............ ..355Subarrays
em matrizes
irregulares .............................................. .................................................. ............. ..356Comparando
matrizes retangulares e denteadas ............................................. ................. ..357A declaração
foreach ............................................... ............................................... ..358A variável de iteração é
somente leitura ........................................... .................................................. ..... .. 360A instrução foreach
com matrizes multidimensionais ............................................ ......................... .. 361Covariância de
matriz ................................................ .................................................. ..... .. 363Membros úteis herdados
da matriz .............................................. .................................. ..364O Método
Clone ............................................... .................................................. ............................ ..366Comparando
Tipos de Array ............................................... .............................................. ..368■ Capítulo 15: Delegados
............................................. ...................................... ... 36 9O que é um
delegado? ............................................. .................................................. .... .. 370Declarando o tipo de
delegado .............................................. ........................................ ..372Criando o objeto
Delegate .............................................. ....................................... ..373Atribuindo
Delegados ................................................ .................................................. ..375Combinando
Delegados ................................................ ................................................. . 0,376Adicionando métodos a
delegados .............................................. ...................................... ..377Removendo métodos de um
delegado ............................................. ............................ ..378Invocando um
Delegado ............................................... .................................................. .. ..379

Página 19

■ CONTEÚDOxviiExemplo de
delegado ................................................ .................................................. .... ..380Invocando Delegados
com Valores de Retorno ............................................. ........................ ..381Invocando Delegados com
Parâmetros de Referência ............................................. ............ 382Métodos
anônimos ................................................ ................................................. . 0,383Usando métodos
anônimos ............................................... .................................................. ............. .. 383Sintaxe de
métodos anônimos .............................................. .................................................. ........ ..384Escopo de
variáveis e parâmetros ............................................. .................................................. .. ..386Expressões
Lambda ................................................ .................................................. ..388■ Capítulo 16:
Eventos ............................................. ........................................... ... 39 1Os eventos são como
delegados .............................................. ........................................... .. 392Um evento tem um delegado
privado ............................................ .................................................. ........ ..393Visão geral dos
componentes do código-fonte ............................................. ......................... ..394Declarando um
evento ............................................... .................................................. .... ..395Um evento é um
membro ............................................. .................................................. ........................ ..396O tipo de
delegado e EventHandler ............................................. .................................................. ..396Levantando
um Evento ............................................... .................................................. ....... .. 397Inscrevendo-se em
um evento .............................................. ............................................... ..398Removendo manipuladores
de eventos ............................................... .................................................. ................ ..400Uso de evento
padrão ............................................... ................................................ .. 401Usando a classe
EventArgs .............................................. .................................................. ................ ..401Transmitindo
dados estendendo EventArgs ............................................. ................................................. . 0,402Usando
o Delegado Personalizado .............................................. .................................................. .............. ..403O
código MyTimerClass ............................................... ............................................. ..406Acessadores de
eventos ................................................ .................................................. ...... ..408■ Capítulo 17:
Interfaces ............................................. ..................................... ... 40 9O que é uma
interface? ............................................. .................................................. .. ..410Exemplo usando a
interface IComparable ............................................. .......................................... ..411Declarando uma
interface ............................................... ................................................ .. 414

Página 20

■ CONTEÚDOxvii euImplementando uma


Interface ............................................... .......................................... ..416Exemplo com uma interface
simples ............................................. .................................................. ....... .. 417Uma interface é um tipo
de referência ............................................ ................................... .. 418Usando o como Operador com
Interfaces ............................................ ........................... ..420Implementando várias
interfaces ............................................... ............................... ..421Implementando Interfaces com
Membros Duplicados ............................................. ........, 422Referências a múltiplas
interfaces .............................................. ................................ ..424Um membro herdado como uma
implementação ............................................ ................. ..426Implementações explícitas de membros de
interface .............................................. ................ ..427Acessando Implementações de Membros de
Interface Explícita ............................................. ...................... ..430Interfaces podem herdar
interfaces .............................................. ................................. ..431Exemplo de diferentes classes
implementando uma interface ........................................... ..................... ..432■ Capítulo 18:
Conversões ............................................. .................................. ... 43 5O que são
conversões? .............................................. ................................................ .. 436Conversões
implícitas ................................................ .................................................. ..437Conversões explícitas e
elenco .............................................. ................................. ..438Fundição ................................................. ..
................................................ .......................................... ... 439Tipos de
conversão ............................................... .................................................. ..440Conversões
numéricas ................................................ ................................................. . .440Conversões numéricas
implícitas ............................................... .................................................. .......... .. 441Contexto de
verificação de estouro ............................................... .................................................. ............. ..
442Conversões numéricas
explícitas ............................................... .................................................. .......... .. 444Conversões de
referência ................................................ .............................................. ..448Conversões de referência
implícita ............................................... .................................................. ....... .. 449Conversões de
referência explícita ............................................... .................................................. ....... .. 451Conversões
de referência explícita válidas .............................................. ................................................. . .
452Conversões de boxe ................................................ .................................................. . .. 454O Boxing
Cria uma Cópia .............................................. ................................................. . 0,455

Página 21
■ CONTEÚDOxixConversões de
Unboxing ................................................ ............................................... ..456As conversões de
Unboxing ............................................... ......................................... .. 457Conversões definidas pelo
usuário .............................................. ........................................... .. 458Restrições em conversões
definidas pelo usuário ............................................ ............................................ .. 458Exemplo de uma
conversão definida pelo usuário ........................................... ................................................. . .
459Avaliando conversões definidas pelo
usuário ............................................. ................................................. . , 461Exemplo de uma conversão em
várias etapas definida pelo usuário .......................................... .................................. ..461O é
Operador ............................................... .................................................. ......... .. 463O como
Operador ............................................... .................................................. ........ .. 464■ Capítulo 19:
Genéricos ............................................. ........................................ ... 46 5O que são
genéricos? .................................................. ................................................. . , 466Um exemplo de
pilha ............................................... .................................................. .............................. .. 466Genéricos
em C # ............................................... .................................................. ........... .. 468Continuando com o
exemplo de pilha ............................................. .................................................. ... ..469Classes
genéricas ................................................ .................................................. ....... ..470Declarando uma
classe genérica .............................................. ............................................ ..471Criando um tipo
construído .............................................. ....................................... ..472Criando Variáveis e
Instâncias .............................................. ................................ ..473O exemplo de pilha usando
genéricos ............................................. .................................................. ... ..475Comparando a pilha
genérica e não genérica ............................................ ..................................... ..477Restrições nos
parâmetros de tipo .............................................. .................................. ..478Onde
Cláusulas ................................................ .................................................. ................................ ..479Tipos
de restrição e
ordem .............................................. .................................................. .............. ..480Métodos
Genéricos ................................................ .................................................. ...... .. 481Declarando um
método genérico .............................................. .................................................. ............. .. 482Invocando
um Método
Genérico .............................................. .................................................. ............... ..483Exemplo de um
método genérico ............................................. .................................................. ............ ..485Métodos de
extensão com classes genéricas ............................................. ..................... .. 486Estruturas
genéricas ................................................ .................................................. ........ ..487

Página 22

CONTEÚDOxxDelegados
genéricos ................................................ .................................................. .... ..488Outro exemplo de
delegado genérico. .................................................. ................................................ .. 490Interfaces
genéricas. .................................................. .................................................. ..491Um exemplo de uso de
interfaces genéricas. .................................................. ............................................. .. 492As
implementações de interface genérica devem ser
exclusivas ............................................ ............................. .. 493Covariância e contravariância em
genéricos. .................................................. .......... ..494Covariância e Contravariância em
Interfaces ............................................. ....................................... ..500Mais sobre a
variação ............................................... .................................................. ......................... ..502Capítulo 20:
Enumeradores e Iteradores ............................................ ............. .. 50 5Enumeradores e tipos
enumeráveis. .................................................. ..................... ..506Usando a declaração
foreach. .................................................. .................................................. ........ ..506Tipos de
enumeradores ............................................... .................................................. ........................ ..507Usando
a interface do IEnumerator. .................................................. ............................ ..508Declarando um
Enumerador IEnumerator .............................................. ................................................ .. 511A interface
IEnumerable ............................................... .......................................... ..513Exemplo de uso de
IEnumerable e IEnumerator ............................................. ..................................... ..514The Noninterface
Enumerator ............................................... ..................................... ..516As interfaces de enumeração
genérica. .................................................. ..................... ..518A interface IEnumerator
<T>. .................................................. .............................. ..519A interface IEnumerable
<T> ............................................ ...................................... .. 522Iteradores. ..................................................
.................................................. .............. ... 524Blocos de
iterador ................................................ .................................................. ................................. ...
525Usando um Iterador para Criar um
Enumerador. .................................................. ..................................... ..526Usando um Iterador para Criar
um Enumerável ........................................... ............................................ .. 528Padrões de iterador
comuns. .................................................. ..................................... .. 530Produzindo Enumeráveis e
Enumeradores. .................................................. ............ .. 531Produzindo vários
enumeráveis. .................................................. ........................... .. 532Produzindo vários
enumeradores. .................................................. ........................... .. 534Nos bastidores com
iteradores ............................................. ................................. ..536Baixe a partir de Wow! e-book
<www.wowebook.com>

Página 23

■ CONTEÚDOxxi■ Capítulo 21: Introdução ao LINQ ........................................... ....................... ... 53 7O


que é LINQ? .................................................. .................................................. ........ .. 538Provedores
LINQ ................................................ .................................................. ......... .. 539Tipos
anônimos ................................................ .................................................. .......................... ..540Sintaxe de
consulta e sintaxe de método ............................................. ................................. ..542Variáveis de
consulta ................................................ .................................................. ........ .. 544A Estrutura de
Expressões de Consulta ............................................. ............................. ..546A cláusula
from ............................................... .................................................. .............................. ..547A cláusula de
junção ............................................... .................................................. ............................... ... 549O que é
uma associação? .................................................. .................................................. ............................ ...
550A partir de . . . deixei . . . onde Seção no Corpo da
Consulta ............................................ ......................... ..553A cláusula
orderby ............................................... .................................................. .......................... ..557O seleto. . .
cláusula de grupo ................................................ .................................................. ........... ..558Tipos
anônimos em consultas .............................................. .................................................. ........... ..560A
cláusula do
grupo ............................................... .................................................. ............................. ..561Continuação
da consulta ................................................ .................................................. ......................... ..563Os
operadores de consulta padrão .............................................. .................................... .. 564Assinaturas dos
Operadores de Consulta Padrão ............................................ ........................................ ..567Delegados
como parâmetros ............................................... .................................................. ................ .. 569Os tipos
de delegado predefinidos do LINQ ............................................. .................................................. ..
571Exemplo de uso de um parâmetro de
delegado ............................................. ................................................. . 0,572Exemplo usando um
parâmetro de expressão Lambda ............................................ ................................. ..573LINQ to
XML ............................................... .................................................. .............. ..575Linguagens de
marcação ................................................ .................................................. ......................... ..575XML Basics
................................................ .................................................. .................................... ... 576As classes
XML ............................................... .................................................. ............................. ..578Usando
valores da árvore XML ............................................ .................................................. ....... ... 58
1Trabalhando com atributos XML .............................................. .................................................. ........... ...
58 6Outros tipos de nós .............................................. .................................................. ....................... ..
590Usando consultas LINQ com LINQ to XML ........................................... .................................................. .
.. 592

Página 24

■ CONTEÚDOxxi i■ Capítulo 22: Introdução à Programação Assíncrona ............................. .. 59


5Processos, Threads e Programação Assíncrona ........................................... ... .596Considerações sobre
multithreading ................................................ .................................................. ......... 597A complexidade
do multithreading .............................................. .................................................. ..... .. 598Loops
paralelos ................................................ .................................................. .......... .. 599A classe
BackgroundWorker ............................................... ..................................... ..602Exemplo de código usando
a classe BackgroundWorker ............................................ .............................. ..606Exemplo da classe
BackgroundWorker em um programa WPF ......................................... ................... 0,610Padrões de
programação assíncrona ............................................... ........................ .. 613BeginInvoke e
EndInvoke ............................................... ........................................ .. 614O padrão de espera até a
conclusão ........................................... .................................................. ................ .. 616A classe
AsyncResult ............................................... .................................................. .................... .. 617O padrão de
votação ............................................... .................................................. .......................... .. 618O padrão de
retorno de chamada ............................................... .................................................. ....................... ..
620Temporizadores ................................................. .................................................. .................... ... 624■
Capítulo 23: Diretivas de pré-processador ............................................ ................. .. 62 7O que são
diretivas de pré-processador? .................................................. ......................... .. 628Regras
gerais................................................ .................................................. ........... .. 628As diretivas #define e
#undef ........................................... ................................. .. 630Compilação
condicional ................................................ ............................................. .. 631As construções de
compilação condicional .............................................. ...................... .. 632Diretivas de
diagnóstico ................................................ ................................................. . 0,635Diretivas de número de
linha ............................................... .............................................. .. 636Diretivas
regionais ................................................ .................................................. ..... .. 637A diretiva de aviso
#pragma ............................................. ................................... .. 638■ Capítulo 24: Reflexão e
Atributos ........................................... ................. .. 63 9Metadados e
reflexão ............................................... ............................................. .. 640A classe de
tipo ............................................... .................................................. .......... .. 640

Página 25

■ CONTEÚDOxxii euObtendo um objeto de


tipo .............................................. .................................................. ..642O que é um
atributo? .................................................. ............................................... .. 645Aplicando um
Atributo ............................................... .................................................. ..646Atributos Predefinidos e
Reservados .............................................. .................................. ..647O Atributo
Obsoleto ............................................... .................................................. ..................... .. 647O Atributo
Condicional ............................................... .................................................. ................. .. 648Atributos
predefinidos ................................................ .................................................. ...................... ..650Mais
sobre como aplicar atributos .............................................. ..................................... 651Atributos múltiplos
................................................ .................................................. .......................... ..651Outros tipos de alvos
.............................................. .................................................. ..................... ..652Atributos
globais ................................................ .................................................. ............................ ... 653Atributos
personalizados ................................................ .................................................. .... ..654Declarando um
atributo personalizado .............................................. .................................................. ........... ..654Usando
Construtores de
Atributos ............................................... .................................................. ........... ..655Especificando o
construtor ............................................... .................................................. .............. ..655Usando o
Construtor ............................................... .................................................. ...................... ..656Parâmetros
posicionais e nomeados em
construtores ............................................ .............................. ..657Restringindo o uso de um
atributo ............................................ .................................................. ... 658Práticas sugeridas para
atributos personalizados ............................................. ......................................... ..660Acessando um
Atributo ............................................... ................................................ .. 661Usando o método
IsDefined .............................................. .................................................. ............. ..661Usando o método
GetCustomAttributes .............................................. ............................................. ..662■ Capítulo 25:
Outros tópicos ............................................ .................................. ... 66 3Visão
geral................................................. .................................................. ................. ..664Cordas ......................
........................... .................................................. ................... ... 664Usando a classe
StringBuilder ............................................... .................................................. ................ ..666Formatando
Strings Numéricos ............................................... .................................................. ............ ..667Analisando
Strings para Valores de Dados ............................................. ..................................... .. 672

Página 26

■ CONTEÚDOxxi vMais sobre os tipos anuláveis ............................................. ..................................... ..


674O operador de coalescência nula .............................................. .................................................. ...........
.. 676Usando tipos definidos pelo usuário anuláveis
............................................ .................................................. .... ..677Método
principal ................................................ .................................................. ............ .. 679Acessibilidade do
principal ............................................... .................................................. ........................ .. 680Comentários
da documentação ................................................ ........................................ ..681Inserindo comentários de
documentação ............................................... ................................................. . 0,682Usando outras tags
XML .............................................. .................................................. ...................... ..683Tipos
aninhados ................................................ .................................................. ........... ..684Exemplo de uma
classe aninhada ............................................. .................................................. ................ ..685Visibilidade
e tipos
aninhados .............................................. .................................................. ............... ..686Interoperando
com
COM ............................................... ............................................. ..689Índice................................................
. .................................................. ................ ..693

Página 27

■ CONTEÚDOxxvSobre o autor■ Dan Solis é bacharel em artes com especialização em biologia e


inglês.Ele inicialmente trabalhou em pesquisas sobre a estrutura de cristais bi e tri-metálicos, atéele
descobriu que gostava de programar muito mais do que trabalhar em um laboratório. Ele tambémtem
mestrado em ciência da computação pela Universidade deCalifórnia em Santa Bárbara, onde se
concentrou em linguagens de programaçãoe design do compilador.Dan programa profissionalmente há
mais de 20 anos, com maisda metade desse tempo trabalhando como consultor e programador
contratado, incluindovários projetos para Serviços de Consultoria Microsoft. Seus projetos de consultoria
têmvariava de programas para análise de fundos mútuos e gestão da cadeia de abastecimento
parasistemas para rastreamento de mísseis. Ele também ministrou cursos sobre programação
diversalinguagens, programação do Windows, componentes internos do UNIX e uma série de outros
tópicos,nos Estados Unidos e na Europa.A primeira linguagem de programação de Dan foi C, mas ele
logo ficou intrigado com os artigos de jornalsobre uma nova linguagem que está sendo desenvolvida
chamada “C com Classes”. Eventualmente, esse idioma foi renomeadoC ++ e lançado para o mundo. Ele
começou a usar C ++ assim que conseguiu acesso a um compilador, e elepor fim, começou a ministrar
seminários de treinamento no idioma, além de continuar a programar.Com o advento do C #, .NET e
WPF, ele passou a aproveitar as inúmeras vantagens do novoplataforma e tem trabalhado com eles com
entusiasmo desde então.
Página 28

■ CONTEÚDOxxviAgradecimentosQuero agradecer a Sian por me apoiar e encorajar diariamente, e


quero agradecer aos meus paise irmãos e irmãs por seu amor e apoio contínuos.Também quero
expressar minha gratidão às pessoas da Apress que trabalharam comigo para trazer estelivro até a
fruição. Eu realmente aprecio que eles tenham entendido e apreciado o que eu estava tentando fazer
etrabalhou comigo para alcançá-lo. Obrigado a todos vocês.

Página 29

■ INTRODUÇÃOxxviiIntroduçãoO objetivo deste livro é ensinar os fundamentos e a mecânica da


programação C #língua. A maioria dos livros ensina programação principalmente usando texto. Isso é
ótimo para romances, mas muitos dosconceitos importantes de linguagens de programação podem ser
melhor compreendidos por meio de uma combinação depalavras, figuras e tabelas.Muitos de nós
pensamos visualmente, e as figuras e tabelas podem ajudar a esclarecer e cristalizar nosso
entendimentode um conceito. Em vários anos ensinando linguagens de programação, descobri que as
imagens que desenheinos quadros brancos foram as coisas que mais rapidamente ajudaram os alunos a
entender os conceitos que eu estavatentando transmitir. As ilustrações por si só, no entanto, não são
suficientes para explicar uma linguagem de programação eplataforma. O objetivo deste livro é encontrar
a melhor combinação de palavras e ilustrações para lhe dar umacompreensão completa da linguagem e
permitir que o livro sirva como um recurso de referência também.Este livro foi escrito para quem deseja
uma introdução à linguagem de programação C # - deo novato ao programador experiente. Para aqueles
que estão apenas começando na programação, incluí oFundamentos. Para programadores experientes, o
conteúdo é apresentado de forma sucinta, de uma forma que permite que você vádiretamente para as
informações necessárias, sem ter que percorrer oceanos de palavras. Para ambos os conjuntos
deprogramadores, o conteúdo em si é apresentado graficamente, de uma forma que deve tornar a
linguagem mais fácilaprender.Você pode baixar o código-fonte para todos os programas de exemplo do
livro no site da Apress—apress.com. E embora eu não possa responder a perguntas específicas sobre seu
código, você pode entrar em contato comigosugestões ou comentários em dansolis@sbcglobal.net. Você
também pode visitar meu site—illustrcsharp.com. Finalmente, se você estiver interessado em aprender
a programar usando o Windows PresentationFoundation, por favor, dê uma olhada em meu livro -
Illustrated WPF , que usa o mesmo estilo e abordagem queeste livro.Espero que este livro torne o
aprendizado de C # uma experiência agradável para você! Cuidar.Dan Solis

Página 30

Página 31

CAPÍTULO 1■ ■ ■1C # e .NET Framework■ Antes de .NET■ Digite Microsoft .NET■ Compilando para a
linguagem intermediária comum■ Compilando para código nativo e execução■ O Common Language
Runtime■ A infraestrutura de linguagem comum■ Revisão dos acrônimos

Página 32
CAPÍTULO 1 ■ C # E O .NET FRAMEWORK2Antes de .NETA linguagem de programação C # foi projetada
para desenvolver programas para o .NET Framework da Microsoft.Este capítulo dá uma breve olhada na
origem do .NET e em sua arquitetura básica. Para começar, vamoso nome certo: C # é pronunciado "veja
bem" 1Programação do Windows no final da década de 1990No final da década de 1990, a programação
do Windows usando a plataforma da Microsoft havia se dividido em váriosramos. A maioria dos
programadores estava usando Visual Basic (VB), C ou C ++. Alguns programadores C e C ++estavam
usando a API Win32 bruta, mas a maioria estava usando o Microsoft Foundation Classes (MFC). Outros
tiverammovido para o Component Object Model (COM).Todas essas tecnologias tinham seus próprios
problemas. A API Win32 bruta não era orientada a objetos eusá-lo exigia muito mais trabalho do que o
MFC. MFC era orientado a objetos, mas era inconsistente evelho. COM, embora conceitualmente
simples, era complexo em sua codificação real e exigia muitoencanamento deselegante.Outra deficiência
de todas essas tecnologias de programação era que elas se destinavam principalmente adesenvolver
código para desktop em vez da Internet. Na época, a programação para a Web era umpensado
posteriormente e parecia muito diferente da codificação para a área de trabalho.Metas para os serviços
da plataforma de última geraçãoO que realmente precisávamos era um novo começo - uma estrutura de
desenvolvimento orientada a objetos integrada quetraria consistência e elegância de volta à
programação. Para atender a essa necessidade, a Microsoft decidiudesenvolver um ambiente de
execução de código e um ambiente de desenvolvimento de código que atendesse a esses objetivos.A
Figura 1-1 lista esses objetivos.Figura 1-1. Metas para a plataforma de próxima geraçãoDigite
Microsoft .NETEm 2002, a Microsoft lançou a primeira versão do .NET Framework, que prometia resolver
o antigoproblemas e cumprir as metas para os sistemas de próxima geração. O .NET Framework é muito
maisambiente consistente e orientado a objetos do que a tecnologia de programação MFC ou
COM.Alguns de seus recursos incluem o seguinte:1 Certa vez, fui entrevistado para um cargo C #
contratado quando o entrevistador de Recursos Humanos me perguntou comotive muita experiência em
programação em “ver libra” (em vez de “ver nitidamente”)! Levei um momento para percebersobre o
que ele estava falando.Baixe a partir de Wow! e-book <www.wowebook.com>

Página 33

CAPÍTULO 1 ■ C # E O .NET FRAMEWORK3•Múltiplas plataformas: o sistema é executado em uma ampla


variedade de computadores, desde servidores até desktopmáquinas para PDAs e telefones
celulares.•Padrões da indústria: o sistema usa protocolos de comunicação padrão da indústria, como
XML,HTTP, SOAP e WSDL.•Segurança : O sistema pode fornecer um ambiente de execução muito mais
seguro, mesmo na presença decódigo obtido de fontes suspeitas.Componentes do .NET FrameworkO
.NET Framework consiste em três componentes, conforme mostrado na Figura 1-2. O ambiente de
execuçãoé chamado de Common Language Runtime (CLR). O CLR gerencia a execução do programa em
tempo de execução,incluindo o seguinte:•Gerenciamento de memória•Verificação de segurança de
código•Execução de código, gerenciamento de thread e tratamento de exceções•Coleta de lixoAs
ferramentas de programação incluem tudo que você precisa para codificação e depuração, incluindo
oSegue:•O ambiente de desenvolvimento integrado do Visual Studio•Compiladores compatíveis com
.NET (por exemplo, C #, VB .NET, JScript, F #, IronRuby e C ++ gerenciado)•Depuradores•Tecnologias de
desenvolvimento da Web do lado do servidor, como ASP.NET ou WCFA Base Class Library (BCL) é uma
grande biblioteca de classes usada pelo .NET Framework e disponível paravocê para usar em seus
programas também.Figura 1-2. Componentes do .NET Framework

Página 34

CAPÍTULO 1 ■ C # E O .NET FRAMEWORK4Um ambiente de programação aprimoradoO .NET Framework


oferece aos programadores melhorias consideráveis em relação ao Windows anteriorambientes de
programação. As seções a seguir fornecem uma breve visão geral de seus recursos e
benefícios.Ambiente de desenvolvimento orientado a objetosO CLR, o BCL e o C # são projetados para
serem totalmente orientados a objetos e atuar como um sistema bem integradomeio Ambiente.O
sistema fornece um modelo de programação consistente e orientado a objetos para ambos os
programas locaise sistemas distribuídos. Ele também fornece uma interface de desenvolvimento de
software para aplicativos de desktopprogramação, programação de aplicativos móveis e
desenvolvimento web, consistentes em uma ampla gamade alvos, de servidores a telefones
celulares.Coleta Automática de LixoO CLR possui um serviço chamado coletor de lixo (GC), que gerencia
automaticamente a memória para você.•O GC remove automaticamente da memória os objetos que seu
programa não acessará mais.•O GC alivia os programadores de tarefas que tradicionalmente tinham que
executar, comodesalocar memória e procurar vazamentos de memória. Esta é uma grande melhoria,
pois a caçapara vazamentos de memória pode ser difícil e demorado.InteroperabilidadeO .NET
Framework foi projetado para interoperabilidade entre diferentes linguagens .NET,sistema ou DLLs
Win32 e COM.•A interoperabilidade da linguagem .NET permite que módulos de software escritos
usando diferentes linguagens .NET parainteragir perfeitamente.- Um programa escrito em uma
linguagem .NET pode usar e até mesmo herdar de uma classe escrita emoutra linguagem .NET, desde
que certas regras sejam seguidas.- Devido à sua capacidade de integrar facilmente módulos produzidos
em diferentes programaçõeslinguagens, o .NET Framework às vezes é descrito como independente de
linguagem .

Página 35

CAPÍTULO 1 ■ C # E O .NET FRAMEWORK5•.NET fornece um recurso chamado plataforma invoke (P /


Invoke) , que permite que o código escrito para .NET parachamar e usar código não escrito para .NET. Ele
pode usar funções C brutas importadas do Win32 padrãoDLLs, como as APIs do Windows.•O .NET
Framework também permite interoperabilidade com COM. O software .NET Frameworkcomponentes
podem chamar componentes COM e componentes COM podem chamar componentes .NET como seeles
próprios eram componentes COM.Sem necessidade de COMO .NET Framework libera o programador do
legado COM. Como um programador C #, você não precisapara usar COM e, portanto, não precisa de
nenhum dos seguintes:•A interface IUnknown : No COM, todos os objetos devem implementar a
interface IUnknown. Em contraste, todosOs objetos .NET derivam de uma única classe chamada objeto.
A programação da interface ainda é um importanteparte do .NET, mas não é mais o tema
central.•Bibliotecas de tipos : em COM, as informações de tipo são mantidas em bibliotecas de tipos
como arquivos .tlb, que são separadosdo código executável. Em .NET, as informações de tipo de
programa são mantidas agrupadas com o código emo arquivo do programa.•Contagem de referências :
Em COM, o programador tinha que controlar o número de referências a umobjeto para certificar-se de
que não foi excluído na hora errada. Em .NET, o GC rastreia as referênciase remove objetos somente
quando apropriado.•HRESULT: COM usou o tipo de dados HRESULT para retornar códigos de erro de
tempo de execução. .NET não usaHRESULTs. Em vez disso, todos os erros inesperados de tempo de
execução produzem exceções.•O registro : os aplicativos COM tiveram que ser registrados no registro do
sistema, que contéminformações sobre as configurações do sistema operacional e aplicativos.
.INTERNETos aplicativos não precisam usar o registro. Isso simplifica a instalação e remoção
deprogramas. (No entanto, há algo semelhante chamado cache global de assemblies , que abordareino
Capítulo 10.)Embora a quantidade de código COM que está sendo escrito atualmente seja bastante
pequena, ainda há bastantenúmero de componentes COM em sistemas atualmente em uso, e
programadores C # às vezes precisamescrever código que faça interface com esses componentes. C # 4.0
apresenta vários novos recursos que tornam issotarefa mais fácil.

Página 36

CAPÍTULO 1 ■ C # E O .NET FRAMEWORK6Implantação simplificadaA implantação de programas escritos


para o .NET Framework pode ser muito mais fácil do que antes, para oseguintes razões:•O fato de que os
programas .NET não precisam ser registrados no registro significa que nocaso mais simples, um
programa só precisa ser copiado para a máquina de destino e está pronto para ser executado.•.NET
oferece um recurso chamado de execução lado a lado , que permite que diferentes versões de uma
DLLexistem na mesma máquina. Isso significa que todo executável pode ter acesso à versão doa DLL para
a qual foi criado.Segurança de tipoO CLR verifica e garante a segurança do tipo de parâmetros e outros
objetos de dados - mesmo entrecomponentes escritos em diferentes linguagens de programação.A
Biblioteca de Classes BásicasO .NET Framework fornece uma extensa biblioteca de classes base,
chamada, não surpreendentemente, de Base ClassBiblioteca ( BCL ). (Às vezes também é chamado de
Framework Class Library - FCL). Você pode usar este extensoconjunto de código disponível ao escrever
seus próprios programas. Algumas das categorias são as seguintes:•Classes básicas gerais : classes que
fornecem um conjunto extremamente poderoso de ferramentas para uma amplagama de tarefas de
programação, como manipulação de arquivos, manipulação de strings, segurança ecriptografia•Classes
de coleção : classes que implementam listas, dicionários, tabelas de hash e matrizes de bits•Classes de
segmentação e sincronização : classes para a construção de programas multithread•Classes XML :
classes para criar, ler e manipular documentos XML

Página 37

CAPÍTULO 1 ■ C # E O .NET FRAMEWORK7Compilando para a linguagem intermediária comumO


compilador para uma linguagem .NET pega um arquivo de código-fonte e produz um arquivo de saída
chamadomontagem. A Figura 1-3 ilustra o processo.•Um assembly é um executável ou DLL.•O código
em um assembly não é um código de máquina nativo, mas uma linguagem intermediária chamada
deLinguagem intermediária comum ( CIL ).•Uma montagem, entre outras coisas, contém os seguintes
itens:- O CIL do programa- Metadados sobre os tipos usados no programa- Metadados sobre referências
a outros assembliesFigura 1-3. O processo de compilaçãoA sigla para o idioma intermediário mudou ao
longo do tempo, e diferentes referências usamtermos diferentes. Dois outros termos para o CIL que você
pode encontrar são Linguagem Intermediária (IL)e Microsoft Intermediate Language (MSIL). Esses
termos foram usados com freqüência durante o início do .NETdesenvolvimento e documentação inicial.

Página 38

CAPÍTULO 1 ■ C # E O .NET FRAMEWORK8Compilando para código nativo e execuçãoO CIL do programa


não é compilado para o código de máquina nativo até que seja chamado para ser executado. Em tempo
de execução, o CLRexecuta as seguintes etapas, conforme mostrado na Figura 1-4:•Ele verifica as
características de segurança do conjunto.•Ele aloca espaço na memória.•Ele envia o código executável
do assembly para o compilador just-in-time (JIT), que compilapartes dele para o código nativo.O código
executável no assembly é compilado pelo compilador JIT somente quando necessário. É
entãoarmazenado em cache no caso de ser necessário para execução novamente mais tarde no
programa. Usar este processo significa que o códigoque não é chamado durante a execução não é
compilado para o código nativo, e o código que é chamado só precisa sercompilado uma vez.Figura 1-4.
A compilação para o código nativo ocorre em tempo de execuçãoUma vez que o CIL é compilado para o
código nativo, o CLR o gerencia conforme ele é executado, realizando tarefas comoliberando memória
órfã, verificando limites de array, verificando tipos de parâmetros e gerenciandoexceções. Isso traz dois
termos importantes:•Código gerenciado : o código escrito para o .NET Framework é chamado de código
gerenciado e precisa do CLR.•Código não gerenciado : código que não é executado sob o controle do
CLR, como Win32 C / C ++DLLs, é chamado de código não gerenciado.A Microsoft também fornece uma
ferramenta chamada Native Image Generator , ou Ngen , que leva uma montageme produz código nativo
para o processador atual. O código executado por meio do Ngen evita o JITprocesso de compilação em
tempo de execução.

Página 39

CAPÍTULO 1 ■ C # E O .NET FRAMEWORK9Visão geral da compilação e execuçãoO mesmo processo de


compilação e execução é seguido independentemente do idioma do originalArquivos Fonte. A Figura 1-5
ilustra todos os processos de compilação e tempo de execução para três programasescritos em
diferentes idiomas.Figura 1-5. Visão geral dos processos de tempo de compilação e execução3

Página 40

CAPÍTULO 1 ■ C # E O .NET FRAMEWORK10O Common Language RuntimeO principal componente do


.NET Framework é o CLR, que fica no topo do sistema operacional egerencia a execução do programa,
conforme mostrado na Figura 1-6. O CLR também oferece os seguintes serviços:•Coleta de lixo
automática•Segurança e autenticação•Extensiva funcionalidade de programação por meio do acesso ao
BCL - incluindo funcionalidades comoserviços da web e serviços de dadosFigura 1-6. Visão geral do CLR

Página 41

CAPÍTULO 1 ■ C # E O .NET FRAMEWORK11A infraestrutura de linguagem comumCada linguagem de


programação tem um conjunto de tipos intrínsecos que representam objetos como inteiros,
flutuantesnúmeros de pontos, caracteres e assim por diante. Historicamente, as características desses
tipos variam deuma linguagem de programação para outra e de plataforma para plataforma. Por
exemplo, o número de bitsa constituição de um número inteiro tem variado amplamente, dependendo
do idioma e da plataforma.Essa falta de uniformidade, no entanto, torna difícil se quisermos que os
programas funcionem bem com outrosprogramas e bibliotecas escritos em diferentes linguagens. Para
haver ordem e cooperação, deve haver umconjunto de padrões.A Common Language Infrastructure (CLI)
é um conjunto de padrões que une todos os componentes do.NET Framework em um sistema coeso e
consistente. Ele apresenta os conceitos e a arquitetura dosistema e especifica as regras e convenções às
quais todo o software deve aderir. A Figura 1-7 mostraos componentes do CLI.Figura 1-7. Componentes
da CLITanto o CLI quanto o C # foram aprovados como especificações de padrão internacional aberto
pela EcmaInternacional. (O nome "Ecma" costumava ser um acrônimo para os fabricantes europeus de
computadoresAssociação, mas agora é apenas uma palavra em si.) Os membros da Ecma incluem
Microsoft, IBM, Hewlett-Packard,Adobe e muitas outras empresas associadas a computadores e
eletrônicos de consumo.

Página 42

CAPÍTULO 1 ■ C # E O .NET FRAMEWORK12Partes importantes da CLIEmbora a maioria dos


programadores não precise saber os detalhes das especificações CLI, você deve pelo menosestar
familiarizado com o significado e a finalidade do Common Type System e da Common
LanguageEspecificação.Sistema de tipo comum (CTS)O Common Type System ( CTS ) define as
características dos tipos que devem ser usados no gerenciadocódigo. Alguns aspectos importantes do
CTS são os seguintes:•O CTS define um rico conjunto de tipos intrínsecos, com características fixas e
específicas para cada tipo.•Os tipos fornecidos por uma linguagem de programação compatível com
.NET geralmente mapeiam para algunssubconjunto deste conjunto definido de tipos intrínsecos.•Uma
das características mais importantes do CTS é que todos os tipos são derivados de um comumclasse base
- chamada de objeto.Especificação de linguagem comum (CLS)A Common Language Specification ( CLS )
especifica as regras, propriedades e comportamentos de um .NET-linguagem de programação
compatível. Os tópicos incluem tipos de dados, construção de classes epassagem de parâmetro.Baixe a
partir de Wow! e-book <www.wowebook.com>

Página 43

CAPÍTULO 1 ■ C # E O .NET FRAMEWORK13Revisão dos acrônimosEste capítulo cobriu muitos acrônimos


.NET, então a Figura 1-8 o ajudará a mantê-los corretos.Figura 1-8. Os acrônimos .NET

Página 44

Página 45

CAPÍTULO 2■ ■ ■15Visão geral da programação C #■ Um programa simples em C #■ Identificadores e


palavras-chave■ Principal: o ponto de partida de um programa■ Espaço em branco■ Declarações■
Saída de texto de um programa■ Comentários: anotando o código

Página 46
CAPÍTULO 2 ■ VISÃO GERAL DA PROGRAMAÇÃO C #16Um programa simples em C #Este capítulo
estabelece as bases para estudar C #. Já que vou usar exemplos de código extensivamente em todo
otexto, primeiro preciso mostrar a você a aparência de um programa C # e o que suas várias partes
significam.Vou começar demonstrando um programa simples e explicando seus componentes um por
um. Isso vaiapresentar uma variedade de tópicos, desde a estrutura de um programa C # até o método
de produção do programasaída para a tela.Com essas preliminares de código-fonte fora do caminho,
posso usar amostras de código livremente em todoo resto do texto. Portanto, ao contrário dos capítulos
seguintes, onde um ou dois tópicos são abordados em detalhes, estecapítulo aborda muitos tópicos com
apenas um mínimo de explicação.Vamos começar examinando um programa C # simples. O código-fonte
completo do programa é mostrado no topo,área sombreada na Figura 2-1. Conforme mostrado, o código
está contido em um arquivo de texto chamado SimpleProgram.cs. Como vocêleia, não se preocupe em
entender todos os detalhes. A Tabela 2-1 fornece uma linha por linhadescrição do código.•Quando o
código é compilado e executado, ele exibe a string “Olá!” em uma janelaa tela.•A linha 5 contém dois
caracteres de barra contíguos. Esses personagens - e tudo o que se segueeles na linha - são ignorados
pelo compilador. Isso é chamado de comentário de uma linha .Figura 2-1. O programa SimpleProgram

Página 47

CAPÍTULO 2 ■ VISÃO GERAL DA PROGRAMAÇÃO C #17Tabela 2-1. O Programa SimpleProgram, linha por
linhaNúmero da linhaDescriçãoLinha 1Diz ao compilador que este programa usa tipos do namespace
System.Linha 3Declara um novo namespace, denominado Simple.•O novo namespace começa na chave
aberta na linha 4 e se estendeatravés da chave correspondente na linha 12.•Todos os tipos declarados
nesta seção são membros do namespace.Linha 5Declara um novo tipo de classe, denominado
Programa.•Quaisquer membros declarados entre as chaves correspondentes nas linhas 6 e11 são
membros que compõem esta classe.Linha 7Declara um método chamado Main como membro da classe
Program.•Neste programa, Main é o único membro da classe Program.•Main é uma função especial
usada pelo compilador como ponto de partida doprogramaLinha 9Contém apenas uma declaração
simples e única; esta linha constitui o corpo de Main.•As instruções simples são encerradas por um
ponto e vírgula.•Esta declaração usa uma classe chamada Console, no namespace System, para
imprimirenviar a mensagem para uma janela na tela•Sem a instrução using na linha 1, o compilador não
teria conhecidoonde procurar a classe Console.Mais sobre SimpleProgramO programa AC # consiste em
uma ou mais declarações de tipo. Muito deste livro é gasto explicando odiferentes tipos que você pode
criar e usar em seus programas. Os tipos em um programa podem ser declarados emqualquer ordem.
No exemplo SimpleProgram, apenas um tipo de classe é declarado.Um namespace é um conjunto de
declarações de tipo associadas a um nome. SimpleProgram usa doisnamespaces. Ele cria um novo
namespace chamado Simple, no qual declara seu tipo (classe Program), eusa a classe Console definida
em um namespace chamado System.Para compilar o programa, você pode usar o Visual Studio ou o
compilador de linha de comando. Para usar ocompilador de linha de comando, em sua forma mais
simples, use o seguinte comando em uma janela de comando:csc SimpleProgram.cs

Página 48

CAPÍTULO 2 ■ VISÃO GERAL DA PROGRAMAÇÃO C #18Neste comando, csc é o nome do compilador de


linha de comando e SimpleProgram.cs é o nomedo arquivo de origem.Identificadores e palavras-
chaveIdentificadores são cadeias de caracteres usadas para nomear coisas como variáveis, métodos,
parâmetros e um hostde outras construções de programação que serão abordadas posteriormente.Você
pode criar identificadores autodocumentáveis concatenando palavras significativas em um úniconome
descritivo, usando letras maiúsculas e minúsculas (por exemplo, CardDeck, PlayersHand,
FirstName,SocialSecurityNum). Certos caracteres são permitidos ou proibidos em certas posições em um
identificador.A Figura 2-2 ilustra essas regras.•Os caracteres alfabéticos e sublinhados (a a z, A a Z e _)
são permitidos em qualquerposição.•Os dígitos não são permitidos na primeira posição, mas são
permitidos em qualquer outro lugar.•O caractere @ é permitido na primeira posição de um identificador,
mas não em qualquer outro lugar. O uso deo caractere @, embora permitido, não é recomendado para
uso geral.Figura 2-2. Caracteres permitidos em identificadoresOs identificadores são sensíveis a
maiúsculas e minúsculas. Por exemplo, os nomes de variáveis myVar e MyVar são
diferentesidentificadores. É geralmente uma má ideia, no entanto, ter identificadores que diferem
apenas no caso de algunsas letras, porque são facilmente confundidas.Por exemplo, no seguinte trecho
de código, as declarações de variáveis são todas válidas e declaramdiferentes variáveis inteiras. Mas usar
nomes semelhantes tornará a codificação mais sujeita a erros edepuração mais difícil. Aqueles que
estiverem depurando seu código posteriormente não ficarão satisfeitos.// Válido sintaticamente, mas
não faça isso!int totalCycleCount;int TotalCycleCount;int TotalcycleCount;

Página 49

CAPÍTULO 2 ■ VISÃO GERAL DA PROGRAMAÇÃO C #19Convenções de NomenclaturaA especificação da


linguagem C # sugere que certas convenções de maiúsculas e minúsculas sejam usadas na criação de
identificadores.A Tabela 2-2 resume as diretrizes sugeridas para revestimento.Para a maioria dos
identificadores de tipo, o estilo de capitalização Pascal é recomendado. Neste estilo, cada uma das
palavrascombinado para fazer um identificador é capitalizado - por exemplo, FirstName e
LastName.Tabela 2-2. Estilos de nomenclatura de identificador recomendadosNome do
EstiloDescriçãoUso recomendadoExemplosCasing PascalCada palavra noidentificador está em
maiúscula.Use para nomes de tipo enomes de membros.CardDeck, DealersHandInvólucro de
cameloCada palavra noidentificador, exceto oprimeiro, é capitalizado.Use para variáveis locais
eparâmetros do método.totalCycleCount,randomSeedParamMaiúsculasO identificador écomposto de
todosletras maiúsculas.Use apenas para abreviações. IO, DMA, XMLEmbora estas sejam as diretrizes
sugeridas, muitas organizações usam outras convenções—particularmente na nomenclatura de campos
de membros, que apresentarei no próximo capítulo. Dois dosconvenções comuns são as
seguintes:•Comece um nome de campo com um sublinhado: _highTemp, _lowTemp•Comece um nome
de campo com m_: m_highTemp, m_lowTempAmbos os métodos têm a vantagem de mostrar
imediatamente que esses identificadores sãonomes de campo. Esses formulários também permitem que
o recurso IntelliSense do Visual Studio agrupe todos os campos emos pop-ups.

Página 50

CAPÍTULO 2 ■ VISÃO GERAL DA PROGRAMAÇÃO C #20Palavras-chavePalavras-chave são os tokens de


string de caracteres usados para definir a linguagem C #. A Tabela 2-3 fornece uma lista completadas
palavras-chave C #.Algumas coisas importantes que você deve saber sobre as palavras-chave
são:•Palavras-chave não podem ser usadas como nomes de variáveis ou qualquer outra forma de
identificador, a menos que precedidas deo personagem.•Todas as palavras-chave C # consistem
inteiramente em letras minúsculas. (Nomes de tipo .NET, no entanto, usam Pascalinvólucro.)Tabela 2-3.
As palavras-chave C #const abstratoexternointForacurtotipo deComocontinue
falsointerfacesobreportamanho
deuintbasedecimalfinalmenteinternoparamsstackallocUlongboolpadrãofixoéprivadoestáticonão
verificadopausadelegar
flutuadorfechaduraprotegidocordainseguroByteFazparalongopúblicoestruturaushortcasoDuplopara
cadanamespacesomente leiturainterruptorusandopegaroutrovamos
paraNovorefistovirtualCaracteresenumE senuloRetornalançarvazioverificadoeventoobjeto
implícitosbyteverdadevolátilclasseexplícito emoperadorseladoexperimentarenquantoPalavras-chave
contextuais são identificadores que agem como palavras-chave apenas em certas construções de
linguagem. Noessas posições, eles têm significados particulares; mas ao contrário das palavras-chave,
que nunca podem ser usadas comoidentificadores, palavras-chave contextuais podem ser usados como
identificadores em outras partes do código. A Tabela 2-4 contéma lista de palavras-chave
contextuais.Tabela 2-4. As palavras-chave contextuais C #adicionarascendentededescendentedinâmicoé
igual adeobterglobalgrupopara dentroJunte-sedeixeiemordenar
porparcialremoverselecionarconjuntovalorvarOndeprodução

Página 51

CAPÍTULO 2 ■ VISÃO GERAL DA PROGRAMAÇÃO C #21Principal: o ponto de partida de um


programaCada programa C # deve ter uma classe com um método (função) chamado Main. No
SimpleProgramprograma mostrado anteriormente, ele foi declarado em uma classe chamada
Programa.•O ponto de partida da execução de cada programa C # é na primeira instrução em Main.•O
nome Main deve estar em maiúscula.•A forma mais simples de Main é a seguinte:static void Main ()
{Afirmações}Espaço em brancoO espaço em branco em um programa refere-se a caracteres que não
possuem um caractere de saída visível. Espaço em branco emo código-fonte é ignorado pelo compilador,
mas é usado pelo programador para tornar o código mais claro emais fácil de ler. Alguns dos caracteres
de espaço em branco incluem o seguinte:•Espaço•Aba•Nova linha•Retorno de carruagemPor exemplo,
os fragmentos de código a seguir são tratados exatamente da mesma forma pelo compilador, apesar
desuas diferenças na aparência.// Bem formatadoA Principal(){Console.WriteLine ("Olá!");}// Apenas
concatenadoMain () {Console.WriteLine ("Olá!");}

Página 52

CAPÍTULO 2 ■ VISÃO GERAL DA PROGRAMAÇÃO C #22AfirmaçõesAs instruções em C # são muito


semelhantes às de C e C ++. Esta seção apresenta a forma geral deafirmações; os formulários de
declaração específicos são abordados no Capítulo 9.Declarações SimplesUma declaração é uma
instrução de código-fonte que descreve um tipo ou diz ao programa para executar uma ação.•Uma
instrução simples é encerrada por um ponto e vírgula.Por exemplo, o código a seguir é uma sequência de
duas instruções simples. A primeira declaração defineuma variável inteira chamada var1 e inicializa seu
valor em 5. A segunda instrução imprime o valor devariável var1 para uma janela na tela.int var1 =
5;System.Console.WriteLine ("O valor de var1 é {0}", var1);BlocosUm bloco é uma sequência de zero ou
mais instruções delimitadas por um conjunto correspondente de chaves; atua como umdeclaração
sintática única.Você pode criar um bloco do conjunto de duas instruções no exemplo anterior, incluindo
oinstruções em chaves correspondentes, conforme mostrado no código a seguir:{int var1 =
5;System.Console.WriteLine ("O valor de var1 é {0}", var1);}Algumas coisas importantes a saber sobre os
bloqueios são as seguintes:•Você pode usar um bloco sempre que a sintaxe exigir uma instrução, mas a
ação que você precisa exigirmais de uma declaração simples.•Determinadas construções de programa
requerem blocos. Nessas construções, você não pode substituir um simplesdeclaração para o
bloco.•Embora uma instrução simples seja encerrada por um ponto e vírgula, um bloco não é seguido
por umponto e vírgula. (Na verdade, o compilador permitirá , mas não é um bom estilo.){ Terminando
ponto e vírgula↓Terminando ponto e vírgulaint var2 = 5;↓System.Console.WriteLine ("O valor de var1 é
{0}", var1);}↑ Sem ponto-e-vírgula finalBaixe a partir de Wow! e-book <www.wowebook.com>

Página 53

CAPÍTULO 2 ■ VISÃO GERAL DA PROGRAMAÇÃO C #23Saída de texto de um programaUma janela de


console é uma janela de prompt de comando simples que permite a um programa exibir texto ereceber
entrada do teclado. O BCL fornece uma classe chamada Console (no namespace System),que contém
métodos para inserir e enviar dados para uma janela do console.EscreverWrite é membro da classe
Console. Ele envia uma string de texto para a janela de console do programa. Em seuforma mais simples,
Write envia uma string literal de texto para a janela. A string deve ser colocada entreaspas - aspas
duplas, não aspas simples.A linha de código a seguir mostra um exemplo de uso do membro
Write:Console.Write ("Este é um texto trivial.");↑String de saídaEste código produz a seguinte saída na
janela do console:Este é um texto trivial.Outro exemplo é o código a seguir, que envia três strings literais
para ojanela do console:System.Console.Write ("Este é o texto1.");System.Console.Write ("Este é o
texto2.");System.Console.Write ("Este é o text3.");Este código produz a saída a seguir. Observe que Write
não acrescenta um caractere de nova linhaapós uma string, a saída das três instruções é executada em
uma única linha.Este é o texto1. Este é o texto2. Este é o
texto3.↑↑↑PrimeiroSegundoTerceirodeclaraçãodeclaraçãodeclaração

Página 54

CAPÍTULO 2 ■ VISÃO GERAL DA PROGRAMAÇÃO C #24WriteLineWriteLine é outro membro do Console,


que executa as mesmas funções que Write, mas acrescenta umcaractere de nova linha ao final de cada
string de saída.Por exemplo, se você usar o código anterior, substituindo WriteLine por Write, a saída
será ativadalinhas separadas:System.Console.WriteLine ("Este é o texto 1.");System.Console.WriteLine
("Este é o texto 2.");System.Console.WriteLine ("Este é o texto 3.");Este código produz a seguinte saída
na janela do console:Este é o texto 1.Este é o texto 2.Este é o texto 3.

Página 55

CAPÍTULO 2 ■ VISÃO GERAL DA PROGRAMAÇÃO C #25A string de formatoA forma geral das instruções
Write e WriteLine leva mais de um único parâmetro.•Se houver mais de um único parâmetro, os
parâmetros serão separados por vírgulas.•O primeiro parâmetro deve ser sempre uma string e é
chamado de string de formato .•A string de formato pode conter marcadores de substituição .- Um
marcador de substituição marca a posição na string de formato onde um valor deveria estarsubstituído
na string de saída.- Consiste em um número inteiro dentro de um conjunto de chaves correspondentes.
O inteiro é o numéricoposição do valor de substituição a ser usado.•Os parâmetros que seguem a string
de formato são chamados de valores de substituição . Estas substituiçõesos valores são numerados,
começando em 0.A sintaxe é a seguinte:Console.WriteLine ( FormatString, SubVal0, SubVal1,
SubVal2 , ...);Por exemplo, a seguinte declaração tem dois marcadores de substituição, numerados 0 e 1,
e doisvalores de substituição, cujos valores são 3 e 6, respectivamente.Marcadores de
substituição↓↓Console.WriteLine ("Dois inteiros de amostra são {0} e {1}.", 3, 6);↑↑String de
formatoValores de substituiçãoEste código produz a seguinte saída na tela:Dois inteiros de amostra são 3
e 6.

Página 56

CAPÍTULO 2 ■ VISÃO GERAL DA PROGRAMAÇÃO C #26Vários marcadores e valoresEm C #, você pode


usar qualquer número de marcadores e qualquer número de valores.•Os valores podem ser usados em
qualquer ordem.•Os valores podem ser substituídos qualquer número de vezes na string de formato.Por
exemplo, a instrução a seguir usa três marcadores e apenas dois valores. Observe que o valor 1 éusado
antes do valor 0 e esse valor 1 é usado duas vezes.Console.WriteLine ("Três inteiros são {1}, {0} e {1}.", 3,
6);Este código exibe o seguinte na tela:Três inteiros são 6, 3 e 6.Um marcador não deve tentar fazer
referência a um valor em uma posição além do comprimento da lista devalores de substituição. Se isso
acontecer, não produzirá um erro de compilação, mas um erro de tempo de execução (chamado de
exceção ).Por exemplo, na declaração a seguir, existem dois valores de substituição, com as posições 0 e
1.O segundo marcador, entretanto, faz referência à posição 2 - que não existe. Isso produzirá umerro de
tempo de execução.Posição 0 Posição 1↓ ↓Console.WriteLine ("Dois inteiros são {0} e {2}.", 3 6); // Erro!
↑Não há valor de posição 2.

Página 57

CAPÍTULO 2 ■ VISÃO GERAL DA PROGRAMAÇÃO C #27Comentários: anotando o códigoVocê já viu


comentários de linha única, então aqui vou discutir o segundo tipo de comentários embutidos—
comentários delimitados - e mencione um terceiro tipo chamado comentários de documentação .•Os
comentários delimitados têm um marcador inicial de dois caracteres e um marcador final de dois
caracteres.•O texto entre os marcadores correspondentes é ignorado pelo compilador.•Os comentários
delimitados podem abranger qualquer número de linhas.Por exemplo, o código a seguir mostra um
comentário delimitado abrangendo várias linhas.↓ Início do comentário abrangendo várias linhas/ *Este
texto é ignorado pelo compilador.Ao contrário dos comentários de linha única, comentários
delimitadoscomo este, pode-se abranger várias linhas.* /↑ Fim do comentárioUm comentário delimitado
também pode abranger apenas parte de uma linha. Por exemplo, a seguinte declaração mostratexto
comentado no meio de uma linha. O resultado é a declaração de uma única variável, var2.Começo do
comentário↓int / * var 1, * / var2;↑Fim do comentário■ Nota Os comentários de linha única e
delimitados se comportam em C # da mesma forma que em C e C ++.

Página 58

CAPÍTULO 2 ■ VISÃO GERAL DA PROGRAMAÇÃO C #28Mais sobre comentáriosExistem várias outras


coisas importantes que você precisa saber sobre os comentários:•Comentários delimitados aninhados
não são permitidos. Apenas um comentário pode entrar em vigor por vez. Se vocêstentativa de aninhar
comentários, o comentário que começar primeiro estará em vigor até o final de seu escopo.•O escopo
para tipos de comentários específicos é o seguinte:- Para comentários de uma linha, o comentário está
em vigor até o final da linha atual.- Para comentários delimitados, o comentário está em vigor até que o
primeiro delimitador final seja encontrado.As seguintes tentativas de comentários estão incorretas:↓
Abre o comentário/ * Esta é uma tentativa de um comentário aninhado./ * ← Ignorado porque está
dentro de um comentárioComentário interno* / ← Fecha o comentário porque é o primeiro delimitador
final encontrado* /← Erro de sintaxe porque não possui delimitador de abertura↓ Abre o comentário↓
Ignorado porque está dentro de um comentário// Comentário de uma linha / * Comentário aninhado?
* / ← Incorreto porque não tem delimitador de aberturaComentários de documentaçãoC # também
fornece um terceiro tipo de comentário: o comentário da documentação . Comentários de
documentaçãocontêm texto XML que pode ser usado para produzir a documentação do programa.
Comentários deste tipo parecemcomentários de linha única, exceto que eles têm três barras contíguas
em vez de duas. Vou cobrircomentários de documentação no Capítulo 25.O código a seguir mostra a
forma dos comentários da documentação:/// <resumo>/// Esta classe faz .../// </summary>programa de
aula{...

Página 59

CAPÍTULO 2 ■ VISÃO GERAL DA PROGRAMAÇÃO C #29Resumo dos tipos de comentáriosComentários


embutidos são seções de texto que são ignoradas pelo compilador, mas são incluídas no código
paradocumente. Os programadores inserem comentários em seu código para explicá-lo e documentá-lo.
Tabela 2-5resume os tipos de comentários.Tabela 2-5 . Tipos de comentáriosTipoDescrição do fim do
inícioÚnica linha//O texto do marcador de início ao final da linha atual éignorado pelo
compilador.Delimitado/ ** /O texto entre os marcadores de início e fim é ignorado
pelocompilador.Documentação///Comentários deste tipo contêm texto XML que deve ser usadopor uma
ferramenta para produzir a documentação do programa.

Página 60

Página 61

CAPÍTULO 3■ ■ ■31Tipos, armazenamento e variáveis■ Programa AC # é um conjunto de declarações


de tipo■ Um tipo é um modelo■ Instanciando um tipo■ Membros de dados e membros de função■
Tipos predefinidos■ Tipos definidos pelo usuário■ A pilha e a pilha■ Tipos de valor e tipos de
referência■ Variáveis■ Digitação estática e a palavra-chave dinâmica■ Tipos anuláveis

Página 62
CAPÍTULO 3 ■ TIPOS, ARMAZENAMENTO E VARIÁVEIS32Programa AC # é um conjunto de declarações
de tipoSe você fosse caracterizar amplamente o código-fonte dos programas C e C ++, você poderia dizer
que umprograma é um conjunto de funções e tipos de dados e que um programa C ++ é um conjunto de
funções e classes. UMAO programa C #, entretanto, é um conjunto de declarações de tipo.•O código-
fonte de um programa C # ou DLL é um conjunto de uma ou mais declarações de tipo.•Para um
executável, um dos tipos declarados deve ser uma classe que inclui um método chamado Main.•Um
namespace é uma maneira de agrupar um conjunto relacionado de declarações de tipo e dar um nome
ao grupo.Uma vez que seu programa é um conjunto relacionado de declarações de tipo, você
geralmente declarará seu programadigite dentro de um namespace que você criar.Por exemplo, o código
a seguir mostra um programa que consiste em três declarações de tipo. Os trêstipos são declarados
dentro de um novo namespace chamado MyProgram.namespace MyProgram// Crie um novo
namespace.{DeclaraçãoOfTypeA// Declare um tipo.DeclaraçãoOfTypeB// Declare um tipo.classe C//
Declare um tipo.{static void Main (){...}}}Os namespaces são abordados com mais detalhes no Capítulo
10.Baixe a partir de Wow! e-book <www.wowebook.com>

Página 63

CAPÍTULO 3 ■ TIPOS, ARMAZENAMENTO E VARIÁVEIS33Um tipo é um modeloUma vez que um


programa C # é apenas um conjunto de declarações de tipo, aprender C # consiste em aprender a criar
etipos de uso. Portanto, a primeira coisa que você precisa fazer é ver o que é um tipo.Você pode começar
pensando em um tipo como modelo para a criação de estruturas de dados. Não são os dadosestrutura
em si, mas especifica as características dos objetos construídos a partir do modelo.Um tipo é definido
pelos seguintes elementos:•Um nome•Uma estrutura de dados para conter seus membros de
dados•Comportamentos e restriçõesPor exemplo, a Figura 3-1 ilustra os componentes de dois tipos:
short e int.Figura 3-1. Um tipo é um modelo.Instanciando um tipoA criação de um objeto real a partir do
modelo do tipo é chamada de instanciar o tipo.•O objeto criado pela instanciação de um tipo é chamado
de objeto do tipo ou instância dotipo. Os termos são intercambiáveis.•Cada item de dados em um
programa C # é uma instância de algum tipo - um tipo fornecido pelolinguagem, fornecida pela BCL ou
outra biblioteca, ou definida pelo programador.A Figura 3-2 ilustra a instanciação de objetos de dois
tipos predefinidos.Figura 3-2. Instanciar um tipo cria uma instância.

Página 64

CAPÍTULO 3 ■ TIPOS, ARMAZENAMENTO E VARIÁVEIS34Membros de dados e membros de


funçãoAlguns tipos, como short, int e long, são chamados de tipos simples e podem armazenar apenas
um único item de dados.Outros tipos podem armazenar vários itens de dados. Uma matriz , por
exemplo, é um tipo que pode armazenar váriositens do mesmo tipo. Os itens individuais são chamados
de elementos e são referenciados por um número, chamadoum índice . O Capítulo 14 descreve os arrays
em detalhes.Tipos de membrosOutros tipos, entretanto, podem conter itens de dados de muitos tipos
diferentes. Os elementos individuais nestestipos são chamados de membros e, ao contrário de matrizes,
em que cada membro é referido por um número, estesmembros têm nomes distintos.Existem dois tipos
de membros: membros de dados e membros de função.•Os membros de dados armazenam dados que
são relevantes para o objeto da classe ou para a classe como um todo.•Os membros da função executam
o código. Os membros da função definem como o tipo pode agir.Por exemplo, a Figura 3-3 mostra alguns
dos membros de dados e membros de função do tipo XYZ. istocontém dois membros de dados e dois
membros de função.Figura 3-3. Tipos especificam membros de dados e membros de função.

Página 65

CAPÍTULO 3 ■ TIPOS, ARMAZENAMENTO E VARIÁVEIS35Tipos PredefinidosC # fornece 16 tipos


predefinidos, que são mostrados na Figura 3-4 e listados nas Tabelas 3-1 e 3-2. Elesinclui 13 tipos simples
e 3 tipos não simples.Os nomes de todos os tipos predefinidos consistem em todos os caracteres
minúsculos . O simples predefinidotipos incluem o seguinte:•Onze tipos numéricos, incluindo o
seguinte:- Vários comprimentos de tipos inteiros com e sem sinal.- Tipos de ponto flutuante - float e
double.- Um tipo decimal de alta precisão denominado decimal. Ao contrário de float e double, digite
decimal poderepresentam números fracionários decimais exatamente. Geralmente é usado para cálculos
monetários.•Um tipo de caractere Unicode, chamado char.•Um tipo booleano, denominado bool. O tipo
bool representa valores booleanos e deve ser um de doisvalores - verdadeiro ou falso.■ Nota Ao
contrário de C e C ++, os valores numéricos não têm uma interpretação booleana em C #.Os três tipos
não simples são os seguintes:•String de tipo, que é uma matriz de caracteres Unicode•Objeto de tipo,
que é o tipo no qual todos os outros tipos são baseados•Tipo dinâmico, que é usado ao usar assemblies
escritos em linguagens dinâmicas

Página 66

CAPÍTULO 3 ■ TIPOS, ARMAZENAMENTO E VARIÁVEIS36Figura 3-4. Os tipos predefinidosMais sobre os


tipos predefinidosTodos os tipos predefinidos são mapeados diretamente para os tipos .NET subjacentes.
Os nomes de tipo C # são apenasapelidos para os tipos .NET, portanto, usar os nomes .NET funciona bem
sintaticamente, embora isso não seja recomendado.Em um programa C #, você deve usar os nomes C #
em vez dos nomes .NET.Os tipos simples predefinidos representam um único item de dados. Eles estão
listados na Tabela 3-1, junto comos intervalos de valores que eles podem representar e os tipos .NET
subjacentes aos quais eles mapeiam.

Página 67

CAPÍTULO 3 ■ TIPOS, ARMAZENAMENTO E VARIÁVEIS37Tabela 3-1. Os Tipos Simples


PredefinidosNomeSignificadoAlcance.NET FrameworkTipoPadrãoValorsbyteInteiro assinado de 8 bits-
128-127System.SByte0byteInteiro sem sinal de 8 bits0-255System.Byte0curtoInteiro assinado de 16 bits-
32.768-32.767System.Int160ushort inteiro não assinado de 16 bits0-65.535System.UInt160intInteiro
assinado de 32 bits-2.147.483.648-2.147.483.647System.Int320uintInteiro sem sinal de 32 bits0-
4.294.967.295System.UInt320longoInteiro assinado de 64 bits-9.223.372.036.854.775.808-
9.223.372.036.854.775.807System.Int640UlongInteiro não assinado de 64 bits0-
18.446.744.073.709.551.615System.UInt640flutuadorFlutuação de precisão simples1,5 × 10-45–3,4 ×
1038System.Single0.0fduplo flutuador de precisão dupla5 × 10-324–1,7 ×
10308System.Double0.0dboolboleanoverdadeiro falsoSystem.BooleanfalsoCaracteresCaractere
UnicodeU + 0000 – U + ffffSystem.Char\ x0000Valor decimal decimal com 28-precisão de dígitos
significativos± 1,0 × 1028– ± 7,9 × 1028System.Decimal0mOs tipos predefinidos não simples são um
pouco mais complexos. Os valores do tipo string contêm zero oumais caracteres Unicode. O tipo de
objeto é a classe base para todos os outros tipos no sistema, incluindoos tipos predefinidos e simples. A
Tabela 3-2 mostra os tipos não simples predefinidos.Tabela 3-2. Os tipos não simples
predefinidosNomeSignificadoTipo .NET FrameworkobjetoA classe base da qual todos os outros tipos são
derivadosSystem.ObjectcordaUma sequência de caracteres UnicodeSystem.StringdinâmicoUm tipo
projetado para ser usado com conjuntos escritos emlinguagens dinâmicas

Página 68

CAPÍTULO 3 ■ TIPOS, ARMAZENAMENTO E VARIÁVEIS38Tipos definidos pelo usuárioAlém dos 15 tipos


predefinidos fornecidos pelo C #, você também pode criar seus próprios tipos definidos pelo usuário.
LáExistem seis tipos de tipos que você pode criar. Eles são os seguintes:•tipos de classe•tipos de
estrutura•tipos de array•tipos de enum•tipos de delegado•tipos de interfaceVocê cria um tipo usando
uma declaração de tipo , que inclui as seguintes informações:•O tipo de tipo que você está criando•O
nome do novo tipo•Uma declaração (nome e especificação) de cada um dos membros do tipo - exceto
para array etipos de delegado, que não têm membros nomeadosDepois de declarar um tipo, você pode
criar e usar objetos do tipo como se fossemtipos predefinidos. A Figura 3-5 resume o uso de tipos
predefinidos e definidos pelo usuário. Usando predefinidotipos é um processo de uma etapa em que
você simplesmente instancia os objetos desse tipo. Usando definido pelo usuáriotipos é um processo de
duas etapas. Você deve primeiro declarar o tipo e, em seguida, instanciar objetos do tipo.Figura 3-5. Os
tipos predefinidos requerem apenas instanciação. Os tipos definidos pelo usuário requerem duas
etapas:declaração e instanciação.

Página 69

CAPÍTULO 3 ■ TIPOS, ARMAZENAMENTO E VARIÁVEIS39A pilha e a pilhaEnquanto um programa está


sendo executado, seus dados devem ser armazenados na memória. Quanta memória é necessária para
umitem, e onde e como é armazenado, depende de seu tipo.Um programa em execução usa duas
regiões de memória para armazenar dados: a pilha e o heap .A pilhaO sistema cuida de toda a
manipulação da pilha. Você, como programador, não precisa fazer nadacom ele explicitamente. Mas
entender suas funções básicas lhe dará uma melhor compreensão do queseu programa está fazendo
quando está em execução e permite que você entenda melhor a documentação C #e literatura.A pilha é
uma matriz de memória que atua como uma estrutura de dados LIFO (último a entrar, primeiro a sair).
Ele armazena váriostipos de dados:•Os valores de certos tipos de variáveis•O ambiente de execução
atual do programa•Parâmetros passados para métodosFatos sobre pilhasAs características gerais das
pilhas são as seguintes:•Os dados podem ser adicionados e excluídos apenas do topo da pilha.•Colocar
um item de dados no topo da pilha é chamado de empurrar o item para a pilha.•Excluir um item do topo
da pilha é chamado de retirar o item da pilha.A Figura 3-6 ilustra as funções e a terminologia da
pilha.Figura 3-6. Empurrando e pulando na pilha

Página 70
CAPÍTULO 3 ■ TIPOS, ARMAZENAMENTO E VARIÁVEIS40A pilhaO heap é uma área onde pedaços de
memória são alocados para armazenar certos tipos de objetos de dados. Ao contráriopilha, a memória
pode ser armazenada e removida da pilha em qualquer ordem. A Figura 3-7 mostra um programaque
armazenou quatro itens no heap.Figura 3-7. A pilha de memóriaEmbora seu programa possa armazenar
itens no heap, ele não pode excluí-los explicitamente. Em vez disso, oO coletor de lixo (GC) do CLR limpa
automaticamente os objetos de heap órfãos quando determina queseu código não os está mais
acessando. Isso o livra do que em outras linguagens de programação pode seruma tarefa sujeita a erros.
A Figura 3-8 ilustra o processo de coleta de lixo.Figura 3-8. Coleta de lixo automática na pilha

Página 71

CAPÍTULO 3 ■ TIPOS, ARMAZENAMENTO E VARIÁVEIS41Tipos de valor e tipos de referênciaO tipo de um


item de dados define quanta memória é necessária para armazená-lo e os membros de dados
quecompreendê-lo. O tipo também determina onde um objeto é armazenado na memória - a pilha ou
heap.Os tipos são divididos em duas categorias: tipos de valor e tipos de referência. Objetos desses tipos
sãoarmazenados de forma diferente na memória.•Os tipos de valor requerem apenas um único
segmento de memória, que armazena os dados reais.•Os tipos de referência requerem dois segmentos
de memória:- O primeiro contém os dados reais - e está sempre localizado no heap.- O segundo é uma
referência que aponta para onde na pilha os dados estão armazenados.Os dados que não são membros
de outro tipo são armazenados conforme mostrado na Figura 3-9. Para tipos de valor, os dados
sãoarmazenados na pilha. Para tipos de referência, os dados reais são armazenados no heap e a
referência é armazenadana pilha.Figura 3-9. Armazenamento de dados que não fazem parte de outro
tipoArmazenamento de membros de um objeto de tipo de referênciaA Figura 3-9 mostra como os dados
são armazenados quando não são membros de outro tipo. Quando é um membro deoutro tipo, os dados
podem ser armazenados de forma um pouco diferente.•A parte de dados de um objeto de tipo de
referência é sempre armazenada no heap, conforme mostrado na Figura 3-9.•Um objeto de tipo de
valor, ou a parte de referência de um tipo de referência, pode ser armazenado na pilha oua pilha,
dependendo das circunstâncias.Suponha, por exemplo, que você tenha uma instância de um tipo de
referência, chamado MyType, que possui doismembros - um membro do tipo de valor e um membro do
tipo de referência. Como é armazenado? É o tipo de valormembro armazenado na pilha e o tipo de
referência dividido entre a pilha e o heap, conforme mostrado emFigura 3-9? A resposta é não.

Página 72

CAPÍTULO 3 ■ TIPOS, ARMAZENAMENTO E VARIÁVEIS42Lembre-se de que, para um tipo de referência,


os dados de uma instância são sempre armazenados no heap. Desde ambosmembros fazem parte dos
dados do objeto, ambos são armazenados no heap, independentemente de seremvalor ou tipos de
referência. A Figura 3-10 ilustra o caso do tipo MyType.•Mesmo que o membro A seja um tipo de valor,
ele faz parte dos dados da instância de MyType e éportanto, armazenado com os dados do objeto no
heap.•O membro B é um tipo de referência e, portanto, sua parte de dados sempre será armazenada no
heap, comomostrado pela pequena caixa marcada "Dados". A diferença é que sua referência também é
armazenada noheap, dentro da parte de dados do objeto MyType envolvente.Figura 3-10.
Armazenamento de dados como parte de um tipo de referênciaNota Para qualquer objeto de um tipo de
referência, todos os seus membros de dados são armazenados no heap, independentemente deeles são
do tipo de valor ou tipo de referência.Categorizando os tipos C #A Tabela 3-3 mostra todos os tipos
disponíveis em C # e quais tipos eles são - tipos de valor ou referênciatipos. Cada tipo de referência é
abordado posteriormente no texto.Tabela 3-3. Tipos de valor e tipos de referência em C #Tipos de
valorTipos de ReferênciaTipos
predefinidossbytebyteflutuadorobjetocurtointlongoboolushortuintUlongDuploCaracteresdecimalcordadi
nâmicoTipos definidos pelo usuárioestruturaenumclasseinterfacedelegarmatrizBaixe a partir de Wow! e-
book <www.wowebook.com>

Página 73

CAPÍTULO 3 ■ TIPOS, ARMAZENAMENTO E VARIÁVEIS43VariáveisUma linguagem de programação de


propósito geral deve permitir que um programa armazene e recupere dados.•Uma variável é um nome
que representa os dados armazenados na memória durante a execução do programa.•C # fornece
quatro categorias de variáveis, cada uma das quais será discutida em detalhes. Esses tipos sãolistado na
Tabela 3-4.Tabela 3-4. Os quatro tipos de variáveisNomeMembro de uma descrição de tipoVariável
localNãoContém dados temporários dentro do escopo de um métodoCamposimContém dados
associados a um tipo ou instância de um tipoParâmetroNãoUma variável temporária usada para passar
dados de um método paraoutro métodoElemento ArraysimUm membro de uma coleção sequenciada de
(normalmente)itens de dados homogêneosDeclarações de variáveisUma variável deve ser declarada
antes de ser usada. A declaração da variável define a variável erealiza duas coisas:•Ele dá um nome à
variável e associa um tipo a ela.•Ele permite que o compilador aloque memória para ele.Uma declaração
de variável simples requer pelo menos um tipo e um nome. A seguinte declaração defineuma variável
chamada var2, do tipo int:Tipo↓int var2;↑NomePor exemplo, a Figura 3-11 representa a declaração de
quatro variáveis e suas posições na pilha.Figura 3-11. Declarações de tipo de valor e variável de tipo de
referência

Página 74

CAPÍTULO 3 ■ TIPOS, ARMAZENAMENTO E VARIÁVEIS44Inicializadores de variávelAlém de declarar o


nome e o tipo de uma variável, você pode opcionalmente usar a declaração para inicializar seumemória
para um valor específico.Um inicializador de variável consiste em um sinal de igual seguido pelo valor de
inicialização, conforme mostrado aqui:Inicializador↓int var2 = 17;Variáveis locais sem inicializadores têm
um valor indefinido e não podem ser usadas até que tenhamfoi atribuído um valor. A tentativa de usar
uma variável local indefinida faz com que o compilador produza ummensagem de erro.A Figura 3-12
mostra uma série de declarações de variáveis locais à esquerda e a pilha resultanteconfiguração à direita.
Algumas das variáveis têm inicializadores e outras não.Figura 3-12. Inicializadores de variávelInicialização
AutomáticaAlguns tipos de variáveis são definidos automaticamente para valores padrão se forem
declarados sem um inicializador,e outros não. Variáveis que não são inicializadas automaticamente com
os valores padrão contêm indefinidovalores até que o programa atribua um valor a eles. A Tabela 3-5
mostra quais tipos de variáveis sãoinicializado automaticamente e quais não são. Abordarei cada um dos
cinco tipos de variáveis posteriormente no texto.Tabela 3-5. Tipos de VariáveisTipo de
VariávelArmazenado emUso auto-inicializadoVariáveis locaisEmpilhar ou empilhar e empilhar NãoUsado
para computação local dentro de ummembro de funçãoCampos de classeHeapsimMembros de uma
classeCampos StructEmpilhar ou empilharsimMembros de uma estruturaParâmetrosPilhaNãoUsado
para passar valores para dentro e para forade um métodoElementos de matrizHeapsimMembros de uma
matriz

Página 75

CAPÍTULO 3 ■ TIPOS, ARMAZENAMENTO E VARIÁVEIS45Declarações de múltiplas variáveisVocê pode


declarar várias variáveis em uma única instrução de declaração.•As variáveis em uma declaração de
múltiplas variáveis devem ser todas do mesmo tipo.•Os nomes das variáveis devem ser separados por
vírgulas. Os inicializadores podem ser incluídos com onomes de variáveis.Por exemplo, o código a seguir
mostra duas instruções de declaração válidas com várias variáveis.Observe que as variáveis inicializadas
podem ser misturadas com variáveis não inicializadas, desde que estejam separadaspor vírgulas. A
última declaração de declaração mostrada é inválida porque tenta declarar diferentestipos de variáveis
em uma única instrução.// Declarações de variáveis - algumas com inicializadores, outras semint var3 =
7, var4, var5 = 3;var6 duplo, var7 = 6,52;TipoTipo diferente↓↓int var8, float var9; // Erro! Não é possível
misturar tipos (int e float)Usando o valor de uma variávelUm nome de variável representa o valor
armazenado pela variável. Você pode usar o valor usando a variávelnome.Por exemplo, na instrução a
seguir, o valor de var2 é recuperado da memória e colocado ema posição da variável.Console.WriteLine
("{0}", var2);Digitação estática e a palavra-chave dinâmicaUma coisa que você notou é que cada variável
inclui o tipo da variável, permitindo quecompilador para determinar a quantidade de memória
necessária em tempo de execução e quais partes devem serarmazenados na pilha e quais no heap. O
tipo da variável é determinado em tempo de compilação enão pode ser alterado em tempo de execução.
Isso é chamado de digitação estática .Nem todos os idiomas, porém, são tipificados estaticamente.
Muitos, incluindo linguagens de script comoIronPython e IronRuby são digitados dinamicamente . Ou
seja, o tipo de uma variável pode não ser resolvidoaté o tempo de execução. Uma vez que essas são
linguagens .NET, os programas C # precisam ser capazes de usar assemblies escritos emessas línguas.Para
resolver o problema que C # precisa ser capaz de resolver em tempo de compilação um tipo referenciado
em umassembly que não resolve seus tipos até o tempo de execução, os designers da linguagem C #
adicionaram a palavra-chavedinâmico para a linguagem. A palavra-chave dinâmica representa um tipo C
# real específico que sabe comoresolve-se em tempo de execução. Ou seja, é tipificado estaticamente
como dinâmico!Isso satisfaz ambas as restrições. O compilador C # pode resolver a palavra-chave para
um tipo real, e oO objeto de tipo pode resolver-se para o tipo do assembly de destino em tempo de
execução.

Página 76

CAPÍTULO 3 ■ TIPOS, ARMAZENAMENTO E VARIÁVEIS46Tipos anuláveisExistem situações,


particularmente ao trabalhar com bancos de dados, em que você deseja indicar que umvariável
atualmente não contém um valor válido. Para tipos de referência, você pode fazer isso facilmente,
definindo ovariável para nula. Quando você define uma variável de um tipo de valor, no entanto, sua
memória é alocada seja ounão seu conteúdo tem qualquer significado válido.O que você gostaria nesta
situação é ter um indicador booleano associado à variável, entãoque quando o valor é válido, o indicador
é verdadeiro, e quando o valor não é válido, o indicador é falso.Tipos anuláveis permitem que você crie
uma variável de tipo de valor que pode ser marcada como válida ou inválida para quevocê pode verificar
se uma variável é válida antes de usá-la. Os tipos de valor regulares são chamados de tipos não
anuláveis.Criação de um tipo anulávelUm tipo anulável é sempre baseado em outro tipo, chamado de
tipo subjacente , que já foideclarado.•Você pode criar um tipo anulável a partir de qualquer tipo de
valor, incluindo os tipos predefinidos e simples.•Você não pode criar um tipo anulável de um tipo de
referência ou de outro tipo anulável.•Você não declara explicitamente um tipo anulável em seu código.
Em vez disso, você declara uma variável de umtipo anulável . O compilador cria implicitamente o tipo
anulável para você.Para criar uma variável de tipo anulável, basta adicionar um ponto de interrogação ao
final do nome dotipo subjacente, na declaração da variável. Infelizmente, essa sintaxe faz parecer que
você tem ummuitas perguntas sobre seu código.Por exemplo, o código a seguir declara uma variável do
tipo int anulável. Observe que o sufixo éanexado ao nome do tipo - não ao nome da variável.Sufixo↓int?
myNInt = 28;↑O nome do tipo anulável inclui o sufixo.Com esta instrução de declaração, o compilador se
encarrega de produzir o tipo anulável ecriando a variável desse tipo.

Página 77

CAPÍTULO 3 ■ TIPOS, ARMAZENAMENTO E VARIÁVEIS47Usar um tipo anulável é quase o mesmo que


usar uma variável de qualquer outro tipo. Lendo uma variável de umtipo anulável retorna seu valor. Você
deve, entretanto, certificar-se de que a variável não seja nula. Tentandoler o valor de uma variável nula
produz uma exceção.•Como qualquer variável, para recuperar seu valor, basta usar seu nome.•Para
verificar se um tipo anulável tem um valor, você pode compará-lo com nulo.Compare com nulo↓if
(myInt1! = null)Console.WriteLine ("{0}", myInt1);↑Use o nome da variávelAmbos os conjuntos de código
produzem a seguinte saída:15Você pode converter facilmente entre um tipo anulável e seu tipo não
anulável correspondente. Nós iremosem conversões em detalhes no Capítulo 18, mas os pontos
importantes para tipos anuláveis são os seguintes:•Há uma conversão implícita entre um tipo não
anulável e sua versão anulável. Ou seja naoelenco é necessário.•Há uma conversão explícita entre um
tipo anulável e sua versão não anulável.Por exemplo, as linhas a seguir mostram a conversão em ambas
as direções. Na primeira linha, um literal do tipoint é convertido implicitamente em um valor do tipo int?
e é usado para inicializar a variável do tipo anulável.Na segunda linha, a variável é explicitamente
convertida em sua versão não anulável.int? meuInt1 = 15;// Converter implicitamente int em int?int
regInt = (int) myInt1;// Converter int explicitamente? para int

Página 78

CAPÍTULO 3 ■ TIPOS, ARMAZENAMENTO E VARIÁVEIS48Atribuição a um tipo anulávelVocê pode atribuir


três tipos de valores a uma variável de tipo anulável:•Um valor do tipo subjacente•Um valor do mesmo
tipo anulável•O valor nuloO código a seguir mostra um exemplo de cada um dos três tipos de
atribuição:int? myI1, myI2, myI3;myI1 = 28;// Valor do tipo subjacentemyI2 = myI1;// Valor do tipo
anulávelmyI3 = nulo;// NuloConsole.WriteLine ("myI1: {0}, myI2: {1}", myI1, myI2);Este código produz a
seguinte saída:myI1: 28, myI2: 28No Capítulo 25, quando você tiver uma compreensão mais clara do C #,
explicarei os pontos mais delicados dotipos anuláveis.
Página 79

CAPÍTULO 4■ ■ ■49Aulas: o básico■ Visão geral das aulas■ Programas e aulas: um exemplo rápido■
Declarando uma classe■ Membros da classe■ Criação de variáveis e instâncias de uma classe■
Alocando memória para os dados■ Membros da instância■ Modificadores de acesso■ Acessando
membros de dentro da classe■ Acessando membros de fora da classe■ Juntando tudo

Página 80

CAPÍTULO 4 ■ AULAS: O BÁSICO50Visão geral das aulasNo capítulo anterior, você viu que o C # fornece
seis tipos definidos pelo usuário. O mais importante deles,e o que abordarei primeiro é a classe . Uma
vez que o tópico das classes em C # é amplo, sua discussão seráespalhados pelos próximos
capítulos.Uma classe é uma estrutura de dados ativaAntes dos dias de análise e design orientado a
objetos, os programadores pensavam em um programa apenas como umsequência de instruções. O foco
na época era estruturar e otimizar essas instruções.Com o advento do paradigma orientado a objetos, o
foco mudou de instruções de otimização paraorganizar os dados e funções de um programa em
conjuntos encapsulados de itens de dados relacionados logicamente efunções, chamadas classes.Uma
classe é uma estrutura de dados que pode armazenar dados e executar códigos. Ele contém o
seguinte:•Membros de dados , que armazenam dados associados à classe ou uma instância da classe.
Dadosos membros geralmente modelam os atributos do objeto do mundo real que a classe
representa.•Membros de função , que executam código. Os membros da função geralmente modelam
as funções eações do objeto do mundo real que a classe representa.A classe AC # pode ter qualquer
número de membros de dados e funções. Os membros podem ser qualquercombinação de nove tipos de
membros possíveis. A Tabela 4-1 mostra esses tipos de membros. Os que vou cobrir emeste capítulo -
campos e métodos - são verificados na tabela.Tabela 4-1. Tipos de membros da classeDados de
armazenamento de membrosMembros de função executam código✓ Campos❑ Constantes✓
Métodos❑ Propriedades❑ Construtores❑ Destruidores❑ Operadores❑ Indexadores❑ Eventos■ Nota
As classes são conjuntos encapsulados de itens de dados relacionados logicamente e funções que
geralmente representamobjetos no mundo real ou um mundo conceitual.

Página 81

CAPÍTULO 4 ■ AULAS: O BÁSICO51Programas e aulas: um exemplo rápidoUm programa C # em execução


é um grupo de objetos de tipo em interação, a maioria dos quais são instâncias de classes. ParaPor
exemplo, suponha que você tenha um programa que simula um jogo de pôquer. Quando está em
execução, pode ter uminstância de uma classe chamada Dealer, cujo trabalho é executar o jogo, e várias
instâncias de uma classe chamadaJogador, que representa os jogadores do jogo.O objeto Dealer
armazena informações como o estado atual do baralho de cartas e o número dejogadoras. Suas ações
incluem embaralhar o baralho e distribuir as cartas.A classe Player é muito diferente. Ele armazena
informações como o nome do jogador e a quantidade dedinheiro restante para apostar e executa ações
como analisar a mão atual do jogador e fazer apostas.A Figura 4-1 ilustra o programa em execução.Figura
4-1. Os objetos em um programa em execuçãoUm programa real, sem dúvida, conteria dezenas de
outras classes além de Dealer e Player.Isso inclui classes como Cartas e Baralho. Cada classe modela algo
que é um componente deo jogo de pôquer.■ Nota Um programa em execução é um conjunto de objetos
interagindo entre si.

Página 82

CAPÍTULO 4 ■ AULAS: O BÁSICO52Declarando uma classeEmbora os tipos int, double e char sejam
definidos na linguagem C #, classes como Dealer e Player,como você provavelmente pode imaginar, não
são definidos pelo idioma. Se quiser usá-los em um programa, vocêtem que defini-los você mesmo. Você
faz isso escrevendo uma declaração de classe .Uma declaração de classe define as características e os
membros de uma nova classe. Não cria uminstância da classe, mas cria o modelo a partir do qual as
instâncias da classe serão criadas. A classedeclaração fornece o seguinte:•O nome da classe•Os
membros da classe•As características da classeA seguir está um exemplo da sintaxe mínima para uma
declaração de classe. As chaves contêmas declarações de membros que constituem o corpo da classe .
Os membros da classe podem ser declarados em qualquer ordemdentro do corpo da classe. Isso significa
que é perfeitamente normal que a declaração de um membro se refira a outromembro que ainda não
está definido até mais abaixo na declaração da classe.Keyword Class name↓↓classe
MyExcellentClass{Declarações de membros}Por exemplo, o código a seguir mostra os contornos de duas
declarações de classe:classe Dealer// declaração de classe{...}classe jogador// declaração de
classe{...}Nota Uma vez que uma declaração de classe "define" uma nova classe, você frequentemente
verá uma declaração de classe referida comodefinição de classe na literatura e no uso comum entre
programadores.Baixe a partir de Wow! e-book <www.wowebook.com>

Página 83

CAPÍTULO 4 ■ AULAS: O BÁSICO53Membros da classeOs campos e métodos são os mais importantes


dos tipos de membros da classe. Os campos são membros de dados emétodos são membros de
função.CamposUm campo é uma variável que pertence a uma classe.•Pode ser de qualquer tipo,
predefinido ou definido pelo usuário.•Como todas as variáveis, os campos armazenam dados e têm as
seguintes características:- Eles podem ser escritos.- Eles podem ser lidos.A sintaxe mínima para declarar
um campo é a seguinte:Tipo↓Identificador de tipo ;↑Nome do campoPor exemplo, a seguinte classe
contém a declaração do campo MyField, que pode armazenar umvalor int:classe MyClass{ Type↓int
MyField;↑} Nome do campo■ Nota Ao contrário de C e C ++, não há variáveis globais (ou seja, variáveis
ou campos) declaradas fora de um tipo. Tudoos campos pertencem a um tipo e devem ser declarados na
declaração do tipo.

Página 84

CAPÍTULO 4 ■ AULAS: O BÁSICO54Inicialização de campo explícita e implícitaVisto que um campo é um


tipo de variável, a sintaxe de um inicializador de campo é a mesma da variávelinicializador mostrado no
capítulo anterior.•Um inicializador de campo é parte da declaração do campo e consiste em um sinal de
igual seguido por umexpressão que avalia um valor.•O valor de inicialização deve ser determinável em
tempo de compilação .classe MyClass{int F1 = 17;}↑Inicializador de campo•Se nenhum inicializador for
usado, o valor de um campo é definido pelo compilador para um valor padrão, determinado poro tipo do
campo. A Tabela 3-1 (no Capítulo 3) fornece os valores padrão para os tipos simples. Pararesumindo-os,
porém, o valor padrão para cada tipo é 0 e falso para bool. O padrão paratipos de referência é nulo.Por
exemplo, o código a seguir declara quatro campos. Os primeiros dois campos são inicializados
implicitamente. oos segundos dois campos são inicializados explicitamente com inicializadores.classe
MyClass{int F1;// Inicializado com 0 - tipo de valorstring F2;// Inicializado para nulo - tipo de
referênciaint F3 = 25;// Inicializado em 25string F4 = "abcd";// Inicializado em "abcd"}Declarações com
vários camposVocê pode declarar vários campos do mesmo tipo na mesma instrução, separando os
nomes comvírgulas. Você não pode misturar tipos diferentes em uma única declaração. Por exemplo,
você pode combinar os quatroprecedendo as declarações de campo em duas instruções, com
exatamente o mesmo resultado semântico:int F1, F3 = 25;string F2, F4 = "abcd";

Página 85

CAPÍTULO 4 ■ AULAS: O BÁSICO55MétodosUm método é um bloco nomeado de código executável que


pode ser executado a partir de muitas partes diferentes doprograma, e até mesmo de outros programas.
(Existem também métodos anônimos, que não são nomeados—mas irei abordá-los no Capítulo
15.)Quando um método é chamado ou invocado , ele executa seu código e retorna ao código que o
chamou.Alguns métodos retornam um valor para a posição de onde foram chamados. Métodos
correspondem afunções de membro em C ++.A sintaxe mínima para declarar um método inclui os
seguintes componentes:•Tipo de retorno : indica o tipo de valor que o método retorna. Se um método
não retorna um valor,o tipo de retorno é especificado como void.•Nome : este é o nome do
método.•Lista de parâmetros : consiste em pelo menos um conjunto vazio de parênteses
correspondentes. Se houverparâmetros (que abordarei no próximo capítulo), eles são listados entre os
parênteses.•Corpo do método : consiste em um conjunto correspondente de chaves, contendo o código
executável.Por exemplo, o código a seguir declara uma classe com um método simples chamado
PrintNums. Dedeclaração, você pode dizer o seguinte sobre PrintNums:•Ele não retorna nenhum valor;
portanto, o tipo de retorno é especificado como void.•Ele tem uma lista de parâmetros vazia.•Ele
contém duas linhas de código no corpo do método.classe SimpleClass{Lista de parâmetros de tipo de
retorno↓↓void PrintNums (){Console.WriteLine ("1");Console.WriteLine ("2");}}■ Nota Ao contrário de C
e C ++, não há funções globais (ou seja, métodos ou funções) declaradas fora de um tipodeclaração.
Além disso, ao contrário de C e C ++, não existe um tipo de retorno “padrão” para um método. Todos os
métodos devem incluir umretornar digite ou liste-o como vazio.

Página 86

CAPÍTULO 4 ■ AULAS: O BÁSICO56Criação de variáveis e instâncias de uma classeA declaração da classe


é apenas o projeto a partir do qual as instâncias da classe são criadas. Assim que uma aula édeclarado,
você pode criar instâncias da classe.•As classes são tipos de referência, o que, como você se lembrará do
capítulo anterior, significa queeles requerem memória tanto para a referência aos dados quanto para os
dados reais.•A referência aos dados é armazenada em uma variável do tipo de classe. Então, para criar
uma instância doclasse, você precisa começar declarando uma variável do tipo de classe. Se a variável
não foi inicializada, éo valor é indefinido.A Figura 4-2 ilustra como definir a variável para conter a
referência. No topo do código noà esquerda está uma declaração para a classe Dealer. Abaixo disso está
uma declaração para a classe Program, que contém o métodoA Principal. Main declara a variável
theDealer do tipo Dealer. Uma vez que a variável não foi inicializada, seu valor éindefinido, conforme
mostrado à direita na figura.Figura 4-2. Alocando memória para a referência de uma variável de classe

Página 87

CAPÍTULO 4 ■ AULAS: O BÁSICO57Alocando memória para os dadosDeclarar a variável do tipo de classe


aloca a memória para conter a referência, mas não omemória para armazenar os dados reais do objeto
de classe. Para alocar memória para os dados reais, você usa onovo operador.•O novo operador aloca e
inicializa memória para uma instância de qualquer tipo especificado. istoaloca a memória da pilha ou
heap, dependendo do tipo.•Use o novo operador para formar uma expressão de criação de objeto , que
consiste no seguinte:- A palavra-chave novo.- O nome do tipo de instância para a qual a memória deve
ser alocada.- Parênteses correspondentes, que podem ou não incluir parâmetros. Vou discutir mais
sobreos parâmetros possíveis mais tarde.Parênteses de palavra-chave são obrigatórios.↓↓novo
TypeName ()↑Tipo•Se a memória alocada é para um tipo de referência, a expressão de criação do objeto
retorna uma referênciapara a instância alocada e inicializada do objeto no heap.Isso é exatamente o que
você precisa para alocar e inicializar a memória para manter os dados da instância da classe.Use o novo
operador para criar uma expressão de criação de objeto e atribua o valor retornado por ele aovariável de
classe. Aqui está um exemplo:Dealer theDealer;// Declara variável para a referência.theDealer = novo
revendedor (); // Aloca memória para o objeto de classe.↑Expressão de criação de objetoO código à
esquerda na Figura 4-3 mostra o novo operador usado para alocar memória e criar uminstância da classe
Dealer, que é então atribuída à variável de classe. A estrutura da memória é ilustradana figura, à direita
do código.Figura 4-3. Alocando memória para os dados de uma variável de classe

Página 88

CAPÍTULO 4 ■ AULAS: O BÁSICO58Combinando as etapasVocê pode combinar as duas etapas


inicializando a variável com a expressão de criação de objeto.Declare a variável.↓Revendedor theDealer =
novo revendedor ();// Declare e inicialize.↑Inicialize com uma expressão de criação de objeto.No caso de
variáveis locais, mas não de campos, você pode simplificar a sintaxe um pouco mais tendo oo compilador
infere o tipo na parte da declaração à esquerda. Mas vou cobrir isso na seção sobrevariáveis no próximo
capítulo.

Página 89

CAPÍTULO 4 ■ AULAS: O BÁSICO59Membros da instânciaUma declaração de classe atua como um


projeto a partir do qual você pode criar tantas instâncias da classe quantovocê gosta.•Membros da
instância : cada instância de uma classe é uma entidade separada que tem seu próprio conjunto de
dadosmembros, distintos das outras instâncias da mesma classe. Estes são chamados de membros de
instânciauma vez que estão associados a uma instância da classe.•Membros estáticos : os membros da
instância são o padrão, mas você também pode declarar membros chamados estáticosmembros que
estão associados à classe, em vez da instância. Abordarei isso no Capítulo 6.Como exemplo de membros
da instância, o código a seguir mostra o programa de pôquer com trêsinstâncias da classe Player. A Figura
4-4 mostra que cada instância possui um valor diferente para o campo Nome.classe Dealer {...}//
Declarar classeclass Player {// Declarar classestring Name;// Field...}class Program {static void Main ()
{Revendedor theDealer = novo revendedor ();Jogador jogador1 = novo jogador ();Jogador jogador2 =
novo jogador ();Jogador player3 = novo jogador ();...}}Figura 4-4. Os membros da instância têm valores
distintos entre os objetos de classe.

Página 90

CAPÍTULO 4 ■ AULAS: O BÁSICO60Modificadores de acessoDe dentro de uma classe, qualquer membro


de função pode acessar qualquer outro membro da classe simplesmente usandonome desse membro.O
modificador de acesso é uma parte opcional de uma declaração de membro que especifica quais outras
partes doprograma tem acesso ao membro. O modificador de acesso é colocado antes dos formulários
de declaração simples.A seguir está a sintaxe para campos e métodos:CamposIdentificador de tipo de
AccessModifierMétodosAccessModifier ReturnType MethodName (){...}As cinco categorias de acesso de
membro são as seguintes. Descreverei os dois primeiros neste capítulo eos outros no Capítulo
7.•privado•público•protegido•interno•interno protegidoAcesso privado e públicoMembros privados
são acessíveis apenas de dentro da classe em que foram declarados - outras classesnão pode ver ou
acessá-los.•O acesso privado é o nível de acesso padrão, portanto, se um membro for declarado sem um
modificador de acesso, eleé um membro privado.•Você também pode usar o modificador de acesso
privado para declarar explicitamente um membro como privado.•Não há diferença semântica entre
declarar um membro privado implicitamente em oposição aexplicitamente. Os formulários são
equivalentes.Por exemplo, as duas declarações a seguir especificam membros privados int:int MyInt1;//
Implicitamente declarado privadoprivate int MyInt2;// Declarado explicitamente como
privado↑Modificador de acesso

Página 91

CAPÍTULO 4 ■ AULAS: O BÁSICO61Os membros públicos são acessíveis a outros objetos no programa.
Você deve usar o acesso públicomodificador para especificar o acesso público.Modificador de
acesso↓public int MyInt;Descrevendo o acesso público e privadoAs figuras neste texto representam
classes como caixas rotuladas, conforme mostrado na Figura 4-5.•Os membros da classe são
representados como caixas menores rotuladas dentro das caixas da classe.•Os membros privados são
representados inteiramente dentro de sua caixa de classe.•Os membros públicos são representados
parcialmente fora de sua caixa de classe.Figura 4-5. Representando classes e membros

Página 92

CAPÍTULO 4 ■ AULAS: O BÁSICO62Exemplo de acesso de membroA classe C1 no código a seguir declara


campos e métodos públicos e privados. A Figura 4-6 ilustraa visibilidade dos membros da classe C1.classe
C1{intF1;// Campo privado implícitoprivate int F2;// Campo privado explícitopublic int F3;// Campo
públicovoid DoCalc ()// Método privado implícito{...}public int GetVal ()// Método público{...}}Figura 4-6.
Membros de classes públicas e privadasBaixe a partir de Wow! e-book <www.wowebook.com>

Página 93
CAPÍTULO 4 ■ AULAS: O BÁSICO63Acessando membros de dentro da classeConforme mencionado, os
membros de uma classe podem acessar os outros membros da classe apenas usando seus nomes.Por
exemplo, a seguinte declaração de classe mostra os métodos da classe acessando os campose outros
métodos. Mesmo que os campos e dois dos métodos sejam declarados privados, todos osos membros
de uma classe podem ser acessados por qualquer método (ou qualquer membro de função) da classe.
Figura 4-7ilustra o código.classe DaysTemp{// Camposint privado Alto = 75;int privado Baixo = 45;//
Métodosprivate int GetHigh (){return High;// Acesse o campo privado}private int GetLow (){return
Low;// Acesse o campo privado}flutuação pública Média (){return (GetHigh () + GetLow ()) / 2; // Acessar
métodos privados}↑↑}Acessando os métodos privadosFigura 4-7. Os membros de uma classe podem
acessar livremente uns aos outros.

Página 94

CAPÍTULO 4 ■ AULAS: O BÁSICO64Acessando membros de fora da classePara acessar um membro de


instância pública de fora da classe, você deve incluir o nome da variável e onome do membro, separado
por um ponto (ponto). Isso é chamado de notação de sintaxe de ponto; será discutido emmais detalhes
posteriormente.Por exemplo, a segunda linha do código a seguir mostra um exemplo de acesso a um
método defora da classe:DaysTemp myDt = new DaysTemp (); // Cria um objeto da classe.float fValue =
myDt.A Average (); // Acesse de fora.↑ ↑Nome da variável Nome do membroComo exemplo, o código a
seguir declara duas classes: DaysTemp e Program.•Os dois campos em DaysTemp são declarados
públicos, para que possam ser acessados de fora da classe.•Method Main é um membro da classe
Program. Ele cria uma variável e um objeto da classe DaysTemp, eatribui valores aos campos do objeto.
Em seguida, ele lê os valores dos campos e os imprime.classe DaysTemp// Declare a classe
DaysTemp{público int Alto = 75;público int Baixo = 45;}programa de aula// Declare o programa da classe.
{static void Main (){Nome variável↓DaysTemp temp = novo DaysTemp ();// Crie o objeto.Nome e campo
da variável↓temp.High = 85;// Atribuir aos campos.temp.Low = 60;Nome e campo da
variável↓Console.WriteLine ("Alto: {0}", temp.High); // Leia os campos.Console.WriteLine ("Baixo: {0}",
temp.Low);}}Este código produz a seguinte saída:Alta: 85Baixo: 60

Página 95

CAPÍTULO 4 ■ AULAS: O BÁSICO65Juntando tudoO código a seguir cria duas instâncias e armazena suas
referências em variáveis chamadas t1 e t2.A Figura 4-8 ilustra t1 e t2 na memória. O código demonstra as
três ações a seguir discutidasaté agora no uso de uma classe:•Declarando uma classe•Criação de
instâncias da classe•Acessando os membros da classe (ou seja, escrevendo para um campo e lendo de
um campo)classe DaysTemp// Declare a classe.{público int Alto, Baixo;// Declare os campos da
instância.public int Average ()// Declare o método da instância.{retorno (alto + baixo) / 2;}}programa de
aula{static void Main (){// Crie duas instâncias de DaysTemp.DaysTemp t1 = novo DaysTemp ();DaysTemp
t2 = novo DaysTemp ();// Grave nos campos de cada instância.t1.Alto = 76; t1.Baixo = 57;t2.Alto = 75;
t2.Baixo = 53;// Leia os campos de cada instância e chame um método de// cada
instância.Console.WriteLine ("t1: {0}, {1}, {2}",t1.Alto, t1.Baixo, t1.Média ());Console.WriteLine ("t2: {0},
{1}, {2}",t2.Alto, t2.Baixo, t2.Média ());↑↑↑}Campo CampoMétodo}
Página 96

CAPÍTULO 4 ■ AULAS: O BÁSICO66Este código produz a seguinte saída:t1: 76, 57, 66t2: 75, 53, 64Figura
4-8. Layout de memória das instâncias t1 e t2

Página 97

CAPÍTULO 5■ ■ ■67Métodos■ A Estrutura de um Método■ Variáveis locais■ Invocações de


método■ Valores Retornados■ Parâmetros■ Parâmetros de valor■ Parâmetros de referência■
Parâmetros de saída■ Matrizes de parâmetros■ Resumo dos tipos de parâmetro■ Stack Frames■
Recursão■ Sobrecarga de método

Página 98

CAPÍTULO 5 ■ MÉTODOS68A Estrutura de um MétodoUm método é um bloco de código com um nome.


Você pode executar o código de outro lugar no programausando o nome do método. Você também pode
passar dados para um método e receber dados de volta como saída.Como você viu no capítulo anterior,
um método é um membro de função de uma classe. Métodos têm doisseções principais, conforme
mostrado na Figura 5-1 - o cabeçalho do método e o corpo do método.•O cabeçalho do método
especifica as características do método, incluindo o seguinte:- Se o método retorna dados e, em caso
afirmativo, que tipo- O nome do método- Quais tipos de dados podem ser passados de e para o método
e como esses dados devem sertratado•O corpo do método contém a sequência de instruções de código
executáveis. A execução começa noprimeira declaração no corpo do método e continua
sequencialmente através do método.Figura 5-1. A estrutura de um métodoO exemplo a seguir mostra a
forma do cabeçalho do método. Vou cobrir cada parte no seguintePáginas.int MeuMétodo (int par1,
string par2)↑ ↑↑Método de RetornoParâmetroDigite o nomeLista

Página 99

CAPÍTULO 5 ■ MÉTODOS69Por exemplo, o código a seguir mostra um método simples chamado


MyMethod que, por sua vez, chama oMétodo WriteLine várias vezes:void MyMethod ()
{Console.WriteLine ("Primeiro");Console.WriteLine ("Último");}Embora esses primeiros capítulos
descrevam classes, há outro tipo definido pelo usuário chamado struct,que irei cobrir no Capítulo 12.
Muito do que este capítulo cobre sobre métodos de classe também é verdade paramétodos de
estrutura.Execução de código no corpo do métodoO corpo do método é um bloco , que (como você se
lembrará do Capítulo 2) é uma sequência de declaraçõesentre chaves. Um bloco pode conter os
seguintes itens:•Variáveis locais•Construções de fluxo de controle•Invocações de método•Blocos
aninhados neleA Figura 5-2 mostra um exemplo de corpo de método e alguns de seus
componentes.Figura 5-2. Exemplo de corpo de método

Página 100

CAPÍTULO 5 ■ MÉTODOS70Variáveis LocaisComo os campos, as variáveis locais armazenam dados.


Embora os campos geralmente armazenem dados sobre o estado do objeto,variáveis geralmente são
criadas para armazenar dados para cálculos locais ou transitórios. A Tabela 5-1 compara econtrasta
variáveis locais e campos de instância.A linha de código a seguir mostra a sintaxe das declarações de
variáveis locais. O inicializador opcionalconsiste no sinal de igual seguido por um valor a ser usado para
inicializar a variável.Nome variável↓Identificador de tipo = valor;↑Inicializador opcional•A existência de
uma variável local é limitada ao bloco em que é criada e os blocos aninhadosdentro dele.- A variável
passa a existir no ponto em que é declarada.- Ele deixa de existir quando o bloco conclui a
execução.•Você pode declarar variáveis locais em qualquer posição no corpo do método, mas elas
devem ser declaradasantes de serem usados.O exemplo a seguir mostra a declaração e o uso de duas
variáveis locais. O primeiro é do tipo int,e o segundo é do tipo SomeClass.static void Main (){int myInt =
15;SomeClass sc = new SomeClass ();...}Tabela 5-1. Campos de instância x variáveis locaisCampo de
InstânciaVariável LocalTempo de vidaComeça quando a instância da classe écriada. Termina quando a
aulainstância não está mais acessível.Começa no ponto do bloco ondeé declarado. Termina quando o
blococompleta a execução.Inicialização implícitaInicializado com um valor padrão para otipo.Sem
inicialização implícita. oo compilador produz uma mensagem de errose a variável não foi atribuída
antesusar.Área de armazenamentoTodos os campos de uma classe são armazenados ema pilha,
independentemente deeles são tipos de valor ou referênciatipos.Tipo de valor: armazenado na pilha.Tipo
de referência: referência armazenada ema pilha e os dados armazenados nopilha.

Página 101

CAPÍTULO 5 ■ MÉTODOS71Digite Inference e a palavra-chave varSe você olhar o código a seguir, verá
que, ao fornecer o nome do tipo no início dodeclaração, você está fornecendo informações que o
compilador já deve ser capaz de inferir dalado direito da inicialização.•Na primeira declaração de
variável, o compilador pode inferir que 15 é um int.•Na segunda declaração, a expressão de criação de
objeto no lado direito retorna um objeto dedigite MyExcellentClass.Portanto, em ambos os casos, incluir
o nome do tipo explícito no início da declaração é redundante.static void Main (){total int =
15;MyExcellentClass mec = new MyExcellentClass ();...}A partir do C # 3.0, você pode usar a nova
palavra-chave var no lugar do nome do tipo explícito noinício da declaração da variável, da seguinte
forma:static void Main (){ Palavra-chave↓var total = 15;var mec = new MyExcellentClass ();...}A palavra-
chave var não sinaliza um tipo especial de variável. É apenas uma abreviação sintática para qualquer
coisatipo pode ser inferido da inicialização no lado direito da instrução. Na primeira declaração,
éabreviação de int. No segundo, é uma forma abreviada de MyExcellentClass. O segmento de código
anterior comos nomes de tipo explícitos e o segmento de código com as palavras-chave var são
semanticamente equivalentes.Algumas condições importantes sobre o uso da palavra-chave var são as
seguintes:•Você pode usá-lo apenas com variáveis locais - não com campos.•Você pode usá-lo apenas
quando a declaração da variável inclui uma inicialização.•Depois que o compilador infere o tipo de uma
variável, ela é fixa e imutável.■ Nota A palavra-chave var não é como o var JavaScript, que pode fazer
referência a diferentes tipos. É uma abreviatura para otipo real inferido do lado direito do sinal de igual.
A palavra-chave var não altera o fortemente tipadonatureza do C # .

Página 102

CAPÍTULO 5 ■ MÉTODOS72Variáveis locais dentro de blocos aninhadosCorpos de método podem ter


outros blocos aninhados dentro deles.•Pode haver qualquer número de blocos, e eles podem ser
sequenciais ou mais aninhados. Os blocos podem seraninhado em qualquer nível.•Variáveis locais
podem ser declaradas dentro de blocos aninhados e, como todas as variáveis locais, seus tempos de
vidae a visibilidade é limitada ao bloco em que são declarados e aos blocos aninhados nele.A Figura 5-3
ilustra a vida útil de duas variáveis locais, mostrando o código e o estado da pilha.As setas indicam a
linha que acabou de ser executada.•A variável var1 é declarada no corpo do método, antes do bloco
aninhado.•A variável var2 é declarada dentro do bloco aninhado. Existe desde o momento em que é
declarado, até o fimdo bloco em que foi declarado.•Quando o controle passa para fora do bloco
aninhado, suas variáveis locais são retiradas da pilha.Figura 5-3. O tempo de vida de uma variável
localNota Em C e C ++ você pode declarar uma variável local, e então dentro de um bloco aninhado você
pode declarar outrovariável local com o mesmo nome. O nome interno mascara o nome externo
enquanto dentro do escopo interno. Em C #,no entanto, você não pode declarar outra variável local com
o mesmo nome dentro do escopo do primeiro nomeindependentemente do nível de aninhamento.Baixe
a partir de Wow! e-book <www.wowebook.com>

Página 103

CAPÍTULO 5 ■ MÉTODOS73Constantes LocaisUma constante local é muito parecida com uma variável
local, exceto que, uma vez inicializada, seu valor não pode ser alterado.Como uma variável local, uma
constante local deve ser declarada dentro de um bloco.As duas características mais importantes de uma
constante são as seguintes:•Uma constante deve ser inicializada em sua declaração.•Uma constante não
pode ser alterada após sua declaração.A declaração principal para uma constante é mostrada a seguir. A
sintaxe é a mesma de um campo oudeclaração de variável, exceto para o seguinte:•A adição da palavra-
chave const antes do tipo.•O inicializador obrigatório. O valor do inicializador deve ser determinável em
tempo de compilação e égeralmente um dos tipos simples predefinidos ou uma expressão feita deles.
Também pode ser oreferência nula, mas não pode ser uma referência a um objeto, porque referências a
objetos sãodeterminado em tempo de execução.■ Nota: A palavra-chave const não é um modificador,
mas parte da declaração principal. Deve ser colocado imediatamenteantes do tipo.Palavra-
chave↓identificador de tipo const = valor ;↑Inicializador necessárioUma constante local, como uma
variável local, é declarada em um corpo de método ou bloco de código e sai do escopono final do bloco
em que está declarado. Por exemplo, no código a seguir, a constante local PI vaifora do escopo no final
do método DisplayRadii.void DisplayRadii (){const double PI = 3,1416;// Declara constante localpara (raio
interno = 1; raio <= 5; raio ++){área dupla = raio * raio * PI; // Lê a partir da constante
localConsole.WriteLine("Raio: {0}, Área: {1}" raio, área);}}

Página 104

CAPÍTULO 5 ■ MÉTODOS74Fluxo de ControleOs métodos contêm a maior parte do código para as ações
que compõem um programa. O restante está em outromembros de função, como propriedades e
operadores - mas a maior parte está nos métodos.O termo fluxo de controle se refere ao fluxo de
execução por meio de seu programa. Por padrão, programaa execução passa sequencialmente de uma
instrução para a próxima. As declarações de controle permitem que vocêmodificar a ordem de
execução.Nesta seção, mencionarei apenas algumas das instruções de controle disponíveis que você
pode usar em seu código.O Capítulo 9 os aborda em detalhes.•Declarações de seleção : essas
declarações permitem que você selecione qual declaração, ou bloco dedeclarações, para executar.- if:
execução condicional de uma instrução- if ... else: execução condicional de uma instrução ou outra-
switch: execução condicional de uma instrução de um conjunto•Instruções de iteração : essas instruções
permitem que você faça um loop, ou itere, em um bloco de instruções.- para: Loop - teste no topo-
while: Loop - testando no topo- fazer: Loop - testando na parte inferior- foreach: Executar uma vez para
cada membro de um conjunto•Declarações de salto : Essas declarações permitem que você pule de um
lugar no bloco ou método paraoutro.- break: sai do loop atual.- continuar: vai para o final do loop atual.-
goto: vai para uma declaração nomeada.- return: retorna a execução ao método de chamada.Por
exemplo, o método a seguir mostra duas das instruções de fluxo de controle. Não se preocupe comos
detalhes.void SomeMethod (){intVal = 3;Operador de comparação de igualdade↓if (intVal == 3)//
declaração ifConsole.WriteLine ("O valor é 3.");para (int i = 0; i <5; i ++)// para
declaraçãoConsole.WriteLine ("Valor de i: {0}", i);}

Página 105

CAPÍTULO 5 ■ MÉTODOS75Invocações de MétodoVocê pode chamar outros métodos de dentro de um


corpo de método.•As frases chamam um método e chamam um método são sinônimos.•Você chama
um método usando seu nome, junto com a lista de parâmetros, que discutirei em breve.Por exemplo, a
classe a seguir declara um método chamado PrintDateAndTime, que é chamado demétodo interno
principal:classe MyClass{void PrintDateAndTime ()// Declare o método.{DateTime dt = DateTime.Now;//
Obtenha a data e a hora atuais.Console.WriteLine ("{0}", dt);// Escreva.}static void Main ()// Declare o
método.{MyClass mc = new MyClass ();mc.PrintDateAndTime ();// Invoque o
método.}↑↑}MétodoVaziolista de parâmetros de nomeA Figura 5-4 ilustra a sequência de ações quando
um método é chamado:1. A execução do método atual é suspensa nesse ponto da invocação.2. O
controle é transferido para o início do método invocado.3. O método invocado é executado até que seja
concluído.4. O controle retorna ao método de chamada.Figura 5-4. Fluxo de controle ao chamar um
método

Página 106

CAPÍTULO 5 ■ MÉTODOS76Valores RetornadosUm método pode retornar um valor para o código de


chamada. O valor retornado é inserido no código de chamada ema posição na expressão onde a
invocação ocorreu.•Para retornar um valor, o método deve declarar um tipo de retorno antes do nome
do método.•Se um método não retornar um valor, ele deve declarar um tipo de retorno void.O código a
seguir mostra duas declarações de método. O primeiro retorna um valor do tipo int. O segundonão
retorna um valor.Tipo de retorno↓int GetHour () {...}void DisplayHour () {...}↑Nenhum valor é
retornado.Um método que declara um tipo de retorno deve retornar um valor do método usando o
seguinteforma da instrução return, que inclui uma expressão após a palavra-chave return. Cada caminho
atravéso método deve terminar com uma declaração de retorno deste formulário.return Expression ;//
Retorna um valor.↑Avalia para um valor do tipo de retornoPor exemplo, o código a seguir mostra um
método chamado GetHour, que retorna um valor do tipo int.Tipo de retorno↓int GetHour (){DateTime dt
= DateTime.Now;// Obtenha a data e a hora atuais.hora interna = dt.Hora;// Obtenha a hora.hora de
retorno;// Retorna um int.}↑Declaração de devolução

Página 107

CAPÍTULO 5 ■ MÉTODOS77Você também pode retornar objetos de tipos definidos pelo usuário. Por
exemplo, o código a seguir retorna um objetodo tipo MyClass:Tipo de retorno - MyClass↓Método
MyClass3 (){MyClass mc = new MyClass ();...return mc;// Retorna um objeto MyClass.}Como outro
exemplo, no código a seguir, o método GetHour é chamado na instrução WriteLine emMain e retorna
um valor int para essa posição na instrução WriteLine.classe MyClass{↓ Tipo de retornopublic int
GetHour (){DateTime dt = DateTime.Now;// Obtenha a data e a hora atuais.hora interna = dt.Hora;//
Obtenha a hora.hora de retorno;// Retorna um int.}↑}Valor de retornoprograma de aula{static void Main
(){Invocação de métodoMyClass mc = new MyClass ();↓Console.WriteLine ("Hora: {0}", mc.GetHour ());}↑
↑}Método de Instâncianome nome

Página 108

CAPÍTULO 5 ■ MÉTODOS78Declaração de devolução e métodos de anulaçãoNa seção anterior, você viu


que os métodos que retornam um valor devem conter instruções de retorno. Vaziométodos não
requerem instruções de retorno. Quando o fluxo de controle atinge a chave de fechamento deo corpo do
método, o controle retorna ao código de chamada e nenhum valor é inserido de volta no código de
chamada.Muitas vezes, no entanto, você pode simplificar a lógica do programa saindo do método mais
cedo, quando certoAs condições se aplicam.•Você pode sair de um método void a qualquer momento
usando a seguinte forma da instrução return,sem parâmetros:Retorna;•Esta forma da instrução de
retorno pode ser usada apenas com métodos declarados nulos.Por exemplo, o código a seguir mostra a
declaração de um método void chamado SomeMethod, quetem três locais possíveis onde pode retornar
ao código de chamada. Os primeiros dois lugares estão em ramos chamados seinstruções, que são
abordadas no Capítulo 9. O último lugar é o final do corpo do método.Tipo de devolução nula↓void
SomeMethod (){...if (alguma condição)// E se ...Retorna;// retorna ao código de chamada....if (Outra
condição)// E se ...Retorna;// retorna ao código de chamada....}// Retorno padrão ao código de
chamada.

Página 109

CAPÍTULO 5 ■ MÉTODOS79O código a seguir mostra um exemplo de método void com uma instrução
return. O método escreveenviar uma mensagem apenas se for depois do meio-dia. O processo, ilustrado
na Figura 5-5, é o seguinte:•Primeiro, o método obtém a data e a hora atuais. (Não se preocupe em
entender os detalhes deisso agora.)•Se a hora for inferior a 12 (ou seja, antes do meio-dia), a instrução
de retorno é executada e o controleretorna imediatamente ao método de chamada sem escrever nada
na tela.•Se a hora for 12 ou mais, a instrução de retorno é ignorada e o código executa o
WriteLinedeclaração, que grava uma mensagem informativa na tela.classe MyClass{↓ Tipo de retorno
anuladovoid TimeUpdate (){DateTime dt = DateTime.Now;// Obtenha a data e a hora atuais.if (dt.Hour
<12)// Se a hora for menor que 12,Retorna;// então retorna.↑Retorne ao método de
chamada.Console.WriteLine ("É tarde!"); // Caso contrário, imprime a mensagem.}static void Main ()
{MyClass mc = new MyClass (); // Crie uma instância da classe.mc.TimeUpdate ();// Invoque o
método.}}Figura 5-5. Usando uma instrução de retorno com um tipo de retorno nulo

Página 110

CAPÍTULO 5 ■ MÉTODOS80ParâmetrosAté agora, você viu que os métodos são unidades nomeadas de
código que podem ser chamadas de muitos lugares em umprograma e pode retornar um único valor
para o código de chamada. Retornar um único valor certamente é valioso,mas e se você precisar
retornar vários valores? Além disso, seria útil ser capaz de passar dados para ummétodo quando começa
a execução. Os parâmetros são variáveis especiais que permitem que você faça as duas
coisas.Parâmetros FormaisParâmetros formais são variáveis locais que são declaradas na lista de
parâmetros da declaração do método,em vez de no corpo do método.O seguinte cabeçalho do método
mostra a sintaxe das declarações dos parâmetros. Ele declara dois formaisparâmetros - um do tipo int e
outro do tipo float.public void PrintSum (int x, float y){↑...Declarações de parâmetros formais}•Como os
parâmetros formais são variáveis, eles têm um tipo de dados e um nome, e podem serescrito e lido.•Ao
contrário de outras variáveis locais de um método, os parâmetros são definidos fora do corpo do método
esão inicializados antes do método começar, exceto por um tipo, chamado de parâmetros de saída,
quecubra em breve.•A lista de parâmetros pode ter qualquer número de declarações de parâmetros
formais, e as declaraçõesdeve ser separado por vírgulas.Os parâmetros formais são usados em todo o
corpo do método, na maior parte, assim como outrosvariáveis. Por exemplo, a seguinte declaração do
método PrintSum usa dois parâmetros formais, x ey, e uma variável local, sum, todas do tipo int.public
void PrintSum (int x, int y){soma int = x + y;Console.WriteLine ("Newsflash: {0} + {1} é {2}", x, y, soma);}

Página 111

CAPÍTULO 5 ■ MÉTODOS81Parâmetros reaisQuando seu código chama um método, os valores dos


parâmetros formais devem ser inicializados antes do códigono método começa a execução.•As
expressões ou variáveis utilizadas para inicializar os parâmetros formais são chamados a realparâmetros .
Eles também são chamados de argumentos .•Os parâmetros reais são colocados na lista de parâmetros
da chamada do método.•Cada parâmetro real deve corresponder ao tipo do parâmetro formal
correspondente, ou oo compilador deve ser capaz de converter implicitamente o parâmetro real para
aquele tipo. Vou explicar odetalhes da conversão de um tipo para outro no Capítulo 18.Por exemplo, o
código a seguir mostra a invocação do método PrintSum, que possui doisparâmetros do tipo de dados
int:PrintSum (5, someInt);↑ ↑Variável de expressão do tipo intQuando o método é chamado, o valor de
cada parâmetro real é usado para inicializar oparâmetro formal correspondente. O corpo do método é
então executado. A Figura 5-6 ilustra orelação entre os parâmetros reais e os parâmetros formais.Figura
5-6. Os parâmetros reais inicializam os parâmetros formais correspondentes.Observe que no código de
exemplo anterior, e na Figura 5-6, o número de parâmetros reais deveser igual ao número de parâmetros
formais (com exceção dos parâmetros params, que ireidiscutir mais tarde). Os parâmetros que seguem
esse padrão são chamados de parâmetros posicionais . Veremos algunsoutras opções em breve.

Página 112
CAPÍTULO 5 ■ MÉTODOS82Um exemplo de métodos com parâmetros de entrada posicionaisNo código a
seguir, a classe MyClass declara dois métodos - um que pega dois inteiros e retorna seussoma e outra
que pega dois flutuadores e retorna sua média. Na segunda invocação, observe queo compilador
converteu implicitamente os dois valores int - 5 e someInt - para o tipo float.Parâmetros formais de
classe MyClass{↓public int Sum (int x, int y)// Declare o método.{retornar x + y;// Retorna a
soma.}Parâmetros formais↓public float Avg (float input1, float input2)// Declare o método.{retorno
(entrada1 + entrada2) / 2.0F;// Retorna a média.}}programa de aula{static void Main (){MyClass myT =
new MyClass ();int someInt = 6;Console.WriteLine("Newsflash: Soma: {0} e {1} é {2}",5, someInt,
myT.Sum (5, someInt));// Invoque o método.↑Console.WriteLineParâmetros reais("Newsflash: Média: {0}
e {1} é {2}",5, someInt, myT.Avg (5, someInt));// Invoque o método.}↑}Parâmetros reaisEste código
produz a seguinte saída:Newsflash: Soma: 5 e 6 é 11Newsflash: Média: 5 e 6 é 5,5Baixe a partir de Wow!
e-book <www.wowebook.com>

Página 113

CAPÍTULO 5 ■ MÉTODOS83Parâmetros de valorExistem vários tipos de parâmetros, que transmitem


dados de e para o método de maneiras ligeiramente diferentes.O tipo que vimos até agora é o tipo
padrão e é chamado de parâmetro de valor .Quando você usa parâmetros de valor, os dados são
passados para o método copiando o valor do realparâmetro para o parâmetro formal. Quando um
método é chamado, o sistema faz o seguinte:•Ele aloca espaço na pilha para os parâmetros formais.•Ele
copia os valores dos parâmetros reais para os parâmetros formais.Um parâmetro real para um
parâmetro de valor não precisa ser uma variável. Pode ser qualquer expressãoavaliando para o tipo de
dados correspondente. Por exemplo, o código a seguir mostra duas chamadas de método. Noprimeiro, o
parâmetro real é uma variável do tipo float. No segundo, é uma expressão que avaliaflutuar.float func1
(float val)// Declare o método.{↑Tipo de dados flutuantefloat j = 2,6F;float k = 5.1F;Variável do tipo
float↓float fValue1 = func1 (k);// Chamada de métodofloat fValue2 = func1 ((k + j) / 3);// Chamada de
método...↑Expressão que avalia como um flutuanteAntes que você possa usar uma variável como um
parâmetro real, essa variável deve ter recebido um valor(exceto no caso de parâmetros de saída, que
abordarei em breve). Para tipos de referência, a variável podeser atribuído a uma referência real ou
nulo.■ Nota O Capítulo 3 abordou os tipos de valor, que, como você se lembrará, são tipos que contêm
seus próprios dados. Nãofique confuso porque agora estou falando sobre parâmetros de valor . Eles são
totalmente diferentes. Parâmetros de valor sãoparâmetros onde o valor do parâmetro real é copiado
para o parâmetro formal.

Página 114

CAPÍTULO 5 ■ MÉTODOS84Por exemplo, o código a seguir mostra um método chamado MyMethod, que
usa dois parâmetros - umvariável do tipo MyClass e um int.•O método adiciona 5 ao campo do tipo int
pertencente à classe e ao int.•Você também pode notar que MyMethod usa o modificador static, que
ainda não expliquei. Vocêspode ignorá-lo por enquanto. Explicarei os métodos estáticos no Capítulo
6.classe MyClass{público int Val = 20;// Inicialize o campo para 20.}programa de aulaParâmetros
formais{↓static void MyMethod (MyClass f1, int f2){f1.Val = f1.Val + 5;// Adicione 5 ao campo do
parâmetro f1.f2 = f2 + 5;// Adicione 5 ao segundo parâmetro.}static void Main (){MyClass a1 = new
MyClass ();int a2 = 10;MeuMétodo (a1, a2);// Chame o método.}↑}Parâmetros reais

Página 115

CAPÍTULO 5 ■ MÉTODOS85A Figura 5-7 ilustra o seguinte sobre os valores dos parâmetros reais e
formais em váriosetapas na execução do método:•Antes da chamada do método, as variáveis a1 e a2,
que serão usadas como parâmetros reais, sãojá está na pilha.•No início do método, o sistema alocou
espaço na pilha para o formalparâmetros e copiou os valores dos parâmetros reais.- Uma vez que a1 é
um tipo de referência, a referência é copiada, resultando tanto no real quanto no formalparâmetros
referentes ao mesmo objeto no heap.- Como a2 é um tipo de valor, o valor é copiado, produzindo um
item de dados independente.•No final do método, f2 e o campo do objeto f1 foram incrementados em
5.- Após a execução do método, os parâmetros formais são retirados da pilha.- O valor de a2, o tipo de
valor, não é afetado pela atividade no método.- O valor de a1, o tipo de referência, entretanto, foi
alterado pela atividade no método.Figura 5-7 . Parâmetros de valor

Página 116

CAPÍTULO 5 ■ MÉTODOS86Parâmetros de ReferênciaO segundo tipo de parâmetro é chamado de


parâmetro de referência .•Ao usar um parâmetro de referência, você deve usar o modificador ref na
declaração e noinvocação do método.•O parâmetro real deve ser uma variável e deve ter sido atribuído
antes de ser usado comoo parâmetro real. Se for uma variável de tipo de referência, pode ser atribuída a
uma referência realou o valor null.Por exemplo, o código a seguir ilustra a sintaxe da declaração e
invocação:Inclui o modificador ref.↓void MyMethod (ref int val)// Declaração de método{...}int y = 1;//
Variável para o parâmetro realMyMethod (ref y);// Chamada de método↑Inclui o modificador
ref.MeuMétodo (ref 3 + 5);// Erro!↑Deve usar uma variávelNa seção anterior, você viu que para
parâmetros de valor, o sistema aloca memória na pilhapara os parâmetros formais. Em contraste, para
parâmetros de referência:•O nome do parâmetro formal atua como se fosse um alias para a variável de
parâmetro real ; é issoage como se referisse ao mesmo local de memória.Uma vez que o nome do
parâmetro formal e o nome do parâmetro real estão agindo como se referissem omesmo local de
memória, claramente todas as alterações feitas no parâmetro formal durante a execução do método
sãovisível após a conclusão do método, por meio da variável de parâmetro real.■ Nota Lembre-se de
usar a palavra-chave ref na declaração do método e na invocação.

Página 117

CAPÍTULO 5 ■ MÉTODOS87Por exemplo, o código a seguir mostra o método MyMethod novamente,


mas desta vez os parâmetros sãoparâmetros de referência em vez de parâmetros de valor:classe
MyClass{público int Val = 20;// Inicializa o campo para 20.}programa de aulamodificador refmodificador
ref{↓↓static void MyMethod (ref MyClass f1, ref int f2){f1.Val = f1.Val + 5;// Adicione 5 ao campo do
parâmetro f1.f2 = f2 + 5;// Adicione 5 ao segundo parâmetro.}static void Main (){MyClass a1 = new
MyClass ();int a2 = 10;MeuMétodo (ref a1, ref a2);// Chame o método.}↑↑}modificadores ref

Página 118
CAPÍTULO 5 ■ MÉTODOS88A Figura 5-8 ilustra o seguinte sobre os valores dos parâmetros reais e
formais em váriosetapas na execução do método:•Antes da chamada do método, as variáveis a1 e a2,
que serão usadas como parâmetros reais, sãojá está na pilha.•No início do método, os nomes dos
parâmetros formais foram definidos como se fossemaliases para os parâmetros reais. Você pode pensar
nas variáveis a1 e f1 como se elas se referissem aomesmo local de memória e a2 e f2 como se eles se
referissem ao mesmo local de memória.•No final do método, f2 e o campo do objeto de f1 foram
incrementados em 5.•Após a execução do método, os nomes dos parâmetros formais desaparecem
("fora do escopo"), mas amboso valor de a2, que é o tipo de valor, e o valor do objeto apontado por a1,
que é otipo de referência, foram alterados pela atividade no método.Figura 5-8. Com um parâmetro de
referência, o parâmetro formal se comporta como se fosse um alias para o realparâmetro.

Página 119

CAPÍTULO 5 ■ MÉTODOS89Parâmetros de saídaOs parâmetros de saída são usados para passar dados
de dentro do método de volta para o código de chamada. Seuso comportamento é muito semelhante
aos parâmetros de referência. Como os parâmetros de referência, os parâmetros de saída têm
oseguintes requisitos:•Você deve usar um modificador na declaração do método e na invocação. Com
saídaparâmetros, o modificador está fora, em vez de ref.•Como os parâmetros de referência, o
parâmetro real deve ser uma variável - não pode ser de outro tipode expressão. Isso faz sentido, uma vez
que o método precisa de um local de memória para armazenar o valorestá voltando.Por exemplo, o
código a seguir declara um método chamado MyMethod, que recebe uma única saídaparâmetro.nosso
modificador↓void MyMethod (out int val)// Declaração de método{...}...int y = 1;// Variável para o
parâmetro realMyMethod (out y);// Chamada de método↑nosso modificadorComo os parâmetros de
referência, os parâmetros formais dos parâmetros de saída agem como se fossem apelidos paraos
parâmetros reais. Quaisquer alterações feitas em um parâmetro formal dentro do método são visíveis
através dea variável de parâmetro real após o método concluir a execução.Ao contrário dos parâmetros
de referência, os parâmetros de saída exigem o seguinte:•Dentro do método, um parâmetro de saída
deve ser atribuído antes de poder ser lido. estesignifica que os valores iniciais dos parâmetros são
irrelevantes e que você não tem que atribuirvalores para os parâmetros reais antes da chamada do
método.•Dentro do método, cada caminho possível através do código deve atribuir um valor a cada
saídaparâmetro antes de o método sair.

Página 120

CAPÍTULO 5 ■ MÉTODOS90Uma vez que o código dentro do método deve gravar em um parâmetro de
saída antes de poder ler a partir dele, éimpossível enviar dados para um método usando parâmetros de
saída. Na verdade, se houver algum caminho de execuçãopor meio do método que tenta ler o valor de
um parâmetro de saída antes que o método tenhaatribuído a ele um valor, o compilador produz uma
mensagem de erro.public void Add2 (out int outValue){int var1 = outValue + 2; // Erro! Não é possível ler
de um parâmetro de saída}// antes de ser atribuído pelo método.Por exemplo, o código a seguir mostra
novamente o método MyMethod, mas desta vez usando a saídaparâmetros:classe MyClass{público int
Val = 20;// Inicializa o campo para 20.}programa de aulanosso modificadornosso modificador{↓↓static
void MyMethod (out MyClass f1, out int f2){f1 = nova MinhaClasse ();// Cria um objeto da classe.f1.Val =
25;// Atribuir ao campo de classe.f2 = 15;// Atribuir ao parâmetro int.}static void Main (){MyClass a1 =
null;int a2;MyMethod (out a1, out a2);// Chame o método.}↑↑}nossos modificadores

Página 121

CAPÍTULO 5 ■ MÉTODOS91A Figura 5-9 ilustra o seguinte sobre os valores dos parâmetros reais e
formais em váriosetapas da execução do método.•Antes da chamada do método, as variáveis a1 e a2,
que serão usadas como parâmetros reais, sãojá está na pilha.•No início do método, os nomes dos
parâmetros formais são definidos como aliases para oparâmetros reais. Você pode pensar nas variáveis
a1 e f1 como se elas se referissem à mesma memórialocal, e você pode pensar em a2 e f2 como se eles
se referissem ao mesmo local de memória. oos nomes a1 e a2 estão fora do escopo e não podem ser
acessados de dentro de MyMethod.•Dentro do método, o código cria um objeto do tipo MyClass e o
atribui a f1. Em seguida, atribui umvalor ao campo de f1 e também atribui um valor a f2. As atribuições
para f1 e f2 são obrigatórias ,uma vez que são parâmetros de saída.•Após a execução do método, os
nomes dos parâmetros formais estão fora do escopo, mas os valoresde a1, o tipo de referência, e a2, o
tipo de valor, foram alterados pela atividade emo método.Figura 5-9. Com um parâmetro de saída, o
parâmetro formal se comporta como se fosse um alias para o realparâmetro, mas com o requisito
adicional de que ele deve ser atribuído dentro do método.

Página 122

CAPÍTULO 5 ■ MÉTODOS92Matrizes de parâmetrosNos tipos de parâmetro que cobri até agora, deve
haver exatamente um parâmetro real para cada formalparâmetro. Matrizes de parâmetros são
diferentes porque permitem zero ou mais parâmetros reais para umparâmetro formal particular. Os
pontos importantes sobre matrizes de parâmetros são os seguintes:•Pode haver apenas uma matriz de
parâmetro em uma lista de parâmetros.•Se houver, deve ser o último parâmetro da lista.Para declarar
uma matriz de parâmetro, você deve fazer o seguinte:•Use o modificador params antes do tipo de
dados.•Coloque um conjunto de colchetes vazios após o tipo de dados.O seguinte cabeçalho de método
mostra a sintaxe para a declaração de uma matriz de parâmetro do tipo int.Neste exemplo, o parâmetro
formal inVals pode representar zero ou mais parâmetros internos reais.Matriz de ints↓void ListInts
(params int [] inVals){...↑↑ModificadorNome do parâmetroO conjunto vazio de colchetes após o nome do
tipo especifica que o parâmetro será uma matriz deints. Você não precisa se preocupar com os detalhes
dos arrays aqui. Eles são abordados em detalhes no Capítulo 14.Para nossos propósitos aqui, entretanto,
tudo que você precisa saber é o seguinte:•Uma matriz é um conjunto ordenado de itens de dados do
mesmo tipo.•Uma matriz é acessada usando um índice numérico.•Uma matriz é um tipo de referência
e, portanto, armazena todos os seus itens de dados no heap.Baixe a partir de Wow! e-book
<www.wowebook.com>

Página 123

CAPÍTULO 5 ■ MÉTODOS93Invocação de MétodoVocê pode fornecer os parâmetros reais para uma


matriz de parâmetro de duas maneiras. Os formulários que você pode usar sãoOs seguintes:•Uma lista
separada por vírgulas de elementos do tipo de dados. Todos os elementos devem ser do
tipoespecificado na declaração do método.ListInts (10, 20, 30);// Três ints•Uma matriz unidimensional
de elementos do tipo de dados.int [] intArray = {1, 2, 3};ListInts (intArray);// Uma variável de
matrizObserve nesses exemplos que você não usa o modificador params na chamada . O uso
domodificador em matrizes de parâmetro não se encaixa no padrão dos outros tipos de parâmetro.•Os
outros tipos de parâmetro são consistentes no sentido de que usam um modificador ou não usam
ummodificador.- parâmetros Valor tomar nenhum modificador em qualquer declaração ou a invocação.-
Os parâmetros de referência e de saída requerem o modificador em ambos os lugares.•O resumo do uso
do modificador params é o seguinte:- É exigido na declaração .- Não é permitido na invocação .Forma
expandidaA primeira forma de invocação de método, onde você usa parâmetros reais separados na
invocação, éàs vezes chamado de forma expandida .Por exemplo, a declaração do método ListInts no
código a seguir corresponde a todos os métodosinvocações abaixo dele, embora tenham diferentes
números de parâmetros reais.void ListInts (params int [] inVals) {...} // Declaração do método...ListInts
();// 0 parâmetros reaisListInts (1, 2, 3);// 3 parâmetros reaisListInts (4, 5, 6, 7);// 4 parâmetros
reaisListInts (8, 9, 10, 11, 12);// 5 parâmetros reais

Página 124

CAPÍTULO 5 ■ MÉTODOS94Quando você usa uma invocação com parâmetros reais separados para uma
matriz de parâmetro, o compiladorfaz o seguinte:•Ele pega a lista de parâmetros reais e os usa para criar
e inicializar uma matriz no heap.•Ele armazena a referência ao array no parâmetro formal da pilha.•Se
não houver parâmetros reais na posição correspondente à matriz de parâmetro formal, oo compilador
cria uma matriz com zero elementos e usa isso.Por exemplo, o código a seguir declara um método
chamado ListInts, que usa uma matriz de parâmetro.Main declara três ints e os passa para o array.classe
MyClassMatriz de parâmetros{↓public void ListInts (params int [] inVals){if ((inVals! = null) &&
(inVals.Length! = 0))for (int i = 0; i <inVals.Length; i ++) // Processa a matriz.{inVals [i] = inVals [i] *
10;Console.WriteLine ("{0}", inVals [i]); // Exibe o novo valor.}}}programa de aula{static void Main (){int
primeiro = 5, segundo = 6, terceiro = 7;// Declare três ints.MyClass mc = new MyClass ();mc.ListInts
(primeiro, segundo, terceiro);// Chame o método.↑Parâmetros reaisConsole.WriteLine ("{0}, {1}, {2}",
primeiro, segundo, terceiro);}}Este código produz a seguinte saída:5060705, 6, 7

Página 125

CAPÍTULO 5 ■ MÉTODOS95A Figura 5-10 ilustra o seguinte sobre os valores dos parâmetros reais e
formais em váriosetapas na execução do método:•Antes da chamada do método, os três parâmetros
reais já estão na pilha.•No início do método, os três parâmetros reais foram usados para inicializar uma
matrizno heap, e a referência ao array foi atribuída ao parâmetro formal inVals.•Dentro do método, o
código primeiro verifica se a referência da matriz não é nula e, em seguida,processa a matriz
multiplicando cada elemento na matriz por 10 e armazenando-o de volta.•Após a execução do método,
o parâmetro formal, inVals, está fora do escopo.Figura 5-10. Matriz de parâmetrosUma coisa importante
a lembrar sobre matrizes de parâmetro é que quando uma matriz é criada noheap, os valores dos
parâmetros reais são copiados para o array. Desta forma, eles são como valorparâmetros.•Se o
parâmetro da matriz for um tipo de valor, os valores são copiados e os parâmetros reais não podem
serafetados dentro do método.•Se o parâmetro da matriz for um tipo de referência, as referências são
copiadas e os objetos referenciados poros parâmetros reais podem ser afetados dentro do método.

Página 126

CAPÍTULO 5 ■ MÉTODOS96Matrizes como parâmetros reaisVocê também pode criar e preencher uma
matriz antes da chamada do método e passar a variável de matriz única comoo parâmetro real. Nesse
caso, o compilador usa seu array, em vez de criar um.Por exemplo, o código a seguir usa o método
ListInts, declarado no exemplo anterior. Nissocódigo, Main cria uma matriz e usa a variável de matriz
como o parâmetro real, em vez de usarinteiros separados.static void Main (){int [] myArr = new int [] {5,
6, 7}; // Cria e inicializa o array.MyClass mc = new MyClass ();mc.ListInts (myArr);// Chame o método
para imprimir os valores.foreach (int x em myArr)Console.WriteLine ("{0}", x); // Imprime cada
elemento.}Este código produz a seguinte saída:506070506070Resumo dos Tipos de ParâmetrosComo
existem quatro tipos de parâmetros, às vezes é difícil lembrar suas várias características.A Tabela 5-2 os
resume, tornando mais fácil compará-los e contrastá-los.Tabela 5-2. Resumo do uso sintático do tipo de
parâmetroParâmetroTipoModificador usado emDeclaração?Usado emInvocação?
ImplementaçãoValorNenhumO sistema copia o valor do realparâmetro para o parâmetro
formal.ReferênciarefsimsimO parâmetro formal aliases o realparâmetro.ResultadoForasimsimO
parâmetro formal aliases o realparâmetro.ArrayparamssimNãoIsso permite passar um número variável
deparâmetros reais para um método.

Página 127

CAPÍTULO 5 ■ MÉTODOS97Sobrecarga de métodoUma classe pode ter mais de um método com o


mesmo nome. Isso é chamado de sobrecarga de método . Cadamétodo com o mesmo nome deve ter
uma assinatura diferente dos outros.•A assinatura de um método consiste nas seguintes informações do
cabeçalho do método dodeclaração do método:- O nome do método- O número de parâmetros- Os tipos
de dados e a ordem dos parâmetros- Os modificadores de parâmetro•O tipo de retorno não faz parte da
assinatura - embora seja um erro comum acreditar que sim.•Observe que os nomes dos parâmetros
formais não fazem parte da assinatura.Não faz parte da assinatura↓Long AddValues (int a, out int b)
{...}↑AssinaturaPor exemplo, os quatro métodos a seguir são sobrecargas do nome do método
AddValues:classe A{Long AddValues (int a, int b){return a + b;}Long AddValues (int c, int d, int e) {return c
+ d + e; }Long AddValues (float f, float g){retorno (longo) (f + g); }Long AddValues (long h, long m){return
h + m;}}O código a seguir mostra uma tentativa ilegal de sobrecarregar o nome do método AddValues.
Os doisos métodos diferem apenas nos tipos de retorno e nos nomes dos parâmetros formais. Mas eles
ainda têm omesma assinatura, pois possuem o mesmo nome de método; e o número, tipos e ordem de
seusos parâmetros são os mesmos. O compilador produziria uma mensagem de erro para este
código.classe BAssinatura{↓AddValues longos (a longo, b longo) {return a + b; }int AddValues (c longo, d
longo) {return c + d; } // Erro, mesma assinatura}↑Assinatura

Página 128

CAPÍTULO 5 ■ MÉTODOS98Parâmetros NomeadosAté agora, em nossa discussão sobre parâmetros,


usamos parâmetros posicionais, que, como você se lembra,significa que a posição de cada parâmetro
real corresponde à posição do correspondente formalparâmetro.Começando com C # 4.0, você pode
listar os parâmetros reais em sua invocação de método em qualquer ordem, comocontanto que você
especifique explicitamente os nomes dos parâmetros. Os detalhes são os seguintes:•Nada muda na
declaração do método. Os parâmetros formais já têm nomes.•Na invocação do método, no entanto,
você usa o nome do parâmetro formal, seguido por dois pontos, emfrente do valor do parâmetro real ou
expressão, conforme mostrado na seguinte chamada de método.Aqui, a, b e c são os nomes dos três
parâmetros formais do método Calc:Valores reais dos parâmetros↓ ↓↓c.Calc (c: 2, a: 4, b: 3);↑ ↑
↑Parâmetros nomeadosA Figura 5-11 ilustra a estrutura do uso de parâmetros nomeados.Figura 5-11. Ao
usar parâmetros nomeados, inclua o nome do parâmetro na chamada do método. Nãomudanças são
necessárias na declaração do método.

Página 129

CAPÍTULO 5 ■ MÉTODOS99Você pode usar parâmetros posicionais e nomeados em uma invocação, mas
todos os parâmetros posicionaisos parâmetros devem ser listados primeiro. Por exemplo, o código a
seguir mostra a declaração de um métodochamado Calc, junto com cinco chamadas diferentes para o
método usando diferentes combinações de posicional eparâmetros nomeados:classe MyClass{public int
Calc (int a, int b, int c){return (a + b) * c; }static void Main (){MyClass mc = new MyClass ();int r0 = mc.Calc
(4, 3, 2);// Parâmetros posicionaisint r1 = mc.Calc (4, b: 3, c: 2);// Parâmetros posicionais e nomeadosint
r2 = mc.Calc (4, c: 2, b: 3);// Mudar ordemint r3 = mc.Calc (c: 2, b: 3, a: 4);// Todos os parâmetros
nomeadosint r4 = mc.Calc (c: 2, b: 1 + 2, a: 3 + 1); // Expressões de parâmetros
nomeadosConsole.WriteLine ("{0}, {1}, {2}, {3}, {4}", r0, r1, r2, r3, r4);}}Este código produz a seguinte
saída:14, 14, 14, 14, 14Os parâmetros nomeados são úteis como meio de autodocumentar um
programa, na medida em que podem ser exibidos, ema posição da chamada do método, quais valores
estão sendo atribuídos a quais parâmetros formais. Paraexemplo, nas duas chamadas a seguir para o
método GetCylinderVolume, a segunda chamada é um pouco maisinformativo e menos sujeito a
erros.classe MyClass{GetCylinderVolume duplo (raio duplo, altura dupla){retornar 3,1416 * raio * raio *
altura;}static void Main (string [] args){MyClass mc = new MyClass ();volume duplo;↓ ↓volume =
mc.GetCylinderVolume (3,0, 4,0);...volume = mc.GetCylinderVolume (raio: 3,0, altura: 4,0);...↑↑}Mais
informativo}

Página 130

CAPÍTULO 5 ■ MÉTODOS100Parâmetros OpcionaisOutro recurso introduzido no C # 4.0 é chamado de


parâmetros opcionais . Um parâmetro opcional é umparâmetro que você pode incluir ou omitir ao
chamar o método.Para especificar que um parâmetro é opcional, você precisa incluir um valor padrão
para esse parâmetro nodeclaração do método. A sintaxe para especificar o valor padrão é a mesma que
para inicializar um localvariável, conforme mostrado na declaração do método do código a seguir. Neste
exemplo:•O parâmetro formal b é atribuído ao valor padrão 3.•Portanto, se o método for chamado com
apenas um único parâmetro, o método usará o valor 3 comoo valor inicial do segundo parâmetro.classe
MyClassParâmetro Opcional{↓public int Calc (int a, int b = 3){↑retornar a + b;Atribuição de valor
padrão}static void Main (){MyClass mc = new MyClass ();int r0 = mc.Calc (5, 6);// Use valores
explícitos.int r1 = mc.Calc (5);// Use o padrão para b.Console.WriteLine ("{0}, {1}", r0, r1);}}Este código
produz a seguinte saída:11, 8

Página 131

CAPÍTULO 5 ■ MÉTODOS101Existem várias coisas importantes a saber sobre a declaração de


parâmetros opcionais:•Nem todos os tipos de parâmetros podem ser usados como parâmetros
opcionais.- Você pode usar tipos de valor como parâmetros opcionais, desde que o valor padrão seja
determinávelem tempo de compilação.- Você só pode usar um tipo de referência como um parâmetro
opcional se o valor padrão for nulo.Figura 5-12. Os parâmetros opcionais podem ser apenas tipos de
parâmetro de valor.•Todos os parâmetros necessários devem ser declarados antes de qualquer
parâmetro opcional ser declarado. Se houverum parâmetro params, ele deve ser declarado após todos
os parâmetros opcionais. A Figura 5-13 ilustraa ordem sintática necessária.Figura 5-13. Na declaração do
método, os parâmetros opcionais devem ser declarados após todos osparâmetros e antes do parâmetro
params, se houver.

Página 132

CAPÍTULO 5 ■ MÉTODOS102Como você viu no exemplo anterior, você usa o valor padrão de um
parâmetro opcional deixandoo parâmetro real correspondente da chamada do método. Você não pode,
no entanto, omitir qualquercombinação de parâmetros opcionais porque em muitas situações seria
ambíguo quanto a qualparâmetros opcionais a serem usados. As regras são as seguintes:•Você deve
omitir os parâmetros a partir do final da lista de parâmetros opcionais e trabalharem direção ao
início.•Ou seja, você pode omitir o último parâmetro opcional ou os últimos n parâmetros opcionais,
mas não podeescolha e opte por omitir qualquer parâmetro opcional arbitrário; eles devem ser
eliminados no final.classe MyClass{public int Calc (int a = 2, int b = 3, int c = 4){return (a + b) * c;}static
void Main (){MyClass mc = new MyClass ();int r0 = mc.Calc (5, 6, 7); // Use todos os valores explícitos.int
r1 = mc.Calc (5, 6); // Use o padrão para c.int r2 = mc.Calc (5);// Use o padrão para b e c.int r3 = mc.Calc
();// Use todos os padrões.Console.WriteLine ("{0}, {1}, {2}, {3}", r0, r1, r2, r3);}}Este código produz a
seguinte saída:77, 44, 32, 20Baixe a partir de Wow! e-book <www.wowebook.com>

Página 133

CAPÍTULO 5 ■ MÉTODOS103Para omitir parâmetros opcionais de posições arbitrárias dentro da lista de


parâmetros opcionais, em vezdo que no final da lista, você deve usar os nomes dos parâmetros
opcionais para desambiguar oatribuições. Portanto, você está usando os parâmetros nomeados e os
recursos de parâmetros opcionais, comoilustrado no seguinte código:classe MyClass{GetCylinderVolume
duplo (raio duplo = 3,0, altura dupla = 4,0){retornar 3,1416 * raio * raio * altura;}static void Main ()
{MyClass mc = new MyClass ();volume duplo;volume = mc.GetCylinderVolume (3,0, 4,0); //
PosicionalConsole.WriteLine ("Volume =" + volume);volume = mc.GetCylinderVolume (raio: 2,0); // Usar
altura padrãoConsole.WriteLine ("Volume =" + volume);volume = mc.GetCylinderVolume (altura: 2,0); //
Usar raio padrãoConsole.WriteLine ("Volume =" + volume);volume = mc.GetCylinderVolume ();// Use os
dois padrõesConsole.WriteLine ("Volume =" + volume);}}Este código produz a seguinte saída:Volume =
113,0976Volume = 50,2656Volume = 56.5488Volume = 113,0976
Página 134

CAPÍTULO 5 ■ MÉTODOS104Stack FramesAté agora, você sabe que as variáveis e parâmetros locais são
mantidos na pilha. Vamos dar uma olhada nessa organizaçãoum pouco mais longe.Quando um método é
chamado, a memória é alocada no topo da pilha para conter uma série de itens de dadosassociado ao
método. Esse pedaço de memória é chamado de estrutura de pilha do método.•A estrutura de pilha
contém memória para armazenar o seguinte:- O endereço de retorno - isto é, onde retomar a execução
quando o método sair- Esses parâmetros que alocam memória, ou seja, os parâmetros de valor do
método, eo array de parâmetros se houver um- Vários outros itens de dados administrativos relevantes
para a chamada do método•Quando um método é chamado, todo o seu quadro de pilha é colocado na
pilha.•Quando o método é encerrado, todo o quadro de pilha é retirado da pilha. Estalar um frame de
pilha éàs vezes chamado de desenrolar a pilha.Por exemplo, o código a seguir declara três métodos.
Principal chama MethodA, que chama MethodB,criando três frames de pilha. Conforme os métodos são
encerrados, a pilha se desenrola.programa de aula{static void MethodA (int par1, int par2)
{Console.WriteLine ("Digite o MétodoA: {0}, {1}", par1, par2);Método B (11, 18);// Chame o
MétodoB.Console.WriteLine ("Método de saída A");}static void MethodB (int par1, int par2)
{Console.WriteLine ("Insira o MétodoB: {0}, {1}", par1, par2);Console.WriteLine ("Método de saída
B");}static void Main (){Console.WriteLine ("Enter Main");Método A (15, 30);// Chame o
MethodA.Console.WriteLine ("Sair principal");}}

Página 135

CAPÍTULO 5 ■ MÉTODOS105Este código produz a seguinte saída:Entrar PrincipalInsira o Método A: 15,


30Insira o Método B: 11, 18Método de saída BMétodo de saída ASair principalA Figura 5-14 mostra
como os frames de cada método são colocados na pilha quando o método échamado e como a pilha é
desfeita conforme os métodos são concluídos.Figura 5-14 . Empilhar frames em um programa simples

Página 136

CAPÍTULO 5 ■ MÉTODOS106RecursãoAlém de chamar outros métodos, um método também pode


chamar a si mesmo . Isso é chamado de recursão.A recursão pode produzir algum código muito
elegante, como o seguinte método para calcular ofatorial de um número. Observe que dentro do
método, o método chama a si mesmo com um parâmetro real deum a menos que seu parâmetro de
entrada.int Fatorial (int inValue){if (inValue <= 1)return inValue;outroretorno inValue * Fatorial (inValue -
1); // Chame o fatorial novamente.}↑Se autodenominaA mecânica de um método que chama a si mesmo
é exatamente a mesma como se tivesse chamado outro, diferentemétodo. Um novo quadro de pilha é
colocado na pilha para cada chamada ao método.Por exemplo, no código a seguir, o método Count
chama a si mesmo com um a menos que seu parâmetro de entradae então imprime seu parâmetro de
entrada. Conforme a recursão fica mais profunda, a pilha fica maior.programa de aula{public void Count
(int inVal){if (inVal == 0)Retorna;Contagem (inVal - 1);// Invoque este método novamente.↑Se
autodenominaConsole.WriteLine ("{0}", inVal);}static void Main (){Programa pr = novo programa
();pr.Count (3);}}Este código produz a seguinte saída:123
Página 137

CAPÍTULO 5 ■ MÉTODOS107A Figura 5-15 ilustra o código. Observe que com um valor de entrada de 3,
existem quatro diferentes,frames de pilha independentes para o método Count. Cada um tem seu
próprio valor para o parâmetro de entrada inVal.Figura 5-15. Exemplo de recursão

Página 138

Página 139

CAPÍTULO 6■ ■ ■109Mais sobre aulas■ Membros da classe■ Membros da classe de instância■


Campos estáticos■ Membros de função estática■ Outros tipos de membros de classe estática■
constantes■ Propriedades■ Construtores de instância■ Construtores estáticos■ Acessibilidade de
construtores■ Destruidores■ Comparação de construtores e destruidores■ O modificador somente
leitura■ Esta palavra-chave■ Indexadores■ Modificadores de acesso em acessores■ Classes parciais

Página 140

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS110Membros da classeOs dois capítulos anteriores cobriram dois
dos nove tipos de membros da classe: campos e métodos. Nissocapítulo, apresentarei mais tipos de
membros da classe e explicarei seus recursos.A Tabela 6-1 mostra uma lista dos tipos de membros da
classe. Aqueles que já foram introduzidos sãomarcado com diamantes. Aqueles que são abordados neste
capítulo estão marcados com uma marca de verificação. Aqueles que vãoa serem abordadas
posteriormente no texto são marcadas com caixas de seleção vazias.Tabela 6-1. Tipos de membros da
classeMembros de dados(Armazenamento de dados)Membros de função(Código de execução)♦
Campos✓ constantes♦ Métodos✓ Propriedades✓ Construtores✓ Destruidores✓ Operadores✓
Indexadores❑ EventosOrdem de modificadores de membrosAnteriormente, você viu que as declarações
de campos e métodos podem incluir modificadores como public eprivado. Neste capítulo, discutirei
vários modificadores adicionais. Uma vez que muitos desses modificadores podemser usados juntos, a
questão que surge é: em que ordem eles devem estar?As declarações de declaração de membro de
classe consistem no seguinte: a declaração principal, um conjunto opcionalde modificadores e um
conjunto opcional de atributos . A sintaxe usada para descrever essa estrutura é a seguinte.Os colchetes
indicam que o conjunto fechado de componentes é opcional.[ atributos ] [ modificadores ]
CoreDeclarationOs componentes opcionais são os seguintes:•Modificadores- Se houver algum
modificador, ele deve ser colocado antes da declaração do núcleo.- Se houver vários modificadores, eles
podem estar em um pedido.•Atributos- Se houver atributos, eles devem ser colocados antes dos
modificadores e da declaração do núcleo.- Se houver vários atributos, eles podem estar em qualquer
ordem.Até agora, expliquei apenas dois modificadores: público e privado, e abordarei os atributos no
Capítulo 24.

Página 141

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS111Por exemplo, público e estático são modificadores que podem
ser usados juntos para modificar certosdeclarações. Visto que ambos são modificadores, eles podem ser
colocados em qualquer ordem. As duas linhas a seguir sãosemanticamente equivalente:public static int
MaxVal;static public int MaxVal;A Figura 6-1 mostra a ordem dos componentes conforme aplicados aos
tipos de membros mostrados até agora: campose métodos. Observe que o tipo do campo e o tipo de
retorno do método não são modificadores—eles fazem parte da declaração central.Figura 6-1. A ordem
dos atributos, modificadores e declarações principais

Página 142

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS112Membros da classe de instânciaOs membros da classe podem


ser associados a uma instância da classe ou à classe como um todo. Por padrão,membros são associados
a uma instância. Você pode pensar em cada instância de uma classe como tendo seu própriocópia de
cada aluno. Esses membros são chamados de membros da instância .Mudanças no valor de um campo
de instância não afetam os valores dos membros em qualquer outroinstância. Até agora, os campos e
métodos que você viu foram todos campos e métodos de instância.Por exemplo, o código a seguir
declara uma classe D com um único campo inteiro Mem1. Main cria doisinstâncias da classe. Cada
instância possui sua própria cópia do campo Mem1. Alterar o valor de uma instânciaa cópia do campo
não afeta o valor da cópia da outra instância. A Figura 6-2 mostra as duas instânciasda classe D.classe
D{public int Mem1;}programa de aula{static void Main (){D d1 = novo D ();D d2 = novo D ();d1.Mem1 =
10; d2.Mem1 = 28;Console.WriteLine ("d1 = {0}, d2 = {1}", d1.Mem1, d2.Mem1);}}Este código produz a
seguinte saída:d1 = 10, d2 = 28Figura 6-2. Cada instância da classe D tem sua própria cópia do campo
Mem1.Baixe a partir de Wow! e-book <www.wowebook.com>

Página 143

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS113Campos estáticosAlém dos campos de instância, as classes


podem ter os chamados campos estáticos .•Um campo estático é compartilhado por todas as instâncias
da classe , e todas as instâncias acessam o mesmolocalização da memória. Portanto, se o valor da
localização da memória for alterado por uma instância, oa mudança é visível para todas as
instâncias.•Use o modificador estático para declarar um campo estático, da seguinte maneira:classe
D{int Mem1;// Campo de instânciastatic int Mem2;// campo estático↑} Palavra-chavePor exemplo, o
código à esquerda na Figura 6-3 declara a classe D com campo estático Mem2 e instânciacampo Mem1.
Main define duas instâncias da classe D. A figura mostra que o campo estático Mem2 é armazenado
separadamentedo armazenamento de qualquer uma das instâncias. Os campos cinza dentro das
instâncias representam o fato de que, dedentro de um método de instância, a sintaxe para acessar ou
atualizar o campo estático é a mesma que para qualquer outrocampo membro.•Como Mem2 é estático,
ambas as instâncias da classe D compartilham um único campo Mem2. Se Mem2 for alterado, issoa
mudança é vista de ambos.•O membro Mem1 não é declarado estático, portanto, cada instância tem sua
própria cópia distinta.Figura 6-3. Membros de dados estáticos e de instância

Página 144

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS114Acessando membros estáticos de fora da classeNo capítulo


anterior, você viu que a notação de sintaxe de ponto é usada para acessar membros de instância defora
da classe. A notação de sintaxe de ponto consiste em listar o nome da instância, seguido por um ponto,
seguidopelo nome do membro.Membros estáticos, como membros de instância, também são acessados
de fora da classe usando a sintaxe de pontonotação. Mas como não há instância, você deve usar o nome
da classe , conforme mostrado aqui:Nome da classe↓D. Mem2 = 5;// Acessando o membro da classe
estática↑Nome do membroExemplo de um campo estáticoO código a seguir expande a classe D anterior
adicionando dois métodos:•Um método define os valores dos dois membros de dados.•O outro método
exibe os valores dos dois membros de dados.classe D {intMem1;static int Mem2;public void SetVars (int
v1, int v2) // Defina os valores{Mem1 = v1; Mem2 = v2; }↑ Acesse como se fosse um campo de
instânciapublic void Display (string str){Console.WriteLine ("{0}: Mem1 = {1}, Mem2 = {2}", str, Mem1,
Mem2); }}↑Acesse como se fosse um campo de instânciaclass Program {static void Main (){D d1 = novo D
(), d2 = novo D (); // Crie duas instâncias.d1.SetVars (2, 4);// Defina os valores de d1.d1.Display
("d1");d2.SetVars (15, 17);// Defina os valores de d2.d2.Display ("d2");d1.Display ("d1"); // Exiba d1
novamente e observe que o}// o valor do membro estático Mem2 mudou!}

Página 145

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS115Este código produz a seguinte saída:d1: Mem1 = 2, Mem2 =
4d2: Mem1 = 15, Mem2 = 17d1: Mem1 = 2, Mem2 = 17Vida útil dos membros estáticosOs tempos de
vida dos membros estáticos são diferentes daqueles dos membros da instância.•Como você viu
anteriormente, os membros da instância passam a existir quando a instância é criada edeixa de existir
quando a instância é destruída.•Membros estáticos, entretanto, existem e são acessíveis mesmo se não
houver instâncias da classe.A Figura 6-4 ilustra uma classe D, com um campo estático, Mem2. Mesmo
que Main não defina nenhuminstâncias da classe, ele atribui o valor 5 ao campo estático e o imprime
sem problemas.Figura 6-4. Campos estáticos sem instâncias de classe ainda podem ser atribuídos e lidos,
porque o campo éassociado à classe, e não a uma instância.O código na Figura 6-4 produz a seguinte
saída:Mem2 = 5■ Nota Os membros estáticos existem mesmo se não houver instâncias da classe. Se um
campo estático tiver um inicializador, ocampo é inicializado antes do uso de qualquer um dos campos
estáticos da classe, mas não necessariamente no início deexecução do programa.

Página 146

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS116Membros de função estáticaAlém dos campos estáticos,


também existem membros de função estáticos.•Membros de função estática, como campos estáticos,
são independentes de qualquer instância de classe. Mesmo se houvernenhuma instância de uma classe,
você ainda pode chamar um método estático.•Membros de função estática não podem acessar
membros de instância. Eles podem, no entanto, acessar outrosmembros estáticos.Por exemplo, a classe
a seguir contém um campo estático e um método estático. Observe que o corpo deo método estático
acessa o campo estático.classe X{static public int A;// campo estáticostatic public void PrintValA ()//
Método estático{Console.WriteLine ("Valor de A: {0}", A);}↑}Acessando o campo estáticoO código a
seguir usa a classe X, definida no código anterior:programa de aula{static void Main (){XA = 10;// Use
notação de sintaxe de pontoX.PrintValA ();// Use notação de sintaxe de ponto} ↑} Nome da classeEste
código produz a seguinte saída:Valor de A: 10
Página 147

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS117A Figura 6-5 ilustra o código anterior.Figura 6-5. Os métodos
estáticos de uma classe podem ser chamados mesmo se não houver instâncias da classe.Outros tipos de
membros de classe estáticaOs tipos de membros da classe que podem ser declarados estáticos são
mostrados na Tabela 6-2. O outroos tipos de membros não podem ser declarados estáticos.Tabela 6-2.
Tipos de membros de classe que podem ser declarados estáticosMembros de dados (armazenar
dados)Membros da função (código de execução)✓ CamposConstantes✓ Métodos✓ Propriedades✓
Construtores✓ OperadoresIndexadores✓ Eventos

Página 148

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS118Constantes de MembrosAs constantes de membro são como as


constantes locais abordadas no capítulo anterior, exceto que elas sãodeclarado na declaração da classe,
conforme mostrado no exemplo a seguir:classe MyClass{const int IntVal = 100;// Define uma constante
do tipo int↑↑// com um valor de 100.}TipoInicializadorconst double PI = 3,1416;// Erro: não pode ser
declarado fora de um tipo// declaraçãoComo constantes locais, o valor usado para inicializar uma
constante de membro deve ser computável na compilaçãotempo e geralmente é um dos tipos simples
predefinidos ou uma expressão composta por eles.classe MyClass{const int IntVal1 = 100;const int
IntVal2 = 2 * IntVal1; // Tudo bem, pois o valor de IntVal1}// foi definido na linha anterior.Como as
constantes locais, você não pode atribuir a uma constante de membro após sua declaração.classe
MyClass{const int IntVal;// Erro: a inicialização é necessária.IntVal = 100;// Erro: atribuição não
permitida.}■ Nota Ao contrário de C e C ++, em C # não há constantes globais. Cada constante deve ser
declarada dentro de um tipo.

Página 149

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS119Constantes são como estáticaConstantes de membro, no


entanto, são mais interessantes do que constantes locais, pois agem como estáticasvalores. Eles são
"visíveis" para todas as instâncias da classe e estão disponíveis mesmo se não houverinstâncias da
classe.Por exemplo, o código a seguir declara a classe X com o campo constante PI. Principal não cria
nenhuminstâncias de X, e ainda pode usar o campo PI e imprimir seu valor.classe X{public const double
PI = 3,1416;}programa de aula{static void Main (){Console.WriteLine ("pi = {0}", X.PI); // Use o campo
estático PI}}Este código produz a seguinte saída:pi = 3,1416

Página 150

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS120Ao contrário da estática real, no entanto, as constantes não
têm seus próprios locais de armazenamento e sãosubstituído pelo compilador em tempo de compilação
de maneira semelhante aos valores #define em C e C ++. esteé mostrado na Figura 6-6, que ilustra o
código anterior. Portanto, embora um membro constante ajacomo uma estática, você não pode declarar
uma constante como estática.const estático duplo PI = 3,14;Erro: não é possível declarar uma constante
como estáticaFigura 6-6. Os campos constantes agem como campos estáticos, mas não têm um local de
armazenamento na memória.

Página 151

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS121PropriedadesUma propriedade é um membro que representa


um item de dados em uma classe ou instância de classe. Usando uma propriedadeparece muito com
escrever ou ler de um campo. A sintaxe é a mesma.Por exemplo, o código a seguir mostra o uso de uma
classe chamada MyClass que possui um campo públicoe uma propriedade pública. Pelo uso, você não
pode distingui-los.MyClass mc = new MyClass ();mc.MyField = 5;// Atribuindo a um
campomc.MyProperty = 10;// Atribuindo a uma propriedadeWriteLine ("{0} {1}", mc.MyField,
mc.MyProperty); // Lê campo e propriedadeUma propriedade, como um campo, tem as seguintes
características:•É um membro da classe nomeado.•Tem um tipo.•Ele pode ser atribuído e lido.Ao
contrário de um campo, no entanto, uma propriedade é um membro de função.•Não necessariamente
aloca memória para armazenamento de dados.•Ele executa o código.Uma propriedade é um conjunto
nomeado de dois métodos correspondentes chamados acessadores .•O acessador set é usado para
atribuir um valor à propriedade.•O acessador get é usado para recuperar um valor da propriedade.A
Figura 6-7 mostra a representação de uma propriedade. O código à esquerda mostra a sintaxe de
declaraçãouma propriedade chamada MyValue, do tipo int. A imagem à direita mostra como as
propriedades serão representadasvisualmente neste texto. Observe que os acessores são mostrados
saindo da parte de trás, porque, como você logoveja, eles não podem ser chamados diretamente.Figura
6-7. Um exemplo de propriedade do tipo int, denominado MyValue

Página 152

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS122Declarações de propriedade e acessoresOs acessadores set e


get têm sintaxe e semântica predefinidas. Você pode pensar no acessor set como ummétodo com um
único parâmetro que “define” o valor da propriedade. O acessador get não temparâmetros e retorna o
valor da propriedade.•O acessador definido sempre tem o seguinte:- Um único parâmetro de valor
implícito denominado value, do mesmo tipo da propriedade- Um tipo de retorno de vazio•O acessador
get sempre tem o seguinte:- Sem parâmetros- Um tipo de retorno do mesmo tipo que a propriedadeA
Figura 6-8 mostra a estrutura de uma declaração de propriedade. Observe na figura que nenhum dos
acessadoresdeclaração tem parâmetro explícito ou declarações de tipo de retorno. Eles não precisam
deles, porque eles sãoimplícito no tipo de propriedade.Figura 6-8. A sintaxe e a estrutura de uma
declaração de propriedadeO valor do parâmetro implícito no acessador definido é um parâmetro de
valor normal. Como outro valorparâmetros, você pode usá-lo para enviar dados para um corpo de
método - ou, neste caso, o bloco de acessador. Uma vezdentro do bloco, você pode usar o valor como
uma variável normal, incluindo a atribuição de valores a ele.Outros pontos importantes sobre acessores
são os seguintes:•Todos os caminhos através da implementação de um acessador get devem incluir uma
instrução de retorno queretorna um valor do tipo de propriedade.•Os acessadores set e get podem ser
declarados em qualquer ordem, e nenhum método diferente dos doisacessores são permitidos em uma
propriedade.Baixe a partir de Wow! e-book <www.wowebook.com>

Página 153
CAPÍTULO 6 ■ MAIS SOBRE AS AULAS123Um exemplo de propriedadeO código a seguir mostra um
exemplo da declaração de uma classe chamada C1 que contém uma propriedadechamado
MyValue.•Observe que a propriedade em si não tem nenhum armazenamento. Em vez disso, os
acessadores determinam o quedeve ser feito com os dados enviados e quais dados devem ser enviados.
Nesse caso, a propriedade usaum campo chamado TheRealValue para armazenamento.•O acessador set
pega seu parâmetro de entrada, valor, e atribui esse valor ao campo TheRealValue.•O acessador get
retorna apenas o valor do campo TheRealValue.A Figura 6-9 ilustra o código.classe C1{private int
TheRealValue;// Campo: memória alocadapublic int MyValue// Propriedade: sem memória
alocada{conjunto{TheRealValue = value;}obter{return TheRealValue;}}}Figura 6-9. Acessadores de
propriedade costumam usar campos para armazenamento

Página 154

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS124Usando uma propriedadeComo você viu anteriormente, você
escreve e lê de uma propriedade da mesma forma que acessa um campo. oacessadores são chamados
implicitamente.•Para escrever em uma propriedade, use o nome da propriedade no lado esquerdo de
uma instrução de atribuição.•Para ler de uma propriedade, use o nome da propriedade em uma
expressão.Por exemplo, o código a seguir contém um esboço da declaração de uma propriedade
chamada MyValue.Você escreve e lê na propriedade usando apenas o nome da propriedade, como se
fosse um nome de campo.int MyValue// Declaração de propriedade{definir {...}obter{ ... }}...Nome da
propriedade↓MeuValor = 5;// Atribuição: o método definido é implicitamente chamadoz = MeuValor;//
Expressão: o método get é implicitamente chamado↑Nome da propriedadeO acessador apropriado é
chamado implicitamente dependendo se você está escrevendo para ouleitura da propriedade. Você não
pode chamar explicitamente os acessadores. Tentar fazer isso produz umerro de compilação.y =
MyValue.get (); // Erro! Não é possível chamar explicitamente get acessador.MyValue.set (5);// Erro! Não
é possível chamar explicitamente o acessador definido.

Página 155

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS125Propriedades e campos associadosUma propriedade é


freqüentemente associada a um campo, conforme mostrado nas duas seções anteriores. Uma prática
comum épara encapsular um campo em uma classe, declarando-o privado e declarando uma
propriedade pública para forneceracesso ao campo de fora da classe. O campo associado a uma
propriedade é chamado de campo de apoioou loja de apoio .Por exemplo, o código a seguir usa a
propriedade pública MyValue para fornecer acesso controlado a particularescampo TheRealValue:classe
C1{privado int TheRealValue = 10; // Campo de apoio: memória alocadapublic int MyValue//
Propriedade: sem memória alocada{definir {TheRealValue = value; } // Define o valor do campo
TheRealValueget {return TheRealValue; } // Obtém o valor do campo}}programa de aula{static void Main
(){Leia a propriedade como se fosse um campo.C1 c = novo C1 ();↓Console.WriteLine ("MyValue: {0}",
c.MyValue);c.MyValue = 20;← Use atribuição para definir o valor de uma propriedade.Console.WriteLine
("MyValue: {0}", c.MyValue);}}

Página 156
CAPÍTULO 6 ■ MAIS SOBRE AS AULAS126Existem várias convenções para nomear propriedades e seus
campos de apoio. Uma convenção é parause a mesma string para ambos os nomes, mas use maiúsculas
e minúsculas (em que a primeira letra é minúscula) para ocampo e revestimento Pascal para a
propriedade. Embora isso viole a regra geral de que é uma má práticatêm identificadores diferentes que
diferem apenas na caixa, tem a vantagem de vincular os dois identificadoresjuntos de uma forma
significativa.Outra convenção é usar casing Pascal para a propriedade e, para o campo, usar o
cameloversão case do mesmo identificador, com um sublinhado na frente.O código a seguir mostra as
duas convenções:private int firstField;// Invólucro de camelopublic int FirstField// Casing Pascal{get
{return firstField; }definir {firstField = value; }}private int _secondField;// Underscore e invólucro de
camelopublic int SecondField{get {return _secondField; }definir {_secondField = value; }}

Página 157

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS127Executando Outros CálculosAcessadores de propriedade não


estão limitados a apenas passar valores de um campo de apoio associado;os acessadores get e set
podem realizar qualquer, ou nenhum, cálculo. A única ação necessária é que o getacessador retorna um
valor do tipo de propriedade.Por exemplo, o exemplo a seguir mostra uma propriedade válida (mas
provavelmente inútil) que apenas retornao valor 5 quando seu acessador get é chamado. Quando o
acessador set é chamado, ele não faz nada. oo valor do valor do parâmetro implícito é ignorado.public
int Useless{set {/ * Não estou configurando nada.* /}get {/ * Estou apenas retornando o valor 5. *
/return 5;}}O código a seguir mostra uma propriedade mais realista e útil, onde o acessador set
executafiltragem antes de definir o campo associado. O acessador set define o campo TheRealValue para
o valor de entrada—a menos que o valor de entrada seja maior que 100. Nesse caso, ele define
TheRealValue como 100.int TheRealValue = 10;// O campoint MyValue// A propriedade{conjunto//
Define o valor do campo{TheRealValue = valor> 100// mas certifique-se de que não seja> 100? 100:
valor;}obter// Obtém o valor do campo{return TheRealValue;}}■ Nota No exemplo de código anterior, a
sintaxe entre o sinal de igual e o final da instrução podeparece um pouco estranho. Essa expressão usa o
operador condicional , que será abordado em mais detalhes emCapítulo 8. O operador condicional é um
operador ternário que avalia a expressão na frente da questãomarca e se a expressão for avaliada como
verdadeira , ela retornará a expressão após o ponto de interrogação. Caso contrário istoretorna a
expressão após os dois pontos.

Página 158

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS128Propriedades somente leitura e somente gravaçãoVocê pode


deixar um ou outro (mas não ambos) dos acessores de uma propriedade indefinidos, omitindo
seudeclaração.•Uma propriedade com apenas um acessador get é chamada de propriedade somente
leitura . Uma propriedade somente leitura é ummaneira segura de passar um item de dados de uma
classe ou instância de classe sem permitirmuito acesso.•Uma propriedade com apenas um acessador
definido é chamada de propriedade somente gravação . Uma propriedade somente gravação é
ummaneira segura de passar um item de dados de fora da classe para a classe sem permitirmuito
acesso.•Pelo menos um dos dois acessadores deve ser definido, ou o compilador produzirá uma
mensagem de erro.A Figura 6-10 ilustra propriedades somente leitura e somente gravação.Figura 6-10 .
Uma propriedade pode ter um ou outro de seus acessores indefinidos.

Página 159

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS129Um exemplo de uma propriedade computada, somente


leituraNa maioria dos exemplos até agora, a propriedade foi associada a um campo, e get e
setacessadores referenciaram esse campo. No entanto, uma propriedade não precisa estar associada a
um campo. NoNo exemplo a seguir, o acessador get calcula o valor de retorno.No código de exemplo a
seguir, a classe RightTriangle representa, não surpreendentemente, um triângulo retângulo.•Ele tem dois
campos públicos que representam os comprimentos dos dois lados do ângulo reto do triângulo.Esses
campos podem ser gravados e lidos.•O terceiro lado é representado pela propriedade Hypotenuse, que
é uma propriedade somente leitura cujo retornoo valor é baseado nos comprimentos dos outros dois
lados. Não é armazenado em um campo. Em vez disso, ele calculao valor correto, sob demanda, para os
valores atuais de A e B.A Figura 6-11 ilustra a propriedade somente leitura Hypotenuse.classe
RightTriangle{duplo público A = 3;duplo público B = 4;Hipotenusa dupla pública// propriedade somente
leitura{get {return Math.Sqrt ((A * A) + (B * B)); } // Calcule o valor de retorno}}programa de aula{static
void Main (){RightTriangle c = novo RightTriangle ();Console.WriteLine ("Hypotenuse: {0}",
c.Hypotenuse);}}Figura 6-11 . Propriedade somente leitura Hypotenuse

Página 160

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS130Exemplo de propriedades e bancos de dadosOutro exemplo


em que uma propriedade não está associada a um campo é quando a propriedade está associadacom
um valor em um banco de dados. Nesse caso, o acessador get faz as chamadas de banco de dados
apropriadas para obter ovalor do banco de dados. O acessador de conjunto faz as chamadas
correspondentes ao banco de dados para definir o novovalor no banco de dados.Por exemplo, a seguinte
propriedade está associada a um determinado valor em algum banco de dados. O códigoassume que há
dois outros métodos na classe para lidar com os detalhes das transações do banco de
dados:•SetValueInDatabase pega um parâmetro inteiro e o usa para definir um campo específico em um
registro emalgum banco de dados.•GetValueFromDatabase recupera e retorna um determinado valor de
campo inteiro de um determinadoregistro em algum banco de dados.int MyDatabaseValue{conjunto//
Define o valor inteiro no banco de dados{SetValueInDatabase (valor);}obter// Obtém o valor inteiro do
banco de dados{return GetValueFromDatabase ();}}Propriedades vs. Campos PúblicosPor uma questão
de prática de codificação preferida, as propriedades são preferidas em relação aos campos públicos por
vários motivos:•Uma vez que as propriedades são membros funcionais em oposição aos membros de
dados, elas permitem que você processea entrada e a saída, o que você não pode fazer com campos
públicos.•A semântica de uma variável compilada e uma propriedade compilada são diferentes.O
segundo ponto tem implicações quando você libera um assembly que é acessado por outro código.
Paraexemplo, às vezes há a tentação de usar um campo público em vez de uma propriedade, com
oraciocinando que se você precisar adicionar processamento aos dados mantidos no campo, você
sempre pode alterá-lopara uma propriedade posteriormente. Isso é verdade, mas se você fizer essa
mudança, também terá que recompilar qualqueroutros assemblies acessando esse campo, porque a
semântica compilada de campos e propriedades sãodiferente. Por outro lado, se você o tivesse
implementado como uma propriedade e apenas alterado seuimplementação , você não precisaria
recompilar os outros assemblies que o acessam.

Página 161

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS131Propriedades implementadas automaticamenteComo as


propriedades são frequentemente associadas a campos de apoio, C # 3.0 adicionado implementado
automaticamentepropriedades , ou propriedades autoimplementadas , que permitem que você apenas
declare a propriedade, semdeclarando um campo de apoio. O compilador cria um campo de apoio
oculto para você e automaticamente conectaos acessadores get e set para ele.Os pontos importantes
sobre propriedades implementadas automaticamente são os seguintes:•Você não declara o campo de
apoio - o compilador aloca o armazenamento para você, com base notipo de propriedade.•Você não
pode fornecer os corpos dos acessadores - eles devem ser declarados simplesmente como ponto e
vírgula. oget atua como uma simples leitura da memória e o conjunto como uma simples escrita.•Você
não pode acessar o campo de apoio a não ser por meio dos acessadores. Já que você não pode acessá-
lode outra forma, não faria sentido ter somente leitura ou somente gravação implementado
automaticamentepropriedades, então eles não são permitidos.O código a seguir mostra um exemplo de
uma propriedade implementada automaticamente:classe C1{← Nenhum campo de apoio
declaradopublic int MyValue// Aloca memória{conjunto; obter;}↑ ↑} Os corpos dos acessadores são
declarados como ponto e vírgula.programa de aula{static void Main (){Use propriedades implementadas
automaticamente como propriedades regulares.C1 c = novo C1 ();↓Console.WriteLine ("MyValue: {0}",
c.MyValue);c.MyValue = 20;Console.WriteLine ("MyValue: {0}", c.MyValue);}}Este código produz a
seguinte saída:MyValue: 0MyValue: 20Além de ser conveniente, as propriedades implementadas
automaticamente permitem que você insira facilmente uma propriedade ondevocê pode ficar tentado a
declarar um campo público.

Página 162

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS132Propriedades estáticasAs propriedades também podem ser


declaradas estáticas. Acessores de propriedades estáticas, como todos os membros estáticos•Não é
possível acessar membros de instância de uma classe, embora eles possam ser acessados por eles•Exista
independentemente de haver instâncias da classe•Deve ser referenciado pelo nome da classe, em vez
de um nome de instância, ao ser acessado defora da classePor exemplo, o código a seguir mostra uma
classe com uma propriedade estática implementada automaticamente chamadaMyValue. Nas primeiras
três linhas de Main, a propriedade é acessada, embora não haja instâncias dea classe. A última linha de
Main chama um método de instância que acessa a propriedade de dentro da classe.classe Trivial{public
static int MyValue {get; conjunto; }public void PrintValue ()Acessado de dentro da
classe{↓Console.WriteLine ("Valor de dentro: {0}", MyValue);}}programa de aula{static void Main
()Acessado de fora da classe{↓Console.WriteLine ("Valor inicial: {0}", Trivial.MyValue);Trivial.MyValue =
10;← Acessado de fora da classeConsole.WriteLine ("Novo valor: {0}", Trivial.MyValue);Trivial tr = novo
Trivial ();tr.PrintValue ();}}Valor inicial: 0Novo valor: 10Valor interno: 10Baixe a partir de Wow! e-book
<www.wowebook.com>
Página 163

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS133Construtores de instânciaUm construtor de instância é um


método especial que é executado sempre que uma nova instância de uma classeé criado.•Um construtor
é usado para inicializar o estado da instância da classe.•Se você deseja ser capaz de criar instâncias de
sua classe de fora da classe, você precisa declararo construtor público.A Figura 6-12 mostra a sintaxe de
um construtor. Um construtor se parece com os outros métodos de uma classedeclaração, com as
seguintes exceções:•O nome do construtor é igual ao nome da classe.•Um construtor não pode ter um
valor de retorno.Figura 6-12 . Declaração do construtorPor exemplo, a classe a seguir usa seu construtor
para inicializar seus campos. Neste caso, tem um campochamado TimeOfInstantiation que é inicializado
com a data e hora atuais.classe MyClass{DateTime TimeOfInstantiation;// Field...public MyClass ()//
Construtor{TimeOfInstantiation = DateTime.Now;// Inicializar campo}...}■ Nota Tendo terminado a
seção sobre propriedades estáticas, dê uma olhada mais de perto na linha que
inicializaTimeOfInstantiation . A classe DateTime é do BCL e Now é uma propriedade estática da classe
DateTime .A propriedade Now cria uma nova instância da classe DateTime , inicializa-a com a data e hora
atuais deo relógio do sistema e retorna uma referência à nova instância DateTime .

Página 164

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS134Construtores com parâmetrosOs construtores são como outros
métodos das seguintes maneiras:•Um construtor pode ter parâmetros. A sintaxe para os parâmetros é
exatamente a mesma que para outrosmétodos.•Um construtor pode estar sobrecarregado.Quando você
usa uma expressão de criação de objeto para criar uma nova instância de uma classe, você usa o
novooperador seguido por um dos construtores da classe. O novo operador usa esse construtor para
criar oinstância da classe.Por exemplo, no código a seguir, Class1 tem três construtores: um que não leva
parâmetros, umque leva um int e outro que leva uma string. Main cria uma instância usando cada
um.classe Class1{int Id;string Name;public Class1 (){Id = 28; Nome = "Nemo"; } // Construtor 0public
Class1 (int val) {Id = val; Nome = "Nemo"; } // Construtor 1public Class1 (String name) {Name = name;} //
Construtor 2public void SoundOff (){Console.WriteLine ("Nome {0}, Id {1}", Nome, Id); }}programa de
aula{static void Main (){Classe1 a = nova Classe1 (),// Chame o construtor 0.b = nova Classe1 (7),//
Chame o construtor 1.c = nova Classe1 ("Bill");// Chame o construtor 2.a.SoundOff ();b.SoundOff
();c.SoundOff ();}}Este código produz a seguinte saída:Nome Nemo, Id 28Nome Nemo, Id 7Nome Bill, Id 0

Página 165

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS135Construtores padrãoSe nenhum construtor de instância for


fornecido explicitamente na declaração da classe, o compilador fornecerá umconstrutor implícito
padrão, que tem as seguintes características:•Não leva parâmetros.•Ele tem um corpo vazio.Se você
declarar quaisquer construtores para uma classe, então o compilador não define um padrãoconstrutor
para a classe.Por exemplo, Class2 declara dois construtores.•Como há pelo menos um construtor
definido explicitamente, o compilador não cria nenhumconstrutores adicionais.•Em Main, há uma
tentativa de criar uma nova instância usando um construtor sem parâmetros.Uma vez que não é
nenhum construtor com parâmetros de zero, o compilador produz uma mensagem de erro.classe
Class2{public Class2 (int Value) {...} // Construtor 0public Class2 (String Value) {...} // Construtor
1}programa de aula{static void Main (){Classe2 a = nova Classe2 (); // Erro! Nenhum construtor com 0
parâmetros...}}

Página 166

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS136Construtores estáticosOs construtores também podem ser


declarados estáticos. Enquanto um construtor de instância inicializa cada nova instância deuma classe,
um construtor estático inicializa itens no nível da classe. Geralmente, construtores estáticos inicializam
ocampos estáticos da classe.•Os itens de nível de classe precisam ser inicializados:- Antes que qualquer
membro estático seja referenciado- Antes de qualquer instância da classe ser criada•Construtores
estáticos são como construtores de instância das seguintes maneiras:- O nome do construtor estático
deve ser igual ao nome da classe.- O construtor não pode retornar um valor.•Os construtores estáticos
são diferentes dos construtores de instância nas seguintes maneiras:- Os construtores estáticos usam a
palavra-chave estática na declaração.- Só pode haver um único construtor estático para uma classe e não
pode ter parâmetros.- Construtores estáticos não podem ter modificadores de acessibilidade.A seguir
está um exemplo de um construtor estático. Observe que sua forma é a mesma de umconstrutor de
instância, mas com a adição da palavra-chave estática.classe Class1{static Class1 (){...// Faça todas as
inicializações estáticas.}...Outras coisas importantes que você deve saber sobre construtores estáticos
são as seguintes:•Uma classe pode ter um construtor estático e construtores de instância.•Como os
métodos estáticos, um construtor estático não pode acessar membros de instância de sua classe e não
podeuse este acessador, que abordaremos em breve.•Você não pode chamar explicitamente
construtores estáticos de seu programa. Eles são chamados automaticamente poro sistema, em algum
momento:- Antes de qualquer instância da classe ser criada- Antes que qualquer membro estático da
classe seja referenciado

Página 167

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS137Exemplo de um construtor estáticoO código a seguir usa um


construtor estático para inicializar um campo estático privado denominado RandomKey, do
tipoAleatória. Random é uma classe fornecida pelo BCL para produzir números aleatórios. Ele está no
namespace System.classe RandomNumberClass{private static Random RandomKey;// Campo estático
privadostatic RandomNumberClass ()// Construtor estático{RandomKey = novo Random ();// Inicializar
RandomKey}public int GetRandomNumber (){return RandomKey.Next ();}}programa de aula{static void
Main (){RandomNumberClass a = new RandomNumberClass ();RandomNumberClass b = new
RandomNumberClass ();Console.WriteLine ("Next Random #: {0}", a.GetRandomNumber
());Console.WriteLine ("Next Random #: {0}", b.GetRandomNumber ());}}Uma execução desse código
produziu a seguinte saída:Próximo número aleatório: 47857058Próximo número aleatório:
1124842041Acessibilidade de construtoresVocê pode atribuir modificadores de acesso a construtores de
instância da mesma forma que pode para outros membros. Notar quenos exemplos, os construtores
foram declarados públicos para que você possa criar instâncias defora da classe.Você também pode criar
construtores privados, que não podem ser chamados de fora da classe, mas podem serusado dentro da
classe, como você verá no próximo capítulo.
Página 168

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS138Inicializadores de objetoAté agora no texto, você viu que uma
expressão de criação de objeto consiste na palavra-chave new seguida porum construtor de classe e sua
lista de parâmetros. Um inicializador de objeto estende essa sintaxe, colocando uma lista deinicializações
de membros no final da expressão. Isso permite que você defina os valores dos campos epropriedades
ao criar uma nova instância de um objeto.A sintaxe possui duas formas, conforme mostrado aqui. Um
formulário inclui a lista de argumentos do construtor eo outro não. Observe que a primeira forma nem
mesmo usa os parênteses que envolveriam olista de argumentos.Inicializador de objeto↓novo
TypeName{ FieldOrProp = InitExpr , FieldOrProp = InitExpr , ...}novo TypeName ( ArgList ) { FieldOrProp =
InitExpr , FieldOrProp = InitExpr , ...}↑↑Inicializador de membroInicializador de membroPor exemplo, para
uma classe chamada Point com dois campos inteiros públicos X e Y, você pode usar oseguinte expressão
para criar um novo objeto:novo ponto {X = 5, Y = 6};↑ ↑Init X Init YO que você deve saber sobre os
inicializadores de objeto é o seguinte:•Os campos e propriedades sendo inicializados devem ser
acessíveis ao código que cria o objeto. Parapor exemplo, no código anterior, X e Y devem ser públicos.•A
inicialização ocorre após o construtor terminar a execução, então os valores podem terfoi definido no
construtor e, em seguida, redefinido para o mesmo valor ou um valor diferente na inicialização do
objeto.

Página 169

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS139O código a seguir mostra um exemplo de uso de um


inicializador de objeto. Em Main, o pt1 chama apenas oconstrutor, que define os valores de seus dois
campos. Para pt2, no entanto, o construtor define os campos 'valores para 1 e 2, e o inicializador os
altera para 5 e 6.public class Point{público int X = 1;público int Y = 2;}programa de aula{static void Main ()
{Inicializador de objetoPonto pt1 = novo Ponto (); _______ ↓ __ ____Ponto pt2 = novo Ponto {X = 5, Y =
6};Console.WriteLine ("pt1: {0}, {1}", pt1.X, pt1.Y);Console.WriteLine ("pt2: {0}, {1}", pt2.X, pt2.Y);}}Este
código produz a seguinte saída:pt1: 1, 2pt2: 5, 6

Página 170

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS140DestruidoresOs destruidores executam as ações necessárias


para limpar ou liberar recursos não gerenciados após uma instânciade uma classe não é mais
referenciado. O que é importante saber sobre destruidores é o seguinte:•Você pode ter apenas um
único destruidor por classe.•Um destruidor não pode ter parâmetros.•Um destruidor não pode ter
modificadores de acessibilidade.•Um destruidor tem o mesmo nome da classe, mas é precedido por um
caractere til (pronunciadoTIL-duh ).•Um destruidor atua apenas em instâncias de classes; portanto, não
há destruidores estáticos .•Você não pode chamar um destruidor explicitamente em seu código . Em vez
disso, é chamado durante o lixoprocesso de coleta, quando o coletor de lixo analisa seu código e
determina que não hámais qualquer caminho através de seu código que faça referência ao objeto.Por
exemplo, o código a seguir ilustra a sintaxe de um destruidor de uma classe chamada Class1:Classe 1{~
Class1 ()// O destruidor{CleanupCode}...}Algumas diretrizes importantes para o uso de destruidores são
as seguintes:•Não implemente um destruidor se você não precisar de um. Eles podem incorrer em
custos de desempenho.•Um destruidor só deve liberar recursos externos que o objeto possui.•Um
destruidor não deve acessar outros objetos porque você não pode assumir que esses objetos nãojá foi
destruído.■ Observação Antes do lançamento da versão 3.0 do C #, os destruidores às vezes eram
chamados de finalizadores . Você podeàs vezes, esse termo ainda é encontrado na literatura e nos
nomes de métodos da API .NET.

Página 171

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS141Chamando o DestruidorAo contrário de um destruidor C ++,


um destruidor C # não é chamado imediatamente quando uma instância sai do escopo.Na verdade, não
há como saber quando o destruidor será chamado. Além disso, como anteriormentemencionado, você
não pode chamar explicitamente um destruidor. Se o seu código precisa de um destruidor, você deve
fornecê-lopara o sistema, que o chamará em algum ponto antes que o objeto seja removido do heap
gerenciado.Se o seu código contém recursos não gerenciados que precisam ser liberados em tempo
hábil, vocênão deve deixar essa tarefa para o destruidor, uma vez que não há garantia de que o
destruidor irá executar qualquertempo em breve. Em vez disso, você deve adotar o padrão padrão em
que suas classes implementam o que é chamadoa interface IDisposable. (Vou cobrir as interfaces no
Capítulo 17.) Isso consiste em encapsular a limpezacodifique esses recursos em um método vazio e sem
parâmetros, que você deve chamar Dispose.Quando você terminar com os recursos e quiser que eles
sejam liberados, você precisa chamar Dispose. Notar queé você quem precisa chamar Dispose - não o
destruidor. O sistema não o chamará automaticamente.Algumas diretrizes para o seu método Dispose
são as seguintes:•Implemente o código em Dispose de forma que seja seguro para o método ser
chamado mais do queuma vez. Se ele já tiver sido chamado, em quaisquer invocações subsequentes, ele
não deve gerar umexceção ou fazer qualquer trabalho adicional. (As exceções são abordadas no Capítulo
11.)•Escreva seu método Dispose e destruidor de forma que, se por algum motivo seu código não chegar
achame Dispose, seu destruidor irá chamá-lo e liberar os recursos.•Como o Dispose está fazendo a
limpeza em vez do destruidor, ele deve chamar oMétodo GC.SuppressFinalize, que diz ao CLR para não
chamar o destruidor deste objeto, porque elejá foi cuidado.O código a seguir descreve o processo de
descarte seguro. Primeiro, a classe precisa declarar um booleanoeliminado campo para controlar se a
limpeza ocorreu. Isso é inicializado como falso quando oobjeto é criado.Dentro do método Dispose, faça
o seguinte:•Verifique o sinalizador para ver se os recursos já foram liberados. Se não, faça oSegue:-
Chame os métodos Dispose em quaisquer recursos gerenciados que precisem disso.- Libere todos os
recursos não gerenciados mantidos pelo objeto.•Agora que o descarte ocorreu, defina o sinalizador
descartado como verdadeiro.•Finalmente, chame o método SuppressFinalize do coletor de lixo para
dizer ao coletor de lixo para nãochame o destruidor da classe.

Página 172

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS142O processo no destruidor é semelhante, mas mais curto do que
no método Dispose. Basta verificar paraveja se o objeto já foi limpo e, se não, libere os recursos não
gerenciados.Observe que, neste caso, você não chama os métodos Dispose de nenhum recurso
gerenciado, porque oo coletor de lixo pode já ter excluído esses objetos.classe MyClass{bool disposed =
false;// Sinalizador indicando status de
descarte//////////////////////////////////////////////////////// //////public void Dispose ()// Dispose
público{if (disposed == false)// Verifique o sinalizador.{// Chame Dispose em recursos gerenciados....//
Libere quaisquer recursos não gerenciados....}disposed = true;// Defina o sinalizador para mostrar
disposição.GC.SuppressFinalize (this); // Diga ao GC para não chamar
Finalize.}//////////////////////////////////////////////////////// //////~ MyClass ()// Destruidor{if
(disposed == false)// Verifique o sinalizador.{// Libere quaisquer recursos não gerenciados....}}...}Baixe a
partir de Wow! e-book <www.wowebook.com>

Página 173

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS143O padrão de descarte padrãoNa seção anterior, você viu que o
código do destruidor é essencialmente um subconjunto do código Dispose. oo padrão padrão transforma
a maior parte do código comum desses dois métodos em outro métodochamado Dispose, que chamarei
de Dispose fatorado . Leva um único parâmetro booleano que é usado paraindique se o método está
sendo chamado a partir do método Dispose público (true) ou dodestruidor (falso).Este padrão de
disposição padrão é mostrado a seguir e ilustrado na Figura 6-13. Vou cobrir omodificadores protegidos
e virtuais no próximo capítulo.classe MyClass: IDisposable{bool disposed = false;// Disposal statuspublic
void Dispose (){Dispose (true);Disposição PúblicaGC.SuppressFinalize (this);}~ MyClass ()
{DestruidorDispose (false);}protegido virtual void Dispose (bool disposing){if (disposed == false){if
(disposing == true){// Descarte os recursos gerenciados.Disposição Fatorada...}// Descarte os recursos
não gerenciados....}disposed = true;}}

Página 174

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS144Figura 6-13 . O padrão de disposição padrãoComparando


Construtores e DestrutoresA Tabela 6-3 fornece um resumo de quando os construtores e destruidores
são chamados.Tabela 6-3. Construtores e DestruidoresQuando e com que frequência é
chamadoConstrutor Chamado uma vez na criação de cada nova instância da
classe.InstânciaDestruidorChamado para cada instância da classe, em algum ponto após o fluxo do
programanão pode mais acessar a instância.Construtor Chamado apenas uma vez - antes do primeiro
acesso de qualquer membro estático dea classe ou antes de quaisquer instâncias da classe serem
criadas, o que ocorrer primeiro.EstáticoDestruidorNão existe - destruidores funcionam apenas em
instâncias.

Página 175

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS145O modificador somente leituraUm campo pode ser declarado
com o modificador somente leitura. O efeito é semelhante a declarar um campo como const, emque
uma vez definido o valor, ele não pode ser alterado.•Embora um campo const só possa ser inicializado
na instrução de declaração do campo, um campo somente leitura podetem seu valor definido em
qualquer um dos seguintes locais:- A declaração de declaração de campo - como um const.- Qualquer
um dos construtores de classe. Se for um campo estático, deve ser feito no campo
estáticoconstrutor.•Embora o valor de um campo const deva ser determinável em tempo de compilação,
o valor de um campo somente leituracampo pode ser determinado em tempo de execução. Esta
liberdade adicional permite que você defina valores diferentessob diferentes circunstâncias ou em
diferentes construtores!•Ao contrário de um const, que sempre age como um estático, o seguinte é
verdadeiro para um campo somente leitura:- Pode ser um campo de instância ou um campo estático.-
Possui um local de armazenamento na memória.

Página 176

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS146Por exemplo, o código a seguir declara uma classe chamada
Shape, com dois campos somente leitura.•O campo PI é inicializado em sua declaração.•O campo
NumberOfSides é definido como 3 ou 4, dependendo de qual construtor é chamado.classe
Shape{ Palavra-chaveInicializado↓↓PI duplo somente leitura = 3,1416;readonly int
NumberOfSides;↑↑Palavra-chaveNão inicializadoforma pública (lado duplo 1, lado duplo 2)//
Construtor{// A forma é um retânguloNumberOfSides = 4;↑... Definido no construtor}forma pública (lado
duplo1, lado duplo2, lado duplo3) // Construtor{// A forma é um triânguloNumberOfSides = 3;↑...
Definido no construtor}}

Página 177

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS147Esta palavra-chaveA palavra-chave this, usada em uma classe, é
uma referência à instância atual. Pode ser usado apenas nos blocosdos seguintes membros da
classe:•Construtores de instância.•Métodos de instância.•Acessores de instância de propriedades e
indexadores. (Os indexadores são abordados na próxima seção.)Obviamente, como os membros
estáticos não fazem parte de uma instância, você não pode usar a palavra-chave this dentroo código de
qualquer membro de função estática. Em vez disso, é usado para o seguinte:•Para distinguir entre
membros da classe e variáveis ou parâmetros locais•Como um parâmetro real ao chamar um métodoPor
exemplo, o código a seguir declara a classe MyClass, com um campo int e um método que leva
umparâmetro int único. O método compara os valores do parâmetro e do campo e retorna omaior valor.
O único fator complicador é que os nomes do campo e o parâmetro formal sãoo mesmo: Var1. Os dois
nomes são distinguidos dentro do método usando a palavra-chave this access parareferencie o
campo.classe MyClass{int Var1 = 10;↑ Ambos são chamados de “Var1” ↓public int ReturnMaxSum (int
Var1){Campo de Parâmetro↓↓return Var1> this.Var1? Var1// Parâmetro: this.Var1;// Field}}programa de
aula{static void Main (){MyClass mc = new MyClass ();Console.WriteLine ("Máx .: {0}", mc.ReturnMaxSum
(30));Console.WriteLine ("Máx .: {0}", mc.ReturnMaxSum (5));}}

Página 178

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS148IndexadoresSuponha que você fosse definir a classe Employee,
com três campos do tipo string (como mostrado na Figura 6-14).Você pode então acessar os campos
usando seus nomes, conforme mostrado no código em Main.Figura 6-14 . Classe simples sem
indexadoresHá momentos, no entanto, em que seria conveniente poder acessá-los com um índice,como
se a instância fosse um array de campos. Isso é exatamente o que os indexadores permitem que você
faça. Se você fosseescreva um indexador para a classe Employee, o método Main pode se parecer com o
código da Figura 6-15. Notar queem vez de usar notação de sintaxe de ponto, os indexadores usam
notação de índice , que consiste em um índice entrecolchetes.Figura 6-15 . Usando campos indexados

Página 179

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS149O que é um indexador?Um indexador é um par de acessores


get e set, semelhantes aos das propriedades. A Figura 6-16 mostra orepresentação de um indexador
para uma classe que pode obter e definir valores do tipo string.Figura 6-16 . Representações de um
indexadorIndexadores e propriedadesIndexadores e propriedades são semelhantes em muitos
aspectos.•Como uma propriedade, um indexador não aloca memória para armazenamento.•Ambos os
indexadores e propriedades são usados principalmente para dar acesso a outros membros de dados
comaos quais estão associados e para os quais fornecem acesso de obtenção e configuração.- Uma
propriedade geralmente representa um único membro de dados.- Um indexador geralmente representa
vários membros de dados.■ Nota Você pode pensar em um indexador como uma propriedade que dá
acesso get e set a vários membros de dados doclasse. Você seleciona qual dos muitos membros de
dados possíveis, fornecendo um índice, que por si só pode ser de qualquertipo - não apenas
numérico.Alguns pontos adicionais que você deve saber ao trabalhar com indexadores são os
seguintes:•Como uma propriedade, um indexador pode ter um ou ambos os acessadores.•Os
indexadores são sempre membros da instância; portanto, um indexador não pode ser declarado
estático.•Como as propriedades, o código que implementa os acessadores get e set não precisa ser
associadocom quaisquer campos ou propriedades. O código pode fazer qualquer coisa ou nada, desde
que o acessador obtenharetorna algum valor do tipo especificado.

Página 180

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS150Declaração de um indexadorA sintaxe para declarar um


indexador é mostrada a seguir. Observe o seguinte sobre indexadores:•Um indexador não tem nome .
No lugar do nome está a palavra-chave this.•A lista de parâmetros é entre quadrados colchetes.•Deve
haver pelo menos uma declaração de parâmetro na lista de parâmetros.Lista de parâmetros de palavra-
chave↓↓ReturnType isto [ Type param1 , ...]{↑↑obterColcheteColchete{...}conjunto{...}}Declarar um
indexador é semelhante a declarar uma propriedade. A Figura 6-17 mostra as semelhanças sintáticase
diferenças.Figura 6-17 . Comparando uma declaração de indexador com uma declaração de propriedade

Página 181

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS151O indexador definir o acessorQuando o indexador é o alvo de


uma atribuição, o acessador de conjunto é chamado e recebe dois itens de dados,do seguinte
modo:•Um parâmetro implícito, denominado valor, que contém os dados a serem armazenados•Um ou
mais parâmetros de índice que representam onde deve ser armazenadoemp [0] = "Doe";↑ ↑Valor do
ÍndiceParâmetroSeu código no acessador set deve examinar os parâmetros de índice, determinar onde
os dados devemser armazenado e, em seguida, armazená-lo.A Figura 6-18 mostra a sintaxe e o
significado do acessador set. O lado esquerdo da figura mostra osintaxe real da declaração do acessador.
O lado direito mostra a semântica do acessador se fosseescrito usando a sintaxe de um método normal.
A figura à direita mostra que o acessador definido tem oseguinte semântica:•Tem um tipo de retorno
nulo.•Ele usa a mesma lista de parâmetros da declaração do indexador.•Ele possui um parâmetro de
valor implícito denominado value, do mesmo tipo do indexador.Figura 6-18 . A sintaxe e o significado da
declaração do acessador set

Página 182

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS152O indexador obtém acessoQuando o indexador é usado para
recuperar um valor, o acessador get é chamado com um ou mais índicesparâmetros. Os parâmetros de
índice representam qual valor recuperar.string s = emp [0];↑Parâmetro de índiceO código no corpo do
acessador get deve examinar os parâmetros do índice, determinar em qual campo elesrepresentar e
retornar o valor desse campo.A Figura 6-19 mostra a sintaxe e o significado do acessador get. O lado
esquerdo da figura mostra osintaxe real da declaração do acessador. O lado direito mostra a semântica
do acessador se fosseescrito usando a sintaxe de um método normal. A semântica do acessador get é a
seguinte:•Ele possui a mesma lista de parâmetros da declaração do indexador.•Ele retorna um valor do
mesmo tipo do indexador.Figura 6-19 . A sintaxe e o significado da declaração de acesso getMais sobre
indexadoresAssim como acontece com as propriedades, os acessadores get e set não podem ser
chamados explicitamente. Em vez disso, o acessador get échamado automaticamente quando o
indexador é usado em uma expressão para um valor. O acessador definido é chamadoautomaticamente
quando o indexador recebe um valor com a instrução de atribuição.Quando um indexador é “chamado”,
os parâmetros são fornecidos entre colchetes.Valor do Índice↓ ↓emp [0] = "Doe";// Chama o acessador
do conjuntostring NewName = emp [0];// Chamadas obtêm acessor↑ÍndiceDeclaração do indexador para
o exemplo de funcionárioO código a seguir declara um indexador para o exemplo anterior: class
Employee.Baixe a partir de Wow! e-book <www.wowebook.com>

Página 183

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS153•O indexador deve ler e gravar valores do tipo string -
portanto, string deve ser declarada como otipo de indexador. Deve ser declarado público para que possa
ser acessado de fora da classe.•Os três campos do exemplo foram indexados arbitrariamente como
inteiros de 0 a 2, portanto,O parâmetro formal entre colchetes, denominado índice neste caso, deve ser
do tipo int.•No corpo do acessador definido, o código determina a qual campo o índice se refere e
atribuio valor do valor da variável implícita para ele. No corpo do acessador get, o código determinaa
qual campo o índice se refere e retorna o valor desse campo.classe Funcionário{public string
LastName;// Chame este campo de 0.public string FirstName;// Chame este campo de 1.public string
CityOfBirth;// Chame este campo de 2.public string this [int index]// Declaração do indexador{conjunto//
Definir a declaração do acessador{switch (índice){caso 0: Sobrenome = valor;pausa;caso 1: FirstName =
value;pausa;caso 2: CityOfBirth = valor;pausa;padrão:// (Exceções no Capítulo 11)lance novo
ArgumentOutOfRangeException ("index");}}obter// Obter declaração do acessador{switch (índice){caso
0: retorna LastName;caso 1: retornar FirstName;caso 2: retornar CityOfBirth;padrão:// (Exceções no
Capítulo 11)lance novo ArgumentOutOfRangeException ("index");}}}}

Página 184
CAPÍTULO 6 ■ MAIS SOBRE AS AULAS154Outro exemplo de indexadorA seguir está um exemplo
adicional que indexa os dois campos int da classe Class1:classe Class1{int Temp0;// campo privadoint
Temp1;// campo privadopublic int this [int index] // O indexador{obter{retorno (0 == índice)// Retorna o
valor de Temp0 ou Temp1? Temp0: Temp1;}conjunto{if (0 == índice)Temp0 = valor;// Observe a variável
implícita "valor".outroTemp1 = valor;// Observe a variável implícita "valor".}}}classe exemplo{static void
Main (){Classe1 a = nova Classe1 ();Console.WriteLine ("Valores - T0: {0}, T1: {1}", a [0], a [1]);a [0] = 15;a
[1] = 20;Console.WriteLine ("Valores - T0: {0}, T1: {1}", a [0], a [1]);}}Este código produz a seguinte
saída:Valores - T0: 0, T1: 0Valores - T0: 15, T1: 20

Página 185

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS155Sobrecarga do indexadorUma classe pode ter qualquer número
de indexadores, desde que as listas de parâmetros sejam diferentes; não é suficiente parao tipo de
indexador seja diferente. Isso é chamado de sobrecarga do indexador , porque todos os indexadores têm
o mesmo“Nome” —a referência de acesso.Por exemplo, a classe a seguir tem três indexadores: dois do
tipo string e um do tipo int. Dodois indexadores do tipo string, um tem um único parâmetro int e o outro
tem dois parâmetros int.classe MyClass{public string this [int index]{obter { ... }definir {...}}public string
this [int index1, int index2]{obter { ... }definir {...}}public int this [float index1]{obter { ... }definir {...}}...}■
Nota Lembre-se de que os indexadores sobrecarregados de uma classe devem ter listas de parâmetros
diferentes.

Página 186

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS156Modificadores de acesso em acessoresNeste capítulo, você viu


dois tipos de membros de função que possuem acessadores get e set: propertiese indexadores. Por
padrão, os dois acessadores de um membro têm o mesmo nível de acesso que o próprio membro.Ou
seja, se uma propriedade tem um nível de acesso de público, então ambos os seus acessadores têm o
mesmo nível de acesso.O mesmo é verdade para indexadores.Você pode, no entanto, atribuir diferentes
níveis de acesso aos dois acessadores. Por exemplo, o seguintecódigo mostra um paradigma comum e
importante de declarar um acessador de conjunto privado e um get públicoacessor. A obtenção é pública
porque o nível de acesso da propriedade é público.Observe neste código que embora a propriedade
possa ser lida de fora da classe, ela só pode ser definidade dentro da própria classe, neste caso pelo
construtor. Esta é uma ferramenta importante para encapsulamento.classe Person{↓↓public string Name
{get; conjunto privado; }pessoa pública (nome da string){Nome = nome;}}programa de aula{static public
void Main (){Pessoa p = nova pessoa ("Capitão Ernest Evans");Console.WriteLine ("O nome da pessoa é
{0}", p.Name);}}Existem várias restrições aos modificadores de acesso dos acessadores. Os mais
importantes sãoOs seguintes:•Um acessador pode ter um modificador de acesso apenas se o membro
(propriedade ou indexador) tiver ambos um getacessador e um acessador definido.•Embora ambos os
acessadores devam estar presentes, apenas um deles pode ter um modificador de acesso.•O
modificador de acesso do acessador deve ser estritamente mais restritivo do que o nível de acesso deo
membro.A Figura 6-20 mostra a hierarquia dos níveis de acesso. O nível de acesso de um acessador deve
ser estritamentemais baixo no gráfico do que o nível de acesso do membro.Por exemplo, se uma
propriedade tem um nível de acesso público, você pode conceder a qualquer um dos quatro níveis de
acesso mais baixosníveis no gráfico para um dos acessadores. Mas se a propriedade tiver um nível de
acesso protegido, o únicoO modificador de acesso que você pode usar em um dos acessadores é
privado.

Página 187

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS157Figura 6-20 . Hierarquia de níveis de acessador estritamente


restritivosClasses parciais e tipos parciaisA declaração de uma classe pode ser particionada entre várias
declarações parciais de classe.•Cada uma das declarações parciais de classe contém as declarações de
alguns dos membros da classe.•As declarações de classe parcial de uma classe podem estar no mesmo
arquivo ou em arquivos diferentes.Cada declaração parcial deve ser rotulada como classe parcial, em
contraste com a classe de palavra-chave única.A declaração de uma classe parcial parece a mesma que a
declaração de uma classe normal, excetoadição do modificador de tipo parcial.Modificador de
tipo↓parcial classe MyPartClass // Mesmo nome de classe que a seguir{declaração membro1declaração
membro2...}Modificador de tipo↓parcial classe MyPartClass // Mesmo nome de classe
anterior{declaração membro 3declaração membro4...}■ Nota O modificador de tipo parcial não é uma
palavra-chave, portanto, em outros contextos, você pode usá-lo como um identificador em
seuprograma. Mas quando usado imediatamente antes da classe de palavras-chave , estrutura ou
interface , ele sinaliza o uso de umtipo parcial.

Página 188

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS158Por exemplo, a caixa à esquerda da Figura 6-21 representa um
arquivo com uma declaração de classe. As caixasà direita da figura representa a mesma declaração de
classe dividida em dois arquivos.Figura 6-21 . Divisão de classes usando tipos parciaisTodas as
declarações de classe parcial que compreendem uma classe devem ser compiladas juntas. Uma classe
usando parcialdeclarações de classe têm o mesmo significado como se todos os membros da classe
fossem declarados dentro de uma única classecorpo de declaração.O Visual Studio usa esse recurso em
seus modelos de programa padrão do Windows. Quando você cria umProjeto ASP.NET ou um projeto
Windows Forms a partir dos modelos padrão, os modelos criam doisarquivos de classe para cada página
da web ou formulário:•Um arquivo contém a classe parcial contendo o código gerado pelo Visual Studio,
declarando ocomponentes na página. Você não deve modificar a classe parcial neste arquivo, uma vez
que é regeneradopelo Visual Studio quando você modifica os componentes na página.•O outro arquivo
contém a classe parcial que você usa para implementar a aparência e o comportamento docomponentes
da página ou formulário.Além das classes parciais, você também pode criar dois outros tipos parciais,
que são os seguintes:•Estruturas parciais. (As estruturas são abordadas no Capítulo 12.)•Interfaces
parciais. (As interfaces são abordadas no Capítulo 17.)

Página 189

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS159Métodos ParciaisMétodos parciais são métodos declarados em


duas partes de uma classe parcial. As duas partes do parcialmétodo pode ser declarado em diferentes
partes da classe parcial ou na mesma parte. As duas partes dométodo parcial são os seguintes:•A
definição da declaração de método parcial:- Fornece a assinatura e o tipo de retorno.- A parte de
implementação da declaração consiste em apenas um ponto e vírgula.•A implementação da declaração
de método parcial :- Fornece a assinatura e o tipo de retorno.- A implementação está no formato normal,
que, como você sabe, é um bloco de instruções.O que é importante saber sobre os métodos parciais é o
seguinte:•Tanto a declaração de definição quanto a de implementação devem corresponder na
assinatura e no tipo de retorno. oa assinatura e o tipo de retorno têm as seguintes características:- A
palavra-chave contextual parcial deve ser incluída tanto na definição quanto na
implementaçãodeclarações imediatamente antes da palavra-chave void.- A assinatura não pode incluir
modificadores de acesso, tornando os métodos parciais implicitamente privados .- O tipo de retorno
deve ser nulo.- A lista de parâmetros não pode conter parâmetros de saída.•Você pode definir um
método parcial sem implementar um método parcial. Neste caso, oo compilador remove a declaração e
todas as chamadas ao método feitas dentro da classe. Se, no entanto,a classe tem um método parcial de
implementação, ela também deve ter um método parcial de definição.

Página 190

CAPÍTULO 6 ■ MAIS SOBRE AS AULAS160O código a seguir mostra um exemplo de um método parcial
chamado PrintSum.•PrintSum é declarado em diferentes partes da classe parcial Myclass: a declaração
de definição no primeiroparte e a declaração de implementação na segunda parte. A implementação
imprime osoma de seus dois parâmetros inteiros.•Como os métodos parciais são implicitamente
privados, PrintSum não pode ser chamado de fora da classe.O método Add é um método público que
chama PrintSum.•Main cria um objeto da classe MyClass e chama o método público Add, que chama o
método PrintSum,que imprime a soma dos parâmetros de entrada.classe parcial MyClass{Deve ser
nulo↓parcial vazio PrintSum (int x, int y); // Definindo método parcial↑↑Palavra-chave contextualSem
implementaçãopublic void Add (int x, int y){PrintSum (x, y);}}classe parcial MyClass{parcial void PrintSum
(int x, int y) // Implementando método parcial{Console.WriteLine ("Soma é {0}", x + y);←
Implementação}}programa de aula{static void Main (){var mc = new MyClass ();mc.Add (5, 6);}}Este
código produz a seguinte saída:A soma é 11

Página 191

CAPÍTULO 7■ ■ ■161Classes e herança■ Herança de classe■ Acessando os membros herdados■


Escondendo membros de uma classe base■ Acesso à base■ Usando referências a uma classe base■
Execução do construtor■ Herança entre montagens■ Modificadores de acesso de membro■ Membros
abstratos■ Classes abstratas■ Classes Seladas■ Métodos Externos

Página 192

CAPÍTULO 7 ■ AULAS E HERANÇA162Herança de classeHerança permite que você defina uma nova
classe que incorpora e estende uma classe já declarada.•Você pode usar uma classe existente, chamada
de classe base , como base para uma nova classe, chamada de derivadaclasse . Os membros da classe
derivada consistem no seguinte:- Os membros em sua própria declaração- Os membros da classe
base•Para declarar uma classe derivada, você adiciona uma especificação de base de classe após o nome
da classe. A base de classea especificação consiste em dois pontos, seguidos do nome da classe a ser
usada como classe base.Diz-se que a classe derivada herda diretamente da classe base listada.•Diz- se
que uma classe derivada estende sua classe base, porque inclui os membros da classe basealém de
qualquer funcionalidade adicional fornecida em sua própria declaração.•Uma classe derivada não pode
excluir nenhum dos membros que herdou.Por exemplo, o seguinte mostra a declaração de uma classe
chamada OtherClass, que é derivada deuma classe chamada SomeClass:Especificação de base de
classe↓classe OtherClass: SomeClass{↑ ↑...Classe Base do Cólon}A Figura 7-1 mostra uma instância de
cada uma das classes. Class SomeClass, à esquerda, tem um campo eum método. A classe OtherClass, à
direita, é derivada de SomeClass e contém um campo adicionale um método adicional.Figura 7-1. Classe
base e classe derivadaBaixe a partir de Wow! e-book <www.wowebook.com>

Página 193

CAPÍTULO 7 ■ AULAS E HERANÇA163Acessando os membros herdadosOs membros herdados são


acessados como se tivessem sido declarados na própria classe derivada. (Herdadoconstrutores são um
pouco diferentes - irei cobri-los mais tarde neste capítulo.) Por exemplo, o código a seguirdeclara as
classes SomeClass e OtherClass, que foram mostradas na Figura 7-1. O código mostra que todosquatro
membros de OtherClass podem ser acessados perfeitamente, independentemente de serem declarados
noclasse base ou a classe derivada.•Main cria um objeto da classe derivada OtherClass.•As próximas
duas linhas em Main chamam o Método1 na classe base , usando Field1 da classe base e entãoField2 da
classe derivada.•As duas linhas subsequentes em Main chamam Method2 na classe derivada ,
novamente usando Field1 doclasse base e depois Field2 da classe derivada.classe SomeClass// Classe
base{public string Field1 = "campo de classe base";public void Method1 (string value) {Console.WriteLine
("Classe base - Método1: {0}", valor);}}classe OtherClass: SomeClass// Classe derivada{string pública
Field2 = "campo de classe derivada";public void Method2 (string value) {Console.WriteLine ("Classe
derivada - Método2: {0}", valor);}}programa de aula{static void Main () {OtherClass oc = new OtherClass
();oc.Method1 (oc.Field1); // Método base com campo baseoc.Method1 (oc.Field2); // Método base com
campo derivadooc.Method2 (oc.Field1); // Método derivado com campo baseoc.Method2 (oc.Field2); //
Método derivado com campo derivado}}Este código produz a seguinte saída:Classe base - Método1:
campo da classe baseClasse base - Método1: campo de classe derivadaClasse derivada - Método2:
campo da classe baseClasse derivada - Método2: campo de classe derivada

Página 194

CAPÍTULO 7 ■ AULAS E HERANÇA164Todas as classes são derivadas do objeto de classeTodas as classes,


exceto objeto de classe especial, são classes derivadas, mesmo se não tiverem uma base de
classeespecificação. O objeto de classe é a única classe que não é derivada, pois é a base da
herançahierarquia.As classes sem uma especificação de base de classe são derivadas implicitamente
diretamente do objeto de classe. Saindofora da especificação da classe base é apenas uma abreviação
para especificar que o objeto é a classe base. Os doisas formas são semanticamente equivalentes, como
mostrado na Figura 7-2.Figura 7-2. A declaração de classe à esquerda deriva implicitamente do objeto de
classe, enquanto a declaração da direitaderiva explicitamente do objeto. As duas formas são
semanticamente equivalentes.Outros fatos importantes sobre a derivação de classe são os
seguintes:•Uma declaração de classe pode ter apenas uma única classe listada em sua especificação de
base de classe. Isso é chamadoherança única .•Embora uma classe possa herdar diretamente de apenas
uma única classe base, não há limite para o nível dederivação. Ou seja, a classe listada como a classe
base pode ser derivada de outra classe, que éderivado de outra classe, e assim por diante, até que você
finalmente alcance o objeto.A classe base e a classe derivada são termos relativos. Todas as classes são
classes derivadas, de objeto oude outra classe - então, geralmente, quando chamamos uma classe de
classe derivada, queremos dizer que é imediatamentederivado de alguma classe diferente de objeto. A
Figura 7-3 mostra uma hierarquia de classes simples. Depois disso eu não voumostre o objeto nas
figuras, uma vez que todas as classes são, em última análise, derivadas dele.Figura 7-3. Uma hierarquia
de classes

Página 195

CAPÍTULO 7 ■ AULAS E HERANÇA165Escondendo Membros de uma Classe BaseEmbora uma classe


derivada não possa excluir nenhum dos membros que herdou, ela pode ocultá-los.•Para ocultar um
membro de dados herdado, declare um novo membro do mesmo tipo e com omesmo nome .•Você
pode ocultar ou mascarar um membro de função herdado, declarando na classe derivada um
novomembro da função com a mesma assinatura. Lembre-se de que a assinatura consiste no nomee
lista de parâmetros, mas não inclui o tipo de retorno.•Para deixar o compilador saber que você está
ocultando propositalmente um membro herdado, use o novomodificador. Sem ele, o programa irá
compilar com sucesso, mas o compilador irá avisá-lo quevocê está escondendo um membro
herdado.•Você também pode ocultar membros estáticos.O código a seguir declara uma classe base e
uma classe derivada, cada uma com um membro de string chamadoField1. A palavra-chave new é usada
para dizer explicitamente ao compilador para mascarar o membro da classe base. Figura 7-4ilustra uma
instância de cada classe.classe SomeClass// Classe base{public string Field1;...}classe OtherClass:
SomeClass// Classe derivada{nova string pública Field1;// Máscara membro da base com o mesmo
nome↑Palavra-chaveFigura 7-4. Escondendo um membro de uma classe base

Página 196

CAPÍTULO 7 ■ AULAS E HERANÇA166No código a seguir, OtherClass deriva de SomeClass, mas oculta
seus dois membros herdados.Observe o uso do novo modificador. O código é ilustrado na Figura 7-
5.classe SomeClass// Classe base{public string Field1 = "SomeClass Field1";public void Method1 (string
value){Console.WriteLine ("SomeClass.Method1: {0}", valor); }}classe OtherClass: SomeClass// Classe
derivada{ Palavra-chave↓nova string pública Field1 = "OtherClass Field1"; // Mascare o membro da
base.new public void Method1 (string value)// Mascare o membro da base.↑{Console.WriteLine
("OtherClass.Method1: {0}", valor); }} Palavra-chaveprograma de aula{static void Main (){OtherClass oc =
new OtherClass (); // Use o membro de mascaramento.oc.Method1 (oc.Field1);// Use o membro de
mascaramento.}}Este código produz a seguinte saída:OtherClass.Method1: OtherClass Field1Figura 7-5.
Escondendo um campo e um método da classe base

Página 197
CAPÍTULO 7 ■ AULAS E HERANÇA167Acesso à BaseSe sua classe derivada absolutamente deve acessar
um membro herdado oculto, você pode acessá-lo usando umexpressão de acesso de base . Esta
expressão consiste na base de palavras-chave, seguida imediatamente por um pontoe o nome do
membro, conforme mostrado aqui:Console.WriteLine ("{0}", base.Field1);↑Acesso à basePor exemplo, no
código a seguir, a classe derivada OtherClass oculta Field1 em sua classe base, masacessa-o usando uma
expressão de acesso base.class SomeClass {// Classe basepublic string Field1 = "Field1 - Na classe
base";}class OtherClass: SomeClass {// Classe derivadanova string pública Field1 = "Field1 - Na classe
derivada";↑↑Esconde o campo na classe basepublic void PrintField1 (){Console.WriteLine (Campo1);//
Acesse a classe derivada.Console.WriteLine (base.Field1);// Acesse a classe base.}↑}Acesso à baseclass
Program {static void Main (){OtherClass oc = new OtherClass ();oc.PrintField1 ();}}Este código produz a
seguinte saída:Field1 - na classe derivadaField1 - Na classe baseSe você usa esse recurso com frequência,
convém reavaliar o design de suas classes. Geralmenteexistem designs mais elegantes, mas o recurso
está lá se houver uma situação em que nada mais servirá.

Página 198

CAPÍTULO 7 ■ AULAS E HERANÇA168Usando referências a uma classe baseUma instância de uma classe
derivada consiste em uma instância da classe base, mais os membros adicionais dea classe derivada.
Uma referência à classe derivada aponta para todo o objeto da classe, incluindo a baseparte da classe.Se
você tiver uma referência a um objeto de classe derivada, poderá obter uma referência apenas à parte
da classe base deo objecto por fundição a referência para o tipo de classe de base utilizando o operador
de conversão . O elencooperador é colocado na frente da referência do objeto e consiste em um
conjunto de parênteses contendo onome da classe para a qual está sendo lançado. A fundição é
abordada em detalhes no Capítulo 18.As próximas seções cobrem o acesso a um objeto usando uma
referência à parte da classe base doobjeto. Começaremos examinando as duas linhas de código a seguir,
que declaram referências a objetos.A Figura 7-6 ilustra o código e mostra as partes do objeto vistas pelas
diferentes variáveis.•A primeira linha declara e inicializa a variável derivada, que contém uma referência
a umobjeto do tipo MyDerivedClass.•A segunda linha declara uma variável do tipo de classe base,
MyBaseClass, e converte a referência emderivado desse tipo, fornecendo uma referência à parte da
classe base do objeto.- A referência à parte da classe base é armazenada na variável mybc, no lado
esquerdo dooperador de atribuição.- A referência à parte da classe base não pode "ver" o resto do
objeto da classe derivada, porqueestá “olhando” para ele por meio de uma referência ao tipo de
base.MyDerivedClass derivado = new MyDerivedClass (); // Crie um objeto.MyBaseClass mybc =
(MyBaseClass) derivado; // Lance a referência.Figura 7-6 . A referência derivada pode ver todo o objeto
MyDerivedClass, enquanto mybc só pode ver oParte MyBaseClass do objeto.

Página 199

CAPÍTULO 7 ■ AULAS E HERANÇA169O código a seguir mostra a declaração e o uso dessas duas classes.
A Figura 7-7 ilustra oobjeto e referências na memória.Main cria um objeto do tipo MyDerivedClass e
armazena sua referência na variável derivada. Principal tambémcria uma variável do tipo MyBaseClass e
a usa para armazenar uma referência à parte da classe base doobjeto. Quando o método Print é
chamado em cada referência, a chamada invoca a implementação dométodo que a referência pode ver,
produzindo strings de saída diferentes.classe MyBaseClass{public void Print (){Console.WriteLine ("Esta é
a classe base.");}}classe MyDerivedClass: MyBaseClass{novo public void Print (){Console.WriteLine ("Esta
é a classe derivada.");}}programa de aula{static void Main (){MyDerivedClass derivado = new
MyDerivedClass ();MyBaseClass mybc = (MyBaseClass) derivado;↑Transmitir para classe
basederivado.Print ();// Chame Print da parte derivada.mybc.Print ();// Chame Print da parte da
base.}}Este código produz a seguinte saída:Esta é a classe derivada.Esta é a classe base.Figura 7-7. Uma
referência à classe derivada e à classe base

Página 200

CAPÍTULO 7 ■ AULAS E HERANÇA170Métodos virtuais e de substituiçãoNa seção anterior, você viu que
ao acessar um objeto de uma classe derivada usando uma referência aa classe base, você obtém os
membros da classe base. Os métodos virtuais permitem uma referência à baseclasse para acessar "até" a
classe derivada.Você pode usar uma referência a uma classe base para chamar um método na classe
derivada , se o seguinte for verdadeiro:•O método na classe derivada e o método na classe base têm
cada um a mesma assinaturae tipo de retorno.•O método na classe base é rotulado como virtual.•O
método na classe derivada é rotulado como substituição.Por exemplo, o código a seguir mostra os
modificadores virtuais e de substituição nos métodos noclasse base e classe derivada:classe
MyBaseClass// Classe base{virtual public void Print ()↑...classe MyDerivedClass: MyBaseClass// Classe
derivada{override public void Print ()↑A Figura 7-8 ilustra esse conjunto de métodos virtuais e de
substituição. Observe como o comportamento difere deo caso anterior, onde usei new para ocultar os
membros da classe base.•Quando o método Print é chamado usando a referência à classe base (mybc),
a chamada do método épassado para a classe derivada e executado, porque- O método na classe base é
marcado como virtual.- Existe um método de substituição correspondente na classe derivada.•A Figura
7-8 ilustra isso, mostrando a seta saindo do verso do método de impressão virtuale apontando para o
método de substituição de impressão.Figura 7-8. Um método virtual e um método de substituição

Página 201

CAPÍTULO 7 ■ AULAS E HERANÇA171O código a seguir é o mesmo da seção anterior, mas, desta vez, os
métodos são rotuladosvirtual e substituir. Isso produz um resultado muito diferente do exemplo anterior.
Nonesta versão, chamar o método por meio da classe base invoca o método na classe derivada.classe
MyBaseClass{virtual public void Print (){Console.WriteLine ("Esta é a classe base.");}}classe
MyDerivedClass: MyBaseClass{override public void Print (){Console.WriteLine ("Esta é a classe
derivada.");}}programa de aula{static void Main (){MyDerivedClass derivado = new MyDerivedClass
();MyBaseClass mybc = (MyBaseClass) derivado;↑derivado.Print ();Transmitir para classe basemybc.Print
();}}Este código produz a seguinte saída:Esta é a classe derivada.Esta é a classe derivada.Outras coisas
importantes a saber sobre os modificadores virtuais e de substituição são as seguintes:•Os métodos
sobrescritos e sobrescritos devem ter a mesma acessibilidade. Em outras palavras, oO método
sobrescrito não pode ser, por exemplo, privado, e o método sobrescrito público.•Você não pode
substituir um método estático ou não virtual.•Métodos, propriedades e indexadores (que abordei no
capítulo anterior) e outrostipo de membro, chamado de eventos (que abordarei posteriormente no
texto), podem ser todos declarados virtuaise substituir.
Página 202

CAPÍTULO 7 ■ AULAS E HERANÇA172Substituindo uma substituição marcada de métodoOs métodos de


substituição podem ocorrer entre quaisquer níveis de herança.•Quando você usa uma referência à parte
da classe base de um objeto para chamar um método substituído, ochamada de método é passada para
cima na hierarquia de derivação para execução para a versão mais derivada dométodo marcado como
substituição.•Se houver outras declarações do método em níveis superiores de derivação que não estão
marcados comosubstituir - eles não são invocados.Por exemplo, o código a seguir mostra três classes
que formam uma hierarquia de herança:MyBaseClass, MyDerivedClass e SecondDerived. Todas as três
classes contêm um método denominado Print, coma mesma assinatura. Em MyBaseClass, Print é
rotulado como virtual. Em MyDerivedClass, é rotulado como override. Noclasse SecondDerived, você
pode declarar o método Print com override ou new. Vamos ver o queacontece em cada caso.classe
MyBaseClass// Classe base{virtual public void Print (){Console.WriteLine ("Esta é a classe base."); }}classe
MyDerivedClass: MyBaseClass// Classe derivada{override public void Print (){Console.WriteLine ("Esta é a
classe derivada."); }}classe SecondDerived: MyDerivedClass// Classe mais derivada{... // Dado nas
páginas seguintes}Caso 1: Declarando impressão com substituiçãoSe você declarar o método Print de
SecondDerived como substituição, ele substituirá ambos os métodos menos derivadosversões do
método, conforme mostrado na Figura 7-9. Se uma referência à classe base é usada para chamar Print,
ela obtémpassou por toda a cadeia até a implementação na classe SecondDerived.Baixe a partir de
Wow! e-book <www.wowebook.com>

Página 203

CAPÍTULO 7 ■ AULAS E HERANÇA173O código a seguir implementa esse caso. Observe o código nas
duas últimas linhas do método Main.•A primeira das duas instruções chama o método Print usando uma
referência para o mais derivadoclasse — SecondDerived. Isso não é uma chamada por meio de uma
referência à parte da classe base, entãochame o método implementado em SecondDerived.•A segunda
instrução, no entanto, chama o método Print usando uma referência à classe base—MyBaseClass.classe
SecondDerived: MyDerivedClass{override public void Print () {↑Console.WriteLine ("Esta é a segunda
classe derivada.");}}programa de aula{static void Main (){Derivado de SecondDerived = novo Derivado de
Second (); // Use SecondDerived.MyBaseClass mybc = (MyBaseClass) derivado; // Use
MyBaseClass.derivado.Print ();mybc.Print ();}}O resultado é que, independentemente de Print ser
chamado por meio da classe derivada ou da classe base, ométodo na classe derivada mais é chamado.
Quando chamado por meio da classe base, é passado para ohierarquia de herança. Este código produz a
seguinte saída:Esta é a segunda classe derivada.Esta é a segunda classe derivada.Figura 7-9. A execução
é passada para o topo da cadeia de vários níveis de substituição.

Página 204

CAPÍTULO 7 ■ AULAS E HERANÇA174Caso 2: Declarando Imprimir com novoSe, em vez disso, você
declarar o método Print de SecondDerived como novo, o resultado é mostrado na Figura 7-10.Main é o
mesmo que no caso anterior.classe SecondDerived: MyDerivedClass{novo public void Print ()
{Console.WriteLine ("Esta é a segunda classe derivada.");}}programa de aula{static void Main ()// A
Principal{Derivado de SecondDerived = novo Derivado de Second (); // Use SecondDerived.MyBaseClass
mybc = (MyBaseClass) derivado;// Use MyBaseClass.derivado.Print ();mybc.Print ();}}O resultado é que
quando o método Print é chamado por meio da referência a SecondDerived, o métodoem
SecondDerived é executado, como você esperaria. Quando o método é chamado por meio de uma
referência aMyBaseClass, no entanto, a chamada do método é passada apenas um nível, para a classe
MyDerived, onde éexecutado. A única diferença entre os dois casos é se o método em SecondDerived é
declaradocom substituição de modificador ou modificador novo.Este código produz a seguinte saída:Esta
é a segunda classe derivada.Esta é a classe derivada.Figura 7-10. Escondendo os métodos substituídos

Página 205

CAPÍTULO 7 ■ AULAS E HERANÇA175Substituindo outros tipos de membrosNas poucas seções


anteriores, você viu como as designações virtual / override funcionam nos métodos.Eles funcionam
exatamente da mesma maneira com propriedades, eventos e indexadores. Por exemplo, o seguinte
códigomostra uma propriedade somente leitura chamada MyProperty usando virtual / override.classe
MyBaseClass{privado int _myInt = 5;virtual public int MyProperty{get {return _myInt; }}}classe
MyDerivedClass: MyBaseClass{int privado _myInt = 10;substituir public int MyProperty{get {return
_myInt; }}}programa de aula{static void Main (){MyDerivedClass derivado = new MyDerivedClass
();MyBaseClass mybc = (MyBaseClass) derivado;Console.WriteLine
(derivado.MyProperty);Console.WriteLine (mybc.MyProperty);}}Este código produz a seguinte
saída:1010

Página 206

CAPÍTULO 7 ■ AULAS E HERANÇA176Execução do ConstrutorNo capítulo anterior, você viu que um


construtor executa o código que prepara uma classe para uso. esteinclui a inicialização de membros
estáticos e de instância da classe. Neste capítulo, você viu que partede um objeto de classe derivada é
um objeto da classe base.•Para criar a parte da classe base de um objeto, um construtor para a classe
base é implicitamente chamado departe do processo de criação da instância.•Cada classe na cadeia de
hierarquia de herança executa seu construtor de classe base antes de executarseu próprio corpo de
construtor.Por exemplo, o código a seguir mostra uma declaração da classe MyDerivedClass e seu
construtor.Quando o construtor é chamado, ele chama o construtor sem parâmetros MyBaseClass ()
antes de executar seupróprio corpo.classe MyDerivedClass: MyBaseClass{MyDerivedClass ()// O
construtor usa o construtor básico MyBaseClass ().{...}A Figura 7-11 mostra a ordem de construção.
Quando uma instância está sendo criada, um dos primeiroscoisas que são feitas são a inicialização de
todos os membros da instância do objeto. Depois disso, a classe baseconstrutor é chamado. Só então o
corpo do construtor da própria classe é executado.Figura 7-11. Ordem de construção do objeto

Página 207

CAPÍTULO 7 ■ AULAS E HERANÇA177Por exemplo, no código a seguir, os valores de MeuCampo1 e


MeuCampo2 seriam definidos como 5 e 0,respectivamente, antes que o construtor da classe base seja
chamado.classe MyDerivedClass: MyBaseClass{int MeuCampo1 = 5;// 1. Membro inicializadoint
MyField2;// Membro inicializadopublic MyDerivedClass ()// 3. Corpo do construtor executado{...}}classe
MyBaseClass{public MyBaseClass ()// 2. Construtor da classe base chamado{...}}■ Cuidado Chamar um
método virtual em um construtor é fortemente desencorajado . O método virtual na classe
basechamaria o método override na classe derivada enquanto o construtor da classe base está sendo
executado. Mas issoseria antes de o corpo do construtor derivado ser executado. Seria, portanto,
chamar para a classe derivadaantes que a classe seja completamente inicializada.

Página 208

CAPÍTULO 7 ■ AULAS E HERANÇA178Inicializadores de construtorPor padrão, o construtor sem


parâmetros da classe base é chamado quando um objeto está sendo construído.Mas os construtores
podem estar sobrecarregados, portanto, uma classe base pode ter mais de um. Se você quer seu
derivadoclasse para usar um construtor de classe base específico diferente do construtor sem
parâmetros, você deve especificarem um inicializador de construtor .Existem duas formas de
inicializador de construtor:•A primeira forma usa a base de palavras-chave e especifica qual construtor
de classe base usar.•A segunda forma usa a palavra-chave this e especifica qual outro construtor desta
classedeve ser usado.Um inicializador de construtor de classe base é colocado após dois pontos
seguindo a lista de parâmetros em uma classedeclaração do construtor. O inicializador de construtor
consiste na base de palavras-chave e na lista de parâmetrosdo construtor base para chamar.Por exemplo,
o código a seguir mostra um construtor para a classe MyDerivedClass.•O inicializador do construtor
especifica que o processo de construção deve chamar a classe baseconstrutor com dois parâmetros,
onde o primeiro parâmetro é uma string e o segundo parâmetroé um int.•Os parâmetros na lista de
parâmetros de base devem corresponder ao parâmetro do construtor de base pretendidolista, em tipo e
ordem.Inicializador de construtor↓public MyDerivedClass (int x, string s): base (s, x){↑...Palavra-
chaveQuando você declara um construtor sem um inicializador de construtor, é um atalho para o
formulário comum inicializador de construtor consistindo em base (), conforme ilustrado na Figura 7-12.
As duas formas sãosemanticamente equivalente.Figura 7-12. Formas equivalentes de um construtor

Página 209

CAPÍTULO 7 ■ AULAS E HERANÇA179A outra forma de inicializador de construtor instrui o processo de


construção (na verdade, o compilador)para usar um construtor diferente da mesma classe. Por exemplo,
o seguinte mostra um construtor com umparâmetro único para a classe MyClass. Esse construtor de
parâmetro único, no entanto, usa um construtor dea mesma classe, mas com dois parâmetros,
fornecendo um parâmetro padrão como o segundo.Inicializador de construtor↓public MyClass (int x):
this (x, "Usando String Padrão"){↑...Palavra-chave}Outra situação em que isso é particularmente útil é
quando você tem vários construtores parauma classe, e eles têm um código comum que deve sempre
ser executado no início do objetoProcesso de construção. Nesse caso, você pode fatorar esse código
comum e colocá-lo em um construtorque é usado como um inicializador de construtor por todos os
outros construtores. Na verdade, este é umprática sugerida, pois reduz a duplicação de código.Você
pode pensar que poderia simplesmente declarar outro método que executa aquelasinicializações e fazer
com que todos os construtores chamem esse método. Isso não é tão bom por vários motivos. oA
primeira é que o compilador pode otimizar certas coisas quando sabe que um método é um construtor.
oa segunda é que existem algumas coisas que podem ser feitas apenas em um construtor e não em
outro lugar. Paraexemplo, no capítulo anterior, você aprendeu que os campos somente leitura podem ser
inicializados apenas dentro de umconstrutor. Você obterá um erro do compilador se tentar inicializar um
campo somente leitura em qualquer outrométodo, mesmo se esse método for chamado apenas por um
construtor.

Página 210

CAPÍTULO 7 ■ AULAS E HERANÇA180Voltando a esse construtor comum, se ele pode se manter por
conta própria como um construtor válido queinicializa tudo na classe que precisa ser inicializado, então é
perfeitamente normal deixá-lo como umconstrutor público.E se, no entanto, ele não inicializar
completamente um objeto? Nesse caso, você não deve permitir queconstrutor pode ser chamado de
fora da classe, já que criaria uma inicialização incompletaobjetos. Para evitar esse problema, você pode
declarar o construtor privado em vez de público, conforme mostrado emo seguinte código:classe
MyClass{readonly int firstVar;somente leitura double secondVar;public string UserName;public int
UserIdNumber;MyClass privado ()// Construtor privado executa inicializações{// comum a outros
construtores.primeira Var = 20;secondVar = 30,5;}public MyClass (string firstName): this () // usa o
inicializador de construtor{UserName = firstName;UserIdNumber = -1;}public MyClass (int idNumber):
this () // usa o inicializador de construtor{UserName = "Anônimo";UserIdNumber = idNumber;}}

Página 211

CAPÍTULO 7 ■ AULAS E HERANÇA181Modificadores de acesso de classeUma classe pode ser vista e


acessada por outras classes no sistema. Esta seção explica a acessibilidade deAulas. Embora eu use
classes nas explicações e exemplos, uma vez que é o que cobrimos até agorano texto, as regras de
acessibilidade também se aplicam aos outros tipos que abordarei posteriormente.O termo visível às
vezes é usado para o termo acessível . Eles podem ser usados alternadamente. Lásão dois níveis de
acessibilidade de classe: pública e interna.•Uma classe marcada como pública pode ser acessada por
código de qualquer assembly no sistema. Para fazer uma aulavisível para outros assemblies, use o
modificador de acesso público, conforme mostrado aqui:Palavra-chave↓public class
MyBaseClass{...•Uma classe marcada como interna só pode ser vista por classes em seu próprio
assembly.- Este é o nível de acessibilidade padrão, a menos que você especifique explicitamente o
modificador público ema declaração da classe, o código fora do assembly não pode acessar a classe.-
Você pode declarar explicitamente uma classe como interna usando o modificador de acesso
interno.Palavra-chave↓classe interna MyBaseClass{...A Figura 7-13 ilustra a acessibilidade de classes
internas e públicas de fora da montagem.A classe MyClass não é visível para as classes na montagem à
esquerda, porque está marcada como interna. ClasseOtherClass, no entanto, é visível para as classes à
esquerda, porque está marcada como pública.Figura 7-13. As classes de outros assemblies podem
acessar classes públicas, mas não podem acessar classes internas.

Página 212

CAPÍTULO 7 ■ AULAS E HERANÇA182Herança entre assembléiasAté agora, declarei classes derivadas no


mesmo assembly que contém a classe base. Mas C # tambémpermite derivar uma classe de uma classe
base definida em uma montagem diferente. Para fazer isso, o seguintedeve ser verdade:•A classe base
deve ser declarada pública para que possa ser acessada de fora de seu assembly.•Você deve incluir uma
referência em seu projeto Visual Studio para o assembly que contém oclasse base.Para tornar mais fácil
referir-se às classes e tipos na outra montagem, sem usar seusnomes qualificados, coloque uma diretiva
using no topo do arquivo de origem, com o namespace contendo oclasses ou tipos que você deseja
acessar.Nota Adicionar uma referência a outro assembly e adicionar uma diretiva using são duas coisas
separadas. Adicionandoa referência ao outro assembly informa ao compilador onde os tipos necessários
são definidos. Adicionando o usandodiretiva permite que você faça referência a outras classes sem ter
que usar seus nomes totalmente qualificados. Capítulo 10 cobreisso em detalhes.Por exemplo, os dois
segmentos de código a seguir, de diferentes assemblies, mostram como é fácilherdar uma classe de
outro assembly. A primeira listagem de código cria um assembly que contém odeclaração de uma classe
denominada MyBaseClass, que possui as seguintes características:•Ele é declarado em um arquivo de
origem chamado Assembly1.cs e dentro de um namespace declarado como BaseClassNS.•É declarado
público para que possa ser acessado de outros assemblies.•Ele contém um único membro, um método
chamado PrintMe, que apenas escreve uma mensagem simplesidentificando a classe.// Nome do
arquivo de origem Assembly1.csusing System;Namespace contendo declaração da classe
base↓namespace BaseClassNS{ Declare a classe pública para que possa ser vista fora da
assembleia.↓public class MyBaseClass {public void PrintMe () {Console.WriteLine ("Eu sou
MyBaseClass");}}}Baixe a partir de Wow! e-book <www.wowebook.com>

Página 213

CAPÍTULO 7 ■ AULAS E HERANÇA183O segundo assembly contém a declaração de uma classe chamada
DerivedClass, que herda deMyBaseClass, declarado no primeiro assembly. O arquivo de origem é
denominado Assembly2.cs. A Figura 7-14 ilustraas duas assembleias.•DerivedClass tem um corpo vazio,
mas herda o método PrintMe de MyBaseClass.•Main cria um objeto do tipo DerivedClass e chama seu
método herdado PrintMe.// Nome do arquivo de origem Assembly2.csusing System;using
BaseClassNS;↑Namespace contendo declaração da classe basenamespace UsesBaseClass{Classe base em
outra montagem↓class DerivedClass: MyBaseClass {// Corpo vazio}class Program {static void Main ()
{DerivedClass mdc = new DerivedClass ();mdc.PrintMe ();}}}Este código produz a seguinte saída:Eu sou
MyBaseClassFigura 7-14. Herdando entre assemblies

Página 214

CAPÍTULO 7 ■ AULAS E HERANÇA184Modificadores de acesso de membroAs duas seções anteriores


explicaram a acessibilidade da classe. Com acessibilidade de classe, existem apenas doismodificadores -
internos e públicos. Esta seção cobre a acessibilidade dos membros . Acessibilidade de classe descrevea
visibilidade de uma aula; acessibilidade de membro descreve a visibilidade dos membros de um objeto
de classe.Cada membro declarado em uma classe é visível para várias partes do sistema, dependendo do
acessomodificador atribuído a ele em sua declaração de classe. Você viu que membros privados são
visíveis apenas paraoutros membros da mesma classe, enquanto os membros públicos podem ser
visíveis para as classes fora da assembleiatambém. Nesta seção, veremos novamente os níveis de acesso
público e privado, bem como os outros trêsníveis de acessibilidade.Antes de examinar os detalhes da
acessibilidade de membros, existem algumas coisas gerais que precisamoscubra primeiro:•Todos os
membros declarados explicitamente na declaração de uma classe são visíveis uns para os outros,
independentemente desuas especificações de acessibilidade.•Membros herdados não são declarados
explicitamente na declaração de uma classe, então, como você verá,membros podem ou não estar
visíveis para membros de uma classe derivada.•Existem cinco níveis de acesso de membro:- público-
privado- protegido- interno- interno protegido•Você deve especificar os níveis de acesso do membro por
membro. Se você não especificar um acessonível de um membro, seu nível de acesso implícito é
privado.•Um membro não pode ser mais acessível do que sua classe. Ou seja, se uma classe tem um
nível de acessibilidadelimitando-o à assembleia, os membros individuais da classe não podem ser vistos
fora da assembleia,independentemente de seus modificadores de acesso, mesmo públicos.

Página 215

CAPÍTULO 7 ■ AULAS E HERANÇA185Regiões que acessam um membroOs modificadores de acesso de


membro na declaração de uma classe especificam quais outros tipos podem e não podem acessarquais
membros da classe. Por exemplo, a seguinte declaração mostra membros declarados com ocinco níveis
de acesso.public class MyClass{públicoint Member1;privadoint Member2;protegidoint
Member3;internoint Member4;protegido interno int Member5;...Os níveis de acesso são baseados em
duas características em relação à classe sendo declarada:•Se a classe é derivada da classe que está
sendo declarada•Se uma classe está no mesmo assembly que a classe que está sendo declaradaEssas
duas características geram quatro grupos, conforme ilustrado na Figura 7-15. Em relação à aulasendo
declarada, outra classe pode ser qualquer uma das seguintes:•Na mesma montagem e derivada dela
(canto inferior direito)•Na mesma montagem, mas não derivada dela (canto inferior esquerdo)•Em uma
montagem diferente e derivada dela (canto superior direito)•Em uma montagem diferente e não
derivada dela (canto superior esquerdo)Essas características são usadas para definir os cinco níveis de
acesso.Figura 7-15. Áreas de acessibilidade

Página 216

CAPÍTULO 7 ■ AULAS E HERANÇA186Acessibilidade de membros públicosO nível de acesso público é o


menos restritivo. Todas as classes dentro e fora da montagem têm acesso gratuitoacesso ao membro. A
Figura 7-16 ilustra a acessibilidade de um membro da classe pública de MyClass.Para declarar um
membro público, use o modificador de acesso público, conforme mostrado.Palavra-chave↓public int
Member1;Figura 7-16. Um membro público de uma classe pública é visível para todas as classes no
mesmo assembly ou outroassembléias.Acessibilidade de membros privadosO nível de acesso privado é o
mais restritivo.•Um membro de classe privada pode ser acessado apenas por membros de sua própria
classe. Não pode ser acessadopor outras classes, incluindo classes que são derivadas dele.•Um membro
privado pode, entretanto, ser acessado por membros de classes aninhadas em sua classe. Aninhadoas
classes são abordadas no Capítulo 25.A Figura 7-17 ilustra a acessibilidade de um membro privado.Figura
7-17. Um membro privado de qualquer classe é visível apenas para membros de sua própria classe (ou
classes aninhadas).
Página 217

CAPÍTULO 7 ■ AULAS E HERANÇA187Acessibilidade de membros protegidosO nível de acesso protegido


é como o nível de acesso privado, exceto que também permite classes derivadas dea classe para acessar
o membro. A Figura 7-18 ilustra a acessibilidade protegida. Observe que mesmo as classesfora da
montagem que são derivados da classe têm acesso ao membro.Figura 7-18. Um membro protegido de
uma classe pública é visível para membros de sua própria classe ou classes derivadasa partir dele. As
classes derivadas podem até estar em outros assemblies.Acessibilidade de membro internoOs membros
marcados como internos são visíveis para todas as classes da montagem, mas não para as classes fora
domontagem, conforme ilustrado na Figura 7-19.Figura 7-19. Um membro interno de uma classe pública
é visível para os membros de qualquer classe na mesma assembleiamas não para classes fora da
assembleia.

Página 218

CAPÍTULO 7 ■ AULAS E HERANÇA188Acessibilidade de membros internos protegidosOs membros


marcados como protegidos internos são visíveis para todas as classes que herdam da classe e também
paratodas as classes dentro da montagem, conforme mostrado na Figura 7-20. Observe que o conjunto
de classes de acesso permitido éo conjunto combinado de classes permitidas pelo modificador protegido
mais o conjunto de classes permitidas pelomodificador interno. Observe que esta é a união de protegido
e interno - não a interseção.Figura 7-20. Um membro interno protegido de uma classe pública é visível
para os membros das classes da mesmaassembly ou a membros de classes derivadas dessa classe. Não é
visível para classes em outros assemblies quenão são derivados da classe.Resumo dos modificadores de
acesso de membroAs duas tabelas a seguir resumem as características dos cinco níveis de acesso de
membro. Tabela 7-1 listasos modificadores e fornece um resumo intuitivo dos efeitos do
modificador.Tabela 7-1. Modificadores de acesso de membroModificadorSignificadoprivadoAcessível
apenas dentro da classeinternoAcessível a todas as classes dentro desta montagemprotegidoAcessível a
todas as classes derivadas desta classeinterno protegidoAcessível a todas as classes que são derivadas
desta classe ou declaradasdentro desta assembleiapúblicoAcessível a qualquer aula

Página 219

CAPÍTULO 7 ■ AULAS E HERANÇA189A Figura 7-21 mostra a acessibilidade relativa dos cinco
modificadores de acesso de membro.Figura 7-21. Acessibilidade relativa dos vários modificadores de
acesso de membroA Tabela 7-2 lista os modificadores de acesso no lado esquerdo da tabela e as
categorias de classes emo topo. Derivado refere-se a classes derivadas da classe que declara o membro.
Não derivado significa classesnão derivado da classe que declara o membro. Uma marca em uma célula
significa que a categoria da classe podeacessar membros com o modificador correspondente.Tabela 7-2.
Resumo de acessibilidade de membrosAulas na mesma assembléiaAulas em diferentes
montagensDerivados não derivadosNão DerivadoDerivadoprivadointerno✓✓protegido✓✓interno
protegido✓✓✓público✓✓✓✓

Página 220
CAPÍTULO 7 ■ AULAS E HERANÇA190Membros abstratosUm membro abstrato é um membro de função
que foi projetado para ser substituído. Um membro abstrato tem oseguintes características:•Está
marcado com o modificador abstrato.•Não possui um bloco de código de implementação. Os blocos de
código de membros abstratos sãorepresentado por ponto e vírgula.Por exemplo, o código a seguir de
dentro de uma definição de classe declara dois membros abstratos: ummétodo abstrato chamado
PrintStuff e uma propriedade abstrata chamada MyProperty. Observe os pontos-e-vírgulas emlocal dos
blocos de implementação.Palavra-chavePonto e vírgula no lugar da implementação↓↓Abstract public
void PrintStuff (string s) ;abstract public int MyProperty{obter; ← Ponto e vírgula no lugar de
implementaçãoconjunto; ← Ponto e vírgula no lugar de implementação}Membros abstratos podem ser
declarados apenas em classes abstratas , que veremos na próxima seção.Quatro tipos de membro
podem ser declarados como abstratos:•Métodos•Propriedades•Eventos•Indexadores

Página 221

CAPÍTULO 7 ■ AULAS E HERANÇA191Outros fatos importantes sobre membros abstratos são os


seguintes:•Membros abstratos, embora devam ser substituídos por um membro correspondente em um
derivadoclasse, não pode usar o modificador virtual além do modificador abstrato.•Tal como acontece
com os membros virtuais, a implementação de um membro abstrato em uma classe derivada
deveespecifique o modificador de substituição.A Tabela 7-3 compara e contrasta membros virtuais e
membros abstratos.Tabela 7-3. Comparando membros virtuais e abstratosMembro VirtualMembro
AbstratoPalavra-chavevirtualresumoCorpo de implementaçãoTem um corpo de implementaçãoNenhum
corpo de implementação—ponto e vírgula ao invésSubstituído em uma classe derivadaPode ser
substituído—usando substituiçãoDeve ser substituído—usando substituiçãoTipos de
membrosMétodosPropriedadesEventosIndexadoresMétodosPropriedadesEventosIndexadores

Página 222

CAPÍTULO 7 ■ AULAS E HERANÇA192Classes abstratasAs classes abstratas são projetadas para serem
herdadas. Uma classe abstrata pode ser usada apenas como a classe base deoutra classe.•Você não
pode criar instâncias de uma classe abstrata.•Uma classe abstrata é declarada usando o modificador
abstrato.Palavra-chave↓classe abstrata MyClass{...}•Uma classe abstrata pode conter membros abstratos
ou membros regulares não abstratos. Os membrosde uma classe abstrata pode ser qualquer combinação
de membros abstratos e membros normais comimplementações.•Uma classe abstrata pode ser derivada
de outra classe abstrata. Por exemplo, o seguinteo código mostra uma classe abstrata derivada de
outra.classe abstrata AbClass// Classe abstrata{...}classe abstrata MyAbClass: AbClass// Classe abstrata
derivada de{// uma classe abstrata...}•Qualquer classe derivada de uma classe abstrata deve
implementar todos os membros abstratos da classe porusando a palavra-chave override, a menos que a
classe derivada seja abstrata.Baixe a partir de Wow! e-book <www.wowebook.com>

Página 223

CAPÍTULO 7 ■ AULAS E HERANÇA193Exemplo de uma classe abstrata e um método abstratoO código a


seguir mostra uma classe abstrata chamada AbClass com dois métodos.O primeiro método é um método
normal com uma implementação que imprime o nome da classe.O segundo método é um método
abstrato que deve ser implementado em uma classe derivada. ClasseDerivedClass herda de AbClass e
implementa e substitui o método abstrato. Main cria umobjeto de DerivedClass e chama seus dois
métodos.Palavra-chave↓classe abstrata AbClass// Classe abstrata{public void IdentifyBase ()// Método
normal{Console.WriteLine ("Eu sou AbClass"); }Palavra-chave↓abstract public void IdentifyDerived ();//
Método abstrato}classe DerivedClass: AbClass// Classe derivada{Palavra-chave↓override public void
IdentifyDerived ()// Implementação de{Console.WriteLine ("Eu sou DerivedClass"); }// método
abstrato}programa de aula{static void Main (){// AbClass a = new AbClass ();// Erro. Não é possível
instanciar// a.IdentifyDerived ();// uma classe abstrata.DerivedClass b = new DerivedClass (); // Instancie
a classe derivada.b.IdentifyBase ();// Chame o método herdado.b.IdentifyDerived ();// Chame o método
"abstrato".}}Este código produz a seguinte saída:Eu sou AbClassEu sou DerivedClass

Página 224

CAPÍTULO 7 ■ AULAS E HERANÇA194Outro exemplo de uma classe abstrataO código a seguir mostra a
declaração de uma classe abstrata que contém membros de dados, bem comomembros da função.
Membros de dados não podem ser declarados como abstratos.classe abstrata MyBase // Combinação de
membros abstratos e não abstratos{public int SideLength= 10;// Membro de dadosconst int
TriangleSideCount = 3;// Membro de dadosAbstract public void PrintStuff (string s); // Método
abstratoabstract public int MyInt {get; conjunto; } // propriedade abstratapublic int PerimeterLength ()//
Método regular, não abstrato{return TriangleSideCount * SideLength; }}classe MyClass: MyBase{public
override void PrintStuff (string s) // Substituir método abstrato{Console.WriteLine (s); }private int
_myInt;public override int MyInt// Substituir propriedade abstrata{get {return _myInt; }definir {_myInt =
value; }}}programa de aula{static void Main (string [] args){MyClass mc = new MyClass ();mc.PrintStuff
("Esta é uma string.");mc.MyInt = 28;Console.WriteLine (mc.MyInt);Console.WriteLine ("Comprimento do
perímetro: {0}", mc.PerimeterLength ());}}Este código produz a seguinte saída:Isso é uma
string.28Comprimento do perímetro: 30

Página 225

CAPÍTULO 7 ■ AULAS E HERANÇA195Classes SeladasNa seção anterior, você viu que uma classe abstrata
deve ser usada como uma classe base - não pode serinstanciado como um objeto de classe autônomo. O
oposto é verdadeiro para uma classe selada .•Uma classe selada pode ser instanciada apenas como um
objeto de classe autônomo - ela não pode ser usada como baseclasse.•Uma classe selada é rotulada
com o modificador selado.Por exemplo, a seguinte classe é uma classe selada. Qualquer tentativa de
usá-lo como a classe base de outroclasse produzirá um erro de compilação.Palavra-chave↓classe selada
MyClass{...}

Página 226

CAPÍTULO 7 ■ AULAS E HERANÇA196Classes estáticasUma classe estática é uma classe em que todos os
membros são estáticos. Classes estáticas são usadas para agrupar dados efunções que não são afetadas
pelos dados da instância. Um uso comum de uma classe estática pode ser criar uma
matemáticabiblioteca contendo conjuntos de métodos e valores matemáticos.O que é importante saber
sobre as classes estáticas é o seguinte:•A própria classe deve ser marcada como estática.•Todos os
membros da classe devem ser estáticos.•A classe pode ter um construtor estático, mas não pode ter um
construtor de instância, já que vocênão pode criar uma instância da classe.•As classes estáticas são
seladas implicitamente. Ou seja, você não pode herdar de uma classe estática.Você acessa os membros
de uma classe estática da mesma forma que acessaria qualquer membro estático, usando onome da
classe e o nome do membro.O código a seguir mostra um exemplo de classe estática:A classe deve ser
marcada como estática↓classe pública estática MyMath{flutuante estático público PI = 3.14f;public static
bool IsOdd (int x)↑{return x% 2 == 1; }Os membros devem ser estáticos↓public static int Times2 (int x)
{return 2 * x; }}programa de aula{static void Main (){Use o nome da classe e o nome do membro.int val =
3;↓Console.WriteLine ("{0} é ímpar é {1}.", Val, MyMath.IsOdd (val));Console.WriteLine ("{0} * 2 = {1}.",
Val, MyMath.Times2 (val));}}Este código produz a seguinte saída:3 é estranho é verdadeiro.3 * 2 = 6.

Página 227

CAPÍTULO 7 ■ AULAS E HERANÇA197Métodos de ExtensãoAté agora neste texto, cada método que você
viu foi associado à classe em que foi declarado.O recurso de método de extensão introduzido no C # 3.0
estende esse limite, permitindo que você escrevamétodos associados a classes diferentes da classe em
que foram declarados.Para ver como você pode usar esse recurso, dê uma olhada no código a seguir. Ele
contém a classe MyData,que armazena três valores do tipo double e contém um construtor e um
método chamado Sum, queretorna a soma dos três valores armazenados.classe MyData{D1 duplo
privado;// CamposD2 duplo privado;D3 duplo privado;public MyData (duplo d1, duplo d2, duplo d3)//
Construtor{D1 = d1; D2 = d2; D3 = d3;}soma dupla pública ()// Soma do Método{retornar D1 + D2 +
D3;}}Esta é uma classe bastante limitada, mas suponha que seria mais útil se contivesse outro
método,que retornou a média dos três pontos de dados. Com o que você sabe até agora sobre as aulas,
existemvárias maneiras de implementar a funcionalidade adicional:•Se você tiver o código-fonte e puder
modificar a classe, poderá, é claro, apenas adicionar o novométodo para a classe.•Se, no entanto, você
não pode modificar a classe - por exemplo, se a classe estiver em uma biblioteca de classes de terceiros
-então, contanto que não seja selado, você pode usá-lo como uma classe base e implementar o
adicionalmétodo em uma classe derivada dele.Se, no entanto, você não tiver acesso ao código ou a
classe estiver lacrada ou houver algum outro designrazão pela qual nenhuma dessas soluções
funcionará, então você terá que escrever um método em outra classeque usa os membros da classe
disponíveis publicamente.

Página 228

CAPÍTULO 7 ■ AULAS E HERANÇA198Por exemplo, você pode escrever uma classe como a do código a
seguir. O código contém um estáticoclasse chamada ExtendMyData, que contém um método estático
chamado Average, que implementa ofuncionalidade adicional. Observe que o método usa uma instância
de MyData como parâmetro.classe estática ExtendMyDataInstância da classe MyData{↓public static
double Average (MyData md){return md.Sum () / 3;}↑}Use a instância de MyData.programa de aula{static
void Main (){Instância de MyDataMyData md = novo MyData (3, 4, 5);↓Console.WriteLine ("Média: {0}",
ExtendMyData.Aadise (md));}↑}Chame o método estático.Este código produz a seguinte saída:Média:
4Embora esta seja uma solução perfeitamente adequada, seria mais elegante se você pudesse chamar o
método ema própria instância da classe, em vez de criar uma instância de outra classe para agir sobre
ela. Os dois seguinteslinhas de código ilustram a diferença. O primeiro usa o método mostrado -
invocando um método estático emuma instância de outra classe. O segundo mostra o formulário que
gostaríamos de usar - invocando uma instânciamétodo no próprio objeto.ExtendMyData.A Average
(md)// Formulário de invocação estáticamd.A Average ();// Formulário de invocação de instânciaOs
métodos de extensão permitem que você use o segundo formulário, mesmo que o primeiro seja
omaneira normal de escrever a invocação.

Página 229

CAPÍTULO 7 ■ AULAS E HERANÇA199Fazendo uma pequena mudança na declaração do método


Average, você pode usar a invocação de instânciaFormato. A alteração que você precisa fazer é adicionar
a palavra-chave this antes do nome do tipo no parâmetrodeclaração conforme mostrado a seguir.
Adicionando esta palavra-chave ao primeiro parâmetro do método estático dea classe estática muda de
um método regular da classe ExtendMyData para um método de extensão da classeMeus dados. Agora
você pode usar os dois formulários de invocação.Deve ser uma classe estática↓classe estática
ExtendMyData{ Deve ser público e estáticoPalavra-chave e tipo↓↓public static double Average (este
MyData md){...)}Os requisitos importantes para um método de extensão são os seguintes:•A classe na
qual o método de extensão é declarado também deve ser declarada como estática.•O próprio método
de extensão deve ser declarado estático.•O método de extensão deve conter como seu primeiro
parâmetro digite a palavra-chave this, seguida pelonome da classe que está estendendo.A Figura 7-22
ilustra a estrutura de um método de extensão.Figura 7-22. A estrutura de um método de extensão

Página 230

CAPÍTULO 7 ■ AULAS E HERANÇA200O código a seguir mostra um programa completo, incluindo a


classe MyData e o método de extensão Averagedeclarado na classe ExtendMyData. Observe que o
método Average é invocado exatamente como se fosse uma instânciamembro do MyData! A Figura 7-22
ilustra o código. As classes MyData e ExtendMyData juntas agem como oaula desejada, com três
métodos.namespace ExtensionMethods{classe selada MyData{duplo privado D1, D2, D3;public MyData
(duplo d1, duplo d2, duplo d3){D1 = d1; D2 = d2; D3 = d3; }soma dupla pública () {return D1 + D2 +
D3; }}classe estática ExtendMyDataPalavra-chave e tipo{↓public static double Average (este MyData md)
{↑Estática declaradareturn md.Sum () / 3;}}programa de aula{static void Main (){MyData md = novo
MyData (3, 4, 5);Console.WriteLine ("Soma: {0}", md.Sum ());Console.WriteLine ("Média: {0}", md.Affet
());}↑}Invocar como um membro de instância da classe}Este código produz a seguinte saída:Soma:
12Média: 4

Página 231

CAPÍTULO 8■ ■ ■201Expressões e operadores■ Expressões■ Literais■ Ordem de avaliação■


Operadores aritméticos simples■ O operador restante■ Operadores de comparação relacional e de
igualdade■ Operadores de incremento e decremento■ Operadores lógicos condicionais■ Operadores
lógicos■ Operadores de turno■ Operadores de atribuição■ O operador condicional■ Operadores
aritméticos unários■ Conversões de tipo definidas pelo usuário■ Sobrecarga do operador■ O tipo de
operador■ Outros operadores

Página 232

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES202ExpressõesEste capítulo define expressões e descreve os


operadores fornecidos pelo C #. Também explica como vocêpode definir os operadores C # para
trabalhar com suas classes definidas pelo usuário.Uma expressão é uma string de operadores e
operandos. A seguir estão algumas das construções quepodem atuar como
operandos:•Literais•Constantes•Variáveis•Chamadas de método•Acessores de elemento, como
acessadores de array e indexadores•Outras expressõesOs operadores C # usam um, dois ou três
operandos. Um operador faz o seguinte:•Pega seus operandos como entrada•Executa uma
ação•Retorna um valor, com base na açãoAs expressões podem ser combinadas, usando operadores,
para criar outras expressões, como mostrado nesteexpressão, com três operadores e quatro
operandos:Avaliar uma expressão é o processo de aplicação de cada operador aos seus operandos, na
devidaseqüência, para produzir um valor.•O valor é retornado à posição em que a expressão foi avaliada.
Lá, pode serturn ser um operando em uma expressão envolvente.•Além do valor retornado, algumas
expressões também têm efeitos colaterais, como definir um valorem memória.Baixe a partir de Wow! e-
book <www.wowebook.com>

Página 233

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES203LiteraisLiterais são números ou strings digitados no


código-fonte que representam um valor definido específico de umtipo específico.Por exemplo, o código
a seguir mostra literais de seis tipos. Observe, por exemplo, a diferençaentre o literal duplo e o literal
flutuante.static void Main ()Literais{↓Console.WriteLine ("{0}", 1024);// int literalConsole.WriteLine ("{0}",
3.1416);// literal duploConsole.WriteLine ("{0}", 3.1416F);// float literalConsole.WriteLine ("{0}",
verdadeiro);// literal booleanoConsole.WriteLine ("{0}", 'x');// literal de caractereConsole.WriteLine
("{0}", "Olá"); // string literal}A saída deste código é a seguinte:10243,14163,1416VerdadexOláComo os
literais são escritos no código-fonte, seus valores devem ser conhecidos no momento da
compilação.Vários dos tipos predefinidos têm suas próprias formas de literal:•O tipo bool tem dois
literais: verdadeiro e falso.•Para variáveis de tipo de referência, literal nulo significa que a variável não
está definida para uma referênciaem memória.

Página 234

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES204Literais inteirosLiterais inteiros são os literais mais


comumente usados. Eles são escritos como uma sequência de dígitos decimais,com o seguinte:•Sem
ponto decimal•Um sufixo opcional para especificar o tipo do inteiroPor exemplo, as linhas a seguir
mostram quatro literais para o inteiro 236. Cada um é interpretado pelocompilador como um tipo
diferente de inteiro, dependendo de seu sufixo.236// int236L// longo236U// unsigned236UL// sem sinal
longoLiterais de tipo inteiro também podem ser escritos na forma hexadecimal (hex). Os dígitos devem
ser os dígitos hexadecimais(0 a F), e a string deve ser precedida por 0x ou 0X (numeral 0 , letra x ).A
Figura 8-1 mostra as formas dos formatos literais inteiros. Componentes com nomes em
quadradocolchetes são opcionais.Figura 8-1. Os formatos literais inteirosA Tabela 8-1 lista os sufixos
literais inteiros. Para um determinado sufixo, o compilador interpretará a string dedígitos como o menor
dos tipos inteiros correspondentes que podem representar o valor sem perder dados.Por exemplo,
pegue os literais 236 e 5000000000, nenhum dos quais tem um sufixo. Já que 236 pode serrepresentado
com 32 bits, será interpretado pelo compilador como um int. O maior número, no entanto,não caberá
em 32 bits, então o compilador irá representá-lo como um longo.Tabela 8-1. Sufixos literais
inteirosSufixoTipo InteiroNotasNenhumint , uint , long , ulongU , uuint , ulongL , llong , ulongUsar a letra l
minúscula não é recomendado, porqueé facilmente confundido com o dígito 1 .ul , uL , Ul , ULlu , Lu , lU ,
LUUlongUsar a letra l minúscula não é recomendado, porqueé facilmente confundido com o dígito 1

Página 235

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES205Literais reaisLiterais para números reais consistem no


seguinte:•Dígitos decimais•Um ponto decimal opcional•Uma parte expoente opcional•Um sufixo
opcionalPor exemplo, o código a seguir mostra vários formatos de literais dos tipos reais:float f1 =
236F;d1 duplo = 236,714;duplo d2 = 0,35192;duplo d3 = 6,338e-26;A Figura 8-2 mostra os formatos
válidos para literais reais. Os componentes com nomes entre colchetes sãoopcional. A Tabela 8-2 mostra
os sufixos reais e seus significados.Figura 8-2. Os verdadeiros formatos literaisTabela 8-2 . Sufixos para os
literais reaisSufixoTipo RealNenhumDuploF , fflutuadorD , dDuploM , mdecimal

Página 236

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES206■ Nota literais reais sem um sufixo são do tipo double ,
não float !Literais de caracteresUm literal de caractere consiste em uma representação de caractere
entre duas aspas simples. Um personagema representação pode ser qualquer um dos seguintes: um
único caractere, uma sequência de escape simples, um escape hexadecimalseqüência ou uma seqüência
de escape Unicode.•O tipo de um literal de caractere é char.•Uma sequência de escape simples é uma
barra invertida seguida por um único caractere.•Uma sequência de escape hexadecimal é uma barra
invertida, seguida por um x maiúsculo ou minúsculo , seguido por atéquatro dígitos hexadecimais.•Uma
sequência de escape Unicode é uma barra invertida, seguida por um u maiúsculo ou minúsculo , seguido
poraté quatro dígitos hexadecimais.Por exemplo, o código a seguir mostra vários formatos de literais de
caracteres:char c1 = 'd';// Single characterchar c2 = '\ n';// Sequência de escape simpleschar c3 = '\
x0061';// Sequência de escape hexadecimalchar c4 = '\ u005a';// seqüência de escape UnicodeA Tabela
8-3 mostra alguns dos caracteres especiais importantes e suas codificações.Tabela 8-3. Personagens
especiais importantesNomeCodificação hexadecimal de sequência de escapeNulo\
00x0000Alerta\uma0x0007Backspace\ b0x0008Aba horizontal\ t0x0009Nova linha\ n0x000AAba
vertical\ v0x000BFeed de formulário\ f0x000C

Página 237

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES207NomeCodificação hexadecimal de sequência de


escapeRetorno de carruagem\ r0x000DCitação dupla\ "0x0022Citação única\ '0x0027Barra
invertida\\0x005CLiterais de stringLiterais de string usam aspas duplas em vez de aspas simples usadas
em literais de caracteres.Existem dois tipos de literais de string:•Literais de string regulares•Literais de
string literalUm literal de string regular consiste em uma sequência de caracteres entre um conjunto de
aspas duplas. Um regularliteral de string pode incluir o seguinte:•Personagens•Sequências de escape
simples•Sequências de escape hex e UnicodeAqui está um exemplo:string st1 = "Olá!";string st2 = "Val1 \
t5, Val2 \ t10";string st3 = "Adicionar \ x000ASome \ u0007Interest";Um literal de string literal é escrito
como um literal de string regular, mas é precedido por um caractere @. ocaracterísticas importantes de
literais de string literalmente são as seguintes:•Os literais literal são diferentes dos literais de string
regulares porque as sequências de escape não são avaliadas.Tudo entre o conjunto de aspas duplas -
incluindo o que normalmente seria consideradosequências de escape - é impresso exatamente como
está listado na string.•A única exceção com literais textuais são conjuntos de aspas duplas contíguas, que
sãointerpretado como um caractere de aspas duplas simples .

Página 238

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES208Por exemplo, o código a seguir compara alguns literais de


string regulares e literais:string rst1 = "Olá!";string vst1 = @ "Olá!";string rst2 = "Começou, \" Quatro
pontos e sete ... \ "";string vst2 = @ "Tudo começou," "Quatro pontos e sete ..." "";string rst3 = "Valor 1 \
t 5, Val2 \ t 10"; // Interpreta a sequência da guia escstring vst3 = @ "Valor 1 \ t 5, Val2 \ t 10"; // Não
interpreta a guiastring rst4 = "C: \\ Arquivos de programas \\ Microsoft \\";string vst4 = @ "C: \ Arquivos
de programas \ Microsoft \";string rst5 = "Imprimir \ x000A múltiplas \ u000A linhas";string vst5 = @
"ImprimirMúltiploLinhas ";A impressão dessas strings produz a seguinte saída:Olá!Olá!Começava com
"Quatro pontos e sete ..."Começava com "Quatro pontos e sete ..."Valor 15, Val210Valor 1 \ t 5, Val2 \ t
10C: \ Arquivos de programas \ Microsoft \C: \ Arquivos de programas \ Microsoft
\ImpressãoMúltiploLinhasImpressãoMúltiploLinhas■ Nota O compilador economiza memória por ter
literais de string idênticos compartilhando o mesmo local de memória ema pilha.

Página 239

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES209Ordem de AvaliaçãoUma expressão pode ser composta


de muitas subexpressões aninhadas. A ordem em que as subexpressõessão avaliados podem fazer
diferença no valor final da expressão.Por exemplo, dada a expressão 3 * 5 + 2, existem dois resultados
possíveis, dependendo da ordem emem que as subexpressões são avaliadas, conforme mostrado na
Figura 8-3.•Se a multiplicação for realizada primeiro, o resultado será 17.•Se 5 e 2 forem somados
primeiro, o resultado será 21.Figura 8-3. Ordem simples de avaliaçãoPrecedênciaVocê sabe de seus dias
de escola primária que no exemplo anterior, a multiplicação deve serexecutada antes da adição porque a
multiplicação tem uma precedência mais alta do que a adição. Mas ao contráriodias de escola primária,
quando você tinha quatro operadores e dois níveis de precedência, as coisas são um pouco
maiscomplexo com C #, que tem mais de 45 operadores e 14 níveis de precedência.A Tabela 8-4 mostra a
lista completa de operadores e suas precedências. A tabela lista as maioresoperadores de precedência
na parte superior e continua com os operadores de precedência mais baixa na parte inferior.Tabela 8-4.
Precedência do operador: da mais alta para a mais baixaCategoriaOperadoresPrimárioax, f (x ), a [x] , x +
+ , x-- , novo , tipo de , verificado , não verificadoUnário+ , - , ! , ~ , ++ x , --x , (T) xMultiplicativo* , / ,
%Aditivo+ , -Mudança<< , >>Relacional e tipo< , > , <= , > = , é , comoIgualdade== , ! =E lógicoEXOR
lógico^

Página 240

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES210CategoriaOperadoresOR lógico|Condicional


E&&Condicional OU||Condicional?:Tarefa= , * = , / = , % = , + = , - = , << = , >> = , & = , ^ = , |
=AssociatividadeSe todos os operadores em uma expressão têm diferentes níveis de precedência, avalie
cadasubexpressão, começando naquele com o nível mais alto, e trabalhe para baixo na escala de
precedência.Mas e se dois operadores sequenciais tiverem o mesmo nível de precedência? Por exemplo,
dado oexpressão 2/6 * 4, existem duas sequências de avaliação possíveis:(2/6) * 4 = 4/3ou2 / (6 * 4) =
1/12Quando os operadores sequenciais têm o mesmo nível de precedência, a ordem de avaliação é
determinadapor associatividade do operador. Ou seja, dados dois operadores do mesmo nível de
precedência, um ou outroterá precedência, dependendo da associatividade dos operadores. Algumas
características importantes dea associatividade do operador é a seguinte e está resumida na Tabela 8-
5:•Os operadores associativos à esquerda são avaliados da esquerda para a direita.•Os operadores
associativos à direita são avaliados da direita para a esquerda.•Os operadores binários, exceto os
operadores de atribuição, são associativos à esquerda.•Os operadores de atribuição e o operador
condicional são associativos à direita.Portanto, dadas essas regras, a expressão do exemplo anterior deve
ser agrupada da esquerda para a direita,dando (2/6) * 4, que resulta em 4/3.

Página 241

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES211Tabela 8-5. Resumo da Associatividade do OperadorTipo


de OperadorAssociatividadeOperadores de atribuiçãoAssociativo à direitaOutros operadores
bináriosAssociativo à esquerdaO operador condicional Associativo à direitaVocê pode definir
explicitamente a ordem de avaliação das subexpressões de uma expressão usandoparênteses. As
subexpressões entre parênteses fazem o seguinte:•Substituir as regras de precedência e
associatividade•São avaliados em ordem do conjunto aninhado mais interno para o mais externo

Página 242

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES212Operadores aritméticos simplesOs operadores


aritméticos simples realizam as quatro operações aritméticas básicas e estão listados na Tabela 8-6.Esses
operadores são binários e associativos à esquerda.Tabela 8-6. Os operadores aritméticos
simplesOperadorNomeDescrição+AdiçãoAdiciona os dois operandos.-SubtraçãoSubtrai o segundo
operando do primeiro.*Multiplicação Multiplica os dois operandos./DivisãoDivide o primeiro operando
pelo segundo. Rodadas de divisão inteirao resultado em direção a 0 para o inteiro mais próximo.Os
operadores aritméticos realizam as operações aritméticas padrão em todas as operações simples
predefinidastipos aritméticos.A seguir estão exemplos de operadores aritméticos simples:int x1 = 5 +
6;d1 duplo = 5,0 + 6,0;int x2 = 12 - 3;d2 duplo = 12,0 - 3,0;int x3 = 3 * 4;duplo d3 = 3,0 * 4,0;int x4 =
10/3;duplo d4 = 10,0 / 3,0;byte b1 = 5 + 6;sbyte sb1 = 6 * 5;Baixe a partir de Wow! e-book
<www.wowebook.com>

Página 243

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES213O operador restanteO operador restante (%) divide o


primeiro operando pelo segundo operando, ignora o quociente eretorna o resto. A Tabela 8-7 fornece
sua descrição.O operador restante é binário e associativo à esquerda.Tabela 8-7. O operador
restanteOperadorNomeDescrição%RestanteDivide o primeiro operando pelo segundo operando
eretorna o restoAs linhas a seguir mostram exemplos do operador de resto inteiro:•0% 3 = 0, porque 0
dividido por 3 é 0 com um resto de 0.•1% 3 = 1, porque 1 dividido por 3 é 0 com um resto de 1.•2% 3 =
2, porque 2 dividido por 3 é 0 com um restante de 2.•3% 3 = 0, porque 3 dividido por 3 é 1 com um resto
de 0.•4% 3 = 1, porque 4 dividido por 3 é 1 com um resto de 1.O operador de resto também pode ser
usado com números reais para fornecer resíduos reais .Console.WriteLine ("0,0f% 1.5f é {0}", 0,0f%
1.5f);Console.WriteLine ("0,5f% 1.5f é {0}", 0,5f% 1.5f);Console.WriteLine ("1.0f% 1.5f é {0}", 1.0f%
1.5f);Console.WriteLine ("1.5f% 1.5f é {0}", 1.5f% 1.5f);Console.WriteLine ("2.0f% 1.5f é {0}", 2.0f%
1.5f);Console.WriteLine ("2,5f% 1.5f é {0}", 2,5f% 1.5f);Este código produz a seguinte saída:0,0f% 1.5f é
0// 0,0 / 1,5 = 0 resto 00,5f% 1,5f é 0,5// 0,5 / 1,5 = 0 resto, 51.0f% 1.5f é 1// 1.0 / 1.5 = 0 resto 11.5f%
1.5f é 0// 1,5 / 1,5 = 1 resto 02.0f% 1.5f é 0,5// 2,0 / 1,5 = 1 resto 0,52,5f% 1.5f é 1// 2,5 / 1,5 = 1 resto 1

Página 244

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES214Operadores de comparação relacional e de igualdadeOs


operadores de comparação relacional e de igualdade são operadores binários que comparam seus
operandos eretorna um valor do tipo bool. A Tabela 8-8 lista esses operadores.Os operadores relacionais
e de igualdade são binários e associativos à esquerda.Tabela 8-8. Os operadores de comparação
relacional e de igualdadeOperadorNomeDescrição<Menor queverdadeiro se o primeiro operando for
menor que o segundo operando; falsode outra forma>Maior queverdadeiro se o primeiro operando for
maior que o segundo operando; falsode outra forma<=Menos que ou igual averdadeiro se o primeiro
operando for menor ou igual ao segundo operando;caso contrário, falso> =Melhor que ou igual
averdadeiro se o primeiro operando for maior ou igual ao segundooperando; caso contrário, falso==Igual
averdadeiro se o primeiro operando for igual ao segundo operando; falsode outra forma! =Diferente
deverdadeiro se o primeiro operando não for igual ao segundo operando; falsode outra formaUma
expressão binária com um operador relacional ou de igualdade retorna um valor do tipo bool.■ Nota Ao
contrário de C e C ++, os números em C # não têm uma interpretação booleana.int x = 5;if (x)// Errado. x
é do tipo int, não do tipo booleano....if (x == 5) // Bom, já que a expressão retorna um valor do tipo
booleano...Quando impressos, os valores booleanos true e false são representados pelos valores de
saída da string Truee falso.int x = 5, y = 4;Console.WriteLine ("x == x é {0}", x == x);Console.WriteLine ("x
== y é {0}", x == y);

Página 245

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES215A saída deste código é a seguinte:x == x é verdadeirox ==


y é falsoOperações de comparação e igualdadeAo comparar a maioria dos tipos de referência para
igualdade, apenas as referências são comparadas.•Se as referências são iguais, ou seja, se apontam para
o mesmo objeto na memória, a igualdadea comparação é verdadeira; caso contrário, é falso, mesmo que
os dois objetos separados na memória sejam exatamenteequivalente em todos os outros aspectos.•Isso
é chamado de comparação superficial .A Figura 8-4 ilustra a comparação dos tipos de referência.•À
esquerda da figura, as referências de a e b são as mesmas, então uma comparação seriaretornar
verdadeiro.•À direita da figura, as referências não são as mesmas, mesmo que o conteúdo das duasOs
objetos AClass eram exatamente os mesmos, a comparação retornaria falso.Figura 8-4. Comparando
tipos de referência para igualdade

Página 246

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES216Objetos do tipo string também são tipos de referência,


mas são comparados de forma diferente. Quando as cordas sãocomparados por igualdade, eles são
comparados em comprimento e conteúdo com distinção entre maiúsculas e minúsculas.•Se duas strings
têm o mesmo comprimento e o mesmo conteúdo que diferencia maiúsculas de minúsculas, a
comparação de igualdaderetorna verdadeiro, mesmo que ocupem áreas diferentes da memória.•Isso é
chamado de comparação profunda .Delegados, que são abordados no Capítulo 15, também são tipos de
referência e também usam comparação profunda.Quando os delegados são comparados quanto à
igualdade, a comparação retorna verdadeiro se ambos os delegados forem nulos ou seambos têm o
mesmo número de membros em suas listas de invocação e as listas de invocação correspondem.Ao
comparar expressões numéricas, os tipos e valores são comparados. Ao comparar enumtipos, as
comparações são feitas nos valores subjacentes dos operandos. Enums são cobertos porCapítulo 13.

Página 247

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES217Operadores de incremento e decrementoO operador de


incremento adiciona 1 ao operando. O operador de decremento subtrai 1 do operando.A Tabela 8-9 lista
os operadores e suas descrições.Esses operadores são unários e possuem duas formas, a pré -forma e a
pós -forma, que atuamde forma diferente.•Na pré-forma, o operador é colocado antes do operando; por
exemplo, ++ x e --y.•Na pós-forma, o operador é colocado após o operando; por exemplo, x ++ e
y--.Tabela 8-9. Os operadores de incremento e decrementoOperadorNomeDescrição++Pré-incremento +
+ varAumente o valor da variável em 1 e salve-o de volta ema variável. Retorne o novo valor da
variável.Var ++ pós-incrementoAumente o valor da variável em 1 e salve-o de volta ema variável. Retorna
o valor antigo da variável antes de serincrementado.-Pré-decremento --varDiminua o valor da variável
em 1 e salve-o de volta ema variável. Retorne o novo valor da variável.Var pós-decremento -Diminua o
valor da variável em 1 e salve-o de volta ema variável. Retorna o valor antigo da variável antes de
serdiminuiu.Ao comparar as formas pré e pós dos operadores•O valor final armazenado da variável
operando após a execução da instrução é o mesmoindependentemente se a forma pré ou pós do
operador é usada.•A única diferença é o valor retornado pelo operador para a expressão.A Tabela 8-10
mostra um exemplo que resume o comportamento.

Página 248
CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES218Tabela 8-10. Comportamento dos operadores de pré e
pós-incremento e decrementoExpressão: x = 10Valor devolvido aoExpressãoValor da VariávelApós
avaliaçãoPré-incremento++ x1111Pós-incrementox ++1011Pré-decremento--x99Pós-decremento x--
109Por exemplo, o seguinte é uma demonstração simples das quatro versões diferentes dos
operadores.Para mostrar os diferentes resultados na mesma entrada, o valor do operando x é redefinido
para 5 antes de cadadeclaração de atribuição.int x = 5, y;y = x ++; // resultado: y: 5, x: 6Console.WriteLine
("y: {0}, x: {1}", y, x);x = 5;y = ++ x; // resultado: y: 6, x: 6Console.WriteLine ("y: {0}, x: {1}", y, x);x = 5;y =
x--; // resultado: y: 5, x: 4Console.WriteLine ("y: {0}, x: {1}", y, x);x = 5;y = --x; // resultado: y: 4, x:
4Console.WriteLine ("y: {0}, x: {1}", y, x);Este código produz a seguinte saída:y: 5, x: 6y: 6, x: 6y: 5, x: 4y:
4, x: 4

Página 249

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES219Operadores lógicos condicionaisOs operadores lógicos


são usados para comparar ou negar os valores lógicos de seus operandos eretornando o valor lógico
resultante. A Tabela 8-11 lista os operadores.Os operadores lógicos AND e OR lógico são binários e
associativos à esquerda. O NÃO lógico é unário.Tabela 8-11. Os operadores lógicos
condicionaisOperadorNomeDescrição&&E lógicotrue se ambos os operandos forem verdadeiros; caso
contrário, falso||OR lógicoverdadeiro se pelo menos um operando for verdadeiro; caso contrário, falso!
NÃO Lógicoverdadeiro se o operando for falso; caso contrário, falsoA sintaxe para esses operadores é a
seguinte, onde Expr1 e Expr2 avaliam os valores booleanos:Expr1 && Expr2Expr1 || Expr2! Expr

Página 250

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES220A seguir estão alguns exemplos:bool bVal;bVal = (1 == 1)


&& (2 == 2); // Verdadeiro, ambas as expressões de operando são verdadeirasbVal = (1 == 1) && (1 == 2);
// Falso, a expressão do segundo operando é falsabVal = (1 == 1) || (2 == 2); // Verdadeiro, ambas as
expressões de operando são verdadeirasbVal = (1 == 1) || (1 == 2); // Verdadeiro, a expressão do
primeiro operando é verdadeirabVal = (1 == 2) || (2 == 3); // False, ambas as expressões de operando
são falsasbVal = verdadeiro;// Defina bVal como verdadeiro.bVal =! bVal;// bVal agora é falso.Os
operadores lógicos condicionais operam em modo de "curto-circuito", o que significa que, se após
avaliarExpr1 o resultado já pode ser determinado, então ele ignora a avaliação de Expr2 . O seguinte
códigomostra exemplos de expressões em que o valor pode ser determinado após avaliar o primeiro
operando:bool bVal;bVal = (1 == 2) && (2 == 2); // False, após avaliar a primeira expressãobVal = (1 == 1)
|| (1 == 2); // Verdadeiro, após avaliar a primeira expressãoPor causa do comportamento de curto-
circuito, não coloque expressões com efeitos colaterais (como alterar umvalor) em Expr2 , uma vez que
podem não ser avaliados. No código a seguir, o pós-incremento da variáveliVal não seria executado, pois
após executar a primeira subexpressão, pode-se determinar queo valor de toda a expressão é falso.bool
bVal; int iVal = 10;bVal = (1 == 2) && (9 == iVal ++); // resultado: bVal = False, iVal = 10↑↑FalsoNunca
avaliado

Página 251
CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES221Operadores lógicosOs operadores lógicos bit a bit são
freqüentemente usados para definir os padrões de bits de parâmetros para métodos. Tabela 8-12lista os
operadores lógicos bit a bit.Esses operadores, exceto para negação bit a bit, são binários e associativos à
esquerda. A negação bit a bitoperador é unário.Tabela 8-12. Os operadores lógicosNome do
operadorDescriçãoEE bit a bitProduz o AND bit a bit dos dois operandos. O bit resultante é 1somente se
ambos os bits de operando forem 1.|OR bit a bitProduz o OR bit a bit dos dois operandos. O bit
resultante é 1 sequalquer um dos bits de operando é 1.^XOR bit a bitProduz o XOR bit a bit dos dois
operandos. O bit resultante é 1apenas se um, mas não ambos, bits de operando forem 1.~Bit a
bitnegaçãoCada bit do operando é alternado para seu oposto. Isso produz oum complemento do
operando.Os operadores binários bit a bit comparam os bits correspondentes em cada posição em cada
um de seus doisoperandos, e eles definem o bit no valor de retorno de acordo com a operação lógica.

Página 252

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES222A Figura 8-5 mostra quatro exemplos de operações


lógicas bit a bit.Figura 8-5 . Exemplos de operadores lógicos bit a bitO código a seguir implementa os
exemplos anteriores:byte const x = 12, y = 10;sbyte a;a = x & y;// a = 8a = x | y;// a = 14a = x ^ y;// a = 6a
= ~ x;// a = -13Baixe a partir de Wow! e-book <www.wowebook.com>

Página 253

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES223Operadores de turnoOs operadores de deslocamento bit


a bit deslocam o padrão de bits tanto para a direita quanto para a esquerda um número especificado de
posições, comos bits vagos preenchidos com 0s ou 1s. A Tabela 8-13 lista os operadores de turno.Os
operadores de deslocamento são binários e associativos à esquerda. A sintaxe dos operadores de
deslocamento bit a bit é mostradaaqui. O número de posições a serem deslocadas é dado por Count
.Operando << Contagem// Desvio à esquerdaOperando >> Contagem// Deslocamento para a
direitaTabela 8-13 . Os Operadores de TurnoNome do operadorDescrição<<Desvio à esquerdaMuda o
padrão de bits para a esquerda pelo número fornecido de posições. Os bits mudaramna extremidade
esquerda estão perdidos. As posições de bits que se abrem à direita são preenchidascom
0s.>>Deslocamento para a direitaMuda o padrão de bits para o número determinado de posições. Bits
deslocadoso lado direito está perdido.Para a grande maioria da programação em C #, você não precisa
saber nada sobre o hardwarepor baixo. Se você estiver fazendo manipulação bit a bit de números com
sinais, no entanto, pode ser útil sabersobre a representação numérica. O hardware subjacente
representa números binários assinados em umforma chamada complemento de dois . Na representação
de complemento de dois, os números positivos têm seusforma binária normal. Para negar um número,
você pega a negação bit a bit do número e adiciona 1 a ele.Este processo transforma um número
positivo em sua representação negativa e vice-versa. Em doiscomplemento, todos os números negativos
têm um 1 na posição do bit mais à esquerda. A Figura 8-6 mostra a negação deo número 12.Figura 8-6.
Para obter a negação de um número de complemento de dois, pegue sua negação bit a bit e adicione
1.A representação subjacente é importante ao deslocar os números assinados porque o resultado
dedeslocar um valor integral um bit para a esquerda é o mesmo que multiplicá-lo por dois. Deslocar para
a direita éo mesmo que dividi-lo por dois.Se, no entanto, você deslocasse um número negativo para a
direita e o bit mais à esquerda fossepreenchido com um 0, ele produziria o resultado errado. O 0 na
posição mais à esquerda indicaria umnúmero positivo. Mas isso é incorreto, porque dividir um número
negativo por 2 não produz umnúmero positivo.

Página 254

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES224Para resolver esta situação, quando o operando é um


inteiro com sinal, se o bit mais à esquerda do operando for um1 (indicando um número negativo), as
posições dos bits que se abrem à esquerda são preenchidas com 1s em vez de 0s.Isso mantém a
representação correta de complemento de dois. Para números positivos ou sem sinal, bitas posições que
se abrem à esquerda são preenchidas com 0s.A Figura 8-7 mostra como a expressão 14 << 3 seria
avaliada em um byte. Esta operação causaOs seguintes:•Cada um dos bits no operando (14) é deslocado
três lugares para a esquerda.•As três posições de bit vagas na extremidade direita são preenchidas com
0s.•O valor resultante é 112.Figura 8-7. Exemplo de deslocamento à esquerda de três bitsA Figura 8-8
ilustra as operações de deslocamento bit a bit.Figura 8-8 . Mudanças bit a bitO código a seguir
implementa os exemplos anteriores:int a, b, x = 14;a = x << 3;// Shift leftb = x >> 3;// Shift
rightConsole.WriteLine ("{0} << 3 = {1}", x, a);Console.WriteLine ("{0} >> 3 = {1}", x, b);Este código produz
a seguinte saída:14 << 3 = 11214 >> 3 = 1

Página 255

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES225Operadores de atribuiçãoOs operadores de atribuição


avaliam a expressão no lado direito do operador e usam esse valor paradefina a expressão da variável no
lado esquerdo do operador. A Tabela 8-14 lista os operadores de atribuição.Os operadores de atribuição
são binários e associativos à direita.Tabela 8-14. Os operadores de atribuiçãoDescrição do
operador=Atribuição simples; avalie a expressão à direita e atribua o valor retornado aa variável ou
expressão à esquerda.* =Atribuição de compostos; var * = expr é igual a var = var * ( expr )./ =Atribuição
de compostos; var / = expr é igual a var = var / ( expr ).% =Atribuição de compostos; var % = expr é igual a
var = var % ( expr ).+ =Atribuição de compostos; var + = expr é igual a var = var + ( expr ).- =Atribuição de
compostos; var - = expr é igual a var = var - ( expr ).<< =Atribuição de compostos; var << = expr é igual a
var = var << ( expr ).>> =Atribuição de compostos; var >> = expr é igual a var = var >> ( expr ).&
=Atribuição de compostos; var & = expr é igual a var = var & ( expr ).^ =Atribuição de compostos; var ^ =
expr é igual a var = var ^ ( expr ).| =Atribuição de compostos; var | = expr é igual a var = var | ( expr ).A
sintaxe é a seguinte:Expressão do operador VariableExpression

Página 256

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES226Para atribuição simples, a expressão à direita do


operador é avaliada e seu valor éatribuído à variável à esquerda.int x;x = 5;x = y * z;Os tipos de objetos
que podem estar no lado esquerdo de um operador de atribuição são os seguintes. Elessão discutidos
mais adiante no texto.•Variáveis (variáveis locais, campos,
parâmetros)•Propriedades•Indexadores•EventosAtribuição CompostoFreqüentemente, você vai querer
avaliar uma expressão e adicionar os resultados ao valor atual de uma variável, comomostrado aqui:x = x
+ expr;Os operadores de atribuição compostos permitem um método abreviado para evitar a repetição
dovariável do lado esquerdo do lado direito em certas circunstâncias comuns. Por exemplo, os dois
seguintesdeclarações são semanticamente equivalentes, mas a segunda é mais curta e tão fácil de
entender.x = x + (y - z);x + = y - z;As outras instruções de atribuição compostas são análogas:Observe os
parênteses.↓ ↓x * = y - z; // Equivalente a x = x * ( y - z )x / = y - z; // Equivalente a x = x / (y - z)...

Página 257

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES227O operador condicionalO operador condicional é uma


maneira poderosa e sucinta de retornar um de dois valores, com base noresultado de uma condição. A
Tabela 8-15 mostra o operador.O operador condicional é ternário.Tabela 8-15 . O operador
condicionalOperadorNomeDescrição? :Operador condicional Avalia uma expressão e retorna um de dois
valores,dependendo se a expressão retorna verdadeiro ou falsoA sintaxe do operador condicional é
mostrada a seguir. Tem uma expressão de teste e dois resultadosexpressões.•A condição deve retornar
um valor do tipo bool.•Se a Condição for avaliada como verdadeira, a Expressão1 será avaliada e
retornada. De outra forma,Expression2 é avaliada e retornada.Condição ? Expression1 : Expression2O
operador condicional pode ser comparado com a construção if ... else. Por exemplo, o seguinteA
construção if ... else verifica uma condição e, se a condição for verdadeira, atribui 5 à variável intVar.Caso
contrário, ele atribui o valor 10.if (x <y)// if ... elseintVar = 5;outrointVar = 10;O operador condicional
pode realizar a mesma operação de uma forma menos detalhada, conforme mostrado nodeclaração
seguinte:intVar = x <y? 5: 10;// Operador condicionalColocar a condição e cada expressão de retorno em
linhas separadas, como no código a seguir, tornaa intenção muito fácil de entender.intVar = x <y? 5: 10;

Página 258

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES228A Figura 8-9 compara as duas formas mostradas no


exemplo.Figura 8-9. O operador condicional versus if ... elsePor exemplo, o código a seguir usa o
operador condicional três vezes - uma vez em cada um dosDeclarações WriteLine. Na primeira instância,
ele retorna o valor de x ou o valor de y. No segundoduas instâncias, ele retorna a string vazia ou a string
“não”.int x = 10, y = 9;int highVal = x> y// Doença? x// Expressão 1: y;// Expressão 2Console.WriteLine
("highVal: {0} \ n", highVal);Console.WriteLine ("x é {0} maior que y",x> y// Doença? ""// Expressão 1: "
não" );// Expressão 2y = 11;Console.WriteLine ("x é {0} maior que y",x> y// Doença? ""// Expressão 1: "
não" );// Expressão 2Este código produz a seguinte saída:highVal: 10x é maior que yx não é maior que
y■ Nota O if ... else declaração é um fluxo de controle declaração . Deve ser usado para fazer um ou
outrode duas ações. O operador condicional retorna uma expressão. Deve ser usado para devolver um
ou outrodois valores .

Página 259

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES229Operadores aritméticos unáriosOs operadores unários


definem o sinal de um valor numérico. Eles estão listados na Tabela 8-16.•O operador positivo unário
simplesmente retorna o valor do operando.•O operador negativo unário retorna o valor do operando
subtraído de 0.Tabela 8-16. Os Operadores UnáriosNome do operadorDescrição+Sinal positivoRetorna o
valor numérico do operando-Sinal negativoRetorna o valor numérico do operando subtraído de 0Por
exemplo, o código a seguir mostra o uso e os resultados dos operadores:int x = +10;// x = 10int y = -x;// y
= -10int z = -y;// z = 10

Página 260

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES230Conversões de tipo definidas pelo usuárioAs conversões


definidas pelo usuário são discutidas em mais detalhes no Capítulo 18, mas vou mencioná-las aqui
comobem porque eles são operadores.•Você pode definir conversões implícitas e explícitas para suas
próprias classes e estruturas. estepermite converter um objeto de seu tipo definido pelo usuário em
algum outro tipo e vice-versa.•C # fornece conversões implícitas e explícitas.- Com uma conversão
implícita , o compilador faz a conversão automaticamente, se necessário,quando está resolvendo quais
tipos usar em um determinado contexto.- Com uma conversão explícita , o compilador fará a conversão
apenas quando um elenco explícitooperador é usado.A sintaxe para declarar uma conversão implícita é a
seguinte. Os modificadores públicos e estáticos sãonecessário para todas as conversões definidas pelo
usuário.RequeridosAlvoFonte↓↓↓operador implícito estático público TargetType ( SourceType Identifier )
{...return ObjectOfTargetType ;}A sintaxe para a conversão explícita é a mesma, exceto que explícito é
substituído por implícito.O código a seguir mostra um exemplo de declarações para operadores de
conversão que converterão umobjeto do tipo LimitedInt para digitar int e vice-versa.classe
LimitedIntAlvoFonte{↓↓operador implícito estático público int (LimitedInt li) // LimitedInt to int{return
li.TheValue;}Fonte Alvo↓↓operador implícito estático público LimitedInt (int x) // int para
LimitedInt{LimitedInt li = novo LimitedInt ();li.TheValue = x;return li;}private int _theValue = 0;public int
TheValue {...}}Por exemplo, o código a seguir reitera e usa os dois operadores de conversão de tipo
recém-definidos.Em Main, um literal int é convertido em um objeto LimitedInt e, na linha seguinte, um
objeto LimitedInt éconvertido em um int.

Página 261

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES231classe LimitedInt{const int MaxValue = 100;const int


MinValue = 0;operador implícito estático público int (LimitedInt li) // Converter tipo{return
li.TheValue;}operador implícito estático público LimitedInt (int x)// Converter tipo{LimitedInt li = novo
LimitedInt ();li.TheValue = x;return li;}private int _theValue = 0;public int TheValue// Property{get {return
_theValue; }conjunto{if (valor <MinValue)_theValue = 0;outro_theValue = value> MaxValue? Valor
máximo: valor;}}}programa de aula{static void Main ()// A Principal{LimitedInt li = 500;// Converter 500
em LimitedIntvalor int = li;// Converter LimitedInt em intConsole.WriteLine ("li: {0}, valor: {1}",
li.TheValue, valor);}}Este código produz a seguinte saída:li: 100, valor: 100

Página 262

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES232Conversão explícita e o operador de elencoO código de


exemplo anterior mostrou a conversão implícita do int em um tipo LimitedInt e oconversão implícita de
um tipo LimitedInt em um int. Se, no entanto, você declarou as duas conversõesoperadores como
explícitos, você teria que usar explicitamente os operadores de conversão ao fazer as conversões.Um
operador de conversão consiste no nome do tipo para o qual você deseja converter a expressão,
dentroum conjunto de parênteses. Por exemplo, no código a seguir, o método Main converte o valor 500
em umObjeto LimitedInt.Operador de elenco↓LimitedInt li = (LimitedInt) 500;Por exemplo, aqui está a
parte relevante do código, com as alterações marcadas:↓operador explícito estático público int
(LimitedInt li){return li.TheValue;}↓operador público estático explícito LimitedInt (int x){LimitedInt li =
novo LimitedInt ();li.TheValue = x;return li;}static void Main (){↓LimitedInt li = (LimitedInt) 500;valor int =
(int) li;↑Console.WriteLine ("li: {0}, valor: {1}", li.TheValue, valor);}Em ambas as versões do código, a saída
é a seguinte:li: 100, valor: 100Existem dois outros operadores que pegam um valor de um tipo e
retornam um valor diferente,tipo especificado. Estes são o operador is e o operador as. Eles são
abordados no final do Capítulo 18.Baixe a partir de Wow! e-book <www.wowebook.com>

Página 263

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES233Sobrecarga do operadorOs operadores C #, como você


viu, são definidos para trabalhar usando os tipos predefinidos como operandos. E seconfrontado com
um tipo definido pelo usuário, um operador simplesmente não saberia como processá-lo.
Operadorsobrecarga permite que você defina como os operadores C # devem operar nos operandos de
seu usuáriotipos definidos.•A sobrecarga do operador está disponível apenas para classes e
estruturas.•Você pode sobrecarregar um operador x para usar com sua classe ou estrutura, declarando
um método chamadooperador x que implementa o comportamento (por exemplo, operador +, operador
- e assim por diante).- Os métodos de sobrecarga para operadores unários usam um único parâmetro da
classe outipo de estrutura.- Os métodos de sobrecarga para operadores binários usam dois parâmetros,
pelo menos um dos quais deveser do tipo classe ou estrutura.operador public static LimitedInt -
(LimitedInt x)// Unáriooperador public static LimitedInt + (LimitedInt x, double y) // BinárioA declaração
de um método de sobrecarga do operador requer o seguinte:•A declaração deve usar os modificadores
estáticos e públicos.•O operador deve ser membro da classe ou estrutura da qual é um operador.Por
exemplo, o código a seguir mostra dois dos operadores sobrecarregados da classe LimitedInt: ooperador
de adição e o operador de negação. Você pode dizer que é negação e não subtração porqueo método de
sobrecarga do operador tem apenas um único parâmetro e, portanto, é unário; Considerando que
aoperador de subtração é binário.classe LimitedInt Return{Requeridostipo Operador de palavra-
chaveOperando↓↓↓ ↓↓operador public static LimitedInt + (LimitedInt x, double y){LimitedInt li = novo
LimitedInt ();li.TheValue = x.TheValue + (int) y;return li;}operador public static LimitedInt - (LimitedInt x)
{// Nessa classe estranha, negar um valor apenas define seu valor como 0.LimitedInt li = novo LimitedInt
();li.TheValue = 0;return li;}...}

Página 264

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES234Restrições à sobrecarga do operadorNem todos os


operadores podem ser sobrecarregados e existem restrições aos tipos de sobrecarga que podem
serfeito. As coisas importantes que você deve saber sobre as restrições à sobrecarga do operador
sãodescrito mais adiante nesta seção.Apenas os seguintes operadores podem ser sobrecarregados. Em
destaque na lista está ooperador de atribuição.Operadores unários sobrecarregáveis : +, -,!, ~, ++, -,
verdadeiro, falsoOperadores binários sobrecarregáveis: +, -, *, /,%, &, |, ^, <<, >>, ==,! =,>, <,> =, <=Os
operadores de incremento e decremento são sobrecarregáveis. Mas, ao contrário das versões
predefinidas, hánão há distinção entre o pré e pós-uso do operador sobrecarregado.Você não pode fazer
o seguinte com sobrecarga de operador:•Crie um novo operador•Alterar a sintaxe de um
operador•Redefina como um operador funciona nos tipos predefinidos•Altere a precedência ou
associatividade de um operador■ Observação Seus operadores sobrecarregados devem estar de acordo
com os significados intuitivos dos operadores.

Página 265

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES235Exemplo de sobrecarga do operadorO exemplo a seguir


mostra as sobrecargas de três operadores para a classe LimitedInt: negação,subtração e adição.class
LimitedInt {const int MaxValue = 100;const int MinValue = 0;operador public static LimitedInt -
(LimitedInt x){// Nessa classe estranha, negar um valor apenas define seu valor como 0.LimitedInt li =
novo LimitedInt ();li.TheValue = 0;return li;}operador public static LimitedInt - (LimitedInt x, LimitedInt y)
{LimitedInt li = novo LimitedInt ();li.TheValue = x.TheValue - y.TheValue;return li;}operador public static
LimitedInt + (LimitedInt x, double y){LimitedInt li = novo LimitedInt ();li.TheValue = x.TheValue + (int)
y;return li;}private int _theValue = 0;public int TheValue{get {return _theValue; }conjunto{if (valor
<MinValue)_theValue = 0;outro_theValue = value> MaxValue? Valor máximo: valor;}}}

Página 266

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES236class Program {static void Main () {LimitedInt li1 = novo
LimitedInt ();LimitedInt li2 = novo LimitedInt ();LimitedInt li3 = novo LimitedInt ();li1.TheValue = 10;
li2.TheValue = 26;Console.WriteLine ("li1: {0}, li2: {1}", li1.TheValue, li2.TheValue);li3 =
-li1;Console.WriteLine ("- {0} = {1}", li1.TheValue, li3.TheValue);li3 = li2 - li1;Console.WriteLine ("{0} - {1} =
{2}",li2.TheValue, li1.TheValue, li3.TheValue);li3 = li1 - li2;Console.WriteLine ("{0} - {1} = {2}",li1.TheValue,
li2.TheValue, li3.TheValue);}}Este código produz a seguinte saída:li1: 10, li2: 26-10 = 026 - 10 = 1610 - 26
= 0O tipo de operadorO operador typeof retorna o objeto System.Type de qualquer tipo fornecido como
seu parâmetro. Deste objeto,você pode aprender as características do tipo. (Existe apenas um objeto
System.Type para qualquer tipo.)Você não pode sobrecarregar o operador typeof. A Tabela 8-17 lista as
características do operador.O operador typeof é unário.Tabela 8-17. O tipo de
operadorOperadorDescriçãotipo deRetorna o objeto System.Type de um determinado tipo

Página 267

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES237A seguir está um exemplo da sintaxe do operador typeof.


Tipo é uma classe no Sistemanamespace.Digite t = typeof (SomeClass)Por exemplo, o código a seguir usa
o operador typeof para obter informações sobre uma classe chamadaSomeClass e para imprimir os
nomes de seus campos e métodos públicos.using System.Reflection; // Use o namespace Reflection para
aproveitar ao máximo// para determinar informações sobre um tipo.classe SomeClass{public int
Field1;public int Field2;public void Method1 () {}public int Method2 () {return 1; }}programa de
aula{static void Main (){Digite t = typeof (SomeClass);FieldInfo [] fi = t.GetFields ();MethodInfo [] mi =
t.GetMethods ();foreach (FieldInfo f in fi)Console.WriteLine ("Campo: {0}", f.Nome);foreach (MethodInfo
m em mi)Console.WriteLine ("Método: {0}", m.Name);}}

Página 268

CAPÍTULO 8 ■ EXPRESSÕES E OPERADORES238A saída deste código é a seguinte:Campo:


Campo1Campo: Campo2Método: Método1Método: Método 2Método: ToStringMétodo: igualMétodo:
GetHashCodeMétodo: GetTypeO operador typeof também é chamado pelo método GetType, que está
disponível para cada objeto de cadatipo. Por exemplo, o código a seguir recupera o nome do tipo do
objeto:classe SomeClass{...}programa de aula{static void Main (){SomeClass s = new SomeClass
();Console.WriteLine ("Tipo s: {0}", s.GetType (). Nome);}}Este código produz a seguinte saída:Tipo s:
SomeClassOutros OperadoresOs operadores abordados neste capítulo são os operadores padrão para os
tipos integrados. Ha outrooperadores de uso especial que serão tratados posteriormente neste livro,
junto com seus tipos de operando. Paraexemplo, os tipos anuláveis têm um operador especial
denominado operador de coalescência nulo, que édescrito no Capítulo 25 junto com uma descrição mais
detalhada dos tipos anuláveis.

Página 269

CAPÍTULO 9■ ■ ■239Afirmações■ O que são declarações?■ Declarações de expressão■ Declarações


de fluxo de controle■ A declaração if■ O if. . . outra declaração■ A declaração switch■ O loop while■
The do Loop■ O for Loop■ Declarações de salto■ A declaração de quebra■ A declaração continue■
Declarações etiquetadas■ A declaração goto■ A declaração de uso■ Outras declarações

Página 270

CAPÍTULO 9 ■ DECLARAÇÕES240O que são declarações?As instruções em C # são muito semelhantes às


de C e C ++. Este capítulo cobre as características de umInstrução C #, bem como as instruções de fluxo
de controle fornecidas pela linguagem.•Uma declaração é uma instrução de código-fonte que descreve
um tipo ou diz ao programa para executaruma ação.•Existem três categorias principais de afirmações:-
Declarações de declaração: declarações que declaram tipos ou variáveis- Declarações incorporadas:
declarações que executam ações ou gerenciam o fluxo de controle- Declarações rotuladas: declarações
para as quais o controle pode saltarOs capítulos anteriores cobriram uma série de declarações de
declarações diferentes, incluindodeclarações de variáveis locais, classes e membros da classe. Este
capítulo cobre oinstruções, que não declaram tipos, variáveis ou instâncias. Em vez disso, eles usam
expressões econstruções de fluxo de controle para trabalhar com os objetos e variáveis que foram
declarados pelodeclarações de declaração.•Uma instrução simples consiste em uma expressão seguida
por um ponto e vírgula.•Um bloco é uma sequência de instruções delimitadas por chaves
correspondentes. As declarações fechadaspode incluir o seguinte:- Declarações de declaração-
Declarações embutidas- Declarações rotuladas- Blocos aninhadosO código a seguir fornece exemplos de
cada um:int x = 10;// Declaração simplesint z;// Declaração simples{// Quadraint y = 20;// Declaração
simplesz = x + y;// Instrução incorporadatopo: y = 30;// Declaração rotulada...{// Bloco aninhado...}}

Página 271
CAPÍTULO 9 ■ DECLARAÇÕES241■ Nota Um bloco conta sintaticamente como uma única instrução
incorporada. Em qualquer lugar que uma declaração embutida estejanecessário sintaticamente, você
pode usar um bloco.Uma instrução vazia consiste em apenas um ponto e vírgula. Você pode usar uma
declaração vazia em qualquer posiçãoonde a sintaxe da linguagem requer uma instrução incorporada,
mas a lógica do seu programa nãorequer qualquer ação.Por exemplo, o código a seguir é um exemplo de
como usar a instrução vazia:•A segunda linha do código é uma instrução vazia. É necessário porque deve
haver umdeclaração embutida entre a parte if e a parte else da construção.•A quarta linha é uma
instrução simples, conforme mostrado pelo ponto-e-vírgula final.if (x <y);// Declaração vaziaoutroz = a +
b;// Declaração simplesDeclarações de expressãoO capítulo anterior examinou expressões. Expressões
retornar valores, mas eles também podem ter sideefeitos .•Um efeito colateral é uma ação que afeta o
estado do programa.•Muitas expressões são avaliadas apenas por seus efeitos colaterais.Você pode criar
uma instrução a partir de uma expressão, colocando um terminador de instrução (ponto-e-vírgula)
apósisto. Qualquer valor retornado pela expressão é descartado. Por exemplo, o código a seguir mostra
umdeclaração de expressão. Consiste na expressão de atribuição (um operador de atribuição e
doisoperandos) seguido por um ponto e vírgula. Isso faz as seguintes duas coisas:•A expressão atribui o
valor à direita do operador ao local da memória referenciadopela variável x. Embora este seja
provavelmente o principal motivo da declaração, este é considerado oefeito colateral.•Depois de definir
o valor de x, a expressão retorna com o novo valor de x. Mas não há nada pararecebe este valor de
retorno, então ele é ignorado.x = 10;Todo o motivo para avaliar a expressão é atingir o efeito colateral.

Página 272

CAPÍTULO 9 ■ DECLARAÇÕES242Declarações de fluxo de controleC # fornece as construções de fluxo de


controle comuns às linguagens de programação modernas.•A execução condicional executa ou ignora
uma seção de código dependendo de uma condição. oas instruções de execução condicional são as
seguintes:- E se- se ... mais- interruptor•As instruções de loop repetidamente executam uma seção de
código. As declarações de loop são asSegue:- enquanto- Faz- para- para cada•As instruções de salto
mudam o fluxo de controle de uma seção de código para uma instrução específica emoutra seção do
código. As declarações de salto são as seguintes:- pausa- continuar- Retorna- vamos para-
lançarExecução condicional e construções de loop (diferente de foreach) requerem uma expressão de
teste, oucondição , para determinar onde o programa deve continuar a execução.Observação Ao
contrário de C e C ++, as expressões de teste devem retornar um valor do tipo bool . Os números não
têm um booleanointerpretação em C #.Baixe a partir de Wow! e-book <www.wowebook.com>

Página 273

CAPÍTULO 9 ■ DECLARAÇÕES243A declaração ifA instrução if implementa a execução condicional. A


sintaxe para a instrução if é mostrada aqui eé ilustrado na Figura 9-1.•TestExpr deve ser avaliado como
um valor do tipo bool.•Se TestExpr for avaliado como verdadeiro, a instrução será executada.•Se for
avaliada como falsa, a declaração é ignorada.if ( TestExpr )DeclaraçãoFigura 9-1 . A declaração ifO código
a seguir mostra exemplos de instruções if:// Com uma declaração simplesif (x <= 10)z = x - 1;// Instrução
única - não são necessárias chaves// Com um blocoif (x> = 20){x = x - 5;// Bloco - chaves necessáriasy = x
+ z;}int x = 5;if (x)// Erro: a expressão de teste deve ser bool, não int{...}
Página 274

CAPÍTULO 9 ■ DECLARAÇÕES244O se. . . outra declaraçãoA instrução if ... else implementa uma
ramificação bidirecional. A sintaxe para a instrução if ... else émostrado aqui e é ilustrado na Figura 9-
2.•Se TestExpr for avaliado como verdadeiro, a Declaração1 será executada.•Se for avaliada como falsa, a
Instrução2 será executada.if ( TestExpr )Declaração 1outroDeclaração 2Figura 9-2 . O se. . . outra
declaraçãoA seguir está um exemplo da instrução if ... else:if (x <= 10)z = x - 1;// declaração
únicaoutro{// Declarações múltiplas - blocox = x - 5;y = x + z;}

Página 275

CAPÍTULO 9 ■ DECLARAÇÕES245A declaração switchA instrução switch implementa ramificação


multiway. A Figura 9-3 mostra a sintaxe e a estrutura dodeclaração switch.•A instrução switch contém
zero ou mais seções switch .•Cada seção de switch começa com um ou mais rótulos de switch.Figura 9-
3 . Estrutura de uma instrução switchOs rótulos de troca têm o seguinte formato:case
ConstantExpression :↑↑Palavra-chaveAlternar terminador de etiquetaO fluxo de controle através da
estrutura na Figura 9-3 é o seguinte:•A expressão de teste, TestExpr , é avaliada na parte superior da
construção.•Cada seção de switch deve terminar com a instrução break ou uma das outras quatro
instruções de salto.- As declarações de salto são break, return, continue, goto e throw, e são
descritasmais tarde neste capítulo.- Das cinco instruções de salto, a instrução break é a mais comumente
usada para encerrar umseção switch. A instrução break desvia a execução para o final da instrução
switch.Cobriremos todas as instruções de salto posteriormente neste capítulo.•Se o valor de TestExpr for
igual ao valor ConstExpr1 , a expressão constante na primeira opçãorótulo e, em seguida, as instruções
na lista de instruções após o rótulo switch são executadas, atéuma das instruções de salto é
encontrada.•A seção padrão é opcional, mas se for incluída, deve terminar com uma das instruções de
salto.

Página 276

CAPÍTULO 9 ■ DECLARAÇÕES246A Figura 9-4 ilustra o fluxo geral de controle por meio de uma instrução
switch. Você pode modificar ofluir por meio de uma instrução switch com uma instrução goto ou uma
instrução return.Figura 9-4 . O fluxo de controle por meio de uma instrução switch

Página 277

CAPÍTULO 9 ■ DECLARAÇÕES247Um exemplo de switchO código a seguir executa a instrução switch


cinco vezes, com o valor de x variando de 1 a 5.A partir da saída, você pode dizer qual seção de caso foi
executada em cada ciclo do loop.para (int x = 1; x <6; x ++){interruptor (x)// Avalie o valor da variável x.
{caso 2:// Se x for igual a 2Console.WriteLine ("x é {0} - No Caso 2", x);pausa;// Vá para o fim do
switch.caso 5:// Se x for igual a 5Console.WriteLine ("x é {0} - No Caso 5", x);pausa;// Vá para o fim do
switch.padrão:// Se x não for 2 nem 5Console.WriteLine ("x é {0} - No caso padrão", x);pausa;// Vá para o
fim do switch.}}Este código produz a seguinte saída:x é 1 - no caso padrãox é 2 - no caso 2x é 3 - no caso
padrãox é 4 - no caso padrãox é 5 - no caso 5
Página 278

CAPÍTULO 9 ■ DECLARAÇÕES248Mais sobre a declaração de switchUma instrução switch pode ter


qualquer número de seções switch, incluindo nenhuma. A seção padrão não énecessário, conforme
mostrado no exemplo a seguir. É, no entanto, geralmente considerado uma boa prática parainclua-o,
pois ele pode detectar erros em potencial.Por exemplo, a instrução switch no código a seguir não tem
seção padrão. O interruptorinstrução está dentro de um loop for, que executa a instrução cinco vezes,
com o valor de x começando em 1e terminando em 5.para (int x = 1; x <6; x ++){interruptor (x){caso
5:Console.WriteLine ("x é {0} - No Caso 5", x);pausa;}}Este código produz a seguinte saída:x é 5 - no caso
5O código a seguir possui apenas a seção padrão:para (int x = 1; x <4; x ++){interruptor (x)
{padrão:Console.WriteLine ("x é {0} - No caso padrão", x);pausa;}}Este código produz a seguinte saída:x é
1 - no caso padrãox é 2 - no caso padrãox é 3 - no caso padrão

Página 279

CAPÍTULO 9 ■ DECLARAÇÕES249Mudar de etiquetasA expressão após o caso de palavra-chave em um


rótulo de switch deve ser uma expressão constante e deveportanto, pode ser completamente avaliado
pelo compilador em tempo de compilação . Também deve ser do mesmo tipo quea expressão de
testePor exemplo, a Figura 9-5 mostra três exemplos de instruções switch.Figura 9-5 . Instruções de
switch com diferentes tipos de rótulos de switch■ Nota Ao contrário de C e C ++, cada seção de switch ,
incluindo a seção padrão opcional, deve terminar com um dosdeclarações de salto. Em C #, você não
pode executar o código em uma seção de switch e depois passar para a próxima.Embora C # não permita
a passagem de uma seção de switch para outra, você pode fazer oSegue:•Você pode anexar vários
rótulos de switch a qualquer seção de switch.•Seguindo a lista de declarações associadas a um caso,
deve haver uma das declarações de saltoantes do próximo rótulo de switch, a menos que não haja
instruções executáveis intermediárias entre osmudar rótulos.Por exemplo, no código a seguir, uma vez
que não há instruções executáveis entre os três primeirosmudar de rótulo, não há problema em seguir
um após o outro. Casos 5 e 6, no entanto, possuem um executáveldeclaração entre eles, então deve
haver uma declaração de salto antes do caso 6.interruptor (x){caso 1:// Aceitávelcaso 2:caso 3:...//
Execute este código se x for igual a 1, 2 ou 3.pausa;caso 5:y = x + 1;caso 6:// Não é aceitável porque não
há pausa...

Página 280

CAPÍTULO 9 ■ DECLARAÇÕES250The while LoopO loop while é uma construção de loop simples em que
a expressão de teste é executada no topo do loop.A sintaxe do loop while é mostrada aqui e ilustrada na
Figura 9-6.•Primeiro, TestExpr é avaliado.•Se TestExpr for avaliado como falso, a execução continuará
após o final do loop while.•Caso contrário, quando TestExpr for avaliado como verdadeiro, a instrução
será executada e TestExpr seráavaliado novamente. Cada vez que TestExpr é avaliado como verdadeiro, a
instrução é executada outra vez. oo loop termina quando TestExpr é avaliado como falso.while
( TestExpr )DeclaraçãoFigura 9-6 . O loop whileO código a seguir mostra um exemplo do loop while, onde
a variável de expressão de teste começacom um valor de 3 e é diminuído a cada iteração. O loop termina
quando o valor da variáveltorna-se 0.int x = 3;enquanto (x> 0){Console.WriteLine ("x: {0}",
x);x--;}Console.WriteLine ("Fora do loop");Este código produz a seguinte saída:x: 3x: 2x: 1Fora do circuito

Página 281

CAPÍTULO 9 ■ DECLARAÇÕES251The do LoopO loop do é uma construção de loop simples em que a


expressão de teste é realizada na parte inferior dociclo. A sintaxe do loop do é mostrada aqui e ilustrada
na Figura 9-7.•Primeiro, a instrução é executada.•Então, TestExpr é avaliado.•Se TestExpr retornar
verdadeiro, a instrução será executada novamente.•Cada vez que TestExpr retorna verdadeiro, a
instrução é executada novamente.•Quando TestExpr retorna falso, o controle passa para a instrução
após o final doconstrução de loop.FazDeclaraçãowhile ( TestExpr );// Fim do loop doFigura 9-7 . O laço do

Página 282

CAPÍTULO 9 ■ DECLARAÇÕES252O loop do tem várias características que o diferenciam de outras


construções de fluxo de controle. Elessão as seguintes:•O corpo do loop, Statement , é sempre
executado pelo menos uma vez, mesmo se TestExpr forinicialmente falso.•O ponto-e-vírgula é
necessário após o parêntese de fechamento da expressão de teste.O código a seguir mostra um exemplo
de um loop do:int x = 0;FazConsole.WriteLine ("x é {0}", x ++);enquanto (x <3);↑RequeridosEste código
produz a seguinte saída:x é 0x é 1x é 2Baixe a partir de Wow! e-book <www.wowebook.com>

Página 283

CAPÍTULO 9 ■ DECLARAÇÕES253O for LoopA construção do loop for executa o corpo do loop, desde que
a expressão de teste retorne verdadeira quando foravaliada no topo do loop. A sintaxe do loop for é
mostrada aqui e ilustrada na Figura 9-8.•No início do loop for, o Initializer é executado uma vez.•TestExpr
é então avaliado.•Se TestExpr retornar verdadeiro, Statement será executado, seguido por
IterationExpr .•O controle então retorna ao topo do loop e TestExpr é avaliado novamente.•Contanto
que TestExpr retorne verdadeiro, Statement , seguido por IterationExpr , é executado.•Assim que
TestExpr retornar falso, a execução continuará na instrução seguinte à instrução .Separados por ponto e
vírgula↓↓para ( Initializer ; TestExpr ; IterationExpr )DeclaraçãoAlgumas partes da declaração são
opcionais.•Initializer , TestExpr e IterationExpr são opcionais. Suas posições podem ser deixadas em
branco. Se oA posição TestExpr é deixada em branco, presume-se que o teste retorne verdadeiro.
Portanto, deve haver algumoutro método de sair da instrução se o programa deve evitar entrar em um
loop infinito.•Os dois pontos-e-vírgulas são necessários como separadores de campo.Figura 9-8 . O laço
for

Página 284

CAPÍTULO 9 ■ DECLARAÇÕES254A Figura 9-8 ilustra o fluxo de controle por meio da instrução for. Você
também deve saber oseguindo sobre seus componentes:•O Initializer é executado apenas uma vez,
antes de qualquer outra parte da construção for. Geralmente é usado paradeclara e inicializa os valores
locais a serem usados no loop.•TestExpr é avaliado para determinar se a instrução deve ser executada ou
ignorada. Isso deveavaliar para um valor do tipo bool.•IterationExp r é executado imediatamente após a
declaração e antes de retornar ao topo do looppara TestExpr .Por exemplo, no seguinte código:•Antes de
mais nada, o inicializador (int i = 0) define uma variável chamada i e inicializa seu valora 0.•A condição (i
<3) é então avaliada. Se for verdade, o corpo do loop é executado.•Na parte inferior do loop, depois que
todas as instruções do loop foram executadas, a IterationExprinstrução é executada - neste caso,
incrementando o valor de i.// O corpo deste loop for é executado três vezes.para (int i = 0; i <3; i +
+)Console.WriteLine ("loop interno. I: {0}", i);Console.WriteLine ("Fora do Loop");Este código produz a
seguinte saída:Loop interno. i: 0Loop interno. i: 1Loop interno. i: 2Fora de Loop

Página 285

CAPÍTULO 9 ■ DECLARAÇÕES255O escopo das variáveis em uma declaração forQuaisquer variáveis


declaradas no inicializador são visíveis apenas na instrução for .•Isso é diferente de C e C ++, onde a
declaração introduz a variável nobloco envolvente.•O código a seguir ilustra esse ponto:O tipo é
necessário aqui para declaração.↓for (int i = 0; i <10; i ++) // A variável i está no escopo aqui, e
tambémDeclaração ;// aqui dentro da declaração.// Aqui, após a declaração, eu não existo mais.O tipo é
necessário aqui novamente porque a variável anterior i deixou de existir.↓for (int i = 0; i <10; i ++) //
Precisamos definir uma nova variável i aqui,Declaração ;// o anterior deixou de existir.As variáveis locais
declaradas dentro do corpo do loop são conhecidas apenas dentro do loop.■ Nota Ao contrário de C e C
++, o escopo das variáveis declaradas no inicializador dura apenas pelo comprimento do loop.

Página 286

CAPÍTULO 9 ■ DECLARAÇÕES256Expressões múltiplas no inicializador e expressão de iteraçãoTanto a


expressão inicializadora quanto a expressão iterativa podem conter múltiplas expressões, desde queeles
são separados por vírgulas.Por exemplo, o código a seguir tem duas declarações de variáveis no
inicializador e duas expressõesna expressão de iteração:static void Main (){const int MaxI = 5;Duas
declaraçõesDuas expressões↓↓para (int i = 0, j = 10; i <MaxI; i ++, j + = 10){Console.WriteLine ("{0}, {1}", i,
j);}}Este código produz a seguinte saída:0, 101, 202, 303, 404, 50

Página 287

CAPÍTULO 9 ■ DECLARAÇÕES257Declarações de saltoQuando o fluxo de controle atinge as declarações


de salto, a execução do programa é incondicionalmente transferida paraoutra parte do programa. As
declarações de salto são as seguintes:•pausa•continuar•Retorna•vamos para•lançarEste capítulo cobre
as primeiras quatro dessas declarações. A declaração de lançamento é explicada emCapítulo 11.A
declaração de quebraNo início deste capítulo, você viu a instrução break usada na instrução switch.
Também pode ser usado emos seguintes tipos de declaração:•para•para cada•enquanto•FazNo corpo
de uma dessas instruções, break faz com que a execução saia do loop interno mais interno.Por exemplo,
o seguinte loop while seria um loop infinito se dependesse apenas de seu testeexpressão, o que é
sempre verdade. Mas em vez disso, após três iterações do loop, a instrução break éencontrado, e o loop
é encerrado.int x = 0;enquanto (verdadeiro){x ++;if (x> = 3)pausa;}

Página 288

CAPÍTULO 9 ■ DECLARAÇÕES258The continue StatementA instrução continue faz com que a execução
do programa vá para o topo do loop mais interno doseguintes tipos:•enquanto•Faz•para•para cadaPor
exemplo, o seguinte for loop é executado cinco vezes. Nas primeiras três iterações, ele encontraa
instrução continue e volta diretamente para o topo do loop, perdendo a instrução WriteLine ema parte
inferior do loop. A execução atinge apenas a instrução WriteLine durante as duas últimas iterações.para
(int x = 0; x <5; x ++)// Execute o loop cinco vezes{if (x <3)// As três primeiras vezescontinuar;// Volte
diretamente para o topo do loop// Esta linha só é alcançada quando x é 3 ou maior.Console.WriteLine
("O valor de x é {0}", x);}Este código produz a seguinte saída:O valor de x é 3O valor de x é 4O código a
seguir mostra um exemplo de uma instrução continue em um loop while. Este código produza mesma
saída do exemplo anterior para loop.int x = 0;enquanto (x <5){if (x <3){x ++;continuar;// Voltar ao topo
do loop}// Esta linha é alcançada apenas quando x é 3 ou maior.Console.WriteLine ("O valor de x é {0}",
x);x ++;}

Página 289

CAPÍTULO 9 ■ DECLARAÇÕES259Declarações etiquetadasUma declaração rotulada consiste em um


identificador, seguido por dois pontos, seguido por uma declaração. Tem oseguinte
formulário:Identificador : DeclaraçãoUma declaração rotulada é executada exatamente como se o rótulo
não estivesse lá e consistisse apenas emParte da declaração .•Adicionar um rótulo a uma instrução
permite que o controle seja transferido para a instrução de outra partedo código.•Instruções rotuladas
são permitidas apenas dentro de blocos.EtiquetasOs rótulos têm seu próprio espaço de declaração,
então o identificador em uma declaração rotulada pode ser qualqueridentificador - incluindo aqueles
que podem ser declarados em um escopo sobreposto, como variáveis locais ounomes de parâmetros.Por
exemplo, o código a seguir mostra o uso válido de um rótulo com o mesmo identificador de umvariável
local:{int xyz = 0;// Variável xyz...xyz: Console.WriteLine ("Sem problemas.");// Label xyz}Existem
restrições, no entanto. O identificador não pode ser•O mesmo que outro identificador de rótulo com um
escopo sobreposto•Uma palavra-chave

Página 290

CAPÍTULO 9 ■ DECLARAÇÕES260O escopo das declarações rotuladasAs instruções rotuladas não podem
ser vistas (ou acessadas) de fora do bloco em que foram declaradas. oo escopo de uma declaração
rotulada é•O bloco em que é declarado•Quaisquer blocos aninhados dentro desse blocoPor exemplo, o
código à esquerda da Figura 9-9 contém vários blocos aninhados, com seus escoposmarcado. Existem
duas instruções rotuladas declaradas no escopo B do programa: incremento e fim.•As partes
sombreadas à direita da figura mostram as áreas do código em que odeclarações estão no escopo.•O
código no escopo B e todos os blocos aninhados podem ver e acessar as instruções rotuladas.•Código de
qualquer um dos escopos internos pode saltar para fora das demonstrações rotulados.•O código de fora
(escopo A, neste caso) não pode saltar para um bloco com uma declaração rotulada.Figura 9-9 . O
escopo dos rótulos inclui blocos aninhados.

Página 291

CAPÍTULO 9 ■ DECLARAÇÕES261A declaração gotoA instrução goto transfere incondicionalmente o


controle para uma instrução rotulada. Sua forma geral é aa seguir, onde Identifier é o identificador de
uma declaração rotulada:goto Identifier ;Por exemplo, o código a seguir mostra o uso simples de uma
instrução goto:bool thingsAreFine;enquanto (verdadeiro){thingsAreFine = GetNuclearReactorCondition
();if (thingsAreFine)Console.WriteLine ("As coisas estão bem.");outrogoto NotSoGood;}NotSoGood:
Console.WriteLine ("Temos um problema.");A instrução goto deve estar dentro do escopo da instrução
rotulada.•A instrução goto pode saltar para qualquer instrução rotulada dentro de seu próprio bloco ou
pode saltar para fora a qualquerbloco no qual está aninhado.•Uma instrução goto não pode saltar para
nenhum bloco aninhado em seu próprio bloco.■ Cuidado O uso da instrução goto é fortemente
desencorajado, porque pode levar a um código que está malestruturado e difícil de depurar e manter.
Carta de Edsger Dijkstra de 1968 para as Comunicações da ACM,intitulada “Declaração Go To Considered
Harmful,” foi uma contribuição importante para a ciência da computação; foi um dosas primeiras
descrições publicadas das armadilhas do uso da instrução goto .A instrução goto dentro de uma
instrução switchExistem também duas outras formas da instrução goto, para uso dentro das instruções
switch. Estes vãoas instruções transferem o controle para o rótulo switch com o nome correspondente
na instrução switch.goto case ConstantExpression;goto default;

Página 292

CAPÍTULO 9 ■ DECLARAÇÕES262A declaração de usoCertos tipos de objetos não gerenciados são


limitados em número ou são caros com os recursos do sistema. Estáimportante que quando seu código
for concluído com eles, eles sejam lançados o mais rápido possível. O usodeclaração ajuda a simplificar o
processo e garante que esses recursos sejam descartados corretamente.Um recurso é uma classe ou
estrutura que implementa a interface System.IDisposable. Interfaces sãocoberto em detalhes no
Capítulo 17 - mas, em resumo, uma interface é uma coleção de funções não implementadasmembros
que classes e estruturas podem escolher implementar. A interface IDisposable contém um únicométodo
denominado Dispose.As fases de uso de um recurso são mostradas na Figura 9-10 e consistem no
seguinte:•Alocando o recurso•Usando o recurso•Eliminação do recursoSe ocorrer um erro de tempo de
execução inesperado durante a parte do código que usa o recurso, o códigoo descarte do recurso pode
não ser executado.Figura 9-10 . Componentes do uso de um recursoNota A instrução using é diferente
das diretivas using . As diretivas de uso são abordadas emCapítulo 10.Baixe a partir de Wow! e-book
<www.wowebook.com>

Página 293

CAPÍTULO 9 ■ DECLARAÇÕES263Empacotando o Uso do RecursoA instrução using ajuda a reduzir o


problema potencial de um erro inesperado em tempo de execução, ordenadamenteempacotar o uso de
um recurso.Existem duas formas de declaração using. O primeiro formulário é o seguinte e é ilustrado
emFigura 9-11.•O código entre parênteses aloca o recurso.•Declaração é o código que usa o recurso.•A
instrução using gera implicitamente o código para descartar o recurso.using ( ResourceType Identifier =
Expression ) Instrução↑↑Aloca recursoUsa recursoErros inesperados de tempo de execução são
chamados de exceções e são abordados no Capítulo 11. A maneira padrãode lidar com a possibilidade de
exceções é colocar o código que pode causar uma exceção em um bloco trye coloque qualquer código
que deva ser executado, haja ou não uma exceção, em um bloco finally.Esta forma da instrução using faz
exatamente isso. Ele executa o seguinte:•Aloca o recurso•Declaração de locais em um bloco try•Cria
uma chamada para o método Dispose do recurso e o coloca em um bloco finallyFigura 9-11 . O efeito da
instrução using

Página 294

CAPÍTULO 9 ■ DECLARAÇÕES264Exemplo de declaração de usoO código a seguir usa a instrução using


duas vezes - uma vez com uma classe chamada TextWriter e uma vez com umclasse chamada
TextReader, ambos do namespace System.IO. Ambas as classes implementam o IDisposableinterface,
conforme exigido pela instrução using.•O recurso TextWriter abre um arquivo de texto para gravação e
grava uma linha no arquivo.•O recurso TextReader então abre o mesmo arquivo de texto e lê e exibe o
conteúdo, linhapor linha.•Em ambos os casos, a instrução using garante que os métodos Dispose dos
objetos sejam chamados.•Observe também a diferença entre as instruções using em Main e as diretivas
using noprimeiras duas linhas.using System;// usando DIRECTIVE; não usando declaraçãousing
System.IO;// usando DIRECTIVE; não usando declaraçãonamespace UsingStatement{programa de
aula{static void Main (){// usando declaraçãousando (TextWriter tw = File.CreateText ("Lincoln.txt"))
{tw.WriteLine ("Quatro pontos e sete anos atrás, ...");}// usando declaraçãousando (TextReader tr =
File.OpenText ("Lincoln.txt")){string InputString;while (null! = (InputString = tr.ReadLine
()))Console.WriteLine (InputString);}}}}Este código produz a seguinte saída:Oitenta e sete anos atrás, ...

Página 295

CAPÍTULO 9 ■ DECLARAÇÕES265Vários recursos e aninhamentoA instrução using também pode ser


usada com vários recursos do mesmo tipo, com o recursodeclarações separadas por vírgulas. A sintaxe é
a seguinte:Apenas um tipo de RecursoRecurso↓↓↓usando ( ResourceType Id1 = Expr1, Id2 = Expr2, ...)
EmbeddedStatementPor exemplo, no código a seguir, cada instrução using aloca e usa dois
recursos:static void Main (){usando (TextWriter tw1 = File.CreateText ("Lincoln.txt"),tw2 = File.CreateText
("Franklin.txt")){tw1.WriteLine ("Quatro pontos e sete anos atrás, ...");tw2.WriteLine ("Cedo para dormir;
Cedo para acordar ...");}usando (TextReader tr1 = File.OpenText ("Lincoln.txt"),tr2 = File.OpenText
("Franklin.txt")){string InputString;while (null! = (InputString = tr1.ReadLine ()))Console.WriteLine
(InputString);while (null! = (InputString = tr2.ReadLine ()))Console.WriteLine (InputString);}}A instrução
using também pode ser aninhada. No código a seguir, além do aninhamento doinstruções, observe
também que não é necessário usar um bloco com a segunda instrução using porqueconsiste em apenas
uma declaração simples e única.usando (TextWriter tw1 = File.CreateText ("Lincoln.txt")){tw1.WriteLine
("Quatro pontos e sete anos atrás, ...");using (TextWriter tw2 = File.CreateText ("Franklin.txt")) //
Nestedtw2.WriteLine ("Cedo para dormir; Cedo para acordar ..."); // Solteiro}

Página 296

CAPÍTULO 9 ■ DECLARAÇÕES266Outra forma de declaração de usoOutra forma da instrução using é a


seguinte:Recurso de palavra-chaveUsa recurso↓↓↓usando ( Expression ) EmbeddedStatementNeste
formulário, o recurso é declarado antes da instrução using.TextWriter tw = File.CreateText
("Lincoln.txt");// Recurso declaradousando (tw)// usando declaraçãotw.WriteLine ("Quatro pontos e sete
anos atrás, ...");Embora este formulário ainda garanta que o método Dispose sempre será chamado
depois que você terminar de usaro recurso, ele não o protege de tentar usar o recurso após a instrução
usingliberou seus recursos não gerenciados, deixando-o em um estado inconsistente. Portanto, dá
menos proteçãoe está desanimado. Este formulário é ilustrado na Figura 9-12.Figura 9-12 . Declaração
de recurso antes da instrução using

Página 297

CAPÍTULO 9 ■ DECLARAÇÕES267Outras DeclaraçõesExistem outras declarações associadas a


características específicas da linguagem. Estas declaraçõessão abordados nas seções que tratam desses
recursos. As declarações abordadas em outros capítulos sãomostrado na Tabela 9-1.Tabela 9-1.
Declarações abordadas em outros capítulosDeclaraçãoDescriçãoCapítulo Relevanteverificado,
desmarcadoEssas instruções controlam a verificação de estourocontexto.Capítulo 18para cadaEsta
declaração itera por meio de cada membro de umcoleção.Capítulos 14 e 20tente, lance, finalmenteEssas
declarações estão associadas a exceções.Capítulo 11RetornaEsta instrução retorna o controle para a
função de chamadamembro e também pode retornar um valor.capítulo 5produçãoEsta instrução é usada
com iteradores.Capítulo 20

Página 298

Página 299

CAPÍTULO 10■ ■ ■269Namespaces e assemblies■ Referenciando outros conjuntos■ Namespaces■ O


uso de diretivas■ A estrutura de uma montagem■ A identidade de uma assembléia■ Conjuntos
fortemente nomeados■ Implantação privada de uma montagem■ Assembléias compartilhadas e o
GAC■ Arquivos de configuração■ Assinatura atrasada

Página 300

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS270Referenciando outras montagensNo Capítulo 1, demos


uma olhada de alto nível no processo de compilação. Você viu que o compilador pega oarquivo de
código-fonte e produz um arquivo de saída denominado assembly . Este capítulo examina mais de
pertoassemblies e como eles são produzidos e implantados. Você também verá como os namespaces
ajudamorganizar tipos.Todos os programas que você viu até agora, em sua maioria, declararam e usaram
suas próprias classes. Nomuitos projetos, entretanto, você desejará usar classes ou tipos de outros
assemblies. Esses outrosassemblies podem vir da BCL ou de um fornecedor terceirizado, ou você mesmo
pode tê-los criado.Elas são chamadas de bibliotecas de classes e os nomes de seus arquivos de
montagem geralmente terminam com o .dllextensão em vez da extensão .exe.Suponha, por exemplo,
que você deseja criar uma biblioteca de classes que contém classes e tipos que podemser usado por
outros conjuntos. O código-fonte para uma biblioteca simples é mostrado no exemplo a seguir eestá
contido em um arquivo chamado SuperLib.cs. A biblioteca contém uma única classe pública chamada
SquareWidget.A Figura 10-1 ilustra a produção do DLL.classe pública SquareWidget{público duplo
SideLength = 0;área dupla pública{get {return SideLength * SideLength; }}}Figura 10-1 . O código-fonte do
SuperLib e o conjunto resultantePara criar uma biblioteca de classes usando o Visual Studio 2010,
selecione o modelo Class Library do instaladoModelos do Windows. Especificamente, quando estiver no
Visual Studio, faça o seguinte:1. Selecione Arquivo Novo Projeto e a janela Novo Projeto será aberta.2.
No painel esquerdo do painel Modelos Instalados, encontre o nó Visual C # em OutroRaiz de idiomas e
selecione a entrada do Windows.3. No painel direito, selecione o modelo de Biblioteca de Classes.

Página 301

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS271Suponha também que você está escrevendo um


programa chamado MyWidgets e deseja usar o SquareWidgetclasse. O código do programa está em um
arquivo chamado MyWidgets.cs e é mostrado no exemplo a seguir.O código simplesmente cria um
objeto do tipo SquareWidget e usa os membros do objeto.using System;classe WidgetsProgram{static
void Main (){SquareWidget sq = new SquareWidget (); // Da biblioteca de classe↑Não declarado nesta
assembleiasq.SideLength = 5,0;// Defina o comprimento lateral.Console.WriteLine (sq.Area);// Imprima a
área.} ↑} Não declarado nesta assembleiaObserve que o código não declara a classe SquareWidget. Em
vez disso, você usa a classe definida emSuperLib. Quando você compila o programa MyWidgets, no
entanto, o compilador deve estar ciente de que seuo código usa o assembly SuperLib para obter as
informações sobre a classe SquareWidget. Para fazer isso, você precisapara fornecer ao compilador uma
referência ao assembly, fornecendo seu nome e localização.No Visual Studio, você pode adicionar
referências a um projeto da seguinte maneira:•Selecione o Solution Explorer e encontre a pasta
References abaixo do nome do projeto. oA pasta References contém uma lista dos assemblies usados
pelo projeto.•Clique com o botão direito na pasta References e selecione Add Reference. Existem cinco
guias paraescolher, permitindo que você encontre a biblioteca de classes de maneiras diferentes.•Para o
nosso programa, selecione a guia Browse, navegue até o arquivo DLL que contém a classe
SquareWidgetdefinição e selecione-a.•Clique no botão OK e a referência será adicionada ao projeto.

Página 302

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS272Depois de adicionar a referência, você pode compilar


MyWidgets. A Figura 10-2 ilustra o completoprocesso de compilação.Figura 10-2 . Referenciando outra
montagemBaixe a partir de Wow! e-book <www.wowebook.com>

Página 303

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS273Biblioteca mscorlibHá uma biblioteca de classe que


tenho usado em todos os exemplos do livro até agora. É aquele que contéma classe Console. A classe
Console é definida em um assembly chamado mscorlib em um arquivo chamadomscorlib.dll. Você não
encontrará esta montagem listada na pasta References, entretanto. Montagem mscorlibcontém as
definições dos tipos C # e os tipos básicos para a maioria das linguagens .NET. Deve ser
semprereferenciado ao compilar um programa C #, para que o Visual Studio não se incomode em
mostrá-lo nas referênciaspasta.Quando você leva em consideração o mscorlib, o processo de compilação
para MyWidgets se parece mais com orepresentação mostrada na Figura 10-3. Depois disso, assumirei o
uso do assembly mscorlib semrepresentando-o novamente.Figura 10-3 . Bibliotecas de classes de
referênciaAgora, suponha que seu programa esteja funcionando bem com a classe SquareWidget, mas
você desejaexpandir seus recursos para usar uma classe chamada CircleWidget, que é definida em um
assembly diferente chamadoUltraLib. O código-fonte do MyWidgets agora se parece com o seguinte. Ele
cria um objeto SquareWidget comodefinido em SuperLib e um objeto CircleWidget conforme definido
em UltraLib.classe WidgetsProgram{static void Main (){SquareWidget sq = new SquareWidget (); // De
SuperLib...Círculo CircleWidget = novo CircleWidget (); // Do UltraLib...}}

Página 304

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS274O código-fonte da biblioteca de classes UltraLib é


mostrado no exemplo a seguir. Observe que além dea classe CircleWidget, como a biblioteca SuperLib,
também declara uma classe chamada SquareWidget. Você pode compilarUltraLib para uma DLL e
adicione-o à lista de referências no projeto MyWidgets.classe pública SquareWidget{...}classe pública
CircleWidget{público duplo Raio = 0;área dupla pública{obter { ... }}}Uma vez que ambas as bibliotecas
contêm uma classe chamada SquareWidget, quando você tenta compilar o programaMyWidgets, o
compilador produz uma mensagem de erro porque não sabe qual versão da classeSquareWidget para
usar. A Figura 10-4 ilustra esse conflito de nomes .Figura 10-4 . Como os assemblies SuperLib e UltraLib
contêm declarações para uma classe chamadaSquareWidget, o compilador não sabe qual instanciar.

Página 305

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS275NamespacesNo exemplo MyWidgets, uma vez que você


tem o código-fonte, você pode resolver o conflito de nomes apenasalterar o nome da classe
SquareWidget no código-fonte SuperLib ou no código-fonte UltraLibcódigo. Mas e se essas bibliotecas
tivessem sido desenvolvidas por empresas diferentes e você não tivesse oCódigo fonte? Suponha que o
SuperLib foi produzido por uma empresa chamada MyCorp e o UltraLib foiproduzido pela empresa
ABCCorp. Nesse caso, você não seria capaz de usá-los juntos se usassequaisquer classes ou tipos onde
houve um conflito.Como você pode imaginar, com sua máquina de desenvolvimento contendo
assemblies produzidos por dezenas, senão centenas de empresas diferentes, é provável que haja uma
certa duplicação de nomesdas aulas. Seria uma pena se você não pudesse usar dois assemblies no
mesmo programa só porque elesaconteceu de ter nomes de tipo em comum.Suponha, no entanto, que a
MyCorp tivesse uma política de prefaciar todas as suas classes com uma string queconsistia no nome da
empresa seguido pelo nome do produto seguido pelo nome descritivo da classe.Suponha ainda que a
ABCCorp tenha a mesma política. Nesse caso, os três nomes de classes em nosso exemploseria
denominado MyCorpSuperLibSquareWidget, ABCCorpUltraLibSquareWidget
eABCCorpUltraLibCircleWidget, conforme mostrado na Figura 10-5. Estes são nomes de classe
perfeitamente válidos ehá pouca chance de as aulas da biblioteca de uma empresa conflitarem com as
de outra empresa.Figura 10-5. Com strings de eliminação de ambiguidades precedidas dos nomes das
classes, não há conflito entre osbibliotecas.Nosso programa de exemplo, no entanto, precisaria usar
esses nomes longos e se pareceria com oSegue:classe WidgetsProgram{static void Main ()
{MyCorpSuperLibSquareWidget sq= novo MyCorpSuperLibSquareWidget (); // De SuperLib...Círculo
ABCCorpUltraLibCircleWidget= novo ABCCorpUltraLibCircleWidget (); // Do UltraLib...}}

Página 306
CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS276Embora isso resolva o problema do conflito, esses novos
nomes sem ambigüidade são mais difíceis de ler edesajeitado de trabalhar, mesmo com o
IntelliSense.Suponha, no entanto, que além dos caracteres normalmente permitidos em um
identificador, você pudessetambém use o caractere ponto dentro da string - embora ainda não no início
ou no final donome da classe. Nesse caso, poderíamos tornar os nomes mais compreensíveis,
comoMyCorp.SuperLib.SquareWidget, ABCCorp.UltraLib.SquareWidget e
ABCCorp.UltraLib.CircleWidget.Agora o código se pareceria com o seguinte:classe WidgetsProgram{static
void Main (){MyCorp.SuperLib.SquareWidget sq= novo MyCorp.SuperLib.SquareWidget (); // De
SuperLib...ABCCorp.UltraLib.CircleWidget circle= novo ABCCorp.UltraLib.CircleWidget (); // Do
UltraLib...}}Isso nos leva ao conceito de nome de namespace e um namespace.•Você pode pensar em
um nome de namespace como uma string de caracteres (que pode incluir pontos dentro dostring) colada
na frente do nome da classe ou tipo e separada por um ponto.•A string completa, incluindo o nome do
namespace, o ponto de separação e o nome da classe, é chamada deo nome totalmente qualificado da
classe .•Um namespace é o conjunto de classes e tipos que compartilham esse nome de namespace.A
Figura 10-6 ilustra essas definições.Figura 10-6. Um namespace é o conjunto de definições de tipo que
compartilham o mesmo nome de namespace.

Página 307

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS277Você pode usar namespaces para agrupar um conjunto


de tipos e dar-lhes um nome. Geralmente você quernomes de namespace para serem descritivos dos
tipos contidos no namespace e para serem distintos deoutros nomes de namespace.Você cria um
namespace declarando o namespace no arquivo de origem que contém o seu tipodeclarações. O
seguinte mostra a sintaxe para declarar um namespace. Você então declara todos os seusclasses e
outros tipos entre as chaves da declaração de namespace. Estes são então osmembros do
namespace.Nome do namespace da palavra-chave↓↓namespace NamespaceName{TypeDeclarations}O
código a seguir mostra como os programadores da MyCorp poderiam criar a
MyCorp.SuperLibnamespace e declara a classe SquareWidget dentro dele.Nome da empresa Período↓
↓namespace MyCorp.SuperLib{classe pública SquareWidget{público duplo SideLength = 0;área dupla
pública{get {return SideLength * SideLength; }}}}

Página 308

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS278Agora, quando a empresa MyCorp envia a você a nova


montagem atualizada, você pode usá-la modificandoseu programa MyWidgets, conforme mostrado
aqui:classe WidgetsProgram{static void Main (){Nome totalmente qualificadoNome totalmente
qualificado↓↓MyCorp.SuperLib.SquareWidget sq = new MyCorp.SuperLib.SquareWidget ();↑↑Nome do
namespace Nome da classeCírculo CircleWidget = novo CircleWidget ();...Agora que você especificou
explicitamente a versão SuperLib de SquareWidget em seu código, oo compilador não terá mais
problemas para distinguir as classes. O nome totalmente qualificado é um pouco longodigitar, mas pelo
menos agora você pode usar as duas bibliotecas. Um pouco mais adiante neste capítulo, abordaremos o
uso de aliasdiretiva para resolver o inconveniente de ter que digitar repetidamente o nome totalmente
qualificado.Se o conjunto UltraLib também for atualizado com um namespace pela empresa que o
produz(ABCCorp), então o processo de compilação seria como mostrado na Figura 10-7.Figura 10-7 .
Bibliotecas de classes com namespaces

Página 309

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS279Nomes de namespaceComo você viu, o nome de um


namespace pode conter o nome da empresa que criou o assembly.Além de identificar a empresa, o
nome também é usado para ajudar os programadores a ter uma ideia rápida dotipos de tipos definidos
no namespace.Alguns pontos importantes sobre os nomes de namespaces são os seguintes:•Um nome
de namespace pode ser qualquer identificador válido, conforme descrito no Capítulo 2.•Além disso, um
nome de namespace pode incluir qualquer número de caracteres de período. Você pode usar issopara
organizar tipos em hierarquias.Por exemplo, a Tabela 10-1 fornece os nomes de alguns dos namespaces
no .NET BCL.Tabela 10-1 . Exemplos de namespaces do
BCLSistemaSystem.DataSystem.DrawingSystem.IOMicrosoft.CSharpMicrosoft.VisualBasicAs diretrizes de
nomenclatura de namespace sugerem o seguinte:•Inicie os nomes de namespace com o nome da
empresa.•Siga o nome da empresa com o nome da tecnologia.•Não nomeie um namespace com o
mesmo nome de uma classe ou tipo.Por exemplo, o departamento de desenvolvimento de software da
Acme Widget Company desenvolvesoftware nos três namespaces a seguir, conforme mostrado no
código a seguir:•AcmeWidgets.SuperWidget•AcmeWidgets.Media•AcmeWidgets.Gamesnamespace
AcmeWidgets.SuperWidget{classe SPDBase ......}

Página 310

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS280Mais sobre namespacesExistem vários outros pontos


importantes que você deve saber sobre os namespaces:•Cada nome de tipo em um namespace deve ser
diferente de todos os outros.•Os tipos em um namespace são chamados de membros do
namespace.•Um arquivo de origem pode conter qualquer número de declarações de namespace,
sequencialmente ou aninhadas.A Figura 10-8 mostra um arquivo de origem à esquerda que declara dois
namespaces sequencialmente, com váriostipos em cada um. Observe que, embora os namespaces
contenham vários nomes de classes em comum,eles são diferenciados por seus nomes de namespace,
conforme mostrado na montagem à direita da figura.Figura 10-8 . Vários namespaces em um arquivo de
origemO .NET Framework BCL oferece milhares de classes e tipos definidos para escolher na
construçãoseus programas. Para ajudar a organizar esta vasta gama de funcionalidades disponíveis, tipos
com funcionalidades relacionadassão declarados no mesmo namespace. O BCL usa mais de 100
namespaces para organizar seus tipos.

Página 311

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS281Espaços de nomes espalhados pelos arquivosUm


namespace não está fechado. Isso significa que você pode adicionar mais declarações de tipo a ele,
declarando-o novamenteposteriormente no arquivo de origem ou em outro arquivo de origem.Por
exemplo, a Figura 10-9 mostra a declaração de três classes, todas no mesmo namespace, masdeclarado
em arquivos de origem separados. Os arquivos de origem podem ser compilados em um único conjunto,
como mostrado emFigura 10-9, ou em montagens separadas, conforme mostrado na Figura 10-10.Figura
10-9 . Um namespace pode ser espalhado pelos arquivos de origem e compilado em um único
assembly.Figura 10-10 . Um namespace pode ser espalhado pelos arquivos de origem e compilado para
separar assemblies.

Página 312

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS282Namespaces aninhadosOs namespaces podem ser


aninhados, produzindo um namespace aninhado . O aninhamento de namespaces permite que você crie
umhierarquia conceitual de tipos.Existem duas maneiras de declarar um namespace
aninhado:•Aninhamento textual : você pode criar um namespace aninhado colocando sua declaração
dentro docorpo de declaração do namespace delimitador. Isso é ilustrado à esquerda na Figura 10-11.
Nissoexemplo, o namespace OtherNs está aninhado no namespace MyNamespace.•Declaração
separada : você também pode criar uma declaração separada para o namespace aninhado, masvocê
deve usar seu nome totalmente qualificado na declaração. Isso é ilustrado à direita na Figura10-11.
Observe que na declaração do namespace aninhado OtherNs, o nome totalmente
qualificadoMyNamespace.OtherNS é usado.Figura 10-11 . As duas formas de declarar um namespace
aninhado são equivalentes.Ambas as formas das declarações de namespace aninhadas mostradas na
Figura 10-11 produzem o mesmomontagem, conforme ilustrado na Figura 10-12. A figura mostra as duas
classes declaradas no arquivo SomeLib.cs,com seus nomes totalmente qualificados.Figura 10-12 .
Estrutura de namespace aninhadaEmbora o namespace aninhado esteja dentro do namespace
delimitador, seus membros não são membros deo namespace delimitador. Um equívoco comum é que,
uma vez que o namespace aninhado está dentro donamespace delimitador, os membros do namespace
aninhado devem ser um subconjunto do namespace delimitadornamespace. Isso não é verdade; os
namespaces são separados.kBaixe a partir de Wow! e-book <www.wowebook.com>

Página 313

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS283O uso de diretivasNomes totalmente qualificados


podem ser bastante longos e usá-los em todo o código pode se tornar bastantepesado. Existem duas
diretivas de compilador, no entanto, que permitem que você evite ter que usar totalmentenomes
qualificados - a diretiva using namespace e a diretiva using alias .Dois pontos importantes sobre o uso de
diretivas são os seguintes:•Eles devem ser colocados no topo do arquivo de origem, antes de qualquer
declaração de tipo .•Eles se aplicam a todos os namespaces no arquivo de origem atual.A diretiva de uso
de namespaceVocê viu no exemplo MyWidgets várias seções anteriores que você pode especificar uma
classe usando o totalmentenome qualificado. Você pode evitar ter que usar o nome longo, colocando as
diretivas de namespace noparte superior do arquivo de origem.A diretiva using namespace instrui o
compilador que você usará tipos de certosnamespaces específicos. Você pode então ir em frente e usar
os nomes de classe simples sem ter quequalificá-los.Quando o compilador encontra um nome que não
está no namespace atual, ele verifica a lista denamespaces fornecidos nas diretivas de namespace using
e anexa o nome desconhecido ao primeironamespace na lista. Se o nome totalmente qualificado
resultante corresponder a uma classe neste assembly ou a umassembly referenciado, o compilador usa
essa classe. Se não corresponder, ele tenta o próximo namespace ema lista.A diretiva using namespace
consiste na palavra-chave using, seguida por um identificador de namespace.Palavra-chave↓using
System;↑Nome do namespaceUm método que tenho usado ao longo do texto é o método WriteLine,
que é membro declasse Console, no namespace System. Em vez de usar seu nome totalmente
qualificado em todo o código, eusimplificou nosso trabalho um pouco, pelo uso da diretiva using
namespace no topo do código.Por exemplo, o código a seguir usa a diretiva using namespace na primeira
linha para afirmar que oo código usa classes ou outros tipos do namespace System.using System;//
usando a diretiva de namespace...System.Console.WriteLine ("Este é o texto 1"); // Use um nome
totalmente qualificadoConsole.WriteLine ("Este é o texto 2");// Usar diretiva

Página 314

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS284A diretiva de uso de aliasA diretiva using alias permite
que você atribua um alias para um dos seguintes:•Um namespace•Um tipo em um namespacePor
exemplo, o código a seguir mostra o uso de dois usando diretivas de alias. A primeira diretrizinstrui o
compilador que o identificador Syst é um alias para o namespace System. A segunda diretriz dizesse
identificador SC é um alias para a classe System.Console.Namespace de alias de palavra-chave↓
↓↓usando Syst = System;usando SC = System.Console;↑ ↑↑Alias de palavra-chaveClasseO código a seguir
usa esses aliases. Todas as três linhas de código em Main chamam oMétodo
System.Console.WriteLine.•A primeira instrução em Main usa o alias para um namespace —System.•A
segunda instrução usa o nome totalmente qualificado do método.•A terceira instrução usa o alias para
uma classe —Console.usando Syst = System;// usando a diretiva de aliasusando SC = System.Console;//
usando a diretiva de aliasnamespace MyNamespace{classe SomeClass{static void Main (){ Alias para
namespace↓Syst.Console.WriteLine ("Usando o alias de namespace.");System.Console.WriteLine
("Usando um nome totalmente qualificado.");SC.WriteLine("Usando o alias de tipo");↑} Alias para a
classe}}

Página 315

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS285A Estrutura de uma AssembleiaComo você viu no


Capítulo 1, um assembly não contém código de máquina nativo, mas o intermediário comumCódigo de
idioma (CIL). Ele também contém tudo que o compilador Just-in-Time (JIT) precisa para convertero CIL
em código nativo em tempo de execução, incluindo referências a outros assemblies aos quais faz
referência. O arquivoextensão para um assembly é geralmente .exe ou .dll.A maioria das montagens é
composta de um único arquivo. A Figura 10-13 ilustra as quatro seções principais douma assembléia.•O
manifesto do assembly contém o seguinte:- A identidade da assembleia- Uma lista dos arquivos que
compõem a montagem- Um mapa de onde as coisas estão na montagem- Informações sobre outros
assemblies que são referenciados•A seção de metadados de tipo contém as informações sobre todos os
tipos definidos no assembly.Essas informações contêm tudo o que há para saber sobre cada tipo.•A
seção CIL contém todo o código intermediário para a montagem.•A seção de recursos é opcional, mas
pode conter recursos gráficos ou de linguagem.Figura 10-13 . A estrutura de uma montagem de arquivo
único

Página 316
CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS286Embora a maioria das montagens inclua um único
arquivo, algumas possuem mais. Para uma montagem com váriosmódulos, um arquivo é o módulo
primário e os outros são módulos secundários .•O módulo primário contém o manifesto do assembly e
referências ao secundáriomódulos.•Os nomes de arquivo dos módulos secundários terminam com a
extensão .netmodule.•Os conjuntos de vários arquivos são considerados uma única unidade. Eles são
implantados juntos e com controle de versãojuntos.A Figura 10-14 ilustra um conjunto de vários arquivos
com módulos secundários.Figura 10-14 . Um conjunto multifile

Página 317

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS287A identidade de uma assembleiaNo .NET Framework, os


nomes de arquivo dos assemblies não são tão importantes quanto em outros sistemas operacionaise
ambientes. O que é muito mais importante é a identidade de uma assembléia.A identidade de uma
montagem possui quatro componentes que, juntos, devem identificá-la de maneira exclusiva.
Estesquatro componentes são os seguintes:•Nome simples : este é apenas o nome do arquivo sem a
extensão do arquivo. Cada montagem tem um simplesnome. Também é chamado de nome do assembly
ou nome amigável .•Número da versão : consiste em uma string de quatro inteiros separados por
pontos, na formaMajorVersion.MinorVersion.Build.Revision —por exemplo, 2.0.35.9.•Informações de
cultura : Esta é uma string que consiste em dois a cinco caracteres que representam umidioma, ou um
idioma e um país ou região. Por exemplo, o nome da cultura em inglês comousado nos Estados Unidos é
en-US. Para o alemão usado na Alemanha, é de-DE.•Chave pública : esta string de 128 bytes deve ser
exclusiva da empresa que produz a montagem.A chave pública é parte de um par de chaves pública /
privada, que é um conjunto de duas grandes, especialmente escolhidasnúmeros que podem ser usados
para criar assinaturas digitais seguras. A chave pública, como o próprio nome indica, pode sertornou-se
público. A chave privada deve ser protegida pelo proprietário. A chave pública faz parte do
conjuntoidentidade. Veremos o uso da chave privada posteriormente neste capítulo.

Página 318

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS288Os componentes do nome de uma montagem são


incorporados ao manifesto da montagem. Figura 10-15ilustra esta seção do manifesto.Figura 10-15 . Os
componentes de uma identidade de montagem no manifestoA Figura 10-16 mostra alguns dos termos
usados na documentação .NET e literatura sobre oidentidade de uma montagem.Figura 10-16 . Termos
para a identidade de um conjunto

Página 319

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS289Conjuntos fortemente nomeadosUm assembly de nome


forte é aquele que possui uma assinatura digital exclusiva anexada a ele. Fortementeassemblies
nomeados são muito mais seguros do que assemblies que não têm nomes fortes, para oseguintes
razões:•Um nome forte identifica exclusivamente um assembly. Ninguém mais pode criar uma
montagem com omesmo nome forte, para que o usuário possa ter certeza de que o assembly veio da
fonte reivindicada.•O conteúdo de um assembly com um nome forte não pode ser alterado sem a
segurançacomponentes do CLR que detectam a modificação.Um assembly com nome fraco é aquele que
não tem nome forte. Uma vez que uma montagem com nome fraconão tem uma assinatura digital, é
inerentemente inseguro. Como uma corrente é tão forte quanto seu elo mais fraco,por padrão,
assemblies fortemente nomeados só podem acessar outros assemblies fortemente nomeados. (Há
também ummaneira de permitir "chamadores parcialmente confiáveis", mas não vou abordar esse
tópico.)O programador não produz o nome forte. O compilador o produz tomandoinformações sobre o
conjunto e hash para criar uma assinatura digital exclusiva que anexa aomontagem. As informações que
ele usa no processo de hash são as seguintes:•A sequência de bytes que compõe a montagem•O nome
simples•O número da versão•A informação cultural•O par de chave pública / privada■ Nota Há alguma
diversidade na nomenclatura em torno dos nomes fortes. O que estou chamando de
“fortementenomeado ”é frequentemente referido como“ nome forte ”. O que estou chamando de
"nome fraco" às vezes é referido como "nãonome forte ”ou“ montagem com um nome simples ”.

Página 320

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS290Criando uma montagem fortemente nomeadaPara


nomear fortemente um assembly usando o Visual Studio 2010, você deve ter uma cópia da chave
pública / privadaarquivo de par. Se você não tiver um arquivo de chave, pode fazer com que o Visual
Studio gere um para você. Você pode então fazerOs seguintes:1. Abra as propriedades do projeto.2.
Selecione a guia Assinatura.3. Marque a caixa de seleção Assinar a montagem e insira a localização do
arquivo-chave ou crie umnovo.Ao compilar o código, o compilador produzirá um assembly com um
nome forte. Figura 10-17ilustra as entradas e saídas do compilador.Figura 10-17 . Criação de uma
montagem fortemente nomeada

Página 321

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS291Implantação privada de uma montagemA implantação


de um programa em uma máquina de destino pode ser tão simples quanto criar um diretório na
máquinae copiar o aplicativo para ele. Se o aplicativo não precisar de outros assemblies (como DLLs)
ouse as DLLs necessárias estiverem no mesmo diretório, o programa deve funcionar perfeitamente onde
está.Os programas implantados desta forma são chamados de assemblies privados , e este método de
implantação é denominadoimplantação do xcopy .Assemblies privados podem ser colocados em quase
qualquer diretório e são auto-suficientes, desde que todos os arquivosdos quais dependem estão no
mesmo diretório ou subdiretório. Na verdade, você poderia ter váriosdiretórios em várias partes do
sistema de arquivos, cada um com o conjunto idêntico de assemblies, e eles iriamtodos funcionam bem
em seus vários locais.Algumas coisas importantes a saber sobre a implantação de assembly privado são
as seguintes:•O diretório no qual os assemblies privados são colocados é chamado de diretório do
aplicativo .•Um assembly privado pode ter um nome forte ou um nome fraco.•Não há necessidade de
registrar componentes no registro.•Para desinstalar um assembly privado, basta excluí-lo do sistema de
arquivos.

Página 322
CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS292Assembléias compartilhadas e o GACAssemblies
privados são muito úteis, mas às vezes você vai querer colocar uma DLL em um lugar central para que
umuma única cópia pode ser compartilhada por outros assemblies no sistema. .NET tem esse
repositório, chamado decache global de assemblies (GAC). Um assembly colocado no GAC é chamado de
assembly compartilhado .Alguns fatos importantes sobre o GAC são os seguintes:•Apenas assemblies
com nomes fortes podem ser adicionados ao GAC.•Embora as versões anteriores do GAC aceitassem
apenas arquivos com a extensão .dll, agora você pode adicionarassemblies com a extensão .exe
também.•O GAC está localizado em um subdiretório denominado Assembly, do diretório do sistema
Windows.Instalando assemblies no GACQuando você tenta instalar um conjunto no GAC, os
componentes de segurança do CLR devem primeiroverifique se a assinatura digital no assembly é válida.
Se não houver assinatura digital ou se for inválida,o sistema não o instalará no GAC.Esta é uma
verificação única, no entanto. Depois que uma montagem está no GAC, nenhuma verificação adicional é
necessáriaquando é referenciado por um programa em execução.O utilitário de linha de comando
gacutil.exe permite adicionar e excluir assemblies do GAC eliste os assemblies que ele contém. Os três
sinalizadores mais úteis são os seguintes:•/ i: insere uma montagem no GAC•/ u: desinstala um
assembly do GAC•/ l: Lista os assemblies no GACBaixe a partir de Wow! e-book <www.wowebook.com>

Página 323

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS293Execução lado a lado no GACDepois que um assembly é


implantado no GAC, ele pode ser usado por outros assemblies no sistema.Lembre-se, entretanto, de que
a identidade de um assembly consiste em todas as quatro partes do nome totalmente
qualificado.Portanto, se o número da versão de uma biblioteca mudar ou se ela tiver uma chave pública
diferente, essas diferençasespecificar conjuntos diferentes.O resultado é que pode haver muitos
assemblies diferentes no GAC que têm o mesmo arquivonome. Embora tenham o mesmo nome de
arquivo, são montagens diferentes e coexistem perfeitamentejuntos no GAC. Isso torna mais fácil para
diferentes aplicativos usarem versões diferentes do mesmoDLL ao mesmo tempo, uma vez que são
assemblies diferentes com identidades diferentes. Isso é chamado de ladoexecução lado a lado .A Figura
10-18 ilustra quatro DLLs diferentes no GAC, todas com o mesmo nome de arquivo—MyLibrary.dll.
Olhando para a figura, você pode ver que os três primeiros vêm da mesma empresa,porque eles têm a
mesma chave pública, e o quarto vem de uma fonte diferente, pois tem umchave pública diferente. Essas
versões diferem da seguinte forma:•Uma versão em inglês 1.0.0.0, da empresa A•Uma versão em inglês
2.0.0.0, da empresa A•Uma versão alemã 1.0.0.0, da empresa A•Uma versão em inglês 1.0.0.0, da
empresa BFigura 10-18 . Quatro DLLs lado a lado diferentes no GAC

Página 324

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS294Arquivos de configuraçãoOs arquivos de configuração


contêm informações sobre o aplicativo, para uso pelo CLR em tempo de execução. Eles podeminstruir o
CLR a fazer coisas como usar uma versão diferente de uma DLL ou procurar em diretórios adicionaisao
pesquisar uma DLL referenciada pelo programa.Os arquivos de configuração consistem em código XML e
não contêm código C #. Os detalhes de escrever o XMLo código está além do escopo deste texto, mas
você deve entender o propósito dos arquivos de configuraçãoe como eles são usados. Uma maneira pela
qual eles são usados é para atualizar um conjunto de aplicativos para usar o novoversão de uma
DLL.Suponha, por exemplo, que você tenha um aplicativo que faz referência a uma DLL no GAC. A
identidade dea referência no manifesto do aplicativo deve corresponder exatamente à identidade do
assembly no GAC. E seuma nova versão da DLL é lançada, ela pode ser adicionada ao GAC, onde pode
coexistir alegremente com oversão antiga.O aplicativo, no entanto, ainda tem embutida em seu
manifesto a identidade da versão antiga doDLL. A menos que você recompile o aplicativo e faça
referência à nova versão da DLL, ele irácontinue a usar a versão antiga. Tudo bem, se é isso que você
quer.Se, no entanto, você não deseja recompilar o aplicativo, mas deseja que ele use a nova DLL,
entãopode criar um arquivo de configuração dizendo ao CLR para usar a nova versão em vez da versão
antiga. oarquivo de configuração é colocado no diretório do aplicativo.A Figura 10-19 ilustra objetos no
processo de tempo de execução. O aplicativo MyProgram.exe à esquerdasolicita a versão 1.0.0.0 do
MyLibrary.dll, conforme indicado pela seta tracejada. Mas o aplicativo tem umarquivo de configuração,
que instrui o CLR a carregar a versão 2.0.0.0. Observe que o nome doarquivo de configuração consiste no
nome completo do arquivo executável, incluindo a extensão, mais oextensão adicional .config.Figura 10-
19 . Usando um arquivo de configuração para vincular a uma nova versão

Página 325

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS295Assinatura AtrasadaÉ importante que as empresas


guardem cuidadosamente a chave privada de seu par oficial de chaves pública / privada.Caso contrário,
se pessoas não confiáveis conseguissem obtê-lo, poderiam publicar o código disfarçado decódigo da
empresa. Para evitar isso, as empresas claramente não podem permitir o acesso gratuito ao arquivo que
contém seuspar de chave pública / privada. Em grandes empresas, a nomenclatura final forte de uma
montagem é frequentemente realizada emno final do processo de desenvolvimento, por um grupo
especial com acesso ao par de chaves.Isso pode causar problemas, no entanto, nos processos de
desenvolvimento e teste, por vários motivos.Primeiro, uma vez que a chave pública é um dos quatro
componentes da identidade de um assembly, ela não pode ser definida até que ochave pública é
fornecida. Além disso, um assembly com nome fraco não pode ser implantado no GAC. Tanto oos
desenvolvedores e testadores precisam ser capazes de compilar e testar o código da forma como será
implementado emlançamento, incluindo sua identidade e localização no GAC.Para permitir isso, há uma
forma modificada de atribuição de um nome forte, chamada assinatura atrasada ouassinatura parcial ,
que supera esses problemas, mas sem liberar o acesso à chave privada.Na assinatura atrasada, o
compilador usa apenas a chave pública do par de chaves pública / privada. O públicoA chave pode então
ser colocada no manifesto para completar a identidade do assembly. A assinatura atrasada também usa
umbloco de 0s para reservar espaço para a assinatura digital.Para criar um assembly assinado com
atraso, você deve fazer duas coisas. Primeiro, crie uma cópia do arquivo-chave quetem apenas a chave
pública, em vez do par de chaves pública / privada. Em seguida, adicione um atributo adicional
chamadoDelaySignAttribute para o escopo do assembly do código-fonte e defina seu valor como true.

Página 326

CAPÍTULO 10 ■ NAMESPACES E CONJUNTOS296A Figura 10-20 mostra a entrada e a saída para produzir
um assembly assinado com retardo. Observe oseguindo na figura:•Na entrada, o DelaySignAttribute está
localizado nos arquivos de origem e o arquivo de chave contém apenasa chave pública.•Na saída, há um
espaço reservado para a assinatura digital na parte inferior da montagem.Figura 10-20 . Criação de uma
montagem assinada com atrasoSe você tentar implantar o assembly assinado com atraso no GAC, o CLR
não permitirá isso, porque não éfortemente nomeado. Para implantá-lo em uma máquina específica,
você deve primeiro emitir um comando de linha de comandoque desativa a verificação de assinatura do
GAC nessa máquina, apenas para este conjunto, e permite que sejainstalado no GAC. Para fazer isso,
emita o seguinte comando no prompt de comando do Visual Studio.sn –vr MyAssembly.dllAgora você
olhou para assemblies com nomes fracos, assemblies assinados com atraso e com nomes
fortesassembléias. A Figura 10-21 resume as diferenças em suas estruturas.Figura 10-21 . As estruturas
das diferentes etapas de assinatura de montagem

Página 327

CAPÍTULO 11■ ■ ■297Exceções■ O que são exceções?■ A declaração try■ As classes de exceção■ A
cláusula catch■ Exemplos usando cláusulas catch específicas■ A seção de cláusulas gerais■ O bloco
final■ Encontrando um manipulador para uma exceção■ Pesquisando mais longe■ Lançamento de
exceções■ Jogando sem um objeto de exceção

Página 328

CAPÍTULO 11 ■ EXCEÇÕES298O que são exceções?Uma exceção é um erro de tempo de execução em


um programa que viola um sistema ou restrição do aplicativo, ou umcondição que não deve ocorrer
durante a operação normal. Exemplos são quando um programa tentadivide um número por zero ou
tenta escrever em um arquivo somente leitura. Quando isso ocorre, o sistema captura oerro e gera uma
exceção.Se o programa não tiver fornecido o código para tratar a exceção, o sistema interromperá o
programa. Paraexemplo, o código a seguir levanta uma exceção quando tenta dividir por zero:static void
Main (){int x = 10, y = 0;x / = y;// Tenta dividir por zero - levanta uma exceção}Quando este código é
executado, o sistema exibe a seguinte mensagem de erro:Exceção não tratada:
System.DivideByZeroException: tentativa de divisão por zero.em Exceptions_1.Program.Main () em C: \
Progs \ Exceptions \ Program.cs: linha 12

Página 329

CAPÍTULO 11 ■ EXCEÇÕES299A declaração tryA instrução try permite que você designe blocos de código
para serem protegidos contra exceções e fornecercódigo para tratá-los, caso ocorram. A instrução try
consiste em três seções, conforme mostrado na Figura 11-1.•O bloco try contém o código que está
sendo protegido contra exceções.•A seção cláusulas catch contém uma ou mais cláusulas catch . Estes
são blocos de código para lidaras exceções. Eles também são conhecidos como manipuladores de
exceção .•O bloco finally contém código a ser executado em todas as circunstâncias, seja ou nãoexceção
é levantada.Figura 11-1 . Estrutura da instrução try

Página 330

CAPÍTULO 11 ■ EXCEÇÕES300Lidando com a exceçãoO exemplo anterior mostrou que tentar dividir por
zero causa uma exceção. Você pode modificar oprograma para lidar com essa exceção, colocando o
código dentro de um bloco try e fornecendo uma captura simplescláusula. Quando a exceção é gerada,
ela é capturada e tratada no bloco catch.static void Main (){int x = 10;experimentar{int y = 0;x / = y;//
Gera uma exceção}pegar{...// Código para lidar com a exceçãoConsole.WriteLine ("Manipulando todas as
exceções - Continue em execução");}}Este código produz a seguinte mensagem. Observe que, além da
mensagem de saída, não háindicação de que ocorreu uma exceção.Lidando com todas as exceções -
Continue em execução

Página 331

CAPÍTULO 11 ■ EXCEÇÕES301As classes de exceçãoExistem muitos tipos diferentes de exceções que


podem ocorrer em um programa. O BCL define uma série declasses de exceção, cada uma
representando um tipo específico. Quando ocorre, o CLR faz o seguinte:•Ele cria um objeto de exceção
para o tipo.•Ele procura uma cláusula catch apropriada para lidar com isso.Todas as classes de exceção
são basicamente derivadas da classe System.Exception. A Figura 11-2 mostra umparte da hierarquia de
herança de exceção.Figura 11-2 . Estrutura da hierarquia de exceção

Página 332

CAPÍTULO 11 ■ EXCEÇÕES302Um objeto de exceção contém propriedades somente leitura com


informações sobre a exceção quecausou isso. A Tabela 11-1 mostra algumas dessas propriedades.Tabela
11-1 . Propriedades selecionadas de um objeto de
exceçãoPropriedadeTipoDescriçãomensagemcordaEsta propriedade contém uma mensagem de erro
explicando a causa doexceção.StackTracecordaEsta propriedade contém informações que descrevem
onde a exceçãoocorreu.InnerExceptionExceçãoSe a exceção atual foi levantada por outra exceção, esta
propriedadecontém uma referência à exceção anterior.HelpLinkcordaEsta propriedade pode ser definida
por exceções definidas pelo aplicativo para fornecer umURN ou URL para obter informações sobre a
causa da exceção.FontecordaSe não for definido por uma exceção definida pelo aplicativo, esta
propriedade contémo nome da montagem onde a exceção se originou.Baixe a partir de Wow! e-book
<www.wowebook.com>

Página 333

CAPÍTULO 11 ■ EXCEÇÕES303A cláusula catchA cláusula catch trata das exceções. Existem três
formulários, permitindo diferentes níveis de processamento.A Figura 11-3 mostra os formulários.Figura
11-3 . As três formas da cláusula catchA cláusula catch geral pode aceitar qualquer exceção, mas não
pode determinar o tipo de exceção quecausou isso. Isso permite apenas o processamento geral e a
limpeza de qualquer exceção que possa ocorrer.A forma específica da cláusula catch leva o nome de uma
classe de exceção como parâmetro. Correspondeexceções da classe especificada ou classes de exceção
derivadas dela.A cláusula catch específica com forma de objeto fornece mais informações sobre a
exceção. istocorresponde a exceções da classe especificada ou classes de exceção derivadas dela. Dá-lhe
uma referência ao objeto de exceção criado pelo CLR, atribuindo-o à variável de exceção . Você pode
acessar opropriedades da variável de exceção dentro do bloco da cláusula catch para obter informações
específicas sobre oexceção levantada.Por exemplo, o código a seguir lida com exceções do tipo
IndexOutOfRangeException. Quando umocorrer, uma referência ao objeto de exceção real é passada
para o código com o nome do parâmetro e. otrês instruções WriteLine leem, cada uma, um campo de
string do objeto de exceção.Variável de exceção do tipo de exceção↓↓catch (IndexOutOfRangeException
e){Acessando a variável de exceção↓Console.WriteLine ("Mensagem: {0}", e.Message);Console.WriteLine
("Fonte: {0}", e.Source);Console.WriteLine ("Pilha: {0}", e.StackTrace);

Página 334

CAPÍTULO 11 ■ EXCEÇÕES304Exemplos usando cláusulas catch específicasVoltando ao nosso exemplo


de divisão por zero, o código a seguir modifica a cláusula catch anterior paralidar especificamente com
exceções da classe DivideByZeroException. Enquanto no exemplo anterior, oa cláusula catch trataria
qualquer exceção levantada no bloco try, o exemplo atual somente trataráaqueles da classe
DivideByZeroException.int x = 10;experimentar{int y = 0;x / = y;// Gera uma exceção}Tipo de
exceção↓catch (DivideByZeroException){...Console.WriteLine ("Tratamento de uma exceção.");}Você
pode modificar ainda mais a cláusula catch para usar uma variável de exceção. Isso permite que você
acesse oobjeto de exceção dentro do bloco catch.int x = 10;experimentar{int y = 0;x / = y;// Gera uma
exceção}Variável de exceção do tipo de exceção↓↓catch (DivideByZeroException e){Acessando a variável
de exceção↓Console.WriteLine ("Mensagem: {0}", e.Message);Console.WriteLine ("Fonte: {0}",
e.Source);Console.WriteLine ("Pilha: {0}", e.StackTrace);}Este código produz a seguinte saída:Mensagem:
Tentativa de divisão por zero.Fonte: Exceções 1Pilha: em Exceptions_1.Program.Main () em C: \ Progs \
Exceptions 1 \Exceções 1 \ Program.cs: linha 14

Página 335

CAPÍTULO 11 ■ EXCEÇÕES305The catch Clauses SectionO objetivo de uma cláusula catch é permitir que
você trate uma exceção de uma maneira elegante. Se você pegara cláusula tem a forma de um
parâmetro, então o sistema definiu essa variável de exceção para umreferência ao objeto de exceção,
que você pode inspecionar para determinar a causa da exceção. Se oexceção foi o resultado de uma
exceção anterior, você pode obter uma referência a essa exceção anteriorobjeto de exceção da
propriedade InnerException da variável de exceção.A seção de cláusulas catch pode conter várias
cláusulas catch. A Figura 11-4 mostra um resumo doseção de cláusulas catch.Figura 11-4 . Estrutura da
seção de cláusulas catch de uma instrução tryQuando uma exceção é levantada, o sistema pesquisa a
lista de cláusulas catch em ordem, e o primeiroA cláusula catch que corresponde ao tipo do objeto de
exceção é executada. Por causa disso, existem doisregras importantes para ordenar as cláusulas catch.
Eles são os seguintes:•As cláusulas catch específicas devem ser ordenadas com os tipos de exceção mais
específicos primeiro,progredindo para o mais geral. Por exemplo, se você declarar uma classe de exceção
derivada deNullReferenceException, a cláusula catch para seu tipo de exceção derivada deve ser listada
antesa cláusula catch para NullReferenceException.•Se houver uma cláusula catch geral, ela deve ser a
última, depois de todas as cláusulas catch específicas. Usando o geralcláusula catch é desencorajada.
Você deve usar uma das cláusulas catch específicas, se possível. oa cláusula catch geral esconde bugs
permitindo que o programa continue a execução e pode deixar oprograma em um estado desconhecido.
Página 336

CAPÍTULO 11 ■ EXCEÇÕES306O bloco finalmenteSe o fluxo de controle de um programa entra em uma


instrução try que tem um bloco finally, o bloco finally é sempreexecutado. A Figura 11-5 mostra o fluxo
de controle.•Se nenhuma exceção ocorrer dentro do bloco try, no final do bloco try, o controle pula
qualquercatch cláusulas e vai para o bloco finally.•Se ocorrer uma exceção dentro do bloco try, a cláusula
catch apropriada nas cláusulas catchseção é executada, seguida pela execução do bloco finally.Figura 11-
5 . Execução do bloco finallyO bloco finally sempre será executado antes de retornar ao código de
chamada, mesmo se um bloco trytem uma instrução de retorno ou uma exceção é lançada no bloco
catch. Por exemplo, no código a seguir,há uma instrução de retorno no meio do bloco try que é
executado sob certas condições. estenão permite que ele ignore a instrução finally.experimentar{if (inVal
<10) {Console.Write ("Primeira Ramificação -");Retorna;}outroConsole.Write ("Segunda Filial
-");}finalmente{Console.WriteLine ("Na declaração finalmente"); }Este código produz a seguinte saída
quando a variável inVal tem o valor 5:First Branch - In finally statement

Página 337

CAPÍTULO 11 ■ EXCEÇÕES307Encontrando um manipulador para uma exceçãoQuando um programa


levanta uma exceção, o sistema verifica se o programa forneceu ummanipulador para ele. A Figura 11-6
mostra o fluxo de controle.•Se a exceção ocorreu dentro de um bloco try, o sistema irá verificar se
alguma das capturascláusulas podem tratar a exceção.•Se uma cláusula catch apropriada for encontrada,
ocorre o seguinte:- A cláusula catch é executada.- Se houver um bloco finally, ele será executado.- A
execução continua após o final da instrução try (ou seja, após o bloco finally, ouapós a última cláusula
catch se não houver um bloco finally).Figura 11-6 . Exceção com manipulador na instrução try atual

Página 338

CAPÍTULO 11 ■ EXCEÇÕES308Pesquisando MaisSe a exceção foi levantada em uma seção de código que
não foi protegida por uma instrução try ou se o trydeclaração não tem um manipulador de exceção
correspondente, o sistema terá que procurar mais por ummanipulador correspondente. Ele fará isso
pesquisando na pilha de chamadas, em sequência, para ver se há umencerrando o bloco try com um
manipulador correspondente.A Figura 11-7 ilustra o processo de pesquisa. À esquerda da figura está a
estrutura de chamada do código,e à direita está a pilha de chamadas. A figura mostra que o Método2 é
chamado de dentro do bloco try deMétodo 1. Se ocorrer uma exceção dentro do bloco try no Método2,
o sistema fará o seguinte:•Primeiro, ele verifica se o Método2 tem manipuladores de exceção que
podem manipular a exceção.- Nesse caso, o Método2 trata disso e a execução do programa continua.- Se
não, o sistema continua descendo a pilha de chamadas para o Método1, procurando um
apropriadomanipulador.•Se o Método1 tiver uma cláusula catch apropriada, o sistema fará o seguinte:-
Volta ao topo da pilha de chamadas - que é o Método2- Executa o bloco final de Método2 e tira o
Método2 da pilha- Executa a cláusula catch do Método1 e seu bloco finally•Se o Método 1 não tiver uma
cláusula catch apropriada, o sistema continuará pesquisando opilha de chamadas.Figura 11-7 .
Pesquisando na pilha de chamadas
Página 339

CAPÍTULO 11 ■ EXCEÇÕES309Algoritmo GeralA Figura 11-8 mostra o algoritmo geral para lidar com uma
exceção.Figura 11-8 . O algoritmo geral para lidar com uma exceção

Página 340

CAPÍTULO 11 ■ EXCEÇÕES310Exemplo de pesquisa na pilha de chamadasNo código a seguir, Main inicia


a execução e chama o método A, que chama o método B. Uma descrição ediagrama do processo são
fornecidos após o código e na Figura 11-9.programa de aula{static void Main (){MyClass MCls = new
MyClass ();experimentar{MCls.A (); }catch (DivideByZeroException e){Console.WriteLine ("cláusula catch
in Main ()"); }finalmente{Console.WriteLine ("cláusula finalmente em Main ()"); }Console.WriteLine
("Após a instrução try in Main.");Console.WriteLine ("-- Continue correndo.");}}classe MyClass{public
void A (){experimentar{B (); }catch (System.NullReferenceException){Console.WriteLine ("cláusula catch
em A ()"); }finalmente{Console.WriteLine ("cláusula finalmente em A ()"); }}vazio B (){int x = 10, y =
0;experimentar{x / = y; }catch (System.IndexOutOfRangeException){Console.WriteLine ("cláusula catch
em B ()"); }finalmente{Console.WriteLine ("cláusula finalmente em B ()"); }}}

Página 341

CAPÍTULO 11 ■ EXCEÇÕES311Este código produz a seguinte saída:finalmente cláusula em B ()finalmente


cláusula em A ()cláusula catch em Main ()finalmente cláusula em Main ()Depois de tentar a instrução em
Main.-- Continue correndo.1. Main chama A, que chama B, que encontra uma exceção
DivideByZeroException.2. O sistema verifica a seção catch de B em busca de uma cláusula catch
correspondente. Embora tenha um paraIndexOutOfRangeException, não há um para
DivideByZeroException.3. O sistema então desce a pilha de chamadas e verifica a seção catch de A, onde
encontra que Atambém não tem uma cláusula catch correspondente.4. O sistema continua descendo a
pilha de chamadas e verifica a seção da cláusula catch principal, ondeacha que o principal não tem uma
cláusula DivideByZeroException captura.5. Embora a cláusula catch correspondente agora tenha sido
localizada, ela ainda não foi executada . Em vez disso, oo sistema volta ao topo da pilha, executa a
cláusula final de B e retira B da chamadapilha.6. O sistema então se move para A, executa sua cláusula
finally e retira A da pilha de chamadas.7. Finalmente, a cláusula catch correspondente de Main é
executada, seguida por sua cláusula finally. Execuçãoem seguida, continua após o final da instrução try
de Main.

Página 342

CAPÍTULO 11 ■ EXCEÇÕES312Figura 11-9 . Procurando na pilha por um manipulador de exceçãoBaixe a


partir de Wow! e-book <www.wowebook.com>

Página 343

CAPÍTULO 11 ■ EXCEÇÕES313Lançamento de exceçõesVocê pode fazer seu código gerar uma exceção
explicitamente usando a instrução throw. A sintaxe para oA declaração de lançamento é a
seguinte:throw ExceptionObject ;Por exemplo, o código a seguir define um método chamado PrintArg,
que recebe um argumento de stringe imprime. Dentro do bloco try, ele primeiro verifica se o argumento
não é nulo. Se for, écria uma instância ArgumentNullException e a lança. A instância de exceção é
capturada na capturadeclaração, e a mensagem de erro é impressa. Main chama o método duas vezes:
uma vez com um argumento nulo eentão com um argumento válido.classe MyClass{public static void
PrintArg (string arg){experimentar{if (arg == null)Forneça o nome do argumento
nulo{↓ArgumentNullException myEx = new ArgumentNullException ("arg");jogue
myEx;}Console.WriteLine (arg);}catch (ArgumentNullException e){Console.WriteLine ("Mensagem: {0}",
e.Message);}}}programa de aula{static void Main (){string s = nulo;MyClass.PrintArg (s);MyClass.PrintArg
("Olá!");}}Este código produz a seguinte saída:Mensagem: o valor não pode ser nulo.Nome do
parâmetro: argOlá!

Página 344

CAPÍTULO 11 ■ EXCEÇÕES314Lançando sem um objeto de exceçãoA instrução throw também pode ser
usada sem um objeto de exceção, dentro de um bloco catch.•Este formulário relança a exceção atual, e
o sistema continua sua busca pormanipuladores para isso.•Este formulário pode ser usado apenas
dentro de uma instrução catch.Por exemplo, o código a seguir relança a exceção de dentro da primeira
cláusula catch:classe MyClass{public static void PrintArg (string arg){experimentar{experimentar{if (arg
== null)Forneça o nome do argumento nulo{↓ArgumentNullException myEx = new
ArgumentNullException ("arg");jogue myEx;}Console.WriteLine (arg);}catch (ArgumentNullException e)
{Console.WriteLine ("Inner Catch: {0}", e.Message);lançar;} ↑} Jogue novamente a exceção, sem
parâmetros adicionaispegar{Console.WriteLine ("Outer Catch: Handling an Exception.");}}}class Program
{static void Main () {string s = nulo;MyClass.PrintArg (s);}}

Página 345

CAPÍTULO 11 ■ EXCEÇÕES315Este código produz a seguinte saída:Captura interna: o valor não pode ser
nulo.Nome do parâmetro: argCaptura externa: Lidando com uma exceção.

Página 346

Página 347

CAPÍTULO 12■ ■ ■317Structs■ O que são structs?■ Estruturas são tipos de valor■ Atribuindo a uma
estrutura■ Construtores e destruidores■ Inicializadores de campo não são permitidos■ As estruturas
são seladas■ Boxe e Unboxing■ Estruturas como valores e parâmetros de retorno■ Informações
adicionais sobre estruturas

Página 348

CAPÍTULO 12 ■ STRUTTS318O que são structs?Structs são tipos de dados definidos pelo programador,
muito semelhantes às classes. Eles têm membros de dados emembros da função. Embora semelhante às
classes, há uma série de diferenças importantes. A maioriaos mais importantes são os seguintes:•Classes
são tipos de referência e structs são tipos de valor.•As estruturas são vedadas implicitamente, o que
significa que não podem ser derivadas.A sintaxe para declarar uma estrutura é semelhante à de declarar
uma classe:Palavra-chave↓struct StructName{Declarações de membros}Por exemplo, o código a seguir
declara uma estrutura chamada Point. Possui dois campos públicos, chamados X eY. Em Main, três
variáveis do tipo de estrutura Point são declaradas e seus valores são atribuídos e impressos.struct
Point{public int X;public int Y;}programa de aula{static void Main (){Aponte primeiro, segundo,
terceiro;primeiro.X = 10; primeiro.Y = 10;segundo.X = 20; segundo.Y = 20;terceiro.X = primeiro.X +
segundo.X;terceiro.Y = primeiro.Y + segundo.Y;Console.WriteLine ("primeiro: {0}, {1}", primeiro.X,
primeiro.Y);Console.WriteLine ("segundo: {0}, {1}", segundo.X, segundo.Y);Console.WriteLine ("terceiro:
{0}, {1}", terceiro.X, terceiro.Y);}}

Página 349

CAPÍTULO 12 ■ STRUTTS319Estruturas são tipos de valorComo acontece com todos os tipos de valor,
uma variável de um tipo de estrutura contém seus próprios dados. Consequentemente:•Uma variável de
um tipo de estrutura não pode ser nula.•Duas variáveis de structs não podem se referir ao mesmo
objeto.Por exemplo, o código a seguir declara uma classe chamada CSimple, uma estrutura chamada
Simple e uma variávelDe cada. A Figura 12-1 mostra como os dois seriam organizados na memória.classe
CSimple{public int X;public int Y;}estrutura simples{public int X;public int Y;}programa de aula{static void
Main (){CSimple cs = novo CSimple ();Ss simples = novo simples ();...Figura 12-1 . Arranjo de memória de
uma classe versus uma estrutura

Página 350

CAPÍTULO 12 ■ STRUTTS320Atribuindo a um StructAtribuir uma estrutura a outra copia os valores de


uma estrutura para a outra. Isso é bem diferente decopiar de uma variável de classe, onde apenas a
referência é copiada.A Figura 12-2 mostra a diferença entre a atribuição de uma variável de classe e uma
variável de estrutura.Observe que após a atribuição da classe, cs2 está apontando para o mesmo objeto
no heap que cs1. Mas depois doatribuição de estrutura, os valores dos membros ss2 são cópias daqueles
em ss1.classe CSimple{public int X; public int Y; }estrutura simples{public int X; public int Y; }programa de
aula{static void Main (){CSimple cs1 = novo CSimple (), cs2 = nulo;// Instâncias de classeSimples ss1 =
novo Simples (), ss2 = novo Simples (); // Instâncias Structcs1.X = ss1.X = 5;// Atribuir 5 para ss1.X e
cs1.Xcs1.Y = ss1.Y = 10;// Atribua 10 a ss1.Y e cs1.Ycs2 = cs1;// Atribuir instância de classess2 = ss1;//
Atribuir instância de estrutura}}Figura 12-2 . Atribuindo uma variável de classe e uma variável de
estrutura

Página 351

CAPÍTULO 12 ■ STRUTTS321Construtores e DestruidoresAs estruturas podem ter construtores de


instância e estáticos, mas destruidores não são permitidos.Construtores de instânciaA linguagem fornece
implicitamente um construtor sem parâmetros para cada estrutura. Este construtor define cadados
membros da estrutura para o valor padrão para esse tipo. Os membros do valor são definidos com seus
valores padrão.Os membros de referência são definidos como nulos.O construtor sem parâmetros
predefinido existe para cada estrutura - e você não pode excluir ou redefiniristo. Você pode, no entanto,
criar construtores adicionais, desde que eles tenham parâmetros. Observe que este édiferente das
classes. Para classes, o compilador fornecerá um construtor implícito sem parâmetros apenas senenhum
outro construtor é declarado.Para chamar um construtor, incluindo o construtor implícito sem
parâmetros, use o operador new. Aviso prévioque o novo operador é usado mesmo que a memória não
seja alocada no heap.Por exemplo, o código a seguir declara uma estrutura simples com um construtor
que leva dois intparâmetros. Main cria duas instâncias da estrutura - uma usando o construtor implícito
sem parâmetrose o segundo com o construtor declarado de dois parâmetros.estrutura simples{public int
X;public int Y;public Simples (int a, int b)// Construtor com parâmetros{X = a;Y = b;}}programa de
aula{static void Main (){Chamar construtor implícito↓Simples s1 = novo Simples ();Simples s2 = novo
Simples (5, 10);↑Construtor de chamadasConsole.WriteLine ("{0}, {1}", s1.X, s1.Y);Console.WriteLine ("{0},
{1}", s2.X, s2.Y);}}

Página 352

CAPÍTULO 12 ■ STRUTTS322Você também pode criar uma instância de uma estrutura sem usar o novo
operador. Se você fizer isso, no entanto,existem algumas restrições, que são as seguintes:•Você não
pode usar o valor de um membro de dados até que tenha definido explicitamente.•Você não pode
chamar qualquer membro de função da estrutura até que todos os membros de dados tenham
sidoatribuído.Por exemplo, o código a seguir mostra duas instâncias de struct Simple criadas sem usar
onovo operador. Quando há uma tentativa de acessar s1 sem definir explicitamente os valores do
membro de dados,o compilador produz uma mensagem de erro. Não há problemas de leitura de s2 após
atribuir valoresaos seus membros.estrutura simples{public int X;public int Y;}programa de aula{static
void Main (){ Sem chamadas de construtor↓ ↓S1, s2 simples;Console.WriteLine ("{0}, {1}", s1.X, s1.Y);//
Erro do compilador↑ ↑s2.X = 5;Ainda não atribuídos2.Y = 10;Console.WriteLine ("{0}, {1}", s2.X, s2.Y);//
ESTÁ BEM}}Baixe a partir de Wow! e-book <www.wowebook.com>

Página 353

CAPÍTULO 12 ■ STRUTTS323Construtores estáticosTal como acontece com as classes, os construtores


estáticos de structs criam e inicializam os membros de dados estáticos e não podemmembros da
instância de referência. Os construtores estáticos para estruturas seguem as mesmas regras das
classes.Um construtor estático é chamado antes da primeira das duas ações a seguir:•Uma chamada
para um construtor declarado explicitamente•Uma referência a um membro estático da
estruturaResumo de construtores e destruidoresA Tabela 12-1 resume o uso de construtores e
destruidores com estruturas.Tabela 12-1. Resumo de construtores e
destruidoresTipoDescriçãoConstrutor de instância (sem parâmetros)Não pode ser declarado no
programa. Um implícitoconstrutor é fornecido pelo sistema para todas as estruturas. istonão pode ser
excluído ou redefinido pelo programa.Construtor de instância (com parâmetros) Pode ser declarado no
programa.Construtor estáticoPode ser declarado no programa.DestruidorNão pode ser declarado no
programa. Destruidores não sãopermitido.

Página 354
CAPÍTULO 12 ■ STRUTTS324Inicializadores de campo não são permitidosInicializadores de campo não
são permitidos em declarações de estrutura, conforme mostrado no código a seguir:estrutura
simples{Não permitido↓public int x = 0;// Erro de compilaçãopublic int y = 10;// Erro de
compilação}↑Não permitidoEstruturas são seladasAs estruturas são sempre vedadas implicitamente e,
portanto, você não pode derivar outras estruturas delas.Uma vez que structs não suportam herança, o
uso de vários modificadores de membros de classe commembros da estrutura não fariam sentido;
portanto, eles não podem ser usados em suas declarações. Os modificadoresque não podem ser usados
com structs são os seguintes:•protegido•interno•resumo•virtualAs próprias estruturas são, nos
bastidores, derivadas de System.ValueType, que é derivadodo objeto.As duas palavras-chave associadas
à herança que você pode usar com membros de estrutura são as novas e substituemmodificadores, ao
criar um membro com o mesmo nome de um membro da classe base System.ValueType,a partir da qual
todas as estruturas são derivadas.Boxing e UnboxingTal como acontece com outros dados de tipo de
valor, se você deseja usar uma instância de struct como um objeto de tipo de referência, você devefaça
uma cópia em caixa. O encaixotamento e o desencaixotamento são explicados no Capítulo 18.

Página 355

CAPÍTULO 12 ■ STRUTTS325Estruturas como valores e parâmetros de retornoAs estruturas podem ser


usadas como valores e parâmetros de retorno.•Valor de retorno : quando uma estrutura é um valor de
retorno, uma cópia é criada e retornada domembro da função.•Parâmetro de valor : quando uma
estrutura é usada como um parâmetro de valor, uma cópia do parâmetro realstruct é criado. A cópia é
usada na execução do método.•Parâmetros ref e out : se você usar uma estrutura como parâmetro ref
ou out, uma referência à estrutura épassado para o método para que os membros de dados possam ser
alterados.Informações adicionais sobre structsAlocar structs requer menos sobrecarga do que criar
instâncias de uma classe, portanto, usar structs em vez deas aulas às vezes podem melhorar o
desempenho - mas tome cuidado com o alto custo de encaixotar e desencaixotar.Finalmente, algumas
coisas que você deve saber sobre structs são as seguintes:•Os tipos simples predefinidos (int, short, long
e assim por diante), embora considerados primitivos no .NETe C #, são realmente implementados nos
bastidores em .NET como estruturas.•Você pode declarar estruturas parciais da mesma maneira que
classes parciais, conforme descrito no Capítulo 6.As estruturas, assim como as classes, podem
implementar interfaces, que serão abordadas no Capítulo 17.

Página 356

Página 357

CAPÍTULO 13■ ■ ■327Enumerações■ Enumerações■ Sinalizadores de bits■ Mais sobre Enums

Página 358

CAPÍTULO 13 ■ ENUMERAÇÕES328EnumeraçõesUma enumeração, ou enum, é um tipo definido pelo


programador, como uma classe ou estrutura.•Como structs, enums são tipos de valor e, portanto,
armazenam seus dados diretamente, em vez de separadamente,com uma referência e dados.•Enums
têm apenas um tipo de membro: constantes nomeadas com valores integrais.O código a seguir mostra
um exemplo da declaração de um novo tipo de enum chamado TrafficLight,que contém três membros.
Observe que a lista de declarações de membros é uma lista separada por vírgulas;não há ponto-e-vírgula
em uma declaração de enum.Nome da palavra-chave↓↓enum TrafficLight{Verde, ← Separado por vírgula
- sem ponto e vírgulaAmarelo, ← separados por vírgula - sem ponto e vírgulaVermelho}Cada tipo de
enum tem um tipo integral subjacente, que por padrão é int.•Cada membro enum é atribuído a um valor
constante do tipo subjacente.•Por padrão, o compilador atribui 0 ao primeiro membro e atribui a cada
membro subsequente ovalor um a mais do que o membro anterior.Por exemplo, no tipo TrafficLight, o
compilador atribui os valores int 0, 1 e 2 aos membrosVerde, Amarelo e Vermelho, respectivamente. Na
saída do código a seguir, você pode ver o subjacentevalores de membro convertendo-os para o tipo int.
A Figura 13-1 ilustra sua disposição na pilha.TrafficLight t1 = TrafficLight.Green;TrafficLight t2 =
TrafficLight.Yellow;TrafficLight t3 = TrafficLight.Red;Console.WriteLine ("{0}, \ t {1}", t1, (int)
t1);Console.WriteLine ("{0}, \ t {1}", t2, (int) t2);Console.WriteLine ("{0}, \ t {1} \ n", t3, (int) t3);↑Cast para
int

Página 359

CAPÍTULO 13 ■ ENUMERAÇÕES329Este código produz a seguinte saída:Verde, 0Amarelo, 1Vermelho


2Figura 13-1 . As constantes de membro de um enum são representadas por valores integrais
subjacentes.Você pode atribuir valores enum a variáveis do tipo enum. Por exemplo, o código a seguir
mostraa declaração de três variáveis do tipo TrafficLight. Observe que você pode atribuir literais de
membro avariáveis, ou você pode copiar o valor de outra variável do mesmo tipo.programa de
aula{static void Main (){Variável de TipoMembro↓ ↓↓TrafficLight t1 = TrafficLight.Red;// Atribuir de
membroTrafficLight t2 = TrafficLight.Green; // Atribuir de membroTrafficLight t3 = t2;// Atribuir da
variávelConsole.WriteLine (t1);Console.WriteLine (t2);Console.WriteLine (t3);}}Este código produz a
seguinte saída. Observe que os nomes dos membros são impressos como strings.VermelhoVerdeVerde

Página 360

CAPÍTULO 13 ■ ENUMERAÇÕES330Definir o tipo subjacente e os valores explícitosVocê pode usar um


tipo integral diferente de int, colocando dois-pontos e o nome do tipo após o nome do enum.O tipo
pode ser qualquer tipo inteiro. Todas as constantes de membro são do tipo subjacente do
enum.Cólon↓enum TrafficLight: ulong{↑...Tipo subjacenteOs valores das constantes de membro podem
ser quaisquer valores do tipo subjacente. Para definir explicitamente ovalor de um membro, use um
inicializador após seu nome na declaração enum. Pode haver duplicatavalores, embora não sejam nomes
duplicados, conforme mostrado aqui:enum TrafficLight{Verde = 10,Amarelo = 15,// Valores
duplicadosVermelho = 15// Valores duplicados}Por exemplo, o código na Figura 13-2 mostra duas
declarações equivalentes de enum TrafficLight.•O código à esquerda aceita o tipo e a numeração
padrão.•O código à direita define explicitamente o tipo subjacente como int e os membros como
valorescorrespondendo aos valores padrão.Figura 13-2 . Declarações de enum equivalentes

Página 361

CAPÍTULO 13 ■ ENUMERAÇÕES331Numeração implícita de membroVocê pode atribuir explicitamente


os valores para qualquer uma das constantes de membro. Se você não inicializar um membroconstante,
o compilador atribui a ela implicitamente um valor. A Figura 13-3 ilustra as regras que o compilador usa
paraatribuindo esses valores.•Os valores associados aos nomes dos membros não precisam ser
distintos.Figura 13-3 . O algoritmo para atribuir valores de membroPor exemplo, o código a seguir
declara duas enumerações. CardSuit aceita o implícitonumeração dos membros, conforme demonstrado
nos comentários. FaceCards define alguns membros explicitamente eaceita a numeração implícita dos
outros.enum CardSuit{Corações,// 0 - já que este é o primeiroClubes,// 1 - Um a mais que o
anteriorDiamantes,// 2 - Um a mais que o anteriorEspadas,// 3 - Um a mais que o anteriorMaxSuits// 4 -
Uma maneira comum de atribuir uma constante}// para o número de itens listados.enum FaceCards{//
Membro// Valor atribuídoJack= 11, // 11 - Definido explicitamenteRainha,// 12 - Um a mais que o
anteriorRei,// 13 - Um a mais que o anteriorÁs,// 14 - Um a mais que o anteriorNumberOfFaceCards =
4, // 4 - Definido explicitamenteSomeOtherValue,// 5 - Um a mais que o anteriorMaiorFaceCard = Ace //
14 - Ace é definido acima}

Página 362

CAPÍTULO 13 ■ ENUMERAÇÕES332Sinalizadores de bitsOs programadores há muito usam os diferentes


bits em uma única palavra como uma forma compacta de representar um conjunto desinalizadores de
ligar / desligar. Enums oferecem uma maneira conveniente de implementar isso.As etapas gerais são as
seguintes:1Determine quantos sinalizadores de bits você precisa e escolha um tipo integral sem sinal
com o suficientebits para segurá-los.2Determine o que cada posição de bit representa e dê um nome a
ele. Declare um enum dotipo integral escolhido, com cada membro representado por uma posição de
bit.3 -Use o operador OR bit a bit para definir os bits apropriados em uma palavra contendo os
sinalizadores de bit.4 -Descompacte os sinalizadores de bit usando o operador AND bit a bit ou o método
HasFlag.Por exemplo, o código a seguir mostra a declaração enum que representa as opções de um
cartãobaralho em um jogo de cartas. O tipo subjacente, uint, é mais do que suficiente para conter os
quatro sinalizadores de bits necessários.Observe o seguinte sobre o código:•Os membros têm nomes
que representam opções binárias.- Cada opção é representada por uma posição de bit específica na
palavra. As posições dos bits mantêm um0 ou 1.- Uma vez que um sinalizador de bit representa um
conjunto de bits que estão ligados ou desligados, você não deseja usar 0 como umvalor de membro. Já
tem um significado - que todos os sinalizadores de bits estão desligados.•A representação hexadecimal é
frequentemente usada ao trabalhar com padrões de bits porque há umacorrelação direta entre um
padrão de bits e sua representação hexadecimal do que com seu decimalrepresentação.•Decorar o
enum com o atributo Flags não é realmente necessário, mas fornece algunsconveniência, que discutirei
em breve. Os atributos são abordados no Capítulo 24.[Bandeiras]enum CardDeckSettings:
uint{SingleDeck = 0x01,// Bit 0LargePictures = 0x02,// Bit 1FancyNumbers = 0x04,// Bit 2Animação =
0x08// Bit 3}A Figura 13-4 ilustra essa enumeração.Baixe a partir de Wow! e-book
<www.wowebook.com>

Página 363

CAPÍTULO 13 ■ ENUMERAÇÕES333Figura 13-4 . Definição dos bits da bandeira e suas representações


individuaisPara criar uma palavra com os sinalizadores de bits apropriados, declare uma variável do tipo
enum e useo operador OR bit a bit para definir os bits necessários. Por exemplo, o código a seguir define
três dosquatro opções:Tipo Enum Palavra sinalizadoraSinalizadores de bits em OU
juntos↓↓↓CardDeckSettings ops = CardDeckSettings.SingleDeck| CardDeckSettings.FancyNumbers|
CardDeckSettings.Animation;Antes de C # 4.0, para determinar se um determinado bit foi definido, você
usaria o bit a bit ANDoperador com a palavra de sinalização e o sinalizador de bit.Por exemplo, o código
a seguir verifica um valor para ver se o sinalizador de bit FancyNumbers está definido. istofaz isso
colocando o AND nesse valor com o sinalizador de bit e depois comparando esse resultado com o
sinalizador de bit. Se obit foi definido no valor original, então o resultado da operação AND terá o
mesmo padrão de bit quea bandeira de bits.bool useFancyNumbers =(ops &
CardDeckSettings.FancyNumbers) == CardDeckSettings.FancyNumbers;↑↑Palavra bandeiraBandeira de
bitsA Figura 13-5 ilustra o processo de criação da palavra bandeira e, em seguida, verificar se umbit está
definido.Figura 13-5 . Produzindo uma palavra de bandeira e verificando-a para uma bandeira de bit
particular

Página 364

CAPÍTULO 13 ■ ENUMERAÇÕES334Este processo de verificar uma palavra de sinalização para um


determinado bit ou conjunto de bits é uma tarefa tão comum que C #4.0 introduziu um novo método de
instância para o tipo enum para fazer o processo para você. O método é chamadoHasFlag. Você o usa em
uma instância de uma palavra de sinalização e passa o sinalizador de bits que deseja verificar.Por
exemplo, a verificação anterior de useFancyNumbers pode ser significativamente reduzida e
simplificadaà seguinte declaração:UseFancyNumbers = ops.HasFlag
(CardDeckSettings.FancyNumbers);↑↑Palavra bandeiraBandeira de bitsO método HasFlag também pode
verificar vários sinalizadores de bits. Por exemplo, o código a seguir verificase a palavra de sinalização,
ops, tem os bits Animation e FancyNumbers definidos. O código faz oSegue:•A primeira instrução cria
uma instância de palavra de teste, chamada testFlags, com Animation eConjunto de bits
FancyNumbers.•Em seguida, ele passa testFlags como o parâmetro para o método HasFlags.•HasFlags
verifica se todos os sinalizadores definidos na palavra de teste também estão definidos na palavra de
sinalização, ops.Se estiverem, HasFlag retornará verdadeiro. Caso contrário, ele retorna
falso.CardDeckSettings testFlags =CardDeckSettings.Animation |
CardDeckSettings.FancyNumbers;UseAnimationAndFancyNumbers = ops.HasFlag (testFlags);↑↑Palavra
bandeiraPalavra de teste

Página 365

CAPÍTULO 13 ■ ENUMERAÇÕES335O atributo sinalizadoresAbordaremos os atributos no Capítulo 24,


mas vale a pena mencionar o atributo Sinalizadores aqui. Um atributoaparece como uma string entre
colchetes colocados na linha acima de uma declaração de classe. As bandeirasatributo não altera os
cálculos em tudo. No entanto, oferece vários recursos convenientes.Primeiro, ele informa o compilador,
os navegadores de objetos e outras ferramentas que examinam o código que os membrosdo enum
devem ser combinados como sinalizadores de bits, em vez de usados apenas como valores separados.
Isso permiteos navegadores para interpretar as variáveis do tipo enum de forma mais apropriada.Em
segundo lugar, permite que o método ToString de um enum forneça uma formatação mais apropriada
para ovalores de sinalizadores de bits. O método ToString pega um valor enum e o compara com os
valores domembros constantes do enum. Se corresponder a um dos membros, ToString retorna o nome
da string deo membro.Suponha, por exemplo, que você tenha usado a declaração enum para
CardDeckSettings (fornecida nocódigo anterior) e não usaram o atributo Flags. A primeira linha do
código a seguir cria umvariável (nomeada ops) do tipo enum e define o valor de um único bit de
sinalizador. A segunda linha usaToString para obter o nome da string do membro representado por esse
valor.CardDeckSettings ops = CardDeckSettings.FancyNumbers; // Defina o sinalizador de
bit.Console.WriteLine (ops.ToString ());// Imprime seu nome.Este código produz a seguinte
saída:FancyNumbers

Página 366

CAPÍTULO 13 ■ ENUMERAÇÕES336Isso é muito bom, mas suponha que você defina sinalizadores de
dois bits em vez de um, como no código a seguir.Suponha também que você não usou o atributo Flags na
declaração enum.// Definir sinalizadores de dois bits.ops = CardDeckSettings.FancyNumbers |
CardDeckSettings.Animation;Console.WriteLine (ops.ToString ());// Imprimir o que?O valor resultante de
ops é 12, onde 4 é da sinalização FancyNumbers e 8 é da animaçãobandeira. Na segunda linha, quando
ToString tenta pesquisar o valor na lista de membros enum,descobre que não há nenhum membro com
o valor 12 - então, ele apenas retorna a string que representa 12. Oa saída resultante é a seguinte:12Se,
no entanto, você alterar seu código para usar o atributo Flags antes da declaração do enum, estediz ao
método ToString que os bits podem ser considerados separadamente. Ao pesquisar o valor,descubra que
12 corresponde aos membros flag de dois bits FancyNumbers e Animation. Ele então voltariaa string
contendo seus nomes, separados por vírgula e espaço, conforme mostrado aqui:FancyNumbers,
animação

Página 367

CAPÍTULO 13 ■ ENUMERAÇÕES337Exemplo de uso de sinalizadores de bitsO código a seguir reúne


todas as partes do uso de sinalizadores de bits:[Bandeiras]enum CardDeckSettings: uint{SingleDeck =
0x01,// bit 0LargePictures = 0x02,// bit 1FancyNumbers = 0x04,// bit 2Animação = 0x08// bit 3}classe
MyClass{bool UseSingleDeck= falso,UseBigPics= falso,UseFancyNumbers= falso,UseAnimation=
falso,UseAnimationAndFancyNumbers = false;public void SetOptions (ops CardDeckSettings)
{UseSingleDeck = ops.HasFlag (CardDeckSettings.SingleDeck);UseBigPics= ops.HasFlag
(CardDeckSettings.LargePictures);UseFancyNumbers = ops.HasFlag
(CardDeckSettings.FancyNumbers);UseAnimation = ops.HasFlag
(CardDeckSettings.Animation);CardDeckSettings testFlags =CardDeckSettings.Animation |
CardDeckSettings.FancyNumbers;UseAnimationAndFancyNumbers = ops.HasFlag (testFlags);}public void
PrintOptions (){Console.WriteLine ("Configurações de opções:");Console.WriteLine ("Usar Deck Único- {0}
", UseSingleDeck);Console.WriteLine ("Use imagens grandes- {0} ", UseBigPics);Console.WriteLine ("Use
Fancy Numbers- {0} ", UseFancyNumbers);Console.WriteLine ("Mostrar animação- {0} ",
UseAnimation);Console.WriteLine ("Mostrar Animação e FancyNumbers -
{0}",UseAnimationAndFancyNumbers);}}
Página 368

CAPÍTULO 13 ■ ENUMERAÇÕES338programa de aula{static void Main (string [] args){MyClass mc = new


MyClass ();CardDeckSettings ops = CardDeckSettings.SingleDeck| CardDeckSettings.FancyNumbers|
CardDeckSettings.Animation;mc.SetOptions (ops);mc.PrintOptions ();}}Este código produz a seguinte
saída:Configurações de opções:Usar deck único- VerdadeUse imagens grandes- FalsoUse números
extravagantes- VerdadeMostrar animação- VerdadeMostrar animação e FancyNumbers - Verdadeiro

Página 369

CAPÍTULO 13 ■ ENUMERAÇÕES339Mais sobre EnumsEnums têm apenas um único tipo de membro: as


constantes de membro declaradas.•Você não pode usar modificadores com os membros. Todos eles têm
implicitamente a mesma acessibilidade queo enum.•Como os membros são estáticos, eles são acessíveis
mesmo se não houver variáveis do tipo enum.Use o nome do tipo enum, seguido por um ponto e o
nome do membro.Por exemplo, o código a seguir não cria nenhuma variável do tipo enum TrafficLight,
masos membros são acessíveis e podem ser impressos usando WriteLine.static void Main ()
{Console.WriteLine ("{0}", TrafficLight.Green);Console.WriteLine ("{0}",
TrafficLight.Yellow);Console.WriteLine ("{0}", TrafficLight.Red);}↑↑Nome Enum Nome do membro

Página 370

CAPÍTULO 13 ■ ENUMERAÇÕES340Um enum é um tipo distinto. Comparar membros de enum de


diferentes tipos de enum resulta em uma compilaçãoerro de tempo. Por exemplo, o código a seguir
declara dois tipos de enum.•A primeira instrução if é adequada porque compara membros diferentes do
mesmo tipo de enum.•A segunda instrução if produz um erro porque compara membros de enum
diferentestipos, embora suas estruturas e nomes de membros sejam exatamente os mesmos.enum
FirstEnum// Primeiro tipo de enum{Mem1,Mem2}enum SecondEnum// Segundo tipo de
enum{Mem1,Mem2}programa de aula{static void Main (){if (FirstEnum.Mem1 <FirstEnum.Mem2) // OK
- membros do mesmo tipo de enumConsole.WriteLine ("True");if (FirstEnum.Mem1
<SecondEnum.Mem1) // Erro - diferentes tipos de enumConsole.WriteLine ("True");}}

Página 371

CAPÍTULO 14■ ■ ■341Arrays■ Arrays■ Tipos de matrizes■ Uma matriz como um objeto■ Matrizes
unidimensionais e retangulares■ Instanciando uma matriz unidimensional ou retangular■ Acessando
elementos da matriz■ Inicializando um Array■ Matrizes denteadas■ Comparando matrizes
retangulares e denteadas■ A declaração foreach■ Matriz de covariância■ Membros de matriz
herdados úteis■ Comparando Tipos de Matriz

Página 372

CAPÍTULO 14 ■ ARRAYS342ArraysUma matriz é um conjunto de elementos de dados uniformes,


representados por um único nome de variável. O indivíduoos elementos são acessados usando o nome
da variável junto com um ou mais índices entre quadradoscolchetes, conforme mostrado aqui:Índice de
nome de matriz↓ ↓MyArray [4]DefiniçõesVamos começar com algumas definições importantes
relacionadas a arrays em C #.•Elementos : os itens de dados individuais de uma matriz são chamados de
elementos . Todos os elementos de uma matriz devemser do mesmo tipo ou derivado do mesmo
tipo.•Classificação / dimensões : os arrays podem ter qualquer número positivo de dimensões. O
número dedimensões que uma matriz tem é chamada de classificação .•Comprimento da dimensão :
cada dimensão de uma matriz tem um comprimento , que é o número de posições emessa
direção.•Comprimento da matriz : o número total de elementos contidos em uma matriz, em todas as
dimensões, é chamado decomprimento da matriz.Detalhes importantesA seguir estão alguns fatos gerais
importantes sobre matrizes C #:•Depois que um array é criado, seu tamanho é fixo. C # não oferece
suporte a matrizes dinâmicas.•Os índices de matriz são baseados em 0 . Ou seja, se o comprimento de
uma dimensão for n , os valores do índice variam de 0para n - 1. Por exemplo, a Figura 14-1 mostra as
dimensões e comprimentos de dois exemplos de matrizes.Observe que, para cada dimensão, os índices
variam de 0 a comprimento - 1.Figura 14-1 . Dimensões e tamanhosBaixe a partir de Wow! e-book
<www.wowebook.com>

Página 373

CAPÍTULO 14 ■ ARRAYS343Tipos de matrizesC # fornece dois tipos de matrizes:•Matrizes


unidimensionais podem ser consideradas uma única linha, ou vetor , de elementos.•Matrizes
multidimensionais são compostas de tal forma que cada posição no vetor primário é ela própria
umarray, chamado de subarray . As posições nos vetores de submatriz podem ser submatrizes.Além
disso, existem dois tipos de matrizes multidimensionais, matrizes retangulares e matrizes denteadas ,que
possuem as seguintes características:•Matrizes retangulares- São arrays multidimensionais onde todos
os subarrays em uma dimensão particular têm o mesmocomprimento- Sempre use um único conjunto
de colchetes, independentemente do número de dimensõesint x = meuVetor2 [4, 6, 1]// Um conjunto de
colchetes•Matrizes denteadas- São matrizes multidimensionais em que cada submatriz é uma matriz
independente- Pode ter submatrizes de diferentes comprimentos- Use um conjunto separado de
colchetes para cada dimensão da matrizjagArray1 [2] [7] [4]// Três conjuntos de colchetesA Figura 14-2
mostra os tipos de arrays disponíveis em C #.Figura 14-2 . Matrizes unidimensionais, retangulares e
denteadas

Página 374

CAPÍTULO 14 ■ ARRAYS344Uma matriz como um objetoUma instância de array é um objeto cujo tipo
deriva da classe System.Array. Uma vez que as matrizes são derivadas deessa classe base BCL, eles
herdam uma série de membros úteis, como o seguinte:•Rank: uma propriedade que retorna o número
de dimensões da matriz•Comprimento: uma propriedade que retorna o comprimento (o número total
de elementos) da matrizArrays são tipos de referência e, como acontece com todos os tipos de
referência, eles têm uma referência aos dados eo próprio objeto de dados. A referência está na pilha ou
no heap, e o próprio objeto de dados sempreestar na pilha. A Figura 14-3 mostra a configuração da
memória e os componentes de um array.Figura 14-3 . Estrutura de uma matrizEmbora uma matriz seja
sempre um tipo de referência, os elementos da matriz podem ser tipos de valor outipos de
referência.•Uma matriz é chamada de matriz de tipo de valor se os elementos armazenados forem tipos
de valor.•Um array é chamado de array de tipo de referência se os elementos armazenados no array são
referências deobjetos de tipo de referência.A Figura 14-4 mostra uma matriz de tipo de valor e uma
matriz de tipo de referência.Figura 14-4 . Os elementos podem ser valores ou referências.

Página 375

CAPÍTULO 14 ■ ARRAYS345Matrizes Unidimensionais e RetangularesSintaticamente, arrays


unidimensionais e arrays retangulares são muito semelhantes, então vou tratá-los juntos.Em seguida,
tratarei as matrizes irregulares separadamente.Declaração de uma matriz unidimensional ou
retangularPara declarar uma matriz unidimensional ou retangular, use um único conjunto de colchetes
entre o tipoe o nome da variável.Os especificadores de classificação são vírgulas entre colchetes. Eles
especificam o número de dimensões domatriz terá. A classificação é o número de vírgulas mais um. Por
exemplo, nenhuma vírgula indica ummatriz dimensional, uma vírgula indica uma matriz bidimensional e
assim por diante.O tipo base, junto com os especificadores de classificação, é o tipo da matriz. Por
exemplo, o seguintelinha de código declara uma matriz unidimensional de longos. O tipo da matriz é
long [], que é lido como“Uma série de comprimentos”.Especificadores de classificação = 1↓long []
secondArray;↑Tipo de matrizO código a seguir mostra exemplos de declarações de matrizes retangulares.
Observe o seguinte:•Você pode ter quantos especificadores de classificação forem necessários.•Você
não pode colocar comprimentos de dimensão de array na seção de tipo de array. A classificação faz parte
do arraytipo, mas os comprimentos das dimensões não fazem parte do tipo.•Quando uma matriz é
declarada, o número de dimensões é fixo. O comprimento das dimensões,entretanto, não é
determinado até que a matriz seja instanciada.Especificadores de classificação↓int [,,] firstArray;// Tipo
de array: array 3D de intint [,] arr1;// Tipo de array: array 2-D de intlong [,,] arr3;// Tipo de array: array 3-
D de longa↑Tipo de matrizlong [3,2,6] SecondArray;// Errado! Erro de compilação↑ ↑ ↑Comprimentos de
dimensão não permitidos!■ Nota Ao contrário de C / C ++, os colchetes seguem o tipo base, não o nome
da variável.

Página 376

CAPÍTULO 14 ■ ARRAYS346Instanciando uma Matriz Unidimensional ou RetangularPara instanciar uma


matriz, você usa uma expressão de criação de matriz . Uma expressão de criação de array consiste emo
novo operador, seguido pelo tipo básico, seguido por um par de colchetes. O comprimento de
cadadimensão é colocada em uma lista separada por vírgulas entre os colchetes.A seguir estão exemplos
de declarações de matriz unidimensional:•Array arr2 é um array unidimensional de quatro ints.•Array
mcArr é uma matriz unidimensional de quatro referências MyClass.•A Figura 14-5 mostra seus layouts na
memória.Quatro elementos↓int [] arr2 = novo int [4];MyClass [] mcArr = new MyClass [4];↑Expressão de
criação de matrizA seguir está um exemplo de uma matriz retangular. Array arr3 é um array
tridimensional.•O comprimento da matriz é 3 * 6 * 2 = 36.•A Figura 14-5 mostra seu layout na
memória.Comprimentos das dimensões↓int [,,] arr3 = novo int [3,6,2];Figura 14-5 . Declarando e
instanciando matrizes■ Nota Ao contrário das expressões de criação de objeto, as expressões de criação
de array não contêm parênteses - mesmo paramatrizes de tipo de referência.

Página 377
CAPÍTULO 14 ■ ARRAYS347Acessando Elementos de MatrizUm elemento da matriz é acessado usando
um valor inteiro como um índice na matriz.•Cada dimensão usa indexação baseada em 0.•O índice é
colocado entre colchetes após o nome da matriz.O código a seguir mostra exemplos de declaração,
gravação e leitura de um modelo unidimensionale uma matriz bidimensional:int [] intArr1 = novo int
[15];// Declara array 1-D.intArr1 [2] = 10;// Escreva no elemento 2 da matriz.int var1 = intArr1 [2];// Leia
a partir do elemento 2 do array.int [,] intArr2 = novo int [5,10]; // Declara array 2-D.intArr2 [2,3] = 7;//
Escreva no array.int var2 = intArr2 [2,3]; // Leia a partir do array.O código a seguir mostra o processo
completo de criação e acesso a uma matriz unidimensional:int [] myIntArray;// Declare o
array.myIntArray = new int [4];// Instancie a matriz.para (int i = 0; i <4; i ++)// Defina os
valores.myIntArray [i] = i * 10;// Leia e exiba os valores de cada elemento.para (int i = 0; i <4; i +
+)Console.WriteLine ("Valor do elemento {0} = {1}", i, myIntArray [i]);Este código produz a seguinte
saída:O valor do elemento 0 é 0O valor do elemento 1 é 10O valor do elemento 2 é 20O valor do
elemento 3 é 30

Página 378

CAPÍTULO 14 ■ ARRAYS348Inicializando um ArraySempre que uma matriz é criada, cada um dos


elementos é inicializado automaticamente com o valor padrão para otipo. Os valores padrão para os
tipos predefinidos são 0 para tipos inteiros, 0,0 para tipos de ponto flutuante,false para booleanos e null
para tipos de referência.Por exemplo, o código a seguir cria uma matriz e inicializa seus quatro
elementos com o valor 0.A Figura 14-6 ilustra o layout na memória.int [] intArr = novo int [4];Figura 14-
6 . Inicialização automática de uma matriz unidimensionalInicialização explícita de matrizes
unidimensionaisPara uma matriz unidimensional, você pode definir valores iniciais explícitos, incluindo
uma lista de inicializaçãoimediatamente após a expressão de criação de matriz de uma instanciação de
matriz.•Os valores de inicialização devem ser separados por vírgulas e colocados em um conjunto de
chaves.•Os comprimentos das dimensões são opcionais, uma vez que o compilador inferirá os
comprimentos a partir do número deinicializando valores.•Observe que nada separa a expressão de
criação de array e a lista de inicialização. Isso é,não há sinal de igual ou outro operador de conexão.Por
exemplo, o código a seguir cria uma matriz e inicializa seus quatro elementos com os valoresentre as
chaves. A Figura 14-7 ilustra o layout na memória.Lista de inicialização↓int [] intArr = novo int [] {10, 20,
30, 40};↑Sem operador de conexãoFigura 14-7 . Inicialização explícita de uma matriz unidimensional

Página 379

CAPÍTULO 14 ■ ARRAYS349Inicialização explícita de matrizes retangularesPara inicializar explicitamente


uma matriz retangular, você precisa seguir estas regras:•Cada vetor de valores iniciais deve ser colocado
entre chaves.•Cada dimensão também deve ser aninhada e colocada entre chaves.•Além dos valores
iniciais, as listas de inicialização e componentes de cada dimensão devemtambém podem ser separados
por vírgulas.Por exemplo, o código a seguir mostra a declaração de uma matriz bidimensional com
umlista de inicialização. A Figura 14-8 ilustra o layout na memória.Listas de inicialização separadas por
vírgulas↓↓int [,] intArray2 = novo int [,] {{10, 1}, {2, 10}, {11, 9}};Figura 14-8. Inicializando uma matriz
retangularPontos de sintaxe para inicializar matrizes retangularesMatrizes retangulares são inicializadas
com listas de inicialização separadas por vírgulas aninhadas. A inicializaçãoas listas são aninhadas entre
chaves. Isso às vezes pode ser confuso, para obter o aninhamento, agrupamento evírgulas à direita, as
dicas a seguir podem ser úteis:•As vírgulas são usadas como separadores entre todos os elementos e
grupos .•As vírgulas não são colocadas entre chaves esquerdas.•As vírgulas não são colocadas antes de
uma chave direita.•Leia as especificações de classificação da esquerda para a direita, designando o
último número como "elementos" e todosos outros como “grupos”.

Página 380

CAPÍTULO 14 ■ ARRAYS350Por exemplo, leia a seguinte declaração como “intArray tem quatro grupos
de três grupos de doiselementos. ”Listas de inicialização, aninhadas e separadas por vírgulasint [,,]
intArray = new int [4,3,2] {↓↓↓{{8, 6}, {5, 2}, {12, 9}},{{6, 4}, {13, 9}, {18, 4}},{{7, 2}, {1, 13}, {9, 3}},{{4, 6},
{3, 2}, {23, 8}}};Sintaxe de atalhoAo combinar declaração, criação de array e inicialização em uma única
instrução, você pode omitir oexpressão de criação de matriz parte da sintaxe inteiramente e fornece
apenas a parte de inicialização. Figura14-9 mostra essa sintaxe de atalho.Figura 14-9 . Atalho para
declaração, criação e inicialização de array

Página 381

CAPÍTULO 14 ■ ARRAYS351Matrizes digitadas implicitamenteAté agora, especificamos explicitamente os


tipos de array no início de todas as nossas declarações de array. Mas comooutras variáveis locais, seus
arrays também podem ser digitados implicitamente. Isso significa o seguinte:•Ao inicializar uma matriz,
você pode deixar o compilador inferir o tipo da matriz a partir do tipo deos inicializadores. Isso é
permitido, desde que todos os inicializadores possam ser convertidos implicitamente em umúnico
tipo.•Assim como com as variáveis locais digitadas implicitamente, use a palavra-chave var em vez do
tipo de array.O código a seguir mostra versões explícitas e implícitas de três declarações de matriz. O
primeiro conjunto é ummatriz unidimensional de ints. O segundo é uma matriz bidimensional de ints. O
terceiro é uma matriz decordas. Observe que na declaração de intArr4 implicitamente digitada, você
ainda precisa incluir a classificaçãoespecificador na inicialização.ExplícitoExplícito↓↓int [] intArr1 = novo
int [] {10, 20, 30, 40};var intArr2 = novo [] {10, 20, 30, 40};↑↑Palavra-chaveInferidoint [,] intArr3 = novo int
[,] {{10, 1}, {2, 10}, {11, 9}};var intArr4 = novo [,] {{10, 1}, {2, 10}, {11, 9}};↑Especificador de
classificaçãostring [] sArr1 = nova string [] {"vida", "liberdade", "busca da felicidade"};var sArr2 = novo []
{"vida", "liberdade", "busca da felicidade"};

Página 382

CAPÍTULO 14 ■ ARRAYS352Juntando tudoO código a seguir reúne todas as peças que examinamos até
agora. Ele cria, inicializa e usa ummatriz retangular.// Declara, cria e inicializa uma matriz digitada
implicitamente.var arr = new int [,] {{0, 1, 2}, {10, 11, 12}};// Imprima os valores.para (int i = 0; i <2; i +
+)para (int j = 0; j <3; j ++)Console.WriteLine ("Elemento [{0}, {1}] é {2}", i, j, arr [i, j]);Este código produz a
seguinte saída:Elemento [0,0] é 0Elemento [0,1] é 1Elemento [0,2] é 2Elemento [1,0] é 10Elemento [1,1]
é 11Elemento [1,2] é 12Baixe a partir de Wow! e-book <www.wowebook.com>

Página 383
CAPÍTULO 14 ■ ARRAYS353Matrizes denteadasUma matriz denteada é uma matriz de matrizes. Ao
contrário de matrizes retangulares, as submatrizes de uma matriz denteada podem terdiferentes
números de elementos.Por exemplo, o código a seguir declara uma matriz denteada bidimensional. A
Figura 14-10 mostra olayout do array na memória.•O comprimento da primeira dimensão é 3.•A
declaração pode ser lida como “jagArr é uma matriz de três matrizes de ints.”•Observe que a figura
mostra quatro objetos de array - um para o array de nível superior e três para osubarrays.int [] [] jagArr =
novo int [3] []; // Declare e crie um array de nível superior....// Declara e cria subarrays.Figura 14-10.
Uma matriz denteada é uma matriz de matrizes.

Página 384

CAPÍTULO 14 ■ ARRAYS354Declarando uma Matriz JaggedA sintaxe de declaração para matrizes


denteadas requer um conjunto separado de colchetes para cada dimensão.O número de conjuntos de
colchetes na declaração da variável de matriz determina a classificação dea matriz.•Uma matriz
denteada pode ter qualquer número de dimensões maior que um.•Tal como acontece com matrizes
retangulares, comprimentos de dimensão não podem ser incluídos na seção de tipo de matriz dea
declaração.Especificadores de classificação↓int [] [] SomeArr;// Rank = 2int [] [] [] OtherArr;// Rank =
3↑↑Tipo de array Nome do arrayInstanciação de atalhoVocê pode combinar a declaração de array
irregular com a criação do array de primeiro nível usando um array-expressão de criação, como na
declaração a seguir. A Figura 14-11 mostra o resultado.Três submatrizes↓int [] [] jagArr = novo int [3]
[];Figura 14-11. Instanciação de primeiro nível de atalhoVocê não pode instanciar mais do que a matriz
de primeiro nível na instrução de declaração.Permitido↓int [] [] jagArr = novo int [3] [4];// Errado! Erro de
compilação↑Não permitido

Página 385

CAPÍTULO 14 ■ ARRAYS355Instanciando uma Matriz JaggedAo contrário de outros tipos de matrizes,


você não pode instanciar totalmente uma matriz denteada em uma única etapa. Desde um
denteadoarray é um array de arrays independentes, cada array deve ser criado separadamente.
Instanciando um full jaggedarray requer as seguintes etapas:1. Instancie a matriz de nível superior.2.
Instancie cada subarray separadamente, atribuindo a referência do array recém-criado aoelemento
apropriado de sua matriz contida.Por exemplo, o código a seguir mostra a declaração, instanciação e
inicialização de doismatriz denteada dimensional. Observe no código que a referência a cada subarray é
atribuída a umelemento na matriz de nível superior. As etapas 1 a 4 no código correspondem ao
numeradorepresentações na Figura 14-12.int [] [] Arr = novo int [3] [];// 1. Instancie o nível superiorArr
[0] = novo int [] {10, 20, 30};// 2. Instanciar subarrayArr [1] = novo int [] {40, 50, 60, 70};// 3. Instancie o
subarrayArr [2] = novo int [] {80, 90, 100, 110, 120}; // 4. Instancie o subarrayFigura 14-12. Criação de
uma matriz denteada bidimensional

Página 386

CAPÍTULO 14 ■ ARRAYS356Subarrays em Jagged ArraysUma vez que os submatrizes em uma matriz


irregular são, eles próprios, matrizes, é possível ter matrizes retangulares dentromatrizes denteadas. Por
exemplo, o código a seguir cria uma matriz denteada de três dimensõesmatrizes retangulares e inicializa-
as com valores. Em seguida, exibe os valores.•A Figura 14-13 ilustra a estrutura.•O código usa o método
GetLength (int n) de arrays, herdado de System.Array, para obter ocomprimento da dimensão
especificada da matriz.int [] [,] Arr;// Um array de arrays 2-DArr = novo int [3] [,]; // Instancie um array
de três arrays 2-D.Arr [0] = novo int [,] {{10, 20},{100, 200}};Arr [1] = novo int [,] {{30, 40, 50}, {300, 400,
500}};Arr [2] = novo int [,] {{60, 70, 80, 90}, {600, 700, 800, 900}};↓ Obtenha o comprimento da dimensão
0 de Arrpara (int i = 0; i <Arr.GetLength (0); i ++){↓ Obtenha o comprimento da dimensão 0 de Arr [i]para
(int j = 0; j <Arr [i] .GetLength (0); j ++){↓ Obtenha o comprimento da dimensão 1 de Arr [i]para (int k = 0;
k <Arr [i] .GetLength (1); k ++) {Console.WriteLine("[{0}] [{1}, {2}] = {3}", i, j, k, Arr [i] [j,
k]);}Console.WriteLine ("");}Console.WriteLine ("");}Figura 14-13. Matriz denteada de três matrizes
bidimensionais

Página 387

CAPÍTULO 14 ■ ARRAYS357Comparando matrizes retangulares e denteadasA estrutura de matrizes


retangulares e denteadas é significativamente diferente. Por exemplo, a Figura 14-14 mostraa estrutura
de uma matriz retangular três por três, bem como uma matriz denteada de três unidades
unidimensionaismatrizes de comprimento 3.•Ambos os arrays contêm nove inteiros, mas como você
pode ver, suas estruturas são bastante diferentes.•A matriz retangular possui um único objeto de matriz,
enquanto a matriz denteada tem quatro objetos de matriz.Figura 14-14. Comparando a estrutura de
matrizes retangulares e dentadasMatrizes unidimensionais têm instruções específicas no CIL que
permitem que sejam otimizadas paradesempenho. Matrizes retangulares não têm essas instruções e não
são otimizadas para o mesmo nível.Por causa disso, às vezes pode ser mais eficiente usar matrizes
irregulares de matrizes unidimensionais—que podem ser otimizados - do que matrizes retangulares, que
não podem.Por outro lado, a complexidade da programação pode ser menor para uma matriz retangular
porque podeser tratada como uma única unidade, em vez de uma matriz de matrizes.

Página 388

CAPÍTULO 14 ■ ARRAYS358A declaração foreachA instrução foreach permite que você acesse
sequencialmente cada elemento em uma matriz. Na verdade é maisconstrução geral, pois também
funciona com outros tipos de coleção, mas esta seção apenas discuteseu uso com matrizes. O Capítulo
20 cobre seu uso com outros tipos de coleção.Os pontos importantes da declaração foreach são os
seguintes:•A variável de iteração é uma variável temporária do mesmo tipo que os elementos da matriz.
oA instrução foreach usa a variável de iteração para representar sequencialmente cada elemento do
array.•A sintaxe da instrução foreach é mostrada aqui, onde- Tipo é o tipo dos elementos da matriz. Você
pode fornecer explicitamente o seu tipo ou pode permitirpode ser digitado implicitamente e inferido
pelo compilador, uma vez que o compilador conhece o tipo dearray.- Identificador é o nome da variável
de iteração.- ArrayName é o nome da matriz a ser processada.- A declaração é uma declaração simples
ou um bloco que é executado uma vez para cada elemento noarray.Declaração de variável de iteração
explicitamente digitada↓foreach ( identificador de tipo em ArrayName )DeclaraçãoDeclaração de variável
de iteração digitada implicitamente↓foreach (var Identifier in ArrayName )DeclaraçãoNo texto a seguir,
às vezes usarei a digitação implícita e, outras vezes, usarei a digitação explícita,que você pode ver o tipo
exato que está sendo usado. Mas as formas são semanticamente equivalentes.

Página 389

CAPÍTULO 14 ■ ARRAYS359A instrução foreach funciona da seguinte maneira:•Ele começa com o


primeiro elemento da matriz e atribui esse valor à variável de iteração .•Em seguida, executa o corpo da
instrução. Dentro do corpo, você pode usar a variável de iteração como umalias somente leitura para o
elemento da matriz.•Depois que o corpo é executado, a instrução foreach seleciona o próximo elemento
na matriz erepete o processo.Dessa forma, ele percorre o array, permitindo que você acesse cada
elemento um por um. Paraexemplo, o código a seguir mostra o uso de uma instrução foreach com uma
matriz unidimensional dequatro inteiros:•A instrução WriteLine, que é o corpo da instrução foreach, é
executada uma vez para cadaos elementos da matriz.•Na primeira vez no loop, o item da variável de
iteração tem o valor do primeiro elemento doarray. A cada vez sucessivas, ele possui o valor do próximo
elemento na matriz.int [] arr1 = {10, 11, 12, 13};Declaração de variável de iteração↓Uso de variável de
iteraçãoforeach (item interno em arr1)↓Console.WriteLine ("Valor do item: {0}", item);Este código produz
a seguinte saída:Valor do item: 10Valor do item: 11Valor do item: 12Valor do item: 13

Página 390

CAPÍTULO 14 ■ ARRAYS360A variável de iteração é somente leituraComo o valor da variável de iteração


é somente leitura, obviamente ele não pode ser alterado. Mas isso tem diferenteefeitos em matrizes de
tipo de valor e matrizes de tipo de referência.Para matrizes de tipo de valor, isso significa que você não
pode alterar os dados da matriz. Por exemplo, noseguinte código, a tentativa de alterar os dados na
variável de iteração produz um tempo de compilaçãomensagem de erro:int [] arr1 = {10, 11, 12,
13};foreach (item interno em arr1)item ++; // Erro de compilação. Alterar o valor da variável não é
permitido.Para matrizes de tipo de referência, você ainda não pode alterar a variável de iteração, mas a
variável de iteraçãocontém apenas a referência aos dados, não os próprios dados. Portanto, embora
você não possa alterar a referência,você pode alterar os dados por meio da variável de iteração.O código
a seguir cria uma matriz de quatro objetos MyClass e os inicializa. No primeiro foreachdeclaração, os
dados em cada um dos objetos são alterados. Na segunda instrução foreach, os dados alteradosé lido a
partir dos objetos.classe MyClass{public int MeuCampo = 0;}class Program {static void Main () {MyClass
[] mcArray = new MyClass [4];// Criar arraypara (int i = 0; i <4; i ++){mcArray [i] = new MyClass ();// Criar
objetos de classemcArray [i] .MyField = i;// Definir campo}foreach (item MyClass em
mcArray)item.MeuCampo + = 10;// Altere os dados.foreach (item MyClass em
mcArray)Console.WriteLine ("{0}", item.MyField); // Leia os dados alterados.}}Este código produz a
seguinte saída:10111213

Página 391

CAPÍTULO 14 ■ ARRAYS361A instrução foreach com matrizes multidimensionaisEm uma matriz


multidimensional, os elementos são processados na ordem em que o índice mais à direita
éincrementado mais rápido. Quando o índice foi de 0 para comprimento - 1, o próximo índice à esquerda
éincrementado e os índices à direita são redefinidos como 0.Exemplo com uma matriz retangularO
exemplo a seguir mostra a instrução foreach usada com uma matriz retangular:programa de aula{static
void Main (){total int = 0;int [,] arr1 = {{10, 11}, {12, 13}};foreach (elemento var em arr1){total + =
elemento;Console.WriteLine("Elemento: {0}, Total Atual: {1}", elemento, total);}}}Este código produz a
seguinte saída:Elemento: 10, Total Atual: 10Elemento: 11, Total Atual: 21Elemento: 12, Total Atual:
33Elemento: 13, Total Atual: 46

Página 392

CAPÍTULO 14 ■ ARRAYS362Exemplo com uma matriz denteadaUma vez que matrizes denteadas são
matrizes de matrizes, você deve usar instruções foreach separadas para cada dimensão ema matriz
denteada. As instruções foreach devem ser aninhadas corretamente para garantir que cada array
aninhado sejaprocessado corretamente.Por exemplo, no código a seguir, a primeira instrução foreach
percorre a matriz de nível superior—arr1 - selecionando o próximo subarray para processar. A instrução
foreach interna processa os elementos deesse subarray.programa de aula{static void Main (){total int =
0;int [] [] arr1 = novo int [2] [];arr1 [0] = novo int [] {10, 11};arr1 [1] = novo int [] {12, 13, 14};foreach (int
[] array em arr1) // Processa o nível superior.{Console.WriteLine ("Iniciando nova matriz");foreach (int
item in array) // Processa o segundo nível.{total + = item;Console.WriteLine ("Item: {0}, Total Atual: {1}",
item, total);}}}}Este código produz a seguinte saída:Iniciando nova matrizItem: 10, Total Atual: 10Item:
11, Total Atual: 21Iniciando nova matrizItem: 12, Total Atual: 33Item: 13, Total Atual: 46Item: 14, Total
Atual: 60Baixe a partir de Wow! e-book <www.wowebook.com>

Página 393

CAPÍTULO 14 ■ ARRAYS363Matriz de covariânciaSob certas condições, você pode atribuir um objeto a


um elemento da matriz, mesmo se o objeto não for dotipo de base do array. Esta propriedade dos arrays
é chamada de covariância do array . Você pode usar a covariância de matriz se oa seguir são
verdadeiras:•A matriz é uma matriz de tipo de referência.•Há uma conversão implícita ou explícita entre
o tipo de objeto que você está atribuindo eo tipo de base da matriz.Uma vez que sempre há uma
conversão implícita entre uma classe derivada e sua classe base, você podesempre atribua um objeto de
uma classe derivada a uma matriz declarada para a classe base.Por exemplo, o código a seguir declara
duas classes, A e B, onde a classe B deriva da classe A.a última linha mostra a covariância atribuindo
objetos do tipo B a elementos da matriz do tipo A. A Figura 14-15 mostrao layout de memória para o
código.classe A { ... }// Classe baseclasse B: A {...}// Classe derivadaclass Program {static void Main () {//
Dois arrays do tipo A []A [] AArray1 = novo A [3];A [] AArray2 = novo A [3];// Normal - atribuindo objetos
do tipo A a uma matriz do tipo AAArray1 [0] = novo A (); AArray1 [1] = novo A (); AArray1 [2] = novo A
();// Covariant - atribuindo objetos do tipo B a uma matriz do tipo AAArray2 [0] = novo B (); AArray2 [1] =
novo B (); AArray2 [2] = novo B ();}}Figura 14-15. Matrizes mostrando covariância■ Nota Não há
covariância para matrizes de tipo de valor.

Página 394

CAPÍTULO 14 ■ ARRAYS364Membros úteis da matriz herdadaMencionei anteriormente que os arrays C #


são derivados da classe System.Array. Dessa classe base eles herdamuma série de propriedades e
métodos úteis. A Tabela 14-1 lista alguns dos mais úteis.Tabela 14-1. Alguns membros úteis herdados por
matrizesMembroTipoTempo de vidaSignificadoClassificaçãoInstância de propriedade Obtém o número
de dimensões da matrizcomprimentoInstância da propriedade Obtém o número total de elementos em
todas as dimensõesda matrizGetLengthMétodoInstância Retorna o comprimento de uma dimensão
específica da matrizClaroMétodoEstáticoDefine um intervalo de elementos como 0 ou
nuloOrdenarMétodoEstáticoClassifica os elementos em uma matriz
unidimensionalBinarySearchMétodoEstáticoPesquisa uma matriz unidimensional por um valor,
usandobusca bináriaCloneMétodoInstância Executa uma cópia superficial da matriz - copiando apenas
oelementos, tanto para matrizes de tipos de valor como de referênciatiposÍndice
deMétodoEstáticoRetorna o índice da primeira ocorrência de um valor em ummatriz
unidimensionalReverterMétodoEstáticoInverte a ordem dos elementos de um intervalo de um-matriz
dimensionalGetUpperBoundMétodoInstância Obtém o limite superior na dimensão especificada

Página 395

CAPÍTULO 14 ■ ARRAYS365Por exemplo, o código a seguir usa algumas dessas propriedades e


métodos:public static void PrintArray (int [] a){foreach (var x em a)Console.Write ("{0}",
x);Console.WriteLine ("");}static void Main (){int [] arr = novo int [] {15, 20, 5, 25, 10};PrintArray
(arr);Array.Sort (arr);PrintArray (arr);Array.Reverse (arr);PrintArray (arr);Console.WriteLine
();Console.WriteLine ("Classificação = {0}, Comprimento = {1}", arr.Rank, arr.Length);Console.WriteLine
("GetLength (0) = {0}", arr.GetLength (0));Console.WriteLine ("GetType ()= {0} ", arr.GetType ());}Este
código produz a seguinte saída:15 20 5 25 105 10 15 20 2525 20 15 10 5Classificação = 1, Comprimento =
5GetLength (0) = 5GetType ()= System.Int32 []

Página 396

CAPÍTULO 14 ■ ARRAYS366O Método CloneO método Clone executa uma cópia superficial de uma
matriz. Isso significa que ele apenas cria um clone dopróprio array. Se for uma matriz de tipo de
referência, ela não copia os objetos referenciados pelos elementos. Este temresultados diferentes para
matrizes de tipo de valor e matrizes de tipo de referência.•A clonagem de uma matriz de tipo de valor
resulta em duas matrizes independentes.•A clonagem de um array de tipo de referência resulta em dois
arrays apontando para os mesmos objetos.O método Clone retorna uma referência do objeto de tipo,
que deve ser convertido para o tipo de matriz.int [] intArr1 = {1, 2, 3};Tipo de matrizRetorna um
objeto↓↓int [] intArr2 = (int []) intArr1.Clone ();Por exemplo, o código a seguir mostra um exemplo de
clonagem de uma matriz de tipo de valor, produzindo doismatrizes independentes. A Figura 14-16 ilustra
as etapas mostradas no código.static void Main (){int [] intArr1 = {1, 2, 3};// Passo 1int [] intArr2 = (int [])
intArr1.Clone ();// Passo 2intArr2 [0] = 100; intArr2 [1] = 200; intArr2 [2] = 300; // Etapa 3}Figura 14-16. A
clonagem de uma matriz de tipo de valor produz duas matrizes independentes.

Página 397

CAPÍTULO 14 ■ ARRAYS367A clonagem de um array de tipo de referência resulta em dois arrays


apontando para os mesmos objetos . O seguinte códigomostra um exemplo. A Figura 14-17 ilustra as
etapas mostradas no código.classe A{public int Value = 5;}programa de aula{static void Main (){A []
AArray1 = novo A [3] {novo A (), novo A (), novo A ()}; // Passo 1A [] AArray2 = (A []) AArray1.Clone ();//
Passo 2AArray2 [0] .Value = 100;AArray2 [1] .Value = 200;AArray2 [2] .Value = 300;// Etapa 3}}Figura 14-
17. A clonagem de um array de tipo de referência produz dois arrays que fazem referência aos mesmos
objetos.

Página 398

CAPÍTULO 14 ■ ARRAYS368Comparando Tipos de MatrizA Tabela 14-2 resume algumas das semelhanças
e diferenças importantes entre os três tiposde matrizes.Tabela 14-2. Resumo Comparando Tipos de
MatrizSintaxeTipo de MatrizArrayObjetosColchetesForma de vírgulasUnidimensional•Tem
otimizaçãoinstruções em CIL.1Conjunto único NãoRetangular•Multidimensional.•Todos os subarrays em
ummultidimensionalarray deve ser doMesmo comprimento.1Conjunto único
SimDenteado•Multidimensional.•Subarrays podem ser decomprimentos diferentes.Multiple
MultipleconjuntosNão

Página 399

CAPÍTULO 15■ ■ ■369Delegados■ O que é um delegado?■ Declarando o tipo de delegado■ Criando


o objeto delegado■ Atribuição de delegados■ Combinando Delegados■ Adicionando métodos a
delegados■ Removendo métodos de um delegado■ Invocando um Delegado■ Exemplo de delegado■
Invocando delegados com valores de retorno■ Invocando delegados com parâmetros de referência■
Métodos anônimos■ Expressões Lambda

Página 400

CAPÍTULO 15 ■ DELEGADOS370O que é um delegado?Um delegado é um tipo definido pelo usuário,


como uma classe. Mas enquanto uma classe representa uma coleção de dados, umdelegate mantém
controle de um ou mais métodos. Você usa um delegado fazendo o seguinte. Nós iremosatravés de cada
uma dessas etapas em detalhes nas seções a seguir.1. Declare um novo tipo de delegado com uma
assinatura específica e tipo de retorno. Um delegadodeclaração parece uma declaração de método,
exceto que não tem umbloco de implementação.2. Declare uma variável de delegado do novo tipo de
delegado.3. Crie um objeto do tipo delegado e atribua-o à variável delegada. O novo delegadoobjeto
inclui uma referência a um método com a mesma assinatura definida na primeira etapa.4. Adicione
métodos adicionais ao objeto delegado. Esses métodos devem ter o mesmotipo de assinatura e retorno
como o tipo de delegado definido na primeira etapa.5. Em todo o código, você pode invocar o delegado,
como se fosse um método. Quandovocê invoca o delegado, cada um dos métodos que ele contém é
executado.Ao observar as etapas anteriores, você deve ter notado que elas são semelhantes às etapas
de criaçãoe usando uma classe. A Figura 15-1 compara os processos de criação e uso de classes e
delegados.Figura 15-1. Um delegado é um tipo de referência definido pelo usuário, como uma classe.■
Nota: Se você tem experiência em C ++, a maneira mais rápida de entender os delegados é pensar
emcomo ponteiros de função C ++ orientados a objetos e com segurança de tipos em esteróides.
Página 401

CAPÍTULO 15 ■ DELEGADOS371Você pode pensar em um delegado como um objeto que contém uma
lista ordenada de métodos com o mesmoassinatura e tipo de retorno, conforme ilustrado na Figura 15-
2.•A lista de métodos é chamada de lista de invocação .•Métodos detidas por um delegado pode ser de
qualquer classe ou struct , enquanto eles combinam tanto odo delegado- Tipo de retorno- Assinatura
(incluindo modificadores ref e out)•Os métodos na lista de invocação podem ser métodos de instância
ou métodos estáticos.•Quando um delegado é invocado, cada método em sua lista de invocação é
executado.Figura 15-2 . Um delegado como uma lista de métodos

Página 402

CAPÍTULO 15 ■ DELEGADOS372Declarando o tipo de delegadoDelegados são tipos, assim como classes


são tipos. E como acontece com as classes, um tipo de delegado deve ser declaradoantes de usá-lo para
criar variáveis e objetos do tipo. O código de exemplo a seguir declara umtipo de delegado.•Mesmo que
a declaração do tipo delegado pareça uma declaração de método, não precisa serdeclarado dentro de
uma classe porque é uma declaração de tipo.Nome do tipo de delegado de palavra-chave↓↓delegate
void MyDel (int x);A declaração de um tipo delegado se parece muito com a declaração de um método,
pois tem umtipo de retorno e uma assinatura . O tipo de retorno e a assinatura especificam a forma dos
métodos que odelegado aceitará.Por exemplo, o código a seguir declara o tipo de delegado MyDel. Esta
declaração especifica quedelegados deste tipo só aceitarão métodos que tenham um único parâmetro
int e que não tenhamvalor de retorno. A Figura 15-3 mostra uma representação do tipo delegado à
esquerda e o objeto delegadoa direita.Nome do tipo de delegado↓delegate void MyDel (int x);↑↑Tipo de
retorno AssinaturaFigura 15-3 . Delegar tipo e objetoA declaração de tipo delegado difere de uma
declaração de método de duas maneiras. O tipo de delegadodeclaração•É precedido pela palavra-chave
delegado•Não tem corpo de métodoBaixe a partir de Wow! e-book <www.wowebook.com>

Página 403

CAPÍTULO 15 ■ DELEGADOS373Criando o objeto delegadoUm delegado é um tipo de referência e,


portanto, possui uma referência e um objeto. Depois que um tipo de delegado édeclarado, você pode
declarar variáveis e criar objetos do tipo. O código a seguir mostra odeclaração de uma variável de um
tipo delegado:Variável de tipo de delegado↓↓MyDel delVar;Existem duas maneiras de criar um objeto
delegado. O primeiro é usar um objeto de criaçãoexpressão com o novo operador, conforme mostrado
no código a seguir. O operando do novo operadorconsiste no seguinte:•O nome do tipo de
delegado.•Um conjunto de parênteses contendo o nome de um método a ser usado como o primeiro
membro dolista de invocação. O método pode ser um método de instância ou um método
estático.Método de instância↓delVar = novo MyDel (myInstObj.MyM1); // Criar delegado e salvar
ref.dVar = novo MyDel (SClass.OtherM2); // Criar delegado e salvar ref.↑Método estáticoVocê também
pode usar a sintaxe de atalho, que consiste apenas no especificador de método, conforme mostrado
noseguinte código. Este código e o código anterior são equivalentes. Usar a sintaxe de atalho
funcionaporque há uma conversão implícita entre um nome de método e um tipo de delegado
compatível.delVar = myInstObj.MyM1;// Criar delegado e salvar referência.dVar = SClass.OtherM2;//
Criar delegado e salvar referência.

Página 404

CAPÍTULO 15 ■ DELEGADOS374Por exemplo, o código a seguir cria dois objetos delegados - um com um
método de instância e ooutro com um método estático. A Figura 15-4 mostra as instanciações dos
delegados. Este código assume queexiste um objeto chamado myInstObj, que é uma instância de uma
classe que definiu um método chamado MyM1não retornando nenhum valor e tomando um int como
parâmetro. Ele também assume que existe uma classe chamada SClass,que tem um método estático
OtherM2 com um tipo de retorno e assinatura correspondente aos do delegado MyDel.delegate void
MyDel (int x);// Declara o tipo de delegado.MyDel delVar, dVar;// Crie duas variáveis de
delegado.Método de instância↓delVar = novo MyDel (myInstObj.MyM1); // Criar delegado e salvar
ref.dVar = novo MyDel (SClass.OtherM2); // Criar delegado e salvar ref.↑Método estáticoFigura 15-4 .
Instanciando os delegadosAlém de alocar a memória para o delegado, a criação de um objeto delegado
também coloca o primeirométodo na lista de invocação do delegado.Você também pode criar a variável
e instanciar o objeto na mesma instrução, usando osintaxe do inicializador. Por exemplo, as instruções a
seguir também produzem a mesma configuração mostrada emFigura 15-4:MyDel delVar = novo MyDel
(myInstObj.MyM1);MyDel dVar = novo MyDel (SClass.OtherM2);As seguintes instruções usam a sintaxe
de atalho, mas novamente produzem os resultados mostrados na Figura 15-4:MyDel delVar =
myInstObj.MyM1;MyDel dVar = SClass.OtherM2;

Página 405

CAPÍTULO 15 ■ DELEGADOS375Atribuição de DelegadosComo delegados são tipos de referência, você


pode alterar a referência contida em uma variável de delegado poratribuindo a ele. O antigo objeto
delegado será descartado pelo coletor de lixo (GC) quando forem torno dele.Por exemplo, o código a
seguir define e altera o valor de delVar. A Figura 15-5 ilustrao código.MyDel delVar;delVar =
myInstObj.MyM1; // Crie e atribua o objeto delegado....delVar = SClass.OtherM2; // Crie e atribua o
novo objeto delegado.Figura 15-5 . Atribuindo a uma variável delegada

Página 406

CAPÍTULO 15 ■ DELEGADOS376Combinar DelegadosTodos os delegados que você viu até agora tiveram
apenas um único método em suas listas de invocação. Delegados podemser “combinados” usando o
operador de adição. O resultado da operação é a criação de um novodelegado, com uma lista de
invocação que é a concatenação de cópias das listas de invocação dos doisdelegados operandos.Por
exemplo, o código a seguir cria três delegados. O terceiro delegado é criado a partir docombinação dos
dois primeiros.MyDel delA = myInstObj.MyM1;MyDel delB = SClass.OtherM2;MyDel delC = delA +
delB;// Possui lista de invocação combinadaEmbora o termo delegados combinados possa dar a
impressão de que os delegados operandos sãomodificados, eles não são alterados de forma alguma. Na
verdade, os delegados são imutáveis . Depois que um objeto delegado é criado,não pode ser alterado.A
Figura 15-6 ilustra os resultados do código anterior. Observe que os delegados operandos
permaneceminalterado.Figura 15-6 . Combinar delegados
Página 407

CAPÍTULO 15 ■ DELEGADOS377Adicionando Métodos a DelegadosEmbora você tenha visto na seção


anterior que delegados são, na realidade, imutáveis, C # fornece sintaxe parafazendo parecer que você
pode adicionar um método a um delegado, usando o operador + =.Por exemplo, o código a seguir
“adiciona” dois métodos à lista de invocação do delegado. oos métodos são adicionados ao final da lista
de invocação. A Figura 15-7 mostra o resultado.MyDel delVar = inst.MyM1; // Crie e inicialize.delVar + =
SCl.m3;// Adicione um método.delVar + = X.Act;// Adicione um método.Figura 15-7 . Resultado da
“adição” de métodos a um delegado. Na realidade, porque os delegados são imutáveis, odelegado
resultante com três métodos em sua lista de invocação é um delegado inteiramente novo apontado pora
variável.O que está realmente acontecendo, é claro, é que quando o operador + = é usado, um novo
delegado écriado, com uma lista de invocação que é a combinação do delegado à esquerda mais o
método listadoa direita. Este novo delegado é então atribuído à variável delVar.

Página 408

CAPÍTULO 15 ■ DELEGADOS378Removendo Métodos de um DelegadoVocê também pode remover um


método de um delegado, usando o operador - =. O código a seguir mostra o usodo operador. A Figura
15-8 mostra o resultado deste código quando aplicado ao delegado ilustrado emFigura 15-7.delVar - =
SCl.m3;// Remova o método do delegado.Figura 15-8 . Resultado da remoção de um método de um
delegadoTal como acontece com a adição de um método a um delegado, o delegado resultante é, na
verdade, um novo delegado. O novodelegado é uma cópia do antigo delegado, mas sua lista de
invocação não contém mais a referência aométodo que foi removido.A seguir estão alguns itens a serem
lembrados ao remover métodos:•Se houver várias entradas para um método na lista de invocação, o
operador - = começa a pesquisar emparte inferior da lista e remove a primeira instância do método de
correspondência que encontrar.•A tentativa de excluir um método que não está no delegado não tem
efeito.•A tentativa de invocar um delegado vazio lança uma exceção.•Você pode verificar se a lista de
invocação de um delegado está vazia, comparando o delegado com nulo. E sea lista de invocação está
vazia, o delegado é nulo.

Página 409

CAPÍTULO 15 ■ DELEGADOS379Invocando um DelegadoVocê invoca um delegado chamando-o, como se


fosse simplesmente um método. Os parâmetros usados para invocar odelegado são usados para invocar
cada um dos métodos na lista de invocação (a menos que um dos parâmetros sejaum parâmetro de
saída, que abordaremos em breve).Por exemplo, o delegado delVar, conforme mostrado no código a
seguir, assume um único valor de entrada inteiro.Invocar o delegado com um parâmetro faz com que ele
invoque cada um dos membros em sua lista de invocação como mesmo valor de parâmetro (55, neste
caso). A Figura 15-9 ilustra a invocação.MyDel delVar = inst.MyM1;delVar + = SCl.m3;delVar + =
X.Act;...delVar (55);// Invoque o delegado....Figura 15-9. Quando o delegado é invocado, ele executa
cada um dos métodos em sua lista de invocação, com omesmos parâmetros com os quais foi
chamado.Um método pode estar na lista de invocação mais de uma vez. Se estiver na lista mais de uma
vez, quandoo delegado é invocado, o método será chamado cada vez que for encontrado na lista.
Página 410

CAPÍTULO 15 ■ DELEGADOS380Exemplo de delegadoO código a seguir define e usa um delegado sem


parâmetros e nenhum valor de retorno. Note oseguinte sobre o código:•Teste de classe define duas
funções de impressão.•O método Main cria uma instância do delegado e adiciona mais três métodos.•O
programa então chama o delegado, que chama seus métodos. Antes de invocar o delegado,no entanto,
ele verifica se não é nulo.// Defina um tipo de delegado sem valor de retorno e sem parâmetros.delegate
void PrintFunction ();classe Teste{public void Print1 (){Console.WriteLine ("Print1 - instância"); }public
static void Print2 (){Console.WriteLine ("Print2 - estático"); }}programa de aula{static void Main (){Teste t
= novo Teste (); // Crie uma instância de classe de teste.PrintFunction pf; // Cria um delegado nulo.pf =
t.Print1;// Instancie e inicialize o delegado.// Adicione mais três métodos ao delegado.pf + =
Test.Print2;pf + = t.Print1;pf + = Test.Print2;// O delegado agora contém quatro métodos.if (null! = pf)//
Certifique-se de que o delegado não seja nulo.pf ();// Invoque o delegado.outroConsole.WriteLine
("Delegate is empty");}}Este código produz a seguinte saída:Print1 - instânciaPrint2 - estáticoPrint1 -
instânciaPrint2 - estático

Página 411

CAPÍTULO 15 ■ DELEGADOS381Invocando Delegados com Valores de RetornoSe um delegado tem um


valor de retorno e mais de um método em sua lista de invocação, ocorre o seguinte:•O valor retornado
pelo último método na lista de invocação é o valor retornado dodelegar invocação.•Os valores de
retorno de todos os outros métodos na lista de invocação são ignorados.Por exemplo, o código a seguir
declara um delegado que retorna um valor int. Main cria um objetodo delegado e adiciona dois métodos
adicionais. Em seguida, ele chama o delegado na instrução WriteLinee imprime seu valor de retorno. A
Figura 15-10 mostra uma representação gráfica do código.delegar int MyDel ();// Declara o método com
valor de retorno.class MyClass {intValue = 5;public int Add2 () {IntValue + = 2; return IntValue;}public int
Add3 () {IntValue + = 3; return IntValue;}}class Program {static void Main () {MyClass mc = new MyClass
();MyDel mDel = mc.Add2;// Crie e inicialize o delegado.mDel + = mc.Add3;// Adicione um método.mDel
+ = mc.Add2;// Adicione um método.Console.WriteLine ("Valor: {0}", mDel ());}↑}Chame o delegado e use
o valor de retorno.Este código produz a seguinte saída:Valor: 12Figura 15-10 . O valor de retorno do
último método executado é o valor retornado pelo delegado.

Página 412

CAPÍTULO 15 ■ DELEGADOS382Invocando Delegados com Parâmetros de ReferênciaSe um delegado


tem um parâmetro de referência, o valor do parâmetro pode mudar ao retornar de um oumais dos
métodos na lista de invocação.•Ao chamar o próximo método na lista de invocação, o novo valor do
parâmetro - não ovalor inicial - é aquele passado para o próximo método.Por exemplo, o código a seguir
invoca um delegado com um parâmetro de referência. Figura 15-11ilustra o código.delegate void MyDel
(ref int X);classe MyClass{public void Add2 (ref int x) {x + = 2; }public void Add3 (ref int x) {x + = 3; }static
void Main (){MyClass mc = new MyClass ();MyDel mDel = mc.Add2;mDel + = mc.Add3;mDel + =
mc.Add2;int x = 5;mDel (ref x);Console.WriteLine ("Valor: {0}", x);}}Este código produz a seguinte
saída:Valor: 12Figura 15-11 . O valor de um parâmetro de referência pode mudar entre as
chamadas.Baixe a partir de Wow! e-book <www.wowebook.com>

Página 413

CAPÍTULO 15 ■ DELEGADOS383Métodos AnônimosAté agora, você viu que pode usar métodos estáticos
ou métodos de instância para instanciar um delegado.Em qualquer caso, o método em si pode ser
chamado explicitamente de outras partes do código e, é claro, deveser membro de alguma classe ou
estrutura.E se, no entanto, o método for usado apenas uma vez - para instanciar o delegado? Nesse caso,
outrodo que o requisito sintático para criar o delegado, não há necessidade real de um separado,
nomeadométodo. Os métodos anônimos permitem dispensar o método nomeado separado.•Um
método anônimo é um método declarado embutido, no ponto de instanciar um delegado.Por exemplo, a
Figura 15-12 mostra duas versões da mesma classe. A versão à esquerda declara eusa um método
denominado Add20. A versão à direita usa um método anônimo. oo código não sombreado de ambas as
versões é idêntico.Figura 15-12 . Comparando um método nomeado e um método anônimoAmbos os
conjuntos de código na Figura 15-12 produzem a seguinte saída:2526Usando métodos anônimosVocê
pode usar um método anônimo nos seguintes lugares:•Como uma expressão inicializadora ao declarar
uma variável delegada.•No lado direito de uma instrução de atribuição ao combinar delegados.•No lado
direito de uma instrução de atribuição, adicionando um delegado a um evento. Capítulo 16cobre
eventos.

Página 414

CAPÍTULO 15 ■ DELEGADOS384Sintaxe de métodos anônimosA sintaxe de uma expressão de método


anônima inclui os seguintes componentes:•O tipo de delegado de palavra-chave•A lista de parâmetros ,
que pode ser omitida se o bloco de instruções não usar nenhum parâmetro•O bloco de instrução , que
contém o código do método anônimoParâmetroPalavra-chaveListaBloco de declaração↓↓↓delegado
( parâmetros ) { ImplementationCode }Tipo de DevoluçãoUm método anônimo não declara
explicitamente um tipo de retorno. O comportamento da implementaçãoo próprio código, entretanto,
deve corresponder ao tipo de retorno do delegado, retornando um valor desse tipo. Se odelegate tem
um tipo de retorno void, então o código do método anônimo não pode retornar um valor.Por exemplo,
no código a seguir, o tipo de retorno do delegado é int. O código de implementação deo método
anônimo deve, portanto, retornar um int em todos os caminhos do código.Tipo de retorno do tipo de
delegado↓delegate int OtherDel (int InParam);static void Main (){OutroDel del = delegado (int x){retornar
x + 20;// Retorna um int};...}

Página 415

CAPÍTULO 15 ■ DELEGADOS385ParâmetrosExceto no caso de parâmetros de matriz, a lista de


parâmetros de um método anônimo deve corresponder à deo delegado nas seguintes três
características:•Número de parâmetros•Tipos e posições dos parâmetros•ModificadoresVocê pode
simplificar a lista de parâmetros de um método anônimo, deixando os parênteses vazios ouomitindo-os
completamente, mas apenas se ambos os itens a seguir forem verdadeiros:•A lista de parâmetros do
delegado não contém nenhum parâmetro de saída.•O método anônimo não usa nenhum parâmetro.Por
exemplo, o código a seguir declara um delegado que não tem nenhum parâmetro de saída e ummétodo
anônimo que não usa nenhum parâmetro. Uma vez que ambas as condições são atendidas, você pode
omitir olista de parâmetros do método anônimo.delegate void SomeDel (int X);// Declare o tipo de
delegado.SomeDel SDel = delegado// Lista de parâmetros omitida{PrintMessage ();Limpar();};Params
ParâmetrosSe a lista de parâmetros da declaração de delegado contiver um parâmetro params, a
palavra-chave paramsé omitido da lista de parâmetros do método anônimo. Por exemplo, no código a
seguir,isto acontece:•A declaração de tipo delegado especifica o último parâmetro como um parâmetro
de tipo params.•A lista de parâmetros de métodos anônimos, entretanto, deve omitir a palavra-chave
params.palavra-chave params usada na declaração de tipo de delegado↓delegate void SomeDel (int X,
params int [] Y);palavra-chave params omitida no método anônimo correspondente↓SomeDel mDel =
delegado (int X, int [] Y){...};

Página 416

CAPÍTULO 15 ■ DELEGADOS386Escopo de variáveis e parâmetrosOs escopos de parâmetros e variáveis


locais declarados dentro de um método anônimo são limitados aocorpo do código de implementação,
conforme ilustrado na Figura 15-13.Por exemplo, o método anônimo a seguir define o parâmetro y e a
variável local z. Depois depróximo ao corpo do método anônimo, y e z não estão mais no escopo. A
última linha do códigoproduziria um erro de compilação.Figura 15-13 . Escopo de variáveis e
parâmetrosVariáveis ExternasAo contrário dos métodos nomeados de um delegado, os métodos
anônimos têm acesso às variáveis locais eambiente do escopo que os cerca.•As variáveis do escopo
circundante são chamadas de variáveis externas.•Uma variável externa usada no código de
implementação de um método anônimo é consideradacapturado pelo método.Por exemplo, o código na
Figura 15-14 mostra a variável x definida fora do método anônimo. oo código no método, entretanto,
tem acesso a x e pode imprimir seu valor.Figura 15-14 . Usando uma variável externa

Página 417

CAPÍTULO 15 ■ DELEGADOS387Extensão da vida útil da variável capturadaUma variável externa


capturada permanece viva enquanto seu método de captura for parte do delegado, mesmo se ovariável
normalmente teria saído do escopo.Por exemplo, o código da Figura 15-15 ilustra a extensão do tempo
de vida de uma variável capturada.•A variável local x é declarada e inicializada dentro de um
bloco.•Delegate mDel é então instanciado, usando um método anônimo que captura a variável externa
x.•Quando o bloco é fechado, x sai do escopo.•Se a instrução WriteLine após o fechamento do bloco
fosse descomentada,causar um erro de compilação, porque faz referência a x, que agora está fora do
escopo.•O método anônimo dentro do delegado mDel, no entanto, mantém x em seu ambiente e
imprimeseu valor quando mDel é invocado.Figura 15-15 . Variável capturada em um método anônimoO
código na figura produz a seguinte saída:Valor de x: 5

Página 418

CAPÍTULO 15 ■ DELEGADOS388Expressões LambdaC # 2.0 introduziu métodos anônimos, que


permitiam incluir pequenos trechos de código embutido quandocriar ou adicionar delegados. A sintaxe
para métodos anônimos, no entanto, é um tanto prolixae requer informações que o próprio compilador
já conhece. Em vez de exigir que você incluaessas informações redundantes, C # 3.0 introduziu
expressões lambda , que reduzem a sintaxe demétodos anônimos. Você provavelmente vai querer usar
expressões lambda em vez de anônimasmétodos. Na verdade, se as expressões lambda tivessem sido
introduzidas primeiro, nunca teria havidométodos anônimos.Na sintaxe do método anônimo, a palavra-
chave delegate é redundante porque o compilador podejá veja que você está atribuindo o método a um
delegado. Você pode transformar facilmente um anônimométodo em uma expressão lambda fazendo o
seguinte:•Exclua a palavra-chave delegada.•Coloque o operador lambda, =>, entre a lista de parâmetros
e o corpo do anônimométodo. O operador lambda é lido como "vai para".O código a seguir mostra essa
transformação. A primeira linha mostra um método anônimo sendoatribuído à variável del. A segunda
linha mostra o mesmo método anônimo após ter sidotransformada em uma expressão lambda, sendo
atribuída à variável le1.MeuDel del = delegado (int x) {return x + 1; }; // Método anônimoMyDel le1 =(int
x) => {return x + 1; }; // Lambda expression■ Nota O termo expressão lambda vem do cálculo lambda ,
que foi desenvolvido na década de 1920 e1930 pelo matemático Alonzo Church e outros. O cálculo
lambda é um sistema para representar funçõese usa a letra grega lambda (λ) para representar uma
função sem nome. Mais recentemente, a programação funcionallinguagens como Lisp e seus dialetos
usam o termo para representar expressões que podem ser usadas para descrever diretamentea
definição de uma função, em vez de usar um nome para ela.

Página 419

CAPÍTULO 15 ■ DELEGADOS389Essa transformação simples é menos prolixa e parece mais limpa, mas
economiza apenas seis caracteres.Há mais, no entanto, que o compilador pode inferir, permitindo que
você simplifique a expressão lambdaalém disso, conforme mostrado no código a seguir.•A partir da
declaração do delegado, o compilador também conhece os tipos de parâmetros do delegado,portanto, a
expressão lambda permite que você omita os tipos de parâmetro, conforme mostrado noatribuição a
le2.- Os parâmetros listados com seus tipos são chamados explicitamente digitados .- Aqueles listados
sem seus tipos são chamados de digitados implicitamente .•Se houver apenas um único parâmetro
digitado implicitamente, você pode deixar de fora os parênteses ao redorele, conforme mostrado na
atribuição a le3.•Finalmente, as expressões lambda permitem que o corpo da expressão seja um bloco
de instrução ou umexpressão. Se o bloco de instrução contém uma única instrução de retorno, você
pode substituir obloco de instruções com apenas a expressão que segue a palavra-chave return,
conforme mostrado noatribuição a le4.MeuDel del = delegado (int x) {return x + 1; }; // Método
anônimoMyDel le1 =(int x) => {return x + 1; }; // Lambda expressionMyDel le2 =(x) => {return x + 1; }; //
Lambda expressionMyDel le3 =x => {return x + 1; }; // Lambda expressionMyDel le4 =x =>x + 1; //
Lambda expressionA forma final da expressão lambda tem cerca de um quarto dos caracteres do
originalmétodo anônimo e mais limpo e fácil de entender.

Página 420

CAPÍTULO 15 ■ DELEGADOS390O código a seguir mostra a transformação completa. A primeira linha do


Main mostra um anônimométodo sendo atribuído à variável del. A segunda linha mostra o mesmo
método anônimo, apóstendo sido transformado em uma expressão lambda, sendo atribuído à variável
le1.delegar MyDel duplo (int par);static void Main (){MeuDel del = delegado (int x) {return x + 1; }; //
Método anônimoMyDel le1 =(int x) => {return x + 1; }; // Lambda expressionMyDel le2 =(x) => {return x +
1; };MyDel le3 =x => {return x + 1; };MyDel le4 =x =>x + 1;Console.WriteLine ("{0}", del
(12));Console.WriteLine ("{0}", le1 (12)); Console.WriteLine ("{0}", le2 (12));Console.WriteLine ("{0}", le3
(12)); Console.WriteLine ("{0}", le4 (12));}Alguns pontos importantes sobre as listas de parâmetros de
expressão lambda são os seguintes:•Os parâmetros na lista de parâmetros de uma expressão lambda
devem corresponder aos do delegado emnúmero, tipo e posição.•Os parâmetros na lista de parâmetros
de uma expressão não precisam incluir o tipo (ou seja,digitados implicitamente ) a menos que o
delegado tenha parâmetros ref ou out - neste caso, os tipos sãoobrigatório (isto é, digitado
explicitamente ).•Se houver apenas um único parâmetro e ele for digitado implicitamente, os parênteses
ao redor podem seromitido. Caso contrário, eles são obrigatórios.•Se não houver parâmetros, você deve
usar um conjunto vazio de parênteses.A Figura 15-16 mostra a sintaxe para expressões lambda.Figura
15-16. A sintaxe para expressões lambda consiste no operador lambda com a seção de parâmetroà
esquerda e o corpo lambda à direita.

Página 421

CAPÍTULO 16■ ■ ■391Eventos■ Os eventos são como delegados■ Visão geral dos componentes do
código-fonte■ Declaração de um evento■ Levantando um Evento■ Inscrever-se em um evento■ Uso
de evento padrão■ O código MyTimerClass■ Acessadores de eventos

Página 422

CAPÍTULO 16 ■ EVENTOS392Os eventos são como delegadosO capítulo anterior cobriu os delegados.
Muitos aspectos dos eventos são semelhantes aos dos delegados. NoNa verdade, um evento é como um
delegado mais simples especializado para um uso específico. A Figura 16-1 ilustra que,como um
delegado, um evento possui métodos registrados com ele e invoca esses métodos quando é invocado.A
seguir estão alguns termos importantes relacionados a eventos:•Gerando um evento : O termo para
invocar ou disparar um evento. Quando um evento é gerado, todos osos métodos registrados com ele
são chamados - em ordem.•Editor: Uma classe ou estrutura que disponibiliza um evento para outras
classes ou estruturas para seu uso.•Assinante : uma classe ou estrutura que registra métodos com um
editor.•Manipulador de eventos : um método que é registrado com um evento. Pode ser declarado na
mesma classe oustruct como o evento ou em uma classe ou struct diferente.Figura 16-1 . Editores e
assinantesBaixe a partir de Wow! e-book <www.wowebook.com>

Página 423

CAPÍTULO 16 ■ EVENTOS393Um evento tem um delegado privadoHá boas razões para as semelhanças
nos comportamentos dos delegados e eventos. Um evento contém umdelegado privado, conforme
ilustrado na Figura 16-2. O que é importante saber sobre a privacidade de um eventodelegado são os
seguintes:•Um evento dá acesso estruturado ao seu delegado controlado de forma privada.•Ao
contrário de muitas operações disponíveis com um delegado, com um evento você só pode adicionar,
remover,e invocar manipuladores de eventos.•Quando um evento é gerado, ele invoca o delegado, que
chama sequencialmente os métodos nolista de invocação.Observe na Figura 16-2 que apenas os
operadores + = e - = estão destacados à esquerda do evento. Isto éporque são as únicas operações
permitidas em um evento.Figura 16-2. Um evento tem um delegado encapsuladoA Figura 16-3 ilustra a
visualização do tempo de execução de uma classe de publicador com um evento denominado Elapsed.
Classe A eClassB, à direita, cada um possui um manipulador de eventos registrado com Elapsed. Dentro
do evento você pode ver odelegado referenciando os dois manipuladores de eventos. Além do evento, a
editora também contém o código quelevanta o evento.Figura 16-3 . Estrutura e terminologia de uma
aula com um evento temporizador

Página 424

CAPÍTULO 16 ■ EVENTOS394Visão geral dos componentes do código-fonteCinco componentes de


código precisam estar no local para usar eventos. Vou cobrir cada um deles a seguirseções, e eles são
ilustrados na Figura 16-4. Esses componentes são os seguintes:•Declaração de tipo de delegado : O
evento e os manipuladores de eventos devem ter uma assinatura comum etipo de retorno, que é
descrito pela declaração do tipo delegado.•Declarações do manipulador de eventos : Estas são as
declarações nas classes de assinante dos métodos(manipuladores de eventos) a serem executados
quando o evento é gerado. Estes não precisam ser separadosmétodos. Eles podem ser métodos
anônimos ou expressões lambda.•Declaração do evento: esta é a declaração na classe do editor do
evento que mantém einvoca os manipuladores de eventos.•Registro de evento : este é o código que
conecta os manipuladores de evento ao evento.•Código que gera o evento : este é o código no editor
que chama o evento, fazendo com que eleinvocar seus manipuladores de eventos.Figura 16-4. Os cinco
componentes do código-fonte do uso de um evento

Página 425

CAPÍTULO 16 ■ EVENTOS395Declarando um EventoO editor deve fornecer o evento e geralmente


fornece o código para gerá-lo.Criar um evento é simples - requer apenas um tipo de delegado e um
nome. A sintaxe para um eventodeclaração é mostrada no código a seguir, que declara um evento
denominado Elapsed. Observe o seguintesobre o evento decorrido:•Ele é declarado dentro de uma
classe chamada MyTimerClass.•Ele aceita manipuladores de eventos com o tipo de retorno e assinatura
correspondente ao tipo de delegadoEventHandler.•Ele é declarado público para que outras classes e
estruturas possam registrar manipuladores de eventos com ele.classe MyTimerClass{Palavra-chaveNome
do evento↓↓evento público EventHandler decorrido;↑Tipo de delegadoVocê pode declarar mais de um
evento em uma instrução de declaração usando uma lista separada por vírgulas.Por exemplo, a seguinte
instrução declara três eventos:evento público EventHandler MyEvent1, MyEvent2, OtherEvent;↑Três
eventosVocê também pode tornar os eventos estáticos, incluindo a palavra-chave static, conforme
mostrado a seguirdeclaração:evento público estático EventHandler decorrido;↑Palavra-chave

Página 426

CAPÍTULO 16 ■ EVENTOS396Um evento é um membroUm erro comum é pensar em um evento como


um tipo, o que não é. Um evento é um membro , e hávárias ramificações importantes para isso:•Como
um membro não é um tipo, você não usa uma expressão de criação de objeto (uma nova expressão)para
criar seu objeto.•Porque um evento é um membro- Deve ser declarado em uma classe ou estrutura, com
os outros membros.- Você não pode declarar um evento em um bloco de código executável.•Um
membro do evento é inicializado implícita e automaticamente como nulo com os outros membros.O tipo
de delegado e EventHandlerUma declaração de evento requer o nome de um tipo de delegado . Você
pode declarar um ou usar um quejá existe. Se você declarar um tipo de delegado, ele deve especificar a
assinatura e o tipo de retorno dos métodosque será armazenado pelo evento.Uma ideia melhor é usar o
delegado EventHandler, que é um tipo de delegado predefinido usado pelo.NET BCL e designado como o
padrão para uso com eventos. Você é fortemente encorajado a usá-lo. oo código a seguir mostra como a
declaração do EventHandler se parece no BCL. O delegado EventHandleré abordado com mais detalhes
posteriormente neste capítulo.delegado público void EventHandler (remetente do objeto, EventArgs e);

Página 427

CAPÍTULO 16 ■ EVENTOS397Levantando um EventoO próprio membro do evento contém apenas os


manipuladores de eventos que precisam ser chamados. Nada acontececom eles, a menos que o evento
seja gerado. Você precisa ter certeza de que há código para fazer exatamente isso, nomomentos
apropriados.Por exemplo, o código a seguir gera o evento Elapsed. Observe o seguinte sobre o
código:•Antes de disparar o evento, o código o compara com nulo, para ver se ele contém algum
eventomanipuladores. Se o evento for nulo, ele estará vazio.•Gerar o próprio evento é como invocar
uma função.- Use o nome do evento, seguido pela lista de parâmetros entre parênteses.- A lista de
parâmetros deve corresponder ao tipo de delegado do evento.if (decorrido! = nulo)// Certifique-se de
que existem métodos para executar.Decorrido (fonte, args);// Aumente o evento.↑↑Lista de parâmetros
de nome de eventoJuntar a declaração do evento e o código para gerar o evento dá a seguinte
classedeclaração para o editor. O código contém dois membros: o evento e um método
chamadoOnOneSecond, que gera o evento.public class MyTimerClass{evento público EventHandler
decorrido; // Declare o evento.private void OnOneSecond (fonte do objeto, argumentos EventArgs){if
(Elapsed! = null) // Certifique-se de que existem métodos para executar.Decorrido (fonte, args);}↑Levante
o evento.// O código a seguir garante que o método OnOneSecond seja chamado a cada// 1.000
milissegundos....}Por enquanto, vou deixar o método OnOneSecond ser de alguma forma,
misteriosamente, chamado uma vez a cada segundo. Mais tarde emo capítulo mostrarei como fazer isso
acontecer. Mas, por enquanto, lembre-se destes pontos importantes:•A classe do editor tem um evento
como membro.•A classe contém o código para gerar o evento.

Página 428

CAPÍTULO 16 ■ EVENTOS398Inscrever-se em um eventoPara adicionar um manipulador de eventos a


um evento, o manipulador deve ter o mesmo tipo de retorno e assinatura que odelegado do
evento.•Use o operador + = para adicionar um manipulador de eventos a um evento, conforme
mostrado no código a seguir.•O método pode ser qualquer um dos seguintes:- Um método de instância-
Um método estático- Um método anônimo- Uma expressão lambdaPor exemplo, o código a seguir
adiciona três métodos ao evento Elapsed. O primeiro é uma instânciamétodo usando o formulário de
método. O segundo é um método estático usando o formulário de método. O terceiro é ummétodo de
instância usando o formulário de delegado.Instância de classeMétodo de instância↓↓mc.Elapsed + =
ca.TimerHandlerA;// Formulário de referência do métodomc.Elapsed + = ClassB.TimerHandlerB;//
Formulário de referência do método↑↑Membro do eventoMétodo estáticomc.Elapsed + = novo
EventHandler (cc.TimerHandlerC); // Delegar formulárioAssim como com delegados, você pode usar
métodos anônimos e expressões lambda para adicionar eventosmanipuladores. Por exemplo, o código a
seguir primeiro usa uma expressão lambda e depois usa ummétodo anônimo.mc.Elapsed + = (fonte,
args) =>// Lambda expression{Console.WriteLine ("Expressão Lambda.");};mc.Elapsed + = delegate (fonte
do objeto, EventArgs args) // Método anônimo{Console.WriteLine ("Método anônimo.");};

Página 429

CAPÍTULO 16 ■ EVENTOS399O programa a seguir usa a classe MyTimerClass declarada na seção


anterior. O códigoexecuta o seguinte:•Ele registra dois manipuladores de eventos de duas instâncias de
classe diferentes.•Depois de registrar os manipuladores de eventos, ele dorme por dois segundos.
Durante esse tempo, a classe do cronômetrogera o evento duas vezes e ambos os manipuladores de
eventos são executados a cada vez.public class MyTimerClass {...}classe ClassA{public void
TimerHandlerA (fonte do objeto, EventArgs args) // Manipulador de eventos{Console.WriteLine
("Manipulador de Classe A chamado");}}classe ClassB{public static void TimerHandlerB (fonte do objeto,
EventArgs args) // Estático{Console.WriteLine ("Manipulador de Classe B chamado");}}programa de
aula{static void Main (){ClasseA ca = nova ClasseA ();// Crie o objeto de classe.MyTimerClass mc = new
MyTimerClass (); // Crie o objeto cronômetro.mc.Elapsed + = ca.TimerHandlerA;// Adicionar o
manipulador A - instância.mc.Elapsed + = ClassB.TimerHandlerB; // Adicionar manipulador B -
estático.Thread.Sleep (2250);}}Quando fornecido com o código para MyTimerClass, este código produz a
seguinte saída:Manipulador de classe A chamadoManipulador de classe B chamadoManipulador de
classe A chamadoManipulador de classe B chamado

Página 430

CAPÍTULO 16 ■ EVENTOS400Removendo manipuladores de eventosQuando terminar de usar um


manipulador de eventos, você deve removê-lo do evento, para permitir que o lixocoletor para liberar
essa memória. Você remove um manipulador de eventos de um evento usando o operador - =,como
mostrado aqui:mc.Elapsed - = ca.TimerHandlerA;// Remova o manipulador A.Por exemplo, o código a
seguir remove o manipulador de eventos para ClassB após as duas primeiras vezes que oevento é gerado
e, em seguida, permite que o programa seja executado por mais dois segundos....mc.Elapsed + =
ca.TimerHandlerA;// Adicione o manipulador de instância A.mc.Elapsed + = ClassB.TimerHandlerB; //
Adicione o manipulador estático B.Thread.Sleep (2250);// Durma mais de 2 segundos.mc.Elapsed - =
ClassB.TimerHandlerB; // Remova o manipulador estático B.Console.WriteLine ("manipulador de eventos
Classe B removido");Thread.Sleep (2250);// Durma mais de 2 segundos.Este código produz a seguinte
saída. As primeiras quatro linhas são o resultado de ambos os manipuladores seremchamado duas vezes,
nos primeiros dois segundos. Depois que o manipulador para ClassB é removido, apenas o manipulador
para oinstância de ClassA é chamada, durante os últimos dois segundos.Manipulador de classe A
chamadoManipulador de classe B chamadoManipulador de classe A chamadoManipulador de classe B
chamadoManipulador de eventos de classe B removidoManipulador de classe A chamadoManipulador
de classe A chamado

Página 431

CAPÍTULO 16 ■ EVENTOS401Uso de evento padrãoA programação GUI é orientada por eventos, o que
significa que enquanto o programa está em execução, ele pode ser interrompidoa qualquer momento
por eventos como cliques em botões, pressionamentos de teclas ou temporizadores do sistema. Quando
isso acontece, oo programa precisa lidar com o evento e então continuar seu curso.Claramente, esse
tratamento assíncrono de eventos de programa é a situação perfeita para usar eventos C #.A
programação Windows GUI usa eventos tão extensivamente que existe um padrão .NET Framework
padrãopara usá-los, que você é fortemente encorajado a seguir.A base do padrão padrão para uso de
eventos é o tipo de delegado EventHandler, que édeclarado no namespace System. A declaração do tipo
de delegado EventHandler é mostrada noseguinte código:•O primeiro parâmetro destina-se a conter
uma referência ao objeto que gerou o evento. É do tipoobjeto e pode, portanto, corresponder a
qualquer instância de qualquer tipo.•O segundo parâmetro destina-se a conter informações de estado
de qualquer tipo apropriado para oinscrição.•O tipo de retorno é nulo.delegado público void
EventHandler (remetente do objeto, EventArgs e);Usando a classe EventArgsO segundo parâmetro no
tipo de delegado EventHandler é um objeto da classe EventArgs, que édeclarado no namespace System.
Você pode ficar tentado a pensar que, uma vez que o segundo parâmetro édestinado a transmitir dados,
um objeto de classe EventArgs seria capaz de armazenar dados de algum tipo. Você poderiaestar
errado.•A classe EventArgs foi projetada para não transportar dados. É usado para manipuladores de
eventos que não precisamtransmitir dados - e geralmente é ignorado por eles.•Se você deseja passar
dados, você deve declarar uma classe derivada de EventArgs, com o apropriadocampos para conter os
dados que você deseja transmitir.Mesmo que a classe EventArgs não passe dados, é uma parte
importante do padrão deusando o delegado EventHandler. Objeto de classe e classe EventArgs são as
classes base para qualqueros tipos reais são usados como parâmetros. Isso permite que o EventHandler
forneça uma assinatura que é omenor denominador comum para todos os eventos e manipuladores de
eventos, permitindo que eles tenham exatamente doisparâmetros, em vez de ter assinaturas diferentes
para cada caso.

Página 432

CAPÍTULO 16 ■ EVENTOS402Transmissão de dados estendendo EventArgsPara passar dados no segundo


parâmetro do seu manipulador de eventos e aderir às convenções padrão,você precisa declarar uma
classe personalizada derivada de EventArgs que pode armazenar os dados que você precisa passar. oo
nome da classe deve terminar em EventArgs . Por exemplo, o código a seguir declara uma classe
personalizada quepode armazenar uma string em um campo chamado Message:Nome da classe
personalizada Classe base↓↓public class MyTCEventArgs: EventArgs{public string Message;// Armazena
uma mensagempublic MyTCEventArgs (string s)// O construtor define a mensagem.{Message = s;}}Baixe
a partir de Wow! e-book <www.wowebook.com>

Página 433
CAPÍTULO 16 ■ EVENTOS403Usando o Delegado PersonalizadoAgora que você tem uma classe
personalizada para passar dados no segundo parâmetro de seus manipuladores de eventos, vocêprecisa
de um tipo de delegado que usa a nova classe personalizada. Há duas maneiras de você fazer isto:•A
primeira maneira é usar um delegado não genérico. Para fazer isso, faça o seguinte:- Crie um novo
delegado personalizado usando seu tipo de classe personalizada, conforme mostrado no código a seguir.-
Use o novo nome do delegado nas outras quatro seções do código do evento.Nome de delegado
personalizadoClasse personalizada↓↓delegado público void MyTCEventHandler (objeto remetente,
MyTCEventArgs e);•A segunda maneira foi introduzida com C # 2.0 e usa o delegado genérico
EventHandler <>.O Capítulo 19 cobre os genéricos do C #. Para usar o delegado genérico, faça o seguinte,
conforme mostrado nocódigo a seguir:- Coloque o nome da classe personalizada entre os colchetes
angulares.- Use a string inteira onde quer que você tenha usado o nome do seu tipo de delegado
personalizado.Por exemplo, esta é a aparência da declaração do evento:Delegado genérico usando classe
personalizada↓evento público EventHandler <MyTCEventArgs> Decorrido;↑Nome do eventoUse a classe
personalizada e o delegado personalizado, não genérico ou genérico, nas outras quatroseções de código
que tratam do evento.Por exemplo, o código a seguir atualiza o código MyTimerClass para usar uma
classe EventArgs personalizadachamado MyTCEventArgs e o EventHandler <> delegado genérico.

Página 434

CAPÍTULO 16 ■ EVENTOS404public class MyTCEventArgs: EventArgs{public string Message;Declaração


de classe personalizadapublic MyTCEventArgs (string s) {Message = s;}}public class MyTimerClass Generic
delegate{↓evento público EventHandler <MyTCEventArgs> Decorrido; // Declaração de eventoprivate
void OnOneSecond (fonte do objeto, argumentos EventArgs){if (decorrido! = nulo){MyTCEventArgs
mtcea =new MyTCEventArgs ("Mensagem de OnOneSecond");Código para aumentar o eventoDecorrido
(fonte, mtcea);}}... // Este código é fornecido no final do capítulo.}classe ClassA{public void
TimerHandlerA (fonte do objeto, argumentos MyTCEventArgs){Manipulador de
eventosConsole.WriteLine ("Mensagem de Classe A: {0}", args.Message);}}

Página 435

CAPÍTULO 16 ■ EVENTOS405programa de aula{static void Main (){ClasseA ca = nova ClasseA


();MyTimerClass mc = new MyTimerClass ();mc.Elapsed + =// Registra o manipulador.novo EventHandler
<MyTCEventArgs> (ca.TimerHandlerA);Thread.Sleep (3250);}}Este código produz a seguinte
saída:Mensagem Classe A: Mensagem de OnOneSecondMensagem Classe A: Mensagem de
OnOneSecondMensagem Classe A: Mensagem de OnOneSecond

Página 436

CAPÍTULO 16 ■ EVENTOS406O código MyTimerClassAgora que você viu todos os cinco componentes de
código que precisam ser implementados para usar um evento, possomostra a classe MyTimerClass
completa que o código está usando.A maioria das coisas sobre a aula foram bem claras - ela tem um
evento chamado decorrido que pode serinscrito e um método chamado OnOneSecond que é chamado a
cada segundo e gera o evento. ÚnicoA questão remanescente sobre isso é: "O que faz com que
OnOneSecond seja chamado a cada segundo?"A resposta é que criei o método OnOneSecond e o
inscrevi como um manipulador de eventos para umevento em uma classe chamada Timer, no
namespace System.Timers. O evento no Timer é gerado a cada 1.000milissegundos e chama o
manipulador de eventos OnOneSecond, que por sua vez gera o evento Elapsed na classeMyTimerClass. A
Figura 16-5 mostra a estrutura do código.Figura 16-5 . A estrutura de código de MyTimerClassA classe
Timer é uma ferramenta útil, então mencionarei um pouco mais sobre ela. Primeiro, tem um evento
público chamadoDecorrido. Se isso parece familiar, é porque eu nomeei o evento em MyTimerClass
depois dele. Os nomes têmnenhuma outra conexão além dessa. Eu poderia ter nomeado o evento em
MyTimerClass qualquer coisa.Uma das propriedades de Timer é Interval, que é do tipo double e
especifica o número demilissegundos entre o aumento do evento. A outra propriedade que o código usa
é Enabled, que é do tipobool e inicia e interrompe o cronômetro.

Página 437

CAPÍTULO 16 ■ EVENTOS407O código real é o seguinte. As únicas coisas que não mostrei anteriormente
são o cronômetro privadocampo, denominado MyPrivateTimer, e o construtor da classe. O construtor
faz o trabalho de configuraçãoo cronômetro interno e anexando-o ao manipulador de eventos
OnOneSecond.public class MyTimerClass{evento público EventHandler decorrido;private void
OnOneSecond (fonte do objeto, argumentos EventArgs){if (decorrido! = nulo)Decorrido (fonte, args);}//
------------private System.Timers.Timer MyPrivateTimer; // Temporizador privadopublic MyTimerClass ()//
Construtor{MyPrivateTimer = new System.Timers.Timer (); // Crie o cronômetro privado.// A seguinte
declaração define nosso método OnOneSecond acima como um evento// manipulador para o evento
Decorrido da classe Timer. É completamente// não relacionado ao nosso evento Decorrido, declarado
acima.MyPrivateTimer.Elapsed + = OnOneSecond; // Anexe nosso manipulador de eventos.// Property
Interval é do tipo double e especifica o número de// milissegundos entre quando seu evento é
gerado.MyPrivateTimer.Interval = 1000;// intervalo de 1 segundo.// Property Enabled é do tipo bool e
liga e desliga o cronômetro.MyPrivateTimer.Enabled = true;// Inicie o cronômetro.}}

Página 438

CAPÍTULO 16 ■ EVENTOS408Acessores de eventosO último tópico a ser abordado neste capítulo são os
acessadores de eventos. Mencionei anteriormente que os operadores + = e - =foram os únicos
operadores permitidos para um evento. Esses operadores têm o comportamento bem definido quevocê
viu até agora neste capítulo.Você pode, no entanto, alterar o comportamento desses operadores e fazer
com que o evento execute qualquercódigo que você gosta quando eles são usados. Você pode fazer isso
definindo acessadores de evento para o evento.•Existem dois acessores: adicionar e remover.•A
declaração de um evento com acessores é semelhante à declaração de uma propriedade.O exemplo a
seguir mostra a forma de uma declaração de evento com acessadores. Ambos acessorestem um
parâmetro de valor implícito chamado valor que faz referência a um método de instância oumétodo
estático.evento público EventHandler decorrido{adicionar{...// Código para implementar o operador =
+}remover{...// Código para implementar o operador - =}}Quando os acessadores de evento são
declarados, o evento não contém um objeto delegado incorporado. Vocêsdeve implementar seu próprio
mecanismo de armazenamento para armazenar e remover os métodos registrados como evento.Os
acessadores de evento agem como métodos nulos, o que significa que eles não podem usar instruções
de retorno queretorna um valor.

Página 439

CAPÍTULO 17■ ■ ■409Interfaces■ O que é uma interface?■ Declarando uma interface■


Implementando uma interface■ Uma interface é um tipo de referência■ Usando o como Operador com
Interfaces■ Implementando várias interfaces■ Implementando interfaces com membros duplicados■
Referências a várias interfaces■ Um membro herdado como uma implementação■ Implementações
explícitas de membros de interface■ Interfaces podem herdar interfaces

Página 440

CAPÍTULO 17 ■ INTERFACES410O que é uma interface?Uma interface é um tipo de referência que


especifica um conjunto de membros de função, mas não os implementa.Outros tipos - classes ou
estruturas - podem implementar interfaces.Para ter uma ideia das interfaces, começarei mostrando uma
que já está definida. O BCL declara uminterface chamada IComparable, cuja declaração é mostrada no
código a seguir. Observe que ocorpo da interface contém a declaração de um único método, CompareTo,
que leva um único parâmetro detipo de objeto. Embora o método tenha um nome, parâmetros e um
tipo de retorno, não háimplementação. Em vez disso, a implementação é substituída por um ponto e
vírgula.Nome da interface de palavras-chave↓↓interface pública IComparable{int CompareTo (objeto
obj);}↑Ponto e vírgula no lugar da implementação do métodoA Figura 17-1 ilustra a interface
IComparable. O método CompareTo é mostrado em cinza para ilustrarque não contém uma
implementação.Figura 17-1 . Representação da interface IComparableEmbora a declaração da interface
não forneça uma implementação para o método CompareTo, oA documentação .NET da interface
IComparable descreve o que o método deve fazer, caso você crie umclasse ou estrutura que implementa
a interface. Diz que quando o método CompareTo é chamado, ele deveretorna um dos seguintes
valores:•Um valor negativo, se o objeto atual for menor que o objeto de parâmetro•Um valor positivo,
se o objeto atual for maior que o objeto de parâmetro•Zero, se os dois objetos forem considerados
iguais na comparação

Página 441

CAPÍTULO 17 ■ INTERFACES411Exemplo de uso da interface IComparablePara entender o que isso


significa e por que é útil, vamos começar dando uma olhada no código a seguir,que pega uma matriz não
classificada de inteiros e os classifica em ordem crescente.•A primeira linha cria uma matriz de cinco
inteiros que não estão em uma ordem específica.•A segunda linha usa o método estático Sort da classe
Array para classificar os elementos.•O loop foreach os imprime, mostrando que os inteiros agora estão
em ordem crescente.var myInt = new [] {20, 4, 16, 9, 2}; // Cria um array de ints.Array.Sort (myInt);//
Classifica os elementos por magnitude.foreach (var i in myInt)// Imprima-os.Console.Write ("{0}", i);Este
código produz a seguinte saída:2 4 9 16 20O método Sort da classe Array funciona muito bem em uma
matriz de ints, mas o que aconteceria se você fossetentar usá-lo em uma de suas próprias aulas,
conforme mostrado aqui?classe MyClass// Declara uma classe simples.{public int TheValue;}...MyClass []
mc = new MyClass [5];// Crie um array de cinco elementos....// Crie e inicialize os elementos.Array.Sort
(mc);// Tenta usar Sort - levanta a exceçãoQuando você tenta executar esse código, ele gera uma
exceção em vez de classificar os elementos. O motivoSort não funciona com o array de objetos MyClass
é que ele não sabe como comparar os dados definidos pelo usuárioobjetos e como classificar sua
ordem.O algoritmo usado por Sort depende do fato de que ele pode usar o método CompareTo do
elemento paradeterminar a ordem de dois elementos. O tipo int implementa IComparable, mas MyClass
não, entãoquando Sort tenta chamar o método CompareTo inexistente de MyClass, ele gera uma
exceção.

Página 442

CAPÍTULO 17 ■ INTERFACES412Você pode fazer o método Sort funcionar com objetos do tipo MyClass
fazendo com que a classe implementeIComparável. Para implementar uma interface, uma classe ou
estrutura deve fazer duas coisas:•Ele deve listar o nome da interface em sua lista de classes base.•Ele
deve fornecer uma implementação para cada um dos membros da interface.Por exemplo, o código a
seguir atualiza MyClass para implementar a interface IComparable. Observe oseguinte sobre o código:•O
nome da interface está listado na lista de classes base da declaração da classe.•A classe implementa um
método chamado CompareTo, cujo tipo de parâmetro e tipo de retorno correspondemaqueles do
membro da interface.•O método CompareTo é implementado para satisfazer a definição dada na
interfacedocumentação. Ou seja, ele retorna 1 negativo, 1 positivo ou 0, dependendo de seu valor
comparadopara o objeto passado para o método.Nome da interface na lista de classes base↓classe
MyClass: IComparable{public int TheValue;public int CompareTo (object obj) // Implementação do
método de interface{MinhaClasse mc = (MinhaClasse) obj;if (this.TheValue <mc.TheValue) retorna -1;if
(this.TheValue> mc.TheValue) retorna 1;return 0;}}A Figura 17-2 ilustra a classe atualizada. A seta do
método de interface acinzentado para a classemétodo indica que o método de interface não contém
código, mas é implementado pelo nível de classemétodo.Figura 17-2. Implementando IComparable em
MyClassBaixe a partir de Wow! e-book <www.wowebook.com>

Página 443

CAPÍTULO 17 ■ INTERFACES413Agora que MyClass implementa IComparable, Sort funcionará


perfeitamente. Não seria, aliás,foram suficientes para apenas declarar o método CompareTo - ele deve
fazer parte da implementação dointerface, o que significa colocar o nome da interface na lista de classes
base.A seguir mostra o código atualizado completo, que agora pode usar o método Sort para classificar
ummatriz de objetos MyClass. Main cria e inicializa um array de objetos MyClass e depois os imprime.Em
seguida, chama Sort e os imprime novamente para mostrar que foram classificados.classe MyClass:
IComparable// Classe implementa interface.{public int TheValue;public int CompareTo (objeto obj)//
Implementar o método.{MinhaClasse mc = (MinhaClasse) obj;if (this.TheValue <mc.TheValue) retorna
-1;if (this.TheValue> mc.TheValue) retorna 1;return 0;}}programa de aula{static void PrintOut (string s,
MyClass [] mc){Console.Write (s);foreach (var m em mc)Console.Write ("{0}",
m.TheValue);Console.WriteLine ("");}static void Main (){var myInt = new [] {20, 4, 16, 9, 2};MyClass []
mcArr = new MyClass [5]; // Cria um array de objs de MyClass.para (int i = 0; i <5; i ++)// Inicialize o array.
{mcArr [i] = new MyClass ();mcArr [i] .TheValue = myInt [i];}PrintOut ("Pedido inicial:", mcArr); // Imprime
o array inicial.Array.Sort (mcArr);// Classifica a matriz.PrintOut ("Ordem classificada:", mcArr); //
Imprime a matriz classificada.}}Este código produz a seguinte saída:Pedido inicial: 20 4 16 9 2Ordem
classificada: 2 4 9 16 20

Página 444

CAPÍTULO 17 ■ INTERFACES414Declarando uma interfaceA seção anterior usou uma interface que já foi
declarada no BCL. Nesta seção, você verácomo declarar interfaces.O que é importante saber sobre como
declarar uma interface são os seguintes:•Uma declaração de interface não pode conter membros de
dados .•Uma declaração de interface pode conter apenas declarações dos seguintes tipos de
nonstaticmembros da função:- Métodos- Propriedades- Eventos- Indexadores•As declarações desses
membros de função não podem conter nenhum código de implementação. Em vez disso, umO ponto e
vírgula deve ser usado para o corpo de cada declaração de membro.•Por convenção, os nomes de
interface começam com um I maiúsculo (por exemplo, ISaveable).•Como classes e estruturas, as
declarações de interface também podem ser divididas em declarações de interface parciais,conforme
descrito na seção “Classes parciais” do Capítulo 6.

Página 445

CAPÍTULO 17 ■ INTERFACES415O código a seguir mostra um exemplo de declaração de uma interface


com dois membros do método:Nome da interface de palavras-chave↓↓interface IMyInterface1Ponto e
vírgula no lugar do corpo{↓int DoStuff (int nVar1, long lVar2);Double DoOtherStuff (string s, x
longo);}↑Ponto e vírgula no lugar do corpoHá uma diferença importante entre a acessibilidade de uma
interface e a acessibilidade demembros da interface:•Uma declaração de interface pode ter qualquer
um dos modificadores de acesso público, protegido, interno ouprivado.•Os membros de uma interface,
no entanto, são implicitamente públicos e nenhum modificador de acesso, incluindopúblico, são
permitidos.Modificadores de acesso são permitidos em interfaces.↓interface pública
IMyInterface2{private int Method1 (int nVar1, long lVar2);// Error} ↑Modificadores de acesso NÃO são
permitidos em membros da interface.

Página 446

CAPÍTULO 17 ■ INTERFACES416Implementando uma InterfaceApenas classes ou estruturas podem


implementar uma interface. Conforme mostrado no exemplo de classificação, para implementar
uminterface, uma classe ou estrutura deve•Inclui o nome da interface em sua lista de classes
base•Fornece implementações para cada um dos membros da interfacePor exemplo, o código a seguir
mostra uma nova declaração para a classe MyClass, que implementainterface IMyInterface1, declarada
na seção anterior. Observe que o nome da interface está listado nolista de classes base após os dois
pontos e que a classe fornece o código de implementação real para a interfacemembros.Nome da
interface do cólon↓↓class MyClass: IMyInterface1{int DoStuff (int nVar1, long lVar2){...}// Código de
implementaçãoDouble DoOtherStuff (string s, x longo){...}// Código de implementação}Algumas coisas
importantes a saber sobre a implementação de interfaces são as seguintes:•Se uma classe implementa
uma interface, ela deve implementar todos os membros dessa interface.•Se uma classe é derivada de
uma classe base e também implementa interfaces, o nome da classe basedeve ser listado na lista de
classes base antes de qualquer interface, conforme mostrado aqui:A classe base deve ser o primeiro
nome da interface↓↓classe Derivado: MyBaseClass, IIfc1, IEnumerable, IComparable{...}

Página 447

CAPÍTULO 17 ■ INTERFACES417Exemplo com uma interface simplesO código a seguir declara uma
interface chamada IIfc1, que contém um único método chamado PrintOut.A classe MyClass implementa
a interface IIfc1 listando-a em sua lista de classes base e fornecendo um métodochamado PrintOut que
corresponde à assinatura e ao tipo de retorno do membro da interface. Main cria umobjeto da classe e
chama o método do objeto.interface IIfc1 Ponto e vírgula no lugar do corpo// Declare interface{↓void
PrintOut (string s);}Implementar interface↓classe MyClass: IIfc1// Declarar classe{public void PrintOut
(string s)// Implementation{Console.WriteLine ("Chamando através de: {0}", s);}}programa de aula{static
void Main (){MyClass mc = new MyClass ();// Criar instânciamc.PrintOut ("objeto");// Método de
chamada}}Este código produz a seguinte saída:Chamando através de: objeto

Página 448

CAPÍTULO 17 ■ INTERFACES418Uma interface é um tipo de referênciaUma interface é mais do que


apenas uma lista de membros para uma classe ou estrutura implementar. É um tipo de referência.Você
não pode acessar uma interface diretamente por meio dos membros do objeto de classe. Você pode, no
entanto, obter umreferência à interface lançando a referência de objeto de classe para o tipo da
interface. Uma vez que você tenhauma referência à interface, você pode usar a notação de sintaxe de
ponto com a referência à interface de chamadamembros.Por exemplo, o código a seguir mostra um
exemplo de como obter uma referência de interface de uma classereferência de objeto.•Na primeira
instrução, a variável mc é uma referência a um objeto de classe que implementa a interface IIfc1.A
instrução converte essa referência a uma referência à interface e a atribui à variável ifc.•A segunda
instrução usa a referência à interface para chamar o método de implementação.Interface Cast para
interface↓↓IIfc1 ifc = (IIfc1) mc;// Obter ref para interface↑↑Interface ref Classe objeto refifc.PrintOut
("interface");// Use ref para interface para chamar o membro↑Use a notação de sintaxe de ponto para
chamar por meio da referência de interface.Por exemplo, o código a seguir declara uma interface e uma
classe que a implementa. O código emMain cria um objeto da classe e chama o método de
implementação por meio do objeto da classe. Isso tambémcria uma variável do tipo de interface, projeta
a referência do objeto de classe para o tipo de interface echama o método de implementação por meio
da referência à interface. A Figura 17-3 ilustra a classee a referência à interface.

Página 449

CAPÍTULO 17 ■ INTERFACES419interface IIfc1{void PrintOut (string s);}classe MyClass: IIfc1{public void


PrintOut (string s){Console.WriteLine ("Chamando através de: {0}", s);}}programa de aula{static void Main
(){MyClass mc = new MyClass (); // Criar objeto de classemc.PrintOut ("objeto"); // Chamar método de
implementação de objeto de classeIIfc1 ifc = (IIfc1) mc; // Cast classe objeto ref para interface
refifc.PrintOut ("interface"); // Método de interface de chamada}}Este código produz a seguinte
saída:Chamando através de: objetoChamando por meio de: interfaceFigura 17-3 . Uma referência ao
objeto de classe e uma referência à interface

Página 450

CAPÍTULO 17 ■ INTERFACES420Usando o como Operador com InterfacesNa seção anterior, você viu que
pode usar o operador de conversão para obter uma referência aointerface. Uma ideia ainda melhor é
usar o operador as. O operador as é abordado em detalhes no Capítulo 18,mas vou mencioná-lo aqui
também, já que é uma boa escolha para usar com interfaces.Se você tentar lançar uma referência de
objeto de classe para uma referência de uma interface que a classe nãoimplementar, a operação de
conversão gerará uma exceção. Você pode evitar esse problema usando o comooperador em vez disso.
Funciona da seguinte maneira:•Se a classe implementa a interface, a expressão retorna uma referência à
interface.•Se a classe não implementa a interface, a expressão retorna nulo em vez de gerar
umexceção.O código a seguir demonstra o uso do operador as. A primeira linha usa o operador as
paraobter uma referência de interface de um objeto de classe. O resultado da expressão define o valor
de b comonull ou a uma referência a uma interface ILiveBirth.A segunda linha verifica o valor de b e, se
não for nulo, executa o comando que chama ométodo do membro da interface.Classe objeto ref Nome
da interface↓↓ILiveBirth b = a como ILiveBirth;// Atua como elenco: (ILiveBirth) a↑ ↑Operador de
Interfacerefif (b! = null)Console.WriteLine ("O bebê é chamado: {0}", b.BabyCalled ());

Página 451

CAPÍTULO 17 ■ INTERFACES421Implementando várias interfacesNos exemplos mostrados até agora, as


classes implementaram uma única interface.•Uma classe ou estrutura pode implementar qualquer
número de interfaces.•Todas as interfaces implementadas devem ser listadas na lista de classes base e
separadas por vírgulas(seguindo o nome da classe base, se houver).Por exemplo, o código a seguir
mostra a classe MyData, que implementa duas interfaces: IDataStoree IDataRetrieve. A Figura 17-4
ilustra a implementação das interfaces múltiplas na classe MyData.interface IDataRetrieve {int GetData
(); }// Declare interfaceinterface IDataStore {void SetData (int x); }// Declare
interfaceInterfaceInterface↓↓classe MyData: IDataRetrieve, IDataStore// Declarar classe{int Mem1;//
Declare campopublic int GetData (){return Mem1; }public void SetData (int x) {Mem1 = x; }}programa de
aula{static void Main ()// A Principal{Dados MyData = new MyData ();data.SetData (5);Console.WriteLine
("Value = {0}", data.GetData ());}}Este código produz a seguinte saída:Valor = 5Figura 17-4. Classe
implementando interfaces múltiplas

Página 452

CAPÍTULO 17 ■ INTERFACES422Implementando interfaces com membros duplicadosUma vez que uma


classe pode implementar qualquer número de interfaces, é possível que duas ou mais das interfacesos
membros podem ter a mesma assinatura e tipo de retorno. Então, como o compilador lida com
issosituação?Por exemplo, suponha que você tenha duas interfaces - IIfc1 e IIfc2 - conforme mostrado a
seguir. Cada interfacetem um método denominado PrintOut, com a mesma assinatura e tipo de retorno.
Se você fosse criar uma classe queimplementou ambas as interfaces, como você deve lidar com esses
métodos de interface duplicados?interface IIfc1{void PrintOut (string s);}interface IIfc2{void PrintOut
(string t);}A resposta é que se uma classe implementa várias interfaces, onde várias das interfaces
têmmembros com a mesma assinatura e tipo de retorno, a classe pode implementar um único membro
quesatisfaz todas as interfaces que contêm aquele membro duplicado.Por exemplo, o código a seguir
mostra a declaração da classe MyClass, que implementa ambosIIfc1 e IIfc2. Sua implementação do
método PrintOut atende aos requisitos para ambas as interfaces.classe MyClass: IIfc1, IIfc2//
Implementar ambas as interfaces.{public void PrintOut (string s)// Implementação única para
ambos{Console.WriteLine ("Chamando através de: {0}", s);}}programa de aula{static void Main (){MyClass
mc = new MyClass ();mc.PrintOut ("objeto");}}Baixe a partir de Wow! e-book <www.wowebook.com>

Página 453

CAPÍTULO 17 ■ INTERFACES423Este código produz a seguinte saída:Chamando através de: objetoA


Figura 17-5 ilustra os métodos de interface duplicados sendo implementados por um único nível de
classeimplementação do método.Figura 17-5. Várias interfaces implementadas pelo mesmo membro da
classe

Página 454

CAPÍTULO 17 ■ INTERFACES424Referências a múltiplas interfacesVocê viu anteriormente que as


interfaces são tipos de referência e que você pode obter uma referência para uma interfaceusando o
operador as ou lançando uma referência de objeto para o tipo de interface. Se uma classe
implementainterfaces múltiplas, você pode obter referências separadas para cada uma.Por exemplo, a
classe a seguir implementa duas interfaces com o único método PrintOut. ocódigo no método de
chamadas principais PrintOut de três maneiras:•Por meio do objeto de classe•Por meio de uma
referência à interface IIfc1•Por meio de uma referência à interface IIfc2A Figura 17-6 ilustra o objeto de
classe e as referências a IIfc1 e IIfc2.interface IIfc1// Declare interface{void PrintOut (string s);}interface
IIfc2// Declare interface{void PrintOut (string s);}classe MyClass: IIfc1, IIfc2// Declarar classe{public void
PrintOut (string s){Console.WriteLine ("Chamando através de: {0}", s);}}

Página 455

CAPÍTULO 17 ■ INTERFACES425programa de aula{static void Main (){MyClass mc = new MyClass ();IIfc1


ifc1 = (IIfc1) mc;// Obtenha ref para IIfc1IIfc2 ifc2 = (IIfc2) mc;// Obtenha ref para IIfc2mc.PrintOut
("objeto");// Chamada por meio de objeto de classeifc1.PrintOut ("interface 1");// Ligue através do
IIfc1ifc2.PrintOut ("interface 2");// Ligue através do IIfc2}}Este código produz a seguinte saída:Chamando
através de: objetoChamada por meio de: interface 1Chamando por: interface 2Figura 17-6. Referências
separadas para diferentes interfaces na classe

Página 456

CAPÍTULO 17 ■ INTERFACES426Um membro herdado como uma implementaçãoUma classe que


implementa uma interface pode herdar o código para uma implementação de um de seusAulas. Por
exemplo, o código a seguir ilustra uma classe que herda o código de implementação de umclasse
base.•IIfc1 é uma interface com um membro de método chamado PrintOut.•MyBaseClass contém um
método chamado PrintOut que corresponde ao método do IIfc1.•A classe Derived tem um corpo de
declaração vazio, mas deriva da classe MyBaseClass e contémIIfc1 em sua lista de classes base.•Mesmo
que o corpo da declaração de Derived esteja vazio, o código na classe base satisfaz orequisito para
implementar o método de interface.interface IIfc1 {void PrintOut (string s); }classe MyBaseClass//
Declara a classe base.{public void PrintOut (string s)// Declare o método.{Console.WriteLine ("Chamando
através de: {0}", s);}}classe Derivado: MyBaseClass, IIfc1// Declara classe.{}class Program {static void
Main (){D derivado = novo Derivado ();// Criar objeto de classed.PrintOut ("objeto.");// Método de
chamada}}A Figura 17-7 ilustra o código anterior. Observe que a seta de IIfc1 desce para o códigona
classe base.Figura 17-7 . Implementação na classe base

Página 457

CAPÍTULO 17 ■ INTERFACES427Implementações explícitas de membros de interfaceVocê viu em uma


seção anterior que uma única classe pode implementar todos os membros exigidos por váriosinterfaces,
conforme ilustrado nas Figuras 17-5 e 17-6.Mas e se você quiser implementações separadas para cada
interface? Neste caso, você pode criar o quesão chamados de implementações de membros de interface
explícitos . Uma implementação de membro de interface explícita temas seguintes características:•Como
todas as implementações de interface, ele é colocado na classe ou estrutura que implementa a
interface.•É declarado usando um nome de interface qualificado , que consiste no nome da interface e
no membronome, separado por um ponto.O código a seguir mostra a sintaxe para declarar
implementações de membros de interface explícitas. Cadadas duas interfaces implementadas por
MyClass implementa sua própria versão do método PrintOut.classe MyClass: IIfc1, IIfc2{Nome de
interface qualificado↓void IIfc1.PrintOut (string s)// Implementação explícita{...}void IIfc2.PrintOut (string
s)// Implementação explícita{...}}A Figura 17-8 ilustra a classe e as interfaces. Observe que as caixas que
representam o explícitoas implementações do membro da interface não são mostradas em cinza, pois
agora representam o código real.Figura 17-8 . Implementações explícitas de membros de interface

Página 458

CAPÍTULO 17 ■ INTERFACES428Por exemplo, no código a seguir, a classe MyClass declara um membro


de interface explícitoimplementações para os membros das duas interfaces. Observe que neste exemplo
há apenasimplementações de membros de interface explícitas. Não há implementação em nível de
classe.interface IIfc1 {void PrintOut (string s); } // Declare interfaceinterface IIfc2 {void PrintOut (string
t); } // Declare interfaceclasse MyClass: IIfc1, IIfc2{ Nome de interface qualificado↓void IIfc1.PrintOut
(string s)// membro da interface explícita{// implementaçãoConsole.WriteLine ("IIfc1: {0}", s);}Nome de
interface qualificado↓void IIfc2.PrintOut (string s)// membro da interface explícita{//
implementaçãoConsole.WriteLine ("IIfc2: {0}", s);}}programa de aula{static void Main (){MyClass mc =
new MyClass ();// Criar objeto de classeIIfc1 ifc1 = (IIfc1) mc;// Obtenha referência para IIfc1ifc1.PrintOut
("interface 1");// Chamar implementação explícitaIIfc2 ifc2 = (IIfc2) mc;// Obtenha referência para
IIfc2ifc2.PrintOut ("interface 2");// Chamar implementação explícita}}Este código produz a seguinte
saída:IIfc1: interface 1IIfc2: interface 2
Página 459

CAPÍTULO 17 ■ INTERFACES429A Figura 17-9 ilustra o código. Observe na figura que os métodos de
interface não estão apontando paraimplementações de nível de classe, mas contêm seu próprio
código.Figura 17-9. Referências a interfaces com implementações explícitas de membros de
interfaceQuando há uma implementação de membro de interface explícita, uma implementação em
nível de classe épermitido, mas não obrigatório. A implementação explícita satisfaz o requisito de que a
classe oustruct deve implementar o método. Você pode, portanto, ter qualquer um dos três
seguintescenários de implementação:•Uma implementação em nível de classe•Uma implementação de
membro de interface explícita•Implementação de membro de interface em nível de classe e explícita

Página 460

CAPÍTULO 17 ■ INTERFACES430Acessando implementações explícitas de membros de interfaceUma


implementação de membro de interface explícita pode ser acessada apenas por meio de uma referência
à interface.Isso significa que mesmo outros membros da classe não podem acessá-los diretamente.Por
exemplo, o código a seguir mostra a declaração da classe MyClass, que implementa interfaceIIfc1 com
uma implementação explícita. Observe que mesmo o Método1, que também é membro de MyClass,não
pode acessar diretamente a implementação explícita.•As duas primeiras linhas do Método1 produzem
erros de compilação porque o método está tentando acessar oimplementação diretamente.•Apenas a
última linha no Método1 irá compilar, porque ela converte a referência para o objeto atual(this) para
uma referência ao tipo de interface e usa essa referência para a interface para chamar oimplementação
de interface explícita.classe MyClass: IIfc1{void IIfc1.PrintOut (string s) // Implementação explícita da
interface{Console.WriteLine ("IIfc1");}public void Method1 (){Imprimir("...");// Erro de
compilaçãothis.PrintOut ("...");// Erro de compilação((IIfc1) isso) .PrintOut ("..."); // OK, método de
chamada} ↑} Lance para uma referência à interfaceEssa restrição tem uma ramificação importante para a
herança. Já que outros colegas de classenão pode acessar diretamente implementações de membros de
interface explícitas, membros de classes derivadas doa classe claramente também não pode acessá-los
diretamente. Eles devem sempre ser acessados por meio de uma referência aa interface.

Página 461

CAPÍTULO 17 ■ INTERFACES431Interfaces podem herdar interfacesVocê viu anteriormente que as


implementações de interface podem ser herdadas de classes base. Mas uma interface em sipode herdar
de uma ou mais interfaces.•Para especificar que uma interface herda de outras interfaces, coloque os
nomes das interfaces baseem uma lista separada por vírgulas após dois pontos após o nome da interface
na declaração da interface,como mostrado aqui:CólonLista de interface base↓↓interface IDataIO:
IDataRetrieve, IDataStore{...•Ao contrário de uma classe, que pode ter apenas um único nome de classe
em sua lista de classes base, uma interface pode terqualquer número de interfaces em sua lista de
interfaces base.- As próprias interfaces na lista podem ter interfaces herdadas.- A interface resultante
contém todos os membros que declara, bem como todos aqueles de sua baseinterfaces.O código na
Figura 17-10 mostra a declaração de três interfaces. Interface IDataIO herda deOs dois primeiros. A
figura à direita mostra o IDataIO englobando as outras duas interfaces.Figura 17-10 . Classe com
interface herdando várias interfaces

Página 462

CAPÍTULO 17 ■ INTERFACES432Exemplo de diferentes classes implementando uma interfaceO código a


seguir ilustra vários aspectos das interfaces que foram cobertos. O programa declarauma classe chamada
Animal, que é usada como uma classe base para várias outras classes que representam vários tipos
deanimais. Ele também declara uma interface chamada ILiveBirth.Todas as classes Cat, Dog e Bird
derivam da classe base Animal. Cat e Dog implementam oInterface ILiveBirth, mas a classe Bird não.Em
Main, o programa cria um array de objetos Animal e o preenche com um objeto de classe de cadados
três tipos de classes de animais. O programa então itera através da matriz e, usando o comooperador,
recupera referências à interface ILiveBirth de cada objeto que possui um e chama seuMétodo
BabyCalled.interface ILiveBirth// Declare interface{string BabyCalled ();}classe Animal {}// Classe Base
Animalclasse Cat: Animal, ILiveBirth// Declarar classe Cat{string ILiveBirth.BabyCalled (){return "gatinho";
}}classe Cachorro: Animal, ILiveBirth// Declare a classe Dog{string ILiveBirth.BabyCalled (){return
"filhote"; }}classe Bird: Animal// Declare a classe Bird{}Baixe a partir de Wow! e-book
<www.wowebook.com>

Página 463

CAPÍTULO 17 ■ INTERFACES433programa de aula{static void Main (){Animal [] animalArray = novo


Animal [3]; // Criar matriz AnimalanimalArray [0] = novo Cat ();// Inserir objeto de classe CatanimalArray
[1] = novo pássaro ();// Inserir objeto de classe BirdanimalArray [2] = novo Cachorro ();// Inserir objeto
de classe Dogforeach (Animal a in animalArray) // Percorrer a matriz{ILiveBirth b = a como ILiveBirth; //
se implementa ILiveBirth ...if (b! = null)Console.WriteLine ("O bebê é chamado: {0}", b.BabyCalled
());}}}Este código produz a seguinte saída:O bebê se chama: gatinhoO bebê se chama: cachorrinhoA
Figura 17-11 ilustra a matriz e os objetos na memória.Figura 17-11 . Diferentes tipos de objetos da classe
base Animal são intercalados na matriz.

Página 464

Página 465

CAPÍTULO 18■ ■ ■435Conversões■ O que são conversões?■ Conversões implícitas■ Conversões


explícitas e elenco■ Tipos de conversão■ Conversões numéricas■ Conversões de referência■
Conversões de boxe■ Conversões de unboxing■ Conversões definidas pelo usuário■ O é Operador■ O
como Operador

Página 466

CAPÍTULO 18 ■ CONVERSÕES436O que são conversões?Para entender o que são conversões, vamos
começar considerando o caso simples em que vocêdeclarar duas variáveis de tipos diferentes e, em
seguida, atribuir o valor de uma (a fonte ) à outra (aalvo ). Antes que a atribuição possa ocorrer, o valor
de origem deve ser convertido em um valor de destinotipo. A Figura 18-1 ilustra a conversão de tipo.•A
conversão é o processo de pegar um valor de um tipo e usá-lo como o valor equivalente deoutro tipo.•O
valor resultante da conversão deve ser igual ao valor de origem, mas notipo de alvo.Figura 18-1.
Conversão de tipoPor exemplo, o código na Figura 18-2 mostra a declaração de duas variáveis de tipos
diferentes.•var1 é do tipo short, um inteiro assinado de 16 bits que é inicializado em 5. var2 é do tipo
sbyte, um 8 bitsinteiro com sinal que é inicializado com o valor 10.•A terceira linha do código atribui o
valor de var1 a var2. Uma vez que são dois tipos diferentes, ovalor de var1 deve ser convertido para um
valor do mesmo tipo que var2 antes que a atribuição possa serrealizada. Isso é executado usando a
expressão cast, que você verá em breve.•Observe também que o valor e o tipo de var1 não foram
alterados. Embora seja chamado de conversão, estesignifica apenas que o valor da fonte é usado como o
tipo de destino - não que a fonte é alterada parao tipo de alvo.Figura 18-2 . Convertendo de um short
para um sbyte

Página 467

CAPÍTULO 18 ■ CONVERSÕES437Conversões implícitasPara certos tipos de conversões, não há


possibilidade de perda de dados ou precisão. Por exemplo, é fácilpara inserir um valor de 8 bits em um
tipo de 16 bits sem perda de dados.•O idioma fará essas conversões para você automaticamente. Estes
são chamados de implícitosconversões.•Ao converter de um tipo de origem com menos bits para um
tipo de destino com mais bits, os bits extrasno destino precisa ser preenchido com 0s ou 1s.•Ao
converter de um tipo não assinado menor para um tipo não assinado maior, o extra, maisbits
significativos do alvo são preenchidos com 0s. Isso é chamado de extensão zero.A Figura 18-3 mostra um
exemplo da extensão zero de um valor de 8 bits de 10 convertido para 16 bitsvalor de 10.Figura 18-3 .
Extensão zero em conversões não assinadasPara a conversão entre tipos assinados, os bits extras mais
significativos são preenchidos com o bit de sinal doexpressão de origem.•Isso mantém o sinal e a
magnitude corretos para o valor convertido.•Isso é chamado de extensão de sinal e é ilustrado na Figura
18-4, primeiro com 10 e depois com –10.Figura 18-4. Extensão de assinatura em conversões assinadas

Página 468

CAPÍTULO 18 ■ CONVERSÕES438Conversões explícitas e elencoAo converter de um tipo mais curto para


um tipo mais longo, é fácil para o tipo mais longo conter todos os bits deo tipo mais curto. Em outras
situações, no entanto, o tipo de destino pode não ser capaz de acomodar ovalor da fonte sem perda de
dados.Por exemplo, suponha que você deseja converter um valor ushort em um byte.•Um ushort pode
conter qualquer valor entre 0 e 65.535.•Um byte só pode conter um valor entre 0 e 255.•Contanto que
o valor ushort que você deseja converter seja inferior a 256, não haverá perda de dados. E seé maior,
entretanto, os bits mais significativos serão perdidos.Por exemplo, a Figura 18-5 mostra uma tentativa de
converter um ushort com um valor de 1.365 em um byte,resultando em perda de dados.Figura 18-5 .
Tentando converter um ushort em um byteClaramente, apenas um número relativamente pequeno (0,4
por cento) dos possíveis valores ushort de 16 bits sem sinalpode ser convertido com segurança em um
tipo de byte de 8 bits sem sinal sem perda de dados. O resto resulta em dadosestouro, produzindo
valores diferentes.

Página 469
CAPÍTULO 18 ■ CONVERSÕES439CastingPara os tipos predefinidos, C # converterá automaticamente de
um tipo de dados para outro - mas apenasentre aqueles tipos para os quais não há possibilidade de
perda de dados entre o tipo de origem e o destinotipo. Isto é, a linguagem não fornece conversão
automática entre dois tipos se houver qualquervalor do tipo de origem que perderia dados se fosse
convertido para o tipo de destino. Se você quiser fazer umconversão desse tipo, você deve usar uma
conversão explícita, chamada de expressão de conversão .O código a seguir mostra um exemplo de uma
expressão de conversão. Ele converte o valor de var1 em tiposbyte. Uma expressão de elenco consiste
no seguinte:•Um conjunto de parênteses correspondentes contendo o nome do tipo de destino•A
expressão de origem, após os parêntesesTipo de alvo↓(sbyte) var1;↑Expressão fonteAo usar uma
expressão de elenco, você assume explicitamente a responsabilidade de realizar a operaçãoque pode
perder dados. Essencialmente, você está dizendo: “Apesar da possibilidade de perda de dados, eu sei o
que estoufazendo, então faça esta conversão de qualquer maneira. ” (Certifique-se, porém, que você
não sabe o que está fazendo.)Por exemplo, a Figura 18-6 mostra expressões de conversão convertendo
dois valores do tipo ushort em tipo byte.No primeiro caso, não há perda de dados. No segundo caso, os
bits mais significativos são perdidos, dando umvalor de 85 - o que claramente não é equivalente ao valor
de origem, 1.365.Figura 18-6. Transmitindo um ushort para um byteA saída do código na figura é a
seguinte:sb: 10 = 0xAsb: 85 = 0x55

Página 470

CAPÍTULO 18 ■ CONVERSÕES440Tipos de ConversõesExistem várias conversões padrão predefinidas


para os tipos numérico e de referência. oas categorias são ilustradas na Figura 18-7.•Além das
conversões padrão, você também pode definir conversões implícitas e explícitas paraseus tipos definidos
pelo usuário.•Há também um tipo predefinido de conversão chamado boxing, que converte qualquer
tipo de valor emqualquer um destes:- Tipo de objeto- Digite System.ValueType•Unboxing converte um
valor em caixa de volta ao seu tipo original.Figura 18-7. Tipos de conversãoConversões
NuméricasQualquer tipo numérico pode ser convertido em qualquer outro tipo numérico, conforme
ilustrado na Figura 18-8. Algunsas conversões são conversões implícitas e outras devem ser
explícitas.Figura 18-8 . Conversões numéricas

Página 471

CAPÍTULO 18 ■ CONVERSÕES441Conversões Numéricas ImplícitasAs conversões numéricas implícitas


são mostradas na Figura 18-9.•Há uma conversão implícita do tipo de origem para o tipo de destino se
houver um caminho, seguindoas setas, do tipo de origem ao tipo de destino.•Qualquer conversão
numérica para a qual não haja um caminho seguindo as setas do tipo de origempara o tipo de destino
deve ser uma conversão explícita .A figura demonstra que, como você esperaria, há uma conversão
implícita entretipos que ocupam menos bits do que aqueles que ocupam mais bits.Figura 18-9. As
conversões numéricas implícitas

Página 472

CAPÍTULO 18 ■ CONVERSÕES442Contexto de verificação de estouroVocê viu que as conversões


explícitas têm a possibilidade de perder dados e não serem capazes de representaro valor de origem de
forma equivalente no tipo de destino. Para tipos integrais, C # oferece a capacidade deescolha se o
tempo de execução deve verificar o resultado de estouro ao fazer esses tipos deconversões. Isso é feito
por meio do operador verificado e da declaração verificada.•Se um segmento de código é verificado ou
não, é chamado de contexto de verificação de estouro .- Se você designar uma expressão ou segmento
de código como verificado, o CLR levantará umExceção OverflowException se a conversão produzir um
estouro.- Se o código não for verificado, a conversão continuará independentemente de haver
umtransbordar.•O contexto de verificação de estouro padrão não é verificado.Os operadores marcados e
não marcadosOs operadores marcados e não marcados controlam o contexto de verificação de estouro
de uma expressão, que écolocado entre um conjunto de parênteses. A expressão não pode ser um
método. A sintaxe é a seguinte:verificado ( Expressão )desmarcado ( Expressão )Por exemplo, o código a
seguir executa a mesma conversão - primeiro em um operador verificado e depoisem um operador não
verificado.•No contexto não verificado, o estouro é ignorado, resultando no valor 208.•No contexto
verificado, uma exceção OverflowException é gerada.ushort sh = 2000;byte sb;sb = não verificado ((byte)
sh);// bits mais significativos perdidosConsole.WriteLine ("sb: {0}", sb);sb = verificado ((byte) sh);//
OverflowException geradoConsole.WriteLine ("sb: {0}", sb);Baixe a partir de Wow! e-book
<www.wowebook.com>

Página 473

CAPÍTULO 18 ■ CONVERSÕES443Este código produz a seguinte saída:sb: 208Exceção não tratada:


System.OverflowException: a operação aritmética resultou em um estouro.em Test1.Test.Main () em C: \
Programs \ Test1 \ Program.cs: linha 21As declarações marcadas e não marcadasOs operadores
marcados e não marcados que você acabou de ver atuam na única expressão entre oparênteses. As
instruções marcadas e não verificadas executam a mesma função, mas controlam todas asconversões em
um bloco de código, em vez de em uma única expressão.As instruções marcadas e não marcadas podem
ser aninhadas em qualquer nível.Por exemplo, o código a seguir usa instruções marcadas e não
verificadas e produz o mesmoresultados como o exemplo anterior, que usa expressões marcadas e não
marcadas. Neste caso, entretanto,blocos de código são afetados, em vez de apenas expressões.byte
sb;ushort sh = 2000;não verificado// Set unchecked{sb = (byte) sh;Console.WriteLine ("sb: {0}",
sb);verificado// Definir verificado{sb = (byte) sh;Console.WriteLine ("sb: {0}", sh);}}

Página 474

CAPÍTULO 18 ■ CONVERSÕES444Conversões Numéricas ExplícitasVocê viu que as conversões implícitas


são convertidas automaticamente da expressão de origem para o destinodigite porque não há possível
perda de dados. Com as conversões explícitas, no entanto, há opossibilidade de perda de dados, por isso
é importante para você como programador saber como uma conversãolidar com essa perda se ela
ocorrer.Nesta seção, você examinará cada um dos vários tipos de conversões numéricas explícitas.
Figura18-10 mostra o subconjunto de conversões explícitas mostradas na Figura 18-8.Figura 18-10. As
conversões numéricas explícitasIntegral para IntegralA Figura 18-11 mostra o comportamento das
conversões explícitas de integral para integral. No caso verificado, sea conversão perde dados, a
operação gera uma exceção OverflowException. No caso não verificado,quaisquer bits perdidos não são
relatados.Figura 18-11. Conversões explícitas de tipo inteiro para tipo inteiro

Página 475

CAPÍTULO 18 ■ CONVERSÕES445flutuar ou dobrar para IntegralAo converter um tipo de ponto flutuante


em um tipo inteiro, o valor é arredondado para 0 para o mais próximointeiro. A Figura 18-12 ilustra as
condições de conversão. Se o valor arredondado não estiver dentro da faixa deo tipo de alvo, então•O
CLR gera uma exceção OverflowException se o contexto de verificação de estouro for verificado.•C # não
define qual deve ser seu valor se o contexto estiver desmarcado.Figura 18-12. Converter um flutuante ou
duplo em um tipo inteirodecimal para integralAo converter de decimal para tipo inteiro, o CLR levanta
uma exceção OverflowException se oo valor resultante não está dentro do intervalo do tipo de destino. A
Figura 18-13 ilustra as condições de conversão.Figura 18-13 . Converter um decimal em um tipo inteiro

Página 476

CAPÍTULO 18 ■ CONVERSÕES446dobre para flutuarOs valores do tipo float ocupam 32 bits e os valores
do tipo double ocupam 64 bits. Quando um duplo é arredondadopara um float, o valor do tipo double é
arredondado para o valor do tipo float mais próximo. A Figura 18-14 ilustra ocondições de conversão.•Se
o valor for muito pequeno para ser representado por um float, o valor é definido como positivo ou0
negativo.•Se o valor for muito grande para ser representado por um float, o valor é definido como
positivo ouinfinito negativo.Figura 18-14. Convertendo um duplo em um flutuadorflutuar ou dobrar para
decimalA Figura 18-15 mostra as condições de conversão para conversão de tipos de ponto flutuante em
decimal.•Se o valor for muito pequeno para ser representado pelo tipo decimal, o resultado será
definido como 0.•Se o valor for muito grande, o CLR gerará uma exceção OverflowException.

Página 477

CAPÍTULO 18 ■ CONVERSÕES447Figura 18-15. Convertendo um float ou double em um decimaldecimal


para flutuar ou dobrarAs conversões de decimal para os tipos de ponto flutuante sempre são bem-
sucedidas. Pode, no entanto, haver uma perda deprecisão. A Figura 18-16 mostra as condições de
conversão.Figura 18-16 . Converter um decimal em flutuante ou duplo

Página 478

CAPÍTULO 18 ■ CONVERSÕES448Conversões de ReferênciaComo você bem sabe, os objetos de tipo de


referência compreendem duas partes na memória: a referência eos dados.•Parte das informações
mantidas pela referência é o tipo de dados para os quais ela aponta .•Uma conversão de referência pega
uma referência de origem e retorna uma referência apontando para o mesmocoloque na pilha, mas
“rotule” a referência como um tipo diferente.Por exemplo, o código a seguir mostra duas variáveis de
referência, myVar1 e myVar2, que apontam para omesmo objeto na memória. O código é ilustrado na
Figura 18-17.•Para myVar1, o objeto ao qual faz referência se parece com um objeto do tipo B - o que
realmente é.•Para myVar2, o mesmo objeto se parece com um objeto do tipo A.- Mesmo que esteja
realmente apontando para um objeto do tipo B, ele não pode ver as partes de B queestender A e,
portanto, não pode ver o Field2.- A segunda instrução WriteLine, portanto, causaria um erro de
compilação.Observe que a “conversão” não altera minhaVar1.classe A {public int Field1; }classe B: A
{public int Field2; }programa de aula{static void Main (){B minhaVar1 = novo B ();Retorne a referência a
myVar1 como uma referência a uma classe A.↓A minhaVar2 = (A) minhaVar1;Console.WriteLine ("{0}",
myVar2.Field1);// BemConsole.WriteLine ("{0}", myVar2.Field2);// Erro de compilação!}↑}myVar2 não
pode ver o Field2.

Página 479

CAPÍTULO 18 ■ CONVERSÕES449Figura 18-17. Uma conversão de referência retorna um tipo diferente


associado ao objeto.Conversões de referência implícitaAssim como há conversões numéricas implícitas
que o idioma executará automaticamente para você,também há conversões de referência implícitas. Eles
são ilustrados na Figura 18-18.•Todos os tipos de referência têm uma conversão implícita em tipo de
objeto.•Qualquer interface pode ser convertida implicitamente em uma interface da qual é
derivada.•Uma classe pode ser convertida implicitamente para- Qualquer classe na cadeia da qual é
derivado- Qualquer interface que implementeFigura 18-18. Conversões implícitas para classes e
interfaces

Página 480

CAPÍTULO 18 ■ CONVERSÕES450Um delegado pode ser convertido implicitamente nas classes e


interfaces .NET BCL mostradas na Figura 18-19.Uma matriz, ArrayS , com elementos do tipo Ts , pode ser
convertida implicitamente para o seguinte:•A classe .NET BCL e as interfaces mostradas na Figura 18-
19.•Outra matriz, ArrayT , com elementos do tipo Tt , se todas as opções a seguir forem verdadeiras:-
Ambas as matrizes têm o mesmo número de dimensões.- Os tipos de elemento, Ts e Tt , são tipos de
referência - não tipos de valor.- Há uma conversão implícita entre os tipos Ts e Tt .Figura 18-19.
Conversões implícitas para delegados e matrizes

Página 481

CAPÍTULO 18 ■ CONVERSÕES451Conversões de referência explícitaAs conversões de referência


explícitas são conversões de referência de um tipo geral para um tipo mais especializado.•As conversões
explícitas incluem- Conversões de um objeto para qualquer tipo de referência- Conversões de uma classe
base para uma classe derivada dela•As conversões de referência explícitas são ilustradas invertendo
cada uma das setas nas Figuras 18-18e 18-19.Se esse tipo de conversão fosse permitido sem restrições,
você poderia facilmente tentar fazer referênciamembros de uma classe que não estão realmente na
memória. O compilador, porém, não permitir que esses tipos deconversões. Mas quando o sistema os
encontra em tempo de execução, ele gera uma exceção.Por exemplo, o código na Figura 18-20 converte
a referência da classe base A em sua classe derivada B eatribui à variável myVar2.•Se myVar2 tentasse
acessar o Field2, estaria tentando acessar um campo na “parte B”do objeto, que não existe - causando
uma falha de memória.•O tempo de execução irá capturar essa conversão inadequada e gerar uma
exceção InvalidCastException.Observe, entretanto, que isso não causa um erro de compilação.Figura 18-
20. Casts inválidos geram exceções de tempo de execução .

Página 482
CAPÍTULO 18 ■ CONVERSÕES452Conversões de referência explícita válidasExistem três situações em
que uma conversão de referência explícita terá sucesso em tempo de execução, ou seja, nãolevanta uma
exceção InvalidCastException.O primeiro caso é onde a conversão explícita é desnecessária, ou seja,
onde a linguagem seriaexecutou uma conversão implícita para você de qualquer maneira. Por exemplo,
no código a seguir, oa conversão explícita é desnecessária porque sempre há uma conversão implícita de
uma classe derivada parauma de suas classes básicas.classe A { }classe B: A {}...B minhaVar1 = novo B ();A
minhaVar2 = (A) minhaVar1; // Cast é desnecessário; A é a classe base de B.O segundo caso é onde a
referência de origem é nula. Por exemplo, no código a seguir, mesmoembora normalmente não seja
seguro converter uma referência de uma classe base para aquela de uma classe derivada, oa conversão é
permitida porque o valor da referência de origem é nulo.classe A { }classe B: A {}...A myVar1 = null;B
minhaVar2 = (B) minhaVar1; // Permitido porque myVar1 é nulaBaixe a partir de Wow! e-book
<www.wowebook.com>

Página 483

CAPÍTULO 18 ■ CONVERSÕES453O terceiro caso é quando os dados reais apontados pela referência de
origem podem ser convertidos com segurançaimplicitamente. O código a seguir mostra um exemplo e a
Figura 18-21 ilustra o código.•A conversão implícita na segunda linha faz myVar2 "pensar" que está
apontando para dados do tipoA, enquanto na verdade está apontando para um objeto de dados do tipo
B.•A conversão explícita na terceira linha é converter uma referência de uma classe base para uma
referência de umade suas classes derivadas. Normalmente, isso geraria uma exceção. Neste caso, no
entanto, o objetosendo apontado, na verdade, é um item de dados do tipo B.B minhaVar1 = novo B ();A
minhaVar2 = minhaVar1; // Lance implicitamente myVar1 para o tipo A.B minhaVar3 = (B) minhaVar2; //
Este elenco é bom porque os dados são do tipo B.Figura 18-21. Transmitindo para um tipo seguro

Página 484

CAPÍTULO 18 ■ CONVERSÕES454Conversões de boxeTodos os tipos C #, incluindo os tipos de valor, são


derivados do objeto de tipo. Tipos de valor, no entanto, são eficientes,tipos leves que não incluem, por
padrão, seu componente de objeto no heap. Quando o objetocomponente é necessário, no entanto,
você pode usar boxing , que é uma conversão implícita que assume um valortype value, cria a partir dele
um objeto de tipo de referência completa no heap e retorna uma referência ao objeto.Por exemplo, a
Figura 18-22 mostra três linhas de código.•As primeiras duas linhas de código declaram e inicializam a
variável de tipo de valor i e o tipo de referênciavariável oi.•Na terceira linha de código, você deseja
atribuir o valor da variável i a oi. Mas oi é um tipo de referênciavariável e deve ser atribuída uma
referência a um objeto no heap. A variável i, no entanto, é um valortipo e não tem uma referência a um
objeto no heap.•O sistema, portanto, encaixota o valor de i fazendo o seguinte:- Criação de um objeto
do tipo int no heap- Copiando o valor de i para o objeto int- Retornar a referência do objeto int para oi
para armazenar como sua referênciaFigura 18-22. O boxing cria um objeto de tipo de referência
completa a partir de um tipo de valor.

Página 485
CAPÍTULO 18 ■ CONVERSÕES455O boxing cria uma cópiaUm mal-entendido comum sobre o boxe é que
ele age de alguma forma sobre o item que está sendo encaixotado. istonão. Ele retorna uma cópia do
tipo de referência do valor. Após o procedimento de boxe, existem doiscópias do valor - o original do tipo
de valor e a cópia do tipo de referência - cada um dos quais pode sermanipulado separadamente.Por
exemplo, o código a seguir mostra a manipulação separada de cada cópia do valor. Figura18-23 ilustra o
código.•A primeira linha define a variável de tipo de valor i e inicializa seu valor para 10.•A segunda linha
cria a variável de tipo de referência oi e a inicializa com a cópia em caixa devariável i.•As últimas três
linhas de código mostram i e oi sendo manipulados separadamente.int i = 10;// Crie e inicialize o tipo de
valorCaixa i e atribuir sua referência a oi.↓objeto oi = i;// Cria e inicializa o tipo de
referênciaConsole.WriteLine ("i: {0}, io: {1}", i, oi);i = 12;oi = 15;Console.WriteLine ("i: {0}, io: {1}", i,
oi);Este código produz a seguinte saída:i: 10, io: 10i: 12, io: 15Figura 18-23. O boxing cria uma cópia que
pode ser manipulada separadamente.

Página 486

CAPÍTULO 18 ■ CONVERSÕES456As conversões de boxeA Figura 18-24 mostra as conversões de boxe.


Qualquer tipo de valor ValueTypeS pode ser convertido implicitamente emqualquer um dos tipos de
objeto, System.ValueType ou InterfaceT , se ValueTypeS implementar InterfaceT .Figura 18-24 . O boxing
é a conversão implícita de tipos de valor em tipos de referência .Conversões de UnboxingUnboxing é o
processo de converter um objeto em uma caixa de volta ao seu tipo de valor.•Unboxing é uma conversão
explícita.•O sistema executa as seguintes etapas ao desempacotar um valor para ValueTypeT :- Verifica
se o objeto que está sendo desempacotado é na verdade um valor em caixa do tipo ValueTypeT .- Copia
o valor do objeto para a variável.Por exemplo, o código a seguir mostra um exemplo de
desempacotamento de um valor.•A variável de tipo de valor i é encaixotada e atribuída à variável de tipo
de referência oi.•A variável oi é então desempacotada e seu valor é atribuído à variável de tipo de valor
j.static void Main (){int i = 10;Caixa i e atribuir sua referência a oi.↓objeto oi = i;Retire oi da caixa e atribua
seu valor a j.↓int j = (int) oi;Console.WriteLine ("i: {0}, oi: {1}, j: {2}", i, oi, j);}

Página 487

CAPÍTULO 18 ■ CONVERSÕES457Este código produz a seguinte saída:i: 10, oi: 10, j: 10A tentativa de
desempacotar um valor para um tipo diferente do tipo original levanta uma
InvalidCastExceptionexceção.As conversões de unboxingA Figura 18-25 mostra as conversões de
unboxing.Figura 18-25 . As conversões de unboxing

Página 488

CAPÍTULO 18 ■ CONVERSÕES458Conversões definidas pelo usuárioAlém das conversões padrão, você


também pode definir conversões implícitas e explícitas para o seupróprias classes e estruturas.A sintaxe
para conversões definidas pelo usuário é mostrada a seguir.•A sintaxe é a mesma para as declarações de
conversão implícita e explícita, exceto para opalavras-chave implícitas e explícitas.•Os modificadores
public e static são obrigatórios.RequeridosPalavra-chave do operadorFonte↓↓↓↓operador implícito
estático público TargetType ( SourceType Identifier ){↑Implícito ou explícito...return
ObjectOfTargetType ;}Por exemplo, o seguinte mostra um exemplo da sintaxe de um método de
conversão que converte umobjeto do tipo Person para um int:operador implícito estático público int
(Pessoa p){return p.Age;}Restrições em conversões definidas pelo usuárioExistem algumas restrições
importantes nas conversões definidas pelo usuário. Os mais importantes são osSegue:•Você só pode
definir conversões definidas pelo usuário para classes e estruturas.•Você não pode redefinir conversões
implícitas ou explícitas padrão.•O seguinte é verdadeiro para o tipo de origem S e o tipo de destino T:- S
e T devem ser de tipos diferentes.- S e T não podem ser relacionados por herança. Ou seja, S não pode
ser derivado de T , e T não pode serderivado de S .- Nem S nem T podem ser um tipo de interface ou o
objeto de tipo.- O operador de conversão deve ser um membro de qualquer das S ou T .•Você não pode
declarar duas conversões, uma implícita e outra explícita, com a mesma fontee tipos de alvo.

Página 489

CAPÍTULO 18 ■ CONVERSÕES459Exemplo de uma conversão definida pelo usuárioO código a seguir


define uma classe chamada Person que contém o nome e a idade de uma pessoa. A classe
tambémdefine duas conversões implícitas. O primeiro converte um objeto Person em um valor int. O
valor int alvoé a idade da pessoa. O segundo converte um int em um objeto Person.classe Person{public
string Name;public int Age;pessoa pública (nome da string, idade interna){Nome = nome;Idade =
idade;}operador implícito estático público int (Pessoa p) // Converter Pessoa em int.{return
p.Age;}operador implícito estático público Person (int i) // Converte int em Person.{retornar uma nova
pessoa ("Nemo", i);}}programa de aula{static void Main (){Conta de pessoa = nova pessoa ("conta",
25);Converta um objeto Person em um int.↓idade int = fatura;Console.WriteLine ("Informações da
pessoa: {0}, {1}", fatura.Nome, idade);Converta um int em um objeto Person.↓Pessoa anônima =
35;Console.WriteLine ("Informações da pessoa: {0}, {1}", anon.Name, anon.Age);}}

Página 490

CAPÍTULO 18 ■ CONVERSÕES460Este código produz a seguinte saída:Informações da pessoa: conta,


25Informações da pessoa: Nemo, 35Se você tivesse definido os mesmos operadores de conversão como
explícitos em vez de implícitos, entãoprecisaram usar expressões cast para realizar as conversões,
conforme mostrado aqui:Explícito...↓operador explícito estático público int (Pessoa p){return
p.Age;}...static void Main (){... Requer expressão de elenco↓idade int = fatura (int);...

Página 491

CAPÍTULO 18 ■ CONVERSÕES461Avaliação de conversões definidas pelo usuárioAs conversões definidas


pelo usuário discutidas até agora converteram diretamente o tipo de fonte em um objeto dotipo de
destino em uma única etapa, conforme mostrado na Figura 18-26.Figura 18-26. Conversão definida pelo
usuário em uma única etapaMas as conversões definidas pelo usuário podem ter até três etapas na
conversão completa. Figura 18-27ilustra essas etapas, que incluem o seguinte:•A conversão padrão
preliminar•A conversão definida pelo usuário•A seguinte conversão padrãoNão é não mais do que uma
única conversão definida pelo utilizador na cadeia.Figura 18-27. Conversão definida pelo usuário em
várias etapasExemplo de uma conversão em várias etapas definida pelo usuárioO código a seguir declara
a classe Employee, que é derivada da classe Person.•Várias seções atrás, o exemplo de código declarou
uma conversão definida pelo usuário da classe Person paraint. Portanto, se houver uma conversão
padrão de Funcionário para Pessoa e outra de Int para Flutuante, vocêpode ser convertido de
Funcionário em flutuante.- Há uma conversão padrão de Funcionário para Pessoa, uma vez que
Funcionário é derivado dePessoa.- Há uma conversão padrão de int para float, uma vez que é um
numérico implícitoconversão.•Como todas as três partes da cadeia existem, você pode converter de
Funcionário em flutuante. Figura 18-28ilustra como o compilador realiza a conversão.

Página 492

CAPÍTULO 18 ■ CONVERSÕES462classe Funcionário: Pessoa {}classe Person{public string Name;public


int Age;// Converta um objeto Person em um int.operador implícito estático público int (Pessoa p){return
p.Age;}}programa de aula{static void Main (){Conta do funcionário = novo funcionário ();bill.Name =
"William";bill.Age = 25;Converta um funcionário em um flutuador.↓float fVar = bill;Console.WriteLine
("Informações da pessoa: {0}, {1}", bill.Name, fVar);}}Este código produz a seguinte saída:Informações da
pessoa: William, 25Figura 18-28. Conversão de empregado para flutuarBaixe a partir de Wow! e-book
<www.wowebook.com>

Página 493

CAPÍTULO 18 ■ CONVERSÕES463O é OperadorConforme mostrado anteriormente, algumas tentativas


de conversão não são bem-sucedidas e geram umExceção InvalidCastException em tempo de execução.
Em vez de tentar uma conversão cegamente, você pode usar oé o operador para verificar se uma
conversão seria concluída com êxito.A sintaxe do operador is é a seguinte, onde Expr é a expressão de
origem:Retorna um bool↓Expr é TargetTypeO operador retorna verdadeiro se Expr pode ser convertido
com sucesso para o tipo de destino através de qualquer um dosOs seguintes:•Uma conversão de
referência•Uma conversão de boxe•Uma conversão unboxingPor exemplo, no código a seguir, você usa o
operador is para verificar se nota variável do tipoFuncionário pode ser convertido para o tipo Pessoa e,
em seguida, você executa a ação apropriada.classe Funcionário: Pessoa {}classe Person{public string
Name = "Anonymous";público int Idade = 25;}programa de aula{static void Main (){Conta do funcionário
= novo funcionário ();Pessoa p;// Verifique se a conta variável pode ser convertida para o tipo Pessoaif (a
conta é uma pessoa){p = fatura;Console.WriteLine ("Informações da pessoa: {0}, {1}", p.Nome,
p.Age);}}}O operador is pode ser usado apenas para conversões de referência e conversões de boxing e
unboxing.Não pode ser usado para conversões definidas pelo usuário.

Página 494

CAPÍTULO 18 ■ CONVERSÕES464O como OperadorO operador as é como o operador de conversão,


exceto que ele não levanta uma exceção. Se a conversão falhar,em vez de levantar uma exceção, ele
retorna nulo.A sintaxe do operador as é a seguinte, onde•Expr é a expressão de origem.•TargetType é o
tipo de destino, que deve ser um tipo de referência.Retorna uma referência↓Expr como TargetTypeComo
o operador as retorna uma expressão de referência, ele pode ser usado como a fonte para uma
atribuição.Por exemplo, fatura variável do tipo Funcionário é convertida para o tipo Pessoa, usando o
operador as, eatribuído à variável p do tipo Person. Em seguida, você verifica se p é nulo antes de usá-
lo.classe Funcionário: Pessoa {}classe Person{public string Name = "Anonymous";público int Idade =
25;}programa de aula{static void Main (){Conta do funcionário = novo funcionário ();Pessoa p;p = faturar
como pessoa;if (p! = null){Console.WriteLine ("Informações da pessoa: {0}, {1}", p.Nome, p.Age);}}}Como
o operador is, o operador as pode ser usado apenas para conversões de referência e boxeconversões.
Não pode ser usado para conversões definidas pelo usuário ou conversões para um tipo de valor.

Página 495

CAPÍTULO 19■ ■ ■465Genéricos■ O que são genéricos?■ Genéricos em C #■ Classes genéricas■


Declarando uma classe genérica■ Criação de um tipo construído■ Criação de variáveis e instâncias■
Restrições nos parâmetros de tipo■ Métodos Genéricos■ Métodos de extensão com classes
genéricas■ Estruturas genéricas■ Delegados genéricos■ Interfaces genéricas■ Covariância e
Contravariância em genéricos

Página 496

CAPÍTULO 19 ■ GENÉRICOS466O que são genéricos?Com as construções de linguagem que você


aprendeu até agora, você pode construir objetos poderosos de muitos diferentestipos. Você faz isso
principalmente declarando classes que encapsulam o comportamento que você deseja e, em seguida,
criandoinstâncias dessas classes.Todos os tipos usados nas declarações de classe até agora foram tipos
específicos - seja um programadordefinido ou fornecido pela linguagem ou pelo BCL. Há momentos, no
entanto, em que uma aula seria maisútil se você pudesse "destilar" ou "refatorar" suas ações e aplicá-las
não apenas aos tipos de dados paraque são codificados, mas para outros tipos também.Os genéricos
permitem que você faça exatamente isso. Você pode refatorar seu código e adicionar uma camada
adicional deabstração para que, para certos tipos de código, os tipos de dados não sejam codificados
permanentemente. Isto é particularmenteprojetado para casos em que há várias seções de código
executando as mesmas instruções, mas emdiferentes tipos de dados.Isso pode parecer muito abstrato,
então começaremos com um exemplo que deve deixar as coisas mais claras.Um exemplo de
pilhaSuponha primeiro que você tenha criado o seguinte código, que declara uma classe chamada
MyIntStack, queimplementa uma pilha de ints. Ele permite que você coloque os ints na pilha e os tire.
Este, por falar nisso,não é a pilha do sistema.classe MyIntStack// Empilhar para ints{StackPointer int =
0;int [] StackArray;// Array de int↑intint↓public void Push (int x)// Tipo de entrada: int{...}int↓public int
Pop ()// Tipo de retorno: int{...}...}

Página 497

CAPÍTULO 19 ■ GENÉRICOS467Suponha agora que você gostaria da mesma funcionalidade para valores
do tipo float. Existem váriosmaneiras de conseguir isso. Uma maneira é realizar as seguintes etapas para
produzir o código subsequente:•Recorte e cole o código da classe MyIntStack.•Altere o nome da classe
para MyFloatStack.•Altere as declarações int apropriadas para declarações flutuantes em toda a
declaração da classe.classe MyFloatStack// Empilhar para flutuadores{StackPointer int = 0;float []
StackArray;// Array of float↑flutuadorflutuador↓public void Push (float x)// Tipo de entrada:
float{...}flutuador↓flutuante público Pop ()// Tipo de retorno: float{...}...}Esse método certamente
funciona, mas é sujeito a erros e tem as seguintes desvantagens:•Você precisa inspecionar cada parte da
classe cuidadosamente para determinar quais declarações de tipo precisamser alterado e que deve ser
deixado sozinho.•Você precisa repetir o processo para cada novo tipo de classe de pilha que você precisa
(long, double, string,e assim por diante).•Após o processo, você acaba com várias cópias de código
quase idêntico, ocupandoespaço adicional.•Depurar e manter as implementações paralelas é
deselegante e sujeito a erros.7

Página 498

CAPÍTULO 19 ■ GENÉRICOS468Genéricos em C #Com o C # 2.0, a Microsoft introduziu os recursos


genéricos , que oferecem maneiras mais elegantes de usar um conjunto decódigo com mais de um tipo.
Os genéricos permitem que você declare o código parametrizado por tipo , que você podeinstanciar com
diferentes tipos. Isso significa que você pode escrever o código com "marcadores de posição para tipos"
eem seguida, forneça os tipos reais ao criar uma instância da classe.Neste ponto do texto, você deve
estar bem familiarizado com o conceito de que um tipo não é um objeto, masum modelo para um
objeto. Da mesma forma, um tipo genérico não é um tipo, mas um modelo para um tipo. Figura19-1
ilustra esse ponto.Figura 19-1. Tipos genéricos são modelos para tipos.C # fornece cinco tipos de
genéricos: classes, estruturas, interfaces, delegados e métodos. Notar queos primeiros quatro são tipos e
métodos são membros.A Figura 19-2 mostra como os tipos genéricos se encaixam nos outros tipos
cobertos.Figura 19-2. Tipos genéricos e definidos pelo usuário

Página 499

CAPÍTULO 19 ■ GENÉRICOS469Continuando com o exemplo de pilhaNo exemplo da pilha, com as


classes MyIntStack e MyFloatStack, os corpos das declarações doas classes são idênticas, exceto nas
posições que lidam com o tipo de valor mantido pela pilha.•Em MyIntStack, essas posições são ocupadas
pelo tipo int.•Em MyFloatStack, eles são ocupados por float.Você pode criar uma classe genérica de
MyIntStack fazendo o seguinte:•Pegue a declaração de classe MyIntStack e, em vez de substituir float
por int, substitua odigite o placeholder T.•Altere o nome da classe para MyStack.•Coloque a string <T>
após o nome da classe.O resultado é a seguinte declaração de classe genérica. A corda consiste nos
colchetes angulares como T significa que T é um espaço reservado para um tipo. (Não precisa ser a letra
T - pode ser qualquer identificador.)Em todos os lugares ao longo do corpo da declaração de classe onde
T está localizado, um tipo real precisaráser substituído pelo compilador.classe MyStack <T>{StackPointer
int = 0;T [] StackArray;↑↓public void Push (T x) {...}↓público T Pop () {...}...}

Página 500

CAPÍTULO 19 ■ GENÉRICOS470Classes GenéricasAgora que você viu uma classe genérica, vamos
examinar as classes genéricas em mais detalhes e ver como elascriado e usado.Como você sabe, existem
duas etapas para criar e usar suas próprias classes regulares não genéricas:declarar a classe e criar
instâncias da classe. Mas classes genéricas não são classes reais, masmodelos para classes - então você
deve primeiro construir tipos de classes reais a partir deles. Você pode então criarreferências e instâncias
desses tipos de classe construídos.A Figura 19-3 ilustra o processo em um alto nível. Se ainda não está
totalmente claro, não se preocupe -vamos cobrir cada parte nas seções a seguir.1. Declare uma classe,
usando espaços reservados para alguns dos tipos.2. Forneça tipos reais para substituir os espaços
reservados. Isso dá a você uma aula realdefinição, com todos os “espaços em branco” preenchidos.3.
Crie instâncias a partir da definição de classe “preenchida”.Figura 19-3. Criação de instâncias de um tipo
genérico

Página 501

CAPÍTULO 19 ■ GENÉRICOS471Declarando uma classe genéricaDeclarar uma classe genérica simples é


muito parecido com declarar uma classe regular, com as seguintes diferenças:•Coloque um conjunto
correspondente de colchetes angulares após o nome da classe.•Entre os colchetes angulares, coloque
uma lista separada por vírgulas das strings de espaço reservado querepresentam os tipos, a serem
fornecidos sob demanda. Eles são chamados de parâmetros de tipo .•Use os parâmetros de tipo em
todo o corpo da declaração da classe genérica para representaros tipos que devem ser substituídos
em.Por exemplo, o código a seguir declara uma classe genérica chamada SomeClass. Os parâmetros de
tipo sãolistados entre os colchetes angulares e, em seguida, usados em todo o corpo da declaração como
se fossemtipos reais.Parâmetros de tipo↓classe SomeClass <T1, T2>{ Normalmente, os tipos seriam
usados nessas posições.↓↓T1 público SomeVar = T1 novo ();T2 OtherVar público = T2 novo
();}↑↑Normalmente, os tipos seriam usados nessas posições.Não existe uma palavra-chave especial que
sinalize uma declaração de classe genérica. Em vez disso, a presença do tipolista de parâmetros,
demarcada com colchetes angulares, distingue uma declaração de classe genérica de uma
regulardeclaração de classe.

Página 502

CAPÍTULO 19 ■ GENÉRICOS472Criando um tipo construídoVocê não pode criar objetos de classe


diretamente de uma classe genérica. Primeiro, você precisa dizer ao compilador o queos tipos reais
devem ser substituídos pelos espaços reservados (os parâmetros de tipo). O compilador pega
aquelestipos reais e cria um modelo a partir do qual cria objetos de classe reais.Para construir um tipo
de classe a partir de uma classe genérica, liste o nome da classe e forneça tipos reais entre oscolchetes
angulares, no lugar dos parâmetros de tipo. Os tipos reais sendo substituídos pelos parâmetros de
tiposão chamados de argumentos de tipo.Argumentos de tipo↓SomeClass <short, int>O compilador pega
os argumentos de tipo e os substitui por seus tipos correspondentesparâmetros em todo o corpo da
classe genérica, produzindo o tipo construído - a partir do qualinstâncias de classe são criadas.A Figura
19-4 mostra a declaração da classe genérica SomeClass à esquerda. À direita, mostra oclasse construída
criada usando os argumentos de tipo short e int.Figura 19-4. Fornecer argumentos de tipo para todos os
parâmetros de tipo de uma classe genérica produz umclasse a partir da qual os objetos de classe reais
podem ser criados.A Figura 19-5 ilustra a diferença entre parâmetros de tipo e argumentos de tipo.•As
declarações de classes genéricas têm parâmetros de tipo, que atuam como marcadores de posição para
os tipos.•Os argumentos de tipo são os tipos reais que você fornece ao criar um tipo construído.Figura
19-5. Parâmetros de tipo versus argumentos de tipoBaixe a partir de Wow! e-book
<www.wowebook.com>
Página 503

CAPÍTULO 19 ■ GENÉRICOS473Criação de variáveis e instânciasUm tipo de classe construída é usado


apenas como um tipo regular na criação de referências e instâncias. Por exemplo,o código a seguir
mostra a criação de dois objetos de classe.•A primeira linha mostra a criação de um objeto a partir de
uma classe regular não genérica. Este é um formulário quevocê deve estar completamente familiarizado
agora.•A segunda linha de código mostra a criação de um objeto da classe genérica SomeClass,
instanciadocom os tipos short e int. A forma é exatamente análoga à linha acima dela, com oformulários
de classe no lugar de um nome de classe regular.•A terceira linha é a mesma semanticamente que a
segunda linha, mas em vez de listar asdigite em ambos os lados do sinal de igual, ele usa a palavra-chave
var para fazer o compilador usar tipoinferência.MyNonGenClassmyNGC = new MyNonGenClass();Classe
construídaClasse construída↓↓SomeClass <short, int> mySc1 = new SomeClass <short int> ();varmySc2 =
new SomeClass <short, int> ();Tal como acontece com as classes não genéricas, a referência e a instância
podem ser criadas separadamente, conforme mostrado emFigura 19-6. A figura também mostra que o
que está acontecendo na memória é o mesmo que para uma classe não genérica.•A primeira linha
abaixo da declaração de classe genérica aloca uma referência na pilha para a variávelmyInst. Seu valor é
nulo.•A segunda linha aloca uma instância no heap e atribui sua referência à variável.Figura 19-6.
Usando um tipo construído para criar uma referência e uma instânciaMuitos tipos de classes diferentes
podem ser construídos a partir da mesma classe genérica. Cada um é um separadotipo de classe, como
se tivesse sua própria declaração de classe não genérica separada.

Página 504

CAPÍTULO 19 ■ GENÉRICOS474Por exemplo, o código a seguir mostra a criação de dois tipos da classe
genérica SomeClass. oo código é ilustrado na Figura 19-7.•Um tipo é construído com os tipos short e
int.•O outro é construído com os tipos int e long.classe SomeClass <T1, T2>// Classe
genérica{...}programa de aula{static void Main (){var primeiro = new SomeClass <short, int> (); // Tipo
construídovar second = new SomeClass <int, long> (); // Tipo construído...Figura 19-7 . Duas classes
construídas criadas a partir de uma classe genérica

Página 505

CAPÍTULO 19 ■ GENÉRICOS475O exemplo de pilha usando genéricosO código a seguir mostra o exemplo
de pilha implementado usando genéricos. Método Main define doisvariáveis: stackInt e stackString. Os
dois tipos construídos são criados usando int e string como oargumentos de tipo.classe MyStack <T>{T []
StackArray;StackPointer int = 0;public void Push (T x){if (! IsStackFull)StackArray [StackPointer ++] =
x;}público T Pop (){return (! IsStackEmpty)? StackArray [- StackPointer]: StackArray [0];}const int
MaxStack = 10;bool IsStackFull {get {return StackPointer> = MaxStack; }}bool IsStackEmpty {get {return
StackPointer <= 0; }}public MyStack (){StackArray = novo T [MaxStack];}public void Print (){para (int i =
StackPointer -1; i> = 0; i--)Console.WriteLine ("Valor: {0}", StackArray [i]);}}

Página 506

CAPÍTULO 19 ■ GENÉRICOS476programa de aula{static void Main (){var stackInt = new MyStack <int>
();var stackString = new MyStack <string> ();stackInt.Push (3);stackInt.Push (5);stackInt.Push
(7);stackInt.Print ();stackString.Push ("Genéricos são ótimos!");stackString.Push ("Olá!");stackString.Print
();}}Este código produz a seguinte saída:Valor: 7Valor: 5Valor: 3Valor: Olá!Valor: os genéricos são ótimos!

Página 507

CAPÍTULO 19 ■ GENÉRICOS477Comparando a pilha genérica e não genéricaA Tabela 19-1 resume


algumas das diferenças entre a versão não genérica inicial da pilha ea versão genérica final da pilha. A
Figura 19-8 ilustra algumas dessas diferenças.Tabela 19-1 . Diferenças entre as pilhas não genéricas e
genéricasNão genéricoGenéricoTamanho do código fonteMaior: você precisa de um novoimplementação
para cada tipo.Menor: você só precisa de umimplementação, independentemente donúmero de tipos
construídos.Tamanho ExecutávelA versão compilada de cadapilha está presente, independentemente
dese é usado.Apenas tipos para os quais existe umtipos construídos estão presentes
noexecutável.Facilidade de escritaMais fácil de escrever porque é maisconcreto.Mais difícil de escrever
porque é maisresumo.Dificuldade de manterMais sujeito a erros de manutenção,já que todas as
mudanças precisam seraplicado para cada tipo aplicável.Mais fácil de manter, porquemodificações são
necessárias apenas emum lugar.Figura 19-8 . Pilha não genérica versus pilha genérica

Página 508

CAPÍTULO 19 ■ GENÉRICOS478Restrições nos parâmetros de tipoNo exemplo genérico da pilha, a pilha


não fez nada com os itens que continha, exceto armazenareles e estourá-los. Ele não tentou adicioná-los,
compará-los ou fazer qualquer outra coisa que exigiriausando operações dos próprios itens. Há um bom
motivo para isso. Uma vez que a pilha genérica nãosabe o tipo de itens que irá armazenar, ele não pode
saber quais membros esses tipos implementam.Todos os objetos C #, no entanto, são basicamente
derivados de objeto de classe, portanto, a única coisa que a pilha pode sercerteza sobre os itens que está
armazenando é que eles implementam os membros do objeto de classe. Esses incluemmétodos
ToString, Equals e GetType. Fora isso, ele não pode saber quais membros estão disponíveis.Contanto que
seu código não acesse os objetos dos tipos que ele manipula (ou contanto que ele adira aomembros do
objeto de tipo), sua classe genérica pode lidar com qualquer tipo. Digite os parâmetros que atendem a
issorestrição são chamados de parâmetros de tipo ilimitado . Se, no entanto, seu código tentar usar
qualquer outro membro,o compilador produzirá uma mensagem de erro.Por exemplo, o código a seguir
declara uma classe chamada Simple com um método chamado LessThan queleva duas variáveis do
mesmo tipo genérico. LessThan tenta retornar o resultado do uso do menor queoperador. Mas nem
todas as classes implementam o operador menor que, portanto, você não pode simplesmente substituir
T. qualquer classe.O compilador, portanto, produz uma mensagem de erro.classe Simple <T>{static public
bool LessThan (T i1, T i2){retornar i1 <i2;// Error}...}Para tornar os genéricos mais úteis, portanto, você
precisa ser capaz de fornecer informações adicionais parao compilador sobre quais tipos de tipos são
aceitáveis como argumentos. Esses pedaços adicionais deas informações são chamadas de restrições .
Apenas os tipos que atendem às restrições podem ser substituídos pelo dadoparâmetro de tipo.

Página 509
CAPÍTULO 19 ■ GENÉRICOS479Onde cláusulasAs restrições são listadas como cláusulas where.•Cada
parâmetro de tipo que possui restrições possui sua própria cláusula where.•Se um parâmetro tiver várias
restrições, elas serão listadas na cláusula where, separadas por vírgulas.A sintaxe de uma cláusula where
é a seguinte:Parâmetro de tipoLista de restrições↓↓onde TypeParam : constraint ,
constraint , ...↑↑Palavra-chaveCólonOs pontos importantes sobre onde as cláusulas são as
seguintes:•Eles são listados após o colchete angular de fechamento da lista de parâmetros de tipo.•Eles
não são separados por vírgulas ou qualquer outro token.•Eles podem ser listados em qualquer
ordem.•O token é uma palavra-chave contextual, para que você possa usá-lo em outros contextos.Por
exemplo, a seguinte classe genérica tem três parâmetros de tipo. T1 é ilimitado. Para T2, apenasclasses
do tipo Cliente ou classes derivadas de Cliente podem ser usadas como argumentos de tipo. Para T3,
apenasclasses que implementam a interface IComparable podem ser usadas como argumentos de
tipo.Sem limites com restrições↓ ↓Sem separadoresclasse MyClass <T1, T2, T3>↓onde T2: Cliente//
Restrição para T2onde T3: IComparável// Restrição para T3{↑...Sem separadores}

Página 510

CAPÍTULO 19 ■ GENÉRICOS480Tipos de restrição e ordemExistem cinco tipos de restrições. Eles estão


listados na Tabela 19-2.Tabela 19-2. Tipos de restriçõesDescrição do tipo de restriçãoNome da
classeApenas classes deste tipo, ou classes derivadas dele, podem ser usadas como o
tipoargumento.classeQualquer tipo de referência, incluindo classes, arrays, delegados e interfaces, pode
ser usadocomo o argumento de tipo.estruturaQualquer tipo de valor pode ser usado como o argumento
de tipo.InterfaceNameApenas esta interface, ou tipos que implementam esta interface, podem ser
usados como otipo de argumento.Novo()Qualquer tipo com um construtor público sem parâmetros
pode ser usado como o tipoargumento. Isso é chamado de restrição do construtor .As cláusulas where
podem ser listadas em qualquer ordem. As restrições em uma cláusula where, no entanto, devem
sercolocados em uma ordem específica, conforme mostrado na Figura 19-9.•Pode haver no máximo uma
restrição primária e, se houver, ela deve ser listada primeiro.•Pode haver qualquer número de restrições
InterfaceName .•Se a restrição do construtor estiver presente, ela deve ser listada por último.Figura 19-
9. Se um parâmetro de tipo tiver várias restrições, eles devem estar nesta ordem.As seguintes
declarações mostram exemplos de cláusulas where:classe SortedList <S>onde S: IComparable <S>
{...}classe LinkedList <M, N>onde M: IComparable <M>onde N: IClonável {...}class MyDictionary
<KeyType, ValueType>onde KeyType: IEnumerable,Novo(){...}

Página 511

CAPÍTULO 19 ■ GENÉRICOS481Métodos GenéricosAo contrário dos outros genéricos, um método não é


um tipo, mas um membro. Você pode declarar métodos genéricos emclasses genéricas e não genéricas e
em estruturas e interfaces, conforme mostrado na Figura 19-10.Figura 19-10 . Os métodos genéricos
podem ser declarados em tipos genéricos e não genéricos.

Página 512

CAPÍTULO 19 ■ GENÉRICOS482Declarando um método genéricoOs métodos genéricos possuem uma


lista de parâmetros de tipo e restrições opcionais.•Os métodos genéricos têm duas listas de
parâmetros:- A lista de parâmetros do método , entre parênteses- A lista de parâmetros de tipo , entre
colchetes angulares•Para declarar um método genérico, faça o seguinte:- Coloque a lista de parâmetros
de tipo imediatamente após o nome do método e antes do métodolista de parâmetros.- Coloque
quaisquer cláusulas de restrição após a lista de parâmetros do método.Lista de parâmetros de
tipoCláusulas de restrição↓↓public void PrintData <S, T> (S p, T t) onde S: Pessoa{↑...Lista de parâmetros
do método}Nota Lembre-se de que a lista de parâmetros de tipo vai depois do nome do método e antes
do parâmetro do métodoLista.Baixe a partir de Wow! e-book <www.wowebook.com>

Página 513

CAPÍTULO 19 ■ GENÉRICOS483Invocando um Método GenéricoPara invocar um método genérico,


forneça argumentos de tipo com a invocação do método, conforme mostrado aqui:Argumentos de
tipo↓MeuMétodo <short, int> ();MyMethod <int, long> ();A Figura 19-11 mostra a declaração de um
método genérico chamado DoStuff, que tem dois tiposparâmetros. Abaixo dele estão dois lugares onde o
método é chamado, cada um com um conjunto diferente de tipoparâmetros. Cada uma dessas instâncias
construídas produz uma versão diferente do método, como mostradoà direita da figura.Figura 19-11 .
Um método genérico com duas instanciações

Página 514

CAPÍTULO 19 ■ GENÉRICOS484Tipos de inferênciaSe você estiver passando parâmetros para um


método, o compilador pode às vezes inferir a partir dos tipos deparâmetros de método os tipos que
devem ser usados como parâmetros de tipo do método genérico. Isso podetornar as chamadas de
método mais simples e fáceis de ler.Por exemplo, o código a seguir declara MyMethod, que recebe um
parâmetro de método do mesmodigite como o parâmetro de tipo.public void MeuMétodo <T> (T
meuVal) {...}↑ ↑Ambos são do tipo TSe você invocar MyMethod com uma variável do tipo int, conforme
mostrado no código a seguir, as informações emo parâmetro de tipo da invocação do método é
redundante, pois o compilador pode ver a partir do métodoparâmetro que é um int.int myInt =
5;MyMethod <int> (myInt);↑ ↑Ambos são intsComo o compilador pode inferir o parâmetro de tipo a
partir do parâmetro do método, você pode omitir o tipoparâmetro e seus colchetes angulares da
invocação, conforme mostrado aqui:MyMethod (myInt);

Página 515

CAPÍTULO 19 ■ GENÉRICOS485Exemplo de um método genéricoO código a seguir declara um método


genérico chamado ReverseAndPrint em uma classe não genérica chamadaSimples. O método leva como
parâmetro um array de qualquer tipo. Main declara três tipos diferentes de array.Em seguida, ele chama
o método duas vezes com cada array. A primeira vez que chama o método com uma determinada
matriz,usa explicitamente o parâmetro de tipo. Na segunda vez, o tipo é inferido.classe simples// Classe
não genérica{static public void ReverseAndPrint <T> (T [] arr) // Método genérico{Array.Reverse
(arr);foreach (T item na chegada)// Use o argumento de tipo T.Console.Write ("{0},", item.ToString
());Console.WriteLine ("");}}programa de aula{static void Main (){// Crie arrays de vários tipos.var intArray
= new int [] {3, 5, 7, 9, 11};var stringArray = nova string [] {"primeiro", "segundo", "terceiro"};var
doubleArray = new double [] {3.567, 7.891, 2.345};Simple.ReverseAndPrint <int> (intArray);// Invoke
methodSimple.ReverseAndPrint (intArray);// Inferir tipo e invocarSimple.ReverseAndPrint <string>
(stringArray); // Invoke methodSimple.ReverseAndPrint (stringArray);// Inferir tipo e
invocarSimple.ReverseAndPrint <double> (doubleArray); // Invoke methodSimple.ReverseAndPrint
(doubleArray);// Inferir tipo e invocar}}Este código produz a seguinte saída:11, 9, 7, 5, 3,3, 5, 7, 9,
11,terceiro, segundo, primeiro,primeiro segundo terceiro,2,345, 7,891, 3,567,3,567, 7,891, 2,345,

Página 516

CAPÍTULO 19 ■ GENÉRICOS486Métodos de extensão com classes genéricasOs métodos de extensão são


descritos em detalhes no Capítulo 7 e funcionam tão bem com classes genéricas. Elespermitem que você
associe um método estático em uma classe com uma classe genérica diferente e invoque ométodo como
se fosse um método de instância em uma instância construída da classe.Tal como acontece com as
classes não genéricas, um método de extensão para uma classe genérica deve satisfazer o
seguinterestrições:•Deve ser declarado estático.•Deve ser membro de uma classe estática.•Deve conter
como seu primeiro parâmetro digite a palavra-chave this, seguida pelo nome do genéricoclasse que
estende.O código a seguir mostra um exemplo de um método de extensão chamado Print em uma classe
genérica chamadaTitular <T>:classe estática ExtendHolder{public static void Print <T> (este titular <T> h)
{T [] vals = h.GetValues ();Console.WriteLine ("{0}, \ t {1}, \ t {2}", vals [0], vals [1], vals [2]);}}titular da
classe <T>{T [] Vals = novo T [3];Detentor público (T v0, T v1, T v2){Vals [0] = v0; Vals [1] = v1; Vals [2] =
v2; }public T [] GetValues () {return Vals; }}programa de aula{static void Main (string [] args) {var
intHolder = novo titular <int> (3, 5, 7);var stringHolder = novo titular <string> ("a1", "b2",
"c3");intHolder.Print ();stringHolder.Print ();}}Este código produz a seguinte saída:3, 5, 7a1, b2, c3

Página 517

CAPÍTULO 19 ■ GENÉRICOS487Estruturas GenéricasComo classes genéricas, estruturas genéricas podem


ter parâmetros de tipo e restrições. As regras e condiçõespara estruturas genéricas são iguais àquelas
para classes genéricas.Por exemplo, o código a seguir declara uma estrutura genérica chamada
PieceOfData, que armazena erecupera uma parte dos dados, o tipo dos quais é determinado quando o
tipo é construído. Principais criaçõesobjetos de dois tipos construídos - um usando int e o outro usando
string.struct PieceOfData <T>// Estrutura genérica{public PieceOfData (valor T) {_data = valor; }private T
_data;public T Data{obter {return _data; }definir {_data = value; }}}programa de aula{static void Main
()Tipo construído{↓var intData = novo PieceOfData <int> (10);var stringData = new PieceOfData <string>
("Olá.");↑Tipo construídoConsole.WriteLine ("intData = {0}", intData.Data);Console.WriteLine ("stringData
= {0}", stringData.Data);}}Este código produz a seguinte saída:intData = 10stringData = Olá.

Página 518

CAPÍTULO 19 ■ GENÉRICOS488Delegados GenéricosDelegados genéricos são muito parecidos com


delegados não genéricos, exceto que os parâmetros de tipo determinamas características de quais
métodos serão aceitos.•Para declarar um delegado genérico, coloque a lista de parâmetros de tipo entre
colchetes angulares após o delegadonome e antes da lista de parâmetros de delegado.Parâmetros de
tipo↓delegar R MyDelegate <T, R> (valor T);↑↑Tipo de retornoDelegar parâmetro formal•Observe que
existem duas listas de parâmetros: a lista de parâmetros formais de delegado e o tipolista de
parâmetros.•O escopo dos parâmetros de tipo inclui o seguinte:- O tipo de retorno- A lista formal de
parâmetros- As cláusulas de restrição

Página 519

CAPÍTULO 19 ■ GENÉRICOS489O código a seguir mostra um exemplo de um delegado genérico. Em


Main, o delegado genérico MyDelegate éinstanciado com um argumento do tipo string e inicializado com
o método PrintString.delegate void MyDelegate <T> (valor T);// Delegado genéricoclasse simples{static
public void PrintString (string s) // Método corresponde ao delegado{Console.WriteLine (s);}static public
void PrintUpperString (string s) // Método corresponde ao delegado{Console.WriteLine ("{0}", s.ToUpper
());}}programa de aula{static void Main (){var myDel =// Criar inst de delegadonew MyDelegate <string>
(Simple.PrintString);myDel + = Simple.PrintUpperString;// Adicione um método.myDel ("Olá.");//
Chamar delegado}}Este código produz a seguinte saída:Olá.OLÁ.

Página 520

CAPÍTULO 19 ■ GENÉRICOS490Outro exemplo de delegado genéricoComo o recurso LINQ do C # 3.0 usa


delegados genéricos extensivamente, vale a pena mostrar outro exemploantes de chegarmos lá.
Abordarei o próprio LINQ e mais sobre seus delegados genéricos no Capítulo 21.O código a seguir
declara um delegado genérico chamado Func, que usa métodos com doisparâmetros e que retornam um
valor. O tipo de retorno do método é representado como TR, e o métodoos tipos de parâmetros são
representados como T1 e T2.Delegar tipo de parâmetro↓ ↓↓ ↓delegado público TR Func <T1, T2, TR> (T1
p1, T2 p2); // Delegado genérico↑↑classe simplesDelegar tipo de retorno{static public string PrintString
(int p1, int p2) // Método corresponde ao delegado{total int = p1 + p2;return total.ToString ();}}programa
de aula{static void Main (){var myDel =// Criar inst de delegadonew Func <int, int, string>
(Simple.PrintString);Console.WriteLine ("Total: {0}", myDel (15, 13)); // Chamar delegado}}Este código
produz a seguinte saída:Total: 28

Página 521

CAPÍTULO 19 ■ GENÉRICOS491Interfaces GenéricasInterfaces genéricas permitem que você escreva


interfaces onde os parâmetros formais e tipos de retorno demembros da interface são parâmetros de
tipo genérico. Declarações de interface genéricas são semelhantes às não genéricasdeclarações de
interface, mas têm a lista de parâmetros de tipo entre colchetes angulares após o nome da interface.Por
exemplo, o código a seguir declara uma interface genérica chamada IMyIfc.•Simples é uma classe
genérica que implementa a interface genérica IMyIfc.•Main instancia dois objetos da classe genérica: um
com tipo int e outro com tipocorda.Parâmetro de tipo↓interface IMyIfc <T>// Interface genérica{T
ReturnIt (T inValue);}Interface genérica do parâmetro de tipo↓↓class Simple <S>: IMyIfc <S>// Classe
genérica{public S ReturnIt (S inValue)// Implementar interface genérica{return inValue; }}programa de
aula{static void Main (){var trivInt = new Simple <int> ();var trivString = new Simple <string>
();Console.WriteLine ("{0}", trivInt.ReturnIt (5));Console.WriteLine ("{0}", trivString.ReturnIt
("Olá."));}}Este código produz a seguinte saída:5Olá.

Página 522

CAPÍTULO 19 ■ GENÉRICOS492Um exemplo de uso de interfaces genéricasO exemplo a seguir ilustra


dois recursos adicionais de interfaces genéricas:•Como outros genéricos, instâncias de uma interface
genérica instanciada com diferentes parâmetros de tiposão interfaces diferentes.•Você pode
implementar uma interface genérica em um tipo não genérico .Por exemplo, o código a seguir é
semelhante ao último exemplo, mas, neste caso, Simple não é genéricoclasse que implementa uma
interface genérica. Na verdade, ele implementa duas instâncias de IMyIfc. Uma instância éinstanciado
com tipo int e o outro com tipo string.interface IMyIfc <T>// Interface genérica{T ReturnIt (T
inValue);}Duas interfaces diferentes da mesma interface genérica↓↓class Simple: IMyIfc <int>, IMyIfc
<string> // Classe não genérica{public int ReturnIt (int inValue)// Implementar interface usando
int{return inValue; }public string ReturnIt (string inValue) // Implementar interface usando string{return
inValue; }}programa de aula{static void Main (){Simples trivial = novo Simples ();Console.WriteLine ("{0}",
trivial.ReturnIt (5));Console.WriteLine ("{0}", trivial.ReturnIt ("Olá."));}}Este código produz a seguinte
saída:5Olá.Baixe a partir de Wow! e-book <www.wowebook.com>

Página 523

CAPÍTULO 19 ■ GENÉRICOS493Implementações de interface genérica devem ser exclusivasAo


implementar uma interface em um tipo genérico, não deve haver combinação possível de
tipoargumentos que criariam uma interface duplicada no tipo.Por exemplo, no código a seguir, a classe
Simple usa duas instanciações da interface IMyIfc.•O primeiro é um tipo construído, instanciado com o
tipo int.•O segundo tem um parâmetro de tipo em vez de um argumento.Não há nada de errado em si
com a segunda interface, já que é perfeitamente normal usar uminterface. O problema aqui, porém, é
que permite um possível conflito, porque se int for usado como o tipoargumento para substituir S na
segunda interface, então Simple teria duas interfaces do mesmo tipo—o que não é permitido.interface
IMyIfc <T>{T ReturnIt (T inValue);}Duas interfaces↓↓class Simple <S>: IMyIfc <int>, IMyIfc <S> // Erro!
{public int ReturnIt (int inValue) // Implementar a primeira interface.{return inValue;}public S ReturnIt (S
inValue) // Implementar a segunda interface,{// mas se for int, seriareturn inValue;// igual ao
anterior.}}■ Nota Os nomes de interfaces genéricas não entram em conflito com interfaces não
genéricas. Por exemplo, no anteriorcódigo, poderíamos também ter declarado uma interface não
genérica chamada IMyIfc .

Página 524

CAPÍTULO 19 ■ GENÉRICOS494Covariância e Contravariância em GenéricosComo você viu ao longo


deste capítulo, quando você cria uma instância de um tipo genérico, o compiladorpega a declaração de
tipo genérico e os argumentos de tipo e cria um tipo construído. Um engano queas pessoas
normalmente fazem, no entanto, é assumir que você pode atribuir um delegado de um tipo derivado a
umvariável de um delegado de um tipo base. Nas seções a seguir, veremos este tópico, que é
chamadovariância. Existem três tipos de variância - covariância , contravariância e invariância
.Começaremos revisando algo que você já aprendeu: cada variável tem um tipo atribuído a ela,e você
pode atribuir um objeto de um tipo mais derivado a uma variável de um de seus tipos básicos. Isso é
chamadocompatibilidade de atribuição . O código a seguir demonstra compatibilidade de atribuição com
uma classe baseAnimal e uma classe Cachorro derivado de Animal. Em Main, você pode ver que o código
cria um objeto do tipoDog e o atribui à variável a2 do tipo Animal.classe animal{public int NumberOfLegs
= 4;}classe Cachorro: Animal{}programa de aula{static void Main (){Animal a1 = novo Animal ();Animal a2
= novo cão ();Console.WriteLine ("Número de patas de cachorro: {0}", a2.NumberOfLegs);}}A Figura 19-
12 ilustra a compatibilidade de atribuição. Nesta figura, as caixas que mostram o cachorro eOs objetos
animais também mostram suas classes básicas.Figura 19-12. A compatibilidade de atribuição significa
que você pode atribuir uma referência de um tipo mais derivado a umvariável de um tipo menos
derivado.Agora vamos ver um caso mais interessante, expandindo o código das seguintes maneiras,
conforme mostradoSegue:•Este código adiciona um delegado genérico chamado Factory, que leva um
parâmetro de tipo único T, não levaparâmetros do método e retorna um objeto do tipo T.

Página 525

CAPÍTULO 19 ■ GENÉRICOS495•Eu adicionei um método chamado MakeDog que não aceita parâmetros
e retorna um objeto Dog. estemétodo, portanto, corresponde a delegate Factory se usarmos Dog como o
parâmetro de tipo.•A primeira linha de Main cria um objeto delegado cujo tipo é delegate Factory <Dog>
e atribuisua referência à variável dogMaker, do mesmo tipo.•A segunda linha tenta atribuir um delegado
do tipo delegado Factory <Dog> a um tipo de delegadovariável chamada animalMaker do tipo delegate
Factory <Animal>.Esta segunda linha em Main, no entanto, causa um problema e o compilador produz
uma mensagem de errodizendo que não pode converter implicitamente o tipo à direita para o tipo à
esquerda.classe Animal {público int Pernas = 4; } // Classe baseclasse Cachorro: Animal {}// Classe
derivadadelegar T Factory <T> (); ← delegar Fábricaprograma de aula{estático Dog MakeDog ()← Método
que corresponde à Fábrica de delegado{retornar novo Cachorro ();}static void Main (){Factory <Dog>
dogMaker = MakeDog; ← Criar objeto delegadoFábrica <Animal> animalMaker = dogMaker; ← Tentativa
de atribuir objeto delegadoConsole.WriteLine (animalMaker () .Legs.ToString ());}}Parece fazer sentido
que um delegado construído com o tipo de base seja capaz de conter umdelegado construído com o tipo
derivado. Então, por que o compilador fornece uma mensagem de erro? Não faz oprincípio de
compatibilidade de atribuição em espera?O princípio é válido, mas não se aplica nesta situação! O
problema é que embora Dogderiva de Animal, o delegado Factory <Cão> não deriva de delegado Factory
<Animal>. Em vez de,ambos os objetos delegados são pares, derivados do delegado de tipo, que deriva
do objeto de tipo, como mostradona Figura 19-13. Nenhum delegado é derivado do outro, portanto, a
compatibilidade de atribuição não se aplica.Figura 19-13. A compatibilidade de atribuição não se aplica
porque os dois delegados não estão relacionados por herança.

Página 526

CAPÍTULO 19 ■ GENÉRICOS496Embora a incompatibilidade de tipos de delegado não permita atribuir


um tipo à variável de outrotipo, é muito ruim nesta situação, porque no código de exemplo, a qualquer
momento, executaríamos o delegadoanimalMaker, o código de chamada esperaria ter uma referência a
um objeto Animal retornado. Se issoretornou uma referência a um objeto Dog em vez disso, o que seria
perfeitamente adequado, pois uma referência a um objeto Dog é umreferência a um Animal, por
compatibilidade de atribuição.Olhando para a situação com mais cuidado, podemos ver que para
qualquer delegado genérico, se um tipoparâmetro é usado apenas como um valor de saída , então a
mesma situação se aplica. Em todas essas situações, vocêseria capaz de usar um tipo de delegado
construído criado com uma classe derivada e funcionaria bem,uma vez que o código de chamada
sempre estaria esperando uma referência à classe base - que é exatamente o queiria ficar.Esta relação
constante entre o uso de um tipo derivado apenas como um valor de saída e a validade deo delegado
construído é chamado de covariância e agora é explicitamente permitido no C # 4.0. Para deixar o
compiladorsaiba que é isso que você pretende, você deve marcar o parâmetro de tipo na declaração de
delegado coma palavra-chave out.Por exemplo, se alterarmos a declaração de delegado no exemplo
adicionando a palavra-chave out, comomostrado aqui, o código compila e funciona bem.delegar T
Factory <out T> ();↑Covariância de especificação de palavra-chavedo parâmetro de tipoA Figura 19-14
ilustra os componentes de covariância neste exemplo:•A variável na pilha à esquerda é do tipo delegado
T Factory <out T> (), onde a variável tipo Té da classe Animal.•O delegado real construído no heap, à
direita, foi declarado com uma variável de tipo declasse Dog, que é derivada da classe Animal.•Isso é
aceitável porque quando o delegado é chamado, o código de chamada recebe um objeto do
tipoCachorro, em vez do objeto esperado do tipo Animal. O código de chamada pode operar livremente
noParte animal do objeto como ele espera fazer.Figura 19-14. A relação covariante permite que um tipo
mais derivado esteja nas posições de retorno e de saída.

Página 527

CAPÍTULO 19 ■ GENÉRICOS497O código a seguir ilustra uma situação relacionada. Neste exemplo, há
um delegado, chamado Action1,que recebe um único parâmetro de tipo e um único parâmetro de
método cujo tipo é o do tipoparâmetro e não retorna nenhum valor.O código também contém um
método chamado ActOnAnimal, cuja assinatura e tipo de retorno void correspondema declaração do
delegado.A primeira linha em Main cria um delegado construído usando o tipo Animal e o método
ActOnAnimal,cuja assinatura e tipo de retorno void correspondem à declaração do delegado. Na
segunda linha, no entanto, oo código tenta atribuir a referência a este delegado a uma variável de pilha
chamada dog1, do tipo delegadoAção1 <Cão>.class Animal {public int NumberOfLegs = 4; }classe
Cachorro: Animal {}programa de aulaPalavra-chave para contravariância{↓delegate void Action1 <em T>
(T a);estático void ActOnAnimal (Animal a) {Console.WriteLine (a.NumberOfLegs); }static void Main ()
{Ação1 <Animal> act1 = ActOnAnimal;Ação1 <Cão> cão1 = ato1;dog1 (novo Cachorro ());}}Este código
produz a seguinte saída:4Como na situação anterior, por padrão, você não pode atribuir os dois tipos
incompatíveis. Mas também gosto desituação anterior, há situações em que a atribuição funcionaria
perfeitamente bem.Na verdade, isso é verdade sempre que o parâmetro de tipo é usado apenas como
um parâmetro de entrada parao método no delegado. A razão para isso é que, embora o código de
invocação passe por umreferência a uma classe mais derivada, o método no delegado espera apenas
uma referência a uma classe menorclasse derivada - que obviamente recebe e sabe como manipular.Esta
relação, permitindo um objeto mais derivado onde um objeto menos derivado é esperado, é
chamadacontravariância e agora é explicitamente permitido no C # 4.0. Para usá-lo, você deve usar a
palavra-chave in com oparâmetro de tipo, conforme mostrado no código.

Página 528

CAPÍTULO 19 ■ GENÉRICOS498A Figura 19-15 ilustra os componentes de contravariância na linha 2 de


Main.•A variável na pilha à esquerda é do tipo delegado void Ação1 <em T> (T p), onde o tipovariável é
da classe Dog.•O delegado real construído, à direita, é declarado com uma variável de tipo da classe
Animal,que é uma classe base da classe Dog.•Isso funciona bem porque quando o delegado é chamado,
o código de chamada passa em um objeto do tipoDog, para o método ActOnAnimal, que está esperando
um objeto do tipo Animal. O método pode livrementeoperar na parte Animal do objeto como ele espera
fazer.Figura 19-15. A relação contravariante permite que mais tipos derivados sejam permitidos como
parâmetros de entrada.

Página 529

CAPÍTULO 19 ■ GENÉRICOS499A Figura 19-16 resume as diferenças entre covariância e contravariância


em um genéricodelegar.•A figura superior ilustra a covariância.- A variável na pilha à esquerda é do tipo
delegado F <out T> () onde a variável de tipo éde uma classe chamada Base.- O delegado real construído,
à direita, foi declarado com uma variável de tipo de classeDerivado, que é derivado da classe Base.- Isso
funciona bem porque quando o delegado é chamado, o método retorna uma referência a umobjeto do
tipo derivado, que também é uma referência à classe base, que é exatamente o queo código de chamada
está esperando.•A figura inferior ilustra a contravariância.- A variável na pilha à esquerda é do tipo
delegado void F <em T> (T p), onde o tipoparâmetro é da classe Derived.- O delegado construído real, à
direita foi declarado com uma variável de tipo de classe Base,que é uma classe base da classe Derived.-
Isso funciona bem porque quando o delegado é chamado, o código de chamada passa em um objeto
deo tipo derivado, ao método que está esperando um objeto do tipo básico. O métodopode operar
livremente na parte de base do objeto como espera fazer.Figura 19-16. Uma comparação de covariância
e contravariância

Página 530

CAPÍTULO 19 ■ GENÉRICOS500Covariância e Contravariância nas InterfacesAgora você deve ter uma


compreensão da covariância e contravariância conforme se aplica aos delegados.Os mesmos princípios
se aplicam às interfaces, incluindo a sintaxe usando as palavras-chave out e in nodeclaração de
interface.O código a seguir mostra um exemplo de uso de covariância com uma interface. As coisas a
serem observadassobre o código são os seguintes:•O código declara uma interface genérica com o
parâmetro de tipo T. A palavra-chave out especifica que oo parâmetro de tipo é covariante.•A classe
genérica SimpleReturn implementa a interface genérica.•O método DoSomething mostra como um
método pode usar uma interface como parâmetro. Este métodotoma como parâmetro uma interface
IMyIfc genérica construída com o tipo Animal.O código funciona da seguinte maneira:•As primeiras duas
linhas de Main criam e inicializam uma instância construída de classe genéricaSimpleReturn, usando a
classe Dog.•A próxima linha atribui esse objeto a uma variável na pilha que é declarada da interface
construídadigite IMyIfc <Animal>. Observe várias coisas sobre esta declaração:- O tipo à esquerda da
tarefa é um tipo de interface - não uma classe.- Mesmo que os tipos de interface não correspondam
exatamente, o compilador os permite por causa deo especificador de saída covariante na declaração de
interface.•Finalmente, o código chama o método DoSomething com a classe covariant construída que
implementaa interface.

Página 531

CAPÍTULO 19 ■ GENÉRICOS501class Animal {public string Name; }classe Cachorro: Animal {};Palavra-
chave para covariância↓interface IMyIfc <out T>{T GetFirst ();}classe SimpleReturn <T>: IMyIfc <T>{itens T
[] públicos = novos T [2];public T GetFirst () {retornar itens [0]; }}programa de aula{static void
DoSomething (IMyIfc <Animal> returner){Console.WriteLine (returner.GetFirst (). Name);}static void Main
(){SimpleReturn <Dog> dogReturner = novo SimpleReturn <Dog> ();dogReturner.items [0] = new Dog ()
{Name = "Avonlea"};IMyIfc <Animal> animalReturner = dogReturner;DoSomething (dogReturner);}}Este
código produz a seguinte saída:Avonlea

Página 532

CAPÍTULO 19 ■ GENÉRICOS502Mais sobre a variaçãoAs duas seções anteriores explicaram a covariância


e a contravariância explícitas. Também há uma situaçãoonde o compilador reconhece automaticamente
que um certo delegado construído é covariante oucontravariante e torna o tipo coerção
automaticamente. Isso acontece quando o objeto ainda não teveum tipo atribuído a ele. O código a
seguir mostra um exemplo.A primeira linha de Main cria um delegado construído do tipo Factory
<Animal> a partir de um método ondeo tipo de retorno é um objeto Dog, não um objeto Animal. Ao
criar este delegado, o nome do método nolado direito do operador de atribuição ainda não tem um tipo,
e o compilador pode determinar que ométodo se ajusta ao tipo do delegado, exceto que seu tipo de
retorno é do tipo Cachorro em vez do tipo Animal. ocompilador é inteligente o suficiente para perceber
que esta é uma relação covariante e cria o tipo construído eatribui à variável.Compare isso com as
atribuições na terceira e quarta linhas do Main. Nestes casos, oexpressões no lado direito do sinal de
igual já têm um tipo e, portanto, precisam do especificador de saídana declaração de delegado para
sinalizar ao compilador para permitir que sejam covariantes.classe Animal {público int Pernas = 4; }//
Classe baseclasse Cachorro: Animal {}// Classe derivadaprograma de aula{delegar T Factory <out T>
();estático Dog MakeDog () {return new Dog (); }static void Main (){Fábrica <Animal> animalMaker1 =
MakeDog; // Coagido implicitamenteFactory <Dog> dogMaker = MakeDog;Fábrica <Animal>
animalMaker2 = dogMaker; // Requer o especificador de saídaFábrica <Animal> animalMaker3= novo
Factory <Dog> (MakeDog); // Requer o especificador de saída}}Esta coerção implícita implementando
covariância e contravariância está disponível sem opalavras-chave de entrada / saída antes do C #
4.0.Baixe a partir de Wow! e-book <www.wowebook.com>

Página 533

CAPÍTULO 19 ■ GENÉRICOS503Outras coisas importantes que você deve saber sobre a variação são as
seguintes:•Como você viu, a variância trata da questão de onde é seguro substituir um tipo de base por
umtipo derivado e vice-versa. A variação, portanto, se aplica apenas a tipos de referência, uma vez que
os tipos de valornão pode ser derivado.•Variância explícita, usando as palavras-chave in e out se aplica
apenas a delegados e interfaces - nãoclasses, estruturas ou métodos.•Os parâmetros de delegado e tipo
de interface que não incluem a palavra-chave in ou out são chamadosinvariante . Esses tipos não podem
ser usados de forma covariante ou contravariante.Contravariante↓delegar T Factory <out R, em S, T>
();↑↑CovariantInvariante

Página 534

Página 535

CAPÍTULO 20■ ■ ■505Enumeradores e Iteradores■ Enumeradores e tipos enumeráveis■ Usando a


interface IEnumerator■ A interface IEnumerable■ O enumerador de não interface■ As interfaces
genéricas de enumeração■ A interface IEnumerator <T>■ A interface IEnumerable <T>■ Iteradores■
Padrões de iterador comuns■ Produção de Enumeráveis e Enumeradores■ Produção de múltiplos
enumeráveis■ Produção de múltiplos enumeradores■ Nos bastidores com iteradores

Página 536

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES506Enumeradores e tipos enumeráveisNo Capítulo 14,


você viu que pode usar uma instrução foreach para percorrer os elementos de um array.Neste capítulo,
você dará uma olhada mais de perto nos arrays e verá porque eles podem ser processados por
foreachafirmações. Você também verá como adicionar esse recurso às suas próprias classes definidas
pelo usuário. Mais tarde emo capítulo, explicarei o uso de iteradores.Usando a declaração
foreachQuando você usa uma instrução foreach com uma matriz, a instrução apresenta cada elemento
noarray, um por um, permitindo que você leia seu valor.Por exemplo, o código a seguir declara uma
matriz com quatro elementos e, em seguida, usa um loop foreachpara imprimir os valores dos itens:int []
arr1 = {10, 11, 12, 13};// Defina o array.foreach (item interno em arr1)// Enumere os
elementos.Console.WriteLine ("Valor do item: {0}", item);Este código produz a seguinte saída:Valor do
item: 10Valor do item: 11Valor do item: 12Valor do item: 13Por que isso funciona, aparentemente
magicamente, com matrizes? A razão é que uma matriz pode produzir,a pedido, um objeto denominado
enumerador . O enumerador é um objeto que pode retornar os elementosda matriz, um por um, em
ordem, conforme solicitado. O enumerador “conhece” a ordem dos itense mantém o controle de onde
está na sequência. Em seguida, ele retorna o item atual quando solicitado.Para tipos que possuem
enumeradores, deve haver uma maneira de recuperá-los. A forma padrão derecuperar o enumerador de
um objeto no .NET é chamar o método GetEnumerator do objeto. Tipos queimplementar um método
GetEnumerator são chamados de tipos enumeráveis ou apenas enumeráveis . Matrizes
sãoenumeráveis.A Figura 20-1 ilustra a relação entre enumeráveis e enumeradores.

Página 537

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES507Figura 20-1 . Visão geral de enumeradores e


enumeráveisA construção foreach foi projetada para funcionar com enumeráveis. Contanto que o objeto
ao qual é dadoiterar é um tipo enumerável, como uma matriz, ele executará as seguintes
ações:•Obtenha o enumerador do objeto chamando seu método GetEnumerator•Solicite cada item do
enumerador e disponibilize-o para seu código como a iteraçãovariável , que seu código pode ler (mas
não alterar).Deve ser enumerável↓foreach ( digite VarName em EnumerableObject ){...}Tipos de
EnumeradoresExistem três variações nos enumeradores. Todos eles funcionam essencialmente da
mesma maneira, com apenas algunsdiferenças. Vou discutir todos os três tipos. Você pode implementar
enumeradores usando o seguinte:•As interfaces IEnumerator / IEnumerable - chamadas de forma de
interface não genérica•As interfaces IEnumerator <T> / IEnumerable <T> - chamadas de forma de
interface genérica•O formulário que não usa interfaces

Página 538

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES508Usando a interface IEnumeratorEsta seção começará


examinando o primeiro na lista anterior: a forma de interface não genérica. esteforma de enumerador é
uma classe que implementa a interface IEnumerator. É chamado de não genérico porquenão usa
genéricos C #.A interface IEnumerator contém três membros de função: Current, MoveNext e
Reset.•Atual é uma propriedade que retorna o item na posição atual na sequência.- É uma propriedade
somente leitura.- Ele retorna uma referência de objeto de tipo, portanto, um objeto de qualquer tipo
pode ser retornado.•MoveNext é um método que avança a posição do enumerador para o próximo item
na coleção. istotambém retorna um valor booleano, indicando se a nova posição é uma posição válida ou
está alémo final da sequência.- Se a nova posição for válida, o método retorna verdadeiro.- Se a nova
posição não for válida (ou seja, está além do fim), o método retorna falso.- A posição inicial do
enumerador é antes do primeiro item na sequência. MoveNext deveser chamado antes do primeiro
acesso do Current.•Redefinir é um método que redefine a posição ao estado inicial.A Figura 20-2 ilustra
uma coleção de três itens, que é mostrado à esquerda da figura, e seuenumerador, que é mostrado à
direita. Na figura, o enumerador é uma instância de uma classe chamadaArrEnumerator.Figura 20-2 . O
enumerador para uma pequena coleção

Página 539

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES509A classe de enumerador geralmente é declarada


como uma classe aninhada da classe para a qual é um enumerador.Uma classe aninhada é declarada
dentro da declaração de outra classe. As classes aninhadas são descritas em detalhes emCapítulo 25.A
forma como o enumerador mantém o controle do item atual na sequência é inteiramente de
implementação-dependente. Pode ser implementado como uma referência a um objeto, um valor de
índice ou outra coisainteiramente. No caso do tipo de array unidimensional integrado, é simplesmente o
índice do item.A Figura 20-3 ilustra os estados de um enumerador para uma coleção de três itens. Os
estados sãorotulados de 1 a 5.•Observe que no estado 1, a posição inicial do enumerador é -1 (ou seja,
antes do primeiro elementoda coleção).•Cada transição entre os estados é causada por uma chamada
para MoveNext, que avança a posição noseqüência. Cada chamada para MoveNext entre os estados 1 e
4 retorna verdadeiro. Na transição entreestados 4 e 5, no entanto, a posição acaba além do último item
da coleção, então ométodo retorna falso.•No estado final, quaisquer chamadas adicionais para
MoveNext retornam false.Figura 20-3 . Os estados de um enumerador
Página 540

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES510Dado o enumerador de uma coleção, você deve ser


capaz de simular um loop foreach percorrendoos itens na coleção usando os membros MoveNext e
Current. Por exemplo, você sabe que matrizessão enumeráveis, portanto, o código a seguir faz
manualmente o que a instrução foreach faz automaticamente . NoNa verdade, o compilador C # gera
exatamente esse código quando você escreve um loop foreach.static void Main (){int [] MyArray = {10,
11, 12, 13};// Crie um array.IEnumerator ie = MyArray.GetEnumerator (); // Obtenha seu
enumerador.while (ie.MoveNext ())// Mova para o próximo item.{int i = (int) ie.Corrente;// Pega o item
atual.Console.WriteLine ("{0}", i);// Escreva.}}Este código produz a seguinte saída:10111213

Página 541

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES511Declarando um Enumerador IEnumeratorPara criar


uma classe de enumerador de interface não genérica, você deve declarar uma classe que implemente
oInterface IEnumerator. A interface do IEnumerator possui as seguintes características:•É um membro
do namespace System.Collections.•Ele contém os três membros Current, MoveNext e Reset.O código a
seguir mostra o esboço de uma classe de enumerador não genérico. Não mostra como oposição é
mantida. Observe que Current retorna uma referência a um objeto.using System.Collections;// Inclui o
namespace.classe MyEnumerator: IEnumerator{Retorna uma referência a um objeto↓objeto público
Current {get; }// Atualpublic bool MoveNext () {...}// MoveNextpublic void Reset () {...}// Redefinir...}

Página 542

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES512Por exemplo, o código a seguir implementa uma


classe de enumerador que lista uma matriz de nomes de cores:using System.Collections;classe
ColorEnumerator: IEnumerator{↑string [] Colors; Implementa IEnumeratorposição int = -1;objeto público
atual// Atual{obter{if (Posição == -1)lance novo InvalidOperationException ();if (Posição ==
Colors.Length)lance novo InvalidOperationException ();retornar Cores [Posição];}}public bool MoveNext
()// MoveNext{if (Posição <Cores.Comprimento - 1){Posição ++;return true;}outroretorna falso;}public
void Reset ()// Redefinir{Posição = -1;}public ColorEnumerator (string [] theColors)// Construtor{Colors =
nova string [theColors.Length];para (int i = 0; i <theColors.Length; i ++)Cores [i] = as cores [i];}}Baixe a
partir de Wow! e-book <www.wowebook.com>

Página 543

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES513A interface IEnumerableA interface IEnumerable


tem apenas um único membro, o método GetEnumerator, que retorna umenumerador do objeto.A
Figura 20-4 mostra a classe MyClass, que tem três itens para enumerar e implementa oInterface
IEnumerable implementando o método GetEnumerator.Figura 20-4 . O método GetEnumerator retorna
um objeto enumerador para a classe.O código a seguir mostra o formulário para a declaração de uma
classe enumerável:using System.Collections;Implementa a interface IEnumerable↓class MyClass:
IEnumerable{public IEnumerator GetEnumerator {...}... ↑} Retorna um objeto do tipo IEnumeratorO
código a seguir fornece um exemplo de uma classe enumerável que usa a classe de
enumeradorColorEnumerator do exemplo anterior. Lembre-se de que ColorEnumerator implementa
IEnumerator.using System.Collections;classe MyColors: IEnumerable{string [] Colors = {"Vermelho",
"Amarelo", "Azul"};public IEnumerator GetEnumerator (){retornar novo ColorEnumerator
(Colors);}↑}Uma instância da classe de enumerador

Página 544

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES514Exemplo de uso de IEnumerable e


IEnumeratorColocando os exemplos MyColors e ColorEnumerator juntos, você pode adicionar uma
classe chamada Program com umMétodo principal que cria uma instância de MyColors e a usa em um
loop foreach.using System;using System.Collections;namespace ColorCollectionEnumerator{classe
ColorEnumerator: IEnumerator{string [] Colors;posição int = -1;public ColorEnumerator (string []
theColors)// Construtor{Colors = nova string [theColors.Length];para (int i = 0; i <theColors.Length; i +
+)Cores [i] = as cores [i];}objeto público atual// Atual{obter{if (Posição == -1){lance novo
InvalidOperationException ();}if (Posição == Colors.Length){lance novo InvalidOperationException
();}retornar Cores [Posição];}}public bool MoveNext ()// MoveNext{if (Posição <Cores.Comprimento - 1)
{Posição ++;return true;}outroretorna falso;}public void Reset ()// Redefinir{Posição = -1; }}

Página 545

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES515classe MyColors: IEnumerable{string [] Colors =


{"Vermelho", "Amarelo", "Azul"};public IEnumerator GetEnumerator (){retornar novo ColorEnumerator
(Colors);}}programa de aula{static void Main (){MyColors mc = new MyColors ();foreach (cor da string em
mc)Console.WriteLine (cor);}}}Este código produz a seguinte saída:VermelhoAmareloAzul

Página 546

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES516O enumerador não interfaceVocê acabou de ver


como usar as interfaces IEnumerable e IEnumerator para criar enumeráveis úteise enumeradores. Mas
existem várias desvantagens neste método.Primeiro, lembre-se de que o objeto retornado por Current é
do tipo objeto. Para tipos de valor, isso significaque antes de serem retornados por Current, eles devem
ser encaixotados para transformá-los em objetos. Eles devem entãoser desempacotados novamente
após serem recebidos do Current. Isso pode exigir um desempenho substancialpenalidade se precisar
ser feito em grandes quantidades de dados.Outra desvantagem do método de interface não genérico é
que você perdeu a segurança de tipo. Os valoressendo enumerados estão sendo tratados como objetos
e, portanto, podem ser de qualquer tipo. Isso elimina a segurança deverificação de tipo em tempo de
compilação.Você pode resolver esses problemas fazendo as seguintes alterações no enumerador /
enumeráveldeclarações de classe.•Para a classe de enumerador- Do não derivar a classe de
IEnumerator.- Implemente MoveNext como antes.- Implementar Current como antes, mas tem como
tipo de retorno o tipo dos itenssendo enumerado.- Você não tem que implementar Reset.•Para a classe
enumerável- Não derive a classe de IEnumerable.- Implemente GetEnumerator como antes, mas faça
com que seu tipo de retorno seja o tipo declasse de enumerador.

Página 547
CAPÍTULO 20 ■ ENUMERADORES E ITERADORES517A Figura 20-5 mostra as diferenças. O código da
interface não genérica está à esquerda, e a não interfaceo código está à direita. Com essas mudanças, a
instrução foreach ficará perfeitamente feliz em processar seucoleção, mas sem as desvantagens
listadas.Figura 20-5 . Comparando enumeradores baseados em interface e não baseados em
interfaceUm possível problema com a implementação do enumerador sem interface é que os tipos de
outrosos assemblies podem esperar que a enumeração seja implementada usando o método de
interface. Se esses objetostentar obter uma enumeração de seus objetos de classe usando as
convenções de interface, eles não serão capazespara encontrá-los.Para resolver esse problema, você
pode implementar os dois formulários nas mesmas classes. Ou seja, você pode criarimplementações
para Current, MoveNext, Reset e GetEnumerator no nível de classe e também criarimplementações de
interface explícitas para eles. Com ambos os conjuntos de implementações, o tipo seguro,
maisimplementação eficiente será chamada por foreach e outras construções que podem usar a não
interfaceimplementações, enquanto as outras construções chamarão as implementações de interface
explícita. Um mesmoa melhor maneira, entretanto, é usar os formulários genéricos, que descrevo a
seguir.

Página 548

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES518As interfaces genéricas de enumeraçãoA terceira


forma de enumerador usa as interfaces genéricas IEnumerable <T> e IEnumerator <T>. Eles
sãochamados de genéricos porque usam genéricos C #. Usá-los é muito semelhante ao uso de formas
não genéricas.Essencialmente, as diferenças entre os dois são as seguintes:•Com a forma de interface
não genérica- O método GetEnumerator da interface IEnumerable retorna uma instância de classe de
enumeradorque implementa IEnumerator.- A classe que implementa IEnumerator implementa a
propriedade Current, que retorna umreferência do objeto de tipo, que você deve então converter para o
tipo real do objeto.•Com o formulário de interface genérico- O método GetEnumerator da interface
IEnumerable <T> retorna uma instância de uma classe queimplementa IEnumerator <T>.- A classe que
implementa IEnumerator <T> implementa a propriedade Current, que retorna uminstância do tipo real,
em vez de uma referência ao objeto da classe base.O ponto mais importante a notar, porém, é que as
implementações de interface não genérica sãonão é seguro para tipo. Eles retornam referências ao
objeto de tipo, que deve então ser convertido para os tipos reais. Comnas interfaces genéricas , no
entanto, o enumerador é seguro para tipos, retornando referências aos tipos reais. Doas três formas de
enumeração, esta é a que você deve implementar e usar. Os outros são para legadocódigo desenvolvido
antes do C # 2.0, quando os genéricos foram introduzidos.

Página 549

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES519A interface IEnumerator <T>A interface IEnumerator


<T> usa genéricos para retornar um tipo derivado real, em vez de uma referência aum objeto.A interface
IEnumerator <T> deriva de duas outras interfaces: o IEnumerator não genéricointerface e a interface
IDisposable. Deve, portanto, implementar seus membros.•Você já viu a interface não genérica do
IEnumerator e seus três membros.•A interface IDisposable tem um método único, vazio e sem
parâmetros chamado Dispose, que pode serusado para liberar recursos não gerenciados mantidos pela
classe. (O método Dispose foi descrito emCapítulo 6.)•A própria interface IEnumerator <T> tem uma
única propriedade, Current, que retorna uma instância detipo T ou derivado de T - em vez de uma
referência de objeto de tipo.•Já que IEnumerator <T> e IEnumerator têm um membro chamado Current,
você deve explicitamenteimplementar a versão IEnumerator e implementar a versão genérica na própria
classe, comomostrado na Figura 20-6.A Figura 20-6 ilustra a implementação da interface.Figura 20-6 .
Implementando a interface IEnumerator <T>

Página 550

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES520A declaração da classe que implementa a interface


deve ser semelhante ao padrão emo código a seguir, onde T é o tipo retornado pelo enumerador:using
System.Collections;using System.Collections.Generic;classe MyGenEnumerator: IEnumerator <T>{public
T Current {get {…}}// IEnumerator <T> --CurrentImplementação explícita↓objeto IEnumerator.Current
{get {...}} // IEnumerator - Atualpublic bool MoveNext () {...}// IEnumerator - MoveNextpublic void Reset
() {...}// IEnumerator - Resetpublic void Dispose () {...}// IDisposable - Dispose...}

Página 551

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES521Por exemplo, o código a seguir implementa o


exemplo ColorEnumerator usando o método genéricointerface do enumerador:using
System.Collections;using System.Collections.Generic; String de tipo substituto para T↓classe
ColorEnumerator: IEnumerator <string>{string [] Colors;posição int = -1;Retorna o tipo de argumento
type↓public string Current// Atual - genérico{get {return Colors [Position]; }}Implementação
explícita↓objeto IEnumerator.Current// Atual - não genérico{get {return Colors [Position]; }}public bool
MoveNext ()// MoveNext{if (Posição <Cores.Comprimento - 1){Posição ++;return true;}outroretorna
falso;}public void Reset ()// Redefinir{Posição = -1; }public void Dispose () {}public ColorEnumerator
(string [] cores)// Construtor{Cores = nova string [cores.Comprimento];para (int i = 0; i
<cores.Comprimento; i ++)Cores [i] = cores [i];}}

Página 552

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES522A interface IEnumerable <T>A interface IEnumerable


<T> genérica é muito semelhante à versão não genérica, IEnumerable. O genéricoversion deriva de
IEnumerable, portanto, também deve implementar a interface IEnumerable.•Como IEnumerable, a
versão genérica também contém um único membro, um método chamadoGetEnumerator. Esta versão
de GetEnumerator, no entanto, retorna um objeto de classe que implementa ointerface genérica do
IEnumerator <T>.•Uma vez que a classe deve implementar dois métodos GetEnumerator, você deve
implementar explicitamente oversão não genérica e implementar a versão genérica no nível da classe,
conforme mostrado na Figura 20-7.A Figura 20-7 ilustra a implementação da interface.Figura 20-7 .
Implementando a interface IEnumerable <T>Baixe a partir de Wow! e-book <www.wowebook.com>

Página 553

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES523O código a seguir mostra um padrão para


implementar a interface genérica. T é o tipo retornado poro enumerador.using System.Collections;using
System.Collections.Generic;classe MyGenEnumerable: IEnumerable <T>{public IEnumerator <T>
GetEnumerator () {...} // IEnumerable <T> versionImplementação explícita↓IEnumerator
IEnumerable.GetEnumerator () {...} // IEnumerable version...}O código a seguir mostra o uso da interface
enumerável genérica:using System.Collections;using System.Collections.Generic;Substitua o tipo real por
T↓classe MyColors: IEnumerable <string>{string [] Colors = {"Vermelho", "Amarelo", "Azul"};Substitua o
tipo real por T↓public IEnumerator <string> GetEnumerator ()// Versão IEnumerable <T>{retornar novo
ColorEnumerator (Colors);}Implementação explícita↓IEnumerator IEnumerable.GetEnumerator ()//
Versão IEnumerable{retornar novo ColorEnumerator (Colors);}}

Página 554

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES524IteradoresEnumeráveis classes e enumeradores são


usados extensivamente nas classes de coleção .NET, por isso é importanteque você sabe como eles
funcionam. Mas agora que você sabe como criar suas próprias classes enumeráveis eenumeradores,
você ficará satisfeito em saber que, a partir do C # 2.0, a linguagem ficou muito mais simplesforma de
criar enumeradores e enumeráveis. Na verdade, o compilador os criará para você. oa construção que os
produz é chamada de iterador . Você pode usar os enumeradores e enumeráveisgerado por iteradores
sempre que você usaria enumeradores ou enumeráveis codificados manualmente.Antes de explicar os
detalhes, vamos dar uma olhada em dois exemplos. A seguinte declaração de métodoimplementa um
iterador que produz e retorna um enumerador.•O iterador retorna um enumerador genérico que
retorna três itens do tipo string.•As declarações de retorno de rendimento declaram que este é o
próximo item na enumeração .Retorne um enumerador genérico.↓public IEnumerator <string>
BlackAndWhite ()// Versão 1{rendimento retorno "preto";// rendimento retornorendimento retornar
"cinza";// rendimento retornorendimento retorno "branco";// rendimento retorno}A seguinte
declaração de método é outra versão que produz o mesmo resultado:Retorne um enumerador
genérico.↓public IEnumerator <string> BlackAndWhite ()// Versão 2{string [] theColors = {"preto",
"cinza", "branco"};para (int i = 0; i <theColors.Length; i ++)rendimento retorna as cores [i];// rendimento
retorno}Eu não expliquei a declaração de retorno de rendimento ainda, mas ao inspecionar esses
segmentos de código, vocêpode ter a sensação de que algo está diferente neste código. Não parece
muito certo. o queexatamente a declaração de retorno de rendimento faz?Por exemplo, na primeira
versão, se o método retornar na primeira instrução de retorno de rendimento, então oas duas últimas
declarações nunca podem ser alcançadas. Se não retornar na primeira instrução, mas continuaraté o
final do método, o que acontece com os valores? E na segunda versão, se odeclaração de retorno de
rendimento no corpo do loop retorna na primeira iteração, então o loop nunca obteráa quaisquer
iterações subsequentes.E, além de tudo isso, um enumerador não retorna apenas todos os elementos de
uma vez - ele retorna umnovo valor a cada acesso da propriedade Atual. Então, como isso dá a você um
enumerador? Claramenteeste código é diferente de tudo o que foi mostrado antes.

Página 555

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES525Blocos de iteradorUm bloco iterador é um bloco de


código com uma ou mais declarações de rendimento. Qualquer um dos três tipos deblocos de código
podem ser blocos iteradores:•Um corpo de método•Um corpo acessador•Um corpo de operadorOs
blocos de iteradores são tratados de maneira diferente dos outros blocos. Outros blocos contêm
sequências dedeclarações que são tratadas imperativamente . Ou seja, a primeira instrução do bloco é
executada, seguida poras instruções subsequentes e, eventualmente, o controle sai do bloco.Um bloco
iterador, por outro lado, não é uma sequência de comandos imperativos a serem executados emum
tempo. Em vez disso, é declarativo; descreve o comportamento da classe de enumerador que você
desejacompilador para construir para você. O código no bloco iterador descreve como enumerar os
elementos.Os blocos de iterador têm duas declarações especiais:•A declaração de retorno de
rendimento especifica o próximo item na sequência a ser retornado.•A declaração de quebra de
rendimento especifica que não há mais itens na sequência.O compilador pega esta descrição de como
enumerar os itens e a usa para construir umclasse de enumerador, incluindo todos os métodos
necessários e implementações de propriedade. A aula resultanteestá aninhado dentro da classe onde o
iterador é declarado.Você pode fazer com que o iterador produza um enumerador ou um enumerável,
dependendo do retornotipo que você usa para o bloco iterador, conforme mostrado na Figura 20-
8.Figura 20-8. Você pode fazer com que um bloco iterador produza um enumerador ou um enumerável,
dependendo deo tipo de retorno que você especifica.

Página 556

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES526Usando um Iterador para Criar um EnumeradorO


código a seguir ilustra como usar um iterador para criar uma classe enumerável.•MyClass usa o método
iterador BlackAndWhite para produzir um enumerador para a classe.•MyClass também implementa o
método GetEnumerator, que por sua vez chama BlackAndWhite e retornao enumerador que
BlackAndWhite retorna a ele.•Observe que em Main, você pode usar uma instância da classe
diretamente na instrução foreach, poisa classe é enumerável.classe MyClass{public IEnumerator <string>
GetEnumerator (){return BlackAndWhite ();// Retorna o enumerador.}Retorna um enumerador↓public
IEnumerator <string> BlackAndWhite () // Iterator{rendimento retorno "preto";rendimento retornar
"cinza";rendimento retorno "branco";}}programa de aula{static void Main (){MyClass mc = new MyClass
();Use a instância de MyClass.↓foreach (sombra de corda em mc)Console.WriteLine (sombra);}}Este
código produz a seguinte saída:Pretocinzentobranco

Página 557

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES527A Figura 20-9 mostra o código para MyClass à


esquerda e os objetos resultantes à direita. Note comomuito é criado para você automaticamente pelo
compilador.•O código do iterador é mostrado no lado esquerdo da figura e mostra que seu tipo de
retorno éIEnumerator <string>.•No lado direito da figura, o diagrama mostra que a classe aninhada
implementaIEnumerator <string>.Figura 20-9 . Um bloco iterador que produz um enumerador

Página 558

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES528Usando um Iterador para Criar um EnumerávelO


exemplo anterior criou uma classe composta por duas partes: o iterador que produziu o enumeradore o
método GetEnumerator que retornou esse enumerador. Neste exemplo, o iterador é usado paracrie um
enumerável em vez de um enumerador . Existem algumas diferenças importantes entre esteexemplo e o
último:•No exemplo anterior, o método iterador BlackAndWhite retornou um IEnumerator <string>
eMyClass implementou o método GetEnumerator retornando o objeto criado por
BlackAndWhite.•Neste exemplo, o método iterador BlackAndWhite retorna um IEnumerable <string>
em vez deum IEnumerator <string>. MyClass, portanto, implementa seu método GetEnumerator
chamando primeirométodo BlackAndWhite para obter o objeto enumerável e, em seguida, chamar o
GetEnumerator desse objetométodo e retornando seus resultados.•Observe que na instrução foreach
em Main, você pode usar uma instância da classe ou chamarBlackAndWhite diretamente, pois retorna
um enumerável. Ambas as formas são mostradas.classe MyClass{public IEnumerator <string>
GetEnumerator (){IEnumerable <string> myEnumerable = BlackAndWhite (); // Get enumerablereturn
myEnumerable.GetEnumerator ();// Obter enumerador}Retorna um enumerável↓public IEnumerable
<string> BlackAndWhite (){rendimento retorno "preto";rendimento retornar "cinza";rendimento retorno
"branco";}}programa de aula{static void Main (){MyClass mc = new MyClass ();Use o objeto de
classe.↓foreach (sombra de corda em mc)Console.Write ("{0}", sombra);Use o método do iterador de
classe.↓foreach (string shadow in mc.BlackAndWhite ())Console.Write ("{0}", sombra);}}

Página 559

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES529Este código produz a seguinte saída:preto cinza


branco preto cinza brancoA Figura 20-10 ilustra o enumerável genérico produzido pelo iterador
enumerável no código.•O código do iterador é mostrado no lado esquerdo da figura e mostra que seu
tipo de retorno éIEnumerable <string>.•No lado direito da figura, o diagrama mostra que a classe
aninhada implementa ambosIEnumerator <string> e IEnumerable <string>.Figura 20-10. O compilador
produz uma classe que é enumerável e enumeradora. Isso tambémproduz o método BlackAndWhite que
retorna o objeto Enumerable.p

Página 560

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES530Padrões de iterador comunsAs duas seções


anteriores mostraram que você pode criar um iterador para retornar um enumerável ou
umenumerador . A Figura 20-11 resume como usar os padrões de iterador comuns.•Quando você
implementa um iterador que retorna um enumerador, você deve tornar a classeenumerável
implementando GetEnumerator para que ele retorne o enumerador retornado peloiterador. Isso é
mostrado à esquerda da figura.•Em uma classe, quando você implementa um iterador que retorna um
enumerável, você pode tornar issoa própria classe enumerável ou não, tornando-a ou não
implementando GetEnumerator.- Se você implementar GetEnumerator, faça com que ele chame o
método iterator para obter uma instância doclasse gerada automaticamente que implementa
IEnumerable. Em seguida, retorne o enumeradorconstruído por GetEnumerator a partir deste objeto
IEnumerable, conforme mostrado à direita da figura.- Se você não tornar a própria classe enumerável
não implementando GetEnumerator, você podeainda usa o enumerável retornado pelo iterador,
chamando o método iterador diretamente, comomostrado na segunda instrução foreach à direita.Figura
20-11 . Os padrões de iterador comuns
Página 561

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES531Produzindo Enumeráveis e EnumeradoresOs


exemplos anteriores usaram iteradores que retornaram um IEnumerator <T> ou um IEnumerable <T>.
Vocêstambém pode criar iteradores que retornam as versões não genéricas. Os tipos de retorno que
você pode especificar sãoOs seguintes:•IEnumerator <T> (genérico - substitua T por um tipo
real)•IEnumerable <T> (genérico - substitua T por um tipo real)•IEnumerator (não
genérico)•IEnumerable (não genérico)Para os dois tipos de enumerador, o compilador gera uma classe
aninhada que contém oimplementação do enumerador não genérico ou genérico, com o
comportamento especificado pelobloco iterador.Para os dois tipos enumeráveis, ele faz ainda mais. Ele
produz uma classe aninhada que é enumerávele o enumerador. A classe, portanto, implementa a
interface do enumerador e oMétodo GetEnumerator. Observe que GetEnumerator é implementado
como parte da classe aninhada - não como parteda classe envolvente .

Página 562

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES532Produzindo Vários EnumeráveisNo exemplo a seguir,


a classe ColorCollection tem dois iteradores enumeráveis - um enumerandoos itens em ordem
progressiva e os demais enumerando-os na ordem inversa. Observe que emboratem dois métodos que
retornam enumeráveis, a própria classe não é enumerável, pois nãoimplementar GetEnumerator.using
System;using System.Collections.Generic;// Você precisa deste namespace.namespace
ColorCollectionIterator{classe ColorCollection{string [] Colors = {"Vermelho", "Laranja", "Amarelo",
"Verde", "Azul", "Roxo"};public IEnumerable <string> Forward () {// Enumerable iteratorpara (int i = 0; i
<Colors.Length; i ++)rendimento retorno Cores [i];}public IEnumerable <string> Reverse () {//
Enumerable iteratorpara (int i = Colors.Length - 1; i> = 0; i--)rendimento retorno Cores [i];}}Baixe a partir
de Wow! e-book <www.wowebook.com>

Página 563

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES533programa de aula{static void Main (){ColorCollection


cc = novo ColorCollection ();Retorne enumerable para a instrução foreach↓foreach (cor da string em
cc.Forward ())Console.Write ("{0}", cor);Console.WriteLine ();Retorne enumerable para a instrução
foreach↓foreach (cor da string em cc.Reverse ())Console.Write ("{0}", cor);Console.WriteLine ();// Pule o
foreach e use manualmente o enumerável e o enumerador.IEnumerable <string> ieable = cc.Reverse
();IEnumerator <string> ieator = ieable.GetEnumerator ();while (ieator.MoveNext ())Console.Write ("{0}",
ieator.Current);Console.WriteLine ();}}}Este código produz a seguinte saída:Vermelho Laranja Amarelo
Verde Azul RoxoRoxo Azul Verde Amarelo Laranja VermelhoRoxo Azul Verde Amarelo Laranja Vermelho

Página 564

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES534Produção de múltiplos enumeradoresO exemplo


anterior usou iteradores para produzir uma classe com dois enumeráveis. Este exemplo mostra
doiscoisas. Primeiro, ele usa iteradores para produzir uma classe com dois enumeradores. Em segundo
lugar, mostra como os iteradorespodem ser implementados como propriedades em vez de métodos.O
código declara duas propriedades que definem dois enumeradores diferentes. O método
GetEnumeratorretorna um ou outro dos dois enumeradores, dependendo do valor da variável
booleanaColorFlag. Se ColorFlag for verdadeiro, o enumerador Colors será retornado. Caso contrário, o
BlackAndWhiteenumerador é retornado.class MyClass: IEnumerable <string>{bool ColorFlag =
true;public MyClass (sinalizador bool)// Construtor{ColorFlag = bandeira;}IEnumerator <string>
BlackAndWhite// Propriedade - iterador do enumerador{obter{rendimento retorno "preto";rendimento
retornar "cinza";rendimento retorno "branco";}}IEnumerator <string> Cores// Propriedade - iterador do
enumerador{obter{string [] theColors = {"blue", "red", "yellow"};para (int i = 0; i <theColors.Length; i +
+)rendimento retorna as cores [i];}}

Página 565

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES535public IEnumerator <string> GetEnumerator () //


GetEnumerator{retornar ColorFlag? Cores// Retorna o enumerador de cores: Preto e branco;// Retorna
o enumerador
BlackAndWhite}System.Collections.IEnumeratorSystem.Collections.IEnumerable.GetEnumerator ()
{retornar ColorFlag? Cores// Retorna o enumerador de cores: Preto e branco;// Retorna o enumerador
BlackAndWhite}}programa de aula{static void Main (){MyClass mc1 = new MyClass (true);// Chame o
construtor com trueforeach (string s em mc1)Console.Write ("{0}", s);Console.WriteLine ();MinhaClasse
mc2 = nova MinhaClasse (falso); // Chame o construtor com falseforeach (string s em mc2)Console.Write
("{0}", s);Console.WriteLine ();}}Este código produz a seguinte saída:azul vermelho amarelopreto cinza
branco

Página 566

CAPÍTULO 20 ■ ENUMERADORES E ITERADORES536Nos bastidores com iteradoresA seguir estão


algumas outras coisas importantes que você deve saber sobre os iteradores:•Os iteradores exigem o
namespace System.Collections.Generic, então você deve incluí-lo com umusando a diretiva.•Nos
enumeradores gerados pelo compilador, o método Reset não é suportado. Está implementado,uma vez
que é exigido pela interface, mas a implementação lança umExceção System.NotSupportedException se
for chamado. Observe que o método Reset é mostradoesmaecido na Figura 20-9.Nos bastidores, a classe
de enumerador gerada pelo compilador é uma máquina de estado comquatro estados:Antes : o estado
inicial antes da primeira chamada para MoveNext.Em execução : o estado inserido quando MoveNext é
chamado. Enquanto neste estado, o enumerador determinae define a posição para o próximo item. Ele
sai do estado quando encontra um retorno de rendimento, um rendimentoquebra, ou o fim do corpo do
iterador.Suspenso : o estado em que a máquina de estado está aguardando a próxima chamada para
MoveNext.Depois : o estado em que não há mais itens para enumerar.Se a máquina de estado estiver
nos estados anterior ou suspenso e houver uma chamada para o MoveNextmétodo, ele entra no estado
de execução. No estado de execução , ele determina o próximo item na coleçãoe define a posição.Se
houver mais itens, a máquina de estado entrará no estado suspenso . Se não houver mais itens,vai para
o estado posterior , onde permanece. A Figura 20-12 mostra a máquina de estado.Figura 20-12 . Uma
máquina de estado iteradora
Página 567

CAPÍTULO 21■ ■ ■537Introdução ao LINQ■ O que é LINQ?■ Provedores LINQ■ Sintaxe de consulta e
sintaxe de método■ Variáveis de consulta■ A estrutura das expressões de consulta■ Os operadores de
consulta padrão■ LINQ to XML

Página 568

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ538O que é LINQ?Em um sistema de banco de dados relacional,


os dados são organizados em tabelas normalizadas e acessados com umlinguagem de consulta simples,
mas poderosa - SQL. SQL pode funcionar com qualquer conjunto de dados em um banco de dados
porque oos dados são organizados em tabelas, seguindo regras rígidas.Em um programa, ao contrário de
um banco de dados, no entanto, os dados são armazenados em objetos de classe ou estruturas que são
todosmuito diferente. Como resultado, não existe uma linguagem de consulta geral para recuperar
dados de dadosestruturas. O método de recuperação de dados de objetos sempre foi projetado de
forma personalizada como parte doprograma. O LINQ, no entanto, facilita a consulta de coleções de
objetos.A seguir estão as características importantes de alto nível do LINQ:•LINQ significa Language
Integrated Query e é um link pronunciado .•LINQ é uma extensão do .NET Framework que permite
consultar coleções de dados em ummaneira semelhante ao uso de SQL para consultar bancos de
dados.•Com o LINQ, você pode consultar dados de bancos de dados, coleções de objetos de programa,
documentos XML,e mais.O código a seguir mostra um exemplo simples de uso do LINQ. Neste código, a
fonte de dados sendoconsultado é simplesmente uma matriz de ints. A definição da consulta é a
declaração com o de e selecionepalavras-chave. Embora a consulta seja definida nesta instrução, ela é
realmente executada e usada noforeach declaração na parte inferior.static void Main (){números int [] =
{2, 12, 5, 15};// Fonte de dadosIEnumerable <int> lowNums =// Defina e armazene a consulta.de n em
númerosonde n <10selecione n;foreach (var x em lowNums)// Execute a consulta.Console.Write ("{0},",
x);}Este código produz a seguinte saída:2, 5,

Página 569

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ539Provedores LINQNo exemplo anterior, a fonte de dados era


simplesmente uma matriz de ints, que é um objeto na memória deo programa. LINQ, no entanto, pode
funcionar com muitos tipos diferentes de fontes de dados, como SQLbancos de dados, documentos XML
e uma série de outros. Para cada tipo de fonte de dados, no entanto, sob oabrange deve haver um
módulo de código que implementa as consultas LINQ em termos dessa fonte de dadostipo. Esses
módulos de código são chamados de provedores LINQ . Os pontos importantes sobre os provedores
LINQ sãoOs seguintes:•A Microsoft fornece provedores LINQ para vários tipos de fontes de dados
comuns, conforme mostrado emFigura 21-1.•Você pode usar qualquer linguagem habilitada para LINQ
(C # em nosso caso) para consultar qualquer tipo de fonte de dados para o qualexiste um provedor
LINQ.•Novos provedores LINQ são constantemente produzidos por terceiros para todos os tipos de
dadostipos de fonte.Figura 21-1. A arquitetura do LINQ, as linguagens habilitadas para LINQ e os
provedores LINQExistem livros inteiros dedicados ao LINQ em todas as suas formas e sutilezas, mas isso
está claramente além doâmbito deste capítulo. Em vez disso, este capítulo irá apresentá-lo ao LINQ e
explicar como usá-lo comobjetos de programa (LINQ to Objects) e XML (LINQ to XML).

Página 570

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ540Tipos anônimosAntes de entrar nos detalhes dos recursos de


consulta do LINQ, começarei cobrindo um recurso de linguagem quepermite que você crie tipos de
classes sem nome. Esses são chamados, não surpreendentemente, de tipos anônimos .No Capítulo 6,
cobrimos os inicializadores de objeto , que é a construção que permite inicializar ocampos e
propriedades de uma nova instância de classe ao usar uma expressão de criação de objeto. Só para
lembrarvocê, este tipo de expressão de criação de objeto consiste em três componentes: a palavra-chave
novo, a classenome ou construtor e o inicializador de objeto. O inicializador de objetos consiste em uma
lista separada por vírgulasde inicializadores de membro entre um conjunto de chaves.A criação de uma
variável de tipo anônimo usa o mesmo formulário, mas sem o nome da classe ouconstrutor. A linha de
código a seguir mostra a forma de expressão de criação de objeto de um tipo anônimo:Inicializador de
objeto↓novo {FieldProp = InitExpr, FieldProp = InitExpr, ...}↑↑Inicializador de membroInicializador de
membroO código a seguir mostra um exemplo de criação e uso de um tipo anônimo. Ele cria uma
variávelchamado de aluno, com um tipo anônimo que possui três propriedades de string e uma
propriedade int. Aviso ema instrução WriteLine de que os membros da instância são acessados como se
fossem membros de umtipo nomeado.static void Main (){var student = new {LName = "Jones", FName =
"Mary", Age = 19, Major = "History"};↑↑Deve usar varInicializador de objetoConsole.WriteLine ("{0} {1},
Idade {2}, Principal: {3}",aluno.FNome, aluno.LNome, aluno.Idade, aluno.Maior);}Este código produz a
seguinte saída:Mary Jones, 19 anos, Principal: HistóriaO que você deve saber sobre os tipos anônimos é
o seguinte:•Tipos anônimos podem ser usados apenas com variáveis locais - não com membros da
classe.•Visto que um tipo anônimo não tem um nome, você deve usar a palavra-chave var como a
variáveltipo.

Página 571

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ541Quando o compilador encontra o inicializador de objeto de


um tipo anônimo, ele cria uma nova classedigite com um nome privado que ele constrói. Para cada
inicializador de membro, ele infere seu tipo e cria umvariável privada desse tipo na nova classe e cria
uma propriedade de leitura / gravação para acessar a variável.A propriedade tem o mesmo nome do
inicializador de membro. Depois que o tipo anônimo é construído,o compilador cria um objeto desse
tipo.Além da forma de atribuição de inicializadores de membro, inicializadores de objeto de tipo
anônimo tambémpermitem duas outras formas: identificadores simples e expressões de acesso de
membro. Essas duas formas sãochamados inicializadores de projeção . A seguinte declaração de variável
mostra todos os três formulários. O primeiroo inicializador de membros está no formulário de atribuição.
O segundo é um identificador e o terceiro é um membroexpressão de acesso.var student = new {Age =
19, Major, Other.Name};Por exemplo, o código a seguir usa todos os três tipos. Observe que os
inicializadores de projeção devem serdefinido antes da declaração do tipo anônimo. Principal é uma
variável local e Nome é um campo estáticoda classe Outro.classe Outro{string pública estática Name =
"Mary Jones";}programa de aula{static void Main (){string Major = "História";Formulário de
atribuiçãoIdentificador↓↓var student = new {Age = 19, Other.Name, Major};↑Acesso de
membroConsole.WriteLine ("{0}, Idade {1}, Principal: {2}",aluno.Nome, aluno.Idade, aluno.Maior);}}Este
código produz a seguinte saída:Mary Jones, 19 anos, Principal: HistóriaA forma do inicializador de
projeção do inicializador de objeto que acabou de ser mostrado tem exatamente o mesmo resultado que
oformulário de atribuição mostrado aqui:var student = new {Age = Age, Name = Other.Name, Major =
Major};Embora seu código não possa ver o tipo anônimo, ele é visível para navegadores de objetos. Se o
compiladorencontra outro tipo anônimo com os mesmos nomes de parâmetro, com os mesmos tipos
inferidos ena mesma ordem, ele reutilizará o tipo e criará uma nova instância - não criará um novo tipo
anônimo.

Página 572

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ542Sintaxe de consulta e sintaxe de métodoExistem duas formas


sintáticas que você pode usar ao escrever consultas LINQ - sintaxe de consulta e sintaxe de método.•A
sintaxe de consulta é uma forma declarativa que se parece muito com uma instrução SQL. A sintaxe da
consulta éescrito na forma de expressões de consulta .•A sintaxe do método é uma forma imperativa ,
que usa invocações de método padrão. Os métodos sãoa partir de um conjunto denominado operadores
de consulta padrão , que será descrito posteriormente neste capítulo.•Você também pode combinar os
dois formulários em uma única consulta.A Microsoft recomenda o uso de sintaxe de consulta porque é
mais legível, afirma mais claramente o seuintenções de consulta e, portanto, é menos sujeito a erros.
Existem alguns operadores, no entanto, que podem serescrito apenas usando a sintaxe do método.Nota
As consultas expressas usando a sintaxe de consulta são traduzidas pelo compilador C # em forma de
invocação de método.Não há diferença no desempenho do tempo de execução entre os dois
formulários.Baixe a partir de Wow! e-book <www.wowebook.com>

Página 573

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ543O código a seguir mostra todos os três formulários de


consulta. Na parte da sintaxe do método, você pode descobrir que oparâmetro do método Where
parece um pouco estranho. É uma expressão lambda, conforme descrito no Capítulo 15.Abordarei seu
uso no LINQ um pouco mais adiante neste capítulo.static void Main (){números int [] = {2, 5, 28, 31, 17,
16, 42};var numsQuery = de n em números// Sintaxe de consultaonde n <20selecione n;var
numsMethod = numbers.Where (x => x <20);// Sintaxe do métodoint numsCount = (de n em números//
Combinadoonde n <20selecione n) .Count ();foreach (var x em numsQuery)Console.Write ("{0},",
x);Console.WriteLine ();foreach (var x em numsMethod)Console.Write ("{0},", x);Console.WriteLine
();Console.WriteLine (numsCount);}Este código produz a seguinte saída:2, 5, 17, 16,2, 5, 17, 16,4

Página 574

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ544Variáveis de ConsultaAs consultas LINQ podem retornar dois


tipos de resultados: uma enumeração , que lista os itens que atendem à consultaparâmetros; ou um
único valor, chamado escalar , que é alguma forma de resumo dos resultados que satisfizeramA
pergunta.No código de exemplo a seguir, acontece o seguinte:•A primeira instrução cria uma matriz de
ints e a inicializa com três valores.•A segunda instrução retorna um objeto IEnumerable, que pode ser
usado para enumerar os resultadosda consulta.•A terceira instrução executa uma consulta e, em
seguida, chama um método (Count) que retorna a contagem deos itens retornados da consulta.
Abordaremos operadores que retornam escalares, como Count, mais tardeno capítulo.números int [] =
{2, 5, 28};IEnumerable <int> lowNums = de n em números // Retorna um enumeradoronde n
<20selecione n;int numsCount= (de n em números // Retorna um intonde n <20selecione n) .Count ();A
variável à esquerda do sinal de igual é chamada de variável de consulta . Embora os tipos devariáveis de
consulta são fornecidas explicitamente nas instruções de exemplo, você também poderia ter o
compiladorinferir os tipos de variáveis de consulta usando a palavra-chave var no lugar dos nomes de
tipo.É importante entender o conteúdo das variáveis de consulta. Depois de executar o código anterior,a
variável de consulta lowNums não contém os resultados da consulta. Em vez disso, ele contém um
objeto do tipoIEnumerable <int>, que pode realizar a consulta se for solicitado a fazê-lo posteriormente
no código. Inquerira variável numsCount, no entanto, contém um valor inteiro real, que pode ter sido
obtido apenas porrealmente executando a consulta.

Página 575

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ545As diferenças no tempo de execução das consultas podem ser
resumidas da seguinte forma:•Se uma expressão de consulta retornar uma enumeração, a consulta não
será executada até que a enumeração sejaprocessado.- Se a enumeração for processada várias vezes, a
consulta será executada várias vezes.- Se os dados mudarem entre o momento em que a enumeração é
produzida e o momento em que a consulta éexecutada, a consulta é executada nos novos dados.•Se a
expressão da consulta retornar um escalar, a consulta será executada imediatamente e o resultado
seráarmazenado na variável de consulta.A Figura 21-2 ilustra isso para a consulta enumerável. A variável
lowNums contém uma referência aoenumerable que pode enumerar os resultados da consulta da
matriz.Figura 21-2. O compilador cria um objeto que implementa IEnumerable <int> e armazena a
consulta emo objeto.

Página 576

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ546A estrutura das expressões de consultaUma expressão de


consulta consiste em uma cláusula from seguida por um corpo de consulta, conforme ilustrado na Figura
21-3. AlgunsUma das coisas importantes a saber sobre as expressões de consulta são as seguintes:•As
cláusulas devem aparecer na ordem mostrada.- As duas partes necessárias são a cláusula from e a
cláusula select ... group.- As outras cláusulas são opcionais.•Em uma expressão de consulta LINQ, a
cláusula select está no final da expressão. Isso é diferentedo que SQL, onde a instrução SELECT está no
início de uma consulta. Uma das razões para usaresta posição em C # permite que o IntelliSense do
Visual Studio forneça mais opções enquantovocê está inserindo o código.•Pode haver qualquer número
de cláusulas from ... let ... where, conforme ilustrado na figura.Figura 21-3. A estrutura de uma instrução
de consulta consiste em uma cláusula from seguida por um corpo de consulta.

Página 577

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ547A cláusula deA cláusula from especifica a coleta de dados que
deve ser usada como fonte de dados. Também apresenta ovariável de iteração. Os pontos importantes
sobre a cláusula from são os seguintes:•A variável de iteração representa sequencialmente cada
elemento na fonte de dados.•A sintaxe da cláusula from é mostrada a seguir, onde- Tipo é o tipo dos
elementos da coleção. Isso é opcional, porque o compilador podeinferir o tipo da coleção.- Item é o
nome da variável de iteração .- Itens é o nome da coleção a ser consultada. A coleção deve ser
enumerável, comodescrito no Capítulo 13.Declaração de variável de iteração↓do tipo de item em itensO
código a seguir mostra uma expressão de consulta usada para consultar uma matriz de quatro ints.
Iteraçãoo item variável representará cada um dos quatro elementos da matriz e será selecionado
ourejeitado pelas cláusulas where e select que o seguem. Este código omite o tipo opcional (int) dea
variável de iteração.int [] arr1 = {10, 11, 12, 13};Variável de iteração↓var query = do item em arr1onde
item <13 ← Usa a variável de iteraçãoSelecionar item;← Usa a variável de iteraçãoforeach (item var na
consulta)Console.Write ("{0},", item);Este código produz a seguinte saída:10, 11, 12,

Página 578

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ548A Figura 21-4 mostra a sintaxe da cláusula from. O


especificador de tipo é opcional, pois pode serinferida pelo compilador. Pode haver qualquer número de
cláusulas de junção opcionais.Figura 21-4. A sintaxe da cláusula fromEmbora haja uma grande
semelhança entre a cláusula LINQ from e a instrução foreach, háExistem várias diferenças
importantes:•A instrução foreach executa seu corpo no ponto do código em que é encontrada. oda
cláusula, por outro lado, não executa nada. Ele cria um objeto enumerável que éarmazenado na variável
de consulta. A própria consulta pode ou não ser executada posteriormente no código.•A instrução
foreach especifica imperativamente que os itens da coleção devem ser consideradosem ordem, do
primeiro ao último. A cláusula from declara declarativamente que cada item noa coleta deve ser
considerada, mas não pressupõe um pedido.

Página 579

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ549A cláusula de adesãoA cláusula de junção em LINQ é muito


parecida com a cláusula JOIN em SQL. Se você estiver familiarizado com junções de SQL, entãoas junções
no LINQ não serão nada novo para você conceitualmente, exceto pelo fato de que agora você pode
executar-los em coleções de objetos, bem como tabelas de banco de dados. Se você é novo em
associações ou precisa de uma atualização, entãoa próxima seção deve ajudar a esclarecer as coisas para
você.As primeiras coisas importantes a saber sobre uma junção são as seguintes:•Uma operação de
junção pega duas coleções e cria uma nova coleção temporária de objetos, ondecada objeto contém
todos os campos de um objeto de ambas as coleções iniciais.•Use uma junção para combinar dados de
duas ou mais coleções.A sintaxe para uma junção é mostrada aqui. Ele especifica que a segunda coleção
deve ser unida aocobrança na cláusula anterior.Palavra-chavePalavra-chavePalavra-chave↓↓↓↓juntar
Identificador em Collection2 em Field1 igual Field2↑↑Especifique a coleção adicionalOs campos para
comparare ID para referenciá-lopela igualdadeA Figura 21-5 ilustra a sintaxe da cláusula de junção.Figura
21-5. Sintaxe para a cláusula de junçãoA seguinte instrução anotada mostra um exemplo da cláusula de
junção:Primeira coleção e ID↓Item da primeira coleção Item da segundavar query = from s em
alunos↓↓junte-se c em studentsInCourses em s.StID é igual a c.StID↑↑Segunda coleção e IDCampos para
comparar

Página 580

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ550O que é uma associação?Uma junção em LINQ pega duas
coleções e cria uma nova coleção onde cada elemento tem membros deos elementos das duas coleções
originais.Por exemplo, o código a seguir declara duas classes: Student e CourseStudent.•Objetos do tipo
Aluno contêm o sobrenome e o número de identificação do aluno.•Objetos do tipo CourseStudent
representam um aluno que está matriculado em um curso e contêm onome do curso e um número de
identificação do aluno.classe pública estudante{public int StID;public string LastName;}classe pública
CourseStudent{public string CourseName;public int StID;}A Figura 21-6 mostra a situação em um
programa onde há três alunos e três cursos, eos alunos estão matriculados em vários cursos. O programa
possui uma matriz chamada alunos, de Alunoobjetos, e uma matriz chamada studentsInCourses, de
objetos CourseStudent, que contém um objeto paracada aluno matriculado em cada curso.Figura 21-6.
Alunos matriculados em vários cursos

Página 581

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ551Suponha agora que você deseja obter o sobrenome de todos
os alunos de um determinado curso. oa matriz students tem os sobrenomes, e a matriz
studentsInCourses tem a inscrição no cursoem formação. Para obter as informações, você deve
combinar as informações nas matrizes, com base nocampo ID do aluno, que é comum a objetos de
ambos os tipos. Você pode fazer isso com uma junção no campo StID.A Figura 21-7 mostra como a
junção funciona. A coluna da esquerda mostra a matriz de alunos e a da direitacoluna mostra a matriz
studentsInCourses. Se pegarmos o primeiro registro do aluno e compararmos sua identificação como ID
do aluno em cada objeto studentsInCourses, descobrimos que dois deles correspondem, conforme
mostrado na parte superior doa coluna central. Se fizermos o mesmo com os outros dois alunos,
descobrimos que o segundo alunoestá fazendo um curso e o terceiro aluno está fazendo dois cursos.Os
cinco objetos acinzentados na coluna do meio representam a junção das duas matrizes no campo StID.
Cadaobjeto contém três campos: o campo LastName da classe Alunos, o campo CourseName daClasse
CourseStudent e o campo StID comum a ambas as classes.Figura 21-7. Duas matrizes de objetos e sua
junção no campo StId

Página 582

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ552O código a seguir reúne todo o exemplo. A consulta encontra
os sobrenomes de todos osalunos fazendo o curso de história.programa de aula{public class Student {//
Declare classes.public int StID;public string LastName;}public class CourseStudent {public string
CourseName;public int StID;}// Inicializa matrizes.static CourseStudent [] studentsInCourses = novo
CourseStudent [] {novo CourseStudent {CourseName = "Arte",StID = 1},novo CourseStudent
{CourseName = "Arte",StID = 2},novo CourseStudent {CourseName = "História", StID = 1},novo
CourseStudent {CourseName = "História", StID = 3},novo CourseStudent {CourseName = "Física", StID =
3},};Aluno estático [] alunos = novo Aluno [] {novo aluno {StID = 1, LastName = "Carson"},novo aluno
{StID = 2, LastName = "Klassen"},novo aluno {StID = 3, LastName = "Fleming"},};static void Main (){//
Encontre os sobrenomes dos alunos que fazem história.var query = from s em alunosjunte-se c em
studentsInCourses em s.StID é igual a c.StIDonde c.CourseName == "História"selecione s.LastName;//
Mostra os nomes dos alunos que fazem história.foreach (var q na consulta)Console.WriteLine ("Aluno
fazendo História: {0}", q);}}Este código produz a seguinte saída:Aluno fazendo História: CarsonAluno
fazendo História: FlemingBaixe a partir de Wow! e-book <www.wowebook.com>

Página 583

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ553A partir de . . . deixei . . . onde seção no corpo da consultaA


seção opcional from ... let ... where é a primeira seção do corpo da consulta. Pode ter qualquer número
dequalquer uma das três cláusulas que o compõem - a cláusula from, a cláusula let e a cláusula where.
Figura21-8 resume a sintaxe das três cláusulas.Figura 21-8. A sintaxe do de. . . deixei . . . cláusula where

Página 584

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ554A cláusula deVocê viu que uma expressão de consulta começa
com uma cláusula required from, que é seguida pelo corpo da consulta.O próprio corpo pode começar
com qualquer número de cláusulas from adicionais, onde cada cláusula from subsequenteespecifica uma
coleta de dados de origem adicional e introduz uma nova variável de iteração para uso em
maisavaliações. A sintaxe e os significados de todas as cláusulas from são os mesmos.O código a seguir
mostra um exemplo desse uso.•A primeira cláusula from é a cláusula necessária da expressão de
consulta.•A segunda cláusula from é a primeira cláusula do corpo da consulta.•A cláusula select cria
objetos de um tipo anônimo.static void Main (){var groupA = novo [] {3, 4, 5, 6};var grupo B = novo [] {6,
7, 8, 9};var someInts = de a no grupo A← Exigido primeiro da cláusulade b no grupo B← Primeira cláusula
do corpo da consultaonde a> 4 && b <= 8selecione novo {a, b, soma = a + b}; ← Objeto de tipo
anônimoforeach (var a em alguns)Console.WriteLine (a);}Este código produz a seguinte saída:{a = 5, b =
6, soma = 11}{a = 5, b = 7, soma = 12}{a = 5, b = 8, soma = 13}{a = 6, b = 6, soma = 12}{a = 6, b = 7, soma =
13}{a = 6, b = 8, soma = 14}

Página 585

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ555A cláusula letA cláusula let leva a avaliação de uma expressão
e a atribui a um identificador para ser usado em outroavaliações. A sintaxe da cláusula let é a
seguinte:let Identifier = ExpressionPor exemplo, a expressão de consulta no código a seguir emparelha
cada membro da matriz groupA comcada elemento do array groupB. A cláusula where elimina cada
conjunto de inteiros das duas matrizes ondea soma dos dois não é igual a 12.static void Main (){var
groupA = novo [] {3, 4, 5, 6};var grupo B = novo [] {6, 7, 8, 9};var someInts = de a no grupo Ade b no
grupo Bdeixe somar = a + b← Armazenar resultado em nova variávelonde soma == 12selecione novo {a,
b, soma};foreach (var a em alguns)Console.WriteLine (a);}Este código produz a seguinte saída:{a = 3, b =
9, soma = 12}{a = 4, b = 8, soma = 12}{a = 5, b = 7, soma = 12}{a = 6, b = 6, soma = 12}

Página 586
CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ556A cláusula whereA cláusula where elimina itens de outras
considerações se eles não atenderem à condição especificada.A sintaxe da cláusula where é a
seguinte:onde BooleanExpressionCoisas importantes que você deve saber sobre a cláusula where:•Uma
expressão de consulta pode ter qualquer número de cláusulas where, contanto que estejam node ...
deixar ... onde seção.•Um item deve satisfazer todas as cláusulas where para evitar a eliminação de uma
consideração posterior.O código a seguir mostra um exemplo de uma expressão de consulta que contém
duas cláusulas where. oonde as cláusulas eliminam cada conjunto de inteiros das duas matrizes onde a
soma das duas não é maiormaior ou igual a 11, e o elemento do grupo A não é o valor 4. Cada conjunto
de elementos selecionados devesatisfazer as condições de ambas as cláusulas where.static void Main ()
{var groupA = novo [] {3, 4, 5, 6};var grupo B = novo [] {6, 7, 8, 9};var someInts = from int a in groupAde
int b no grupo Bdeixe somar = a + bonde soma> = 11← Condição 1onde a == 4← Condição 2selecione
novo {a, b, soma};foreach (var a em alguns)Console.WriteLine (a);}Este código produz a seguinte saída:{a
= 4, b = 7, soma = 11}{a = 4, b = 8, soma = 12}{a = 4, b = 9, soma = 13}

Página 587

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ557O pedido por cláusulaA cláusula orderby pega uma expressão
e retorna os itens de resultado na ordem de acordo com a expressão.A Figura 21-9 mostra a sintaxe da
cláusula orderby. As palavras-chave opcionais ascendentes edescendente define a direção do pedido.
Expressão é geralmente um campo dos itens.•A ordenação padrão de uma cláusula orderby é crescente.
Você pode, no entanto, definir explicitamente oordenação dos elementos para ascendente ou
descendente, usando o ascendente epalavras-chave decrescentes.•Pode haver qualquer número de
cláusulas orderby e elas devem ser separadas por vírgulas.Figura 21-9. A sintaxe da cláusula orderbyO
código a seguir mostra um exemplo de registros de alunos ordenados pelas idades dos alunos. Aviso
prévioque a matriz de informações do aluno é armazenada em uma matriz de tipos anônimos.static void
Main () {var students = new [] // Matriz de objetos de um tipo anônimo{novo {LName = "Jones", FName
= "Mary", Age = 19, Major = "History"},novo {LName = "Smith", FName = "Bob", Age = 20, Major =
"CompSci"},novo {LName = "Fleming", FName = "Carol", Idade = 21, Principal = "História"}};var query =
de aluno em alunosorderby student.Age ← Ordenar por Idade.selecionar aluno;foreach (var s na
consulta) {Console.WriteLine ("{0}, {1}: {2} - {3}",s.LName, s.FName, s.Age, s.Major);}}Este código produz
a seguinte saída:Jones, Mary: 19 - HistóriaSmith, Bob: 20 - CompSciFleming, Carol: 21 - História

Página 588

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ558O seleto. . . cláusula de grupoExistem dois tipos de cláusulas


que compõem a seção do grupo select ... - a cláusula select e agrupo ... por cláusula. Enquanto as
cláusulas que precedem a seção selecionar ... grupo especificam as fontes de dadose quais objetos
escolher, a seção selecionar ... grupo faz o seguinte:•A cláusula select especifica quais partes dos objetos
escolhidos devem ser selecionados. Pode especificarqualquer um dos seguintes:- Todo o item de dados-
Um campo do item de dados- Um novo objeto compreendendo vários campos do item de dados (ou
qualquer outro valor, paraesse assunto).•A cláusula group ... by é opcional e especifica como os itens
escolhidos devem ser agrupados. Bemcobrir o grupo ... por cláusula mais adiante no capítulo.A Figura
21-10 mostra a sintaxe para a cláusula select ... group.Figura 21-10. A sintaxe do select. . . cláusula de
grupo

Página 589

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ559O código a seguir mostra um exemplo do uso da cláusula


select para selecionar todo o item de dados. Primeiro,o programa cria uma matriz de objetos de um tipo
anônimo. A expressão de consulta então usa oselect statement para selecionar cada item da
matriz.using System;usando System.Linq;class Program {static void Main () {var students = new [] //
Matriz de objetos de um tipo anônimo{novo {LName = "Jones", FName = "Mary", Age = 19, Major =
"History"},novo {LName = "Smith", FName = "Bob", Age = 20, Major = "CompSci"},novo {LName =
"Fleming", FName = "Carol", Idade = 21, Principal = "História"}};var query = from s em alunosSelecione%
s;foreach (var q na consulta)Console.WriteLine ("{0}, {1}: Idade {2}, {3}",q.LName, q.FName, q.Age,
q.Major);}}Este código produz a seguinte saída:Jones, Mary: Idade 19, HistóriaSmith, Bob: Idade 20,
CompSciFleming, Carol: Age 21, HistoryVocê também pode usar a cláusula select para escolher apenas
campos específicos do objeto. Por exemplo, oA cláusula select no código a seguir seleciona apenas o
sobrenome do aluno.var query = from s em alunosselecione s.LName;foreach (var q na
consulta)Console.WriteLine (q);Quando você substitui essas duas instruções pelas duas instruções
correspondentes noexemplo completo, o programa produz a seguinte saída:JonesSmithFleming

Página 590

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ560Tipos anônimos em consultasO resultado de uma consulta


pode consistir em itens das coleções de origem, campos dos itens na origemcoleções ou tipos
anônimos.Você pode criar um tipo anônimo em uma cláusula select colocando chaves em torno de uma
vírgulalista separada de campos que você deseja incluir no tipo. Por exemplo, para fazer o código no
anteriorseção selecione apenas os nomes e especializações dos alunos, você pode usar a seguinte
sintaxe:selecione novo {s.LastName, s.FirstName, s.Major};↑Tipo anônimoO código a seguir cria um tipo
anônimo na cláusula select e usa-o posteriormente noInstrução WriteLine.using System;usando
System.Linq;class Program {static void Main (){var students = new [] // Matriz de objetos de um tipo
anônimo{novo {LName = "Jones", FName = "Mary", Age = 19, Major = "History"},novo {LName = "Smith",
FName = "Bob", Age = 20, Major = "CompSci"},novo {LName = "Fleming", FName = "Carol", Idade = 21,
Principal = "História"}};var query = from s em alunosselecione novo {s.LName, s.FName, s.Major};↑Crie
um tipo anônimoforeach (var q na consulta)Console.WriteLine ("{0} {1} - {2}",q.FName, q.LName,
q.Major);}↑}Campos de acesso do tipo anônimoEste código produz a seguinte saída:Mary Jones -
HistóriaBob Smith - CompSciCarol Fleming - História

Página 591

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ561A cláusula do grupoA cláusula group agrupa os objetos


selecionados de acordo com algum critério. Por exemplo, com a matriz dealunos nos exemplos
anteriores, o programa poderia agrupar os alunos de acordo com suas áreas de concentração.O que é
importante saber sobre a cláusula de grupo são os seguintes:•Quando os itens são incluídos no
resultado da consulta, eles são colocados em grupos de acordo com ovalor de um determinado campo.
O valor no qual os itens são agrupados é chamado de chave .•Ao contrário da cláusula select, a cláusula
group não retorna um enumerável que pode enumerar oitens da fonte original. Em vez disso, ele retorna
um enumerável que enumera os grupos deitens que foram formados.•Os próprios grupos são
enumeráveis e podem enumerar os itens reais.Um exemplo da sintaxe da cláusula de grupo é o
seguinte:grupo aluno por aluno. Maior;↑↑Palavra-chavePalavra-chavePor exemplo, o código a seguir
agrupa os alunos de acordo com suas especializações:static void Main (){var students = new [] // Matriz
de objetos de um tipo anônimo{novo {LName = "Jones", FName = "Mary", Age = 19, Major =
"History"},novo {LName = "Smith", FName = "Bob", Age = 20, Major = "CompSci"},novo {LName =
"Fleming", FName = "Carol", Idade = 21, Principal = "História"}};var query = de aluno em alunosgrupo
aluno por aluno. Maior;foreach (var s na consulta)// Enumere os grupos.{Console.WriteLine ("{0}",
s.Key);↑Chave de agrupamentoforeach (var t em s)// Enumere os itens do grupo.Console.WriteLine ("{0},
{1}", t.LName, t.FName);}}

Página 592

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ562Este código produz a seguinte saída:HistóriaJones,


MaryFleming, CarolCompSciSmith, BobA Figura 21-11 ilustra o objeto que é retornado da expressão de
consulta e armazenado novariável de consulta.•O objeto retornado da expressão de consulta é um
enumerável que enumera os gruposresultante da consulta.•Cada grupo é diferenciado por um campo
denominado Chave.•Cada grupo é enumerável e pode enumerar seus itens.Figura 21-11 . A cláusula
group retorna uma coleção de coleções de objetos em vez de uma coleçãode objetos.Baixe a partir de
Wow! e-book <www.wowebook.com>

Página 593

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ563Continuação da ConsultaUma cláusula de continuação de


consulta pega o resultado de uma parte de uma consulta e atribui a ele um nome para que possa
serusado em outra parte da consulta. A Figura 21-12 mostra a sintaxe para a continuação da
consulta.Figura 21-12. A sintaxe da cláusula de continuação da consultaPor exemplo, a consulta a seguir
une groupA e groupB e nomeia a associação groupA eB. Isso entãoexecuta uma seleção simples de
groupA eB.static void Main (){var groupA = novo [] {3, 4, 5, 6};var grupoB = novo [] {4, 5, 6, 7};var
someInts = de a no grupo Ajunte-se a b no grupo B em a igual a bem groupAandB← Continuação da
consultade c no grupo A e Bselecione c;foreach (var a em alguns)Console.Write ("{0}", a);}Este código
produz a seguinte saída:4 5 6

Página 594

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ564Os operadores de consulta padrãoOs operadores de consulta


padrão compreendem um conjunto de métodos chamado interface de programação de aplicativo(API)
que permite consultar qualquer array ou coleção .NET. Características importantes da consulta
padrãooperadores são os seguintes:•Os objetos de coleção consultados são chamados de sequências e
devem implementar o IEnumerable <T>interface, onde T é um tipo.•Os operadores de consulta padrão
usam sintaxe de método.•Alguns operadores retornam objetos IEnumerable (ou outras sequências),
enquanto outros retornam escalares.Os operadores que retornam escalares executam suas consultas
imediatamente e retornam um valor em vez de umobjeto enumerável a ser iterado posteriormente.Por
exemplo, o código a seguir mostra o uso dos operadores Sum e Count, que retornam ints. Aviso prévioo
seguinte sobre o código:•Os operadores são usados como métodos diretamente nos objetos de
sequência , que neste caso énúmeros da matriz.•O tipo de retorno não é um objeto IEnumerable, mas
um int.programa de aula{int estático [] números = novo int [] {2, 4, 6};static void Main (){total int =
números. Soma ();int howMany = numbers.Count ();↑↑ ↑EscalarOperador de
sequênciaobjetoConsole.WriteLine ("Total: {0}, Contagem: {1}", total, comoMuitos);}}Este código produz
a seguinte saída:Total: 12, Contagem: 3

Página 595

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ565Existem 47 operadores de consulta padrão que se enquadram


em 14 categorias diferentes. Essas categorias sãomostrado na Tabela 21-1.Tabela 21-1. Categorias dos
operadores de consulta padrãoNomeNúmero deOperadoresDescriçãoRestrição1Retorna um
subconjunto dos objetos da sequência, com base na seleçãocritérioProjeção2Seleciona quais partes dos
objetos de uma sequência são finalmente retornadasParticionamento4Pula ou retorna objetos de uma
sequênciaJunte-se2Retorna um objeto IEnumerable que une duas sequências, com base emalgum
critérioConcatenação 1Produz uma única sequência de duas sequências separadasEncomenda2Solicita
uma sequência com base nos critérios fornecidosAgrupamento1Agrupa uma sequência com base nos
critérios fornecidosConjunto4Executa operações de conjunto em uma sequênciaConversão7Converte
sequências em várias formas, como matrizes, listas edicionáriosIgualdade1Compara duas sequências
para igualdadeElemento9Retorna um elemento específico de uma sequênciaGeração3Gera
sequênciasQuantificadores3Retorna valores booleanos especificando se um determinado predicado
éverdade sobre uma sequênciaAgregar7Retorna um único valor que representa as características de uma
sequência

Página 596

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ566Expressões de consulta e os operadores de consulta


padrãoConforme mencionado no início do capítulo, cada expressão de consulta também pode ser escrita
usando o métodosintaxe com os operadores de consulta padrão. O conjunto de operadores de consulta
padrão é um conjunto de métodos pararealizando consultas. O compilador traduz cada expressão de
consulta em forma de operador de consulta padrão.Claramente, uma vez que todas as expressões de
consulta são traduzidas nos operadores de consulta padrão - os operadorespode realizar tudo o que é
feito por expressões de consulta. Mas as operadoras também oferecem recursos adicionaisque não
estão disponíveis na forma de expressão de consulta. Por exemplo, operadores Sum e Count, que foram
usados emo exemplo anterior, pode ser expresso apenas usando a sintaxe do método.As duas formas,
expressões de consulta e sintaxe de método, no entanto, podem ser combinadas. Por exemplo, oo
código a seguir mostra uma expressão de consulta que também usa o operador Count. Observe que a
expressão de consultaparte da instrução está entre parênteses, que é seguida por um ponto e o nome
do método.static void Main (){número var = novo int [] {2, 6, 4, 8, 10};int howMany = (de n em
númerosonde n <7selecione n) .Count ();↑↑Operador de expressão de consultaConsole.WriteLine
("Contagem: {0}", howMany);}Este código produz a seguinte saída:Contagem: 3

Página 597

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ567Assinaturas dos Operadores de Consulta PadrãoOs


operadores de consulta padrão são métodos declarados na classe System.Linq.Enumerable. Esses
métodos,no entanto, não são quaisquer métodos - eles são métodos de extensão que estendem a classe
genéricaIEnumerable <T>.Os métodos de extensão foram abordados nos capítulos 7 e 19, mas o mais
importante a lembrarsobre eles é que eles são métodos estáticos públicos que, embora definidos em
uma classe, são projetados paraadicione funcionalidade a uma classe diferente - aquela listada como o
primeiro parâmetro formal. Este parâmetro formaldeve ser precedido pela palavra-chave this.Por
exemplo, a seguir estão as assinaturas de três dos operadores: Count, First e Where. EmÀ primeira vista,
as assinaturas dos operadores podem ser um tanto intimidantes. Observe o seguinte sobreas
assinaturas:•Uma vez que os operadores são métodos genéricos, eles têm um parâmetro genérico (T)
associado comos nomes deles.•Uma vez que os operadores são métodos de extensão que estendem
IEnumerable <T>, eles devem satisfazer aseguintes requisitos sintáticos:- Devem ser declarados públicos
e estáticos.- Eles devem ter este indicador de extensão antes do primeiro parâmetro.- Eles devem ter
IEnumerable <T> como o primeiro tipo de parâmetro.SempreNome ePrimeiropúblico, estáticoparam
genéricoparâmetro↓↓↓public static intCount <T> (esta fonte IEnumerable <T>);public static TPrimeiro <T>
(esta fonte IEnumerable <T>);public static IEnumerable <T> Onde <T> (esta fonte de IEnumerable
<T>, ...);↑↑RetornaExtensãotipoindicador

Página 598

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ568Por exemplo, o código a seguir mostra o uso dos operadores
Count e First. Ambos os operadores tomamapenas um único parâmetro - a referência ao objeto
IEnumerable <T>.•O operador Count retorna um único valor, que é a contagem de todos os elementos
na sequência.•O primeiro operador retorna o primeiro elemento da sequência.As duas primeiras vezes
que os operadores são usados neste código, eles são chamados diretamente, como o normalmétodos,
passando o nome da matriz como o primeiro parâmetro. Nas duas linhas seguintes, no entanto, elessão
chamados usando a sintaxe do método de extensão, como se fossem membros do método da matriz,
que éenumerável. Observe que, neste caso, nenhum parâmetro é fornecido. Em vez disso, o nome da
matriz foi movidoda lista de parâmetros antes do nome do método. Lá, é usado como se contivesse uma
declaração deo método.As chamadas de sintaxe direta e as chamadas de sintaxe de extensão são
completamente equivalentes em efeito - apenas suasa sintaxe é diferente.usando System.Linq;...static
void Main (){int [] intArray = new int [] {3, 4, 5, 6, 7, 9};Array como parâmetro↓var count1 =
Enumerable.Count (intArray); // Chamado diretamentevar firstNum1 = Enumerable.First (intArray); //
Chamado diretamentevar count2 = intArray.Count ();// Chamado como extensãovar firstNum2 =
intArray.First ();// Chamado como extensão↑Array como objeto estendidoConsole.WriteLine ("Contagem:
{0}, FirstNumber: {1}", count1, firstNum1);Console.WriteLine ("Contagem: {0}, FirstNumber: {1}", count2,
firstNum2);}Este código produz a seguinte saída:Contagem: 6, FirstNumber: 3Contagem: 6, FirstNumber:
3
Página 599

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ569Delegados como parâmetrosComo você acabou de ver na


seção anterior, o primeiro parâmetro de cada operador é uma referência a umObjeto IEnumerable <T>.
Os parâmetros seguintes podem ser de qualquer tipo. Muitos operadores tomam genéricosdelegados
como parâmetros. (Delegados genéricos foram explicados no Capítulo 19.) A coisa mais importante
paralembre-se de delegados genéricos como parâmetros é o seguinte:•Delegados genéricos são usados
para fornecer código definido pelo usuário ao operador.Para explicar isso, começarei com um exemplo
que mostra várias maneiras de usar o operador Count.O operador Count está sobrecarregado e possui
dois formulários. O primeiro formulário, que foi usado na anteriorexemplo, tem um único parâmetro,
conforme mostrado aqui:public static int Count <T> (esta fonte de IEnumerable <T>);Como todos os
métodos de extensão, você pode usá-lo na forma de método estático padrão ou na forma de ummétodo
de instância em uma instância da classe que ele estende, conforme mostrado nas duas linhas de código a
seguir:var count1 = Linq.Enumerable.Count (intArray); // Formulário de método estáticovar count2 =
intArray.Count ();// Formulário de método de instânciaNessas duas instâncias, a consulta conta o
número de ints na matriz de inteiros fornecida. Suponha,no entanto, você só deseja contar os elementos
ímpares da matriz. Para fazer isso, você deve fornecer oMétodo de contagem com código que determina
se um inteiro é ímpar.Para fazer isso, você usaria a segunda forma do método Count, que é mostrada a
seguir. Tem umdelegado genérico como seu segundo parâmetro. No momento em que é invocado, você
deve fornecer um objeto delegadoque usa um único parâmetro de entrada do tipo T e retorna um valor
booleano. O valor de retorno doo código de delegado deve especificar se o elemento deve ser incluído
na contagem.public static int Count <T> (esta fonte IEnumerable <T>,Func <T, bool>
predicado);↑Delegado genérico

Página 600

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ570Por exemplo, o código a seguir usa a segunda forma do


operador Count para instruí-lo a incluirapenas os valores que são ímpares. Ele faz isso fornecendo uma
expressão lambda que retorna verdadeiro se oo valor de entrada é ímpar e falso caso contrário. (As
expressões lambda foram abordadas no Capítulo 15.) Em cadaiteração por meio da coleção, Count
chama esse método (representado pela expressão lambda) como valor atual como entrada. Se a entrada
for ímpar, o método retorna verdadeiro e Count inclui o elementono total.static void Main (){int []
intArray = new int [] {3, 4, 5, 6, 7, 9};var countOdd = intArray.Count (n => n% 2 == 1);↑Expressão lambda
que identifica os valores ímparesConsole.WriteLine ("Contagem de números ímpares: {0}",
countOdd);}Este código produz a seguinte saída:Contagem de números ímpares: 4

Página 601

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ571Os tipos de delegado predefinidos do LINQComo o operador


Count do exemplo anterior, muitos dos operadores LINQ exigem que vocêfornecer código que orienta
como o operador realiza sua operação. Você pode fazer isso usando delegateobjetos como
parâmetros.Lembre-se do Capítulo 15 que você pode pensar em um objeto delegado como um objeto
que contém ummétodo ou lista de métodos com uma assinatura específica e tipo de retorno. Quando o
delegado é invocado, oos métodos que ele contém são chamados em sequência.LINQ define duas
famílias de tipos de delegados genéricos para uso com os operadores de consulta padrão. Estessão os
delegados Func e os delegados Action. Cada conjunto possui 17 membros.•Os objetos delegados que
você cria para uso como parâmetros reais devem ser desses tipos de delegado ouesses formulários.•TR
representa o tipo de retorno e é sempre o último na lista de parâmetros de tipo.Os primeiros quatro
delegados Func genéricos são listados aqui. A primeira forma não leva parâmetros de método eretorna
um objeto do tipo de retorno. O segundo usa um único parâmetro de método e retorna um valor,e assim
por diante. Observe que o parâmetro de tipo de retorno tem a palavra-chave out, tornando-o
covariante. Podeportanto, aceite o tipo declarado ou qualquer tipo derivado desse tipo. Os parâmetros
de entrada têm opalavra-chave, tornando-os contravariantes. Eles, portanto, podem aceitar o tipo
declarado, ou qualquer tipo derivadodesse tipo.Delegado público TR Func <out TR>();Função TR do
delegado público <in T1, out TR>(T1 a1);Delegado público TR Func <em T1, em T2, fora TR> (T1 a1, T2
a2);delegado público TR Func <em T1, em T2, em T3, fora TR> (T1 a1, T2 a2, T3 a3);↑↑↑Tipo de
retornoParâmetros de tipoParâmetros do métodoCom isso em mente, se você olhar novamente para a
declaração de Conde, que se segue, você verá que oo segundo parâmetro deve ser um objeto delegado
que leva um único valor de algum tipo T como métodoparâmetro e retorna um valor do tipo bool.public
static int Count <T> (esta fonte IEnumerable <T>,Func <T, bool> predicado);↑ ↑Tipo de parâmetro Tipo de
retornoUm delegado de parâmetro que produz um valor booleano é chamado de predicado .Os
primeiros quatro delegados de ação são os seguintes. Eles são iguais aos delegados Func, excetoque eles
não têm valor de retorno e, portanto, nenhum parâmetro de tipo de valor de retorno. Todos os seus
parâmetros de tiposão contravariantes.delegado público void Ação();delegado público void Ação <em
T1>(T1 a1);delegado público void Ação <em T1, em T2> (T1 a1, T2 a2);delegado público void Ação <em
T1, em T2, em T3> (T1 a1, T2 a2, T3 a3);

Página 602

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ572Exemplo de uso de um parâmetro de delegadoAgora que


você entende melhor a assinatura de Count e o uso do LINQ de parâmetros delegados genéricos, vocêser
capaz de entender melhor um exemplo completo.O código a seguir declara primeiro o método IsOdd,
que usa um único parâmetro do tipo int eretorna um valor booleano informando se o parâmetro de
entrada era estranho. O Método Main faz o seguinte:•Ele declara uma matriz de ints como a fonte de
dados.•Ele cria um objeto delegado chamado MyDel do tipo Func <int, bool> e usa o método IsOdd
parainicializar o objeto delegado. Observe que você não precisa declarar o tipo de delegado Funcporque,
como você viu, já é predefinido pelo LINQ.•Ele chama Count usando o objeto delegado.programa de
aula{static bool IsOdd (int x) // Método a ser usado pelo objeto delegado{return x% 2 == 1; // Retorna
verdadeiro se x for ímpar.}static void Main (){int [] intArray = new int [] {3, 4, 5, 6, 7, 9};Func <int, bool>
meuDel = novo Func <int, bool> (IsOdd); // Delegar objetovar countOdd = intArray.Count (myDel);// Use
delegateConsole.WriteLine ("Contagem de números ímpares: {0}", countOdd);}}Este código produz a
seguinte saída:Contagem de números ímpares: 4Baixe a partir de Wow! e-book <www.wowebook.com>

Página 603

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ573Exemplo usando um parâmetro de expressão LambdaO


exemplo anterior usou um método separado e um delegado para anexar o código ao operador.
estenecessário declarar o método, declarar o objeto delegado e, em seguida, passar o objeto delegado
parao operador. Isso funciona bem e é exatamente a abordagem certa a ser tomada se um dos
seguintesas condições são verdadeiras:•Se o método deve ser chamado de algum outro lugar no
programa que não apenas do lugar em que é usadopara inicializar o objeto delegado•Se o código no
corpo do método for mais do que apenas uma instrução ou duas longasSe nenhuma dessas condições
for verdadeira, no entanto, você provavelmente deseja usar um mais compacto emétodo localizado de
fornecer o código ao operador, usando uma expressão lambda, conforme descrito emCapítulo
15.Podemos modificar o exemplo anterior para usar uma expressão lambda, primeiro excluindo o
método IsOddinteiramente e colocando a expressão lambda equivalente diretamente na declaração do
objeto delegado.O novo código é mais curto e mais limpo e tem a seguinte aparência:programa de
aula{static void Main (){int [] intArray = new int [] {3, 4, 5, 6, 7, 9};Expressão lambda↓var countOdd =
intArray.Count (x => x% 2 == 1);Console.WriteLine ("Contagem de números ímpares: {0}",
countOdd);}}Como no exemplo anterior, este código produz a seguinte saída:Contagem de números
ímpares: 4

Página 604

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ574Também poderíamos ter usado um método anônimo no lugar


da expressão lambda, como mostradoSegue. Isso é mais detalhado, porém, e como as expressões
lambda são equivalentes semanticamente esão menos detalhados, há poucos motivos para usar
métodos anônimos mais.programa de aula{static void Main (){int [] intArray = new int [] {3, 4, 5, 6, 7,
9};Método anônimo↓Func <int, bool> myDel = delegate (int x){return x% 2 == 1;};var countOdd =
intArray.Count (myDel);Console.WriteLine ("Contagem de números ímpares: {0}", countOdd);}}

Página 605

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ575LINQ to XMLExtensible Markup Language (XML) é um método


importante de armazenamento e troca de dados. LINQ adicionarecursos para a linguagem que tornam o
trabalho com XML muito mais fácil do que métodos anteriores, como XPathe XSLT. Se você estiver
familiarizado com esses métodos, ficará satisfeito em saber que LINQ to XMLsimplifica a criação, a
passagem e a manipulação de XML de várias maneiras, incluindo as seguintes:•Você pode criar uma
árvore XML de cima para baixo, com uma única instrução.•Você pode criar e manipular XML na memória
sem ter um documento XML para contera árvore.•Você pode criar e manipular nós de string sem ter um
subnó Texto.Embora eu não vá dar um tratamento completo do XML, começarei dando uma breve
introdução aoantes de descrever alguns dos recursos de manipulação de XML fornecidos pelo
LINQ.Linguagens de marcaçãoUma linguagem de marcação é um conjunto de tags colocadas em um
documento para fornecer informações sobre as informações nodocumento. Ou seja, as tags de marcação
não são os dados do documento - elas contêm dados sobre os dados.Os dados sobre os dados são
chamados de metadados .Uma linguagem de marcação é um conjunto definido de tags projetado para
transmitir tipos específicos de metadados sobreo conteúdo de um documento. HTML, por exemplo, é a
linguagem de marcação mais conhecida. ometadados em suas tags contêm informações sobre como
uma página da web deve ser processada em um navegador ecomo navegar entre as páginas usando os
links de hipertexto.Enquanto a maioria das linguagens de marcação contém um conjunto predefinido de
tags, XML contém apenas algumastags, e o resto são definidos pelo programador para representar
quaisquer tipos de metadados necessáriospor um tipo de documento específico. Contanto que o escritor
e o leitor dos dados concordem com o que as tags significam,as tags podem conter qualquer informação
útil que os designers desejem.

Página 606

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ576XML BasicsOs dados em um documento XML estão contidos


em uma árvore XML, que consiste principalmente em um conjunto de elementos aninhados.O elemento
é o componente fundamental de uma árvore XML. Cada elemento tem um nome e podecontêm dados.
Alguns também podem conter outros elementos aninhados. Os elementos são demarcados pela
abertura etags de fechamento. Quaisquer dados contidos por um elemento devem estar entre suas tags
de abertura e fechamento.•Uma tag de abertura começa com um colchete angular aberto, seguido pelo
nome do elemento, seguidoopcionalmente, por quaisquer atributos, seguidos por um colchete angular
de fechamento.<PhoneNumber>•Uma tag de fechamento começa com um colchete angular aberto,
seguido por um caractere de barra, seguido pelonome do elemento, seguido por um colchete angular de
fechamento.</PhoneNumber>•Um elemento sem conteúdo pode ser representado por uma única tag
que começa com um ângulo abertocolchete, seguido pelo nome do elemento, seguido por uma barra e é
encerrado com umcolchete angular de fechamento.<PhoneNumber />O fragmento XML a seguir mostra
um elemento denominado EmployeeName seguido por um elemento vaziochamado
PhoneNumber.<EmployeeName> Sally Jones </EmployeeName>↑↑↑Tag de aberturaTag de fechamento
de conteúdo<PhoneNumber /> ← Elemento sem conteúdoOutras coisas importantes a saber sobre XML
são as seguintes:•Os documentos XML devem ter um único elemento raiz que contém todos os outros
elementos.•As tags XML devem ser aninhadas corretamente.•Ao contrário das tags HTML, as tags XML
diferenciam maiúsculas de minúsculas.•Os atributos XML são pares de nome / valor que contêm
metadados adicionais sobre um elemento. oparte do valor de um atributo deve sempre estar entre
aspas, que podem seraspas duplas ou aspas simples.•Os espaços em branco em um documento XML são
mantidos. Isso é diferente do HTML, onde o espaço em branco éconsolidado em um único espaço na
saída.

Página 607

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ577O seguinte documento XML é um exemplo de XML que


contém informações sobre doisfuncionários. Esta árvore XML é extremamente simples para mostrar os
elementos com clareza. O importantecoisas a serem observadas sobre a árvore XML são as seguintes:•A
árvore contém um nó raiz do tipo Funcionários que contém dois nós filho do tipo Funcionário.•Cada nó
Employee contém nós que contêm o nome e os números de telefone de um
funcionário.<Empregados><Employee><Name> Bob Smith </Name><PhoneNumber> 408-555-1000
</PhoneNumber><CellPhone /></Employee><Employee><Name> Sally Jones </Name><PhoneNumber>
415-555-2000 </PhoneNumber><PhoneNumber> 415-555-2001
</PhoneNumber></Employee></Employees>A Figura 21-13 ilustra a estrutura hierárquica da árvore
XML de amostra.Figura 21-13. Estrutura hierárquica da árvore XML de amostra
Página 608

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ578As classes XMLLINQ to XML pode ser usado para trabalhar
com XML de duas maneiras. A primeira maneira é como um XML simplificadomanipulação API. A
segunda maneira é usar os recursos de consulta do LINQ que você viu no inícioparte deste capítulo. Vou
começar apresentando o LINQ to XML API.A API LINQ to XML consiste em várias classes que
representam os componentes de uma árvore XML.As três classes mais importantes que você usará são
XElement, XAttribute e XDocument. Ha outroclasses também, mas essas são as principais.Na Figura 21-
13, você viu que uma árvore XML é um conjunto de elementos aninhados. A Figura 21-14 mostra as
classesusado para construir uma árvore XML e como eles podem ser aninhados.Por exemplo, a figura
mostra o seguinte:•Um nó XDocument pode ter o seguinte como seus nós filhos diretos:- No máximo,
um de cada um dos seguintes tipos de nó: um nó XDeclaration, um XDocumentTypenó e um nó
XElement- Qualquer número de nós XProcessingInstruction•Se houver um nó XElement de nível superior
no XDocument, ele é a raiz do restante dos elementosna árvore XML.•O elemento raiz pode, por sua vez,
conter qualquer número de XElement, XComment ouNós XProcessingInstruction, aninhados em
qualquer nível.Figura 21-14. A estrutura de contenção de nós XMLExceto para a classe XAttribute, a
maioria das classes usadas para criar uma árvore XML são derivadas de umclasse chamada XNode e são
referidos genericamente na literatura como "XNodes". A Figura 21-14 mostra oClasses XNode em nuvens
brancas, enquanto a classe XAttribute é mostrada em uma nuvem cinza.

Página 609

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ579Criar, salvar, carregar e exibir um documento XMLA melhor


maneira de demonstrar a simplicidade e o uso da API XML é mostrar exemplos de código simples.Por
exemplo, o código a seguir mostra como é simples realizar várias das tarefas importantesnecessário ao
trabalhar com XML.Ele começa criando uma árvore XML simples que consiste em um nó chamado
Funcionários, com dois subnóscontendo os nomes de dois funcionários. Observe o seguinte sobre o
código:•A árvore é criada com uma única instrução que cria todos os elementos aninhados no local na
árvore.Isso é chamado de construção funcional .•Cada elemento é criado no local usando uma
expressão de criação de objeto, usando o construtor dotipo do nó.Depois de criar a árvore, o código a
salva em um arquivo chamado EmployeesFile.xml, usando Salvar do XDocumentmétodo. Em seguida, ele
lê a árvore XML do arquivo usando o método estático de carregamento do XDocument e atribuia árvore
para um novo objeto XDocument. Finalmente, ele usa WriteLine para exibir a estrutura da árvore
mantida poro novo objeto XDocument.using System;using System.Xml.Linq;// Espaço de nomes
necessárioclass Program {static void Main () {Funcionários XDocument1 =novo XDocument (// Crie o
documento XML.novo XElement ("Funcionários",// Crie o elemento raiz.new XElement ("Name", "Bob
Smith"), // Criar elementonew XElement ("Name", "Sally Jones") // Criar elemento));workers1.Save
("EmployeesFile.xml");// Salvar em um arquivo// Carrega o documento salvo em uma nova
variável.Funcionários do XDocument2 = XDocument.Load ("EmployeesFile.xml");↑Método
estáticoConsole.WriteLine (funcionários2);// Exibir documento}}Este código produz a seguinte
saída:<Empregados><Name> Bob Smith </Name><Name> Sally Jones </Name></Employees>

Página 610
CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ580Criando uma Árvore XMLNo exemplo anterior, você viu que
pode criar um documento XML na memória usandoconstrutores para XDocument e XElement. No caso
de ambos os construtores•O primeiro parâmetro é o nome do objeto.•O segundo parâmetro e os
seguintes contêm os nós da árvore XML. O segundo parâmetrodo construtor é um parâmetro params e,
portanto, pode ter qualquer número de parâmetros.Por exemplo, o código a seguir produz uma árvore
XML e a exibe usando o Console.WriteLinemétodo:using System;using System.Xml.Linq;// Este
namespace é obrigatório.programa de aula{static void Main () {XDocument employeeDoc =novo
XDocument (// Crie o documento.new XElement ("Employees", // Cria o elemento raiz.new XElement
("Employee", // Primeiro elemento de funcionárionovo XElement ("Nome", "Bob Smith"),novo XElement
("PhoneNumber", "408-555-1000")),new XElement ("Employee", // Segundo elemento de
funcionárionovo XElement ("Nome", "Sally Jones"),novo XElement ("PhoneNumber", "415-555-
2000"),novo XElement ("PhoneNumber", "415-555-2001"))));Console.WriteLine (employeeDoc); //
Mostra o documento}}Este código produz a seguinte saída:<Empregados><Employee><Name> Bob
Smith </Name><PhoneNumber> 408-555-1000 </PhoneNumber></Employee><Employee><Name>
Sally Jones </Name><PhoneNumber> 415-555-2000 </PhoneNumber><PhoneNumber> 415-555-2001
</PhoneNumber></Employee></Employees>

Página 611

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ581Usando valores da árvore XMLO poder do XML se torna


evidente quando você atravessa uma árvore XML e recupera ou modifica valores. Tabela21-2 mostra os
principais métodos usados para recuperar dados.Tabela 21-2. Métodos para consultar XMLNome do
MétodoClasseTipo de DevoluçãoDescriçãoNósXdocumentXElementIEnumerable <object>Retorna todos
os filhos do atualnó, independentemente do seu tipoElementosXdocumentXElementIEnumerable
<XElement> Retorna todos os nós atuaisNós filho XElement ou todos os filhosnós com um nome
específicoElementoXdocumentXElementXElementRetorna o primeiro nó atualNó filho XElement ou o
primeiro filhonó com um nome específicoDescendentesXElementIEnumerable <XElement> Retorna todo
o XElement descendentenós ou todos os descendentes XElementnós com um nome
específico,independentemente do seu nível de aninhamentoabaixo do nó atualDescendantsAndSelf
XElementIEnumerable <XElement> O mesmo que Descendants, mas tambéminclui o nó
atualAntepassadosXElementIEnumerable <XElement> Retorna todos os XElement ancestraisnós ou
todos os XElement ancestraisnós acima do nó atual quetem um nome
específicoAncestorsAndSelfXElementIEnumerable <XElement> O mesmo que Ancestors, mas também
incluio nó atualPaiXElementXElementRetorna o nó pai donó atual

Página 612

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ582Algumas das coisas importantes que você deve saber sobre os
métodos da Tabela 21-2 são as seguintes:•Nodes: o método Nodes retorna um objeto do tipo
IEnumerable <object>, porque os nósretornados podem ser de tipos diferentes, como XElement,
XComment e assim por diante. Você pode usar o tipométodo parametrizado OfType < type > para
especificar que tipo de nós retornar. Por exemplo, oa seguinte linha de código recupera apenas os nós
XComment:IEnumerable <XComment> comments = xd.Nodes (). OfType <XComment> ();•Elementos:
Uma vez que recuperar XElements é um requisito comum, há um atalho paraexpression Nodes (). OfType
<XElement> () —o método Elements.- Usar o método Elements sem parâmetros retorna todos os
XElements filhos.- Usar o método Elements com um único parâmetro de nome retorna apenas os
XElements filhoscom esse nome. Por exemplo, a linha de código a seguir retorna todos os nós filhos
XElementcom o nome PhoneNumber .IEnumerable <XElement> empPhones = emp.Elements
("PhoneNumber");•Elemento: este método recupera apenas o primeiro XElement filho do nó atual.
Como oMétodo de elementos, ele pode ser chamado com um ou nenhum parâmetro. Sem parâmetros,
ele obtémo primeiro nó filho XElement. Com um único parâmetro de nome, ele obtém o primeiro nó
filho XElementdesse nome.•Descendentes e ancestrais: esses métodos funcionam como os métodos dos
elementos e dos pais, masem vez de retornar os elementos filho imediatos ou o elemento pai, eles
incluem os elementosabaixo ou acima do nó atual, independentemente da diferença no nível de
aninhamento.Baixe a partir de Wow! e-book <www.wowebook.com>

Página 613

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ583O código a seguir ilustra os métodos Element e


Elements:using System;using System.Collections.Generic;using System.Xml.Linq;class Program {static
void Main () {XDocument employeeDoc =novo XDocument (novo XElement ("Funcionários",novo
XElement ("Funcionário",novo XElement ("Nome", "Bob Smith"),novo XElement ("PhoneNumber", "408-
555-1000")),novo XElement ("Funcionário",novo XElement ("Nome", "Sally Jones"),novo XElement
("PhoneNumber", "415-555-2000"),novo XElement ("PhoneNumber", "415-555-2001"))));Obtenha o
primeiro filho XElement chamado "Funcionários"↓XElement root = employeeDoc.Element
("Funcionários");IEnumerable <XElement> workers = root.Elements ();foreach (XElement emp em
funcionários){Obtenha o primeiro XElement filho denominado "Nome"↓XElement empNameNode =
emp.Element ("Nome");Console.WriteLine (empNameNode.Value);Obtenha todos os elementos filhos
chamados "PhoneNumber"↓IEnumerable <XElement> empPhones = emp.Elements
("PhoneNumber");foreach (telefone XElement em empPhones)Console.WriteLine ("{0}",
phone.Value);}}}Este código produz a seguinte saída:Bob Smith408-555-1000Sally Jones415-555-
2000415-555-2001

Página 614

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ584Adicionando Nós e Manipulando XMLVocê pode adicionar um


elemento filho a um elemento existente usando o método Add. O método Add permite que
vocêadicione quantos elementos desejar em uma única chamada de método, independentemente dos
tipos de nó que você está adicionando.Por exemplo, o código a seguir cria uma árvore XML simples e a
exibe. Em seguida, usa o Adicionarmétodo para adicionar um único nó ao elemento raiz. Depois disso,
ele usa o método Add uma segunda vez paraadicione três elementos - dois XElements e um XComment.
Observe os resultados na saída:using System;using System.Xml.Linq;programa de aula{static void Main ()
{XDocument xd = novo XDocument (// Criar árvore XMLnovo XElement ("root",novo XElement
("primeiro")));Console.WriteLine ("Árvore original");Console.WriteLine (xd); Console.WriteLine (); //
Exibir a árvore.XElement rt = xd.Element ("root");// Obtenha o primeiro elemento.rt.Add (novo
XElement ("segundo"));// Adicione um elemento filho.rt.Add (novo XElement ("terceiro"),// Adicione
mais três filhos.novo XComment ("Comentário importante"),novo XElement
("quarto"));Console.WriteLine ("Árvore modificada");Console.WriteLine (xd);// Exibir árvore modificada}}

Página 615

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ585Este código produz a seguinte saída:<root><primeiro


/></root><root><primeiro /><segundo /><terceiro /><! - Comentário importante -><quarta /></root>O
método Add coloca os novos nós filhos após os nós filhos existentes, mas você pode colocar onós antes e
entre os nós filhos também, usando AddFirst, AddBeforeSelf eMétodos AddAfterSelf.A Tabela 21-3 lista
alguns dos métodos mais importantes de manipulação de XML. Observe que alguns dosos métodos são
aplicados ao nó pai e outros ao próprio nó.Tabela 21-3. Métodos para manipular XMLNome do
MétodoChamada da descriçãoAdicionarPaiAdiciona novos nós filhos após os nós filhos existentes do
atualnóAddFirstPaiAdiciona novos nós filhos antes dos nós filhos existentes do
atualnóAddBeforeSelfNóAdiciona novos nós antes do nó atual no mesmo nívelAddAfterSelfNóAdiciona
novos nós após o nó atual no mesmo nívelRemoverNóApaga o nó atualmente selecionado e seu
conteúdoRemoveNodesNóExclui o XElement atualmente selecionado e seu
conteúdoSetElementPaiDefine o conteúdo de um nóReplaceContentNóSubstitui o conteúdo de um nó

Página 616

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ586Trabalho com atributos XMLOs atributos fornecem


informações adicionais sobre um nó XElement. Eles são colocados na tag de abertura doElemento
XML.Ao construir funcionalmente uma árvore XML, você pode adicionar atributos apenas
incluindoConstrutores XAttribute dentro do escopo do construtor XElement. Existem duas formas
deConstrutor XAttribute; um leva um nome e um valor, e o outro leva uma referência a um jáXAttribute
existente.O código a seguir adiciona dois atributos à raiz. Observe que ambos os parâmetros para o
XAttributeconstrutor são strings; a primeira especifica o nome do atributo e a segunda fornece o
valor.XDocument xd = novo XDocument (Valor do nomenovo XElement ("root", ↓↓new XAttribute
("color", "red"), // Construtor de atributonew XAttribute ("size", "large"), // Construtor de atributonovo
XElement ("primeiro"),novo XElement ("segundo")));Console.WriteLine (xd);Este código produz a
seguinte saída. Observe que os atributos são colocados dentro da aberturatag do elemento.<root color =
"red" size = "large"><primeiro /><segundo /></root>

Página 617

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ587Para recuperar um atributo de um nó XElement, use o


método Attribute, fornecendo o nome deo atributo como o parâmetro. O código a seguir cria uma
árvore XML com um nó com dois atributos—cor e tamanho. Em seguida, ele recupera os valores dos
atributos e os exibe.static void Main (){XDocument xd = novo XDocument (// Criar árvore XMLnovo
XElement ("root",novo XAttribute ("color", "red"),novo XAttribute ("tamanho", "grande"),novo XElement
("primeiro")));Console.WriteLine (xd); Console.WriteLine ();// Exibir árvore XMLXElement rt = xd.Element
("root");// Obtenha o elemento.XAttribute color = rt.Attribute ("color");// Obtenha o atributo.XAttribute
size = rt.Attribute ("size");// Obtenha o atributo.Console.WriteLine ("a cor é {0}", color.Value); // Exibir
atr. valorConsole.WriteLine ("size is {0}", size.Value); // Exibir atr. valor}Este código produz a seguinte
saída:<root color = "red" size = "large"><primeiro /></root>cor é vermelhao tamanho é grande

Página 618

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ588Para remover um atributo, você pode selecionar o atributo e


usar o método Remover ou usar oMétodo SetAttributeValue em seu pai e defina o valor do atributo
como null. O seguinte códigodemonstra os dois métodos:static void Main () {XDocument xd = novo
XDocument (novo XElement ("root",novo XAttribute ("color", "red"),novo XAttribute ("tamanho",
"grande"),novo XElement ("primeiro")));XElement rt = xd.Element ("root");// Obtenha o
elemento.rt.Attribute ("color"). Remove ();// Remova o atributo de cor.rt.SetAttributeValue ("size",
null);// Remova o atributo de tamanho.Console.WriteLine (xd);}Este código produz a seguinte
saída:<root><primeiro /></root>

Página 619

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ589Para adicionar um atributo a uma árvore XML ou alterar o


valor de um atributo, você pode usar oMétodo SetAttributeValue, conforme mostrado no código a
seguir:static void Main () {XDocument xd = novo XDocument (novo XElement ("root",novo XAttribute
("color", "red"),novo XAttribute ("tamanho", "grande"),novo XElement ("primeiro")));XElement rt =
xd.Element ("root");// Obtenha o elemento.rt.SetAttributeValue ("size", "medium"); // Alterar o valor do
atributort.SetAttributeValue ("largura", "estreito"); // Adicione um atributo.Console.WriteLine (xd);
Console.WriteLine ();}Este código produz a seguinte saída:<root color = "red" size = "medium" width =
"narrow"><primeiro /></root>

Página 620

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ590Outros tipos de nósTrês outros tipos de nós usados nos
exemplos anteriores são XComment, XDeclaration eXProcessingInstruction. Eles são descritos nas seções
a seguir.XCommentOs comentários em XML consistem em texto entre os tokens <! - e ->. O texto entre
os tokens éignorado por analisadores XML. Você pode inserir texto em um documento XML usando a
classe XComment, conforme mostrado ema seguinte linha de código:novo XComment ("Este é um
comentário")XDeclarationOs documentos XML começam com uma linha que inclui a versão do XML
usado, o tipo de codificação de caracteresutilizado e se o documento depende de referências externas.
Esta é uma informação sobre o XML, entãona verdade, são metadados sobre os metadados! Isso é
chamado de declaração XML e é inserido usando oClasse XDeclaration. O seguinte mostra um exemplo
de uma declaração XDeclaration:novo XDeclaration ("1.0", "utf-8", "sim")XProcessingInstructionUma
instrução de processamento XML é usada para fornecer dados adicionais sobre como um documento
XML deve serusado ou interpretado. Mais comumente, as instruções de processamento são usadas para
associar uma folha de estilo com oDocumento XML.Você pode incluir uma instrução de processamento
usando o construtor XProcessingInstruction, queaceita dois parâmetros de string - um destino e uma
string de dados. Se a instrução de processamento leva vários dadosparâmetros, esses parâmetros devem
ser incluídos na segunda string de parâmetro doConstrutor XProcessingInstruction, conforme mostrado
no código do construtor a seguir. Observe que nesteexemplo, o segundo parâmetro é uma string literal, e
as aspas duplas literais dentro da string sãorepresentado por conjuntos de duas aspas duplas
contíguas.new XProcessingInstruction ("xml-stylesheet",@ "href =" "histórias" ", type =" "text / css" "")

Página 621

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ591O código a seguir usa todas as três construções:static void
Main (){XDocument xd = novo XDocument (novo XDeclaration ("1.0", "utf-8", "sim"),novo XComment
("Este é um comentário"),new XProcessingInstruction ("xml-stylesheet",@ "href =" "stories.css" "type ="
"text / css" ""),novo XElement ("root",novo XElement ("primeiro"),novo XElement ("segundo")));}Este
código produz a seguinte saída no arquivo de saída. Usar um WriteLine de xd, no entanto,não mostra a
declaração de declaração, embora esteja incluída no arquivo do documento.<? xml version = "1.0"
encoding = "utf-8" standalone = "yes"?><! - Este é um comentário -><? xml-stylesheet href = "stories.css"
type = "text / css"?><root><primeiro /><segundo /></root>

Página 622

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ592Usando consultas LINQ com LINQ to XMLVocê pode combinar
a API LINQ XML com expressões de consulta LINQ para produzir XML simples, mas poderosopesquisas
em árvore.O código a seguir cria uma árvore XML simples, a exibe na tela e a salva em um
arquivochamado SimpleSample.xml. Embora não haja nada de novo neste código, usaremos esta árvore
XML noexemplos a seguir.static void Main (){XDocument xd = novo XDocument (novo XElement
("MyElements",novo XElement ("primeiro",novo XAttribute ("color", "red"),novo XAttribute ("tamanho",
"pequeno")),novo XElement ("segundo",novo XAttribute ("color", "red"),novo XAttribute ("tamanho",
"médio")),novo XElement ("terceiro",novo XAttribute ("color", "blue"),novo XAttribute ("tamanho",
"grande"))));Console.WriteLine (xd);// Exibir árvore XMLxd.Save ("SimpleSample.xml");// Salvar árvore
XML}Este código produz a seguinte saída:<MyElements><primeira cor = "vermelho" size = "pequeno"
/><second color = "red" size = "medium" /><terceira cor = "azul" size = "grande" /></MyElements>Baixe
a partir de Wow! e-book <www.wowebook.com>

Página 623

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ593O código de exemplo a seguir usa uma consulta LINQ simples
para selecionar um subconjunto de nós do XMLárvore e os exibe de várias maneiras. Este código faz o
seguinte:•Ele seleciona na árvore XML apenas os elementos cujos nomes têm cinco caracteres. Desde
onomes dos elementos são primeiro , segundo e terceiro , apenas os nomes dos nós primeiro e terceiro
correspondem aocritério de pesquisa e, portanto, esses nós são selecionados.•Ele exibe os nomes dos
elementos selecionados.•Ele formata e exibe os nós selecionados, incluindo o nome do nó e os valores
doatributos. Observe que os atributos são recuperados usando o método Attribute, e os valores deos
atributos são recuperados com a propriedade Value.static void Main (){XDocument xd =
XDocument.Load ("SimpleSample.xml"); // Coloque o documento.XElement rt = xd.Element
("MyElements");// Obtenha o elemento raiz.var xyz = de e em rt.Elements ()// Selecione os elementos
cujoonde e.Name.ToString (). Length == 5 // nomes têm 5 caracteres.selecione e;foreach (XElement x em
xyz)// Exibem oConsole.WriteLine (x.Name.ToString ());// elementos selecionados.Console.WriteLine
();foreach (XElement x em xyz)Console.WriteLine ("Nome: {0}, cor: {1}, tamanho: {2}",x.Nome,x.Atributo
("cor"). Valor,x.Atributo ("tamanho") .Valor);↑↑}Obtenha o atributo. Obtenha o valor do atributo.Este
código produz a seguinte saída:primeiroterceiroNome: primeiro, cor: vermelho, tamanho:
pequenoNome: terceiro, cor: azul, tamanho: grande

Página 624

CAPÍTULO 21 ■ INTRODUÇÃO AO LINQ594O código a seguir usa uma consulta simples para recuperar
todos os elementos de nível superior da árvore XML ecria um objeto de tipo anônimo para cada um. O
primeiro uso do método WriteLine mostra oformatação padrão do tipo anônimo. A segunda instrução
WriteLine formata explicitamente omembros dos objetos de tipo anônimo.using System;usando
System.Linq;using System.Xml.Linq;static void Main (){XDocument xd = XDocument.Load
("SimpleSample.xml"); // Coloque o documento.XElement rt = xd.Element ("MyElements");// Obtenha o
elemento raiz.var xyz = de e em rt.Elements ()selecione novo {e.Name, color = e.Attribute
("color")};↑foreach (var x em xyz)Crie um tipo anônimo.Console.WriteLine (x);// Formatação
padrãoConsole.WriteLine ();foreach (var x em xyz)Console.WriteLine ("{0, -6}, cor: {1, -7}", x.Name,
x.color.Value);}Este código produz a seguinte saída. As primeiras três linhas mostram a formatação
padrão dotipo anônimo. As últimas três linhas mostram a formatação explícita especificada na string de
formato dosegundo método WriteLine.{Name = first, color = color = "red"}{Nome = segundo, cor = cor =
"vermelho"}{Nome = terceiro, cor = cor = "azul"}primeiro, cor: vermelhosegundo, cor: vermelhoterceiro,
cor: azulA partir desses exemplos, você pode ver que pode combinar facilmente a API XML com a
consulta LINQrecursos para produzir recursos de consulta XML poderosos.

Página 625

CAPÍTULO 22■ ■ ■595Introdução ao AssíncronoProgramação■ Processos, threads e programação


assíncrona■ Loops paralelos■ A classe BackgroundWorker■ Padrões de programação assíncrona■
BeginInvoke e EndInvoke■ Temporizadores

Página 626

CAPÍTULO 22 ■ INTRODUÇÃO À PROGRAMAÇÃO ASSÍNCRONA596Processos, threads e programação


assíncronaNeste capítulo, vamos apresentar quatro métodos que você pode usar para adicionar
multithreading ao seuprogramas. Este capítulo é um pouco diferente dos capítulos anteriores porque vai
além de apenascaracterísticas da linguagem. Em vez disso, também incluiremos classes do BCL e
incluiremos alguma programaçãotécnicas. Apesar do fato de que essas coisas estão um pouco além dos
recursos de linguagem, eu quero fazerisso porque é imperativo que nós, como programadores,
aumentemos nosso uso de multiprocessamento em nosso código—e acho que um primeiro livro sobre C
# é um bom lugar para começar.Quando você inicia um programa, o sistema cria um novo processo na
memória. Um processo é o conjunto derecursos que compõem um programa em execução. Isso inclui o
espaço de endereço virtual, identificadores de arquivo e umhost de outras coisas necessárias para o
programa funcionar.Dentro do processo, o sistema cria um objeto kernel, chamado thread , que
representa o realprograma em execução. ( Thread é a abreviatura de "thread de execução".) Uma vez
que o processo é configurado, o sistemainicia a execução da thread na primeira instrução do método
Main.Algumas coisas importantes a saber sobre os tópicos são:•Por padrão, um processo contém
apenas um único thread, que é executado desde o início doprograma até o fim.•Um thread pode gerar
outros threads de modo que, a qualquer momento, um processo pode ter vários threads emvários
estados, executando diferentes partes do programa.•Se houver vários threads em um processo, todos
eles compartilham os recursos do processo.•São threads, não processos, que são as unidades
programadas pelo sistema para execução emo processador.Todos os programas de amostra mostrados
até agora neste livro usaram apenas um único thread e executaramsequencialmente da primeira
instrução do programa à última. Isso é chamado de programação síncrona .A programação assíncrona
refere-se a programas que geram vários threads, que são, pelo menosconceitualmente, executado ao
mesmo tempo. (Eles não poderia realmente ser executadas ao mesmo tempo.)Se o programa estiver
sendo executado em um sistema multiprocessador, os diferentes threads podem realmente
serexecutando ao mesmo tempo em processadores diferentes. Isso pode melhorar consideravelmente o
desempenho, ecomo processadores multicore se tornam a norma, precisamos escrever nossos
programas para tirar proveito dissooportunidade.Em um sistema de processador único, porém,
claramente apenas uma instrução pode ser executada pelo processadorde uma vez. Neste caso, o
sistema operacional coordena os threads para que o processador seja compartilhadoentre eles. Cada
thread obtém o processador por um curto período de tempo, chamado de intervalo de tempo , antes de
ser iniciadoo processador e enviado para o final da linha. Este compartilhamento round-robin do
processador permite que todos osthreads trabalham seus caminhos através do código.

Página 627

CAPÍTULO 22 ■ INTRODUÇÃO À PROGRAMAÇÃO ASSÍNCRONA597Considerações sobre multithreadingO


uso de vários threads em um programa, chamado multithreading , ou apenas threading , cria uma
sobrecarga do programae complexidade adicional do programa. aqui estão alguns exemplos:•Existem
custos de tempo e recursos tanto na criação quanto na destruição de threads.•O tempo necessário para
agendar threads, carregá-los no processador e armazenar seusestados após cada fração de tempo é pura
sobrecarga.•Uma vez que todos os threads em um processo compartilham os mesmos recursos e heap,
ele adicionacomplexidade de programação para garantir que eles não pisem no trabalho um do
outro.•Depurar programas multithread pode ser bastante difícil, uma vez que o tempo em cada
execução doprograma pode ser diferente, produzindo resultados diferentes. E o ato de executar o
programa em umo depurador acaba com o tempo.Apesar dessas considerações, os benefícios do
threading podem superar seus custos, contanto que seja usadosabiamente - e não usado em demasia.
Por exemplo, você já viu isso em um sistema multiprocessador, se odiferentes threads podem ser
colocados em diferentes processadores, o que pode resultar em uma execução muito mais eficiente.Para
ajudar a aliviar alguns dos custos associados à criação e destruição de threads, o CLRmantém um pool de
threads para cada processo. Inicialmente, o pool de threads de um processo está vazio, mas depois que
uma thread écriado e usado por um processo e, em seguida, o thread conclui sua execução, ele não é
destruído, mas em vez dissoadicionado ao pool de threads do processo. Mais tarde, se o processo
precisar de outro thread, o CLR recicla um dea piscina, economizando uma quantidade significativa de
tempo.Outro exemplo comum onde o multithreading é crucial é na interface gráfica do usuário ( GUI )
programação, em que os usuários esperam uma resposta rápida sempre que clicam em um botão ou
usam o teclado. Noneste caso, se o programa precisa realizar uma operação que vai levar um tempo
considerável,deve realizar essa operação em outro encadeamento, deixando o encadeamento principal
disponível para responder aoentrada do usuário. Seria totalmente inaceitável que o programa não
respondesse durante esse período.

Página 628

CAPÍTULO 22 ■ INTRODUÇÃO À PROGRAMAÇÃO ASSÍNCRONA598A complexidade do


multithreadingEmbora o multithreading seja conceitualmente fácil, acertar todos os detalhes pode ser
frustrantemente difícil emprogramas não triviais. As áreas que precisam ser consideradas são as
seguintes:•Comunicação entre os threads : Existem alguns mecanismos integrados para a
comunicaçãoentre os threads, então isso geralmente é feito simplesmente usando sua memória
compartilhada, já que a memóriao espaço é visível e acessível por todos os threads no mesmo
processo.•Tópicos de coordenação : embora seja fácil criar tópicos, você também precisa ser capaz de
coordenarSuas ações. Por exemplo, um tópico pode precisar esperar que um ou mais outros tópicos
sejam concluídosantes de continuar sua execução.•Sincronização do uso de recursos : Uma vez que
todos os threads em um processo compartilham os mesmos recursos ememória, você precisa se
certificar de que os diferentes threads não estão acessando e alterando-os emao mesmo tempo,
causando inconsistências de estado.O namespace System.Threading contém classes e tipos que você
pode usar para construirsistemas multithread. Isso inclui a própria classe Thread e classes como Mutex,
Semaphore eMonitor, que é usado para sincronizar o uso de recursos. O uso, complexidades e nuances
deste complicadoassunto estão além do escopo deste texto, e seria melhor você se estabelecer com um
aprofundamentolivro sobre o assunto.

Página 629

CAPÍTULO 22 ■ INTRODUÇÃO À PROGRAMAÇÃO ASSÍNCRONA599Loops paralelos.NET 4.0 introduziu


uma nova biblioteca, chamada Biblioteca Paralela de Tarefas, que simplifica muitoprogramação paralela.
Este é um grande avanço e inclui uma grande quantidade de material - muito mais do queEu posso
cobrir neste capítulo. Então, infelizmente, eu tive que resolver apenas aguçando seu
apetiteapresentando apenas duas de suas construções muito simples que você pode aprender e usar de
forma rápida e fácil. Estessão o loop Parallel.For e o loop Parallel.ForEach. Essas construções estão
noNamespace System.Threading.Tasks.A esta altura do livro, tenho certeza de que você está bastante
familiarizado com os loops foreach padrão do C #.Essas são construções comuns e tremendamente
poderosas. Muitas vezes, ao usar essas construções,cada iteração depende de um cálculo ou ação na
iteração anterior. Mas nem sempre é assim.Quando as iterações são independentes, seria uma grande
vantagem se você pudesse colocar iterações diferentesem processadores diferentes e processá-los em
paralelo. Isso é exatamente o que o Parallel.For eAs construções Parallel.ForEach fazem.Essas
construções estão na forma de métodos com parâmetros de entrada. Existem 12 sobrecargas
doParallel.For método, mas o mais simples tem a seguinte assinatura:•O parâmetro fromInclusive é o
primeiro inteiro na série de iterações.•O parâmetro toExclusive é um número inteiro maior que o último
índice da iteraçãoSeries. Ou seja, é o mesmo que comparar no índice de expressão < ToExclusive .•O
corpo é um delegado que recebe um único parâmetro de entrada. O código do corpo é executado uma
vezpor iteração.void Parallel.For (int fromInclusive , int toExclusive , Action body );O código a seguir é um
exemplo usando a construção Parallel.For. Ele itera de 0 a 15 eimprime o índice de iteração e o quadrado
do índice. Observe que se encaixa no requisito de que cadaiteração é independente de qualquer outra
iteração. Observe também que você deve usar oNamespace System.Threading.Tasks.using System;using
System.Threading.Tasks;// Deve usar este namespacenamespace ExampleParallelFor{programa de
aula{static void Main (){Paralelo. Para (0, 15, i =>Console.WriteLine ("O quadrado de {0} é {1}", i, i * i));}}}

Página 630

CAPÍTULO 22 ■ INTRODUÇÃO À PROGRAMAÇÃO ASSÍNCRONA600Uma execução desse código em um


PC com um processador de dois núcleos produziu a seguinte saída. Notar quevocê não tem garantia de
nenhuma ordem particular das iterações.O quadrado de 0 é 0O quadrado de 7 é 49O quadrado de 8 é
64O quadrado de 9 é 81O quadrado de 10 é 100O quadrado de 11 é 121O quadrado de 12 é 144O
quadrado de 13 é 169O quadrado de 3 é 9O quadrado de 4 é 16O quadrado de 5 é 25O quadrado de 6 é
36O quadrado de 14 é 196O quadrado de 1 é 1O quadrado de 2 é 4Outro exemplo é o código a seguir.
Este programa preenche uma matriz inteira, em paralelo, com oquadrado do índice de
iteração.programa de aula{static void Main (){const int maxValues = 50;int [] quadrados = novo int
[maxValues];Parallel.For (0, maxValues, i => quadrados [i] = i * i);}}Ao contrário do exemplo anterior,
embora as iterações possam ser executadas em paralelo e em qualquerordem, o resultado final é uma
matriz contendo os primeiros 50 quadrados.

Página 631

CAPÍTULO 22 ■ INTRODUÇÃO À PROGRAMAÇÃO ASSÍNCRONA601A outra construção de loop paralelo é


o método Parallel.ForEach. Existem mais de uma dúziasobrecargas para este método, mas o mais simples
é o seguinte:•O TSource é o tipo de objeto da coleção.•A fonte é a coleção de objetos TSource.•O corpo
é a expressão lambda a ser aplicada a cada elemento da coleção.static ParallelLoopResult ForEach
<TSource> ( fonte IEnumerable <TSource> ,Action <TSource> body )Um exemplo de uso do método
Parallel.ForEach é o código a seguir. Neste caso, TSource éstring, e a fonte é uma string [].using
System;using System.Threading.Tasks;namespace ParallelForeach1{programa de aula{static void Main ()
{string [] quadrados = nova string []{"Nós", "manter", "estes", "verdades", "para", "ser",
"evidente","aquele", "todos", "homens", "são", "criados", "iguais"};Parallel.ForEach (quadrados,i =>
Console.WriteLine (string.Format ("{0} tem {1} letras", i, i.Length)));}}}Uma execução deste código em um
PC com um processador de dois núcleos produziu a seguinte saída, mas oa ordem pode mudar a cada
vez:"Nós" tem 2 letras"igual" tem 5 letras"verdades" tem 6 letras"para" tem 2 letras"ser" tem 2
letras"aquele" tem 4 letras"hold" tem 4 letras"estes" tem 5 letras"all" tem 3 letras"homens" tem 3
letras"são" tem 3 letras"criado" tem 7 letras"evidente" tem 12 letras

Página 632

CAPÍTULO 22 ■ INTRODUÇÃO À PROGRAMAÇÃO ASSÍNCRONA602A classe BackgroundWorkerEmbora


grande parte da programação assíncrona seja complexa, a classe BackgroundWorker torna-a simplespara
executar uma tarefa em segundo plano em um thread separado. Esta classe foi projetada principalmente
para GUIprogramação (Windows Forms e WPF) para permitir que eles descarreguem tarefas demoradas
do principalthread para um thread de fundo. A Figura 22-1 ilustra os principais membros da classe. O
seguinte é umvisão geral desses membros:•As duas primeiras propriedades mostradas na figura são
usadas para definir se a tarefa em segundo plano poderelatar seu progresso para o thread principal e se
ele suporta o cancelamento do thread principal.Use a terceira propriedade para descobrir se a tarefa em
segundo plano está em execução.•A classe tem três eventos, que são usados para sinalizar diferentes
eventos e estados do programa. Vocêsprecisa escrever manipuladores de eventos para esses eventos
para tomar as ações apropriadas paraseu programa.- O evento DoWork é gerado quando o thread em
segundo plano é iniciado.- O evento ProgressChanged é gerado quando a tarefa em segundo plano relata
o progresso.- O evento RunWorkerCompleted é gerado quando o trabalhador em segundo plano sai.•Os
três métodos são usados para iniciar ações ou alterar o estado.- Chamar o método RunWorkerAsync
recupera um thread de segundo plano que executa o DoWorkmanipulador de eventos.- Chamar o
método CancelAsync define a propriedade CancelamentoPending como verdadeira,
potencialmente,embora não necessariamente, cancelando o tópico. É responsabilidade do evento
DoWorkmanipulador para inspecionar essa propriedade para determinar se ela deve interromper seu
processamento.- O método ReportProgress pode ser chamado pelo manipulador de eventos DoWork (do
plano de fundothread ) quando deseja relatar seu progresso ao thread principal.Figura 22-1. Os
principais membros da classe BackgroundWorkerBaixe a partir de Wow! e-book <www.wowebook.com>

Página 633

CAPÍTULO 22 ■ INTRODUÇÃO À PROGRAMAÇÃO ASSÍNCRONA603Para usar um objeto de classe


BackgroundWorker, você precisa escrever os seguintes manipuladores de eventos. O primeiro
énecessário, pois contém o código que você deseja que seja executado pelo thread de segundo plano,
mas os outros doissão opcionais, dependendo das necessidades do seu programa.•O manipulador
anexado ao evento DoWork contém o código que você deseja executar em segundo planoem um tópico
separado.- Na Figura 22-2, esse manipulador é denominado DoTheWork e está em uma caixa gradiente
sombreada para ilustrarque é executado no segmento separado.- O evento DoWork é gerado quando o
thread principal chama o método RunWorkerAsync.•O manipulador anexado ao evento
ProgressChanged deve conter o código a ser executado nothread principal quando a tarefa em segundo
plano relata seu progresso.- O evento ProgressChanged é gerado quando o processo em segundo plano
chama o ReportProgressmétodo.- Chamar o método ReportProgress é como o thread de segundo plano
se comunica com othread principal.•O manipulador anexado ao evento RunWorkerCompleted deve
conter o código a serexecutado no encadeamento principal após o encadeamento em segundo plano
concluir a execução doManipulador de eventos DoWork.A Figura 22-2 mostra a estrutura do seu
programa, com os manipuladores de eventos anexados aos eventos deo objeto
BackgroundWorker.Figura 22-2. Seu código fornece manipuladores de eventos para os eventos que
controlam o fluxo por meio da execução deas tarefas.

Página 634
CAPÍTULO 22 ■ INTRODUÇÃO À PROGRAMAÇÃO ASSÍNCRONA604Os delegados para esses
manipuladores de eventos são os seguintes. Cada um leva uma referência de objeto como o
primeiroparâmetro e uma subclasse especializada da classe EventArgs como o segundo parâmetro.void
DoWorkEventHandler(remetente do objeto, DoWorkEventArgs e)void ProgressChangedEventHandler
(objeto remetente, ProgressChangedEventArgs e)void RunWorkerCompletedEventHandler (objeto
remetente, RunWorkerCompletedEventArgs e)A Figura 22-3 ilustra a estrutura das classes EventArg
usadas por esses manipuladores de eventos.Figura 22-3. As classes EventArg usadas pelos
manipuladores de eventos BackgroundWorkerQuando você tem os manipuladores de eventos escritos e
anexados a seus eventos, você pode usar a classefazendo o seguinte:•Comece criando um objeto da
classe BackgroundWorker e configurando-o.- Se você deseja que o thread de trabalho comunique a
progressão para o thread principal, defina oPropriedade WorkerReportsProgress para true.- Se você
deseja cancelar o thread de trabalho do thread principal, defina oPropriedade
WorkerSupportsCancellation como true.•Agora que o objeto está configurado, você pode iniciá-lo
chamando o método RunWorkerAsync do objeto.Isso recupera um thread de segundo plano que gera o
evento DoWork e executa o manipulador do eventono fundo.

Página 635

CAPÍTULO 22 ■ INTRODUÇÃO À PROGRAMAÇÃO ASSÍNCRONA605Agora você tem o thread principal e o


thread de segundo plano em execução. Enquanto o fundothread está em execução, você pode continuar
o processamento no thread principal.•No thread principal, se você habilitou a propriedade
WorkerSupportsCancellation, então você pode chamaro método CancelAsync do objeto. Isso não
cancela o thread em segundo plano! Em vez disso, ele define opropriedade CancelamentoPending do
objeto para verdadeiro que precisa ser verificado pelo evento DoWorkcódigo do manipulador em
execução no thread de segundo plano.•O thread de segundo plano, entretanto, continua a realizar suas
tarefas computacionais, tambémfazendo o seguinte:- Se a propriedade WorkerReportsProgress for
verdadeira e o thread em segundo plano tiver progredido pararelatório para o thread principal, então ele
deve chamar ReportProgress do objeto BackgroundWorkermétodo. Quando o thread de segundo plano
chama o método ReportProgress, isso gera oEvento ProgressChanged no thread principal, que executa o
manipulador de eventos correspondente.- Se a propriedade WorkerSupportsCancellation estiver
habilitada, o código do manipulador de eventos DoWorkdeve verificar regularmente a propriedade
CancelamentoPendente para determinar se foicancelado. Nesse caso, ele deve sair.- Se o thread de
segundo plano terminar seu processamento sem ser cancelado, ele pode retornar um resultadoao
thread principal, definindo o campo Result no parâmetro DoWorkEventArgs mostradoanteriormente, na
Figura 22-3.•Quando o thread em segundo plano sai, o evento RunWorkerCompleted é gerado e seu
manipulador éexecutado no thread principal. O parâmetro RunWorkerCompletedEventArgs pode
conterinformações do thread de segundo plano agora concluído, como o valor de retorno e seo tópico
foi cancelado.

Página 636

CAPÍTULO 22 ■ INTRODUÇÃO À PROGRAMAÇÃO ASSÍNCRONA606Exemplo de código usando a classe


BackgroundWorkerEmbora a classe BackgroundWorker tenha sido projetada para programação GUI,
começarei mostrando seu usocom um programa de console, pois é o que usamos ao longo do livro. Na
próxima seção, mostrareium exemplo com um programa GUI.Este programa cria um thread de segundo
plano que soma uma sequência de números. Várias vezes duranteo processo ele verifica para ver se foi
cancelado. Se descobrir que foi cancelado, ele limpa esaídas. Caso contrário, se for concluído, ele
armazena o total no campo Resultado e sai.Enquanto isso, o thread principal soma sua própria sequência
de números e relata seu total, junto com oresultado do thread em segundo plano.using System;using
System.ComponentModel;// Deve ter este namespaceusing System.Threading;// Deve ter este
namespacenamespace ConsoleBackgroundWorker{classe DoBackgroundwork{BackgroundWorker
bgWorker = new BackgroundWorker ();public long BackgroundTotal {get; conjunto privado; }public bool
CompletedNormally {get; conjunto privado; }// Construtorpublic DoBackgroundwork (){// Definir
propriedades de BackgroundWorkerbgWorker.WorkerReportsProgress =
true;bgWorker.WorkerSupportsCancellation = true;// Conecte os manipuladores ao objeto
BackgroundWorker.bgWorker.DoWork+ = DoWork_Handler;bgWorker.ProgressChanged + =
ProgressChanged_Handler;bgWorker.RunWorkerCompleted + = RunWorkerCompleted_Handler;}public
void StartWorker (){if (! bgWorker.IsBusy)bgWorker.RunWorkerAsync ();}// Isso apenas calcula a soma
dos inteiros de 0 ao valor de entrada.public static long CalculateTheSequence (valor longo){total longo =
0;para (int i = 0; i <valor; i ++)total + = i;retorno total;}

Página 637

CAPÍTULO 22 ■ INTRODUÇÃO À PROGRAMAÇÃO ASSÍNCRONA607public void DoWork_Handler


(remetente do objeto, argumentos DoWorkEventArgs){BackgroundWorker worker = remetente como
BackgroundWorker;// Faça o cálculo de fundototal longo = 0;para (int i = 1; i <= 5; i ++){// Cada vez que
passar pelo loop, verifique se fomos canceladosif (trabalhador.CancelaçãoPendente){args.Cancel =
true;worker.ReportProgress (-1);pausa;}outro{// Se não tivermos sido cancelados, continue o
cálculo.total + = CalculateTheSequence (i * 10000000);worker.ReportProgress (i * 20);// Desacelere o
programa para uma taxa de saída mais confortável// apenas para esta demonstração.Thread.Sleep
(300);}}args.Result = total; // Armazene o resultado e saia.}// Manipula a entrada do thread em segundo
plano.private void ProgressChanged_Handler(remetente do objeto, argumentos
ProgressChangedEventArgs){saída de string= args.ProgressPercentage == -1? "Cancelado": string.Format
("{0}% ", args.ProgressPercentage);Console.WriteLine (saída);}// Após a conclusão do thread em segundo
plano, resuma e armazene o resultado.private void RunWorkerCompleted_Handler(remetente do objeto,
args RunWorkerCompletedEventArgs){CompletedNormally =! Args.Cancelled;BackgroundTotal =
args.Cancelled? 0: (longo) args.Result; // Cast do objeto}

Página 638

CAPÍTULO 22 ■ INTRODUÇÃO À PROGRAMAÇÃO ASSÍNCRONA608public void Cancel (){if


(bgWorker.IsBusy)bgWorker.CancelAsync ();}}programa de aula{static void Main ()
{GiveInstructionsToTheUser ();OutputTheSummaryHeaders ();// Criar e iniciar o trabalho de
fundoDoBackgroundwork bgw = new DoBackgroundwork ();bgw.StartWorker ();// Inicie o cálculo no
thread principal. Cada vez através do loop,// verifique se o usuário cancelou o thread em segundo
plano.// Após o cálculo, adicione um breve sono, apenas para desacelerar o programa// baixo o
suficiente para que o thread principal não seja executado mais rápido que o fundo.long mainTotal =
0;para (int i = 0; i <5; i ++){if (Program.CheckForCancelInput ())bgw.Cancel ();mainTotal + =
DoBackgroundwork.CalculateTheSequence (100000000);Thread.Sleep (200);Console.WriteLine ("{0}%", (i
+ 1) * 20);}SummarizeResults (bgw, mainTotal);Console.ReadLine ();}private static void
GiveInstructionsToTheUser (){Console.WriteLine ("Pressione <Enter> para iniciar o trabalho em segundo
plano.");Console.WriteLine ("Pressione <Enter> novamente para cancelar o trabalhador em segundo
plano.");Console.ReadLine ();}private static void OutputTheSummaryHeaders (){Console.WriteLine
("Fundo Principal");Console.WriteLine ("---------------------");}

Página 639

CAPÍTULO 22 ■ INTRODUÇÃO À PROGRAMAÇÃO ASSÍNCRONA609private static void SummarizeResults


(DoBackgroundwork bgw, long mainTotal){if (bgw.CompletedNormally){Console.WriteLine ("\
nBackground concluído normalmente");Console.WriteLine ("Background total = {0}",
bgw.BackgroundTotal);}outro{Console.WriteLine ("\ nBackground cancelado");}Console.WriteLine ("Total
principal = {0}", mainTotal);}private static bool CheckForCancelInput (){bool doCancel =
Console.KeyAvailable;if (doCancel)Console.ReadKey ();return doCancel;}}}Este código produz os
seguintes resultados quando pode ser executado até a conclusão.Pressione <Enter> para iniciar o
trabalhador em segundo plano.Pressione <Enter> novamente para cancelar o trabalhador em segundo
plano.Fundo Principal---------------------20%40%20%60%40%80%60%100%80%100%Fundo concluído
normalmenteTotal de fundo = 2749999925000000Total principal = 24999999750000000

Página 640

CAPÍTULO 22 ■ INTRODUÇÃO À PROGRAMAÇÃO ASSÍNCRONA610Exemplo da classe BackgroundWorker


em um programa WPFComo a classe BackgroundWorker é usada principalmente com programação GUI,
o programa a seguir mostraseu uso em um programa WPF simples, em vez dos programas de console
que usamos ao longo do texto. WPFé o substituto da Microsoft para a estrutura de programação da GUI
do Windows Forms. Para maisinformações sobre programação WPF, consulte meu livro Illustrated WPF ,
também publicado pela Apress.Este programa produz a janela mostrada à esquerda na Figura 22-4.
Quando você clica no processobotão, ele inicia o thread de fundo, que se reporta ao thread principal a
cada meio segundo eincrementa a barra de progresso na parte superior em 10 por cento. Na conclusão,
ele mostra a caixa de diálogo à direitada Figura 22-4.Figura 22-4. O programa WPF de exemplo usando a
classe BackgroundWorkerPara criar este programa WPF no Visual Studio 2010, faça o seguinte:1.
Selecione o item de menu Arquivo ➤ Novo ➤ Projeto, que abre a janela Novo Projeto.2. No painel à
esquerda da janela, abra a seção Modelos Instalados, se não forjá aberto.3. Na categoria C #, clique na
entrada do Windows. Isso preenche o painel central com omodelos de programas do Windows
instalados.4. Clique em Aplicativo WPF e, na parte inferior da janela, insira SimpleWorker noCaixa de
texto do nome. Abaixo disso, selecione um local e clique no botão OK.Existem apenas dois arquivos que
você modificará - MainWindow.xaml e MainWindow.xaml.cs. Modifique o seuArquivo MainWindow.xaml
para corresponder à seguinte listagem:<Window x: Class = "SimpleWorker.MainWindow"xmlns = "
http://schemas.microsoft.com/winfx/2006/xaml/presentation "xmlns: x = "
http://schemas.microsoft.com/winfx/2006/xaml "Title = "MainWindow" Height = "150" Width =
"250"><StackPanel><ProgressBar Name = "progressBar" Height = "20" Width = "200" Margin = "10"
/><Button Name = "btnProcess" Width = "100" Click = "btnProcess_Click"Margin = "5"> Processo
</Button><Button Name = "btnCancel" Width = "100" Click = "btnCancel_Click"Margin = "5"> Cancelar
</Button></StackPanel></Window>

Página 641

CAPÍTULO 22 ■ INTRODUÇÃO À PROGRAMAÇÃO ASSÍNCRONA611Modifique seu arquivo


MainWindow.xaml.cs para corresponder à seguinte listagem:using System.Windows;using
System.ComponentModel;using System.Threading;namespace SimpleWorker{classe pública parcial
MainWindow: Window{BackgroundWorker bgWorker = new BackgroundWorker ();public MainWindow
(){InitializeComponent ();// Definir propriedades de BackgroundWorkerbgWorker.WorkerReportsProgress
= true;bgWorker.WorkerSupportsCancellation = true;// Conecte os manipuladores ao objeto
BackgroundWorker.bgWorker.DoWork+ = DoWork_Handler;bgWorker.ProgressChanged + =
ProgressChanged_Handler;bgWorker.RunWorkerCompleted + = RunWorkerCompleted_Handler;}private
void btnProcess_Click (objeto remetente, RoutedEventArgs e){if (!
bgWorker.IsBusy)bgWorker.RunWorkerAsync ();}private void ProgressChanged_Handler (objeto
remetente,ProgressChangedEventArgs args){progressBar.Value = args.ProgressPercentage;}

Página 642

CAPÍTULO 22 ■ INTRODUÇÃO À PROGRAMAÇÃO ASSÍNCRONA612private void DoWork_Handler (objeto


remetente, argumentos DoWorkEventArgs){BackgroundWorker worker = remetente como
BackgroundWorker;para (int i = 1; i <= 10; i ++){if (trabalhador.CancelaçãoPendente){args.Cancel =
true;pausa;}outro{worker.ReportProgress (i * 10);Thread.Sleep (500);}}}private void
RunWorkerCompleted_Handler (objeto remetente,RunWorkerCompletedEventArgs args)
{progressBar.Value = 0;if (args.Cancelled)MessageBox.Show ("Processo cancelado.", "Processo
cancelado");outroMessageBox.Show ("Processo concluído normalmente.", "Processo
concluído");}private void btnCancel_Click (objeto remetente, RoutedEventArgs e){bgWorker.CancelAsync
();}}}Baixe a partir de Wow! e-book <www.wowebook.com>

Página 643

CAPÍTULO 22 ■ INTRODUÇÃO À PROGRAMAÇÃO ASSÍNCRONA613Padrões de programação


assíncronaNo Capítulo 15, cobrimos o tópico de delegados, e você viu que quando um objeto delegado é
invocado, eleinvoca os métodos contidos em sua lista de invocação. Isso é feito de forma síncrona, como
se os métodosfoi convocado pelo programa.Se um objeto delegado tiver apenas um único método (que
chamarei de método referenciado ) em sua invocaçãolista, ele pode executar esse método de forma
assíncrona. A classe delegada tem dois métodos, chamados BeginInvokee EndInvoke, que são usados
para fazer isso. Você usa esses métodos da seguinte maneira:•Quando você chama o método
BeginInvoke do delegado, ele inicia seu método referenciado executando em umsepare o thread do pool
de threads e retorne imediatamente ao thread inicial. oa thread inicial continua enquanto o método
referenciado é executado em paralelo.•Quando seu programa deseja recuperar os resultados do método
assíncrono concluído, eleverifica a propriedade IsCompleted do IAsyncResult retornado por BeginInvoke
ou chama ométodo EndInvoke do delegado para aguardar a conclusão do delegado.A Figura 22-5 mostra
os três padrões padrão para usar esse processo. Em todos os três padrões, o inicialthread inicia uma
chamada de método assíncrona e, em seguida, faz algum processamento adicional. Os padrõesdiferem,
no entanto, nas formas em que o encadeamento inicial recebe a informação de que o encadeamento
geradoCompletou.•No padrão esperar até que seja feito , depois de gerar o método assíncrono e fazer
algunsprocessamento adicional, o encadeamento inicial para e espera que o encadeamento gerado
termineantes de continuar.•No padrão de pesquisa , o encadeamento inicial verifica periodicamente se o
encadeamento geradoconcluída e, caso não esteja, continua o processamento adicional.•No padrão de
retorno de chamada , o thread inicial continua a execução sem esperar