Young87

SmartCat's Blog

So happy to code my life!

游戏开发交流QQ群号60398951

当前位置:首页 >AI写作

c#题例-2025-06-25 09:38:48

当然可以!下面是一道**专家级别**的 C# 程序员逻辑面试题,涉及到 **委托、事件、闭包、线程安全和异步编程**等多个高级概念,考察候选人对语言机制的理解深度与实际应用能力。

---

### 🧠 面试题:异步事件订阅中的闭包陷阱

#### 问题描述:

请看以下 C# 控制台程序代码片段。这段代码使用了事件、委托、`async/await` 和循环结构。

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

public class EventPublisher
{
public event EventHandler DataReady;

public async Task PublishDataAsync()
{
await Task.Delay(100); // 模拟异步操作
DataReady?.Invoke(this, "Hello World");
}
}

class Program
{
static async Task Main(string[] args)
{
var publisher = new EventPublisher();

for (int i = 0; i < 5; i++)
{
int localCopy = i;
publisher.DataReady += async (sender, e) =>
{
await Task.Delay(localCopy * 100); // 模拟延迟
Console.WriteLine($"Received data after {localCopy * 100}ms: {e}");
};
}

await publisher.PublishDataAsync();
Console.WriteLine("Published data.");
Console.ReadLine(); // 防止控制台退出
}
}
```

#### 问题:

1. 这段代码输出的结果是什么?为什么?
2. 是否存在潜在的 **内存泄漏或线程安全问题**?如果有,请指出。
3. 如何改进这段代码以避免可能的问题,并确保每个事件处理器按预期执行?

---

### 🔍 参考答案要点:

#### 1. 输出结果分析:

- 所有订阅者会在 `publisher.PublishDataAsync()` 中被触发。
- 每个订阅者的 `localCopy` 是循环变量 `i` 的副本,不会出现常见的“闭包陷阱”(因为用了 `int localCopy = i;`)。
- 所有事件处理函数会并发执行(因为是 `async void`),主函数不会等待它们完成。
- 因此,`"Published data."` 很可能会在所有事件处理之前打印。
- 实际输出顺序不确定,但大致如下:

```
Published data.
Received data after 0ms: Hello World
Received data after 100ms: Hello World
Received data after 200ms: Hello World
Received data after 300ms: Hello World
Received data after 400ms: Hello World
```

但由于是 `async void`,控制台有可能提前关闭(如果没有 `Console.ReadLine()`)。

#### 2. 存在的问题:

- **async void 的风险**:
- 使用 `async void` 作为事件处理函数容易导致异常无法捕获、死锁、控制台提前退出等问题。
- 推荐改为 `async Task` 并显式等待。

- **事件未取消订阅**:
- 如果 `publisher` 生命周期较长,而 `Program` 或其他对象已不再需要接收事件,则会导致内存泄漏。
- 应该使用 `-=` 显式取消订阅,或使用弱引用事件模式。

- **事件执行顺序不可控**:
- 由于是并发执行,不能保证哪个事件先执行完毕。

#### 3. 改进建议:

##### ✅ 改进版本一:将事件改为可等待的任务列表

```csharp
public class EventPublisher
{
public event Func DataReady; // 更适合异步事件

public async Task PublishDataAsync()
{
await Task.Delay(100);
if (DataReady != null)
{
foreach (var handler in DataReady.GetInvocationList())
{
await ((Func)handler)();
}
}
}
}
```

然后在 `Main` 方法中:

```csharp
for (int i = 0; i < 5; i++)
{
int localCopy = i;
publisher.DataReady += async () =>
{
await Task.Delay(localCopy * 100);
Console.WriteLine($"Received data after {localCopy * 100}ms");
};
}

await publisher.PublishDataAsync();
Console.WriteLine("All handlers completed.");
```

##### ✅ 改进版本二:使用 `IProgress` 替代事件(适用于更现代的场景)

---

### 💡 考察点总结:

| 技术点 | 考察内容 |
|--------|----------|
| 委托与事件 | 对多播委托的理解 |
| 异步编程 (`async/await`) | 正确使用异步方法 |
| `async void` vs `async Task` | 对异常处理和生命周期管理的理解 |
| 闭包与捕获变量 | 是否理解值类型与闭包的行为 |
| 内存泄漏 | 是否知道事件可能导致的对象生命周期延长 |
| 线程安全 | 多线程环境下的状态访问是否安全 |

---

如果你想要更多类似级别的题目,我可以继续为你出题 😎

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

上一篇: c#题例-2025-06-25 17:18:37

下一篇: c#题例-2025-06-25 02:51:57

精华推荐