1. 程式人生 > >C#系列——記一次業務需求:物件的深拷貝

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是否預留了某些特殊的通道來處理這種深拷貝。希望知道的大俠多多指教~~

相關推薦

C#系列——業務需求物件拷貝

  這篇隨筆著實在意料之外,主要是源於上週開發BS的一個業務,需要用到物件的深拷貝。說的直白一點,就是將物件記憶體分配區和引用完全拷貝一份新的。這種需求以前就遇到過,怎麼解決的已經記不清了。這次趁著這個機會將物件的深拷貝這個知識點記錄下。   先來說說業務場景,直接上程式碼:        //0.反

產品需求圖片等比縮放和CSS自適應布局16:9

是我 width 圖片展示 網上 IT tom 就會 很好 尺寸 前言   前陣子,產品跑過來問我現有的模板中沒有圖片模板,需要添加一個圖片模板;然而,他要求圖片在展示區最好能夠實現隨著窗口的變化而自動按圖片比例等比縮放,並且居中展示圖片。我當時想著,拋開技術實現層面,圖

業務復雜的解決流程

控制 blog 修改 流程 block 今天 原子性 道理 復雜 一、遇到的問題:   今天在搬磚的時候,分類情況比較多,多次修改後,總是會報出一些問題   大概條件有兩種摻雜:表類型、列包含標誌    二、解決思路:   1、縮小到最小變化量,首先確定有兩種

業務上線出現的故障---微信轉發信用卡邀約開卡業務出現高几率失敗情況

具體情況: 在一次協助xx銀行上線時,遇到這樣的一個問題:此業務功能是將信用卡邀約開卡的頁面轉發給微信好友,好友點開連結後可以直接跳轉到申請信用卡的頁面; 出現的問題是經過大量的測試,有50%的是友點開頁面後還是一個邀請信用卡開卡的頁面。 故障原因是: 程式碼的問題 是soc

報錯Android ClassNotFoundException: Didn't find class on path

場景 專案上線後,使用者反饋,安裝完開啟應用就顯示“應用已停止執行”,改錯誤發生在5.0以上的手機,經測試6.7.8.均沒有問題,推測是6.0以下會有這個問題。除錯時,報錯如下: 提示類找不到,

踩坑使用Navicat連線Mysql8.0.11

  MySQL8.0正式版8.0.11已釋出,官方表示MySQL8要比MySQL5.7塊兩倍,同事還帶來了大量的改進和更快的效能!   從MySQL5.7升級到MySQL8.0僅支援通過使用in-place方式進行升級,並且不支援從MySQL8.0降級到MySQ

業務程式碼的重構

業務需求 收聽任務側面的提示語根據使用者收聽時長和任務的完成情況進行動態提示。 流程圖如下 客戶端的虛擬碼 重構前程式碼點評

生產事故30萬單就這樣沒了!

# 背景 你好,我是彤哥。 昨天晚上下班回家,在地鐵上,老大突然打來電話,B系統生產環境響應緩慢,影響了A系統的使用,幾萬小哥收不了單,大概有30萬單卡住了,你去幫忙定位一下。 我8點半左右到家,立馬上線入會。 # 重啟 我入會的時候,已經有同事在幫忙定位了,俗話說的好,重啟能解決80%的問題,如果

,徹底理解JavaScript拷貝

## 導語 這一次,通過本文徹底理解JavaScript深拷貝! 閱讀本文前可以先思考三個問題: - JS世界裡,資料是如何儲存的? - 深拷貝和淺拷貝的區別是什麼? - 如何寫出一個真正合格的深拷貝? 本文會一步步解答這三個問題 ## 資料是如何儲存的 先看一個問題,下面這段程式碼的輸出結果是什麼

誤刪除系列有道筆記資料恢復過程

1. 環境: 三個有道雲筆記客戶端分別是windows7、iphone、MAC   2. 事件發生過程: 今天早上在win7的有道雲筆記下記錄一些筆記時,出現誤刪除操作,當時我使用的是它的markdown模組進行記錄操作,剛好一個文件記錄了我一週一些學習過程的筆記,就在某一個時刻敲鍵盤時,

