Colabora .NET

Transmutación de Código VB6 a Lenguajes .NET

 

Fecha: 06/Ago/2006 (06-08-06)
Autor: Harvey Triana

 


Transmutación de Código VB6 a Lenguajes .NET

 

Manifiesto de una traducción formal de una clase VB6 a clases VB.NET y C#

 

Por Harvey Triana, 8 de Agosto de 2006

 

Introducción

 

Muchas veces me he preguntado si realmente alguien programa VB.NET puro. Sabemos que VB.NET no es una metamorfosis de VB, es una especificación nueva derivada de otro lenguaje, C#. - ¿Es justo que por un puñado de palabras reservadas llamemos Visual Basic a VB.NET?, Siendo realistas, las dos primeras palabras de VB.NET son una mera ­ etiqueta. Pero no es dramático, ni tengo el propósito de polemizar, VB.NET es un buen lenguaje y muy elegante.

 

Percibo que VB.NET puro esta sometido a un segundo plano por C#. El gran grueso de los ejemplos de VB.NET que encuentras están escritos por programadores C#, para programadores C#, y traducidos a VB.NET por un tonto robot. Esto deslegitima aun más a VB.NET como familia de VB. De otra parte, mucho código de VB.NET, en especial el de los programadores emigrantes de VB6, usa el nombre de espacios Microsoft.VisualBasic, y creen erróneamente están produciendo código VB.NET. Esto último es una cuestión a la que naturalmente huyen los programadores puristas de lenguajes .NET.

 

Pero entonces ¿A que deberían aspirar los programadores del Visual Basic Clásico? El objetivo de este articulo es tomar una buena clase de VB6, traducirla a VB.NET puro (sin el nombre de espacios Microsoft.VisualBasic) y luego a C#. Quizás se sorprenda de la similitud de VB.NET con C#, cuando programamos VB.NET puro, y la gran distancia que hay entre VB6 y VB.NET.

 

Mucho se ha escrito de la migración de VB6 a VB.NET no obstante me pregunto si se estará orientando bien a los programadores VB6. Quizás seria preferible olvidarse que existe VB.NET y comenzar con C#.

 

 

El Clásico Utilitario Números a Palabras

 

Imagino que por su utilidad, el problema de traducir un número en sus palabras es tratado desde que existe la programación. La solución del problema es un buen ejercicio para todos los programadores dedicados.

 

En este artículo trato el problema de conversión de números a palabras es su versión en ingles. Escogí la versión en ingles porque el código es más corto y tiene una lógica más simple de seguir en cuanto a propósitos didácticos.

 

Cuando trate este problema, cumplí los siguientes objetivos: (1) Convertir un entero en palabras, (2) Traducir un número real a palabras, y (3) Traducir una cantidad monetaria en palabras. Por ejemplo, el número 123.456 debe producir la siguiente salida:

 

 

Función

Respuesta

Moneda a Palabras

One Hundred Twenty Three Dolars And Forty Six Cents

Real a Palabras

One Hundred Twenty Three Dot Four Hundred Fifty Six

Entero a Palabras

One Hundred Twenty Three

 

 

Básicamente se trata de una sola función la cual convierte un número entero a palabras, las demás son derivadas de la misma con ciertos detalles.

 

Para propósitos del articulo, he creado tres clases, la primera en VB6, la segunda en VB.NET, y la tercera en C#.

 

Solución en VB.NET

 

En principio, yo podría tomar la clase escrita en VB6 (puede consultar su código en el anexo 1), importar la librería Microsoft.VisualBasic, y pegar código. Con unos cuantos ajustes podría hacer funcionar esto en VB.NET. Con esta estrategia voy a producir un código VB.NET sucio, y poco didáctico a la hora de aprovechar el potencial real de VB.NET. Tampoco voy a ser honesto con la eficiencia, ya que voy a colocar una capa de software delante de VB.NET para hacer funcionar mi código. Si, vamos a programar formalmente en un lenguaje .NET deberíamos evitar el nombre de espacios Microsoft.VisualBasic.

 

