七、Net中的反射.doc

上传人:sk****8 文档编号:3555084 上传时间:2019-06-04 格式:DOC 页数:44 大小:174.50KB
下载 相关 举报
七、Net中的反射.doc_第1页
第1页 / 共44页
七、Net中的反射.doc_第2页
第2页 / 共44页
七、Net中的反射.doc_第3页
第3页 / 共44页
七、Net中的反射.doc_第4页
第4页 / 共44页
七、Net中的反射.doc_第5页
第5页 / 共44页
点击查看更多>>
资源描述

1、 1 / 44 .Net 中的反射.Net 中的反射(序章) - Part.1 引言反射是.Net 提供给我们的一件强力武器,尽管大多数情况下我们不常用到反射,尽管我们可能也不需要精通它,但对反射的使用作以初步了解在日后的开发中或许会有所帮助。反射是一个庞大的话题,牵扯到的知识点也很多,包括程序集、自定义特性、泛型等,想要完全掌握它非常不易。本文仅仅对反射做一个概要介绍,关于它更精深的内容,需要在实践中逐渐掌握。本文将分为下面几个部分介绍.Net 中的反射:1.序章,我将通过一个例子来引出反射,获得对反射的第一印象。 2.反射初步、Type 类、反射普通类型。(修改中,近期发布.) 3.反射特

2、性(Attribute)。 4.xxxx (待定) 5. 序章如果你还没有接触过反射,而我现在就下一堆定义告诉你什么是反射,相信你一定会有当头一棒的感觉。我一直认为那些公理式的定义和概念只有在你充分懂得的时候才能较好的发挥作用。所以,我们先来看一个开发中常遇到的问题,再看看如何利用反射来解决:在进行数据库设计的过程中,常常会建立一些基础信息表,比如说:全国的城市,又或者订单的状态。假设我们将城市的表,起名为 City,它通常包含类似这样的字段:Id Int Identity(1,1) 城市 IdName Varchar(50) 城市名称ZIP Varchar(10) 城市邮编. / 略这个表将

3、供许多其他表引用。假如我们在建立一个酒店预订系统,那么酒店信息表(Hotel)就会引用此表,用 CityId 字段来引用酒店所在城市。对于城市(City)表这种情况,表里存放的记录(城市信息)是不定的,意思就是说:我们可能随时会向这张表里添加新的城市 (当某个城市的第一家酒店想要加入预订系统时,就需要在 City 表里新添这家酒店所在的城市)。此时,这样的设计是合理的。1.建表及其问题我们再看看另外一种情况,我们需要标识酒店预订的状态:未提交、已提交、已取消、受理中、已退回、已订妥、已过期。此时,很多开发人员会在数据库中建立一张小表,叫做BookingStatus(预订状态),然后将如上状态加

4、入进去,就好像这样:如同城市(City) 表一样,在系统的其他表,比如说酒店订单表(HotelOrder)中,通过字段StatusId 引用这个表来获取酒店预订状态。然而,几个月以后,虽然看上去和城市表的用法一样,结果却发现这个表只在数据库做联合查询或者 只在程序中调用,却从来不做修改,因为预订流程确定下来后一般是不会变更的。在应用程序中,也不会给用户提供对这个表记录的增删改操作界面。2 / 44 .Net 中的反射而在程序中调用这个表时,经常是这种情况:我们需要根据预订状态对订单列表进行筛选。此时通常的做法是使用一个下拉菜单(DropDownList),菜单的数据源(DataSource),

5、我们可以很轻易地通过一个 SqlDataReader 获得,我们将 DropDownList 的文本 Text 设为 Status 字段,将值 Value 设为 Id 字段。此时,我们应该已经发现问题:1.如果我们还有航班预订、游船预订,或者其他一些状态,我们需要在数据库中创建很多类似的小表,造成数据库表的数目过多。 2.我们使用 DropDownList 等控件获取表内容时,需要连接到数据库进行查询,潜在地影响性能。 同时,我们也注意到三点:1.此表一般会在数据库联合查询中使用到。假设我们有代表酒店订单的 HotelOrder 表,它包含代表状态的 StatusId 字段,我们的查询可能会像

