## Capítulo 4. Construindo Classes e Assemblies com o VB .NET

NESTE CAPÍTULO

- ### Criando Sua Primeira Biblioteca de Classes

- ### O Código "Final"

- ### Compilando o Assembly

- ### Trabalhando Com Assemblies e o GAC

Durante os últimos três anos, muitos desenvolvedores em VB têm passado boa parte do seu tempo criando componentes COM. Estes componentes são usados em camadas médias em sistemas multicamadas. Os benefícios da criação de aplicações multicamadas são bem conhecidos e incluem:

- Reutilização de Código
- Eliminação de muitas das dores de cabeça de distribuição
- Encapsulamento da lógica de negócio para controlar processos de negócios e acesso ao banco de dados

Por isto não é surpresa que o VB .NET lhe permita criar componentes, mas eles não mais são componentes COM. Componentes COM têm certos elementos tais como IDs de classes (CLSIDS), type libraries e IDs de interfaces (IIDs). Cada classe em um componente COM tem que suportar as interfaces IUnknown e IDispatch.

O VB .NET trata uma ou mais classes compiladas dentro de um arquivo como uma biblioteca de classes e não como um componente COM. Bibliotecas de classes são compiladas dentro de um assembly, que frequentemente tem a extensão .dll. Você pode usar classes de uma biblioteca de classes quase do mesmo modo como usa classes de um componente COM. Você pode instanciar os objetos na aplicação cliente e então chamar propriedades, métodos e responder a eventos. Contudo, assemblies não são componentes COM; eles são assemblies .NET.

### Criando Sua Primeira Biblioteca de Classes

Para ver como criar a sua primeira biblioteca de classes, inicie o Visual Studio .NET e, da página de início, clique em Create new Project. Da caixa de diálogo New Project, escolha Visual Basic Projects na listbox Project Types e, então, escolha Class Library na listbox Templates. Nomeie o projeto Healthcare e clique no botão OK.

Neste ponto, você tem uma nova classe criada para você. A primeira coisa que você pode notar é que a classe não possui um designer, que é o "formulário" sobre o qual você pode dispor controles. Esta falta de um designer torna a classe diferente de muitos outros tipos de arquivos VB .NET, tais como formulários, formulários Web e mesmo Web Services. Você inicia com uma classe vazia. Você nem mesmo tem um bloco de código gerado pelo Visual Studio .NET como você tem em muitos outros tipos de arquivos.

Neste momento, você tem uma classe chamada Class1. Aqui é onde as coisas começam a divergir do VB6. No VB6, você tinha uma classe por módulo de classe e elas eram compiladas em um único componente. Os módulos de classe do VB6 tinham uma extensão .CLS. No VB .NET, o seu módulo tem uma extensão VB e um único arquivo .VB de código fonte pode conter mais de uma classe. Você pode criar uma nova classe a qualquer momento usando um bloco Class ... End Class. Finalmente, um ou mais arquivos de código podem ser compilados em um assembly.

Na janela de código, mude a linha de definição da classe para nomeá-la Paciente. Em outras palavras, mude esta linha:

Public Class Class1

para esta:

Public Class Paciente

Agora você mudou o nome da classe, mas se você olhar na janela do Soluction Explorer ou se apenas olhar na tab da janela de código atual, você vê que o nome do arquivo continua sendo Class1.vb.

Clique com o botão direito sobre o ícone Class1.vb no Soluction Explorer e escolha Rename. Nomeie o arquivo Healthcare.vb. Você pode ver a tab na principal janela de código mudar para refletir o novo nome de arquivo. O que você fez foi mudar o nome do arquivo que contém uma ou mais classes.

### Adicionando uma Classe "Incrementada"

Você já tem a sua primeira classe, que foi criada para você quando você criou a biblioteca de classes. Esta classe é agora chamada Paciente, mas ela não tem um construtor (Public Sub New) como muitos novos arquivos no VB .NET têm. Você pode adicionar o seu próprio construtor se você quiser que sua classe tenha um.

Esta classe não tem um construtor pré-criado para você e nem tem um designer. Mas é possível criar uma classe que vem com um designer e um construtor já criados. No Soluction Explorer, clique com o botão direito no nome do projeto, escolha Add e então escolha Add Component. Da caixa de diálogo que se abre, escolha Component Class e clique em Open. Isto adiciona um novo arquivo ao projeto, que tem uma classe nele. Esta classe, contudo, inclui um designer, conforme mostrado na Figura 4.1.

Figura 4.1. Uma nova classe criada com um designer e um construtor já criado.

Como a nova classe inclui um designer, você pode realizar ações tais como arrastar conexões a bancos de dados para facilitar a criação de componentes de acesso a dados.

Dando um clique duplo no designer abre a janela de código. Você pode ver que há mais código nesta classe que na classe que foi criada com o projeto da biblioteca de classes. Se você examinar o código, você notará a seguinte linha:

Inherits System.ComponentModel.Component

Esta linha faz a classe herdar da classe base Component no System.ComponentModel. Se você adicionasse esta linha á sua classe Healthcare, você teria acesso a um designer também lá.

Por enquanto, retorne à classe genérica Healthcare que você criou antes.

### Criando Propriedades

Você pode iniciar agora a criação de propriedades para a sua classe, mas saiba que a sintaxe para a criação de propriedades mudou.

Adicione uma propriedade chamada `FirstName` criando primeiro uma variável privada. Dentro da classe adicione o seguinte código:

Dim msFirstName as String

