【.NET Core】Lazy<T> 实现延迟加载详解
【.NET Core】Lazy 实现延迟加载详解
一、背景与问题
在复杂的软件系统中,对象的初始化往往伴随着资源消耗。传统做法通常会在对象创建时立即执行初始化逻辑,这可能导致以下问题:
- 资源浪费:某些对象可能永远不会被使用,但初始化过程仍会占用系统资源
- 启动延迟:大量对象的提前初始化会显著增加应用启动时间
- 依赖冲突:某些对象的初始化需要依赖其他对象的完成,传统顺序初始化容易引发依赖关系混乱
以ASP.NET Core的依赖注入为例,当注册大量服务时,框架会立即创建实例。但如果服务的实际使用场景是异步的,这种提前实例化反而会带来不必要的资源消耗。Lazy
二、基本原理
Lazy
- 委托封装:将初始化逻辑封装为Func
委托 - 线程安全机制:通过Lazy
的内部锁机制保证多线程安全 - 缓存机制:初始化完成后会缓存实例,避免重复创建
关键代码结构如下:
public class Lazy<T>
{
private T _value;
private readonly Func<T> _factory;
private readonly object _lock = new object();
private bool _isInitialized;
public Lazy(Func<T> factory)
{
_factory = factory;
}
public T Value
{
get
{
if (!_isInitialized)
{
lock (_lock)
{
if (!_isInitialized)
{
_value = _factory();
_isInitialized = true;
}
}
}
return _value;
}
}
}三、环境准备
开发环境要求:
- .NET Core 2.0 或更高版本
- Visual Studio 2019 或更高版本
- 基础 C# 知识
项目结构建议:
/LazyLoadingDemo
/Controllers
/Models
/Services
Program.cs
Startup.cs四、核心实现
示例1:基础延迟加载
public class DatabaseConnection
{
public string ConnectionString { get; } = "Server=myServer;Database=myDB;User=myUser;Password=myPass;";
public DatabaseConnection()
{
Console.WriteLine("DatabaseConnection initialized");
}
public void Connect()
{
Console.WriteLine("Connecting to database...");
}
}
public class LazyExample
{
private static readonly Lazy<DatabaseConnection> _lazyInstance =
new Lazy<DatabaseConnection>(() => new DatabaseConnection());
public static void Run()
{
Console.WriteLine("Before initialization");
var instance = _lazyInstance.Value;
instance.Connect();
}
}关键代码解释:
_lazyInstance是一个静态的Lazy实例 Value属性在首次访问时触发初始化- 在
Connect()方法中输出调试信息验证初始化时机
示例2:多线程安全初始化
public class ThreadSafeLazy
{
private static readonly Lazy<ThreadSafeLazy> _lazyInstance =
new Lazy<ThreadSafeLazy>(() => new ThreadSafeLazy(), LazyThreadSafetyMode.PublicationOnly);
private int _counter = 0;
private ThreadSafeLazy()
{
Console.WriteLine("ThreadSafeLazy initialized");
}
public void Increment()
{
lock (this)
{
_counter++;
Console.WriteLine($"Counter: {_counter}");
}
}
}
public class ThreadTest
{
public static void Run()
{
Task.Run(() => {
var instance = ThreadSafeLazy._lazyInstance.Value;
instance.Increment();
}).Wait();
Task.Run(() => {
var instance = ThreadSafeLazy._lazyInstance.Value;
instance.Increment();
}).Wait();
}
}关键点:
- 使用
LazyThreadSafetyMode.PublicationOnly保证线程安全 lock(this)确保多线程下的线程安全- 通过
Task.Run模拟多线程环境
示例3:结合依赖注入
public interface IConfigService
{
string GetSetting(string key);
}
public class ConfigService : IConfigService
{
private readonly Lazy<Dictionary<string, string>> _settings =
new Lazy<Dictionary<string, string>>(() => LoadSettings());
private Dictionary<string, string> LoadSettings()
{
Console.WriteLine("Loading configuration settings");
return new Dictionary<string, string>
{
{ "env", "production" },
{ "debug", "false" }
};
}
public string GetSetting(string key)
{
return _settings.Value.ContainsKey(key) ? _settings.Value[key] : null;
}
}
public class LazyService
{
private readonly IConfigService _configService;
public LazyService(IConfigService configService)
{
_configService = configService;
}
public void ShowSettings()
{
var debug = _configService.GetSetting("debug");
Console.WriteLine($"Debug mode: {debug}");
}
}关键点:
- 将配置加载延迟到第一次调用时
- 通过依赖注入实现松耦合设计
- 使用Lazy
封装配置加载逻辑
五、完整案例
场景描述
构建一个日志记录系统,其中日志文件的路径由配置决定。当配置未指定时,日志系统应延迟初始化文件路径,并在首次写入时动态生成。
项目结构
/LoggingSystem
/Models
LogSettings.cs
/Services
LazyLogger.cs
LogService.cs
Program.cs代码实现
// Models/LogSettings.cs
public class LogSettings
{
public string Path { get; set; } = "default.log";
public bool IsEnabled { get; set; } = true;
}
// Services/LazyLogger.cs
public class LazyLogger
{
private readonly Lazy<string> _filePath;
private readonly LogSettings _settings;
public LazyLogger(LogSettings settings)
{
_settings = settings;
_filePath = new Lazy<string>(() =>
{
if (!_settings.IsEnabled) return null;
return Path.Combine(Environment.CurrentDirectory, _settings.Path);
});
}
public string GetFilePath()
{
return _filePath.Value;
}
public void WriteLog(string message)
{
var filePath = _filePath.Value;
if (filePath == null) throw new InvalidOperationException("Logging is disabled");
File.AppendAllText(filePath, $"{DateTime.Now}: {message}\n");
}
}
// Services/LogService.cs
public class LogService
{
private readonly LazyLogger _logger;
public LogService(LazyLogger logger)
{
_logger = logger;
}
public void Log(string message)
{
_logger.WriteLog(message);
}
}
// Program.cs
public class Program
{
public static void Main()
{
var settings = new LogSettings { Path = "logs/app.log", IsEnabled = false };
var logger = new LazyLogger(settings);
var logService = new LogService(logger);
Console.WriteLine("Before logging");
logService.Log("First log message");
}
}关键点:
- 通过Lazy
延迟确定日志文件路径 - 在日志写入时检查配置有效性
- 在配置未启用时避免初始化
- 通过依赖注入实现灵活配置
六、源码解析
.NET Core中的Lazy
public class Lazy<T>
{
private T _value;
private readonly Func<T> _factory;
private readonly object _lock = new object();
private bool _isInitialized;
public Lazy(Func<T> factory)
{
_factory = factory;
}
public Lazy(Func<T> factory, LazyThreadSafetyMode mode)
{
_factory = factory;
_mode = mode;
}
public T Value
{
get
{
if (!_isInitialized)
{
lock (_lock)
{
if (!_isInitialized)
{
try
{
_value = _factory();
_isInitialized = true;
}
catch (Exception ex)
{
throw new LazyInitializationException("Lazy initialization failed", ex);
}
}
}
}
return _value;
}
}
}关键机制:
- 双检锁(Double-Check Locking):在获取Value时先检查是否已初始化,未初始化时再加锁
- 异常处理:在初始化过程中捕获异常并抛出LazyInitializationException
- 线程安全模式:支持
PublicationOnly和ThreadSafe两种模式 - 缓存机制:一旦初始化完成,后续访问直接返回缓存实例
七、进阶使用
1. 与依赖注入结合
services.AddTransient(typeof(Lazy<>), typeof(LazyFactory<>));通过自定义工厂实现更复杂的依赖注入逻辑。
2. 复杂对象的延迟初始化
public class ComplexService
{
private readonly Lazy<BigObject> _bigObject = new Lazy<BigObject>(() =>
{
Console.WriteLine("Initializing big object...");
return new BigObject();
});
public void UseBigObject()
{
var obj = _bigObject.Value;
Console.WriteLine("Using big object");
}
}3. 带参数的延迟初始化
public class ParameterizedLazy
{
private readonly Lazy<string> _lazyValue = new Lazy<string>(() =>
{
Console.WriteLine("Initializing with parameter");
return "Lazy value with parameter";
});
public string GetValue()
{
return _lazyValue.Value;
}
}八、性能与工程实践
1. 性能优化
- 避免过度延迟:过度使用会导致初始化逻辑分散,增加调试难度
- 预初始化策略:在应用启动时预初始化关键对象
- 缓存策略:对重复请求的延迟初始化结果进行缓存
- 异步初始化:使用Lazy<Task
>实现异步初始化
2. 异常处理
try
{
var value = _lazyInstance.Value;
}
catch (LazyInitializationException ex)
{
Console.WriteLine($"Initialization failed: {ex.Message}");
}3. 资源释放
public class DisposableLazy<T> : IDisposable
{
private readonly Lazy<T> _lazy;
private bool _disposed = false;
public DisposableLazy(Func<T> factory)
{
_lazy = new Lazy<T>(factory);
}
public T Value => _lazy.Value;
public void Dispose()
{
if (!_disposed)
{
_lazy = null;
_disposed = true;
}
}
}4. 线程安全策略
| 模式 | 适用场景 | 说明 |
|---|---|---|
| PublicationOnly | 单线程访问 | 仅保证发布时的线程安全 |
| ThreadSafe | 多线程访问 | 保证整个生命周期的线程安全 |
九、常见问题与踩坑
1. 初始化失败处理
var lazy = new Lazy<SomeType>(() => InitializeSomeType());
try
{
var instance = lazy.Value;
}
catch (Exception ex)
{
Console.WriteLine($"Initialization error: {ex.Message}");
}2. 多线程初始化冲突
var lazy1 = new Lazy<SomeType>(() => CreateInstance());
var lazy2 = new Lazy<SomeType>(() => CreateInstance());3. 配置变更问题
var settings = new LogSettings { Path = "new.log" };
var logger = new LazyLogger(settings);
settings.Path = "old.log"; // 配置变更后不会影响已初始化的实例4. 资源泄漏风险
var lazy = new Lazy<BigObject>(() => new BigObject());
lazy.Value; // 资源已被占用十、最佳实践
适用场景
- 资源密集型对象:如数据库连接池、大型文件加载器
- 可选功能:如某些可选插件或模块
- 依赖注入:延迟初始化依赖项
- 配置驱动的初始化:根据配置决定是否初始化
- 单例模式:确保单例的延迟初始化
避免使用场景
- 初始化成本低的对象:如简单POCO类
- 需要立即初始化的场景:如核心业务逻辑
- 频繁访问的实例:可能增加额外开销
- 配置频繁变更:可能导致不一致
推荐方案
- 核心业务对象:使用常规实例化
- 可选服务:使用Lazy
延迟初始化 - 配置依赖对象:使用Lazy
结合配置管理 - 缓存对象:使用Lazy
配合缓存策略 - 复杂对象:使用Lazy
配合工厂模式
十一、总结
Lazy
- 按需初始化:减少资源浪费
- 提高性能:避免不必要的初始化
- 简化代码:提供优雅的延迟加载机制
- 保证线程安全:提供多种安全模式选择
在实际开发中,需要根据具体场景选择合适的使用策略:
- 对于关键业务对象,优先使用常规实例化
- 对于可选功能或配置依赖对象,使用Lazy
实现延迟初始化 - 对于复杂对象,结合工厂模式和Lazy
实现灵活控制 - 在多线程环境中,注意选择合适的线程安全模式
开发者应避免过度使用Lazy
评论已关闭