CodeGym /课程 /C# SELF /局部函数捕获变量

局部函数捕获变量

C# SELF
第 11 级, 课程 5
可用

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
闭包:变量 count在方法结束后还活着

这里,即使GetCounter方法已经结束,count变量还活着,因为被返回的函数捕获了它。这就叫闭包(closure)——后面会专门讲,但局部函数的机制其实是一样的。

6. 常见错误和有趣场景

在调用局部函数前修改变量

有时候你会遇到这种情况:局部函数捕获的变量在调用前被改了——结果可能和你想的不一样。

例子:


void Example()
{
    int x = 42;
    void PrintX() {  Console.WriteLine(x);  } 

    x = 100; // 变量被改了!
    PrintX(); // 输出100,不是42!
}

小技巧:局部函数总是看到变量在调用时的最新值。

在for/foreach循环里捕获变量(再说一次)

经典大坑:无论是面试还是大项目,写循环逻辑时一定要检查:我是不是捕获了循环里的“活”变量?它到底会怎么表现?

2
任务
C# SELF, 第 11 级, 课程 5
已锁定
简单使用本地函数和变量捕获
简单使用本地函数和变量捕获
2
任务
C# SELF, 第 11 级, 课程 5
已锁定
用本地函数初始化数字计数器
用本地函数初始化数字计数器
1
调查/小测验
元组和本地函数第 11 级,课程 5
不可用
元组和本地函数
元组相比 out 和匿名类型的优势
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION