谈C#反射的运用

来源:互联网 发布:微软软件代理商 编辑:程序博客网 时间:2024/06/07 05:19
谈C#反射的运用 1
一、引言 2
二、反射的概念: 2
三、反射的效率 2
四、用反射看C#对实体类的封装 4
五、用反射改进的抽象工厂设计模式 7

(一)、抽象产品程序集(HotelMSIDAL) 9

(二)、模型程序集(HotelMSModel) 11

(三)、应用程序配置文件(App.config) 15

(四)、抽象工厂程序集(HotelMSAbstractDALFactory) 15

(五)、具体工厂程序集Sql版 (HotelMSSqlServerDAL) 17


一、引言

反射对于众多的程序员来说早已经不是什么陌生的话题,在开源框架泛滥的今天,反射的运用早已如同雪花漫天飞舞。那么,发射到底是什么,反射效率如何,什么时候用反射比较合适,对于这些,我们多数时候是纸上谈兵,没有自己亲身通过代码来印证。有关反射的话题,MSDN上描述详尽。在此我不敢大言不惭,仅以自身的实践谈一些浅薄的想法,愿有抛砖引玉之效。

二、反射的一般概念:

MSDN上说:反射提供了封装程序集、模块和类型的对象。您可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性。我觉得这个已经是对反射比较干脆利落的诠释。这里的”动态”二字非常重要,即指的是运行时刻,在运行的时刻我们可以根据程序逻辑或外部要求(比如配置文件等)创建类型的实例,然后实现象用new构造对象一样同等的效果。当然构造了这个实例之后下来的访问和以前就一样。


三、反射的效率
为了比较反射和非反射条件代码的运行效率,我编写如下代码:

/// <summary>

/// 水果实体类,将用两种方式实现构造

/// </summary>

class Fruit

{

private String _name;//名字

public String Name

{

get { return _name; }

set { _name = value; }

}

private String _color;//颜色

public String Color

{

get { return _color; }

set { _color = value; }

}

private float _price;//单价

public float Price

{

get { return _price; }

set { _price = value; }

}

/// <summary>

/// 有参数构造方法

/// </summary>

public Fruit(String name, String color, float price)

{

this._name = name;

this._color = color;

this._price = price;

}

/// <summary>

/// 无参数构造方法

/// </summary>

public Fruit() { }

}

/// <summary>

/// 主程序,比较反射与非反射的效率

/// </summary>

class Program

{

static void Main(string[] args)

{

//循环执行的次数

const int COUNT = 100000;

//以下用new构造对象

//开始时间

DateTime startTime = System.DateTime.Now;

for (int i = 0; i < COUNT; i++)

{

Fruit fruit = new Fruit("banana", "yellow",6.5f);

}

//结束时间

DateTime endTime = System.DateTime.Now;

TimeSpan span = endTime.Subtract(startTime);

Console.WriteLine("用new构造{0}个对象花费时间{1}毫秒", COUNT, span.Milliseconds);

//以下用反射构造对象

DateTime rstartTime = System.DateTime.Now;

for (int i = 0; i < COUNT; i++)

{

Object obj = Activator.CreateInstance(typeof(ConsoleDemo.Fruit));

}

DateTime rendTime = System.DateTime.Now;

TimeSpan rspan = rendTime.Subtract(rstartTime);

Console.WriteLine("用反射构造{0}个对象花费时间{1}毫秒", COUNT, rspan.Milliseconds);


}

}

在我的机器循环构造10万个对象,上面执行效果如下:

用new构造100000个对象花费时间10毫秒

用反射构造100000个对象花费时间981毫秒

请按任意键继续. . .

我们不难看出时间差别是巨大的,采用反射构造对象的实例,机器明显出现延迟。

在第二个循环中:Activator.CreateInstance(typeof(ConsoleDemo.Fruit));代码是先获得Fruit类的Type对象,然后交由Activator 来调用默认的构造方法创建实例。我们把获得Fruit类的Type对象放到循环外面:

//以下用反射构造对象

