VB .NET - Usando Extensions Methods


Se você chegou a programar usando a linguagem Visual Basic nas versões anteriores a lançamento da plataforma .NET é testemunha da grande mudança e ganho de produtividade na linguagem. A cada nova versão são agregadas novas funcionalidades que tornam a vida do desenvolvedor mais fácil e por consequência levam a um produto final, o software, a ser mais robusto.

e você esta pensando : "Quanto mudança, em tão pouco tempo..." eu concordo plenamente com você pois se acompanharmos do Framework de 2000 até hoje , iremos ver que em 7 anos tivemos 5 versões disponibilizadas:

Ano/Plataforma 2002 2003 2005 2006 2008 2009
Visual Studio VS 2002 VS 2003 VS 2005 VS 2005 + Add In VS 2008 VS 2010
Linguagens Nativas VB 7.0

C# 1.0

VB 7.1

C# 1.1

VB 8.0

C# 2.0

VB 8.0

C# 2.0

VB 9.0

C# 3.0

VB 10
C# 4.0
Versão do Framework 1.0 1.1 2.0 3.0 3.5 4.0
CLR 1.0 1.1 2.0 2.0 2.0 3.0

Nesta última versão a grande novidade ficou por conta do LINQ - Language Integrated Query. E para dar suporte ao LINQ muitos recursos novos são usados. Eu vou falar justamente de um destes recursos.

Hoje eu vou falar sobre uma destas novas funcionalidades introduzidas conhecida como Extension Methods.

A rigor os Extensions Methods são uma implementação do padrão de projeto estrutural Composite e permitem que você adicione uma nova funcionalidade a um tipo de dado que já foi definido sem ter que criar um novo tipo derivado; dessa forma a funcionalidade comporta como um outro membro do tipo.

Através da aplicação dos Extensions Methods você pode, por exemplo, incluir funcionalidades adicionais a uma string sem ter que herdar da classe String, e o novo comportamento é tratado como um novo membro assim com os Extensions Methods é possível escrever um método que pode ser chamado como se ele fosse um método de instância de um tipo existente.

A seguir algumas características dos Extensions Methods:

nota : O LINQ usa o recurso dos Extensions Methods de forma extensiva.

Vejamos a seguir um exemplo de um método de extensão criado no Visual Basic 2008 Express Edition:

Abra o VB 2008 Express e crie um novo projeto do tipo Windows Application com o nome ExtensionsMethodsVB;

A partir do menu Project (Projeto) selecione a opção Add Module

A seguir selecione o template Module e informe o nome Extensions Methods (ou qualquer outro nome sugestivo) e clique no botão adicionar:

Agora vamos criar um método de extensão bem simples para exibir uma string em uma caixa de mensagem definindo o seguinte código no módulo:

- Usamos o  namespace System.Runtime.CompileServices;
- Decoramos o método Imprimir com a declaração <Extension()>;
- O método será aplicado qualquer variável do tipo String;

Vamos usar o método de extensão criado no formulário form1.vb do projeto definindo no formulário uma caixa de texto(txtValor) e um controle Button (btnUsar) conforme o leiaute abaixo:

A seguir defina o código abaixo no evento Click do botão de comando:

Declaramos a string exemplo e atribuimos a ela o valor informado na caixa de texto. Em segaida vamos usar o método criado. 

Note que ao digitarmos o ponto para exibir os membros da variável exemplo do tipo string, além dos métodos da classe String, temos também acesso ao método de extensão Exibir que criamos no modulo do projeto.

Ao executarmos o projeto, informarmos um valor na caixa de texto e clicarmos no botão de comando teremos a string sendo exibida em uma caixa de mensagem conforme abaixo:

Simples , não é mesmo !!!

Você pode definir um método de extensão para a maioria dos tipos que podem ser representados em uma lista de parâmetros no Visual Basic incluindo:

Como o primeiro parâmetro do método especifica o tipo de dados que um método de extensão extende, ele é obrigatório, e,por este motivo parâmetros opcionais (Optional) e parâmetros ParamArray não podem ser usados como primeiro parâmetro na lista de parâmetros.

