Lazy 是延迟初始化机制,解决“何时初始化”而非“初始化快慢”,适用于构造开销大且非必用的对象;误用会导致重复初始化、额外开销或失去延迟意义。

Lazy 不是“懒”,是“等真要用时再造”
它不解决初始化逻辑本身的速度,只解决「要不要初始化」的时机问题。如果你的对象构造开销大、又不是每次都会用到,Lazy<t></t> 才值得上——否则加了反而多一层委托调用和线程同步开销。
常见错误现象:Lazy<t></t> 实例一创建就触发初始化(比如误用了 Value 属性在构造后立刻读取),或者多个线程反复初始化(没设 isThreadSafe = true)。
- 默认构造的
Lazy<t></t>是线程安全的,但会带来Monitor.Enter开销;如果确定单线程使用,可用new Lazy<t>(func, isThreadSafe: false)</t> - 不要把
Lazy<t></t>当缓存用:它只保证初始化一次,不处理后续值变更;要响应式更新得换IObservable<t></t>或手动重置 - 初始化函数里抛异常?
Lazy<t>.Value</t>第二次读会直接 rethrow 原异常,不是重新执行——这点常被忽略
别在属性里无脑套 new Lazy()
写成 public Lazy<list>> Items => new Lazy<list>>(LoadItems);</list></list> 看似延迟,实则每次 get 都新建一个 Lazy<t></t> 实例,完全失去意义。
正确做法是字段级缓存:
private readonly Lazy<List<string>> _items = new Lazy<List<string>>(LoadItems); public List<string> Items => _items.Value;
使用场景:ASP.NET Core 的 Controller 中依赖注入大配置对象、WinForms 启动时加载大量 UI 资源、单元测试中模拟耗时外部服务。
- 字段必须是
readonly,否则可能被意外重赋值,导致多次初始化 - 如果
LoadItems依赖实例状态(比如this._config),确保该状态在Lazy<t></t>构造前已就绪 - 别对值类型(如
int、DateTime)用Lazy<t></t>——堆分配+同步开销远超收益
async 场景下 Lazy 不行,得用 Lazy>
Lazy<t></t> 的工厂函数不能是 async,因为 Func<t></t> 不接受 Task<t></t> 返回值。强行 await 会导致同步阻塞或死锁(尤其在 UI/ASP.NET 同步上下文里)。
正确解法是把异步延迟封装进 Lazy<task>></task>:
private readonly Lazy<Task<List<string>>> _data = new Lazy<Task<List<string>>>(LoadDataAsync); public async Task<List<string>> GetDataAsync() => await _data.Value;
注意:此时 _data.Value 返回的是同一个 Task 实例,不会重复发起请求——这是 Lazy<task>></task> 的关键价值。
- 别用
Task.Run(() => new Lazy<t>(...))</t>,这等于绕路启动线程,还丢掉上下文捕获能力 - 如果需要取消支持,工厂函数里得接收
CancellationToken,并传给内部await调用 - 异常处理逻辑和同步版一致:第一次 await 失败,后续 await 都直接 throw 同一个
AggregateException
替代方案比 Lazy 更轻量的情况
当对象构造只是稍慢、且确定只在某个方法内用一次,直接用局部变量 + 提前 return 更干净:
public string GetReport() {
if (!_shouldGenerateReport) return string.Empty;
var data = ExpensiveLoad(); // 就这儿造,不用包 Lazy
return Format(data);
}
性能影响明显:空的 Lazy<t></t> 实例约 24 字节(.NET 6+),而一次判断 + 局部变量几乎零成本。
- 配置类、DTO、简单集合初始化,通常不值得上
Lazy<t></t> - Unity 或 AOT 编译环境(如 iOS)要注意:
Lazy<t></t>内部用反射生成委托,可能触发 IL trimming 问题 - 调试时看
Lazy<t></t>字段值,VS 默认显示IsValueCreated = false,容易误判为“没生效”,其实只是还没触发