DateTime rstartTime = System.DateTime.Now;

Type type = typeof(ConsoleDemo.Fruit);

for (int i = 0; i < COUNT; i++)

{

Object obj = Activator.CreateInstance(type);

}

DateTime rendTime = System.DateTime.Now;

TimeSpan rspan = rendTime.Subtract(rstartTime);

Console.WriteLine("用反射构造{0}个对象花费时间{1}毫秒", COUNT, rspan.Milliseconds);

在我的机器循环构造10万个对象,上面执行效果如下:
用new构造100000个对象花费时间10毫秒
用反射构造100000个对象花费时间931毫秒
请按任意键继续. . .
我们反射的效率有所提高,但是收效甚微呀。
不言而喻,反射是以牺牲效率为代价的。

四、用反射看C#对实体类的封装
学过Java和C#的成员都有这样的感受:在Java里面的实体Bean为了实现封装,通常把实体所有的属性设置为私有的,然后为每个私有的属性建立共有的访问器,通常如下:
public class Pet{

/*私有属性的声明*/

private String name;

private String kind;

private short age;


/*公有的访问器*/

public String getName(){

return this.name;

}


public void setName(String name){

//体现封装对私有属性的保护

if (name != null && !name.equals(“”)){

this.name = name;

}

}


public String getKind(){

return this.kind;

}


public void setKind(String kind){

if (kind != null && !kind.equals(“”)){

this.kind = kind;

}

}

public short getAge(){

return this.age;

}

public void setAge(short age){

//体现封装对私有属性的保护

if (age > 0 && age < 100){

this.age = age;

}

}

}

从上面我们可以看出我们要访问被封装起来的私有属性只有通过调用它对应的访问器才能达到次目的。这样就意味着对属性的访问变成了对方法的操作。
而微软的C#在这一点上有彻底的改变。让我看看C#风格的实体类:
class Pet

{

//字段

private String _name;

//属性

public String Name

{

get { return _name; }

set { _name = value; }

}

//字段

private String _kind;

//属性

public String Kind

{

get { return _kind; }

set { _kind = value; }

}

//字段

private short _age;

//属性

public short Age

{

get { return _age; }

set

{

if (value > 0 && value < 100)

{

_age = value;

}

}

}

}

在名称约定方面有了一些改变:这里私有的称为字段,公有的称为属性。每个字段的访问都要通过对属性的访问实现。在这里我们看不到了Java的方法的身影,那么是否Java的getter、setter方法在C#里面消失了呢???我们不禁发出这样的疑问。
还是让我们通过反射窥视它的内幕吧!
//获得类型对象

Type type = typeof(ConsoleDemo.Pet);

//迭代所有属性

Console.WriteLine("属性信息如下:");

foreach (PropertyInfo p in type.GetProperties())

{

Console.WriteLine(p.Name);

}

Console.WriteLine("---------------------");

//迭代所有方法

Console.WriteLine("方法信息如下:");

foreach (MethodInfo m in type.GetMethods())

{

Console.WriteLine(m.Name);

}

Console.WriteLine("---------------------");

运行结果:
属性信息如下:
Name
Kind
Age
---------------------
方法信息如下:
get_Name
set_Name
get_Kind
set_Kind
get_Age
set_Age
GetType
ToString
Equals
GetHashCode
---------------------
请按任意键继续. . .
我们吃惊地发现原来C#幕后仍然有着这些Java里面等同的”访问器”。
现在一切明朗,毫无疑问:
首先,在C#里面你仍然完全可以写象Java那样的代码,C#当然支持这种方法,这跟语法没有直接联系。
其次,C#为了程序员更方便地操作字段,它提供了属性这样的访问器,使用属性当然比使用方法操作更为简单,但是我们无法回避的是在MSIL里面仍有着gettter、setter样的方法在发挥等效的作用,为了简单起见,微软干脆屏蔽了对setter、getter的访问权。
这样程序员只需要操作属性就一切OK了,封装效果依然完美体现。
五、用反射改进的抽象工厂设计模式