6、这样:Select *, (Select Status From BookingStatus Where Id = HotelOrder.StatusId) as Status From HotelOrder。 2.在应用程序中,此表经常作为 DropDownList 或者其他 List 控件的数据源。 3.这个表几乎从不改动。 2.数组及其问题意识到这样设计存在问题,我们现在就想办法解决它。我们所想到的第一个办法是可以在程序中创建一个数组来表示预订状态,这样我们就可以删掉 BookingStatus 状态表(注意可以这样做是因为 BookingStatus 表的内容确定后几乎从不改动)。st

7、ring BookingStatus = “NoUse“, “未提交“,“已提交 “,“已取消“,“受理中“,“ 已退回“,“已订妥“,“已过期“; / 注意数组的 0 号元素仅仅是起一个占位作用,以使程序简洁。因为 StatusId 从 1开始。我们先看它解决了什么:上面提到的问题 1、问题 2 都解决了,既不需要在数据库中创建表,又无需连接到数据库进行查询。我们再看看当我们想要用文本显示酒店的预订时,该怎么做(假设有订单类 HotelOrder,其属性 StatusId 代表订单状态,为 int 类型 )。/ GetItem 用于获取一个酒店订单对象, orderId 为 int 类型,代

8、表订单的 IdHotelOrder myOrder = GetItem(orderId);lbStatus.Text = BookingStatusmyOrder.StatusId; /lbStatus 是一个 Label 控件目前为止看上去还不错,现在我们需要进行一个操作,将订单的状态改为“受理中” 。myOrder.StatusId = 4;很不幸,我们发现了使用数组可能带来的第一个问题:不方便使用,当我们需要更新订单3 / 44 .Net 中的反射的状态值时,我们需要去查看 BookingStatus 数组的定义(除非你记住所有状态的数字值) ,然后根据状态值在数组中的位置来给对象的属性

9、赋值。我们再看另一个操作,如果某个订单的状态为“已过期”,就要对它进行删除:if(BookingStatusmyOrder.StatusId=“已过期“)DeleteItem(myOrder); / 删除订单此时的问题和上面的类似:我们需要手动输入字符串“已过期” ,此时 Vs2005 的智能提示发挥不了任何作用,如果我们不幸将状态值记错,或者手误打错,就将导致程序错误,较为稳妥的做法还是按下 F12 导向到 BookingStatus 数组的定义,然后将 “已过期”复制过来。现在,我们再看看如何来绑定到一个 DropDownList 下拉列表控件(Id 为 ddlStatus)上。ddlSt

10、atus.DataSource = BookingStatus;ddlStatus.DataBind();但是我们发现产生的 HTML 代码是这样:未提交已提交已取消受理中已退回已订妥已过期我们看到,列表项的 value 值与 text 值相同,这显然不是我们想要的,怎么办呢?我们可以给下拉列表写一个数据绑定的事件处理方法。protected void Page_Load(object sender, EventArgs e) ddlStatus.DataSource = BookingStatus;ddlStatus.DataBound += new EventHandler(ddlStat

11、us_DataBound);ddlStatus.DataBind();void ddlStatus_DataBound(object sender, EventArgs e) int i = 0;ListControl list = (ListControl)sender; /注意,将 sender 转换成 ListControlforeach (ListItem item in list.Items) 4 / 44 .Net 中的反射i+;item.Value = i.ToString(); 这样,我们使用数组完成了我们期望的效果,虽然这样实现显得有点麻烦,虽然还存在上面提到的不便于使用的问

12、题,但这些问题我们耐心细心一点就能克服,而软件开发几乎从来就没有 100%完美的解决方案,那我们干脆就这样好了。NOTE:在 ddlStatus_DataBound 事件中,引发事件的对象 sender 显然是 DropDownList,但是这里却没有将 sender 转换成 DropDownList,而是将它转换成基类型 ListControl。这样做是为了更好地进行代码重用,ddlStatus_DataBound 事件处理方法将不仅限于 DropDownList,对于继承自 ListControl 的其他控件,比如 RadioButtonList、ListBox 也可以不加改动地使用 dd

