反射原理及简介

  • 格式:pdf
  • 大小:131.35 KB
  • 文档页数:4

反射原理及简介

⼀.什么是反射

Reflection,中⽂翻译为反射。这是.Net中获取运⾏时类型信息的⽅式,

.Net的应⽤程序由⼏个部分:‘程序集(Assembly)’、‘模块(Module)’、‘类型(class)’组成,⽽反射提供⼀种编程的⽅式,让程序员可以在程序运

⾏期获得这⼏个组成部分的相关信息,

例如:Assembly类可以获得正在运⾏的程序集信息,也可以动态的加载程序集,以及在程序集中查找类型信息,并创建该类型的实例。

Type类可以获得对象的类型信息,此信息包含对象的所有要素:⽅法、构造器、属性等等,通过Type类可以得到这些要素的信息,并且调

⽤之。

MethodInfo包含⽅法的信息,通过这个类可以得到⽅法的名称、参数、返回值等,并且可以调⽤之。诸如此类,

还有FieldInfo、EventInfo等等,这些类都包含在System.Reflection命名空间下。

⼆. 关于程序集和命名空间的关系

很多⼈对这个概念可能还是很不清晰,对于合格的.Net程序员,有必要对这点进⾏澄清。

程序集是.NET应⽤程序执⾏的最⼩单元,编译出来的.dll和.exe都是程序集。 程序集和命名空间的关系不是⼀⼀对应,也不互相包含,⼀个

程序集⾥⾯可以有多个命名空间,⼀个命名空间也可以在多个程序中存在,这样说可能有点模糊,举个例⼦:

程序集A包含两个命名空间:

namespace N1

{

public class AC1 {…}

public class AC2 {…}

}

namespace N2

{

public class AC3 {…}

public class AC4{…}

}

程序集B包含两个命名空间:

namespace N1

{

public class BC1 {…}

public class BC2 {…}

}

namespace N2

{

public class BC3 {…}

public class BC4{…}

}

这两个程序集中都有N1和N2两个命名空间,⽽且各声明了两个类,这样是完全可以的,然后我们在⼀个应⽤程序中引⽤程序集A,那么在

这个应⽤程序中,我们能看到N1下⾯的类为AC1和AC2,N2下⾯的类为AC3和AC4。

接着我们去掉对A的引⽤,加上对B的引⽤,那么我们在这个应⽤程序下能看到的N1下⾯的类变成了BC1和BC2,N2下⾯也⼀样。 如果我们

同时引⽤这两个程序集,那么N1下⾯我们就能看到四个类:AC1、AC2、BC1和BC2。

到这⾥,我们可以清楚⼀个概念了,命名空间只是说明⼀个类型是那个族的,⽐如有⼈是汉族、有⼈是回族;⽽程序集表明⼀个类型住在哪

⾥,⽐如有⼈住在北京、有⼈住在上海;那么北京有汉族⼈,也有回族⼈,上海有汉族⼈,也有回族⼈,这是不⽭盾的。

上⾯我们说了,程序集是⼀个类型居住的地⽅,那么在⼀个程序中要使⽤⼀个类,就必须告诉编译器这个类住在哪⼉,编译器才能找到它,

也就是说必须引⽤该程序集。

那么如果在编写程序的时候,也许不确定这个类在哪⾥,仅仅只是知道它的名称,就不能使⽤了吗?答案是可以,这就是反射了,就是在程

序运⾏的时候提供该类型的地址,⽽去找到它。

三.为什么使⽤反射

有⼈会有疑问,程序所⽤的类既然可以事先写好,那么为什么还要在程序运⾏的时候去⽣成,这样岂不是浪费系统资源。存在就是合理的,

既然微软给我们开发这项技术,肯定是这个这个东西有需求,举个简单的例⼦:

现在要开发⼀个报表打印模块,有的企业要求数据以Excel报表,有的企业要求打印⽔晶报表等等,这个时候我们就可以先定义⼀个接⼝,

任何报表打印⽅法都必须实现这个接⼝:

public interface IReport

{

void StartPrint();

}

我们通过配置⽂件可以加载对应的读取报表的类型,因为报表打印类都实现了IReport的接⼝,所以都可以通过反射的⽅式强转为该接⼝类

型,这样就可以实现⽆需修改底层代码就可以实现打印不同的数据报表,符合我i们程序设计的开闭原则,这个就是反射最经典的应⽤。

public static class Factory

{

//【1】读取配置⽂件

static string reportType = ConfigurationManager.AppSettings["ReportType"].ToString();

//【2】使⽤反射创建实现接⼝类的对象并以接⼝类型返回

public static IReport ChooseReportType()

{

return (IReport)Assembly.Load("UseFactory").CreateInstance("UseFactory." + reportType);

}

}

