.NET  - Usando tipos de dados corretamente


Muitas pessoas que decidem aprender uma linguagem de programação como VB .NET ou C# geralmente não atentam para a importância de compreender os tipos de dados suportados e a forma correta de usá-los. No caso do VB .NET a atenção deveria ser redobrada visto que muitos estão migrando do VB5 ou VB6 para o VB .NET e houve alterações importantes que se não forem consideradas podem dar muita dor de cabeça no futuro.

Na tabela abaixo temos os tipos de dados do VB .NET:

Tipo de dados tipo .NET Runtime tamanho intervalo
Boolean System.Boolean 4 bytes True ou False
Byte System.Byte 1 byte 0 até 255 (sem sinal)
Char System.Char 2 bytes 0 até 65535 (sem sinal)
Date System.DateTime 8 bytes Janeiro 1, 1 CE até Dezembro 31, 9999
Decimal System.Decimal 12 bytes +/-79,228,162,514,264,337,593,543,950,335 sem ponto decimal ; +/-7.9228162514264337593543950335 com 28 casas a direita do decimal; o menor valor diferente de zero é algo em torno de 0.0000000000000000000000000001
Double System.Double 8 bytes -1.79769313486231E308 até -4.94065645841247E-324  para valores negativos; 4.94065645841247E-324 até 1.79769313486232E308 para valores positivos;
Integer System.Int32 4 bytes -2,147,483,648 até 2,147,483,647
Long System.Int64 8 bytes -9,223,372,036,854,775,808 até 9,223,372,036,854,775,807
Object System.Object (class) 4 bytes Qualquer tipo pode ser armazenado.
Short System.Int16 2 bytes -32,768 até 32,767.
Single System.Single 4 bytes -3.402823E38 até -1.401298E-45 para valores negativos; 1.401298E-45 até 3.402823E38 para valores positivos;
String System.String (class) 10 bytes + (2 * tamanho da string) Até aproximadamente dois bilhões de caracteres Unicode.
User-Defined Type (estrutura) (herda de System.ValueType) Soma dos tamanhos dos seus membros Cada membro da estrutura possui um intervalo determinado pelo seu tipo de dados e é independente dos intervalos dos demais números;

Eu vou simular um cenário para mostrar que um pequeno detalhe pode afetar , dependendo do ambiente, o comportamento esperado por um programa.

Vamos supor que um programador trabalhando para uma instituição financeira é encarregado de criar um módulo para tratar o saldo de operações em um determinado tipo de conta onde o cliente que possui um saldo positivo poderá programar amortizações ou retiradas programadas até zerar o saldo.

Assim, se a conta possui um saldo de 100.00 ele poderá efetuar amortizações/retiradas em parcelas mensais; por exemplo 10 parcelas de R$ 10,00 até o saldo final ser igual a zero. Se o saldo for negativo o programa não poderá permitir retirada/amortização. Com isso definido vamos ao código que o programador usou:

Usando o Visual Basic 2008 Express Edition vamos criar um projeto chamado DoubleNet e no formulário form1.vb vamos definir o seguinte leiaute:

Controles usados no formulário:
  • Button - btnSimular - Simular 10 Retiradas
  • TetBox - txtSaldo - Saldo Atual
  • TextBox - txtParcela - Valor Retirada
  • ListBox1 - lstbResultado - exibe o resultado da operação de retirada;

É definido o saldo Atual e o valor da parcela da retirada.

Para simular as retiradas é usado um loop que será executado até que o saldo for maior que zero onde do valor do saldo atual será subtraído o valor da parcela; a cada interação incrementamos um contador para saber o número de retiradas que foi efetuada.

Abaixo temos o trecho de código usado :

While seuSaldo > 0

     seuSaldo -= parcela
     totalRetiradas += 1

End While

Como estamos usando um ListBox para exibir o resultado o código final associado ao evento Click do botão - Simular 10 Retiradas - é o seguinte:

Private Sub btnSimular_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSimular.Click
 

'limpamos o controle ListBox e indicamos que estamos usando um Double

lstbResultado.Items.Clear()

lstbResultado.Items.Add(" ---- Usando Double ----")

 

