C#.net之反射初探

什么是反射?
反射的定义:审查元数据并收集关于它的类型信息的能力。元数据(编译以后的最基本数据单元)就是一大堆的表,当编译程序集或者模块时,编译器会创建一个类定义表,一个字段定义表,和一个方法定义表等。System.Reflection命名空间包含的几个类,允许你反射(解析)这些元数据表的代码
 

和反射相关的命名空间(我们就是通过这几个命名空间访问反射信息):
System.Reflection.MemberInfo
System.Reflection.EventInfo
System.Reflection.FieldInfo
System.Reflection.MethodBase
System.Reflection.ConstructorInfo
System.Reflection.MethodInfo
System.Reflection.PropertyInfo
System.Type
System.Reflection.Assembly
 

 

反射的作用?

1.可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现 有对象中获取类型
2.应用程序需要在运行时从某个特定的程序集中载入一个特定的类型,以便实现某个任务时可以用到反射。
3.反射主要应用与类库,这些类库需要知道一个类型的定义,以便提供更多的功能。
 

应用要点:
1. 现实应用程序中很少有应用程序需要使用反射类型
2. 使用反射动态绑定需要牺牲性能
3. 有些元数据信息是不能通过反射获取的
4. 某些反射类型是专门为那些clr 开发编译器的开发使用的,所以你要意识到不是所有的反射类型都是适合每个人的。
 

 

反射appDomain 的程序集

   当你需要反射AppDomain 中包含的所有程序集,示例如下:
static void Main
{
      //通过GetAssemblies 调用appDomain的所有程序集
foreach (Assembly assem in Appdomain.currentDomain.GetAssemblies())
{
              //反射当前程序集的信息
reflector.ReflectOnAssembly(assem)
}
}
说明:调用AppDomain 对象的GetAssemblies 方法将返回一个由System.Reflection.Assembly元素组成的数组。
 

 

反射单个程序集

上面的方法讲的是反射AppDomain的所有程序集,我们可以显示的调用其中的一个程序集,system.reflecton.assembly 类型提供了下面三种方法:
1.Load 方法:极力推荐的一种方法,Load 方法带有一个程序集标志并载入它,Load 将引起CLR把策略应用到程序集上,先后在全局程序集缓冲区,应用程序基目录和私有路径下面查找该程序集,如果找不到该程序集系统抛出异常
2.LoadFrom 方法:传递一个程序集文件的路径名(包括扩展名),CLR会载入您指定的这个程序集,传递的这个参数不能包含任何关于版本号的信息,区域性,和公钥信息,如果在指定路径找不到程序集抛出异常。
3.LoadWithPartialName:永远不要使用这个方法,因为应用程序不能确定再在载入的程序集的版本。该方法的唯一用途是帮助那些在.Net框架的测试环节使用.net 框架提供的某种行为的客户,这个方法将最终被抛弃不用。
注意:system.AppDomain 也提供了一种Load 方法,他和Assembly的静态Load 方法不一样,AppDomain的load 方法是一种实例方法,返回的是一个对程序集的引用,Assembly的静态Load 方发将程序集按值封装发回给发出调用的AppDomain.尽量避免使用AppDomain的load 方法
 

 

利用反射获取类型信息

前面讲完了关于程序集的反射,下面在讲一下反射层次模型中的第三个层次,类型反射
一个简单的利用反射获取类型信息的例子:
using system;
using sytem.reflection;
class reflecting
{
static void Main(string[]args)
{
reflecting reflect=new reflecting();//定义一个新的自身类
//调用一个reflecting.exe程序集
assembly myAssembly =assembly.loadfrom(“reflecting.exe”)
reflect.getreflectioninfo(myAssembly);//获取反射信息
}
//定义一个获取反射内容的方法
void getreflectioninfo(assembly myassembly)
{
    type[] typearr=myassemby.Gettypes();//获取类型
    foreach (type type in typearr)//针对每个类型获取详细信息
    {
      //获取类型的结构信息
      constructorinfo[] myconstructors=type.GetConstructors;
      //获取类型的字段信息
      fieldinfo[] myfields=type.GetFiedls()
      //获取方法信息
      MethodInfo myMethodInfo=type.GetMethods();
      //获取属性信息
      propertyInfo[] myproperties=type.GetProperties
      //获取事件信息
      EventInfo[] Myevents=type.GetEvents;
    
}
}
}
其它几种获取type对象的方法:
1.System.type 参数为字符串类型,该字符串必须指定类型的完整名称(包括其命名空间)
2.System.type 提供了两个实例方法:GetNestedType,GetNestedTypes
3.Syetem.Reflection.Assembly 类型提供的实例方法是:GetType,GetTypes,GetExporedTypes
4.System.Reflection.Moudle 提供了这些实例方法:GetType,GetTypes,FindTypes
 

 