在这里,我假设您已经有反射的使用经验和抽象工厂设计模式的概念。

我用一个简单的场景来实现这种设计。我的项目解决方案如下图:




这是一个简单的酒店管理系统的持久层的实现,解释如下:

1. HotelMSAbstractDALFactory:抽象工厂程序集

2. HotelMSIDAL:抽象数据访问对象(DAO)

3. HotelMSSqlServerDAL:SqlServer版本的工厂实现和抽象数据访问对象实现。

4. HotelMSModel:系统模型,即房间实体类和房间类型实体类。

对抽象工厂的概念我们用图简单描述一下:




在抽象工厂之中,作为客户的程序已经不再依赖具体产品和具体工厂,而直接依赖的抽象工厂和抽象产品。

1.抽象工厂定义了工厂应该具有的生产抽象产品的行为。

2.抽象产品定义了产品应该具有的特性和行为。

3.客户只在乎生产出来的产品符合抽象产品提出的要求即可。

如此之后大大降低了客户与具体产品和具体工厂的依赖性。在本案例中,对应点如下:

工厂方面:

具体工厂一 -------- SqlServerDALFactory(SqlServer数据库工厂类的特殊实现)

具体工厂二 -------- AccessDALFactory(Access数据库工厂类的特殊实现)

产品方面:

抽象产品一 -------- AbstractRoomDAO(抽象的房间DAO)
抽象产品二
-------- AbstractRoomTypeDAO(抽象的房间类型DAO)

本案例的目的,也是我今天写这个的原因,实现具体工厂和具体产品与本系统的彻底解耦。当用户更换了数据之后,用户只需要重写具体工厂和具体产品,然后修改外部配置即可一切搞定,原系统的代码原封不动。以不变应万变。

一、抽象产品程序集(HotelMSIDAL)

namespace HotelMS.IDAL
{

/// <summary>

/// 对数据库表写操作行为

/// </summary>

public enum PostType

{

Add,

Delete,

Update

}

/// <summary>

/// 定义RoomDAO的约定行为

/// </summary>

public abstract class AbstractRoomDAO

{

/// <summary>

/// 数据库联接

/// </summary>

public System.Data.IDbConnection conn = null;

/// <summary>

/// 查找房间集合

/// </summary>

public abstract IList<Room> FindRoomListByRoom(Room room);

/// <summary>

/// 对房间实现增删改的接口

/// </summary>

public abstract int DoRoom(Room room, PostType postType);

}

}

namespace HotelMS.IDAL
{


/// <summary>

/// 定义RoomTypeDAO的约定行为

/// </summary>

public abstract class AbstractRoomTypeDAO

{

/// <summary>

/// 数据库联接

/// </summary>

public System.Data.IDbConnection conn = null;


/// <summary>

/// 查找房间类型集合

/// </summary>

protected abstract IList<RoomType> FindRoomTypeListByRoomType(RoomType roomType);

/// <summary>

/// 对房间类型实现增删改的接口

/// </summary>

protected abstract int DoRoomType(RoomType roomType, PostType postType);

}

}


二、模型程序集(HotelMSModel)

namespace HotelMS.Model
{

/// <summary>

/// 客房实体

/// </summary>

public class Room

{

private int _roomID;

private string _number;

private int _bedNumber;

private string _description;

private string _state;

private int _guestNumber;

private RoomType _roomType;

/// <summary>

/// 客房实体对应的房间类型实体(Many to One)

/// </summary>

public RoomType RoomType

{

get { return _roomType; }

set { _roomType = value; }

}

/// <summary>

/// Id,主键

/// </summary>

public int RoomID

{

set { _roomID = value; }

get { return _roomID; }

}

/// <summary>

/// 房间号

/// </summary>

public string Number

{

set { _number = value; }

get { return _number; }

}

/// <summary>

/// 床位数

/// </summary>

public int BedNumber

{

set { _bedNumber = value; }

get { return _bedNumber; }

}

/// <summary>

/// 客房描述

/// </summary>

public string Description

{

set { _description = value; }

get { return _description; }

}

/// <summary>

/// 客房状态,分入住、空间、维修

/// </summary>

public string State

{

set { _state = value; }

get { return _state; }

}

/// <summary>

/// 入住人数

/// </summary>

public int GuestNumber

{

set { _guestNumber = value; }

get { return _guestNumber; }

}


}

}

//-----------------------------------------------------

namespace HotelMS.Model
{

// <summary>

/// 客房类型实体

/// </summary>

public class RoomType

{

private int _typeID;

private string _typeName;

private decimal _typePrice;

private string _isAddBed;

private decimal _addBedPrice;

private string _remark;

private IList<Room> _roomList;

/// <summary>

/// 当前房间类型的房间集合(One to Many)

/// </summary>

private IList<Room> RoomList

{

get

{

if (_roomList == null)

{

_roomList = new List<Room>();

}

return _roomList;

}

set

{

if (value == null)

{

throw new ArgumentNullException("集合不允许赋空值");

}

_roomList = value;

}

}

/// <summary>

/// Id,主键

/// </summary>

public int TypeID

{

set { _typeID = value; }

get { return _typeID; }

}

/// <summary>

/// 客房类型名称

/// </summary>

public string TypeName

{

set { _typeName = value; }

get { return _typeName; }

}

/// <summary>

/// 客房类型价格

/// </summary>

public decimal TypePrice

{

set { _typePrice = value; }

get { return _typePrice; }

}

/// <summary>

/// 是否可以加床

/// </summary>

public string IsAddBed

{

set { _isAddBed = value; }

get { return _isAddBed; }

}

/// <summary>

/// 加床价格

/// </summary>

public decimal AddBedPrice

{

set { _addBedPrice = value; }

get { return _addBedPrice; }

}

/// <summary>

/// 备注

/// </summary>

public string Remark

{

set { _remark = value; }

get { return _remark; }

}

}

}


三、应用程序配置文件(App.config)

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

<appSettings>

<!--自定义工厂的程序集和类型信息-->

<add key="DalFactoryName" value="HotelMSSqlServerDAL.dll,HotelMS.SqlServerDAL.SqlServerDALFactory"/>

<!--数据库程序集的强名称-->

<add key="ConnectionAssemblyName"

value="System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>

<!--当前有效的数据库连接对象的类型信息-->

<add key="ConnectionTypeName"

value="System.Data.SqlClient.SqlConnection"/>

</appSettings>

<connectionStrings>

<!--当前有效的数据库连接信息-->

<add name="ConnectionString"

connectionString="Data Source=.;Initial Catalog=pubs;User ID=sa;Pwd=sa" />

</connectionStrings>

</configuration>

//这个配置文件的信息会被抽象工厂读取


四、抽象工厂程序集(HotelMSAbstractDALFactory)

