Você está na página 1de 434

Guia de Estudo do

Programador C #
(MCSD)
Exame: 70-483
-
Ali Asad
Hamza Ali
Guia de Estudo do
Programador C #
(MCSD)
Exame: 70-483

Ali Asad
Hamza Ali

www.allitebooks.com

Guia de Estudo do Programador C # (MCSD) 


Ali Asad \琀 屴 \ 琀 Hamza Ali
Sialkot, Paquistão \ 琀 屴 \ Sialkot, Paquistão
ISBN-13 (pbk): 978-1-4842-2859-3 \ 琀 ISBN-13 (eletrônico): 978-1-4842-2860-9
DOI 10.1007 / 978-1-4842-2860-9
   

Número de controle da Biblioteca do Congresso: 2017944951


Copyright © 2017 por Ali Asad e Hamza Ali
Este trabalho está sujeito a direitos autorais. Todos os direitos são reservados pelo Editor,
independente de todo ou parte do material, especificamente os direitos de tradução,
reimpressão, reutilização de ilustrações, recitação, transmissão, reprodução em microfilmes ou
de qualquer outra forma física, e transmissão ou armazenamento de informações e
recuperação, adaptação eletrônica, software de computador ou por metodologia semelhante ou
diferente agora conhecida ou desenvolvida a seguir.
Nomes, logotipos e imagens com marca registrada podem aparecer neste livro. Em vez de
usar um símbolo de marca registrada em todas as ocorrências de nome, logotipo ou imagem
com marca registrada, usamos os nomes, logotipos e imagens apenas de maneira editorial e
para o benefício do proprietário da marca registrada, sem intenção de violar a marca
registrada.

O uso nesta publicação de nomes comerciais, marcas comerciais, marcas de serviço e termos
semelhantes, mesmo que não sejam identificados como tal, não deve ser tomado como uma
expressão de opinião sobre se eles estão ou não sujeitos a direitos de propriedade.
Embora se acredite que os conselhos e informações contidos neste livro sejam verdadeiros e
precisos na data da publicação, nem os autores, nem os editores nem o editor podem aceitar
qualquer responsabilidade legal por quaisquer erros ou omissões que possam ser feitos. O
editor não oferece garantia, expressa ou implícita, com relação ao material aqui contido.

Imagem da capa criada por FreePik


Diretor-gerente: Welmoed Spahr
Diretor Editorial: Todd Green
Editor de aquisições: Celestin Suresh John
Editor de desenvolvimento: Anila Vincent e Laura Berendson
Revisor técnico: Syed Lakhtey Hussnain
Editor Coordenador: Sanchita Mandal
Editor de Cópia: Larissa shmailo
Compositor: SPi Global
Indexador: SPi Global
Artista: SPi Global
Distribuído para o comércio de livros em todo o mundo pela Springer Science + Business Media New York,
233 Spring Street, 6th Floor, New York, NY 10013. Telefone 1-800-Springer, fax (201) 348-4505,
e-mail orders-ny@springer-sbm.com , ou visita www.springeronline.com . Apress Media, LLC
é uma LLC da Califórnia e o único membro (proprietário) é a Springer Science + Business
Media Finance Inc (SSBM Finance Inc). SSBM Finance Inc é uma empresa de Delaware .
Para obter informações sobre traduções, envie um e-mail para rights@apress.com ou
visite http://www.apress.com/ rights-permission .
Os títulos Apress podem ser adquiridos em massa para uso acadêmico, corporativo ou
promocional. 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, consulte nossa página de vendas em massa de impressão e e-
books em http://www.apress.com/bulk-sales .
Qualquer código fonte ou outro material suplementar referenciado pelo autor neste livro está
disponível para os leitores no GitHub na página de produtos do livro, localizada em
www.apress.com/978-1-4842-2859-3 . Para informações mais detalhadas, visite
http://www.apress.com/source-code .
Impresso em papel sem ácido

www.allitebooks.com

Dedicado à minha família (Mama [ Samina ], Papa [ Asad ], irmão


[ Hamza ], irmãs [ Rimsha, Aima, Azma ]); e ao meu querido
amigo, Sundus Naveed .
Obrigado por me apoiar e acreditar em mim. Mais importante, vocês nunca
tentaram me mudar; em vez disso, você me deu a confiança e a liberdade de
trabalhar nos meus sonhos.
Por isso, sou eternamente grato. Obrigado!!!
- Ali Asad

Dedicado ao meu pai [ Muhammad Arif ], que sempre me incentiva e me


apóia a aprender e fornecer conhecimento, e toda a minha família (Mãe [
Yasmeen Tahira ], meus irmãos [ Adil Ali e Awais Ali ], minha cunhada [
Noureen Azmat ],
minha sobrinha fofa [ Zoha Adil ], minhas irmãs [ Iram Suhaib e Aqsa Hamid ],
e minha amada noiva [ Zunaira Shafqat Ali ].)
Hamza Ali

www.allitebooks.com

Resumo dos conteúdos


S b t i
Sobre os autores xix

Sobre o Revisor Técnico \

Agradecimentos \ ?? xxiii

Introdução \ xxv

Prefácio \ 🏻 xxx

■ ■ Capítulo 1: Princípios básicos de C # \ 1


■ ■ Capítulo 2: Tipos em C # 39

■ ■ Capítulo 3: Introdução à programação orientada a objetos \ 65


■ ■ Capítulo 4: Adiantamento C # \ 95 95

■ ■ Capítulo 5: Delegados e eventos de implementação \ 153


■ ■ Capítulo 6: Mergulhe profundamente no LINQ 177
■ ■ Capítulo 7: Gerenciar o ciclo de vida do objeto 197
■ ■ Capítulo 8: Programação multithread, assíncrona e paralela 207
■ ■ Capítulo 9: Tratamento de exceções e validação de entrada de aplicativos \ 271
■ ■ Capítulo 10: Operações de E / S de arquivo \ 291

■ ■ Capítulo 11: Serialização e desserialização \ 🏻 305


■ ■ Capítulo 12: Dados de consumo \
■ ■ Capítulo 13: Trabalhando com criptografia \ 347

www.allitebooks.com

■ Resumo dos conteúdos


■ ■ Capítulo 14: Montagem e reflexão \ 365
■ ■ Capítulo 15: Depuração e diagnóstico \ 395
■ ■ Capítulo 16: Perguntas sobre os exames práticos \ 423

Índice 467
vi

www.allitebooks.com

Conteúdo

Sobre os autores xix

Sobre o Revisor Técnico \

Agradecimentos \ ?? xxiii

Introdução \ xxv

Prefácio \ 🏻 xxx

■ ■ Capítulo 1: Fundamentos de C # \ 1 Estrutura do Programa e Fundamentos da Linguagem \ 1


Primeiro programa em C # 2 variáveis e tipos de dados \ 5 Operador em C # 6 Expressão em C # \ 7 Tipo de fundição \ 8 var keyword \ 9 Array in C

#\9

Fluxo do Programa de Implementação \ 15

Estrutura de Decisão \ 15

Operadores de Decisão \ 19

Loops em C # \ 20

Instruções de salto em C # \ 23

Métodos em C # \ 28

Argumento nomeado ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 29 Passe por

referência com a palavra-chave ref 30

vii

www.allitebooks.com

■ Conteúdo
Passe por referência com a palavra-chave \ 31 Use a matriz de parâmetros para passar o argumento do método ilimitado 32

Resumo 33

Desafios do código 33
P t b d áti fí i 35
Perguntas sobre o exame de prática física 35

■ ■ Capítulo 2: Tipos em C # 39
Entenda os tipos \ 39

Crie tipos \ 39

Enum \ 40.

Estrutura \ 42.

Classe \ 45

Tipos e gerenciamento de memória \ 48

Tipo de valor 48 Tipo de referência \ 48 Heap \ 48 Stack \ 49 Registo \ acto 50

Tipos especiais em C # \ 50

System.Object Type \ 50

Tipo anônimo 51

Tipo dinâmico \ 52 52

Tipo nulo \ 53

Tipo estático \ 54

Conversão de tipo \ 56

Conversão implícita de tipo 56

Conversão de tipo explícita \ 56

Conversão de tipo definido pelo usuário \ 57

Resumo 59

Desafios do código 60

Perguntas sobre o exame de prática física 60

viii

www.allitebooks.com

■ Conteúdo
■■ Capítulo 3: Introdução à programação orientada a objetos \ 65 Introdução à programação orientada a objetos \ 65 OOP em uma TORTA 66

Encapsulamento 66

Especificadores de acesso \ 66 66 Proteção de Dados \ 70

Herança \ 75

Herança Multi Nível \ 76

Classe abstrata \ 77 Interface \ 78

Implemente a interface de forma implícita 79 Implementar interface explicitamente \ 80

Polimorfismo \ 81

Polimorfismo estático \ 81 Polimorfismo Dinâmico 87

Resumo 89

D fi d ódi 90
Desafios do código 90

Perguntas sobre o exame de prática física 91

■ ■ Capítulo 4: Adiantamento C # \ 95 95
Boxe e desembalagem 95

Boxing 95 95

Unboxing 96

Desempenho do Boxe e Desembalagem 97

Genéricos \ 97

Restrições aos parâmetros genéricos de tipo tem 98 Métodos Genéricos 104

Coleção \ 106

System.Collections \ 106

System.Collections.Generics \ 112

System.Collections.Concurrent \ 120

ix

www.allitebooks.com

■ Conteúdo
Implementar a interface da estrutura tem ter 120

IEnumerable & IEnumerable <T> \ 120

IEnumerator & IEnumerator <T> \ 124

ICollection & ICollection <T> \ 129

IList & IList <T> \ 130

IComparável e IComparável <T> \ 132

IComparer & IComparer <T> \ 135

IEquatable <T> 138

Trabalhando com Strings 140

StringBuilder 141

StringReader 141

StringWriter \ 142

Enumerar métodos de string tenha ter 143 143

Método String.Format 147

Resumo 150

Desafios do código 150

Perguntas sobre o exame de prática física 150

■ ■ Capítulo 5: Delegados e eventos de implementação \ 153


Delegado \ 153

Delegado Multicast 154 Common Built-in Delegados 157 157 Variação de delegado 161 Problemas com o Delegado \ 163

Método Anônimo 164

E ã L bd \ 166
Expressão Lambda \ 166

Evento \ 168

Use delegados internos para implementar eventos \ 170 vantagens de eventos \ 174

Resumo 174

Desafios do código 175

Perguntas sobre o exame de prática física 175

www.allitebooks.com

■ Conteúdo
■ ■ Capítulo 6: Mergulhe profundamente no LINQ 177
Introdução ao LINQ \ 177

Por que usamos o LINQ 178

Entendendo os operadores LINQ 178

Operador de filtragem 179

Operador de projeção 179

Operador de Junta \ 179

Operador de agrupamento \ 🏻 180


Operador de partição \ 180

Agregação \ 180

Entenda a sintaxe LINQ 181

Sintaxe do método 181 Query Syntax \ 182

Trabalhando com consultas LINQ \ 183

Recursos C # para suportar LINQ \ 183 Partes da Operação de Consulta \

⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀ 186

LINQ to XML 191

Crie dados XML \ 191 Atualizar dados XML \ 192 Ler dados XML \ 193

Resumo 194

Desafios do código 195

Perguntas sobre o exame de prática física 195

■ ■ Capítulo 7: Gerenciar o ciclo de vida do objeto 197


Fundamentos do ciclo de vida do objeto 197

Criação de um objeto \ 197 Exclusão de um Objeto \ 197

XI
■ Conteúdo
Fundamentos da coleção de lixo .NET 198

Quando a coleta de lixo é executada \ 198

Coletor de lixo e pilha gerenciada \ 198

Gerações \ 198

Passos envolvidos na coleta de lixo \ 199

Gerencie Recursos Não Gerenciados \ 199

Implementar IDisposable para liberar recursos não gerenciados \ foi pode ter sido um `` problema '' 199 Dispor dentro de tentativa / finalmente

Bloquear \ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 201 201 Padrão Descartável \

202

Vazamentos de memória 203

Gerencie vazamentos de memória \ 203

Resumo 204

Desafios do código 204

Perguntas sobre o exame de prática física 204

■■ Capítulo 8: Programação multithread, assíncrona e paralela 207


Trabalhando com Threads \ 207

Crie e inicie um thread 208 Thread.Join () 210 Thread de primeiro plano e fundo 211 Passar um método de parametrização para a Thread

⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ \ 215 ThreadPriority \ 216 ThreadStatic \ 218

Thread Pool 221

Trabalhando com tarefas \ 223

Crie e execute uma tarefa \ 224 Criar e executar uma tarefa <Result> \ 228 Aguardar uma ou mais tarefas \ 234 Tarefas Múltiplas em Cadeia com

Continuações \ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀ 243

xii

■ Conteúdo
Sincronização de variáveis em multithreading \ 245

Dead Lock tem 249

CancelamentoToken \ 251

Tornando a interface do usuário responsiva \ 253

Como tornar a interface do usuário responsiva com Async e Await \

⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 259

Programação Paralela 260

Coleção Concorrente \ 260

Parallel.For & Parallel.Foreach \ 263

PLINQ \ 265

Resumo 266

Desafios do código 267

Perguntas sobre o exame de prática física 267


■■ Capítulo 9: Tratamento de exceções e validação de entrada de aplicativo \ 271
Introdução à Exceção \ 271 Manipulando Exceção 272

try-catch \ 272 try-catch ( ExceptionType ex) \ 273 try-catch (ExceptionType) \ 275 try-catch-finalmente 276 tentativa-finalmente \ 278 usar blocos

de captura múltipla de lidar com várias excepções 279

Jogando Exceções \ 280

Re-lançando uma exceção \ 282 Lançando uma exceção com uma exceção interna \ 283

Criando exceções personalizadas \ 284 Validação de entrada de aplicativo 285

Expressões regulares \ 285

Resumo 289

Desafios do código 289

Perguntas sobre o exame de prática física 290

xiii

■ Conteúdo
■ ■ Capítulo 10: Operações de E / S de arquivo \ 291
Trabalhando com o Drive \ 291 Trabalhando com Diretórios 292

Diretório e DirectoryInfo \ 292

Trabalhando com arquivos 294

File and FileInfo \

Trabalhando com Stream 295

FileStream \ 296

MemoryStream 298

Fluxo Buffered \ 298

Trabalhando com o File Reader and Writer 299

StringReader e StringWriter \ 299

BinaryReader e BinaryWriter \ 299

StreamReader e StreamWriter \ 300

Comunicação pela rede 300 Trabalhando com E / S de arquivo assíncrono 301

Async e Aguardam no Arquivo I / O \ 301

Resumo 302

Desafios do código 303

Perguntas sobre o exame de prática física 303

■ ■ Capítulo 11: Serialização e desserialização \ 🏻 305


Serialização e desserialização \ 305

Serialização tem ter 305

Deserialização \ 305

Representação pictórica \ 305

Explicação \ 306

Serialização binária \ 307

Usando o serializador binário \ 307

S i li ã XML 308
Serialização XML 308

Usando XML Serializer 308 Usando o serializador DataContract 312

xiv

■ Conteúdo
Serialização JSON tem 313

Usando DataContractJsonSerializer \ 313 Using JavaScriptSerializer 314

Serialização Personalizada \ 315

Usando ISerializable \ 315

Comparação de desempenho de serialização 316

Resumo 317

Desafios do código 317

Perguntas sobre o exame de prática física 317

■ ■ Capítulo 12: Dados de consumo \


Trabalhando com um banco de dados \ 319

ADO.NET 319

Provedores de Dados \ 320

Conexão 320

Comando \ 323

Partes conceituais da ADO. NET \ 323

Camada conectada tem ter 323

Camada desconectada \ 326

Estrutura da entidade \ 328

Consuma dados XML e JSON \ 335

Dados XML \ 335 JSON Data \ 337

Trabalhando com Web Services 337

Serviço Web ASMX \ 337 WCF Web Service Serviço da web WCF 343 vs. ASMX Web Service \ 343

Resumo 344

Desafios do código 344

Perguntas sobre o exame de prática física 344

xv

■ Conteúdo
■ ■ Capítulo 13: Trabalhando com criptografia \ 347
Criptografia 347

Criptografia \ 347

A áli i t áfi \ 348
Análise criptográfica \ 348

Representação pictórica \ 348

Tipos de Criptografia \ 349

Criptografia simétrica \ Criptografia 349 assimétrica \ 351

Implemente o gerenciamento de chaves \ 353

Chaves simétricas 353 Chaves assimétricas 353

Encrypt Stream \ 354 Trabalhando com Classe de Dados Protegidos \ 355

Protect () \ 355 Desproteger \

Gerencie e crie certificados digitais 357

Crie e instale o certificado \ 357

Trabalhando com o Namespace System.Security \ 357

Código de Segurança de Acesso (CAS) 358

Declarative

Imperativo

Hashing \ 358

Hashing de sal \ 360

Escolha um algoritmo apropriado

⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 361 Resumo 362 Desafios de

Código 363 Questões sobre o Exame Prático 363

xvi

■ Conteúdo
■ ■ Capítulo 14: Montagem e reflexão \ 365
Introdução às Assembleias 365

Quando o código é compilado 365 tipos de montagem \

⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀

DE FABRICANTES DO FACEBOOK 366

Criando e usando montagem personalizada 366

Biblioteca de vínculo dinâmico (.DLL) 366 Executável (.EXE) 370

Montagem WinMD 371

Crie o conjunto WinMD 371

Cache de montagem global (GAC) \ 372

Instale uma montagem no GAC \ 372 AssemblyInfo.cs 373

Reflexão em C # \ 375

Trabalhando com Reflexão \ 375

Atributos em C # foram 385

Crie um atributo personalizado \ 385


Use ILdasm.exe para exibir o conteúdo do assembly 392 Resumo 2 393 Desafios de Código ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 393 ■ ■ Capítulo
15: Depuração e diagnóstico \ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 395

Escolha o tipo de construção apropriado \ 397 Criando e gerenciando diretivas do compilador \ 398 Entenda PDBs e Símbolos \ 401

xvii

■ Conteúdo
Diagnóstico 402

Instrumentando uma Aplicação 402

Registro e rastreamento \ 403

Criação de perfil do aplicativo \ 411

Resumo 419 Code Challenges 420 Questões do exame prático 420 ■ ■ Capítulo 16: Questões do exame prático \ 423 Objetivo 1: Gerenciar o fluxo do
programa \ 423 Objetivo 2: Criar e usar tipos \ 431 Objetivo 3: Depurar segurança de aplicativos e implementos 443 Obje ctive 4: Implementar Acesso a

Dados \ tenha pode ter: 451

Índice 467

xviii
sobre os autores

Ali Asad é um dos principais programadores de C # do


Paquistão. Ele é especialista em Microsoft (MS) em C # desde
2015. No Paquistão, ele é conhecido por seu popular
treinamento em certificação C # da Microsoft, que ajudou
muitos estudantes a passar nos exames de certificação da
Microsoft.
Ali é um membro ativo da comunidade; ele fala sobre
Desenvolvimento de Jogos e Programação C # em diferentes
conferências e workshops.
Você pode entrar em contato com Ali Asad através de:
• twitter.com/imaliasad
• facebook.com/imaliasad
• linkedin.com/in/imaliasad
• imaliasad@outlook.com

Hamza Ali é especialista da Microsoft (MS em C #) desde 2015 e


um instrutor independente que ensina tecnologias .NET e a
plataforma Cloud em geral, além de treinamento em
certificação Microsoft C # e ASP.NET MVC, em particular. Hamza
também está exercitando sua experiência em JaSol Technologies
(emergente Software House in Market, de sua propriedade)
como CTO.
Ele discursa em diferentes palestras técnicas e ministra
sessões sobre diferentes ferramentas, tecnologias e estruturas,
incluindo ASP.NET Core, Angularjs, Reactjs, Visual Studio Team
Services, WCF Services e APIs da Web, usando seu conhecimento
e experiência.
Você pode entrar em contato com Hamza Ali através de:
• linkedin.com/in/hamzaali2
• facebook.com/hamZaali.003
• hamzaaliarif@hotmail.com
• twitter.com/arreez11

xix
Sobre o Revisor Técnico

Syed Lakhtey Hussnain é engenheiro de software em uma


ampla gama de aplicativos com sólida experiência no
desenvolvimento de aplicativos da web (incluindo ERPs, CRMs)
com o ASP.NET MVC. Lakhtey está trabalhando como
estrategista de tecnologia com a equipe de desenvolvimento
sênior da Barracuda Inc. para reconstruir aplicativos da Web
inteiros que atendem aos fins comerciais. Lakhtey também
trabalha como treinador em uma startup chamada '7Colors',
onde ministrou treinamentos aos membros de sua comunidade
nas certificações .NET. Ele também é desenvolvedor de soluções
certificado da Microsoft em aplicativos da Web e AppBuilder.
Lakhtey tem seu profundo interesse em Música e Arte e
gosta muito disso. Lakhtey vive em Kharian, Paquistão, com
seus pais.
Lakhtey pode ser alcançado em:
• lakhtey_hussnain@hotmail.com
• https://twitter.com/lakhtey22
• https://www.facebook.com/hassan.lakhtey
• https://www.linkedin.com/in/lakhtey/

xxi
Agradecimentos

Ali Asad: É uma honra e um privilégio ter trabalhado com a Apress. Gostaria de
agradecer a cada pessoa que contribuiu muito para o livro:
• A Celestin Suresh John por me proporcionar a oportunidade de escrever este livro.
• A Sanchita Mandal, pelo tremendo apoio e coordenação que me ajudou
muito no processo de redação.
• Ao meu co-autor Hamza Ali por sua grande parceria. Sem o seu apoio,
este livro não teria a qualidade que possui agora.
• A Syed Lakhtey Hussnain por suas habilidades de revisão técnica.
Isso me ajudou a corrigir os erros que cometi.
• A David V Cobin, por reservar um tempo para ler este livro e fornecer
seu prefácio valioso.
• Para toda a equipe da Apress , que tornou este livro possível.
Há pessoas na vida que são especiais, que o inspiram, o apóiam e fazem de você a pessoa
que você é hoje. Fui muito abençoado por ter essas pessoas na minha vida, porque sem elas eu
não seria capaz de escrever este livro.
• A Ali Raza (AlizDesk) por me orientar a fazer o Exame de Certificação Microsoft.
• A Mubashar Raffique, por me apoiar na academia, onde treinamos
estudantes e profissionais para os exames de certificação da Microsoft.
• A Usman Ur Rehman por todos os seus valiosos conselhos e suporte que
me ajudaram bastante durante o tempo em que trabalhei no Microsoft
Innovation Center, em Lahore.
• A Faqeeha Riaz por fazer minhas tarefas do semestre e me ajudar a me
preparar para os exames finais porque, sem o seu apoio, eu não seria
capaz de dar meu foco total a este livro.
• Aos meus pais (mamãe [ Samina ], papai [ Asad ]) por me dar a
liberdade de fazer um bom trabalho na vida.
• Por último, mas não menos importante, a Sundus Naveed por toda a sua paciência, apoio e
amor.

xxiii

■ Agradecimentos
Hamza Ali: Sinto-me honrado em trabalhar com a Apress, uma das editoras de livros de
qualidade . Gostaria de agradecer as contribuições feitas por:
• Celestin Suresh John: por oferecer a oportunidade de escrever este livro.
• Sanchita Mandal: pelo seu apoio comprometido e contínuo para
manter e concluir o livro dentro do prazo.
• Ali Asad: meu co-autor, por sua grande contribuição, além de seu apoio
para escrever, concluir e manter a qualidade do livro e, obviamente, a
oportunidade do livro.
• Syed Lakhtey Hussnain: pela revisão técnica e apontamento de alguns
erros profundos, que melhoraram a qualidade.
• David V Cobin: por escrever o prefácio deste livro.
• Zunaira Shafqat Ali: pelo apoio e compreensão em todo o processo de
redação de livros.

Também gostaria de agradecer:


• minha família: por encorajamento e apoio.
• Ali Imran: por apoio e orientação.
• Mubashar Rafique: pela disponibilidade e pensamentos úteis.

xxiv

Introdução

Este livro aborda conhecimentos básicos e avançados necessários para passar no exame
70-483. Abrange o uso e as técnicas utilizadas para o desenvolvimento profissional.
Este livro cobre todos os objetivos listados no programa oficial do Exame 70-483.
Este livro é adequado para estudantes ou leitores que possuem um conhecimento
básico de C #, para levá-los a um nível avançado adequado para desenvolvedores
experientes.

Público-alvo
Alunos ou leitores com um conhecimento básico de C # e o aluno além desse estágio são o
público-alvo. A Microsoft recomenda um ano de experiência em C # antes de aparecer no
Exame 70-483, mas este livro (com sua estrutura de explicação sincronizada e básica a
avançada) leva os alunos ou desenvolvedores de nível básico, intermediário ou avançado a
esse nível em que podem aparecer facilmente para o exame 70-483 com preparação
satisfatória que também ajuda na clareza dos conceitos.
Este livro prepara os leitores para o Exame 70-483 e, ao passar neste exame, os
certificados "Microsoft Certified Professional" e "Microsoft Specialist: Programming in C
#" são concedidos pela Microsoft.

Conteúdo coberto
Este livro aborda o conteúdo listado no programa oficial do Exame 70-483, além dos tópicos
básicos relacionados ao conteúdo oficial, para que a sincronicidade possa ser mantida e os
leitores possam entender o conteúdo passo a passo.
Este livro usa C # 5.0 e .NET Framework 4.5 em seu conteúdo e exemplos. As perguntas
do tipo exame para cada capítulo também são abordadas por este livro para oferecer
melhor compreensão aos leitores, além de desafios no exame para aprimorar as
habilidades de codificação dos leitores.

Requisitos do livro
Para ler e implementar exemplos de código, você precisará de:
• Um sistema (PC) com Windows 10
• Comunidade Microsoft Visual Studio 2015 (esta edição está disponível
gratuitamente) ou acima. Você pode baixar esta versão no seguinte link:

https://www.visualstudio.com/downloads/

xxv

■ Introdução
Estrutura do Livro
O livro está estruturado para que a base de conhecimento seja construída gradualmente. A estrutura do
capítulo é a seguinte:
• Cada capítulo contém o objetivo a ser abordado.
• Exemplos do mundo real para limpar os conceitos dos leitores.
• Mapeamento de exemplos do mundo real em código.
• Notas e dicas dos autores para melhores práticas.
• Links úteis sobre recursos adicionados quando necessário.
• No final, MCQs estruturados para exames são dados para testar a
capacidade do leitor com base em sua compreensão do capítulo.
Cada capítulo é mapeado para 4 objetivos principais do Exame 70-483 em relação
ao seu conteúdo. Os objetivos são:
\ 1. \ Gerenciar o fluxo do programa de 25 a 30%
\ 2. \ Criar e usar tipos 25-30%
\ 3. \ Depurar aplicativos e implementar segurança 25-30%
\ 4. \ Implementar Acesso a Dados 25-30%
Os objetivos (com seus subobjetivos) explicados neste livro em relação aos capítulos são:

Gerenciar fluxo do programa 25-30%


Este objetivo explica como você pode usar programas C # simples que executam toda a sua
lógica de cima para baixo e também usar programas C # complexos que não possuem um
fluxo de programa fixo. Nesse objetivo, abordaremos os seguintes subobjetivos:
\ 1. \ Implementar processamento multithreading e assíncrono. (Capítulo 8 )
\ 2. \ Gerenciar multithreading. (Capítulo 8 )
\ 3. \ Implementar fluxo do programa. (Capítulo 1 )
\ 4. \ Crie e implemente eventos e retornos de chamada. (Capítulo 5 )
\ 5. \ Implementar tratamento de exceções. (Capítulo 9 )

Criar e usar tipos 25-30%


Este objetivo explica o sistema de tipos padrão no .NET e explica como você pode usá-lo em um
programa C # simples. Esse objetivo também explica como você pode criar seus tipos
personalizados usando struct, enumerações e classes e usá-los efetivamente para criar
programas C # complexos usando princípios orientados a objetos . Nesse objetivo,
abordaremos os seguintes subobjetivos:
\ 1. \ Criar tipos. (Capítulo 2 )
\ 2. \ Consumir tipos. (Capítulo 2 )
\ 3. \ Aplicar encapsulamento. (Capítulo 3 )

xxvi

■ Introdução
\ 4. \ Criar e implementar hierarquia de classes. (Capítulo 3 )
\ 5. \ Localizar, executar e criar tipos em tempo de execução. (Capítulo 14 )
\ 6. \ Gerenciar o ciclo de vida do objeto. (Capítulo 6 )
\ 7. \ Manipular seqüências de caracteres. (Capítulo 4 )

Depurar aplicativos e implementar segurança 25-30%


Este objetivo explica como você pode depurar um aplicativo validando entradas do usuário,
gerenciando assemblies etc. Além disso, você aprenderá como proteger seu aplicativo
implementando diferentes técnicas de criptografia (isto é, simétricas e assimétricas) e muito
mais. Nesse objetivo, abordaremos os seguintes subobjetivos:
\ 1. \ Validar entrada do aplicativo. (Capítulo 9 )
\ 2. \ Execute a criptografia simétrica e assimétrica. (Capítulo 13 )
\ 3. \ Gerenciar montagens. (Capítulo 14 )
\ 4. \ Depurar um aplicativo. (Capítulo 15 )
\ 5. \ Implementar diagnósticos em um aplicativo. (Capítulo 15 )

Implementar acesso a dados de 25 a 30%


Este objetivo explica como você pode usar as bibliotecas .NET para manipular dados em um
sistema de arquivos. Explica como você pode usar o LINQ para consultar dados, usar o
ADO.NET para acessar um banco de dados e muito mais. Nesse objetivo, abordaremos os
seguintes subobjetivos:
\ 1. \ Execute operações de E / S. (Capítulo 10 )
\ 2. \ Consumir dados. (Capítulo 12 )
\ 3. \ Consultar e manipular dados e objetos usando o LINQ. (Capítulo 6 )
\ 4. \ Serializar e desserializar dados. (Capítulo 11 )
\ 5. \ Armazenar dados e recuperar dados de coleções. (Capítulo 4 )

Mantenha contato
Criamos uma comunidade pequena e eficaz em um grupo do Facebook para os leitores deste
livro. É altamente recomendável que você faça parte do nosso grupo no Facebook. Se você
tiver algum problema, sinta-se à vontade para postar perguntas ou iniciar uma discussão
relacionada ao Microsoft Certification Exam 70-483 em: https://www.facebook.com/groups/
Exam70483 / .

xxvii

Prefácio

Como desenvolvedor profissional há várias décadas, tenho visto e participado de muitos


programas de certificação diferentes. A Microsoft investiu pesadamente em um conjunto de
exames e certificações que são indicativos da capacidade do candidato de aplicar o
conhecimento relevante a situações do mundo real.
Quando fui abordado pela primeira vez sobre escrever um prefácio para este livro no
exame 70-483 , fui cauteloso. Ao longo dos anos, tenho visto muitas publicações que não
fornecem nenhum entendimento real do material subjacente. No entanto, após o recebimento
do rascunho deste livro, essas preocupações foram eliminadas.
Os capítulos contêm tópicos que vão desde os recursos básicos até os avançados da
linguagem C #, usando uma combinação de texto narrativo e exemplos de código. Mesmo se
você for um desenvolvedor experiente de C #, é altamente recomendável iniciar a
preparação para o exame desde o início.
A página de políticas e perguntas frequentes sobre os exames no site da Microsoft afirma
especificamente: "A melhor maneira de se preparar para um exame é praticar as habilidades".
Eu encorajo todos os leitores deste livro para também passar as mãos no tempo com o
material; inicie o Visual Studio, insira o código de exemplo, escreva alguns programas
relacionados à capacidade e use o depurador para percorrer o código.
Com o material deste livro, o leitor diligente deve estar a caminho do nível de entendimento
necessário para se sair bem no exame 70-483 . Mesmo que seu foco imediato não esteja na
certificação, sempre há necessidades de aprendizado, revisão e referência que podem ser
atendidas mantendo uma cópia deste livro à mão.
David V. Corbin
Presidente / Arquiteto Chefe
Dynamic Concepts Development Corp.

xxix

CAPÍTULO 1

Fundamentos de C #

Para se preparar para o Exame de Certificação Microsoft 70-483 , é essencial aprender


os fundamentos da programação em C #. Este capítulo ensina como :
\ 1. \ Escreva seu primeiro programa em C #.
\ 2. \ Trabalhe com variáveis, tipos de dados primitivos e operadores.
\ 3. \ Use conversão de tipo implícita e explícita.
\ 4. \ Use a palavra-chave var.
\ 5. \ Trabalhar com matrizes.
\ 6. \ Definir estrutura de decisão.
\ 7. \ Defina operadores de decisão.
\ 8. \ Trabalhe com loops.
\ 9. \ Use instruções de salto.
\ 10. \ Use e defina métodos.
Para aproveitar ao máximo este capítulo, pegue um lápis e papel, anote cada ponto e escreva o
código
snippets no Microsoft Visual Studio 2012 ou superior. No final deste capítulo, você pode
praticar todos os conceitos: revisando o resumo, concluindo desafios de código e resolvendo
questões de múltipla escolha. Boa sorte!

Estrutura do programa e fundamentos da linguagem


Esta seção nos ajuda a começar a estrutura do programa, aprendendo os blocos de
construção básicos da programação em C # . Esses blocos de construção incluem:
• Escreva o primeiro programa em C #
• Trabalho com variáveis, tipos de dados primitivos e operadores
• Compreender expressões em c #
• Entender a conversão de tipos em C #
• Use a palavra-chave var
• Matriz em c #

© Ali Asad e Hamza Ali 2017 11


A. Asad e H. Ali, O Guia de Estudo do Programador C # (MCSD) , DOI 10.1007 / 978-1-4842-2860-9_1

Capítulo 1 ■ Fundamentos de C #
Primeiro programa em c #
Escrever seu primeiro programa C # é tão simples quanto escrever um programa em C ++ /
Java ou em qualquer linguagem de programação de alto nível . Preferimos escrever código
no aplicativo do console para praticar todos os tópicos do Exame 70-483 . É necessário que
saibamos como criar um projeto de console C # vazio no Visual Studio para gravar o
programa.

Para criar um projeto de console C # vazio no Visual Studio 2012 ou superior, siga estas
etapas, começando com (Figura 1-1 ):

Figura 1-1.  Abra um novo projeto no Microsoft Visual Studio

Abra o Visual Studio, clique em Arquivo ➤ Novo Projeto .


Uma janela (Figura 1-2 ) será exibida para criar um projeto .NET. Siga as etapas abaixo
para criar um projeto de console C # vazio no Visual Studio.
2

Capítulo 1 ■ Fundamentos de C #

Figura 1-2.  Escolha o modelo do projeto

\ 1. \ Selecione o modelo “Visual C #” no painel esquerdo.


\ 2. \ Selecione "Aplicativo de console" como um tipo de projeto.
\ 3. \ Escreva um nome exclusivo para o seu projeto.
\ 4. \ Selecione "OK" para criar o projeto.

A classe Program.cs aparecerá, que contém algum código padrão. Esses códigos são
divididos em diferentes segmentos (usando, namespace, classe, principal).

Capítulo 1 ■ Fundamentos de C #
Figura 1-3.  Program.cs

\ 1. \ using : statement ajuda a importar o espaço para nome no programa.


Esses namespaces têm tipos que podemos usar para desenvolver
aplicativos.
\ 2. \ namespace FirstProject : C # segue estritamente o design orientado a
objetos . Portanto, quando criamos um projeto de console vazio, ele cria
um espaço para nome com um nome de projeto. Dentro do namespace,
escrevemos tipos para o nosso projeto.
\ 3. \ class Program : C # cria uma classe padrão dentro do namespace
chamada “Program”. Nas classes internas, escrevemos métodos,
campos, propriedades e eventos que podemos reutilizar no projeto.
\ 4. \ Principal : o programa C # deve conter um método principal. É
onde a execução do programa começa.

Dentro do método principal, escreva a seguinte linha de código para imprimir uma boa
mensagem na tela de saída, como mostra a Listagem 1-1 .

Listagem 1-1.  Primeiro programa C #


static void Main (string [] args)
{
Console.WriteLine ("Bem-vindo, desenvolvedores!");
}

Para executar o código acima, pressione “ f5 ” ou clique no botão Iniciar da barra de


ferramentas no Visual Studio. Ele imprimirá "Bem-vindo, desenvolvedores!" na tela de
saída.

www.allitebooks.com

Capítulo 1 ■ Fundamentos de C #

■■ Nota   Console.WriteLine é um método que pega a mensagem e a imprime na tela de saída.


Parabéns , você escreveu com sucesso seu primeiro aplicativo no Visual Studio usando C #.
Agora você está pronto para começar sua jornada para se tornar um Microsoft Certified
Professional & Specialist: Programming in C #.

Variáveis e tipos de dados


Os dados estão em toda parte . Nosso trabalho como desenvolvedor é manipular dados e
produzir os resultados necessários. Os dados têm vários tipos (por exemplo, texto, áudio e
vídeo, etc.). Cada tipo de dados pode conter um tamanho diferente na memória. O mesmo
conceito se aplica ao escrever um aplicativo em C #. Temos variáveis para armazenar dados e
tipos de dados para descrever que tipo / tamanho de dados podem ser armazenados em uma
variável.

Sintaxe

Date_Type Nome da variável = Valor;

Fragmento de código

Listagem 1-2.  Inicialize uma variável inteira "idade"


int idade = 10;

Tipos de dados em c #
Existem alguns tipos de dados em C # comuns, usados com freqüência e com tamanhos
diferentes na memória. Na Tabela 1-1 , listamos alguns deles.

Tabela 1-1.  Tipos de dados comuns em C #


Tipo de dados Exemplo Valor padrão Tamanho da memória Classificação
int 456 00 4 bytes Tipo de valor
flutuador 10.05f 0.0f 4 bytes Tipo de valor
em dobro 19.5D 0.0D 8 bytes Tipo de valor
Caracteres 'UMA' '\ 0' 2 bytes Tipo de valor
byte 5 00 8 mordeu Tipo de valor
corda "Dev" Nulo (2 bytes) * (comprimento da string) Tipo de referência
bool verdade Falso 1 1 byte Tipo de valor
           

Capítulo 1 ■ Fundamentos de C #
Variáveis em c #
Variáveis são espaços reservados , para armazenar dados na memória por um período
temporário. Na programação, uma variável é usada frequentemente para recuperar e editar
dados na memória para produzir os resultados necessários. Ao definir uma variável, existem
algumas regras que devemos seguir.
•O nome de uma variável deve começar com um alfabeto ou sublinhado (_)
. As variáveis também podem ser alfanuméricas .
•O nome deve ser exclusivo e não pode ser uma palavra-chave (por exemplo, “usando”).
• Não insira espaço ao definir um nome, use estojo de camelo
(studentName) ou pascal case (StudentName).

Operador em c #
Operadores são símbolos especiais , usados com variáveis (operandos), para
manipular dados com o objetivo de produzir os resultados necessários. Os operadores
estão em diferentes categorias. Alguns deles estão listados abaixo:
• Operador aritmético
• Operador relacional
• Operador lógico booleano

Operador aritmético
Usamos operadores aritméticos em valores numéricos para executar operações
matemáticas. Por exemplo, a seguir (Tabela 1-2), cada operador aritmético é usado para
executar uma operação matemática diferente.
Tabela 1-2  Operadores aritméticos em C #
Operador Descrição Exemplo
   

+ Operador Adicionar usado para adicionar dois valores numéricos int add = 10 + 5; //
- Subtrair Operador usado para subtrair dois valores numéricos int min = 10-5; //
* Operador de multiplicação usado para multiplicar dois valores numéricosint mul = 10 * 5; //
/ Operador de divisão usado para dividir dois valores numéricos int div = 10 / 5; //
% Operador de módulo usado para retornar o restante de dois int mod = 10% 5; //
  valores numéricos      
         

Operador relacional
O operador relacional é usado para comparar dois valores (operandos) e retornar Booleano como resultado.

Capítulo 1 ■ Fundamentos de C #
Tabela 1-3  Operadores relacionais em c #
Operador Descrição Exemplo
> Maior que o operador retorna "Verdadeiro", verificação bool = 4> 3; //Ver
se o primeiro valor for maior que o segundo verificação bool = 3> 4; // Fal
valor. Caso contrário, retornará "False".
<O operador menor que retorna "True", se o verificação bool = 2 <4; //Ve
primeiro valor for menor que o segundo valor. verificação bool = 4 <2; //Fal
Caso contrário, retornará "False". verificação bool = 2 == 2; //Ve
== Igual ao operador retorna "True", se o verificação bool = 2 == 3; //Fal
primeiro valor corresponder ao segundo valor. verificação bool = 2! = 3; //Ve
Caso contrário, retornará "False". Bool check = 2! = 2; //Fal
!= Diferente do operador retorna "True", se o prim
  não corresponde ao segundo valor. Retorna "Fals verificação bool = 2> = 1; //Ve
ambos os valores são iguais. verificação bool = 2> = 2; //Ve
 
>= Maior que Igual ao operador retorna "Verdadei verificação bool = 1> = 2; //Fal
valor é maior ou igual ao segundo valor. Caso co
  verificação bool = 2 <= 3; //Ve
  retornará "False". verificação bool = 2 <= 2; //Ve
<= Menor que Igual ao operador retorna "Verdade verificação bool = 2 <= 1; //Fal
  o valor é menor ou igual ao segundo valor. Caso
retornará "False".

Operadores lógicos booleanos


Operadores lógicos são usados entre dois valores booleanos. Alguns operadores lógicos são descritos na
tabela abaixo.

Tabela 1-4.  Operadores lógicos booleanos em c #


Operador Descrição Exemplo

O operador && And retorna Verificação booleana = True && True; //Ve
"True" se os dois valores booleanos Verificação booleana = True && False;//Fa
forem verdadeiros. Caso contrário,
retornará "False". Verificação booleana = True || Falso; //Ve
Bool check = False || Falso; //Fa
|| O operador OR retorna "True" se Bool check =! (falso); //Ve
algum dos dois valores tiver o valor
"True". Se todos os valores forem Bool check =! (verdade); //Fa
"Falso", ele retornará "Falso".
! Não é operador , se o valor for "False",
ele retornará "True" e se o valor for
"True", retornará "False".

Expressão em c #
Expressão nos ajuda a avaliar o resultado de declarações simples ou complicadas. Na verdade, é
uma série de um ou mais operandos, valores literais e invocações de métodos com zero ou mais
operadores que ajudam a avaliar um resultado .

Fragmento de código

Listagem 1-3.  Escreva uma expressão simples em C #

int i = 4;
int j = (i * 4) + 3; //
Saída j = 19

Capítulo 1 ■ Fundamentos de C #
Tipo Fundição
C # é uma linguagem fortemente tipada, o que significa que o tipo de variável deve
corresponder ao seu valor na compilação e no tempo de execução . Na maioria dos casos,
precisamos converter o tipo de dado para armazená-lo em outro tipo. Por exemplo, estamos
obtendo dados da string (“10”) e queremos convertê-los em int32 para executar uma operação
aritmética.
Há duas maneiras de o C # ajudar a converter o tipo de um objeto ou variável, usando:
• Conversão implícita
• Conversão explícita

Conversão implícita
A conversão implícita acontece automaticamente pelo próprio compilador. Nenhuma sintaxe
de conversão especial é necessária e nenhum dado é perdido durante a conversão implícita.

Exemplo

Listagem 1-4.  Conversão implícita de dados integrais pequenos para maiores

int i = 10; d
duplo = i;

Listagem 1-5.  Conversão implícita de derivado para o tipo base

objeto o = new Program ();

Conversão explícita
A conversão / sintaxe especial é necessária quando os dados não podem ser convertidos em
outros tipos automaticamente. Os dados podem ser perdidos na conversão explícita.

Exemplo

Listagem 1-6.  Conversão explícita de dados maiores em tipos de dados menores


d duplo = 3,1417;
int i = (int) d;
// use (type) para converter um tipo explicitamente
Listagem 1-7.  Conversão explícita de cadeia de caracteres no tipo de dados primitivo com o método "Parse"
string s = "22";
int age = int.Parse (s);

■■ Nota   Cada tipo permitido possui um método Parse. Isso ajuda a converter dados
de cadeia nesse tipo de permissão associado.

Capítulo 1 ■ Fundamentos de C #
palavra-chave var
Var é um tipo implícito , usado para armazenar qualquer valor de uma expressão. O tipo
de variável var depende do valor designado no tempo de compilação . Se o valor de uma
expressão, objeto ou variável for sequência, o tipo de variável var será sequência. Se o valor
for int32, o tipo de variável var se tornará int32.
A palavra-chave var é altamente recomendada quando:
• você prefere bons nomes de variáveis sobre o tipo;
• O nome do tipo é longo;
• a expressão é complexa e você não sabe o tipo de valor que ela retorna.

Sintaxe

var nome_da_variável = dados;

Fragmento de código

Listagem 1-8.  Atribua qualquer valor nas variáveis var


var idade = 22;    
//tipo dea idade é int32
varnome = "TodosAsad "; //tipo donome écorda
varmatemática= 10 / int.Parse ("10");//tipo domatemáticaéint32

■■ Nota   Sempre inicialize a variável var com um valor. Caso contrário, o compilador irá gerar um
erro.

Matriz em c #
Matriz é uma coleção ou série de elementos do mesmo tipo. Cada elemento armazena dados
que podem ser acessados chamando seu número de índice com um nome de matriz. Uma
matriz pode ter três tipos:
• Matriz de dimensão única
• Matriz de várias dimensões
• Matriz irregular

Matriz de dimensão única


Na dimensão única, uma matriz armazena elementos de maneira linear . Na maioria das
vezes de desenvolvimento, usamos uma matriz de dimensão única.

Sintaxe

tipo [] nameOfArray;
• tipo especifica que tipo de matriz de dados pode armazenar
• [] especifica que é uma matriz
• nameOfArray especifica o nome da matriz

Capítulo 1 ■ Fundamentos de C #
Fragmento de código

Listagem 1-9.  Declarar uma matriz de string

amigos da string [];

Inicializar uma matriz

type [] nameOfArray = novo tipo [size];

• novo tipo [tamanho] , ajuda a inicializar séries de elementos de uma


matriz na memória. Tamanho informa o comprimento total de uma
matriz.

Fragmento de código

Listagem 1-10.  Declarar uma matriz de cadeia de tamanho 4


string [] friends = nova string [4];

Inicializar uma matriz com valores (a)

type [] nameOfArray = novo tipo


[size]; nameOfArray [index] = valor;
• nameOfArray [index] = valor ; diz para armazenar valor no índice específico de uma matriz.
•O índice de uma matriz não pode sair dos limites.
Fragmento de código

Listagem 1-11.  Declarar e inicializar matriz de cadeia de tamanho 4 com valores


string [] friends = nova string [4];
amigos [0] = "Ali";
amigos [1] = "Mubashar";

Inicializar uma matriz com valores (b)

tipo [] nameofArray = {values};


• digite [] nameofArray = {values}; diz para armazenar valores
diretamente sem especificar o comprimento de uma matriz. O
comprimento de uma matriz depende do número de valores escritos
dentro de {} chaves.
Fragmento de código

Listagem 1-12.  Declarar e inicializar uma matriz de string com valores

string [] friends = {"Ali", "Mubashar"};

Inicializar uma matriz com valores (c)

tipo [] nameOfArray = novo int [tamanho] {valores};

10
Capítulo 1 ■ Fundamentos de C #
• digite [] nameofArray = new int [tamanho] {valores}; diz para armazenar valores de
tamanho 4.

Fragmento de código

Listagem 1-13.  Declarar e inicializar matriz de cadeia de tamanho 4 com valores

string [] friends = new string [4] {"Ali", "Mubashar", "Lakhtey", "Hamza"};

Inicializar uma matriz com valores (d)

tipo [] nameOfArray = new int [] {values};

• digite [] nameofArray = new int [] {values} ; diz para inicializar uma


matriz sem tamanho fixo. Seu tamanho depende do número de valores
escritos dentro de {} chaves.

Fragmento de código

Listagem 1-14.  Declarar e inicializar uma matriz com valores


string [] friends = new string [] {"Ali", "Mubashar", "Lakhtey", "Hamza"};

Matriz de várias dimensões em C #


A matriz 2D é o tipo mais comum de matriz multidimensional que usamos em C #. No mundo
real, o array 2D é usado para armazenar dados mais complexos em um sistema (por exemplo:
imagem digital e jogo de tabuleiro). A matriz 2D pode ser vista como uma tabela, que possui
linhas e colunas.

Sintaxe

tipo [,] my2dArray = novo int [rowSize, colSize];


• digite [,] informa que o array é 2D.
• int [RowSize, Colsize] diz o tamanho da fila e tamanho da coluna.

Fragmento de código

Listagem 1-15.  Declarar matriz 2D de int, com 2 linhas e 5 colunas

int [,] números = novo int [2,5];

O código acima declara a matriz "numbers" com 2 linhas e 5 colunas.

Inicializar matriz 2D com valores (a)

tipo [,] my2dArray = nova int [rowSize, colSize]


{
{values},
{values}
};

11

Capítulo 1 ■ Fundamentos de C #
Fragmento de código

Listagem 1-16.  Inicializar matriz 2D com valores em sub-matrizes


int [,] number = new int [2, 5]
{
{2,4,6,8,10},
{1,3,5,7,9}
};

O snippet de código acima informa que “numbers” é uma matriz 2D com tamanho de
linha 2 e tamanho de coluna 5, o que significa que ele armazena duas matrizes de
dimensão única com tamanho 5.

Acessar matriz 2D
Usamos loops para acessar valores de uma matriz 2D. Os loops são discutidos com muitos
detalhes posteriormente neste capítulo. A seguir, está um trecho de código que explica
como acessar valores de uma matriz 2D.

Fragmento de código

Listagem 1-17.  Exibir dados da matriz 2D

int [,] number = new int [2, 5]


{
{2,4,6,8,10},
{1,3,5,7,9}
};

for (int row = 0; row <numbers.GetLength (0); row ++)


{
for (int col = 0; col <números.GetLength (1); col ++)
{
Console.Write (números [linha, col]);
}
Console.WriteLine ();
}

//Resultado
246810
13579

■■ Nota   GetLength (int32) retorna o número total de elementos em uma dimensão específica de uma
matriz.

Matriz irregular em C #
É uma matriz de uma matriz, o que significa que é um tipo de matriz cujos elementos também
são uma matriz. Cada elemento de uma matriz irregular pode ter um tamanho diferente.

Sintaxe

type [] [] jaggedArray = novo tipo [rowSize] [];

12

Capítulo 1 ■ Fundamentos de C #
• type [] [] informa que é um array irregular.
• type [rowSize] [] informa que o tamanho da linha é fixo, mas o tamanho
da coluna não é fixo, porque cada elemento tem um tamanho diferente de
matriz.

Fragmento de código

int [] [] irregular = novo int [4] [];

Declarar uma matriz irregular


Cada índice de uma matriz irregular é inicializado com um novo tamanho de matriz.

Fragmento de código

Listagem 1-18.  Declarar uma matriz irregular com 4 linhas


int [] [] irregular = novo int [4] [];

Listagem 1-19.  Declare cada linha com um novo tamanho de matriz


irregular [0] = novo int
[2]; irregular [1] = novo
int [3]; irregular [2] =
novo int [4]; irregular
[3] = novo int [5];

• int [4] [] informa que a matriz irregular tem 4 linhas, mas o número de colunas não está
especificado.
• Jagged [0] = novo int [2] ; diz que a linha 0 possui 2 colunas.
• Jagged [3] = novo int [5] ; diz que a linha 3 tem 5 colunas.

Inicializar matriz irregular com valores (a)

Listagem 1-20.  Inicialize o valor no índice da matriz irregular

irregular [0] [0] =


4; irregular [0] [1]
= 5;

• irregular [0] [0] = 4 ; armazene o valor na matriz irregular da linha 0 e coluna 0.


• irregular [0] [1] = 5 ; armazenar valor na matriz irregular da linha 0 e coluna 1.

Inicializar matriz irregular com valor (b)

Listagem 1-21.  Inicializar matriz irregular de int com valores

irregular [0] = novo int [] {4, 5};


irregular [1] = novo int [] {6, 7, 8};

• irregular [0] = novo int [] {4,5} ; inicialize uma matriz na linha 0 com valores {4,5}.
• irregular [1] = novo int [] {6,7,8} ; inicialize uma matriz na linha 1 com valores {6,7,8}.

13

Capítulo 1 ■ Fundamentos de C #
Inicializar matriz irregular com valores (c)

Listagem 1-22.  Inicialize matriz irregular de int com valores dentro de sub-matrizes

int [] [] irregular =
{
nova int [] {4,5}, nova
int [] {6,7,8}, nova int
[] {9,10,11}, nova int []
{12,13,14,15}
};

• Inicialize uma matriz irregular com várias matrizes. O tamanho das


linhas da matriz irregular depende do número de matrizes. Nesse
caso, o número de linhas é 4.

Acessar matriz irregular


Os valores em uma matriz irregular são acessados especificando o índice de linhas e colunas.

Fragmento de código

Listagem 1-23.  Valor de exibição do índice irregular da matriz


Console.WriteLine (irregular [0] [0]);
Console.WriteLine (irregular [0] [1]);

Loop sobre matriz irregular

Listagem 1-24.  Use o loop for para exibir cada valor em um índice de matriz irregular
// Inicializa a matriz irregular com
valores int [] [] jagged =
{
nova int [] {4,5}, nova
int [] {6,7,8}, nova int
[] {9,10,11}, nova int []
{12,13,14,15}
};

// Faz um loop sobre cada índice da


matriz irregular para (int i = 0; i
<comprimento irregular; i ++)
{

for (int j = 0; j <irregular [i] .Comprimento; j ++)


{
Console.Write (irregular [i] [j]);
}

Console.WriteLine ();
}
• jagged.Length : obtenha o número total de linhas em uma matriz irregular.
• irregular [int] .Length : obtém o número total de colunas de uma linha específica.

14

Capítulo 1 ■ Fundamentos de C #

Implementar fluxo do programa


Normalmente, todas as instruções de um programa são executadas de cima para baixo . Mas
em um aplicativo real, controlamos o fluxo de execução, apresentando:
• Estrutura decisão
• Operadores de decisão
• Loops
• Instruções de salto

O fluxo de controle ajuda nosso programa a executar ou pular um bloco de código,


ajuda a repetir um código até que uma condição seja satisfeita e ajuda nosso controle a
saltar para qualquer lugar do código.

Estrutura de decisão
As estruturas de decisão permitem que um programa seja executado apenas em determinadas
condições . Normalmente, nosso programa é executado em um fluxo simples que executa todo
o código de cima para baixo sem pular nenhum código. Mas, no mundo real, nosso aplicativo
nos ajuda a decidir qual código executar em determinadas condições. Por exemplo, você está
criando um programa que verifica a idade de uma pessoa e decide se uma pessoa atingiu ou
não sua idade de aposentadoria. Nesse caso, introduzimos estruturas de decisão para permitir
que o aplicativo decida se uma pessoa atingiu sua idade de aposentadoria ou não.
O C # possui algumas estruturas de decisão que podemos usar listadas abaixo.
• Se {}
• if-else {}
• if-else se {}
• alternar {}

Se {} estrutura em c #
A declaração If nos ajuda a controlar o fluxo de um programa. Ele executa um programa
apenas quando uma determinada condição retorna verdadeira .

Sintaxe

se (condição)
{
// TODO: executar programa quando a condição retorna True
}

• Se (condição) retornar verdadeiro, ele executará instruções escritas


dentro de {} chaves. Se a condição retornar false, ignorará o código
escrito dentro de {} chaves.

Exemplo
Vamos escrever um código que imprima uma mensagem "Número Par" somente quando um número for
par.

15

Capítulo 1 ■ Fundamentos de C #
Listagem 1-25.  Escreva o código C # para verificar se o número é par
número int = 16;

if (número% 2 == 0)
{
Console.WriteLine ("Número Par");
}

// Número
Par de
Saída

If Else {} Estrutura em C #
Na aplicação real, nos encontramos em uma situação certa ou errada. Por exemplo, se
inserirmos um nome de usuário correto, o sistema efetuará login. Mas, se inserirmos um nome
de usuário inválido, o sistema exibirá um erro. Em tais situações, escrevemos código dentro de
instruções if-else .
Código escrito dentro se o bloco {} for executado quando as condições forem
satisfatórias. No entanto, se uma condição não atender , o código escrito dentro de outro
bloco {} será executado.

Sintaxe

se (condição)
{
// TODO: executar código quando a condição é satisfeita
}
outro
{
// TODO: executar código quando a condição não satisfaz
}
• Se (condição) retornar verdadeiro, ele executará instruções escritas dentro de {} chaves.
•O outro bloco {} será executado quando if (condition) retornar false.

Exemplo
Para entender se-else, vamos escrever um código de login básico. O código a seguir verificará
se um nome de usuário está correto , imprimirá a mensagem "Login com êxito". Mas se um
nome de usuário não estiver correto, ele imprimirá a mensagem "Nome de usuário inválido"
na tela de saída.

Listagem 1-26.  Verifique se o nome de usuário está correto


string nome de usuário =
"dev"; if (nome de
usuário == "dev")
{
Console.WriteLine ("Login bem-sucedido");
}
outro
{
Console.WriteLine ("Nome de usuário inválido, tente novamente");
}

//Resultado
Login bem-sucedido

16

Capítulo 1 ■ Fundamentos de C #

■■ Nota   Se o nome de usuário for diferente de "dev", nesse caso, a mensagem "Nome de
usuário inválido, tente novamente" será impressa na tela.

If Else If {} Estrutura
Cadeia de múltiplos if e else faz if else-if. Ajuda um programa a examinar várias condições
(opções) para executar um bloco de código específico.

Sintaxe

se (condição)
{
// TODO: executado se a condição satisfizer
}
senão se (condição)
{
// TODO: executado se a condição satisfizer
}
senão se (condição)
{
// TODO: executado se a condição satisfizer
}
.
.
.
outro
{
// TODO: executado se nenhuma condição for atendida
}

• Se (condição) retornar verdadeiro, ele executará instruções escritas dentro de {} chaves.


• O controle Else If (condição) verificará a condição de else se somente se
a condição retornar falsa. Quando mais, se a condição retornar true,
executará o código escrito dentro de seu corpo.

• Quando else-if (condição) retornar falso, o controle passará para o


próximo else-if (condição). Quando next else-if (condition) retornar false,
passará para o próximo else else-if (condition) e continuará a fazê-lo até
encontrar outro bloco da estrutura else else-if final .
• Quando qualquer condição for satisfeita , o controle executará a
instrução de código escrita dentro de seu bloco e pulará a estrutura
restante else-if e else em sua cadeia.
• Else {} somente será executado quando nenhuma condição estiver em sua cadeia.

Exemplo
Vamos criar um programa que verifique sua idade e imprima na tela de saída, seja você
criança, adolescente, adulto ou velho.

17

Capítulo 1 ■ Fundamentos de C #
Listagem 1-27.  Verifique a idade do usuário e exiba uma boa mensagem
int idade =
20; if (idade
<11)
{
Console.WriteLine ("Você é uma criança!");
}
caso contrário, se (idade <18)
{
Console.WriteLine ("Você é adolescente!");
}
caso contrário, se (idade <50)
{
Console.WriteLine ("Você é um adulto!");
}
outro
{
Console.WriteLine ("Você é uma pessoa idosa");
}

No exemplo acima, a primeira e a segunda condições não satisfazem, mas a terceira


condição se (idade <50) for satisfeita, e o aplicativo imprimirá “Você é adulto” na tela. Depois
de executar o bloco de código, o controle irá pular o restante else if e sair da estrutura if-else .

Alternar {} Estrutura em C #
Switch é outra estrutura de decisão, altamente recomendável quando fornecemos constantes
para comparar com uma expressão. Se nenhuma das constantes corresponder à expressão, o
bloco padrão será executado.
Sintaxe

switch (expressão)
{
constante de caso: //
bloco de
caso
quebrar;

constante de caso: //
bloco de
caso
quebrar;
.
.
.

padrão:
// bloco de caso padrão
quebrar;
}

18

Capítulo 1 ■ Fundamentos de C #
Exemplo
Suponha que estamos criando um aplicativo que nos ajude a decidir se um número é par ou ímpar.

Listagem 1-28.  Verifique se o número é par ou ímpar


int i = 3;

interruptor (i% 2)
{
caso 0:
Console.WriteLine (“{0} é um número par”, i);
quebrar;

caso 1:
Console.WriteLine ("{0} é um número ímpar", i);
quebrar;
}

■■ Nota   Use caixa de mudança somente quando tivermos uma lista definida de
constantes que possamos comparar com o resultado de uma expressão. Caso contrário,
use a estrutura if-else .

Operadores de decisão
Existem alguns operadores em C # que nos ajudam a retornar dados apenas quando uma
determinada condição é satisfeita. Esses são:
• Operador condicional (?:)
• Operador de coalescência nula (??)

Operador condicional (? :)
O operador condicional verifica uma condição e retorna um valor. Se uma condição satisfizer,
ela retornará um valor que está no bloco "True". Mas se não satisfizer a condição, ele
retornará um valor que está no bloco "False".

Sintaxe

(Doença) ? True_Statement: False_Statement;

Exemplo
Suponha que estamos criando um aplicativo que nos diga se um número é par ou ímpar.

Listagem 1-29.  Verifique se o número é par ou ímpar


int num = 2;
resultado da string = (num% 2 == 0)? "Par ou
ímpar"; Console.WriteLine ("{0} é {1}", num,
resultado);

19

Capítulo 1 ■ Fundamentos de C #

■■ Nota   Use uma declaração condicional apenas quando desejar retornar um valor. Caso contrário, use uma
instrução if-else .

Explicação
Vamos entender como um operador condicional funciona no exemplo acima.
• ((num% 2) == 0) É uma expressão booleana ou uma condição a ser satisfeita.
• ? É um operador condicional. Isso ajuda a decidir qual declaração
retornar. Se uma condição satisfizer, ela retornará uma declaração True; e
se não satisfizer uma condição, retornará uma declaração False.
• “Par”: “Ímpar” Essas são duas instruções, separadas por dois pontos (:).
Uma declaração verdadeira está antes de dois pontos (:) e uma declaração
falsa é depois de dois pontos (:)

Operador de coalescência nula (??)


Existem muitos casos em que garantimos que não armazenamos um valor nulo em uma
variável. Podemos conseguir isso usando um operador Null Coalescing. Retorna uma variável
à esquerda (operando) se não for nula; caso contrário, ele retornará um valor padrão
armazenado em uma variável à direita (operando).

Sintaxe

leftOperand ?? rightOperand;

Fragmento de código

Listagem 1-30.  Defina o valor "user" no nome de usuário se "name" for nulo
nome da cadeia = nulo;

// define nome de usuário = nome, se o nome


não for nulo. // define nome de usuário =
"usuário", se o nome for nulo.

string nome de usuário = nome ?? "do utilizador";

Loops em c #
Em uma aplicação real, às vezes executamos o mesmo bloco de código várias vezes. Nesse
caso, usamos loops para iterar as mesmas instruções de código pelo número x de vezes.
No C #, temos quatro tipos de loops que podemos usar para iterar uma instrução de código várias vezes.
• loop while
• loop do- while
• para loop
• loop foreach

Enquanto Loop
O loop while ajuda a iterar as instruções de código até que uma condição escrita dentro de while () retorne
true.

20

Capítulo 1 ■ Fundamentos de C #
Sintaxe

while (condição)
{
// Execute Code: desde que a condição retorne true.
}

Fragmento de código

Listagem 1-31.  Use o loop while para imprimir olá mundo por 20 vezes
bool isFound = false;
valor int = 0;
while (isFound! = true) // verifica se o código está ou não em seu bloco
{
Se (valor == 99)
{
isFound = true;
}
valor = valor + 3;

■■ Nota   Use o loop while quando souber que uma condição é verdadeira e não souber
quantas vezes ela irá repetir um bloco de código.

Loop Do-while
O loop do - while ajuda a iterar sobre instruções de código. Funciona da mesma forma que o
loop while; a única diferença é que a condição sempre verifica no final .

Sintaxe

Faz
{
// Código de execução: até que a condição seja verdadeira

} while (condição);

Fragmento de código

Listagem 1-32.  Use loop do- while para imprimir o Hello World por 5 vezes
int count = 1;
do // Não verifique a condição na primeira iteração
{
Console.WriteLine ("Olá Mundo");
count ++;

} while (contagem <= 5); // verifica a condição: se verdadeiro, o bloco executa

21

Capítulo 1 ■ Fundamentos de C #
Explicação
Quando o do-while é executado pela primeira vez, ele não verifica a condição; em vez disso,
executa o código dentro dele. Quando a primeira iteração é concluída, ela verifica a
condição para executar a iteração pela segunda vez. Ele será repetido continuamente, desde
que a condição seja verdadeira.

For Loop
O loop For é usado em um caso em que temos números fixos para iterar um bloco de código por várias vezes.

Sintaxe

for (inicialização variável; condição; incremento / decremento)


{
// Código de execução: até que a condição seja verdadeira
}

Explicação
Vamos entender a sintaxe do loop for.
• variable_initialization : Nesta parte do loop, a variável é declarada e
inicializada ou apenas inicializada e esta instrução (primeira parte do
loop) é executada uma vez quando os controles entram no loop.
• Condição : Condição emite um sinal verde para fazer um loop para iterar
sobre o bloco de código somente quando uma condição retornar
verdadeira.
• Incremento / decremento : o incremento / decremento ajuda a controlar a iteração do loop.

Fragmento de código

Listagem 1-33.  Use for loop para imprimir “Hello World” por 5 vezes
for (contagem int = 1; contagem <= 5; contagem ++)
{
Console.WriteLine ("Olá Mundo");
}

Explicação
int count = 1; aqui está um valor de contagem inicializado com o valor 1; depois, uma
condição será verificada se estiver satisfeita, e um controle poderá entrar nele para que o
corpo do loop execute todas as instruções escritas dentro dele. Após executar uma iteração
do loop, um controle passará para uma assinatura de loop for para executar count ++; isso
permite que uma variável de contagem lembre quantas vezes foi executada. Depois, ele
verifica uma condição e, se for novamente satisfeito, entra em um corpo de loop para
executar todas as instruções. É repete o mesmo ciclo (verificação de condição ➤ corpo loop
de execução ➤ valor de incremento de contagem), desde que uma condição é verdadeira.

■■ Nota O   loop For é utilizável quando várias iterações e condições para finalização são definidas.
Foreach loop
O loop Foreach sempre funciona na coleção ; o número de iterações depende do tamanho de
sua coleção. Em cada iteração, o loop foreach obtém um valor do próximo índice de uma
coleção.

22

Capítulo 1 ■ Fundamentos de C #
Sintaxe

foreach (item var na coleção)


{
// Run Code
}

Fragmento de código

Listagem 1-34.  Use o loop foreach para iterar sobre uma matriz
int [] array = {1, 2, 3, 4, 5}; // Coleção de int

foreach (item int na matriz) // iterando sobre cada índice da coleção


{
 
Console.WriteLine (item); // valor de impressão armazenado nesse índice
}
 

Explicação
Vamos dividir um trecho de código do foreach e entender seu trabalho passo a passo.
• item int : é uma variável de espaço reservado que armazena um valor que determinado
índice da matriz possui.
• in é uma palavra-chave, que obtém o valor de uma matriz [index], até
obter todos os valores de índice em uma iteração contínua sobre uma
matriz.
• matriz é o nome de uma colecção int [] acima definido um loop foreach.

■■ Nota   Não podemos modificar o valor de uma coleção enquanto iteramos sobre ela em um loop
foreach.

Instruções de salto em c #
As instruções de salto permitem que os controles do programa se movam de um ponto
para outro em qualquer local específico durante a execução de um programa .
Abaixo estão as instruções de salto que podemos usar em C #:
• Ir para
• Pausa
• continuar
• Retorno
• Arremessar

Vamos para
Uma instrução goto é uma instrução de salto que transfere seus controles para uma instrução
rotulada . A instrução goto requer que o rótulo identifique o local para onde o controle irá. Um
rótulo é qualquer identificador válido e deve ser seguido por dois pontos . O rótulo é colocado
antes da declaração para onde o controle deve ser transferido.

23

Capítulo 1 ■ Fundamentos de C #
Um uso comum da instrução goto é alternar o controle de transferência para casos de
comutação específicos ou loops aninhados para alterar o controle quando o trabalho é
concluído ou depende do cenário.
Sintaxe
label: //
algum
código goto
label;

OU
rótulo
goto; label:
// algum
código

Ir para instruções no caso de switch

Listagem 1-35.  Use "goto" na caixa de comutação

caractere char = 'e';


switch (caractere)
{
caso 'a':
{
Console.WriteLine ("O caractere é uma
vogal."); quebrar;

}
case 'e':
{
ir para o caso 'a';
}
caso 'i':
{
ir para o caso 'a';
}
case 'o':
{
ir para o caso 'a';
}
case 'u':
{
ir para o caso 'a';
}
case 'y':
{
Console.WriteLine ("Às vezes, o caractere é uma
vogal."); quebrar;

}
padrão:
{
Console.WriteLine ("O caractere é uma
consoante"); quebrar;

}
}
24

www.allitebooks.com

Capítulo 1 ■ Fundamentos de C #
// Saída: o caractere
é vogal.

Explicação
No exemplo de código acima, o controle salta para o caso "e". Dentro da caixa "e", a instrução
"case" é executada e o controle salta para o bloco "a" e imprime "Character is vogal". na tela de
saída.

Saltar instruções em loops


Vamos dar outro exemplo simples para entender mais claramente:
Listagem 1-36.  Use ir para o loop for

int [] number = new int [] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

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


{
if (números [i] == 8)
{
vá para Controle;
}
}

Console.WriteLine ("Fim do loop");

Ao controle:
Console.WriteLine ("O número é 8");

//Resultado
// O número é 8

Explicação
No exemplo acima, sempre que o compilador detectar o valor dos números [i] e se houver um
8, o compilador passará para o rótulo “Control” e começará a executar o código após o rótulo
“Control”. Portanto, a saída será apenas "O número é 8".

■■ Nota   Existem duas formas de instruções goto: salto para frente e salto para trás . A
Figura 1-4 mostra o fluxo da instrução goto de maneira direta e reversa.

25

Capítulo 1 ■ Fundamentos de C #

Figura 1-4.  Fluxo da instrução Goto

• Ir para instruções com exemplo de loop mostra salto para frente


• Instruções Goto com Switch mostram salto para trás da instrução goto.
■■ Nota   Evite instruções goto nesses cenários que levam ao código inacessível.

Quebrar
Break é uma palavra-chave que também é uma instrução de salto, que finaliza o fluxo do
programa em loop ou na instrução switch (ou seja, ignora o bloco atual e passa para o bloco
ou código externo, se houver).

Use a instrução break em loop


Listagem 1-37.  Usar arrombamento para loop

int [] number = new int [] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

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


{
if (números [i] == 3)
{
quebrar;
}
Console.Write (números [i]);
}

Console.WriteLine ("Fim do loop");

Explicação
Quando o snippet de código acima for executado, a saída será "End of Loop". Vamos entender como.

26

Capítulo 1 ■ Fundamentos de C #
Quando uma condição if escrita dentro do loop for satisfeita, a palavra-chave break será
executada. Ele encerra a iteração restante do loop e salta o controle do loop e inicia a execução
do código que está escrito fora do loop, ou seja, “Console.WriteLine (“ End of Loop ”);”.

Continuar
A instrução Continue também é uma instrução de salto, que ignora a iteração atual e move
o controle para a próxima iteração do loop.
Continuar é uma palavra - chave, o mesmo que quebra, mas com o comportamento acima
mencionado.

Use a instrução continue em loop


Listagem 1-38.  Use "continuar" no loop for
int [] number = new int [] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; for (int i =
0; i <10; i ++)
{
if (números [i] == 5)
{
continuar;
}
Console.Write (números [i]);
}
Console.WriteLine ("Fim do loop");

Explicação
Neste exemplo, o loop for funcionará normalmente, mas quando o número de valores [i] se
tornar 5, ele ignorará a iteração, o que significa que interromperá a execução da iteração atual
e passará para a próxima iteração.

//Resultado
1234678910 Fim do loop

Retorna
Return também é uma instrução de salto, que retorna o controle do programa para o
método de chamada . Ele retorna um valor ou nada, dependendo da natureza do método
(ou seja, tipo de retorno do método).
Return também é uma palavra - chave com o comportamento acima mencionado.

Use return no método (a)


Listagem 1-39.  Use "return" no método

static int getAge ()


{
retorno 20;
}
static void Main (string [] args)

27

Capítulo 1 ■ Fundamentos de C #
{
Console.WriteLine ("Bem-vindo à certificação Exam 70-483
"); int idade = getAge ();
Console.WriteLine ("Idade é:" + idade);
}

Explicação
Neste exemplo, o método getAge () é um tipo de int, portanto, o método retorna o valor do tipo
int e o controle automaticamente vai para onde está chamando, ou seja, int age = getAge () no
método principal. Portanto, o valor retornado pelo método getAge é armazenado na variável
"age".

Usar declaração de retorno no método principal (b)


Listagem 1-40.  Declaração de retorno no método principal

static void Main (string [] args)


{
Console.WriteLine ("Bem-vindo à certificação Exam 70-483
"); Retorna;

Console.WriteLine ("Esta declaração nunca será executada!");


}

Explicação
No segundo exemplo acima, o método retornou o tipo void, o que significa nada, portanto, não
é necessário retornar valor. Nesse caso, usamos a instrução "return" sem um valor, o que ajuda
a pular as instruções restantes do método e saltar o controle de volta para onde o método foi
chamado. Nota:
• Se a instrução de retorno for usada no bloco try / catch e este try /
catch finalmente bloquear, então o bloco finalmente será executado
nessa condição também e depois que o controle voltar ao método de
chamada.
• Dica: o código após a declaração de retorno está inacessível. Portanto, é
aconselhável usar a declaração de retorno dentro do bloco if-else , se
estivermos dispostos a pular a declaração restante do método somente
quando uma determinada condição for atendida. Caso contrário, execute
o método completo.

Métodos em C #
O método contém uma série de instruções de código que executam uma certa funcionalidade.
Isso ajuda a organizar melhor o código, elimina a duplicação e ajuda a reutilizá- lo
repetidamente.

Sintaxe
   

Return_Type Method_Name (Parameter_List)


{    
// TODO: Corpo do método
}
• Method_Name: o método possui um nome exclusivo, que ajuda a
reutilizar a funcionalidade do código sempre que for chamado.

28.

Capítulo 1 ■ Fundamentos de C #
• Return_Type: o método contém uma série de instruções de código que
manipulam dados e geram resultados. Se esse resultado tiver que ser
usado em outro lugar no código, os dados deverão retornar onde são
chamados. Return_Type nos ajuda a descrever, que tipo de dados um
método retornará. Se um método não retornar nenhum valor, use " void"
para seu tipo de retorno.

• Parameter_List: podemos passar valores nos métodos através de


parameter_list. O tipo de valor deve corresponder ao tipo de parâmetro do
método.
• Corpo do método: aqui escrevemos instruções de código que um
método deve conter. Ele é executado apenas quando o método é
chamado pelo nome no código.

Fragmento de código
Suponha que desejemos criar um método que use dois valores int, os adicione e retorne um resultado.

Listagem 1-41.  Retornar um resultado


int Sum (int a, int b)
{
int add = a + b;
return add;
}

Agora, o método tem uma lista de parâmetros. Esses parâmetros estão sendo adicionados
na variável "add"; na próxima instrução, o valor armazenado na variável "add" é retornado.
Observe que o tipo de variável de retorno deve corresponder ao return_type (int) do método.

■■ Nota   Todas as variáveis definidas no corpo do método são variáveis locais .

Argumento nomeado
Um argumento nomeado nos métodos nos ajuda a passar valores em ordem aleatória .
Tudo o que precisamos fazer é usar um nome de parâmetro com dois pontos (:) e passar o
valor para lá. Por exemplo, no exemplo anterior do método, criamos o método "Sum", que
utiliza dois parâmetros "(int a, int b)". Agora, com o argumento nomeado, podemos passar o
valor "b" primeiro e o valor "a" por último.

Listagem 1-42.  Use argumentos nomeados para passar valor durante a chamada do método

Soma (b: 5, a: 10);

Argumento opcional
Quando definimos uma lista de parâmetros na assinatura do método, torna-se obrigatório e
necessário passar valores para todos os parâmetros. O argumento opcional nos dá uma
vantagem de marcar um parâmetro como opcional, para que possamos chamar um método
sem passar valor no parâmetro opcional.
Tudo o que precisamos fazer é atribuir alguns valores padrão aos parâmetros que
devem ser marcados como opcionais. Isso permite que um usuário chame um método sem
passar valores para um parâmetro opcional.

29

Capítulo 1 ■ Fundamentos de C #
Listagem 1-43.  Definir parâmetro opcional
int Sum (int a, int b = 1)
{
int add = a + b;
return add;
}

Agora, observe o parâmetro int b; possui um valor padrão = 1, o que ajuda o usuário a
chamar o método Sum sem passar o valor de "int b". Nesse caso, “int b” teria um valor padrão =
1. Se o usuário passar um valor de “int b”, o valor padrão será substituído pelo valor mais
recente fornecido pelo usuário. Por exemplo,

Listagem 1-44.  Usar o recurso do argumento opcional durante a chamada do método


Soma (10); // a = 10, b = 1
Soma (10, 5); // a = 10, b = 5

Passar por referência com a palavra-chave ref


A palavra-chave ref nos ajuda a passar um argumento por referência. Qualquer alteração no
valor do parâmetro no método refletiria as mesmas alterações no valor da variável original
, onde o argumento do método estava sendo chamado.

Sintaxe
A sintaxe de passar argumento por referência com a palavra-chave "ref" é extremamente
simples. Basta escrever a palavra-chave “ref” antes de definir um parâmetro na assinatura do
método e ao passar um argumento enquanto chama um método.

myMethod (dados de referência);


// use “ref” com o valor do parâmetro quando o método for chamado

void myMethod (ref int d)


{
//FAÇAM:
}
// use "ref" com a definição de parâmetro na definição do método

Exemplo
No código abaixo, estamos incrementando o valor do parâmetro ref em um. As mesmas
alterações refletiriam na variável original em que o método é chamado.

Listagem 1-45.  Altere o valor original da variável passando-o no argumento do método por referência
Programa de aula
{
estático vazio PassByRef (ref int i)
{
i = i + 1;
}

30

Capítulo 1 ■ Fundamentos de C #
static void Main (string [] args)
{
int j = 0;  
PassByRef (ref j);
 
Console.WriteLine (j); // j = 1
}
}

No código acima, consulte a definição do método PassByRef (), a palavra-chave "ref" é


escrita antes de int i, o que nos diz que esse parâmetro levaria referência, não seu valor. Agora
veja também dentro do método Main (), onde PassByRef () está sendo chamado. Aqui também
escrevemos "ref" antes da variável "j", que estamos passando como argumento. Diz para passar
uma referência de "j" (não o valor de "j"), que é basicamente um endereço de memória . Na
próxima declaração, imprimimos o valor de "j". Todas as alterações que ocorrem em PassByRef
() afetam a mesma alteração em "j", o que significa que o valor de "j" agora é 1.

Passe por referência sem palavra-chave


A palavra-chave out funciona da mesma forma que a palavra-chave ref. A diferença é que
podemos passar uma variável não inicializada para o argumento do método usando a
palavra-chave Além disso, é usado para obter mais de um parâmetro de retorno de um
método. Out significa um parâmetro de referência , que significa que a palavra-chave out
passa um argumento por referência.

■■ Nota   O valor de uma variável out deve ser inicializado no corpo do método.
Sintaxe
A sintaxe da palavra-chave out é igual à palavra-chave ref. Escrevemos a palavra-chave
“out” antes de um parâmetro antes de definir um parâmetro na assinatura do método e
antes de passar um argumento ao chamar um método.

MethodName (fora da variávelName);


// use a palavra-chave com o parâmetro quando o método for chamado.

Nome do método nulo (fora v)


{
//FAÇAM:
}
// use a palavra-chave com o parâmetro quando o método for definido

Exemplo
No código abaixo, estamos inicializando um valor de parâmetro no método As mesmas
alterações afetariam a variável, que foi passada como argumento do método.
Listagem 1-46.  Passe a variável vazia no argumento do método através da palavra-chave out e
inicialize-a no corpo do método

Programa de aula
{
static void outMethod (saída int i)
{
i = 1;
}

31

Capítulo 1 ■ Fundamentos de C #
static void Main (string [] args)
{
int j;  
outMethod (out j);  
Console.WriteLine (j); // j = 1
}
}

No método principal, veja que a variável "j" não é inicializada e passada para outMethod.
Ao passá-lo para outMethod, devemos escrever a palavra-chave "out". Dentro do corpo
outMethod, o valor de "I" é inicializado com 1. As mesmas alterações afetam a variável original
no método principal "j", porque o valor de j não é 1.

Usar matriz de parâmetros para passar o argumento do método


ilimitado
O número de argumentos de um método depende do comprimento de uma lista de
parâmetros na assinatura do método. Por exemplo, em nosso exemplo anterior do método
"Sum", não podemos passar mais de dois valores em seus argumentos. Mas, com o array
param, podemos passar um número ilimitado de argumentos.

Sintaxe

A sintaxe do uso da matriz params é simples; basta escrever "params" antes de um tipo
de matriz na lista de parâmetros do método.

void myMethod (params int [] args)


{
//FAÇAM:
}

Exemplo

Suponha que somos obrigados a criar um método que use argumentos ilimitados e retorne
uma soma de todos os valores dos argumentos.

Listagem 1-47.  Adicione argumentos ilimitados e retorne seu resultado

int Sum (params int [] args)


{
int add = 0;

foreach (item int em args)


{
adicionar = adicionar + item;
}

return add;
}
32.

Capítulo 1 ■ Fundamentos de C #
No snippet de código acima, veja que a palavra-chave "params" é usada antes da matriz
int. O código escrito no corpo do método é simples e direto. Ele está iterando sobre todos os
argumentos e adicionando cada um deles com o próximo valor. No final, ele retorna a soma
de todos os argumentos.

Soma (1, 2, 3, 4, 5); // retornar 15

Na linha acima, o método Sum está sendo chamado, passou 5 argumentos e retorna uma
soma de 5 argumentos (que é 15).

■■ Nota   1 - Um método shouldn ' t têm matriz mais do que um parâmetro.


2 - Se houver mais de um parâmetro, o array de parâmetros deve ser o último.

Sumário
• var é um tipo implícito; ele pode armazenar dados de qualquer tipo em tempo de compilação.
• Operadores são símbolos especiais que manipulam dados para produzir um resultado
necessário.
• C # é uma linguagem fortemente tipada.
• Sem perda de dados na conversão implícita de tipo. Nenhuma sintaxe
especial é necessária para a conversão implícita de tipos.
• Os dados podem ser perdidos na conversão de tipo explícita. Sintaxe
especial necessária para conversão de tipo explícita.
• Matriz irregular é uma matriz de uma matriz, o que significa que o
número de linhas na matriz irregular é fixo, mas o número de colunas
não é fixo.
• Use a palavra-chave “ref” no parâmetro do método para passar dados por sua referência.
• Use "params array" para passar argumentos ilimitados nos métodos.
• Use switch quando tivermos constantes para comparar.
• Para repetir instruções repetidamente, use loops.
• Para iterar sobre a coleção, use o loop foreach.
• Use instruções de salto (ou seja, vá para, quebre, continue e retorne) para
alterar o fluxo normal do programa.

Desafios de código
Desafio 1: Desenvolva um aplicativo de calculadora aritmética
Desenvolva um aplicativo de calculadora que ajude os usuários a adicionar, subtrair, multiplicar ou
dividir dois valores.

33
Capítulo 1 ■ Fundamentos de C #
A saída deve ser como:

Pressione qualquer tecla a seguir para executar uma operação aritmética:

1 - Adição
2 - Subtração
3 - Multiplicação
4 - Divisão

11
Digite o valor 1: 10
Digite o valor 2: 20

10 + 20 = 30

Deseja continuar novamente (S / N)?

Dicas:
• Use um método separado para +, -, *, /
• Use a estrutura de caixa de chave para selecionar a opção do usuário.
• Use enquanto loop para repetir o programa até o usuário pressiona “n”.

Desafio 2: Desenvolver um aplicativo de boletim escolar


Desenvolver um aplicativo de boletim que salve as informações das marcas dos alunos;
mostre a posição e o boletim de cada aluno em ordem decrescente.

Exigências
• Cada aluno tem três disciplinas (inglês, matemática e computador).
•O aplicativo salvará as notas de cada aluno junto com o nome do aluno.
•O aplicativo calculará as notas totais.
•O aplicativo mostrará a posição e o boletim em ordem decrescente.

A saída deve ser como:


Pressione qualquer tecla a seguir

Digite o total de alunos: 2

Digite o nome do aluno: Lakhtey

Digite as marcas em inglês (de 100): 50

Inserir marcas matemáticas (de 100): 60

Inserir marcas de computador (em 100): 30

34

Capítulo 1 ■ Fundamentos de C #
*********************************************
Digite o nome do aluno: Ali Asad

Digite as marcas em inglês (de 100): 60

Inserir marcas matemáticas (de 100): 70

Inserir marcas de computador (em 100): 30


****************Boletim*******************

****************************************
Nome do aluno: Ali Asad, Posição: 1, Total: 160/300

****************************************

Nome do aluno: Lakhtey, Posição: 2, Total: 140/300

****************************************

Dicas:
• Use uma matriz multidimensional para armazenar as informações do aluno.
• Use loops para iterar as informações de cada aluno para gerar um relatório.

Practice Exam Questions


Questão 1
Qual dos seguintes métodos nos ajuda a converter dados do tipo string em números inteiros? Selecione
dois.
\ UMA)\Convert.toInt32 ();
\ B) \ Convert.Int32 ();

\ C) \ int.parse ();
\ D) \ parse.int ();

Questão 2
Suponha que você esteja implementando um nome de método "Show" que possa receber um
número ilimitado de argumentos int. Como você vai definir a assinatura do método?
\ A) \ Show nulo (int [] arg)
\ B) \ void Show (params int [] arg)
\ C) \ Show nulo (int a)
\ D) \ Show nulo (ref int a)

35

Capítulo 1 ■ Fundamentos de C #
Questão 3
Você está desenvolvendo um aplicativo que salva as informações do usuário. O aplicativo
inclui o seguinte segmento de código (números de linha incluídos para referência).

01 public bool IsNull (nome da string)


02 {
03 retornam verdadeiro;
04 }

Você precisa avaliar se um nome é nulo.


Qual segmento de código você deve inserir na linha 03

A)
if (nome = nulo)
{
return true;
}
B)
if (nome == nulo)
{
return true;
}

C)
if (nulo)
{
return true;
}

D)
if (! name)
{
return true;
}

Pergunta 4
Você precisa usar o operador de coalescência nula para garantir que a variável "name" tenha
um valor que não seja nulo. Selecione a maneira correta de usar o operador de
coalescência nula em C #.
\ A) \ nome da string = n ?? "Sem nome";
\ B) \ string name = "No Name" ?? nulo;
\ C) \ string name = "Sem nome"? nulo;
\ D) \ nome da cadeia = nulo? "Sem nome";

36.

Capítulo 1 ■ Fundamentos de C #
Questão 5
Qual instrução de salto você usará para iniciar a próxima iteração enquanto ignora a iteração atual do
loop?
\ A) \ Quebra
\ B) \ Continuar
\ C) \ Saltar
\ D) \ Retorno

Respostas
\ 1. \ A, C
\ 2. \ B
\ 3. \ B
\ 4. \ UMA

\ 5. \ B
37.

CAPÍTULO 2

Tipos em c #

C # é uma linguagem fortemente tipada . Ele diz que os dados devem ter um tipo que defina
sua natureza e comportamento. O tipo ajuda a manipular os dados de maneira muito
gerenciada . Abordaremos os seguintes objetivos principais que ajudam a criar e consumir
tipos em C #.
\ 1. \ Entenda os tipos
\ 2. \ Criar tipos
\ 3. \ Tipos e gerenciamento de memória
\ 4. \ Tipos especiais em C #
\ 5. \ Conversão de tipo

Compreender os tipos
Tipos são a declaração de um objeto que armazena informações e ações que um objeto usa
para produzir os resultados necessários. Tipo também armazena as seguintes informações:
• Quanta memória um objeto contém
• Local da memória onde o objeto está armazenado na memória
• O tipo de base herdado de

Essas informações ajudam o compilador a garantir que tudo seja do tipo seguro. No
capítulo anterior, aprendemos como criar variáveis usando tipos internos comuns , como
int, float e bool .
Fragmento de código

Listagem 2-1. Tipos internos em C #

int idade = 22;


nome da string = "Ali Asad";

Criar tipos
O C # permite que os usuários criem seus próprios tipos usando:
\ 1. \ Enum
\ 2. \ Struct
\ 3. \ Class

© Ali Asad e Hamza Ali 2017 39.


A. Asad e H. Ali, O Guia de Estudo do Programador C # (MCSD) , DOI 10.1007 / 978-1-4842-2860-9_2
Capítulo 2 ■ Tipos em C #
Enum
Enum, também conhecido como enumeração, é um conjunto de constantes inteiras
nomeadas. É usado para agrupar constantes similares nomeadas logicamente (por exemplo,
dias da semana e cores do arco-íris etc.).
Sintaxe

enumeração MyEnum
{
// lista de constantes nomeadas
}

Fragmento de código

Listagem 2-2.  Use enum dentro da estrutura de caixa de comutação


enum Status
{
Vivo,
Ferido,
morto
}

Programa de aula
{
static void Main (string [] args)
{
Status player = Status.Alive;

mudar de jogador)
{
case Status.Alive: //
Código do Alive
break;

case Status.Injured: //
DO Injury code
break;

case Status.Dead: //
Código Morto
break;

}
}
}

• Status player = Status.Alive; diz ao jogador que está vivo.

40.

Capítulo 2 ■ Tipos em C #

■■ Nota   Nas estruturas de decisão, as enumerações são usadas principalmente com


uma instrução switch que usa o valor constante da enumeração para saltar rapidamente
para um bloco de caso específico.

• Enums são utilizáveis para usar valores constantes.


• Enums são legíveis e fornecem apenas as informações necessárias no código.
• Enums são fortemente tipados. Portanto, uma enumeração de um
tipo não pode ser implicitamente atribuída a uma enumeração de
outro tipo.

Enum e Inteiro
Por padrão, o primeiro valor da constante nomeada do enum é sempre "0" e o valor de
cada enumerador sucessivo é aumentado em "1".
Por exemplo, ao seguir as constantes da enumeração, o valor de Alive é 0, Injured é 1 e Dead é 2.

Listagem 2-3.  Valor padrão das constantes de enum


enum Status
{
Vivo, // 0
Ferido, // 1
Morto // 2
}

Para obter o valor inteiro de cada constante, é necessária uma conversão explícita.

int valueOfAlive = (int) Status.Alive;

valueOfAlive armazenaria '0'.

Substituir valores da constante


Use inicializadores para substituir o valor padrão das constantes da enumeração. Portanto, as
constantes subsequentes são forçadas a incrementar seus valores a partir do valor de
substituição. No seguinte snippet de código, Alive é inicializado com o valor 2. Portanto, feridos
terão 3 e Dead terá 4.

Listagem 2-4.  Inicialize os valores constantes do enum

enum Status
{
Vivo = 2,
Ferido, // 3
Morto // 4
}

41.

Capítulo 2 ■ Tipos em C #
Tipos de suporte
O Enum suporta os seguintes tipos para os valores de suas constantes:
• byte
• sbyte
• curto
• ushort
• int
• uint
• longo
• ulong

Impor enum para armazenar o valor no tipo acima.

Listagem 2-5.  Altere o tipo de constantes da enum para "byte"


enumeração Status: byte
{
Vivo = 1,
Ferido,
Morto
}

O tipo de valor de Alive seria byte. Isso ajuda a minimizar o armazenamento de memória necessário
para inicializar uma enumeração.

Struct
Struct é usado para encapsular o atributo e o comportamento de uma entidade. É usado para
definir os objetos que contêm pouca memória. A maioria dos tipos primitivos (int, float, bool)
em C # são compostos de struct. O Struct não suporta todos os princípios orientados a objetos .
Sintaxe

struct NameOfType
{
// ...
}

• struct é uma palavra-chave, usada para declarar um tipo.

Fragmento de código

Listagem 2-6.  Defina e use o tipo personalizado "Vector" com struct


Vetor de struct
{
public int x;
público int;
}

42.

Capítulo 2 ■ Tipos em C #
Programa de aula
{
static void Main (string [] args)
{
Vetor vector = new Vector ();
vector.x = 5;
vector.y = 10;

Console.WriteLine ("x = {0}", vetor.x);


Console.WriteLine ("y = {0}", vetor.y);
}
}

• Vetor vetorial = novo vetor (); declare e inicialize o tipo personalizado


Vector com nova palavra-chave.
• vetor.x = 5; atribua um valor 5 ao seu atributo "x".

Os dados encapsulados por struct são seus membros de dados . As variáveis são
conhecidas como campos e funções incluídas nele, denominadas funções-membro.
struct não está limitado a campos, mas também pode ter funções, construtores,
indexadores, eventos, tipos aninhados e uma interface implementada .

Construtor em struct
O construtor é um método chamado e executado primeiro pelo tempo de execução, logo após a
instância do tipo ser criada na memória. Não possui um tipo de retorno. É usado para
inicializar membros de dados que protegem o aplicativo de qualquer erro de cálculo de lixo.
• Construtor padrão (parâmetro menos) não é permitido em struct.
•O construtor é opcional em struct, mas se incluído, não deve ser sem parâmetros.
•O construtor pode estar sobrecarregado, mas cada construtor
sobrecarregado deve inicializar todos os membros de dados.
• Membros ou campos de dados não podem ser inicializados no corpo da
estrutura. Use o construtor para inicializá-los.
• Criar o objeto (sem uma nova palavra-chave) não causaria a chamada
do construtor, mesmo que um construtor esteja presente.

Sintaxe

struct TypeName
{
public TypeName (lista de parâmetros)
{
// inicializa campos
}
// declarar campos
}

43

Capítulo 2 ■ Tipos em C #
Fragmento de código

Listagem 2-7.  Definir construtor de parâmetros em struct

Vetor de struct
{
//Construtor
Vetor público (int a, int b)
{
// Inicializar
campos x = a;

y = b;
}

// Campos
public int x;
público int;
}

Programa de aula
{
static void Main (string [] args)
{
// Inicializa o vetor, passando o valor 5,10 para seu construtor
Vector vector = new Vector (5, 10);

Console.WriteLine ("x = {0}", vetor.x);


Console.WriteLine ("y = {0}", vetor.y);
}
}

• novo vetor (5, 10); passou 5,10 valores para seu construtor. Portanto, ele
pode inicializar seus campos.

■■ Nota   Não use struct para definir tipos complexos.

esta palavra-chave
essa palavra-chave indica a instância atual . É uma variável do tipo de referência
especial usada para chamar o membro de uma instância dentro da definição de método
não estático .
esta palavra-chave tem muitos usos:
• Passar um objeto em si como parâmetro para outros métodos.
• Para retornar um objeto em si a partir de um método.
• Declarar um método de extensão.
• Para eliminar o conflito de nomenclatura do nome da variável e do campo de instância de um
parâmetro.

44

Capítulo 2 ■ Tipos em C #
Fragmento de código

Listagem 2-8.  Use o operador "this" para acessar o membro da instância de um tipo Vector

Vetor de struct
{
//Construtor
Vetor público (int x, int y)
{
// Inicializa campos
this.x = x; this.y = y;

// Campos
public int x;
público int;
}

• this.x se refere à variável de instância x (public int x).


• this.x = x; aqui x é uma variável local (parâmetro do método) que
armazena o valor em uma variável de instância “x” (public int x).

Classe
Classe é usada para encapsular o atributo e o comportamento de uma entidade. Ele suporta
princípios orientados a objetos . Portanto, as classes são úteis para definir tipos complexos.
Sintaxe

classe <class_name>
{
// Campos
// propriedades
// Construtores
// métodos
// eventos
// delegados
// classes aninhadas
}

Fragmento de código

Listagem 2-9.  Defina e use o tipo personalizado com "classe"


classe Pessoa
{
nome público da
string; idade
pública;

45

Capítulo 2 ■ Tipos em C #
exibição pública nula ()
{
Console.WriteLine ("Nome = {0} Idade = {1}", nome, idade);
}
}

Programa de aula
{
static void Main (string [] args)
{
Pessoa pessoa = nova Pessoa
(); person.name = "Hamza
Ali"; person.age = 20;

person.Display ();
}
}

Construtor na Classe
O construtor é um método, chamado e executado primeiro pelo tempo de execução, logo após a
instância do seu tipo ser criada na memória. Não possui um tipo de retorno. É usado para
inicializar membros de dados que protegem o aplicativo de qualquer erro de cálculo de lixo.
Sintaxe

classe TypeName
{
public TypeName ()
{
// inicializa o membro de dados
}
// declarar membro de dados
}

Fragmento de código

Listagem 2-10.  Definir construtor padrão na classe

classe Pessoa
{
nome público da
string; idade
pública;
// Construtor padrão
public Person ()
{
nome =
"NILL"; idade
= -1;
}
}

46.

Capítulo 2 ■ Tipos em C #
• O construtor padrão não possui um parâmetro.
•A classe também pode ter um construtor parametrizado.

Construtor Base
Classe pode ter muitas classes derivadas. Uma classe derivada herda atributos e métodos de sua
classe base. Se uma classe base tiver um construtor parametrizado, sua classe derivada deverá
passar valores para inicializar o construtor da classe base.
Sintaxe

classe DerivedClass: BaseClass


{

public DerivedClass (tipo x): base (x)


{
}
}

• base (..) chama e passa valores para um construtor parametrizado de BaseClass.


•O construtor de classe derivada tem pelo menos os mesmos parâmetros
do construtor de BaseClass. Portanto, ele poderia passar valor ao
construtor de sua classe base.

Fragmento de código

Listagem 2-11.  Passar valor para o construtor da classe pai


classe Pessoa
{
nome da string
protegida; idade
protegida;

Pessoa pública (nome da sequência, idade int)


{
this.name =
nome; this.age =
idade;
}
}

class Employee: Person


{

Funcionário público (sequência n, int a): base (n, a)


{
// ...
}
}

Programa de aula
{

47

Capítulo 2 ■ Tipos em C #
static void Main (string [] args)
{
Funcionário emp = novo funcionário ("Hamza", 20);
}
}

• Funcionário emp = novo funcionário (“Hamza”, 20); ele chama e


passa valores para um construtor parametrizado de BaseClass.

Tipos e gerenciamento de memória


No tópico acima, aprendemos como criar tipos usando enum, struct e classe . O C # tem um
conceito para definir esses termos em: Tipo de valor e referência .

Tipo de valor
Um tipo que é definido por struct ou enum é conhecido como um tipo de valor . Ele contém
dados em sua própria alocação de memória.

Tipo de referência
Um tipo que é definido por classe, interface ou delegado é conhecido como um tipo de
referência . Ele contém um ponteiro para um local de memória que contém dados chamados
tipo de referência.
Em uma estrutura .NET, o CLR gerencia instâncias de valor e tipo de referência em três locais de
memória:
\ 1. \ Heap
\ 2. \ Stack
\ 3. \ Registros

Montão
É um local de memória onde as instâncias do tipo de referência são armazenadas.
Instâncias do tipo de valor também podem ser armazenadas no heap quando:
• tipo de valor faz parte de uma classe;
•o tipo de valor está em caixa;
• tipo de valor é uma matriz;
• tipo de valor é uma variável estática;
•o tipo de valor é usado em um bloco assíncrono ou iterador;
• tipo de valor são locais fechados de um método lambda ou anônimo.

Instâncias do tipo de valor permanecem mais longas apenas quando qualquer um dos
casos acima for verdadeiro. O heap é um local ideal para instâncias com vida útil mais longa.
Por exemplo, no seguinte trecho de código, veja como o CLR gerencia a alocação de memória.

endereço da string = "Sialkot, Punjab";

48.
Capítulo 2 ■ Tipos em C #
A figura 2-1 a seguir mostra o Heap de memória gerenciada.

Figura 2-1.  Pilha de memória gerenciada

■■ Nota   O tamanho da memória da pilha é maior que o tamanho da pilha e do registro.

Pilha
É um local de memória onde instâncias temporárias de curta duração do tipo de valor e o
endereço de memória de um objeto podem ser armazenados. As variáveis temporárias de
curta duração que o Stack pode armazenar são:
• tipos de valor declarados dentro de um corpo de método ou dentro de uma lista de
parâmetros;
• o endereço de memória de uma instância de um tipo de referência.

Ele usa o algoritmo LIFO (Last In First Out) para gerenciar o tempo de vida de cada
variável em uma pilha. Seu tamanho de memória é relativamente menor que o heap, como
mostra a Figura 2-2 .
Por exemplo, no seguinte trecho de código, veja como o CLR gerencia a alocação de memória.

int idade = 22;

49.

Capítulo 2 ■ Tipos em C #
Figura 2-2.  Pilha

Registro
É um local de memória onde instâncias de instâncias temporárias de curta duração do tipo de
valor ou valores de computação de operações aritméticas são armazenadas no registro. Seu
tamanho de memória é relativamente menor que o Stack. Depende do CLR, que decide quais
instâncias de memória de curta duração são armazenadas na pilha ou no registro.

Tipos especiais em c #
O C # fornece tipos especiais que são açúcar sintático para os usuários. Esses tipos ajudam
os usuários a maximizar sua produtividade escrevendo código auxiliar dentro deles. Esses
tipos especiais estão listados abaixo.
• Tipo System.Object
• tipo anônimo
• tipo dinâmico
• tipo anulável
• tipo estático

Tipo System.Object
Todos os tipos de valor e referência são derivados do tipo system.object. No .NET, o objeto
é a base de toda a hierarquia de tipos. A Figura 2-3 abaixo mostra a hierarquia de tipos do
system.object.

50.

Capítulo 2 ■ Tipos em C #

Figura 2-3.  Hierarquia de tipos do System.object


Como todos os tipos no .NET são derivados de system.object, ele pode manipular valores de
todos os tipos. Por exemplo, no seguinte snippet de código, o objeto armazenou os valores de
string e int.

Listagem 2-12.  Armazene os dados de qualquer tipo no tipo system.object

nome do objeto =
"Ali"; idade do objeto
= 22;

Todo tipo no .NET herda métodos do tipo system.object, que eles podem substituir . Esses métodos
são:
• Igual ao uso para comparar dois objetos.
• Finalize o uso para executar operações de limpeza antes que o objeto seja destruído.
• GetHashCode usado para obter o valor de hash do objeto no HashTable.
• ToString usado para obter as informações do objeto em forma de texto.

Tipo anônimo
Tipos que não têm nomes são chamados tipos anônimos. Eles são usados para agrupar dados
temporários em propriedades somente leitura. Diferentemente da classe, os tipos anônimos
não têm um blueprint para definir os tipos de propriedade. Portanto, cada propriedade deve
ter um dado para determinar seu tipo de propriedade.
Tipos anônimos são criados usando um novo operador com inicializador de objetos. A
variável implícita do tipo var é usada para manter a referência de tipos anônimos.
Sintaxe

var variableName = new {/ * inicializador de objetos * /};

51

Capítulo 2 ■ Tipos em C #
Fragmento de código

Listagem 2-13.  Definir e usar tipo anônimo em C #

var pessoa = nova {Nome = "Ali", Idade = 22};

Console.WriteLine ("Name = {0}", person.Name);


Console.WriteLine ("Age = {0}", person.Age);

// Saída
Ali
22

Observações
• No tipo anônimo, o valor da propriedade não pode ser nulo .
• Tipo anônimo não possui definição de método .
• Tipos anônimos são do tipo classe .
•O tipo anônimo não pode ser usado como parâmetro do método do tipo de retorno .
•O tipo anônimo é útil para armazenar o resultado da consulta da coleção .

Tipo Dinâmico
O tipo dinâmico é usado para armazenar e manipular quaisquer dados cuja definição de tipos
e erros de operação sejam determinados em tempo de execução. Ignora verificações em
tempo de compilação . Portanto, é fácil acessar APIs COM e DOM com tipo dinâmico.

É
É definido usando uma palavra-chave dinâmica.
Sintaxe

variável variávelName = data;

Fragmento de código

Listagem 2-14.  Use tipo dinâmico

preço dinâmico = 20;


Console.WriteLine (price.GetType
());

nome dinâmico = "Ali";


Console.WriteLine (name.GetType
());

//Resultado
System.Int32
System.String

Diferentemente do tipo implícito (palavra-chave var), o tipo dinâmico pode


armazenar valores de tipos diferentes com a mesma variável dinâmica. Portanto, uma
variável dinâmica pode alterar seu tipo em tempo de execução.

52

Capítulo 2 ■ Tipos em C #
Listagem 2-15.  Alterar o tipo de variável dinâmica em tempo de execução
dinâmico i = "Ali";
Console.WriteLine (i.GetType ());

i = 22; Console.WriteLine (i.GetType ());


//Resultado

System.String
System.Int32

Observações
• O tipo dinâmico pode ser usado para campo, propriedade, indexador,
parâmetro de método e tipo de retorno .
•A exceção é lançada em tempo de execução se o tipo de dados ou o nome do membro não for
compatível.

Tipo Anulável
Normalmente, o tipo de valor não pode ser atribuído com valor nulo. Mas com o tipo anulável,
o tipo de valor pode ser atribuído com valor nulo.
O tipo de valor pode se tornar do tipo anulável usando “ ? ”.
Sintaxe

valueType? variableName = null;

Fragmento de código

Listagem 2-16.  Tipo anulável de bool

bool? isMarried = null;


isMarried = true;
?? Operador
Use coalescência nula "??" com tipo anulável para tipo não anulável .

Listagem 2-17.  Usar operador de coalaescência nula com tipos anuláveis


bool? isMarried = null;
bool casado = isMarried ?? falso;

Pontos importantes
• Anulável <T> é uma alternativa de "?" operador. O exemplo acima pode ser escrito como
Anulável <bool> isMarried = null;
•O tipo de valor é colocado em caixa sempre que se torna anulável.

53

Capítulo 2 ■ Tipos em C #
Tipo estático
Diferentemente das classes normais, as classes estáticas não podem ser instanciadas . Eles são
úteis para definir métodos estáticos auxiliares. A classe estática não possui nenhum membro
da instância. Todos os membros devem ser estáticos na classe estática. Portanto, os membros
de classes estáticas podem acessar usando o próprio nome da classe .
A classe estática é definida escrevendo uma palavra-chave estática antes da definição da classe.
Sintaxe

classe estática MyStaticClass


{
// define membros estáticos
}

Code Snipeet

Listagem 2-18.  Definir método estático dentro da classe estática

auxiliar de classe estática


{
public static void MyMethod ()
{
// ...
}
}

Listagem 2-19.  Acessar método estático

Para acessar MyMethod (), use o seguinte código:


Helper.MyMethod ();

Construtor estático
Diferente da classe normal, a classe estática não contém um construtor de instância pública
. Ele contém um construtor estático privado para inicializar membros estáticos. É chamado
automaticamente antes da criação da primeira instância ou da referência a qualquer membro
estático.
Fragmento de código

Listagem 2-20.  Definir construtor estático


auxiliar de classe estática
{
estática pública int idade;
Auxiliar estático ()
{
idade = 22;
}

54

Capítulo 2 ■ Tipos em C #
Métodos de extensão
Métodos de extensão são métodos estáticos especiais. Eles injetam métodos de adição sem
alterar, derivar ou recompilar o tipo original. Eles são sempre chamados como se fossem um
método de instância.
• Os métodos de extensão são sempre definidos dentro da classe estática .
• O primeiro parâmetro do método de extensão deve ter o operador
"this" , que informa em qual instância esse método de extensão deve
fornecer acesso.
• O método de extensão deve ser definido no mesmo espaço para nome em
que é usado ou importar o espaço para nome no qual o método de
extensão foi definido.

Sintaxe

classe estática pública ExtensionClass


{
public static void ExtensionMethod (esta origem int)
{
// ...
}
}

Fragmento de código

Listagem 2-21.  Definir e usar o método de extensão

Extensão de namespace
{
classe estática pública ExtensionClass
{
bool estático público isLessThan (esta origem int, int compareValue)
{
// retorna true se o valor da origem for
menor se (origin <compareValue)
return true;
outro
retorna falso;
}
}

Programa de aula
{
static void Main (string [] args)
{
int idade = 22;
verificação bool = age.isLessThan (30);

Console.WriteLine (cheque);
}
}

}
// saída
True
55

Capítulo 2 ■ Tipos em C #
idade é uma variável inteira; é chamado o método de extensão isLessThan . Lembre-se,
isLessThan não é definido por uma estrutura .NET para números inteiros. Sua funcionalidade
é adicionada usando o método de extensão.
O valor "30" é passado no parâmetro isLessThan como compareValue , enquanto essa
origem int se refere à variável idade em si.

Conversão de tipo
A conversão de um tipo para outro é chamada de conversão de tipo. A conversão de tipo tem três formas:
\ 1. \ Conversão implícita de tipo
\ 2. \ Conversão de tipo explícita
\ 3. \ Conversão de tipo definido pelo usuário

Conversão implícita de tipo


Se um tipo é convertido em outro automaticamente, isso é chamado de conversão implícita de
tipo. Isso é feito pelo compilador automaticamente. Um exemplo comum é a conversão da
classe derivada em classe base.

classe A {...}
classe B: A {...} A
a = novo B ();

Conversão explícita de tipos


Se um tipo precisa de sintaxe especial para convertê-lo em outro, é chamado de conversão
explícita de tipo. Isso é feito pelo usuário . Um exemplo comum é a conversão da classe base
em classe derivada.
A conversão explícita de tipos é feita de duas maneiras:
\ 1. \ como palavra-chave
O valor \ 2. \ (type) com is keyword

como operador
como é uma palavra-chave usada para converter explicitamente um tipo para outro. Se um
tipo for convertido com êxito, ele retornará valor nesse tipo. Se um tipo não for convertido
corretamente, ele retornará valor nulo .

Listagem 2-22.  Use a palavra-chave "as" para conversão de tipo explícita

classe A {...}
classe B: A {...} A
a = novo B ();

// converte explicitamente do tipo A


para BB b = a como B;

56.
Capítulo 2 ■ Tipos em C #
é operador
É uma palavra-chave usada para corresponder a um tipo . Se um tipo corresponde, ele
retorna true; caso contrário, ele retornará false. Um uso comum de é keyword vem com
(tipo) conversão de tipo explícita.
(type) é usado para converter um tipo explicitamente. Se um tipo for convertido com
êxito, ele retornará um valor nesse tipo. Se um tipo não é convertido corretamente, gera uma
exceção . Para evitar essa exceção, é comum verificar o tipo dentro da caixa de areia do
operador is .

Listagem 2-23.  Use is keyword para combinar um tipo com outro tipo

classe A {...}
classe B: A {...} A
a = novo B ();

se (a é B)
{
// converte explicitamente do tipo A
para BB b = (B) a;
}

■■ Nota   Para evitar qualquer exceção de conversão, usamos a palavra-chave is


para verificar se um tipo pode ser conversível ou não.

Conversão de tipo definido pelo usuário


O C # permite que os usuários forneçam a definição de conversão para seu tipo
personalizado . Sua definição é semelhante à definição de sobrecarga do operador.
A conversão definida pelo usuário é de dois tipos:
\ 1. \ Conversão definida pelo usuário implícita
\ 2. \ Conversão definida pelo usuário explícita

Conversão implícita definida pelo usuário


Um usuário pode definir uma definição implícita de conversão em um tipo que ajude a
convertê-la em outro tipo. A conversão implícita ocorre automaticamente.
Para conversão implícita, um método estático especial é definido com uma palavra-
chave implícita e de operador dentro da definição de tipo.
Sintaxe

classe MyClass
{
operador implícito estático público returnType (tipo t)
{
// ...
}
}

57

Capítulo 2 ■ Tipos em C #
• returnType informa que tipo de dados seria retornado na conversão implícita.
• type t informa qual tipo seria convertido implicitamente.

Fragmento de código
Listagem 2-24.  Definir definição implícita de conversão de tipo
classe Byte
{
público int bits = 8;
operador implícito estático público int (Byte b)
{
retorno b.bits;
}

Programa de aula
{
static void Main (string [] args)
{
Byte b = novo Byte (); int
totalBits = b;
Console.WriteLine (totalBits);

}
}
//Resultado
8

Aqui, o byte " b " obteria uma conversão implícita em "int" retornando o número total de bits em um
byte.

Conversão definida pelo usuário explícita


Um usuário pode definir uma definição de conversão explícita em um tipo que ajude a convertê-
la em outro tipo. A transmissão é necessária para converter um tipo em outro. Os dados podem
ser perdidos na conversão explícita.
Para conversão explícita, um método estático especial é definido com uma palavra-
chave explícita e operator dentro da definição de tipo.
Sintaxe

Listagem 2-25.  Definir definição de conversão de tipo explícita


classe MyClass
{
operador explícito estático público returnType (tipo t)
{
// ...
}
}

58

Capítulo 2 ■ Tipos em C #
• returnType informa que tipo de dados seria retornado na conversão implícita.
• type t informa qual tipo seria convertido implicitamente.

Fragmento de código

classe Pessoa
{
public int Age {get; conjunto; }
public string Nome {get;
conjunto; }

sequência de operadores explícita estática pública (Pessoa por)


{
retorno por.Nome;
}
}

Programa de aula
{
static void Main (string [] args)
{
Pessoa por = nova pessoa {Idade = 22, Nome = "Ali"};

nome da string = (string)


por; Console.WriteLine
(nome);
}
}
// Saída
Ali

onde (string) por ; lança os dados de uma pessoa em "string" explicitamente, retornando o nome da
pessoa.

Sumário
• Os valores da constante de enum podem substituir o valor inteiro.
O construtor Struct deve inicializar todos os membros de dados .
• O valor padrão do tipo de referência é sempre nulo .
• Tipos definidos com struct e enum são exemplos de tipos de valor.
• Tipos definidos com classe, interface e delegados são exemplos de tipo de referência.
•O tipo System.Object é a classe base de todos os tipos na hierarquia C #.
• Tipos anônimos devem ter uma ou mais propriedades somente leitura .
• Tipos dinâmicos são úteis para interagir com COM, DOM e APIs dinâmicas.
•O tipo de valor pode armazenar nulo quando declarado como nulo "?" .

59.

Capítulo 2 ■ Tipos em C #
• Tipos estáticos não podem ser instanciados .
• Os tipos estáticos têm apenas membros estáticos.
• Os métodos de extensão são definidos apenas dentro da classe estática
para estender a funcionalidade de um tipo de instância .
• A conversão especial é necessária para a conversão explícita de tipos.
• como operador é usado para converter um tipo em outro tipo.
•O usuário pode escrever sua definição para conversão de tipo usando
palavras-chave implícitas e explícitas com métodos estáticos
especiais .

Desafios de código
Desenvolver aplicativo conversor de temperatura
O aplicativo possui duas classes: FahrenheitTemperature e CelsiusTemperature.
FahrenheitTemperature armazena temperatura em Fahrenheit e CelsiusTemperature
armazena temperatura em Celsius. Você precisa definir métodos de conversão em ambas as
classes para converter Fahrenheit em Celsius implicitamente e vice-versa.

Practice Exam Questions


Questão 1
Suponha que você esteja desenvolvendo um aplicativo que salva o valor da idade em números inteiros.

int idade = 22;

Você é solicitado a selecionar o snippet de código correto para definir o método de extensão para a idade.

A)
Extensão de classe
{
public static void ExtensionMethod (int i)
{
// ...
}
}

B)
Extensão de classe estática
{
public static void ExtensionMethod (int i)
{
// ...
}
}

60

Capítulo 2 ■ Tipos em C #
C)
Extensão de classe estática
{
public static void ExtensionMethod (este int i)
{
// ...
}
}

D)
Extensão de classe estática
{
public static void ExtensionMethod (int i)
{
// ...
}
}

Questão 2
Qual operador é usado para comparar tipos?
\ UMA)\Como
\ B) \ é

\ C) \ isto
\ D) \ ?
Questão 3
Escolha o segmento de código correto para definir a conversão implícita de tipo para uma classe Person.

A)
classe Pessoa
{
nome público da
string; idade
pública;

operador implícito estático público this [int i]


{
this.age = i;

devolva isso;
}
}

61

Capítulo 2 ■ Tipos em C #
B)
classe Pessoa
{
nome público da
string; idade
pública;

operador implícito estático público Person (string n)


{
Pessoa pessoa = nova Pessoa {idade = 0, nome = n};
pessoa de retorno;
}
}

C)
classe Pessoa
{
nome público da
string; idade
pública;

Pessoa implícita estática pública (string n)


{
Pessoa pessoa = nova Pessoa {idade = 0, nome = n};
pessoa de retorno;
}
}

D)
classe Pessoa
{
nome público da
string; idade
pública;

operador implícito estático público Person (esta sequência n)


{
Pessoa pessoa = nova Pessoa {idade = 0, nome = n};
pessoa de retorno;
}
}

Pergunta 4
Qual operador é usado para obter dados da instância dentro da definição de tipo?
\ UMA)\ Como
\ B) \ é
\ C) \ isto

\ D) \ ?

62

Capítulo 2 ■ Tipos em C #
Questão 5
Que tipo não pode ser instanciado?
\ A) \ tipo de enum
\ B) \ tipo estático
\ C) \ tipo de classe
\ D) \ System.Object

Respostas
\ 1. \ C
\ 2. \ B
\ 3. \ B
\ 4. \ C

\ 5. \ B
63.

CAPÍTULO 3

Introdução à Programação
Orientada a Objetos

O C # fornece suporte total à programação orientada a objetos . Neste capítulo, você


percorrerá os seguintes tópicos de POO:
\ 1. \ Introdução à Programação Orientada a Objetos
\ 2. \ OOP em uma TORTA
\ 3. \ Encapsulamento
\ 4. \ Herança
\ 5. \ Polimorfismo

Introdução à Programação Orientada a Objetos


A programação orientada a objetos (OOP) é uma técnica de design de software que ajuda a
organizar dados e métodos em um único objeto. Ajuda os objetos a conversar entre si,
definindo relacionamentos entre eles.
Em uma entrevista da Rolling Stone em 1994, Steve Jobs (CEO da Apple) explica a
programação orientada a objetos. Sua explicação ainda nos ajuda a aprender o que é POO em
termos simples.

Jeff Goodell: Você explicaria, em termos simples, exatamente o que é software


orientado a objetos ?
Steve Jobs: Os objetos são como pessoas . Eles estão vivendo, respirando coisas
que têm conhecimento dentro deles sobre como fazer as coisas e têm memória
dentro delas para que possam se lembrar das coisas. E, em vez de interagir com
eles em um nível muito baixo, você interage com eles em um nível muito alto de
abstração , como estamos fazendo aqui.
Aqui está um exemplo: se eu sou seu objeto de lavanderia , você pode me dar
suas roupas sujas e me enviar uma mensagem que diz: "Você pode lavar minhas
roupas, por favor". Por acaso, sei onde fica a melhor lavanderia de São Francisco.
E eu falo inglês e tenho dólares nos bolsos. Então eu saio, peguei um táxi e pedi ao
motorista que me levasse a este lugar em San Francisco. Vou lavar suas roupas,
pulo de volta no táxi, volto aqui. Dou-lhe suas roupas limpas e digo: "Aqui estão
suas roupas limpas".
© Ali Asad e Hamza Ali 2017 65
A. Asad e H. Ali, O Guia de Estudo do Programador C # (MCSD) , DOI 10.1007 / 978-1-4842-2860-9_3

Capítulo 3 ■ Introdução à programação orientada a objetos


Você não tem ideia de como eu fiz isso. Você não tem conhecimento do local da
lavanderia. Talvez você fale francês e não consiga nem pegar um táxi. Você não
pode pagar por um, não tem dólares no bolso. No entanto, eu sabia como fazer
tudo isso. E você não precisava saber disso. Toda essa complexidade estava
escondida dentro de mim, e fomos capazes de interagir em um nível muito alto de
abstração. Isso é o que os objetos são. Eles encapsulam a complexidade e as
interfaces para essa complexidade são de alto nível.

Fonte:
http://www.edibleapple.com/2011/10/29/steve-jobs-explains-object-oriented-programming/

OOP em uma TORTA


Em uma OOP nuthshell tem três pilares fundamentais: P olymorphism, eu nheritance e E
ncapsulation ( PIE ). Esses pilares definem flexibilidade para se comunicar com outros
objetos, reutilização para evitar duplicação e proteção de dados para ocultar a
complexidade da implementação do mundo exterior.

Encapsulamento
O encapsulamento é um dos três pilares fundamentais de um programa orientado a objetos,
que diz que, quando dados (atributos) e métodos (comportamentos) são definidos em uma
única entidade, é chamado de encapsulamento. Também se refere a um princípio de design
orientado a objetos chamado Data Hiding , que restringe a acessibilidade dos dados (atributo)
e o método (comportamento) de uma entidade que não são necessários para o usuário.
O encapsulamento é implementado de duas maneiras:
\ 1. \ Especificadores de acesso
\ 2. \ Proteção de Dados

Especificadores de acesso
Especificadores de acesso são palavras-chave especiais, usadas para definir o nível de
acessibilidade de um tipo (classe, estrutura, enum) e todos os membros e métodos de dados
definidos dentro dele.
Em C #, temos cinco tipos de especificadores de acesso. Cada especificador de acesso
define um nível de acessibilidade exclusivo. Esses especificadores de acesso são:
\ 1. \ Público
\ 2. \ Privado
\ 3. \ Protegido
\ 4. \ Interno
\ 5. \ Interno protegido

Público
Membros definidos com especificadores de acesso público podem ser acessados dentro e
fora da classe. Os dados públicos também podem ser acessíveis de fora do projeto.

66.
Capítulo 3 ■ Introdução à programação orientada a objetos
Sintaxe

public Tipo MemberName;

Fragmento de código

Listagem 3-1.  Definir um método com especificador de acesso público


acesso à classe
{
Método public void ()
{
Console.WriteLine ("Método Público");
}
}

Programa de aula
{

static void Main (string [] args)


{
Acesso de acesso = novo
Access (); access.Method ();
}
}

Privado
Membros definidos com especificadores de acesso privado são acessíveis apenas dentro da
classe e não podem ser acessados de fora da classe.
Sintaxe

private Tipo MemberName;

Fragmento de código

Listagem 3-2.  Definir um campo com um especificador de acesso privado


acesso à classe
{
private int idade = 10;
public int GetAge ()
{
idade de retorno;
}

público vazio SetAge (int a)


{
idade = a;
}
}

67

Capítulo 3 ■ Introdução à programação orientada a objetos


Programa de aula
{

static void Main (string [] args)


{
Acesso de acesso = novo
Access (); int age =
access.GetAge ();
}
}

■■ Nota   Use métodos públicos para acessar um membro privado no mundo exterior.

Protegido
Membros definidos com especificadores de acesso protegido podem ser acessados dentro da
classe e também dentro de suas classes filho. Eles não podem ser acessíveis fora da classe.
Sintaxe

private Tipo MemberName;

Fragmento de código

Listagem 3-3.  Definir um campo com especificador de acesso protegido

classe Parent
{
idade protegida;
}
classe Criança: Pai
{
exibição pública nula ()
{
Console.WriteLine ("Idade é = {0}", idade);
//Console.WriteLine("Idade é = {0} ", base.age);

}
}

■■ Nota   Base é uma palavra-chave usada para acessar membros definidos como
especificadores de acesso público / protegido em uma classe pai / base.

68

Capítulo 3 ■ Introdução à programação orientada a objetos


interno
Dentro da montagem do projeto, os membros definidos com especificadores de acesso
interno podem ser acessados dentro e fora da classe. Mas eles não são acessíveis a
nenhuma classe definida fora da montagem do projeto.
Sintaxe

tipo interno MemberName;

Fragmento de código

Listagem 3-4.  Definir um campo com um especificador de acesso interno

namespace Csharp
{
acesso à classe
{
int idade interna = 10;
}

Programa de aula
{

static void Main (string [] args)


{
Acesso de acesso = novo
Access (); int age = access.age;
}
}
}

■■ Observação   Em C #, as classes por padrão são internas ., O que significa que nenhum assembly externo
pode acessar as classes padrão. Eles só podem ser acessíveis a outros assemblies se as classes estiverem
marcadas com especificadores de acesso público .

Protegido Internamente
Internal protected é uma união de comportamento interno e protegido de especificadores de
acesso, que diz que, dentro da montagem do projeto, os membros definidos com
especificadores de acesso protegido interno são acessíveis tanto dentro como fora da classe e
também para suas classes filho. Mas eles não são acessíveis a nenhuma classe definida fora do
escopo de montagem do projeto.
Sintaxe

tipo protegido interno MemberName;

69

Capítulo 3 ■ Introdução à programação orientada a objetos


Fragmento de código

Listagem 3-5.  Definir um campo com um especificador de acesso protegido interno

namespace Csharp
{
classe Parent
{
int protegido interno idade = 10;

classe Criança: Pai


{
exibição pública nula ()
{
Console.WriteLine ("age = {0}", base.age);
}
}

Programa de aula
{

static void Main (string [] args)


{
Pai pai = novo pai (); int idade
= pai.age;

}
}
}

■■ Nota   Os membros protegidos internos não são acessíveis apenas às classes filho, mas
também são acessíveis a outras classes da mesma montagem do projeto.

Proteção de dados
Em C #, os dados são armazenados em uma única variável ou em uma matriz. Para proteger esses dados
contra danos acidentais, temos:
\ 1. \ Propriedades
\ 2. \ Indexadores

Propriedades
As propriedades são usadas para encapsular o valor de um campo particular. Eles usam
especificadores de acesso, que oferecem melhor controle para ler, gravar ou manipular o valor
de um campo. Ele cria uma caixa de areia sobre os campos, o que a protege de salvar dados
falsos.

70

Capítulo 3 ■ Introdução à programação orientada a objetos


As propriedades são de dois tipos:
\ 1. \ Propriedade completa
\ 2. \ Propriedade Automática

Propriedade completa
Na propriedade completa, declaramos campos particulares e os encapsulamos dentro da definição de
uma propriedade.
Sintaxe

private Tipo field_name;


Tipo de acesso_específico Nome_Propriedade
{
get {return
nome_do_campo;}
conjunto {nome_do_campo
= valor;}
}

• obter acessadores de propriedades usados para retornar o valor de um campo.


• definir acessadores de propriedades usados para definir um valor em um campo.
• value é uma palavra-chave usada para atribuir um valor a um campo.

Fragmento de código

Listagem 3-6.  Definir e usar a propriedade completa


classe Aluno
{
int int privado;
public int Idade
{
get {return this.age; }
defina {this.age = value; }
}
}

Programa de aula
{

static void Main (string [] args)


{

Student std = new Student ();


std.Age = 10;
}
}

71

Capítulo 3 ■ Introdução à programação orientada a objetos

■■ Nota   Podemos transformar uma propriedade completa em somente leitura de duas maneiras.
Remova o bloco set {} da definição de propriedade completa ou marque o bloco set com um
especificador de acesso privado. Uma propriedade somente leitura é usada para retornar o valor de um
campo e um usuário não pode definir seu valor de fora da classe.

Propriedade Auto
A propriedade Auto é muito parecida com a propriedade completa. A única diferença principal
é que ele não requer nenhum campo ou lógica extra em seu get e set para manipular valores,
porque um compilador cria seu próprio campo privado automaticamente. É apenas um açúcar
sintático que o C # fornece aos seus desenvolvedores.
Sintaxe

Access_Specifier Digite Property_Name {get; conjunto; }


Fragmento de código

Listagem 3-7.  Definir e usar propriedade automática

classe Aluno
{
public int Age {get; conjunto; }
}

Programa de aula
{

static void Main (string [] args)


{

Student std = new Student ();


std.Age = 10;
}
}
■■ Nota   Podemos tornar uma propriedade automática uma propriedade
somente leitura de duas maneiras. Remova o bloco set {} da definição de propriedade
automática ou torne o bloco set privado.

Indexadores
Os indexadores são usados para encapsular o valor de uma matriz. Ele se comporta e funciona
como propriedade. Ele também usa especificadores de acesso, que oferecem melhor controle
para ler, gravar ou manipular o valor de uma matriz. Ele cria uma sandbox sobre uma matriz,
que a protege de:
\ 1. \ salvando dados falsos em uma matriz;
\ 2. \ usando o valor de índice errado em uma matriz;
\ 3. \ alterando a referência de uma matriz do mundo exterior.

72

Capítulo 3 ■ Introdução à programação orientada a objetos


Sintaxe

Access_Specifier Digite este [int index]


{
get {/ * retorna o valor do índice especificado da matriz aqui * /}
defina {/ * defina o índice especificado para o valor aqui * /}
}

• Tipo define o tipo de uma matriz, ou seja, (int [], objeto [] ..)
• isso define a matriz principal de um objeto.
• [int index] define o índice de uma matriz; no entanto, o C # não limita o
tipo de índice com número inteiro. Por exemplo, podemos usar string
como um tipo de índice, o que seria útil para pesquisar dados específicos
de uma coleção (dicionário <string, objeto>).

Fragmento de código

Listagem 3-8.  Definir indexador

classe Temprature
{
// declara uma matriz privada do tipo
float. flutuação privada [] weekTemp =
{47,5F, 40,0F, 52,5F, 45,5F, 48,0F, 38,0F, 35,7F};

// usa o indexador de flutuação, para


encapsular o weekTemp público que flutua
este [int index]
{
pegue
{
return weekTemp [index];
}
conjunto
{
weekTemp [index] = valor;
}
}
}

Listagem 3-9.  Use o indexador dentro do método Main


Programa de aula
{
static void Main (string [] args)
{
Temprature temp = new Temprature
(); float todayTemp = temp [1]; //ler

temp [1] = -5,0F; //Escreva

}
}
73

Capítulo 3 ■ Introdução à programação orientada a objetos


Lembre-se de que usamos o indexador chamando o nome do objeto juntamente
com um índice de matriz, como neste caso "temp [1]", que indica o valor de 1 índice que
o indexador do temp encapsulou.

■■ Nota   Em uma classe, deve haver apenas um indexador. No entanto, você pode
sobrecarregar um único indexador várias vezes.

Validar dados do indexador


Os dados podem ser validados quando configurados ou obtidos usando instruções if-else . Por
exemplo, podemos verificar se um valor é maior que 0; então pode ser definido na memória. Da
mesma forma, podemos verificar se o valor do índice deve ser menor que o comprimento de
sua matriz e maior ou igual a 0.

Listagem 3-10.  Validar valores do indexador


flutuação privada [] weekTemp =
{47,5F, 40,0F, 52,5F, 45,5F, 48,0F, 38,0F, 35,7F};

// usa o indexador de flutuação, para


encapsular o weekTemp público que flutua
este [int index]
{
pegue
{
if (index> = 0 && <weekTemp.Length)
{
return weekTemp [index];
}
outro
{
retornar 0;
}
}
conjunto
{
if (valor> 0)
{
weekTemp [index] = valor;

}
outro
{
Console.WriteLine ("Por favor, defina um valor maior que 0");
}

}
}
■■ Nota   Você pode validar os dados do índice e seu valor dentro de um bloco get
e set com uma instrução if-else simples .

74

Capítulo 3 ■ Introdução à programação orientada a objetos

Herança
A herança é um dos três pilares fundamentais da programação orientada a objetos. Ele permite
que novas classes reutilizem ou herdem propriedades e métodos de uma classe existente.
A classe cujos membros são herdados é chamada classe base e a classe que herdou
esses membros é chamada classe derivada (Figura 3-1 ).

Figura 3-1.  Herança

Sintaxe

Access_Specifier BaseClassName
{
// TODO: código da classe base
}

Access_Specifier DerivedClassName : BaseClassName


{
// TODO: código da classe derivada
}

Fragmento de código

Listagem 3-11.  Herdar classe pai na classe filho

classe Parent
{
public string SurName {get; conjunto; }
}

classe Criança: Pai


{
string privada _name;

75

Capítulo 3 ■ Introdução à programação orientada a objetos


public string Name
{
pegue
{
return (_name + "" + base.SurName);
}
conjunto
{
Nome = valor;
}
}
}

■■ Nota   No C #, uma classe não pode herdar de várias classes, mas pode ser herdada em vários níveis.

Herança multinível
Quando uma classe é derivada de uma classe derivada, isso é chamado de herança em vários níveis .

Figura 3-2.   Herança multinível

Fragmento de código

Listagem 3-12.  Usar herança multinível


classe GrandParent
{
GrandParent público ()
{

76

www.allitebooks.com

Capítulo 3 ■ Introdução à programação orientada a objetos


Console.WriteLine ("Grand Parent");
}
}

classe Pai: GrandParent


{
Pai público ()
{
Console.WriteLine ("Pai");
}
}

classe Criança: Pai


{
Criança pública ()
{
Console.WriteLine ("Filho");
}
}

Programa de aula
{

static void Main (string [] args)


{
Criança criança = nova Criança ();

}
}

Resultado
Grand Parent
Pai
Criança

No snippet de código acima, a classe Child herdou da classe Parent e a mesma classe
Parent herdou da classe GrandParent. Isso é chamado de herança multinível .
Lembre-se de que , na herança, ao criar um objeto de uma classe derivada, o compilador
sempre executa o construtor da classe base / pai primeiro e depois executa o construtor da
classe filha. Se a mesma classe pai herdou de outra classe, o construtor dessa classe será
executado primeiro e, em seguida, descerá para o nível filho e neto para executar o construtor
um após o outro.

■■ Nota   Uma classe não pode herdar várias classes, mas pode implementar várias interfaces ao mesmo
tempo.

Classe abstrata
Classes abstratas não podem ser instanciadas . É usado como classe base, onde fornece
membros comuns a todas as suas classes derivadas. É substituído parcialmente ou não é de
todo. Também é usado para declarar métodos abstratos (método sem definição) que, quando
herdados, devem ser substituídos por suas classes derivadas.

77

Capítulo 3 ■ Introdução à programação orientada a objetos


Sintaxe

classe abstrata Class_Name


{
// TODO: código
}

Fragmento de código

Listagem 3-13.  Definir e usar a classe abstrata


classe abstrata Veículo
{
rodas int protegidas;
public int Wheels
{
obter {rodas de retorno; }
}
}

classe Bike: Vehicle


{
bicicleta pública ()
{
base.wheels = 2;
}

Programa de aula
{

static void Main (string [] args)


{
Veículo veículo = bicicleta nova ();
Console.WriteLine
(vehicle.Wheels);

}
}

■■ Nota   A classe do veículo não pode ser instanciada, mas pode armazenar referência ao seu objeto filho
Bike. Isso é chamado de polimorfismo. Você aprenderá mais sobre polimorfismo e métodos abstratos nos
próximos tópicos.

Interface
A interface não pode ser instanciada . Seus membros não possuem detalhes de
implementação . Todos os detalhes da implementação são definidos por classes que
implementam (herdam) interfaces. Interface fornece o mais alto nível de abstração.

78

Capítulo 3 ■ Introdução à programação orientada a objetos


Sintaxe

interface IName
{
//FAÇAM:

Em C #, a classe pode implementar a interface de duas maneiras:


\ 1. \ Implementar interface implicitamente
\ 2. \ Implementar interface explicitamente

Implementar interface implicitamente


Interfaces implícitas são implementadas publicamente . É implementado quando a definição
explícita dos membros de cada interface não é necessária.
Fragmento de código
Listagem 3-14.  Definir e usar a interface

interface IVehicle
{
int Wheels {get; }
}

classe Bike: IVehicle


{
rodas int privadas;
public int Wheels
{
pegue
{
rodas de retorno;
}
}

Programa de aula
{

static void Main (string [] args)


{
Veículo veículo = bicicleta nova ();
Console.WriteLine
(vehicle.Wheels);

}
}

79

Capítulo 3 ■ Introdução à programação orientada a objetos


Pontos chave
\ 1. \ Não use especificadores de acesso com membros da interface.
\ 2. \ Não defina a definição de membros da interface.
\ 3. \ Propriedade automática, indexador, método e evento podem ser
usados como um membro de uma interface.
\ 4. \ A classe deve implementar a definição completa dos membros da
interface. Caso contrário, poderá ocorrer um erro no tempo de
compilação / execução.
\ 5. \ Class pode implementar mais de uma interface.

Implementar interface explicitamente


Interfaces explícitas são implementadas em particular. Nós os implementamos explicitamente
quando uma definição separada do membro de cada interface é necessária. Por exemplo,
quando uma classe implementa mais de uma interface que compartilha o nome de um
membro comum, é necessária uma implementação explícita da interface para separar a
definição de cada membro.
Fragmento de código

Listagem 3-15.  Definir e usar implementação explícita de interfaces


interface IPortuguês
{
int Marks {get; }
}
interface IMath
{
int Marks {get; }
}

classe Aluno: IEnglish, IMath


{
int inglês = 10; int
math = 9;

int IMath .Marks


{
pegue
{
retornar inglês;
}
}

int IEnglish .Marks


{
pegue

80

Capítulo 3 ■ Introdução à programação orientada a objetos


{
retornar matemática;
}
}
}

Programa de aula
{

static void Main (string [] args)


{
Student std = new Student ();

int english = ((IEnglish) std) .Marks;


int math = ((IMath) std). Marcas;

Console.WriteLine ("Marcas em inglês = {0} Marcas em


matemática = {1}", inglês, matemática);

}
}

Explicação
\ 1. \ IMath.Marks usado para implementar a interface explicitamente,
definindo o nome do nome de gravação da interface antes do nome do
membro.
\ 2. \ Nenhum especificador de acesso usado para implementar interfaces explícitas.
\ 3. \ ((Português) std). Marcas ; usado para acessar a propriedade "Marks" da interface
"IEnglish".

Polimorfismo
Polimorfismo é tudo sobre mudança de comportamentos; em outras palavras, são
formas diferentes de um objeto. Em C #, o polimorfismo é de dois tipos:
\ 1. \ Polimorfismo estático
\ 2. \ Polimorfismo dinâmico

Polimorfismo estático
Polimorfismo na programação é sobre mudar o comportamento. Embora o polimorfismo
estático signifique alterar o comportamento dos métodos em tempo de compilação, também é
conhecido como ligação inicial.
Em C #, o polimorfismo estático pode ser implementado de duas maneiras:
\ 1. \ Sobrecarga de método
\ 2. \ Sobrecarga do Operador

Sobrecarga de método
Na definição de tipo interno (classe, estrutura), podemos ter vários métodos com o mesmo
nome, mas com parâmetros diferentes; isso é chamado de sobrecarga de método.

81

Capítulo 3 ■ Introdução à programação orientada a objetos


Em C #, podemos sobrecarregar um método de duas maneiras:
\ 1. \ Por tipos de parâmetros
\ 2. \ Por comprimento dos parâmetros

Método de sobrecarga por tipo de parâmetro


Um método pode ser sobrecarregado definindo diferentes tipos de parâmetros para cada
método que compartilham o mesmo nome do método.
Por exemplo
No seguinte trecho de código, temos o método "Adicionar" , sobrecarregado pela definição de diferentes
tipos de parâmetros.

Listagem 3-16.  Definir sobrecarga de método por tipo de parâmetro


Calculadora de classe
{
public void Add (int a, int b)
{
resultado int = a + b;

Console.WriteLine ("Soma de entradas = {0}", resultado);


}

public void Add (string a, string b)


{
resultado da string = a + b;

Console.WriteLine ("Soma das cadeias = {0}", resultado);


}
}

Programa de aula
{
static void Main (string [] args)
{
Calculadora cal = nova Calculadora
(); cal.Add (1, 2);
cal.Add ("C", "Sharp");
}
}
//Resultado
Soma de polegadas = 3
Soma das strings = CSharp
Explicação
• cal.Add (1, 2); quando é chamado, o controle executa e executa um
método Add sobrecarregado, que possui dois parâmetros int .
• cal.Add ("C", "Sharp"); quando é chamado, o controle irá para um
método Add sobrecarregado que possui dois parâmetros de string

82

Capítulo 3 ■ Introdução à programação orientada a objetos


Método de sobrecarga pelo comprimento do parâmetro
Um método pode ser sobrecarregado definindo um comprimento de parâmetro diferente
para cada método que compartilha o mesmo nome do método.
Por exemplo
No seguinte snippet de código, temos o método "Show" sobrecarregado por um comprimento de
parâmetro diferente.

Listagem 3-17.  Definir sobrecarga de método pelo comprimento do parâmetro


Programa de aula
{
Public void Show (nome da string)
{
Console.WriteLine ("Nome = {0}", nome);
}

Public void Show (nome da string, int idade)


{
Console.WriteLine ("Nome = {0} - Idade = {1}", nome, idade);
}
static void Main (string [] args)
{
Programa programa = novo
programa (); program.Show
("Ali"); program.Show ("Ali", 22);

}
}

// Nome
da saída =
Ali
Nome = Ali - Idade = 22

Explicação
• program.Show ("Ali"); quando é chamado, o controle executa e executa
um método Show sobrecarregado, que possui um único parâmetro do
tipo string .
• program.Show ("Ali", 22); quando é chamado, o controle executa
e executa um método Show sobrecarregado, que possui dois
parâmetros de string e tipo int .

■■ Nota   Métodos com o mesmo nome, mas diferentes tipos de retorno não são considerados
sobrecarregados.

Sobrecarga do operador
Em C #, podemos sobrecarregar a definição de um operador para tipos personalizados (classe,
estrutura). Para sobrecarregar a definição de um operador, definimos métodos especiais dentro
de um tipo personalizado. Esses métodos ajudam o compilador a distinguir entre diferentes
significados de um operador que produz resultados diferentes para um tipo diferente.

83

Capítulo 3 ■ Introdução à programação orientada a objetos


Geralmente, em C #, podemos sobrecarregar três tipos de operadores:
\ 1. \ Operadores Unários
\ 2. \ Operadores binários
\ 3. \ Operadores de comparação

Sobrecarregar operadores unários


O operador unário opera em um único operando (+, -, !, ++, -, true, false). Esses são operadores
unários que podem ser sobrecarregados em C #.
Sintaxe

operador public return_type estático público (Tipo t)


{
// FAÇAM:
}

• O método estático sobrecarregado pelo operador deve ser estático.


• operator é uma palavra-chave usada para definir um método sobrecarregado pelo operador.
• op use o símbolo especial do operador, descreva qual definição de
operador será sobrecarregada, ou seja, (+, -, ..).
• Digite onde o tipo deve ser struct ou classe.
Fragmento de código
Listagem 3-18.  Definir e usar sobrecarga unária do operador

classe Distância
{
public int meter {get; conjunto; }

operador de distância estática pública ++ (distância dis)


{
dis.meter + = 1;
return dis;
}
}

Programa de aula
{
static void Main (string [] args)
{
Distância distância = nova Distância
(); distance.meter = 5;

distance ++;
Console.WriteLine (distance.meter);

}
}
84
Capítulo 3 ■ Introdução à programação orientada a objetos
Explicação
distance ++; é chamado método operator ++; passou seu próprio objeto para o parâmetro operator ++.

Operador binário de sobrecarga


O operador binário opera em dois operandos ( + , - , * , / , % , &, | , ^ , << , >> ). Esses são
operadores binários que podem ser sobrecarregados em c #.
Sintaxe

operador public return_type estático público (Type1 t1, Type2 t2)


{
//FAÇAM:
}

• Tipo t1 é operando do lado esquerdo


• Tipo t2 é operando do lado direito

Fragmento de código

Listagem 3-19.  Definir e usar sobrecarga de operador binário


classe Aluno
{
public int Marks {get; conjunto;
} // + Método de sobrecarga do
operador

operador Student estático público + (Student s1, Student s2)


{
Student std = new Student ();

std.Marks = s1.Marks + s2.Marks;

return std;
}
}

Programa de aula
{
static void Main (string [] args)
{
Aluno s1 = novo Aluno {Marcas = 10};
Aluno s2 = novo Aluno {Marcas = 20};

Estudante s3 = s1 + s2;
Console.WriteLine (s3.Marks);

}
}
//Resultado
30
85

Capítulo 3 ■ Introdução à programação orientada a objetos


Explicação
Estudante s3 = s1 + s2; quando é chamado, o operador + método será executado, o que leva
s1 e s2 para seus valores de parâmetro.

Operador de comparação de sobrecarga


Operador de comparação opera em dois operandos e retornos valor booleano quando compara
do lado esquerdo valor do operando com do lado direito valor do operando ( == , ! = , < , > , <= , >
= ). Esses são operadores de comparação que podem ser sobrecarregados em c #.
Sintaxe

operador bool estático público op (Type1 t1, Type2 t2)


{
//FAÇAM:
}

Fragmento de código

Listagem 3-20.  Definir e usar o operador de comparação

classe Distância
{
public int meter {get; conjunto; }

operador público bool estático <(Distância d1, Distância d2)


{
retorno (d1.meter <d2.meter);
}
operador bool estático público> (Distância d1, Distância d2)
{
retorno (d1.meter> d2.meter);
}

Programa de aula
{
static void Main (string [] args)
{
Distância d1 = nova Distância {metro = 10};
Distância d2 = nova Distância {metro = 20};

se (d1 <d2)
{
Console.WriteLine ("d1 é menor que d2");
}
senão se (d2 <d1)
{
Console.WriteLine ("d2 é menor que d1");
}

}
}
86

Capítulo 3 ■ Introdução à programação orientada a objetos


Explicação

if (d1 <d2): Quando esse código é executado, o operador <método será executado, o que toma d1
e d2 como parâmetro. Retorna true se o valor do medidor de d1 for menor que o valor do
medidor de d2.

■■ Nota   Sobrecarregue sempre o operador oposto ao operador de comparação. Por exemplo, sempre que
sobrecarregamos menos que o operador, devemos sobrecarregar maior que o operador. O mesmo se aplica a ==
, ! = Operador.

Polimorfismo dinâmico
O polimorfismo na programação é sobre mudar o comportamento, enquanto o polimorfismo
dinâmico significa mudar o comportamento de um objeto no tempo de execução ,
substituindo a definição de um método. Também é conhecido como ligação tardia .

Em C #, o método é substituído por duas maneiras:


\ 1. \ Método virtual
\ 2. \ Método abstrato

Método virtual
Virtual é uma palavra-chave usada com método na classe base para definir um método
virtual. Método virtual tem uma definição de seu método; sua classe derivada pode
herdar ou substituir sua definição. Portanto, ao chamar o nome do método, o tempo de
execução determinará qual método chamar.
Sintaxe

return_type virtual methodName ()


{
//FAÇAM:
}

Fragmento de código

Listagem 3-21.  Definir método virtual


classe Veículo
{
void público virtual Run ()
{
Console.WriteLine ("Executar veículo");
}
}

87

Capítulo 3 ■ Introdução à programação orientada a objetos


Listagem 3-22.  Substituir método virtual
classe Bike: Vehicle
{
substituição pública void Run ()
{
Console.WriteLine ("Corrida de bicicleta");
}
}

Programa de aula
{

static void Main (string [] args)


{
Veículo vc = bicicleta
nova (); vc.Run ();

}
}

Resultado
Bike Run

Explicação
\ 1. \ public virtual void Run () {..} define um método virtual na classe base.
\ 2. \ public override void Run () {..} substitui o método Run na classe
derivada, definindo sua própria implementação do método Run ().
\ 3. \ Veículo vc = nova Bike (); vc mantém objeto de sua classe filho “Bike”.
\ 4. \ vc.Run (); O controle passará primeiro para o método "Executar" base.
Quando o tempo de execução é encontrado, é um método virtual; ele
passará para a definição "Bike" da classe derivada para encontrar a
implementação do método "Run ()". Se encontrar o método, ele o
invocará; caso contrário, retornará à classe base para executar o
método Run () virtual.

Método abstrato
abstract é uma palavra-chave usada com o método na classe abstract para declarar um método
abstrato. Ao contrário do método virtual, o método abstrato não tem sua definição de método.
Assim, sua classe derivada deve implementar a definição de método abstrato, caso contrário,
o erro de tempo de compilação será gerado. Os métodos abstratos sempre declaram dentro de
uma classe abstrata.
Sintaxe

resumo class_Name
{
resumo público Type Method ();
}

88

Capítulo 3 ■ Introdução à programação orientada a objetos


Fragmento de código

Listagem 3-23.  Definir e substituir o método abstrato

classe abstrata Veículo


{
resumo público vazio Run ();
}

classe Bike: Vehicle


{
substituição pública void Run ()
{
Console.WriteLine ("Corrida de bicicleta");
}
}

Programa de aula
{

static void Main (string [] args)


{
Veículo vc = bicicleta
nova (); vc.Run ();

}
}

Explicação
\ 1. \ public abstract void Run (); declarar método abstrato sem
implementar sua definição.
\ 2. \ public override void Run (); substitui a definição do método Run na
classe derivada "Bike". Assim, quando o método Run () é chamado, o
método sempre derivado será chamado.

Sumário
•O C # possui cinco especificadores de acesso , como Público,
Privado, Protegido, Interno, Interno Protegido.
• Propriedades e indexadores são usados para encapsular dados.
• A classe derivada pode herdar todos os dados de sua classe base, exceto
uma que é mencionada com o especificador de acesso privado .
• C # não permite herança múltipla , mas permite herança em vários níveis .
• Uma classe deve implementar todos os membros de uma interface e
todos os métodos abstratos de uma classe abstrata.
• O método abstrato só pode ser escrito dentro de uma classe abstrata.

89

Capítulo 3 ■ Introdução à programação orientada a objetos

Desafios de código
Desafio 1: Desenvolver um transformador
Todos nós assistimos ao filme Transformers . São veículos inteligentes que podem se
transformar em jatos, carros e barcos. Seu trabalho é fazer uma aplicação em que um
transformador possa mudar seu comportamento nos seguintes veículos.

VeículoCondição para transformar Atributos


Jato Quando o transformador está no ar Rodas = 8
    Velocidade máxima = 900
Carro Quando o transformador está na estradaRodas = 4
Velocidade máxima = 350
   
Barco Quando o transformador está na água Rodas = 0
Velocidade máxima = 200
       

Seu trabalho é implementar um método Run para cada veículo que o transformador
funcionar sempre que o cenário mudar.

Gorjeta
\ 1. \ Use Enum para armazenar a paisagem, ou seja, ar, estrada, água.
\ 2. \ Siga o princípio OOP do polimorfismo.

Desafio 2: Desenvolver o sistema de lavanderia de Steve Jobs


Steve Jobs descreveu o POO em um exemplo muito simples. Seu trabalho é usar esse
exemplo para desenvolver um aplicativo que use o Princípio Orientado a Objetos. O
exemplo que Steve Jobs nos deu é dado abaixo:

Se eu sou seu objeto de lavanderia , você pode me dar suas roupas sujas e me
enviar uma mensagem dizendo: "Você pode lavar minhas roupas, por favor". Por
acaso, sei onde fica a melhor lavanderia de São Francisco. E eu falo inglês e tenho
dólares nos bolsos. Então, saio para pegar um táxi e digo ao motorista que me
leve a este lugar em San Francisco. Vou lavar suas roupas, pulo de volta no táxi,
volto aqui. Dou-lhe suas roupas limpas e digo: "Aqui estão suas roupas limpas".

90

Capítulo 3 ■ Introdução à programação orientada a objetos

Practice Exam Questions


Questão 1
Suponha que você esteja desenvolvendo um aplicativo que inclua o seguinte segmento de código:

interface ICricket
{
void Play ();
}

interface IFootball
{
void Play ();
}

Você precisa implementar os dois métodos Play () em uma classe derivada chamada
Player que usa o método Play () de cada interface.
Quais dois segmentos de código você deve usar?

A)
Jogador da classe: ICricket, IFootball
{
void ICricket.Play ()
{
//FAÇAM:
}

void IFootball.Play ()
{
//FAÇAM:
}
}

B)
Player player = novo Player ();
((ICricket) player) .Play ();
((IFootball) player) .Play ();

C)
Player player = novo Player ();
player.Play ();

D)
Player player = novo Player ();
player.Play (ICricket);
player.Play (IFootball);
91

Capítulo 3 ■ Introdução à programação orientada a objetos


E)
Jogador da classe: ICricket, IFootball
{
public void ICricket.Play ()
{
//FAÇAM:
}

public void IFootball.Play ()


{
//FAÇAM:
}
}

Questão 2
Suponha que você esteja desenvolvendo um aplicativo. O aplicativo possui duas classes
denominadas Player e Person. A classe Player deve atender aos seguintes requisitos:
\ 1. \ Ele deve herdar da classe Person.

\ 2. \ Deve não ser herdadas por outras classes na


aplicação. Qual segmento de código você deve usar?

A)
Classe selada Jogador: Pessoa
{
//FAÇAM:
}

B)
classe abstrata Jogador: Pessoa
{
//FAÇAM:
}

C)
Jogador de classe privada: Pessoa
{
//FAÇAM:
}

D)
classe parcial Jogador: Pessoa
{
//FAÇAM:
}

92

Capítulo 3 ■ Introdução à programação orientada a objetos


Questão 3
Suponha que você esteja criando uma classe chamada Player. A classe expõe uma propriedade
de sequência chamada HitSpeed. Aqui está o trecho de código da classe Player.

01. jogador de classe


02. {
03. public int HitSpeed
04. {
05. get;
06. conjunto;
07. }
08. }

A propriedade HitSpeed deve atender aos seguintes requisitos:


\ 1. \ O valor deve ser acessado por código na classe Player .
\ 2. \ O valor deve ser acessado para classes derivadas do Player.
\ 3. \ O valor deve ser modificado apenas pelo código dentro da classe Player.
Você precisa garantir que a implementação da propriedade EmployeeType atenda aos
requisitos. Qual segmento de código você deve substituir na linha 05. E 06.?
\ A) \ Substitua a linha 05. por
“public get;”. Substitua a linha
06. Por "conjunto privado;".

\ B) \ Substitua a linha 05. por


“protected get;”. Substitua a
linha 06. Por "conjunto
privado;".
\ C) \ Substitua a linha 05. por "get interno;".
Substitua a linha 06. Por “conjunto protegido interno;”.
\ D) \ Substitua a linha 05. por “proteção
interna protegida;”. Substitua a linha 06.
Por “conjunto interno;”.

Pergunta 4
O método a seguir “Display” é considerado sobrecarregado?

classe Pessoa
{
exibição pública nula ()
{
//
}
public int Display ()
{
//
}
}
A) sim
B) Não.

93

Capítulo 3 ■ Introdução à programação orientada a objetos


Questão 5
Como você encapsula uma matriz de números
inteiros em um indexador? Você deve escolher o
segmento de código correto.

A)
matriz int [] privada;
public int this [int index]
{
get {return array [index]; } set
{array [index] = valor; }
}

B)
matriz int [] privada;
public int this (int index)
{
get {return array [index]; } set
{array [index] = valor; }
}

C)
matriz int [] privada;
public int [] esse [int index]
{
get {matriz de retorno; }
set {array [index] = valor; }
}

D)
matriz int [] privada;
índice int privado;

public int this


{
get {return array [index]; } set
{array [index] = valor; }
}

Respostas
\ 1. \ A, B
\ 2. \ UMA
\ 3. \ B
\ 4. \ B

\ 5. \ UMA

94

CAPÍTULO 4

C # avançado
C # é uma linguagem muito rica que fornece muito código de açúcar do qual os
desenvolvedores podem aproveitar. Neste capítulo, examinaremos alguns dos recursos mais
populares do C #, como:
\ 1. \ Boxing / Unboxing
\ 2. \ Genéricos
\ 3. \ Coleção
\ 4. \ Interfaces de estrutura
\ 5. \ Manipulando Strings

Boxe e Unboxing
Boxe e unboxing são conceitos importantes no sistema de um tipo C #. Eles foram
introduzidos no C # 1 quando não havia um conceito definido para generalização de tipos.

Boxe
Boxe refere-se à conversão implícita de um tipo de valor em um tipo de objeto ou a qualquer
interface que ele implemente, por exemplo, int para IComparable <int>. Além disso, a conversão
de um tipo de valor subjacente em um tipo anulável também é conhecida como boxe.
Durante o boxe, o tipo de valor está sendo alocado em um heap gerenciado em vez de em uma pilha.
Sintaxe

objeto boxedVariable = valueType_variable;

Fragmento de código
 

Listagem 4-1.  Valor int em caixa


 

int idade = 22;


 
objeto boxedAge = idade; //Boxe

Explicação
No exemplo acima (Listagem 4-1 ), o valor inteiro age é encaixotado e atribuído ao objeto boxedAge.

© Ali Asad e Hamza Ali 2017 95


A. Asad e H. Ali, O Guia de Estudo do Programador C # (MCSD) , DOI 10.1007 / 978-1-4842-2860-9_4

Capítulo 4 ■ Avançado C #

Figura 4-1.  Boxe


Desembalagem
Desmarcar caixa refere-se a uma conversão explícita do tipo de objeto para o tipo de valor
não anulável ou a conversão de um tipo de interface para um tipo de valor não anulável , por
exemplo, IComparable <int> para int. Além disso, a conversão do tipo anulável no tipo de valor
subjacente também é conhecida como unboxing.
Durante a remoção da caixa do correio, o valor da caixa é retirado da caixa do heap
gerenciado para um tipo de valor que está sendo alocado em uma Pilha.
Sintaxe

valueType unboxedVariable = (valueType) boxedVariable;

Fragmento de código
 

Listagem 4-2.  Fora da caixa, valor em caixa


 

int idade = 22;


 
objeto boxedAge = idade; //Boxe

int unboxedAge = (int) boxedAge; // Unboxing

Explicação
No exemplo acima (Listagem 4-2 ), o objeto de valor em caixa boxedAge está sendo
descompactado em int unboxedAge . Durante o desempacotamento, o CLR faz a seguinte
operação:
• Verifique se o valor em caixa é do tipo de valor fornecido.
• Atribua um valor a uma variável de tipo de valor a partir do valor em caixa.

96

Capítulo 4 ■ Avançado C #

Figura 4-2.  Desembalagem

Desempenho de Boxe e Unboxing


Boxe e Unboxing são muito caros em termos de operações de computação para um
processador. Portanto, é melhor evitar o uso de tipos de valor onde eles devem ser
encaixotados e descaixados muitas vezes, por exemplo, em ArrayList (ArrayList armazena tudo
como uma coleção de objetos). Quando um valor está na caixa, uma nova instância deve ser
criada no heap. Isso pode levar até 20 vezes mais do que uma simples atribuição de referência.
Quando um valor em caixa é retirado da caixa, leva 4 vezes mais que uma atribuição de
referência simples. Portanto, é sempre preferível usar genéricos em vez de boxe e unboxing.

Genéricos
Os genéricos foram introduzidos no C # 2. Forneceu o conceito de segurança de
tipo . Genéricos define uma classe de maneira que seus campos, métodos, parâmetros etc.
possam trabalhar com qualquer tipo de dados real . Ele executa verificações em
tempo de compilação para segurança de tipo e é muito mais rápido que o boxe / unboxing
para generalização de tipo.
Sintaxe

classe ClassName <T>


{
//FAÇAM:
}

• Classe genérica: defina usando colchetes angulares " <> ".


• T é um parâmetro do tipo genérico; refere-se a qualquer tipo de
tempo de compilação que é fornecido quando uma classe é instanciada.
• Da mesma maneira, também podemos definir estruturas genéricas e interfaces genéricas.

97

Capítulo 4 ■ Avançado C #
Fragmento de código

Listagem 4-3.  Definir e usar classe genérica

classe GenericClass <T>


{
// tipo 'T' será definido na instanciação de GenericClass
private T genericField;

public T GenericMethod (T genericParameter)


{
this.genericField = genericParameter;
retorne this.genericField;
}

public T GenericProperty {get; conjunto; }

}
Programa de aula
{
static void Main (string [] args)
{
// Aqui o tipo <T> torna-se string
GenericClass <string> genStr = new GenericClass <string> ();
string strData = genStr.GenericMethod ("C #");
genStr.GenericProperty = "Exame de certificação:";
Console.WriteLine ("{0} {1}", strData, genStr.GenericProperty);

// Aqui o tipo <T> torna-se int


GenericClass <int> genInt = new GenericClass <int>
(); int intData = genInt.GenericMethod (70);
genInt.GenericProperty = 483;
Console.WriteLine ("{0} - {1}", intData, genInt.GenericProperty);

}
}
//Resultado
Exame de certificação
C #: 70 - 483

■■ Nota   Você pode criar mais de um parâmetro de tipo genérico dentro de <> colchetes angulares, ou
seja, <T, M>.

Restrições nos parâmetros de tipo genérico


Restrições nos parâmetros de tipo genérico são úteis para restringir os tipos de tipos que
podem ser usados para argumentos de tipo para instanciar uma classe genérica. O erro em
tempo de compilação será gerado se o código do cliente tentar instanciar uma classe genérica
usando um tipo restrito ao parâmetro type.

98

Capítulo 4 ■ Avançado C #
onde keyword é usada para aplicar restrições nos parâmetros de tipo genérico.
Sintaxe

classe ClassName <T> em que T: specConstraint


{
//FAÇAM:
}

Tipo de restrições
Existem 6 tipos de restrições que podemos aplicar em parâmetros de tipo genérico. A
tabela a seguir (Tabela 4-1 ) lista os tipos de restrições.

Tabela 4-1.  Lista de restrições para parâmetros de tipo genérico


Restrições Explicação
onde T: struct O tipo "T" deve ser um tipo de valor
onde T: classe O tipo "T" deve ser um tipo de referência
onde T: novo () O tipo "T" deve ter uma definição de construtor público padrão
onde T: U O tipo "T" deve ser ou filho do tipo "U"
onde T: interfaceName O tipo "T" deve ser ou implementar uma interface especificada
   

Fragmento de código

Listagem 4-4.  Restrição "onde T: struct"


classe GenericClass <T> em que T: struct
{
// Onde T: struct diz, 'T' pode ser apenas um tipo de
valor. private T genericField;

public T GenericMethod (T genericParameter)


{
this.genericField = genericParameter;
retorne this.genericField;
}

public T GenericProperty {get; conjunto; }

}
Programa de aula
{
static void Main (string [] args)
{

99

Capítulo 4 ■ Avançado C #
// Aqui o tipo <T> torna-se int, que é um tipo de valor
GenericClass <int> genInt = new GenericClass <int>
(); int intData = genInt.GenericMethod (70);
genInt.GenericProperty = 483;
Console.WriteLine ("{0} - {1}", intData, genInt.GenericProperty);

}
}

Listagem 4-5.  Restrição “where T: class”


classe GenericClass <T> em que T: class
{
// tipo 'T' será um tipo de referência
private T genericField;

public T GenericMethod (T genericParameter)


{
this.genericField = genericParameter;
retorne this.genericField;
}

public T GenericProperty {get; conjunto; }

Programa de aula
{
static void Main (string [] args)
{
// Aqui o tipo <T> se torna string. Qual é o tipo de referência
GenericClass <string> genStr = new GenericClass <string> ();
string strData = genStr.GenericMethod ("C #");
genStr.GenericProperty = "Exame de certificação:";
Console.WriteLine ("{0} {1}", strData, genStr.GenericProperty);

}
}

Listagem 4-6.  Restrição "onde T: novo ()"


classe MyClass
{
// Construtor público padrão
public MyClass ()
{

}
}

100
Capítulo 4 ■ Avançado C #
classe GenericClass <T> em que T: new ()
{

//FAÇAM:
}

Programa de aula
{
static void Main (string [] args)
{
// Aqui 'T' é Myclass. Que possui o construtor público padrão
GenericClass <MyClass> genMC = new GenericClass
<MyClass> ();
}
}

Listagem 4-7.  Restrição "onde T: BaseClass"


classe Pessoa
{

}
classe Aluno: Pessoa
{

classe GenericClass <T> em que T: Person


{
//FAÇAM:
}

Programa de aula
{
static void Main (string [] args)
{

GenericClass <Person> genPer = new GenericClass <Person> ();

// O aluno também é uma pessoa. Isso também é válido.


GenericClass <Student> genStd = new GenericClass <Student>
();
}
}

101

Capítulo 4 ■ Avançado C #
Listagem 4-8.  Restrição “where T: interfaceName” interface IPerson
{

}
classe Pessoa: IPerson
{
// Implementar Iperson
}
classe Aluno: Pessoa
{
//FAÇAM:
}

classe GenericClass <T> em que T: IPerson


{
//FAÇAM:
}

Programa de aula
{
static void Main (string [] args)
{
// Aqui 'T' é IPerson
GenericClass <IPerson> genIPer = new GenericClass <IPerson> ();

// Aqui 'T' é a pessoa que implementou 'IPerson'


GenericClass <Person> genPer = new GenericClass <Person>
();

// Aqui 'T' é Student, ele herda 'Person' que implementa 'IPerson'.


GenericClass <Student> genStd = new GenericClass <Student> ();
}
}

Listagem 4-9.  Restrição "onde T: U"


classe Pessoa
{
//FAÇAM
}

classe Aluno: Pessoa


{
//
}
classe GenericClass <T, U> em que T: U
{
//FAÇAM
}

102

Capítulo 4 ■ Avançado C #
Programa de aula
{
static void Main (string [] args)
{
// Aqui os tipos 'T' e 'U' são os mesmos
GenericClass <Person, Person> genPP
=
new GenericClass <Pessoa, Pessoa> ();

// Aqui 'T' herda o tipo 'U' GenericClass


<Student, Person> genSP =
new GenericClass <Estudante, Pessoa> ();
}
}
Da mesma forma, podemos aplicar mais de uma restrição nos argumentos de tipo.

Listagem 4-10.  Restrição “where T: BaseClass, new ()”


classe Pessoa
{
public string Nome {get;
conjunto; } Pessoa pública ()
{
this.Name = "padrão";
}
}
classe Aluno: Pessoa
{
//FAÇAM:
}

classe GenericClass <T> em que T: Person, new ()


{
// Onde T só pode ser Pessoa que possui um construtor padrão
// TODO:

Programa de aula
{
static void Main (string [] args)
{

GenericClass <Person> genPer = new GenericClass <Person> ();

// O aluno também é uma pessoa. Isso também é válido.


GenericClass <Student> genStd = new GenericClass <Student>
();
}
}

103

Capítulo 4 ■ Avançado C #
Métodos genéricos
Os métodos genéricos ajudam a digitar com segurança o tipo de argumento de um método, o
que ajuda a chamar o parâmetro de um método para vários tipos.
Sintaxe

returnType methodName <T> (T arg)


{
//FAÇAM:
}

• O método genérico define usando colchetes angulares “ <> ”.


• T é um parâmetro do tipo genérico; refere-se a qualquer tipo de
tempo de compilação que é fornecido quando o método genérico é
chamado.
Fragmento de código

Listagem 4-11.  Use métodos genéricos


exemplo de classe
{
public void GenericMethodArgs <T> (primeiro T)
{
Console.WriteLine (primeiro);
}

public T ReturnFromGenericMethodArgs <T> (primeiro T)


{
retornar primeiro;
}
public void MultipleGenericMethodArgs <T, U> (T primeiro, U segundo)
{
Console.WriteLine ("{0}: {1}", primeiro, segundo);
}

public U ReturnFromMultipleGenericMethodArgs <T, U> (T primeiro)


{
U temp = padrão (U);

temperatura de retorno;
}

}
Programa de aula
{

static void Main (string [] args)


{
Exemplo ex = novo Exemplo ();

104

Capítulo 4 ■ Avançado C #
// Chame o método genérico que possui um tipo
genérico único ex.GenericMethodArgs <int> (10);
int FromSingle = ex.ReturnFromGenericMethodArgs <int>
(10); Console.WriteLine (FromSingle + "\ n");

// Chame o método genérico que possui vários tipos


genéricos ex.MultipleGenericMethodArgs <string, int>
("Exam", 70483); int FromMultiple =
ex.ReturnFromMultipleGenericMethodArgs <string, int>
("Exame:"); Console.WriteLine (FromMultiple);
}
}

//Resultado
10
10

Exame:
70483 0

Restrições em métodos genéricos


Também é possível aplicar restrições em métodos genéricos para restringir os tipos de tipos
usados para passar valores durante a chamada de método.

Listagem 4-12.  Restrição no método genérico

exemplo de classe
{
public void GenericMethod <T> (Arg arg) em que T: struct
{
//FAÇAM:
Console.WriteLine (arg);
}
}
Programa de aula
{

static void Main (string [] args)


{
Exemplo ex = novo Exemplo
(); ex.GenericMethod <int>
(5);

// sem chamar o método genérico.


ex.GenericMethod (10);

}
}
//Resultado
5
10

105

Capítulo 4 ■ Avançado C #
• O método genérico também pode ser chamado sem <> colchetes
angulares. Os tipos de argumentos genéricos dependem do tipo de
passagem de valores no parâmetro de um método genérico.

■■ Nota Os   genéricos também podem ser usados para definir delegados e eventos
genéricos , que abordaremos no próximo capítulo.

Coleção
A coleção ajuda a gerenciar um grupo de objetos relacionados. No C #, coleções são
estruturas de dados que fornecem uma maneira flexível de armazenar e recuperar objetos
dinamicamente . Diferentemente das matrizes, um grupo de objetos em uma coleção pode
aumentar e diminuir a qualquer momento.
Coleções são classes instanciadas para gerenciar um grupo de objetos relacionados. No C #,
existem três tipos de coleções:
\ 1. \ System.Collections
\ 2. \ System.Collections.Generic
\ 3. \ System.Collections.Concurrent

System.Collections
System.Collections é um espaço para nome que contém classes e interfaces que gerencia
um grupo de dados. Ele armazena cada dado na forma de um tipo system.object .
Portanto, um grupo de dados do tipo de valor sempre é colocado na caixa / fora da caixa .
Ele define várias estruturas de dados para armazenar e recuperar dados como lista, fila
e hashtable.

Tabela 4-2.  Classes usadas com frequência no espaço de nome system.collections


Classe Explicação
ArrayList Matriz de objetos cujo tamanho pode aumentar e diminuir dinamicamente
Hashtable Coleção de pares chave / valor, organizada com base no código hash
Fila Gerencia o grupo de dados na ordem FIFO (primeiro a entrar, primeiro a sair)
Pilha Gerencia o grupo de dados na ordem Last In, First Out (LIFO)
   

ArrayList
É uma variedade de objetos que podem crescer e diminuir seu tamanho dinamicamente. Ao
contrário de matrizes, um ArrayList pode armazenar dados de vários tipos de dados. Pode ser
acessado por seu índice. Inserir e excluir um elemento no meio de um ArrayList é mais caro
do que inserir ou excluir um elemento no final de um ArrayList.
Um ArrayList contém muitos métodos e propriedades que ajudam a gerenciar um
grupo de objetos. A seguir, é apresentada uma lista de algumas propriedades e métodos
usados com freqüência, definidos em um ArrayList.

106

Capítulo 4 ■ Avançado C #
Tabela 4-3.  Métodos e propriedades frequentemente usados de ArrayList

Método e Propriedade Explicação


   

Adicionar() Adicionar um objeto ao final de ArrayList


Contém () Retorne true se o objeto específico estiver em ArrayList
Clone() Crie uma cópia superficial de ArrayList
Retirar() Remova a primeira ocorrência de objeto específico em ArrayList
RemoveAt () Remova o objeto do índice específico de ArrayList
Claro() Remova todos os objetos da ArrayList
Contagem Obter o número real de objetos armazenados em ArrayList
Capacidade Obter ou definir o número de objetos que ArrayList pode conter
   

Fragmento de código

Listagem 4-13.  Use ArrayList para gerenciar um grupo de objetos

using System.Collections;

Programa de aula
{

static void Main (string [] args)


{
ArrayList arraylist = new ArrayList ();

// adiciona objetos no
arraylist arraylist.Add (22);
arraylist.Add ("Ali");
arraylist.Add (true);

// Iterar sobre cada índice de arraylist para


(int i = 0; i <arraylist.Count; i ++)
{
System.Console.WriteLine (arraylist [i]);
}

arraylist.Remove (22);

System.Console.WriteLine ();
foreach (item var no arraylist)
{
System.Console.WriteLine (item);
}
}
}

107

Capítulo 4 ■ Avançado C #
//Resultado
22
Ali
True

Todos
Verdade

Hashtable
O Hashtable armazena cada elemento de uma coleção em um par de chaves / valores. Otimiza
as pesquisas computando a chave de hash e a armazena para acessar o valor correspondente.
Abaixo estão alguns métodos e propriedades comuns usados em uma classe Hashtable.

Tabela 4-4.  Métodos e propriedades frequentemente usados de Hashtable


Método e Propriedade Explicação
Adicionar() Adicione um elemento com a chave e o valor especificados
ContainsKey () Retorne true se a chave específica estiver no Hashtable
ContainsValue Retorno true se um valor específico estiver no Hashtable
Clone() Crie uma cópia superficial do Hashtable
Retirar() Remova o elemento com a chave especificada de ArrayList
Claro() Remova todos os objetos do Hashtable
Contagem Obter o número real de pares de chave / valor no Hashtable
Chaves Obter lista de chaves contidas no Hashtable
Valores Obter lista de valores contidos no Hashtable
   

Fragmento de código

Listagem 4-14.  Gerenciar informações da empresa e de seu proprietário no Hashtable


using System.Collections;
using System;

Programa de aula
{
static void Main (string [] args)
{
Proprietário do Hashtable = new Hashtable ();

// Adicione alguns valores no Hashtable


// Não há chaves, mas o valor pode ser
duplicado owner.Add ("Bill", "Microsoft");
owner.Add ("Paul", "Microsoft"); owner.Add
("Steve", "Apple"); owner.Add ("Mark",
"Facebook");

108
Capítulo 4 ■ Avançado C #
// Exibir valor contra a chave
Console.WriteLine ("Bill é o proprietário de {0}", proprietário ["Bill"]);

// ContainsKey pode ser usado para testar a chave antes


de inserir if (! Owner.ContainsKey ("Trump"))
{
owner.Add ("Trump", "A organização Trump");
}

// Quando você usa o foreach para enumerar elementos da tabela de hash,


// os elementos são recuperados como objetos
KeyValuePair. // DictionaryEntry é o par de chave e valor
Console.WriteLine ();
foreach (item DictionaryEntry no proprietário)
{
Console.WriteLine ("{0} é proprietário de {1}", item.Key, item.Value);
}

// Obter todos os valores


armazenados no Hashtable var
allValues = owner.Values;
Console.WriteLine ();
foreach (item var em allValues)
{
Console.WriteLine ("Company: {0}", item);
}

}
}
//Resultado
Bill é o proprietário da Microsoft

Steve é o proprietário da Apple


Trump é o proprietário da The Trump Organization
Mark é o proprietário do Facebook
Bill é o proprietário da Microsoft
Paul é o proprietário da Microsoft

Companhia: Apple
Companhia: The Trump Organization
Companhia: Facebook
Companhia: Microsoft
Companhia: Microsoft

Fila
Fila é uma classe do espaço para nome System.Collections. Ele armazena e recupera objetos na
ordem FIFO (primeiro a entrar, primeiro a sair). Em outras palavras, ele gerencia uma coleção
de objetos por ordem de chegada.
Abaixo estão alguns métodos e propriedades comuns usados na classe Queue.

109

Capítulo 4 ■ Avançado C #
Tabela 4-5.  Métodos e propriedades frequentemente usados de Fila
Método e Propriedade Explicação
Enfileirar () Adicione um elemento ao final da fila
Retirar da fila () Remova e retorne o objeto no início da fila
Olhadinha() Retornar o objeto no início da fila sem removê-lo
ToArray () Copie os elementos da fila para uma nova matriz
Contém () Retorne true se um objeto especificado estiver na Fila
Claro() Remova todos os objetos da fila
Clone() Crie uma cópia superficial da fila
Contagem Obter o número real de objetos na fila
   

Fragmento de código

Listagem 4-15.  Gerenciar o nome do dia da semana em uma fila

using System.Collections;
using System;

Programa de aula
{
static void Main (string [] args)
{
Dias da fila = nova fila ();

// Adiciona (Enque) objetos


nos dias da fila.Enqueue
("Mon"); days.Enqueue ("Ter");
days.Enqueue ("Wed");
days.Enqueue ("Qui");
days.Enqueue ("Fri");
days.Enqueue ("Sat");
days.Enqueue ("Sun");

// Exibe as propriedades e os valores da fila. Console.WriteLine ("O


total de elementos na fila é {0}", days.Count);

// Remova e retorne o primeiro elemento da fila


Console.WriteLine ("{0}", days.Dequeue ());

// retorna o primeiro elemento da fila sem removê-lo da fila //


retorna 'Tue'

Console.WriteLine ("{0}", days.Peek ());

// Itera sobre cada elemento da fila


Console.WriteLine ();

110

Capítulo 4 ■ Avançado C #
foreach (item var em dias)
{
Console.WriteLine (item);
}

}
}
//Resultado
O total de elementos na fila é
7 seg.

ter

ter
Casar
qui
Sex
Sentou
Sol

Pilha
Stack é uma classe do espaço para nome System.Collections. Ele armazena e recupera objetos
na ordem LIFO (Last In, First Out). Em outras palavras, os elementos pressionados no final
aparecerão primeiro, por exemplo, uma pilha de pratos.
Abaixo estão alguns métodos e propriedades comuns usados na classe Stack.

Tabela 4-6.  Métodos e propriedades frequentemente usados de Stack


Método e Propriedade Explicação
Empurrar() Inserir o objeto na parte superior da pilha
Pop () Remova e retorne o objeto na parte superior da pilha
Olhadinha() Retorne o objeto no topo da pilha sem removê-lo
ToArray () Copie os elementos Stack para uma nova matriz
Contém () Retorno true se um objeto especificado estiver na pilha
Claro() Remova todos os objetos da pilha
Clone() Crie uma cópia superficial da pilha
Contagem Obter o número real de objetos na pilha
   

Fragmento de código

Listagem 4-16.  Gerenciar o histórico do navegador no Stack


using System.Collections;
using System;

Programa de aula
{
static void Main (string [] args)

111

Capítulo 4 ■ Avançado C #
{
Histórico da pilha = nova pilha ();

// Insere o histórico do navegador no


histórico da pilha.Push ("google.com");
history.Push ("facebook.com/imaliasad");
history.Push ("twitter.com/imaliasad");
history.Push ("youtube.com");

// Exibe as propriedades e os valores da pilha. Console.WriteLine ("O


total de elementos na pilha é {0}", history.Count);

// Remova e retorne o elemento superior do


Stack Console.WriteLine ("{0}", history.Pop ());

// retorna o elemento superior do Stack sem removê-lo da


Stack // retorna 'twitter.com/imaliasad' Console.WriteLine ("
{0}", history.Peek ());

// Itera sobre cada elemento do Stack


Console.WriteLine ();
foreach (item var no histórico)
{
Console.WriteLine (item);
}

}
}
//Resultado
O total de elementos na pilha
é 4 youtube.com
twitter.com/imaliasad

twitter.com/imaliasad
facebook.com/imaliasad
google.com

System.Collections.Generics
System.Collections.Generics é um espaço para nome que contém classes e interfaces para
gerenciar uma coleção fortemente tipada . Em uma coleção genérica, os dados não podem
ser colocados na caixa / fora da caixa porque os dados sempre são protegidos por tipo. É
mais rápido e melhor que as classes e interfaces definidas no System.Collections. Ele
também define várias estruturas de dados para armazenar e recuperar dados como
Lista <T>, Fila <T>, Pilha <T> e Dicionário <TKey, TValue>.

112

Capítulo 4 ■ Avançado C #
Tabela 4-7.  Classes usadas com freqüência no espaço para nome System.Collections.Generic
Classe Explicação
Lista <T> Lista de objetos de tipo seguro que podem aumentar e diminuir dinamicamente
Dicionário <Tkey, Tvalue>Representa a coleção de chaves e valores de segurança de tipo
Fila <T> Representa First In, First Out coleção de tipo seguro objetos
Pilha <T> Representa Last In, First Out coleção de tipo seguro objetos
   

Lista <T>
Lista <T> é uma coleção de objetos com segurança de tipo. A lista pode aumentar e diminuir
seu tamanho dinamicamente. Com o suporte a genéricos, ele pode armazenar uma coleção
de qualquer tipo de maneira segura . Portanto, é muito mais rápido e otimizado que
ArrayList .
A lista <T> contém muitos métodos e propriedades que ajudam a gerenciar um grupo de
dados. A seguir, é apresentada uma lista de algumas propriedades e métodos usados com
freqüência, definidos na Lista <T>.

Tabela 4-8.  Métodos e propriedades freqüentemente usados da Lista <T>


Método e Propriedade Explicação
Adicionar() Adicione um objeto ao final da lista <T>
Contém () Retorne true se o objeto especificado estiver na Lista <T>
Ordenar() Classifique todos os objetos da Lista <T> usando o comparador
Retirar() Remova a primeira ocorrência de um objeto específico na Lista <T>
RemoveAt () Remova o objeto do índice especificado da Lista <T>
Claro() Remova todos os objetos da lista <T>
Encontrar() Pesquise o objeto usando o predicado especificado
Contagem Obter o número real de objetos armazenados na Lista <T>
   

Fragmento de código

Listagem 4-17.  Gerenciar objetos de vários tipos na lista <T>


using System.Collections.Generic;
using System;

classe Pessoa
{
public string Nome {get;
conjunto; } public int Age {get;
conjunto; }
}
Programa de aula

113

Capítulo 4 ■ Avançado C #
{
static void Main (string [] args)
{
List <Person> people = new List <Person> ();

// Adicionar pessoa na lista


people.Add (nova Pessoa {Nome = "Ali", Idade = 22});
people.Add (nova Pessoa {Nome = "Sundus", Idade =
21}); people.Add (new Person {Name = "Hogi", Age =
12});

// Obter número total de pessoas na lista Console.WriteLine


("Total de pessoas são: {0}", people.Count);

// Itera sobre cada pessoa


Console.WriteLine (); foreach
(var pessoa em pessoas)
{
Console.WriteLine ("Name: {0} - Age: {1}", person.Name, person.Age);
}

// Instancia e preenche a lista de int com valores List


<int> marks = new List <int>
{
10,
25,
15
23
};

// Remova '25' das marcas


da lista.Remove (25);

// Pega cada elemento pelo seu


índice Console.WriteLine ();
Console.Write ("Marks:");
for (int i = 0; i <marks.Count; i ++)
{
Console.Write (marca [i] + "");
}
}
}
//Resultado
Total de pessoas são: 3

Nome: Ali - Idade: 22


Nome: Sundus - Idade: 21
Nome: Hogi - Idade: 12

Marcas: 10 15 23

114

Capítulo 4 ■ Avançado C #
Dicionário <TKey, TValue>
O dicionário <TKey, TValue> é uma classe de System.Collections.Generic. É uma coleção
segura de tipos de pares de chave / valor. Cada chave no dicionário deve ser exclusiva e
pode armazenar vários valores na mesma chave. O dicionário <TKey, TValue> é muito
mais rápido que o Hashtable.
Abaixo estão alguns métodos e propriedades comuns usados na classe Dictionary <TKey, TValue>.

Tabela 4-9.  Métodos e propriedades freqüentemente usados da classe Dictionary <Tkey, TValue>
Método e Propriedade Explicação
Adicionar() Adicione um par de chave / valor com segurança de tipo no Dicionário.
ContainsKey () Retorne true se a chave específica estiver no dicionário.
ContainsValue Retorne verdadeiro se um valor específico estiver no dicionário.
Claro() Remova todos os objetos do dicionário.
Retirar() Remova o elemento com a chave especificada no dicionário.
Contagem Obtenha o número real de pares de chave / valor no dicionário.
Chaves Obtenha a lista de chaves contidas no Dicionário.
Valores Obter lista de valores contidos no dicionário.
   

Fragmento de código

Listagem 4-18.  Gerenciar alunos no Dicionário


using System.Collections.Generic;
using System;

classe Aluno
{
public string Nome {get;
conjunto; } public int Age {get;
conjunto; }
}
Programa de aula
{
static void Main (string [] args)
{
// Inicializa o dicionário (int para o número do rolo e o atribui ao
aluno) Dictionary <int, Student> students = new Dictionary <int,
Student> ();

// Adicionando aluno ao rolo #


students.Add (53, novo aluno {Nome = "Ali Asad", idade = 22});
students.Add (11, novo aluno {Nome = "Sundus Naveed", idade = 21});
students.Add (10, new Student {Name = "Hogi", Age = 12});
// Nome para exibição contra a chave
Console.WriteLine ("O rolo nº 11 é: {0}", alunos [11] .Nome);

115

Capítulo 4 ■ Avançado C #
// ContainsKey pode ser usado para testar a chave antes
de inserir if (! Students.ContainsKey (13))
{
students.Add (13, novo aluno {Nome = "Lakhtey", idade = 21});
}

// Quando você usa o foreach para enumerar elementos do dicionário,


// os elementos são recuperados como objeto KeyValuePairPair. //
KeyValuePair <TKey, TValue> é o par de chave e valor do dicionário
Console.WriteLine ();

foreach (KeyValuePair <int, Student> aluno em alunos)


{
Console.WriteLine ("Nº do rolo: {0} - Nome: {1} - Idade:
{2}", aluno.Key, aluno.Valor.Nome, aluno.Valor.Age);
}

// Obter todos os valores armazenados


no dicionário var allValues =
students.Values; Console.WriteLine ();
foreach (aluno var em allValues)
{
Console.WriteLine ("Name: {0} - Age: {1}",
student.Name, student.Age);
}

}
}
//Resultado
O rolo nº 11 é: Sundus Naveed

Roll # 53 - Nome: Ali Asad - Idade: 22


Rolo # 11 - Nome: Sundus Naveed - Idade: 21
Rolo # 10 - Nome: Hogi - Idade: 12
Rolo # 13 - Nome: Lakhtey - Idade: 21

Nome: Ali Asad - Idade: 22


Nome: Sundus Naveed - Idade: 21
Nome: Hogi - Idade: 12
Nome: Lakhtey - Idade: 21

Fila <T>
A fila <T> é uma classe de tipo seguro do espaço para nome System.Collections.Generic. Ele
armazena e recupera dados na ordem FIFO (primeiro a entrar, primeiro a sair). Em outras
palavras, ele gerencia uma coleta de dados por ordem de chegada. É muito mais rápido do que
o Queue definido na System.Collections porque o valor do tipo fica encaixotado /
desemoldurado na fila, enquanto Queue <T> sempre digite-cofres -lo.
Abaixo estão alguns métodos e propriedades comuns usados na classe Fila <T>.

116
  Capítulo 4 ■ Avançado C #

Tabela 4-10.  Métodos e propriedades freqüentemente usados da Fila <T>


   
Método e Propriedade Explicação
Enfileirar () Adicione um elemento ao final da fila <T>.
Retirar da fila () Remova e retorne um elemento no início da Fila <T>.
Olhadinha() Retorne um elemento no início da Fila <T> sem removê-lo.
ToArray () Copia os elementos da fila <T> para uma nova matriz.
Contém () Retorne true se um elemento especificado estiver na Fila <T>.
Claro() Remova todos os elementos da fila <T>.
Contagem Obtenha o número real de objetos na fila.
   

Fragmento de código

Listagem 4-19.  Gerenciar dias da semana na fila <string>

using System.Collections.Generic;
using System;

Programa de aula
{
static void Main (string [] args)
{
Fila <sequência> dias = nova Fila <sequência> ();

// Adicione o objeto de string


(Enque) em dias days.Enqueue
("Mon"); days.Enqueue ("Ter");
days.Enqueue ("Wed");
days.Enqueue ("Qui");
days.Enqueue ("Fri"); days.Enqueue
("Sat"); days.Enqueue ("Sun");

// Exibe as propriedades e os valores da fila.


Console.WriteLine ("Total de elementos na fila <string> são
{0}",
days.Count);

// Remova e retorne o primeiro elemento da fila <string>


Console.WriteLine ("{0}", days.Dequeue ());

// retorna o primeiro elemento da fila sem removê-lo da fila //


retorna 'Tue'

Console.WriteLine ("{0}", days.Peek ());

// Itera sobre cada elemento da fila


Console.WriteLine ();
foreach (item var em dias)

117

Capítulo 4 ■ Avançado C #
{
Console.WriteLine (item);
}

}
}
//Resultado
O total de elementos na fila <string> é
7 seg.

ter

ter
Casar
qui
Sex
Sentou
Sol

Pilha <T>
A pilha <T> é uma classe do espaço para nome System.Collections.Generic. Ele armazena e
recupera elementos na ordem LIFO (Last In, First Out). Em outras palavras, os elementos
pressionados no final aparecerão primeiro, por exemplo, uma pilha de pratos. É muito mais
rápido do que Stack definido na System.Collections porque o valor do tipo fica encaixotado /
desemoldurado no Stack, enquanto Stack <T> sempre digite-cofres -lo.
Abaixo estão alguns métodos e propriedades comuns usados na classe Stack <T>.

Tabela 4-11.  Métodos e propriedades freqüentemente usados da pilha <T>


Método e Propriedade Explicação
Empurrar() Insira o elemento na parte superior da pilha <T>.
Pop () Remova e devolva o elemento na parte superior da pilha <T>.
Olhadinha() Retorne o elemento na parte superior da pilha <T> sem removê-lo.
ToArray () Copie os elementos Stack <T> para uma nova matriz.
Contém () Retorne true se um elemento especificado estiver na pilha <T>.
Claro() Remova todos os elementos da pilha <T>.
Contagem Obtenha o número real de elementos na pilha <T>.
   

118

Capítulo 4 ■ Avançado C #
Fragmento de código

Listagem 4-20.  Gerenciar o histórico do navegador na pilha <string>


using System.Collections.Generic;
using System;

Programa de aula
{
static void Main (string [] args)
{
Stack <string> history = new Pilha <string> ();

// Insere o histórico do navegador na pilha


<string> history.Push ("google.com");
history.Push ("facebook.com/imaliasad");
history.Push ("twitter.com/imaliasad");
history.Push ("youtube.com");

// Exibe as propriedades e os valores da pilha <string>.


Console.WriteLine ("O total de elementos na pilha <string> é
{0}",
history.Count);

// Remova e retorne o elemento superior do Stack


<string> Console.WriteLine ("{0}", history.Pop ());

// retorna o elemento superior da pilha <string> sem removê-lo da


pilha // retorna 'twitter.com/imaliasad'
Console.WriteLine ("{0}", history.Peek ());

// Itera sobre cada elemento do Stack <string>


Console.WriteLine ();
foreach (item var no histórico)
{
Console.WriteLine (item);
}

}
}
//Resultado
O total de elementos na pilha <string>
é 4 youtube.com twitter.com/imaliasad

twitter.com/imaliasad
facebook.com/imaliasad
google.com

119

Capítulo 4 ■ Avançado C #
System.Collections.Concurrent
O espaço para nome System.Collections.Concurrent foi introduzido na estrutura do .NET 4. Ele
fornece várias classes de coleções seguras para encadeamento que protegem uma coleção de
serem manipuladas por vários encadeamentos. As classes de coleção definidas em um
System.Collections.Concurrent podem ser manipuladas apenas por um único thread. O .NET 4.0
Framework apresenta várias coleções seguras de thread no espaço para nome
System.Collections.Concurrent.

Tabela 4-12.  Classes usadas com freqüência no espaço para nome System.Collections.Concurrent
Classe Explicação
ConcurrentBag <T> Representa uma coleção de objetos não ordenada e segura para threads .
ConcurrentDictionary <T, V>Representa uma coleção segura de segmentos de pares de valor-chave .
ConcurrentQueue <T> Representa uma thread-safe coleção First In, First Out (FIFO).
ConcurrentStack <T> Representa uma coleção LIFO (Last In, First Out) com segurança de thread .
   

Discutiremos mais sobre System.Collections.Concurrent no Capítulo 8 : Programação


multithread, assíncrona e paralela.
Implementar interface de estrutura
Você pode aproveitar a estrutura do .NET em tipos personalizados implementando as seguintes interfaces.
C # fornece built-in interfaces que são úteis para gerenciar tipos personalizados para
diferentes fins úteis como definir coleções personalizadas e recursos dispose que não são mais
necessários.
• IEnumerable e IEnemerable <T>
• IEnumerator e IEnumerator <T>
• ICollection e ICollection <T>
• IList e IList <T>
• IComparable e IComparable <T>
• IComparer e IComparer <T>
• IEquatable <T>

IEnumerable & IEnumerable <T>


O .NET definiu duas bibliotecas de classe base. Há uma interface IEnumerable não genérica
para criar uma coleção não genérica personalizada e uma interface IEnumerable <T>
segura para tipos genéricos para criar uma coleção segura para tipos .

IEnumerable
A interface IEnumerable é definida no espaço para nome System.Collections. Ajuda a criar uma
coleção não genérica personalizada. Ele contém um único método GetEnumerator que
retorna um IEnumerator. Discutiremos o IEnumerator em muitos detalhes em um tópico
posterior. Mas, por enquanto, o IEnumerator é usado para iterar sobre uma coleção, armazena
as informações de um índice atual, seu valor e se uma iteração de coleção foi concluída ou não.

120

Capítulo 4 ■ Avançado C #
O loop Foreach apenas itera sobre os tipos que implementaram a interface IEnumerable,
ou seja, ArrayList e Queue.

Listagem 4-21.  Definição de IEnumerable


interface pública IEnumerable
{
IEnumerator GetEnumerator ();
}

Listagem 4-22.  Definir ArrayList personalizado


using System;
using System.Collections;

classe myArrayList: IEnumerable


{
objeto [] array = novo objeto [4];
int índice = -1;

public void Add (objeto o)


{
if (++ index <array.Length)
{
matriz [index] = o;
}
}
public IEnumerator GetEnumerator ()
{
for (int i = 0; i <array.Length; i ++)
{
matriz de retorno de rendimento [i];
}
}
}
Programa de aula
{
static void Main (string [] args)
{
myArrayList list = new myArrayList ();

// armazena dados do objeto na lista


myArraylist.Add ("Ali");
list.Add (22);
list.Add ("Sundus");
list.Add (21);

121

Capítulo 4 ■ Avançado C #
foreach (item var na lista)
{
Console.WriteLine (item);
}
}
}
// Saída
Ali
22
Sundus
21

Explicação
O loop Foreach chamado método GetEnumerator da “lista”, que gera um valor de retorno do
índice de cada matriz em cada iteração. Portanto, myArrayList agora se tornou uma coleção
personalizada devido ao IEnumerable. Na imagem a seguir, você pode ver um valor de
retorno de rendimento do índice de cada matriz em cada iteração. A figura a seguir (Figura
4-3 ) explica como o retorno do rendimento em um loop funciona.

Figura 4-3.  Retorno de rendimento em um loop

■■ Nota O   rendimento retorna um item; nos salvou de escrever o código completo para definir o
IEnumerator.

IEnumerable <T>
IEnumerable <T> é uma interface com segurança de tipo definida no espaço para nome
System.Collections.Generic. É usado para criar uma coleção personalizada de tipo seguro .

Listagem 4-23.  Definição de IEnumerable <T>

interface pública IEnumerable <out T>: IEnumerable


{
IEnumerator <T> GetEnumerator ();
}

Como você pode ver, IEnumerable <T> herda uma interface IEnumerable.
Portanto, um tipo que implementa IEnumerable <T> também deve implementar
IEnumerable.

122

Capítulo 4 ■ Avançado C #
Listagem 4-24.  Crie uma coleção personalizada de tipo seguro
using System;
using System.Collections;
using System.Collections.Generic;

classe myList <T>: IEnumerable <T>


{
Lista <T> lista = nova Lista <T> ();

// Obtém o
comprimento da lista
<T> public int Length
{
get {return list.Count; }
}
public void Add (dados T)
{
list.Add (dados);
}
public IEnumerator <T> GetEnumerator ()
{
foreach (item var na lista)
{
item de retorno de rendimento;
}
}

IEnumerator IEnumerable.GetEnumerator ()
{
// retorna IEnumerator <T>
GetEnumerator () retorna
this.GetEnumerator ();
}
}

classe Pessoa
{
public string Nome {get;
conjunto; } public int Age {get;
conjunto; }
}
Programa de aula
{
static void Main (string [] args)
{
myList <Person> pessoas = novo myList <Person> ();
people.Add (nova Pessoa {Nome = "Ali", Idade = 22});
people.Add (nova Pessoa {Nome = "Sundus", Idade =
21}); people.Add (new Person {Name = "Hogi", Age =
12});

123

Capítulo 4 ■ Avançado C #
Console.WriteLine ("Total de pessoas: {0} \ n",
pessoas.Comprimento); foreach (Pessoa pessoa em pessoas)
{
Console.WriteLine ("Name: {0} Age: {1}", person.Name, person.Age);
}

}
}
//Resultado
Total de pessoas: 3

Nome: Ali Idade: 22


Nome: Sundus Idade: 21
Nome: Hogi Idade: 12

IEnumerator e IEnumerator <T>


O .NET definiu duas bibliotecas de classe base. Existem interfaces IEnumerator não genéricas
e genéricas para definir a iteração de uma coleção.

IEnumerator
IEnumerator é uma interface não genérica definida no espaço para nome
System.Collections. Possui métodos e propriedades que uma coleção implementa para
definir sua iteração.

Listagem 4-25.  Definição de IEnumerator

interface pública IEnumerator


{
// Obtém o valor do índice atual do objeto de
coleção Current {get; }

// Move para o próximo índice da coleção


bool MoveNext ();

// Mova para a posição inicial do índice = -1


void Reset ();
}

Listagem 4-26.  Defina a iteração da sua coleção personalizada com IEnumerator


using System;
using System.Collections;
using System.Collections.Generic;

classe Pessoas: IEnumerable


{
Pessoa pessoas;
int índice = -1;
124

Capítulo 4 ■ Avançado C #
public void Add (Pessoa por)
{
if (++ index <people.Length)
{
pessoas [índice] = por;
}
}
Pessoas públicas (tamanho int)
{
pessoas = nova pessoa [tamanho];
}

public IEnumerator GetEnumerator ()


{
retornar novo PersonEnum (pessoas);
}
}

// Implementar IEnumerator
classe PersonEnum: IEnumerator
{
Pessoa pessoas;
int índice = -1;

public PersonEnum (Person [] pessoas)


{
_pessoas = pessoas;
}

// Verifique se o foreach pode passar para a próxima


iteração ou não o bool público MoveNext ()
{
return (++ índice <_people.Length);
}

// Redefine a iteração
public void Reset ()
{
índice = -1;
}

// Obter o objeto
público do valor atual
Current
{
pegue
{
return _people [index];
}
}

125

Capítulo 4 ■ Avançado C #
classe Pessoa
{
public string Nome {get;
conjunto; } public int Age {get;
conjunto; }
}
Programa de aula
{
static void Main (string [] args)
{
Pessoas pessoas = novas pessoas (3);

people.Add (nova Pessoa {Nome = "Ali", Idade = 22});


people.Add (nova Pessoa {Nome = "Sundus", Idade =
21}); people.Add (new Person {Name = "Hogi", Age =
12});

foreach (item var em pessoas)


{
// Transmitir do objeto para
Person Person person =
(Person) item;
Console.WriteLine ("Name: {0} Age: {1}", person.Name, person.Age);
}

}
}
// Nome da saída:
Ali Idade: 22
Nome: Sundus
Idade: 21 Nome:
Hogi Idade: 12

IEnumerator <T>
IEnumerator <T> é uma interface genérica definida no espaço para nome
System.Collections.Generic. Possui métodos e propriedades que uma coleção de tipo seguro
deve implementar para definir sua iteração.

Listagem 4-27.  Definição de IEnumerator <T>

interface pública IEnumerator <out T>: IDisposable, IEnumerator


{

// elemento na coleção na posição atual do enumerador. T atual {get; }

Como você pode ver, o IEnumerator <T> herda as interfaces IDisposable e IEnumerator.
Portanto, um tipo que implementa IEnumerator <T> também deve implementar essas
interfaces.

126

Capítulo 4 ■ Avançado C #
Listagem 4-28.  Escrever um tipo seguro iteração de um costume tipo seguro coleção com IEnumerator
<T>
using System;
using System.Collections;
using System.Collections.Generic;

classe myList <T>: IEnumerable <T>


{
Lista T [];
int índice = -1;

public void Add (T obj)


{
if (++ index <list.Length)
{
lista [index] = obj;
}
}

public IEnumerator <T> GetEnumerator ()


{
retornar novo TEnum <T> (lista);
}

IEnumerator IEnumerable.GetEnumerator ()
{
retorne this.GetEnumerator ();
}

public myList (tamanho int)


{
lista = novo T [tamanho];
}

// Implementar IEnumerator
classe TEnum <T>: IEnumerator <T>
{
Lista T [];
int índice = -1;

TEnum público (T [] objs)


{
_list = objs;
}

// Retorna se o foreach pode iterar para o próximo


índice ou não ser público bool MoveNext ()
{
return (++ índice <_list.Length);
}

127

Capítulo 4 ■ Avançado C #
Redefinição de void pública ()
{
índice = -1;
}

// Obtém o valor seguro do tipo do índice da


matriz atual // É a implementação do
IEnumerator <T> public T Current

{
pegue
{
retornar _list [index];
}
}

// É a implementação do objeto
'IEnumerator' IEnumerator.Current
{
pegue
{
// return T Current
retorna this.Current;
}
}

// É a implementação da interface IDispose public


void Dispose ()
{
// Escreva código para descartar recursos desnecessários
}

classe Pessoa
{
public string Nome {get;
conjunto; } public int Age {get;
conjunto; }
}

Programa de aula
{
static void Main (string [] args)
{
myList <Person> pessoas = novo myList <Person> (3);

people.Add (nova Pessoa {Nome = "Ali", Idade = 22});


people.Add (nova Pessoa {Nome = "Sundus", Idade =
21}); people.Add (new Person {Name = "Hogi", Age =
12});

128

Capítulo 4 ■ Avançado C #
foreach (item var em pessoas)
{
// Não há necessidade de transmitir
Console.WriteLine ("Nome: {0} Idade: {1}", item.Name, item.Age);
}

}
}
// Nome da saída:
Ali Idade: 22
Nome: Sundus
Idade: 21 Nome:
Hogi Idade: 12

ICollection e ICollection <T>


ICollection e ICollection <T> são interfaces usadas para estender a definição de coleção personalizada.

ICollection
ICollection é uma interface definida em System.Collections para estender a definição de uma
coleção não genérica personalizada .
Ele define tamanho, enumeradores e métodos de sincronização para todas as coleções não
genéricas.

Listagem 4-29.  Definição de ICollection


interface pública ICollection: IEnumerable
{

// Obtém o número de elementos contidos no ICollection. int


Count {get; }

// Obtém um valor indicando se o acesso ao ICollection


// está sincronizado (thread safe).
bool IsSynchronized {get; }

// Obtém um objeto que pode ser usado para sincronizar o acesso ao


ICollection. objeto SyncRoot {get; }

// Copia os elementos da ICollection para uma matriz,


// iniciando em um índice System.Array específico.
void CopyTo (matriz Array, int index);
}

ICollection herda de IEnumerable. Portanto, todos os membros da interface IEnumerable


devem ser implementados em todas as classes que implementam a interface ICollection.

ICollection <T>
ICollection <T> é uma interface de segurança de tipo definida em System.Collections.Generic.
Estende a funcionalidade de coleções genéricas.
129

Capítulo 4 ■ Avançado C #
Ele define métodos para manipular coleções genéricas.

Listagem 4-30.  Definição de ICollection <T>

interface pública ICollection <T>: IEnumerable <T>, IEnumerable


{

// Obtém o número de elementos contidos na `Generic.ICollection`. int


Count {get; }

// Obtém um valor indicando se o `Generic.ICollection`


// é somente leitura.
bool IsReadOnly {get; }

// Adiciona um item ao `System.Collections.Generic.ICollection`.


Adicionar vazio (item T);

// Remove todos os itens da `Generic.ICollection`.


vazio Clear ();

// Determina se o `System.Collections.Generic.ICollection`
// contém um valor específico.
bool Contém (item T);

// Copia os elementos da `Generic.ICollection` para uma matriz,


// iniciando em um índice System.Array específico.
void CopyTo (matriz T [], int arrayIndex);
// Remove a primeira ocorrência de um objeto da `Generic.ICollection`. bool
Remover (item T);
}

Não se parece exatamente com uma ICollection não genérica . A nova definição de
ICollection <T> possui mais alguns métodos, como Adicionar, Remover e Limpar.

IList e IList <T>


IList e IList <T> são interfaces que estendem a funcionalidade de um tipo de coleção personalizado.

IList
IList é uma interface definida em System.Collections. As implementações do IList se enquadram em três
categorias:
\ 1. \ somente leitura
\ 2. \ tamanho fixo
\ 3. \ tamanho variável

130

Capítulo 4 ■ Avançado C #
Uma lista somente leitura não pode ser modificada. Uma lista de tamanho fixo não
pode aumentar ou diminuir, mas seus elementos podem ser editáveis, enquanto uma lista
de tamanho variável permite adição, remoção e modificação de elementos.
IList representa uma coleção não genérica de objetos que podem ser acessados individualmente pelo
índice.

Listagem 4-31.  Definição de IList


interface pública IList: ICollection, IEnumerable
{

// Obtém ou define o elemento no índice


especificado. objete este [int index] {get; conjunto; }

// Obtém um valor indicando se o IList tem um tamanho fixo.


IsFixedSize {get; }

// Obtém um valor indicando se o IList é


somente leitura. bool IsReadOnly {get; }

// Adiciona um item ao System.Collections.IList.


int Add (valor do objeto);

// Remove todos os itens do System.Collections.IList.


vazio Clear ();

// Determina se o IList contém um valor específico. bool


Contém (valor do objeto);

// Determina o índice de um item específico no IList. int


IndexOf (valor do objeto);

// Insere um item na IList no índice especificado.


inserção nula (índice int, valor do objeto);

// Remove a primeira ocorrência de um objeto específico do IList.


Remover nulo (valor do objeto);
// Remove o item System.Collections.IList no índice especificado. void
RemoveAt (int index);
}

IList herda de ICollection e IEnumerable. Portanto, todos os membros das interfaces


ICollection e IEnumerable devem ser implementados em todas as classes que
implementam a interface IList.

IList <T>
IList <T> é uma interface definida em System.Collections.Generic. É usado para estender a
coleção genérica personalizada. Não se parece exatamente com um IList não genérico . A
nova definição de IList <T> é um pouco menor que o equivalente não genérico . Temos
apenas alguns métodos novos para acessar uma coleção com posicionamento específico.

131

Capítulo 4 ■ Avançado C #
Listagem 4-32.  Definição de IList <T>
IList <T> representa uma coleção de objetos que podem ser acessados individualmente pelo índice.

interface pública IList <T>: ICollection <T>, IEnumerable <T>, IEnumerable


{

// Obtém ou define o elemento no índice


especificado. T este [int index] {get; conjunto; }

// Determina o índice de um item específico no `Generic.IList`1. int


IndexOf (item T);

// Insere um item no `IGeneric.IList`1 no índice especificado.


inserção nula (índice int, item T);

// Remove o item `SGeneric.IList` no índice especificado.


void RemoveAt (int index);
}

IList <T> herda de ICollection <T>, IEnumerable <T> e IEnumerable. Portanto, todos os
membros das interfaces ICollection <T>, IEnumerable <T> e IEnumerable devem ser
implementados em todas as classes que implementam a interface IList <T>.

IComparable e IComparable <T>


IComparable e IComparable <T> são interfaces usadas para definir um método de comparação
para um tipo para ordenar ou classificar suas instâncias. O método CompareTo retorna um
Int32 que possui um dos três valores que têm o seguinte significado:
• Retorne zero , a instância atual ocorrerá na mesma posição.
• Menos que zero , a instância atual precede o objeto especificado pelo
método CompareTo na ordem de classificação.
• Maior que zero , a instância atual segue o objeto especificado por
CompareTo na ordem de classificação.

IComparável
IComparable é uma interface definida no espaço para nome do sistema. Ele pega um objeto
como parâmetro e retorna um resultado como um int32.

Listagem 4-33.  Definição de IComparable


interface pública IComparable
{
// Compara a instância atual com outro objeto do mesmo
// digita e retorna
// um número inteiro que indica se a instância atual precede,
// segue ou
// ocorre na mesma posição na ordem de classificação que o outro objeto.

int CompareTo (objeto obj);


}
132

Capítulo 4 ■ Avançado C #
Listagem 4-34.  Implementar IComparable e classificar lista de pessoas
using System;
using System.Collections;

classe Pessoa: IComparable


{
public string Nome {get;
conjunto; } public int Age {get;
conjunto; public int CompareTo
(objeto obj)
{
Pessoa seguinte = (Pessoa) obj;
retornar this.Age.CompareTo (next.Age);
}
}

Programa de aula
{
static void Main (string [] args)
{
Pessoas ArrayList = new ArrayList ();

people.Add (nova Pessoa {Nome = "Sundus", Idade =


21}); people.Add (nova Pessoa {Nome = "Ali", Idade =
22}); people.Add (new Person {Name = "Hogi", Age =
12});

// classifica a lista de
pessoas people.Sort ();

foreach (Pessoa pessoa em pessoas)


{
Console.WriteLine (person.Age + "" + person.Name);
}

}
}
//Resultado
12 Hogi
21 Sundus
22 Ali

IComparável <T>
IComparable <T> é uma interface de segurança de tipo definida no espaço para nome do
sistema. Ele pega um parâmetro de segurança de tipo e retorna um resultado como um
int32. Sua implementação é a mesma que IComparable.
133

Capítulo 4 ■ Avançado C #
Listagem 4-35.  Definição de IComparável <T>
interface pública IComparable <em T>
{

// Compara a instância atual com outro objeto do mesmo tipo


// e retorna um número inteiro que indica se a instância atual
// precede, segue ou ocorre na mesma posição na classificação
ordem // como o outro objeto.

int CompareTo (T outro);


}

using System;
using System.Collections.Generic;

classe Pessoa: IComparable <Person>


{
public string Nome {get;
conjunto; } public int Age {get;
conjunto; }

public int CompareTo (outra pessoa)


{
retornar this.Age.CompareTo (other.Age);
}
}

Programa de aula
{
static void Main (string [] args)
{
List <Person> people = new List <Person> ();

people.Add (nova Pessoa {Nome = "Sundus", Idade =


21}); people.Add (nova Pessoa {Nome = "Ali", Idade =
22}); people.Add (new Person {Name = "Hogi", Age =
12});

// classifica a lista de
pessoas people.Sort ();

foreach (var pessoa em pessoas)


{
Console.WriteLine (person.Age + "" + person.Name);
}

}
}
//Resultado
12 Hogi
21 Sundus
22 Ali

134

Capítulo 4 ■ Avançado C #
IComparer e IComparer <T>
IComparer e IComparer <T> são interfaces usadas para implementar em uma classe
separada que ajuda a classificar os objetos de acordo com seus valores de campo ou
propriedade.

IComparer
IComparer é uma interface definida no espaço para nome System.Collections.Generic.
Ajuda a comparar dois objetos.

Listagem 4-36.  Definição de IComparer

interface pública IComparer


{
// Compara dois objetos e retorna um valor indicando se um está
// menor que, igual a ou maior que o outro.

int Compare (objeto x, objeto y);


}

Listagem 4-37.  Classificar pessoa por idade e nome


using System;
using System.Collections;

classe Pessoa
{
public string Nome {get;
conjunto; } public int Age {get;
conjunto; }

classe sortAge: IComparer


{
public int Compare (objeto x, objeto y)
{
Pessoa primeiro = (Pessoa)
x; Pessoa segundo =
(Pessoa) y;

retornar first.Age.CompareTo (second.Age);


}
}

classe SortName: IComparer


{
public int Compare (objeto x, objeto y)
{
Pessoa primeiro = (Pessoa)
x; Pessoa segundo =
(Pessoa) y;

135

Capítulo 4 ■ Avançado C #
retornar first.Name.CompareTo (second.Name);
}
}

Programa de aula
{
static void Main (string [] args)
{
Pessoas ArrayList = new ArrayList ();

people.Add (nova Pessoa {Nome = "Sundus", Idade =


21}); people.Add (nova Pessoa {Nome = "Ali", Idade =
22}); people.Add (new Person {Name = "Hogi", Age =
12});

// lista de classificação de
acordo com a idade
people.Sort (new sortAge ());

foreach (Pessoa pessoa em pessoas)


{
Console.WriteLine (person.Age + "" + person.Name);
}

Console.WriteLine ();
// lista de classificação de
acordo com o nome
people.Sort (new SortName
());

foreach (Pessoa pessoa em pessoas)


{
Console.WriteLine (person.Name + "" + person.Age);
}

}
}
//Resultado
12 Hogi
21 Sundus
22 Ali

Ali 22
Hogi 12
Sundus 21

IComparer <T>
IComparer <T> é uma interface de segurança de tipo definida em
System.Collections.Generic. Ajuda a comparar dois objetos. São necessários parâmetros de
segurança de tipo para seu método Compare.

136

Capítulo 4 ■ Avançado C #
Listagem 4-38.  Definição de IComparer <T>
interface pública IComparer <em T>
{
// Compara dois objetos e retorna um valor indicando
// se um é menor que, igual a ou maior que o outro.

int Compare (T x, T y);


}
Listagem 4-39.  Classificar pessoa por idade e nome
using System;
using System.Collections.Generic;

classe Pessoa
{
public string Nome {get;
conjunto; } public int Age {get;
conjunto; }

classe sortAge: IComparer <Person>


{
public int Compare (Pessoa x, Pessoa y)
{
retornar x.Age.CompareTo (y.Age);
}
}

classe SortName: IComparer <Person>


{
public int Compare (Pessoa x, Pessoa y)
{
retornar x.Name.CompareTo (y.Name);
}
}

Programa de aula
{
static void Main (string [] args)
{
List <Person> people = new List <Person> ();

people.Add (nova Pessoa {Nome = "Sundus", Idade =


21}); people.Add (nova Pessoa {Nome = "Ali", Idade =
22}); people.Add (new Person {Name = "Hogi", Age =
12});

// lista de classificação de
acordo com a idade
people.Sort (new sortAge ());

137

Capítulo 4 ■ Avançado C #
foreach (var pessoa em pessoas)
{
Console.WriteLine (person.Age + "" + person.Name);
}

Console.WriteLine ();
// lista de classificação de
acordo com o nome
people.Sort (new SortName
());

foreach (var pessoa em pessoas)


{
Console.WriteLine (person.Name + "" + person.Age);
}

}
}
//Resultado
12 Hogi
21 Sundus
22 Ali

Ali 22
Hogi 12
Sundus 21

IEquatable <T>
IEquatable <T> é uma interface implementada por tipos cujos valores podem ser equacionados
(por exemplo, as classes numéricas e de string). Porém, para a maioria dos tipos de referência, o
uso do IEquatable é evitado porque, se você o fizer, precisará substituir os métodos
Object.Equals (Object) e GetHashCode. Portanto, seu comportamento é consistente com o método
IEquatable.Equals.

Listagem 4-40.  Definição de IEquatable <T>


interface pública IEquatable <T>
{

// Indica se o objeto atual é igual a


// outro objeto do mesmo tipo.

bool igual a (T outro);


}

Listagem 4-41.  Igualar dois objetos


using System;

classe Pessoa: IEquatable <Person>


{
public string Nome {get;
conjunto; } public int Age {get;
conjunto; }

138

Capítulo 4 ■ Avançado C #
public bool Equals (outra pessoa)
{
if (this.Name.CompareTo (other.Name) == 0 && this.Age == other.Age)
{
return true;
}
outro
{
retorna falso;
}
}

public override bool Equals (objeto obj)


{
Pessoa outra = (Pessoa) obj;
retorne this.Equals (other);
}

substituição pública int GetHashCode ()


{
// implementação personalizada da
string hashcode hash = this.Name +
this.Age; return hash.GetHashCode
();
}
operador public bool estático == (Pessoa pessoa1, Pessoa pessoa2)
{
if (((object) person1) == null || ((object) person2) == null)
retorna Object.Equals (person1, person2);

retornar person1.Equals (person2);


}

operador public bool estático! = (Pessoa pessoa1, Pessoa pessoa2)


{
if (((object) person1) == null || ((object) person2) == null)
return! Object.Equals (person1, person2);

return! (person1.Equals (person2));


}
}

Programa de aula
{
static void Main (string [] args)
{
Pessoa pessoa1 = nova Pessoa
(); person1.Age = 22;
person1.Name = "Ali";

139

Capítulo 4 ■ Avançado C #
Pessoa pessoa2 = nova Pessoa
(); person2.Age = 22;
person2.Name = "Ali";

Console.WriteLine (person1 == person2);


}
}
// Saída
True

Trabalhando com Strings


String é usada para armazenar valores de texto. String é imutável, o que significa que, uma vez
que uma variável de string armazena algum texto, ela não pode editá-lo novamente; o texto é
armazenado como uma coleção somente leitura de objetos Char. Portanto, sempre que o valor
de uma variável de cadeia é atualizado, ele recria uma instância para literais de cadeia, o que
não é bom em termos de consumo de memória e processo.

Listagem 4-42.  Registre quanto tempo leva para anexar uma sequência 1.000.000 de vezes

using System;
using System.Diagnostics; // para cronômetro

Programa de aula
{
static void Main (string [] args)
{
Cronômetro = novo cronômetro ();

// Grave quanto tempo


watch.Start ();

string mystring = "teste"; for


(int i = 1; i <100000; i ++)
{
mystring + = i;
}

// Parar o tempo de
gravação watch.Stop
();

float miliToSec = watch.ElapsedMilliseconds / 1000;


Console.WriteLine ("Tempo total: {0} s", miliToSec);

}
}
//Resultado
Tempo total: 51s

Na minha máquina, foram necessários 51 segundos para anexar uma string por 100.000
vezes, porque toda vez que o CLR cria uma nova instância de literais de string e reatribui sua
referência a uma variável de string.

140

Capítulo 4 ■ Avançado C #
StringBuilder
StringBuilder é uma classe de System.Text que fornece melhor desempenho ao manipular
dados de texto de uma maneira muito melhor que um System.String tradicional. StringBuilder
é mutável, o que significa que os dados de texto podem ser editáveis. Seu método Append ajuda
a concatenar os dados de texto de uma maneira melhor.

Listagem 4-43.  Registre quanto tempo leva para anexar o texto de um StringBuilder por 100.000 vezes
using System;
using System.Diagnostics; // para
cronômetro using System.Text;

Programa de aula
{
static void Main (string [] args)
{
Cronômetro = novo cronômetro ();

// Grave quanto tempo


watch.Start ();

StringBuilder mystring = novo StringBuilder ("teste");


for (int i = 1; i <100000; i ++)
{
mystring.Append (i);
}

// Parar o tempo de
gravação watch.Stop
();

Console.WriteLine ("Tempo total: {0} ms", watch.ElapsedMilliseconds);

}
}
//Resultado
Tempo total: 35ms

Na minha máquina, foram necessários 35 milissegundos para acrescentar um texto


no StringBuilder, enquanto no exemplo anterior System.String levou 51 segundos .
Portanto, o StringBuilder é mais rápido que o System.String
StringReader
StringReader é uma classe do System.IO usada para ler linhas de uma string. Com
StringReader, podemos ler um personagem com Leia ou ReadAsync método, e uma
seqüência inteira com ReadToEnd ou ReadToEndAsync método. Esse tipo ajuda a acessar
dados da string por meio de uma interface orientada a fluxo .
Listagem 4-44.  Ler linha por linha com StringReader
using System;
using System.IO;

Programa de aula
{
141

Capítulo 4 ■ Avançado C #
static void Main (string [] args)
{
// '@' É uma string literal literal. Ele ignora a sequência de
caracteres de escape text = @ "Olá, sou Ali Asad.
Eu posso ajudá-lo no exame de certificação C #.
Ajudei muitas pessoas como você na preparação para os
exames. Acredito que se trabalharmos juntos, você pode se
tornar:
Profissional certificado pela Microsoft e especialista em C # ";

Leitor de StringReader = novo StringReader


(texto); int currentLine = 0;
linha de string = "";

// retorna cada linha da string para 'line'


while ((line = reader.ReadLine ())! = null)
{
Console.WriteLine ("linha {0}: {1}", ++ currentLine, linha);
}

}
}
//Resultado
Linha1: Olá, sou Ali Asad.
Linha2: Eu posso ajudá-lo no exame de certificação C #.
Linha 3: Ajudei muitas pessoas como você na preparação para os exames.
Linha4: Acredito que se trabalharmos juntos, você pode se tornar:
Line5: Microsoft Certified Professional & Specialist em C #

StringWriter
StringWriter é uma classe do System.IO. É usado para gravar em uma classe StringBuilder.
Com StringWriter, podemos escrever um personagem / string com Write ou WriteAsync
método, e toda uma linha de corda com WriteLine ou WriteLineAsync método. É uma maneira
eficiente de usar o StringBuilder com o StringWriter para manipular a string.

Listagem 4-45.  Grave dados da string no StringBuilder usando StringWriter

using System;
using System.IO;
using System.Text;

Programa de aula
{
static void Main (string [] args)
{
Construtor StringBuilder = novo StringBuilder ();

StringWriter swriter = novo StringWriter (construtor);


swriter.Write ("Ali Asad");

142

Capítulo 4 ■ Avançado C #
Console.WriteLine (builder.ToString ());

}
}
// Saída
Ali Asad

Enumerar métodos de string


String usada para dados de texto. Possui muitos métodos e propriedades que ajudam a
manipular dados de texto. Alguns deles estão listados abaixo.
Clone ()
Use para criar clone de sequência no tipo de objeto.

texto da string = "Ali Asad";


string clonada = text.Clone () como string;

Console.WriteLine (clonado);
// Saída
Ali Asad

Comparado a( )
Compare dois valores de sequência e retorne o valor inteiro. Retorna 0 para verdadeiro.

string text1 = "ali";


string text2 = "asad";

if ((text1.CompareTo (text2)) == 0)
{
Console.WriteLine ("ambos os textos são iguais");
}
outro
{
Console.WriteLine ("ambos os textos não são iguais");
}
//Resultado
o texto não é o mesmo

Termina com( )

Return true se encontrar um caractere especificado é o último caractere de uma string.

string text1 = "ali"; Console.WriteLine


(text1.EndsWith ("i"));
// Saída
True

143
Capítulo 4 ■ Avançado C #
É igual a( )
Compare duas strings e retorne true se forem iguais.

string text1 = "ali";


string text2 = "ali";

Console.WriteLine (texto1.Equals (texto2));


// Saída
True

Índice de( )
Retorne o número do índice da primeira ocorrência de um caractere especificado.

string text1 = "ali"; Console.WriteLine


(text1.IndexOf ('l'));
//Resultado
11

Abaixar( )
Retorne a minúscula da string.

string text1 = "ALI";


Console.WriteLine (text1.ToLower
());
//
Output
ali

ToUpper ()
Retorne a caixa alta da string.

string text1 = "ali";


Console.WriteLine (text1.ToUpper
());
//Resultado
TODOS

Inserir ()
Retorna uma nova string na qual um novo caractere / string é inserido em um índice especificado de uma
string.

string text1 = "Ali"; Console.WriteLine


(text1.Insert (3, "Asad"));
// Saída
Ali Asad

144

Capítulo 4 ■ Avançado C #
LastIndexOf ()
Retorne o último índice de um caractere especificado em uma sequência.
string text1 = "ali asad"; Console.WriteLine
(text1.LastIndexOf ('a'));
//Resultado
6

Retirar( )
Retorne uma nova string excluindo todos os caracteres de um índice especificado até o final.

string text1 = "ali asad";


Console.WriteLine (text1.Remove
(3));
//
Output
ali

Substituir ()
Retorna uma nova string na qual a ocorrência de caracteres especificados é substituída
por outros caracteres especificados.

string text1 = "ali asad"; Console.WriteLine


(text1.Replace ("ali", "asad"));
// Saída
asad
asad

Dividido( )
Divida uma sequência em uma matriz de sequências baseada nos caracteres que ocorrem em uma
sequência.

string text1 = "ali asad";

string [] subString = text1.Split ('');

foreach (item var na subString)


{
Console.WriteLine (item);
}
// Saída
ali asad

Começa com( )
Retorne true se o início de uma string começar com um caractere / string especificado.

string text1 = "ali asad";

Console.WriteLine (text1.StartsWith ("al"));


// Saída
True

145

Capítulo 4 ■ Avançado C #
Substring ()
Retorne uma nova sequência que contenha caracteres de um índice inicial especificado para um
comprimento especificado de caracteres.

string text1 = "ali asad";

Console.WriteLine (text1.Substring (2, 5));


// Saída
i asa

ToCharArray ()
Retorna uma nova matriz de caracteres que contém o caractere de uma string.

string text1 = "ali";

char [] chArray = texto1.ToCharArray ();

foreach (item var no chArray)


{
Console.WriteLine (item);
}
// Saída
a
li

Aparar( )
Remova os espaços em branco do início e do final de uma string.

string text1 = " todos ";

Console.WriteLine ("{0} {1}", texto1.Trim (). Comprimento, texto1.Trim ());


// Saída
3 ali

Para sequenciar( )
Converte um objeto em sua representação de string. É um método que pode ser substituído em
qualquer tipo personalizado para obter as informações do objeto como uma sequência.

using System;

classe Pessoa
{
public string Nome {get;
conjunto; } public int Age {get;
conjunto; }

substituição pública string ToString ()


{

146

Capítulo 4 ■ Avançado C #
string data = "Name =" + this.Name + "" + "Age =" + this.Age;

retornar dados;
}
}

Programa de aula
{
static void Main (string [] args)
{
Pessoa pessoa = nova Pessoa {Nome = "Ali", Idade = 22};

// person & person.ToString () são os mesmos nesse


caso // Portanto, ambos produzem o mesmo resultado
em tempo de execução. // person = person.ToString ()
Console.WriteLine (person); Console.WriteLine
(person.ToString ());
}
}
Resultado
Nome = Ali Idade = 22

Método String.Format
String.Format ajuda a representar valores de objetos em um formato especificado e
retorná-los como uma string. Sua sintaxe é semelhante ao método
Console.WriteLine.
Sintaxe

variável de string = string.Format ("");

Fragmento de código

nome da string =
"Ali"; int idade = 22;

string info = string.Format ("Nome = {0} Idade = {1}", nome, idade);

Console.WriteLine (informações);
//Resultado
Nome = Ali Idade = 22

Formatos especiais para exibir o valor do objeto


Objetos são de vários tipos. Para cada tipo, os dados são armazenados ou exibidos em um
formato diferente. Alguns dos formatos estão listados abaixo:
• Formatos numéricos padrão
• Controle de espaçamento
• Alinhamento de controle
147

Capítulo 4 ■ Avançado C #
Formatos numéricos padrão
Sequências numéricas padrão são usadas para formatar tipos numéricos comuns. Eles estão listados abaixo
em uma tabela.

Tabela 4-13.  Especificador numérico de formato


Especificador de formatoExplicação
"C" ou "c" Usado para formatar o valor da moeda
"D" ou "d" Usado para formatar dígitos inteiros com sinal negativo opcional
"E" ou "e" Usado para formatar notação exponencial
"F" ou "f" Usado para o especificador de precisão para definir o valor flutuante fixo
"N" ou "n" Usado para formatar números por separadores de grupos
"P" ou "p" Usado para exibir porcentagem com número
"X" ou "x" Usado para exibir o valor hexadecimal
   

Listagem 4-46.  Usar formato de moeda


preço decimal = 1921,39m;
Console.WriteLine (price.ToString ("C")));
// Saída $
1.921,39
Listagem 4-47.  Usar formato de dígito inteiro
int temp = 12; Console.WriteLine
(temp.ToString ("D")); // D3 = 3 dígitos
serão exibidos (012) Console.WriteLine
(temp.ToString ("D3"));
//Resultado
12
012

Listagem 4-48.  Usar formato exponencial


valor duplo = 54321,6789;
Console.WriteLine (value.ToString ("E"));
//Resultado
5.432168E + 004

Listagem 4-49.  Use o formato de flutuação fixa

número duplo = 18934.1879;


Console.WriteLine (Number.ToString
("F")));
//Resultado
18934.19

148

Capítulo 4 ■ Avançado C #
Listagem 4-50.  Use separadores de grupo para formatar números
Número int = 12345678;
Console.WriteLine (Number.ToString
("N"));
//Resultado
12.345.678,00

Listagem 4-51.  Mostrar valor percentual


int Número = 1; Console.WriteLine
(Number.ToString ("P"));
//Resultado
100,00%

Listagem 4-52.  Exibir valor hexadecimal de um número


Número int = 2154; Console.WriteLine
(Number.ToString ("X"));
//Resultado
86A

Controle de espaçamento
O espaçamento é útil para formatar a saída. String pode ajudar a formatar o espaçamento.

Listagem 4-53.  Crie 10 espaços


nome da string =
"Ali"; int idade = 22;

Console.WriteLine ("Nome {0,10} | Idade {1, 10}", nome, idade);


//Resultado
   
Nome Ali Era 22

Alinhamento de controle
Por padrão, as strings estão alinhadas à direita. Para criar uma sequência alinhada à esquerda
em um campo, você precisa usar um sinal negativo, como {0, -5} para definir um campo
alinhado à direita com 5 caracteres .

Listagem 4-54.  Controlar o alinhamento do texto


nome da string = "Ali";

Console.WriteLine ("- {0, -8} | end", nome);


//Resultado
 
- Todos | end

149

Capítulo 4 ■ Avançado C #

Sumário
• Boxe refere-se à conversão implícita de um tipo de valor em um tipo
de objeto ou em qualquer interface que ele implemente.
• Desmarcar a caixa refere-se a uma conversão explícita de um tipo de
objeto para um tipo de valor não anulável ou à conversão de um tipo de
interface para um tipo de valor não anulável .
•O boxe pode levar até 20 vezes mais do que uma simples atribuição de
referência. Quando um valor em caixa é retirado da caixa, leva 4 vezes
mais que uma atribuição de referência simples.
• Os genéricos executam verificações em tempo de compilação para
segurança de tipo e é muito mais rápido que o boxe / unboxing.
• onde a palavra-chave é usada para aplicar restrições nos parâmetros de tipo genérico.
• C # define a estrutura de dados em uma coleção (ou seja, ArrayList, Stack, Queue).
•O usuário pode criar uma coleção personalizada implementando IEnumerable ou
IEnumerable <T>.
•O StringBuilder fornece melhor desempenho ao manipular dados de
texto de uma maneira muito melhor do que o System.String
tradicional.
• StringReader é geralmente usado para ler linhas de uma string.
• StringWriter é usado para escrever texto em um StringBuilder.
• String.Format ajuda a representar valores de objetos em um formato
especificado e retorná-los como uma string.

Desafios de código
Desafio 1: desenvolva uma coleção genérica personalizada.
Crie uma coleção genérica personalizada que implemente as seguintes interfaces.
• IList <T>
• ICollection <T>
• IEnumerable <T>
Practice Exam Questions
Questão 1
Você está desenvolvendo um jogo que permite aos jogadores coletar de 0 a 1.000 moedas. Você
está criando um método que será usado no jogo. O método inclui o seguinte código. (Os
números de linha estão incluídos apenas para referência.)

01 string pública FormatCoins (nome da string, moedas int)


02 {

04 }

150

Capítulo 4 ■ Avançado C #
O método deve atender aos seguintes requisitos:
• Retorne uma string que inclua o nome do jogador e o número de moedas.
• Exiba o número de moedas sem zeros à esquerda se o número for 1 ou maior.
• Exiba o número de moedas como um único 0 se o número for 0.

Você precisa garantir que o método atenda aos requisitos. Qual segmento de código você
deve inserir na linha 03?

A)
retornar string.Format ("Player {0}, coletado {1} moedas", nome, coins.ToString ("### 0"));

B)
retornar string.Format ("Jogador {0}, coletado {1: 000 #} moedas", nome, coins.ToString ());

C)
retornar string.Format ("Player {name}, coletado {coins.ToString ('### 0')} coins");

D)
retornar string.Format ("Jogador {0}, coletado {1: D3} moedas", nome, moedas);

Questão 2
O código a seguir está encaixotado no objeto o.

d duplo = 34,5;
objeto o = d;

Você é solicitado a converter "objeto o" em "int" ".

A)
int i = (int) o;

B)
int i = (int) (duplo) o;

C)
int i = (int) (flutuante) (duplo) o;

D)
int i = (flutuante) o;
151

Capítulo 4 ■ Avançado C #
Questão 3
Suponha que você esteja desenvolvendo um aplicativo que armazene o histórico do navegador
de um usuário. Qual classe de coleção ajudará a recuperar as informações da última página
visitada?

A)
ArrayList

B)
Fila

C)
Pilha

D)
HashTable

Respostas
\ 1. \ D
\ 2. \ C

\ 3. \ C

152
CAPÍTULO 5

Delegados de Implementação e
Eventos

Em qualquer linguagem moderna, o desenvolvimento orientado a eventos é usado para


estruturar um programa em torno de vários eventos. Esses eventos executam uma certa
funcionalidade quando uma determinada condição satisfaz, por exemplo, fecha o aplicativo
quando um usuário clica no botão "Sair". Ou desligue o sistema quando a temperatura do calor
aumentar, etc.
Neste capítulo, aprenderemos tudo o que precisamos saber sobre desenvolvimento
orientado a eventos em C #.

Delegar
Delegado é um tipo, semelhante aos ponteiros de função em C / C ++. Ele armazena a referência
de um método dentro de um objeto delegado, para chamar o método referenciado em qualquer
lugar do código. Também permite que o método seja passado como argumento de outro
método. Ao contrário dos ponteiros de função no C ++, os representantes são ponteiros de
função com segurança de tipo .
A declaração delegada determina um tipo que pode se referir a um método, que possui o
mesmo conjunto de argumentos e retorna um tipo. Um único objeto delegado pode conter a
referência de vários métodos que podem ser chamados em um único evento.
Sintaxe

delegado access_specifier retun_type delegateName (argument_list);

Fragmento de código

Listagem 5-1.  Declarar, instanciar e usar um delegado


// declarar um delegado
delegado público void delegateName (string msg);

// Declara um método com a mesma assinatura que o


delegado. exibição estática de vazio (string msg)
{
Console.WriteLine (msg);
}

static void Main (string [] args)


{
// Crie uma instância do delegate
delegateName del = new delegateName
(display);

// Chamando o
delegado del ("Ali
Asad");

© Ali Asad e Hamza Ali 2017 153


A. Asad e H. Ali, Guia de Estudo do Programador C # (MCSD) , DOI 10.1007 / 978-1-4842-2860-9_5
Capítulo 5 ■ Implementando delegados e eventos
• novo nome do delegado (exibição); passe a referência do
método "display" no construtor delegateName.
• del ( “Ali Asad”); chame “del” que chama o método “display”.
O representante também pode armazenar diretamente a referência de um método. Veja o seguinte
trecho de código.

Listagem 5-2.  Armazene a referência de um método diretamente

// Crie uma instância do delegate


delegateName del = display;

O delegado também pode ser chamado usando o método .invoke. Veja o seguinte trecho de código.

Listagem 5-3.  Use o método .invoke

// chama o método usando .invoke () e passa a string


msg del.Invoke ("Ali Asad");

Delegado de difusão seletiva


Delegado mantém a referência de mais de um método chamado delegado multicast. Ajuda
a invocar os vários métodos.
• Ao usar + = , o delegado pode adicionar uma nova referência de método
sobre uma referência armazenada existente.
• Usando - =, o delegado pode remover a referência de um método da instância de um delegado.

Listagem 5-4.  Adicionar referência de vários métodos

using System;

// declarar um delegado
delegado público void delegateName (string
msg); classe MyClass
{
// Declara um método com a mesma assinatura que o
delegado. exibição estática de vazio (string msg)
{
Console.WriteLine ("display: {0}", msg);
}

show nulo estático (string msg)


{
Console.WriteLine ("show: {0}", msg);
}

tela vazia estática (string msg)


{
Console.WriteLine ("tela: {0}", msg);
}

154

Capítulo 5 ■ Implementando delegados e eventos


static void Main (string [] args)
{
delegateName del = display;

// Delegado multicast
del + = show;
del + = tela;
// chamando
delegate del
("Ali");

}
}
// Exibição
de saída: Ali
show: Ali
tela: Ali

• del + = referência add do método no topo de referência existente do método.


• del ( “Ali Asad”); invoque todos os métodos um por um na mesma ordem em que foram
adicionados.

Listagem 5-5.  Remover a referência do método da instância de um delegado

using System;

// declarar um delegado
delegado público void delegateName (string
msg); classe MyClass
{
// Declara um método com a mesma assinatura que o
delegado. exibição estática de vazio (string msg)
{
Console.WriteLine ("display: {0}", msg);
}

show nulo estático (string msg)


{
Console.WriteLine ("show: {0}", msg);
}

tela vazia estática (string msg)


{
Console.WriteLine ("tela: {0}", msg);
}

static void Main (string [] args)


{
delegateName del = display;

155

Capítulo 5 ■ Implementando delegados e eventos


// Delegado multicast
del + = show;
del + = tela;

// remove a referência do
método del - = show;

// chamando
delegate del
("Ali");

}
}
// Exibição
de saída:
tela Ali: Ali

• del - = mostrar; remove a referência de um método show de uma instância delegada.

Listagem 5-6.  Faça um loop sobre cada método usando o método getinvocationlist
using System;

// declarar um delegado
public delegate int delegateName ();
classe MyClass
{
static int Get20 ()
{
Console.Write ("Get20 ():");
retorno 20;
}
static int Get30 ()
{
Console.Write ("Get30 ():");
retornar 30;
}
static int Get15 ()
{
Console.Write ("Get15 ():");
retorno 15;
}

static void Main (string [] args)


{
delegateName del = Get20;

// adiciona referência
de método del + =
Get30;
del + = Get20;
del + = Get15;

156

Capítulo 5 ■ Implementando delegados e eventos


foreach (item delegateName em del.GetInvocationList ())
{
// invoca cada método e exibe o valor de retorno
Console.WriteLine (item ());
}

}
// Saída
Get20 (): 20
Get30 (): 30
Get20 (): 20
Get15 (): 15

• del.GetInvocationList (); retorna uma lista de todos os métodos referenciados armazenados


em "del".

Delegados internos comuns


C # fornece muitos built-in delegados que são úteis para fins comuns. Estes built-in tipos
fornecem uma notação abreviada que praticamente elimina a necessidade de tipos de
declaração de delegado.
Alguns comum built-in delegados são:
• Ação
• Ação <>
• Func <>
• Predicado <>

Açao
Ação é um built-in tipo delegado disponível no namespace System. Pode ser usado com
métodos que não retornam um valor e não possuem lista de parâmetros.
Sintaxe

delegado público void Action ()

Fragmento de código

Listagem 5-7.  Usar delegado de ação


using System;

classe MyClass
{
estático voidMethod ()
{
Console.WriteLine ("Método de Vazio");
}

157

Capítulo 5 ■ Implementando delegados e eventos


static void emptyMethod ()
{
Console.WriteLine ("Método Vazio");
}

static void Main (string [] args)


{

Ação ato = voidMethod;


ato + = emptyMethod;
Aja();
}

}
// Método
vazio de
saída Método
vazio

Ação <>
Ação <> é um representante genérico. Pode ser usado com métodos que tenham pelo menos um
argumento e não retornem um valor. O delegado Action <> vem com 16 sobrecargas genéricas,
o que significa que ele pode levar até 16 argumentos do método void.
Fragmento de código

Listagem 5-8.  Delegado Use Action <>


using System;

classe MyClass
{
static void myintMethod (int i)
{
Console.WriteLine ("myintMethod: i = {0}", i);
}

static void myintStringMethod (int i, string s)


{
Console.WriteLine ("myintStringMethod: i = {0} s = {1}", i, s);
}
static void Main (string [] args)
{

Ação <int> myIntAct = myintMethod;


Ação <int, string> myIntStringAct = myintStringMethod;

myIntAct (22);
myIntStringAct (22, "Ali");

158

Capítulo 5 ■ Implementando delegados e eventos


// Saída myintMethod: i = 22
myintStringMethod: i = 22 s = Ali

• O delegado da ação <> é seguro para tipos, o que significa que ele pode
receber argumentos de qualquer tipo e o tipo de argumento será seguro
em tempo de compilação.

Func <>
Func <> é um representante genérico. Pode ser usado com métodos que retornam um valor e
podem ter uma lista de parâmetros. O último parâmetro de Func <> determina o tipo de retorno
do método e os parâmetros restantes são usados para a lista de argumentos de um método. O
delegado Func <> vem com 17 sobrecargas genéricas, o que significa que ele usa o último
parâmetro como o tipo de retorno do método e os 16 restantes podem ser usados como uma
lista de argumentos do método. Além disso, se o Func <> tiver apenas um parâmetro, seu
primeiro parâmetro será considerado como o tipo de retorno do método.
Fragmento de código

Listagem 5-9.  Usar delegado Func <>

using System;

classe MyClass
{
static int Add (int x, int y)
{
Console.Write ("{0} + {1} =", x, y);
retorno (x + y);
}

static int Min (int x, int y)


{
Console.Write ("{0} - {1} =", x, y);
retorno (x - y);
}

static int Mul (int x, int y)


{
Console.Write ("{0} * {1} =", x, y);
retorno (x * y);
}

string estática Name ()


{
Console.Write ("Meu nome é =");
retornar "Ali Asad";
}

sequência estática DynamicName (nome da sequência)


{
Console.Write ("Meu nome é =");
nome de retorno;
}
159

Capítulo 5 ■ Implementando delegados e eventos


static void Main (string [] args)
{

// retorna o valor da string


Func <string> info = Name;
Console.WriteLine (info ());

// retorna string e aceita string como parâmetro


Func <string, string> dynamicInfo =
DynamicName; Console.WriteLine (dynamicInfo
("Hamza Ali"));

// retorna int e recebe dois int como


parâmetro Func <int, int, int> calcule =
Adicionar; calcular + = min;

calcular + = Mul;

foreach (item Func <int, int, int> no calculador.GetInvocationList ())


{
Console.WriteLine (item (10,5));
}

}
//Resultado
Meu nome é = Ali Asad
Meu nome é = Hamza
Ali 10 + 5 = 15 10 - 5 =
10 10 * 5 = 50

•O primeiro parâmetro em func <> determina o tipo de retorno do método


e o restante é considerado como uma lista do tipo de argumento do
método.

Predicado <T>
Um delegado predicado representa um método que usa um parâmetro de entrada e retorna
um valor bool com base em alguns critérios.
Sintaxe

público delegado bool Predicado <T> ()


Fragmento de código

Listagem 5-10.  Use Predicado para determinar se um número é par ou não


using System;

classe MyClass
{

160

Capítulo 5 ■ Implementando delegados e eventos


bool estático Par (int i)
{
retorno (i% 2 == 0);
}
static void Main (string [] args)
{

Predicado <int> isEven = Even;

Console.WriteLine (isEven (7));


}

}
// Saída
falsa

Variação no Delegado
Com variação nos delegados, o método não precisa corresponder ao tipo de delegado. Como a
variação fornece um grau de flexibilidade ao combinar um tipo de delegado com a assinatura
do método, podemos usar a variação das duas maneiras a seguir.
\ 1. \ Covariância
\ 2. \ Contravariância

Covariância
A covariância é aplicada no tipo de retorno de um método . Com covariância, um
delegado pode manter uma referência de um método, cujo valor de retorno é um tipo
derivado do tipo de retorno na assinatura do delegado.
Fragmento de código

Listagem 5-11.  Covariância no Delegado

using System;

classe Pai {}
classe Filho: Pai {}

delegar Parent CovarianceHandle ();

Programa de aula
{
static CovarianceMethod ()
{
Console.WriteLine ("Método de
Covariância"); retornar novo Child ();
}

static void Main (string [] args)

161
Capítulo 5 ■ Implementando delegados e eventos
{
//Covariância
CovarianceHandle del = CovarianceMethod;

del ();
}
}
// Método de
covariância de
saída

Contravariância
A contravariância é aplicada no tipo de parâmetro de um método . Com
contravariância, um delegado pode manter uma referência de um método cujo valor de
parâmetro é um tipo base do tipo de parâmetro de assinatura de delegado.
Fragmento de código

Listagem 5-12.  Contravariância no delegado


using System;

classe Pai {}
classe Filho: Pai {}

delegar nulo ContravarianceHandle (Criança c);

Programa de aula
{
static void ContravarianceMethod (pai p)
{
Criança ch = p como Criança;
Console.WriteLine ("Método de
contravariância");
}

static void Main (string [] args)


{

ContravarianceHandle del = ContravarianceMethod;

Criança criança = nova Criança ();

// Contravariância
del (filho);
}
}
// Método de
contravariância de
saída

162

Capítulo 5 ■ Implementando delegados e eventos


Problemas com o delegado
Os delegados têm alguns problemas que os eventos superaram. Esses problemas são:
\ 1. \ Qualquer pessoa pode usar um operador de atribuição que pode
sobrescrever as referências de métodos.

Listagem 5-13.  Substituir as referências de métodos no Delegado


using System;

Programa de aula
{
Exibição de estática ()
{
Console.WriteLine ("Display");
}

estático vazio Show ()


{
Console.WriteLine ("Show");
}
static void Main (string [] args)
{
Ação ato = Display;
ato + = Mostrar;

ato = exibição;

Aja();
}
}
//
Exibição
de saída

\ 2. \ Delegate pode ser chamado em qualquer lugar no código, o


que pode violar a regra do encapsulamento.

Listagem 5-14.  Alerta de temperatura ambiente alta


using System;

Sala de aula
{
Ação pública <int> OnHeatAlert;

int temp;
public int Temperature
{
get {retorna this.temp; }
definir

163

Capítulo 5 ■ Implementando delegados e eventos


{
temp = valor;
if (temp> 60)
{
if (OnHeatAlert! = null)
{
OnHeatAlert (temp);
}
}
}
}
}
Programa de aula
{
alarme nulo estático (int temp)
{
Console.WriteLine ("Ativar CA, está quente. A temperatura da sala é {0}", temp);
}
static void Main (string [] args)
{
Sala = nova sala ();
room.OnHeatAlert =
Alarme;

// OnHeatAlert será chamado


room.Temperature = 90;

room.Temperature = 15; //
OnHeatAlert será chamado
// O que não deve ser chamado porque room não é
hot room.OnHeatAlert (room.Temperature);
// Delegado é chamado fora da classe Room
}
}
//Resultado
Ligue AC, está quente. A temperatura
do quarto é 90 Ligada AC, está
quente. A temperatura do quarto é 15

Método Anônimo
Um método anônimo é um método sem nome. Estes são métodos que são definidos com uma
palavra-chave delegada. Um método anônimo não tem um tipo de retorno em sua assinatura.
Seu tipo de retorno depende do tipo de variável delegada que mantém sua referência.
Sintaxe

delegate_type delegate_variable = delegate (lista_parâmetro)


{
// Corpo do método
};

164

Capítulo 5 ■ Implementando delegados e eventos


Fragmento de código

Listagem 5-15.  Implementar métodos anônimos

using System;

Programa de aula
{
static void Main (string [] args)
{
// Método anônimo que não retorna valor
Action act = delegate ()
{
Console.WriteLine ("Método Anônimo Interno");
};

// Método anônimo que retorna o valor


Func <int, int> func = delegate (int num)
{
Console.Write ("Inside Func:");
return (num * 2);
};

Aja();
Console.WriteLine (func (4));
}
}
//Resultado
Método Inside
Anonymous Inside
Func: 8

Listagem 5-16.  Passar método anônimo como argumento de método

using System;

Programa de aula
{
public static void TestAnonymous (ato de ação)
{
Aja();
}
static void Main (string [] args)
{
TestAnonymous (delegate ()
{
Console.WriteLine ("Passar método anônimo no parâmetro do método");
});
}
}
//resultado
Passar método anônimo no perâmetro do método

165

Capítulo 5 ■ Implementando delegados e eventos

Expressão Lambda
A expressão lambda é uma versão melhor da implementação do método anônimo.
Sintaxe

delegate_type delegate_variable = (lista_parâmetro) =>


{
// Corpo do método
};

OU

delegate_type delegate_variable = (lista_parâmetro) => expressão;

Para criar uma expressão lambda, especificamos parâmetros de entrada (se houver)
no lado esquerdo do operador lambda => e colocamos o bloco de expressão ou instrução
no outro lado.
Fragmento de código

Listagem 5-17.  Implementar método anônimo com expressão lambda

using System;

Programa de aula
{
static void Main (string [] args)
{
// Expressão Lambda que não retorna valor
Action act = () =>
{
Console.WriteLine ("Expressão interna do Lambda");
};

// Expressão Lambda que possui valor de retorno


Func <int, int> func = (int num) =>
{
Console.Write ("Inside Func:");
return (num * 2);
};

Aja();
Console.WriteLine (func (4));
}
}
//Resultado
Método Inside
Anonymous Inside
Func: 8

166

Capítulo 5 ■ Implementando delegados e eventos


Se o corpo de um método anônimo contiver apenas uma instrução, a menção de chaves {{}
”e uma palavra-chave de retorno com o valor retornado é opcional. Veja o seguinte trecho de
código:

Listagem 5-18.  Implementar método anônimo em linha


using System;

Programa de aula
{
static void Main (string [] args)
{
// Expressão lambda que não retorna valor Action act
= () => Console.WriteLine ("Hello World");

// Expressão Lambda que possui valor de retorno


Func <int, int> func = (int num) => num * 2;

Aja();
Console.WriteLine (func (4));
}
}
// Saída Hello
World Inside
Func: 8

A expressão lambda também oferece a capacidade de não especificar um tipo de


parâmetro. Seu tipo de parâmetro dependerá do tipo de parâmetro do tipo delegado que
mantém sua referência. Veja o seguinte trecho de código.

Listagem 5-19.  Método anônimo sem especificar o tipo de parâmetro


// tipo de nome será string
Ação <string> actName = (nome) => Console.WriteLine (nome);
// para um único parâmetro, podemos negligenciar ()
paranthese Action <string> actName2 = name =>
Console.WriteLine (name);

Func <int, int> mul = (x) => x * 2;

actName ("Ali");
actName2 ("Ali");

Console.WriteLine (mul (10));


// Saída
Ali Ali

20

167

Capítulo 5 ■ Implementando delegados e eventos


Listagem 5-20.  Passar expressão lambda em um parâmetro de método
using System;

Programa de aula
{
static void TestLambda (ato de ação)
{
Console.WriteLine ("Método Lambda de
Teste"); Aja();
}
static void Main (string [] args)
{
// Passa a expressão Lambda como
parâmetro TestLambda (() =>
{
Console.WriteLine ("Inside Lambda");
});
}
}
//Resultado
Teste o método
Lambda dentro do
Lambda

Evento
Evento é uma ação que é executada quando uma condição especificada é satisfeita. Ele notifica
todos os seus assinantes sobre a ação que será executada. Por exemplo, quando um evento do
Windows 10 foi lançado, a Microsoft notificou todos os clientes para atualizar seu SO
gratuitamente. Portanto, neste caso, a Microsoft é uma editora que lançou ( levantadas ) um
evento do Windows 10 e notificado os clientes sobre isso e clientes são os assinantes do evento
e participou ( tratada ) do evento.
Da mesma forma, o evento C # é usado na classe para fornecer notificações aos clientes
dessa classe quando algo acontece com seu objeto. Eventos são declarados usando delegados.
Portanto, uma classe que contém a definição de um evento e seu representante é chamada
Publisher . Por outro lado, uma classe que aceita o evento e fornece um manipulador de
eventos é chamada Assinante .
Sintaxe
evento delegate_type OnEventName;

Fragmento de código

Listagem 5-21.  Declarar um evento


deletar void DieEventHandler ();
classe Pessoa
{
// Declarar um evento
evento público DieEventHandler Die;
}

168

Capítulo 5 ■ Implementando delegados e eventos


• Evento sempre é um membro de dados de uma classe ou estrutura.
Não pode ser declarado dentro de um método.
• É uma boa convenção de nomenclatura postfixar um nome
de delegado personalizado com “EventHandler” somente
quando for usado com o evento.

Listagem 5-22.  Manipulando e levantando um evento

using System;

Sala de aula
{
evento público Ação <object> Alert;

temperatura int int;


public int Temperature
{
get {retorne.Temperature; }
definir

{
this.Temperature = value;

if (Temperatura> 60)
{
if (Alerta! = nulo)
{
Alerta (isto);
}
}
}
}
}

Programa de aula
{
static void Main (string [] args)
{
Sala myRoom = nova Sala ();

// Inscreva-se em um
evento myRoom.Alert +
= OnAlert;

// Evento de alerta
invocará
myRoom.Temperature =
65;
}

OnAlert nulo estático privado (objeto o)


{
Sala = Sala (o);
Console.WriteLine ("Desligando. A temperatura da sala é {0}", room.Temperature);
}
}

169

Capítulo 5 ■ Implementando delegados e eventos


//Resultado
Desligando. A temperatura do quarto é 65

•O evento sempre é inscrito usando + =, por exemplo, myRoom.Alert + =


OnAlert ;. Não pode ser inscrito usando um único operador de atribuição.
•O evento é cancelado com o uso de object.EventName - = MethodName ;.
• myRoom.Temperature = 65; O evento de alerta será chamado porque a
temperatura da sala é superior a 60. Portanto, a condição é satisfeita e o
evento deve ser chamado.
• Para convenções de nomenclatura, é bom prefixar o nome de um método
com On somente quando for usado com o evento, por exemplo, On Alert.
•O evento sempre deve ser chamado dentro de uma classe onde está
definido. Diferentemente dos delegados, os eventos não podem ser
chamados fora da classe em que estão definidos.

Use delegados internos para implementar eventos


O C # fornece alguns representantes importantes para implementar eventos. Esses
delegados são úteis em determinadas situações. Alguns desses delegados são:
• EventHandler
• PropertyChangedEventHandler

EventHandler
EventHandler é um representante definido no espaço para nome do sistema. Este
delegado define um método do tipo de retorno nulo.
\ 1. \ Seu primeiro parâmetro é do tipo System.Object que se refere à
instância (onde o evento foi definido) que gera o evento.
\ 2. \ Seu segundo parâmetro é de um tipo EventArgs que contém dados
do evento. Se o evento não tiver nenhum dado para passar, o
segundo parâmetro será simplesmente o valor do campo
EventArgs.Empty. No entanto, se ele tiver um valor para passar, será
encapsulado em um tipo derivado de EventArgs.
Sintaxe

namespace System
{
delegado público void EventHandler (remetente do objeto, EventArgs e);
}

Fragmento de código

Listagem 5-23.  Manipulando e gerando um evento usando EventHandler


using System;

Sala de aula
{
evento público EventHandler Alert;

170

Capítulo 5 ■ Implementando delegados e eventos


temperatura int int;
public int Temperature
{
get {retorne.Temperature; }
definir

{
this.Temperature = value;
if (this.Temperature> 60)
{
if (Alerta! = nulo)
{

Alerta (this, EventArgs.Empty);


}
}
}
}
}

Programa de aula
{
static void Main (string [] args)
{
Sala = nova sala ();
room.Alert + = OnAlert;

room.Temperature = 75;
}

OnAlert nulo estático privado (remetente do objeto, EventArgs e)


{
Sala = remetente (sala);
Console.WriteLine ("Desligando, Temperatura da sala = {0}", room.Temperature);
}
}
//Resultado
Desligando, Temperatura ambiente = 75

Listagem 5-24.  Passar dados do evento usando EventHandler


using System;

classe HotelData: EventArgs


{
cadeia pública HotelName {get;
conjunto; } public int TotalRooms {get;
conjunto; }
}
Sala de aula
{
evento público EventHandler Alert;

171
Capítulo 5 ■ Implementando delegados e eventos
temperatura int int;
public int Temperature
{
get {retorne.Temperature; }
definir

{
this.Temperature = value;
if (this.Temperature> 60)
{
if (Alerta! = nulo)
{
Dados do HotelData = new HotelData
{
Nome do hotel = "Hotel 5
estrelas", Total de quartos =
450
};

// Passa dados do
evento Alert (this,
data);
}
}
}
}
}

Programa de aula
{
static void Main (string [] args)
{
Sala = nova sala ();
room.Alert + = OnAlert;

room.Temperature = 75;
}

OnAlert nulo estático privado (remetente do objeto, EventArgs e)


{
Sala = remetente (sala); Dados
do HotelData = (HotelData) e;

Console.WriteLine ("Desligando, Temperatura da sala = {0}", room.Temperature);


Console.WriteLine ("{0} possui total de {1} quartos", data.HotelName,
data.TotalRooms);
}
}
//Resultado
Desligando, Temperatura ambiente = 75
Hotel 5 estrelas tem um total de 450 quartos

172

Capítulo 5 ■ Implementando delegados e eventos


PropertyChangedEventHandler
PropertyChangedEventHandler é um representante definido no espaço para nome
System.ComponentModel. É usado com evento para referir um método que será chamado
sempre que uma Propriedade for alterada em um componente.
Sintaxe

delegado público void


PropertyChangedEventHandler (objeto
remetente, PropertyChangedEventArgs e

O evento PropertyChanged usa um delegado PropertyChangedEventHandler na interface


INotifyPropertyChanged . A classe, que implementa a interface INotifyPropertyChanged, deve
definir a definição de evento PropertyChanged.
Fragmento de código

Listagem 5-25.  Implementar INotifyPropertyChanged


using
System.ComponentModel;
using System;

public class Pessoa: INotifyPropertyChanged


{
nome da string
privada; // Declarar
o evento
evento público PropertyChangedEventHandler PropertyChanged;

Pessoa pública ()
{
}

Pessoa pública (valor da sequência)


{
this.name = value;
}

sequência pública PersonName


{
get {nome de
retorno; } definir

{
nome = valor;
// Chame OnPropertyChanged sempre que a propriedade
for atualizada OnPropertyChanged ("PersonName");
}
}

// Crie o método OnPropertyChanged para aumentar o


evento protegido void OnPropertyChanged (string name)
{
Manipulador PropertyChangedEventHandler = PropertyChanged;

173

Capítulo 5 ■ Implementando delegados e eventos


if (manipulador! = nulo)
{
manipulador (este, novo PropertyChangedEventArgs (nome));
}
}
}

Programa de aula
{
static void Main (string [] args)
{
Pessoa pessoa = nova Pessoa ();

person.PropertyChanged + = OnPropertyChanged;

person.PersonName = "Ali";
}

private static void OnPropertyChanged (objeto remetente, PropertyChangedEventArgs e)


{
Pessoa pessoa = (Pessoa) remetente;

Console.WriteLine ("A propriedade [{0}] possui um novo


valor = [{1}]", e.PropertyName, person.PersonName);
}
}
//Resultado
A propriedade [PersonName] tem um novo valor = [Ali]

Vantagens dos Eventos


\ 1. \ Evento encapsula um delegado; evita a substituição de uma
referência de método restringindo o uso do operador assignment =.
\ 2. \ Diferentemente do delegado, o evento não pode ser chamado fora
da classe, o que garante que o evento será chamado somente
quando uma determinada codificação estiver em conformidade.

Sumário
• Delegados são indicadores de função . Eles armazenam a referência
de método (s) dentro de um objeto delegado.
•O delegado pode ser chamado em qualquer lugar do código para chamar o (s) método (s).
• Delegado de ação armazena a referência de um método que não retorna um valor.
• Delegado Func armazena a referência de um método que retorna um valor.
•O delegado predicado armazena a referência de um método que usa um
parâmetro de entrada e retorna um valor bool.

174

Capítulo 5 ■ Implementando delegados e eventos


•A covariância no delegado é aplicada no tipo de retorno de um método .
•A contravariância no delegado é aplicada no parâmetro de entrada de um método .
•A expressão Lambda é usada para criar um método anônimo.
• Evento encapsula um delegado e executa os métodos referidos
quando uma determinada condição é satisfeita.

Desafios de código
Challenge1: Aplicação do Boletim do Estudante
Escreva um aplicativo que lide com um boletim de estudante. O aplicativo deve salvar as
marcas da ciência da computação, matemática e inglês. Cada sujeito tem um total de 50 marcas.
Se um aluno obtiver pelo menos 75/150 notas, um evento será acionado e mostrará uma
mensagem de felicitações ao passar no exame. Caso contrário, ele exibirá uma nota “F”.
Practice Exam Questions
Desafio 1: Invocar um evento se o nome de uma pessoa for
alterado
Crie uma classe Pessoa que possua a propriedade "Nome". Sua tarefa é chamar um evento que
verifique se o nome de uma pessoa mudou ou não e atribuir um novo valor ao nome de uma
pessoa.

Questão 1
Suponha que você esteja escrevendo uma classe que precisa de um delegado que
possa se referir a um método de dois parâmetros de string de entrada e retornar um
valor inteiro. Escolha o delegado certo entre as seguintes opções.

A)
Ação <int, string, string>
B)
Func <string, string, int>
C)
Predicado <int, string, string>
D)
EventArgs <int, string, string>

Questão 2
Você está implementando um método que cria uma instância de uma classe chamada Person. A
classe Person contém um evento público chamado Die. O seguinte segmento de código define o
evento Die:

Evento público EventHandler Die;

175

Capítulo 5 ■ Implementando delegados e eventos


Você precisa criar um manipulador de eventos para o evento Die usando uma expressão lambda.

A)
Pessoa pessoa = nova Pessoa ();
person.Die = (s, e) => {/ * Corpo do método * /};
B)
Pessoa pessoa = nova Pessoa ();
person.Die - = (s, e) => {/ * Corpo do método * /};
C)
Pessoa pessoa = nova Pessoa ();
person.Die + = (s, e) => {/ * Corpo do método * /};
D)
Pessoa pessoa = nova Pessoa ();
person.Die + = () => {/ * Corpo do método
* /};

Questão 3
Suponha que você esteja escrevendo um método que tenha um parâmetro de string de entrada
e retorne True se o valor do parâmetro de entrada de string estiver em maiúsculas. Qual dos
seguintes delegados você usará para consultar esse método?
\ A) \ Ação <bool, string>
\ B) \ Func <bool, string>
\ C) \ Predicado <sequência>
\ D) \ EventHandler

Respostas
\ 1. \ B
\ 2. \ C

\ 3. \ C

176

CAPÍTULO 6

Mergulhe profundamente no
LINQ

LINQ é um recurso do C # introduzido no .NET 3.5. Permite trabalhar com diferentes tipos de
dados e fornece uma maneira fácil e poderosa de escrever e manter consultas.
Neste capítulo, alcançaremos os seguintes objetivos:
\ 1. \ Entenda o LINQ
\ 2. \ Entenda os operadores LINQ
\ 3. \ Entenda as sintaxes do LINQ
\ 4. \ Trabalhando com consultas LINQ
\ 5. \ Trabalhando com LINQ to XML
Introdução ao LINQ
LINQ (Language Integrated Query) é uma maneira de consultar diferentes tipos de fontes de
dados que oferecem suporte a IEnumerable <T> ou IQueryable <T> . Ele oferece uma maneira
fácil e elegante de acessar ou manipular dados de um objeto de banco de dados, documento
XML e objetos na memória .

Por que usamos o LINQ


O LINQ geralmente é mais importante que outras estruturas de consulta devido à sua
maneira de trabalhar com diferentes fontes de dados. De acordo com o MSDN :

As consultas geralmente são expressas em uma linguagem de consulta


especializada. Diferentes idiomas foram desenvolvidos ao longo do tempo para os
vários tipos de fontes de dados, por exemplo, SQL para bancos de dados
relacionais e XQuery para XML. Portanto, os desenvolvedores tiveram que
aprender uma nova linguagem de consulta para cada tipo de fonte ou formato de
dados que eles devem suportar. O LINQ simplifica essa situação, oferecendo um
modelo consistente para trabalhar com dados em vários tipos de fontes e
formatos de dados. Em uma consulta LINQ, você está sempre trabalhando com
objetos. Você usa os mesmos padrões básicos de codificação para consultar e
transformar dados em documentos XML, bancos de dados SQL, conjuntos de
dados ADO.NET, coleções .NET e qualquer outro formato para o qual um
provedor LINQ esteja disponível.

© Ali Asad e Hamza Ali 2017 177


A. Asad e H. Ali, O Guia de Estudo do Programador C # (MCSD) , DOI 10.1007 / 978-1-4842-2860-9_6

Capítulo 6 ■ Mergulhe profundamente no LINQ


Tipos de LINQ
O LINQ opera com uma fonte de dados diferente e, devido ao trabalho com essas fontes de
dados, é classificado nos seguintes tipos:

LINQ to Object
O LINQ to Object fornece suporte para interação com objetos .NET na memória
implementados por uma interface IEnumerable <T> . Usaremos o LINQ para objetar a
explicação das consultas do LINQ.

LINQ para entidades


O LINQ to Entities fornece suporte para interação com um banco de dados relacional usando
um ADO.NET Entity Framework. É mais flexível que o LINQ to SQL, mas complexo. Facilita
diferentes provedores de dados, como Oracle, My SQL, MS SQL, etc.

LINQ para o conjunto de dados


O LINQ to Dataset fornece suporte para interação com um cache de dados na memória de
maneira fácil e rápida.

LINQ para SQL


O LINQ to SQL, também conhecido como DLINQ, fornece suporte para interação com um banco de dados de
relações como objetos.
LINQ para XML
O LINQ to XML, também conhecido como XLINQ, fornece suporte para interação com
documentos XML, ou seja, para carregar documentos XML e para executar consultas como
leitura, filtro, modificação, adição de nó etc. em dados XML.

LINQ paralelo
O LINQ paralelo, também conhecido como PLINQ, fornece suporte para o trabalho paralelo do LINQ.
Usaremos o LINQ to Object para elaborar o tópico "Trabalhando com consultas
LINQ" e para a elaboração explícita de "LINQ to XML" como um tópico.

Noções básicas sobre operadores LINQ


Os operadores LINQ são na verdade um conjunto de métodos de extensão. Esses
operadores formam o padrão LINQ. Esses operadores oferecem flexibilidade para
consultar dados, como filtragem de dados, classificação etc.
Os seguintes operadores de consulta LINQ discutiremos:
\ 1. \ Operador de filtragem
\ 2. \ Operador de projeção
\ 3. \ Associando Operador

178

Capítulo 6 ■ Mergulhe profundamente no LINQ


\ 4. \ Operador de agrupamento
\ 5. \ Operador de Partição
\ 6. \ Agregação

Neste tópico, entenderemos o objetivo desses operadores no LINQ.

Operador de filtragem
O Operador de filtragem é usado para filtrar uma coleção ou sequência de dados com base no
predicado ou em alguma condição específica. Discutiremos o seguinte operador de filtragem
neste capítulo:

Tabela 6-1.  Operador de filtragem


Operador Descrição Sintaxe
Onde Filtrar dados com base em predicado ou condiçãoOnde
     

Operador de projeção
O Operador de projeção é usado quando um objeto é transformado em um novo formulário
com base em alguma condição ou não. Discutiremos o seguinte operador de projeção neste
capítulo:

Tabela 6-2.  Operador de projeção


Operador Descrição Sintaxe
Selecione Selecione um resultado obtido de uma fonte de dadosSelecione
     

Operador Associado
O operador de junção é usado para juntar duas ou mais seqüências ou coleções com base
em alguma chave e produzir um resultado. Discutiremos o seguinte operador de junção
neste capítulo:

Tabela 6-3.  Operador Associado


Operador Descrição Sintaxe
Junte-se Seqüência de junção com base em uma chave correspondentejoin..in..on.equals
     

179

Capítulo 6 ■ Mergulhe profundamente no LINQ


Operador de agrupamento
O operador de agrupamento é usado para organizar elementos com base em uma
determinada chave. Discutiremos o seguinte operador de agrupamento neste capítulo:

Tabela 6-4.  Operador de agrupamento


OperadorDescrição Sintaxe
     

GroupBy Retorne uma sequência de itens em grupos como umgrupo ... por <ou> grupo ... por..into
 
IGroup <chave, elemento> <ou>
GroupBy (<predicado>)
       

■■ Observe que   GroupBy e ToLookup são operadores de agrupamento e são suportados pela consulta e pela
sintaxe do método, exceto pelo ToLookup (), que é suportado apenas na sintaxe do método. A sintaxe da consulta
é discutida no próximo tópico.

Operador de partição
Operador de partição é usado para dividir a coleção ou sequência em duas partes e retornar a
parte restante (registro) deixada pela implicação desses operadores de partição. Discutiremos o
seguinte operador de partição neste capítulo:

Tabela 6-5.  Operador de Partição


Operador Descrição Sintaxe
Pular Ignore o número de registros fornecido e retorneIgnorar <T> (<contagem>)
os restantes.
   
Toma Pegue o número fornecido de registros e pule o Tome <T> (<contagem>)
os restantes.
       

Agregação
Agregação significa aplicar funções agregadas no LINQ. Função agregada é uma função que
calcula uma consulta e retorna um único valor. Discutiremos a seguinte função agregada neste
capítulo:
Tabela 6-6.  Funções agregadas
Operador Descrição Sintaxe
Média Faça a média de uma coleção numérica. Média <T> (<param>)
Contagem Conte o número de elementos em uma coleção. Contagem <T> (<param>)
Máx. Retorne o valor mais alto da coleção de valores numéricos. Máximo <T> (<param>)
Mín. Retorne o valor mais alto da coleção de valores numéricos. Mín <T> (<param>)
Soma Calcule a soma dos valores numéricos em uma coleção. Soma <T> (<param>)
     

180

Capítulo 6 ■ Mergulhe profundamente no LINQ


O uso desses operadores é definido no tópico “Trabalhando com Consultas LINQ”.

Compreender a sintaxe do LINQ


O LINQ fornece maneiras diferentes de interagir com fontes de dados para consultá-las. Isso
facilita os desenvolvedores de SQL a interagir com diferentes fontes de dados para consulta
usando C #, fornecendo a eles a sintaxe LINQ Query e também facilita os desenvolvedores de C
# que não possuem uma sólida experiência em SQL para consultar os dados, oferecendo a
facilidade da sintaxe do método LINQ.
Essas duas maneiras ou sintaxes da consulta LINQ são:
\ 1. \ Sintaxe do método
\ 2. \ Sintaxe da consulta

Essas duas sintaxes da consulta LINQ são semanticamente idênticas, mas presume-se que
escrever uma consulta usando a sintaxe de consulta seja mais fácil e mais simples.

Sintaxe do método
O LINQ fornece a sintaxe do método para interagir com diferentes fontes de dados para
consultá-las. Basicamente, ele usa métodos de extensão para consultar dados. Também é
conhecido como Consulta de sintaxe Lambda, pois o método de extensão usa a sintaxe
lambda como predicado. É também chamado de Sintaxe Fluente ou de Extensão de Método .
Sintaxe

resultado = DataSource.Operator (<expressão lambda>);

OU

resultado = DataSource.Operator (<expressão lambda>). Operator (<optional>);

onde resultado deve ser de um tipo de dados retornados. Você também pode usar o tipo var
quando não tiver certeza sobre o tipo de dados retornado.

■■ Nota A   sintaxe do método também é chamada de sintaxe fluente, pois permite chamar uma série de
métodos de extensão.

A Listagem 6-1 mostra o exemplo da sintaxe do método.


Fragmento de código
Vamos dar um exemplo de uma coleção de frutas como:

Listagem 6-1.  Matriz de frutas


string [] fruits = nova string []
{
"Maçã", "Manga", "Morango", "Data",
"Banana", "Abacate", "Cereja", "Uva",
"Goiaba", "Melão", "Laranja", "Tomate"
};

181

Capítulo 6 ■ Mergulhe profundamente no LINQ


Agora queremos frutas cujo nome começa com "A". Portanto, fazemos uma consulta sobre
frutas (fonte de dados) para obter o resultado necessário.

Figura 6-1.  Consulta de sintaxe de método

Agora o resultado contém todas as frutas cujos nomes começam com "A". Como a consulta
retornará todas as frutas começando com "A", seria uma coleção de frutas, e as variáveis que
recebem essas coleções devem ser do mesmo tipo que o tipo da coleção, que é uma coleção de
seqüências de caracteres. Também podemos aplicar outro operador (método de extensão) na
mesma consulta para contar o número de frutas cujos nomes começaram com "A".

int fruitsLength = fruits.Where (p => p.StartsWith ("A")). Count ();

O operador where filtra a fonte de dados dos frutos com base no predicado fornecido (no
corpo de onde ) e obtém todos os frutos cujo nome começou com "A" e, além disso, Count ()
contará todos os frutos retornados por Where () e retorna o número de frutas contadas para a
variável fruitsLength .

Sintaxe da consulta
O LINQ fornece outra maneira de executar uma fontes de dados, que é sintaxe de
consulta de maneira diferente, como usar SQL para consulta. É a compreensão de consulta
banco de dados racional. Também é conhecido ou expressão de consulta
como sintaxe .
Sintaxe

<Tipo de resultado retornado> result = from <range variable> na fonte de dados


<Operadores de consulta> <expressão
lambda> <operador select ou groupBy>
<resultado>

É o mesmo que consulta no SQL, com pouca diferença. A consulta nesta sintaxe
sempre termina com uma seleção ou grupo .. pelo operador e começa com uma palavra-
chave from.
Fragmento de código
Vamos pegar o exemplo acima de frutas e executar os mesmos cenários com esse tipo de sintaxe de consulta.

182
Capítulo 6 ■ Mergulhe profundamente no LINQ
Para obter todos os frutos cujo nome começou com "A", uma consulta com esse tipo de sintaxe seria:

Figura 6-2.  Sintaxe da consulta

onde p de frutas é o mesmo que foreach (var p de frutas) . Aqui p, que é o resultado,
retornará e armazenará na variável de resultado .
Assim como fizemos na sintaxe do método para aplicar o operador (método de extensão)
ainda mais para filtrar a consulta, também podemos fazer o mesmo nesse tipo de sintaxe.
Tomando o mesmo exemplo de contagem do número de frutas cujo nome começa com "A", a
consulta seria como:

int result = (de p nos frutos em


que p.StartsWith ("A")
seleciona p) .Count ();

Trabalhando com consultas LINQ


Neste tópico, discutiremos as consultas LINQ em detalhes. Vimos até agora as maneiras de
interagir com diferentes fontes de dados. Agora, conheceremos mais sobre consultas LINQ e
executaremos importantes operadores LINQ para consultar dados.

Recursos de C # para suportar LINQ


Alguns recursos são adicionados ao C # que oferecem suporte ao LINQ. Alguns recursos são
necessários para criar uma consulta, enquanto outros são para ajudá-lo a criar uma
consulta de maneira agradável e fácil. Esses recursos são:
\ 1. \ Variáveis de Tipo Implicitamente
\ 2. \ Inicializadores de objeto
\ 3. \ Tipos anônimos
\ 4. \ Expressões Lambda
\ 5. \ Métodos de extensão
Esses são os recursos de idioma que tornam possível o LINQ. Todos os recursos são explicados nos
capítulos anteriores.

183

Capítulo 6 ■ Mergulhe profundamente no LINQ


Partes da operação de consulta
Ao trabalhar com consultas LINQ, sempre há três etapas ou ações:
\ 1. \ Obtenha a fonte de dados
\ 2. \ Crie uma consulta
\ 3. \ Execute a consulta
A Listagem 6-2 mostra essas três ações ou etapas da consulta LINQ.
Fragmento de código

Listagem 6-2.  Etapas de uma consulta


// 1- Primeira etapa (obtenção da fonte de
dados) string [] fruits = new string []
{
"Maçã", "Manga", "Morango", "Data",
"Banana", "Abacate", "Cereja", "Uva",
"Goiaba", "Melão", "Laranja", "Tomate"
};

// 2- Segundo passo (criação da


consulta) var resultado = de p nos
frutos, selecione p;

// Terceira etapa (execução da


consulta) foreach (item var no
resultado)
{
Console.WriteLine (item);
}

Explicação
Conforme mostrado no código, três partes problemáticas ou ações da consulta LINQ são
expressas. Os detalhes dessas etapas ou ações são discutidos abaixo.

Fonte de dados
No trecho de código acima, a fonte de dados é uma matriz que suporta implicitamente
IEnumerable <T> para que possa ser consultada. Para consulta, uma fonte de dados deve estar
na memória; é por isso que, se houver uma fonte de dados XML, ela deverá ser carregada na
memória. A fonte de dados pode ser diferente, ou seja, objetos na memória , objetos de banco de
dados ou dados XML.

Criação de Consulta
A consulta informa as informações para recuperar ou processar o que for necessário para
processar a partir da fonte de dados. A consulta pode ser gravada com um tipo diferente de
sintaxe oferecido pelo C #. Diferentes tipos de operadores LINQ podem ser executados na
consulta para filtrar, classificar, agrupar e modelar dados antes que eles retornem.

184

Capítulo 6 ■ Mergulhe profundamente no LINQ


Execução de Consulta
É importante saber que sempre que uma consulta é gravada, a execução da consulta não é feita.
A execução da consulta varia dependendo da sua escolha. Por padrão, a execução da consulta é
adiada até que você itere sobre a variável de consulta, mas você pode forçá-la a executar no
momento de sua criação.

Execução diferida
A execução de uma consulta quando ela é gravada é adiada por padrão e você não pode obter
o resultado de uma consulta até iterar sobre a variável de consulta ou executar métodos
agregados ( Max () , Min () e etc) ou métodos de extensão ( ToList ( ) , ToArray () e etc) para
obter o resultado. Esse conceito é chamado de execução adiada da consulta. A Listagem 6-3
mostra este conceito:

Listagem 6-3.  Execução diferida

classe Pessoa
{
ID público int {get; conjunto; }
public string Nome {get; conjunto; }
string pública Endereço {get;
conjunto; } salário decimal público
{get; conjunto; }

Lista <Person> pessoas = nova Lista <Person> ()


{
nova pessoa () {ID = 1, nome = "Ali
Asad"}, nova pessoa () {ID = 5, nome =
"Hamza Ali"},

};

var query = de p em pessoas,


selecione p;

int count = 0;
count = query.Count (); // Conta 2 registros

persons.Add (new Person () {ID = 3, Nome = "John Snow"});

count = query.Count (); // Contagem 3 registros

Console.WriteLine (consulta);

O código está apenas contando o número de registros. Depois que uma consulta é gravada,
uma operação de Count () é executada para obter algum resultado e, nesse momento, retornará
o número de registros, mas não no momento em que a consulta foi gravada. Como há adição em
uma fonte de dados após a gravação de uma consulta, ela não deveria ter sido adicionada na
variável count ; mas isso não acontece na execução adiada, pois você não obterá resultados até
que algum tipo de operação seja executada. Então, novamente, após a adição de um novo
elemento na fonte de dados, quando Count () é chamado, ele obtém o resultado mais recente.

■■ Nota   A execução adiada retorna os dados mais recentes.


185

Capítulo 6 ■ Mergulhe profundamente no LINQ


Execução imediata
Execução imediata de uma consulta é a execução no momento em que uma consulta é gravada.
Força a consulta LINQ a ser executada e retorna os resultados imediatamente. Executando
métodos / métodos agregados ou chamando ToList <T> ou ToArray <T> (métodos de extensão)
em uma consulta, você pode forçá-lo a executar imediatamente. Execução imediata retorna os
dados mais recentes (resultado). A Listagem 6-4 mostra este conceito:

Listagem 6-4.  Forçar Execução


Lista <Person> pessoas = nova Lista <Person> ()
{
nova pessoa () {ID = 1, nome = "Ali
Asad"}, nova pessoa () {ID = 5, nome =
"Hamza Ali"},

};

var query = (de p em pessoas,


selecione p). ToList
();

persons.Add (new Person () {ID = 3, Nome = "John Snow"});

foreach (item var na consulta)


{
Console.WriteLine (item.ID + "\ t" + item.Name);
}

Esse código não exibirá o ID e o nome da última pessoa adicionada (pessoa adicionada após
a consulta ser gravada), pois há uma execução imediata da consulta executando o método de
extensão ( ToList () ) nela e, nesse momento, a consulta escrita realizada no pessoas variável
continha apenas dois registros de Pessoa, de modo a consulta retornará essas duas pessoas.

■■ Nota   A execução do Operador de agrupamento GroupBy () é adiada, enquanto a


execução de outro operador de agrupamento ToLookup () é imediata.

Operadores LINQ para consultar dados


A visão geral dos operadores padrão do LINQ é discutida anteriormente. O uso detalhado
desses operadores nas consultas LINQ é expresso abaixo. Considere um exemplo de Pessoa
com seu ID, Nome, Endereço e Salário e inicialize todas as pessoas usando o Inicializador de
Objetos.

Listagem 6-5.  Inicialização do objeto Person


classe Pessoa
{
ID público int {get; conjunto; }
public string Nome {get; conjunto; }
string pública Endereço {get;
conjunto; } salário decimal público
{get; conjunto; }

186

Capítulo 6 ■ Mergulhe profundamente no LINQ


Lista <Person> pessoas = nova Lista <Person> ()
{
nova pessoa () {ID = 1, nome = "Ali Asad", endereço = "Paquistão", salário
= 10000}, nova pessoa () {ID = 5, nome = "Hamza Ali", endereço =
"Paquistão", salário = 20000}, nova pessoa () {ID = 3, nome = "John Snow",
endereço = "Canadá", salário = 15000}, nova pessoa () {ID = 2, nome =
"Lakhtey", endereço = "Paquistão ", Salário = 5000}, nova pessoa () {ID = 4,
nome =" Umar ", endereço =" Reino Unido ", salário = 25000},
nova pessoa () {ID = 6, nome = "Mubashar", endereço = "Paquistão", salário = 8000},
};

Agora veremos a implementação dos operadores LINQ neste cenário.

Operador de filtragem
Este operador é usado para filtrar dados com base em alguns critérios. A Listagem 6-6 mostra o exemplo
deste operador:
Listagem 6-6.  Operador de filtragem

IEnumerable <Person> result = from p em pessoas


onde p.Name.Length> 4
selecione p;

foreach (item var no resultado)


{
Console.WriteLine (item.ID + "\ t" + item.Name + "\ t" + item.Address);
}

Operador de projeção
O Operador de projeção é usado para projetar uma fonte ou um elemento que não seja uma
fonte com base na função de transformação. Existem basicamente dois operadores de
projeção: Select e SelectMany . A Listagem 6-7 e a Listagem 6-8 mostram o exemplo desses
dois operadores:

Selecione
Listagem 6-7.  Selecionar Operador
IEnumerable <string> result = from p em pessoas
onde p.Name.Length> 4
selecione p.Name;
foreach (nome do var no resultado)
{
Console.WriteLine (nome);
}

187

Capítulo 6 ■ Mergulhe profundamente no LINQ


SelectMany
Listagem 6-8.  Operador SelectMany
var resultado = (de p em pessoas
em que
p.Name.Length> 4
selecione new
{
PersonID = p.ID,
PersonName =
p.Name,
PersonAddress = p.Address
});

foreach (item var no resultado)


{
Console.WriteLine (item.PersonID + "\ t" + item.PersonName);
}

A consulta SelectMany inclui várias propriedades que não são definidas em nenhuma
classe e podem recuperar o resultado de uma consulta acessando essas propriedades do tipo
anônimo. Esse tipo de consulta é chamado Consulta de Tipo Anônimo .
Operador Associado
O operador de junção é usado para unir as seqüências com base nas chaves correspondentes.
Veja o exemplo de uma turma e seus alunos e o objetivo é saber qual aluno é de qual classe.
A Listagem 6-9 mostra o exemplo deste operador:

Listagem 6-9.  Operador Associado

classe Classe
{
public int ClassID {get; conjunto; }
public string ClassName {get;
conjunto; }
}

classe Aluno
{
public int StudentID {get; conjunto; }
string pública StudentName {get;
conjunto; } public int ClassID {get;
conjunto; }
}

Listar <Class> classes = new List <Class> ();


classes.Add (nova classe {ClassID = 1, ClassName = "BSCS"});
classes.Add (nova classe {ClassID = 2, ClassName = "BSSE"});
classes.Add (nova classe {ClassID = 3, ClassName = "BSIT"});

Listar <Estudante> alunos = nova Lista <Estudante> ();


students.Add (novo aluno {ClassID = 1, StudentID = 1, StudentName = "Hamza"});
students.Add (novo aluno {ID da classe = 2, ID do aluno = 2, StudentName =
"Zunaira"}); students.Add (novo Student {ClassID = 1, StudentID = 3, StudentName =
"Zeeshan"});
188

Capítulo 6 ■ Mergulhe profundamente no LINQ


var resultado = (de std em alunos
junte clas nas classes em std.ClassID é igual a clas.ClassID,
selecione novo

{
_Student =
std.StudentName, _Class =
clas.ClassName
});

foreach (item var no resultado)


{
Console.WriteLine (item._Student + "\ t" + item._Class);
}

Operador de agrupamento
O operador de agrupamento é usado para organizar uma sequência de itens em grupos
como um IGroup <chave, elemento> . Tome um cenário para organizar os alunos por
endereço. A Listagem 6-10 mostra este cenário:

Listagem 6-10.  Operador de agrupamento

var resultado = de p no grupo


de pessoas p por
p.Endereço;

foreach (aluno var no resultado)


{
Console.WriteLine ("Endereço:" +
student.Key); foreach (var st no aluno)
{
Console.WriteLine (st.ID + "\ t" + st.Name);
}
}

Operador de partição
O Operador de Partição é usado para dividir a coleção ou sequência em duas partes e retornar
o restante deixado pela implicação de um desses operadores de partição. Ele contém
operadores Take and Skip . As Listagens 6-11 e 6-12 mostram o exemplo desses dois operadores:

Toma
Listagem 6-11.  Operador Take

resultado var = (de p em pessoas


onde p.Address.StartsWith ("P")
selecione p) .Take (2);

foreach (item var no resultado)


{
Console.WriteLine (item.ID + "\ t" + item.Name);
}

189

Capítulo 6 ■ Mergulhe profundamente no LINQ


Pular
Listagem 6-12.  Pular operador

resultado var = (de p em pessoas


onde p.Address.StartsWith ("P")
selecione p) .Skip (2);

foreach (item var no resultado)


{
Console.WriteLine (item.ID + "\ t" + item.Name);
}

Agregação
A função agregada é usada para calcular uma consulta e retornar um único valor. A
listagem a seguir de algumas funções agregadas é mostrada abaixo:

Média
Listagem 6-13.  Função média
var averageSalary = (de p em pessoas,
selecione p.Salary) .Average
();

Console.WriteLine (averageSalary);

Contagem
Listagem 6-14.  Função de contagem
var noOfPersons = (de p em pessoas
onde p.Address.StartsWith ("P")
selecione p) .Count ();

Console.WriteLine (noOfPersons);

Máx.
Listagem 6-15.  Função máxima

var maximumSalary = (de p em pessoas,


selecione p.Salary) .Max
();

Console.WriteLine (maximumSalary);

190

Capítulo 6 ■ Mergulhe profundamente no LINQ


Mín.
Listagem 6-16.  Função mínima

var minimumSalary = (de p em pessoas,


selecione p.Salary) .Min
();

Console.WriteLine (minimumSalary);

LINQ para XML


Para interagir com XML em C #, a linguagem de consulta XML é usada (que é de alguma forma
complexa) para os desenvolvedores executarem operações baseadas em XML nos dados XML
(adicionar nó, excluir nó etc.) em C #, ou seja, usando XmlDocument, XmlWriter e Classes
XmlReader. O LINQ resolve esse tipo de problema e oferece suporte para interagir com dados
XML usando o LINQ. Você pode carregar o documento XML na memória, consultar e modificar o
documento de maneira fácil usando o LINQ. A principal vantagem do LINQ to XML é que você
pode usar o LINQ with XML da mesma maneira que usa o LINQ com objeto (LINQ to Object) ou
outros provedores.
O espaço de nome System.Xml.Linq fornece as classes necessárias para interagir com
dados / documento XML em C #. Algumas das classes são:
\ 1. \ XAttribute
\ 2. \ XComment
\ 3. \ XContainer
\ 4. \ XDeclaration
\ 5. \ XDocument
\ 6. \ XElement
\ 7. \ XNamespace
\ 8. \ XNode
\ 9. \ XObject
\ 10. \ XText
Algumas dessas classes serão usadas nos próximos tópicos para mostrar diferentes
operações executadas em dados XML usando o LINQ.
Criar dados XML
O LINQ to XML fornece a facilidade de criar um documento XML de maneira fácil. Você pode
usar as classes mencionadas acima para criar o documento / dados XML, ou seja, XElement
(usado para criar o elemento (Node) no documento XML) ou XAttribute (usado para criar o
atributo de um elemento específico). A Listagem 6-17 mostra como criar dados XML usando as
classes fornecidas junto com a ajuda do LINQ.

191

Capítulo 6 ■ Mergulhe profundamente no LINQ


Fragmento de código

Listagem 6-17.  Criação de dados XML

XElement rootElement = novo XElement


("RootElement"); rootElement.Add (novo XElement
("Nome", "Hamza Ali")); rootElement.Add (novo
XElement ("Idade", "21")); rootElement.Add (novo
XElement ("Endereço", "Paquistão")); rootElement.Save
("Sample.xml");
O construtor do XElement está sobrecarregado. Ele pega o nome do elemento, seu valor,
etc., e você pode adicionar subelemento (conforme adicionado no código) usando o objeto raiz
ou o elemento pai.
A saída do seguinte código seria semelhante a:

<RootElement> <Name>
Hamza Ali </Name> <Age>
21 </Age> <Address>
Paquistão </Address>

</RootElement>

■■ Nota   Você também pode adicionar nós onde quiser nos dados XML, ou seja, anexar
um nó em um local específico no documento XML.

Atualizar dados XML


Usando LINQ to XML, você pode atualizar ou excluir um nó ou valor de nó específico. A
Listagem 6-18 mostra como atualizar ou excluir um nó específico ou seu valor.
Fragmento de código

Listagem 6-18.  Atualização de dados XML

string xmlData = @ "<RootElement> <Name> Hamza


Ali </Name> <Age> 21
</Age> <Address> Paquistão
</Address>

</RootElement> ";

Documento XDocument = novo


XDocument (); document =
XDocument.Parse (xmlData);
// isso lerá o nó do nome se a idade for 21 var
readNode = (from p in document.Descendants ()
onde p.Element ("Age"). Value == "21"
selecione p.Element ("Name")).
FirstOrDefault ();
Console.WriteLine ("O nome da pessoa com 21 anos é:" + readNode.Value);

// Atualizar nome (Node) com o valor “Ali


Asad” readNode.ReplaceWith ("Ali Asad");

Console.WriteLine ("O valor do nó é


atualizado"); // Agora você pode salvar este
Xml no documento / arquivo document.Save
("Sample.xml");

192

Capítulo 6 ■ Mergulhe profundamente no LINQ


// isso excluirá o nó de endereço

document.Descendants (). Where (s => s.Value == "Pakistan"). Remove ();

document.Save ("Amostra atualizada 1.xml");

Agora você pode ler o documento XML salvo e obterá conteúdo atualizado.

Ler dados XML


Também podemos ler os dados XML inteiros ou específicos usando o LINQ. O LINQ fornece
a maneira linear de jogar com XML. Você pode ler dados XML lendo o arquivo XML ou a
sequência XML.
Por exemplo, temos XML no formato de sequência:

string xmlData = @ "<RootElement> <Name>


Hamza Ali </Name> <Age>
21 </Age> <Address>
Paquistão </Address>

</RootElement> ";

Os dados XML de um arquivo também podem ser lidos. O código a seguir mostra como ler
dados XML no formato de sequência de um arquivo:

// lê xml do arquivo
Stream xmlFromFile = File.Open ("Sample.xml",
FileMode.Open); Leitor StreamReader = novo StreamReader
(xmlFromFile); string xmlData = reader.ReadToEnd ();

A Listagem 6-19 mostra como ler todos os dados XML usando o LINQ.
Fragmento de código

Listagem 6-19.  Ler dados XML

string xmlData = @ "<RootElement> <Name>


Hamza Ali </Name> <Age>
21 </Age> <Address>
Paquistão </Address>

</RootElement> ";

Documento XDocument = novo


XDocument (); document =
XDocument.Parse (xmlData); var xml =
(de p no document.Elements ()
selecione p) .ToList ();
foreach (item var em xml)
{
Console.WriteLine (item.ToString ());
}
Os dados XML no formato de sequência precisam ser analisados no documento XML para
que o LINQ possa ser aplicado para executar mais operações do LINQ to XML. Quando os dados
XML formatados em cadeia são analisados, você pode usar seus métodos ou propriedades. O
método Elements () obtém todos os elementos de um documento XML (obtido pela análise de
dados XML formatados em cadeia).
193

Capítulo 6 ■ Mergulhe profundamente no LINQ


Também podemos pesquisar nos dados XML para encontrar algum elemento ou valor ou
atributo específico de elemento, dependendo de nossos cenários.
A Listagem 6-20 mostra como ler algum elemento específico (um Nó) ou o valor do elemento.
Fragmento de código

Listagem 6-20.  Ler nó específico


// isso lerá o nó do nome
var readNode = (de p no document.Descendants ()
selecione p.Element ("Name")).
FirstOrDefault ();
Console.WriteLine (readNode);

// esta consulta lerá o valor de Name (Node)


var readNodeValue = (de p no document.Descendants ()
selecione p.Element ("Name"). Value)
.FirstOrDefault ();
Console.WriteLine (readNodeValue);

Você também pode ler o XML com base em alguns critérios, ou seja,

// isso lerá o nó do nome se a idade for 21 var


readNode = (from p in document.Descendants ()
onde p.Element ("Age"). Value == "21"
selecione p.Element ("Name")).
FirstOrDefault ();

Console.WriteLine (readNode);

■■ Nota   No XML, há uma diferença entre Elemento e Nó. Um nó pode ser um nó de elemento, um nó
de atributo, um nó de texto etc., enquanto um elemento é tudo, incluindo seu início e fim.

Sumário
\ 1. \ LINQ é um recurso do C # que permite trabalhar com
diferentes tipos de dados e fornece uma maneira fácil e
poderosa de escrever e manter consultas.
Operadores \ 2. \ LINQ operam em seqüências e oferecem flexibilidade
para consultar dados, como filtragem de dados, classificação etc.
\ 3. \ Funções agregadas são as funções que calculam uma consulta e
retornam um único valor.

\ 4. \ LINQ possui duas sintaxes básicas: sintaxe de consulta e sintaxe de método.


A consulta \ 5. \ LINQ consiste em três ações ou etapas principais:
Obtenção da fonte de dados, criação da consulta e execução da
consulta.
\ 6. \ Existem dois tipos de execução da consulta LINQ: Adiada e Imediata.
\ 7. \ LINQ to XML fornece a facilidade de interagir com dados XML usando a consulta LINQ.
194

Capítulo 6 ■ Mergulhe profundamente no LINQ

Desafios de código
Desafio 1: Executar operação CRUD usando LINQ to Object
Escreva um aplicativo de console e crie CRUD (Create, Read, Update e Delete) junto com a
função Search. Tome os países como uma fonte de dados (com suas propriedades) e execute
consultas LINQ sobre isso.

Practice Exam Questions


Questão 1
Você tem o seguinte código:

int [] Marcas = new int [] {59, 24, 40, 100, 35, 75, 90};

Você precisa obter todas as marcas maiores que 60. Qual snippet de código você deve usar?

A) var query = Marks.Take (60);


B) var query = Marks.Where (s => s> 60);
C) var query = Marks.Any (s => s> 60);
D) var query = de p em Marcas
onde p> 60
selecione p;

Questão 2
Para executar uma consulta, uma fonte de dados deve ser implementada por:
\ A) \ Enumerável ou Queryable
\ B) \ Enumerável e Queryable
\ C) \ IEnumerable ou IQueryable
\ D) \ IEnumerable e IQueryable

Questão 3
Você desenvolveu um aplicativo que exibe a lista de alunos. Você precisa exibir 10 alunos por
vez e assim por diante. Qual trecho de código você usaria para essa finalidade?

A) página estática pública IEnumerable <int> (fonte IEnumerable <int>, página int, int pageSize)
{
return source.Skip ((page - 1) * pageSize) .Take (pageSize);
}

B) página estática pública IEnumerable <int> (fonte IEnumerable <int>, página int, int pageSize)
{
retornar source.Skip ((página - 1) * página) .Take (pageSize);
}

195

Capítulo 6 ■ Mergulhe profundamente no LINQ


C) página estática pública IEnumerable <int> (fonte IEnumerable <int>, página int, int pageSize)
{
retorne source.Take ((page - 1) * page) .Skip (pageSize);
}

D) página estática pública IEnumerable <int> (fonte IEnumerable <int>, página int, int pageSize)
{
retornar source.Take ((página - 1) * pageSize) .Skip (pageSize);
}

Respostas
\ 1. \ B & D
\ 2. \ C
\ 3. \ UMA

196

CAPÍTULO 7
Gerenciar o ciclo de vida do
objeto
No .NET, o "ciclo de vida" de um objeto é o período de tempo entre sua criação e sua
destruição. Neste capítulo, aprenderemos:
\ 1. \ Fundamentos do ciclo de vida do objeto
\ 2. \ Fundamentos da coleta de lixo do .NET
\ 3. \ Gerenciamento de recursos não gerenciados
\ 4. \ Gerenciamento de vazamentos de memória

Fundamentos do ciclo de vida do objeto


O ciclo de vida de um objeto é simplesmente o tempo entre quando um objeto é criado na
memória e quando é destruído a partir dele. Fundamentalmente, o ciclo de vida de um
objeto envolve as duas etapas a seguir:
\ 1. \ Criação de um Objeto
\ 2. \ Exclusão de um objeto

Criação de um Objeto
Usamos uma nova palavra-chave para instanciar um novo objeto.

Pessoa 0bj = nova pessoa ();

Um bloco de memória está alocado. Esse bloco de memória é grande o suficiente para
armazenar o objeto (o CLR lida com a alocação de memória para objetos gerenciados). O bloco
de memória é convertido em um objeto inicializado na memória (podemos controlar esta etapa
implementando um construtor ).

Exclusão de um objeto
Usamos a destruição para recuperar quaisquer recursos usados por esse objeto. O objeto é
limpo, por exemplo, liberando quaisquer recursos não gerenciados usados pelo aplicativo,
como identificadores de arquivo e conexões com o banco de dados (podemos controlar esta
etapa implementando um destruidor ). A memória usada pelo objeto é recuperada.
Com a Coleta de Lixo , o CLR lida com a liberação da memória usada pelos objetos
gerenciados; no entanto, se usarmos objetos não gerenciados, talvez seja necessário liberar
manualmente a memória implementando IDisposable .

© Ali Asad e Hamza Ali 2017 197


A. Asad e H. Ali, Guia de Estudo do Programador C # (MCSD) , DOI 10.1007 / 978-1-4842-2860-9_7

Capítulo 7 ■ Gerenciar o ciclo de vida do objeto

Fundamentos da coleta de lixo do .NET


Em uma estrutura .NET, a coleta de lixo ( GC ) é um serviço de gerenciamento automático de
memória que cuida da limpeza de recursos para todos os objetos gerenciados no heap
gerenciado. Tem os seguintes benefícios:
\ 1. \ Permite que os desenvolvedores gravem aplicativos sem se
preocupar em liberar memória manualmente.
\ 2. \ Aloca memória em um heap gerenciado.
\ 3. \ Ativa a segurança da memória.
\ 4. \ Recupera objetos não utilizados da memória.

Quando a coleta de lixo é executada


A coleta de lixo é um processo muito caro; não é executado o tempo todo, é executado
quando qualquer uma das seguintes condições for verdadeira:
\ 1. \ Quando o sistema fica sem memória física.
\ 2. \ Quando o método GC.Collect é chamado manualmente.
\ 3. \ Quando objetos alocados na memória precisam de mais espaço.

Coletor de lixo e pilha gerenciada


Quando o coletor de lixo é inicializado pelo CLR, ele armazena e gerencia objetos
alocando um segmento de memória chamado heap gerenciado.
Cada processo gerenciado no .NET possui um heap gerenciado. Cada encadeamento em um
processo compartilha o mesmo heap gerenciado para armazenar e gerenciar objetos.
O coletor de lixo chama um método Win32 VirtualAlloc para reservar um segmento de
memória na pilha gerenciada. Quando o coletor de lixo precisa liberar um segmento de
memória, ele chama um método VirtualFree do win32 .
Quando o coletor de lixo é executado, ele remove objetos mortos e recupera sua memória;
que compacta os objetos vivos em conjunto para preservar a sua localidade e torna o heap
gerenciado menor.
O volume de objetos de memória alocados e a quantidade de objetos de memória
sobrevivente em um heap de memória gerenciada determina quantas vezes e por quanto
tempo um coletor de lixo será executado.
O trabalho do coletor de lixo depende de quantos objetos estão alocados em um heap
gerenciado. Por exemplo, se menos objetos forem alocados em um heap gerenciado, menos
coletor de lixo de trabalho deverá ser executado e vice-versa. É aconselhável não alocar objetos
gerenciados em um heap gerenciado mais do que o necessário. Por exemplo, não aloque uma
matriz de 10 bytes quando você precisou apenas de uma matriz de 5 bytes. A pilha é de dois
tipos: pilha de objetos grandes e pilha de objetos pequenos . Um heap de objeto grande
geralmente contém objetos cujo tamanho é 85.000 bytes e maior; esses tipos de objetos são
geralmente matrizes.

Gerações
O GC apóia o conceito de gerações. Ajuda a organizar objetos de vida curta e longa em um heap
gerenciado. Existem três gerações:
\ 1. \ Geração 0
\ 2. \ Geração 1
\ 3. \ Geração 2

198

Capítulo 7 ■ Gerenciar o ciclo de vida do objeto


Geração 0
Quando um objeto é alocado no heap, ele pertence à geração 0. É a geração jovem, que
contém objetos de vida curta, como variáveis temporárias. Se os objetos recém-alocados
tiverem um tamanho maior, eles irão para o heap de objetos grandes em uma coleção da
geração 2. O GC ocorre principalmente na geração 0.

Geração 1
Quando os objetos sobreviver a partir de uma coleta de lixo da geração 0, eles vão para
a geração 1. Objetos na geração 1 servir como um amortecedor entre vida curta e
longa duração objetos.

Geração 2
Quando os objetos sobrevivem de uma coleta de lixo da geração 1, eles vão para a geração 2. Os
objetos da geração 2 servem como objetos de vida longa . Se os objetos ainda sobreviverem na
geração 2, eles permanecerão na geração 2 até estarem vivos.

Etapas envolvidas na coleta de lixo


\ 1. \ Suspenda todos os threads gerenciados, exceto o segmento que
acionou a coleta de lixo.
\ 2. \ Encontre uma lista de todos os objetos ativos.
\ 3. \ Remova os objetos mortos e recupere sua memória.
\ 4. \ Compactar os objetos sobreviventes e promovê-los para uma geração mais antiga.

Gerenciar recursos não gerenciados


Em uma estrutura .NET, o coletor de lixo controla automaticamente o ciclo de vida de um
recurso gerenciado. Mas não pode lidar automaticamente com o ciclo de vida de um recurso
não gerenciado; devemos liberar explicitamente recursos de recursos não gerenciados para
manipulá-los manualmente. Alguns recursos comuns não gerenciados são: abrir um arquivo,
conexão com o banco de dados ou conexão de rede, etc.

Implementar IDisposable para liberar recursos não gerenciados


Os tipos que usam recursos não gerenciados devem implementar IDisposable para recuperar a
memória não gerenciada. O método Dispose é usado para liberar o recurso não gerenciado da
memória. Para impedir que o coletor de lixo chame o finalizador de um objeto (Destructor) , o
método de descarte usa o método GC.SuppressFinalize .

Listagem 7-1.  Definição de IDisposable


// Fornece um mecanismo para liberar recursos não
gerenciados. interface pública IDisposable
{

Dispose ();
}

199

Capítulo 7 ■ Gerenciar o ciclo de vida do objeto


O método Dispose pode ser chamado de duas maneiras:
\ 1. \ tenta / finalmente bloqueia
\ 2. \ using instrução

Chamar Dispose Dentro de tentar / finalmente Bloquear


Para descartar um recurso não gerenciado, o método de descarte pode ser chamado dentro de um bloco try /
finalmente.

Listagem 7-2.  Implementar IDisposable

using System;
using System.IO;
classe myClass: IDisposable
{
leitor público
StreamReader; public void
Dispose ()
{
// Limpar recursos não gerenciados

if (leitor! = nulo)
reader.Dispose ();

GC.SuppressFinalize (this);
}
}

Programa de aula
{
static void Main (string [] args)
{
myClass obj = null;
tentar

{
obj = new myClass ();

}
finalmente
{
// chama o método de
disposição
obj.Dispose ();

}
}
}

• StreamReader é um tipo que contém um recurso não gerenciado.


• GC.SuppressFinalize (this) impede a execução de um finalizador.

200

Capítulo 7 ■ Gerenciar o ciclo de vida do objeto


Instrução Dispose Inside Using de Dispose
Quando um tipo implementa uma interface IDisposable, seu método de descarte deve
chamar qualquer parte do código para recuperar a memória de um recurso não gerenciado.
C # introduzido using statement, que só pode ser usado com tipos que implementam uma
interface IDisposable; ele chama automaticamente o método Dispose após o término da
instrução using (quando o controle sai do bloco usando {}).
Sintaxe

using (tipo variableName = novo tipo ())


{
//FAÇAM:
}

Fragmento de código

Listagem 7-3.  Implementar IDisposable

using System;
using System.IO;
classe myClass: IDisposable
{
leitor público
StreamReader; public void
Dispose ()
{
// Limpa recursos não
gerenciados if (reader! = Null)
reader.Dispose ();

GC.SuppressFinalize (this);

Console.WriteLine ("Disposed");
}
}

Programa de aula
{
static void Main (string [] args)
{
using (myClass obj = new myClass ())
{

}
Console.WriteLine ("End");
}
}
// Fim
descartado
da saída

201

Capítulo 7 ■ Gerenciar o ciclo de vida do objeto


Padrão descartável
O padrão descartável é uma maneira padrão de implementar a interface IDisposable. Por
exemplo, consulte o seguinte trecho de código:
Fragmento de código

Listagem 7-4.  Usar padrão descartável

using System;
using System.IO;

classe myClass: IDisposable


{
// Sinalizador: Verifique se o método de descarte já foi
chamado? bool disposto = false;
// type usa recurso não gerenciado
Leitor StreamReader;

// Implementação pública do padrão Dispose exigível pelos


consumidores. public void Dispose ()
{
Descarte (verdadeiro);
GC.SuppressFinalize (this);
}

// Implementação protegida do padrão Dispose.


vazio virtual protegido Dispose (eliminação de bool)
{
se
(descartado)
retornar;

se (descartando)
{
if (leitor! = nulo)
reader.Dispose ();
// Libere qualquer outro objeto gerenciado aqui.

// Libere qualquer objeto não gerenciado aqui.

disposto = verdadeiro;
}

// Finalizador aka Destructor


~ myClass ()
{
Descarte (falso);
}

202

Capítulo 7 ■ Gerenciar o ciclo de vida do objeto


Explicação
O trecho de código acima (Listagem 7-4 ) é o padrão geral para implementar o padrão de
descarte. O valor booleano disposto determina se o método de descarte foi chamado. O
método Dispose sem parâmetros é usado para liberar recursos não gerenciados e para indicar
que existe um finalizador que ele não precisa ser executado. O Dispose (bool) indica se o
método foi chamado de um método Dispose sem parâmetros ou se foi chamado de um
finalizador (destruidor).

Perdas de memória
Se um aplicativo não liberar o recurso alocado na memória após a conclusão do uso, ele
criará um vazamento de memória porque a mesma memória alocada não está mais sendo
usada pelo aplicativo.
Se vazamentos de memória não forem gerenciados corretamente, o sistema acabará
ficando sem memória; consequentemente, o sistema começa a fornecer um tempo de resposta
lento e o usuário não pode fechar o aplicativo. O único truque é reiniciar o computador, ponto
final.

Gerenciar vazamentos de memória


Vazamentos de memória devem ser gerenciados. A seguir, algumas causas comuns de vazamento de
memória:
\ 1. \ Mantendo referências a objetos gerenciados por um longo tempo.
\ 2. \ Não foi possível gerenciar o recurso não gerenciado.
\ 3. \ Referência estática.
\ 4. \ Evento com a falta de inscrição.

Mantendo referências a objetos gerenciados por muito tempo


Se as referências de um objeto gerenciado permanecerem mais longas do que o necessário,
os contadores de desempenho poderão mostrar um aumento constante no consumo de
memória e poderá ocorrer uma OutOfMemoryException. Isso pode ocorrer devido a um
escopo global variável, porque o GC não pode destruir uma variável ativa, mesmo que ela
não esteja mais sendo usada por um aplicativo.
O desenvolvedor precisa lidar com isso informando quanto tempo uma variável pode
manter uma referência e destruí-la depois que ela não é mais necessária.

Não foi possível gerenciar o recurso não gerenciado


O coletor de lixo não pode liberar a memória do recurso não gerenciado. O desenvolvedor
precisa liberar explicitamente recursos de recursos não gerenciados. Para fazer isso, o
desenvolvedor precisa implementar uma interface IDisposable em tipos que usam recursos
não gerenciados. Caso contrário, ocorrerão vazamentos de memória.

Referência estática
Se um objeto for referenciado por um campo estático, ele nunca será liberado. Tais objetos se
tornam duradouros. O desenvolvedor precisa garantir que objetos de campo estático
desnecessários sejam destruídos ao terminar de serem usados pelo aplicativo.

203

Capítulo 7 ■ Gerenciar o ciclo de vida do objeto


Evento com cancelamento de inscrição ausente
Se um manipulador de eventos estiver inscrito (+ =), o publicador do evento manterá uma
referência ao assinante por meio do delegado do manipulador de eventos (assumindo que o
delegado seja um método de instância). Se o editor viver mais que o assinante, ele manterá o
assinante ativo, mesmo quando não houver outras referências ao assinante.
Essa é a causa do vazamento de memória quando a desinscrição de um evento não está definida.
Se o desenvolvedor cancelar a inscrição (- =) do evento com um manipulador igual, ele
removerá o manipulador e gerenciará vazamentos de memória.

Sumário
•O ciclo de vida de um objeto é simplesmente um período entre o
momento em que um objeto é criado na memória e quando é destruído.
• A coleta de lixo é um serviço de gerenciamento automático de memória
que cuida da limpeza de recursos para todos os objetos gerenciados no
heap gerenciado.
•O heap gerenciado organiza objetos em gerações.
• Objetos temporários e alocados recentemente são movidos para a geração 0.
•A geração 2 é um local onde objetos de vida longa são compactados.
• O método Dispose em IDisposable ajuda a liberar memória de um recurso não gerenciado.

Desafios de código
Desafio 1: imprimir código HTML do google.com
Escreva um aplicativo que obtenha o código html de www.google.com e imprima o código html
na tela do console. É necessário controlar a vida útil dos recursos não gerenciados e garantir
que eles sejam descartados corretamente usando o Padrão descartável.

Practice Exam Questions


Questão 1
Um aplicativo inclui um objeto que executa um processo de longa execução . Você precisa
garantir que o coletor de lixo não libere os recursos do objeto até que o processo seja concluído.
Qual método de coletor de lixo você deve usar?
\ A) \ WaitForFullGCComplete ()
\ B) \ WaitForFullGCApproach ()
\ C) \ KeepAlive () // ans
\ D) \ WaitForPendingFinalizers ()

204

Capítulo 7 ■ Gerenciar o ciclo de vida do objeto


Questão 2
Suponha que você esteja escrevendo um aplicativo que use recursos não gerenciados. Você
implementou uma interface IDisposable para gerenciar a memória de recursos não
gerenciados. Ao implementar o método Dispose, qual método você deve usar para impedir que
o coletor de lixo chame o finalizador do objeto?
\ A) \ GC.SuppressFinalize (this) // ans
\ B) \ GC.SuppressFinalize (true)
\ C) \ GC.WaitForFullGCApproach ()
\ D) \ GC.WaitForPendingFinalizers ()

Questão 3
Você está instanciando um recurso não gerenciado; qual das seguintes afirmações você usaria
para instanciar um recurso não gerenciado para que seu método Dispose sempre chame
automaticamente?
\ A) \ if-else {}
\ B) \ tentar / capturar
\ C) \ usando ()
\ D) \ switch ()

Respostas
\ 1. \ C
\ 2. \ UMA

\ 3. \ C
205

CAPÍTULO 8

Programação multithread,
assíncrona e paralela

Neste capítulo, aprenderemos como aumentar o desempenho de operações complicadas e


demoradas de um aplicativo:
\ 1. \ Trabalhando com Threads
\ 2. \ Trabalhando com tarefas
\ 3. \ Tornando a interface do usuário responsiva (assíncrona e aguardando)
\ 4. \ Usando a programação paralela

Trabalhando com Threads


Um encadeamento controla o fluxo de um programa executável. Por padrão, um programa
tem um thread chamado Main Thread . O thread principal inicia quando o controle entra no
método Main e termina quando o método Main retorna.

Se a execução de um programa é controlada por mais de um thread, é chamado de


Aplicativo Multithread . Esse programa aumenta o desempenho e o tempo de resposta de um
aplicativo. Em C #, o espaço para nome System.Threading é usado para criar e gerenciar
threads em um aplicativo multithread. Um thread pode ser criado usando a classe
System.Threading.Thread . Um thread pode ser manipulado apenas em um método. Por
exemplo, MainThread precisa de um método Main para controlar o fluxo de um programa.
Em um programa C #, um encadeamento pode ser encontrado em qualquer um dos seguintes
estados:

Tabela 8-1.  Estados de um Thread


Estado Explicação
Não iniciado O segmento foi criado, mas ainda não iniciado
Corrida O thread está executando um programa
WaitSleepJoinO encadeamento está bloqueado devido ao método Wait, Sleep ou Join
Suspenso O segmento está suspenso
Parado O encadeamento está parado, normalmente ou abortado
   

© Ali Asad e Hamza Ali 2017 207


A. Asad e H. Ali, Guia de Estudo do Programador C # (MCSD) , DOI 10.1007 / 978-1-4842-2860-9_8
Capítulo 8 ■ Programação multithread, assíncrona e paralela
A classe System.Threading.Thread contém os seguintes métodos e propriedades
comuns, que são úteis para gerenciar um encadeamento.

Tabela 8-2.  Métodos comuns e propriedades da classe Thread


Métodos e propriedadesExplicação
Começar() Altera o estado do encadeamento para Running
Junte-se() Aguarde o término de um encadeamento antes de executar o encadeamento de cham
Dormir() Suspender um encadeamento por um número especificado de milissegundos
Currículo() Retomar a execução do encadeamento suspenso
Abortar() Encerra a execução de um encadeamento
CurrentThread Retorna uma referência do encadeamento atual
Está vivo Retorna true se o encadeamento não foi encerrado ou abortado
IsBackground Obtenha ou defina para indicar que um thread é ou não é um thread em segundo pla
Nome Obter ou definir o nome de um thread
ThreadState Retorna o estado atual do encadeamento
   

Criar e iniciar um thread


Dentro do MainThread , um thread pode ser inicializado usando a classe Thread do
espaço para nome System.Threading. Um encadeamento pode iniciar sua execução
quando um método Thread.Start () é chamado.
Sintaxe
Thread variableName = new Thread (novo ThreadStart (voidMethod));

• ThreadStart é um delegado; representa o método que é executado em um thread.

OU

Thread variableName = new Thread (voidMethod);

• Também podemos fazer referência a “voidMethod” para encadear sem


usar explicitamente “ThreadStart delegate”.

Fragmento de código

Listagem 8-1.  Crie e inicie um encadeamento


Programa de aula
{

estático void MyThreadMethod ()


{
Console.WriteLine ("Olá do meu segmento
personalizado"); for (int i = 0; i <10; i ++)
{
Console.Write ("{0}", i);
}

208

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Console.WriteLine ();
Console.WriteLine ("Tchau do meu segmento personalizado");
}

static void Main (string [] args)


{
// Instancia um thread
Thread myThread = new Thread (novo ThreadStart (MyThreadMethod));

// Inicia a execução do thread


myThread.Start ();

// Faz parte do método principal


Console.WriteLine ("Hello From Main
Thread");

}
}
//Resultado
Olá do segmento principal
Olá do meu segmento
personalizado 1 2 3 4 5 6 7 8
9
Tchau do meu tópico personalizado

Explicação
(Na Listagem 8-1 ) O Thread Principal inicializa “mythread” e imprime “Hello From Main
Thread”. Enquanto "mythread" estava sendo inicializado, "myThread.Start ()" altera seu estado
para execução e depois executa "MyThreadMethod ()". “Hello From Main Thread” fazia parte
do MainThread e foi exibido na tela primeiro, porque “myThread” estava demorando para
mudar seu estado para execução.

Figura 8-1.  Fluxo de trabalho de MainThread e myThread


209

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Thread.Join ()
O método Thread.Join () é usado para manter o segmento de chamada em espera até que o
segmento chamado não seja parado ou sua execução seja encerrada.
Thread.Join () altera o estado do segmento de chamada para ThreadState.WaitSleepJoin .
Além disso, o Thread. Join () não pode ser chamado em um segmento que não esteja no
estado ThreadState.Unstarted .
Fragmento de código

Listagem 8-2.  Use Thread.Join () para manter a execução do Main Thread

estático void MyThreadMethod ()


{
Console.WriteLine ("Olá do meu segmento
personalizado"); for (int i = 0; i <10; i ++)
{
Console.Write ("{0}", i);
}
Console.WriteLine ();
Console.WriteLine ("Tchau do meu segmento personalizado");
}

static void Main (string [] args)


{
// Instancia um thread
Thread myThread = new Thread (novo ThreadStart (MyThreadMethod));

// Inicia a execução do thread


myThread.Start ();

// Aguarde até o final do mythread


myThread.Join ();

// Todo o resto faz parte do Thread Principal.


Console.WriteLine ("Hello from Main
Thread");

} //Resultado
Olá do meu segmento
personalizado 1 2 3 4 5 6 7 8
9
Adeus do meu tópico
personalizado Olá do
tópico principal

Explicação
(Na Lista 8-2 ) Desta vez, devido a “ mythread. Join () ”, MainThread (thread de chamada)
imprimirá“ Hello From Main Thread ”por último, porque o “ mythread. O método join ()
”força MainThread (thread de chamada) a aguardar até que o mythread não seja finalizado.

210.

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Figura 8-2.  O método Join permite que MainThread aguarde até que myThread termine

Primeiro plano e segmento de plano de fundo


Existem dois tipos de threads em C #, ou seja, thread de primeiro plano e thread de segundo
plano . Por padrão, em C # todos os threads são inicializados como thread em primeiro
plano . Um aplicativo não pode finalizar sua execução até que todos os seus threads em
primeiro plano sejam concluídos.
Um encadeamento em segundo plano é quase idêntico a um encadeamento em
primeiro plano. A única diferença é que, se o thread principal tiver concluído sua execução e o
thread de segundo plano for o único thread restante no aplicativo, o thread principal
encerrará o aplicativo e não esperará que o thread de segundo plano seja concluído .

Fragmento de código

Listagem 8-3.  Criar e iniciar um encadeamento em primeiro plano

estático void MyThreadMethod ()


{
Console.WriteLine ("Olá do meu segmento
personalizado"); for (int i = 0; i <10; i ++)
{
Console.Write ("{0}", i);
}
Console.WriteLine ();
Console.WriteLine ("Tchau do meu segmento personalizado");
}

211

Capítulo 8 ■ Programação multithread, assíncrona e paralela


static void Main (string [] args)
{
// Instancia um thread
Thread myThread = new Thread (novo ThreadStart (MyThreadMethod));

// por padrão, o valor Isbackgrount é sempre


falso myThread.IsBackground = false;

// Inicia a execução do thread


myThread.Start ();

// Todo o resto faz parte do Thread Principal.


Console.WriteLine ("Hello from Main
Thread");

//Resultado
Olá do segmento principal
Olá do meu segmento
personalizado 1 2 3 4 5 6 7 8
9
Tchau do meu tópico personalizado

Explicação
(Na Listagem 8-3 ) Por padrão, o valor de “Thread.IsBackground = false; ”, O que o torna um
segmento em primeiro plano. Mesmo que o MainThread não tenha outro comando após
imprimir “Hello From Main Thread”, ele não será encerrado até que o thread de primeiro
plano “ mythread ” seja concluído ou finalizado.
Figura 8-3.  O thread principal não pode terminar até que o thread de primeiro plano termine

212

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Fragmento de código

Listagem 8-4.  Crie e inicie um encadeamento em segundo plano


estático void MyThreadMethod ()
{
Console.WriteLine ("Olá do meu segmento
personalizado"); for (int i = 0; i <10; i ++)
{
Console.Write ("{0}", i);
}
Console.WriteLine ();
Console.WriteLine ("Tchau do meu segmento personalizado");
}

static void Main (string [] args)


{
// Instancia um thread
Thread myThread = new Thread (novo ThreadStart (MyThreadMethod));

// agora o thread se torna um thread em


segundo plano myThread.IsBackground =
true ;

// Inicia a execução do thread


myThread.Start ();

// Todo o resto faz parte do Thread Principal.


Console.WriteLine ("Hello from Main
Thread");

}
//Resultado
Hello From Main Thread

Explicação
(Na Listagem 8-4 ) “Mythread” agora é um encadeamento em segundo plano porque seu valor
da propriedade Background está definido como true , o que significa que MainThread
termina logo depois que (Mythread) executa seu último comando para imprimir “Hello
From Main Thread” e ganhou ' Não espere até que o " livro de mitos " seja concluído ou
encerrado.

213
Capítulo 8 ■ Programação multithread, assíncrona e paralela

Figura 8-4.  O Thread principal não espera pelos threads em segundo plano

Passar um método Parameterize para um thread


E se quisermos que um método parametrizado seja executado em um thread separado? Para
fazer isso, precisamos de um delegado "ParameterizedThreadStart" dentro do construtor de
um Thread. Ele contém uma referência do método que aceita um objeto como entrada.
Fragmento de código

Listagem 8-5.  Passar um argumento para um método de encadeamento


estático void MyThreadMethod (número do objeto)
{
int count = número (int); Console.WriteLine ("Olá
do meu segmento personalizado"); for (int i = 0; i
<contagem; i ++)

{
Console.Write ("{0}", i);
}
Console.WriteLine ();
Console.WriteLine ("Tchau do meu segmento personalizado");
}

static void Main (string [] args)


{
// Instancia um thread Thread
myThread = new Thread (
novo ParameterizedThreadStart (MyThreadMethod));

// Inicia a execução do thread


myThread.Start (5);

214

Capítulo 8 ■ Programação multithread, assíncrona e paralela


// Todo o resto faz parte do Thread Principal.
Console.WriteLine ("Hello from Main
Thread");

}
//Resultado
Olá do segmento principal
Olá do meu segmento
personalizado 1 2 3 4

Tchau do meu tópico personalizado

Explicação
"ParameterizedThreadStart" é um delegado; ele mantém uma referência de um método
dentro do construtor do Thread que aceita um objeto como entrada.
" Mythread.Start (5) " , inicia a execução do mythread e também passa o valor " 5 "
como uma entrada de objeto para " MyThreadMethod ".

Thread.Sleep (milissegundos)
É usado para suspender a execução de um encadeamento atual por um número especificado de
milissegundos.
Fragmento de código

Listagem 8-6.  Bloquear a execução de um encadeamento por um período especificado


estático void MyThreadMethod ()
{
Console.WriteLine ("Início do
MyThread"); for (int i = 0; i <5; i ++)
{
// suspende o thread por 100 milissegundos
Thread.Sleep (100 ); Console.Write ("{0}",
i);

}
Console.WriteLine ();
Console.WriteLine ("Fim de
MyThread");
}

static void Main (string [] args)


{
Console.WriteLine ("Início do thread principal");

// Instancia um thread
Thread myThread = new Thread (novo ThreadStart (MyThreadMethod));

// Inicia a execução do thread


myThread.Start ();

// Thread principal aguarde até que mythread


termine myThread.Join ();

215

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Console.WriteLine ("Método
Principal"); for (int i = 0; i <5; i ++)
{
// Suspende o encadeamento por 100
milissegundos Thread.Sleep (100 );
Console.Write ("{0}", i);

}
Console.WriteLine (); Console.WriteLine
("Fim do thread principal");
}
//Resultado
Início do Tópico
Principal Início do
MyThread
1234
Fim do método
principal do
MyThread
1234
Fim da linha principal

Linha prioritária
A prioridade da thread define quanto tempo de CPU um thread terá para execução. Quando
um encadeamento é criado, inicialmente ele é atribuído com prioridade Normal. Um
encadeamento pode ser atribuído com qualquer uma das seguintes prioridades:

Tabela 8-3.  Enums de prioridade de segmento


Prioridade Explicação
   

Alto O encadeamento será agendado antes dos encadeamentos com qualquer prioridade
Acima do normal O encadeamento será agendado antes dos encadeamentos com prioridade normal
Normal Será agendado antes dos Threads com prioridade BelowNormal
Abaixo do normalO encadeamento será agendado antes dos Encadeamentos com prioridade mais baixa
Mais baixo Agendará após Threads com prioridade BelowNormal
   

Fragmento de código

Listagem 8-7.  Priorizar um encadeamento

parada bool estática = false;

estático vazio Main ()


{
Thread thread1 = new Thread (novo ThreadStart
(myMethod)); thread1.Name = "Tópico 1";
thread1.Priority = ThreadPriority.Lowest ;

216

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Thread thread2 = new Thread (novo ThreadStart
(myMethod)); thread2.Name = "Tópico 2";
thread2.Priority = ThreadPriority.Highest ;

Thread thread3 = new Thread (novo ThreadStart


(myMethod)); thread3.Name = "Tópico 3";
thread3.Priority = ThreadPriority.BelowNormal ;

thread1.Start ();
thread2.Start ();
thread3.Start ();

Thread.Sleep
(10000); stop = true;

}
estático privado myMethod ()
{

// Obter nome do segmento atual


string threadName = Thread.CurrentThread.Name.ToString
(); // Obter prioridade do segmento atual
string threadPriority = Thread.CurrentThread.Priority.ToString ();

contagem de uint = 0;

while (parar! = verdadeiro)


{
count ++;
}

Console.WriteLine ("{0, -11} com prioridade {1,11}" +


"tem uma contagem = {2,13}",
Thread.CurrentThread.Name,
Thread.CurrentThread.Priority.ToString (), count) ;

}
//Resultado
O Thread 3 com Prioridade BelowNormal possui uma
contagem = 3990463114 O Thread 2 com Prioridade
AboveNormal possui uma contagem = 4151716090 O Thread 1
com Prioridade Normal possui uma contagem = 4139585342

Explicação
O exemplo acima (Listagem 8-7 ) mostra que o tempo de CPU de um encadeamento depende
de sua prioridade. No exemplo, o Thread 2 tem uma prioridade acima dos outros, portanto,
incrementa mais o valor da contagem usando mais tempo de CPU. Embora o Thread 3 tenha a
menor prioridade, portanto, o número de incrementos é menor que os outros threads.

217

Capítulo 8 ■ Programação multithread, assíncrona e paralela


ThreadStatic
ThreadStatic é um atributo usado na parte superior de um campo estático para tornar seu valor exclusivo
(local) para cada thread.
Fragmento de código

Listagem 8-8.  Use ThreadStatic

using System;
using System.Threading;

Programa de aula
{
[ThreadStatic]
static int _count = 0;

estático vazio Main ()


{
Tópico threadA = new Thread (() =>
{
for (int i = 0; i <10; i ++)
{
Console.WriteLine ("ThreadA _count = {0}", _count ++);
}
});

Tópico threadB = novo Tópico (() =>


{
for (int i = 0; i <10; i ++)
{
Console.WriteLine ("ThreadB _count = {0}", _count ++);
}
});

threadA.Start ();
threadB.Start ();

}
}
//Resultado
ThreadA _count =
0 ThreadA _count
= 1 ThreadA
_count = 2
ThreadA _count =
3 ThreadA _count
= 4 ThreadA
_count = 5
ThreadA _count =
6 ThreadA _count
= 7 ThreadA
_count = 8
ThreadA _count =
9 ThreadB _count
= 0 ThreadB
_count = 1

218

Capítulo 8 ■ Programação multithread, assíncrona e paralela


ThreadB _count = 2
ThreadB _count = 3
ThreadB _count = 4
ThreadB _count = 5
ThreadB _count = 6
ThreadB _count = 7
ThreadB _count = 8
ThreadB _count = 9

Explicação
No snippet de código acima (Listagem 8-8 ), os dois threads têm seus valores locais
exclusivos de _count . Ambos os segmentos aumentaram o valor de _count 10 vezes. O
resultado final não é 19, porque cada thread incrementou o valor de sua cópia local da
variável _count .
Figura 8-5.  Cada encadeamento possui sua própria cópia da variável _count

Fragmento de código

Listagem 8-9.  Compartilhar um recurso comum para vários threads


using System;
using System.Threading;

Programa de aula
{

static int _count = 0;

219

Capítulo 8 ■ Programação multithread, assíncrona e paralela


estático vazio Main ()
{
Tópico threadA = new Thread (() =>
{
for (int i = 0; i <10; i ++)
{
Console.WriteLine ("ThreadA _count = {0}", _count ++);
}
});

Tópico threadB = novo Tópico (() =>


{
for (int i = 0; i <10; i ++)
{
Console.WriteLine ("ThreadB _count = {0}", _count ++);
}
});

threadA.Start ();
threadB.Start ();

}
}
//Resultado
ThreadA _count = 0
ThreadA _count = 1
ThreadA _count = 2
ThreadA _count = 3
ThreadA _count = 4
ThreadA _count = 5
ThreadA _count = 6
ThreadA _count = 7
ThreadA _count = 8
ThreadA _count = 9
ThreadB _count =
10 ThreadB _count
= 11 ThreadB
_count = 12
ThreadB _count =
13 ThreadB _count
= 14 ThreadB
_count = 15
ThreadB _count =
16 ThreadB _count
= 17 ThreadB
_count = 18
ThreadB _count =
18 ThreadB _count
= 19

Explicação
No snippet de código acima (Listagem 8-9 ), a variável _count não marcou com o atributo “
ThreadStatic ”, portanto, os dois threads compartilharam a mesma variável _count . Quando
um segmento incrementa o valor de _count , ele afeta o valor da variável _count que é usada
no outro segmento .

220

Capítulo 8 ■ Programação multithread, assíncrona e paralela

Figura 8-6.  Cada thread compartilha um _count comum

Grupo de discussão
O custo de instanciar um encadeamento gerenciado é mais alto do que reutilizar um
encadeamento livre. No .NET, um pool de threads é útil para reutilizar os threads gratuitos .
Um conjunto de encadeamentos é uma coleção de encadeamentos em segundo plano
criados por um sistema e estão disponíveis para executar qualquer tarefa quando necessário.
Quando um programa requer um encadeamento extra, é mais eficiente usar os
encadeamentos gratuitos disponíveis em um conjunto de encadeamentos, pois pode
economizar o custo de criação de um encadeamento. E quando um thread conclui sua
execução, ele pode voltar ao pool de threads para que outros programas possam reutilizar o
mesmo thread novamente.
O .NET implementou sua própria definição de pool de threads por meio da classe
ThreadPool. Ele possui um método, QueueUserWorkItem, que ajuda a enfileirar a execução de
threads disponíveis em um pool de threads.
Fragmento de código

Listagem 8-10.  Reutilizar um thread do ThreadPool

using System;
using System.Threading;

Programa de aula
{

estático vazio Main ()


{
// Coloque na fila o encadeamento.
ThreadPool.QueueUserWorkItem (novo WaitCallback (ThreadProc));

221

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Console.WriteLine ("Hello from Main Thread.");

// O pool de threads usa threads em segundo plano, é importante


// para manter o thread principal ocupado, caso contrário, o programa será encerrado
// antes que o encadeamento em segundo plano conclua sua execução

Console.ReadLine (); // Aguarde Enter

Console.WriteLine ("Hello Again from Main Thread.");

// Fila o encadeamento com o


Lambda
ThreadPool.QueueUserWorkItem (s)
=>
{
// s = estado
// nenhum valor é
atribuído a s // então s é
nulo
Console.WriteLine ("Oi, eu sou outro thread gratuito do pool de threads");
});

Console.ReadLine (); // Aguarde Enter


}

// Este procedimento de thread executa a


tarefa. estático void ThreadProc (Object
stateInfo)
{
// Nenhum objeto de estado foi passado para QueueUserWorkItem, portanto
// stateInfo é nulo.
Console.WriteLine ("Olá do pool de threads.");
}
}
//Resultado
Olá do segmento principal.
Olá do pool de threads.

Olá novamente do segmento principal.


Oi eu sou outro segmento livre do pool de threads.

Explicação
• WaitCallback é um representante que representa um método de
retorno de chamada a ser executado por um thread do pool de threads.
O método que ele representa pega um objeto.
• ThreadPool.QueueUserWorkItem enfileira um thread em segundo
plano disponível para execução.
• Console.ReadLine mantém a linha principal em espera até o usuário pressionar “Enter”.
222

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Limitação do pool de threads
• É difícil saber quando um thread de um pool de threads terminou sua execução.
• Não existe um método “Iniciar”, portanto, não podemos dizer quando
um thread de um pool de threads iniciou sua execução porque está
sendo gerenciado pelo sistema.
• Ele não pode gerenciar um thread que retorna um valor.

Figura 8-7.  Ciclo de vida de threads do pool de threads

Trabalhando com tarefas


Tarefa é uma parte importante da Task Parallel Library. É um objeto leve que gerencia
assincronamente a unidade de trabalho. A tarefa não cria novos threads. Em vez disso,
gerencia com eficiência os encadeamentos de um conjunto de encadeamentos. As tarefas são
executadas pelo TaskScheduler, que enfileira tarefas em threads.
Tarefa fornece os seguintes recursos avançados sobre encadeamento e conjunto de encadeamentos.
\ 1. \ Task permite retornar um resultado.
\ 2. \ Dá um melhor controle programático para executar e aguardar uma tarefa.
\ 3. \ Reduz o tempo de alternância entre vários threads.

223

Capítulo 8 ■ Programação multithread, assíncrona e paralela


\ 4. \ Permite encadear várias tarefas e pode executar cada tarefa uma
após a outra usando ContinueWith ().
\ 5. \ Ele pode criar um relacionamento pai / filho quando uma
tarefa é iniciada a partir de outra.
\ 6. \ A tarefa pode cancelar sua execução usando tokens de cancelamento.
\ 7. \ A tarefa deixa o CLR da sobrecarga de criação de mais threads;
em vez disso, usa implicitamente o thread do threadpool.
\ 8. \ A implementação assíncrona é fácil na tarefa, usando as palavras-
chave "assíncrona" e "aguardando".
\ 9. \ Task aguarda que todos os objetos de tarefas fornecidos concluam a execução.

Criar e executar uma tarefa


Para criar uma tarefa que não retorna um valor, usamos uma classe Task do espaço para
nome System.Threading.Tasks . Ele contém alguns métodos e propriedades importantes
que são úteis para gerenciar a operação da tarefa.

Tabela 8-4.  Métodos comuns e propriedades da classe de tarefas


Métodos e propriedadesExplicação
Corre() Retorna uma tarefa que enfileira o trabalho a ser executado no ThreadPool
Começar() Inicia uma tarefa
Esperar() Aguarde a tarefa especificada para concluir sua execução
WaitAll () Aguarde a conclusão de todos os objetos de tarefa fornecidos
WaitAny () Aguarde a conclusão de qualquer objeto de tarefa fornecido
Continue com() Crie uma cadeia de tarefas que são executadas uma após a outra
Status Obter o status da tarefa atual
Está cancelado Obtenha um valor booleano para determinar se uma tarefa é cancelada
Está completo Obtenha um valor booleano para determinar se uma tarefa foi concluída
IsFaulted Obtém se a tarefa é concluída devido a uma exceção não tratada.
Fábrica Fornecer método de fábrica para criar e configurar uma tarefa
   

A tarefa é uma parte importante da programação assíncrona e é executada em um


thread do conjunto de threads. Geralmente, uma expressão lambda é usada para
especificar o trabalho que a Tarefa deve executar.

224

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Sintaxe
Tarefa mytask = new Task (actionMethod);

• actionMethod é um método que possui um tipo de retorno nulo e não


aceita parâmetro de entrada; em outras palavras, há um delegado "Action"
no parâmetro do construtor Task.
•A tarefa possui um total de 8 construtores sobrecarregados, mas
geralmente trabalhamos com o primeiro construtor sobrecarregado
que possui um delegado "Action" em seu parâmetro de entrada.
Fragmento de código

Listagem 8-11.  Crie e inicie uma tarefa


using System;
using System.Threading.Tasks;

Programa de aula
{

estático vazio Main ()


{
// inicialize o mytask e atribua
// uma unidade de trabalho na forma de 'myMethod ()'
Tarefa myTask = nova tarefa (myMethod);
myTask.Start (); // Inicia a execução do mytask

myTask.Wait (); // Aguarde até o mytask concluir seu trabalho

// Faz parte do método principal


Console.WriteLine ("Bye From Main
Thread");

estático privado myMethod ()


{
Console.WriteLine ("Olá da minha
tarefa"); for (int i = 0; i <10; i ++)
{
Console.Write ("{0}", i);
}
Console.WriteLine ();
Console.WriteLine ("Tchau da minha
tarefa");

}
}
//Resultado
Olá da minha tarefa
123456789
Tchau da minha
tarefa Tchau da
discussão principal

225

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Explicação
Sabemos que o Task executa nos threads de segundo plano de um pool de threads.
Portanto (na Listagem 8-11 ), é importante escrever um método "wait ()" , caso contrário,
o programa será encerrado assim que o Thread Principal concluir sua execução.
Fragmento de código

Listagem 8-12.  Reutilize uma tarefa usando Task.Factory.StartNew


using System;
using System.Threading.Tasks;

Programa de aula
{

estático vazio Main ()


{
// inicialize e inicie o mytask e atribua //
uma unidade de trabalho no corpo de
lambda exp
Tarefa mytask = Task.Factory.StartNew (new Action
(myMethod)); mytask.Wait (); // Aguarde até o mytask
concluir seu trabalho

// Faz parte do método principal


Console.WriteLine ("Hello From Main
Thread");

static void myMethod ()


{
Console.WriteLine ("Olá da minha
tarefa"); for (int i = 0; i <10; i ++)
{
Console.Write ("{0}", i);
}
Console.WriteLine ();
Console.WriteLine ("Tchau da minha
tarefa");
}

}
//Resultado
Olá da minha tarefa 1
2 3 4 5 6 7 8 9 Adeus da
minha tarefa Olá da
lista principal

Explicação
(Na Listagem 8-12 ) Crie uma tarefa e inicie-a imediatamente chamando o método StartNew .
No .NET 4.0, é preferível usar Task.Factory.StartNew para criar e iniciar uma tarefa, pois
economiza custos de desempenho, enquanto Task (…) .Start () consome mais custos de
desempenho para criar e iniciar uma tarefa.

226

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Code Snipppet

Listagem 8-13.  Reutilize uma tarefa usando Task.Run


using System;
using System.Threading.Tasks;

Programa de aula
{

estático vazio Main ()


{
// inicialize e execute o mytask e atribua //
uma unidade de trabalho na forma de
'myMethod ()'
Tarefa mytask = Task.Run (nova ação (myMethod));

mytask.Wait (); // Aguarde até o mytask concluir seu trabalho


// Faz parte do método principal
Console.WriteLine ("Hello From Main
Thread");

estático privado myMethod ()


{
Console.WriteLine ("Olá da minha
tarefa"); for (int i = 0; i <10; i ++)
{
Console.Write ("{0}", i);
}
Console.WriteLine ();
Console.WriteLine ("Tchau da minha
tarefa");

}
}
//Resultado
Olá da minha tarefa
123456789
Tchau da minha
tarefa Tchau da
discussão principal

Explicação
(Na Listagem 8-13 ) Task.Run () retorna e executa uma tarefa atribuindo uma unidade de
trabalho na forma de um método ("myMethod"). No .NET 4.5, é preferível usar o Task.Run
porque gerencia o Task com mais eficiência do que o Task.Factory.StartNew.

227

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Fragmento de código

Listagem 8-14.  Use a expressão Lambda para usar o Task.Run

using System;
using System.Threading.Tasks;

Programa de aula
{

estático vazio Main ()


{
// inicialize e execute o mytask e atribua //
uma unidade de trabalho no corpo do
lambda exp Task myTask = Task.Run ( () =>
{
Console.WriteLine ("Olá da minha
tarefa"); for (int i = 0; i <10; i ++)
{
Console.Write ("{0}", i);
}
Console.WriteLine ();
Console.WriteLine ("Tchau da minha
tarefa");
} );

myTask.Wait (); // Aguarde até o mytask concluir seu trabalho

// Faz parte do método principal


Console.WriteLine ("Hello From Main
Thread");

}
//Resultado
Olá da minha tarefa
123456789
Tchau da minha
tarefa Tchau da
discussão principal

Explicação
(Na Listagem 8-14 ) () => {} a expressão lambda é usada para atribuir um método anônimo a
Task.Run (). O myTask executará o método anônimo em uma tarefa separada.

■■ Nota   Geralmente, a expressão lambda é usada para atribuir uma unidade de trabalho no Task.Run.

Criar e executar uma tarefa <Result>


A tarefa <Result> é usada com operações assíncronas que retornam um valor. A classe Task
<Result> é encontrada no espaço para nome System.Threading.Task e herda da classe Task.

228

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Sintaxe

Tarefa <TResult> mytask = new Tarefa <TResult> (funcMethod);

• funcMethod é um método que possui um tipo de retorno do tipo TResult


e não aceita parâmetro de entrada; em outras palavras, há um delegado
"Func <TResult>" no parâmetro do construtor Task.
Fragmento de código

Listagem 8-15.  Obtenha um valor de um método usando a Tarefa <T>


using System;
using System.Threading;
using
System.Threading.Tasks;

Programa de aula
{

estático vazio Main ()


{
Tarefa <int> myTask = nova Tarefa <int>
(myMethod); myTask.Start (); // inicia o
myTask

Console.WriteLine ("Hello from Main Thread");

// Aguarde o thread principal até o myTask terminar


// e retorna o valor da operação myTask (myMethod) int i =
myTask.Result;
Console.WriteLine ("myTask tem um valor de retorno =
{0}", i); Console.WriteLine ("Bye From Main Thread");
}

static int myMethod ()


{
Console.WriteLine ("Hello from myTask
<int>"); Thread.Sleep (1000);

retornar 10;
}

}//Resultado
Olá do Tópico Principal Olá do
myTask <int> O myTask tem
um valor de retorno = 10 Bye
Do Tópico Principal

229

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Explicação
• Tarefa <int> informa à operação da tarefa para retornar um valor inteiro.
• myTask.Result; é uma propriedade que retorna um valor quando a
tarefa é concluída e bloqueia a execução de um encadeamento de
chamada (nesse caso, seu encadeamento principal) até que a tarefa
conclua sua execução.
Fragmento de código

Listagem 8-16.  Use Task <T> .Factory.StartNew para retornar um valor de um método Task
using System;
using System.Threading;
using
System.Threading.Tasks;

Programa de aula
{

estático vazio Main ()


{
Tarefa <int> myTask = Tarefa <int> .Factory.StartNew <int> (myMethod);

Console.WriteLine ("Hello from Main Thread");

// Aguarde o thread principal até o myTask terminar


// e retorna o valor da operação myTask (myMethod) int i =
myTask.Result;

Console.WriteLine ("myTask tem um valor de retorno =


{0}", i); Console.WriteLine ("Bye From Main Thread");
}

static int myMethod ()


{
Console.WriteLine ("Hello from myTask
<int>"); Thread.Sleep (1000);

retornar 10;
}
}
//Resultado
Olá do Tópico Principal Olá do
myTask <int> O myTask tem
um valor de retorno = 10 Bye
Do Tópico Principal

Explicação
(Na Listagem 8-16 ) Crie uma tarefa <T> e inicie-a imediatamente chamando o método
StartNew . No .NET 4.0, é preferível usar a Tarefa <T> .Factory.StartNew para criar e iniciar
uma tarefa, pois economiza custo de desempenho, enquanto a Tarefa <T> (…) .Start () consome
mais custo de desempenho para criar e iniciar um tarefa.

230

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Fragmento de código

Listagem 8-17.  Use Task.Run <int> para refazer um valor do método Task
using System;
using System.Threading;
using
System.Threading.Tasks;

Programa de aula
{

estático vazio Main ()


{
Tarefa <int> myTask = Task.Run <int> (novo Func <int> (myMethod));

Console.WriteLine ("Hello from Main Thread");

// Aguarde o thread principal até que myTask termine // e


retorne o valor da operação myTask (myMethod) int i =
myTask.Result;

Console.WriteLine ("myTask tem um valor de retorno =


{0}", i); Console.WriteLine ("Bye From Main Thread");
}

static int myMethod ()


{
Console.WriteLine ("Hello from myTask
<int>"); Thread.Sleep (1000);

retornar 10;
}

}
//Resultado
Olá do Tópico Principal Olá do
myTask <int> O myTask tem
um valor de retorno = 10 Bye
Do Tópico Principal

Explicação
• Task.Run <int> () pega um delegado Func <int> para referenciar um
método que retorna um valor inteiro. Este método é executado por uma
tarefa e um valor é retornado usando a propriedade Result .
Fragmento de código

Listagem 8-18.  Use a expressão Lambda para retornar um valor do método de uma tarefa

using System;
using System.Threading;
using
System.Threading.Tasks;

Programa de aula
{
231

Capítulo 8 ■ Programação multithread, assíncrona e paralela


estático vazio Main ()
{
Tarefa <int> myTask = Task.Run <int> ( () =>
{
Console.WriteLine ("Hello from myTask <int>");
Thread.Sleep (1000);
retornar 10;
} );

Console.WriteLine ("Hello from Main Thread");

// Aguarde o encadeamento principal até que myTask


seja finalizado // e retorne o valor da operação
myTask
int i = myTask.Result;

Console.WriteLine ("myTask tem um valor de retorno =


{0}", i); Console.WriteLine ("Bye From Main Thread");
}

//Resultado
Olá do Tópico Principal Olá do
myTask <int> O myTask tem
um valor de retorno = 10 Bye
Do Tópico Principal

Explicação
(Na Listagem 8-18 ) A expressão Lambda pode ser usada para definir uma unidade de trabalho
para a Tarefa <int>. E, dentro da expressão lambda, seu valor de retorno deve corresponder ao
tipo de tarefa <T>.
Fragmento de código

Listagem 8-19.  Use var & Task.Run <T> com Lambda Expression para retornar um valor do método Task

using System;
using System.Threading;
using
System.Threading.Tasks;

Programa de aula
{

estático vazio Main ()


{
var myTask = Task.Run <int> ( () =>
{
Console.WriteLine ("Hello from myTask <int>");
Thread.Sleep (1000);
retornar 10;
} );

232

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Console.WriteLine ("Hello from Main Thread");

// Aguarde o encadeamento principal até que myTask


seja finalizado // e retorne o valor da operação
myTask
int i = myTask.Result;

Console.WriteLine ("myTask tem um valor de retorno =


{0}", i); Console.WriteLine ("Bye From Main Thread");
}

}
//Resultado
Olá do Tópico Principal Olá do
myTask <int> O myTask tem
um valor de retorno = 10 Bye
Do Tópico Principal

Explicação
No trecho de código acima (Listagem 8-19 ), a tarefa <int> não definiu; em vez disso, a palavra-chave var é
usada. A palavra-chave Var detecta o tipo de tarefa <T> observando o tipo de tarefa <T> gravada no lado
direito (que neste caso é a tarefa <int>).

Fragmento de código

Listagem 8-20.  Use var & Task.Run com Lambda Expression para retornar um valor do método Task
using System;
using System.Threading;
using
System.Threading.Tasks;

Programa de aula
{

estático vazio Main ()


{
var myTask = Task.Run ( () =>
{
Console.WriteLine ("Hello from myTask <int>");
Thread.Sleep (1000);

retornar 10;
} );

Console.WriteLine ("Hello from Main Thread");

// Aguarde o encadeamento principal até que myTask


seja finalizado // e retorne o valor da operação
myTask
int i = myTask.Result;

Console.WriteLine ("myTask tem um valor de retorno =


{0}", i); Console.WriteLine ("Bye From Main Thread");
}

}
233
Capítulo 8 ■ Programação multithread, assíncrona e paralela
//Resultado
Olá do Tópico Principal Olá do
myTask <int> O myTask tem
um valor de retorno = 10 Bye
Do Tópico Principal

Explicação
No trecho de código acima (Listagem 8-20 ), a tarefa <int> não definiu nos dois lados. Em vez
disso, a palavra-chave var é usada. A palavra-chave Var detecta o tipo de tarefa <T>
procurando no valor de retorno da expressão lambda.

Aguarde uma ou mais tarefas


As tarefas são executadas de forma assíncrona em um thread do conjunto de encadeamentos. O
conjunto de encadeamentos contém encadeamentos em segundo plano; portanto, quando a
tarefa está em execução, o encadeamento principal pode encerrar o aplicativo antes que a
tarefa seja concluída. Para sincronizar a execução do thread principal e as tarefas assíncronas,
usamos o método Wait .
O método Wait bloqueia a execução de um encadeamento de chamada até que a
execução de uma tarefa especificada seja concluída.
A seguir, são apresentados métodos importantes de espera que ajudam a sincronizar um encadeamento
principal com as Tarefas.
\ 1. \ Task.Wait ()
\ 2. \ Task.Wait (milissegundos)
\ 3. \ Task.WaitAll ()
\ 4. \ Task.WaitAll (milissegundos)
\ 5. \ Task.WaitAny

Task.Wait ()
Para aguardar a conclusão de uma única tarefa, você pode chamar o método Task.Wait. Ele
bloqueia o encadeamento de chamada até que a tarefa especificada conclua sua execução.
Fragmento de código

Listagem 8-21.  Use Task.Wait para manter a execução do Thread Principal


using System;
using System.Threading;
using
System.Threading.Tasks;

Programa de aula
{

estático vazio Main ()


{
Tarefa myTask = Task.Run (() =>
{
Thread.Sleep (1000);
Console.WriteLine ("Tarefa concluída após 1 segundo");
});

234

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Console.WriteLine ("Hello from Main Thread");
myTask.Wait (); // espera até que o myTask seja
concluído

Console.WriteLine ("Bye From Main Thread");


}

}
//Resultado
Tarefa Hello From Main
Thread concluída após 1
segundo Bye From Main
Thread

Task.Wait (milissegundos)
O método Task.Wait (milissegundos) bloqueia a execução de um encadeamento de chamada até
que a tarefa especificada seja concluída ou que um intervalo de tempo limite termine.
Fragmento de código

Listagem 8-22.  Use Task.Wait (millisec) para aguardar o Thread Principal por um tempo específico

using System;
using System.Threading;
using
System.Threading.Tasks;

Programa de aula
{

estático vazio Main ()


{
Tarefa myTask = Task.Run (() =>
{
// Aguarde 2 segundos Thread.Sleep (2000);
Console.WriteLine ("myTask concluída após 2 s");

});

Tarefa myTask2 = Task.Run (() =>


{
// Aguarde meio
segundo
Thread.Sleep (500);
Console.WriteLine ("myTask2 concluída após meio segundo");
});

myTask.Wait (1000); // aguarde 1 segundo


Console.WriteLine ("Hello from Main
Thread");

myTask2.Wait (1000); // aguarde 1 segundo


Console.WriteLine ("Hello from Main Thread,
again");

Console.WriteLine ("By From Main Thread");


}

}
235

Capítulo 8 ■ Programação multithread, assíncrona e paralela


//Resultado
myTask2 concluído após meio
segundo Olá do Thread principal
Olá do Tópico Principal,
novamente Por Do Tópico
Principal
Explicação
• myTask.Wait (1000 ) bloqueia a execução por 1 segundo; O myTask não
concluiu sua execução em um determinado período; portanto, o myTask
foi encerrado.
• myTask2.Wait (1000) também bloqueia a execução por 1 segundo, mas o
myTask2 conclui sua execução antes de 1 segundo. Portanto, o
encadeamento principal continua sua execução assim que o myTask2 é
concluído.

Task.WaitAll ()
O método Task.WaitAll bloqueia a execução de um thread de chamada até que todas as
tarefas especificadas concluam sua execução. WaitAll é um método estático da classe Task.
Todos os objetos de tarefa devem ser referenciados em uma única matriz e o método
WaitAll precisa dessa matriz para bloquear a execução de um encadeamento de chamada até
que todas as tarefas especificadas em uma matriz sejam concluídas.
Fragmento de código

Listagem 8-23.  Use Task.WaitAll para aguardar o Thread Principal até que todas as Tarefas especificadas
estejam em execução

using System;
using System.Threading;
using
System.Threading.Tasks;

Programa de aula
{

estático vazio Main ()


{
Tarefa tsk1 = Task.Run (() =>
{
Thread.Sleep (100);
Console.WriteLine ("tsk1 concluído");

});

Tarefa tsk2 = Task.Run (() =>


{
Thread.Sleep (500);
Console.WriteLine ("tsk2 concluído");
});

Tarefa tsk3 = Task.Run (() =>


{
Thread.Sleep (1000);
Console.WriteLine ("tsk3 concluído");
});

236

Capítulo 8 ■ Programação multithread, assíncrona e paralela


// Armazena a referência de todas as tarefas em uma
matriz de Task Task [] allTasks = {tsk1, tsk2, tsk3};

// Aguarde a conclusão de todas


as tarefas Task.WaitAll (allTasks);

Console.WriteLine ("Por do thread principal");


}

}
//Resultado
tsk1 concluído
tsk2 concluído
tsk3 concluído
Por do main 0 \ thread

Task.WaitAll (task [], milissegundos)


O método Task.WaitAll (task [], milissegundos) bloqueia a execução de um thread de chamada
até que todas as tarefas especificadas terminem ou que um intervalo de tempo limite termine.
Fragmento de código

Listagem 8-24.  Use Task.WaitAll (task [], milissegundo) para aguardar MainThread até
que todos os especificados estejam em execução por um período especificado
using System;
using System.Threading;
using
System.Threading.Tasks;

Programa de aula
{

estático vazio Main ()


{
Tarefa tsk1 = Task.Run (() =>
{
Thread.Sleep (500);
Console.WriteLine ("tsk1 concluído");

});

Tarefa tsk2 = Task.Run (() =>


{
Thread.Sleep (2000);
Console.WriteLine ("tsk2 concluído");
});

Tarefa tsk3 = Task.Run (() =>


{
Thread.Sleep (1000);

237

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Console.WriteLine ("tsk3 concluído");
});

// Armazena a referência de todas as tarefas em


uma matriz de Task Task [] allTasks = {tsk1, tsk2,
tsk3};

// Aguarde a conclusão de todas


as tarefas Task.WaitAll (allTasks,
1200);

Console.WriteLine ("Por do thread principal");


}

}
//Resultado
tsk1 concluído
tsk3 concluído
Por da linha principal
Task.WaitAny ()
Task.WaitAny é um método estático da classe Task. Bloqueia a execução de um
encadeamento de chamada até que qualquer primeira tarefa de uma coleção de tarefas
conclua sua execução.
Fragmento de código

Listagem 8-25.  Use Task.WaitAny para aguardar um thread principal, até que qualquer primeiro thread
conclua sua execução

using System;
using System.Threading;
using
System.Threading.Tasks;

Programa de aula
{

estático vazio Main ()


{
Tarefa tsk1 = Task.Run (() =>
{
Thread.Sleep (1000);
Console.WriteLine ("tsk1 concluído");

});

Tarefa tsk2 = Task.Run (() =>


{
Thread.Sleep (500);
Console.WriteLine ("tsk2 concluído");
});

Tarefa tsk3 = Task.Run (() =>


{

238

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Thread.Sleep (2000);
Console.WriteLine ("tsk3 concluído");
});

// Armazena a referência de todas as tarefas em


uma matriz de Task Task [] allTasks = {tsk1, tsk2,
tsk3};

// Aguarde a conclusão de todas


as tarefas Task.WaitAny
(allTasks);

Console.WriteLine ("Por do thread principal");


}

}
//Resultado
Tsk2 concluído
Por da linha principal

Task.WaitAny (task [], milissegundos)


O método Task.WaitAny (task [], milissegundos) bloqueia a execução de um encadeamento de
chamada até que qualquer primeira tarefa de uma coleção de tarefas seja concluída ou um
intervalo de tempo limite termine.
Fragmento de código

Listagem 8-26.  Use Task.WaitAny (task [], s) para aguardar um encadeamento principal por um período
especificado

using System;
using System.Threading;
using
System.Threading.Tasks;

Programa de aula
{

estático vazio Main ()


{
Tarefa tsk1 = Task.Run (() =>
{
Thread.Sleep (500);
Console.WriteLine ("tsk1 concluído");

});

Tarefa tsk2 = Task.Run (() =>


{
Thread.Sleep (2000);
Console.WriteLine ("tsk2 concluído");
});

Tarefa tsk3 = Task.Run (() =>


{

239

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Thread.Sleep (1000);
Console.WriteLine ("tsk3 concluído");
});

// Armazena a referência de todas as tarefas em


uma matriz de Task Task [] allTasks = {tsk1, tsk2,
tsk3};

// Aguarde a conclusão de todas


as tarefas Task.WaitAny
(allTasks, 1200);

Console.WriteLine ("Por do thread principal");


}

}
//Resultado
tsk1 concluído
Por da linha principal

Encadear várias tarefas com continuações


O método Task.ContinueWith é usado para criar cadeias de várias tarefas. Cada próxima
tarefa em uma cadeia não será agendada para execução até que a tarefa atual seja concluída
com êxito, com falha devido a uma exceção não tratada ou com saída antecipada devido ao
cancelamento.
Fragmento de código

Listagem 8-27.  Use Task.ContinueWith para encadear uma tarefa após a outra.
using System;
using System.Threading;
using
System.Threading.Tasks;

Programa de aula
{

estático vazio Main ()


{
Tarefa tsk1 = Task.Run (() =>
{
Thread.Sleep (100);
Console.WriteLine ("tsk1");
});

// Execute o tsk2 assim que o tsk1 for


concluído Tarefa tsk2 = tsk1.
ContinueWith ((t) =>
{
Thread.Sleep (500);
Console.WriteLine ("tsk2");
});

tsk2.Wait ();
}

240

Capítulo 8 ■ Programação multithread, assíncrona e paralela


}
//Resultado
tsk1
tsk2

Explicação
• tsk1.ContinueWith ((t) => {..}) ; execute e retorne uma nova tarefa
quando o tsk1 concluir sua execução. Aqui " t " no parâmetro de entrada
do método ContinueWith é a referência de tsk1. Esse "t" pode ser utilizado
no corpo de uma expressão lambda. Por exemplo, se tsk1 retornar um
valor, usando "t" o valor de retorno poderá ser utilizado no corpo de uma
expressão lambda.
• Tsk2.Wait (); deve esperar pelo tsk2 para concluir sua execução, e sua
execução será iniciada quando o tsk1 concluir sua execução. Portanto,
tsk2.Wait () deve aguardar todas as tarefas que foram encadeadas com ele.

Use a tarefa <TResult> com continuação


Tarefa <TResult> é uma tarefa que retorna um valor do tipo TResult. A tarefa <TResult>
pode ser utilizável com continuação. Tal tarefa <TResult> retorna um valor para que
uma nova tarefa em uma cadeia possa usá-lo.
Fragmento de código

Listagem 8-28.  Retorne um resultado da primeira tarefa no corpo de uma segunda tarefa usando
Task.Result;

using System;
using System.Threading;
using
System.Threading.Tasks;

Programa de aula
{
estático vazio Main ()
{
Tarefa <string> tsk1 = Task.Run (() =>
{
Thread.Sleep
(100); retornar
"Ali";
});

// Execute o tsk2 assim que o tsk1 for


concluído Tarefa tsk2 =
tsk1.ContinueWith ((t) =>
{
// Aguarde tsk1 e retorne seu valor
string name = t.Result ;

Console.WriteLine ("Meu nome é: {0}", nome);


});

tsk2.Wait ();
}

241

Capítulo 8 ■ Programação multithread, assíncrona e paralela


}
//Resultado
O meu nome é Ali

Explicação
• t.Result; aguarde o tsk1 concluir sua execução e retornar o valor dele.

TaskContinuationOption
TaskContinuationOption é uma enumeração usada para especificar quando uma tarefa em
uma cadeia contínua é executada. A seguir, estão algumas das enumerações mais comuns para
TaskContinuationOption:
• OnlyOnFaulted Especifica que a tarefa de continuação deve ser
agendada apenas se seu antecedente tiver lançado uma exceção não
tratada.
• NotOnFaulted Especifica que a tarefa de continuação deve ser
agendada se seu antecedente não gerar uma exceção não
tratada.
• OnlyOnCanceled Especifica que a continuação deve ser agendada
apenas se seu antecedente foi cancelado. Uma tarefa será cancelada
se sua propriedade Task.Status após a conclusão for
TaskStatus.Canceled.
• NotOnCanceled Especifica que a tarefa de continuação deve ser
agendada se seu antecedente não tiver sido cancelado.
• OnlyOnRanToCompletion Especifica que a tarefa de continuação deve ser
agendada se o antecedente for concluído.
• NotOnRanToCompletion Especifica que a tarefa de continuação deve ser
agendada se seu antecedente não for executado até a conclusão.

Fragmento de código

Listagem 8-29.  Use TaskContinuationOption para executar a tarefa encadeada apenas se alguma condição
atender
using System;
using System.Threading;
using
System.Threading.Tasks;

Programa de aula
{

estático vazio Main ()


{
Tarefa <string> tsk1 = Task.Run (() =>
{
lança nova exceção ();

Console.WriteLine ("tsk1
correu"); Thread.Sleep (100);
retornar "Ali";
});

242

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Tarefa tsk2 = tsk1.ContinueWith ((t) =>
{

Console.WriteLine ("o tsk2 foi executado quando o tsk1 lançou uma exceção");

} , TaskContinuationOptions.OnlyOnFaulted );

tsk2.Wait ();
}

}
//Resultado
O tsk2 foi executado quando o tsk1 lançou uma exceção

Explicação
Na (Listagem 8-29 ), o segundo parâmetro do método tsk1.ContinueWith (
TaskContinuationOptions) foi especificado com OnlyOnFaulted , que diz que o tsk2 só
pode ser executado se o tsk1 lançou uma exceção sem tratamento, caso contrário, ignorará
a execução do tsk2.
Da mesma forma, podemos especificar TaskContinuationOptions com outras
enumerações, ou seja, OnlyOnCanceled, NotOnFaulted, etc.
A tarefa retornada não será agendada para execução até que a tarefa atual seja
concluída. Se os critérios de continuação especificados por meio do parâmetro
continuationOptions não forem atendidos, a tarefa de continuação será cancelada em vez de
agendada.
Opções para quando a continuação está agendada e como ela se comporta:

Tarefa aninhada
Uma tarefa aninhada é apenas uma instância de tarefa criada no delegado do usuário de outra
tarefa. Uma tarefa filho é uma tarefa aninhada criada com a opção AttachedToParent. Uma
tarefa pode criar qualquer número de tarefas filho e / ou aninhadas, limitadas apenas pelos
recursos do sistema. O exemplo a seguir mostra uma tarefa pai que cria uma tarefa aninhada
simples.

Tarefa filho desanexada


Toda tarefa aninhada é por padrão uma tarefa filho desanexada. Ele é executado independentemente de
seu pai.
Fragmento de código
Listagem 8-30.  Criar tarefa aninhada
using System;
using System.Threading;
using
System.Threading.Tasks;
Programa de aula
{

estático vazio Main ()


{

Tarefa outer = Task.Run (() =>


{
Console.WriteLine ("Olá, sou uma tarefa externa");
243

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Tarefa inner = Task.Run (() =>
{
Console.WriteLine ("Oi, eu sou uma tarefa
interna"); Thread.Sleep (2000);
Console.WriteLine ("Por da tarefa
interna");
});

Thread.Sleep (500); Console.WriteLine


("Por da tarefa externa");
});

outer.Wait ();
}

}
//Resultado
Oi eu sou tarefa
externa HI eu sou
tarefa interna Por
da tarefa externa

Explicação
(Listagem 8-30 ) Podemos criar uma tarefa interna o quanto quisermos. Mas as tarefas internas
e externas serão executadas independentemente uma da outra. Quando uma tarefa externa
concluir sua execução, ela sairá e será sincronizada com o encadeamento principal.

Tarefa filho anexada ao pai


Uma tarefa filho aninhada pode se conectar ao pai usando a opção AttachedToParent. A
tarefa pai não pode terminar sua execução até que todas as tarefas filho anexadas concluam
sua execução.
Fragmento de código

Listagem 8-31.  Use AttachedToParent para criar uma tarefa filho aninhada

using System;
using System.Threading;
using
System.Threading.Tasks;
Programa de aula
{

estático vazio Main ()


{
Tarefa externa = nova Tarefa (() =>
{
Console.WriteLine ("Olá, sou uma tarefa externa");

// AttachedToParent disponível apenas com a nova


Task () Task inner = new Task (() =>

244

Capítulo 8 ■ Programação multithread, assíncrona e paralela


{
Console.WriteLine ("Oi, eu sou uma tarefa
interna"); Thread.Sleep (2000);
Console.WriteLine ("Por da tarefa
interna");
}, TaskCreationOptions.AttachedToParent );

inner.Start ();

Thread.Sleep (500); Console.WriteLine


("Por da tarefa externa");
});

outer.Start ();
outer.Wait ();
}

}
//Resultado
Oi, eu sou tarefa
externa Oi, eu sou
tarefa interna Por
tarefa externa Por
tarefa interna

Explicação
(Na Listagem 8-31 ) É importante não usar “Task.Run ()” ao criar uma tarefa filho que depende
de seu pai. No trecho de código acima, uma nova tarefa aninhada foi criada e anexada ao pai
usando a propriedade "AttachedToParent" como o segundo argumento de " new task (). "

Sincronização de variáveis no multithreading


Em um ambiente multithreading, a mesma variável pode ser acessada por dois ou mais
threads. Se a operação executada em uma variável compartilhada for atômica ou
segura para threads, ela produzirá um resultado preciso. Se a operação não for atômica ou não
for segura para threads, ela produzirá resultados imprecisos.
Na operação atômica, apenas um único encadeamento de cada vez pode executar uma
única instrução e produzir resultados precisos; enquanto, em uma operação não atômica , mais
de um encadeamento está acessando e manipulando o valor de uma variável compartilhada, o
que produz um resultado impreciso (por exemplo, se um encadeamento está lendo um valor e o
outro encadeamento ao mesmo tempo está editando isto).
Fragmento de código

Listagem 8-32.  Vários threads acessando o mesmo recurso "variável"


estático vazio Main ()
{
int num = 0;
comprimento int = 500000;

// Executar em um thread separado do


conjunto de threads Task tsk =
Task.Run (() =>
{
for (int i = 0; i <comprimento; i ++)

245

Capítulo 8 ■ Programação multithread, assíncrona e paralela


{
num = num + 1;

}
});

// Executar no segmento principal


for (int i = 0; i <comprimento; i ++)
{
num = num - 1;
}

tsk.Wait ();
Console.WriteLine (num);
}
// Saída
-1500

Explicação
O trecho de código acima (Listagem 8-32 ) fornece resultados imprecisos porque dois
threads estão acessando e manipulando o valor de "num" ao mesmo tempo. A declaração "
num = num + 1;" é realmente uma combinação de mais de uma declaração; primeiro ele lerá
o valor atual de "num", adicionará 1 ao seu valor atual e o atribuirá a "num".
Imagine se a rosca principal leu o valor de num = 6, mas a outra rosca leu o valor de num =
3. Quando a rosca principal diminui o valor de "num", ela se torna 5. Mas a outra rosca já leu o
valor de num = 3 ; quando incrementado, o valor de num torna-se "4", o que é totalmente
errado, porque o outro encadeamento deve obter o valor mais recente de num e depois
incrementá-lo e o resultado deve ser "6". (A saída deste programa pode ser diferente se você a
executar na sua máquina, porque depende do ciclo de execução da CPU.)

Manipular a sincronização de variáveis no multithreading


A seguir, estão três maneiras comuns de lidar com variáveis de sincronização em um ambiente multithread.
\ 1. \ Bloquear
\ 2. \ Monitor
\ 3. \ Interlock

bloqueio (objeto)
Lock é uma palavra-chave em C #; impede que um thread execute o mesmo bloco de código
que outro thread está executando. Esse bloco de código é chamado de código bloqueado.
Portanto, se um encadeamento tentar inserir um código bloqueado, ele aguardará até que o
objeto seja liberado. A palavra-chave lock chama Enter no início do bloco e Exit no final do
bloco.

A melhor prática é usar a palavra-chave lock com um objeto particular ou com uma
variável de objeto estático particular para proteger dados comuns a todas as instâncias.

246
Capítulo 8 ■ Programação multithread, assíncrona e paralela
Fragmento de código

Listagem 8-33.  Use lock para proteger com segurança um recurso compartilhado

using System;
using System.Threading;
using
System.Threading.Tasks;

Programa de aula
{

}
//Resultado
00

Explicação
• lock (thislock) {…} impedirá que outros threads manipulem a memória
compartilhada, ou seja, “n”. Quando o controle sai do bloco, a memória
compartilhada se torna utilizável para qualquer thread.
• thislock é a mesma variável usada em vários threads, notificando
outros threads se alguém já o usou para bloquear um bloco de código.
• Portanto, a memória compartilhada torna -se segura para threads e o programa fornece um
resultado preciso.

Monitor
A classe Monitor também garante que nenhum outro encadeamento possa executar a mesma
seção de código ou uma memória compartilhada até que seja executado pelo proprietário do
bloqueio.
Fragmento de código

Listagem 8-34.  Use Monitor.Enter para thread-safe um recurso compartilhado


using System;
using System.Threading;
using
System.Threading.Tasks;

Programa de aula
{
// Este objeto é usado para bloquear um bloco
objeto estático privado thislock = new object ();

estático vazio Main ()


{
int num = 0;
comprimento int = 500000;

// Executar em um thread separado do


conjunto de threads Task tsk =
Task.Run (() =>
{
for (int i = 0; i <comprimento; i ++)

247

Capítulo 8 ■ Programação multithread, assíncrona e paralela


{
// bloqueia o bloco de código
Monitor.Enter (este bloqueio);
num = num + 1;

// desbloqueia o código bloqueado


Monitor.Exit (este bloqueio);

}
});

// Executar no segmento principal


for (int i = 0; i <comprimento; i ++)
{
// bloqueia o bloco de código
Monitor.Enter (este bloqueio);

num = num - 1;

// desbloqueia o código bloqueado


Monitor.Exit (este bloqueio);
}

tsk.Wait ();
Console.WriteLine (num);
}

}
//Resultado
00

Explicação
• Monitor.Enter ou Monitor.TryEnter método é utilizado para bloquear
um bloco de código para os outros fios e impedir outros segmentos de
executá-lo.
• O método Monitor.Exit é usado para desbloquear o código bloqueado de
outro thread e permitir que outros threads o executem.

Intertravado
A classe intertravada é usada para sincronizar o acesso de objetos de memória
compartilhada entre vários encadeamentos. A classe intertravada fornece a seguinte
operação útil na memória compartilhada:
\ 1. \ Métodos de incremento e decremento , usados para incrementar ou
diminuir um valor da variável.
\ 2. \ Método Add and Read , usado para adicionar um valor inteiro a uma
variável ou ler um valor inteiro de 64 bits como uma operação atômica.
Os métodos \ 3. \ Exchange e CompareExchange , usados para realizar
uma troca atômica retornando um valor e substituindo-o por um
novo valor, ou dependerá do resultado de uma comparação.
248

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Fragmento de código

Listagem 8-35.  Use Interlocked para proteger com segurança um recurso compartilhado

using System;
using System.Threading;
using
System.Threading.Tasks;

Programa de aula
{
estático vazio Main ()
{
int num = 0;
comprimento int = 500000;

// Executar em um thread separado do


conjunto de threads Task tsk =
Task.Run (() =>
{
for (int i = 0; i <comprimento; i ++)
{
Bloqueio.Incremento (ref num);

}
});

// Executar no segmento principal


for (int i = 0; i <comprimento; i ++)
{
Interlocked.Decrement (ref num);
}

tsk.Wait ();
Console.WriteLine (num);
}

}
//Resultado
00

Explicação
• Intertravado. O incremento pega a referência de uma memória
compartilhada, ou seja, “num” e a incrementa, protegendo-a por
thread .
• Interlocked.Decrement pega a referência de uma memória
compartilhada, ou seja, "num" e a diminui, protegendo-a de threads .

Dead Lock
Em um ambiente multithread, um bloqueio morto pode ocorrer; ele congela o aplicativo
porque duas ou mais atividades aguardam a conclusão uma da outra. Geralmente, ocorre
quando um recurso compartilhado é bloqueado por um thread e outro está aguardando para
acessá-lo.
249

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Fragmento de código

Listagem 8-36.  Criar um bloqueio morto

using System;
using System.Threading;
using
System.Threading.Tasks;

Programa de aula
{
// usado como objetos de bloqueio
objeto estático privado thislockA = new object ();
objeto estático privado thislockB = new object ();

estático vazio Main ()


{
Tarefa tsk1 = Task.Run (() =>
{
lock (thislockA)
{
Console.WriteLine ("thislockA of tsk1");

lock (thislockB)
{
Console.WriteLine ("thislockB of tsk2");
Thread.Sleep (100);

}
}
});

Tarefa tsk2 = Task.Run (() =>


{
lock (thislockB)
{
Console.WriteLine ("thislockB of tsk2");

lock (thislockA)
{
Console.WriteLine ("thislockA of tsk2");
Thread.Sleep (100);
}
}
});

Tarefa [] allTasks = {tsk1, tsk2}; Task.WaitAll


(allTasks); // Aguarde todas as tarefas

Console.WriteLine ("Programa executado com êxito");


}

250

Capítulo 8 ■ Programação multithread, assíncrona e paralela


// Saída thislockA
de tsk1 thislockB
de tsk2
/ * Aplicativo congelado * /

Explicação
Aqui está como o aplicativo ficou congelado.
\ 1. \ Tsk1 adquire o bloqueio “thislockA”.
\ 2. \ Tsk2 adquire o bloqueio “thislockB”.
\ 3. \ Tsk1 tenta adquirir o bloqueio "thislockB", mas ele já é mantido pelo
Tsk2 e, portanto, o Tsk1 bloqueia até que "thislockB" seja liberado.
\ 4. \ Tsk2 tenta adquirir o bloqueio "thislockA", mas é mantido pelo
Tsk1 e, portanto, o Tsk2 bloqueia até que "thislockA" seja liberado.
Nesse ponto, os dois threads estão bloqueados e nunca serão ativados. Portanto, o aplicativo
congelou.
Para impedir que um aplicativo congele, é importante usar uma instrução de bloqueio com
cuidado; caso contrário, você atirará no seu próprio pé.

CancelamentoToken
CancellationToken propaga uma notificação de cancelamento para operações como threads,
itens de trabalho do conjunto de threads ou objetos de tarefa.
O cancelamento ocorre ao solicitar um código que chama o método
CancellationTokenSource.Cancel e o delegado do usuário finaliza a operação. No entanto, uma
operação pode ser finalizada:
\ 1. \ simplesmente retornando do delegado;
\ 2. \ chamando o método CancellationTokenSource.Cancel .
A seguir, são apresentadas etapas gerais para a implementação do modelo de cancelamento:
\ 1. \ Instanciar um CancellationTokenSource .
\ 2. \ Obtenha um CancellationToken da propriedade CancellationTokenSource.Token.
\ 3. \ Passe o CancellationToken para cada tarefa ou thread que escuta o cancelamento.
\ 4. \ Forneça um mecanismo para cada tarefa ou thread para responder ao cancelamento .
\ 5. \ Chame o método CancellationTokenSource.Cancel para fornecer
uma notificação de cancelamento.

Fragmento de código

Listagem 8-37.  Solicitar um thread para cancelar sua execução

using System;
using System.Threading;
using
System.Threading.Tasks;

Programa de aula
{

251

Capítulo 8 ■ Programação multithread, assíncrona e paralela


estático vazio Main ()
{
// 1 - Instancia uma fonte de token de cancelamento
CancellationTokenSource source = new
CancellationTokenSource ();

// 2 - Obter token da propriedade


CancellationTokenSource.Token CancellationToken token
= source.Token ;

// 3 - Passa o token para a


tarefa Task tsk =
Task.Run (() =>
{
Console.WriteLine ("Olá de tsk");
enquanto (verdadeiro)
{

Thread.Sleep (1000);

Console.WriteLine ("*");

if ( token.IsCancellationRequested == true)
{
Console.WriteLine ("Tchau de tsk");
Retorna;
}

}, símbolo);

Console.WriteLine ("Olá do thread principal");


//Esperar
Thread.Sleep (4000);

// 4 - notificar para cancelamento


source.Cancel (); // IsCancellationRequested = true;

//Esperar
Thread.Sleep (1000);

Console.WriteLine ("Tchau do thread principal");

}
//Resultado
Olá do tópico principal
Olá do tsk
*
*
*
*

252

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Tchau de tsk
Tchau da discussão principal

Explicação
• Tsk.Run () continuará sua operação até que IsCancellationRequested se torne verdadeiro.
• IsCancellationRequested se torna verdadeiro quando o método
source.Cancel () é chamado no thread principal após 4 segundos.

Tornando a interface do usuário responsiva


Em qualquer aplicativo GUI do .NET (Windows Form, WPF, ASP.NET etc.), a Interface do
Usuário (UI) não responde quando uma operação complexa e demorada é executada
durante um evento.
Um thread da interface do usuário (interface do usuário ) gerencia o ciclo de vida dos
controles da interface do usuário (botões, caixa de texto etc.), e é comumente usado para
manipular entradas do usuário e responder a eventos do usuário.
Antes de mergulharmos no tópico, devemos executar as seguintes etapas:
\ 1. \ Crie um projeto de formulário do Windows em C # vazio. (Você
pode criar qualquer aplicativo GUI, como WPF, ASP.NET etc.)
\ 2. \ Na caixa de ferramentas, arraste um botão e um rótulo para o formulário principal.
\ 3. \ Clique duas vezes no botão para gerar o código para o evento click.
Figura 8-8.  Arraste o botão e o rótulo no aplicativo de formulário do Windows vazio

No evento click no botão, simulamos uma operação demorada usando o método Thread.Sleep.

253

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Listagem 8-38.  Executar uma operação demorada em um thread da interface do usuário
private void button1_Click (remetente do objeto, EventArgs e)
{
// Aguarde 5
segundos
Thread.Sleep (5000);

label1.Text = "Olá, mundo";

Quando executamos o código acima e clicamos no botão1, o aplicativo desliga. Até que o
encadeamento da interface do usuário esteja ocupado executando comandos demorados , ele
não poderá responder a nenhum comando adicional do usuário, por exemplo, arrastando a
janela do aplicativo e clicando no botão Fechar, etc.

Como tornar a interface do usuário responsiva com Async e Await


No .NET Framework 4.5, as palavras-chave assíncronas e aguardam foram introduzidas. Eles
tornam a programação assíncrona muito mais simples e fornecem uma maneira mais simples
de tornar a interface do usuário responsiva.
Para tornar a interface do usuário responsiva, é essencial não executar operações
complicadas e demoradas em um thread da interface do usuário. Em vez disso, essas operações
demoradas devem ser executadas em tarefas separadas, controladas por async e aguardar
palavras-chave. Fazendo isso, o encadeamento da interface do usuário se torna gratuito e
disponível para responder a qualquer entrada do usuário.

As etapas a seguir são essenciais para executar qualquer método de forma assíncrona:
\ 1. \ O tipo de retorno de um método de evento não muda. Mas o tipo
de retorno de um método normal deve mudar para Tarefa <tipo de
retorno>.
\ 2. \ Você deve usar a palavra-chave “async” antes do return_type / Task
<return_type> de qualquer método.
\ 3. \ Você deve usar a palavra-chave “aguardar” quando um método é
chamado cujo tipo de retorno é “Tarefa / Tarefa <T>”.

Executar evento de clique de forma assíncrona


É importante descobrir quais comandos estão demorando mais tempo. Coloque esses
comandos em um método separado, cujo tipo de retorno é Tarefa / Tarefa <int>.
Em seguida, use a palavra-chave "assíncrona" em sua assinatura e escreva a palavra-chave
"aguardar" antes que o método do tipo "Tarefa" seja chamado.
Sintaxe

private assync void button1_Click (remetente do objeto, EventArgs e)


{

aguarde DoComplicatedTaskAsync ();


}

254

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Fragmento de código

Listagem 8-39.  Use async e aguarde em um método de evento

private assync void button1_Click (remetente do objeto, EventArgs e)


{
label1.Text = "Olá, mundo";

aguarde DoComplicatedTaskAsync ();

label1.Text = "Tchau, mundo";

tarefa privada DoComplicatedTaskAsync ()


{
Tarefa tarefa = Task.Run (() =>
{
Thread.Sleep (5000);
});

tarefa de retorno;
}

Explicação
(Listagem 8-39 ) O thread da interface do usuário exibirá "Hello World" em um rótulo e
aguarde até que DoComplicatedTaskAsync () esteja em execução. Enquanto isso, o thread da
interface do usuário permanece responsivo a qualquer entrada do usuário. Depois que
DoComplicatedTaskAsync () for concluída, o thread da interface do usuário exibirá "Bye World"
no rótulo.
DoComplicatedTaskAsync () é um método que retorna uma tarefa que executa uma
operação demorada . O nome de um método assíncrono, por convenção, termina com o sufixo
"Async".
o async é usado para qualificar um método como um método assíncrono.
waitit é semelhante ao método Wait, mas não interrompe o encadeamento da interface do
usuário e pode retornar um valor se uma tarefa tiver algum valor para retornar. É importante
observar que a palavra-chave wait não pode funcionar em um método que não esteja marcado
com uma palavra-chave assíncrona.
255

Capítulo 8 ■ Programação multithread, assíncrona e paralela

Figura 8-9.  Fluxo de trabalho do método assíncrono

Executar método normal de forma assíncrona


Em qualquer método normal, é importante descobrir quais operações são demoradas e
executá-las em uma tarefa separada.
Fragmento de código

Listagem 8-40.  Use assíncrono e aguarde um método normal


private assync void button1_Click (remetente do objeto, EventArgs e)
{

label1.Text = "Olá, mundo";

aguarde normal_methodAsync ();

label1.Text = "Tchau, mundo";

Tarefa assíncrona privada normal_methodAsync ()


{
aguarde DoComplicatedTaskAsync ();

256

Capítulo 8 ■ Programação multithread, assíncrona e paralela


tarefa privada DoComplicatedTaskAsync ()
{
Tarefa tarefa = Task.Run (() =>
{
Thread.Sleep (3000);

});

tarefa de retorno ;
}

Explicação
(Listagem 8-39 ) O thread da interface do usuário exibirá “Hello World” no label1 e aguarde até
que normal_methodAsync () esteja em execução. Enquanto isso, o thread da interface do
usuário permanece responsivo a qualquer entrada do usuário. normal_methodAsync é um
método assíncrono; ele executa DoComplicatedTaskAsync em uma tarefa separada e aguarda
sua conclusão. Depois de concluir a execução, normal_methodAsync retorna ao evento click em
que foi chamado. Em seguida, o thread da interface do usuário exibe "Bye World" no rótulo1.
Pontos importantes
normal_methodAsync ( ) não retorna nenhum valor; use "Tarefa" como um tipo de retorno.
Quando "async" é usado com métodos normais, "void" não pode ser usado como um tipo de
retorno; usaremos "Tarefa".
O método DoComplicatedTaskAsync () usou "Task" como seu tipo de retorno. Portanto,
em seu corpo, o método deve retornar um objeto do tipo "Tarefa".
normal_methodAsync e DoComplicatedTaskAsync Ambos os métodos têm um tipo de
retorno de "Tarefa". Mas, no corpo deles, apenas o método DoComplicatedTaskAsync retorna
um objeto do tipo "Tarefa" porque sua assinatura não marca com a palavra-chave "async".

Use Aguardar para obter valor da tarefa <T>


A palavra-chave aguardar funciona como o método Wait, mas também pode ser usado para obter valor
da tarefa <T>.
Fragmento de código

Listagem 8-41.  Use wait para retornar um valor de um método Task <T>

private assync void button1_Click (remetente do objeto, EventArgs e)


{

label1.Text = "Olá, mundo";

int valor = aguardar DoComplicatedTaskAsync ();

label1.Text = "Bye World" + valor.ToString ();


}

Tarefa privada <int> DoComplicatedTaskAsync ()


{
Tarefa <int> task = Task.Run (() =>
{

257

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Thread.Sleep
(5000); retorno 15;
});

tarefa de retorno ;
}

Explicação
aguardar palavra-chave aguarda a conclusão de DoComplicatedTaskAsync. A assinatura do
método de DoComplicatedTaskAsync possui "Task <int>"; o método retorna uma tarefa que
retorna um valor "15" após aguardar 5 segundos. A palavra-chave Aguardar retorna o valor
"15" da tarefa do método DoComplicatedTaskAsync (). O thread da interface do usuário
exibirá "Bye World15" no rótulo1.

Use o Async Lambda


a expressão lambda assíncrona ajuda a criar um método assíncrono anônimo. Para usar uma
expressão lambda assíncrona, precisamos delegar. Seu tipo de retorno depende do tipo de
delegado.
Sintaxe

( assíncrono () =>
{
aguarde ;
});

Fragmento de código

Listagem 8-42.  Usar lambda assíncrona

private assync void button1_Click (remetente do objeto, EventArgs e)


{
label1.Text = "Olá, mundo";

Func <Tarefa > asyncLambda = ( async () =>


{
aguarde myWait (5000);
});

aguarde asyncLambda ();

label1.Text = "Tchau, mundo";


}

Tarefa privada myWait (int milisec)


{
Tarefa tarefa = Task.Run (() =>
{
Thread.Sleep (milissegundos);
});

tarefa de retorno;
}
258

Capítulo 8 ■ Programação multithread, assíncrona e paralela


Explicação
(Listagem 8-42 ) No corpo do async lambda, é essencial usar uma palavra-chave wait . Para
executar o lambda assíncrono, é necessário um delegado, como Func <Task>. o delegado
asynclambda foi chamado com uma palavra-chave wait; até que o asynclambda esteja em
execução, o thread da interface do usuário aguardará e permanecerá responsivo.

Task.Delay (milissegundos)
Ao contrário do Thread.Sleep, o Task.Delay não bloqueia o segmento atual. Em vez disso,
gera um atraso lógico por um período especificado. Task.Delay destina-se a ser executado
de forma assíncrona. Aguardar é usado com Task.Delay porque retorna uma tarefa.
Fragmento de código

Listagem 8-43.  Use Task.Delay para manter a execução de uma tarefa


private assync void button1_Click (remetente do objeto, EventArgs e)
{
label1.Text = "Olá, mundo";

aguarde Task.Delay (3000);

label1.Text = "Tchau, mundo";


}

Explicação
(Na Listagem 8-43 ) Após 3 segundos, o encadeamento da interface do usuário exibirá “Bye
World” no label1.text, mas, enquanto isso, permanece responsivo.

Impedir que o aplicativo cruze a linha


Em um ambiente multithread, apenas um thread da interface do usuário pode alterar o valor
dos controles da interface do usuário (botão, rótulo, caixa de texto etc.). Se outro
encadeamento tentar alterar o valor de um controle de interface do usuário, uma exceção de
encadeamento cruzado surgirá porque o Runtime não permitirá que nenhum encadeamento
manipule diretamente outros dados de encadeamento.

Listagem 8-44.  Exemplo de rosca cruzada


private assync button1_Click (remetente do objeto, EventArgs e)
{
Tarefa tarefa = Task.Run (() =>
{
label1.Text = "Olá, mundo";
Thread.Sleep (3000);

label1.Text = "Tchau, mundo";


});

aguardar tarefa;
}

When the above code runs, an exception will arise which says, “Cross-thread operation not valid”.

259

Chapter 8 ■ Multithreaded, Async & Parallel Programming


this.BeginInvoke
BeginInvoke method is used to change values of UI control from other threads. It does it in a
thready-safe way. It requires a delegate; it tells which UI control needs to change its value.

Listing 8-45.  Use lock to thread-safe a shared resource


private async void button1_Click(object sender, EventArgs e)
{
Task task = Task.Run(() =>
{
this.BeginInvoke(new Action(() =>
{
label1.Text = "Hello";
}));

});

await task;
}

The value of label1.Text shall be changed to “Hello” and no exception will arise because
it’s a thread- safe operation.
Parallel Programming
In the modern era, computers and workstations have at least two or four cores that help
multiple threads to execute simultaneously. .NET provides easy ways to handle multiple
threads on multiple cores.
In .NET, you can take advantage of parallelism by:
\ 1.\ Concurrent Collection
\ 2.\ Parallel.For & Parallel.Foreach
\ 3.\ PLINQ

Concurrent Collection
In a multithreaded enviroment, multiple threads can access the same data at the same time to
read/add/edit it. Such data aren’t thread-safe and become vulnerable to multiple threads.
In C# we have Generic collections. These collections are type-safe, which means at compile
time we can make a collection of any type. But these collections are not thread-safe. They
become vulnerable when multiple threads can manipulate the same data at the same time.
Generic collections can also become thread-safe if they are used in a proper locking
statement, but locking the entire collection for the sake of adding/removing an item could be a
big performance hit. .NET has its own thread-safe collection called Concurrent collection. It was
introduced in .NET 4.0. It contains the following thread-safe collections defined in the
System.Collections.Concurrent namespace.
\ 1.\ ConcurrentDictionary<K,V>: Thread-safe dictionary in key value pairs
\ 2.\ ConcurrentQueue<T>: Thread-safe FIFO data structure

260

Chapter 8 ■ Multithreaded, Async & Parallel Programming


\ 3.\ ConcurrentStack<T>: Thread-safe LIFO data structure
\ 4.\ ConcurrentBag<T>: Thread-safe implementation of an unordered collection
\ 5.\ BlockingCollection<T>: Provides a Classical Producer Consumer pattern

Code Snippet

Listing 8-46.  Use generic collection in multiple threads

using System.Collections.Generic;
using System.Threading.Tasks;

class Program
{

static void Main()


{
Dictionary<int, int> dic = new Dictionary<int, int>();

Task tsk1 = Task.Run(() =>


{
for(int i = 0; i < 100; i++)
{
dic.Add(i, i + 1);
}
});

Task tsk2 = Task.Run(() =>


{
for (int i = 0; i < 100; i++)
{
dic.Add(i + 1, i);
}
});

Task[] allTasks = { tsk1, tsk2 };


Task.WaitAll(allTasks); // Wait for all tasks

}
//Output
/* System.AggregateException accur because 'an item with the same key has already been
added'. */

Explanation
(In Listing 8-46) Tsk1 and Tsk2 both tried to manipulate the key of dictionary, hence an error occurs.

261

Chapter 8 ■ Multithreaded, Async & Parallel Programming


Code Snippet

Listing 8-47.  Use Concurrent Collection to prevent multiple threads from accessing a
resource at the same time
using System.Collections.Concurrent;
using System.Threading.Tasks;

class Program
{

static void Main()


{
ConcurrentDictionary<int, int> dic =
new ConcurrentDictionary<int, int>();

Task tsk1 = Task.Run(() =>


{
for(int i = 0; i < 100; i++)
{
dic.TryAdd(i, i + 1);
}
});

Task tsk2 = Task.Run(() =>


{
for (int i = 0; i < 100; i++)
{
dic.TryAdd(i + 1, i);
}
});

Task[] allTasks = { tsk1, tsk2 };


Task.WaitAll(allTasks); // Wait for all tasks

System.Console.WriteLine("Program ran succussfully");


}

}
//Output
Program ran succussfully
Explanation
ConcurrentDictionary<K,V> is a thread-safe collection; in the above code snippet, it prevents
multiple threads from working on the same key value. If another thread tried to add a new key
value which was already added by another thread, it would skip the iteration and move the
control to the next iteration. This way, no conflict would occur and hence the program would
run succussfully.

262

Chapter 8 ■ Multithreaded, Async & Parallel Programming


Similarly, there are other concurrent collections, like:
\ 1.\ ConcurrentQueue<T>, it has Enque() method to enque an item and
TryDeque() method to remove and return the first item.
\ 2.\ ConcurrentStack< T> it has Push() method to push an item and
TryPop() method to remove and return the last item.
\ 3.\ ConcurrentBack<T> it has Add() method to add an item and TryTake()
method to remove and return the item.

But Generics are not thread-safe it’s a programmer’s responsibility. Let’s say you have a list
collecting some objects. That list is shared amongst several threads; then it may work
hazardously if two threads try to access the List at the same point in time, like
adding/removing/iterating items from the same list at the same time.

Thread safety can be implemented with the help of locking the collection and other
similar ways. But locking the entire list for the sake of adding/removing an item could be a
big performance hit for an application based on the circumstances.

Parallel.For & Parallel.Foreach


Parallel.For and Parallel.Foreach are used in parallel programming to iterate statements
over multiple threads.

Parallel.For
It is used to iterate a for loop upon multiple threads and processors. In most cases, Parallel.For
loop is much faster than a normal for loop.
Syntax

Parallel.For(fromInclusive, toExclusive, Action<int> body);

Code Snippet

Listing 8-48.  Use Parallel.For


using System;
using System.Threading.Tasks;

class Program
{
static void Main()
{
Parallel.For(1, 5, (i) =>
{
Console.WriteLine(i);
});

}
}

263

Chapter 8 ■ Multithreaded, Async & Parallel Programming


//Output
1
3
4
2
/* output will be different when you run the same code, because in a multithreaded
enviroment, the scheduler decides which thread should run first */

Parallel.Foreach
It is used to iterate a foreach loop upon multiple threads and processors. In most cases,
Parallel.Foreach loop is much faster than a normal foreach loop.
Syntax

Parallel.ForEach<T>(collection<T> data, Action<T> body);

Code Snippet

Listing 8-49.  Use Parallel.Foreach

using System;
using System.Threading.Tasks;

class Program
{
static void Main()
{
int[] data = { 1, 2, 3, 4, 5 };

Parallel.ForEach<int>(data, (d) =>


{
Console.WriteLine(d);
});

}
//Output
1
3
4
2
5
/* output will be different when you run the same code, because in a multithreaded
enviroment the scheduler decides which thread should run first */

264

Chapter 8 ■ Multithreaded, Async & Parallel Programming


PLINQ
PLINQ is the parallel version of LINQ. It means queries can be executed on multiple threads by
partitioning the data source into segments. Each segment executes on separate worker threads
in parallel on multiple processors. Usually, parallel execution significantly runs faster than
sequential LINQ. However, parallelism can slow down the execution on complicated queries.
It has the following common methods to help in parallelism:
\ 1.\ AsParallel() Divide the data source in segments on multiple threads
\ 2.\ AsSequential() Specify the query shall be executed sequentially
\ 3.\ AsOrdered() Specify the query shall preserve the ordering of data
\ 4.\ AsUnordered() Specity the query shall not preserve the ordering of data
\ 5.\ ForAll() Process the result in parallel

Code Snippet

Listing 8-50.  Run a LINQ query in parallel by using “AsParallel()”

using System;
using System.Linq;

class Program
{

static void Main()


{
var data = Enumerable.Range(1, 50);

//split source in segments on multiple threads


//by using AsParalletl() with source 'data' var
plinq = from d in data.AsParallel()
where d % 10 ==
0 select d;

foreach (var item in plinq)


{
Console.WriteLine(item);
}

}
//Output
10
20
30
40
50

265

Chapter 8 ■ Multithreaded, Async & Parallel Programming


Explanation
The above code snippet (Listing 8-50) tells how to make a sequential LINQ to a PLINQ by
splitting the source into segments on multiple threads by using the AsParallel() method. No
doubt its speed is faster than a sequential LINQ query.

Summary
• Monitor.Enter or Monitor.TryEnter method is used to lock a block of
code for other threads and prevent other threads from executing it.
• A thread controls the flow of an executable program.
• By default, a program has one thread called Main Thread. Main Thread
starts when control enters in the Main method and it terminates when
Main method returns.
• Application has two kinds of threads: Background Thread and Foreground Thread.
• Background thread doesn’t hold the main thread to finish its execution. If
the main thread completes its execution it will terminate the progam.
• Foreground thread does hold the main thread to terminate the
progam until foreground completes its execution.
• Threadpriority defines how much CPU time a thread will have for execution.
• ThreadStatic is an attribute used on top of a static field to make its
value unique (local) for each thread.
• A thread pool is a collection of background threads, created by a
system and is available to perform any task when required.
• Task doesn’t create new threads. Instead it efficiently manages
threads of a threadpool.
• Task.Run manages Task more efficiently than Task.Factory.StartNew.
• Task.ContinueWith method is used to run multiple Tasks in a chain
only when a specified condition satisfies.
• Lock is a C# keyword; it prevents a thread from executing the same
block of code that another thread is executing.
• CancellationToken propagates a notification that operations (threads,
thread pool work items, or task objects) should be canceled.
• UI (user-interface) thread manages the life cycle of UI controls (buttons,
textbox, etc.), and it is commonly used to handle user inputs and
respond to user events.
• async and await keywords are used to make the UI of an application responsive.
• async lambda expression helps to create an anonymous async method.
• Task.Delay doesn’t block the current thread. Instead it makes a logical
delay for a specified period of time. It is better than using Thread.Sleep
for an asynchronous operation.
• this.BeginInvoke method is used to send value to controls (button,
textbox, etc.) of UI Threads.

266

Chapter 8 ■ Multithreaded, Async & Parallel Programming


• Concurrent Collections are thread-safe collections.
• Parallel.For and Parallel.Foreach are used to iterate loops upon
multiple threads and processors.
• PLINQ is the parallel version of LINQ. By using “AsParallel” method,
LINQ query divides its data source on multiple threads.

Code Challenges
Challenge 1: Develop a Windows Form Project to Display HTML
Develop a simple Windows Form Project in C# which gets the HTML of “google.com” and
displays it on a label.

Application must have these UI Controls:


\ 1.\ A Textbox to enter URL
\ 2.\ A Label to display result
\ 3.\ A button for downloading HTML and displaying it on
a label Your goals are:
\ 1.\ The application must be responsive and use async and await.
\ 2.\ Download the HTML of a URL and show it on a label.

Practice Exam Questions


Question 1
Suppose an application has a method name PrintAsterisk() that prints asterisks on a screen
continuously. The method runs on a Task separate from the user interface. The application
includes the following code. (Line numbers are included for reference only.)

01. static void PrintAsterisk(CancellationToken token)


02. {
03. while(!token.IsCancellationRequested)
04. {
05. Thread.Sleep(100);
06. Console.Write(" *");
07. }

09. }
10. private static void Main()
11. {
12. var tokenSource = new CancellationTokenSource();
13. var task = Task.Run(() => PrintAsterisk(tokenSource.Token));
14. Console.WriteLine("Press [Enter] to stop printing Asterisk");
15. Console.ReadLine();
16.
17. task.Wait();
18. }

267

Chapter 8 ■ Multithreaded, Async & Parallel Programming


You need to ensure that the application stop printing the Asterisk on screen when the user
presses the Enter key. Which code segment should you insert at line 16?
\ A)\ tokenSource.Token.Register( () => tokenSource.Cancel() );
\ B)\ tokenSource.Cancel(); Ans
\ C)\ tokenSource.IsCancellationRequested = true;
\ D)\ tokenSource.Dispose();

Question 2
Suppose an application uses multiple asynchronous tasks to optimize performance. The
application will be deployed in a distributed environment. You need to get the result of an
asynchronous task from a web service.

The data will later be parsed by a separate task. Which code segment should you use?

A)
protected async void StartTask()
{
string result = await GetData();
...
}

public Task<string> GetData()


{
...
}
B)
protected async void StartTask()
{
string result = await GetData();
...
}

public async Task<string> GetData()


{
...
}

C)
protected async void StartTask()
{
string result = GetData();
...
}

public Task<string> GetData()


{
...
}

268

Chapter 8 ■ Multithreaded, Async & Parallel Programming


D)
protected async void StartTask()
{
string result = async GetData();
...
}

public Task<string> GetData()


{
...
}

Question 3
Identify a correct way to implement locking.

A)
//lockthis, is a private static variable of type object. lock
(lockthis)

{
...
}

B)
lock (new object())
{
...
}

C) lock
()
{
...
}

D)
lock (this)
{
...
}

269

Chapter 8 ■ Multithreaded, Async & Parallel Programming


Question 4
An application uses multiple asynchronous tasks to optimize performance. You create three
tasks by using the following code segment. (Line numbers are included for reference only.)

01. private void MultipleTasks()


02. {
03. Task[] tasks = new Task[]
04. {
05. Task.Run(()=>Thread.Sleep(2000)),
06. Task.Run(()=>Thread.Sleep(3000)),
07. Task.Run(()=>Thread.Sleep(1000)),
08. };

10. ...
11. }

You need to ensure that the MultipleTasks () method waits until all three tasks
complete before continuing. Which code segment should you insert at line 09?
\ A)\ task.WaitFor(3);
\ B)\ tasks.Yield();
\ C)\ tasks.WaitForCompletion();
\ D)\ Task.WaitAll(tasks);

Question 5
Which of the following methods is used to run a LINQ query in parallel?
\ A)\ AsParallel();
\ B)\ RunParallel();
\ C)\ ToParallel();
\ D)\ Parallel();

Answers
\ 1.\ B
\ 2.\ B
\ 3.\ A
\ 4.\ D

\ 5.\ A
270

CHAPTER 9

Exception Handling and


Validating Application Input

Introduction to Exception
Exception is an unexpected error that occurs at runtime (when an application is running).
Sometimes a programmer doesn’t know what and which exception could occur at runtime.
For example, the code is reading a file but the file is missing from the location where it is
read. It is accessing or reading data across the network but the Internet is not available, or it
is loading some kind of data in memory but the system runs out of memory, etc. In all these
cases, programmers write the code right but, due to unconditional ways, an exception could
occur.
If exceptions are not handled properly, they can break the execution of a running
program. To handle an exception, we write the suspicious code inside a try-catch block. When
an exception is caught, its object can be use to read the detail of an exception, for example,
error message and exception Stack, etc.
In terms of programming, an exception is a C# class (System.Exception). A
developer can create a custom-exception by inheriting System.Exception class and can
customize it accordingly.
A custom-exception is useful when a developer wants to provide his own error messages
to C# code. The following is a list of some common .NET exceptions that may occur at
runtime.
• System.Exception, is either thrown by a system or a running application to
report an error.
• InvalidOperationException, is thrown when the state of an object
cannot invoke a method or execute an expression.
• ArgumentException, is thrown when a method is invoked and one of its
parameters doesn’t meet the specification of a parameter.
• ArgumentNullException, is thrown when a method is invoked and
one of its paremeter arguments is null.
• ArgumentOutOfRangeException, is thrown when the value of an
argument is outside the range of values as defined by the type of the
arguments of the invoked method.
• NullReferenceException, is thrown when you try to use a reference
which is not initialized, or try to access a member of a type which is not
initialized in memory.
• IndexOutOfRangeException, is thrown when an index of an array
tries to access something which is outside of the array’s range.
© Ali Asad and Hamza Ali 2017 271
A. Asad and H. Ali, The C# Programmer’s Study Guide (MCSD), DOI 10.1007/978-1-4842-2860-9_9

Chapter 9 ■ Exception Handling and Validating Application Input


• StackOverflowException, is thrown when the Stack has too many nested
methods and it cannot add more methods to execute it.
• OutOfMemoryException, is thrown when there is not enough
memory to run a program.
• ArithmeticException, is thrown when there is an error in an arithmetic operation.
• DivideByZeroException, is thrown when there is an attempt to divide an
integral or decimal value with zero.
• OverflowException, is thrown when an arithmetic operation returns a
value that is outside the range of the data type.
• IOException, is thrown when there is an error in an IO operation.
• DirectoryNotFoundException, is thrown when there is an attempt to
access a directory that is not found in the system.
• FileNotFoundException, is thrown when there is an attempt to access a
file that is not found in the system.
• SqlException, is thrown when an sql server returns a warning or error.

Handling Exception
An exception can be handled by writing a code (that might throw an error at runtime) inside a
try-catch or a try-catch-finally block.

try-catch
try-catch are two separate blocks that come together to handle an exception. In try block, we
write the suspicious code that may throw an exception at runtime; use catch block to handle
an exception thrown by the code written in try block as shown in the following Listing 9-1
Syntax
try
{
//Write Suspicious Code
}
catch
{

//Do something when exception occurs


}

Code Snippet

Listing 9-1.  Use try-catch to handle an exception

using System;

namespace DemoProject
{

272

Chapter 9 ■ Exception Handling and Validating Application Input


class Program
{
static void Main(string[] args)
{
int[] numbers = new int[2];

try
{
numbers[0] = 0;
numbers[1] = 1;
numbers[2] = 2;

foreach (int i in numbers)


{
Console.WriteLine(i);
}
}
catch
{
Console.WriteLine("An exception is thrown");
}
}
}
}
//Output
An exception is thrown

Explanation
The above code (Listing 9-1) handles the exception and didn’t break the execution of the running
program.

try-catch (ExceptionType ex)


try-catch are two separate blocks that come together to handle an exception. In try block, we
write the suspicious code that may throw an exception at runtime; use catch (ExceptionType
ex) block to handle the object of a specific type of exception.
Syntax
try
{
//Write Suspicious Code
}
Catch (ExceptionType ex)
{

//Do something when specified type of exception occurs


}

273

Chapter 9 ■ Exception Handling and Validating Application Input


Code Snippet

Listing 9-2.  Use try-catch (ExceptionType ex) to handle a specific exception instance
using System;

namespace DemoProject
{
class Program
{
static void Main(string[] args)
{
int[] numbers = new int[2];

try
{
numbers[0] = 0;
numbers[1] = 1;
numbers[2] = 2;

foreach (int i in numbers)


{
Console.WriteLine(i);
}
}
catch (IndexOutOfRangeException ex)
{
Console.WriteLine("Error Message: {0}", ex.Message);
}
}
}
}
//Output
Error Message: Index was outside the bound of the array.

Explanation
The catch block handled the object of IndexOutOfRangeException that was thrown by try block.
ex.Message property is used to display the error message of the exception.

Code Snippet

Listing 9-3.  Use try-catch (Exception ex) to handle an all exception instance

using System;

namespace DemoProject
{
class Program
{
static void Main(string[] args)
{
int[] numbers = new int[2];

274

Chapter 9 ■ Exception Handling and Validating Application Input


try
{
numbers[0] = 0;
numbers[1] = 1;
numbers[2] = 2;

foreach (int i in numbers)


{
Console.WriteLine(i);
}
}
catch (Exception ex)
{
Console.WriteLine("Error Message: {0}", ex.Message);
Console.WriteLine("ExceptionType: {0}", ex.GetType());
}
}
}
}
//Output
Error Message: Index was outside the bound of the array.
ExceptionType: System.IndexOutOfRangeException

Explanation
System.Exception is a base class of an all exception type, so we can use catch (Exception ex) to
handle those exceptions whose types are unknown to us. When the exception is handled in
catch {}, we can use ex. GetType() method to get the type of exception. Also, it is the best practice
is to write Exception class (in catch block) because Exception is something that can occur for
many reasons.

try-catch (ExceptionType)
try-catch are two separate blocks that come together to handle an exception. In try block, we
write the suspicious code that may throw an exception at runtime; in catch (ExceptionType)
block we handle the specific type of exception. However, in catch (ExceptionType) we can’t hold
the reference of an exception object.
Syntax
try
{
//Write Suspicious Code
}
Catch (ExceptionType)
{

//Do something when specific type of exception occurs


}

275

Chapter 9 ■ Exception Handling and Validating Application Input


Code Snippet

Listing 9-4.  Use try-catch (ExceptionType) to handle a specific exception

using System;

namespace DemoProject
{
class Program
{
static void Main(string[] args)
{
int[] numbers = new int[2];

try
{
numbers[0] = 0;
numbers[1] = 1;
numbers[2] = 2;

foreach (int i in numbers)


{
Console.WriteLine(i);
}
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("Index out of bound exception is thrown");
}
}
}
}
//Output
Index out of bound exception is thrown

Explanation
Catch block handles the exception of type IndexOutOfRangeException. The catch block cannot
hold the reference of an IndexOutOfRangeException object. Under catch block we write the
code to execute when a specific type of exception is raised.

try-catch-finally
try-catch-finally is a full version of handling exception in a better way. It comprises of three blocks:
• try{}, is used to write a block of code that may throw an exception.
• catch{}, is used to handle a specific type of exception.
• finally {}, is used to to clean up actions that are performed in a try block.
finally block is always run at the end, regardless of whether an exception is thrown or
a catch block matching the exception type is found.

276

Chapter 9 ■ Exception Handling and Validating Application Input


Syntax
try
{
// Code to try goes here.
}
catch (ExceptionType ex)
{
// Code to handle the exception goes here.

}
finally
{
// Code to execute after the try-catch blocks
// goes here.
}

Code Snippet

Listing 9-5.  Use try-catch-finally to handle an exception gracefully

using System;

namespace DemoProject
{
class Program
{
static void Main(string[] args)
{
int[] numbers = new int[2];

try
{
numbers[0] = 0;
numbers[1] = 1;
numbers[2] = 2;

foreach (int i in numbers)


{
Console.WriteLine(i);
}
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("Index out of bound exception is thrown");
}
finally
{
numbers = null;
Console.WriteLine("Program
Ends");
}
}

277

Chapter 9 ■ Exception Handling and Validating Application Input


}
}
//Output
Index out of bound exception is thrown
Program Ends

Explanation
(In Listing 9-5) Finally block is executed right after the catch block finishes its execution. Usually
finally block is used to free the resources.

try-finally
finally block can be used after try block to release the resources used by code written in try block.
Syntax
try
{
// Code to try goes here.
}

finally
{
// Code to execute after the try blocks
// goes here.
}

Code Snippet

Listing 9-6.  Use try-finally


using System;

namespace DemoProject
{
class Program
{
static void Main(string[] args)
{
int[] numbers = new int[2];

try
{
numbers[0] = 0;
numbers[1] = 1;

foreach (int i in numbers)


{
Console.WriteLine(i);
}
}

278

Chapter 9 ■ Exception Handling and Validating Application Input


finally
{
numbers = null;
Console.WriteLine("Program
Ends");
}
}
}
}
//Output
0
1
Program Ends

Use Multiple Catch Blocks to Handle Multiple Exceptions


After first try{} block, more than one catch block can be used to handle multiple types of
exceptions that can be thrown by try block.
When stacking multiple catch blocks, we use the most specific exception type on the first
catch block and use the least specific exception type at the last catch block.
Syntax
try
{
// Code to try goes here.
}
catch (ExceptionType ex)
{
// Code to handle the exception goes here.

}
catch (ExceptionType ex)
{
// Code to handle the exception goes here.

finally
{
// Code to execute after the try-catch blocks
// goes here.
}

Code Snippet

Listing 9-7.  Use multiple catch blocks to handle multiple exception types
using System;

namespace DemoProject
{
class Program

279
Chapter 9 ■ Exception Handling and Validating Application Input
{
static void Main(string[] args)
{
try
{
Divide(1, 0, "Result = ");
}
catch (DivideByZeroException)
{
Console.WriteLine("Divide by zero exception");
}
catch (NullReferenceException)
{
Console.WriteLine("Null reference exception");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Console.WriteLine("Program Ends");
}
}

private static void Divide(int a, int b, string s)


{
int result = a / b;
Console.WriteLine(s.ToUpper() + result);
}
}
}
//Output
Divide by zero exception
Program Ends

Explanation
Multiple catch blocks are used to catch multiple exception types. When a specified exception
type matches with catch (exceptiontype) it will execute the block and then jump to the finally
block if it exists.

Throwing Exceptions
In C#, an object of exception can be explictly thrown from code by using the throw keyword. A
programmer should throw an exception from code if one or more of the following conditions
are true:
\ 1.\ When method doesn’t complete its defined functionality, for
example, Parameters has null values, etc.
\ 2.\ When an invalid operation is running, for example, trying to write to a
read-only file, etc.

280

Chapter 9 ■ Exception Handling and Validating Application Input


Syntax

throw exception;

Code Snippet
Listing 9-8.  Throw a new exception instance

using System;

namespace DemoProject
{
class Program
{
static void Main(string[] args)
{
try
{
Show(null, 10);
}
catch (ArgumentException ex)
{
Console.WriteLine(ex.Message);
}
}

private static void Show(string fname, int age)


{
if (fname == null)
{
throw new ArgumentException("Parameter cannot be null", "fname");
}

Console.WriteLine(fname + " " + age);


}
}
}
//Output
Parameter cannot be
null Parameter name:
fname

Explanation
Show method has a check statement; if parameter “fname” is null then it throws a
new instance of ArgumentException with a custom message passed to its constructor.
The second parameter of argumentexception shows the name of the parameter which
causes the error, i.e., “fname”.
When the exception caught is a catch block, it shows the message along with the name of
the parameter that causes the exception, i.e., “fname”.

281

Chapter 9 ■ Exception Handling and Validating Application Input


Re-throwing an Exception
If an exception is caught but still wants to throw to be caught again by the calling method, then
use simple throw; for example, you may catch and log an exception and then re-throw it to be
handled by the calling method.
By re-throwing an exception you can preserve the stack trace, which tells where the
exception arised in the first place and where it was re-thrown.
Syntax

throw;

Code Snippet

Listing 9-9.  Re-Throw an exception and preserve stack-trace


using System;

namespace DemoProject
{
class Program
{
static void Main(string[] args)
{
try
{
Show(null, 10);
}
catch (NullReferenceException ex)
{
Console.WriteLine(ex.StackTrace);
}

private static void Show(string fname, int age)


{
try
{
Console.WriteLine(fname.ToUpper() + " " + age);
}
catch (NullReferenceException)
{
//Log the exception message here!
throw;

}
}
}
//Output

at DemoProject.Program.Show(String fname, Int32 age) in


C:\Users\aliso\Source\Repos\demo\ DemoProject\DemoProject\Program.cs:line 29
282

Chapter 9 ■ Exception Handling and Validating Application Input


at DemoProject.Program.Main(String[] args) in
C:\Users\aliso\Source\Repos\demo\ DemoProject\DemoProject\Program.cs:line 12\Call
stack

Explanation
ex.StackTrace shows the exception first arised at Show method in Line 29 and then it was
passed to the Main Method, where it was handled again in line 12. Therefore, by using the
throw keyword we can pass the exception to the calling method to handle the exception.

Throwing an Exception with an Inner Exception


An exception can be thrown along with an inner exception by passing the inner exception in
the second parameter of the newly arised exception. Also, if you throw a new exception with
the initial exception you will preserve the initial stack trace too.
Code Snippet

Listing 9-10.  Throw a new exception with an inner exception and preserve the stack-trace

using System;

namespace DemoProject
{
class Program
{
static void Main(string[] args)
{
try
{
Show(null, 10);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);

Console.WriteLine(ex.InnerException.Message);
}

private static void Show(string fname, int age)


{
try
{
Console.WriteLine(fname.ToUpper() + " " + age);
}
catch (NullReferenceException ex)
{
//Null Reference passed in second parameter of new exception
//so, null reference becomes the inner exception.
throw new Exception("A new exception is arised",ex);

283

Chapter 9 ■ Exception Handling and Validating Application Input


}

}
}
}
//Output
A new exception is arised
at DemoProject.Program.Show(String fname, Int32 age) in C:\Users\aliso\Source\Repos\demo\
DemoProject\DemoProject\Program.cs:line 34

at DemoProject.Program.Main(String[] args) in C:\Users\aliso\Source\Repos\demo\


DemoProject\DemoProject\Program.cs:line 12
Object reference not set to an instance of an object.

Creating Custom Exceptions


In C#, a custom exception can be created by inheriting the System.Exception class.
Generally, custom exceptions are useful in large-scale projects where multiple modules
are talking to each other.
Syntax
class <ClassName> : System.Exception
{
...
}

Code Snippet

Listing 9-11.  Create a custom exception

using System;
namespace DemoProject
{
class MyCustomException : System.Exception
{
//Overload constructor if you want

public MyCustomException(string message) : base(message)


{
//TODO: Provide definition if you want
}
}

class Program
{
static void Main(string[] args)
{
try
{
Show();
}

284

Chapter 9 ■ Exception Handling and Validating Application Input


catch (MyCustomException ex)
{
Console.WriteLine(ex.Message);
}

private static void Show()


{

throw new MyCustomException("It's a custom exception!");


}
}
}
//Output
It's a custom exception!

Explanation
Show method throws a custom exception with a custom message. Main method handled
the custom exception and showed the custom message.

Validating Application Input


The output and result of an application or operation depends upon its input. The input data
must be validated so the application can produce the right results. Most of the time, data can be
validated through a simple if-else statement, but using if-else on each data wouldn’t be easy to
manage. For example, what if the huge list of email ids has to be validated? In that case .NET
provides Regular Expressions to validate string values quickly.

Regular Expressions
.NET Framework provides a regular expression engine to validate a large amount of text by
quickly parsing the text to find a specific character pattern. For example, a regular expression
can be used to validate the pattern for email id, etc.

Character Pattern Cheatsheet


Character pattern defines how a string must be represented; patterns help to validate a string.
The following are some commonly used characters for pattern matching a text in regular
expression.
• *, matches the previous character for zero or more times. E.g.,“bo*”
matches either “b” or “boo”.
• +, matches the previous character for one or more times. E.g., “bo+”
matches either “bo” or “boo”.
• ?, matches the previous element zero or one time. E.g., “Al?i” matches
either “Ai” or “Ali”
• ^, matches the character at the beginning of a string. E.g., “^\d{3}” matches “123-ali”

285

Chapter 9 ■ Exception Handling and Validating Application Input


• $, matches the character at the end of a string. E.g., “\d{3}$” matches “ali-123”
• {n}, matches the previous element for “n” times. E.g., “\d{3}” matches “125”
• x|y, matches either x or y. E.g., “a|bc” matches “a” or “bc”
• [xyz], matches any one of the enclosed characters. E.g., “[ali]” matches “a” in “Fart”
• [^xyz], it’s the negation of all enclosed characters. The matches string must
not have those character sets. E.g., “[^ab]” matches “film”
• \d, matches a digit. Equivalent to [0-9]
• \D, matches a non-digit. Equivalent to [^0-9]
• \s, matches a whitespace, tab, form-feed, etc. Equivalent to [\f\n\r\t\v]
• \S, matches a non-white space. Equivalent to [^\f\n\r\t\v]
• \w, matches a word including an underscore. Equivalent to [A-Za-z0-9]
• \W, matches a non-word character. Equivalent to [^A-Za-z0-9]

Regex
In C#, we use the Regex class of System.Text.RegularExpressions namespace; it represents the
.NET Framework’s regular expression engine. The Regex class contains methods and
properties to validate a text with a specific character pattern; some of them are listed below.
• IsMatch(string input), returns true if the regular expression specified in
the Regex constructor matches with the specified input string.
• IsMatch(string input, int startat), returns true if the regular
expression specified in the Regex constructor matches with the specified
input string and begins at the specified starting position of the string.
• IsMatch(string input, string pattern), returns true if the
specified regular expression matches with the specified input
string.
• Matches(string input), searches the specified input string for all
occurrences of a regular expression.
• Match(string InputStr, string Pattern), matches the input string
with a string pattern.
• Replace(string input, string replacement), in a specified input string,
replaces all strings that match a regular expression pattern with a
specified replacement string.

Code Snippet

Listing 9-12.  Validate phone number with Regular Expression

using System;
using System.Text.RegularExpressions;
namespace DemoProject
{
class Program

286

Chapter 9 ■ Exception Handling and Validating Application Input


{
static void Main(string[] args)
{
//Pattern for Matching Pakistan's Phone Number
string pattern = @"\(\+92\)\s\d{3}-\d{3}-\d{4}";

//Ali's Phone Number


string inputStr = "(+92) 336-071-7272";
bool isMatched = Regex.IsMatch(inputStr, pattern);

if(isMatched == true)
{
Console.WriteLine("Pattern for phone number is matched with inputStr");
}
else
{
Console.WriteLine("Pattern for phone number is not matched with inputStr");
}

}
}
//Output
Pattern for phone number is matched with inputStr.

Explanation
Pattern string contains character set, which makes sure if the input string is according to
Pakistan’s phone number’s pattern. Here is how it works:
@“\(\+92\)\s\d{3}-\d{3}-\d{4}”;

Table 9-1.  Explain Phone number Pattern


Pattern Meaning
   

\(' \(' matches '('


\+' \+' matches '+'
92' 92' matches '92'
\)' \)' matches ')'
\s' \s' matches a space ' '
\d{3}' \d{3}' matches numeric digits for 3 times, its equivalent to '456'
-' -' matches '-'
\d{3}' \d{3}' matches numeric digits for 3 times, its equivalent to '456'
-' -' matches '-'
\d{4} \d{4}' matches numeric digits for 4 times, its equivalent to '4561'
   

287
Chapter 9 ■ Exception Handling and Validating Application Input
Code Snippet

Listing 9-13.  Validate an Email ID with Regular Expression

using System;
using System.Text.RegularExpressions;

namespace DemoProject
{
class Program
{
static void Main(string[] args)
{
//Pattern for Matching an email id
string pattern =
@"^\w+[a-zA-Z0-9]+([-._][a-z0-9]+)*@([a-z0-9]+)\.\w{2,4}";

//Ali's email id
string inputStr = "imaliasad@outlook.com";
bool isMatched = Regex.IsMatch(inputStr, pattern);

if(isMatched == true)
{
Console.WriteLine("Pattern for email id is matched with inputStr");
}
else
{
Console.WriteLine("Pattern for email isn't matched with inputStr");
}

}
}
//Output
Pattern for email id is matched with inputStr.

Explanation

Pattern string contains chararacter set, which makes sure if the input string is according to
the email ID pattern. Here is how it works:

@“^\w+[a-zA-Z0-9]+([-._][a-z0-9]+)*@([a-z0-9]+)\.\w{2,4}”

288

Chapter 9 ■ Exception Handling and Validating Application Input


Table 9-2.  Explain Email ID Pattern
Pattern Meaning
   

^' ^' matches everything from start


\w+ \w+' tells there must be at least one or more alphabets
[a-zA-Z0-9]+ [a-zA-Z0-9]+' tells there must be one or more alphanumeric
[-._] tells there can be any included special character i.e '-._'
([-._][a-z0-9]+)* tells there can be a special character and alphanumeric values

@ @' matches '@'


\. \.' matches a dot '.'
\w{2,4} \w{2,4} tells there must be minimum 2 or maximum 4 words
   

Summary
• Exception is an error that occurs at runtime and may break the
execution of an application.
• try-catch-finally blocks are useful to handle exception gracefully.
• Programatically, an exception can be thrown by using a throw keyword.
• A custom can be created by inheriting the Exception class.
• Regular Expression is useful to validate the large string values with certain patterns.

Code Challenges
Challenge 1: Validate Email, Phone Number, and Website
You’re developing an application that asks the user to enter their
\ 1.\ Email ID
\ 2.\ Phone Number
\ 3.\ Date of Birth
\ 4.\ Zip Code
\ 5.\ Website

Your application must validate their information by using regular expression and
must handle exception in case the user enters invalid values.

289

Chapter 9 ■ Exception Handling and Validating Application Input

Practice Exam Questions


Question 1
How do you throw an exception to preserve stack-trace information?
\ A)\ throw;
\ B)\ throw new Exception();
\ C)\ throw e;
\ D)\ return new Exception();

Question 2
You need to validate a string which has numbers in 333-456 format. Which pattern would you choose?
\ A)\ @“\d\d-\d\d”
\ B)\ @“\n{3}-\n{3}”
\ C)\ @“[0-9]+-[0-9]”
\ D)\ @“\d{3}-\d{3}”

Question 3
Suppose you’re developing an application that require that need to define its own custom
exceptions. Which of the following class you’d inherit to create a custom exception?
\ A)\ Attribute
\ B)\ Exception
\ C)\ IEnumerable
\ D)\ IEnumerator

Answers
\ 1.\ A
\ 2.\ D

\ 3.\ B

290

CHAPTER 10

File I/O Operations


Interacting with files is a common task when developing an application. Sometimes you need to
store some kind of information into files and store that information in a different format (i.e.,
binary or text), or you need to send or access some kind of data over the network. In these
scenarios, a .NET framework provides classes to deal with them.
This chapter will cover the main concepts used to interact with File System and give an
understanding of the working of the following things:
\ 1.\ Drives and Directories
\ 2.\ Files and Streams
\ 3.\ Interaction with Remote Files
\ 4.\ Asynchronous File I/O
The .NET Framework gives the classes to interact with a File I/O that can be found in the
System.IO namespace. This namespace is the collection of the base classes devoted to
file-based and memory-based input and output services.

Working with Drive


Drive or Storage is important to know about when dealing with file system. The .NET
framework provides a class (DriveInfo) to interact with a storage medium, which may be a hard
drive or any other storage (i.e., removable disk). This class gives you information about the
drives, such as the name, size, and free space of the drive. You can also get to know which
drives are available and what kind they are.
Listing 10-1 shows how you can interact with Drives and get to know about them.
Code Snippet

Listing 10-1.  Single Drive info


//Get the Drive
DriveInfo info = new DriveInfo(@"C:\");
Console.WriteLine("Name is: "+info.Name);
Console.WriteLine("Drive Type is: "+info.DriveType);

Explanation
DriveInfo is a class provided by .NET in System.IO, used to interact with Drives. In DriveInfo’s
constructor, the Drive name is passed and you can access its information as shown in Listing
10-1.

© Ali Asad and Hamza Ali 2017 291


A. Asad and H. Ali, The C# Programmer’s Study Guide (MCSD), DOI 10.1007/978-1-4842-2860-9_10

Chapter 10 ■ File I/O Operations


You can also get all the drives and fetch their details using the GetDrives() method (static
method of DriveInfo class). Listing 10-2 shows the use of the GetDrives() method.
Code Snippet

Listing 10-2.  All Drive info


//Get the all the drive
DriveInfo[] driveInfo = DriveInfo.GetDrives();
foreach (var info in driveInfo)
{
Console.WriteLine("Name is: " + info.Name);
Console.WriteLine("Drive Type is: " + info.DriveType);
Console.WriteLine("********************");
}

Working with Directories


Drive contains directories and files. To work with them, DirectoryInfo or Directory (Static Class)
is used. Both the classes can be used to access directory structure. You can access all the folders’
files (or the subfolder), as well as the specific file in the folder or sub-folder using these classes.
You can also create and perform other folder-related operations on a new folder or directory
using these classes.

Directory and DirectoryInfo


Directory Class is a static class performing a single operation. It’s usually used when
performing single task, like just creating a folder. It is preferable to use in such cases.
DirectoryInfo is a non-static class performing multiple operations. It’s usually used when
performing multiple operations/tasks, like creating a folder, then creating sub-folders or
moving them or getting files from that folder. It is preferable to use in such cases.
Listing 10-3 shows how you can create a new folder using these both classes.
Code Snippet

Listing 10-3.  Create Directory


//Create new directory/folder using Directory Class
DirectoryInfo directory = Directory.CreateDirectory("Directory Folder");

//Create new directory/folder using DirectoryInfo Class


DirectoryInfo directoryInfo = new DirectoryInfo("DirectoryInfo Folder");
directoryInfo.Create();

Explanation
As shown from the code, you just need to give a full path along with a folder name where you
want to create the folder as a parameter in the CreateDirectory() method of Directory Class. A
new directory or folder with the name “Directory Folder” will be created. (As you can see, there
is just the name of a folder, i.e., when the path is not given, then by default the folder will be
created in the current directory where you are working). And a newly created directory
returns DirectoryInfo’s object on which you can perform further operations given by a
DirectoryInfo class on a newly created folder like the Exist() method (to check the existence of a
folder), the Delete() method (to delete the folder), or the CreateSubdirectory() method (to create
a subdirectory).

292

Chapter 10 ■ File I/O Operations


The code also shows the creation of a folder by DirectoryInfo Class. Give the name of a
folder or path along with the folder name in DirectoryInfo’s constructor and it will catch the
path where to create the folder and, later, its object’s method: enter Create(), and a new folder
named “DirectoryInfo folder” will be created. After creation, you can perform an operation on
the newly created folder.
Listing 10-4 shows how you can check the existence of a created folder.
Code Snippet

Listing 10-4.  checking the existence of specific directory


//Check Existence of created directory/folder using Directory Class
if(Directory.Exists("Directory Folder"))
{
Console.WriteLine("Directory Folder Exists");
}
//Check Existence of created directory/folder using DirectoryInfo Class if
(directoryInfo.Exists)
{
Console.WriteLine("DirectoryInfo Folder is Exists");
}

Explanation
As shown from code, if Directory Class is used, you have to give explicitly the path of the folder
to perform any of the operations, but when DirectoryInfo Class is used, you just need to call its
properties or functions as DirecotoryInfo’s object already knows where or what the folder is.
You can also delete the folder by using the Delete() method but if the directory is not
found, you will encounter the following exception: DirectoryNotFoundException.
To move the directory from one location to another location is also a common task to use.
Listing 10-5 shows how to move a folder using Directory and DirectoryInfo class.
Code Snippet

Listing 10-5.  Move directory from one location to another


//Using Directory Class
Directory.Move("Directory Folder", "../Moved Directory Folder");

//Using DirectoryInfo Class


directoryInfo.MoveTo("../Moved DirectoryInfo Folder");

Explanation
Move() method is used with Directory Class (Static class), whereas MoveTo() method is used
with DirectoryInfo Class. Move() method requires you to know the source directory path
and the destination directory path, whereas the MoveTo() method just requires the
destination directory path because the DirectoryInfo's object already constrains the
reference of the source directory path.

■■Note  When working with the huge directory structure, use the EnumerateDirectories() method instead of the
GetDirectories() method to fetch the directories, as EnumerateDirectories() start enumerating directories before they have
been completely retrieved (Lazy Loading or Deferred Execution); whereas in the GetDirectories(⁜) case, code would not
move forward until all the list of directories have been retrieved (Immediate Execution).

293

Chapter 10 ■ File I/O Operations

Working with Files


As Directory and DirectoryInfo class allow you to interact with folder structure, File and
FileInfo allow you to interact with files, for example, to create file or to delete a file or check
its existence or the operations or properties provided by File or FileInfo Class.
Directory or DirectoryInfo class is also used to fetch all the files in a specific folder or its
sub-folder or files with specific types (such as images), and File or FileInfo class is used to
access the information of those files or to perform operations on those files.
Listing 10-6 shows how to fetch all the files from a folder/directory using these classes:
Code Snippet

Listing 10-6.  get all files from a specific folder


//Get file from specific directory using Directory Class string[]
fileNames= Directory.GetFiles("Directory Folder"); foreach
(var name in fileNames)
{
Console.WriteLine("Name is:{0}",name);
}
//Get Files from specific directory using DirectoryInfo Class DirectoryInfo
directoryInfo = new DirectoryInfo("DirectoryInfo Folder"); FileInfo[] files=
directoryInfo.GetFiles();

foreach (var file in files)


{
Console.WriteLine("Name is:{0}",file.Name);
Console.WriteLine("Directory Name:{1}",file.DirectoryName);
}

GetFiles() method is an overloaded method and you can use this accordingly.

■■Note  Directory Class will just give you names of files in the provided directory, whereas
DirectoryInfo will return a FileInfo(Class) object on which you can perform file-related operations.

File and FileInfo


Like Directory and DirectoryInfo class, .NET also provided File and FileInfo class to have the
same working context but be used to interact with files.
Listing 10-7 shows some of the operations performed on a specific file.
Code Snippet

Listing 10-7.  Some tasks performed on a specific file


//To Create a file in current location named "File" using File(Static Class)
File.Create("File.txt").Close();

//To Write content in a file named "File" File.WriteAllText("File.txt",


"This is file created by File Class");

294

Chapter 10 ■ File I/O Operations


//To Read the file named "File"
string fileContent= File.ReadAllText("File.txt");
Console.WriteLine(fileContent);

//To Copy "File" from current location to a new one (Previous folder)
File.Copy("File.txt", "../Copied File.txt");

//To Create file in current location named "FileInfo" using FileInfo Class
FileInfo info = new FileInfo("FileInfo.txt");
info.Create();

//To Move "FileInfo" from current location to a new one (Previous Folder)
info.MoveTo("../Moved FileInfo.txt");

As you noticed in the first line of code, Create() method is preceded by the Close() method.
This is due to File (Static Class) performing a single operation which is, in this case, creation of
a file and, after creation of a file, you must close the file before performing another operation
on it. That’s why Close() is called after creation: so that the file could be written.
These operations performed on a file are much like those performed on Directory.
Copy/CopyTo (methods of File/FileInfo) should be used where you want to leave one copy at a
previous location and another/others at a new location; whereas Move/MoveTo (methods of
File/FileInfo) should be used where you do not want to leave a copy at a previous location but
just copy it to a new location. It is just like cut and paste behavior.
When you need to update the content of a file or need to change the content of an already
created file, then you can use the AppendText() method provided by both File and FileInfo
Classes.
Basically, there are two different methods to create a file. These are given below with details:
\ 1.\ Create(): Create or override a file specified in a parameter as a Path
and return FileStream’s object.
\ 2.\ CreateText(): Create or open a file for writing and return StreamWriter’s object.

Working with Stream


Stream is an abstract class used for writing and reading bytes. It is for File I/O operations. When
working with files, it’s important to know about Stream because a file is stored on your
computer hard drive or DVD in a sequence of bytes. When communicating over the network, a
file is transferred in the form of a sequence of bytes. Also, it is stored in memory in the form of a
sequence of bytes.
Stream has three main tasks:
\ 1.\ Writing: Writing means to convert the object or data into bytes and
then store it in memory or a file, or it can be sent across the network.
\ 2.\ Reading: Reading means to read the bytes and convert them into
something meaningful, such as Text, or to deserialize them into an
object.
\ 3.\ Seeking: It is the concept of query for the current position of a cursor
and moving it around. Seeking is not supported by all the streams, i.e.,
you cannot move forward or backward in a stream of bytes that is
being sent over a network.
Stream has the following types:
\ 1.\ FileStream
\ 2.\ MemoryStream
295

Chapter 10 ■ File I/O Operations


\ 3.\ BufferedStream
\ 4.\ NetwrokStream
\ 5.\ CryptoStream

The Stream offers you different kinds of operations, such as Create, Read, and Seek, etc.

FileStream
FileStream drives from the abstract class Stream, mainly used to write and read bytes in the file.

Using FileStream with File/FileInfo Class


Listing 10-8 shows how you can use FileStream when you can write content in a file when you
interact with File Class.
Code Snippet

Listing 10-8.  Use of FileStream with File to write content in a file

FileStream fileStream = File.Create("File.txt");


string content = "This is file content";
byte[] contentInBytes = Encoding.UTF8.GetBytes(content);
fileStream.Write(contentInBytes,0,contentInBytes.Length);
fileStream.Close();

Explanation
Basically, when you create the file using File or FileInfo Class, it returns an object of type
FileStream or StreamWriter or another Stream (sequence of bytes), as the file is stored or
transferred in the form of bytes. After getting the stream of a file, you can perform respective
operations on a file depending on the type of Stream. As in this case, File.Create() returns a
FileStream object so you can further perform FileStream’s operations on the created file.
As mentioned, Stream works on bytes; therefore, to write something in the file, you need to
convert the content in the form of bytes and then you can write into the file using FileStream’s
Write() method.
The Write() method takes three parameters containing the bytes of contents to write, the
starting, and the ending position of bytes to write.
When you are dealing with Files, it is important to release the resource as shown in the
code for File; Close() method must be called to release the file resources so that it can be used
for later operations to be performed in Files. If you don’t release the resource, you will get the
exception “A file is open/being used in another process” or something like this. You can also
use block to release the resource.

■■Note  The process of converting characters into bytes and vice versa is called Encoding and Decoding.

Using FileStream Class


FileStream Class is used for the creation and writing of content in a file.

296

Chapter 10 ■ File I/O Operations


Syntax

FileStream <object_name> =new FileStream(<File_Name>,<FileMode>,<FileAccess>,<FileShare>)

Explanation
FileStream has some parameters to explain. The following details illustrate parameters
that FileStream accepts:

Table 10-1.  FileStream Parameters


Parameter Description
File_NameFile_Name is the name of a file on which an operation will perform.
FileMode FileMode is an enumeration that gives a different method to open the file:
    1.  Append: It Creates the file if the file does not exist and, if it exists, it puts the
 
  cursor at the end of the file.
    2.  Create: Creates a new file and, if the file already exists, it will override it.
 
    3.  CreateNew: Creates a new file and, if the file already exists, it will throw an
 
  exception.
    4.  Open: Opens the file.
 
    5.  OpenOrCreate: Opens the existing file; if it’s not found, then it creates a new one.
 
    6.  Truncate: opens the existing file and truncates its size to zero bytes.
 
FileAccess FileAccess is an enumeration that gives a different method to access a file:
      1.  Read: tells the file has just read access.
    2.  ReadWrite: tells the file has read and write access.
 
    3.  Write: tells the file has just write access.
 
FileShare FileShare is an enumetation that gives different methods:
 
    1.  Delete: Allows subsequent deleting of a file.
      2.  Inheritable: Allows the file to handle child process inheritance.
    3.  None: Stops to share the file. File must be closed before access by another
 
  process.
    4.  Read: Allows file for reading.
 
 
    5.  ReadWrite: Allows file for reading and writing.
    6.  Write: Allows file to write.
    

Listing 10-9 shows how to write data in a file using FileStream Class.
Code Snippet

Listing 10-9.  FileStream to write in the file


FileStream fileStream = new FileStream("File.txt",FileMode.Create,FileAccess.Write
,FileShare.Write);
string content = "This is file content";
byte[] contentInBytes = Encoding.UTF8.GetBytes(content);
fileStream.Write(contentInBytes, 0, contentInBytes.Length);
fileStream.Close();

297

Chapter 10 ■ File I/O Operations


MemoryStream
MemoryStream drives from the abstract class Stream; it’s mainly used to write and read bytes
from memory. Listing 10-10 shows how to write and read from MemoryStream.
Code Snippet

Listing 10-10.  Use of MemoryStream


MemoryStream memoryStream = new
MemoryStream(); string content = "This is file
content";
byte[] contentInBytes = Encoding.UTF8.GetBytes(content);

//Write into file


memoryStream.Write(contentInBytes, 0, contentInBytes.Length);

//Set the position to the begninig of stream


memoryStream.Seek(0, SeekOrigin.Begin);

//Read from file


byte[] readContent = new byte[memoryStream.Length];

int count= memoryStream.Read(readContent, 0,


readContent.Length); for (int i =count; i < memoryStream.Length;
i++)
{
readContent[i] = Convert.ToByte(memoryStream.ReadByte());
}
string result= Encoding.UTF8.GetString(readContent);
Console.WriteLine(result);

BufferedStream
Buffer is a block of bytes in memory used to cache the data. BufferedStream needs stream to
be buffered. Listing 10-11 shows how you can write and read from Buffer using
BufferStream.
Code Snippet

Listing 10-11.  Use of Buffer Stream


FileStream fileStream = File.Create("Sample.txt");
BufferedStream memoryStream = new
BufferedStream(fileStream); string content = "This is file
content";
byte[] contentInBytes = Encoding.UTF8.GetBytes(content);

//Write into file


memoryStream.Write(contentInBytes, 0, contentInBytes.Length);

//Set the position to the begninig of stream


memoryStream.Seek(0, SeekOrigin.Begin);

//Read from file


byte[] readContent = new byte[memoryStream.Length];

298

Chapter 10 ■ File I/O Operations


int count= memoryStream.Read(readContent, 0,
readContent.Length); for (int i =count; i < memoryStream.Length;
i++)
{
readContent[i] = Convert.ToByte(memoryStream.ReadByte());
}
string result= Encoding.UTF8.GetString(readContent);
Console.WriteLine(result);

■■Note  NetworkStream and CryptoStream are also common. It is recommended to


explore them too, but these are not in exam 70-483.

Working with File Reader and Writer


To convert bytes into readable form or to write or read values as bytes or as string, .NET offers
the following classes in such a case. For those purposes, we have:
\ 1.\ StringRead and StringWriter
\ 2.\ BinaryReader and BinaryWriter
\ 3.\ StreamReader and StreamWriter

StringReader and StringWriter


These classes are used to read and write characters to and from the string. Listing 10-12
shows the use of StringReader and StringWriter.
Code Snippet

Listing 10-12.  StringReader and StringWriter

//Write string or characters


StringWriter stringWriter = new StringWriter();
stringWriter.Write("String Writer example");
stringWriter.Write(" Append Text");
Console.WriteLine(stringWriter.ToString());
//Read string

StringReader stringReader = new StringReader("String Reader


Example"); Console.WriteLine(stringReader.ReadLine());

BinaryReader and BinaryWriter


These classes are used to read and write values as Binary Values. Listing 10-13 shows the
example of BinaryReader and BinaryWriter.
Code Snippet

Listing 10-13.  BinaryReader and BinaryWriter

//Write Data Types values as Binary Values in Sample.dat file


FileStream file = File.Create("Sample.dat");

299

Chapter 10 ■ File I/O Operations


BinaryWriter binaryWriter = new BinaryWriter(file);
binaryWriter.Write("String Value");
binaryWriter.Write('A'); binaryWriter.Write(true);
binaryWriter.Close();

//Read Binary values as respective data type's values from Sample.dat


FileStream fileToOpen = File.Open("Sample.dat", FileMode.Open);
BinaryReader binaryReader = new BinaryReader(fileToOpen);
Console.WriteLine(binaryReader.ReadString());
Console.WriteLine(binaryReader.ReadChar());
Console.WriteLine(binaryReader.ReadBoolean());

Explanation
BinaryReader has methods to read a specific data type’s value. For example, if there is a string
value in binary form then you use the ReadString() method and so on, but if there is no written
value as binary and you want to read it then exception will be thrown. Also, it is important to
read ordinally as values are written.

StreamReader and StreamWriter


StreamWriter drives from TextWriter class; it’s used to write character/characters to the stream.
StreamReader drives from TextReader class; it’s used to read bytes or string. Listing 10-14 shows
the example of StreamReader and StreamWriter.
Code Snippet

Listing 10-14.  StreamReader and StreamWriter


StreamWriter streamWriter = new
StreamWriter("Sample.txt"); streamWriter.Write('A');
StreamReader streamReader = new
StreamReader("Sample.txt");
Console.WriteLine(streamReader.ReadLine());

Communication over the Network


System.Net namespace provides support for your applications to communicate across a
network. Most commonly, the members of this namespace you use are WebRequest and
WebResponse classes. Both of these classes are abstract and used to communicate over the
network. System.Net namespace also provides specific implemented classes that depend on
what the protocol is going to use for communication. For example, HttpWebRequest class and
HttpWebResponse class are used when you are using Http Protocol.
In General, we use WebRequest class to send the request for information and
WebResponse class to receive the response of the requested information.
Listing 10-15 shows how to use these classes when communicating over the network.
Code Snippet

Listing 10-15.  WebRequest and WebResponse


WebRequest request = WebRequest.Create("http://www.apress.com");
WebResponse response = request.GetResponse();

300

Chapter 10 ■ File I/O Operations


StreamReader reader = new
StreamReader(response.GetResponseStream()); string result =
reader.ReadToEnd();

Console.WriteLine(result);
response.Close();

Explanation
WebRequest is created using the Create() method (static method of WebRequest class),
which takes the address of the request in a string or Uri format. WebResponse is linked to
WebRequest, so it gets the response of the requested information or data using its
GetResponse() method.
The Create() method of WebRequest inspects the address and chooses the correct protocol
implementation. In code, we passed http://www.apress.com, so it would choose Http protocol
and return the HttpWebRequest. You can also use WebRequest’s method or properties to perform
further operations on it.
After getting the response, StreamReader is used to get the response in stream so that it can be read.

Working with asynchronous File I/O


Reading and writing of the file might be a time-consuming task and you have to wait a long time
to finish the operation. The code in this chapter is called synchronous code. The code is
executed line by line and often waits till the task ends. This wait can be long enough to annoy
the user and put a severe impact on the user experience.

For example, in desktop applications, you have one thread that is the main thread and
which is responsible for all the tasks, i.e., updating the UI and processing other tasks as well.
If you have a long task to process (i.e., waiting for the network stream to respond or reading
a file from Internet), then the main thread will be busy in processing that task and,
meanwhile, the UI of the application will be stuck and be unresponsive, which will be a bad
experience for the user. In such scenarios, such a long-running task should be processed in
another thread so that the main thread is not busy and the application stays responsive. In
this way, your code will execute in an asynchronous manner.

■■Note  Details of Synchronous and Asynchronous code are discussed in Chapter 8.

Async and Await in File I/O


Async (async) and Await (await) are the keywords provided by .NET Framework in C# 5.0.
They tell the compiler to execute code in an asynchronous manner.
According to MSDN:

AnAsyncmethodcontainsasyncinitsname,suchasReadAsync,WriteAsync,ReadLineAsync,
and ReadToEndAsync, etc. These async methods are implemented on stream
classes such as Stream, FileStream, MemoryStream, and on classes that are used
for reading from or writing to streams such as TextReader and TextWriter.

Listing 10-16 shows how to write and read a file asynchronously.

301

Chapter 10 ■ File I/O Operations


Code Snippet

Listing 10-16.  Asynchronous File I/O


//Write to the File
FileStream file = File.Create("Sample.txt");
StreamWriter writer = new StreamWriter(file);
await writer.WriteAsync("Asynchronously Written
Data"); writer.Close();

//Read From File


FileStream readFile = File.Open("Sample.txt", FileMode.Open);
StreamReader reader = new StreamReader(readFile);
string result = await reader.ReadToEndAsync();
Console.WriteLine(result);

■■Note  You can find more on “Working with asynchronous File I/O” from the following link: https://msdn.
microsoft.com/en-us/library/kztecsys(v=vs.110).aspx

Summary
\ 1.\ DriveInfo class gives you support to interact with Drives.
\ 2.\ C# gives Directory and DirectoryInfo Classes to interact with
Directories. Directory class is static and preferable for single
operations, whereas DirectoryInfo is preferable for multiple
operations.
\ 3.\ File and FileInfo: both classes are used to interact with Files. File Class
is static and preferable for performing a single operation on a file,
whereas FileInfo is for multiple operations.
\ 4.\ Stream is an abstract class used for writing and reading bytes. It has
three main tasks: Reading, Writing, and Seeking.
\ 5.\ FileStream drives from the abstract class Stream; it’s mainly used to
write and read bytes in the file.
\ 6.\ MemoryStream drives from the abstract class Stream; it’s mainly
used to write and read bytes from memory.
\ 7.\ Buffer is a block of bytes in memory used to cache the data.
BufferedStream needs stream to be buffered, i.e., a stream of bytes in
memory for caching the data.
\ 8.\ StringReader and StringWriter classes are used to read and write
characters to and from the string.
\ 9.\ BinaryReader and BinaryWriter classes are used to read and write
values as Binary Values.
\ 10.\ StreamWriter drives from TextWriter; it’s used to write
character/characters to the stream. StreamReader drives from
TextReader; it’s used to read bytes or string.
\ 11.\ For communication over a network, we use WebRequest class to send
the request for information and WebResponse class to receive the
response of the requested information.
302

Chapter 10 ■ File I/O Operations

Code Challenges
Challenge 1: Download and Save Image
Download any of Image and convert in bytes and save those bytes in file named ImageData in
your local space. Read those bytes from ImageData file and convert them into Image form and
save obtained image in local drive as well.

Practice Exam Questions


Question 1
You have to develop an application for an organization which reads the file and displays the
content of a file. Which code snippet will properly fulfill your requirement?:

A) string fileContent = "";


StreamReader reader = new
StreamReader("data.txt"); fileContent =
reader.ReadToEnd(); reader.Close();

B) string fileContent = "";


StreamReader reader = null;
using (reader = new StreamReader("data.txt"))
{
fileContent = reader.ReadToEnd();
}

C) string fileContent = "";


try

{
StreamReader reader = new
StreamReader("data.txt"); fileContent =
reader.ReadToEnd();
}
catch
{

D) string fileContent = "";


StreamReader reader = new
StreamReader("data.txt"); fileContent =
reader.ReadToEnd();

Question 2
You need to read a file from a web server and save the content of the file locally.
Which code snippet will more preferable?:

A) WebRequest request =
WebRequest.Create(remoteFileUri); WebResponse
response = request.GetResponse();
StreamReader reader = new
StreamReader(response.GetResponseStream()); StreamWriter writer =
new StreamWriter("localFile.txt"); writer.Write(reader.ReadToEnd());

303

Chapter 10 ■ File I/O Operations


B) WebRequest request =
WebRequest.Create(remoteFileUri); WebResponse
response = request.GetResponse();
StreamReader reader = new
StreamReader(response.GetResponseStream()); StreamWriter writer =
new StreamWriter("localFile.txt"); writer.Write(reader.ReadToEnd());
writer.Close();
reader.Close();
response.Close();

C) WebResponse response = null;


WebRequest request =
WebRequest.Create(""); using (response =
request.GetResponse())
{
StreamReader reader = new
StreamReader(response.GetResponseStream()); StreamWriter writer =
new StreamWriter("localFile.txt"); writer.Write(reader.ReadToEnd());
}

D) WebResponse response =
null; StreamReader reader =
null;
WebRequest request =
WebRequest.Create(""); using (response =
request.GetResponse())
reader = new
StreamReader(response.GetResponseStream()); StreamWriter
writer = new StreamWriter("localFile.txt");
writer.Write(reader.ReadToEnd());

Question 3
You are working on an application that reads the file named “sample.txt”. Your application
takes care of the following points when it reads the sample.txt file.
It does not make changes to the “sample” file.
It must allow other processes to access the “sample” file.
It must not throw an exception if the “sample” file does not exist.
Which code snippet should you choose to take care of said points about sample.txt?:

A) var read = File.Open("sample.txt", FileMode.Open, FileAccess.Read, FileShare.Read);

B) var read = File.Open("sample.txt", FileMode.OpenOrCreate, FileAccess.Read, FileShare.


ReadWrite);

C) var read = File.Open("sample.txt", FileMode.Open, FileAccess.ReadWrite, FileShare.Read);


D) var read = File.Open("sample.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

Answers
\ 1.\ A & B
\ 2.\ B

\ 3.\ B

304

CHAPTER 11

Serialization and Deserialization

When communicating with remote applications, you will often exchange data with other
applications. Serialization and Deserialization of data is done before the exchange of data,
when it’s received or sent.
In this chapter, we will understand the following topics:
\ 1.\ Serializations and Deserialization
\ 2.\ Binary Serialization
\ 3.\ XML Serialization
\ 4.\ JSON Serialization
\ 5.\ Custom Serialization

Serialization and Deserialization


Serialization and Deserialization are the processes of serializing and deserializing data. C#
provides different techniques to perform these processes.

Serialization
The Process of converting an object or object graph into a stream of bytes is called
Serialization. It is the process of transforming an object into bytes or text in order to store it
into any kind of storage or exchange the object over the network.

Deserialization
The Process of converting a back stream of bytes into an object or object graph is called Deserialization.

Pictorial Representation
The process of serialization and deserialization is illustrated by a figure by which you can get
to know how this is basically performed:
© Ali Asad and Hamza Ali 2017 305
A. Asad and H. Ali, The C# Programmer’s Study Guide (MCSD), DOI 10.1007/978-1-4842-2860-9_11

Chapter 11 ■ Serialization and Deserialization

Figure 11-1.  Serialization and Deserialization

Explanation
Serialization and Deserialization of data or an object is commonly used in those cases where
you will often exchange data with other applications. For example, when data is going to be
sent to a web service or over a network stream, you first have to convert data into a stream of
bytes and, on the receiving side, you have to convert it back from a stream of bytes to an object
that is your main concern. This is called Serialization and Deserialization, respectively.
The Serialized object carries an object’s data in the form of a stream along with the
information of object’s type, i.e., its version, culture, and assembly name.
The .NET Framework provides classes to help you to serialize and deserialize the object
and also offers you ways to configure your own objects.
By default, there are three serialization/deserialization mechanisms provided by .NET Framework:
\ 1.\ BinaryFormatter
\ 2.\ XmlSerializer
\ 3.\ DataContractSerializer
BinaryFormatter is a serializer to serialize and deserialize the data in Binary format.
XmlSerializer is used to serialize and deserialize the object in an XML document. This serializer
enables you to control how objects are encoded into XML. DataContractSerializer is also used to
serialize the object into an XML Stream by using a supplied data contract.
There are also other serializers which are used to serialize and deserialize the data
according to their usage, such as:

306

Chapter 11 ■ Serialization and Deserialization


\ 1.\ DataContractJsonSerializer: Serialize the objects to the
JavaScript Object Notation (JSON) and deserialize JSON data to
objects.
\ 2.\ JavaScriptSerializer: Serialize and deserialize the objects for
AJAX-enabled application.
We’ll explain these serialization mechanisms according to the Exam ref 70-483 point of view.

■■Note  Methods are not serialized because serialization only serializes the data stored by an object.

Binary Serialization
Binary serialization serializes an object or data or object graph in binary format.
Binary serialization uses binary encoding for serialization to produce compact serialized
data for uses as storage or socket-based network streams.
A binary sterilized object contains serialized data along with the object’s Type
information including version, public token, culture, and assembly name.

■■Note  Binary serialization is dependent upon a .NET Platform, i.e., to exchange a binary serialized object or
data from one application to another application, and both applications must be in a .NET platform.

Using Binary Serializer


Binary serializer uses a BinaryFormatter class to implement Binary Serialization. It is more
secure than other serializations. To perform this type of serialization, you just need to mark
an item with the SerializableAttribute. After that, you need to use the instance of Binary
Serializer to serialize the object or object graph. The following are namespaces used in
Binary Serialization:
\ 1.\ System.Runtime.Serialization
\ 2.\ System.Runtime.Serialization.Formatters.Binary
You can serialize the object into a file or memory or database according to your need.
Listing 11-1 shows how you can configure an object for binary serialization, serialize it
into file, and then deserialize it into an object.
Code Snippet

Listing 11-1.  Binary Serialization


[Serializable] public
class Teacher
{
public int ID { get; set; } public string
Name { get; set; } public decimal
Salary { get; set; }
}
//Created the Instance and initialized
Teacher teacher = new Teacher()

307

Chapter 11 ■ Serialization and Deserialization


{
ID = 1,
Name = "Ijaz",
Salary = 1000
};

//Binary Serializer
BinaryFormatter formatter = new BinaryFormatter();
//Sample.bin(Binary File is Created) to store binary serialized data
using (FileStream file=new FileStream("Sample.bin",FileMode.Create))
{
//this function serialize the "teacher" (Object) into "file" (File)
formatter.Serialize(file,teacher);
}

Console.WriteLine("Binary Serialization is Successfully Done!");


//Binary Deserialization
using (FileStream file=new FileStream("Sample.bin",FileMode.Open))
{
Teacher dteacher=(Teacher)formatter.Deserialize(file);
}
Console.WriteLine("Binary Deserialization is Successfully Done!");

Explanation
In binary serialization, all the fields can be serialized, even those that are private. You can
prevent fields from being serialized by using a NonSerialized attribute. For example, you don’t
want to serialize the field Salary of Teacher. You can do this:

[Serializable] public
class Teacher
{
public int ID { get; set; } public
string Name { get; set; }
[NonSerialized]

public decimal Salary;


}

Binary serialization is stricter than other serializations. When the Binary Serializer can’t
find a specific field, it throws an exception. You can use OptionalFieldAttribute to make sure
that the binary serializer knows that the field is added in later versions and the current
serialized object will not contain this field.

■■Note  Constructor does not execute during Binary deserialization.

XML Serialization
XML serialization serializes an object into XML format or an XML stream. In XML serialization,
only public fields or properties can be serialized. Unlike Binary serialization, it does not include
a serialized object’s type

308

Chapter 11 ■ Serialization and Deserialization


information. For example, if you have a serialized object of type Teacher, then there is no
guarantee that it would be deserialized into an object of type Teacher. That's why XML
Serialization does not store an object’s type information.
According to MSDN:

XML serialization does not convert methods, indexers, private fields, or read-only
properties (except read-only collections). To serialize all of an object's fields and
properties, both public and private, use the DataContractSerializer instead of XML
serialization.

Using XML Serializer


XML serialization uses XmlSerializer class to implement XML serialization. XmlSerializer is
less strict than BinarySerializer, but it does not have best performance. It also does not
maintain an object’s information and you cannot serialize private fields.
To perform XML serialization, you mark your type with a Serializable attribute which
tells the .NET framework that type should be serializable. It will check your object and
object graph (all the objects it references) to make sure that it will serialize all the
connected objects.

■■Tip  XML serialization can be done without specifying a Serializable attribute on the type, but it is bad approach.

Listing 11-2 shows how you can configure an object for XML serialization, serialize it into a
file and then deserialize it into an object.
Code Snippet

Listing 11-2.  XML Serialization using XmlSerializer


[Serializable] public
class Teacher
{
public int ID { get; set; } public
string Name { get; set; } public
long Salary { get; set; }

}
XmlSerializer xml = new XmlSerializer(typeof(Teacher));
using (var stream = new FileStream("Sample.xml", FileMode.Create))
{
xml.Serialize(stream, t);
}

Console.WriteLine("Data has been Serialized!");

Teacher teacher = null;


using (var stream = new FileStream("Sample.xml", FileMode.Open))
{
XmlSerializer xml = new XmlSerializer(typeof(Teacher));
teacher = (Teacher)xml.Deserialize(stream);
}

309

Chapter 11 ■ Serialization and Deserialization


Console.WriteLine(teacher.ID);
Console.WriteLine(teacher.Name);
Console.WriteLine(teacher.Salary);

Console.WriteLine("Data has been Deserialized!");

Serialized object
The serialized object in XML format looks like:

<?xml version="1.0"?>
<Teacher
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ID>2</ID>
<Name>Ahsan</Name>
<Salary>20000</Salary>
</Teacher>

Explanation
XML serialization can be configured to get more control over the type to be serialized using
attributes provided by the System.Xml.Serialization namespace. The following are important
attributes (with their use) that are commonly used:
\ 1.\ XmlRoot: Applied on Type, which tells the compiler that this is going
to be the main/parent Node of a Serialized object in XML.
\ 2.\ XmlAttribute: Applied on any of the public fields mapped into an
attribute on its parent node.
\ 3.\ XmlElement: Applied on any of the public fields mapped into an
element of a parent node.
\ 4.\ XmlIgnore: Applied on any of the public fields which will not be serialized.
\ 5.\ XmlArray, XmlArrayItem: These two (XmlArray and
XmlArrayItem) can be applied on any of the public fields of the
type collection for serialization.
By default, each public field of your type is serialized as XmlElement. Using these
above-mentioned attributes, you can map your object into proper XML format.
Listing 11-3 shows how to configure your type more for XML serialization.
Code Snippet

Listing 11-3.  Controlled XML serialization

[Serializable]
[XmlRoot("Teacher")]
public class teacherClass
{
[XmlAttribute("ID")] public
int id { get; set; }
[XmlElement("Name")]
public string name { get; set; }
[XmlIgnore]
public long salary { get; set; }
310

Chapter 11 ■ Serialization and Deserialization


[XmlElement("Students")]
public studentClass st { get; set; }

}
[Serializable]
public class studentClass
{
[XmlAttribute("RollNo")] public
int rollno { get; set; }
[XmlElement("Marks")]
public int marks { get; set; }
}
//Serialization
teacherClass t = new teacherClass
{
id = 2,
name =
"Ahsan", salary
= 20000,
st = new studentClass
{
rollno = 1,
marks = 50
}
};

XmlSerializer xml = new XmlSerializer(typeof(teacherClass)); using


(var stream = new FileStream("Sample.xml", FileMode.Create))
{
xml.Serialize(stream, t);
}
Console.WriteLine("Data has been Serialized!");
//Deserialization
teacherClass teacher = null;
using (var stream = new FileStream("Sample.xml", FileMode.Open))
{
XmlSerializer xml = new XmlSerializer(typeof(teacherClass));
teacher = (teacherClass)xml.Deserialize(stream);
}

Console.WriteLine(teacher.id);
Console.WriteLine(teacher.name);
Console.WriteLine(teacher.salary);
Console.WriteLine(teacher.st.rollno);
Console.WriteLine(teacher.st.marks);
Console.WriteLine("Data has been Deserialized!");

311

Chapter 11 ■ Serialization and Deserialization


Serialized Object
The serialized object in XML format looks like:

<?xml version="1.0"?>
<Teacher
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" ID="2">
<Name>Ahsan</Name>
<Students
RollNo="1">
<Marks>50</Marks>
</Students>
</Teacher>

This is serialization of a teacherClass object and all the objects connected with it
(object graph). As in code, you can also configure Attributes to grasp more control over
the object to be serialized.

■■Note  Type must be public for XML serialization, as XmlSerializer serializes only public types or members.

Using DataContract Serializer


DataContractSerializer serialize an object into an XML format using a supplied data contract.
When working with WCF, your types are serialized so that they can be sent to other
applications. This serialization is done by DataContractSerializer or DataContractJsonSerializer
(discussed next).
The main differences between DataContractSerializer and XmlSerializer are:
\ 1.\ Instead of using Serializable Attribute, you use DataContract attribute.
\ 2.\ Members are not serialized by default as in XmlSerializer.
\ 3.\ All the members you want to serialize must be explicitly
marked with a DataMember attribute.
\ 4.\ To ignore a member to be serialized, you use the IgnoreDataMember
attribute instead of XmlIgnore.
\ 5.\ Private fields are also serializable by DataContractSerializer,
which is not possible in XmlSerializer.
\ 6.\ In DataContractSerializer, you use the WriteObject() method to
serialize an object and ReadObject() method to deserialize the
stream into an object.

■■Note  WCF uses DataContractSerializer as the default Serializer.


Listing 11-4 shows how to serialize and deserialize an object.
Code Snippet

Listing 11-4.  XML Serialization using DataContractSerializer


[DataContract]
public class Teacher
{

312

Chapter 11 ■ Serialization and Deserialization


[DataMember]
private int id = 1;
[DataMember]
public string name { get; set; }
[IgnoreDataMember]
public long salary { get; set; }

}
//Serialization
DataContractSerializer dataContract = new DataContractSerializer(typeof(Teacher));
using (var stream = new FileStream("Sample.xml", FileMode.Create))
{
dataContract.WriteObject(stream, t);
}
Console.WriteLine("Data has been Serialized!");

//Deserialization
Teacher teacher = null;
DataContractSerializer dataContract = new DataContractSerializer(typeof(Teacher));

using (var stream = new FileStream("Sample.xml", FileMode.Open))


{
teacher = (Teacher)dataContract.ReadObject(stream);
}
Console.WriteLine("Data has been Deserialized!");

You can use DataContractSerializer from the System.Runtime.Serialization namespace in the


same way you used XmlSerializer and BinarySerializer (BinaryFormatter) with the difference of
attributes or methods to serialize and deserialize.

■■Note  WCF (Windows Communication Foundation) is a framework for building a service-oriented


application. This Topic is discussed in Chapter 13 “Accessing Remote Data.”

JSON Serialization
JSON Serialization serializes an object into JSON (JavaScript Object Notation) format, an
efficient encoding format that is specifically useful when sending a small amount of data
between a Client (Browser)
and AJAX-enabled Web services.
JSON Serialization is automatically handled by WCF when you use DataContract Types
in service operations that are exposed over AJAX-enabled endpoints.
However, in some cases you may need to execute this serialization manually with JSON
serialization, as this is a more lightweight medium to store data into some storage or send over
the network.

Using DataContractJsonSerializer
DataContractJsonSerializer is used to convert an object into JSON data and convert back JSON
data into an object. DataContractJsonSerializer is a class provided by .NET in the
System.Runtime.Serialization.Json namespace.

313

Chapter 11 ■ Serialization and Deserialization


Like DataContractSerializer, DataContractJsonSerializer provides a WriteObject() method
for serialization and a ReadObject() method for deserialization. The rest of the procedure for
JSON Serialization is the same as the others. It is mainly used with WCF.
Listing 11-5 shows JSON serialization using DataContractJsonSerializer.
Code Snippet

Listing 11-5.  JSON Serialization using DataContractJsonSerializer


[DataContract]
public class Teacher
{
[DataMember]
private int id = 1;
[DataMember]
public string name { get; set; }
[DataMember]
public long salary { get; set; }

//Serialization
DataContractJsonSerializer dataContract = new DataContractJsonSerializer(typeof(Teacher));
using (var stream = new FileStream("Sample.json", FileMode.Create))
{
dataContract.WriteObject(stream, t);
}
Console.WriteLine("Data has been Serialized!");
//Deserialization      
Teacher teacher = null;    
DataContractJsonSerializer dataContract = new DataContractJsonSerializer(typeof(Teacher));
using (var stream = new FileStream("Sample.json", FileMode.Open))
{
teacher = (Teacher)dataContract.ReadObject(stream);
}
Console.WriteLine("Data has been Deserialized!");

Serialized Object

{"id":1,"name":"Ahsan","salary":20000}

Private members are also serialized in Json Serialization.

■■Note  DataContractJsonSerializer supports the same types as DataContractSerializer.

Using JavaScriptSerializer
JavaScriptSerializer is a class provided by .NET in the System.Web.Script.Serialization
namespace found in the System.Web.Extension assembly used to serialize and deserialize an
object into Json format for AJAX-enabled applications.
314
Chapter 11 ■ Serialization and Deserialization
Listing 11-6 shows a basic example of how to serialize and deserialize an
object using JavaScriptSerializer.

■■Note  There is no attribute required for the object’s Type to be serialized when using JavaScriptSerializer.
Code Snippet

Listing 11-6.  JSON Serialization using JavaScriptSerializer

private class Teacher


{
private int id { get; set; } public
string name { get; set; } public
long salary { get; set; }

//Serialization
JavaScriptSerializer dataContract = new JavaScriptSerializer();

string serializedDataInStringFormat = dataContract.Serialize(steacher);

Console.WriteLine("Data has been Serialized!");

//Deserialization Teacher
dteacher = null;

dteacher = dataContract.Deserialize<Teacher>(serializedDataInStringFormat);

Console.WriteLine("Data has been Deserialized!");

■■Note  Private members cannot be serialized using JavaScriptSerializer for Json Serialization.

Custom Serialization
Custom serialization allows an object to control its own serialization and deserialization. One
of the ways to implement a custom serialization is to implement an ISerializable interface on
an object’s Type.

Using ISerializable
ISerializable is an interface that allows you to implement custom serialization. This interface
involves the GetObjectData() method and a special constructor that is used when the object is
deserialized.
Listing 11-7 shows custom serialization.

315

Chapter 11 ■ Serialization and Deserialization


Code Snippet

Listing 11-7.  Custom serialization using Iserializable interface


[Serializable]
public class Teacher : ISerializable
{
public int ID { get; set; } public
string Name { get; set; } public
Teacher()

{
}
protected Teacher(SerializationInfo info,StreamingContext context)
{
this.ID = info.GetInt32("IDKey");
this.Name = info.GetString("NameKey");
}

[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter = true)]
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("IDKey", 1);
info.AddValue("NameKey",
"Hamza")
}
}

Explanation
GetObjectData() method is called during serialization and you need to populate the
SerializationInfo provided with the method call. Add the variable or value to be serialized
with the name associated with it in the AddValue() method of SerializationInfo’s object. You
can use any text as a name associated with a value or variable. You can add any or a few
number of variables provided with the method call in SerializationInfo’s object. These
provided variables or values will be serialized. With deserialization, a special constructor
would call and serialized values deserialize by calling the Get method of the
SerializationInfo’s object.

Serialization Performance Comparison


The following table shows the rough idea of performance of serialization techniques by size of
data (in bytes) and time (in milliseconds) taken to serialize and deserialize an object or object
graph:

Table 11-1.  Performance Comparison of different Serialization techniques


Binary XML Data Contract
   

Size (Small) 669 298 370


Serialize 0.0210 0.0218 0.004
Deserialize 0.0194 0.0159 0.0127
Size (Large) 204,793 323,981 364,299
Serialize 13.7000 5.5080 4.4438
Deserialize 19.3976 7.8893 11.4690
         

316

Chapter 11 ■ Serialization and Deserialization

Summary
\ 1.\ The process of converting an object or object graph into a stream
of bytes is called Serialization, and the reverse process is called
Deserialization.
\ 2.\ Binary Serialization is performed using a Serializable attribute. It is
more secure than other serializations but restricted to a .NET
Platform.
\ 3.\ XML Serialization serialized only public members and is not restricted to a
.NET Platform. An XML Serialized object is readable as compared to
a Binary Serialized object, which is not readable to humans.
\ 4.\ XmlSerializer and DataContractSerializer: both classes can be used
for XML Serialization.
\ 5.\ JSON serialization is considered a fast serialization approach. It is
lightweight compared to XML and Binary Serialization. As with XML
serialization, you can just serialize public members.
\ 6.\ DataContractJsonSerializer and JavaScriptSerializer: both classes can be
used for JSON serialization.
\ 7.\ Custom Serialization can also be performed by implementing an
ISerializable interface.

Code Challenges
Challenge 1: Perform Deserialization
You are given a sample file of serialized data in XML format (taken from MSDN); you need to
deserialize the data using the appropriate deserialization technique.

Practice Exam Questions


Question 1
You are developing an application that retrieves Person type data from the Internet using
JSON. You have written the following function for receiving the data so far:

serializer.Deserialize<Person>(json);

Which code segment should you use before this function?

A) DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(Person));

B) DataContractSerializer serializer = new DataContractSerializer(typeof(Person));

C) JavaScriptSerializer serializer = new JavaScriptSerializer();

D) NetDataContractSerializer serializer = new NetDataContractSerializer();

317

Chapter 11 ■ Serialization and Deserialization


Question 2
You need to store a large amount of data in a file. Which serializer would you consider better?
\ A)\ XmlSerializer
\ B)\ DataContractSerializer
\ C)\ DataContractJsonSerializer
\ D)\ BinaryFormatter
\ E)\ JavaScriptSerializer

Question 3
You want to serialize data in Binary format but some members don’t need to be serialized.
Which attribute should you use?
\ A)\ XmlIgnore
\ B)\ NotSerialized
\ C)\ NonSerialized
\ D)\ Ignore

Answers
\ 1.\ C
\ 2.\ D

\ 3.\ C

318

CHAPTER 12

Consume Data
To work with data is an important part of developing an application. A normal application
stores data in memory but when this application ends, data is lost. In this case, when you don’t
want to lose your data, the
.NET framework gives you the interactive way to store your data in a persistent way. This can
be achieved by interacting with a database or external web service to insert or retrieve data
whenever you require.
In this chapter, we will learn about:
\ 1.\ Working with a database
\ 2.\ Consuming XML and JSON data
\ 3.\ Working with a Web service

Working with a Database


The .NET framework provides the namespace System.Data.dll, using the classes to interact
with relational database systems. These classes come under ADO.NET, in which there are
three conceptual parts:
\ 1.\ Connected Layer
\ 2.\ Disconnected Layer
\ 3.\ Entity Framework

ADO.NET
ADO.NET is a set of object-oriented libraries used to interact with a database. It enables you to
connect with a database and perform different database-oriented operations on it, such as a
CRUD operation (Create, Read, Update, and Delete data). According to MSDN:

“ADO.NET is a set of classes that expose data access services for .NET
Framework programmers. ADO.NET provides a rich set of components for
creating distributed, data- sharing applications. It is an integral part of the .NET
Framework, providing access to relational, XML, and application data.”

System.Data provides different Types (Class or interface, etc.) that provide data access for
different data providers.

© Ali Asad and Hamza Ali 2017 319


A. Asad and H. Ali, The C# Programmer’s Study Guide (MCSD), DOI 10.1007/978-1-4842-2860-9_12

Chapter 12 ■ Consume Data


Data Providers
The .NET framework allows you to work with different types of databases, for example,
Microsoft SQL Server, Oracle, and MySQL database. System.Data.dll provides different Data
providers to work with different databases. Data Providers are used for connecting to a
database, executing a command, and retrieving a result. For example, if you want to work with
an MS SQL server database, .NET gives you a Data Provider for this, i.e., the
System.Data.SqlClient namespace (provides data access for Microsoft SQL server database)
and the System.Data.OleDb namespace. For more information on Data Providers, visit:
https://msdn.microsoft.com/en-us/library/a6cd7c08(v=vs.110).aspx

We will discuss the interaction with an MS SQL server database in this chapter.

Connection
After deciding the data provider, connection to a specific database is important to work with.
You must establish a connection with your database for further interaction. ADO.NET provides
classes to establish connection with a specific database. A connection string is required which
contains all the information to connect with a database including data location, database name,
data provider, and authentication against database.
DbConnection class is a base class that provides the connection-related functionality.
SqlConnection is derived with DbConnection class and can be used for connection with an
SQL Server database. To connect with a specific database using a connection string, you must
open the connection to proceed further.
The following steps show how to connect with an MS SQL server database:
Step 1:
Right-click on your project in Visual Studio and Click on Add ➤ New Item. Select DataSet
and name it whatever you like (Sample.xsd in this example).
Figure 12-1.  Choose DataSet
320

Chapter 12 ■ Consume Data


After this, the Sample.xsd window appears in front of you.
Step 2:
Add Data Connection by going to Server Explorer. In Server Explorer, right-click on Data
Connections
➤ Add Connection; a wizard will open where you need to specify Server name and select a
database which you made in SQL server. In this example, I have added School database,
having two tables: Student (StudentID, StudentName) and Class (ClassID, ClassName,
StudentID: as foreign key).

Figure 12-2.  Choose Data Source after selecting DataSet

After selecting database, Click OK.


Step 3:
Drag the added connection (from the Server Explorer) in Sample.xsd (the window open in
front of you) and Save the Sample.xsd.
321

Chapter 12 ■ Consume Data


Figure 12-3.  Drag added Database from Server Explorer into Sample.xsd

Step 4:
As the components are added, now you can interact with your database. Listing 12-1
shows how to connect with an added database:

Listing 12-1.  Connection in C#

string connectionString = "YOUR CONNECTION STRING


HERE"; SqlConnection con = new
SqlConnection(connectionString); con.Open();

■■Note  To get a connection string, right-click on your Connection (from Server


Explorer) and click on properties. Search for the Connection String property, where you find
your connection string. Just copy and paste in your code.

You can also build your connection string dynamically by using the
provided class SqlConnectionStringBuilder. Listing 12-2 shows dynamic
building of a connection string:

322

Chapter 12 ■ Consume Data


Listing 12-2.  Dynamic building of a connection string
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
builder.DataSource = "";
builder.InitialCatalog = "";
builder.IntegratedSecurity = true;

string connectionString = builder.ToString();

■ ■ Note  You must close the connection after performing the operation. Having
connections open for too long is a problem, as other users can’t connect . The server allows a
specific number of connections and, if the limit is exceeded, it will not allow any other user to
connect due to the busyness of the already allotted connections (that are still open).

Command
ADO.NET provides the class SqlCommand, used to execute statements (commands/queries)
against the database. By using this class, you can execute insert, delete, update, or stored
procedure commands. Listing 12-3 shows how to give command of a specify query:
Code Snippet

Listing 12-3.  Command in C#

string command = "select * from Student";


SqlCommand cmd = new
SqlCommand(command,con);

SqlCommand requires a command (statement/query) to execute and a connection


(connection string) on which a written command is going to execute. This code knows which
command to execute on which connection (in other words, what to do using which path).

Conceptual parts of ADO. NET


Conceptually, ADO.NET consists of three layers, i.e., the different ways of interaction with a
database. Each layer has its own suitability according to the scenario. The details of these
layers are described below.

Connected Layer
In a connected layer, you connect to a database as a data source and execute queries by
writing SQL. These queries are used by ADO.NET and forwarded to your database of choice.
In this way of interacting with a database, you normally use Connection, Command, and
DataReader objects.

323

Chapter 12 ■ Consume Data


ExecuteNonQuery
ExecuteNonQuery is a method performed on a Command (SqlCommand) object used to
execute the statement specified by a Command object and does not return result set(s) but a
number of rows affected in a database by query execution. It is basically called on a Command
object, having the query of insert, delete, and update. These queries do not return any record
but a number of rows affected; that’s why these types of queries are executed by
ExecuteNonQuery Method.
Listing 12-4 shows the example of ExecuteNonQuery:
Code Snippet

Listing 12-4.  ExecuteNonQuery on Insert Command

string connectionString = "YOUR CONNECTION STRING


HERE"; SqlConnection con = new
SqlConnection(connectionString); con.Open();

string command = "Insert into Student values(1,'Hamza Ali')";


SqlCommand cmd = new SqlCommand(command, con);
int result = cmd.ExecuteNonQuery();
con.Close();
if (result > 0)
Console.WriteLine("Data is Inserted");
else

Console.WriteLine("Error while inserting");

This code basically inserts data in a Student table. ExecuteNonQuery() returns the number of affected
rows.

■■Tip  When database related work is done, try to close the connection immediately. It is
recommended to use a Using block in this case.

ExecuteScalar
The ExecuteScalar method is also performed on a Command’s object in a case where you write
queries that return a single value. This is the case in which you use aggregate functions in your
queries.
Listing 12-5 shows how to use the ExecuteScalar method:
Code Snippet

Listing 12-5.  ExecuteScalar on aggregate function


string con = "YOUR CONNECTION STRING HERE";

string command = "select count(*) from Student";


SqlCommand cmd = new SqlCommand(command,
con); var noOfStudents = cmd.ExecuteScalar();
con.Close(); Console.WriteLine(noOfStudents);

ExecuteScalar returns a single value of type Object which you can cast on the corresponding type.

324

Chapter 12 ■ Consume Data


ExecuteReader
The ExecuteReader method is also called on a Command’s object where you need to retrieve
the data, i.e., in the case of a “select” query. This method returns an SqlDataReader object that
remains connected to a database the whole time the reader is open. SqlDataReader is a
forward-only resultset, which means you cannot move to any previous record and can read one
record at a time. You can read the specific column of a table by index number or column name.
Listing 12-6 shows the use of the ExecuteReader method:
Code Snippet

Listing 12-6.  ExecuteReader in Select Command


string con = "YOUR CONNECTION STRING HERE";

string command = "select * from Student";


SqlCommand cmd = new
SqlCommand(command, con);

SqlDataReader reader = cmd.ExecuteReader();

int StudentID = 0;
string StudentName = null;
if (reader.HasRows)
{
while (reader.Read())
{
StudentID = int.Parse(reader[0].ToString());//0 index means first clm in the table which is
StudentID

StudentName = reader["StudentName"].ToString();//it will fetch the value of provided clm


name

}
}
reader.Close();
con.Close();

Console.WriteLine("ID is: " + StudentID);


Console.WriteLine("Name is: " + StudentName);

SqlDataReader provides some properties like HasRows (to check if an SqlDataReader


object has a row/ rows or not), FieldCount, IsClosed, Item[Int32], and Item[string].
The last two properties are indexers which we have used in the above example. These are
used to fetch a specific column value based on its name(string) or index number(int).
Read() method reads the record from a database and is ready to read for the next, while
the loop iterates and execution takes place for the next record and so on until there is the last
record and the loop ends.
You must close the reader object and then close the connection object. Forgetting
to close the connection can hurt performance. You can use the “Using” block to avoid
such things.
■■Note  You can use the OleDbDataReader class in place of the SqlDataReader class
for retrieving data from Microsoft Access.

325

Chapter 12 ■ Consume Data


ExecuteXMLReader
The ExecuteXmlReader method is also called on a Command’s object and is the same as
ExecuteReader but the difference is that it returns an XmlReader object used to represent
data as XML.

Disconnected Layer
In a disconnected layer, you normally use DataSets and DataTables that copy the structure of
a relational database in memory. A DataSet is created in the result of an execution of a
query against a connected database. It can be manipulated in memory and changes to a
database take place using DataAdapter. DataTable and DataSets are another way to retrieve
results from a database.

DataTable
DataTable is the same as DataReader except DataTable can also move forward and back. It is
disconnected from a database and you can make changes to data in DataTable and commit or
update a database with these changes

DataSet
DataSet is the container of DataTables. You can write a query that returns multiple resultsets
and can be contained in a DataSet. You can then perform further operations on a received
DataSet, such as filtering or sorting, etc. These updatings take place in memory.

DataAdapter
DataAdapter is the important object when you work with a disconnected layer. It acts like a
bridge between data in memory and a database. DataAdapter populates a DataTable or
DataSets and reconnects data in memory to a database. You can perform insert, update, delete,
or read query while the data is in memory and then reconnect to a database to commit the
changes.
Listing 12-7 shows how to perform database-oriented operations using a disconnected layer:
Code Snippet

Listing 12-7.  Disconnected layer operations

string con = "YOUR CONNECTION STRING HERE";

string command = "select * from Student";


SqlDataAdapter ad = new SqlDataAdapter(command,
con);

DataTable tbl = new DataTable(); ad.Fill(tbl);//Now


the data in DataTable (memory)
con.Close();//connection closed

foreach (DataRow item in tbl.Rows)


{
Console.WriteLine("ID is: " + item[0]);
Console.WriteLine("Name is: " + item[1]);
}

326

Chapter 12 ■ Consume Data


When DataAdapter’s Fill method is called, a query will be executed and the Fill() method
will populate the DataTable(get the data and map into DataTable). DataTable doesn’t need to
keep open the connection to populate the data, which is not the case for DataReader (in a
connected layer). This is the beauty of a disconnected layer, and it has better performance than
a connected layer as it deals with the data present in memory, which is quickly accessible.
You can also use DataSet instead of DataTable when expecting multiple resultsets. The
working is the same except it can return multiple tables. DataSet has the property of Table by
which you can iterate over specific table data.
As stated, you can perform further operations on DataTable or DataSet, such as insert,
delete, etc. (Data in memory), and these operations are fast in performance compared to
operations performed in a Connected layer. As in a connected layer, database is connected and
ADO.NET architecture takes the query and map into a database (which is time-consuming
compared to map) or performs some function on the data which is present in memory but not
at some external location (the case of a disconnected layer).
Listing 12-8 shows insertion of data in a DataTable and commits the changes to a database:
Code Snippet

Listing 12-8.  Insertion of data (disconnected layer)


string connectionString = "YOUR CONNECTION STRING
HERE"; SqlConnection con = new
SqlConnection(connectionString); con.Open();

string command = "select * from Student";//Currently has One Row(for example)


SqlDataAdapter ad = new SqlDataAdapter(command, con);

DataTable tbl = new DataTable(); ad.Fill(tbl);//Now


the data in DataTable (memory)

//Data in Memory (One Row)


foreach (DataRow item in tbl.Rows)
{
Console.WriteLine("ID is: " + item[0]);
Console.WriteLine("Name is: " + item[1]);
}

//New Record to add in DataTable


DataRow newRow = tbl.NewRow();
newRow["StudentID"] = 2;
newRow["StudentName"] = "Ali
Asad"; tbl.Rows.Add(newRow);

//Two Rows(As new row added to


DataTable) foreach (DataRow item in
tbl.Rows)
{
Console.WriteLine("ID is: " + item[0]);
Console.WriteLine("Name is: " + item[1]);
}

//Now newRow has to add in Database(Pass newRow Parameters to this insert


query) string newCommand = @"Insert into Student(StudentID,StudentName)
Values(@StudentID,@StudentName)";

327
Chapter 12 ■ Consume Data
SqlCommand insertCommand = new SqlCommand(newCommand, con);

//Create the parameters


insertCommand.Parameters.Add(new SqlParameter("@StudentID", SqlDbType.Int,
Int32. MaxValue,"StudentID"));
insertCommand.Parameters.Add(new SqlParameter("@StudentName",
SqlDbType.VarChar, 40,"StudentName"));

//Associate Insert Command to DataAdapter so that it could add into Database


ad.InsertCommand = insertCommand;

ad.Update(tbl);

con.Close();

In this example, a Parameter property of a Command object is used, which takes new
parameter- related data, such as column name, column size, and parameter name. “newRow”
added in DataTable (new record in memory) but didn’t add in database, but later used the
Update() method of DataAdpater, which reconnects to a database to take changes (i.e., updated
DataTable mapped to a database).
You can perform further operations likewise, i.e., to delete data, write a delete query, and
associate it with a DataAdapter object like da.DeleteCommand=””;, etc.

Entity Framework
The connected and disconnected layers force you to treat data in the manner of a physical
schema of a database. These layers are tightly coupled with the relational database, as the user
needs to use SQL to perform queries, and to keep in mind connection, command, DataReader,
DataSet, and DataAdapter, etc. Unlike these layers, Entity Framework gives you the
object-oriented way to interact with a database. Using this conceptual layer of ADO.NET, you
don’t need to worry about connection or command-like objects. This kind of stuff is
automatically handled by Entity Framework.
Like ADO.NET, ADO.NET Entity Framework is also a set of libraries for interaction with a
database in a different conceptual manner.
Entity Framework is an object relational mapping framework for ADO.NET. It has a
graphical view on which you can drag and drop database objects and update this view
whenever there is a change in the database. This is called the Object Relational Mapper
(ORM). This is a preferable approach to interacting with a database for those who
have/haven’t weak knowledge of SQL, because it gives object-oriented interaction with a
database as it maps the database schema into C# classes. It also makes the code short to
interact with the database and handle a lot of things by itself.
LINQ is used instead of SQL and you can use one of LINQ’s types with your data source
provided by ADO.NET (Database).

■■Note  When you perform LINQ queries to your ADO.NET data source, the entity
framework runtime generates a proper SQL statement on your behalf. But it slows down
the performance as compared to the connected and the disconnected layer.

Entity Framework (EF) normally has four approaches to use:


\ 1.\ EF Designer from database
\ 2.\ Empty EF Designer model

328

Chapter 12 ■ Consume Data


\ 3.\ Empty Code First model
\ 4.\ Code first from database
These approaches can be used by using Entity Data Model Wizard but the “Code first
from database” approach can also be used without the Entity Data Model Wizard.
Every approach has its own suitability. We will take “EF Designer from database”
approach to interact with a database.
To use the Entity Framework approach (EF Designer from database) to interact with a
database using the Entity Data Model Wizard:
Step 1:
Right-click on your project and click on Add ➤ New Item. Select “ADO.NET Entity Data
Model”, name it whatever you like (in this example I named it Sample), and click on the “Add”
button.

Figure 12-4.  Choose ADO.NET Entity Data Model

Step 2:
After clicking the “Add” button, the next step is to choose Model Contents. Choose the
first one, EF Designer from database, and click the “Next” button.

329

Chapter 12 ■ Consume Data


Figure 12-5.  Choose Model Contents

Step 3:
After clicking on the “Next” button, the next step is to choose Data Connection. Choose from
the dropdown box (Data Connection will be chosen and a Connection string will be made. Note
the name below in the TextBox that would be the object of your database. You can change this
name. I named it SchoolDB) and click the “Next” button, (move to Step 5); if not, then click on
the “New Connection” button from the window in front of you (move to Step 4).
Step 4:
If you clicked on the “New Connection” button, then specify the server name and database
and click the “OK” button.

330

Chapter 12 ■ Consume Data

Figure 12-6.  Choose Data Source/Connection String


(The "Connect to a database" panel will enable for entry when you specify the server
name.) Data Connection will be selected and a connection string will be made. Click the
“Next” button.
Step 5:
After clicking the “Next” button, choose your Database Objects and Settings. There are
checkboxes of “Tables”, “View”, and “Stored Procedures and Functions”. Select the checkbox or
checkboxes on the Database Objects which you want to add.

331

Chapter 12 ■ Consume Data

Figure 12-7.  Choose your Database Objects and Settings

I select the “Tables” in this example. After selecting, click the “Finish” button.
Wizard is now finished. Required references and a file named “Sample.edmx” shall be
added in your projects.
332

Chapter 12 ■ Consume Data

Figure 12-8.  The edmx file of added Database

Now you are ready to interact with your database.


Listing 12-9 shows how you can get all the student data using Entity Framework:
Code Snippet

Listing 12-9.  Read data using EF


//Database object
SchoolDB db = new SchoolDB();

//LINQ query to get students


var students = (from p in db.Students
select p).ToList();

foreach (var student in students)


{
Console.WriteLine("ID is: " + student.StudentID);
Console.WriteLine("Name is: " + student.StudentName);
}

333

Chapter 12 ■ Consume Data


Just the code! This is the power of Entity Framework. You don’t need anything else except
to work with a relative thing only. SchoolDB is a name that represents your database. It
contains all the things inside your database. You can use its object to access or use database
things, such as to access tables, etc.
Listing 12-10 shows how you can add a record (new student) into a student table using EF:
Code Snippet

Listing 12-10.  Insertion of data using EF


//Database object
SchoolDB db = new
SchoolDB(); //Add new Student
Student st = new Student();
st.StudentID = 3;
st.StudentName="Mubashar
Rafique";

db.Students.Add(st);
db.SaveChanges();

Console.WriteLine("Student Added!");

It is as simple as adding a list item in List. “db” is the object of the database and you can
access its tables like db.Students and, to add a new student’s object, call Add() method on
db.Students like db.Students. Add(st); this will add “st” student into Students and, after saving
the database, a new student will be added to your database.
You can perform other operations like update, delete, and find
using LINQ. Listing 12-11 shows this functionality:
Code Snippet

Listing 12-11.  Find, Update, and Delete using EF


//Database object
SchoolDB db = new SchoolDB();
//Find specific Studnet by ID (let say id is 2) var
std = (from p in db.Students
where p.StudentID == 2
select p).FirstOrDefault();

if (std != null)//if student is found


{
//Show the record
Console.WriteLine("ID is: " + std.StudentID + " Name is: " + std.StudentName);
}

if (std != null)//if student is found


{
//update the record.
std.StudentName = "Updated
Name"; db.SaveChanges();

334

Chapter 12 ■ Consume Data


if (std != null)//if student is found
{
//delete the record
db.Students.Remove(std);
db.SaveChanges();
}

These operations performed using other layers (connected and disconnected) are far easier
to perform using this layer. And the developers who haven’t much knowledge about SQL or are
bothered by the connected or disconnected layer have a better choice to interact with a
database using EF.

■■Note  Every layer has its own suitability and the choice of using it depends on the scenario and efficiency.

Consume XML and JSON Data


XML and JSON are mainly used for communication over the network between different
applications or different platforms. We will discuss these two formats of passing messages/data
over the Internet with a brief description.

XML Data
XML (Extensible Markup Language)is basically designed to store and transport data. The .NET
Framework provides classes to work with XML. These classes are present in System.Xml.dll.
You can read the XML documents as well as create them along with the implication of other
operations like edit and parse, and store XML documents in memory or on disk.
We mainly use three classes to work with XML data:
\ 1.\ XmlDocument: This class reads the entire XML into the memory and
lets you navigate and edit XML.
\ 2.\ XmlReader: This class reads the XML element vise, reads the current
element, and moves for next. It holds the current element in memory
instead of holding the entire document or XML in memory; that’s
why it is fast and less memory- consuming.
\ 3.\ XmlWriter: This class is used to create XML. It is fast way to write XML data.

■■Note  LINQ to XML provides the flexible way to interact with XML data.
For example, we have a sample XML file and want to read this data:

<Student>
<ID>1</ID>
<Name>Hamza
Ali</Name>
</Student>

335

Chapter 12 ■ Consume Data


Listing 12-12 shows how to read the above XML using XmlReader:
Code Snippet

Listing 12-12.  Read XML using XmlReader


string xml = @"<Student> <ID>1</ID>
<Name>Hamza
Ali</Name>
</Student>";

//to read xml string as a stream StringReader


sReader = new StringReader(xml);

//reader needs xml data as stream (xmlReader is ready)


XmlReader xReader = XmlReader.Create(sReader);
while (xReader.Read())//Read the entire xml
{
Console.WriteLine(xReader.Value);
}

You can also use XmlDocument to read XML:

//to read xml string as a stream StringReader


sReader = new StringReader(xml);

XmlDocument doc = new


XmlDocument(); doc.Load(sReader);
foreach (XmlNode item in doc.DocumentElement)
{
Console.WriteLine(item.InnerText);
}

DocumentElement gets the root element of XML. If you want to create an XML document,
XmlWriter will be used in this case. Listing 12-13 shows how you can write XML data using
XmlWriter:
Code Snippet

Listing 12-13.  Write XML data using XmlWriter


//Stream to store xml
StringWriter stream = new StringWriter();
using (XmlWriter writer = XmlWriter.Create(stream, new XmlWriterSettings() { Indent = true
}))//Indent to space between elements
{
writer.WriteStartDocument();//Star Doc
writer.WriteStartElement("Student");//write Elelment "Student"
writer.WriteAttributeString("ID", "1");//Student's attribute "ID" with value 1
writer.WriteElementString("Name", "Hamza Ali"); //"Name" element inside Student

with inner text "Hamza Ali"


writer.WriteEndElement();
}
Console.WriteLine(stream.ToString());//show written xml
336

Chapter 12 ■ Consume Data


You can further store the “stream” to a file.

JSON Data
JSON is another format used to transport data over the Internet. These types of formats (XML
and JSON) are used by Web Services or Web APIs. It is lightweight and more human-readable
than XML. You normally use those classes which are used for serialization of data in JSON
format. Basically, use of JSON data is the same as JSON serialization which is discussed in
Chapter 11. The .NET provides a JavaScriptSerializer class for JSON data parsing. Additionally,
we use Newtonsoft.Json library to parse JSON data. You can visit the following link to consume
JSON data using Newtonsoft.json:

http://www.newtonsoft.com/json

Working with Web Services


Web services are another way to store and retrieve data from a remote location. Data can
travel through different applications using these services.
You just need to know the address of the web service and know how to call it.
Implementation behind the calling is completely hidden from its consumers.
The .NET framework provides the facility to develop such services. You can develop this
kind of service using .NET’s technology WCF (Windows Communication Foundation) and using
Visually Designed class for creating Web Service (ASMX Service). (This feature is included in
.NET Framework 3.5 and below.) This is an old approach to create web services.

ASMX Web Service


There are two main steps to create a web service using “Visually Designed Class for web service”:
\ 1.\ Creating the Web Service
\ 2.\ Creating Proxy and Consuming the Web Service

Creating the Web Service


The following steps show the way to create the web service:
Step 1:
Open VS, create a new project by navigating to “Web”, then select ASP.NET Empty Web
Application. Name it whatever you like (in this example, I named it WebServiceInCSharp)
and click the “OK” button (the Framework selected should be “.NET Framework 3.5”).

337

Chapter 12 ■ Consume Data

Figure 12-9.  New ASP.NET Empty Web Application Project

Step 2:
After the creation of the project, right-click on project ➤ Add ➤ New Item. Select Web
Service (ASMX) and name it whatever you like (in this example, I named it SampleService) and
click the “Add” button.
338

Chapter 12 ■ Consume Data

Figure 12-10.  Add ASMX Web Service

Step 3:
After adding the SampleService, SampleService.asmx will be added in your project, which
is a Visually Designed class, i.e., your web service. It looks like:

Listing 12-14.  ASMX Web Service Class


namespace WebServiceInCSharp
{
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo =
WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the
following line.

// [System.Web.Script.Services.ScriptService]
public class SampleService : System.Web.Services.WebService
{

[WebMethod]
public string HelloWorld()
{
return "Hello World";
}

}
}

339

Chapter 12 ■ Consume Data


In this class, you can add your logics and methods to expose for consumption this web
service at the client side.

■■Note  In ASMX Web Service, Service class is implemented by the “WebService” attribute and the
methods of this class (service) are implemented by “WebMethod”. These attributes are necessary to
expose the service, otherwise the service (if the WebService attribute is missing) will not be exposed to
the client; and if class is implemented by the WebService attribute, its methods (if not implemented by
the WebMethod attribute) will not be exposed to the client. Service exposed to the client means method
prototypes are exposed to the client for use but their implementation remains hidden.

Your service is ready to use by the client side. The next steps are optional. (Follow the next
steps to test that you created service in the browser.)
Step 1:
I have also added two other functions in the SampleService.asmx file (in the SampleService
class under the HelloWorld method), like:

[WebMethod]
public int Add(int a,int b)
{
return a + b;
}

[WebMethod]
public int Subtract(int a,int b)
{
return a - b;
}

To test this service, right-click on SampleService.asmx ➤ View in the Browser.


The list of methods written in your service are the list down in front of you. The view can
give you access to use it as a client (it basically exposes the methods of the Web service).

Figure 12-11.  List of Methods exposed by Service

Step 2:
Click on any of one to test the method. (Click on Add.)
340

Chapter 12 ■ Consume Data


Figure 12-12.  Invoking Add Method of Service

As you can see from your code, add() methods take two parameters with the names a and
b and return a result. The same happens here.
Step 3:

After involving the method, the result would be viewed in the form of XML as described
XML and JSON used to transport data over the network and mainly used by a Web Service or
Web APIs.

Figure 12-13.  Respose of Add method in XML format

These are the steps for creating and testing a created web service.

341

Chapter 12 ■ Consume Data


Create Proxy and Consume the service
Creation of Proxy is important, as it registers with the client application and allows web
services’ methods to be used as local methods at the client side.
For this you must follow the following steps:
Step 1:
Create another project (C# Console Project) in your solution and name it whatever you like.
Right-click on References ➤ Add Service Reference. Click on the “Discover” button, as it will
fetch web services in your solution. You can give any address (external) for the web service you
want to consume.
Figure 12-14.  Add Service Reference to Client Project

You can change the namespace for your discovered web service. (I named it MyService,
which it will use later in the code.)
Step 2:
After clicking the “OK” button, a folder of Service References is added with necessary DLLs.
Now in your code file (Program file), simply write the following code snippet:

Listing 12-15.  Creation of Proxy to consume Web Service

//Create the proxy for your service to use its methods


MyService.SampleServiceSoapClient proxy = new
MyService.SampleServiceSoapClient();
342

Chapter 12 ■ Consume Data


int addResult = proxy.Add(5, 10);
int subtractResult = proxy.Subtract(100, 40);

Console.WriteLine("Addition Result is: " + addResult);


Console.WriteLine("Subtraction Result is: " + subtractResult);

MyService is the Namespace which you added while adding SampleService in this
project. You can create a proxy with the written class (above in code).
When there is a change in Service, you just need to update your added reference to that
service. This is done by just expanding the folder “Service Reference” in Client project.
Right-click on your service and click “Update Service Reference”.

■■Note  The class of your service is SampleService but, at the time of creating a proxy, you
have to write SoapClient (suffix) with the class name as SampleServiceSoapClient. If the
service class name is MyService the proxy class will be MyServiceSoapClient.

WCF Web Service


According to MSDN:

Windows Communication Foundation (WCF) is a framework for building service-


oriented applications. Using WCF, you can send data as asynchronous messages
from one service endpoint to another. A service endpoint can be part of a
continuously available service hosted by IIS, or it can be a service hosted in an
application.

Modern web services are being created using WCF. You can follow the simple WCF
getting started directions from the following link:

https://msdn.microsoft.com/en-us/library/ms734712(v=vs.110).aspx

■■Note  In WCF Web Service, Service class is implemented by the ServiceContract attribute (like WebService
in ASMX) and its methods are implemented by the OperationContract (like WebMethod in ASMX).

WCF web service vs. ASMX Web Service


Table 1-1.  WCF vs. ASMX Web Service
WCF Service ASMX Service
1.  WCF service can be hosted in IIS, WAS, 1.  ASMX service can just be hosted in IIS.
Console, WCF Provided Host
2.  It supports multiple communication protocols i.e.,2. It supports only HTTP.
HTTP, TCP, MSMQ, and NamedPipes.
     
3.  It uses DataContractSerializer. 3. It uses XmlSerializer.
       

343

Chapter 12 ■ Consume Data

Summary
\ 1.\ ADO.NET is a set of object-oriented libraries used to interact with a database.
\ 2.\ In a connected layer, you connect to a database as a data source and
execute queries by writing SQL. These queries are used by ADO.NET
and forwarded to your database of choice.
\ 3.\ In a disconnected layer, you normally use DataSets and DataTables
that copy the structure of a relational database in memory. A DataSet
is created as the result of an execution of query against a connected
database.
\ 4.\ Entity Framework is an object relational mapping framework for ADO.NET
\ 5.\ Web service is imaginary software available on the Internet used by
the client to expose its service in a standardized XML or JSON
messaging system.
\ 6.\ ASMX web service is a Visually Designed class (Service) that is
available to create at .NET framework 3.5
\ 7.\ WCF web service is the evolution of ASMX web service, and modern
services are being developed using a .NET Framework through WCF.

Code Challenges
Challenge 1: Create ASMX Web Service
Create a database of School with one table, Student having required fields (StudentID as
primary key and StudentName). Create a Web Service named SchoolService and include
ADO.NET for databse interactivity. The service should include two methods to add and read all
students (students should be returned to client in JSON format). Create a console project
(Client) which exposes this service and consumes the written methods in service.
[Hint] Create an Asp.NET Empty Web Project with the name SchoolService and then add
an ASMX web service named SchoolWebService. Write the Add() method for add student and
the ReadAll() method for read all students. Add an ADO.NET component into this project to
connect the service with a database. Make another class (Serializable) so that data can be sent
in serialized form. Without making data serializable, it does not send back to the client.

Practice Exam Questions


Question 1
You want to retrieve data from Microsoft Access 2013, which should be read-only. Which class you should
use?
\ A)\ SqlDataAdapter
\ B)\ DbDataAdapter
\ C)\ OleDbDataReader
\ D)\ SqlDataReader

344
Chapter 12 ■ Consume Data
Question 2
Suppose you created the ASMX Web Service named SampleService. Which class you would use
to create the proxy for this service?
\ A)\ SampleServiceSoapClient
\ B)\ SampleService
\ C)\ SampleServiceClient
\ D)\ SampleServiceSoapProxy

Question 3
Choose the correct code snippet/snippets for insert query (insert code snippet of C#, syntax vise):

A) SqlConnection con=new
SqlConnection(“ConectionString”); SqlCommand
cmd=new SqlCommand(insertQuery,con); Con.open();
Cmd.ExecuteNonQuery();
Con.close();

B) Using(SqlConnection con=new SqlConnection(“ConectionString”))


{
SqlCommand cmd=new
SqlCommand(insertQuery,con);
Cmd.ExecuteNonQuery();
}

C) SqlConnection con=new
SqlConnection(“ConectionString”); Using( SqlCommand
cmd=new SqlCommand(insertQuery,con))
{
Con.open();
Cmd.ExecuteNonQuery();
}

D) Using(SqlConnection con=new SqlConnection(“ConectionString”))


{
SqlCommand cmd=new
SqlCommand(insertQuery,con); Con.Open();
Cmd.ExecuteNonQuery();
}

Answers
\ 1.\ C
\ 2.\ A

\ 3.\ A, D

345

CHAPTER 13
Working with Cryptography
Security is an important part to cover when developing your application. You need to take
care about data privacy, user authenticity, data travel security and that data is not be
compromised.
The .NET Framework gives you a powerful way to secure your sensitive data. It gives
several algorithms which you can use in development of your application. In this chapter, we
will cover the following topics:
\ 1.\ Cryptography and Cryptanalysis
\ 2.\ Encryption and Decryption
\ 3.\ Symmetric and Asymmetric Encryption
\ 4.\ Digital Certificates
\ 5.\ Key Management
\ 6.\ Code Access Security
\ 7.\ Hashing
\ 8.\ Securing String Data

Cryptography
The word Cryptography is formed from two words: “crypto” means encrypted or hidden
and “graphy” means designing or writing or the representation of something.
Cryptography deals with the study of secret communication. It is the technique to
hide data or messages into a hidden or unreadable form.
Cryptography is mainly used to send data from an insecure channel so that data
can reach its destination successfully. It is performed by doing Encryption on data.

Encryption
Encrypt is also formed from two words: “en” means to make and “crypt” means secret or
unreadable or hidden. Therefore, encrypt means to make hidden or to make unreadable,
and the process of making unreadable is called Encryption.
It is the process of transforming a plain text into an unreadable form of cipher text by
performing some algorithms on it.
In ancient times, to do secure communication or send messages via an insecure channel to
receivers, cryptography was used. This is done by sending a messenger with an encoded
(Encrypted) message from the sender to the receiver. The receiver knew the pattern and
performed the decoding called Cryptanalysis to decode (Decrypt) the message according to a
pattern or rules set between the two parties for secret communication.

© Ali Asad and Hamza Ali 2017 347


A. Asad and H. Ali, The C# Programmer’s Study Guide (MCSD), DOI 10.1007/978-1-4842-2860-9_13

Chapter 13 ■ Working with Cryptography


The set of rules or algorithm used for encryption is known to the receiver and the
sender. This set of rules or algorithm should be kept secret from others or you can use a
public way to send your message using a key (same like a password) which should be kept
secret. The key for your algorithm controls the encryption process.

■■Info  Plain Text is a message or data that is human-readable and cipher text is
such a text which is encrypted (meaningless, unreadable).
Cryptanalysis
Decrypt is also formed from two words: “de” means remove or opposite or transform and
“crypt” means hidden or unreadable, so decrypt means transform a hidden or unreadable
message, and the process of transforming cipher text into plain text is called Decryption.

Pictorial Representation
A pictorial representation of the general encryption and decryption process is as follows:

Figure 13-1.  Representation of Encryption and Decryption Process

■■Info  Cryptography is the art or study of encryption and decryption.

348

Chapter 13 ■ Working with Cryptography

Types of Encryption
There are two types of encryption which have their own suitability for use according to two scenarios.
These are:
\ 1.\ Symmetric Encryption
\ 2.\ Asymmetric Encryption

Symmetric Encryption
Symmetric encryption is the encryption in which you send the key along with the data so that
the user can decrypt the data with the same key. It is also called shared secret encryption.
Data is secure due to symmetric encryption but it should travel to an authorized person
as a key also travels with the data. Once the data goes to an unauthorized person, data
becomes compromised as the receiver could decrypt data with the received key.
The algorithm for symmetric encryption works in the following way: the data to be
encrypted is transformed into blocks of cipher and each block has a specific size to contain
ciphered data. This is called cipher block chaining. When the data is bigger than the size of
the block (block size), data is split into multiple blocks. The block size depends on the
algorithm used.
The first block contains encrypted value of some random value called Initialization
Vector (IV) and encryption key, the next block contains encrypted value of previous block
with key and so on. If the size of last block is less than the data resides on it, the block gets
padded. Symmetric algorithm is fast than asymmetric encryption and suitable for large
amount of data.
The .NET Framework gives five different symmetric algorithms to work with.

Table 13-1.  Symmetric Algorithms


AlgorithmDescription
AES AES (Advanced Encryption Standard) is a symmetric algorithm. It was designed for both
software and hardware. It has support for 128-bit data and 128,192,256-bit key.
 
DES DES (Data Encryption Standard) is a symmetric algorithm published by National Institute
of Standard and Technology (NIST).
 
RC2 RC2 (Ron’s Code or Rivest Cipher) also known as ARC2 is a symmetric algorithm designed
  by Ron Rivest.
Rijndael Rijndael is symmetric algorithm chosen by NSA as a Advanced Encryption Standard (AES).
TripleDes TripleDes also known as 3DES (Triple Data Encryption Standard) applies DES algorithm
  three times to each data block.
   

These symmetric algorithm are defined in .NET and can be found their classes in
System.Security. Cryptography.
For example, we have a secret data: “Secret Message”, and want to encrypt it. You can
use any of the above algorithms (classes). Listing 13-1 shows how you can perform
symmetric encryption.
Code Snippet

Listing 13-1.  symmetic encryption


//specify the data
string plainData = "Secret Message";

349

Chapter 13 ■ Working with Cryptography


//convert into bytes of array
byte[] plainDataInBytes = Encoding.UTF8.GetBytes(plainData);

//Create a default cryptography object used to perform symmetric encryption


SymmetricAlgorithm symmetricAlgo = SymmetricAlgorithm.Create();

//Create encryptor with key and IV (Optional)


ICryptoTransform encryptor =
symmetricAlgo.CreateEncryptor(symmetricAlgo.Key, symmetricAlgo.IV);

byte[] cipherDataInBytes = encryptor.TransformFinalBlock(plainDataInBytes, 0,


plainDataInBytes.Length);

//get the bytes of encrypted data into string


string cipherData = Encoding.UTF8.GetString(cipherDataInBytes);

Console.WriteLine("Encrypted Data is: "+ cipherData);

■■Info  The algorithm and key used for encryption should be same while decrypting.
Data must be in bytes as System.Security.Cryptography works on bytes of data to
encrypt. SymmetricAlgorithm class is an abstract class of symmetric algorithms (Aes,
DES, etc.). You can use
its Create method to create the default object for cryptography. By default, it uses a
RijndaelManaged algorithm (a managed version of Rijndael algorithm). You can give the
name of any symmetric algorithm in Create method or can create the instance of them.
After specifying the algorithm, you specify the key and IV (which are optional) and
create encryptor. TransformFinalBlock used to transform data in bytes to cipher text.

■■Info  Whenever the encryption performs, cipher text changes.


Listing 13-2 shows how to decrypt data.
Code Snippet

Listing 13-2.  Symmetric Decryption

//Create a default cryptography object used to perform symmetric encryption


SymmetricAlgorithm symmetricAlgo = SymmetricAlgorithm.Create();

ICryptoTransform decryptor =
symmetricAlgo.CreateDecryptor(symmetricAlgo.Key, symmetricAlgo.IV);

byte[] plainDataInBytes = decryptor.TransformFinalBlock(cipherDataInBytes, 0,


cipherDataInBytes.Length);

string plainData= Encoding.UTF8.GetString(plainDataInBytes);

Console.WriteLine("Decrypted Data is: " + plainData);

To decrypt data, create decryptor and call the same function on cipher text (TransformFinalBlock).
350

Chapter 13 ■ Working with Cryptography


The output would be look like:

Figure 13-2.  Output

Asymmetric Encryption
Asymmetric encryption uses a pair of two keys instead of one for encryption. These two keys
are mathematically related to each other. One of the keys is called Public key and other one is
called Private key. You use one of the keys to encrypt data and other to decrypt data. The other
key should be from the pair of keys you generated. The encryption you do with these keys is
interchangeable. For example, if key1 encrypts the data then key2 can decrypt it and if key2
encrypt the data then key1 can decrypt it, because one of them can be given to everyone and
the other one should be kept secret.
The data gets encrypted with the receiver’s public key and can only be decrypted by the
private key from the specific receiver because only that user should have access to the
private key.
The public key transmits along the data while the secret key kept with the recipient.
Asymmetric encryption avoids sharing the encryption key; that’s why it is more secure than
a symmetric key. But, on the other hand, it is slower than symmetric encryption.
The .NET Framework provides several Asymmetric algorithms to work with.

Table 13-2.  Asymmetric Algorithms


Algorithm Description
RSA RSA is an asymmetric algorithm commonly used by modern computers.
DSA DSA (Digital Signature Algorithm), produced by NIST, is a standard to create digital
signatures for data integrity.
 
ECDsa ECDsa (Elliptic Curve Digital Signature) offers variant of the DSA.
ECDiffieHellmanProvides a basic set of operations that ECDH implementations must support.
   

These Asymmetric algorithms are defined in .NET and their classes can be found in
System.Security. Cryptography.
Take the above used example in symmetric encryption and perform any of the
asymmetric algorithm provided for encryption to show how it works.
Listing 13-3 shows how to generate keys used for DSA asymmetric encryption.
Code Snippet

Listing 13-3.  DSA asymmetric enryption

//Creation of asymmetric algo object


RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();

//saving the key information to RSAParameters structure


RSAParameters RSAKeyInfo =
rsa.ExportParameters(false);

351

Chapter 13 ■ Working with Cryptography


//generating both keys( public and private)
string publicKey = rsa.ToXmlString(false);
string privateKey = rsa.ToXmlString(true);

ToXmlString method returns the public or private key based on the Boolean value.
To generate a private key make the value true, and for a public key the value shall be
false.
Now we have two interlinked keys of an asymmetric algorithm. If A wants to send data to
B then both parties should have an understanding about the pattern or keys used for
communication between them.
The recipient (B) should have the private key for decryption and the sender (A) will
encrypt data using the public key. The data that traveled to B will only be decrypted with the
secret key which generated along with the public key (used for encryption).
Listing 13-4 shows how to encrypt data with the available or obtained public key and
decrypt with the private key.
Code Snippet

Listing 13-4.  encrypt and decrypt data with public key, private key
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();

//Encrypting Code (On Sender side)

//data to encrypt
string data = "Secret Message";
//convert into bytes
byte[] dataInBytes = Encoding.UTF8.GetBytes(data);

//Specify the public key obtained from receiver


rsa.FromXmlString(publicKey);

//Use Encrypt method for encryption


byte[] encryptedDataInBytes = rsa.Encrypt(dataInBytes, true);

//get the bytes of encrypted data into string


string encryptedData = Encoding.UTF8.GetString(encryptedDataInBytes);

Console.WriteLine("\nEncrypted Data is: "+ encryptedData);

//Decrpyting Code (on receiver side)

//Specify the private key


rsa.FromXmlString(privateKey);
//Use Decrypt method for encryption
byte[] decryptedDataInBytes= rsa.Decrypt(encryptedDataInBytes, true);

//get the bytes of decrypted data into string


string decryptedData = Encoding.UTF8.GetString(decryptedDataInBytes);

Console.WriteLine("Decrypted Data is: "+ decryptedData);

You can use a private key (instead of public) for encryption and public for decryption.
One could be known to all and the other must be secret.
352

Chapter 13 ■ Working with Cryptography

■■Info  Combining a symmetric and an asymmetric algorithm can be more secure and help
you to transmit a larger amount of data.

Implement Key management


The management of Keys used for encryption is an important part of cryptography process. In
a Symmetric algorithm, a key and an IV are required to generate. The key must be secret and
only known to the receiver so that others can not decrypt data. Asymmetric requires the
creation of two keys where one should be public and the other must be private.

Symmetric Keys
In a symmetric algorithm, keys must be private, whereas there is no compulsion for an IV.
Listing 13-5 shows how to create a symmetric key and an IV.
Code Snippet

Listing 13-5.  Creation of symmetric key and IV


SymmetricAlgorithm symmetric =
SymmetricAlgorithm.Create(); symmetric.GenerateIV();
symmetric.GenerateKey();

Asymmetric Keys
When the instance of an asymmetric algorithm created, a key pair of public and private key
generated. ToXmlString method returns a key in XML form and ExportParameters returns
RSAParameters that hold information of key.
The private key should be stored securely so that no unauthorized person can steal it. For
this purpose, you should use a key container to manage the private key.
Listing 13-6 shows how to store a private key in a key container.
Code Snippet

Listing 13-6.  store private key in key container

//Creating the container

CspParameters parameter = new


CspParameters(); parameter.KeyContainerName
= "KeyContainer";

//Creation of asymmetric algo object


RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(parameter);

//saving the key information to RSAParameters structure


RSAParameters RSAKeyInfo =
rsa.ExportParameters(false);

string privateKey = rsa.ToXmlString(true);


Console.WriteLine("Key is stored in Container"+ privateKey);

353

Chapter 13 ■ Working with Cryptography


Listing 13-7 shows how to delete key from key container.
Code Snippet

Listing 13-7.  delete key from key container

//Creating the container

CspParameters parameter = new CspParameters();


parameter.KeyContainerName = SET THE NAME OF THAT KEY CONTAINER USED TO STORE KEY;

//Creation of asymmetric algo object


RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(parameter);

//saving the key information to RSAParameters structure


RSAParameters RSAKeyInfo =
rsa.ExportParameters(false);

rsa.PersistKeyInCsp = false;
rsa.Clear();

Console.WriteLine("Key is Deleted");

You can also read the key from the container.

Encrypt Stream
Streams are covered in Chapter 10. Encrypting the data that goes through streams for privacy
and integrity is also important.
C# provides a class, CryptoStream, for the encryption of data that travels
through streams. Listing 13-8 shows how you can encrypt data.
Code Snippet

Listing 13-8.  encrypt stream


string message = "SECRET MESSAGE";

SymmetricAlgorithm symmetric = SymmetricAlgorithm.Create();

ICryptoTransform encryptor = symmetric.CreateEncryptor(symmetric.Key, symmetric.IV);

MemoryStream memoryStream = new MemoryStream();

//crptoStream know encrptor and stream in which data to written


CryptoStream crptoStream = new CryptoStream(memoryStream, encryptor,
CryptoStreamMode. Write);

//writer has reference of cryptoStream (what to encrypt and where)


using (StreamWriter streamWriter = new
StreamWriter(crptoStream))
{
//write the ecrypted message into memeory
stream streamWriter.Write(message);
}
354
Chapter 13 ■ Working with Cryptography
//close cryptoStream
crptoStream.Close();
//Close memoryStream
memoryStream.Close();

Listing 13-9 shows how to decrypt data.


Code Snippet

Listing 13-9.  decrypt stream

ICryptoTransform decryptor = symmetric.CreateDecryptor(symmetric.Key, symmetric.IV);

MemoryStream memoryStream = new MemoryStream(CIPER_TEXT_HERE);

CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor,


CryptoStreamMode. Read);

using (StreamReader streamReader = new StreamReader(cryptoStream))


{
string decryptedData = streamReader.ReadToEnd();
}

Working with ProtectedData Class


Without worrying about using the encryption algorithm (i.e., symmetric or asymmetric), you
can still protect your data by using the ProtectedData class.
In a .NET framework, a ProtectedData class contains two static methods:
\ 1.\ Protect()
\ 2.\ Unprotect() to decrypt the data
The ProtectedData class is a part of the System.Security.Cryptography namespace;
to add the namespace in a project you must add the System.Security assembly in the
references folder.

Protect()
Protect method is the static method of ProtectedData class; it is used to encrypt the data. It
contains the following method signature:

public static byte[] Protect(byte[] userData,byte[] optionalEntropy,


DataProtectionScope scope)

• userData: An array of bytes that contains data to be encrypted.


• optionalEntropy: Is an optional byte array that is used to increase the
complexity of the encryption, or null for no additional complexity.
• scope: It takes the value of the DataProtectionScope enumeration that
specifies the scope of encryption.

355

Chapter 13 ■ Working with Cryptography


Code Snippet

Listing 13-10.  encrypt by Protect method

string message = "Hello World";


//Convert data into a byte array
byte[] userData = Encoding.UTF8.GetBytes(message);

//encrypt the data by using ProtectedData.Protect method


byte[] encryptedDataInBytes = ProtectedData.Protect(userData, null, DataProtectionScope.
CurrentUser);

string encryptedData = Encoding.UTF8.GetString(encryptedDataInBytes);

Console.WriteLine("Encrypted Data is: " + encryptedData);

Explanation
The string data convert into a byte array and then the Protect method encrypts it, while
DataProtectionScope.CurrentUser specifies only the current user can decrypt the encrypted
data. DataProtectionScope is enumeration. CurrentUser means only the current user can
encrypt the data and LocalMachine means all the users of a local machine can encrypt data.

Unprotect
Unprotect method is the the static method of the ProtectedData class; it is used to decrypt
the encrypted data. It contains the following method signature:

public static byte[] Unprotect(byte[] userData,byte[] optionalEntropy,


DataProtectionScope scope)

• userData: An array of bytes that contains data to be encrypted.


• optionalEntropy: Is an optional byte array that is used to increase the
complexity of the encryption, or null for no additional complexity.
• scope: It takes the value of DataProtectionScope enumeration that
specifies the scope of encryption.

The method signature of both Protect and Unprotect methods are the same.
Code Snippet

Listing 13-11.  decrypt by UnProtect method


byte[] decryptedDataInBytes = ProtectedData.Unprotect(encryptedDataInBytes, null,
DataProtectionScope.CurrentUser);

string decryptedData = Encoding.UTF8.GetString(decryptedDataInBytes);

Console.WriteLine("Decrypted Data is: " + decryptedData);

356

Chapter 13 ■ Working with Cryptography


Explanation
The encrypted data is decrypted by using the Unprotect method; it takes data that is
encrypted (i.e., encryptedDataInBytes) and then decrypts it.

Manage and Create Digital Certificates


A digital certification uses hashing and asymmetric encryption to authenticate the identity of
the owner (signed object) to others. An owner of the certificate contains both public and private
keys. The public key is used to encrypt the sent message while the private key is used to decrypt
it; only the owner of the certificate has access to the private key to decrypt the encrypted
message. This way, digital certificates enable the integrity of data.
A digital certificate is part of a public key infrastructure (PKI). A public key infrastructure
is a system of digital certificates, certificate authorities, and other registration authorities to
verify and authenticate the validity of each involved party.

Create and Install Certificate


Certificate Authority (CA) is a third-party tool that is used to issue a certificate. Each
certificate contains a public key and the data, such as, a subject to which the certificate is
issued, a validity date for how long the certificate will remain validated, and the information
about the issuer who issued the certificate.
We'll use a tool, Makecert.exe, that will help us to create an X.509 digital certificate, which
is commonly used to authenticate clients and servers, encrypt, and digitally sign messages.
Follow the following steps to create a digital certificate:
\ 1.\ Run Command Prompt as Administrator.
\ 2.\ To create a digital certificate, enter the following command:
makecert {Certificate_Name}.cer
\ 3.\ makecert myCert.cer
The above command will create a certificate file of name “myCert.cer”. To use the
generated certificate file, you must install it in your machine to be able to use it. Certificate
Store is a place where you stored the certificate after installation.
Follow the following steps to create and install a certificate.
\ 1.\ Run Command Prompt as Administrator.
\ 2.\ To create a digital certificate, enter the following command:
makecert {Certificate_Name}.cer

makecert -n "CN=myCert" -sr currentuser -ss myCertStore

The above command will create and install a certificate file.

Working with System.Security Namespace


The System.Security namespace contains the fundamental building blocks of a .NET code
access security framework. Child namespace System.Security.Permissions provides Code
Access Security (CAS), which protects your computer from malicious code.

357

Chapter 13 ■ Working with Cryptography


Code Access Security (CAS)
The CLR in .NET Framework enforces security restrictions to use third party resources. You
must ask for permission to access and manipulate the protected resources of third party
tools.
There are two ways to specify CAS in C# code:
\ 1.\ Declarative
\ 2.\ Imperative

Declarative
In a declarative way, we use attributes to apply security information.
Code Snippet

Listing 13-12.  Declarative CAS


[FileIOPermission(SecurityAction.Demand,
AllLocalFiles =
FileIOPermissionAccess.Read)]
public void MyDeclarativeCAS()
{
// Method body
}

Imperative
In an imperative way, we explicitly ask for the permission in the code.
Code Snippet

Listing 13-13.  Imperative CAS


FileIOPermission fp = new FileIOPermission(PermissionState.None);
fp.AllLocalFiles = FileIOPermissionAccess.Read;
fp.Demand();

FIleIOPermissionAccess.Read will explicitly allow the read-only file access.

Hashing
Hashing (performing hashing algorithms) is the process of converting data into short and
fixed length unreadable form. This process is irreversible, i.e., you cannot convert hashed data
back to the original one. Every time you generate hash for specific data, it will be the same
output (hashed form). It is used to check the integrity of data, string comparison, Data
authenticity and, most importantly for security, password storage. Unlike encryption, Hashing
is a one-way process.
C# provides several algorithms of hashing to work with. Table 13-3 shows the hash algorithms provided
in System.Security.Cryptography.

358

Chapter 13 ■ Working with Cryptography


Table 13-3.  Hashing Algorithms
Algorithm Description
SHA1 SHA1 is a cryptography hash function, resulting in a 160-bit hash value.
SHA256 SHA256 is a cryptography hash function, resulting in a 256-bit hash value.

SHA512 SHA512 is a cryptography hash function, resulting in a 512-bit hash value.


SHA384 SHA384 is a cryptography hash function, resulting in a 384-bit hash value.
RIPEMD160RIPEMD (RACE Integrity Primitives Evaluation Message Digest) 160 is a cryptography
 
hash function, similar in performance to SHA1.
   

These algorithms (classes) are defined in .NET and can be used to perform hashing. You can
use any of the above hashing algorithms. We use SHA256 in the example to understand how it
is performed.
For example, you have a password and want to store it in your database so that if anyone
ever stole the database, the hacker would not know the password as it would be in unreadable
form.
Listing 13-14 shows how to perform hashing.
Code Snippet

Listing 13-14.  Hashing


//password to be hashed
string password = "HelloWorld";

//password in bytes
var passwordInBytes = Encoding.UTF8.GetBytes(password);
//Create the SHA512 object
HashAlgorithm sha512 =
SHA512.Create();

//generate the hash


byte[] hashInBytes = sha512.ComputeHash(passwordInBytes);

var hashedData = new StringBuilder();


foreach (var item in hashInBytes)
{
hashedData.Append(item);
}

Console.WriteLine("Hashed Password is: " + hashedData.ToString());

You can save a hashed password into a database or compare a logged-in user by converting
the password into hashed form and comparing its hash with an already stored hashed value of
that specific user.
There is a problem in this process. Every time a request is being sent or a user logs in, the
same hash is generated. So the hacker can track down the traffic through a communication
channel and the hacker gets to know that each time the data is traveled, the
password/message/hashed value is the same. Therefore, the hacker can send the same value
without knowing what it is and can successfully enter in your system, which is a security
breach.

To avoid such a problem, salt hashing comes in handy.

359

Chapter 13 ■ Working with Cryptography


Salt Hashing
Salt is non-repetitive random data that is added with the hashed value to make it unique
every time it is generated.
Listing 13-15 shows how to perform salt hashing.
Code snippet

Listing 13-15.  Salt Hashing


//password to be hashed
string password = "HelloWorld";

//generate Salt (GUID is globally uniqe identifer)


Guid salt = Guid.NewGuid();

//Merge password with random value


string saltedPassword = password + salt;

//password in bytes
var passwordInBytes = Encoding.UTF8.GetBytes(password + salt);

//Create the SHA512 object


HashAlgorithm sha512 =
SHA512.Create();

//generate the hash


byte[] hashInBytes = sha512.ComputeHash(passwordInBytes);

var hashedData = new StringBuilder();


foreach (var item in hashInBytes)
{
hashedData.Append(item);
}

Console.WriteLine("Unique hashed Password is: " + hashedData.ToString());


NewGuid method created a global unique identifier, i.e., it changed a value
concatenation with a password to generate a different hash every time the code runs;
hence, salt hashing protects you from a security attack by hackers.
C# provides GetHashCode() method on every instance to generate its hash code, which
is normally used for a string or value comparison .

■■Tip  Use GetHashCode() method for comparing values instead of comparing values itself.

Choosing an appropriate Algorithm


When you have multiple algorithms for performing encryption or hashing, then it is important
to choose the best algorithm with respect to the scenario. The following points illustrate the
usage of different commonly used algorithms with respect to the scenario.

360

Chapter 13 ■ Working with Cryptography


\ 1.\ When there is a scenario to deal with more sensitive data, you
should use Asymmetric encryption instead of symmetric
encryption.
\ 2.\ When there is a scenario for data privacy, use Aes (Symmetric algorithm).
\ 3.\ When there is a scenario for Data Integrity, use
HMACSHA256 and HMACSHA512 hashing algorithms.
\ 4.\ When there is a scenario for digital signing (Digital Signature), use ECDsa and RSA.
\ 5.\ When there is a scenario to generate a random
number, use RNGCryptoServiceProvider.

■■Info  You can read more about this topic from the following link:
https://msdn.microsoft.com/en-us/library/0ss79b2x(v=vs.110).aspx

Working with SecureString Class


When working with secure strings (data) such as passwords or credit card numbers (which
are commonly in string formats), we normally use string class or type to store or work with
them. This is inappropriate because string stores your data in plain text, so your sensitive
data is open for attack. String class is
also immutable, which leaves copies in memory on every change which could be
compromised as it is impossible for a garbage collector to clear all the copies of data.
In such a situation, C# provides SecureString class to work with your sensitive strings. It
can be found in the System.Security namespace. It makes your string more secure. SecureString
automatically encrypts the string and stores it in a special memory location. It is mutable and
implemented by IDisposable; that's why there is not a problem of multiple copies of data and
the impossibility of a garbage collector to clear all copies. Whenever you are done working
with SecureString, you can make sure its content is removed from memory, using IDisposable.
SecureString does not properly secure the data but minimizes the risk for data to be
compromised. It takes string character by character, not the whole string at all. When
necessary, you can make the string encrypted by SecureString as just read-only.
Listing 13-16 shows how you can secure the string using SecureString class.
Code Snippet

Listing 13-16.  SecureString class to secure sensitive data

SecureString secureString = new SecureString();

Console.Write("Please enter your Credit Card Number: ");


while (true)
{
ConsoleKeyInfo enteredKey =
Console.ReadKey(true); if (enteredKey.Key ==
ConsoleKey.Enter)
break;
secureString.AppendChar(enteredKey.KeyChar);
Console.Write("#");
}
secureString.MakeReadOnly();

//When done with SecureString, Dispose the content so that it does not remain in memory
secureString.Dispose();
361

Chapter 13 ■ Working with Cryptography


You can also read the encrypted string (by SecureString) using the special class Marshal,
which can be found in System.Runtime.InteropServices. Reading the encrypted string makes the
string decrypted and returns it as a normal string (plain text) so you must clear the normal
string from memory after reading; even these would be an exception. So encapulate reading
code with try/catch/finally block.
Listing 13-17 shows how you can read the string as plain text.
Code Snippet

Listing 13-17.  Read the data protected by SecureString


IntPtr plainTextAsIntPtr = IntPtr.Zero;
try

{
//Decrypt string (as a IntPtr)
plainTextAsIntPtr = Marshal.SecureStringToGlobalAllocUnicode(secureString);
Marshal.PtrToStringUni(plainTextAsIntPtr);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
//This method CLeared dycrypted string from memory
Marshal.ZeroFreeGlobalAllocUnicode(plainTextAsIntPtr);
Console.WriteLine("Memory Cleared.");
}

The Marshal class gives a method for decrypting string along with a method to clear
the content of decrypted string from memory.
SecureStringToGlobalAllocUnicode() method is static and is used to read the secure string
and return the address of a memory location which contains the value as IntPtr (pointer).
That pointer contains the address of the memory location, and is converted to a string (value)
that the pointer contains (points to).
ZeroFreeGlobalAllocUnicode() method is also static and is used along with
SecureStringToGlobalAllocUnicode() method to free the content of the decrypted string from
memory. Marshal class also provides other methods for reading the secure string along with
their respective methods for disposing the decrypted content from memory. You can find out
about them and more about SecureString from the following link:

https://msdn.microsoft.com/en-us/library/system.security.securestring(v=vs.110).aspx

Summary
\ 1.\ Encryption is the process of converting plain text into cipher text.
\ 2.\ Decryption is the process of converting cipher text into plain text.
\ 3.\ Symmetric Encryption uses one key to encrypt and decrypt data.
\ 4.\ Asymmetric Encryption uses two mathematically linked keys: public
and private. One of them is used to encrypt the data and other is used to
decrypt the data.

362

Chapter 13 ■ Working with Cryptography


\ 5.\ Digital Certificates are used for the authenticity of an author.
\ 6.\ Hashing is the process of converting data into long unreadable
form and it cannot be converted back.

Code Challenges
Challenge 1: Develop a simple window form application and
perform Salt hashing
Create a simple database table of login to store username, hashed password, and GUID value
(salt value). Use the Connected Layer approach to interact with the database. Make two forms:
Registration and Login. Register the user (password should be inserted in hashed form) and
then log in the user successfully.

Practice Exam Questions


Question 1
The application needs to encrypt highly sensitive data. Which algorithm should you use?
\ A)\ DES
\ B)\ Aes
\ C)\ TripleDES
\ D)\ RC2

Question 2
You are developing an application which transmits a large amount of data. You need to
ensure the data integrity. Which algorithm should you use?
\ A)\ RSA
\ B)\ HMACSHA256
\ C)\ Aes
\ D)\ RNGCryptoServiceProvider

Question 3
Salt Hashing is done by:
\ A)\ Merging data with random value and perform cryptography.
\ B)\ Merging data with random value and perform cryptanalysis.
\ C)\ Merging data with random value and perform encryption.
\ D)\ Merging data with random value and perform hashing.

363
Chapter 13 ■ Working with Cryptography
Answers
\ 1.\ B
\ 2.\ B

\ 3.\ D

364

CHAPTER 14
Assembly and Reflection
Assembly is an important part in any .NET application development, while reflection is used to
read all the information of an assembly at runtime. In this chapter, we'll learn:
\ 1.\ Assembly
\ 2.\ Creation and Use an Assembly
\ 3.\ Installing an Assembly in a Global Assembly Cache
\ 4.\ Reflection in C#
\ 5.\ Creating and Using Custom Attributes

Introduction to Assemblies
An assembly is the output of the compiled code. It’s a physical code that is used to deploy an
application. In a .NET, assembly it is typically a Dynamic Link Library (DLL) or an Executable
(EXE) file.

When Code is Compiled


When code is compiled, a compiler converts the source code into Microsoft Intermediate Language
(MSIL) code; it is a CPU-independent code. The Common Language Runtime (CLR) uses Just In
Time (JIT) compiler to convert MSIL code into a native code to the operating system. This MSIL
code is available in a portable executable (PE) file, which helps in executing an assembly.
When a compiler converts the source code into MSIL code it also creates its metadata.
Metadata stores information about the data stored in an assembly. For example, it contains
information of the types available in that assembly, their containing namespaces, base class of
each available type, the interfaces it implemented, its methods and their scope, each method’s
parameters, each type’s properties, and so on. In other words metadata is the encrypted
documentation of a source code.
When a code is compiled successfully, an assembly manifest file is generated. It’s an XML
file which contains information about assembly, like its name, version number, and an
optional strong name that uniquely identifies the assembly. It also contains the names of other
reference assemblies used in the code.

Types of Assembly
Assembly is typically divided into two types:
\ 1.\ Private Assembly
\ 2.\ Public Assembly

© Ali Asad and Hamza Ali 2017 365


A. Asad and H. Ali, The C# Programmer’s Study Guide (MCSD), DOI 10.1007/978-1-4842-2860-9_14

Chapter 14 ■ Assembly and Reflection


Private Assembly
A private assembly (.dll or .exe) can be used by only a single application. Generally private
assembly is found in the application root folder.
If another application tries to refer a private assembly, it must used store a copy of that
private assembly in its root directory, otherwise the application won't be able to deploy
succussfully.

Public/Shared Aseembly
A public assembly (.dll or .exe) can be used by multiple applications at a time. It is also
known as a shared assembly, which is stored in Global Assembly Cache (GAC). This shared
assembly also known as a strong name assembly.
Generally, when an application is deploying, it doesn't need a public assembly to be
referenced in the root folder of the application.

Uses of Assembly
Assembly has many uses; some of the important uses of assembly are given below:
\ 1.\ Assembly allows component-based development, which means multiple
assemblies can reuse each other’s types, methods, and classes to build a
software product.

\ 2.\ Assemblies help in versioning, which is useful to archive


previously built assemblies.
\ 3.\ Assembly enables security, which can manage by specifying the level of
trust for code from a particular site or zone.
\ 4.\ Assembly supports culture and language, so when an application is
deployed it can display results according to a specific culture or
language.

Creating and Using Custom Assembly


An assembly is either a .DLL or an .EXE file.

Dynamic Link Library (.DLL)


Dynamic Link Library (.DLL) is a class library that contains namespaces, types, and methods to
be reused by other applications. For example, “System” is a .dll, which is a Class library that
contains namespaces, types, and methods that we reuse in our application, i.e.,
Console.WriteLine(“”);

Create a Custom .DLL


These steps will help you to create a .dll
\ 1.\ Open Visual Studio
\ 2.\ Select Visual C#
\ 3.\ Select Class Library as a project template

366

Chapter 14 ■ Assembly and Reflection


\ 4.\ Enter name for project
\ 5.\ Click Ok
Figure 14-1.  Create an Empty C# Class Library Project

The following code will be shown:

using System;

namespace MyCustomLibrary
{
public class Class1
{
}
}

You can clearly see that class library project doesn't have a Main Method. Therefore, a
class library doesn't have an entry point.
Let's create a simple method inside Class1, which returns the square of an integer.

Listing 14-1.  Create a Method Inside a Class Library


using System;

namespace MyCustomLibrary

367

Chapter 14 ■ Assembly and Reflection


{
public class Class1
{
public static int square(int i)
{
return (i * i);
}
}
}

Press Ctrl + Shift + B to build the assembly.


Now, go to the Bin ➤ Debug folder of the project; there you'll see a
MyCustomLibrary.dll file. Congratulations, you've created your first .DLL file.

Figure 14-2.  Output Files of Class Library Project

■■Note  A member of the class library must be public, so a member can be accessable in another project.

Use a Custom .DLL


A custom .dll can be reused in another application by copying and pasting the .dll file in the root
folder of the application, and then referencing its file path in the Reference Folder.
In the example, we're creating an empty console application and then we'll use myCustomLibrary.dll.
\ 1.\ Create an Empty C# Console Application.
\ 2.\ Copy the MyCustomLibrary.dll in the root directory of Console App.
\ 3.\ Right-click on References Folder in Solution Explorer and click Add Reference.
368

Chapter 14 ■ Assembly and Reflection

Figure 14-3.  Add Custom .DLL File Reference

\ 4.\ A new window will pop up; click on the “Browse” button to select the
MyCustomLibrar.dll that you've copied in the root folder of the
console app and then Click Okay.
Now, MyCustomLibrary.dll shall be available in the References folder of Console App.

369

Chapter 14 ■ Assembly and Reflection


Figure 14-4.  List of .DLL Files in References Folder

We can use all the public methods and types available in MyCustomLibrary in our
console app. For example, we can write the following code in Main Method.

Listing 14-2.  Read a Class Library Inside Main Method

static void Main(string[] args)


{
int i = 4;
int sqri = MyCustomLibrary.Class1.square(i);

Console.WriteLine("Square of {0} = {1}",i, sqri);


}
//Output
Square of 4 = 16

When code is compiled it matches all the types and methods with the assemblies
referenced in the References folder; if the the types or method aren’t defined in the project
or in the reference assemblies, then an error will occur.

Executeable (.EXE)
Executeable assemblies are those assemblies which have a Main Method defined in it. Main
Method is the entry point of executable assemblies to run. In an operating system, these
executeable files take processor and memory (Stack, Heap) for running, for example, Console
App, Windows Form App, WPF App, etc.

370

Chapter 14 ■ Assembly and Reflection

WinMD Assembly
The concept of WinMD Assembly was introduced when Windows 8 came out. WinMD stands
for Windows Meta Data. It allows the communication between different programming
languages. For example, WinMD library built with C# can be used in a C++ project. It removed
the language barrier through Windows Runtime Component.

Create WinMD Assembly


WinMD Assembly is used in store apps; to make the WinMD Assembly, you must install
Windows 8.1 SDK. At this point, I assume your Visual Studio has Windows 8.1 SDK installed.
Now, to create the WinMD Assembly, follow these steps:
\ 1.\ Open Visual Studio.
\ 2.\ Create a new project, select Visual C#➤ Windows Store, and
then select Windows Runtime Component as a project
template and select Okay.
Figure 14-5.  Create an Empty Windows Runtime Component

Now, create a static method that returns the square root of an integer value.

371

Chapter 14 ■ Assembly and Reflection


Listing 14-3.  Create a Method Inside a Windows Runtime Component Project
public sealed class Class1
{
public static int square(int i)
{
return (i * i);
}
}

Build the assembly and now you can use it in any store app template of any language, i.e., VB.NET, F#,
etc.

■■Note  In WinMD assemblies, all types must be sealed, and if polymorphism is required
then use interface on the sealed classed to implement polymorphism.

Global Assembly Cache (GAC)


Global Assembly Cache is a common shared location of a system to store assemblies of all .NET
applications that run on a certain machine. These assemblies are shared by several .NET
applications on the computer.
A developer can install his own developed assembly in GAC. It not only gives the
advantage of sharing the same assembly among multiple .NET application but also helps in
providing special security. For example, not all users can delete the assembly; only
administrators can remove the assembly.
In addition to security, assembly installed in GAC gives the ability to archive and use
multiple versions of the same assembly.

Install an Assembly in GAC


An assembly (.DLL) can install in GAC only if it has a strong name. The following steps are
required to install an assembly (.DLL) into GAC.
\ 1.\ Create Strong Name Key and associate it with assembly.
\ 2.\ Use gacutil.exe.

Strong Name
It ensures the uniqueness among assemblies (even with assemblies having the same names)
by unqiue key pairs. A strong name consists of a unique identity with its public key, version
number, text name, culture information, and a digital signature.
Create a Strong Name Key
Strong Name Key is file that needs to be associated with the assembly to make it a strong name assembly.
\ 1.\ Open Visual Studio and create an empty C# Class Library Project
(build the project by pressing ctrl+shift+B).
\ 2.\ Goto StartMenu ➤ Find, Visual Studio Command Prompt ➤
Run it as Administrator.
372

Chapter 14 ■ Assembly and Reflection


\ 3.\ Use Visual Studio Command Prompt to navigate to the root folder of
class library project. e.g., cd “{PathLocation}” press enter (cd
“C:\Users\aliso\Documents\
visual studio 2015\Projects\myClassLibrary”). ORcd "{PathLocation}”
press enter.  

\ 4.\ Inside the project’s root folder, create a strong name key by writing a
command ‘“sn -k {KeyName}.snk” (for example, “sn -k
myClassLibrarykey.snk”), and press Enter.

Associate Strong Name Key with Assembly


When a strong name key is generated, it must be associated with an assembly so that the
assembly can become a strong name assembly. To do this, follow the following simple
steps.
\ 1.\ Open the AssemblyInfo.cs file in Visual Studio .NET Solution Explorer,
(This file is underneath the Properties file of solution explorer.)
\ 2.\ Associate a strong name key with the assembly by adding an assembly
attribute and location of a strong name key, such as [assembly:
AssemblyKeyFile("myCl assLibrarykey.snk")]
\ 3.\ Press ctrl + shift + B. This will associate the strong name key pair with the
assembly. Remember, Visual Studio must be runing as Administrator.

Use Gacutil.exe
Gacutil.exe is a tool that is used to install a strong name assembly into a global assembly
cache. Follow the following steps to install a strong name assembly into GAC:
\ 1.\ Run Visual Studio Command Prompt as Administrator.
\ 2.\ Enter the following command to install a strong name assembly into GAC:

gacutil -i "{file path of strong name assembly}.dll"

for example, gacutil -i "C:\Users\aliso\Documents\Visual Studio


2015\Projects\
myClassLibrary\myClassLibrary\bin\Debug\myClassLibrary.dll"
OR
If the command prompt already navigated to the folder where a
strong name assembly is stored, then you can directly enter the
following command:

gacutil -i myClassLibrary.dll

AssemblyInfo.cs
Whenever a new .NET assembly project is created in Visual Studio, a file named AssemblyInfo is
created that contains attributes used to define, name, description, version, etc.
The AssemblyInfo.cs file can be found in the solution explorer ‘Properties➤AssemblyInfo.cs’.

373
Chapter 14 ■ Assembly and Reflection

Figure 14-6.  AssemblyInfo.cs

Versioning the Assembly


In the development lifecycle (e.g., Development, Test, Production), versioning the assembly
helps team members to identify multiple versions of the same assembly, which helps in
troubleshooting a problem or identifying which assembly to deploy.
In .NET, Versioning is done only on assemblies with strong names. An attribute
“AssemblyVersion” is used to manage the versioning of the assembly.

[assembly: AssemblyVersion("{Major}.{Minor}.{Build Number}.{Revision}")]


OR
[assembly: AssemblyVersion("1.0.0.0")]

It consists of four important parts:


\ 1.\ Major Version
\ 2.\ Minor Version
\ 3.\ Build Number
\ 4.\ Revision

Major Version
An integer value, incremented for major releases, such as adding new features.

Minor Version
An integer value, incremented for minor releases, such as introducing small changes to existing features.

Build Number
An integer value, typically incremented automatically as part of every successful build
performed on the Build Server. This allows each build to be tracked and tested.

Revision
An integer value, incremented on builds which is released with hotfixes or patches.

374

Chapter 14 ■ Assembly and Reflection

Reflection in C#
Reflection is used to read attributes (metadata) to obtain information of all assemblies,
modules, and types of a running application.
Basically, reflection converts binary (Low-Level) information to human-readable
(High-Level) language and allows humans (developers) to manipulate the data and behavior of
an application at runtime.
In terms of processing, reflection costs a lot of processor power because, by using
metadata, it reverse engineers all the binary data to readable data.

Working with Reflection


System.Reflection namespace contains tons of classes that dynamically allow you to create
and use types, methods, and properties of a running application.

Use Reflection to Read Current Assembly


System.Reflection.Assembly class contains methods and properties used to read and
manipulate information of an assembly at runtime.
Code Snippet

Listing 14-4.  Use Reflection to Read Current Assembly

using System;
using System.Reflection;

namespace DemoAssembly
{
class Program
{
static void Main(string[] args)
{
//Get current loaded assembly
Assembly assembly = Assembly.GetExecutingAssembly();

//Get Full Name of the current Assembly


string assemblyName =
assembly.FullName;

Console.WriteLine(assemblyName);
}
}

}
//Output
DemoAssembly, Version = 1.0.0.0, Culture = neutral, PublicKeyToken = null

Explanation
(Listing 14-4) When an application is running, it gets the metadata of the current assembly and
reads its full- name. Assembly Fullname always contains 4 parts, which describe the assembly
name, assembly version number, and assembly culture, and tells if the asssembly is strong
named (if it has a public key token associated with it).

375

Chapter 14 ■ Assembly and Reflection


Use Reflection to Read all Types of an Assembly
Reflection can also read all the types defined in a particular assembly at runtime.
Code Snippet

Listing 14-5.  Use Reflection to Read all Types in Current Assembly


using System;
using System.Reflection;

namespace DemoAssembly
{
class Program
{
static void Main(string[] args)
{
//Get current loaded assembly
Assembly assembly = Assembly.GetExecutingAssembly();

//Get all types defined in an assembly


Type[] types = assembly.GetTypes();

//Get information of each type


foreach (Type type in types)
{
//Return name of a type and its base type
Console.WriteLine("Type Name:{0}, Base Type:{1}",
type.Name, type.BaseType);

}
}
}

class A { } class
B:A{}

}
//Output
Type Name:Program, Base
Type:System.Object Type Name:A, Base
Type:System.Object Type Name:B, Base
Type:DemoAssembly.A

Explanation
(Listing 14-5) Type is a class used to store information of any type. Type class contains
methods and properties used to read and edit values of a specified type.
In the above example, assembly.GetTypes() returns an array of non-static Types. By using
properties like type.Name and type.BaseType we can get the name of a type and its base type
respectively.

376

Chapter 14 ■ Assembly and Reflection


Use Reflection to Read Metadata of Properties and Methods
During runtime, reflection can help to read all the information of a type in an assembly
including its methods, properties, events, etc.
Code Snipppet

Listing 14-6.  Use Reflection to Read Metadata of a Property

using System;
using System.Reflection;

namespace DemoAssembly
{
class Program
{
public int Age { get; set; } public
string Name { get; set; }

static void Main(string[] args)


{
//Get current loaded assembly
Assembly assembly = Assembly.GetExecutingAssembly();
//Get all types defined in an assembly
Type[] types = assembly.GetTypes();

//Dig information of each type


foreach (Type type in types)
{
//Return name of a type
Console.WriteLine("Type Name:{0}, Base Type:{1}",
type.Name, type.BaseType);

//Get all properties defined in a type


PropertyInfo[] properties = type.GetProperties();

foreach (PropertyInfo property in properties)


{
Console.WriteLine("\t{0} has {1} type",
property.Name,
property.PropertyType);
}

}
}
}

class A
{
public int Random { get; set; }
}
class B { }

377

Chapter 14 ■ Assembly and Reflection


}
//Output
Type Name:Program, Base Type:
System.Object Age has System.Int32
type
Name has System.String type Type
Name:A, Base Type:System.Object
Random has System.Int32 type Type
Name:B, Base Type:System.Object

Explanation
(Listing 14-6) PropertyInfo class is used to store information of a property. It contains the
method and properties used to read and edit data of a property. By default,
Type.GetProperties() returns all non-static public properties of a type.
Property.Name returns the name of a property. Property.PropertyType returns the type of the property.

Listing 14-7.  Use Reflection to Read Metadata of a Method

using System;
using System.Reflection;

namespace DemoAssembly
{
class Program
{
public void Show() { }
public int SqRoot(int i)
{
return (i * i);
}

static void Main(string[] args)


{
//Get current loaded assembly
Assembly assembly = Assembly.GetExecutingAssembly();

//Get all types defined in an assembly


Type[] types = assembly.GetTypes();

//Dig information of each type


foreach (Type type in types)
{
//Return name of a type
Console.WriteLine("Type Name:{0}, Base Type:{1}",
type.Name, type.BaseType);

//Get all non-static methods of a type


MethodInfo[] methods =
type.GetMethods();

foreach (MethodInfo method in methods)


{
Console.WriteLine("\tMethod Name:{0}, Return Type:{1}",

378

Chapter 14 ■ Assembly and Reflection


method.Name, method.ReturnType);
}

}
}
}

}
//Output
Type Name:Program, Base Type:System.Object Method
Name:Show, Return Type:System.Void Method
Name:SqRoot, Return Type:System.Int32 Method
Name:ToString, Return Type:System.String Method
Name:Equals, Return Type:System.Boolean Method
Name:GetHashCode, Return Type:System.Int32
Method Name:GetType, Return Type:System.Type

Explanation
MethodInfo is a class that stores information of a method. MethodInfo class contains methods
and properties that are used to read and edit data of a method. By default, Type.GetMethods()
returns all non- static public methods of a type.
method.Name returns the name of a method. method.ReturnType returns the return type
of a method. The output of a program also showed “ToString”,”Equals”,”GetHashCode”
and ”GetType” methods
which aren't defined in Program class. These methods were defined in System.Object class.
Since every class inherits System.Object class, the program showed these methods too.
Similarly, there are other methods and properties defined in System.Type class which are
useful to get not only information about methods and properties but also about events,
interfaces, fields, etc.

Use Reflection to Get and Set Value of Object’s Property


Reflection can also be used to read and write actual value stored in a property of some
class's instance at runtime.
Code Snippet

Listing 14-8.  Use Reflection to Read Values of a Property

using System;
using System.Reflection;

namespace DemoAssembly
{
class Person
{
public int Age { get; set; }
public string FirstName { get; set; }
}

class Program
{

379

Chapter 14 ■ Assembly and Reflection


static void Main(string[] args)
{
var personobj = new Person { FirstName = "Sundus", Age = 21 };
var personobj2 = new Person { FirstName = "Ali", Age = 22 };

//Store Metadata of Person Type in Type's Object


//return Type of 'Person' class
Type persontype = typeof(Person);

//Specify which property information is required


//Return metadata of specified property
PropertyInfo nameproperty = persontype.GetProperty("FirstName");

//Specify 'instance' (personobj) of 'Type' (Person)


//Whose 'property' (nameproperty) value is required
var value = nameproperty.GetValue(personobj);

Console.WriteLine("{0} = {1}", nameproperty.Name, value);

}
}

}
//Output
FirstName =
Sundus

Explanation
To get a value of a specified object's property, the following steps are required:
\ 1.\ Return and store the type of Object by using typeof operator or GetType method.
\ 2.\ Return and store metadata of specified property of a type.
\ 3.\ Use GetValue() method. Specify the type's instance whose value is about to get.

Code Snippet

Listing 14-9.  Use Reflection to Set and Read Values of a Property


using System;
using System.Reflection;

namespace DemoAssembly
{
class Person
{
public int Age { get; set; }
public string FirstName { get; set; }
}
class Program
{

380

Chapter 14 ■ Assembly and Reflection


static void Main(string[] args)
{
var personobj = new Person { FirstName = "Sundus", Age = 21 };
var personobj2 = new Person { FirstName = "Ali", Age = 22 };

//Store Metadata of Person Type in Type's Object


//return Type of 'Person' class
Type persontype = typeof(Person);

//Specify which property information is required


//Return metadata of specified property
PropertyInfo nameproperty = persontype.GetProperty("FirstName");

//Specify 'instance' (personobj) of 'Type' (Person) //Whose


'property' (nameproperty) value is about to change
nameproperty.SetValue(personobj, "Lakhtey");

//Specify 'instance' (personobj) of 'Type' (Person)


//Whose 'property' (nameproperty) value is required
var value = nameproperty.GetValue(personobj);

Console.WriteLine("{0} = {1}", nameproperty.Name, value);

}
}

}
//Output
FirstName = Lakhtey

Explanation
To set a value of a specified object's property, the following steps are required:
\ 1.\ Return and store the type of Object by using typeof operator or GetType method.
\ 2.\ Return and store metadata of the specified property of a type.
\ 3.\ Use SetValue() method. Specify the type's instance and value that is about to set.

Use Reflection to Invoke a Method of an Object


Reflection can also be used to invoke any defined method of an object anytime during runtime.
Code Snippet

Listing 14-10.  Use Reflection to Invoke the Method of an Object


using System;
using System.Reflection;

namespace DemoAssembly
{

381

Chapter 14 ■ Assembly and Reflection


class Person
{
public int Age { get; set; }
public string FirstName { get; set; }

public int Show()


{
Console.WriteLine("FirstName = {0}", FirstName);

return Age;
}
}

class Program
{
static void Main(string[] args)
{
var personobj = new Person { FirstName = "Sundus", Age = 21 };
var personobj2 = new Person { FirstName = "Ali", Age = 22 };

//Store Metadata of Person Type in Type's Object


//return Type of 'Person' class
Type persontype = personobj.GetType();

//Specify which method's information is required


//Return metadata of specified method
MethodInfo methodinfo = persontype.GetMethod("Show");

//Provide instance (personobj) name whose method is about to invoke


//pass parameter value 'null' if specified method has parameter var
returnValue = methodinfo.Invoke(personobj, null);

Console.WriteLine("Age = {0}", returnValue);

}
}

}
//Output
FirstName =
Sundus Age = 21

Explanation
To Invoke a specified method at runtime, the following steps are required:
\ 1.\ Return and store the type of Object by using the typeof operator or
GetType method.
\ 2.\ Return and store metadata of a specified method of a type.
\ 3.\ Use Invoke() method. Specify the type's instance and parameter values
to invoke the method of a specified type's instance.

382

Chapter 14 ■ Assembly and Reflection


Use Reflection to Get Private Members
By default, reflection is used to get all public members, but with some code tweaking it can also
be useful to find private members of a type. To get the private member, we specify the
BindingFlags.NonPublic enum in the paremeter of Type.GetFields() and Type.GetMethods()
methods, etc.
Code Snippet
Listing 14-11.  Use Reflection to Read Private Members
using System;
using System.Reflection;

namespace DemoAssembly
{
class Person
{
private int Age { get; set; }
private string FirstName { get; set; }

public Person(int age, string name)


{
this.Age = age;
this.FirstName =
name;
}

class Program
{
static void Main(string[] args)
{
var personobj = new Person (21, "Sundus");
var personobj2 = new Person(22, "Ali");

//Store Metadata of Person Type in Type's Object


//return Type of 'Person' class

Type persontype = personobj.GetType();

//Pass BindingFlags to specify what kind of


//data member you want.
//NonPublic =
Private //Non-Static =
Instance

PropertyInfo[] props =
props.GetProperties(BindingFlags.NonPublic |
BindingFlags.Instance);

foreach (PropertyInfo prop in props)


{
Console.WriteLine("{0} = {1}", prop.Name, prop.GetValue(personobj));
}

383

Chapter 14 ■ Assembly and Reflection


}
}

}
//Output Age = 21
FirstName =
Sundus

Explanation
GetProperties() is used to return property information by using the BindingFlags enums; this
method can return the specified type of properties. These enums tell a property should be
non-public and non- static, etc. When passing bindingflags, use the vertical bar pipe '|' to add
more than one BindingFlag in the GetProperties() method.

Use Reflection to Get Static Members


By default, reflection is used to get the public instance member of a type, but by using
BindingFlags.Public and BindingFlags.Static together we can get the public static members of
a type.
Code Snippet

Listing 14-12.  Use Reflection to Read Static Member


using System;
using System.Reflection;

namespace DemoAssembly
{
class Person
{
public static string company = "Microsoft";

class Program
{
static void Main(string[] args)
{
//Store Metadata of Person Type in Type's Object
//return Type of 'Person' class

Type persontype = typeof(Person);

//Pass BindingFlags to specify what kind of


//data member you want.
//BindingFlags.Static = Static Member
//BindingFlags.Public = Public Member

FieldInfo[] fields = persontype.GetFields(BindingFlags.Public |


BindingFlags.Static);

384

Chapter 14 ■ Assembly and Reflection


foreach (FieldInfo field in fields)
{
Console.WriteLine("{0}", field.Name);
}

}
}

}
//Output
Company

Attributes in C#
Attributes are a kind of metadata for tagging C# code (types, methods, properties, and so
forth). Attributes can be used with reflection to query down C# code at runtime, for code
generation, or in editor at compile time in any number of ways (for example, to hide/seek
windowsform controls from toolbar).
Syntax for Specifying an Attribute to C# Code
[attribute(parameter_name = value, ...)]
Element

Create a Custom Attribute


In term of programming, attributes are C# classes, inherited from the type “Attribute”. When
creating a custom attribute, it is a rule to suffix its class name with “Attribute”. For example, see
the below code snippet.

class MyCustomAttribute : Attribute


{

where MyCustomAttribute is the name of a custom attribute that inherits a class “Attribute”.

Use Custom Attribute with Reflection


By using reflection, we can query down any C# code that was marked with a custom attribute.

Specify a Custom Attribute on a C# Code (Class, Method, etc)


In the example, a custom attribute is going to specify on Class, Method, and Properties, and
then we'll use reflection to query it down.
Code Snippet

Listing 14-13.  Use Custom Attribute on a C# Code


using System;
using System.Linq;

385

Chapter 14 ■ Assembly and Reflection


using System.Reflection;

namespace DemoProject
{
class MyCustomAttribute : Attribute
{

[MyCustom] //Class, Marked with Custom


Attribute class Person
{

//Property, Without Custom Attribute


public int ID { get; set; }

[MyCustom] //Property, Marked with Custom


Attribute public int Age { get; set; }

//Method, Without Custom


Attribute public void Bye()
{
Console.WriteLine("Bye, world!");
}

[MyCustom] //Method, Marked with Custom


Attribute public void Hi()
{
Console.WriteLine("Hi, world!");
}
}

//Class, Without Custom Attribute


class Machine
{
public int ID { get; set; }
}

class Program
{
static void Main(string[] args)
{
Assembly assembly = Assembly.GetExecutingAssembly();

//Get all types that are marked with 'MyCustomAttribute'


var types =
from t in assembly.GetTypes()
where t.GetCustomAttributes<MyCustomAttribute>().Count()
> 0 select t;

foreach (var type in types)


{

386

Chapter 14 ■ Assembly and Reflection


Console.WriteLine(type.Name);

//Get all properties which are marked with 'MyCustomAttribute'


var properties =
from p in type.GetProperties()
where p.GetCustomAttributes<MyCustomAttribute>
().Count()> 0 select p;

foreach (var property in properties)


{
Console.WriteLine("\tProperty Name: {0}", property.Name);
}

//Get all methods which are marked with 'MyCustomAttribute'


var methods =
from m in type.GetMethods()
where m.GetCustomAttributes<MyCustomAttribute>
().Count()> 0 select m;

foreach (var method in methods)


{
Console.WriteLine("\tMethod Name: {0}()", method.Name);
}
}
}
}

}
//Output
Person
Property Name: Age
Method Name: Hi()

Explanation
[MyCustom] = [MyCustomAttribute] because .NET framework already knows “Attribute” is a
suffix, so it is a feature of C# which allows it to ignore suffix.
In above code snippet (Listing 14-13), a custom attribute of name “MyCustomAttribute” is
created. This attribute [MyCustom] is marked on a class, property, and method.
In the main method, by using reflection, all the types, properties, and methods which were
marked with “MyCustomAttribute” can be found by using the GetCustomAttributes<TAttribute>
() method.

Declaring Properties in Custom Attribute Class


Properties can be declared in a custom attribute class. Values of these properties can be
assigned when an instance of custom attribute is attached to any C# code element.
Only public property with get;set; can declare in attribute class.

387

Chapter 14 ■ Assembly and Reflection


Code Snippet

Listing 14-14.  Read Attribute of a C# Code

using System;
using System.Reflection;

namespace demoProject
{
class DeveloperAttribute : Attribute
{
public string Name { get; set; }
public int Age { get; set; }
}

[Developer(Name = "Ali Asad", Age = 22)]


class VehicleApp
{
public int Wheels { get; set; }
public string Color { get; set; }
}

class Program
{
static void Main(string[] args)
{
//******Retrieve Property Values**********//

//Get types
Type vtype = typeof(VehicleApp);
Type atype = typeof(DeveloperAttribute);

//get the developerattribute attached with vehivle type


DeveloperAttribute developer =
(DeveloperAttribute)Attribute.GetCustomAttribute(vtype,
atype);

Console.WriteLine(developer.Age);
Console.WriteLine(developer.Name);
}
}
}
//Output
22
Ali

Explanation
Only public property can be used in a custom attribute class. Its value can be assigned when
the attribute is attached to any C# code. By using the Attribute.GetCustomAttribute() method,
the value stored in properties of an attribute can be retrieved. To retrieve a custom attribute
instance from a class, we need to specifiy what type of Attribute it is and what type of class it is
attached to by using the typeof operator or getType() method.

388

Chapter 14 ■ Assembly and Reflection


Declaring Constructor in Custom Attribute Class
A constructor can be declared in a custom attribute class in the same way that it is declared in
any C# class. Constructor can contain a parameter which can also be an optional parameter.
Constructor is useful to assign values to properties defined in custom attribute class.
Code Snippet

Listing 14-15.  Declare a Constructor in Custom Attribute Class

using System;

namespace demoProject
{
class DeveloperAttribute : Attribute
{
public string Name { get; set; }
public int Age { get; set; }

public DeveloperAttribute(string name, int age = -1)


{