13、lStatus_DataBound 方法。如果你对事件绑定还不熟悉,请参考 C#中的委托和事件 一文。这里也可以使用 Dictionary来完成,但都存在类似的问题,就不再举例了。3.枚举及其问题然而不幸的事又发生了. 我们的预订程序分为两部分:一部分为 B/S 端,在 B/S 端可以进行酒店订单的 创建(未提交)、提交(已提交)、取消提交( 已取消),另外还可以看到是不是已订妥;一部分为 C/S 端,为酒店的预订中心,它可以进行其他状态的操作。此时,对于整个系统来说,应该有全部的 7 个状态。但对于 B/S 端来说,它只有 未提交、已提交、已取消、已订妥 四个状态,对应的值分别为 1、2 、

14、3、6。我们回想一下上面是如何使用数组来解决的,它存在一个缺陷:我们默认地将订单状态值与数组的索引一一对应地联系了起来。所以在绑定 DropDownList 时,我们采用自增的方式来设定列表项的 Value 值;或者在显示状态时,我们通过 lbStatus.Text = BookingStatusmyOrder.StatusId; 这样的语句来完成。而当这种对应关系被打破时,使用数组的方法就失效了,因为如果不利用数组索引,我们没有额外的地方去存储状态的数字值。此时,我们想到了使用枚举:public enum BookingStatus 未提交 = 1,已提交,已取消,已订妥 = 65 / 44

15、 .Net 中的反射我们想在页面输出一个订单的状态时,可以这样:HotelOrder myOrder = GetItem(orderId); /获取一个订单对象lbStatus.Text = (BookingStatus)myOrder.StatusId).ToString(); / 输出文本值我们想更新订单的状态为 “已提交”:myOrder.StatusId = (int)BookingStatus.已提交;当状态为“已取消”时我们想执行某个操作:if(BookingStatus.已取消 = (BookingStatus)myOrder.StatusId)/ Do some action此

16、时,VS 2005 的智能提示已经可以发挥完全作用,当我们在 BookingStatus 后按下“.”时,可以显示出所有的状态值。NOTE:当我们使用枚举存储状态时,myOrder 对象的 StatusId 最好为 BookingStatus 枚举类型,而非 int 类型,这样操作会更加便捷一些,但为了和前面使用数组时的情况保持统一,这里 StatusId 仍使用 int 类型。以上三种情况使用枚举都显得非常的流畅,直到我们需要绑定枚举到 DropDownList 下拉列表的时候:我们知道,可以绑定到下拉列表的有两类对象,一类是实现了 IEnumerable 接口的可枚举集合,比如 Array

17、List,String,List ;一类是实现了 IListSource 的数据源,比如 DataTable,DataSet。NOTE:实际上 IListSource 接口的 GetList()方法返回一个 IList 接口,IList 接口又继承了IEnumerable 接口。由此看来,IEnumerable 是实现可枚举集合的基础,在我翻译的一篇文章 C#中的枚举器 中,对这个主题做了详细的讨论。可我们都知道:枚举 enum 是一个基本类型,它不会实现任何的接口,那么我们下来该如何做呢?4.使用反射遍历枚举字段最笨也是最简单的办法,我们可以先创建一个 GetDataTable 方法,此方法