Usar el nombre de espacios Microsoft.VisualBasic es como si, por alguna razón de logística, un programador VB.NET que nunca ha escrito VB6, tuviera que programar en VB6. El, al desconocer VB6, le parecerían extrañas funciones como Left$(), Right$() o Mid$(). Posiblemente optaría por crea una clase Global Multiuse con un método SubString(), - o si es mas osado crearía una clase StringVBNET, y se ahorraría análisis. No obstante estaría subutilizando VB6 ya que al colocar una capa de software delante de VB6 para que su código funciones, omite las funciones de la especificación VB6, y obliga al modulo de ejecución de VB6 a hacer trabajo extra. Esta analogía es equivalente al programador VB.NET que usa el espacio de nombres Microsoft.VisualBasic para migrar un código de VB6 a VB.NET.

 

El código a continuación es VB.NET puro. Hasta la función IsNumeric fue reemplazada por unas líneas muy eficientes.

 

 

VB.NET: Clase N2W.vb

 

Option Strict On '//required if we desired translate to C#

 

Imports System.Globalization

 

Public Class CN2W

 

Private Const ZERO As String = "Zero"

 

Private IntegerPart As String = ZERO

Private RealPart As String = ZERO

 

Private m_DecimalSeparator As String

Private m_GroupSeparator As String

 

Private aT0() As String = {"One", "Two", "Three", "Four", "Five", "Six", _

                           "Seven",  "Eight", "Nine"}

Private aT1() As String = {"Ten", "Eleven", "Twelve", "Thirteen", "Fourteen", _

                           "Fifteen", "Sixteen", "Seventeen", "Eighteen", _

                           "Nineteen"}

Private aT2() As String = {"Twenty", "Thirty", "Forty", "Fifty", "Sixty", _

                           "Seventy", "Eighty", "Ninety"}

Private aT3() As String = {"", "", " Thousand ", " Million ", " Billion ", _

                           " Trillion ", "", "", "", ""}

 

Public Sub New()

    m_DecimalSeparator = _

               CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator

    m_GroupSeparator = _

               CultureInfo.CurrentCulture.NumberFormat.CurrencyGroupSeparator

End Sub

 

Public Function IntegerToWords(ByVal Number As String) As String

    Call RealToWords(Number)

    Return IntegerPart

End Function

 

Public Function RealToWords(ByVal Number As String) As String

    Dim DotPos As Integer = 0

    IntegerPart = ZERO

    RealPart = ZERO

    If IsNumeric(Number) Then

        Number = Number.Replace(m_GroupSeparator, "")

        DotPos = Number.IndexOf(DecimalSeparator)

        If DotPos >= 0 Then

            IntegerPart = ToWords(Convert.ToInt32( _

                                  Number.Substring(0, DotPos)))

            RealPart = ToWords(Convert.ToInt32(

                       Number.Substring(DotPos + 1, Number.Length - DotPos - 1)))

        Else

            IntegerPart = ToWords(Convert.ToInt32(Number))

        End If

    End If

    Return IntegerPart & " Dot " & RealPart

End Function

 

Public Function CurrencyToWords( _

    ByVal Number As String, _

    ByVal MoneyName As String) As String

   

    Dim s As String = ""

 

    IntegerPart = ZERO

    RealPart = ZERO

 

    If IsNumeric(Number) Then

        ' round to money number

        s = RealToWords(RoundNumberString(Convert.ToDouble(Number), 2))

    End If

    ' add money text

    If IntegerPart = ZERO Then IntegerPart = "No"

    Select Case RealPart

        Case ZERO : RealPart = "And No Cents"

        Case "One" : RealPart = "And One Cent"

        Case Else : RealPart = "And " & RealPart & " Cents"

    End Select

    Return IntegerPart & " " & MoneyName & " " & RealPart

End Function

 

Private Function ToWords(ByVal Number As Integer) As String

    Dim s As String = ""

    Dim d As String = ""

    Dim r As String = ""

    Dim n As Integer = 0

 

    s = Number.ToString

    n = 1

    Do Until s.Length = 0

        ' convert last 3 digits of s to english words

        If s.Length < 3 Then

            d = ConvertHundreds(s)

        Else

            d = ConvertHundreds(s.Substring(s.Length - 3, 3))

        End If

        If d.Length > 0 Then r = d & aT3(n) & r

        If s.Length > 3 Then

            ' remove last 3 converted digits from s.

            s = s.Substring(0, s.Length - 3)

        Else

            s = ""

        End If

        n += 1

    Loop

    If r.Length = 0 Then r = ZERO

    Return r.Trim

End Function

 

