延迟初始化是一种将对象的创建延迟到第一次需要用时的技术。简而言之,就是对象的初始化发生在第一次需要调用的时候执行。通常所说的延迟初始化和延迟实例化的意思是相同。通过使用延迟基础,可以避免应用程序不必要的计算和内存消耗。
从.NET 4.0开始,可以使用Lazy来实现对象的延迟初始化,从而优化系统的性能。延迟初始化就是将对象的初始化延迟到第一次使用该对象时。延迟初始化是我们优化程序性能的一种方式。如创建一个对象时需要花费很大的开销,而这一对象在系统运行过程中不一定会用到,这时就可以使用延迟初始化,在第一次使用该对象时再对其进行实例化。如果没有用在整个应用程序生命期则不需要初始化,使用延迟初始化可以提高程序的利用率,从而使程序占用更少的内存。
Lazy是一个类,用于实现惰性加载(Lazy Initialization)
。惰性加载是指对象的创建被推迟,直到第一次被使用时,Lazy<T>
允许在第一次访问对象时进行初始化,这对于大型或资源密集型对象的性能优化非常有用。可以通过提供一个委托Delegate
来延迟初始化对象。Lazy<T>
确保所有线程使用同一个惰性加载对象的实例,并且丢弃使用的实例,从而优化内存使用。
Lazy<T>
允许你将对象的创建推迟到首次访问时。Lazy<T>
提供了线程安全的延迟初始化,确保在多线程环境中也能正确工作。Lazy<T>
会自动丢弃初始化失败的实例,优化内存使用。Lazy<T>.Value
属性访问延迟初始化的对象。在使用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
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
属性被首次访问时才会真正地创建,同时会将其缓存到Value
中,以便将来访问。
Value
属性是只读的,也就意味着如果Value
存储了引用类型,将无法为其分配新对象,只可以更改此对象公共地属性或字段等,如果Value
存储的是值类型。那么就不能修改其值,只能通过再次调用变量的函数使用新的参数来创建的变量。
在Lazy
对象创建后,在首次访问变量的Value
属性前。
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
对象,同时必须编写自己的线程安全代码才能执行此操作。
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; }
}
}
public Lazy (bool isThreadSafe):
isThreadSafe 的布尔参数,该方法参数用于指定是否从多线程访问 Value 属性。 如果想要仅从一个线程访问属性,则传入 false 以获取适度的性能优势。 如果想要从多线程访问属性,则传入 true 以指示 Lazy 实例正确处理争用条件(初始化时一个线程引发异常)。
public Lazy (LazyThreadSafetyMode mode)
提供线程安全模式
public Lazy (Func valueFactory)
lambda 表达式传递给新的 Lazy 对象的构造函数。 下一次访问 Value 属性将导致新 Lazy 的初始化,并且其 Value 属性此后会返回已分配给该属性的新值。