CLR via C#:程序集加載和反射

程序集加載:具有以下特性:
1.Assembly.Load:在大多數動態可擴展應用程序中,該函數是將程序集加載到AppDomain的首選方式。
2.AssemblyName.GetAssemblyName:該函數打開指定的文件,找到AssemblyRef元數據表的記錄項,提取程序集標識信息,然後以AssemblyName對象的形式返回這些信息。
3.Assembly.LoadFrom:該函數用來加載指定路徑名的程序集。流程如下所示:
1>.調用AssemblyName.GetAssemblyName函數來獲取具有程序集標識信息的AssemblyName對象。
2>.調用Assembly.Load函數,將AssemblyName對象傳遞給該函數。
3>.如果已加載具有相同標識的程序集的話,就直接返回該程序集;否則就應用版本綁定重定向策略,並在各個位置查找匹配的程序集。
4>.如果查找到匹配的程序集的話,就會加載該程序集;否則就加載LoadFrom實參指定的程序集。
4.Assembly.LoadFile:該函數可以從任意路徑加載程序集,而且可以將具有相同標識的程序集多次加載到一個AppDomain中。
5.Assembly.ReflectionOnlyLoad / Assembly.ReflectionOnlyLoadFrom:該函數在加載程序集時,CLR禁止程序集中的任何代碼執行;主要用來分析程序集的元數據。
6.將依賴的DLL嵌入到程序集中時會增大運行時內存消耗,但是在執行依賴DLL中的代碼時就可以正常運行。
7.爲了解決在運行時找不到依賴的DLL問題,可以向AppDomain的ResolveAssembly事件登記回調函數,然後在該函數內部通過調用Assembly.Load或者Assembly.LoadFrom函數來加載依賴的DLL;也可以向AppDomain的ReflectionOnlyAssemblyResolve事件登記回調函數,然後在該函數內部通過調用Assembly.ReflectionOnlyLoad或者Assembly.ReflectionOnlyLoadFrom函數來加載依賴的DLL。參考代碼如下所示:

private static Assembly ResolveEventHandle(Object sender, ResolveEventArgs args)
{
	String dllName = new AssemblyName(args.Name).Name + ".dll";
	
	var assem = Assembly.GetExecutingAssembly();
	String resName = assem.GetManifestResourceNames().FirstOrDefault(rn => rn.EndsWith(dllName));

	// Not found, maybe another handler will find it
	if (resName == null)
	{
		return null;
	}

	using (var stream = assem.GetManifestResourceStream(resName))
	{
		byte[] assemblyData = new byte[stream.Length];
		stream.Read(assemblyData, 0, assemblyData.Length);
		return Assembly.Load(assemblyData);
	}
}

反射:反射是十分強大的機制,它允許在運行時查找類型以及使用該類型的成員。具有以下特性:
1.反射造成編譯時無法保證類型的安全性。
2.反射查找類型時會掃描程序集元數據並不斷執行字符串搜索操作,從而造成速度慢。
3.反射使用類型成員時先會檢查實參是否具有正確的類型以及調用者是否具有足夠的權限;然後將實參打包成數組並在內部解包到線程棧上,從而造成速度慢。可以讓類型從編譯時已知的基類派生或者讓類型實現編譯時已知的接口來解決該問題。

類型引用對象:既是Type對象。具有以下特性:
1.在一個AppDomain中,每個類型只有一個類型引用對象。
2.Object對象.GetType函數可以用來獲取當前對象的類型引用對象。
3.Type.GetType函數用來獲取指定類型字符串的類型引用對象。執行流程如下所示:
1>.當參數只是一個類型字符串時,如果調用程序集有定義指定的類型,就返回該類型引用對象;否則如果MSCorLib.dll有定義指定的類型,就返回該類型引用對象;否則就返回null或者拋出TypeLoadException。
2>.當參數是含有程序集的類型字符串時,如果指定程序集有定義指定的類型,就返回該類型引用對象;否則就返回null或者拋出TypeLoadException。
4.Type.ReflectionOnlyGetType函數的執行行爲類似與Type.GetType函數的執行行爲,只是該函數只能以反射的方式加載,不能執行。
5.TypeInfo對象.AsType函數用來獲取類型引用對象。
6.Assembly.GetType和DefinedTypes以及ExportedTypes函數來獲取類型引用對象。
7.typeof操作符是效率最高的獲取類型引用對象。
8.Type對象.MakeByRefType函數用來獲取"類型&對象"。參考代碼如下所示:

typeof("Int32").MakeByRefType();
// 等價於
Type.GetType("System.Int32&");
// 等價於
ref Int32 變量名

類型對象 :就是將類型引用對象具體化,從而獲取一個新的類型對象。具有以下特性:
1.Activator.CreateInstance函數執行流程如下所示:
1>.當參數是類型引用對象時,該函數會以不調用構造函數的形式返回一個新的類型對象。
2>.當參數是程序集和類型的字符串時,該函數會返回一個跨AppDomain通信的ObjectHandle對象。當調用ObjectHandle對象的Unwarp函數具體化時,CLR會先將程序集加載到AppDomain中;然後創建代理類型對象(類型對象按引用封送)或者副本對象(類型對象按值封送)。
2.Activator.CreateInstanceFrom函數的執行行爲類似與Activator.CreateInstance函數的執行行爲,但是該函數只接收程序集和類型的字符串參數。
3.AppDomain對象.CreateInstance,CreateInstanceAndUnwrap,CreateInstanceFrom和CreateInstanceFromAndUnwrap函數的執行行爲類似與Activator類的相關函數的執行行爲,區別在於這些函數都是實例函數且在指定的AppDomain中構造類型對象。
4.類型引用對象綁定到一個特定的構造器;然後獲取ConstructorInfo對象並調用Invoke函數來獲取一個新的類型對象。
5.Array.CreateInstance函數用來創建數組類型對象。
6.MethodInfo.CreateDelegate函數用來創建委託類型對象。
7.泛型類型引用對象.MakeGenericType函數用來獲取泛型類型對象。

類型成員對象:既是MemberInfo派生類型對象。具有以下特性:
1.類型成員的反射層次結構如下圖所示:在這裏插入圖片描述
其中TypeInfo用來表示嵌套類型成員,FieldInfo表示字段成員,ConstructorInfo表示構造函數成員,MethodInfo表示函數成員,PropertyInfo表示屬性成員,EventInfo表示事件成員。
2.類型成員通用的屬性和函數如下表所示:

成員名稱 成員類型 說明
Name String 成員名稱
DeclaringType Type 成員類型
Module Module 成員模塊
CustomAttributes IEnumerable 應用到成員身上的定製特性實例集合

3.調用類型成員而需調用的函數如下表所示:

成員類型 調用函數
FieldInfo 調用GetValue獲取字段值;調用SetValue設置字段值
ConstructorInfo 調用Invoke構造類型的實例並調用構造函數
MethodInfo 調用Invoke來調用類型的函數
PropertyInfo 調用GetValue來調用屬性的get訪問器函數;調用SetValue來調用屬性的set訪問器函數
EventInfo 調用AddEventHandler來調用事件的add訪問器函數;調用RemoveEventHandler來調用屬性的remove訪問器函數

4.ConstructorInfo成員,MethodInfo成員,PropertyInfo成員,EventInfo成員,不僅可以調用GetParameters函數來獲取ParameterInfo對象數組,從而瞭解這些類型成員的參數信息;還可以調用ReturnParameter函數來獲取一個ParameterInfo對象,從而瞭解這些類型成員的返回信息。
5.在相同的對象上多次調用相同的成員時,可以使用委託來訪問成員,從而提高性能。
6.在相同的類型不同的對象上調用相同成員時,可以使用dynamic基元類型來簡化成員的訪問,從而提高性能。

類型信息對象:既是TypeInfo對象。具有以下特性:
1.Type對象.GetTypeInfo函數用來獲取類型信息對象。
2.Assembly,AssemblyQualifiedName,FullName和Module等屬性返回定義該類型的程序集或模塊的名稱以及類型的全名。
3.BaseType屬性用來獲取對類型的基類型的引用。
4.IsPublic,IsSealed,IsAbstract,IsClass和IsValueType等屬性指明瞭與類型關聯的標誌。
5.DeclaredMembers屬性用來獲取類型的所有MemberInfo成員。
6.DeclaredNestedTypes屬性用來獲取所有的TypeInfo成員。
7.GetDeclaredNestedType函數用來獲取指定名稱的TypeInfo成員。
8.DeclaredFields屬性用來獲取所有的FieldInfo成員。
9.GetDeclaredField函數用來獲取指定名稱的FieldInfo成員。
10.DeclaredConstructors屬性用來獲取所有的ConstructorInfo成員。
11.DeclaredMethods屬性用來獲取所有的MethodInfo成員。
12.GetDeclaredMethods函數用來獲取指定名稱的MethodInfo成員列表。
13.GetDeclaredMethod函數用來獲取指定名稱的MethodInfo成員。
14.DeclaredProperties屬性用來獲取所有的PropertyInfo成員。
15.GetDeclaredProperty函數用來獲取指定名稱的PropertyInfo成員。
16.DeclaredEvents屬性用來獲取所有的EventInfo成員。
17.GetDeclaredEvent函數用來獲取指定名稱的EventInfo成員。
18.IsAssignableFrom函數用來判定指定參數是否可以分配給當前的類型。

使用綁定句柄:用來減少進程中緩存相關對象的內存。具有以下特性:
1.緩存Type對象佔用的內存要大於緩存RuntimeTypeHandle對象。兩者互相轉換函數如下所示:
1>.Type.GetTypeHandle函數用來將指定Type對象轉換成RuntimeTypeHandle對象。
2>.Type.GetTypeFromHandle函數用來將指定的RuntimeTypeHandle對象轉換成Type對象。
2.緩存FieldInfo對象佔用的內存要大於緩存RuntimeFieldHandle對象。兩者互相轉換函數如下所示:
1>.FieldInfo對象.FieldHandle函數用來將指定的FieldInfo對象轉換成RuntimeFieldHandle對象。
2>.FiledInfo.GetFieldFromHandle函數用來將指定的RuntimeFieldHandle對象轉換成FieldInfo對象。
3.緩存MethodInfo對象佔用的內存要大於緩存RuntimeMethodHandle對象。兩者互相轉換函數如下所示:
1>.MethodInfo對象.MethodHandle函數用來將指定的MethodInfo對象轉換成RuntimeMethodHandle對象。
2>.MethodInfo.GetMethodFromHandle函數用來將指定的RuntimeMethodHandle對象轉換成MethodInfo對象。

設計支持加載項的應用程序:如下所示:
設計流程如下所示
1.創建宿主SDK程序集。該程序集內部定義了一個宿主應用程序與加載項之間進行通信的接口類型。
2.創建加載項程序集。該程序集將實現宿主SDK程序集中定義的接口類型。
3.創建宿主應用程序程序集。該程序集將使用宿主SDK程序集中定義的接口類型。
參考代碼如下所示

// 宿主SDK程序集的代碼如下所示:
using System

namespace HostSDK
{
	public interface IAddIn
	{
		String DoSomething(Int32 x);
	}
}

// 加載項程序集代碼如下所示:
using System;
using HostSDK;

public sealed class AddIn_A : IAddIn
{
	public AddIn_A() {}

	public String DoSomething(Int32 x)
	{
		return "AddIn_A: " + x.ToString();
	}
}

public sealed class AddIn_B : IAddIn
{
	public AddIn_B() {}

	public String DoSomething(Int32 x)
	{
		return "AddIn_B: " + x.ToString();
	}
}

// 宿主應用程序程序集代碼如下所示:
using System;
using System.IO;
using System.Reflection;
using System.Collections.Generic;
using HostSDK;

public static class Program
{
	public static void Main()
	{
		// 查找宿主應用程序程序集所在的目錄
		String AddInDir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
		// 獲取加載項程序集列表:假定加載項程序集跟宿主應用程序程序集在同一目錄
		var AddInAssemblies = Directory.EnumerateFiles(AddInDir, "*.dll");
		// 創建IAddIn接口派生的類型引用對象集合
		var AddInTypes = 
		from file in AddInAssemblies 
		let assembly = Assembly.Load(file) // 加載指定加載項程序集
		from t in assembly.ExportedTypes // 遍歷加載項程序集的公開導出類型
		where t.IsClass && typeof(IAddIn).GetTypeInfo().IsAssignableFrom(t.GetTypeInfo()) // 是否爲IAdInfo接口派生的類型
		select t;

		foreach (Type t in AddInTypes)
		{
			// 創建指定類型引用對象的類型對象,並賦值給接口對象,從而實現多態
			IAddIn ai = (IAddIn)Activator.CreateInstance(t);
			Console.WriteLine(ai.DoSomething(666));
		}
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章