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语句后,出了块可以正常访问了,这是怎么回事。看来想要解开这个谜,恐怕得深入底层了。可惜现在还不了解,如果高手路过,请不吝赐教。

原文地址:https://www.cnblogs.com/chengyb/p/13913806.html