Private Function ConvertHundreds(ByVal pNumber As String) As String

    Dim rtn As String = ""

 

    If Not Convert.ToInt32(pNumber) = 0 Then

        ' append leading zeros to number.

        pNumber = ("000" & pNumber).Substring(pNumber.Length, 3)

        ' do we have a hundreds place digit to convert?

        If Not pNumber.Substring(0, 1) = "0" Then

            rtn = ConvertDigit(pNumber.Substring(0, 1)) & " Hundred "

        End If

        ' do we have a tens place digit to convert?

        If pNumber.Length >= 2 Then

            If Not pNumber.Substring(1, 1) = "0" Then

                rtn &= ConvertTens(pNumber.Substring(1))

            Else

                rtn &= ConvertDigit(pNumber.Substring(2))

            End If

        End If

        Return rtn.Trim

    Else

        Return ""

    End If

End Function

 

Private Function ConvertTens(ByVal pTens As String) As String

    Dim r As String = ""

    ' is value between 10 and 19?

    If Convert.ToInt32(pTens.Substring(0, 1)) = 1 Then

        r = aT1(Convert.ToInt32(pTens) - 10)

    Else

        ' otherwise it's between 20 and 99.

        r = aT2(Convert.ToInt32(pTens.Substring(0, 1)) - 2) & " "

        ' convert ones place digit

        r &= ConvertDigit(pTens.Substring(pTens.Length - 1, 1))

    End If

    Return r

End Function

 

Private Function ConvertDigit(ByVal pNumber As String) As String

    If pNumber = "0" Then

        Return ""

    Else

        Return aT0(Convert.ToInt32(pNumber) - 1)

    End If

End Function

 

Private Function RoundNumberString( _

    ByVal Number As Double, _

    ByVal Decimals As Integer) As String

   

    Dim s As String

    Dim r As Double

 

    ' round first

    r = 10 ^ Decimals

    ' Math.Floor make sure the float round. Suggested by Guillermo Som

    r = CType(Math.Floor(Number * r + 0.5), Integer) / r

    ' complete with zeros

    s = r.ToString

    If s.IndexOf(".") < 0 Then s &= "."

    Do While s.Substring(s.IndexOf(".")).Length <= Decimals

        s &= "0"

    Loop

    Return s

End Function

 

'/ Source: http://aspalliance.com/articleViewer.aspx?aId=80&pId=

'/ Traslate and review to VB.NET by Harvey Triana

Private Function IsNumeric(ByVal s As String) As Boolean

    Dim HasDecimal As Boolean = False

    Dim i As Integer = 0

    Dim r As Boolean = False

    Dim ds As Char = Convert.ToChar(DecimalSeparator)

    Dim gs As Char = Convert.ToChar(GroupSeparator)

 

    Do While i < s.Length

        ' Check for decimal

        If s(i) = ds Then

            If HasDecimal Then

                r = False

            Else ' 1st decimal

                ' inform loop decimal found and continue

                HasDecimal = True

                i += 1

                Continue Do

            End If

        End If

        ' check if number

        If Char.IsNumber(s(i)) Or (s(i) = gs) Then

            r = True

        Else

            r = False

            Exit Do

        End If

        i += 1

    Loop

    Return r

End Function

 

Public ReadOnly Property GroupSeparator() As String

    Get

        Return m_GroupSeparator

    End Get

End Property

 

Public ReadOnly Property DecimalSeparator() As String

    Get

        Return m_DecimalSeparator

    End Get

End Property

 

End Class

 

 

Traducción Eficiente VB6 a VB.NET

 

El siguiente análisis de la traducción formal de VB6 a VB.NET pone de manifiesto detalles importantes que se deberían seguir para una traducción eficiente.

 

1. No Emplear el Espacio de Nombres Microsoft.VisualBasic

 

Omitir el espacio de nombres Microsoft.VisualBasic por supuesto hace bastante mas difícil la migración del código, pero pone de presente que vamos a producir un código .NET puro. El espacio de nombres Microsoft.VisualBasic se agrega de manera predeterminada a proyectos VB.NET, para omitirlo puede entrar a propiedades, ubicar la ficha Referencias, y desactivar la casilla correspondiente  a Microsoft.VisualBasic.

 

2. Usar  Option Strict On

 

