C#/.NET 后端开发面试题:完整指南

Milad Bonakdar
作者
通过涵盖 ASP.NET Core、Entity Framework、依赖注入和系统设计等关键面试题,精通 C# 和 .NET 后端开发。
引言
C# 和 .NET 已经发生了显著的演变,成为了一个强大的、跨平台的生态系统,用于构建高性能的后端系统。随着 .NET Core(现在只有 .NET 5+)的出现,它已成为云原生应用、微服务和企业解决方案的首选。
本指南涵盖了 C# 和 .NET 领域后端开发人员需要掌握的重要面试问题。我们将探讨语言基础、ASP.NET Core 架构、使用 Entity Framework 进行数据库交互以及最佳实践,以帮助你为下一次面试做好准备。
C# 语言基础
1. C# 中 struct 和 class 有什么区别?
答案:
- Class(类): 引用类型(分配在堆上)。当你将一个类对象传递给一个方法时,你传递的是内存位置的引用。方法内部的更改会影响原始对象。支持继承。
- Struct(结构): 值类型(分配在栈上)。当你传递一个结构时,会传递数据的副本。方法内部的更改不会影响原始结构。不支持继承(但可以实现接口)。
- 用法: 对于表示单个值的、小型、不可变的数据结构(如
Point、Color),使用struct。对于大多数其他对象,使用class。
稀有度: 常见 难度: 简单
2. 解释 async 和 await。它们如何帮助提高可伸缩性?
答案:
async 和 await 是用于异步编程的关键字。
- 机制: 当遇到
await关键字时,方法的执行会暂停,线程被释放回线程池以处理其他请求。当等待的任务完成时,执行恢复(可能在不同的线程上)。 - 可伸缩性: 在 Web 服务器(如 Kestrel)中,线程是有限的资源。如果一个线程阻塞等待 I/O(数据库、网络),它就无法处理其他请求。通过在 I/O 操作期间不阻塞线程,异步允许服务器使用更少的线程处理更多的并发请求。
稀有度: 非常常见 难度: 中等
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): 一个跨平台、开源和模块化的实现。它运行在 Windows、Linux 和 macOS 上。它针对高性能和云部署进行了优化。
- 主要区别: 跨平台支持、对微服务架构友好、更高的性能、并行版本控制。
稀有度: 常见 难度: 简单
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 问题是什么,你如何解决它?
答案: 当您获取一个实体列表(1 个查询),然后在循环中访问每个项目的相关实体时,会发生 N+1 问题,导致 N 个额外的查询。
- 解决方法: 使用
.Include()方法进行预先加载。这将生成一个 SQLJOIN,以在单个查询中获取相关数据。 - 示例:
context.Orders.Include(o => o.Customer).ToList();
稀有度: 非常常见 难度: 中等
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代表下一个处理程序。中间件组件被链接起来以形成请求管道。
稀有度: 不常见 难度: 困难