18、依据枚举的字段值和数字值构建一个 DataTable,最后返回这个构建好的 DataTable:private static DataTable GetDataTable() DataTable table = new DataTable();table.Columns.Add(“Name“, Type.GetType(“System.String“); /创建列table.Columns.Add(“Value“, Type.GetType(“System.Int32“); /创建列DataRow row = table.NewRow();6 / 44 .Net 中的反射row0 = Book

19、ingStatus.未提交.ToString();row1 = 1;table.Rows.Add(row);row = table.NewRow();row0 = BookingStatus.已提交.ToString();row1 = 2;table.Rows.Add(row);row = table.NewRow();row0 = BookingStatus.已取消.ToString();row1 = 3;table.Rows.Add(row);row = table.NewRow();row0 = BookingStatus.已订妥.ToString();row1 = 6;table.Ro

20、ws.Add(row);return table;接下来,为了方便使用,我们再创建一个专门采用这个 DataTable 来设置列表控件的方法SetListCountrol():/ 设置列表public static void SetListControl(ListControl list) list.DataSource = GetDataTable(); / 获取 DataTablelist.DataTextField = “Name“;list.DataValueField = “Value“;list.DataBind();现在,我们就可以在页面中这样去将枚举绑定到列表控件:protec

21、ted void Page_Load(object sender, EventArgs e)SetListControl(ddlStatus); / 假设页面中已有 ID 为 ddlStatus 的 DropDownList如果所有的枚举都要通过这样去绑定到列表,我觉得还不如在数据库中直接建表,这样实在是太麻烦了,而且我们是根据枚举的文本和值去 HardCoding 出一个 DataTable 的:DataRow row = table.NewRow();7 / 44 .Net 中的反射row0 = BookingStatus.未提交.ToString();row1 = 1;table.Row

22、s.Add(row);row = table.NewRow();row0 = BookingStatus.已提交.ToString();row1 = 2;table.Rows.Add(row);row = table.NewRow();row0 = BookingStatus.已取消.ToString();row1 = 3;table.Rows.Add(row);row = table.NewRow();row0 = BookingStatus.已订妥.ToString();row1 = 6;table.Rows.Add(row);这个时候,我们想有没有办法通过遍历来实现这里?如果想要遍历这里

23、,首先,我们需要一个包含枚举的每个字段信息的对象,这个对象至少包含两条信息,一个是字段的文本(比如“未提交”),一个是字段的数字型值(比如 1) ,我们暂且管这个对象叫做 field。其次,应该存在一个可遍历的、包含了字段信息的对象(也就是 filed) 的集合,我们暂且管这个集合叫做 enumFields。那么,上面就可以这样去实现:foreach (xxxx field in enumFields)DataRow row = table.NewRow();row0 = field.Name; / 杜撰的属性,代表 文本值( 比如“未提交”)row1 = filed.intValue; /

24、杜撰的属性,代表 数字值(比如 1)table.Rows.Add(row);这段代码很不完整,我们注意到 xxxx,它应该是封装了字段信息(或者叫元数据 metadata)的对象的类型。而对于 enumFields,它的类型应该是 xxxx 这个类型的集合。这段代码是我们按照思路假想和推导出来的。实际上,.Net 中提供了 Type 类 和 System.Reflection 命名空间来帮助解决我们现在的问题。我在后面将较详细地介绍 Type 类,现在只希望你能对反射有个第一印象,所以只简略地作以说明:Type 抽象类提供了访问类型元数据的能力,当你实例化了一个 Type 对象后,你可以通过它

25、的属性和方法,获取类型的元数据信息,或者进一步获得该类型的成员的元8 / 44 .Net 中的反射数据。注意到这里,因为 Type 对象总是基于某一类型的,并且它是一个抽象类,所以我们在创建 Type 类型时,必须要提供 类型,或者类型的实例,或者类型的字符串值(Part.2 会说明)。创建 Type 对象有很多种方法,本例中,我们使用 typeof 操作符来进行,并传递BookingStatus 枚举:Type enumType = typeof(BookingStatus);然后,我们应该想办法获取 封装了字段信息的对象 的集合。Type 类提供 GetFields()方法来实现这一过程,

26、它返回一个 FieldInfo 数组。实际上,也就是上面我们 enumFields 集合的类型。FieldInfo enumFields = enumType.GetFields();现在,我们就可以遍历这一集合:foreach (FieldInfo field in enumFields)if (!field.IsSpecialName)DataRow row = table.NewRow();row0 = field.Name; / 获取字段文本值row1 = Convert.ToInt32(myField.GetRawConstantValue(); / 获取 int 数值table.R

27、ows.Add(row);这里 field 的 Name 属性获取了枚举的文本, GetRawConstantValue()方法获取了它的 int 类型的值。我们看一看完整的代码:private static DataTable GetDataTable() Type enumType = typeof(BookingStatus); / 创建类型FieldInfo enumFields = enumType.GetFields(); /获取字段信息对象集合DataTable table = new DataTable();table.Columns.Add(“Name“, Type.GetT

28、ype(“System.String“);table.Columns.Add(“Value“, Type.GetType(“System.Int32“);/ 遍历集合foreach (FieldInfo field in enumFields) if (!field.IsSpecialName) 9 / 44 .Net 中的反射DataRow row = table.NewRow();row0 = field.Name;row1 = Convert.ToInt32(field.GetRawConstantValue();/row1 = (int)Enum.Parse(enumType, fie

29、ld.Name); /也可以这样table.Rows.Add(row);return table;注意,SetListControl()方法依然存在并有效,只是为了节省篇幅,我没有复制过来,它的使用和之前是一样的,我们只是修改了 GetDataTable()方法。5.使用泛型来达到代码重用观察上面的代码,如果我们现在有另一个枚举,叫做 TicketStatus,那么我们要将它绑定到列表,我们唯一需要改动的就是这里:Type enumType = typeof(BookingStatus); /将 BookingStatus 改作 TicketStatus既然这样,我们何不定义一个泛型类来进行代

30、码重用呢?我们管这个泛型类叫做EnumManager。public static class EnumManagerprivate static DataTable GetDataTable()Type enumType = typeof(TEnum); / 获取类型对象FieldInfo enumFields = enumType.GetFields();DataTable table = new DataTable();table.Columns.Add(“Name“, Type.GetType(“System.String“);table.Columns.Add(“Value“, Typ

31、e.GetType(“System.Int32“);/遍历集合foreach (FieldInfo field in enumFields)if (!field.IsSpecialName)DataRow row = table.NewRow();row0 = field.Name;row1 = Convert.ToInt32(field.GetRawConstantValue();/row1 = (int)Enum.Parse(enumType, field.Name); 也可以这样10 / 44 .Net 中的反射table.Rows.Add(row);return table;publi

32、c static void SetListControl(ListControl list)list.DataSource = GetDataTable();list.DataTextField = “Name“;list.DataValueField = “Value“;list.DataBind();OK,现在一切都变得简便的多,以后,我们再需要将枚举绑定到列表,只要这样就行了(ddl 开头的是 DropDownList,rbl 开头的是 RadioButtonList):EnumManager.SetListControl(ddlBookingStatus); EnumManager.S

33、etListControl(rblTicketStatus);NOTE:如果你对泛型不熟悉,请参阅 C# 中的泛型 一文。上面的实现并没有考虑到性能的问题,仅仅为了引出反射使用的一个实例。6 .Net 中反射的一个范例。不管是 VS2005 的智能提示,还是修改变量名时的重构功能,都使用了反射功能。在.Net FCL 中,也经常能看到反射的影子,这里就向大家演示一个最常见的例子。大家知道,在CLR 中一共有两种类型,一种是值类型,一种是引用类型。声明一个引用类型的变量并对类型实例化,会在应用程序堆(Application Heap)上分配内存,创建对象实例,然后将对象实例的内存地址返回给变量,

34、变量保存的是内存地址,实际相当于一个指针;声明一个值类型的实例变量,则会将它分配在线程堆栈(Thread Stack)上,变量本身包含了值类型的所有字段。现在假设我们需要比较两个对象是否相等。当我们比较两个引用类型的变量是否相等时,我们比较的是这两个变量所指向的是不是堆上的同一个实例(内存地址是否相同) 。而当我们比较两个值类型变量是否相等时,怎么做呢?因为变量本身就包含了值类型所有的字段(数据),所以在比较时,就需要对两个变量的字段进行逐个的一对一的比较,看看每个字段的值是否都相等,如果任何一个字段的值不等,就返回 false。实际上,执行这样的一个比较并不需要我们自己编写代码,Microsoft 已经为我们提供了实现的方法:所有的值类型继承自 System.ValueType, ValueType 和所有的类型都继承自System.Object ,Object 提供了一个 Equals()方法,用来判断两个对象是否相等。但是ValueType 覆盖了 Object 的 Equals()方法。当我们比较两个值类型变量是否相等时,可以调用继承自 ValueType 类型的 Equals()方法。

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 教育教学资料库 > 精品笔记

Copyright © 2018-2021 Wenke99.com All rights reserved

工信部备案号浙ICP备20026746号-2  

公安局备案号:浙公网安备33038302330469号

本站为C2C交文档易平台,即用户上传的文档直接卖给下载用户,本站只是网络服务中间平台,所有原创文档下载所得归上传人所有,若您发现上传作品侵犯了您的权利,请立刻联系网站客服并提供证据,平台将在3个工作日内予以改正。