Embora seja um recurso poderoso você deve levar em consideração os seguintes cenários para melhores práticas ao escrever os seus próprios métodos de extensão:

Outro fator importante que você deve levar em conta é a precedência na execução de métodos de extensão com a mesma assinatura.

Quando dois métodos de extensão que possuem assinaturas idênticas estão acessíveis no mesmo escopo, o método com maior precedência será invocado. A precedência de um método de extensão é baseada no mecanismo usado para trazer o método para o escopo. A lista a seguir mostra a hierarquia de precedência do maior para o menor:

  1. Métodos de Extensão definidos dentro do módulo atual;
  2. Métodos de Extensão definidos dentro de tipos de dadaos no namespace atual ou em qualquer um de seus pais;
  3. Métodos de Extensão definidos dentro de qualquer tipo de imports no arquivo atual;
  4. Métodos de Extensão definidos dentro de qualquer namespace no arquivo atual;
  5. Métodos de Extensão definidos dentro de imports do tipo nivel de projeto

Se a precedência não resolver a ambiguidade, você pode usar a qualificação completa do nome para definir o método que você esta chamando.

Para encerrar vamos criar um novo método de extensão que faça algo de realmente útil como a validação de um campo do tipo String.

Para isso vamos incluir a definição do método de extensão no módulo que já criamos conforme o código abaixo:

Neste código definimos um método de extensão chamado ValidaCampo() usando dois parâmetros:

Observe que definimos o namespace System.Text.RegularExpressions para termos acesso as classes Regex.