四.如何使⽤反射获取类型

获取类信息有两种⽅式:

第⼀种⽅式,得到实例化对象。 这个时侯我仅仅是得到这个实例对象,得到的⽅式也许是⼀个object的引⽤,也许是⼀个接⼝的引⽤,但是

我并不知道它的确切类型,我需要了解,那么就可以通过调⽤System.Object上声明的⽅法GetType来获取实例对象的类型对象,⽐如在某个

⽅法内,我需要判断传递进来的参数是否实现了某个接⼝,如果实现了,则调⽤该接⼝的⼀个⽅法。

public void Progress(object o)

{

Type objType = o.GetType();

if (objType.GetInterface("ITest") !=null)

{

//调⽤该接⼝的⽅法

}

}

第⼆种获取类型的⽅法是通过Type.GetType以及Assembly.GetType⽅法,但是在使⽤该⽅法时我们需要注意⼀些问题点。在程序集A.dll中

需要反射程序集B.dll中的类型。如果使⽤稍有不慎,就会产⽣运⾏时错误。例如使⽤Type.GetType("BNameSpace.ClassName")在程序集

A.dll获取程序集B.dll中的类型,就会返回Null。

关于跨程序集的反射,有两点需要注意:

1、如果使⽤typeof,编译能通过,则跨程序集的反射⼀定可以正常运⾏。可以说,typeof是⽀持强类型的。⽐如

Type supType = typeof(BNameSpace.SubSpace.Class);

如果当前程序集没有添加对EnterpriseServerBase.dll的引⽤,则编译会报错。

2、如果使⽤Type.GetType来进⾏反射的话,情况就复杂些。这是因为Type.GetType是⾮强类型的。Type.GetType的参数是⼀个string为类

型的完全限定名,如果当string表⽰的⽬标类型不在当前程序集中,则运⾏时Type.GetType会返回null。解决的办法是:⾸先加载⽬标程序

集,然后再使⽤Assembly.GetType⽅法来获取类型。如:

Assembly asmb = Assembly.LoadFrom("EnterpriseServerBase.dll") ;

Type supType = asmb.GetType("EnterpriseServerBase.DataAccess.IDBAccesser") ;

注意:当使⽤Type.GetType的时候,即使你添加了对EnterpriseServerBase.dll的引

⽤,Type.GetType("EnterpriseServerBase.DataAccess.IDBAccesser")也会返回null,这是因为Type.GetType只会在当前程序集中进⾏类型

搜索。

5.如何根据类型动态创建对象

第⼀种⽅式

public class Example

{

static void Main()

{

// Create an instance of the StringBuilder type using

// Activator.CreateInstance.

Object o = Activator.CreateInstance(typeof(StringBuilder));

// Append a string into the StringBuilder object and display the

// StringBuilder.

StringBuilder sb = (StringBuilder) o; sb.Append("Hello, there.");

Console.WriteLine(sb);

// Create an instance of the SomeType class that is defined in this

// assembly.

System.Runtime.Remoting.ObjectHandle oh =

Activator.CreateInstanceFrom(Assembly.GetEntryAssembly().CodeBase,

typeof(SomeType).FullName);

// Call an instance method defined by the SomeType type using this object.

SomeType st = (SomeType) oh.Unwrap();

st.DoSomething(5);

}

}

例⼆:根据有参数的构造器创建对象

namespace TestSpace

{

public class TestClass

{

private string _value;

public TestClass(string value)

{

_value=value;

}

}

}

Type t = Type.GetType(“TestSpace.TestClass”);

Object[] constructParms = new object[] {“hello”}; //构造器参数

TestClass obj = (TestClass)Activator.CreateInstance(t,constructParms);

把参数按照顺序放⼊⼀个Object数组中即可

第三种⽅式:

假如我们并没有加载对应.cs⽂件的程序集,那么我们该如何简洁的创建对象类型呢:

object objCal =Assembly.LoadFrom("CalDLL.dll").CreateInstance("CalDLL.Calculator");

是不是很简洁,当然我们也可以先获取对象的类型,再⽤Activator.CreateInstance 来创建对象,不过稍显⿇烦:

Assembly objAssembly = Assembly.LoadFrom("CalDLL.dll");

Type objType = objAssembly.GetType("CalDLL.Calculator");

object objCal = Activator.CreateInstance(objType);

这样也能达到相同的效果。

6.如何获取⽅法以及动态调⽤⽅法

应⽤反射动态调⽤⽅法的⽰例如下:

using System;

using System.Reflection;