设置反射类型的成员

   反射类型的成员就是反射层次模型中最下面的一层数据。我们可以通过type对象的GetMembers 方法取得一个类型的成员。如果我们使用的是不带参数的GetMembers,它只返回该类型的公共定义的静态变量和实例成员,我们也可以通过使用带参数的GetMembers通过参数设置来返回指定的类型成员。具体参数参考msdn 中system.reflection.bindingflags 枚举类型的详细说明。
例如:

//设置需要返回的类型的成员内容
bindingFlags bf=bingdingFlags.DeclaredOnly|bingdingFlags.Nonpublic|BingdingFlags.Public;
foreach (MemberInfo mi int t.getmembers(bf))
{
    writeline(mi.membertype)    //输出指定的类型成员
}
 

 

通过反射创建类型的实例

通过反射可以获取程序集的类型,我们就可以根据获得的程序集类型来创建该类型新的实例,这也是前面提到的在运行时创建对象实现晚绑定的功能
我们可以通过下面的几个方法实现:
1. System.Activator 的CreateInstance方法。该方法返回新对象的引用。具体使用方法参见msnd
2. System.Activator 的createInstanceFrom 与上一个方法类似,不过需要指定类型及其程序集
3.System.Appdomain 的方法:createInstance,CreateInstanceAndUnwrap,CreateInstranceFrom和CreateInstraceFromAndUnwrap
4. System.type的InvokeMember实例方法:这个方法返回一个与传入参数相符的构造函数,并构造该类型。
5.System.reflection.constructinfo 的Invoke实例方法
 

 

反射类型的接口

如果你想要获得一个类型继承的所有接口集合,可以调用Type的FindInterfaces GetInterface或者GetInterfaces。所有这些方法只能返回该类型直接继承的接口,他们不会返回从一个接口继承下来的接口。要想返回接口的基础接口必须再次调用上述方法。
 

 

反射的性能:

使用反射来调用类型或者触发方法,或者访问一个字段或者属性时CLR需要做更多的工作:校验参数,检查权限等等,所以速度是非常慢的。所以尽量不要使用反射进行编程,对于打算编写一个动态构造类型(晚绑定)的应用程序,可以采取以下的几种方式进行代替:
1.通过类的继承关系。让该类型从一个编译时可知的基础类型派生出来,在运行时生成该类 型的一个实例,将对其的引用放到其基础类型的一个变量中,然后调用该基础类型的虚方法。
2.通过接口实现。在运行时,构建该类型的一个实例,将对其的引用放到其接口类型的一个变量中,然后调用该接口定义的虚方法。
3.通过委托实现。让该类型实现一个方法,其名称和原型都与一个在编译时就已知的委托相符。在运行时先构造该类型的实例,然后在用该方法的对象及名称构造出该委托的实例,接着通过委托调用你想要的方法。这个方法相对与前面两个方法所作的工作要多一些,效率更低一些
使用反射动态调用类成员,需要Type类的一个方法:InvokeMember。对该方法的声明如下(摘抄于MSDN):
public object InvokeMember(

    string name,

    BindingFlags invokeAttr,

    Binder binder,

    object target,

    object[] args

);

参数

name

String,它包含要调用的构造函数、方法、属性或字段成员的名称。

- 或 -

空字符串 (""),表示调用默认成员。

invokeAttr

一个位屏蔽,由一个或多个指定搜索执行方式的 BindingFlags 组成。访问可以是 BindingFlags 之一,如 Public、NonPublic、Private、InvokeMethod 和 GetField 等。不需要指定查找类型。如果省略查找类型,则将应用 BindingFlags.Public | BindingFlags.Instance。

binder

一个 Binder 对象,该对象定义一组属性并启用绑定,而绑定可能涉及选择重载方法、强制参数类型和通过反射调用成员。