Esto es particularmente importante si se desea producir código muy resistente y evita “late bindig” que no percibimos. De otra parte, facilita el camino para migrar el código VB.NET a C# si se quisiera. Al obligar a usar conversión formal de tipos, estamos cumpliendo una regla formal de C#. La ausencia de esta directiva tiende a producir un código .NET no puro.

 

3. Usar Métodos de Framework Siempre que sea Posible

 

Al omitir el espacio de nombres Microsoft.VisualBasic, las funciones de cadena clásicas de VB6 como Mid$, Left$, y Rigth$, InStr, y otras, deben ser reemplazadas por los métodos que suministra el Framework. En general SubString reemplaza a Mid$, Left$, y Right$, e IndexOf a InStr. No obstante debe tenerse precaución ya que SubString por si solo no produce todo el funcionamiento de estas funciones. Si deseemos podemos escribir dichas funciones en un lenguaje .NET para su uso. Si esta interesado en la cuestión puede ver http://200.118.112.230/DV/articles/BasicStrings_DotNET.htm

 

4. Asignación de Valor de Variables en su Declaración

 

VB6 no soporta asignación de valor de variables en su declaración. Es conveniente ahorrar líneas de código en VB.NET y escribir las líneas pertinentes.

 

5. Reemplazo de Arreglos Variant

 

La declaración explicita de los arreglos es más intuitiva y elegante en VB.NET, además, podemos usar el tipo pertinente, lo que produce un código de mejor desempeño. Por supuesto, recordar que .NET inicia los arreglos con índice cero, por favor, no recurrir a artificios .NET para producir arreglos que no inicien en cero, la potencia de .NET es opacada. Siempre hay forma de reescribir las formulas que manipulan índices para ajustarse a la norma de .NET.

 

6. Usar Métodos de Operador

 

Ya que VB.NET soporta operadores como “+=”, “&=”, conviene usarlos. El desempeño es mejor que usar la sintaxis convencional de VB6.

 

7. Usar Herencia Si es Posible

 

Este punto da para un articulo de varias páginas. En realidad desde una aplicación normal de VB6 es difícil percibir en que nos beneficiaria la herencia formal. Sin embargo para aplicaciones grandes la necesidad se puede vislumbrar con facilidad. Otra cuestión que viene a colación es la herencia que produce Implements de VB6 contra la herencia formal de lenguajes .NET, o la herencia que produce Implements de lenguajes .NET. Cuando una aplicación robusta de VB6 que ha usado Implements masivamente, es preferible no migrarla a lenguajes .NET. Quizás sea preferible analizar la arquitectura desde un punto de vista .NET y hacer todo de nuevo si el tiempo lo permite. Es mi caso con algunas de mis aplicaciones.

 

8. Operaciones con Variables de Cadena

 

Sabemos que las variables de cadena en .NET son inmutables, es decir se crea un nuevo espacio de memoria en cada asignación de una variable tipo String. A raíz de esto, el Framework suministra la clase StringBuilder para hacer eficiente operaciones con cadenas. Estuve discutiendo el asunto con varios expertos en los foros de C# de Microsoft (Jon Skeet, Greg Young, Ignacio Machin). La conclusión es que  si las operaciones con cadena requieren varios ciclos de computación, típico en un bucle largo, es imperioso usar StringBuilder. Si las iteraciones son de un numero discreto no muy grande, es permitido el uso directo de String en operaciones de cadena, sin mucha perdida de desempeño.  En el ejemplo del articulo las operaciones con cadena rara vez superan las ocho iteraciones, por lo que es eficiente mantener el código si recurrir a StringBuilder.

 

9. Una función IsNumeric Eficiente

 

Los lenguajes .NET en su especificación no incorporan IsNumeric, pero hay varias formas de reproducir la función. El articulo de Ambrose Little (ver referencias) polemiza el asunto y suministra la función con mejor desempeño, la cual es la que uso en este articulo, traducida desde C# a VB.NET. Recomendada.

 

 

Para terminar esta discusión, agrego que posiblemente a Usted se le ocurran otras reglas de traducción VB6 a VB.NET. No obtente he descrito las que a mi parecer son el principio.

 

 

Probando la Clase CN2W de VB.NET

 

La clase CN2W puede usarse en cualquier contexto .NET. A continuación muestro una ejemplo extraído de  una aplicación de Windows con VB.NET. Supongo que con los nombres es suficiente para intuir su uso.

 

