C# 反射

[转]C#反射
 
反射(Reflection)是.NET中的重要机制,通过放射,可以在运行时获得.NET中每一个类型(包括类、结构、委托、接口和枚举等)的成员,包括方法、属性、事件,以及构造函数等。还可以获得每个成员的名称、限定符和参数等。有了反射,即可对每一个类型了如指掌。如果获得了构造函数的信息,即可直接创建对象,即使这个对象的类型在编译时还不知道。



程序代码在编译后生成可执行的应用,我们首先要了解这种可执行应用程序的结构。

应用程序结构分为应用程序域—程序集—模块—类型—成员几个层次,公共语言运行库加载器管理应用程序域,这种管理包括将每个程序集加载到相应的应用程序域以及控制每个程序集中类型层次结构的内存布局。

程序集包含模块,而模块包含类型,类型又包含成员,反射则提供了封装程序集、模块和类型的对象。我们可以使用反射动态地创建类型的实例,将类型绑定到现有对象或从现有对象中获取类型,然后调用类型的方法或访问其字段和属性。反射通常具有以下用途。

(1)使用Assembly定义和加载程序集,加载在程序集清单中列出模块,以及从此程序集中查找类型并创建该类型的实例。
(2)使用Module了解包含模块的程序集以及模块中的类等,还可以获取在模块上定义的所有全局方法或其他特定的非全局方法。
(3)使用ConstructorInfo了解构造函数的名称、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等。使用Type的GetConstructors或GetConstructor方法来调用特定的构造函数。
(4)使用MethodInfo了解方法的名称、返回类型、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等。使用Type的GetMethods或GetMethod方法来调用特定的方法。
(5)使用FiedInfo了解字段的名称、访问修饰符(如public或private)和实现详细信息(如static)等,并获取或设置字段值。
(6)使用EventInfo了解事件的名称、事件处理程序数据类型、自定义属性、声明类型和反射类型等,添加或移除事件处理程序。
(7)使用PropertyInfo了解属性的名称、数据类型、声明类型、反射类型和只读或可写状态等,获取或设置属性值。
(8)使用ParameterInfo了解参数的名称、数据类型、是输入参数还是输出参数,以及参数在方法签名中的位置等。

System.Reflection.Emit命名空间的类提供了一种特殊形式的反射,可以在运行时构造类型。
反射也可用于创建称为类型浏览器的应用程序,使用户能够选择类型,然后查看有关选定类型的信息。
此外,Jscript等语言编译器使用反射来构造符号表。System.Runtime.Serialization命名空间中的类使用反射来访问数据并确定要永久保存的字段,System.Runtime.Remoting命名空间中的类通过序列化来间接地使用反射。
2008年03月01日 星期六 下午 07:36
[来源]http://blog.csdn.net/sscsgss/archive/2006/10/19/1341035.aspx
提纲:
1、 什么是反射
2、 命名空间与装配件的关系
3、 运行期得到类型信息有什么用
4、 如何使用反射获取类型
5、 如何根据类型来动态创建对象
6、 如何获取方法以及动态调用方法
7、 动态创建委托

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");
     这样才可以,大家可以看下面这个帖子:
      http://expert.csdn.net/Expert/topic/2210/2210762.xml?temp=.1919977    qqchen的回答很精彩

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”);

到这里,我们简单的讲述了反射的作用以及一些基本的用法,还有很多方面没有涉及到,有兴趣的朋友可以参考MSDN。
   很奇怪,很多人都不愿看MSDN,其实你想要的答案,99%都可以在里面找到。

 

 

 c#中的反射     CSDN Blog推出文章指数概念,文章指数是对Blog文章综合评分后推算出的,综合评分项分别是该文章的点击量,回复次数,被网摘收录数量,文章长度和文章类型;满分100,每月更新一次。

c#中的反射 作者: YAOTIEBING
反射的概述
  
  反射的定义:审查元数据并收集关于它的类型信息的能力。元数据(编译以后的最基本数据单元)就是一大堆的表,当编译程序集或者模块时,编译器会创建一个类定义表,一个字段定义表,和一个方法定义表等,。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.通过委托实现。让该类型实现一个方法,其名称和原型都与一个在编译时就已知的委托相符。在运行时先构造该类型的实例,然后在用该方法的对象及名称构造出该委托的实例,接着通过委托调用你想要的方法。这个方法相对与前面两个方法所作的工作要多一些,效率更低一些
上次在MSDN网站看到一个比较动态调用代码的文章,用到的例子似乎比较复杂,为计算一个复杂多项式子而将其中部分割开,动态形成代码段来被循环调用。详细看.NET下几种动态生成代码方式比较。今天看到微软C#团队的Eric Gunnerson写的另外一篇关于动态调用代码性能的比较文章,为了说明结果和计算的准确性,减少由于函数复杂而受编译优化的影响,他使用了一个极为简单的例子:
输入一个参数,然后返回这个参数加一,这么简单的函数,优化和没有优化的代码应该不会有差别的了。
    public class Processor
    
{
        
public int Process(int value)
        
{
            
return value + 1;
        }

    }



而对比方面,除了上次那几种外,还加了代理方式调用来进行比较。
1. 直接调用
int value = processor.Process(i);
2. 用反射机制,Type.InvokeMember()调用。
    Type t = typeof(Processor);
    
int value = 
        (
int) t.InvokeMember(
                  
"Process"
         BindingFlags.Instance 
| BindingFlags.Public | 
                  BindingFlags.InvokeMethod, 
                  
null, processor, new object[] {i});

3. 通过一个接口
    public interface IProcessor
   
{
        
int Process(int value);
    }


4. 通过一个委托Delegate
    public delegate int ProcessCaller(int value);
    ProcessCaller processCaller 
= new ProcessCaller(processor.Process);
    
int value = processCaller(i); 

5. 也通过反射机制建立委托再动态调用
    Type delegateType = CreateCustomDelegate(methodInfo);
    Delegate p 
= Delegate.CreateDelegate(delegateType, 
                                         process, 
"Process");
    
int value = (int) p.DynamicInvoke(new object[] {i});

6. 元编程方式
对于2和5由于使用反射机制,不可避免需要建立中间的临时对象去传递参数,将参数和返回值装箱等操作,因此花费了大量的机器时间。

下面是运行的某次结果(循环100000次):




结论:
1.直接调用速度最快是肯定的。
2.接口调用比元编程速度快,而元编程又比委托方式快,但微软相信Whidbey会极大优化委托调用方式,从而使它接近接口调用的水平。
3.直接用Type的反射机制是速度最慢的,比用反射机制建立委托来动态调用还慢。
4.直接使用委托不够灵活,有时候需要用反射机制建立委托来调用,但会减低性能,希望Whidbey优化了委托的性能后这种情况可以改善,灵活是需要牺牲性能的。
 
在实际开发中,我们经常需要从数据库中读取数据并赋值给实体类的相应属性。在.Text的DataDTOProvider中存在大量这样的代码, 比如:
public Role[] GetRoles(int BlogID)
        
{
            System.Collections.ArrayList al
=new System.Collections.ArrayList();
            IDataReader reader
=DbProvider.Instance().GetRoles(BlogID);
            
try
            
{
                
while(reader.Read())
                
{
                    Role role
=new Role();
                    
if(reader["RoleID"]!=DBNull.Value)
                    
{
                        role.RoleID
=(int)reader["RoleID"];
                    }

                    
if(reader["Name"]!=DBNull.Value)
                    
{
                        role.Name
=(string)reader["Name"];
                    }

                    
if(reader["Description"]!=DBNull.Value)
                    
{
                        role.Description
=(string)reader["Description"];
                    }

                    
//ReaderToObject(reader,role);
                    al.Add(role);
                }

            }

            
finally
            
{
                reader.Close();
            }

            
return (Role[])al.ToArray(typeof(Role));
        
        }

对于上面的代码,我觉得有几点不优雅之处:
1、每次对Role的属性进行赋值时,都要检查reader的值是否为DBNull,出现了很多重复代码
2、每次对Role的属性进行赋值时,都要进行类型转换, 而Role属性的类型是已知的,是不是可以自动完成这样的转换?
3、每次对Role的属性进行赋值时,都要进行Role属性与数据库字段的对应。如果我们在设计数据库与实体类时,保证数据库字段与实体类属性采用同样的名称,那利用反射,我们可以通过代码自动进行属性与字段的对应。即使数据库字段与属性不同名,我们也可以通过更改查询语句,来做到这一点。
是不是可以对上面的代码进行改进,使代码变得更优雅?那优雅的代码应该是什么样的呢?如果我们用上面代码中注释的代码行ReaderToObject(reader,role);取代它之前的对Role属性进行赋值的语句,是不是会使代码变得更优雅?ReaderToObject的作用就是自动完成将reader中的值写入到role中对应的属性中(前提是reader中的字段与role中对应的属性具有相同的名称)。现在我们的任务就是实现ReaderToObject, 有了强大的武器—Reflection,我们的任务就变得很轻松, 也不多说了,下面的代码是我的实现方法:
private void ReaderToObject(IDataReader reader,object targetObj)
        
