什麼是 淺拷貝(Shallow Copy) 與 深拷貝(Deep copy)
Shallow Copy 是軟體工程師經常遇到的問題,要解決這個問題之前我們先來了解問題的原因
在大部分的程式語言中;物件型別的都是pass by reference 這意味著在記憶體stack中存的是這個物件的「地址」,物件本身的值則存在於heap中。
淺拷貝發生的情況
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 物件複製方法比較