目錄
- 一、方法的結構
- 二、方法體內部的代碼執行
- 三、區域變數
- 3.1 型別推斷和 var 關鍵字
- 3.2 嵌套塊中的區域變數
- 四、區域常量
- 五、控制流
- 六、方法呼叫
- 七、回傳值
- 八、回傳陳述句和 void 方法
- 九、區域函式
- 十、引數
- 10.1 形參
- 10.2 實參
- 十一、值引數
- 十二、參考引數
- 十三、參考型別作為值引數和參考引數
- 十四、輸出引數
- 十五、引數陣列
- 15.1 方法呼叫
- 15.2 將陣列作為實參
- 十六、引數型別總結
- 十七、ref 區域變數和 ref 回傳
- 十八、方法多載
- 十九、命名引數
- 二十、可選引數
- 二十一、堆疊幀
- 二十二、遞回
一、方法的結構
方法是一塊具有名稱的代碼,可以使用方法的名稱從別的地方執行代碼,也可以把資料傳入方法并接收資料輸出,
方法是類的函式成員,方法主要有兩個部分,如下圖所示:方法頭和方法體,
- 方法頭指定方法的特征,包括:
- 方法是否回傳資料,如果回傳,回傳什么型別;
- 方法的名稱;
- 哪種型別的資料可以傳遞給方法或從方法回傳,以及應如何處理這些資料,
- 方法體包含可執行代碼的陳述句序列,執行程序從方法體的第一條陳述句開始,一直到整個方法結束,

下面的示例展示了方法頭的形式,接下來闡述其中的每一部分,

例如,下面的代碼展示了一個名稱為 MyMethod 的簡單方法,它多次呼叫 WriteLine 方法,
void MyMethod()
{
Console.WriteLine("First");
Console.WriteLine("First");
}
二、方法體內部的代碼執行
方法體是一個塊,是大括號括起的陳述句序列(參見[{{< ref "01-03-CSharp Hello World.md#六陳述句" >}}]({{< ref "01-03-CSharp Hello World.md#六陳述句" >}})),塊可以包含以下專案:
- 區域變數;
- 控制流結構;
- 方法呼叫;
- 內嵌的塊;
- 其他方法,稱為區域函式,
下圖展示了一個方法體及其組成元素的示例,

三、區域變數
區域變數也保存資料,欄位通常保存和物件狀態有關的資料,而創建區域變數經常是用于保存區域的或臨時的計算資料,下表對比了區域變數和實體欄位的差別,下面這行代碼展示了區域變數宣告的語法,可選的初始化陳述句由等號和用于初始化變數的值組成,

- 區域變數的存在和生存期僅限于創建它的塊及其內嵌的塊,
- 從宣告它的那一點開始存在,
- 在塊完成執行時結束存在,
- 可以在方法體內任意位置宣告區域變數,但必須在使用它們之前宣告,
下面的示例展示了兩個區域變數的宣告和使用,第一個是 int 型別變數,第二個是 SomeClass 型別變數,
static void Main()
{
int myInt = 15;
SomeClass sc = new SomeClass();
...
}
| - | 實體欄位 | 區域變數 |
|---|---|---|
| 生存期 | 從實體被創建時開始,直到實體不再被訪問的時候結束 | 從它在塊中被宣告的那一刻開始,在塊完成執行時結束 |
| 隱式初始化 | 初始化成該型別的默認值 | 沒有隱式初始化,如果變數在使用之前沒有被賦值,編譯器會產生一條錯誤訊息 |
| 存盤區域 | 由于實體欄位是類的成員,所以所有欄位都存盤在堆里,無論它們是值型別的還是參考型別的 | 值型別:存盤在堆疊里 參考型別:參考存盤在堆疊里,資料存盤在堆里 |
3.1 型別推斷和 var 關鍵字
如果觀察下面的代碼、你會發現在宣告的開始部分提供型別名時,你提供的是編譯器能從初始化陳述句的右邊推斷出來的資訊,
- 在第一個變數宣告中,編譯器能推斷出 15 是
int型, - 在第二個宣告中,右邊的物件創建運算式回傳了一個
MyExcellentClass型別的物件,
所以在兩種情況中,在宣告的開始部分包括顯式的型別名是多余的,
static void Main()
{
int total = 15;
MyExcellentClass mec = new MyExcellentClass();
...
}
為了避免這種冗余,可以在變數宣告的開始部分的顯式型別名的位置使用新的關鍵字 var,如:
static void Main()
{
var total = 15;
var mec = new MyExcellentClass();
...
}
var 關鍵字并不是表示特殊變數,它只是句法上的速記,表示任何可以從初始化陳述句的右邊推斷出的型別,在第一個宣告中,它是 int 的速記;在第二個宣告中,它是 MyExcellentClass 的速記,前文中使用顯式型別名的代碼片段和使用 var 關鍵字的代碼片段在語意上是等價的,
使用 var 關鍵字有一些重要的條件:
- 只能用于區域變數,不能用于欄位;
- 只能在變數宣告中包含初始化時使用;
- 一旦編譯器推斷出變數的型別,它就是固定且不能更改的,
說明
var關鍵字不像 JavaScript 的var那樣可以參考不同的型別,它是從等號右邊推斷出的實際型別的速記,var關鍵字并不改變 C# 的強型別性質,
3.2 嵌套塊中的區域變數
方法體內部可以嵌套其他的塊,
- 可以有任意數量的塊,并且它們既可以是順序的也可以是嵌套的,塊可以嵌套到任何級別,
- 區域變數可以在嵌套塊的內部宣告,并且和所有的區域變數一樣,它們的生存期和可見性僅限于宣告它們的塊及其內嵌塊,
下圖闡明了兩個區域變數的生存期,展示了代碼和堆疊的狀態,箭頭標出了剛執行過的行,
- 變數
var1宣告在方法體中,在嵌套塊之前, - 變數
var2宣告在嵌套塊內部,它從被宣告的那一刻開始存在,直到宣告它的那個塊的尾部結束, - 當控制傳出嵌套塊時,它的區域變數從堆疊中彈出,

說明 在 C 和 C++ 中,可以先宣告一個區域變數,然后在嵌套塊中宣告另一個名稱相同的區域變數,在內部范圍,內部名稱掩蓋了外部名稱,然而,在 C# 中不管嵌套級別如何,都不能再第一個名稱的有效范圍內宣告另一個同名的區域變數,
四、區域常量
區域常量很像區域變數,只是一旦被初始化,它的值就不能改變了,如同區域變數,區域常量必須宣告在塊的內部,
常量的兩個最重要的特征如下:
- 在宣告是必須初始化,
- 在宣告后不能改變,
常量的核心宣告如下所示:
const Type Identifier = Value;
語法與欄位或變數的宣告相同,只有如下兩點不同:
- 在類型之前增加關鍵字
const; - 必須有初始化陳述句,初始化值必須在編譯期決定,通常是一個預定義簡單型別或由其組成的運算式,它還可以是
null參考,但它不能是某物件的參考,因為物件的參考是在運行時決定的,
說明 關鍵字
const不是修飾符,而是核心宣告的一部分,它必須直接放在型別的前面,
就像區域變數,區域常量宣告在方法體或代碼塊里,并在宣告它的塊結束的地方失效,例如,在下面的代碼中,型別為內嵌型別 double 的區域常量 PI 在方法 DisplayRadii 結束后失效,
void DisplayRadii()
{
const double PI =3.1416;
for(int radius = 1; radius <= 5; radius++)
{
double area = radius * radius * PI;
Console.WriteLine($"Radius: {radius}, Area: {area}");
}
}
五、控制流
方法包含了組成程式的行為的大部分代碼,剩余部分在其他的函式成員中,如屬性和運算子,
術語控制流指的是程式從頭到尾的執行流程,默認情況下,程式執行順序地從一條陳述句到下一條陳述句,控制流陳述句允許你改變執行的順序,
這一節只會提及一些能在代碼中使用的控制陳述句,這一篇([{{< ref "01-10-陳述句.md#三控制流陳述句" >}}]({{< ref "01-10-陳述句.md#三控制流陳述句" >}}))會詳細介紹它們,
- 選擇陳述句 利用這些陳述句可以選擇要執行的陳述句或陳述句塊,
if有條件地執行一條陳述句,if...else有條件地執行一潭訓另一條陳述句,switch有條件地執行一組陳述句中的某一條,
- 迭代陳述句 這些陳述句可以在一個陳述句塊上回圈或迭代,
for回圈——在頂部測驗,while回圈——在頂部測驗,do...while回圈——在底部測驗,foreach為一組中每個成員執行一次,
- 跳轉陳述句 這些陳述句可以讓你從代碼塊或方法體內部的一個地方跳到另一個地方,
break跳出當前回圈,continue到當前回圈的底部,goto到一個命名的陳述句,return回傳到呼叫方法繼續執行,
例如,下面的方法展示了兩個控制流陳述句:
void SomeMethod()
{
int intVal = 3;
if(intVal == 3) // if 陳述句
{
Console.WriteLine("Value is 3.");
}
for(int i = 0; i < 5; i++) // for 陳述句
{
Console.WriteLine($"Value of i: {i}");
}
}
六、方法呼叫
可以從方法體的內部呼叫其他方法,
呼叫方法時要使用方法名并帶上引數串列,引數串列將在稍后討論,
例如,下面的類宣告了一個名為 PrintDateAndTime 的方法,該方法將在 Main 方法內呼叫,
class MyCalss
{
void PrintDateAndTime() // 宣告方法
{
DateTime dt = DateTime.Now; // 獲取當前日期和時間
Console.WriteLine($"{dt}"); // 輸出
}
static void Main()
{
MyClass mc = new MyClass();
mc.PrintDateAndTime(); // 呼叫方法
}
}
下圖闡明了呼叫方法時的動作順序,
- 當前方法的執行在呼叫點被掛起,
- 控制轉移到被呼叫的方法的開始,
- 被呼叫方法執行直到完成,
- 控制回到發起呼叫的方法,

七、回傳值
方法可以向呼叫代碼回傳一個值,回傳的值被插入到呼叫代碼中發起呼叫的運算式所在的位置,
- 要回傳值,方法必須在方法名前面宣告一個回傳型別,
- 如果方法不回傳值,它必須宣告
void回傳型別,
下面的代碼展示了兩個方法宣告,第一個回傳 int 型值,第二個不回傳值,
int GetHour(){...} // 回傳 int 型別
void DisplayHour(){...} // 無回傳值
宣告了回傳型別的方法必須使用如下形式的回傳陳述句從方法中回傳一個值,回傳陳述句包括關鍵字 return 及其后面的運算式,每一條貫穿方法的路徑都必須以一條這種形式的 return 陳述句結束,
return Expression; // 回傳一個值
例如,下面的代碼展示了一個名為 GetHour 的方法,它回傳 int 型值,
int GetHour() // 回傳 int 型
{
DateTime dt = DateTime.Now; // 獲取當前日期和時間
int hour = dt.Hour; // 獲取小時數
return hour; // 回傳一個 int 型的值
}
也可以回傳用戶定義型別的物件,例如,下面的代碼回傳一個 MyClass 型別的物件,
MyClass Method3()
{
MyClass mc = new MyClass();
...
return mc; // 回傳一個 MyClass 物件
}
來看另一個示例,在下面的代碼中,方法 GetHour 在 Main 的 WriteLine 陳述句中被呼叫個,并在該位置回傳一個 int 值到 WriteLine 陳述句中,
class MyClass
{
public int GetHour()
{
DateTime dt = DateTime.Now;
int hour = dt.Hour;
return hour;
}
}
class Program
{
static void Main()
{
MyClass mc = new MyClass();
Console.WriteLine($"Hour: {mc.GetHour()}"); // 方法呼叫
}
}
八、回傳陳述句和 void 方法
在上一節,我們看到有回傳值的方法必須包含回傳陳述句,void 方法不需要回傳陳述句,當控制流到達方法體的關閉大括號時,控制流回傳到呼叫代碼,并且沒有值被插入到呼叫代碼中.
不過,當特定條件符合的時候,找們常常會提前退出方法以簡化程式邏輯,
- 可以在任何時候使用下面的回傳陳述句退出方法,不帶引數:
return; - 這種形式的回傳陳述句只能用于用
void宣告的方法,
例如,下面的代碼展示了一個名為 SomeMethod 的 void 方法的宣告,它可以在三個可能的地方回傳到呼叫代碼,前兩個在 if 陳述句分支內,最后一個是方法體的結尾處,
void SomeMethod()
{
...
if(SomeCondition) // 如果...
return; // 回傳到呼叫代碼
...
if(OtherCondition) // 如果...
return; // 回傳到呼叫代碼
...
} // 默認回傳到呼叫代碼
下面的代碼展示了一個帶有一潭訓傳陳述句的 void 方法示例,該方法只有當時間是下午的時候才輸出一條訊息,如下圖所示,其程序如下:
- 首先,方法獲取當前日期和時間(現在不用理解這些細節),
- 如果小時小于12(也就是在中午之前),那么執行
return陳述句,不在螢屏上輸出任何東西,直接把控制回傳給呼叫方法, - 如果小時大于等于12,則跳過
return陳述句,代碼執行WriteLine陳述句,在螢屏上輸出資訊,
class MyClass
{
void TimeUpdate() // void 回傳型別
{
DateTime dt = DateTime.Now; // 獲取當前日期和時間
if(dt.Hour < 12) // 若小時數小于 12
return; // 則回傳,回傳到呼叫方法
Console.WriteLine("It's afternoon!"); // 否則,輸出訊息
}
static void Main()
{
MyClass mc = new MyClass();
mc.TimeUpdate();
}
}

