方法 是封裝可重用邏輯的基本單元。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 | 部分方法 (僅宣告/實作分離) |
預設模式。實值型別複製值,參考型別複製參考(指向同一物件):
void ByValue(int x, StringBuilder sb) {
x = 10; // 不影響呼叫端
sb.Append("!"); // 影響呼叫端物件內容
sb = null; // 不影響呼叫端變數參考
}
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; // 修改陣列元素
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 參數在方法所有路徑指派。
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 避免此開銷。
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 避免配置
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、支援遞迴、編譯器最佳化捕捉變數。
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)。in:> 16 bytes 考慮 in,搭配 readonly struct。GetDataAsync,回傳 Task<T>。ref struct 能當方法參數嗎?A: 能,但只能用 ref/out/in/params,不可用 async/yield/Lambda 捕捉。
A: 加具名參數、強制轉型、或重新設計避免模稜兩可(如 Process(int) vs Process(long) 呼叫 Process(42))。
A: 預設堆疊 ~1MB,約數千~萬層視框架大小。請改用迴圈處理無上限深度。