- 或 -

若为空引用(Visual Basic 中为 Nothing),则使用 DefaultBinder。

target

要在其上调用指定成员的 Object。

args

包含传递给要调用的成员的参数的数组。

 

返回值

表示被调用成员的返回值的 Object。

 

备注:

下列 BindingFlags 筛选标志可用于定义包含在搜索中的成员:

 

为了获取返回值,必须指定 BindingFlags.Instance 或 BindingFlags.Static。

指定 BindingFlags.Public 可在搜索中包含公共成员。

指定 BindingFlags.NonPublic 可在搜索中包含非公共成员(即私有成员和受保护的成员)。

指定 BindingFlags.FlattenHierarchy 可包含层次结构上的静态成员。

下列 BindingFlags 修饰符标志可用于更改搜索的执行方式:

 

BindingFlags.IgnoreCase,表示忽略 name 的大小写。

BindingFlags.DeclaredOnly,仅搜索 Type 上声明的成员,而不搜索被简单继承的成员。

可以使用下列 BindingFlags 调用标志表示要对成员采取的操作:

 

CreateInstance,表示调用构造函数。忽略 name。对其他调用标志无效。

InvokeMethod,表示调用方法,而不调用构造函数或类型初始值设定项。对 SetField 或 SetProperty 无效。

GetField,表示获取字段值。对 SetField 无效。

SetField,表示设置字段值。对 GetField 无效。

GetProperty,表示获取属性。对 SetProperty 无效。

SetProperty 表示设置属性。对 GetProperty 无效。

 

下面通过例题对该方法进行简单应用(我一直以为,要让例题起到更容易理解文字的意义和作用,撰写的例题越简单越直观越好。)

using System;

using System.Reflection;

 

namespace ConsoleApplication9

{

     class Love

     {

         public int field1;

         private string _name;

         public Love()

         {

         }

 

         public string Name

         {

             get

             {

                 return _name;

             }

             set

             {

                 _name=value;

             }

         }

 

         public int GetInt(int a)

         {

             return a;

         }

 

         public void Display(string str)

         {

             Console.WriteLine(str);

         }

     }

 

     ///<summary>

     /// Class1 的摘要说明。

     ///</summary>

     class Class1

     {

         ///<summary>

         ///应用程序的主入口点。

         ///</summary>

         [STAThread]

         static void Main(string[] args)

         {

             //

             // TODO: 在此处添加代码以启动应用程序

             //

 

             Love love = new Love();

             Type type = love.GetType();

 

             Object obj = type.InvokeMember(null,

                 BindingFlags.DeclaredOnly |

                 BindingFlags.Public | BindingFlags.NonPublic |

                 BindingFlags.Instance | BindingFlags.CreateInstance, null, null, args);

 

 

             //调用没有返回值的方法

             type.InvokeMember("Display",BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance , null , obj , new object[]{"aldfjdlf"});

 

             //调用有返回值的方法

             int i = (int)type.InvokeMember("GetInt",BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance,null,obj,new object[]{1});

             Console.WriteLine(i);

 

             //设置属性值

             type.InvokeMember("Name",BindingFlags.SetProperty,null,obj,new string[]{"abc"});

             //获取属性值

             string str=(string)type.InvokeMember("Name",BindingFlags.GetProperty,null,obj,null);

             Console.WriteLine(str);

 

             //设置字段值

             type.InvokeMember("field1",BindingFlags.SetField,null,obj,new object[]{444});

 

             //获取字段值

             int f=(int)type.InvokeMember("field1",BindingFlags.GetField,null,obj,null);

             Console.WriteLine(f);

             Console.ReadLine();

         }

     }

}
 

以下是摘自CSDN的关于Dotnet的“反射”的入门知识。

1、什么是反射
       Reflection,中文翻译为反射。
       这是.Net中获取运行时类型信息的方式,.Net的应用程序由几个部分:‘程序集(Assembly)’、‘模块(Module)’、‘类型(class)’组成,而反射提供一种编程的方式,让程序员可以在程序运行期获得这几个组成部分的相关信息,例如:

       Assembly类可以获得正在运行的装配件信息,也可以动态的加载装配件,以及在装配件中查找类型信息,并创建该类型的实例。