九、區域函式
正如剛剛所解釋的,方法塊內的代碼可以呼叫另一個方法,如果另一個方法在同一個類內,可以直接使用它的名稱并傳入所需的引數(參加下一節)進行呼叫,如果另一個方法在不同的類中,必須通討這個類的一個物件實體呼叫它,另一個類中的方法必須使用 public 訪問修飾符宣告,
從 C# 7.0 開始,你可以在一個方法中宣告另一個單獨的方法,這樣可以將嵌入的方法跟其他代碼隔離開來,所以它只能在句含它的方法內呼叫,如果使用恰當,這可以使代碼更清晰,更易于維護,這些嵌入的方法被稱為區域函式,
與區域變數必須在使用之前講行宣告不同,你可以在包含方法的任意位置宣告區域函式,
下面的代碼演示了一個 MethodWithLocalFunction 方法,它包含了一個區域函式,叫作 MyLocalFunction,
class Program
{
public void MethodWithLocalFunction()
{
int MyLocalFunction(int z1)
{
return z1 * 5;
}
int results = MyLocalFunction(5); // 呼叫區域函式
Console.WriteLine($"Results of local function call: {results}");
}
static void Main(string[] args)
{
Program myProgram = new Program();
myProgram.MethodWithLocalFunction(); // 呼叫方法
}
}
輸出
Results of local function call: 25
十、引數
迄今為止,你已經看到方法是可以從程式中很多地方呼叫的命名代碼單元,它能把一個值回傳給呼叫代碼,回傳一個值的確有用,但如果需要回傳多個值呢?還有,能在方法開始執行的時候把資料傳入方法也會有用,引數就是允許你做這兩件事的特殊變數,
10.1 形參
形參是區域變數,它宣告在方法的引數串列中,而不是在方法體中,
下面的方法頭展示了引數宣告的語法,它宣告了兩個形參:一個是 int 型,另一個是 float 型,
public void PrintSum(int x, float y)
{
...
}
- 因為形參是變數,所以它們有型別和名稱,并能被寫入和讀取,
- 和方法中的其他區域變數不同,引數在方法體的外面定義并在方法開始之前初始化(但有一種型別例外,稱為輸出引數,我們將很快談到它),
- 引數串列中可以有任意數目的形參宣告,而且宣告必須用逗號隔開,
形參在整個方法體內使用,在大部分地方就像其他區域變數一樣,例如,下面的 PrintSum 方法的宣告使用兩個形參 × 和 y,以及一個區域變數 sum,它們都是 int 型,
public void PrintSum(int x, int y)
{
int sum = x + y;
Console.WriteLine($"Newsflash: {x} + {y} is {sum}");
}
10.2 實參
當代碼呼叫一個方法時,形參的值必須在方法的代碼開始執行之前初始化,用于初始化形參的運算式或變數稱作實參(actual parameter,有時也稱 argument),
- 實參位于方法呼叫的引數串列中,
- 每一個實參必須與對應形參的型別相匹配,或是編譯器必須能夠把實參隱式轉換為那個型別,
例如,下面的代碼展示了方法 PrintSum 的呼叫,它有兩個 int 型別的實參,
PrintSum(5,someInt);
當方法被呼叫的時候,每個實參的值都被用于初始化相應的形參,方法體隨后被執行,下圖闡明了實參和形參的關系,

注意在之前那段示例代碼及上圖中,實參的數量必須和形參的數量一致,并且每個實參的型別也必須和所對應的形參型別一致,這種形式的引數叫作位置引數,稍后會看其他的一些選項,現在先來看看位置引數,
位置引數示例
在如下代碼中,Myclass 類宣告了兩個方法——一個方法接受兩個整數并回傳它們的和,另一個方法接受兩個 float 并回傳它們的平均值,對于第二次呼叫,注意編譯器把 int 值 5 和 someInt 隱式轉換成了 float 型別,
class MyClass
{
public int Sum(int x, int y) // 宣告方法
{
return x + y; // 回傳和
}
public float Avg(float input1,float input2) // 宣告方法
{
return (input1 + input2) / 2.0F; // 回傳和
}
}
public class Program
{
public static void Main()
{
MyClass myT = new MyClass();
int someInt = 6;
Console.WriteLine($"Newsflash: Sum: {5} and {someInt} is {myT.Sum(5,someInt)}"); // 呼叫方法
Console.WriteLine($"Newsflash: Avg: {5} and {someInt} is {myT.Avg(5,someInt)}"); // 呼叫方法
}
}
輸出
Newsflash: Sum: 5 and 6 is 11
Newsflash: Avg: 5 and 6 is 5.5
十一、值引數
引數有幾種,各自以略微不同的方式從方法傳入或傳出資料,到目前為止,你看到的這種型別是默認的型別,稱為值引數(value parameter),
當你使用值引數時,通過將實參的值復制到形參的方式把資料傳遞給方法,方法被呼叫時,系統執行如下操作,
- 在堆疊中為形參分配空間,
- 將實參的值復制給形參,
值引數的實參不一定是變數,它可以是任何能計算成相應資料型別的運算式,例如,下面的代碼展示了兩個方法呼叫,在第一個方法呼叫中,實參是 float 型別的變數;在第二個方法呼叫中,實參是計算成float 的運算式,
public static void Main()
{
float j = 2.6F;
float k = 5.1F;
float fValue1 = func1(k);
float fValue2 = func1((k + j) / 3);
}
static float func1(float val)
{
return val;
}
用作實參之前,變數必須被賦值(除非是輸出引數,稍后會介紹),對于參考型別,變數可以被設定為一個實際的參考或 null,
說明 這篇文章([{{< ref "01-04-型別和變數.md#八值型別和參考型別" >}}]({{< ref "01-04-型別和變數.md#八值型別和參考型別" >}}))介紹了值型別,所謂值型別就是指型別本身包含其值,不要把值型別和這里介紹的值引數混淆,它們是完全不同的兩個概念,值引數是把實參的值復制給形參,
例如,下面的代碼展示了一個名為 MyMethod 的方法,它有兩個引數:一個 MyClass 型別的變數和一個 int,
- 方法為類的
int型別欄位和引數都加上 5, - 你可能還注意到
MyMethoed使用了修飾符static,我們還沒有解釋過這個關鍵字,現在你可以忽略它,
class MyClass
{
public int Val = 20; //初始化欄位為 20
}
class Program
{
static void MyMethod(MyClass f1, int f2)
{
f1.Val = f1.Val + 5; // 引數的欄位加 5
f2 = f2 + 5; // 另一個引數加 5
Console.WriteLine($"f1.Val: {f1.Val}, f2: {f2}");
}
static void Main()
{
MyClass a1 = new MyClass();
int a2 = 10;
MyMethod(a1,a2);
Console.WriteLine($"a1.Val: {a1.Val}, a2: {a2}");
}
}
輸出
f1.Val: 25, f2: 15
a1.Val: 25, a2: 10
下圖展示了實參和形參在方法執行的不同階段的值,它表明了一下 3 點,
- 在方法被呼叫前,用作實參的變數
a2以及在堆疊里了, - 在方法開始時,系統在堆疊中為形參分配空間,并從實參復制值,
- 因為
a1是參考型別的,所以參考被復制,結果實參和形參都參考堆中的同一個物件, - 因為
a2的值型別的,所以被復制,產生了一個獨立的資料項,
- 因為
- 在方法的結尾,
f2和物件f1的欄位都被加上了 5.- 方法執行后,形參從堆疊中彈出,
a2,值型別,它的值不受方法行為的影響,a1,參考型別,它的值被方法的行為改變了,

