1. 程式人生 > 實用技巧 >C#中關於變數的作用域不易理解的特例

C#中關於變數的作用域不易理解的特例

變數的作用域的基本規則是很簡單容易理解的,但有幾個特例實在是很費解。

第一個,使用迴圈時:

 1         static void Main(string[] args)
 2         {
 3             string str1 = "Hello1";     // 宣告並初始化的話單步執行的時候正常執行
 4             Console.WriteLine("{0}", str1);    // 正常訪問
 5 
 6             string str2;                // 只是宣告的話單步執行的時候會跳過該行
 7             str2 = "
Hello2"; // 先宣告再初始化 8 Console.WriteLine("{0}", str2); // 正常訪問 9 10 Test1(); 11 Test2(); 12 Test3(); 13 } 14 15 static void Test1() // 在單獨的塊中初始化 16 { 17 string str3; 18 { 19 str3 = "
Hello3"; // 先宣告再在單獨的塊中初始化 20 } 21 Console.WriteLine("{0}", str3); // 出了塊仍可以正常訪問 22 23 string str4; 24 { 25 //string str5; // 此處宣告的變量出了塊無法訪問 26 { 27 str4 = "Hello4"; // 先宣告再在巢狀的塊中初始化 28 }
29 } 30 Console.WriteLine("{0}", str4); // 出了塊仍可以正常訪問 31 //Console.WriteLine("{0}", str5); // error CS0103: 當前上下文中不存在名稱“str5” 32 } 33 34 static void Test2() // 初始化text 35 { 36 string text = null; // 宣告並初始化 37 for (int i = 0; i < 10; i++) 38 { 39 text = "Line " + Convert.ToString(i); 40 Console.WriteLine("{0}", text); 41 } 42 Console.WriteLine("Last text output in loop: {0}", text); 43 } 44 45 static void Test3() // 未初始化text,雖在迴圈中賦值,但出了迴圈不能訪問,編譯錯誤 46 { 47 string text; // 僅宣告而不初始化 48 for (int i = 0; i < 10; i++) // 如為在for括號內初始化的變數,出了迴圈仍能使用 49 { 50 text = "Line " + Convert.ToString(i); // 迴圈內初始化 51 Console.WriteLine("{0}", text); 52 } 53 //Console.WriteLine("Last text output in loop: {0}", text); 54 // error CS0165: 使用了未賦值的區域性變數“text”(推測:迴圈塊和普通塊可能不完全一樣) 55 }

Test3()中,僅宣告而不初始化text變數,雖在迴圈中賦值,但出了迴圈不能訪問,編譯錯誤。如果使用C語言,這種方式實測是完全沒問題的,但C#不知為什麼不行,為什麼這麼設計。

在C#高階程式設計(忘了第幾版了),是這麼說的:

* 迴圈之前賦給text空字串(或null),而在迴圈之後的程式碼中,該text就不會是空字串了,其原因並不明顯。
* 這種情況的解釋涉及到分配給text變數的記憶體空間,實際上任何變數都是這樣。
* 只宣告一個簡單變數型別,並不會引起其他的變化。只有在給變數賦值後,這個值才佔用一塊記憶體空間。
* 如果這種佔據記憶體空間的行為在迴圈中發生,該值實際上定義為一個區域性值,在迴圈的外部會超出了其作用域。
* 即使變數本身沒有區域性化到迴圈上,迴圈所包含的值也區域性化到該迴圈上。
* 但是,在迴圈外部賦值可以確保該值是主體程式碼的區域性值,在迴圈內部它仍處於其作用域中。
* 這意味著變數在退出主體程式碼塊之前是沒有超出作用域的,所以可以在迴圈外部訪問它的值。
這種說法勉強可以接受吧。但是Test1()中出了塊仍可以正常訪問,個人推測:迴圈的塊和普通的塊可能不完全一樣。

第二個,使用try塊時:

 1 static void Main(string[] args)
 2         {
 3 #if 初始化text
 4             string text = null;     // 宣告並初始化(單步執行的時候會執行)
 5             try
 6             {
 7                 text = "Hello!";
 8             }
 9             catch (Exception)
10             {
11             }
12             finally
13             {
14                 Console.WriteLine("Finally1: {0}", text);    // 可以正常訪問
15             }
16             Console.WriteLine("END1: {0}", text);            // 可以正常訪問
17 #else
18             string text;        // 僅宣告而不初始化(單步執行的時候會跳過),雖在try塊中賦值,但出了try塊不一定能使用
19             try
20             {
21                 text = "Hello!";
22                 //return;       // 實測沒作用
23             }
24             catch (Exception)
25             {
26                 return;         // return究竟起了什麼作用使得text可以訪問了?
27             }
28             finally
29             {
30                 //Console.WriteLine("Finally2: {0}", text);    // 出錯:使用了未賦值的區域性變數
31                 //return;       // error CS0157: 控制不能離開finally子句主體
32             }
33             Console.WriteLine("END2: {0}", text);    // 如果沒有catch中的return,這兒出錯:使用了未賦值的區域性變數
34 #endif
35         }

和上例一樣,但是偶然在catch塊中添加了return語句後,出了塊可以正常訪問了,這是怎麼回事。看來想要解開這個謎,恐怕得深入底層了。可惜現在還不瞭解,如果高手路過,請不吝賜教。