3. Classes, estruturas, interfaces
3.1. O objeto no exemplo
3.1.1. Noções gerais
Abordaremos agora, por meio de exemplos, a programação orientada a objetos. Um objeto é uma entidade que contém dados que definem seu estado (chamados de propriedades) e funções (chamadas de métodos). Um objeto é criado de acordo com um modelo chamado de classe:
Public Class c1
' atributos
Private p1 As type1
Private p2 As type2
....
' método
Public Sub m1(....)
...
End Sub
' método
Public Function m2(...)
....
End Function
End Class
A partir da classe C1 anterior, é possível criar vários objetos O1, O2,… Todos terão as propriedades p1, p2, … e os métodos m3, m4, … Mas terão valores diferentes para suas propriedades pi, possuindo, assim, cada um um estado próprio. Por analogia, a declaração
cria dois objetos (o termo está incorreto aqui) do tipo (classe) Integer. Sua única propriedade é seu valor. Se O1 é um objeto do tipo C1, O1.p1 designa a propriedade p1 de O1 e O1.m1 designa o método m1 de O1. Consideremos um primeiro modelo de objeto: a classe personne.
3.1.2. Definição da classe pessoa
A definição da classe personne será a seguinte:
Public Class personne
' atributos
Private prenom As String
Private nom As String
Private age As Integer
' método
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' método
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Temos aqui a definição de uma classe, ou seja, de um tipo de dados. Ao criarmos variáveis desse tipo, elas serão chamadas de objetos ou instâncias de classes. Uma classe é, portanto, um modelo a partir do qual os objetos são construídos. Os membros ou campos de uma classe podem ser dados (atributos), métodos (funções) ou propriedades. As propriedades são métodos específicos que servem para obter ou definir o valor dos atributos do objeto. Esses campos podem ser acompanhados por uma das três palavras-chave a seguir:
Um campo privado (private) só é acessível pelos métodos internos da classe | |
Um campo público (public) é acessível por qualquer função, definida ou não dentro da classe | |
Um campo protegido (protected) só é acessível pelos métodos internos da classe ou de um objeto derivado (veja mais adiante o conceito de herança). |
Em geral, os dados de uma classe são declarados como privados, enquanto seus métodos e propriedades são declarados como públicos. Isso significa que o usuário de um objeto (o programador):
- não terá acesso direto aos dados privados do objeto
- poderá chamar os métodos públicos do objeto e, em especial, aqueles que darão acesso aos seus dados privados.
A sintaxe de declaração de uma classe é a seguinte:
public class classe
private donnée ou méthode ou propriété privée
public donnée ou méthode ou propriété publique
protected donnée ou méthode ou propriété protégée
end class
A ordem de declaração dos atributos private, protected e public é arbitrária.
3.1.3. O método inicializa
Voltemos à nossa classe [personne], declarada da seguinte forma:
Public Class personne
' atributos
Private prenom As String
Private nom As String
Private age As Integer
' método
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' método
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Qual é a função do método initialise? Como nom, prenom e age são dados privados da classe personne, as instruções:
são inválidas. Precisamos inicializar um objeto do tipo personne por meio de um método público. Essa é a função do método initialise. Escreveremos:
A sintaxe p1.initialise é válida, pois initialise é de acesso público.
3.1.4. O operador new
A sequência de instruções
está incorreta. A instrução
declara p1 como uma referência a um objeto do tipo personne. Esse objeto ainda não existe e, portanto, p1 não está inicializado. É como se escrevêssemos:
onde se indica explicitamente, com a palavra-chave nothing, que a variável p1 ainda não faz referência a nenhum objeto. Quando, em seguida, escrevemos
, estamos chamando o método initialise do objeto referenciado por p1. No entanto, esse objeto ainda não existe e o compilador sinalizará o erro. Para que p1 faça referência a um objeto, é preciso escrever:
Isso tem como efeito a criação de um objeto do tipo personne ainda não inicializado: os atributos nom e prenom, que são referências a objetos do tipo String, terão o valor nothing, e age terá o valor 0. Portanto, há uma inicialização padrão. Agora que p1 faz referência a um objeto, a instrução de inicialização desse objeto
é válida.
3.1.5. A palavra-chave Me
Vamos examinar o código do método initialise:
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
A instrução Me.prenom=P significa que o atributo prenom do objeto atual (Me) recebe o valor P. A palavra-chave Me designa o objeto atual: aquele no qual se encontra o método executado. Como sabemos disso? Vejamos como ocorre a inicialização do objeto referenciado por p1 no programa chamador:
É chamado o método initialise do objeto p1. Quando, nesse método, se faz referência ao objeto Me, na verdade está-se fazendo referência ao objeto p1. O método initialise também poderia ter sido escrito da seguinte forma:
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
prenom = P
nom = N
Me.age = age
End Sub
Quando um método de um objeto faz referência a um atributo A desse objeto, a notação Me.A é implícita. Deve-se utilizá-la explicitamente quando houver conflito de identificadores. Esse é o caso da instrução:
Me.age=age;
onde age designa um atributo do objeto atual, bem como o parâmetro age recebido pelo método. É necessário, então, eliminar a ambiguidade, designando o atributo age como Me.age.
3.1.6. Um programa de teste
Aqui está um breve programa de teste:
Public Class personne
' atributos
Private prenom As String
Private nom As String
Private age As Integer
' método
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' método
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
e os resultados obtidos:
dos>vbc personne1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
3.1.7. Usar um arquivo de classes compiladas (assembly)
Observe que, no exemplo anterior, há duas classes em nosso programa de teste: as classes personne e test1. Existe outra maneira de proceder:
-
compila-se a classe
pessoaem um arquivo específico chamado assembly. Esse arquivo tem a extensão .dll -
compila-se a classe test1 fazendo referência ao assembly que contém a classe pessoa.
Os dois arquivos-fonte ficam assim:
test.vb | |
personne2.vb | |
A classe personne é compilada pela seguinte instrução:
dos>vbc /t:library personne2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
24/02/2004 16:50 509 personne2.vb
24/02/2004 16:49 143 test.vb
24/02/2004 16:50 3 584 personne2.dll
A compilação gerou um arquivo personne2.dll. É a opção de compilação /t:library que indica a geração de um arquivo “assembly”. Agora, vamos compilar o arquivo test.vb:
dos>vbc /r:personne2.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
24/02/2004 16:50 509 personne2.vb
24/02/2004 16:49 143 test.vb
24/02/2004 16:50 3 584 personne2.dll
24/02/2004 16:51 3 072 test.exe
A opção de compilação /r:personne2.dll indica ao compilador que ele encontrará determinadas classes no arquivo personne2.dll. Quando, no arquivo-fonte test.vb, ele encontrar uma referência à classe personne — classe não declarada no arquivo-fonte test.vb —, ele procurará a classe personne nos arquivos .dll referenciados pela opção /r. Lá, ele encontrará a classe personne no assembly personne2.dll. Seria possível incluir outras classes nesse assembly. Para utilizar, durante a compilação, vários arquivos de classes compiladas, escrever-se-á:
A execução do programa test1.exe produz os seguintes resultados:
3.1.8. Outro método é inicializado
Consideremos novamente a classe personne e adicionemos a ela o seguinte método:
' método
Public Sub initialise(ByVal P As personne)
prenom = P.prenom
nom = P.nom
Me.age = P.age
End Sub
Agora temos dois métodos com o nome initialise: isso é válido, desde que aceitem parâmetros diferentes. É o que ocorre neste caso. O parâmetro agora é uma referência P a uma pessoa. Os atributos da pessoa P são, então, atribuídos ao objeto atual (Me). Observe-se que o método initialise tem acesso direto aos atributos do objeto P, embora estes sejam do tipo private. Isso sempre é verdade: um objeto O1 de uma classe C sempre tem acesso aos atributos dos objetos da mesma classe C. Aqui está um teste da nova classe personne, que foi compilada em personne.dll, conforme explicado anteriormente:
' opções
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
Module test1
Sub Main()
Dim p1 As New personne
p1.initialise("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
Dim p2 As New personne
p2.initialise(p1)
Console.Out.Write("p2=")
p2.identifie()
End Sub
End Module
e seus resultados:
3.1.9. Construtores da classe Pessoa
Um construtor é um procedimento chamado New, que é chamado durante a criação do objeto. Geralmente, ele é usado para inicializá-lo. Se uma classe tiver um construtor que aceite n argumentos argi, a declaração e a inicialização de um objeto dessa classe poderão ser feitas da seguinte forma:
dim objet as classe =new classe(arg1,arg2, ... argn)
ou
dim objet as classe
…
objet=new classe(arg1,arg2, ... argn)
Quando uma classe possui um ou mais construtores, um desses construtores deve ser obrigatoriamente utilizado para criar um objeto dessa classe. Se uma classe C não tiver nenhum construtor, ela possui um construtor padrão, que é o construtor sem parâmetros: public New(). Os atributos do objeto são, então, inicializados com valores padrão. Foi isso que aconteceu nos programas anteriores, nos quais escrevemos:
Vamos criar dois construtores para nossa classe personne:
' opções
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
' a classe Pessoa
Public Class personne
' atributos
Private prenom As String
Private nom As String
Private age As Integer
' construtores
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
initialise(P, N, age)
End Sub
Public Sub New(ByVal P As personne)
initialise(P)
End Sub
' métodos de inicialização do objeto
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
Public Sub initialise(ByVal P As personne)
prenom = P.prenom
nom = P.nom
Me.age = P.age
End Sub
' método
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Nossos dois construtores limitam-se a chamar os métodos initialise correspondentes. Vale lembrar que, quando em um construtor encontramos a notação initialise(P), por exemplo, o compilador traduz como Me.initialise(P). No construtor, o método initialise é, portanto, chamado para trabalhar no objeto referenciado por Me, ou seja, o objeto atual, aquele que está sendo construído. Aqui está um pequeno programa de teste:
' opções
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
' página de teste
Module test
Sub Main()
Dim p1 As New personne("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
Dim p2 As New personne(p1)
Console.Out.Write("p2=")
p2.identifie()
End Sub
End Module
e os resultados obtidos:
3.1.10. Referências de objetos
Sempre utilizamos a mesma classe personne. O programa de teste fica assim:
' opções
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
' página de teste
Module test
Sub Main()
' p1
Dim p1 As New personne("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
' p2 faz referência ao mesmo objeto que p1
Dim p2 As personne = p1
Console.Out.Write("p2=")
p2.identifie()
' p3 faz referência a um objeto que será uma cópia do objeto referenciado por p1
Dim p3 As New personne(p1)
Console.Out.Write("p3=")
p3.identifie()
' altera-se o estado do objeto referenciado por p1
p1.initialise("Micheline", "Benoît", 67)
Console.Out.Write("p1=")
p1.identifie()
' como p2 = p1, o objeto referenciado por p2 deve ter mudado de estado
Console.Out.Write("p2=")
p2.identifie()
' como p3 não aponta para o mesmo objeto que p1, o objeto ao qual p3 aponta não deve ter mudado
Console.Out.Write("p3=")
p3.identifie()
End Sub
End Module
Os resultados obtidos são os seguintes:
p1=Jean,Dupont,30
p2=Jean,Dupont,30
p3=Jean,Dupont,30
p1=Micheline,Benoît,67
p2=Micheline,Benoît,67
p3=Jean,Dupont,30
Ao declarar a variável p1 por meio de
p1 faz referência ao objeto personne("Jean","Dupont",30), mas não é o próprio objeto. Em C, diríamos que se trata de um ponteiro, c.a.d, que aponta para o endereço do objeto criado. Se, em seguida, escrevermos:
Não é o objeto personne("Jean","Dupont",30) que é modificado, e sim a referência p1 que muda de valor. O objeto personne("Jean","Dupont",30) será “perdido” se não for referenciado por nenhuma outra variável.
Quando se escreve:
inicializa-se o ponteiro p2: ele “aponta” para o mesmo objeto (designa o mesmo objeto) que o ponteiro p1. Assim, se modificarmos o objeto “apontado” (ou referenciado) por p1, modificamos aquele referenciado por p2.
Quando se escreve:
ocorre a criação de um novo objeto, que é uma cópia do objeto referenciado por p1. Esse novo objeto será referenciado por p3. Se modificarmos o objeto “apontado” (ou referenciado) por p1, não alteraremos em nada aquele referenciado por p3. É isso que mostram os resultados obtidos.
3.1.11. Os objetos temporários
Em uma expressão, é possível chamar explicitamente o construtor de um objeto: ele é criado, mas não temos acesso a ele (para modificá-lo, por exemplo). Esse objeto temporário é criado para fins de avaliação da expressão e, em seguida, descartado. O espaço de memória que ele ocupava será automaticamente recuperado posteriormente por um programa chamado “garimpo de lixo”, cuja função é recuperar o espaço de memória ocupado por objetos que não são mais referenciados pelos dados do programa. Consideremos o seguinte novo programa de teste:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
' página de teste
Module test
Sub Main()
Dim p As New personne(New personne("Jean", "Dupont", 30))
p.identifie()
End Sub
End Module
e modifiquemos os construtores da classe personne para que exibam uma mensagem:
' construtores
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Console.Out.WriteLine("Constructeur personne(String, String, integer)")
initialise(P, N, age)
End Sub
Public Sub New(ByVal P As personne)
Console.Out.WriteLine("Constructeur personne(personne)")
initialise(P)
End Sub
Obtemos os seguintes resultados:
dos>test
Constructeur personne(String, String, integer)
Constructeur personne(personne)
Jean,Dupont,30
mostrando a construção sucessiva dos dois objetos temporários.
3.1.12. Métodos de leitura e gravação dos atributos privados
Adicionamos à classe personne os métodos necessários para ler ou modificar o estado dos atributos dos objetos:
Imports System
Public Class personne
' atributos
Private prenom As [String]
Private nom As [String]
Private age As Integer
' construtores
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
Public Sub New(ByVal P As personne)
Me.prenom = P.prenom
Me.nom = P.nom
Me.age = P.age
End Sub
' identificadores
Public Sub identifie()
Console.Out.WriteLine((prenom + "," + nom + "," + age))
End Sub
' acessores
Public Function getPrenom() As [String]
Return prenom
End Function
Public Function getNom() As [String]
Return nom
End Function
Public Function getAge() As Integer
Return age
End Function
'modificadores
Public Sub setPrenom(ByVal P As [String])
Me.prenom = P
End Sub
Public Sub setNom(ByVal N As [String])
Me.nom = N
End Sub
Public Sub setAge(ByVal age As Integer)
Me.age = age
End Sub
End Class
Testamos a nova classe com o seguinte programa:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
' página de teste
Public Module test
Sub Main()
Dim P As New personne("Jean", "Michelin", 34)
Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
P.setAge(56)
Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
End Sub
End Module
e obtemos os seguintes resultados:
3.1.13. As propriedades
Existe outra maneira de acessar os atributos de uma classe: criar propriedades. Elas nos permitem manipular atributos privados como se fossem públicos. Consideremos a seguinte classe personne, na qual os acessadores e modificadores anteriores foram substituídos por propriedades de leitura e gravação:
' opções
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
' classe pessoa
Public Class personne
' atributos
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' fabricantes
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Me._prenom = P
Me._nom = N
Me._age = age
End Sub
Public Sub New(ByVal P As personne)
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
End Sub
' identifica
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' propriedades
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' idade válida?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
Uma propriedade (Property) permite ler (get) ou definir (set) o valor de um atributo. Em nosso exemplo, colocamos o prefixo _ nos nomes dos atributos para que as propriedades tenham o mesmo nome dos atributos primitivos. De fato, uma propriedade não pode ter o mesmo nome do atributo que ela gerencia, pois, nesse caso, haveria um conflito de nomes na classe. Portanto, nomeamos nossos atributos como _prenom, _nom, _age e modificamos os construtores e métodos de acordo com isso. Em seguida, criamos três propriedades: nom, prenom e age. Uma propriedade é declarada da seguinte forma:
Public Property nom() As Type
Get
...
End Get
Set(ByVal Value As Type)
...
End Set
End Property
onde Type deve ser o tipo do atributo gerenciado pela propriedade. Ela pode ter dois métodos chamados get e set. O método get geralmente é responsável por retornar o valor do atributo que gerencia (ele poderia retornar outra coisa; nada o impede). O método set recebe um parâmetro chamado value, que normalmente é atribuído ao atributo que ele gerencia. Ele pode aproveitar para verificar a validade do valor recebido e, eventualmente, lançar uma exceção caso o valor seja inválido. É isso que é feito aqui para a idade.
Como esses métodos get e set são chamados? Consideremos o seguinte programa de teste:
' opções
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
' página de teste
Module test
Sub Main()
Dim P As New personne("Jean", "Michelin", 34)
Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
P.age = 56
Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
Try
P.age = -4
Catch ex As Exception
Console.Error.WriteLine(ex.Message)
End Try
End Sub
End Module
Na instrução
buscamos obter os valores das propriedades prenom, nom e age da pessoa P. É o método get dessas propriedades que é chamado e que retorna o valor do atributo que elas gerenciam.
Na instrução
, deseja-se definir o valor da propriedade age. Nesse caso, é o método set dessa propriedade que é chamado. Ele receberá 56 em seu parâmetro **value**.
Uma propriedade P de uma classe C que defina apenas o método get é chamada de “somente leitura”. Se c for um objeto da classe C, a operação c.P=valor será rejeitada pelo compilador.
A execução do programa de teste anterior produz os seguintes resultados:
As propriedades nos permitem, portanto, manipular atributos privados como se fossem públicos.
3.1.14. Métodos e atributos de classe
Suponhamos que queiramos contar o número de objetos [personne] criados em um aplicativo. Podemos gerenciar um contador por conta própria, mas corremos o risco de esquecer os objetos temporários que são criados aqui e ali. Pareceria mais seguro incluir nos construtores da classe [personne] uma instrução que incremente um contador. O problema é passar uma referência desse contador para que o construtor possa incrementá-lo: é preciso passar um novo parâmetro a eles. Também é possível incluir o contador na definição da classe. Como se trata de um atributo da própria classe e não de um objeto específico dessa classe, ele é declarado de maneira diferente com a palavra-chave Shared:
Para referenciá-lo, escreve-se personne._nbPersonnes para indicar que se trata de um atributo da própria classe personne. Aqui, criamos um atributo privado ao qual não teremos acesso direto fora da classe. Portanto, criamos uma propriedade pública para dar acesso ao atributo da classe nbPersonnes. Para definir o valor de nbPersonnes, o método get dessa propriedade não precisa de um objeto personne específico: na verdade, _nbPersonnes não é o atributo de um objeto específico, mas sim o atributo de toda uma classe. Por isso, é necessária uma propriedade também declarada como Shared:
que, externamente, será chamada com a sintaxe personne.nbPersonnes. A propriedade é declarada como somente leitura (ReadOnly), pois não oferece um método set. Veja um exemplo. A classe personne fica da seguinte forma:
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
' classe
Public Class personne
' atributos de classe
Private Shared _nbPersonnes As Long = 0
' atributos de instância
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' construtores
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' mais uma pessoa
_nbPersonnes += 1
Me._prenom = P
Me._nom = N
Me._age = age
End Sub
Public Sub New(ByVal P As personne)
' mais uma pessoa
_nbPersonnes += 1
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
End Sub
' identifica
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' propriedade de classe
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' propriedades de instância
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' idade válida?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
Com o seguinte programa:
' opções
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
' pág. de teste
Module test
Sub Main()
Dim p1 As New personne("Jean", "Dupont", 30)
Dim p2 As New personne(p1)
Console.Out.WriteLine(("Nombre de personnes créées : " & personne.nbPersonnes))
End Sub
End Module
obtêm-se os seguintes resultados:
3.1.15. Passagem de um objeto para uma função
Já mencionamos que, por padrão, o VB.NET passa os parâmetros efetivos de uma função por valor: os valores dos parâmetros efetivos são copiados para os parâmetros formais. No caso de um objeto, não se deve se deixar enganar pelo uso incorreto da linguagem, que ocorre sistematicamente quando se fala de “objeto” em vez de “referência de objeto”. Um objeto só é manipulado por meio de uma referência (um ponteiro) a ele. Portanto, o que é transmitido a uma função não é o próprio objeto, mas uma referência a esse objeto. Assim, é o valor da referência — e não o valor do próprio objeto — que é duplicado no parâmetro formal: não há criação de um novo objeto. Se uma referência de objeto R1 for passada para uma função, ela será copiada para o parâmetro formal correspondente R2. Portanto, as referências R2 e R1 apontam para o mesmo objeto. Se a função modificar o objeto apontado por R2, ela obviamente modificará aquele referenciado por R1, uma vez que se trata do mesmo objeto.
![]() |
É o que mostra o exemplo a seguir:
' opções
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
' página de teste
Module test
Sub Main()
' uma pessoa p1
Dim p1 As New personne("Jean", "Dupont", 30)
' exibição p1
Console.Out.Write("Paramètre effectif avant modification : ")
p1.identifie()
' alteração p1
modifie(p1)
' exibição p1
Console.Out.Write("Paramètre effectif après modification : ")
p1.identifie()
End Sub
Sub modifie(ByVal P As personne)
' exibição de pessoa P
Console.Out.Write("Paramètre formel avant modification : ")
P.identifie()
' alteração P
P.prenom = "Sylvie"
P.nom = "Vartan"
P.age = 52
' exibição de P
Console.Out.Write("Paramètre formel après modification : ")
P.identifie()
End Sub
End Module
Os resultados obtidos são os seguintes:
Paramètre effectif avant modification : Jean,Dupont,30
Paramètre formel avant modification : Jean,Dupont,30
Paramètre formel après modification : Sylvie,Vartan,52
Paramètre effectif après modification : Sylvie,Vartan,52
Percebe-se que houve a criação de apenas um objeto: o da pessoa p1 do procedimento Main e que o objeto foi efetivamente modificado pela função modifie.
3.1.16. Uma matriz de pessoas
Um objeto é um dado como qualquer outro e, por isso, vários objetos podem ser agrupados em uma tabela:
' opções
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
' página de teste
Module test
Sub Main()
' uma tabela de pessoas
Dim amis(2) As personne
amis(0) = New personne("Jean", "Dupont", 30)
amis(1) = New personne("Sylvie", "Vartan", 52)
amis(2) = New personne("Neil", "Armstrong", 66)
' exibição
Console.Out.WriteLine("----------------")
Dim i As Integer
For i = 0 To amis.Length - 1
amis(i).identifie()
Next i
End Sub
End Module
A instrução Dim amigos(2) As pessoa cria uma matriz de 3 elementos do tipo personne. Esses 3 elementos são inicializados aqui com os valores nothing e c.a.d, de modo que não fazem referência a nenhum objeto. Mais uma vez, por conveniência, fala-se em matriz de objetos, embora se trate apenas de uma matriz de referências a objetos. A criação da matriz de objetos, que é ela própria um objeto, não cria nenhum objeto do tipo de seus elementos: isso deve ser feito posteriormente. Obtêm-se os seguintes resultados:
3.2. Herança por exemplo
3.2.1. Noções gerais
Abordamos aqui o conceito de herança. O objetivo da herança é “personalizar” uma classe existente para que ela atenda às nossas necessidades. Suponhamos que queiramos criar uma classe enseignant: um professor é uma pessoa específica. Ele possui atributos que outra pessoa não terá: a disciplina que leciona, por exemplo. Mas também possui os atributos de qualquer pessoa: nome, sobrenome e idade. Um professor, portanto, faz parte integralmente da classe personne, mas possui atributos adicionais. Em vez de criar uma classe enseignant do zero, seria preferível aproveitar o que já existe na classe personne e adaptá-la às características específicas dos professores. É o conceito de herança que nos permite fazer isso. Para expressar que a classe enseignant herda as propriedades da classe personne, escreveremos:
Public Class enseignant
Inherits personne
Observe-se a sintaxe específica em duas linhas. A classe personne é chamada de classe pai (ou mãe) e a classe enseignant, de classe derivada (ou filha). Um objeto enseignant possui todas as características de um objeto personne: ele tem os mesmos atributos e os mesmos métodos. Esses atributos e métodos da classe pai não são repetidos na definição da classe filha: basta indicar os atributos e métodos adicionados pela classe filha. Suponhamos que a classe personne esteja definida da seguinte forma:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
' classe pessoa
Public Class personne
' atributos de classe
Private Shared _nbPersonnes As Long = 0
' atributos de instância
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' construtores
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' mais uma pessoa
_nbPersonnes += 1
' construção
Me._prenom = P
Me._nom = N
Me._age = age
' acompanhamento
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
Public Sub New(ByVal P As personne)
' mais uma pessoa
_nbPersonnes += 1
' construção
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
' acompanhamento
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
' propriedade de classe
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' propriedades de instância
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' idade válida?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
Public ReadOnly Property identite() As String
Get
Return "personne(" & _prenom & "," & _nom & "," & age & ")"
End Get
End Property
End Class
O método identifie foi substituído pela propriedade identité, que é somente de leitura e identifica a pessoa. Criamos uma classe enseignant herdando da classe personne:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Public Class enseignant
Inherits personne
' atributos
Private _section As Integer
' construtor
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' rastreamento
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' propriedade da seção
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
End Class
A classe enseignant adiciona aos métodos e atributos da classe personne:
- um atributo section, que corresponde ao número da seção à qual o professor pertence no corpo docente (basicamente, uma seção por disciplina)
- um novo construtor que permite inicializar todos os atributos de um professor
A declaração
Public Class enseignant
Inherits personne
indica que a classe enseignant deriva da classe personne.
3.2.2. Criação de um objeto professor
O construtor da classe enseignant é o seguinte:
' construtor
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' acompanhamento
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
A declaração
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
declara que o construtor recebe quatro parâmetros: P, N, age e section. Ela deve passar três deles, (P,N,age), para sua classe base, neste caso a classe personne. Sabe-se que essa classe possui um construtor pessoa(string, string, int) que permitirá criar uma pessoa com os parâmetros passados (P,N,age). A classe [enseignant] passa os parâmetros (P, N, idade) para sua classe base da seguinte maneira:
MyBase.New(P, N, age)
Assim que a construção da classe base for concluída, a construção do objeto enseignant prossegue com a execução do corpo do construtor:
Me._section = section
Em resumo, o construtor de uma classe derivada:
- passa para sua classe base os parâmetros de que ela precisa para ser construída
- usa os demais parâmetros para inicializar seus próprios atributos
Também seria possível escrever:
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
Me._prenom = P
Me._nom = N
Me._age = age
Me._section = section
' acompanhamento
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
Isso é impossível. A classe personne declarou como privados (private) seus três campos _prenom, _nom e _age. Apenas objetos da mesma classe têm acesso direto a esses campos. Todos os outros objetos, incluindo objetos derivados como neste caso, devem utilizar métodos públicos para ter acesso a eles. A situação teria sido diferente se a classe personne tivesse declarado os três campos como protegidos (protected): nesse caso, ela permitiria que classes derivadas tivessem acesso direto aos três campos. Em nosso exemplo, usar o construtor da classe pai foi, portanto, a solução correta e é o método habitual: ao construir um objeto filho, primeiro chama-se o construtor do objeto pai e, em seguida, completam-se as inicializações específicas do objeto filho (section em nosso exemplo).
Vamos compilar as classes personne e enseignant em assemblies:
dos>vbc /t:library personne.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>vbc /r:personne.dll /t:library enseignant.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
25/02/2004 10:08 1 828 personne.vb
25/02/2004 10:11 675 enseignant.vb
25/02/2004 10:12 223 test.vb
25/02/2004 10:16 4 096 personne.dll
25/02/2004 10:16 3 584 enseignant.dll
Observe-se que, para compilar a classe filha enseignant, foi necessário referenciar o arquivo personne.dll, que contém a classe personne. Vamos tentar um primeiro programa de teste:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
Este programa se limita a criar um objeto enseignant (new) e a identificá-lo. A classe enseignant não possui o método identité, mas sua classe pai possui um, que, além disso, é público: por herança, ele se torna um método público da classe enseignant. Os resultados obtidos são os seguintes:
dos>vbc /r:personne.dll /r:enseignant.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>test
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Jean,Dupont,30)
Percebe-se que:
- um objeto personne foi criado antes do objeto enseignant
- a identidade obtida é a do objeto personne
3.2.3. Sobrecarga de um método ou de uma propriedade
No exemplo anterior, obtivemos a identidade da parte personne do professor, mas faltam algumas informações específicas da classe enseignant (a seção). Portanto, precisamos definir uma propriedade que permita identificar o professor:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Public Class enseignant
Inherits personne
' atributos
Private _section As Integer
' construtor
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' rastreamento
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' propriedade de seção
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
' sobrecarregamento da propriedade de identidade
Public Shadows ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
End Class
O método identite da classe enseignant baseia-se no método identite de sua classe pai (MyBase.identite) para exibir sua parte “personne” e, em seguida, complementa com o campo _section, que é específico da classe enseignant. Observe a declaração da propriedade identite:
Public Shadows ReadOnly Property identite() As String
que indica que a propriedade “identite” “oculta” o método de mesmo nome que poderia existir na classe pai. Consideremos um objeto enseignant, denotado por E. Esse objeto contém, em seu interior, um objeto personne:
![]() |
A propriedade identity está definida tanto na classe enseignant quanto em sua classe pai, personne. Na classe filha enseignant, a propriedade identite deve ser precedida pela palavra-chave **shadows para indicar que se está redefinindo uma nova propriedade identite para a classe enseignant.
Public Shadows ReadOnly Property identite() As String
A classe enseignant agora possui duas propriedades identite:
- aquela herdada da classe pai personne
- a sua própria
Se E for um objeto enseignant, E.identite designa o método identite da classe enseignant. Diz-se que a propriedade identite da classe pai é “redefinida” pela propriedade identite da classe filha. De modo geral, se O for um objeto e M um método, para executar o método O.M, o sistema procura um método M na seguinte ordem:
- na classe do objeto O
- na sua classe pai, se houver uma
- na classe-mãe da sua classe-mãe, se ela existir
- etc…
A herança permite, portanto, redefinir na classe filha métodos/propriedades com o mesmo nome que existem na classe pai. É isso que permite adaptar a classe filha às suas próprias necessidades. Associada ao polimorfismo, que veremos um pouco mais adiante, a sobrecarga de métodos/propriedades é o principal benefício da herança. Consideremos o mesmo exemplo anterior:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
Os resultados obtidos desta vez são os seguintes:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Jean,Dupont,30),27)
3.2.4. O polimorfismo
Consideremos uma hierarquia de classes: C0 C1 C2 … Cn, onde Ci Cj, indica que a classe Cj é derivada da classe Ci. Isso implica que a classe Cj possui todas as características da classe Ci, além de outras. Sejam Oi objetos do tipo Ci. É válido escrever:
De fato, por herança, a classe Cj possui todas as características da classe Ci, além de outras. Portanto, um objeto Oj do tipo Cj contém em si um objeto do tipo Ci. A operação
faz com que Oi seja uma referência ao objeto do tipo Ci contido no objeto Oj.
O fato de uma variável Oi da classe Ci poder, na verdade, referenciar não apenas um objeto da classe Ci, mas qualquer objeto derivado da classe Ci é chamado de polimorfismo: a capacidade de uma variável referenciar diferentes tipos de objetos. Vejamos um exemplo e consideremos a seguinte função, independente de qualquer classe:
Sub affiche(ByVal p As personne)
Também poderíamos escrever
quanto
Nesse último caso, o parâmetro formal do tipo personne da função affiche receberá um valor do tipo enseignant. Como o tipo enseignant deriva do tipo personne, isso é válido.
3.2.5. Redefinição e polimorfismo
Vamos completar nossa procedimento affiche:
Sub affiche(ByVal p As personne)
' exibe identidade de p
Console.Out.WriteLine(p.identite)
End Sub
O método p.identite retorna uma sequência de caracteres que identifica o objeto personne.. O que ocorre, no caso do nosso exemplo anterior, com um objeto enseignant:
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
Vejamos o exemplo a seguir:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Module test
Sub Main()
' um professor
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' uma pessoa
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' exibe
Sub affiche(ByVal p As personne)
' exibe identidade de p
Console.Out.WriteLine(p.identite)
End Sub
End Module
Os resultados obtidos são os seguintes:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Lucile,Dumas,56)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
A execução mostra que a instrução p.identite executou, em cada ocasião, a propriedade identite de uma personne, a pessoa contida na enseignant e, em seguida, a própria personne. Ela não se adaptou ao objeto realmente passado como parâmetro para a affiche. Seria preferível ter a identidade completa do enseignant. Para isso, seria necessário que a notação p.identite fizesse referência à propriedade identite do objeto efetivamente apontado por p, em vez da propriedade identite da parte “personne” do objeto efetivamente apontado por p. É possível obter esse resultado declarando identite como uma propriedade redefiniável (overridable) na classe base personne:
Public Overridable ReadOnly Property identite() As String
Get
Return "personne(" & _prenom & "," & _nom & "," & age & ")"
End Get
End Property
A palavra-chave overridable** torna identite uma propriedade redefiniável ou virtual. Essa palavra-chave também pode ser aplicada a métodos. As classes derivadas que redefinirem uma propriedade ou método virtual devem, então, utilizar a palavra-chave overrides em vez de shadows para qualificar sua propriedade/método redefinido. Assim, na classe enseignant, a propriedade identite é definida da seguinte forma:
' sobrecarregamento da propriedade de identidade
Public Overrides ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
O programa de teste:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Module test
Sub Main()
' um professor
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' uma pessoa
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' exibe
Sub affiche(ByVal p As personne)
' exibe identidade de p
Console.Out.WriteLine(p.identite)
End Sub
End Module
produz, então, os seguintes resultados:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Lucile,Dumas,56),61)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
Desta vez, conseguimos identificar completamente o professor. Vamos agora redefinir um método, em vez de uma propriedade. A classe object é a classe “pai” de todas as classes VB.NET. Assim, quando escrevemos:
estamos escrevendo implicitamente:
A classe object define um método virtual ToString:

O método ToString retorna o nome da classe à qual o objeto pertence, conforme mostra o exemplo a seguir:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Module test2
Sub Main()
' um professor
Console.Out.WriteLine(New enseignant("Lucile", "Dumas", 56, 61).ToString())
' uma pessoa
Console.Out.WriteLine(New personne("Jean", "Dupont", 30).ToString())
End Sub
End Module
Os resultados gerados são os seguintes:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant
Construction personne(string, string, int)
personne
Observe-se que, embora não tenhamos redefinido o método ToString nas classes personne e enseignant, é possível constatar que o método ToString da classe object ainda é capaz de exibir o nome real da classe do objeto. Vamos redefinir o método ToString nas classes personne e enseignant:
' ToString
Public Overrides Function ToString() As String
' atribui-se a propriedade “identidade”
Return identite
End Function
A definição é a mesma nas duas classes. Consideremos o seguinte programa de teste:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Module test
Sub Main()
' um professor
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' uma pessoa
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' cartaz
Sub affiche(ByVal p As personne)
' exibe identidade de p
Console.Out.WriteLine(p.identite)
End Sub
End Module
Os resultados da execução são os seguintes:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Lucile,Dumas,56),61)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
3.3. Definir um indexador para uma classe
Consideremos a classe [ArrayList] predefinida na plataforma .NET. Essa classe permite armazenar objetos em uma lista. Ela pertence ao espaço de nomes [System.Collections]. No exemplo a seguir, utilizamos essa classe para armazenar uma lista de pessoas (em sentido amplo):
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Imports System.Collections
' classe listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' para adicionar uma pessoa à lista
Public Overloads Sub Add(ByVal p As personne)
MyBase.Add(p)
End Sub
' um indexador
Default Public Shadows Property Item(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
' outro indexador
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' procurando a pessoa chamada N
Dim i As Integer
For i = 0 To Count - 1
If CType(Me(i), personne).nom = N Then
Return i
End If
Next i
Return -1
End Get
End Property
' toString
Public Overrides Function ToString() As String
' retorna (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' percorre-se a matriz dinâmica
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' último elemento
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
Vamos apresentar algumas informações sobre certas propriedades e métodos da classe [ArrayList]:
atributo que indica o número de elementos da lista | |
método que permite adicionar um objeto à lista | |
método que retorna o elemento i da lista |
Observamos que, para obter o elemento nº i da lista, não escrevemos [liste.Item(i)], mas diretamente [liste(i)], o que, à primeira vista, parece estar errado. No entanto, isso é possível porque a classe [ArrayList] define uma propriedade padrão [Item] de acordo com uma sintaxe semelhante à seguinte:
Default Public Property Item(ByVal i As Integer) As Object
Get
...
End Get
Set(ByVal Value As personne)
...
End Set
End Property
Quando o compilador encontra a notação [liste(i)], ele verifica se a classe [ArrayList] definiu uma propriedade com a seguinte assinatura:
Default Public Property Proc(ByVal var As Integer) As Type
Nesse caso, ele encontrará o procedimento [Item]. Em seguida, ele converterá a notação [liste(i)] em [liste.Item(i)]. Chamamos a propriedade [Item] de propriedade indexada por padrão da classe [ArrayList]. A execução do programa anterior produz os seguintes resultados:
dos>vbc /r:personne.dll /r:enseignant.dll lstpersonnes1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:34 3 584 lstpersonnes1.exe
12/03/2004 13:39 661 lstpersonnes1.vb
11/03/2004 18:26 4 096 personne.dll
dos>lstpersonnes1
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(paul,chenou,31)
personne(nicole,chenou,11)
enseignant(personne(jacques,sileau,33),61)
Vamos criar uma classe chamada [listeDePersonnes], que seria uma lista de pessoas, ou seja, uma lista específica que parece natural derivar da classe [ArrayList]:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Imports System.Collections
' classe listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' para adicionar uma pessoa à lista
Public Shadows Sub Add(ByVal p As Object)
' é necessário que p seja uma pessoa
If Not TypeOf (p) Is personne Then
Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
Else
MyBase.Add(p)
End If
End Sub
' um indexador
Default Public Shadows Property Index(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
' toString
Public Overrides Function ToString() As String
' retorna (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' percorre-se a matriz dinâmica
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' último elemento
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
A classe apresenta os seguintes métodos e propriedades:
retorna uma sequência de caracteres que “representa” o conteúdo da lista | |
método que permite adicionar uma pessoa à lista | |
propriedade indexada por padrão que retorna a pessoa i da lista |
Consideremos as novidades:
Foi criado um novo método [Add].
' para adicionar uma pessoa à lista
Public Shadows Sub Add(ByVal p As Object)
' é necessário que p seja uma pessoa
If Not TypeOf (p) Is personne Then
Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
Else
MyBase.Add(p)
End If
End Sub
Já existe um na classe pai [ArrayList] com a mesma assinatura; por isso, utiliza-se a palavra-chave [Shadows] para indicar que o novo procedimento substitui o da classe pai. O método [Add] da classe filha verifica se o objeto adicionado é realmente do tipo [personne] ou derivado com a função [TypeOf]. Caso contrário, é lançada uma exceção com a instrução [Throw]. Se o objeto adicionado for do tipo [personne], o método [Add] da classe base é utilizado para realizar a adição.
É criada uma propriedade indexada:
' um indexador
Default Public Shadows Property Index(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
Já existe uma propriedade padrão chamada [Item] na classe base com a mesma assinatura. Portanto, é necessário usar a palavra-chave [Shadows] para indicar que a nova propriedade indexada [Index] irá “ocultar” a propriedade da classe base [Item]. Observe-se que isso se aplica mesmo que as duas propriedades não tenham o mesmo nome. A propriedade [Index] permite referenciar a pessoa nº i da lista. Ela se baseia na propriedade [Item] da classe base para ter acesso ao elemento nº i do objeto [ArrayList] subjacente. São realizadas alterações de tipo para levar em conta que a propriedade [Item] trabalha com elementos do tipo [Object], enquanto a propriedade [Index] trabalha com elementos do tipo [personne].
Por fim, redefinimos (Overrides) o método [ToString] da classe [ArrayList]:
' toString
Public Overrides Function ToString() As String
' retorna (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' percorre-se a matriz dinâmica
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' último elemento
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
Este método retorna uma sequência de caracteres no formato “(e1, e2, ..., en)”, em que e1, e2, ..., en são os elementos da lista. Observe-se a notação [Me(i)], que designa o elemento nº i do objeto atual [Me]. É utilizada a propriedade indexada por padrão. Assim, [Me(i)] é equivalente a [Me.Index(i)].
O código da classe é colocado no arquivo [lstpersonnes2.vb] e compilado:
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
dos>vbc /r:personne.dll /r:enseignant.dll /t:library lstpersonnes2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:50 3 584 lstpersonnes2.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
É criado um programa de teste:
' opções
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
Imports System.Collections
Module test
Sub Main()
' criação de uma lista vazia de pessoas
Dim liste As listeDePersonnes = New listeDePersonnes
' criação de pessoas
Dim p1 As personne = New personne("paul", "chenou", 31)
Dim p2 As personne = New personne("nicole", "chenou", 11)
Dim e1 As enseignant = New enseignant("jacques", "sileau", 33, 61)
' preenchimento da lista
liste.Add(p1)
liste.Add(p2)
liste.Add(e1)
' exibição da lista
Console.Out.WriteLine(liste.ToString)
' adição de um objeto diferente de pessoa
Try
liste.Add(4)
Catch e As Exception
Console.Error.WriteLine(e.Message)
End Try
End Sub
End Module
Ele é compilado:
dos>vbc /r:personne.dll /r:enseignant.dll /r:lstpersonnes2.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:50 3 584 lstpersonnes2.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
12/03/2004 15:50 3 584 test.exe
12/03/2004 15:49 623 test.vb
e, em seguida, executado:
dos>test
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
([personne(paul,chenou,31)],[personne(nicole,chenou,11)],[enseignant(personne(jacques,sileau,33),61)])
L'objet ajouté (4) n'est pas une personne
Pode-se querer escrever
onde l seria do tipo [listeDePersonnes]. Aqui, queremos indexar a lista l não mais por um número de elemento, mas por um nome de pessoa. Para isso, definimos uma nova propriedade indexada por padrão:
' outro indexador
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' buscando a pessoa com o nome N
Dim i As Integer
For i = 0 To Count - 1
If CType(Me(i), personne).nom = N Then
Return i
End If
Next i
Return -1
End Get
End Property
A primeira linha
Default Public Shadows ReadOnly Property Index(ByVal N As String) As Integer
indica que estamos criando novamente uma propriedade indexada por padrão. Todas as propriedades por padrão devem ter o mesmo nome, neste caso, [Index]. A nova propriedade [Index] indexa a classe listeDePersonnes por uma sequência de caracteres N. O resultado de listeDePersonnes(N) é um inteiro. Esse inteiro representará a posição na lista da pessoa com o nome N ou -1, caso essa pessoa não conste na lista. Define-se apenas a propriedade get, impedindo assim a escrita de listeDePersonnes ("nom")=valeur, que teria exigido a definição da propriedade set). Daí a palavra-chave [ReadOnly]. A palavra-chave [Shadows] é necessária para ocultar a propriedade padrão da classe base (embora ela não tenha a mesma assinatura).
No corpo do get, percorre-se a lista de pessoas em busca do nome N passado como parâmetro. Se ele for encontrado na posição i, retorna-se i; caso contrário, retorna-se -1.
Um novo programa de teste poderia ser o seguinte:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
' página de teste
Module test
Sub Main()
' uma lista de pessoas
Dim l As New listeDePersonnes
' adição de pessoas
l.Add(New personne("jean", "dumornet", 10))
l.Add(New personne("pauline", "duchemin", 12))
' exibição
Console.Out.WriteLine(("l=" + l.ToString))
l.Add(New personne("jacques", "tartifume", 27))
Console.Out.WriteLine(("l=" + l.ToString))
' alteração do elemento 1
l(1) = New personne("sylvie", "cachan", 5)
' exibição do elemento 1
Console.Out.WriteLine(("l[1]=" + l(1).ToString))
' exibição da lista l
Console.Out.WriteLine(("l=" + l.ToString))
' busca de pessoas
Dim noms() As String = New [String]() {"cachan", "inconnu"}
Dim i As Integer
For i = 0 To noms.Length - 1
Dim inom As Integer = l(noms(i))
If inom <> -1 Then
Console.Out.WriteLine(("personne(" & noms(i) & ")=" & l(inom).ToString))
Else
Console.Out.WriteLine(("personne(" + noms(i) + ") n'existe pas"))
End If
Next i
End Sub
End Module
A execução produz os seguintes resultados:
personne(string, string, int)
Construction personne(string, string, int)
l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)])
Construction personne(string, string, int)
l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)],[personne(jacques,tartifume,27)])
Construction personne(string, string, int)
l[1]=personne(sylvie,cachan,5)
l=([personne(jean,dumornet,10)],[personne(sylvie,cachan,5)],[personne(jacques,tartifume,27)])
personne(cachan)=personne(sylvie,cachan,5)
personne(inconnu) n'existe pas
3.4. As estruturas
A estrutura VB.NET deriva diretamente da estrutura da linguagem C e é muito semelhante a uma classe. Uma estrutura é definida da seguinte forma:
Structure spersonne
' atributos
...
' propriedades
...
' construtores
...
' métodos
End Structure
Apesar da semelhança na sintaxe, existem diferenças importantes entre classe e estrutura. O conceito de herança, por exemplo, não existe nas estruturas. Se criarmos uma classe que não deva ser derivada, quais são as diferenças entre estrutura e classe que nos ajudarão a escolher entre as duas? Vamos usar o exemplo a seguir para descobrir:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
' estrutura spersonne
Structure spersonne
Public nom As String
Public age As Integer
End Structure
' classe pessoa
Class cpersonne
Public nom As String
Public age As Integer
End Class
' um módulo de teste
Public Module test
Sub Main()
' uma pessoa p1
Dim sp1 As spersonne
sp1.nom = "paul"
sp1.age = 10
Console.Out.WriteLine(("sp1=spersonne(" & sp1.nom & "," & sp1.age & ")"))
' uma pessoa p2
Dim sp2 As spersonne = sp1
Console.Out.WriteLine(("sp2=spersonne(" & sp2.nom & "," & sp2.age & ")"))
' sp2 foi modificado
sp2.nom = "nicole"
sp2.age = 30
' verificação de sp1 e sp2
Console.Out.WriteLine(("sp1=cpersonne(" & sp1.nom & "," & sp1.age & ")"))
Console.Out.WriteLine(("sp2=cpersonne(" & sp2.nom & "," & sp2.age & ")"))
' uma pessoa cp1
Dim cp1 As New cpersonne
cp1.nom = "paul"
cp1.age = 10
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
' uma pessoa cp2 P2
Dim cp2 As cpersonne = cp1
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
' P2 foi alterado
cp2.nom = "nicole"
cp2.age = 30
' verificação de P1 e P2
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
End Sub
End Module
Ao executar este programa, obtêm-se os seguintes resultados:
sp1=spersonne(paul,10)
sp2=spersonne(paul,10)
sp1=cpersonne(paul,10)
sp2=cpersonne(nicole,30)
cP1=cpersonne(paul,10)
cP2=cpersonne(paul,10)
cP1=cpersonne(nicole,30)
cP2=cpersonne(nicole,30)
Enquanto nas páginas anteriores deste capítulo utilizávamos uma classe personne, agora utilizamos uma estrutura spersonne:
' estrutura da pessoa
Structure spersonne
Public nom As String
Public age As Integer
End Structure
A declaração
Dim sp1 As spersonne
cria uma estrutura (nome, idade) e o valor de sp1 é essa própria estrutura.
A declaração
Dim cp1 As New cpersonne
cria um objeto [cpersonne] (grosso modo, o equivalente à nossa estrutura) e cp1 é, então, o endereço (a referência) desse objeto.
Resumindo
- no caso da estrutura, o valor de sp1 é a própria estrutura
- no caso da classe, o valor de p1 é o endereço do objeto criado
![]() |
![]() |
Quando, no programa, escrevemos
Dim sp2 As spersonne = sp1
uma nova estrutura (nome, idade) é criada e inicializada com o valor de p1, ou seja, a própria estrutura.
![]() |
A estrutura de sp1 é, portanto, duplicada em sp2. Trata-se de uma cópia de valores.
A instrução
Dim cp2 As cpersonne = cp1
funciona de maneira diferente. O valor de cp1 é copiado para cp2, mas, como esse valor é, na verdade, o endereço do objeto, este não é duplicado. Ele simplesmente passa a ter duas referências:
![]() |
No caso da estrutura, se modificarmos o valor de sp2, não modificamos o valor de sp1, como mostra o programa. No caso do objeto, se modificarmos o objeto apontado por cp2, aquele apontado por cp1 será modificado, pois se trata do mesmo objeto. É isso que os resultados do programa também demonstram.
Portanto, podemos concluir dessas explicações que:
- o valor de uma variável do tipo estrutura é a própria estrutura
- o valor de uma variável do tipo objeto é o endereço do objeto ao qual ela aponta
Uma vez compreendida essa diferença fundamental, a estrutura se mostra muito semelhante à classe, como demonstra o novo exemplo a seguir:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
' estrutura de pessoa
Structure personne
' atributos
Private _nom As String
Private _age As Integer
' propriedades
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
_age = value
End Set
End Property
' Construtor
Public Sub New(ByVal NOM As String, ByVal AGE As Integer)
_nom = NOM
_age = AGE
End Sub 'New
' TOSTRING
Public Overrides Function ToString() As String
Return "personne(" & nom & "," & age & ")"
End Function
End Structure
' um módulo de teste
Module test
Sub Main()
' uma pessoa p1
Dim p1 As New personne("paul", 10)
Console.Out.WriteLine(("p1=" & p1.ToString))
' uma pessoa p2
Dim p2 As personne = p1
Console.Out.WriteLine(("p2=" & p2.ToString))
' p2 é modificado
p2.nom = "nicole"
p2.age = 30
' verificação de p1 e p2
Console.Out.WriteLine(("p1=" & p1.ToString))
Console.Out.WriteLine(("p2=" & p2.ToString))
End Sub
End Module
Obtêm-se os seguintes resultados de execução:
A única diferença notável aqui entre estrutura e classe é que, com uma classe, os objetos p1 e p2 teriam o mesmo valor ao final do programa, ou seja, o valor de p2.
3.5. As interfaces
Uma interface é um conjunto de protótipos de métodos ou propriedades que forma um contrato. Uma classe que decide implementar uma interface se compromete a fornecer uma implementação de todos os métodos definidos na interface. É o compilador que verifica essa implementação. Veja, por exemplo, a definição da interface Istats:
Qualquer classe que implemente essa interface será declarada como
public class C
Implements Istats
...
function moyenne() as Double Implements Istats.moyenne
...
end function
function écartType () as Double Implements Istats. écartType
...
end function
end class
Os métodos [moyenne] e [écartType] deverão ser definidos na classe C. Consideremos o código a seguir:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
' estrutura
Public Structure élève
Public _nom As String
Public _note As Double
' construtor
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
' classe notas
Public Class notes
' atributo
Protected _matière As String
Protected _élèves() As élève
' fabricante
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
' registro de alunos e disciplinas
Me._matière = MATIERE
Me._élèves = ELEVES
End Sub
' ToString
Public Overrides Function ToString() As String
Dim valeur As String = "matière=" + _matière + ", notes=("
Dim i As Integer
' concatenamos todas as notas
For i = 0 To (_élèves.Length - 1) - 1
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "],"
Next i
'última nota
If _élèves.Length <> 0 Then
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "]"
End If
valeur += ")"
' fim
Return valeur
End Function
End Class
A turma notes reúne as notas de uma turma em uma disciplina:
Public Class notes
' atributo
Protected _matière As String
Protected _élèves() As élève
Os atributos são declarados como protected para que possam ser acessados por uma classe derivada. O tipo élève é uma estrutura que armazena o nome do aluno e sua nota na disciplina:
Public Structure élève
Public _nom As String
Public _note As Double
' construtor
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
Decidimos derivar essa classe notes em uma classe notesStats, que teria dois atributos adicionais: a média e o desvio-padrão das notas:
Public Class notesStats
Inherits notes
Implements Istats
' atributos
Private _moyenne As Double
Private _écartType As Double
A classe notesStats implementa a seguinte interface Istats:
Isso significa que a classe notesStats deve ter dois métodos chamados moyenne e écartType com a assinatura indicada na interface Istats. A classe notesStats é a seguinte:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Public Class notesStats
Inherits notes
Implements Istats
' atributos
Private _moyenne As Double
Private _écartType As Double
' construtor
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
MyBase.New(MATIERE, ELEVES)
' cálculo da média das notas
Dim somme As Double = 0
Dim i As Integer
For i = 0 To ELEVES.Length - 1
somme += ELEVES(i)._note
Next i
If ELEVES.Length <> 0 Then
_moyenne = somme / ELEVES.Length
Else
_moyenne = -1
End If
' desvio padrão
Dim carrés As Double = 0
For i = 0 To ELEVES.Length - 1
carrés += Math.Pow(ELEVES(i)._note - _moyenne, 2)
Next i
If ELEVES.Length <> 0 Then
_écartType = Math.Sqrt((carrés / ELEVES.Length))
Else
_écartType = -1
End If
End Sub
' ToString
Public Overrides Function ToString() As String
Return MyBase.ToString() & ",moyenne=" & _moyenne & ",écart-type=" & _écartType
End Function 'ToString
' métodos da interface Istats
Public Function moyenne() As Double Implements Istats.moyenne
' retorna a média das notas
Return _moyenne
End Function
Public Function écartType() As Double Implements Istats.écartType
' retorna o desvio-padrão
Return _écartType
End Function
End Class
A média _moyenne e o desvio-padrão _ecartType são calculados assim que o objeto é criado. Portanto, os métodos moyenne e écartType precisam apenas retornar o valor dos atributos _moyenne e _ecartType. Ambos os métodos retornam -1 se a matriz de alunos estiver vazia.
A seguinte classe de teste:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Module test
Sub Main()
' alunos e notas
Dim ELEVES() As élève = {New élève("paul", 14), New élève("nicole", 16), New élève("jacques", 18)}
' que são gravadas em um objeto de notas
Dim anglais As New notes("anglais", ELEVES)
' e que são exibidas
Console.Out.WriteLine((anglais.ToString))
' o mesmo com média e desvio-padrão
anglais = New notesStats("anglais", ELEVES)
Console.Out.WriteLine((anglais.ToString))
End Sub
End Module
retorna os seguintes resultados:
matière=anglais, notes=([paul,14],[nicole,16],[jacques,18])
matière=anglais, notes=([paul,14],[nicole,16],[jacques,18]),moyenne=16,écart-type=1,63299316185545
A classe notesStats poderia muito bem ter implementado os métodos moyenne e écartType por conta própria, sem indicar que implementava a interface Istats. Qual é a utilidade das interfaces? É a seguinte: uma função pode aceitar como parâmetro formal um dado do tipo de uma interface I. Qualquer objeto de uma classe C que implemente a interface I poderá, então, ser um parâmetro efetivo dessa função. Consideremos o seguinte exemplo:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
' uma interface Iexample
Public Interface Iexemple
Function ajouter(ByVal i As Integer, ByVal j As Integer) As Integer
Function soustraire(ByVal i As Integer, ByVal j As Integer) As Integer
End Interface
' uma primeira classe
Public Class classe1
Implements Iexemple
Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
Return a + b + 10
End Function
Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
Return a - b + 20
End Function
End Class
'uma segunda classe
Public Class classe2
Implements Iexemple
Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
Return a + b + 100
End Function
Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
Return a - b + 200
End Function
End Class
A interface Iexemple define dois métodos: ajouter e soustraire. As classes classe1 e classe2 implementam essa interface. Observe-se que essas classes não realizam nenhuma outra ação, a fim de simplificar o exemplo. Agora, consideremos o exemplo a seguir:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
' classe de teste
Module test
'calcular
Sub calculer(ByVal i As Integer, ByVal j As Integer, ByVal inter As Iexemple)
Console.Out.WriteLine(inter.ajouter(i, j))
Console.Out.WriteLine(inter.soustraire(i, j))
End Sub
' a função Main
Sub Main()
' criação de dois objetos classe1 e classe2
Dim c1 As New classe1
Dim c2 As New classe2
' chamadas à função estática calcular
calculer(4, 3, c1)
calculer(14, 13, c2)
End Sub
End Module
A função calculer aceita como parâmetro um elemento do tipo Iexemple. Portanto, ela poderá receber para esse parâmetro tanto um objeto do tipo classe1 quanto do tipo classe2. É isso que é feito no procedimento Main, com os seguintes resultados:
Vemos, portanto, que temos aqui uma propriedade semelhante ao polimorfismo observado nas classes. Se um conjunto de classes Ci não relacionadas entre si por herança (portanto, não é possível utilizar o polimorfismo de herança) apresentar um conjunto de métodos com a mesma assinatura, pode ser interessante agrupar esses métodos em uma interface I da qual todas as classes envolvidas herdariam. Instâncias dessas classes Ci podem, então, ser utilizadas como parâmetros de funções que aceitam um parâmetro do tipo I, c.a.d. Essas funções utilizam apenas os métodos dos objetos Ci definidos na interface I e não os atributos e métodos específicos das diferentes classes Ci. Observe, por fim, que a herança de interfaces pode ser múltipla, c.a.d. pode-se escrever
Public Class classe
Implements I1,I2,...
onde os Ij são interfaces.
3.6. Os espaços de nomes
Para escrever uma linha na tela, usamos a instrução
Se analisarmos a definição da classe Console
Namespace: System
Assembly: Mscorlib (in Mscorlib.dll)
descobrimos que ela faz parte do espaço de nomes System. Isso significa que a classe Console deveria ser designada por System.Console e, na verdade, deveríamos escrever:
Isso é evitado usando uma cláusula imports:
Diz-se que se importa o namespace System com a cláusula imports. Quando o compilador encontrar o nome de uma classe (neste caso, Console), ele procurará por ela nos diferentes espaços de nomes importados pelas cláusulas imports. Nesse caso, ele encontrará a classe Console no espaço de nomes System. Observemos agora a segunda informação associada à classe Console:
Assembly: Mscorlib (in Mscorlib.dll)
Essa linha indica em qual “assembly” se encontra a definição da classe Console. Ao compilar fora do Visual Studio.NET e precisar fornecer as referências das diferentes dll que contêm as classes a serem utilizadas, essa informação pode ser útil. Vale lembrar que, para referenciar os dll necessários à compilação de uma classe, escreve-se:
Ao criar uma classe, é possível criá-la dentro de um espaço de nomes. O objetivo desses espaços de nomes é evitar conflitos de nomes entre classes quando estas são comercializadas, por exemplo. Consideremos duas empresas, E1 e E2, que distribuem classes empacotadas, respectivamente, nos dll, E1.dll e E2.dll. Suponhamos que um cliente C compre esses dois conjuntos de classes, nos quais ambas as empresas definiram uma classe personne. O cliente C compila um programa da seguinte maneira:
Se o código-fonte prog.vb utilizar a classe personne, o compilador não saberá se deve utilizar a classe personne de E1.dll ou a de E2.dll. Ele sinalizará um erro. Se a empresa E1 se certificar de criar suas classes em um espaço de nomes chamado E1 e a empresa E2 em um espaço de nomes chamado E2, as duas classes personne passarão a se chamar E1.personne e E2.personne. O cliente deverá utilizar em suas classes ou E1.personne, ou E2.personne, mas não personne. O espaço de nomes permite eliminar a ambiguidade. Para criar uma classe em um espaço de nomes, escreve-se:
Namespace istia.st
Public Class personne
' definição da classe
...
end Class
end Namespace
A título de exemplo, vamos criar em um namespace nossa classe personne, estudada anteriormente. Escolheremos istia.st como namespace. A classe personne passa a ser:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
' criação do espaço de nomes istia.st
Namespace istia.st
Public Class personne
' atributos
Private prenom As String
Private nom As String
Private age As Integer
' método
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' método
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
End Namespace
Essa classe é compilada em personne.dll:
dos>vbc /t:library personne.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
Agora, vamos utilizar a classe personne em uma classe de teste:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Imports istia.st
' página de teste
Public Module test
Sub Main()
Dim p1 As New personne
p1.initialise("Jean", "Dupont", 30)
p1.identifie()
End Sub
End Module
Para evitar escrever
Dim p1 As New istia.st.personne
importamos o namespace istia.st com uma cláusula imports:
Imports istia.st
Agora vamos compilar o programa de teste:
dos>dir
12/03/2004 18:06 3 584 personne.dll
11/03/2004 18:27 610 personne.vb
12/03/2004 18:05 254 test.vb
dos>vbc /r:personne.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
12/03/2004 18:06 3 584 personne.dll
11/03/2004 18:27 610 personne.vb
12/03/2004 18:08 3 072 test.exe
12/03/2004 18:05 254 test.vb
Isso gera um arquivo test.exe que, quando executado, apresenta os seguintes resultados:
3.7. O exemplo IMPOTS
Retomamos o cálculo do imposto já estudado no capítulo anterior e o processamos utilizando uma classe. Recorde-se o problema:
Consideramos o caso simplificado de um contribuinte que tem apenas seu salário para declarar:
- calcula-se o número de quotas do empregado nbParts = nbEnfants/2 + 1 se ele for solteiro, nbEnfants/2 + 2 se for casado, onde nbEnfants é o número de filhos.
- se tiver pelo menos três filhos, recebe mais meia parcela
- calcula-se sua renda tributável R = 0,72 * S, onde S é seu salário anual
- calcula-se seu coeficiente familiar QF = R / nbParts
- calcula-se seu imposto I. Consideremos a tabela a seguir:
12620,0 | 0 | 0 |
13.190 | 0,05 | 631 |
15.640 | 0,1 | 1.290,5 |
24.740 | 0,15 | 2.072,5 |
31.810 | 0,2 | 3.309,5 |
39.970 | 0,25 | 4.900 |
48.360 | 0,3 | 6898,5 |
55.790 | 0,35 | 9.316,5 |
92.970 | 0,4 | 12.106 |
127.860 | 0,45 | 16.754,5 |
151.250 | 0,50 | 23.147,5 |
172.040 | 0,55 | 30.710 |
195.000 | 0,60 | 39.312 |
0 | 0,65 | 49.062 |
Cada linha possui 3 campos. Para calcular o imposto I, procura-se a primeira linha em que QF ≤ campo1. Por exemplo, se QF = 23000, será encontrada a linha
O imposto I é, então, igual a 0,15*R - 2072,5*nbParts. Se QF for tal que a relação QF <= campo1 nunca for verificada, então serão utilizados os coeficientes da última linha. Aqui:
o que resulta no imposto I = 0,65 * R - 49.062 * nbParts.
A classe de imposto será definida da seguinte forma:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Public Class impot
' dados necessários para o cálculo do imposto
' provêm de uma fonte externa
Private limites(), coeffR(), coeffN() As Decimal
' fabricante
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' verifica-se se as três tabelas têm o mesmo tamanho
Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length
If Not OK Then
Throw New Exception("Les 3 tableaux fournis n'ont pas la même taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
End If
' tudo certo
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' cálculo do imposto
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' cálculo do número de cotas
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' cálculo do rendimento tributável e do quociente familiar
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' cálculo do imposto
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
É criado um objeto “imposto” com os dados necessários para o cálculo do imposto de um contribuinte. Essa é a parte estável do objeto. Uma vez criado esse objeto, é possível chamar repetidamente seu método calcular, que calcula o imposto do contribuinte com base em seu estado civil (casado ou solteiro), no número de filhos e no salário anual. Um programa de teste poderia ser o seguinte:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Imports Microsoft.VisualBasic
Module test
Sub Main()
' programa interativo de cálculo de imposto
' o usuário digita três dados no teclado: casado nbEnfants salário
' o programa exibe então o imposto a pagar
Const syntaxe As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' tabelas de dados necessárias para o cálculo do imposto
Dim limites() As Decimal = {12620D, 13190D, 15640D, 24740D, 31810D, 39970D, 48360D, 55790D, 92970D, 127860D, 151250D, 172040D, 195000D, 0D}
Dim coeffR() As Decimal = {0D, 0.05D, 0.1D, 0.15D, 0.2D, 0.25D, 0.3D, 0.35D, 0.4D, 0.45D, 0.5D, 0.55D, 0.6D, 0.65D}
Dim coeffN() As Decimal = {0D, 631D, 1290.5D, 2072.5D, 3309.5D, 4900D, 6898.5D, 9316.5D, 12106D, 16754.5D, 23147.5D, 30710D, 39312D, 49062D}
' criação de um objeto de imposto
Dim objImpôt As impot = Nothing
Try
objImpôt = New impot(limites, coeffR, coeffN)
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(1)
End Try
' loop infinito
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Long
While True
' solicita-se os parâmetros para o cálculo do imposto
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' o que fazer?
If paramètres Is Nothing OrElse paramètres = "" Then
Exit While
End If
' verificação do número de argumentos na linha inserida
Dim erreur As Boolean = False
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe)
erreur = True
End If
' verificação da validade dos parâmetros
If Not erreur Then
' casado
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
erreur = True
End If
' nbEnfants
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
' salário
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
End If
' se os parâmetros estiverem corretos — calcula-se o imposto
If Not erreur Then
Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire) & " F"))
Else
Console.Error.WriteLine(syntaxe)
End If
End While
End Sub
End Module
Aqui está um exemplo de execução do programa anterior:
dos>vbc /t:library impots.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dir>dir
12/03/2004 18:24 4 096 impots.dll
12/03/2004 18:20 1 483 impots.vb
12/03/2004 18:21 2 805 test.vb
dos>vbc /r:impots.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
12/03/2004 18:24 4 096 impots.dll
12/03/2004 18:20 1 483 impots.vb
12/03/2004 18:26 6 144 test.exe
12/03/2004 18:21 2 805 test.vb
dos>test
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :x x x
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22504 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :





