Clique aqui para ler todos os artigos desta edição
Serializando objetos com .Net
por Wallace Cézar Sales dos Santos
Um dos grandes desafios do desenvolvimento orientado a objetos é a serialização e desserialização dos objetos. O .Net Framework oferece recursos importantes e facilitadores para a realização dessa tarefa durante o ciclo de vida de um objeto. A serialização é utilizada porque precisamos, basicamente, persistir o estado dos objetos.
Introdução
Serialização é o processo pelo qual convertemos o estado de um objeto num formato em que possamos persisti-lo ou transportá-lo. Desserialização é o processo inverso, permitindo assim, uma facilidade maior em manipular objetos durante espaços de tempo e limites de aplicação, conforme a necessidade identificada e projetada.
Utilizamos serialização de diversas formas nos sistemas: para armazenar um objeto no banco de dados ou em disco, serializar para compartilhar objetos entre várias aplicações ou ainda, transportar objetos através de uma rede, como nas operações de Remoting. O .Net Framework nos fornece nativamente duas técnicas para serialização de objetos: Serialização binária e Serialização por XML e SOAP.
Por que Serializar?
A primeira coisa que precisamos ter em mente é “Por que queremos serializar um objeto?”. As razões podem ser muitas, porém estarão sempre relacionadas com duas operações: A primeira é armazenar o estado de um objeto em uma cópia exata que será recuperada posteriormente; a segunda razão é transportar o estado do objeto entre domínios de aplicações, que podem ser num mesmo computador, numa intranet ou mesmo na Internet, através de Web Services, por exemplo.
No primeiro caso pensamos imediatamente às operações relacionadas com a sigla CRUD e os bancos de dados: Create, Read, Update e Delete. Afinal, o normal é fazermos exatamente isto: desenhamos métodos dos objetos utilizando bancos de dados para armazenar seu estado. Só que esta atividade pode ser muito difícil de realizar a medida em que a complexidade do objeto aumenta e passamos a não ter mais apenas um objeto e sim vários objetos complexos. A tarefa, até então trivial, ganha exponencial complexidade. Notem, não quero de forma alguma passar a idéia de que isto é errado, porém é importante sabermos que temos opções a este árduo trabalho (determinada através dos requerimentos e da correta arquitetura do software), uma vez que a serialização nos fornece um mecanismo bastante interessante e conveniente para alcançarmos este objetivo, com um esforço substancialmente menor.
Para a segunda opção, é importante termos em mente que um objeto somente é válido no domínio da aplicação que o criou. Se este objeto não for serializável, qualquer tentativa de transferi-lo de um domínio para outro resultará em fracasso. No .Net realizamos as operações de transferência de objetos entre domínios de aplicação através do uso de Remoting, que não é o escopo neste artigo.
Serializando um objeto
A serialização no .Net é um processo relativamente simples. Precisamos apenas marcar a classe desejada como Serializável, que pode ser feito através da adição do atributo à classe desejada, como vemos na Listagem 1. Este atributo, quando adicionado à uma classe, indica que suas instâncias poderão ser serializadas através de operações de Reflection. Para a classe em si, o trabalho está finalizado.
Listagem 1: Marcando uma classe como serializável
Public Class Prancha
'Adicione aqui o código da Classe
End Class
A grande questão vem com a necessidade do uso da serialização. Uma vez identificados os requisitos no sistema onde faremos uso desta tecnologia, cabe-nos realizar as tarefas necessárias para implementá-la no código. A tarefa continua simples, mas agora precisamos tomar alguns cuidados e realizar algumas pequenas tarefas para o completo sucesso da operação. A primeira é referenciar em nossa classe os namespaces que permitirão o uso de serialização: System.Runtime.Serialization, que contém as classes utilizadas para as operações de serialização e desserialização de objetos. A segunda é definir qual formatador utilizaremos e fazermos a também a declaração do namespace para uso na classe, que pode ser o System.Runtime.Serialization.Formatters.[Binary]ou[Soap]. O formatador define como será armazenado o estado do objeto: o primeiro caso para serialização binária e o segundo em formato XML/SOAP. Cabe ressaltar que se você desejar utilizar a opção com SOAP, deverá fazer uma referência separada ao Assembly de nome igual antes no projeto (esta classe encontra-se num assembly diferente).
O passo seguinte é criar uma instância do formatador escolhido (uma das opções citadas anteriormente) e uma instância de uma classe derivada da classe System.IO.Stream (FileStream, por exemplo). Esta classe Stream serve para que possamos, neste caso, persistir em disco o estado do objeto. Depois de criadas ambas as intâncias, executamos o método Serialize() do formatador, como podemos verificar na Listagem 2.
Listagem 2: Implementando a serialização
'Criando uma instância do Formatador
Dim pranchaFormatter As IFormatter = New SoapFormatter
'Criando uma instância do Stream
Dim stream As Stream = New FileStream("Prancha.xml", FileMode.Create, FileAccess.Write, FileShare.None)
'Serializando o objeto
pranchaFormatter.Serialize(stream, prancha)
'Fechando o Stream
stream.Close()
Note na Listagem 2 que em nenhum momento definimos o que será ou não serializado. É importante ter em mente que o mecanismo de serialização automaticamente armazena o estado de todas as variáveis de classe (campos), desde que definidos como privados. A Listagem 3 demonstra a implementação completa da Classe Prancha e dela somente será serializado as informações armazenadas nos campos da região “Campos internos”. Porém, chamo a atenção para um detalhe: o campo _tipoPrancha está marcado com o atributo , e isso significa que esta informação não será serializada pelo mecanismo. Isto é necessário pois haverá ocasiões onde não desejaremos que o estado de uma determina propriedade seja armazenado, como por exemplo, a senha de um usuário do sistema.
Listagem 3: a classe Prancha
Public Class Prancha
'Adicione aqui o código da Classe
#Region " Campos internos"
Private _numeroQuilha As NumeroQuilha = NumeroQuilha.TriQuilha
Private _tamanho As Single = 6.0F
Private _tipoRabeta As TipoRabeta = TipoRabeta.Squash
Private _largura As Single = 19.0F
Private _proprietario As String = String.Empty
Private _dataFabricacao As DateTime = DateTime.Now
Private _tipoPrancha As TipoPrancha = TipoPrancha.HotDog
Private _shaper As Shaper = Nothing
#End Region
#Region " Constantes"
Private Const PRANCHA_PEQUENO As String = "O tamanho informado é muito pequeno."
Private Const PRANCHA_ESTREITA As String = "A largura informada é muito estreita."
Private Const PROPRIETARIO_VAZIO As String = "É necessário informar o nome do proprietário da prancha."
Private Const DATA_INVALIDA As String = "Data informada inválida."
#End Region
#Region " Construtores"
Public Sub New()
MyBase.New()
End Sub
Public Sub New(ByVal proprietario As String, ByVal tamanho As String)
Me.New()
With Me
.Proprietario = proprietario
.Tamanho = tamanho
End With
End Sub
#End Region
#Region " Propriedades"
Public Property NumeroQuilha() As NumeroQuilha
Get
Return Me._numeroQuilha
End Get
Set(ByVal Value As NumeroQuilha)
Me._numeroQuilha = Value
End Set
End Property
Public Property Tamanho() As Single
Get
Return Me._tamanho
End Get
Set(ByVal Value As Single)
If (Value < 4.5F) Then Throw New ApplicationException(Me.PRANCHA_PEQUENO)
Me._tamanho = Value
End Set
End Property
Public Property TipoRabeta() As TipoRabeta
Get
Return Me._tipoRabeta
End Get
Set(ByVal Value As TipoRabeta)
Me._tipoRabeta = Value
End Set
End Property
Public Property Largura() As Single
Get
Return Me._largura
End Get
Set(ByVal Value As Single)
If (Value < 18.0F) Then Throw New ApplicationException(Me.PRANCHA_ESTREITA)
Me._largura = Value
End Set
End Property
Public Property Proprietario() As String
Get
Return Me._proprietario
End Get
Set(ByVal Value As String)
If ((Value Is String.Empty) OrElse (Value Is Nothing)) Then Throw New ApplicationException(Me.PROPRIETARIO_VAZIO)
Me._proprietario = Value
End Set
End Property
Public Property DataFabricacao() As DateTime
Get
Return Me._dataFabricacao
End Get
Set(ByVal Value As DateTime)
If ((Value < DateTime.Today) OrElse (Value = Nothing)) Then Throw New ApplicationException(Me.DATA_INVALIDA)
Me._dataFabricacao = Value
End Set
End Property
Public Property TipoPrancha() As TipoPrancha
Get
Return Me._tipoPrancha
End Get
Set(ByVal Value As TipoPrancha)
Me._tipoPrancha = Value
End Set
End Property
Public ReadOnly Property Shaper() As Shaper
Get
If (Me._shaper Is Nothing) Then Me._shaper = New Shaper
Return Me._shaper
End Get
End Property
#End Region
End Class
Uma vez executado o código apresentado na Listagem 2 sobre a classe apresentada na Listagem 3, teremos como resultado o arquivo Prancha.xml, como vemos na Listagem 4. Um detalhe que é importante observar é o fato da serialização ter ocorrido inclusive para os objetos membros – campo _shaper, que é um tipo Shaper, sem que tenhamos qualquer linha de codificação a mais para realizar esta tarefa.
Listagem 4: Arquivo gerado pelo formatador SoapFormatter