十二、參考引數
第二種引數型別稱為參考引數,
- 使用參考引數時,必須在方法的宣告和呼叫中都使用
ref修飾符, - 實參必須是變數,在用作實參前必須被賦值,如果是參考型別變數,可以賦值為一個參考或
null,
例如,下面的代碼闡明了參考引數的宣告和呼叫的語法:
void MyMethod(ref int val) // 方法宣告
{
...
}
int y = 1; // 實參變數
MyMethod(ref y); // 方法呼叫
MyMethod(ref 3 + 5); // 錯誤!必須使用變數
在之前的內容中我們已經認識到了,對于值引數,系統在堆疊上為形參分配記憶體,相反,參考引數具有以下特征,
- 不會在堆疊上為形參分配記憶體,
- 形參的引數名將作為實參變數的別名,指向相同的記憶體位置,
由于形參名和實參名指向相同的記憶體位置,所以在方法的執行程序中,對形參做的任何改變,在方法完成后依然可見(表現在實參變數上),
說明 記住要再方法的宣告和呼叫上都使用
ref關鍵字,
例如,下面的代碼再次展示了方法 MyMethod,但這一次引數是參考引數而不是值引數,
class MyClass
{
public int Val = 20; //初始化欄位為 20
}
class Program
{
static void MyMethod(ref MyClass f1, ref int f2)
{
f1.Val = f1.Val + 5; // 引數的欄位加 5
f2 = f2 + 5; // 另一個引數加 5
Console.WriteLine($"f1.Val: {f1.Val}, f2: {f2}");
}
static void Main()
{
MyClass a1 = new MyClass();
int a2 = 10;
MyMethod(ref a1,ref a2);
Console.WriteLine($"a1.Val: {a1.Val}, a2: {a2}");
}
}
輸出
f1.Val: 25, f2: 15
a1.Val: 25, a2: 15
注意,不管 MyClass 物件 f1 是否是通過 ref 傳遞給方法,f1.Val 的值都是相同的,稍后會對此進行詳細的討論,
下圖闡明了再方法執行的不同階段實參和形參的值,
- 在方法呼叫之前,將要被用作實參的變數
a1和a2已經在堆疊里了, - 在方法的開始,形參名被設定為實參的別名,變數
a1和f1參考相同的記憶體位置,a2和f2參考相同的記憶體位置, - 在方法的結束位置,
f2和f1的物件的欄位都被加上了 5, - 方法執行之后,形參的名稱已經失效,但是值型別
a2的值和參考型別a1所指向的物件的值都被方法內的行為改變了,

十三、參考型別作為值引數和參考引數
在前幾節中我們看到了,對于一個參考型別物件,不管是將其作為值引數傳遞還是作為參考引數傳遞,都可以在方法成員內部修改它的成員,不過,我們并沒有在方法內部設定形參本身,本節來看看在方法內設定參考型別形參時會發生什么,
- 將參考型別物件作為值引數傳遞 如果在方法內創建一個新物件并賦值給形參,將切斷形參與實參之間的關聯,并且在方法呼叫結束后,新物件也將不復存在,
- 將參考型別物件作為參考引數傳遞 如果在方法內創建一個新物件并賦值給形參,在方法結束后該物件依然存在,并且是實參所參考的值,
下面的代碼展示了第一種情況——將參考型別物件作為值引數傳遞:
class MyClass
{
public int Val = 20;
}
class Program
{
static void RefAsParamenter(MyClass f1)
{
f1.Val = 50;
Console.WriteLine($"After member assignment: {f1.Val}");
f1 = new MyClass();
Console.WriteLine($"After new object creation: {f1.Val}");
}
static void Main()
{
MyClass a1 = new MyClass();
Console.WriteLine($"Before method call: {a1.Val}");
RefAsParamenter(a1);
Console.WriteLine($"After method call: {a1.Val}");
}
}
輸出
Before method call: 20
After member assignment: 50
After new object creation: 20
After method call: 50
下圖闡明了關于上述代碼的以下幾點,
- 在方法開始時,實參和形參指向堆中相同的物件,
- 在為物件的成員賦值之后,它們仍指向堆中相同的物件,
- 當方法分配新的物件并賦值給形參時,(方法外部的)實參仍指向原始物件,而形參指向的是新物件,
- 在方法呼叫之后,實參指向原始物件,形參和新物件都會消失,

