跳躍陳述式 - break
、continue
、return
和 goto
跳躍陳述式會無條件地傳移控制權。 break
陳述式會終止最接近的封閉式反覆項目陳述式或 switch
陳述式。 continue
陳述式會啟動最接近的封閉式反覆項目陳述式。 return
陳述式:終止執行在其中出現的函數,並且將控制權傳回給呼叫端。 goto
陳述式:將控制權轉移至以標籤標示的陳述式。
如需擲回例外狀況並無條件傳輸控件之 throw
陳述式的詳細資訊,請參閱例外狀況處理陳述式一文的 throw
陳述式一節。
break
陳述式
break
陳述式會終止最接近的封閉式反覆項目陳述式 (即 for
、foreach
、while
或 do
迴圈) 或 switch
陳述式。 break
陳述式會將控制權轉移至已終止陳述式後面的陳述式 (如果有的話)。
int[] numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
foreach (int number in numbers)
{
if (number == 3)
{
break;
}
Console.Write($"{number} ");
}
Console.WriteLine();
Console.WriteLine("End of the example.");
// Output:
// 0 1 2
// End of the example.
在巢狀迴圈中,break
陳述式只會終止包含它的最內層迴圈,如下列範例所示:
for (int outer = 0; outer < 5; outer++)
{
for (int inner = 0; inner < 5; inner++)
{
if (inner > outer)
{
break;
}
Console.Write($"{inner} ");
}
Console.WriteLine();
}
// Output:
// 0
// 0 1
// 0 1 2
// 0 1 2 3
// 0 1 2 3 4
當您在迴圈內部使用 switch
陳述式時,參數區段結尾的 break
陳述式只會將控制權移出 switch
陳述式。 包含 switch
陳述式的迴圈不會受到影響,如下列範例所示:
double[] measurements = [-4, 5, 30, double.NaN];
foreach (double measurement in measurements)
{
switch (measurement)
{
case < 0.0:
Console.WriteLine($"Measured value is {measurement}; too low.");
break;
case > 15.0:
Console.WriteLine($"Measured value is {measurement}; too high.");
break;
case double.NaN:
Console.WriteLine("Failed measurement.");
break;
default:
Console.WriteLine($"Measured value is {measurement}.");
break;
}
}
// Output:
// Measured value is -4; too low.
// Measured value is 5.
// Measured value is 30; too high.
// Failed measurement.
continue
陳述式
continue
陳述式會啟動最接近封閉式反覆項目陳述式的新反覆項目 (即 for
、foreach
、while
或 do
迴圈),如下列範例所示:
for (int i = 0; i < 5; i++)
{
Console.Write($"Iteration {i}: ");
if (i < 3)
{
Console.WriteLine("skip");
continue;
}
Console.WriteLine("done");
}
// Output:
// Iteration 0: skip
// Iteration 1: skip
// Iteration 2: skip
// Iteration 3: done
// Iteration 4: done
return
陳述式
return
陳述式會終止執行在其中出現的函數,並且將控制權和函數結果 (如果有的話) 傳回至呼叫端。
如果函數成員未計算值,則您可以使用不含運算式的 return
陳述式,如下列範例所示:
Console.WriteLine("First call:");
DisplayIfNecessary(6);
Console.WriteLine("Second call:");
DisplayIfNecessary(5);
void DisplayIfNecessary(int number)
{
if (number % 2 == 0)
{
return;
}
Console.WriteLine(number);
}
// Output:
// First call:
// Second call:
// 5
如上述範例所示,您通常會使用不含運算式的 return
陳述式來提早終止函數成員。 如果函數成員未包含 return
陳述式,則會在執行其最後一個陳述式之後終止。
如果函數成員會計算值,則您可以搭配使用 return
陳述式與運算式,如下列範例所示:
double surfaceArea = CalculateCylinderSurfaceArea(1, 1);
Console.WriteLine($"{surfaceArea:F2}"); // output: 12.57
double CalculateCylinderSurfaceArea(double baseRadius, double height)
{
double baseArea = Math.PI * baseRadius * baseRadius;
double sideArea = 2 * Math.PI * baseRadius * height;
return 2 * baseArea + sideArea;
}
return
陳述式具有運算式時,除非不同步,否則必須將該運算式隱含地轉換成函數成員的傳回類型。 從 async
函數所傳回的運算式必須隱含地轉換成 Task<TResult> 或 ValueTask<TResult> 或類型引數,而無論哪一個都是函數的傳回類型。 如果 async
函數的傳回類型是 Task 或 ValueTask,則您可以使用不含運算式的 return
陳述式。
參考傳回
根據預設,return
陳述式會傳回運算式的值。 您可以傳回變數的參考。 參考傳回值 (或 ref 傳回值) 是方法以傳參考方式傳回給呼叫者的值。 也就是說,呼叫端可以藉由方法修改所傳回的值,而且該變更會反映在呼叫方法的物件狀態中。 若要這樣做,請使用含 ref
關鍵字的 return
陳述式,如下列範例所示:
int[] xs = new int [] {10, 20, 30, 40 };
ref int found = ref FindFirst(xs, s => s == 30);
found = 0;
Console.WriteLine(string.Join(" ", xs)); // output: 10 20 0 40
ref int FindFirst(int[] numbers, Func<int, bool> predicate)
{
for (int i = 0; i < numbers.Length; i++)
{
if (predicate(numbers[i]))
{
return ref numbers[i];
}
}
throw new InvalidOperationException("No element satisfies the given condition.");
}
參考傳回值允許方法將變數參考 (而非值) 傳回給呼叫者。 呼叫者接著可以選擇將傳回的變數視為以傳值方式或以傳址方式傳回。 呼叫端可以建立本身為參考傳回值的新變數,稱為 ref 區域變數。 「參考傳回值」表示方法會將「參考」 (或別名) 傳回給某個變數。 該變數的範圍必須包含方法。 該變數的存留期必須延伸到傳回方法。 呼叫者對方法的傳回值所進行的修改,是針對方法傳回的變數進行。
宣告方法傳回「參考傳回值」,表示方法會將別名傳回給變數。 此設計意圖通常是呼叫端程式碼會透過別名來存取該變數 (包括進行修改)。 藉傳址傳回的方法不可以有傳回類型 void
。
為了讓呼叫者修改物件的狀態,參考傳回值必須儲存至明確定義為 參考變數的變數。
ref
傳回值是在已呼叫方法的範圍中,另一個變數的別名。 您可以將任何使用 ref 傳回值的用法,解譯為使用別名所代表的變數:
- 當您指派其值時,是將值指派給別名的變數。
- 當您讀取其值時,是讀取別名的變數值。
- 如果您「藉傳址」將其傳回,則是將別名傳回至同一個變數。
- 如果您「藉傳址」將其傳遞到另一個方法,則是將參考傳遞至別名的變數。
- 當您建立 ref 區域變數別名時,就是對相同變數建立新的別名。
ref 傳回必須是 ref-safe-context 至呼叫端方法。 這表示:
- 傳回值必須有超過方法執行期間的存留期。 換句話說,其不能是將其傳回之方法中的區域變數。 它可以是類別的執行個體或靜態欄位,也可以是傳遞給方法的引數。 嘗試傳回區域變數會產生編譯器錯誤 CS8168「無法藉傳址傳回本機 'obj',因為其非參考本機」。
- 傳回值不能是常值
null
。 使用 ref 傳回值的方法,可以將別名傳回給目前值為null
(未具現化) 值的變數,或是適用於實值類型的可為 Null 實值類型。 - 傳回值不能是常數、列舉成員、屬性的以傳值方式傳回值,或是
class
或struct
的方法。
此外,非同步方法上不允許參考傳回值。 在非同步方法完成執行之前,可能會傳回非同步方法,但其傳回值仍然為未知。
傳回「參考傳回值」的方法必須:
下列範例顯示滿足那些條件並傳回參考給名為 p
之 Person
物件的方法:
public ref Person GetContactInformation(string fname, string lname)
{
// ...method implementation...
return ref p;
}
這是更完整的 ref 傳回範例,其中顯示方法簽章和方法主體。
public static ref int Find(int[,] matrix, Func<int, bool> predicate)
{
for (int i = 0; i < matrix.GetLength(0); i++)
for (int j = 0; j < matrix.GetLength(1); j++)
if (predicate(matrix[i, j]))
return ref matrix[i, j];
throw new InvalidOperationException("Not found");
}
所呼叫的方法也可能將傳回值宣告為 ref readonly
,藉傳址方式將值傳回,並且強制使呼叫程式碼無法修改傳回值。 呼叫的方法可以將值儲存在區域 ref readonly
參考變數,以避免複製傳回值。
下面範例會定義具有 Title
和 Author
這兩個 String 欄位的 Book
類別。 它也會定義 BookCollection
類別,以包含 Book
物件的私用陣列。 以傳參考方式傳回個別書籍物件,方法是呼叫其 GetBookByTitle
方法。
public class Book
{
public string Author;
public string Title;
}
public class BookCollection
{
private Book[] books = { new Book { Title = "Call of the Wild, The", Author = "Jack London" },
new Book { Title = "Tale of Two Cities, A", Author = "Charles Dickens" }
};
private Book nobook = null;
public ref Book GetBookByTitle(string title)
{
for (int ctr = 0; ctr < books.Length; ctr++)
{
if (title == books[ctr].Title)
return ref books[ctr];
}
return ref nobook;
}
public void ListBooks()
{
foreach (var book in books)
{
Console.WriteLine($"{book.Title}, by {book.Author}");
}
Console.WriteLine();
}
}
呼叫者將 GetBookByTitle
方法所傳回的值儲存為 ref 區域變數時,呼叫者對傳回值進行的變更會反映在 BookCollection
物件中,如下列範例所示。
var bc = new BookCollection();
bc.ListBooks();
ref var book = ref bc.GetBookByTitle("Call of the Wild, The");
if (book != null)
book = new Book { Title = "Republic, The", Author = "Plato" };
bc.ListBooks();
// The example displays the following output:
// Call of the Wild, The, by Jack London
// Tale of Two Cities, A, by Charles Dickens
//
// Republic, The, by Plato
// Tale of Two Cities, A, by Charles Dickens
goto
陳述式
goto
陳述式會將控制權轉移至以標籤標示的陳述式,如下列範例所示:
var matrices = new Dictionary<string, int[][]>
{
["A"] =
[
[1, 2, 3, 4],
[4, 3, 2, 1]
],
["B"] =
[
[5, 6, 7, 8],
[8, 7, 6, 5]
],
};
CheckMatrices(matrices, 4);
void CheckMatrices(Dictionary<string, int[][]> matrixLookup, int target)
{
foreach (var (key, matrix) in matrixLookup)
{
for (int row = 0; row < matrix.Length; row++)
{
for (int col = 0; col < matrix[row].Length; col++)
{
if (matrix[row][col] == target)
{
goto Found;
}
}
}
Console.WriteLine($"Not found {target} in matrix {key}.");
continue;
Found:
Console.WriteLine($"Found {target} in matrix {key}.");
}
}
// Output:
// Found 4 in matrix A.
// Not found 4 in matrix B.
如上述範例所示,您可以使用 goto
陳述式來脫離巢狀迴圈。
提示
當您使用巢狀迴圈時,請考慮將個別迴圈重構為個別的方法。 這可能會導致更簡單且更容易閱讀的程式碼,而不需要 goto
陳述式。
您也可以使用 switch
陳述式中的 goto
陳述式,以將控制權轉移至具有常數大小寫標籤的參數區段,如下列範例所示:
using System;
public enum CoffeeChoice
{
Plain,
WithMilk,
WithIceCream,
}
public class GotoInSwitchExample
{
public static void Main()
{
Console.WriteLine(CalculatePrice(CoffeeChoice.Plain)); // output: 10.0
Console.WriteLine(CalculatePrice(CoffeeChoice.WithMilk)); // output: 15.0
Console.WriteLine(CalculatePrice(CoffeeChoice.WithIceCream)); // output: 17.0
}
private static decimal CalculatePrice(CoffeeChoice choice)
{
decimal price = 0;
switch (choice)
{
case CoffeeChoice.Plain:
price += 10.0m;
break;
case CoffeeChoice.WithMilk:
price += 5.0m;
goto case CoffeeChoice.Plain;
case CoffeeChoice.WithIceCream:
price += 7.0m;
goto case CoffeeChoice.Plain;
}
return price;
}
}
在 switch
陳述式內,您也可以使用 goto default;
陳述式以將控制權轉移至具有 default
標籤的參數區段。
如果目前函數成員中沒有具有給定名稱的標籤,或 goto
陳述式不在標籤範圍內,則會發生編譯時期錯誤。 也就是說,您無法使用 goto
陳述式以將控制權轉移出目前的函數成員,或傳送至任何巢狀範圍。
C# 語言規格
如需詳細資訊,請參閱 C# 語言規格的下列幾節: