Está en la página 1de 7

Cómo evaluar expresiones matemáticas

en VB .Net
Contenido

Introducción
La clase Evaluador
El método PrecompilarAssembly
El método Evaluar
Implementado Evaluador
Conclusión
Referencias

Introducción

Generalmente denominamos funciones, a las expresiones matemáticas o no, posibles de


ser evaluadas. Sin embargo, para simplificar este artículo, las llamaremos sencillamente
"expresiones", para no confundirlas con el concepto de función en los lenguajes de
programación.

En algunas ocasiones nos encontramos con la imposibilidad de evaluar directamente una


expresión en VB .Net. Y es que desde los inicios del Basic, nunca ha existido una función
propia del lenguaje que evalúe una expresión.

Recuerdo con nostalgia cuando, con GWBasic, aprendí un artilugio que consistía en que
nuestro código creara un archivo secuencial (con extensión .bas) que contuviera el código
de una función, y dentro de esta función escribiéramos la expresión que queríamos
evaluar. Claro, esto era posible pues GW era un lenguaje interpretado y no existía un
compilador que generara un ejecutable.

En VB el truco consistía en usar una referencia a un objeto Microsoft Script Control y


usar su método Eval. Como se muestra en el ejemplo siguiente:

Dim oSC As New ScriptControl


Dim expMath As String
Dim expToEval As String

oSC.Language = "VBScript"

expMath = "X ^ 2 + 2 * X + 1"


expToEval = Replace(expMath, "X", 5)

MsgBox oSC.Eval(expToEval)
Esta técnica, aunque resulta muy útil y en algunos casos soluciona nuestros problemas,
está limitada a que sólo podemos usar objetos y/o funciones definidas por VBScript
dentro del parámetro Expression del método Eval.

Todo esto ha servido para que reflexione sobre cuán viejo soy, aunque éstas no son las
técnicas de reflexión que se usan en VS .Net. De éstas vamos a hablar más adelante.

Si bien en VB .Net podemos hacer referencia a un objeto Microsoft Script Control,


tendríamos las mismas desventajas que con versiones anteriores de VB. ¿Cómo podemos
entonces evaluar una expresión en VB .Net, sin desaprovechar las bondades de .Net? Si
recordamos el artilugio que se usaba con GWBasic, lo desempolvamos y lo
implementamos bajo la plataforma .Net, podremos no solo evaluar una expresión, si no
crear y ejecutar cualquier código de forma dinámica. Sin embargo, por ahora nos
conformaremos con crear una clase que evalúe una expresión matemática.

Crearemos entonces una clase Evaluador. Esta creará un assembly con el código que
deseamos ejecutar, en este caso un método o función con la expresión a evaluar. Luego,
usando la reflexión invocará este método y obtendremos los resultados. Suena sencillo…
¡Realmente lo es!

La clase Evaluador

La clase Evaluador contendrá un objeto privado (oEnsamblado) de tipo assembly (del


espacio de nombres System.Reflection) y dos métodos públicos: PrecompilarAssembly
y Evaluar. He decidido separar ambos métodos por cuestiones de rendimiento. Si
combináramos los dos métodos y fuésemos a dibujar la grafica de una expresión, el
rendimiento final sería simplemente inaceptable. La compilación de un assembly
requiere una cantidad de proceso considerable.

El objeto oEnsamblado será la representación del assembly que compilaremos. Este


assembly, como mencionamos anteriormente, será una clase (EvalClase) que expondrá
un único método (Eval) que contendrá la expresión a evaluar y retornará el resultado:

Imports System.Text
Imports System.CodeDom.Compiler
Imports System.Collections.Specialized

   Public Class Evaluador


   Private oEnsamblado As System.Reflection.Assembly

   Public Sub PrecompilarAssembly ...

   End Sub

   Public Function Evaluar ...


   End Sub

El método PrecompilarAssembly

El método PrecompilarAssembly, como su nombre lo indica, precompila el assembly de


la clase que contiene un método con la expresión a evaluar. Vamos a visualizar el código
a generar y los posibles escenarios dependientes de la expresiones a evaluar.

Supongamos que deseamos evaluar la expresión matemática y(x) = x + 3. El código


necesario sería algo así como lo que se muestra a continuación:

Public Class EvalClase


   Public Shared Function Eval(ByVal X As Double) as Object
      Return X + 3
   End Function
End Class

Por otro lado, supongamos que queremos evaluar z(x,y) = Log(x) + y:

Imports System.Math

Public Class EvalClase


   Public Shared Function Eval(ByVal X As Double, ByVal Y
As Double) as Object
      Return Log10(X) + Y
   End Function
End Class

Vemos que existen tres factores cambiantes: la expresión, los parámetros del método
Eval y los namespaces. Por lo tanto nuestro método Precompilarfuncion recibirá tres
parámetros, correspondientes a esos elementos. Veamos entonces el método
PrecompilarAssembly:

Public Function PrecompilarAssembly(ByVal Funcion As


String, _
ByVal ParametrosList As StringCollection, ByVal
NameSpaceList As StringCollection) As Boolean

Dim mStrings As String


   Dim mParametros As String
   'Definimos un objeto de tipo StringBuilder que contendra
el código a compilar
   Dim CodigoFuente As New StringBuilder()

