在C#中,Lazy<T> 是一个用于实现延迟初始化(Lazy Initialization)的泛型类,主要作用为它包装的类只有在第一次访问时才创建和初始化对象

适合场景

  • 高开销对象
    对象创建成本高(如读取大文件、复杂计算、数据库连接)。
  • 非必需资源
    可能不会被使用的资源(如可选功能、用户未触发的操作)。
  • 依赖循环
    解决类之间初始化顺序导致的循环依赖问题。
  • 线程安全的单例
    替代双重检查锁(Double-Check Locking),简化线程安全单例实现。
  • 避免启动卡顿
    将非关键初始化推迟到程序启动后执行,提升启动速度。

基础语法

Lazy() 初始化 Lazy 类的新实例。 发生延迟初始化时,将使用目标类型的无参数构造函数。
Lazy(Boolean) 初始化 Lazy 类的新实例。 发生延迟初始化时,将使用目标类型的无参数构造函数和指定的初始化模式。
Lazy(Func) 初始化 Lazy 类的新实例。 发生延迟初始化时,将使用指定的初始化函数。
Lazy(LazyThreadSafetyMode) 初始化 Lazy 类的新实例,该实例使用 T 的无参数构造函数和指定的线程安全模式。
Lazy(T) 初始化使用预初始化指定值的 Lazy 类的新实例。
Lazy(Func, Boolean) 初始化 Lazy 类的新实例。 发生延迟初始化时,将使用指定的初始化函数和初始化模式。
Lazy(Func, LazyThreadSafetyMode) 初始化使用指定的初始化函数和线程安全模式的 Lazy 类的新实例。
Lazy<T> lazyObject = new Lazy<T>(() => 
{
    // 初始化逻辑
    return new T(); 
});
  • 泛型参数 T:需要延迟初始化的对象类型(如 List<int>Big 等)。

  • 构造函数:通过委托(通常为 lambda 表达式)定义初始化逻辑。若有无参构造函数,可省略委托直接实例化(可省略委托直接实例化(如 new Lazy<Student>())。

    Lazy<Student> lazyObject = new Lazy<Student>();
    var student = lazyObject.Value;
    

    Value属性触发初始化并获取对象实例,后续访问直接返回已缓存的值

支持三种线程安全模式(枚举LazyThreadSafetyMode)

  1. None:不保证线程安全。适用于单线程场景。
  2. PublicationOnly:允许多线程竞争初始化,多线程下可能会多次调用 valueFactory,但只保留一次创建的实例。
  3. ExecutionAndPublication(默认):完全线程安全,确保只调用一次 valueFactory。

注意事项

  1. 避免循环依赖
    Lazy<T> 的初始化逻辑中嵌套了其他 Lazy<T> 的访问,可能导致死锁。
  2. 内存占用
    Lazy<T> 本身有额外内存开销,轻量对象可能不值得使用。

常见应用

基础使用

//Lazy<T>()适用无参数构造方法的类
static Lazy<ExpensiveResource> lazyResource = new Lazy<ExpensiveResource>();

static void Main()
{
	Console.WriteLine("Start Program.");

	// 资源尚未实例化
	// 只有第一次访问 .Value 时才会实例化
	lazyResource.Value.DoWork();
}

class ExpensiveResource
{
	public ExpensiveResource()
	{
		Console.WriteLine("ExpensiveResource Created!");
	}

	public void DoWork() => Console.WriteLine("Working...");
}

结果打印

Start Program.
ExpensiveResource Created!
Working...

单例方法

public class Singleton
{
    // 使用 Lazy 保证线程安全的延迟初始化
    private static readonly Lazy<Singleton> _instance = 
        new Lazy<Singleton>(() => new Singleton(), isThreadSafe: true);

    public static Singleton Instance => _instance.Value;

    private Singleton() { } // 私有构造函数
}

// 使用
Singleton obj = Singleton.Instance; // 首次访问时创建

注入场景,一个类里有很多方法,但是需要调用这个服务的方法只有一两个,且可能执行不到

// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    // 注册实际服务
    services.AddScoped<IExpensiveService, ExpensiveService>();
    
    // 注册 Lazy 包装器
    services.AddScoped(provider => 
        new Lazy<IExpensiveService>(() => provider.GetRequiredService<IExpensiveService>())
    );
}

// 使用方
public class ConsumerController : Controller
{
    private readonly Lazy<IExpensiveService> _lazyService;
    
    public ConsumerController(Lazy<IExpensiveService> lazyService)
    {
        _lazyService = lazyService; // 注入 Lazy
    }

    public IActionResult Action()
    {
        if (condition)
        {
            // 按需访问(首次访问时初始化)
            _lazyService.Value.DoSomething();
        }
        return View();
    }
}

注意

  1. 明确使用场景
    仅对真正可能不被使用初始化成本高的依赖项使用 Lazy<T>,避免滥用。
  2. 生命周期匹配
    • 若依赖项是 Scoped,确保 Lazy<T> 也是 Scoped(而非 Singleton)。
    • 若依赖项是 Transient,每次访问 Value 会创建新实例(需确认是否符合预期)。