C#中關於變數的作用域不易理解的特例
阿新 • • 發佈:2020-11-02
變數的作用域的基本規則是很簡單容易理解的,但有幾個特例實在是很費解。
第一個,使用迴圈時:
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語句後,出了塊可以正常訪問了,這是怎麼回事。看來想要解開這個謎,恐怕得深入底層了。可惜現在還不瞭解,如果高手路過,請不吝賜教。