動態

詳情 返回 返回

C# 的程序結構 - 動態 詳情

C# 程序的常規結構

C# 語言規範

C# 程序由一個或多個文件組成。每個文件都包含零個或多個命名空間。命名空間包含類、結構、接口、枚舉和委託或其他命名空間等類型。下面的示例是包含所有這些元素的 C# 程序的框架。

using System;

Console . WriteLine ( "Hello world!" );

namespace YourNamespace
    {
        class YourClass
        {
        }

        struct YourStruct
            {
            }

        interface IYourInterface
            {
            }

        delegate int YourDelegate ( );

        enum YourEnum
            {
            }

        namespace YourNestedNamespace
            {
                struct YourStruct
                    {
                    }
            }
    }

前面的示例對程序的入口點使用頂級語句。只有一個文件可以有頂級語句。程序的入口點是該文件中程序文本的第一個文本行。在本例中,該值為 Console . WriteLine ( "Hello world!" );。還可以創建一個名為 Main 的靜態方法作為程序的入口點,如以下示例所示:

// 一個 C# 程序的框架結構
using System;
namespace YourNamespace
    {
        class YourClass
            {
            }

        struct YourStruct
            {
            }

        interface IYourInterface
            {
            }

        delegate int YourDelegate ( );

        enum YourEnum
            {
            }

        namespace YourNestedNamespace
            {
                struct YourStruct
                    {
                    }
            }

        class Program
            {
                static void Main ( string [ ] args )
                    {
                        Console . WriteLine ( "Hello world!" );
                    }
            }
    }

在這種情況下,程序從 Main 方法的左大括號開始,即 Console . WriteLine ( "Hello world!" );。

生成和運行 C# 程序

C# 是一種已編譯的語言。在大多數 C# 程序中,使用 dotnet build 命令將一組源文件編譯為二進制包。然後,使用 dotnet run 命令運行程序。(可以簡化此過程,因為 dotnet run 會在必要時編譯程序後再運行它)。這些工具支持豐富的配置選項和命令行開關。. dotnet NET SDK 中包含的命令行接口(CLI)提供了許多工具來生成和修改 C# 文件。

從 C# 14 和 .NET 10 開始,可以創建基於文件的程序,從而簡化 C# 程序的生成和運行。使用 dotnet run 命令運行包含在單個 *.cs 文件中的程序。例如,如果以下代碼片段存儲在名為 hello-world.cs 的文件中,可以通過鍵入 dotnet run hello-world.cs 運行它。

#!/usr/local/share/dotnet/dotnet run
Console . WriteLine ( "Hello, World!" );

程序的第一行包含 Unix shell 的 #! 序列。dotnet CLI 的位置可能因不同的發行版而異。在任何 Unix 系統上,如果對 C# 文件設置了 execute (+x) 權限,則可以從命令行運行 C# 文件:

./hello-world.cs

這些程序的源必須是單個文件,否則所有 C# 語法都有效。可以將基於文件的程序用於小型命令行實用工具、原型或其他試驗。基於文件的程序允許預處理器指令配置生成系統。

表達式和語句

C# 程序是通過表達式和語句來構建的。表達式會生成一個值,而語句則會執行一項操作:

表達式是計算為單個值的值、變量、運算符和方法調用的組合。表達式生成結果,可在預期值的位置使用。以下示例是表達式:

  • 42 // (文本值)
  • x + y // (算術運算)
  • Math . Max ( a , b ) // (方法調用)
  • condition ? trueValue : falseValue // (條件表達式)
  • new Person ( "John" ) // (對象創建)

語句是執行操作的完整指令。語句不返回值;相反,它們控制程序流、聲明變量或執行作。以下示例是陳述:

  • int x = 42; // (申報單)
  • Console . WriteLine ( "Hello" ); // (表達式語句 - 封裝方法調用表達式)
  • if ( condition ) { / code / } // (條件語句)
  • return result; // (return 語句)

關鍵區別:表達式計算結果為值,而語句執行操作。某些構造(如方法調用)可以是兩者。例如,Math . Max ( a , b ) 在 int result = Math . Max ( a , b); 中時是一個表達式,但單獨寫成 Math . Max ( a , b ); 時,它是一條表達式語句。

