Você está na página 1de 340

Captulo 1 Tipos de dados e Interfaces

Capitulo 1 Tipos de dados e Interfaces Introduo

O .NET Framework 2.0 fornece uma poro de tipos predefinidos que so necessrios para a criao dos mais diversos tipos de aplicaes que o mesmo fornece para ns desenvolvedores. Esses tipos so utilizados constantemente nas aplicaes, para armazenar valores, bem como uma estrutura extensvel para que voc crie seus prprios tipos. Neste captulo voc entender como funciona a arquitetura de tipos do .NET Framework 2.0, ver tambm sobre os tipos intrnsicos disponveis. Alm disso, vamos abordar um assunto fortemente ligado aos tipos: tipos-valor e tipos-referncia e, para complementar, analisaremos o boxing e unboxing, que um grande vilo em termos de performance das aplicaes .NET-based. Para finalizar a parte sobre tipos, temos ainda alguns tipos especiais que precisam ser examinados: Generics, Nullable Types, Exceptions e Attributes. Na segunda parte do captulo, vamos analisar as Interfaces que so disponveis dentro do .NET Framework 2.0 e tambm veremos como criar suas prprias Interfaces para utilizar no seu cdigo ou, se desejar, expor esta para que consumidores de seu componente consigam implement-la, criando uma espcie de contrato que teus componentes exigem para poderem trabalhar. Examinando a arquitetura e os tipos CTS Common Type System Como todos sabem, os tipos so pea fundamental em qualquer tipo de linguagem de programao, seja para a criao de componentes ou para tipos de aplicaes que interajam com o usurio (Windows ou Web). Como a idia da Microsoft tornar tudo o mais integrado possvel, ela criou uma especificao de tipos chamada Common Type System (CTS), que descreve como os tipos so definidos e como se comportam. Esses tipos so compartilhados entre todas as linguagens .NET. Sendo assim, voc pode criar um componente em Visual C# e consumir este mesmo componente em uma aplicao cliente escrita em Visual Basic .NET sem nenhum problema, j que o tipo esperado ou o tipo a ser devolvido so de conhecimento de ambas. Voc tambm ouvir o termo cross-language integration para essa mesma explicao. Alm disso, um outro ponto importante do CTS que tambm especfica as regras de visibilidade de tipos para acesso aos membros do mesmo. Atualmente temos os seguintes modificadores de acesso disponveis: 1

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


Modificador VB.NET Modificador C# Private private Protected Friend Friend Protected Public protected internal internal protected public

Descrio Pode ser acessado por outros mtodos somente dentro do mesmo tipo (classe). Pode ser acessado por outros mtodos dentro do mesmo tipo e tambm por tipos derivados. Pode ser chamado de qualquer local desde que seja dentro do mesmo Assembly. Pode ser acessado por outros mtodos dentro do mesmo tipo e tambm por tipos derivados dentro do mesmo Assembly. Pode ser chamado de qualquer local.

Para finalizar essa introduo sobre o CTS, h ainda uma das principais e mais importantes regras definidas por ele: todos os tipos devem herdar direta ou indiretamente de um tipo predefinido: System.Object. Este a raiz de todos os tipos dentro do .NET Framework e, conseqentemente, ser tambm a raiz para os tipos customizados por voc dentro da sua aplicao e, sendo assim, ele fornece um conjunto mnimo de comportamentos: Representao em uma string do estado do objeto Consultar o tipo verdadeiro da instncia Extrair o cdigo hash para a instncia Comparar duas instncias quanto igualdade Realizar uma cpia da instncia

CLS Common Language Specification Graas um ambiente de execuo comum e informaes em metadados, a CLR (Common Language Runtime) integra as linguagem e permitem que estas compartilhem os tipos criados em uma linguagem sejam tratado de forma igual na outra. Essa integrao fantstica, mas muitas linguagens so bem diferentes entre si, ou seja, algumas fazem distino entre maisculas e minsculas, outras no oferecem sobrecargas de operadores, etc.. Se algum desejar criar um determinado tipo que seja compatvel com qualquer outra linguagem .NET, voc ter que utilizar somente os recursos que obrigatoriamente estejam tambm nas outras linguagens. Para isso, a Microsoft criou o Common Language Specification (CLS), que especifica o conjunto mnimo de recursos que devem ser suportados se desejam gerar cdigo para o CLR. Geralmente empresas que esto criando seus compiladores para .NET, devem seguir rigorosamente estas especificaes. Para assegurar que o componente que est desenvolvendo seja compatvel com qualquer linguagem .NET, voc pode incluir um atributo chmado CLSCompliant no arquivo AssemblyInfo que instrui o compilador a se certificar que um tipo pblico que est sendo Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces

disponibilizado no contenha nenhuma construo que evite de ser acessado a partir de outra linguagem. O cdigo abaixo exemplifica o uso deste atributo:
VB.NET <Assembly:CLSCompliant(True)> C# [Assembly:CLSCompliant(true)]

Abaixo, a imagem ilustra o conjunto de tudo que vimos at o momento:

Imagem 1.1 Uma exibio em forma de conjunto para entermos a relao entre elas. Tipos fornecidos pelo .NET Framework Dentro do .NET Framework 2.0, vrias funcionalidades so encapsuladas dentro de objetos e estes, por sua vez, so instncias de tipos fornecidos pelo sistema. O .NET Framework j traz internamente vrios tipos predefinidos, quais so conhecidos como tipos bsicos, ou base system types. Um exemplo tpico disso um controle Button. Internamente ele possui uma poro de membros que expem ou recebem tipos de dados como strings, inteiros, decimais ou at mesmo outros tipos/objetos. H alguns tipos de dados que so muito comuns em vrias linguagens de programao. Podemos citar vrios deles: inteiros, strings, doubles, etc.. So to comuns que grande parte dos compiladores permitem utilizarmos um cdigo que manipule esses tipos de Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces

forma simples, um atalho. Se esse atalho no existisse, teramos que fazer algo mais ou menos como:
VB.NET Dim codigo As New System.Int32() C# System.Int32 codigo = new System.Int32();

Apesar de funcionar sem nenhum problema, isso no nada cmodo, justamente pela freqencia com qual utilizamos esses tipos. Felizmente tanto o Visual Basic .NET quanto o Visual C# fornecem vrias keywords que o compilador faz o trabalho de mapear diretamente para o tipo correspondente dentro do .NET Framework Class Library (FCL). Esses tipos de dados que o compilador suporta diretamente so chamados de tipos primitivos. Com isso, o cdigo acima ficaria muito mais produtivo se utilizssemos uma sintaxe semelhante a mostrada logo abaixo:
VB.NET Dim codigo As Integer C# int codigo = 0;

Tipos valor e tipos referncia O Common Language Runtime (CLR) suporta duas espcies de tipos: tipos-valor (value types) e tipos-referncia (reference types). Os tipos-valor so variveis que diretamente contm seu prprio valor, sendo assim, cada varivel mantm a cpia de seus dados e, conseqentemente, operaes que utilizam essa varivel no afetar o seu valor. Tipos-valor so dividos em dois tipos: built-in e user-defined types. O primeiro trata-se de valores que so fornecidos com o prprio .NET Framework, como o caso de inteiros, floats, etc.. J o segundo, so tipos que definimos dentro de nossos componentes ou aplicaes. Geralmente esses tipos so estruturas de dados ou enumeradores. As estruturas de dados so bem semelhantes a uma classe e podem conter membros para armazenar os dados e tambm funes para desempenhar algum trabalho. J os enumeradores so constantes que fixam a escolha de algums dos valores fornecidos por ele, o que impossibilita o consumidor de passar algo diferente do que espera, ou melhor, de passar algum valor que seu mtodo/classe no saiba trabalhar.

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces

J os tipos-referncia contm ao invs do valor, uma referncia para os dados e, sendo assim, possvel termos duas variveis apontando para o mesmo lugar na memria, ou melhor, para o mesmo objeto. Como os tipos-referncia apontam para o mesmo local, uma operao poder alterar o contedo desta varivel, j que o mesmo est compartilhado. Geralmente todos os objetos dentro do .NET, sejam eles intrnsicos ao Framework ou os objetos customizados, so tipos-referncia. Os tipos-referncia tambm so divididos em built-in e user-defined types assim como os tipos-valor. Para exemplificar, built-in type podemos a classe Form (Windows Forms) e a classe Page (Web Forms), entre muitos outros. Para built-in types temos os tipos/objetos que criamos para a aplicao/componente que estamos desenvolvendo. Apesar da imaginao ser o limite, temos alguns exemplos: Cliente, LeitorCNAB, etc.. A hierarquia dos tipos ilustrada atravs da imagem 1.2:

Imagem 1.2 Hierarquia dos tipos do .NET Framework. Alm desta grande diferena entre os dois tipos, ainda h uma separao quando falamos em nvel de memria. Cada um dos tipos so armazenados em regies completamente diferentes. Os tipo-valor so armazenados na Stack e os tipo-referncia na Heap. A memria Stack um bloco de memria alocado para cada programa em runtime. Durante a vida de execuo, quando uma funo invocada, todas as variveis que ela utiliza so colocadas na Stack. Assim que a funo retornada ou o escopo de um bloco finalizado, o mais breve possvel os valores ali colocados sero descartados, liberando assim, a memria que estava sendo ocupada. Como falamos anteriormente, quando uma varivel do tipo-valor passada de uma funo, uma cpia do valor passado e, se esta funo alterar este valor, no refletir no valor original. Como os tipos de dados dentro do .NET Framework so uma estrutura, eles so tambm armazenados na Stack. Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces

No caso de tipos-referncia, os dados so armazenados na memria Heap, enquanto a referncia do mesmo colocado na mmoria Stack. Isso acontece quando utilizando o operador new (New em VB.NET) no cdigo, que retornar o endereo de memrio do objeto. Isso acontece quando precisamos criar efetivamente o objeto para utiliz-lo. Para entender melhor esse processo, vamos analisar o cdigo abaixo:
VB.NET Dim cliente1 As Cliente() Dim cliente2 As Cliente = cliente1 C# Cliente cliente1 = new Cliente(); Cliente cliente2 = cliente1;

Quando atribumos o cliente1 ao cliente2 recm criado, voc est copiando a referncia ao objeto que, por sua vez, aponta para uma determinada seo na memria Heap. No cdigo acima, quando voc efetuar uma mudana, seja ela no objeto cliente1 ou cliente2, refletir no mesmo objeto da memria Heap. Atravs da imagem 1.2, podemos visualizar as memrias (Stack e Heap) em conjunto quando armazenam tipos-referncia:

Imagem 1.3 Memria Stack e Heap armazenando tipos-referncia. Como j falamos anteriormente, todas os objetos so tipos-referncia. Alm deles, ainda temos as Interfaces que tambm operam em modelo tipo-referncia. As Interfaces contm mtodos e propriedades em seu interior mas no possui nenhuma implementao concreta. Elas devem ser implementadas em classes para implementar a sua funcionalidade. As Interfaces sero discutidas mais detalhadamente ainda neste captulo. Nota Importante: Todos os tipos em .NET so, em ltima instncia, derivados de System.Object. Como dito anteriormente, todos os tipos do .NET sejam eles intrnsicos ao Framework ou sejam objetos customizados, so tipos-referncia. Mas e quanto aos tipos-valor? No vimos que inteiros, floats so tipo-valor? Sim, mas a questo que a Microsoft se preocupou com isso e criou uma possibilidade de diferenciar um de outro. Se analisar atentamente a documentao do .NET Framework, ver que a maioria dos tipos-valor, por exemplo integer, decimal, datetime so representados atravs de Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces

estruturas de dados: System.Int32, System.Decimal e System.DateTime respectivamente. Toda e qualquer estrutura derivada de System.ValueType e esta, por sua vez, herda de System.Object. Alm da classe System.ValueType fornecer toda a estrutura bsica para todos os tipos-valor do .NET ela tambm tratada de forma diferente pelo runtime, j que seus tipos derivados devem ser colocados na memria Stack. Estrutura de dados e enumeradores herdam de System.ValueType. Essa distino entre tipos-valor e tipos-referncia deve existir, pois o desempenho de uma aplicao poderia seriamente ser comprometida, pois imagine se a utilizao de um simples inteiro, tivssemos que alocar memria para isso. Os tipos valores so considerados peso-leve e, como vimos acima, so alocados na Stack da Thread. As melhorias em termos de performance no param por a. Esses valores mais volteis no ficam sob inspeo do Garbage Collector e fora do Heap, o que reduz o nmero de passagens do coletor. Boxing e Unboxing Como vimos na seo anterior, os tipos-valor so muito mais leves que tipos-referncia porque no so alocados no Heap e, conseqentemente, no sofrem coleta de lixo atravs do Garbage Collector e no so referenciados por ponteiros. Mas imagine uma situao onde voc precisa obter uma referncia a uma instncia do tipo valor. Um caso tpico para exemplificar so o uso de colees. H dentro do .NET Framework um objeto chamado ArrayList (System.Collections) que, permite-nos criar uma coleo de qualquer tipo. Como ele pode armazenar qualquer tipo, ele aceita nos parmetros de seus mtodos um System.Object (tipos-referncia). Como tudo em .NET , direta ou indiretamente, um tipo de System.Object, posso adicionar nesta coleo qualquer tipo, mesmo sendo um tipo-valor. Mas e se quisermos adicionar um tipo-valor nesta coleo, como por exemplo, uma coleo de inteiros? Para isso, basta simplesmente fazermos:
VB.NET Dim colecao As New ArrayList(); colecao.Add(1) colecao.Add(2) C# ArrayList colecao = new ArrayList(); colecao.Add(1); colecao.Add(2);

O mtodo Add recebe um tipo-referncia (System.Object). Isso quer dizer que o mtodo Add exige uma refncia (ponteiro) para um objeto no Heap. Mas no cdigo acima, Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces

adicionanado um inteiro e tipo-valor no possui ponteiros para o Heap, j que so armazenados na Stack. Isso s possvel graas ao boxing. Boxing na maioria dos casos ocorre implicitamente. Abaixo o que acontece nos bastidores quando boxing ocorre: 1. A quantidade de memria alocada no Heap de acordo com o tamanho do tipovalor e qualquer overhead a mais, se for o caso. 2. Os campos do tipo-valor so copiados para a Heap que foi previamente alocada. 3. O endereo com a referncia retornado. Felizmente compiladores das linguagens como Visual Basic .NET e Visual C# produzem automaticamente o cdigo necessrio para realizar o boxing. Isso perfeitamente notvel no cdigo acima, pois no precisamos escrever nada mais do que precisaramos no caso de adicionarmos um tipo-referncia. O unboxing o contrrio do boxing, mas considere sendo uma operao mais fcil em relao ao boxing. Unboxing apenas obter o ponteiro para tipo-valor bruto contido dentro de um objeto e, neste caso, no necessrio copiar nenhum tipo de dado na memria. Obviamente que boxing e unboxing comprometem a performance da aplicao em termos de velocidade como de memria. Felizmente no .NET Framework 2.0, foi introduzido o conceito de Generics Collections que elimina completamente estes problemas. Veremos sobre Generics Collections no Captulo 2. Examinando tipos especiais Generics Generics um novo conceito introduzido na verso 2.0 do .NET Framework. Generics permitem termos classes, mtodos, propriedades, delegates e Interfaces que trabalhem com um tipo no especificado. Esse tipo no especificado quer dizer que estes membros trabalharo com os tipos que voc especificar em sua construo. O melhor entendimento deste conceito provavelmente ser quando estiver aprendendo sobre Generics Collections, que ser abordado no Captulo 2. Como vimos anteriormente, o ArrayList permite adicionarmos qualquer tipo dentro dele. Mas e se quisssemos apenas adicionar valores inteiros, ou somente strings? Isso no seria possvel pois o mtodo Add aceito um System.Object. Com Generics, possvel criar colees de um determinado tipo, o que permitir que o usurio (outro desenvolvedor) somente adicione variveis do mesmo tipo e, se por acaso ele quiser adicionar algo incompatvel, o erro j detectado em design-time. O .NET Framework 2.0 fornece uma poro de colees utilizando Generics que veremos mais detalhadamente no Captulo 2 deste curso. Classes genricas oferecem vrias vantagens, entre elas: Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces

1. Reusabilidade: Um simples tipo genrico pode ser utilizado em diversos cenrios. Um exemplo uma classe que fornece um mtodo para somar dois nmeros. S que estes nmeros podem ser do tipo Integer, Double ou Decimal. Utilizando o conceito de Generics, no precisaramos de overloads do mtodo Somar(...). Bastaria criar um nico mtodo com parmetros genricos e a especificao do tipo a ser somado fica a cargo do consumidor. 2. Type-safety: Generics fornecem uma melhor segurana, mais especificamente em colees. Quando criamos uma coleo genrica e especificamos em seu tipo uma string, somente podemos adicionar strings, ao contrrio do ArrayList. 3. Performance: Fornecem uma melhor performance, j que no h mais o boxing e unboxing. Alm disso, o nmero de converses cai drasticamente, j que tudo passa a trabalhar com um tipo especificado, o que evita transformarmos em outro tipo para termos acesso a suas propriedades, mtodos e eventos. Classes genricas Uma classe que aceita um tipo em sua declarao pode trabalhar internamente com este tipo. Isso quer dizer que os mtodos podem aceitar em seus parmetros objetos do tipo especificado na criao da classe, retornar esses tipos em propriedades, etc.. Para exemplificarmos, vejamos uma classe que aceita um valor genrico, o que quer dizer, que ela pode trabalhar (fortemente tipada) com qualquer tipo. Atravs do exemplo abaixo T identifica o tipo genrico que, j pode ser acessado internamente pela classe.
VB.NET Public Class ClasseGenerica(Of T) Private _valor As T Public Property Valor() As T Get Return Me._valor End Get Set(ByVal value As T) Me._valor = value End Set End Property End Class Utilizao: Dim classe1 As New ClasseGenerica(Of String) classe1.Valor = .NET

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


Dim classe2 As New ClasseGenerica(Of Integer) classe2.Valor = 123 C# public class ClasseGenerica<T> { private T _valor; public T Valor { get { return this._valor; } set { this._valor = value; } }

10

//Utilizao: ClasseGenerica<string> classe1 = new ClasseGenerica<string>(); classe1.Valor = .NET; ClasseGenerica<int> classe2 = new ClasseGenerica<int>(); classe2.Valor = 123;

O que diferencia uma classe normal de uma classe genrica o tipo que devemos especificamos durante a criao da mesma. O valor T pode ser substituido por qualquer palavra que voc achar mais conveniente para a situao e, quando quiser referenciar o tipo genrico em qualquer parte da classe, poder acess-lo como um tipo qualquer, como um Integer e felizmente, o Intellisense d suporte completo a Generics. Digamos que sua classe somente trabalhar com Streams, ento poderia definir T como TStream (se assim desejar):
VB.NET Public Class ClasseGenerica(Of TStream) End Class C# public class ClasseGenerica<TStream>

{ }

10

//

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces

11

Como podemos notar no exemplo, a classe1 trabalha somente com valores do tipo string. J a classe2 somente trabalha com valores do tipo inteiro. Mesmo que quiser adicionar um tipo incompatvel, o erro j informado em design-time. Nota: O Visual C# disponibiliza uma keyword bastante til chamada default. Ela utilizada em classes genricas para inicializar um tipo qualquer, pois o Visual C# temos obrigatoriamente que definir um valor default para qualquer tipo, esta vem para suprir esta necessidade. Em casos de tipos-referncia, retornado um valor nulo. J em casos de valores numricos, 0 retornado. Quando o tipo uma estrutura, retornado para cada membro desta, 0 ou nulo, dependendo do tipo de cada um. O cdigo abaixo exemplifica o uso desta keyword:
C# public class Lista<T> where T : IComparable { public void Add(T item) { T temp = default(T); // .... } }

Tipos e Termos Para o entendimento completo de Generics necessrio conhecermos alguns termos que so utilizados durante o uso de Generics. Inicialmente temos dois termos a serem analisados: type parameters e type arguments. O primeiro deles refere-se ao parmetro que utilizado na criao do tipo genrico. J os type arguments referem-se aos tipos que substituem os type parameters. O cdigo abaixo exemplifica essa diferena:
VB.NET Public Class ClasseGenerica(Of T) T = type parameter ... End Class Utilizao Dim c As New ClasseGenerica(Of String) String = type argument C# public class ClasseGenerica<T> //T = type parameter { //... }

11

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


//Utilizao ClasseGenerica<string> c = new ClasseGenerica<string>(); //string = type argument

12

Classes genricas so tambm chamadas de open types. Levam essa definio porque permitem construirmos uma nica classe utilizando diversos tipos. Para ela ser encarada como um open type necessrio que a classe tenha, no mnimo, um tipo a ser especificado. Baseando-se no exemplo acima, a classe ClasseGenerica trata-se de uma open type porque permite, atravs do type parameter T, especificarmos um tipo que a classe ir manipular. As instncias de classes open types so consideradas constructed types. Alm disso, ainda temos mais duas formas de tipos genricos: open constructed type e close constructed type. A primeira forma, open constructed type, criado quando, no mnimo, um type argument no especificado, continuando aberto para uma definio futura. Para transformar essa explicao em cdigo, temos o seguinte exemplo:
VB.NET Public Class ClasseGenerica(Of T) ... End Class Public Class Derivada(Of T) Inherits ClasseGenerica(Of T) End Class C# public class ClasseGenerica<T> { //... } public class Derivada<T> : ClasseGenerica<T> { //... }

Como podemos notar, o tipo ainda continua aberto para que se possa fazer a definio efetiva do tipo mais tarde. Finalmente, temos o close constructed type, que criado especificando todos os type arguments, no permitindo deix-lo aberto para uma futura definio. O exemplo abaixo ilustra o close constructed type:
VB.NET

12

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


Public Class ClasseGenerica(Of K, T) ... End Class Public Class Derivada Inherits ClasseGenerica(Of String, Cliente) End Class C# public class ClasseGenerica<K, T> { //... } public class Derivada : ClasseGenerica<string, Cliente> { //... }

13

Mas Generics no param por aqui. Existem muitas outras possibilidades para tornarmos os Generics ainda mais poderosos, como por exemplo critrios (constraints), criao de mtodos genricos, delegates, Interfaces, entre outros, quais veremos a seguir. Mtodos genricos Quando criamos uma classe genrica, informamos o seu tipo logo na construo da mesma. Isso nos habilita a possibilidade de utilizar esse tipo genrico no inteiror da classe em qualquer outro membro. O exemplo abaixo exibe como construir esse metdo:
VB.NET Public Class ClasseGenerica(Of T) Public Function RetornaValor(value As T) As T Return value End Function End Class C# public class ClasseGenerica<T>

public T RetornaValor(T value) { return value; }

13

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces

14

Como pode notar, quando informamos na construo da classe que ela uma classe genrica, podemos utilizar o tipo T em toda a classe. O mtodo RetornaValor recebe esse tipo e o retorna. Quando instanciamos a classe e especificamos o tipo, como por exemplo uma String, todos os membros que utilizam T passaro a utilizar String. Felizmente a verificao de tipos feito em design-time, o que significa que se tentar passar um nmero inteiro, ele j acusar o erro, sem a necessidade de executar o cdigo para descobrir a possvel falha. Atravs da imagem 1.4 podemos certificar que ao definir o tipo, a classe e o mtodo somente permitir trabalhar com ele (onde for definido):

Imagem 1.4 Intellisense dando suporte total a Generics. Se definirmos o tipo da classe como qualquer outro tipo durante a criao do objeto, onde o T estiver especificado ser substituido por ele. S que existem situaes onde a classe no genrica, mas sim somente um mtodo de uma classe qualquer deve suportar parmetros genricos. Para isso, h a possibilidade de criarmos mtodos genricos. So basicamente como as classes: definimos o tipo em sua construo e pode-se utilizar tal tipo no interior do respectivo mtodo, seja nos parmetros ou no retorno do mesmo. Um exemplo da construo de mtodos genricos exibido abaixo:
VB.NET Public Class Classe Public Function RetornaValor(Of T)(ByVal value As T) As T Return value End Function End Class C# public class Classe { public T RetornaValor<T>(T value) { return value; }

14

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


}

15

Para criarmos um mtodo genrico que independe de um tipo especificado na criao da classe, devemos colocar logo aps o nome do mesmo o tipo genrico que dever ser especificado pelo desenvolvedor que o est consumindo. Neste caso, o escopo de utilizao de T (tipo genrico) passa a ser somente o mtodo. Abaixo mostrado como utiliz-lo:
VB.NET Dim generic As New Classe() Dim id As Integer = generic.RetornaValor(Of Integer)(12) Dim nome As String = generic.RetornaValor(Of String)("Jos") C# Classe generic = new Classe(); int id = generic.RetornaValor<int>(12); string nome = generic.RetornaValor<string>("Jos");

Propriedades genricas As propriedades no tem nada de especial. Elas apenas podem retornar um tipo genrico especificado na criao da classe ou Interface. Um exemplo de sua utilizao est no primeiro trecho de cdigo desta seo, que a propriedade Valor da classe ClasseGenerica. Ela devolve um tipo T que especificado na criao da classe. Interfaces genricas Assim como as classes, as Interfaces tambm suportam tipos genricos. A forma de criao tambm idntica a especificao de tipo genrico da classe e, podemos em seu inteiror, utilizar este tipo. Dentro do .NET Framework 2.0 existem vrias Interfaces genricas e estudaremos algumas delas quando estivermos falando sobre colees genricas, no Captulo 2. J o exemplo de sua criao e implementao mostrada atravs do cdigo abaixo:
VB.NET Public Interface ITest(Of T) Property Valor() As T Sub Adicionar(ByVal value As T)

15

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


End Interface Public Class ClasseConcreta Implements ITest(Of String) Public Sub Adicionar(ByVal value As String) _ Implements ITest(Of String).Adicionar End Sub Public Property Valor() As String _ Implements ITest(Of String).Valor Get End Get Set(ByVal value As String) End Set End Property End Class C# public interface ITest<T> { T Valor { get;set; } void Adicionar(T value); } public class ClasseConcreta : ITest<string> { public string Valor { get { } set { } } public void Adicionar(string value) { }

16

Obviamente que quando implementamos a Interface ITest em uma classe qualquer, obrigatoriamente devemos definir o tipo que a Interface ir trabalhar e, neste caso, o tipo String. Com isso, todos os tipos genricos especificados na Interface sero implementados utilizando o tipo String para esta classe. Israel Aece | http://www.projetando.net

16

Captulo 1 Tipos de dados e Interfaces

17

Nada impede voc de implementar esta mesma Interface em uma outra classe com um tipo diferente, ou ainda, implementar a mesma Interface com tipos diferentes. A nica considerao a fazer quando implementamos a mesma Interface em uma classe, pois neste caso, as Intefaces so implementadas explicitamente. Veremos a implementao explcitas de Interfaces ainda neste captulo. Delegates genricos Assim como os membros que vimos at o momento, os delegates tambm permitem serem criados de forma genrica. Basicamente levam tambm o tipo a ser especificado, o que conseqentemente, somente permitir apontar para um membro que obrigatoriamente atende aquele tipo. O exemplo abaixo cria um delegate genrico que aceita um tipo a ser especificado durante a sua criao. Esse delegate poder ser utilizado para qualquer funo que contenha o mesmo nmero de parmetros, independentemente do tipo:
VB.NET Public Delegate Sub Del(Of T)(ByVal item As T) Sub Main() Dim delInteger As New Del(Of Integer)(AddressOf WriteInteger) Dim delString As New Del(Of String)(AddressOf WriteString) delInteger(123) delString("Jos") End Sub Public Sub WriteInteger(ByVal i As Integer) Console.WriteLine(i) End Sub Public Sub WriteString(ByVal str As String) Console.WriteLine(str) End Sub C# public delegate void Del<T>(T item); static void Main(string[] args) { Del<int> delInteger = new Del<int>(WriteInteger); Del<string> delString = new Del<string>(WriteString); delInteger(123); delString("Jos");

17

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


} public static void WriteInteger(int i) { Console.WriteLine(i); } public static void WriteString(string str) { Console.WriteLine(str); }

18

O Visual C# ainda tem alguns atalhos para o cdigo acima. O primeiro deles o chamado de method group conversion que permite definir o delegate genrico sem a necessidade de instanci-lo. Sendo assim, o cdigo em C# que faz a definio do mtodo a ser executado quando o delegate for disparado, poderia ser alterado para:
C# public delegate void Del<T>(T item); static void Main(string[] args) { Del<int> delInteger = WriteInteger; Del<string> delString = WriteString; delInteger(123); delString("Jos");

//Mtodos ocultados

Para finalizar, o Visual C# ainda tem um forma de tornar mais simples ainda o processo que so os metdos annimos. Os mtodos annimos so uma novidade do Visual C# 2.0 e permitem que o desenvolvedor defina o cdigo a ser executado pelo delegate diretamente, sem a necessidade de criar um procedimento para isso. Abaixo temos o cdigo em Visual C# modificado que faz o mesmo que vimos anteriormente, mas agora com o uso dos mtodos annimos:
C# public delegate void Del<T>(T item); static void Main(string[] args) {

18

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces

19

Del<string> delString = new Del<string>(delegate(string str) { Console.WriteLine(str); }); Del<int> delInteger = new Del<int>(delegate(int i) { Console.WriteLine(i); }); delInteger(123); delString("Jos");

Estruturas genricas Assim como as classes e Interfaces, as estruturas tambm podem ser genricas e seguem exatamente as mesmas regras para Generics. Um exemplo tpico de estrutura genrica a Nullable<T>, discutida na prxima seo, ainda neste captulo. Constraints Vimos at o momento como criar classes, mtodos, Interfaces e delegates genricos. Eles permitem que o consumidor possa definir qualquer tipo. Mas e se quisermos restringir quais tipos podem ser especificados na criao dos Generics? neste momento que entra em ao as constraints. As constraints so regras que aplicamos aos tipos genricos para espeficarmos o que o consumidor pode ou no definir como tipo. Atualmente existem seis tipos de constraints: Constraint where T : struct where T : class where T : new() where T : Base Class where T : interface where T : U Descrio O tipo especificado dever ser um value-type, mas precisamente uma estrutura. Mais uma vez, a estrutura Nullable<T> um exemplo. O tipo especificado dever ser uma reference-type, como classe, Interface, delegate ou Array. O tipo especificado dever ter um construtor pblico sem nenhum parmetro. Se for utilizado com outras constraints, ento esta dever ser a ltima. O tipo especificado dever derivar da classe informada. O tipo especificado dever implementar a Interface informada, podendo inclusive, definir vrias constraints desse tipo, o que indica que o tipo dever obrigatoriamente implementar todas elas. Quando h mais de um tipo especificado, podemos definir a constraint onde onde obrigatoriamente dever herdar do outro.

19

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces

20

As constraints so bastante teis, pois apesar de garantir a integridade do seu tipo, ou seja, o consumidor no poder passar um tipo que possa violar a constraint. Alm disso, voc j notificado pelo compilador se isso acontecer, que no permite compilar a aplicao com a falha. Para criar as constraints, voc utiliza a keyword where (no caso do Visual C#). No Visual Basic .NET, a keyword As, listando constraint ao lado de constraint para a definio do tipo genrico. Abaixo vamos exemplificar como definir cada uma das constraints disponveis: where T : struct
VB.NET Public Structure Nullable(Of T As Structure) C# public struct Nullable<T> where T : struct

where T : class
VB.NET Public Class Util(Of T As Class) C# public class Util<T> where T : class

where T : new()
VB.NET Public Class Util(Of T As New()) C# public class Util<T> where T : new()

where T : Base Class


VB.NET Public Class Util(Of T As BaseReader) C#

20

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


public class Util<T> where T : BaseReader

21

where T : Interface
VB.NET Public Class Util(Of T As IReader) C# public class Util<T> where T : IReader

where T : U
VB.NET Public Class Util(Of T, U As T) C# public class Util<T, U> where T : U

Finalmente, se desejar definir vrias constraints em um determinado tipo, basta ir enfileirando uma ao lado da outra, como mostrado logo abaixo. Somente atente-se para o Visual Basic .NET que, neste caso, exige que voc envolva as constraints entre chaves {}:
VB.NET Public Class Util(Of T As {IComparable, IReader, New}) C# public class Util<T> where T : IComparable, IReader, new()

NullableTypes Qualquer tipo-valor em .NET possui sempre um valor padro. Isso quer dizer que ele nunca poder ter um valor nulo e mesmo que tentar, definindo nulo (Nothing em VB.NET e null em C#) para o tipo-valor, o compilador atirar uma exceo. Um exemplo o DateTime. Ele tem um valor padro que 01/01/0001 00:00:00. Apesar de estranha, uma data vlida. Muitas vezes um tipo pode ter uma data no informada, como por exemplo, imagine um objeto Funcionario que tem uma propriedade chamada DataDemissao. S que este Israel Aece | http://www.projetando.net

21

Captulo 1 Tipos de dados e Interfaces

22

funcionario no foi demitido. Ento como conseguimos distingir se essa data vlida ou no? Pois bem, o .NET Framework 2.0 fornece o que chamamos de Nullable Types. System.Nullable<T> uma estrutura de dados genrica que aceita como tipo qualquer outro tipo, desde que esse tipo seja uma outra estrutura, como por exemplo: Int32, Double, DateTime, etc.. Atravs deste tipo especial podemos definir valores nulos para ele, como por exemplo:
VB.NET Dim dataDemissao As Nullable(Of DateTime) dataDemissao = Nothing C# Nullable<DateTime> dataDemissao = null;

A estrutura genrica Nullable<T> fornece tambm uma propriedade chamada HasValue do tipo booleana que retorna um valor indicando se existe ou no um valor definido. E note que a estrutura Nullable<T> trata-se de uma estrutura genrica, onde o tipo a ser definido obrigatoriamente deve tambm ser uma estrutura, devido a constraint que obriga isso. Isso pode ser facilmente notado na documentao:
VB.NET Public Structure Nullable(Of T As Structure) C# public struct Nullable<T> where T : struct

Esses tipos so ideais para utiliz-los junto com registros retornados de uma base de dados qualquer. Apesar de no ter uma grande integrao, ajuda imensamente, para conseguirmos mapear as colunas do result-set para as propriedades dos objetos da aplicao que permitem valores nulos. Exceptions Essa uma das partes mais elegantes do .NET Framework: Excees e o tratamento delas. Mas afinal, o que uma exceo: Exceo uma violao de alguma suposio da interface do seu tipo. Por exemplo, ao projetar um determinado tipo, voc imagina as mais diversas situaes em que seu tipo ser utilizado, difinindo tambm seus campos, propriedades, mtodos e eventos. Como j sabemos, a maneira como voc define esses membros, torna-se a interface do seu tipo.

22

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces

23

Assim sendo, dado um mtodo chamado TransferenciaValores, que recebe como parmetro dois objetos do tipo Conta e um determinado valor (do tipo Double) que deve ser transferido entre elas, precisamos valid-los para que a transferncia possa ser efetuada com xito. O desenvolvedor da classe precisar ter conhecimento suficiente para implementar essa tal validao e, no esquecer do mais importante: documentar claramente para que os utilizadores deste componente possam implementar o cdigo que far a chamada ao mtodo da maneira mais eficiente possvel, poupando ao mximo que surpresas ocorram em tempo de execuo.
VB.NET Public Shared Sub TransferenciaValores(de Conta, valor As Double) ... End Sub C# public static void double valor){ //... }

As

Conta,

para

As

TransferenciaValores(Conta

de,

Conta

para,

Dentro deste nosso cenrio, vamos analisar algumas suposies (validaes) que devemos fazer para que o mtodo acima possa ser executado da forma esperada: Certificar que de e para no so nulos Certificar que de e para no referenciam a mesma conta Se o valor for maior que zero Se o valor maior que o saldo disponvel

Necessitamos agora informar ao chamador que alguma dessas regras foi violada e, como fazemos isso? Atirando uma exceo. Como dissemos logo no comeo desta seo, ter uma exceo nem sempre algo negativo na aplicao, pois o tratamento de excees permite capturar a exceo, trat-la e a aplicao continuar correndo normalmente. O tratamento delas d se de forma estrutura, utilizando o Try/Catch/Finally. Mas nem sempre necessrio ter o overhead desta estrutura, pois h algumas formas de assegurar que o cdigo no dar erros com outros mtodos, tambm fornecidos pelo .NET Framework. Para exemplificar, vamos comparar dois cdigos: o primeiro, apesar de resolver o problema, no a melhor forma; j o segundo pode ser a melhor alternativa para a resoluo, ou melhor, para evitar um possvel problema:
C#

23

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


//Cdigo ruim try {

24

} catch { Console.WriteLine(Tipo incompatvel.); } try {

IReader reader = (IReader)e.Data; reader.ExecuteOperation();

} catch { Console.WriteLine(Id invlido.); } //Cdigo reformulado IReader reader = (IReader)e.Data as IReader; if(reader != null) { reader.ExecuteOperation(); } else { Console.WriteLine(Tipo incompatvel.); } string tempId = 12; int id = 0; if(int.TryParse(tempId, out id)) { this.BindForm(id); } else { Console.WriteLine(Id invlido.); }

string tempId = 12; int id = Convert.ToInt32(tempId); this.BindForm(id);

24
VB.NET

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


Cdigo ruim Try Dim reader As IReader = CType(e.Data, IReader) reader.ExecuteOperation() Catch Console.WriteLine(Tipo incompatvel.) End Try Try Dim tempId As String = 12 Dim id As Integer = Convert.ToInt32(tempId) Me.BindForm(id) Catch Console.WriteLine(Id invlido.) End Try Cdigo reformulado Dim reader As IReader = TryCast(e.Data, IReader) If Not IsNothing(reader) Then reader.ExecuteOperation() Else Response.Write(Tipo incompatvel.) End if Dim tempId As String = 12 Dim id As Integer = 0 If Integer.TryParse(tempId, id) Then Me.BindForm(id) Else Console.WriteLine(Id invlido.) End if

25

Veremos mais tarde, ainda neste captulo, sobre os operadores de converso. Atributos Os atributos so tags declarativas quais podemos decorar nossos membros (assemblies, classes, mtodos, propriedades, etc.) de acordo com a necessidade e com a caracterstica do atributo. Esses atributos so armazenados junto aos metadados do elemento e fornecem informaes para o runtime que o utilizar para extrair informaes em relao ao membro em que aplicado. Dentro do .NET Framework existem uma poro de atributos, como o caso do CLSCompliant que vimos no incio deste captulo e tambm do atributo WebMethod que utilizado para expor um mtodo via WebService. H a possibilidade de Israel Aece | http://www.projetando.net 25

Captulo 1 Tipos de dados e Interfaces

26

configurarmos inteiramente a segurana de um componente atravs da forma declarativa, que justamente utilizando atributos. Para que voc possa criar o teu prprio atributo, voc obrigatoriamente deve derivar da classe base chamada System.Attribute. Imagine que voc tenha uma classe chamada Cliente. Essa classe possui vrias propriedades e, entre elas, a propriedade Nome. Voc cria uma coleo de clientes e a popula com todos os clientes da sua empresa. Em um segundo momento, voc cria um atributo chamado ReportAttribute que pode ser aplicado exclusivamente em propriedades. Esse atributo indica quais propriedades devem fazer parte de um relatrio qualquer. Para exemplificar apenas o uso do atributo em conjunto com a propriedade, analise o cdigo a seguir:
VB.NET <AttributeUsage(AttributeTargets.Property)> _ Public Class ReportAttribute Inherits Attribute End Class Public Class Cliente Private _nome As String <Report()> _ Public Property Nome() As String Get Return Me._nome End Get Set(ByVal value As String) Me._nome = value End Set End Property outras propriedades End Class C# [AttributeUsage(AttributeTargets.Property)] public class ReportAttribute : Attribute { } public class Cliente { private string _nome; [Report]

26

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


public string Nome { get { return this._nome; } set { this._nome = value; } } } // outras propriedades

27

Como podemos reparar, na criao da classe ReportAttribute herdamos diretamente da classe Attribute. Denotamos esse atributo customizado com um outro atributo fornecido pelo .NET Framework que o AttributeUsage que permite especificamos em qual tipo de membro podemos aplicar o atributo. Para o nosso cenrio, vamos pode aplic-lo somente em propriedades. Depois dele pronto, basta simplesmente aplicar o mesmo nas propriedades que qualquer objetos customizado. Claro que precisaramos ainda de uma funo que extrai esses atributos via reflexo (System.Reflection) para assim tomarmos uma deciso baseandose no atributo. Vale lembrar tambm que perfeitamente possvel termos propriedades nos atributos customizados para assim passarmos mais informaes extras a nvel de metadados ou at mesmo informaes de regras de negcios. Para finalizar, qualquer membro pode suportar um ou vrios atributos. Trabalhando com Interfaces O que so Interfaces? Uma Interface se assemelha a uma classe. Ela pode conter mtodos, propriedades e eventos, mas sem qualquer implementao de cdigo, ou seja, somente contm a assinatura dos membros. As Interfaces so implementadas em classes e estas, por sua vez, implementam todo o cdigo para os membros abstratos da Interface, colocando ali o cdigo especfico para a classe que a implementa. As Interfaces so teis para arquiteturas de softwares que exigem um ambiente plug-andplay. Isso quer dizer que uma Interface pode referenciar armazenar uma instncia de uma classe concreta desde que esta instncia seja um tipo da Interface. Enteda-se por ser um tipo de uma classe que implementa a Interface em questo. As Interfaces podem ser implementadas em vrias e classes e uma classe pode implementar vrias Interfaces. Quando uma classe implementa uma Interface qualquer, Israel Aece | http://www.projetando.net

27

Captulo 1 Tipos de dados e Interfaces

28

isso assegurar que o classe fornece exatamente os mesmos membros especificados na Interface implementada de forma pblica. A Interface uma forma de contrato, j que deixa explcito exatamente os membros que a classe dever implementar. O .NET Framework usa Interfaces por toda sua arquitetura e nos permite criar nossas prprias Interfaces. Alm disso, o .NET Framework tambm expe Interfaces que nos fornecem o contrato para implementarmos em nossos objetos para garantir que eles implementem exatamente os membros que so necessrios para outros tipos poderem manuse-lo. Entre as inmeros Interfaces fornecidas, vamos analisar algumas delas, as mais teis ao nosso dia--dia e entender como e quando implement-las. Quando falamos de implementao de Interfaces, h dois tipos: 1. Normal: Esta opo a mais tradicional, pois implementamos a Interface em uma classe qualquer e os mtodos, propriedades e eventos definidos pela Interface so publicamente disponibilizados tambm pela classe. 2. Explcita: A opo implcita permite-nos implementar vrias Interfaces com membros com o mesmo nome em um determinado objeto. Isso um caso comum, pois h situaes onde a Interface tem um mtodo comum com outra Interface mas ambas precisam ser implementadas. A implementao explcita so suportadas tanto no Visual Basic .NET quanto no Visual C#, mas h uma pequena diferena: o Visual C# prefixa o nome do mtodo com o nome da Interface correspondente; j o Visual Basic .NET cria nomes diferentes para o mtodo, mas permite invoc-lo atravs da Interface. Alm disso, o Visual Basic .NET ainda permite que se invoque os mtodos atravs da instncia da classe o que no permitido no Visual C#. Abaixo exibido um exemplo deste cenrio:
VB.NET Public Interface IReader Sub Start() End Interface Public Interface IWriter Sub Start() End Interface Public Class Process Implements IReader Implements IWriter Public Sub Start() Implements IReader.Start Console.WriteLine("IReader.Start") End Sub Public Sub Start1() Implements IWriter.Start

28

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


Console.WriteLine("IWriter.Start") End Sub End Class C# public interface IReader { void Start(); } public interface IWriter { void Start(); } public class Process : IReader, IWriter { void IReader.Start() { Console.WriteLine("IReader.Start"); } void IWriter.Start() { Console.WriteLine("IWriter.Start"); }

29

Como podemos ver, as Interfaces IReader e IWriter possui um mesmo mtodo chamado Start. A nica observao que no caso do Visual Basic .NET ele necessariamente precisa ter um nome diferente na classe concreta Process. No caso do Visual C#, o prefixo do mtodo, que o nome da Interface, garante a distino entre elas. As diferenas no param a. No caso do Visual C# esses mtodos somente so acessveis se forem invocados atravs da prpria Interface. Isso quer dizer que, atravs da instncia da classe Process no est disponvel e, se reparar, o Intellisense tambm no suporta. Isso j possvel no Visual Basic .NET. O cdigo abaixo mostra a utilizao da classe Process:
VB.NET Dim p As New Process Dim reader As IReader = p Dim writer As IWriter = p reader.Start() writer.Start() Mas tambm possvel fazer: p.Start() p.Start1()

29

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


C# Process p = new Process(); IReader reader = (IReader)p; IWriter writer = (IWriter)p; reader.Start(); writer.Start();

30

Esse tipo de implementao somente se faz necessria quando h necessidade de implementar duas Interfaces em um objeto que contm exatamente um mesmo membro. Um exemplo disso dentro do .NET Framework so as estruturas de dados, como por exemplo: Int32, Double, etc., elas implementam explicitamente a Interface IConvertible. Converso de tipos utilizando IConvertible Imagine que esteja desenvolvendo uma aplicao em que contm uma classe chamada CalculoDeMedicoesPredio e dentro dela toda a lgica para calcular as medies de uma construo qualquer. Agora, voc precisa deste valor para gerar outras informaes para o usurio, como por exemplo relatrios ou at mesmo grficos. Geralmente, os mdulos que esperam esses valores (que talvez no seja nem a sua aplicao, pode ser um componente de terceiros) exigem que os valores estejam em um determinado tipo. Sendo assim, como eu fao para converter meu tipo complexo (CalculoDeMedicoesPredio) em um tipo esperado pelo mdulo para gerao de relatrios e grficos? O .NET Framework fornece uma Interface chamada IConvertible que permite-nos implement-la em nossos objetos. Essa Interface possuiu uma srie de mtodos que permitem converter um objeto em um tipo fornecedido pelo .NET Framework, a saber: Boolean, SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double, Decimal, DateTime, Char, e String. Os nomes dos mtodos da Interface em questo so basicamente os mesmos, apenas prefixados com To. Exemplo: ToBoolean, ToDateTime, ToString, etc.. Como j devem desconficar, implementando a Interface IConvertible em seu tipo, poder utiliz-lo passando para o mtodo correspondente a converso que quer fazer para a classe Convert. Um ponto importante aqui que voc precisa antentar-se para quais tipos o seu objeto pode ser convertido. No caso acima, no faz sentindo convertermos a classe CalculoDeMedicoesPredio para Boolean. Nos tipos incompatveis o ideal sempre voc atirar uma exceo do tipo InvalidCastException para no permitem converses no suportadas. Atravs do cdigo abaixo possvel analisar como implementar a Interace IConvertible:

30

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


VB.NET Public Class CalculoDeMedicoesPredio Implements IConvertible Private _altura As Double Private _largura As Double Public Sub New(ByVal largura As Double, _ ByVal altura As Double) Me._altura = altura Me._largura = largura End Sub

31

Public Function ToBoolean(ByVal provider As IFormatProvider) As Boolean Implements IConvertible.ToBoolean Throw New InvalidCastException End Function Public Function ToDouble(ByVal provider As IFormatProvider) As Double Implements IConvertible.ToDouble Return Me._altura * Me._largura End Function 'demais mtodos End Class Utilizao: Dim calculo As New CalculoDeMedicoesPredio(10, 30) Dim area As Double = Convert.ToDouble(calculo) Console.WriteLine(area) C# public class CalculoDeMedicoesPredio : IConvertible { private double _largura; private double _altura; public CalculoDeMedicoesPredio(double largura, double altura) { this._altura = altura; this._largura = largura; } public bool ToBoolean(IFormatProvider provider) { throw new InvalidCastException;

31

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


} public double ToDouble(IFormatProvider provider) { return this._altura * this._largura; } } // demais mtodos

32

//Utilizao: CalculoDeMedicoesPredio calculo = new CalculoDeMedicoesPredio(10, 30); double area = Convert.ToDouble(calculo); Console.WriteLine(area);

IComparer, IComparable e IEquatable Geralmente quando estamos trabalhando com objetos customizados dentro da aplicao, muito normal querermos exibir tais objetos para o usurio. Para exemplificar, teremos um objeto do tipo Usuario que possui uma propriedade do tipo Nome. Imagine que voc tenha uma lista de usurios com nomes aleatrios, ou seja, sem uma ordenao especfica. Essa lista conter objetos do tipo Usuario com o seu respectivo nome. A aplicao se encarrega de carregar essa lista e chega o nomento em que voc precisa exib-la em um controle qualquer e, necessrio que essa lista seja exibida aplicando uma ordenao alfabtica na mesma. Como proceder para customizar a ordenao? Pois bem, vrias colees no .NET Framework, como por exemplo List<T>, ArrayList, etc., fornecem um mtodo chamado Sort. S que apenas invocar este mtodo para ordenar a lista no o suficiente. necessrio que voc implemente as Interfaces IComparer<T> (IComparer) e IComparable<T> (IComparable) para isso. A primeira delas, IComparer<T>, customiza a forma de como ordenar a coleo; j a segunda, IComparable<T>, utilizada como definir como a ordenao realizada em uma classe especfica. Inicialmente iremos analisar a Interface IComparable<T>. Ela fornece um mtodo chamado CompareTo, que permite comparar a instncia da uma classe onde esta Interface est sendo implementando com um objeto (do mesmo tipo) que passado para o mtodo. Com a verso 2.0 do .NET Framework foi criada uma verso nova desta mesma Interface IComparable, que a IComparable<T>. A diferena em relao a IComparable que permite trabalharmos com Generics, o que melhora muito a performance, j que no necessrio efetuarmos a converso de System.Object para a classe que estamos utilizando e, somente depois disso, compararmos. Israel Aece | http://www.projetando.net

32

Captulo 1 Tipos de dados e Interfaces

33

Com todos os conceitos definidos, resta-nos partir para a implementao concreta para analisarmos:
VB.NET Public Class Usuario Implements IComparable(Of Usuario) Private _nome As String Public Sub New(ByVal nome As String) Me._nome = nome End Sub Public Property Nome() As String Get Return Me._nome End Get Set(ByVal value As String) Me._nome = value End Set End Property Public Function CompareTo(ByVal other As Usuario) As _ Integer Implements IComparable(Of Usuario).CompareTo Return Me.Nome.CompareTo(other.Nome) End Function End Class C# public class Usuario : IComparable<Usuario> { private string _nome; public Usuario(string nome) { this._nome = nome; } public string Nome { get { return _nome; } set { _nome = value; } } public int CompareTo(Usuario other) { return this.Nome.CompareTo(other.Nome);

33

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


}

34

Ao especificar a implementao da Interface IComparable<T>, j definimos que esta Interface dever trabalhar com o tipo Usuario, que justamente a classe onde estamos implementando-a. Dentro do mtodo CompareTo fornecido por ela comparamos a propriedade Nome da instncia corrente com a propriedade Nome da instncia que vem como parmetro para mtodo. Reparem que utilizando a Interface genrica no necessrio efetuar a converso dentro do mtodo para, em seguinda, efetuar a comparao, pois o objeto j est com o tipo especificado na implementao da Interface. Ao exibir a coleo depois de invocar o mtodo Sort, o resultado ser o seguinte:
VB.NET Dim list As New List(Of Usuario) list.Add(New Usuario("Virginia")) list.Add(New Usuario("Zuleika")) list.Add(New Usuario("Ana")) list.Add(New Usuario("Elisabeth")) list.Add(New Usuario("Tiago")) list.Add(New Usuario("Humberto")) list.Sort() Output Ana Elisabeth Humberto Tiago Virginia Zuleika C# List<Usuario> list = new List<Usuario>(); list.Add(new Usuario("Virginia")); list.Add(new Usuario("Zuleika")); list.Add(new Usuario("Ana")); list.Add(new Usuario("Elisabeth")); list.Add(new Usuario("Tiago")); list.Add(new Usuario("Humberto")); list.Sort(); //Output Ana Elisabeth Humberto Tiago

34

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


Virginia Zuleika

35

Um ponto bastante importante que deve ser comentado com relao ao cdigo acima que, ao invocar o mtodo Sort e o objeto especificado no implementar a Interface IComparable, uma exceo do tipo InvalidOperationException ser lanada. Como o exemplo um tanto quanto simples, pois tem apenas uma propriedade e, conseqentemente, um nico campo de ordenao, como ficaria se quisssemos disponibilizar vrias formas para ordenao? Exemplo: inicialmente a listagem exibida com os nomes em ordem alfabtica. Em seguida, gostaria de exibir a mesma listagem, mas agora ordenando por grupo. Para isso, devemos utilizar a Interface IComparer ou IComparer<T>. Essa Interface fornece um mtodo denominado Compare, que compara dois objetos retornando um nmero inteiro indicando se um objeto menor, igual ou maior que o outro objeto. Esta Interface dever ser implementada em uma classe diferente da atual (Usuario), uma espcie de classe de utilidades para executar o seu trabalho. O primeiro passo ser a criao da classe que implementar a Interface IComparer<T> e, sem seguida, codificar o mtodo Compare. Devemos ainda criar um enumerador para que o consumidor da classe possa escolher por qual campo ele deseja ordenar a listagem. A implementao exibida abaixo:
VB.NET Public Class UsuarioSorting Implements IComparer(Of Usuario) Public Enum SortType Nome Grupo End Enum Private _sortType As SortType Public Sub New(ByVal sortType As SortType) Me._sortType = sortType End Sub Public Function Compare(ByVal x As Usuario, ByVal y Usuario) _ As Integer Implements IComparer(Of Usuario).Compare Select Case Me._sortType Case SortType.Grupo Return x.Grupo.CompareTo(y.Grupo) Case SortType.Nome As

35

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


Return x.Nome.CompareTo(y.Nome) End Select Return 0 End Function End Class C# public class UsuarioSorting : IComparer<Usuario> { public enum SortType { Nome, Grupo } private SortType _sortType; public UsuarioSorting(SortType sortType) { this._sortType = sortType; } public int Compare(Usuario x, Usuario y) { switch (this._sortType) { case SortType.Grupo: return x.Grupo.CompareTo(y.Grupo); case SortType.Nome: return x.Nome.CompareTo(y.Nome); } } return 0;

36

A classe UsuarioSorting responsvel por receber qual o tipo de ordenao e, baseado nele ir determinar qual das propriedades devem ser comparadas para montar a ordenao. Finalmente, a utilizadao desta classe muda ligeiramente ao que vimos anteriormente durante o exemplo da Interface IComparable<T>. A diferena resume a informar para um dos overloads do mtodo Sort da coleo um objeto que customiza o ordenao (que obrigatoriamente deve implementar a Interface IComparer<T>):
VB.NET ordenar por nome usuarios.Sort(New UsuarioSorting(UsuarioSorting.SortType.Nome))

36

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces

37

ordenar por grupo usuarios.Sort(New UsuarioSorting(UsuarioSorting.SortType.Grupo)) C# //ordenar por nome usuarios.Sort(new UsuarioSorting(UsuarioSorting.SortType.Nome)); //ordenar por grupo usuarios.Sort(new UsuarioSorting(UsuarioSorting.SortType.Grupo));

Nota: Quando ambas Interfaces (IComparable<T> e IComparer<T> implementadas, somente a Interface IComparer<T> utilizada.

so

Ainda temos a Interface IEquatable<T> que compara dois objetos quanto igualdade, fazendo basicamente o que j faz o mtodo Equals de System.Object, mas assim como o IComparable<T>, com uma vantagem: trabalha com o tipo especfico e no System.Object, o que evita o boxing/unboxing. Essa Interface tem um mtodo chamado Equals que recebe como parmetro um objeto do mesmo tipo especificado na declarao da Interface. A implementao da Interface exibida abaixo:
VB.NET Public Class Usuario Implements IEquatable(Of Usuario) Private _nome As String Public Property Nome() As String Get Return Me._nome End Get Set(ByVal value As String) Me._nome = value End Set End Property Public Function Equals1(ByVal other As Usuario) As _ Boolean Implements IEquatable(Of Usuario).Equals If IsNothing(other) Then Return False Return Me.Nome = other.Nome End Function End Class C#

37

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


public class Usuario : IEquatable<Usuario> { private string _nome; public Usuario(string nome) { this._nome = nome; } public string Nome { get { return _nome; } set { _nome = value; } } public bool Equals(Usuario other) { if(other == null) return false; return other.Nome == this.Nome; }

38

natural que ao utilizar a classe e chamar o mtodo Equals, ver que na lista de overloads aparecer duas verses do mesmo mtodo. Uma delas espera exatamente o tipo informado na Interface IEquatable<T>, enquanto a outra espera um System.Object, que obviamente herda do tambm de System.Object. Copiando a referncia de um objeto utilizando ICloneable Quando falamos a respeito de tipos-referncia citamos que quando atribumos uma instncia de um objeto qualquer a um objeto do mesmo tipo recm criado, ambas passam a apontar (ter a mesma referncia) ao mesmo objeto na memria Heap. Sendo assim, qualquer mudana efetuada em algum dos objetos, ser imediatamente refletida no outro. Mas pode existir situaes onde ser necessrio criarmos uma cpia, ou melhor, um clone de objeto para que essa operao no interfira em seu original. Para assegurar isso, o Microsoft disponibilizou uma Interface chamada ICloneable que cria uma nova instncia do objeto em questo com os mesmo valores da instncia corrente. Esse processo chamado de clonar o objeto e, como j era de se esperar, a Interface fornece um mtodo chamado Clone para efetuarmos essa operao. Mas, quando falamos em clone, existem dois tipos: 1. Shallow-cloning: Este tipo de clone se resume a copiar apenas os valores, sem copiar qualquer referncia a outros objetos que existam internamente ao objeto.Com isso, tipos-referncia continuaram apontando para o mesmo local, mesmo no clone. Israel Aece | http://www.projetando.net 38

Captulo 1 Tipos de dados e Interfaces

39

2. Deep-cloning: Este tipo de clone, alm de copiar os valores, tambm copia os membros que so tipo-referncias, criando assim um novo objeto completamente independente. Atravs das imagens abaixo conseguimos comparar as diferncias entre os tipos de clones suportados:

Imagem 1.5 Shallow-Clone Os tipos-referncia ainda continuam apontando para o mesmo local.

Imagem 1.6 Deep-Clone Uma cpia completa do objeto. A classe System.Object fornece um mtodo protegido (protected) chamado MemberwiseClone. Este mtodo retorna um clone no estilo Shallow, ou seja, copiar os valores e apenas as referncia para os membros tipo-referncia, se existirem. Dependendo do cenrio isso no muito interessante. Se desejar mesmo fazer um clone do tipo Deep-Clonning, ser necessrio implementar a Interface ICloneable e customizar o mtodo Clone. A implementao no complexa e podemos comprovar atravs do cdigo abaixo: 39

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


VB.NET Public Class Cliente Private _nome As String Public Sub New(ByVal nome As String) Me._nome = nome End Sub Public Property Nome() As String Get Return Me._nome End Get Set(ByVal value As String) Me._nome = value End Set End Property End Class Public Class Pedido Implements ICloneable Private _id As Integer Private _data As DateTime Private _cliente As Cliente

40

Public Sub New(ByVal id As Integer, ByVal data As DateTime) Me._data = data Me._id = id End Sub Public Property Cliente() As Cliente Get Return Me._cliente End Get Set(ByVal value As Cliente) Me._cliente = value End Set End Property Public Function Clone() As Object _ Implements System.ICloneable.Clone Dim pedidoClone As New Pedido(Me._id, Me._data) pedidoClone.Cliente = New Cliente(Me.Cliente.Nome) Return pedidoClone End Function

40

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


End Class Utilizao: Dim pedido1 As New Pedido(1, DateTime.Now) pedido1.Cliente = New Cliente("Jos Torres") Dim pedido2 As Pedido = DirectCast(pedido1.Clone(), Pedido) pedido2.Cliente.Nome = "Maria Torres" Console.WriteLine(pedido1.Cliente.Nome) Console.WriteLine(pedido2.Cliente.Nome) Output: Jos Torres Maria Torres C# public class Cliente { private string _nome; public Cliente(string nome) { this._nome = nome; } public string Nome { get { return this._nome; } set { this._nome = value; } }

41

public class Pedido : ICloneable { private int _id; private DateTime _data; private Cliente _cliente; public Pedido(int id, DateTime data) { this._data = data; this._id = id;

41

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


} public Cliente Cliente { get { return this._cliente; } set { this._cliente = value; } } public object Clone() { Pedido clone = new Pedido(this._id, this._data); clone.Cliente = new Cliente(this.Cliente.Nome); return clone; }

42

//Utilizao: Pedido pedido1 = new Pedido(1, DateTime.Now); pedido1.Cliente = new Cliente("Jos Torres"); Pedido pedido2 = (Pedido)pedido1.Clone(); pedido2.Cliente.Nome = "Maria Torres"; Console.WriteLine(pedido1.Cliente.Nome); Console.WriteLine(pedido2.Cliente.Nome); //Output: //Jos Torres //Maria Torres

Como podemos notar, o clone copiou na ntegra todos os valores do pedido1, mesmo os valores que so tipos-referncia. Agora, se apenas modificarmos a implementao do mtodo Clone retornando a chamada para o mtodo MemberwiseClone de System.Object, veremos que apenas a referncia para o objeto Cliente copiado e, sendo assim, ao alterar a propriedade Nome do pedido2 refletir tambm no pedido1. Formatando um tipo em uma string com IFormattable J vimos e utilizamos vrias vezes padres de formatao que so fornecidos intrinsicamente pela infraestrutura do .NET Framework desde a sua verso 1.x. Esses padres so comuns quando necessitamos formatar datas ou nmeros, para exibirmos ao Israel Aece | http://www.projetando.net 42

Captulo 1 Tipos de dados e Interfaces

43

usurio um valor mais amigvel e, alguns exemplos tpicos esto neste artigo. Mas h situaes em que precisamos formatar o nosso objeto customizado e, felizmente, o .NET Framework fornece uma interface chamada IFormattable qual possui um nico mtodo denominado, ToString qual invocado automaticamente pelo runtime quando especificamos uma formatao. Para o nosso cenrio de exemplo, teremos dois objetos: Cliente e Cnpj. Cada cliente obrigatoriamente ter uma propriedade do tipo Cnpj. Este tipo por sua vez, implementar a interface IFormattable e dever fornecer dois tipos de formatao: DF e PR. O primeiro significa Documento Formatado e retornar o valor do CNPJ formatado; j a segunda opo significa Prefixo e, se o usurio optar por este tipo de formatao, ser retornado apenas o prefixo do CNPJ que, para quem no sabe, trata-se dos 9 primeiros dgitos. A arquitetura de classes que servir como exemplo:
VB.NET Public Class Cliente Private _nome As String Private _cnpj As Cnpj Public Sub New(nome As String, cnpj As String) Me._nome = nome Me._cnpj = New Cnpj(cnpj) End Sub Public Property Nome() As String Get Return Me._nome End Get Set(Value As String) Me._nome = value End Set End Property Public Property Cnpj() As Cnpj Get Return Me._cnpj End Get Set(Value As Cnpj) Me._cnpj = value End Set End Property End Class Public Class Cnpj Implements IFormattable Private _numero As String

43

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


Public Sub New(numero As String) Me._numero = numero End Sub Public Overloads Function ToString(format As String, _ formatProvider As IFormatProvider) As String _ Implements IFormattable.ToString

44

If format = "DF" Then Return Convert.ToDouble(Me._numero).ToString("000\.000\.000\/0000\-00") Else If format = "PR" Then Return Convert.ToDouble(Me._numero.Substring(0, 9)).ToString("000\.000\.000") End If Return Me._numero End Function Public Overrides Function ToString() As String Return Me.ToString(Nothing, Nothing) End Function End Class Utilizao: Dim c As New Cnpj("999999999999999") Response.Write(string.Format("O documento formatado {0:DF}.", c)) Response.Write(string.Format("O prefixo {0:PR}.", c)) Response.Write(string.Format("O documento {0}.", c)) ' Output: ' O documento formatado 999.999.999/9999-99. ' O prefixo 999.999.999. ' O documento 999999999999999. C# public class Cliente { private string _nome; private Cnpj _cnpj; public Cliente(string nome, string cnpj) { this._nome = nome; this._cnpj = new Cnpj(cnpj); }

44

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


public string Nome { get { return this._nome; } set { this._nome = value; } } public Cnpj Cnpj { get { return this._cnpj; } set { this._cnpj = value; } }

45

public class Cnpj : IFormattable { private string _numero; public Cnpj(string numero) { this._numero = numero; } public string ToString(string formatProvider) { if (format == "DF") return format, IFormatProvider

Convert.ToDouble(this._numero).ToString(@"000\.000\.000\/0000\00"); else if (format == "PR") return Convert.ToDouble(this._numero.Substring(0, 9)).ToString(@"000\.000\.000"); } return this._numero;

45

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


public override string ToString() { return this.ToString(null, null); }

46

//Utilizao: Cnpj c = new Cnpj("999999999999999"); Response.Write(string.Format("O documento formatado {0:DF}.", c)); Response.Write(string.Format("O prefixo {0:PR}.", c)); Response.Write(string.Format("O documento {0}.", c)); // Output: // O documento formatado 999.999.999/9999-99. // O prefixo 999.999.999. // O documento 999999999999999.

Como podemos reparar, a classe Cliente tem uma propriedade chamada Cnpj do tipo Cnpj. O objeto Cnpj implementa a Interface IFormattable e, dentro do mtodo ToString, verifica qual o formato est sendo passado para ele. Baseando-se neste formato que uma determinada formatao aplicada ao nmero do CNPJ e, caso nenhuma formatao especificada, somente o nmero retornado, sem nenhuma espcie de formatao. Esse tipo de formatao torna tudo muito flexvel e, podemos ainda especificar o tipo de formatao em controles DataBound, como por exemplo o GridView do ASP.NET, da mesma forma que fazemos para datas e nmeros. As imagens abaixo ilustram isso:

Imagem 1.7 Aplicando formatao em controles. Liberando referncias a objetos no gerenciados atravs da interface IDisposable 46

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces

47

Utilizar o mtodo Finalize disponibilizado pela classe System.Object muito til, pois assegura que recursos no gerenciados no fiquem perdidos quando objetos gerenciados descartado. S que h um problema neste cenrio: no possvel saber quando o mtodo Finalize chamado e o fato deste mtodo no ser pblico, no h a possibilidade de invoc-lo explicitamente. Imagine o seguinte cenrio: a conexo com um banco de dados qualquer extremamente custosa e deixar ela aberta at que o runtime determine que ela seja descartada, podemos ter vrios danos, pois isso pode acontecer at alguns dias depois. Para termos a possibilidade de finalizar o objeto deterministicamente e, ali fecharmos conexes com bancos de dados e arquivos a Microsoft implementou um padro chamado Dispose. Atravs de uma Interface chamada IDisposable, que contm um nico mtodo chamado Dispose, voc pode implementar na sua classe de acesso a dados, arquivos, MessageQueue, entre outros recursos para que o recurso seja explicitamente fechado quando o processo finalizar, sem a necessidade de aguardar que o Garbage Collector decida fazer isso. Na maioria dos cenrio onde se utilizar o padro Dispose, dentro da implementao do mtodo invoca-se um outro mtodo chamado SuppressFinalize da classe GC (Garbage Collector). Teoricamente como todos os recursos so finalizados dentro do mtodo Dispose, no h necessidade do runtime invocar o mtodo Finalize, justamente porque no tem mais nada a fazer, pois o objeto j foi descartado. Abaixo podemos visualizar uma classe customizada que implementa o padro Dispose:
VB.NET Public Class Reader Implements IDisposable Private _reader As StreamReader Private disposed As Boolean Public Sub New(ByVal filename As String) Me._reader = New StreamReader(filename) End Sub Protected Overridable Sub Dispose(ByVal disposing As Boolean) If Not Me.disposed Then If disposing Then If Not IsNothing(Me._reader) Then Me._reader.Dispose() End If End If Me.disposed = True End If

47

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


End Sub Public Sub Dispose() Implements IDisposable.Dispose Dispose(True) GC.SuppressFinalize(Me) End Sub End Class C# public class Reader : IDisposable { private StreamReader _reader; private bool disposed = false; public Reader(string filename) { this._reader = new StreamReader(filename); } protected virtual void Dispose(bool disposing) { if (!disposed) { if (disposing) { if (this._reader != null) { this._reader.Dispose(); } } } disposed = true;

48

public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }

Analisando o cdigo acima, nota-se que existem dois mtodos Dispose. O mtodo que no tem nenhum parmetro o mtodo fornecido pela Interface IDisposable. Alm dele, criamos um mtodo protegido (protected) e que pode ser sobrescrito nas classes derivadas que faz a anlise dos recursos que devem ser descartados quando o mtodo pblico Dispose chamado. Israel Aece | http://www.projetando.net

48

Captulo 1 Tipos de dados e Interfaces

49

Alm disso, termos uma classe que implementa IDisposable interessante porque podemos ainda utilizar em conjunto com o bloco using. Isso quer dizer que, ao utilizar o objeto dentro de um bloco using, o runtime se encarrega de invocar o mtodo Dispose mesmo que alguma exceo ocorra at o trmino da operao e sem termos o cdigo envolvido em blocos Try/Catch/Finally. Isso se d porque o compilar de encarrega de transformar o bloco using em um bloco envolvido por Try/Finally. Para finalizar, o cdigo abaixo exibe o consumo da classe Reader que foi construda acima com o padro IDisposable. A classe ser utilizada em conjunto com o bloco using. Isso far com que, ao chegar no trmino do bloco, o mtodo Dispose chamado automaticamente pelo runtime:
VB.NET Using reader As New Reader("C:\Temp.txt") 'executa outras operaes End Using C# using (Reader reader = new Reader("C:\\Temp.txt")) { //executa outras operaes }

Casting Efetuando converses em tipos-referncia Uma das partes mais importantes do Common Language Runtime (CLR) a segurana de tipos. Em runtime, o CRL sempre sabe qual tipo o objeto realmente e, se em algum momento voc quiser saber, basta chamar o mtodo GetType. Cast (ou converso) algo que os desenvolvedores utilizam imensamente na aplicao, convertendo um objeto em vrios outros tipos diferentes (desde que suportados). Geralmente, o cast para o tipo base no exige nenhum tipo especial. Para citar um exemplo, voc poderia fazer atribuir a uma varivel do tipo System.Object a instncia de uma classe Cliente, podendo utilizar o seguinte cdigo:
VB.NET Dim cliente As New Cliente() cliente.Nome = Jos Torres Dim obj As Object = cliente

49

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


C# Cliente cliente = new Cliente(); cliente.Nome = Jos Torres; object obj = cliente;

50

Para fazer o inverso, ou seja, extrair da varivel obj o Pedido que est dentro dela, necessrio efetuar o cast se estiver utilizando C#. O Visual Basic .NET no exige que voc faa isso explictamente, pois ele gera o cdigo necessrio para tal operao. No entanto sempre menos performtico fazer dessa forma e, alm disso, dificulta encontrarmos possveis problemas ainda em compile-time, ou seja, se o tipo no for compatvel, somente descobriremos isso quando a aplicao for executada e uma exceo for atirada. Para melhorar isso no Visual Basic .NET, v at as propriedades do projeto no Solution Explorer, em seguida na aba Compile e defina a opo Option Strict como On. Isso obrigar o desenvolvedor a fazer as converses explcitas no cdigo. Para extrair o pedido da varivel obj, o cdigo o seguinte:
VB.NET Dim pedidoNovo As Pedido = DirectCast(obj, Pedido) C# Pedido pedidoNovo = (Pedido)obj;

O cdigo acima somente resultar com sucesso se o valor contido dentro da varivel obj for realmente um Pedido. Do contrrio, a aplicao retornar um exceo do tipo InvalidCastException, informando que no possvel converter o que est em obj para Pedido. Para evitar que problemas como este ocorra em tempo de execuo, tanto o Visual Basic .NET quando o Visual C# fornecem alguns operadores que permitem que a converso seja feita de forma mais segura, evitando assim, o problema que identificamos acima. Felizmente para ambas as linguagens temos um operador que, se a converso no for satisfeita, ao invs de atirar uma exceo, retornar uma valor nulo e, conseqentemente, voc deve tratar isso para dar continuidade na execuo do cdigo. Estamos falando do operador as para o Visual C# e o operador TryCast para o Visual Basic .NET. Para exemplificar o uso destes operadores, vamos transformar o exemplo que fizemos logo acima para utilizar os operadores em questo:
VB.NET Dim pedidoNovo As Pedido = TryCast(obj, Pedido) If Not IsNothing(pedidoNovo) Then Console.WriteLine(pedidoNovo.Data)

50

Israel Aece | http://www.projetando.net

Captulo 1 Tipos de dados e Interfaces


End If C# Pedido pedidoNovo = obj as Pedido; if(pedidoNovo != null) { Console.WriteLine(pedidoNovo.Data); }

51

importante dizer que ao utilizar esses operadores, de nada vai adiantar se no verificar se est ou no nulo, pois se por algum motivo retornar nulo e voc invocar algum membro deste objeto, o runtime atirar uma exceo do tipo NullReferenceException.

51

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees


Captulo 2 Trabalhando com Colees Introduo

Desde a primeira verso do .NET Framework existem classes que possibilitam armazenarmos outros objetos e fornecem toda a funcionalidade para interagir com estes objetos. Essas colees obviamente foram mantidas, mas a Microsoft criou um novo subset de colees chamado Generics que possibilita ao desenvolvedor escrever um cdigo muito mais limpo e robusto. No decorrer deste artigo veremos as colees disponveis dentro do .NET Framework 2.0. O captulo comea mostrando as colees existentes desde a verso 1.0 do mesmo e, avanando um pouco mais, analisaremos as colees genricas disponveis na verso 2.0 que uma funcionalidade que trouxe uma grande flexibilidade e uma melhoria notvel em termos de performance e produtividade na escrita do cdigo. No veremos apenas as colees concretas, mas tambm vamos entender qual a arquitetura disponvel e as Interfaces necessrias e disponveis para a criao de colees customizadas. Ambas sees onde so abordados cada tipo de coleo, ser abordado as Interfaces disponveis para efetuarmos a comparao entre objetos. Depois de analisar as coleo primrias e as colees genricas, vamos abordar as colees especializadas, que so criadas para uma finalidade muito especfica e tambm classes que fornecem as funcionalidades bsicas para a criao de colees customizadas para uma determinado cenrio. O que so Colees? Colees so classes que armazenam em seu interior os mais diversos tipos de objetos. Elas so muito mais fcil de manipular em relao a Arrays, justamente porque colees fornecem uma infraestrutura completa para a manipulao de seus elementos. Para citar alguns de seus benefcios, podemos destacar: redimensionamento automtico; mtodo para inserir, remover e verificar se existe um determinado elemento; mtodos que permitem o usurio interceptar a insero ou a excluso de um elemento. As colees disponibilizadas pelo .NET Framework 2.0 e as Interfaces que so base para grande parte delas esto atualmente disponibilizadas em dois namespaces diferentes: System.Collections e System.Collections.Generics. O primeiro j existe desde a verso 1.0 do .NET Framework, mas o segundo uma novidade que foi incorporada na verso 2.0. Dentro destes namespaces, encontramos vrias classes e Interfaces para a criao dos mais diversos tipos de colees. As colees esto definidas em trs categorias: Colees Ordenadas, Colees Indexadas e Colees baseadas em "chave-e-valor". Veremos abaixo a finalidade de cada uma delas: Israel Aece | http://www.projetando.net 1

Captulo 2 Trabalhando com Colees

Colees Ordenadas: So colees que so distingidas apenas pela ordem de insero e assim controla a ordem em que os objetos podem ser recuperados da coleo. System.Collections.Stack e System.Collections.Queue so exemplos deste tipo de coleo. Colees Indexadas: So colees que so distingidas pelo fato de que seus valores e/ou objetos podem ser recuperados/acessados atravs de um ndice numrico (baseado em 0 (zero)). System.Collections.ArrayList um exemplo deste tipo de coleo. Colees "Chave-e-Valor": Como o prprio tipo diz, uma coleo que contm uma chave associado a algum tipo. Seus ndices so classificados no valor da chave e podem ser recuperados na ordem classificado pela enumerao. System.Collections.HashTable um exemplo deste tipo de coleo.

Nota: Para todos os tipos citados como exemplo para a descrio dos tipos de colees sempre haver um correspondente dentro do namespace System.Collections.Generics, que trabalhar de forma genrica. Colees Primrias Definimos como colees primrias todas as colees disponveis dentro do namespace System.Collections. Estas colees tambm so chamadas de colees fracamente tipadas ou colees no genricas. Essas colees, apesar de suportar grande partes das funcionalidades que so disponibilizadas pelo .NET Framework, h um grande problema, j que essas classes operam com tipos System.Object, o que causa problemas em termos de performance, pois se quiser fazer uma coleo de inteiros teremos que pagar pelo boxing e unboxing. Alm disso, ainda h a questo de no trabalhar efetivamente com um nico tipo, ou seja, o fato de possibilitar incluir um System.Object, est permitindo adicionar qualquer tipo de objeto dentro da coleo, o que pode causar problemas durante a execuo da aplicao. Para enterdemos a estrutura das colees primrias, vamos analisar todas as Interfaces que foram implementadas nas colees concretas e que tambm esto disponveis para que o desenvolvedor possa customizar seus prprias colees. A Imagem 2.1 exibe a hierarquia das Interfaces e a tabela a seguir detalha cada uma dessas Interfaces.

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees

Imagem 2.1 Hierarquia das Interfaces do namespace System.Collections Interface ICollection Descrio a Interface base para todas as colees que esto contidas dentro do namespace System.Collections. Direta ou indiretamente essas classes a implementa. Ela por sua vez composta por trs propriedades (Count, IsSynchronized e SyncRoot) e um mtodo (CopyTo). Interfaces como IList e IDictionary derivam desta Interface ICollection, pois estas Interfaces so mais especializadas, tendo tambm outros mtodos a serem definidos alm dos quais j Israel Aece | http://www.projetando.net 3

Captulo 2 Trabalhando com Colees

IComparer

esto contidos dentro da Interface ICollection. J as classes como System.Collections.Queue e System.Collections.Stack implementam a Interface ICollection diretamente. Esta Interface utilizada para customizar uma ordenao (sorting) em uma determinada coleo. A Interface IComparer composta por um mtodo chamado Compare, qual compara dois objetos e retorna um valor inteiro indicando se os objetos so ou no iguais, ou seja, se os valores comparados forem iguais, 0 (zero) retornado. H classes, como por exemplo o ArrayList, que j implementa o mtodo Sort para tal ordenao, mas h casos em que desejamos criar uma ordenao customizada, onde a ordenao padro no nos interessa. justamente neste momento que a Interface IComparer utilizada, inclusive o mtodo Sort da classe ArrayList tem um overload que recebe como parmetro um objeto do tipo Interface IComparer. Quando utilizamos o mtodo Sort do ArrayList sem informar nenhum parmetro, a ordenao feita em ordem ascendente, a padro. Mas podemos querer ordenar em ordem descendente, e com isso teramos que criar uma classe que implementa a Interface IComparer, codificando assim o mtodo Compare. A Interface IDictionary a base para todas as colees do tipo "chave-e-valor". Cada elemento inserido neste tipo de coleo armazenado em um objeto do tipo DictionaryEntry (Structure).

IDictionary

Cada associao deve ter um chave original que no seja uma referncia nula, mas o seu respectivo valor de associao pode incluir sem problemas uma referncia nula. IDictionaryEnumerator Esta Interface tem a mesma finalidade da Interface IEnumerator, ou seja, percorrer e ler os elementos de uma determinada coleo, mas esta em especial, trata de enumerar os elementos de um dicionrio, ou seja, uma coleo que contenha "chave-e-valor". IEnumerable a base direta ou indiretamente para algumas outras Interfaces e todas as colees a implementam. Esta interface tem apenas um mtodo a ser implementado, chamado GetEnumerator, qual retorna um objeto do tipo da Interface de IEnumerator. IEnumerator a base para todos os enumeradores. Composta por uma propriedade (Current) e dois mtodos (MoveNext e Reset), percorre e l todos os elementos de uma determinada coleo, permitindo apenas ler os dados, no podendo alter-los. Vale levar em considerao que enumeradores no podem ser Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees

IEqualityComparer

utilizados para a coleo subjacente. Expe os mtodos chamados Equals e GetHashCode que permitem a comparao entre dois objetos quando a igualdade. Esta Interface foi introduzida a partir do .NET Framework 2.0. Derivada das Interfaces ICollection e IEnumerable ela a Interface base para todas as listas contidas no .NET Framework. Listas quais podemos acessar individualmente por um determinado ndice. As implementaes da Interface IList podem estar em uma das seguintes categorias: Read-Only, Fixed-Size e Variable-Size. Uma IList Read-Only, no permite que voc modifique os elementos. J uma IList Fixed-Size, no permite adicionar ou remover os elementos, mas permite que voc altere os elementos existentes. E por fim, uma IList Variable-Size, qual permite que voc adicione, remova ou altere os elementos. Claro que quando implementamos esta Interface em alguma classe, necessrio criarmos um membro privado que ser o responsvel por armazenar os items. Este membro privado manipulado pelos mtodos e propriedades da Interface IList que sero implementados nesta classe.

IList

Antes de efetivamente entrar em cada uma das colees concretas dentro das colees primrias, vamos analisar a implementao de alguma das Interfaces que vimos acima para um cenrio customizado de uma determinada aplicao. Entre as vrias Interfaces acima mostradas, duas delas praticamente trabalham em conjunto: IEnumerable e IEnumerator. A Interface IEnumerator fornece mtodos para iterar pela coleo; j a Interface IEnumerable deve ser implementada em um tipo que voc deseja iterar pela coleo atravs de um loop foreach (For Each em Visual Basic .NET). A implementao destas Interfaces geralmente feito em classes separadas e, na maioria dos casos, o mtodo GetEnumerator da Interface IEnumerable retorna a instncia da classe privada que implementa IEnumerator. O cdigo abaixo exemplifica como iterar pelas cidades categorias contidas dentro da classe CategoriesEnumerator:
VB.NET Public Class CategoriesEnumerator Implements IEnumerator Private _categories() As String = _ {"ASP.NET", "VB.NET", "C#", "SQL", "XML"} Private _current As Integer = -1

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees

Public ReadOnly Property Current() As Object Implements IEnumerator.Current Get If Me._current < 0 OrElse Me._current > 4 Then Throw New InvalidOperationException Else Return Me._categories(Me._current) End If End Get End Property Public Function MoveNext() IEnumerator.MoveNext Me._current += 1 Return Me._current <= 4 End Function As Boolean Implements

Public Sub Reset() Implements IEnumerator.Reset Me._current = -1 End Sub End Class C# public class CategoriesEnumerator : IEnumerator { private string[] _categories = new string[] { "ASP.NET", "VB.NET", "C#", "SQL", "XML" }; private int _current = -1; public object Current { get { if (this._current < 0 || this._current > 4) throw new InvalidOperationException(); else return this._categories[this._current]; } } public bool MoveNext() { this._current++; return this._current <= 4; } public void Reset() {

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees


this._current = -1;

A classe acima est finalizada. Se quisssemos exibir os valores na tela, bastaria loop verificando se o mtodo retorna True ou False e exibir o contedo da tela recuperand-o atravs da propriedade Current. Mas e se quisssemos iterar por essa coleo atravs de um lao For Each? nesta ocasio que a Interface IEnumerable entra em ao. necessrio que voc cria uma classe auxiliar, implementando a Interface em questo onde, dentro do mtodo GetEnumerator fornecido por ela, retornar uma instncia da classe CategoriesEnumerator. O cdigo abaixo mostra essa implementao na ntegra e a respectiva utilizao:
VB.NET Public Class Categories Implements IEnumerable Public Function GetEnumerator() As IEnumerator IEnumerable.GetEnumerator Return New CategoriesEnumerator() End Sub End Class Utilizao: For Each category As String In New Categories() Console.WriteLine(category) Next C# public class Categories : IEnumerable { public IEnumerator GetEnumerator() { return new CategoriesEnumerator(); } } //Utilizao: foreach (string category in new Categories()) Console.WriteLine(category); Implements

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees

Outra Interface que comumente utilizada a IList. Ela fornece grande parte da infraestrutura para todas as colees do .NET Framework. Um exemplo de implementao para esta Interface exibido abaixo:
VB.NET Public Class TestIList Implements IList Private _arr As ArrayList Public Sub New() Me._arr = New ArrayList End Sub Public ReadOnly Property Count() System.Collections.ICollection.Count Get Return Me._arr.Count() End Get End Property Public Function Add(ByVal value Implements System.Collections.IList.Add Return Me._arr.Add(value) End Function ' outros mtodos ocultados End Class C# public class TestIList : IList { private ArrayList _arr; public TestIList() { this._arr = new ArrayList(); } public int Count { get { return this._arr.Count; } } public int Add(object value) As Integer Implements

As

Object)

As

Integer

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees


{ } }

return this._arr.Add(value);

importante ressaltar que no cdigo acima criado um membro privado chamado de _arr do tipo ArrayList e que sua manipulao feita atravs dos mtodos que esto definidos na Interface IList e nesta classe implementados. Com a Interface IList conseguimos criar uma coleo customizada, podendo inclusive criar uma coleo fortemente tipada, onde seus tipos seriam nicos, ou seja, seria aceito apenas um objeto de um tipo definido e assim, em design-time j conseguiramos detectar possveis problemas de casting, mas infelizmente no evita o boxing e unboxing. O cdigo acima apenas um exemplo para entedermos o funcionamento da implementao da Interface IList, onde a implementamos e criamos um membro privado do tipo ArrayList para armazenar os objetos, pois dentro do namespace System.Collections j temos um classe abstrata, chamada CollectionBase, que a base para a criao de colees fortemente tipadas. Veremos mais tarde, ainda neste captulo, sobre a coleo CollectionBase. Colees ArrayList A coleo ArrayList um dos objetos mais tradicionais que o .NET Framework expe. Trata-se de uma classe que implementa a Interface IList e muito similar a um Array unidemensional, mas automaticamente redimesiona o seu tamanho (que inicialmente 0) de acordo com a necessidade. Atravs do mtodo Add, podemos adicionar qualquer tipo de objeto, sendo uma referncia nula, um objeto j adicionado anteriormente ou de tipos diferentes. Depois de adicionados, seus items podem ser acessados atravs de um ndice inteiro (propriedade (indexer) fornecido pela Interface IList), que indica a posio do elemento que quer recuperar. Trata-se de uma coleo baseada em 0 (zero) e, sendo assim, a posio do primeiro elemento 0. Para exibir um exemplo do uso da classe ArrayList um tanto quanto simples:
VB.NET Dim categorias As New ArrayList() categorias.Add(ASP.NET) categorias.Add(VB.NET) categorias.Add(C#)

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees


categorias.Add(XML) Utilizao: For index As Integer = 0 To categorias.Count 1 Console.WriteLine(categorias(index)) Next C# ArrayList categorias = new ArrayList(); categorias.Add(ASP.NET); categorias.Add(VB.NET); categorias.Add(C#); categorias.Add(XML); for (int index = 0; index < categorias.Count; index++) Console.WriteLine(categorias[index]);

10

Stack A coleo Stack uma coleo do tipo LIFO (last-in-first-out), ou seja, o primeiro item a entrar na coleo o ltimo a sair. Ela oferece dois mtodos importantes: Push e Pop. O primeiro encarrega-se de adicionar o item a coleo. J o segundo, Pop, utilizado para recuperar e remover o ltimo item adicionado na coleo. Alm destes dois mtodos, ainda existe o mtodo Peek, que trabalha basicamente da mesma forma que o mtodo Pop, ou seja, recupera o ltimo item adicionado, mas com uma grande diferena: no remove o item da coleo. Para exibir um exemplo da utilizao da coleo Stack, analise o cdigo abaixo:
VB.NET Dim stack As New Stack() stack.Push("1") stack.Push("2") stack.Push("3") stack.Push("4") Console.WriteLine(Quantidade: & stack.Count) While(stack.Count > 0) Console.WriteLine(stack.Pop()); End While Console.WriteLine(Quantidade: & stack.Count) Output: Quantidade: 4 4

10

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees


3 2 1 Quantidade: 0 C# Stack stack = new Stack(); stack.Push("1"); stack.Push("2"); stack.Push("3"); stack.Push("4"); Console.WriteLine(Quantidade: + stack.Count); while (stack.Count > 0) Console.WriteLine(stack.Pop()); Console.WriteLine(Quantidade: + stack.Count); //Output: Quantidade: 4 4 3 2 1 Quantidade: 0

11

Queue A coleo Queue trata-se de uma coleo do tipo FIFO (first-in-first-out), ou seja, o primeiro a entrar na lista o primeiro a sair. H alguns programas que trabalham neste formato, como por exemplo o Message Queue do Windows. Esse tipo de colees so teis quando necessitamos trabalhar em um ambiente que exige um processamento sequencial. Podemos fazer uma analogia a qualquer tipo de fila de qualquer estabelecimento. Exemplo: em um banco, onde somente exista um caixa atendendo uma fila de N clientes, o primeiro a chegar, que obviamente est no incio da file, ser o primeiro a ser atendido e, conseqentemente, o primeiro a sair e, o processo continua at o trmino da fila. Assim como na coleo Stack, a Queue tambm possui dois mtodos importantes: Enqueue e Dequeue. O primeiro encarrega-se de enfileirar o valor a coleo. J o segundo, Dequeue, utilizado para recuperar e remover o item mais antigo adicionado na coleo. Novamente temos o mtodo Peek, que trabalha basicamente da mesma forma que o mtodo Dequeue, ou seja, recupera o item mais antigo adicionado, mas com uma grande diferena: no remove o item da coleo. Para exibir um exemplo da utilizao da coleo Queue, analise o cdigo abaixo: Israel Aece | http://www.projetando.net

11

Captulo 2 Trabalhando com Colees

12

VB.NET Dim queue As New Queue() queue.Enqueue("1") queue.Enqueue("2") queue.Enqueue("3") queue.Enqueue("4") Console.WriteLine(Quantidade: & queue.Count) While(queue.Count > 0) Console.WriteLine(queue.Dequeue()); End While Console.WriteLine(Quantidade: & queue.Count) Output: Quantidade: 4 4 3 2 1 Quantidade: 0 C# Queue queue = new Queue(); queue.Enqueue("1"); queue.Enqueue("2"); queue.Enqueue("3"); queue.Enqueue("4"); Console.WriteLine(Quantidade: + queue.Count); while (queue.Count > 0) Console.WriteLine(queue.Dequeue()); Console.WriteLine(Quantidade: + queue.Count); //Output: Quantidade: 4 4 3 2 1 Quantidade: 0

Hashtable Esta classe representa uma coleo do tipo chave/valor e so organizadas de acordo com um valor hash da chave informada e armazenada em uma estrutura do tipo Israel Aece | http://www.projetando.net

12

Captulo 2 Trabalhando com Colees

13

DictionaryEntry. Como calculado um valor hash em cima da chave informada, a performance beneficiada, pois reduz o nmero de comparaes efetuadas quando se procura por uma determinada chave, pois o Hashtable extrai o cdigo hash do valor a ser encontrado e submete este valor para a pesquisa. Certifique-se sempre antes de adicionar um item a coleo de que esta chave j no existe, pois do contrrio, uma exceo do tipo ArgumentException atirada. Como o Hashtable armazena o cdigo hash para a chave informada, no altere este valor, seno o Hashtable no ser mais capaz de encontr-lo. Se a sua chave tende a mudar, ento o correto remover o item da coleo, alterar a chave e, em seguida, adicion-lo novamente. A chave nem sempre precisa ser uma string. Qualquer objeto que implemente o mtodo System.Object.GetHashCode e System.Object.Equals ser permitido. importante saber que a chave no pode ser uma referncia nula, ao contrrio do valor, que permite isso. Para exibir um exemplo simples da utilizao da coleo Hashtable, analise o cdigo abaixo:
VB.NET Dim table As new Hashtable() table.Add("AC", "Acre") table.Add("RJ", "Rio de Janeiro") table.Add("SP", "So Paulo") For Each entry As DictionaryEntry In table Console.WriteLine(entry.Key & " - " & entry.Value) Next C# Hashtable table table.Add("AC", table.Add("RJ", table.Add("SP", = new Hashtable(); "Acre"); "Rio de Janeiro"); "So Paulo");

foreach (DictionaryEntry entry in table) Console.WriteLine(entry.Key + " - " + entry.Value);

Como mencionamos anteriormente, os objetos adicionados dentro de uma coleo Hashtable so do tipo DictionaryEntry, o que significa que para iterar entre os valores do mesmo, ter que gerar o lao como exemplicado no cdigo acima. SortedList 13

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees

14

A coleo SortedList basicamente uma coleo Hashtable, com uma diferena: uma coleo ordenada pela chave. Evidentemente que a SortedList tende ser mais lenta que a Hashtable, devido ao overhead de sorting. Quando um item adicionado em uma SortedList, ele j inserido exatamente no local correto e os ndices so ajustados automaticamente. O mesmo acontece quando o elemento removido da coleo. Entretanto, importante observar que o ndice de um determinado item pode variar, pois a coleo reordenada e, se tiver um ndice para um objeto especfico, ele j pode no mais apontar para o mesmo. Para exibir um exemplo simples da utilizao da coleo SortedList, analise o cdigo abaixo:
VB.NET Dim list As new SortedList() list.Add("RJ", "Rio de Janeiro") list.Add("AC", "Acre") list.Add("SP", "So Paulo") For Each entry As DictionaryEntry In list Console.WriteLine(entry.Key & " - " & entry.Value) Next Output AC Acre RJ Rio de Janeiro SP So Paulo C# SortedList list = new SortedList(); list.Add("RJ", "Rio de Janeiro"); list.Add("AC", "Acre"); list.Add("SP", "So Paulo"); foreach (DictionaryEntry entry in list) Console.WriteLine(entry.Key + " - " + entry.Value); //Output AC Acre RJ Rio de Janeiro SP So Paulo

Alm dos items da coleo SortedList serem acessveis atravs da chave, tambm possvel acess-lo atravs do ndice. Para isso, utilize o mtodo GetByIndex passando como parmetro um nmero inteiro que corresponde ao elemento que quer recuperar. Israel Aece | http://www.projetando.net

14

Captulo 2 Trabalhando com Colees


CollectionBase

15

muito comum em runtime retornarmos valores da Base de Dados e armazenarmos em objetos do tipo Datasets, mas com isso ocorre o Weakly Typed e o Late Binding, ou seja, no temos a segurana de tipos e o acesso suas propriedades somente ocorre durante o tempo de execuo. Tendo esse problema, o que podemos fazer para termos nossos prprios objetos com suas respectivas propriedades e mtodos ao invs de uma genrica? Eis o momento que entra em cena a classe CollectionBase. A classe CollectionBase uma classe onde podemos apenas herd-la, ou seja, uma classe abstrata. Ela implementa as trs seguintes Interfaces: IList, ICollection e IEnumerable que definem os mtodos que permitem aceitar tipos System.Object. Alm disso, ela ainda encapsula um objeto do tipo ArrayList, onde ficaro armazenados os elementos da coleo. Entre os principais mtodos desta classe, temos: Mtodo Add Contains Insert Item Remove RemoveAt Descrio Adiciona um novo elemento na coleo e retorna o indce (posio) que o objeto foi adicionado. Verifica se j existe um determinado elemento dentro da coleo e retorna um valor boleano indicando ou no sua existncia. Insere um elemento na coleo em uma determinada posio. Retorna ou recebe um elemento dado uma posio. Remove um elemento da coleo. Remove um objeto da coleo dado uma posio.

Os mtodos acima so expostos atravs da propriedade List. Ser necessrio criar esses mtodos que serviro de wrapper para os mtodos internos de manipulao da coleo. A direferena que esses mtodos que sero criados e expostos para o cliente devem aceitar em seus parmetros o mesmo tipo, isso quer dizer que, se a coleo trabalhar somente com objetos do tipo Usuario, necessrio que os membros Add, Remove, Contains e Item recebam este mesmo tipo para garantir o type-safe. O trecho de cdigo abaixo mostra como a classe deve ser implementada:
VB.NET Public Class UsuarioColecao Inherits CollectionBase Public Function Add(ByVal u As Usuario) As Integer Return MyBase.List.Add(u) End Function Public Function Contains(ByVal u As Usuario) As Boolean Return MyBase.List.Contains(u)

15

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees


End Function Public Sub Insert(ByVal index As Integer, _ ByVal u As Usuario) MyBase.List.Insert(index, u) End Sub Default Usuario Get Public Property Item(ByVal index As Integer)

16

As

Return CType(MyBase.List(index), Usuario) End Get Set(ByVal Value As Usuario) MyBase.List(index) = Value End Get End Property

Public Sub Remove(ByVal u As Usuario) MyBase.List.Remove(u) End Sub End Class C# public class UsuarioColecao : CollectionBase { public int Add(Usuario u) { return base.List.Add(u); } public bool Contains(Usuario u) { return base.List.Contains(u); } public void Insert(int index, Usuario u) { base.List.Insert(index, u); } public Usuario this[int index] { get { return (Usuario)base.List[index]; } set { base.List[index] = value;

16

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees


}

17

public void Remove(Usuario u) { base.List.Remove(u); }

Durante a sua utilizao, ela somente poder manipular objetos do tipo Usuario. Qualquer outro tipo que voc tentar adicionar resultar em um erro de compilao e no conseguir compilar o projeto at que o problema seja sanado. Isso ir resolver o problema do type-safe, j que ao invs de expor objetos do tipo ArrayList, voc pode optar por expor a coleo customizada. Apesar de uma boa melhora, ainda temos alguns problemas que nos causam prejuzo: a produtividade muito baixa, j que, se tivssemos 20 colees de objetos diferentes, devemos ter 20 colees distintas. Alm disso, o problema de boxing/unboxing continua existindo, j que internamente a classe CollectionBase armazena em um objeto ArrayList. Esses problemas foram todos abolidos com a introduo dos Generics, que veremos ainda neste captulo. DictionaryBase Assim como a classe CollectionBase, a DictionaryBase vem para forneceer uma infraestrutura base para a criao de dicionrios (pares de chave-valor) com tipos especficos que voc desejar, para novamente garantir o type-safe. Internamente esta coleo armazena os itens em uma outra colees do tipo Hashtable, que j vimos acima. A implementao basicamente a mesma em relao ao que vimos acima com a coleo CollectionBase, apenas mudando que temos uma coleo do tipo chave-valor que, por sua vez, armazenar uma string como chave e um objeto do tipo Usuario como valor:
VB.NET Public Class UsuarioDictionary Inherits DictionaryBase Public Sub Add(ByVal key As String, ByVal value As Usuario) MyBase.Dictionary.Add(key, value) End Function Outros mtodos End Class

17

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees


C# public class UsuarioDictionary : DictionaryBase { public void Add(string key, Usuario value) { base.Dictionary.Add(key, value); } } //Outros mtodos

18

Com relao a forma de iterao, continua sendo a mesma da Hashtable, ou seha, atravs da estrutura DictionaryEntry. Mais uma vez, isso somente ajudar no que diz respeito ao type-safe. A produtividade e performance ainda continuam comprometidas. Colees Genricas Na seo anterior vimos a infraestrutura e a utilizao das mais diversas colees disponveis no .NET Framework desde a sua verso 1.0. Como tambm foi dito, temos alguns problemas quando fazemos das colees primrias, pois havia sempre boxing e unboxing para adicionar, recuperar e remover os elementos de cada uma das colees. Um outro agravante que para termos uma coleo fortemente tipada tnhamos que escrever uma poro de cdigo para garantir o type-safe, mas mesmo assim, no evitava o boxing/unboxing, prejudicando imensamente a performance. Felizmente a Microsoft introduziu as colees genricas. Essas colees so basicamente as mesmas que vimos anteriormente com uma enorme diferena: operam com qualquer tipo definido dentro do .NET Framework ou um tipo customizado pelo desenvolvedor. Alm disso, no h mais boxing/unboxing e tambm no exige mais o casting quando necessitar recuperar um dos elementos da coleo, j que toda a checagem de tipos efetuada durante a escrita do cdigo, ou seja, se criar uma coleo genrica especificando o tipo string para este coleo, jamais conseguir adicionar um tipo de dado inteiro ou decimal. Interfaces disponveis Foi introduzido um novo namespace chamado de System.Collections.Generic. Dentro deste namespace h todas as Interfaces primrias, s que em sua forma genrica. Isso quer dizer que temos todas essas Interfaces agoram operando com um tipo genrica. Basicamente dentro de cada Interface genrica temos o(s) mesmo(s) membro(s) que possuem as Interfaces primrias, s que estes tipos so substitudos pelo tipo que o desenvolvedor desejar manipular.

18

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees

19

Como a maioria das Interfaces tem exatamente a mesma finalidade das Interfaces primrias, apenas operando com um tipo especfico, as mesmas sero apenas citadas abaixo sem suas definies, mas sempre com suas correspondentes primrias: Interface Genrica ICollection<T> IComparer<T> IDictionary<TKey, TValue> IEnumerable<T> IEnumerator<T> IEqualityComparer<T> IList<T> Correspondente Primria ICollection IComparer IDictionary IEnumerable IEnumerator IEqualityComparer IList

Cada uma destas Interfaces so implementadas nas mais diversas colees genricas, quais sero abordadas ainda neste captulo. importante dizer que estas Interfaces, com as Interfaces primrias, esto a disposio para que o desenvolvedor possa criar classes (colees) mais customizadas, de acordo com sua necessidade. Na tabela acima, o T dever ser substitudo por um tipo que voc desejar que essa Interface manipule. Uma das nicas excees que a Interface IDictionary necessita de dois tipos: um para a chave e outro para o valor. Isso ir possibilitar o utilizador da Interface determinar qual ser o tipo de dado da chave e o tipo de dado do valor que dever ser armazenado pela coleo. Para implementar uma dessas Interfaces, devemos utilizar a seguinte sintaxe:
VB.NET Imports System.Collections.Generic Public Class ListaUsuarios Implements IList(Of Usuario) ... End Class C# using System.Collections.Generic; public class ListUsuarios : IList<Usuario> { //... }

19 Colees List<T> Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees

20

Uma das mais populares colees fornecidas pelo namespace das colees genricas, System.Collections.Generic, a coleo List<T>. Essa coleo pode ser comparada ao ArrayList, devido as suas finalidades, s que a coleo List<T> permite que voc especifique um tipo, que a coleo toda ir manipular, o que garantirar que todas as propriedades e mtodos desta classe somente aceitaro objetos do tipo especificado na construo da coleo, ao contrrio do ArrayList que, por sua vez, aceita um System.Object, em outras palavras, qualquer coisa, gerando todos os problemas que j discutimos acima. Assim como o ArrayList, a coleo List<T> permite acessar seu itens atravs de um ndice, mtodos para efetuar buscas, ordenao e a manipulao completa a coleo e seus respectivos itens. Ambas as colees implementam as Interfaces IList e IList<T>, respectivamente. Entre os vrios mtodos e propriedades fornecidos pela coleo List<T> podemos citar os mais importantes que, operam sempre com o tipo especificado durante a criao da coleo: Mtodo Contains IndexOf Sort Descrio Dado um objeto, ele retorna um valor booleano indicando se o mesmo existe ou no dentro da coleo corrente. Dado um objeto, ele faz a busca dentro da coleo corrente e, se o encontrar, retorna um nmero inteiro indicando a posio do elemento. Se no for encontrado, -1 retornado. Como o prprio nome diz, utilizado para permitir a ordenao dos itens que esto contidos na coleo. Este mtodo, em um dos seus overloads, aceita uma instncia de um objeto que implementa a Interface IComparer<T> que poder determinar os critrios de ordenao da coleo. Retorna um nmero inteiro indicando o nmero de elementos dentro da coleo. Se no existir nenhum elemento, 0 retornado. Atravs da propriedade default (indexer em Visual C#), podemos recuperar ou atribuir um valor para um determinado item da coleo. Quando invocado, remove todos os itens da coleo.

Count Item Clear

Atravs do cdigo abaixo, podemos analisar como devemos proceder para a utilizao da coleo List<T>:
VB.NET Imports System.Collections.Generic Dim lista As New List(Of String) lista.Add(Generics legal!) lista.Add(Visual Basic .NET suporta Generics!) lista.Add(123) gera um erro

20

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees


C# using System.Collections.Generic; List<string> lista = new List<string>(); lista.Add(Generics legal!); lista.Add(Visual C# suporta Generics!); lista.Add(123); // gera um erro

21

Nota Importante: Como a coleo List<T> otimizada apenas para execuo de algumas tarefas onde ter uma boa performance crucial. Sendo assim, voc nunca deve retornar uma coleo do tipo List<T> em suas APIs de object models e sim uma coleo do tipo Collection<T>. A coleo do tipo List<T> no permite que voc receba notificaes quando o cliente modificar a coleo. Collection<T> A classe/coleo Collection<T> est localizada dentro do namespace System.Collections.ObjectModel. Ela fornece toda a base para a criao de uma coleo genrica e, apesar de no estar em um namespace dentro de Generic, ela uma coleo genrica da mesma forma. Para utilizao dela, voc pode criar diretamente a instncia dela, definindo o tipo que ela ir operar ou, se desejar, ter um tipo especfico que representar a coleo. Neste segundo caso, voc pode herdar diretamente de Collection<T> e j especificar o tipo. Assim, voc poder expor essa coleo aos clientes e ainda, como j foi falado acima, a coleo Collection<T> flexvel, pois permite interceptarmos a insero ou remoo de algum elemento da coleo, limpeza, definio de algum elemento mas, para isso, ser mesmo necessrio herdar em sua classe (coleo) e sobrescrever os mtodos que permitem isso, como por exemplo o mtodo RemoveItem. Como existem duas formas de utilizarmos essa coleo, vamos analisar os dois tipos, sendo o primeiro a utilizao direta da coleo Collection<T> e, em seguida, essa mesma classe ser herdada em uma coleo mais customizada, onde poderemos interceptar algumas operaes sob ela.
VB.NET Imports System.Collections.ObjectModel Dim coll As New Collection(Of String) coll.Add(Generics legal!) coll.Add(Visual Basic .NET suporta Generics!) C#

21

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees


using System.Collections.ObjectModel; Collection<string> coll = new Collection<string>(); coll.Add(Generics legal!); coll.Add(Visual C# suporta Generics!);

22

VB.NET Imports System.Collections.ObjectModel Module Module1 Sub Main() Dim usuarios As New ColecaoUsuario() AddHandler usuarios.Removed, AddressOf Removido usuarios.Add("Jos") usuarios.Add("Joo") usuarios.Add("Augusto") usuarios.Add("Israel") usuarios.Add("Marcelo") usuarios.Add("Claudia") usuarios.Add("Leandro") usuarios.Remove("Israel") End Sub Private Sub Removido(ByVal sender As Object, ByVal e RemovedItemEventArgs) Console.WriteLine("Item removido: " + e.RemovedItem) End Sub End Module Public Class ColecaoUsuario Inherits Collection(Of String) Public Event Removed As EventHandler(Of RemovedItemEventArgs) Protected Overrides Sub RemoveItem(ByVal index As Integer) Dim removedItem As String = Me(index) MyBase.RemoveItem(index) Dim args As New RemovedItemEventArgs(removedItem) RaiseEvent Removed(Me, args) End Sub End Class Public Class RemovedItemEventArgs Inherits EventArgs Private _removedItem As String As

22

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees

23

Public Sub New(ByVal removedItem As String) Me._removedItem = removedItem End Sub Public ReadOnly Property RemovedItem() As String Get Return Me._removedItem End Get End Property End Class C# using System.Collections.ObjectModel; class Program { static void Main(string[] args) { ColecaoUsuario usuarios = new ColecaoUsuario(); usuarios.Removed += new EventHandler<RemovedItemEventArgs>(Removido); usuarios.Add("Jos"); usuarios.Add("Joo"); usuarios.Add("Augusto"); usuarios.Add("Israel"); usuarios.Add("Marcelo"); usuarios.Add("Flavia"); usuarios.Add("Leandro"); usuarios.Remove("Israel");

static void Removido(object sender, Program.RemovedItemEventArgs e) { Console.WriteLine("Item removido: " + e.RemovedItem); } public class ColecaoUsuario : Collection<string> { public event EventHandler<RemovedItemEventArgs> Removed; protected override void RemoveItem(int index) { string removedItem = this.Items[index]; base.RemoveItem(index); if (Removed != null) {

23

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees


RemovedItemEventArgs args = new RemovedItemEventArgs(removedItem); this.Removed(this, args);

24

public class RemovedItemEventArgs : EventArgs { private string _removedItem; public RemovedItemEventArgs(string removedItem) { this._removedItem = removedItem; } public string RemovedItem { get { return this._removedItem; } }

Como podemos analisar neste segundo caso, foi criado uma classe chamada ColecaoUsuario que herda diretamente da classe Collection<T>, especificando o tipo string. Com isso, temos acesso a todos os mtodos fornecidos pela classe base. A idia que quando um elemento for removido da coleo, uma mensagem seja exibida ao usurio que o respectivo item foi removido. Para isso, iremos sobrescrever o mtodo RemoveItem, que denotado como protected virtual na classe base. Isso ir permitir interceptar o processo de remoo e, via eventos, podemos notificar o cliente que a coleo foi alterada, algo que no possvel fazer com a classe List<T>. Stack<T> e Queue<T> Ambas as colees tem exatamente a mesma finalidade que as colees primrias Stack e Queue. A nica diferena entre elas que as classes Stack<T> e Queue<T> operam da forma genrica. Isso quer dizer que, seus principais mtodos: Push e Pop, Enqueue e Dequeue, respectivamente, inserem ou retornam sempre o tipo especificado durante a criao da coleo. Em relao as colees primria, na delcarao precisamos especificar o tipo, asim como j necessrio com a classe List<T>.
VB.NET Imports System.Collections.Generic

24

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees

25

Dim coll As New Stack(Of String) C# using System.Collections.Generic; Stack<string> coll = new Stack<string>();

Dictionary<TKey, TValue> Essa coleo trata-se da verso genrica da classe Hashtable. Todas as operaes fornecidas por esta classe baseiam-se nos tipos especificados em sua declarao. Esta classe fornece em sua construo dois tipos que devemos informar: TKey e TValue. O primeiro representa o tipo que ser a chave e o segundo representar o tipo que ser o objeto que devemos aceitar como valor, associado a chave. Assim como o Hashtable, a chave no pode ser uma referncia nula, mas o valor pode ser, sem problema algum. Ainda h uma mudana com relao ao objeto que representar a chave e o valor durante a iterao da coleo. Ao invs de utilizarmos a estrutura DictionaryEntry, estaremos utilizando a estrutura KeyValuePair<TKey, TValue> que a verso genrica da estrutura anterior e ser utilizado por todas as colees genricas que operam utilizando chave e valor. Essa coleo utiliza uma implementao da Interface IEqualityComparer para determinar se as chaves so ou no iguais. Para isso, voc pode especificar essa implementao customizada atravs do construtor da classe Dictionary. O cdigo abaixo exibe uma uma soluo onde a chave deve ser uma string e o valor um objeto do tipo Usuario. Depois de adicionar os itens e suas respectivas chaves, ele exibe os mesmos atravs de um lao For Each. Se reparar, logo aps a propriedade Key ou Value da estrutura KeyValuePair, j podemos acessar qualquer membro dos objetos, como o caso da propriedade Nome da classe Usuario:
VB.NET Imports System.Collections.Generic Dim dic As New Dictionary(Of String, Usuario) dic.Add("JT", New Usuario("Jose Torres")) dic.Add("ZC", New Usuario("Zuleika Camargo")) dic.Add("MA", New Usuario("Maria Antonieta")) For Each pair As KeyValuePair(Of String, Usuario) In dic Console.WriteLine(pair.Value.Nome) Next

25

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees


C# using System.Collections.Generic; Dictionary<string, Usuario> dic = new Dictionary<string, Usuario>(); dic.Add("JT", new Usuario("Jose Torres")); dic.Add("ZC", new Usuario("Zuleika Camargo")); dic.Add("MA", new Usuario("Maria Antonieta")); foreach (KeyValuePair<string, Usuario> pair in dic) { Console.WriteLine(pair.Value.Nome); }

26

SortedList<TKey, TValue> e SortedDictionary<TKey, TValue> Novamente, a SortedList<TKey, TValue> e a verso genrica para a classe SortedList, que opera com tipos genricos. Essa classe recebe em seu construtor uma implementao da Interface IComparer para determinar se as chaves so ou no iguais. A SortedDictionary<TKey, TValue> trata-se de uma nova coleo que foi introduzida na verso 2.0 do .NET Framework, que tambm trabalha como uma coleo de chave e valor, baseando-se em uma nica chave. Assim como a SortedList, tambm ordenada pela chave, mas com uma importante diferena: muito mais rpido em comparada a SortedList<TKey, TValue> em relao a insero e remoo de elementos, mas utiliza mais memria que ela. A utilizao destas classes so bem semelhantes. O que realmente muda o comportamente interno de cada uma delas.
VB.NET Imports System.Collections.Generic Dim sl As New SortedList(Of String, Usuario) Dim sd As New SortedDictionary(Of String, Usuario) sl.Add("MA", New Usuario("Maria Antonieta")) sd.Add("MA", New Usuario("Maria Antonieta")) C# using System.Collections.Generic; SortedList<string, Usuario> sl = new SortedList<string, Usuario> (); = new SortedDictionary<string, Usuario> sd

26

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees


SortedDictionary<string, Usuario> (); sl.Add("MA", new Usuario("Maria Antonieta")); sd.Add("MA", new Usuario("Maria Antonieta"));

27

LinkedList<T> Imagine que temos uma lista de tarefas a serem realizadas em um determinado projeto. Essas tarefas so melhorias, validaes e tambm alguns ajustes que devem ser realizados para que o projeto continue funcionando. Voc e sua equipe vo desenvolvem a lista com todas essas tarefas e, neste momento, no se preocupam com a propriedade de cada uma delas. Como tal projeto j est em produo e os clientes esto a todo vapor o utilizando, as melhorias devem ser as ltimas a serem realizadas. J as validaes e os ajustes devem ter uma prioriodade maior sobre as melhorias. Neste cenrio, como que podemos adicionar os ajustes e validaes acima das melhorias, que no so uma necessidade no momento? Transformando todo esse cenrio em .NET, mais especificamente em uma coleo, essas tarefas, ainda sem uma ordem especfica, foram sendo adicionadas uma coleo. Agora necessrio adicionar as tarefas especificando um n (tarefa) que representado pelo elemento da coleo e assim, podemos inserir a nova tarefa antes ou depois do n especificado durante a insero do novo elemento. A coleo que nos oferece essa estrutura a LinkedList<T>. Ela permite inserir um elemento na coleo antes ou depois de elemento j existente que especificado durante a insero ou remoo. Cada um dos elementos que so inseridos dentro desta coleo so encapsulados dentro de uma classe do tipo LinkedListNode<T>. Como trata-se de uma coleo do tipo linked, a classe LinkedListNode<T> fornece duas propriedades bastante interessantes, chamadas: Previous e Next que retornam tambm um objeto do tipo LinkedListNode<T>. O primeiro, Previous, retorna uma referncia para o n anterior e nulo se estiver sendo chamado atravs do primeiro n da coleo. J o mtodo Next, retorna uma referncia para o n seguinte, tambm retornando nulo se a propriedade estiver sendo chamada atravs do ltimo n da coleo. J entre os membros da classe LinkedList<T>, temos alguns que merecem serem citados: Membro First Last Descrio Esta propriedade retorna um objeto do tipo LinkedListNode<T> que representa o primeiro elemento da coleo. Se a coleo estiver vazia, ser retornado um valor nulo. Esta propriedade retorna um objeto do tipo LinkedListNode<T> que representa o ltimo elemento da coleo. Se a coleo estiver vazia, ser retornado um valor nulo. Israel Aece | http://www.projetando.net

27

Captulo 2 Trabalhando com Colees


AddAfter AddBeforer AddFirst AddLast Find

28

RemoveFirst RemoveLast

Adiciona um novo n com o novo valor, logo aps o n especificado. Adiciona um novo n com o novo valor, antes do n especificado. Adiciona um novo n no incio da coleo. Adiciona um novo n no final da coleo. Dado um valor, retorna a um objeto do tipo LinkedListNode<T> que representa o n em que o objeto especificado est encapsulado. Se a coleo no encontrar o valor especificado, uma referncia nula retornada. Remove o primeiro n da coleo. Remove o ltimo n da coleo.

Para exemplificar, ser criado uma coleo LinkedList<T> onde teremos as tarefas de um determinado projeto. Teremos trs tarefas de categorias diferentes: melhoria, ajuste e erro. Como os erros so prioridades, devemos sempre coloc-los em uma sequncia, j que os erros so sempre crticos e tem uma maior prioridade em relao a qualquer outra tarefa. Sendo assim, analise o cdigo abaixo:
VB.NET Imports System.Collections.Generic Dim projeto As New LinkedList(Of String) projeto.AddFirst("[Melhoria]: Melhoria 1.") projeto.AddFirst("[Ajuste]: Ajuste 1.") projeto.AddFirst("[Erro]: Erro 1.") Dim erro1 As LinkedListNode(Of String) = projeto.Find("[Erro]: Erro 1.") If Not IsNothing(erro1) Then projeto.AddAfter(erro1, "[Erro]: Erro 2.") End If For Each node As String In projeto Console.WriteLine(node) Next C# using System.Collections.Generic; LinkedList<string> projeto = new LinkedList<string>(); projeto.AddFirst("[Melhoria]: Melhoria 1."); projeto.AddFirst("[Ajuste]: Ajuste 1."); projeto.AddFirst("[Erro]: Erro 1."); LinkedListNode<string> erro1 = projeto.Find("[Erro]: Erro 1.");

28

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees


if(erro1 != null) projeto.AddAfter(erro1, "[Erro]: Erro 2."); foreach (string node in projeto) Console.WriteLine(node);

29

Inicialmente criamos a instncia da coleo LinkedList especificando o tipo string. Em seguida, adicionamos trs itens (tarefas) atravs do mtodo AddFirst. Como este mtodo adiciona o item sempre em primeiro lugar na lista, neste caso, o ltimo ser o primeiro. Como no nosso cenrio importante termos os erros em primeiro lugar, ao receber um novo erro que deve ser colocado no lista, precisamos recuperar o ltimo erro inserido atravs do mtodo Find e, se encontrado na coleo, devemos invocar o mtodo AddAfter para adicionar essa nova tarefa imediatamente abaixo do ltima tarefa de erro criada. Para ambos os cdigos, o retorno ser o mesmo:
[Erro]: Erro 1. [Erro]: Erro 2. [Ajuste]: Ajuste 1. [Melhoria]: Melhoria 1.

Iterators Os Iterators uma nova funcionalidade disponibilizada apenas pelo Visual C# 2.0. Logo no incio deste captulo, em Colees Primrias, vimos que para que possamos iterar entre os elementos de uma coleo customizada atravs de um lao foreach (For Each em Visual Basic .NET) necessrio implementarmos a Interface IEnumerable. Esta Interface IEnumerable expe um mtodo chamado GetEnumerator que retorna um enumerador (classe que implementa a Interface IEnumerator) que, por sua vez, fornecem os seguintes membros: MoveNext, Current, Reset e Dispose, quais so utilizados pelo runtime para que ele possa recuperar cada item da coleo. Agora, com os Iterators, isso no mais necessrio, j que quando o compilador detecta a presena de um Iterator, ele automaticamente gera o cdigo necessrio para a criao do enumerador. Com isso, no ser mais necessrio criarmos uma classe, geralmente uma classe privada, que implemente a Interface IEnumerator que retornada atravs do mtodo GetEnumerator. Para exemplificar o uso de um Iterator, podemos utilizar o mesmo exemplo que utilizamos quando foi abordado sobre as Interfaces IEnumerable e IEnumerator. A idia iterar entre as categorias definidas no interior de uma classe qualquer: 29

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees

30

C# public class Categories : IEnumerable { private string[] _categories = new string[ ] { "ASP.NET", "VB.NET", "C#", "SQL", "XML" }; public IEnumerator GetEnumerator() { for (int i = 0; i < this._categories.Length; i++) { yield return this._categories[i]; } }

//Utilizao: foreach (string category in new Categories()) Console.WriteLine(category);

Apesar de no mais precisarmos de uma classe auxiliar para criar o enumerador, quando utilizamos a keyword yield, automaticamente o compilar gera a classe que implementa a Interface IEnumerator, implementando todos os mtodos necessrios para a iterao entre os elementos da coleo. A keyword yield que determina a criao do enumerador, ainda permite uma outra forma de passar os itens para o mesmo, mantendo o mesmo resultado final. O trecho de cdigo abaixo mostrado essa outra forma da utilizao do Iterator:
C# public class Categories : IEnumerable { public IEnumerator GetEnumerator() { yield return "ASP.NET"; yield return "VB.NET"; yield return "C#"; yield return "SQL"; yield return "XML"; } }

Nota: Apesar de que o exemplo est sendo mostrado com a Interface primria IEnumerable, voc pode tambm implementar os Iterators com a Interface genrica IEnumerable<T>. Israel Aece | http://www.projetando.net

30

Captulo 2 Trabalhando com Colees

31

Colees Especializadas Como o prprio nome diz, as colees especializadas so colees criadas especificamente para um propsito muito especial. A Microsoft disponibilizou vrias delas que esto contidas dentro do namespace System.Collections.Specialized. Essas classes foram criadas para operarem sempre com um determinado tipo, justamente para conseguir adaptar e otimizar o melhor possvel a coleo, sem overheads extras. As colees especializadas fornecidas pelo .NET Framework esto divididas em quatro categorias, quais podemos analisar atravs da tabela abaixo: Categoria Strings Classes Dictionary Classes Descrio So colees que foram criadas exclusivamente para manipularem strings: StringCollection, StringDictionary, StringEnumerator e CollectionsUtil. Colees contidas dentro desta categoria, fornecem dicionrios de dados de algo performance que, dependendo do que armazenam e da quantidade de elementos, mudam o seu comportamento interno para otimizar a leitura, gravao e o armazenamento dos dados. Entre as colees desta categoria temos: ListDictionary, HybridDictionary e OrderedDictionary. As classes disponibilizadas aqui fornecem uma base para uma coleo que chaves strings e valores objects que voc pode acessar atravs da chave ou de um ndice. Isso tipo de coleo permitir que adicionemos valores duplicados, agrupando por uma chave. Como exemplo destas classes temos: NameValueCollection e NameObjectCollectionBase. Fornece uma estrutura para armazenar valores booleanos e pequenos inteiros em 32-bits de memria. Para isso temos as seguintes estruturas: BitVector32 e BitVector32.Section. Cada uma das categorias e suas respectivas colees estaro detalhadamente explicadas nas prximas pginas. Colees Specialized String Classes Israel Aece | http://www.projetando.net 31

Named Collection Classes

Bit Structures

Captulo 2 Trabalhando com Colees


StringCollection

32

Esta coleo basicamente anloga a uma coleo do tipo ArrayList em termos de funcionalidades. A nica diferena que a classe StringCollection trabalha apenas com strings. Internamente, a classe StringCollection armazena os valores em um objeto do tipo ArrayList e, serve de wrapper para o mesmo, fornecendo ao cliente todos os mtodos necessrios para manipular os elementos dentro dele contidos. A sua implementao to simples quanto a do ArrayList:
VB.NET Imports System.Collections.Specialized Dim categorias As New StringCollection() categorias.Add(ASP.NET) categorias.Add(VB.NET) categorias.Add(C#) categorias.Add(XML) Utilizao: For index As Integer = 0 To categorias.Count 1 Console.WriteLine(categorias(index)) Next C# using System.Collections.Specialized; StringCollection categorias = new StringCollection(); categorias.Add(ASP.NET); categorias.Add(VB.NET); categorias.Add(C#); categorias.Add(XML); for (int index = 0; index < categorias.Count; index++) Console.WriteLine(categorias[index]);

StringDictionary Esta coleo basicamente anloga a uma coleo do tipo Hashtable em termos de funcionalidades. A nica diferena que a classe StringDictionary trabalha apenas com strings tanto na chave quanto no valor, podendo apenas o valor ser uma referncia nula. Internamente, a classe StringDictionary armazena os valores em um objeto do tipo Hashtable e, serve de wrapper para o mesmo, fornecendo ao cliente todos os mtodos necessrios para manipular os elementos dentro dele contidos. Israel Aece | http://www.projetando.net

32

Captulo 2 Trabalhando com Colees

33

Um pequena diferena que, ao contrrio da classe/coleo Hashtable, esta coleo trabalha independentemente de maisculas e minsculas, fazendo com que as chaves sejam armazenadas em lowercase. A sua implementao to simples quanto a do Hashtable:
VB.NET Imports System.Collections.Specialized Dim dic As new StringDictionary() dic.Add("AC", "Acre") dic.Add("RJ", "Rio de Janeiro") dic.Add("SP", "So Paulo") For Each entry As DictionaryEntry In dic Console.WriteLine(entry.Key & " - " & entry.Value) Next C# using System.Collections.Specialized; StringDictionary dic = new StringDictionary(); dic.Add("AC", "Acre"); dic.Add("RJ", "Rio de Janeiro"); dic.Add("SP", "So Paulo"); foreach (DictionaryEntry entry in dic) Console.WriteLine(entry.Key + " - " + entry.Value);

StringEnumerator Como j vimos acima, os laos foreach (For Each em Visual Basic .NET) ocultam toda a complexidade dos enumeradores, e alm disso, recomendado pela Microsoft ao invs de manipular diretamente o enumerador. Para saber mais sobre os enumeradores, consulta a seo das colees primrias onde so abordadas as Interfaces IEnumerable e IEnumerator. A classe StringEnumerator um enumerador que manipula uma coleo de strings. Com isso, o mtodo GetEnumerator da classe StringCollection retorna um enumerador do tipo StringEnumerator, qual utilizaremos para iterar entre os elementos da coleo. O cdigo abaixo exibe um exemplo de como extrair o enumerador de uma StringCoolection: 33
VB.NET Imports System.Collections.Specialized

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees


Dim cats As New StringCollection() cats.AddRange(New String() {"ASP.NET", "XML"})

34

"VB.NET",

"C#",

"SQL",

Dim enumerador As StringEnumerator = cats.GetEnumerator() While (enumerador.MoveNext()) Console.WriteLine(enumerador.Current) End While C# using System.Collections.Specialized; StringCollection cats = new StringCollection(); cats.AddRange(new string[] { "ASP.NET", "VB.NET", "C#", "SQL", "XML" }); StringEnumerator enumerador = cats.GetEnumerator(); while (enumerador.MoveNext()) Console.WriteLine(enumerador.Current);

CollectionsUtil Esta classe fornece dois mtodos estticos para a criao de colees que ignoram a diferenciao entre maisculas e minsculas. Ela fornece dois que auxiliam na criao das colees: CreateCaseInsensitiveHashtable e CreateCaseInsensitiveSortedList. O primeiro deles, retorna uma instncia de um Hashtable que no far a distino entre maisculas e minsculas, o que significa que a chave SP e sp so iguais. J o segundo mtodo, CreateCaseInsensitiveSortedList, tem a mesma finalidade, s que retorna um objeto do tipo SortedList que ignora tambm maisculas e minsculas. O cdigo abaixo exibe a criao das colees acima citadas atravs dos mtodos estticos fornecidos pela classe CollectionsUtil:
VB.NET Imports System.Collections.Specialized Dim t As Hashtable = _ CollectionsUtil.CreateCaseInsensitiveHashtable() Dim s As SortedList = _ CollectionsUtil.CreateCaseInsensitiveSortedList() C# using System.Collections.Specialized; Hashtable hashTable = CollectionsUtil.CreateCaseInsensitiveHashtable();

34

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees


SortedList sortedList = CollectionsUtil.CreateCaseInsensitiveSortedList();

35

Uma outra alternativa em relao a classe CollectionsUtil passar como parmetro para as classes Hashtable ou SortedList uma instncia da classe StringComparer que foi implementanda para ignorar a diferenciao entre maisculas e minsculas. Um dos overloads do construtor dessas classes recebem um tipo IEqualityComparer, onde voc pode informar a implementao que fornecida atravs do mtodo esttico CurrentCultureIgnoreCase da classe StringComparer. A alternativa exibida atravs do cdigo abaixo:
VB.NET Imports System.Collections.Specialized Dim t As Hashtable = _ New Hashtable(StringComparer.CurrentCultureIgnoreCase) Dim s As SortedList = _ New SortedList(StringComparer.CurrentCultureIgnoreCase) C# using System.Collections.Specialized; Hashtable hashTable = new Hashtable(StringComparer.CurrentCultureIgnoreCase); SortedList lisortedListst = new SortedList(StringComparer.CurrentCultureIgnoreCase);

Specialized Dictionary Classes ListDictionary Este dicionrio de dados uma implementao simples da Interface IDictionary. Ele muito menor e mais rpido quando comparado com o Hashtable e se o nmero de elemento for menor ou igual a 10 e, no deve ser utilizado se a performance importante para uma coleo com muitos elementos. Os itens que esto dentro deste dicionrio no garantem que estaro em uma ordem especfica e o cdigo que voc escreve no deve depender da ordem corrente de adio. A sua utilizao em termos de escrita de cdigo idntica ao Hashtable. necessrio instanci-lo e, atravs do mtodo Add, adicionar a chave e valor. HybridDictionary Israel Aece | http://www.projetando.net

35

Captulo 2 Trabalhando com Colees

36

Essa coleo tem um comportamente um tanto quanto especial. Trata-se tambm de um dicionrio de dados que implementa a Interface IDictionary e que, internamente, armazena os dados em um objeto do tipo ListDictionary enquanto a coleo pequena e, quando a coleo ganha um grande nmero de elemento, automaticamente o repositrio trocado por uma Hashtable. Com este comportamento, esta coleo recomendada para casos onde o nmero de elementos desconhecido, onde ele garantir a performance enquanto a coleo estiver pequena e tambm para quando esta mesma coleo ganhar uma proporo maior. Alm disso, essa coleo recebe em seu construtor um valor booleano indicando se a coleo vai ou no ignorar a diferena entre maisculas e minsculas quando for comparar uma chave. Mais uma vez, a utilizao da coleo HybridDictionary em termos de escrita de cdigo idntica ao Hashtable. necessrio instanci-lo e, atravs do mtodo Add, adicionar a chave e valor. OrderedDictionary Essa classe implementa a Interface IOrderedDictionary que representa uma coleo indexada atravs de pares de chave e valor. Toda coleo que implementa essa Interface pode acessar seus elementos atravs de um chave ou ndice. basicamente uma forma de combinar um ArrayList com um Hashtable, pois os elementos do ArrayList somente so acessveis atravs de ndices e o Hashtable, podemos acessar os elementos atravs de uma chave. Como a Interface IOrderedDictionary tambm fornece uma propriedade default (indexer em Visual C#), temos agora duas propriedades deste tipo para recuperar um determinado elemento. A diferena entre as duas propriedade que uma delas receber um nmero inteiro que representar o elemento que desejamos recuperar; a segunda, receber um objeto que representa a chave que foi utilizada para inserir o elemento dentro da coleo. Podemos visualizar isso atravs do cdigo abaixo:
VB.NET Imports System.Collections.Specialized Dim dic As New OrderedDictionary dic.Add("AC", "Acre") dic.Add("RJ", "Rio de Janeiro") dic.Add("SP", "So Paulo") Console.WriteLine(dic("RJ")) Console.WriteLine(dic(1))

36

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees


C# using System.Collections.Specialized; OrderedDictionary dic = new OrderedDictionary(); dic.Add("AC", "Acre"); dic.Add("RJ", "Rio de Janeiro"); dic.Add("SP", "So Paulo"); Console.WriteLine(dic["RJ"]); Console.WriteLine(dic[1]);

37

Ambas as formas (atravs da chave e atravs do ndice) retornaro Rio de Janeiro. Specialized Named Collection Classes NameObjectCollectionBase Esta uma classe abstrata para as colees considerada named collections. Internamente ela mantm um objeto do tipo Hashtable onde sua chave uma string e seu valor um object. Como trata-se de uma classe abstrata, voc pode estar customizando a sua prpria coleo a partir desta classe base. Quando for criar a sua prpria coleo herdando desta, no se preocupe em precisar implementar nenhum mtodo. Apenas alguns mtodos so denotados como protected virtual (Protected Overridable em Visual Basic .NET), quais voc pode customizar para a sua coleo. NameValueCollection Derivada de NameObjectCollectionBase, esta uma coleo que pode ser acessada atravs de uma chave ou um ndice. Mas o diferencial desta classe mesmo a possibilidade de armazenar mltiplas valores (strings) atravs de uma nica chave. Isso quer dizer que voc pode adicionar chaves repetidas que, internamente, a coleo se encarrega de ajustar para quando desejar extrair os valores, todos eles sejam retornados. Alguns exemplos tpicos deste tipo de coleo so as propriedades QueryString, Forms e Headers da classe HttpRequest, utilizada em aplicaes ASP.NET. Para exemplificar a utilizao desta coleo, analise o cdigo abaixo:
VB.NET Imports System.Collections.Specialized Dim queryStrings As New NameValueCollection queryStrings.Add("Alunos", "Jos") queryStrings.Add("Alunos", "Ivan")

37

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees


queryStrings.Add("Alunos", "Mario") queryStrings.Add("Alunos", "Castro") queryStrings.Add("Alunos", "Mendes") queryStrings.Add("Instrutor", "Israel") For Each s As String In queryStrings.AllKeys Console.WriteLine("{0} - {1}", s, queryStrings(s)) Next C# using System.Collections.Specialized; NameValueCollection queryStrings = new NameValueCollection(); queryStrings.Add("Alunos", "Jos"); queryStrings.Add("Alunos", "Ivan"); queryStrings.Add("Alunos", "Mario"); queryStrings.Add("Alunos", "Castro"); queryStrings.Add("Alunos", "Mendes"); queryStrings.Add("Instrutor", "Israel"); foreach (string s in queryStrings.AllKeys) Console.WriteLine("{0} - {1}", s, queryStrings[s]);

38

Como podemos visualizar, perfeitamente permitido adicionar chaves iguais que a coleo trabalha sem nenhum problema. Em seguinda, dentro do lao percorremos a coleo distinta de chaves e, atravs de uma propriedade default (indexer em Visual C#) da classe NameValueCollection, passamos a chave corrente e ela retornar todos os valores que esto vinculados a chave informada. Para ambos os cdigo, o retorno ser o seguinte:
Alunos - Jos,Ivan,Mario,Castro,Mendes Instrutor Israel

Bit Structures BitVector32 e BitVector32.Section Essas duas estruturas trabalham em conjunto e permitem voc armazenar dentro dela valores inteiros ou valores booleanos utilizando apenas 32 bits de memria. Essa estrutura tem uma performance superior quando comparado a classe BitArray, justamente por manipular tipos-valor e no tipos-referncia. Comparer

38

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees

39

Ambos namespaces System.Collections e System.Collections.Generic possuem classes com implementaes padres das Interfaces IComparer e IComparer<T>, respectivamente. Os dois tpicos abaixo explicam e ilustram a utilizao de cada uma dessas classes. Comparer Primrio (System.Collections) Dentro deste namespace temos uma classe chamada Comparerm que possui uma implementao bsica da Interface IComparer (discutida no captulo 1). Essa classe recebe em seu construtor um objeto do tipo CultureInfo (contido no namespace System.Globalization) utilizando isso para a globalizao da aplicao, pois em diferentes culturas, podemos ter diferentes formas de comparao e tambm de resultados. O exemplo abaixo ilustra como utiliz-la, especificando a nossa cultura (ptBR) para efetuar a comparao:
VB.NET Imports System.Collections Imports System.Globalization Dim comp As New Comparer(New CultureInfo("pt-BR")) Console.WriteLine("Comparando Paulo e paulo : {0}", _ comp.Compare("Paulo", "paulo")) C# using System.Collections; using System.Globalization; Comparer comp = new Comparer(new CultureInfo("pt-BR")); Console.WriteLine("Comparando Paulo e paulo : {0}", comp.Compare("Paulo", "paulo"));

Como j sabemos, o mtodo Compare retorna um nmero inteiro indicando se um objeto menor, igual ou maior que o outro. No nosso caso, ser retornado 1, indicando que Paulo maior que paulo. Essa classe ainda possui um membro pblico esttico chamado Default, que retorna uma instncia da classe Comparer com a cultura especificada dentro da thread atual (Thread.CurrentCulture). Comparer Genrico (System.Collections.Generic) Quando necessitamos ordenar uma lista, como por exemplo, a classe List<T>, necessitamos que os objetos contidos nela sejam ordenados alfabticamente. Sendo assim, quando voc chama o mtodo Sort da classe List<T> e o seu objeto no Israel Aece | http://www.projetando.net 39

Captulo 2 Trabalhando com Colees

40

implementar a Interface genrica IComparable<T> ou a Interface primria IComparable, uma excesso disparada. Como trata-se de uma classe genrica chamada Comparer<T>. O fato dela ser genrica, necessrio especificarmos na sua chamada, o tipo com o qual ela ir trabalhar. Para o nosso exemplo, desejaremos ordenar uma lista, que contm objetos do tipo Usuario, atravs do nome (string), de forma alfabtica. Essa classe fornece uma propriedade esttica, de somente leitura chamada Default, que retorna um comparer especfico para o tipo informado que, no nosso caso, ser uma string. Para o exemplo, foi construdo uma classe chamada Usuario que, em seu construtor, passado o nome do mesmo e, expe uma propriedade de escrita/leitura chamada Nome. O trecho de cdigo abaixo exibe na ntegra a construo da classe e tambm o cdigo responsvel pela ordenao da lista de usurios:
VB.NET Public Class Usuario Private _nome As String Public Sub New(ByVal nome As String) Me._nome = nome End Sub Public Property Nome() As String Get Return Me._nome End Get Set(ByVal value As String) Me._nome = value End Set End Property End Class ... Imports System.Collections.Generic Sub Main() Dim l As New List(Of Usuario) l.Add(New Usuario("Israel")) l.Add(New Usuario("Roberto")) l.Add(New Usuario("Anderson")) l.Add(New Usuario("Paulo")) l.Add(New Usuario("Leandro")) l.Sort(New Comparison(Of Usuario)(AddressOf InternalSort)) For Each u As Usuario In l Console.WriteLine(u.Nome) Next

40

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees


End Sub

41

Private Function InternalSort(ByVal u1 As Usuario, ByVal u2 As Usuario) As Integer Return Comparer(Of String).Default.Compare(u1.Nome, u2.Nome) End Function C# public class Usuario { private string _nome; public Usuario(string nome) { this._nome = nome; } public string Nome { get { return this._nome; } set { this._nome = value; } }

//... using System.Collections.Generic; static void Main(string[] args) { List<Usuario> l = new List<Usuario>(); l.Add(new Usuario("Israel")); l.Add(new Usuario("Roberto")); l.Add(new Usuario("Anderson")); l.Add(new Usuario("Paulo")); l.Add(new Usuario("Leandro")); l.Sort(new Comparison<Usuario>(InternalSort)); foreach (Usuario u in l) Console.WriteLine(u.Nome);

41

private static int InternalSort(Usuario u1, Usuario u2) {

Israel Aece | http://www.projetando.net

Captulo 2 Trabalhando com Colees


return Comparer<string>.Default.Compare(u1.Nome, u2.Nome);

42

Nota: Lembrando que no Visual C# h a possibilidade de utlizarmos os mtodos annimos para evitar a criao de um procedimento adicional.

42

Israel Aece | http://www.projetando.net

Captulo 3 Utilizao de Assemblies


Captulo 3 Utilizao de Assemblies Introduo

At o momento, escrevemos o cdigo dentro do Visual Studio .NET e, pressionando F5 ou Ctrl + F5 (Build) rodamos a aplicao para ela ser executada e, conseqentemente, testamos para ver se o resultado o esperado. Mas e nos bastidores, o que realmente acontece? A finalidade deste captulo justamente abordar o processo que feito quando rodamos a aplicao. O que acontece por detrs disso a compilao do cdigo escrito em um Assembly. Alm de entedermos o processo de gerao do Assembly, vamos analisar a forma que temos para assinar o Assembly e, assim, garantirmos uma maior segurana e unicidade. Ainda nesta primeira parte, analisaremos os tipos de Assemblies e tambm as formas que temos para distribu-los; como embutir arquivos de recursos dentro de um Assembly e como acess-lo. J na segunda parte do captulo, ser abordado as Installer Classes, quais fornecem uma possibilidade de automatizamos a instalao dos Assemblies nos clientes. Finalmente, veremos alguns funcionalidades disponveis para criarmos sees de configuraes customizadas dentro do arquivo de configurao da aplicao. Isso ir permitir termos uma aplicao mais flexvel e bem mais intuitiva para os clientes que iro instal-la. O que so Assemblies? No primeiro captulo analisamos o CLR Common Language Runtime que, como o prprio nome diz, um runtime comum para todas as aplicaes que utilizam o .NET Framework. Sendo assim, o CLR no conhece nada sobre a linguagem de programao que voc escolheu para melhor expressar suas intenes. Quando voc executa a aplicao dentro do Visual Studio .NET, o compilador da linguagem escolhida encarregado verificao da sintaxe e da escrita correta, analisando o seu cdigo fonte e certificando do que aquilo que escreveu faz algum sentido. Entre os vrios compiladores disponveis, temos os mais comuns: vbc.exe (Visual Basic .NET) e csc.exe (Visual C#). Para entendermos melhor o processo de compilao e criao do Assembly, vamos utilizar neste momento um utilitrio de linha de comando para isso, deixando temporariamente, o Visual Studio .NET de lado. Sendo assim, voc pode criar um arquivo de cdigo (Visual Basic .NET ou Visual C#) no Notepad. Como no teremos o auxlio do Visual Studio .NET neste momento, deveremos utilizar o compilador correspondente da linguagem para efetuarmos a compilao. Independente de qual linguagem e qual compilador est utilizando, o resultado da compilao de um nico arquivo ser sempre o mesmo: um mdulo gerenciado. Um mdulo gerenciado um executvel portvel (PE), que necessita obrigatoriamente do CLR para poder funcionar. A figura abaixo ilusta este processo: Israel Aece | http://www.projetando.net

Captulo 3 Utilizao de Assemblies

Imagem 3.1 Criao de um mdulo gerenciado. Dentro da plataforma Microsoft .NET, um Assembly um cdigo parcialmente compilado, tratando-se de um agrupamento lgico de um ou mais mdulos gerenciados ou arquivos de recursos. Alm disso, um Assembly a menor unidade de reutilizao, segurana e controle de verso. Dizemos parcialmente compilado, porque o cdigo est em uma linguagem intermediria, chamada de MSIL: Microsoft Intermediate Language. Existem dois tipos de Assembly: EXE (Executable) e DLL (Dynamic Link Library). O primeiro deles gerado quando uma aplicao do tipo Windows Forms, Windows Service ou Console so criadas; j o segundo, a DLL, trata-se de uma biblioteca de tipos que podem ser utilizados por vrias outras aplicaes. Dentro de cada um destes tipos de Assemblies, ainda tipos um sub-tipo que so: single-file assemblies e multi-file assemblies. O primeiro deles, single-file assemblies, so Assemblies que contm apenas um mdulo gerenciado dentro dele, qual far todo o trabalho necessrio para a aplicao ou biblioteca funcionar. J o segundo, multi-file assemblies, so compostos por mais de um mdulo gerenciado, quais so colocados dentro deste Assembly. Vale lembrar que ainda podemos adicionar arquivos de recursos, como por exemplo: *.jpg, *.ico, *.txt, etc.. Veremos mais detalhadamente sobre arquivos de recurso ainda neste captulo. Dentro dos Assemblies tambm temos o manifesto. O manifesto contm todas as informaes sobre os itens que esto contidos dentro do Assembly, incluindo tudo o que ele expe ao mundo. Ele tambm informa todas as dependncias existentes no seu Assembly para com outros Assemblies. Todo Assembly requer um manifesto. Criao de Assemblies Para efeitos de exemplo, vamos criar dois tipos de Assemblies: single-file assemblies e multi-file assemblies. A comear pelo single-file, devemos criar um arquivo distinto para cada linguagem, um para Visual Basic .NET e outro para Visual C#. O exemplo abaixo Israel Aece | http://www.projetando.net 2

Captulo 3 Utilizao de Assemblies

exibe a criao destes cdigos utilizando Notepad. Cada um deles so salva em um diretrio temporrio chamado Temp dentro da unidade C com suas respectivas extenses.
VB.NET Imports System Public Class BoasVindas Public Shared Sub Main() Console.WriteLine("Seja bem-vindo pelo VB.NET.") Console.ReadLine() End Sub End Class C# using System; public class BoasVindas { public static void Main() { Console.WriteLine("Seja bem-vindo pelo Visual C#."); Console.ReadLine(); } }

Como pode reparar, trata-se de um arquivo simples. Para recapitular, cada um das linguagens tem um compilador prprio. No caso do Visual C#, o compilador csc.exe e do Visual Basic .NET o vbc.exe. Ambas esto contidos dentro do seguinte diretrio: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727. Como esses utilitrios so executados a partir de linha de comando e voc optar por abrir o Visual Studio 2005 Command Prompt, no ser necessrio digitar o caminho todo at o executvel para execut-lo. Abaixo est a chamada para o compilador, passando como parmetro o arquivo a ser compilado e, em seguida, o output de cada compilador:
VB.NET C:\Temp>vbc BoasVindasVB.vb Microsoft (R) Visual Basic Compiler version 8.0.50727.42 for Microsoft (R) .NET Framework version 2.0.50727.42 Copyright (c) Microsoft Corporation. All rights reserved. C# C:\Temp>csc BoasVindasCS.cs

Israel Aece | http://www.projetando.net

Captulo 3 Utilizao de Assemblies

Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.42 for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727 Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.

Com isso, finalizamos o processo de um single-file assembly. Criamos ento dois executveis: BoasVindasVB.exe e BoasVindasCS.exe. Agora, a idia termos mais de um arquivo (mdulo gerenciado) e unir todos estes em um multi-file assembly. Para isso, devemos ento criar dois arquivos de cdigo. O primeiro trata-se de uma classe que ser consumida por um outro cdigo, em uma outra classe, em um arquivo distinto. O cdigo abaixo mostra o arquivo Aluno.cs e Aluno.vb. J os cdigos que esto logo na seqencia, utilizam as classes Aluno.cs e Aluno.vb:
VB.NET Imports System Public Class Aluno Public Sub Show(ByVal nome As String) Console.WriteLine("Seja Bem-vindo: " & nome) End Sub End Class C# using System; public class Aluno { public void Show(string nome) { Console.WriteLine("Seja Bem-vindo: " + nome); } }

VB.NET Imports System Public Class Escola Public Shared Sub Main() Dim aluno As New Aluno() aluno.Show("Jos Castro") End Sub

Israel Aece | http://www.projetando.net

Captulo 3 Utilizao de Assemblies


End Class C# using System; public class Escola { public static void Main() { Aluno aluno = new Aluno(); aluno.Show("Jos Castro"); } }

Mais uma vez, utilizaremos os compiladores de cada linguagem para gerar o executvel. S que agora temos uma ligeira mudana em relao ao single-file assembly. Para cada um dos arquivos de cdigo que temos, necessitamos criar um mdulo gerenciado e, para isso, utilizamos a mesma tcnica que o single-file assembly. Agora, quando referenciamos esta classe em um outro mdulo gerenciado, necessrio primeiro adicionarmos o mdulo pr-criado ao Assembly que ser gerado. Os compiladores fornecem um parmetro chamado addmodule que permitem adicionarmos a referncia para o(s) mdulo(s) que ser(o) utilizado(s). A sintaxe para isso exibida atravs do output que o compilador gerou ao efetuar o processo:
VB.NET C:\Temp>vbc /t:module Aluno.vb Microsoft (R) Visual Basic Compiler version 8.0.50727.42 for Microsoft (R) .NET Framework version 2.0.50727.42 Copyright (c) Microsoft Corporation. All rights reserved. C:\Temp>vbc /addmodule:Aluno.netmodule /out:Escola.exe Escola.vb Microsoft (R) Visual Basic Compiler version 8.0.50727.42 for Microsoft (R) .NET Framework version 2.0.50727.42 Copyright (c) Microsoft Corporation. All rights reserved. C# C:\Temp>csc /t:module Aluno.cs Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.42 for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727 Copyright (C) Microsoft Corporation 2001-2005. All rights reserved. C:\Temp>csc /addmodule:Aluno.netmodule /out:Escola.exe Escola.cs Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.42 for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727 Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.

Israel Aece | http://www.projetando.net

Captulo 3 Utilizao de Assemblies

Claro que todo este processo est manual justamente por questes de entendimento. Quando voc utiliza uma IDE, como por exemplo o Visual Studio .NET, tudo isso feito de forma transparente, gerando apenas o EXE ou a DLL. O arquivo AssemblyInfo Este arquivo criado por padro nas maiorias das aplicaes e atravs dele que podemos colocar vrias informaes a nvel de Assembly. Atravs de vrios atributos fornecidos pelo .NET Framework, podemos definir vrias informaes como por exemplo: nome do produto, verso, empresa, descrio, cultura, segurana, etc.. Quando voc cria uma aplicao utilizando o Visual Studio .NET, esse arquivo j gerado automaticamente e voc pode abr-lo e configurar de acordo com a sua necessidade. O cdigo abaixo mostra um arquivo AssemblyInfo padro:
VB.NET Imports System Imports System.Reflection Imports System.Runtime.InteropServices <Assembly: <Assembly: <Assembly: <Assembly: <Assembly: <Assembly: <Assembly: <Assembly: <Assembly: <Assembly: AssemblyTitle("VB")> AssemblyDescription("Exemplos em VB.NET.")> AssemblyCompany("People Computao")> AssemblyProduct("VB")> AssemblyCopyright("Copyright 2007")> AssemblyTrademark("")> ComVisible(False)> Guid("4db1f6e9-653c-479f-ab0a-0c713b7d7822")> AssemblyVersion("1.0.0.0")> AssemblyFileVersion("1.0.0.0")>

C# using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; [assembly: [assembly: [assembly: [assembly: [assembly: [assembly: [assembly: [assembly: [assembly: AssemblyTitle("CS")] AssemblyDescription("Exemplos em C#.")] AssemblyCompany("People Computao")] AssemblyProduct("CS")] AssemblyCopyright("Copyright 2007")] AssemblyTrademark("")] AssemblyCulture("")] ComVisible(false)] Guid("653551f4-88ef-4c39-bcd9-0a00e39d4e42")]

Israel Aece | http://www.projetando.net

Captulo 3 Utilizao de Assemblies


[assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")]

Strong Names Imaginem que temos um Assembly chamado CalculosDiversos.dll criado pela empresa ABC Dev Company. Esse Assembly contm uma poro de tipos que so utilizados para os mais diversos clculos. Esse Assembly ser, muito provavelmente, por vrias aplicaes. Agora, temos um diretrio conhecido que serve como repositrio para os Assemblies que so utilizados pelas mais diversas aplicaes e, um deles, o CalculosDiversos.dll. Bem, neste momento, temos uma outra empresa que decide criar um mesmo Assembly, com o mesmo nome de arquivo e, se colocado dentro do diretrio que o nosso repositrio de Assemblies, as aplicaes que fazem o uso deste Assembly deixar de funcionar, pois a DLL foi sobrescrita e, como a ltima vence, estamos novamente com o inferno das DLLs em plena era .NET. Como possvel notar, diferenciar um Assembly apenas pelo nome de arquivo no necessrio para garantir a unicidade. Felizmente o .NET fornece uma forma de identificarmos o Assembly como sendo nico e, para isso, utilizamos strong names. As informaes que compem um Assembly que assinado por uma strong name consiste em quatro atributos, que o identificaro unicamente: o nome do arquivo (sem extenso), o nmero de verso, a cultura e um token de chave pblica. Como o nome, cultura e a verso do Assembly podem repetir de uma empresa para outra, a Microsoft decidiu utilizar tecnologias de criptografias baseadas em chave pblica/privada para garantir a unicidade do Assembly. Sendo assim, um Assembly assinado com uma strong name possui o nome do arquivo, a cultura, a verso e uma chave privada do publicador do mesmo. A Microsoft disponibilizou um utilitrio de linha de comando que permite-nos gerar uma strong name para assinarmos os Assemblys que desejarmos. Esse utilitrio chama-se sn.exe (sn = strong name) e, assim como os compiladores, encontra-se no seguinte local do disco: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727. Basicamente, para gerar um par de chave pblica/privada, basta simplesmente fazer:
C:\>sn -k C:\Temp\StrongName.snk

O parmetro k indica ao utilitrio para gerar a chave. Lembrando que, se quiser, poder abrir o Visual Studio 2005 Command Prompt para poupar a digitao do caminho completo at o utilitrio. O arquivo gerado atravs do comando acima conter as chaves pblica e privada. Israel Aece | http://www.projetando.net

Captulo 3 Utilizao de Assemblies

Assinando o Assembly Agora que j sabe como criar uma strong name, voc precisa refernci-la no seu projeto. Para isso, voc dever utilizar o atributo chamado AssemblyKeyFileAttribute que fornecido pelo .NET Framework e configur-lo dentro do arquivo AssemblyInfo. O cdigo abaixo exemplifica o uso deste atributo:
VB.NET <Assembly: AssemblyKeyFile("C:\Temp\StrongName.snk")> C# [assembly: AssemblyKeyFile(@"C:\Temp\StrongName.snk")]

Quando o compilador encontra esse atributo, ele assinar o Assembly com a chave privada e ir incorporar a chave pblica. Isso, alm de garantir a unicidade do Assembly, garantir tambm a chacagem de integridade, ou seja, passando pelas checagens de segurana do .NET Framework, ir garantir que o contedo do Assembly no foi alterado desde a sua ltima compilao. Formas de distribuio de Assemblies Assemblies privados Quando referenciamos um determinado Assembly no projeto que estamos desenvolvendo, esse componente geralmente ser copiado para a pasta da aplicao. Outra possibilidade quando fazemos uma referncia direta para o mesmo, e o manipulamos via Reflection. Esses tipos de Assemblies so considerados Assemblies privados, pois so utilizados por uma ou mais aplicaes especficas, mas sempre tendo uma cpia local para mesmo. Esse tipo de Assemblies apesar de funcionar bem em alguns cenrios, dificulta o processo de deployment quando se trata de um cenrio mais complexo, onde a quantidade de aplicaes/usurios que utilizam esses Assemblies muito grande. A alternativa a este mtodo a depositar esse Assembly no Global Assembly Cache, tema da prxima seo. Global Assembly Cache GAC Como vimos acima, uma das maiores dificuldades quando trabalhamos com Assemblies privados a questo do deployment. Mesmo um Assembly assinado com uma strong name permitiria, por exemplo, a execuo lado-a-lado. neste momento que entra em ao o GAC Global Assembly Cache. Se um Assembly poder ser carregado por mltiplas aplicaes, esse Assembly dever ser colocado em um local de conhecimento de todos. Esse local conhecido trata-se do GAC, Israel Aece | http://www.projetando.net

Captulo 3 Utilizao de Assemblies

que um centralizador dos Assemblies disponveis para consumo. Quando um Assembly adicionado ali, ele ter uma sria de benefcios, tais como: Facilidade de deploy, j que est tudo centralizado Melhoria na performance Possibilita a execuo lado-a-lado de um mesmo Assembly com verses diferentes, j que um diretrio fsico no resolve o problema, pois podemos ter dois componentes com o mesmo nome de arquivo.

H trs formas de inserirmos um determinado Assembly no GAC: Windows Explorer: Se navegar via Windows Explorer at o diretrio C:\WINDOWS\ASSEMBLY voc ver todos os Assemblies que esto no GAC daquela maquina especfica. Voc pode, via drag-and-drop, adicionar Assemblies ali, mas isso somente interessante em ambiente de desenvolvimento. Utilitrio Gacutil.exe: Este utilitrio, fornecido tambm pelo SDK do .NET Framework, permite via linha de comando, interagir com o GAC, adicionando, removendo, listando, etc., Assemblies. Este utilitrio utlizando em ambientes de testes e desenvolvimento, nunca em produo. Installers: Permite adicionarmos a instalao dos componentes dentro do GAC via Windows Installer. Isso perfeitamente til quando desejamos empacotar o sistema em um projeto de setup para que o usurio final, muitas vezes sem muito conhecimento, possa instalar o sistema sem maiores dificuldades. Este a forma que se utiliza para a instalao de um componente em uma mquina cliente, j que o .NET Framework redistribuvel no possui o utilitrio GacUtil.exe.

A imagem abaixo mostra o Global Assembly Cache GAC, j com uma poro de Assemblies adicionadas pela instalao do .NET Framework. Cada linha contm o nome do Assembly, a verso, a cultura e a chave pblica:

Israel Aece | http://www.projetando.net

Captulo 3 Utilizao de Assemblies


Imagem 3.2 Global Assembly Cache GAC. Instalando um Assembly no GAC

10

Para instalar um Assembly no GAC, somente permitido se o usurio que estiver fazendo isso tiver privilgios administrativos e, o mais importante, o Assembly dever ter uma strong name definida. O trecho de cdigo abaixo exibe o resultado da adio de um componente dentro do GAC e, a imagem a seguir, exibe o GAC j com o componente devidamente adicionado:
C:\>gacutil -i C:\Temp\ComponenteComum.dll Microsoft (R) .NET Global Assembly Cache Utility. Version 2.0.50727.42 Copyright (c) Microsoft Corporation. All rights reserved. Assembly successfully added to the cache

Imagem 3.3 Componete j adicionado ao GAC. Nota: Somente adicione no GAC os Assemblies que realmente sero compartilhados entre vrias aplicaes, caso contrrio, o Assembly dever ser implantando privadamente, ou seja, junto ao diretrio da aplicao. Embutindo arquivos dentro de um Assembly Como vimos acima, um contm mdulos gerenciados, metadados, manifesto, cdigo IL e os recursos. Recursos ou arquivos de recursos so informaes que tambm so embutidas dentro de um determinado Assembly que so informaes necessrias para o funcionamento do Assembly. Geralmente essas informaes so imagens, arquivos xml, arquivos texto, mensagens, etc.. Se analisarmos o prprio .NET Framework, ele possui uma poro de recursos e, como exemplo, podemos citar o Assembly System.Windows.Forms.dll que embuti vrias imagens que so utilizadas pelos controles que l esto contidos quando so colocados na barras de ferramentas do Visual Studio .NET. Um outro exemplo o caso do Assembly System.Web.dll que, na seo de recursos, alm de imagens, possui tambm arquivos javascript que so utilizados pelos controles do ASP.NET. Israel Aece | http://www.projetando.net

10

Captulo 3 Utilizao de Assemblies

11

Quando estamos em um mundo mais prximo da nossa realidade, cones ou imagens que nossa aplicao depende para ser exibido nos formulrios da aplicao, poderiam ser tambm embutidos dentro do Assembly. Sendo assim, podemos ento adicionar uma imagem qualquer dentro do projeto e, clicando com o boto direito do mouse e indo at a opo Propriedades, a janela de Propriedades exibida. Encontre a propriedade Build Action e a defina como Embedded Resource. Isso far com que o arquivo (seja texto, imagem, cone, etc.) seja embutido dentro do Assembly. Supondo que a imagem adicionada foi Background.jpg, a chave para acess-la ser: NomeProjeto.Background.jpg. O trecho de cdigo abaixo exibe a forma de recuperar a imagem via cdigo e defin-la como sendo o Background de um formulrio Windows Forms.
VB.NET Imports System.IO Imports System.Reflection Dim asb As [Assembly] = Assembly.GetExecutingAssembly() Me.BackgroundImage = _ New Bitmap(asb.GetManifestResourceStream("VB.Background.jpg")) C# using System.IO; using System.Reflection; Assembly asb = Assembly.GetExecutingAssembly(); this.BackgroundImage = new Bitmap(asb.GetManifestResourceStream("CS.Background.jpg"));

A possibilidade de incluir arquivos dentro de um Assembly facilita a distribuio, j que voc no precisa se preocupar em manter os caminhos dos arquivos, j que eles esto embutidos e a forma de acess-los diferente. Alm disso, temos uma maior segurana, pois depois de compilado, no mais possvel alter-lo a menos que, abra o projeto e edite o arquivo e, finalmente, gere um novo Assembly contendo as novas informaes. Instalao de Assemblies Instaladores padres Desde as primeiras verses do .NET Framework temos uma nova categoria de tipos de projetos que esto embutidos dentro das templates do Visual Studio .NET. Essa categoria trata-se dos projetos de Setup. Israel Aece | http://www.projetando.net 11

Captulo 3 Utilizao de Assemblies

12

Apesar de ser possvel utilizarmos a tcnica de XCOPY para a instalao de projetos no cliente. O XCOPY trata-se de simplesmente copiar o(s) arquivo(s) do projeto em um dispositivo qualquer, como por exemplo, CD, Pen-drive, etc., e levar at o cliente e l copiar todos os arquivos para o disco da mquina onde o sistema ir rodar. A desinstalao to simples quanto a instalao, bastante apenas localizar a pasta onde o projeto est instalado e exclu-la do disco. Apesar de simples, isso nem sempre o ideal por muitas razes: 1. No existe segurana. 2. Exige que a pessoa que ir instalar, tenha um conhecimento razovel. 3. No conseguimos automatizar as configuraes na mquina durante a instalao. Visando todos esses problemas, a Microsoft decidiu criar os projetos de Setup para auxiliar essa tarefa bastante comum. Os projetos so bastante interessantes e permitem uma construo muito rpido, sem a necessidade de adquirir componentes de terceiros. Um dos grandes benefcios a possibilidade de gerar uma instalao transacionada, ou seja, se durante a instalao algo falhar, seguramente ele ir desfazer todas as mudanas que o instalador fez. Atualmente temos os seguintes projetos disponveis: Tipo de Projeto Setup Project Web Setup Project Merge Module Project CAB Project Descrio Utilizado para a criao de projetos de instalao para aplicaes que rodam em ambiente Windows (Windows Forms). Utilizado para criao de projetos de instalao para aplicaes ASP.NET. Utilizado para criar um instalador para componentes compartilhados. Utilizado para gerar e comprimir arquivos CAB para disponibiliz-los para download.

Customizando da instalao e desinstalao Os projetos de Setup atendem perfeitamente para a instalao bsica de um software: cpia de arquivos, criao de pastas, criao de items no menu Iniciar do Windows e atalho na rea de trabalho do usurio. Mas h cenrios onde voc quer customizar a instalao de um projeto. Geralmente esse projeto requer diversas configuraes que voc precisar informar ou que ele mesmo possa criar durante o processo de instalao. Essas configuraes so, por exemplo, criao de base de dados e seus objetos dentro do SQL Server, criao de arquivos no disco, criao de Message Queue, entre outras vrias possibilidades. Para resolver esse problema, podemos utilizar o que chamamos de classes de instalao ou Installer Classes.

12

Israel Aece | http://www.projetando.net

Captulo 3 Utilizao de Assemblies

13

Fornecidas pelo namespace System.Configuration.Install (para ter acesso as classes, necessrio fazer a referncia System.Configuration.Install.dll), essas classes disponibilizam uma gama de funcionalidades que podemos customizar a instalao do nosso projeto. Entre as classes disponveis, temos a classe Installer, AssemblyInstaller, ComponentInstaller, InstallContext e a classe TransactedInstaller. Como a idia aqui customizar toda a instalao de um determinado software, essas classes vo nos auxiliar durante toda a criao desta forma customizada de instalar uma aplicao. Installer A classe Installer responsvel por fornecer todas as funcionalidades necessrias para a customizao da instalao, sendo a classe base para todos os outros Installers dentro do .NET Framework. O passos a seguir definem o processo que deve ser realizado para que o instalador seja construdo e executado corretamente: 1. Criar um instalador, herdando da classe Installer. 2. Sobrescreva os mtodos Install, Commit, Rollback e Uninstall. Esses mtodos no em visibilidade pblica, pois so marcados como protected. Sero invocados automaticamente a partir da instalao do software. 3. Adicione o atributo RunInstallerAttribute a classe instaladora que est criado. 4. Invoque o instalador. Pode ser atravs do utilitrio de linha de comando installutil.exe ou atravs da classe AssemblyInstaller, qual veremos mais detalhadamente logo abaixo. Esta classe possui uma propriedade denominada Installers, que recebe como parmetro uma coleo de Installers que, por sua vez, cada elemento um tipo Installer. Esses Installers faro parte de uma mesma instalao e nos permite ter uma hierarquia de instaladores. Cada um dos mtodos Install, Commit, Rollback e Uninstall recebem como parmetro uma coleo do tipo chave-valor (IDictionary) para que voc possa receber informaes entre os mtodos da instalao corrente e, quando o mesmo finalizado, eles valores so persistidos para mais tarde, quando a desinstalao acontecer, voc consiga desfazer o que tinha feito. Para exemplificar, imagine que durante a instalao, eu preciso criar um arquivo no disco com um nome que gerado dinamicamente. Esse nome gerado e ento, necessitamos guard-lo para que no momento da desinstalao da aplicao (ou no Rollback da instalao), voc consiga excluir o arquivo gerada pelo instalador. O atributo RunInstallerAttribute especifica que quando o instalador customizado for executado para instalar um determinado Assembly, ele dever invocar todas as classes que esto decoradas com este atributo e que em seu construtor, o valor definido esteja como True. Para transformarmos isso em cdigo, abaixo mostrado um exemplo onde temos uma aplicao console que o sistema que desenvolvemos e que o cliente precisar utilizar e, Israel Aece | http://www.projetando.net

13

Captulo 3 Utilizao de Assemblies

14

em seguida, a classe instaladora do nosso sistema. Ainda na aplicao que o usurio ter acesso, alm de termos os cdigos normais, teremos que ter tambm o instalador que a classe que herda da classe Installer e aplica o atributo RunInstallerAttribute.
VB.NET Console.WriteLine("Esta aplicao que o cliente solicitou...") Console.ReadLine() C# Console.WriteLine("Esta aplicao que o cliente solicitou..."); Console.ReadLine(); Instalador (dentro da mesma aplicao) VB.NET Imports Imports Imports Imports Imports System System.IO System.Collections System.ComponentModel System.Configuration.Install

Namespace Aplicacao <RunInstaller(true)> _ Public Class Instalador Inherits Installer Public Overrides Sub Install(ByVal stateSaver As IDictionary) Dim file As String = "c:\ViveraDuranteAplicacao.txt" Using sw As New StreamWriter(file) sw.Write("Conteudo!") End Using stateSaver("Arquivo") = file MyBase.Install(stateSaver) End Sub Public Overrides Sub Uninstall(ByVal savedState As IDictionary) Dim file As String = savedState("Arquivo").ToString() If File.Exists(file) Then File.Delete(file) End If MyBase.Uninstall(savedState) End Sub End Class End Namespace C#

14

Israel Aece | http://www.projetando.net

Captulo 3 Utilizao de Assemblies


using using using using using System; System.IO; System.Collections; System.ComponentModel; System.Configuration.Install;

15

namespace Aplicacao { [RunInstaller(true)] public class Instalador : Installer { public override void Install(IDictionary stateSaver) { string file = @"c:\ViveraDuranteAplicacao.txt"; using (StreamWriter sw = new StreamWriter(file)) { sw.Write("Conteudo!"); } stateSaver["Arquivo"] = file; base.Install(stateSaver);

public override void Uninstall(IDictionary savedState) { string file = savedState["Arquivo"].ToString(); if (File.Exists(file)) File.Delete(file); } base.Uninstall(savedState);

Como podemos reparar, somente optamos por sobrescrever os mtodos Install e Uninstall. Como j era de se esperar, o primeiro deles ocorre apenas quando a aplicao for instalada. Como estamos colocando o nome do arquivo gerado dentro da coleo que vem como parmetro (stateSaver), ele valor ser mantido e mais tarde, quando o mtodo Uninstall acontecer, ele conseguir recuperar o nome do arquivo para que ele possa exclu-lo. A classe Installer ainda contm uma propriedade bastante til chamada Context do tipo InstallContext que, como o prprio nome diz, traz informaes a respeito do contexto de instalao do Assembly, como por exemplo, o local do arquivo de log da instalao, o local do arquivo para salvar informaes requisitadas pelo mtodo Uninstall e, os argumentos que so passados atravs do utilitrio installutil.exe, quais so mapeados Israel Aece | http://www.projetando.net 15

Captulo 3 Utilizao de Assemblies

16

para entradas dentro da propriedade Parameters, do tipo StringDictionary. Alm disso, a classe InstallContext ainda fornece um mtodo chamado LogMessage que permite escrevermos algo na console e no arquivo de log da instalao. Eventos A classe Installer tambm fornece uma poro de eventos interessantes para comunicarse com o cliente e notific-lo de acordo com o progresso da instalao. A tabela abaixo descreve cada um destes eventos disponveis: Evento AfterInstall AfterRollback AfterUninstall BeforeInstall BeforeRollback BeforeUninstall Committed Committing AssemblyInstaller Para fins de exemplo, tambm criaremos uma aplicao console para que sirva como instalador do sistema acima criado. Para conseguirmos instalar a aplicao dinamicamente, ou seja, via cdigo, ser necessrio utilizarmos uma nova classe, tambm exposta pelo namespace System.Configuration.Install. Neste momento entra em cena a classe AssemblyInstaller. Tambm derivada da classe Installer, esta classe responsvel por instalar um Assembly. Dado o caminho fsico completo at o Assembly, essa classe carrega-o e extrair de dentro dele todos os instaladores l contidos e os executam. Para que isso seja possvel, devemos seguir rigorosamente os passos que vimos acima para a criao do Installer e mantendo todos os instaladores com o modificador de acesso definido como public, caso contrrio, no possvel ser acessado externamente. O trecho de cdigo abaixo cria a instncia da classe AssemblyInstaller apontando para o Assembly que deseja instalar. Descrio Ocorre depois que todos os mtodos Install de todos os instaladores contidos na propriedade Installers foram executados. Ocorre depois que todos os mtodos Rollback de todos os instaladores contidos na propriedade Installers foram executados. Ocorre depois que todos os mtodos Uninstall de todos os instaladores contidos na propriedade Installers foram executados. Ocorre antes que o mtodo Install de cada instalador contido na propriedade Installers seja executado. Ocorre antes que o mtodo Rollback de cada instalador contido na propriedade Installers seja executado. Ocorre antes que o mtodo Uninstall de cada instalador contido na propriedade Installers seja executado. Ocorre depois de que todos os instaladores contidos na propriedade Installers concretizaram o trabalho com xito. Ocorre antes de todos os instaladores contidos na propriedade Installers concretizaram o trabalho.

16

Israel Aece | http://www.projetando.net

Captulo 3 Utilizao de Assemblies

17

Quando invocamos os mtodos Install, Commit, Rollback e Uninstall da classe AssemblyInstaller, internamente ela invoca os respetivos mtodos dos instaladores do Assembly informado. O trecho de cdigo invoca o mtodo Install e Commit:
VB.NET Imports System Imports System.Collections Imports System.Configuration.Install Dim savedState As IDictionary = New Hashtable() Dim cmdLine() As String = New String() {"/LogFile=Log.txt"} Using installer As New ("c:\Temp\Aplicacao.exe", cmdLine) installer.Install(savedState) installer.Commit(savedState) End Using Console.WriteLine("Aplicao Instalada.") Console.ReadLine() C# using System; using System.Collections; using System.Configuration.Install; IDictionary savedState = new Hashtable(); string[] cmdLine = new string[] { "/LogFile=Log.txt" }; using (AssemblyInstaller installer = AssemblyInstaller("c:\\Temp\\Aplicacao.exe", cmdLine)) { installer.Install(savedState); installer.Commit(savedState); } Console.WriteLine("Aplicao Instalada."); Console.ReadLine(); new AssemblyInstaller

Mas e se desejarmos desinstalar a aplicao? Bem, to simples quanto a instalao. Cria-se o objeto AssemblyInstaller apontando para o Assembly, passando os argumentos se desejar e agora basta chamar o mtodo Uninstall ou invs de Install e Commit. ComponentInstaller Mais um derivado da classe Installer, este componente permite a possibilidade de interargimos com o instalador, passando para ele uma instncia de um objeto para que ele possa utilizar para a instalao correta da aplicao. Israel Aece | http://www.projetando.net 17

Captulo 3 Utilizao de Assemblies

18

Assim como a classe Installer, voc tambm deve criar um instalador, mas agora, utilizando como base o ComponentInstaller. Esta classe basicamente fornece um mtodo abstrato chamado de CopyFromComponent que recebe em seu parmetro uma instncia de um objeto que implemente a Interface IComponent. Um exemplo tpico quando voc precisa criar objetos dentro do SQL Server (Stored Procedures, Tabelas, etc.). notvel que para isso precisamos de uma conexo com o servidor de banco de dados. Como pode haver a possibilidade desta conexo j estar disponvel, poderia simplesmente mandar essa conexo para que o instalador possa utilizar para a criao dos objetos que mencionamos acima. TransactedInstaller Finalmente temos o instalador TransactedInstaller, qual tambm derivado da classe Installer. Esse instalador tem um funcionalida extremamente til, envolve a instalao da aplicao em um ambiente transacionado. Isso quer dizer que, se alguma exceo ocorrer durante o processo de instalao, o TransactedInstaller deixar o computador em qual est sendo instalado em um estado consistente. Basicamente, ele garantir que o mtodo Rollback ser executado e, sendo assim, no esquea de sobrescrever o mtodo em seu instalador para poder contemplar uma possvel exceo que possa vir a acontecer. O cdigo abaixo uma verso transacionada do qual vimos um pouco mais acima, quando ainda falvamos da classe AssemblyInstaller:
VB.NET Imports System Imports System.Collections Imports System.Configuration.Install Dim savedState As IDictionary = New Hashtable() Dim cmdLine() As String = New String() {"/LogFile=Log.txt"} Using installer As New ("c:\Temp\Aplicacao.exe", cmdLine) Using tran As New TransactedInstaller() tran.Context = installer.Context tran.Installers.Add(installer) tran.Install(savedState) tran.Commit(savedState) End Using End Using Console.WriteLine("Aplicao Instalada.") Console.ReadLine C# using System; AssemblyInstaller

18

Israel Aece | http://www.projetando.net

Captulo 3 Utilizao de Assemblies


using System.Collections; using System.Configuration.Install; IDictionary savedState = new Hashtable(); string[] cmdLine = new string[] { "/LogFile=Log.txt" };

19

using (AssemblyInstaller installer = new AssemblyInstaller(@"c:\\Temp\\Aplicacao.exe", cmdLine)) { using (TransactedInstaller tran = new TransactedInstaller()) { tran.Context = installer.Context; tran.Installers.Add(installer); tran.Install(savedState); tran.Commit(savedState);

Console.WriteLine("Aplicao Instalada."); Console.ReadLine();

Arquivos de configurao O que so arquivos de configurao Os arquivos de configurao so arquivos que colocamos no interior da aplicao que, permitem de uma forma bem flexvel, customizar parmetros para que a mesma possa trabalhar corretamente. A flexibilidade verdadeira porque alguns parmetros variam de um ambiente para outro, como o caso, por exemplo, de uma conexo com o banco de dados. Quando a aplicao est sendo desenvolvido, muito provavelmente, a conexo tem que apontar para o servidor de banco de dados de desenvolvimento. Agora, quando mandamos essa aplicao para o cliente, as configuraes de servidor de base de dados so completamente diferentes. Como no possvel compilar a aplicao no cliente, seria interessante se, de alguma forma, consegussemos alterar essa configurao sem a necessidade de recompilar a aplicao. Temos dois tipos de arquivo de configurao: App.Config e Web.Config. O primeiro deles utilizado em aplicaes executveis, como por exemplo, aplicaes Windows, Windows Services, etc; j o segundo, utilizado por aplicaes Web/ASP.NET, tambm com a finalidade de configurao de parmetros que a aplicao necessita para trabalhar. Alm as configuraes de conexes com a base de dados, ainda temos uma seo interessante, que a AppSettings. Essa seo permite colocarmos qualquer tipo de Israel Aece | http://www.projetando.net

19

Captulo 3 Utilizao de Assemblies

20

informao que necessitamos parametrizar na aplicao. Imagine que a sua aplicao manipule alguma fila de mensagens de Message Queue. Cada cliente tem a fila disponvel para a sua aplicao utilizar mas, cada uma tem um nome diferente. Ento para isso, podemos parametrizar o nome da fila no arquivo de configurao e, em cada cliente, voc altera o nome, sem a necessidade de qualquer trabalho adicional. Para exemplificar a parametrizao da conexo com o banco de dados e as informaes de parmetros via arquivo de configurao, o cdigo d uma idia de como proceder:
<?xml version="1.0"?> <configuration> <appSettings> <add key="MessageQueueName" value="FileDeMensagens" /> </appSettings> <connectionStrings> <add name="SqlConnectionString" connectionString="Data Catalog=MeuBanco;Integrated Security=SSPI;" providerName="System.Data.SqlClient" /> </connectionStrings> </configuration>

Source=.;Initial

Como podemos visualizar, atravs do elemento add podemos adicionar quantos parmetros e conexes forem necessria para a aplicao poder trabalhar. O que precisa se atentar com relao aos elementos key (em AppSetings) e em name (em connectionStrings), pois elas no podem repetirem dentro do mesmo arquivo. Agora, dentro da aplicao voc pode acessar as informaes que criamos no arquivo de configurao acima. Para isso, basta utilizar a classe ConfigurationManager que est contida dentro do namespace System.Configuration. Entre vrias funcionalidades, essa classe expe duas propriedades chamadas AppSettings e ConnectionStrings que retornam colees de cada uma das sees. O trecho de cdigo exibe a forma que utilizamos para poder recuper-las:
VB.NET Imports System.Configuration ... Console.WriteLine(ConfigurationManager.AppSettings("MessageQueueN ame")) Console.WriteLine(ConfigurationManager.ConnectionStrings("SqlConn ectionString")) C# using System.Configuration;

20

Israel Aece | http://www.projetando.net

Captulo 3 Utilizao de Assemblies

21

//... Console.WriteLine(ConfigurationManager.AppSettings["MessageQueueN ame"]); Console.WriteLine(ConfigurationManager.ConnectionStrings["SqlConn ectionString"]);

Criando uma seo de configurao customizada O que vimos at o momento satisfaz uma boa parte das necessidades que temos. Mas agora, gostaramos de criar uma seo de configurao prpria na aplicao que estamos desenvolvendo para ter um maior controle e uma tipagem mas eficiente. A idia aqui mostrar como criar uma seo de configurao especfica para a aplicao que estamos desenvolvendo. Para iniciar, mesmo tendo um namespace disponvel na aplicao quando a criamos, necessrio adicionar a referncia a uma DLL chamada System.Configuration.dll. Essa DLL disponibilizar para a aplicao vrias classes que iremos utilizar para a construo dessa seo de configurao customizada. ConfigurationElement ConfigurationElement uma classe abstrata que representa um determinado elemento dentro do arquivo de configurao. Como trata-se de uma classe abstrata, obrigatoriamente deve ser herdada em uma classe concreta que representar elementos de configurao XML, que tratam-se tambm dos parmetros que precisamos para que nossa aplicao trabalhe/manipule. A classe concreta que herda de ConfigurationElement basicamente conter as propriedades que desejam disponibilizar no arquivo de configuration para ser definido e, conseqentemente, utilizado pela aplicao. Essas propriedades so configuradas com alguns atributos que iro definir toda as caractersticas individuais de cada propriedade, como por exemplo, o tipo, nome, valor padro, etc. Para exemplificar a criao da seo customizada, vamos inicialmente analisar a implementao de ConfigurationElement logo abaixo:
VB.NET Imports System.Configuration Public Class UrlConfigElement Inherits ConfigurationElement DefaultValue:="Microsoft", <ConfigurationProperty("name", IsRequired:=True, IsKey:=True)> _ Public ReadOnly Property Name() As String

21

Israel Aece | http://www.projetando.net

Captulo 3 Utilizao de Assemblies


Get

22

Return MyBase.Item("name").ToString() End Get End Property <ConfigurationProperty("url", DefaultValue:="http://www.microsoft.com", IsRequired:=True)> _ <RegexStringValidator("\w+:\/\/[\w.]+\S*")> _ Public ReadOnly Property Url() As String Get Return MyBase.Item("url").ToString() End Get End Property DefaultValue:=0, <ConfigurationProperty("port", IsRequired:=False)> _ <IntegerValidator(MinValue:=0, MaxValue:=8080, ExcludeRange:=False)> _ Public ReadOnly Property Port() As Integer Get Return MyBase.Item("port").ToString() End Get End Property End Class C# using System.Configuration; public class UrlConfigElement : ConfigurationElement { [ConfigurationProperty("name", DefaultValue = "Microsoft", IsRequired = true, IsKey = true)] public string Name { get { return (string)this["name"]; } } [ConfigurationProperty("url", DefaultValue "http://www.microsoft.com", IsRequired = true)] [RegexStringValidator(@"\w+:\/\/[\w.]+\S*")] public string Url { get { return (string)this["url"]; } =

22

Israel Aece | http://www.projetando.net

Captulo 3 Utilizao de Assemblies


}

23

[ConfigurationProperty("port", DefaultValue = (int)0, IsRequired = false)] [IntegerValidator(MinValue = 0, MaxValue = 8080, ExcludeRange = false)] public int Port { get { return (int)this["port"]; } } }

Temos no cdigo acima uma classe chamada UrlConfigElement que contm trs propriedades: Name, Url e Port. Essa classe representa informaes a respeito de um website especfica com o seu nome e a porta. Cada uma dessas propriedades contm uma atributo chamado ConfigurationProperty que, como vimos acima, indica como ela ser representada pelo XML dentro do arquivo de configurao. Esse atributo permite-nos informaes algunas informaes importantes a respeito da propriedades, uma forma de vincular as propriedades do cdigo configuraes do arquivo de configurao. A tabela abaixo descreve as principais configuraes que podemos fazer para cada propriedade individualmente: Propriedade DefaultValue Description IsKey IsRequired Name Type Validators Os validadores disponibilizam uma forma interessante para validarmos as informaes que so colocadas no arquivo de configurao. Assim como o atributo ConfigurationProperty, os validadores tambm so definidos via atributos. Todos os Israel Aece | http://www.projetando.net 23 Descrio Define um valor padro para a propriedade que utilizada quando no informada pelo arquivo de configurao. Geralmente utilizada quando a propriedade IsRequired definida como False. Utilizado apenas para definir uma descrio. Ela no utilizada pela aplicao. Indica se a propriedade a chave para o objeto, qual tambm ser utilizada dentro de uma possvel coleo dentro do arquivo de configurao. Indica se a propriedade ou no obrigatria. Especifica o nome da propriedade que ser utilizado no arquivo de configurao. Define o tipo que a propriedade dever ter no arquivo de configurao.

Captulo 3 Utilizao de Assemblies

24

validadores para esta finalidade herdam diretamente de uma classe abstrata chamada ConfigurationValidatorBase que define dois mtodos que devem ser implementados: CanValidate e Validate. O primeiro deles, CanValidate define se um objeto pode ser validado baseando-se em um determinado tipo; o segundo e ltimo mtodo, Validate, determina a validao efetiva do objeto, indicando se ele est ou no de acordo com o que a aplicao espera para trabalhar. Existem vrios validadores disponveis dentro do .NET Framework, mais precisamente, dentro do namespace System.Configuration e, como j era de se esperar, podemos implementar o nosso prprio validador herdando de ConfigurationValidatorBase. Se analisarmos o trecho de cdigo acima, veremos o uso de dois validadores diferentes: RegexStringValidator e IntegerValidator. O primeiro deles permite validar uma determinada string de acordo com uma regular expressions; j o IntegerValidator faz a verificao para saber se o valor informado ou no do tipo Int32. Ambos atributos fornecem propriedades diferentes, que so necessrias de acordo com o tipo de validao. ConfigurationElementCollection Como o prprio nome diz, ConfigurationElementCollection, trata-se de uma coleo de elementos dentro do arquivo de configurao. A idia por trs desta objeto/coleo est em permitir a declarao no arquivo de configurao de mltiplos elementos de um mesmo tipo que, no nosso caso, o UrlConfigElement. A sua implementao no muito complexa. Trata-se tambm de uma classe abstrata que fornece dois principais mtodos que devem ser implementados na classe derivada: CreateNewElement e GetElementKey. O mtodo CreateNewElement permite criarmos elementos de um determinado tipo que deve sempre retornar uma instncia do objeto que estamos querendo armazenar dentro da nossa coleo. J o mtodo GetElementKey deve retornar a chave para o objeto, que sempre a propriedade que est marcada com o atributo IsKey do atributo ConfigurationProperty dentro do objeto que herda de ConfigurationElement. O cdigo abaixo demonstra como devemos proceder para utilizar a classe abstrata ConfigurationElementCollection:
VB.NET Imports System.Configuration ... Public Class UrlColl Inherits ConfigurationElementCollection Protected Overloads Overrides Function CreateNewElement() As ConfigurationElement Return New UrlConfigElement() End Function Protected Overrides Function GetElementKey(ByVal element As

24

Israel Aece | http://www.projetando.net

Captulo 3 Utilizao de Assemblies


ConfigurationElement) As Object Return DirectCast(element, UrlConfigElement).Name End Function End Class C# using System.Configuration; //... public class UrlsCollection : ConfigurationElementCollection { protected override ConfigurationElement CreateNewElement() { return new UrlConfigElement(); }

25

protected override Object GetElementKey(ConfigurationElement element) { return ((UrlConfigElement)element).Name; } }

ConfigurationSection Finalmente, temos a classe (tambm abstrata) ConfigurationSection. Ela representa uma seo dentro do arquivo de configurao, que permite-nos customizar uma seo para um determinado tipo que criamos dentro da aplicao. Voc dever herd-la para fornecer ao sistema uma forma programtica de fortemente tipada de acessar as configuraes que foram definidas no arquivo de configurao. Assim como aconteceu com a classe que herda de ConfigurationElement, a seo que vamos customizar a partir de ConfigurationSection precisa apenas das propriedades que a mesma ir expor para serem configuradas a partir do arquivo de configurao. Como a idia aqui tambm mostrar o uso da coleo que criamos um pouco mais acima ento, uma das propriedades, ir retornar uma coleo com todos os elementos j configurados. Atravs do cdigo abaixo podemos analisar a implementao da seo customizada, j com a propriedade que expe a coleo de elementos do tipo UrlConfigElement e tambm um nico elemento que ir expor apenas um objeto, tambm do tipo UrlConfigElement:
VB.NET Imports System.Configuration ... Public Class UrlsSection

25

Israel Aece | http://www.projetando.net

Captulo 3 Utilizao de Assemblies


Inherits ConfigurationSection

26

<ConfigurationProperty("name", DefaultValue:="Favoritos", IsRequired:=True, IsKey:=False)> _ <StringValidator(InvalidCharacters:=" ~!@#$%^&*()[]{}/;'\""|\\", MinLength:=1, MaxLength:=60)> _ Public ReadOnly Property Name() As String Get Return MyBase.Item("name").ToString() End Get End Property <ConfigurationProperty("simple")> _ Public ReadOnly Property Simple() As UrlConfigElement Get Return DirectCast(MyBase.Item("simple"), UrlConfigElement) End Get End Property <ConfigurationProperty("urls", IsDefaultCollection:=False)> _ Public ReadOnly Property Urls() As UrlColl Get Return DirectCast(MyBase.Item("urls"), UrlColl) End Get End Property End Class C# using System.Configuration; //... public class UrlsSection : ConfigurationSection { [ConfigurationProperty("name", DefaultValue = "Favoritos", IsRequired = true, IsKey = false)] [StringValidator(InvalidCharacters = " ~!@#$%^&*()[]{}/;'\"|\\", MinLength = 1, MaxLength = 60)] public string Name { get { return (string)this["name"]; } } [ConfigurationProperty("simple")] public UrlConfigElement Simple { get

26

Israel Aece | http://www.projetando.net

Captulo 3 Utilizao de Assemblies


{ } }

27

return (UrlConfigElement)base["simple"];

[ConfigurationProperty("urls", IsDefaultCollection = false)] public UrlsCollection Urls { get { return (UrlsCollection)base["urls"]; } }

Temos ento a propriedade Name que expe uma string, a propriedade Simple que retorna uma instncia (individual e isolada) do elemento UrlConfigElement e a propriedade UrlsCollections que, como o prprio nome diz, retorna uma coleo de objetos do tipo UrlConfigElement, conforme criamos um pouco mais acima. Registrando a seo customizada Depois de todas as classes criadas, chega o momento que precisamos configurar o arquivo de configurao para definir como estruturar o arquivo e tambm colocar o valor correspondente a cada uma das propriedades. Para registrarmos essa seo dentro do arquivo de configurao da aplicao necessrio utilizarmos o elemento configSections. Atravs dele, especificamos o nome da seo que ser definido para estruturarmos o XML. Alm disso, necessrio informar o tipo que ser utilizado (que na verdade a classe UrlsSection que criamos acima) e tambm o Assembly em qual ela se encontra. Para exemplificar a configurao, vamos analisar o cdigo abaixo:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="urlsSection" type="Aplicacao.UrlsSection, Aplicacao" /> </configSections> </configuration>

27

Como podemos ver, dentro do elemento configSections criamos uma nova seo atravs do elemento section. Primeiramente precisamos informar o nome que essa seo ter dentro do arquivo de configurao. Aqui voc pode escolher o que achar mais vivel para Israel Aece | http://www.projetando.net

Captulo 3 Utilizao de Assemblies

28

a sua aplicao mas, lembre-se que a partir dela que ir recuperar as informaes no seu cdigo. Em seguida, utilizamos o atributo type para espeficiar que essa seo ir ser baseada em um tipo que criamos no cdigo. Esse tipo deve ser informado com o seu fullname, ou seja, desde o namespace raiz at a classe que ser utilizada. Alm disso, ainda necessrio dizer qual o Assembly em que o mesmo est contido que, no nosso caso, chama-se Aplicacao. Para complementar o arquivo de configurao, necessitamos agora incluir a seo customizada que criamos anteriormente. Como nomeamos essa seo como urlsSection, ela que ser utilizada para configurarmos todas as propriedades que criamos via cdigo. A comear pela propriedade Name e tambm uma propriedade Simple, que armazena um elemento individual do tipo UrlConfigElement. Para finalizar, temos a propriedade Urls que uma coleo e a sua estrutura um pouco diferenciada, justamente porque armazena vrios elementos que, no nosso caso, do tipo UrlConfigElement. Abaixo temos o arquivo de configurao na ntegra:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="urlsSection" type="Aplicacao.UrlsSection, Aplicacao" /> </configSections> <urlsSection name="Favoritos"> <simple name="Microsoft" url="http://www.microsoft.com" port="0" /> <urls> <clear /> <add name="People" url="http://www.people.com.br" port="0" /> <add name="Projetando.NET" url="http://www.projetando.net/" port="8080" /> </urls> </urlsSection> </configuration>

Como podemos notar, temos o elementos urls qual podemos adicionar quantos elementos desejarmos, pois trata-se de uma coleo. Agora, para acessarmos essas informaes dentro da aplicao, necessrio utilizarmos a classe ConfigurationManager que Israel Aece | http://www.projetando.net

28

Captulo 3 Utilizao de Assemblies

29

fornece vrios mtodos estticos, que permitem manipular o arquivo de configurao da aplicao corrente. Um mtodo importante que a classe ConfigurationManager disponibiliza o mtodo OpenExeConfiguration que retorna um objeto do tipo Configuration que representa as configuraes que esto no arquivo de configurao. Mas no nosso caso, utilizaremos uma espcie de atalho. Trata-se de um outro mtodo, chamado GetSection que, dado uma string com o nome da seo, ele retorna a instncia da mesma. O cdigo abaixo mostra como resgatar as informaes do arquivo de configurao e, para fins de exemplo, vamos apenas mostr-los na tela:
VB.NET Imports System.Configuration Dim section As UrlsSection DirectCast(ConfigurationManager.GetSection("urlsSection"), UrlsSection) Console.WriteLine(section.Name) For Each element As UrlConfigElement In section.Urls Console.WriteLine(element.Name & " - " & element.Url) Next C# using System.Configuration; UrlsSection sec = ConfigurationManager.GetSection("urlsSection") as UrlsSection; Console.WriteLine(sec.Name); foreach (UrlConfigElement element in sec.Urls) Console.WriteLine(element.Name + " - " + element.Url); =

O quadro abaixo exibe o output deste cdigo:


Favoritos People - http://www.people.com.br Projetando.NET - http://www.projetando.net/

29

Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes


Captulo 4 Monitoramento e depurao de aplicaes Introduo

Como o Visual Studio .NET a principal ferramenta para o desenvolvimento de aplicaes .NET, no seria completa se no tivesse vrias funcionalidades em sua IDE que permite a fcil depurao e monitoramento das aplicaes. A utilizao do depurador do Visual Studio .NET tem duas vantagens: 1. fornece uma interface familiar que permite o acompanhamento passo--passo do cdigo/processo. 2. tem um forte integrao com o Visual Studio .NET. O Visual Studio .NET em conjunto com o seu depurador fornecem uma gama de ferramentas que do uma grande flexibilidade para a depurao completa de uma aplicao, seja ela Windows, Web ou qualquer tipo de servio. Alm das funcionalidades que j esto embutidas, elas tambm so extensveis, o que permite os desenvolvedores customizarem, extendendo alguns aspectos e funcionalidades para ter uma melhor visualizao de problemas mais especficos. A primeira parte do captulo trata de como manipular o Event Log do Windows, que um repositrio de informaes referentes as mais diversas aplicaes que so executadas em cima do sistema operacional. Veremos como criar repositrios especficos para a nossa aplicao dentro dele, bem como logar informaes dentro dela. Logo em seguida, analisaremos como manipular os processos que esto sendo executados na mquina em que a aplicao executada. J na segunda parte, analisaremos grande parte das funcionalidades que a IDE do Visual Studio .NET fornece para ajudar na depurao da aplicao e tambm como extend-la com um exemplo simples. Finalmente, veremos como habilitar o Tracing dentro da aplicao, que permite acompanhar o comportamento da mesma durante os testes, bem como quando estiver em produo, para detectar possveis falhas. Windows Event Log Quando um problema ocorre o administrador do sistema ou tcnicos que fornecem suporte devem ter informaes a respeito da falha, quem gerou, quando, etc.. Muitas aplicaes persistem erros e eventos de diversas formas, mantendo cada uma, um padro proprietrio que satisfaz a necessidade dela. Esses padres proprietrios possuem diferentes informaes, formatos e disponibilizam, tambm de forma diferente, para o usurio. O Event Log do Windows fornece uma forma centralizada e padronizada para que as aplicaes (e tambm para o sistema operacional) possa salvar os eventos e erros que desejarem.

Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes

Felizmente o prprio Windows fornece uma interface que permite aos usurios visualizarem os possveis problemas e eventos que ocorreram em uma determinada aplicao. Essa interface chama-se Event Viewer e pode ser visualizada atravs da imagem abaixo:

Imagem 4.1 Interface do Event Viewer do Windows Tipos de Logs O .NET Framework j traz embutido diversas classes que auxiliam a manipulao total do Event Log do Windows. Essas classes fornecem toda a infraestrutura para a criao de novos repositrios como a gravao de eventos dentro deles. Atualmente existem cinco tipos de eventos que podem ser logados. A tabela abaixo descreve cada um desses tipos: Tipo de Evento Error Warning Information Success Audit Failure Audit Descrio Um evento que indica um problema grave, como por exemplo, uma falha ocorreu quando um servio tentou ser carregado. Um evento que no uma falha, mas pode representar um futuro problema. Um evento que tem a finalidade de logar informaes a nvel de sucesso, como por exemplo, servio foi iniciado, servio foi parado. Um evento que indica que a auditoria de segurana foi efetuada com sucesso. Um evento que indica que a auditoria de segurana falhou.

Quando voc no informa nenhum dos tipos de eventos que vimos acima, por padro, ele assumo o tipo Information. Computadores que rodam sistema operacional Windows 2000 ou superior possuem 3 tipos de event logs: 1. System Log: armazena eventos que ocorrem nos componentes do sistema, como por exemplo um problema com um driver qualquer. 2. Security Log: armazena eventos com relao segurana. 3. Application Log: armazena eventos que ocorrem em uma aplicao Israel Aece | http://www.projetando.net 2

Capitulo 4 Monitoramento e depurao de aplicaes

Arquitetura e manipulao fornecida pelo .NET O .NET Framework fornece um namespace chamado System.Diagnostics. dentro dele que temos todas as classes necessrias para manipular o Event Log do Windows. Este namespace vai muito alm disso, fornecendo classes para tracing e monitoramento das aplicaes que veremos mais tarde, ainda neste captulo. Basicamente temos trs tipos principais que so utilizados para a manipulao do Event Log: EventLog, EventLogEntryType e EventLogEntry. A classe EventLog responsvel por interagir entre a aplicao e o Event Log do Windows, customizando para que a sua aplicao possa gravar os eventos relevantes dentro dele. A partir dele tambm podemos, alm de criar logs e entradas, ler todas as entradas de um log, bem como excluir os logs. Entendam por log o repositrio que armazena todas as entradas feitas pelas aplicaes. Essa uma classe que fornece uma poro de mtodos estticos que permitem interagir com o Event Log. Para exemplificar a criao de um novo log, vamos analisar o cdigo abaixo:
VB.NET Imports System.Diagnostics If Not EventLog.SourceExists("AplicacaoWeb") Then EventLog.CreateEventSource("AplicacaoWeb", "People") End If If Not EventLog.SourceExists("AplicacaoWin") Then EventLog.CreateEventSource("AplicacaoWin", "People") End If Dim logWeb As New EventLog() logWeb.Source = "AplicacaoWeb" logWeb.WriteEntry("Log - Web App.") Dim logWin As New EventLog() logWin.Source = "AplicacaoWin" logWin.WriteEntry("Log - Win App.") C# using System.Diagnostics; if(!EventLog.SourceExists("AplicacaoWeb")) EventLog.CreateEventSource("AplicacaoWeb", "People"); if(!EventLog.SourceExists("AplicacaoWin")) EventLog.CreateEventSource("AplicacaoWin", "People");

Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes


EventLog logWeb = new EventLog(); logWeb.Source = "AplicacaoWeb"; logWeb.WriteEntry("Log - Web App."); EventLog logWin = new EventLog(); logWin.Source = "AplicacaoWin"; logWin.WriteEntry("Log - Win App.");

Antes de analisar o cdigo, necessrio termos em mente dois termos importantes: Log e Source. O primeiro deles, Log, o local onde armazenaremos os eventos, ou seja, o repositrio dos eventos. J o segundo, Source, quem (aplicao/servio) gerou o evento. Neste momento nos deparamos com um mtodo chamado WriteEntry que responsvel por encapsular a escrita de uma entrada dentro do Event Log. No exemplo acima, utilizamos um dos overloads do mtodo que disponibilizado, ou seja, somente passando a mensagem que ser gravada no Log. Dentre vrios overloads que este mtodo fornece, temos ainda um que recebe como parmetro, alm da mensagem, um enumerador do tipo EventLogEntryType que, nada mais que o tipo de evento (Error, Warning, Information, Success Audit e Failure Audit) e, como no informamos nenhum tipo no exemplo acima, por padro, ele assume Information. Como podemos visualizar atravs do cdigo acima, criamos um Log para a empresa People. Todas as aplicaes da People vo logar as informaes dentro deste repositrio. Depois disso, cada aplicaes tem o seu identificador, o Source, que identificar dentro do Event Log quem foi o responsvel pela gerao do evento. Atravs da imagem abaixo voc pode analisar os eventos recm criados e com o Source j especificado (em vermelho):

Imagem 4.2 Event Log j com as entradas. Se quisermos apagar via aplicao tanto o Log quanto o Source tambm possvel atravs da classe EventLog, como mostrado abaixo: 4

Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes


VB.NET Imports System.Diagnostics EventLog.DeleteEventSource("AplicacaoWeb") EventLog.DeleteEventSource("AplicacaoWin") EventLog.Delete("People") C# using System.Diagnostics; EventLog.DeleteEventSource("AplicacaoWeb"); EventLog.DeleteEventSource("AplicacaoWin"); EventLog.Delete("People");

Cada entrada que adicionamos vo sendo colocadas dentro do Event Log. Como vimos um pouco mais acima, alm dos mtodos estticos que a classe EventLog disponibiliza, ela fornece outras funcionalidades, pois a partir de uma instncia dela que temos acesso ao Log como um todo, permitindo interagir com este Log, gravando entradas, lendo essas entradas, etc.. O exemplo abaixo mostra como devemos fazer para exibir os eventos que foram previamente depositados dentro do Event Log do Windows:
VB.NET Imports System.Diagnostics Dim logs As New EventLog("People") For Each log As EventLogEntry In logs.Entries Console.WriteLine(log.Message) Next C# using System.Diagnostics; EventLog logs = new EventLog("People"); foreach(EventLogEntry log in logs.Entries) Console.WriteLine(log.Message);

Neste momento temos nos deparamos com uma nova classe do tipo EventLogEntry. Esta classe representa uma entrada individual que est dentro do Event Log. A nica finalidade desta classe mesmo encapsular todo o acesso um determinado registro que est dentro do Event Log e, sendo assim, no permitido criar instncias da mesma. A forma de trabalhar com ela mostrado acima, ou seja, quando iteramos atravs da

Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes

propriedade Entries do objeto EventLog, cada elemento que a coleo retorna do tipo EventLogEntry e, entre as principais propriedades que ela expe, temos: Propriedade EntryType Index InstanceId Message Source UserName Descrio Retorna um enumerador do tipo EventLogEntryType que indica qual o tipo da entrada. Retorna um nmero inteiro indicando qual o ndice que a entrada ocupa dentro do Log. Retorna um nmero que identifica unicamente a entrada dentro do Source. Recupera a mensagem. Recupera o nome da aplicao que gerou o evento. Recupera o nome do usurio que responsvel pelo evento.

Nota: As aplicaes ASP.NET tambm suportam o acesso a Event Logs e fornecem uma arquitetura flexvel para escolhermos onde desejamos persistir os eventos. Esse recurso chama-se Health Monitoring que est fora do escopo desta curso. Processos Imagine que a sua aplicao gera arquivos em formato texto que salvam diversos tipos de informaes. Em algum local, dentro desta mesma aplicao, voc lista todos os arquivos gerados e disponveis para que o usurio possa visualizar. Para poupar trabalho do usurio que precisaria conhecer o caminho at o arquivo para abr-lo no bloco de notas, automatizamos este passo, ou seja, quando o usurio dar um duplo clique em cima do arquivo, o notepad aberto automaticamente, passando para o mesmo o arquivo texto a ser exibido. neste momento que um novo processo criado. O .NET Framework fornece, tambm dentro do namespace System.Diagnostics, uma classe chamada Process que permite extrair informaes dos processos que esto sendo executados dentro de um determinado computador. Dentro do Windows, voc pode ver todos os processos que esto sendo executados no momento atravs da Windows Task Manager, quais podemos tambm acess-los dentro de uma aplicao .NET. Para fins de exemplo, vamos utilizar o bloco de notas, mas nada impede de inicializarmos aplicaes mais complexas, como por exemplo, o Word, Excel, etc.. A classe Process fornece acesso para os processos que esto sendo executados no computador. Essa classe permite-nos controlar um determinado processo, iniciando, parando e para fins de monitoramento da aplicao. A classe Process tambm fornece vrios mtodos estticos para a manipulao do de um determinado processo, que permite inicializar diretamente, sem a necessidade de uma instncia da classe Process. Entre os principais mtodos, temos: Mtodos Estticos GetCurrentProcess Descrio Retorna um objeto do tipo Process contendo todas as informaes Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes

GetProcessById

do projeto corrente. Dado um nmero inteiro que representa o identificar de um processo, retorna um objeto do tipo Process que representa o processo. O Id que ele espera como parmetro o PID (Process Identifier), qual pode ser visualizado atravs da barra de tarefas do Windows, como mostrado atravs da imagem abaixo:

GetProcesses Start

Imagem 4.3 Barra de Tarefas do Windows Retorna um array de objetos do tipo Process que representam todos os processos que esto rodando no computador. Permite inicializar diretamente um processo, sem a necessidade de criar um objeto do tipo Process.

Logo, para inicializarmos o bloco de notas, podemos optar por duas formas: a primeira utilizar o mtodo esttico Start. J a segunda, atravs de uma instncia de uma classe do tipo Process. A segunda te possibilita uma maior controle sobre a mesma se precisar mais tarde recuperar informaes a respeito do processo. O cdigo abaixo exibe duas formas equivalentes de inicializar o bloco de notas:
VB.NET Imports System.Diagnostics Primeira forma: Process.Start("notepad.exe", "Arquivo.txt") Segunda forma: Dim p As New Process() p.StartInfo.FileName = "notepad.exe" p.StartInfo.Arguments = "Arquivo.txt" p.Start() C# using System.Diagnostics; //Primeira forma: Process.Start("notepad.exe", "Arquivo.txt");

Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes

//Segunda forma: Process p = new Process(); p.StartInfo.FileName = "notepad.exe"; p.StartInfo.Arguments = "Arquivo.txt"; p.Start();

Depois de inicializado o processo, se em algum momento quisermos finalizar o mesmo, utilizamos o mtodo Kill, que fora a finalizao do processo. Como esse mtodo executado assincronamente, em seguida invoque o mtodo WaitForExit para aguardar o processo ser finalizado, se assim desejar. Agora, se desejar listar todos os processos que esto sendo executados atualmente na mquina, ento pode optar pela utilizao do mtodo GetProcesses, assim como mostrado abaixo:
VB.NET Imports System.Diagnostics For Each p As Process In Process.GetProcesses() Console.WriteLine(String.Format("Processo: {0} Id: {1}", _ p.ProcessName, _ p.Id)) Next C# using System.Diagnostics; foreach(Process p in Process.GetProcesses()) { Console.WriteLine(String.Format("Processo: {0} Id: {1}", p.ProcessName, p.Id)); }

Depurando uma aplicao Sempre que estamos desenvolvendo aplicaes, independente de qual linguagem ou plataforma estamos utilizando, h sempre uma necessidade muito grande de podemos depur-la enquanto o processo de desenvolvimento acontece. A depurao consiste em conseguirmos encontrar possveis falhas dentro da nossa aplicao e, entre essas falhas, temos basicamente trs tipos: sintaxe, runtime e lgica.

Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes

A primeira falha, a de sintaxe, que a mais fcil de ser identificada, acontece quando escrevemos algum tipo de cdigo que o compilador no consegue entender e j acusa o problema antes mesmo da aplicao ser executada. Em segundo lugar, temos as falhas que ocorrem em tempo de execuo que, se no for tratada, muitas vezes fora a aplicao a ser fechada e, para citar alguns exemplos temos: acesso a algum componente, servidor de banco de dados inexistente, servio indisponvel, etc.. Finalmente, temos as falhas de lgica que so um pouco mais difcies de encontrar, j que o compilador no consegue antecipar a falha e ela provavelmente ir explodir quando tal cdigo for executado. Alguns exemplos desse tipo de falha so: incompatibilidade de tipos, diviso por zero, objeto inexistente, dados invlidos, etc.. Com todos esses possveis problemas que todos ns desenvolvedores estamos acostumados a enfrentar, seria terrvel se no tivssemos uma ferramenta eficaz para nos ajudar a encontrar o problema que est acontecendo. Felizmente o Microsoft Visual Studio .NET 2005 fornece uma grande ferramenta de debugger que auxilia na depurao de qualquer projeto que est sendo nele construdo, permitindo diagnosticar e consertar rapidamente o problema que est ocorrendo. O debugger permite voc analisar e modificar valores de variveis, visualizar a StackTrace, threads, dumps de memria e muito mais. Alm disso, possui Intellisense em algumas janelas usadas exclusivamente para depurao e, apesar de fortemente integrado com o Visual Studio 2005, no deixa de ser flexvel e permite que ns desenvolvedores estendam as funcionalidades e customizamos alguns controles para facilitar ainda mais o processo de depurao de cdigo. Quando voc desenvolve uma aplicao, no Visual Studio .NET voc trabalha em dois modos: modo de desenvolvimento e modo de execuo. Quando estamos em modo de desenvolvimento, voc pode criar, editar o cdigo e tambm detectar possveis problemas de sintaxe, que a prpria IDE lhe ajudar a resolver, como por exemplo o caso do Visual Basic .NET:

Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes

10

Imagem 4.4 Auxiliar que nos sugere as possibilidades que existem para ajustar o cdigo Agora, problemas de lgica dificilmente voc detecta durante esse modo. Somente quando a aplicao for executada e voc ver que o resultado no o esperado que voc detecta um possvel problema mas, no pode mudar nenhuma linha de cdigo neste momento. Neste momento que aparece um novo modo, que chamamos de break mode. Esse modo permite-nos para a aplicao em um determinado ponto para que seja possvel acompanhar a executao via linha de cdigo e, conseqentemente, analisar tudo o que acontece nos bastidores da aplicao. Para entrar neste modo, voc precisa colocar um breakpoint na linha que deseja que o depurador para que assim voc possa acompanhar. Atravs da tecla F5 ou mesmo via menu Debug, Start Debbuing voc tambm pode iniciar a aplicao anexando a mesma, o depurador. Com a aplicao neste modo, voc j conseguir averiguar o cdigo que escreveu e acompanhar a execuo passo passo do mesmo. Nas verses anteriores do Visual Studio .NET tnhamos os data tips. Os data tips serviam para quando, em break mode, passvamos o mouse por cima de uma tipo simples (inteiro, string, double, etc.) ele j exibia o valor da varivel. J o Visual Studio .NET 2005, os data tips ainda existem, mas agora muito mais poderosos, pois permitem tambm a visualizao de objetos mais complexos, podendo visualizar todas as suas propriedades. J no caso das colees, os elementos tambm podem ser visualizados simplesmente passando o cursor do mouse em cima, assim como mostrado na imagem abaixo: Israel Aece | http://www.projetando.net

10

Capitulo 4 Monitoramento e depurao de aplicaes

11

Imagem 4.5 Data tips para objetos complexos Criando um Debug Visualizer A Microsoft incluiu na verso 2.0 do .NET Framework uma possibilidade de criar um debugger customizado para um determinado objeto que temos. Isso permite-nos criar uma interface mais amigvel em relao qual fornecida quando utilizamos o debug do Visual Studio .NET 2005. Essa tcnica chamada de Debugger Visualizer. Voc pode escrever esse visualizador para qualquer objeto da sua aplicao, com excesso de um Object ou Array. A arquitetura do debugger est dividida em duas partes: O debugger side corre dentro do debugger do Visual Studio .NET, podendo ser criado e exibido uma interface para seu visualizador. O debuggee side corre dentro do processo que o Visual Studio .NET est depurando.

O objeto que voc est querendo visualizar (uma string ou Image, por exemplo) existe dentro do processo debuggee. Logo, o debuggee side deve mandar os dados para o debugger side que, por sua vez, exibe o objeto para que o desenvolvedor possa depurar. Essa visualizao criada por ns que, ainda nesse artigo, veremos como cri-la. O debugger side recebe o objeto que ser visualizado atravs de um object provider, o qual implementa uma Interface chamada IVisualizerObjectProvider. O debuggee side Israel Aece | http://www.projetando.net

11

Capitulo 4 Monitoramento e depurao de aplicaes

12

envia o objeto atravs de um object source, o qual deriva de uma classe chamada VisualizerObjectSource. O object provider tambm devolve o objeto para o object source, permitindo assim, alm de exibir o objeto, edit-lo no visualizador (se assim desejar) e devolv-lo para a aplicao. Essa comunicao efetuada atravs de um objeto do tipo Stream. Para criarmos este visualizador, primeiramente precisamos marcar a classe como sendo uma classe de DebuggerVisualizer e, para isso, fornecido um atributo chamado DebuggerVisualizer (usado a nvel de Assembly ou classe) para defin-la como um visualizador. Alm disso, a classe dever obrigatoriamente herdar de uma classe abstrata chamada DialogDebuggerVisualizer e implementar o mtodo chamado Show para customizar a visualizao. O atributo DebuggerVisualizer, contido dentro do namespace System.Diagnostics, fornece mais alguns parmetros para a configurao do visualizador. O primeiro parmetro tipo, ou seja, a classe derivada DialogDebuggerVisualizer que o nosso visualizador efetivamente; j o segundo especifica o tipo de objeto que far a comunicao entre o debugger side e o debuggee side e, se no informado, um padro ser utilizado. O restante, chamado de "Named Parameters", voc deve especificar o tipo de objeto que o visualizador ir trabalhar (uma string ou Image, por exemplo) e no outro, o nome que aparecer dentro do Visual Studio .NET 2005, para permitir visualizao. Para ilustrar, veremos abaixo o cdigo que cria o visualizador:
VB.NET Imports Imports Imports Imports Imports <_

System Microsoft.VisualStudio.DebuggerVisualizers System.Windows.Forms System.Drawing System.Diagnostics

Assembly: DebuggerVisualizer(GetType(DebugTools.ImageVisualizer), _ GetType(VisualizerObjectSource), _ Target := GetType(System.Drawing.Image), _ Description := "Image Visualizer") _ > Namespace DebugTools Public Class ImageVisualizer Inherits DialogDebuggerVisualizer Protected Overrides Sub Show(IDialogVisualizerService windowService, _ IVisualizerObjectProvider objectProvider) Image) Image data = DirectCast(objectProvider.GetObject(),

12

Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes

13

Using(ImageVisualizerForm ImageVisualizerForm()) f.Image = data windowService.ShowDialog(f) End Using End Sub End Class End Namespace C# using using using using using [

New

System; Microsoft.VisualStudio.DebuggerVisualizers; System.Windows.Forms; System.Drawing; System.Diagnostics;

assembly: DebuggerVisualizer(typeof(DebugTools.ImageVisualizer), typeof(VisualizerObjectSource), Target = typeof(System.Drawing.Image), Description = "Image Visualizer") ] namespace DebugTools { public class ImageVisualizer : DialogDebuggerVisualizer { protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider) { Image data = (Image)objectProvider.GetObject(); using(ImageVisualizerForm f ImageVisualizerForm()) { f.Image = data; windowService.ShowDialog(f); } } } } = new

Note que foi criado um formulrio chamado ImageVisualizerForm. Este formulrio encarregado de receber a imagem e exib-la. No h segredos nele, ou seja, apenas foi criada uma propriedade chamada Image que recebe a imagem vinda do object provider. O primeiro parmetro do mtodo Show, chamado windowService do tipo Israel Aece | http://www.projetando.net

13

Capitulo 4 Monitoramento e depurao de aplicaes

14

IDialogVisualizerService, fornece mtodos para que o visualizador possa estar exibindo formulrios, controles e janelas de dilogo. Se voc quiser que o seu visualizador edite o objeto e o devolva para a aplicao, ter que sobrescrever os mtodos TransferData ou CreateReplacementObject da classe VisualizerObjectSource. Finalmente, para que possamos utilizar o visualizador dentro do Visual Studio .NET 2005 teremos que compilar o projeto e, com a DLL gerada, coloc-la dentro do diretrio <Diretorio VS.NET>\Common7\Packages\Debugger\Visualizers. Quando abrir o Visual Studio, j ter acesso ao visualizador, assim como mostrado atravs da imagem abaixo:

Imagem 4.6 Utilizando Debug Visualizer customizado Ainda existem outros atributos como o DebuggerVisualizer. Da mesma forma que o visualizador, Ainda dentro do namespace System.Diagnostics temos outras classes importantes que auxiliar no processo de depurao de cdigo. Essas classes so: Debugger, StackFrame e StackTrace. A primeira delas, Debugger, trata-se de uma classe que permite, programaticamente, efetuar a comunicao com o debugger do Visual Studio .NET 2005. Essa classe comumente utilizada quando voc precisa depurar uma seo de cdigo que j foi executada por algum outro processo, ou ainda, quando esse cdigo disparado sem antes mesmo da aplicao inicializar. Essa classe alguns membros estticos que veremos a seguir as suas funcionalidades: Membro IsAttached Break Descrio Retorna um valor booleano indicando se debugger est ou no anexado ao processo. Quando chamado este mtodo, ele gera um breakpoint e, conseqentemente, a aplicao entra em break mode e assim voc consegue depurar o cdigo. Israel Aece | http://www.projetando.net

14

Capitulo 4 Monitoramento e depurao de aplicaes


IsLogging Launch Log

15

Indica se o log est ou no habilitado para o debugger. Quando o debugger no estiver anexado ao processo, voc pode invocar esse mtodo para lanar o debugger e anex-lo ao processo. Insere uma mensagem no debugger corrente.

A classe StackFrame, apesar de ser pblica, utilizada pela infraestrutura do .NET Framework e no indicado utiliz-la diretamente no seu cdigo. Stack frame trata-se da representao fsica de uma chamada alguma funo dentro da thread corrente. Esse objeto criado e colocado dentro da pilha de chamadas de funes que so realizadas em toda a execuo da thread. A classe StackTrace basicamente uma coleo de StackFrames. A classe Exception fornece uma propriedade chamada StackTrace do tipo string. Quando alguma exceo acontece no cdigo, essa propriedade retornar todas as chamadas realizadas as funes at que onde o problema aconteceu. Isso ajudar imensamente voc a detectar onde o problema realmente se encontra. Janelas de depurao Quando iniciamos o depurador do Visual Studio .NET, vrias janelas ficam disponveis para nos auxiliar durante o processo de depurao. Essas janelas ficam disponveis no menu Debug, Windows. Cada uma delas fornece um funcionalidade diferente, a seguir: Janela Breakpoint Output Script Explorer Watch Autos Locals Immediate Call Stack Threads Modules Descrio Exibe uma janela onde voc pode visualizar todos os breakpoint definidos na aplicao e tambm fornece recursos para exclu-los ou desabilit-los. Exibe informaes de status para vrias features da IDE do Visual Studio .NET. Exibe uma lista de arquivos de scripts que esto atualmente carregados no programa que voc est depurando. Cria uma lista customizada de variveis e expresses que deseja monitorar. Visualiza todas as variveis dentro bloco corrente e de trs blocos antes e trs blocos depois do bloco corrente. Permite visualizar e modificar o valor das variveis locais. Utilizado para depurar e avaliar expresses, executar cdigo, imprimir valores de variveis, entre outros, a partir de linha de comando. Visualiza todo o histrico de chamadas das linhas de cdigo que esto sendo depuradas. Exibe e controla as threads do programa que est sendo depurado. Exibe uma lista de mdulos (DLL ou EXE) que esto sendo utilizadas pelo programa que est sendo depurado, mostrando informaes relevantes sobre cada uma delas. Israel Aece | http://www.projetando.net

15

Capitulo 4 Monitoramento e depurao de aplicaes


Processes Memory Disassembly Tracing

16

Exibe uma lista com todos os processos que voc tem anexado ou lanado a partir do Visual Studio .NET. Disponibiliza informaes e permite a visualizao do espao de memria que est sendo utilizado pela sua aplicao. Esta janela mostra o cdigo assembly correspondente ao cdigo, criado pelo compilador.

Tracing a forma que temos para monitorar a execuo da aplicao enquanto ela est rodando. Em tempo de desenvolvimento, voc pode adicionar instrumentaes de tracing e debugging para a aplicao .NET e, poder utiliz-la durante o processo de desenvolvimento e tambm depois de distribudo. Para aplicaes Windows, o namespace System.Diagnostics fornece duas classes para esse tipo de monitoramento: Trace e Debug. J quando estamos falando de aplicaes Web, temos a classe TraceContext dentro do namespace System.Web, que nada mais que, entre outras funcionalidades especficas para o contexto, um wrapper para a classe Trace. Basicamente as classes Trace e Debug possuem as mesmas finalidades. A nica diferena significativa entre as classes que primeira delas, a Trace, compilada por padro para dentro do Assembly e a classe Debug no. Enquanto estamos desenvolvendo a aplicao, tanto as informaes de Trace e Debug so exibidas na janela Output do Visual Studio .NET. Para habilitar essas funcionalidades para elas serem distribudas juntamente com o Assembly, voc deve compilar a aplicao com a diretiva TRACE ou DEBUG. Para habilitar ou desabilitar esses recursos voc tem duas formas: ou via IDE ou via linha de comando: 1. Via IDE: a. Visual Basic .NET: Propriedades do Projeto Aba Compile Advanced Compile Options Compilation Constants. b. Visual C#: Propriedades do Projeto Aba Build General. 2. Via linha de comando: a. Visual Basic .NET: vbc /r:App.dll /d:TRACE=TRUE /d:DEBUG=FALSE Main.vb b. Visual C#: csc /r:App.dll /d:TRACE /d:DEBUG=FALSE Main.cs O uso destas classes bastante simples, mas antes de visualizar como utiliz-las, vamos analisar os mtodos estticos que elas fornecem e para que serve cada um deles. Logo em seguida, temos um trecho de cdigo que basicamente mostra os mtodos sendo invocados a partir de suas respectivas classes. Mtodo Assert Descrio Dado uma condio, ele analisa se a mesma ou no falsa. Se for, a mensagem especificada armazenada e, se estiver rodando uma aplicao que possui uma interface grfica, uma caixa de dilogo Israel Aece | http://www.projetando.net 16

Capitulo 4 Monitoramento e depurao de aplicaes

17

WriteIf Fail Write WriteLine WriteLineIf

exibida com mensagem. Dado uma condio, ele analisa se a mesma ou no verdadeira. Se for, a mensagem especificada armazenada. Grava a mensagem especificada e exibe uma caixa de dilogo exibida com a mesma mensagem. Simplesmente salva a mensagem. Salva mensagem, adicionando uma quebra de linha. Dado uma condio, ele analisa se a mesma ou no verdadeira. Se for, a mensagem especificada armazenada juntamente com uma quebra de linha.

VB.NET Imports System.Diagnostics Trace.Assert(1 > 2, "1 nunca ser maior que 2") Debug.Assert(1 > 2, "1 nunca ser maior que 2") Trace.WriteIf(1 = 1, "1 igual a 1") Debug.WriteIf(1 = 1, "1 igual a 1") Trace.Fail("Algo aconteceu.") Debug.Fail("Algo aconteceu.") Trace.Write("Um valor para logar.") Debug.Write("Um valor para logar.") Trace.WriteLine("Um valor para logar c/ quebra de linha.") Debug.WriteLine("Um valor para logar c/ quebra de linha.") Trace.WriteLineIf(1 = 1, "1 igual a 1 c/ quebra de linha.") Debug.WriteLineIf(1 = 1, "1 igual a 1 c/ quebra de linha.") C# using System.Diagnostics; Trace.Assert(1 > 2, "1 nunca ser maior que 2"); Debug.Assert(1 > 2, "1 nunca ser maior que 2"); Trace.WriteIf(1 = 1, "1 igual a 1"); Debug.WriteIf(1 = 1, "1 igual a 1"); Trace.Fail("Algo aconteceu."); Debug.Fail("Algo aconteceu."); Trace.Write("Um valor para logar."); Debug.Write("Um valor para logar."); Trace.WriteLine("Um valor para logar c/ quebra de linha.");

17

Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes


Debug.WriteLine("Um valor para logar c/ quebra de linha."); Trace.WriteLineIf(1 = 1, "1 igual a 1 c/ quebra de linha."); Debug.WriteLineIf(1 = 1, "1 igual a 1 c/ quebra de linha.");

18

At o momento essas configuraes nos atendem. Mas nem sempre temos o Visual Studio .NET e sua janela de Output nos clientes que iremos instalar a aplicao. Seria muito mais conveniente neste caso, podemos persistir tais informaes em arquivos textos ou at mesmo no Event Log do Windows, se assim desejarmos. Alm disso, pode surgir a necessidade de filtrar qual tipo de mensagem desejamos persistir e, nos exemplos acima tudo, sem excesso, salvo. neste momento que entra em ao outros objetos que so utilizados para fornecer uma maior flexibilidade para configurar e dar manuteno no tracing da aplicao. Nesta seo veremos os seguintes elementos: TraceSwitch, TraceListener e TraceSource. TraceSwitch Switches permitem voc habilitar, desabilitar e filtrar as mensagens de tracing, determinando se elas devem ou no serem persistidas. So objetos que esto acessveis via cdigo, mas que tambm podem ser configurados via arquivos de configurao, o que d uma maior flexibilidade, j que possveis alteraes no exigir que se recompile o programa. Atualmente existem trs tipos de switches fornecidos pelo .NET Framework: BooleanSwitch, TraceSwitch e SourceSwitch. O primeiro deles possibilita ligar e desligar o tracing. J os dois ltimos, permitem voc habilitar ou desabilitar o switch para um determinado nvel. Esse switch baseia-se nos seguintes nveis, disponibilizados pelo enumerador TraceLevel: Error, Warning, Info e Verbose. So esses nveis que so utilizados como filtros para configurarmos dentro da aplicao. Podemos em algum momento, queremos filtrar somente as mensagens de nvel Error e, mais tarde, somente o que for Warning. Atravs da tabela abaixo conseguimos visualizar de forma tabular o que cada um os nveis possibilita: Item Constante Tipo de Mensagem Off 0 Nenhuma. Error 1 Somente mensagens de erro. Warning 2 Mensagens de aviso e mensagens de erro. Info 3 Mensagens informativas, de aviso e erro. Verbose 4 Mensagens verbose, informativas, de aviso e erro. Verbose: exibe toda e qualquer mensagem de tracing e debugging. 18

Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes

19

Inicialmente vamos analisar a implementao de cdigo para o switch BooleanSwitch onde, via arquivo de configurao, vamos ver como devemos proceder para habilitar e desabilitar o switch, sem a necessidade de recompilar o cdigo. Inicialmente, analisaremos a configurao que deve ser realizada no arquivo de configurao da aplicao para suportar o swtich. O cdigo a seguir idntico para qualquer uma das linguagens:
App.Config <configuration> <system.diagnostics> <switches> <add name="ModuloDados" value="0"/> </switches> </system.diagnostics> </configuration>

No atributo name definimos o nome do switch. Esse nome o mesmo que deve ser referenciado dentro da aplicao, para vincular o switch ao arquivo de configurao. O atributo value indica se o switch est ou no habilitado, ou seja, 0 (zero) est desabilitado e 1 (um) est habilitado. O cdigo abaixo como fazer o vinculo entre o arquivo de configurao e a aplicao e, reparem que o mesmo nome passado para o construtor da classe BooleanSwitch:
VB.NET Imports System.Diagnostics Private dataSwitch As New _ BooleanSwitch("ModuloDados", "Acesso a dados.") Sub Main() If dataSwitch.Enabled Then Console.WriteLine("Habilitado.") Else Console.WriteLine("No habilitado.") End If End Sub C# using System.Diagnostics; private static BooleanSwitch dataSwitch = new BooleanSwitch("ModuloDados", "Acesso a dados."); static void Main(string[] args) {

19

Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes


if (dataSwitch.Enabled) { Console.WriteLine("Habilitado."); } else { Console.WriteLine("No habilitado."); }

20

J a utilizao do TraceSwitch tambm bem semelhante, pois tambm permite a configurao via App.Config. Neste caso, no arquivo de configurao vamos informar qual o tipo de mensagem de tracing que desejamos que seja persistida, filtrando a partir de nvel de mensagem, mais precisamente, atravs do enumerador TraceLevel, qual j analisamos mais acima. Essa classe possui quatro propriedades, sendo uma para cada nvel do enumerador TraceLevel: TraceError, TraceInfo, TraceVerbose e TraceWarning, onde cada uma retorna um valor booleano indicando se o switch pode ou no persistir informaes de um determinado nvel. Portanto, no arquivo de configurao devemos informar um dos itens do enumerador, podendo ser a constante (nmero inteiro) ou o alias. O cdigo abaixo exemplifica o uso do switch TraceSwitch:
App.Config <configuration> <system.diagnostics> <switches> <add name="ModuloDados" value="Warning"/> </switches> </system.diagnostics> </configuration>

VB.NET Imports System.Diagnostics Private dataSwitch As New _ TraceSwitch("ModuloDados", "Acesso a dados.") Sub Main() Console.WriteLine("Error: {0}", dataSwitch.TraceError) Console.WriteLine("Warning: {0}", dataSwitch.TraceWarning) End Sub C#

20

Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes


using System.Diagnostics; private static TraceSwitch dataSwitch = new TraceSwitch("ModuloDados", "Acesso a dados.");

21

static void Main(string[] args) { Console.WriteLine("Error: {0}", dataSwitch.TraceError); Console.WriteLine("Warning: {0}", dataSwitch.TraceWarning); }

Se repararem no atributo value do arquivo de configurao, temos ali definido Warning (podendo ser a constante 2 que teria o mesmo efeito). Como esses itens so acumulativos, o Warning ir gravar tanto informaes de erros quanto de avisos. O ltimo, porm no menos importante, o SourceSwitch. Trata-se de uma nova classe que foi includa na verso 2.0 do .NET Framework. Esse switch utilizado em conjunto com o objeto TraceSource, qual veremos mais abaixo. A classe SourceSwitch possui uma propriedade importante chamada Level. Essa propriedade recebe um valor fornecido pelo enumerador SourceLevels. Os valores definidos por esse enumerador identificar quais eventos sero capturados pelo tracing. Veremos mais detalhes sobre esse switch mais abaixo, quando abordarmos a respeito da classe TraceSource. TraceListener Logo no comeo desta seo, vimos que todas as informaes depositadas atravs da classe Trace e tambm da classe Debug so enviadas e exibidas na janela Output do Visual Studio .NET. Isso ajuda quando estamos em tempo de desenvolvimento, mas no resolve quando a aplicao instalada no cliente. necessrio definirmos algum lugar que permita persistir fisicamente os dados para uma posterior anlise. Felizmente temos a nossa disposio os listeners. Os listeners so responsveis por coletar, armazenar e direcionar as informaes de tracing. Tanto a classe Trace quanto a classe Debug possuem uma propriedade chamada Listeners que expem uma coleo do tipo TraceListenerCollection, que permite adicionarmos quantos listeners forem necessrios. Sendo assim, podemos ter as mensagens de tracing sendo persistidas de diversas formas. Atualmente dentro do .NET Framework temos alguns listeners a nossa disposio. Todos os listeners herdam diretamente da classe abstrata TraceListener. Abaixo temos uma tabela que descreve todos os listeners que esto contidos dentro do .NET Framework: Listener TextWriterTraceListener Descrio Redireciona toda a sada para um stream, persistindo o Israel Aece | http://www.projetando.net

21

Capitulo 4 Monitoramento e depurao de aplicaes

22

contedo dentro de um arquivo texto convencional Redireciona toda a sada para o Event Log do Windows. Esse o listener padro que adicionado a classe Trace e Debug. Os mtodos Write e WriteLine enviam o contedo para a janela Output do Visual Studio .NET e tambm para o mtodo Log do classe Debugger (discutida mais acima). ConsoleTraceListener Redireciona toda a sada para a console. DelimitedListTraceListener Redireciona toda a sada para um stream, persistindo o contedo dentro de um arquivo texto mas, neste caso, utilizando um delimitador. EventLogTraceListener DefaultTraceListener O delimitador pode ser definido atravs da propriedade Delimiter. Redireciona toda a sada para um stream, persistindo o contedo dentro de um arquivo em formato XML.

XmlWriterTraceListener

Todos os listeners podem ser configurados via cdigo (Visual Basic .NET/Visual C#) ou, como j era de se esperar, via arquivo de configurao. Como temos a propriedade Listeners, que expe uma coleo, podemos adicionar quantos quisermos, ou seja, podemos ter as informaes de tracing persistidas em arquivos XML e arquivos textos convencionais. Nota: Todos os listeners possuem um mtodo chamado Flush e outro chamado Close. Quando voc invoca os mtodos WriteXXX na verdade as informaes esto sendo armazenadas na memria. Para voc persistir os dados fisicamente, ento ter que invocar o mtodo Flush que tem a finalidade de gravar os dados em disco para todos os listeners ou o mtodo Close que, alm de persistir os dados para todos os listeners, tambm os fecha. Finalmente, vamos analisar a implementao dos listeners em cdigo e, em seguida, via arquivo de configurao. Reparem que o mtodo Clear da classe Trace se encarrega de limpar todos os listeners existentes na coleo e, conseqentemente, o listener padro:
VB.NET Imports System.Diagnostics Trace.Listeners.Clear() Trace.Listeners.Add(New XmlWriterTraceListener("Log.xml")) Trace.Listeners.Add(New TextWriterTraceListener("Log.txt")) Trace.WriteLine("Teste de Tracing", "Testes") Trace.WriteLine("Adicionando uma nova entrada", "Testes") Trace.Flush()

22

Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes


C# using System.Diagnostics; Trace.Listeners.Clear(); Trace.Listeners.Add(new XmlWriterTraceListener("Log.xml")); Trace.Listeners.Add(new TextWriterTraceListener("Log.txt")); Trace.WriteLine("Teste de Tracing", "Testes"); Trace.WriteLine("Adicionando uma nova entrada", "Testes"); Trace.Flush();

23

J abaixo temos a configurao dos listeners via arquivo de configurao, o que da uma maior flexibilidade, j que podemos adicion-los ou remov-los, plug and play. Lembrando que a configurao vlida para qualquer uma das linguagens (Visual Basic .NET ou Visual C#):
App.Config <configuration> <system.diagnostics> <trace autoflush="true" indentsize="4"> <listeners> <remove name="Default" /> <add name="txtListener" type="System.Diagnostics.TextWriterTraceListener" initializeData="Log.txt" /> <add name="xmlListener" type="System.Diagnostics.XmlWriterTraceListener" initializeData="Log.xml" /> </listeners> </trace> </system.diagnostics> </configuration>

TraceSource Esta classe foi includa a partir da verso 2.0 do .NET Framework. Em projetos com muitos componentes, seria interessante termos um tracing diferenciado para cada um deles, o que ficaria muito mais simples de gerenciar e tambm uma melhor forma de organizar. Com essa necessidade, sugiu a classe TraceSource, que permite configurarmos as informaes necessrias de tracing, como por exemplo os listeners, switches, etc.. O TraceSource permite montarmos toda a configurao necessria para cada seo ou componente do cdigo que estamos desenvolvendo e, como j previsto, alm da Israel Aece | http://www.projetando.net

23

Capitulo 4 Monitoramento e depurao de aplicaes

24

codificao via Visual Basic .NET ou Visual C#, permite tambm a configurao via App.Config, mas antes de entendermos a sua implementao, vamos analisar algums membros que essa classe fornece para a manipulao do tracing: Membro Construtor Descrio Essa classe possui dois construtores. O primeiro deles aceita somente uma string indicando o nome do TraceSource. J o segundo construtor, alm desta string, aceita tambm um parmetro do tipo SourceLevels. SourceLevels um enumerador que especifica o nvel das mensagens que sero filtradas pelo switch. Entre os possveis valores temos: ActivityTracing: Permite efetuar o tracing dos seguintes eventos: Stop, Start, Suspend, Transfer e Resume. All: Permite o tracing de todos os eventos. Critical: Permite somente eventos crticos. Error: Permite eventos crticos e eventos de erros. Information: Permite efetuar o tracing de eventos crticos, de erros, avisos e informaes. Off: No captura nenhum evento. Verbose: Permite efetuar o tracing de eventos crticos, de erros, avisos, informaes e tambm de verbose. Warning: Permite efetuar o tracing de eventos crticos, de erros e avisos. Retorna a coleo de listeners. Expe ou recebe um objeto SourceSwitch. Permite escrevemos dados (de objetos e variveis) dentro dos listeners. Esse mtodo recebe trs parmetros: o primeiro deles do tipo TraceEventType, que trata-se de um enumerador que define os seguintes valores: Critical: Erro fatal. Error: Erro que possvel ser recuperado. Information: Informaes adicionais. Resume: Reiniciando uma operao; Start: Iniciando uma operao. Stop: Parando uma operao. Suspend: Suspendendo uma operao. Transfer: Transferncia de controle para uma outra operao. Verbose: Tracing de debug. Warning: Problema no crtico.

Listeners Switch TraceData

24

J o segundo parmetro do mtodo do tipo inteiro, identifica o Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes

25

TraceEvent TraceInformation TraceTransfer

evento e, finalmente o ltimo parmetro, do tipo Object que o valor que ser salvo pela switch. Possui os mesmos parmetros do mtodo anterior, com excesso do ltimo, que aqui do tipo string, que permite escrever uma mensagem dentro da coleo de listeners. Permite escrever uma informao adicional dentro da coleo de listeners. Escreve uma mensagem de transferncia de controle de operao.

Quando utilizamos os mtodos TraceData e TraceEvent que esperam em seu primeiro parmetro um valor fornecido pelo enumerador TraceEventType, o valor vai ser comparado com o valor especificado na propriedade Level da classe SourceSwitch atravs do enumerador SourceLevels. Veremos a partir do cdigo abaixo a razo disso:
VB.NET Imports System.Diagnostics Sub Main() Dim source As New TraceSource("Trace - Componente 1") Dim defaultSwitch As New SourceSwitch("switch") defaultSwitch.Level = SourceLevels.Critical source.Switch = defaultSwitch source.Listeners.Add(New TextWriterTraceListener("Log.txt")) source.TraceEvent(TraceEventType.Critical, 12, crtica") source.TraceEvent(TraceEventType.Information, 22, de informao") source.Close() End Sub C# using System.Diagnostics; static void Main(string[] args) { TraceSource source = new TraceSource("Trace - Componente 1"); SourceSwitch defaultSwitch = new SourceSwitch("switch"); defaultSwitch.Level = SourceLevels.Critical; source.Switch = defaultSwitch; source.Listeners.Add(new TextWriterTraceListener("Log.txt")); source.TraceEvent(TraceEventType.Critical, 12, crtica"); source.TraceEvent(TraceEventType.Information, 22, de informao"); source.Close(); "Mensagem "Mensagem "Mensagem "Mensagem

25

Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes


}

26

Como podemos ver, definimos SourceLevels.Critical na propriedade Level da classe SourceSwitch. Em seguida, adicionamos a instncia da classe SourceSwitch na propreidade Switch da instncia da classe TraceSource onde tambm definimos o listener TextWriterTraceListener para persistir os dados em um arquivo texto chamado Log.txt. Finalmente, invocamos o mtodo TraceEvent para inserirmos uma nova entrada no tracing. O primeiro mtodo, informamos como TraceEventType o valor Critical. J na segunda vez que invocamos o mesmo mtodo, informamos como TraceEventType o valor Information. Sendo assim, o segundo no ser persistido, justamente porque o switch somente aceita, no mnimo, eventos do tipo Critical. Isso ir garantir que, desenvolvedores que utilizam o componente, no grave informaes desnecessrias. Finalmente, se quisermos configurar o mesmo TraceSource, s que agora dentro do arquivo de configurao, podemos proceder da seguinte forma:
App.Config <?xml version="1.0" encoding="utf-8" ?> <configuration> <system.diagnostics> <sources> <source name="Trace - Componente 1" switchName="switch"> <listeners> <add name="myLocalListener" type="System.Diagnostics.TextWriterTraceListener" initializeData="Log.txt" /> </listeners> </source> </sources> <switches> <add name="switch" value="Information" /> </switches> </system.diagnostics> </configuration>

E para utiliz-lo dentro da aplicao, faz:


VB.NET Imports System.Diagnostics Dim source As New TraceSource("Trace - Componente 1")

26

Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes


source.TraceEvent(TraceEventType.Critical, crtica") source.TraceEvent(TraceEventType.Information, informao") source.Close() C# using System.Diagnostics; TraceSource source = new TraceSource("Trace - Componente 1"); source.TraceEvent(TraceEventType.Critical, crtica"); source.TraceEvent(TraceEventType.Information, informao"); source.Close(); 12, 22, 12, 22,

27

"Mensagem "Mensagem de

"Mensagem "Mensagem de

WMI Windows Management Instrumentation Windows Management Instrumentation (WMI) parte do sistema operacional que fornece uma infraestrutura para controle, gerenciamento e informaes para o gerenciamento do sistema operacional. Ele pode fornecer informaes relevantes para monitoramento de aplicaes e, conseqentemente, reportar de forma amigvel ao usurio. O .NET Framework fornece uma variedade de classes que nos auxiliam para resgatar informaes referentes ao sistema operacional. Essas classes esto disponveis dentro do namespace System.Management que, necessrio fazer a referncia System.Management.dll na aplicao se desejar utilizar o WMI. Atravs da imagem abaixo possvel visualizar a arquitetura do WMI dentro do mundo .NET:

27

Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes

28

Imagem 4.7 Arquitetura WMI dentro do .NET Framework. O namespace System.Management fornece classes para manipularmos o WMI de uma forma simples e bastante produtiva, possibilitando escrever as queries que utilizamos para extrairmos informaes do sistema operacional, dispositivos e outras aplicao, de uma forma semelhante ao que existe atualmente com a linguagem SQL. Dentro deste namespace temos a classe chamada ManagementObjectSearcher, que uma das mais conhecidas e utilizadas. Essa classe comumente utilizada para recuperar diversos tipos de informaes, como por exemplo enumerar todos os drives de um determinado computador, os adaptadores de rede, processos que est sendo executados e muitos outros objetos dentro do sistema. Quando desejamos recuperar esse tipo de informao, utilizamos uma query, baseada em SQL. Conseqentemente, isso ir possibilitar colocarmos critrios na query para extrairmos informaes mais precisas, como por exemplo, exibir somente os processos que esto atualmente pausados, etc.. Essa query representada por um objeto do tipo ObjectQuery. A imagem abaixo ilustra a hierarquia das queries disponveis dentro deste namespace:

28

Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes

29

Imagem 4.8 Estrutura das queries disponveis para WMI Como a classe ManagementObjectSearcher permite buscar objetos baseados em uma query, ela fornece um mtodo chamado Get que retorna uma objeto (coleo) do tipo ManagementObjectCollection, contendo todos os objetos encontrados, sendo cada um deles representados por um objeto do tipo ManagementObject. Esse objeto representa os dados de um determinado objeto WMI que foi encontrado pela query. Para exemplificar a utilizao do que vimos at o momento, o cdigo abaixo exibe como recuperar todos os processos que esto sendo executados na mquina:
VB.NET Imports System.Management Dim searcher As New ManagementObjectSearcher("SELECT Win32_Service WHERE Started = TRUE") For Each service As ManagementObject In searcher.Get() Console.WriteLine("Servio = " & service("Caption")) Next C# using System.Management; ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT WHERE Started = TRUE"); * FROM Win32_Service * FROM

29

foreach (ManagementObject service in searcher.Get())

Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes


Console.WriteLine("Servio = " + service["Caption"]);

30

Um detalhe no cdigo acima com relao ao objeto ManagementObject. Ele possui uma propriedade default (indexer em Visual C#) que expe uma coleo do tipo PropertyDataCollection contendo todas as propriedades de um determinado objeto. O nico problema aqui que, por questes de genericidade, voc dever passar uma string contendo o nome da propriedade do objeto que deseja recuperar. Isso vai implicar em saber as propriedades que o objeto possui pois, se passar um nome que no existe, uma excesso do tipo ManagementException ser atirada. Ainda dentro deste mesmo namespace temos a classe ManagementEventWatcher. Essa classe disponibiliza um evento chamdo EventArrived que disparado quando um novo evento acontece. Esse evento, quando assinado, servem para notificar a aplicao que o utiliza para efetuar alguma ao customizada. Esse evento recebe em seu parmetro um objeto do tipo EventArrivedEventArgs, contendo informaes relacionadas ao evento que ocorreu. O cdigo abaixo exibe como implementar essa classe para monitorar os eventos:
VB.NET Imports System.Management Sub Main() Dim watcher As New ManagementEventWatcher( _ New WqlEventQuery( _ "SELECT * FROM __InstanceCreationEvent " & _ "WITHIN 1 WHERE TargetInstance ISA ""Win32_Process""")) AddHandler watcher.EventArrived, AddressOf EventArrived watcher.Start() System.Threading.Thread.Sleep(180000) watcher.Stop() End Sub Private Sub EventArrived(ByVal sender As Object, _ ByVal e As EventArrivedEventArgs) Console.WriteLine("Processo criado = " & _ DirectCast(e.NewEvent("TargetInstance"), ManagementBaseObject)("Caption")) End Sub C# using System.Management; static void Main(string[] args) {

30

Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes

31

ManagementEventWatcher watcher = new ManagementEventWatcher(new WqlEventQuery( @"SELECT * FROM __InstanceCreationEvent WITHIN 1 " + @"WHERE TargetInstance ISA ""Win32_Process""")); watcher.EventArrived += new EventArrivedEventHandler(EventArrived); watcher.Start(); System.Threading.Thread.Sleep(180000); watcher.Stop();

static void EventArrived(object sender, EventArrivedEventArgs e) { Console.WriteLine("Processo criado = " + ((ManagementBaseObject)e.NewEvent["TargetInstance"])["Caption"]); }

A partir do cdigo acima, instanciamos a classe ManagementEventWatcher para monitorar os processos que esto sendo criados. Neste momento, temos uma nova classe chamada WqlEventQuery, que uma classe que trabalha exclusivamente com eventos do WMI. A query informada no exemplo, monitora os processos que esto sendo criados. Em seguida, assinamos o evento EventArrived e, especificamos qual o procedimento que ser disparado quando o evento ocorrer. Finalmente, chamamos o mtodo Start do objeto ManagementEventWatcher para iniciar o monitoramento e, para fins de exemplo, suspendemos a execuo da thread atravs do mtodo Sleep, mantendo-a parada por 3 minutos. Nesse momento, todo e qualquer processo que for iniciado, como por exemplo, o bloco de notas, a calculadora do Windows, o evento EventArrived ser disparado e, no caso acima, uma mensagem contendo o nome do processo recm iniciado, ser escrita na tela. Ainda temos um outro namespace chamado System.Management.Instrumentation que disponibiliza um conjunto de classes para que as aplicaes, tambm construdas em .NET, possam passar para o Object Manager informaes a seu respeito, informaes quais, mais tarde sero acessadas pelas aplicaes de monitoramento, via classes disponveis dentro do namespace System.Management. Basicamente para expor informaes para o WMI para que aplicaes de monitoramento consumam, necessrio criarmos uma classe que herde de uma classe abstrata chamada BaseEvent. Essa classe abstrata implementa a Interface IEvent, qual possui um mtodo chamado Fire. Esse mtodo delega a chamada para a classe Instrumentation que, efetivamente invocar o evento e, conseqentemente, mandar para o WMI. Para j ilustrarmos, abaixo est somente a classe, que herda diretamente da classe BaseEvent e adiciona os propriedades necessrias que armazenaro as informaes que devem ser expostas ao WMI e tambm as aplicaes de monitoramento. Israel Aece | http://www.projetando.net

31

Capitulo 4 Monitoramento e depurao de aplicaes


VB.NET Imports System.Management.Instrumentation Public Class EventoAppTeste Inherits BaseEvent Private _codigo As Integer Private _nome As String

32

Public Sub New(ByVal codigo As Integer, ByVal nome As String) Me._codigo = codigo Me._nome = nome End Sub Public Property Codigo() As Integer Get Return Me._codigo End Get Set(ByVal value As Integer) Me._codigo = value End Set End Property Public Property Nome() As String Get Return Me._nome End Get Set(ByVal value As String) Me._nome = value End Set End Property End Class C# using System.Management.Instrumentation; public class EventoAppTeste : BaseEvent { private int _codigo; private string _nome; public EventoAppTeste(int codigo, string nome) { this._codigo = codigo; this._nome = nome; } public int Codigo {

32

Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes


get { } set { } }

33

return this._codigo;

this._codigo = value;

public string Nome { get { return this._codigo; } set { this._codigo = value; } }

A seguir, necessrio incluirmos dentro do Assembly que ter esse evento, um instalador para que o mesmo seja capaz de inserir dentro do WMI o tal evento. Para a criao deste instador, iremos criar uma classe que herda diretamente da classe DefaultManagementProjectInstaller. Essa classe herda da classe Installer e possui a implementao necessria para a instalao de um Assembly que possui informaes de instrumentao WMI. Somente a herana necessria e nenhum mtodo precisa ser sobrescrito. O cdigo abaixo mostra como cri-la:
VB.NET Imports System.ComponentModel Imports System.Management.Instrumentation <RunInstaller(True)> Public Class Instalador Inherits DefaultManagementProjectInstaller End Class C# using System.ComponentModel; using System.Management.Instrumentation; [RunInstaller(true)] public class Instalador : DefaultManagementProjectInstaller { }

33

Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes

34

Nota: Para saber mais sobre instaladores e sobre o atributo RunInstallerAttribute, consulte o Captulo 3. Ainda, neste mesmo Assembly necessrio adicionarmos um atributo chamado InstrumentedAttribute no arquivo AssemblyInfo. Esse atributo tambm est contido dentro do namespace System.Management.Instrumentation e indica que o Assembly possui informaes de instrumentao. O trecho de cdigo abaixo exibe a configurao deste atributo dentro do projeto onde o evento foi criado, informando em seu construtor o namespace que ser utilizado pela instrumentao:
VB.NET Imports System.Management.Instrumentation <Assembly: Instrumented("Root/Default")> C# using System.Management.Instrumentation; [assembly: Instrumented("Root/Default")]

Finalmente, para adicionarmos o evento dentro do WMI, necessrio instalarmos o Assembly, mais precisamente o instalador que est contido dentro dele. Para isso, temos duas possibilidades para instal-lo: 1. Empacotar em um arquivo MSI 2. Rodar o utilitrio installutil.exe Para fins de exemplo, iremos optar pela segunda opo:
C:\>installutil C:\App.exe ... ... ... The transacted install has completed.

Nota: Atente-se para abrir o Prompt do Visual Studio .NET ao invs do Prompt fornecido pelo Windows, para evitar escrever todo o caminho at o utilitrio. Quando o utilitrio installutil.exe encontrar o instalador dos eventos de WMI dentro do Assembly, ele far todo o trabalho necessrio para adicion-lo dentro da arquitetura do WMI e possibilitar outras aplicaes a consum-los. Uma vez criado os eventos dentro do WMI, o mesmo Assembly que o cria, geralmente o mesmo que o invoca (via mtodo Fire) para notificar o WMI. Sendo assim, para fazermos um teste, temos que criar Israel Aece | http://www.projetando.net 34

Capitulo 4 Monitoramento e depurao de aplicaes

35

instncias da classe EventoAppTeste, definirmos os valores das propriedades e invocar o mtodo Fire. O cdigo a seguir mostra um exemplo dentro de um lao For de 10 iteraes, suspendendo a thread por 5 segundos:
VB.NET For i As Integer = 0 To 10 Dim ev As New EventoAppTeste(I, "Nome " + i.ToString()) ev.Fire() System.Threading.Thread.Sleep(5000); Next C# for (int i = 0; i < 10; i++) { new EventoAppTeste(i, "Nome " + i.ToString()).Fire(); System.Threading.Thread.Sleep(5000); }

Esse lao far com que ele mande 10 notificaes do evento para o WMI e agora, compete a cada aplicao que quiser monitorar esses eventos, apanh-los e exibir da forma que achar mais conveniente. No nosso caso, utilizaremos o mesmo exemplo que analisamos um pouco mais acima, quando utilizamos a classe ManagementEventWatcher, mudando ligeiramente a query para apenas recuperarmos os eventos do tipo EventoAppTeste. O cdig abaixo mostra um exemplo na ntegra de um cliente que deseja recuperar um determinado tipo de evento:
VB.NET Imports System.Management Sub Main() Dim watcher As New ManagementEventWatcher("root/default", _ "SELECT * FROM EventoAppTeste") AddHandler watcher.EventArrived, AddressOf EventArrived watcher.Start() System.Threading.Thread.Sleep(180000) watcher.Stop() End Sub Private Sub EventArrived(ByVal sender As Object, _ ByVal e As EventArrivedEventArgs) Console.WriteLine(e.NewEvent.Properties("Codigo").Value) Console.WriteLine(e.NewEvent.Properties("Nome").Value) End Sub

35

Israel Aece | http://www.projetando.net

Capitulo 4 Monitoramento e depurao de aplicaes


C# using System.Management; static void Main(string[] args) { ManagementEventWatcher watcher = new ManagementEventWatcher("root/default", "SELECT * FROM EventoAppTeste"); watcher.EventArrived += new EventArrivedEventHandler(EventArrived); watcher.Start(); System.Threading.Thread.Sleep(180000); watcher.Stop();

36

static void EventArrived(object sender, EventArrivedEventArgs e) { Console.WriteLine(e.NewEvent.Properties["Codigo"].Value); Console.WriteLine(e.NewEvent.Properties["Nome"].Value); }

A nica mudana considervel neste cdigo em relao ao que vimos um pouco mais acima, com relao ao construtor da classe ManagementEventWatcher. No primeiro parmetro, ele recebe uma string que identifica o escopo (namespace) que o watcher dever monitorar; j o segundo parmetro, a query que vai definir qual evento dever ser monitorado e, se repararem, a clusula FROM recebe o nome do evento que criamos anteriormente, ou seja, EventoAppTeste.

36

Israel Aece | http://www.projetando.net

Captulo 5 Manipulando o sistema de arquivos


Captulo 5 Manipulando o sistema de arquivos Introduo

A maioria das aplicaes que existem atualmente sempre necessitam de, alguma forma, manipular arquivos que esto em algum local do disco. Sejam arquivos de configuraes, texto, XML, etc.. Geralmente as aplicaes tambm utilizam o sistema de arquivos para persistir alguns objetos para mais tarde por recuper-los e, para isso, podemos utilizar o sistema de arquivos. Alm disso, h uma srie de instituies, mais precisamente os bancos, utilizam arquivos para a comunicao entre o banco e os clientes. Com isso, o cliente precisa tanto gerar o arquivo para quando quiser enviar algo ao banco como ter a possibilidade de ler e interpretar o arquivo quando o banco disponibilizar o retorno. A finalidade deste captulo mostrar como utilizar as classes contidas dentro do .NET Framework para a manipulao de arquivos. Inicialmente falaremos sobre como recuperar as informaes a nvel de drive, arquivo e diretrios. Vamos verificar como proceder manipular um caminho at um diretrio ou arquivo. Para finalizar esta primeira parte, iremos por em funcionamento o objeto que a Microsoft disponibilizou para monitorar um determinado diretrio. J na segunda parte, vamos aprender como ler e salvar os dados a partir de streams. Alm disso, veremos as alternativas, agora embutidas, que permitem comprimir e descomprimir um arquivo. Finalmente, vamos abordar o uso de um objeto que existe desde a primeira verso do .NET Framework: a StringBuilder. Essa classe responsvel por manipular uma string de forma bem eficiente, principalmente quando se diz respeito a concatenao de strings. Extraindo informaes do sistema de arquivos a partir de classes fornecidas pelo .NET Framework O .NET Framework disponibiliza um namespace chamado System.IO que contm todas as classes necessrias para a manipulao de arquivos e diretrios. Algumas classes tambm fornecem suporte assncrono, o que permite ler dados de um arquivo sem a necessidade de congelar a tela que o usurio est utilizando. Veremos a partir de agora trs classes interessantes que so utilizadas para trabalhar com todas as espcies de objetos que temos a nvel de sistema de arquivos: Drive, Diretrio e Arquivo. Drivers A partir do .NET 2.0 temos uma classe chamada DriveInfo. Essa classe basicamente acessa informaes de um determinado drive. Essa classe possui vrios mtodos e propriedades para manipular um determinado drive do computador. Alm desses Israel Aece | http://www.projetando.net 1

Captulo 5 Manipulando o sistema de arquivos

membros de instncia, temos um mtodo esttico chamado GetDrives que retorna um array de objetos do tipo DriveInfo onde, cada um, representa um drive do computador onde a aplicao est sendo executada. Entre as propriedades da classe DriveInfo, temos: Propriedade AvaliableFreeSpace DriveFormat DriveType Descrio Indica a quantidade de espao disponvel no disco. Especifica o sistema de arquivo, como NTFS ou FAT32 Indica o tipo de drive, pode ser: CDRom Fixed Unknow Network NoRootDirectory Ram Removable Retorna um valor booleano indicando se o drive est pronto para ser acessado/lido. Nome do drive. Retorna o diretrio raiz do drive. Indica o total de espao disponvel no drive. Retorna o total de espao que o drive possui. Recupera o rtulo do volume do drive.

IsReady Name RootDirectory TotalFreeSpace TotalSize VolumeLabel

Para exemplificar o uso desta classe, podemos fazer como mostrado atravs do cdigo abaixo:
VB.NET Imports System.IO For Each d As DriveInfo In DriveInfo.GetDrivers() If d.IsReady Then Console.WriteLine(d.DriveType.ToString()) Console.WriteLine(d.Name) End If Next C# using System.IO; foreach (DriveInfo d in DriveInfo.GetDrives()) { if (d.IsReady) { Console.WriteLine(d.DriveType.ToString()); Console.WriteLine(d.Name);

Israel Aece | http://www.projetando.net

Captulo 5 Manipulando o sistema de arquivos


}

Diretrios Assim como a classe DriveInfo, tambm temos uma classe esttica chamada Directory que permite manipularmos os diretrios que existem na mquina em que a aplicao est sendo executada ou de algum outro local, dependendo da autorizao. Esta classe expe mtodos estticos para criar, mover, renomear e excluir diretrios. Alm disso, permite recuperar todos os seus subdiretrios e arquivos para manipul-los ou exib-los. Ainda h a classe DirectoryInfo que tambm permite executar as mesmas operaes que a classe Directory fornece, mas agora para um diretrio especfico. Se voc precisar invocar vrias propriedades e/ou mtodos relacionados a um diretrio, considere o uso desta classe em relao a classe Directory porque a checagem de segurana no ser sempre necessria. Caso o teu cenrio no seja esse, ento opte por invocar os mtodos estticos da classe Directory. Entre as propriedades disponveis pela classe DirectoryInfo, podemos citar as principais: Propriedade CreationTime FullName Name Parent Root Descrio Retorna a data de criao do diretrio. Retorna o caminho completo do diretrio, desde a sua raiz. Retorna o nome do diretrio. Retorna o diretrio que pai do diretrio corrente. Retorna o nvel mais alto onde, na maioria dos casos, o drive.

Para citar alguns exemplos do uso desta classes, veremos abaixo como criar, ler o contedo e apagar um determinado diretrio:
VB.NET Imports System.IO Dim path As String = "c:\Temp\ArquivosProcessados" Dim dir As DirectoryInfo = Nothing If Not Directory.Exists(path) Then dir = Directory.CreateDirectory(path) End If If Not IsNothing(dir) Then Sub-diretrios For Each subDir As DirectoryInfo in dir.GetDirectories() Console.WriteLine(subDir.Name) Next

Israel Aece | http://www.projetando.net

Captulo 5 Manipulando o sistema de arquivos


Arquivos For Each fileName As FileInfo in dir.GetFiles() Console.WriteLine(filename.Name) Next dir.Delete(True) End If C# using System.IO; string path = @"c:\Temp\ArquivosProcessados"; DirectoryInfo dir = null; if (!Directory.Exists(path)) { dir = Directory.CreateDirectory(path); } if (dir != null) { //Sub-diretrios foreach (DirectoryInfo subDir in dir.GetDirectories()) Console.WriteLine(subDir.Name); //Arquivos foreach (FileInfo fileName in dir.GetFiles()) Console.WriteLine(fileName.Name); } dir.Delete(true);

Como podemos ver, criamos o diretrio a partir do mtodo esttico CreateDirectory da classe Directory. Depois de criado, recuperamos a instncia do tipo DirectoryInfo que traz as informaes a respeito do diretrio recm criado. De posse desta instncia, podemos ter acesso a diversas propriedades e mtodos para manipular o diretrio. Se analisarmos o cdigo acima, utilizarmos dois mtodos: GetDirectories e GetFiles. O primeiro deles retorna um array de DirectoryInfo contendo todos os sub-diretrios de um diretrio qualquer. J o segundo tambm retorna um array de FileInfo contendo todos os arquivos de um determinado diretrio. Esse mtodo ainda possui um overload que permite informar qual tipo de arquivos que desejamos recuperar., como por exemplo: se desejarmos apenas recuperar os arquivos texto, ento poderamos fazer: dir.GetFiles(*.txt). Como esses mtodos so invocados a partir de uma instncia do tipo DirectoryInfo, todas as informaes so pertinentes ao diretrio nele informado. Finalmente chamamos o mtodo Delete para excluir o diretrio. O parmetro True que passamos para ele, indica que ser uma excluso recursiva, ou seja, tudo que estiver dentro dele, tambm ser excludo. Israel Aece | http://www.projetando.net

Captulo 5 Manipulando o sistema de arquivos

Nota: Quando invocamos os mtodos estticos GetDirectories e GetFiles da classe Directory, retornado um array de string contendo os nomes dos diretrios e arquivos, respectivamente. Arquivos De forma parecida aos anteriores, temos tambm duas classes para manipulao de arquivos: File e FileInfo. A classe File fornece mtodos estticos para as operaes mais tpicas com arquivos, como por exemplo, criao, cpia, excluso e a movimentao de arquivos. J a classe FileInfo traz informaes completas de um determinado arquivo, alm tambm, de fornecer as operaes tpicas e, como j era de se esperar, operando apenas com o arquivo que ela representa. Assim como a classe DirectoryInfo, utilize-a somente se precisar efetuar vrias operaes em cima deste arquivo. Do contrrio, opte pela uso da classe File que, para uma nica operao, mais performtica. Entre as propriedades disponveis pela classe FileInfo, podemos citar as principais: Propriedade CreationTime Directory DirectoryName Extension FullName IsReadOnly Length Name Descrio Retorna a data de criao do arquivo. Retorna uma instncia da classe DirectoryInfo que representa o diretrio em que o arquivo est contido. Retorna o caminho completo at o diretrio onde o arquifo est contido. Retorna a extenso do arquivo. Se o arquivo chamar Arquivo.txt essa propriedade retornar .txt. Resgata o caminho completo do arquivo. Retorna um valor booleano indicando se o arquivo ou no de somente leitura. Retorna o tamanho do arquivo. Recupera o nome do arquivo.

Para manipular os arquivos do disco utilizando essas classes, podemos analisar o cdigo abaixo que explifica o que falamos acima com um trecho de cdigo:
VB.NET Imports System.IO Dim fileName As String = "Arquivo.txt" If Not File.Exists(fileName) Then File.Create(fileName) Dim info As New FileInfo(fileName) Console.WriteLine(info.CreationTime)

Israel Aece | http://www.projetando.net

Captulo 5 Manipulando o sistema de arquivos


File.Delete(fileName) End If C# using System.IO; string fileName = "Arquivo.txt"; if (!File.Exists(fileName)) { File.Create(fileName); FileInfo info = new FileInfo(fileName); Console.WriteLine(info.CreationTime); } File.Delete(fileName);

Manipulando caminhos (paths) Muitas vezes precisamos manipular os caminhos fsicos que temos para atender uma determinada necessidade. Para isso, sempre manipulamos a string que contm o caminho completo, at chegarmos no valor que desejamos. Como isso muito trabalhoso e pode gerar alguns erros, a Microsoft decidiu criar uma classe esttica chamada Path. Essa classe encapsula todo o trabalho para manipular caminhos de arquivos e diretrios. Alm disso, as operaes so executadas para suportar multi-plataforma, ou seja, como isso pode variar de sistema operacional para sistema operacional, a classe Path se encarrega de retornar o valor correto baseando-se na plataforma que a aplicao est sendo executada. Para citar alguns mtodos, vamos nos restringir aos mais populares e, para uma melhor descrio e exemplos, consulte a documentao do Visual Studio .NET ou, se desejar, pode consultar online: http://www.msdn.com/library. Mtodo ChangeExtension Descrio Dado um arquivo com sua extenso e uma nova extenso, esse mtodo retorna uma nova string contendo o mesmo arquivo (com seu caminho), mas agora com a nova extenso informada. Permite combinar dois caminhos, fazendo todo o trabalho para manipulao dos separadores. Dado um caminho, ele retorna uma string contendo apenas o caminho at o ltimo diretrio. Dado um nome de arquivo junto com a sua extenso, ele retorna apenas a extenso do arquivo. Exemplo: Arquivo.txt retorna .txt. Israel Aece | http://www.projetando.net

Combine GetDirectoryName GetExtension

Captulo 5 Manipulando o sistema de arquivos


GetFileName

Dado um caminho completo at um determinado arquivo, retorna apenas o nome do arquivo junto com a sua extenso. GetFileNameWithoutExtension Dado um caminho completo at um determinado arquivo, retorna apenas o nome do arquivo, sem a extenso. GetRandomFileName Retorna um nome de arquivo aleatrio, podendo inclusive ser utilizado como nome para diretrios. Este mtodo no cria o arquivo/pasta fisicamente. GetTempFileName Cria fisicamente um arquivo vazio com um nome nico e de extenso .TMP, que retornado pelo mtodo. GetTempPath Retorna o caminho at o diretrio de sistema que a pasta temporria. Geralmente o caminho : C:\Documents and Settings\<Usuario>\Local Settings\Temp\ Retorna um valor booleano indicando se o caminho possui ou no uma extenso de arquivo.

HasExtension Monitoramento de diretrios

O namespace System.IO fornece uma classe um tanto quanto interessante. Ela chama-se FileSystemWatcher que tem a finalidade de monitorar um determinado diretrio e, qualquer mudana que nele acontea, a classe capaz de notificar a aplicao e, conseqentemente, voc pode tratar isso da forma que desejar. Essa classe recebe em seu construtor o caminho que o watcher ir monitorar. Entre os eventos que esta classe fornece temos: Created, Changed, Deleted e Renamed. O primeiro deles invocado pelo runtime quando um novo arquivo ou diretrio criado. O segundo disparado quando um arquivo ou diretrio alterado; j o evento Deleted disparado quando algum diretrio ou arquivo excludo e, finalmente, o evento Renamed, disparado quando algum diretrio ou arquivo renomeado. Uma propriedade tambm muito importante a propriedade EnableRaisingEvents. Essas propriedade recebe um valor booleano indicando se o watcher est ou no habilitado. Enquanto essa propriedade estiver definida como False, os eventos no sero disparados. Para exemplificar o uso desta classe, criaremos um wrapper para o mesmo que encapsular todo o trabalho que o FileSystemWatcher ir desempenhar dentro da aplicao. Esse criao deste wrapper mostrado abaixo:
VB.NET Imports System.IO Public Class FileWatcher

Israel Aece | http://www.projetando.net

Captulo 5 Manipulando o sistema de arquivos

Implements IDisposable Private _watcher As FileSystemWatcher Public Sub New(ByVal path As String) Me._watcher = New FileSystemWatcher(path) Me.InitializeEvents() Me._watcher.EnableRaisingEvents = True End Sub Private Sub InitializeEvents() AddHandler Me._watcher.Created, _ AddressOf Me._watcher_Created AddHandler Me._watcher.Changed, _ AddressOf Me._watcher_Changed AddHandler Me._watcher.Deleted, _ AddressOf Me._watcher_Deleted AddHandler Me._watcher.Renamed, _ AddressOf Me._watcher_Renamed End Sub Private Sub _watcher_Created(ByVal sender As Object, _ ByVal e As FileSystemEventArgs) Me.Show(e) End Sub Private Sub _watcher_Renamed(ByVal sender As Object, _ ByVal e As RenamedEventArgs) Me.Show(e) End Sub Private Sub _watcher_Deleted(ByVal sender As Object, _ ByVal e As FileSystemEventArgs) Me.Show(e) End Sub Private Sub _watcher_Changed(ByVal sender As Object, _ ByVal e As FileSystemEventArgs) Me.Show(e) End Sub Private Sub Show(ByVal e As FileSystemEventArgs) Console.WriteLine(e.ChangeType.ToString()) Console.WriteLine(e.FullPath) Console.WriteLine("---------------------") End Sub Public Sub Dispose() Implements IDisposable.Dispose

Israel Aece | http://www.projetando.net

Captulo 5 Manipulando o sistema de arquivos


If Not IsNothing(Me._watcher) Then Me._watcher.Dispose() End If End Sub End Class C# using System.IO; public class FileWatcher : IDisposable { private FileSystemWatcher _watcher; public FileWatcher(string path) { this._watcher = new FileSystemWatcher(path); this.InitializeEvents(); this._watcher.EnableRaisingEvents = true;

private void InitializeEvents() { this._watcher.Created += new FileSystemEventHandler(_watcher_Created); this._watcher.Changed += new FileSystemEventHandler(_watcher_Changed); this._watcher.Deleted += new FileSystemEventHandler(_watcher_Deleted); this._watcher.Renamed += new RenamedEventHandler(_watcher_Renamed); } void _watcher_Created(object sender, FileSystemEventArgs e) { this.Show(e); } void _watcher_Renamed(object sender, RenamedEventArgs e) { this.Show(e); } void _watcher_Deleted(object sender, FileSystemEventArgs e) { this.Show(e); } void _watcher_Changed(object sender, FileSystemEventArgs e)

Israel Aece | http://www.projetando.net

Captulo 5 Manipulando o sistema de arquivos


{ }

10

this.Show(e);

private void Show(FileSystemEventArgs e) { Console.WriteLine(e.ChangeType.ToString()); Console.WriteLine(e.FullPath); Console.WriteLine("---------------------"); } public void Dispose() { if (this._watcher != null) this._watcher.Dispose(); }

Como podemos analisar, assinamos todos os eventos e, quando uma determinada ao acontecer, os respectivos eventos sero disparados. Esse wrapper serve apenas para encapsular todo o trabalho que o FileSystemWatcher ir desenvolver na aplicao e que no seria obrigatoriamente necessrio cri-lo para o FileSystemWatcher funcionar. Finalmente, para utilizar este wrapper, devemos fazer:
VB.NET Using watcher As New FileWatcher("c:\Temp\") While (True) End While End Using C# using (FileWatcher w = new FileWatcher(@"c:\Temp\")) { while (true); }

A classe criada dentro de um bloco Using para garantir que o mtodo Dispose seja executado mesmo que um erro ocorra. Para o exemplo, vamos criar, renomear, alterar e excluir um arquivo dentro do diretrio c:\Temp. A imagem abaixo mostra os logs que foram efetuados a partir do wrapper que criamos acima: 10

Israel Aece | http://www.projetando.net

Captulo 5 Manipulando o sistema de arquivos

11

Imagem 5.1 Output do wrapper FileWatcher Lendo e gravando dados em arquivos Quando estamos desenvolvendo uma aplicao que permite ler os dados de um repositrio de dados, como por exemplo, o banco de dados, arquivos do disco ou qualquer outro recurso que seja capaz de persistir os dados, precisamos de alguma forma, extrair e interpretar essas informaes. Um stream serve como condutor entre o cdigo da aplicao e a fonte (arquivo, banco de dados, memria, etc.). O processo de mover os dados (bytes) entre a fonte de dados e a aplicao chamada de streaming. O .NET Framework possui vrias classes que permitem manipular os mais diversos tipos de streams. Essas classes esto contidas dentro do namespace System.IO. Dentro deste namespace temos a classe Stream. Essa a classe base (e tambm abstrata) para todos os tipos de streams. Atravs de streams podemos realizar dois tipos de operaes: 1. Leitura de dados trata-se de transferir os valores que esto na fonte para um tipo de dado estruturado, dentro da aplicao. 2. Escrita de dados em streams, que permite voc enviar valores para serem gravados no stream. A classe abstrata Stream fornece vrios mtodos interessantes, tambm abstratos, que devem ser obrigatoriamente implementados nas classes derivadas e, entre eles esto: Membro CanReader CanWriter CanSeek Descrio Indica se o stream suporta ou no leitura de dados. Indica se o stream suporta ou no a escrita de dados. Indica se o stream permite ou no manter e manipular um ponteiro da posio dentro do stream. Israel Aece | http://www.projetando.net 11

Captulo 5 Manipulando o sistema de arquivos


Length Position Read Write Seek Flush Close

12

Retorna o comprimento do stream em bytes. Indica a posio do ponteiro dentro do stream. Efetua a leitura de uma sria de bytes do stream. Grava uma sria de bytes dentro do stream. Move o ponteiro do stream para um determinada posio. Quando invocado, esse mtodo salva todo o contedo do stream em sua fonte. Fecha o stream. Quando esse mtodo chamado, todo oseu contedo salvo na fonte.

Essa classe abstrata Stream implementada em diversos outros tipos de streams e, entre eles, temos: FileStream, MemoryStream e BufferedStream. A primeira delas, FileStream, um wrapper para um stream que manipula um determinado arquivo, j dando suporte a operaes sncronas e assncronas. Essa classe utilizada para ler, salvar, abrir e fechar arquivos do disco, lembrando que ele armazena o contedo em buffer para ter um ganho de performance. A classe File (que vimos um pouco mais acima), fornece alguns mtodos que, ao serem executados, retornam um stream do tipo FileStream. Entre esses mtodos temos: Create, Open, OpenRead e OpenWrite. Para utilizar a classe FileStream, podemos fazer:
VB.NET Imports System.IO Imports System.Text Dim conteudo As String = "Utilizando o FileStream via VB." Dim bytes() As Byte = Encoding.Default.GetBytes(conteudo) Using stream As FileStream = File.Create("Teste.txt") stream.Write(bytes, 0, bytes.Length) End Using C# using System.IO; using System.Text; string conteudo = "Utilizando o FileStream via C#."; byte[] bytes = Encoding.Default.GetBytes(conteudo); using (FileStream stream = File.Create("Teste.txt")) { stream.Write(bytes, 0, bytes.Length); }

12

Israel Aece | http://www.projetando.net

Captulo 5 Manipulando o sistema de arquivos

13

Via mtodo esttico Create da classe File, criamos um arquivo chamado Teste.txt e atribuimos o retorno deste mtodo, que ser um objeto stream do tipo FileStream. Notem que isso est sendo feito dentro de um bloco using que, mesmo que algum erro acontea entre esse bloco, o stream ser fechado de forma segura, invocando o mtodo Dispose automaticamente. Depois disso, utilizamos o mtodo Write que escreve um contedo sincronamente no arquivo. Nota: A tcnica do bloco using ser utilizada daqui para frente. Dando seqencia, vamos falar um pouco sobre a classe MemoryStream. Como o prprio nome diz, essa classe cria um stream que tem como repositrio, a memria e, sendo assim, quando a aplicao finalizada, esses valores sero perdidos, no tendo uma forma de persitncia fsica. Apesar de deficincia, ela tem um timo benefcio, que justamente o armazenamento na memria, que possibilita um acesso bem mais rpido que o sistema de arquivo, FileStream. Como essa classe voltil, o interessante utiliz-la para manipular dados temporrios, que no exigem serem persistidos. Ela tambm estende a classe Stream, fornecendo suporte a chamadas sncronas e assncronas. A utilizao dessa classe bem semelhante ao que vimos um pouco acima com o FileStream. O trecho de cdigo abaixo exemplifica o uso dela:
VB.NET Imports System.IO Imports System.Text Dim conteudo As String = "Utilizando o FileStream via VB." Dim bytes() As Byte = Encoding.Default.GetBytes(conteudo) Dim length As Integer = bytes.Length Using ms As New MemoryStream(bytes) ms.Write(bytes, 0, length) ms.Seek(0, SeekOrigin.Begin) Dim output() As Byte = New Byte(length) {} ms.Read(output, 0, length) Console.WriteLine(Encoding.Default.GetString(output)) End Using C# using System.IO; using System.Text; string conteudo = "Utilizando o FileStream via C#."; byte[] bytes = Encoding.Default.GetBytes(conteudo); int length = bytes.Length;

13

Israel Aece | http://www.projetando.net

Captulo 5 Manipulando o sistema de arquivos


using (MemoryStream ms = new MemoryStream(length)) { ms.Write(bytes, 0, length); ms.Seek(0, SeekOrigin.Begin); byte[] output = new byte[length]; ms.Read(output, 0, length); Console.WriteLine(Encoding.Default.GetString(output));

14

Apesar de ser bem semelhante ao FileStream, h um detalhe bem interessante que precisamos analisar. Esse detalhe com relao ao construtor da classe MemoryStream. H um overload que permite passar um array de bytes. Isso evitaria de chamar o mtodo Write e Seek para escrever o contedo e retornar a posio do ponteiro para o nicio, pois o cdigo interno ao construtor se encarregaria de realizar todas essas tarefas. Ainda descendendo da classe Stream, temos a classe BufferedStream. Buffer um bloco de bytes que residem na memria, usado para efetuar o cache de dados para evitar, a todo momento, fazer chamadas ao sistema operacional e, conseqentemente, melhorar a performance. O buffer pode ser usado para leitura ou para gravao, mas nunca simultneamente. A classe BufferedStream possui uma layer de buffer e tambm responsvel por gerenci-la. O exemplo abaixo exibe como criar um arquivo atravs do mtodo Create da classe File e atribuir o seu retorno, um objeto do tipo FileStream, a um BufferedStream:
VB.NET Imports System.IO Using stream As New BufferedStream(File.Create("Teste.txt")) realizao das operaes End Using C# using System.IO; using (BufferedStream stream BufferedStream(File.Create("Teste.txt"))) { // realizao das operaes } = new

Alm desses streams que vimos at o momento, ainda temos ainda dois streams que so exclusivos para trabalhar com caracteres de entrada em um encoding especfico. O primeiro deles o StreamReader. Ele geralmente utilizado para ler linhas de informaes de um arquivo texto qualquer. A utilizao desta classe bem simples, ou Israel Aece | http://www.projetando.net

14

Captulo 5 Manipulando o sistema de arquivos

15

seja, precisamos somente no construtor dela, passar o caminho do arquivo que desejamos ler e, em seguida, invocar o mtodo ReadLine ou o mtodo ReadToEnd. A diferena entre os dois que o primeiro l apenas uma linha por vez, o que necessitaria voc coloc-lo dentro de um loop para ler todas as linhas do arquivo; j o segundo, em um nico mtodo, retorna todo o contedo do arquivo. O cdigo abaixo ilustra o uso desta classe:
VB.NET Imports System.IO Using stream As New StreamReader("c:\Temp\Teste.txt") Console.WriteLine(stream.ReadToEnd()) End Using C# using System.IO; using (StreamReader stream = ("c:\Temp\Teste.txt")) { Console.WriteLine(stream.ReadToEnd()); } new StreamReader

J a classe corresponde ao StreamReader para gravao a classe StreamWriter. Trabalhamos basicamente da mesma forma: passamos para o construtor o caminho e nome do arquivo que desejamos salvar o contedo e, via mtodo Write ou WrilteLine, vamos adicionando o contedo ao arquivo, conforme o exemplo abaixo:
VB.NET Imports System.IO Using stream As New StreamWriter("c:\Temp\Teste.txt") stream.WriteLine("Gravando via StreamWriter no VB.") End Using C# using System.IO; using (StreamWriter stream = new StreamWriter ("c:\Temp\Teste.txt")) { stream.WriteLine("Gravando via StreamWriter no C#."); }

15

Israel Aece | http://www.projetando.net

Captulo 5 Manipulando o sistema de arquivos

16

Finalmente, temos as classes BinaryReader e BinaryWriter. Essas duas classes servem para ler e gravar tipos de dados primitivos como valores binrios em um encoding especfico. A gravao de dados via BinaryWriter tranquila, pois informamos o stream (ou o arquivo) em que os dados sero gravados e, em seguida, invocamos o mtodo Write que sobrecarregado, possuindo sobrecargas para os mais diversos tipos de dados que o .NET Framework suporta. A classe BinaryReader, que l os dados de um determinado stream, precisa de alguns cuidados a parte. Primeiramente precisamos invocar o mtodo PeekChar para que o BinaryReader, que retorna o prximo caracter disponvel mas no avana a posio do ponteiro. A partir de agora, voc chama os mtodos respectivos a cada tipo que voc salvo, exemplo: suponhamos que voc criou um inteiro e uma string, logo, deve invocar os mtodo ReadInt32 e ReadString, sucessivamente. Todos os mtodos ReadXXX, alm de retornar o valor correspondente, tambm responsvel por avanar a posio do ponteiro interno do stream. Para exemplificar o uso do que vimos aqui sobre o BinaryWriter e BinaryReader, veja o cdigo abaixo:
VB.NET Imports System.IO Using bw As New BinaryWriter(File.Create("Teste.bin")) bw.Write(123) bw.Write("VB.NET e C#") bw.Write(true) bw.Write(false) bw.Write(1.290) End Using Using br As New BinaryReader(File.Open("Teste.bin", FileMode.Open)) If Not br.PeekChar() = -1 Then Console.WriteLine(br.ReadInt32()) Console.WriteLine(br.ReadString()) Console.WriteLine(br.ReadBoolean()) Console.WriteLine(br.ReadBoolean()) Console.WriteLine(br.ReadDouble()) End If End Using C# using System.IO; using (BinaryWriter bw BinaryWriter(File.Create("Teste.bin"))) { bw.Write(123); bw.Write("VB.NET e C#"); = new

16

Israel Aece | http://www.projetando.net

Captulo 5 Manipulando o sistema de arquivos


bw.Write(true); bw.Write(false); bw.Write(1.290);

17

using (BinaryReader br = new BinaryReader(File.Open("Teste.bin", FileMode.Open))) { if (br.PeekChar() != -1) { Console.WriteLine(br.ReadInt32()); Console.WriteLine(br.ReadString()); Console.WriteLine(br.ReadBoolean()); Console.WriteLine(br.ReadBoolean()); Console.WriteLine(br.ReadDouble()); } }

A imagem abaixo trata-se do arquivo Teste.txt com os valores salvos em formato binrio:

Imagem 5.2 Arquivo Teste.bin com seus valores binrios Compresso de dados Quando precisvamos comprimir ou descomprimir arquivos (streams) nas verses 1.x do .NET Framework tnhamos que recorrer a bibliotecas de terceiros para isso. A Microsoft se preocupou com essa necessidade e decidiu incorporar essa funcionalidade dentro do .NET Framework 2.0. com isso que surge um novo namespace chamado System.IO.Compression, fornecendo classes que efetuam compresso e descompresso de streams. Atualmente temos duas classes contidas dentro deste namespace (que herdam diretamente da classe Stream): DeflateStream e GZipStream. A classe DeflateStream fornece mtodos e propriedades para compresso e descompresso de streams utilizando o algoritmo Deflate. Essa classe no permite a compresso de arquivos com um tamanho maior que 4GB.

17

Israel Aece | http://www.projetando.net

Captulo 5 Manipulando o sistema de arquivos

18

J a classe GZipStream tambm utiliza o mesmo algoritmo da classe DeflateStream, mas pode ser estendido para utilizar outros formatos de compresso. Basicamente, a classe GZipStream um wrapper para um DeflateStream, incluindo header, body e footer onde, a seo header contm informaes de metadados a respeito do contedo comprimido, o body a seo onde o contedo comprimido armazenado e a seo footer permite incluir valores de verificaes de redundncia cclica para detectar o corrompimento dos dados. Alm disso, essa classe ainda possibilita detectar erros durante a compresso e, se desejar compartilhar os dados comprimidos com outros programas, utilize o GZipStream ao invs da classe DeflateStream. Essa classe no permite a descompresso de arquivos com um tamanho maior que 4GB. Exemplos: Como o cdigo para exemplificar o uso destas classes um pouco extenso, ele no ser colocado aqui, mas voc pode analisar a utilizao deles na aplicao de demonstrao. Manipulao de Strings Utilizao da classe StringBuilder Quando manipulamos strings dentro de uma aplicao .NET elas so mapeadas para a classe System.String. Essas strings so imutveis, ou seja, quando fazemos uma operao onde alteramos o seu valor ou at mesmo chamando um mtodo dela, como por exemplo, o mtodo Substring, uma nova string gerada. Outro ponto importante quando estamos falando de concatenao de strings. Geralmente utilizamos isso quando precisamos montar um valor proveniente de uma base de dados, ou at mesmo de uma construo mais complexa. Popularmos essas concatenaes so realizadas dentro de loops que, se no tomarmos um pouco de cuidado, a performance pode ser comprometida. A questo da performance tambm bastante importante quando precisamos manipular a string, como por exemplo, inserindo um valor em um local especificado, remover uma string ou at mesmo trocar uma string por outra dentro de uma string maior. Tudo isso que as vezes pode ser muito custoso, a Microsoft se antecipou e criou uma classe para manipular de forma mais gil as strings. Trata-se da classe StringBuilder, que est contida dentro do namespace System.Text. Esse classe representa agora um string mutvel, ou seja, ele pode sofrer inseres de novas strings, remov-las e trocar caracteres. Essa classe fornece uma poro de mtodos que permitem manipular a string que est contida dentro da mesma. Entre os principais mtodos e propriedades temos: Membro Capacity Descrio Define a quantidade mxima de memria alocada pela instncia corrente. A capacidade pode ser diminuida, mas no pode ser menor que o valor especificado pela propriedade Length e tambm no pode ser maior que o valor especificado pela propriedade MaxCapacity. Israel Aece | http://www.projetando.net 18

Captulo 5 Manipulando o sistema de arquivos

19

Chars Length MaxCapacity Append

AppendFormat Insert Remove Replace ToString

O valor padro para essa propriedade 16. Ao acrescentar caracteres dentro dele, o StringBuilder verifica se o array interno que armazena os caracteres est tentando aumentar alm da sua capacidade. Se sim, automaticamente dobrar o tamanho dessa capacidade, alocando um novo array e copiando os caracteres anteriores para este novo e, conseqentemente, o array original ser descartado. Esse aumento dinmico prejudica a performance e, sendo assim, tente definir uma capacidade para evitar isso. Baseando-se em um ndice, retorna um tipo Char que indica o caracter que est dentro da string. Retorna um nmero inteiro que indica o tamanho da string. Define a quantidade mxima de caracteres que a instncia pode armazenar. Esse um mtodo que possui vrios overloads, basicamente suportando em cada overload um tipo de dado diferente. Quando esse mtodo invocado, o valor passado para o mesmo ser adicionado ao fim da string. Permite adicionar um valor no final da string, mas a diferena em relao ao mtodo Append que permite formatar o valor antes dele ser efetivamente adicionado. Permite adicionar um valor em uma posio especfica. Assim como o mtodo Append, ele possui vrios overloads que permitem passamos os mais diversos tipos para ser adicionado. Este mtodo aceita dois nmeros inteiros como parmetro. O primeiro deles a posio inicial dentro da string e, o segundo, a quantidade de caracteres que deseja remover da string. Modifica todas as ocorrncias que forem encontradas dentro da string por um outro valor. Esse mtodo permite converter a instncia corrente da classe StringBuilder em uma string.

Para exemplificar o uso desta classe e de seus membros, o cdigo abaixo manipula uma string que est contida dentro da classe StringBuilder:
VB.NET Imports System.Text Dim sb As New StringBuilder(16, 150) Dim versoesNET As Object() = {"1.0", "1.1", "2.0"} sb.Append("Utilizando a Classe String Builder.") sb.AppendFormat(" Ela disponilizada nas seguintes verses: {0}, {1} e {2}.", versoesNET) sb.Insert(34, " do namespace System.Text")

19

Israel Aece | http://www.projetando.net

Captulo 5 Manipulando o sistema de arquivos


sb.Remove(0, 13) Console.WriteLine(sb.ToString()) C# using System.Text; StringBuilder sb = new StringBuilder(16, 150); object[] versoesNET = { "1.0", "1.1", "2.0" };

20

sb.Append("Utilizando a Classe String Builder."); sb.AppendFormat(" Ela disponilizada nas seguintes verses: {0}, {1} e {2}.", versoesNET); sb.Insert(34, " do namespace System.Text"); sb.Remove(0, 13); Console.WriteLine(sb.ToString());

Inicialmente criamos um objeto do tipo StringBuilder, definindo a sua capacidade de memria alocada como 16 e a capacidade mxima de caracteres que ele pode suportar como 150. Em seguida, utilizamos os mtodos que vimos detalhadamente na tabela, um pouco mais acima, suas respectivas funcionalidades. O resultado para ambos os cdigos :
Classe String Builder do namespace System.Text. disponilizada nas seguintes verses: 1.0, 1.1 e 2.0. Ela

Consideraes de Performance Assim como h o mtodo Append para a classe StringBuilder, existe o mtodo Concat para a classe String. A finalidade de ambos adicionar um novo contedo ao fim de uma determinada string. A principal diferena entre eles que o concatenao via classe String sempre retorna uma nova string pois, como vimos acima, as strings so imutveis. J a classe StringBuilder alterar o seu contedo interno de forma mais performtica mas, temos um overhead a mais que justamente a criao do objeto StringBuilder. Sendo assim, quantas vezes precisamos chamar o mtodo Append para compensar a construo e o uso do objeto StringBuilder? Pois bem, existem uma poro de informaes a respeito e abaixo tentaremos elencar os mais tpicos, lembrando que isso pode variar de um cenrio ao outro. Se voc no tiver idia do tamanho final da string, ento utilize o StringBuilder se tiver ao menos sete concatenaes 20

Israel Aece | http://www.projetando.net

Captulo 5 Manipulando o sistema de arquivos


21

Se voc puder antecipar o tamanho da string (em pelo menos 30%), ento utilize o StringBuilder se tiver ao menos cinco concatenaes Se voc puder antecipar precisamente o resultado da string, ento utilize o StringBuilder se voc tiver ao menos trs concatenaes Sem qualquer uma das condies atendidas, StringBuilder mais rpido se voc tiver ao menos trs concatenaes

Regular Expressions Expresses regulares forencem uma forma potente, eficiente e flexvel para prcessar textos. Trata-se uma linguagem que foi criada e otimizada para manipular textos e, atravs delas, voc pode facilmente extrair, ler, editar, trocar, excluir substrings de um determinado texto. Elas so ferramentas indispensveis quando utilizamos arquivos de logs, processamento de arquivos HTML, validaes de informaes que o usurio fornece para a aplicao, entre outras inmeras necessidades. As expresses regulares consistem em dois tipos de caracteres: literal (que so os caracteres normais) e os metacaracteres (smbolos). O conjunto desses caracteres do as expresses regulares todo o poder de precessamento do texto. Para mostrar um exemplo de expresso regular, podemos citar: \s2000. Quando ela aplicada em um texto, ela ir procurar por todas as ocorrncias com o valor 2000 que esto precedidos de qualquer caracter de espao, podendo ser um espao simples ou um TAB. Como manipular expresses regulares no uma tarefa fcil, o .NET Framework fornece uma gama de classes que permitem manipular as expresses regulares de forma fcil. Todas as classes esto contidas dentro de um namespace chamado System.Text.RegularExpressions e, entre as classes disponveis temos: RegEx, Match , MatchCollection, Group, GroupCollection, Capture e CaptureCollection. Ainda temos um delegate chamado MatchEvaluator que representa um mtodo que ser invocado quando um determinado valor encontrado dentro de um texto durante o processo de replace. Mas antes de analisar as classes que o .NET Framework fornece, precisamos entender um pouco mais sobre as expresses regulares e como constru-las. Vale lembrar que no iremos nos aprofundar muito porque esse assunto muito mais extenso do que veremos aqui. Atravs da tabela abaixo, vamos analisar, inicialmente, os metacaracteres e o que eles representam dentro de uma expresso regular: Metacaracter Descrio . Estipula qualquer caracter. Exemplo: 1. n.o: A expresso ir procurar por essa palavra, no importando que caracter esteja substituindo o ponto. Israel Aece | http://www.projetando.net Grupo Representantes 21

Captulo 5 Manipulando o sistema de arquivos


[]

22

Defina uma lista de caracteres permitidos. Permite Representantes tambm definir um intervalo de caracteres. Exemplo: 2. n[a]o: Se executando a expresso com o ponto para procurarmos, por exemplo, pela palavra no, se existir palavras no texto que atendam aos caracteres n e o, eles tambm sero retornados, exemplo: teclando, expandindo. Como a idia recuperar apenas a palavra no, independente de ter colocado o til ou no, pode optar por essa expresso regular. 3. [0-9]: Permite somente nmeros que estiverem entre 0 e 9. Representantes Defina uma lista de caracteres proibidos. Exemplo: 4. [^0-9]: A partir desta expresso regular, voc est querendo procurar por todos os caracteres, com exceo dos nmeros de 0 a 9. 5. [^a-f]: Neste, voc est procurando por todos os caracteres, com exceo do intervalo de a at f (minsculo). Quantificadores Zero ou um (opcional). Exemplo: 6. bater?: Essa expresso indica que o r ser opcional e, sendo assim as palavras bate e bater so perfeitamente vlidas. Quantificadores Zero, um ou mais. Exemplo: 7. 10*: O astersco no se importa com o valor, ou seja, pode ter, no ter ou pode ter inmeras ocorrncias. No caso mostrado no exemplo, todos os valores que contemplem ou no o zero ser retornado, tipo: 1, 100, 1000, 10000, etc. Mas o 2 no se enquadrara. Quantificadores Um ou mais. Exemplo: 8. 10+: Neste caso, o zero precisa ocorrer pelo menos uma vez. Sendo assim, 10, 100, Israel Aece | http://www.projetando.net

[^]

22

Captulo 5 Manipulando o sistema de arquivos

23

{n,m}

1000, etc. seria retornado mas, 1 no se enquadra. Quantificadores De x at m. Exemplo: 9. [a-g]{2,4}: Permite encontrar dentro de uma string os caracteres de a at g que contenham no mnimo dois e no mxima quatro ocorrncias. ncoras Incio da linha. Exemplo: 10. ^[a-z]: Neste cenrio, a finalidade do acento circunflexo apenas de indicar o nicio da linha e, nesta expresso regular, estamos procurando por linhas que comeam com caracteres de a at z. ncoras Fim da linha. Exemplo: 11. ^$: Essa expresso regular contempla uma linha vazia. ncoras nicio ou fim de uma palavra. Exemplo: 12. \ba: Ir retornar todas as palavras que comea com a letra a. 13. a\b: Ir retornar todas as palavras que acaba com a letra a. Define um caracter como literal. Exemplo: 14. [0-9]\.[0-9]: Neste caso, o metacaracter no exercer a sua funcionalidade dentro do mundo das expresses regulares, sendo agora, um caracter normal. Ou um ou outro. Exemplo: 15. item1|item2: Permite encontrar um ou outro item especificado na criao. Delimita um grupo. Exemplo: 16. ([0-9]){2}: Todo o contedo definido Israel Aece | http://www.projetando.net

\b

()

23

Captulo 5 Manipulando o sistema de arquivos


dentro de um grupo so encarados como sendo um nico bloco na expresso e, no nosso exemplo, ele trar todos os nmeros que estiverem separados de dois em dois, ou seja, dado uma string do tipo 1234 567 89023, os valores retornados so: 12, 34, 56, 89 e 02. Retrovisores. Exemplo: 17. ([a-z]+)\1: Os retrovisores permitem recuperarmos informaes repetidas. Eles so utilizados em conjunto com os grupos e repetem o resultado (no a expresso) do grupo, ou seja, para o exemplo acima, o grupo procura por todas as letras entre a e z e, em conjunto com o \1, retornar o apenas os casos onde a letra seguinte a mesma definida pelo grupo. Para o exemplo ahjkko ppoijj yytrwwul ppmnbbvf ele retornar: kk, pp, jj, yy, ww, pp, bb.

24

\1...\9

Ainda existem vrios outros caracteres que utilizamos em expresses regulares, mas no vamos abordar todos aqui justamente por no ser o foco do captulo. A seguir, veremos as classes que o namespace System.Text.RegularExpressions fornece para manipular strings e regular expressions. Assim como os caracteres, no analisaremos as classes fornecidas pelo namespace, pois como j sabemos, no foco do captulo e, vamos analisar somente as principais classes necessrias para tratarmos expresses regulares. Para iniciarmos, vamos analisar a classe RegEx. Essa classe representa uma expresso regular imutvel, ou seja, de somente leitura, fornecendo mtodos estticos que permitem a anlise de expresses regulares sem a necessidade de criar a instncia da classe. O exemplo abaixo mostra como devemos proceder para utiliz-la. A expresso regular a seguir, verifica se o valor informado ou no um nmero:
VB.NET Imports System.Text.RegularExpressions Dim numero1 As String = "123a" Dim numero2 As String = "456" Console.WriteLine(Regex.IsMatch(numero1, "^[0-9]+$")) Console.WriteLine(Regex.IsMatch(numero2, "^[0-9]+$")) Output False

24

Israel Aece | http://www.projetando.net

Captulo 5 Manipulando o sistema de arquivos


True C# using System.Text.RegularExpressions; string numero1 = "123a"; string numero2 = "456"; Console.WriteLine(Regex.IsMatch(numero1, "^[0-9]+$")); Console.WriteLine(Regex.IsMatch(numero2, "^[0-9]+$")); //Output False True

25

Neste momento apresentaremos uma nova classe chamada Match. Essa representa o resultado de uma anlise de expresso regular que retornado atravs do mtodo Match da classe RegEx. O exemplo abaixo ilustra o uso desta classe:
VB.NET Imports System.Text.RegularExpressions Dim numero1 As String = "123" Dim r As New Regex("^[0-9]+$") Dim match As Match = r.Match(numero1) Console.WriteLine(match.Success) Console.WriteLine(match.Value) Console.WriteLine(match.Length) Output True 123 3 C# using System.Text.RegularExpressions; string numero1 = "123"; Regex r = new Regex("^[0-9]+$"); Match match = r.Match(numero1); Console.WriteLine(match.Success); Console.WriteLine(match.Value); Console.WriteLine(match.Length); //Output True

25

Israel Aece | http://www.projetando.net

Captulo 5 Manipulando o sistema de arquivos


123 3

26

Mudamos ligeiramente o cdigo para anlise da expresso regular. Ao invs de chamarmos o mtodo esttico Match, optamos por criar a instncia da classe RegEx, passando em seu construtor a expresso que ser aplicada a string. Criamos um objeto do tipo Match e atribuimos a ele o retorno do mtodo Match da classe RegEx. A classe Match possui algumas informaes importantes a respeito do resultado da anlise. Abaixo esto listados as principais propriedades: Propriedade Index Length Success Value Descrio Retorna um nmero inteiro indicando a posio onde o primeiro caracter foi encontrado. Retorna um nmero inteiro indicando a posio inicial da substring. Atravs de um valor booleano, indica se foi a expresso foi ou no satisfeita. Retorna a substring que foi encontrado a partir da expresso regular.

Para encerrar a seo sobre expresses regulares e as classes do .NET Framework que as manipulam, vamos analisar a classe MatchCollection que, como o prprio nome diz, trata-se de uma coleo de objetos do tipo Match. Nos exemplos que vimos na tabela de metacaracteres h a possibilidade de retornar vrios resultados. Sendo assim, utilizaremos um novo mtodo da classe RegEx que chamado de Matches. Lembre-se de que tambm existe esse mesmo mtodo em sua forma esttica, que no necessita da instncia da classe e que voltaremos a utiliz-lo no exemplo a seguir:
VB.NET Imports System.Text.RegularExpressions Dim coll As MatchCollection = _ Regex.Matches("1234 567 89023 2", "([0-9]){3}") For Each m As Match In coll Console.WriteLine(m.Value) Next C# using System.Text.RegularExpressions; MatchCollection coll = Regex.Matches("1234 567 89023 2", "([0-9]){3}"); foreach (Match m in coll) Console.WriteLine(m.Value);

26

Israel Aece | http://www.projetando.net

Captulo 5 Manipulando o sistema de arquivos

27

Para ambos os cdigos, o resultado o seguinte:


123 567 890

27

Israel Aece | http://www.projetando.net

Captulo 6 - Serializao
Captulo 6 Serializao Introduo

Para enviar objetos de um local para outro, preciso que voc converta o objeto em um determinado formato. Quando estamos desenvolvendo uma aplicao, muito comum precisarmos transferir dados desta aplicao para uma outra qualquer. Neste momento precisamos fazer uso de um processo, chamado de serializao onde o objeto que dever ser transferido ser persistido em um determinado formato e, em seguida, ser enviado para o destino. Quando o destino recebe a informao, antes de a processar, ele efetua o processo de deserializao onde, converteremos esse pacote em um objeto conhecido. Esse processo chama-se deserializao. O que Serializao? Serializao (serialization) o processo onde voc converte um objeto em um stream de bytes para persist-lo na memria, em um banco de dados ou em arquivo. A idia salvar o estado do objeto para que a aplicao seja capaz de restaurar o estado atual do objeto quando necessrio. O processo inverso chamado de deserializao (deserialization). O .NET Framework 2.0 j fornece classes e Interfaces para persistir os objetos em formato binrio e SOAP e, alm disso, traz tambm classes e Interfaces para que possa persistir o objeto em formato XML, para facilitar a comunicao entre diferentes plataformas e ainda, fornece mecanismo para podermos customizarmos a serializao do objeto, mudando o seu comportamente padro para um mais especfico. O formato da serializao vai depender para onde deseja enviar o objeto. Por exemplo, se desejarmos passar o objeto entre aplicaes .NET, ou melhor, entre AppDomains diferentes, podemos serializar o objeto em formato binrio. Se desejarmos enviar o objeto para um Web Service, ento necessrio serializar este objeto em formato XML. A imagem abaixo ilustra como funciona o processo de serializao:

1 Imagem 6.1 Processo de serializao Israel Aece | http://www.projetando.net

Captulo 6 - Serializao

Quando voc desenvolve uma aplicao que internamente faz o uso de objetos, quando a aplicao fechada, todos os objetos so descartados juntamente com a mesma, ou seja, se voc fez vrias configuraes em um determinado objeto, definiu valores em propriedades, invocou mtodos que efetuaramos operaes internas ao objeto, tudo isso ser perdido quando a aplicao for finalizada. Se desejar manter esses valores, para mais tarde quando retornar a aplicao, necessrio serializar esse objeto fisicamente no disco. Para efetuar essa serializao podemos, em primeiro momento, apenas utilizar as classes padres fornecidas pelo .NET Framework 2.0. Inicialmente, precisamos de um formatter. Como j vimos acima, a serializao pode ser realizada em um stream usando SOAP, XML ou Binary. Os formatters so utilizados para determinar em qual desses formatos o objeto ser serializado. O .NET Framework fornece dois formatters: BinaryFormatter e SoapFormatter, onde ambas fornecem os mtodos de serializao e deserializao. A classe BinaryFormatter est disponvel dentro do namespace System.Runtime.Serialization.Formatters.Binary. J a classe SoapFormatter encontrase dentro do namespace System.Runtime.Serialization.Formatters.Soap s que necessrio adicionar referncia System.Runtime.Serialization.Formatters.Soap.dll. A classe BinaryFormatter, como o prprio nome diz, persite os dados em formato binrio, serializando todos os membros da classe, inclusive os membros privados. J a classe SoapFormatter persiste os dados em formato SOAP, que uma XML especializado para facilitar o transporte de dados via web e permite apenas a serializao de membros definidos com o atributo Serializable. Ambas as classes fornecem tambm um mtodo para serializao chamado Serialize e outro para deserializao, chamado de Deserialize, mtodos provenientes da Interface IFormatter. O mtodo Serialize recebe um stream que onde o objeto, que passado como segundo parmetro, ser persistido. J o mtodo Deserialize, dado um stream contendo o valor persitido, retorna um Object, resultando do processo de deserializao. Para exemplificar a utilizao dos dois tipos de formatters que vimos at agora, vamos criar uma classe chamada Funcionario que ter duas propriedades, a saber: Nome e Salario, sendo a primeira do tipo string e a segunda do tipo double. A nica diferena aqui que temos que dizer ao runtime que a classe Funcionario uma classe serializvel. Como vimos anteriormente, precisamos aplicar a classe o atributo Serializable. Por questes de espao, somente colocaremos aqui a declarao da classe para exibir como aplicar o atributo. A parte importante resume-se na criao de dois mtodos auxiliares, sendo uma para serializar e outro para deserializar o objeto Funcionario. Vejamos o cdigo a seguir:
VB.NET Imports System.Runtime.Serialization.Formatters.Soap <Serializable()> Public Class Funcionario ... End Class

Israel Aece | http://www.projetando.net

Captulo 6 - Serializao
Private fileName As String = "Cliente.bin" Sub Main() Dim f As New Funcionario("Joo Mendes", 1300.0) Serialize(f) Dim f1 As Funcionario = Deserialize() Console.WriteLine(f1.Nome & " - " & f1.Salario) End Sub

Private Sub Serialize(ByVal f As Funcionario) Using stream As Stream = File.Open(fileName, FileMode.Create) Dim formatter As New BinaryFormatter() formatter.Serialize(stream, f) End Using End Sub Private Function Deserialize() As Funcionario Dim f As Funcionario = Nothing Using stream As Stream = File.Open(fileName, FileMode.Open) Dim formatter As New BinaryFormatter() f = TryCast(formatter.Deserialize(stream), Funcionario) End Using Return f End Function C# using System.Runtime.Serialization.Formatters.Binary; [Serializable]public class Funcionario { //... } private static string fileName = "Cliente.bin"; static void Main(string[] args) { Funcionario f = new Funcionario("Joo Mendes", 1300.0); Serialize(f); Funcionario f1 = Deserialize(); Console.WriteLine(f1.Nome + " - " + f1.Salario);

private static void Serialize(Funcionario f) { using (Stream stream = File.Open(fileName, FileMode.Create)) {

Israel Aece | http://www.projetando.net

Captulo 6 - Serializao
BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(stream, f);

private static Funcionario Deserialize() { Funcionario f = null; using (Stream stream = File.Open(fileName, FileMode.Open)) { BinaryFormatter formatter = new BinaryFormatter(); f = formatter.Deserialize(stream) as Funcionario; } return f; }

O exemplo acima faz a serializao e deserializao utilizando o formatter BinaryFormatter. Criamos dois mtodos auxilares para facilitar o trabalho. O primeiro deles, Serialize, recebe a instncia de um objeto Funcionario que o persiste. J o segundo mtodo auxiliar, Deserialize, retorna um objeto Funcionario que extrado do stream, que foi anteriormente salvo. A imagem abaixo exibe o contedo do arquivo que serializamos:

Imagem 6.2 Arquivo com o objeto serializado Agora, se desejarmos persistir o mesmo objeto em formato SOAP, para facilitar o transporte dos dados via web, o cdigo que vimos acima, muda ligeiramente. As nicas alteraes com relao ao formatter e o namespace onde ele reside. Temos ento que trocar de BinaryFormatter para SoapFormatter.
VB.NET Imports System.Runtime.Serialization.Formatters.Soap ... Dim formatter As New SoapFormatter() ...

Israel Aece | http://www.projetando.net

Captulo 6 - Serializao
C# using System.Runtime.Serialization.Formatters.Soap; //... SoapFormatter formatter = new SoapFormatter(); //...

Finalmente temos o contedo salva em um formato XML:

Imagem 6.3 Objeto persistido atravs da classe SoapFormatter Nota: Assim como existe o atributo Serializable para indicarmos o que pode e o que no pode ser serializado no objeto, temos tambm o atributo NonSerializable que indica se um determinado membro pode ou no ser serializado. Serializao em formato XML Haver alguns momentos onde ser necessrio persistir um determinado objeto em um formato para que ele seja independente de plataforma, ou seja, nem sempre voc pode garantir que o cliente que ir consumir o objeto serializado utilize tambm .NET. Felizmente a Microsoft pensou nessa possibilidade e disponibilizou uma classe chamada XmlSerializer disponvel dentro do namespace System.Xml.Serialization. Essa classe permite serializar propriedades pblicas e membros do objeto em um formato XML para armazenamento ou mesmo para transporte. Israel Aece | http://www.projetando.net 5

Captulo 6 - Serializao

Mais uma vez, se utilizarmos o mesmo exemplo que usamos para exemplificar a utilizao das classes BinaryFormatter e SoapFormatter, basta alterarmos o formatter para XmlSerializer e, em seu construtor, especificar o tipo qual ele ir trabalhar. O trecho de cdigo abaixo exibe como devemos configurar o XmlSerializer para serializarmos o objeto Funcionario que utilizamos um pouco mais acima em outros exemplos:
VB.NET Imports System.Xml.Serialization ... Dim formatter As New XmlSerializer(GetType(Funcionario)) ... C# using System.Xml.Serialization; //... XmlSerializer formatter = new XmlSerializer(typeof(Funcionario)); //...

A imagem abaixo exibe o output gerado pela serializao atravs da classe XmlSerializer:

Imagem 6.4 Output da classe XmlSerializer Como vimos anteriormente, a classe SoapFormatter serializa e deserializa objetos em formato SOAP, de acordo com as especificaes estipuladas pelo W3C, incluindo Israel Aece | http://www.projetando.net

Captulo 6 - Serializao

informaes extras, exclusivas para o contexto, como por exemplo, o header de uma mensagem SOAP e bem limitado em relao a customizao do formato a ser gerado. J a classe XmlSerializer faz o trabalho de persistncia em formato XML, permitindo controlar como ser o formato do documento XML que ser gerado, ao contrrio da classe SoapFormatter. Essa customizao possvel graas aos atributos que esto disponveis dentro do namespace System.Xml.Serialization, que podem ser aplicados nas propriedades e classes que desejamos persistir. Por padro, a serializao XML no inclui informaes sobre os tipos do objeto, assim como podemos reparar atravs da imagem 6.4. Isso acontece porque, durante o processo de deserializao, o tipo extrado do prprio objeto. Por padro, a classe XmlSerializer mapeia cada campo e propriedade do objeto para um elemento dentro do XML com o mesmo nome. Esses atributos esto divididos entre duas categorias: atributos para XML e atributos para SOAP e todos eles esto contidos dentro do namespace System.Xml.Serialization. Esses atributos controlam a forma que o XML e o SOAP gerado, permitindo uma formatao mais customizada ao nosso cenrio. As duas tabelas a seguir detalham cada um desses atributos: Atributo - SOAP SoapAttribute SoapElement SoapEnum SoapIgnore SoapInclude Aplica-se Campo pblico, propriedade, parmetro ou valor de retorno Campo pblico, propriedade, parmetro ou valor de retorno Campo pblico que um enumerador Campos pblicos e propriedades Descrio O membro da classe ser serializado como um atributo XML. A classe ser serializada como um elemento XML. Aplicado a enumeradores para customizar como seus valores sero serializados. A propriedade ou o campo ser ignorado quando a classe for serializada. Um determinado tipo deve ser includo quando for gerar schemas e, conseqentemente, ser reconhecido quando for serializado. A classe deve ser serializada como um tipo XML. Descrio Quando deserializado, o array ser carregado com objetos do tipo XmlAttribute que representam todos os atributos XML desconhecidos do schema.

Classes derivadas e mtodos pblicos para documentos WSDL Classes pblicas Aplica-se Campo pblico, propriedade, parmetro ou valor de retorno que retorna um array de objetos do tipo XmlAttribute

SoapType Atributo XML XmlAnyAttribute

Israel Aece | http://www.projetando.net

Captulo 6 - Serializao
XmlAnyEment Campo pblico, propriedade, parmetro ou valor de retorno que retorna um array de objetos do tipo XmlElemet Campo pblico, propriedade, parmetro ou valor de retorno que retorna um array de objetos complexos Campo pblico, propriedade, parmetro ou valor de retorno que retorna um array de objetos complexos Campo pblico, propriedade, parmetro ou valor de retorno Campo pblico, propriedade, parmetro ou valor de retorno Campo pblico, propriedade, parmetro ou valor de retorno Campo pblico que um enumerador Campos pblicos e propriedades

XmlArray

Quando deserializado, o array ser carregado com objetos do tipo XmlElemet que representam todos os elementos XML desconhecidos do schema. Os membros do array sero gerados como membros do array XML. Os tipos derivados que podem ser inseridos dentro array. Usualmente aplicado junto com um XmlArray. O membro ser serializado como um atributo XML. Especifica que membro pode ser futuramente detectado utilizando um enumerador. O campo ou a propriedade ser serializado como um elemento XML. Aplicado a enumeradores para customizar como seus valores sero serializados. A propriedade ou o campo ser ignorado quando a classe for serializada. Um determinado tipo deve ser includo quando for gerar schemas e, conseqentemente, ser reconhecido quando for serializado. Controla a serializao XML do atributo, definindo ele como sendo o root. O campo ou a propriedade deve ser serializada como um texto XML. O nome e namespace do tipo XML.

XmlArrayItem

XmlAttribute XmlChoiceIdentifier XmlElement XmlEnum XmlIgnore XmlInclude

Classes derivadas e retorno de mtodos pblicos para documentos WSDL Classes pblicas Campos pblicos e propriedades

XmlRoot XmlText XmlType

Classes pblicas

Observao: O sufixo Attribute foi removido da coluna Atributo por questes de espao. Apesar de existir, o compilador consegue entender e torna o sufixo no obrigatrio quando utilizado. Israel Aece | http://www.projetando.net

Captulo 6 - Serializao

Para exemplificar a utilizao de algum desses atributos vamos analisar trechos do cdigo que estar disponvel na ntegra na demonstrao do captulo. Atravs do cdigo abaixo veremos a utilizao dos atributos XmlRoot, XmlAttribute e XmlIgnore. O XmlRoot vai determinar qual ser o elemento raiz do documento XML. O construtor deste atributo pode receber uma string contendo o nome mas, se nenhum for informado, o mesmo nome do membro ser definido como raiz. J o segundo atributo, XmlAttribute, utilizado para marcarmos uma propriedade ou campo como sendo um atributo ao invs de um elemento. Finalmente, o XmlIgnore que ir evitar de serializar um determinado campo do objeto.
VB.NET Imports System.Xml Imports System.Xml.Serialization <XmlRoot("funcionariosAtivos"), Serializable> _ Public Class Empresa Private _sala As String ... <XmlAttribute("salaDoEscritorio")> _ Public Property Sala As String ... End Property End Class <Serializable> _ Public Class Funcionario Private _salario As Double ... <XmlIgnore> _ Public Property Salario As Double ... End Property End Class C# using System.Xml; using System.Xml.Serialization; [Serializable] [XmlRoot("funcionariosAtivos")] public class Empresa { private string _sala;

Israel Aece | http://www.projetando.net

Captulo 6 - Serializao

10

//... [XmlAttribute("salaDoEscritorio")] public string Sala { //... }

[Serializable] public class Funcionario { private double _salario; //... [XmlIgnore] public double Salario { //... }

O documento XML gerado exibido abaixo. Se analisarmos, o elemento raiz chama-se funcionariosAtivos, assim como definimos atravs do elemento XmlRoot. Repare tambm que a propriedade Sala foi serializada com o nome salaDoEscritorio e sendo atributo do elemento funcionariosAtivos. Finalmente, a propriedade Salario no persitido, justamente porque o atributo XmlIgnore foi especificado nele.
XML <?xml version="1.0"?> <funcionariosAtivos xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" salaDoEscritorio="Oitavo andar"> <Funcionarios> <Funcionario> <Nome>Joo Mendes</Nome> <Superior> <Nome>Mateus Golias</Nome> </Superior> </Funcionario> </Funcionarios> </funcionariosAtivos>

10

Israel Aece | http://www.projetando.net

Captulo 6 - Serializao
Customizando a serializao XML

11

Se desejarmos customizar a serializao e deserializao realizada pela classe XmlSerializer, podemos implementar a Interface IXmlSerializable na classe que ser persistida. Essa Interface fornece trs mtodos: GetSchema, ReadXml e WriteXml. O primeiro deles, retorna um objeto do tipo XmlSchema que descreve o documento XML que gerado pelo mtodo WriteXml e lido pelo mtodo ReadXml. J os mtodos ReadXml e WriteXml l e gera o documento XML, respectivamente. Para o mtodo ReadXml passado como parmetro um objeto do tipo XmlReader, que o stream que representa o objeto serializado. Esse mtodo deve ser capaz de extrair as informaes do objeto e reconstitu-lo. J para o mtodo WriteXml, um objeto do tipo XmlWriter passado como parmetro. Esse parmetro fornecer mtodos para que seja possvel persistir o objeto em formato XML. A classe CPF abaixo implementa a Interface IXmlSerializable e customizada cada um dos mtodos de uma forma simples para fins de exemplo.
VB.NET Imports System.Xml Imports System.Xml.Serialization Imports System.Xml.Schema Public Class CPF Implements IXmlSerializable Private _numero As String Public Property Numero() As String Get Return Me._numero End Get Set(ByVal value As String) Me._numero = value End Set End Property Public Function GetSchema() As XmlSchema _ Implements IXmlSerializable.GetSchema Return Nothing End Function Public Sub ReadXml(ByVal reader As XmlReader) _ Implements IXmlSerializable.ReadXml Me._numero = reader.ReadString() End Sub

11

Israel Aece | http://www.projetando.net

Captulo 6 - Serializao

12

Public Sub WriteXml(ByVal writer As XmlWriter) _ Implements IXmlSerializable.WriteXml writer.WriteString(Me._numero) End Sub End Class C# using System.Xml; using System.Xml.Serialization; using System.Xml.Schema; public class CPF : IXmlSerializable { private string _numero; public string Numero { get { return this._numero; } set { this._numero = value; } } public XmlSchema GetSchema() { return null; } public void ReadXml(XmlReader reader) { this._numero = reader.ReadString(); } public void WriteXml(XmlWriter writer) { writer.WriteString(this._numero); }

12 Se repararmos, quando a Interface IXmlSerializable implementada, a classe dispensa o uso do atributo Serializable. importante dizer tambm que a forma como se faz a serializao atravs do objeto XmlSerializer no muda absolutamente nada. Israel Aece | http://www.projetando.net

Captulo 6 - Serializao

13

A classe XmlSerializer ainda fornece quatro eventos interessantes que so invocados durante a deserializao do objeto. Quando serializamos um determinado objeto, persistimos as informaes necessrias que desejamos que sejam armazenadas. Suponhamos que mais tarde, quando queremos recuperar esse objeto atravs do processo de deserializao, o arquivo em que o persistimos tiver atributos ou elementos desconhecidos, ou seja, membros que no fazem parte do schema atual do objeto, eles sero ignorados pelo processo de deserializao. Apesar de ignorados pelo processo, podemos ser notificados quando isso acontecer e isso possvel atravs desses quatro eventos que falamos. A tabela abaixo explica detalhadamente cada um deles: Evento UnknownAttribute Descrio Ocorre quando o objeto XmlSerializer encontrar um atributo desconhecido durante o processo de deserializao. UnknownElement Ocorre quando o objeto XmlSerializer encontrar um elemento desconhecido durante o processo de deserializao. UnknownNode Ocorre quando o objeto XmlSerializer encontrar um n desconhecido durante o processo de deserializao. UnreferencedObject Ocorre quando durante o processo de deserializao de um stream baseado em SOAP, quando o objeto XmlSerializer encontrar um tipo conhecido que no usado ou referenciado. Para que seja possvel interceptar o lanamento desses eventos, necessrio anexarmos os procedimentos que sero disparados quando o mesmo for disparado. Temos abaixo um exemplo simples de como proceder neste caso:
VB.NET Imports System.Xml.Serialization Using stream As Stream = File.Open(fileName, FileMode.Open) Dim f As New XmlSerializer(GetType(CPF)) AddHandler f.UnknownAttribute, AddressOf UnknownAttribute AddHandler f.UnknownElement, AddressOf UnknownElement AddHandler f.UnknownNode, AddressOf UnknownNode Dim c As CPF = TryCast(f.Deserialize(stream), CPF) End Using Private Sub UnknownAttribute(ByVal sender As Object, _ ByVal e As XmlAttributeEventArgs) Console.WriteLine(e.Attr.Name) Console.WriteLine(e.LineNumber) End Sub Private Sub UnknownNode(ByVal sender As Object, _ ByVal e As XmlNodeEventArgs)

13

Israel Aece | http://www.projetando.net

Captulo 6 - Serializao

14

Console.WriteLine(e.Name) Console.WriteLine(e.LineNumber) End Sub Private Sub UnknownElement(ByVal sender As Object, _ ByVal e As XmlElementEventArgs) Console.WriteLine(e.Element.Name) Console.WriteLine(e.LineNumber) End Sub C# using System.Xml.Serialization; using (Stream stream = File.Open(fileName, FileMode.Open)) { XmlSerializer f = new XmlSerializer(typeof(T)); f.UnknownElement += new XmlElementEventHandler(UnknownElement); f.UnknownNode += new XmlNodeEventHandler(UnknownNode); f.UnknownAttribute += new XmlAttributeEventHandler(UnknownAttribute); } CPF c = f.Deserialize(stream) as CPF;

static void UnknownAttribute(object sender, XmlAttributeEventArgs e) { Console.WriteLine(e.Attr.Name); Console.WriteLine(e.LineNumber); } static void UnknownNode(object sender, XmlNodeEventArgs e) { Console.WriteLine(e.Name); Console.WriteLine(e.LineNumber); } static void UnknownElement(object sender, XmlElementEventArgs e) { Console.WriteLine(e.Element.Name); Console.WriteLine(e.LineNumber); }

14

Israel Aece | http://www.projetando.net

Captulo 6 - Serializao

15

Serializao customizada Quanto mais voc trabalha com serializao e deserializao de objetos para envi-los para os mais diversos locais, mais aumenta a necessidade de customizar esses processos para atender uma determinada funcionalidade. Como vimos at o momento, podemos utilizar as classes fornecidas pelo .NET Framework para efetuarmos a persistncia, mas se estivermos falando de um cenrio muito especfico, felizmente como grande parte do .NET Framework, ele tambm fornece classes e Interfaces para customizarmos os processos de serializao e deserializao, como por exemplo, podemos customizar um formatter especfico para que ele atenda ao nosso cenrio. J notamos que durante o processo de serializao, precisaremos especificar quais valores sero salvos e, quando fazer o inverso, o processo de deserializao, devemos extrair os valores do stream para o nosso objeto corrente. O .NET Framework fornece duas estruturas SerializationEntry, StreamingContext e a classe SerializationInfo. Esses tipos so utilizados para especificar os valores que so requeridos para representar o objeto na sua forma serializada e recuper-los durante o processo de deserializao. Alm disso, esses tipos fornecem informaes a respeito do tipo que foi serializado, Assembly, etc.. Para um detalhamento melhor, a estrutura SerializationEntry contm as propriedades Name, ObjectType e Value, quais disponibilizam o nome, tipo e valor, respectivamente, do objeto serializado. J a estrutura StreamingContext fornece informaes a respeito da fonte e do destino do stream, ou seja, durante o processo de serializao, define o destino dos dados e, quando for o processo de deserializao, especifica a fonte do stream. Finalmente a classe SerializationInfo, que est contida dentro do namespace System.Runtime.Serialization, armazena todo o contedo que necessrio tanto para serializar quanto para deserializar um determinado objeto em uma coleo do tipo chavevalor. Interfaces Como dissemos h pouco tempo atrs, o .NET Framework disponibiliza vrias Interfaces que podemos utilizar para customizar os processos de serializao e deserializao. Essas Interfaces esto contidas dentro do namespace System.Runtime.Serialization. Essas Interfaces, quando implementadas, permitem um melhor controle sob como os objetos so serializados e deseriliazados. As Interfaces que temos a nossa disposio so: ISerializable, IFormatter, IFormatterConverter e IDeserializationCallback. Abaixo analisaremos com mais detalhes cada uma delas e qual a sua utilidade. 15

Israel Aece | http://www.projetando.net

Captulo 6 - Serializao
ISerializable

16

Qualquer classe pode ser seralizada desde que esteja marcada com o atributo Serializable. Se a classe precisa controlar o processo de serializao, voc pode implementar a Interface ISerializable. Por padro, o formatter que est sendo utilizado para o processo de serializao invoca o mtodo GetObjectData (fornecido pela Interface em questo) e fornece um objeto do tipo SerializationInfo com todos os dados que so requeridos para representar o objeto. A implementao desta Interface implica em a classe possuir um construtor que recebe como parmetro um objeto do tipo SerializationInfo e outro do tipo StreamingContext. Durante o processo de deserializao, esse construtor invocado somente depois que os dados foram deserializados pelo formatter. Geralmente esse construtor deve ser protected se a classe permitir derivaes. O techo de cdigo abaixo ilustra a utilizao da implementao desta Interface:
VB.NET Imports System.Runtime.Serialization Public Class CPF Implements ISerializable Private _numero As Integer Private _nome As String Public Sub New() End Sub Protected Sub New(ByVal info As SerializationInfo, _ ByVal context As StreamingContext) Me._numero = info.GetInt32("numero") Me._nome = info.GetString("nome") End Sub Propriedades que expe os membros internos _numero e _nome Public Sub GetObjectData(ByVal info As SerializationInfo, _ ByVal context As StreamingContext) _ Implements ISerializable.GetObjectData info.AddValue("numero", Me._numero) info.AddValue("nome", Me._nome) End Sub End Class

16

Israel Aece | http://www.projetando.net

Captulo 6 - Serializao
C# using System.Runtime.Serialization; public class CPF : ISerializable { private int _numero; private string _nome; public CPF() { } protected CPF(SerializationInfo info, context) { this._numero = info.GetInt32("numero"); this._nome = info.GetString("nome"); } // Propriedades que expe os membros internos // _numero e nome public void GetObjectData(SerializationInfo StreamingContext context) { info.AddValue("numero", this._numero); info.AddValue("nome", this._nome); } }

17

StreamingContext

info,

IFormatter A Interface IFormatter por qualquer classe que ir ser um formatter, fornecendo funcionalidades para formatar objetos serializados e tambm possibilitando o controle do output dos processos de serializao e deserializao. Essa Interface fornece os dois principais mtodos que so utilizados por um formatter: Serialize e Deserialize. Alm de disponibilizar todos os membros necessrios para a criao de um formatter customizado, essa Interface tambm implementada em uma classe abstrata chamada Formatter. Essa classe fornece funcionalidades bsicas e pode ser utilizada (herdada) para todos os formatters customizados que forem construdos ao invs de implementar diretamente a Interface IFormatter. Neste caso, todos os membros que so herdados da Interface IFormatter so mantidos como abstratos; ela somente adiciona outros membros que auxiliam durante os processos. IFormatterConverter Essa Interface fornece uma conexo entre a instncia da classe SerializationInfo e o formatter para que seja possvel efetuar as converses de tipos necessrias durante os Israel Aece | http://www.projetando.net 17

Captulo 6 - Serializao

18

processos. Assim como a Interface IFormatter implementada na classe abstrata Formatter mas, Interface IFormatter tambm implementada em uma classe chamada FormatterConverter, contendo toda a implementao bsica para o cdigo necessrio para efetuar as converses. Internamente, essa classe faz o uso da classe Convert. IDeserializationCallback Vamos imaginar o seguinte cenrio: temos uma classe chamada Calculo que, dados dois nmeros ele calcula a soma entre eles. Naturalmente quando optar por serializar esse objeto, voc no ir armazenar o valor do resultado, ou seja, vai apenas se preocupar em salvar os valores que efetivamente geram o resultado. At o momento, nada diferente. Como propriedades pblicas so, por padro, persistidas automaticamente, no teremos problemas com relao a perda dos dados. Mas, e se no momento da deserializao quisermos notificar o objeto recm revitalizado para que ele faa alguma operao? neste momento que a Interface IDeserializationCallback entra em ao. Quando o mtodo Deserialize do formatter chamado, internamente ele verifica se o tipo do objeto que est sendo recuperado implementa ou no essa Interface. Se estiver implementada, o mtodo OnDeserialization, fornecido por ela, executado. Trazendo isso para o nosso exemplo, poderemos nesse momento, efetuarmos o clculo (soma) dos nmeros para quando o usurio recuperar o valor, o mesmo j estar processado. Para exemplificar a utilizao desta Interface, o cdigo abaixo exibe apenas os pontos relevantes do cdigo necessrios para essa implementao:
VB.NET Imports System.Runtime.Serialization <Serializable()> Public Class Soma Implements IDeserializationCallback Private _numero1 As Integer Private _numero2 As Integer Private _total As Integer propriedades ocultadas por questes de espao Public Sub OnDeserialization(ByVal sender As Object) _ Implements IDeserializationCallback.OnDeserialization Me._total = Me._numero1 + Me._numero2 End Sub End Class C# using System.Runtime.Serialization;

18

Israel Aece | http://www.projetando.net

Captulo 6 - Serializao

19

[Serializable] public class Soma : IDeserializationCallback { private int _numero1; private int _numero2; private int _total; // propriedades ocultadas por // questes de espao public void OnDeserialization(object sender) { this._total = this._numero1 + this._numero2; }

Quando o usurio requisitar pelo membro _total ele j conter o resultado da soma entre os dois membros internos.

19

Israel Aece | http://www.projetando.net

Captulo 7 Globalizao de Aplicaes


Captulo 7 Globalizao de Aplicaes Introduo

Atualmente muito comum as empresas estarem expandindo seus negcios entre vrios pases do mundo. Alm disso, ainda h casos onde pessoas estrangeiras so comumente contratados por essas empresas. Com isso, o software ou at mesmo o website da empresa, dever contemplar vrios idiomas. Para possibilitar isso, as aplicaes devem ser capazes de configurar o padro e formatao dos nmeros (isso inclui o sistema monetrio) e datas e, principalmente, serem capazes de ajustar a interface com usurio, pois em determinados idiomas, podem exigir mais espao para uma determinada informao e, se a aplicao no previnir isso, poder deixar a tela inutilizlvel. Neste captulo veremos detalhadamente como devemos proceder para configurarmos uma aplicao para que a mesma suporte as funcionalidades de globalizao, analisando as classes e tipos fornecidos pelo namespace System.Globalization. Globalizao e Localizao Quando estamos falando de aplicaes para mltiplos idiomas e pases, temos que entender dois aspectos muito importantes que so a globalizao e localizao: Globalizao: O processo de globalizao a habilidade de construir aplicaes/websites que so suportadas e adaptveis para as mais diferentes culturas. Localizao: a habilidade de localizar a aplicao para uma cultura e regio especfica, criando tradues para os recursos que a aplicao utiliza em seu interior. Um exemplo tpico a localizao de uma aplicao/website para o portugus para vrias regies, como o Brasil (pt-BR) e Portugal (pt-PT).

Como podemos notas na descrio acima, as culturas so identificadas por um padro universal, contendo duas partes, sendo a primeira delas dois caracteres minsculos que identificam o idioma. J na segunda parte, existem mais dois caracteres maisculos que representam o pas. Existem uma enorme lista com todos as culturas suportadas pelo .NET Framework, mas que no ser exibida aqui por questes de espao. Mas se desejar consultar, basta localizar a classe CultureInfo no MSDN Library local ou no site e l ter a lista completa. Quando trabalhamos com culturas, temos dois conceitos importantes que devemos levar em considerao. Trata-se da cultura corrente e da cultura de interface (uiculture). A Israel Aece | http://www.projetando.net

Captulo 7 Globalizao de Aplicaes

primeira delas, utilizada para operarmos com formatao de datas e nmeros e, alm disso, utilizada durante a escrita do cdigo; j a segunda, utilizada pelo Resource Manager para analisar uma cultura especfica e recuperar os recursos em tempo de execuo. Para definirmos cada uma dessas culturas, utilizamos as propriedades CurrentCulture e CurrentUICulture, respectivamente. Essas propriedades so estticas e esto contidas dentro da classe Thread que, por sua vez, est dentro do namespace System.Threading. Utilizando Culturas Uma das classes essencias na criao de aplicaes globalizadas, cada instncia da classe CultureInfo representa uma cultura especfica, contendo informaes especficas da cultura que ela representa e, entre essas informaes, temos o nome da cultura, sistema de escrita, calendrios, como formatar datas, etc.. A tabela abaixo exibe os principais mtodos e propriedades desta classe. Membro Calendar ComparerInfo CultureTypes Descrio Propriedade de somente leitura que retorna um objeto do tipo Calendar. O objeto Calendar representa as divises do tempo, como semanas, meses e anos. Propriedade de somente leitura que retorna um objeto do tipo ComparerInfo que define como comparar as strings para a cultura corrente. Propriedade de somente leitura que retorna uma combinao do enumerador CultureTypes, indicando que tipo a cultura corrente pertence. Entre as opes fornecidas pelo enumerador CultureTypes, temos: AllCultures Todas as culturas, incluindo as culturas que fazem parte do .NET Framework (neutras e especficas), culturas instaladas no Windows e as culturas customizadas, criadas pelo usurio. FrameworkCultures Culturas especficas e neutras que fazem parte do .NET Framework. InstalledWin32Cultures Todas as culturas instaladas dentro do Windows. NeutralCultures Culturas que esto associadas com um idioma mas no com uma regio/pas especfico. ReplacementCultures Culturas customizadas pelo usurio que substituem as culturas disponibilizadas pelo .NET Framework. SpecificCultures Culturas que no so especficas para uma regio/pas. UserCustomCulture Culturas customizadas, criadas pelo usurio. Israel Aece | http://www.projetando.net

Captulo 7 Globalizao de Aplicaes

CurrentCulture

CurrentUICulture

DateTimeFormat DisplayName EnglishName InstalledUICulture IsNeutralCulture IsReadOnly Name NativeName NumberFormat

Parent TextInfo UseUserOverride

WindowsOnlyCultures Somente as culturas instaladas dentro do Windows e no fazem parte do .NET Framework. Propriedade esttica de somente leitura que retorna um objeto do tipo CultureInfo que est sendo utilizada pela thread corrente. Essa propriedade nada mais que um wrapper para a propriedade esttica CurrentCulture da classe Thread. Propriedade esttica de somente leitura que retorna um objeto do tipo CultureInfo que est sendo utilizada pelo Resource Manager para extrair os recursos em tempo de execuo. Essa propriedade nada mais que um wrapper para a propriedade esttica CurrentUICulture da classe Thread. Propriedade de somente leitura que retorna um objeto do tipo DateTimeFormatInfo que define as formas apropriadas para exibir e formatar datas e horas para a cultura corrente. Propriedade de somente leitura que retorna uma string com o nome da cultura no formato full, exemplo: en-US. Propriedade de somente leitura que retorna uma string com o nome da cultura em ingls. Propriedade esttica de somente leitura que retorna um objeto do tipo CultureInfo que representa a cultura instalada com o sistema operacional. Propriedade de somente leitura que retorna um valor booleano indicando se o objeto CultureInfo corrente representa uma cultura neutra. Propriedade de somente leitura que retorna um valor booleano indicando se o objeto CultureInfo corrente ou no somente leitura. Propriedade de somente leitura que retorna uma string contendo o nome da cultura corrente no seguinte formato: English (United States). Propriedade de somente leitura que retorna uma string contendo o nome da cultura corrente em seu idioma atual: English (United States). Propriedade de somente leitura que retorna um objeto do tipo NumberFormatInfo que define as formas apropriadas para exibir e formatar nmeros (inclusive o sistema monetrio, porcentagens) para a cultura corrente. Propriedade de somente leitura que retorna um objeto do tipo CultureInfo que representa a cultura pai da cultura corrente. Propriedade de somente leitura que retorna um objeto do tipo TextInfo que define a forma de escrita associada com a cultura corrente. Propriedade de somente leitura que retorna um valor booleano indicando se o objeto CultureInfo corrente utiliza as opes de Israel Aece | http://www.projetando.net

Captulo 7 Globalizao de Aplicaes

culturas definidas pelo usurio atravs das Configuraes Regionais do Painel de Controle do Windows. CreateSpecificCulture Mtodo esttico que, dado uma cultura especfica, cria e retorna um objeto do tipo CultureInfo associado com a cultura informada. GetCultureInfo Mtodo esttico que, dado uma cultura especfica, retorna uma instncia do objeto CultureInfo (read-only) associado com a cultura informada. GetCultures Mtodo esttico que retorna um array de culturas, onde cada um dos elementos representado por um objeto do tipo CultureInfo. GetFormat Este mtodo, atravs de um objeto do tipo Type, retorna uma instncia de um formatador associadao com a cultura corrente. Esse mtodo somente aceita como parmetro um objeto Type que representa a classe NumberFormatInfo ou a classe DateTimeFormatInfo. Do contrrio, esse mtodo retornar nulo. O cdigo abaixo exibe a forma de criao e a exibio de algumas das propriedades do objeto CultureInfo:
VB.NET Imports System.Globalization Sub Main() Dim pt As New CultureInfo("pt-BR") Dim en As CultureInfo CultureInfo.CreateSpecificCulture("en-US") Show(New CultureInfo() {pt, en}) End Sub Private Sub Show(ByVal cultures() As CultureInfo) For Each ci As CultureInfo In cultures Console.WriteLine("------------------------") Console.WriteLine(ci.DisplayName) Console.WriteLine(ci.DateTimeFormat.DateSeparator) Console.WriteLine(ci.DateTimeFormat.FirstDayOfWeek.ToString()) Console.WriteLine(ci.NumberFormat.CurrencyDecimalSeparator) Next End Sub C# using System.Globalization;

Israel Aece | http://www.projetando.net

Captulo 7 Globalizao de Aplicaes

static void Main(string[] args) { CultureInfo pt = new CultureInfo("pt-BR"); CultureInfo en = CultureInfo.CreateSpecificCulture("en-US"); Show(new CultureInfo[] { pt, en }); } static void Show(CultureInfo[] cultures) { foreach (CultureInfo ci in cultures) { Console.WriteLine("------------------------"); Console.WriteLine(ci.DisplayName); Console.WriteLine(ci.DateTimeFormat.DateSeparator); Console.WriteLine(ci.DateTimeFormat.FirstDayOfWeek.ToString()); Console.WriteLine(ci.NumberFormat.CurrencyDecimalSeparator); } }

A nica diferena entre a criao do objeto pt e en que no primeiro, pt, foi criado a partir da instncia da classe CultureInfo; j com o en, optamos por criar a partir do mtodo esttico CreateSpecificCulture da classe CultureInfo. O resultado para ambos os cdigos so idnticos e exibido abaixo:
-----------------------Portuguese (Brazil) / Sunday , -----------------------English (United States) / Sunday .

Recuperando informaes de uma regio (pas) Existe uma classe dentro do namespace System.Globalization chamada RegionInfo. Ao contrrio da classe CultureInfo, ela no representa as preferncias do usurio e no depende do idioma ou cultura do mesmo. A classe RegionInfo fornece informaes referente a uma regio/pas especifco.

Israel Aece | http://www.projetando.net

Captulo 7 Globalizao de Aplicaes

Essa classe contm um overload que recebe uma string. Essa string deve conter o nome da regio que voc deseja recuperar as informaes e, esse nome, deve ser dois caracteres maisculos de acordo com o padro estabelecido pela ISO. A tabela completa pode ser consultada quando voc abre a documentao da classe RegionInfo. Entre as principais propriedades desta classe tempos (com os exemplos baseados em uma instncia da classe RegionInfo que representa o Brasil): Propriedade CurrencyEnglishName CurrencyNativeName CurrencySymbol CurrentRegion DisplayName EnglishName Name NativeName Descrio Propriedade de somente leitura que retorna uma string contendo o nome, em ingls, da moeda utilizada pela regio corrente. Exemplo: Real Propriedade de somente leitura que retorna uma string contendo o nome no idioma nativo da regio corrente. Exemplo: Real Propriedade de somente leitura que retorna uma string contendo o smbolo monetrio utilizado pela regio corrente. Exemplo: R$ Propriedade esttica de somente leitura que retorna uma instncia da classe RegionInfo representando a regio da thread corrente. Propriedade de somente leitura que retorna uma string contendo o nome da regio corrente no idioma localizado do .NET Framework. Exemplo: Brazil Propriedade de somente leitura que retorna uma string contendo o nome, em ingls, da regio corrente. Exemplo: Brazi Propriedade de somente leitura que retorna uma string com o nome da regio corrente. Esse nome representado por dois caracteres maisculos. Exemplo: BR Propriedade de somente leitura que retorna uma string contendo o nome da regio corrente em seu idioma nativo. Exemplo: Brasil.

Logo abaixo temos um exemplo da utilizao desta classe:


VB.NET Imports System.Globalization Dim r As New RegionInfo("BR") Console.WriteLine(r.CurrencyNativeName) Console.WriteLine(r.CurrencySymbol) Console.WriteLine(r.NativeName) C# using System.Globalization; RegionInfo r = new RegionInfo("BR");

Israel Aece | http://www.projetando.net

Captulo 7 Globalizao de Aplicaes


Console.WriteLine(r.CurrencyNativeName); Console.WriteLine(r.CurrencySymbol); Console.WriteLine(r.NativeName);

Formatao de Datas e Nmeros Em alguns momentos acima vimos mencionados as classes DateTimeFormatInfo e NumberFormatInfo. Essas classes fornecem uma grande funcionalidade, que a formatao e padronizao de datas, horas e nmeros dentro da plataforma .NET. Essas classes fornecem informaes especficas para a manipulao de datas, horas e nmeros em diferentes regies e, como vimos, a classe CultureInfo disponibiliza propriedades que retornam a instncia dessas respectivas classes j com a cultura especificada mas, nada impede que voc crie a sua prpria instncia e customize as suas propriedades necessrias. A classe DateTimeFormatInfo Como falamos acima, a classe DateTimeFormatInfo ir auxiliar na formatao de data e hora de acordo com a cultura selecionada. Todas as propriedades que essa classe possui j refletem as informaes da cultura especfica quando voc extrai a instncia dessa classe a partir do mtodo GetFormat ou da propriedade DateTimeFormat da classe CultureInfo. Essa classe tambm suporta um padro pr-determinado, onde podemos especificar alguns caracteres que determinam a formatao da data/hora que ser exibido. Alguns desses especificadores so mostrados atravs da tabela abaixo: Especificador Descrio d Especifica um padro para a exibio de uma data de uma forma reduzida. um atalho para a propriedade ShortDatePattern da classe DateTimeFormatInfo. D Especifica um padro para a exibio de uma data de uma forma mais completa. um atalho para a propriedade LongDatePattern da classe DateTimeFormatInfo. f Especifica um padro para a exibio de uma hora de uma forma reduzida. um atalho para a propriedade ShortTimePattern da classe DateTimeFormatInfo. F Especifica um padro para a exibio de uma hora de uma forma mais completa. um atalho para a propriedade LongTimePattern da classe DateTimeFormatInfo. t Exibe uma combinao da data em seu formato completo e a hora em sua forma reduzida. T Exibe uma combinao da data em seu formato completo e a hora em sua forma completa. um atalho para a propriedade Israel Aece | http://www.projetando.net

Captulo 7 Globalizao de Aplicaes


FullDateTimePattern da classe DateTimeFormatInfo.

Alm dos padres que vimos na tabela acima, ainda h a possibilidade de, com esses mesmos caracteres, combin-los para customizarmos como a data/hora ser exibida. A estrutura DateTime fornece alguns mtodos que serve como wrapper para as propriedades que citamos acima e, alm disso, o mtodo ToString desta estrutura possui alguns overloads que permitem customizarmos a formatao. Para testarmos as formataes acima, vamos utiliz-la no exemplo a seguir:
VB.NET Dim hoje As DateTime = DateTime.Now Console.WriteLine(hoje.ToString("d")) Console.WriteLine(hoje.ToString("D")) Console.WriteLine(hoje.ToString("f")) Console.WriteLine(hoje.ToString("F")) Console.WriteLine(hoje.ToString("t")) Console.WriteLine(hoje.ToString("T")) Console.WriteLine(hoje.ToString("dd/MM/yyyy")) C# DateTime hoje = DateTime.Now; Console.WriteLine(hoje.ToString("d")); Console.WriteLine(hoje.ToString("D")); Console.WriteLine(hoje.ToString("f")); Console.WriteLine(hoje.ToString("F")); Console.WriteLine(hoje.ToString("t")); Console.WriteLine(hoje.ToString("T")); Console.WriteLine(hoje.ToString("dd/MM/yyyy"));

Como resultado obtemos:


31/3/2007 sbado, 31 de maro de 2007 sbado, 31 de maro de 2007 18:23 sbado, 31 de maro de 2007 18:23:13 18:23 18:23:13 31/03/2007

Na ltima linha do exemplo, utilizamos um overload do mtodo ToString que permite passarmos uma combinao de alguns caracteres que permite-nos customizar a formatao da data/hora. No exemplo MM significa que se trata de ms com duas casas. Atente-se, pois mm (minsculos) trata-se de minutos. Israel Aece | http://www.projetando.net

Captulo 7 Globalizao de Aplicaes

Quando fazemos a formatao da forma acima, por padro, o DateTimeFormatInfo que utilizado extrado atravs da propriedade esttica CurrentInfo da mesma. Vale lembrar que essa propriedade nada mais que um wrapper para a propriedade CurrentCulture da thread corrente. Mas podemos criar instncias da nossa prpria classe DateTimeFormatInfo para customizarmos como a data/hora ser tratada. Antes de analisarmos como devemos proceder para utiliz-la, vamos entender um pouco melhor algumas das propriedades que ela nos fornece atravs da tabela abaixo: Membro AbbreviateDayNames CurrentInfo DateSeparator DayNames FirstDayOfWeek Descrio Propriedade que retorna um array de strings, onde cada elemento do mesmo corresponde a um dia em sua forma abreviada. Propriedade esttica de somente leitura que retorna um objeto do tipo DateTimeFormatInfo da thread atual. Uma string que determina qual ser o separador das datas. Propriedade que retorna um array de strings, onde cada elemento do mesmo corresponde a um dia com o seu nome completo. Propriedade que podemos definir qual ser o primeiro dia da semana. Essa propriedade definimos com alguma opo do enumerador DayOfWeek. As opes fornecidas por esse enumerador so: Friday Indica sexta-feira. Saturday Indica sbado. Sunday Indica domingo. Monday Indica segunda-feira. Thursday Indica tera-feira. Tuesday Indica quinta-feira. Wednesday Indica quarta-feira. Propriedade onde definimos o formato da data e hora em seu formato longo. Este padro est associado ao caracter F que vimos acima. Propriedade onde definimos o formato do dia e ms, que esto associados com os caracteres d e M. Propriedade que retorna um array de strings, onde cada elemento do mesmo corresponde a um ms com o seu nome completo. Uma string que determina qual ser o separador de horas. Propriedade onde definimos o formato do ano e ms, que esto associados com os caracteres y e Y.

FullDateTimePattern MonthDayPattern MonthNames TimeSeparator YearMonthPattern

Israel Aece | http://www.projetando.net

Captulo 7 Globalizao de Aplicaes

10

Como dissemos acima, podemos criar uma instncia dessa classe customizarmos como desejarmos. Vemos mais utilidade nisso quando estamos criando uma cultura customizada, que analisaremos mais adiante. Abaixo est a forma que devemos proceder para a criao deste objeto:
VB.NET Dim dtfi As New DateTimeFormatInfo() dtfi.DateSeparator = "|" dtfi.TimeSeparator = "." Console.WriteLine(DateTime.Now.ToString(dtfi)) C# DateTimeFormatInfo dtfi = new DateTimeFormatInfo(); dtfi.DateSeparator = "|"; dtfi.TimeSeparator = "."; Console.WriteLine(DateTime.Now.ToString(dtfi));

importante notar que um dos overloads do mtodo ToString da estrutura DateTime recebe um tipo IFormatProvider e, como a classe DateTimeFormatInfo implementa essa Interface, ela pode ser passada para o mesmo. O resultado do cdigo exibido abaixo:
04|02|2007 10.19.49

A classe NumberFormatInfo Assim como a classe DateTimeFormatInfo, a classe NumberFormatInfo responsvel por tratar da formatao de nmeros dentro da plataforma .NET, ainda estendendo para o sistema monetrio da regio corrente. Essa classe tambm fornece alguns caracteres, com padres pr-determinados que podemos utilizar para customizar a formatao de um determinado valor. Alm disso, ela fornece tambm propriedades que podemos definir os smbolos, separadores decimais, etc. Atravs da tabela abaixo, vamos analisar os caracteres que temos disponveis para a formatao de valores numricos: Caracter c, C d, D e, E f, F g, G Descrio Formato monetrio. Formato decimal. Formato cientfico (exponencial). Formato fixo. Formato padro. Israel Aece | http://www.projetando.net

10

Captulo 7 Globalizao de Aplicaes


n, N r, R x, X

11

Formato numrico. Formato roundtrip. Esse formato assegura que os nmeros convertidos em strings tero o mesmo valor quando eles forem convertidos de volta para nmeros. Formato hexadecimal.

Com o conhecimento desses caracteres, j possvel utiliz-los no overload do mtodo ToString da estrutura Double, onde podemos determinar qual a forma que o valor contido dentro dela ser exibido. Alm do caracter, ainda possvel determinar a quantidade de casas decimais que ser exibido, usando em conjunto com o caracter de formatao um nmero que ir informar quantas casas decimais dever ter:
VB.NET Dim valor As Double = 123.2723 Console.WriteLine(valor.ToString("C2")) Console.WriteLine(valor.ToString("N3")) C# Double valor = 123.2723; Console.WriteLine(valor.ToString("C2")); Console.WriteLine(valor.ToString("N3"));

O resultado desse cdigo mostrado abaixo:


R$ 123,27 123,272

Mais uma vez, importante dizer que quando utilizamos as formataes dessa forma, apesar de explicitamente no estarmos utilizando a classe NumberFormatInfo, ela extrada automaticamente da thread corrente e, conseqentemente, formatando os valores baseando-se nessas informaes. Para conhecermos um pouco mais sobre a classe NumberFormatInfo, vamos analisar algumas das propriedades que elas nos fornece atravs da tabela abaixo: Propriedade CurrencyDecimalDigits Descrio Propriedade que recebe um nmero inteiro indicando quantas casas decimais utilizada em valores monetrios. CurrencyDecimalSeparator Propriedade que recebe uma string contendo o caracter que ser utilizado como separador de casas decimais. CurrencyGroupSeparator Propriedade que recebe uma string contendo o caracter que ser utilizado como separador dos grupos de nmeros. Israel Aece | http://www.projetando.net

11

Captulo 7 Globalizao de Aplicaes

12

CurrencyGroupSizes CurrentInfo NegativeSign NumberDecimalDigits NumberDecimalSeparator NumberGroupSeparator NumberGroupSizes PositiveSign

utilizado entre os grupos de nmeros. Propriedade que recebe um array de nmeros inteiros que representam qual ser o comprimento de cada grupo. Propriedade esttica de somente leitura que retorna um objeto do tipo NumberFormatInfo da thread atual. Propriedade que recebe uma string contendo o caracter que ser associado ao nmero quando ele for negativo. Propriedade que recebe um nmero inteiro indicando quantas casas decimais utilizada em nmeros em geral. Propriedade que recebe uma string contendo o caracter que ser utilizado como separador de casas decimais para nmeros em geral. Propriedade que recebe uma string contendo o caracter que ser utilizado como separador dos grupos de nmeros. utilizado entre os grupos de nmeros. Propriedade que recebe um array de nmeros inteiros que representam qual ser o comprimento de cada grupo. Propriedade que recebe uma string contendo o caracter que ser associado ao nmero quando ele for positivo.

Como dissemos acima, podemos criar uma instncia dessa classe customizarmos como desejarmos. Vemos mais utilidade nisso quando estamos criando uma cultura customizada, que analisaremos mais adiante. Abaixo est a forma que devemos proceder para a criao do objeto NumberFormatInfo:
VB.NET Dim nfi As New NumberFormatInfo() nfi.CurrencyDecimalDigits = 3 nfi.CurrencyDecimalSeparator = "*" nfi.CurrencyGroupSeparator = "_" nfi.CurrencyGroupSizes = New Integer(1) { 2 } nfi.CurrencySymbol = "Dinheiro do Brasil " Dim d As Double = 8789282212.9384738747 Console.WriteLine(d.ToString("C", nfi)) C# NumberFormatInfo nfi = new NumberFormatInfo(); nfi.CurrencyDecimalDigits = 3; nfi.CurrencyDecimalSeparator = "*"; nfi.CurrencyGroupSeparator = "_"; nfi.CurrencyGroupSizes = new int[1] { 2 }; nfi.CurrencySymbol = "Dinheiro do Brasil "; Double d = 8789282212.9384738747; Console.WriteLine(d.ToString("C", nfi));

12

Israel Aece | http://www.projetando.net

Captulo 7 Globalizao de Aplicaes

13

O resultado desse cdigo mostrado abaixo:


Dinheiro do Brasil 87_89_28_22_12*938

Criando uma cultura customizada Como vimos at o momento, utilizamos as culturas definidas pelo prprio .NET Framework, como o caso de pt-BR, en-US ou pt-PT. Essas culturas satisfazem a maior parte das aplicaes que necessitam serem globalizadas. S que pode haver cenrio onde necessrio criarmos uma cultura prpria, customizando-a para que atenda ao problema pelo qual ela foi criada. Felizmente, como o .NET Framework estensvel, ele fornece uma classe chamada CultureAndRegionInfoBuilder que permite a criao de uma cultura customizada de forma bem simples e rpida, sem a necessidade de aplicar o conceito de herana. Ela, por sua vez, contm vrias propriedades e mtodos que nos auxiliam na criao dessa cultura customizada e que importante analis-los para saber qual a sua finalidade: Membro CompareInfo Descrio Define um objeto do tipo CompareInfo que define como as strings sero comparadas com essa cultura. CultureName Propriedade de somente leitura que retorna o nome da cultura. GregorianDateTimeFormat Define um objeto do tipo DateTimeFormatInfo que define como as datas e horas so tratadas com essa cultura. NumberFormat Define um objeto do tipo NumberFormatInfo que define como os nmeros (e valores monetrios) so tratados com essa cultura. LoadDataFromCultureInfo Mtodo que recebe como parmetro um objeto do tipo CultureInfo que carrega as propriedades do objeto corrente com as propriedades correspondentes da cultura informada. LoadDataFromRegionInfo Mtodo que recebe como parmetro um objeto do tipo RegionInfo que carrega as propriedades do objeto corrente com as propriedades correspondentes da regio informada Register Persiste a cultura criada como uma cultura customizada no computador local e a disponibiliza para as aplicaes. Save Permite persistir a cultura criada em um arquivo fsico, em formato XML para uso futuro. Unregister Mtodo esttico que, dado uma string contendo o nome da cultura customizada, ele exclui a mesma do computador local. Israel Aece | http://www.projetando.net

13

Captulo 7 Globalizao de Aplicaes

14

Para exemplificar a criao desa cultura customizada, vamos analisar o cdigo abaixo:
VB.NET Dim cultureKey As String = "pt-BRCustom" Dim cst As Newew CultureAndRegionInfoBuilder( _ cultureKey, _ CultureAndRegionModifiers.None) cst.LoadDataFromCultureInfo(New CultureInfo("pt-BR")) cst.LoadDataFromRegionInfo(New RegionInfo("pt-BR")) cst.NumberFormat.CurrencyDecimalDigits = 4 cst.NumberFormat.CurrencyDecimalSeparator = "*" cst.Register() Dim pt As New CultureInfo(cultureKey) Dim valor As Double = 8789282212.9384738747 Console.WriteLine(valor.ToString("C", pt.NumberFormat)) CultureAndRegionInfoBuilder.Unregister(cultureKey) C# string cultureKey = "pt-BRCustom"; CultureAndRegionInfoBuilder cst = new CultureAndRegionInfoBuilder( cultureKey, CultureAndRegionModifiers.None); cst.LoadDataFromCultureInfo(new CultureInfo("pt-BR")); cst.LoadDataFromRegionInfo(new RegionInfo("pt-BR")); cst.NumberFormat.CurrencyDecimalDigits = 4; cst.NumberFormat.CurrencyDecimalSeparator = "*"; cst.Register(); CultureInfo pt = new CultureInfo(cultureKey); double valor = 8789282212.9384738747; Console.WriteLine(valor.ToString("C", pt.NumberFormat)); CultureAndRegionInfoBuilder.Unregister(cultureKey);

O resultado desse cdigo mostrado abaixo:


R$ 8.789.282.212*9385

14 Nota: Apesar da classe CultureAndRegionInfoBuilder estar contida dentro do namespace System.Globalization, necessrio adicionar uma referncia ao Assembly sysglobl.dll na aplicao que desejar utiliz-la. Israel Aece | http://www.projetando.net

Captulo 7 Globalizao de Aplicaes

15

Encoding A codificao de caracteres a forma que temos de representar caracteres em uma seqencia de bits. Cada caracter, depois de codificado, representado por um cdigo nico (tambm chamado de code point) dentro de uma code page. Uma code page uma lista de code points em uma certa ordem e utilizada para suportar idiomas especficos ou grupos de idiomas que compartilham o mesmo sistema de escrita. As code pages do Windows contm 256 code points (baseado em zero: 0 255). Na maioria das code pages, os primeiros 127 code points so sempre os mesmos caracteres para permitir a compatibilidade com o legado. Os 128 code points restantes diferem entre as code pages. Existem vrios padres de codificao disponveis para representar os caracteres nos mais diversos idiomas. Inicialmente o padro ASCII, que baseado no alfabeto ingls foi desenvolvido pela IBM. Esse padro utiliza 7 bits mas como h idiomas que possuem vrios outros caracteres e, conseqentemente, esse padro no suportaria todos os idiomas. Neste momento introduzido o padro Unicode, que possibilita 8 bits para os caracteres. Alm de suportar os caracteres definidos pelo ASCII, ele tambm suporta todos os caracteres conhecidos usados nos mais diversos idiomas. Atualmente, temos 3 verses do padro Unicode: UTF-8, UTF-16 e UTF-32. O que diferem nestes padres, a quantidade de bytes utilizados para armazenar os caracteres: o primeiro utiliza 1 byte, o segundo 2 bytes e o ltimo 4 bytes. O .NET Framework suporta esses padres que podemos, atravs de classes, utiliz-los em nossas aplicaes. As classes para isso esto contidas dentro do namespace System.Text e, uma das princpais delas a classe Encoding. O .NET Framework fornece as seguintes implementaes da classe Encoding para suportar alguns dos padres existentes atualmente: Classe ASCIIEncoding Descrio Codifica os caracteres baseando-se no padro ASCII. Essa classe corresponde ao code page 20127. Para cri-la basta criar uma instncia da mesma ou chamar a propriedade esttica ASCII da classe Encoding que j retornar uma instncia dessa classe. Codifica os caracteres baseando-se no padro UTF-7. Essa classe corresponde ao code page 65000. Para cri-la basta criar uma instncia da mesma ou chamar a propriedade esttica UTF7 da classe Encoding que j retornar uma instncia dessa classe. Codifica os caracteres baseando-se no padro UTF-8. Essa classe corresponde ao code page 65001. Israel Aece | http://www.projetando.net

UTF7Encoding

UTF8Encoding

15

Captulo 7 Globalizao de Aplicaes

16

Para cri-la basta criar uma instncia da mesma ou chamar a propriedade esttica UTF8 da classe Encoding que j retornar uma instncia dessa classe. UnicodeEncoding Codifica os caracteres baseando-se no padro UTF-16. Esse padro podem utilizar uma espcie de ordenao (little-endian e big-endian), onde cada uma delas representam um code page diferente. A primeira deles equivale ao code page 1200 e a segunda ao code page 1201. Para cri-la basta criar uma instncia da mesma ou chamar a propriedade esttica Unicode da classe Encoding que j retornar uma instncia dessa classe. Codifica os caracteres baseando-se no padro UTF-32. Esse padro podem utilizar uma espcie de ordenao (little-endian e big-endian), onde cada uma delas representam um code page diferente. A primeira deles equivale ao code page 65005 e a segunda ao code page 65006. Para cri-la basta criar uma instncia da mesma ou chamar a propriedade esttica UTF32 da classe Encoding que j retornar uma instncia dessa classe. Nota: Para uma referncia completa de todas as code pages e seus respectivos cdigos, sugiro consultar a documentao da classe Encoding do MSDN Library. Alm das propriedade estticas que vimos acima que a classe Encoding fornece, ainda existem algumas outras propriedades e mtodos importantes e que merecem serem citados. A tabela abaixo descreve alguns desses membros: Membro BodyName Descrio Propriedade de somente leitura que retorna uma string contendo o nome do codificador corrente. CodePage Propriedade de somente leitura que retorna um nmero inteiro contendo o identificar do codificar corrente. Default Propriedade esttica de somente leitura que retorna um objeto do tipo Encoding representando o codificador corrente do sistema. EncodingName Propriedade de somente leitura que retorna uma string contendo a descrio (em forma legvel) do codificador corrente. Convert Mtodo esttico que converte um array de bytes de um codificador para outro. GetBytes Mtodo que retorna um array de bytes contendo o resultado da codificao dos caracteres passado para o mtodo. GetDecoder Mtodo que retorna um objeto do tipo Decoder, que responsvel por converter uma determinada seqencia de bytes em uma seqencia de caracteres. Israel Aece | http://www.projetando.net

UTF32Encoding

16

Captulo 7 Globalizao de Aplicaes


GetEncoder GetPreamble GetString

17

Mtodo que retorna um objeto do tipo Encoder, que responsvel por converter uma determinada seqencia de caracteres em uma seqencia de bytes. Mtodo que retorna um array de bytes que ir identificar se o codificador suporta ordenao dos bytes em um formato big-endian ou little-endian. Mtodo que, dado um array de bytes, ele decodifica e retorna uma string contendo o seu valor legvel.

Para demonstrar a utilizao de duas dessas classes de codificao fornecidas pelo .NET Framework (ASCIIEncoding e UnicodeEncoding), vamos analisar o cdigo abaixo que, dado uma mensagem, ele recupera os bytes do mesma e, em seguida, traz em seu formato original.
VB.NET Imports System.Text Sub Main() Dim msg As String = "Codgificao - .NET Framework" Dim ascii As New ASCIIEncoding Dim unicode As New UnicodeEncoding Dim asciiBytes() As Byte = ascii.GetBytes(msg) Dim unicodeBytes() As Byte = unicode.GetBytes(msg) ShowBytes(asciiBytes) ShowBytes(unicodeBytes) Dim asciiMsg As String = ascii.GetString(asciiBytes) Dim unicodeMsg As String = unicode.GetString(unicodeBytes) Console.WriteLine(Environment.NewLine) Console.WriteLine(asciiMsg) Console.WriteLine(unicodeMsg) End Sub Sub ShowBytes(ByVal msg() As Byte) Console.WriteLine(Environment.NewLine) For Each b As Byte In msg Console.Write("[{0}]", b) Next End Sub C# using System.Text; static void Main(string[] args)

17

Israel Aece | http://www.projetando.net

Captulo 7 Globalizao de Aplicaes


{

18

string msg = "Codificao - .NET Framework"; ASCIIEncoding ascii = new ASCIIEncoding(); UnicodeEncoding unicode = new UnicodeEncoding(); byte[] asciiBytes = ascii.GetBytes(msg); byte[] unicodeBytes = unicode.GetBytes(msg); ShowBytes(asciiBytes); ShowBytes(unicodeBytes); string asciiMsg = ascii.GetString(asciiBytes); string unicodeMsg = unicode.GetString(unicodeBytes); Console.WriteLine(Environment.NewLine); Console.WriteLine(asciiMsg); Console.WriteLine(unicodeMsg);

static void ShowBytes(byte[] msg) { Console.WriteLine(Environment.NewLine); foreach (byte b in msg) { Console.Write("[{0}]", b); } }

O resultado para ambos os cdigo :


[67][111][100][103][105][102][105][99][97][63][63][111][32][45][3 2][46][78][69][ 84][32][70][114][97][109][101][119][111][114][107] [67][0][111][0][100][0][103][0][105][0][102][0][105][0][99][0][97 ][0][231][0][22 7][0][111][0][32][0][45][0][32][0][46][0][78][0][69][0][84][0][32 ][0][70][0][114 ][0][97][0][109][0][101][0][119][0][111][0][114][0][107][0] Codgifica??o - .NET Framework Codgificaao - .NET Framework

Como falamos mais acima, o padro ASCII somente somente 256 caracteres e, somente caracteres do alfabeto ingls. Como o idioma ingls no possui caracteres acentuadas,

18

Israel Aece | http://www.projetando.net

Captulo 7 Globalizao de Aplicaes

19

algumas coisas so perdidas e, quando no so encontradas um correspondente, um ponto de interrogao ? colocado no lugar do caracter desconhecido por aquele padro. Tratando as falhas na codificao/decodificao Quando utilizamos algum codificador que no consegue codificar ou decodificar algum caracter, ele coloca um ponto de interrogao ? para indicar que o codificador corrente no capaz de traduz-lo. A classe Encoding fornece dois mtodos chamados GetEncoder e GetDecoder, que retornam os objetos responsveis por codificar e decodificar as strings, respectivamente. Ambas as classes possuem uma propriedade chamada FallBack. Essa propriedade recebe um objeto do tipo EncoderFallback ou DecoderFallback que representam uma ao que ser executada quando um caracter ou um byte no puder ser convertido. As classes EncoderFallback e DecoderFallback so utilizadas na codificao e decodificao, respectivamente. A primeira delas a classe base para todos os fallbacks de codificao e, a segunda, a classe base para todos os fallbacks de decodificao. Conseguimos, atravs da imagem abaixo, entender a hierarquia dessas classes.

19

Israel Aece | http://www.projetando.net

Captulo 7 Globalizao de Aplicaes

20

Imagem 7.1 Hierarquia dos fallbacks de codificao e decodificao. Basicamente, para ambos os casos, temos dois tipos de fallbacks: Fallbacks de Substituio: Quando um caracter ou byte no consegue ser traduzido, ele substitui o mesmo por um caracter que podemos determinar. Por padro, o ponto de interrogao ? utilizado. o EncoderReplacementFallback: Fallback que invocado quando um caracter que puder ser convertido em uma seqencia de bytes, convertendo o caracter para um valor pr-definido. o DecoderReplacementFallback: Fallback que invocado quando uma seqencia de bytes no puder ser convertida em um caracter, convertendo o byte para um valor pr-definido. Fallbacks de Excesses: Quando um caracter ou byte no consegue ser traduzido, ele atira uma excesso do tipo EncoderFallbackException ou DecoderFallbackException, dependendo da operao que estamos tentando realizar.

20

Israel Aece | http://www.projetando.net

Captulo 7 Globalizao de Aplicaes

21

o EncoderExceptionFallback: Fallback que invocado quando um caracter que puder ser convertido em uma seqencia de bytes, atirando uma excesso do tipo EncoderFallbackException. o DecoderExceptionFallback: Fallback que invocado quando uma seqencia de bytes no puder ser convertida em um caracter, atirando uma excesso do tipo DecoderFallbackException. O cdigo abaixo exemplifica a utilizao dos fallbacks:
VB.NET Imports System.Text Dim encoder As Encoder = Encoding.ASCII.GetEncoder() encoder.Fallback = New EncoderReplacementFallback("*") Dim chars() As Char = "BC".ToCharArray() Dim buffer(chars.Length) As Byte encoder.GetBytes(chars, 0, chars.Length, buffer, 0, True) Console.WriteLine(Encoding.ASCII.GetString(buffer)) C# using System.Text; Encoder encoder = Encoding.ASCII.GetEncoder(); encoder.Fallback = new EncoderReplacementFallback("*"); char[] chars = "BC".ToCharArray(); Byte[] buffer = new byte[chars.Length]; encoder.GetBytes(chars, 0, chars.Length, buffer, 0, true); Console.WriteLine(Encoding.ASCII.GetString(buffer));

O resultado para ambos os cdigo :


*BC

O "" substitudo pelo "*" porque esse caracter no est contemplado no padro ASCII e, conseqentemente, no consegue ser "traduzido". A utilizao dos fallbacks so muito teis quando voc quer customizar a leitura ou gravao de streams. A utilizao de fallbacks de excesses so as vezes mais utilizadas quando a leitura dos caracteres devem ser muito mais precisa. 21

Israel Aece | http://www.projetando.net

Captulo 8 - Criptografia
Captulo 8 Criptografia Introduo

Criptografia de dados um ponto muito importante nos mais diversos tipos de aplicaes. Geralmente, em aplicaes onde alguns dos dados so muito sigilosos, como o caso de aplicaes financeiras, quais mantm os dados de seus clientes, necessrio que se mantenha esses dados seguros e desejavelmente, se esses dados cairem em mos erradas, essas pessoas com ms intenes, no consigam entender e recuperar esses dados em sua forma legvel. justamente neste cenrio que a criptografia entra, ou seja, ao se detectar os dados sensveis durante a anlise do projeto, deve ser aplicar algum algoritmo de criptografia para manter esses dados o mais seguro possvel. Isso vital para uma aplicao deste nvel. Neste captulo analisaremos as possibilidades que o .NET Framework nos fornece para aplicar criptografia em dados, bem como executar o processo reverso, ou seja, ser capaz de trazer os dados em sua forma legvel. Todos os recursos esto contidos dentro do namespace System.Security.Cryptography. Ainda h a possibilidade de efetuarmos a criptografia em uma nica direo, ou seja, no sermos mais capazes de recuperar o seu contedo em sua forma legvel. Esse processo conhecido como hash e ser extensivamente abordado na segunda parte deste captulo. O que criptografia? Criptografia trata-se de o processo de converter alguns dados em um formato difcil de ler e compreender. O processo de criptografia capaz de, dado um contedo qualquer (nmero, letras, etc.), transform-lo em uma seqencia de caracteres que, a olha humano, no capaz de ser traduzido. A criptografia utilizada para: 1. Confidencialidade: Para garantir que os dados permaneam privados. Geralmente, a confidencialidade obtida com a criptografia. Os algoritmos de criptografia (que usam chaves de criptografia) so usados para converter texto sem formatao em texto codificado e o algoritmo de descriptografia equivalente usado para converter o texto codificado em texto sem formatao novamente. Os algoritmos de criptografia simtricos usam a mesma chave para a criptografia e a descriptografia, enquanto que os algoritmos assimtricos usam um par de chaves pblica/privada. 2. Integridade de dados: Para garantir que os dados sejam protegidos contra modificao acidental ou deliberada (mal-intencionada). A integridade, geralmente, fornecida por cdigos de autenticao de mensagem ou hashes. Um valor de hash um valor numrico de comprimento fixo derivado de uma seqncia de dados. Os valores de hash so usados para verificar a integridade Israel Aece | http://www.projetando.net

Captulo 8 - Criptografia

dos dados enviados por canais no seguros. O valor do hash de dados recebidos comparado ao valor do hash dos dados, conforme eles foram enviados para determinar se foram alterados. 3. Autenticao: Para garantir que os dados se originem de uma parte especfica. Os certificados digitais so usados para fornecer autenticao. As assinaturas digitais geralmente so aplicadas a valores de hash, uma vez que eles so significativamente menores que os dados de origem que representam. Os algoritmos de criptografia utilizam um valor um tanto quanto complexo, chamado de cipher (ou como key), para ser utilizado no processo de criptografia. O processo que utiliza o cipher para encriptar os dados e produzir o valor j criptografado chamado de algoritmo de criptografia. Os valores que compem os cipher podem ser de diferentes tamanhos e, quanto maior, mais seguro e, conseqentemente, mais difcil de ser quebrado. Como j dissemos na introduo do captulo, o processo de criptografia um processo que consiste em duas rotinas: criptografar os dados e decriptograf-los, que justamente o processo inverso, ou seja, dado o valor criptografado, ele capaz de recuperar o seu contedo em sua legvel. Mas importante lembrar que esse processo somente possvel se a chave utilizada for a mesma chave (cipher) aplicada durante o processo de criptografia. Dentro do processo de criptografia, existem duas categorias de algoritmos. Essas categorias so baseadas em que cipher utilizado e como ele gerenciado. Essas categorias so chamadas de criptografia simtrica e criptografia assimtrica. A primeira delas, a criptografia simtrica, utiliza uma chave nica privada, tanto para criptografar quanto para descriptografar os dados. J a criptografia assimtrica, utiliza um par de chaves, sendo uma pblica e uma privada. Uma delas mantida privada e a outra ditribuda publicamente. Criptografia simtrica A criptografia simtrica utiliza apenas uma chave privada que necessria tanto para criptografar quanto para descriptografar as informaes e utilizada em um grupo pequenos de parceiros e companhias. Os algoritmos de criptografia simtricas so menos complexos e mais eficiente que os algoritmos de criprografia assimtricas, justamente porque possuem apenas uma nica chave. Sendo assim, se essa chave cair nas mos de pessoas erradas, poder comprometer a segurana dos dados, j que ela poder descriptografar os dados sem nenhum outro problema e, se isso realmente acontecer, voc deve obrigatoriamente e o mais rpido possvel trocar essa chave. Devido a essa enorme responsabilidade de ter que manter essa chave segura, muitas empresas optam por utilizar a criptografia assimtrica, que veremos mais adiante. Israel Aece | http://www.projetando.net

Captulo 8 - Criptografia

Dentro do .NET Framework temos os mais conhecidos algoritmos de criptografia simtrica implementadas em diversas classes. Essas classes esto contidas dentro do namespace System.Security.Cryptography. Entre os algoritmos temos: Data Encryption Standard (DES), Triple DES, RC2 (Rivest Cipher), e Rijndael. O imagem abaixo ilustra a hierarquia das classes que fornecem os mais algoritmos simtricos:

Imagem 8.1 Hierarquia das classes de criptografia simtrica Como podemos notar, todas as classes desta categoria de criptografia herdam diretamente da classe abstrata SymmetricAlgorithm, que fornece todas as funcionalidades bsicas para qualquer algoritmo de criptografia simtrica. Todas as classes que herdam dela, como o caso das classes DES, TripleDES, RC2 e Rijndael, so tambm definidas como abstratas e, mais tarde, sero implementadas pela classe concreta, que veremos a seguir. Cada uma dessas classes que herdam diretamente da classe abstrata SymmetricAlgorithm, so agora herdadas, criando um CSP (Cryptographic Service Provider) correspondente para cada um dos algoritmos. Um CSP um mdulo independente que atualmente executa os algoritmos de criptografia para autenticao, codificao e criptografia, ou seja, basicamente um wrapper para o algoritmo de criptografia correspondente, encapsulando o acesso a objetos no gerenciados pelo runtime do .NET Framework que so utilizados durante o processo de criptografia. Para termos uma noo melhor do que cada uma das classes fornecem, vamos analisar a relao abaixo: Algoritmo DES Descrio Criado pela IBM em 1975, suporta uma chave de 56 bits, mas dentro do .NET Framework, possibilita o uso de uma chave de 64 bits. O .NET Framework suporta apenas porque esse algoritmo popularmente utilizado, mas j foi quebrado. Essa classe base para todos os algoritmos baseando em DES e sua CSP correspondente a classe Israel Aece | http://www.projetando.net

Captulo 8 - Criptografia
DESCryptoServiceProvider.

Essa classe ainda possui dois mtodos pblicos e estticos importantes, que analisam a complexidade e determinam se a chave ou no fcil de ser quebrada. Esses mtodos so invocados durante o proceso de criptografia e descriptografia e atira uma exceo do tipo CryptographicException caso a chave seja fcil de ser quebrada, o que obriga-nos a chamar os mtodos dentro de um bloco de tratamento de erros para antecipar esses possveis problemas. Os mtodos so: IsWeakKey: Existem quatro chaves que so consideradas fracas e facilmente de serem quebradas. Esse mtodo retorna um valor booleano indicando se a chave fornecida ou no fraca. IsSemiWeakKey: Existe seis chaves que so consideradas semifracas e podem ser facilmente quebradas. Esse mtodo retorna um valor booleano indicando se a chave fornecida ou no semifraca.

TripleDES

Nota: Felizmente o mtodo GenerateKey nunca retornar uma chave que fraca ou semi-fraca. Criado pela IBM em 1978, suporta uma chave de 168 bits, mas dentro do .NET Framework, possibilita o uso de uma chave de 128 at 192 bits. Essa classe base para todos os algoritmos baseando em TripleDES e sua CSP correspondente a classe TripleDESCryptoServiceProvider. Esse algoritmo baseado no algoritmo DES e, sendo assim, contm tambm chaves conhecidas que podem quebrar a criptografia. No entanto, esse algoritmo considerado muito mais seguro em comparao ao DES, justamente por ter uma chave maior e o algoritmo aplicado trs vezes antes de disponibilizar o resultado. Essa classe tambm possui um mtodo pblico e esttico chamado IsWeakKey que retorna um valor booleano indicando se a chave fornecida ou no fraca. Representa a classe base para todas as implementaes do algoritmo RC2. Esse algoritmo foi criado em 1987 por Ron Rivest. Esse algoritmo suporta chaves de 40 at 128 bits. Essa classe base para todos os algoritmos baseando em RC2 e sua CSP correspondente a classe RC2CryptoServiceProvider. Criado em 1998 por Joan Daemen e Vincent Rijmen, Rijndael tambm conhecida como Advanced Encryption Standard (AES). Essa classe suporta chaves de 128 at 256 bits e, dentro do .NET Framework, o algoritmo Rijndael suporta valores fixos de 128, 192 ou 256 bits. Essa classe base para todos os algoritmos baseando em Rijndael e a classe concreta que implementa essa algoritmo dentro do .NET Framework a Israel Aece | http://www.projetando.net

RC2

Rijndael

Captulo 8 - Criptografia
classe RijndaelManaged. Tipos adicionais

Antes de analisarmos exemplos de cdigos que utilizamos para efetuarmos os processos de criptografia e descriptografia, devemos estudar um pouco mais sobre alguns tipos adicionais que temos dentro do .NET Framework, que so utilizados em conjunto com as classes responsveis pela criptografia em si. O primeiro deles a classe CryptoStream. Essa classe herda diretamente da classe Stream e fornece um link entre o stream de dados e as transformaes realizadas durante o processo de criptografia. Seu construtor recebe trs parmetros: um objeto do tipo Stream, que onde os dados criptografados sero armazenados; j o segundo parmetro, recebe um objeto do tipo ICryptoTransform. Essa Interface implementada em classes que define as operaes bsicas de transformaes criptogrficas. Instncias de objetos que implementam esta Interface so retornados quando invocamos os mtodos CreateEncryptor e CreateDecryptor referente algum algoritmo fornecido pela plataforma .NET. Finalmente, o terceiro e ltimo parmetro, recebe uma das opes fornecidas pelo enumerador CryptoStreamMode, que ir indicar qual ser o modo que o stream ser operado. As opes que temos neste enumerador so: Opo Read Write Descrio Permite acesso leitura ao stream de criptografia. Permite acesso escrita ao stream de criptografia.

Alm da classe CryptoStream ainda fazemos uso de algumas classes contidas dentro do namespace System.IO para poder efetuar a leitura e a escrita dos dados criptografados. Entre essas classes temos a classe MemoryStream, StreamWriter e StreamReader, quais j abordamos anteriormente no Captulo 5. No cenrio de criptografia, a classe MemoryStream utilizada para armazenar os dos que sero criptografados ou descriptografados; j as classes StreamWriter e StreamReader so utilizadas no processo de criptografia e descriptografia de dados, respectivamente. Por questes de espao, no abordaremos todos os algoritmos de criptografia simtrica aqui. Apenas como exemplo, teremos a implementao aqui do algoritmo DES e na aplicao de demonstrao do captulo teremos todos os algoritmos implementados. Sendo assim, o exemplo abaixo utiliza um nico CSP para efetuar tanto a criptografia quanto a descriptografia de uma mensagem simples.
VB.NET Imports Imports Imports Imports

System System.IO System.Text System.Security.Cryptography

Israel Aece | http://www.projetando.net

Captulo 8 - Criptografia
Using csp As New DESCryptoServiceProvider() Dim buffer() As Byte = Nothing

Using ms As New MemoryStream() Using stream As New CryptoStream(ms, csp.CreateEncryptor, CryptoStreamMode.Write) Using sw As New StreamWriter(stream) sw.Write("Trabalhando com Criptografia") End Using End Using buffer = ms.ToArray() Console.WriteLine(Encoding.Default.GetString(buffer)) End Using Using ms As New MemoryStream(buffer) Using stream As New CryptoStream(ms, csp.CreateDecryptor(), CryptoStreamMode.Read) Using sr As New StreamReader(stream) Console.WriteLine(sr.ReadLine()) End Using End Using End Using End Using C# using using using using System; System.IO; System.Text; System.Security.Cryptography;

using (DESCryptoServiceProvider csp = new DESCryptoServiceProvider()) { byte[] buffer = null; using (MemoryStream ms = new MemoryStream()) { using (CryptoStream stream = new CryptoStream(ms, csp.CreateEncryptor(), CryptoStreamMode.Write)) { using (StreamWriter sw = new StreamWriter(stream)) { sw.WriteLine("Trabalhando com Criptografia"); } } buffer = ms.ToArray(); Console.WriteLine(Encoding.Default.GetString(buffer));

Israel Aece | http://www.projetando.net

Captulo 8 - Criptografia
}

using (MemoryStream ms = new MemoryStream(buffer)) { using (CryptoStream stream = new CryptoStream(ms, csp.CreateDecryptor(), CryptoStreamMode.Read)) { using (StreamReader sr = new StreamReader(stream)) { Console.WriteLine(sr.ReadLine()); } } } }

Analisando o cdigo acima e como j foi dito, utilizamos um nico CSP do tipo DESCryptoServiceProvider que utilizado tanto para criptografar quanto para descriptografar a mensagem. O mtodo CreateEncryptor e CreateDecryptor so sobrecarregados; para cada um deles existe a possibilidade de invoc-los sem a necessidade de parmetros e, neste caso, os valores da chave (propriedade Key) e do vetor (propriedade IV) so passados automaticamente e, se esses valores no foram definidos pelo desenvolvedor, como foi o caso acima, os mtodos GenerateKey e GenerateIV so invocados. Nota: IV (Initialization Vector) trata-se de uma valor que aplicado na criptografia simtrica para garantir que uma mesma mensagem no seja criptografada da mesma forma, o que poderia corromper o algoritmo, ou seja, sabendo que a palavra Microsoft fosse sempre criptografada com um determinado conjunto de caracteres, bastaria percorrer o restante da mensagem e onde fosse encontrado esse conjunto, sabe-se que a palavra Microsoft que ali est. Como estamos fazendo os dois processos de uma nica vez, no precisamos nos preocupar com a chave e o vetor que so automaticamente gerados, justamente porque utilizamos a mesma instncia do CSP DESCryptoServiceProvider. Obviamente que esses valores devem ser guardados com muita segurana se futuramente voc desejar descriptografar a mensagem. Lembrando que esses valores tambm devem ser distribudos para todos que podem descriptografar os dados. Criptografia Assimtrica Tambm conhecida como chave pblica, a chave assimtrica trabalha com duas chaves: uma denominada privada e a outra pblica. Nesse mtodo, uma pessoa deve criar uma chave de codificao e envi-la a quem for mandar informaes a ela. Essa a chave pblica. Uma outra chave deve ser criada para a decodificao. Esta chave, tambm conhecida como chave privada, secreta e no deve ser compartilhada. Israel Aece | http://www.projetando.net 7

Captulo 8 - Criptografia

Criptografia assimtrica utilizada em larga escala, pois ideal para cenrios como por exemplo a prpria Internet, onde a chave privada permanece secreta e a chave pblica ser largamente distribuda. Um outro exemplo tpico de criptografia assimtrica utilizada em assinaturas digitais, que muito usado com chaves pblicas. Trata-se de um meio que permite provar que um determinado documento eletrnico de procedncia verdadeira. O receptor da informao usar a chave pblica fornecida pelo emissor para se certificar da origem. Alm disso, a chave fica integrada ao documento de forma que qualquer alterao por terceiros imediatamente a invalide. O .NET Framework utiliza essa tcnica quando assinamos um Assembly com um strong name. Como algoritmos de criptografia assimtrica so considerados mais complexos em relao aos algoritmos de criptografia simtrica, a criptografia assimtrica ser executada com mais lentido. Dentro do .NET Framework temos os mais conhecidos algoritmos de criptografia assimtrica implementadas em diversas classes. Essas classes esto contidas dentro do namespace System.Security.Cryptography. Entre os algoritmos temos o DSA e o RSA. O imagem abaixo ilustra a hierarquia das classes que fornecem os mais algoritmos assimtricos:

Imagem 8.2 Hierarquia das classes de criptografia assimtrica Como podemos notar, todas as classes desta categoria de criptografia herdam diretamente da classe abstrata AsymmetricAlgorithm, que fornece todas as funcionalidades bsicas para qualquer algoritmo de criptografia simtrica. Todas as classes que herdam dela, Israel Aece | http://www.projetando.net

Captulo 8 - Criptografia

como o caso das classes DSA e RSA, so tambm definidas como abstratas e, mais tarde, sero implementadas pela classe concreta, que veremos a seguir. Cada uma dessas classes que herdam diretamente da classe abstrata AsymmetricAlgorithm, so agora herdadas, criando um CSP (Cryptographic Service Provider) correspondente para cada um dos algoritmos. Um CSP um mdulo independente que atualmente executa os algoritmos de criptografia para autenticao, codificao e criptografia, ou seja, basicamente um wrapper para o algoritmo de criptografia correspondente. Para termos uma noo melhor do que cada uma das classes fornecem, vamos analisar a relao abaixo: Algoritmo DSA Descrio Criado em 1991, o DSA (Digital Signature Algorithm) utiliza uma chave de 512 at 1024 bits e utilizada como base para todos os algoritmos de assinatura digital. Essa classe base para todos os algoritmos baseando em DSA e sua CSP correspondente a classe DSACryptoServiceProvider. Criado em 1977 por Ron Rivest, Adi Shamir e Len Adleman um dos algoritmos mais utilizados atualmente. Dentro do .NET Framework suportado uma chave de 384 at 16.384 bits se utilizar o Microsoft Enhanced Cryptographic Provider e uma chave de 384 at 512 bits se utilizar o Microsoft Base Cryptographic Provider. Essa classe base para todos os algoritmos baseando em RSA e sua CSP correspondente a classe RSACryptoServiceProvider.

RSA

DSA Como j vimos, o DSA um algoritmo assimtrico e a sua chave privada opera sobre o hash da mensagem SHA-1. Para verificar a assinatura um pedao do cdigo calcula o hash e outro pedao usa a chave pblica para decifrar a assinatura, e por fim ambos comparam os resultados garantindo a autoria da mensagem. O DSA trabalha com chaves de 512 at 1024 bits, porm ao contrrio do RSA, o DSA somente assina e no garante confidencialidade. Outro ponto contra o DSA que a gerao da assinatura mais rpida do que o RSA, porm de 10 at 40 vezes mais lenta para conferir a assinatura. Para exemplificar a utilizao deste algoritmo, iremos utilizar a classe DSACryptoServiceProvider fornecida pelo .NET Framework. Alm dessa classe, ainda temos outras duas classes que devemos utilizar: DSASignatureFormatter e DSASignatureDeformatter. A primeira delas utilizada para criar um algoritmo de assinatura digital atravs de um mtodo chamado CreateSignature; j a segunda, utilizada para verificar se uma determinada assinatura ou no vlida, tambm atravs de um mtodo chamado VerifySignature. O cdigo mostra como fazermos para utilizar as classes acima mencionadas: Israel Aece | http://www.projetando.net

Captulo 8 - Criptografia

10

VB.NET Imports System Imports System.Security.Cryptography Dim hash As Byte() = {22, 45, 78, 53, 1, 2, 205, 98, 75, 123, 45, 76, 143, 189, 205, 65, 12, 193, 211, 255} Dim algoritmo As String = "SHA1" Using csp As New DSACryptoServiceProvider Dim formatter As New DSASignatureFormatter(csp) formatter.SetHashAlgorithm(algoritmo) Dim signedHash As Byte() = formatter.CreateSignature(hash) Dim deformatter As New DSASignatureDeformatter(csp) deformatter.SetHashAlgorithm(algoritmo) If deformatter.VerifySignature(hash, signedHash) Then Console.WriteLine("Assinatura vlida.") Else Console.WriteLine("Assinatura invlida.") End If End Using C# using System; using System.Security.Cryptography; byte[] hash = { 22, 45, 78, 53, 1, 2, 205, 98, 75, 123, 45, 76, 143, 189, 205, 65, 12, 193, 211, 255 }; string algoritmo = "SHA1"; = using (DSACryptoServiceProvider csp DSACryptoServiceProvider()) { DSASignatureFormatter formatter = DSASignatureFormatter(csp); formatter.SetHashAlgorithm(algoritmo); byte[] signedHash = formatter.CreateSignature(hash); DSASignatureDeformatter deformatter DSASignatureDeformatter(csp); deformatter.SetHashAlgorithm(algoritmo); = new new

new

if (deformatter.VerifySignature(hash, signedHash)) { Console.WriteLine("Assinatura vlida."); } else

10

Israel Aece | http://www.projetando.net

Captulo 8 - Criptografia
{ } }

11

Console.WriteLine("Assinatura invlida.");

RSA O exemplo do algoritmo RSA mais simples em relao ao algoritmo DSA. Dentro do .NET Framework temos a classe (CSP) RSACryptoServiceProvider que fornece uma implementao do algoritmo RSA. Essa classe fornece dois mtodos chamados Encrypt e Decrypt. Ambos recebem um array de bytes onde, no primeiro caso, o mtodo Encrypt, o array de bytes representa a mensagem a ser criptografada e retornar um array de bytes com a mensagem criptografada; j o segundo mtodo, Decrypt, receber um array de bytes contendo a mensagem criptografada e retornar um array de bytes com a mensagem descriptografada. O exemplo abaixo exibe a utilizao da implementao do algoritmo RSA fornecido pelo .NET Framework:
VB.NET Imports System Imports System.Security.Cryptography Using csp As New RSACryptoServiceProvider Dim msg As Byte() = Encoding.Default.GetBytes("Trabalhando com criptografia") Dim msgEncriptada As Byte() = csp.Encrypt(msg, True) Console.WriteLine(Encoding.Default.GetString(msgEncriptada)) Dim msgDescriptada As Byte() = csp.Decrypt(msgEncriptada, True) Console.WriteLine(Encoding.Default.GetString(msgDescriptada)) End Using C# using System; using System.Security.Cryptography; using (RSACryptoServiceProvider csp = new RSACryptoServiceProvider()) { byte[] msg = Encoding.Default.GetBytes("Trabalhando com criptografia"); byte[] msgEncriptada = csp.Encrypt(msg, true); Console.WriteLine(Encoding.Default.GetString(msgEncriptada));

11

Israel Aece | http://www.projetando.net

Captulo 8 - Criptografia
byte[] msgDescriptada = csp.Decrypt(msgEncriptada, true); Console.WriteLine(Encoding.Default.GetString(msgDescriptada)); }

12

CryptoConfig Essa classe utilizada em aplicaes que no podem (ou no querem) depender de um algoritmo especfico de criptografia ou hashing. Isso pode aumentar a segurana, j que a aplicao poder ser flexvel ao ponto de escolher um entre uma poro de algoritmos disponveis e aplicar a criptografia/hashing. Pode-se optar por armazenar o algoritmo utilizado dentro da base de dados e, a qualquer momento que desejar, pode recuperar tanto o valor criptografado quanto o algoritmo que deve ser aplicado para descriptografar o valor. Essa classe possui um mtodo chamado esttico CreateFromName que recebe como parmetro uma string contendo o algoritmo concreto que deseja criar, retornando um System.Object contendo a classe devidamente instanciada. Esse mtodo ainda possui um overload que recebe um array de System.Object que so os parmetros que devem ser passados para o construtor da classe, quando houver. A string que ser informada para o mtodo deve estar dentro de uma das opes da tabela abaixo: Nome/Chave SHA SHA1 System.Security.Cryptography.SHA1 System.Security.Cryptography.HashAlgorithm MD5 System.Security.Cryptography.MD5 SHA256 SHA-256 System.Security.Cryptography.SHA256 SHA384 SHA-384 System.Security.Cryptography.SHA384 SHA512 SHA-512 System.Security.Cryptography.SHA512 RSA System.Security.Cryptography.RSA System.Security.Cryptography.AsymmetricAlgorith m DSA System.Security.Cryptography.DSA Algoritmo que ser instanciado SHA1CryptoServiceProvider SHA1CryptoServiceProvider SHA1CryptoServiceProvider SHA1CryptoServiceProvider MD5CryptoServiceProvider MD5CryptoServiceProvider SHA256Managed SHA256Managed SHA256Managed SHA384Managed SHA384Managed SHA384Managed SHA512Managed SHA512Managed SHA512Managed RSACryptoServiceProvider RSACryptoServiceProvider RSACryptoServiceProvider DSACryptoServiceProvider DSACryptoServiceProvider

12

Israel Aece | http://www.projetando.net

Captulo 8 - Criptografia
DES System.Security.Cryptography.DES 3DES TripleDES Triple DES System.Security.Cryptography.TripleDES System.Security.Cryptography.SymmetricAlgorithm RC2 System.Security.Cryptography.RC2 Rijndael System.Security.Cryptography.Rijndael

13

DESCryptoServiceProvider DESCryptoServiceProvider TripleDESCryptoServiceProvider TripleDESCryptoServiceProvider TripleDESCryptoServiceProvider TripleDESCryptoServiceProvider TripleDESCryptoServiceProvider RC2CryptoServiceProvider RC2CryptoServiceProvider RijndaelManaged RijndaelManaged

O cdigo abaixo exibe uma forma de utilizar essa classe, passando como parmetro para o mtodo CreateFromName o valor SHA, que far com que uma instncia do algoritmo do tipo SHA1CryptoServiceProvider seja retornado.
VB.NET Imports System Imports System.Security.Cryptography Dim csp As SHA1CryptoServiceProvider = DirectCast(CryptoConfig.CreateFromName("SHA"), SHA1CryptoServiceProvider) C# using System; using System.Security.Cryptography; SHA1CryptoServiceProvider csp = (SHA1CryptoServiceProvider)CryptoConfig.CreateFromName("SHA");

O que hashing? Ao contrrio dos tipos de criptografias que vimos at o momento, o objetivo do hash no garantir a confidenciabilidade de uma determinada informao, mas sim garantir que a mesma no sofra nenhuma espcie de adulterao. Sendo assim, a tcnica de hashing considera unidirecional, ou seja, uma vez aplicado o algoritmo de hashing em uma informao, jamais ser possvel recuperar aquela informao em seu estado legvel. Isso quer dizer que um determinado valor sempre gerar o mesmo cdigo hash e, para comparar se os dois valores hashes so iguais, voc deve aplicar o algoritmo hash ao valor informado pelo usurio e, o valor gerado, deve ser comparado quanto igualdade ao valor hash que tem armazenado dentro do sistema. Israel Aece | http://www.projetando.net

13

Captulo 8 - Criptografia

14

Obviamente que o mesmo algoritmo deve ser aplicado para garantir que seja possvel efetuar a comparao. O .NET Framework suporte trs algoritmos de hash, a saber: MD5, SHA1e HMAC e esto assim distribudas:

Imagem 8.3 Hierarquia das classes de hashing Todos os algoritmos que implementam a tcnica de hashing herdam, direta ou indiretamente, de uma classe abstrata chamada HashAlgorithm. Como podemos notar na imagem 8.3, essa classe abstrata herdada diretamente pelas classes SHA1, MD5, KeyedHashAlgorithm, RIPEMD160, SHA256, SHA384 e SHA512. Cada uma dessas classes possuem caractersticas que diferem uma das outras. Essas caractersticas consistem basicamente no nmero de bits que cada uma operar e todas elas tratam-se de classes abstratas que possuem a implementao bsica especfica para cada algoritmo e devem obrigatoriamente devem serem implementadas pelas classes concretas. Para entendermos um pouco melhor sobre cada algorimo, vamos analisar a tabela abaixo: Algoritmo MD5 SHA1 Descrio O MD5 (Message-Digest algorithm 5) um algoritmo de hash de 128 bits unidirecional desenvolvido pela RSA Data Security, Inc., o sucessor do MD4. A famlia SHA (Secure Hash Algorithm) um sistema de funes criptogrficas de hash. O primeiro membro da familia SHA foi publicado em 1993 e foi chamado de SHA. Entretanto atualmente foi deonominado por SHA-0 para evitar confuso com os seus sucesores. Dois anos mais tarde, o primeiro sucessor do SHA-0 foi publicado com o nome de SHA-1. Existem atualmente quatro variaes deste algoritmo, que se difrerenciam nas interaes e na sua sada, que foram melhorados. So eles: : SHA-224, SHA-256, SHA-384, e SHA-512 (Todos eles so referenciados como SHA-2). J existem registros de ataques ao SHA-0 e ao SHA-1, mas nenhum registro ao SHA-2. HMAC (Hash-based Message Authentication Code) ma tcnica que utiliza uma funo de hash criptogrfica, uma mensagem e uma chave. Constitui um dos meios predominantes para garantir que os dados no foram corrompidos durante a transio da mensagem entre o emissor e o Israel Aece | http://www.projetando.net

HMAC

14

Captulo 8 - Criptografia
receptor.

15

Qualquer algoritmo de hashing pode ser utilizado com HMAC, sendo preferveis as funes mais seguras, como por exemplo a SHA-1. Abaixo ser exibido um exemplo para cada algoritmo que vimos na tabela acima. Basicamente, todas as classes que fornecem implementaes de algoritmos de hashing, possuem um mtodo chamado ComputeHash que recebem um array de bytes contendo a mensagem a ser aplicado o hash, devolvendo tambm um array de bytes, s que contendo o resultado do hash. Vamos iniciar pelo algortimo MD5. Assim como as classes de criptografias simtricas e assimtricas, as classes de hashing tambm possuem seus CSPs correspondentes e, no caso do algoritmo MD5, temos uma classe abstrata chamada MD5, que a classe base para todos os algoritmos MD5 e a classe MD5CryptoServiceProvider que o CSP correspondente. Abaixo analisaremos o cdigo responsvel por aplicar um algoritmo hash MD5:
VB.NET Imports System Imports System.Security.Cryptography Using csp As New MD5CryptoServiceProvider Dim msg As Byte() = Encoding.Default.GetBytes("MinhaSenha") Dim hash As Byte() = csp.ComputeHash(msg) For i As Integer = 0 To hash.Length 1 Console.Write(hash(i).ToString("x2")) Next End Using C# using System; using System.Security.Cryptography; using (MD5CryptoServiceProvider csp = MD5CryptoServiceProvider()) { byte[] msg = Encoding.Default.GetBytes("MinhaSenha"); byte[] hash = csp.ComputeHash(msg); for (int i = 0; i < hash.Length; i++) Console.Write(hash[i].ToString("x2")); new

15

Israel Aece | http://www.projetando.net

Captulo 8 - Criptografia

16

Como falamos acima, invocamos o mtodo ComputeHash passando o valor onde ser aplicado o hash e devolvido um array de bytes contendo o valor do hash gerado. Apenas fazemos um lao For para converter cada byte em valor hexadecimal (isso determinado pela sobrecarga do mtodo ToString) correspondente para visualizarmos a valor, que 775b70588a139c914412b7fdbc20d1c7 e este valor ser sempre o mesmo para MinhaSenha. Utilizando agora o algoritmo SHA1, o cdigo muda ligeiramente. Apenas o que ir mudar ser o CSP utilizado. S que para este algoritmo, temos dois CSPs disponveis que so SDA1CryptoServiceProvider e o SHA1Managed. O resultado de ambos so idnticos, mas o que muda seu comportamento interno, ou seja, a classe SHA1Managed uma verso gerenciada do algoritmo SHA1, o que proporciona um gerenciamento melhor de memria, sem a necessidade de servir de wrapper para objetos no gerenciados, como o caso do SDA1CryptoServiceProvider. Um exemplo da sua utilizao a seguinte:
VB.NET Imports System Imports System.Security.Cryptography Using alg As New SHA1Managed Dim msg As Byte() = Encoding.Default.GetBytes("MinhaSenha") Dim hash As Byte() = alg.ComputeHash(msg) For i As Integer = 0 To hash.Length 1 Console.Write(hash(i).ToString("x2")) Next End Using C# using System; using System.Security.Cryptography; using (SHA1Managed alg = new SHA1Managed()) { byte[] msg = Encoding.Default.GetBytes("MinhaSenha"); byte[] hash = alg.ComputeHash(msg); for (int i = 0; i < hash.Length; i++) Console.Write(hash[i].ToString("x2"));

O valor do hash gerado para a string MinhaSenha atravs da classe SHA1Managed a seguinte: 16c5e5376a4654937efedc675e941e32f917985e. Um detalhe importante aqui que, se utilizarmos o CSP SHA1CryptoServiceProvider o resultado seria o mesmo. Israel Aece | http://www.projetando.net

16

Captulo 8 - Criptografia

17

Finalmente, iremos utilizar o algoritmo HMAC. Atravs da imagem 8.3, a classe abstrata HMAC a classe base para todas as implementaes deste algoritmo e estende a classe KeyedHashAlgorithm. Contrariamente s assinaturas digitais, os MACs so computados e verificados com a mesma chave, para que assim, possam ser apenas verificadas pelo respectivo receptor. Um cenrio de exemplo : A Empresa 1 (emissora da mensagem) e a Empresa 2 (receptora) compartilham uma mesma chave secreta. A Empresa 1 utiliza a mensagem e a chave para computar o MAC, e envia o MAC juntamente com a mensagem. Quando a Empresa 2 receber a mensagem, descodifica o MAC e verifica-o para ver se o seu MAC corresponde ao da Empresa 1. Se corresponder, ento ele sabe que a mensagem da Empresa 1 e que ningum a modificou desde que foi enviada pela Empresa 1. Se algum tentar corromper a mensagem, pode faz-lo, mesmo no possuindo a chave secreta, contudo no consegue produzir o MAC. Neste caso, o receptor ir detectar a alterao que foi feita e sabe que a mensagem foi corrompida. Os algoritmos HMAC utilizam uma chave gerada randomicamente para aplicar o hash. Essa chave bem semelhante ao Salt e concatenada com o valor de hash gerado da mensagem a ser enviada. O resultado da concatenao entre a chave e o hash da mensagem novamente submetido a ao algoritmo de hash, resultando em um valor duas vezes hasheado com um Salt. Como a chave gerada randomicamente, a cada gerao, o output para uma mesma mensamgem completamente diferente. Nota: Salt so utilizados para dificultar o processo de hashing, ou seja, so valores adicionais, geralmente gerados randomicamente, que so concatenados na mensagem que ir sofrer o processo de hashing. Quando voc manipula dados que esto sendo armazenados dentro de um banco de dados qualquer em seu formato hashed, voc precisa tambm armazenar o Salt gerada, justamente para conseguir comparar o hash informado pelo usurio com o hash armazenado dentro da base de dados. Existem vrias implementaes dentro do .NET Framework para HMAC, cada uma utilizando um algoritmo diferente, geralmente diferenciando a quantidade de bits em seu valor hash criado. Para fins de exemplo, vamos utilizar a classe HMACSHA256 e, em relao ao cdigo que vimos um pouco mais acima, a nica alterao apenas o nome do algoritmo:
VB.NET Imports System Imports System.Security.Cryptography Using alg As New HMACSHA256 Dim msg As Byte() = Encoding.Default.GetBytes("MinhaSenha") Dim hash As Byte() = alg.ComputeHash(msg) For i As Integer = 0 To hash.Length 1 Console.Write(hash(i).ToString("x2")) Next

17

Israel Aece | http://www.projetando.net

Captulo 8 - Criptografia
End Using C# using System; using System.Security.Cryptography; using (HMACSHA256 alg = new HMACSHA256()) { byte[] msg = Encoding.Default.GetBytes("MinhaSenha"); byte[] hash = alg.ComputeHash(msg); for (int i = 0; i < hash.Length; i++) Console.Write(hash[i].ToString("x2"));

18

Vale lembrar que o valor do hash criado para o mesmo valor ser sempre alterado, devido a natureza do algoritmo, como j vimos acima. Um detalhe importante que as classes que fornecem algoritmos de HMAC possuem uma propriedade chamada Key, que retorna um array de bytes, representando a chave utilizada no algoritmo hash. DPAPI Com o lanamento do Windows 2000, uma nova API de proteo de dados foi criada. Essa API chamada de DPAPI (Data Protection API) e encapsula grande parte das complexidades que esto em torno dos processos de criptografia e descriptografia. Essas classes esto integradas com o Windows, mas no so gerenciadas pelo .NET Framework. Na verso 2.0 do .NET Framework, foram introduzidas duas novas classes chamadas ProtectedData e ProtectedMemory que servem como wrapper para as classes contidas dentro da DPAPI e tornam a sua utilizao bastante simples. Essas classes esto contidas dentro do namespace System.Security.Cryptography mas exige uma referncia adicional para a System.Security.dll, fornecida com a verso 2.0 do .NET Framework. A primeira delas, ProtectedData utilizada para proteger dados definidos pelo usurio. Essa classe no exige outras classes (algoritmos) de criptografia. Essa classe fornece dois mtodos principais chamados de Protect e Unprotect. Ambos os mtodos so estticos e, o mtodo Protect, alm de outros parmetros, recebe principalmente um array de bytes que representa o valor a ser protegido, retornando tambm um array de bytes contendo o valor criptografado; j o mtodo Unprotect recebe um array de bytes representando o dado protegido (criptografado) e retorna um array de bytes contendo o contedo em seu formato original. possvel utilizar uma entropia quando invocamos o mtodo Protect. Essa entropia tratase de um valor aleatrio, fornecido pela aplicao, que ser utilizada pelo DPAPI na formao da chave de criptografia. O problema com o uso de um parmetro adicional de Israel Aece | http://www.projetando.net

18

Captulo 8 - Criptografia

19

entropia precisar ser armazenado com segurana pelo aplicativo, o que apresenta outro problema de gerenciamento de chaves, como j discutimos acima. importante dizer tambm que, se um valor de entropia passado para o mtodo Protect, o mesmo valor deve ser tambm passado para o mtodo Unprotect. Finalmente, ambos os mtodos recebem em seu ltimo parmetro um enumerador do tipo DataProtectionScope. Esse enumerador fornece duas opes, a saber: Opo CurrentUser LocalMachine Descrio O data a ser protegido associado com o usurio corrente e, somente as threads que esto rodando com esse usurio que podero descriptografar os dados. O data a ser protegido associado com a mquina. Qualquer processo rodando dentro do computador, poder descriptografar os dados.

O cdigo abaixo exemplifica a utilizao da classe ProtectedData, onde devemos criar uma entropia (pode ser opcional null em Visual C# e Nothing em Visual Basic .NET) e submetermos tanto a entropia quanto a mensagem a ser criptografada para os mtodos Protect e Unprotect:
VB.NET Imports System Imports System.Security.Cryptography Dim Dim Dim Dim entropia() As Byte = {12, 73, 6, 92} msg As String = "Valor a ser protegido." msgEmBytes() As Byte = Encoding.Default.GetBytes(msg) msgProtegida() As Byte = _ ProtectedData.Protect( _ msgEmBytes, _ entropia, _ DataProtectionScope.CurrentUser)

Console.WriteLine( _ Encoding.Default.GetString( _ ProtectedData.Unprotect( _ msgProtegida, _ entropia, _ DataProtectionScope.CurrentUser))) C# using System; using System.Security.Cryptography; byte[] entropia = { 12, 73, 6, 92 }; string msg = "Valor a ser protegido."; byte[] msgEmBytes = Encoding.Default.GetBytes(msg);

19

Israel Aece | http://www.projetando.net

Captulo 8 - Criptografia
byte[] msgProtegida = ProtectedData.Protect( msgEmBytes, null, DataProtectionScope.CurrentUser); Console.WriteLine( Encoding.Default.GetString( ProtectedData.Unprotect( msgProtegida, null, DataProtectionScope.CurrentUser)));

20

Ainda dentro do DPAPI temos a classe ProtectedMemory. Como o prprio nome diz, essa classe protege os dados que residem na memria e, assim como a classe ProtectedData, no necessita de outras classes (algoritmos) de criptografia. As diferenas entre a classe ProtectedData e a classe ProtectedMemory so: A classe ProtectedMemory no utiliza entropia; O enumerador que passado para os mtodos Protect e Unprotect da classe ProtectedMemory do tipo MemoryProtectionScope, que contm as seguintes opes: Descrio Todo cdigo em qualquer processo pode descriptografar os dados. Somente o cdigo que est rodando dentro do mesmo contexto de usurio que protegeu os dados poder descriptografar os dados. Somente o cdigo que est rodando dentro do mesmo processo que protegeu os dados poder descriptografar os dados.

Opo CrossProcess SameLogon SameProcess

Os mtodos Protect e Unprotect no retornam nenhum valor. Ele utilizar o mesmo local onde est o array de bytes com o valor a ser protegido e aplicar a criptografia no mesmo lugar; Os dados que sero protegidos devem ter 16 bytes ou ser mltiplo de 16 bytes.

O cdigo abaixo exibe a utilizao da classe ProtectedMemory:


VB.NET Imports System Imports System.Security.Cryptography Dim msg As String = "1234567890poiuyt" Dim msgEmBytes() As Byte = Encoding.Default.GetBytes(msg) ProtectedMemory.Protect(msgEmBytes,

20

Israel Aece | http://www.projetando.net

Captulo 8 - Criptografia
MemoryProtectionScope.SameLogon) ProtectedMemory.Unprotect(msgEmBytes, MemoryProtectionScope.SameLogon) C# using System; using System.Security.Cryptography; string msg = "1234567890poiuyt"; byte[] msgEmBytes = Encoding.Default.GetBytes(msg); ProtectedMemory.Protect(msgEmBytes, MemoryProtectionScope.SameLogon); ProtectedMemory.Unprotect(msgEmBytes, MemoryProtectionScope.SameLogon);

21

Valores Randmicos Quando utilizamos os processos de criptografia e hashing, em alguns momentos necessrio criarmos chaves ou valores randmicos que so necessrios para garantir que o algoritmo funcione como desejado. Muitas vezes esses valores so gerados automaticamente pelos algoritmos de criptografia/hashing, mas o .NET Framework disponibiliza publicamente duas classes que podemos utilizar para a gerao destes valores randmicos. As classes fornecidas so: RandomNumberGenerator e RNGCryptoServiceProvider. A primeira delas, uma classe abstrata que a base necessria que todas as classes geradoras de valores randmicos devem herdar. J a segunda classe, trata-se de uma classe concreta (CSP), chamada RNGCryptoServiceProvider. Ela extende a classe abstrata RandomNumberGenerator e implementa um Random Number Generator (RNG). A imagem abaixo exibe a hierarquia entre essas classes:

Imagem 8.3 Hierarquia das classes que geram valores randmicos A classe RNGCryptoServiceProvider extensamente utilizada pelo prprio .NET Framework na gerao de chaves e dos vetores que discutimos acima, durante a gerao Israel Aece | http://www.projetando.net

21

Captulo 8 - Criptografia

22

automtica desses valores. Essa classe fornece dois mtodos chamados GetBytes e GetNonZeroBytes. O mtodo GetBytes recebe um array de bytes onde ele ir popul-lo com uma seqencia randmica de valores. Assim, como o mtodo GetBytes, o mtodo GetNonZeroBytes, tambm recebe um array de bytes onde o mtodo ir popul-lo com uma seqencia randmica, s que sem zeros. O cdigo abaixo exibe uma possvel forma de como utilizar essa classe:
VB.NET Imports System Imports System.Security.Cryptography Dim csp As New RNGCryptoServiceProvider Dim salt(64) As Byte csp.GetBytes(salt) C# using System; using System.Security.Cryptography; RNGCryptoServiceProvider csp = new RNGCryptoServiceProvider(); byte[] salt = new byte[64]; csp.GetBytes(salt);

22

Israel Aece | http://www.projetando.net

Captulo 9 Utilizando Code Access Security CAS


Captulo 9 Utilizando Code Access Security CAS Introduo

Toda aplicao que utiliza o Common Language Runtime (CLR) obrigatoriamente deve interagir com o sistema de segurana do mesmo. Quando a aplicao executada, automaticamente avaliado se ela tem ou no determinados privilgios. Dependendo das permisses que a aplicao tem, ela poder rodar perfeitamente ou gerar erros relacionados a segurana. Como estamos diante de um ambiente cada vez mais conectado, muito comum expor o cdigo de diferentes formas e diferentes locais. Muitos mecanismos de segurana concedem direitos de acesso aos recursos (arquivos e pastas por exemplo) baseando-se nas credenciais (nome de usurio e password) do usurio. S que isso acaba sendo uma tcnica perigosa, j que os usurios podem obter o cdigo de diversos locais, inclusive locais desconhecidos, que podem expor cdigos maliciosos, cdigos que contm vulnerabilidades, etc.. Code Access Security (tambm conhecido como CAS), um mecanismo que ajuda limitar e proteger o acesso que o cdigo que est querendo realizar, protegendo os recursos e operaes. Este captulo abordar, em sua primeira parte, como utilizar o CAS, que fornecido juntamente com o .NET Framework e, como configurar devidamente a aplicao para evitar problemas relacionados a segurana. J a segunda parte, analisaremos a segunraa baseada em roles. Conceitos bsicos Code Access Security Toda aplicao que executado sob a plataforma .NET interagi com o sistema de segurana Code Access Security. De acordo com as permisses que ele recebe, ele pode executar de forma legal ou no, o que gerar uma exceo de segurana. As configuraes de segurana local em um computador particular o que vai decidir, em ltima instncia, que permisses o cdigo ir receber. Como esses configuraes podem varias de computador para computador voc deve assegurar que seu cdigo ter as permisses suficientes para ser executado. Sendo assim, todo o desenvolvedor deve estar familiarizado com os seguintes conceitos de segurana, que so necessrios para todas as aplicaes escritas em utilizando a plataforma .NET: Escrever cdigo type-safe: para habilitar o benefcio da utilizao do Code Access Security voc deve utilizar um compilador que gere o cdigo verifiably type-safe. 1

Israel Aece | http://www.projetando.net

Captulo 9 Utilizando Code Access Security CAS

Sintaxe imperativa e declarativa: so as duas formas que temos para interagir com o Code Access Security. Com a primeira delas, utilizamos as classes de forma programtica, ou seja, como j fazemos na maioria das vezes com outros objetos; a segunda forma, declarativa, permite decorarmos os tipos com atributos que definem a forma de segunraa a ser aplicada ao membro. Requerimento de permisses: a forma que o teu cdigo informa ao runtime as permisses que ele precisa para poder ser executada. Esses requerimentos so analisados pelo runtime durante a carga do cdigo para a memria. Esse tipo de permisso aplicado a nvel de Assembly. Secure Class Libraries: uma biblioteca segura uma biblioteca que utiliza a segurana sob demanda para assegurar que o chamador tem ou no permisso para acessar um determinado recurso.

Como j vimos, o Code Access Security so as permisses que concedemos ao cdigo para que o mesmo pode ser executado. Mas a segurana no se resume somente a isso; existe uma poro de conceitos que precisamos analisar detalhadamente para, em seguida, aplicar efetivamente em nosso cdigo. Cada um desses conceitos so analisados a seguir. Evidence Evidncia A evidncia o conjunto de informaes sobre um determinado Assembly. Entre essas informaes temos a identidade e origem do Assembly. O Code Access Security utiliza essa evidncia do Assembly e a poltica de segurana corrente do computador onde o mesmo est sendo executado, para determinar se ele possui ou no permisses suficientes para acessar um determinado recurso. Toda aplicao .NET roda em um AppDomain, sob o controle do host que cria o AppDomain e carrega os Assemblies para dentro do mesmo. O host tem acesso a evidncia do(s) Assembly(ies) que est(o) dentro deste AppDomain. Atualmente temos dois tipos principais de evidncias: a nvel de Host e a nvel de Assembly. Por padro, o .NET Framework utiliza somente a evidncia a nvel de host, que a informao fornecida a partir do AppDomain onde a aplicao executada. Tipicamente, a evidncia do host vai informar a origem do Assembly e se ele est devidamente assinado (strongname). J a evidncia a nvel de Assembly fornecida a partir do prprio Assembly, e pode ser definida a partir dos desenvolvedores ou administradores. A evidncia de um Assembly poder incluir: Evidncia All Code Diretrio Aplicao Hash Condio (classe) AllMembershipCondition da O local fsico onde a ApplicationDirectoryMembershipCondition aplicao foi instalada. Um cdigo hash que HashMembershipCondition utilizado para algoritmos Israel Aece | http://www.projetando.net Descrio

Captulo 9 Utilizando Code Access Security CAS


de hashing. Assinatura do publicador do Assembly. Site de origem do Assembly. Uma chave criptogrfica, contendo o StrongName do Assembly. URL de origem do Assembly. Zona de origem do Assembly, como por exemplo a Internet.

Publicador Site StrongName URL Zona

PublisherMembershipCondition SiteMembershipCondition StrongNameMembershipCondition UrlMembershipCondition ZoneMembershipCondition

Para cada um dos itens acima, existe um classe que corresponde uma condio, utilizada em um code group, que analisaremos mais tarde ainda neste captulo. Essas classes esto informardas na terceira coluna da tabela acima. Para manipularmos isso via cdigo, temos uma classe chamada Evidence dentro do namespace System.Security.Policy. Essa classe nada mais que uma coleo que armazena um conjunto de objetos que representam as evidncias (Host ou Assembly). O trecho de cdigo abaixo exibe a forma de como devemos proceder para extrairmos as informaes de evidncia de um Assembly:
VB.NET Imports Imports Imports Imports

System System.Collections System.Reflection System.Security.Policy

Dim a As Assembly = _ [Assembly].GetAssembly(Type.GetType("System.String")) Dim e As Evidence = a.Evidence Dim i As IEnumerator = e.GetEnumerator() While i.MoveNext() Console.WriteLine(i.Current) End While C# using using using using System; System.Collections; System.Reflection; System.Security.Policy;

Assembly a = Assembly.GetAssembly(Type.GetType("System.String"));

Israel Aece | http://www.projetando.net

Captulo 9 Utilizando Code Access Security CAS


Evidence e = a.Evidence; IEnumerator i = e.GetEnumerator(); while(i.MoveNext()) Console.WriteLine(i.Current);

Permissions Permisses As permisses, que aqui tambm so conhecidas como code access permissions, so os direitos de acesso a determinado recursos do computador. O .NET Framework possui muitas classes embutidas que foram desenhadas para proteger o acesso a determinados recursos do computador onde a aplicao executada. Isso auxilia bastante, j que no precisamos escrever cdigo necessrio para efetuarmos a checagem de segurana; essas classes j tem essa finalidade. Para o exemplo, temos algumas permisses (em forma de classes) listadas abaixo: Permisso DataProtectionPermission EnvioronmentPermission EventLogPermission FileIOPermission PrintingPermission RegistryPermission SqlClientPermission StorePermission UIPermission O que protege Controla o acesso a dados criptografados em memria. Controla o acesso a variveis de ambiente. Controla o acesso ao Event Log do Windows. Controla o acesso ao sistema do arquivos. Controle o acesso as impressoras. Controla o acesso ao Registry do Windows. Controla o acesso ao banco de dados SQL Server a partir do provider, tambm fornecido pela plataforma. Controla o acesso aos repositrios de certificados X.509. Controla o acesso a criao de elementos Windows.

Essas classes esto contidas dentro do namespace System.Security.Permissions e, grande parte dessas classes, herdam direta ou indiretamente de uma classe abstrata chamada CodeAccessPermission, que define toda a estrutura para as permisses. Security Policy Polticas de Segurana As polticas de segurana determinam o mapeamento entre a evidncia do Assembly que o host fornece para o mesmo e o conjunto de permisses concedidas ao Assembly. As polticas de segurana esto divididas em quatro nveis, quais esto abaixo descritos: Nvel de Segurana Enterprise Descrio Especificada pelo administrador da rede. Contm a hierarquia de code groups que sero aplicados para todos os cdigos gerenciados que sero executados dentro da rede. Israel Aece | http://www.projetando.net 4

Captulo 9 Utilizando Code Access Security CAS


Machine User Application Domain (opcional)

Contm a hierarquia de code groups que sero aplicados para todos os cdigos gerenciados que sero executados dentro de um determinado computador. Contm a hierarquia de code groups que sero aplicados para todos os cdigos gerenciados que sero executados dentro de um usurio. Este nvel de segurana opcional fornece isolamento e limites de segurana para o cdigo gerenciado que est sendo executado.

A permisso final concedida definida uma por Assembly e, sendo assim, cada Assembly dentro da aplicao pode ter diferentes permisses. Finalmente, se desejarmos manipular as configuraes de polticas de segurana via cdigo, podemos utilizar a classe SecurityManager, que est contida dentro do namespace System.Security, que possui vrios membros estticos que permitem interagir com o sistema de segurana. Atravs desta classe, utilizamos o mtodo PolicyHierarchy que retorna um enumerador com os nveis em que as polticas de segurana se encontram:
VB.NET Imports Imports Imports Imports

System System.Collections System.Security System.Security.Policy

Dim i As IEnumerator = SecurityManager.PolicyHierarchy() While i.MoveNext() Dim p As PolicyLevel = DirectCast(i.Current, PolicyLevel) Console.WriteLine(p.Label) End While C# using using using using System; System.Collections; System.Security; System.Security.Policy;

IEnumerator i = SecurityManager.PolicyHierarchy(); while (i.MoveNext()) { PolicyLevel p = (PolicyLevel)i.Current; Console.WriteLine(p.Label); }

Permission Sets Conjunto de Permisses Israel Aece | http://www.projetando.net

Captulo 9 Utilizando Code Access Security CAS

Todas os nveis de segurana contm uma lista de conjunto de permisses. Cada um desses conjuntos representam uma espcie de conjunto de acesso confivel a determinados recursos do computador. Para exemplificar, abaixo temos uma tabela com os conjuntos de permisses prdefinidos pelo .NET Framework: Permission Set FullTrust SkipVerification Execution Nothing LocalIntranet Descrio Fornece acesso a todos os recursos protegidos por permisses. Permite que a checagem de segurana no seja realizada. Fornece permisso apenas para o cdigo ser executado, no permitindo acesso qualquer recurso protegido. No fornece nenhum tipo de permisso, impedindo-o de ser executado. Permite acesso para execuo do cdigo, criao de elementos a nvel de interface sem qualquer restrio, acesso ao isolated storage sem limite de quota, utilizar servios de DNS, ler algumas variveis de ambiente, realizar conexes com a site de onde o Assembly se originou e ler arquivos que esto dentro da mesma pasta do Assembly. Permite acesso para execuo do cdigo, criar janelas e caixas de dilogos, realizar conexes com a site de onde o Assembly se originou e acessar o isolated storage com quota. Fornece todas as permisses padres, exceto as permisses para a opo SkipVerification.

Internet Everything

O trecho de cdigo abaixo utiliza a classe SecurityManager para recuperar todos os nveis de polticas de segurana (enterprise, machine e user) e seus respectivos conjuntos de permisses da mquina em que o cdigo executado:
VB.NET Imports Imports Imports Imports

System System.Collections System.Security System.Security.Policy

Dim i As IEnumerator = SecurityManager.PolicyHierarchy() While i.MoveNext() Dim p As PolicyLevel = DirectCast(i.Current, PolicyLevel) Console.WriteLine(p.Label) Dim np As IEnumerator = p.NamedPermissionSets.GetEnumerator() While np.MoveNext() Dim pset As NamedPermissionSet = _ DirectCast(np.Current, NamedPermissionSet) Name: Console.WriteLine("\tPermission Set: \n\t\t {0}\n\t\t Description: {1}", pset.Name, pset.Description) End While

Israel Aece | http://www.projetando.net

Captulo 9 Utilizando Code Access Security CAS


End While C# using using using using System; System.Collections; System.Security; System.Security.Policy;

IEnumerator i = SecurityManager.PolicyHierarchy(); while (i.MoveNext()) { PolicyLevel p = (PolicyLevel)i.Current; Console.WriteLine(p.Label); IEnumerator np = p.NamedPermissionSets.GetEnumerator(); while (np.MoveNext()) { NamedPermissionSet pset = (NamedPermissionSet)np.Current; Name: Console.WriteLine("\tPermission Set: \n\t\t {0}\n\t\t Description: {1}", pset.Name, pset.Description); } }

Code Groups Grupos de cdigos Os code groups so o corao do sistema de segurana do .NET Framework. Ele consiste em uma expresso condicional que, se for atendida, concede um determinado conjunto de permisses (permission set) que esto associadas ao code group. Em tempo de execuo, a condio avaliada comparando as informaes do code group com o a evidncia que foi extrada do Assembly. Para exemplificar, podemos dizer que ele somente ter acesso ao sistema de arquivos, se o publicador do Assembly for a Empresa ABC. Os code groups so armazenados em um formato de uma rvore lgica e, se a condio A no for atendida, as condies abaixo dela no sero analisadas e, conseqentemente, o cdigo no ter acesso as permisses que a mesma poderia vir a conceder. A imagem abaixo ilustra de forma bem clara a hierarquia dos code groups e como eles so avaliados em tempo de execuo:

Israel Aece | http://www.projetando.net

Captulo 9 Utilizando Code Access Security CAS

Imagem 9.1 Avaliando as condies. Como podemos analisar, quando um determinado code group no atende a um determinado critrio (quadrados na cor vermelha), as permisses relacionadas a ele so negadas e, conseqentemente, o que vem abaixo (quadrados na cor laranja) no avaliado. Esse processo repetido para todos os nveis das polticas de segurana (enterprise, machine e user). Em tempo de execuo, essas condies so transformadas em classes do tipo xxxMembershipCondition que vimos um pouco mais acima. Essas classes implementam a Interface IMembershipCondition que define um teste para determinar se o cdigo do Assembly membro de um determinado code group. Stack Walk Uma das partes essencias do sistema de segurana o processo que chamamos de stack walk. Quando um determinado mtodo chamado, os dados referente ao mesmo (parmetros que so passados para o mtodo, o endereo de retorno quando o mtodo retornar e variveis locais) so colocados em uma espcie de pilha de chamadas, call stack. Cada um desses registros so tambm chamados de stack frame. Em determinados estgios da execuo do cdigo, a thread que est executando pode precisar acessar um recurso protegido, como por exemplo, o sistema de arquivos. Antes de efetivamente conceder acesso a esse determinado recurso, sob demanda, uma verificao efetuada em toda a call stack, analisando se todos os chamadores possuem Israel Aece | http://www.projetando.net 8

Captulo 9 Utilizando Code Access Security CAS

direitos ao recurso solicitado. Neste momento, se algum dos chamadores no tiver a permisso necessria, uma exceo atirada. Esse processo chamado de stack walk. Para fazer uma analogia em um mundo real, imagine que h uma pessoa que deseja alugar um livro em uma biblioteca mas ela no tem um cadastro na mesma. Imagine que essa pessoa pedi um favor para algum que tenha esse cadastro, para que ele possa pegar o livro na biblioteca e, em seguida, o emprestar. Como o bibliotecrio nada sabe sobre o que se passa, ele analisar o cadastro da pessoa que for diretamente at a biblioteca e, estando com o cadastro correto, alugar o livro sem maiores problemas. Esse processo mostrado atravs da imagem abaixo:

Imagem 9.2 Luring Attack. Apesar dessa forma ser executada sem maiores problemas, isso no traz uma segurana para a biblioteca. Trazendo para o mundo de desenvolvimento de software, entendemos isso como um ataque, chamado de Luring Attack. O luring attack um tipo de ataque que eleva os privilgios de quem o executa, concedendo mais privilgios do que realmente ele possui. Para evitar o luring attack, o .NET capaz de analisar toda a cadeia de chamadores e analisar se todos eles possuem ou no os direitos necessrios quando algum recurso protegido requisitado. Ainda dentro do nosso exemplo, quando o Jos Torres chegar ao bibliotecrio para alugar o livro, o bibliotecrio ir analisar todos os chamadores que fazem parte do processo e, ir identificar que o Manoel Costa no tem cadastro na biblioteca, o que banir o aluguel do livro. A imagem abaixo ilustra esse processo:

Israel Aece | http://www.projetando.net

Captulo 9 Utilizando Code Access Security CAS

10

Imagem 9.3 Evitando o Luring Attack. Como vimos anteriormente, temos classes que so nomeadas com um sufixo Permission. Essas classes herdam da classe abstrata CodeAccessPermission e protegem determinados recursos de um computador. Essa classe abstrata fornece quatro principais mtodos (de instncia) que, so utilizados para determinar se o cdigo possui ou no acesso ao recurso que a instncia representa. Esses mtodos so listados e descritos atravs da tabela abaixo: Mtodo Assert Descrio Concede acesso ao recurso protegido pela instncia mesmo que os chamadores no tenham permisso para isso. Esse mtodo pode ser utilizado quando a sua biblioteca necessita acessar um recurso protegido de forma completamente oculta aos chamadores. De qualquer forma, utilize esse mtodo com muito cuidado, porque ele pode deixar a sua aplicao/biblioteca vulnervel a ataques. Ao chamar esse mtodo, a checagem realizada em toda a stack (de cima para baixo), forando uma exceo do tipo SecurityException ser atirada, se algum dos chamadores dentro da stack no tiver a permisso para o recurso protegido pela instncia. Esse mtodo geralmente utilizado por bibliotecas que precisam assegurar que o chamador tem acesso a um recurso protegido. Israel Aece | http://www.projetando.net

Demand

10

Captulo 9 Utilizando Code Access Security CAS


Deny

11

Previne os chamadores dentro da stack de acessar o recurso protegido pela instncia, mesmo que eles tenham permisso para isso. A imagem abaixo ilustra o processo da utilizao deste mtodo:

Imagem 9.4 Utilizao do mtodo Deny. Como sabemos, o mtodo Demand executa a checagem dentro da stack para verificar se todos os chamadores possuem a permisso necessria para acessar o recurso. Na imagem acima, quando o Mtodo B avaliado, vemos que dentro dele, o mtodo Deny foi chamado, o que evita que a permisso seja concedida. Note que, ao encontrar a chamada para o mtodo Deny, o Mtodo A no chega a ser avaliado, pois independente do resultado, a permisso no ser concedida. Em essncia, o mtodo PermitOnly tem o mesmo efeito do mtodo Deny, mas define uma condio diferente de como a segurana deve ser analisada para conceder ou negar acesso um determinado recurso. Ao invs de dizer que um recurso especfico no pode ser acessado ( o que o mtodo Deny faz), o mtodo PermitOnly informa somente os recursos que voc quer conceder acesso. Se voc chamar o mtodo PermitOnly em uma permisso X, o mesmo que chamar que chamar o mtodo Deny para todas as permisses, com exceo da permisso X. Alm desses mtodos, a classe CodeAccessPermission ainda fornece quatro mtodos estticos que so utilizados para reverter alguma das ordens acima, que foram aplicadas pelos mtodos Assert, Deny ou PermitOnly, fazendo com que essas ordens sejam desfeitas. A tabela abaixo descreve cada um desses mtodos: Israel Aece | http://www.projetando.net 11

PermitOnly

Captulo 9 Utilizando Code Access Security CAS


Mtodo RevertAll

12

Descrio Esse mtodo desfaz todos as ordens realizadas pelos mtodos Assert, Deny ou PermitOnly e, se nenhum desses mtodos foi previamente invocado, uma exceo do tipo ExecutionEngineException ser atirada. RevertAssert Remove qualquer instruo gerada pelo mtodo Assert dentro de um mesmo frame. Se o mtodo Assert no foi previamente invocado, uma exceo ExecutionEngineException do tipo ser atirada. RevertDeny Remove qualquer instruo gerada pelo mtodo Deny dentro de um mesmo frame. Se o mtodo Deny no foi previamente invocado, uma exceo ExecutionEngineException do tipo ser atirada. RevertPermitOnly Remove qualquer instruo gerada pelo mtodo PermitOnly dentro de um mesmo frame. Se o mtodo PermitOnly no foi previamente invocado, uma exceo ExecutionEngineException do tipo ser atirada. Voc deve utilizar esses mtodos com extremo cuidado porque eles modificam a forma com que o Code Access Security avalia a stack walk, o que pode possibilitar que sua aplicao sofra com ataques do tipo luring attack, como vimos nas imagens acima. Geralmente, a checagem de segurana examina todos os chamadores que esto dentro da stack para assegurar que cada um deles possuem as devidas permisses para acessar o recurso protegido que est sendo solicitado. Entretanto, atravs dos mtodos acima podemos sobrescrever esse comportamento e, conseqentemente, customizar a forma com que o Code Access Security concede ou nega o acesso um determinado recurso. Esse processo conhecido como Overriding Security Checks. Toda vez que um mtodo chama outro, um novo frame gerado dentro da stack para armazenar informaes a respeito do mtodo que est sendo invocado. Cada um desses frames contm informaes sobre a chamada de qualquer um dos mtodos Assert, Deny ou PermitOnly e, se o chamador utiliza mais que um desses mtodos dentro do mesmo local (mtodo), o runtime aplica as seguintes regras: Se, durante a checagem da stack walk, o runtime descobrir mais que uma chamada para um mesmo mtodo (Assert, Deny ou PermitOnly) dentro do mesmo frame, o segundo causar uma exceo. Quando houver chamadas diferentes aos mtodos Assert, Deny ou PermitOnly dentro do mesmo frame, o runtime os processar na seguinte ordem: PermitOnly, Deny e, finalmente, o Assert. 12

E como vimos na tabela dos mtodos Revert***, voc utiliza-os quando desejar reverter qualquer uma das operaes previamente executadas. Ferramentas Israel Aece | http://www.projetando.net

Captulo 9 Utilizando Code Access Security CAS

13

Com exceo das classes que o .NET Framework fornece para customizarmos via cdigo o que a nossa aplicao necessita para poder funcionar, ainda h duas ferramentas teis, quais foram disponibilizadas com a instala do .NET Framework, que permitem interagirmos com o sistema de segurana, modificando as polticas de segurana da mquina, do usurio ou da rede. A primeira delas, chamada de .NET Framework 2.0 Configuration. Essa ferramenta permite-nos configurarmos no somente a parte de segurana, mas o GAC e outras coisas que esto fora do escopo deste captulo. Se reparar na imagem abaixo, temos uma opo chamada Runtime Security Policy, onde podemos customizar os code groups e permissions sets para os nveis de segurana existentes. A imagem abaixo ilustra a interface desta ferramenta:

Imagem 9.5 - .NET Framework 2.0 Configuration. Para acessar essa ferramenta, v at as Ferramentas Administrativas que est dentro do Painel de Controle do Windows e clique em Microsoft .NET Framework 2.0 Configuration. Alm desta ferramenta grfica, ainda existe uma outra chamada CasPol.exe. Trata-se de um utilitrio de linha de comando que permite voc interagir com o sistema de segurana do .NET Framework e, basicamente, fornece as mesmas funcionalidades que a interface acima. 13

Israel Aece | http://www.projetando.net

Captulo 9 Utilizando Code Access Security CAS

14

Esse utilitrio disponibilizado junto com o .NET Framework e encontra-se localizado no seguinte endereo: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727. Como esses utilitrios so executados a partir de linha de comando e voc optar por abrir o Visual Studio 2005 Command Prompt, no ser necessrio digitar o caminho todo at o executvel para execut-lo. A sintaxe para a sua utilizao simples:
C:\caspol <opes> <argumentos>

Entre as opes que so aceitas, temos algumas delas listadas atravs da tabela abaixo: Opo -l -lg -lp -ag -url -n -exclusive Descrio Lista todas as informaes disponveis para a policy level padro, que o nvel Machine. Lista todos os code groups para a policy level padro. Lista todas as permissions sets. Adiciona um novo code group na policy level padro. Define a URL para um endereo HTTP ou FTP, indicando onde o Assembly se originou. Isso ser adicionado em forma de uma condio (UrlMembershipCondition). O nome para o novo code group. Definindo esta opo como on indica que qualquer Assembly que atender a condio definida pelo code group que voc est criando, ser associado com a permission set para esta policy level. Isso pode ser utilizado para nvel de testes, pois ir garantir que o cdigo que est rodando no recebe a permisso de FullTrust. Altera um code group existente. Remove um code group existente.

-cg -rg

Abaixo exibido algumas possveis formas de combinar essas opes e argumentos para a utilizao deste utilitrio:
C:\caspol l C:\caspol lg C:\caspol ag 1 url Grupo_De_Teste exclusive on C:\caspol rg Grupo_De_Teste file:///C:/Teste/* Internet n

14

Israel Aece | http://www.projetando.net

Captulo 9 Utilizando Code Access Security CAS

15

Com exceo da lista acima, temos outras opes e argumentos que podemos informar par ao utilitrio caspol.exe. Para ter acessa a todas elas, v at o MSDN Library ou, ainda no prompt de comando, digite:
C:\caspol -?

Avaliando as permisses Quando a aplicao inicializada, a mesma carregada para dentro de um processo, que chamado tambm de host. Esse host extrai e examina a evidncia do Assembly, podendo diferentes informaes serem coletadas de acordo com a origem do mesmo. A evidncia consiste, entre outras inforames, o strong-name, zona e publicador do Assembly. Depois de capturada, essa evidncia passada para o sistema de segurana do .NET Framework para que o mesmo avalie as polticas de seguranas. A partir deste momento, baseando-se na evidncia extrada do Assembly, o runtime comear a avaliar a rvore de code groups. Se a condio for atendida, dizemos que o Assembly membro do code group e, alm disso, o conjunto de permisses (permission set) vinculadas a essa condio ser concedido ao Assembly. Caso contrrio, possveis code groups que estiverem abaixo da condio no atendida, no sero avaliados e, obviamente, no sero concedidos as possveis permisses que ele poderia vir definir. Depois deste processo, a unio dos conjuntos de permisses computada e esse processo repetido para cada nvel das polticas de segurana (policy levels). Depois de todos os nveis avaliados (Enterprise, Machine, User e AppDomain), o Assembly receber a interseo de todos os grupos de permisses entre os nveis. A imagem abaixo ilustra esse todo esse processo que, a primeira vista, parece ser complicado:

15

Israel Aece | http://www.projetando.net

Captulo 9 Utilizando Code Access Security CAS

16

Imagem 9.6 Avaliando as permisses de um Assembly Como podemos notar, os nveis Enterprise, Machine, User e AppDomain so avaliados e somente so concedidos as permisses que esto contidas em todos os nveis que, no exemplo acima, so P2 e P5. Isto evitar que um usurio individual ou a aplicao conceda permisses adicionais que no foram concedidas pelo administrador (Enterprise). Ainda possvel adicionar atributos a nvel de Assembly que permite especificarmos as solicitaes de permisses necessrias, mnimas e opcionais que o Assembly necessita para poder trabalhar. Abaixo veremos cada uma dessas opes: Permisses Mnimas: especificada a partir do atributo RequestPermission, permite especificar as permisses mnimas que so necessrias para que o Assembly para executar o seu trabalho. Se essas permisses no forem concedidas, ao carregar o Assembly o cdigo no ser executado e uma exceo do tipo PolicyException ser atirada. Permisses Opcionais: especifica as permisses que seu cdigo pode utilizar para executar mas, se no for concedida, o mesmo ser capaz de executar. Neste caso, extremamente importante que voc utilize o tratamento de erros ou, de alguma outra forma, monitorar o trecho do cdigo que exige essa permisso, pois, se no fizer isso, uma exceo pode ocorrer e danificar a sua aplicao. Permisses Recusadas: especifica as permisses que seu cdigo jamais dever receber, mesmo se as polticas de segurana as concedam.

O trecho de cdigo abaixo exibe como configurar esses parmetros dentro do arquivo AssemblyInfo.vb ou AssemblyInfo.cs:
VB.NET - (AssemblyInfo.vb) Imports System.Security.Permissions

16

Israel Aece | http://www.projetando.net

Captulo 9 Utilizando Code Access Security CAS

17

<Assembly: EnvironmentPermission(SecurityAction.RequestMinimum, Read := "windir")> <Assembly: RegistryPermission(SecurityAction.RequestOptional)> <Assembly: FileIOPermission(SecurityAction.RequestRefuse, Read := "C:\")> C# - (AssemblyInfo.cs) using System.Security.Permissions; [assembly: EnvironmentPermission(SecurityAction.RequestMinimum, Read="windir")] [assembly: RegistryPermission(SecurityAction.RequestOptional)] [assembly: FileIOPermission(SecurityAction.RequestRefuse, Read = "C:\\")]

Como podemos notar no cdigo acima, definimos que o Assembly precisa, no mnimo, de permisso suficiente para ler a varivel de ambiente chamada windir; alm disso, opcionalmente, pode ter acesso ao registry do Windows onde a aplicao estiver sendo executada e, finalmente, negamos o acesso a leitura da unidade C da mquina onde a aplicao estiver sendo executada. Ao especificar a permisso, definimos se ela vai ser mnima, opcional ou recusada atravs de um enumerador chamado SecurityAction. Para encerrar, depois de todas essas checagens, o Assembly recebe o que chamamos de Final Permission Grant. Essa permisso final definida atravs do resultado da seguinte operao:
FG = SP ((M U O) R)

Onde FG a permisso final (final grant), SP o grupo de permisses (permission set) que o Assembly recebe das polticas de segurana; depois disso, as permisses mnimas (M) unem-se as permisses opcionais (O), exclundo as permisses recusadas (R). Com o resultado disso, feito uma interseo com o grupo de permisses concedidos pelas polticas de segurana e, finalmente, atribudo a permisso final do Assembly. Implementando a segurana no cdigo No cdigo, o que precisamos fazer instanciar a classe concreta da permisso, como por exemplo, a classe FileIOPermission, especificar os parmetros necessrios que a mesma exige e, atravs dos mtodos que vimos acima (Demand, Assert, Deny ou PermitOnly) modificamos o stack walk e, conseqentemente, customizamos o acesso ao recurso protegido. Israel Aece | http://www.projetando.net 17

Captulo 9 Utilizando Code Access Security CAS

18

Permisses Declarativas vs. Imperativas Como vimos acima, h duas formas de aplicarmos a segurana em uma aplicao .NET. Essas formas so conhecidas como declarativa e imperativa. Abaixo h um exemplo de cada um destes estilos: Forma Declarativa:
VB.NET <FileIOPermission(SecurityAction.Assert, Read:="C:\")> _ Public Sub ReadFile() ... End Sub C# [FileIOPermission(SecurityAction.Assert, Read:="C:\\")] public void ReadFile() { //... }

Forma Imperativa:
VB.NET Public Sub ReadFile() Dim f As New FileIOPermission(FileIOPermissionAccess.Read, "C:\") f.Assert() ... End Sub C# public void ReadFile() { new FileIOPermission(FileIOPermissionAccess.Read, "C:\\").Assert(); //... }

H algumas razes para escolher entre um estilo e outro. Um das grandes diferenas que o estilo declarativo exige que todas as regras de segurana sejam definidas em tempo de compilao, permitindo apenas aplicar essas definies em mtodos, classes ou Assemblies. Israel Aece | http://www.projetando.net

18

Captulo 9 Utilizando Code Access Security CAS

19

J o estilo imperativo te d uma maior flexibilidade ao estilo declarativo, pois permite que voc manipule a segurana em tempo de execuo, podendo assim, determinar o momento preciso de aplicar a checagem de segurana. Infelizmente, esse modo no permite extrairmos informaes de metadados relacionadas a segurana. Essa ferramenta, chamada Permission View (Permview.exe), permite extrairmos essas informaes de metadados somente a partir do estilo declarativo. Esse utilitrio fornecido somente com a verso 1.x do .NET Framework. Para utiliz-lo, necessrio apenas informar caminho at o Assembly que deseja visualizar as informaes. O cdigo abaixo ilustra como devemos proceder para invocar esse utilitrio:
C:\ permview Aplicacao.exe

Segurana baseada em papis (roles) A segurana baseada em roles permite aos desenvolvedores controlarem o acesso das aplicaes construdos sob a plataforma .NET baseando-se na identifidade do usurio. Neste caso, trabalhamos com dois conceitos chamados: identity e principal. O primeiro deles, identity, encapsula informaes a respeito da identificao do usurio que est sendo validado. Entre essas informaes temos o nome do usurio e o tipo de autenticao. Basicamente, as identities so responsveis pela autenticao do usurio. O .NET Framework fornece trs tipos de objetos de identidade: Windows Identity: representa a identidade od usurio e o mtodo de autenticao que suportado pelo sistema operacional Windows. Alm disso, esse tipo de identidade, fornece a possibilidade de personificarmos o usurio corrente para um outro usurio, talvez com mais ou menos privilgios para poder acessar um recurso protegido que, o usurio corrente talvez no tenha acesso. A classe que representa essa identidade a WindowsIdentity. Generic Identity: representa a identidade do usurio baseando em uma autenticao customizada que definida pela aplicao. A classe GenericIdentity implementa esse tipo de identidade. Custom Identity: representa uma identidade que encapsula as informaes customizadas do usurio. Qualquer identidade customizada deve implementar a Interface IIdentity que, por sua vez, fornece trs membros que so listados na tabela abaixo: Descrio Retorna uma string contendo o nome corrente do usurio. Retorna um valor booleano indicando se o usurio est ou no autenticado. Israel Aece | http://www.projetando.net 19

Membro Name IsAuthenticated

Captulo 9 Utilizando Code Access Security CAS


AuthenticationType

20

Retorna o uma string contendo o tipo de autenticao do usurio corrente.

J o principal representa o contexto de segurana em que o cdigo est sendo executado. Basicamente, as principals so responsveis pela autorizao do usurio. O .NET Framework fornece trs tipos de objetos relacionadas ao principal: Windows Principal: representa usurios do sistema operacional Windows e suas respectivas roles. Cada role representa um grupo que podem conter membros. A classe que representa essa principal a WindowsPrincipal. Generic Principal: representa usurios e suas respectivas roles, mas de forma independente ao sistema operacional. A classe GenericPrincipal implementa este tipo de principal. Custom Principal: representa uma principal que encapsula a autorizao de um determinado usurio. Qualquer principal customizada deve implementar a Interface IPrincipal.

A Interface IPrincipal possui apenas dois membros e, sendo assim, todas as classes relacionadas a principal tambm os possuem, j que implementam direta ou indiretamente a Interface IPrincipal. Os membros desta Interface so listados abaixo: Membro Identity IsInRole Descrio Retorna um objeto que implementa a Interface IIdentity representando a identidade do usurio corrente. Dado uma string contendo o nome da role (grupo), retorna um valor booleano indicando se o usurio corrente est ou no contido neste grupo.

Todas as Interfaces e classes que vimos acima esto contidas dentro do namespace System.Security.Principal. Abaixo exibido uma imagem que ilustra as classes e Interfaces que vimos acima.

20

Israel Aece | http://www.projetando.net

Captulo 9 Utilizando Code Access Security CAS

21

Imagem 9.7 Estrutura das classes identity e principal. Dentro do namespace System.Threading existe uma classe chamada Thread. Essa classe determina como controlar uma thread dentro da aplicao. Essa classe, entre vrios membros, possui uma propriedade esttica chamada CurrentPrincipal que recebe e retorna uma instncia de um objeto que implementa a Interface IPrincipal. atravs desta propriedade que devemos definir qual ser a identity e principal que ir representar o contexto do segurana para a thread atual. Sendo assim, temos que, de acordo com o tipo de autenticao/autorizao que iremos adotar na aplicao, devemos criar a instncia de uma identiy e principal e, em seguida, defin-la na propriedade CurrentPrincipal da classe Thread. Abaixo temos um exemplo utilizando as classes identity e principal relacionados ao sistema operacional Windows e tambm a forma genrica: Windows
VB.NET Imports System.Threading Imports System.Security.Principal Dim identity As WindowsIdentity = WindowsIdentity.GetCurrent() Thread.CurrentPrincipal = New WindowsPrincipal(identity) Console.WriteLine(Thread.CurrentPrincipal.Identity.Name)

21

Israel Aece | http://www.projetando.net

Captulo 9 Utilizando Code Access Security CAS

22

Console.WriteLine(Thread.CurrentPrincipal.IsInRole("Admin").ToStr ing()) C# using System.Threading; using System.Security.Principal; WindowsIdentity identity = WindowsIdentity.GetCurrent(); Thread.CurrentPrincipal = new WindowsPrincipal(identity); Console.WriteLine(Thread.CurrentPrincipal.Identity.Name); Console.WriteLine(Thread.CurrentPrincipal.IsInRole("Admin").ToStr ing());

Genrica
VB.NET Imports System.Threading Imports System.Security.Principal Dim identity As New GenericIdentity("Jose") Dim roles() As String = { "Admin", "RH", "Financeiro" } Thread.CurrentPrincipal = New GenericPrincipal(identity, roles) Console.WriteLine(Thread.CurrentPrincipal.Identity.Name) Console.WriteLine(Thread.CurrentPrincipal.IsInRole("Admin").ToStr ing()) C# using System.Threading; using System.Security.Principal; GenericIdentity identity = new GenericIdentity("Jose"); string[] roles = new string[] { "Admin", "RH", "Financeiro" }; Thread.CurrentPrincipal = new GenericPrincipal(identity, roles); Console.WriteLine(Thread.CurrentPrincipal.Identity.Name); Console.WriteLine(Thread.CurrentPrincipal.IsInRole("Admin").ToStr ing());

Utilizando a forma genrica, voc pode customizar o repositrio de onde voc pode recuperar os dados de acesso e tambm os grupos que o usurio est contido e, utiliz-los na aplicao. Um exemplo buscar os dados em um banco de dados e, se encontrado, criar os objetos identity e principal com os dados do usurio. Israel Aece | http://www.projetando.net

22

Captulo 9 Utilizando Code Access Security CAS

23

Personificao Personificar a habilidade que a thread possui para executar uma determinada tarefa em um contexto de segurana que diferente do contexto que foi criado para o processo da prpria thread. Uma das principais razes para utilizar a personificao quando voc precisa acessar um determinado recurso que, o usurio corrente no possui privilgios e, neste caso, personificamos o mesmo para que a tarefa seja executada atravs de um outro usurio, com maiores privilgios e, conseqentemente, que tenha acesso ao recurso necessitado. A classe WindowsIdentity fornece um mtodo que permite personificarmos o usurio. Esse mtodo chama-se Impersonate que, atravs da instncia da classe WindowsIdentity, ir personificar para o usurio que est definido nela. Se bem sucedido, esse mtodo retorna um objeto do tipo WindowsImpersonationContext que representa o usurio do Windows j no contexto personificado. Essa classe tambm fornece um mtodo chamado Undo que devemos utilizar para reverter a personificao, voltando ao contexto do usurio original. O exemplo abaixo mostra uma funo que, via PInvoke, verifica se existe ou no o usurio Teste e, se existir, cria um objeto do tipo WindowsIdentity, passando como parmetro o token do usurio validado para efetuar a personificao para o mesmo. Repare tambm que o mtodo Undo chamado dentro do bloco finally para garantir que o mesmo ser executado caso algum problema ocorra e assim, evitar com que o usurio fique personificado.
VB.NET Imports System Imports System.Security.Principal Imports System.Runtime.InteropServices Public Class Program <DllImport("advapi32.dll")> _ Public Shared Function LogonUser( _ ByVal lpszUsername As String, _ ByVal lpszDomain As String, _ ByVal lpszPassword As String, _ ByVal dwLogonType As Integer, _ ByVal dwLogonProvider As Integer, _ ByRef phToken As IntPtr) As Boolean End Function Public Shared Sub Main() Impersonate() End Sub

23

Israel Aece | http://www.projetando.net

Captulo 9 Utilizando Code Access Security CAS

24

Public Shared Sub Impersonate() Dim token As IntPtr Try If LogonUser("Teste", String.Empty, "123456", 2, 0, token) Then Dim identity As New WindowsIdentity(token) Dim impersonationContext As WindowsImpersonationContext = Nothing Console.WriteLine("1: " + WindowsIdentity.GetCurrent().Name) impersonationContext = identity.Impersonate() Console.WriteLine("2: " + WindowsIdentity.GetCurrent().Name) Finally impersonationContext.Undo() Console.WriteLine("3: " + WindowsIdentity.GetCurrent().Name) End Try End If Finally token = IntPtr.Zero End Try End Sub End Class C# using System; using System.Security.Principal; using System.Runtime.InteropServices; class Program { [DllImport("advapi32.dll")] private static extern bool LogonUser( String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken); static void Main() { WindowsIdentity identity = WindowsIdentity.GetCurrent(); Impersonate(); } private static void Impersonate() Try

24

Israel Aece | http://www.projetando.net

Captulo 9 Utilizando Code Access Security CAS


{

25

IntPtr token; try {

out token))

if (LogonUser("Teste", string.Empty, "123456", 2, 0, {

= new WindowsIdentity identity WindowsIdentity(token); WindowsImpersonationContext impersonationContext = null; try {

Console.WriteLine("1: WindowsIdentity.GetCurrent().Name); impersonationContext identity.Impersonate(); Console.WriteLine("2: WindowsIdentity.GetCurrent().Name); } finally { impersonationContext.Undo(); Console.WriteLine("3: WindowsIdentity.GetCurrent().Name); } } } finally { token = IntPtr.Zero; } } }

"

+ =

"

"

25

Israel Aece | http://www.projetando.net

Captulo 10 Envio de Mensagens (E-mails)


Captulo 10 Envio de Mensagens (E-mails) Introduo

muito comum todo e qualquer tipo de aplicao enviar e-mails para satisfazer um determinado processo ou notificar algum de que uma condio foi alcanada. Felizmente, o .NET Framework fornece intrinsicamente, sem a necessidade de utilizar componentes de terceiros, um conjunto de classes que podem ser utilizadas para construir e enviar e-mails. Nas verses 1.x do .NET Framework, as classes relacionado ao envio de e-mails estavam contidas em uma namespace chamado System.Web.Mail, dentro do Assembly System.Web.dll. Como envio de e-mails no uma exclusividade de aplicaes Web, isso ficou um pouco confuso e ainda, necessrio fazermos a referncia ao Assembly System.Web.dll em uma aplicao Windows Forms se l quisermos enviar e-mails. Definitivamente isso no faz sentido. Na verso 2.0 do .NET Framework isso foi mudado e agora essas classes esto contidas dentro do namespace System.Net.Mail, prontas para serem utilizadas. Essas classes fornecem toda a infraestrutura para a criao de e-mails, possibilidade de anexar vrios destinatrios (inclusive em cpia carbono), anexar arquivos e embutir arquivos (imagens) no corpo da mensagem, utilizadas para compor a mensagem. Alm da criao, ainda temos uma classe importante, chamada de SmtpClient, que encapsula todo o processo de envio da mensagem. Atravs deste captulo, analisaremos as principais classes e como proceder para criar e enviar e-mail a partir de aplicaes .NET. Criando um Email Para que possamos construir um e-mail precisamos utilizar a classe MailMessage. Como o prprio nome diz, essa classe representa um e-mail, contendo os arquivos em anexo, o remetente, destinatrio, assunto, corpo, etc.. A instncia desta classe pode ser passada para o mtodo Send da classe SmtpClient para que possa definitivamente enviar o e-mail ao destinatrio. Analisaremos essa classe com mais detalhes nas prximas sees. Para familiarizarmos melhor com a classe MailMessage, a tabela abaixo mostra as principais propriedades que ela expe e que podemos utilizar para configur-la: Propriedade AlternateViews Descrio Esta coleo representa cpias de um mesmo e-mail em diferentes formatos, ou seja, voc pode ter uma verso do e-mail em HTML e uma verso do e-mail em texto puro, para aqueles clientes que no conseguem visualizar o contedo da mensagem em formato HTML. Uma coleo de elementos do tipo Attachment que Israel Aece | http://www.projetando.net

Attachments

Captulo 10 Envio de Mensagens (E-mails)

Bcc

indica um determinado arquivo que ser anexado mensagem. Uma coleo de elementos do tipo MailAddress que indica os endereos que recebero uma cpia do e-mail, mas ficaro ocultos.

Os destinatrios colocados nesta seo no so visualizados pelos receptores do e-mail. Body Uma string contendo o corpo da mensagem. Essa string poder conter tags HTML se o corpo do e-mail for criado baseando-se em HTML. BodyEncoding Define o encoding do corpo do e-mail. CC Uma coleo de elementos do tipo MailAddress que indica os endereos que esto copiados no e-mail e que, conseqentemente, recebero uma cpia do e-mail. DeliveryNotificationsOptions Especifica se uma notificao dever ser enviado ao remetente do e-mail. Essa propriedade definida com uma das opes especificados pelo enumerador DeliveryNotificationOptions, que fornece os seguintes valores: Delay Notifica se a entrega est atrasada. Never Nunca notifica. None Sem notificao. OnFailure Notifica se a entrega falhou. OnSuccess Notifica se a entrega foi feita com sucesso. Recebe um objeto do tipo MailAddress contendo as informaes a respeito do remetente da mensagem. Trata-se de uma coleo do tipo NameValueCollection, com as chaves do cabealho que so transmitidos com o e-mail. Especifica um valor booleano indicando se o corpo da mensagem est ou no em formato HTML. Indica a prioridade da mensagem atravs do enumerador MailPriority. As opes que ele fornece so: High Prioridade alta. Low Prioridade baixa. Normal Prioriedade normal. Recebe um objeto do tipo MailAddress que utilizado, ao invs da propriedade From, quando o usurio responder ao e-mail. Recebe um objeto do tipo MailAddress que utilizado Israel Aece | http://www.projetando.net

From Headers IsBodyHtml Priority

ReplyTo Sender

Captulo 10 Envio de Mensagens (E-mails)

Subject SubjectEncoding To

como remetente do e-mail. Uma string contendo o assunto do e-mail. Define o encoding do corpo do assunto. Recebe um objeto do tipo MailAddress contendo as informaes a respeito do destinatrio da mensagem.

A classe MailAddress que mencionamos vrias vezes na tabela acima, trata-se de um objeto que representa um endereo de correio eletrnico, independente se ele remetente ou destinatrio. Essa classe tem apenas quatro propriedades: Address, DisplayName, Host e User. A primeira delas, Address, recebe uma string contendo o endereo de e-mail; a seguir, temos a propriedade DisplayName, que tambm recebe uma string onde podemos definir o nome amigvel a ser exibido que alguns leitores de e-mail utilizam para exibir; a terceira delas, a propriedade Host, trata-se de uma propriedade de somente leitura que retorna uma string contendo o host informado na propriedade Address; finalmente, a propriedade User, tambm retorna uma string contendo o nome do usurio (a primeira parte, antes do @) informado na propriedade Address. As propriedade To, CC e Bcc expe uma coleo fortemente tipada do tipo MailAddressCollection que somente operam com objetos do tipo MailAddress. O trecho de cdigo abaixo exemplifica a utilizao da classe MailMessage em conjunto com a classe MailAddress:
VB.NET Imports System.Net.Mail Dim de As New MailAddress("israel@projetando.net", "Israel Ace Via .NET") Dim para As New MailAddress("israelaece@yahoo.com.br", "Israel Ace") Dim msg As New MailMessage(de, para) msg.Attachments.Add(New Attachment("Teste.txt")) msg.Subject = "Teste de envio no .NET" msg.Body = "<b>E-mail enviado via .NET 2.0</b>" msg.IsBodyHtml = True C# using System.Net.Mail; MailAddress de = new MailAddress("israel@projetando.net", "Israel Ace - Via .NET"); MailAddress para = new MailAddress("israelaece@yahoo.com.br", "Israel Ace"); MailMessage msg = new MailMessage(de, para); msg.Attachments.Add(new Attachment("Teste.txt")); msg.Subject = "Teste de envio no .NET"; msg.Body = "<b>E-mail enviado via .NET 2.0</b>"; msg.IsBodyHtml = true;

Israel Aece | http://www.projetando.net

Captulo 10 Envio de Mensagens (E-mails)

Uma alternativa ao cdigo acima para deixar o contedo do e-mail mais flexvel a diversos leitores, utilizar os AlternateViews para criar verses diferentes do mesmo corpo do e-mail, para que seja possvel que usurios que suportam HTML e aqueles no suportam, consigam visualizar a mensagem. Neste caso, o cdigo tem uma mudana um pouco radical na definio do corpo do e-mail, ou seja, no ser mais necessrio definir a propriedade Body, pois criaremos isso a partir da classe AlternateView. O cdigo abaixo ilustra apenas a criao dos AlternateViews, mantendo o restando do cdigo idntico ao que temos acima:
VB.NET Imports System.Net.Mail Imports System.Net.Mime msg.AlternateViews.Add( _ AlternateView.CreateAlternateViewFromString( _ "<b>E-mail enviado via .NET 2.0 - HTML</b>", _ Nothing, _ MediaTypeNames.Text.Html)) msg.AlternateViews.Add( _ AlternateView.CreateAlternateViewFromString( _ "E-mail enviado via .NET 2.0 - Plain Text", _ Nothing, _ MediaTypeNames.Text.Plain)) C# using System.Net.Mail; using System.Net.Mime; msg.AlternateViews.Add( _ AlternateView.CreateAlternateViewFromString( "<b>E-mail enviado via .NET 2.0 - HTML</b>", null, _ MediaTypeNames.Text.Html)); msg.AlternateViews.Add( AlternateView.CreateAlternateViewFromString( "E-mail enviado via .NET 2.0 - Plain Text", null, MediaTypeNames.Text.Plain));

O mtodo esttico CreateAlternateViewFromString retorna um objeto do tipo AlternateView com o body pr-configurado. Para esse mesmo mtodo, passamos como

Israel Aece | http://www.projetando.net

Captulo 10 Envio de Mensagens (E-mails)

ltimo parmetro, o tipo da visualizao, indicando atravs da classe MediaTypeNames. A imagem abaixo ilustra o e-mail recebido dentro do Microsoft Outlook:

Imagem 11.1 E-mail recebido no Microsoft Outlook. Embutindo imagens como recursos A verso 2.0 do .NET Framework j traz intrinsicamente um recurso que nas verses anteriores somente conseguamos com a utilizao de componentes de terceiros; trata-se da opo de agora podermos embutir dentro do e-mail imagens que faro parte do contedo do mesmo. Nas verses anteriores, se no quisssemos utilizar componentes de terceiros, tnhamos que disponibilizar em algum lugar pblico, geralmente imagens, que iriam fazer parte do contedo do email e, atravs do acesso via HTTP, a exibamos no inteiro do corpo do email. O ponto negativo disso que o usurio que est lendo o e-mail depende de uma conexo ativa com a internet para que o consiga visualizar essas imagens. Com a verso 2.0 do .NET Framework, temos duas principais classes para trabalharmos com isso. So elas: AlternateView e LinkedResource. A primeira especifica diferentes cpias do contedo do email, ou seja, voc define o e-mail com o formato e tags HTML e, se o leitor de e-mails do destinatrio no suportar HTML, voc pode fornecer atravs desta classe, uma verso em plain-text do mesmo contedo. J a segunda classe, representa um recurso externo que ser embutido dentro do contedo do email que, na maioria dos casos, uma imagem. Depois desta classe criada, o adicionamos na coleo de LinkedResources do objeto AlternateView. O cdigo abaixo mostra-nos como devemos proceder para conseguirmos enviar um email com uma imagem embutida no corpo do mesmo: 5

Israel Aece | http://www.projetando.net

Captulo 10 Envio de Mensagens (E-mails)


VB.NET Imports System.Net.Mail Imports System.Net.Mime

Dim de As New MailAddress("israel@projetando.net", "Israel Ace ") Dim para As New MailAddress("israelaece@yahoo.com.br", "Israel Ace") Dim msg As New MailMessage(de, para) msg.Subject = "Teste de E-mail" Dim body As String = _ "<img src=""cid:Imagem1"" /><br><br><b>E-mail .NET 2.0</b>"

enviado

via

Dim view As AlternateView = _ AlternateView.CreateAlternateViewFromString(body, MediaTypeNames.Text.Html) Dim resource As New LinkedResource("Logo.gif") resource.ContentId = "Imagem1" view.LinkedResources.Add(resource) msg.AlternateViews.Add(view) C# using System.Net.Mail; using System.Net.Mime;

Nothing,

MailAddress de = new MailAddress("israel@projetando.net", "Israel Ace"); MailAddress para = new MailAddress("israelaece@yahoo.com.br", "Israel Ace"); MailMessage msg = new MailMessage(de, para); msg.Subject = "Teste de E-mail"; string body = @"<img src=""cid:Imagem1"" /><br><br><b>E-mail .NET 2.0</b>";

enviado

via

AlternateView view = AlternateView.CreateAlternateViewFromString(body, MediaTypeNames.Text.Html); LinkedResource resource = new LinkedResource("Logo.gif"); resource.ContentId = "Imagem1"; view.LinkedResources.Add(resource); msg.AlternateViews.Add(view);

null,

Israel Aece | http://www.projetando.net

Captulo 10 Envio de Mensagens (E-mails)

Como podemos analisar no cdigo acima, criamos uma classe do tipo MailMessage, como j fazamos nas verses anteriores. Dentro do contedo do e-mail (body), definimos a tag img e o atributo src que corresponder a imagem no local que desejarmos. Atravs do cdi especificamos que o contedo ser "substitudo" pelo contedo que mais tarde vamos vir a embutir. Atravs do mtodo esttico CreateAlternateViewFromString, onde passamos o corpo da mensagem e o tipo que ela ir ser (no caso HTML), devolvemos uma instancia da classe AlternateView baseada nesses mesmos parmetros. Depois disso, criamos um objeto do tipo LinkedResource, onde vamos definir a imagem (ou recurso) que vamos embutir. importante dizer que a propriedade ContentId deve ter exatamente o mesmo ID que definimos no cid do corpo da mensagem. Agora basta adicionarmos o objeto na coleo de LinkedResources do objeto AlternateView e, este por sua vez, adicionarmos na coleo de Views do objeto MailMessage. A imagem abaixo ilustra o e-mail, dentro do Microsoft Outlook, j com a imagem embutida:

Imagem 11.2 E-mail com imagem embutida. A classe SmtpClient Depois da mensagem criada, necessrio envi-la para o seu destino. A classe MailMessage no tem funcionalidade para isso e, neste momento, utilizaremos a classe SmtpClient. Essa classe permite enviar e-mails atravs do protocolo SMTP. Para que o envio seja possvel, necessrio que voc informe os seguintes dados: O host que o servidor SMTP que voc utilizar para enviar o e-mail. Essa informao pode ser definida no construtor da classe SmtpClient ou atravs da propriedade Host. Israel Aece | http://www.projetando.net 7

Captulo 10 Envio de Mensagens (E-mails)


Credenciais para autenticao, se requerida, podendo ser configurada atravs da propriedade Credentials, tambm da classe SmtpClient. Endereo do remetente, destinatrio(s) e o contedo a ser enviado. Tudo isso definido na classe MailMessage, que vimos acima como configur-la.

A configurao dessa classe pode ser realizada de duas formas: via cdigo ou declarativamente, atravs do arquivo *.config da aplicao. Basicamente, a diferena que a segunda opo te fornece uma flexibilidade maior, j que as informaes no ficam em hard-code. Para exemplificar, vamos analisar as duas formas, a comear pela configurao via cdigo:
VB.NET Imports System.Net.Mail Dim msg As New MailMessage() configurao do MailMessage suprimido Dim smtp As New SmtpClient("mail.servidor.com.br") smtp.Send(msg) C# using System.Net.Mail; MailMessage msg = new MailMessage(); //configurao do MailMessage suprimido SmtpClient smtp = new SmtpClient("mail.servidor.com.br") smtp.Send(msg);

Caso o servidor de SMTP necessite de autenticao, ento necessrio criar uma instncia da classe NetworkCredential, contida no namespace System.Net, informando o userName e o password e, em seguida, atribuir a instncia desta classe na propriedade Credentials da classe SmtpClient. A classe SmtpClient utiliza o mtodo Send, passando uma instncia de uma classe MailMessage para enviar. A classe SmtpClient ainda permite o envio assncrono de email, ou seja, ela fornece um mtodo chamado SendAsync que, podemos trabalhar em conjuto com o evento SendCompleted que ser disparado quando o envio do e-mail for completado. Esse evento utiliza o delegate SendCompletedEventHandler que define como argumento um objeto do tipo AsyncCompletedEventArgs, que retorna informaes a respeito do processo de envio do e-mail. Ambas classes esto contidas dentro do namespace System.ComponentModel. O trecho de cdigo abaixo ilustra como proceder para enviar o e-mail de forma assncrona:

Israel Aece | http://www.projetando.net

Captulo 10 Envio de Mensagens (E-mails)


VB.NET Imports System.ComponentModel Imports System.Net.Mail Dim msg As New MailMessage() configurao do MailMessage suprimido Dim smtp As New SmtpClient("mail.servidor.com.br") AddHandler smtp.SendCompleted, AddressOf Callback smtp.SendAsync(msg, Nothing) ... Public Sub Callback(ByVal sender As Object, _ ByVal e As AsyncCompletedEventArgs) If Not IsNothing(e.Error) Then Console.WriteLine(e.Error.Message) End If End Sub C# using System.ComponentModel; using System.Net.Mail; MailMessage msg = new MailMessage(); //configurao do MailMessage suprimido SmtpClient smtp = new SmtpClient("mail.servidor.com.br") smtp.SendCompleted += new SendCompletedEventHandler(Callback); smtp.SendAsync(msg, null); //...

private void Callback(object sender, AsyncCompletedEventArgs e) { if (e.Error != null) { Console.WriteLine(e.Error.Message); } }

O segundo parmetro (definido como Nothing no exemplo) que passado para o mtodo SendAsync um objeto do tipo System.Object que ser devolvido dentro do mtodo de Callback, atravs da propriedade UserState do objeto AsyncCompletedEventArgs. Tratamento de Erros

Israel Aece | http://www.projetando.net

Captulo 10 Envio de Mensagens (E-mails)

10

Quando a classe SmtpClient no consegue, por algum motivo, enviar o e-mail, algumas excees especficas podem ser atiradas. Ainda dentro do namespace System.Net.Mail, temos algumas excees que, como j sabemos, herdam direta ou indiretamente da classe Exception, quais so atiradas quando algum problema ocorrece. Para entendermos a hierarquia das excees dentro deste namespace, vamos analisar a imagem abaixo:

Imagem 11.3 Hierarquia das excees do namespace System.Net.Mail. Abaixo est a descrio para cada uma das excees: System.Net.Mail.SmtpException: representa uma exceo que atirada pela classe SmtpClient quando no possvel completar a operao de envio, invocado pelo mtodo Send ou SendAsync. A propriedade StatusCode contm o cdigo do status, retornado pelo servidor de SMTP. System.Net.Mail.SmtpFailedRecipientException: representa uma exceo que atirada pela classe SmtpClient quando no possvel completar a operao de envio para um destinatrio especfico, invocado pelo mtodo Send ou SendAsync. System.Net.Mail.SmtpFailedRecipientsException: representa uma exceo que atirada pela classe SmtpClient quando no possvel entregar a mensagem para todos os destinatrios. Como pode ocorrer erros durante o envio de e-mails, necessrio envolver a chamada do mtodo Send ou o mtodo SendAsync em um bloco Try/Catch para capturar a falha e no corromper o seu cdigo. importante lembrar que a ordem dos blocos Catchs devem ser ordenados do mais especfico para o mais genrico, que exatamente a ordem de baixo para cima da imagem 11.3. Com isso conseguimos customizar a mensagem de erro para o usurio e tomar uma deciso mais compatvel com o problema ocorrido. O cdigo abaixo exemplifica o uso:

10

Israel Aece | http://www.projetando.net

Captulo 10 Envio de Mensagens (E-mails)


VB.NET Imports System.Net.Mail Try Dim msg As New MailMessage() configurao do MailMessage suprimido

11

Dim smtp As New SmtpClient("mail.servidor.com.br") smtp.Send(msg) Catch e As SmtpFailedRecipientsException Console.WriteLine(e.ToString()) Catch e As SmtpFailedRecipientException Console.WriteLine(e.ToString()) Catch e As SmtpException Console.WriteLine(e.ToString()) Catch e As Exception Console.WriteLine(e.ToString()) End Try C# using System.Net.Mail; try {

MailMessage msg = new MailMessage(); //configurao do MailMessage suprimido SmtpClient smtp = new SmtpClient("mail.servidor.com.br") smtp.Send(msg);

} catch(SmtpFailedRecipientsException e) { Console.WriteLine(e.ToString()); } catch(SmtpFailedRecipientException e) { Console.WriteLine(e.ToString()); } catch(SmtpException e) { Console.WriteLine(e.ToString()); } catch(Exception e) { Console.WriteLine(e.ToString()); }

11

Israel Aece | http://www.projetando.net

Captulo 11 Criando Servios do Windows


Captulo 11 Criando Servios do Windows Introduo

H situaes onde precisamos ter sistemas que rodam independentemente da interao de um usurio. Deixar a aplicao a cargo do usurio execut-la de tempo em tempo podemos ter um problema mais srio, j que o usurio pode esquecer de execut-la e, conseqentemente, uma determinada tarefa deixa de ser executada. Os servios do Windows (Windows Services) permitem executarmos uma tarefa de forma automtica, sem a interveno humana e sem a necessidade de ter um usurio logado na mquina, podendo inclusive serem inicializados automaticamente quando o sistema operacional entrar no ar. Esses servios so executados em background, sem que o usurio perceba que o processo est em execuo e so ideais para a construo de servios que exigem um processo de longa durao ou mesmo tarefas peridicas. Esses servios no possuem interface grfica, apenas ferramentas do prprio Windows (ou customizadas tambm atravs do .NET) para que voc possa interagir com o mesmo. Uma das finalidades desta captulo apresentar o namespace System.ServiceProcess (contido dentro do Assembly System.ServiceProcess.dll), que fornece as classes e tipos necessrios para a construo destes servios. Alm disso, iremos aprender como proceder para a construo, depurao e instalao de um servio do Windows. Service Control Manager SCM Uma servio Windows no pode ser simplesmente executado. Ele precisa de um ambiente para isso, que o execute de forma automtica e segura. neste momento que entra em cena o Service Control Manager SCM. O SCM permite-nos interagir com um servio do Windows, ou seja, podemos inicializar, parar e at mesmo executadr comandos em servios contidos em uma determinada mquina. Alm da interface grfica que o Windows fornece para manipularmos os servios, dentro do .NET Framework tambm temos uma classe chamada de ServiceController que permite, via cdigo, interagirmos com os servios. Criando um servio do Windows O Visual Studio .NET fornece uma template de projeto, chamada de Windows Service, que podemos utilizar para a criao de um servio do Windows. Ao criar esse tipo de projeto atravs do Visual Studio .NET, uma servio padro adicionado ao projeto e, um mtodo chamado Main criado. Esse mtodo o ponto de entrada das aplicaes .NET e, no caso do servio do Windows, tem a finalidade que de inicializar o servio atravs do mtodo esttico Run da classe ServiceBase.

Israel Aece | http://www.projetando.net

Captulo 11 Criando Servios do Windows

O Assembly gerado por esse tipo de projeto ser do EXE, podendo conter dentro dele vrios servios. Se repararmos, cada servio que criado dentro do projeto, herda diretamente de uma classe base chamada ServiceBase. Essa classe a base para todos os servios que sero hospedados dentro do Windows. A classe ServiceBase possui uma poro de membros importantes que merecem serem descritos. Atravs das tabelas abaixo, podemos analisar as principais propriedades e mtodos, repectivamente: Propriedade AutoLog CanHandlePowerEvent CanHandleSessionChangeEvent CanPauseAndContinue CanShutdown CanStop EventLog Descrio Esta propriedade recebe um valor booleano indicando se os comandos Start, Stop, Pause e Continue sero logados dentro do Event Log do Windows. Recebe um valor booleano indicando se o servio poder ou no receber notificaes de mudana do status de energia. Recebe um valor booleano indicando se o servio pode capturar eventos de mudana de sesso de um Terminal Server. Valor booleano que indica se o servio pode ser pausado e, em seguida, continuado. Valor booleano que indca se o servio deve ser notificado quando o sistema operacional estiver em processo de shutting down. Indica se o servio poder ser parado. Recebe uma instncia da classe EventLog que o servio do Windows utilizar para escrever as notificaes de Start, Stop, Pause e Continue. Ser utilizado a seo Application do Event Log para armazenar essas informaes. Define um valor inteiro que utilizado para reportar algum erro para o SCM. Recebe uma string contendo o nome que identificar o servio para o Service Control Manager (SCM). Descrio Quando implementado na classe derivada, esse mtodo executado quando o comando Continue enviado pelo SCM para o servio. Geralmente ocorre quando voc opta por continuar a execuo de um servio que est atualmente pausado. importante dizer que se a propriedade CanPauseAndContinue estiver definida com False, o SCM nunca notificar o servio e, conseqentemente, o mtodo OnContinue nunca ser invocado. Israel Aece | http://www.projetando.net

ExitCode ServiceName Mtodo OnContinue

Captulo 11 Criando Servios do Windows

OnCustomCommand Quando implementado na classe derivada, esse mtodo executado quando o SCM passa um comando customizado para o servio, permitindo assim, especificar uma funcionalidade adicional ao servio. Comandos customizados so passado a partir do mtodo ExecuteCommand da classe ServiceController, qual ser abordada mais tarde, ainda neste captulo. Quando implementado na classe derivada, esse mtodo executado quando o comando Pause enviado pelo SCM para o servio. importante dizer que se a propriedade CanPauseAndContinue estiver definida com False, o SCM nunca notificar o servio e, conseqentemente, o mtodo OnPause nunca ser invocado. Quando implementado na classe derivada, esse mtodo executado quando o status de energia alterado. Isso geralmente aplicado a notebooks. importante dizer que se a propriedade CanHandlePowerEvent estiver definida com False, o SCM nunca notificar o servio e, conseqentemente, o mtodo OnPowerEvent nunca ser invocado. Quando implementado na classe derivada, esse mtodo executado quando o evento recebido atravs de uma sesso de Terminal Server. importante dizer que se a propriedade CanHandleSessionChangeEvent estiver definida com False, o SCM nunca notificar o servio e, conseqentemente, o mtodo OnSessionChange nunca ser invocado. Quando implementado na classe derivada, esse mtodo executado quando o sistema est em processo de shutting down. importante dizer que se a propriedade CanShutdown estiver definida com False, o SCM nunca notificar o servio e, conseqentemente, o mtodo OnShutdown nunca ser invocado. Quando implementado na classe derivada, esse mtodo executado quando o comando Start enviado pelo SCM para o servio ou quando o sistema operacional inicializado (desde que o servio esteja definido para executar automaticamente). A implementao desse mtodo sempre esperado para que o servio seja til e tenha um funcionamento adequado. Quando implementado na classe derivada, esse mtodo Israel Aece | http://www.projetando.net

OnPause

OnPowerEvent

OnSessionChange

OnShutdown

OnStart

OnStop

Captulo 11 Criando Servios do Windows

executado quando o comando Stop enviado pelo SCM para o servio. Quando a propriedade CanStop definida como False, o SCM ignora o comando Stop e, conseqentemente, no o passa para o servio. A implementao desse mtodo sempre esperado para que o servio seja til e tenha um funcionamento adequado. Trata-se de um mtodo esttico que tem dois overloads. Um deles recebe uma instncia de um objeto do tipo ServiceBase; j o segundo overload, recebe um array de elementos do tipo ServiceBase. Geralmente esse mtodo chamado dentro do mtodo Main da aplicao Windows Service para servir como ponto de inicializao para o(s) servio(s). Em seguida, esse mesmo mtodo carrega o(s) servio(s) para a memria e somente inicializar efetivamente o(s) servio(s) quando o comando Start for passado pelo SCM. Vimos que podemos inicializar, pausar ou parar um determinado servio. Mas como devemos proceder para automatizar as tarefas dentro dos servios? Exemplo: queremos que ele gerencie um determinado recurso ou peridicamente execute alguma tarefa? Agora, tudo por conta dos desenvolvedores, ou seja, chegou o momento de escrevermos cdigo dentro do servio. Esse cdigo ter todo o processo para a execuo das tarefas que ele dever desempenhar. Quando precisamos peridicamente executar uma ou vrias tarefas, muito comum utilizarmos dentro dos servios do Windows o objeto Timer, que est contido dentro do namespace System.Timers. Esse objeto, dado um intervalo (em milisegundos), ele executa infinitamente (ou at o servio ser parado) e, quando o intervalo alcanado, um evento chamado Elapsed disparado, que exatamente onde voc dever colocar o cdigo a ser executado. Alm disso, em outros cenrios, podemos utilizar watchs, como o caso da objeto FileSystemWatcher, contido dentro do namespace System.IO. Esse objeto monitora um determinado local fsico do sistema de arquivos e, quando alguma mudana acontecer, ele detecta e dispara um evento que, voc pode utilizar para efetuar algum processamento. Para maiores detalhes sobre este objeto, consulte o Captulo 5 Manipulando o sistema de arquivos. Retomando o projeto de Windows Service, temos para cada servio do Windows dois arquivos relacionados. Eles compem uma nica classe, ambos os arquivos contm uma mesma classe, mas utilizando o conceito das partial classes que, depois de compilado, transforma-se em apenas uma nica classe. A tabela abaixo descreve a finalidade de cada um dos arquivos: Israel Aece | http://www.projetando.net

Run (esttico)

Captulo 11 Criando Servios do Windows


Arquivo Service1.Designer.vb Service1.Designer.cs

Service1.vb Service1.cs

Descrio Este arquivo contm a herana da classe base ServiceBase. Alm disso, possui todas as informaes a nvel de inicializao do servio e tambm uma parte visual (j que herda indiretamente da classe Component, contido no namespace System.ComponentModel), onde voc pode arrastar controles (componentes) para ele. Este arquivo, onde implementamos os cdigos de cada um dos eventos que vimos acima.

Para exemplificarmos, a vamos criar um servio chamado PeopleServices.Service1, onde iremos adicionar um objeto do tipo Timer para escrevermos a hora atual em um arquivo a cada dez segundos (10000 milisegundos). Para manter a utilidade das partial classes, criaremos o objeto Timer dentro do arquivo (classe) Service1.Designer e l tambm iremos criar a instncia da mesma. No devemos esquecer de vincular o procedimento que ser executado quando o evento Elapsed for disparado e, para isso, utilizaremos o conceito de vinculao dinmica de eventos. O Timer possui dois mtodos chamados Start e Stop que, so auto-explicativos. Quando o servio do Windows for inicializado, devemos iniciar o Timer e, quando o servio for parado, devemos parar o Timer. Sendo assim, chamaremos cada um desses mtodos nos mtodos OnStart e OnStop do servio do Windows e, dentro do evento Elapsed, o cdigo que executa a escrita no arquivo texto ser executado. O cdigo abaixo mostra como efetuar essa configurao que acabamos de descrever nos dois arquivos, apenas poupando algumas sees por questes de espao:
VB.NET Service1.Designer.vb Imports System.Timers Imports System.ServiceProcess Partial Class Service1 Inherits ServiceBase 'outros membros ocultados Private components As System.ComponentModel.IContainer Private _timer As Timer Private Sub InitializeComponent() components = New System.ComponentModel.Container() Me.ServiceName = "PeopleServices.Service1" Me._timer = New Timers.Timer(10000) AddHandler _timer.Elapsed, AddressOf Me.OnTimedEvent End Sub End Class

Israel Aece | http://www.projetando.net

Captulo 11 Criando Servios do Windows


C# - Service1.Designer.cs using System.Timers; using System.ServiceProcess;

partial class Service1 : ServiceBase { private System.ComponentModel.IContainer components = null; private Timer _timer; //outros membros ocultados private void InitializeComponent() { components = new System.ComponentModel.Container(); this.ServiceName = "Service1"; this._timer = new Timer(10000); this._timer.Elapsed ElapsedEventHandler(OnTimedEvent); } } += new

VB.NET Service1.vb Imports System.IO Imports System.Timers Public Class Service1 Protected Overrides Sub OnStart(ByVal args() As String) Me._timer.Start() End Sub Private Sub OnTimedEvent(ByVal source As Object, ByVal e As ElapsedEventArgs) Using sw As New StreamWriter(File.Open("C:\Logs.txt", FileMode.Append)) sw.WriteLine(DateTime.Now.ToString("HH:mm:ss")) End Using End Sub Protected Overrides Sub OnStop() Me._timer.Stop() End Sub End Class C# - Service1.cs using System; using System.IO;

Israel Aece | http://www.projetando.net

Captulo 11 Criando Servios do Windows


using System.Timers; public partial class Service1 { //outros membros ocultados protected override void OnStart(string[] args) { this._timer.Start(); } void OnTimedEvent(object sender, ElapsedEventArgs e) { using (StreamWriter sw = StreamWriter(File.Open("C:\\Logs.txt", FileMode.Append))) { sw.WriteLine(DateTime.Now.ToString("HH:mm:ss")); } } protected override void OnStop() { this._timer.Stop(); }

new

Quando o mtodo Start do objeto Timer chamado, ele comea a ser executado e, quando o valor especificado no construtor desta classe, que o intervalo, for atingido, o evento Elapsed ser disparado e, no exemplo acima, escreve uma linha no arquivo especificado, com a hora atual. Isso acontecer at que o mtodo Stop seja invocado. Instalando um servio do Windows Assim como o .NET Framework trouxe classes para facilitar no desenvolvimento de servios do Windows, ele tambm dispe classes que encapsulam o processo de instalao deste servio. Ainda dentro do namespace System.ServiceProcess existem duas classes para isso: ServiceInstaller e ServiceProcessInstaller. A primeira delas, ServiceInstaller, responsvel por instalar um servio que implementa a classe ServiceBase e chamada pelo utilitrio de instalao quando o servio est sendo instalado. Essa classe herda indiretamente da classe Installer, que vimos extensivamente a sua estrutura no Captulo 3 Utilizao de Assemblies. Para cada servio a ser instalado, uma instncia desta classe necessria para efetuar as configuraes relacionada a cada um deles. A tabela abaixo sumariza as propriedades mais importantes, descrevendo cada uma delas: Israel Aece | http://www.projetando.net 7

Captulo 11 Criando Servios do Windows


Propriedade Description

Descrio Recebe uma string contendo a descrio para o servio. Trata-se de uma descrio para que o usurio que ir operar o servio saiba para qual finalidade o mesmo se destina. Essa mensagem tambm exibida na console de gerenciamento fornecida pelo Windows e tambm atravs do utilitrio de linha de comando, chamado Sc.exe.

DisplayName Atravs de uma string, define um nome amigvel que identifica o nome do servio para o usurio. ServiceName Indica o nome uso pelo sistema para identificar o servio. O valor desta propriedade deve obrigatoriamente ser identica a propriedade ServiceName da classe ServiceBase do servio que voc quer instalar. StartType Essa propriedade indica como o servio ser iniciado. Ela recebe uma das opes fornecidas pelo enumerador ServiceStartMode, que pode ser uma das trs opes descritas abaixo: Automatic Indica que o servio ser inicializado automaticamente quando o sistema operacional for inicializado. Disabled Indica que o servio est desabilitado e no pode ser inicializado pelo usurio ou pela aplicao. Manual Indica que o servio pode somente ser inicializado manualmente, por um usurio ou por uma aplicao.

Israel Aece | http://www.projetando.net

Captulo 11 Criando Servios do Windows

J a classe ServiceProcessInstaller responsvel por instalar um executvel que contm classes que estendem a classe ServiceBase e tambm chamada pelo utilitrio de instalao. Atravs da instncia da classe ServiceProcessInstaller podemos (e devemos) definir informaes relacionadas a parte de segurana do servio. Temos trs propriedades que utilizamos para definir a segurana, a saber: Propriedade Account Descrio Indica qual o tipo de conta em que o servio ir rodar. Essa propriedade definida atravs do enumerador ServiceAccount, que possui quatro opes, descritas abaixo: LocalService Ir executar no contexto de uma conta que age como um usurio no privilegiado no computador local e com credenciais annimas em qualquer computador remoto. LocalSystem Ir executar no contexto de uma conta que possue privilgios locais e possui credenciais em qualquer computador remoto. NetworkService Ir executar no contexto de uma conta que age sem privilgios no computador local e possue credenciais em qualquer computador remoto. User Opo padro. Ir solicitar um nome de usurio e senha vlidos para quando o servio instalado e executado no contexto de uma conta especificada na rede. As informaes de nome de usurio e senha so informados atravs das propriedades UserName e Password, que esto logo abaixo. Define uma string contendo a senha associada com a conta de usurio em que o servio ir ser executado. Define uma string contendo o nome de usurio em que o servio ir ser executado.

Password UserName

Nota: A definio das propriedades UserName e Password so importante, pois automatizam a inicializao do servio quando o sistema operacional, por algum motivo, for reinicializado e o servio precisa ser inicializado. Quando a propriedade Account estiver definida como User e as propriedades UserName e Password estiverem vazias, ao instalar o servio, ser necessrio que voc informe o nome de usurio e senha e, se no forem vlidos, o servio no ser instalado. Felizmente o Visual Studio .NET facilita a criao destas duas classes necessrias para a instalao dos servios. Quando voc abre o arquivo do servio em modo design, ao clicar com o boto direito do mouse em qualquer parte dele, abrir uma menu de contexto e ter uma opo chamada Add Installer. Ao clicar nela, um arquivo chamando ProjectInstaller adicionado ao projeto e, dentro dele, as classes que vimos acima, j pr-configuradas com as propriedades que vimos acima, podendo ser altereadas tambm Israel Aece | http://www.projetando.net

Captulo 11 Criando Servios do Windows

10

atravs da janela de propriedades. O ProjectInstaller nada mais que uma classe que herda diretamente da classe Installer e que est decorada com o atributo RunInstallerAttribute que, como vimos no Captulo 3, indica que o instalador ser executado quando voc instalar o Assembly. Os servios Windows, depois de devidamente criados e com seus respectivos instaladores, chega o momento onde devemos coloc-lo em seu devido lugar dentro do sistema operacional e, para isso, utilizamos o utilitrio chamado installutil.exe que, dado um caminho fsico at o Assembly (EXE), ele sai a procura de classes que esto decoradas com o atributo RunInstallerAttribute definido com True e as executam. Depois disso, se tudo ocorrer com sucesso, voc j conseguir visualizar o servio dentro da console do Windows, como mostrado na imagem abaixo:

Imagem 12.2 Servio da People j devidamente instalado. Interagindo com um servio do Windows Depois de devidamente instalado, necessrio termos o controle sobre ele, ou seja, precisamos ter acesso as operaes bsicas de todo servio Windows, que iniciar, parar, pausar, recuperar informaes, etc.. Para isso, temos trs formas. A primeira delas, utilizando a console que o Windows fornece que est em Painel de Controle, Ferramentas Administrativas e, sem seguida, clique em Servios; outra possibilidade atravs de um utilitrio de linha de comando chamado Sc.exe; finalmente, temos a possibilidade de controlar um servio do Windows a partir de uma aplicao .NET e, para isso, temos uma classe chamada ServiceController. Essa classe representa um determinado servio do Windows e Israel Aece | http://www.projetando.net

10

Captulo 11 Criando Servios do Windows

11

fornece todas as funcionalidades necessrias para que possamos manipular o servio que ela representa. Quando utilizamos a console fornecida pelo prprio sistema operacional, temos a possibilidade de localizar o servio e, quando clicamos com o boto direito do mouse em cima do mesmo, um menu de contexto exibido com as operaes que podemos executar no servio, como por exemplo, o mtodo Start, Stop e Pause. A imagem abaixo ilustra esse menu:

Imagem 12.3 Console fornecida pelo Windows para gerenciamento do servio. Alm dela, temos tambm o utilitrio de linha de comando, chamado Sc.exe. O utilitrio serve para quando queremos automatizar a manipulao do servio, ou seja, a partir de uma instalao de um software, necessitamos parar um determinado servio e, quando a instalao for concluda, o servio reinicializado. A imagem abaixo exibe como executar esse utilitrio e passar os comandos para que ele possa trabalhar:

11

Israel Aece | http://www.projetando.net

Captulo 11 Criando Servios do Windows

12

Imagem 12.4 Utilitrio de linha de comando para a manipulao de servios do Windows. Finalmente, temos uma soluo .NET para acessar um determinado servio do Windows. Trata-se de uma classe chamada ServiceController. Essa classe fornece o acesso a um servio do Windows, possibilitando oper-lo e extrair informaes sobre ele. O interessante que, como os dois primeiros casos, no est limitado a acessar servios somente feitos em .NET, mas podendo acessar todo e qualquer servio Windows. Alm disso, ele fornece uma funcionalidade interessante que no temos na console do Windows: possvel criar uma aplicao .NET que envie comandos customizados para o servio atravs desta classe. Essa classe tambm fornecida como um controle, contida na aba componentes do Visual Studio .NET, como mostrado na imagem abaixo:

Imagem 12.5 Controle ServiceController na ToolBox do Visual Studio .NET. Atravs da tabela abaixo, analisaremos os membros mais importantes que so fornecidos pela classe ServiceController: Membro Descrio CanPauseAndContinue Propriedade de somente leitura que retorna um valor booleano indicando se o servio referenciado permite ou no ser pausado e, em seguida, continuado. Israel Aece | http://www.projetando.net 12

Captulo 11 Criando Servios do Windows


CanShutdown

13

CanStop DependentServices DisplayName MachineName ServiceName ServicesDependendOn ServiceType

Propriedade de somente leitura que retorna um valor booleano indicando se o servio referenciado permite ou no ser notificado quando o sistema operacional estiver em processo de shutting down. Propriedade de somente leitura que retorna um valor booleano indicando se o servio referenciado permite ou ser parado depois de inicializado. Recupera um array de elementos do tipo ServiceController, onde cada elemento representa um servio que depende do servio referenciado. Retorna uma string contendo o nome amigvel do servio referenciado. Propriedade que podemos definir ou ler o nome da mquina em que o servio reside. O padro o computador local, tambm indicado como .. Propriedade que podemos definir ou ler o servio que a instncia do classe ServiceController referencia. Recupera um array de elementos do tipo ServiceController, onde cada um dos elementos representa um servio que o servio referenciado depende. Define um dos valores estipulados pelo enumerador ServiceType, que so descritos abaixo: Adapter Um servio para um dispositivo de hardware que requer seu prprio driver. FileSystemDriver Um driver do sistema de arquivos, que tambm pode ser um driver de dispositivo de Kernel. InteractiveProcess Um servio que pode interagir com o Desktop. KernelDriver Um driver de dispositivo de Kernel, como disco rgido ou qualquer outro driver de mais baixo nvel. RecognizerDriver Um driver de sistema de arquivo que utilizado durante a inicializao para determinar o sistema de arquivo presente no sistema. Win32OwnProcess Um programa que pode ser iniciado pelo ServiceController e que obedece o protocolo do servio. Win32SharedProcess Um servio Win32 que pode compartilhar o processo com outros servios Win32. Propriedade de somente leitura que retorna o status atual do servio referenciado, que definido pelo enumerador ServiceControllerStatus, que tem as seguintes opes: Israel Aece | http://www.projetando.net

Status

13

Captulo 11 Criando Servios do Windows

14

ContinuePending O servio continua pendente. Pause O servio est pausado. PausePending O servio est sendo pausado. Running O servio est rodando. StartPending O servio est sendo iniciado. Stopped O servio est parado. StopPending O servio est sendo parado.

Close Continue ExecuteCommand

GetDevices

GetServices Pause Refresh

importante analisar que somente ter o status mais atual depois que chamar o mtodo Refresh, que veremos mais abaixo. Este mtodo desconecta a instncia do classe ServiceController do servio e libera todos os recursos relacionados a ele. Responsvel por continuar um servio, depois dele estar pausado. Atravs deste mtodo possvel passar um comando customizado para o servio sem alterar o seus status. Neste caso, o ideal sempre implementar o mtodo OnCustomCommand para, dentro do servio, recuperar essa informao e tomar uma deciso baseando-se nisso. Trata-se de um mtodo esttico que recupera todos os servios relacionados a driver de dispositivos no computador referenciado. Esse mtodo retorna um array contendo elementos do tipo ServiceController. Trata-se de um mtodo esttico que recupera todos os servio do computador referenciado. Esse mtodo retorna um array contendo elementos do tipo ServiceController. Pausa o servio. Atualiza para os valores correntes todas os valores das propriedades, como o caso da propriedade Status. Se esse mtodo no for chamado, voc nunca conseguir recuperar o status mais atual em que o servio se encontra. Inicializa o servio. Para o servio e qualquer servio que dependente do mesmo. Dado uma opo contida dentro do enumerador ServiceControllerStatus, aguarda o servio atingir este status. Esse mtodo sobrecarregado, que permite receber um objeto do tipo TimeSpan, para que seja possvel especificar um timeout, pois se caso o status nunca for atingido, possvel determinar um tempo e, se ele for atingido e o status no for mudado, ele interromper a espera. Israel Aece | http://www.projetando.net

Start Stop WaitForStatus

14

Captulo 11 Criando Servios do Windows

15

Para dar um exemplo simples da utilizao da classe ServiceController, vamos recuperar o servio People que criamos um pouco acima e invocar algumas das propriedades:
VB.NET Imports System.ServiceProcess Dim sc As New ServiceController("PeopleServices.Service1", ".") lblNome.Text = "DisplayName: " & sc.DisplayName lblServiceName.Text = "ServiceName: " & sc.ServiceName C# using System.ServiceProcess; ServiceController sc = new ServiceController("PeopleServices.Service1", "."); lblNome.Text = "DisplayName: " + sc.DisplayName; lblServiceName.Text = "ServiceName: " + sc.ServiceName;

importante salientar que as propriedades DisplayName e ServiceName diferem bastante. A propriedade DisplayName apenas um nome amigvel que damos ao servio para que os usurios que estejam visualizando consigam identificar melhor o servio. J a propriedade ServiceName utilizado para identificar o servio para o sistema operacional e que voc precisa informar no construtor da classe ServiceController para que seja possvel recuper-lo. Depurando um servio do Windows Como os servios do Windows devem ser executados a partir do SCM, necessrio instalarmos antes de execut-lo, mesmo que essa execuo seja ainda em tempo de desenvolvimento, mais precisamente, em depurao. Alm de instalado, para que a depurao funcione, necessrio que ele tambm esteja inicializado. O processo de depurao um pouco mais detalhado em relao as aplicaes de interface grfica. O primeiro passo importante certificar-se de que est instalando um Assembly em seu modo Debug. Depois do servio devidamente instalado, voc pode abrir o projeto do servio dentro do Visual Studio .NET, marcar onde ir querer o BreakPoint e, em seguida, v at o menu Debug >> Attach To Process (ou atravs do atalho Ctrl + Alt + P) e na caixa de dilogo que lhe apresentada, selecione o servio na lista de servios em execuo e ento clique no boto Attach. Isso far com que o debugger do Visual Studio .NET seja anexado ao processo do servio e, quando a execuo chegar no Breakpoint, voc conseguir acompanhar o processo via linha de cdigo. A imagem abaixo ilustra a janela que apresentada para voc escolher o processo qual quer anexar o depurador: Israel Aece | http://www.projetando.net

15

Captulo 11 Criando Servios do Windows

16

Imagem 12.6 Anexando o debugger ao servio Windows.

16

Israel Aece | http://www.projetando.net

Captulo 12 Interoperabilidade com componentes COM


Captulo 12 Interoperabilidade com componentes COM Introduo

Antes mesmo da plataforma .NET surgir, as empresas j estavam desenvolvendo aplicaes e componentes nas mais diversas linguagens, como por exemplo C++, Visual Basic 6, etc.. No h dvidas que as empresas investiram tempo e dinheiro na criao destes componentes que so, em algumas vezes, largamente utilizados e, dificilmente, sero sobrescritos instantaneamento para .NET, utlizando o cdigo gerenciado. No somente esse cenrio, mas temos diversos outros componentes que so expostos via COM que poderamos estar utilizando dentro da plataforma .NET. Felizmente o .NET tem a capacidade de comunicar com o mundo COM, permitindo criarmos componentes que sejam acessveis a linguagens no .NET e, tambm, permite utilizarmos os componentes legados dentro das aplicaes .NET. Na primeira parte deste captulo, vamos abordar como devemos proceder para a criao de componentes que sero utilizados por aplicaes/componentes COM e tambm como consumir componentes COM. A segunda, e ltima parte, destina-se a analisarmos um servio chamado PInvoke que a plataforma .NET nos disponibiliza. Ela nos permite fazer chamadas a DLLs no gerenciadas e, alm disso, efetuarmos chamadas as APIs que o Windows disponibiliza para termos acesso a informaes do sistema operacional. Acessando componentes COM Criao de Assembly para Interoperabilidade Antes de mais nada, vamos entender o que algo interopervel: Interoperabilidade a capacidade de um sistema ( informatizado ou no) de se comunicar de forma transparente (ou o mais prximo disso) com outro sistema (semelhante ou no). Para um sistema ser considerado interopervel, muito importante que ele trabalhe com padres abertos. Seja um sistema de portal, seja um sistema educacional ou ainda um sistema de comrcio eletrnico, ou e-commerce, hoje em dia se caminha cada vez mais para a criao de padres para sistemas. Wikipdia Como o .NET j uma realidade, muitas empresas j esto trabalhando em cima dessa plataforma, mas mantendo alguns softwares e componentes ainda desenvolvidos em uma tecnologia mais antiga. Hoje encontramos muitas aplicaes rodando em Visual Basic 6 e, como j discutimos acima, foi injetado muitos recursos para o desenvolvimento dessas aplicaes, que no podem ser descartados.

Israel Aece | http://www.projetando.net

Captulo 12 Interoperabilidade com componentes COM

Quando comeamos a falar em migrao, se o software foi politicamente escrito, ento a parte da interface com o usurio, geralmente, onde temos menos cdigos, o que conseqentemente ser a primeira parte a ser migrada para a nova plataforma. Sendo assim, mesmo em .NET, queremos continuar utilizando os componentes feitos em Visual Basic 6.0 que, neste primeiro momento, no sero migrados. Para tornar a comunicao entre o mundo COM e o mundo .NET possvel, entra em cena um Assembly chamado Interop Assembly. Esse Assembly, fornece uma layer intermediria entre os dois mundos, que habilita a comunicao entre o runtime .NET Framework e o componente (ou aplicao) COM. O Interop Assembly nada mais que um wrapper para os membros (metadados) que o mesmo expe. Para gerar esse Assembly de interoperabilidade, basta voc adicionar uma referncia a um componente COM em uma aplicao .NET. Ao compilar a aplicao, dentro do diretrio \bin da mesma voc notar que, alm do Assembly que gerado pela aplicao, um outro Assembly, este de interoperabilidade, tambm gerado com a seguinte nomenclatura: Interop.[Nome Componente COM].dll. E esses Interop Assemblies so criados um para cada referncia COM que tiver em seu projeto. Se voc no tiver o Visual Studio .NET no momento, ento voc pode recorrer a um utilitrio de linha de comando, qual tambm tem a finalidade de criar Interop Assemblies. Esse utilitrio chamado TlbImp.exe e encontrado dentro do diretrio onde est contido o .NET Framework. Uma dica aqui abrir o utilitrio a partir do prompt do Visual Studio .NET para evitar procurar o path completo do mesmo. Uma possvel sintaxe para utilizar este utilitrio :
C:\ tlbimp Componente.dll /out:Interop.Componente.dll

Alm dessas duas forma, ainda h a possibilidade de criar esse componente dinamicamente, ou seja, o .NET Framework fornece classes que permitem, via cdigo, criarmos um Interop Assembly. Para isso, existe um namespace chamado System.Runtime.InteropServices que fornece todos os tipos necessrios para implementarmos essa soluo. A principal classe que temos dentro deste namespace que fornece o conjunto de servios necessrios para converter um Assembly gerenciado acessvel via COM e extrair um Interop Assembly de um componente COM a classe TypeLibConverter que, inclui as mesmas opes do utilitrio Tlbimp.exe. Essa classe possui trs mtodos que podemos utilizar, dependendo da necessidade. Esses mtodos so: Mtodo Descrio ConvertAssemblyToTypeLib Dado um Assembly .NET, extrai a biblioteca de tipos e cria um Assembly que pode ser utilizado atravs do mundo COM. Israel Aece | http://www.projetando.net 2

Captulo 12 Interoperabilidade com componentes COM

ConvertTypeLibToAssembly Dado um Assembly COM, extrai a biblioteca de tipos e cria um Assembly de interoperabilidade que pode ser utilizado por aplicaes .NET. GetPrimaryInteropAssembly Atravs de uma GUID, que identifica o objeto dentro (type library) do Registry, recupera o nome e o code base de um Assembly de interoperabilidade. Esse mtodo retorna um valor booleano indicando se Assembly foi ou no encontrado. Expondo componentes .NET para o mundo COM Uma vez que criamos classes em .NET aplicaes COM podem utiliz-las, se assim desejar. Entretanto, para assegurar que isso seja possvel, necessrio nos atentarmos em alguns detalhes que precisam ser analisados para que o mesmo consiga ser visvel ao mundo COM. Para disponibilizar o componente para interoperabilidade com o mundo COM, voc primeiramente deve desenhar o seu componente visando facilitar esse processo e, para isso, voc deve explicitamente implementar Interfaces em seus componentes que sero expostos. Isso necessrio porque componentes COM no podem conter os membros diretamente e, sendo assim, devem ter as Interfaces implementadas. Apesar do COM poder gerar a Interface automaticamente, melhor voc criar isso manualmente, o que permitir um maior controle sob o componente e suas formas de atualizao. Quando os componentes COM geram automaticamente a sua Interface, voc no pode fazer nenhum mudana em sua estrutura pblica. Isso acontece porque componentes COM so imutveis. Se voc romper essa regra, o componente deixar de ser invocado. Justamente por essa questo que permitir que a Interface seja criada automaticamente no uma boa idia e, o ideal estar criando as suas prprias Interfaces, pois voc ter uma maior segurana, j que conseguir garantir que a Interface no mudar. Para manipularmos a forma com a qual a Interface criada e manipulada, temos um atributo chamado ClassInterfaceAttribute que devemos decorar o componente. Esse atributo recebe em seu construtor, uma das opes do enumerador ClassInterfaceType, que esto descritas abaixo: Opo AutoDispatch AutoDual None Descrio Opo padro que indica que a classe suporta late-binding quando chamada atravs de um cliente COM e omite a descrio da Interface. Utilize essa opo quando a classe pode sofrer mudanas futuras. Esta opo cria uma Interface que permite o early-binding. Somente utilize essa opo se essa classe no sofrer mudanas futuras. O COM Interop no criar a Interface padro para a classe. Neste caso, voc dever, explicitamente, fornecer uma Interface para ser implementada na classe que ser exposta para o mundo COM. Israel Aece | http://www.projetando.net

Captulo 12 Interoperabilidade com componentes COM

O segundo detalhe que temos que nos atentar com relao aos membros que desejamos expor ao mundo COM. Por padro, construtores parametrizados, mtodos estticos e constantes no so expostos. Com exceo disso, todos os tipos e membros pblicos so acessveis via COM. Mas e se existir algum tipo ou membro pblico que voc quer ocultar dos clientes COM? neste caso que entra em cena o atributo ComVisibleAttribute. Esse atributo pode ser utilizado nos mais diversos tipos e membros e, em seu construtor, recebe um parmetro booleano indicando se o membro ser ou no visvel para os clientes COM. O cdigo abaixo exemplifica a implementao de uma Interface em uma classe. Esse processo idntico ao que j conhecemos dentro da plataforma .NET. Em seguida, aplicamos o atributo ClassInterfaceAttribute no componente, definindo em seu construtor a opo None do enumerador ClassInterfaceType. Finalmente, uma propriedade criada mas negamos o acesso a mesma para clientes COM atravs do atributo ComVisibleAttribute, definindo-o como False:
VB.NET Imports System.Runtime.InteropServices Public Interface IMensagens Function BoasVindas(ByVal nome As String) As String End Interface <ClassInterface(ClassInterfaceType.None)> _ Public Class Usuarios Implements IMensagens Public Function BoasVindas(ByVal nome As String) As String _ Implements IMensagens.BoasVindas Return "Ol " & nome End Function <ComVisible(False)> _ Public ReadOnly Property SenhaTemporaria() As String Get Return "P@$$w0rd" End Get End Property End Class C# using System.Runtime.InteropServices; public interface IMensagens { string BoasVindas(string nome);

Israel Aece | http://www.projetando.net

Captulo 12 Interoperabilidade com componentes COM


} [ClassInterface(ClassInterfaceType.None)] public class Usuarios : IMensagens { public string BoasVindas(string nome) { return "Ol " + nome; } [ComVisible(false)] public string SenhaTemporaria { get { return "P@$$w0rd"; } }

Um detalhe importante com relao ao atributo ComVisibleAttribute. Por padro, esse atributo definido como True e, sendo assim, todos os tipos so gerenciados esto disponveis para o mundo COM. Esse atributo, alm de decorar individualmente cada tipo, pode tambm ser aplicado a nvel de Assembly, atravs do arquivo Assembly.vb ou Assembly.cs. Depois do Assembly .NET devidamente gerado, chega o momento de utiliz-lo em uma aplicao COM, como por exemplo, o Visual Basic 6. Mas somente com o Assembly .NET puro ainda no possvel, pois o mesmo precisa ser primeiramente registrado para, somente assim, ser acessado atravs do aplicaes COM. Para registr-lo, existe um utilitrio de linha de comando chamado regasm.exe, que est contido dentro da seguinte pasta: %windir%\Microsoft.NET\Framework\v2.0.50727. Esse utilitrio l os metadados do Assembly, os extrai e adiciona as entradas necessrias dentro do Registry, permitindo assim, o acesso via clientes COM. Abaixo um exemplo de como registrar um componente .NET com esse utilitrio:
C:\ regasm Componente.dll /tlb:Componente.tlb

Uma vez feito isso, basta voc adicionar a referncia ao mesmo em seu projeto (COM) e utilit-lo. Definindo a Interface padro 5

Israel Aece | http://www.projetando.net

Captulo 12 Interoperabilidade com componentes COM

Quando desejamos expor um componente .NET para o mundo COM, ele deve ter algumas configuraes extras em relao aos componentes que so utilizados somente por aplicaes .NET. Quando o definimos com o valor ClassInterfaceType.None no componente, indicar que a criao da Interface ser fornecida por ns. Neste caso, ao registrar o componente com o Type Library Exporter (Tlbexp.exe), o mesmo ser exposto com a primeira interface pblica visvel encontrada pelo utilitrio, definindo assim, a interface padro do componente para o mundo COM. Mas e quando existerem mais que uma Interface implementada no componente e, por algum motivo, queremos expor no a primeira Interface pblica visvel, mas a segunda ou a terceira. A verso 2.0 do .NET Framework introduz um novo atributo chamado ComDefaultInterfaceAttribute que, podemos expecific-lo no componente para determinar qual ser a interface padro utilizada independentemente da ordem de implementao. Em seu construtor, devemos especificar a Interface (atravs de um objeto do tipo Type) padro para o mundo COM. O exemplo abaixo exemplifica o componente decorado com este atributo:
VB.NET Imports System.Runtime.InteropServices <ClassInterface(ClassInterfaceType.None)> _ <ComDefaultInterface(GetType(ILogin))> _ Public Class AuthenticationServices Implements IData Implements ILogin Implementao... End Class C# using System.Runtime.InteropServices; [ClassInterface(ClassInterfaceType.None)] [ComDefaultInterface(typeof(ILogin))] public class AuthenticationServices : IData, ILogin { //Implementao... }

Para consumir o componente no mundo COM, podemos fazer (exemplo em VB6):


[ Sem o atributo ComDefaultInterface ] Dim authService As New AuthenticationServices

Israel Aece | http://www.projetando.net

Captulo 12 Interoperabilidade com componentes COM


Dim login As ILogin Set login = authService authService.Update() MsgBox login.Validate("IA", "Pa$$w0rd") [ Com o atributo ComDefaultInterface ] Dim authService As New AuthenticationServices Dim data As IData Set data = authService MsgBox authService.Validate("IA", "Pa$$w0rd") data.Update()

PInvoke O .NET Framework fornece uma servio chamado PInvoke Platform Invoke. Esse servio possibilita a chamada a cdigo no gerenciado, que esto implementados em DLL (dynamic link library), como o caso das APIs do Windows. Esse servio localiza e invoca uma funo e se encarrega de efetuar o marshaling dos argumentos automaticamente quando necessrio. Marshaling a tcnica utilizada para converter valores e trafeg-los entre processos e threads diferentes. As tcnicas de marshaling so utilizadas para compatibilidade de tipos. Quando invocamos um mtodo no gerenciado a partir do cdigo gerenciado, os tipos de dados devem ser convertidos em um tipo correspondente do mundo no gerenciado e, quando no possvel mapear tipos apenas com a declarao de tipos correspondentes, temos que utilizar tcnicas diferentes como converso de formato e o comportamento dos mesmos. Veremos mais a respeito do mashalling no decorrer deste captulo. A imagem abaixo ilustra o processo de chamada a um cdigo gerenciado a partir do PInvoke:

7 Imagem 13.1 Processo de chamada do PInvoke. Israel Aece | http://www.projetando.net

Captulo 12 Interoperabilidade com componentes COM

O processo ocorre em quatro passos: 1. Localiza a DLL contendo a funo. 2. Carrega a DLL na memria. 3. Localiza a funo dentro da memria, passando os parmetros e efetuando o marshaling quando necessrio. 4. Transfere o controle para a funo do cdigo no gerenciado. 5. Se excees acontecerem elas so atiradas pela funo de cdigo no gerenciado para o chamador do cdigo gerenciado. Observao: Localizar, carregar a DLL e localizar a funo na memria somente ocorre na primeira chamada para a funo. Para chamar uma funo de uma DLL que est escrita em cdigo no gerenciado a partir do cdigo gerenciado, primeiramente devemos criar uma definio desta funo em cdigo gerenciado, dentro de uma classe qualquer. Essa funo no tem nenhuma implementao de cdigo e obrigatoriamente dever conter a mesma assinatura da funo no gerenciada que deseja invocar. Depois disso, utilizamos um atributo chamado DllImportAttribute que utilizado para especificar a localizao da DLL onde est contido o mtodo externo. Observao 1: Existe um site chamada PInvoke.net: http://www.pinvoke.net que fornece gratuitamente uma listagem, j com exemplos de cdigos, para as APIs do Win32 e outros componentes no gerenciados, com exemplos em Visual Basic .NET e Visual C#. Observao 2: Antes de utilizar uma API do Windows, verifique se no existe uma classe gerenciada (dentro do .NET Framework) que tenha a mesma finalidade. O atributo DllImporteAttribute possue em seu construtor alguns named parameters que podemos configur-los para customizar o comportamento do PInvoke. Esses parmetros so descritos na tabela abaixo: Parmetro BestFitMapping Descrio Indica se o ajuste durante a converso de ANSI para Unicode. Isso permitir uma anlise mais precisa com relao a converso que, as vezes, pode no ser o ideal, pois podemos perder ou ser substituda alguma informao. Caracteres que no conseguem ser mapeados so representados pelo ponto de interrogao ?. Este campo indica qual ser a convenso adotada para a chamada do mtodo no gerenciado. Esse campo aceita umas das opes contidas no enumerador CallingConvention. Abaixo esto as descries das opes Israel Aece | http://www.projetando.net

CallingConvention

Captulo 12 Interoperabilidade com componentes COM


que ele fornece.

CharSet

Cdecl O chamador limpa a stack. Utilizado para casos em que o mtodo um nmero variado de parmetros. FastCall No suportado. StdCall A funo limpa a stack. Esta a convenso padro para quando desejar invocar funes no gerenciadas atravs do PInvoke. Winapi Indica que usar a converso adotada pelo sistema operacional. Esta tambm a opo padro. Indica qual ser o conjuto de caracteres utilizados durante o marshaling de strings. Esse valor definido a partir do enumerador CharSet, que contm as seguintes opes: Ansi Efetua o marshaling de strings em formato ANSI. Auto O PInvoke decide em tempo de execuo entre Ansi e Unicode, baseando-se no sistema operacional. Unicode Efetua o marshaling de strings em formato Unicode de 2 bytes. Indica o nome da funo da DLL no gerenciada que ser invocada. Esse valor somente exigido quando o nome da funo no gerenciada difere da definio (nome) da funo gerenciada. Este campo controla como o runtime ir efetuar a busca pela funo a ser executada. Se este campo estiver definido como False, e o CharSet estiver definido como Ansi, a letra A ser adicionada no final do nome da funo; agora, se o CharSet estiver definido como Unicode, a letra W adicionada no final do nome da funo. Isso porque quando estamos trabalhando com as funes das APIs do Windows, que trabalham com strings, normalmente temos duas verses da mesma funo disponveis, que a verso Ansi e a verso Unicode, que so sufixadas respectivamente por A e W. A tabela abaixo exibe um relacionamento entre os campos CharSet e ExactSpelling, exibindo o valor da propriedade ExactSpelling baseando-se nos valores padres definidos por cada linguagem: Israel Aece | http://www.projetando.net

EntryPoint

ExactSpelling

Captulo 12 Interoperabilidade com componentes COM


Linguagem VB.NET C# PreserveSig Ansi True False Unicode True False Auto False False

10

Indica se os mtodos no gerenciados que possuem HRESULT ou retval como valores de retornos so diretamente traduzidos ou convertidos em excees.

True O retorno do mtodo ser um nmero inteiro que conter o HRESULT. False O mtodo automaticamente converter os valores de HRESULT ou retval em excees. importante se atentar aqui, pois exige que a assinatura do mtodo seja mudada para retornar um nmero inteiro. SetLastError Indica se SetLastError ou no chamado. Se o valor for definido como True, o marshaler invoca GetLastError and faz o cache do valor retornado para previnir que ele seja sobrescrito por alguma outra API. Assim, voc poder recuperar o cdigo de erro atravs do mtodo esttico GetLastWin32Error da classe Marshal. ThrowOnUnmappableChar Valor booleano que indica se uma exceo ser atirada quando um caracter no conseguir ser mapeado. O cdigo abaixo exemplifica como devemos criar o mtodo gerenciado para a chamada da funo no gerenciada e, neste exemplo, iremos invocar a funo LogonUser da API advapi32.dll. Esse mtodo retornar uma valor booleano indicando se as credenciais informadas atravs dos parmetros userName e password so ou no vlidas.
VB.NET Imports System Imports System.Runtime.InteropServices Public Class Program <DllImport("advapi32.dll")> _ Public Shared Function LogonUser( _ ByVal lpszUsername As String, _ ByVal lpszDomain As String, _ ByVal lpszPassword As String, _ ByVal dwLogonType As Integer, _ ByVal dwLogonProvider As Integer, _ ByRef phToken As IntPtr) As Boolean End Function Public Shared Sub Main()

10

Israel Aece | http://www.projetando.net

Captulo 12 Interoperabilidade com componentes COM

11

Dim token As IntPtr If LogonUser("Usuario", String.Empty, "P@$$w0rd", 2, 0, token) Then Console.WriteLine("Usurio vlido.") Else Console.WriteLine("Usurio invlido.") End If End Sub End Class C# using System; using System.Runtime.InteropServices; class Program { [DllImport("advapi32.dll")] private static extern bool LogonUser( String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken); static void Main(string[] args) { IntPtr token; if (LogonUser("login", string.Empty, "P@$$w0rd", out token)) Console.WriteLine("Usurio vlido."); else Console.WriteLine("Usurio invlido."); } }

2,

0,

Como podemos notar no cdigo acima, apenas decoramos o mtodo gerenciado LogonUser com o atributo DllImportAttribute, sem definir os demais campos que vimos na tabela acima, o que far com que ele assuma os valores padres. importante dizer que voc poder customizar esses valores de acordo com a sua necessidade, olhando sempre a finalidade para qual cada um deles se destina. Nota Quando estamos utilizando o Visual Basic .NET, ele fornece uma alternativa, mas restrita, para invocar mtodos no gerenciados. Para isso, utilizamos uma keyword chamada Declare em conjunto com outra keyword chamada Alias que, por sua vez, Israel Aece | http://www.projetando.net

11

Captulo 12 Interoperabilidade com componentes COM

12

permite definirmos qual a funo do cdigo gerenciado queremos invocar. Abaixo est um exemplo da utilizao da funo LogonUser da API advapi32.dll com esta forma exclusiva do Visual Basic .NET:
Imports System Imports System.Runtime.InteropServices Public Class Program Public Declare Auto Function LogonUser Lib "advapi32.dll" Alias "LogonUser" ( _ ByVal lpszUsername As String, _ ByVal lpszDomain As String, _ ByVal lpszPassword As String, _ ByVal dwLogonType As Integer, _ ByVal dwLogonProvider As Integer, _ ByRef phToken As IntPtr) As Boolean Public Shared Sub Main() Dim token As IntPtr If LogonUser("Israel Aece", String.Empty, 2, 0, token) Then Console.WriteLine("Usurio vlido.") Else Console.WriteLine("Usurio invlido.") End If End Sub End Class

"P@$$w0rd",

Como podemos notar, a funo deve receber os mesmos parmetros da funo no gerenciada e, a forma de acesso a mesma dentro do mtodo Main exatamente a mesma. Para finalizar, essa forma de acesso interessante, porm possui apenas um subconjunto de configuraes para a chamada da funo no gerenciada. Se desejar ter uma maior controle nas configuraes, utilize o atribute DllImportAttribute. Passagem de Parmetros Como comentado acima, quando invocamos uma funo de cdigo no gerenciado, os parmetros e seus valores de retorno (quando existem) devem ser convertidos em parmetros correspondentes ao mundo no gerenciado. Esse processo, como j sabemos, chama-se mashalling. A maioria dos tipos de dados da plataforma .NET so convertidos sem nenhum problema para o mundo gerenciado, pois h sempre um tipo correspondente. Se desejar visualizar uma tabela completa com o mapeamente de tipos entre cdigo gerenciado e no Israel Aece | http://www.projetando.net 12

Captulo 12 Interoperabilidade com componentes COM

13

gerenciado, consulte este link: http://msdn2.microsoft.com/en-us/library/ac7ay120.aspx (MSDN Library). As excees so para tipos genricos, estruturas e objetos. Tipos genricos no so suportados e estruturas ou objetos exigem informaes adicionais para essa converso. Geralmente o CLR controla o layout fsico dos campos de uma estrutura ou objeto dentro da memria gerenciada. Se essa estrutura ou classe precisa ser ajustado de alguma forma, voc deve decorar a estrutura ou objeto com o atributo StructLayoutAttribute. Esse atributo permite voc controlar explicitamente o layout fsico dos campos de uma estrutura ou objeto que sero passados para o cdigo no gerenciado que, por sua vez, espera em um layout especfico. Esse atributo fornece um construtor sobrecarregado que aceita como parmetro uma das opes fornecidas pelo enumerador LayoutKind. As opes fornecidas por esse enumerador esto descritas logo abaixo: Opo Auto Explicit Descrio O runtime automaticamente escolhe um layout apropriado para os membros de um determinado objeto dentro da memria no gerenciada. Especifica que cada de cada membro dentro da memria no gerenciada explicitamente controlada. Para isso, cada membro deve ser marcada com o atributo FieldOffsetAttribute para indicar a posio do campo dentro do tipo. Indica que os membros de um determinado objeto sero expostos de forma seqencial, na ordem em que eles aparecem quando eles so exportados para a memria no gerenciada.

Sequential

Abaixo exibido um exemplo de como utilizar este atributo:


VB.NET Imports System Imports System.Runtime.InteropServices <StructLayout(LayoutKind.Sequential)> _ Public Structure Point Public X As Integer Public Y As Integer End Structure C# using System; using System.Runtime.InteropServices; [StructLayout(LayoutKind.Sequential)] public struct Point { public int X; public int Y;

13

Israel Aece | http://www.projetando.net

Captulo 12 Interoperabilidade com componentes COM


}

14

Apesar do marshaling ser um processo que acontece de forma transparente para os demais tipos de dados, h a possibilidade de customizarmos essa converso para que problemas com incompatibilidade de tipos no acontea. Para essa customizao, temos a disposio o atributo MarshalAsAttribute que permite voc mudar o comportamento padro da converso de tipos (campos, parmetros ou valores de retornos) e especificar um tipo mais especfico. No entanto esse atributo somente necessrio quando um tipo pode ser convertido em vrios outros tipos. Um exemplo disso a converso de uma string em cdigo no gerenciado. Ela pode ser convertida em vrios tipos: LPStr, LPWStr, LPTStr ou Bstr. Por padro, ela convertida em Bstr para mtodos COM. O atributo MarshalAsAttribute possui um construtor sobrecarregado que aceita uma das opes fornecidas pelo enumerador UnmanagedType. Esse enumerador determinada como os tipos sero convertido para o cdigo no gerenciado. Suas opes so os tipos de dados no gerenciados disponveis que podemos escolher para mapear um tipo gerenciado em um tipo no gerenciado, mudando assim o comportamento padro. O cdigo abaixo mostra um exemplo do uso deste atributo em um parmetro de mtodo e em um campo pblico:
VB.NET Imports System Imports System.Runtime.InteropServices <DllImport("Componente.dll")> _ Private Shared Function FuncaoTeste(<MarshalAs(UnmanagedType.LPStr)> ByVal s As String) As Boolean End Function ---<MarshalAs(UnmanagedType.LPStr)> _ Public Silly As Nome C# using System; using System.Runtime.InteropServices; [DllImport("Componente.dll"] private static extern FuncaoTeste([MarshalAs(UnmanagedType.LPStr)] string s) bool

14

Israel Aece | http://www.projetando.net

Captulo 12 Interoperabilidade com componentes COM


//---[MarshalAs(UnmanagedType.LPStr)] public string Nome;

15

A classe Marshal A classe Marshal fornece uma coleo de estticos mtodos para interagir e manipular o cdigo no gerenciado. Esses metdos estticos fornecidos pela classe Marshal so essenciais para trabalhar com cdigo no gerenciado. Muitos desses mtodos que esto definidos nesta classe so tipicamente utilizados por desenvolvedores que precisam fornecer/criar uma ponte entre os mundos gerenciados e no gerenciados.

15

Israel Aece | http://www.projetando.net

Captulo 13 - Reflection
Captulo 13 Reflection Introduo

Reflection (ou Reflexo) a habilidade que temos em extrair e descobrir informaes de metadados de um determinado Assembly. Os metadados descrevem os campos (propriedades, membros e eventos) de um tipo juntamente com seus mtodos e, durante a compilao, o compilar gerar e armazenar metadados dentro do Assembly. So os metadados que permitem uma das maiores faanhas dentro da plataforma .NET, ou seja, escrevermos um componente em Visual C# e consum-lo em uma aplicao em Visual Basic .NET. Reflection no permite apenas extrair informaes em runtime, mas tambm permitir que se carreegar Assemblies, instancie classes, invoque seus mtodos, etc.. Reflection algo muito poderoso que existe e possibilita dar uma grande flexibilidade para a aplicao. O pprio .NET Framework utiliza Reflection internamente em diversos cenrios, como por exemplo o Garbage Collector examina os objetos que um determinado objeto referencia para saber se o mesmo est ou no sendo utilizado. Alm disso, quando serializamos um objeto, o .NET Framework utiliza Reflection para extrair todos os valores do membros internos do objeto para persist-los. O prprio Visual Studio .NET utiliza informaes extradas via Reflection para habilitar o Intellisense e mais, quando est desenvolvendo um formalrio e vai at a janela de propriedades de um determinado controle, o Visual Studio .NET extrai os membros do controle via Reflection para exibir e, conseqentemente, alterar de acordo com a necessidade. A idia deste captulo apresentar como utilizar essa ferramenta poderosa que a Microsoft disponibilizou dentro do .NET Framework para extrair informaes de metadados. Todas as classes que utilizaremos para Reflection esto contidas dentro do namespace System.Reflection e, na primeira parte do captulo, veremos como possvel carregar Assemblies em memria, para em seguida, conseguir extrair as informaes de classes, propriedades, mtodos e eventos que um determinado tipo apresenta. AppDomains Historicamente, um processo criado para isolar uma aplicao que est sendo executado dentro do mesmo computador. Cada aplicao carregada dentro de um processo separado, que isola uma aplicao das outras. Esse isolamento necessrio porque os endereos so relativos um determinado processo. O cdigo gerenciado passa por um processo de verificao que deve ser executado antes de rodar. Esse processo determina se o cdigo pode acessar endereo de memria invlidos ou executar alguma outra ao que poderia causar alguma falha no processo. O cdigo que passa por essa verificao chamado de type-safe e permite ao CLR fornecer um grande nvel de isolamento, como um processo, s que com muito mais performance. Israel Aece | http://www.projetando.net 1

Captulo 13 - Reflection

AppDomain ou domnio de aplicao um ambiente isolado, seguro e verstil que o CLR pode utilizar para isolar aplicaes. Voc pode rodar vrias aplicaes em um nico processo Win32 com o mesmo nvel de isolamento que existiria em um processo separado para cada uma dessas aplicaes, sem ter o overhead que existe quando necessrio fazer uma chamado entre esses processos. Alm disso, um AppDomain seguro, j que o CLR garante que um AppDomain no conseguir acessar os dados que esto contidos em outro AppDomain. Essa habilidade de poder rodar vrias aplicaes dentro de um nico processo aumenta consideravelmente a escalabilidade do servidor. Um AppDomain criado para servir de container para uma aplicao gerenciada. A inicializao de um AppDomain consiste em algumas tarefas, como por exemplo, a criao da memria heap, onde todos os reference-types so alocados e de onde o lixo coletado e a criao de um pool de threads, que pode ser utilizado por qualquer um dos tipos gerenciados que esto carregados dentro do processo. O Windows no fornece uma forma de rodar aplicao .NET. Isso feito a partir de uma CLR Host, que uma aplicao responsvel por carregar o CLR dentro de um processo, criando AppDomains dentro do mesmo e executando o cdigo das aplicaes que desenvolvemos dentro destes AppDomains. Quando uma aplicao inicializada, um AppDomain criado para ela. Esse AppDomain tambm conhecido como default domain e ele somente ser descarregado quando o processo terminar. Sendo assim, o Assembly inicial rodar dentro deste AppDomain e, se desejar, voc pode criar um novos AppDomains e carregar dentro destes outros Assemblies mas, uma vez carregado, voc no pode descarreg-lo e isso somente acontecer quando a AppDomain for descarregada. O .NET Framework disponibiliza uma classe chamada AppDomain que representa e permite manipular AppDomains. Essa classe fornece vrios mtodos (alguns estticos) que auxiliam desde a criao at o trmino de um AppDomain. Entre esses principais mtodos, temos: Mtodo CreateDomain CurrentDomain Descrio Mtodo esttico que permite a criao de uma nova AppDomain. Retorna um objeto do tipo AppDomain representando o AppDomain da thread corrente. DoCallback Executa um cdigo em uma outra aplicao a partir de um delegate. GetAssemblies Retorna um array de objetos do tipo Assembly, onde cada elemento um Assembly que foi carregado dentro do AppDomain. IsDefaultAppDomain Retorna uma valor boolano indicando se o AppDomain trata-se do AppDomain padro. Load Permite carregar um determinado Assembly dentro do Israel Aece | http://www.projetando.net

Captulo 13 - Reflection
AppDomain. Descarrega um determinado AppDomain.

Unload

Para exemplificar a criao e o descarregamento de um segundo AppDomain, vamos analisar o trecho cdigo abaixo:
VB.NET Imports System Dim _domain As AppDomain = AppDomain.CreateDomain("TempDomain") ... AppDomain.Unload(_domain) C# using System; AppDomain _domain = AppDomain.CreateDomain("TempDomain"); //... AppDomain.Unload(_domain);

Assemblies Nomenclatura dos Assemblies A nomenclatura de um Assembly (conhecida como display name ou identidade do Assembly) consiste em 4 informaes: nome (sem .exe ou .dll), verso, cultura e a public key token (que ser nula se uma strong name no for definida). Para exemplificar isso, vamos analisar o Assembly System.Data.dll da verso 2.0 do .NET Framework que responsvel pelas classes de acesso a dados e tambm um Assembly customizado chamado PeopleLibrary, onde no foi criado uma strong name:
System.Data, Version=2.0.0.0, PublicKeyToken=b77a5c561934e089 PeopleLibrary, PublicKeyToken=null Version=1.0.0.0, Culture=neutral, Culture=neutral,

O .NET Framework fornece uma classe chamada que descreve a identidade do Assembly, chamada AssemblyName. Entre as vrias propriedades que essa classe possui, podemos destacar as mais importantes: CodeBase, CultureInfo, FullName, KeyPair, Name e Version. Carregamento manual de Assemblies Israel Aece | http://www.projetando.net

Captulo 13 - Reflection

Quando referenciamos um Assembly qualquer dentro de uma aplicao, o CLR decide quando carreg-lo. Quando voc chama um mtodo, o CLR verifica o cdigo IL para ver quais tipos esto sendo referenciados e, finalmente, carrega os Assemblies onde os tais tipos esto sendo referenciados. Se um Assembly j estiver contido dentro do AppDomain, o CLR inteligente ao ponto de conseguir identificar e no carregar o mesmo Assembly novamente. Mas quando voc quer extrair informaes de metadados de um determinado Assembly, necessrio carreg-lo para dentro do seu AppDomain e, depois disso, poder extrair os tipos que ele expe. Para que isso seja possvel, o namespace System.Reflection possui uma classe chamada Assembly que possui, alm de seus mtodos de instncia, alguns mtodos estticos que so utilizados para carregar um determinado Assembly. Entre esses mtodos estticos para carregamento do Assembly temos: Mtodo GetAssembly GetCallingAssembly GetEntryAssembly GetExecutingAssembly Load Descrio Dado um determinado tipo, esse mtodo consegue extrair o Assembly em qual esse tipo est contido. Retorna um objeto do tipo Assembly que representa o Assembly onde o mtodo est sendo invocado. Retorna um objeto do tipo Assembly que est contido no AppDomain padro (default domain). Retorna uma instncia do Assembly onde o cdigo est sendo executado. Um mtodo com vrios overloads que retorna um determinado Assembly. Um dos overloads mais comuns, o que aceita uma string, conteudo o seu fully qualified name (nome, verso, cultura e token) como vimos acima. Se o Assembly especificado conter com uma strong name, o CLR ento procura dentro do GAC, seguido pelo diretrio base da aplicao e dos diretrios privados da mesma. Agora, se o Assembly especificado no conter uma strong name, ele apenas no procurar dentro do GAC e, para ambos os casos, se o Assembly no for encontrado, uma exceo do tipo System.IO.FileNotFoundException. Permite carregar um Assembly a partir do caminho fisico at o mesmo, podendo carregar um Assembly de qualquer local que ele esteja, no resolvendo as dependncias. Esse mtodo utilizando em cenrios mais limitados, onde no possvel a utilizao do mtodo LoadFrom, j que no permite carregar os Assemblies que tenham a mesma identidade, mas esto fisicamente em locais diferentes. Carrega um Assembly baseando-se no caminho fsico, Israel Aece | http://www.projetando.net 4

LoadFile

LoadFrom

Captulo 13 - Reflection

LoadWithPartialName

resolvendo todas as suas dependncias (ver nota abaixo). Carrega um Assembly do diretrio da aplicao ou do GAC, mas trata-se de um mtodo obsoleto e, ao invs dele, utilize o mtodo Load.

Esse mtodo no deve ser utilizado, porque voc nunca sabe a verso do Assembly que ir carregar. ReflectionOnlyLoad Carrega um Assembly em um contexto reflection-only, ou seja, o Assembly poder apenas ser examinado, mas no executado. ReflectionOnlyLoadFrom Dado o caminho fsico at o Assembly, o mesmo carregado dentro do domnio do chamador, tambm em um contexto reflection-only. Nota Importante: Vamos imaginar que temos uma aplicao Windows Forms (EXE) e uma biblioteca (DLL) que essa aplicao Windows utiliza. A estrutura a seguinte:
C:\Program Files\PeopleUI\WinUI.exe C:\Program Files\PeopleUI\PeopleLibrary.dll

Como podemos ver, a aplicao Windows (WinUI.exe) foi desenvolvida referenciando a biblioteca PeopleLibrary.dll. Imagine que, dentro do mtodo Main ele carrega a seguinte DLL: C:\PeopleLibrary.dll atravs do mtodo LoadFrom da classe Assembly. Imagine que, depois de carregado o mesmo Assembly, mas de um outro local, ao invocar um mtodo dentro do PeopleLibrary.dll ele invocar de qual dos dois locais (C:\ ou C:\Program Files\PeopleUI\)? Como o CLR no pode supor que os Assemblies so iguais somente porque o nome do arquivo coincide, felizmente ele sabe qual dever executar. Quando o mtodo LoadFrom executado, o CLR extrai informaes sobre a identidade do Assembly (verso, cultura e token), passando essas informaes para o mtodo Load que, faz a busca pelo Assembly. Se um Assembly correspondente for encontrado, o CLR far a comparao do caminho do arquivo especifico no mtodo LoadFrom e do caminho encontrado pelo mtodo Load. Se os caminhos forem idnticos, o Assembly ser considerado parte da aplicao, caso contrrio, ser considerado um arquivo de dados. Sempre que possvel, bom evitar o uso do mtodo LoadFrom e opte por utilizar o mtodo Load. A razo para isso que, internamente, o mtodo LoadFrom invoca o mtodo Load. Alm disso, o LoadFrom trata o Assembly como um arquivo de dados e, se o CLR carrega dentro de um AppDomain o mesmo Assembly a partir de caminhos diferentes, uma grande quantidade de memria desperdiada.

Israel Aece | http://www.projetando.net

Captulo 13 - Reflection

Exemplo: O cdigo abaixo exemplifica a utilizao do mtodo LoadFrom da classe Assembly. O trecho de cdigo foi extrado de uma demonstrao e, voc pode consultar na ntegra no seguinte local: XXXXXXXXXXXXXXXXXXXXXX.
VB.NET Imports System Imports System.Reflection Dim asb As Assembly = Assembly.LoadFrom("C:\Comp.dll") C# using System; using System.Reflection; Assembly asb = Assembly.LoadFrom("C:\\Comp.dll");

Trabalhando com Metadados Como j vimos anteriormente, os metadados so muito importantes dentro da plataforma .NET. Vimos tambm como criar AppDomains e carregar Assemblies dentro deles. Somente carregar os Assemblies dentro de um AppDomain no tem muitas utilidades. Uma vez que ele encontra-se carregado, perfeitamente possvel extrair informaes de metadados dos tipos que esto contidos dentro Assembly e, para isso, utilizaremos vrias classes que esto contidas dentro do namespace System.Reflection. A partir de agora analisaremos essas classes que esto disponveis para a criao e manipulao de tipos, como por exemplo, invocar mtodos, recuperar ou definir valores para propriedades, etc.. Antes de mais nada, precisamos entender qual a hierarquia dos objetos que esto disponveis para a manipulao dos metadados e para invoc-los dinamicamente. A imagem abaixo exibe tal hierarquia onde, como j era de se esperar, o ancestral comum o System.Object.

Israel Aece | http://www.projetando.net

Captulo 13 - Reflection

Imagem 15.1 Hierarquia das classes para manipulao de metadados System.Reflection.MemberInfo MemberInfo uma classe abstrata que base para todas as classes utilizadas para resgatar informaes sobre os membros sejam eles construtores, eventos, campos, mtodos ou propriedades de uma determinada classe. Basicamente essa classe fornece as funcionalidades bsicas para todos as classes que dela derivarem. Os membros desta classe abstrata so: Membro Name MemberType Descrio Retorna uma string representando o membro. Propriedade de somente leitura que retorna um item do enumerador MemberTypes indicando qual tipo de membro ele . Entre os itens deste enumerador, temos: All Especifica todos os tipos Constructor Especifica que o membro um construtor, representado pelo tipo ConstructorInfo. Custom Especifica que o membro um membro customimzado. Israel Aece | http://www.projetando.net 7

Captulo 13 - Reflection

Event Especifica que o membro um evento, representado pelo tipo EventInfo. Field Especifica que o membro um campo, representado pelo tipo FieldInfo. Method Especifica que o membro um mtodo, representado pelo tipo MethodInfo. NestedType Especifica que o membro um tipo aninhado, representado pelo tipo MemberInfo. Property Especifica que o membro uma propriedade, representada pelo tipo PropertyInfo. TypeInfo Especifica que o membro um tipo, representado pelo tipo TypeInfo. DeclaringType Propriedade de somente leitura que retorna um objeto do tipo Type indicando de qual tipo o objeto. ReflectedType Propriedade de somente leitura que retorna um objeto do tipo Type indicando o tipo que foi utilizado para obter a instncia deste membro. GetCustomAttributes Retorna um array contendo algum atributos customizados que o membro contm. Cada um dos elementos representado por um System.Object. IsDefined Retorna um valor booleano indicando se existe ou no um determinado atributo especifico aplicado no membro. System.Type Essa uma das mais importantes classes da plataforma .NET. Essa uma das principais classes utilizadas para voc poder extrair informaes de um tipo. System.Object possui um mtodo chamado GetType que retorna o tipo da instncia corrente, sendo representado por um objeto do tipo Type. Sendo assim, todo e qualquer objeto possui esse mtodo, j que todo tipo herda direta ou indiretamente dessa classe. O mtodo GetType acima utilizamos quando j temos a instncia do objeto. Mas e quando no a temos? Para esse caso, a classe Type fornece um mtodo esttico, tambm chamado de GetType que, dado um tipo (atravs de uma string contendo o AssemblyQualifiedName, que inclui o namespace at o nome do tipo que deve ser carregado), ele retorna o objeto Type que o representa e, conseqentemente, conseguir extrair as informaes de metadados do mesmo. Alm desse mtodo, a classe Type ainda possui um mtodo interessante chamado GetArrayType, que retorna um array de objetos do tipo Type, onde cada elemento representa o tipo correspondente do elemento do array. Entre todos os membros da classe Type, podemos destacar: Membro Assembly BaseType Descrio Propriedade de somente leitura que retorna um objeto do tipo Assembly em que o tipo declarado. Propriedade de somente leitura que retorna um objeto do Israel Aece | http://www.projetando.net 8

Captulo 13 - Reflection

DeclaringType IsAbstract IsArray IsByRef IsClass IsEnum IsGenericParameter IsGenericType IsInterface IsNested IsValueType FindInterfaces FindMembers GetConstructor

GetConstructors GetCustomAttributes GetDefaultMembers

tipo Type que representa o tipo qual o tipo corrente foi herdado. Propriedade de somente leitura que retorna um objeto do tipo Type que representa o tipo do membro. Propriedade de somente leitura que retorna um valor booleano indicando se o tipo ou no abstrato. Propriedade de somente leitura que retorna um valor booleano indicando se o tipo ou no um array. Propriedade de somente leitura que retorna um valor booleano indicando se o tipo ou no passado por referncia. Propriedade de somente leitura que retorna um valor booleano indicando se o tipo ou no uma classe. Propriedade de somente leitura que retorna um valor booleano indicando se o tipo ou no um enumerador. Propriedade de somente leitura que retorna um valor booleano indicando se o tipo representa um type parameter de um tipo genrico ou uma definio de mtodo genrico. Propriedade de somente leitura que retorna um valor booleano indicando se o tipo ou no um tipo genrico. Propriedade de somente leitura que retorna um valor booleano indicando se o tipo ou no uma Interface. Propriedade de somente leitura que retorna um valor booleano indicando se o tipo ou no aninhado a outro tipo. Propriedade de somente leitura que retorna um valor booleano indicando se o tipo ou no um tipo-valor. Retorna um array de objetos do tipo Type com todas as Interfaces implementadas ou herdadas pelo tipo. Retorna um array de objetos do tipo MemberInfo com todos os membros de um determinado tipo. Mtodo sobrecarregado que procura por um construtor de instncia pblico. A busca feita baseando-se em um array de objetos do tipo Type que passado para esse mtodo, que procurar pelo construtor que atender exatamente esses parmetros. Se encontrado, uma instncia da classe ConstrutorInfo retornada. Retorna um array de objetos do tipo ConstructorInfo, onde cada elemento representa um construtor do tipo. Retorna um array de objetos do tipo System.Object, onde cada elemento representa um atributo que foi aplicado ao membro. Procura por membros que aplicam o atributo DefaultMemberAttribute. Se encontrado, um array de elementos do tipo MemberInfo retornado, onde cada Israel Aece | http://www.projetando.net

Captulo 13 - Reflection

10

elemento representar um membro que aplica o atributo acima especificado. O atributo DefaultMemberAttribute define um determinado membro como sendo um membro padro, que invocado pelo mtodo InvokeMember da classe Type. Dado uma string com o nome de um evento existente no tipo, esse mtodo retorna um objeto do tipo EventInfo representando o evento. Retorna um array de objetos do tipo EventInfo, onde cada elemento representa um evento existente no tipo. Dado uma string com o nome de um campo existente no tipo, esse mtodo retorna um objeto do tipo FieldInfo representando o campo. Retorna um array de objetos do tipo FieldInfo, onde cada elemento representa um campo pblico existente no tipo. Dado uma string com o nome de uma Interface, ele retornar um objeto do tipo Type que represente a mesma, desde que ela esteja implementada no tipo. Retorna um array de objetos do tipo Type com todas as Interfaces implementadas ou herdadas pelo tipo. Dado uma string com o nome de um membro existente no tipo, esse mtodo retorna um objeto do tipo MemberInfo representando o membro. Retorna um array de objetos do tipo MemberInfo, onde cada elemento representa um membro (propriedades, mtodos, campos e eventos) existente no tipo. Dado uma string com o nome de um mtodo existente no tipo, esse mtodo retorna um objeto do tipo MethodInfo representando o mtodo. Retorna um array de objetos do tipo MethodInfo, onde cada elemento representa um mtodo existente no tipo. Dado uma string com o nome de um tipo aninhado existente no tipo, esse mtodo retorna um objeto do tipo Type representando o tipo aninhado. Retorna um array de objetos do tipo Tipo, onde cada elemento representa um tipo aninhado existente no tipo. Retorna um array de objetos do tipo PropertyInfo, onde cada elemento representa uma propriedade existente no tipo. Dado uma string com o nome de uma propriedade existente no tipo, esse mtodo retorna um objeto do tipo PropertyInfo representando a propriedade.

GetEvent GetEvents GetField GetFields GetInterface GetInterfaces GetMember GetMembers GetMethod GetMethods GetNestedType GetNestedTypes GetProperties GetProperty

10

Israel Aece | http://www.projetando.net

Captulo 13 - Reflection

11

Como temos duas formas de extrair o Type de algum objeto, o cdigo abaixo exemplifica ambas, aplicando a lgica em cima, a primeira delas utilizando o mtodo GetType a partir da instncia da classe e a outra forma atravs do mtodo esttico GetType da classe Type.
VB.NET Imports System Dim id As Integer = 123 Dim forma1 As Type = id.GetType() Dim forma2 As Type = Type.GetType("System.Int32") Console.WriteLine(forma1.FullName) Console.WriteLine(forma2.FullName) C# using System; int id = 123; Type forma1 = id.GetType(); Type forma2 = Type.GetType("System.Int32"); Console.WriteLine(forma1.FullName); Console.WriteLine(forma2.FullName);

Ambos os cdigos exibiro System.Int32, que representa o FullName do tipo inteiro. Vimos acima todos os mtodos que a classe Type fornece. Cada um dos mtodos retornam objetos especficos para cada um dos membros que existem dentro de um determinada objeto. A partir daqui, como j somos capazes de extrair o tipo de um objeto, iremos analisar esses objetos especficos, responsveis por representar os membros do tipo, onde poderemos extrair informaes de metadados referentes a cada um deles. Mas para que podemos entender o grande potencial do Reflection, vamos criar uma classe customizada, que possua membros, propriedades, mtodos e eventos para que possamos extrair as informaes de metadados da mesma. Isso no quer dizer que no seja possvel extrair as mesmas informaes de uma classe de dentro do .NET Framework. Para fins de exemplo, vamos criar uma classe chamada Cliente que ser composta por alguns membros que iremos utilizar como base para os futuros exemplos:
VB.NET Public Class Cliente Public X As Integer Private _id As Integer Private _nome As String

11

Israel Aece | http://www.projetando.net

Captulo 13 - Reflection
Public Event AlterouDados As EventHandler Public Sub New() End Sub Public Sub New(ByVal id As Integer, ByVal nome As String) Me._id = id Me._nome = nome End Sub Public Property Id() As Integer Get Return Me._id End Get Set(ByVal value As Integer) Me._id = value RaiseEvent AlterouDados(Me, EventArgs.Empty) End Set End Property Public Property Nome() As String Get Return Me._nome End Get Set(ByVal value As String) Me._nome = value RaiseEvent AlterouDados(Me, EventArgs.Empty) End Set End Property

12

Public Function ExibeDados() As String Dim msg As String = String.Format("{0} - {1}", Id, Nome) Return msg End Function End Class C# public class Cliente { public int X; private int _id; private string _nome; public event EventHandler AlterouDados; public Cliente() { } public Cliente(int id, string nome) { this._id = id; this._nome = nome;

12

Israel Aece | http://www.projetando.net

Captulo 13 - Reflection
} public int Id { get { return _id; } set { _id = value; if (AlterouDados EventArgs.Empty); } } public string Nome { get { return _nome; } set { _nome = value; if (AlterouDados EventArgs.Empty); } }

13

!=

null)

AlterouDados(this,

!=

null)

AlterouDados(this,

public string ExibeDados() { string msg = string.Format("{0} - {1}", Id, Nome); return msg; }

Como podemos ver, a classe possui dois membros, um do tipo inteiro e uma string e, para cada um deles, temos uma propriedade de escrita/leitura que encapsulam o acesso aos mesmos e um evento que disparado quando o valor da propriedade alterado; alm disso, h tambm dois construtores, onde um deles no possui nenhum parmetro e o outro recebe o numero e nome do cliente; finalmente, temos um mtodo chamado ExibeDados que retorna uma string com o cdigo e nome do cliente. Antes de extrairmos as informaes relacionados a cada um dos membros internos, precisamos primeiramente recupera o tipo atravs do mtodo GetType, fornecido pela instncia da classe Cliente, que ser armazenado em um objeto chamado tipo qual iremos utilizar por todos os exemplos adiante.
VB.NET Dim cliente As New Cliente() cliente.Id = 123 cliente.Nome = "Jos Torres" Dim tipo As Type = cliente.GetType()

13

Israel Aece | http://www.projetando.net

Captulo 13 - Reflection
C# Cliente cliente = new Cliente(); cliente.Id = 123; cliente.Nome = "Jos Torres"; Type tipo = cliente.GetType();

14

System.Reflection.FieldInfo Essa classe representa um membro pblico de um determinado tipo, fornecendo informaes de metadata e atributos que possam estar aplicados ao membro. Essa classe no possui um construtor pblico e, instncias da mesma, so retornadas a partir dos mtodos GetField e GetFields da classe Type. Abaixo exibido a forma que podemos utilizar e extrair os membros pblicos da classe Cliente a partir do mtodo GetFields:
VB.NET Imports System.Reflection For Each fi As FieldInfo In tipo.GetFields() Console.WriteLine( _ String.Format("Nome: {0} - Tipo: {1}", _ fi.Name, fi.FieldType)) Next C# using System.Reflection; foreach (FieldInfo fi in tipo.GetFields()) { Console.WriteLine( string.Format("Nome: {0} - Tipo: {1}", fi.Name, fi.FieldType)); }

Output Nome: X - Tipo: System.Int32

A propriedade FieldType retorna um objeto do tipo Type representando o tipo do membro. Alm desta propriedades existem algumas outras que merecem ser comentadas, como por exemplo, IsPublic, IsPrivate e IsStatic, que so auto-explicativas. System.Reflection.MethodBase Israel Aece | http://www.projetando.net

14

Captulo 13 - Reflection

15

A classe MethodBase trata-se de uma classe abstrata que fornece informaes a respeito de mtodos e construtores de um determinado tipo, atributos que so aplicados aos mesmos e mtodos e propriedades para manipulao de mtodos genricos. Ela fornece tambm um mtodo chamado GetParameters, que retorna uma coleo de objetos do tipo ParameterInfo, onde cada um deles representa um parmetro que pode existir no mtodo. A classe ParameterInfo possui uma propriedade chamada ParameterType que retorna um objeto do tipo Type representando o tipo do parmetro e, alm dela, a classe ParameterInfo possui algumas outras propriedades teis para extrairmos informaes relacionadas a cada parmetro e, entre elas, temos a propriedade IsOut, que retorna um valor booleano indicando se o parmetro trata-se de um parmetro de output. A classe MethodBase ainda possui um mtodo, um tanto quanto interessante, chamado GetMethodBody. Esse mtodo retorna um objeto do tipo MethodBody, contendo o MSIL stream, variveis locais (declaradas dentro do mtodo) e estruturas de excees. Finalmente, a classe MethodBase classe serve como base para as classes concretas ConstructorInfo e MethodInfo, que trazem informaes customizadas para cada um dos tipos. O primeiro deles, ConstructorInfo, trata-se de uma classe que representa um determinado construtor pblico de um tipo, fornecendo informaes de metadados respeito do construtor e tambm descobrindo os atributos que o construtor pode ter. Essa classe no possui um construtor pblico e, instncias da mesma, so retornadas a partir dos mtodos GetConstructor e GetConstructos da classe Type. O cdigo abaixo exemplifica a utilizao da classe ConstructorInfo, extraindo os construtores da classe Cliente:
VB.NET Imports System.Reflection For Each ci As ConstructorInfo In tipo.GetConstructors() Console.WriteLine(String.Format("Construtor: {0}", ci.Name)) For Each pi As ParameterInfo In ci.GetParameters() Console.WriteLine( _ String.Format(" Parmetro: {0} - Tipo: {1}", _ pi.Name, pi.ParameterType)) Next Next C# using System.Reflection; foreach (ConstructorInfo ci in tipo.GetConstructors())

15

Israel Aece | http://www.projetando.net

Captulo 13 - Reflection
{

16

Console.WriteLine(String.Format("Construtor: {0}", ci.Name)); foreach (ParameterInfo pi in ci.GetParameters()) { Console.WriteLine( string.Format(" Parmetro: {0} - Tipo: {1}", pi.Name, pi.ParameterType)); }

Output Construtor: .ctor Construtor: .ctor Parmetro: id - Tipo: System.Int32 Parmetro: nome - Tipo: System.String

Como podemos ver, temos um lao For aninhado que utilizado para recuperar os parmetros de um determinado construtor a partir de uma instncia de um objeto ConstructorInfo. A segunda e ltima classe que deriva de MethodBase, a classe MethodInfo, tem um comportamento muito parecido com a classe ConstructorInfo, s que neste caso, cada objeto deste representa um mtodo pblico de um tipo. A classe MethodInfo tambm pode receber parmetros e ter atributos. A nica exceo que a classe MethodInfo pode ter um tipo de retorno, ou seja, uma funo que retorna um valor qualquer e, para isso, a classe MethodInfo fornece uma propriedade chamado ReturnType que retorna um objeto do tipo Type representando o tipo que o mtodo retorna. Essa classe no possui um construtor pblico e, instncias da mesma, so retornadas a partir dos mtodos GetMethod e GetMethods da classe Type. Atravs do cdigo abaixo podemos visualizar a utilizao da classe MethodInfo. Vale lembrar que todas as classes herdam direta ou indiretamente da classe System.Object e, conseqentemente, todos os mtodos pblicos l contidos, como por exemplo, GetType, Equals, ToString etc. so tambm exibidos.
VB.NET Imports System.Reflection For Each mi As MethodInfo In tipo.GetMethods() Console.WriteLine( _ String.Format("Mtodo: {0} - Retorno: {1}", _ mi.Name, mi.ReturnType)) For Each pi As ParameterInfo In mi.GetParameters()

16

Israel Aece | http://www.projetando.net

Captulo 13 - Reflection
Console.WriteLine( _ String.Format(" Parametro: {0} - Tipo: {1}", _ pi.Name, pi.ParameterType)) Next

17

Next

C# using System.Reflection; foreach (MethodInfo mi in tipo.GetMethods()) { Console.WriteLine( String.Format("Mtodo: {0} - Retorno: {1}", mi.Name, mi.ReturnType)); foreach (ParameterInfo pi in mi.GetParameters()) { Console.WriteLine( string.Format(" Parmetro: {0} - Tipo: {1}", pi.Name, pi.ParameterType)); }

Output Mtodo: add_AlterouDados - Retorno: System.Void Parmetro: value - Tipo: System.EventHandler Mtodo: remove_AlterouDados - Retorno: System.Void Parmetro: value - Tipo: System.EventHandler Mtodo: get_Id - Retorno: System.Int32 Mtodo: set_Id - Retorno: System.Void Parmetro: value - Tipo: System.Int32 Mtodo: get_Nome - Retorno: System.String Mtodo: set_Nome - Retorno: System.Void Parmetro: value - Tipo: System.String Mtodo: ExibeDados - Retorno: System.String Mtodo: GetType - Retorno: System.Type Mtodo: ToString - Retorno: System.String Mtodo: Equals - Retorno: System.Boolean Parmetro: obj - Tipo: System.Object Mtodo: GetHashCode - Retorno: System.Int32

System.Reflection.PropertyInfo Essa classe representa uma propriedade pblica de um determinado tipo, fornecendo informaes de metadata e atributos que possam estar aplicados propriedade. Essa classe no possui um construtor pblico e, instncias da mesma, so retornadas a partir Israel Aece | http://www.projetando.net

17

Captulo 13 - Reflection

18

dos mtodos GetProperty e GetProperties da classe Type. A classe PropertyInfo possui uma propriedade chamada PropertyType, que retorna um objeto do tipo Type representando o tipo da propriedade e ainda, possui duas propriedades chamadas CanRead e CanWrite, que retornam um valor booleano indicando se a propriedade pode ser lida e escrita, respectivamente. Abaixo exibido a forma que podemos utilizar e extrair os membros pblicos da classe Cliente a partir do mtodo GetProperties:
VB.NET Imports System.Reflection For Each pi As PropertyInfo In tipo.GetProperties() Console.WriteLine( _ String.Format("Propriedade: {0} - Tipo: {1}", _ pi.Name, pi.PropertyType)) Next C# using System.Reflection; foreach (PropertyInfo pi in tipo.GetProperties()) { Console.WriteLine( string.Format("Propriedade: {0} - Tipo: {1}", pi.Name, pi.PropertyType)); }

Output Propriedade: Id - Tipo: System.Int32 Propriedade: Nome - Tipo: System.String

System.Reflection.EventInfo Essa classe representa um evento pblico de um determinado tipo, fornecendo informaes de metadata e atributos que possam estar aplicados ao evento. Essa classe no possui um construtor pblico e, instncias da mesma, so retornadas a partir dos mtodos GetEvent e GetEvents da classe Type. A classe EventInfo possui uma propriedade chamada EventHandlerType, que retorna um objeto do tipo Type representando o tipo do delegate utilizado para declarar o evento. Abaixo exibido a forma que podemos utilizar e extrair os membros pblicos da classe Cliente a partir do mtodo GetEvents:
VB.NET Imports System.Reflection

18

Israel Aece | http://www.projetando.net

Captulo 13 - Reflection

19

For Each ei As EventInfo In tipo.GetEvents() Console.WriteLine( _ String.Format("Evento: {0} - Tipo: {1}", _ ei.Name, ei.EventHandlerType)) Next C# using System.Reflection; foreach (EventInfo ei in tipo.GetEvents()) { Console.WriteLine( string.Format("Evento: {0} - Tipo: {1}", ei.Name, ei.EventHandlerType)); }

Output Evento: AlterouDados - Tipo: System.EventHandler

Invocando os membros em runtime Agora que j sabemos como extrair as informaes dos tipos, como podemos fazer para invocar esses membros automaticamente? Mas para fazermos isso, necessrio entendermos o conceito de Binding. Binding o processo de localizar a implementao de um determinado tipo e existem dois tipos: Early Binding e Late Binding. O Early Binding ocorre quando voc declara uma varivel j especificando um tipo ao invs de um System.Object (que a base), fortemente tipando e j tendo conhecimento do objeto em tempo de desenvolvimento e, alm disso, voc ter a checagem de tipos e converses sendo feitas em compile-time, ou seja, no precisar esperar a aplicao ser executada para detectar possveis problemas. Isso tambm ir permitir que o compilador trabalhe de forma mais eficiente, fazendo otimizaes antes da aplicao efetivamente ser executada. J o Late Binding o processo inverso, ou seja, voc somente ir conhecer o tipo em runtime. O Late Binding menos performtico que o Early Binding, mas te dar uma maior flexibilidade, flexibilidade qual necessria quando trabalhamos com Reflection para criar e instanciar os membros de um determinado tipo. justamente esse tipo de binding que vamos utilizar aqui. Antes de executar qualquer mtodo ou propriedade, temos primeiramente que instanciar a classe em runtime e, para isso, existem duas formas. A primeira delas utilizando o mtodo de instncia chamado CreateInstance da classe Assembly ou o mtodo esttico, Israel Aece | http://www.projetando.net

19

Captulo 13 - Reflection

20

tambm chamado CreateInstance, da classe Activator. A diferena entre eles que, no caso da classe Assembly, o tipo informado ser procurado dentro do Assembly que a instncia da classe Assembly est referenciado; j no caso da classe Activator, recebe o nome (identidade) de um Assembly de qual ela efetivamente dever criar a instncia da classe. Internamente, o mtodo CreateInstance da classe Assembly, invoca o mtodo CreateInstance da classe Activator. Ambos os mtodos retornam um objeto do tipo System.Object com a instncia da classe especifica criada. Ainda utilizando o exemplo da classe Cliente que construmos acima, vamos analisar como devemos procede para criar a instncia da mesma em runtime. A classe Cliente est agora em um outro Assembly (uma DLL) em local fsico diferente. Para fins de exemplo, a instncia ser criada a partir do mtodo CreateInstance da instncia da classe Assembly, qual foi criada com o retorno do mtodo esttico LoadFrom, tambm da classe Assembly, qual analisamos anteriormente. O cdigo abaixo exemplifica como instanciar a classe Cliente:
VB.NET Imports System.Reflection Dim asb As Assembly = Assembly.LoadFrom("C:\PeopleLibrary.dll") Dim cliente As Object = asb.CreateInstance( _ "PeopleLibrary.Cliente", _ False, _ BindingFlags.CreateInstance, _ Nothing, _ Nothing, _ Nothing, _ Nothing) C# using System.Reflection; Assembly asb = Assembly.LoadFrom(@"C:\PeopleLibrary.dll"); object cliente = asb.CreateInstance( "PeopleLibrary.Cliente", false, BindingFlags.CreateInstance, null, null, null, null);

O mtodo CreateInstance da classe Assembly sobrecarregado e, em um dos seus overloads, possvel passar vrios parmetros para que o mtodo cria a instncia do objeto. Entre esses parmetros, temos: Israel Aece | http://www.projetando.net

20

Captulo 13 - Reflection
Parmetro typeName ignoreCase bindingAttr

21

Descrio Uma string representando o tipo que dever ser instanciado. Um valor booleano indicando se a busca pelo tipo dever ou no ser case-sensitive. Uma combinao de valores disponibilizados no enumerador BindingFlags que afetar na forma que a busca pelo tipo ser efetuada. Entre os valores disponibilizados por esse enumerador, temos os principais deles descritos abaixo: CreateInstance Especifica que o Reflection dever criar uma instncia do tipo especificado e chama o construtor que se enquandra com o array de System.Objects que pode ser passado para o mtodo CreateInstance. DeclaredOnly Somente membros declarados no mesmo nvel da hierarquia do tipo ser considerado na busca por membros e tipos. Default Especifica o flag de no-binding. ExactBinding Esta opo especifica que os tipos dos argumentos fornecidos devem exatamente coincidir com os tipos correspondentes dos parmetros. Uma Exception lanada se o chamador informar um Binder. FlattenHierarchy Especifica que membros estticos pblicos e protegidos devem ser retornados. Membros estticos privados no so retornados. Membros estticos incluem campos, mtodos, eventos e propriedades. Tipos aninhados no so retornados. GetField Espefica que o valor de um campo especifco deve ser retornado. GetProperty Especifica que o valor de uma propriedade especfica deve ser retornada. IgnoreCase Especifica que o case-sensitive deve ser considerado quando efetuar o binding. IgnoreReturn Usada em interoperabilidade com COM, ignora o valor de retorno do mtodo. Instance Especifica que membros de instncia so includos na pesquisa do membro. InvokeMethod Especifica que o mtodo ser invocado. NonPublic Especifica que membros no-pblicos sero includos na pesquisa do membro. OptionalParamBinding Esta opo utilizada quando o mtodo possui parmetros default. Public Especifica que membros pblicos sero includos na pesquisa de membros e tipos. SetField Este valor especifica que o valor de um membro Israel Aece | http://www.projetando.net

21

Captulo 13 - Reflection

22

especfico deve ser definido. SetProperty Espeifica que o valor de uma propriedade especfica deve ser definido. Static Especifica que membros estticos sero includos na pesquisa do membro. binder Um objeto que habilita o binding, coero de tipos do argumentos, invocao dos membros e recuperar a instncia de objetos MemberInfo via Reflection. Se nenhum binder for informado, um binder padro utilizado. args Um array de System.Object contendo os argumentos que devem ser passados para o construtor. Se o construtor for sobrecarregado, a escolha do qual utilizar ser feito a partir do nmero de tipo dos parmetros informados quando invocar o objeto. culture Uma instncia da classe CultureInfo que utilizada para a coero de tipos. Se uma referncia nula for passado para este parmetro, a cultura da thread corrente ser utilizada. activationAttributes Um array de elementos do tipo System.Object contendo um ou mais atributos que podem ser utilizados durante a ativao do objeto. Se adicionarmos um construtor na classe Cliente, ento devemos passar ao parmetro args um array com os valores para satisfazer o overload, onde necessrio estar com o mesmo nmero de parmetros, ordem e tipos. Para fins de exemplo, vamos, atravs do cdigo abaixo, invocar a classe Cliente passando os objetos para o construtor que aceita um inteiro e uma string. O cdigo muda ligeiramente:
VB.NET Imports System.Reflection Dim asb As Assembly = Assembly.LoadFrom("C:\PeopleLibrary.dll") Dim constrArgs() As Object = {123, "Jos Torres"} Dim cliente As Object = asb.CreateInstance( _ "PeopleLibrary.Cliente", _ False, _ BindingFlags.CreateInstance, _ Nothing, _ constrArgs, _ Nothing, _ Nothing) C# using System.Reflection; Assembly asb = Assembly.LoadFrom(@"C:\PeopleLibrary.dll"); object[] constrArgs = { 123, "Jos Torres" }; object cliente = asb.CreateInstance(

22

Israel Aece | http://www.projetando.net

Captulo 13 - Reflection
"PeopleLibrary.Cliente", false, BindingFlags.CreateInstance, null, constrArgs, null, null);

23

O mtodo CreateInstance retorna um System.Object representando a instncia da classe informada. Se caso o tipo no for encontrado, o mtodo retorna uma valor nulo e, sendo assim, necessrio testar essa condio para no deixar a aplicao falhar. Em seguida, depois do objeto devidamente instanciado, vamos analisar como fazer para invocar os mtodos e propriedades que o objeto possui. Para que possamos invocar os tipos do objeto, necessrio extrairmos o Type do mesmo como j vimos acima e, dando seqncia ao exemplo, as prximas linhas de cdigo ilustram como extrair o objeto Type, atravs do mtodo GetType da varivel cliente:
VB.NET Dim tipo As Type = cliente.GetType() C# Type tipo = cliente.GetType();

Essa classe tem um mtodo denominado ExibeDados, que retorna uma string com o Id e o Nome do cliente concatenados. Como definimos no construtor da classe os valores 123 e Jos Torres, ao invocar o mtodo ExibeDados, esses valores devero ser exibidos. Para que seja possvel invocar o mtodo em runtime, necessitamos utilizar o mtodo InvokeMember da classe Type, que retorna um System.Object com o valor retornado pelo mtodo. O exemplo abaixo ilustra como utiliz-lo:
VB.NET Console.WriteLine(tipo.InvokeMember( _ "ExibeDados", _ BindingFlags.InvokeMethod, _ Nothing, _ cliente, _ Nothing)) C# Console.WriteLine(tipo.InvokeMember( "ExibeDados", BindingFlags.InvokeMethod, null, cliente,

23

Israel Aece | http://www.projetando.net

Captulo 13 - Reflection
null));

24

Entre os parmetros que esse mtodo utiliza, temos (na ordem em que aparecem no cdigo acima): uma string contendo o nome do construtor, mtodo, propriedade ou campo a ser invocado (se informar uma string vazia, o membro padro ser invocado); uma combinao das opes fornecidas pelo enumerador BindingFlags (detalhado mais acima) indicando como a busca pelo membro ser efetuada; binder a ser utilizado para a pesquisa do membro; a instncia do objeto de onde o mtodo ser pesquisado e executado e, finalmente, um array de elementos do tipo System.Object, contendo os parmetros necessrios para ser passado para o mtodo a ser executado. Alm do mtodo, ainda h a possibilidade de invocarmos propriedades em runtime. Podemos alm de ler as informaes de cada uma delas, podemos definir os valores a elas. Para isso, utilizamos o mtodo GetProperty da classe Type, que retorna uma instncia da classe PropertyInfo, que representa a propriedade e, atravs dela, definimos os valores e extraimos para escrev-los, assim como mostrado no trecho de cdigo abaixo:
VB.NET Dim nome As PropertyInfo = tipo.GetProperty("Nome") Dim id As PropertyInfo = tipo.GetProperty("Id") nome.SetValue(cliente, "Mario Oliveira", Nothing) id.SetValue(cliente, 456, Nothing) Console.WriteLine(nome.GetValue(cliente, Nothing)) Console.WriteLine(id.GetValue(cliente, Nothing)) C# PropertyInfo nome = tipo.GetProperty("Nome"); PropertyInfo id = tipo.GetProperty("Id"); nome.SetValue(cliente, "Mario Oliveira", null); id.SetValue(cliente, 456, null); Console.WriteLine(nome.GetValue(cliente, null)); Console.WriteLine(id.GetValue(cliente, null));

Basicamente, quando chamamos o mtodo SetValue, passamos a instncia do objeto onde as propriedades sero manipuladas; o novo valor a ser definido para a propriedade e, finalmente, um objeto do tipo System.Object, quando a propriedade se tratar de uma propriedade indexada. J o mtodo GetValue quase idntico, apenas no temos o valor a ser definido, pois como o prprio nome do mtodo diz, ele utilizado para ler o contedo da propriedade. Israel Aece | http://www.projetando.net

24

Captulo 13 - Reflection

25

Finalmente, se quisermos extrair os eventos que a classe possui e vincularmos dinamicamente para que o mesmo seja disparado, podemos fazer isso atravs do mtodo AddEventHandler fornecido pelo classe EventInfo. Como sabemos, a classe Cliente fornece um evento chamado AlterouDados, qual podemos utilizar para que quando um valor for alterado em uma das propriedades, esse evento seja disparado. O cdigo abaixo ilustra como configurar para vincular dinamicamente o evento:
VB.NET Dim ev As EventInfo = tipo.GetEvent("AlterouDados") ev.AddEventHandler(cliente, New EventHandler(AddressOf Teste)) ... Private Sub Teste(ByVal sender As Object, ByVal e As EventArgs) Console.WriteLine("Alterou...") End Sub C# EventInfo ev = tipo.GetEvent("AlterouDados"); ev.AddEventHandler(cliente, new EventHandler(Teste)); //... private void Teste(object sender, EventArgs e) { Console.WriteLine("Alterou..."); }

Criao dinmica de Assemblies H um namespace dentro de System.Reflection chamado de System.Reflection.Emit. Dentro deste namespace existem vrias classes que so utilizadas para criarmos dinamicamente um Assembly e seus respectivos tipos. Essas classes so tambm conhecidas como builder classes, ou seja, para cada um dos membros que vimos anteriormente, como por exemplo, Assembly, Type, Constructor, Event, Property, etc., existem uma classe correspodente com um sufixo em seu nome, chamado XXXBuilder, indicando que um construtor de um dos itens citados. Para a criao de um Assembly dinmico, temos as seguintes classes: Classe ModuleBuilder EnumBuilder TypeBuilder Descrio Cria e representa um mdulo. Representa um enumerador. Fornece um conjunto de rotinas que so utilizados para Israel Aece | http://www.projetando.net 25

Captulo 13 - Reflection

26

ConstructorBuilder EventBuilder FieldBuilder PropertyBuilder MethodBuilder ParameterBuilder GenericTypeParameterBuilder LocalBuilder ILGenerator

criar classes, podendo adicionar mtodos e campos. Define um construtor para uma classe. Define um evento para uma classe. Define um campo. Define uma propriedade para uma determinada classe. Define um mtodo para uma classe. Define um parmetro. Define um parmetro genrico para classes e mtodos. Cria uma varivel dentro de um mtodo ou construtor. Gera cdigo MSIL (Microsoft Intermediate Language).

26

Israel Aece | http://www.projetando.net

Você também pode gostar