什么是反射?
反射是一个程序集发现及运行的过程,通过反射可以得到*.exe或*.dll等程序集内部的信息。使用反射可以看到一个程序集内部的接口、类、方法、字段、属性、特性等等信息。
在System.Reflection命名空间内包含多个反射常用的类,下面表格列出了常用的几个类。
反射是一种机制,通过这种机制我们可以知道一个未知类型的类型信息比如,有一个对象a,这个对象不是我们定义的,也许是通过网络捕捉到的,也许是使用泛型定义的,但我们想知道这个对象的类型信息,想知道这个对象有哪些方法或者属性什么的.甚至我们想进一步调用这个对象的方法.关键是现在我们只知道它是一个对象,不知道它的类型,自然不会知道它有哪些方法等信息.
这时我们该怎么办?反射机制就是解决这么一个问题的.通过反射机制我们可以知道未知类型对象的类型信息.
再比如,我们有一个dll文件,我们想调用里面的类.现在假设这个dll文件的类的定义,数量等不是固定的,是经常变化的.也许某一天你要在这个dll里面增加一个类定义.也许你觉得这没什么问题,现在关键是我们在另一个程序集里面要调用这个dll,这是我们的程序必须能够适应这个dll的变化,也就是说即使改变了dll文件的定义也不需要改变我们的程序集.这时候我们就会使用一个未知dll.我们该怎么办?同样,反射机制帮助了我们,我们可以通过反射来实现.
下面我们来举一个例子.例子的思路是这样的:我们有一个dll.该dll里面有许多关于运动的.一个类记录了一种体育运动的信息.我们在另外一个程序里面要知道这个dll的信息:(如果你还不能明白我的意思,请耐心的照我的步骤把这个过程走一变!)
下面我们来举一个例子.
例子的思路是这样的:我们有一个dll.该dll里面有许多关于运动的类.每一个类记录了一种体育运动的信息.我们在另外一个程序里面要知道这个dll的信息:(如果你还不能明白我的意思,请耐心的照我的步骤把这个过程走一变!)
第一步:我们建一个类库文件
Sport.cs.
内容如下:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks; public abstract class Sport{ protected string name; public abstract string GetDuration(); public abstract string GetName(); }
第二步,我们再建一个类库名为
SomeSports.cs
的文件,内容如下:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace SomeSports{ public class Football : Sport{ public Football(){ name = "Football";} public override string GetDuration(){ return "four 15 minute quarters";} public override string GetName(){ return name;}} public class Hockey : Sport{ public Hockey(){ name = "Hockey";} public override string GetDuration(){ return "three 20 minute periods";} public override string GetName(){ return name;}} public class Soccer : Sport{ public Soccer(){ name = "Soccer";} public override string GetDuration(){ return "two 45 minute halves";} public override string GetName(){ return name;}}}
下面我们用命令"
csc /t:library /r:Sport.dll SomeSports.cs"编译该文件.现在我们有了我们的运动信息
dll文件.现在我们想通过程序知道里面有哪些类.请进入最后一步:
第三步:我们创建文件
AssemblyDemo.cs".
内容如下:
using System;using System.Reflection;namespace AssemblyDemo{ public class AssemblyDemo{ public static void Main(string[] args){ int i, j; //========================== //First the command line arguments are evaluated.if there isn't //at least one,a usage message is printed //================================= if (args.GetLength(0) < 1){ Console.WriteLine("usage is AssemblyDemo");} else{ //======================== // An Assembly object is obtained from the command line argument //======================== //Assembly assembly = Assembly.LoadFrom(args[0]); Assembly assembly = Assembly.LoadFrom("SomeSports.dll"); Type[] types = assembly.GetTypes(); Console.WriteLine(assembly.GetName().Name + "contains the following types"); for (i = 0; i < types.GetLength(0); ++i){ Console.WriteLine("\r(" + i + ")" + types[i].Name);}i = types.Length - 1; Console.Write("make selection(0-" + i + ");"); j = Convert.ToInt32(Console.ReadLine()); Console.WriteLine(); if (types[j].IsSubclassOf(typeof(Sport))){ ConstructorInfo ci = types[j].GetConstructor(new Type[0]); Sport sport = (Sport)ci.Invoke(new Object[0]); Console.WriteLine(sport.GetName() + "has" + sport.GetDuration());} else{ Console.WriteLine(types[j].Name + " is not a sub-class of Sport");} Console.ReadLine();}}}}
模块信息是通过Module类访问的。下面通过一个类子,讲解下Module
类的使用,如果你是一个用心的程序员,应该了解下Module
的详细信息。
下面我们写一个新的文件
using System; using System.Reflection; public class ModuleDemo { public static void Main(string[] args) { //======================= // Am Module object is obtained representing the // SomeSports.dll library file //======================= Assembly assembly = Assembly.Load("SomeSports"); Module module = assembly.GetModule("SomeSports.dll"); //====================== //Search the module for the type named "Football" Type[] types = module.FindTypes(Module.FilterTypeName,"Football"); if(types.Length != 0) { ConstructorInfo ci = types[0].GetConstructor(new Type[0]); Sport sport = (Sport)ci.Invoke(new Object[0]); Console.WriteLine(sport.GetName() + " has "+sport.GetDuration()); } else { Console.WriteLine("type not found"); } } }
我们用csc /r:Sport.dll ModuleDemo.cs编译,然后用
MouduleDemo运行程序就能看到如下输出Football has four 15 minute quarters
。
System.Reflection命名空间
(1) AppDomain:应用程序域,可以将其理解为一组程序集的逻辑容器 (2) Assembly:程序集类 (3) Module:模块类 (4) Type:使用反射得到类型信息的最核心的类 他们之间是一种从属关系,也就是说,一个AppDomain可以包含N个Assembly,一个Assembly可以包含N个Module,而一个Module可以包含N个Type.
通过Assembly可以动态加载程序集的三种方法:
1,Assembly.Load()
这个方法通过程序集的长名称(包括程序集名,版本信息,语言文化,公钥标记)来加载程序集的,会加载此程序集引用的其他程序集,一般情况下都应该优先使用这个方法,他的执行效率比LoadFrom要高很多,而且不会造成重复加载的问题(原因在第2点上说明)
2,Assembly.LoadFrom()
这个方法从指定的路径来加载程序集,实际上这个方法被调用的时候,CLR会打开这个文件,获取其中的程序集版本,语言文化,公钥标记等信息,把他们传递给Load方法,接着,Load方法采用上面的策略来查找程序集。如果找到了程序集,会和LoadFrom方法中指定的路径做比较,如果路径相同,该程序集会被认为是应用程序的一部分,如果路径不同或Load方法没有找到程序集,那该程序集只是被作为一个"数据文件"来加载,不会被认为是应用程序的一部分。这就是在第1点中提到的Load方法比LoadFrom方法的执行效率高的原因。另外,由于可能把程序集作为"数据文件"来加载,所以使用LoadFrom从不同路径加载相同程序集的时候会导致重复加载。当然这个方法会加载此程序集引用的其他程序集。3,Assembly.LoadFile()
这个方法是从指定的文件来加载程序集,和上面方法的不同之处是这个方法不会加载此程序集引用的其他程序集! 结论:一般大家应该优先选择Load方法来加载程序集,如果遇到需要使用LoadFrom方法的时候,最好改变设计而用Load方法来代替!
参考: