WPF中的Lazy<T>使用方法

Lazy<T> 通用使用指南

Lazy<T> 是 .NET 内置的**延迟初始化(Lazy Initialization)**工具类,位于 System 命名空间。它的核心思想是:对象的创建被推迟到第一次真正被访问时才执行,并且保证只创建一次、线程安全


一、核心概念

csharp 复制代码
// 1. 声明一个"延迟容器",此刻并未创建 T 的实例
Lazy<MyService> lazy = new(() => new MyService());

// 2. 第一次访问 .Value 时才真正创建,之后复用缓存
MyService service = lazy.Value;

关键点:

概念 说明
延迟 工厂委托(Func<T>)只在第一次访问 .Value 时执行
缓存 创建结果被内部缓存,后续访问直接返回,不重复创建
线程安全 默认模式下,多线程同时首次访问时只有一个线程执行工厂,其余等待复用

二、构造函数与线程安全模式

Lazy<T> 有多个构造函数重载,最常用的是:

csharp 复制代码
// 方式一:无参(要求 T 有无参构造函数)
Lazy<MyService> a = new();

// 方式二:传入工厂委托(最常用,可自定义创建逻辑)
Lazy<MyService> b = new(() => new MyService("config"));

// 方式三:指定线程安全模式
Lazy<MyService> c = new(() => new MyService(), LazyThreadSafetyMode.ExecutionAndPublication);

三种 LazyThreadSafetyMode 对比:

模式 线程安全 工厂执行次数 适用场景
ExecutionAndPublication(默认) ✅ 完全线程安全 仅 1 次 绝大多数场景,推荐
PublicationOnly ✅ 发布安全 可能多次(取先完成者) 工厂很慢、可接受重复计算、结果可丢弃
None ❌ 不保证 可能多次 明确单线程访问,追求极致性能

注意:PublicationOnly 下工厂可能被多个线程各执行一次,但最终只有一个结果被"发布";None 模式下多线程访问会出问题,除非你确定只有单线程访问。


三、使用场景

场景 1:单例模式(最经典)

保证全局唯一实例,且首次访问才创建:

csharp 复制代码
public sealed class ConfigManager
{
    private static readonly Lazy<ConfigManager> _instance = new(() => new ConfigManager());

    public static ConfigManager Instance => _instance.Value;

    private ConfigManager()
    {
        // 加载配置...
    }
}

优点:比手写双检锁(Double-Check Locking)更简洁,线程安全由框架保证。

场景 2:昂贵资源的按需加载

对象创建成本高(IO、网络、反射、大对象),但可能根本用不到:

csharp 复制代码
public class ReportService
{
    // 数据库连接池、大字典、正则表达式等,用到才创建
    private readonly Lazy<Regex> _emailRegex = new(() => new Regex(
        @"^[^@\s]+@[^@\s]+\.[^@\s]+$", RegexOptions.Compiled));

    private readonly Lazy<Dictionary<string, string>> _lookupTable = new(() =>
    {
        var dict = new Dictionary<string, string>();
        // 从文件/数据库加载大量数据...
        return dict;
    });

    public bool IsValidEmail(string email) => _emailRegex.Value.IsMatch(email);
}

优点:程序启动快,资源真正被用到时才付出创建成本。

场景 3:静态字段的延迟初始化

静态字段在类首次被引用时就会初始化,用 Lazy<T> 可推迟到真正访问:

csharp 复制代码
public static class AppInfo
{
    // 不访问就不执行,避免无谓的反射/IO
    private static readonly Lazy<Version> _version = new(() =>
        Assembly.GetExecutingAssembly().GetName().Version!);

    public static Version Version => _version.Value;
}

场景 4:避免循环依赖 / 初始化顺序问题

两个对象互相引用时,用 Lazy<T> 打破初始化顺序的耦合:

csharp 复制代码
public class ServiceA
{
    public Lazy<ServiceB> B { get; } = new(() => new ServiceB());
}

public class ServiceB
{
    public Lazy<ServiceA> A { get; } = new(() => new ServiceA());
}

场景 5:属性级缓存(Memoization)

把"计算一次、多次复用"的逻辑封装成属性:

csharp 复制代码
public class Order
{
    private readonly Lazy<decimal> _total = new(() =>
    {
        // 复杂的累加计算,只算一次
        return Items.Sum(i => i.Price * i.Quantity);
    });

    public decimal Total => _total.Value;
}

场景 6:依赖注入容器 / 服务定位器

在简易 IoC 容器中,用 Lazy<T> 实现"按需创建 + 单例缓存":

csharp 复制代码
public class SimpleContainer
{
    private readonly Dictionary<Type, Lazy<object>> _services = new();

    public void Register<TService>(Func<TService> factory) where TService : class
    {
        _services[typeof(TService)] = new Lazy<object>(() => factory()!);
    }

    public TService Resolve<TService>() where TService : class
    {
        return (TService)_services[typeof(TService)].Value;
    }
}

四、注意事项

  1. 默认用 ExecutionAndPublication :除非有明确理由,不要轻易改成 NonePublicationOnly
  2. 工厂委托里不要有副作用 :工厂可能(在 PublicationOnly 下)被多次执行,副作用代码应避免放在里面。
  3. 异常处理 :默认模式下,如果工厂抛出异常,Lazy<T> 会缓存异常,后续访问会再次抛出同一个异常 (不会重试)。如需重试,可考虑 PublicationOnly 或自行处理。
  4. IsValueCreated 属性:可判断实例是否已创建,用于调试或条件逻辑。
  5. 不要与 async 混用Lazy<T> 的工厂是同步的 Func<T>,无法直接 await。需要异步延迟初始化时,考虑 Lazy<Task<T>>
csharp 复制代码
private readonly Lazy<Task<HeavyData>> _data = new(async () =>
{
    await Task.Delay(100); // 模拟异步加载
    return new HeavyData();
});

// 使用:await _data.Value;
  1. Lazy<T>Lazy<Task<T>> 的区别:前者缓存的是"创建好的对象",后者缓存的是"一个 Task",适合异步场景。

五、总结

Lazy<T> = 延迟创建 + 只创建一次 + 线程安全 三合一。凡是遇到"对象创建成本高、可能用不到、但需要全局唯一或复用"的场景,都可以优先考虑它------尤其是单例模式,它是最简洁、最不易出错的实现方式。

相关推荐
zQ.ii2 小时前
WPF 触发器学习笔记
笔记·学习·wpf
SamChan908 小时前
用PostgreSQL+pgvector构建PDF翻译记忆库:向量相似度检索+增量更新实战
数据库·python·ai·postgresql·pdf·wpf
李高钢1 天前
开源控件库 HandyControl 介绍与简单实践:让 WPF 界面告别“上个世纪“
开源·wpf
旋生万物2 天前
【终极实战】用Python从零“生成“一个宇宙:螺旋干涉模型的代码实现
开发语言·前端·人工智能·react.js·php·wpf
Now喔2 天前
WPF画刷介绍
wpf
李高钢2 天前
WPF/WinForms 客户端通过 gRPC 与后端通信:从 Proto 契约到流式调用的完整指南
wpf·grpc·winforms
一见已难忘3 天前
基于 Harmony 6.0 应用的宠物寄养预约系统实现
wpf·宠物
fl1768313 天前
基于C#WPF实现的内存加速球清理类似360加速球内存清理
开发语言·c#·wpf
李高钢3 天前
【WPF】高级 UI 与性能优化实战:从卡顿到丝滑
ui·性能优化·wpf