{
            
for(int i=0;i<reader.FieldCount;i++)
            
{
                System.Reflection.PropertyInfo propertyInfo
=targetObj.GetType().GetProperty(reader.GetName(i));
                
if(propertyInfo!=null)
                
{
                    
if(reader.GetValue(i)!=DBNull.Value)
                    
{
                        propertyInfo.SetValue(targetObj,reader.GetValue(i),
null);
                    }

                }

            }

        }
ReaderToObject可以将reader中的数据读入到任何实体类中。数据库字段与实体类属性的映射原则是名称相同。当然,我们也可以通过配置文件来进行两者映射。

    个人想法:在开发中,面对那么多设计思想和设计模式,常常令人感到迷惑,当你把更多的精力放在选用哪个设计思想或设计模式时,我觉得不要忽略很重要的一点,尽可能地减少重复代码,只要我们能有效地减少重复代码,我们采用的方法就是好方法,而不要太在乎采用了哪种模式。就像独孤九剑,正因为摆脱了传统招式的束缚,才能战无不胜!
先来看这段NUnit测试代码,我们希望用反射机制在运行时访问一个对象的枚举类型的域或属性:
 
[TestFixture]
public class PaymentInfo
{
 public enum PaymentType
 {
    Cash, CreditCard, Check
 }

 public PaymentType Type;

 public void Test()
 {
    PaymentInfo payment = new PaymentInfo();
    payment.Type = PaymentType.Cash;

    System.Reflection.FieldInfo enumField = GetType().GetField("Type");

    int paymentTypeInt32;

    paymentTypeInt32 = (int)enumField.GetValue(payment);
    Assert.AreEqual((int)PaymentType.Cash, paymentTypeInt32);

    enumField.
SetValue(payment, paymentTypeInt32);
    Assert.AreEqual(PaymentType.Cash, payment.Type);
 }
}
 
 
在这个测试中,使之通过的办法其实非常简单:把划线部分强制转换为枚举类型即可,如:(PaymentType)paymentTypeInt32。可问题是:在运行时如何动态转换类型呢?比如说我在写ElegantDAL的时候,需要将从数据库读出的一个类型为int的数值写入到要返回的对象的一个枚举型字段中,此时我只有fieldInfocolumnValueresultObject,然而写成fieldInfo.SetValue(resultObject, columnValue)就会出现前面提到的错误,可是我又只有一个运行时的Type信息(fieldInfo.FieldType),我又不能写成fieldInfo.SetValue(resultObject, (fieldInfo.FieldType)columnValue)……
 
只好将这种情况列为一个特例处理,而我们的救兵则是Enum.ToObject()方法——你知道有更好的方法解决这个问题吗?
 
BTW: 很多朋友写信来问偶承诺的下一篇文章什么时候才能搞定,其实我也不想拖,可最近工作确实比较紧张,而我的最佳写作时间又都在深更半夜(之前还要热身进入状态),所以迟迟不能结稿。目前其实已经写了好多(已经和第一篇长度差不多了),可是发现要写的内容还太多(也许是我写得太细了,因为我的目标不仅是讲how,更多的是whatwhy),所以现在决定把本篇文字再细分为两部分(上篇只讲TP/RP+MBR对象;把CBO相关的内容放到下篇再写),以便能够尽快让大家看到新的内容——嗯,争取周五release吧。至于还有朋友希望我能够深入写些关于ElegantDAL的设计与实现细节,这个留待稍后吧,正好我们将要整理的项目文档中也要有这一块内容(现在在我们这个企业级项目中用得还真挺好),到时候一起写啦(希望能够和canyue尽快完成第三版本的重写工作,目前使用的是第二版)。
实际上运行测试时发现在标红的这行上抛出一个异常:“对象类型无法转换为目标类型”。究其原因,原来是因为CLR的反射机制不允许枚举类型与整数类型之间隐式转换。不过C#编译器还是允许我们通过强制类型转换的语法来进行两者间的显式转换。


posted @ 2012-05-24 14:33  Patrick005  阅读(504)  评论(0编辑  收藏  举报