下面的代碼演示了將參考型別物件作為參考引數的情況,除了方法宣告和方法呼叫時要使用 ref 關鍵字外,與上面的代碼完全相同,
class MyClass
{
public int Val = 20;
}
class Program
{
static void RefAsParamenter(ref MyClass f1)
{
f1.Val = 50;
Console.WriteLine($"After member assignment: {f1.Val}");
f1 = new MyClass();
Console.WriteLine($"After new object creation: {f1.Val}");
}
static void Main()
{
MyClass a1 = new MyClass();
Console.WriteLine($"Before method call: {a1.Val}");
RefAsParamenter(ref a1);
Console.WriteLine($"After method call: {a1.Val}");
}
}
輸出
Before method call: 20
After member assignment: 50
After new object creation: 20
After method call: 20
你肯定記還記得,參考引數充當形參的別名,這樣一來上面的代碼就很好解釋了,下圖闡明了上述代碼的一下幾點,
- 在方法呼叫時,形參和實參指向堆中相同的物件,
- 對成員值的修改會同時影響到形參和實參,
- 當方法創建新的物件并賦值給形參時,形參和實參的參考都指向該新物件,
- 在方法結束后,實參指向在方法內創建的新物件,

十四、輸出引數
輸出引數用于從方法體內把資料傳出到呼叫代碼,它們的行為與參考引數類似,如同參考引數,輸出引數有以下要求,
- 必須在宣告和呼叫中都使用修飾符,輸出引數的修飾符是
out而不是ref, - 和參考引數類似,實參必須是變數,而不能是其他型別的運算式,這是有道理的,因為方法需要記憶體位置來保存回傳值,
例如,下面的代碼宣告了名為 MyMethod 方法,它帶有單個輸出引數,
void MyMethod(out int val)
{
...
}
...
int y = 1;
MyMethod(out y); // 方法呼叫
與參考引數類似,輸出引數的形參充當實參的別名,形參和實參都是同一塊記憶體位置的名稱,顯然,在方法內對形參做的任何改變,在方法執行完成之后,(通過實參變數)都是可見的,
與參考引數不同,輸出引數有以下要求,
- 在方法內部,給輸出引數賦值之后才能讀取它,這意味著引數的初始值是無關的,而且沒有必要在方法呼叫之前為實參賦值,
- 在方法內部,在方法回傳之前,代碼中每條可能的路徑都必須為所有輸出引數賦值,
因為方法內的代碼在讀取輸出引數之前必須對其寫人,所以不可能使用輸出引數把資料傳入方法,事實上,如果方法中有任何執行路徑試圖在方法給輸出引數賦值之前讀取它的值,編譯器就會產生一條錯誤訊息,
public void Add2(out int outValue)
{
int var1 = outValue + 2; // 錯誤!在方法賦值之前,無法讀取輸出變數
}
例如,下面的代碼再次展示了方法 MyMetod,但這次試用了輸出引數,
class MyClass
{
public int Val = 20;
}
class Program
{
static void MyMethod(out MyClass f1, out int f2)
{
f1 = new MyClass();
f1.Val = 25;
f2 = 15;
}
static void Main()
{
MyClass a1 = null;
int a2;
MyMethod(out a1, out a2); // 呼叫方法
}
}
下圖闡述了在方法執行的不同階段中實參和形參的值,
- 在方法呼叫之前,將要被用作實參的變數
a1和a2已經在堆疊里了, - 在方法的開始,形參的名稱被設定為實參的別名,你可以認為變數
a1和f1指向的是相同的記憶體位置,也可以認為a2和f2指向的是相同的記憶體位置,a1和a2不在作用域之內,所以不能在MyMethod中方為, - 在方法內部,代碼創建了一個
MyClass型別的物件并把它賦值給f1,然后賦一個值給f1的欄位,也賦一個值給f2,對f1和f2的賦值都是必須的,因為他們是輸出引數, - 方法執行之后,形參的名稱已經失效,但是參考型別的
a1和值型別的a2的值都被方法內的行為改變了,

從 C# 7.0 開始,你不再需要預先宣告一個變數來用作 out 引數了,你可以在呼叫方法時在引數串列中添加一個變數型別,它將作為變數宣告,
例如,在之前的代碼示例中,Main 方法宣告了 a1 和 a2 變數,然后在呼叫 MyMethod 時將它們用作 out 引數,如下所示:
static void Main()
{
MyClass a1 = null;
int a2;
MyMethod(out a1,out a2);
}
如果使用新的語法,你可以:
- 消除顯式的變數宣告;
- 直接在方法呼叫時加入變數型別宣告,
下面的代碼演示了新的形式:
static void Main()
{
MyMethod(out MyClass a1,out int a2);
}
雖然 a1 和 a2 只在方法呼叫陳述句中進行了宣告,但它們也可以在方法呼叫完成后繼續使用,如以下代碼所示:
static void Main()
{
MyMethod(out MyClass a1, out int a2); // 呼叫方法
Console.WriteLine(a2); // 使用回傳的值
a2 += 5; // 寫入變數
Console.WriteLine(a2);
}
輸出
15
20
十五、引數陣列
至此,在本篇所述的引數型別中,一個形參必須嚴格地對應一個實參,引數陣列則不同,它允許特定型別的零個或多個實參對應一個特定的形參,引數陣列的重點如下,
- 在一個引數串列中只能有一個引數陣列,
- 如果有,它必須是串列中的最后一個,
- 由引數陣串列示的所有引數必須是同一型別,
宣告一個引數陣列時必須做的事如下,
- 在資料型別前使用
params修飾符, - 在資料型別后放置一組空的方括號,
下面的方法頭展示了 int 型引數陣列的宣告語法,在這個示例中,形參 inVals 可以代表零個或多個 int 實參,
void ListInts(params int[] inVals)
{
...
}
型別名后面的空方括號指明了引數是一個整數陣列,
- 陣列是一組有序的同一型別的資料項,
- 陣列使用一個數字索引進行訪問,
- 陣列是一個參考型別,因此它的所有資料項都保存在堆中,
15.1 方法呼叫
可以使用兩種方式為引數陣列提供實參,
- 一個用逗號分隔的該資料型別元素的串列,所有元素必須是方法宣告中指定的型別,
ListInts(10, 20, 30); // 3 個 int
- 一個該資料型別遠素的一維陣列,
int intArray = {1, 2, 3};
ListInts(intArray); // 一個陣列變數
請注意,在這些示例中,沒有在呼叫時使用 params 修飾符,引數陣列中修飾符的使用與其他引數型別的模式并不相符,
- 其他引數型別是一致的,要么使用修飾符,要么都不使用修飾符,
- 值引數的宣告和呼叫都不帶修飾符,
- 參考引數和輸出引數在兩個地方都需要修飾符,
params修飾符的用法總結如下,- 在宣告中需要修飾符,
- 在呼叫中不允許有修飾符,
延伸式
方法呼叫的第一種形式有時被稱為延伸式,這種形式在呼叫中使用獨立的實參,
例如,下面代碼中的方法 ListInts 的宣告可以匹配其后所有的方法呼叫,雖然它們的實引數目不同,
void ListInts(params int[] inVals) // 方法宣告
{
...
}
ListInts(); // 0 個實參
ListInts(1, 2, 3); // 3 個實參
ListInts(4, 5, 6, 7); // 4 個實參
ListInts(8, 9, 10, 11, 12); // 5 個實參
在使用一個為引數陣列使用獨立實參的呼叫時,編譯器做下面幾件事,
- 接受實參串列,用它們在堆中創建并初始化一個陣列,
- 把陣列的參考保存到堆疊中的形參里,
- 如果在對應形引陣列的位置沒有實參,編譯器會創建一個有零個元素的陣列類使用,
例如,下面的代碼宣告了一個名為 ListInts 的方法,它接受有一個引數陣列,Main 宣告了 3 個整數并把它們傳給了陣列,
class MyClass
{
public void ListInts(params int[] inVals)
{
if((inVals != null) && (inVals.Length !=0))
{
for(int i = 0; i < inVals.Length; i++)
{
inVals[i] = inVals[i] * 10;
Console.WriteLine($"{inVals[i]}");
}
}
}
}
class Program
{
static void Main()
{
int first = 5, second = 6, third = 7; // 宣告 3 個 int
MyClass mc = new MyClass();
mc.ListInts(first, second, third); // 呼叫方法
Console.WriteLine($"{first}, {second}, {third}");
}
}
輸出
50
60
70
5, 6, 7
下圖闡明了在方法執行的不同階段實參和形參的值,
- 方法呼叫之前,3 個實參已經在堆疊里,
- 在方法的開始,3 個實參被用于初始化堆中的陣列,并且陣列的參考被賦值給形參
intVals, - 在方法內部,代碼首先檢查以確認陣列參考不是
null,然后處理陣列,把每個元素乘以 10 并保存回去, - 方法執行之后,形參
inVals失效,
關于引數陣列,需要記住的一點是當陣列在堆中被創建時,實參的值被復制到陣列中,這樣,它們像值引數,
- 如果陣列引數是值型別,那么值被復制,實參在方法內部不受影響,
- 如果陣列引數是參考型別,那么參考被復制,實參參考的物件在方法內部會受到影響,

