程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> “對象”到“山寨對象”的完整轉換

“對象”到“山寨對象”的完整轉換

編輯:關於.NET

在NET中,拷貝分為淺拷貝和深拷貝,

淺拷貝:創建一個新對象,然後將當前對象的非靜態字段復制到該新對象。如果字段是值類型的,則對該字段執行逐位復制。如果字段是引用類型,則復制引用但不復制引用的對象;因此,原始對象及其復本引用同一對象(引用MSDN),對於這種的實現其實很簡單,就是用Object類的MemberwiseClone方法。

深拷貝:創建一個新對象,這個新對象所包含的值和引用這些都是新的對象,也就是對該對象的所有東西進行完整的復制。MS對於這種方式基本上沒有提供出實現,或者說根本就提供不了,因為這有很多因素來制約。不過它還是對少數的幾個類提供了,例如DataTable的Copy方法....

"山寨文化",現在已經成為了流行的一個詞,"山寨手機"、"山寨版明星"、連"春晚"也要山寨起來了.....!

既然現實環境有那麼多的"山寨",那在程序的環境中,我們就更需要了!程序世界中的山寨就是對象的副本!MS只給了我們表層的復制,下面就讓我們將"山寨"進行到底!

當我們要用到復制、粘貼、或撤消... 這些動作的時候,往往需要用到對象的副本進行操作,這樣就需要去做深拷貝(WindowsForm開發中,這種情況是特別多的),但是麻煩的問題就出現在這裡:我們可以實現ICloneable接口,然後手寫Clone方法來達到我們的目的,可是,如果對象之間的關系比較多,對象中的屬性又比較多的時候,那麼對每個類寫Clone這個方法的時候... 恐怖呀!這需要增加很多的開發時間,我們為什麼不弄一個比較公用的方法呢?!

好的,方法出來了,其實就是做一個基類,裡面有個共有的拷貝方法,每個想去深拷貝的類都要繼承它。

再看下面BaseCopyObject的代碼:

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
namespace CloneProject
{
public abstract class BaseCopyObject
{
#region ICloneable 成員
/// <summary>
/// 拷貝對象
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public virtual object CopyObject(params object[] obj)
{
object newObj = null;
Type mType = this.GetType();
ConstructorInfo[] constructArr = mType.GetConstructors();
//存在Public的構造函數
if (constructArr != null && constructArr.Length > 0)
{
newObj = Activator.CreateInstance(mType, this.GetConstructParams());
//獲取實例字段
FieldInfo[] mFields = mType.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
if (mFields != null && mFields.Length > 0)
{
foreach (FieldInfo info in mFields)
{
this.SetFieldValue(info, newObj, obj);
}
}
}
return newObj;
}
/// <summary>
/// 設置對象字段
/// </summary>
/// <param name="type"></param>
/// <param name="pValue"></param>
/// <param name="newObj"></param>
/// <param name="objs"></param>
private void SetFieldValue(FieldInfo field, object newObj, params object[] objs)
{
//事件字段
EventInfo ev = this.GetType().GetEvent(field.Name);
if (ev != null)
{
return;
}
Type mFieldType = field.FieldType;
object pValue = field.GetValue(this);
if (pValue != null)
{
//非值類型或者string類型
if (mFieldType != typeof(string) && !mFieldType.IsValueType)
{
//繼承BaseCopyObjectType的類
if (this.IsBaseCopyObjectType(mFieldType))
{
//使用默認的方式COPY
if (!this.CopyDefineElement(mFieldType, ref pValue))
{
if (objs != null && objs.Length > 1)
{
//存在循環引用
if (pValue == objs[0])
{
pValue = objs[1];
}
else
{
pValue = ((BaseCopyObject)pValue).CopyObject(new object[] { this, newObj });
}
}
else
{
pValue = ((BaseCopyObject)pValue).CopyObject(new object[] { this, newObj });
}
}
}
else
{
this.CopyDefineElement(mFieldType, ref pValue);
}
}
field.SetValue(newObj, pValue);
}
}
#region 私有方法
private bool IsBaseCopyObjectType(Type type)
{
int i = -1;
IsBaseCopyObjectType(type.BaseType, ref i);
if (i == 1)
{
return true;
}
else
{
return false;
}
}
private void IsBaseCopyObjectType(Type type, ref int i)
{
if (type == typeof(Object))
{
i = 0;
}
else if (type == typeof(BaseCopyObject))
{
i = 1;
}
if (i == -1)
{
this.IsBaseCopyObjectType(type.BaseType, ref i);
}
}
#endregion
#region 虛方法
/// <summary>
/// 獲取構造函數的參數
/// </summary>
/// <returns></returns>
protected virtual object[] GetConstructParams()
{
return null;
}
/// <summary>
/// 自定義復制處理
/// </summary>
/// <param name="type"></param>
/// <param name="obj"></param>
/// <returns></returns>
protected virtual bool CopyDefineElement(Type type, ref object obj)
{
return false;
}
#endregion
#endregion
}
}

