面试官关注的是实际场景中的问题识别、权衡取舍与可维护编码能力。string是引用类型但不可变且重载==,应使用string.equals而非referenceequals;ienumerable延迟执行易致重复计算,需按需缓存;task.run不解决i/o异步,滥用会降低asp.net core吞吐量;record的with仅支持init/set属性,本质为浅拷贝。

C# 面试题没有标准“2026 年版”答案——面试官真正看的不是你背过多少题,而是你能否在具体场景中识别问题、权衡取舍、写出可维护的代码。下面挑几个高频但容易答偏的点,直击实操细节。
为什么 string 是引用类型却表现得像值类型?
这不是“表现像”,而是编译器和运行时共同做了两件事:string 不可变(immutable),且重载了 == 运算符。所以 str1 == str2 默认比较内容而非引用。
- 错误认知:以为
string是值类型 → 实际它继承自object,分配在堆上,typeof(string).IsClass返回true - 坑点:用
ReferenceEquals(str1, str2)判断字符串相等会出错,尤其在字符串驻留(interning)后,相同字面量可能指向同一对象,但逻辑上不该依赖这个行为 - 正确做法:内容比较用
string.Equals(a, b, StringComparison.Ordinal);明确需要引用比较时才用ReferenceEquals
IEnumerable<t></t> 延迟执行导致的常见内存/性能陷阱
很多人知道“延迟执行”,但没意识到多次遍历 IEnumerable<t></t> 可能重复触发耗时操作(如数据库查询、文件读取、HTTP 调用)。
- 典型错误:把 LINQ 查询结果直接赋给字段或传给多个方法,每次调用
Count()、ToList()或foreach都重新执行源逻辑 - 修复方式:按需选择缓存策略 —— 确定数据小且不变,用
.ToList();需流式处理且只遍历一次,保持IEnumerable<t></t>;若需多次遍历又怕重复计算,考虑IReadOnlyList<t></t>或显式缓存 - 示例:
var query = dbContext.Users.Where(u => u.IsActive); // IQueryable<User>,延迟执行 Console.WriteLine(query.Count()); // 触发 SQL COUNT(*) Console.WriteLine(query.ToList().Count); // 再次触发 SELECT *,全量拉取!
async/await 中 Task.Run 的滥用场景
不是所有“想异步”的地方都该加 Task.Run。它本质是把同步工作扔进线程池,**不能让 CPU 密集型操作变快,反而增加调度开销**。
- 适合用:CPU 密集 + 必须不阻塞 UI/请求线程(如 WinForms 渲染线程、ASP.NET Core 同步上下文敏感场景)
- 不该用:
- 已存在原生异步 API 的场景(如
HttpClient.GetAsync、FileStream.ReadAsync)→ 直接 await - 在 ASP.NET Core 中对普通业务逻辑包裹
Task.Run→ 抢占线程池资源,降低吞吐量
- 已存在原生异步 API 的场景(如
- 替代思路:I/O 操作优先走 async API;CPU 密集型任务若真必须后台跑,评估是否该拆到后台服务或队列中处理
为什么 record 类的 with 表达式不能修改 init 属性以外的字段?
with 是语法糖,底层调用的是生成的 Clone() + 属性赋值。它只作用于 init 或 set 可写的属性,而 record 的主构造参数默认生成 init 属性。
- 常见误解:“
with就是深拷贝” → 实际是浅拷贝,且仅对可写属性生效;若字段是private set或只有get,with无法修改 - 验证方式:反编译看生成的
<clone>$</clone>方法,它只复制声明为init或set的成员 - 如果需要定制复制逻辑(比如忽略某字段、转换值),应显式重写
Clone()或使用record struct+ 手动构造
真正卡人的从来不是概念定义,而是你在改一段遗留代码时,突然发现 IEnumerable 被反复枚举了七次,或者 async 方法里嵌了三层 Task.Run 还配了 .Result —— 那些地方,文档不会标红,但线上会报错。