'obtemos o valor do saldo da parcela e definimos o valor da variável para contar as retiradas como sendo igual a zero

Dim seuSaldo As Double = txtSaldo.Text

Dim totalRetiradas As Integer = 0

Dim parcela As Double = txtParcela.Text

 

'executamo o laço While enquando o saldo for maior que zero

While seuSaldo > 0

    seuSaldo -= parcela

    totalRetiradas += 1
 

     'exibimos o total de retiradas e o saldo no ListBox

    lstbResultado.Items.Add("Retirou " & totalRetiradas & " parcela")

    lstbResultado.Items.Add("Saldo Atual: " & seuSaldo)

End While
 

'Após encerrar o laço exibimos o total de retiradas

lstbResultado.Items.Add("-----------------")

lstbResultado.Items.Add("Vocˆ retirou " & totalRetiradas & " parcelas!")

 

End Sub

Vamos agora verificar se a lógica esta correta testando executando o programa e fazendo alguns testes:

a-) Teste com saldo inicial igual a R$ 100,00 e parcela igual a 10,00:

O resultado confere e parece que tudo esta correto.

Vamos agora testar com um saldo atual igual a 150,50 e parcela de 15,05. Estaremos esperando 10 retiradas de R$ 15,05 , correto ???

Executando o projeto iremos obter:

O resultado foi uma surpresa , não é mesmo ???

Veja que na décima retirada o saldo é um valor muito pequeno mas diferente de zero e assim foi permitido uma nova retirada. Uma catástrofe...

Mas qual o erro estamos cometendo ? Erro de lógica ? Erro de cálculo ? 

Nada disso o erro esta nas seguintes linhas de código : 

Dim seuSaldo As Double = txtSaldo.Text
Dim parcela As Double = txtParcela.Text

Mas Onde ????

O programador definiu as variável como sendo do tipo Double que suporta uma precisão numérica muito grande assim na décima parcela retirada o saldo é um número muito pequeno ( 3,552713678805 elevado a -15) que devido a precisão do tipo de dados usado é levado em conta e computado.

Dessa forma o saldo não é menor que zero permitindo assim uma nova retirada.

Desta forma um pequeno detalhe poderia dar uma grande dor de cabeça.

Um Double possui um tamanho de 8 bytes e atua no intervalo de : -1.79769313486231E308 até -4.94065645841247E-324

Como resolver o problema não permitindo que a diferença seja computada ?

Conselho : No VB .NET para efetuar cálculos com valores monetários devemos usar o tipo Decimal.

No VB6 havia  o tipo Currency que era usado para cálculos financeiros mas no VB .NET ele não existe e foi substituído pelo tipo Decimal.

Se você pretende continuar usando o VB6 a sugestão é não usar o tipos de dados Currency , use o tipo de dados Decimal no VB6 e no VB .NET use o tipo de dados Decimal ou Long

Além desta alteração temos abaixo uma tabela comparando o tipo de dados usado no VB6 e o atualmente usado no VB .NET:

VB6

VB.NET

Comentários

Integer

Short

16 bits

Long

Integer

32 bits

N/A

Long

64 bits

Variant

N/D

Use o novo tipo de dados Object

Currency

N/D

Use o Decimal no VB6 ou Decimal ou Long no VB .NET.

N/D

Decimal

Disponível no VB6(converter de Variant para Decimal com a função cDec.) Nativo no VB.NET

String

String

O VB.NET não suporta strings de tamanho fixo.

Para usar o tipo de dados Decimal no VB6 você pode declarar a variável como Variant e usar a função cDec para convertê-la para Decimal.

Conselho : A regra de ouro é sempre declarar as variáveis entes de usá-las. (Nunca use Option Explicit Off)

A opção Option Strict é nova no VB.NET ; usando a opção Option Strict evitamos os erros em tempo de execução que se originam de conversões automáticas de variáveis.

Assim , na conversão de uma variável do tipo Int32 para o tipo Int16 ocorre um estreitamento  que pode ou não dar certo pois podem existir valores que ao serem convertidos para int16 percam a precisão. Se você trabalhar com a opção Option Strict desativada , o VB vai deixar você tentar , se der certo muito bem se não der ....

Vamos a um exemplo :

