lazy.value在首次读取返回值时才执行工厂函数,且默认线程安全仅保证其最多执行一次;工厂函数内异常会被缓存并重复抛出,非线程安全逻辑需自行处理。

Lazy
Lazy.Value 什么时候才真正执行工厂函数?
不是 new Lazy<t>()</t> 的那一刻,也不是访问 Value 属性的 getter 本身,而是在第一次读取 Value 的返回值时才调用工厂函数。这个“第一次”默认线程安全:多个线程同时触发,也只执行一次工厂逻辑。
- 验证是否真延迟?在工厂函数里加
Console.WriteLine("init!"),只会在首次Value访问时输出 - 误判常见于:
var f = new Foo(); new Lazy<foo>(() => f)</foo>——f已在上一行创建,完全失去延迟意义 - 工厂函数抛出
InvalidOperationException?该异常会被缓存;后续每次读Value都 rethrow 同一个异常实例,不会重试
为什么 public Lazy Property => new Lazy(f) 是典型错误?
每次 get 属性都新建一个 Lazy<t></t> 实例,Value 永远是“第一次”,彻底失去延迟加载价值。
- 正确写法必须是字段级缓存:
private readonly Lazy<list>> _items = new Lazy<list>>(LoadItems);</list></list> - 对应属性应为:
public List<string> Items => _items.Value;</string> - 字段必须是
readonly,否则可能被意外重赋值,导致重复初始化 - 若
LoadItems依赖this._config等实例状态,确保该状态在_items构造前已就绪
LazyThreadSafetyMode 选哪个?别瞎设 None
默认 LazyThreadSafetyMode.ExecutionAndPublication 已覆盖绝大多数场景:严格单次执行 + 单次发布,既防重复初始化,也防拿到部分构造对象。
- 设为
LazyThreadSafetyMode.None后,多线程并发首次读Value可能创建多个实例,且无法预测哪个被最终返回——这不是 bug,是设计使然 - 仅在明确单线程上下文(如控制台主流程、单元测试 setup)中禁用同步才合理
-
LazyThreadSafetyMode.PublicationOnly适合初始化极快、结果等价的场景(如new Random()),但不适用于依赖配置或 DB 连接的逻辑 - 别手动用
lock包裹Value访问——这破坏 Lazy 设计契约,还可能死锁
异步初始化不能直接用 async Func,怎么办?
Lazy<t></t> 的工厂函数不能是 async,因为 Func<t></t> 不接受 Task<t></t> 返回值。强行 await 会导致同步阻塞或死锁(尤其在 UI/ASP.NET 同步上下文里)。
- 正确解法是封装成
Lazy<task>></task>:private readonly Lazy<task>>> _data = new Lazy<task>>>(LoadDataAsync);</task></task> - 对外暴露异步方法:
public async Task<list>> GetDataAsync() => await _data.Value;</list> - 此时
_data.Value返回的是同一个Task实例,不会重复发起请求——这是关键价值 - 绝对不要写
Task.Run(() => new Lazy<t>())</t>:绕过 Lazy 上下文捕获能力,额外起线程,纯属负优化
最容易被忽略的点是:Lazy 的线程安全仅保证「工厂函数最多执行一次」,不保护工厂函数内部逻辑的线程安全性。比如你在工厂里往静态字典写数据,依然要自己加锁或换 ConcurrentDictionary。










