1. 入门
回忆一下作用域
想象一下,变量就像是大办公室里的员工,而方法、循环和代码块就是房间和办公室。有些员工只能进自己的房间,有些则能在整栋楼里走动。谁能在哪出现——这就是他们的作用域(scope)。
作用域决定了你在程序里哪里能“看到”某个变量,以及能不能用它。
主要的作用域类型
在C#里,主要有这些作用域:
| 作用域 | 例子 | 变量“可见”范围 |
|---|---|---|
| 局部 | 在方法或代码块内部 | 只在这个块里 |
| 方法参数 | 在方法签名里 | 只在方法内部 |
| 类变量(字段) | 在类体里但不在方法里 | 这个类的所有方法里 |
| 循环/条件里的变量 | 在循环/if里 |
只在这些里 |
带注释的例子
public class Office
{
int buildingNumber = 50; // 类字段:在所有方法里都能看到 public void PrintInfo() {
int roomNumber = 101; // 局部变量:只在PrintInfo里可见 if (roomNumber > 100) {
int deskNumber = 5; // 只在这个if块里可见 Console.WriteLine(deskNumber);
} Console.WriteLine(deskNumber); //
错误! deskNumber在这里已经不可见了
}
}
2. 局部函数和作用域
谁能“看到”谁?
当你在方法里(甚至在循环或条件里)声明局部函数时,它和上面声明的变量处于同一个作用域。局部函数就像是“同一个房间”的一部分。
例子
局部函数能看到外部作用域的变量
void PrintWithPrefix(string message)
{
string
prefix = "[日志]: "; void Print() {
Console.WriteLine(
prefix +
message); // 能看到这两个变量!
} Print();
}
这里prefix和message这两个变量在局部函数Print里是可见的,因为它们是在同一个或更外层的作用域里声明的。
这里有几个作用域?
上面例子里:
- 有方法PrintWithPrefix的作用域
- 里面还有函数Print的作用域
3. 变量捕获(Capture)
变量捕获——就是局部函数用到了在它外面但同一作用域里声明的变量。
局部函数和匿名方法(lambda表达式,后面会讲)会记住(或者说“捕获”)它们声明时能访问到的所有变量。
可以说,函数像是拍了个快照(capture)——即使很久以后调用,也能用这些变量。
示意图
Main方法
└─ 变量x
└─ 局部函数F() ← “捕获”了x
例子——最简单的捕获
void CounterExample()
{
int counter = 0;
void Increase()
{
counter++; // 这个函数捕获了counter变量
}
Increase();
Increase();
Console.WriteLine(counter); // 输出2
}
这里局部函数Increase被调用两次后,counter的值变成了2。
4. 变量捕获的用法
变量捕获让你可以很方便地在方法和局部函数之间“传递”数据,不用每次都加参数。
如果没有捕获机制,你就得把所有变量都作为参数传进去:
void CounterExampleWithoutCapture()
{
int counter = 0;
void Increase(ref int c)
{
c++;
}
Increase(ref counter);
Increase(ref counter);
Console.WriteLine(counter);
}
这样很麻烦——为啥要一直写ref,还把函数签名搞复杂了,其实它完全可以直接“看到”外面的变量啊!
5. 局部函数和变量在方法外的生命周期
变量会比方法活得久吗?
如果你把局部函数(或者带lambda的委托)传到当前方法外面,捕获的变量就不会在方法结束时“死掉”。CLR(.NET虚拟机)会帮你把需要的东西都“拽住”留在内存里。
例子:函数活在方法外
Func<int> GetCounter()
{
int count = 0;
int Increment()
{
count++;
return count;
}
return Increment; // 把函数返回到外面!
}
var counter = GetCounter();
Console.WriteLine(counter()); // 1
Console.WriteLine(counter()); // 2
这里,即使GetCounter方法已经结束,count变量还活着,因为被返回的函数捕获了它。这就叫闭包(closure)——后面会专门讲,但局部函数的机制其实是一样的。
6. 常见错误和有趣场景
在调用局部函数前修改变量
有时候你会遇到这种情况:局部函数捕获的变量在调用前被改了——结果可能和你想的不一样。
例子:
void Example()
{
int x = 42;
void PrintX() { Console.WriteLine(x); }
x = 100; // 变量被改了!
PrintX(); // 输出100,不是42!
}
小技巧:局部函数总是看到变量在调用时的最新值。
在for/foreach循环里捕获变量(再说一次)
经典大坑:无论是面试还是大项目,写循环逻辑时一定要检查:我是不是捕获了循环里的“活”变量?它到底会怎么表现?
GO TO FULL VERSION