C#/.NET 后端面试题与参考答案

Milad Bonakdar
作者
围绕 ASP.NET Core、EF Core、async、DI、REST API、测试和架构,系统准备 C#/.NET 后端开发面试。
引言
准备 C#/.NET 后端开发面试时,你需要能说明如何用 ASP.NET Core 构建 API、如何用 EF Core 保持数据访问高效、如何编写不阻塞线程的 async 代码,以及如何用依赖注入组织服务。好的回答会把概念连接到真实的后端取舍:性能、可靠性、可测试性或可维护性。
可以把这份指南当作实用检查清单。先看简短答案,再练习把每个答案转化为来自项目、实习、技术测试或线上问题排查的具体例子。
C# 语言基础
1. C# 中 struct 和 class 有什么区别?
答案:
- Class(类): 引用类型(分配在堆上)。当你将一个类对象传递给一个方法时,你传递的是内存位置的引用。方法内部的更改会影响原始对象。支持继承。
- Struct(结构): 值类型(分配在栈上)。当你传递一个结构时,会传递数据的副本。方法内部的更改不会影响原始结构。不支持继承(但可以实现接口)。
- 用法: 对于表示单个值的、小型、不可变的数据结构(如
Point、Color),使用struct。对于大多数其他对象,使用class。
稀有度: 常见 难度: 简单
2. 解释 async 和 await。它们如何帮助提高可伸缩性?
答案:
async 和 await 让方法在 I/O 操作进行时暂停,并在 Task 完成后继续执行。
- 机制:
await不会创建新线程。对于数据库、文件或网络调用,它会释放请求线程。 - 可伸缩性: 这有助于提高 I/O 型 ASP.NET Core 端点的吞吐量,但不会让 CPU 密集型工作变快。
- 面试提示: 讨论数据库 I/O 时,可以提到
CancellationToken、ToListAsync、SaveChangesAsync等 EF Core 方法。
稀有度: 非常常见 难度: 中等
3. 什么是依赖注入(DI),它在 .NET 中是如何实现的?
答案: 依赖注入是一种设计模式,其中类的依赖项是从外部提供的,而不是在内部创建的。
- 在 .NET 中: ASP.NET Core 有一个内置的 DI 容器。你在
Program.cs(或旧版本中的Startup.cs)中注册服务,并通过构造函数注入来注入它们。 - 生命周期:
- Transient(瞬时): 每次请求时都会创建。
- Scoped(范围): 每个客户端请求(HTTP 请求)创建一次。
- Singleton(单例): 第一次请求时创建,并由所有后续请求共享。
稀有度: 非常常见 难度: 中等
4. IEnumerable<T> 和 IQueryable<T> 有什么区别?
答案:
IEnumerable<T>: 在内存中执行查询。如果与数据库(EF Core)一起使用,它会从服务器获取所有数据到客户端内存,然后进行过滤。适用于 LINQ to Objects。IQueryable<T>: 远程执行查询(例如,在 SQL 服务器上)。它构建一个表达式树,该表达式树被转换为 SQL 查询。过滤发生在数据库端,这对于大型数据集来说效率更高。
稀有度: 常见 难度: 中等
5. 什么是扩展方法?
答案: 扩展方法允许你“添加”方法到现有类型,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。
- 语法: 定义为静态类中的静态方法。第一个参数指定该方法作用于哪个类型,并在前面加上
this关键字。 - 示例: 向
string类添加WordCount()方法。
稀有度: 常见 难度: 简单
ASP.NET Core & 架构
6. ASP.NET Core 中的中间件是什么?
答案: 中间件是软件组件,它们被组装到应用程序管道中以处理请求和响应。
- 管道: 每个组件选择是否将请求传递给管道中的下一个组件,并且可以在调用下一个组件之前和之后执行工作。
- 示例: 身份验证、授权、日志记录、异常处理、静态文件服务。
- 顺序很重要: 在
Program.cs中添加中间件的顺序定义了执行顺序。
稀有度: 非常常见 难度: 中等
7. 解释 .NET Core 和 .NET Framework 之间的区别。
答案:
- .NET Framework: 最初的 Windows 专用实现,成熟但绑定 Windows。
- .NET Core / 现代 .NET: 跨平台、开源的后续路线。从 .NET 5 开始通常直接称为
.NET;当前版本包括支持 C# 14 的 .NET 10 LTS。 - 主要区别: 现代 .NET 通常是新后端 API 的默认选择,因为它支持 Linux 容器、并行版本、当前 ASP.NET Core 功能以及持续的运行时改进。
稀有度: 常见 难度: 简单
8. 什么是 Kestrel?
答案: Kestrel 是一个跨平台、开源、事件驱动的 Web 服务器,默认包含在 ASP.NET Core 模板中。
- 作用: 它侦听 HTTP 请求并将它们传递给应用程序。
- 用法: 它可以独立运行(边缘服务器),也可以在反向代理(如 IIS、Nginx 或 Apache)后面运行。建议在生产环境中使用反向代理来处理安全性、负载平衡和 SSL 终止。
稀有度: 中等 难度: 中等
9. 如何在 ASP.NET Core 中处理全局异常处理?
答案: 你应该使用中间件进行全局异常处理,而不是在每个控制器中使用 try-catch 块。
UseExceptionHandler: 内置的中间件,可以捕获异常、记录它们,并在备用管道中重新执行请求(通常指向错误页面或 API 响应)。- 自定义中间件: 你可以编写自定义中间件来捕获异常并返回标准化的 JSON 错误响应(例如,
ProblemDetails)。
稀有度: 常见 难度: 中等
数据库 & Entity Framework
10. 什么是 Entity Framework Core (EF Core)? Code-First vs. Database-First?
答案: EF Core 是一个用于 .NET 的对象关系映射器 (ORM)。它使开发人员能够使用 .NET 对象来操作数据库。
- Code-First(代码优先): 你首先定义你的域类(实体),EF Core 使用迁移基于它们创建/更新数据库模式。是新项目的首选。
- Database-First(数据库优先): 你有一个现有的数据库,EF Core 基于该模式生成 .NET 类(脚手架)。
稀有度: 常见 难度: 简单
11. EF Core 中的 N+1 问题是什么,你如何解决它?
答案: N+1 问题是先查询实体列表,然后在循环中为每个元素加载相关数据,导致大量额外数据库查询。
- 解决方法: 用
Include、过滤 Include、投影或显式加载有意识地加载相关数据。在关键路径中避免 lazy loading。 - 示例:
await context.Orders.Include(o => o.Customer).ToListAsync(); - 取舍: 包含多个集合导航属性时,可以考虑 split query 或投影,避免 cartesian explosion 和不必要的列加载。
稀有度: 非常常见 难度: 中等
12. 解释仓储模式。为什么要将其与 EF Core 一起使用?
答案: 仓储模式使用类似于集合的接口来访问域对象,从而在域和数据映射层之间进行协调。
- 优点: 将应用程序与特定的数据访问技术(EF Core)解耦,使单元测试更容易(可以模拟存储库),并集中数据访问逻辑。
- 缺点/争论: EF Core 的
DbContext已经是仓储/工作单元模式的实现。添加另一个层有时可能是多余的(“抽象之上的抽象”)。
稀有度: 常见 难度: 困难
REST APIs & Web Services
13. 什么是 HTTP 动词及其典型用法?
答案:
- GET: 检索资源。安全且幂等。
- POST: 创建新资源。非幂等。
- PUT: 更新/替换现有资源。幂等。
- PATCH: 部分更新资源。不一定是幂等的(但通常是)。
- DELETE: 删除资源。幂等。
稀有度: 常见 难度: 简单
14. 401 Unauthorized 和 403 Forbidden 有什么区别?
答案:
- 401 Unauthorized(未授权): 用户未经过身份验证。客户端应该登录并重试。“我不知道你是谁。”
- 403 Forbidden(已禁止): 用户已经过身份验证,但没有权限访问该资源。“我知道你是谁,但你不能这样做。”
稀有度: 常见 难度: 简单
测试 & 最佳实践
15. 单元测试和集成测试有什么区别?
答案:
- 单元测试: 隔离地测试一小段代码(通常是一个方法)。依赖项被模拟(使用像 Moq 这样的工具)。快速且可靠。
- 集成测试: 测试应用程序的不同部分如何协同工作(例如,API 端点 + 数据库)。速度较慢,但验证了实际的系统行为。
稀有度: 常见 难度: 简单
16. 如何在单元测试中模拟依赖项?
答案: 像 Moq 或 NSubstitute 这样的模拟框架用于创建接口的虚假实现。
- 目的: 隔离被测试的类。例如,如果测试
UserService,则模拟IUserRepository,这样就不会访问真正的数据库。 - 示例 (Moq):
稀有度: 常见 难度: 中等
17. 什么是 SOLID 原则?举一个例子。
答案: SOLID 是 5 个设计原则的首字母缩写,这些原则使软件更易于理解、更灵活和更易于维护。
- Single Responsibility Principle (SRP)(单一职责原则)
- Open/Closed Principle (OCP)(开闭原则)
- Liskov Substitution Principle (LSP)(里氏替换原则)
- Interface Segregation Principle (ISP)(接口隔离原则)
- Dependency Inversion Principle (DIP)(依赖倒置原则)
- 示例 (DIP): 高级模块不应依赖于低级模块。两者都应该依赖于抽象。这是依赖注入的基础。
稀有度: 常见 难度: 困难
18. 什么是装箱和拆箱?为什么要避免它?
答案:
- 装箱: 将值类型(如
int)转换为引用类型(object)。它在堆上分配内存。 - 拆箱: 将引用类型转换回值类型。
- 性能: 两者都是昂贵的操作(内存分配、类型检查)。与旧的集合(
ArrayList)相比,泛型(List<T>)有助于避免装箱/拆箱。
稀有度: 常见 难度: 中等
19. C# 中的 using 语句是什么?
答案:
using 语句提供了一种方便的语法,可确保正确使用 IDisposable 对象。
- 机制: 当块退出时(即使发生异常),它会自动调用对象的
Dispose()方法。 - 现代语法:
using var file = File.OpenRead("path");(不需要大括号,在作用域结束时释放)。
稀有度: 常见 难度: 简单
20. 在“责任链”模式的上下文中解释中间件的概念。
答案: ASP.NET Core 中间件是责任链模式的经典实现。
- 模式: 请求沿着处理程序链传递。每个处理程序决定是处理请求还是将其传递给链中的下一个处理程序。
- 应用: 在 .NET 中,
RequestDelegate代表下一个处理程序。中间件组件被链接起来以形成请求管道。
稀有度: 不常见 难度: 困难


