1. 什么是Assembly?
在C#中,Assembly是.NET框架的一個(gè)基本構(gòu)建模塊。它可以被看作是一個(gè)包含代碼和資源的可部署單元,通常以DLL或EXE文件的形式存在。Assembly承載了以下幾個(gè)關(guān)鍵特性:
- 代碼封裝:Assembly將相關(guān)的代碼和資源進(jìn)行封裝,是代碼邏輯和資源的集合。
- 版本控制:每個(gè)Assembly都有一個(gè)版本號(hào),這對(duì)于管理應(yīng)用程序的不同版本非常重要。
- 安全性:Assembly包含安全身份信息,例如強(qiáng)名稱簽名,可以確保代碼的完整性和來(lái)源。
- 類型信息:Assembly包含元數(shù)據(jù),描述了其內(nèi)部類型和成員,可以被其他代碼使用。
- 可再分發(fā)性:通過(guò)將功能模塊化,Assembly可以在不同應(yīng)用程序之間共享和重用。
- 依賴管理:Assembly提供了依賴關(guān)系的管理,確保應(yīng)用程序能夠正確加載和使用所需的組件。
Assembly分為兩種類型:
- 私有Assembly:僅供單個(gè)應(yīng)用程序使用,通常存放在應(yīng)用程序的目錄中。
- 共享Assembly:可以被多個(gè)應(yīng)用程序使用,通常存放在全局程序集緩存(GAC)中。
GAC是什么?
GAC,全稱為全局程序集緩存(Global Assembly Cache),是.NET框架提供的一個(gè)用于存儲(chǔ)共享Assembly的特殊文件夾。GAC的主要作用是允許多個(gè)應(yīng)用程序共享使用公共的Assembly,實(shí)現(xiàn)代碼重用和版本管理。以下是GAC的一些重要特點(diǎn):
- 共享使用:Assembly存放在GAC中后,可以被多個(gè)應(yīng)用程序引用和使用,避免了重復(fù)存儲(chǔ)和部署。
- 版本控制:GAC支持不同版本的同一個(gè)Assembly共存,這使得應(yīng)用程序可以使用不同版本的組件而不產(chǎn)生沖突。
- 安全性:只有具有強(qiáng)名稱簽名的Assembly才能存放在GAC中,強(qiáng)名稱簽名確保了Assembly的唯一性和完整性。
- 管理工具:可以使用命令行工具如
gacutil
來(lái)安裝或卸載GAC中的Assembly。
2. 使用場(chǎng)景是什么?
Assembly在C#和.NET開發(fā)中有多種使用場(chǎng)景,包括:
- 模塊化開發(fā):將應(yīng)用程序分解為多個(gè)功能模塊,每個(gè)模塊作為一個(gè)獨(dú)立的Assembly開發(fā)和維護(hù)。
- 代碼重用:將通用功能封裝成Assembly,以便在不同項(xiàng)目中共享和重用。
- 插件架構(gòu):使用Assembly實(shí)現(xiàn)插件系統(tǒng),允許動(dòng)態(tài)加載和執(zhí)行外部組件。
- 版本管理:通過(guò)Assembly的版本控制機(jī)制,支持應(yīng)用程序的平滑升級(jí)和不同版本共存。
- 分布式應(yīng)用:在分布式系統(tǒng)中,將不同服務(wù)或組件打包為Assembly,便于部署和管理。
- 安全性要求:使用強(qiáng)名稱簽名的Assembly,確保代碼的完整性和來(lái)源可信。
- 跨語(yǔ)言互操作:通過(guò)Assembly提供的元數(shù)據(jù)支持,允許不同.NET語(yǔ)言之間的互操作。
3. Assembly和AppDomain有什么區(qū)別?
在C#和.NET中,Assembly和AppDomain是兩個(gè)不同的概念,各自承擔(dān)不同的角色:
Assembly
基本構(gòu)建單元:Assembly是.NET應(yīng)用程序的基本構(gòu)建模塊,包含代碼和資源,通常以DLL或EXE文件形式存在。
模塊化和重用:Assembly用于模塊化開發(fā)和代碼重用,可以被多個(gè)應(yīng)用程序共享。
版本和安全:支持版本管理和強(qiáng)名稱簽名,確保代碼的完整性和來(lái)源可信。
類型信息:包含元數(shù)據(jù),描述類型和成員信息,支持反射。
AppDomain
應(yīng)用程序隔離:AppDomain是.NET中用于隔離應(yīng)用程序的執(zhí)行環(huán)境,提供了一個(gè)輕量級(jí)的進(jìn)程內(nèi)隔離機(jī)制。
安全和穩(wěn)定:在不同AppDomain中運(yùn)行的代碼是相互隔離的,防止錯(cuò)誤和崩潰的傳播,提高應(yīng)用程序的穩(wěn)定性和安全性。
動(dòng)態(tài)加載和卸載:允許在運(yùn)行時(shí)動(dòng)態(tài)加載和卸載Assembly,不需要重啟整個(gè)應(yīng)用程序。
跨域通信:AppDomain之間可以通過(guò)序列化和遠(yuǎn)程處理進(jìn)行通信。
區(qū)別
- 作用域:Assembly是代碼和資源的物理單位,而AppDomain是邏輯的執(zhí)行環(huán)境。
- 用途:Assembly用于模塊化和重用,AppDomain用于隔離和管理執(zhí)行。
- 隔離性:AppDomain提供代碼執(zhí)行的隔離,而Assembly在加載后共享到AppDomain中。
4. Assembly.Load和AppDomain.Load有什么區(qū)別?
System.AppDomain 提供了 Load方法。和Assembly 的靜態(tài)Load 方法不同,AppDomaim的Load是實(shí)例方法,它允許將程序集加載到指定的AppDomain 中。該方法設(shè)計(jì)由非托管代碼調(diào)用,允許宿主將程序集“注入”特定 AppDomain 中。托管代碼的開發(fā)人員一般情況下不應(yīng)調(diào)用它,因?yàn)檎{(diào)用 AppDomaim 的Load 方法時(shí)需要傳遞一個(gè)標(biāo)識(shí)了程序集的字符串。該方法隨后會(huì)應(yīng)用策略,并在一些常規(guī)位置搜索程序集。我們知道,AppDomain 關(guān)聯(lián)了一些告訴 CLR如何查找程序集的設(shè)置。為了加載這個(gè)程序集,CLR 將使用與指定AppDomain 關(guān)聯(lián)的設(shè)置,而非與發(fā)出調(diào)用之AppDomain 關(guān)聯(lián)的設(shè)置。但AppDomain 的 Load 方法會(huì)返回對(duì)程序集的引用。由于System.Assembly類不是從System.MarshalByRefObject派生的,所以程序集對(duì)象必須按值封送回發(fā)出調(diào)用的那個(gè)AppDomain。但是,現(xiàn)在CLR就會(huì)用發(fā)出調(diào)用的那個(gè) AppDomain 的設(shè)置來(lái)定位并加載程序集。如果使用發(fā)出調(diào)用的那個(gè) AppDomain 的策略和搜索位置找不到指定的程序集,就會(huì)拋出一個(gè) FileNotFoundException。這個(gè)行為一般不是你所期望的,所以應(yīng)該避免使用 AppDomain 的 Load 方法。
一臺(tái)機(jī)器可能同時(shí)存在具有相同標(biāo)識(shí)的多個(gè)程序集。由于重要提示LoadFrom會(huì)在內(nèi)部調(diào)用 Load,所以CLR有可能不是加載你指定的文件而是加載一個(gè)不同的文件,從而造成非預(yù)期的行為。強(qiáng)烈建議每次生成程序集時(shí)都更改版本號(hào),確保每個(gè)版本都有自己的唯一性標(biāo)識(shí),確保LoadFrom方法的行為符合預(yù)期。除此之外Assembly.Load
和AppDomain.Load
用于加載程序集,但它們的使用場(chǎng)景和行為有所不同:
Assembly.Load
- 作用域:在當(dāng)前應(yīng)用程序域(AppDomain)中加載程序集。
- 用法:通常用于在運(yùn)行時(shí)加載程序集,適用于大多數(shù)動(dòng)態(tài)加載需求。
- 返回值:返回一個(gè)
Assembly
對(duì)象,表示已加載的程序集的引用。 - 限制:無(wú)法跨應(yīng)用程序域加載程序集,僅限于當(dāng)前AppDomain。
AppDomain.Load
- 作用域:可以在指定的應(yīng)用程序域中加載程序集。
- 用法:常用于需要在特定AppDomain中加載程序集的場(chǎng)景。
- 返回值:同樣返回一個(gè)
Assembly
對(duì)象,但是在指定的AppDomain中加載。 - 跨域加載:允許在不同的AppDomain中加載程序集,實(shí)現(xiàn)更好的隔離。
區(qū)別
- 加載位置:
Assembly.Load
在當(dāng)前AppDomain加載,而AppDomain.Load
可以指定AppDomain。 - 隔離性:
AppDomain.Load
提供了更好的隔離,可以在不同的應(yīng)用程序域中加載程序集。 - 使用場(chǎng)景:
Assembly.Load
適用于簡(jiǎn)單的動(dòng)態(tài)加載,AppDomain.Load
適用于需要隔離和管理的復(fù)雜場(chǎng)景。
什么是System.MarshalByRefObject對(duì)象?
System.MarshalByRefObject
是 .NET 框架中的一個(gè)基類,允許對(duì)象通過(guò)引用在應(yīng)用程序域(AppDomain)之間進(jìn)行通信。它的主要作用是在跨域場(chǎng)景中支持對(duì)象的遠(yuǎn)程訪問(wèn)。
關(guān)鍵點(diǎn):
- .NET 中的應(yīng)用程序域類似于輕量級(jí)的進(jìn)程,用于隔離應(yīng)用程序。
- 每個(gè)應(yīng)用程序域都有自己的內(nèi)存空間和資源,防止不同域之間的直接訪問(wèn)。
- 默認(rèn)情況下,對(duì)象在不同的應(yīng)用程序域之間傳遞時(shí)是通過(guò)序列化(Marshal-by-Value)進(jìn)行的。
- 繼承自
MarshalByRefObject
的對(duì)象,可以通過(guò)引用進(jìn)行傳遞,這意味著對(duì)象本身并不會(huì)被復(fù)制到目標(biāo)域,而是通過(guò)代理進(jìn)行訪問(wèn)。
- 適用于需要在不同應(yīng)用程序域或不同計(jì)算機(jī)之間進(jìn)行通信的場(chǎng)景。
- 典型應(yīng)用包括遠(yuǎn)程方法調(diào)用(Remoting)。
- 繼承
MarshalByRefObject
的對(duì)象通常會(huì)有一個(gè)有限的生命周期,由遠(yuǎn)程調(diào)用的服務(wù)端來(lái)管理。 - 可以通過(guò)覆蓋
InitializeLifetimeService
方法來(lái)控制對(duì)象的生存時(shí)間。
使用場(chǎng)景:
- 在分布式系統(tǒng)中,需要跨域或者跨進(jìn)程進(jìn)行通信時(shí)。
- 需要通過(guò)遠(yuǎn)程方法調(diào)用訪問(wèn)對(duì)象時(shí)。
5. CLR為什么不提供卸載?
CLR不提供卸載單獨(dú)程序集的能力。如果 CLR 允許這樣做,那么一旦線程從某個(gè)方法返回至已卸載的一個(gè)程序集中的代碼,應(yīng)用程序就會(huì)崩潰。健壯性和安全性是CLR最優(yōu)先考慮的目標(biāo),如果允許應(yīng)用程序以這樣的一種方式崩潰,就和它的設(shè)計(jì)初衷背道而馳了。卸載程序集必須卸載包含它的整個(gè)AppDomain。使用 ReflectionOnlyLoadFrom或ReflectionOnlyLoad 方法加載的程序集表面上是可以卸載的。畢竟,這些程序集中的代碼是不允許執(zhí)行的。但CLR 一樣不允許卸載用這兩個(gè)方法加載的程序集。因?yàn)橛眠@兩個(gè)方法加載了程序集之后,仍然可以利用反射來(lái)創(chuàng)建對(duì)象,以便引用這些程序集中定義的元數(shù)據(jù)。如果卸載程序集,就必須通過(guò)某種方式使這些對(duì)象失效。無(wú)論是實(shí)現(xiàn)的復(fù)雜性,還是執(zhí)行速度,跟蹤這些對(duì)象的狀態(tài)都是得不償失的。
總結(jié):
不提供直接卸載單個(gè)程序集的功能,主要有以下幾個(gè)原因:
- 內(nèi)存管理復(fù)雜性:卸載單個(gè)程序集會(huì)增加內(nèi)存管理的復(fù)雜性,可能導(dǎo)致內(nèi)存泄漏或其他資源管理問(wèn)題。
- 依賴關(guān)系:程序集之間可能存在復(fù)雜的依賴關(guān)系,卸載一個(gè)程序集可能會(huì)影響其他程序集的正常運(yùn)行。
- 應(yīng)用程序穩(wěn)定性:為了確保應(yīng)用程序的穩(wěn)定性和一致性,CLR選擇不支持單個(gè)程序集的卸載。
- 替代方案:CLR提供了應(yīng)用程序域(AppDomain)作為隔離和管理程序集的機(jī)制??梢孕遁d整個(gè)AppDomain,從而釋放相關(guān)的程序集和資源。
6. 反射的性能
太多文章講解反射的好處和使用這里就不說(shuō)了直接來(lái)看缺點(diǎn)是什么,原因有哪些。
缺點(diǎn):
- 反射造成編譯時(shí)無(wú)法保證類型安全性。由于反射嚴(yán)重依賴字符串,所以會(huì)喪失編譯時(shí)類型安全性。例如,執(zhí)行 Type.GetType(“int”);要求通過(guò)反射在程序集中查找名為“int”的類型,代碼會(huì)通過(guò)編譯,但在運(yùn)行時(shí)會(huì)返回null,因?yàn)镃LR只知道System.Int32不知int。
- 反射速度慢。使用反射時(shí),類型及其成員的名稱在編譯時(shí)未知;你要用字符電名稱標(biāo)識(shí)每個(gè)類型及其成員,然后在運(yùn)行時(shí)發(fā)現(xiàn)它們。也就是說(shuō),使用System.Reflection命名空間中的類型掃描程序集的元數(shù)據(jù)時(shí),反射機(jī)制會(huì)不停地執(zhí)行字符串搜索。通常,字符串搜索執(zhí)行的是不區(qū)分大小寫的比較,這會(huì)進(jìn)一步影響速度。
使用反射調(diào)用成員也會(huì)影響性能。用反射調(diào)用方法時(shí),首先必須將實(shí)參打包(pack)成數(shù)組:在內(nèi)部,反射必須將這些實(shí)參解包(unpack)到線程棧上。此外,在調(diào)用方法前,CLR 必須實(shí)參具有正確的數(shù)據(jù)類型。最后,CLR必須確保調(diào)用者有正確的安全權(quán)限來(lái)訪問(wèn)被調(diào)用的成員。好上述所有原因,最好避免利用反射來(lái)訪問(wèn)字段或調(diào)用方法/屬性。應(yīng)該利用以下兩種技權(quán)一開發(fā)應(yīng)用程序來(lái)動(dòng)態(tài)發(fā)現(xiàn)和構(gòu)造類型實(shí)例。
- 讓類型從編譯時(shí)已知的基類型派生。在運(yùn)行時(shí)構(gòu)造派生類型的實(shí)例,將對(duì)它的引用放到基類型的變量中(利用轉(zhuǎn)型),再調(diào)用基類型定義的虛方法。
- 讓類型實(shí)現(xiàn)編譯時(shí)已知的接口。在運(yùn)行時(shí)構(gòu)造類型的實(shí)例,將對(duì)它的引用放到接口類型的變量中(利用轉(zhuǎn)型),再調(diào)用接口定義的方法。
示例代碼:
using System.Diagnostics;
using System.Reflection;
namespace AssemblyDemo;
class Program
{
static void Main(string[] args)
{
// 創(chuàng)建測(cè)試對(duì)象
var testObject = new TestClass();
// 測(cè)試直接調(diào)用
Stopwatch directStopwatch = Stopwatch.StartNew();
for (int i = 0; i < 1000000; i++)
{
testObject.SimpleMethod();
}
directStopwatch.Stop();
Console.WriteLine($"直接調(diào)用時(shí)間: {directStopwatch.ElapsedMilliseconds} ms");
// 獲取方法信息
MethodInfo methodInfo = typeof(TestClass).GetMethod("SimpleMethod");
// 測(cè)試反射調(diào)用
Stopwatch reflectionStopwatch = Stopwatch.StartNew();
for (int i = 0; i < 1000000; i++)
{
methodInfo.Invoke(testObject, null);
}
reflectionStopwatch.Stop();
Console.WriteLine($"反射調(diào)用時(shí)間: {reflectionStopwatch.ElapsedMilliseconds} ms");
}
}
class TestClass
{
private int _counter = 0;
public void SimpleMethod()
{
// 增加計(jì)數(shù)器
_counter++;
}
}
運(yùn)行結(jié)果:
直接調(diào)用時(shí)間: 1 ms
反射調(diào)用時(shí)間: 10 ms
該文章在 2024/9/18 12:01:20 編輯過(guò)