相關部分
您可以在基礎指南的 “類型” 部分中瞭解到這些程序元素的相關內容:

  • 結構
  • 命名空間
  • 接口
  • 枚舉
  • 委託

主函數(Main ( ))和命令行參數

“Main” 方法是 C# 應用程序的入口點。當應用程序啓動時,Main 方法是首先被調用的方法。
在 C# 程序中只能有一個入口點。如果您的程序中有多個類都具有 Main 方法,那麼您必須使用 “StartupObject”(啓動對象)編譯選項來指定要作為入口點使用的 Main 方法。以下示例會將命令行參數的數量作為其第一個操作來顯示:

class TestClass
    {
        static void Main ( string [ ] args )
            {
                Console . WriteLine ( args . Length );
            }
    }

您還可以在一個文件中使用頂級語句作為應用程序的入口點。與主方法一樣,頂級語句也可以返回值並訪問命令行參數。以下示例使用 foreach 循環使用 args 變量顯示命令行參數,並在程序結束時返回一個成功代碼(0):如果一個程序不止有一個具有主方法的類,您必須使用 StartupObject 編譯器選項編譯您的程序,以指定要作為入口點使用的主方法。以下示例在執行的第一個操作中顯示命令行參數的數量:

using System . Text;

StringBuilder sb = new ( );
sb . AppendLine ( "以下的參數被傳遞了:" );

foreach ( var arg in args )
    {
        sb . AppendLine ( $"參數 = {arg}" );
    }

Console . WriteLine ( sb . ToString ( ) );

return 0;

從 C# 14 版本開始,程序可以是基於文件的程序,即一個單獨的文件包含整個程序內容。要運行基於文件的程序,可以使用命令 “dotnet run <文件名.cs>”,或者在第一行使用 “#!/usr/local/share/dotnet/dotnet run” 這樣的指令(僅限於 Unix 命令行環境)。

概述

  • Main 方法是可執行程序的入口點;它是程序控制的起始點和終止點。
  • Main 方法必須聲明在類或結構體內部。其外部類可以是靜態的。
  • Main 方法必須是 static 的。
  • Main 方法可以具有任何訪問修飾符(除了 file 修飾符)。
  • Main 方法可以具有 void、int、Task 或 Task < int > 這樣的返回類型。
  • 只有當 Main 方法返回 Task 或 Task < int > 時,其聲明中才可以包含 async 修飾符。此規則明確排除了 async void Main 方法。
  • Main 方法可以帶有或不帶有包含命令行參數的 string [ ] 參數。使用 Visual Studio 創建 Windows 應用程序時,您可以手動添加該參數,或者使用 GetCommandLineArgs ( ) 方法獲取命令行參數。參數以零索引的命令行參數形式讀取。與 C 和 C++ 不同,程序的名稱在 args 數組中不被視為第一個命令行參數,而是 GetCommandLineArgs ( ) 方法的第一個元素。

以下列表展示了最常見的 Main 方法聲明:

static void Main ( ) { }
static int Main ( ) { }
static void Main ( string [ ] args ) { }
static int Main ( string [ ] args ) { }
static async Task Main ( ) { }
static async Task < int > Main ( ) { }
static async Task Main ( string [ ] args ) { }
static async Task < int > Main ( string [ ] args ) { }

添加了 “async” 和 “Task” 以及 “Task < int >” 的返回類型後,當控制枱應用程序需要在 “Main” 方法中啓動並等待異步操作時,程序代碼就會變得更加簡潔。

Main 函數的返回值

您可以通過以下任一方式在主函數中定義該方法,從而從該函數返回一個整數:

主聲明 主方法代碼
static int Main ( ) 不使用 args 或 await
static int Main ( string [ ] args ) 使用 args 但不使用 await
static async Task < int > Main ( ) 使用 await 但不使用 args
static async Task < int > Main ( string [ ] args ) 使用 args 並使用 await

如果主方法的返回值不被使用,返回 void 或 Task 可以使代碼稍微簡潔一些。

主聲明 主方法代碼
static void Main ( ) 不使用 args 或 await
static void Main ( string [ ] args ) 使用 args 但不使用 await
static async Task Main ( ) 使用 await 但不使用 args
static async Task Main ( string [ ] args ) 使用 args 並使用 await

然而,返回 int 或 Task < int > 可使程序向調用可執行文件的其他程序或腳本傳遞狀態信息。