Ejemplo de uso de la clase N2W de VB.NET

 

Private Sub btnCurrencyToWords_Click( _

    ByVal sender As System.Object, _

    ByVal e As System.EventArgs) Handles btnCurrencyToWords.Click

   

    Dim n2w As CN2W = New CN2W()

    Me.txtOutput.Text = n2w.CurrencyToWords(Me.txtNumber.Text, "Dolars")

End Sub

 

En la descarga al final de articulo encuentra la aplicación N2W_VB completa.

 

 

Solución en C#

 

Consejo comanche. Es posible traducir una aplicación VB6 a C#, pero es varias veces más dificil que migrar primero a VB.NET y luego a C#. Los lenguages .NET tiene su asidero en C#, y este deberia ser la opcion para comenzar una aplicación .NET desde cero. Yo habia abandonado C en 1995 cuando descubri Visual Basic, vaya, uno hacia las cosas en menos de la tercera parte del tiempo. No obstante la programacion por punteros se olvida si no se practica, ademas es horrorosa. Asi pues, me case con Visual Basic. Ahora, VS2005 nos trae un C que se depura como Visual Basic y el Intellisence es potente ¿Por qué no usarlo?. Ademas, este maldito C# es muy bonito.

 

El codigo del problema expuesto en este articulo en C# es el siguiente.

 

 

Clase N2W en C#

using System;

using System.Globalization;

 

public class CN2W

{

    const string ZERO = "Zero";

 

    string IntegerPart = ZERO;

    string RealPart = ZERO;

 

    string _DecimalSeparator;

    string _GroupSeparator;

 

    string[] aT0 = new string[] {"One", "Two", "Three", "Four", "Five", "Six",

                                 "Seven", "Eight", "Nine"};

    string[] aT1 = new string[] {"Ten", "Eleven", "Twelve", "Thirteen",

                                 "Fourteen", "Fifteen", "Sixteen", "Seventeen",

                                 "Eighteen", "Nineteen"};

    string[] aT2 = new string[] {"Twenty", "Thirty", "Forty", "Fifty", "Sixty",

                                 "Seventy", "Eighty", "Ninety" };

    string[] aT3 = new string[] {"", "", " Thousand ", " Million ", " Billion ",

                                 "Trillion ", "", "", "", "" };

 

    public CN2W()

    {

        _DecimalSeparator =

              CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator;

        _GroupSeparator =

              CultureInfo.CurrentCulture.NumberFormat.CurrencyGroupSeparator;

    }

 

    public string IntegerToWords(string Number)

    {

        RealToWords(Number);

        return IntegerPart;

    }

 

    public string RealToWords(string Number)

    {

        int DotPos = 0;

 

        IntegerPart = ZERO;

        RealPart = ZERO;

 

        if (IsNumeric(Number))

        {

            Number = Number.Replace(_GroupSeparator, "");

            DotPos = Number.IndexOf(_DecimalSeparator);

            if (DotPos >= 0)

            {

                IntegerPart = ToWords(Convert.ToInt32(

                                      Number.Substring(0, DotPos)));

                RealPart = ToWords(Convert.ToInt32(

                      Number.Substring(DotPos + 1, Number.Length - DotPos - 1)));

            }

            else

            {

                IntegerPart = ToWords(Convert.ToInt32(Number));

            }

        }

        return IntegerPart + " Dot " + RealPart;

    }

 

    public string CurrencyToWords(string Number, string MoneyName)

    {

        string s;

 

        IntegerPart = ZERO;

        RealPart = ZERO;

 

        if(IsNumeric(Number))

        {

            // round to money number

            s = RealToWords(RoundNumberString(Convert.ToDouble(Number), 2));

        }

        // add money text

        if (IntegerPart == ZERO) {IntegerPart = "No";}

        switch(RealPart)

        {

            case ZERO:

                RealPart = "And No Cents";

                break;

            case "One":

                RealPart = "And One Cent";

                break;

            default:

                RealPart = "And " + RealPart + " Cents";

                break;

        }

        return IntegerPart + " " + MoneyName + " " + RealPart;

    }

 

    private string ToWords(int Number)

