Udostępnij za pomocą


Samouczek: tworzenie programów w języku C# opartych na plikach

Aplikacje oparte na plikach to programy zawarte w jednym *.cs pliku, które są kompilowane i uruchamiane bez odpowiedniego pliku projektu (*.csproj). Aplikacje oparte na plikach są idealne do nauki języka C#, ponieważ mają mniej złożoności: cały program jest przechowywany w jednym pliku. Aplikacje oparte na plikach są również przydatne do kompilowania narzędzi wiersza polecenia. Na platformach Unix aplikacje oparte na plikach można uruchamiać przy użyciu #!dyrektyw (shebang). W tym samouczku nauczysz się następujących rzeczy:

  • Utwórz program oparty na plikach.
  • Dodaj obsługę narzędzia Shebang (#!) systemu Unix.
  • Odczytywanie argumentów wiersza polecenia.
  • Obsługa standardowych danych wejściowych.
  • Pisanie danych wyjściowych grafiki ASCII.
  • Przetwarzanie argumentów wiersza polecenia.
  • Użyj przeanalizowanych wyników wiersza polecenia.
  • Przetestuj ostateczną aplikację.

Tworzysz program oparty na plikach, który zapisuje tekst jako sztukę ASCII. Aplikacja jest zawarta w jednym pliku, używa pakietów NuGet, które implementują niektóre podstawowe funkcje.

Wymagania wstępne

Tworzenie programu opartego na plikach

  1. Otwórz program Visual Studio Code i utwórz nowy plik o nazwie AsciiArt.cs. Wprowadź następujący tekst:

    Console.WriteLine("Hello, world!");
    
  2. Zapisz plik. Następnie otwórz zintegrowany terminal w programie Visual Studio Code i wpisz:

    dotnet run AsciiArt.cs
    

Przy pierwszym uruchomieniu tego programu dotnet host tworzy plik wykonywalny z pliku źródłowego, przechowuje artefakty kompilacji w folderze tymczasowym, a następnie uruchamia utworzony plik wykonywalny. Możesz zweryfikować to środowisko, wpisując dotnet run AsciiArt.cs ponownie. Tym razem host określa, dotnet że plik wykonywalny jest bieżący i uruchamia plik wykonywalny bez ponownego utworzenia pliku wykonywalnego. Nie widzisz żadnych danych wyjściowych kompilacji.

W poprzednich krokach pokazano, że aplikacje oparte na plikach nie są plikami skryptów. Są to pliki źródłowe języka C#, które są tworzone przy użyciu wygenerowanego pliku projektu w folderze tymczasowym. Jeden z wierszy danych wyjściowych wyświetlanych podczas tworzenia programu powinien wyglądać mniej więcej tak (w systemie Windows):

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

Na platformach unix folder wyjściowy jest podobny do następującego:

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

Te dane wyjściowe informują o tym, gdzie są umieszczane pliki tymczasowe i dane wyjściowe kompilacji. W ramach tego samouczka podczas edytowania pliku źródłowego host aktualizuje plik dotnet wykonywalny przed jego uruchomieniem.

Aplikacje oparte na plikach to zwykłe programy w języku C#. Jedynym ograniczeniem jest to, że muszą być zapisywane w jednym pliku źródłowym. Jako punkt wejścia można użyć instrukcji najwyższego poziomu lub metody klasycznej Main . Można zadeklarować dowolne typy: klasy, interfejsy i struktury. Algorytmy można strukturę w programie opartym na plikach tak samo jak w dowolnym programie języka C#. Można nawet zadeklarować wiele przestrzeni nazw, aby zorganizować kod. Jeśli okaże się, że program oparty na plikach rośnie zbyt duży dla pojedynczego pliku, możesz przekonwertować go na program oparty na projekcie i podzielić źródło na wiele plików. Aplikacje oparte na plikach to doskonałe narzędzie do tworzenia prototypów. Możesz rozpocząć eksperymentowanie z minimalnym obciążeniem, aby udowodnić koncepcje i algorytmy kompilacji.

Obsługa systemu Unix shebang (#!)

Uwaga / Notatka

#! Obsługa dyrektyw dotyczy tylko platform unix. Nie ma podobnej dyrektywy dla systemu Windows do bezpośredniego wykonywania programu w języku C#. W systemie Windows należy użyć dotnet run polecenia w wierszu polecenia.

W systemie UNIX można uruchamiać aplikacje oparte na plikach bezpośrednio, wpisując nazwę pliku źródłowego w wierszu polecenia zamiast dotnet run. Musisz wprowadzić dwie zmiany:

  1. Ustaw uprawnienia wykonywania w pliku źródłowym:

    chmod +x AsciiArt.cs
    
  2. Dodaj dyrektywę shebang (#!) jako pierwszy wiersz AsciiArt.cs pliku:

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

Lokalizacja dotnet programu może być inna w różnych instalacjach systemu UNIX. Użyj polecenia which dotnet , aby zlokalizować dotnet hosta w swoim środowisku.

Alternatywnie możesz użyć #!/usr/bin/env dotnet, aby automatycznie ustalić ścieżkę dotnet ze zmiennej środowiskowej PATH.

#!/usr/bin/env dotnet

Po wprowadzeniu tych dwóch zmian można uruchomić program bezpośrednio z poziomu wiersza polecenia:

./AsciiArt.cs

Jeśli wolisz, możesz usunąć rozszerzenie, aby można było wpisać ./AsciiArt zamiast tego. Możesz dodać element #! do pliku źródłowego, nawet jeśli używasz systemu Windows. Wiersz polecenia systemu Windows nie obsługuje #!programu , ale kompilator języka C# umożliwia korzystanie z tej dyrektywy w aplikacjach opartych na plikach na wszystkich platformach.

Odczytywanie argumentów wiersza polecenia

Teraz zapisz wszystkie argumenty w wierszu polecenia w danych wyjściowych.

  1. Zastąp bieżącą zawartość AsciiArt.cs następującym kodem:

    if (args.Length > 0)
    {
        string message = string.Join(' ', args);
        Console.WriteLine(message);
    }
    
  2. Tę wersję można uruchomić, wpisując następujące polecenie:

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

    Opcja -- wskazuje, że wszystkie następujące argumenty poleceń powinny zostać przekazane do programu AsciiArt. Argumenty This is the command line. są przekazywane jako tablica ciągów, gdzie każdy ciąg jest jednym wyrazem: This, , isthe, command, i line..

Ta wersja demonstruje następujące nowe pojęcia:

  • Argumenty wiersza polecenia są przekazywane do programu przy użyciu wstępnie zdefiniowanej zmiennej args. Zmienna args jest tablicą ciągów: string[]. Jeśli długość args wynosi 0, oznacza to, że nie podano żadnych argumentów. W przeciwnym razie każde słowo na liście argumentów jest przechowywane w odpowiednim wpisie w tablicy.
  • Metoda string.Join łączy wiele ciągów w jeden ciąg z określonym separatorem. W tym przypadku separator jest jedną spacją.
  • Console.WriteLine Zapisuje ciąg w standardowej konsoli wyjściowej, a następnie nowy wiersz.

Obsługa standardowych danych wejściowych

Obsługuje to poprawnie argumenty wiersza polecenia. Teraz dodaj kod do obsługi odczytywania danych wejściowych ze standardowych danych wejściowych (stdin) zamiast argumentów wiersza polecenia.

  1. Dodaj następującą else klauzulę do instrukcji if dodanej w poprzednim kodzie:

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

    Powyższy kod odczytuje dane wejściowe konsoli do momentu odczytania pustego wiersza lub elementu null . (Metoda Console.ReadLine zwraca null , jeśli strumień wejściowy jest zamknięty, wpisując ctrl+C.)

  2. Przetestuj odczytywanie standardowych danych wejściowych, tworząc nowy plik tekstowy w tym samym folderze. Nadaj plikowi input.txt nazwę i dodaj następujące wiersze:

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

    Zachowaj krótkie wiersze, aby formatować je poprawnie podczas dodawania funkcji do używania grafiki ASCII.

  3. Uruchom ponownie program.

    Za pomocą powłoki bash:

    cat input.txt | dotnet run AsciiArt.cs
    

    Lub za pomocą programu PowerShell:

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

Teraz program może zaakceptować argumenty wiersza polecenia lub standardowe dane wejściowe.

Pisanie danych wyjściowych ASCII Art

Następnie dodaj pakiet, który obsługuje sztukę ASCII, Colorful.Console. Aby dodać pakiet do programu opartego na plikach, należy użyć #:package dyrektywy .

  1. Dodaj następującą dyrektywę po #! dyrektywie w pliku AsciiArt.cs:

    #:package Colorful.Console@1.2.15
    

    Ważne

    Wersja 1.2.15 była najnowszą wersją Colorful.Console pakietu, gdy ten samouczek został ostatnio zaktualizowany. Sprawdź stronę NuGet pakietu dla najnowszej wersji, aby upewnić się, że używasz wersji pakietu z najnowszymi poprawkami zabezpieczeń.

  2. Zmień wiersze, które wywołają Console.WriteLine metodę Colorful.Console.WriteAscii , aby zamiast tego użyć metody:

    async Task WriteAsciiArt(AsciiMessageOptions options)
    {
        foreach (string message in options.Messages)
        {
            Colorful.Console.WriteAscii(message);
            await Task.Delay(options.Delay);
        }
    }
    
  3. Uruchom program i zobaczysz dane wyjściowe grafiki ASCII zamiast tekstu.

Opcje polecenia procesu

Następnie dodajmy analizowanie wiersza polecenia. Bieżąca wersja zapisuje każde słowo jako inny wiersz danych wyjściowych. Dodane argumenty wiersza polecenia obsługują dwie funkcje:

  1. Cytuj wiele wyrazów, które powinny być zapisywane w jednym wierszu:

    AsciiArt.cs "This is line one" "This is another line" "This is the last line"
    
  2. Dodaj opcję wstrzymania --delay między poszczególnymi wierszami:

    AsciiArt.cs --delay 1000
    

Użytkownicy powinni mieć możliwość używania obu argumentów razem.

Większość aplikacji wiersza polecenia musi analizować argumenty wiersza polecenia, aby efektywnie obsługiwać opcje, polecenia i dane wejściowe użytkownika. BibliotekaSystem.CommandLine udostępnia kompleksowe możliwości obsługi poleceń, poleceń podrzędnych, opcji i argumentów, co pozwala skoncentrować się na tym, co robi aplikacja, a nie na mechaniki analizowania danych wejściowych wiersza polecenia.

Biblioteka System.CommandLine oferuje kilka kluczowych korzyści:

  • Automatyczne generowanie tekstu i walidacja tekstu.
  • Obsługa konwencji wiersza polecenia POSIX i Windows.
  • Wbudowane możliwości uzupełniania kart.
  • Spójne zachowanie analizowania w aplikacjach.
  1. System.CommandLine Dodaj pakiet. Dodaj tę dyrektywę po istniejącej dyrektywie pakietu:

    #:package System.CommandLine@2.0.0
    

    Ważne

    Wersja 2.0.0 była najnowszą wersją, gdy ten samouczek został ostatnio zaktualizowany. Jeśli jest dostępna nowsza wersja, użyj najnowszej wersji, aby upewnić się, że masz najnowsze pakiety zabezpieczeń. Sprawdź stronę NuGet pakietu dla najnowszej wersji, aby upewnić się, że używasz wersji pakietu z najnowszymi poprawkami zabezpieczeń.

  2. Dodaj niezbędne instrukcje using w górnej części pliku (po dyrektywach #! i #:package ):

    using System.CommandLine;
    using System.CommandLine.Parsing;
    
  3. Zdefiniuj opcję opóźnienia i argument komunikatów. Dodaj następujący kod, aby utworzyć CommandLine.Option obiekty i CommandLine.Argument reprezentujące opcję wiersza polecenia i argument:

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

    W aplikacjach wiersza polecenia opcje zazwyczaj zaczynają się -- od (podwójna kreska) i mogą akceptować argumenty. Opcja --delay akceptuje argument liczby całkowitej, który określa opóźnienie w milisekundach. Określa messagesArgument , jak wszystkie pozostałe tokeny po opcjach są analizowane jako tekst. Każdy token staje się oddzielnym ciągiem w tablicy, ale tekst może być cytowany w celu uwzględnienia wielu wyrazów w jednym tokenie. Na przykład "This is one message" staje się pojedynczym tokenem, a jednocześnie This is four tokens staje się czterema oddzielnymi tokenami.

    Powyższy kod definiuje typ argumentu --delay dla opcji i że argumenty są tablicą string wartości. Ta aplikacja ma tylko jedno polecenie, więc używasz głównego polecenia.

  4. Utwórz polecenie główne i skonfiguruj je za pomocą opcji i argumentu. Dodaj argument i opcję do głównego polecenia:

    RootCommand rootCommand = new("Ascii Art file-based program sample");
    
    rootCommand.Options.Add(delayOption);
    rootCommand.Arguments.Add(messagesArgument);
    
  5. Dodaj kod, aby przeanalizować argumenty wiersza polecenia i obsłużyć wszelkie błędy. Ten kod weryfikuje argumenty wiersza polecenia i przechowuje przeanalizowane argumenty w System.CommandLine.ParseResult obiekcie:

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

Powyższy kod weryfikuje wszystkie argumenty wiersza polecenia. Jeśli walidacja zakończy się niepowodzeniem, błędy są zapisywane w konsoli programu , a aplikacja kończy działanie.

Korzystanie z analizowanych wyników wiersza polecenia

Teraz zakończ aplikację, aby użyć przeanalizowanych opcji i zapisać dane wyjściowe. Najpierw zdefiniuj rekord do przechowywania analizowanych opcji. Aplikacje oparte na plikach mogą zawierać deklaracje typów, takie jak rekordy i klasy. Muszą one znajdować się po wszystkich instrukcjach najwyższego poziomu i funkcjach lokalnych.

  1. Dodaj deklarację record do przechowywania komunikatów i wartości opcji opóźnienia:

    public record AsciiMessageOptions(string[] Messages, int Delay);
    
  2. Dodaj następującą funkcję lokalną przed deklaracją rekordu. Ta metoda obsługuje zarówno argumenty wiersza polecenia, jak i standardowe dane wejściowe i zwraca nowe wystąpienie rekordu:

    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. Utwórz funkcję lokalną, aby napisać grafikę ASCII z określonym opóźnieniem. Ta funkcja zapisuje każdy komunikat w rekordzie z określonym opóźnieniem między poszczególnymi komunikatami:

    async Task WriteAsciiArt(AsciiMessageOptions options)
    {
        foreach (string message in options.Messages)
        {
            Colorful.Console.WriteAscii(message);
            await Task.Delay(options.Delay);
        }
    }
    
  4. Zastąp zapisaną if wcześniej klauzulę następującym kodem, który przetwarza argumenty wiersza polecenia i zapisuje dane wyjściowe:

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

Utworzono record typ, który zapewnia strukturę analizowanych opcji wiersza polecenia i argumentów. Nowe funkcje lokalne tworzą wystąpienie rekordu i używają rekordu do zapisywania danych wyjściowych sztuki ASCII.

Testowanie ostatecznej aplikacji

Przetestuj aplikację, uruchamiając kilka różnych poleceń. Jeśli masz problem, oto gotowy przykład do porównania z utworzonymi elementami:

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

W tym samouczku przedstawiono tworzenie programu opartego na plikach, w którym program jest kompilny w jednym pliku C#. Te programy nie używają pliku projektu i mogą używać #! dyrektywy w systemach unix. Uczniowie mogą tworzyć te programy po wypróbowaniu naszych samouczków online i przed utworzeniem większych aplikacji opartych na projekcie. Aplikacje oparte na plikach są również doskonałą platformą dla narzędzi wiersza polecenia.