变量的作用域的基本规则是很简单容易理解的,但有几个特例实在是很费解。
第一个,使用循环时:
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语句后,出了块可以正常访问了,这是怎么回事。看来想要解开这个谜,恐怕得深入底层了。可惜现在还不了解,如果高手路过,请不吝赐教。