Dela via


Självstudie: Skapa filbaserade C#-program

Filbaserade appar är program som finns i en enda *.cs fil som du skapar och kör utan motsvarande projektfil (*.csproj). Filbaserade appar är idealiska för att lära sig C# eftersom de har mindre komplexitet: Hela programmet lagras i en enda fil. Filbaserade appar är också användbara för att skapa kommandoradsverktyg. På Unix-plattformar kan du köra filbaserade appar med hjälp #! av (shebang)- direktiv. I den här handledningen kommer du att:

  • Skapa ett filbaserat program.
  • Lägg till Stöd för Unix shebang (#!).
  • Läsa kommandoradsargument.
  • Hantera standardindata.
  • Skriv ASCII-konstutdata.
  • Bearbeta kommandoradsargument.
  • Använd parsade kommandoradsresultat.
  • Testa det slutliga programmet.

Du skapar ett filbaserat program som skriver text som ASCII-konst. Appen finns i en enda fil, använder NuGet-paket och implementerar kärnfunktioner.

Förutsättningar

Skapa ett filbaserat program

  1. Öppna Visual Studio Code och skapa en ny fil med namnet AsciiArt.cs. Ange följande text:

    Console.WriteLine("Hello, world!");
    
  2. Spara filen. Öppna sedan den integrerade terminalen i Visual Studio Code och skriv:

    dotnet run AsciiArt.cs
    

Första gången du kör det här programmet dotnet skapar värden den körbara filen från din källfil, lagrar byggrelaterade filer i en tillfällig mapp och därefter körs den skapade körbara filen. Du kan verifiera den här upplevelsen genom att dotnet run AsciiArt.cs skriva igen. Den här gången dotnet avgör värden att den körbara filen är aktuell och kör den körbara filen utan att skapa den igen. Du ser inga build-utdata.

Föregående steg visar att filbaserade appar inte är skriptfiler. Det är C#-källfiler som hosten dotnet skapar med hjälp av en genererad projektfil i en tillfällig mapp. En av de utdatarader som visas när du skapar programmet bör se ut ungefär så här (i Windows):

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

På Unix-plattformar liknar utdatamappen följande:

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

Utdata anger var de temporära filerna och byggutdata placeras. I den här självstudien, när du redigerar källfilen, dotnet uppdaterar värden den körbara filen innan den körs.

Filbaserade appar är vanliga C#-program. Den enda begränsningen är att du måste skriva dem i en källfil. Du kan använda toppnivåinstruktioner eller en klassisk Main metod som startpunkt. Du kan deklarera alla typer: klasser, gränssnitt och structs. Du kan strukturera algoritmerna i ett filbaserat program på samma sätt som i alla C#-program. Du kan till och med deklarera flera namnområden för att organisera koden. Om du upptäcker att ett filbaserat program blir för stort för en enskild fil kan du konvertera det till ett projektbaserat program och dela upp källan i flera filer. Filbaserade appar är ett bra prototypverktyg. Du kan börja experimentera med minimala omkostnader för att bevisa begrepp och bygga algoritmer.

Stöd för Unix shebang (#!)

Anmärkning

Stöd för #! direktiv gäller endast på Unix-plattformar. Det finns inget liknande direktiv för Windows att köra ett C#-program direkt. I Windows måste du använda dotnet run på kommandoraden.

På Unix kan du köra filbaserade appar direkt. I stället för att använda dotnet runskriver du källfilnamnet på kommandoraden. Du måste göra två ändringar:

  1. Ange körningsbehörigheter för källfilen:

    chmod +x AsciiArt.cs
    
  2. Lägg till ett shebang-direktiv (#!) som den första raden i AsciiArt.cs filen:

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

Platsen för dotnet kan vara olika för olika Unix-installationer. Använd kommandot which dotnet för att hitta dotnet host i din miljö.

Alternativt kan du använda #!/usr/bin/env dotnet för att automatiskt bestämma dotnet-sökvägen ur PATH-miljövariabeln.

#!/usr/bin/env dotnet

När du har gjort dessa två ändringar kan du köra programmet direkt från kommandoraden:

./AsciiArt.cs

Om du vill kan du ta bort tillägget så att du kan skriva ./AsciiArt i stället. Du kan lägga till i #! källfilen även om du använder Windows. Windows-kommandoraden stöder #!inte , men C#-kompilatorn tillåter det direktivet i filbaserade appar på alla plattformar.

Läsa kommandoradsargument

Skriv nu alla argument på kommandoraden till utdata.

  1. Ersätt det aktuella innehållet i AsciiArt.cs med följande kod:

    if (args.Length > 0)
    {
        string message = string.Join(' ', args);
        Console.WriteLine(message);
    }
    
  2. Du kan köra den här versionen genom att skriva följande kommando:

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

    Alternativet -- anger att alla följande kommandoargument ska skickas till AsciiArt-programmet. Argumenten This is the command line. skickas som en matris med strängar, där varje sträng är ett ord: This, is, the, commandoch line..

Den här versionen visar dessa nya begrepp:

  • Den fördefinierade variabeln args skickar kommandoradsargumenten till programmet. Variabeln args är en matris med strängar: string[]. Om längden args på är 0 angavs inga argument. Annars lagras varje ord i argumentlistan i motsvarande post i matrisen.
  • Metoden string.Join kopplar flera strängar till en enda sträng med den angivna avgränsaren. I det här fallet är avgränsaren ett enda blanksteg.
  • Console.WriteLine skriver strängen till standardutdatakonsolen följt av en ny rad.

Hantera standardindata

Föregående kod hanterar kommandoradsargument korrekt. Lägg nu till koden för att hantera läsindata från standardindata (stdin) i stället för kommandoradsargument.

  1. Lägg till följande else sats i -instruktionen if som du lade till i föregående kod:

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

    Föregående kod läser konsolens indata tills antingen en tom rad eller en null lästs. (Metoden Console.ReadLine returnerar null om indataströmmen stängs genom att skriva ctrl+C.)

  2. Testa att läsa standardindata genom att skapa en ny textfil i samma mapp. Namnge filen input.txt och lägg till följande rader:

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

    Håll linjerna korta så att de formateras korrekt när du lägger till funktionen för att använda ASCII-konst.

  3. Kör programmet igen.

    Genom att använda bash:

    cat input.txt | dotnet run AsciiArt.cs
    

    Eller genom att använda PowerShell:

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

Nu kan programmet acceptera antingen kommandoradsargument eller standardindata.

Skriv ut ASCII-konst

Lägg sedan till ett paket som stöder ASCII-konst, Colorful.Console. Om du vill lägga till ett paket i ett filbaserat program använder du #:package direktivet.

  1. Lägg till följande direktiv efter #! direktivet i filen AsciiArt.cs :

    #:package Colorful.Console@1.2.15
    

    Viktigt!

    Versionen 1.2.15 var den senaste versionen av Colorful.Console paketet när den här självstudien senast uppdaterades. Kontrollera paketets NuGet-sida för den senaste versionen för att se till att du använder en paketversion med de senaste säkerhetskorrigeringarna.

  2. Ändra de rader som anropar Console.WriteLine för att använda Colorful.Console.WriteAscii metoden i stället:

    Colorful.Console.WriteAscii(line);
    
  3. Kör programmet. Du ser ASCII-konstutdata i stället för ekade text.

Processkommandoalternativ

Lägg sedan till kommandoradsparsning. Den aktuella versionen skriver varje ord som en annan rad med utdata. De kommandoradsargument som du lägger till stöder två funktioner:

  1. Citera flera ord som ska skrivas på en rad:

    AsciiArt.cs "This is line one" "This is another line" "This is the last line"
    
  2. Lägg till ett --delay alternativ för att pausa mellan varje rad:

    AsciiArt.cs --delay 1000
    

Användarna kan använda båda argumenten tillsammans.

De flesta kommandoradsprogram behöver parsa kommandoradsargument för att hantera alternativ, kommandon och användarindata effektivt. BiblioteketSystem.CommandLine innehåller omfattande funktioner för att hantera kommandon, underkommandon, alternativ och argument. Du kan koncentrera dig på vad programmet gör i stället för mekaniken för att parsa indata från kommandoraden.

Biblioteket System.CommandLine har flera viktiga fördelar:

  • Automatisk textgenerering och validering av hjälp.
  • Stöd för POSIX- och Windows-kommandoradskonventioner.
  • Inbyggda funktioner för flikkomplettering.
  • Konsekvent parsningsbeteende mellan program.
  1. System.CommandLine Lägg till paketet. Lägg till det här direktivet efter det befintliga paketdirektivet:

    #:package System.CommandLine@2.0.0
    

    Viktigt!

    Versionen 2.0.0 var den senaste versionen när den här självstudien senast uppdaterades. Om det finns en nyare version tillgänglig använder du den senaste versionen för att se till att du har de senaste säkerhetspaketen. Kontrollera paketets NuGet-sida för den senaste versionen för att se till att du använder en paketversion med de senaste säkerhetskorrigeringarna.

  2. Lägg till nödvändiga användningsinstruktioner överst i filen (efter direktiven #! och #:package ):

    using System.CommandLine;
    using System.CommandLine.Parsing;
    
  3. Definiera argumentet fördröjning och meddelanden. Lägg till följande kod för att skapa objekten CommandLine.Option och CommandLine.Argument för att representera kommandoradsalternativet och argumentet:

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

    I kommandoradsprogram börjar alternativen vanligtvis med -- (dubbel bindestreck) och kan acceptera argument. Alternativet --delay accepterar ett heltalsargument som anger fördröjningen i millisekunder. Definierar messagesArgument hur eventuella återstående token efter alternativ parsas som text. Varje token blir en separat sträng i matrisen, men text kan citeras för att inkludera flera ord i en token. Till exempel "This is one message" blir en enda token, medan This is four tokens blir fyra separata token.

    Föregående kod definierar argumenttypen för --delay alternativet och att argumenten är en matris med string värden. Det här programmet har bara ett kommando, så du använder rotkommandot.

  4. Skapa ett rotkommando och konfigurera det med alternativet och argumentet. Lägg till argumentet och alternativet i rotkommandot:

    RootCommand rootCommand = new("Ascii Art file-based program sample");
    
    rootCommand.Options.Add(delayOption);
    rootCommand.Arguments.Add(messagesArgument);
    
  5. Lägg till koden för att parsa kommandoradsargumenten och hantera eventuella fel. Den här koden validerar kommandoradsargumenten och lagrar parsade argument i System.CommandLine.ParseResult objektet:

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

Föregående kod verifierar alla kommandoradsargument. Om verifieringen misslyckas skriver appen fel till konsolen och avslutas.

Använda parsade kommandoradsresultat

Slutför nu appen för att använda de parsade alternativen och skriva utdata. Definiera först en post som ska innehålla de parsade alternativen. Filbaserade appar kan innehålla typdeklarationer, till exempel poster och klasser. De måste vara efter alla toppnivåinstruktioner och lokala funktioner.

  1. Lägg till en record deklaration för att lagra meddelandena och alternativet för fördröjning:

    public record AsciiMessageOptions(string[] Messages, int Delay);
    
  2. Lägg till följande lokala funktion före postdeklarationen. Den här metoden hanterar både kommandoradsargument och standardindata och returnerar en ny postinstans:

    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. Skapa en lokal funktion för att skriva ASCII-konsten med den angivna fördröjningen. Den här funktionen skriver varje meddelande i posten med den angivna fördröjningen mellan varje meddelande:

    async Task WriteAsciiArt(AsciiMessageOptions options)
    {
        foreach (string message in options.Messages)
        {
            Colorful.Console.WriteAscii(message);
            await Task.Delay(options.Delay);
        }
    }
    
  4. if Ersätt satsen som du skrev tidigare med följande kod som bearbetar kommandoradsargumenten och skriver utdata:

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

Du har skapat en record typ som ger struktur åt de parsade kommandoradsalternativen och argumenten. Nya lokala funktioner skapar en instans av posten och använder posten för att skriva ASCII-konstutdata.

Testa det slutliga programmet

Testa programmet genom att köra flera olika kommandon. Om du har problem kan du jämföra det här exemplet med det du har skapat:

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

I den här självstudien har du lärt dig att skapa ett filbaserat program, där du skapar programmet i en enda C#-fil. De här programmen använder inte en projektfil och kan använda #! direktivet om Unix-system. Elever kan skapa dessa program efter att ha provat våra online-självstudier och innan de skapar större projektbaserade appar. Filbaserade appar är också en bra plattform för kommandoradsverktyg.