首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> C# 入門

C# 方法完整指南:定義、多載、遞迴、參數傳遞、out/ref/in、區域函數與模式比對

     編輯:C# 入門

C# 方法完整指南:定義、多載、遞迴、參數傳遞、out/ref/in、區域函數與模式比對

方法 是封裝可重用邏輯的基本單元。C# 方法支援豐富的參數傳遞模式、多載解析、遞迴、區域函數、模式比對等進階功能。掌握方法設計原則,能寫出清晰、可測試、高效能的程式碼。

方法定義語法

[attributes] [modifiers] ReturnType MethodName(ParameterList) {
    // Body
    return value; // void 方法可省略
}

// 完整範例
public static int CalculateSum(int a, int b, int c = 0) { // 選擇性參數
    return a + b + c;
}

修飾詞說明

修飾詞說明
static屬於類別,無需實例呼叫
virtual允許衍生類別 override
override覆寫基底虛擬/抽象方法
abstract無實作,衍生類必須實作
sealed禁止進一步 override (搭配 override)
extern外部實作 (如 P/Invoke)
async非同步方法,回傳 Task/Task<T>/ValueTask
partial部分方法 (僅宣告/實作分離)

參數傳遞模式

1. 傳值

預設模式。實值型別複製值,參考型別複製參考(指向同一物件):

void ByValue(int x, StringBuilder sb) {
    x = 10;           // 不影響呼叫端
    sb.Append("!");   // 影響呼叫端物件內容
    sb = null;        // 不影響呼叫端變數參考
}

2. ref:傳參考

傳遞變數儲存位置的參考,允許修改呼叫端變數本身:

void Swap(ref int a, ref int b) {
    (a, b) = (b, a); // 元組解構交換
}
int x = 1, y = 2;
Swap(ref x, ref y); // x=2, y=1

// ref 回傳 (C# 7+):回傳變數參考
ref int Find(int[] arr, int value) {
    for (int i = 0; i < arr.Length; i++)
        if (arr[i] == value) return ref arr[i];
    throw new KeyNotFoundException();
}
ref int slot = ref Find(arr, 42);
slot = 99; // 修改陣列元素

3. out:輸出參數

強制方法指派值給參數,呼叫端變數無需初始化:

bool TryParse(string s, out int result) {
    return int.TryParse(s, out result);
}
TryParse("42", out int n); // n = 42
// C# 7+ 內建宣告
if (int.TryParse("42", out var n)) { }

ref vs out:ref 需初始化、雙向;out 不需初始化、單向輸出。編譯器強制 out 參數在方法所有路徑指派。

4. in:唯讀參考

C# 7.2+ 以參考傳遞大型結構體,避免複製,且禁止修改:

readonly struct BigStruct { public long A,B,C,D,E,F; }
void Process(in BigStruct data) {
    // data.A = 1; // 編譯錯誤:不可修改
    Console.WriteLine(data.A);
}
Process(largeStruct); // 無複製,唯讀

防禦性複製:若在 in 參數上呼叫非 readonly 方法,編譯器會建立副本。標記結構體 readonly struct 避免此開銷。

5. params:參數陣列

int Sum(params int[] numbers) {
    int total = 0;
    foreach (var n in numbers) total += n;
    return total;
}
Sum(1,2,3);           // 隱建陣列
Sum(new[] {1,2,3});   // 顯式陣列
// C# 13+ params Span 避免配置

6. 選擇性參數與具名參數

void Connect(string host, int port = 80, bool ssl = false, int timeout = 5000) { }
Connect("localhost");                    // 預設值
Connect("localhost", 443);               // 位置對應
Connect("localhost", ssl: true);         // 具名跳過 port
Connect("localhost", timeout: 10000);    // 具名

方法多載

同名、不同參數簽章(型別、數量、順序、ref/out/in、泛型約束)。僅傳回型別不同不可多載。

void Process(int x) { }
void Process(string s) { }
void Process(int x, string s) { }
void Process(string s, int x) { } // 順序不同 = 不同簽章
// void Process(ref int x) { } // ref/out/in 屬於簽章一部分

解析優先序:精確匹配 > 隱式轉換 > 選擇性參數 > params 陣列。

遞迴與尾呼叫

// 階乘
long Factorial(int n) {
    if (n <= 1) return 1;
    return n * Factorial(n - 1);
}

// 尾遞迴 (需編譯器/執行期支援尾呼叫最佳化,CLR x64 支援)
long FactorialTail(int n, long acc = 1) {
    if (n <= 1) return acc;
    return FactorialTail(n - 1, n * acc);
}

注意:C# 編譯器不保證尾呼叫最佳化。深度遞迴請改用迴圈或 Stack 避免 StackOverflowException。

區域函數

C# 7+ 在方法內定義方法,捕捉外部變數、避免委派配置、封裝邏輯:

public int ProcessData(int[] data) {
    int sum = 0;
    
    // 區域函數:可存取 sum、data
    void AddRange(int start, int count) {
        for (int i = start; i < start + count; i++)
            sum += data[i];
    }
    
    AddRange(0, data.Length / 2);
    AddRange(data.Length / 2, data.Length - data.Length / 2);
    return sum;
}

優勢:無委派配置、可用 ref/out、支援遞迴、編譯器最佳化捕捉變數。

模式比對與 Switch 運算式

C# 7+ 讓方法式邏輯更簡潔:

// 傳統
object Process(object input) {
    if (input is int i) return i * 2;
    if (input is string s) return s.Length;
    return null;
}

// Switch 運算式 (C# 8+)
object Process(object input) => input switch {
    int i => i * 2,
    string s => s.Length,
    double d when d > 0 => (int)d,
    _ => null
};

擴充方法

為現有型別「新增」方法,實為靜態方法 + this 修飾詞:

public static class StringExtensions {
    public static bool IsNullOrWhiteSpace(this string s) 
        => string.IsNullOrWhiteSpace(s);
    
    public static T? ToEnum(this string s) where T : struct 
        => Enum.TryParse(s, out T result) ? result : null;
}

// 使用
"hello".IsNullOrWhiteSpace(); // false
"Active".ToEnum();    // Status.Active

最佳實踐

  • 單一職責:方法只做一件事,名稱動詞開頭(Calculate、Validate、Save)。
  • 參數少於 4 個:超過請用參數物件/記錄型別。
  • 避免 out/ref 濫用:回傳 Tuple/Record/Result 型別更清晰。
  • 大型結構體用 in:> 16 bytes 考慮 in,搭配 readonly struct。
  • 選擇性參數謹慎用:公開 API 易造成版本相容問題,內部方法較適合。
  • 區域函數替代私有方法:邏輯僅在單一方法使用時。
  • 非同步方法尾碼 Async:GetDataAsync,回傳 Task<T>。

常見問題 FAQ

Q: ref struct 能當方法參數嗎?

A: 能,但只能用 ref/out/in/params,不可用 async/yield/Lambda 捕捉。

Q: 多載解析模稜兩可怎麼辦?

A: 加具名參數、強制轉型、或重新設計避免模稜兩可(如 Process(int) vs Process(long) 呼叫 Process(42))。

Q: 遞迴深度限制?

A: 預設堆疊 ~1MB,約數千~萬層視框架大小。請改用迴圈處理無上限深度。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved