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