Archivo por meses: agosto 2020

Compilar y ejecutar (utilidad para .NET)

Pues eso… esta utilidad la publiqué el 5 de enero de 2019, pero solo en elguille.info (Utilidades para .NET: Compilar y ejecutar) y aparte del código fuente puse la opción de instalarla usando ClickOnce.

En esa ocasión utilizaba código de Roslyn 2.0.1 (Microsoft.CodeDom.Providers.DotNetCompilerPlatform) para compilar el código desde la aplicación: se tomaba el código (un texto) de Visual Basic o C#, se compilaba y se mostraba el resultado por la consola virtual usada en la aplicación, de modo que pudieras ver el resultado de la salida.

Algo como lo mostrado en la siguiente captura, donde el panel superior es el código y el inferior es la salida al compilar y ejecutar dicho código.

 

El código de esa utilidad lo he cambiado con fecha del 30 y 31 de agosto de 2020, entre otras cosas para usar Roslyn 3.6.0, que actualmente es la versión más reciente.

Y el escribir esto aquí, en el blog, es para comentarte que al usar esa nueva versión del paquete de NuGet daba error al ejecutar el código (y pulsar en el botón de compilar).

El error era que no encontraba el compilador de Visual Basic (vbc.exe) o C# (csc.exe) y el path que daba era el path del ejecutable seguido de bin\roslyn, por ejemplo: «<resto del path>\bin\roslyn\vbc.exe»

Yo ya estaba por desistir y dejar la versión 2.0.1 de Roslyn ya que al fin y al cabo me permitía usar la versión más reciente de los compiladores de VB y C#, pero buscando en el NuGet del CodeDom.Providers.DotNetCompilerPlatform de Roslyn 3.6.0 y concretamente al mirar en el enlace que hay debajo de «This package was built from the source at» me llevó al GitHub de aspnet / RoslynCodeDomProvider. Y mirando en las Issues (había 3) me topé con la de WolfgangHG con el título: [3.6.0] Missing entry «aspnet:RoslynCompilerLocation» in app.config causes compile to fail, y eso era todo lo que hacía falta… añadir un trozo de código que en teoría faltaba para que funcionara a la perfección (ese código si se incluye en roslyn 2.0.1) y es este:

  <appSettings>
    <add key="aspnet:RoslynCompilerLocation" value="roslyn"/>
  </appSettings>

Y ya si quieres, cambia la asignación de compilerOptions de C# y VB (en la sección system.codedom / compilers) para que la versión del compilador sea la predeterminada: /langversion:default. Ya que en mi caso, la versión de C# estaba en la 7.3 y se puede usar la 8.0. Con Visual Basic, la «default» es la versión 16 (la última hasta la fecha de hoy).

En la siguiente captura puedes ver las versiones que aceptan los compiladores vbc.exe y csc.exe en el Roslyn 3.6.0:

Versiones de los lenguajes de VB y C# soportadas por los compiladores incluidos en Roslyn 3.6.0

Como ves, la versión predeterminada (y la mayor) de vbc.exe en la 16, mientras que la del compilador de C# (csc.exe) es la 8.0.

NOTA:
En la preview de Visual Studio 2019 la versión más alta de C# es la 9.0, la de VB sigue siendo la 16.

Nota:
Desde la página de la utilidad en elguille.info puedes instalarla con ClickOnce y tienes el código fuente de la utilidad (en Visual Basic) y algunos ejemplos tanto en VB como en C#.

Nos vemos.
Guillermo

Cálculos aritméticos en tu código con EvaluarExpresiones

Pues eso… ya he corregido y actualizado el código de evaluar expresiones aritméticas que publiqué inicialmente en octubre de 2007. Además he ampliado la funcionalidad que tenía usando algunas cosas que ya hacía en la versión que creé en su día con Visual Basic 6 (cFormulas – Una clase para analizar expresiones numéricas).

Como por ejemplo poder definir variables y funciones propias, además de poder usar contenido entre comillas dobles (solo para mostrar, no es que utilice variables alfanuméricas).