以下示例展示瞭如何獲取進程的退出代碼。

此示例使用了 .NET Core 命令行工具。

通過運行 “dotnet new console” 命令創建一個新的應用程序。在 “Program.cs” 文件中修改 “Main” 方法如下:

class MainReturnValTest
    {
        static int Main ( )
            {
                // ……
                return 0;
            }
    }

記得將此程序保存為 “MainReturnValTest.cs”。

在 Windows 系統中執行程序時,Main 函數返回的任何值都會被存儲在一個環境變量中。可以通過批處理文件中的 ERRORLEVEL 或 PowerShell 中的 $LastExitCode 來獲取該環境變量的值。

您可以使用 dotnet CLI 的 dotnet build 命令來構建該應用程序。

接下來,創建一個 PowerShell 腳本來運行該應用程序並顯示結果。將以下代碼粘貼到一個文本文件中,並將其保存為名為 “test.ps1” 的文件,存放在包含項目文件的文件夾中。通過在 PowerShell 提示符處輸入 “test.ps1” 來運行此 PowerShell 腳本。

因為代碼返回了 0,所以批處理文件會報告成功。然而,如果您將 MainReturnValTest.cs 文件中的返回值更改為非 0 值,然後重新編譯程序,之後執行 PowerShell 腳本時就會報告失敗。

dotnet run
if ( $LastExitCode -eq 0 )
    {
        Write-Host "成功執行"
    }
else
    {
        Write-Host "執行有錯誤"
    }
Write-Host "返回值 = " $LastExitCode
// 成功執行
// 返回值 = 0

異步 Main 函數返回值

當您為 Main 函數聲明異步返回值時,編譯器會生成在 Main 函數中調用異步方法的樣板代碼:

class Program
    {
        static async Task < int > Main ( string [ ] args )
            {
                await AsyncConsoleWork ( );
            }
        private static async Task < int > AsyncConsoleWork ( )
            {
                return 0;
            }
    }

在兩個例子中,程序的主要部分都在 AsyncConsoleWork ( ) 方法的內部。

將 Main 函數聲明為異步的一個優點在於,編譯器總是能生成正確的代碼。

當應用程序入口點返回一個 Task 或 Task < int > 類型時,編譯器會生成一個新的入口點,該入口點會調用應用程序代碼中聲明的入口方法。假設這個入口點被命名為 $GeneratedMain,那麼編譯器會為這些入口點生成以下代碼:

  • static Task Main ( ) 會導致編譯器生成類似於以下的代碼:private static void $GeneratedMain ( ) => Main ( ) . GetAwaiter ( ) . GetResult ( );
  • static Task Main ( string [ ] ) 會導致編譯器生成類似於以下的代碼:private static void $GeneratedMain ( string [ ] args ) => Main ( args ) . GetAwaiter ( ) . GetResult ( );
  • static Task < int > Main ( ) 會導致編譯器生成類似於以下的代碼:private static int $GeneratedMain ( ) => Main ( ) . GetAwaiter ( ) . GetResult ( );
  • static Task < int > Main ( string [ ] ) 會導致編譯器生成類似於以下的代碼:private static int $GeneratedMain ( string [ ] args ) => Main ( args ) . GetAwaiter ( ) . GetResult ( );

注意:如果在主方法上使用了 “async” 修飾符,編譯器將會生成相同的代碼。

命令行參數

您可以通過以下任一方式在 Main 方法中定義該方法,從而向其傳遞參數:

Main 聲明 Main 方法代碼
static void Main ( string [ ] args ) 沒有返回值且不使用 await
static int Main ( string [ ] args ) 返回一個值但不使用 await
static async Task Main ( string [ ] args ) 使用 await 但不返回值
static async Task < int > Main ( string [ ] args ) 返回一個值並使用 await

如果不需要使用這些參數,可以在方法聲明中省略 args,這樣代碼會更簡潔一些:

Main 聲明 Main 方法代碼
static void Main ( ) 沒有返回值且不使用 await
static int Main ( ) 返回值但不使用 await
static async Task Main ( ) 使用 await 但不返回值
static async Task < int > Main ( ) 返回值並使用 await