A seguir temos uma das formas de usar o método definido acima. No exemplo eu estou usando o evento Validating da caixa de texto txtValor.text definida no formulário form1.vb do projeto:

    Private Sub txtValor_Validating(ByVal sender As System.Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles txtValor.Validating
        Dim campo As String
        campo = txtValor.Text
        If Not campo.ValidaCampo("^([2-9]{3}-)?[2-9]{3}-\d{4}$") Then
            MessageBox.Show("Valor de telefone inválido", "", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End If
    End Sub

A variável campo do tipo string usa o método de extensão ValidaCampo definindo uma máscara para validar um campo do tipo telefone. Este é apenas uma das possibilidades...

O código do exemplo utiliza a expressão regular "^([2-9]{3}-)?[2-9]{3}-\d{4}$". Abaixo temos o significado da expressão:

^ Identifica o inicio da string
[2-9]{3}- Identifica os caracteres de 2 a 9 exatamente 3 vezes , seguido por um -
([2-9]{3}-)? Identifica a expressão "[2-9]{3}-" uma ou zero vezes
[2-9]{3}- Identifica os caracteres de 2 a 9 , extamente 3 vezes, seguido por um -
\d{4} Identifica qualquer digito exatamente 4 vezes
$ Identifica com o fim da string

O resultado é que a string poderá ter os seguintes formatos: 222-2222 ou 222-222-2222.

Note que a classe Regex possui um método estático (shared) que usamos sem ter que criar uma instância da classe Regex.

A seguir temos um outro exemplo de como usar o método de extensão ValidaCampo() no evento TextChanged da caixa de texto:

Private Sub txtValor_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles txtValor.TextChanged
        Dim campo As String
        campo = txtValor.Text
        If Not campo.ValidaCampo("^([2-9]{3}-)?[2-9]{3}-\d{4}$") Then
            txtValor.BackColor = Color.Yellow
        Else
            txtValor.BackColor = Color.White
        End If
    End Sub

O efeito obtido será que ao digitar um valor que não coincida com a expressão regular definida a caixa de texto terá sua cor de fundo alterada para amarelo até que o valor correto seja informado quando isso ocorrer a cor de fundo fica branca novamente.

Vamos agora criar um Extension Method que estende de uma interface: a interface IMath. Neste caso teremos que implementar a interface declarando-a:

Public Interface IMath
End Interface

Temos também que declarar um novo imports no arquivo de módulo:

Imports System.Math

A seguir declare a função calculaExpoente conforme abaixo:

  <Extension()> _
    Public Function calculaExpoente(ByVal im As IMath, ByVal numero As Integer, ByVal expoente As Double)
        Return Math.Pow(numero, expoente)
    End Function

Agora no formulário vamos incluir 3 novas caixas de texto e mais um controle Button conforme o leiaute abaixo:

No evento Click do botão - Calcula Exponenciação - digite o código abaixo para usar o método de extenção criado:

 Private Sub btnCalculaExpoente_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculaExpoente.Click
        Dim num As Integer
        Dim exp As Double
        Dim im As IMath = Nothing

        num = Convert.ToInt32(txtNumero.Text)
        exp = Convert.ToDouble(txtExpoente.Text)

        txtResultado.Text = im.calculoExpoente(num, exp)

    End Sub

O código completo do módulo com todos os métodos de extensão criados é exibido abaixo:

Vamos continuar...

Já pensou em adicionar um método a uma classe já existente sem herdar esta classe ou ter que sobrescrever nenhum método ?

Pois é justamente isso que o recurso do Extensions Methods proporciona: criar métodos de extensão para uma classe já existente.

Este recurso é implementado de forma diferente no VB .NET e no C#. Vamos supor que você deseja incluir um método de extensão na classe String e que este método deverá efetuar a validação do número do CPF.

No mesmo projeto que criamos no início do artigo inclua um novo módulo através do menu Project selecione Add Module com o nome ValidaCPF.

A implementação do método validaCPF como um extension methods no VB .NET pode ser feita criando um modulo e digitando o código conforme abaixo:

Imports System.Runtime.CompilerServices

Public Module ValidaCPF

<Extension()> _

Public Function ValidaCPF(ByVal strCPFCliente As String) As Boolean

'--Declaração das Variáveis
Dim strCPFOriginal As String = strCPFCliente.Replace(".", "").Replace("-", "")
Dim strCPF As String = Mid(strCPFOriginal, 1, 9)
Dim strCPFTemp As String
Dim intSoma As Integer
Dim intResto As Integer
Dim strDigito As String
Dim intMultiplicador As Integer = 10
Const constIntMultiplicador As Integer = 11
Dim i As Integer
'--------------------------

For i = 0 To strCPF.ToString.Length - 1
   intSoma += CInt(strCPF.ToString.Chars(i).ToString) * intMultiplicador
   intMultiplicador -= 1
Next

If (intSoma Mod constIntMultiplicador) < 2 Then
    intResto = 0
Else
    intResto = constIntMultiplicador - (intSoma Mod constIntMultiplicador)
End If

strDigito = intResto
intSoma = 0

strCPFTemp = strCPF & strDigito
intMultiplicador = 11

For i = 0 To strCPFTemp.Length - 1
   intSoma += CInt(strCPFTemp.Chars(i).ToString) * intMultiplicador
   intMultiplicador -= 1
Next

If (intSoma Mod constIntMultiplicador) < 2 Then
   intResto = 0
Else
   intResto = constIntMultiplicador - (intSoma Mod constIntMultiplicador)
End If

strDigito &= intResto

If strDigito = Mid(strCPFOriginal, 10, strCPFOriginal.Length) Then
   Return True
Else
   Return False
End If

End Function

End Module

Com isso acabamos de incluir o método ValidaCPF na classe String. Dúvida ???

Para testar inclua uma caixa de texto e um button o formulário form1.vb e a seguir no evento Click do Button inclua o código abaixo:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

Dim cpf As String = TextBox1.Text.Trim

If cpf.ValidaCPF Then
     MsgBox("CPF válido.")
Else
     MsgBox("CPF inválido.")
End If

End Sub

Acabamos de criar o método ValidaCPF para a classe String (pelo menos no escopo do nosso programa atual) usando Extensions Methods sem ter que recompilar a classe nem que usar herança.

Bem, agora acho que esta na hora de encerrar...

Usado com bom senso os Extensions Methods podem ser uma 'mão na roda' para o desenvolvedor Visual Basic.

Pegue o projeto completo aqui:ExtensionsMethodsVB.zip

Eu sei é apenas VB .NET, mas eu gosto...

referências:


José Carlos Macoratti