C#Lazy
Lazy<T>是一个用于实现延迟初始化(Lazy Initialization)的泛型类,它可以将昂贵对象的创建推迟到第一次实际使用时。这种模式能有效提升应用程序的启动性能和资源利用率。
💡 核心概念与价值
Lazy<T>的核心思想是“按需创建”。它特别适用于以下场景:
-
资源密集型对象:创建成本高、耗时长的对象,如加载大型文件、建立数据库连接或进行复杂计算。
-
可能不被使用的对象:某些功能或组件在程序运行过程中可能根本不会被访问,使用
Lazy<T>可以避免不必要的初始化开销。 -
提升启动速度:通过将非核心资源的初始化后移,让应用程序更快启动。
使用Lazy<T>时,需要注意以下几点:
-
线程安全:Lazy<T>默认是线程安全的,可以在多线程环境下使用,确保只有一个线程可以初始化实例。
-
初始化方式:可以通过传递一个委托来自定义初始化过程,如果不传递,则使用类型的默认构造函数。
-
异常处理:如果初始化过程中出现异常,则每次访问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 );
📚 实际应用场景
-
实现单例模式(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; }
-
延迟加载资源
对于如图片、音频、视频等大型资源,可以使用
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; }
-
在依赖注入中使用
在现代应用开发中,依赖注入容器也广泛利用延迟加载来优化服务启动时间。
-
⚠️ 注意事项与最佳实践
-
异常处理:如果初始化委托(
valueFactory)中抛出异常,Lazy<T>会缓存这个异常。之后每次访问Value属性都会再次抛出相同的异常。因此,务必确保初始化逻辑的健壮性,或在委托内进行适当的异常处理。 -
避免过度使用:只有在对象确实创建昂贵或可能不被使用时才考虑使用
Lazy<T>。不必要的包装会增加代码复杂性并带来微小的性能开销。 -
检查是否已初始化:可以通过
IsValueCreated属性来检查对象是否已经被初始化,这在某些调试或特定逻辑中很有用。 -
if (lazyObject.IsValueCreated) { Console.WriteLine("对象已经初始化了。"); }
-
浙公网安备 33010602011771号