编辑
2025-09-17
C#
00

目录

什么是延迟加载?
Lazy\<T\>基础用法
深入理解Lazy\<T\>
基本示例:资源延迟加载
线程安全模式
异常处理和缓存异常
实际应用场景
单例模式与Lazy结合
依赖注入中的延迟解析
性能优化和注意事项
内存使用权衡
循环依赖问题
最佳实践总结
总结

在C#开发中,合理管理资源加载是提升应用性能的关键。本文将详细介绍C#中的Lazy<T>类,这个强大的延迟加载工具能帮助你优化程序执行效率,避免不必要的资源消耗。其实用到这个主要是取近一个老项目迁移,依赖注入有循环注入的,以前听说这个可以,也就试了一下,发现这个可用性还有不少地方。

什么是延迟加载?

延迟加载(Lazy Loading)是一种设计模式,核心思想是:只在真正需要时才创建对象或加载资源。在C#中,Lazy<T>类提供了一种简单优雅的方式来实现这一模式。

Lazy<T>基础用法

Lazy<T>类封装了一个延迟初始化的对象。以下是基本用法:

C#
// 创建Lazy<T>实例 Lazy<ExpensiveObject> lazyObject = new Lazy<ExpensiveObject>(() => new ExpensiveObject()); // 首次访问Value属性时,才会创建ExpensiveObject实例 ExpensiveObject instance = lazyObject.Value; // 判断是否已经初始化 bool isValueCreated = lazyObject.IsValueCreated;

深入理解Lazy<T>

基本示例:资源延迟加载

下面通过一个完整示例来展示Lazy的基本用法:

C#
using System; using System.Threading; public class Program { public static void Main() { Console.WriteLine("程序启动..."); // 创建Lazy<T>对象,但不会立即初始化大型资源 Lazy<LargeResource> lazyResource = new Lazy<LargeResource>(() => { Console.WriteLine("正在创建大型资源..."); return new LargeResource(); }); Console.WriteLine("Lazy<T>对象已创建,但大型资源尚未加载"); Console.WriteLine($"资源是否已初始化: {lazyResource.IsValueCreated}"); // 模拟程序运行一段时间 Console.WriteLine("程序执行其他操作..."); Thread.Sleep(2000); // 只有在首次访问Value属性时,才会执行初始化委托 Console.WriteLine("即将访问资源..."); LargeResource resource = lazyResource.Value; Console.WriteLine($"资源是否已初始化: {lazyResource.IsValueCreated}"); // 再次访问不会重新创建 Console.WriteLine("再次访问资源..."); resource = lazyResource.Value; Console.WriteLine("程序结束"); } } // 模拟需要大量资源初始化的类 public class LargeResource { public LargeResource() { // 模拟耗时操作 Thread.Sleep(3000); Console.WriteLine("大型资源初始化完成!"); } public void DoWork() { Console.WriteLine("资源正在工作..."); } }

image.png

线程安全模式

Lazy<T>提供三种不同的线程安全模式:

C#
namespace AppLazyx { public class ExpensiveResource { private static int counter = 0; public string Id { get; private set; } public ExpensiveResource(string mode) { // 模拟初始化耗时 Thread.Sleep(100); Id = $"{mode}-{Interlocked.Increment(ref counter)}"; Console.WriteLine($"创建了资源: {Id}"); } } public class Program { public static void Main() { // 1. ExecutionAndPublication (默认) - 完全线程安全 Lazy<ExpensiveResource> safeLazy = new Lazy<ExpensiveResource>( () => new ExpensiveResource("完全线程安全模式"), LazyThreadSafetyMode.ExecutionAndPublication); // 2. PublicationOnly - 仅发布线程安全 Lazy<ExpensiveResource> publicationLazy = new Lazy<ExpensiveResource>( () => new ExpensiveResource("仅发布线程安全模式"), LazyThreadSafetyMode.PublicationOnly); // 3. None - 不保证线程安全 Lazy<ExpensiveResource> nonSafeLazy = new Lazy<ExpensiveResource>( () => new ExpensiveResource("非线程安全模式"), LazyThreadSafetyMode.None); // 测试多线程访问 TestMultiThreadAccess(safeLazy, "ExecutionAndPublication"); TestMultiThreadAccess(publicationLazy, "PublicationOnly"); } private static void TestMultiThreadAccess(Lazy<ExpensiveResource> lazy, string mode) { Console.WriteLine($"\n测试 {mode} 模式下的多线程访问:"); // 创建多个任务同时访问Lazy对象 Task[] tasks = new Task[5]; for (int i = 0; i < tasks.Length; i++) { tasks[i] = Task.Run(() => { Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId} 正在访问资源..."); ExpensiveResource resource = lazy.Value; Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId} 获取到资源: {resource.Id}"); }); } Task.WaitAll(tasks); Console.WriteLine($"{mode} 模式测试完成,资源ID: {lazy.Value.Id}\n"); } } }

image.png

异常处理和缓存异常

Lazy<T>会缓存初始化过程中发生的异常,下次访问时会重新抛出:

C#
namespace AppLazyx { public class ProblematicResource { public ProblematicResource() { Console.WriteLine("创建有问题的资源..."); } } public class Program { public static void Main() { // 创建可能抛出异常的Lazy对象 Lazy<ProblematicResource> lazyResource = new Lazy<ProblematicResource>(() => { Console.WriteLine("尝试初始化有问题的资源..."); throw new InvalidOperationException("资源初始化失败"); }); try { // 首次访问会触发异常 Console.WriteLine("首次尝试访问资源..."); var resource = lazyResource.Value; } catch (InvalidOperationException ex) { Console.WriteLine($"捕获到异常: {ex.Message}"); } try { // 再次访问会重新抛出缓存的异常,而不会重新执行初始化 Console.WriteLine("\n再次尝试访问资源..."); var resource = lazyResource.Value; } catch (InvalidOperationException ex) { Console.WriteLine($"再次捕获到异常: {ex.Message}"); } } } }

image.png

实际应用场景

单例模式与Lazy结合

C#
namespace AppLazyx { // 使用Lazy实现线程安全的单例模式 public class ConfigurationManager { // 私有静态Lazy<T>字段 private static readonly Lazy<ConfigurationManager> _instance = new Lazy<ConfigurationManager>(() => new ConfigurationManager()); // 公共静态实例属性 public static ConfigurationManager Instance => _instance.Value; // 配置数据 private readonly string[] _configData; // 私有构造函数 private ConfigurationManager() { Console.WriteLine("加载配置文件..."); // 模拟从文件加载配置 _configData = new string[] { "数据库连接串: Server=myserver;Database=mydb;", "超时设置: 30000", "最大连接数: 100" }; Console.WriteLine("配置文件加载完成"); } public string GetSetting(int index) { if (index >= 0 && index < _configData.Length) return _configData[index]; return "设置不存在"; } } public class Program { public static void Main() { Console.WriteLine("程序启动..."); Console.WriteLine("执行业务逻辑,暂不需要配置..."); // 模拟程序运行一段时间后才需要访问配置 Console.WriteLine("\n需要访问配置,第一次访问将触发ConfigurationManager初始化"); var configManager = ConfigurationManager.Instance; // 使用配置 Console.WriteLine($"读取配置: {configManager.GetSetting(0)}"); Console.WriteLine($"读取配置: {configManager.GetSetting(1)}"); // 再次获取实例不会重新初始化 Console.WriteLine("\n再次获取ConfigurationManager实例"); var sameManager = ConfigurationManager.Instance; Console.WriteLine($"是否为同一个实例: {ReferenceEquals(configManager, sameManager)}"); } } }

image.png

依赖注入中的延迟解析

C#
using System; namespace AppLazyx { // 服务接口 public interface IEmailService { void SendEmail(string to, string subject, string body); } // 服务实现 public class EmailService : IEmailService { public EmailService() { Console.WriteLine("邮件服务初始化中..."); // 模拟复杂的资源初始化 System.Threading.Thread.Sleep(2000); Console.WriteLine("邮件服务就绪"); } public void SendEmail(string to, string subject, string body) { Console.WriteLine($"发送邮件到: {to}"); Console.WriteLine($"主题: {subject}"); Console.WriteLine($"内容: {body}"); } } // 使用延迟加载的服务容器 public class ServiceContainer { // 使用Lazy延迟初始化服务 private readonly Lazy<IEmailService> _emailService = new Lazy<IEmailService>(() => new EmailService()); // 暴露延迟加载的服务 public Lazy<IEmailService> EmailService => _emailService; } // 演示类 public class DependencyInjectionDemo { public static void Main() { // 创建服务容器 var container = new ServiceContainer(); Console.WriteLine("服务容器已创建,但服务尚未初始化"); // 创建使用服务的组件 var userRegistration = new UserRegistration(container.EmailService); Console.WriteLine("用户注册组件已创建"); // 进行不需要发送邮件的操作 userRegistration.ValidateUser("user@example.com", "password"); // 只有在需要发送欢迎邮件时才会初始化邮件服务 Console.WriteLine("\n准备发送欢迎邮件..."); userRegistration.SendWelcomeEmail("user@example.com"); } } // 使用延迟加载服务的组件 public class UserRegistration { private readonly Lazy<IEmailService> _emailService; public UserRegistration(Lazy<IEmailService> emailService) { _emailService = emailService; Console.WriteLine("用户注册组件已初始化,尚未加载邮件服务"); } public bool ValidateUser(string email, string password) { Console.WriteLine($"验证用户: {email}"); // 此操作不需要邮件服务 return true; } public void SendWelcomeEmail(string email) { Console.WriteLine($"准备向 {email} 发送欢迎邮件"); // 只有在这里才会初始化邮件服务 _emailService.Value.SendEmail( email, "欢迎加入我们!", "感谢您的注册,这里是一些使用说明..." ); Console.WriteLine("欢迎邮件已发送"); } } }

image.png

性能优化和注意事项

内存使用权衡

C#
using System; using System.Collections.Generic; using System.Diagnostics; namespace AppLazyx { public class PerformanceDemo { public static void Main() { const int iterations = 10000; Console.WriteLine("性能对比:直接加载 vs. 延迟加载\n"); // 测试直接加载 Stopwatch sw = Stopwatch.StartNew(); List<DataProcessor> directProcessors = new List<DataProcessor>(); for (int i = 0; i < iterations; i++) { directProcessors.Add(new DataProcessor()); } sw.Stop(); Console.WriteLine($"直接创建 {iterations} 个处理器用时: {sw.ElapsedMilliseconds}ms"); // 测试延迟加载但不访问 sw.Restart(); List<Lazy<DataProcessor>> lazyProcessors = new List<Lazy<DataProcessor>>(); for (int i = 0; i < iterations; i++) { lazyProcessors.Add(new Lazy<DataProcessor>()); } sw.Stop(); Console.WriteLine($"创建 {iterations} 个Lazy<T>包装器用时: {sw.ElapsedMilliseconds}ms"); // 测试访问Lazy包装对象的值 sw.Restart(); foreach (var processor in lazyProcessors) { DataProcessor instance = processor.Value; } sw.Stop(); Console.WriteLine($"访问 {iterations} 个Lazy<T>对象的值用时: {sw.ElapsedMilliseconds}ms"); // 内存使用比较 Console.WriteLine("\n内存占用比较 (仅供参考):"); GC.Collect(); GC.WaitForPendingFinalizers(); long beforeSize = GC.GetTotalMemory(true); // 创建大量未初始化的Lazy对象 List<Lazy<LargeData>> lazyData = new List<Lazy<LargeData>>(); for (int i = 0; i < 1000; i++) { lazyData.Add(new Lazy<LargeData>()); } long afterLazySize = GC.GetTotalMemory(true); Console.WriteLine($"1000个未初始化的Lazy<LargeData>占用内存: {(afterLazySize - beforeSize) / 1024}KB"); // 创建同等数量的直接实例 List<LargeData> directData = new List<LargeData>(); for (int i = 0; i < 1000; i++) { directData.Add(new LargeData()); } long afterDirectSize = GC.GetTotalMemory(true); Console.WriteLine($"1000个直接创建的LargeData占用内存: {(afterDirectSize - afterLazySize) / 1024}KB"); } } // 模拟数据处理器 public class DataProcessor { public DataProcessor() { // 模拟轻量级初始化 } } // 模拟大数据对象 public class LargeData { // 模拟大量数据 private byte[] _data = new byte[10000]; } }

image.png

循环依赖问题

这个对有些注入依赖时用处挺大的,但最好不要这么干 。

C#
using System; // 演示循环依赖问题 public class CircularDependencyDemo { public static void Main() { try { Console.WriteLine("尝试创建有循环依赖的对象..."); // 创建A和B,它们互相引用 Lazy<ComponentA> lazyA = null; Lazy<ComponentB> lazyB = null; lazyA = new Lazy<ComponentA>(() => new ComponentA(lazyB)); lazyB = new Lazy<ComponentB>(() => new ComponentB(lazyA)); // 尝试访问A,这将触发循环依赖 Console.WriteLine("尝试访问组件A..."); var a = lazyA.Value; // 这行代码永远不会执行,因为上面会抛出异常 Console.WriteLine("成功创建组件!"); } catch (Exception ex) { Console.WriteLine($"发生异常: {ex.GetType().Name}"); Console.WriteLine($"异常信息: {ex.Message}"); if (ex.InnerException != null) { Console.WriteLine($"内部异常: {ex.InnerException.Message}"); } Console.WriteLine("\n解决方案:重新设计对象关系,避免循环依赖,或使用接口注入"); } } } public class ComponentA { private readonly Lazy<ComponentB> _b; public ComponentA(Lazy<ComponentB> b) { Console.WriteLine("创建组件A"); _b = b; // 访问B将触发循环依赖 Console.WriteLine($"组件A尝试访问组件B..."); var temp = _b.Value; } } public class ComponentB { private readonly Lazy<ComponentA> _a; public ComponentB(Lazy<ComponentA> a) { Console.WriteLine("创建组件B"); _a = a; // 访问A将触发循环依赖 Console.WriteLine($"组件B尝试访问组件A..."); var temp = _a.Value; } }

image.png

最佳实践总结

  1. 合理使用场景
    • 创建成本高的对象
    • 可能不会使用的对象
    • 需要线程安全的单例模式
  2. 线程安全考虑
    • 默认模式(ExecutionAndPublication)适合大多数场景
    • 性能敏感场景可考虑使用PublicationOnly
    • 单线程环境可使用None模式获得最佳性能
  3. 避免陷阱
    • 注意处理初始化异常
    • 避免循环依赖
    • 不要在构造函数中过度使用Lazy,可能导致复杂性增加

总结

Lazy<T>是C#中实现延迟加载的强大工具,可显著优化应用性能和资源使用。本文通过详细示例展示了其基本用法、线程安全模式、异常处理以及实际应用场景。掌握Lazy<T>的使用技巧,让你的C#应用更高效、更可靠。

希望这篇详解对你有所帮助!如有问题,欢迎在评论区留言交流。


关键词:C# Lazy、延迟加载、懒加载、Lazy<T>、性能优化、C#单例模式、线程安全

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!