反射(Reflection)是.NET中的一个重要技术,通过反射可以在运行时获得某个类型的各种信息,包括方法、属性、事件、以及构造函数等。还可以获得每个成员的名称、访问权限和参数等信息,由于这些信息都保存在程序集的元数据中,因此反射处理的对像是程序集元数据。利用反射技术,即可对每一个类型了如指掌。知道了类型的相关信息,就可以在程序运行时动态地创建对像,调用方法、设置属性和激发事件,所有这些都不是编译时完成的。.NET Framework甚至提供了一种方法,运行时在内存中直接无中生有的创建一个程序集(Assembly)向程序集中添加类型(type),通过ILGenerator对像直接向类型方法注入IL指令并直接执行之。
.NET程序集的层次结构
C#程序由一个或多个源文件组成。程序中声明类型(type),类型包含”成员”(Member)比如典型的类中就有方法,属性,字段和事件等成员,并且可按命名空间(Namespace)进行组织,类和接口就是类型的示例。在编译C#程序时,它们被打包为程序集(Assembly),
程序集通常具有文件扩展名.exe或.dll,前者是独立的可执行程序,后者必需被加载到一个进程之后,其包含的代码才能被执行。
每个程序集都由一个或若干个模块(Moudle)组成,模块包含IL指令与资源,但不包含程序集清单,以EXE或DLL文件的形式存在。
绝大数情况下,一个程序集只包含一个模块,换句话说模块文件(DLL或EXE)就是程序集本身。
NET Framework以一系列的类型来表达上述概念
类型名 | 说明 |
Assembly | 程序集 |
Module | 模块 |
ConstructorInfo | 构造函数 |
MethodInfo | 方法 |
FieldInfo | 字段 |
PropertyInfo | 属性 |
EventInfo | 事件 |
ParameterInfo | 参数 |
每个特定的程序集都由一个Assembly类的实例表示。Assembly类提供了一些静态方法用于得到这个对像的引用
获取特定类型所在程序集引用:Console.WriteLine(Assembly.GetAssembly(typeof(Program)).Location); //program是定义的一个类 也可以用:Console.WriteLine(typeof(Program)Assembly.Location);
获取包含了当前正在执行的代码的程序集引用:Console.WriteLine(Assembly.GetExecutingAssembly().Location);
获取入口程序集引用:入口程序集,指的是进程的默认应用程序域中首先启动的那个程序集,比如控制台应用程序中Main方法所在的那个程序集,如果是其它的应用程序域,通常是指这一应用程序域调用ExecuteAssembly方法所执行的第一个程序集。 如以下代码输出当前应用程序域中入口程序集的完整地址 Console.WriteLine(Assembly.GetEntryAssembly().Location);
Type类是一种特殊的数据类型,所有数据类型都有属于自已的特定信息。
首先来看一段代码
public class MyClass
{
public int MyField;
public void MyMethod() { }
public string MyProperty { get; set; }
}
class Program
{
static void Main(string[] args)
{
MyClass obj = new MyClass(); //创建对象
//获取类型对象
Type typ = obj.GetType();
//输出类的名字
Console.WriteLine(typ.Name);
//判断其是否公有的
Console.WriteLine(typ.IsPublic);
//判断两个对象是否属于同一类型
Console.WriteLine(IsTheSameType(obj,new MyClass()));
Console.ReadKey();
}
static bool IsTheSameType(Object obj1, Object obj2)
{
return (obj1.GetType() == obj2.GetType());
}
}
上述代码先创建了一个MyClass类型的对像,通过调用GetType方法(从Object类继承)得到一个Type类型的对像,根据此对像即可知道MyClass这一类型的类型名,是否公在等信息,判断两个对像是否属于同一类型,可以直接比对其Type对像是否为同一个,具体看IsTheSameType方法。
创建Type对像
通过对像创建
.Net Framework中所有的数据类型都派生自Object类,而Object类提供了GetType方法,所以可以通过对所在的对对像调用GetType演绎法儿取其数据类型信息
int i = 100;
Type type = i.GetType();
Console.WriteLine(type.Name); //输出为int32
通过语言关键字创建
c#提供了一个typeof对像,可以根据数据类型直接获取对应的Type对像
如:Type type=typeof(int);
通过传入的参数名获取Type
GetType有几个重载形式,
常用的是以下这种:public static Type GetType(string name),需要注意的是传入的参数必须是完整的类型限定名,比如"System.Int32",而不是是仅为"Int32"
代码示例:
namespace MyNameSpace
{
namespace ChildSpace
{
public class Outer
{
public class Inner{ }
}
}
}
以上代码要获取outer所对应的type对像: Type type = Type.GetType("MyNameSpace.ChildSpace.Outer");
上述代码中还有一个类Inner,所有代码都在类的内部,这种类称为内部类或嵌套类,使用+来构造内部类的类型名,
Type type3 = Type.GetType("MyNameSpace.ChildSpace.Outer+Inner");
获取了Type对像之后,即可使用它的各种方法来获取特定数据类型的各种信息,下面这段示例代码获取MyClass.dll中类UserInfo下的各个成员并显示到窗体上,结果如
//MyClass定义如下
namespace MyClass
{
public class UserInfo
{
string strT;
public UserInfo()
{
strT = "aaa";
}
public UserInfo(string a, string b)
{
strT = a + b;
}
public int Add(int i)
{
return i;
}
public int Subtract(int i, int j)
{
return i - j;
}
public string Name
{
get;
set;
}
public string Age
{
get;
set;
}
public string Tel;
public string Mobile;
}
}
//获取MyClass.dll中UserInfo类的各个成员
public void ReflecForClass()
{
Assembly assembly = Assembly.LoadFrom("MyClass.dll");
object obj = assembly.CreateInstance("MyClass.UserInfo");
Type type = obj.GetType();
//构造函数
ConstructorInfo[] cons = type.GetConstructors();
string constructTemp = "" ;
foreach (ConstructorInfo con in cons)
{
constructTemp += con.ToString() + "\r\n";
}
txtConstructor.Text = constructTemp;
//方法
MethodInfo[] meths = type.GetMethods();
string methodTemp = "";
foreach (MethodInfo meth in meths)
{
methodTemp += meth.ToString() + "\r\n";
}
txtMethod.Text = methodTemp;
//属性
PropertyInfo[] props = type.GetProperties();
string propertyTemp = "";
foreach (PropertyInfo prop in props)
{
propertyTemp += prop.ToString() + "\r\n";
}
txtProperty.Text = propertyTemp;
//字段
FieldInfo[] fields = type.GetFields();
string fieldTemp = "";
foreach (FieldInfo field in fields)
{
fieldTemp += field.ToString() + "\r\n";
}
txtField.Text = fieldTemp;
}
Type类获取类型信息的各种方法总结为:
首先看个例子,一个四则运算器,这个例子不一样的地方是基于反射开发的。
首先会定义一个接口
namespace InterfaceLib
{
public interface IMathOpt
{
double GetIt(double x, double y);
}
}
再定义一个MathLibrary库,定义了加、减、乘、除四种运算,所有类都实现了接口库中的IMathOpt接口
namespace MathOptLibrary
{
public class AddClass:IMathOpt
{
double IMathOpt.GetIt(double x, double y)
{
return x + y;
}
}
public class SubstructClass : IMathOpt
{
double IMathOpt.GetIt(double x, double y)
{
return x - y;
}
}
public class MultiplyClass :IMathOpt
{
double IMathOpt.GetIt(double x, double y)
{
return x * y;
}
}
public class DivideClass :IMathOpt
{
double IMathOpt.GetIt(double x, double y)
{
return x / y;
}
}
}
启动项目DynamicCreatedObject引用InterfaceLib,但不需要引用MathLibrary.dll
private void btnResult_Click(object sender, EventArgs e)
{
IMathOpt mathopt;
Assembly assembly = Assembly.LoadFrom("MathOptLibrary.dll");
switch (cbxOpt.SelectedItem.ToString())
{
case "+":
mathopt =(IMathOpt)assembly.CreateInstance("MathOptLibrary.AddClass");
break;
case "-":
mathopt = (IMathOpt)assembly.CreateInstance("MathOptLibrary.SubstructClass");
break;
case "*":
mathopt = (IMathOpt)assembly.CreateInstance("MathOptLibrary.MultiplyClass");
break;
case "/":
mathopt = (IMathOpt)assembly.CreateInstance("MathOptLibrary.DivideClass");
break;
}
labResult.Text = mathopt.GetIt(Convert.ToInt32(txtNumA.Text), Convert.ToInt32
(txtNumB.Text)).ToString();
}
当用户从下拉框中选择一种运算时,使用Assembly类的createInstance方法创建对像,然后再使用它来完成计算,此项目关键点是DynamicCreatorObject项目中没有对MathLibrary项目的引用 ,只有对接口库InterfaceLib的引用,主程序中针对IMathOpt接口编程,没有任何代码绑定到MathLibrary中的具体类型,这就剥离了界面代码与功能代码,使这两者可以独立的变化,比如可以提供多种不同的用户界面,或者提供使用不同的算法实现功能代码,这样整个程序的可扩展性大大增加,不会出现牵一发而动全身的现像。