15.2 將陣列作為實參
也可以在方法呼叫之前創建并組裝一個陣列,把單一的陣列變數作為實參傳遞,這種情況下,編譯器使用你的陣列而不是重新創建一個,
例如,下面的代碼使用前一個示例中宣告的方法 ListInts,在這段代碼中,Main 創建一個陣列,并用陣列變數而不是使用獨立的整數作為實參,
static void Main()
{
int[] myArr = new int[]{5, 6, 7}; // 創建并初始化陣列
MyClass mc = new MyClass();
mc.ListInts(myArr); // 呼叫方法來列印值
foreach(int x in myArr)
Console.WriteLine($"{x}"); // 輸出每個元素
}
輸出
50
60
70
50
60
70
十六、引數型別總結
因為有 4 中引數型別,所以有時很難記住它們的不同特征,下表對它們做了總結,以便于比較和對照,
| 引數型別 | 修飾符 | 是否在宣告時使用 | 是否在呼叫時使用 | 執行 |
|---|---|---|---|---|
| 值 | 無 | 系統把實參的值復制到形參 | ||
| 參考 | ref | 是 | 是 | 形參是實參的別名 |
| 輸出 | out | 是 | 是 | 僅包含一個回傳的值,形參是實參的別名 |
| 陣列 | params | 是 | 否 | 允許傳遞可變數目的實參到方法 |
十七、ref 區域變數和 ref 回傳
在本篇前面你已經看到了,你可以使用 ref 關鍵字傳遞一個物件參考給方法呼叫,這樣在呼叫背景關系中,對物件的任何改動在方法回傳后依然可見,ref 回傳功能則相反,它允許你將一個參考發送到方法外,然后在呼叫背景關系內使用這個參考,一個相關的功能是 ref 區域變數,它允許一個變數是另一個變數的別名,
我們將從 ref 區域變數這個功能開始講解,下面是關于 ref 區域變數功能的重要事項,
- 你可以使用這個功能創建一個變數的別名,即使參考的物件是值型別,
- 對任意一個變數的賦值都會反映到另一個變數上,因為它們參考的是相同的物件,即使是值型別,
創建別名的語法需要使用關鍵字 ref 兩次,一次是在別名宣告的型別的前面,另一次是在賦值運算子的右邊,“被別名”的變數的前面,如下所示:
ref int y = ref x;
下面的代碼是一個示例,其中使用 ref 區域變數功能創建了變數 × 的一個別名,叫作 y,當 x 改變時,y 也會變,反之亦然,
class Program
{
static void Main()
{
int x = 2;
ref int y = ref x;
Console.WriteLine($"x = {x}, y = {y}");
x = 5;
Console.WriteLine($"x = {x}, y = {y}");
y = 6;
Console.WriteLine($"x = {x}, y = {y}");
}
}
輸出
x = 2, y = 2
x = 5, y = 5
x = 6, y = 6
但是,別名功能不是 ref 區域變數功能最常見的用途,實際上,它經常和 ref 回傳功能一起使用,ref 回傳功能提供了一種使方法回傳變數參考而不是變數值的方法,這里需要的額外語法也使用了 ref 關鍵字兩次:
- 一次是在方法的回傳型別宣告之前
- 另一次是在
return關鍵字之后,被回傳物件的變數名之前
下面的代碼演示了一個例子,注意,在方法呼叫之后,因為呼叫了修改 ref 區域變數的代碼,所以類的欄位值改變了,
class Simple
{
private int Score = 5;
public ref int RefToValue() // 注意 ref 關鍵字
{
return ref Score; // 注意 ref 關鍵字
}
public void Display()
{
Console.WriteLine($"Value inside class object: {Score}");
}
}
class Program
{
static void Main()
{
Simple s = new Simple();
s.Display();
ref int v1OutSide = ref s.RefToValue(); // 注意 ref 關鍵字
v1OutSide = 10; // 在呼叫域外修改值
s.Display();
}
}
輸出
Value inside class object: 5
Value inside class object: 10
另一個可能有用的例子是 Math 庫中 Max 方法的變形,提供兩個數字型別的變數,Math.Max 能夠回傳兩個值中較大的那個,但是,假設你想回傳的是包含較大值的變數的參考,而不是實際的值,為此,你可以使用 ref 回傳功能,如以下代碼所示,
class Program
{
static ref int Max(ref int p1, ref int p2)
{
if(p1 > p2)
{
return ref p1; // 回傳參考,而不是值
}
else
{
return ref p2; // 回傳參考,而不是值
}
}
static void Main()
{
int v1 = 10;
int v2 = 20;
Console.WriteLine("Start");
Console.WriteLine($"v1: {v1}, v2: {v2}\n");
ref int max = ref Max(ref v1, ref v2);
Console.WriteLine("After assignment");
Console.WriteLine($"max: {max}\n");
max++;
Console.WriteLine("After increment");
Console.WriteLine($"max: {max}, v1: {v1}, v2: {v2}");
}
}
輸出
Start
v1: 10, v2: 20
After assignment
max: 20
After increment
max: 21, v1: 10, v2: 21
這個功能有如下額外限制,
- 你不能將回傳型別是
void的方法宣告為ref回傳方法, ref return運算式不能回傳如下內容:- 空值
- 常量
- 列舉成員
- 類或者結構體的屬性
- 指向只讀位置的指標
ref return運算式只能指向原先就在呼叫域內的位置,或者欄位,所以它不能指向方法的區域變數,ref區域變數只能被賦值一次,也就是說,一旦初始化,它就不能指向不同的記憶體位置了,- 即使將一個方法宣告為
ref回傳方法,如果在呼叫該方法時省略了ref關鍵字,則回傳的將是值,而不是指向值的記憶體位置的指標, - 如果將
ref區域變數作為常規的實際引數傳遞給其他方法,則該方法僅獲取該變數的一個副本,盡管ref區域變數包含指向記憶體位置的指標,但是當以這種方式使用時,它會傳遞值而不是參考,
十八、方法多載
一個類中可以有多個同名方法,這叫作方法多載(method overloading),使用相同名稱的每個方法必須有一個和其他方法不同的簽名(signature),
請注意,方法多載(method overloading)的概念與繼承中“方法覆寫”(method overriding)不同
- 方法的簽名由下列資訊組成,它們在方法宣告的方法頭中:
- 方法的名稱;
- 引數的數目;
- 引數的資料型別和順序;
- 引數修飾符,
- 回傳型別不是簽名的一部分,而我們往往誤認為它是簽名的一部分,
- 請注意,形參的名稱也不是簽名的一部分,
long AddValues(int a, out int b)
{
...
}

