Young87

SmartCat's Blog

So happy to code my life!

游戏开发交流QQ群号60398951

当前位置:首页 >AI写作

c#题例-2025-06-26 15:31:52

当然可以!下面是一道**专家级别**的 C# 程序员逻辑面试题,涉及 **委托、泛型、异步编程、闭包和线程安全**等多个高级语言特性,适合考察候选人的综合理解能力和实际开发经验。

---

### 🧠 面试题:深入理解委托、闭包与异步执行顺序

#### 题目描述:

请分析以下代码,并回答控制台输出的内容是什么?并解释其背后的原因。

```csharp
using System;
using System.Threading.Tasks;

class Program
{
static void Main(string[] args)
{
var actions = new Action[5];

for (int i = 0; i < 5; i++)
{
Task.Delay(i * 100).ContinueWith(_ =>
{
int captured = i;
actions[captured] = () => Console.WriteLine(captured);
});
}

Task.Delay(600).Wait(); // Wait for async operations to complete

for (int i = 0; i < 5; i++)
{
actions[i]?.Invoke();
}
}
}
```

---

### ✅ 考察点:

- **闭包与变量捕获陷阱**
- **异步任务调度与执行顺序**
- **ContinueWith 的执行上下文**
- **数组初始化与延迟赋值行为**
- **线程安全问题(虽然本题未直接体现)**

---

### 💡 参考答案:

#### 控制台输出结果:
```
5
5
5
5
5
```

#### 原因解析:

1. **变量捕获陷阱(Closure Capture)**
在 `for` 循环中使用了 lambda 表达式来创建委托。由于 C# 中的循环变量在 C#5 之前是“共享”的(即使在 C#5+ 是每次迭代新建变量),但在 `ContinueWith` 中使用的 `i` 是被闭包捕获的,而不是立即求值。

2. **ContinueWith 异步执行时机**
每个 `Task.Delay(i * 100)` 会异步执行,并在其完成后触发 `ContinueWith`。这些 continue 操作不是立即执行,而是在主线程等待 `600ms` 后才陆续完成。

3. **循环结束后 `i=5`**
当所有异步操作开始执行时,主循环早已结束,此时 `i == 5`,所以所有的 `captured` 都等于 5(注意这里虽然有 `captured = i`,但此时的 `i` 已经是最终值 5)。

4. **actions 数组中的每个 Action 都打印 5**
因此当最后调用 `actions[i].Invoke()` 时,所有委托都捕获的是同一个值 5。

---

### 🚫 常见误区:

- 认为 `captured = i` 是在异步任务发生前就保存了当前 `i` 的值。
- 忽略了异步任务调度的延迟性,导致对闭包变量的理解错误。
- 混淆了同步和异步代码的执行顺序。

---

### 🔧 如何修复?

如果你希望每个 `Action` 打印的是 0 到 4,可以在循环内部引入一个局部变量:

```csharp
for (int i = 0; i < 5; i++)
{
int loopIndex = i; // 创建一个新的局部变量,每个循环独立
Task.Delay(i * 100).ContinueWith(_ =>
{
actions[loopIndex] = () => Console.WriteLine(loopIndex);
});
}
```

这样每个异步任务捕获的是不同的变量副本,输出就会变成:

```
0
1
2
3
4
```

---

如果你想我继续出一道类似的题目或不同方向(如反射、表达式树、性能优化等)的题目,也可以告诉我!

除特别声明,本站所有文章均为原创,如需转载请以超级链接形式注明出处:SmartCat's Blog

上一篇: c#题例-2025-06-26 22:18:44

下一篇: c#题例-2025-06-26 09:58:44

精华推荐