【.NET Core】Lazy 实现延迟加载详解
文章目录
一、概述
延迟初始化是一种将对象的创建延迟到第一次需要用时的技术。简而言之,就是对象的初始化发生在第一次需要调用的时候执行。通常所说的延迟初始化和延迟实例化的意思是相同。通过使用延迟基础,可以避免应用程序不必要的计算和内存消耗。
从.NET 4.0开始,可以使用Lazy来实现对象的延迟初始化,从而优化系统的性能。延迟初始化就是将对象的初始化延迟到第一次使用该对象时。延迟初始化是我们优化程序性能的一种方式。如创建一个对象时需要花费很大的开销,而这一对象在系统运行过程中不一定会用到,这时就可以使用延迟初始化,在第一次使用该对象时再对其进行实例化。如果没有用在整个应用程序生命期则不需要初始化,使用延迟初始化可以提高程序的利用率,从而使程序占用更少的内存。
二、Lazy是什么
Lazy是一个类,用于实现惰性加载(Lazy Initialization)
。惰性加载是指对象的创建被推迟,直到第一次被使用时,Lazy<T>
允许在第一次访问对象时进行初始化,这对于大型或资源密集型对象的性能优化非常有用。可以通过提供一个委托Delegate
来延迟初始化对象。Lazy<T>
确保所有线程使用同一个惰性加载对象的实例,并且丢弃使用的实例,从而优化内存使用。
- 延迟初始化(Lazy Initialization):
Lazy<T>
允许你将对象的创建推迟到首次访问时。 - 线程安全(Thread-Safe):
Lazy<T>
提供了线程安全的延迟初始化,确保在多线程环境中也能正确工作。 - 自动丢弃未使用的实例:如果对象未被使用,
Lazy<T>
会自动丢弃初始化失败的实例,优化内存使用。 - 支持复杂的初始化逻辑:你可以提供一个委托,允许你在初始化对象时执行复杂的逻辑。
- Value 属性:通过
Lazy<T>.Value
属性访问延迟初始化的对象。
三、Lazy基本用法
3.1 构造时使用默认的初始化方式
在使用Lazy时,如果没有在构造函数中传入委托,则在首次访问值属性时,将会使用Activator.CreateInstance
来创建类型的对象,如果此类型没有无参数的构造函数时将会引发运行时异常。
本文实例类:
public class Employee
{
public int Id { get; set; } = 101;
public string? Code { get; set; } = "G001";
public string? Name { get; set; } = "爷叔";
public string? Address { get; set; } = "上海市黄河路1001号";
public Employee() {}
public Employee(int id,string code,string name,string address)
{
this.Id = id;
this.Code = code;
this.Name = name;
this.Address = address;
}
public void Show()
{
Console.WriteLine($"Id={Id},Code={Code},Name={Name},Address={Address}");
}
}
public static void Main(string[] arg)
{
Lazy<Employee> lazyEmployee = new Lazy<Employee>();
Console.WriteLine($"Main->is lazyData Initialized? value = {lazyEmployee.IsValueCreated}");
lazyEmployee.Value.Show();//此处访问时才会将Data真正的初始化
Console.WriteLine($"Main->is lazyData Initialized? value = {lazyEmployee.IsValueCreated}");
}
运行结果
Main->is lazyData Initialized? value = False
Id=101,Code=G001,Name=爷叔,Address=上海市黄河路1001号
Main->is lazyData Initialized? value = True
3.2 构造时使用指定的委托初始化
public static void Main(string[] arg)
{
Lazy<Employee> lazyEmployee = new Lazy<Employee>(() =>
{
Console.WriteLine("Main->lazyData will be Initialized!");
return new Employee(2,"G003","阿宝","上海市南京路001号");
});
Console.WriteLine($"Main->is lazyData Initialized? value = {lazyEmployee.IsValueCreated}");
lazyEmployee.Value.Show();//此处访问时才会将Data真正的初始化
Console.WriteLine($"Main->is lazyData Initialized? value = {lazyEmployee.IsValueCreated}");
}
运行结果
Main->is lazyData Initialized? value = False
Main->lazyData will be Initialized!
Id=2,Code=G003,Name=阿宝,Address=上海市南京路001号
Main->is lazyData Initialized? value = True
四、Lazy.Value使用
由上面两个应用,可以看出Lazy
对象创建后,并不会立即创建对应地对象,只有在变量的Value
属性被首次访问时才会真正地创建,同时会将其缓存到Value
中,以便将来访问。
Value
属性是只读的,也就意味着如果Value
存储了引用类型,将无法为其分配新对象,只可以更改此对象公共地属性或字段等,如果Value
存储的是值类型。那么就不能修改其值,只能通过再次调用变量的函数使用新的参数来创建的变量。
在Lazy
对象创建后,在首次访问变量的Value
属性前。
五、Lazy扩展用法
5.1 实现延迟属性
public class Customer
{
private Lazy<Employee> employee;
public int CustomerId { get; private set; }
public Customer(int id, string code, string name, string address)
{
this.CustomerId = id;
employee = new Lazy<Employee>(() => new Employee(
this.CustomerId, "C001", "李阿宝", "上海市南京西路1100号"
));
}
}
从上面介绍Lazy.Value
中可以得知:Value的属性是只读,示例中只提供了Get的访问器,并未提供Set的访问器。如果需要支持读取与写入属性的话,则Set访问器必须创建一个新地Lazy
对象,同时必须编写自己的线程安全代码才能执行此操作。
5.2 Lazy
实现惰性加载单例模式
public class Singleton<T> where T : class
{
private static readonly Lazy<T> current = new Lazy<T>(
() => Activator.CreateInstance<T>(), // factory method
true); // double locks
public static object Current
{
get { return current.Value; }
}
}
六、Lazy常用扩展方法
-
public Lazy (bool isThreadSafe):
isThreadSafe 的布尔参数,该方法参数用于指定是否从多线程访问 Value 属性。 如果想要仅从一个线程访问属性,则传入 false 以获取适度的性能优势。 如果想要从多线程访问属性,则传入 true 以指示 Lazy 实例正确处理争用条件(初始化时一个线程引发异常)。
-
public Lazy (LazyThreadSafetyMode mode)
提供线程安全模式
-
public Lazy (Func valueFactory)
lambda 表达式传递给新的 Lazy 对象的构造函数。 下一次访问 Value 属性将导致新 Lazy 的初始化,并且其 Value 属性此后会返回已分配给该属性的新值。