C#系列——記一次業務需求:對象的深拷貝


  這篇隨筆着實在意料之外,主要是源於上周開發BS的一個業務,需要用到對象的深拷貝。說的直白一點,就是將對象內存分配區和引用完全拷貝一份新的。這種需求以前就遇到過,怎么解決的已經記不清了。這次趁着這個機會將對象的深拷貝這個知識點記錄下。

  先來說說業務場景,直接上代碼:

       //0.反射得到工廠屬性
            var lstRes = new List<List<DragElementProp>>();
            var oType = typeof(Ewin.CommonLib.DtoModel.DTO_TM_PLANT);
            var lstAttr = ReflectorAttribute(oType);

            //1.給每個工廠對象的屬性賦值,構造前台需要的數據結構
            var lstPropModel = oType.GetProperties();
            foreach (var oModel in lstModel)
            {
                var lstResTmp = new List<DragElementProp>();
                foreach (var oAttr in lstAttr)
                {
                    var oPropModel = lstPropModel.FirstOrDefault(x => x.Name == oAttr.Name);
                    if (oPropModel == null)
                    {
                        continue;
                    }
                    oAttr.Value = oPropModel.GetValue(oModel);
            lstResTmp.Add(oAttr); }
lstRes.Add(lstResTmp); }

需求就是lstAttr變量保存的是一個List<DragElementProp>類型的集合,需要遍歷lstModel,需要將每一個的oModel的Name屬性的值賦給lstAttr實例的Value屬性。然后保存多個lstAttr的集合,形如List<List<DragElementProp>>。通過上面的代碼在foreach (var oModel in lstModel)里面每次new一個新的var lstResTmp = new List<DragElementProp>();來保存賦值后lstAttr,明眼人一看就知道這種方式肯定不行,因為C#里面class是引用類型,每次改變的都是唯一的一個lstAttr實例,通過上面代碼的方式得到的lstRes肯定會是幾個相同的lstAttr,即最后一次賦值的lstAttr。

  怎么辦?各種百度、各種博客園。查了多篇博文,發現答案千篇一律,深拷貝對象的三種解決方案:

  • 實現ICloneable接口,自定義拷貝功能
  • 序列化/反序列化類實現
  • 通過反射實現

 

我們逐一看看這幾種方式

(1)實現ICloneable接口的方式,貼上園子里面的代碼

public class Person:ICloneable 
{ 
    public int Age { get; set; } 
    public string Address { get; set; } 
    public Name Name { get; set; } 
    public object Clone() 
    { 
      Person tem = new Person(); 
      tem.Address = this.Address; 
      tem.Age = this.Age; 
      tem.Name = new Name(this.Name.FristName, this.Name.LastName); 
      return tem; 
    } 
} 

很顯然,這種方式不可取。如果一個類里面有多個其他類成員,那不是每個都要去定義這樣一個clone方法。太low。

 

(2)序列化反序列化方式。貼上園子里面的代碼

[Serializable] 
public class Person : ICloneable 

  public
object Clone()   {     using (MemoryStream ms = new MemoryStream(1000))     {       object CloneObject;       BinaryFormatter bf = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone));       bf.Serialize(ms, this);       ms.Seek(0, SeekOrigin.Begin);       // 反序列化至另一個對象(即創建了一個原對象的深表副本)       CloneObject = bf.Deserialize(ms);       // 關閉流       ms.Close();       return CloneObject;     }   }
}

這種方式比上面方式好一點,但是需要對象是可序列化的,即要加上[Serializable]特性標簽,博主試過如果一個普通的類調用這個方法會報異常。

博主用Newtonsoft.Json重新寫了個:

       foreach (var oModel in lstModel)
            {
                var lstResTmp = new List<DragElementProp>();
                foreach (var oAttr in lstAttr)
                {
                    var oPropModel = lstPropModel.FirstOrDefault(x => x.Name == oAttr.Name);
                    if (oPropModel == null)
                    {
                        continue;
                    }
                    oAttr.Value = oPropModel.GetValue(oModel);
                }
                //深拷貝一個集合到另一個集合
                var oJsonValue = Newtonsoft.Json.JsonConvert.SerializeObject(lstAttr);
                lstResTmp.AddRange(Newtonsoft.Json.JsonConvert.DeserializeObject<List<DragElementProp>>(oJsonValue));
                lstRes.Add(lstResTmp);
            }

這種方式對對象沒什么太特殊的要求。

 

(3)反射的方式,博主自己簡單寫了一個:

     public static T CloneModel<T>(T oModel)
        {
            var oRes = default(T);
            var oType = typeof(T);

            //得到新的對象對象
            oRes = (T)Activator.CreateInstance(oType);

            //給新的對象復制
            var lstPro = oType.GetProperties();
            foreach (var oPro in lstPro)
            {
                //從舊對象里面取值
                var oValue = oPro.GetValue(oModel);

                //復制給新的對象
                oPro.SetValue(oRes, oValue);
            }

            return oRes;
        }

這種方式也比較簡單,但考慮到反射得性能問題,而且如果是clone集合,需要遍歷去反射這樣效率就更低。

 

  綜上所述:要深拷貝一個對象,其實上述無論哪種方式都是新產生一個對象,然后給新的對象依次賦值來實現。方案一一般不可取,方案二在集合的序列化方便可能效率稍微高點,方案三如果只是簡單的拷貝一個對象我覺得也是不錯的選擇。反正博主更加偏好方案二,用起來簡單。

  

  反正找了好久說的都這三種方式,這次先記錄下,如果沒有更好的方式就用這些方案先解決吧,當然,如果以后知道了更好的方式也可以拿出來和大家分享。也不知道.Net是否預留了某些特殊的通道來處理這種深拷貝。希望知道的大俠多多指教~~

 


免責聲明!

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



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