C#Lazy

Lazy<T>是一个用于实现延迟初始化(Lazy Initialization)的泛型类,它可以将昂贵对象的创建推迟到第一次实际使用时。这种模式能有效提升应用程序的启动性能和资源利用率。

💡 核心概念与价值

Lazy<T>的核心思想是“按需创建”。它特别适用于以下场景:

  • 资源密集型对象:创建成本高、耗时长的对象,如加载大型文件、建立数据库连接或进行复杂计算。

  • 可能不被使用的对象:某些功能或组件在程序运行过程中可能根本不会被访问,使用 Lazy<T>可以避免不必要的初始化开销。

  • 提升启动速度:通过将非核心资源的初始化后移,让应用程序更快启动。

 

使用Lazy<T>时,需要注意以下几点:

  1. 线程安全:Lazy<T>默认是线程安全的,可以在多线程环境下使用,确保只有一个线程可以初始化实例。

  2. 初始化方式:可以通过传递一个委托来自定义初始化过程,如果不传递,则使用类型的默认构造函数。

  3. 异常处理:如果初始化过程中出现异常,则每次访问Value属性时都会抛出异常,除非重新初始化。

🛠️ 基本用法与示例

使用 Lazy<T>非常简单,其核心是通过访问 Value属性来触发初始化。

// 1. 基本用法:使用类型的默认构造函数
Lazy<ExpensiveObject> lazyObject1 = new Lazy<ExpensiveObject>();
// 在访问 Value 属性之前,ExpensiveObject 并不会被创建
ExpensiveObject obj1 = lazyObject1.Value;

// 2. 使用委托自定义初始化逻辑
Lazy<ExpensiveObject> lazyObject2 = new Lazy<ExpensiveObject>(() => 
{
    // 这里可以编写复杂的初始化代码
    return new ExpensiveObject("自定义参数");
});

ExpensiveObject obj2 = lazyObject2.Value;

 示例:模拟一个初始化昂贵的对象

public class ExpensiveService
{
    public ExpensiveService()
    {
        Console.WriteLine(">>> ExpensiveService 被创建了!这个过程很耗时...");
        // 模拟昂贵的初始化操作,如读取大文件或连接数据库
        Thread.Sleep(2000); 
    }
    public void DoWork() => Console.WriteLine("正在工作...");
}

class Program
{
    static void Main()
    {
        Console.WriteLine("程序开始...");
        // 创建 Lazy 实例,但昂贵的对象尚未初始化
        Lazy<ExpensiveService> lazyService = new Lazy<ExpensiveService>();
        Console.WriteLine("Lazy 对象已创建,但 ExpensiveService 还未初始化。");

        // 第一次访问 .Value 属性,触发实际创建
        Console.WriteLine("首次访问 lazyService.Value...");
        lazyService.Value.DoWork(); // 此时会看到构造函数中的输出

        // 后续访问将直接返回已创建好的实例,不会再次初始化
        Console.WriteLine("再次访问 lazyService.Value...");
        lazyService.Value.DoWork();
    }
}

 输出结果:

程序开始...
Lazy 对象已创建,但 ExpensiveService 还未初始化。
首次访问 lazyService.Value...
>>> ExpensiveService 被创建了!这个过程很耗时...
正在工作...
再次访问 lazyService.Value...
正在工作...

 从输出可以看出,ExpensiveService的构造函数只在第一次访问 .Value时被调用了一次。

 

🔒 线程安全模式

Lazy<T>的一个重要优势是内置了对多线程环境的支持。你可以通过 LazyThreadSafetyMode枚举来指定不同的线程安全行为。

// 1. ExecutionAndPublication (默认模式,最安全)
// 确保只有一个线程能执行初始化,初始化后所有线程都看到同一个实例。
Lazy<ExpensiveObject> safeLazy = new Lazy<ExpensiveObject>(
    () => new ExpensiveObject(),
    LazyThreadSafetyMode.ExecutionAndPublication
);

// 2. PublicationOnly
// 允许多个线程同时执行初始化,但只采用第一个成功初始化的结果,适合初始化成本极高且可接受短暂资源浪费的场景。
Lazy<ExpensiveObject> publicationLazy = new Lazy<ExpensiveObject>(
    () => new ExpensiveObject(),
    LazyThreadSafetyMode.PublicationOnly
);

// 3. None
// 非线程安全,性能最高,但仅适用于单线程环境。
Lazy<ExpensiveObject> unsafeLazy = new Lazy<ExpensiveObject>(
    () => new ExpensiveObject(),
    LazyThreadSafetyMode.None
);

📚 实际应用场景

  1. 实现单例模式(Singleton)

    这是 Lazy<T>最经典的应用之一,它能以简洁且线程安全的方式实现延迟加载的单例。

    public sealed class Singleton
    {
        // 私有构造函数
        private Singleton() { }
        // 使用 Lazy<T> 包裹单例实例
        private static readonly Lazy<Singleton> _instance =
            new Lazy<Singleton>(() => new Singleton());
        // 公共访问点
        public static Singleton Instance => _instance.Value;
    }

     

  2. 延迟加载资源

    对于如图片、音频、视频等大型资源,可以使用 Lazy<T>确保只在需要时才加载。

    public class ImageLoader
    {
        private Lazy<Bitmap> _lazyImage = new Lazy<Bitmap>(() => 
            (Bitmap)Image.FromFile("path/to/large_image.jpg"));
        public Bitmap GetImage() => _lazyImage.Value;
    }

     

  3. 在依赖注入中使用

    在现代应用开发中,依赖注入容器也广泛利用延迟加载来优化服务启动时间。

  4. ⚠️ 注意事项与最佳实践

    • 异常处理:如果初始化委托(valueFactory)中抛出异常,Lazy<T>会缓存这个异常。之后每次访问 Value属性都会再次抛出相同的异常。因此,务必确保初始化逻辑的健壮性,或在委托内进行适当的异常处理。

    • 避免过度使用:只有在对象确实创建昂贵或可能不被使用时才考虑使用 Lazy<T>。不必要的包装会增加代码复杂性并带来微小的性能开销。

    • 检查是否已初始化:可以通过 IsValueCreated属性来检查对象是否已经被初始化,这在某些调试或特定逻辑中很有用。

    • if (lazyObject.IsValueCreated)
      {
          Console.WriteLine("对象已经初始化了。");
      }

       

posted @ 2025-11-18 13:35  家煜宝宝  阅读(2)  评论(0)    收藏  举报