Young87

SmartCat's Blog

So happy to code my life!

游戏开发交流QQ群号60398951

当前位置:首页 >AI写作

c#题例-2025-07-18 21:52:20

当然可以!下面是一道**专家级别**的 C# 逻辑面试题,它不仅考察对 C# 语言特性的掌握,还涉及**委托、事件、闭包、线程安全、异步编程模型**等多个高级概念的综合运用。

---

### 🧠 面试题:闭包陷阱与事件生命周期管理

#### 📌 题目描述:

你正在开发一个多线程的 C# 应用程序,其中有一个 `Worker` 类,它会周期性地触发事件,并允许订阅者通过 lambda 表达式注册回调。

请看以下代码片段:

```csharp
public class Worker
{
public event EventHandler DoWork;

public void Start()
{
Task.Run(async () =>
{
while (true)
{
await Task.Delay(1000);
DoWork?.Invoke(this, EventArgs.Empty);
}
});
}
}

public class Program
{
public static void Main()
{
var worker = new Worker();
worker.Start();

int count = 0;
worker.DoWork += (sender, e) =>
{
count++;
Console.WriteLine($"Work count: {count}");
};

Console.ReadLine();
}
}
```

#### ❓问题:

1. **这段代码存在什么潜在问题?**
2. **如果在 `Main` 方法中添加如下代码,会发生什么?为什么会发生?**

```csharp
worker = null;
GC.Collect();
```

3. **如何修改 `Worker` 类或其使用方式,以避免上述问题?请写出修改后的代码并解释其原理。**

---

### 💡 考察点解析:

| 考察点 | 说明 |
|--------|------|
| **闭包与捕获变量** | Lambda 表达式捕获了 `count` 变量,形成闭包,可能导致意外行为或内存泄漏。 |
| **事件与内存泄漏** | 事件订阅者未取消订阅,即使外部变量置为 null,仍可能被根引用保留。 |
| **线程与异步安全** | 使用 `Task.Run` 和 `await Task.Delay` 模拟定时器,需考虑线程安全与资源释放。 |
| **GC 与对象生命周期** | 即使显式设置对象为 null 并调用 `GC.Collect()`,由于事件订阅的存在,对象仍无法被回收。 |
| **弱引用或事件解耦机制** | 是否了解 `WeakReference`、`EventHandlerList`、`IWeakEventPattern` 等高级机制。 |

---

### ✅ 参考答案(节选):

#### 1. **潜在问题:**

- **内存泄漏**:lambda 表达式捕获了局部变量 `count`,而 `Worker` 对象持有该事件的引用,导致 `Worker` 实例和 `Main` 方法上下文无法被回收。
- **闭包陷阱**:多个 lambda 可能共享同一个 `count` 变量副本,导致计数错误。
- **未取消订阅**:事件订阅者未主动取消订阅,造成资源无法释放。

#### 2. **添加 `worker = null; GC.Collect();` 后:**

不会触发 `Worker` 实例的回收,因为:
- `Worker` 实例仍然被它自己的 `DoWork` 事件所引用(通过 lambda)。
- 即使外部引用置为 null,GC 也无法回收,因为事件链上仍有引用路径。

#### 3. **修改方案:**

使用**弱引用事件模式**或手动取消订阅。以下是使用取消订阅的简单修改:

```csharp
public class Program
{
public static void Main()
{
var worker = new Worker();
worker.Start();

int count = 0;
EventHandler handler = (sender, e) =>
{
count++;
Console.WriteLine($"Work count: {count}");
};

worker.DoWork += handler;

// 模拟一段时间后取消订阅
Task.Delay(5000).ContinueWith(_ =>
{
worker.DoWork -= handler;
Console.WriteLine("Unsubscribed.");
});

Console.ReadLine();
}
}
```

---

### 🧪 进阶思考(面试官可追问):

- 如何实现一个**支持弱引用事件订阅的 Worker 类**?
- 如何使用 `IWeakEventListener` 接口(WPF)避免内存泄漏?
- 如何使用 `ConditionalWeakTable` 实现自定义弱引用事件机制?

---

如果你希望我继续提供类似难度的题目(如并发、反射、表达式树、性能优化等方向),欢迎继续提问!

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

上一篇:无

下一篇: c#题例-2025-07-18 16:19:09

精华推荐