注意:您還可以使用 Environment . CommandLine 或 Environment . GetCommandLineArgs 來從任何位置在控制枱或 Windows 窗體應用程序中訪問命令行參數。要在 Windows 窗體應用程序的 Main 方法聲明中啓用命令行參數,您必須手動修改 Main 的聲明。Windows 窗體設計器生成的代碼在創建 Main 時沒有輸入參數。
Main 方法的參數是一個字符串數組,該數組代表命令行參數。通常,您可以通過測試“長度”屬性來確定參數是否存在,例如:

if ( args . Length == 0 )
    {
        Console . WriteLine ("請輸入一個數字參數。" );
        return 1;
    }

提示:“args” 數組不能為 null。因此,在不進行 null 檢查的情況下訪問其 “Length” 屬性是安全的。

您還可以通過使用 Convert 類或 Parse 方法將字符串參數轉換為數值類型。例如,以下語句使用 Parse 方法將字符串轉換為一個 long:
long num = Int64 . Parse ( args [ 0 ] );
此外,還可以使用 C# 中的 long 類型(它與 Int64 類型等價):
long num = long . Parse ( args [ 0 ] );
您還可以使用 Convert 類的 ToInt64 方法來實現相同的功能:
long num = Convert . ToInt64 ( s );
提示:解析命令行參數可能會比較複雜。不妨考慮使用 “System . CommandLine” 庫(目前處於測試階段)來簡化這一過程。

以下示例展示瞭如何在控制枱應用程序中使用命令行參數。該應用程序在運行時接收一個參數,將該參數轉換為整數,並計算該數字的階乘。如果未提供任何參數,該應用程序會發出一條消息,説明程序的正確使用方法。

要從命令提示符窗口編譯並運行該應用程序,請按照以下步驟操作:

  1. 將以下代碼粘貼到任何文本編輯器中,然後將其保存為名為 “Factorial.cs” 的文本文件。

    public class LeiFF
    {
     public static long JCH ( int n )
     {
         // 對無效輸入進行測試
         if ((n < 0) || (n > 20))
         {
             return -1;
         }
         // 採用迭代方式而非遞歸方式計算階乘
         long JG = 1;
         for (int i = 1; i <= n; i++)
         {
             JG *= i;
         }
         return JG;
     }
    }
    
    public class LeiMain
    {
     private static int Main(string[] args)
     {
         if (args.Length == 0)
         {
             Console.WriteLine("請輸入一個數字參數。");
             Console.WriteLine("使用説明:階乘 < int >");
             return 1;
         }
         bool b = int.TryParse(args[0], out int z);
         if (b == false)
         {
             Console.WriteLine("請輸入一個數字參數。");
             Console.WriteLine("使用説明:階乘 < int >");
             return 1;
         }
         long JG = LeiFF.JCH(z);
         if (JG == -1)
         {
             Console.WriteLine("輸入的值必須大於等於 0 且小於等於 20。");
             return 0;
         }
         else
         {
             Console.WriteLine($"方法輸出以下內容:{z} 的階乘:{JG}。");
             return 0;
         }
     }
    }

    在主方法的開頭,程序會檢查輸入參數是否未被提供,通過比較 args 參數的長度是否為 0 來進行判斷。如果沒有找到任何參數,則會顯示幫助信息。

如果提供了參數(即 args . Length > 0),程序會嘗試將輸入參數轉換為數字。如果參數不是數字,則此示例會拋出異常。

在計算完階乘(將其存儲在類型為 long 的 JG 變量中)後,會根據 result 變量的值打印詳細的結果。

  1. 在 “開始” 屏幕或 “開始” 菜單中,打開一個 Visual Studio 開發者命令提示符窗口,然後導航至包含您所創建文件的文件夾。
  2. 要編譯該應用程序,請輸入以下命令:
    dotnet build
    如果您的應用程序沒有編譯錯誤,就會生成一個名為 “C__命令行的階乘.dll” 的二進制文件。
  3. 請輸入以下命令來計算 3 的階乘:
    “dotnet run -- 3”
  4. 如果在命令行中將數字 3 作為程序的參數輸入,程序的輸出結果為:3 的階乘是 6。

頂級語句 - 沒有 Main 方法的程序

在控制枱應用程序項目中,您不必顯式包含一個 Main 方法。相反,您可以使用頂級語句功能來減少需要編寫的代碼量。