namespace HotelMS.DALFactory
{

using System.Configuration;

using HotelMS.IDAL;

using HotelMS.Model;

/// <summary>

/// 抽象工厂,具体工厂必须具有无参数构造方法,以用于反射出对象

/// </summary>

public abstract class AbstractDALFactory

{

protected static System.Data.IDbConnection conn = null;

private static System.Data.IDbConnection CreateConnectioin()

{

//读取配置文件

String connectionString =

ConfigurationManager

.ConnectionStrings["ConnectionString"]

.ConnectionString;

//读取数据库程序集的强名称

String connectionAssemblyName =

ConfigurationManager

.AppSettings["ConnectionAssemblyName"];

/*数据库连接对象的类型信息,

*比如System.Data.SqlClient.SqlConnection
*/

String connectionTypeName =

ConfigurationManager

.AppSettings["ConnectionTypeName"];


//载入数据库程序集,并创建一个Assembly对象

Assembly ass = Assembly.Load(connectionAssemblyName);

if (ass != null)

{

//从Assembly对象创建连接类的实例

Object obj = ass.CreateInstance(connectionTypeName);

//连接对象的转型并赋值

conn = obj != null ? obj as System.Data.IDbConnection : null;

if (conn != null)

{

//连接对象的连接串赋值

conn.ConnectionString = connectionString;

}

}

return conn;


}

//工厂方法的抽象,创建房间DAO

public abstract AbstractRoomDAO CreateRoomDAO();

//工厂方法的抽象,创建房间类型DAO

public abstract AbstractRoomTypeDAO CreateRoomTypeDAO();

/// <summary>

/// 抽象工厂的全局方法,用于创建具体工厂对象

/// </summary>

/// <returns></returns>

public static AbstractDALFactory CreateDALFactory()

{

//初始化连接

CreateConnectioin();

//读取自定义工厂程序集配置信息

String dalFactoryName =

ConfigurationManager

.AppSettings["DalFactoryName"];

//载入自定义工厂程序集,并创建Assembly对象

Assembly ass = Assembly.LoadFrom(dalFactoryName.Split(',')[0]);

if (ass != null)

{

//根据Assembly对象创建工厂对象

Object obj = ass.CreateInstance(dalFactoryName.Split(',')[1]);

//工厂对象的转型并赋值
return obj != null ? obj as AbstractDALFactory : null;

}

return null;

}

}

}


五、具体工厂程序集Sql版 (HotelMSSqlServerDAL)

namespace HotelMS.SqlServerDAL
{

/// <summary>

/// SqlServer数据库的DAL工厂实现

/// </summary>

public class SqlServerDALFactory:AbstractDALFactory

{

/// <summary>

/// 创建房间数据访问对象

/// </summary>

/// <returns></returns>

public override AbstractRoomDAO CreateRoomDAO()

{

AbstractRoomDAO roomDao = new RoomDAO();

roomDao.conn = conn;

return roomDao;

}

/// <summary>

/// 创建房间类型数据访问对象

/// </summary>

public override AbstractRoomTypeDAO CreateRoomTypeDAO()

{

AbstractRoomTypeDAO roomTypeDao = new RoomTypeDAO();

roomTypeDao.conn = conn;

return roomTypeDao;

}

}

}

namespace HotelMS.SqlServerDAL
{

using HotelMS.IDAL;

/// <summary>

/// 房间DAO实现类

/// </summary>

public class RoomDAO:AbstractRoomDAO

{

public override IList<HotelMS.Model.Room> FindRoomListByRoom(HotelMS.Model.Room room)

{

//throw new Exception("The method or operation is not implemented.");

return new List<HotelMS.Model.Room>();

}

public override int DoRoom(HotelMS.Model.Room room, PostType postType)

{

switch (postType)

{

case PostType.Add:

break;

case PostType.Delete:

break;

case PostType.Update:

break;

default:

break;

}

return 1;

}

}

}

namespace HotelMS.SqlServerDAL
{

using HotelMS.IDAL;

/// <summary>

/// 房间类型DAO实现类

/// </summary>

public class RoomTypeDAO:AbstractRoomTypeDAO

{

protected override IList<HotelMS.Model.RoomType> FindRoomTypeListByRoomType(HotelMS.Model.RoomType roomType)

{

return new List<HotelMS.Model.RoomType>();

}

protected override int DoRoomType(HotelMS.Model.RoomType roomType, PostType postType)

{

switch (postType)

{

case PostType.Add:

break;

case PostType.Delete:

break;

case PostType.Update:

break;

default:

break;

}

throw new Exception("The method or operation is not implemented.");


}

}

}

对于第五个程序集“具体工厂程序集Sql版 (HotelMSSqlServerDAL)”,在项目发布之后,可以在不动项目原代码的情形下,可以通过修改配置文件App.config的对应节点实现替换。而整个软件架构和原有代码分毫未动。

一些值得注意的问题:

1. 全局程序集的加载比较特殊,最好采用强名称实现,这也是官方推荐的方式。

2. 对于其他程序集合,要确保和主应用程序(exe)在同一个目录下面。

3.App.config在主项目应用程序下面。
原创粉丝点击