Para usar el «evaluador de expresiones» necesitas tener una referencia a la DLL (elGuille.EvaluarExpresiones.dll) en tu proyecto de .NET (al menos funciona con Visual Basic y C#) y tener instalado al menos el .NET 4.7.

Crear una variable a la que asignes una nueva instancia de elGuille.Developer.EvaluarFunciones.
Dim ev As New elGuille.Developer.EvaluarFunciones.

Si quieres añadir variables antes de empezar a usar el método Evalua, lo puedes hacer con ev.NewVariable(«A», «123+5+2″) y si quieres crear una función, hazlo con ev.NewFunction(«Doble», «N», «N*2»). Si quieres hacer varias asignaciones de un golpe, puedes usar ev.AsignarVariables(«C=A+B: D=12345: F=A*D»).

Y para evaluar las diferentes expresiones usa el método Evalua al que le pasarás como argumento una cadena con el «texto» a evaluar, por ejemplo:
resultado = ev.Evalua(«X=10:A=5*3: P=3.14: X + A + P»)

Descargar el código fuente y la DLL

El código completo de la DLL está en EvaluarExpresiones_src.zip que tiene un tamaño de 191 KB (195.897 bytes) y que puedes descargar pulsando directamente en el enlace anterior (está en el sitio downloads.elguille.info).
Revisión v.58 del 29/Ago/2020

Ese código es un proyecto de Visual Basic .NET.

En otra ocasión actualizaré este artículo para poner algunos ejemplos de uso. Aunque los publicados en los dos enlaces que te puse al principio te pueden servir.

Espero que te sea de utilidad… ya sabes… de eso es de lo que se trata 😉

Nos vemos.
Guillermo

Métodos de extensión (ejemplos para Visual Basic y C#)

Pues eso… y por si no lo sabes, esto de los métodos de extensión (Extension Methods en inglés) sirve para ampliar la funcionalidad de clases existentes, ya sean definidas en el propio .NET (String, Integer, TextBox, etc.) como en otras librerías (bibliotecas) que tengamos referenciadas en nuestro proyecto.

NOTA:
Aquí no te voy a explicar en detalle de qué va esto de los métodos de extensión, solo decirte cómo crear los tuyos propios, con código de Visual Basic y de C#, para ello te pondré unos ejemplos.

NOTA2:
¡Vaya despiste!
Pues eso… que se ve que en septiembre del año pasado (5-sep-2019) ya publiqué algo sobre los métodos de extensión… y es que lo tenía apuntado (en mi cabeza) y se ve que después de publicarlo no lo borré… jajaja… en fin… Más vale 2 que nada… 😉

En este artículo voy a ponerte un par de ejemplos para añadir una nueva función a la clase String y algunos métodos de conversión a la clase TextBox, de forma que puedas usarlos para convertir el contenido de la propiedad Text en un valor entero, un valor decimal, etc.

Crear métodos de extensión en Visual Basic

En Visual Basic necesitamos una importación a System.Runtime.CompilerServices y crear los métodos en un módulo (no se pueden crear en clases).
La definición del método empezará con el atributo <Extension> y después seguirá la definición de la función ( o método), pero en el primer parámetro indicaremos la clase que queremos ampliar (o extender) y después, opcionalmente, podemos usar más parámetros.

Crear método de extensión en C#

Para crear métodos de extensión en C# necesitamos una clase definida como static y definir los métodos de extensión también como compartidos (static), pero no es necesario usar la importación de CompilerServices ni añadir el atributo Extension, ya que el compilador sabe que es un método de extensión si el primer argumento de la función (o método) empieza con this y el tipo que queremos extender.

Un par de ejemplos de métodos de extensión

En el siguiente código tenemos un par de ejemplos de métodos de extensión de la clase String, el primero como función y el segundo como un método que no devuelve nada.
Primero el código de Visual Basic y después el de C#.

Imports System.Runtime.CompilerServices

Module Extensiones
    <Extension>
    Public Function Prueba(str As String) As String
        Return "PRUEBA " & str
    End Function

    <Extension>
    Public Sub Imprimir(str As String)
        Console.WriteLine(str)
    End Sub
End Module
static class Extensiones
{
    //[Extension]
    public static string Prueba(this string str)
    {
        return "PRUEBA " + str;
    }

    public static void Imprimir(this string str)
    {
        Console.WriteLine(str);
    }

Algunos métodos prácticos para extender String y TextBox

Aquí te dejo el código para extender la clase String con un método que quita las tildes (acentos) a las vocales, con idea de que podamos hacer comparaciones sin que se tenga en cuenta si la vocal está o no acentuada (con la tilde).

En el caso del control TextBox te dejo los métodos de extensión para convertir el contenido en un valor de tipo Integer, Double, Decimal, Date y TimeSpan.
Esto lo empecé a usar en el último programa que he hecho, hasta ahora, cuando quería convertir el contenido de una cadena en un valor numérico, etc., lo que hacía era usar funciones para esas conversiones.
Esas conversiones podría haberlas puesto en la clase String, pero al hacerla en el TextBox me ahorraba tener que indicar la propiedad Text cada vez que quería hacer la conversión 😉
En el código de ejemplo te pongo también la función que convierte en número entero para la clase String, así lo tendrás fácil si quieres extender dicha clase en lugar del TextBox.

El código para Visual Basic

Primero los métodos de extensión y después cómo usarlos.

Imports System

Imports System.Runtime.CompilerServices
Imports System.Windows.Forms

Module Extensiones

    ''' <summary>
    ''' Devuelve un valor Integer de la propiedad Text del TextBox indicado.
    ''' </summary>
    ''' <param name="txt">El TextBox a extender</param>
    <Extension>
    Public Function AsInteger(txt As TextBox) As Integer
        Dim i As Integer = 0

        Integer.TryParse(txt.Text, i)

        Return i
    End Function

    ''' <summary>
    ''' Devuelve un valor Double de la propiedad Text del TextBox indicado.
    ''' </summary>
    <Extension>
    Public Function AsDouble(txt As TextBox) As Double
        Dim d As Double = 0

        Double.TryParse(txt.Text, d)

        Return d
    End Function

    ''' <summary>
    ''' Devuelve un valor Decimal de la propiedad Text del TextBox indicado.
    ''' </summary>
    <Extension>
    Public Function AsDecimal(txt As TextBox) As Decimal
        Dim d As Decimal = 0

        Decimal.TryParse(txt.Text, d)

        Return d
    End Function

    ''' <summary>
    ''' Devuelve un valor TimeSpan de la propiedad Text del TextBox indicado.
    ''' </summary>
    <Extension>
    Public Function AsTimeSpan(txt As TextBox) As TimeSpan
        Dim c As New TimeSpan(0, 0, 0)

        TimeSpan.TryParse(txt.Text, c)

        Return c
    End Function

    ''' <summary>
    ''' Devuelve un valor Date de la propiedad Text del TextBox indicado.
    ''' Si es un valor nulo o vacío, devuelve el 1 de enero de 1900.
    ''' </summary>
    <Extension>
    Public Function AsDate(txt As TextBox) As Date
        Dim d As DateTime = New DateTime(1900, 1, 1)

        If Not (String.IsNullOrWhiteSpace(txt.Text) OrElse txt.Text.Equals(DBNull.Value)) Then
            DateTime.TryParse(txt.Text, d)
            If d.Year < 1900 Then
                d = New DateTime(1900, 1, 1, 0, 0, 0)
            End If
        Else
            ' asignar el 01/01/1900 si es un valor en blanco
            d = New DateTime(1900, 1, 1, 0, 0, 0)
        End If

        Return d.Date
    End Function


    ''' <summary>
    ''' Quitar las tildes de una cadena.
    ''' </summary>
    ''' <param name="str">La cadena a extender</param>
    <Extension>
    Public Function QuitarTildes(ByVal str As String) As String
        Dim tildes1 = "ÁÉÍÓÚáéíóú"
        Dim tildes0 = "AEIOUaeiou"
        Dim res As New System.Text.StringBuilder

        For i = 0 To str.Length - 1
            Dim j = tildes1.IndexOf(str(i))
            If j > -1 Then
                res.Append(tildes0.Substring(j, 1))
            Else
                res.Append(str(i))
            End If
        Next
        Return res.ToString
    End Function

    ''' <summary>
    ''' Devuelve un valor Integer de la cadena a la que se aplica este método.
    ''' </summary>
    <Extension>
    Public Function AsInteger(str As String) As Integer
        Dim i As Integer = 0

        Integer.TryParse(str, i)

        Return i
    End Function



    <Extension>
    Public Function Prueba(str As String) As String
        Return "PRUEBA " & str
    End Function

    <Extension>
    Public Sub Imprimir(str As String)
        Console.WriteLine(str)
    End Sub
End Module
Imports System

Imports System.Windows.Forms

Module Module1

    Sub Main()
        Dim s = "Hola"
        Console.WriteLine(s.Prueba)

        s.Imprimir()

        s = "Código de ejemplos prácticos"
        Console.WriteLine(s.QuitarTildes())

        Dim txt As New TextBox

        txt.Text = "125"
        Console.WriteLine("txt.Text : {0}", txt.Text)
        Console.WriteLine("txt.Text.AsInteger+10 : {0}", txt.Text.AsInteger + 10)
        Console.WriteLine("txt.AsInteger+10 : {0}", txt.AsInteger + 10)

        txt.Text = "125,38"
        Console.WriteLine("txt.Text : {0}", txt.Text)
        Console.WriteLine("txt.AsDecimal*2 : {0}", txt.AsDecimal * 2)
        Console.WriteLine("txt.AsDouble*2 : {0}", txt.AsDouble * 2)

        txt.Text = "10:30"
        Console.WriteLine("txt.Text : {0}", txt.Text)
        Console.WriteLine("txt.AsTimeSpan : {0}", txt.AsTimeSpan)

        txt.Text = "24/08/2020"
        Console.WriteLine("txt.Text : {0}", txt.Text)
        Console.WriteLine("txt.AsDate : {0}", txt.AsDate)
        Console.WriteLine("txt.AsDate.AddDays(2) : {0}", txt.AsDate.AddDays(2))


        Console.ReadKey()
    End Sub

End Module

El código para C#

Primero los métodos de extensión y después cómo usarlos.

using System;
using System.Windows.Forms;

static class Extensiones
{
    //[Extension]
    public static string Prueba(this string str)
    {
        return "PRUEBA " + str;
    }

    public static void Imprimir(this string str)
    {
        Console.WriteLine(str);
    }

    /// <summary>
    /// Devuelve un valor Integer de la propiedad Text del TextBox indicado.
    /// </summary>
    /// <param name="txt">El TextBox a extender</param>
    public static int AsInteger(this TextBox txt)
    {
        int i = 0;

        int.TryParse(txt.Text, out i);

        return i;
    }

    /// <summary>
    /// Devuelve un valor Double de la propiedad Text del TextBox indicado.
    /// </summary>
    public static double AsDouble(this TextBox txt)
    {
        double d = 0;

        double.TryParse(txt.Text, out d);

        return d;
    }

    /// <summary>
    /// Devuelve un valor Decimal de la propiedad Text del TextBox indicado.
    /// </summary>
    public static decimal AsDecimal(this TextBox txt)
    {
        decimal d = 0;

        decimal.TryParse(txt.Text, out d);

        return d;
    }

    /// <summary>
    /// Devuelve un valor TimeSpan de la propiedad Text del TextBox indicado.
    /// </summary>
    public static TimeSpan AsTimeSpan(this TextBox txt)
    {
        TimeSpan c = new TimeSpan(0, 0, 0);

        TimeSpan.TryParse(txt.Text, out c);

        return c;
    }

    /// <summary>
    /// Devuelve un valor Date de la propiedad Text del TextBox indicado.
    /// Si es un valor nulo o vacío, devuelve el 1 de enero de 1900.
    /// </summary>
    public static DateTime AsDate(this TextBox txt)
    {
        DateTime d = new DateTime(1900, 1, 1);

        if (!(string.IsNullOrWhiteSpace(txt.Text) || txt.Text.Equals(DBNull.Value)))
        {
            DateTime.TryParse(txt.Text, out d);
            if (d.Year < 1900)
                d = new DateTime(1900, 1, 1, 0, 0, 0);
        }
        else
            // asignar el 01/01/1900 si es un valor en blanco
            d = new DateTime(1900, 1, 1, 0, 0, 0);

        return d.Date;
    }


    /// <summary>
    /// Quitar las tildes de una cadena.
    /// </summary>
    /// <param name="str">La cadena a extender</param>
    public static string QuitarTildes(this string str)
    {
        var tildes1 = "ÁÉÍÓÚáéíóú";
        var tildes0 = "AEIOUaeiou";
        System.Text.StringBuilder res = new System.Text.StringBuilder();

        for (var i = 0; i <= str.Length - 1; i++)
        {
            var j = tildes1.IndexOf(str[i]);
            if (j > -1)
                res.Append(tildes0.Substring(j, 1));
            else
                res.Append(str[i]);
        }
        return res.ToString();
    }

    /// <summary>
    /// Devuelve un valor Integer de la cadena a la que se aplica este método.
    /// </summary>
    public static int AsInteger(this string str)
    {
        int i = 0;

        int.TryParse(str, out i);

        return i;
    }
}
using System;

using System.Windows.Forms;

class Program
{
    static void Main(string[] args)
    {
        var s = "Hola";
        Console.WriteLine(s.Prueba());
        //Console.WriteLine(s.Prueba2());

        s.Imprimir();

        s = "Código de ejemplos prácticos";
        Console.WriteLine(s.QuitarTildes());


        TextBox txt = new TextBox();

        txt.Text = "125";
        Console.WriteLine("txt.Text : {0}", txt.Text);
        Console.WriteLine("txt.Text.AsInteger+10 : {0}", txt.Text.AsInteger() + 10);
        Console.WriteLine("txt.AsInteger+10 : {0}", txt.AsInteger() + 10);

        txt.Text = "125,38";
        Console.WriteLine("txt.Text : {0}", txt.Text);
        Console.WriteLine("txt.AsDecimal*2 : {0}", txt.AsDecimal() * 2);
        Console.WriteLine("txt.AsDouble*2 : {0}", txt.AsDouble() * 2);

        txt.Text = "10:30";
        Console.WriteLine("txt.Text : {0}", txt.Text);
        Console.WriteLine("txt.AsTimeSpan : {0}", txt.AsTimeSpan());

        txt.Text = "24/08/2020";
        Console.WriteLine("txt.Text : {0}", txt.Text);
        Console.WriteLine("txt.AsDate : {0}", txt.AsDate());
        Console.WriteLine("txt.AsDate.AddDays(2) : {0}", txt.AsDate().AddDays(2));


        Console.ReadKey();
    }

    //Error CS1106: Un método de extensión debe definirse en una clase estática no genérica

    //public static string Prueba2(this string str)
    //{
    //    return "PRUEBA2 " + str;
    //}

}

Y esto es todo… espero que te haya sido de utilidad… ya sabes que esa es la idea 😉

Nos vemos.
Guillermo

Cálculos aritméticos en tu código con NCalc

Pues eso… el otro día me decidí a quitar la referencia al evaluador de expresiones de JScript de Microsoft (JScript.Eval.JScriptEvaluate) porque desde hace años está obsoleta (según la documentación de Microsoft, ya en Visual Studio 2005 lo estaba).

Así que… buscando en la red de redes, me topé con NCalc que actualmente (a la hora de escribir esto el 24 de agosto de 2020) está disponible en GitHub.

Lo único que tienes que hacer es descargar el código, en el directorio Evaluant.Calculator hay un proyecto de C# que es el que debes usar en tu solución de Visual Studio y agregar una referencia, así, mediante el espacio de nombres NCalc podrás usar las clases (concretamente Expression) para hacer los cálculos aritméticos.

NOTA:
En mi sitio también tengo una DLL con código para evaluar expresiones (y funciones) que la hice con Visual Studio 2005 (en octubre de 2007) y que cuando fui a usarla en el proyecto que necesitaba el evaluador aritmético, me dio algún error (ya no recuerdo cuál) así que, decidí usar una que ya estuviese probada ampliamente y por eso me decidí por la de Microsoft JScript.
Y haciendo hoy alguna que otra prueba, me doy cuenta que falla en algo tan simple como 1*2*3+5, que en lugar de 11 me muestra 16, tal como sería si hubiese indicado 1*2*(3+5). Sin embargo esto si da el resultado correcto: 2*3+5 .En fin…
(¡Ya está solucionado, ese y otros fallillos que he ido encontrando! a ver si lo publico… )

Nota:
Aquí tienes el artículo sobre la nueva versión que he creado de EvaluarExpresiones.

NOTA:
El código de ejemplo que te muestro a continuación es solo para Visual Basic, ya que en la página de NCalc tienes abundantes ejemplos para C#.

¿Cómo evaluar expresiones aritméticas con NCalc?

La forma de usar NCalc es simple, primero creamos una instancia de la clase Expression pasándole como parámetro la expresión a evaluar (en el siguiente ejemplo, la variable texto, de tipo String, contiene la expresión a evaluar).

Dim exp As New NCalc.Expression(texto)

Para evaluar esa expresión indicada en el constructor llamamos al método Evaluate, el cual devuelve un valor de tipo Object con el resultado de dicha evaluación.

Dim res = exp.Evaluate()

Indicar variables en la expresión a evaluar

Si quieres evaluar una expresión que tenga argumentos (variables) puedes definir esas variables usando el método Parameters de la clase Expression.

En el siguiente ejemplo, están definidas las variables X y A y se usan en la expresión a evaluar.
En el primer caso, X se le asigna un valor fijo (10 en este caso) y a la variable A se le asigna el resultado de una expresión evaluada.

Dim exp As New NCalc.Expression("X + A")
exp.Parameters("X") = 10 ' "10*2"
exp.Parameters("A") = New NCalc.Expression("10 + 5").Evaluate
Dim res = exp.Evaluate()

Nota que si el valor que le asignas al parámetro está entre comillas dobles, se considera que es una cadena lo que estás asignado, no que evalúe el contenido de esa cadena.

Para probarlo, quita el comentario de la asignación del parámetro X para que sea: exp.Parameters(«X») = «10*2»
El resultado mostrado al evaluar X + A sería 10*215. Es decir, lo trataría como una cadena alfanumérica.

Usar variables de forma dinámica (o casi)

La clase Expression puede evaluar parámetros de forma dinámica, es decir, asignados externamente (o casi): Para ello en lugar de usar Parameters para definir esos valores se utiliza un delegado (o lo que se conoce como un método de evento) y en ese método se pueden indicar los valores que queramos… eso sí, esto no es realmente de forma dinámica, si no, que ya deben estar predefinidos… o casi…

En el siguiente ejemplo además de los valores de X y A está el valor de Pi, ese valor se obtiene desde el método de evento definido en el delegado del evento EvaluateParameter de la clase Expression.

Dim exp As New NCalc.Expression("X + A + Pi")
exp.Parameters("X") = 10
exp.Parameters("A") = New NCalc.Expression("10 + 5").Evaluate
AddHandler exp.EvaluateParameter, AddressOf evaluarParametro

Dim res = exp.Evaluate()

En ese método, tal como veremos en el siguiente código, se pueden definir otros valores que usemos en cualquiera de las expresiones evaluadas. Y si la variable contiene espacios o caracteres especiales, los podemos poner entre corchetes para que no den error.

Private Sub evaluarParametro(name As String, args As ParameterArgs)
    If name = "Pi" Then
        args.Result = 3.14
    ElseIf name = "Caña" Then
        args.Result = 17
    ElseIf name = "aleta" Then
        args.Result = 12
    ElseIf name = "gafas snorkel" Then
        args.Result = 8
    Else
        args.Result = 0
    End If
End Sub

La forma de usar los valores de Caña y gafas snorkel es de esta forma:

Dim exp As New NCalc.Expression("X + A + [Caña] + aleta * 2 + [gafas snorkel]")
exp.Parameters("X") = 10
exp.Parameters("A") = New NCalc.Expression("10 + 5").Evaluate
AddHandler exp.EvaluateParameter, AddressOf evaluarParametro

Dim res = exp.Evaluate()

Un par de consejos al usar NCalc

El primero y más importante es que los decimales hay que indicarlos siempre con puntos, independientemente de cómo lo tengas configurado en tu equipo.

El segundo es el uso de EvaluateOptions.IgnoreCase como segundo argumento en el constructor de la clase Expression, que supuestamente sirve para que al evaluar las expresiones (y los valores indicados en parámetros) no distinga entre mayúsculas y minúsculas. Pero como te digo, eso es lo que parece que dice la documentación, pero según he comprobado, solo funciona con las funciones internas no con los valores que tú hayas indicado en los parámetros o parámetros evaluados.

El tercero es que te leas la Wiki de NCalc, que aunque los ejemplos estén en C#, al menos puedas saber las diferentes funciones y operadores que se pueden usar.

Espero que te haya sido de utilidad, esa es siempre la idea.

Nos vemos.
Guillermo