頂級語句允許您直接在文件的根目錄編寫可執行代碼,無需將代碼封裝在類或方法中。這意味着您可以創建程序,而無需像以往那樣創建 Program 類和 Main 方法。在這種情況下,編譯器會為應用程序生成一個 Program 類和一個入口點方法。生成的方法名稱不是 Main,這是實現細節,您的代碼無法直接引用。
這是一個完整的 C# 程序的 Program.cs 文件:
Console . WriteLine ( "Hello World!" );
頂級語句讓您能夠編寫簡單的程序,用於諸如 Azure Functions 和 GitHub Actions 這樣的小型實用工具。它們還使新的 C# 程序員更容易開始學習和編寫代碼。

以下各節將説明關於頂級語句您可以做什麼以及不能做什麼的規則。

僅一個頂級文件

應用程序必須只有一個入口點。一個項目中只能有一個包含頂級語句的文件。在一個項目中將頂級語句放在多個文件中會導致以下編譯錯誤:

警告 CS8802:只能有一個編譯單元包含頂級語句。

一個項目可以有任意數量的源代碼文件,這些文件沒有頂級語句。

沒有其他入口點

您可以顯式編寫一個 Main 方法,但它不能作為入口點發揮作用。編譯器會發出以下警告:

警告 CS7022:程序的入口點是全局代碼;忽略 'Main ( )' 入口點。

在包含頂級語句的項目中,您不能使用 -main 編譯器選項來選擇入口點,即使該項目具有一個或多個 Main 方法。

using 指令

對於包含頂級語句的單個文件,using 指令必須位於該文件的開頭,例如:

using System . Text;

StringBuilder sb = new ( );
sb . AppendLine ( "傳遞了如下參數:" );

foreach ( var arg in args )
    {
        sb . AppendLine ( $"參數 = {arg}" );
    }

Console . WriteLine ( sb . ToString ( ) );

return 0;

全局命名空間(global namespace)

頂層語句默認處於全局命名空間中。

命名空間與類型定義

包含頂級語句的文件還可以包含命名空間和類型定義,但這些內容必須在頂級語句之後出現。例如:

MyClass . FFTest ( );
MyNamespace . MyClass . FFMy ( );

public class MyClass
    {
        public static void FFTest ( )
            {
                Console . WriteLine ( "Hello World!" );
            }
    }

namespace MyNamespace
    {
        class MyClass
            {
                public static void FFMy ( )
                    {
                        Console . WriteLine ( "Hello World from MyNamespace . MyClass . FFMy!" );
                    }
            }
    }

參數

頂層語句可以引用 “args” 變量來訪問輸入的任何命令行參數。該 “args” 變量永遠不會為空,但如果未提供命令行參數,則其長度為零。例如:

if ( args . Length > 0 )
    {
        foreach ( var arg in args )
            {
                Console . WriteLine ( $"參數 = {arg}" );
            }
    }
else
    {
        Console . WriteLine ( "沒有參數" );
    }

await

您可以使用 “await” 關鍵字來調用異步方法。例如:

Console . WriteLine ( “Hello ” );
await Task . Delay ( 5000 );
Console . WriteLine ( “World!” );

進程的退出代碼(Exit code)

若要在應用程序結束時返回一個整數值,請像在返回整數的 Main 方法中那樣使用 return 語句。例如:

string?  s = Console . ReadLine ( );
int Zhi = int . Parse ( s ?? "-1" );
return Zhi;

 隱式入口點方法

編譯器會生成一個方法,作為項目中具有頂級語句的程序的入口點。該方法的簽名取決於頂級語句中是否包含 await 關鍵字或 return 語句。以下表格展示了方法簽名的樣子(為了方便起見,表中使用了方法名 Main)。

頂級語句包含 隱式 Main 函數簽名
await 和 return static async Task < int > Main ( string [ ] args )
await static async Task Main ( string [ ] args )
return static int Main ( string [ ] args )
無 await 和 return static void Main ( string [ ] args )

從 C# 14 開始,程序可以是基於文件的程序,即單個文件包含整個程序。使用命令 “dotnet run <文件.cs>” 或在第一行使用 “#!/usr/local/share/dotnet/dotnet run”(僅限 Unix shells 環境)來運行基於文件的程序。

Add a new 評論

Some HTML is okay.