反射Reflection,MFC时代叫RTTI(Runtime Type Identification) 运行时类型识别,提供一种动态创建对象的能力。

这里不谈反射的概念和基本用法,仅仅就我遇到的ERP系统中,有哪些地方用到了反射,是如何用的。

1  操作对象的属性或方法  Get/Set property and invoke method

通过反射调用,代码中很容易形成抽象化的公共代码,比如,系统中很多地方,直接用反射对对象赋值,参考:

ReflectionHelper.SetPropertyValue(salesOrder, "OrderAmount", 3123.54);

这样直接给销售订单的订单金额赋值3123.54,也可以调用方法:

ReflectionHelper.InvokeMethod(license, "VerifyExpiredDate");

2  调用功能  Execute function

ERP系统中的每个功能对应一个类型定义,用一个特性FunctionCodeAttribute标识出来,参考下面的代码定义。

[FunctionCode("ICIMSR")]
public partial class InventoryReceipt :Foundation.Forms.EntryForm
{
private IInventoryMovementManager _inventoryMovementManager;
private InventoryMovementEntity _inventoryMovement; 

运行时通过遍历程序集中类型有加FunctionCodeAttribute特性的,即可找到这个功能对应的类型,实例化类型即可。

public void OpenFunctionForm(string functionCode)
{
functionCode = functionCode.ToUpper().Trim();
Type formBaseType = null; if (!_formBaseType.TryGetValue(functionCode, out formBaseType))
{
Assembly assembly = Assembly.GetExecutingAssembly();
foreach (Type type in assembly.GetTypes())
{
try
{
object[] attributes = type.GetCustomAttributes(typeof(FunctionCode), true);
foreach (object obj in attributes)
{
FunctionCode attribute = (FunctionCode)obj;
if (!string.IsNullOrEmpty(attribute.Value))
{
if (!_formBaseType.ContainsKey(attribute.Value))
_formBaseType.Add(attribute.Value, type); if (formBaseType == null && attribute.Value.Equals(functionCode,StringComparison.InvariantCultureIgnoreCase))
formBaseType = type;
} if (formBaseType != null)
{
goto Found;
}
} }
catch
{ }
}
}
Found:
if (formBaseType != null)
{
object entryForm = Activator.CreateInstance(formBaseType) as Form;
Form functionForm = (Form)entryForm;
OpenFunctionForm(functionForm);
} }

这个用法就是.NET反射实现插件化应用程序的例子。


3  简化重复代码  Avoid duplicate code

比如一个考勤系统中,定义8个时间点,字段名称依次是T1Begin, T2End,T2Begin,T2End,T3Begin,T3End,T4Begin,T4End。在使用这8个变量的时候,可以在系统中直接调用逐个上述字段,也可以用一个循环语句完成调用,参考下面的代码:

ShiftEntity shift....
for (int shiftIndex = 1; shiftIndex <= 4; shiftIndex++)
{ object begin= ReflectionHelper.GetPropertyValue(shift, string.Format("T{0}Begin", shiftIndex));
object end= ReflectionHelper.GetPropertyValue(shift, string.Format("T{0}End", shiftIndex));

如代码中所示,使用反射方法获取对象的值,节省了一部分代码,代码的可维护性也好一点。

再参看下面的ERP自定义字段的例子,供应商表(Vendor)表增加20个自定义字段,SQL语句看起来是这样的:

ALTER TABLE Vendor ADD  COLUMN [USER_DEFINED_FIELD_1] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
ALTER TABLE Vendor ADD COLUMN [USER_DEFINED_FIELD_2] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
ALTER TABLE Vendor ADD COLUMN [USER_DEFINED_FIELD_3] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
ALTER TABLE Vendor ADD COLUMN [USER_DEFINED_FIELD_4] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
ALTER TABLE Vendor ADD COLUMN [USER_DEFINED_FIELD_5] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
ALTER TABLE Vendor ADD COLUMN [USER_DEFINED_FIELD_6] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
ALTER TABLE Vendor ADD COLUMN [USER_DEFINED_FIELD_7] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
ALTER TABLE Vendor ADD COLUMN [USER_DEFINED_FIELD_8] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
ALTER TABLE Vendor ADD COLUMN [USER_DEFINED_FIELD_9] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
ALTER TABLE Vendor ADD COLUMN [USER_DEFINED_FIELD_10] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
ALTER TABLE Vendor ADD COLUMN [USER_DEFINED_FIELD_11] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
ALTER TABLE Vendor ADD COLUMN [USER_DEFINED_FIELD_12] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
ALTER TABLE Vendor ADD COLUMN [USER_DEFINED_FIELD_13] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
ALTER TABLE Vendor ADD COLUMN [USER_DEFINED_FIELD_14] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
ALTER TABLE Vendor ADD COLUMN [USER_DEFINED_FIELD_15] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
ALTER TABLE Vendor ADD COLUMN [USER_DEFINED_FIELD_16] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
ALTER TABLE Vendor ADD COLUMN [USER_DEFINED_FIELD_17] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
ALTER TABLE Vendor ADD COLUMN [USER_DEFINED_FIELD_18] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
ALTER TABLE Vendor ADD COLUMN [USER_DEFINED_FIELD_19] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
ALTER TABLE Vendor ADD COLUMN [USER_DEFINED_FIELD_20] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL

我用反射调用这些属性,参考下面的代码,简洁容易理解:

for (int i = 1; i <= 20; i++)
{
string fieldName = string.Format("UserDefinedField{0}", i);
decimal fieldValue = (decimal) ReflectionHelper.GetPropertyValue(vendor, fieldName);
amount += fieldValue;
}

再举一个数据读取的例子,先参考下面的硬编码的例子程序。

System.DateTime start = DateTime.Now;
System.Data.IDataReader dr = GetData(recordCount);
while (dr.Read())
{
CustomTypes.Employees newEmployee = new CustomTypes.Employees();
newEmployee.Address = dr["Address"].ToString();
newEmployee.BirthDate = DateTime.Parse(dr["BirthDate"].ToString());
newEmployee.City = dr["City"].ToString();
newEmployee.Country = dr["Country"].ToString();
newEmployee.EmployeeID = Int32.Parse(dr["EmployeeID"].ToString());
newEmployee.Extension = dr["Extension"].ToString();
newEmployee.FirstName = dr["FirstName"].ToString();
newEmployee.HireDate = DateTime.Parse(dr["HireDate"].ToString());
newEmployee.LastName = dr["LastName"].ToString();
newEmployee.PostalCode = dr["PostalCode"].ToString();
newEmployee.Region = dr["Region"].ToString();
newEmployee.ReportsTo = Int32.Parse(dr["ReportsTo"].ToString());
newEmployee.Title = dr["Title"].ToString();
newEmployee.TitleOfCourtesy = dr["TitleOfCourtesy"].ToString();
pgbHardCodedConversion.Increment(1);
}
dr.Close();

如果用反射,参考一下代码例子:

List<Employees>  employee= MapDataToBusinessEntityCollection<Employees>(sqlDataReader);
public static List<T> MapDataToBusinessEntityCollection<T>  (IDataReader dr)  where T : new()
{
Type businessEntityType = typeof (T);
List<T> entitys = new List<T>();
Hashtable hashtable = new Hashtable();
PropertyInfo[] properties = businessEntityType.GetProperties();
foreach (PropertyInfo info in properties)
{
hashtable[info.Name.ToUpper()] = info;
}
while (dr.Read())
{
T newObject = new T();
for (int index = 0; index < dr.FieldCount; index++)
{
PropertyInfo info = (PropertyInfo) hashtable[dr.GetName(index).ToUpper()];
if ((info != null) && info.CanWrite)
{
info.SetValue(newObject, dr.GetValue(index), null);
}
}
entitys.Add(newObject);
}
dr.Close();
return entitys;
}
 

一对一比较没有看出优势,但是如果有很多个数据读取器读取数据转化为实体对象集合,则只需要像上面那样一句话调用,代码可复用,开发效率相当高。

 

4  运行时绑定 Runtime bind

需要设计一个水晶报表查看器,它不依赖于具体的水晶报表运行库,编译时完全不知道是在引用水晶报表,运行时检测环境安装的水晶报表版本,调用相应类型的水晶报表运行库。

object subreports = ReflectionHelper.GetPropertyValue(reportDocument, "Subreports");
IEnumerator subreportEnumerator = (IEnumerator)ReflectionHelper.InvokeMethod(subreports, "GetEnumerator");
while (subreportEnumerator.MoveNext())
{
object subreport = subreportEnumerator.Current;
string reprotName = (string)ReflectionHelper.GetPropertyValue(subreport, "Name");
if (string.Compare(reprotName, subreportName, true) == 0)
{
return subreport;
}
}
 

反射中类型都是object,所以foreach操作需要变成方法调用。

当需要延迟绑定类型时,代码可以考虑用反射编程。虽然书写代码和调试困难,但是运行时灵活,可适应性好。

5  从资源文件或内存中创建类型实例  Invoke method from embeded  resouces

当.NET程序集不是以编译时添加引用的方式引用,则可以用反射调用这些程序集中的方法。比如我将程序集编译完成后,以嵌入式资源文件的形式附加到主程序中,主程序要能调用嵌入的资源文件程序集,必须用反射调用。

Assembly library = Assembly.Load("License");
Type type = library.GetType("License.ErpLicense");
object empLicense= ReflectionHelper.CreateObjectInstance(type);
ReflectionHelper.InvokeMethod(empLicense, "VerifyLicense");
 

6  检测环境 Environment detection

这个和第4步的用意一样,凡是不能在编译时确定引用到的类型,运行时只有用反射尝试调用引用它的类型。以水晶报表环境检测的例子,需要从高版本到低版本逐个遍历,直到找到合适的版本。

以下是水晶报表运行库检测代码片段。

CrystalReportVersion? nullable = null;
ArrayList list = null;
if (supportedVersions == null)
{
list = new ArrayList(Enum.GetValues(typeof(CrystalReportVersion)));
}
else
{
list = new ArrayList(supportedVersions);
}
list.Sort();
for (int i = list.Count - 1; i >= 0; i--)
{
CrystalReportVersion crystalReportVersion = (CrystalReportVersion) list[i];
try
{
if (IsCrystalReportInstalled(crystalReportVersion, false))
{
nullable = new CrystalReportVersion?(crystalReportVersion);
break;
}
}
catch
{
}
}
if (!nullable.HasValue)
{
throw new ApplicationException("Crystal Reports runtime is not installed");
}


我们要检测系统是否安装特定系统的运行库,先尝试以反射的方式创建这个类型的实例,如果返回空对象,则有可能表示未安装系统运行库。

7  克隆对象 Clone object

.NET中克隆/复制一个对象,能够以序列化的方式做深拷贝(Deep copy)实现,参考下面的代码例子。

EntityCollection collection = ...
byte[] bytes = Serialize(collection);
EntityCollection receiptCollection = (EntityCollection)Deserialize(bytes); public static object Deserialize(byte[] buffer)
{
BinaryFormatter binaryF = new BinaryFormatter();
MemoryStream ms = new MemoryStream(buffer, 0, buffer.Length, false);
object obj = binaryF.Deserialize(ms);
ms.Close();
return obj;
} public static byte[] Serialize(object obj)
{
BinaryFormatter binaryF = new BinaryFormatter();
MemoryStream ms = new MemoryStream();
binaryF.Serialize(ms, obj);
ms.Seek(0, SeekOrigin.Begin);
byte[] buffer = new byte[(int)ms.Length];
ms.Read(buffer, 0, buffer.Length);
ms.Close();
return buffer;
}

代码中的receiptCollection和collection是指向两块不同的内存地址,也就是改变一个对象的值不会影响另一个对象。

这种复制对象的方法要求原对象的类型与新的对象的类型完全一样,否则会抛出类型异常。

有时候我们需要另外一种复制,将一个对象中的属性值,复制到另一个对象中去,举例子说明:

销售订单SalesOrder有OrderNo, OrderDate, CustomerNo三个属性,销售订单修改SalesOrderAmendment也有

OrderNo, OrderDate, CustomerNo三个属性,当我们想做销售订单的修改业务,需要根据销售订单创建销售订单修改单,可以用下面的方法:

SalesOrder  salesOrder...
SalesOrderAmendment orderAmendment=new SalesOrderAmendment();
orderAmendment.OrderNo=salesOrder.OrderNo;
orderAmendment.OrderDate=salesOrder.OrderDate;
orderAmendment.CustomerNo=salesOrder.CustomerNo;

标准的销售订单实体可能有50个以上的属性,则上面的赋值代码需要重复很多次。借助于反射,用一句话完成相同属性的赋值,参考下面的代码:

SalesOrder  salesOrder...
SalesOrderAmendment orderAmendment=new SalesOrderAmendment();
CopyObjectFieldValue(salesOrder,orderAmendment); public static void CopyObjectFieldValue(IEntity2 sourceEntity, IEntity2 targetEntity)
{
BindingFlags flags = (BindingFlags.CreateInstance | BindingFlags.Instance | BindingFlags.Static |
BindingFlags.NonPublic | BindingFlags.Public); CopyObjectFieldValue(sourceEntity, targetEntity, flags);
}

也就是说反射可以将两个对象的公共属性的值,从一个对象复制到另一个对象,这在ERP的单据复制,单据下推等功能中有重要的作用,节省了大量的重复性代码。

在对象赋值的过程中,要考虑对象为可空类型或泛型的情况。比如整数类型与可空的整数类型,是可以相互赋值的,但是在反射中,我们需要取它的原生类型,参考下面的例子代码。

Type dataType = column.DataType;
if (ReflectionHelper.IsNullable(dataType))
dataType = ReflectionHelper.GetUnderlyingTypeOf(dataType);

对象之间的赋值操作也不复杂,参考下面的代码,无非是获取属性,设置属性值。

public  void CopyTo(object sourceObject, object targetObject)
{
object[] value = new object[1];
Type sourceType = sourceObject.GetType();
Type targetType = targetObject.GetType(); LoadProperties(sourceObject, sourceType);
LoadProperties(targetObject, targetType);
List<PropertyInfo> sourcePoperties = Properties[sourceType.FullName] as List<PropertyInfo>;
List<PropertyInfo> targetProperties = Properties[targetType.FullName] as List<PropertyInfo>; foreach (PropertyInfo sourceProperty in sourcePoperties)
{
PropertyInfo targetPropertyInfo = targetProperties.
Find(delegate(PropertyInfo prop)
{ return prop.Name == sourceProperty.Name ; });
if (sourceProperty.CanRead)
{
if (targetPropertyInfo.CanWrite)
{
value[0] = sourceProperty.GetValue(sourceObject, BindingFlags.Public, null, null, null);
targetPropertyInfo.SetValue(targetObject, value, null);
}
}
}
}
 

8 预加载类型 Preload type

通过在系统启动时预先创建对象的实例(通常是一些控件),如果稍后执行的界面功能中用到这些控件时,界面的响应速度会快一些,参考下面的代码例子加深印象。

public void PreloadAssemblyType()
{
LoadAssembly("Foundation.Component", "Foundation.WinUI.Image");
LoadAssembly("Foundation.Component", "Foundation.WinUI.GroupBox");
LoadAssembly("Foundation.Component", "Foundation.WinUI.Label");
LoadAssembly("Foundation.Component", "Foundation.WinUI.Button");

LoadAssembly的程序代码如下,仅仅是创建对象的实例:

private static void LoadAssembly(string assemblyName, string className)
{
object obj2 = null;
try
{
obj2 = ReflectionHelper.CreateObjectInstance(assemblyName, className);
}
catch
{
}
finally
{
obj2 = null;
GC.Collect();
}
}
 

9 类型转换与赋值  Convert type and its value

反射应用于类型转化的例子,比较合理的一个例子是将List<T>转化为DataTable,或是将DataTable转化为List<T>。

public static DataTable ToDataTable<T>(this IEnumerable<T> varlist)
{
DataTable dtReturn = new DataTable();
PropertyInfo[] oProps = null; if (varlist == null) return dtReturn;
foreach (T rec in varlist)
{
if (oProps == null)
{
oProps = ((Type) rec.GetType()).GetProperties();
foreach (PropertyInfo pi in oProps)
{
Type colType = pi.PropertyType;
if ((colType.IsGenericType) && (colType.GetGenericTypeDefinition() == typeof (Nullable<>)))
{
colType = colType.GetGenericArguments()[0];
} dtReturn.Columns.Add(new DataColumn(pi.Name, colType));
}
} DataRow dr = dtReturn.NewRow();
foreach (PropertyInfo pi in oProps)
{
dr[pi.Name] = pi.GetValue(rec, null) == null ? DBNull.Value : pi.GetValue(rec, null);
}
dtReturn.Rows.Add(dr);
}
return dtReturn;
}
 

仅仅获取类型的公共属性,再复制到另一个对象中,抽象的代码通用性也高。

10 测试 Test

实际环境中,有些类型所依赖的环境是不容易测试的,可以考虑用反射创建一些fake值,注入到要测试的类型中。

这样可以让难以模拟测试的类型,通过测试。

技术水平有限,写的不对的地方请多指正。

一直都在说反射很有用 谈谈大型.NET ERP系统有哪些地方用到了反射的更多相关文章

  1. WCF技术剖析之三十:一个很有用的WCF调用编程技巧[下篇]

    原文:WCF技术剖析之三十:一个很有用的WCF调用编程技巧[下篇] 在<上篇>中,我通过使用Delegate的方式解决了服务调用过程中的异常处理以及对服务代理的关闭.对于<WCF技术 ...

  2. 分享20款移动开发中很有用的 jQuery 插件

    今天,很显然每个网站都需要有一个移动优化的界面以提高移动用户的使用体验.在开发任何移动项目时,要尽可能保持每一种资源尺寸都尽可能的小,以给最终用户提供一个好的体验是非常重要的.在这篇文章中我们已经编制 ...

  3. 继承自NSObject的不常用又很有用的函数(2)

    函数调用 Objective-C是一门动态语言,一个函数是由一个selector(SEL),和一个implement(IML)组成的.Selector相当于门牌号,而Implement才是真正的住户( ...

  4. 20+ 个很有用的 jQuery 的 Google 地图插件

    转自:http://www.oschina.net/translate/20-useful-jquery-google-maps-plugins Google 地图在寻找我们想要了解的商店或者其它有趣 ...

  5. 你不一定知道的几个很有用的 Git 命令

    这里给大家分享一些很有用的 Git 命令,其中很多用法你可能都不知道,无论你是工作在团队环境中或在您的个人项目中,这些命令将对你帮助很大,让你可以更加高效的进行项目开发,更轻松愉快的工作和生活. 您可 ...

  6. 8个很有用的PHP安全函数,你知道几个?

    原文:Useful functions to provide secure PHP application 译文:有用的PHP安全函数 译者:dwqs 安 全是编程非常重要的一个方面.在任何一种编程语 ...

  7. WCF技术剖析之三十:一个很有用的WCF调用编程技巧[上篇]

    原文:WCF技术剖析之三十:一个很有用的WCF调用编程技巧[上篇] 在进行基于会话信道的WCF服务调用中,由于受到并发信道数量的限制,我们需要及时的关闭信道:当遇到某些异常,我们需要强行中止(Abor ...

  8. Unix / 类 Unix shell 中有哪些很酷很冷门很少用很有用的命令?(转)

    著作权归作者所有. 商业转载请联系作者获得授权,非商业转载请注明出处. 作者:孙立伟 链接:http://www.zhihu.com/question/20140085/answer/14107336 ...

  9. [转帖]Windows 操作系统有哪些原生的工具和软件不被人了解却很有用?

    Windows 操作系统有哪些原生的工具和软件不被人了解却很有用? 蛋蛋 司马米青E1E1九木 https://www.zhihu.com/question/25343481/answer/30798 ...

随机推荐

  1. (完全背包 大数)Dollar Dayz (POJ 3181)

    http://poj.org/problem?id=3181 Description Farmer John goes to Dollar Days at The Cow Store and disc ...

  2. MATLAB不运行也不报错

    今天本来挺激动找到能运行的好几个程序 MATLAB忙到busy也是停不下来 本来不以为然 结果呢 吃了个水果 一杯水都喝下去了 还没结果(⊙o⊙) 这时候解决办法只有一个 Ctrl+c

  3. Jetty源码分析(一)

    一.目的 1.了解jetty组成架构: 2.学习jetty启动过程: 3.学习请求访问过程: 4.学习jetty内各模块作用,学习各模块内部代码: 二.jetty版本 本文所学习的jetty版本为:9 ...

  4. 编译系统中的 NFA/DFA算法理解

    1.问题概述 NFA 和 DFA浅析---要深入了解正则表达式,必须首先理解有穷自动机. 有穷自动机(Finite Automate)是用来模拟实物系统的数学模型,它包括如下五个部分: 有穷状态集St ...

  5. 人脸识别经典算法三:Fisherface(LDA)

    Fisherface是由Ronald Fisher发明的,想必这就是Fisherface名字由来.Fisherface所基于的LDA(Linear Discriminant Analysis,线性判别 ...

  6. 4..一起来学hibernate之Session详解

    后续... 后续... 后续... 后续... 后续... 后续... 后续... 后续... 后续... 后续... 后续... 后续... 后续... 后续... 后续...

  7. Movies

    码头风云欲望号街车 不可思议的收缩人 The Incredible Shrinking Man (1957) 风之谷 西域威龙 对话 天地无限 现代启示录 黑暗之心 Hearts of Darknes ...

  8. 在Python中调用glutInit遇到OpenGL.error.NullFunctionError的解决方法

    在window10 64bit + Python环境下,通过pip install PyOpenGL成功之后,无奈执行到glutInit()时候总是报错. OpenGL.error.NullFunct ...

  9. windows下安装python和依赖包的利器——Anaconda

    在windows下安装python和很多依赖包,安装起来略为痛苦,可以使用python的大整合包——Anaconda Anaconda下载地址: http://continuum.io/downloa ...

  10. FUND

    The Shaanxi Natural Science Plan Project of China Grant NO.: 2014JM8322