Agora, adicione o seguinte código. Repare que tão logo você digita a primeira linha, muito do restante do código será completado para você pelo IDE. Você ainda terá que adicionar o Return e o assinalamento.

Public Property FirstName() As String
Get
Return msFirstName
End Get
Set(ByVal Value As String)
msFirstName = Value
End Set
End Property

Repare na nova sintaxe para criar propriedades. Você não mais criar procedimentos Public Property Get/Public Property Let. Agora, você cria um bloco Public Property e adiciona uma seção Get e Set a ele. Não há mais Let, o que torna a vida mais fácil.

Observe também que você não tem que adicionar um argumento à definição da propriedade como você tem de fazer no procedimento Public Property Let do VB6. Isto significa que você não mais tem que criar especificamente um argumento para aceitar o valor recebido; se o usuário passar um valor, ele é posto na variável Value automaticamente.

### Criando um Cliente de Teste

Agora é hora de testar esta classe. É verdade que ela só tem uma propriedade, mas você precisa ver como chamar esta classe numa aplicação cliente. Do menu File, escolha New e então Project. Desta vez adicione uma aplicação Windows. Nomeie-a HealthcareClient, mas antes de clicar em OK, certifique-se de que você selecionou o botão de opção Add to Soluction. O padrão é fechar a solution atual e criar uma nova. Escolhendo adicionar este novo projeto á solution atual, você tem o equivalente a um grupo do VB6.

Após você clicar no botão OK, o novo projeto é carregado no Soluction Explorer, conforme mostrado na Figura 4.2. Como no VB6, o nome do projeto que aparece em negrito no Soluctoin Explorer é o projeto que irá iniciar quando você iniciar a aplicação. Simplesmente clique com o botão direito sobre o projeto HealthcareClient e escolha Set as StartUp Project do menu pop-up.

Figura 4.2. O Solution Explorer exibindo dois projetos carregados ao mesmo tempo.

No Soluction Explorer, clique com o botão direito no nó References para o projeto HealthcareClient e escolha Add Reference. A caixa de diálogo Add Reference aparecerá. Clique na tab Projects e você irá ver o seu projeto Healthcare. Ele já está destacado, mas o botão OK está desabilitado, como você pode ver na figura 4.3. Clique o botão Select para mover o Healthcare para dentro da caixa Selected Components e então clique no botão OK.

Figura 4.3. A caixa de diálogo Add Reference.

Agora, no formulário de HealthcareClient, adicione um botão. Dê um clique duplo para abrir a janela de código e entre com o código a seguir para o procedimento de evento Button1_Click:

Protected Sub button1_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles button1.Click
Dim myPaciente As New Healthcare.Paciente()
myPaciente.FirstName = "Bob"
MsgBox(myPaciente.FirstName)
End Sub

Este código deve parecer muito familiar para os desenvolvedores de VB6. Após adicionar uma referência, você instancia o objeto atribuindo New Healthcare.cPaciente à variável myPaciente. Se você está acostumado com o VB6, você pode ser tentado a experimentar um atalho e digitar esta linha de código:

Dim myPaciente As New Paciente()

Se você digitar esta linha, contudo, você receberá um erro que diz User-defined type not defined: Paciente. Isto mostra que você necessita ter o nome do componente completamente qualificado. A palavra Healthcare neste caso não é o nome do assembly, ela é o namespace.

Para contornar este problema, você pode importar o namespace contendo a classe Paciente. Como foi visto no capítulo 3, "Principais Mudanças noVB .NEt", todos os projetos têm um namespace padrão, e o nome do namespace padrão é o mesmo do projeto. Portanto, se você for ao topo do módulo de código e adicionar um comando Imports, a referência abreviada para a classe Paciente irá funcionar. O seu comando Imports precisa estar no topo do módulo e ele irá se parecer com isto:

Imports Healthcare

Você pode ter notado, no código, que você usou a palavra New para criar um objeto. No VB6, você poderia usar a palavra New de duas formas. Aqui está a primeira:

Dim myPaciente as New Healthcare.Paciente

No VB6, este código funciona, mas não é a melhor forma de criar objetos. Quando você usa este método, o objeto não é realmente criado até que você chame a primeira propriedade ou método. Na verdade, cada chamada a propriedade ou método requer uma checagem para ver se o objeto já foi instanciado. Para evitar este sobre-trabalho, você deve usar este método no VB6:

Dim myPaciente as Healthcare.Paciente
Set myPaciente = New Healthcare.Paciente

No VB .NET, contudo, os dois métodos seguintes são considerados equivalentes:

Dim myPaciente As New Paciente()
Dim myPaciente As Paciente = New Paciente()

Em ambas as linhas, o objeto é criado na memória imediatamente. Isto significa que com o VB .NET, você terá o objeto na memória, e você evita o sobre-trabalho de ter que checar se o objeto já está na memória. Com o VB6, este sobre-trabalho poderia ocorrer a cada chamada ao objeto se o instanciasse usando a sintaxe abreviada. Com o VB .NET, você evita completamente isto porque o VB .NET sabe que o objeto é instanciado imediatamente e não precisa checar se ele já existe.

### Propriedades Apenas de Leitura e Apenas de Gravação

Voltando à biblioteca de classes que você criou, você pode notar que a propriedade FirstName, que você criou, tem tanto uma seção Get como Set no bloco da propriedade. No VB6, para tornar uma propriedade apenas leitura, você simplesmente não criava o procedimento Public Property Let. Para criar uma propriedade apenas de gravação, você não criava o procedimento Public Property Get.

Você pode querer criar uma propriedade apenas de leitura no VB .NET simplesmente deixando de fora o bloco Set ... End Set. Contudo, o VB .NET trata propriedades apenas de leitura e apenas de gravação de modo diferente: Você precisa adicionar uma palavra chave na declaração da propriedade. Para criar uma propriedade apenas de leitura chamada Idade (você pode assumir que ela é calculada da data de nascimento da pessoa), o seu código deve se parecer com isto:

Public ReadOnly Property Idade() As Single
Get
'obtém a data de nascimento
'calcula idade com a data de nascimento
'retorna idade
End Get
End Property
A criação de uma propriedade apenas de gravação é igualmente simples. Apenas ponha a palavra WriteOnly na declaração da propriedade e tenha apenas um bloco Set ... End Set no procedimento da propriedade.

Propriedades Parametrizadas

É possível criar propriedades parametrizadas. Usando o exemplo de um paciente, considere que um paciente pode ter vários médicos a atendê-lo ao mesmo tempo. Portanto, embora a sua biblioteca de classes Healthcare tenha uma classe Médico, o paciente poderá ter uma coleção de objetos Médico. Você poderia acessar esta coleção através de uma propriedade parametrizada e percorrer a coleção.

A criação de uma propriedade parametrizada é simples: você apenas adiciona um parâmetro ao procedimento de propriedade. Se você tem uma propriedade Médicos que percorre uma coleção de objetos Médico, o código se pareceria com isto:

Dim ListaDeMédicos As New Collection()
Public ReadOnly Property Médicos(ByVal iIndex As Integer) _
As Médico
Get
Return CType(ListaDeMédicos(iIndex), Médico)
End Get
End Property

Você pode notar algo de incomum neste código.A funcionalidade básica está ali, você passa um índice e obtém um objeto particular da coleção. Note, porém, que, embora a ListaDeMédicos esteja definida como um tipo de dados Collection, o Option Strict irá impedir a conversão automática de uma tipo de dados Collection para o tipo Médico. Portanto, se Option Strict estiver ligado, você precisa chamar a função CType para passar o objeto que você quer converter e o tipo de classe na qual você quer converter o objeto. Apenas então o retorno será bem sucedido.

Propriedades Default

No começo do capítulo 3, você aprendeu que as propriedades default sem parâmetros se foram. Contudo, se uma propriedade tiver um ou mais parâmetros, ela pode ser a propriedade default. Portanto, a propriedade Médicos em Paciente poderia ser uma propriedade default.

Para tornar uma propriedade na propriedade defalt é tão simples quanto adicionar a palavra Default na frente da declaração da propriedade. Para tornar a propriedade Médicos a default, a sua declaração ficaria assim:

Default Public ReadOnly Property _
Médicos(ByVal iIndex As Integer) As Médico

Agora, no seu programa cliente, você poderia chamar a propriedade default do objeto Paciente. As duas últimas linhas deste código são equivalentes:

Imports Healthcare
...
Dim Paciente As New Paciente()
Dim Med As New Médico()
Med = Paciente.Médicos(1)
Med = Paciente(1) 'equivalente à linha acima

Construtores nas Suas Classes

A classe que você criou até agora, Paciente, não veio com um construtor; em outras palavras, não havia um Sub New disponível quando você acabou de criar a classe. Construtores podem ser bastante úteis, porque eles lhe permitem instanciar um objeto já com alguns valores nele.

Por exemplo, assuma que você gostaria de poder passar uma identificação de paciente ao instanciar um objeto Paciente. Isto significa que você poderia escrever código que criaria o objeto e, em tempo de criação, ler um banco de dado e preencher as propriedades de um Paciente em particular. Sua definição para o Sub New ficaria assim:

Public Sub New(Optional ByVal iPacienteID As Integer = 0)

Seu código cliente poderia agora instanciar o objeto e passar um valor na instanciação. Qualquer das linhas seguintes lhe permitiriam criar uma instância do objeto com um valor já atribuído:

Dim Paciente As New Paciente(1)
Dim Paciente As Paciente = New Paciente(1) 'equivalente á linha de cima

Classes Sem Construtores

Obviamente, suas classes não têm que ter uma construtor. Se você somente usa o bloco Class ... End Class para criar uma classe, você não receberá um Sub New. Mesmo que uma classe não tenha construtores ou implemente System.ComponentModel.Component, a classe pode ainda ter propriedades e métodos, e pode ser criada como qualquer outra classe. A classe Médicos que foi mencionada em exemplos anteriores poderia ser assim:

Public Class Médico
Dim miMedID As Integer
Public Property MédicoID() As Integer
Get
Return miMedID
End Get
Set
miMedID = Value
End Set
End Property
Public ReadOnly Property Idade() As Single
Get
'obtém a data de nascimento
'calcula idade com a data de nascimento
'retorna idade
End Get
End Property
End Class

Adicionando Métodos às Classes

Para adicionar um método a uma classe procede-se do mesmo modo como no VB6. Se você quiser criar um método Internar na classe Paciente, o seu código poderia ser assim:

Public Function Internar() As Boolean
'inclui o Paciente no banco de dados
Return True
End Function

A chamada a este método do cliente é igualmente simples:

If Paciente.Internar Then...

Antes que você comece a pensar que nada mudou nos métodos, entenda que há várias mudanças na definição de métodos. Contudo, as mudanças são específicas da herança e elas serão cobertas no capítulo 5, "Herança no VB .NET"..

Adicionando Eventos

Para adicionar um evento á sua classe, use a palavra chave Event. Imagine que você queira adicionar um evento à classe que o notifique quando você tiver resultados pendentes de exames laboratoriais. Você poderia criar o evento usando um código como este dentro da classe que necessita disparar o evento:

Event LabResult(ByVal TipoLab As String)

Isto apenas criar a definição do evento no seu código. Para realmente causar o disparo do evento, você tem que ter um comando RaiseEvent em alguma parte do seu código. Por exemplo, se você atribui a propriedade PacienteID, você pode checar o banco de dados para verificar algum novo resultado de exame para aquele paciente. Se houver algum, você pode disparar um evento. Seu código seria parecido com isto:

Dim miPacienteID As Integer
Public Property PacienteID() As Integer
Get
Return miPacienteID
End Get
Set
miPacienteID = Value
'verifica o banco de dados de exames para este paciente
'se houver resultados de novos exames
RaiseEvent LabResult("CBC")
End Set
End Property

Se isto fosse um procedimento real, você não codificaria fixo o valor "CBC" no evento, mas o iria pegar do banco de dados.

Trantando o Evento Usando WithEvents

Você tem duas opções para tratar este evento no cliente. A primeira forma é usar a palavra chave WithEvents. A segunda forma é usar o comando AddHandler.

Para usar a palavra chave WithEvents, você precisa declarar o objeto e incluir a palavra WithEvents. Note que se você estiver usando WithEvents, a declaração não pode ser local a um sub ou função. Portanto, o código a seguir deve estar fora de qualquer sub ou função, e estar a nível de módulo no seu cliente:

Dim WithEvents Paciente As New Paciente

Esta linha assume que você importou o namespace HealthCare. Esta linha fica a nível de módulo da sua classe, porque você não pode usar WithEvents em uma declaração interna a um procedimento.

Agora, para adicionar um procedimento de evento, clique na combo Class Name no topo da janela de código e escolha Paciente. Na combo Method Name, escolha LabResult. Isto cria um procedimento de evento chamado Paciente_LabResult que se parece com isto:

Public Sub Paciente_LabResult(ByVal TipoLab As System.String) _
Handles Paciente.LabResult
...
End Sub

Tratando o Evento com `AddHandler`

A segunda forma de tratar eventos é usar o comando AddHandler.Você agora não tem que declarar um objeto usando WithEvents. Você o define como fez antes. Você precisa ter um sub ou função que agirá como um procedimento de evento.

Em seguida, você usa o comando AddHandler para ligar um evento em particular do objeto ao procedimento que você criou para tratar o evento.

Por exemplo, assuma que você quer criar um tratamento de evento chamado LabHandler. Este procedimento seria definido como um sub padrão. Ele teria que ter os mesmos argumentos definidos no evento LabResult da classe Paciente. No exemplo mostrado aqui, o evento LabResult passa um parâmetro com o tipo de dados String. Portanto, o seu procedimento teria que aceitar uma string como um argumento.

Quando você usa AddHandler, você especifica o nome do evento na classe Paciente que você quer que seja tratado (LabResult), mas você precisa também referir o procedimento; neste caso, LabHandler. Contudo, você não refere LabHandler diretamente; você o refere por endereço usando o operador AddressOf. Seu código para configurar o tratamento de evento com AddHandler ficaria assim:

Protected Sub Button1_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Button1.Click
Dim Paciente As New Paciente()
AddHandler Paciente.LabResult, AddressOf Me.LabHandler
...
End Sub
Private Sub LabHandler(ByVal TipoLab As String)
...
End Sub

Note que no AddHandler, você refere a variável de objeto (Paciente) e o nome do evento (LabResult) usando a sintaxe Objeto.Evento.

Por que você poderia querer usar AddHandler ao invés de WithEvents? A vantagem de usar AddHandler é que você pode criar um procedimento que pode tratar múltiplos eventos, mesmo se os eventos forem disparados de diferentes componentes. Você também pode tratar eventos de objetos que você cria em tempo de execução. Por exemplo, você poderia obter uma coleção de objetos passados para você, mas ainda ser capaz de tratar os seus eventos usando AddHandler.

O Código "Final"

O código seguinte nada faz e algumas mudanças anteriores foram desfeitas neste código. Se você ainda estiver tentando estudá-lo e quiser ter certeza de que ele irá compilar, eis aqui como o seu código dentro do projeto Healthcare deverá ficar:

Public Class Paciente
Dim msFirstName As String
Dim ListaDeMédicos As New Collection()
Dim miPacienteID As Integer
Public Property FirstName() As String
Get
Return msFirstName
End Get
Set(ByVal Value As String)
msFirstName = Value
End Set
End Property
Default Public ReadOnly Property Médicos _
(ByVal iIndex As Integer) As Médico
Get
Return CType(ListaDeMédicos(iIndex), Médico)
End Get
End Property
Public Function Admit() As Boolean
'adiciona um Paciente ao banco de dados
Return True
End Function
Event LabResult(ByVal TipoLab As String)
Public Property PacienteID() As Integer
Get
Return miPacienteID
End Get
Set(ByVal Value As Integer)
miPacienteID = Value
'verifica o banco de dados de exames para este Paciente
'se houver algum resultado de exame
RaiseEvent LabResult("CBC")
End Set
End Property
End Class
Public Class Médico
Dim miMedID As Integer
Public Property MédicoID() As Integer
Get
Return miMedID
End Get
Set(ByVal Value As Integer)
miMedID = Value
End Set
End Property
Public ReadOnly Property Idade() As Single
Get
'pega data de nascimento
'calcula idade com a data de nascimento
'retorna a idade
End Get
End Property
End Class

Compilando o Assembly

Agora que você criou uma biblioteca de classe com duas classes (Paciente e Médico), é hora de compilar o seu assembly. No VB6, você compilaria um componente COM, mas você não mais está no VB6. Você agora está escrevendo para a .NET Framework e isto significa que você irá compilar um assembly. O assembly pode ter uma extensão .DLL, mas ele não é uma DLL tradicional no sentido da API do Windows nem é uma DLL de componente no sentido do VB6.

Criar o asembly é bem simples. A opção de criação não mais está no menu File, mas está agora em um menu separado intitulado Build. Se você clicar no menu Build, você verá várias escolhas. Por quê, neste exemplo, você tem uma solution com uma biblioteca de classe (Healthcare) e uma aplicação Windows (HealthcareClient), você verá opções para compilar ou re-compilar a solution. Você também tem uma opção para distribuir a aplicação. Na próxima seção do menu Build, você tem a opção para compilar ou re-compilar um dos projetos da solution, dependendo de que projeto está destacado no Solution Explorer quando você clica no menu. Finalmente, há a escolha para a compilação batch e uma para o Configuration Manager.

No .NET, você pode compilar seus assemblies tanto no modo Debug ou no modo Release, ou você pode criar seus próprios modos customizados. O modo Debug compila não usa nenhuma otimização do compilador e compila em informações simbólicas de depuração. O modo Release não compila qualquer informação simbólica de depuração e aplica otimizações do compilador. Naturalmente, você usará o modo Debug enquanto estiver desenvolvendo e depurando sua aplicação. Após você ter usado o modo Debug durante o desenvolvimento e depuração da sua aplicação, você muda para o modo Release e re-compila o seu assembly. Observe que você pode modificar estes modos ou adicionar o seu próprio. Por exemplo, você poderia adicionar informação de depuração em projetos compilados no modo Release, se você optar por modificar o modo Release desta forma.

Você pode ver que a opção Debug é selecionada por padrão se você olhar na barra de ferramentas. A combo de configuração de solutions permite que você escolha Debug ou Release e entrar no Configurations Manager. Você vê isto na figura 4.4.

Figura 4.4. A opção de configuração de solutions na barra de ferramentas.

Agora, apenas clique no projeto Healthcare uma vez na janela do Solution Explorer. Isto irá selecioná-lo como o projeto a ser compilado se você escolher não compilar a solution total. Agora, clique no menu Build e escolha Buid Healthcare. A janela de Output na base do IDE irá reportar quando a compilação terminar e mostrar quantos projetos compilaram, quantos falharam e quantos foram pulados. Neste caso, o projeto HealthcareClient não é contado como pulado, desta forma você deverá ver que um projeto foi compilado, sem falhas e nenhum projeto foi saltado.

O processo de compilação cria dois arquivos. A localização default para o projeto é Meus Documentos\Visual Studio Projects\Healthcare. Se você olhar na pasta, você verá uma pasta bin abaixo dela. Dentro da pasta bin há dois arquivos: healthcare.dll e healthcare.bdb. A DLL é o assembly e o PDB é também usado como um formato comum para arquivos usado pelo sistema operacional Palm. Isto significa que os arquivos .PDB estão associados com a aplicação desktop Palm.

Reutililzando o Assembly em Outras Aplicações

Um das outras grandes diferenças entre isto e o VB6 é que os assemblies do VB .NET não usam o registry. Na verdade o VB .NET não adiciona nenhuma informação sobre o assembly no Registry. Se você pesquisar pelo Registry, você não encontrará qualquer menção ao assembly Healthcare. Você encontrará informação sobre o projeto e a solution, mas isto é apenas para que o Visual Studio possa mostrá-los na lista dos mais recentemente utilizados.

Dado que não há nada sobre o assembly no registry, como você o reutiliza em outra aplicação? Se você iniciar um novo projeto de aplicação Windows no VB .NET e clicar com o botão direito no nó References na janela do Solution Explorer, a caixa de diálog Add Reference irá aparecer. Contudo, o componente Healthcare não estará listado. Isto não deve ser muito surpreendente; ele estava mostrado antes porque o projeto HealthcareClient era parte da mesma solution. Agora, contudo, você iniciou um novo projeto que cria uma nova solution. Esta solution está em um diretório diferente e, portanto, não tem qualquer conhecimento do componente Healthcare.

Para que o novo projeto use o componente Healthcare, certifique-se de estar na tab .NET Framework da caixa de diálogo Add Reference e clique no botão Browse. Navegue até Meus Documentos\Visual Studio Projects\Healthcare\bin e escolha Healthcare.dll. O nó References na janela do Solution Explorer agora mostra o componente Healthcare.

Sua aplicação pode agora usar o componente Healthcare. Você pode criar objetos digitando o seguinte código:

Dim Paciente As New Healthcare.Paciente()

Você pode sempre usar o comando Imports para importar o namespace Healthcare, assim você poderia instanciar objetos com apenas este código:

Dim Paciente As New Paciente()

Como o .NET localiza os Assemblies

Pense de novo no COM: Quando um programa faz uma chamada a um componente, o Service Control Manager vai ao registry, pesquisa a informação do componente, localiza o objeto, cria-o e retorna à aplicação chamadora um ponteiro para o objeto recém criado. O .NET não usa o registry, portanto, ele tem que usar um mecanismo diferente para localizar os assemblies que são referenciados em uma aplicação ( ou em outro assembly ). Os detalhes completos não são de necessidade fundamental para se entender como criar componentes, mas um conhecimento básico do processo é importante para solucionar problemas.

Passo 1: Obtendo a Informação da Referência

Sua aplicação tenta chamar uma referência assembly. A referência contém a seguinte informação sobre o assembly:

- Nome
- Versão
- Cultura
- Chave pública (para assemblies rigorosamente nomeados)

O runtime usa esta informação para tentar localizar o assembly usando os passos seguintes.

Passo 2: Pesquisando no arquivo de configuração

O arquivo de configuração é um conceito poderoso. Você pode criar um arquivo de configuração para tratar qualquer informação de referência que pode mudar. Por exemplo, se você pensar que você irá necessitar de atualizar uma versão particular de um assembly usado pela sua aplicação, você pode armazenar esta informação em um arquivo de configuração. Você também pode forçar uma aplicação a se ligar apenas a versões de componentes que estavam disponíveis quando a aplicação foi criada. Isto é chamado de Modo Seguro. O arquivo de configuração é um arquivo XML.

Passo 3: Usando CodeBases

Se você quiser impedir a pesquisa por um componente, você pode especificar um tipo de valor chamado CodeBase no arquivo de configuração. Quando o runtime carrega o arquivo especificado no CodeBase, ele checa a informação de versão para certificar-se de que ela confere com a referência usada na aplicação. Por default, se o assembly apontado pelo CodeBase tiver a mesma versão ou maior, a ligação ocorrerá.

Se não houver nenhum CodeBase, o processo de busca será iniciado. A busca simplesmente é feita pelo runtime usando o seguinte critério. Primeiro, o runtime procura no GAC se o assembly for rigorosamente nomeado. Se o assembly for privado, a pesquisa é feita somente no diretório raiz da aplicação, referido como AppBase. O runtime vê uma referência na aplicação a um assembly tal como Healthcare e pesquisa pelo assembly primeiramente como Healthcare.DLL.

Se o assembly não for encontrado no diretório AppBase, a pesquisa continua e o runtime pesquisa baseando-se no caminho informado no arquivo de configuração. Este caminho é especificado com a tag <AppDomain> , por exemplo, desta forma:

<probing privatePath="MyAssemblies"/>

O runtime automaticamente junta algumas outras informações ao caminho da pesquisa. Por exemplo, se o assembly que você está procurando for Healthcare, o runtime pesquisa em AppBase\Healthcare. Ele também examina o diretório bin, portanto, ele pesquisa AppBase\bin. Por quê você especificou MyAssemblies na tag <AppDomain>, o runtime pesquisa AppBase\MyAssemblies .

Finalmente, a pesquisa inclui outras informações de localização de língua. Portanto, se você estiver criando um aplicação para múltiplas línguas ou para uma língua específica, o local é adicionado. Se o local for de, o caminho da pesquisa incluirá `AppBase\Bin\de`, `AppBase\Healthcare\de` e `AppBase\MyAssemblies\de`.

Passo 4: O GAC

Lembra-se que o Global Assembly Cache (GAC) é um cache para assemblies usados por múltiplas aplicações na mesma máquina? Mesmo se um assembly for encontrado por busca ou por uso de um CodeBase, o runtime procura por versões atualizadas no GAC, desde que você não tenha desligado esta funcionalidade. Se uma versão mais recente existir no GAC, ela será usada.

Se nenhum assembly for encontrado usando um CodeBase ou a busca, o runtime pesquisa o GAC por um que confira. Se um assembly for encontrado, ele será usado.

Para adicionar assemblies, remover assemblies ou ver assemblies no GAC, use a aplicação de console gacutil.exe fornecida pelo Framework. O GAC também é um diretório, portanto, você pode adicionar assemblies ao GAC meramente copiando-os para dentro do diretório.

Passo 5: O Administrador entre em cena

É possível para o administrador criar um arquivo que especifica uma versão particular de um assembly a ser usado. Se uma aplicação está pesquisando pela versão 2.1.4.0 do Healthcare, o arquivo do Administrador pode apontar a aplicação para uma outra versão, forçando uma atualização ou regressão para uma versão mais antiga.

Trabalhando Com Assemblies e o GAC

Nota

Você precisa possuir privilégios de administrador no seu computador para completar os exemplos nesta seção. Apenas um administrador pode deletar arquivos do GAC.

Frequentemente, quando você criar um assembly, você irá querer compartilhá-lo entre múltiplas aplicações. Isto não é um problema, porque múltiplas aplicações podem apontar para a mesma DLL. Contudo, quando você atribui uma referência a uma DLL dentro de um projeto, ele grava o caminho da DLL. E se o caminho mudar posteriormente? Ou se você não souber o caminho final na máquina de produção?

Conforme você já leu, o Global Assembly Cache permite lhe colocar componente em um cache que pode ser acessado por qualquer aplicação. Portanto, você poderia pegar a Healthcare.DLL e adicioná-la ao GAC, permitindo que as aplicações a acessem independentemente da localização física da DLL.

Atribuindo o Nome Rigoroso

Par ver isto em ação, a primeira coisa que você precisa é atribuir um nome rigoroso ao componente. Um nome rigoroso é uma forma de identificar claramente um componente. Isto inclui algumas informações padrão, tais como o nome do assembly, número de versão e informações de cultura se fornecidas. Também inclui uma chave pública e uma assinatura digital.

Nomes rigorosos são uma forma de um cliente verificar se um componente não foi modificado desde que o componente foi referenciado pelo cliente quando da sua criação. Isto significa que o cliente pode certificar-se de que o assembly não foi modificado e de sua origem. O uso de um nome rigoroso segue este procedimento:

1. O assembly é compilado com um nome rigoroso atribuído a ele. A assinatura digital é criada com a chave privada do desenvolvedor e a assinatura é armazenada num arquivo executável portável do assembly. Esta combinação de nome e chave garante unicidade da mesma forma que uma GUID (Identificação Global Única) no mundo do COM.
2. O cliente referencia o assembly. A criação desta referência adiciona a chave pública do assembly como parte da referência. Tecnicamente, apenas uma parte da chave pública do assembly é adicionada à referência para economizar espaço.
3. Em tempo de execução, o cliente verifica o nome rigoroso usando a chave pública para checar a assinatura digital.

Atribuindo um nome rigoroso também garante que ninguém mais pode criar uma nova versão do seu componente. Por quê o seu componente está assinado com a sua chave única, ninguém mais pode criar uma novo versão porque ele não possui a sua chave. Clientes podem certificar-se de que as novas versões vêm apenas do autor original. Finalmente, nomes rigorosos são usados para verificar que um componente não foi modificado desde a sua criação, bastante parecido com a tecnologia Authenticode e controles ActiveX que são baixados e usados em um navegador.

Para atribuir um nome rigoroso ao seu assembly Healthcare, esteja certo de que o projeto Healthcare está fechado na janela do Solution Explorer. Clique em Project, Properties para abrir a janela de propriedades do Healthcare. Clique em Strong Name na janela de navegação do lado esquerdo.

Você tem duas formas de criar nomes rigorosos: você pode usar um arquivo chave ou um container de chave. O método do arquivo chave não é tão seguro como o método do container de chave. Com o arquivo chave, você inclui o arquivo no projeto, o que significa que ele pode ser compartilhado inadvertidamente. e você é advertido disto na caixa de diálogo. O container de chave realmente acessa a chave do runtime e portanto não tem que armazenar o arquivo chave no projeto. Por enquanto, você fará do modo mais simples e usará o arquivo chave.

Clique em Generate Key sob a textbox Key File e `KeyFile.snk` aparece na textbox. Você pode ver isto na Figura 4.5.

Figura 4.5. A tabe Strong Name da caixa de diálogo Project Properties.

Após clicar no botão OK, você pode ver que o arquivo `KeyFile.snk` foi adicionado ao seu projeto se olhar no Solution Explorer.

Os próximos passos irão parecer antigos a princípio, mas eu explicarei o que está acontecendo na medida que você avançar. Entenda que você está prestes a testar usando o componente de um caminho especificado e então do GAC.

Adicionando o Assembly ao GAC

Agora que você atribuiu um nome rigoroso para o assembly, vá em frente e compile o assembly novamente escolhendo Build Healthcare no menu Build. Relembre que, por default, você cria o assembly na pasta Meus Documentos\Visual Studio Projects\Healthcare\bin (se você estiver usando o diretório default para criar a aplicação). Agora, porém, copie o Healthcare.dll que você criou e mova-o para outro diretório; para estes exemplos, eu assumirei que ele foi copiado para o raiz do drive C:, mas você é livre para copiá-lo para onde quiser. O importante é que fique num diretório fácil de acessar.

Agora, crie um novo projeto tipo Windows Application e chame-o NewHealthcareClient. Dê um duplo clique com o botão direito no nó References da janela do Solution Explorer e escolha Add Reference. Clique na tab Projects e então clique no botão Browse. Navegue até o raiz de C: , selecione o Healthcare.dll que você copiou para lá e, então, clique OK.

Você adicionou uma referência à sua nova aplicação cliente ao assembly que você criou anteriormente. Se você expandir o nó References no Solution Explorer, clicar em Healthcare e olhar na janela Properties, você notará que a propriedade Path está configurada para C:\Healthcare.dll. Isto parece ser exatamente o que você fez no exemplo anterior e, até aqui, de fato é. A única diferença, até aqui, é que o assembly tem um nome rigoroso.

Agora, no formulário do seu projeto NewHealthcareClient, adicione um botão e o seguinte código:

Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
Dim myPaciente As New Paciente()
myPaciente.FirstName = "Sue"
MsgBox(myPaciente.FirstName)
End Sub

Você pode ir em frente e rodar esta aplicação para testá-la, mas você quererá compilá-la antes de continuar testando. Clique no menu Build e escolha Build. O seu arquivo NewHealthcareClient.exe estará em Meus Documentos\Visual Studio Projects\NewHealthcareClient\bin por padrão. Se você olhar neste diretório, verá algo estranho: uma copia de Healthcare.dll! Ele foi automaticamente copiado para o diretório no qual você compilou a aplicação.

Não é sempre que se deseja ter a dll no diretório de execução de cada aplicação. Primeiro, uma única aplicação pode referenciar dúzias de assemblies e você não quer que todos eles dentro do diretório da aplicação. Além disto, você pode querer o componente compartilhado por múltiplas aplicações, de modo que você pode distribuir ajustes sem ter de se preocupar em copiar para múltiplos diretórios.

No diretório para o qual o EXE foi compilado, rode o NewHealthcareClient para ver se ele funciona. Ele deve funcionar sem problems. Agora, neste diretório, delete o Healthcare.dll. Rode a aplicação novamente e clique no botão que você adicionou. Agora, você tem uma mensagem de erro esquisita como mostrado na Figura 4.6.

Figura 4.6. Um erro indicando que um assembly não pode ser encontrado.

Se você clicar no botão Details na caixa de mensagem do erro, você verá a seguinte informação:

"System.IO.FileNotFoundException: File or assembly name Healthcare, or one
of its dependencies, was not found." , traduzindo: "System.IO.FileNotFoundException: Arquivo ou assembly de nome Healthcare ou uma das suas dependências não foi localizado.".

Você pode agora estar coçando sua cabeça. Você referenciou um assembly no diretório raiz do drive C: e o assembly ainda existe. Contudo, sua aplicação aparentemente está procurando pelo arquivo apenas no diretório local. Agora que a cópia local se foi, a aplicação fica confusa e indica que não consegue achar o assembly.

Contudo, a aplicação também está pesquisando o GAC e, naturalmente, também não está localizando o assembly lá. Você precisa primeiro adicionar o assembly ao GAC para que a aplicação possa enchergá-lo.

Abra uma janela de console e navegue até o diretório raiz do drive C:. Você usará o gacutil para instalar o assembly no GAC. Contudo, você precisa achar o gacutil e também digitar o caminho completo ou, neste caso, apenas copiá-lo para um diretório no caminho. Você o encontrara provavelmente no diretório C:\Arquivos de programas\Microsoft Visual Studio .NET\FrameworkSDK\Bin . Após ter localizado o gacutil, digite o seguinte comando:

gacutil /i healthcare.dll

Isto instala o assembly no GAC. Se você não tivesse criado um nome rigoroso, você receberia um mensagem de que não poderia instalar o assembly no GAC sem um nome rigoroso. Por você ter criado um nome rigoroso para este assembly, ele é instalado no GAC sem problemas.

Agora, volte à sua aplicação NewHealthcareClient e execute-a novamente. Desta vez, ela roda normalmente. Você não fez uma mudança sequer à aplicação cliente, mas quando ela procura pelo assembly, ela checa não apenas o diretório local (que não mais contém uma cópia do assembly), mas também o GAC. Neste caso, naturalmente, ela encontra o assembly no GAC e o usa.

Agora, remova o assembly do GAC. De volta à janela de console, digite o seguinte comando:

gacutil /u healthcare

Note que você não inclui a extensão .DLL no nome do assembly a remover. Após ter removido o assembly, rode sua aplicação cliente novamente. Agora, duas coisas podem acontecer. Se você não encerrou o cliente antes de desinstalar o assembly do GAC, o cliente continua a rodar sem problemas. Contudo, se você o encerrou e agora o reinicia, ele não irá rodar. Isto é por quê o cliente armazenou o assembly num cache de memória e não notou que ele havia sido removido do GAC, a menos que fosse encerrado e reiniciado.

Versão e Assemblies .NET

É importante compreender agora o esquema de controle de versão dos assemblies .NET. A primeira regra a recordar é simples: Apenas assemblies com nomes rigorosos têm a política de versão aplicada a eles. Portanto, se você não estiver usando nomes rigorosos, isto não é algo com que tenha que se preocupar.

Assuma que você vai usar nomes rigorosos. Talvez você queira nomes rigorosos porque você quer usar o GAC ou você usará nomes rigorosos para uma pequena segurança adicional. De qualquer forma, é importante entender como o controle de versão funciona com assemblies rigorosamente nomeados.

Números de Versão .NET

O número de versão .NET consiste de quatro partes:

<número principal>.<número secundário>.<compilação>.<revisão>

Assuma que você está trabalhando num projeto e que você está compilando uma nova versão constantemente, digamos diariamente. A cada vez que você compila a aplicação, você irá incrementar a versão de compilação. Se você tiver que fazer múltiplas compilações no mesmo dia, você pode incrementar o número de revisão. Independente de como você usa as quatro partes da versão para acompanhar seus projetos, eles se tornam muito importantes quando você começa a modificá-los.

Os dois primeiros números são constantemente referenciados como o número de versão lógica de um assembly. Por exemplo, no mundo do COM, é comum perguntar "Qual a versão de ADO que você está usando?" com respostas comuns do tipo "2.5" ou "2.6".

Embora os dois primeiros números possam ser a versão lógica, contudo, as aplicações clientes de assemblies rigorosamente nomeados consideram todos os números. O runtime vê assemblies de diferentes números de versão como assemblies diferentes, e isto significa que o seu cliente, por default, os verá como diferentes também. É também possível para um administrador passar por cima deste comportamento default se necessário.

A atribuição do número de versão pode ser obtida de duas formas. Se você examinar o arquivo AssembyInfo.vb que é criado como parte do seu projeto, você verá a seguinte linha:

<Assembly: AssemblyVersion("1.0.*")>

Isto lhe permitirá atribuir a versão da aplicação. Ou, você pode entrar com as seguintes linhas no módulo de código para as suas classes:

Imports System.Reflection
<Assembly: AssemblyVersionAttribute("2.3.4.6")>

Estas linhas têm que ficar fora das classes que você cria.

Sumário

A criação de classes (ou objetos) é uma das coisas mais comumente feitas por um desenvolvedor em VB. A criação de classe é diferente no VB .NET, como você pode ver neste capítulo. Ela muda ainda mais quando você começa a estudar a herança, que é o que veremos no próximo capítulo.

As principais diferenças estão em como você cria propriedades, como trata propriedades apenas leitura, apenas gravação e default, e como você trata eventos. Há também uma grande diferença no fato de que você compila assemblies ao invés de componentes COM. Finalmente, o modo como os arquivos são alocados também é muito diferente.

Finalmente, você viu o compartilhamento de arquivos usando o GAC, e deu uma olhadela em como é feito o controle de versão no .NET. Ao longo do restante do curso, você verá bibliotecas de classes usadas ocasionalmente para encapsular alguma funcionalidade das suas aplicações.