C# .NET 中的 淺拷貝(Shallow Copy) 和 深拷貝(Deep copy)

什麼是 淺拷貝(Shallow Copy) 與 深拷貝(Deep copy)

Shallow Copy 是軟體工程師經常遇到的問題,要解決這個問題之前我們先來了解問題的原因

在大部分的程式語言中;物件型別的都是pass by reference 這意味著在記憶體stack中存的是這個物件的「地址」,物件本身的值則存在於heap中。

淺拷貝發生的情況

Shallow Copy
var obj1 = new Object();
var obj2 = obj1;
obj2.Name = "Change"

在這邊我們可以看到運用2個變數分別是obj1 與 obj2 當我們在操作obj2時這時候會發生什麼事呢?

obj1的Name屬性也一起被賦予Change 我們以為用變數obj2 複製了一個新的物件

但事實上 obj1 與 obj2 都指向同一個物件,因為在stack中所儲存的變數都是同一個地址。



這聽起來很困惑,感覺起來跟上面那隻貓貓一樣的悲傷,因為這和我們設想的不一樣

抱歉讓容許我再用更容易理解的說法向您解釋

假如有一天你在東區的街道逛街有人向你發送了一張名片,上面的地址是:台北市中正區重慶南路一段122

這時你的朋友在高雄的愛河散步也剛好拿到一張名片,上面的地址是:台北市中正區重慶南路一段122號

如果你們都按造名片上的地址會前往到同一個地點嗎?

是的你們最終都會來到總統府前面,為什麼?

因為名片上的地址是一樣的都指向總統府

這也是一開始我們說的參考型別的本身在heap中,變數只是拿到了一段地址!

以上的例子很簡單,在實際的專案中會遇到更複雜的狀況

專案中發生淺拷貝的狀況

淺拷貝的出現並不是那麼的值觀,當發現這個狀況時往往是Bug出現的時候

最近在專案中發生了一個狀況,有一個複製活動的功能,但當活動被複製以後舊的活動ID竟然也會被賦予新活動的ID,當下發現這個狀況的時候就意識到大概是發生了淺拷貝的狀況

code大概是這樣的

 public Content GetContent(int id)
        {
            Content content = null;
            var info = Getinfo(id);
            var list = GetList(id);
            if (info != null && list != null && list.Any())
            {
                content = new content()
                {
                    info = maFlowInfo,
                    list = list.ToList()
                };
            }
            return content;

會用舊活動的id 去找到 info 跟 list 並且創建一個新的content 物件將其回傳

而list則是List<Campaign>也是另一個物件

最終我們拿著這個content去做操作,但卻影響到了我們舊有的活動,因為此時新的content其實引用的是舊的活動,那這時候我們應該怎麼辦呢? 我們應該進行深拷貝(Deep copy) !

C# .NET中的深拷貝(Deep copy)

C# .NET中最簡單解決淺拷貝達到深拷貝的方式是利用Newtonsoft.Json中提供的序列化與反序列化來對物件進行複製,再拿這個複製出來的副本去操作,這樣可以有效的讓新的物件與舊的物件做隔離

也是微軟目前較為推薦的做法

 public Content GetContent(int id)
        {
            Content content = null;
            var info = Getinfo(id);
            var list = GetList(id);
            if (info != null && list != null && list.Any())
            {
                var copyInfo = JsonConvert.SerializeObject(info);
                var copyList = JsonConvert.SerializeObject(list);
                content = new content()
                {
                    info = JsonConvert.DeserializeObject<Info>(copyInfo),
                    list = JsonConvert.DeserializeObject<List>(list)
                };
            }
            return content;

我們利用了Newtonsoft.Json套件輕鬆地做到了序列化與反序列化進而得到了一個副本,這是在解決淺拷貝的問題時有效又簡單的作法,但這樣的做法有沒有什麼限制呢?

有的!

1.首先是要被複製的物件是可以被序列化的

2.物件內的屬性不能是存取修飾詞不能為private

另外這樣的複製方法在效能上可能會占用比較多,但目前在.NET的專案中如果沒有以上的狀況的話都會建議用這個方式進行複製。

如果對計算機科學中的複製感到有興趣可以參考以下連結

深度了解淺拷貝(Shallow Copy) VS 深度拷貝(Deep Copy)得部分 by Nick Huang

黑暗執行緒大的JavaScript 物件複製方法比較

Visited 458 times, 1 visit(s) today

Leave A Comment

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *