這篇隨筆着實在意料之外,主要是源於上周開發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是否預留了某些特殊的通道來處理這種深拷貝。希望知道的大俠多多指教~~