程序集加载和反射
一、程序集加载
1,根据程序集名称查找程序集
public class Assembly { public static Assembly Load(AssemblyName assemblyRef); public static Assembly Load(string assemblyString); //未列出不常用的Load重载 }
Assembly.Load("ConsoleApplication2");,
2,根据程序集文件路径名(包含扩展名)查找程序集
public class Assembly { public static Assembly LoadFrom(string path); //未列出不常用的LoadFrom重载 }
Assembly.LoadFrom("ConsoleApplication2.exe");
3,加载程序集时,确保程序集中的任何代码不会执行
public class Assembly { public static Assembly ReflectionOnlyLoadFrom(string assemblyFile); public static Assembly ReflectionOnlyLoad(string assemblyFile); //未列出不常用的ReflectionOnlyLoad重载 }
二、反射的性能
1,反射的两个缺点
①反射造成编译时无法保证类型安全性。由于反射严重依赖字符串,所以会丧失编译时的类型安全性。例如,执行Type.GetType("int");要求通过反射在程序集中查找名为“int”类型,代理会通过编译,但是在运行时会返回null,因为CLR只知“System.Int32”不知道“int”
②反射速度慢。使用反射是,类型及其成员的名称在编译时未知。你需要用字符串名称标识每个类型及其成员,然后再运行时发现他们。也就是说,使用System.Reflection命名空间中的类型扫描程序集的元数据时,反射机制会不停地执行字符串搜索。通常,字符串搜索执行的是不区分大小写的比较,这会进一步影响速度
③用反射调用方法时,首先必须将实参打包成数组。在内部,反射必须将这些实参解包到线程栈上。此外,在调用方法前,CLR必须检查实参具有正确的数据类型。最后,CLR必须确保调用者有正确的安全权限来访问被调用的成员
2,利用一下两种技术之一开发应用程序来动态发现和构造类型实例
①让类型从编译时一直的基类型派生。在运行时构造派生类型的实例,将对它的引用方到基类的变量中(利用转型),再调用基类定义的虚方法
②让类型实现编译时已知的接口。在运行时构造类型的实例,将对它的引用放到接口类型的变量中利用转型(),在调用接口定义的方法
3,发现程序集中定义的类型
namespace ConsoleApplication2 { public class Program { static void Main(string[] args) { //显示将程序集加载到这个AppDomain中 Assembly a = Assembly.Load("ConsoleApplication2"); foreach (var exportedType in a.ExportedTypes) { //显示访问修饰符为public的类型 Console.WriteLine(exportedType.FullName); } Console.ReadKey(); } } }
4,类型对象的准确含义
①System.Type类型提供了静态GetType方法的几个重载版本。所有版本都接受一个String参数。字符串必须指定类型的全名(包括它的命名空间)。找到,则返回对恰当的Type对象的引用。没有找到,则就检查MSCorLib.dll定义类型,如果还是没找到,则为null或抛出System.TyprLoadException。可向GetType传递限定程序集的类型字符创,比如“System.Int32,mscorlib,Version=4.0.0.0,Culture=neutral,PublicKeyToken=677a5c61934e098”,GetType方法会在指定程序集中查找类型(如果有必要会加载程序集)
②System.Type类型提供静态ReflectionOnlyGetType方法。该方法与上一条提到的GetType方法在行为上相似,只是类型会以“仅反射”的方式加载,不能执行
③System.TypeInfo类型提供了实例成员DeclaredNestedTypes和GetDeclaredNestedType
④System.Reflection.Assemble类型提供了实例成员GetType,DefinedTypes和ExportedTypes
var t = Type.GetType("ConsoleApplication2.Program"); Console.WriteLine(t == null ? "未找到" : "找到");
private static void SomeMethod(object o) { //GetType在运行时返回对象的类型(晚期绑定) //typeof返回指定的类型(早起绑定) if (o.GetType() == typeof (FileInfo)) { } }
上述代码的第一个if语句检查变量o是否引用了FileInfo类型的对象;它不检查o是否引用从FileInfo类型派生的对象(使用is/as操作符会检查派生对象)
使用Type对象的GetTypeInfo方法获取对类型的更多控制
5,构造类型的实例
①System.Activator的CreateInstance方法
②System.Activator的CreateInstanceFrom方法
他与CreateInstance的行为相似,只是必须通过字符串来指出类型及其程序集。程序集用Assembly的LoadFrom(而非Load)方法加载到调用AppDomain中。由于都不接受Type参数,所以返回的都是一个ObjectHandle对象引用,必须调用Unwrap方法进行具体化
③System. AppDomain的方法
AppDomain类型提供了4个用于构造类型实例的实例方法(每个都有几个重载版本),包括CreateInstance,CreateInstanceAndUnwrap,CreateInstanceForm和CreateInstanceFromAndUnwrap。这些方法的行为和Activator类的方法相似,区别在于它们都是实例方法,允许指定哪个AppDomain中构造对象。另外,带Unwrap后缀的方法还能简化操作,必须执行额外的方法调用
④System.Reflection.ConstructorInfo的Invoke实例方法
使用一个Type对象引用,可以绑定到一个特定的构造器,并获取对构造器的ConstructorInfo对象的引用。然后,可利用ConstructorInfo对象引用来调用它的Incoke方法。类型总是在调用AppDomain中创建,返回的是对新对象的引用
⑤创建数组
创建数组需要调用Array的静态CreateInstance方法
⑥创建委托
创建委托需要调用MethodInfo的静态CreateDelegate方法
⑦构造泛型类型
static void Main(string[] args) { //获取对泛型类型的类型对象引用 var openType = typeof (Dictionary<,>); //使用TKey=String、TValue=Int32封闭泛型类型 var closedType = openType.MakeGenericType(typeof (string), typeof (Int32)); //构造封闭类型的实例 var o = Activator.CreateInstance(closedType); Console.WriteLine(o.GetType()); Console.ReadKey(); }
三、设置支持加载项的引用程序
1,HostSDK.dll接口设计
public interface IAddIn { string DoSomething(int x); }
2,加载项AddInTypes.dll设计
public class AddIn_A : IAddIn { public string DoSomething(int x) { return "AddIn_A:" + x; } } public class AddIn_B : IAddIn { public string DoSomething(int x) { return "AddIn_B:" + x; } }
3,可扩展应用程序设计
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using HostSDK; namespace Host { class Program { static void Main(string[] args) { //查找宿主EXE文件所在的目录(例如:F:/../../bin/Debug) string AddInDir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); //假定加载项程序集和宿主EXE文件在一个目录(例如:F:/../../bin/Debug/HostSDK.dll) var AddInAssemblies = Directory.EnumerateFiles(AddInDir, "*.dll"); var AddInTypes = from file in AddInAssemblies let assembly = Assembly.LoadFrom(file) from t in assembly.ExportedTypes//公开到处的类型 //如果类型实现了IAddIn接口,该接口就可由宿主使用 where t.IsClass && typeof (IAddIn).GetTypeInfo().IsAssignableFrom(t.GetTypeInfo()) select t; foreach (var addInType in AddInTypes) { IAddIn ai = (IAddIn) Activator.CreateInstance(addInType); Console.WriteLine(ai.DoSomething(5)); } Console.ReadKey(); } } }
四、使用反射发现类型的成员
1,发现类型的成员
static void Main(string[] args) { //遍历这个AppDomain中加载的所有程序集 Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly a in assemblies) { //查找程序集中的类型 foreach (Type t in a.ExportedTypes) { //发现类型的成员 foreach (MemberInfo mi in t.GetTypeInfo().DeclaredMembers) { if (mi is Type)//嵌套类型 if (mi is FieldInfo)//字段 if (mi is MethodInfo)//方法 if (mi is ConstructorInfo)//构造函数 if (mi is PropertyInfo)//属性 if (mi is EventInfo)//事件 } } } }
MemberInfo的所有派生类型都通用的属性和方法
成员名称 |
成员类型 |
说明 |
Name |
一个String属性 |
返回成员名称 |
DeclaringType |
一个Type属性 |
返回声明成员的Type |
Module |
一个Module属性 |
返回声明成员的Module |
CustomAttributes |
该属性返回一个IEnumerable<CustomAttributeData> |
返回一个集合,其中每个元素都标识了应用于该成员的一个定制特性的实例。定制特性可应用于如何成员。虽然Assembly不从MemberInfo派生,但它提供了可用于程序集的相同属性 |
2,调用类型成员
成员类型 |
调用(Invoke)成员而需要调用(call)的方法 |
FieldInfo |
调用GetValue获取字段的值 调用SetValue设置字段的值 |
ConstructorInfo |
调用Invoke构造类型的实例并调用构造器 |
MethodInfo |
调用Invoke来调用类型的方法 |
PropertyInfo |
调用GetValue来调用的属性的get访问器方法 调用SetValue来调用属性的set访问器方法 |
EventInfo |
调用AddEventHandler来调用事件的add访问器方法 调用RemoveEventHandler来调用事件的remove访问器方法 |
BindToMemberThenInvokeTheMember:方法演示了如何绑定到成员并调用它
BindToMemberCreateDelegateToMemberThenInvokeThenMember:方法演示了如何绑定到一个对象或成员,然后创建一个委托来引用对象或成员。通过委托来调用的速度很快。如果需要在相容的对象上多次调用相同的成员,这个技术的性能比上一个好
UseDynamicToBindAndInvokeTheMember:方法演示了如何利用C#的dynamic基元类型简化成员访问语法。此外,在相同类型的不同对象上调用相同成员时,这个技术还能提供不错的性能,因为针对每个类型,绑定都只会发生一次。而且可以缓存起来,以后多次调用的速度回非常快。用这个技术也可以调用不容类型的对象的成员
internal sealed class SomeType { private Int32 m_someField; public SomeType(ref Int32 x){x *= 2;} public override string ToString(){return m_someField.ToString();} public Int32 SomeProp { get { return m_someField;;} set { if(value<1)throw new ArgumentOutOfRangeException("value"); m_someField = value; } } public event EventHandler SomeEvent; public void NoCompilerWarnings(){SomeEvent.ToString();} }
internal static class ReflectionExtensions { //这个辅助扩展方法简化了创建委托的语法 public static TDelegate CreateDelegate<TDelegate>(this MethodInfo mi, object target = null) { return (TDelegate) (object) mi.CreateDelegate(typeof (TDelegate), target); } }
public class Program { static void Main(string[] args) { Type t = typeof (SomeType); BindToMemberThenInvokeTheMember(t); BindToMemberCreateDelegateToMemberThenInvokeThenMember(t); UseDynamicToBindAndInvokeTheMember(t); Console.ReadKey(); } private static void BindToMemberThenInvokeTheMember(Type t) { //构造实例 Type ctorArgument = Type.GetType("System.Int32&");//或者typeof (Int32).MakeByRefType(); ConstructorInfo ctor= t.GetTypeInfo().DeclaredConstructors.First(c => c.GetParameters()[0].ParameterType == ctorArgument); object[] args = new object[] {12};//构造器实参 object obj = ctor.Invoke(args); //读写字段 FieldInfo fi = obj.GetType().GetTypeInfo().GetDeclaredField("m_someField"); fi.SetValue(obj, 33); Console.WriteLine(fi.GetValue(obj)); //调用方法 MethodInfo mi= obj.GetType().GetTypeInfo().GetDeclaredMethod("ToString"); string s = (string) mi.Invoke(obj,null); Console.WriteLine(s); //读写属性 PropertyInfo pi= obj.GetType().GetTypeInfo().GetDeclaredProperty("SomeProp"); try { pi.SetValue(obj,0,null); } catch (TargetInvocationException e) { if (e.InnerException.GetType() != typeof (ArgumentOutOfRangeException)) throw; } pi.SetValue(obj,2); Console.WriteLine(pi.GetValue(obj)); //为事件添加和删除方法 EventInfo ei = obj.GetType().GetTypeInfo().GetDeclaredEvent("SomeEvent"); EventHandler eh = new EventHandler(EventCallback); ei.AddEventHandler(obj, eh); ei.RemoveEventHandler(obj, eh); } private static void BindToMemberCreateDelegateToMemberThenInvokeThenMember(Type t) { //构造实例(不能创建对构造器的委托) object[] args = new object[] {12}; object obj = Activator.CreateInstance(t, args); //注意不能创建对字段的委托 //调用方法 MethodInfo mi= obj.GetType().GetTypeInfo().GetDeclaredMethod("ToString"); var toString = mi.CreateDelegate<Func<string>>(obj); //读写属性 PropertyInfo pi = obj.GetType().GetTypeInfo().GetDeclaredProperty("SomeProp"); var setSomeProp = pi.SetMethod.CreateDelegate<Action<Int32>>(obj); setSomeProp(2); var getSomeProp = pi.GetMethod.CreateDelegate<Func<Int32>>(obj); Console.WriteLine(getSomeProp()); //向事件增删委托 EventInfo ei = obj.GetType().GetTypeInfo().GetDeclaredEvent("SomeEvent"); var addSomeEvent= ei.AddMethod.CreateDelegate<Action<EventHandler>>(obj); addSomeEvent(EventCallback); var removeSomeEvent = ei.RemoveMethod.CreateDelegate<Action<EventHandler>>(obj); removeSomeEvent(EventCallback); } private static void UseDynamicToBindAndInvokeTheMember(Type t) { //构造实例(不能创建对构造器的委托) object[] args = new object[] {12}; dynamic obj = Activator.CreateInstance(t, args); try { //读写字段 obj.m_someField = 5; Int32 v = (Int32) obj.m_someField; } catch (RuntimeBinderException e) { //之所以会执行到这里,是因为字段是私有的 } //调用方法 String s = obj.ToString(); Console.WriteLine(s); //读写属性 obj.SomeProp = 2; Console.WriteLine(obj.SomeProp); //从事件增删委托 obj.SomeEvent += new EventHandler(EventCallback); obj.SomeEvent -= new EventHandler(EventCallback); } //添加到事件回调方法 private static void EventCallback(object sender, EventArgs e){} }
成员类型调用(Invoke)成员而需要调用(call)的方法FieldInfo调用GetValue获取字段的值调用SetValue设置字段的值ConstructorInfo调用Invoke构造类型的实例并调用构造器MethodInfo调用Invoke来调用类型的方法PropertyInfo调用GetValue来调用的属性的get访问器方法调用SetValue来调用属性的set访问器方法EventInfo调用AddEventHandler来调用事件的add访问器方法调用RemoveEventHandler来调用事件的remove访问器方法
3,使用绑定句柄减少进程的内存消耗
许多应用程序都绑定了一组类型(Type对象)或者类型成员(MemberInfo派生对象),并将这些类型对象保存在某种形式的集合中。Type和MemberInfo派生会对象需要大量内存。
FCL定义了RuntimeTypeHandle、RuntimeFieldHandle、RuntimeMethodHandle三个运行时句柄类型,这些类型都只包含一个IntPtr,这使类型的实例显得相当精简(相当省内存)
①将Type装换为RuntimeTypeHandle:调用Type的静态GetTypeHandle方法并传递那个Type对象引用
②将RuntimeTypeHandle转换为Type对象:调用Type的静态方法GetTypeFromHandle,并传递那个RuntimeTypeHandle
③将FieldInfo对象转换为一个RuntimeFieldHandle:查询FieldInfo的实例只读属性FieldHandle
④将RuntimeFieldHandle转换为FieldInfo对象:调用FieldInfo的静态方法GetFieldFromHandle
⑤将MethodInfo对象转换为一个RuntimeMethodHandle:查询MethodInfo的实例只读属性MethodHandle
⑥将RuntimeMethodHandle转换MethodInfo对象:调用MethodInfo的静态方法GetMethodFromHandle
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication1 { class Program { private const BindingFlags c_bf = BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; static void Main(string[] args) { Show("显示在任何反射操作之前堆的大小"); //为 MSCorlib.dll中的所有方法构建MethodInfo对象缓存 List<MethodBase> methodInfos = new List<MethodBase>(); foreach (Type t in typeof(Object).Assembly.GetExportedTypes()) { //跳过任何泛型类型 if(t.IsGenericTypeDefinition)continue; MethodBase[] bm = t.GetMethods(c_bf); methodInfos.AddRange(bm); } //显示当绑定所有方法之后,方法的个数和堆的大小 Console.WriteLine("methodInfos集合数量={0:N0}", methodInfos.Count); Show("构建methodInfos之后"); //为所有MethodInfo对象构建RuntimeMethodHandle缓存 List<RuntimeMethodHandle> methodHandles = methodInfos.ConvertAll<RuntimeMethodHandle>(mb => mb.MethodHandle); Show("构建methodHandles之后"); GC.KeepAlive(methodInfos); //阻止缓存被过早的垃圾回收 methodInfos = null; //现在允许缓存垃圾回收 Show("构建methodHandles之后,并且methodInfos = null"); methodInfos = methodHandles.ConvertAll<MethodBase>(rmh => MethodInfo.GetMethodFromHandle(rmh)); Show("重新构建methodInfos之后"); GC.KeepAlive(methodHandles);//阻止缓存被过早的垃圾回收 GC.KeepAlive(methodInfos);//阻止缓存被过早的垃圾回收 methodHandles = null;//现在允许缓存垃圾回收 methodInfos = null; //现在允许缓存垃圾回收 Show("最后"); Console.ReadKey(); } private static void Show(string s) { //GC.GetTotalMemory(true)返回之前等待垃圾回收 Console.WriteLine(" Heap size={0,12:N0} - {1}",GC.GetTotalMemory(true),s); } } }