Sdílet prostřednictvím


Kurz: Vytváření souborových programů v jazyce C#

Aplikace založené na souborech jsou programy obsažené v jednom *.cs souboru, který se sestaví a spustí bez odpovídajícího souboru projektu (*.csproj). Souborové aplikace jsou ideální pro výuku jazyka C#, protože mají menší složitost: Celý program je uložený v jednom souboru. Aplikace založené na souborech jsou také užitečné pro vytváření nástrojů příkazového řádku. Na platformách Unix lze aplikace založené na souborech spouštět pomocí #!direktiv (shebang). V tomto kurzu se naučíte:

  • Vytvořte program založený na souborech.
  • Přidání podpory unixových shebangů (#!).
  • Čtení argumentů příkazového řádku
  • Zpracování standardního vstupu
  • Napište výstup obrázku ASCII.
  • Zpracování argumentů příkazového řádku
  • Použijte analyzované výsledky příkazového řádku.
  • Otestujte konečnou aplikaci.

Vytvoříte souborový program, který zapíše text jako obrázek ASCII. Aplikace je obsažena v jednom souboru, používá balíčky NuGet, které implementují některé základní funkce.

Požadavky

Vytvoření programu založeného na souborech

  1. Otevřete Visual Studio Code a vytvořte nový soubor s názvem AsciiArt.cs. Zadejte následující text:

    Console.WriteLine("Hello, world!");
    
  2. Uložte soubor. Pak otevřete integrovaný terminál v editoru Visual Studio Code a zadejte:

    dotnet run AsciiArt.cs
    

Při prvním spuštění tohoto programu dotnet hostitel sestaví spustitelný soubor ze zdrojového souboru, uloží artefakty sestavení do dočasné složky a potom spustí vytvořený spustitelný soubor. Toto prostředí můžete ověřit opětovným zadáním dotnet run AsciiArt.cs . Tentokrát hostitel zjistí, dotnet že spustitelný soubor je aktuální, a spustí spustitelný soubor bez jeho opětovného sestavení. Nezobrazuje se žádný výstup sestavení.

Předchozí kroky ukazují, že souborové aplikace nejsou soubory skriptů. Jedná se o zdrojové soubory jazyka C#, které jsou vytvořené pomocí vygenerovaného souboru projektu v dočasné složce. Jeden z řádků výstupu zobrazených při vytváření programu by měl vypadat přibližně takto (ve Windows):

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

Na platformách unix je výstupní složka podobná této:

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

Tento výstup vám řekne, kam se umístí dočasné soubory a výstupy sestavení. V tomto kurzu se při každé úpravě zdrojového souboru dotnet hostitel před spuštěním aktualizuje spustitelný soubor.

Souborové aplikace jsou běžné programy jazyka C#. Jediným omezením je, že se musí zapsat do jednoho zdrojového souboru. Jako vstupní bod můžete použít příkazy nejvyšší úrovně nebo klasickou Main metodu. Můžete deklarovat libovolné typy: třídy, rozhraní a struktury. Algoritmy můžete strukturovat v programu založeném na souborech stejně jako v jakémkoli programu jazyka C#. Můžete dokonce deklarovat více oborů názvů pro uspořádání kódu. Pokud zjistíte, že se pro jeden soubor příliš rozrůstá program založený na souborech, můžete ho převést na program založený na projektu a rozdělit zdroj na více souborů. Souborové aplikace jsou skvělým nástrojem pro vytváření prototypů. Můžete začít experimentovat s minimální režií a prokázat koncepty a vytvářet algoritmy.

Podpora pro Unix shebang (#!)

Poznámka:

#! Podpora direktiv se vztahuje pouze na platformy unix. Pro Windows neexistuje podobná direktiva pro přímé spuštění programu v jazyce C#. Ve Windows musíte použít dotnet run na příkazovém řádku.

V unixu můžete spouštět aplikace založené na souborech přímo tak, že místo toho zadáte název zdrojového souboru na příkazovém dotnet runřádku . Potřebujete provést dvě změny:

  1. Nastavte oprávnění ke spuštění zdrojového souboru:

    chmod +x AsciiArt.cs
    
  2. Přidejte direktivu shebang (#!) jako první řádek AsciiArt.cs souboru:

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

Umístění dotnet se může lišit v různých instalacích unixu. Pomocí příkazu which dotnet vyhledejte dotnet hostitele ve vašem prostředí.

Alternativně můžete použít #!/usr/bin/env dotnet k automatickému určení cesty dotnet z proměnné prostředí PATH.

#!/usr/bin/env dotnet

Po provedení těchto dvou změn můžete program spustit přímo z příkazového řádku:

./AsciiArt.cs

Pokud chcete, můžete rozšíření odebrat, abyste místo toho mohli psát ./AsciiArt . Do zdrojového #! souboru můžete přidat i v případě, že používáte Windows. Příkazový řádek Windows nepodporuje #!, ale kompilátor jazyka C# umožňuje direktivu v souborových aplikacích na všech platformách.

Čtení argumentů příkazového řádku

Teď na příkazovém řádku napište všechny argumenty do výstupu.

  1. Nahraďte aktuální obsah AsciiArt.cs následujícím kódem:

    if (args.Length > 0)
    {
        string message = string.Join(' ', args);
        Console.WriteLine(message);
    }
    
  2. Tuto verzi můžete spustit zadáním následujícího příkazu:

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

    Tato -- možnost označuje, že všechny následující argumenty příkazů by měly být předány do programu AsciiArt. Argumenty This is the command line. jsou předány jako pole řetězců, kde každý řetězec je jedno slovo: This, is, the, command, a line..

Tato verze ukazuje tyto nové koncepty:

  • Argumenty příkazového řádku jsou předány programu pomocí předdefinované proměnné args. Proměnná args je pole řetězců: string[]. Pokud je délka args 0, znamená to, že nebyly zadány žádné argumenty. V opačném případě je každé slovo v seznamu argumentů uloženo v odpovídající položce v poli.
  • Metoda string.Join spojí více řetězců do jednoho řetězce se zadaným oddělovačem. V tomto případě je oddělovač jedinou mezerou.
  • Console.WriteLine zapíše řetězec do standardní výstupní konzoly následované novým řádkem.

Zpracování standardního vstupu

Tento příkaz zpracovává správně argumenty příkazového řádku. Teď přidejte kód pro zpracování čtení vstupu ze standardního vstupu (stdin) místo argumentů příkazového řádku.

  1. Do příkazu, který jste přidali v předchozím kódu, přidejte následující else klauzuli if :

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

    Předchozí kód přečte vstup konzoly, dokud se nečte prázdný řádek nebo null přečte. (Metoda Console.ReadLine vrátí null , pokud je vstupní datový proud zavřený zadáním ctrl+C.)

  2. Otestujte čtení standardního vstupu vytvořením nového textového souboru ve stejné složce. Pojmenujte soubor input.txt a přidejte následující řádky:

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

    Řádky nechte krátké, aby se správně naformátovali, když přidáte funkci pro použití obrázků ASCII.

  3. Spusťte program znovu.

    S Bashem:

    cat input.txt | dotnet run AsciiArt.cs
    

    Nebo pomocí PowerShellu:

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

Teď může program přijímat argumenty příkazového řádku nebo standardní vstup.

Zápis výstupu ASCII Art

Dále přidejte balíček, který podporuje ASCII art, Colorful.Console. Pokud chcete přidat balíček do programu založeného na souborech, použijte direktivu #:package .

  1. Do souboru AsciiArt.cs přidejte následující direktivu #! :

    #:package Colorful.Console@1.2.15
    

    Důležité

    1.2.15 Verze byla nejnovější verzí Colorful.Console balíčku při poslední aktualizaci tohoto kurzu. Na stránce NuGet balíčku vyhledejte nejnovější verzi a ujistěte se, že používáte verzi balíčku s nejnovějšími opravami zabezpečení.

  2. Změňte místo toho řádky, které volají Console.WriteLine metodu Colorful.Console.WriteAscii :

    async Task WriteAsciiArt(AsciiMessageOptions options)
    {
        foreach (string message in options.Messages)
        {
            Colorful.Console.WriteAscii(message);
            await Task.Delay(options.Delay);
        }
    }
    
  3. Spusťte program a místo zopakovaného textu uvidíte výstup obrázku ASCII.

Možnosti příkazů procesu

Teď přidáme analýzu příkazového řádku. Aktuální verze zapisuje každé slovo jako jiný řádek výstupu. Argumenty příkazového řádku, které jste přidali, podporují dvě funkce:

  1. Citace více slov, která by se měla napsat na jeden řádek:

    AsciiArt.cs "This is line one" "This is another line" "This is the last line"
    
  2. --delay Přidejte mezi každý řádek možnost pozastavení:

    AsciiArt.cs --delay 1000
    

Uživatelé by měli mít možnost používat oba argumenty společně.

Většina aplikací příkazového řádku potřebuje analyzovat argumenty příkazového řádku, aby bylo možné efektivně zpracovávat možnosti, příkazy a vstupy uživatelů. KnihovnaSystem.CommandLine poskytuje komplexní možnosti pro zpracování příkazů, dílčích příkazů, možností a argumentů, takže se můžete soustředit na to, co vaše aplikace dělá, a ne na mechaniku parsování vstupu příkazového řádku.

Knihovna System.CommandLine nabízí několik klíčových výhod:

  • Automatické generování a ověřování textu nápovědy
  • Podpora konvencí příkazového řádku POSIX a Windows
  • Integrované možnosti dokončování tabulátoru
  • Konzistentní chování analýzy napříč aplikacemi
  1. System.CommandLine Přidejte balíček. Přidejte tuto direktivu za existující direktivu balíčku:

    #:package System.CommandLine@2.0.0
    

    Důležité

    2.0.0 Verze byla nejnovější verzí, kdy byl tento kurz naposledy aktualizován. Pokud je k dispozici novější verze, použijte nejnovější verzi, abyste měli jistotu, že máte nejnovější balíčky zabezpečení. Na stránce NuGet balíčku vyhledejte nejnovější verzi a ujistěte se, že používáte verzi balíčku s nejnovějšími opravami zabezpečení.

  2. Přidejte potřebné příkazy using v horní části souboru (za direktivy #! a #:package direktivy):

    using System.CommandLine;
    using System.CommandLine.Parsing;
    
  3. Definujte možnost zpoždění a argument zpráv. Přidejte následující kód, který vytvoří CommandLine.Option objekty CommandLine.Argument , které představují možnost a argument příkazového řádku:

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

    V aplikacích příkazového řádku obvykle začínají možnostmi -- (dvojitá pomlčka) a můžou přijímat argumenty. Možnost --delay přijímá celočíselnou hodnotu, která určuje zpoždění v milisekundách. Definuje messagesArgument , jak se všechny zbývající tokeny po parsování možností parsují jako text. Každý token se stane samostatným řetězcem v poli, ale text může obsahovat více slov v jednom tokenu. Například "This is one message" se stane jedním tokenem, zatímco This is four tokens se stanou čtyřmi samostatnými tokeny.

    Předchozí kód definuje typ argumentu --delay pro možnost a že argumenty jsou pole hodnot string . Tato aplikace má pouze jeden příkaz, takže použijete kořenový příkaz.

  4. Vytvořte kořenový příkaz a nakonfigurujte ho s možností a argumentem. Přidejte argument a možnost do kořenového příkazu:

    RootCommand rootCommand = new("Ascii Art file-based program sample");
    
    rootCommand.Options.Add(delayOption);
    rootCommand.Arguments.Add(messagesArgument);
    
  5. Přidejte kód pro analýzu argumentů příkazového řádku a zpracování chyb. Tento kód ověří argumenty příkazového řádku a uloží parsované argumenty v objektu System.CommandLine.ParseResult :

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

Předchozí kód ověří všechny argumenty příkazového řádku. Pokud se ověření nezdaří, chyby se zapíšou do konzoly a aplikace se ukončí.

Použití analyzovaných výsledků příkazového řádku

Teď dokončete aplikaci, aby používala parsované možnosti a napsala výstup. Nejprve definujte záznam, který bude obsahovat parsované možnosti. Aplikace založené na souborech můžou obsahovat deklarace typů, jako jsou záznamy a třídy. Musí být po všech příkazech nejvyšší úrovně a místních funkcích.

  1. record Přidejte deklaraci pro uložení zpráv a hodnoty možnosti zpoždění:

    public record AsciiMessageOptions(string[] Messages, int Delay);
    
  2. Před deklaraci záznamu přidejte následující místní funkci. Tato metoda zpracovává argumenty příkazového řádku i standardní vstup a vrací novou instanci záznamu:

    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. Vytvořte místní funkci pro zápis artu ASCII se zadaným zpožděním. Tato funkce zapíše každou zprávu v záznamu se zadaným zpožděním mezi jednotlivými zprávami:

    async Task WriteAsciiArt(AsciiMessageOptions options)
    {
        foreach (string message in options.Messages)
        {
            Colorful.Console.WriteAscii(message);
            await Task.Delay(options.Delay);
        }
    }
    
  4. Nahraďte klauzuli if , kterou jste napsali dříve, následujícím kódem, který zpracovává argumenty příkazového řádku a zapisuje výstup:

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

Vytvořili record jste typ, který poskytuje strukturu pro parsované možnosti a argumenty příkazového řádku. Nové místní funkce vytvoří instanci záznamu a použije záznam k zápisu výstupu ASCII art.

Otestování konečné aplikace

Otestujte aplikaci spuštěním několika různých příkazů. Pokud máte potíže, tady je hotová ukázka pro porovnání s tím, co jste vytvořili:

#!/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);

V tomto kurzu jste se naučili vytvořit souborový program, ve kterém program sestavíte v jednom souboru C#. Tyto programy nepoužívají soubor projektu a mohou používat direktivu #! v systémech unix. Studenti můžou tyto programy vytvářet po vyzkoušení našich online kurzů a před vytvořením větších aplikací založených na projektu. Souborové aplikace jsou také skvělou platformou pro nástroje příkazového řádku.