共用方式為


教學課程:建置檔案型 C# 程式

檔案型應用程式 是包含在單一 *.cs 檔案中的程式,這些程式在沒有對應專案 (*.csproj) 檔案的情況下建置和執行。 基於文件的應用程序非常適合學習 C#,因為它們的複雜性較低:整個程序存儲在一個文件中。 基於文件的應用程序對於構建命令行實用程序也很有用。 在 Unix 平台上,檔案式應用程式可以使用(shebang)#!執行。 在本教學課程中,您會:

  • 建立檔案型程式。
  • 新增 Unix shebang (#!) 支援。
  • 讀取命令列引數。
  • 處理標準輸入。
  • 編寫 ASCII 藝術輸出。
  • 處理命令列引數。
  • 使用剖析的命令列結果。
  • 測試最終應用程序。

您建置一個檔案型程式,將文字寫入為 ASCII 藝術。 應用程式包含在單一檔案中,使用實作某些核心功能的 NuGet 套件。

先決條件

建立檔案型程式

  1. 開啟 Visual Studio Code 並建立名為 AsciiArt.cs的新檔案。 輸入下列文字:

    Console.WriteLine("Hello, world!");
    
  2. 儲存檔案。 然後,在 Visual Studio Code 中開啟整合終端並輸入:

    dotnet run AsciiArt.cs
    

第一次執行此程式時, dotnet 主機會從原始檔建置可執行檔,將建置成品儲存在暫存資料夾中,然後執行建立的可執行檔。 您可以再次輸入 dotnet run AsciiArt.cs 來驗證此體驗。 這一次, dotnet 主機會判斷可執行檔是目前的,並執行可執行檔,而不再次建置它。 您看不到任何建置輸出。

上述步驟示範檔案型應用程式不是腳本檔案。 它們是 C# 來源檔案,是使用暫存資料夾中產生的專案檔案建置。 建置程式時顯示的其中一行輸出應該如下所示 (在 Windows 上):

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

在 Unix 平台上,輸出資料夾類似於:

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

該輸出會告訴您暫存檔案和建置輸出的放置位置。 在本教學課程中,每當您編輯來源檔案時,主機都會 dotnet 在執行檔執行之前更新執行檔。

檔案型應用程式是一般的 C# 程式。 唯一的限制是它們必須寫入一個源文件中。 您可以使用最上層陳述式或傳統 Main 方法作為進入點。 您可以宣告任何類型:類別、介面和結構。 您可以在檔案型程式中建構演算法,就像在任何 C# 程式中一樣。 您甚至可以宣告多個命名空間來組織程式碼。 如果您發現檔案型程式對於單一檔案來說太大,您可以將其轉換為專案型程式,並將來源分割成多個檔案。 基於文件的應用程序是一個很棒的原型設計工具。 您可以以最小的額外負荷開始實驗,以證明概念並建置演算法。

Unix shebang (#!) 支援

備註

對指令的 #! 支援僅適用於 Unix 平台。 Windows 沒有類似的指令可直接執行 C# 程式。 在 Windows 上,您必須在命令列上使用 dotnet run

在 unix 上,您可以直接執行檔案型應用程式,在命令列上輸入來源檔案名稱,而不是 dotnet run。 您需要進行兩項變更:

  1. 設定來源檔案的 執行 權限:

    chmod +x AsciiArt.cs
    
  2. 新增 shebang (#!) 指令作為檔案的第一 AsciiArt.cs 行:

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

在不同的 unix 安裝上,的位置 dotnet 可能不同。 使用指令 which dotnet 在你的環境中定位 dotnet 主機。

或者,你也可以使用#!/usr/bin/env dotnet,自動從 PATH 環境變數中擷取 dotnet 的路徑:

#!/usr/bin/env dotnet

進行這兩項變更後,您可以直接從命令列執行程式:

./AsciiArt.cs

如果您願意,可以刪除擴展名,以便您可以輸入 ./AsciiArt 。 即使您使用 Windows,您也可以將 新增至 #! 來源檔案。 Windows 命令列不支援 #!,但 C# 編譯器允許在所有平台上的檔案型應用程式中使用該指示詞。

讀取命令列引數

現在,將命令列上的所有引數寫入輸出。

  1. 將目前 AsciiArt.cs 的內容取代為下列程式碼:

    if (args.Length > 0)
    {
        string message = string.Join(' ', args);
        Console.WriteLine(message);
    }
    
  2. 您可以輸入下列命令來執行此版本:

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

    -- 選項指出下列所有指令引數都應傳遞至 AsciiArt 程式。 引數This is the command line.會以字串陣列的形式傳遞,其中每個字串都是一個字:Thisisthecommandline.、 和 。

此版本示範了這些新概念:

  • 命令列引數會使用預先定義的變數 args傳遞至程式。 args變數是字串陣列:string[]。 如果長度為 args 0,則表示未提供任何引數。 否則,引數清單上的每個單字都會儲存在陣列中的對應專案中。
  • string.Join 方法會使用指定的分隔符號將多個字串聯結成單一字串。 在此情況下,分隔符號是單一空格。
  • Console.WriteLine 將字串寫入標準輸出主控台,後面接著換行。

處理標準輸入

這可以正確處理命令列引數。 現在,新增程式碼來處理從標準輸入 (stdin) 讀取輸入,而不是命令列引數。

  1. 將下列 else 子句新增至 if 您在上述程式碼中新增的陳述式:

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

    上述程式碼會讀取主控台輸入,直到讀取空白行或 a null 為止。 (如果輸入資料流程已輸入 Console.ReadLine 關閉,則會null傳回

  2. 透過在同一資料夾中建立新的文字檔來測試讀取標準輸入。 為檔案 input.txt 命名並新增下列行:

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

    保持線條簡短,以便在您新增功能以使用 ASCII 藝術時正確格式化。

  3. 再次執行程式。

    使用 bash:

    cat input.txt | dotnet run AsciiArt.cs
    

    或者,使用 PowerShell:

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

現在您的程式可以接受命令列引數或標準輸入。

寫入 ASCII 藝術輸出

接下來,新增支援ASCII藝術的套件 Colorful.Console。 若要將套件新增至檔案型程式,請使用 指引 #:package

  1. 在AsciiArt.cs檔案中的指示詞之後新增 #! 下列指示詞:

    #:package Colorful.Console@1.2.15
    

    這很重要

    上次更新本教學課程時,1.2.15該版本Colorful.Console是套件的最新版本。 檢查套件的 NuGet 頁面 以取得最新版本,以確定您使用具有最新安全性修正程式的套件版本。

  2. 變更呼叫 Console.WriteLine 的行,以改用該 Colorful.Console.WriteAscii 方法:

    async Task WriteAsciiArt(AsciiMessageOptions options)
    {
        foreach (string message in options.Messages)
        {
            Colorful.Console.WriteAscii(message);
            await Task.Delay(options.Delay);
        }
    }
    
  3. 運行該程序,您會看到 ASCII 藝術輸出而不是迴聲文本。

處理命令選項

接下來,我們來新增命令列解析。 目前版本會將每個字寫入不同的輸出行。 您新增的命令列引數支援兩個功能:

  1. 引用多個應該寫在一行上的單詞:

    AsciiArt.cs "This is line one" "This is another line" "This is the last line"
    
  2. 新增每 --delay 行之間暫停的選項:

    AsciiArt.cs --delay 1000
    

使用者應該能夠同時使用這兩個參數。

大多數命令列應用程式需要解析命令列參數才能有效地處理選項、命令和使用者輸入。 該 System.CommandLine 提供了處理命令、子命令、選項和參數的全面功能,使您能夠專注於應用程式的功能,而不是解析命令列輸入的機制。

System.CommandLine 庫具有幾個主要優勢:

  • 自動幫助文本生成和驗證。
  • 支援 POSIX 和 Windows 命令列慣例。
  • 內建索引標籤完成功能。
  • 跨應用程式的一致剖析行為。
  1. 新增 System.CommandLine 套件。 在現有的套件指引之後新增此指引:

    #:package System.CommandLine@2.0.0
    

    這很重要

    該版本 2.0.0 是上次更新本教學課程時的最新版本。 如果有較新的版本可用,請使用最新版本以確保您擁有最新的安全性套件。 檢查套件的 NuGet 頁面 以取得最新版本,以確定您使用具有最新安全性修正程式的套件版本。

  2. 在檔案頂端新增必要的 using 陳述式 (在 and #! 指令之後#:package):

    using System.CommandLine;
    using System.CommandLine.Parsing;
    
  3. 定義延遲選項和訊息引數。 新增下列程式碼以建立 and CommandLine.OptionCommandLine.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."
    };
    

    在命令列應用程式中,選項通常以 (double dash) 開 -- 頭,並且可以接受參數。 此 --delay 選項接受整數引數,以毫秒為單位指定延遲。 定義 messagesArgument 如何將選項之後的任何剩餘權杖剖解析為文字。 每個記號都會成為陣列中的個別字串,但文字可以引號,以在一個記號中包含多個單字。 例如,變成 "This is one message" 單一權杖,而 This is four tokens 變成四個不同的權杖。

    上述程式碼會定義選項的 --delay 引數類型,而且引數是值陣 string 列。 此應用程序只有一個命令,因此您可以使用 root 命令

  4. 建立根命令並使用選項和引數進行配置。 將引數和選項新增至根命令:

    RootCommand rootCommand = new("Ascii Art file-based program sample");
    
    rootCommand.Options.Add(delayOption);
    rootCommand.Arguments.Add(messagesArgument);
    
  5. 新增程式碼以剖析命令列引數並處理任何錯誤。 此程式碼會驗證命令列引數,並將剖析的引數儲存在物件中 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;
    }
    

上述程式碼會驗證所有命令列引數。 如果驗證失敗,錯誤會寫入主控台,而應用程式會結束。

使用剖析的命令列結果

現在,完成應用程序以使用解析的選項並寫入輸出。 首先,定義一筆記錄來保存剖析的選項。 檔案型應用程式可以包含類型宣告,例如記錄和類別。 它們必須在所有頂層陳述式和本機函數之後。

  1. 新增宣 record 告來儲存訊息和延遲選項值:

    public record AsciiMessageOptions(string[] Messages, int Delay);
    
  2. 在記錄宣告前新增下列本機函數。 這個方法同時處理命令列引數和標準輸入,並傳回一個新的記錄實例:

    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. 建立本機函數,以指定的延遲撰寫 ASCII 藝術。 此函式會寫入記錄中的每則訊息,並在每則訊息之間指定延遲:

    async Task WriteAsciiArt(AsciiMessageOptions options)
    {
        foreach (string message in options.Messages)
        {
            Colorful.Console.WriteAscii(message);
            await Task.Delay(options.Delay);
        }
    }
    
  4. 將您先前撰寫的 if 子句取代為下列程式碼,以處理命令列引數並撰寫輸出:

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

您建立了一種 record 類型,為剖析的命令列選項和引數提供結構。 新的本機函式會建立記錄的實例,並使用該記錄來寫入 ASCII 藝術輸出。

測試最終應用程式

透過執行數個不同的命令來測試應用程式。 如果您遇到問題,以下是完成的範例,可與您建立的內容進行比較:

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

在本教學課程中,您已瞭解如何建置檔案型程式,您可以在其中在單一 C# 檔案中建置程式。 這些程式不使用專案檔,並且可以在 Unix 系統上使用 #! 該指令。 學習者可以在嘗試我們的 線上教學課程 後,在建立更大的專案型應用程式之前建立這些程式。 基於文件的應用程序也是命令行實用程序的絕佳平台。