2018第一發【Advanced Installer】打包之旅

4.0 config文件 cli 簡單 官網 class 用法 process file 一、前言 2017年最後幾天,你們都高高興興的跨年,博主還在加班制作.net安裝包。因為年前要出來第一版的安裝包,所以博主是加班加點啊。本來想用VS自帶的制作工具,不過用過的人都知道

Yii2尷尬的bug

警告 use 一次 .com eight TP ger 怎麽 ted 創建一個文章模塊,寫完添加動作之後,分配到視圖,發現報錯: Exception (Not Supported) ‘yii\base\NotSupportedException‘ with messag

[Windows10]修復註冊表相關血案該文件沒有與之關聯的應用來執行該操作。請安裝應用,若已經安裝應用,請在“默認應用設置”頁面中創建關聯。

src 相關 overflow 還在 一次 註冊表 forum sin 嘗試 今天閑得蛋疼清理了一下右鍵菜單,於是在之後某時刻使用Everything的“雙擊路徑列打開目錄”功能時發現異常: [Window Title] Everything

OpenStack報錯No tenant network is available for allocation

建立selfservice網路時報錯: [root@controller ~]# openstack network create selfservice Error while executing command: HttpException: Unknown error, {"Neu

MySQL基礎系列利用儲存過程實現2600萬資料水平分表

日常開發中我們經常會遇到大表的情況,所謂的大表是指儲存了百萬級乃至千萬級條記錄的表。這樣的表過於龐大,導致資料庫在查詢和插入的時候耗時太長,效能低下,如果涉及聯合查詢的情況,效能會更加糟糕。分表的目的就是減少資料庫的負擔,提高資料庫的效率,通常點來講就是提高表的增刪改查效率,本文將介紹我

邊鋒客戶端(C++)實習生面試

第一篇部落格,也是第一次寫部落格,就記一次準備的不是很充分的面試吧。 總體來說應該不算難,還是挺簡單的。 到那邊後先填一張資訊表,然後hr會過來讓你做一套試卷,選擇題和簡答題。大概包括引用、運算子過載、模板類的靜態變數、建構函式等相關,選擇題比較基礎。簡答題包括lambd

JAVA基礎第四章-集合框架Collection篇 JAVA基礎第一章-初識java JAVA基礎第二章-java三大特性封裝、繼承、多型 JAVA基礎第三章-類與物件、抽象類、介面 list迴圈刪除元素的突發事件!

 業內經常說的一句話是不要重複造輪子,但是有時候,只有自己造一個輪子了,才會深刻明白什麼樣的輪子適合山路,什麼樣的輪子適合平地! 我將會持續更新java基礎知識,歡迎關注。   往期章節: JAVA基礎第一章-初識java JAVA基礎第二章-java三大特性

大坑SpringBoot+Mybatis專案中,配置檔案中的修改了SQL語句後不生效

問題:原是SSM框架專案,轉移到SpringBoot+Mybatis,使用的是C3P0連線資料庫。轉移到SpringBoot後的專案,我修改了xml配置檔案中的查詢sql語句,也就是增加了一個查詢欄位,無論是在前端頁面測試,還是使用單元測試時候,我修改後的SQL就是不生效,查

掛馬清除經歷處理一個利用thinkphp5遠端程式碼執行漏洞挖礦的木馬

昨天發現 一臺伺服器突然慢了 top 顯示 幾個程序100%以上的cpu使用 執行命令為 : /tmp/php  -s /tmp/p2.conf 基本可以確定是被掛馬了 下一步確定來源  last 沒有登陸記錄 先幹掉這幾個程序,但是幾分鐘之後又出

C#靜態變數的坑

有時為了加快顯示的速度,會將相對不變的資料給快取起來。在快取起來時,比較直接的方法是放到靜態變數中。 在專案中,由於需要從伺服器中撈起較多的資料,一般要3-5秒,網路稍慢的時候會更長時間。而每次顯示介面時,都需要這麼長的時間,顯然是很不友好的。於是,就將這些資料放到了靜態變