.net面试题目 (问答题)
装箱/拆箱、ref struct、readonly struct 对性能的影响?
答:值类型→引用类型会产生装箱(堆分配)。ref struct 强制栈分配,禁止装箱。
struct与class在内存分配、传参、性能上的差异,什么场景应避免使用大struct?
- 内存分配:struct是值类型,默认分配在栈上(装箱时会进入堆);class是引用类型,分配在堆上,栈上仅存储引用地址。
- 传参差异:struct传值(赋值/传参时复制完整数据);class传引用(赋值/传参时复制引用地址,指向同一堆对象)。
- 性能差异:struct无GC回收开销(栈自动释放),但复制时开销随大小增加而增大;class有GC回收开销,但传参开销固定(仅复制地址)。
- 避免大struct:当struct大小超过16字节时,复制开销会超过引用类型的传参开销,且易触发装箱(如作为泛型参数、存入非泛型集合),此时优先使用class。
Span<T> 的作用
- 答案:Span<T> 提供对连续内存的安全访问,不需要复制数据,常用于高性能场景如字符串处理、缓冲区操作。
DI容器的核心原理?三种生命周期(Transient/Scoped/Singleton)的区别与使用场景?如何替换默认DI容器?
- 核心原理:DI容器负责创建对象、管理对象生命周期、注入依赖,遵循“依赖倒置原则”,解耦对象之间的依赖关系(高层依赖抽象,不依赖具体实现)。
- 生命周期区别:
- Transient:每次请求创建新实例,适合轻量级、无状态对象(如DTO、工具类);
- Scoped:每个HTTP请求创建一个实例,请求结束释放,适合有状态服务(如仓储类、业务服务);
- Singleton:全局唯一,程序启动时创建,程序退出时释放,适合无状态服务(如日志工具、配置服务)。
- 替换默认容器:在Program.cs中,通过Host.ConfigureServices方法,使用第三方容器(如Autofac、Ninject)的扩展方法,替换默认的Microsoft.Extensions.DependencyInjection容器。
中间件的执行顺序?Use与Run的区别?如何实现中间件短路?
- 执行顺序:请求进入时,按中间件注册顺序执行(Use→Use→...);响应返回时,按注册反顺序执行(后注册的先执行响应逻辑),即“洋葱模型”。
- Use与Run区别:Use方法接收RequestDelegate参数,可通过调用_next()将请求传递给下一个中间件;Run方法不接收_next(),会终止请求管道(短路)。
- 中间件短路:在Use方法中,不调用_next(),直接向HttpContext.Response写入响应(如权限验证失败返回401),即可终止管道。
await 之后,代码一定在原线程执行吗?
答:取决于 SynchronizationContext。在 ASP.NET Core 中,由于去掉了上下文,await 之后通常由线程池中的任意可用线程继续执行。
在 UI 线程(如 WPF)中,默认会回到原线程。如果不需要回到原线程,应使用 .ConfigureAwait(false) 来避免潜在的死锁并提升性能。
async/await的底层实现是什么?SynchronizationContext在不同场景下的恢复逻辑,为何要避免async void?
答:
- 底层实现:编译器将async方法编译为状态机(继承IAsyncStateMachine),将方法拆分为多个执行状态;await关键字会暂停当前方法,释放线程,待异步操作完成后,通过状态机恢复方法执行。
- SynchronizationContext恢复逻辑:① UI线程(WPF/WinForm):await后默认回到原UI线程(保证控件操作安全);② ASP.NET Core:无SynchronizationContext,await后由线程池任意可用线程恢复执行(无死锁风险);③ 控制台程序:默认使用线程池上下文,恢复线程不固定。
- 避免async void:async void方法无返回值,异常无法通过await捕获,会直接抛出到上层同步上下文,可能导致进程崩溃(仅事件处理程序可例外,如按钮点击事件)。
.NET Framework 与 .NET(Core)的本质区别?
答:.NET 是跨平台、模块化、AOT 友好;Framework 是 Windows 独占、单体框架。性能上 .NET 10 比 Framework 提升 2-3 倍(JIT 优化 + Native AOT)。
HostBuilder vs WebApplicationBuilder(.NET 6+ Minimal API)区别?
答:WebApplicationBuilder 内置 Kestrel + 默认中间件,简化配置。生产环境仍推荐 HostBuilder 精细控制。
SignalR 与 gRPC 的选择场景?
答:SignalR 适合实时推送(WebSocket fallback);gRPC 适合内部高性能 RPC(HTTP/2 + Protobuf)。
EF Core 与 Dapper 的选择依据?**
答:复杂业务用 EF(LINQ + 迁移);高性能查询用 Dapper(微 ORM)。
DDD、Clean Architecture、Vertical Slice Architecture 的区别?
答:DDD 强调领域模型;Clean 强调依赖倒置;Vertical Slice 按功能切片(一个 Use Case 一个文件),减少抽象地狱,适合微服务。
微服务拆分原则?服务治理如何实现?
答:领域驱动 + 单一职责 + 数据库 per service。服务发现(Consul)、网关(Ocelot/YARP)、熔断(Polly + Resilience)。
EF Core 性能“杀手锏”
非跟踪查询: context.Users.AsNoTracking().ToList(); 绕过状态快照,大幅减少内存开销。
拆分查询(Split Queries):针对一对多关联查询,避免笛卡尔积爆炸:
var blogs = context.Blogs
.Include(b => b.Posts)
.AsSplitQuery() // .NET 5+ 特性
.ToList();
Change Tracking、AsNoTracking、Compiled Queries 的性能对比?
答:默认跟踪适合写操作;AsNoTracking 读操作可提升 30-50%;Compiled Queries(EF 8+)避免每次编译表达式树。
EF Core的ChangeTracker跟踪机制?AsNoTracking、Split Queries的使用场景?批量更新/删除的最佳实践?EF Core核心原理与性能优化
- 答:ChangeTracker跟踪机制:ChangeTracker负责跟踪实体的状态变化(Added/Modified/Deleted/Unchanged),记录实体的原始值和当前值,调用SaveChanges()时,根据状态生成对应的SQL语句(INSERT/UPDATE/DELETE)。
- AsNoTracking:无跟踪查询,ChangeTracker不跟踪实体,适合只读场景(如列表查询),可减少内存开销,提升查询性能。
- Split Queries:拆分查询,针对一对多/多对多关联查询,避免笛卡尔积爆炸(如Include多级导航属性时),将关联查询拆分为多个独立SQL,提升查询效率;注意:非原子操作,需结合事务保证数据一致性。
- 批量更新/删除:优先使用EF Core 7+的ExecuteUpdate/ExecuteDelete方法(直接生成批量SQL),避免循环调用SaveChanges()(多次往返数据库,性能差);复杂批量操作可结合Dapper实现。
ASP.NET Core大表(百万级)查询超时如何优化?Bulk 操作方案?
答:分页 + 索引 + AsSplitQuery;用 Z.EntityFramework.Plus 或 EFCore.BulkExtensions 批量插入/更新。
安全与认证授权JWT + Refresh Token + OpenId Connect 完整方案?
答:IdentityServer / Duende IdentityServer / Keycloak。短期 Access Token + HttpOnly Refresh Token,结合 Cookie + SameSite。
给定以下存在死锁的代码,指出死锁原因并修复,解释为何GetAwaiter().GetResult()同样有风险。
// 存在死锁的代码
public void DeadlockDemo()
{
// 调用异步方法并同步阻塞
var result = GetDataAsync().Result;
Console.WriteLine(result);
}
public async Task<string> GetDataAsync()
{
// 模拟异步操作
await Task.Delay(100);
return "data";
}
修复方案与解释:
- 死锁原因:在有SynchronizationContext的环境(如UI线程、旧版ASP.NET)中,await后会尝试回到原上下文;而GetDataAsync().Result会阻塞当前线程,导致上下文被占用,await无法恢复执行,形成死锁。
修复方案1(推荐):全链路异步化,避免同步阻塞异步。
public async Task DeadlockDemoAsync()
{
var result = await GetDataAsync();
Console.WriteLine(result);
}
修复方案2(无上下文场景):使用ConfigureAwait(false),避免恢复到原上下文。
public async Task<string> GetDataAsync()
{
await Task.Delay(100).ConfigureAwait(false);
return "data";
}
GetAwaiter().GetResult()的风险:与.Result类似,同样会同步阻塞线程,在有SynchronizationContext的环境中仍会引发死锁,仅适合无上下文场景(如控制台程序)
字符串中出现次数最多的字符
var result = input.GroupBy(c => c)
.OrderByDescending(g => g.Count())
.First();
Console.WriteLine($"{result.Key}: {result.Count()}");
`
生产者-消费者模型
var channel = Channel.CreateUnbounded<int>();
// Producer
await channel.Writer.WriteAsync(1);
// Consumer
var item = await channel.Reader.ReadAsync();
此随笔或为自己所写、或为转载于网络。仅用于个人收集及备忘。

浙公网安备 33010602011771号