    {

        string s = "";

        string d = "";

        string r = "";

        int n;

 

        s = Number.ToString();

        n = 1;

        while (s.Length != 0)

        {

            // convert last 3 digits of s to English r.

            if (s.Length < 3) { d = ConvertHundreds(s); }

            else { d = ConvertHundreds(s.Substring(s.Length - 3, 3)); }

            if (d.Length > 0) { r = d + aT3[n] + r; }

            if (s.Length > 3) { s = s.Substring(0, s.Length - 3); }

            else { s = ""; }

            n++;

        }

        if (r.Length == 0) { r = ZERO; }

        return r;

    }

 

    private string ConvertHundreds(string pNumber)

    {

        string rtn = "";

 

        if (!(Convert.ToInt32(pNumber) == 0))

        {

            // append leading zeros to number

            pNumber = ("000" + pNumber).Substring(pNumber.Length, 3);

            // do we have a hundreds place digit to convert?

            if (!(pNumber.Substring(0, 1) == "0"))

            {

                rtn = ConvertDigit(pNumber.Substring(0, 1)) + " Hundred ";

            }

            // do we have a tens place digit to convert?

            if (pNumber.Length >= 2)

            {

                if (!(pNumber.Substring(1, 1) == "0"))

                {

                    rtn += ConvertTens(pNumber.Substring(1));

                }

                else

                {

                    rtn += ConvertDigit(pNumber.Substring(2));

                }

            }

            return rtn.Trim();

        }

        else { return ""; }

    }

 

    private string ConvertTens(string pTens)

    {

        string r = "";

        // is value between 10 and 19?

        if ((Convert.ToInt32(pTens.Substring(0, 1)) == 1))

        {

            r = aT1[Convert.ToInt32(pTens) - 10];

        }

        else

        {

            // otherwise it's between 20 and 99.

            r = aT2[Convert.ToInt32(pTens.Substring(0, 1)) - 2] + " ";

            // convert ones place digit

            r += ConvertDigit(pTens.Substring((pTens.Length - 1), 1));

        }

        return r;

    }

 

    private string ConvertDigit(string pNumber)

    {

        if (pNumber == "0") { return ""; }

        else { return aT0[Convert.ToInt32(pNumber) - 1]; }

    }

 

    private string RoundNumberString(double Number, int Decimals)

    {

        string s;

        double r;

        // round first

        r = Math.Pow(10d, Decimals);

        // Math.Floor make sure the float round. Suggested by Guillermo Som

        r = (int)(Math.Floor(Number * r + 0.5d)) / r;

        // complete with zeros

        s = r.ToString();

        if ((s.IndexOf(_DecimalSeparator) < 0))

        {

            s += _DecimalSeparator;

        }

        while ((s.Substring(s.IndexOf(_DecimalSeparator)).Length <= Decimals))

        {

            s += "0";

        }

        return s;

    }

 

    // Source: http://aspalliance.com/articleViewer.aspx?aId=80&pId=

    // Review by Harvey Triana

    private bool IsNumeric(string s)

    {

        bool hasDecimal = false;

        bool r = false;

        char ds = Convert.ToChar(_DecimalSeparator);

        char gs = Convert.ToChar(_GroupSeparator);

 

        for (int i = 0; i < s.Length; i++)

        {

            // check for decimal

            if (s[i] == ds)

            {

                if (hasDecimal) // 2nd decimal

                    r = false;

                else // 1st decimal

                {

                    // inform loop decimal found and continue

                    hasDecimal = true;

                    continue;

                }

            }

            // check if number

            if (char.IsNumber(s[i]) || (s[i] == gs))

                r = true;

            else

            {

                r = false;

                break;

            }

        }

        return r;

    }

 

    public string GroupSeparator

    {

        get { return _GroupSeparator; }

    }

    public string DecimalSeparator

    {

        get { return _DecimalSeparator; }

    }

}

 

Si comparacion del Código VB.NET contra el código no hay mucho que decir. El codigo C# es como un dibujo calcado del codigo VB.NET puro, o al contrario si lo prefiere. C# produce un código horizontal más corto, y uno vertical más largo (que es diferente a menor número de líneas de código).

 

Aunque si hay un detalle simpatico que se puede mencionar. El operador “^” de VB.NET no tiene equivalente en C#, es por eso que usamos Math.Pow(). - Supongo que se conservo el operador “^” en VB.NET porque ya seria el colmo del olvido con los emigrantes de VB6.

 

Probando la Clase CN2W de C#

 

