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));
		}
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章