C#使用Lazy實(shí)現(xiàn)延遲加載的方法示例
前言
在C#中,Lazy< T> 類(lèi)是一個(gè)非常有用的工具,它可以用于延遲加載值,尤其是在創(chuàng)建對(duì)象時(shí)可能很昂貴,或者你想要延遲初始化直到真正需要該值的情況下。在本文中,我們將詳細(xì)介紹 Lazy< T> 的實(shí)現(xiàn)機(jī)制和用法,并提供一些示例來(lái)展示它的優(yōu)勢(shì)。
1、Lazy 的工作原理
Lazy< T> 類(lèi)是.NET框架中的一個(gè)并發(fā)類(lèi),它允許你延遲初始化一個(gè)對(duì)象,直到這個(gè)對(duì)象被第一次使用時(shí)才進(jìn)行。這意味著,如果多個(gè)線程需要訪問(wèn)同一個(gè)延遲初始化的對(duì)象,Lazy< T> 能夠保證只有一個(gè)線程會(huì)執(zhí)行初始化代碼,從而避免不必要的資源消耗。
Lazy< T> 采用懶漢式初始化模式,在.NET Framework 4.0及之前的版本中,它是線程安全的,采用內(nèi)部互斥鎖(Mutex)來(lái)確保線程安全。但在.NET 4.0之后,Lazy< T> 采用了新的LazyInitializationMode.None模式,允許非線程安全且更高效的初始化,這時(shí)需要開(kāi)發(fā)者自己確保初始化的線程安全。
2、創(chuàng)建 Lazy 實(shí)例
要?jiǎng)?chuàng)建一個(gè) Lazy< T> 實(shí)例,你可以使用以下構(gòu)造函數(shù):
Lazy<T>() : this(LazyThreadSafetyMode.ExecutionAndPublication) Lazy<T>(Func<T> valueFactory) : this(valueFactory, LazyThreadSafetyMode.ExecutionAndPublication) Lazy<T>(LazyThreadSafetyMode mode) Lazy<T>(Func<T> valueFactory, LazyThreadSafetyMode mode)
LazyThreadSafetyMode 是一個(gè)枚舉,用于指定初始化時(shí)的線程安全模式。有四種模式:
- LazyThreadSafetyMode.None:允許非線程安全初始化。
- LazyThreadSafetyMode.ExecutionAndPublication:執(zhí)行初始化時(shí)是線程安全的,且Publish方法也是線程安全的。
- LazyThreadSafetyMode.PublicationOnly:僅Publish方法是線程安全的。
- LazyThreadSafetyMode.UnprotectedPublication:既不是執(zhí)行時(shí)也不是發(fā)布時(shí)線程安全。
3、 使用 Lazy
一旦你創(chuàng)建了一個(gè) Lazy< T> 實(shí)例,你可以通過(guò)其 Value 屬性來(lái)獲取其內(nèi)部值的引用,該屬性是只讀的,并會(huì)在第一次訪問(wèn)時(shí)觸發(fā)值的初始化。
4、示例
下面我們通過(guò)一個(gè)示例來(lái)演示如何使用 Lazy 進(jìn)行延遲加載。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
// 使用 Lazy<T> 創(chuàng)建一個(gè)延遲加載的對(duì)象
Lazy<ExpensiveObject> lazyExpensiveObject = new Lazy<ExpensiveObject>(() => new ExpensiveObject(), LazyThreadSafetyMode.ExecutionAndPublication);
// 獲取對(duì)象值,這將觸發(fā)延遲加載
ExpensiveObject expensiveObject = lazyExpensiveObject.Value;
// 使用 expensiveObject 做些事情
Console.WriteLine(expensiveObject.SomeProperty);
Console.ReadKey();
}
}
class ExpensiveObject
{
public ExpensiveObject()
{
// 模擬一個(gè)初始化代價(jià)很昂貴的操作
Console.WriteLine("Expensive object initialized.");
}
public string SomeProperty { get; set; }
}
在這個(gè)示例中,我們創(chuàng)建了一個(gè) ExpensiveObject 的 Lazy 實(shí)例。這個(gè) ExpensiveObject 的構(gòu)造函數(shù)是一個(gè)耗時(shí)的操作。當(dāng)我們第一次訪問(wèn) lazyExpensiveObject.Value 時(shí),構(gòu)造函數(shù)會(huì)被調(diào)用,并且 ExpensiveObject 實(shí)例會(huì)被創(chuàng)建。注意,之后對(duì)這個(gè)屬性的所有訪問(wèn)都會(huì)直接返回已經(jīng)創(chuàng)建的實(shí)例,而不會(huì)再次調(diào)用構(gòu)造函數(shù)。
注意事項(xiàng)
- 如果你需要在多個(gè)線程中共享延遲加載的對(duì)象,請(qǐng)確保你正確同步對(duì)這個(gè)對(duì)象的訪問(wèn)。
- 如果你的初始化操作是線程安全的,你可以使用
LazyThreadSafetyMode.ExecutionAndPublication,這樣可以保證初始化過(guò)程和發(fā)布過(guò)程都是線程安全的。 - 如果你的初始化操作不依賴(lài)于外部狀態(tài),并且你確信它可以在多個(gè)線程中安全地并行執(zhí)行,你可以使用
LazyThreadSafetyMode.None,這將避免線程鎖定,并可能提高性能。
5、Lazy< T> 實(shí)現(xiàn)延遲加載
Lazy< T> 利用了 C# 的屬性器和反射機(jī)制來(lái)實(shí)現(xiàn)延遲加載。當(dāng)訪問(wèn) Lazy< T> 的 Value 屬性時(shí),如果內(nèi)部值尚未初始化,則初始化它。這個(gè)過(guò)程稱(chēng)為“lazy initialization”。Lazy< T> 提供了幾種不同的線程安全模式,以適應(yīng)不同的場(chǎng)景。
實(shí)現(xiàn)方式
下面是使用 Lazy 進(jìn)行延遲加載資源的基本步驟:
- 創(chuàng)建一個(gè) Lazy 實(shí)例,并通過(guò)提供一個(gè)函數(shù)來(lái)指定要延遲加載的資源。
- 在需要的時(shí)候,通過(guò)訪問(wèn) Lazy 的 Value 屬性來(lái)觸發(fā)資源的加載。
示例:延遲加載圖片
假設(shè)我們有以下一個(gè)類(lèi),它使用 Lazy 來(lái)延遲加載圖片:
using System;
using System.Drawing;
using System.Threading.Tasks;
public class ImageLoader
{
private Lazy<Bitmap> _lazyImage = new Lazy<Bitmap>(() => LoadImageAsync("path/to/image.jpg"), LazyThreadSafetyMode.ExecutionAndPublication);
public Bitmap GetImage()
{
return _lazyImage.Value;
}
private async Task<Bitmap> LoadImageAsync(string imagePath)
{
using (var stream = new FileStream(imagePath, FileMode.Open))
{
return (Bitmap)Image.FromStream(stream);
}
}
}
在這個(gè)例子中,ImageLoader 類(lèi)有一個(gè) Lazy 實(shí)例,它通過(guò)異步方法 LoadImageAsync 加載圖片。當(dāng)調(diào)用 GetImage 方法時(shí),Lazy 會(huì)觸發(fā) LoadImageAsync 的執(zhí)行,并返回圖片。
示例:延遲加載視頻
視頻加載通常涉及到更復(fù)雜的操作,下面是一個(gè)簡(jiǎn)化的例子:
using System;
using System.IO;
using System.Threading.Tasks;
public class VideoLoader
{
private Lazy<FileStream> _lazyVideoStream = new Lazy<FileStream>(() => LoadVideoAsync("path/to/video.mp4"), LazyThreadSafetyMode.ExecutionAndPublication);
public FileStream GetVideoStream()
{
return _lazyVideoStream.Value;
}
private async Task<FileStream> LoadVideoAsync(string videoPath)
{
return new FileStream(videoPath, FileMode.Open);
}
}
在這個(gè)例子中,VideoLoader 類(lèi)使用 Lazy 來(lái)延遲加載視頻文件流。當(dāng) GetVideoStream 被調(diào)用時(shí),視頻文件流會(huì)被創(chuàng)建并返回。
示例:延遲加載音頻
音頻文件的加載可以類(lèi)似于視頻文件的加載:
using System;
using System.IO;
using System.Threading.Tasks;
public class AudioLoader
{
private Lazy<FileStream> _lazyAudioStream = new Lazy<FileStream>(() => LoadAudioAsync("path/to/audio.wav"), LazyThreadSafetyMode.ExecutionAndPublication);
public FileStream GetAudioStream()
{
return _lazyAudioStream.Value;
}
private async Task<FileStream> LoadAudioAsync(string audioPath)
{
return new FileStream(audioPath, FileMode.Open);
}
}
在這個(gè)例子中,AudioLoader 類(lèi)使用 Lazy 來(lái)延遲加載音頻文件流。當(dāng) GetAudioStream 被調(diào)用時(shí),音頻文件流會(huì)被創(chuàng)建并返回。
6、如何在多線程環(huán)境中測(cè)試 Lazy<T> 的線程安全性?
在多線程環(huán)境中測(cè)試 Lazy< T> 的線程安全性通常涉及到模擬 concurrent access(并發(fā)訪問(wèn))來(lái)確保 Lazy< T> 在不同線程之間正確地處理初始化和訪問(wèn)。這里有幾種方法可以用來(lái)測(cè)試 Lazy< T> 的線程安全性:
- 使用 Lazy< T> 的同步模式: 在 Lazy 的構(gòu)造函數(shù)中指定 LazyThreadSafetyMode.ExecutionAndPublication 或 LazyThreadSafetyMode.PublicationOnly,這樣 Lazy< T> 會(huì)確保在多個(gè)線程中的執(zhí)行和發(fā)布都是線程安全的。
- 手動(dòng)同步: 如果你使用的是 LazyThreadSafetyMode.None,你需要手動(dòng)同步對(duì) Lazy< T> 屬性的訪問(wèn)。這可以通過(guò) lock 語(yǔ)句或 Monitor 類(lèi)來(lái)實(shí)現(xiàn)。
- 使用 Task 和 Parallel 類(lèi): 使用 Task 并行庫(kù)來(lái)創(chuàng)建多個(gè)任務(wù),每個(gè)任務(wù)訪問(wèn) Lazy 的 Value 屬性。確保在所有任務(wù)都完成時(shí),Lazy< T> 的初始化只執(zhí)行一次。
- 使用 Mutex 或 Semaphore: 使用 Mutex 或 Semaphore 來(lái)控制對(duì) Lazy< T> 初始化代碼的訪問(wèn),確保初始化是獨(dú)占進(jìn)行的。
- 單元測(cè)試: 編寫(xiě)單元測(cè)試來(lái)模擬并發(fā)訪問(wèn)。可以使用測(cè)試框架(如 NUnit 或 xUnit)來(lái)創(chuàng)建多個(gè)測(cè)試線程,并確保它們正確地訪問(wèn) Lazy< T>。
- 代碼分析工具: 使用像 NDepend 或 SonarQube 這樣的代碼分析工具來(lái)檢測(cè)可能的線程安全問(wèn)題。
下面是一個(gè)簡(jiǎn)單的示例,展示了如何在單元測(cè)試中使用 Lazy< T> 和 Task 來(lái)測(cè)試線程安全性:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
public class LazyTestClass
{
private Lazy<List<int>> _lazyList = new Lazy<List<int>>(() => new List<int>(), LazyThreadSafetyMode.ExecutionAndPublication);
public List<int> GetList()
{
return _lazyList.Value;
}
}
public class Program
{
public static void Main()
{
LazyTestClass testClass = new LazyTestClass();
// 創(chuàng)建多個(gè)任務(wù)來(lái)并發(fā)訪問(wèn) Lazy<T>
var tasks = Enumerable.Range(1, 10).Select(i => Task.Run(() => testClass.GetList()));
// 等待所有任務(wù)完成
Task.WaitAll(tasks.ToArray());
}
}
// 單元測(cè)試
public class LazyTestClassTests
{
[Fact]
public void TestLazyThreadSafety()
{
LazyTestClass testClass = new LazyTestClass();
// 創(chuàng)建多個(gè)測(cè)試線程
var tasks = Enumerable.Range(1, 10).Select(i => Task.Run(() => testClass.GetList()));
// 等待所有任務(wù)完成
Task.WaitAll(tasks.ToArray());
// 斷言列表的實(shí)例只有一個(gè)
Assert.Single(tasks.Select(t => t.Result));
}
}
在這個(gè)示例中,我們創(chuàng)建了一個(gè) LazyTestClass,它有一個(gè) Lazy<List> 成員。我們?cè)谥骱瘮?shù)中創(chuàng)建了多個(gè) Task 來(lái)并發(fā)地訪問(wèn) GetList 方法,該方法返回 Lazy<List> 的值。在單元測(cè)試中,我們使用 Fact 屬性來(lái)標(biāo)記一個(gè)測(cè)試方法,并使用 Assert.Single 來(lái)斷言只有一個(gè) List 實(shí)例被創(chuàng)建。
7、Lazy 加載在性能和用戶(hù)體驗(yàn)方面的作用
Lazy 加載技術(shù)可以顯著提高程序的性能和用戶(hù)體驗(yàn)。以下是它在不同方面的一些潛在作用:
- 性能提升:通過(guò)延遲加載昂貴的資源,程序可以在不需要這些資源時(shí)避免不必要的開(kāi)銷(xiāo)。這意味著資源只有在真正需要時(shí)才會(huì)被加載,從而減少內(nèi)存和CPU的使用。
- 響應(yīng)性增強(qiáng):在用戶(hù)界面(UI)中使用 Lazy 加載可以避免在初始加載時(shí)延遲UI的響應(yīng)。這對(duì)于創(chuàng)建快速啟動(dòng)的應(yīng)用程序至關(guān)重要。
- 資源優(yōu)化:對(duì)于大型資源,如圖片、視頻和音頻文件,Lazy 加載確保只有在用戶(hù)請(qǐng)求時(shí)才加載它們,這樣可以減少應(yīng)用程序的整體大小和加載時(shí)間。
- 多線程支持:Lazy 加載在多線程環(huán)境中自動(dòng)同步,這意味著不必?fù)?dān)心在多個(gè)線程中共享和初始化資源的問(wèn)題。
8、安全性和效率考慮
盡管 Lazy 加載提供了許多好處,但在使用時(shí)也需要考慮安全和效率:
- 線程安全:Lazy 加載默認(rèn)是線程安全的,但在自定義 Lazy 實(shí)現(xiàn)或使用 LazyThreadSafetyMode.None 時(shí),需要確保線程安全。
- 資源泄漏:如果異步加載的資源沒(méi)有正確管理(例如,沒(méi)有釋放或關(guān)閉流),可能會(huì)導(dǎo)致資源泄漏。
- 性能開(kāi)銷(xiāo):即使是 Lazy 加載,如果初始化過(guò)程很昂貴,或者在短時(shí)間內(nèi)多次調(diào)用 Value 屬性,這可能會(huì)導(dǎo)致性能問(wèn)題。
- 過(guò)度依賴(lài):過(guò)度使用 Lazy 加載可能會(huì)導(dǎo)致代碼難以理解和維護(hù),特別是當(dāng)依賴(lài)關(guān)系變得復(fù)雜時(shí)。
總結(jié)
Lazy< T> 是C#中一個(gè)非常有用的并發(fā)特性,它允許開(kāi)發(fā)者延遲初始化對(duì)象,直到這些對(duì)象真正被需要。通過(guò)正確使用 Lazy< T>,你可以?xún)?yōu)化應(yīng)用程序的性能,減少資源消耗,并提高應(yīng)用程序的響應(yīng)性。
在使用 Lazy< T> 時(shí),你需要仔細(xì)考慮線程安全問(wèn)題,并選擇合適的 LazyThreadSafetyMode。此外,你還需要確保在多個(gè)線程中共享延遲加載的對(duì)象時(shí),你的初始化代碼和發(fā)布代碼都是線程安全的。
以上就是C#使用Lazy實(shí)現(xiàn)延遲加載的方法示例的詳細(xì)內(nèi)容,更多關(guān)于C# Lazy延遲加載的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
UpdateLayeredWindow實(shí)現(xiàn)任意異形窗口使用詳解
這篇文章主要為大家介紹了UpdateLayeredWindow實(shí)現(xiàn)任意異形窗口使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
C#探秘系列(一)——ToDictionary,ToLookup
這個(gè)系列我們看看C#中有哪些我們知道,但是又不知道怎么用,又或者懶得去了解的東西,比如這篇我們要介紹的toDictionary和ToLookup。2014-05-05
C#獲取兩個(gè)數(shù)的最大公約數(shù)和最小公倍數(shù)示例
本文介紹了使用C#獲取兩個(gè)數(shù)的最大公約數(shù)和最小公倍數(shù)的示例,大家參考使用吧2014-01-01
Unity3D實(shí)現(xiàn)飛機(jī)大戰(zhàn)游戲(2)
這篇文章主要為大家詳細(xì)介紹了Unity3D實(shí)現(xiàn)飛機(jī)大戰(zhàn)游戲的第二部分,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06

