Compartir a través de


Tutorial: Compilación de programas de C# basados en archivos

Las aplicaciones basadas en archivos son programas contenidos en un único *.cs archivo que se compilan y ejecutan sin un archivo de proyecto (*.csproj) correspondiente. Las aplicaciones basadas en archivos son ideales para aprender C# porque tienen menos complejidad: todo el programa se almacena en un solo archivo. Las aplicaciones basadas en archivos también son útiles para crear utilidades de línea de comandos. En plataformas Unix, las aplicaciones basadas en archivos se pueden ejecutar mediante directivas (shebang). En este tutorial, usted hará lo siguiente:

  • Cree un programa basado en archivos.
  • Agregue compatibilidad con Shebang (#!) de Unix.
  • Lee los argumentos de la línea de comandos.
  • Controle la entrada estándar.
  • Escriba la salida de arte ASCII.
  • Procesar argumentos de línea de comandos.
  • Use los resultados de la línea de comandos analizados.
  • Pruebe la aplicación final.

Crea un programa basado en archivos que escribe texto como arte ASCII. La aplicación está contenida en un único archivo, usa paquetes NuGet que implementan algunas de las características principales.

Prerrequisitos

Creación de un programa basado en archivos

  1. Abra Visual Studio Code y cree un archivo denominado AsciiArt.cs. Introduzca el texto siguiente:

    Console.WriteLine("Hello, world!");
    
  2. Guarde el archivo. A continuación, abra el terminal integrado en Visual Studio Code y escriba:

    dotnet run AsciiArt.cs
    

La primera vez que ejecute este programa, el dotnet host compila el archivo ejecutable a partir del archivo de origen, almacena los artefactos de compilación en una carpeta temporal y, a continuación, ejecuta el ejecutable creado. Puede comprobar esta experiencia escribiendo dotnet run AsciiArt.cs de nuevo. Esta vez, el dotnet host determina que el ejecutable es actual y ejecuta el ejecutable sin volver a compilarlo. No ve ninguna salida de compilación.

Los pasos anteriores muestran que las aplicaciones basadas en archivos no son archivos de script. Son archivos de código fuente de C# que se compilan mediante un archivo de proyecto generado en una carpeta temporal. Una de las líneas de salida que se muestran al compilar el programa debe tener un aspecto similar al siguiente (en Windows):

AsciiArt succeeded (7.3s) → AppData\Local\Temp\dotnet\runfile\AsciiArt-85c58ae0cd68371711f06f297fa0d7891d0de82afde04d8c64d5f910ddc04ddc\bin\debug\AsciiArt.dll

En las plataformas unix, la carpeta de salida es algo similar a:

AsciiArt succeeded (7.3s) → Library/Application Support/dotnet/runfile/AsciiArt-85c58ae0cd68371711f06f297fa0d7891d0de82afde04d8c64d5f910ddc04ddc/bin/debug/AsciiArt.dll

Esa salida indica dónde se colocan los archivos temporales y las salidas de compilación. En este tutorial, cada vez que edite el archivo de origen, el dotnet host actualiza el archivo ejecutable antes de que se ejecute.

Las aplicaciones basadas en archivos son programas de C# normales. La única limitación es que deben escribirse en un archivo de origen. Puede usar instrucciones de nivel superior o un método clásico Main como punto de entrada. Puede declarar cualquier tipo: clases, interfaces y estructuras. Puede estructurar los algoritmos en un programa basado en archivos igual que lo haría en cualquier programa de C#. Incluso puede declarar varios espacios de nombres para organizar el código. Si encuentra que un programa basado en archivos está creciendo demasiado grande para un solo archivo, puede convertirlo a un programa basado en proyectos y dividir el origen en varios archivos. Las aplicaciones basadas en archivos son una excelente herramienta de creación de prototipos. Puede empezar a experimentar con una sobrecarga mínima para probar conceptos y crear algoritmos.

Compatibilidad con Shebang (#!) de Unix

Nota:

La compatibilidad con #! directivas solo se aplica en plataformas unix. No hay una directiva similar para que Windows ejecute directamente un programa de C#. En Windows, debe usar dotnet run en la línea de comandos.

En unix, puede ejecutar aplicaciones basadas en archivos directamente, escribiendo el nombre del archivo de origen en la línea de comandos en lugar de dotnet run. Debe realizar dos cambios:

  1. Establezca permisos de ejecución en el archivo de origen:

    chmod +x AsciiArt.cs
    
  2. Agregue una directiva shebang (#!) como primera línea del AsciiArt.cs archivo:

    #!/usr/local/share/dotnet/dotnet run
    

La ubicación de dotnet puede ser diferente en diferentes instalaciones de Unix. Use el comando which dotnet para buscar el dotnet host en su entorno.

Como alternativa, puede usar #!/usr/bin/env dotnet para resolver automáticamente la ruta de dotnet desde la variable de entorno PATH.

#!/usr/bin/env dotnet

Después de realizar estos dos cambios, puede ejecutar el programa desde la línea de comandos directamente:

./AsciiArt.cs

Si lo prefiere, puede quitar la extensión para que pueda escribir ./AsciiArt en su lugar. Puede agregar al #! archivo de origen incluso si usa Windows. La línea de comandos de Windows no admite #!, pero el compilador de C# permite esa directiva en aplicaciones basadas en archivos en todas las plataformas.

Leer argumentos de la línea de comandos

Ahora, escriba todos los argumentos en la línea de comandos en la salida.

  1. Reemplace el contenido actual de AsciiArt.cs por el código siguiente:

    if (args.Length > 0)
    {
        string message = string.Join(' ', args);
        Console.WriteLine(message);
    }
    
  2. Para ejecutar esta versión, escriba el siguiente comando:

    dotnet run AsciiArt.cs -- This is the command line.
    

    La -- opción indica que todos los argumentos de comando siguientes deben pasarse al programa AsciiArt. Los argumentos This is the command line. se pasan como una matriz de cadenas, donde cada cadena es una palabra: This, is, the, commandy line..

En esta versión se muestran estos nuevos conceptos:

  • Los argumentos de la línea de comandos se pasan al programa mediante la variable argspredefinida . La args variable es una matriz de cadenas: string[]. Si la longitud de args es 0, significa que no se proporcionó ningún argumento. De lo contrario, cada palabra de la lista de argumentos se almacena en la entrada correspondiente de la matriz.
  • El string.Join método combina varias cadenas en una sola cadena, con el separador especificado. En este caso, el separador es un solo espacio.
  • Console.WriteLine escribe la cadena en la consola de salida estándar, seguida de una nueva línea.

Controlar la entrada estándar

Que controla correctamente los argumentos de la línea de comandos. Ahora, agregue el código para controlar la entrada de lectura de la entrada estándar (stdin) en lugar de los argumentos de la línea de comandos.

  1. Agregue la cláusula siguiente else a la if instrucción que agregó en el código anterior:

    else
    {
        while (Console.ReadLine() is string line && line.Length > 0)
        {
            Console.WriteLine(line);
        }
    }
    

    El código anterior lee la entrada de la consola hasta que se lee una línea en blanco o una null . (El Console.ReadLine método devuelve null si la secuencia de entrada está cerrada escribiendo ctrl+C).

  2. Pruebe la lectura de la entrada estándar mediante la creación de un nuevo archivo de texto en la misma carpeta. Asigne al archivo el nombre input.txt y agregue las siguientes líneas:

    Hello from ...
    dotnet!
    
    You can create
    file-based apps
    in .NET 10 and
    C# 14
    
    Have fun writing
    useful utilities
    

    Mantenga las líneas cortas para que tengan el formato correcto al agregar la característica para usar el arte ASCII.

  3. Vuelva a ejecutar el programa.

    Con Bash:

    cat input.txt | dotnet run AsciiArt.cs
    

    O bien, con PowerShell:

    Get-Content input.txt | dotnet run AsciiArt.cs
    

Ahora el programa puede aceptar argumentos de línea de comandos o entrada estándar.

Escritura de la salida de ASCII Art

A continuación, agregue un paquete que admita arte ASCII, Colorful.Console. Para agregar un paquete a un programa basado en archivos, use la #:package directiva .

  1. Agregue la siguiente directiva después de la directiva en el #! archivo AsciiArt.cs:

    #:package Colorful.Console@1.2.15
    

    Importante

    La versión 1.2.15 era la versión más reciente del Colorful.Console paquete cuando este tutorial se actualizó por última vez. Compruebe la página NuGet del paquete para obtener la versión más reciente para asegurarse de que usa una versión del paquete con las correcciones de seguridad más recientes.

  2. Cambie las líneas que llaman Console.WriteLine para usar el Colorful.Console.WriteAscii método en su lugar:

    async Task WriteAsciiArt(AsciiMessageOptions options)
    {
        foreach (string message in options.Messages)
        {
            Colorful.Console.WriteAscii(message);
            await Task.Delay(options.Delay);
        }
    }
    
  3. Ejecute el programa y verá la salida de arte ASCII en lugar de texto eco.

Opciones de comandos de proceso

A continuación, vamos a agregar análisis de línea de comandos. La versión actual escribe cada palabra como una línea de salida diferente. Los argumentos de la línea de comandos que ha agregado admiten dos características:

  1. Cita varias palabras que se deben escribir en una sola línea:

    AsciiArt.cs "This is line one" "This is another line" "This is the last line"
    
  2. Agregue una --delay opción para pausar entre cada línea:

    AsciiArt.cs --delay 1000
    

Los usuarios deben poder usar ambos argumentos juntos.

La mayoría de las aplicaciones de línea de comandos deben analizar los argumentos de la línea de comandos para controlar las opciones, los comandos y la entrada del usuario de forma eficaz. La System.CommandLine biblioteca proporciona funcionalidades completas para controlar comandos, subcomandos, opciones y argumentos, lo que le permite concentrarse en lo que hace la aplicación en lugar de en la mecánica de analizar la entrada de la línea de comandos.

La System.CommandLine biblioteca ofrece varias ventajas clave:

  • Generación y validación automáticas de texto de ayuda.
  • Compatibilidad con convenciones de línea de comandos POSIX y Windows.
  • Funcionalidades de finalización de pestañas integradas.
  • Comportamiento de análisis coherente entre aplicaciones.
  1. Agregue el System.CommandLine paquete. Agregue esta directiva después de la directiva de paquete existente:

    #:package System.CommandLine@2.0.0
    

    Importante

    La versión 2.0.0 era la versión más reciente cuando se actualizó por última vez este tutorial. Si hay una versión más reciente disponible, use la versión más reciente para asegurarse de que tiene los paquetes de seguridad más recientes. Compruebe la página NuGet del paquete para obtener la versión más reciente para asegurarse de que usa una versión del paquete con las correcciones de seguridad más recientes.

  2. Agregue las instrucciones using necesarias en la parte superior del archivo (después de las #! directivas y #:package ):

    using System.CommandLine;
    using System.CommandLine.Parsing;
    
  3. Defina la opción delay y el argumento messages. Agregue el código siguiente para crear los CommandLine.Option objetos y CommandLine.Argument para representar la opción y el argumento de la línea de comandos:

    Option<int> delayOption = new("--delay")
    {
        Description = "Delay between lines, specified as milliseconds.",
        DefaultValueFactory = parseResult => 100
    };
    
    Argument<string[]> messagesArgument = new("Messages")
    {
        Description = "Text to render."
    };
    

    En las aplicaciones de línea de comandos, las opciones suelen comenzar con -- (guión doble) y pueden aceptar argumentos. La --delay opción acepta un argumento entero que especifica el retraso en milisegundos. messagesArgument Define cómo se analizan como texto los tokens restantes después de que las opciones se analicen como texto. Cada token se convierte en una cadena independiente de la matriz, pero el texto se puede citar para incluir varias palabras en un token. Por ejemplo, "This is one message" se convierte en un solo token, mientras This is four tokens que se convierte en cuatro tokens independientes.

    El código anterior define el tipo de argumento para la --delay opción y que los argumentos son una matriz de string valores. Esta aplicación solo tiene un comando, por lo que se usa el comando raíz.

  4. Cree un comando raíz y configúrelo con la opción y el argumento . Agregue el argumento y la opción al comando raíz:

    RootCommand rootCommand = new("Ascii Art file-based program sample");
    
    rootCommand.Options.Add(delayOption);
    rootCommand.Arguments.Add(messagesArgument);
    
  5. Agregue el código para analizar los argumentos de la línea de comandos y controlar los errores. Este código valida los argumentos de la línea de comandos y almacena los argumentos analizados en el System.CommandLine.ParseResult objeto :

    ParseResult result = rootCommand.Parse(args);
    foreach (ParseError parseError in result.Errors)
    {
        Console.Error.WriteLine(parseError.Message);
    }
    if (result.Errors.Count > 0)
    {
        return 1;
    }
    

El código anterior valida todos los argumentos de la línea de comandos. Si se produce un error en la validación, los errores se escriben en la consola y se cierra la aplicación.

Uso de los resultados de la línea de comandos analizados

Ahora, finalice la aplicación para usar las opciones analizadas y escribir la salida. En primer lugar, defina un registro para contener las opciones analizadas. Las aplicaciones basadas en archivos pueden incluir declaraciones de tipos, como registros y clases. Deben ser después de todas las instrucciones de nivel superior y las funciones locales.

  1. Agregue una record declaración para almacenar los mensajes y el valor de la opción delay:

    public record AsciiMessageOptions(string[] Messages, int Delay);
    
  2. Agregue la siguiente función local antes de la declaración de registro. Este método controla los argumentos de la línea de comandos y la entrada estándar y devuelve una nueva instancia de registro:

    async Task<AsciiMessageOptions> ProcessParseResults(ParseResult result)
    {
        int delay = result.GetValue(delayOption);
        List<string> messages = [.. result.GetValue(messagesArgument) ?? Array.Empty<string>()];
    
        if (messages.Count == 0)
        {
            while (Console.ReadLine() is string line && line.Length > 0)
            {
                Colorful.Console.WriteAscii(line);
                await Task.Delay(delay);
            }
        }
        return new([.. messages], delay);
    }
    
  3. Cree una función local para escribir el arte ASCII con el retraso especificado. Esta función escribe cada mensaje en el registro con el retraso especificado entre cada mensaje:

    async Task WriteAsciiArt(AsciiMessageOptions options)
    {
        foreach (string message in options.Messages)
        {
            Colorful.Console.WriteAscii(message);
            await Task.Delay(options.Delay);
        }
    }
    
  4. Reemplace la if cláusula que escribió anteriormente por el código siguiente que procesa los argumentos de la línea de comandos y escribe la salida:

    var parsedArgs = await ProcessParseResults(result);
    
    await WriteAsciiArt(parsedArgs);
    return 0;
    

Ha creado un record tipo que proporciona estructura a las opciones y argumentos de la línea de comandos analizados. Las nuevas funciones locales crean una instancia del registro y usan el registro para escribir la salida de arte ASCII.

Prueba de la aplicación final

Pruebe la aplicación mediante la ejecución de varios comandos diferentes. Si tiene problemas, este es el ejemplo terminado para compararlo con lo que ha creado:

#!/usr/local/share/dotnet/dotnet run

#:package Colorful.Console@1.2.15
#:package System.CommandLine@2.0.0

using System.CommandLine;
using System.CommandLine.Parsing;

Option<int> delayOption = new("--delay")
{
    Description = "Delay between lines, specified as milliseconds.",
    DefaultValueFactory = parseResult => 100
};

Argument<string[]> messagesArgument = new("Messages")
{
    Description = "Text to render."
};

RootCommand rootCommand = new("Ascii Art file-based program sample");

rootCommand.Options.Add(delayOption);
rootCommand.Arguments.Add(messagesArgument);

ParseResult result = rootCommand.Parse(args);
foreach (ParseError parseError in result.Errors)
{
    Console.Error.WriteLine(parseError.Message);
}
if (result.Errors.Count > 0)
{
    return 1;
}

var parsedArgs = await ProcessParseResults(result);

await WriteAsciiArt(parsedArgs);
return 0;

async Task<AsciiMessageOptions> ProcessParseResults(ParseResult result)
{
    int delay = result.GetValue(delayOption);
    List<string> messages = [.. result.GetValue(messagesArgument) ?? Array.Empty<string>()];

    if (messages.Count == 0)
    {
        while (Console.ReadLine() is string line && line.Length > 0)
        {
            // <WriteAscii>
            Colorful.Console.WriteAscii(line);
            // </WriteAscii>
            await Task.Delay(delay);
        }
    }
    return new([.. messages], delay);
}

async Task WriteAsciiArt(AsciiMessageOptions options)
{
    foreach (string message in options.Messages)
    {
        Colorful.Console.WriteAscii(message);
        await Task.Delay(options.Delay);
    }
}

public record AsciiMessageOptions(string[] Messages, int Delay);

En este tutorial, ha aprendido a compilar un programa basado en archivos, donde se compila el programa en un único archivo de C#. Estos programas no usan un archivo de proyecto y pueden usar la #! directiva en sistemas unix. Los alumnos pueden crear estos programas después de probar nuestros tutoriales en línea y antes de compilar aplicaciones basadas en proyectos más grandes. Las aplicaciones basadas en archivos también son una excelente plataforma para las utilidades de la línea de comandos.