Type类可以获得对象的类型信息,此信息包含对象的所有要素:方法、构造器、属性等等,通过Type类可以得到这些要素的信息,并且调用之。
MethodInfo包含方法的信息,通过这个类可以得到方法的名称、参数、返回值等,并且可以调用之。
诸如此类,还有FieldInfo、EventInfo等等,这些类都包含在System.Reflection命名空间下。

2、命名空间与装配件的关系
       很多人对这个概念可能还是很不清晰,对于合格的.Net程序员,有必要对这点进行澄清。
       命名空间类似与Java的包,但又不完全等同,因为Java的包必须按照目录结构来放置,命名空间则不需要。

       装配件是.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。

       到这里,我们可以清楚一个概念了,命名空间只是说明一个类型是那个族的,比如有人是汉族、有人是回族;而装配件表明一个类型住在哪里,比如有人住在北京、有人住在上海;那么北京有汉族人,也有回族人,上海有汉族人,也有回族人,这是不矛盾的。

       上面我们说了,装配件是一个类型居住的地方,那么在一个程序中要使用一个类,就必须告诉编译器这个类住在哪儿,编译器才能找到它,也就是说必须引用该装配件。
       那么如果在编写程序的时候,也许不确定这个类在哪里,仅仅只是知道它的名称,就不能使用了吗?答案是可以,这就是反射了,就是在程序运行的时候提供该类型的地址,而去找到它。
有兴趣的话,接着往下看吧。

3、运行期得到类型信息有什么用
       有人也许疑问,既然在开发时就能够写好代码,干嘛还放到运行期去做,不光繁琐,而且效率也受影响。
这就是个见仁见智的问题了,就跟早绑定和晚绑定一样,应用到不同的场合。有的人反对晚绑定,理由是损耗效率,但是很多人在享受虚函数带来的好处的时侯还没有意识到他已经用上了晚绑定。这个问题说开去,不是三言两语能讲清楚的,所以就点到为止了。
       我的看法是,晚绑定能够带来很多设计上的便利,合适的使用能够大大提高程序的复用性和灵活性,但是任何东西都有两面性,使用的时侯,需要再三衡量。

接着说,运行期得到类型信息到底有什么用呢?
还是举个例子来说明,很多软件开发者喜欢在自己的软件中留下一些接口,其他人可以编写一些插件来扩充软件的功能,比如我有一个媒体播放器,我希望以后可以很方便的扩展识别的格式,那么我声明一个接口:
public interface IMediaFormat
{
string Extension {get;}
Decoder GetDecoder();
}
这个接口中包含一个Extension属性,这个属性返回支持的扩展名,另一个方法返回一个解码器的对象(这里我假设了一个Decoder的类,这个类提供把文件流解码的功能,扩展插件可以派生之),通过解码器对象我就可以解释文件流。
那么我规定所有的解码插件都必须派生一个解码器,并且实现这个接口,在GetDecoder方法中返回解码器对象,并且将其类型的名称配置到我的配置文件里面。
这样的话,我就不需要在开发播放器的时侯知道将来扩展的格式的类型,只需要从配置文件中获取现在所有解码器的类型名称,而动态的创建媒体格式的对象,将其转换为IMediaFormat接口来使用。

这就是一个反射的典型应用。
4、如何使用反射获取类型
       首先我们来看如何获得类型信息。
       获得类型信息有两种方法,一种是得到实例对象
       这个时侯我仅仅是得到这个实例对象,得到的方式也许是一个object的引用,也许是一个接口的引用,但是我并不知道它的确切类型,我需要了解,那么就可以通过调用System.Object上声明的方法GetType来获取实例对象的类型对象,比如在某个方法内,我需要判断传递进来的参数是否实现了某个接口,如果实现了,则调用该接口的一个方法:

public void Process( object processObj )
{
Type t = processsObj.GetType();
if( t.GetInterface(“ITest”) !=null )
             …
}

       另外一种获取类型的方法是通过Type.GetType以及Assembly.GetType方法,如:
          Type t = Type.GetType(“System.String”);
       需要注意的是,前面我们讲到了命名空间和装配件的关系,要查找一个类,必须指定它所在的装配件,或者在已经获得的Assembly实例上面调用GetType。
       本装配件中类型可以只写类型名称,另一个例外是mscorlib.dll,这个装配件中声明的类型也可以省略装配件名称(.Net装配件编译的时候,默认都引用了mscorlib.dll,除非在编译的时候明确指定不引用它),比如:
        System.String是在mscorlib.dll中声明的,上面的Type t = Type.GetType(“System.String”)是正确的
        System.Data.DataTable是在System.Data.dll中声明的,那么:
Type.GetType(“System.Data.DataTable”)就只能得到空引用。
        必须:
Type t = Type.GetType("System.Data.DataTable,System.Data,Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");

5、如何根据类型来动态创建对象
       System.Activator提供了方法来根据类型动态创建对象,比如创建一个DataTable:

Type t = Type.GetType("System.Data.DataTable,System.Data,Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");

DataTable table = (DataTable)Activator.CreateInstance(t);

     例二:根据有参数的构造器创建对象
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数组中即可
6、如何获取方法以及动态调用方法
namespace TestSpace
{
      public class TestClass {
        private string _value;
        public TestClass() {
        }
        public TestClass(string value) {
_value = value;
        }

        public string GetValue( string prefix ) {
if( _value==null )
        return "NULL";
else
        return prefix+" : "+_value;
         }

         public string Value {
set {
     _value=value;
}
get {
     if( _value==null )
      return "NULL";
     else
      return _value;
}
         }
      }
}

       上面是一个简单的类,包含一个有参数的构造器,一个GetValue的方法,一个Value属性,我们可以通过方法的名称来得到方法并且调用之,如:

//获取类型信息
Type t = Type.GetType("TestSpace.TestClass");
//构造器的参数
object[] constuctParms = new object[]{"timmy"};
//根据类型创建对象
object dObj = Activator.CreateInstance(t,constuctParms);
//获取方法的信息
MethodInfo method = t.GetMethod("GetValue");
//调用方法的一些标志位,这里的含义是Public并且是实例方法,这也是默认的值
BindingFlags flag = BindingFlags.Public | BindingFlags.Instance;
//GetValue方法的参数
object[] parameters = new object[]{"Hello"};
//调用方法,用一个object接收返回值
object returnValue = method.Invoke(dObj,flag,Type.DefaultBinder,parameters,null);

       属性与方法的调用大同小异,大家也可以参考MSDN

7、动态创建委托
       委托是C#中实现事件的基础,有时候不可避免的要动态的创建委托,实际上委托也是一种类型:System.Delegate,所有的委托都是从这个类派生的
       System.Delegate提供了一些静态方法来动态创建一个委托,比如一个委托:

namespace TestSpace {
      delegate string TestDelegate(string value);
      public class TestClass {
public TestClass() {
            }
            public void GetValue(string value) {
                return value;
            }
       }
}

使用示例:
TestClass obj = new TestClass();
 
//获取类型,实际上这里也可以直接用typeof来获取类型
Type t = Type.GetType(“TestSpace.TestClass”);
//创建代理,传入类型、创建代理的对象以及方法名称
TestDelegate method = (TestDelegate)Delegate.CreateDelegate(t,obj,”GetValue”);

String returnValue = method(“hello”);

关于Type.GetType(string ATypeName)函数,这里的ATypeName需要填入全名,区分大小写(当然此函数也有一个版本是忽略大小写的),比如要得到String类型,则需要写成Type.GetType("System.String");
另外还有一点,Type.GetType("System.String"会执行成功,但是Type.GetType("MyNameSpace.MyClass")执行返回总是null——这里的MyNameSpace是自定义的命名空间,MyClass是自定义的类,其实如果执行Type.GetType("System.Data.SqlClient.SqlCommand")也是返回null,那是为什么呢?其实反射的时候需要先装载应用程序集,而System.String是一个例外,因为反射的时候默认装载了“mscrolib.dll”,而System.String在这个程序集中,所以就不用装载了,也就是说出现在mscrolib.dll程序集中的类型都是不用装载而可以直接调用Type.GetType(string)函数的。那怎样获取其他程序集中的类型呢?其实也很简单。

           
Assembly a = Assembly.Load("Mydll");
Type t = a.GetType("MyNameSpace.MyClass");
 
声明:本文转载自:http://blog.csdn.net/surely_soft/archive/2008/11/10/3260228.aspx

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值