O código abaixo trabalha com a opção Option Strict desativada :

Option Strict Off

Private Sub Calculo(NumeroMaior as Int32 , NumeroMenor as Int16)

       Dim troca as int32

       troca = NumeroMaior

       NumeroMenor = troca

End Sub

O VB não vai reclamar e não indicará erro neste código seu projeto vai rodar bem até que você tente fazer a  conversão de um número maior que 32.767 , ai sim vai ocorrer um erro na sua aplicação...

Se ativarmos a opção Option Strict o código fica assim :

Option Strict On

Private Sub Calculo(NumeroMaior as Int32 , NumeroMenor as Int16)

       Dim troca as int32

       troca = NumeroMaior

       NumeroMenor = troca

       if NumeroMaior > NumeroMenor.Value then

            MessageBox " Atenção ! Este número não suporta a conversão "

      else

            NumeroMenor = Ctype(troca, Int16)

      endif

End Sub

End Sub

Com a opção Option Strict ativada , a coisa muda de figura. Durante a fase de projeto o VB.NET irá sublinhar o código e será gerado um erro em tempo de compilação. O VB.NET não permite a você usar o código sem fazer a modificação explícita ( manual ) . Você é avisado em tempo e pode ajustar o seu código.

Conselho : Use Option Explicit e Option Strict sempre ativas !

Tanto o VB.NET como C# são linguagens fortemente tipadas ( como Java), isto significa que o compilador verifica por compatibilidades de tipos em tempo de execução em quase todos os casos , impedindo atribuições incompatíveis , proibindo atribuições questionáveis e fornecendo casts quando a compatibilidade de um tipo puder ser determinada apenas em tempo de execução.

Algumas conversões ocorrem automaticamente, sem você ter que se preocupar. Por padrão no VB.NET o casting é automático quando você atribui objetos a variáveis. Os objetos são então convertidos à força para o tipo da variável. Este comportamento pode ser influenciado pelas declarações:

- Option Strict On - (padrão) - o casting é restrito é não é automático
- Option Strict Off - permite conversões implícitas no seu código

Para realizar um casting explícito (conversão forçada) podemos usar o operador Ctype() ou DirectCast()

Dim Q As Object = 2.37                              ' Requer Option Strict Off.
Dim I As Integer = CType(Q, Integer)           ' Funciona
Dim J As Integer = DirectCast(Q, Integer)   ' Falha
Explicando: O tipo de Q em tempo de execução é Double .
                  Como você pode converter Double para Integer , Ctype funciona.
                  DirectCast Falha porque o tipo de Q em tempo de execução Não é Integer.

Ambas as palavras chaves tomam uma expressão a ser convertida como primeiro argumento e o tipo a converter como segundo argumento.

Se não houver uma conversão definida entre o tipo de dados da expressão e o tipo especificado a ser convertido tanto Ctype como DirectCast não irão funcionar. Tanto Ctype como DirectCast lançam a exceção InvalidCastException.

A função CType opera em dois argumentos. A primeira é a expressão a ser convertido, e o segundo é a classe tipo ou objeto de dados de destino. Observe que o primeiro argumento deve ser uma expressão, não um tipo.  CType é uma função in-line, que significa que o código compilado faz a conversão, com freqüência sem gerar um chamada de função. Isso melhora o desempenho.

Vejamos a seguir as principais funções de conversão no VB .NET:

Função Descrição Exemplo
Cbool  Converte para um Booleano ( False ou True). False ou 0 será definido como False. Retorna um Boolean
Dim A, B, C As Integer
Dim Check As Boolean
A = 5
B = 5
Check = CBool(A = B)   ' Check será True.
' ...
C = 0
Check = CBool(C)   ' Check será False.
Cbyte  Converte para um Byte . Qualquer valor maior que 255 ou valor fracionário será perdido. Retorna um Byte.
Dim MyDouble As Double
Dim MyByte As Byte
MyDouble = 125.5678
MyByte = CByte(MyDouble)   ' MyByte será igual a
CChar  Converte para um Caracter . Qualquer valor maior que 65,535  será perdido e ,  se você tentar converter uma string somente o primeiro caractere será convertido.
Dim MyString As String
Dim MyChar As Char
MyString = "BCD"   'converte só o primeiro caractere
MyChar = CChar(MyString)   ' MyChar será igual a "B".
CDate  Converte para um Date. Aceita qualquer representação de data e tempo. Dim MyDateString, MyTimeString As String
Dim MyDate, MyTime As Date
MyDateString = "February 12, 1969"
MyTimeString = "4:35:47 PM"
' ...
MyDate =
CDate(MyDateString)   ' Converte p/ o tipo Date.
MyTime =
CDate(MyTimeString)
  ' Converte p/ o tipo Date.
CDbl  Converte para um Double. Dim MyDec As Decimal
Dim MyDouble As Double
MyDec = 234.456784D    .
MyDouble =
CDbl(MyDec * 8.2D * 0.01D)   ' Converte para Double
CDec  Converte para um Decimal. Dim MyDouble As Double
Dim MyDecimal As Decimal
MyDouble = 10000000.0587
MyDecimal =
CDec(MyDouble)   ' Converte para Decimal.
CInt  Converte para um inteiro. Valores de -2,147,483,648 até 2,147,483,647 . Frações são arredondadas.
Dim MyDouble As Double
Dim MyInt As Integer
MyDouble = 2345.5678
MyInt = CInt(MyDouble)   ' MyInt será igual a 2346.
CLng  Converte para um Longo. Valores -9,223,372,036,854,775,808 até 9,223,372,036,854,775,807. Frações são arredondadas. Dim MyDbl1, MyDbl2 As Double
Dim MyLong1, MyLong2 As Long
MyDbl1 = 25427.45
MyDbl2 = 25427.55
MyLong1 =
CLng(MyDbl1)   ' MyLong1 conterá 25427.
MyLong2 =
CLng(MyDbl2)
   ' MyLong2 conterá 25428.
CSht  Converte para um Short. Valores de 32,768 a 32,767. Frações são arredondadas. Dim MyByte as Byte
Dim MyShort as Short
MyByte = 100
MyShort = CShort(MyByte)   ' Converte para Short.
CStr  converte para um String. Se for uma Data o retorno será no formato - Short Date.
Dim MyDouble As Double
Dim MyString As String
MyDouble = 437.324
MyString = CStr(MyDouble)   ' MyString será igual a "437.324".
CSng  Converte para um Single . -3.402823E+38 até -1.401298E-45  // 1.401298E-45 até 3.402823E+38
Dim MyDouble1, MyDouble2 As Double
Dim MySingle1, MySingle2 As Single
MyDouble1 = 75.3421105
MyDouble2 = 75.3421567
MySingle1 = CSng(MyDouble1)   ' MySingle1 será igual a 75.34211.
MySingle2 = CSng(MyDouble2)   ' MySingle2 será igual a 75.34216.
CObj  Converte para um objeto.
Dim MyDouble As Double
Dim MyObject As Object
MyDouble = 2.7182818284
MyObject = CObj(MyDouble)  ' MyDouble estará apontada para MyObject.
Ctype  Converte para qualquer tipo de dados. Sintaxe:  Variavel = Ctype( TipoVelho , NovoTipo) Dim MyNumber As Long
Dim MyNewType As Single
MyNumber = 1000
MyNewType = CType(MyNumber,Single)  ' MyNewType é igual a p/1000.0

 

Nota:

  1. Se a expressão submetida a função estiver fora do intervalo do tipo de dados para o qual você quer converter ocorrerá um erro

  2. Usamos estas funções para forçar que o resultado de uma operação seja de um tipo particular diferente do resultado padrão. Assim usamos CDec para forçar para decimal em casos no qual a precisão simples, dupla ou um valor inteiro normalmente iria ocorrer.

  3. Se o valor fracionário submetido for exatamente 0,5 , CInt e CLng irão arredondar para o número par mais próximo. Assim 0,5 será arredondado para 0 e 1,5 será arredondado para 2.

  4. CDate reconhece o formato de datas de acordo com a configuração local do sistema. Você deve informar o dia , mês e ano na ordem correta de acordo com a configuração local.

Agora o último conselho: "Quem avisa amigo é..."

Até o próximo artigo...

Referências:


José Carlos Macoratti