13 fevereiro, 2008

Mudando Equals com Nullable Type

Quando queremos comparar dois objetos usamos o Equals herdado de Object, mas a comparação é feita comparando-se o tipo do objeto, não o seu conteúdo. Quando queremos comparar os conteúdos para saber se os objetos são iguais temos que sobreescrever o Equals, comparando os conteúdos. Pequeno ex.:

Public Class Item

Private _ItemId As Integer
Private _ReservaId As Integer

Public Property ItemId() As Integer
Get
Return _ItemId
End Get
Set(ByVal value As Integer)
_ItemId = value
End Set
End Property
Public Property ReservaId() As Integer
Get
Return _ReservaId
End Get
Set(ByVal value As Integer)
_ReservaId = value
End Set
End Property

Public Overloads Function Equals(ByVal obj As Object) As Boolean
Dim o As Item = CType(obj, Item)

Return (Not (o Is Nothing) _
AndAlso Me._ItemId = o._ItemId _
AndAlso Me._ReservaId = o._ReservaId _
)

End Class


Mas e se estivermos usando Nullable Types, ou tipos nulos, como mostrado abaixo?

Public Class Item

Private _ItemId As Integer
Private _ReservaId As Nullable (Of Integer)
(...)

Quando tentamos ler a propriedade Value de um tipo nulo e ele é "nulo" é lançada uma exceção, por isso temos que conferir antes se ele tem um valor na propriedade HasValue.
Poderíamos colocar um monte de If e tratar as Exceptions com o bloco Try...Catch, porém a solução mais elegante é continuar usando a lógica Booleana em estado puro, veja abaixo:

Public Class Item
Private _ItemId As Integer
Private _ReservaId As Nullable (Of Integer)
(...)
Public Overloads Function Equals(ByVal obj As Object) As Boolean
Dim o As Item = CType(obj, Item)

Return (Not (o Is Nothing) _
AndAlso Me._ItemId = o._ItemId _
AndAlso (Not Me._ReservaItemId.HasValue Xor o._ReservaItemId.HasValue) OrElse
Me._ReservaItemId.Value = o._ReservaItemId.Value) _
)
End Function
End Class

Quando as duas expressões de entrada do Xor forem iguais a saída será False, quando uma for diferente da outra será True; por isso eu inverto o resultado com Not. O OrElse é o conhecido Or "curto-circuito", no caso ele irá avaliar a primeira expressão sendo ela verdadeira nem olha a segunda, o que aconteceria no caso do Or simples.

Fazendo o Xor eu verifico se os dois tem valores Nulos ou não, se eles tiverem valores nulos eu não posso verificar o valor, na propriedade Value, pois não existe; mas isso não quer dizer que eles não sejam iguais quando os dois são nulos, simplesmente eles são Nulos! :D

O pulo do gato então é sendo os dois Nulos é retornado True com o Xor, e o OrElse não verifica a segunda expressão, ou no caso de um deles ter valor e o outro não o curto-circuito impede de verificar a segunda expressão, nos dois casos não é lançada uma Exception. Boole puro!