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→ 抢占线程池资源,降低吞吐量 替代思路: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—— 那些地方,文档不会标红,但线上会报错。
