檔案型應用程式 是包含在單一 *.cs 檔案中的程式,這些程式在沒有對應專案 (*.csproj) 檔案的情況下建置和執行。 基於文件的應用程序非常適合學習 C#,因為它們的複雜性較低:整個程序存儲在一個文件中。 基於文件的應用程序對於構建命令行實用程序也很有用。 在 Unix 平台上,檔案式應用程式可以使用(shebang)#!執行。
在本教學課程中,您會:
- 建立檔案型程式。
- 新增 Unix shebang (
#!) 支援。 - 讀取命令列引數。
- 處理標準輸入。
- 編寫 ASCII 藝術輸出。
- 處理命令列引數。
- 使用剖析的命令列結果。
- 測試最終應用程序。
您建置一個檔案型程式,將文字寫入為 ASCII 藝術。 應用程式包含在單一檔案中,使用實作某些核心功能的 NuGet 套件。
先決條件
- .NET 10 SDK。 從 .NET 下載網站下載。
- Visual Studio Code。 從 Visual Studio Code 首頁下載。
- (選用)Visual Studio Code 的 C# DevKit 延伸模組。 從 Visual Studio Code 市集下載。
建立檔案型程式
開啟 Visual Studio Code 並建立名為
AsciiArt.cs的新檔案。 輸入下列文字:Console.WriteLine("Hello, world!");儲存檔案。 然後,在 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。 您需要進行兩項變更:
設定來源檔案的 執行 權限:
chmod +x AsciiArt.cs新增 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# 編譯器允許在所有平台上的檔案型應用程式中使用該指示詞。
讀取命令列引數
現在,將命令列上的所有引數寫入輸出。
將目前
AsciiArt.cs的內容取代為下列程式碼:if (args.Length > 0) { string message = string.Join(' ', args); Console.WriteLine(message); }您可以輸入下列命令來執行此版本:
dotnet run AsciiArt.cs -- This is the command line.此
--選項指出下列所有指令引數都應傳遞至 AsciiArt 程式。 引數This is the command line.會以字串陣列的形式傳遞,其中每個字串都是一個字:This、is、thecommandline.、 和 。
此版本示範了這些新概念:
- 命令列引數會使用預先定義的變數
args傳遞至程式。args變數是字串陣列:string[]。 如果長度為args0,則表示未提供任何引數。 否則,引數清單上的每個單字都會儲存在陣列中的對應專案中。 - 此
string.Join方法會使用指定的分隔符號將多個字串聯結成單一字串。 在此情況下,分隔符號是單一空格。 - Console.WriteLine 將字串寫入標準輸出主控台,後面接著換行。
處理標準輸入
這可以正確處理命令列引數。 現在,新增程式碼來處理從標準輸入 (stdin) 讀取輸入,而不是命令列引數。
將下列
else子句新增至if您在上述程式碼中新增的陳述式:else { while (Console.ReadLine() is string line && line.Length > 0) { Console.WriteLine(line); } }上述程式碼會讀取主控台輸入,直到讀取空白行或 a
null為止。 (如果輸入資料流程已輸入 Console.ReadLine 關閉,則會null傳回。透過在同一資料夾中建立新的文字檔來測試讀取標準輸入。 為檔案
input.txt命名並新增下列行:Hello from ... dotnet! You can create file-based apps in .NET 10 and C# 14 Have fun writing useful utilities保持線條簡短,以便在您新增功能以使用 ASCII 藝術時正確格式化。
再次執行程式。
使用 bash:
cat input.txt | dotnet run AsciiArt.cs或者,使用 PowerShell:
Get-Content input.txt | dotnet run AsciiArt.cs
現在您的程式可以接受命令列引數或標準輸入。
寫入 ASCII 藝術輸出
接下來,新增支援ASCII藝術的套件 Colorful.Console。 若要將套件新增至檔案型程式,請使用 指引 #:package 。
在AsciiArt.cs檔案中的指示詞之後新增
#!下列指示詞:#:package Colorful.Console@1.2.15這很重要
上次更新本教學課程時,
1.2.15該版本Colorful.Console是套件的最新版本。 檢查套件的 NuGet 頁面 以取得最新版本,以確定您使用具有最新安全性修正程式的套件版本。變更呼叫
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); } }運行該程序,您會看到 ASCII 藝術輸出而不是迴聲文本。
處理命令選項
接下來,我們來新增命令列解析。 目前版本會將每個字寫入不同的輸出行。 您新增的命令列引數支援兩個功能:
引用多個應該寫在一行上的單詞:
AsciiArt.cs "This is line one" "This is another line" "This is the last line"新增每
--delay行之間暫停的選項:AsciiArt.cs --delay 1000
使用者應該能夠同時使用這兩個參數。
大多數命令列應用程式需要解析命令列參數才能有效地處理選項、命令和使用者輸入。 該 System.CommandLine 庫 提供了處理命令、子命令、選項和參數的全面功能,使您能夠專注於應用程式的功能,而不是解析命令列輸入的機制。
該 System.CommandLine 庫具有幾個主要優勢:
- 自動幫助文本生成和驗證。
- 支援 POSIX 和 Windows 命令列慣例。
- 內建索引標籤完成功能。
- 跨應用程式的一致剖析行為。
新增
System.CommandLine套件。 在現有的套件指引之後新增此指引:#:package System.CommandLine@2.0.0這很重要
該版本
2.0.0是上次更新本教學課程時的最新版本。 如果有較新的版本可用,請使用最新版本以確保您擁有最新的安全性套件。 檢查套件的 NuGet 頁面 以取得最新版本,以確定您使用具有最新安全性修正程式的套件版本。在檔案頂端新增必要的 using 陳述式 (在 and
#!指令之後#:package):using System.CommandLine; using System.CommandLine.Parsing;定義延遲選項和訊息引數。 新增下列程式碼以建立 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 命令。建立根命令並使用選項和引數進行配置。 將引數和選項新增至根命令:
RootCommand rootCommand = new("Ascii Art file-based program sample"); rootCommand.Options.Add(delayOption); rootCommand.Arguments.Add(messagesArgument);新增程式碼以剖析命令列引數並處理任何錯誤。 此程式碼會驗證命令列引數,並將剖析的引數儲存在物件中 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; }
上述程式碼會驗證所有命令列引數。 如果驗證失敗,錯誤會寫入主控台,而應用程式會結束。
使用剖析的命令列結果
現在,完成應用程序以使用解析的選項並寫入輸出。 首先,定義一筆記錄來保存剖析的選項。 檔案型應用程式可以包含類型宣告,例如記錄和類別。 它們必須在所有頂層陳述式和本機函數之後。
新增宣
record告來儲存訊息和延遲選項值:public record AsciiMessageOptions(string[] Messages, int Delay);在記錄宣告前新增下列本機函數。 這個方法同時處理命令列引數和標準輸入,並傳回一個新的記錄實例:
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); }建立本機函數,以指定的延遲撰寫 ASCII 藝術。 此函式會寫入記錄中的每則訊息,並在每則訊息之間指定延遲:
async Task WriteAsciiArt(AsciiMessageOptions options) { foreach (string message in options.Messages) { Colorful.Console.WriteAscii(message); await Task.Delay(options.Delay); } }將您先前撰寫的
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 系統上使用 #! 該指令。 學習者可以在嘗試我們的 線上教學課程 後,在建立更大的專案型應用程式之前建立這些程式。 基於文件的應用程序也是命令行實用程序的絕佳平台。