例如,下面 4 個方法是方法名 AddValues 的多載:
class A
{
long AddValues(int a, int b)
{
return a + b;
}
long AddValues(int c, int d, int e)
{
return c + d + e;
}
long AddValues(float f, float g)
{
return (long)(f + g);
}
long AddValues(long h, long m)
{
return h + m;
}
}
下面的代碼展示了一個非法的方法多載,兩個方法僅回傳型別和形參名不同,但它們仍有相同的簽名,因為它們的方法名相同,而且引數的數目、型別和順序也相同,編譯器會對這段代碼生成一條錯誤訊息,
class B
{
long AddValues(long a, long b) // 錯誤,相同的簽名
{
return a + b;
}
int AddValues(long c, long d) // 錯誤,相同的簽名
{
return c + d;
}
}
十九、命名引數
至此我們用到的所有引數都是位置引數,也就是說每一個實參的位置都必須與相應的形參位置對應,
此外,C# 還允許我們使用命名引數(named parameter),只要顯式指定引數的名字,就可以以任意順序在方法呼叫中列出實參,細節如下,
- 方法的宣告沒有什么不一樣,形參已經有名字了,
- 不過在呼叫方法的時候,形參的名字后面跟著冒號和實際的引數值或運算式,如下面的方法呼叫所示,在這里
a、b、C是Calc方法 3 個形參的名字,
c.Calc(c:2, a:4, b:3);

class MyClass
{
public int Calc(int a, int b, int c) // 方法宣告和引數串列沒有什么不一樣
{
return (a + b) * c;
}
static void Main()
{
MyClass mc = new MyClass();
int result = mc.Calc(c:2, a:4, b:3); // 在方法呼叫中包含引數名
Console.WriteLine($"{result}");
}
}
在使用命名引數的時候,需要在方法呼叫中包含引數名,無序對方法宣告做任何改變,
在呼叫的時候,你可以同時使用位置引數和命名引數,但所有位置引數必須先列出,例如,下面的代碼演示了 Calc 方法的宣告及其使用位置引數和命名引數不同組合的 5 種呼叫方式,
class MyClass
{
public int Calc(int a, int b, int c) // 方法宣告和引數串列沒有什么不一樣
{
return (a + b) * c;
}
static void Main()
{
MyClass mc = new MyClass();
int r0 = mc.Calc(4, 3, 2); // 位置引數
int r1 = mc.Calc(4, b:3, c:2); // 位置引數和命名引數
int r2 = mc.Calc(4, c:2, b:3); // 交換了順序
int r3 = mc.Calc(c:2, b:3, a:4); // 所有都是命名引數
int r4 = mc.Calc(c:2, b:1+2, a:3+1); // 命名引數運算式
Console.WriteLine($"{r0}, {r1}, {r2}, {r3}, {r4}");
}
}
輸出
14, 14, 14, 14, 14
命名引數對于自描述的程式來說很有用,因為我們可以在方法呼叫的時候顯示哪個值賦給哪個形參,例如,如下代碼呼叫了兩次 GetCylinderVolume,第二次呼叫具有更多的資訊并且更不容易出錯,
class MyClass
{
double GetCylinderVolume(double radius, double height)
{
return 3.1416 * radius * radius * height;
}
static void Main()
{
MyClass mc = new MyClass();
double volume;
volume = mc.GetCylinderVolume(3.0, 4.0);
volume = mc.GetCylinderVolume(radius:3.0, height:4.0); // 命名引數可以在方法呼叫的時候顯示哪個值賦給哪個形參,不容易出錯
}
}
二十、可選引數
C# 還允許使用可選引數(optional parameter),所謂可選引數就是可以在呼叫方法的時候包含這個引數,也可以省略它,
為了表明某個引數是可選的,你需要在方法宣告中為訪引數提供默認值,指定默認值的語法和初始化區域變數的語法一樣,如下面代碼的方法宣告所示,在代碼中,
- 形參
b的默認值設定成 3; - 因此,如果在呼叫方法的時候只有一個引數,方法會使用 3 作為第二個引數的初始值,
class MyClass
{
public int Calc(int a, int b = 3)
{
return a + b;
}
static void Main()
{
MyClass mc = new MyClass();
int r0 = mc.Calc(5,6); // 使用顯示值
int r1 = mc.Calc(5); // 為 b 使用默認值
Console.WriteLine($"{r0}, {r1}");
}
}
輸出
11, 8
對于可選引數的宣告,我們需要知道如下幾個重要事項,
-
不是所有的引數型別都可以作為可選引數,下表列出了何時可以使用可選引數,
-
只要值型別的默認值在編譯的時候可以確定,就可以使用值型別作為可選引數,
-
只有在默認值是
null的時候,參考型別才可以用作可選引數,- 值 ref out params 值型別 是 否 否 否 參考型別 只允許 null 的默認值 否 否 否
-
-
所有必填引數(required paramenter)必須在可選引數宣告之前宣告,如果有
params引數,必須在所有可選引數之后宣告,下圖演示了這種語法順序,