La clase CN2W puede usarse en cualquier contexto .NET. A continuación muestro una ejemplo extraído de  una aplicación de Windows con C#. Supongo que con los nombres es suficiente para intuir su uso.

 

Ejemplo de uso de la clase N2W de C#

private void btnCurrencyToWords_Click(object sender, EventArgs e)

{

    CN2W n2w = new CN2W();

    this.txtOutput.Text = n2w.CurrencyToWords(this.txtNumber.Text, "Dolars");

}

 

En la descarga al final de articulo encuentra la aplicación N2W_VB completa.

 

 

Conclusiones

 

Si vamos a traducir una aplicación VB6 a VB.NET no deberíamos usar el espacio de nombres Microsoft.VisualBasic. También, deberíamos pegarnos a Option Strict On, ya que va a producir un código con mejor fidelidad y eficiencia.

 

Escribir VB.NET puro es equivalente a escribir C#, la diferencia radica en muy poco. Cuando escribes VB.NET puro realmente estas usando .NET en su verdadero potencial.  

 

Por favor, VB.NET no es la versión moderna de Microsoft Visual Basic©, salvo por un asunto publicitario.

 

 

Nota

 

Se hizo una corrección el 6 de Agosto de 2006 para dar soporte al sistema regional Europeo.

 

 

 

Referencias

 

1. Which IsNumeric method should you use? Check this article out to find out!, J. Ambrose Little

http://aspalliance.com/articleViewer.aspx?aId=80&pId=

 

2. Difference between (int), Int32.Parse and Convert.ToInt32

http://blogs.msdn.com/ianhu/archive/2005/12/19/505702.aspx

 

3. el Guille

http://www.elguille.info/

 

Código del Articulo:

http://200.118.112.230/DV/articles/TRANSMUTACION_VB6_DotNET.zip

 

 

 

Anexo 1. Solución VB6

 

La clase origen CN2W es a siguiente. Un código VB6 que trabaja a cabalidad, y es eficiente.

 

VB6: Clase CN2W.cls

' ----------------------------------

' Class CN2W_VB6

' By Harvey Triana

' ----------------------------------

 

Option Explicit

 

Private Const ZERO As String = "Zero"

 

Private IntegerPart      As String

Private RealPart         As String

Private m_DecimalSeparator As String

Private m_GroupSeparator   As String

 

Private aT0, aT1, aT2, aT3

 

Public Function IntegerToWords(ByVal Number As String) As String

    Call RealToWords(Number)

    IntegerToWords = IntegerPart

End Function

 

Public Function RealToWords(ByVal Number As String) As String

    Dim DotPos As Long

 

    IntegerPart = ZERO

    RealPart = ZERO

 

    If IsNumeric(Number) Then

        DotPos = InStr(Number, m_DecimalSeparator)

        Number = Replace$(Number, m_GroupSeparator, "")

        If DotPos Then

            IntegerPart = ToWords(Int(Left$(Number, DotPos - 1)))

            RealPart = ToWords(Int(Mid$(Number, DotPos + 1)))

        Else

            IntegerPart = ToWords(Int(Number))

        End If

    End If

   

    RealToWords = IntegerPart & " Dot " & RealPart

End Function

 

Public Function CurrencyToWords( _

    ByVal Number As String, ByVal MoneyName As String) As String

    Dim s As String

 

    IntegerPart = ZERO

    RealPart = ZERO

 

    If IsNumeric(Number) Then

        ' round to money number

        s = RealToWords(RoundNumberString(CDbl(Number), 2))

    End If

    ' add money text

    If IntegerPart = ZERO Then IntegerPart = "No"

    Select Case RealPart

        Case ZERO: RealPart = "And No Cents"

        Case "One": RealPart = "And One Cent"

        Case Else: RealPart = "And " & RealPart & " Cents"

    End Select

    CurrencyToWords = IntegerPart & " " & MoneyName & " " & RealPart

End Function

 

Private Function ToWords(ByVal Number As Long) As String

    Dim s As String

    Dim d As String

    Dim r As String

    Dim Count As Long

   

    s = CStr(Number)

    Count = 1

    Do Until Len(s) = 0

        ' convert last 3 digits of s to english words

        If Len(s) < 3 Then

            d = ConvertHundreds(s)

        Else

            d = ConvertHundreds(Right$(s, 3))

        End If

        If Len(d) > 0 Then r = d & aT3(Count) & r

        If Len(s) > 3 Then

            ' remove last 3 converted digits from s.

            s = Left$(s, Len(s) - 3)

        Else

            s = ""

        End If

        Count = Count + 1

    Loop

    If Len(r) = 0 Then r = ZERO

    ToWords = Trim$(r)

