一直都在說反射很有用 談談大型.NET ERP系統有哪些地方用到了反射


反射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值,注入到要測試的類型中。

這樣可以讓難以模擬測試的類型,通過測試。

技術水平有限,寫的不對的地方請多指正。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM