反射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. [.Net] 通过反射,给Enum加备注

    今天和大家分享一个给Enum加备注的技巧,话不多说,先上一段代码: namespace TestReflector.Model.Entities { public class UserInfo { p ...

  2. [转]oracle job有定时执行的功能,可以在指定的时间点或每天的某个时间点自行执行任务。

    oracle job有定时执行的功能,可以在指定的时间点或每天的某个时间点自行执行任务. 一.查询系统中的job,可以查询视图 --相关视图 select * from dba_jobs; selec ...

  3. oracle高阶知识点

    ------------------------------------------------- varchar2(4000)字符型,最大长度不能超过4000,与char的区别是不用空格补足 num ...

  4. eclipse maven testng

    安装过程: 1.eclipse官网下载:

  5. "+" 是怎样连接字符串的?

    关于“+”运算符对字符串的连接,不同的平台在实现上可能会略有不同. 1. Oracle JDK1.7 当使用“+”对字符串进行连接时,会创建一个临时的StringBuilder对象,该对象调用appe ...

  6. KMP 算法

    KMP 是一个字符串匹配算法.之所以称之为KMP 是因为这个算法是由Knuth.Morris.Pratt三个提出来的. 这个算法能干什么呢 ? 我想到的有三个: 1. 告诉你一个串是否是另外一个串的子 ...

  7. 【转】C#Winform程序如何发布并自动升级(图解)

    有不少朋友问到C#Winform程序怎么样配置升级,怎么样打包,怎么样发布的,在这里我解释一下打包和发布关于打包的大家可以看我的文章C# winform程序怎么打包成安装项目(图解)其实打包是打包,发 ...

  8. webview使用总结及注意事项

    1 网页 调用后台java代码 ,后台处理 一 网页上click事件 <a href="javascript:;" onclick="window.JsNative ...

  9. Windows 下使用git 将代码托管到开源中国-(http://git.oschina.net/)

    一.准备工作 当然是准备在windows 下使用需要的环境,和工具. msysgit  下载地址:http://msysgit.github.io/ TortoiseGit 下载地址:https:// ...

  10. [FPGA] 1、开发板使用和引脚连接

    目录 1.注意事项 2.设备简介 3.引脚分配 注意事项: ① 插拔下载线时必须断电! ② Quartus II 软件和 NIOS 软件的版本必须一致,并安装在同一个目录下面,安装目录不要有中文和空格 ...