End Function

 

Private Function ConvertHundreds(ByVal pNumber As String) As String

    Dim r As String

 

    If Not Int(pNumber) = 0 Then

        ' append leading zeros to number.

        pNumber = Right$("000" & pNumber, 3)

        ' do we have a hundreds place digit to convert?

        If Not Left$(pNumber, 1) = "0" Then

            r = ConvertDigit(Left$(pNumber, 1)) & " Hundred "

        End If

        ' do we have a tens place digit to convert?

        If Len(pNumber) >= 2 Then

            If Not Mid$(pNumber, 2, 1) = "0" Then

                r = r & ConvertTens(Mid$(pNumber, 2))

            Else

                r = r & ConvertDigit(Mid$(pNumber, 3))

            End If

        End If

        ConvertHundreds = Trim(r)

    End If

End Function

 

Private Function ConvertTens(ByVal pTens As String) As String

    Dim r As String

    ' is value between 10 and 19?

    If Int(Left$(pTens, 1)) = 1 Then

       r = aT1(Int(pTens) - 10)

    Else

       ' otherwise it's between 20 and 99.

       r = aT2(Int(Left$(pTens, 1)) - 2) & " "

       ' convert ones place digit

       r = r & ConvertDigit(Right$(pTens, 1))

    End If

    ConvertTens = r

End Function

 

Private Function ConvertDigit(ByVal pNumber As String) As String

    If pNumber = "0" Then

       ConvertDigit = ""

    Else

       ConvertDigit = aT0(Int(pNumber) - 1)

    End If

End Function

 

Private Function RoundNumberString( _

    ByVal Number As Currency, ByVal Decimals As Long) As String

 

    Dim s As String

    Dim r As Double

 

    ' round first

    r = Int(Number * 10 ^ Decimals + 0.5) / (10 ^ Decimals)

    ' complete with zeros

    s = CStr(r)

    If InStr(s, m_DecimalSeparator) = 0 Then s = s & m_DecimalSeparator

    Do While Len(Mid$(s, InStr(s, m_DecimalSeparator))) <= Decimals

        s = s & "0"

    Loop

    RoundNumberString = s

End Function

 

Private Sub Class_Initialize()

    IntegerPart = ZERO

    RealPart = ZERO

    aT0 = Array("One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight",

                "Nine")

    aT1 = Array("Ten", "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen",

                "Sixteen", "Seventeen", "Eighteen", "Nineteen")

    aT2 = Array("Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy",

                "Eighty", "Ninety")

    aT3 = Array("", "", " Thousand ", " Million ", " Billion ", " Trillion ", "",

                "", "", "")

   

    '$GUILLE: 06/Ago/2006 14.53

    If InStr(Format$(1.5, "#.#"), ",") Then

        m_DecimalSeparator = ","

        m_GroupSeparator = "."

    Else

        m_DecimalSeparator = "."

        m_GroupSeparator = ","

    End If

End Sub

 

Public Property Get DecimalSeparator() As String

    DecimalSeparator = m_DecimalSeparator

End Property

 

Public Property Get GroupSeparator() As String

    GroupSeparator = m_GroupSeparator

End Property

 

 

Nota.  La declaración de variables en el evento Class_Initialize, es una manera de asignar variables en VB6 antes de usarlas ¿Alguna duda? No obstante los arreglos inicializados explícitamente deben ser de tipo Variant, una limitación de VB6.

 

Probando la Clase CN2W de VB6

 

La clase CN2W puede usarse en cualquier contexto COM. A continuación muestro una ejemplo extraído de  una aplicación de Windows con VB6.

 

Ejemplo de uso de la clase N2W de VB6

 

Private Sub btnCurrencyToWords_Click()

    Dim n2w As CN2W

   

    Set n2w = New CN2W

    Me.txtOutput.Text = n2w.CurrencyToWords(Me.txtNumber.Text, "Dolars")

    Set n2w = Nothing

End Sub

 

En la descarga al final de artículo encuentra la aplicación N2W_VB6 completa.

 


ir al índice principal del Guille