在之前的示例中我們已經看到了,可以在方法呼叫的時候省略相應的實參,從而為可選引數使用默認值,但是,不能隨意省略可選引數的組合,因為在很多情況下這么做會導致使用哪些可選引數變得不明確,規則如下:
- 你必須從可選引數串列的最后開始省略,一直到串列開頭,
- 也就是說,你可以省略最后一個可選引數,或是最后 N 個可選引數,但是不能隨意選擇省略任意的可選引數,省略必須從最后開始,
class MyClass
{
public int Calc(int a = 2, int b = 3, int c = 4)
{
return (a + b) * c;
}
static void Main()
{
MyClass mc = new MyClass();
int r0 = mc.Calc(5,6,7); // 使用所有的顯示值
int r1 = mc.Calc(5,6); // 為 c 使用默認值
int r2 = mc.Calc(5); // 為 b 和 c 使用默認值
int r3 = mc.Calc(); // 使用所有的默認值
Console.WriteLine($"{r0}, {r1}, {r2}, {r3}");
}
}
輸出
77, 44, 32, 20
如果需要隨意省略可選引數串列中的可選引數,而不是從串列的最后開始,那么必須使用可選引數的名字來消除賦值的歧義,在這種情況下,你需要結合利用命名引數和可選引數的特性,下面的代碼演示了位置引數、可選引數、和命名引數的這種用法,
class MyClass
{
public double GetCylinderVolume(double radius = 3.0, double height = 4.0)
{
return 3.1416 * radius * radius * height;
}
static void Main()
{
MyClass mc = new MyClass();
double volume;
volume = mc.GetCylinderVolume(3.0,4.0); // 位置引數
Console.WriteLine($"Volume = {volume}");
volume = mc.GetCylinderVolume(radius:2.0); // 使用 height 默認值
Console.WriteLine($"Volume = {volume}");
volume = mc.GetCylinderVolume(height:2.0); // 使用 radius 默認值
Console.WriteLine($"Volume = {volume}");
volume = mc.GetCylinderVolume(); // 使用兩個默認值
Console.WriteLine($"Volume = {volume}");
}
}
輸出
Volume = 113.0976
Volume = 50.2656
Volume = 56.5488
Volume = 113.0976
二十一、堆疊幀
至此,我們已經知道了區域變數和引數是位于堆疊上的,下面深入探討一下其組織,
在呼叫方法的時候,記憶體從堆疊的頂部開始分配,保存和方法關聯的一些資料項,這塊記憶體叫作方法的堆疊幀(stack frame),
- 堆疊幀包含的記憶體保存如下內容,
- 回傳地址,也就是在方法退出的時候繼續執行的位置,
- 分配記憶體的引數,也就是方法的值引數,還可能是引數陣列(如果有的話),
- 和方法呼叫相關的其他管理資料項,
- 在方法呼叫時,整個堆疊幀都會壓人堆疊,
- 在方法退出的時候,整個堆疊幀都會從堆疊上彈出,彈出堆疊幀有的時候也叫作堆疊展開(unwind),
例如,如下代碼宣告了 3 個方法,Main 呼叫 MethodA,MethodA 又呼叫 MedhodB,創建了 3 個堆疊楨,在方法退出的時候,堆疊展開,
class Program
{
static void MethodA(int par1, int par2)
{
Console.WriteLine($"Enter MethodA: {par1}, {par2}");
MethodB(11,18); // 呼叫 MethodB
Console.WriteLine("Exit MethodA");
}
static void MethodB(int par1, int par2)
{
Console.WriteLine($"Enter MethodB: {par1}, {par2}");
Console.WriteLine("Exit MethodB");
}
static void Main()
{
Console.WriteLine("Enter Main");
MethodA(15,30); // 呼叫 MethodA
Console.WriteLine("Exit Main");
}
}
輸出
Enter Main
Enter MethodA: 15, 30
Enter MethodB: 11, 18
Exit MethodB
Exit MethodA
Exit Main
下圖演示了在呼叫方法時堆疊幀壓入堆疊的程序和方法結束后堆疊展開的程序,

二十二、遞回
除了呼叫其他方法,方法也可以呼叫自身,這叫作遞回,
遞回會產生很優雅的代碼,比如下面計算階乘數的方法就是如此,注意在本例的方法內部,方法使用比輸入引數小 1 的實參呼叫自身,
int Factorial(int inValue)
{
if(inValue <= 1)
{
return inValue;
}
else
{
return inValue * Factorial(inValue - 1); // 呼叫自身
}
}
呼叫方法自身的機制和呼叫其他方法其實是完全一樣的,都是為每一次方法呼叫把新的堆疊幀壓入堆疊頂,
例如,在下面的代碼中,Count 方法使用比輸入引數小 1 的值呼叫自身,然后列印輸入的引數,隨著遞回越來越深,堆疊也越來越大,
class Program
{
public void Count(int inVal)
{
if(inVal == 0)
{
return;
}
Count(inVal - 1); // 呼叫自身
Console.WriteLine($"{inVal}");
}
static void Main()
{
Program pr = new Program();
pr.Count(3);
}
}
輸出
1
2
3
下圖演示了這段代碼,注意,如果輸入值 3,那么 Count 方法就有 4 個不同的獨立堆疊幀,每一個都有其自己的輸出引數值 inVal,

原文鏈接:https://www.dotnetprimer.com/csharp/01-methods-and-parameters-in-csharp/
(完)
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/224539.html
標籤:.NET技术
上一篇:C# 中類的基本概念