'Agregamos los Imports necesarios a nuestro codigo fuente


   For Each mStrings In NameSpaceList
      CodigoFuente.Append("Imports " & mStrings & vbCr)
   Next

'Preparamos un string con los parametros que usará el


metodo Eval
   'de de la clase EvalClase
   For Each mStrings In ParametrosList
      mParametros &= ", " & mStrings
   Next

mParametros = Trim(mParametros)
   If mParametros.Length > 0 Then
      mParametros = Trim(Mid(mParametros, 2))
   End If

'Terminamos de construir la clase a compilar


    CodigoFuente.Append("Public Class EvalClase" & vbCr)
    CodigoFuente.Append(" Public Shared Function Eval(" & _
       mParametros & ") as Object" & vbCr)
    CodigoFuente.Append(" Return " & Funcion & vbCr)
    CodigoFuente.Append(" End Function " & vbCr)
    CodigoFuente.Append("End Class " & vbCr)

'Creamos una instancia de la clase VBCodeProvider


    'que usaremos para obtener una referencia a una
interfaz ICodeCompiler
    Dim oCProvider As New VBCodeProvider()
    Dim oCompiler As ICodeCompiler =
oCProvider.CreateCompiler

'Usamos la clase CompilerParameters para pasar parámetros


al compilador
    'En particular, definimos que el assembly sea compilado
en memoria.
    Dim oCParam As New CompilerParameters()
    oCParam.GenerateInMemory = True

'Creamos un objeto CompilerResult que obtendrá los


resultados de la compilación
    Dim oCResult As CompilerResults
    oCResult = oCompiler.CompileAssemblyFromSource(oCParam,
CodigoFuente.ToString)

'Comprobamos que no existan errores de compilación.


    Dim oCError As CompilerError
    If oCResult.Errors.Count > 0 Then
       'Si existen errores los mostramos.
       'Si bien, podriamos implementar un mejor método para
visualizar
       'los errores de compilación, este nos servirá por
los momentos.
       For Each oCError In oCResult.Errors
          MsgBox(oCError.ErrorText.ToString)
       Next
       Return False
    Else
       'Como el ensamblado se generó en memoria, debemos
obtener
       'una referencia al ensamblado generado, para esto
usamos
       'la propiedad CompiledAssembly
       oEnsamblado = oCResult.CompiledAssembly
       Return True
    End If
End Sub

El método PrecompilarAssembly devolverá True o False dependiendo de si la


compilación tuvo éxito o no. El assembly compilado en memoria, estará referenciado por
el objeto oEnsamblado.

El método Evaluar

Ahora necesitamos invocar el método Eval de nuestro assembly recientemente generado.


Para ello, haremos uso de las técnicas de reflexión:

Public Function Evaluar(ByVal ParamArray Parametros() As


Object) As Object
    If oEnsamblado Is Nothing Then
       Return Nothing
    Else
       'Instanciamos la clase EvalClase de nuestro assembly

       'creando un tipo a partir de ella.


       Dim oClass As Type =
oEnsamblado.GetType("EvalClase")

'Usamos GetMethod para accesar al método Eval, e invocamos


este con los parametros necesarios.
       Return oClass.GetMethod("Eval").Invoke(Nothing,
Parametros)
    End If
End Function

Implementado Evaluador
Veamos ahora cómo usar nuestra clase Evaluador. En el ejemplo se evaluara la
expresión z(x,y) = Log(x) + y; con x = 100, y=3.

Nota: recuerda que en notación matemática, Log(x) simboliza el logaritmo de x en base


10; luego, debemos usar la función Log10.

'Creamos una nueva instancia de la clase Evaluador


Dim mEval As New Evaluador.Evaluador()

'Creamnos una variable tipo string y le asignamos la


expresión que queremos evaluar
Dim mExpresion As String = "Log10(X) + Y"

'Creamos un objeto StringCollection y agregamos los


parámetros de entrada que usará el método eval
Dim mParameters As New StringCollection()
mParameters.Add("ByVal X as Double")
mParameters.Add("ByVal Y as Double")

'En este caso, la función Log10() Pertenece al espacio de


nombres System.Math.
'se hace necesario entonces, crear un objeto
StringCollection y agregar
'el namespace System.Math.
Dim mNameSpaces As New StringCollection()
mNameSpaces.Add("System.Math")

'Invocamos el método PrecompilarFunción y verificamos si se


genero correctamente el assembly.
If If mEval.PrecompilarFuncion(mExpresion, mParameters,
mNameSpaces) Then
    'Si el assembly se generó correctamente, creamos un
array con los valores de los parametros a evaluar
    Dim mParam() = {100, 3}
    'invocamos el método Evaluar y mostramos el resultado
    MsgBox(mEval.Evaluar(mParam))
Else
    MsgBox("No se ha generado el Assembly")
End If

Conclusión

Hemos utilizado las técnicas de compilar mediante código para crear un assembly en
memoria y luego, mediante la reflexión, invocar sus métodos. Se me ocurren en este
momento no menos de 10 mejoras que se pueden hacer a la clase Evaluador. Sin
embargo, la he construido con un propósito específico: crear una clase Graficador que
implementará la clase Evaluador para graficar funciones matemáticas y para este fin,
está más que bien como está ahora.

También podría gustarte