基本思路:

1、先獲取這個類型的Type,然後利用Activator.CreateInstance獲取構造函數。

注意的地方:

因為對於繼承BaseCopyObject的類,這些類的構造函數是否公開(Public),參數怎麼定義對於這個方法來說是不確定的,所以在該方法對於構造函數做了兩個動作

1、對於沒有定義Public構造函數的類是復制不了的

2、this.GetConstructParams(),它是用來獲取構造函數的參數數組的,子類可以根據自己的需要去定義構造函數接受的參數,默認是為Null的。

2、獲取該類型的所有字段,mType.GetFields(BindingFlags.NonPublic | BindingFlags.Public | dBindingFlags.Instance),然後通過FieldInfo的GetValue來獲取值,SetValue來賦值,

注意的地方:

1、對於事件字段是不進行復制的,並且復制的時候會把它設置為null,因為對於事件來說,它的范圍比較大,不可能對和它有關的對象都進行復制。

2、如果字段是值類型,很簡單,直接GetValue和SetValue就可以了,不過對於引用類型有以下三種情況:

A、引用類型繼承了BaseCopyObject,可以使用BaseCopyObject的CopyObject方法

pValue = ((BaseCopyObject)pValue).CopyObject(new object[] { this, newObj });

B、A類和B類之間是相互依賴關系的時候,例如a.B = b、並且b.A = a,a、b為A類和B類的對象,這種情況下,復制a對象的時候,對a對象中的所有字段進行復制,但是當復制到b對象的時候,會判斷b對象中的字段是否有指回a對象的,如果有就不進行復制,而是賦引用,如果沒有,就進行復制。

C、引用類型沒有繼承BaseCopyObject,例如,包含DataTable...這些對象,當對象中有這些引用的時候,這個基類的CopyObject是不能進行完全處理的,這就需要去重寫CopyDefineElement這個方法,並且返回true,通過判斷這個類中的字段類型來進行自定義的動作,從而達到自定義拷貝的目的。

3、在有些特殊的情況下,我們不想對某些字段的引用對象進行完整拷貝,這個時候我們可以使用重寫CopyDefineElement這個方法(和上面2.C中的一樣),達到我們自定義的目的

單元測試代碼:

[TestMethod]
public void TestClone()
{
   //對象A
ITEmployeeObj itObj = new ITEmployeeObj();
//對象B
EmployeeObj obj = new EmployeeObj();
obj.EmployeeName = "TestName";
obj.Remark = "TestRemark";
//設置對象A和B的相互依賴
obj.ParentObj = itObj;
itObj.Employee = obj;
//設置對象A的事件屬性
itObj.DealEvent += new EventHandler(itObj_DealEvent);
//復制對象A
ITEmployeeObj copyObj = (ITEmployeeObj)itObj.CopyObject();
//斷言對象A和復制對象的引用不相同
Assert.AreNotEqual(itObj, copyObj);
//斷言對象A.Employee和復制對象的Employee引用不相同
Assert.AreNotEqual(itObj.Employee, copyObj.Employee);
//斷言對象A和復制對象的值類型是一致的
Assert.AreEqual(itObj.Employee.EmployeeName, copyObj.Employee.EmployeeName);
//復制對象中存在相互依賴的時候,引用沒有進行新的復制
Assert.AreEqual(copyObj.Employee.ParentObj, copyObj);
obj.ParentObj = new ITEmployeeObj();
copyObj = (ITEmployeeObj)itObj.CopyObject();
//復制對象沒有相互依賴的時候,進行全新的復制
Assert.AreNotEqual(copyObj.Employee.ParentObj, copyObj);
}

寫完咯!在2008年中,看到了"山寨"開始成長,期望看到2009年"山寨"的前方到底是什麼?!呵呵

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved