1、延迟初始化出现于.NET 4.0,主要用于提高性能,避免浪费计算,并减少程序内存要求。也可以称为,按需加载。
2、从net 4.0开始,C#开始支持延迟初始化,通过Lazy关键字,我们可以声明某个对象为仅仅当第一次使用的时候,再初始化,如果一直没有调用,那就不初始化,省去了一部分不必要的开销,提升了效率,同时Lazy是天生线程安全的
3、注:Lazy<T> 对象初始化默认是线程安全的,在多线程环境下,第一个访问 Lazy<T> 对象的 Value 属性的线程将初始化 Lazy<T> 对象,以后访问的线程都将使用第一次初始化的数据。
对象创建成本高且程序可能不会使用它。 例如,假定内存中有具有 Orders
属性的 Customer
对象,该对象包含大量 Order
对象,初始化这些对象需要数据库连接。 如果用户永远不要求显示 Orders 或在计算中使用该数据,则无需使用系统内存或计算周期来创建它。 通过使用 Lazy<Orders>
来声明 Orders
对象用于迟缓初始化,可以避免在不使用该对象时浪费系统资源。对象创建成本高,且希望将其创建推迟到其他高成本操作完成后。 例如,假定程序在启动时加载多个对象实例,但是只需立即加载其中一部分。 可以通过推迟初始化不需要的对象,直到创建所需对象,提升程序的启动性能。
将委托或者方法对象保存,并在需要的时候调用。
虽然可以编写自己的代码来执行迟缓初始化,但我们建议使用 Lazy<T>。 Lazy<T> 及其相关的类型还支持线程安全并提供一致的异常传播策略。
下表列出了 .NET Framework 版本 4 提供的在不同方案中启用迟缓初始化的类型。
类型 | 说明 |
---|---|
Lazy<T> | 为任何类库或用户定义类型提供迟缓初始化语义的包装类。 |
ThreadLocal<T> | 类似于 Lazy<T>,除了该包装类基于线程本地提供迟缓初始化语义。 每个线程都可以访问自己唯一的值。 |
LazyInitializer | 为对象的迟缓初始化提供高级 static (Visual Basic 中的 Shared )方法,无需支付类的成本。 |
Lazy<T>() |
初始化 Lazy<T> 类的新实例。 发生迟缓初始化时,使用目标类型的无参数构造函数。 |
Lazy<T>(Boolean) |
初始化 Lazy<T> 类的新实例。 发生迟缓初始化时,使用目标类型的无参数构造函数和指定的初始化模式。 |
Lazy<T>(Func<T>) |
初始化 Lazy<T> 类的新实例。 出现迟缓初始化时,将使用指定的初始化函数。 |
Lazy<T>(Func<T>, Boolean) |
初始化 Lazy<T> 类的新实例。 当延迟初始化发生时,将使用指定的初始化函数和初始化模式。 |
Lazy<T>(Func<T>, LazyThreadSafetyMode) |
初始化 Lazy<T> 类的新实例,它使用指定的初始化函数和线程安全模式。 |
Lazy<T>(LazyThreadSafetyMode) |
初始化 Lazy<T> 类的新实例,其中使用 |
Lazy<T>(T) |
初始化 Lazy<T> 类的新实例,该类使用已预先初始化的指定值。 |
Lazy对象创建后,并不会立即创建对应的对象,一旦使用.Vale,那么对应的变量就会被实例化,IsValueCreated属性也就变成了true。
Value属性是只读的,也就意味着如果Value存储了引用类型,将无法为其分配新对象,只可以更改此对象公共的属性或者字段等,如果Value存储的是值类型,那么就不能修改其值了,只能通过再次调用变量的函数使用新的参数来创建新的变量
在Lazy对象创建后,在首次访问变量的Value属性前,
判断是实例化
指定 Lazy<T> 实例如何同步多个线程间的访问。
所有此类构造函数都是完全线程安全的。 在内部使用锁,则可能会发生死锁 |
ExecutionAndPublication | ||
不是线程安全的。 | None | false |
不适用。 |
完全线程安全;用于初始化值的线程争用。 | PublicationOnly |
using System; namespace LazyUsage { class LazyDemo { static void Main() { Lazy<Data> lazyData = new Lazy<Data>(); Console.WriteLine("Main->is lazyData Initialized? value = " + lazyData.IsValueCreated); lazyData.Value.Print();//此处访问时才会将Data真正的初始化 Console.WriteLine("Main->is lazyData Initialized? value = " + lazyData.IsValueCreated); Console.ReadKey(); } } class Data { public Data() { Console.WriteLine("Data::.ctor->Initialized"); } public void Print() { Console.WriteLine("Data::Print->println"); } } }
输出结果
Main->is lazyData Initialized? value = False
Data::.ctor->Initialized
Data::Print->println
Main->is lazyData Initialized? value = True
using System; namespace LazyUsage { class LazyDemo { static void Main() { //指定委托来初始化Data Lazy<Data> lazyData = new Lazy<Data>( () => { Console.WriteLine("Main->lazyData will be Initialized!"); return new Data("Test"); }); Console.WriteLine("Main->is lazyData Initialized? value = " + lazyData.IsValueCreated); lazyData.Value.Print(); Console.WriteLine("Main->is lazyData Initialized? value = " + lazyData.IsValueCreated); Console.ReadKey(); } } class Data { public string Name { get; private set; } public Data(string name) { Name = name; Console.WriteLine("Data::.ctor->Initialized,name = "+name); } public void Print() { Console.WriteLine("Data::Print->name = " + Name); } } }
输出结果
Main->is lazyData Initialized? value = False Main->lazyData will be Initialized! Data::.ctor->Initialized,name = Test Data::Print->name = Test Main->is lazyData Initialized? value = True
class Customer { private Lazy<Orders> _orders; public string CustomerID {get; private set;} public Customer(string id) { CustomerID = id; _orders = new Lazy<Orders>(() => { return new Orders(this.CustomerID); }); } public Orders MyOrders { get { return _orders.Value; } } }
该类相当与一个线程结界,将变量的值和作用限制在线程中。所以用该类包装过的类型是线程安全的。该类包装过的类型变量的只能在该线程中使用,其他线程包括子线程无法使用。
ThreadLocal<T>() |
初始化 ThreadLocal<T> 实例。 |
ThreadLocal<T>(Boolean) |
初始化 ThreadLocal<T> 实例并指定是否可从任意线程访问所有值。 |
ThreadLocal<T>(Func<T>) |
使用指定的 |
ThreadLocal<T>(Func<T>, Boolean) |
使用指定的 |
IsValueCreated |
获取是否在当前线程上初始化 Value。 |
Value |
获取或设置当前线程的此实例的值。 |
Values |
获取当前由已经访问此实例的所有线程存储的所有值的列表。 |
提供线程数据的本地存储。
static void Main(string[] args) { ThreadLocal<int> threadLocal = new ThreadLocal<int>(); //在主线程这个变量值为1 threadLocal.Value = 1; new Thread(() => Console.WriteLine($"托管线程ID:{Thread.CurrentThread.ManagedThreadId} 值为:{threadLocal.Value++}")).Start(); new Thread(() => Console.WriteLine($"托管线程ID:{Thread.CurrentThread.ManagedThreadId} 值为:{threadLocal.Value++}")).Start(); new Thread(() => Console.WriteLine($"托管线程ID:{Thread.CurrentThread.ManagedThreadId} 值为:{threadLocal.Value++}")).Start(); Console.WriteLine($"主线程ID:{Thread.CurrentThread.ManagedThreadId} 值为:{threadLocal.Value}"); }
输出结果:
托管线程ID:10 值为:0
主线程ID:1 值为:1
托管线程ID:11 值为:0
托管线程ID:12 值为:0
using System; using System.Threading; using System.Threading.Tasks; class ThreadLocalDemo { // Demonstrates: // ThreadLocal(T) constructor // ThreadLocal(T).Value // One usage of ThreadLocal(T) static void Main() { // Thread-Local variable that yields a name for a thread ThreadLocal<string> ThreadName = new ThreadLocal<string>(() => { return "Thread" + Thread.CurrentThread.ManagedThreadId; }); // Action that prints out ThreadName for the current thread Action action = () => { // If ThreadName.IsValueCreated is true, it means that we are not the // first action to run on this thread. bool repeat = ThreadName.IsValueCreated; Console.WriteLine("ThreadName = {0} {1}", ThreadName.Value, repeat ? "(repeat)" : ""); }; // Launch eight of them. On 4 cores or less, you should see some repeat ThreadNames Parallel.Invoke(action, action, action, action, action, action, action, action); // Dispose when you are done ThreadName.Dispose(); } } // This multithreading example can produce different outputs for each 'action' invocation and will vary with each run. // Therefore, the example output will resemble but may not exactly match the following output (from a 4 core processor): // ThreadName = Thread5 // ThreadName = Thread6 // ThreadName = Thread4 // ThreadName = Thread6 (repeat) // ThreadName = Thread1 // ThreadName = Thread4 (repeat) // ThreadName = Thread7 // ThreadName = Thread5 (repeat)